diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f906731af4..435e40e9dc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,6 +11,7 @@ This document describes how you can contribute to Telegram Desktop. Please read * [Pull upstream changes into your fork regularly](#pull-upstream-changes-into-your-fork-regularly) * [How to get your pull request accepted](#how-to-get-your-pull-request-accepted) * [Keep your pull requests limited to a single issue](#keep-your-pull-requests-limited-to-a-single-issue) + * [Squash your commits to a single commit](#squash-your-commits-to-a-single-commit) * [Don't mix code changes with whitespace cleanup](#dont-mix-code-changes-with-whitespace-cleanup) * [Keep your code simple!](#keep-your-code-simple) * [Test your changes!](#test-your-changes) @@ -107,6 +108,21 @@ Pull requests should be as small/atomic as possible. Large, wide-sweeping change * If you are making spelling corrections in the docs, don't modify other files. * If you are adding new functions don't '*cleanup*' unrelated functions. That cleanup belongs in another pull request. +#### Squash your commits to a single commit + +To keep the history of the project clean, you should make one commit per pull request. +If you already have multiple commits, you can add the commits together (squash them) with the following commands in Git Bash: + +1. Open `Git Bash` (or `Git Shell`) +2. Enter following command to squash the recent {N} commits: `git reset --soft HEAD~{N} && git commit` (replace `{N}` with the number of commits you want to squash) +3. Press i to get into Insert-mode +4. Enter the commit message of the new commit (and add the [signature](#sign-your-work) at the and) +5. After adding the message, press ESC to get out of the Insert-mode +6. Write `:wq` and press Enter to save the new message or write `:q!` to discard your changes +7. Enter `git push --force` to push the new commit to the remote repository + +For example, if you want to squash the last 5 commits, use `git reset --soft HEAD~5 && git commit` + ### Don't mix code changes with whitespace cleanup If you change two lines of code and correct 200 lines of whitespace issues in a file the diff on that pull request is functionally unreadable and will be **rejected**. Whitespace cleanups need to be in their own pull request. diff --git a/MSVC.md b/MSVC.md index e3bdafcdf6..a9f3453094 100644 --- a/MSVC.md +++ b/MSVC.md @@ -141,7 +141,7 @@ Open **VS2015 x86 Native Tools Command Prompt.bat** (should be in **Start Menu > PKG_CONFIG_PATH="/mingw64/lib/pkgconfig:$PKG_CONFIG_PATH" - ./configure --toolchain=msvc --disable-programs --disable-everything --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=wavpack --enable-decoder=opus --enable-decoder=vorbis --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-decoder=flac --enable-encoder=libopus --enable-parser=aac --enable-parser=aac_latm --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-parser=flac --enable-demuxer=aac --enable-demuxer=wav --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=mov --enable-demuxer=flac --enable-muxer=ogg --enable-muxer=opus --extra-ldflags="-libpath:/d/TBuild/Libraries/opus/win32/VS2010/Win32/Release celt.lib silk_common.lib silk_float.lib" + ./configure --toolchain=msvc --disable-programs --disable-doc --disable-everything --disable-w32threads --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=flac --enable-decoder=gif --enable-decoder=h264 --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=mpeg4 --enable-decoder=msmpeg4v2 --enable-decoder=msmpeg4v3 --enable-decoder=wavpack --enable-decoder=opus --enable-decoder=vorbis --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-encoder=libopus --enable-hwaccel=h264_d3d11va --enable-hwaccel=h264_dxva2 --enable-parser=aac --enable-parser=aac_latm --enable-parser=flac --enable-parser=h264 --enable-parser=mpeg4video --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-demuxer=aac --enable-demuxer=flac --enable-demuxer=gif --enable-demuxer=h264 --enable-demuxer=mov --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=wav --enable-muxer=ogg --enable-muxer=opus --extra-ldflags="-libpath:/d/TBuild/Libraries/opus/win32/VS2010/Win32/Release celt.lib silk_common.lib silk_float.lib" make make install @@ -169,7 +169,7 @@ and run #####Apply the patch cd qtbase && git apply ../../../tdesktop/Telegram/_qtbase_5_5_1_patch.diff && cd .. - + #####Install Windows SDKs diff --git a/QTCREATOR.md b/QTCREATOR.md index da103b0488..d963f9badf 100644 --- a/QTCREATOR.md +++ b/QTCREATOR.md @@ -44,13 +44,24 @@ Download [opus-1.1 sources](http://downloads.xiph.org/releases/opus/opus-1.1.tar ####FFmpeg -Download sources [ffmpeg-2.6.3.tar.bz2](http://ffmpeg.org/releases/ffmpeg-2.6.3.tar.bz2) from https://www.ffmpeg.org/download.html, extract to **/home/user/TBuild/Libraries** to have **/home/user/TBuild/Libraries/ffmpeg-2.6.3**, go to **/home/user/TBuild/Libraries/ffmpeg-2.6.3** and run +In Terminal go to **/home/user/TBuild/Libraries** and run + + git clone git://anongit.freedesktop.org/git/libva + cd libva + ./autogen.sh --enable-static + make + sudo make install + cd .. + + git clone https://github.com/FFmpeg/FFmpeg.git ffmpeg + cd ffmpeg + git checkout release/2.8 sudo apt-get update sudo apt-get -y --force-yes install autoconf automake build-essential libass-dev libfreetype6-dev libgpac-dev libsdl1.2-dev libtheora-dev libtool libva-dev libvdpau-dev libvorbis-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev pkg-config texi2html zlib1g-dev sudo apt-get install yasm - ./configure --prefix=/usr/local --disable-programs --disable-everything --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=wavpack --enable-decoder=opus --enable-decoder=vorbis --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-decoder=flac --enable-encoder=libopus --enable-parser=aac --enable-parser=aac_latm --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-parser=flac --enable-demuxer=aac --enable-demuxer=wav --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=mov --enable-demuxer=flac --enable-muxer=ogg --enable-muxer=opus + ./configure --prefix=/usr/local --disable-programs --disable-doc --disable-pthreads --disable-mmx --disable-everything --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=flac --enable-decoder=gif --enable-decoder=h264 --enable-decoder=h264_vdpau --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=mpeg4 --enable-decoder=mpeg4_vdpau --enable-decoder=msmpeg4v2 --enable-decoder=msmpeg4v3 --enable-decoder=opus --enable-decoder=vorbis --enable-decoder=wavpack --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-encoder=libopus --enable-hwaccel=h264_vaapi --enable-hwaccel=h264_vdpau --enable-hwaccel=mpeg4_vaapi --enable-hwaccel=mpeg4_vdpau --enable-parser=aac --enable-parser=aac_latm --enable-parser=flac --enable-parser=h264 --enable-parser=mpeg4video --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-demuxer=aac --enable-demuxer=flac --enable-demuxer=gif --enable-demuxer=h264 --enable-demuxer=mov --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=wav --enable-muxer=ogg --enable-muxer=opus make sudo make install diff --git a/Telegram/Build.bat b/Telegram/Build.bat index 33c14d499e..650a937ee5 100644 --- a/Telegram/Build.bat +++ b/Telegram/Build.bat @@ -31,6 +31,10 @@ set "DeployPath=%ReleasePath%\deploy\%AppVersionStrMajor%\%AppVersionStrFull%" set "SignPath=..\..\TelegramPrivate\Sign.bat" if %BetaVersion% neq 0 ( + if exist %DeployPath%\ ( + echo Deploy folder for version %AppVersionStr% already exists! + exit /b 1 + ) if exist %ReleasePath%\%BetaKeyFile% ( echo Beta version key file for version %AppVersion% already exists! exit /b 1 @@ -40,19 +44,21 @@ if %BetaVersion% neq 0 ( echo Deploy folder for version %AppVersionStr%.dev already exists! exit /b 1 ) + if exist %ReleasePath%\deploy\%AppVersionStrMajor%\%AppVersionStr%\ ( + echo Deploy folder for version %AppVersionStr% already exists! + exit /b 1 + ) if exist %ReleasePath%\tupdate%AppVersion% ( echo Update file for version %AppVersion% already exists! exit /b 1 ) ) - -if exist %ReleasePath%\deploy\%AppVersionStrMajor%\%AppVersionStr%\ ( - echo Deploy folder for version %AppVersionStr% already exists! - exit /b 1 -) - cd SourceFiles\ -rem copy telegram.qrc /B+,,/Y +if "%1" == "fast" ( + echo Skipping touching of telegram.qrc.. +) else ( + copy telegram.qrc /B+,,/Y +) cd ..\ if %errorlevel% neq 0 goto error @@ -149,7 +155,7 @@ xcopy %DeployPath%\%PortableFile% %FinalDeployPath%\ if %BetaVersion% equ 0 ( xcopy %DeployPath%\%SetupFile% %FinalDeployPath%\ ) else ( - xcopy %DeployPath%\%BetaKeyFile% %FinalDeployPath%\ + xcopy %DeployPath%\%BetaKeyFile% %FinalDeployPath%\ /Y ) xcopy %DeployPath%\Telegram.pdb %FinalDeployPath%\ xcopy %DeployPath%\Updater.exe %FinalDeployPath%\ diff --git a/Telegram/FixMake.sh b/Telegram/FixMake.sh index 77a933e1a1..21abada2b2 100755 --- a/Telegram/FixMake.sh +++ b/Telegram/FixMake.sh @@ -27,4 +27,6 @@ Replace '\-lopenal' '\/usr\/local\/lib\/libopenal\.a' Replace '\-lavformat' '\/usr\/local\/lib\/libavformat\.a' Replace '\-lavcodec' '\/usr\/local\/lib\/libavcodec\.a' Replace '\-lswresample' '\/usr\/local\/lib\/libswresample\.a' +Replace '\-lswscale' '\/usr\/local\/lib\/libswscale\.a' Replace '\-lavutil' '\/usr\/local\/lib\/libavutil\.a' +Replace '\-lva' '\/usr\/local\/lib\/libva\.a' diff --git a/Telegram/FixMake32.sh b/Telegram/FixMake32.sh index 4b7591ab5e..833bca6c6d 100755 --- a/Telegram/FixMake32.sh +++ b/Telegram/FixMake32.sh @@ -27,4 +27,6 @@ Replace '\-lopenal' '\/usr\/local\/lib\/libopenal\.a' Replace '\-lavformat' '\/usr\/local\/lib\/libavformat\.a' Replace '\-lavcodec' '\/usr\/local\/lib\/libavcodec\.a' Replace '\-lswresample' '\/usr\/local\/lib\/libswresample\.a' +Replace '\-lswscale' '\/usr\/local\/lib\/libswscale\.a' Replace '\-lavutil' '\/usr\/local\/lib\/libavutil\.a' +Replace '\-lva' '\/usr\/local\/lib\/libva\.a' diff --git a/Telegram/Resources/lang.strings b/Telegram/Resources/lang.strings index fd065b167a..79aee3ad76 100644 --- a/Telegram/Resources/lang.strings +++ b/Telegram/Resources/lang.strings @@ -48,6 +48,19 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_month11" = "November"; "lng_month12" = "December"; +"lng_month1_small" = "Jan"; +"lng_month2_small" = "Feb"; +"lng_month3_small" = "Mar"; +"lng_month4_small" = "Apr"; +"lng_month5_small" = "May"; +"lng_month6_small" = "Jun"; +"lng_month7_small" = "Jul"; +"lng_month8_small" = "Aug"; +"lng_month9_small" = "Sep"; +"lng_month10_small" = "Oct"; +"lng_month11_small" = "Nov"; +"lng_month12_small" = "Dec"; + "lng_weekday1" = "Mon"; "lng_weekday2" = "Tue"; "lng_weekday3" = "Wed"; @@ -66,6 +79,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_month_day" = "{month} {day}"; "lng_month_day_year" = "{month} {day}, {year}"; +"lng_month_year" = "{month}, {year}"; "lng_box_ok" = "OK"; @@ -108,6 +122,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_server_error" = "Internal server error."; "lng_flood_error" = "Too many tries. Please try again later."; +"lng_gif_error" = "An error has occured while reading GIF animation :("; "lng_deleted" = "Unknown"; "lng_deleted_message" = "Deleted message"; @@ -470,8 +485,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_create_channel_crop" = "Select a square area for channel photo"; "lng_failed_add_participant" = "Could not add user. Please try again later."; -"lng_failed_add_not_mutual" = "Sorry, if a person leaves a group, only a\nmutual contact can bring them back\n(they need to have your phone\nnumber, and you need theirs)."; -"lng_failed_add_not_mutual_channel" = "Sorry, if a person leaves a channel, only a\nmutual contact can bring them back\n(they need to have your phone\nnumber, and you need theirs)."; +"lng_failed_add_not_mutual" = "Sorry, if a person leaves a group, only a mutual contact can bring them back (they need to have your phone number, and you need theirs)."; +"lng_failed_add_not_mutual_channel" = "Sorry, if a person leaves a channel, only a mutual contact can bring them back (they need to have your phone number, and you need theirs)."; "lng_sure_delete_contact" = "Are you sure, you want to delete {contact} from your contact list?"; "lng_sure_delete_history" = "Are you sure, you want to delete all message history with {contact}?\n\nThis action cannot be undone."; @@ -483,7 +498,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_sure_delete_group" = "Are you sure, you want to delete this group? All members will be removed and all messages will be lost."; "lng_message_empty" = "Empty Message"; -"lng_media_unsupported" = "Media Unsupported"; +"lng_message_unsupported" = "This message is not supported by your version of Telegram Desktop. Please update to the last version in Settings or install it from {link}"; "lng_action_add_user" = "{from} added {user}"; "lng_action_add_users_many" = "{from} added {users}"; @@ -561,6 +576,14 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_media_video" = "Video file"; "lng_media_audio" = "Voice message"; +"lng_media_auto_settings" = "Automatic media download settings"; +"lng_media_auto_photo" = "Automatic photo download"; +"lng_media_auto_audio" = "Automatic voice message download"; +"lng_media_auto_gif" = "Automatic GIF download"; +"lng_media_auto_private_chats" = "Private chats"; +"lng_media_auto_groups" = "Groups and channels"; +"lng_media_auto_play" = "Autoplay"; + "lng_emoji_category0" = "Frequently used"; "lng_emoji_category1" = "People"; "lng_emoji_category2" = "Nature"; @@ -571,8 +594,14 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_emoji_category7" = "Symbols & Flags"; "lng_switch_stickers" = "Stickers"; +"lng_switch_stickers_gifs" = "GIFs & Stickers"; "lng_switch_emoji" = "Emoji"; +"lng_saved_gifs" = "Saved GIFs"; +"lng_inline_bot_results" = "Results from {inline_bot}"; +"lng_inline_bot_no_results" = "No results"; +"lng_inline_bot_via" = "via {inline_bot}"; + "lng_box_remove" = "Remove"; "lng_custom_stickers" = "Custom stickers"; @@ -664,6 +693,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_save_file" = "Save file"; "lng_save_downloaded" = "{ready} / {total} {mb}"; "lng_duration_and_size" = "{duration}, {size}"; +"lng_duration_played" = "{played} / {duration}"; +"lng_date_and_duration" = "{date}, {duration}"; "lng_choose_images" = "Choose images"; "lng_context_view_profile" = "View profile"; @@ -699,6 +730,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_context_delete_file" = "Delete File"; "lng_context_close_file" = "Close File"; "lng_context_copy_text" = "Copy Text"; +"lng_context_save_gif" = "Save GIF"; "lng_context_to_msg" = "Go To Message"; "lng_context_reply_msg" = "Reply"; "lng_context_forward_msg" = "Forward Message"; @@ -762,7 +794,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_selected_delete" = "Delete"; "lng_selected_forward" = "Forward"; "lng_selected_count" = "{count:_not_used_|# message|# messages}"; -"lng_selected_cancel_sure_this" = "Do you want to cancel this upload?"; +"lng_selected_cancel_sure_this" = "Cancel uploading?"; +"lng_selected_upload_stop" = "Stop"; "lng_selected_delete_sure_this" = "Do you want to delete this message?"; "lng_selected_delete_sure" = "Do you want to delete {count:_not_used_|# message|# messages}?"; "lng_box_delete" = "Delete"; @@ -801,7 +834,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_new_version_wrap" = "Telegram Desktop was updated to version {version}\n\n{changes}\n\nFull version history is available here:\n{link}"; "lng_new_version_minor" = "— Bug fixes and other minor improvements"; -"lng_new_version_text" = "— Sticker management: manually rearrange your sticker packs, pack order is now synced across all your devices\n— Click and hold on a sticker to preview it before sending\n— New context menu for chats in chats list\n— Support for all existing emoji"; +"lng_new_version_text" = "GIF revolution: 10x faster sending and downloading, autoplay, save your favorite GIFs to a dedicated tab on the sticker panel.\n\nMore about GIFs:\n{gifs_link}\n\nInline bots: A new way to add bot content to any chat. Type a bot's username and your query in the text field to get instant results and send them to your chat partner. Try typing “@gif dog” in your next chat. Sample bots: @gif, @wiki, @bing, @vid, @bold.\n\nMore about inline bots:\n{bots_link}\n\nAlso in this release: New cute design for media, automatic download settings for photos, voice messages and GIFs."; "lng_menu_insert_unicode" = "Insert Unicode control character"; diff --git a/Telegram/Resources/style.txt b/Telegram/Resources/style.txt index 81fc1f8e14..a1f7a785cd 100644 --- a/Telegram/Resources/style.txt +++ b/Telegram/Resources/style.txt @@ -554,8 +554,8 @@ taDefFlat: flatTextarea { font: inpDefFont; cursor: cursor(text); - phColor: #AAA; - phFocusColor: #CCC; + phColor: #999; + phFocusColor: #AAA; phAlign: align(topleft); phPos: point(2px, 0px); phShift: 50px; @@ -776,8 +776,8 @@ setClose: iconedButton(btnDefIconed) { height: 43px; } setClosePos: point(32px, 32px); -setPhotoImg: sprite(0px, 220px, 120px, 120px); -setOverPhotoImg: sprite(122px, 220px, 120px, 120px); +setPhotoImg: sprite(0px, 218px, 120px, 120px); +setOverPhotoImg: sprite(122px, 218px, 120px, 120px); setPhotoDuration: 150; setPadding: 26px; @@ -1015,25 +1015,25 @@ msgPadding: margins(13px, 7px, 13px, 8px); msgMargin: margins(13px, 4px, 53px, 4px); msgLnkPadding: 2px; // for media open / save links msgBorder: #f0f0f0; -msgOutBg: #effdde; msgInBg: #fff; -msgOutSelectBg: #b7dbdb; -msgInSelectBg: #c2dcf2; // #358cd4 with 30% opacity +msgInBgSelected: #c2dcf2; // #358cd4 with 30% opacity +msgOutBg: #effdde; +msgOutBgSelected: #b7dbdb; msgSelectOverlay: #358cd44c; msgStickerOverlay: #358cd47f; -msgOutServiceColor: #3a8e26; -msgInServiceColor: #0e7acd; -msgOutServiceSelColor: #367570; -msgInServiceSelColor: #0e7acd; +msgInServiceFg: #0e7acd; +msgInServiceFgSelected: #0e7acd; +msgOutServiceFg: #3a8e26; +msgOutServiceFgSelected: #367570; msgShadow: 2px; msgInShadow: #748ea229; +msgInShadowSelected: #548dbb29; msgOutShadow: #3ac34740; -msgInSelectShadow: #548dbb29; -msgOutSelectShadow: #37a78e40; -msgInDateColor: #a0acb6; -msgOutDateColor: #6cc264; -msgInSelectDateColor: #6a9cc5; -msgOutSelectDateColor: #50a79c; +msgOutShadowSelected: #37a78e40; +msgInDateFg: #a0acb6; +msgInDateFgSelected: #6a9cc5; +msgOutDateFg: #6cc264; +msgOutDateFgSelected: #50a79c; msgReplyPadding: margins(6px, 6px, 11px, 6px); msgReplyBarPos: point(1px, 0px); @@ -1086,11 +1086,12 @@ msgDateDelta: point(2px, 5px); msgDateImgDelta: 4px; msgDateImgColor: #fff; msgDateImgBg: #00000054; -msgDateImgSelectBg: #1c4a7187; +msgDateImgBgOver: #00000074; +msgDateImgBgSelected: #1c4a7187; msgDateImgPadding: point(8px, 2px); msgDateImgCheckSpace: 4px; -msgDogImg: sprite(213px, 93px, 126px, 126px); +msgDogImg: sprite(216px, 92px, 126px, 126px); historyPadding: 10px; collapseButton: flatButton(btnDefFlat) { @@ -1117,7 +1118,7 @@ defaultTextStyle: textStyle { linkFg: btnYesColor; linkFgDown: btnYesHover; monoFg: #777; - selectBg: msgInSelectBg; + selectBg: msgInBgSelected; selectOverlay: msgSelectOverlay; lineHeight: 0px; } @@ -1135,12 +1136,12 @@ serviceTextStyle: textStyle(defaultTextStyle) { } inTextStyle: textStyle(defaultTextStyle) { monoFg: #4e7391; - selectBg: msgInSelectBg; + selectBg: msgInBgSelected; selectOverlay: msgSelectOverlay; } outTextStyle: textStyle(defaultTextStyle) { monoFg: #469165; - selectBg: msgOutSelectBg; + selectBg: msgOutBgSelected; selectOverlay: msgSelectOverlay; } medviewSaveAsTextStyle: textStyle(defaultTextStyle) { @@ -1165,52 +1166,109 @@ introErrLabelTextStyle: textStyle(defaultTextStyle) { lineHeight: 27px; } -mediaMaxWidth: 250px; -mediaFont: font(fsize); -mediaPadding: margins(7px, 6px, 7px, 6px); +mediaPadding: margins(0px, 0px, 0px, 0px);//1px, 1px, 1px, 1px);//2px, 2px, 2px, 2px); +mediaCaptionSkip: 5px; +mediaHeaderSkip: 5px; mediaThumbSize: 48px; mediaNameTop: 3px; mediaDetailsShift: 3px; -mediaDocOutImg: sprite(6px, 146px, 48px, 48px); -mediaDocInImg: sprite(56px, 146px, 48px, 48px); -mediaAudioOutImg: sprite(106px, 146px, 48px, 48px); -mediaAudioInImg: sprite(156px, 146px, 48px, 48px); -mediaMusicOutImg: sprite(322px, 345px, 48px, 48px); -mediaMusicInImg: sprite(322px, 395px, 48px, 48px); -mediaPlayOutImg: sprite(122px, 341px, 48px, 48px); -mediaPlayInImg: sprite(172px, 341px, 48px, 48px); -mediaPauseOutImg: sprite(222px, 341px, 48px, 48px); -mediaPauseInImg: sprite(272px, 341px, 48px, 48px); -mediaInColor: msgInDateColor; -mediaOutColor: msgOutDateColor; -mediaInSelectColor: msgInSelectDateColor; -mediaOutSelectColor: msgOutSelectDateColor; -mediaOutUnreadColor: #6aad60; -mediaOutUnreadSelectColor: #5aa382; -mediaInUnreadColor: #999; -mediaInUnreadSelectColor: #7b95aa; -mediaUnreadSize: 4px; +mediaInFg: msgInDateFg; +mediaInFgSelected: msgInDateFgSelected; +mediaOutFg: msgOutDateFg; +mediaOutFgSelected: msgOutDateFgSelected; +mediaInUnreadFg: #999; +mediaInUnreadFgSelected: #7b95aa; +mediaOutUnreadFg: #6aad60; +mediaOutUnreadFgSelected: #5aa382; +mediaUnreadSize: 7px; mediaUnreadSkip: 5px; -mediaSaveDelta: 14px; // between bubble and download -mediaSaveButton: flatButton(btnDefFlat) { - color: #507da2; - overColor: #507da2; - downColor: #507da2; +mediaUnreadTop: 6px; - bgColor: white; - overBgColor: overBg; - downBgColor: overBg; - - width: -28px; - height: 34px; - - textTop: 7px; - overTextTop: 7px; - downTextTop: 8px; - - font: font(fsize); - overFont: font(fsize); +mediaInStyle: textStyle(defaultTextStyle) { + linkFg: mediaInFg; + linkFgDown: mediaInFg; } +mediaInStyleSelected: textStyle(defaultTextStyle) { + linkFg: mediaInFgSelected; + linkFgDown: mediaInFgSelected; +} + +msgFileRedColor: #e47272; +msgFileYellowColor: #efc274; +msgFileGreenColor: #61b96e; +msgFileBlueColor: #72b1df; +msgFileRedDark: #cd5b5e; +msgFileYellowDark: #e6a561; +msgFileGreenDark: #4da859; +msgFileBlueDark: #5c9ece; +msgFileRedOver: #c35154; +msgFileYellowOver: #dc9c5a; +msgFileGreenOver: #44a050; +msgFileBlueOver: #5294c4; +msgFileRedSelected: #9f6a82; +msgFileYellowSelected: #b19d84; +msgFileGreenSelected: #46a07e; +msgFileBlueSelected: #5099d0; + +msgFileMenuSize: size(36px, 36px); +msgFileSize: 44px; +msgFilePadding: margins(14px, 12px, 11px, 12px); +msgFileThumbSize: 72px; +msgFileThumbPadding: margins(10px, 10px, 14px, 10px); +msgFileThumbNameTop: 12px; +msgFileThumbStatusTop: 32px; +msgFileThumbLinkTop: 60px; +msgFileThumbLinkInFg: #3da5e0; +msgFileThumbLinkInFgSelected: #3da5e0; +msgFileThumbLinkOutFg: #5eba5b; +msgFileThumbLinkOutFgSelected: #31a298; +msgFileNameTop: 16px; +msgFileStatusTop: 37px; +msgFileMinWidth: 294px; +msgFileInBg: #59b6eb; +msgFileInBgOver: #4eade3; +msgFileInBgSelected: #51a3d3; +msgFileOutBg: #78c67f; +msgFileOutBgOver: #6bc272; +msgFileOutBgSelected: #5fb389; + +msgFileOutImage: sprite(0px, 146px, 18px, 18px); +msgFileOutImageSelected: sprite(18px, 146px, 18px, 18px); +msgFileInImage: sprite(0px, 164px, 18px, 18px); +msgFileInImageSelected: sprite(18px, 164px, 18px, 18px); +msgFileOutFile: sprite(36px, 146px, 18px, 18px); +msgFileOutFileSelected: sprite(54px, 146px, 18px, 18px); +msgFileInFile: sprite(36px, 164px, 18px, 18px); +msgFileInFileSelected: sprite(54px, 164px, 18px, 18px); +msgFileOutDownload: sprite(72px, 142px, 14px, 20px); +msgFileOutDownloadSelected: sprite(86px, 142px, 14px, 20px); +msgFileInDownload: sprite(72px, 162px, 14px, 20px); +msgFileInDownloadSelected: sprite(86px, 162px, 14px, 20px); +msgFileOutCancel: sprite(100px, 147px, 16px, 16px); +msgFileOutCancelSelected: sprite(116px, 147px, 16px, 16px); +msgFileInCancel: sprite(100px, 165px, 16px, 16px); +msgFileInCancelSelected: sprite(116px, 165px, 16px, 16px); +msgFileOutPause: sprite(132px, 147px, 14px, 16px); +msgFileOutPauseSelected: sprite(146px, 147px, 14px, 16px); +msgFileInPause: sprite(132px, 165px, 14px, 16px); +msgFileInPauseSelected: sprite(146px, 165px, 14px, 16px); +msgFileOutPlay: sprite(160px, 146px, 20px, 18px); +msgFileOutPlaySelected: sprite(180px, 146px, 20px, 18px); +msgFileInPlay: sprite(160px, 164px, 20px, 18px); +msgFileInPlaySelected: sprite(180px, 164px, 20px, 18px); + +msgFileRed: sprite(0px, 425px, 20px, 20px); +msgFileYellow: sprite(20px, 425px, 20px, 20px); +msgFileGreen: sprite(40px, 425px, 20px, 20px); +msgFileBlue: sprite(60px, 425px, 20px, 20px); + +msgFileOverDuration: 200; +msgFileRadialLine: 3px; + +msgFileExtPadding: 8px; +msgFileExtTop: 30px; + +msgVideoSize: size(320px, 240px); sendPadding: 9px; btnSend: flatButton(btnDefFlat) { @@ -1261,6 +1319,13 @@ btnAttachEmoji: iconedButton(btnAttachDocument) { width: 33px; } +emojiCircle: size(19px, 19px); +emojiCirclePeriod: 1500; +emojiCircleDuration: 500; +emojiCircleTop: 13px; +emojiCircleLine: 2px; +emojiCircleFg: #b9b9b9; +emojiCirclePart: 3.5; btnBotKbShow: iconedButton(btnAttachEmoji) { icon: sprite(375px, 74px, 21px, 21px); iconPos: point(6px, 12px); @@ -1480,7 +1545,6 @@ notifySlowHideFunc: transition(easeInCirc); notifyWaitShortHide: 0; notifyWaitLongHide: 20000; notifyFastAnim: 150; -notifyFastAnimFunc: transition(linear); notifyWidth: 316px; notifyHeight: 80px; notifyDeltaX: 6px; @@ -1550,7 +1614,7 @@ profileMinBtnPadding: 10px; membersPadding: margins(0px, 10px, 0px, 10px); forwardMargins: margins(30px, 10px, 30px, 10px); -forwardFont: font(16px); +forwardFont: font(16px); forwardBg: rgba(0, 0, 0, 76); btnProfileCancel: flatButton(btnDefFlat, btnDefBig) { color: #666d78; @@ -1820,6 +1884,8 @@ emojiObjectsActive: sprite(308px, 264px, 21px, 22px); emojiSymbolsOver: sprite(84px, 196px, 21px, 22px); emojiSymbolsActive: sprite(287px, 286px, 21px, 22px); stickersSettings: sprite(140px, 124px, 21px, 22px); +savedGifsOver: sprite(329px, 286px, 21px, 22px); +savedGifsActive: sprite(350px, 286px, 21px, 22px); emojiPanCategories: #f7f7f7; @@ -1975,6 +2041,11 @@ botKbScroll: flatScroll(solidScroll) { width: 10px; } +minPhotoSize: 100px; +maxMediaSize: 420px; +maxStickerSize: 256px; +maxGifSize: 320px; + mvBgColor: #222; mvBgOpacity: 0.92; mvThickFont: semiboldFont; @@ -2063,14 +2134,12 @@ mvDocExtFont: font(semibold 18px); mvDocExtColor: white; mvDocExtPadding: 10px; mvDocLinksTop: 57px; -mvDocRed: sprite(0px, 400px, 80px, 80px); -mvDocRedColor: #e47272; -mvDocYellow: sprite(80px, 400px, 80px, 80px); -mvDocYellowColor: #efc274; -mvDocGreen: sprite(160px, 400px, 80px, 80px); -mvDocGreenColor: #61b96e; -mvDocBlue: sprite(240px, 400px, 80px, 80px); -mvDocBlueColor: #72b1df; +mvDocRed: sprite(0px, 400px, 25px, 25px); +mvDocYellow: sprite(25px, 400px, 25px, 25px); +mvDocGreen: sprite(50px, 400px, 25px, 25px); +mvDocBlue: sprite(75px, 400px, 25px, 25px); +mvDocIconSize: 80px; + mvDocLink: linkButton(btnDefLink) { color: #4595d3; overColor: #4595d3; @@ -2098,11 +2167,20 @@ medviewSaveMsg: #000000b2; mvTransparentBrush: sprite(9px, 124px, 8px, 8px); overviewPhotoSkip: 10px; -overviewPhotoMinSize: 100px; +overviewPhotoBg: #F1F1F1; +overviewPhotoMinSize: minPhotoSize; overviewPhotoCheck: sprite(245px, 308px, 32px, 32px); overviewPhotoChecked: sprite(278px, 308px, 32px, 32px); overviewPhotoSelectOverlay: #0a7bb03f; +overviewFilePadding: margins(0px, 3px, 16px, 3px); +overviewFileSize: 70px; +overviewFileNameTop: 7px; +overviewFileStatusTop: 27px; +overviewFileDateTop: 49px; +overviewFileChecked: #2fa9e2; +overviewFileCheck: #00000066; + // Mac specific macAccessory: size(450, 90); @@ -2159,10 +2237,6 @@ mediaviewLoader: size(78px, 33px); mediaviewLoaderPoint: size(9px, 9px); mediaviewLoaderSkip: 9px; -minPhotoSize: 100px; -maxMediaSize: 420px; -maxStickerSize: 256px; - downloadPathSkip: 10px; usernamePadding: margins(23px, 22px, 21px, 12px); @@ -2172,10 +2246,9 @@ usernameTextStyle: textStyle(defaultTextStyle) { } usernameDefaultFg: #777; -youtubeIcon: sprite(336px, 221px, 60px, 60px); -vimeoIcon: sprite(336px, 283px, 60px, 60px); +youtubeIcon: sprite(116px, 338px, 72px, 50px); videoIcon: sprite(0px, 340px, 60px, 60px); -locationSize: size(320, 240); +locationSize: size(320px, 240px); boxOptionListPadding: margins(2px, 20px, 2px, 2px); @@ -2293,11 +2366,24 @@ playlistPadding: 10px; linksSearchMargin: margins(20px, 20px, 20px, 0px); linksMaxWidth: 520px; linksLetterFont: font(24px); -linksMargin: 5px; +linksMargin: margins(0px, 7px, 0px, 5px); +linksTextTop: 6px; linksBorder: 1px; -linksBorderColor: #eaeaea; -linksDateColor: #000; -linksDateMargin: 15px; +linksBorderFg: #eaeaea; +linksDateColor: #808080; +linksDateMargin: margins(0px, 15px, 0px, 2px); linksPhotoCheck: sprite(184px, 196px, 16px, 16px); linksPhotoChecked: sprite(168px, 196px, 16px, 16px); + +inlineResultsLeft: 15px; +inlineResultsSkip: 3px; +inlineMediaHeight: 96px; +inlineThumbSize: 64px; +inlineThumbSkip: 10px; +inlineDescriptionFg: #8a8a8a; +inlineRowMargin: 6px; +inlineRowBorder: linksBorder; +inlineRowBorderFg: linksBorderFg; +inlineResultsMinWidth: 64px; +inlineDurationMargin: 3px; diff --git a/Telegram/SourceFiles/_other/updater.cpp b/Telegram/SourceFiles/_other/updater.cpp index 103803a882..a1dd35aa01 100644 --- a/Telegram/SourceFiles/_other/updater.cpp +++ b/Telegram/SourceFiles/_other/updater.cpp @@ -333,9 +333,8 @@ void updateRegistry() { int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdParamarg, int cmdShow) { openLog(); -#ifdef _NEED_WIN_GENERATE_DUMP _oldWndExceptionFilter = SetUnhandledExceptionFilter(_exceptionFilter); -#endif +// CAPIHook apiHook("kernel32.dll", "SetUnhandledExceptionFilter", (PROC)RedirectedSetUnhandledExceptionFilter); writeLog(L"Updaters started.."); @@ -465,7 +464,6 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara return 0; } -#ifdef _NEED_WIN_GENERATE_DUMP static const WCHAR *_programName = L"Telegram Desktop"; // folder in APPDATA, if current path is unavailable for writing static const WCHAR *_exeName = L"Updater.exe"; @@ -507,10 +505,10 @@ HANDLE _generateDumpFileAtPath(const WCHAR *path) { GetLocalTime(&stLocalTime); - wsprintf(szFileName, L"%s%s-%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp", - szPath, szExeName, updaterVersionStr, - stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay, - stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond, + wsprintf(szFileName, L"%s%s-%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp", + szPath, szExeName, updaterVersionStr, + stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay, + stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond, GetCurrentProcessId(), GetCurrentThreadId()); return CreateFile(szFileName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0); } @@ -546,7 +544,7 @@ void _generateDump(EXCEPTION_POINTERS* pExceptionPointers) { hDumpFile = _generateDumpFileAtPath(wstrPath); } } - + if (!hDumpFile || hDumpFile == INVALID_HANDLE_VALUE) { return; } @@ -564,4 +562,10 @@ LONG CALLBACK _exceptionFilter(EXCEPTION_POINTERS* pExceptionPointers) { return _oldWndExceptionFilter ? (*_oldWndExceptionFilter)(pExceptionPointers) : EXCEPTION_CONTINUE_SEARCH; } -#endif +// see http://www.codeproject.com/Articles/154686/SetUnhandledExceptionFilter-and-the-C-C-Runtime-Li +LPTOP_LEVEL_EXCEPTION_FILTER WINAPI RedirectedSetUnhandledExceptionFilter(_In_opt_ LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) { + // When the CRT calls SetUnhandledExceptionFilter with NULL parameter + // our handler will not get removed. + _oldWndExceptionFilter = lpTopLevelExceptionFilter; + return 0; +} diff --git a/Telegram/SourceFiles/_other/updater.h b/Telegram/SourceFiles/_other/updater.h index 6afd341a33..b7f8fd8c0b 100644 --- a/Telegram/SourceFiles/_other/updater.h +++ b/Telegram/SourceFiles/_other/updater.h @@ -32,12 +32,9 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org using std::deque; using std::wstring; -#define _NEED_WIN_GENERATE_DUMP - -#ifdef _NEED_WIN_GENERATE_DUMP extern LPTOP_LEVEL_EXCEPTION_FILTER _oldWndExceptionFilter; LONG CALLBACK _exceptionFilter(EXCEPTION_POINTERS* pExceptionPointers); -#endif _NEED_WIN_GENERATE_DUMP +LPTOP_LEVEL_EXCEPTION_FILTER WINAPI RedirectedSetUnhandledExceptionFilter(_In_opt_ LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter); static int updaterVersion = 1000; static const WCHAR *updaterVersionStr = L"0.1.0"; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index d81582eff5..69366c14ce 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -64,36 +64,6 @@ void ApiWrap::itemRemoved(HistoryItem *item) { } } -void ApiWrap::itemReplaced(HistoryItem *oldItem, HistoryItem *newItem) { - if (HistoryReply *reply = oldItem->toHistoryReply()) { - ChannelData *channel = reply->history()->peer->asChannel(); - ReplyToRequests *requests(replyToRequests(channel, true)); - if (requests) { - ReplyToRequests::iterator i = requests->find(reply->replyToId()); - if (i != requests->cend()) { - for (QList::iterator j = i->replies.begin(); j != i->replies.end();) { - if ((*j) == reply) { - if (HistoryReply *newReply = newItem->toHistoryReply()) { - *j = newReply; - ++j; - } else { - j = i->replies.erase(j); - } - } else { - ++j; - } - } - if (i->replies.isEmpty()) { - requests->erase(i); - } - } - if (channel && requests->isEmpty()) { - _channelReplyToRequests.remove(channel); - } - } - } -} - void ApiWrap::requestReplyTo(HistoryReply *reply, ChannelData *channel, MsgId id) { ReplyToRequest &req(channel ? _channelReplyToRequests[channel][id] : _replyToRequests[id]); req.replies.append(reply); @@ -460,7 +430,7 @@ void ApiWrap::requestBots(ChannelData *peer) { void ApiWrap::gotChat(PeerData *peer, const MTPmessages_Chats &result) { _peerRequests.remove(peer); - + if (result.type() == mtpc_messages_chats) { const QVector &v(result.c_messages_chats().vchats.c_vector().v); bool badVersion = false; @@ -702,9 +672,9 @@ void ApiWrap::scheduleStickerSetRequest(uint64 setId, uint64 access) { void ApiWrap::requestStickerSets() { for (QMap >::iterator i = _stickerSetRequests.begin(), j = i, e = _stickerSetRequests.end(); i != e; i = j) { + ++j; if (i.value().second) continue; - ++j; int32 wait = (j == e) ? 0 : 10; i.value().second = MTP::send(MTPmessages_GetStickerSet(MTP_inputStickerSetID(MTP_long(i.key()), MTP_long(i.value().first))), rpcDone(&ApiWrap::gotStickerSet, i.key()), rpcFail(&ApiWrap::gotStickerSetFail, i.key()), 0, wait); } @@ -712,10 +682,10 @@ void ApiWrap::requestStickerSets() { void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) { _stickerSetRequests.remove(setId); - + if (result.type() != mtpc_messages_stickerSet) return; const MTPDmessages_stickerSet &d(result.c_messages_stickerSet()); - + if (d.vset.type() != mtpc_stickerSet) return; const MTPDstickerSet &s(d.vset.c_stickerSet()); @@ -761,12 +731,32 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) ++i; } } + if (pack.isEmpty()) { int32 removeIndex = cStickerSetsOrder().indexOf(setId); if (removeIndex >= 0) cRefStickerSetsOrder().removeAt(removeIndex); sets.erase(it); } else { it->stickers = pack; + it->emoji.clear(); + const QVector &v(d.vpacks.c_vector().v); + for (int32 i = 0, l = v.size(); i < l; ++i) { + if (v.at(i).type() != mtpc_stickerPack) continue; + + const MTPDstickerPack &pack(v.at(i).c_stickerPack()); + if (EmojiPtr e = emojiGetNoColor(emojiFromText(qs(pack.vemoticon)))) { + const QVector &stickers(pack.vdocuments.c_vector().v); + StickerPack p; + p.reserve(stickers.size()); + for (int32 j = 0, c = stickers.size(); j < c; ++j) { + DocumentData *doc = App::document(stickers.at(j).v); + if (!doc || !doc->sticker()) continue; + + p.push_back(doc); + } + it->emoji.insert(e, p); + } + } } if (writeRecent) { @@ -916,12 +906,11 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs } } - MainWidget *m = App::main(); for (QMap::const_iterator i = msgsIds.cbegin(), e = msgsIds.cend(); i != e; ++i) { HistoryItem *item = App::histories().addNewMessage(v->at(i.value()), NewMessageExisting); if (item) { item->initDimensions(); - if (m) m->itemResized(item); + Notify::historyItemResized(item); } } @@ -934,7 +923,7 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs if (j != items.cend()) { for (HistoryItemsMap::const_iterator k = j.value().cbegin(), e = j.value().cend(); k != e; ++k) { k.key()->initDimensions(); - if (m) m->itemResized(k.key()); + Notify::historyItemResized(k.key()); } } } @@ -946,5 +935,5 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs } ApiWrap::~ApiWrap() { - App::deinitMedia(false); + App::clearHistories(); } diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 1bf5370987..5daabcf74f 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -29,7 +29,6 @@ public: void init(); void itemRemoved(HistoryItem *item); - void itemReplaced(HistoryItem *oldItem, HistoryItem *newItem); void requestReplyTo(HistoryReply *reply, ChannelData *channel, MsgId id); diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 81ba97b9d4..c375fea478 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -45,21 +45,14 @@ namespace { typedef QMap UpdatedPeers; UpdatedPeers updatedPeers; - typedef QHash PhotosData; PhotosData photosData; - - typedef QHash VideosData; VideosData videosData; - - typedef QHash AudiosData; AudiosData audiosData; + DocumentsData documentsData; typedef QHash ImageLinksData; ImageLinksData imageLinksData; - typedef QHash DocumentsData; - DocumentsData documentsData; - typedef QHash WebPagesData; WebPagesData webPagesData; @@ -69,16 +62,17 @@ namespace { typedef QMap ChannelReplyMarkups; ChannelReplyMarkups channelReplyMarkups; + PhotoItems photoItems; VideoItems videoItems; AudioItems audioItems; DocumentItems documentItems; WebPageItems webPageItems; + SharedContactItems sharedContactItems; + GifItems gifItems; + typedef QMap > RepliesTo; RepliesTo repliesTo; - typedef QMap SharedContactPhones; - SharedContactPhones sharedContactPhones; - Histories histories; typedef QHash MsgsData; @@ -119,6 +113,9 @@ namespace { typedef QHash LastPhotosMap; LastPhotosMap lastPhotosMap; + typedef QMap InlineResultLoaders; + InlineResultLoaders inlineResultLoaders; + style::color _msgServiceBg; style::color _msgServiceSelectBg; style::color _historyScrollBarColor; @@ -208,7 +205,7 @@ namespace App { globalNotifyAllPtr = UnknownNotifySettings; globalNotifyUsersPtr = UnknownNotifySettings; globalNotifyChatsPtr = UnknownNotifySettings; - App::uploader()->clear(); + if (App::uploader()) App::uploader()->clear(); clearStorageImages(); if (w) { w->getTitle()->updateBackButton(); @@ -430,6 +427,7 @@ namespace App { data->setBotInfoVersion(d.vbot_info_version.v); data->botInfo->readsAllHistory = d.is_bot_chat_history(); data->botInfo->cantJoinGroups = d.is_bot_nochats(); + data->botInfo->inlinePlaceholder = d.has_bot_inline_placeholder() ? '_' + qs(d.vbot_inline_placeholder) : QString(); } else { data->setBotInfoVersion(-1); } @@ -465,10 +463,8 @@ namespace App { data->contact = 0; } if (App::main()) { - if (data->contact > 0 && !wasContact) { - App::main()->addNewContact(peerToUser(data->id), false); - } else if (wasContact && data->contact <= 0) { - App::main()->removeContact(data); + if ((data->contact > 0 && !wasContact) || (wasContact && data->contact < 1)) { + Notify::userIsContactChanged(data); } if (emitPeerUpdated) { @@ -573,7 +569,7 @@ namespace App { ChannelData *cdata = data->asChannel(); cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash); - + QString uname = d.has_username() ? textOneLine(qs(d.vusername)) : QString(); cdata->setName(qs(d.vtitle), uname); @@ -905,25 +901,51 @@ namespace App { peerId = peerFromUser(m.vfrom_id); } if (HistoryItem *existing = App::histItemById(peerToChannel(peerId), m.vid.v)) { - bool hasLinks = m.has_entities() && !m.ventities.c_vector().v.isEmpty(); - if ((hasLinks && !existing->hasTextLinks()) || (!hasLinks && existing->textHasLinks())) { - existing->setText(qs(m.vmessage), m.has_entities() ? entitiesFromMTP(m.ventities.c_vector().v) : EntitiesInText()); - existing->initDimensions(); - if (App::main()) App::main()->itemResized(existing); - if (existing->hasTextLinks() && existing->indexInOverview()) { - existing->history()->addToOverview(existing, OverviewLinks); - } + existing->setText(qs(m.vmessage), m.has_entities() ? entitiesFromMTP(m.ventities.c_vector().v) : EntitiesInText()); + existing->updateMedia(m.has_media() ? (&m.vmedia) : 0); + existing->setViewsCount(m.has_views() ? m.vviews.v : -1, false); + existing->initDimensions(); + Notify::historyItemResized(existing); + + existing->addToOverview(AddToOverviewNew); + + if (!existing->detached()) { + App::checkSavedGif(existing); + return true; } - existing->updateMedia(m.has_media() ? (&m.vmedia) : 0, true); - - existing->setViewsCount(m.has_views() ? m.vviews.v : -1); - - return !existing->detached(); + return false; } return false; } + void addSavedGif(DocumentData *doc) { + SavedGifs &saved(cRefSavedGifs()); + int32 index = saved.indexOf(doc); + if (index) { + if (index > 0) saved.remove(index); + saved.push_front(doc); + if (saved.size() > cSavedGifsLimit()) saved.pop_back(); + Local::writeSavedGifs(); + + if (App::main()) emit App::main()->savedGifsUpdated(); + cSetLastSavedGifsUpdate(0); + App::main()->updateStickers(); + } + } + + void checkSavedGif(HistoryItem *item) { + if (!item->toHistoryForwarded() && (item->out() || item->history()->peer == App::self())) { + if (HistoryMedia *media = item->getMedia()) { + if (DocumentData *doc = media->getDocument()) { + if (doc->isGifv()) { + addSavedGif(doc); + } + } + } + } + } + void feedMsgs(const QVector &msgs, NewMessageType type) { QMap msgsIds; for (int32 i = 0, l = msgs.size(); i < l; ++i) { @@ -975,7 +997,7 @@ namespace App { const string &s(d.vbytes.c_string().v); QByteArray bytes(s.data(), s.size()); return ImagePtr(StorageImageLocation(d.vw.v, d.vh.v, 0, 0, 0, 0), bytes); - } + } } break; } return ImagePtr(); @@ -1002,7 +1024,7 @@ namespace App { } return StorageImageLocation(); } - + void feedInboxRead(const PeerId &peer, MsgId upTo) { History *h = App::historyLoaded(peer); if (h) { @@ -1060,7 +1082,7 @@ namespace App { } } if (resized) { - App::main()->itemResized(0); + Notify::historyItemsResized(); } if (main()) { for (QMap::const_iterator i = historiesToCheck.cbegin(), e = historiesToCheck.cend(); i != e; ++i) { @@ -1113,17 +1135,13 @@ namespace App { user->contact = -1; break; } - if (user->contact > 0) { - if (!wasContact) { - App::main()->addNewContact(peerToUser(user->id), false); - } - } else { + if (user->contact < 1) { if (user->contact < 0 && !user->phone.isEmpty() && peerToUser(user->id) != MTP::authedId()) { user->contact = 0; } - if (wasContact) { - App::main()->removeContact(user); - } + } + if ((user->contact > 0 && !wasContact) || (wasContact && user->contact < 1)) { + Notify::userIsContactChanged(user); } bool showPhone = !isServiceUser(user->id) && !user->isSelf() && !user->contact; @@ -1182,7 +1200,7 @@ namespace App { case 'm': newThumbLevel = 2; newMediumLevel = 0; newFullLevel = 3; break; // box 320x320 case 'x': newThumbLevel = 5; newMediumLevel = 3; newFullLevel = 1; break; // box 800x800 case 'y': newThumbLevel = 6; newMediumLevel = 6; newFullLevel = 0; break; // box 1280x1280 - case 'w': newThumbLevel = 8; newMediumLevel = 8; newFullLevel = 2; break; // box 2560x2560 + case 'w': newThumbLevel = 8; newMediumLevel = 8; newFullLevel = 2; break; // box 2560x2560 // if loading this fix HistoryPhoto::updateFrom case 'a': newThumbLevel = 1; newMediumLevel = 4; newFullLevel = 8; break; // crop 160x160 case 'b': newThumbLevel = 3; newMediumLevel = 1; newFullLevel = 7; break; // crop 320x320 case 'c': newThumbLevel = 4; newMediumLevel = 2; newFullLevel = 6; break; // crop 640x640 @@ -1228,7 +1246,7 @@ namespace App { const string &s(i->c_photoSize().vtype.c_string().v); if (s.size()) size = s[0]; } break; - + case mtpc_photoCachedSize: { const string &s(i->c_photoCachedSize().vtype.c_string().v); if (s.size()) size = s[0]; @@ -1269,7 +1287,7 @@ namespace App { } return App::photoSet(photo.vid.v, convert, 0, 0, ImagePtr(), ImagePtr(), ImagePtr()); } - + VideoData *feedVideo(const MTPDvideo &video, VideoData *convert) { return App::videoSet(video.vid.v, convert, video.vaccess_hash.v, video.vdate.v, video.vduration.v, video.vw.v, video.vh.v, App::image(video.vthumb), video.vdc_id.v, video.vsize.v); } @@ -1412,18 +1430,31 @@ 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(username.trimmed(), Qt::CaseInsensitive)) { + if (!i.value()->userName().compare(uname, Qt::CaseInsensitive)) { return i.value()->asUser(); } } return 0; } + void updateImage(ImagePtr &old, ImagePtr now) { + if (now->isNull()) return; + if (old->isNull()) { + old = now; + } else if (DelayedStorageImage *img = old->toDelayedStorageImage()) { + StorageImageLocation loc = now->location(); + if (!loc.isNull()) { + img->setStorageLocation(loc); + } + } + } + PhotoData *photo(const PhotoId &photo) { - PhotosData::const_iterator i = photosData.constFind(photo); - if (i == photosData.cend()) { - i = photosData.insert(photo, new PhotoData(photo)); + PhotosData::const_iterator i = ::photosData.constFind(photo); + if (i == ::photosData.cend()) { + i = ::photosData.insert(photo, new PhotoData(photo)); } return i.value(); } @@ -1431,38 +1462,40 @@ namespace App { PhotoData *photoSet(const PhotoId &photo, PhotoData *convert, const uint64 &access, int32 date, const ImagePtr &thumb, const ImagePtr &medium, const ImagePtr &full) { if (convert) { if (convert->id != photo) { - PhotosData::iterator i = photosData.find(convert->id); - if (i != photosData.cend() && i.value() == convert) { - photosData.erase(i); + PhotosData::iterator i = ::photosData.find(convert->id); + if (i != ::photosData.cend() && i.value() == convert) { + ::photosData.erase(i); } convert->id = photo; + delete convert->uploadingData; + convert->uploadingData = 0; } - convert->access = access; - if (!convert->date && date) { + if (date) { + convert->access = access; convert->date = date; - convert->thumb = thumb; - convert->medium = medium; - convert->full = full; + updateImage(convert->thumb, thumb); + updateImage(convert->medium, medium); + updateImage(convert->full, full); } } - PhotosData::const_iterator i = photosData.constFind(photo); + PhotosData::const_iterator i = ::photosData.constFind(photo); PhotoData *result; LastPhotosMap::iterator inLastIter = lastPhotosMap.end(); - if (i == photosData.cend()) { + if (i == ::photosData.cend()) { if (convert) { result = convert; } else { result = new PhotoData(photo, access, date, thumb, medium, full); } - photosData.insert(photo, result); + ::photosData.insert(photo, result); } else { result = i.value(); - if (result != convert && !result->date && date) { + if (result != convert && date) { result->access = access; result->date = date; - result->thumb = thumb; - result->medium = medium; - result->full = full; + updateImage(result->thumb, thumb); + updateImage(result->medium, medium); + updateImage(result->full, full); } inLastIter = lastPhotosMap.find(result); } @@ -1481,9 +1514,9 @@ namespace App { } VideoData *video(const VideoId &video) { - VideosData::const_iterator i = videosData.constFind(video); - if (i == videosData.cend()) { - i = videosData.insert(video, new VideoData(video)); + VideosData::const_iterator i = ::videosData.constFind(video); + if (i == ::videosData.cend()) { + i = ::videosData.insert(video, new VideoData(video)); } return i.value(); } @@ -1491,42 +1524,42 @@ namespace App { VideoData *videoSet(const VideoId &video, VideoData *convert, const uint64 &access, int32 date, int32 duration, int32 w, int32 h, const ImagePtr &thumb, int32 dc, int32 size) { if (convert) { if (convert->id != video) { - VideosData::iterator i = videosData.find(convert->id); - if (i != videosData.cend() && i.value() == convert) { - videosData.erase(i); + VideosData::iterator i = ::videosData.find(convert->id); + if (i != ::videosData.cend() && i.value() == convert) { + ::videosData.erase(i); } convert->id = video; convert->status = FileReady; } - convert->access = access; - if (!convert->date && date) { + if (date) { + convert->access = access; convert->date = date; + updateImage(convert->thumb, thumb); convert->duration = duration; convert->w = w; convert->h = h; - convert->thumb = thumb; convert->dc = dc; convert->size = size; } } - VideosData::const_iterator i = videosData.constFind(video); + VideosData::const_iterator i = ::videosData.constFind(video); VideoData *result; - if (i == videosData.cend()) { + if (i == ::videosData.cend()) { if (convert) { result = convert; } else { result = new VideoData(video, access, date, duration, w, h, thumb, dc, size); } - videosData.insert(video, result); + ::videosData.insert(video, result); } else { result = i.value(); - if (result != convert && !result->date && date) { + if (result != convert && date) { result->access = access; result->date = date; result->duration = duration; result->w = w; result->h = h; - result->thumb = thumb; + updateImage(result->thumb, thumb); result->dc = dc; result->size = size; } @@ -1535,9 +1568,9 @@ namespace App { } AudioData *audio(const AudioId &audio) { - AudiosData::const_iterator i = audiosData.constFind(audio); - if (i == audiosData.cend()) { - i = audiosData.insert(audio, new AudioData(audio)); + AudiosData::const_iterator i = ::audiosData.constFind(audio); + if (i == ::audiosData.cend()) { + i = ::audiosData.insert(audio, new AudioData(audio)); } return i.value(); } @@ -1545,15 +1578,15 @@ namespace App { AudioData *audioSet(const AudioId &audio, AudioData *convert, const uint64 &access, int32 date, const QString &mime, int32 duration, int32 dc, int32 size) { if (convert) { if (convert->id != audio) { - AudiosData::iterator i = audiosData.find(convert->id); - if (i != audiosData.cend() && i.value() == convert) { - audiosData.erase(i); + AudiosData::iterator i = ::audiosData.find(convert->id); + if (i != ::audiosData.cend() && i.value() == convert) { + ::audiosData.erase(i); } convert->id = audio; convert->status = FileReady; } - convert->access = access; - if (!convert->date && date) { + if (date) { + convert->access = access; convert->date = date; convert->mime = mime; convert->duration = duration; @@ -1561,18 +1594,18 @@ namespace App { convert->size = size; } } - AudiosData::const_iterator i = audiosData.constFind(audio); + AudiosData::const_iterator i = ::audiosData.constFind(audio); AudioData *result; - if (i == audiosData.cend()) { + if (i == ::audiosData.cend()) { if (convert) { result = convert; } else { result = new AudioData(audio, access, date, mime, duration, dc, size); } - audiosData.insert(audio, result); + ::audiosData.insert(audio, result); } else { result = i.value(); - if (result != convert && !result->date && date) { + if (result != convert && date) { result->access = access; result->date = date; result->mime = mime; @@ -1585,9 +1618,9 @@ 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)); + DocumentsData::const_iterator i = ::documentsData.constFind(document); + if (i == ::documentsData.cend()) { + i = ::documentsData.insert(document, new DocumentData(document)); } return i.value(); } @@ -1596,40 +1629,33 @@ namespace App { bool sentSticker = false; if (convert) { if (convert->id != document) { - DocumentsData::iterator i = documentsData.find(convert->id); - if (i != documentsData.cend() && i.value() == convert) { - documentsData.erase(i); + DocumentsData::iterator i = ::documentsData.find(convert->id); + if (i != ::documentsData.cend() && i.value() == convert) { + ::documentsData.erase(i); } + Local::copyStickerImage(mediaKey(DocumentFileLocation, convert->dc, convert->id), mediaKey(DocumentFileLocation, dc, document)); convert->id = document; convert->status = FileReady; sentSticker = !!convert->sticker(); } - convert->access = access; - if (!convert->date && date) { + if (date) { + convert->access = access; convert->date = date; convert->setattributes(attributes); convert->mime = mime; - convert->thumb = thumb; - convert->dc = dc; - convert->size = size; - } else { if (!thumb->isNull() && (convert->thumb->isNull() || convert->thumb->width() < thumb->width() || convert->thumb->height() < thumb->height())) { convert->thumb = thumb; } - if (convert->sticker() && !attributes.isEmpty() && (convert->sticker()->alt.isEmpty() || convert->sticker()->set.type() == mtpc_inputStickerSetEmpty)) { - for (QVector::const_iterator i = attributes.cbegin(), e = attributes.cend(); i != e; ++i) { - if (i->type() == mtpc_documentAttributeSticker) { - const MTPDdocumentAttributeSticker &d(i->c_documentAttributeSticker()); - if (d.valt.c_string().v.length() > 0) { - convert->sticker()->alt = qs(d.valt); - convert->sticker()->set = d.vstickerset; - } - } - } + convert->dc = dc; + convert->size = size; + convert->recountIsImage(); + if (convert->sticker() && convert->sticker()->loc.isNull() && !thumbLocation.isNull()) { + convert->sticker()->loc = thumbLocation; } } - if (convert->sticker() && !convert->sticker()->loc.dc && thumbLocation.dc) { - convert->sticker()->loc = thumbLocation; + + if (cSavedGifs().indexOf(convert) >= 0) { // id changed + Local::writeSavedGifs(); } const FileLocation &loc(convert->location(true)); @@ -1637,45 +1663,32 @@ namespace App { Local::writeFileLocation(mediaKey(DocumentFileLocation, convert->dc, convert->id), loc); } } - DocumentsData::const_iterator i = documentsData.constFind(document); + DocumentsData::const_iterator i = ::documentsData.constFind(document); DocumentData *result; - if (i == documentsData.cend()) { + if (i == ::documentsData.cend()) { if (convert) { result = convert; } else { result = new DocumentData(document, access, date, attributes, mime, thumb, dc, size); + result->recountIsImage(); if (result->sticker()) result->sticker()->loc = thumbLocation; } - documentsData.insert(document, result); + ::documentsData.insert(document, result); } else { result = i.value(); - if (result != convert) { - if (!result->date && date) { - result->access = access; - result->date = date; - result->setattributes(attributes); - result->mime = mime; + if (result != convert && date) { + result->access = access; + result->date = date; + result->setattributes(attributes); + 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; - } else { - if (!thumb->isNull() && (result->thumb->isNull() || result->thumb->width() < thumb->width() || result->thumb->height() < thumb->height())) { - result->thumb = thumb; - } - if (result->sticker() && !attributes.isEmpty() && (result->sticker()->alt.isEmpty() || result->sticker()->set.type() == mtpc_inputStickerSetEmpty)) { - for (QVector::const_iterator i = attributes.cbegin(), e = attributes.cend(); i != e; ++i) { - if (i->type() == mtpc_documentAttributeSticker) { - const MTPDdocumentAttributeSticker &d(i->c_documentAttributeSticker()); - if (d.valt.c_string().v.length() > 0) { - result->sticker()->alt = qs(d.valt); - result->sticker()->set = d.vstickerset; - } - } - } - } - if (result->sticker() && !result->sticker()->loc.dc && thumbLocation.dc) { - result->sticker()->loc = thumbLocation; - } + } + result->dc = dc; + result->size = size; + result->recountIsImage(); + if (result->sticker() && result->sticker()->loc.isNull() && !thumbLocation.isNull()) { + result->sticker()->loc = thumbLocation; } } } @@ -1750,7 +1763,7 @@ namespace App { } return result; } - + ImageLinkData *imageLink(const QString &imageLink) { ImageLinksData::const_iterator i = imageLinksData.constFind(imageLink); if (i == imageLinksData.cend()) { @@ -1758,7 +1771,7 @@ namespace App { } return i.value(); } - + ImageLinkData *imageLinkSet(const QString &imageLink, ImageLinkType type, const QString &url) { ImageLinksData::const_iterator i = imageLinksData.constFind(imageLink); ImageLinkData *result; @@ -1775,16 +1788,16 @@ namespace App { void forgetMedia() { lastPhotos.clear(); lastPhotosMap.clear(); - for (PhotosData::const_iterator i = photosData.cbegin(), e = photosData.cend(); i != e; ++i) { + for (PhotosData::const_iterator i = ::photosData.cbegin(), e = ::photosData.cend(); i != e; ++i) { i.value()->forget(); } - for (VideosData::const_iterator i = videosData.cbegin(), e = videosData.cend(); i != e; ++i) { + for (VideosData::const_iterator i = ::videosData.cbegin(), e = ::videosData.cend(); i != e; ++i) { i.value()->forget(); } - for (AudiosData::const_iterator i = audiosData.cbegin(), e = audiosData.cend(); i != e; ++i) { + for (AudiosData::const_iterator i = ::audiosData.cbegin(), e = ::audiosData.cend(); i != e; ++i) { i.value()->forget(); } - for (DocumentsData::const_iterator i = documentsData.cbegin(), e = documentsData.cend(); i != e; ++i) { + for (DocumentsData::const_iterator i = ::documentsData.cbegin(), e = ::documentsData.cend(); i != e; ++i) { i.value()->forget(); } for (ImageLinksData::const_iterator i = imageLinksData.cbegin(), e = imageLinksData.cend(); i != e; ++i) { @@ -1795,7 +1808,7 @@ namespace App { MTPPhoto photoFromUserPhoto(MTPint userId, MTPint date, const MTPUserProfilePhoto &photo) { if (photo.type() == mtpc_userProfilePhoto) { const MTPDuserProfilePhoto &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))); photoSizes.push_back(MTP_photoSize(MTP_string("c"), uphoto.vphoto_big, MTP_int(640), MTP_int(640), MTP_int(0))); @@ -1820,7 +1833,7 @@ namespace App { History *historyFromDialog(const PeerId &peer, int32 unreadCnt, int32 maxInboxRead) { return ::histories.findOrInsert(peer, unreadCnt, maxInboxRead); } - + History *historyLoaded(const PeerId &peer) { return ::histories.find(peer); } @@ -1836,47 +1849,16 @@ namespace App { return 0; } - void itemReplaced(HistoryItem *oldItem, HistoryItem *newItem) { - if (HistoryReply *r = oldItem->toHistoryReply()) { - QMap &replies(::repliesTo[r->replyToMessage()]); - replies.remove(r); - if (HistoryReply *n = newItem->toHistoryReply()) { - replies.insert(n, true); - } - } - RepliesTo::iterator i = ::repliesTo.find(oldItem); - if (i != ::repliesTo.cend() && oldItem != newItem) { - QMap replies = i.value(); - ::repliesTo.erase(i); - ::repliesTo[newItem] = replies; - for (QMap::iterator i = replies.begin(), e = replies.end(); i != e; ++i) { - i.key()->replyToReplaced(oldItem, newItem); - } - } - newItem->history()->itemReplaced(oldItem, newItem); - if (App::main()) App::main()->itemReplaced(oldItem, newItem); - if (App::hoveredItem() == oldItem) App::hoveredItem(newItem); - if (App::pressedItem() == oldItem) App::pressedItem(newItem); - if (App::hoveredLinkItem() == oldItem) App::hoveredLinkItem(newItem); - if (App::pressedLinkItem() == oldItem) App::pressedLinkItem(newItem); - if (App::contextItem() == oldItem) App::contextItem(newItem); - if (App::mousedItem() == oldItem) App::mousedItem(newItem); - } - - HistoryItem *historyRegItem(HistoryItem *item) { + void historyRegItem(HistoryItem *item) { MsgsData *data = fetchMsgsData(item->channelId()); MsgsData::const_iterator i = data->constFind(item->id); if (i == data->cend()) { data->insert(item->id, item); - return 0; - } - if (i.value() != item && !i.value()->block() && item->block()) { // replace search item - itemReplaced(i.value(), item); - delete i.value(); + } else if (i.value() != item) { + LOG(("App Error: trying to historyRegItem() an already registered item")); + i.value()->destroy(); data->insert(item->id, item); - return 0; } - return (i.value() == item) ? 0 : i.value(); } void historyItemDetached(HistoryItem *item) { @@ -1927,6 +1909,8 @@ namespace App { } void historyClearMsgs() { + ::repliesTo.clear(); + QVector toDelete; for (MsgsData::const_iterator i = msgsData.cbegin(), e = msgsData.cend(); i != e; ++i) { if ((*i)->detached()) { @@ -1952,7 +1936,6 @@ namespace App { } void historyClearItems() { - historyClearMsgs(); randomData.clear(); sentData.clear(); mutedPeers.clear(); @@ -1963,39 +1946,41 @@ namespace App { delete *i; } peersData.clear(); - for (PhotosData::const_iterator i = photosData.cbegin(), e = photosData.cend(); i != e; ++i) { + for (PhotosData::const_iterator i = ::photosData.cbegin(), e = ::photosData.cend(); i != e; ++i) { delete *i; } - photosData.clear(); - for (VideosData::const_iterator i = videosData.cbegin(), e = videosData.cend(); i != e; ++i) { + ::photosData.clear(); + for (VideosData::const_iterator i = ::videosData.cbegin(), e = ::videosData.cend(); i != e; ++i) { delete *i; } - videosData.clear(); - for (AudiosData::const_iterator i = audiosData.cbegin(), e = audiosData.cend(); i != e; ++i) { + ::videosData.clear(); + for (AudiosData::const_iterator i = ::audiosData.cbegin(), e = ::audiosData.cend(); i != e; ++i) { delete *i; } - audiosData.clear(); - for (DocumentsData::const_iterator i = documentsData.cbegin(), e = documentsData.cend(); i != e; ++i) { + ::audiosData.clear(); + for (DocumentsData::const_iterator i = ::documentsData.cbegin(), e = ::documentsData.cend(); i != e; ++i) { delete *i; } - documentsData.clear(); + ::documentsData.clear(); for (WebPagesData::const_iterator i = webPagesData.cbegin(), e = webPagesData.cend(); i != e; ++i) { delete *i; } webPagesData.clear(); if (api()) api()->clearWebPageRequests(); cSetRecentStickers(RecentStickerPack()); - cSetStickersHash(0); cSetStickerSets(StickerSets()); cSetStickerSetsOrder(StickerSetsOrder()); cSetLastStickersUpdate(0); + cSetSavedGifs(SavedGifs()); + cSetLastSavedGifsUpdate(0); cSetReportSpamStatuses(ReportSpamStatuses()); + ::photoItems.clear(); ::videoItems.clear(); ::audioItems.clear(); ::documentItems.clear(); ::webPageItems.clear(); - ::sharedContactPhones.clear(); - ::repliesTo.clear(); + ::sharedContactItems.clear(); + ::gifItems.clear(); lastPhotos.clear(); lastPhotosMap.clear(); ::self = 0; @@ -2085,7 +2070,6 @@ namespace App { } void initMedia() { - deinitMedia(false); audioInit(); if (!::monofont) { @@ -2126,9 +2110,9 @@ namespace App { prepareCorners(ServiceSelectedCorners, st::msgRadius, st::msgServiceSelectBg); prepareCorners(SelectedOverlayCorners, st::msgRadius, st::msgSelectOverlay); prepareCorners(DateCorners, st::msgRadius, st::msgDateImgBg); - prepareCorners(DateSelectedCorners, st::msgRadius, st::msgDateImgSelectBg); + prepareCorners(DateSelectedCorners, st::msgRadius, st::msgDateImgBgSelected); prepareCorners(InShadowCorners, st::msgRadius, st::msgInShadow); - prepareCorners(InSelectedShadowCorners, st::msgRadius, st::msgInSelectShadow); + prepareCorners(InSelectedShadowCorners, st::msgRadius, st::msgInShadowSelected); prepareCorners(ForwardCorners, st::msgRadius, st::forwardBg); prepareCorners(MediaviewSaveCorners, st::msgRadius, st::medviewSaveMsg); prepareCorners(EmojiHoverCorners, st::msgRadius, st::emojiPanHover); @@ -2138,58 +2122,56 @@ namespace App { prepareCorners(BotKeyboardDownCorners, st::msgRadius, st::botKbDownBg); prepareCorners(PhotoSelectOverlayCorners, st::msgRadius, st::overviewPhotoSelectOverlay); - prepareCorners(DocRedCorners, st::msgRadius, st::mvDocRedColor); - prepareCorners(DocYellowCorners, st::msgRadius, st::mvDocYellowColor); - prepareCorners(DocGreenCorners, st::msgRadius, st::mvDocGreenColor); - prepareCorners(DocBlueCorners, st::msgRadius, st::mvDocBlueColor); + prepareCorners(DocBlueCorners, st::msgRadius, st::msgFileBlueColor); + prepareCorners(DocGreenCorners, st::msgRadius, st::msgFileGreenColor); + prepareCorners(DocRedCorners, st::msgRadius, st::msgFileRedColor); + prepareCorners(DocYellowCorners, st::msgRadius, st::msgFileYellowColor); prepareCorners(MessageInCorners, st::msgRadius, st::msgInBg, &st::msgInShadow); - prepareCorners(MessageInSelectedCorners, st::msgRadius, st::msgInSelectBg, &st::msgInSelectShadow); + prepareCorners(MessageInSelectedCorners, st::msgRadius, st::msgInBgSelected, &st::msgInShadowSelected); prepareCorners(MessageOutCorners, st::msgRadius, st::msgOutBg, &st::msgOutShadow); - prepareCorners(MessageOutSelectedCorners, st::msgRadius, st::msgOutSelectBg, &st::msgOutSelectShadow); - prepareCorners(ButtonHoverCorners, st::msgRadius, st::mediaSaveButton.overBgColor, &st::msgInShadow); - + prepareCorners(MessageOutSelectedCorners, st::msgRadius, st::msgOutBgSelected, &st::msgOutShadowSelected); } - - void deinitMedia(bool completely) { + + void clearHistories() { textlnkOver(TextLinkPtr()); textlnkDown(TextLinkPtr()); histories().clear(); - if (completely) { - audioFinish(); - - delete ::sprite; - ::sprite = 0; - delete ::emoji; - ::emoji = 0; - delete ::emojiLarge; - ::emojiLarge = 0; - for (int32 j = 0; j < 4; ++j) { - for (int32 i = 0; i < RoundCornersCount; ++i) { - delete ::corners[i].p[j]; ::corners[i].p[j] = 0; - } - delete ::cornersMask[j]; ::cornersMask[j] = 0; - } - for (CornersMap::const_iterator i = ::cornersMap.cbegin(), e = ::cornersMap.cend(); i != e; ++i) { - for (int32 j = 0; j < 4; ++j) { - delete i->p[j]; - } - } - ::cornersMap.clear(); - mainEmojiMap.clear(); - otherEmojiMap.clear(); - - clearAllImages(); - } else { - clearStorageImages(); - cSetServerBackgrounds(WallPapers()); - } + clearStorageImages(); + cSetServerBackgrounds(WallPapers()); serviceImageCacheSize = imageCacheSize(); } + void deinitMedia() { + audioFinish(); + + delete ::sprite; + ::sprite = 0; + delete ::emoji; + ::emoji = 0; + delete ::emojiLarge; + ::emojiLarge = 0; + for (int32 j = 0; j < 4; ++j) { + for (int32 i = 0; i < RoundCornersCount; ++i) { + delete ::corners[i].p[j]; ::corners[i].p[j] = 0; + } + delete ::cornersMask[j]; ::cornersMask[j] = 0; + } + for (CornersMap::const_iterator i = ::cornersMap.cbegin(), e = ::cornersMap.cend(); i != e; ++i) { + for (int32 j = 0; j < 4; ++j) { + delete i->p[j]; + } + } + ::cornersMap.clear(); + mainEmojiMap.clear(); + otherEmojiMap.clear(); + + clearAllImages(); + } + void hoveredItem(HistoryItem *item) { ::hoveredItem = item; } @@ -2205,7 +2187,7 @@ namespace App { HistoryItem *pressedItem() { return ::pressedItem; } - + void hoveredLinkItem(HistoryItem *item) { ::hoveredLinkItem = item; } @@ -2221,7 +2203,7 @@ namespace App { HistoryItem *pressedLinkItem() { return ::pressedLinkItem; } - + void contextItem(HistoryItem *item) { ::contextItem = item; } @@ -2317,16 +2299,24 @@ namespace App { if (!format) { format = &tmpFormat; } - QImageReader reader(&buffer, *format); - if (animated) *animated = reader.supportsAnimation() && reader.imageCount() > 1; - if (!reader.read(&result)) { - return QImage(); + { + QImageReader reader(&buffer, *format); +#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) + reader.setAutoTransform(true); +#endif + if (animated) *animated = reader.supportsAnimation() && reader.imageCount() > 1; + QByteArray fmt = reader.format(); + if (!fmt.isEmpty()) *format = fmt; + if (!reader.read(&result)) { + return QImage(); + } + fmt = reader.format(); + if (!fmt.isEmpty()) *format = fmt; } - buffer.seek(0); - *format = reader.format(); - QString fmt = QString::fromUtf8(*format).toLower() ; + QString fmt = QString::fromUtf8(*format).toLower(); if (fmt == "jpg" || fmt == "jpeg") { +#if QT_VERSION < QT_VERSION_CHECK(5, 5, 0) ExifData *exifData = exif_data_new_from_data((const uchar*)(data.constData()), data.size()); if (exifData) { ExifByteOrder byteOrder = exif_data_get_byte_order(exifData); @@ -2347,6 +2337,7 @@ namespace App { } exif_data_free(exifData); } +#endif } else if (opaque && result.hasAlphaChannel()) { QImage solid(result.width(), result.height(), QImage::Format_ARGB32_Premultiplied); solid.fill(st::white->c); @@ -2370,8 +2361,24 @@ namespace App { return result; } + void regPhotoItem(PhotoData *data, HistoryItem *item) { + ::photoItems[data].insert(item, NullType()); + } + + void unregPhotoItem(PhotoData *data, HistoryItem *item) { + ::photoItems[data].remove(item); + } + + const PhotoItems &photoItems() { + return ::photoItems; + } + + const PhotosData &photosData() { + return ::photosData; + } + void regVideoItem(VideoData *data, HistoryItem *item) { - ::videoItems[data][item] = true; + ::videoItems[data].insert(item, NullType()); } void unregVideoItem(VideoData *data, HistoryItem *item) { @@ -2382,8 +2389,12 @@ namespace App { return ::videoItems; } + const VideosData &videosData() { + return ::videosData; + } + void regAudioItem(AudioData *data, HistoryItem *item) { - ::audioItems[data][item] = true; + ::audioItems[data].insert(item, NullType()); } void unregAudioItem(AudioData*data, HistoryItem *item) { @@ -2394,8 +2405,12 @@ namespace App { return ::audioItems; } + const AudiosData &audiosData() { + return ::audiosData; + } + void regDocumentItem(DocumentData *data, HistoryItem *item) { - ::documentItems[data][item] = true; + ::documentItems[data].insert(item, NullType()); } void unregDocumentItem(DocumentData *data, HistoryItem *item) { @@ -2406,8 +2421,12 @@ namespace App { return ::documentItems; } + const DocumentsData &documentsData() { + return ::documentsData; + } + void regWebPageItem(WebPageData *data, HistoryItem *item) { - ::webPageItems[data][item] = true; + ::webPageItems[data].insert(item, NullType()); } void unregWebPageItem(WebPageData *data, HistoryItem *item) { @@ -2418,12 +2437,46 @@ namespace App { return ::webPageItems; } - void regSharedContactPhone(int32 userId, const QString &phone) { - ::sharedContactPhones[userId] = phone; + void regSharedContactItem(int32 userId, HistoryItem *item) { + ::sharedContactItems[userId].insert(item, NullType()); + } + + void unregSharedContactItem(int32 userId, HistoryItem *item) { + ::sharedContactItems[userId].remove(item); + } + + const SharedContactItems &sharedContactItems() { + return ::sharedContactItems; + } + + void regGifItem(ClipReader *reader, HistoryItem *item) { + ::gifItems.insert(reader, item); + } + + void unregGifItem(ClipReader *reader) { + ::gifItems.remove(reader); + } + + void stopGifItems() { + if (!::gifItems.isEmpty()) { + 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()); + } + } + } } QString phoneFromSharedContact(int32 userId) { - return ::sharedContactPhones.value(userId); + SharedContactItems::const_iterator i = ::sharedContactItems.constFind(userId); + if (i != ::sharedContactItems.cend()) { + HistoryMedia *media = i->cbegin().key()->getMedia(); + if (media && media->type() == MediaTypeContact) { + return static_cast(media)->phone(); + } + } + return QString(); } void regMuted(PeerData *peer, int32 changeIn) { @@ -2454,6 +2507,19 @@ 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); @@ -2469,7 +2535,7 @@ namespace App { 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()); @@ -2542,12 +2608,15 @@ namespace App { } void setProxySettings(QNetworkAccessManager &manager) { + manager.setProxy(getHttpProxySettings()); + } + + QNetworkProxy getHttpProxySettings() { if (cConnectionType() == dbictHttpProxy) { const ConnectionProxy &p(cConnectionProxy()); - manager.setProxy(QNetworkProxy(QNetworkProxy::HttpProxy, p.host, p.port, p.user, p.password)); - } else { - manager.setProxy(QNetworkProxy(QNetworkProxy::DefaultProxy)); + return QNetworkProxy(QNetworkProxy::HttpProxy, p.host, p.port, p.user, p.password); } + return QNetworkProxy(QNetworkProxy::DefaultProxy); } void setProxySettings(QTcpSocket &socket) { @@ -2557,7 +2626,7 @@ namespace App { } else { socket.setProxy(QNetworkProxy(QNetworkProxy::NoProxy)); } - } + } QImage **cornersMask() { return ::cornersMask; diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index 1402a7e056..c01fdf9802 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -32,12 +32,22 @@ class Color; class FileUploader; #include "history.h" +#include "layout.h" -typedef QMap HistoryItemsMap; +typedef QMap HistoryItemsMap; +typedef QHash PhotoItems; typedef QHash VideoItems; typedef QHash AudioItems; typedef QHash DocumentItems; typedef QHash WebPageItems; +typedef QHash SharedContactItems; +typedef QHash GifItems; + +typedef QHash PhotosData; +typedef QHash VideosData; +typedef QHash AudiosData; +typedef QHash DocumentsData; + struct ReplyMarkup { ReplyMarkup(int32 flags = 0) : flags(flags) { } @@ -46,40 +56,6 @@ struct ReplyMarkup { int32 flags; }; -enum RoundCorners { - NoneCorners = 0x00, // for images - BlackCorners, - ServiceCorners, - ServiceSelectedCorners, - SelectedOverlayCorners, - DateCorners, - DateSelectedCorners, - ForwardCorners, - MediaviewSaveCorners, - EmojiHoverCorners, - StickerHoverCorners, - BotKeyboardCorners, - BotKeyboardOverCorners, - BotKeyboardDownCorners, - PhotoSelectOverlayCorners, - - DocRedCorners, - DocYellowCorners, - DocGreenCorners, - DocBlueCorners, - - InShadowCorners, // for photos without bg - InSelectedShadowCorners, - - MessageInCorners, // with shadow - MessageInSelectedCorners, - MessageOutCorners, - MessageOutSelectedCorners, - ButtonHoverCorners, - - RoundCornersCount -}; - class LayeredWidget; namespace App { @@ -109,6 +85,8 @@ namespace App { void feedChatAdmins(const MTPDupdateChatAdmins &d, bool emitPeerUpdated = true); void feedParticipantAdmin(const MTPDupdateChatParticipantAdmin &d, bool emitPeerUpdated = true); bool checkEntitiesAndViewsUpdate(const MTPDmessage &m); // returns true if item found and it is not detached + void addSavedGif(DocumentData *doc); + void checkSavedGif(HistoryItem *item); void feedMsgs(const QVector &msgs, NewMessageType type); void feedMsgs(const MTPVector &msgs, NewMessageType type); void feedInboxRead(const PeerId &peer, MsgId upTo); @@ -180,7 +158,7 @@ namespace App { inline HistoryItem *histItemById(const FullMsgId &msgId) { return histItemById(msgId.channel, msgId.msg); } - HistoryItem *historyRegItem(HistoryItem *item); + void historyRegItem(HistoryItem *item); void historyItemDetached(HistoryItem *item); void historyUnregItem(HistoryItem *item); void historyClearMsgs(); @@ -214,8 +192,10 @@ namespace App { const QPixmap &emojiLarge(); const QPixmap &emojiSingle(EmojiPtr emoji, int32 fontHeight); + void clearHistories(); + void initMedia(); - void deinitMedia(bool completely = true); + void deinitMedia(); void playSound(); void checkImageCacheSize(); @@ -229,34 +209,53 @@ namespace App { QImage readImage(QByteArray data, QByteArray *format = 0, bool opaque = true, bool *animated = 0); QImage readImage(const QString &file, QByteArray *format = 0, bool opaque = true, bool *animated = 0, QByteArray *content = 0); + void regPhotoItem(PhotoData *data, HistoryItem *item); + void unregPhotoItem(PhotoData *data, HistoryItem *item); + const PhotoItems &photoItems(); + const PhotosData &photosData(); + void regVideoItem(VideoData *data, HistoryItem *item); void unregVideoItem(VideoData *data, HistoryItem *item); const VideoItems &videoItems(); + const VideosData &videosData(); void regAudioItem(AudioData *data, HistoryItem *item); void unregAudioItem(AudioData*data, HistoryItem *item); const AudioItems &audioItems(); + const AudiosData &audiosData(); void regDocumentItem(DocumentData *data, HistoryItem *item); void unregDocumentItem(DocumentData *data, HistoryItem *item); const DocumentItems &documentItems(); + const DocumentsData &documentsData(); void regWebPageItem(WebPageData *data, HistoryItem *item); void unregWebPageItem(WebPageData *data, HistoryItem *item); const WebPageItems &webPageItems(); - void regSharedContactPhone(int32 userId, const QString &phone); + void regSharedContactItem(int32 userId, HistoryItem *item); + void unregSharedContactItem(int32 userId, HistoryItem *item); + const SharedContactItems &sharedContactItems(); QString phoneFromSharedContact(int32 userId); + void regGifItem(ClipReader *reader, HistoryItem *item); + void unregGifItem(ClipReader *reader); + void stopGifItems(); + void regMuted(PeerData *peer, int32 changeIn); 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); + QNetworkProxy getHttpProxySettings(); void setProxySettings(QTcpSocket &socket); QImage **cornersMask(); @@ -295,25 +294,4 @@ namespace App { }; -inline int32 stickersCountHash(bool checkOfficial = false) { - uint32 acc = 0; - bool foundOfficial = false, foundBad = false;; - const StickerSets &sets(cStickerSets()); - const StickerSetsOrder &order(cStickerSetsOrder()); - for (StickerSetsOrder::const_iterator i = order.cbegin(), e = order.cend(); i != e; ++i) { - StickerSets::const_iterator j = sets.constFind(*i); - if (j != sets.cend()) { - if (j->id == 0) { - foundBad = true; - } else if (j->flags & MTPDstickerSet::flag_official) { - foundOfficial = true; - } - if (!(j->flags & MTPDstickerSet::flag_disabled)) { - acc = (acc * 20261) + j->hash; - } - } - } - return (!checkOfficial || (!foundBad && foundOfficial)) ? int32(acc & 0x7FFFFFFF) : 0; -} - #include "facades.h" diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index d6e7ae35fb..856174b53f 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -208,7 +208,7 @@ void Application::updateGotCurrent() { if (!updateReply || updateThread) return; cSetLastUpdateCheck(unixtime()); - QRegularExpressionMatch m = QRegularExpression(qsl("^\\s*(\\d+)\\s*:\\s*([\\x21-\\x7f]+)\\s*$")).match(QString::fromUtf8(updateReply->readAll())); + QRegularExpressionMatch m = QRegularExpression(qsl("^\\s*(\\d+)\\s*:\\s*([\\x21-\\x7f]+)\\s*$")).match(QString::fromLatin1(updateReply->readAll())); if (m.hasMatch()) { uint64 currentVersion = m.captured(1).toULongLong(); QString url = m.captured(2); @@ -445,7 +445,7 @@ void Application::onSwitchDebugMode() { f.write("1"); f.close(); } - App::wnd()->hideLayer(); + Ui::hideLayer(); } } @@ -489,7 +489,7 @@ int32 Application::updatingReady() { #endif FileUploader *Application::uploader() { - if (!::uploader) ::uploader = new FileUploader(); + if (!::uploader && !App::quiting()) ::uploader = new FileUploader(); return ::uploader; } @@ -548,9 +548,9 @@ void Application::stopUpdate() { void Application::startUpdateCheck(bool forceWait) { updateCheckTimer.stop(); if (updateRequestId || updateThread || updateReply || !cAutoUpdate()) return; - + int32 constDelay = cBetaVersion() ? 600 : UpdateDelayConstPart, randDelay = cBetaVersion() ? 300 : UpdateDelayRandPart; - int32 updateInSecs = cLastUpdateCheck() + constDelay + (rand() % randDelay) - unixtime(); + int32 updateInSecs = cLastUpdateCheck() + constDelay + int32(MTP::nonce() % randDelay) - unixtime(); bool sendRequest = (updateInSecs <= 0 || updateInSecs > (constDelay + randDelay)); if (!sendRequest && !forceWait) { QDir updates(cWorkingDir() + "tupdates"); @@ -642,7 +642,7 @@ void Application::socketConnected() { } commands += qsl("CMD:show;"); DEBUG_LOG(("Application Info: writing commands %1").arg(commands)); - socket.write(commands.toLocal8Bit()); + socket.write(commands.toLatin1()); } void Application::socketWritten(qint64/* bytes*/) { @@ -684,7 +684,7 @@ void Application::socketError(QLocalSocket::LocalSocketError e) { socket.close(); psCheckLocalSocket(serverName); - + if (!server.listen(serverName)) { DEBUG_LOG(("Application Error: failed to start listening to %1 server, error %2").arg(serverName).arg(int(server.serverError()))); return App::quit(); @@ -705,10 +705,10 @@ void Application::checkMapVersion() { if (Local::oldMapVersion() < AppVersion) { if (Local::oldMapVersion()) { QString versionFeatures; - if (cDevVersion() && Local::oldMapVersion() < 9014) { - versionFeatures = QString::fromUtf8("\xe2\x80\x94 Sticker management: manually rearrange your sticker packs, pack order is now synced across all your devices\n\xe2\x80\x94 Click and hold on a sticker to preview it before sending\n\xe2\x80\x94 New context menu for chats in chats list\n\xe2\x80\x94 Support for all existing emoji");// .replace('@', qsl("@") + QChar(0x200D)); - } else if (Local::oldMapVersion() < 9015) { - versionFeatures = lang(lng_new_version_text).trimmed(); + if (cDevVersion() && Local::oldMapVersion() < 9019) { + versionFeatures = QString::fromUtf8("\xe2\x80\x94 Choose an emoticon and see the suggested stickers\n\xe2\x80\x94 Bug fixes in minor improvements");// .replace('@', qsl("@") + QChar(0x200D)); + } else if (Local::oldMapVersion() < 9016) { + versionFeatures = lng_new_version_text(lt_gifs_link, qsl("https://telegram.org/blog/gif-revolution"), lt_bots_link, qsl("https://telegram.org/blog/inline-bots")).trimmed(); } else { versionFeatures = lang(lng_new_version_minor).trimmed(); } @@ -769,7 +769,7 @@ void Application::startApp() { } QNetworkProxyFactory::setUseSystemConfiguration(true); - + if (state != Local::ReadMapPassNeeded) { checkMapVersion(); } @@ -799,13 +799,13 @@ void Application::readClients() { for (ClientSockets::iterator i = clients.begin(), e = clients.end(); i != e; ++i) { i->second.append(i->first->readAll()); if (i->second.size()) { - QString cmds(QString::fromLocal8Bit(i->second)); + QString cmds(QString::fromLatin1(i->second)); int32 from = 0, l = cmds.length(); for (int32 to = cmds.indexOf(QChar(';'), from); to >= from; to = (from < l) ? cmds.indexOf(QChar(';'), from) : -1) { QStringRef cmd(&cmds, from, to - from); if (cmd.startsWith(qsl("CMD:"))) { execExternal(cmds.mid(from + 4, to - from - 4)); - QByteArray response(qsl("RES:%1;").arg(QCoreApplication::applicationPid()).toUtf8()); + QByteArray response(qsl("RES:%1;").arg(QCoreApplication::applicationPid()).toLatin1()); i->first->write(response.data(), response.size()); } else if (cmd.startsWith(qsl("SEND:"))) { if (cSendPaths().isEmpty()) { @@ -878,16 +878,20 @@ void Application::closeApplication() { Application::~Application() { App::setQuiting(); + window->setParent(0); anim::stopManager(); socket.close(); closeApplication(); + stopWebLoadManager(); App::deinitMedia(); deinitImageLinkManager(); + mainApp = 0; delete ::uploader; + #ifndef TDESKTOP_DISABLE_AUTOUPDATE delete updateReply; updateReply = 0; @@ -897,7 +901,9 @@ Application::~Application() { updateThread = 0; #endif - delete window; + Window *w = window; + window = 0; + delete w; delete cChatBackground(); cSetChatBackground(0); @@ -906,7 +912,7 @@ Application::~Application() { cSetChatDogImage(0); style::stopManager(); - + delete _translator; } diff --git a/Telegram/SourceFiles/art/sprite.png b/Telegram/SourceFiles/art/sprite.png index b40fa32970..ce32997180 100644 Binary files a/Telegram/SourceFiles/art/sprite.png and b/Telegram/SourceFiles/art/sprite.png differ diff --git a/Telegram/SourceFiles/art/sprite_200x.png b/Telegram/SourceFiles/art/sprite_200x.png index 05d810234d..16b454f057 100644 Binary files a/Telegram/SourceFiles/art/sprite_200x.png and b/Telegram/SourceFiles/art/sprite_200x.png differ diff --git a/Telegram/SourceFiles/audio.cpp b/Telegram/SourceFiles/audio.cpp index 433e549d3c..1f86108be0 100644 --- a/Telegram/SourceFiles/audio.cpp +++ b/Telegram/SourceFiles/audio.cpp @@ -27,15 +27,6 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #define AL_ALEXT_PROTOTYPES #include -extern "C" { - -#include -#include -#include -#include - -} - #ifdef Q_OS_MAC extern "C" { @@ -106,15 +97,11 @@ bool _checkALError() { Q_DECLARE_METATYPE(AudioMsgId); Q_DECLARE_METATYPE(SongMsgId); void audioInit() { - av_register_all(); - avcodec_register_all(); - if (!capture) { capture = new AudioCapture(); cSetHasAudioCapture(capture->check()); } - uint64 ms = getms(); if (audioDevice) return; audioDevice = alcOpenDevice(0); @@ -122,7 +109,7 @@ void audioInit() { LOG(("Audio Error: default sound device not present.")); return; } - + ALCint attributes[] = { ALC_STEREO_SOURCES, 8, 0 }; audioContext = alcCreateContext(audioDevice, attributes); alcMakeContextCurrent(audioContext); @@ -223,7 +210,6 @@ void audioInit() { player = new AudioPlayer(); alcDevicePauseSOFT(audioDevice); - LOG(("Audio init time: %1").arg(getms() - ms)); cSetHasAudioPlayer(true); } @@ -464,7 +450,7 @@ void AudioPlayer::play(const AudioMsgId &audio, int64 position) { } current->audio = audio; current->file = audio.audio->location(true); - current->data = audio.audio->data; + current->data = audio.audio->data(); if (current->file.isEmpty() && current->data.isEmpty()) { setStoppedState(current, AudioPlayerStoppedAtError); onError(audio); @@ -508,14 +494,11 @@ void AudioPlayer::play(const SongMsgId &song, int64 position) { } current->song = song; current->file = song.song->location(true); - current->data = song.song->data; + current->data = song.song->data(); if (current->file.isEmpty() && current->data.isEmpty()) { setStoppedState(current); - if (!song.song->loader) { + if (!song.song->loading()) { DocumentOpenLink::doOpen(song.song); - song.song->openOnSave = 0; - song.song->openOnSaveMsgId = FullMsgId(); - if (song.song->loader) song.song->loader->start(true, true); } } else { current->state = fadedStart ? AudioPlayerStarting : AudioPlayerPlaying; @@ -528,7 +511,7 @@ void AudioPlayer::play(const SongMsgId &song, int64 position) { bool AudioPlayer::checkCurrentALError(MediaOverviewType type) { if (_checkALError()) return true; - + switch (type) { case OverviewAudios: setStoppedState(&_audioData[_audioCurrent], AudioPlayerStoppedAtError); @@ -1103,7 +1086,7 @@ protected: QFile f; int32 dataPos; - + bool openFile() { if (data.isEmpty()) { if (f.isOpen()) f.close(); @@ -1126,7 +1109,6 @@ protected: }; -static const uint32 AVBlockSize = 4096; // 4Kb static const AVSampleFormat _toFormat = AV_SAMPLE_FMT_S16; static const int64_t _toChannelLayout = AV_CH_LAYOUT_STEREO; static const int32 _toChannels = 2; @@ -1189,14 +1171,14 @@ public: return false; } - freq = fmtContext->streams[streamId]->codec->sample_rate; + freq = codecContext->sample_rate; if (fmtContext->streams[streamId]->duration == AV_NOPTS_VALUE) { len = (fmtContext->duration * freq) / AV_TIME_BASE; } else { len = (fmtContext->streams[streamId]->duration * freq * fmtContext->streams[streamId]->time_base.num) / fmtContext->streams[streamId]->time_base.den; } - uint64_t layout = fmtContext->streams[streamId]->codec->channel_layout; - inputFormat = fmtContext->streams[streamId]->codec->sample_fmt; + uint64_t layout = codecContext->channel_layout; + inputFormat = codecContext->sample_fmt; switch (layout) { case AV_CH_LAYOUT_MONO: switch (inputFormat) { @@ -1302,7 +1284,7 @@ public: char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; LOG(("Audio Error: Unable to avcodec_decode_audio4() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - av_free_packet(&avpkt); + av_packet_unref(&avpkt); if (res == AVERROR_INVALIDDATA) return 0; // try to skip bad packet return -1; } @@ -1319,7 +1301,7 @@ public: char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - av_free_packet(&avpkt); + av_packet_unref(&avpkt); return -1; } } @@ -1327,7 +1309,7 @@ public: char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; LOG(("Audio Error: Unable to swr_convert for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - av_free_packet(&avpkt); + av_packet_unref(&avpkt); return -1; } int32 resultLen = av_samples_get_buffer_size(0, _toChannels, res, _toFormat, 1); @@ -1339,7 +1321,7 @@ public: } } } - av_free_packet(&avpkt); + av_packet_unref(&avpkt); return 1; } @@ -1897,7 +1879,7 @@ void AudioCaptureInner::onInit() { } void AudioCaptureInner::onStart() { - + // Start OpenAL Capture const ALCchar *dName = alcGetString(0, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER); DEBUG_LOG(("Audio Info: Capture device name '%1'").arg(dName)); @@ -1918,7 +1900,7 @@ void AudioCaptureInner::onStart() { // Create encoding context d->ioBuffer = (uchar*)av_malloc(AVBlockSize); - + d->ioContext = avio_alloc_context(d->ioBuffer, AVBlockSize, 1, static_cast(d), &AudioCapturePrivate::_read_data, &AudioCapturePrivate::_write_data, &AudioCapturePrivate::_seek_data); int res = 0; char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; @@ -2403,7 +2385,7 @@ public: QString title() { return _title; } - + QString performer() { return _performer; } diff --git a/Telegram/SourceFiles/autoupdater.cpp b/Telegram/SourceFiles/autoupdater.cpp index a34a471c6e..a1a77a4ff0 100644 --- a/Telegram/SourceFiles/autoupdater.cpp +++ b/Telegram/SourceFiles/autoupdater.cpp @@ -51,7 +51,7 @@ void UpdateDownloader::initOutput() { fileName = m.captured(1).replace(QRegularExpression(qsl("[^a-zA-Z0-9_\\-]")), QString()); } if (fileName.isEmpty()) { - fileName = qsl("tupdate-%1").arg(rand()); + fileName = qsl("tupdate-%1").arg(MTP::nonce() % 1000000); } QString dirStr = cWorkingDir() + qsl("tupdates/"); fileName = dirStr + fileName; @@ -614,4 +614,3 @@ QString countBetaVersionSignature(uint64 version) { // duplicated in packer.cpp signature = signature.replace('-', '8').replace('_', 'B'); return QString::fromUtf8(signature.mid(19, 32)); } - diff --git a/Telegram/SourceFiles/boxes/aboutbox.cpp b/Telegram/SourceFiles/boxes/aboutbox.cpp index ee2db425d9..a6bad679e4 100644 --- a/Telegram/SourceFiles/boxes/aboutbox.cpp +++ b/Telegram/SourceFiles/boxes/aboutbox.cpp @@ -84,7 +84,7 @@ void AboutBox::onVersion() { App::app()->clipboard()->setText(url); - App::showLayer(new InformBox("The link to the current private beta version of Telegram Desktop was copied to the clipboard.")); + Ui::showLayer(new InformBox("The link to the current private beta version of Telegram Desktop was copied to the clipboard.")); } else { QDesktopServices::openUrl(qsl("https://desktop.telegram.org/?_hash=changelog")); } diff --git a/Telegram/SourceFiles/boxes/abstractbox.cpp b/Telegram/SourceFiles/boxes/abstractbox.cpp index 10c8d43933..e61b40fcee 100644 --- a/Telegram/SourceFiles/boxes/abstractbox.cpp +++ b/Telegram/SourceFiles/boxes/abstractbox.cpp @@ -36,7 +36,7 @@ void BlueTitleShadow::paintEvent(QPaintEvent *e) { BlueTitleClose::BlueTitleClose(QWidget *parent) : Button(parent) , a_iconFg(st::boxBlueCloseBg->c) -, _a_over(animFunc(this, &BlueTitleClose::animStep_over)) { +, _a_over(animation(this, &BlueTitleClose::step_over)) { resize(st::boxTitleHeight, st::boxTitleHeight); setCursor(style::cur_pointer); connect(this, SIGNAL(stateChanged(int, ButtonStateChangeSource)), this, SLOT(onStateChange(int, ButtonStateChangeSource))); @@ -49,18 +49,15 @@ void BlueTitleClose::onStateChange(int oldState, ButtonStateChangeSource source) } } -bool BlueTitleClose::animStep_over(float64 ms) { +void BlueTitleClose::step_over(float64 ms, bool timer) { float64 dt = ms / st::boxBlueCloseDuration; - bool res = true; if (dt >= 1) { - res = false; + _a_over.stop(); a_iconFg.finish(); } else { a_iconFg.update(dt, anim::linear); } - update((st::boxTitleHeight - st::boxBlueCloseIcon.pxWidth()) / 2, (st::boxTitleHeight - st::boxBlueCloseIcon.pxHeight()) / 2, st::boxBlueCloseIcon.pxWidth(), st::boxBlueCloseIcon.pxHeight()); - return res; - + if (timer) update((st::boxTitleHeight - st::boxBlueCloseIcon.pxWidth()) / 2, (st::boxTitleHeight - st::boxBlueCloseIcon.pxHeight()) / 2, st::boxBlueCloseIcon.pxWidth(), st::boxBlueCloseIcon.pxHeight()); } void BlueTitleClose::paintEvent(QPaintEvent *e) { @@ -156,7 +153,7 @@ void AbstractBox::paintEvent(QPaintEvent *e) { if (paint(p)) return; } -void AbstractBox::animStep(float64 ms) { +void AbstractBox::showStep(float64 ms) { if (ms >= 1) { a_opacity.finish(); _cache = QPixmap(); diff --git a/Telegram/SourceFiles/boxes/abstractbox.h b/Telegram/SourceFiles/boxes/abstractbox.h index 8f3d47f970..4920cebd13 100644 --- a/Telegram/SourceFiles/boxes/abstractbox.h +++ b/Telegram/SourceFiles/boxes/abstractbox.h @@ -41,7 +41,7 @@ public slots: void onStateChange(int oldState, ButtonStateChangeSource source); private: - bool animStep_over(float64 ms); + void step_over(float64 ms, bool timer); anim::cvalue a_iconFg; Animation _a_over; @@ -54,7 +54,7 @@ public: AbstractBox(int32 w = st::boxWideWidth); void parentResized(); - void animStep(float64 ms); + void showStep(float64 ms); void keyPressEvent(QKeyEvent *e); void resizeEvent(QResizeEvent *e); void paintEvent(QPaintEvent *e); diff --git a/Telegram/SourceFiles/boxes/addcontactbox.cpp b/Telegram/SourceFiles/boxes/addcontactbox.cpp index 05fe432e51..3ba4eac434 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.cpp +++ b/Telegram/SourceFiles/boxes/addcontactbox.cpp @@ -235,8 +235,8 @@ void AddContactBox::onImportDone(const MTPcontacts_ImportedContacts &res) { } } if (uid) { - App::main()->addNewContact(uid); - App::wnd()->hideLayer(); + Notify::userIsContactChanged(App::userLoaded(peerFromUser(uid)), true); + Ui::hideLayer(); } else { _save.hide(); _first.hide(); @@ -336,13 +336,13 @@ void NewGroupBox::resizeEvent(QResizeEvent *e) { } void NewGroupBox::onNext() { - App::wnd()->replaceLayer(new GroupInfoBox(_group.checked() ? CreatingGroupGroup : CreatingGroupChannel, true)); + Ui::showLayer(new GroupInfoBox(_group.checked() ? CreatingGroupGroup : CreatingGroupChannel, true), KeepOtherLayers); } GroupInfoBox::GroupInfoBox(CreatingGroupType creating, bool fromTypeChoose) : AbstractBox(), _creating(creating), a_photoOver(0, 0), -_a_photoOver(animFunc(this, &GroupInfoBox::animStep_photoOver)), +_a_photoOver(animation(this, &GroupInfoBox::step_photoOver)), _photoOver(false), _title(this, st::defaultInputField, lang(_creating == CreatingGroupChannel ? lng_dlg_new_channel_name : lng_dlg_new_group_name)), _description(this, st::newGroupDescription, lang(lng_create_group_description)), @@ -464,17 +464,15 @@ void GroupInfoBox::leaveEvent(QEvent *e) { updateSelected(QCursor::pos()); } -bool GroupInfoBox::animStep_photoOver(float64 ms) { +void GroupInfoBox::step_photoOver(float64 ms, bool timer) { float64 dt = ms / st::setPhotoDuration; - bool res = true; if (dt >= 1) { - res = false; + _a_photoOver.stop(); a_photoOver.finish(); } else { a_photoOver.update(dt, anim::linear); } - update(photoRect()); - return res; + if (timer) update(photoRect()); } void GroupInfoBox::onNameSubmit() { @@ -498,7 +496,7 @@ void GroupInfoBox::onNext() { return; } if (_creating == CreatingGroupGroup) { - App::wnd()->replaceLayer(new ContactsBox(title, _photoBig)); + Ui::showLayer(new ContactsBox(title, _photoBig), KeepOtherLayers); } else { bool mega = false; int32 flags = mega ? MTPchannels_CreateChannel::flag_megagroup : MTPchannels_CreateChannel::flag_broadcast; @@ -551,7 +549,7 @@ void GroupInfoBox::exportDone(const MTPExportedChatInvite &result) { if (result.type() == mtpc_chatInviteExported) { _createdChannel->invitationUrl = qs(result.c_chatInviteExported().vlink); } - App::wnd()->showLayer(new SetupChannelBox(_createdChannel)); + Ui::showLayer(new SetupChannelBox(_createdChannel)); } void GroupInfoBox::onDescriptionResized() { @@ -595,7 +593,7 @@ void GroupInfoBox::onPhoto() { } PhotoCropBox *box = new PhotoCropBox(img, (_creating == CreatingGroupChannel) ? peerFromChannel(0) : peerFromChat(0)); connect(box, SIGNAL(ready(const QImage&)), this, SLOT(onPhotoReady(const QImage&))); - App::wnd()->replaceLayer(box); + Ui::showLayer(box, KeepOtherLayers); } void GroupInfoBox::onPhotoReady(const QImage &img) { @@ -604,23 +602,25 @@ void GroupInfoBox::onPhotoReady(const QImage &img) { _photoSmall.setDevicePixelRatio(cRetinaFactor()); } -SetupChannelBox::SetupChannelBox(ChannelData *channel, bool existing) : AbstractBox(), -_channel(channel), -_existing(existing), -_public(this, qsl("channel_privacy"), 0, lang(lng_create_public_channel_title), true), -_private(this, qsl("channel_privacy"), 1, lang(lng_create_private_channel_title)), -_comments(this, lang(lng_create_channel_comments), false), -_aboutPublicWidth(width() - st::boxPadding.left() - st::boxButtonPadding.right() - st::newGroupPadding.left() - st::defaultRadiobutton.textPosition.x()), -_aboutPublic(st::normalFont, lang(lng_create_public_channel_about), _defaultOptions, _aboutPublicWidth), -_aboutPrivate(st::normalFont, lang(lng_create_private_channel_about), _defaultOptions, _aboutPublicWidth), -_aboutComments(st::normalFont, lang(lng_create_channel_comments_about), _defaultOptions, _aboutPublicWidth), -_link(this, st::defaultInputField, QString(), channel->username, true), -_linkOver(false), -_save(this, lang(lng_settings_save), st::defaultBoxButton), -_skip(this, lang(existing ? lng_cancel : lng_create_group_skip), st::cancelBoxButton), -_tooMuchUsernames(false), -_saveRequestId(0), _checkRequestId(0), -a_goodOpacity(0, 0), _a_goodFade(animFunc(this, &SetupChannelBox::animStep_goodFade)) { +SetupChannelBox::SetupChannelBox(ChannelData *channel, bool existing) : AbstractBox() +, _channel(channel) +, _existing(existing) +, _public(this, qsl("channel_privacy"), 0, lang(lng_create_public_channel_title), true) +, _private(this, qsl("channel_privacy"), 1, lang(lng_create_private_channel_title)) +, _comments(this, lang(lng_create_channel_comments), false) +, _aboutPublicWidth(width() - st::boxPadding.left() - st::boxButtonPadding.right() - st::newGroupPadding.left() - st::defaultRadiobutton.textPosition.x()) +, _aboutPublic(st::normalFont, lang(lng_create_public_channel_about), _defaultOptions, _aboutPublicWidth) +, _aboutPrivate(st::normalFont, lang(lng_create_private_channel_about), _defaultOptions, _aboutPublicWidth) +, _aboutComments(st::normalFont, lang(lng_create_channel_comments_about), _defaultOptions, _aboutPublicWidth) +, _link(this, st::defaultInputField, QString(), channel->username, true) +, _linkOver(false) +, _save(this, lang(lng_settings_save), st::defaultBoxButton) +, _skip(this, lang(existing ? lng_cancel : lng_create_group_skip), st::cancelBoxButton) +, _tooMuchUsernames(false) +, _saveRequestId(0) +, _checkRequestId(0) +, a_goodOpacity(0, 0) +, _a_goodFade(animation(this, &SetupChannelBox::step_goodFade)) { setMouseTracking(true); _checkRequestId = MTP::send(MTPchannels_CheckUsername(_channel->inputChannel, MTP_string("preston")), RPCDoneHandlerPtr(), rpcFail(&SetupChannelBox::onFirstCheckFail)); @@ -772,22 +772,20 @@ void SetupChannelBox::updateSelected(const QPoint &cursorGlobalPosition) { } } -bool SetupChannelBox::animStep_goodFade(float64 ms) { +void SetupChannelBox::step_goodFade(float64 ms, bool timer) { float dt = ms / st::newGroupLinkFadeDuration; - bool res = true; if (dt >= 1) { - res = false; + _a_goodFade.stop(); a_goodOpacity.finish(); } else { a_goodOpacity.update(dt, anim::linear); } - update(); - return res; + if (timer) update(); } void SetupChannelBox::closePressed() { if (!_existing) { - App::wnd()->showLayer(new ContactsBox(_channel)); + Ui::showLayer(new ContactsBox(_channel)); } } @@ -872,7 +870,7 @@ void SetupChannelBox::onPrivacyChange() { if (_public.checked()) { if (_tooMuchUsernames) { _private.setChecked(true); - App::wnd()->replaceLayer(new InformBox(lang(lng_channels_too_much_public))); + Ui::showLayer(new InformBox(lang(lng_channels_too_much_public)), KeepOtherLayers); return; } _link.show(); @@ -933,7 +931,7 @@ bool SetupChannelBox::onCheckFail(const RPCError &error) { QString err(error.type()); if (err == "CHANNELS_ADMIN_PUBLIC_TOO_MUCH") { if (_existing) { - App::wnd()->showLayer(new InformBox(lang(lng_channels_too_much_public_existing))); + Ui::showLayer(new InformBox(lang(lng_channels_too_much_public_existing))); } else { _tooMuchUsernames = true; _private.setChecked(true); @@ -961,7 +959,7 @@ bool SetupChannelBox::onFirstCheckFail(const RPCError &error) { QString err(error.type()); if (err == "CHANNELS_ADMIN_PUBLIC_TOO_MUCH") { if (_existing) { - App::wnd()->showLayer(new InformBox(lang(lng_channels_too_much_public_existing))); + Ui::showLayer(new InformBox(lang(lng_channels_too_much_public_existing))); } else { _tooMuchUsernames = true; _private.setChecked(true); @@ -1269,7 +1267,7 @@ void EditChannelBox::onSave() { } void EditChannelBox::onPublicLink() { - App::wnd()->replaceLayer(new SetupChannelBox(_channel, true)); + Ui::showLayer(new SetupChannelBox(_channel, true), KeepOtherLayers); } void EditChannelBox::saveDescription() { diff --git a/Telegram/SourceFiles/boxes/addcontactbox.h b/Telegram/SourceFiles/boxes/addcontactbox.h index 00c600c304..b50c715deb 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.h +++ b/Telegram/SourceFiles/boxes/addcontactbox.h @@ -113,8 +113,6 @@ public: void mousePressEvent(QMouseEvent *e); void leaveEvent(QEvent *e); - bool animStep_photoOver(float64 ms); - void setInnerFocus() { _title.setFocus(); } @@ -136,6 +134,8 @@ protected: private: + void step_photoOver(float64 ms, bool timer); + QRect photoRect() const; void updateMaxHeight(); @@ -202,7 +202,7 @@ protected: private: void updateSelected(const QPoint &cursorGlobalPosition); - bool animStep_goodFade(float64 ms); + void step_goodFade(float64 ms, bool timer); void onUpdateDone(const MTPBool &result); bool onUpdateFail(const RPCError &error); diff --git a/Telegram/SourceFiles/boxes/confirmbox.cpp b/Telegram/SourceFiles/boxes/confirmbox.cpp index 69fbe86b08..e6dd0269a9 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.cpp +++ b/Telegram/SourceFiles/boxes/confirmbox.cpp @@ -94,7 +94,7 @@ void ConfirmBox::mouseReleaseEvent(QMouseEvent *e) { _lastMousePos = e->globalPos(); updateHover(); if (textlnkOver() && textlnkOver() == textlnkDown()) { - App::wnd()->hideLayer(); + Ui::hideLayer(); textlnkOver()->onClick(e->button()); } textlnkDown(TextLinkPtr()); @@ -184,14 +184,16 @@ void ConfirmLinkBox::onOpenLink() { } else { TextLink(_url).onClick(Qt::LeftButton); } - App::wnd()->hideLayer(); + Ui::hideLayer(); } -MaxInviteBox::MaxInviteBox(const QString &link) : AbstractBox(st::boxWidth), -_close(this, lang(lng_box_ok), st::defaultBoxButton), -_text(st::boxTextFont, lng_participant_invite_sorry(lt_count, cMaxGroupCount()), _confirmBoxTextOptions, st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()), -_link(link), _linkOver(false), -a_goodOpacity(0, 0), a_good(animFunc(this, &MaxInviteBox::goodAnimStep)) { +MaxInviteBox::MaxInviteBox(const QString &link) : AbstractBox(st::boxWidth) +, _close(this, lang(lng_box_ok), st::defaultBoxButton) +, _text(st::boxTextFont, lng_participant_invite_sorry(lt_count, cMaxGroupCount()), _confirmBoxTextOptions, st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()) +, _link(link) +, _linkOver(false) +, a_goodOpacity(0, 0) +, _a_good(animation(this, &MaxInviteBox::step_good)) { setMouseTracking(true); _textWidth = st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right(); @@ -213,7 +215,7 @@ void MaxInviteBox::mousePressEvent(QMouseEvent *e) { App::app()->clipboard()->setText(_link); _goodTextLink = lang(lng_create_channel_link_copied); a_goodOpacity = anim::fvalue(1, 0); - a_good.start(); + _a_good.start(); } } @@ -232,17 +234,15 @@ void MaxInviteBox::updateSelected(const QPoint &cursorGlobalPosition) { } } -bool MaxInviteBox::goodAnimStep(float64 ms) { +void MaxInviteBox::step_good(float64 ms, bool timer) { float dt = ms / st::newGroupLinkFadeDuration; - bool res = true; if (dt >= 1) { - res = false; + _a_good.stop(); a_goodOpacity.finish(); } else { a_goodOpacity.update(dt, anim::linear); } - update(); - return res; + if (timer) update(); } void MaxInviteBox::hideAll() { diff --git a/Telegram/SourceFiles/boxes/confirmbox.h b/Telegram/SourceFiles/boxes/confirmbox.h index f2a23bf407..ba439eb957 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.h +++ b/Telegram/SourceFiles/boxes/confirmbox.h @@ -108,8 +108,7 @@ public: void mouseMoveEvent(QMouseEvent *e); void mousePressEvent(QMouseEvent *e); void leaveEvent(QEvent *e); - void updateLink(); - + protected: void hideAll(); @@ -118,7 +117,7 @@ protected: private: void updateSelected(const QPoint &cursorGlobalPosition); - bool goodAnimStep(float64 ms); + void step_good(float64 ms, bool timer); BoxButton _close; Text _text; @@ -132,5 +131,5 @@ private: QString _goodTextLink; anim::fvalue a_goodOpacity; - Animation a_good; + Animation _a_good; }; diff --git a/Telegram/SourceFiles/boxes/connectionbox.cpp b/Telegram/SourceFiles/boxes/connectionbox.cpp index 23ac63af9a..f2c24a9e38 100644 --- a/Telegram/SourceFiles/boxes/connectionbox.cpp +++ b/Telegram/SourceFiles/boxes/connectionbox.cpp @@ -27,17 +27,17 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include "window.h" -ConnectionBox::ConnectionBox() : AbstractBox(st::boxWidth), -_hostInput(this, st::connectionHostInputField, lang(lng_connection_host_ph), cConnectionProxy().host), -_portInput(this, st::connectionPortInputField, lang(lng_connection_port_ph), QString::number(cConnectionProxy().port)), -_userInput(this, st::connectionUserInputField, lang(lng_connection_user_ph), cConnectionProxy().user), -_passwordInput(this, st::connectionPasswordInputField, lang(lng_connection_password_ph), cConnectionProxy().password), -_autoRadio(this, qsl("conn_type"), dbictAuto, lang(lng_connection_auto_rb), (cConnectionType() == dbictAuto)), -_httpProxyRadio(this, qsl("conn_type"), dbictHttpProxy, lang(lng_connection_http_proxy_rb), (cConnectionType() == dbictHttpProxy)), -_tcpProxyRadio(this, qsl("conn_type"), dbictTcpProxy, lang(lng_connection_tcp_proxy_rb), (cConnectionType() == dbictTcpProxy)), -_tryIPv6(this, lang(lng_connection_try_ipv6), cTryIPv6()), -_save(this, lang(lng_connection_save), st::defaultBoxButton), -_cancel(this, lang(lng_cancel), st::cancelBoxButton) { +ConnectionBox::ConnectionBox() : AbstractBox(st::boxWidth) +, _hostInput(this, st::connectionHostInputField, lang(lng_connection_host_ph), cConnectionProxy().host) +, _portInput(this, st::connectionPortInputField, lang(lng_connection_port_ph), QString::number(cConnectionProxy().port)) +, _userInput(this, st::connectionUserInputField, lang(lng_connection_user_ph), cConnectionProxy().user) +, _passwordInput(this, st::connectionPasswordInputField, lang(lng_connection_password_ph), cConnectionProxy().password) +, _autoRadio(this, qsl("conn_type"), dbictAuto, lang(lng_connection_auto_rb), (cConnectionType() == dbictAuto)) +, _httpProxyRadio(this, qsl("conn_type"), dbictHttpProxy, lang(lng_connection_http_proxy_rb), (cConnectionType() == dbictHttpProxy)) +, _tcpProxyRadio(this, qsl("conn_type"), dbictTcpProxy, lang(lng_connection_tcp_proxy_rb), (cConnectionType() == dbictTcpProxy)) +, _tryIPv6(this, lang(lng_connection_try_ipv6), cTryIPv6()) +, _save(this, lang(lng_connection_save), st::defaultBoxButton) +, _cancel(this, lang(lng_cancel), st::cancelBoxButton) { connect(&_save, SIGNAL(clicked()), this, SLOT(onSave())); connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); @@ -215,6 +215,132 @@ void ConnectionBox::onSave() { Local::writeSettings(); MTP::restart(); reinitImageLinkManager(); + reinitWebLoadManager(); emit closed(); } } + +AutoDownloadBox::AutoDownloadBox() : AbstractBox(st::boxWidth) +, _photoPrivate(this, lang(lng_media_auto_private_chats), !(cAutoDownloadPhoto() & dbiadNoPrivate)) +, _photoGroups(this, lang(lng_media_auto_groups), !(cAutoDownloadPhoto() & dbiadNoGroups)) +, _audioPrivate(this, lang(lng_media_auto_private_chats), !(cAutoDownloadAudio() & dbiadNoPrivate)) +, _audioGroups(this, lang(lng_media_auto_groups), !(cAutoDownloadAudio() & dbiadNoGroups)) +, _gifPrivate(this, lang(lng_media_auto_private_chats), !(cAutoDownloadGif() & dbiadNoPrivate)) +, _gifGroups(this, lang(lng_media_auto_groups), !(cAutoDownloadGif() & dbiadNoGroups)) +, _gifPlay(this, lang(lng_media_auto_play), cAutoPlayGif()) +, _sectionHeight(st::boxTitleHeight + 2 * (st::defaultCheckbox.height + st::setLittleSkip)) +, _save(this, lang(lng_connection_save), st::defaultBoxButton) +, _cancel(this, lang(lng_cancel), st::cancelBoxButton) { + + setMaxHeight(3 * _sectionHeight + st::setLittleSkip + _gifPlay.height() + st::setLittleSkip + st::boxButtonPadding.top() + _save.height() + st::boxButtonPadding.bottom()); + + connect(&_save, SIGNAL(clicked()), this, SLOT(onSave())); + connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); + + prepare(); +} + +void AutoDownloadBox::hideAll() { + _photoPrivate.hide(); + _photoGroups.hide(); + _audioPrivate.hide(); + _audioGroups.hide(); + _gifPrivate.hide(); + _gifGroups.hide(); + _gifPlay.hide(); + + _save.hide(); + _cancel.hide(); +} + +void AutoDownloadBox::showAll() { + _photoPrivate.show(); + _photoGroups.show(); + _audioPrivate.show(); + _audioGroups.show(); + _gifPrivate.show(); + _gifGroups.show(); + _gifPlay.show(); + + _save.show(); + _cancel.show(); +} + +void AutoDownloadBox::paintEvent(QPaintEvent *e) { + Painter p(this); + if (paint(p)) return; + + p.setPen(st::black); + p.setFont(st::semiboldFont); + p.drawTextLeft(st::boxTitlePosition.x(), st::boxTitlePosition.y(), width(), lang(lng_media_auto_photo)); + p.drawTextLeft(st::boxTitlePosition.x(), _sectionHeight + st::boxTitlePosition.y(), width(), lang(lng_media_auto_audio)); + p.drawTextLeft(st::boxTitlePosition.x(), 2 * _sectionHeight + st::boxTitlePosition.y(), width(), lang(lng_media_auto_gif)); +} + +void AutoDownloadBox::resizeEvent(QResizeEvent *e) { + _photoPrivate.moveToLeft(st::boxTitlePosition.x(), st::boxTitleHeight + st::setLittleSkip); + _photoGroups.moveToLeft(st::boxTitlePosition.x(), _photoPrivate.y() + _photoPrivate.height() + st::setLittleSkip); + + _audioPrivate.moveToLeft(st::boxTitlePosition.x(), _sectionHeight + st::boxTitleHeight + st::setLittleSkip); + _audioGroups.moveToLeft(st::boxTitlePosition.x(), _audioPrivate.y() + _audioPrivate.height() + st::setLittleSkip); + + _gifPrivate.moveToLeft(st::boxTitlePosition.x(), 2 * _sectionHeight + st::boxTitleHeight + st::setLittleSkip); + _gifGroups.moveToLeft(st::boxTitlePosition.x(), _gifPrivate.y() + _gifPrivate.height() + st::setLittleSkip); + _gifPlay.moveToLeft(st::boxTitlePosition.x(), _gifGroups.y() + _gifGroups.height() + st::setLittleSkip); + + _save.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _save.height()); + _cancel.moveToRight(st::boxButtonPadding.right() + _save.width() + st::boxButtonPadding.left(), _save.y()); +} + +void AutoDownloadBox::onSave() { + bool changed = false; + int32 autoDownloadPhoto = (_photoPrivate.checked() ? 0 : dbiadNoPrivate) | (_photoGroups.checked() ? 0 : dbiadNoGroups); + if (cAutoDownloadPhoto() != autoDownloadPhoto) { + bool enabledPrivate = ((cAutoDownloadPhoto() & dbiadNoPrivate) && !(autoDownloadPhoto & dbiadNoPrivate)); + bool enabledGroups = ((cAutoDownloadPhoto() & dbiadNoGroups) && !(autoDownloadPhoto & dbiadNoGroups)); + cSetAutoDownloadPhoto(autoDownloadPhoto); + if (enabledPrivate || enabledGroups) { + const PhotosData &data(App::photosData()); + for (PhotosData::const_iterator i = data.cbegin(), e = data.cend(); i != e; ++i) { + i.value()->automaticLoadSettingsChanged(); + } + } + changed = true; + } + int32 autoDownloadAudio = (_audioPrivate.checked() ? 0 : dbiadNoPrivate) | (_audioGroups.checked() ? 0 : dbiadNoGroups); + if (cAutoDownloadAudio() != autoDownloadAudio) { + bool enabledPrivate = ((cAutoDownloadAudio() & dbiadNoPrivate) && !(autoDownloadAudio & dbiadNoPrivate)); + bool enabledGroups = ((cAutoDownloadAudio() & dbiadNoGroups) && !(autoDownloadAudio & dbiadNoGroups)); + cSetAutoDownloadAudio(autoDownloadAudio); + if (enabledPrivate || enabledGroups) { + const AudiosData &data(App::audiosData()); + for (AudiosData::const_iterator i = data.cbegin(), e = data.cend(); i != e; ++i) { + i.value()->automaticLoadSettingsChanged(); + } + } + changed = true; + } + int32 autoDownloadGif = (_gifPrivate.checked() ? 0 : dbiadNoPrivate) | (_gifGroups.checked() ? 0 : dbiadNoGroups); + if (cAutoDownloadGif() != autoDownloadGif) { + bool enabledPrivate = ((cAutoDownloadGif() & dbiadNoPrivate) && !(autoDownloadGif & dbiadNoPrivate)); + bool enabledGroups = ((cAutoDownloadGif() & dbiadNoGroups) && !(autoDownloadGif & dbiadNoGroups)); + cSetAutoDownloadGif(autoDownloadGif); + if (enabledPrivate || enabledGroups) { + const DocumentsData &data(App::documentsData()); + for (DocumentsData::const_iterator i = data.cbegin(), e = data.cend(); i != e; ++i) { + i.value()->automaticLoadSettingsChanged(); + } + Notify::automaticLoadSettingsChangedGif(); + } + changed = true; + } + if (cAutoPlayGif() != _gifPlay.checked()) { + cSetAutoPlayGif(_gifPlay.checked()); + if (!cAutoPlayGif()) { + App::stopGifItems(); + } + changed = true; + } + if (changed) Local::writeUserSettings(); + onClose(); +} diff --git a/Telegram/SourceFiles/boxes/connectionbox.h b/Telegram/SourceFiles/boxes/connectionbox.h index f8bbce5ab4..79a43f378f 100644 --- a/Telegram/SourceFiles/boxes/connectionbox.h +++ b/Telegram/SourceFiles/boxes/connectionbox.h @@ -54,3 +54,32 @@ private: BoxButton _save, _cancel; }; + +class AutoDownloadBox : public AbstractBox { + Q_OBJECT + +public: + + AutoDownloadBox(); + void paintEvent(QPaintEvent *e); + void resizeEvent(QResizeEvent *e); + +public slots: + + void onSave(); + +protected: + + void hideAll(); + void showAll(); + +private: + + Checkbox _photoPrivate, _photoGroups; + Checkbox _audioPrivate, _audioGroups; + Checkbox _gifPrivate, _gifGroups, _gifPlay; + + int32 _sectionHeight; + + BoxButton _save, _cancel; +}; diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp index a7827d193b..4ac079ff9f 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.cpp +++ b/Telegram/SourceFiles/boxes/contactsbox.cpp @@ -228,8 +228,8 @@ void ContactsInner::onAddBot() { } else { App::main()->addParticipants(_addToPeer, QVector(1, _bot)); } - App::wnd()->hideLayer(); - App::main()->showPeerHistory(_addToPeer->id, ShowAtUnreadMsgId); + Ui::hideLayer(); + Ui::showPeerHistory(_addToPeer, ShowAtUnreadMsgId); } void ContactsInner::onAddAdmin() { @@ -269,9 +269,9 @@ bool ContactsInner::addAdminFail(const RPCError &error, mtpRequestId req) { _addAdminRequestId = 0; if (_addAdminBox) _addAdminBox->onClose(); if (error.type() == "USERS_TOO_MUCH") { - App::wnd()->replaceLayer(new MaxInviteBox(_channel->invitationUrl)); + Ui::showLayer(new MaxInviteBox(_channel->invitationUrl), KeepOtherLayers); } else if (error.type() == "ADMINS_TOO_MUCH") { - App::wnd()->replaceLayer(new InformBox(lang(lng_channel_admins_too_much))); + Ui::showLayer(new InformBox(lang(lng_channel_admins_too_much)), KeepOtherLayers); } else { emit adminAdded(); } @@ -292,7 +292,7 @@ void ContactsInner::peerUpdated(PeerData *peer) { inited = true; } if (!_chat->canEdit()) { - App::wnd()->hideLayer(); + Ui::hideLayer(); } else if (!_chat->participants.isEmpty()) { for (ContactsData::iterator i = _contactsData.begin(), e = _contactsData.end(); i != e; ++i) { delete i.value(); @@ -741,16 +741,16 @@ void ContactsInner::chooseParticipant() { _addAdminBox = new ConfirmBox(lng_channel_admin_sure(lt_user, _addAdmin->firstName)); connect(_addAdminBox, SIGNAL(confirmed()), this, SLOT(onAddAdmin())); connect(_addAdminBox, SIGNAL(destroyed(QObject*)), this, SLOT(onNoAddAdminBox(QObject*))); - App::wnd()->replaceLayer(_addAdminBox); + Ui::showLayer(_addAdminBox, KeepOtherLayers); } else if (bot() && (peer->isChat() || peer->isMegagroup())) { _addToPeer = peer; ConfirmBox *box = new ConfirmBox(lng_bot_sure_invite(lt_group, peer->name)); connect(box, SIGNAL(confirmed()), this, SLOT(onAddBot())); - App::wnd()->replaceLayer(box); + Ui::showLayer(box, KeepOtherLayers); } else { App::wnd()->hideSettings(true); App::main()->choosePeer(peer->id, ShowAtUnreadMsgId); - App::wnd()->hideLayer(); + Ui::hideLayer(); } } } @@ -1558,7 +1558,7 @@ void ContactsBox::resizeEvent(QResizeEvent *e) { void ContactsBox::closePressed() { if (_inner.channel() && !_inner.hasAlreadyMembersInChannel()) { - App::main()->showPeerHistory(_inner.channel()->id, ShowAtTheEndMsgId); + Ui::showPeerHistory(_inner.channel(), ShowAtTheEndMsgId); } } @@ -1590,8 +1590,8 @@ void ContactsBox::onInvite() { App::main()->addParticipants(_inner.chat() ? (PeerData*)_inner.chat() : _inner.channel(), users); if (_inner.chat()) { - App::wnd()->hideLayer(); - App::main()->showPeerHistory(_inner.chat()->id, ShowAtTheEndMsgId); + Ui::hideLayer(); + Ui::showPeerHistory(_inner.chat(), ShowAtTheEndMsgId); } else { onClose(); } @@ -1713,7 +1713,7 @@ void ContactsBox::onScroll() { } void ContactsBox::creationDone(const MTPUpdates &updates) { - App::wnd()->hideLayer(); + Ui::hideLayer(); App::main()->sentUpdatesReceived(updates); const QVector *v = 0; @@ -1730,7 +1730,7 @@ void ContactsBox::creationDone(const MTPUpdates &updates) { if (!_creationPhoto.isNull()) { App::app()->uploadProfilePhoto(_creationPhoto, peer->id); } - App::main()->showPeerHistory(peer->id, ShowAtUnreadMsgId); + Ui::showPeerHistory(peer, ShowAtUnreadMsgId); } } else { LOG(("API Error: chat not found in updates (ContactsBox::creationDone)")); @@ -1749,7 +1749,7 @@ bool ContactsBox::creationFail(const RPCError &error) { _filter.showError(); return true; } else if (error.type() == "PEER_FLOOD") { - App::wnd()->replaceLayer(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))))); + 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); return true; } return false; @@ -1872,7 +1872,7 @@ void MembersInner::mouseReleaseEvent(QMouseEvent *e) { _kickBox = new ConfirmBox((_filter == MembersFilterRecent ? (_channel->isMegagroup() ? lng_profile_sure_kick : lng_profile_sure_kick_channel) : lng_profile_sure_kick_admin)(lt_user, _kickConfirm->firstName)); connect(_kickBox, SIGNAL(confirmed()), this, SLOT(onKickConfirm())); connect(_kickBox, SIGNAL(destroyed(QObject*)), this, SLOT(onKickBoxDestroyed(QObject*))); - App::wnd()->replaceLayer(_kickBox); + Ui::showLayer(_kickBox, KeepOtherLayers); } _kickDown = -1; } @@ -1993,7 +1993,7 @@ void MembersInner::chooseParticipant() { } if (_sel < 0 || _sel >= _rows.size()) return; if (PeerData *peer = _rows[_sel]) { - App::wnd()->hideLayer(); + Ui::hideLayer(); App::main()->showPeerProfile(peer, ShowAtUnreadMsgId); } } @@ -2199,7 +2199,7 @@ void MembersInner::membersReceived(const MTPchannels_ChannelParticipants &result bool MembersInner::membersFailed(const RPCError &error, mtpRequestId req) { if (mtpIsFlood(error)) return false; - App::wnd()->hideLayer(); + Ui::hideLayer(); return true; } @@ -2298,16 +2298,16 @@ void MembersBox::onScroll() { void MembersBox::onAdd() { if (_inner.filter() == MembersFilterRecent && _inner.channel()->count >= (_inner.channel()->isMegagroup() ? cMaxMegaGroupCount() : cMaxGroupCount())) { - App::wnd()->replaceLayer(new MaxInviteBox(_inner.channel()->invitationUrl)); + Ui::showLayer(new MaxInviteBox(_inner.channel()->invitationUrl), KeepOtherLayers); return; } ContactsBox *box = new ContactsBox(_inner.channel(), _inner.filter(), _inner.already()); if (_inner.filter() == MembersFilterRecent) { - App::wnd()->showLayer(box); + Ui::showLayer(box); } else { _addBox = box; connect(_addBox, SIGNAL(adminAdded()), this, SLOT(onAdminAdded())); - App::wnd()->replaceLayer(_addBox); + Ui::showLayer(_addBox, KeepOtherLayers); } } diff --git a/Telegram/SourceFiles/boxes/languagebox.cpp b/Telegram/SourceFiles/boxes/languagebox.cpp index c1aae95e6f..3ec7ea7eac 100644 --- a/Telegram/SourceFiles/boxes/languagebox.cpp +++ b/Telegram/SourceFiles/boxes/languagebox.cpp @@ -84,16 +84,16 @@ void LanguageBox::mousePressEvent(QMouseEvent *e) { for (int32 i = 1; i < languageCount; ++i) { LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[i] + qsl(".strings"), LangLoaderRequest(lngkeys_cnt)); if (!loader.errors().isEmpty()) { - App::wnd()->showLayer(new InformBox(qsl("Lang \"") + LanguageCodes[i] + qsl("\" error :(\n\nError: ") + loader.errors())); + Ui::showLayer(new InformBox(qsl("Lang \"") + LanguageCodes[i] + qsl("\" error :(\n\nError: ") + loader.errors())); return; } else if (!loader.warnings().isEmpty()) { QString warn = loader.warnings(); if (warn.size() > 256) warn = warn.mid(0, 254) + qsl(".."); - App::wnd()->showLayer(new InformBox(qsl("Lang \"") + LanguageCodes[i] + qsl("\" warnings :(\n\nWarnings: ") + warn)); + Ui::showLayer(new InformBox(qsl("Lang \"") + LanguageCodes[i] + qsl("\" warnings :(\n\nWarnings: ") + warn)); return; } } - App::wnd()->showLayer(new InformBox(qsl("Everything seems great in all %1 languages!").arg(languageCount - 1))); + Ui::showLayer(new InformBox(qsl("Everything seems great in all %1 languages!").arg(languageCount - 1))); } } @@ -124,7 +124,7 @@ void LanguageBox::onChange() { ConfirmBox *box = new ConfirmBox(text, save, st::defaultBoxButton, cancel); connect(box, SIGNAL(confirmed()), this, SLOT(onSave())); connect(box, SIGNAL(closed()), this, SLOT(onRestore())); - App::wnd()->replaceLayer(box); + Ui::showLayer(box, KeepOtherLayers); } } } diff --git a/Telegram/SourceFiles/boxes/passcodebox.cpp b/Telegram/SourceFiles/boxes/passcodebox.cpp index b98f8957d1..45055b2cf4 100644 --- a/Telegram/SourceFiles/boxes/passcodebox.cpp +++ b/Telegram/SourceFiles/boxes/passcodebox.cpp @@ -280,7 +280,7 @@ void PasscodeBox::setPasswordDone(const MTPBool &result) { _setRequest = 0; emit reloadPassword(); ConfirmBox *box = new InformBox(lang(_reenterPasscode.isHidden() ? lng_cloud_password_removed : (_oldPasscode.isHidden() ? lng_cloud_password_was_set : lng_cloud_password_updated))); - App::wnd()->showLayer(box); + Ui::showLayer(box); } bool PasscodeBox::setPasswordFail(const RPCError &error) { @@ -308,7 +308,7 @@ bool PasscodeBox::setPasswordFail(const RPCError &error) { _recoverEmail.showError(); update(); } else if (err == "EMAIL_UNCONFIRMED") { - App::wnd()->showLayer(new InformBox(lang(lng_cloud_password_almost))); + Ui::showLayer(new InformBox(lang(lng_cloud_password_almost))); emit reloadPassword(); } else if (mtpIsFlood(error)) { if (_oldPasscode.isHidden()) return false; @@ -385,7 +385,7 @@ void PasscodeBox::onSave(bool force) { _replacedBy = new ConfirmBox(lang(lng_cloud_password_about_recover), lang(lng_cloud_password_skip_email), st::attentionBoxButton); connect(_replacedBy, SIGNAL(confirmed()), this, SLOT(onForceNoMail())); connect(_replacedBy, SIGNAL(destroyed(QObject*)), this, SLOT(onBoxDestroyed(QObject*))); - App::wnd()->replaceLayer(_replacedBy); + Ui::showLayer(_replacedBy, KeepOtherLayers); } else { QByteArray newPasswordData = pwd.isEmpty() ? QByteArray() : (_newSalt + pwd.toUtf8() + _newSalt); QByteArray newPasswordHash = pwd.isEmpty() ? QByteArray() : QByteArray(32, Qt::Uninitialized); @@ -481,7 +481,7 @@ void PasscodeBox::recover() { connect(_replacedBy, SIGNAL(reloadPassword()), this, SIGNAL(reloadPassword())); connect(_replacedBy, SIGNAL(recoveryExpired()), this, SLOT(onRecoverExpired())); connect(_replacedBy, SIGNAL(destroyed(QObject*)), this, SLOT(onBoxDestroyed(QObject*))); - App::wnd()->replaceLayer(_replacedBy); + Ui::showLayer(_replacedBy, KeepOtherLayers); } void PasscodeBox::recoverStarted(const MTPauth_PasswordRecovery &result) { @@ -583,7 +583,7 @@ void RecoverBox::codeSubmitDone(bool recover, const MTPauth_Authorization &resul _submitRequest = 0; emit reloadPassword(); - App::wnd()->showLayer(new InformBox(lang(lng_cloud_password_removed))); + Ui::showLayer(new InformBox(lang(lng_cloud_password_removed))); } bool RecoverBox::codeSubmitFail(const RPCError &error) { @@ -592,7 +592,7 @@ bool RecoverBox::codeSubmitFail(const RPCError &error) { const QString &err = error.type(); if (err == "PASSWORD_EMPTY") { emit reloadPassword(); - App::wnd()->showLayer(new InformBox(lang(lng_cloud_password_removed))); + Ui::showLayer(new InformBox(lang(lng_cloud_password_removed))); return true; } else if (err == "PASSWORD_RECOVERY_NA") { onClose(); diff --git a/Telegram/SourceFiles/boxes/photosendbox.cpp b/Telegram/SourceFiles/boxes/photosendbox.cpp index 1775207011..d9d933e30b 100644 --- a/Telegram/SourceFiles/boxes/photosendbox.cpp +++ b/Telegram/SourceFiles/boxes/photosendbox.cpp @@ -29,31 +29,67 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org PhotoSendBox::PhotoSendBox(const FileLoadResultPtr &file) : AbstractBox(st::boxWideWidth) , _file(file) -, _thumbx(0) -, _thumby(0) -, _thumbw(0) -, _thumbh(0) -, _namew(0) -, _textw(0) +, _animated(false) , _caption(this, st::confirmCaptionArea, lang(lng_photo_caption)) , _compressedFromSettings(_file->type == PrepareAuto) , _compressed(this, lang(lng_send_image_compressed), _compressedFromSettings ? cCompressPastedImage() : true) , _send(this, lang(lng_send_button), st::defaultBoxButton) , _cancel(this, lang(lng_cancel), st::cancelBoxButton) +, _thumbx(0) +, _thumby(0) +, _thumbw(0) +, _thumbh(0) +, _statusw(0) +, _isImage(false) , _replyTo(_file->to.replyTo) , _confirmed(false) { connect(&_send, SIGNAL(clicked()), this, SLOT(onSend())); connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); + + _animated = false; + QSize dimensions; 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); + for (int32 i = 0, l = attributes.size(); i < l; ++i) { + if (attributes.at(i).type() == mtpc_documentAttributeAnimated) { + _animated = true; + } else if (attributes.at(i).type() == mtpc_documentAttributeImageSize) { + dimensions = QSize(attributes.at(i).c_documentAttributeImageSize().vw.v, attributes.at(i).c_documentAttributeImageSize().vh.v); + } else if (attributes.at(i).type() == mtpc_documentAttributeVideo) { + dimensions = QSize(attributes.at(i).c_documentAttributeVideo().vw.v, attributes.at(i).c_documentAttributeVideo().vh.v); + } + } + if (dimensions.isEmpty()) _animated = false; } - if (_file->type == PreparePhoto) { + if (_file->type == PreparePhoto || _animated) { int32 maxW = 0, maxH = 0; - for (PreparedPhotoThumbs::const_iterator i = _file->photoThumbs.cbegin(), e = _file->photoThumbs.cend(); i != e; ++i) { - if (i->width() >= maxW && i->height() >= maxH) { - _thumb = *i; - maxW = _thumb.width(); - maxH = _thumb.height(); + if (_animated) { + int32 limitW = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); + int32 limitH = st::confirmMaxHeight; + maxW = dimensions.width(); + maxH = dimensions.height(); + if (maxW * limitH > maxH * limitW) { + if (maxW < limitW) { + maxH = maxH * limitW / maxW; + maxW = limitW; + } + } else { + if (maxH < limitH) { + maxW = maxW * limitH / maxH; + maxH = limitH; + } + } + _thumb = imagePix(_file->thumb.toImage(), maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), true, true, false, maxW, maxH); + } else { + for (PreparedPhotoThumbs::const_iterator i = _file->photoThumbs.cbegin(), e = _file->photoThumbs.cend(); i != e; ++i) { + if (i->width() >= maxW && i->height() >= maxH) { + _thumb = *i; + maxW = _thumb.width(); + maxH = _thumb.height(); + } } } int32 tw = _thumb.width(), th = _thumb.height(); @@ -78,32 +114,28 @@ PhotoSendBox::PhotoSendBox(const FileLoadResultPtr &file) : AbstractBox(st::boxW _thumb = QPixmap::fromImage(_thumb.toImage().scaled(_thumbw * cIntRetinaFactor(), _thumbh * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly); _thumb.setDevicePixelRatio(cRetinaFactor()); } else { - _compressed.hide(); - if (!_file->thumb.isNull()) { + if (_file->thumb.isNull()) { + _thumbw = 0; + } else { _thumb = _file->thumb; int32 tw = _thumb.width(), th = _thumb.height(); - if (_thumb.isNull() || !tw || !th) { - _thumbw = _thumbx = _thumby = 0; - } else if (tw > th) { - _thumbw = (tw * st::mediaThumbSize) / th; - _thumbx = (_thumbw - st::mediaThumbSize) / 2; - _thumby = 0; + if (tw > th) { + _thumbw = (tw * st::msgFileThumbSize) / th; } else { - _thumbw = st::mediaThumbSize; - _thumbx = 0; - _thumby = ((th * _thumbw) / tw - st::mediaThumbSize) / 2; + _thumbw = st::msgFileThumbSize; } - } - if (_thumbw) { - _thumb = QPixmap::fromImage(_thumb.toImage().scaledToWidth(_thumbw * cIntRetinaFactor(), Qt::SmoothTransformation), Qt::ColorOnly); - _thumb.setDevicePixelRatio(cRetinaFactor()); + _thumb = imagePix(_thumb.toImage(), _thumbw * cIntRetinaFactor(), 0, true, false, true, st::msgFileThumbSize, st::msgFileThumbSize); } - _name = _file->filename; - _namew = st::mediaFont->width(_name); - _size = formatSizeText(_file->filesize); - _textw = qMax(_namew, st::mediaFont->width(_size)); + _name.setText(st::semiboldFont, _file->filename, _textNameOptions); + _status = formatSizeText(_file->filesize); + _statusw = qMax(_name.maxWidth(), st::normalFont->width(_status)); + _isImage = fileIsImage(_file->filename, _file->filemime); } + if (_file->type != PreparePhoto) { + _compressed.hide(); + } + updateBoxSize(); _caption.setMaxLength(MaxPhotoCaption); _caption.setCtrlEnterSubmit(CtrlEnterSubmitBoth); @@ -111,20 +143,21 @@ PhotoSendBox::PhotoSendBox(const FileLoadResultPtr &file) : AbstractBox(st::boxW connect(&_caption, SIGNAL(resized()), this, SLOT(onCaptionResized())); connect(&_caption, SIGNAL(submitted(bool)), this, SLOT(onSend(bool))); connect(&_caption, SIGNAL(cancelled()), this, SLOT(onClose())); + prepare(); } PhotoSendBox::PhotoSendBox(const QString &phone, const QString &fname, const QString &lname, MsgId replyTo) : AbstractBox(st::boxWideWidth) -, _thumbx(0) -, _thumby(0) -, _thumbw(0) -, _thumbh(0) -, _namew(0) -, _textw(0) , _caption(this, st::confirmCaptionArea, lang(lng_photo_caption)) , _compressed(this, lang(lng_send_image_compressed), true) , _send(this, lang(lng_send_button), st::defaultBoxButton) , _cancel(this, lang(lng_cancel), st::cancelBoxButton) +, _thumbx(0) +, _thumby(0) +, _thumbw(0) +, _thumbh(0) +, _statusw(0) +, _isImage(false) , _phone(phone) , _fname(fname) , _lname(lname) @@ -135,10 +168,9 @@ PhotoSendBox::PhotoSendBox(const QString &phone, const QString &fname, const QSt _compressed.hide(); - _name = lng_full_name(lt_first_name, _fname, lt_last_name, _lname); - _namew = st::mediaFont->width(_name); - _size = _phone; - _textw = qMax(_namew, st::mediaFont->width(_size)); + _name.setText(st::semiboldFont, lng_full_name(lt_first_name, _fname, lt_last_name, _lname), _textNameOptions); + _status = _phone; + _statusw = qMax(_name.maxWidth(), st::normalFont->width(_status)); updateBoxSize(); prepare(); @@ -163,10 +195,12 @@ void PhotoSendBox::onCaptionResized() { } void PhotoSendBox::updateBoxSize() { - if (_file && _file->type == PreparePhoto) { - setMaxHeight(st::boxPhotoPadding.top() + _thumbh + st::boxPhotoPadding.bottom() + st::boxPhotoCompressedPadding.top() + _compressed.height() + (_compressed.checked() ? (st::boxPhotoCompressedPadding.bottom() + _caption.height()) : 0) + st::boxButtonPadding.top() + _send.height() + st::boxButtonPadding.bottom()); + if (_file && (_file->type == PreparePhoto || _animated)) { + setMaxHeight(st::boxPhotoPadding.top() + _thumbh + st::boxPhotoPadding.bottom() + (_animated ? 0 : (st::boxPhotoCompressedPadding.top() + _compressed.height())) + st::boxPhotoCompressedPadding.bottom() + _caption.height() + st::boxButtonPadding.top() + _send.height() + st::boxButtonPadding.bottom()); + } else if (_thumbw) { + setMaxHeight(st::boxPhotoPadding.top() + st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom() + (_file ? (st::boxPhotoCompressedPadding.bottom() + _caption.height()) : 0) + st::boxPhotoPadding.bottom() + st::boxButtonPadding.top() + _send.height() + st::boxButtonPadding.bottom()); } else { - setMaxHeight(st::boxPhotoPadding.top() + st::mediaPadding.top() + st::mediaThumbSize + st::mediaPadding.bottom() + st::boxPhotoPadding.bottom() + st::boxButtonPadding.top() + _send.height() + st::boxButtonPadding.bottom()); + setMaxHeight(st::boxPhotoPadding.top() + st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() + (_file ? (st::boxPhotoCompressedPadding.bottom() + _caption.height()) : 0) + st::boxPhotoPadding.bottom() + st::boxButtonPadding.top() + _send.height() + st::boxButtonPadding.bottom()); } } @@ -182,7 +216,7 @@ void PhotoSendBox::paintEvent(QPaintEvent *e) { Painter p(this); if (paint(p)) return; - if (_file && _file->type == PreparePhoto) { + if (_file && (_file->type == PreparePhoto || _animated)) { if (_thumbx > st::boxPhotoPadding.left()) { p.fillRect(st::boxPhotoPadding.left(), st::boxPhotoPadding.top(), _thumbx - st::boxPhotoPadding.left(), _thumbh, st::confirmBg->b); } @@ -190,36 +224,66 @@ void PhotoSendBox::paintEvent(QPaintEvent *e) { p.fillRect(_thumbx + _thumbw, st::boxPhotoPadding.top(), width() - st::boxPhotoPadding.right() - _thumbx - _thumbw, _thumbh, st::confirmBg->b); } p.drawPixmap(_thumbx, st::boxPhotoPadding.top(), _thumb); + if (_animated) { + QRect inner(_thumbx + (_thumbw - st::msgFileSize) / 2, st::boxPhotoPadding.top() + (_thumbh - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); + p.setPen(Qt::NoPen); + p.setBrush(st::msgDateImgBg); + + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.drawEllipse(inner); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + + p.drawSpriteCenter(inner, st::msgFileInPlay); + } } else { - int32 w = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(), h = st::mediaPadding.top() + st::mediaThumbSize + st::mediaPadding.bottom(); - int32 tleft = st::mediaPadding.left() + st::mediaThumbSize + st::mediaPadding.right(); - int32 twidth = w - tleft - st::mediaPadding.right(); - if (twidth > _textw) { - w -= (twidth - _textw); - twidth = _textw; + int32 w = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); + int32 h = _thumbw ? (st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom()) : (st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom()); + int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0; + if (_thumbw) { + nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); + nametop = st::msgFileThumbNameTop; + nameright = st::msgFileThumbPadding.left(); + statustop = st::msgFileThumbStatusTop; + linktop = st::msgFileThumbLinkTop; + } else { + nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); + nametop = st::msgFileNameTop; + nameright = st::msgFilePadding.left(); + statustop = st::msgFileStatusTop; + } + int32 namewidth = w - nameleft - (_thumbw ? st::msgFileThumbPadding.left() : st::msgFilePadding.left()); + if (namewidth > _statusw) { + w -= (namewidth - _statusw); + namewidth = _statusw; } int32 x = (width() - w) / 2, y = st::boxPhotoPadding.top(); App::roundRect(p, x, y, w, h, st::msgOutBg, MessageOutCorners, &st::msgOutShadow); + if (_thumbw) { - int32 rf(cIntRetinaFactor()); - p.drawPixmap(QPoint(x + st::mediaPadding.left(), y + st::mediaPadding.top()), _thumb, QRect(_thumbx * rf, _thumby * rf, st::mediaThumbSize * rf, st::mediaThumbSize * rf)); + QRect rthumb(rtlrect(x + st::msgFileThumbPadding.left(), y + st::msgFileThumbPadding.top(), st::msgFileThumbSize, st::msgFileThumbSize, width())); + p.drawPixmap(rthumb.topLeft(), _thumb); } else if (_file) { - p.drawPixmap(QPoint(x + st::mediaPadding.left(), y + st::mediaPadding.top()), App::sprite(), st::mediaDocOutImg); - } else { - p.drawPixmap(x + st::mediaPadding.left(), y + st::mediaPadding.top(), userDefPhoto(1)->pix(st::mediaThumbSize)); - } + QRect inner(rtlrect(x + st::msgFilePadding.left(), y + st::msgFilePadding.top(), st::msgFileSize, st::msgFileSize, width())); + p.setPen(Qt::NoPen); + p.setBrush(st::msgFileOutBg); - p.setFont(st::mediaFont->f); - p.setPen(st::black->c); - if (twidth < _namew) { - p.drawText(x + tleft, y + st::mediaPadding.top() + st::mediaNameTop + st::mediaFont->ascent, st::mediaFont->elided(_name, twidth)); - } else { - p.drawText(x + tleft, y + st::mediaPadding.top() + st::mediaNameTop + st::mediaFont->ascent, _name); - } + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.drawEllipse(inner); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); - p.setPen(st::mediaOutColor->p); - p.drawText(x + tleft, y + st::mediaPadding.top() + st::mediaThumbSize - st::mediaDetailsShift - st::mediaFont->descent, _size); + p.drawSpriteCenter(inner, _isImage ? st::msgFileOutImage : st::msgFileOutFile); + } else { + p.drawPixmapLeft(x + st::msgFilePadding.left(), y + st::msgFilePadding.top(), width(), userDefPhoto(1)->pixRounded(st::msgFileSize)); + } + p.setFont(st::semiboldFont); + p.setPen(st::black); + _name.drawLeftElided(p, x + nameleft, y + nametop, namewidth, width()); + + style::color status(st::mediaOutFg); + p.setFont(st::normalFont); + p.setPen(status); + p.drawTextLeft(x + nameleft, y + statustop, width(), _status); } } @@ -251,13 +315,11 @@ void PhotoSendBox::hideAll() { void PhotoSendBox::showAll() { _send.show(); _cancel.show(); - if (_file && _file->type == PreparePhoto) { - _compressed.show(); - if (_compressed.checked()) { - _caption.show(); - } else { - _caption.hide(); + if (_file) { + if (_file->type == PreparePhoto) { + _compressed.show(); } + _caption.show(); } else { _caption.hide(); _compressed.hide(); @@ -287,7 +349,7 @@ void PhotoSendBox::onSend(bool ctrlShiftEnter) { } } if (!_caption.isHidden()) { - _file->photoCaption = prepareText(_caption.getLastText(), true); + _file->caption = prepareText(_caption.getLastText(), true); } App::main()->onSendFileConfirm(_file, ctrlShiftEnter); } else { diff --git a/Telegram/SourceFiles/boxes/photosendbox.h b/Telegram/SourceFiles/boxes/photosendbox.h index 597c4496ac..2c955dfd00 100644 --- a/Telegram/SourceFiles/boxes/photosendbox.h +++ b/Telegram/SourceFiles/boxes/photosendbox.h @@ -65,16 +65,23 @@ private: void updateBoxSize(); FileLoadResultPtr _file; - int32 _thumbx, _thumby, _thumbw, _thumbh; - QString _name, _size; - int32 _namew, _textw; + bool _animated; + + QPixmap _thumb; + InputArea _caption; bool _compressedFromSettings; Checkbox _compressed; BoxButton _send, _cancel; - QPixmap _thumb; + + int32 _thumbx, _thumby, _thumbw, _thumbh; + Text _name; + QString _status; + int32 _statusw; + bool _isImage; QString _phone, _fname, _lname; + MsgId _replyTo; bool _confirmed; diff --git a/Telegram/SourceFiles/boxes/sessionsbox.cpp b/Telegram/SourceFiles/boxes/sessionsbox.cpp index 8d58681ff5..6cc62071db 100644 --- a/Telegram/SourceFiles/boxes/sessionsbox.cpp +++ b/Telegram/SourceFiles/boxes/sessionsbox.cpp @@ -115,7 +115,7 @@ void SessionsInner::onTerminate() { _terminateBox = new ConfirmBox(lang(lng_settings_reset_one_sure), lang(lng_settings_reset_button), st::attentionBoxButton); connect(_terminateBox, SIGNAL(confirmed()), this, SLOT(onTerminateSure())); connect(_terminateBox, SIGNAL(destroyed(QObject*)), this, SLOT(onNoTerminateBox(QObject*))); - App::wnd()->replaceLayer(_terminateBox); + Ui::showLayer(_terminateBox, KeepOtherLayers); } } } @@ -138,7 +138,7 @@ void SessionsInner::onTerminateAll() { _terminateBox = new ConfirmBox(lang(lng_settings_reset_sure), lang(lng_settings_reset_button), st::attentionBoxButton); connect(_terminateBox, SIGNAL(confirmed()), this, SLOT(onTerminateAllSure())); connect(_terminateBox, SIGNAL(destroyed(QObject*)), this, SLOT(onNoTerminateBox(QObject*))); - App::wnd()->replaceLayer(_terminateBox); + Ui::showLayer(_terminateBox, KeepOtherLayers); } void SessionsInner::onTerminateAllSure() { diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index cfa0790d31..4424755959 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -43,6 +43,7 @@ _input(set), _installRequest(0) { void StickerSetInner::gotSet(const MTPmessages_StickerSet &set) { _pack.clear(); + _emoji.clear(); if (set.type() == mtpc_messages_stickerSet) { const MTPDmessages_stickerSet &d(set.c_messages_stickerSet()); const QVector &v(d.vdocuments.c_vector().v); @@ -50,9 +51,26 @@ void StickerSetInner::gotSet(const MTPmessages_StickerSet &set) { for (int32 i = 0, l = v.size(); i < l; ++i) { DocumentData *doc = App::feedDocument(v.at(i)); if (!doc || !doc->sticker()) continue; - + _pack.push_back(doc); } + const QVector &packs(d.vpacks.c_vector().v); + for (int32 i = 0, l = packs.size(); i < l; ++i) { + if (packs.at(i).type() != mtpc_stickerPack) continue; + const MTPDstickerPack &pack(packs.at(i).c_stickerPack()); + if (EmojiPtr e = emojiGetNoColor(emojiFromText(qs(pack.vemoticon)))) { + const QVector &stickers(pack.vdocuments.c_vector().v); + StickerPack p; + p.reserve(stickers.size()); + for (int32 j = 0, c = stickers.size(); j < c; ++j) { + DocumentData *doc = App::document(stickers.at(j).v); + if (!doc || !doc->sticker()) continue; + + p.push_back(doc); + } + _emoji.insert(e, p); + } + } if (d.vset.type() == mtpc_stickerSet) { const MTPDstickerSet &s(d.vset.c_stickerSet()); _setTitle = stickerSetTitle(s); @@ -67,7 +85,7 @@ void StickerSetInner::gotSet(const MTPmessages_StickerSet &set) { } if (_pack.isEmpty()) { - App::wnd()->showLayer(new InformBox(lang(lng_stickers_not_found))); + Ui::showLayer(new InformBox(lang(lng_stickers_not_found))); } else { int32 rows = _pack.size() / StickerPanPerRow + ((_pack.size() % StickerPanPerRow) ? 1 : 0); resize(st::stickersPadding.left() + StickerPanPerRow * st::stickersSize.width(), st::stickersPadding.top() + rows * st::stickersSize.height() + st::stickersPadding.bottom()); @@ -82,7 +100,7 @@ bool StickerSetInner::failedSet(const RPCError &error) { _loaded = true; - App::wnd()->showLayer(new InformBox(lang(lng_stickers_not_found))); + Ui::showLayer(new InformBox(lang(lng_stickers_not_found))); return true; } @@ -91,7 +109,12 @@ void StickerSetInner::installDone(const MTPBool &result) { StickerSets &sets(cRefStickerSets()); _setFlags &= ~MTPDstickerSet::flag_disabled; - sets.insert(_setId, StickerSet(_setId, _setAccess, _setTitle, _setShortName, _setCount, _setHash, _setFlags)).value().stickers = _pack; + StickerSets::iterator it = sets.find(_setId); + if (it == sets.cend()) { + it = sets.insert(_setId, StickerSet(_setId, _setAccess, _setTitle, _setShortName, _setCount, _setHash, _setFlags)); + } + it.value().stickers = _pack; + it.value().emoji = _emoji; StickerSetsOrder &order(cRefStickerSetsOrder()); int32 insertAtIndex = 0, currentIndex = order.indexOf(_setId); @@ -112,16 +135,15 @@ void StickerSetInner::installDone(const MTPBool &result) { sets.erase(custom); } } - cSetStickersHash(stickersCountHash()); Local::writeStickers(); emit installed(_setId); - App::wnd()->hideLayer(); + Ui::hideLayer(); } bool StickerSetInner::installFailed(const RPCError &error) { if (mtpIsFlood(error)) return false; - App::wnd()->showLayer(new InformBox(lang(lng_stickers_not_found))); + Ui::showLayer(new InformBox(lang(lng_stickers_not_found))); return true; } @@ -147,15 +169,14 @@ void StickerSetInner::paintEvent(QPaintEvent *e) { if (goodThumb) { doc->thumb->load(); } else { - bool already = !doc->already().isEmpty(), hasdata = !doc->data.isEmpty(); - if (!doc->loader && doc->status != FileFailed && !already && !hasdata) { - doc->save(QString()); + if (doc->status == FileReady) { + doc->automaticLoad(0); } - if (doc->sticker()->img->isNull() && (already || hasdata)) { - if (already) { + if (doc->sticker()->img->isNull() && doc->loaded() && doc->loaded(true)) { + if (doc->data().isEmpty()) { doc->sticker()->img = ImagePtr(doc->already()); } else { - doc->sticker()->img = ImagePtr(doc->data); + doc->sticker()->img = ImagePtr(doc->data()); } } } @@ -252,7 +273,7 @@ void StickerSetBox::onAddStickers() { void StickerSetBox::onShareStickers() { QString url = qsl("https://telegram.me/addstickers/") + _inner.shortName(); QApplication::clipboard()->setText(url); - App::wnd()->showLayer(new InformBox(lang(lng_stickers_copied))); + Ui::showLayer(new InformBox(lang(lng_stickers_copied))); } void StickerSetBox::onUpdateButtons() { @@ -333,7 +354,7 @@ StickersInner::StickersInner() : TWidget() , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _aboveShadowFadeStart(0) , _aboveShadowFadeOpacity(0, 0) -, _a_shifting(animFunc(this, &StickersInner::animStep_shifting)) +, _a_shifting(animation(this, &StickersInner::step_shifting)) , _itemsTop(st::membersPadding.top()) , _saving(false) , _removeSel(-1) @@ -355,7 +376,7 @@ void StickersInner::paintEvent(QPaintEvent *e) { QRect r(e->rect()); Painter p(this); - updateAnimatedValues(); + _a_shifting.step(); p.fillRect(r, st::white); p.setClipRect(r); @@ -489,7 +510,7 @@ void StickersInner::onUpdateSelected() { } _rows.at(_dragging)->yadd = anim::ivalue(local.y() - _dragStart.y(), local.y() - _dragStart.y()); _animStartTimes[_dragging] = 0; - updateAnimatedRegions(); + _a_shifting.step(getms(), true); emit checkDraggingScroll(local.y()); } else { @@ -509,7 +530,7 @@ void StickersInner::onUpdateSelected() { float64 StickersInner::aboveShadowOpacity() const { if (_above < 0) return 0; - + int32 dx = 0; int32 dy = qAbs(_above * _rowHeight + _rows.at(_above)->yadd.current() - _started * _rowHeight); return qMin((dx + dy) * 2. / _rowHeight, 1.); @@ -538,33 +559,14 @@ void StickersInner::mouseReleaseEvent(QMouseEvent *e) { } } -void StickersInner::updateAnimatedRegions() { - int32 updateMin = -1, updateMax = 0; - for (int32 i = 0, l = _animStartTimes.size(); i < l; ++i) { - if (_animStartTimes.at(i)) { - if (updateMin < 0) updateMin = i; - updateMax = i; - } - } - if (_aboveShadowFadeStart) { - if (updateMin < 0 || updateMin > _above) updateMin = _above; - if (updateMax < _above) updateMin = _above; - } - if (_dragging >= 0) { - if (updateMin < 0 || updateMin > _dragging) updateMin = _dragging; - if (updateMax < _dragging) updateMax = _dragging; - } - if (updateMin >= 0) { - update(0, _itemsTop + _rowHeight * (updateMin - 1), width(), _rowHeight * (updateMax - updateMin + 3)); - } -} - -bool StickersInner::updateAnimatedValues() { +void StickersInner::step_shifting(uint64 ms, bool timer) { bool animating = false; - uint64 ms = getms(); + int32 updateMin = -1, updateMax = 0; for (int32 i = 0, l = _animStartTimes.size(); i < l; ++i) { uint64 start = _animStartTimes.at(i); if (start) { + if (updateMin < 0) updateMin = i; + updateMax = i; if (start + st::stickersRowDuration > ms && ms >= start) { _rows.at(i)->yadd.update((ms - start) / st::stickersRowDuration, anim::sineInOut); animating = true; @@ -575,6 +577,8 @@ bool StickersInner::updateAnimatedValues() { } } if (_aboveShadowFadeStart) { + if (updateMin < 0 || updateMin > _above) updateMin = _above; + if (updateMax < _above) updateMin = _above; if (_aboveShadowFadeStart + st::stickersRowDuration > ms && ms > _aboveShadowFadeStart) { _aboveShadowFadeOpacity.update((ms - _aboveShadowFadeStart) / st::stickersRowDuration, anim::sineInOut); animating = true; @@ -583,17 +587,19 @@ bool StickersInner::updateAnimatedValues() { _aboveShadowFadeStart = 0; } } - return animating; -} - -bool StickersInner::animStep_shifting(float64) { - updateAnimatedRegions(); - - bool animating = updateAnimatedValues(); + if (timer) { + if (_dragging >= 0) { + if (updateMin < 0 || updateMin > _dragging) updateMin = _dragging; + if (updateMax < _dragging) updateMax = _dragging; + } + if (updateMin >= 0) { + update(0, _itemsTop + _rowHeight * (updateMin - 1), width(), _rowHeight * (updateMax - updateMin + 3)); + } + } if (!animating) { _above = _dragging; + _a_shifting.stop(); } - return animating; } void StickersInner::clear() { @@ -630,7 +636,7 @@ void StickersInner::rebuild() { clear(); const StickerSetsOrder &order(cStickerSetsOrder()); _animStartTimes.reserve(order.size()); - + const StickerSets &sets(cStickerSets()); for (int32 i = 0, l = order.size(); i < l; ++i) { StickerSets::const_iterator it = sets.constFind(order.at(i)); @@ -714,13 +720,14 @@ StickersBox::StickersBox() : ItemListBox(st::boxScroll) , _topShadow(this, st::contactsAboutShadow) , _bottomShadow(this) , _scrollDelta(0) -, _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPhotoSize - st::contactsPadding.left() - st::contactsPadding.right()) +, _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.left()) , _about(st::boxTextFont, lang(lng_stickers_reorder), _defaultOptions, _aboutWidth) , _aboutHeight(st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom()) { ItemListBox::init(&_inner, st::boxButtonPadding.top() + _save.height() + st::boxButtonPadding.bottom(), st::boxTitleHeight + _aboutHeight); setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight))); connect(App::main(), SIGNAL(stickersUpdated()), this, SLOT(onStickersUpdated())); + App::main()->updateStickers(); connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); connect(&_save, SIGNAL(clicked()), this, SLOT(onSave())); @@ -793,7 +800,7 @@ void StickersBox::paintEvent(QPaintEvent *e) { p.fillRect(0, 0, width(), _aboutHeight, st::contactsAboutBg); p.setPen(st::stickersReorderFg); - _about.drawLeft(p, st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(), st::stickersReorderPadding.top(), _aboutWidth, width()); + _about.draw(p, st::contactsPadding.left(), st::stickersReorderPadding.top(), _aboutWidth, style::al_center); } void StickersBox::closePressed() { @@ -908,7 +915,6 @@ void StickersBox::onSave() { } } - cSetStickersHash(stickersCountHash()); Local::writeStickers(); if (writeRecent) Local::writeUserSettings(); emit App::main()->stickersUpdated(); diff --git a/Telegram/SourceFiles/boxes/stickersetbox.h b/Telegram/SourceFiles/boxes/stickersetbox.h index b896c8af4b..7fbd2b9720 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.h +++ b/Telegram/SourceFiles/boxes/stickersetbox.h @@ -32,7 +32,7 @@ public: void init(); void paintEvent(QPaintEvent *e); - + bool loaded() const; int32 notInstalled() const; bool official() const; @@ -60,6 +60,7 @@ private: bool installFailed(const RPCError &error); StickerPack _pack; + StickersByEmojiMap _emoji; bool _loaded; uint64 _setId, _setAccess; QString _title, _setTitle, _setShortName; @@ -118,7 +119,7 @@ public: void mousePressEvent(QMouseEvent *e); void mouseMoveEvent(QMouseEvent *e); void mouseReleaseEvent(QMouseEvent *e); - + void rebuild(); bool savingStart() { if (_saving) return false; @@ -144,13 +145,11 @@ public slots: private: - bool animStep_shifting(float64 ms); + void step_shifting(uint64 ms, bool timer); void paintRow(Painter &p, int32 index); void clear(); void setRemoveSel(int32 removeSel); float64 aboveShadowOpacity() const; - void updateAnimatedRegions(); - bool updateAnimatedValues(); int32 _rowHeight; struct StickerSetRow { @@ -203,7 +202,7 @@ public: StickersBox(); void resizeEvent(QResizeEvent *e); void paintEvent(QPaintEvent *e); - + void closePressed(); public slots: diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index 8a54574136..4a1637ba04 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -20,10 +20,10 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org */ #pragma once -static const int32 AppVersion = 9015; -static const wchar_t *AppVersionStr = L"0.9.15"; -static const bool DevVersion = false; -//#define BETA_VERSION (9014003ULL) // just comment this line to build public version +static const int32 AppVersion = 9019; +static const wchar_t *AppVersionStr = L"0.9.19"; +static const bool DevVersion = true; +//#define BETA_VERSION (9015008ULL) // 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"; @@ -83,6 +83,13 @@ enum { LocalEncryptKeySize = 256, // 2048 bit AnimationTimerDelta = 7, + ClipThreadsCount = 8, + AverageGifSize = 320 * 240, + WaitBeforeGifPause = 200, // wait 200ms for gif draw before pausing it + InlineBotRequestDelay = 400, // wait 400ms before context bot realtime request + RecentInlineBotsLimit = 10, + + AVBlockSize = 4096, // 4Kb for ffmpeg blocksize SaveRecentEmojisTimeout = 3000, // 3 secs SaveWindowPositionTimeout = 1000, // 1 sec @@ -108,12 +115,14 @@ enum { AudioVoiceMsgUpdateView = 100, // 100ms AudioVoiceMsgChannels = 2, // stereo AudioVoiceMsgBufferSize = 1024 * 1024, // 1 Mb buffers - AudioVoiceMsgInMemory = 1024 * 1024, // 1 Mb audio is hold in memory and auto loaded + AudioVoiceMsgInMemory = 2 * 1024 * 1024, // 2 Mb audio is hold in memory and auto loaded AudioPauseDeviceTimeout = 3000, // pause in 3 secs after playing is over - StickerInMemory = 1024 * 1024, // 1024 Kb stickers hold in memory, auto loaded and displayed inline + StickerInMemory = 2 * 1024 * 1024, // 2 Mb stickers hold in memory, auto loaded and displayed inline StickerMaxSize = 2048, // 2048x2048 is a max image size for sticker + AnimationInMemory = 10 * 1024 * 1024, // 10 Mb gif and mp4 animations held in memory while playing + MediaViewImageSizeLimit = 100 * 1024 * 1024, // show up to 100mb jpg/png/gif docs in app MaxZoomLevel = 7, // x8 ZoomToScreenLevel = 1024, // just constant @@ -123,6 +132,7 @@ enum { EmojiPanRowsPerPage = 6, StickerPanPerRow = 5, StickerPanRowsPerPage = 4, + SavedGifsMaxPerRow = 4, StickersUpdateTimeout = 3600000, // update not more than once in an hour SearchPeopleLimit = 5, @@ -335,6 +345,7 @@ enum { MaxUploadDocumentSize = 1500 * 1024 * 1024, // 1500mb documents max UseBigFilesFrom = 10 * 1024 * 1024, // mtp big files methods used for files greater than 10mb MaxFileQueries = 16, // max 16 file parts downloaded at the same time + MaxWebFileQueries = 8, // max 8 http[s] files downloaded at the same time UploadPartSize = 32 * 1024, // 32kb for photo DocumentMaxPartsCount = 3000, // no more than 3000 parts diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index 56d96241d1..ef5247c88e 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -163,7 +163,7 @@ 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 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); p.translate(0, st::dlgHeight); @@ -488,16 +488,6 @@ void DialogsInner::removeDialog(History *history) { refresh(); } -void DialogsInner::removeContact(UserData *user) { - if (sel && sel->history->peer == user) { - sel = 0; - } - contactsNoDialogs.del(user); - contacts.del(user); - - refresh(); -} - void DialogsInner::dlgUpdated(DialogRow *row) { if (_state == DefaultState) { update(0, row->pos * st::dlgHeight, fullWidth(), st::dlgHeight); @@ -675,12 +665,12 @@ void DialogsInner::onContextClearHistory() { _menuActionPeer = _menuPeer; ConfirmBox *box = new ConfirmBox(_menuPeer->isUser() ? lng_sure_delete_history(lt_contact, _menuPeer->name) : lng_sure_delete_group_history(lt_group, _menuPeer->name), lang(lng_box_delete), st::attentionBoxButton); connect(box, SIGNAL(confirmed()), this, SLOT(onContextClearHistorySure())); - App::showLayer(box); + Ui::showLayer(box); } void DialogsInner::onContextClearHistorySure() { if (!_menuActionPeer || _menuActionPeer->isChannel()) return; - App::wnd()->hideLayer(); + Ui::hideLayer(); App::main()->clearHistory(_menuActionPeer); } @@ -690,14 +680,14 @@ void DialogsInner::onContextDeleteAndLeave() { _menuActionPeer = _menuPeer; ConfirmBox *box = new ConfirmBox(_menuPeer->isUser() ? lng_sure_delete_history(lt_contact, _menuPeer->name) : (_menuPeer->isChat() ? lng_sure_delete_and_exit(lt_group, _menuPeer->name) : lang(_menuPeer->isMegagroup() ? lng_sure_leave_group : lng_sure_leave_channel)), lang(_menuPeer->isUser() ? lng_box_delete : lng_box_leave), _menuPeer->isChannel() ? st::defaultBoxButton : st::attentionBoxButton); connect(box, SIGNAL(confirmed()), this, SLOT(onContextDeleteAndLeaveSure())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } void DialogsInner::onContextDeleteAndLeaveSure() { if (!_menuActionPeer) return; - App::wnd()->hideLayer(); - App::main()->showDialogs(); + Ui::hideLayer(); + Ui::showChatsList(); if (_menuActionPeer->isUser()) { App::main()->deleteConversation(_menuActionPeer); } else if (_menuActionPeer->isChat()) { @@ -882,7 +872,7 @@ void DialogsInner::onHashtagFilterUpdate(QStringRef newFilter) { } _hashtagFilter = newFilter.toString(); if (cRecentSearchHashtags().isEmpty() && cRecentWriteHashtags().isEmpty()) { - Local::readRecentHashtags(); + Local::readRecentHashtagsAndBots(); } const RecentHashtagPack &recent(cRecentSearchHashtags()); _hashtagResults.clear(); @@ -917,14 +907,6 @@ void DialogsInner::clearSearchResults(bool clearPeople) { _lastSearchId = _lastSearchMigratedId = 0; } -void DialogsInner::itemReplaced(HistoryItem *oldItem, HistoryItem *newItem) { - for (int i = 0; i < _searchResults.size(); ++i) { - if (_searchResults[i]->_item == oldItem) { - _searchResults[i]->_item = newItem; - } - } -} - void DialogsInner::updateNotifySettings(PeerData *peer) { if (_menu && _menuPeer == peer && _menu->actions().size() > 1) { _menu->actions().at(1)->setText(lang(menuPeerMuted() ? lng_enable_notifications_from_tray : lng_disable_notifications_from_tray)); @@ -1096,9 +1078,11 @@ void DialogsInner::peopleReceived(const QString &query, const QVector & void DialogsInner::contactsReceived(const QVector &contacts) { for (QVector::const_iterator i = contacts.cbegin(), e = contacts.cend(); i != e; ++i) { int32 uid = i->c_contact().vuser_id.v; - addNewContact(uid); if (uid == MTP::authedId() && App::self()) { - App::self()->contact = 1; + if (App::self()->contact < 1) { + App::self()->contact = 1; + Notify::userIsContactChanged(App::self()); + } } } if (!sel && contactsNoDialogs.list.count && false) { @@ -1108,23 +1092,25 @@ void DialogsInner::contactsReceived(const QVector &contacts) { refresh(); } -int32 DialogsInner::addNewContact(int32 uid, bool select) { // -2 - err, -1 - don't scroll, >= 0 - scroll - PeerId peer = peerFromUser(uid); - if (!App::peerLoaded(peer)) return -2; - - History *history = App::history(peer); - contacts.addByName(history); - DialogsList::RowByPeer::const_iterator i = dialogs.list.rowByPeer.constFind(peer); - if (i == dialogs.list.rowByPeer.cend()) { - DialogRow *added = contactsNoDialogs.addByName(history); - if (!added) return -2; - return -1; +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; + } + } else { + if (sel && sel->history->peer == user) { + sel = 0; + } + contactsNoDialogs.del(user); + contacts.del(user); } - if (select) { - sel = i.value(); - contactSel = false; - } - return i.value()->pos * st::dlgHeight; + refresh(); } void DialogsInner::refresh(bool toTop) { @@ -1399,7 +1385,7 @@ void DialogsInner::loadPeerPhotos(int32 yFrom) { if (from < _filterResults.size()) { int32 to = (yTo / int32(st::dlgHeight)) + 1, w = width(); if (to > _filterResults.size()) to = _filterResults.size(); - + for (; from < to; ++from) { _filterResults[from]->history->peer->photo->load(); } @@ -1448,7 +1434,7 @@ bool DialogsInner::choosePeer() { } } cSetRecentSearchHashtags(recent); - Local::writeRecentHashtags(); + Local::writeRecentHashtagsAndBots(); emit refreshHashtags(); selByMouse = true; @@ -1501,7 +1487,7 @@ void DialogsInner::saveRecentHashtags(const QString &text) { } } if (!found && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) { - Local::readRecentHashtags(); + Local::readRecentHashtagsAndBots(); recent = cRecentSearchHashtags(); } found = true; @@ -1509,7 +1495,7 @@ void DialogsInner::saveRecentHashtags(const QString &text) { } if (found) { cSetRecentSearchHashtags(recent); - Local::writeRecentHashtags(); + Local::writeRecentHashtagsAndBots(); } } @@ -1737,7 +1723,7 @@ DialogsWidget::DialogsWidget(MainWidget *parent) : TWidget(parent) , _cancelSearch(this, st::btnCancelSearch) , _scroll(this, st::dlgScroll) , _inner(&_scroll, parent) -, _a_show(animFunc(this, &DialogsWidget::animStep_show)) +, _a_show(animation(this, &DialogsWidget::step_show)) , _searchInPeer(0) , _searchInMigrated(0) , _searchFull(false) @@ -1839,13 +1825,11 @@ void DialogsWidget::animShow(const QPixmap &bgAnimCache) { show(); } -bool DialogsWidget::animStep_show(float64 ms) { +void DialogsWidget::step_show(float64 ms, bool timer) { float64 dt = ms / st::slideDuration; - bool res = true; if (dt >= 1) { _a_show.stop(); - res = false; a_coordUnder.finish(); a_coordOver.finish(); a_shadow.finish(); @@ -1865,8 +1849,7 @@ bool DialogsWidget::animStep_show(float64 ms) { a_coordOver.update(dt, st::slideFunction); a_shadow.update(dt, st::slideFunction); } - update(); - return res; + if (timer) update(); } void DialogsWidget::onCancel() { @@ -1879,14 +1862,19 @@ void DialogsWidget::itemRemoved(HistoryItem *item) { _inner.itemRemoved(item); } -void DialogsWidget::itemReplaced(HistoryItem *oldItem, HistoryItem *newItem) { - _inner.itemReplaced(oldItem, newItem); -} - void DialogsWidget::updateNotifySettings(PeerData *peer) { _inner.updateNotifySettings(peer); } +void DialogsWidget::notify_userIsContactChanged(UserData *user, bool fromThisApp) { + if (fromThisApp) { + _filter.setText(QString()); + _filter.updatePlaceholder(); + onFilterUpdate(); + } + _inner.notify_userIsContactChanged(user, fromThisApp); +} + void DialogsWidget::unreadCountsReceived(const QVector &dialogs) { for (QVector::const_iterator i = dialogs.cbegin(), e = dialogs.cend(); i != e; ++i) { switch (i->type()) { @@ -2273,17 +2261,6 @@ bool DialogsWidget::peopleFailed(const RPCError &error, mtpRequestId req) { return true; } -bool DialogsWidget::addNewContact(int32 uid, bool show) { - _filter.setText(QString()); - _filter.updatePlaceholder(); - onFilterUpdate(); - int32 to = _inner.addNewContact(uid, true); - if (to < -1 || !show) return false; - _inner.refresh(); - if (to >= 0) _scroll.scrollToY(to); - return true; -} - void DialogsWidget::dragEnterEvent(QDragEnterEvent *e) { if (App::main()->selectingPeer()) return; @@ -2540,13 +2517,6 @@ void DialogsWidget::removeDialog(History *history) { onFilterUpdate(); } -void DialogsWidget::removeContact(UserData *user) { - _filter.setText(QString()); - _filter.updatePlaceholder(); - onFilterUpdate(); - _inner.removeContact(user); -} - DialogsIndexed &DialogsWidget::contactsList() { return _inner.contactsList(); } @@ -2556,11 +2526,11 @@ DialogsIndexed &DialogsWidget::dialogsList() { } void DialogsWidget::onAddContact() { - App::wnd()->replaceLayer(new AddContactBox()); + Ui::showLayer(new AddContactBox(), KeepOtherLayers); } void DialogsWidget::onNewGroup() { - App::wnd()->showLayer(new NewGroupBox()); + Ui::showLayer(new NewGroupBox()); } bool DialogsWidget::onCancelSearch() { @@ -2571,7 +2541,7 @@ bool DialogsWidget::onCancelSearch() { } if (_searchInPeer && !clearing) { if (!cWideMode()) { - App::main()->showPeerHistory(_searchInPeer->id, ShowAtUnreadMsgId); + Ui::showPeerHistory(_searchInPeer, ShowAtUnreadMsgId); } _searchInPeer = _searchInMigrated = 0; _inner.searchInPeer(0); @@ -2591,7 +2561,7 @@ void DialogsWidget::onCancelSearchInPeer() { } if (_searchInPeer) { if (!cWideMode() && !App::main()->selectingPeer()) { - App::main()->showPeerHistory(_searchInPeer->id, ShowAtUnreadMsgId); + Ui::showPeerHistory(_searchInPeer, ShowAtUnreadMsgId); } _searchInPeer = _searchInMigrated = 0; _inner.searchInPeer(0); diff --git a/Telegram/SourceFiles/dialogswidget.h b/Telegram/SourceFiles/dialogswidget.h index 602774d231..60cb877b93 100644 --- a/Telegram/SourceFiles/dialogswidget.h +++ b/Telegram/SourceFiles/dialogswidget.h @@ -48,7 +48,6 @@ public: void activate(); void contactsReceived(const QVector &contacts); - int32 addNewContact(int32 uid, bool sel = false); // -2 - err, -1 - don't scroll, >= 0 - scroll int32 filteredOffset() const; int32 peopleOffset() const; @@ -72,7 +71,6 @@ public: void dlgUpdated(DialogRow *row); void dlgUpdated(History *row, MsgId msgId); void removeDialog(History *history); - void removeContact(UserData *user); void loadPeerPhotos(int32 yFrom); void clearFilter(); @@ -117,12 +115,13 @@ public: void onFilterUpdate(QString newFilter, bool force = false); void onHashtagFilterUpdate(QStringRef newFilter); void itemRemoved(HistoryItem *item); - void itemReplaced(HistoryItem *oldItem, HistoryItem *newItem); PeerData *updateFromParentDrag(QPoint globalPos); void updateNotifySettings(PeerData *peer); + void notify_userIsContactChanged(UserData *user, bool fromThisApp); + ~DialogsInner(); public slots: @@ -220,7 +219,6 @@ public: void contactsReceived(const MTPcontacts_Contacts &contacts); void searchReceived(DialogsSearchRequestType type, const MTPmessages_Messages &result, mtpRequestId req); void peopleReceived(const MTPcontacts_Found &result, mtpRequestId req); - bool addNewContact(int32 uid, bool show = true); void dragEnterEvent(QDragEnterEvent *e); void dragMoveEvent(QDragMoveEvent *e); @@ -242,7 +240,7 @@ public: void dialogsToUp(); void animShow(const QPixmap &bgAnimCache); - bool animStep_show(float64 ms); + void step_show(float64 ms, bool timer); void destroyData(); @@ -251,7 +249,6 @@ public: void scrollToPeer(const PeerId &peer, MsgId msgId); void removeDialog(History *history); - void removeContact(UserData *user); DialogsIndexed &contactsList(); DialogsIndexed &dialogsList(); @@ -260,10 +257,11 @@ public: void onSearchMore(); void itemRemoved(HistoryItem *item); - void itemReplaced(HistoryItem *oldItem, HistoryItem *newItem); void updateNotifySettings(PeerData *peer); + void notify_userIsContactChanged(UserData *user, bool fromThisApp); + signals: void cancelled(); diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp index 20071440c3..2ee10cd55f 100644 --- a/Telegram/SourceFiles/dropdown.cpp +++ b/Telegram/SourceFiles/dropdown.cpp @@ -28,12 +28,20 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "window.h" #include "apiwrap.h" +#include "mainwidget.h" #include "boxes/confirmbox.h" #include "boxes/stickersetbox.h" -Dropdown::Dropdown(QWidget *parent, const style::dropdown &st) : TWidget(parent), -_ignore(false), _selected(-1), _st(st), _width(_st.width), _hiding(false), a_opacity(0), _shadow(_st.shadow) { +Dropdown::Dropdown(QWidget *parent, const style::dropdown &st) : TWidget(parent) +, _ignore(false) +, _selected(-1) +, _st(st) +, _width(_st.width) +, _hiding(false) +, a_opacity(0) +, _a_appearance(animation(this, &Dropdown::step_appearance)) +, _shadow(_st.shadow) { resetButtons(); _hideTimer.setSingleShot(true); @@ -118,7 +126,7 @@ void Dropdown::resizeEvent(QResizeEvent *e) { void Dropdown::paintEvent(QPaintEvent *e) { QPainter p(this); - if (animating()) { + if (_a_appearance.animating()) { p.setOpacity(a_opacity.current()); } @@ -151,7 +159,7 @@ void Dropdown::enterEvent(QEvent *e) { } void Dropdown::leaveEvent(QEvent *e) { - if (animating()) { + if (_a_appearance.animating()) { hideStart(); } else { _hideTimer.start(300); @@ -231,7 +239,7 @@ void Dropdown::otherEnter() { } void Dropdown::otherLeave() { - if (animating()) { + if (_a_appearance.animating()) { hideStart(); } else { _hideTimer.start(0); @@ -239,8 +247,8 @@ void Dropdown::otherLeave() { } void Dropdown::fastHide() { - if (animating()) { - anim::stop(this); + if (_a_appearance.animating()) { + _a_appearance.stop(); } a_opacity = anim::fvalue(0, 0); _hideTimer.stop(); @@ -256,7 +264,7 @@ void Dropdown::adjustButtons() { void Dropdown::hideStart() { _hiding = true; a_opacity.start(0); - anim::start(this); + _a_appearance.start(); } void Dropdown::hideFinish() { @@ -276,24 +284,22 @@ void Dropdown::showStart() { _hiding = false; show(); a_opacity.start(1); - anim::start(this); + _a_appearance.start(); } -bool Dropdown::animStep(float64 ms) { +void Dropdown::step_appearance(float64 ms, bool timer) { float64 dt = ms / _st.duration; - bool res = true; if (dt >= 1) { + _a_appearance.stop(); a_opacity.finish(); if (_hiding) { hideFinish(); } - res = false; } else { a_opacity.update(dt, anim::linear); } adjustButtons(); - update(); - return res; + if (timer) update(); } bool Dropdown::eventFilter(QObject *obj, QEvent *e) { @@ -311,8 +317,13 @@ bool Dropdown::eventFilter(QObject *obj, QEvent *e) { return false; } -DragArea::DragArea(QWidget *parent) : TWidget(parent), -_hiding(false), _in(false), a_opacity(0), a_color(st::dragColor->c), _shadow(st::boxShadow) { +DragArea::DragArea(QWidget *parent) : TWidget(parent) +, _hiding(false) +, _in(false) +, a_opacity(0) +, a_color(st::dragColor->c) +, _a_appearance(animation(this, &DragArea::step_appearance)) +, _shadow(st::boxShadow) { setMouseTracking(true); setAcceptDrops(true); } @@ -325,7 +336,7 @@ void DragArea::mouseMoveEvent(QMouseEvent *e) { _in = newIn; a_opacity.start(1); a_color.start((_in ? st::dragDropColor : st::dragColor)->c); - anim::start(this); + _a_appearance.start(); } } @@ -336,7 +347,7 @@ void DragArea::dragMoveEvent(QDragMoveEvent *e) { _in = newIn; a_opacity.start(1); a_color.start((_in ? st::dragDropColor : st::dragColor)->c); - anim::start(this); + _a_appearance.start(); } e->setDropAction(_in ? Qt::CopyAction : Qt::IgnoreAction); e->accept(); @@ -351,7 +362,7 @@ void DragArea::setText(const QString &text, const QString &subtext) { void DragArea::paintEvent(QPaintEvent *e) { QPainter p(this); - if (animating()) { + if (_a_appearance.animating()) { p.setOpacity(a_opacity.current()); } @@ -382,7 +393,7 @@ void DragArea::dragLeaveEvent(QDragLeaveEvent *e) { _in = false; a_opacity.start(_hiding ? 0 : 1); a_color.start((_in ? st::dragDropColor : st::dragColor)->c); - anim::start(this); + _a_appearance.start(); } void DragArea::dropEvent(QDropEvent *e) { @@ -401,8 +412,8 @@ void DragArea::otherLeave() { } void DragArea::fastHide() { - if (animating()) { - anim::stop(this); + if (_a_appearance.animating()) { + _a_appearance.stop(); } a_opacity = anim::fvalue(0, 0); hide(); @@ -413,7 +424,7 @@ void DragArea::hideStart() { _in = false; a_opacity.start(0); a_color.start((_in ? st::dragDropColor : st::dragColor)->c); - anim::start(this); + _a_appearance.start(); } void DragArea::hideFinish() { @@ -427,29 +438,34 @@ void DragArea::showStart() { show(); a_opacity.start(1); a_color.start((_in ? st::dragDropColor : st::dragColor)->c); - anim::start(this); + _a_appearance.start(); } -bool DragArea::animStep(float64 ms) { +void DragArea::step_appearance(float64 ms, bool timer) { float64 dt = ms / st::dropdownDef.duration; - bool res = true; if (dt >= 1) { a_opacity.finish(); a_color.finish(); if (_hiding) { hideFinish(); } - res = false; + _a_appearance.stop(); } else { a_opacity.update(dt, anim::linear); a_color.update(dt, anim::linear); } - update(); - return res; + if (timer) update(); } -EmojiColorPicker::EmojiColorPicker() : -_ignoreShow(false), _selected(-1), _pressedSel(-1), _hiding(false), a_opacity(0), _shadow(st::dropdownDef.shadow) { +EmojiColorPicker::EmojiColorPicker() : TWidget() +, _ignoreShow(false) +, _a_selected(animation(this, &EmojiColorPicker::step_selected)) +, _selected(-1) +, _pressedSel(-1) +, _hiding(false) +, a_opacity(0) +, _a_appearance(animation(this, &EmojiColorPicker::step_appearance)) +, _shadow(st::dropdownDef.shadow) { memset(_variants, 0, sizeof(_variants)); memset(_hovers, 0, sizeof(_hovers)); @@ -547,51 +563,52 @@ void EmojiColorPicker::mouseMoveEvent(QMouseEvent *e) { updateSelected(); } -bool EmojiColorPicker::animStep(float64 ms) { - bool res1 = false, res2 = false; - if (!_cache.isNull()) { - float64 dt = ms / st::dropdownDef.duration; - if (dt >= 1) { - a_opacity.finish(); - _cache = QPixmap(); - if (_hiding) { - hide(); - emit hidden(); - } else { - _lastMousePos = QCursor::pos(); - updateSelected(); - } +void EmojiColorPicker::step_appearance(float64 ms, bool timer) { + if (_cache.isNull()) { + _a_appearance.stop(); + return; + } + float64 dt = ms / st::dropdownDef.duration; + if (dt >= 1) { + a_opacity.finish(); + _cache = QPixmap(); + if (_hiding) { + hide(); + emit hidden(); } else { - a_opacity.update(dt, anim::linear); - res1 = true; + _lastMousePos = QCursor::pos(); + updateSelected(); } - update(); + _a_appearance.stop(); + } else { + a_opacity.update(dt, anim::linear); } - if (!_emojiAnimations.isEmpty()) { - uint64 now = getms(); - QRegion toUpdate; - for (EmojiAnimations::iterator i = _emojiAnimations.begin(); i != _emojiAnimations.end();) { - int index = qAbs(i.key()) - 1; - float64 dt = float64(now - i.value()) / st::emojiPanDuration; - if (dt >= 1) { - _hovers[index] = (i.key() > 0) ? 1 : 0; - i = _emojiAnimations.erase(i); - } else { - _hovers[index] = (i.key() > 0) ? dt : (1 - dt); - ++i; - } - toUpdate += QRect(st::dropdownDef.shadow.pxWidth() + st::emojiColorsPadding + index * st::emojiPanSize.width() + (index ? 2 * st::emojiColorsPadding + st::emojiColorsSep : 0), st::dropdownDef.shadow.pxHeight() + st::emojiColorsPadding, st::emojiPanSize.width(), st::emojiPanSize.height()); + if (timer) update(); +} + +void EmojiColorPicker::step_selected(uint64 ms, bool timer) { + QRegion toUpdate; + for (EmojiAnimations::iterator i = _emojiAnimations.begin(); i != _emojiAnimations.end();) { + int index = qAbs(i.key()) - 1; + float64 dt = float64(ms - i.value()) / st::emojiPanDuration; + if (dt >= 1) { + _hovers[index] = (i.key() > 0) ? 1 : 0; + i = _emojiAnimations.erase(i); + } else { + _hovers[index] = (i.key() > 0) ? dt : (1 - dt); + ++i; } - res2 = !_emojiAnimations.isEmpty(); - rtlupdate(toUpdate.boundingRect()); + toUpdate += QRect(st::dropdownDef.shadow.pxWidth() + st::emojiColorsPadding + index * st::emojiPanSize.width() + (index ? 2 * st::emojiColorsPadding + st::emojiColorsSep : 0), st::dropdownDef.shadow.pxHeight() + st::emojiColorsPadding, st::emojiPanSize.width(), st::emojiPanSize.height()); } - return res1 || res2; + if (timer) rtlupdate(toUpdate.boundingRect()); + if (_emojiAnimations.isEmpty()) _a_selected.stop(); } void EmojiColorPicker::hideStart(bool fast) { if (fast) { clearSelection(true); - if (animating()) anim::stop(this); + if (_a_appearance.animating()) _a_appearance.stop(); + if (_a_selected.animating()) _a_selected.stop(); a_opacity = anim::fvalue(0); _cache = QPixmap(); hide(); @@ -604,7 +621,7 @@ void EmojiColorPicker::hideStart(bool fast) { } _hiding = true; a_opacity.start(0); - anim::start(this); + _a_appearance.start(); } } @@ -613,8 +630,8 @@ void EmojiColorPicker::showStart() { _hiding = false; if (!isHidden() && a_opacity.current() == 1) { - if (animating()) { - anim::stop(this); + if (_a_appearance.animating()) { + _a_appearance.stop(); _cache = QPixmap(); } return; @@ -626,7 +643,7 @@ void EmojiColorPicker::showStart() { } show(); a_opacity.start(1); - anim::start(this); + _a_appearance.start(); } void EmojiColorPicker::clearSelection(bool fast) { @@ -676,7 +693,7 @@ void EmojiColorPicker::updateSelected() { } setCursor((_selected >= 0) ? style::cur_pointer : style::cur_default); } - if (startanim && !animating()) anim::start(this); + if (startanim && !_a_selected.animating()) _a_selected.start(); } void EmojiColorPicker::drawVariant(Painter &p, int variant) { @@ -694,8 +711,13 @@ void EmojiColorPicker::drawVariant(Painter &p, int variant) { p.drawPixmapLeft(w.x() + (st::emojiPanSize.width() - (esize / cIntRetinaFactor())) / 2, w.y() + (st::emojiPanSize.height() - (esize / cIntRetinaFactor())) / 2, width(), App::emojiLarge(), QRect(_variants[variant]->x * esize, _variants[variant]->y * esize, esize, esize)); } -EmojiPanInner::EmojiPanInner() : _maxHeight(int(st::emojiPanMaxHeight)), -_top(0), _selected(-1), _pressedSel(-1), _pickerSel(-1) { +EmojiPanInner::EmojiPanInner() : TWidget() +, _maxHeight(int(st::emojiPanMaxHeight)) +, _a_selected(animation(this, &EmojiPanInner::step_selected)) +, _top(0) +, _selected(-1) +, _pressedSel(-1) +, _pickerSel(-1) { resize(st::emojiPanWidth - st::emojiScroll.width, countHeight()); setMouseTracking(true); @@ -711,9 +733,6 @@ _top(0), _selected(-1), _pressedSel(-1), _pickerSel(-1) { _hovers[i] = QVector(_counts[i], 0); } - _saveConfigTimer.setSingleShot(true); - connect(&_saveConfigTimer, SIGNAL(timeout()), this, SLOT(onSaveConfig())); - _showPickerTimer.setSingleShot(true); connect(&_showPickerTimer, SIGNAL(timeout()), this, SLOT(onShowPicker())); connect(&_picker, SIGNAL(emojiSelected(EmojiPtr)), this, SLOT(onColorSelected(EmojiPtr))); @@ -916,15 +935,11 @@ void EmojiPanInner::selectEmoji(EmojiPtr emoji) { qSwap(*i, *(i - 1)); } } - _saveConfigTimer.start(SaveRecentEmojisTimeout); + emit saveConfigDelayed(SaveRecentEmojisTimeout); emit selected(emoji); } -void EmojiPanInner::onSaveConfig() { - Local::writeUserSettings(); -} - void EmojiPanInner::onShowPicker() { int tab = (_pickerSel / MatrixRowShift), sel = _pickerSel % MatrixRowShift; if (tab < emojiTabCount && sel < _emojis[tab].size() && _emojis[tab][sel]->color) { @@ -1030,7 +1045,7 @@ void EmojiPanInner::clearSelection(bool fast) { _hovers[tab][sel] = 0; } _selected = _pressedSel = -1; - anim::stop(this); + _a_selected.stop(); } else { updateSelected(); } @@ -1128,7 +1143,7 @@ void EmojiPanInner::updateSelected() { bool startanim = false; int oldSel = _selected, newSel = selIndex; - + if (newSel != oldSel) { if (oldSel >= 0) { _animations.remove(oldSel + 1); @@ -1155,15 +1170,14 @@ void EmojiPanInner::updateSelected() { } _selected = selIndex; - if (startanim) anim::start(this); + if (startanim && !_a_selected.animating()) _a_selected.start(); } -bool EmojiPanInner::animStep(float64 ms) { - uint64 now = getms(); +void EmojiPanInner::step_selected(uint64 ms, bool timer) { QRegion toUpdate; for (Animations::iterator i = _animations.begin(); i != _animations.end();) { int index = qAbs(i.key()) - 1, tab = (index / MatrixRowShift), sel = index % MatrixRowShift; - float64 dt = float64(now - i.value()) / st::emojiPanDuration; + float64 dt = float64(ms - i.value()) / st::emojiPanDuration; if (dt >= 1) { _hovers[tab][sel] = (i.key() > 0) ? 1 : 0; i = _animations.erase(i); @@ -1173,8 +1187,8 @@ bool EmojiPanInner::animStep(float64 ms) { } toUpdate += emojiRect(tab, sel); } - rtlupdate(toUpdate.boundingRect()); - return !_animations.isEmpty(); + if (timer) rtlupdate(toUpdate.boundingRect()); + if (_animations.isEmpty()) _a_selected.stop(); } void EmojiPanInner::showEmojiPack(DBIEmojiTab packIndex) { @@ -1197,7 +1211,13 @@ void EmojiPanInner::showEmojiPack(DBIEmojiTab packIndex) { } StickerPanInner::StickerPanInner() : TWidget() +, _a_selected(animation(this, &StickerPanInner::step_selected)) , _top(0) +, _showingSavedGifs(cShowingSavedGifs()) +, _showingInlineItems(_showingSavedGifs) +, _setGifCommand(false) +, _lastScrolled(0) +, _inlineWithThumb(false) , _selected(-1) , _pressedSel(-1) , _settings(this, lang(lng_stickers_you_have)) @@ -1210,11 +1230,12 @@ StickerPanInner::StickerPanInner() : TWidget() connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update())); connect(&_settings, SIGNAL(clicked()), this, SLOT(onSettings())); - + _previewTimer.setSingleShot(true); connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreview())); - refreshStickers(); + _updateInlineItems.setSingleShot(true); + connect(&_updateInlineItems, SIGNAL(timeout()), this, SLOT(onUpdateInlineItems())); } void StickerPanInner::setMaxHeight(int32 h) { @@ -1226,17 +1247,25 @@ void StickerPanInner::setMaxHeight(int32 h) { void StickerPanInner::setScrollTop(int top) { if (top == _top) return; + _lastScrolled = getms(); _top = top; updateSelected(); } -int StickerPanInner::countHeight() { - int result = 0, minLastH = _maxHeight - st::rbEmoji.height - st::stickerPanPadding; - for (int i = 0; i < _sets.size(); ++i) { - int cnt = _sets.at(i).pack.size(), rows = (cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0); - int h = st::emojiPanHeader + rows * st::stickerPanSize.height(); - if (i == _sets.size() - 1 && h < minLastH) h = minLastH; - result += h; +int32 StickerPanInner::countHeight(bool plain) { + int result = 0, minLastH = plain ? 0 : (_maxHeight - st::stickerPanPadding); + if (_showingInlineItems) { + result = st::emojiPanHeader; + for (int i = 0, l = _inlineRows.count(); i < l; ++i) { + result += _inlineRows.at(i).height; + } + } else { + for (int i = 0; i < _sets.size(); ++i) { + int cnt = _sets.at(i).pack.size(), rows = (cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0); + int h = st::emojiPanHeader + rows * st::stickerPanSize.height(); + if (i == _sets.size() - 1 && h < minLastH) h = minLastH; + result += h; + } } return qMax(minLastH, result) + st::stickerPanPadding; } @@ -1264,8 +1293,49 @@ void StickerPanInner::paintEvent(QPaintEvent *e) { if (r != rect()) { p.setClipRect(r); } - p.fillRect(r, st::white->b); + p.fillRect(r, st::white); + if (_showingInlineItems) { + paintInlineItems(p, r); + } else { + paintStickers(p, r); + } +} + +void StickerPanInner::paintInlineItems(Painter &p, const QRect &r) { + if (_inlineRows.isEmpty()) { + p.setFont(st::normalFont); + p.setPen(st::noContactsColor); + 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); + + 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) { + const InlineRow &inlineRow(_inlineRows.at(row)); + if (top >= r.top() + r.height()) break; + if (top + inlineRow.height > r.top()) { + int32 left = st::inlineResultsLeft; + if (row == rows - 1) context.lastRow = true; + for (int32 col = 0, cols = inlineRow.items.size(); col < cols; ++col) { + if (left >= tox) break; + + int32 w = inlineRow.items.at(col)->width(); + if (left + w > fromx) { + p.translate(left, top); + inlineRow.items.at(col)->paint(p, r.translated(-left, -top), 0, &context); + p.translate(-left, -top); + } + left += w + st::inlineResultsSkip; + } + } + top += inlineRow.height; + } +} + +void StickerPanInner::paintStickers(Painter &p, const QRect &r) { int32 fromcol = floorclamp(r.x() - st::stickerPanPadding, st::stickerPanSize.width(), 0, StickerPanPerRow); int32 tocol = ceilclamp(r.x() + r.width() - st::stickerPanPadding, st::stickerPanSize.width(), 0, StickerPanPerRow); if (rtl()) { @@ -1310,17 +1380,7 @@ void StickerPanInner::paintEvent(QPaintEvent *e) { if (goodThumb) { sticker->thumb->load(); } else { - bool already = !sticker->already().isEmpty(), hasdata = !sticker->data.isEmpty(); - if (!sticker->loader && sticker->status != FileFailed && !already && !hasdata) { - sticker->save(QString()); - } - if (sticker->sticker()->img->isNull() && (already || hasdata)) { - if (already) { - sticker->sticker()->img = ImagePtr(sticker->already()); - } else { - sticker->sticker()->img = ImagePtr(sticker->data); - } - } + sticker->checkSticker(); } float64 coef = qMin((st::stickerPanSize.width() - st::msgRadius * 2) / float64(sticker->dimensions.width()), (st::stickerPanSize.height() - st::msgRadius * 2) / float64(sticker->dimensions.height())); @@ -1353,6 +1413,8 @@ void StickerPanInner::mousePressEvent(QMouseEvent *e) { updateSelected(); _pressedSel = _selected; + textlnkDown(textlnkOver()); + _linkDown = _linkOver; _previewTimer.start(QApplication::startDragTime()); } @@ -1360,7 +1422,10 @@ void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) { _previewTimer.stop(); int32 pressed = _pressedSel; + TextLinkPtr down(_linkDown); _pressedSel = -1; + _linkDown.reset(); + textlnkDown(TextLinkPtr()); _lastMousePos = e->globalPos(); updateSelected(); @@ -1370,7 +1435,75 @@ void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) { return; } - if (_selected < 0 || _selected != pressed) return; + if (_selected < 0 || _selected != pressed || _linkOver != down) 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()); + } + } + } + return; + } if (_selected >= MatrixRowShift * _sets.size()) { return; } @@ -1405,7 +1538,7 @@ void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) { } } if (refresh) { - refreshRecent(); + refreshRecentStickers(); updateSelected(); update(); } @@ -1434,9 +1567,27 @@ void StickerPanInner::enterFromChildEvent(QEvent *e) { updateSelected(); } +bool StickerPanInner::showSectionIcons() const { + return !inlineResultsShown(); +} + void StickerPanInner::clearSelection(bool fast) { _lastMousePos = mapToGlobal(QPoint(-10, -10)); if (fast) { + if (_showingInlineItems) { + 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); + } + setCursor(style::cur_default); + } + _selected = _pressedSel = -1; + return; + } for (Animations::const_iterator i = _animations.cbegin(); i != _animations.cend(); ++i) { int index = qAbs(i.key()) - 1, tab = (index / MatrixRowShift), sel = index % MatrixRowShift; _sets[tab].hovers[sel] = 0; @@ -1459,35 +1610,247 @@ void StickerPanInner::clearSelection(bool fast) { _sets[tab].hovers[sel] = 0; } _selected = _pressedSel = -1; - anim::stop(this); + _a_selected.stop(); } else { updateSelected(); } } +void StickerPanInner::hideFinish(bool completely) { + if (completely) { + clearInlineRows(false); + for (GifLayouts::const_iterator i = _gifLayouts.cbegin(), e = _gifLayouts.cend(); i != e; ++i) { + i.value()->document()->forget(); + } + 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(); + } + } + } + if (_setGifCommand && _showingSavedGifs) { + App::insertBotCommand(qsl(""), true); + } + _setGifCommand = false; +} + void StickerPanInner::refreshStickers() { clearSelection(true); const StickerSets &sets(cStickerSets()); _sets.clear(); _sets.reserve(sets.size() + 1); - refreshRecent(false); + refreshRecentStickers(false); for (StickerSetsOrder::const_iterator i = cStickerSetsOrder().cbegin(), e = cStickerSetsOrder().cend(); i != e; ++i) { appendSet(*i); } - int32 h = countHeight(); - if (h != height()) resize(width(), h); + if (_showingInlineItems) { + _settings.hide(); + } else { + int32 h = countHeight(); + if (h != height()) resize(width(), h); + + _settings.setVisible(_sets.isEmpty()); + } - _settings.setVisible(_sets.isEmpty()); emit refreshIcons(); updateSelected(); } +bool StickerPanInner::inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, InlineRow &row, int32 &sumWidth) { + LayoutInlineItem *layout = 0; + if (savedGif) { + layout = layoutPrepareSavedGif(savedGif, (_inlineRows.size() * MatrixRowShift) + row.items.size()); + } else if (result) { + layout = layoutPrepareInlineResult(result, (_inlineRows.size() * MatrixRowShift) + row.items.size()); + } + if (!layout) return false; + + layout->preload(); + if (inlineRowFinalize(row, sumWidth, layout->fullLine())) { + layout->setPosition(_inlineRows.size() * MatrixRowShift); + } + row.items.push_back(layout); + sumWidth += layout->maxWidth(); + 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); + if (full || big || force) { + _inlineRows.push_back(layoutInlineRow(row, (full || big) ? sumWidth : 0)); + row = InlineRow(); + row.items.reserve(SavedGifsMaxPerRow); + sumWidth = 0; + return true; + } + return false; +} + +void StickerPanInner::refreshSavedGifs() { + if (_showingSavedGifs) { + clearInlineRows(false); + if (_showingInlineItems) { + const SavedGifs &saved(cSavedGifs()); + if (saved.isEmpty()) { + showStickerSet(RecentStickerSetId); + return; + } else { + _inlineRows.reserve(saved.size()); + InlineRow row; + row.items.reserve(SavedGifsMaxPerRow); + int32 sumWidth = 0; + for (SavedGifs::const_iterator i = saved.cbegin(), e = saved.cend(); i != e; ++i) { + inlineRowsAddItem(*i, 0, row, sumWidth); + } + inlineRowFinalize(row, sumWidth, true); + } + deleteUnusedGifLayouts(); + + int32 h = countHeight(); + if (h != height()) resize(width(), h); + + update(); + } + } + emit refreshIcons(); + + updateSelected(); +} + +void StickerPanInner::inlineBotChanged() { + _setGifCommand = false; + refreshInlineRows(0, InlineResults(), true); + deleteUnusedInlineLayouts(); +} + +void StickerPanInner::clearInlineRows(bool resultsDeleted) { + if (resultsDeleted) { + if (_showingInlineItems) { + _selected = _pressedSel = -1; + } + } else { + if (_showingInlineItems) { + clearSelection(true); + } + for (InlineRows::const_iterator i = _inlineRows.cbegin(), e = _inlineRows.cend(); i != e; ++i) { + for (InlineItems::const_iterator j = i->items.cbegin(), end = i->items.cend(); j != end; ++j) { + (*j)->setPosition(-1); + } + } + } + _inlineRows.clear(); +} + +LayoutInlineGif *StickerPanInner::layoutPrepareSavedGif(DocumentData *doc, int32 position) { + GifLayouts::const_iterator i = _gifLayouts.constFind(doc); + if (i == _gifLayouts.cend()) { + i = _gifLayouts.insert(doc, new LayoutInlineGif(0, doc, true)); + i.value()->initDimensions(); + } + if (!i.value()->maxWidth()) return 0; + + i.value()->setPosition(position); + return i.value(); +} + +LayoutInlineItem *StickerPanInner::layoutPrepareInlineResult(InlineResult *result, int32 position) { + InlineLayouts::const_iterator 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 (!layout) return 0; + + i = _inlineLayouts.insert(result, layout); + layout->initDimensions(); + } + if (!i.value()->maxWidth()) return 0; + + i.value()->setPosition(position); + return i.value(); +} + +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(); + } + _gifLayouts.clear(); + } else { + for (GifLayouts::iterator i = _gifLayouts.begin(); i != _gifLayouts.cend();) { + if (i.value()->position() < 0) { + delete i.value(); + i = _gifLayouts.erase(i); + } else { + ++i; + } + } + } +} + +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(); + } + _inlineLayouts.clear(); + } else { + for (InlineLayouts::iterator i = _inlineLayouts.begin(); i != _inlineLayouts.cend();) { + if (i.value()->position() < 0) { + delete i.value(); + i = _inlineLayouts.erase(i); + } else { + ++i; + } + } + } +} + +StickerPanInner::InlineRow &StickerPanInner::layoutInlineRow(InlineRow &row, int32 sumWidth) { + int32 count = row.items.size(); + t_assert(count <= SavedGifsMaxPerRow); + + row.height = 0; + int32 availw = width() - st::inlineResultsLeft - st::inlineResultsSkip * (count - 1); + for (int32 i = 0; i < count; ++i) { + int32 w = sumWidth ? (row.items.at(i)->maxWidth() * availw / sumWidth) : row.items.at(i)->maxWidth(); + int32 actualw = qMax(w, int32(st::inlineResultsMinWidth)); + row.height = qMax(row.height, row.items.at(i)->resizeGetHeight(actualw)); + if (sumWidth) { + availw -= actualw; + sumWidth -= row.items.at(i)->maxWidth(); + } + } + return row; +} + void StickerPanInner::preloadImages() { - uint64 ms = getms(); + if (_showingInlineItems) { + for (int32 row = 0, rows = _inlineRows.size(); row < rows; ++row) { + for (int32 col = 0, cols = _inlineRows.at(row).items.size(); col < cols; ++col) { + _inlineRows.at(row).items.at(col)->preload(); + } + } + return; + } + for (int32 i = 0, l = _sets.size(), k = 0; i < l; ++i) { for (int32 j = 0, n = _sets.at(i).pack.size(); j < n; ++j) { if (++k > StickerPanPerRow * (StickerPanPerRow + 1)) break; @@ -1499,17 +1862,7 @@ void StickerPanInner::preloadImages() { if (goodThumb) { sticker->thumb->load(); } else { - bool already = !sticker->already().isEmpty(), hasdata = !sticker->data.isEmpty(); - if (!sticker->loader && sticker->status != FileFailed && !already && !hasdata) { - sticker->save(QString()); - } - //if (sticker->sticker->img->isNull() && (already || hasdata)) { - // if (already) { - // sticker->sticker->img = ImagePtr(sticker->already()); - // } else { - // sticker->sticker->img = ImagePtr(sticker->data); - // } - //} + sticker->automaticLoad(0); } } if (k > StickerPanPerRow * (StickerPanPerRow + 1)) break; @@ -1517,6 +1870,8 @@ void StickerPanInner::preloadImages() { } uint64 StickerPanInner::currentSet(int yOffset) const { + if (_showingInlineItems) return NoneStickerSetId; + int y, ytill = 0; for (int i = 0, l = _sets.size(); i < l; ++i) { int cnt = _sets.at(i).pack.size(); @@ -1529,6 +1884,158 @@ uint64 StickerPanInner::currentSet(int yOffset) const { return _sets.isEmpty() ? RecentStickerSetId : _sets.back().id; } +void StickerPanInner::hideInlineRowsPanel() { + clearInlineRows(false); + if (_showingInlineItems) { + _showingSavedGifs = cShowingSavedGifs(); + if (_showingSavedGifs) { + refreshSavedGifs(); + emit scrollToY(0); + emit scrollUpdated(); + } else { + showStickerSet(RecentStickerSetId); + } + } +} + +void StickerPanInner::clearInlineRowsPanel() { + clearInlineRows(false); +} + +int32 StickerPanInner::refreshInlineRows(UserData *bot, const InlineResults &results, bool resultsDeleted) { + _inlineBot = bot; + if (results.isEmpty() && (!_inlineBot || _inlineBot->username != cInlineGifBotUsername())) { + if (resultsDeleted) { + clearInlineRows(true); + } + emit emptyInlineRows(); + return 0; + } + + clearSelection(true); + + t_assert(_inlineBot != 0); + _inlineBotTitle = lng_inline_bot_results(lt_inline_bot, _inlineBot->username.isEmpty() ? _inlineBot->name : ('@' + _inlineBot->username)); + + _showingInlineItems = true; + _showingSavedGifs = false; + + int32 count = results.size(), from = validateExistingInlineRows(results), added = 0; + + if (count) { + _inlineRows.reserve(count); + InlineRow row; + row.items.reserve(SavedGifsMaxPerRow); + int32 sumWidth = 0; + for (int32 i = from; i < count; ++i) { + if (inlineRowsAddItem(0, results.at(i), row, sumWidth)) { + ++added; + } + } + inlineRowFinalize(row, sumWidth, true); + } + + int32 h = countHeight(); + if (h != height()) resize(width(), h); + update(); + + emit refreshIcons(); + + _lastMousePos = QCursor::pos(); + updateSelected(); + + return added; +} + +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)) { + break; + } + ++until; + if (++untilcol == _inlineRows.at(untilrow).items.size()) { + ++untilrow; + untilcol = 0; + } + } + if (until == count) { // all items are layed out + if (untilrow == _inlineRows.size()) { // nothing changed + return until; + } + + for (int32 i = untilrow, l = _inlineRows.size(), skip = untilcol; i < l; ++i) { + for (int32 j = 0, s = _inlineRows.at(i).items.size(); j < s; ++j) { + if (skip) { + --skip; + } else { + _inlineRows.at(i).items.at(j)->setPosition(-1); + } + } + } + if (!untilcol) { // all good rows are filled + _inlineRows.resize(untilrow); + return until; + } + _inlineRows.resize(untilrow + 1); + _inlineRows[untilrow].items.resize(untilcol); + _inlineRows[untilrow] = layoutInlineRow(_inlineRows[untilrow]); + return until; + } + if (untilrow && !untilcol) { // remove last row, maybe it is not full + --untilrow; + untilcol = _inlineRows.at(untilrow).items.size(); + } + until -= untilcol; + + for (int32 i = untilrow, l = _inlineRows.size(); i < l; ++i) { + for (int32 j = 0, s = _inlineRows.at(i).items.size(); j < s; ++j) { + _inlineRows.at(i).items.at(j)->setPosition(-1); + } + } + _inlineRows.resize(untilrow); + + if (_inlineRows.isEmpty()) { + _inlineWithThumb = false; + for (int32 i = until; i < count; ++i) { + if (!results.at(i)->thumb->isNull()) { + _inlineWithThumb = true; + break; + } + } + } + return until; +} + +void StickerPanInner::ui_repaintInlineItem(const LayoutInlineItem *layout) { + uint64 ms = getms(); + if (_lastScrolled + 100 <= ms) { + update(); + } else { + _updateInlineItems.start(_lastScrolled + 100 - ms); + } +} + +bool StickerPanInner::ui_isInlineItemVisible(const LayoutInlineItem *layout) { + int32 position = layout->position(); + if (!_showingInlineItems || position < 0) return false; + + int32 row = position / MatrixRowShift, col = position % MatrixRowShift; + t_assert((row < _inlineRows.size()) && (col < _inlineRows.at(row).items.size())); + + const InlineItems &inlineItems(_inlineRows.at(row).items); + int32 top = st::emojiPanHeader; + for (int32 i = 0; i < row; ++i) { + top += _inlineRows.at(i).height; + } + + return (top < _top + _maxHeight) && (top + _inlineRows.at(row).items.at(col)->height() > _top); +} + +bool StickerPanInner::ui_isInlineItemBeingChosen() { + return _showingInlineItems; +} + void StickerPanInner::appendSet(uint64 setId) { const StickerSets &sets(cStickerSets()); StickerSets::const_iterator it = sets.constFind(setId); @@ -1542,7 +2049,17 @@ void StickerPanInner::appendSet(uint64 setId) { _sets.push_back(DisplayedSet(it->id, it->flags, it->title, pack.size() + 1, pack)); } -void StickerPanInner::refreshRecent(bool performResize) { +void StickerPanInner::refreshRecent() { + if (_showingInlineItems) { + if (_showingSavedGifs) { + refreshSavedGifs(); + } + } else { + refreshRecentStickers(); + } +} + +void StickerPanInner::refreshRecentStickers(bool performResize) { _custom.clear(); clearSelection(true); StickerSets::const_iterator customIt = cStickerSets().constFind(CustomStickerSetId); @@ -1579,7 +2096,7 @@ void StickerPanInner::refreshRecent(bool performResize) { } } - if (performResize) { + if (performResize && !_showingInlineItems) { int32 h = countHeight(); if (h != height()) { resize(width(), h); @@ -1590,14 +2107,15 @@ void StickerPanInner::refreshRecent(bool performResize) { } } -void StickerPanInner::fillIcons(QVector &icons) { +void StickerPanInner::fillIcons(QList &icons) { icons.clear(); - if (_sets.isEmpty()) return; + icons.reserve(_sets.size() + 1); + if (!cSavedGifs().isEmpty()) icons.push_back(StickerIcon(NoneStickerSetId)); - icons.reserve(_sets.size()); + if (_sets.isEmpty()) return; int32 i = 0; if (_sets.at(0).id == RecentStickerSetId) ++i; - if (i > 0) icons.push_back(StickerIcon()); + if (i > 0) icons.push_back(StickerIcon(RecentStickerSetId)); for (int32 l = _sets.size(); i < l; ++i) { DocumentData *s = _sets.at(i).pack.at(0); int32 availw = st::rbEmoji.width - 2 * st::stickerIconPadding, availh = st::rbEmoji.height - 2 * st::stickerIconPadding; @@ -1621,6 +2139,13 @@ void StickerPanInner::fillPanels(QVector &panels) { panels.at(i)->deleteLater(); } panels.clear(); + + if (_showingInlineItems) { + panels.push_back(new EmojiPanel(parentWidget(), _showingSavedGifs ? lang(lng_saved_gifs) : _inlineBotTitle, NoneStickerSetId, true, 0)); + panels.back()->show(); + return; + } + if (_sets.isEmpty()) return; int y = 0; @@ -1636,8 +2161,9 @@ void StickerPanInner::fillPanels(QVector &panels) { } } - void StickerPanInner::refreshPanels(QVector &panels) { + if (_showingInlineItems) return; + if (panels.size() != _sets.size()) return fillPanels(panels); int32 y = 0; @@ -1655,6 +2181,79 @@ void StickerPanInner::updateSelected() { 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; + HistoryCursorState cursor = HistoryDefaultCursorState; + if (sy >= 0) { + row = 0; + for (int32 rows = _inlineRows.size(); row < rows; ++row) { + if (sy < _inlineRows.at(row).height) { + break; + } + sy -= _inlineRows.at(row).height; + } + } + if (sx >= 0 && row >= 0 && row < _inlineRows.size()) { + const InlineItems &inlineItems(_inlineRows.at(row).items); + col = 0; + for (int32 cols = inlineItems.size(); col < cols; ++col) { + int32 width = inlineItems.at(col)->width(); + if (sx < width) { + break; + } + sx -= width + st::inlineResultsSkip; + } + if (col < inlineItems.size()) { + sel = row * MatrixRowShift + col; + inlineItems.at(col)->getState(lnk, cursor, sx, sy); + } else { + row = col = -1; + } + } else { + row = col = -1; + } + int32 srow = (_selected >= 0) ? (_selected / MatrixRowShift) : -1; + int32 scol = (_selected >= 0) ? (_selected % MatrixRowShift) : -1; + if (_selected != sel) { + if (srow >= 0 && scol >= 0) { + t_assert(srow >= 0 && srow < _inlineRows.size() && scol >= 0 && scol < _inlineRows.at(srow).items.size()); + Ui::repaintInlineItem(_inlineRows.at(srow).items.at(scol)); + } + _selected = sel; + if (row >= 0 && col >= 0) { + t_assert(row >= 0 && row < _inlineRows.size() && col >= 0 && col < _inlineRows.at(row).items.size()); + Ui::repaintInlineItem(_inlineRows.at(row).items.at(col)); + } + 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 (_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)); + } + + } + return; + } + int y, ytill = 0, sx = (rtl() ? width() - p.x() : p.x()) - st::stickerPanPadding; for (int c = 0, l = _sets.size(); c < l; ++c) { const DisplayedSet &set(_sets.at(c)); @@ -1733,15 +2332,22 @@ void StickerPanInner::updateSelected() { Ui::showStickerPreview(_sets.at(newSelTab).pack.at(newSel % MatrixRowShift)); } } - if (startanim) anim::start(this); + if (startanim && !_a_selected.animating()) _a_selected.start(); } void StickerPanInner::onSettings() { - App::showLayer(new StickersBox()); + Ui::showLayer(new StickersBox()); } void StickerPanInner::onPreview() { - if (_pressedSel >= 0 && _pressedSel < MatrixRowShift * _sets.size()) { + 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; + } + } 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)); @@ -1750,12 +2356,22 @@ void StickerPanInner::onPreview() { } } -bool StickerPanInner::animStep(float64 ms) { - uint64 now = getms(); +void StickerPanInner::onUpdateInlineItems() { + if (!_showingInlineItems) return; + + uint64 ms = getms(); + if (_lastScrolled + 100 <= ms) { + update(); + } else { + _updateInlineItems.start(_lastScrolled + 100 - ms); + } +} + +void StickerPanInner::step_selected(uint64 ms, bool timer) { QRegion toUpdate; for (Animations::iterator i = _animations.begin(); i != _animations.end();) { int index = qAbs(i.key()) - 1, tab = (index / MatrixRowShift), sel = index % MatrixRowShift; - float64 dt = float64(now - i.value()) / st::emojiPanDuration; + float64 dt = float64(ms - i.value()) / st::emojiPanDuration; if (dt >= 1) { _sets[tab].hovers[sel] = (i.key() > 0) ? 1 : 0; i = _animations.erase(i); @@ -1765,13 +2381,44 @@ bool StickerPanInner::animStep(float64 ms) { } toUpdate += stickerRect(tab, sel); } - rtlupdate(toUpdate.boundingRect()); - return !_animations.isEmpty(); + if (timer) rtlupdate(toUpdate.boundingRect()); + if (_animations.isEmpty()) _a_selected.stop(); } void StickerPanInner::showStickerSet(uint64 setId) { clearSelection(true); + if (setId == NoneStickerSetId) { + bool wasNotShowingGifs = !_showingInlineItems; + if (wasNotShowingGifs) { + _showingInlineItems = true; + _showingSavedGifs = true; + cSetShowingSavedGifs(true); + emit saveConfigDelayed(SaveRecentEmojisTimeout); + } + refreshSavedGifs(); + emit scrollToY(0); + emit scrollUpdated(); + showFinish(); + return; + } + + if (_showingInlineItems) { + if (_setGifCommand && _showingSavedGifs) { + App::insertBotCommand(qsl(""), true); + } + _setGifCommand = false; + + _showingInlineItems = false; + cSetShowingSavedGifs(false); + emit saveConfigDelayed(SaveRecentEmojisTimeout); + + Notify::clipStopperHidden(ClipStopperSavedGifsPanel); + + refreshRecentStickers(true); + emit refreshIcons(); + } + int32 y = 0; for (int c = 0; c < _sets.size(); ++c) { if (_sets.at(c).id == setId) break; @@ -1780,14 +2427,38 @@ void StickerPanInner::showStickerSet(uint64 setId) { } emit scrollToY(y); + emit scrollUpdated(); _lastMousePos = QCursor::pos(); update(); } -EmojiPanel::EmojiPanel(QWidget *parent, const QString &text, uint64 setId, bool special, int32 wantedY) : TWidget(parent), -_wantedY(wantedY), _setId(setId), _special(special), _deleteVisible(false), _delete(special ? 0 : new IconedButton(this, st::notifyClose)) { // NoneStickerSetId if in emoji +void StickerPanInner::updateShowingSavedGifs() { + if (cShowingSavedGifs()) { + if (!_showingInlineItems) { + clearSelection(true); + _showingSavedGifs = _showingInlineItems = true; + if (_inlineRows.isEmpty()) refreshSavedGifs(); + } + } else if (!_showingInlineItems) { + clearSelection(true); + _showingSavedGifs = false; + } +} + +void StickerPanInner::showFinish() { + if (_showingInlineItems && _showingSavedGifs) { + _setGifCommand = App::insertBotCommand('@' + cInlineGifBotUsername(), true); + } +} + +EmojiPanel::EmojiPanel(QWidget *parent, const QString &text, uint64 setId, bool special, int32 wantedY) : TWidget(parent) +, _wantedY(wantedY) +, _setId(setId) +, _special(special) +, _deleteVisible(false) +, _delete(special ? 0 : new IconedButton(this, st::notifyClose)) { // NoneStickerSetId if in emoji resize(st::emojiPanWidth, st::emojiPanHeader); setMouseTracking(true); setFocusPolicy(Qt::NoFocus); @@ -1815,7 +2486,7 @@ void EmojiPanel::updateText() { availw -= st::notifyClose.icon.pxWidth() + st::emojiPanHeaderLeft; } } else { - QString switchText = lang((_setId != NoneStickerSetId) ? lng_switch_emoji : lng_switch_stickers); + QString switchText = lang((_setId != NoneStickerSetId) ? lng_switch_emoji : (cSavedGifs().isEmpty() ? lng_switch_stickers : lng_switch_stickers_gifs)); availw -= st::emojiSwitchSkip + st::emojiPanHeaderFont->width(switchText); } _text = st::emojiPanHeaderFont->elided(_fullText, availw); @@ -1847,11 +2518,36 @@ void EmojiPanel::paintEvent(QPaintEvent *e) { p.drawTextLeft(st::emojiPanHeaderLeft, st::emojiPanHeaderTop, width(), _text); } -EmojiSwitchButton::EmojiSwitchButton(QWidget *parent, bool toStickers) : Button(parent), -_toStickers(toStickers), _text(lang(_toStickers ? lng_switch_stickers : lng_switch_emoji)), -_textWidth(st::emojiPanHeaderFont->width(_text)) { - int32 w = st::emojiSwitchSkip + _textWidth + (st::emojiSwitchSkip - st::emojiSwitchImgSkip); +EmojiSwitchButton::EmojiSwitchButton(QWidget *parent, bool toStickers) : Button(parent) +, _toStickers(toStickers) { setCursor(style::cur_pointer); + updateText(); +} + +void EmojiSwitchButton::updateText(const QString &inlineBotUsername) { + if (_toStickers) { + if (inlineBotUsername.isEmpty()) { + _text = lang(cSavedGifs().isEmpty() ? lng_switch_stickers : lng_switch_stickers_gifs); + } else { + _text = '@' + inlineBotUsername; + } + } else { + _text = lang(lng_switch_emoji); + } + _textWidth = st::emojiPanHeaderFont->width(_text); + if (_toStickers && !inlineBotUsername.isEmpty()) { + int32 maxw = 0; + for (int c = 0; c < emojiTabCount; ++c) { + maxw = qMax(maxw, st::emojiPanHeaderFont->width(lang(LangKey(lng_emoji_category0 + c)))); + } + maxw += st::emojiPanHeaderLeft + st::emojiSwitchSkip + (st::emojiSwitchSkip - st::emojiSwitchImgSkip); + if (_textWidth > st::emojiPanWidth - maxw) { + _text = st::emojiPanHeaderFont->elided(_text, st::emojiPanWidth - maxw); + _textWidth = st::emojiPanHeaderFont->width(_text); + } + } + + int32 w = st::emojiSwitchSkip + _textWidth + (st::emojiSwitchSkip - st::emojiSwitchImgSkip); resize(w, st::emojiPanHeader); } @@ -1869,22 +2565,50 @@ void EmojiSwitchButton::paintEvent(QPaintEvent *e) { } } -EmojiPan::EmojiPan(QWidget *parent) : TWidget(parent), _maxHeight(st::emojiPanMaxHeight), -_horizontal(false), _noTabUpdate(false), _hiding(false), a_opacity(0), _shadow(st::dropdownDef.shadow), -_recent(this , qsl("emoji_group"), dbietRecent , QString(), true , st::rbEmojiRecent), -_people(this , qsl("emoji_group"), dbietPeople , QString(), false, st::rbEmojiPeople), -_nature(this , qsl("emoji_group"), dbietNature , QString(), false, st::rbEmojiNature), -_food(this , qsl("emoji_group"), dbietFood , QString(), false, st::rbEmojiFood), -_activity(this, qsl("emoji_group"), dbietActivity, QString(), false, st::rbEmojiActivity), -_travel(this , qsl("emoji_group"), dbietTravel , QString(), false, st::rbEmojiTravel), -_objects(this , qsl("emoji_group"), dbietObjects , QString(), false, st::rbEmojiObjects), -_symbols(this , qsl("emoji_group"), dbietSymbols , QString(), false, st::rbEmojiSymbols), -_iconOver(-1), _iconSel(0), _iconDown(-1), _iconsDragging(false), -_iconAnim(animFunc(this, &EmojiPan::iconAnim)), -_iconsLeft(0), _iconsTop(0), _iconsStartX(0), _iconsMax(0), _iconsX(0, 0), _iconSelX(0, 0), _iconsStartAnim(0), -_stickersShown(false), _moveStart(0), -e_scroll(this, st::emojiScroll), e_inner(), e_switch(&e_scroll, true), -s_scroll(this, st::emojiScroll), s_inner(), s_switch(&s_scroll, false), _removingSetId(0) { +EmojiPan::EmojiPan(QWidget *parent) : TWidget(parent) +, _maxHeight(st::emojiPanMaxHeight) +, _contentMaxHeight(st::emojiPanMaxHeight) +, _contentHeight(_contentMaxHeight) +, _contentHeightEmoji(_contentHeight - st::rbEmoji.height) +, _contentHeightStickers(_contentHeight - st::rbEmoji.height) +, _horizontal(false) +, _noTabUpdate(false) +, _hiding(false) +, a_opacity(0) +, _a_appearance(animation(this, &EmojiPan::step_appearance)) +, _shadow(st::dropdownDef.shadow) +, _recent(this , qsl("emoji_group"), dbietRecent , QString(), true , st::rbEmojiRecent) +, _people(this , qsl("emoji_group"), dbietPeople , QString(), false, st::rbEmojiPeople) +, _nature(this , qsl("emoji_group"), dbietNature , QString(), false, st::rbEmojiNature) +, _food(this , qsl("emoji_group"), dbietFood , QString(), false, st::rbEmojiFood) +, _activity(this, qsl("emoji_group"), dbietActivity, QString(), false, st::rbEmojiActivity) +, _travel(this , qsl("emoji_group"), dbietTravel , QString(), false, st::rbEmojiTravel) +, _objects(this , qsl("emoji_group"), dbietObjects , QString(), false, st::rbEmojiObjects) +, _symbols(this , qsl("emoji_group"), dbietSymbols , QString(), false, st::rbEmojiSymbols) +, _iconOver(-1) +, _iconSel(0) +, _iconDown(-1) +, _iconsDragging(false) +, _a_icons(animation(this, &EmojiPan::step_icons)) +, _iconsLeft(0) +, _iconsTop(0) +, _iconsStartX(0) +, _iconsMax(0) +, _iconsX(0, 0) +, _iconSelX(0, 0) +, _iconsStartAnim(0) +, _stickersShown(false) +, _shownFromInlineQuery(false) +, _a_slide(animation(this, &EmojiPan::step_slide)) +, e_scroll(this, st::emojiScroll) +, e_inner() +, e_switch(&e_scroll, true) +, s_scroll(this, st::emojiScroll) +, s_inner() +, s_switch(&s_scroll, false) +, _removingSetId(0) +, _inlineBot(0) +, _inlineRequestId(0) { setFocusPolicy(Qt::NoFocus); e_scroll.setFocusPolicy(Qt::NoFocus); e_scroll.viewport()->setFocusPolicy(Qt::NoFocus); @@ -1892,11 +2616,12 @@ s_scroll(this, st::emojiScroll), s_inner(), s_switch(&s_scroll, false), _removin s_scroll.viewport()->setFocusPolicy(Qt::NoFocus); _width = st::dropdownDef.padding.left() + st::emojiPanWidth + st::dropdownDef.padding.right(); - _height = st::dropdownDef.padding.top() + _maxHeight + st::dropdownDef.padding.bottom(); + _height = st::dropdownDef.padding.top() + _contentHeight + st::dropdownDef.padding.bottom(); + _bottom = 0; resize(_width, _height); - e_scroll.resize(st::emojiPanWidth, _maxHeight - st::rbEmoji.height); - s_scroll.resize(st::emojiPanWidth, _maxHeight - st::rbEmoji.height); + e_scroll.resize(st::emojiPanWidth, _contentHeightEmoji); + s_scroll.resize(st::emojiPanWidth, _contentHeightStickers); e_scroll.move(st::dropdownDef.padding.left(), st::dropdownDef.padding.top()); e_scroll.setWidget(&e_inner); @@ -1907,7 +2632,7 @@ s_scroll(this, st::emojiScroll), s_inner(), s_switch(&s_scroll, false), _removin s_inner.moveToLeft(0, 0, s_scroll.width()); int32 left = _iconsLeft = st::dropdownDef.padding.left() + (st::emojiPanWidth - 8 * st::rbEmoji.width) / 2; - int32 top = _iconsTop = st::dropdownDef.padding.top() + _maxHeight - st::rbEmoji.height; + int32 top = _iconsTop = st::dropdownDef.padding.top() + _contentHeight - st::rbEmoji.height; prepareTab(left, top, _width, _recent); prepareTab(left, top, _width, _people); prepareTab(left, top, _width, _nature); @@ -1926,12 +2651,17 @@ s_scroll(this, st::emojiScroll), s_inner(), s_switch(&s_scroll, false), _removin connect(&e_inner, SIGNAL(disableScroll(bool)), &e_scroll, SLOT(disableScroll(bool))); connect(&s_inner, SIGNAL(scrollToY(int)), &s_scroll, SLOT(scrollToY(int))); + connect(&s_inner, SIGNAL(scrollUpdated()), this, SLOT(onScroll())); connect(&e_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); connect(&s_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); 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(emptyInlineRows()), this, SLOT(onEmptyInlineRows())); connect(&s_switch, SIGNAL(clicked()), this, SLOT(onSwitch())); connect(&e_switch, SIGNAL(clicked()), this, SLOT(onSwitch())); @@ -1943,6 +2673,15 @@ s_scroll(this, st::emojiScroll), s_inner(), s_switch(&s_scroll, false), _removin connect(&e_inner, SIGNAL(needRefreshPanels()), this, SLOT(onRefreshPanels())); connect(&s_inner, SIGNAL(needRefreshPanels()), this, SLOT(onRefreshPanels())); + _saveConfigTimer.setSingleShot(true); + connect(&_saveConfigTimer, SIGNAL(timeout()), this, SLOT(onSaveConfig())); + connect(&e_inner, SIGNAL(saveConfigDelayed(int32)), this, SLOT(onSaveConfigDelayed(int32))); + connect(&s_inner, SIGNAL(saveConfigDelayed(int32)), this, SLOT(onSaveConfigDelayed(int32))); + + // inline bots + _inlineRequestTimer.setSingleShot(true); + connect(&_inlineRequestTimer, SIGNAL(timeout()), this, SLOT(onInlineRequest())); + if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged())); } @@ -1952,28 +2691,39 @@ s_scroll(this, st::emojiScroll), s_inner(), s_switch(&s_scroll, false), _removin } void EmojiPan::setMaxHeight(int32 h) { - h = qMin(int(st::emojiPanMaxHeight), h); - if (h == _maxHeight) return; - - int32 was = _maxHeight; _maxHeight = h; + updateContentHeight(); +} + +void EmojiPan::updateContentHeight() { + int32 h = qMin(_contentMaxHeight, _maxHeight); + int32 he = h - st::rbEmoji.height; + int32 hs = h - (s_inner.showSectionIcons() ? st::rbEmoji.height : 0); + if (h == _contentHeight && he == _contentHeightEmoji && hs == _contentHeightStickers) return; + + int32 was = _contentHeight, wase = _contentHeightEmoji, wass = _contentHeightStickers; + _contentHeight = h; + _contentHeightEmoji = he; + _contentHeightStickers = hs; + + _height = st::dropdownDef.padding.top() + _contentHeight + st::dropdownDef.padding.bottom(); - _height = st::dropdownDef.padding.top() + _maxHeight + st::dropdownDef.padding.bottom(); resize(_width, _height); + move(x(), _bottom - height()); - if (was > _maxHeight) { - e_scroll.resize(st::emojiPanWidth, _maxHeight - st::rbEmoji.height); - s_scroll.resize(st::emojiPanWidth, _maxHeight - st::rbEmoji.height); - s_inner.setMaxHeight(_maxHeight); - e_inner.setMaxHeight(_maxHeight); + if (was > _contentHeight || (was == _contentHeight && wass > _contentHeightStickers)) { + e_scroll.resize(st::emojiPanWidth, _contentHeightEmoji); + s_scroll.resize(st::emojiPanWidth, _contentHeightStickers); + s_inner.setMaxHeight(_contentHeightStickers); + e_inner.setMaxHeight(_contentHeightEmoji); } else { - s_inner.setMaxHeight(_maxHeight); - e_inner.setMaxHeight(_maxHeight); - e_scroll.resize(st::emojiPanWidth, _maxHeight - st::rbEmoji.height); - s_scroll.resize(st::emojiPanWidth, _maxHeight - st::rbEmoji.height); + s_inner.setMaxHeight(_contentHeightStickers); + e_inner.setMaxHeight(_contentHeightEmoji); + e_scroll.resize(st::emojiPanWidth, _contentHeightEmoji); + s_scroll.resize(st::emojiPanWidth, _contentHeightStickers); } - _iconsTop = st::dropdownDef.padding.top() + _maxHeight - st::rbEmoji.height; + _iconsTop = st::dropdownDef.padding.top() + _contentHeight - st::rbEmoji.height; _recent.move(_recent.x(), _iconsTop); _people.move(_people.x(), _iconsTop); _nature.move(_nature.x(), _iconsTop); @@ -1999,6 +2749,14 @@ void EmojiPan::onWndActiveChanged() { } } +void EmojiPan::onSaveConfig() { + Local::writeUserSettings(); +} + +void EmojiPan::onSaveConfigDelayed(int32 delay) { + _saveConfigTimer.start(delay); +} + void EmojiPan::paintEvent(QPaintEvent *e) { Painter p(this); @@ -2014,29 +2772,25 @@ void EmojiPan::paintEvent(QPaintEvent *e) { if (_toCache.isNull()) { if (_cache.isNull()) { p.fillRect(myrtlrect(r.x() + r.width() - st::emojiScroll.width, r.y(), st::emojiScroll.width, e_scroll.height()), st::white->b); - if (_stickersShown) { - p.fillRect(r.left(), _iconsTop, r.width(), st::rbEmoji.height, st::emojiPanCategories->b); + if (_stickersShown && s_inner.showSectionIcons()) { + p.fillRect(r.left(), _iconsTop, r.width(), st::rbEmoji.height, st::emojiPanCategories); p.drawSpriteLeft(_iconsLeft + 7 * st::rbEmoji.width + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), st::stickersSettings); if (!_icons.isEmpty()) { - int32 x = _iconsLeft, i = 0, selxrel = _iconSelX.current(), selx = x + selxrel - _iconsX.current(); - if (!_icons.at(i).sticker) { - if (selxrel > 0) { - if (_iconHovers.at(i) < 1) { - p.drawSpriteLeft(x + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), st::rbEmojiRecent.imageRect); - } - if (_iconHovers.at(i) > 0) { - p.drawSpriteLeft(x + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), st::rbEmojiRecent.overImageRect); - } + int32 x = _iconsLeft, i = 0, selxrel = _iconsLeft + _iconSelX.current(), selx = selxrel - _iconsX.current(); + for (int32 l = _icons.size(); i < l && !_icons.at(i).sticker; ++i) { + bool gifs = (_icons.at(i).setId == NoneStickerSetId); + if (selxrel != x) { + p.drawSpriteLeft(x + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), gifs ? st::savedGifsOver : st::rbEmojiRecent.imageRect); } - if (selxrel < st::rbEmoji.width) { - p.setOpacity(1 - (selxrel / st::rbEmoji.width)); - p.drawSpriteLeft(x + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), st::rbEmojiRecent.chkImageRect); + if (selxrel < x + st::rbEmoji.width && selxrel > x - st::rbEmoji.width) { + p.setOpacity(1 - (qAbs(selxrel - x) / st::rbEmoji.width)); + p.drawSpriteLeft(x + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), gifs ? st::savedGifsActive : st::rbEmojiRecent.chkImageRect); p.setOpacity(1); } x += st::rbEmoji.width; - ++i; } + int32 skip = i; QRect clip(x, _iconsTop, _iconsLeft + 7 * st::rbEmoji.width - x, st::rbEmoji.height); if (rtl()) clip.moveLeft(width() - x - clip.width()); @@ -2044,28 +2798,23 @@ 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 + 7 + (_icons.at(0).sticker ? 1 : 0)); i < l; ++i) { + for (int32 l = qMin(_icons.size(), i + 8 - skip); i < l; ++i) { const StickerIcon &s(_icons.at(i)); s.sticker->thumb->load(); QPixmap pix(s.sticker->thumb->pix(s.pixw, s.pixh)); - //if (_iconSel == i) { - // p.setOpacity(1); - //} else { - // p.setOpacity(1. * _iconHovers.at(i) + st::stickerIconOpacity * (1 - _iconHovers.at(i))); - //} + p.drawPixmapLeft(x + (st::rbEmoji.width - s.pixw) / 2, _iconsTop + (st::rbEmoji.height - s.pixh) / 2, width(), pix); x += st::rbEmoji.width; - p.setOpacity(1); } if (rtl()) selx = width() - selx - st::rbEmoji.width; - p.setOpacity(_icons.at(0).sticker ? 1. : qMax(1., selx / st::rbEmoji.width)); - p.fillRect(selx, _iconsTop + st::rbEmoji.height - st::stickerIconPadding, st::rbEmoji.width, st::stickerIconSel, st::stickerIconSelColor->b); + p.setOpacity(skip ? qMax(1., selx / (skip * st::rbEmoji.width)) : 1.); + p.fillRect(selx, _iconsTop + st::rbEmoji.height - st::stickerIconPadding, st::rbEmoji.width, st::stickerIconSel, st::stickerIconSelColor); float64 o_left = snap(float64(_iconsX.current()) / st::stickerIconLeft.pxWidth(), 0., 1.); if (o_left > 0) { p.setOpacity(o_left); - p.drawSpriteLeft(QRect(_iconsLeft + (_icons.at(0).sticker ? 0 : st::rbEmoji.width), _iconsTop, st::stickerIconLeft.pxWidth(), st::rbEmoji.height), width(), st::stickerIconLeft); + p.drawSpriteLeft(QRect(_iconsLeft + skip * st::rbEmoji.width, _iconsTop, st::stickerIconLeft.pxWidth(), st::rbEmoji.height), width(), st::stickerIconLeft); } float64 o_right = snap(float64(_iconsMax - _iconsX.current()) / st::stickerIconRight.pxWidth(), 0., 1.); if (o_right > 0) { @@ -2073,10 +2822,13 @@ void EmojiPan::paintEvent(QPaintEvent *e) { p.drawSpriteRight(QRect(width() - _iconsLeft - 7 * st::rbEmoji.width, _iconsTop, st::stickerIconRight.pxWidth(), st::rbEmoji.height), width(), st::stickerIconRight); } } - } else { - p.fillRect(r.left(), _recent.y(), (rtl() ? _objects.x() : _recent.x() - r.left()), st::rbEmoji.height, st::emojiPanCategories->b); + } else if (_stickersShown) { int32 x = rtl() ? (_recent.x() + _recent.width()) : (_objects.x() + _objects.width()); - p.fillRect(x, _recent.y(), r.left() + r.width() - x, st::rbEmoji.height, st::emojiPanCategories->b); + p.fillRect(x, _recent.y(), r.left() + r.width() - x, st::rbEmoji.height, st::white); + } else { + p.fillRect(r.left(), _recent.y(), (rtl() ? _objects.x() : _recent.x() - r.left()), st::rbEmoji.height, st::emojiPanCategories); + int32 x = rtl() ? (_recent.x() + _recent.width()) : (_objects.x() + _objects.width()); + p.fillRect(x, _recent.y(), r.left() + r.width() - x, st::rbEmoji.height, st::emojiPanCategories); } } else { p.fillRect(r, st::white->b); @@ -2114,14 +2866,27 @@ void EmojiPan::paintEvent(QPaintEvent *e) { } } +void EmojiPan::moveBottom(int32 bottom, bool force) { + _bottom = bottom; + if (isHidden() && !force) { + move(x(), _bottom - height()); + return; + } + if (_stickersShown && s_inner.inlineResultsShown()) { + moveToLeft(0, _bottom - height()); + } else { + moveToRight(0, _bottom - height()); + } +} + void EmojiPan::enterEvent(QEvent *e) { _hideTimer.stop(); if (_hiding) showStart(); } void EmojiPan::leaveEvent(QEvent *e) { - if (_removingSetId) return; - if (animating()) { + if (_removingSetId || s_inner.inlineResultsShown()) return; + if (_a_appearance.animating()) { hideStart(); } else { _hideTimer.start(300); @@ -2134,7 +2899,8 @@ void EmojiPan::otherEnter() { } void EmojiPan::otherLeave() { - if (animating()) { + if (_removingSetId || s_inner.inlineResultsShown()) return; + if (_a_appearance.animating()) { hideStart(); } else { _hideTimer.start(0); @@ -2147,7 +2913,7 @@ void EmojiPan::mousePressEvent(QMouseEvent *e) { updateSelected(); if (_iconOver == _icons.size()) { - App::showLayer(new StickersBox()); + Ui::showLayer(new StickersBox()); } else { _iconDown = _iconOver; _iconsMouseDown = _iconsMousePos; @@ -2160,7 +2926,12 @@ void EmojiPan::mouseMoveEvent(QMouseEvent *e) { _iconsMousePos = e ? e->globalPos() : QCursor::pos(); updateSelected(); - if (!_iconsDragging && !_icons.isEmpty() && _iconDown >= (_icons.at(0).sticker ? 0 : 1)) { + int32 skip = 0; + for (int32 i = 0, l = _icons.size(); i < l; ++i) { + if (_icons.at(i).sticker) break; + ++skip; + } + if (!_iconsDragging && !_icons.isEmpty() && _iconDown >= skip) { if ((_iconsMousePos - _iconsMouseDown).manhattanLength() >= QApplication::startDragDistance()) { _iconsDragging = true; } @@ -2170,7 +2941,7 @@ void EmojiPan::mouseMoveEvent(QMouseEvent *e) { if (newX != _iconsX.current()) { _iconsX = anim::ivalue(newX, newX); _iconsStartAnim = 0; - if (_iconAnimations.isEmpty()) _iconAnim.stop(); + if (_iconAnimations.isEmpty()) _a_icons.stop(); updateIcons(); } } @@ -2188,7 +2959,7 @@ void EmojiPan::mouseReleaseEvent(QMouseEvent *e) { if (newX != _iconsX.current()) { _iconsX = anim::ivalue(newX, newX); _iconsStartAnim = 0; - if (_iconAnimations.isEmpty()) _iconAnim.stop(); + if (_iconAnimations.isEmpty()) _a_icons.stop(); updateIcons(); } _iconsDragging = false; @@ -2205,32 +2976,39 @@ void EmojiPan::mouseReleaseEvent(QMouseEvent *e) { bool EmojiPan::event(QEvent *e) { if (e->type() == QEvent::TouchBegin) { - int a = 0; - } else if (e->type() == QEvent::Wheel && !_icons.isEmpty() && _iconOver >= (_icons.at(0).sticker ? 0 : 1) && _iconOver < _icons.size() && _iconDown < 0) { - QWheelEvent *ev = static_cast(e); - bool hor = (ev->angleDelta().x() != 0 || ev->orientation() == Qt::Horizontal); - bool ver = (ev->angleDelta().y() != 0 || ev->orientation() == Qt::Vertical); - if (hor) _horizontal = true; - int32 newX = _iconsX.current(); - if (/*_horizontal && */hor) { - newX = snap(newX - (rtl() ? -1 : 1) * (ev->pixelDelta().x() ? ev->pixelDelta().x() : ev->angleDelta().x()), 0, _iconsMax); - } else if (/*!_horizontal && */ver) { - newX = snap(newX - (ev->pixelDelta().y() ? ev->pixelDelta().y() : ev->angleDelta().y()), 0, _iconsMax); + + } else if (e->type() == QEvent::Wheel) { + int32 skip = 0; + for (int32 i = 0, l = _icons.size(); i < l; ++i) { + if (_icons.at(i).sticker) break; + ++skip; } - if (newX != _iconsX.current()) { - _iconsX = anim::ivalue(newX, newX); - _iconsStartAnim = 0; - if (_iconAnimations.isEmpty()) _iconAnim.stop(); - updateSelected(); - updateIcons(); + if (!_icons.isEmpty() && _iconOver >= skip && _iconOver < _icons.size() && _iconDown < 0) { + QWheelEvent *ev = static_cast(e); + bool hor = (ev->angleDelta().x() != 0 || ev->orientation() == Qt::Horizontal); + bool ver = (ev->angleDelta().y() != 0 || ev->orientation() == Qt::Vertical); + if (hor) _horizontal = true; + int32 newX = _iconsX.current(); + if (/*_horizontal && */hor) { + newX = snap(newX - (rtl() ? -1 : 1) * (ev->pixelDelta().x() ? ev->pixelDelta().x() : ev->angleDelta().x()), 0, _iconsMax); + } else if (/*!_horizontal && */ver) { + newX = snap(newX - (ev->pixelDelta().y() ? ev->pixelDelta().y() : ev->angleDelta().y()), 0, _iconsMax); + } + if (newX != _iconsX.current()) { + _iconsX = anim::ivalue(newX, newX); + _iconsStartAnim = 0; + if (_iconAnimations.isEmpty()) _a_icons.stop(); + updateSelected(); + updateIcons(); + } } } return TWidget::event(e); } void EmojiPan::fastHide() { - if (animating()) { - anim::stop(this); + if (_a_appearance.animating()) { + _a_appearance.stop(); } a_opacity = anim::fvalue(0, 0); _hideTimer.stop(); @@ -2245,6 +3023,15 @@ void EmojiPan::refreshStickers() { } } +void EmojiPan::refreshSavedGifs() { + e_switch.updateText(); + e_switch.moveToRight(0, 0, st::emojiPanWidth); + s_inner.refreshSavedGifs(); + if (!_stickersShown) { + s_inner.preloadImages(); + } +} + void EmojiPan::onRefreshIcons() { _iconOver = -1; _iconHovers.clear(); @@ -2254,7 +3041,7 @@ void EmojiPan::onRefreshIcons() { _iconsX = anim::ivalue(0, 0); _iconSelX.finish(); _iconsStartAnim = 0; - _iconAnim.stop(); + _a_icons.stop(); if (_icons.isEmpty()) { _iconsMax = 0; } else { @@ -2263,6 +3050,10 @@ void EmojiPan::onRefreshIcons() { } updatePanelsPositions(s_panels, s_scroll.scrollTop()); updateSelected(); + if (_stickersShown) { + validateSelectedIcon(); + updateContentHeight(); + } updateIcons(); } @@ -2295,16 +3086,19 @@ void EmojiPan::updateSelected() { newOver = _icons.size(); } else if (!_icons.isEmpty()) { if (y >= _iconsTop && y < _iconsTop + st::rbEmoji.height && x >= 0 && x < 7 * st::rbEmoji.width && x < _icons.size() * st::rbEmoji.width) { - if (!_icons.at(0).sticker) { + int32 skip = 0; + for (int32 i = 0, l = _icons.size(); i < l; ++i) { + if (_icons.at(i).sticker) break; if (x < st::rbEmoji.width) { - newOver = 0; - } else { - x -= st::rbEmoji.width; + newOver = i; + break; } + x -= st::rbEmoji.width; + ++skip; } if (newOver < 0) { x += _iconsX.current(); - newOver = qFloor(x / st::rbEmoji.width) + (_icons.at(0).sticker ? 0 : 1); + newOver = qFloor(x / st::rbEmoji.width) + skip; } } } @@ -2330,22 +3124,26 @@ void EmojiPan::updateSelected() { _iconAnimations.insert(_iconOver + 1, getms()); } } - if (startanim) _iconAnim.start(); + if (startanim && !_a_icons.animating()) _a_icons.start(); } } void EmojiPan::updateIcons() { + if (!_stickersShown || !s_inner.showSectionIcons()) return; + QRect r(st::dropdownDef.padding.left(), st::dropdownDef.padding.top(), _width - st::dropdownDef.padding.left() - st::dropdownDef.padding.right(), _height - st::dropdownDef.padding.top() - st::dropdownDef.padding.bottom()); update(r.left(), _iconsTop, r.width(), st::rbEmoji.height); } -bool EmojiPan::iconAnim(float64 ms) { - if (!_stickersShown) return false; +void EmojiPan::step_icons(uint64 ms, bool timer) { + if (!_stickersShown) { + _a_icons.stop(); + return; + } - uint64 now = getms(); for (Animations::iterator i = _iconAnimations.begin(); i != _iconAnimations.end();) { int index = qAbs(i.key()) - 1; - float64 dt = float64(now - i.value()) / st::emojiPanDuration; + float64 dt = float64(ms - i.value()) / st::emojiPanDuration; if (index >= _iconHovers.size()) { i = _iconAnimations.erase(i); } else if (dt >= 1) { @@ -2358,7 +3156,7 @@ bool EmojiPan::iconAnim(float64 ms) { } if (_iconsStartAnim) { - float64 dt = (now - _iconsStartAnim) / st::stickerIconMove; + float64 dt = (ms - _iconsStartAnim) / st::stickerIconMove; if (dt >= 1) { _iconsStartAnim = 0; _iconsX.finish(); @@ -2367,58 +3165,65 @@ bool EmojiPan::iconAnim(float64 ms) { _iconsX.update(dt, anim::linear); _iconSelX.update(dt, anim::linear); } - updateSelected(); + if (timer) updateSelected(); } - updateIcons(); + if (timer) updateIcons(); - return !_iconAnimations.isEmpty() || _iconsStartAnim; + if (_iconAnimations.isEmpty() && !_iconsStartAnim) { + _a_icons.stop(); + } } -bool EmojiPan::animStep(float64 ms) { - bool res1 = false; - if (_moveStart) { - float64 movems = getms() - _moveStart; - float64 fullDuration = st::introSlideDelta + st::introSlideDuration, dt = ms / fullDuration; - float64 dt1 = (movems > st::introSlideDuration) ? 1 : (movems / st::introSlideDuration), dt2 = (movems > st::introSlideDelta) ? (movems - st::introSlideDelta) / (st::introSlideDuration) : 0; - if (dt2 >= 1) { - a_fromCoord.finish(); - a_fromAlpha.finish(); - a_toCoord.finish(); - a_toAlpha.finish(); - _fromCache = _toCache = QPixmap(); - _moveStart = 0; - if (_cache.isNull()) showAll(); - } else { - a_fromCoord.update(dt1, st::introHideFunc); - a_fromAlpha.update(dt1, st::introAlphaHideFunc); - a_toCoord.update(dt2, st::introShowFunc); - a_toAlpha.update(dt2, st::introAlphaShowFunc); - res1 = true; - } +void EmojiPan::step_slide(float64 ms, bool timer) { + float64 fullDuration = st::introSlideDelta + st::introSlideDuration, dt = ms / fullDuration; + float64 dt1 = (ms > st::introSlideDuration) ? 1 : (ms / st::introSlideDuration), dt2 = (ms > st::introSlideDelta) ? (ms - st::introSlideDelta) / (st::introSlideDuration) : 0; + if (dt2 >= 1) { + _a_slide.stop(); + a_fromCoord.finish(); + a_fromAlpha.finish(); + a_toCoord.finish(); + a_toAlpha.finish(); + _fromCache = _toCache = QPixmap(); + if (_cache.isNull()) showAll(); + } else { + a_fromCoord.update(dt1, st::introHideFunc); + a_fromAlpha.update(dt1, st::introAlphaHideFunc); + a_toCoord.update(dt2, st::introShowFunc); + a_toAlpha.update(dt2, st::introAlphaShowFunc); } - bool res2 = false; - if (!_cache.isNull()) { - float64 dt = ms / st::dropdownDef.duration; - if (dt >= 1) { - a_opacity.finish(); - if (_hiding) { - res1 = false; - hideFinish(); - } else { - _cache = QPixmap(); - if (_toCache.isNull()) showAll(); - } - } else { - a_opacity.update(dt, anim::linear); - res2 = true; - } + if (timer) update(); +} + +void EmojiPan::step_appearance(float64 ms, bool timer) { + if (_cache.isNull()) { + _a_appearance.stop(); + return; } - update(); - return res1 || res2; + + float64 dt = ms / st::dropdownDef.duration; + if (dt >= 1) { + _a_appearance.stop(); + a_opacity.finish(); + if (_hiding) { + hideFinish(); + } else { + _cache = QPixmap(); + if (_toCache.isNull()) showAll(); + } + } else { + a_opacity.update(dt, anim::linear); + } + if (timer) update(); } void EmojiPan::hideStart() { + if (_removingSetId || s_inner.inlineResultsShown()) return; + + hideAnimated(); +} + +void EmojiPan::prepareShowHideCache() { if (_cache.isNull()) { QPixmap from = _fromCache, to = _toCache; _fromCache = _toCache = QPixmap(); @@ -2426,18 +3231,26 @@ void EmojiPan::hideStart() { _cache = myGrab(this, rect().marginsRemoved(st::dropdownDef.padding)); _fromCache = from; _toCache = to; } +} + +void EmojiPan::hideAnimated() { + if (_hiding) return; + + prepareShowHideCache(); hideAll(); _hiding = true; a_opacity.start(0); - anim::start(this); + _a_appearance.start(); } void EmojiPan::hideFinish() { hide(); e_inner.hideFinish(); + s_inner.hideFinish(true); _cache = _toCache = _fromCache = QPixmap(); - _moveStart = 0; + _a_slide.stop(); _horizontal = false; + _hiding = false; e_scroll.scrollToY(0); if (!_recent.checked()) { @@ -2451,35 +3264,44 @@ void EmojiPan::hideFinish() { _iconsX = anim::ivalue(0, 0); _iconSelX = anim::ivalue(0, 0); _iconsStartAnim = 0; - _iconAnim.stop(); + _a_icons.stop(); _iconHovers = _icons.isEmpty() ? QVector() : QVector(_icons.size(), 0); _iconAnimations.clear(); + + Notify::clipStopperHidden(ClipStopperSavedGifsPanel); } void EmojiPan::showStart() { - if (!isHidden() && a_opacity.current() == 1) { + if (!isHidden() && !_hiding) { return; } if (isHidden()) { e_inner.refreshRecent(); - s_inner.refreshRecent(); + if (s_inner.inlineResultsShown() && refreshInlineRows()) { + _stickersShown = true; + _shownFromInlineQuery = true; + } else { + s_inner.refreshRecent(); + _stickersShown = false; + _shownFromInlineQuery = false; + _cache = QPixmap(); // clear after refreshInlineRows() + } + recountContentMaxHeight(); s_inner.preloadImages(); - _stickersShown = false; _fromCache = _toCache = QPixmap(); - _moveStart = 0; - } - if (_cache.isNull()) { - QPixmap from = _fromCache, to = _toCache; - _fromCache = _toCache = QPixmap(); - showAll(); - _cache = myGrab(this, rect().marginsRemoved(st::dropdownDef.padding)); - _fromCache = from; _toCache = to; + _a_slide.stop(); + moveBottom(y() + height(), true); + } else if (_hiding) { + if (s_inner.inlineResultsShown() && refreshInlineRows()) { + onSwitch(); + } } + prepareShowHideCache(); hideAll(); _hiding = false; show(); a_opacity.start(1); - anim::start(this); + _a_appearance.start(); emit updateStickers(); } @@ -2498,9 +3320,10 @@ bool EmojiPan::eventFilter(QObject *obj, QEvent *e) { //} } else if (e->type() == QEvent::MouseButtonPress && static_cast(e)->button() == Qt::LeftButton/* && !dynamic_cast(obj)*/) { if (isHidden() || _hiding) { - otherEnter(); + _hideTimer.stop(); + showStart(); } else { - otherLeave(); + hideAnimated(); } } return false; @@ -2509,6 +3332,7 @@ bool EmojiPan::eventFilter(QObject *obj, QEvent *e) { void EmojiPan::stickersInstalled(uint64 setId) { _stickersShown = true; if (isHidden()) { + moveBottom(y() + height(), true); show(); a_opacity = anim::fvalue(0, 1); a_opacity.update(0, anim::linear); @@ -2516,9 +3340,38 @@ void EmojiPan::stickersInstalled(uint64 setId) { } showAll(); s_inner.showStickerSet(setId); + updateContentHeight(); showStart(); } +void EmojiPan::ui_repaintInlineItem(const LayoutInlineItem *layout) { + if (_stickersShown && !isHidden()) { + s_inner.ui_repaintInlineItem(layout); + } +} + +bool EmojiPan::ui_isInlineItemVisible(const LayoutInlineItem *layout) { + if (_stickersShown && !isHidden()) { + return s_inner.ui_isInlineItemVisible(layout); + } + return false; +} + +bool EmojiPan::ui_isInlineItemBeingChosen() { + if (_stickersShown && !isHidden()) { + return s_inner.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(); @@ -2612,37 +3465,67 @@ void EmojiPan::onScroll() { st = s_scroll.scrollTop(); if (_stickersShown) { updatePanelsPositions(s_panels, st); - - uint64 setId = s_inner.currentSet(st); - int32 newSel = 0; - for (int32 i = 0, l = _icons.size(); i < l; ++i) { - if (_icons.at(i).setId == setId) { - newSel = i; - break; - } - } - if (newSel != _iconSel) { - _iconSel = newSel; - _iconSelX.start(newSel * st::rbEmoji.width); - _iconsX.start(snap((2 * newSel - 7 - ((_icons.isEmpty() || _icons.at(0).sticker) ? 0 : 1)) * int(st::rbEmoji.width) / 2, 0, _iconsMax)); - _iconsStartAnim = getms(); - _iconAnim.start(); - updateSelected(); - updateIcons(); + validateSelectedIcon(true); + if (st + s_scroll.height() > s_scroll.scrollTopMax()) { + onInlineRequest(); } } s_inner.setScrollTop(st); } +void EmojiPan::validateSelectedIcon(bool animated) { + uint64 setId = s_inner.currentSet(s_scroll.scrollTop()); + int32 newSel = 0; + for (int32 i = 0, l = _icons.size(); i < l; ++i) { + if (_icons.at(i).setId == setId) { + newSel = i; + break; + } + } + if (newSel != _iconSel) { + _iconSel = newSel; + int32 skip = 0; + for (int32 i = 0, l = _icons.size(); i < l; ++i) { + if (_icons.at(i).sticker) break; + ++skip; + } + if (animated) { + _iconSelX.start(newSel * st::rbEmoji.width); + } else { + _iconSelX = anim::ivalue(newSel * st::rbEmoji.width, newSel * st::rbEmoji.width); + } + _iconsX.start(snap((2 * newSel - 7 - skip) * int(st::rbEmoji.width) / 2, 0, _iconsMax)); + _iconsStartAnim = getms(); + _a_icons.start(); + updateSelected(); + updateIcons(); + } +} + void EmojiPan::onSwitch() { QPixmap cache = _cache; _fromCache = myGrab(this, rect().marginsRemoved(st::dropdownDef.padding)); _stickersShown = !_stickersShown; - + if (!_stickersShown) { + Notify::clipStopperHidden(ClipStopperSavedGifsPanel); + } else { + if (cShowingSavedGifs() && cSavedGifs().isEmpty()) { + s_inner.showStickerSet(DefaultStickerSetId); + } else if (!cShowingSavedGifs() && !cSavedGifs().isEmpty() && cStickerSets().isEmpty()) { + s_inner.showStickerSet(NoneStickerSetId); + } else { + s_inner.updateShowingSavedGifs(); + } + if (cShowingSavedGifs()) { + s_inner.showFinish(); + } + validateSelectedIcon(); + updateContentHeight(); + } _iconOver = -1; _iconHovers = _icons.isEmpty() ? QVector() : QVector(_icons.size(), 0); _iconAnimations.clear(); - _iconAnim.stop(); + _a_icons.stop(); _cache = QPixmap(); showAll(); @@ -2650,10 +3533,11 @@ void EmojiPan::onSwitch() { _cache = cache; hideAll(); - _moveStart = getms(); if (_stickersShown) { e_inner.hideFinish(); + } else { + s_inner.hideFinish(false); } a_toCoord = (_stickersShown != rtl()) ? anim::ivalue(st::emojiPanWidth, 0) : anim::ivalue(-st::emojiPanWidth, 0); @@ -2661,7 +3545,7 @@ void EmojiPan::onSwitch() { a_fromCoord = (_stickersShown != rtl()) ? anim::ivalue(0, -st::emojiPanWidth) : anim::ivalue(0, st::emojiPanWidth); a_fromAlpha = anim::fvalue(1, 0); - if (!animating()) anim::start(this); + _a_slide.start(); update(); } @@ -2672,12 +3556,12 @@ void EmojiPan::onRemoveSet(quint64 setId) { ConfirmBox *box = new ConfirmBox(lng_stickers_remove_pack(lt_sticker_pack, it->title), lang(lng_box_remove)); connect(box, SIGNAL(confirmed()), this, SLOT(onRemoveSetSure())); connect(box, SIGNAL(destroyed(QObject*)), this, SLOT(onDelayedHide())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } } void EmojiPan::onRemoveSetSure() { - App::wnd()->hideLayer(); + Ui::hideLayer(); StickerSets::iterator it = cRefStickerSets().find(_removingSetId); if (it != cRefStickerSets().cend() && !(it->flags & MTPDstickerSet::flag_official)) { if (it->id && it->access) { @@ -2698,7 +3582,6 @@ void EmojiPan::onRemoveSetSure() { cRefStickerSets().erase(it); int32 removeIndex = cStickerSetsOrder().indexOf(_removingSetId); if (removeIndex >= 0) cRefStickerSetsOrder().removeAt(removeIndex); - cSetStickersHash(stickersCountHash()); refreshStickers(); Local::writeStickers(); if (writeRecent) Local::writeUserSettings(); @@ -2713,126 +3596,430 @@ void EmojiPan::onDelayedHide() { _removingSetId = 0; } -MentionsInner::MentionsInner(MentionsDropdown *parent, MentionRows *rows, HashtagRows *hrows, BotCommandRows *crows) : _parent(parent), _rows(rows), _hrows(hrows), _crows(crows), _sel(-1), _mouseSel(false), _overDelete(false) { +void EmojiPan::clearInlineBot() { + inlineBotChanged(); + e_switch.updateText(); + e_switch.moveToRight(0, 0, st::emojiPanWidth); +} + +bool EmojiPan::hideOnNoInlineResults() { + return _inlineBot && _stickersShown && s_inner.inlineResultsShown() && (_shownFromInlineQuery || _inlineBot->username != cInlineGifBotUsername()); +} + +void EmojiPan::inlineBotChanged() { + if (!_inlineBot) return; + + if (!isHidden() && !_hiding) { + if (hideOnNoInlineResults() || !rect().contains(mapFromGlobal(QCursor::pos()))) { + hideAnimated(); + } + } + + if (_inlineRequestId) MTP::cancel(_inlineRequestId); + _inlineRequestId = 0; + _inlineQuery = _inlineNextQuery = _inlineNextOffset = QString(); + _inlineBot = 0; + for (InlineCache::const_iterator i = _inlineCache.cbegin(), e = _inlineCache.cend(); i != e; ++i) { + delete i.value(); + } + _inlineCache.clear(); + s_inner.inlineBotChanged(); + s_inner.hideInlineRowsPanel(); + + Notify::inlineBotRequesting(false); +} + +void EmojiPan::inlineResultsDone(const MTPmessages_BotResults &result) { + _inlineRequestId = 0; + Notify::inlineBotRequesting(false); + + InlineCache::iterator it = _inlineCache.find(_inlineQuery); + + 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); + uint64 queryId(d.vquery_id.v); + + if (!adding) { + it = _inlineCache.insert(_inlineQuery, new InlineCacheEntry()); + } + it.value()->nextOffset = qs(d.vnext_offset); + + int32 count = v.size(), added = 0; + if (count) { + 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 { + ++added; + it.value()->results.push_back(result); + } + } + + if (!added) { + it.value()->nextOffset = QString(); + } + } else if (adding) { + it.value()->nextOffset = QString(); + } + + if (!showInlineRows(!adding)) { + it.value()->nextOffset = QString(); + } + onScroll(); +} + +bool EmojiPan::inlineResultsFail(const RPCError &error) { + if (mtpIsFlood(error)) return false; + + Notify::inlineBotRequesting(false); + _inlineRequestId = 0; + return true; +} + +void EmojiPan::queryInlineBot(UserData *bot, QString query) { + bool force = false; + if (bot != _inlineBot) { + inlineBotChanged(); + _inlineBot = bot; + force = true; + } + if (_inlineQuery != query || force) { + if (_inlineRequestId) { + MTP::cancel(_inlineRequestId); + _inlineRequestId = 0; + Notify::inlineBotRequesting(false); + } + if (_inlineCache.contains(query)) { + _inlineRequestTimer.stop(); + _inlineQuery = query; + showInlineRows(true); + } else { + _inlineNextQuery = query; + _inlineRequestTimer.start(InlineBotRequestDelay); + } + } +} + +void EmojiPan::onInlineRequest() { + if (_inlineRequestId || !_inlineBot) return; + _inlineQuery = _inlineNextQuery; + + QString nextOffset; + InlineCache::const_iterator i = _inlineCache.constFind(_inlineQuery); + if (i != _inlineCache.cend()) { + nextOffset = i.value()->nextOffset; + 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)); +} + +void EmojiPan::onEmptyInlineRows() { + if (_shownFromInlineQuery || hideOnNoInlineResults()) { + hideAnimated(); + } else if (!_inlineBot) { + s_inner.hideInlineRowsPanel(); + } else { + s_inner.clearInlineRowsPanel(); + } +} + +bool EmojiPan::refreshInlineRows(int32 *added) { + bool clear = true; + InlineCache::const_iterator i = _inlineCache.constFind(_inlineQuery); + if (i != _inlineCache.cend()) { + clear = i.value()->results.isEmpty(); + _inlineNextOffset = i.value()->nextOffset; + } + if (clear) prepareShowHideCache(); + int32 result = s_inner.refreshInlineRows(_inlineBot, clear ? InlineResults() : i.value()->results, false); + if (added) *added = result; + return !clear; +} + +int32 EmojiPan::showInlineRows(bool newResults) { + int32 added = 0; + bool clear = !refreshInlineRows(&added); + if (newResults) s_scroll.scrollToY(0); + + e_switch.updateText(clear ? QString() : _inlineBot->username); + e_switch.moveToRight(0, 0, st::emojiPanWidth); + + bool hidden = isHidden(); + if (!hidden && !clear) { + recountContentMaxHeight(); + } + if (clear) { + if (!hidden && hideOnNoInlineResults()) { + hideAnimated(); + } else { + _cache = QPixmap(); // clear after refreshInlineRows() + } + } else { + _hideTimer.stop(); + if (hidden || _hiding) { + showStart(); + } else if (!_stickersShown) { + onSwitch(); + } + } + + return added; +} + +void EmojiPan::recountContentMaxHeight() { + if (_shownFromInlineQuery) { + _contentMaxHeight = qMin(s_inner.countHeight(true), int(st::emojiPanMaxHeight)); + } else { + _contentMaxHeight = st::emojiPanMaxHeight; + } + updateContentHeight(); +} + +MentionsInner::MentionsInner(MentionsDropdown *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, StickerPack *srows) +: _parent(parent) +, _mrows(mrows) +, _hrows(hrows) +, _brows(brows) +, _srows(srows) +, _stickersPerRow(1) +, _recentInlineBotsInRows(0) +, _sel(-1) +, _mouseSel(false) +, _overDelete(false) { } void MentionsInner::paintEvent(QPaintEvent *e) { - QPainter p(this); + Painter p(this); + + QRect r(e->rect()); + if (r != rect()) p.setClipRect(r); int32 atwidth = st::mentionFont->width('@'), hashwidth = st::mentionFont->width('#'); int32 mentionleft = 2 * st::mentionPadding.left() + st::mentionPhotoSize; int32 mentionwidth = width() - mentionleft - 2 * st::mentionPadding.right(); int32 htagleft = st::btnAttachPhoto.width + st::taMsgField.textMrg.left() - st::lineWidth, htagwidth = width() - st::mentionPadding.right() - htagleft - st::mentionScroll.width; - int32 from = qFloor(e->rect().top() / st::mentionHeight), to = qFloor(e->rect().bottom() / st::mentionHeight) + 1; - int32 last = _rows->isEmpty() ? (_hrows->isEmpty() ? _crows->size() : _hrows->size()) : _rows->size(); - bool hasUsername = _parent->filter().indexOf('@') > 1; - for (int32 i = from; i < to; ++i) { - if (i >= last) break; + if (!_srows->isEmpty()) { + int32 rows = rowscount(_srows->size(), _stickersPerRow); + int32 fromrow = floorclamp(r.y() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows); + int32 torow = ceilclamp(r.y() + r.height() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows); + int32 fromcol = floorclamp(r.x() - st::stickerPanPadding, st::stickerPanSize.width(), 0, _stickersPerRow); + int32 tocol = ceilclamp(r.x() + r.width() - st::stickerPanPadding, st::stickerPanSize.width(), 0, _stickersPerRow); + for (int32 row = fromrow; row < torow; ++row) { + for (int32 col = fromcol; col < tocol; ++col) { + int32 index = row * _stickersPerRow + col; + if (index >= _srows->size()) break; - bool selected = (i == _sel); - if (selected) { - p.fillRect(0, i * st::mentionHeight, width(), st::mentionHeight, st::mentionBgOver->b); - int skip = (st::mentionHeight - st::notifyClose.icon.pxHeight()) / 2; - if (!_hrows->isEmpty()) p.drawPixmap(QPoint(width() - st::notifyClose.icon.pxWidth() - skip, i * st::mentionHeight + skip), App::sprite(), st::notifyClose.icon); + DocumentData *sticker = _srows->at(index); + if (!sticker->sticker()) continue; + + QPoint pos(st::stickerPanPadding + col * st::stickerPanSize.width(), st::stickerPanPadding + row * st::stickerPanSize.height()); + if (_sel == index) { + QPoint tl(pos); + if (rtl()) tl.setX(width() - tl.x() - st::stickerPanSize.width()); + App::roundRect(p, QRect(tl, st::stickerPanSize), st::emojiPanHover, StickerHoverCorners); + } + + bool goodThumb = !sticker->thumb->isNull() && ((sticker->thumb->width() >= 128) || (sticker->thumb->height() >= 128)); + if (goodThumb) { + sticker->thumb->load(); + } else { + sticker->checkSticker(); + } + + float64 coef = qMin((st::stickerPanSize.width() - st::msgRadius * 2) / float64(sticker->dimensions.width()), (st::stickerPanSize.height() - st::msgRadius * 2) / float64(sticker->dimensions.height())); + if (coef > 1) coef = 1; + int32 w = qRound(coef * sticker->dimensions.width()), h = qRound(coef * sticker->dimensions.height()); + if (w < 1) w = 1; + if (h < 1) h = 1; + QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2); + if (goodThumb) { + p.drawPixmapLeft(ppos, width(), sticker->thumb->pix(w, h)); + } else if (!sticker->sticker()->img->isNull()) { + p.drawPixmapLeft(ppos, width(), sticker->sticker()->img->pix(w, h)); + } + } } - p.setPen(st::black->p); - if (!_rows->isEmpty()) { - UserData *user = _rows->at(i); - QString first = (_parent->filter().size() < 2) ? QString() : ('@' + user->username.mid(0, _parent->filter().size() - 1)), second = (_parent->filter().size() < 2) ? ('@' + user->username) : user->username.mid(_parent->filter().size() - 1); - int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second), unamewidth = firstwidth + secondwidth, namewidth = user->nameText.maxWidth(); - if (mentionwidth < unamewidth + namewidth) { - namewidth = (mentionwidth * namewidth) / (namewidth + unamewidth); - unamewidth = mentionwidth - namewidth; - if (firstwidth < unamewidth + st::mentionFont->elidew) { - if (firstwidth < unamewidth) { - first = st::mentionFont->elided(first, unamewidth); - } else if (!second.isEmpty()) { - first = st::mentionFont->elided(first + second, unamewidth); - second = QString(); + } else { + int32 from = qFloor(e->rect().top() / st::mentionHeight), to = qFloor(e->rect().bottom() / st::mentionHeight) + 1; + int32 last = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size(); + bool hasUsername = _parent->filter().indexOf('@') > 1; + for (int32 i = from; i < to; ++i) { + if (i >= last) break; + + bool selected = (i == _sel); + if (selected) { + p.fillRect(0, i * st::mentionHeight, width(), st::mentionHeight, st::mentionBgOver->b); + int skip = (st::mentionHeight - st::notifyClose.icon.pxHeight()) / 2; + if (!_hrows->isEmpty() || (!_mrows->isEmpty() && i < _recentInlineBotsInRows)) p.drawPixmap(QPoint(width() - st::notifyClose.icon.pxWidth() - skip, i * st::mentionHeight + skip), App::sprite(), st::notifyClose.icon); + } + p.setPen(st::black->p); + if (!_mrows->isEmpty()) { + UserData *user = _mrows->at(i); + QString first = (_parent->filter().size() < 2) ? QString() : ('@' + user->username.mid(0, _parent->filter().size() - 1)), second = (_parent->filter().size() < 2) ? ('@' + user->username) : user->username.mid(_parent->filter().size() - 1); + int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second), unamewidth = firstwidth + secondwidth, namewidth = user->nameText.maxWidth(); + if (mentionwidth < unamewidth + namewidth) { + namewidth = (mentionwidth * namewidth) / (namewidth + unamewidth); + unamewidth = mentionwidth - namewidth; + if (firstwidth < unamewidth + st::mentionFont->elidew) { + if (firstwidth < unamewidth) { + first = st::mentionFont->elided(first, unamewidth); + } else if (!second.isEmpty()) { + first = st::mentionFont->elided(first + second, unamewidth); + second = QString(); + } + } else { + second = st::mentionFont->elided(second, unamewidth - firstwidth); } - } else { - second = st::mentionFont->elided(second, unamewidth - firstwidth); } - } - user->photo->load(); - p.drawPixmap(st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), user->photo->pixRounded(st::mentionPhotoSize)); - user->nameText.drawElided(p, 2 * st::mentionPadding.left() + st::mentionPhotoSize, i * st::mentionHeight + st::mentionTop, namewidth); - - p.setFont(st::mentionFont->f); - p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p); - p.drawText(mentionleft + namewidth + st::mentionPadding.right(), i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first); - if (!second.isEmpty()) { - p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p); - p.drawText(mentionleft + namewidth + st::mentionPadding.right() + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second); - } - } else if (!_hrows->isEmpty()) { - QString hrow = _hrows->at(i); - QString first = (_parent->filter().size() < 2) ? QString() : ('#' + hrow.mid(0, _parent->filter().size() - 1)), second = (_parent->filter().size() < 2) ? ('#' + hrow) : hrow.mid(_parent->filter().size() - 1); - int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second); - if (htagwidth < firstwidth + secondwidth) { - if (htagwidth < firstwidth + st::mentionFont->elidew) { - first = st::mentionFont->elided(first + second, htagwidth); - second = QString(); - } else { - second = st::mentionFont->elided(second, htagwidth - firstwidth); - } - } - - p.setFont(st::mentionFont->f); - if (!first.isEmpty()) { - p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p); - p.drawText(htagleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first); - } - if (!second.isEmpty()) { - p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p); - p.drawText(htagleft + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second); - } - } else { - UserData *user = _crows->at(i).first; - - const BotCommand *command = _crows->at(i).second; - QString toHighlight = command->command; - int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1); - if (hasUsername || botStatus == 0 || botStatus == 2) { - toHighlight += '@' + user->username; - } - if (true || _parent->chat() || botStatus == 0 || botStatus == 2) { user->photo->load(); p.drawPixmap(st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), user->photo->pixRounded(st::mentionPhotoSize)); - } + user->nameText.drawElided(p, 2 * st::mentionPadding.left() + st::mentionPhotoSize, i * st::mentionHeight + st::mentionTop, namewidth); - int32 addleft = 0, widthleft = mentionwidth; - QString first = (_parent->filter().size() < 2) ? QString() : ('/' + toHighlight.mid(0, _parent->filter().size() - 1)), second = (_parent->filter().size() < 2) ? ('/' + toHighlight) : toHighlight.mid(_parent->filter().size() - 1); - int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second); - if (widthleft < firstwidth + secondwidth) { - if (widthleft < firstwidth + st::mentionFont->elidew) { - first = st::mentionFont->elided(first + second, widthleft); - second = QString(); - } else { - second = st::mentionFont->elided(second, widthleft - firstwidth); + p.setFont(st::mentionFont->f); + p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p); + p.drawText(mentionleft + namewidth + st::mentionPadding.right(), i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first); + if (!second.isEmpty()) { + p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p); + p.drawText(mentionleft + namewidth + st::mentionPadding.right() + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second); + } + } else if (!_hrows->isEmpty()) { + QString hrow = _hrows->at(i); + QString first = (_parent->filter().size() < 2) ? QString() : ('#' + hrow.mid(0, _parent->filter().size() - 1)), second = (_parent->filter().size() < 2) ? ('#' + hrow) : hrow.mid(_parent->filter().size() - 1); + int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second); + if (htagwidth < firstwidth + secondwidth) { + if (htagwidth < firstwidth + st::mentionFont->elidew) { + first = st::mentionFont->elided(first + second, htagwidth); + second = QString(); + } else { + second = st::mentionFont->elided(second, htagwidth - firstwidth); + } + } + + p.setFont(st::mentionFont->f); + if (!first.isEmpty()) { + p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p); + p.drawText(htagleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first); + } + if (!second.isEmpty()) { + p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p); + p.drawText(htagleft + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second); + } + } else { + UserData *user = _brows->at(i).first; + + const BotCommand *command = _brows->at(i).second; + QString toHighlight = command->command; + int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1); + if (hasUsername || botStatus == 0 || botStatus == 2) { + toHighlight += '@' + user->username; + } + if (true || _parent->chat() || botStatus == 0 || botStatus == 2) { + user->photo->load(); + p.drawPixmap(st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), user->photo->pixRounded(st::mentionPhotoSize)); + } + + int32 addleft = 0, widthleft = mentionwidth; + QString first = (_parent->filter().size() < 2) ? QString() : ('/' + toHighlight.mid(0, _parent->filter().size() - 1)), second = (_parent->filter().size() < 2) ? ('/' + toHighlight) : toHighlight.mid(_parent->filter().size() - 1); + int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second); + if (widthleft < firstwidth + secondwidth) { + if (widthleft < firstwidth + st::mentionFont->elidew) { + first = st::mentionFont->elided(first + second, widthleft); + second = QString(); + } else { + second = st::mentionFont->elided(second, widthleft - firstwidth); + } + } + p.setFont(st::mentionFont->f); + if (!first.isEmpty()) { + p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p); + p.drawText(mentionleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first); + } + if (!second.isEmpty()) { + p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p); + p.drawText(mentionleft + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second); + } + addleft += firstwidth + secondwidth + st::mentionPadding.left(); + widthleft -= firstwidth + secondwidth + st::mentionPadding.left(); + if (widthleft > st::mentionFont->elidew && !command->descriptionText().isEmpty()) { + p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p); + command->descriptionText().drawElided(p, mentionleft + addleft, i * st::mentionHeight + st::mentionTop, widthleft, 1, style::al_right); } } - p.setFont(st::mentionFont->f); - if (!first.isEmpty()) { - p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p); - p.drawText(mentionleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first); - } - if (!second.isEmpty()) { - p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p); - p.drawText(mentionleft + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second); - } - addleft += firstwidth + secondwidth + st::mentionPadding.left(); - widthleft -= firstwidth + secondwidth + st::mentionPadding.left(); - if (widthleft > st::mentionFont->elidew && !command->descriptionText().isEmpty()) { - p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p); - command->descriptionText().drawElided(p, mentionleft + addleft, i * st::mentionHeight + st::mentionTop, widthleft, 1, style::al_right); - } } + p.fillRect(cWideMode() ? st::lineWidth : 0, _parent->innerBottom() - st::lineWidth, width() - (cWideMode() ? st::lineWidth : 0), st::lineWidth, st::shadowColor->b); } - p.fillRect(cWideMode() ? st::lineWidth : 0, _parent->innerTop(), width() - (cWideMode() ? st::lineWidth : 0), st::lineWidth, st::shadowColor->b); - p.fillRect(cWideMode() ? st::lineWidth : 0, _parent->innerBottom() - st::lineWidth, width() - (cWideMode() ? st::lineWidth : 0), st::lineWidth, st::shadowColor->b); +} + +void MentionsInner::resizeEvent(QResizeEvent *e) { + _stickersPerRow = qMax(1, int32(width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width())); } void MentionsInner::mouseMoveEvent(QMouseEvent *e) { @@ -2843,44 +4030,66 @@ void MentionsInner::mouseMoveEvent(QMouseEvent *e) { void MentionsInner::clearSel() { _mouseSel = _overDelete = false; - setSel((_rows->isEmpty() && _crows->isEmpty() && _hrows->isEmpty()) ? -1 : 0); + setSel((_mrows->isEmpty() && _brows->isEmpty() && _hrows->isEmpty()) ? -1 : 0); } -bool MentionsInner::moveSel(int direction) { +bool MentionsInner::moveSel(int key) { _mouseSel = false; - int32 maxSel = (_rows->isEmpty() ? (_hrows->isEmpty() ? _crows->size() : _hrows->size()) : _rows->size()); + int32 maxSel = (_mrows->isEmpty() ? (_hrows->isEmpty() ? (_brows->isEmpty() ? _srows->size() : _brows->size()) : _hrows->size()) : _mrows->size()); + int32 direction = (key == Qt::Key_Up) ? -1 : (key == Qt::Key_Down ? 1 : 0); + if (!_srows->isEmpty()) { + if (key == Qt::Key_Left) { + direction = -1; + } else if (key == Qt::Key_Right) { + direction = 1; + } else { + direction *= _stickersPerRow; + } + } if (_sel >= maxSel || _sel < 0) { - if (direction < 0) { + if (direction < -1) { + setSel(((maxSel - 1) / _stickersPerRow) * _stickersPerRow, true); + } else if (direction < 0) { setSel(maxSel - 1, true); } else { setSel(0, true); } return (_sel >= 0 && _sel < maxSel); } - setSel((_sel + direction >= maxSel) ? -1 : (_sel + direction), true); + setSel((_sel + direction >= maxSel || _sel + direction < 0) ? -1 : (_sel + direction), true); return true; } bool MentionsInner::select() { - QString sel = getSelected(); - if (!sel.isEmpty()) { - emit chosen(sel); - return true; + if (!_srows->isEmpty()) { + if (_sel >= 0 && _sel < _srows->size()) { + emit selected(_srows->at(_sel)); + } + } else { + QString sel = getSelected(); + if (!sel.isEmpty()) { + emit chosen(sel); + return true; + } } return false; } +void MentionsInner::setRecentInlineBotsInRows(int32 bots) { + _recentInlineBotsInRows = bots; +} + QString MentionsInner::getSelected() const { - int32 maxSel = (_rows->isEmpty() ? (_hrows->isEmpty() ? _crows->size() : _hrows->size()) : _rows->size()); + int32 maxSel = (_mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size()); if (_sel >= 0 && _sel < maxSel) { QString result; - if (!_rows->isEmpty()) { - result = '@' + _rows->at(_sel)->username; + if (!_mrows->isEmpty()) { + result = '@' + _mrows->at(_sel)->username; } else if (!_hrows->isEmpty()) { result = '#' + _hrows->at(_sel); } else { - UserData *user = _crows->at(_sel).first; - const BotCommand *command(_crows->at(_sel).second); + UserData *user = _brows->at(_sel).first; + const BotCommand *command(_brows->at(_sel).second); int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1); if (botStatus == 0 || botStatus == 2 || _parent->filter().indexOf('@') > 1) { result = '/' + command->command + '@' + user->username; @@ -2898,20 +4107,32 @@ void MentionsInner::mousePressEvent(QMouseEvent *e) { _mouseSel = true; onUpdateSelected(true); if (e->button() == Qt::LeftButton) { - if (_overDelete && _sel >= 0 && _sel < _hrows->size()) { + if (_overDelete && _sel >= 0 && _sel < (_mrows->isEmpty() ? _hrows->size() : _recentInlineBotsInRows)) { _mousePos = mapToGlobal(e->pos()); - - QString toRemove = _hrows->at(_sel); - RecentHashtagPack recent(cRecentWriteHashtags()); - for (RecentHashtagPack::iterator i = recent.begin(); i != recent.cend();) { - if (i->first == toRemove) { - i = recent.erase(i); - } else { - ++i; + bool removed = false; + if (_mrows->isEmpty()) { + QString toRemove = _hrows->at(_sel); + RecentHashtagPack &recent(cRefRecentWriteHashtags()); + for (RecentHashtagPack::iterator i = recent.begin(); i != recent.cend();) { + if (i->first == toRemove) { + i = recent.erase(i); + removed = true; + } else { + ++i; + } + } + } else { + UserData *toRemove = _mrows->at(_sel); + RecentInlineBots &recent(cRefRecentInlineBots()); + int32 index = recent.indexOf(toRemove); + if (index >= 0) { + recent.remove(index); + removed = true; } } - cSetRecentWriteHashtags(recent); - Local::writeRecentHashtags(); + if (removed) { + Local::writeRecentHashtagsAndBots(); + } _parent->updateFiltered(); _mouseSel = true; @@ -2935,21 +4156,51 @@ void MentionsInner::leaveEvent(QEvent *e) { } } +void MentionsInner::updateSelectedRow() { + if (_sel >= 0) { + if (_srows->isEmpty()) { + update(0, _sel * st::mentionHeight, width(), st::mentionHeight); + } else { + int32 row = _sel / _stickersPerRow, col = _sel % _stickersPerRow; + update(st::stickerPanPadding + col * st::stickerPanSize.width(), st::stickerPanPadding + row * st::stickerPanSize.height(), st::stickerPanSize.width(), st::stickerPanSize.height()); + } + } +} + void MentionsInner::setSel(int sel, bool scroll) { - if (_sel >= 0) update(0, _sel * st::mentionHeight, width(), st::mentionHeight); + updateSelectedRow(); _sel = sel; - if (_sel >= 0) update(0, _sel * st::mentionHeight, width(), st::mentionHeight); - int32 maxSel = _rows->isEmpty() ? (_hrows->isEmpty() ? _crows->size() : _hrows->size()) : _rows->size(); - if (scroll && _sel >= 0 && _sel < maxSel) emit mustScrollTo(_sel * st::mentionHeight, (_sel + 1) * st::mentionHeight); + updateSelectedRow(); + + if (scroll && _sel >= 0) { + if (_srows->isEmpty()) { + emit mustScrollTo(_sel * st::mentionHeight, (_sel + 1) * st::mentionHeight); + } else { + int32 row = _sel / _stickersPerRow; + emit mustScrollTo(st::stickerPanPadding + row * st::stickerPanSize.height(), st::stickerPanPadding + (row + 1) * st::stickerPanSize.height()); + } + } } void MentionsInner::onUpdateSelected(bool force) { QPoint mouse(mapFromGlobal(_mousePos)); if ((!force && !rect().contains(mouse)) || !_mouseSel) return; - int w = width(), mouseY = mouse.y(); - _overDelete = _rows->isEmpty() && (mouse.x() >= w - st::mentionHeight); - int32 sel = mouseY / int32(st::mentionHeight), maxSel = _rows->isEmpty() ? (_hrows->isEmpty() ? _crows->size() : _hrows->size()) : _rows->size(); + int32 sel = -1, maxSel = 0; + if (!_srows->isEmpty()) { + int32 rows = rowscount(_srows->size(), _stickersPerRow); + int32 row = (mouse.y() >= st::stickerPanPadding) ? ((mouse.y() - st::stickerPanPadding) / st::stickerPanSize.height()) : -1; + int32 col = (mouse.x() >= st::stickerPanPadding) ? ((mouse.x() - st::stickerPanPadding) / st::stickerPanSize.width()) : -1; + if (row >= 0 && col >= 0) { + sel = row * _stickersPerRow + col; + } + maxSel = _srows->size(); + _overDelete = false; + } else { + sel = mouse.y() / int32(st::mentionHeight); + maxSel = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size(); + _overDelete = (!_hrows->isEmpty() || (!_mrows->isEmpty() && sel < _recentInlineBotsInRows)) ? (mouse.x() >= width() - st::mentionHeight) : false; + } if (sel < 0 || sel >= maxSel) { sel = -1; } @@ -2966,11 +4217,20 @@ void MentionsInner::onParentGeometryChanged() { } } -MentionsDropdown::MentionsDropdown(QWidget *parent) : TWidget(parent), -_scroll(this, st::mentionScroll), _inner(this, &_rows, &_hrows, &_crows), _chat(0), _user(0), _channel(0), _hiding(false), a_opacity(0), _shadow(st::dropdownDef.shadow) { +MentionsDropdown::MentionsDropdown(QWidget *parent) : TWidget(parent) +, _scroll(this, st::mentionScroll) +, _inner(this, &_mrows, &_hrows, &_brows, &_srows) +, _chat(0) +, _user(0) +, _channel(0) +, _hiding(false) +, a_opacity(0) +, _a_appearance(animation(this, &MentionsDropdown::step_appearance)) +, _shadow(st::dropdownDef.shadow) { _hideTimer.setSingleShot(true); connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideStart())); connect(&_inner, SIGNAL(chosen(QString)), this, SIGNAL(chosen(QString))); + connect(&_inner, SIGNAL(selected(DocumentData*)), this, SIGNAL(stickerSelected(DocumentData*))); connect(&_inner, SIGNAL(mustScrollTo(int,int)), &_scroll, SLOT(scrollToY(int,int))); connect(App::wnd(), SIGNAL(imageLoaded()), &_inner, SLOT(update())); @@ -2988,88 +4248,152 @@ _scroll(this, st::mentionScroll), _inner(this, &_rows, &_hrows, &_crows), _chat( connect(&_scroll, SIGNAL(geometryChanged()), &_inner, SLOT(onParentGeometryChanged())); connect(&_scroll, SIGNAL(scrolled()), &_inner, SLOT(onUpdateSelected())); - - if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { - connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged())); - } } void MentionsDropdown::paintEvent(QPaintEvent *e) { - QPainter p(this); + Painter p(this); - if (animating()) { + if (_a_appearance.animating()) { p.setOpacity(a_opacity.current()); p.drawPixmap(0, 0, _cache); return; } - p.fillRect(rect(), st::white->b); - + p.fillRect(rect(), st::white); } -void MentionsDropdown::showFiltered(PeerData *peer, QString start) { +void MentionsDropdown::showFiltered(PeerData *peer, QString query, bool start) { _chat = peer->asChat(); _user = peer->asUser(); _channel = peer->asChannel(); - start = start.toLower(); - bool toDown = (_filter != start); - if (toDown) { - _filter = start; + if (query.isEmpty()) { + rowsUpdated(MentionRows(), HashtagRows(), BotCommandRows(), _srows, false); + return; } - updateFiltered(toDown); + _emoji = EmojiPtr(); + + query = query.toLower(); + bool resetScroll = (_filter != query); + if (resetScroll) { + _filter = query; + } + _addInlineBots = start; + + updateFiltered(resetScroll); } -bool MentionsDropdown::clearFilteredCommands() { - if (_crows.isEmpty()) return false; - _crows.clear(); +void MentionsDropdown::showStickers(EmojiPtr emoji) { + bool resetScroll = (_emoji != emoji); + _emoji = emoji; + if (!emoji) { + rowsUpdated(_mrows, _hrows, _brows, StickerPack(), false); + return; + } + + _chat = 0; + _user = 0; + _channel = 0; + + updateFiltered(resetScroll); +} + +bool MentionsDropdown::clearFilteredBotCommands() { + if (_brows.isEmpty()) return false; + _brows.clear(); return true; } -void MentionsDropdown::updateFiltered(bool toDown) { - int32 now = unixtime(); - MentionRows rows; +void MentionsDropdown::updateFiltered(bool resetScroll) { + int32 now = unixtime(), recentInlineBots = 0; + MentionRows mrows; HashtagRows hrows; - BotCommandRows crows; - if (_filter.at(0) == '@' && _chat) { - QMultiMap ordered; - rows.reserve(_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size()); - if (_chat->noParticipantInfo()) { - if (App::api()) App::api()->requestFullPeer(_chat); - } else if (!_chat->participants.isEmpty()) { - for (ChatData::Participants::const_iterator i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) { - UserData *user = i.key(); - if (user->username.isEmpty()) continue; - if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue; - ordered.insertMulti(App::onlineForSort(user, now), user); + BotCommandRows brows; + StickerPack srows; + if (_emoji) { + QMap setsToRequest; + StickerSets &sets(cRefStickerSets()); + const StickerSetsOrder &order(cStickerSetsOrder()); + for (int32 i = 0, l = order.size(); i < l; ++i) { + StickerSets::iterator it = sets.find(order.at(i)); + if (it != sets.cend()) { + if (it->emoji.isEmpty()) { + setsToRequest.insert(it->id, it->access); + it->flags |= MTPDstickerSet_flag_NOT_LOADED; + } else { + StickersByEmojiMap::const_iterator i = it->emoji.constFind(emojiGetNoColor(_emoji)); + if (i != it->emoji.cend()) { + srows += *i; + } + } } } - for (MentionRows::const_iterator i = _chat->lastAuthors.cbegin(), e = _chat->lastAuthors.cend(); i != e; ++i) { - UserData *user = *i; - if (user->username.isEmpty()) continue; - if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue; - rows.push_back(user); - if (!ordered.isEmpty()) { - ordered.remove(App::onlineForSort(user, now), user); + if (!setsToRequest.isEmpty() && App::api()) { + for (QMap::const_iterator i = setsToRequest.cbegin(), e = setsToRequest.cend(); i != e; ++i) { + App::api()->scheduleStickerSetRequest(i.key(), i.value()); } + App::api()->requestStickerSets(); } - if (!ordered.isEmpty()) { - for (QMultiMap::const_iterator i = ordered.cend(), b = ordered.cbegin(); i != b;) { - --i; - rows.push_back(i.value()); + } else if (_filter.at(0) == '@') { + if (_chat) { + mrows.reserve((_addInlineBots ? cRecentInlineBots().size() : 0) + (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size())); + } else if (_channel && _channel->isMegagroup()) { + if (_channel->mgInfo->lastParticipants.isEmpty() || _channel->lastParticipantsCountOutdated()) { + } else { + mrows.reserve((_addInlineBots ? cRecentInlineBots().size() : 0) + _channel->mgInfo->lastParticipants.size()); } + } else if (_addInlineBots) { + mrows.reserve(cRecentInlineBots().size()); } - } else if (_filter.at(0) == '@' && _channel && _channel->isMegagroup()) { - QMultiMap ordered; - if (_channel->mgInfo->lastParticipants.isEmpty() || _channel->lastParticipantsCountOutdated()) { - if (App::api()) App::api()->requestLastParticipants(_channel); - } else { - rows.reserve(_channel->mgInfo->lastParticipants.size()); - for (MegagroupInfo::LastParticipants::const_iterator i = _channel->mgInfo->lastParticipants.cbegin(), e = _channel->mgInfo->lastParticipants.cend(); i != e; ++i) { + if (_addInlineBots) { + for (RecentInlineBots::const_iterator i = cRecentInlineBots().cbegin(), e = cRecentInlineBots().cend(); i != e; ++i) { UserData *user = *i; if (user->username.isEmpty()) continue; if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue; - rows.push_back(user); + mrows.push_back(user); + ++recentInlineBots; + } + } + if (_chat) { + QMultiMap ordered; + mrows.reserve(mrows.size() + (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size())); + if (_chat->noParticipantInfo()) { + if (App::api()) App::api()->requestFullPeer(_chat); + } else if (!_chat->participants.isEmpty()) { + for (ChatData::Participants::const_iterator i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) { + UserData *user = i.key(); + if (user->username.isEmpty()) continue; + if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue; + ordered.insertMulti(App::onlineForSort(user, now), user); + } + } + for (MentionRows::const_iterator i = _chat->lastAuthors.cbegin(), e = _chat->lastAuthors.cend(); i != e; ++i) { + UserData *user = *i; + if (user->username.isEmpty()) continue; + if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue; + mrows.push_back(user); + if (!ordered.isEmpty()) { + ordered.remove(App::onlineForSort(user, now), user); + } + } + if (!ordered.isEmpty()) { + for (QMultiMap::const_iterator i = ordered.cend(), b = ordered.cbegin(); i != b;) { + --i; + mrows.push_back(i.value()); + } + } + } else if (_channel && _channel->isMegagroup()) { + QMultiMap ordered; + if (_channel->mgInfo->lastParticipants.isEmpty() || _channel->lastParticipantsCountOutdated()) { + if (App::api()) App::api()->requestLastParticipants(_channel); + } else { + mrows.reserve(mrows.size() + _channel->mgInfo->lastParticipants.size()); + for (MegagroupInfo::LastParticipants::const_iterator i = _channel->mgInfo->lastParticipants.cbegin(), e = _channel->mgInfo->lastParticipants.cend(); i != e; ++i) { + UserData *user = *i; + if (user->username.isEmpty()) continue; + if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue; + mrows.push_back(user); + } } } } else if (_filter.at(0) == '#') { @@ -3115,7 +4439,7 @@ void MentionsDropdown::updateFiltered(bool toDown) { } } if (cnt) { - crows.reserve(cnt); + brows.reserve(cnt); int32 botStatus = _chat ? _chat->botStatus : ((_channel && _channel->isMegagroup()) ? _channel->mgInfo->botStatus : -1); if (_chat) { for (MentionRows::const_iterator i = _chat->lastAuthors.cbegin(), e = _chat->lastAuthors.cend(); i != e; ++i) { @@ -3130,7 +4454,7 @@ void MentionsDropdown::updateFiltered(bool toDown) { QString toFilter = (hasUsername || botStatus == 0 || botStatus == 2) ? user->botInfo->commands.at(j).command + '@' + user->username : user->botInfo->commands.at(j).command; if (!toFilter.startsWith(_filter.midRef(1), Qt::CaseInsensitive)/* || toFilter.size() + 1 == _filter.size()*/) continue; } - crows.push_back(qMakePair(user, &user->botInfo->commands.at(j))); + brows.push_back(qMakePair(user, &user->botInfo->commands.at(j))); } } } @@ -3142,29 +4466,37 @@ void MentionsDropdown::updateFiltered(bool toDown) { QString toFilter = (hasUsername || botStatus == 0 || botStatus == 2) ? user->botInfo->commands.at(j).command + '@' + user->username : user->botInfo->commands.at(j).command; if (!toFilter.startsWith(_filter.midRef(1), Qt::CaseInsensitive)/* || toFilter.size() + 1 == _filter.size()*/) continue; } - crows.push_back(qMakePair(user, &user->botInfo->commands.at(j))); + brows.push_back(qMakePair(user, &user->botInfo->commands.at(j))); } } } } } - if (rows.isEmpty() && hrows.isEmpty() && crows.isEmpty()) { + rowsUpdated(mrows, hrows, brows, srows, resetScroll); + _inner.setRecentInlineBotsInRows(recentInlineBots); +} + +void MentionsDropdown::rowsUpdated(const MentionRows &mrows, const HashtagRows &hrows, const BotCommandRows &brows, const StickerPack &srows, bool resetScroll) { + if (mrows.isEmpty() && hrows.isEmpty() && brows.isEmpty() && srows.isEmpty()) { if (!isHidden()) { hideStart(); } - _rows.clear(); + _mrows.clear(); _hrows.clear(); - _crows.clear(); + _brows.clear(); + _srows.clear(); } else { - _rows = rows; + _mrows = mrows; _hrows = hrows; - _crows = crows; + _brows = brows; + _srows = srows; + bool hidden = _hiding || isHidden(); if (hidden) { show(); _scroll.show(); } - recount(toDown); + recount(resetScroll); if (hidden) { hide(); showStart(); @@ -3174,36 +4506,42 @@ void MentionsDropdown::updateFiltered(bool toDown) { void MentionsDropdown::setBoundings(QRect boundings) { _boundings = boundings; - resize(_boundings.width(), height()); - _scroll.resize(size()); - _inner.resize(width(), _inner.height()); recount(); } -void MentionsDropdown::recount(bool toDown) { - int32 h = (_rows.isEmpty() ? (_hrows.isEmpty() ? _crows.size() : _hrows.size()) : _rows.size()) * st::mentionHeight, oldst = _scroll.scrollTop(), st = oldst; - - if (_inner.height() != h) { -// st += h - _inner.height(); - _inner.resize(width(), h); +void MentionsDropdown::recount(bool resetScroll) { + int32 h = 0, oldst = _scroll.scrollTop(), st = oldst, maxh = 4.5 * st::mentionHeight; + if (!_srows.isEmpty()) { + int32 stickersPerRow = qMax(1, int32(_boundings.width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width())); + int32 rows = rowscount(_srows.size(), stickersPerRow); + h = st::stickerPanPadding + rows * st::stickerPanSize.height(); + } else if (!_mrows.isEmpty()) { + h = _mrows.size() * st::mentionHeight; + } else if (!_hrows.isEmpty()) { + h = _hrows.size() * st::mentionHeight; + } else if (!_brows.isEmpty()) { + h = _brows.size() * st::mentionHeight; + } + + if (_inner.width() != _boundings.width() || _inner.height() != h) { + _inner.resize(_boundings.width(), h); } if (h > _boundings.height()) h = _boundings.height(); - if (h > 4.5 * st::mentionHeight) h = 4.5 * st::mentionHeight; - if (height() != h) { -// st += _scroll.height() - h; - setGeometry(0, _boundings.height() - h, width(), h); - _scroll.resize(width(), h); + if (h > maxh) h = maxh; + if (width() != _boundings.width() || height() != h) { + setGeometry(0, _boundings.height() - h, _boundings.width(), h); + _scroll.resize(_boundings.width(), h); } else if (y() != _boundings.height() - h) { move(0, _boundings.height() - h); } - if (toDown) st = 0;// _scroll.scrollTopMax(); + if (resetScroll) st = 0; if (st != oldst) _scroll.scrollToY(st); - if (toDown) _inner.clearSel(); + if (resetScroll) _inner.clearSel(); } void MentionsDropdown::fastHide() { - if (animating()) { - anim::stop(this); + if (_a_appearance.animating()) { + _a_appearance.stop(); } a_opacity = anim::fvalue(0, 0); _hideTimer.stop(); @@ -3220,7 +4558,7 @@ void MentionsDropdown::hideStart() { _hiding = true; a_opacity.start(0); setAttribute(Qt::WA_OpaquePaintEvent, false); - anim::start(this); + _a_appearance.start(); } } @@ -3244,13 +4582,13 @@ void MentionsDropdown::showStart() { show(); a_opacity.start(1); setAttribute(Qt::WA_OpaquePaintEvent, false); - anim::start(this); + _a_appearance.start(); } -bool MentionsDropdown::animStep(float64 ms) { +void MentionsDropdown::step_appearance(float64 ms, bool timer) { float64 dt = ms / st::dropdownDef.duration; - bool res = true; if (dt >= 1) { + _a_appearance.stop(); a_opacity.finish(); _cache = QPixmap(); setAttribute(Qt::WA_OpaquePaintEvent); @@ -3260,12 +4598,10 @@ bool MentionsDropdown::animStep(float64 ms) { _scroll.show(); _inner.clearSel(); } - res = false; } else { a_opacity.update(dt, anim::linear); } - update(); - return res; + if (timer) update(); } const QString &MentionsDropdown::filter() const { @@ -3300,11 +4636,8 @@ bool MentionsDropdown::eventFilter(QObject *obj, QEvent *e) { if (isHidden()) return QWidget::eventFilter(obj, e); if (e->type() == QEvent::KeyPress) { QKeyEvent *ev = static_cast(e); - if (ev->key() == Qt::Key_Up) { - _inner.moveSel(-1); - return true; - } else if (ev->key() == Qt::Key_Down) { - return _inner.moveSel(1); + if (ev->key() == Qt::Key_Up || ev->key() == Qt::Key_Down || (!_srows.isEmpty() && (ev->key() == Qt::Key_Left || ev->key() == Qt::Key_Right))) { + return _inner.moveSel(ev->key()); } else if (ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return) { return _inner.select(); } diff --git a/Telegram/SourceFiles/dropdown.h b/Telegram/SourceFiles/dropdown.h index 9a64fce257..36146697b4 100644 --- a/Telegram/SourceFiles/dropdown.h +++ b/Telegram/SourceFiles/dropdown.h @@ -23,7 +23,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "gui/twidget.h" #include "gui/boxshadow.h" -class Dropdown : public TWidget, public Animated { +class Dropdown : public TWidget { Q_OBJECT public: @@ -46,12 +46,12 @@ public: void fastHide(); void ignoreShow(bool ignore = true); - bool animStep(float64 ms); + void step_appearance(float64 ms, bool timer); bool eventFilter(QObject *obj, QEvent *e); bool overlaps(const QRect &globalRect) { - if (isHidden() || animating()) return false; + if (isHidden() || _a_appearance.animating()) return false; return QRect(_st.padding.left(), _st.padding.top(), @@ -91,6 +91,7 @@ private: bool _hiding; anim::fvalue a_opacity; + Animation _a_appearance; QTimer _hideTimer; @@ -98,7 +99,7 @@ private: }; -class DragArea : public TWidget, public Animated { +class DragArea : public TWidget { Q_OBJECT public: @@ -119,10 +120,10 @@ public: void fastHide(); - bool animStep(float64 ms); + void step_appearance(float64 ms, bool timer); bool overlaps(const QRect &globalRect) { - if (isHidden() || animating()) return false; + if (isHidden() || _a_appearance.animating()) return false; return QRect(st::dragPadding.left(), st::dragPadding.top(), @@ -148,6 +149,7 @@ private: anim::fvalue a_opacity; anim::cvalue a_color; + Animation _a_appearance; BoxShadow _shadow; @@ -158,7 +160,7 @@ private: class EmojiPanel; static const int EmojiColorsCount = 5; -class EmojiColorPicker : public TWidget, public Animated { +class EmojiColorPicker : public TWidget { Q_OBJECT public: @@ -174,7 +176,8 @@ public: void mouseReleaseEvent(QMouseEvent *e); void mouseMoveEvent(QMouseEvent *e); - bool animStep(float64 ms); + void step_appearance(float64 ms, bool timer); + void step_selected(uint64 ms, bool timer); void showStart(); void clearSelection(bool fast = false); @@ -200,6 +203,7 @@ private: typedef QMap EmojiAnimations; // index - showing, -index - hiding EmojiAnimations _emojiAnimations; + Animation _a_selected; float64 _hovers[EmojiColorsCount + 1]; @@ -210,6 +214,7 @@ private: QPixmap _cache; anim::fvalue a_opacity; + Animation _a_appearance; QTimer _hideTimer; @@ -217,7 +222,7 @@ private: }; -class EmojiPanInner : public TWidget, public Animated { +class EmojiPanInner : public TWidget { Q_OBJECT public: @@ -234,7 +239,7 @@ public: void leaveToChildEvent(QEvent *e); void enterFromChildEvent(QEvent *e); - bool animStep(float64 ms); + void step_selected(uint64 ms, bool timer); void hideFinish(); void showEmojiPack(DBIEmojiTab packIndex); @@ -249,11 +254,10 @@ public: void fillPanels(QVector &panels); void refreshPanels(QVector &panels); - + public slots: void updateSelected(); - void onSaveConfig(); void onShowPicker(); void onPickerHidden(); @@ -271,6 +275,7 @@ signals: void disableScroll(bool dis); void needRefreshPanels(); + void saveConfigDelayed(int32 delay); private: @@ -283,6 +288,7 @@ private: typedef QMap Animations; // index - showing, -index - hiding Animations _animations; + Animation _a_selected; int32 _top, _counts[emojiTabCount]; @@ -294,14 +300,12 @@ private: int32 _selected, _pressedSel, _pickerSel; QPoint _lastMousePos; - QTimer _saveConfigTimer; - EmojiColorPicker _picker; QTimer _showPickerTimer; }; struct StickerIcon { - StickerIcon() : setId(RecentStickerSetId), sticker(0), pixw(0), pixh(0) { + StickerIcon(uint64 setId) : setId(setId), sticker(0), pixw(0), pixh(0) { } StickerIcon(uint64 setId, DocumentData *sticker, int32 pixw, int32 pixh) : setId(setId), sticker(sticker), pixw(pixw), pixh(pixh) { } @@ -310,7 +314,7 @@ struct StickerIcon { int32 pixw, pixh; }; -class StickerPanInner : public TWidget, public Animated { +class StickerPanInner : public TWidget { Q_OBJECT public: @@ -327,16 +331,26 @@ public: void leaveToChildEvent(QEvent *e); void enterFromChildEvent(QEvent *e); - bool animStep(float64 ms); + void step_selected(uint64 ms, bool timer); + void hideFinish(bool completely); + void showFinish(); void showStickerSet(uint64 setId); + void updateShowingSavedGifs(); + bool showSectionIcons() const; void clearSelection(bool fast = false); void refreshStickers(); - void refreshRecent(bool resize = true); + void refreshRecentStickers(bool resize = true); + void refreshSavedGifs(); + int32 refreshInlineRows(UserData *bot, const InlineResults &results, bool resultsDeleted); + void refreshRecent(); + void inlineBotChanged(); + void hideInlineRowsPanel(); + void clearInlineRowsPanel(); - void fillIcons(QVector &icons); + void fillIcons(QList &icons); void fillPanels(QVector &panels); void refreshPanels(QVector &panels); @@ -345,37 +359,63 @@ public: uint64 currentSet(int yOffset) const; + void ui_repaintInlineItem(const LayoutInlineItem *layout); + bool ui_isInlineItemVisible(const LayoutInlineItem *layout); + bool ui_isInlineItemBeingChosen(); + + bool inlineResultsShown() const { + return _showingInlineItems && !_showingSavedGifs; + } + int32 countHeight(bool plain = false); + + ~StickerPanInner() { + clearInlineRows(true); + deleteUnusedGifLayouts(); + deleteUnusedInlineLayouts(); + } + public slots: void updateSelected(); void onSettings(); void onPreview(); + void onUpdateInlineItems(); signals: void selected(DocumentData *sticker); + void selected(PhotoData *photo); + void selected(InlineResult *result, UserData *bot); + void removing(quint64 setId); void refreshIcons(); + void emptyInlineRows(); void switchToEmoji(); void scrollToY(int y); + void scrollUpdated(); void disableScroll(bool dis); void needRefreshPanels(); + void saveConfigDelayed(int32 delay); + private: + void paintInlineItems(Painter &p, const QRect &r); + void paintStickers(Painter &p, const QRect &r); + int32 _maxHeight; void appendSet(uint64 setId); - int32 countHeight(); void selectEmoji(EmojiPtr emoji); QRect stickerRect(int tab, int sel); typedef QMap Animations; // index - showing, -index - hiding Animations _animations; + Animation _a_selected; int32 _top; @@ -391,8 +431,45 @@ private: QList _sets; QList _custom; + bool _showingSavedGifs, _showingInlineItems; + bool _setGifCommand; + UserData *_inlineBot; + QString _inlineBotTitle; + uint64 _lastScrolled; + QTimer _updateInlineItems; + bool _inlineWithThumb; + + typedef QVector InlineItems; + struct InlineRow { + InlineRow() : height(0) { + } + int32 height; + InlineItems items; + }; + typedef QVector InlineRows; + InlineRows _inlineRows; + void clearInlineRows(bool resultsDeleted); + + typedef QMap GifLayouts; + GifLayouts _gifLayouts; + LayoutInlineGif *layoutPrepareSavedGif(DocumentData *doc, int32 position); + + typedef QMap InlineLayouts; + InlineLayouts _inlineLayouts; + LayoutInlineItem *layoutPrepareInlineResult(InlineResult *result, int32 position); + + bool inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, InlineRow &row, int32 &sumWidth); + bool inlineRowFinalize(InlineRow &row, int32 &sumWidth, bool force = false); + + InlineRow &layoutInlineRow(InlineRow &row, int32 sumWidth = 0); + void deleteUnusedGifLayouts(); + + void deleteUnusedInlineLayouts(); + + int32 validateExistingInlineRows(const InlineResults &results); int32 _selected, _pressedSel; QPoint _lastMousePos; + TextLinkPtr _linkOver, _linkDown; LinkButton _settings; @@ -445,6 +522,7 @@ public: EmojiSwitchButton(QWidget *parent, bool toStickers); // otherwise toEmoji void paintEvent(QPaintEvent *e); + void updateText(const QString &inlineBotUsername = QString()); protected: @@ -454,7 +532,7 @@ protected: }; -class EmojiPan : public TWidget, public Animated { +class EmojiPan : public TWidget, public RPCSender { Q_OBJECT public: @@ -464,6 +542,8 @@ public: void setMaxHeight(int32 h); void paintEvent(QPaintEvent *e); + void moveBottom(int32 bottom, bool force = false); + void enterEvent(QEvent *e); void leaveEvent(QEvent *e); void otherEnter(); @@ -480,13 +560,16 @@ public: return _hiding || _hideTimer.isActive(); } - bool animStep(float64 ms); - - bool iconAnim(float64 ms); + void step_appearance(float64 ms, bool timer); + void step_slide(float64 ms, bool timer); + void step_icons(uint64 ms, bool timer); bool eventFilter(QObject *obj, QEvent *e); void stickersInstalled(uint64 setId); + void queryInlineBot(UserData *bot, QString query); + void clearInlineBot(); + bool overlaps(const QRect &globalRect) { if (isHidden() || !_cache.isNull()) return false; @@ -497,9 +580,19 @@ public: ).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); } + void ui_repaintInlineItem(const LayoutInlineItem *layout); + bool ui_isInlineItemVisible(const LayoutInlineItem *layout); + bool ui_isInlineItemBeingChosen(); + + bool inlineResultsShown() const { + return s_inner.inlineResultsShown(); + } + void notify_automaticLoadSettingsChangedGif(); + public slots: void refreshStickers(); + void refreshSavedGifs(); void hideStart(); void hideFinish(); @@ -518,18 +611,32 @@ public slots: void onRefreshIcons(); void onRefreshPanels(); + void onSaveConfig(); + void onSaveConfigDelayed(int32 delay); + + void onInlineRequest(); + void onEmptyInlineRows(); + signals: void emojiSelected(EmojiPtr emoji); void stickerSelected(DocumentData *sticker); + void photoSelected(PhotoData *photo); + void inlineResultSelected(InlineResult *result, UserData *bot); + void updateStickers(); private: - int32 _maxHeight; + void validateSelectedIcon(bool animated = false); + + int32 _maxHeight, _contentMaxHeight, _contentHeight, _contentHeightEmoji, _contentHeightStickers; bool _horizontal; + void updateContentHeight(); void leaveToChildEvent(QEvent *e); + void hideAnimated(); + void prepareShowHideCache(); void updateSelected(); void updateIcons(); @@ -542,35 +649,36 @@ private: bool _noTabUpdate; - int32 _width, _height; + int32 _width, _height, _bottom; bool _hiding; QPixmap _cache; anim::fvalue a_opacity; + Animation _a_appearance; QTimer _hideTimer; BoxShadow _shadow; FlatRadiobutton _recent, _people, _nature, _food, _activity, _travel, _objects, _symbols; - QVector _icons; + QList _icons; QVector _iconHovers; int32 _iconOver, _iconSel, _iconDown; bool _iconsDragging; typedef QMap Animations; // index - showing, -index - hiding Animations _iconAnimations; - Animation _iconAnim; + Animation _a_icons; QPoint _iconsMousePos, _iconsMouseDown; int32 _iconsLeft, _iconsTop; int32 _iconsStartX, _iconsMax; anim::ivalue _iconsX, _iconSelX; uint64 _iconsStartAnim; - bool _stickersShown; + bool _stickersShown, _shownFromInlineQuery; QPixmap _fromCache, _toCache; anim::ivalue a_fromCoord, a_toCoord; anim::fvalue a_fromAlpha, a_toAlpha; - uint64 _moveStart; + Animation _a_slide; ScrollArea e_scroll; EmojiPanInner e_inner; @@ -583,6 +691,37 @@ private: 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; + InlineCache _inlineCache; + QTimer _inlineRequestTimer; + + void inlineBotChanged(); + int32 showInlineRows(bool newResults); + bool hideOnNoInlineResults(); + void recountContentMaxHeight(); + bool refreshInlineRows(int32 *added = 0); + UserData *_inlineBot; + QString _inlineQuery, _inlineNextQuery, _inlineNextOffset; + mtpRequestId _inlineRequestId; + void inlineResultsDone(const MTPmessages_BotResults &result); + bool inlineResultsFail(const RPCError &error); + }; typedef QList MentionRows; @@ -590,14 +729,15 @@ typedef QList HashtagRows; typedef QList > BotCommandRows; class MentionsDropdown; -class MentionsInner : public QWidget { +class MentionsInner : public TWidget { Q_OBJECT public: - MentionsInner(MentionsDropdown *parent, MentionRows *rows, HashtagRows *hrows, BotCommandRows *crows); + MentionsInner(MentionsDropdown *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, StickerPack *srows); void paintEvent(QPaintEvent *e); + void resizeEvent(QResizeEvent *e); void enterEvent(QEvent *e); void leaveEvent(QEvent *e); @@ -606,14 +746,17 @@ public: void mouseMoveEvent(QMouseEvent *e); void clearSel(); - bool moveSel(int direction); + bool moveSel(int key); bool select(); + void setRecentInlineBotsInRows(int32 bots); + QString getSelected() const; signals: void chosen(QString mentionOrHashtag); + void selected(DocumentData *sticker); void mustScrollTo(int scrollToTop, int scrollToBottom); public slots: @@ -623,12 +766,15 @@ public slots: private: + void updateSelectedRow(); void setSel(int sel, bool scroll = false); MentionsDropdown *_parent; - MentionRows *_rows; + MentionRows *_mrows; HashtagRows *_hrows; - BotCommandRows *_crows; + BotCommandRows *_brows; + StickerPack *_srows; + int32 _stickersPerRow, _recentInlineBotsInRows; int32 _sel; bool _mouseSel; QPoint _mousePos; @@ -636,7 +782,7 @@ private: bool _overDelete; }; -class MentionsDropdown : public TWidget, public Animated { +class MentionsDropdown : public TWidget { Q_OBJECT public: @@ -647,12 +793,13 @@ public: void fastHide(); - bool clearFilteredCommands(); - void showFiltered(PeerData *peer, QString start); - void updateFiltered(bool toDown = false); + bool clearFilteredBotCommands(); + void showFiltered(PeerData *peer, QString query, bool start); + void showStickers(EmojiPtr emoji); + void updateFiltered(bool resetScroll = false); void setBoundings(QRect boundings); - bool animStep(float64 ms); + void step_appearance(float64 ms, bool timer); const QString &filter() const; ChatData *chat() const; @@ -665,6 +812,10 @@ public: bool eventFilter(QObject *obj, QEvent *e); QString getSelected() const; + bool stickersShown() const { + return !_srows.isEmpty(); + } + bool overlaps(const QRect &globalRect) { if (isHidden() || !testAttribute(Qt::WA_OpaquePaintEvent)) return false; @@ -676,6 +827,7 @@ public: signals: void chosen(QString mentionOrHashtag); + void stickerSelected(DocumentData *sticker); public slots: @@ -686,12 +838,15 @@ public slots: private: - void recount(bool toDown = false); + void recount(bool resetScroll = false); QPixmap _cache; - MentionRows _rows; + MentionRows _mrows; HashtagRows _hrows; - BotCommandRows _crows; + BotCommandRows _brows; + StickerPack _srows; + + void rowsUpdated(const MentionRows &mrows, const HashtagRows &hrows, const BotCommandRows &brows, const StickerPack &srows, bool resetScroll); ScrollArea _scroll; MentionsInner _inner; @@ -699,13 +854,16 @@ private: ChatData *_chat; UserData *_user; ChannelData *_channel; + EmojiPtr _emoji; QString _filter; QRect _boundings; + bool _addInlineBots; int32 _width, _height; bool _hiding; anim::fvalue a_opacity; + Animation _a_appearance; QTimer _hideTimer; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index caa7fb9de1..683b3cd277 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -23,14 +23,17 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "window.h" #include "mainwidget.h" +#include "layerwidget.h" + namespace App { void sendBotCommand(const QString &cmd, MsgId replyTo) { if (MainWidget *m = main()) m->sendBotCommand(cmd, replyTo); } - void insertBotCommand(const QString &cmd) { - if (MainWidget *m = main()) m->insertBotCommand(cmd); + 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) { @@ -66,18 +69,6 @@ namespace App { if (Window *win = wnd()) win->showSettings(); } - void showLayer(LayeredWidget *widget, bool forceFast) { - if (Window *w = wnd()) w->showLayer(widget, forceFast); - } - - void replaceLayer(LayeredWidget *widget) { - if (Window *w = wnd()) w->replaceLayer(widget); - } - - void showLayerLast(LayeredWidget *widget) { - if (Window *w = wnd()) w->showLayerLast(widget); - } - } namespace Ui { @@ -90,20 +81,133 @@ namespace Ui { if (MainWidget *m = App::main()) m->ui_hideStickerPreview(); } + void showLayer(LayeredWidget *box, ShowLayerOptions options) { + if (Window *w = App::wnd()) { + w->ui_showLayer(box, options); + } else { + delete box; + } + } + + void hideLayer(bool fast) { + if (Window *w = App::wnd()) w->ui_showLayer(0, ShowLayerOptions(CloseOtherLayers) | (fast ? ForceFastShowLayer : AnimatedShowLayer)); + } + + bool isLayerShown() { + if (Window *w = App::wnd()) return w->ui_isLayerShown(); + return false; + } + + 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 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)); + } + } + } namespace Notify { void userIsBotChanged(UserData *user) { - if (MainWidget *m = App::main()) m->notifyUserIsBotChanged(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->notifyBotCommandsChanged(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->notifyMigrateUpdated(peer); + if (MainWidget *m = App::main()) m->notify_migrateUpdated(peer); + } + + void clipStopperHidden(ClipStopperType type) { + if (MainWidget *m = App::main()) m->notify_clipStopperHidden(type); + } + + void historyItemResized(const HistoryItem *item, bool scrollToIt) { + if (MainWidget *m = App::main()) m->notify_historyItemResized(item, scrollToIt); + } + + void historyItemLayoutChanged(const HistoryItem *item) { + if (MainWidget *m = App::main()) m->notify_historyItemLayoutChanged(item); + } + + void automaticLoadSettingsChangedGif() { + if (MainWidget *m = App::main()) m->notify_automaticLoadSettingsChangedGif(); } } + +namespace Global { + + struct Data { + uint64 LaunchId = 0; + }; + + Data *_data = 0; + + Initializer::Initializer() { + initThirdParty(); + _data = new Data(); + + memset_rand(&_data->LaunchId, sizeof(_data->LaunchId)); + } + + Initializer::~Initializer() { + deinitThirdParty(); + } + +#define DefineGlobalReadOnly(Type, Name) const Type &Name() { \ + t_assert_full(_data != 0, "_data is null in Global::" #Name, __FILE__, __LINE__); \ + return _data->Name; \ +} +#define DefineGlobal(Type, Name) DefineGlobalReadOnly(Type, Name) \ +void Set##Name(const Type &Name) { \ + t_assert_full(_data != 0, "_data is null in Global::Set" #Name, __FILE__, __LINE__); \ + _data->Name = Name; \ +} \ +Type &Ref##Name() { \ + t_assert_full(_data != 0, "_data is null in Global::Ref" #Name, __FILE__, __LINE__); \ + return _data->Name; \ +} + + DefineGlobalReadOnly(uint64, LaunchId); + +}; diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index afbbb8e079..1a80278c75 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -25,7 +25,7 @@ class LayeredWidget; namespace App { void sendBotCommand(const QString &cmd, MsgId replyTo = 0); - void insertBotCommand(const QString &cmd); + bool insertBotCommand(const QString &cmd, bool specialGif = false); void searchByHashtag(const QString &tag, PeerData *inPeer); void openPeerByName(const QString &username, bool toProfile = false, const QString &startToken = QString()); void joinGroupByHash(const QString &hash); @@ -34,23 +34,81 @@ namespace App { bool forward(const PeerId &peer, ForwardWhatMessages what); void removeDialog(History *history); void showSettings(); - void showLayer(LayeredWidget *w, bool forceFast = false); - void replaceLayer(LayeredWidget *w); - void showLayerLast(LayeredWidget *w); }; -namespace Ui { // it doesn't allow me to use UI :( +namespace Ui { // openssl doesn't allow me to use UI :( void showStickerPreview(DocumentData *sticker); void hideStickerPreview(); + 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 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); + } + +}; + +enum ClipStopperType { + ClipStopperMediaview, + ClipStopperSavedGifsPanel, }; namespace Notify { void userIsBotChanged(UserData *user); + void userIsContactChanged(UserData *user, bool fromThisApp = false); void botCommandsChanged(UserData *user); + + void inlineBotRequesting(bool requesting); + void migrateUpdated(PeerData *peer); + void clipStopperHidden(ClipStopperType type); + + void historyItemResized(const HistoryItem *item, bool scrollToIt = false); + inline void historyItemsResized() { + historyItemResized(0); + } + void historyItemLayoutChanged(const HistoryItem *item); + + void automaticLoadSettingsChangedGif(); + +}; + +namespace Global { + + class Initializer { + public: + Initializer(); + ~Initializer(); + }; + +#define DeclareGlobalReadOnly(Type, Name) const Type &Name(); +#define DeclareGlobal(Type, Name) DeclareGlobalReadOnly(Type, Name) \ + void Set##Name(const Type &Name); \ + Type &Ref##Name(); + + DeclareGlobalReadOnly(uint64, LaunchId); + }; diff --git a/Telegram/SourceFiles/fileuploader.cpp b/Telegram/SourceFiles/fileuploader.cpp index 3a2c7eae2d..2c5f30adc7 100644 --- a/Telegram/SourceFiles/fileuploader.cpp +++ b/Telegram/SourceFiles/fileuploader.cpp @@ -46,7 +46,7 @@ void FileUploader::uploadMedia(const FullMsgId &msgId, const ReadyLocalMedia &me } else if (media.type == PrepareAudio) { AudioData *audio = App::feedAudio(media.audio); audio->status = FileUploading; - audio->data = media.data; + audio->setData(media.data); } queue.insert(msgId, File(media)); sendNext(); @@ -54,7 +54,8 @@ void FileUploader::uploadMedia(const FullMsgId &msgId, const ReadyLocalMedia &me void FileUploader::upload(const FullMsgId &msgId, const FileLoadResultPtr &file) { if (file->type == PreparePhoto) { - App::feedPhoto(file->photo, file->photoThumbs); + PhotoData *photo = App::feedPhoto(file->photo, file->photoThumbs); + photo->uploadingData = new PhotoData::UploadingData(file->partssize); } else if (file->type == PrepareDocument) { DocumentData *document; if (file->thumb.isNull()) { @@ -69,7 +70,7 @@ void FileUploader::upload(const FullMsgId &msgId, const FileLoadResultPtr &file) } else if (file->type == PrepareAudio) { AudioData *audio = App::feedAudio(file->audio); audio->status = FileUploading; - audio->data = file->content; + audio->setData(file->content); } queue.insert(msgId, File(file)); sendNext(); @@ -83,13 +84,13 @@ void FileUploader::currentFailed() { } else if (j->type() == PrepareDocument) { DocumentData *doc = App::document(j->id()); if (doc->status == FileUploading) { - doc->status = FileFailed; + doc->status = FileUploadFailed; } emit documentFailed(j.key()); } else if (j->type() == PrepareAudio) { AudioData *audio = App::audio(j->id()); if (audio->status == FileUploading) { - audio->status = FileFailed; + audio->status = FileUploadFailed; } emit audioFailed(j.key()); } @@ -115,7 +116,7 @@ void FileUploader::killSessions() { } void FileUploader::sendNext() { - if (sentSize >= MaxUploadFileParallelSize) return; + if (sentSize >= MaxUploadFileParallelSize || _paused.msg) return; bool killing = killSessionsTimer.isActive(); if (queue.isEmpty()) { @@ -232,6 +233,15 @@ void FileUploader::cancel(const FullMsgId &msgId) { } } +void FileUploader::pause(const FullMsgId &msgId) { + _paused = msgId; +} + +void FileUploader::unpause() { + _paused = FullMsgId(); + sendNext(); +} + void FileUploader::confirm(const FullMsgId &msgId) { } @@ -274,21 +284,28 @@ void FileUploader::partLoaded(const MTPBool &result, mtpRequestId requestId) { int32 dc = dcIt.value(); dcMap.erase(dcIt); + int32 sentPartSize = 0; Queue::const_iterator k = queue.constFind(uploading); if (i != requestsSent.cend()) { - sentSize -= i.value().size(); - sentSizes[dc] -= i.value().size(); + sentPartSize = i.value().size(); requestsSent.erase(i); } else { - sentSize -= k->docPartSize; - sentSizes[dc] -= k->docPartSize; + sentPartSize = k->docPartSize; docRequestsSent.erase(j); } + sentSize -= sentPartSize; + sentSizes[dc] -= sentPartSize; if (k->type() == PreparePhoto) { + k->fileSentSize += sentPartSize; + PhotoData *photo = App::photo(k->id()); + if (photo->uploading() && k->file) { + photo->uploadingData->size = k->file->partssize; + photo->uploadingData->offset = k->fileSentSize; + } emit photoProgress(k.key()); } else if (k->type() == PrepareDocument) { DocumentData *doc = App::document(k->id()); - if (doc->status == FileUploading) { + if (doc->uploading()) { doc->uploadOffset = (k->docSentParts - docRequestsSent.size()) * k->docPartSize; if (doc->uploadOffset > doc->size) { doc->uploadOffset = doc->size; @@ -297,7 +314,7 @@ void FileUploader::partLoaded(const MTPBool &result, mtpRequestId requestId) { emit documentProgress(k.key()); } else if (k->type() == PrepareAudio) { AudioData *audio = App::audio(k->id()); - if (audio->status == FileUploading) { + if (audio->uploading()) { audio->uploadOffset = (k->docSentParts - docRequestsSent.size()) * k->docPartSize; if (audio->uploadOffset > audio->size) { audio->uploadOffset = audio->size; diff --git a/Telegram/SourceFiles/fileuploader.h b/Telegram/SourceFiles/fileuploader.h index 26b542bdb5..460f8e1702 100644 --- a/Telegram/SourceFiles/fileuploader.h +++ b/Telegram/SourceFiles/fileuploader.h @@ -35,12 +35,14 @@ public: int32 fullSize(const FullMsgId &msgId) const; void cancel(const FullMsgId &msgId); + void pause(const FullMsgId &msgId); void confirm(const FullMsgId &msgId); void clear(); public slots: + void unpause(); void sendNext(); void killSessions(); @@ -101,6 +103,7 @@ private: FileLoadResultPtr file; ReadyLocalMedia media; int32 partsCount; + mutable int32 fileSentSize; uint64 id() const { return file ? file->id : media.id; @@ -136,7 +139,7 @@ private: uint32 sentSize; uint32 sentSizes[MTPUploadSessionsCount]; - FullMsgId uploading; + FullMsgId uploading, _paused; Queue queue; Queue uploaded; QTimer nextTimer, killSessionsTimer; diff --git a/Telegram/SourceFiles/gui/animation.cpp b/Telegram/SourceFiles/gui/animation.cpp index 72a2a8d275..f5d59a9381 100644 --- a/Telegram/SourceFiles/gui/animation.cpp +++ b/Telegram/SourceFiles/gui/animation.cpp @@ -26,7 +26,9 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "window.h" namespace { - AnimationManager *manager = 0; + AnimationManager *_manager = 0; + QVector _clipThreads; + QVector _clipManagers; }; namespace anim { @@ -78,177 +80,1237 @@ namespace anim { return delta * (t2 * t2 * t + 1); } - void start(Animated *obj) { - if (!manager) return; - manager->start(obj); - } - - void step(Animated *obj) { - if (!manager) return; - manager->step(obj); - } - - void stop(Animated *obj) { - if (!manager) return; - manager->stop(obj); - } - void startManager() { - delete manager; - manager = new AnimationManager(); + stopManager(); + + _manager = new AnimationManager(); + } void stopManager() { - delete manager; - manager = 0; + delete _manager; + _manager = 0; + if (!_clipThreads.isEmpty()) { + for (int32 i = 0, l = _clipThreads.size(); i < l; ++i) { + _clipThreads.at(i)->quit(); + _clipThreads.at(i)->wait(); + delete _clipManagers.at(i); + delete _clipThreads.at(i); + } + _clipThreads.clear(); + _clipManagers.clear(); + } } } -bool AnimatedGif::animStep(float64 ms) { - int32 f = frame; - while (f < images.size() && ms > delays[f]) { - ++f; - if (f == images.size() && images.size() < framesCount) { - if (reader->read(&img)) { - int64 d = reader->nextImageDelay(), delay = delays[f - 1]; - if (!d) d = 1; - delay += d; - if (img.size() != QSize(w, h)) img = img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - images.push_back(img); - frames.push_back(QPixmap()); - delays.push_back(delay); - for (int32 i = 0; i < images.size(); ++i) { - if (!images[i].isNull() || !frames[i].isNull()) { - images[i] = QImage(); - frames[i] = QPixmap(); - break; - } - } - } else { - framesCount = images.size(); - } - } - if (f == images.size()) { - if (!duration) { - duration = delays.isEmpty() ? 1 : delays.back(); - } +void Animation::start() { + if (!_manager) return; - f = 0; - for (int32 i = 0, s = delays.size() - 1; i <= s; ++i) { - delays[i] += duration; - } - if (images[f].isNull()) { - QString fname = reader->fileName(); - delete reader; - reader = new QImageReader(fname); - } + _cb.start(); + _manager->start(this); + _animating = true; +} + +void Animation::stop() { + if (!_manager) return; + + _animating = false; + _manager->stop(this); +} + +AnimationManager::AnimationManager() : _timer(this), _iterating(false) { + _timer.setSingleShot(false); + connect(&_timer, SIGNAL(timeout()), this, SLOT(timeout())); +} + +void AnimationManager::start(Animation *obj) { + if (_iterating) { + _starting.insert(obj, NullType()); + if (!_stopping.isEmpty()) { + _stopping.remove(obj); } - if (images[f].isNull() && reader->read(&img)) { - if (img.size() != QSize(w, h)) img = img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - images[f] = img; - frames[f] = QPixmap(); + } else { + if (_objects.isEmpty()) { + _timer.start(AnimationTimerDelta); + } + _objects.insert(obj, NullType()); + } +} + +void AnimationManager::stop(Animation *obj) { + if (_iterating) { + _stopping.insert(obj, NullType()); + if (!_starting.isEmpty()) { + _starting.insert(obj, NullType()); + } + } else { + AnimatingObjects::iterator i = _objects.find(obj); + if (i != _objects.cend()) { + _objects.erase(i); + if (_objects.isEmpty()) { + _timer.stop(); + } } } - if (frame != f) { - frame = f; - if (msg && App::main()) { - App::main()->msgUpdated(msg); - } else { - emit updated(); +} + +void AnimationManager::timeout() { + _iterating = true; + uint64 ms = getms(); + for (AnimatingObjects::const_iterator i = _objects.begin(), e = _objects.end(); i != e; ++i) { + i.key()->step(ms, true); + } + _iterating = false; + + if (!_starting.isEmpty()) { + for (AnimatingObjects::iterator i = _starting.begin(), e = _starting.end(); i != e; ++i) { + _objects.insert(i.key(), NullType()); } + _starting.clear(); + } + if (!_stopping.isEmpty()) { + for (AnimatingObjects::iterator i = _stopping.begin(), e = _stopping.end(); i != e; ++i) { + _objects.remove(i.key()); + } + _stopping.clear(); + } + if (!_objects.size()) { + _timer.stop(); + } +} + +void AnimationManager::clipCallback(ClipReader *reader, qint32 threadIndex, qint32 notification) { + ClipReader::callback(reader, threadIndex, ClipReaderNotification(notification)); +} + +QPixmap _prepareFrame(const ClipFrameRequest &request, const QImage &original, bool hasAlpha, QImage &cache) { + bool badSize = (original.width() != request.framew) || (original.height() != request.frameh); + bool needOuter = (request.outerw != request.framew) || (request.outerh != request.frameh); + if (badSize || needOuter || hasAlpha || request.rounded) { + int32 factor(request.factor); + bool newcache = (cache.width() != request.outerw || cache.height() != request.outerh); + if (newcache) { + cache = QImage(request.outerw, request.outerh, QImage::Format_ARGB32_Premultiplied); + cache.setDevicePixelRatio(factor); + } + { + Painter p(&cache); + if (newcache) { + if (request.framew < request.outerw) { + p.fillRect(0, 0, (request.outerw - request.framew) / (2 * factor), cache.height() / factor, st::black); + p.fillRect((request.outerw - request.framew) / (2 * factor) + (request.framew / factor), 0, (cache.width() / factor) - ((request.outerw - request.framew) / (2 * factor) + (request.framew / factor)), cache.height() / factor, st::black); + } + if (request.frameh < request.outerh) { + p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), 0, qMin(cache.width(), request.framew) / factor, (request.outerh - request.frameh) / (2 * factor), st::black); + p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), (request.outerh - request.frameh) / (2 * factor) + (request.frameh / factor), qMin(cache.width(), request.framew) / factor, (cache.height() / factor) - ((request.outerh - request.frameh) / (2 * factor) + (request.frameh / factor)), st::black); + } + } + if (hasAlpha) { + p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), qMax(0, (request.outerh - request.frameh) / (2 * factor)), qMin(cache.width(), request.framew) / factor, qMin(cache.height(), request.frameh) / factor, st::white); + } + QPoint position((request.outerw - request.framew) / (2 * factor), (request.outerh - request.frameh) / (2 * factor)); + if (badSize) { + p.setRenderHint(QPainter::SmoothPixmapTransform); + QRect to(position, QSize(request.framew / factor, request.frameh / factor)); + QRect from(0, 0, original.width(), original.height()); + p.drawImage(to, original, from, Qt::ColorOnly); + } else { + p.drawImage(position, original); + } + } + if (request.rounded) { + imageRound(cache); + } + return QPixmap::fromImage(cache, Qt::ColorOnly); + } + return QPixmap::fromImage(original, Qt::ColorOnly); +} + +ClipReader::ClipReader(const FileLocation &location, const QByteArray &data, Callback::Creator cb) +: _cb(cb) +, _state(ClipReading) +, _width(0) +, _height(0) +, _step(WaitingForDimensionsStep) +, _paused(0) +, _autoplay(false) +, _private(0) { + if (_clipThreads.size() < ClipThreadsCount) { + _threadIndex = _clipThreads.size(); + _clipThreads.push_back(new QThread()); + _clipManagers.push_back(new ClipReadManager(_clipThreads.back())); + _clipThreads.back()->start(); + } else { + _threadIndex = int32(MTP::nonce() % _clipThreads.size()); + int32 loadLevel = 0x7FFFFFFF; + for (int32 i = 0, l = _clipThreads.size(); i < l; ++i) { + int32 level = _clipManagers.at(i)->loadLevel(); + if (level < loadLevel) { + _threadIndex = i; + loadLevel = level; + } + } + } + _clipManagers.at(_threadIndex)->append(this, location, data); +} + +ClipReader::Frame *ClipReader::frameToShow(int32 *index) const { // 0 means not ready + int32 step = _step.loadAcquire(), i; + if (step == WaitingForDimensionsStep) { + if (index) *index = 0; + return 0; + } else if (step == WaitingForRequestStep) { + i = 0; + } else if (step == WaitingForFirstFrameStep) { + i = 0; + } else { + i = (step / 2) % 3; + } + if (index) *index = i; + return _frames + i; +} + +ClipReader::Frame *ClipReader::frameToWrite(int32 *index) const { // 0 means not ready + int32 step = _step.loadAcquire(), i; + if (step == WaitingForDimensionsStep) { + i = 0; + } else if (step == WaitingForRequestStep) { + if (index) *index = 0; + return 0; + } else if (step == WaitingForFirstFrameStep) { + i = 0; + } else { + i = ((step + 2) / 2) % 3; + } + if (index) *index = i; + return _frames + i; +} + +ClipReader::Frame *ClipReader::frameToWriteNext(bool checkNotWriting, int32 *index) const { + int32 step = _step.loadAcquire(), i; + if (step == WaitingForDimensionsStep || step == WaitingForRequestStep || (checkNotWriting && (step % 2))) { + if (index) *index = 0; + return 0; + } + i = ((step + 4) / 2) % 3; + if (index) *index = i; + return _frames + i; +} + +void ClipReader::moveToNextShow() const { + int32 step = _step.loadAcquire(); + if (step == WaitingForDimensionsStep) { + } else if (step == WaitingForRequestStep) { + _step.storeRelease(WaitingForFirstFrameStep); + } else if (step == WaitingForFirstFrameStep) { + } else if (!(step % 2)) { + _step.storeRelease(step + 1); + } +} + +void ClipReader::moveToNextWrite() const { + int32 step = _step.loadAcquire(); + if (step == WaitingForDimensionsStep) { + _step.storeRelease(WaitingForRequestStep); + } else if (step == WaitingForRequestStep) { + } else if (step == WaitingForFirstFrameStep) { + _step.storeRelease(0); + } else if (step % 2) { + _step.storeRelease((step + 1) % 6); + } +} + +void ClipReader::callback(ClipReader *reader, int32 threadIndex, ClipReaderNotification notification) { + // check if reader is not deleted already + if (_clipManagers.size() > threadIndex && _clipManagers.at(threadIndex)->carries(reader)) { + reader->_cb.call(notification); + } +} + +void ClipReader::start(int32 framew, int32 frameh, int32 outerw, int32 outerh, bool rounded) { + if (_clipManagers.size() <= _threadIndex) error(); + if (_state == ClipError) return; + + if (_step.loadAcquire() == WaitingForRequestStep) { + int32 factor(cIntRetinaFactor()); + ClipFrameRequest request; + request.factor = factor; + request.framew = framew * factor; + request.frameh = frameh * factor; + request.outerw = outerw * factor; + request.outerh = outerh * factor; + request.rounded = rounded; + _frames[0].request = _frames[1].request = _frames[2].request = request; + moveToNextShow(); + _clipManagers.at(_threadIndex)->start(this); + } +} + +QPixmap ClipReader::current(int32 framew, int32 frameh, int32 outerw, int32 outerh, uint64 ms) { + Frame *frame = frameToShow(); + t_assert(frame != 0); + + if (ms) { + frame->displayed.storeRelease(1); + if (_paused.loadAcquire()) { + _paused.storeRelease(0); + if (_clipManagers.size() <= _threadIndex) error(); + if (_state != ClipError) { + _clipManagers.at(_threadIndex)->update(this); + } + } + } else { + frame->displayed.storeRelease(-1); // displayed, but should be paused + } + + int32 factor(cIntRetinaFactor()); + if (frame->pix.width() == outerw * factor && frame->pix.height() == outerh * factor) { + moveToNextShow(); + return frame->pix; + } + + frame->request.framew = framew * factor; + frame->request.frameh = frameh * factor; + frame->request.outerw = outerw * factor; + frame->request.outerh = outerh * factor; + + QImage cacheForResize; + frame->pix = QPixmap(); + frame->pix = _prepareFrame(frame->request, frame->original, true, cacheForResize); + + Frame *other = frameToWriteNext(true); + if (other) other->request = frame->request; + + moveToNextShow(); + + if (_clipManagers.size() <= _threadIndex) error(); + if (_state != ClipError) { + _clipManagers.at(_threadIndex)->update(this); + } + + return frame->pix; +} + +bool ClipReader::ready() const { + if (_width && _height) return true; + + Frame *frame = frameToShow(); + if (frame) { + _width = frame->original.width(); + _height = frame->original.height(); + return true; + } + return false; +} + +int32 ClipReader::width() const { + return _width; +} + +int32 ClipReader::height() const { + return _height; +} + +ClipState ClipReader::state() const { + return _state; +} + +void ClipReader::stop() { + if (_clipManagers.size() <= _threadIndex) error(); + if (_state != ClipError) { + _clipManagers.at(_threadIndex)->stop(this); + _width = _height = 0; + } +} + +void ClipReader::error() { + _private = 0; + _state = ClipError; +} + +ClipReader::~ClipReader() { + stop(); +} + +class ClipReaderImplementation { +public: + + ClipReaderImplementation(FileLocation *location, QByteArray *data) + : _location(location) + , _data(data) + , _device(0) + , _dataSize(0) { + } + virtual bool readNextFrame() = 0; + virtual bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) = 0; + virtual int32 nextFrameDelay() = 0; + virtual bool start(bool onlyGifv) = 0; + virtual ~ClipReaderImplementation() { + } + int64 dataSize() const { + return _dataSize; + } + +protected: + FileLocation *_location; + QByteArray *_data; + QFile _file; + QBuffer _buffer; + QIODevice *_device; + int64 _dataSize; + + void initDevice() { + if (_data->isEmpty()) { + if (_file.isOpen()) _file.close(); + _file.setFileName(_location->name()); + _dataSize = _file.size(); + } else { + if (_buffer.isOpen()) _buffer.close(); + _buffer.setBuffer(_data); + _dataSize = _data->size(); + } + _device = _data->isEmpty() ? static_cast(&_file) : static_cast(&_buffer); + } + +}; + +class QtGifReaderImplementation : public ClipReaderImplementation{ +public: + + QtGifReaderImplementation(FileLocation *location, QByteArray *data) : ClipReaderImplementation(location, data) + , _reader(0) + , _framesLeft(0) + , _frameDelay(0) { + } + + bool readNextFrame() { + if (_reader) _frameDelay = _reader->nextImageDelay(); + if (_framesLeft < 1 && !jumpToStart()) { + return false; + } + + _frame = QImage(); // QGifHandler always reads first to internal QImage and returns it + if (!_reader->read(&_frame) || _frame.isNull()) { + return false; + } + --_framesLeft; + return true; + } + + bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) { + t_assert(!_frame.isNull()); + if (size.isEmpty() || size == _frame.size()) { + int32 w = _frame.width(), h = _frame.height(); + if (to.width() == w && to.height() == h && to.format() == _frame.format()) { + if (to.byteCount() != _frame.byteCount()) { + int bpl = qMin(to.bytesPerLine(), _frame.bytesPerLine()); + for (int i = 0; i < h; ++i) { + memcpy(to.scanLine(i), _frame.constScanLine(i), bpl); + } + } else { + memcpy(to.bits(), _frame.constBits(), _frame.byteCount()); + } + } else { + to = _frame.copy(); + } + } else { + to = _frame.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + } + hasAlpha = _frame.hasAlphaChannel(); + _frame = QImage(); + return true; + } + + int32 nextFrameDelay() { + return _frameDelay; + } + + bool start(bool onlyGifv) { + if (onlyGifv) return false; + return jumpToStart(); + } + + ~QtGifReaderImplementation() { + deleteAndMark(_reader); + } + +private: + QImageReader *_reader; + int32 _framesLeft, _frameDelay; + QImage _frame; + + bool jumpToStart() { + if (_reader && _reader->jumpToImage(0)) { + _framesLeft = _reader->imageCount(); + return true; + } + + delete _reader; + initDevice(); + _reader = new QImageReader(_device); +#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) + _reader->setAutoTransform(true); +#endif + if (!_reader->canRead() || !_reader->supportsAnimation()) { + return false; + } + _framesLeft = _reader->imageCount(); + if (_framesLeft < 1) { + return false; + } + return true; + } + +}; + +class FFMpegReaderImplementation : public ClipReaderImplementation { +public: + + FFMpegReaderImplementation(FileLocation *location, QByteArray *data) : ClipReaderImplementation(location, data) + , _ioBuffer(0) + , _ioContext(0) + , _fmtContext(0) + , _codec(0) + , _codecContext(0) + , _streamId(0) + , _frame(0) + , _opened(false) + , _hadFrame(false) + , _frameRead(false) + , _packetSize(0) + , _packetData(0) + , _packetWas(false) + , _width(0) + , _height(0) + , _swsContext(0) + , _frameMs(0) + , _nextFrameDelay(0) + , _currentFrameDelay(0) { + _frame = av_frame_alloc(); + av_init_packet(&_avpkt); + _avpkt.data = NULL; + _avpkt.size = 0; + } + + bool readNextFrame() { + if (_frameRead) { + av_frame_unref(_frame); + _frameRead = false; + } + + int res; + while (true) { + if (_avpkt.size > 0) { // previous packet not finished + res = 0; + } else if ((res = av_read_frame(_fmtContext, &_avpkt)) < 0) { + if (res != AVERROR_EOF || !_hadFrame) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Gif Error: Unable to av_read_frame() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + } + + bool finished = (res < 0); + if (finished) { + _avpkt.data = NULL; + _avpkt.size = 0; + } else { + rememberPacket(); + } + + int32 got_frame = 0; + int32 decoded = _avpkt.size; + if (_avpkt.stream_index == _streamId) { + if ((res = avcodec_decode_video2(_codecContext, _frame, &got_frame, &_avpkt)) < 0) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Gif Error: Unable to avcodec_decode_video2() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + + if (res == AVERROR_INVALIDDATA) { // try to skip bad packet + freePacket(); + _avpkt.data = NULL; + _avpkt.size = 0; + continue; + } + + if (res != AVERROR_EOF || !_hadFrame) { // try to skip end of file + return false; + } + freePacket(); + _avpkt.data = NULL; + _avpkt.size = 0; + continue; + } + if (res > 0) decoded = res; + } + if (!finished) { + _avpkt.data += decoded; + _avpkt.size -= decoded; + if (_avpkt.size <= 0) freePacket(); + } + + if (got_frame) { + int64 duration = av_frame_get_pkt_duration(_frame); + int64 framePts = (_frame->pkt_pts == AV_NOPTS_VALUE) ? _frame->pkt_dts : _frame->pkt_pts; + int64 frameMs = (framePts * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; + _currentFrameDelay = _nextFrameDelay; + if (_frameMs + _currentFrameDelay < frameMs) { + _currentFrameDelay = int32(frameMs - _frameMs); + } + if (duration == AV_NOPTS_VALUE) { + _nextFrameDelay = 0; + } else { + _nextFrameDelay = (duration * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; + } + _frameMs = frameMs; + + _hadFrame = _frameRead = true; + return true; + } + + if (finished) { + if ((res = avformat_seek_file(_fmtContext, _streamId, std::numeric_limits::min(), 0, std::numeric_limits::max(), 0)) < 0) { + if ((res = av_seek_frame(_fmtContext, _streamId, 0, AVSEEK_FLAG_BYTE)) < 0) { + if ((res = av_seek_frame(_fmtContext, _streamId, 0, AVSEEK_FLAG_FRAME)) < 0) { + if ((res = av_seek_frame(_fmtContext, _streamId, 0, 0)) < 0) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Gif Error: Unable to av_seek_frame() to the start %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + } + } + } + avcodec_flush_buffers(_codecContext); + _hadFrame = false; + _frameMs = 0; + } + } + + return false; + } + + bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) { + t_assert(_frameRead); + _frameRead = false; + + if (!_width || !_height) { + _width = _frame->width; + _height = _frame->height; + if (!_width || !_height) { + LOG(("Gif Error: Bad frame size %1").arg(logData())); + return false; + } + } + + QSize toSize(size.isEmpty() ? QSize(_width, _height) : size); + if (to.isNull() || to.size() != toSize) { + to = QImage(toSize, QImage::Format_ARGB32); + } + hasAlpha = (_frame->format == AV_PIX_FMT_BGRA || (_frame->format == -1 && _codecContext->pix_fmt == AV_PIX_FMT_BGRA)); + if (_frame->width == toSize.width() && _frame->height == toSize.height() && hasAlpha) { + int32 sbpl = _frame->linesize[0], dbpl = to.bytesPerLine(), bpl = qMin(sbpl, dbpl); + uchar *s = _frame->data[0], *d = to.bits(); + for (int32 i = 0, l = _frame->height; i < l; ++i) { + memcpy(d + i * dbpl, s + i * sbpl, bpl); + } + } else { + if ((_swsSize != toSize) || (_frame->format != -1 && _frame->format != _codecContext->pix_fmt) || !_swsContext) { + _swsSize = toSize; + _swsContext = sws_getCachedContext(_swsContext, _frame->width, _frame->height, AVPixelFormat(_frame->format), toSize.width(), toSize.height(), AV_PIX_FMT_BGRA, 0, 0, 0, 0); + } + uint8_t * toData[1] = { to.bits() }; + int toLinesize[1] = { to.bytesPerLine() }, res; + if ((res = sws_scale(_swsContext, _frame->data, _frame->linesize, 0, _frame->height, toData, toLinesize)) != _swsSize.height()) { + LOG(("Gif Error: Unable to sws_scale to good size %1, height %2, should be %3").arg(logData()).arg(res).arg(_swsSize.height())); + return false; + } + } + + av_frame_unref(_frame); + return true; + } + + int32 nextFrameDelay() { + return _currentFrameDelay; + } + + QString logData() const { + return qsl("for file '%1', data size '%2'").arg(_location ? _location->name() : QString()).arg(_data->size()); + } + + bool start(bool onlyGifv) { + initDevice(); + if (!_device->open(QIODevice::ReadOnly)) { + LOG(("Gif Error: Unable to open device %1").arg(logData())); + return false; + } + _ioBuffer = (uchar*)av_malloc(AVBlockSize); + _ioContext = avio_alloc_context(_ioBuffer, AVBlockSize, 0, static_cast(this), &FFMpegReaderImplementation::_read, 0, &FFMpegReaderImplementation::_seek); + _fmtContext = avformat_alloc_context(); + if (!_fmtContext) { + LOG(("Gif Error: Unable to avformat_alloc_context %1").arg(logData())); + return false; + } + _fmtContext->pb = _ioContext; + + int res = 0; + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + if ((res = avformat_open_input(&_fmtContext, 0, 0, 0)) < 0) { + _ioBuffer = 0; + + LOG(("Gif Error: Unable to avformat_open_input %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + _opened = true; + + if ((res = avformat_find_stream_info(_fmtContext, 0)) < 0) { + LOG(("Gif Error: Unable to avformat_find_stream_info %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + + _streamId = av_find_best_stream(_fmtContext, AVMEDIA_TYPE_VIDEO, -1, -1, 0, 0); + if (_streamId < 0) { + LOG(("Gif Error: Unable to av_find_best_stream %1, error %2, %3").arg(logData()).arg(_streamId).arg(av_make_error_string(err, sizeof(err), _streamId))); + return false; + } + + // Get a pointer to the codec context for the audio stream + _codecContext = _fmtContext->streams[_streamId]->codec; + _codec = avcodec_find_decoder(_codecContext->codec_id); + + if (onlyGifv) { + if (av_find_best_stream(_fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, 0, 0) >= 0) { // should be no audio stream + return false; + } + if (dataSize() > AnimationInMemory) { + return false; + } + if (_codecContext->codec_id != AV_CODEC_ID_H264) { + return false; + } + } + av_opt_set_int(_codecContext, "refcounted_frames", 1, 0); + if ((res = avcodec_open2(_codecContext, _codec, 0)) < 0) { + LOG(("Gif Error: Unable to avcodec_open2 %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + + return true; + } + + int32 duration() const { + if (_fmtContext->streams[_streamId]->duration == AV_NOPTS_VALUE) return 0; + return (_fmtContext->streams[_streamId]->duration * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; + } + + ~FFMpegReaderImplementation() { + if (_frameRead) { + av_frame_unref(_frame); + _frameRead = false; + } + if (_ioContext) av_free(_ioContext); + if (_codecContext) avcodec_close(_codecContext); + if (_swsContext) sws_freeContext(_swsContext); + if (_opened) { + avformat_close_input(&_fmtContext); + } else if (_ioBuffer) { + av_free(_ioBuffer); + } + if (_fmtContext) avformat_free_context(_fmtContext); + av_frame_free(&_frame); + freePacket(); + } + +private: + uchar *_ioBuffer; + AVIOContext *_ioContext; + AVFormatContext *_fmtContext; + AVCodec *_codec; + AVCodecContext *_codecContext; + int32 _streamId; + AVFrame *_frame; + bool _opened, _hadFrame, _frameRead; + + AVPacket _avpkt; + int _packetSize; + uint8_t *_packetData; + bool _packetWas; + void rememberPacket() { + if (!_packetWas) { + _packetSize = _avpkt.size; + _packetData = _avpkt.data; + _packetWas = true; + } + } + void freePacket() { + if (_packetWas) { + _avpkt.size = _packetSize; + _avpkt.data = _packetData; + _packetWas = false; + av_packet_unref(&_avpkt); + } + } + + int32 _width, _height; + SwsContext *_swsContext; + QSize _swsSize; + + int64 _frameMs; + int32 _nextFrameDelay, _currentFrameDelay; + + static int _read(void *opaque, uint8_t *buf, int buf_size) { + FFMpegReaderImplementation *l = reinterpret_cast(opaque); + return int(l->_device->read((char*)(buf), buf_size)); + } + + static int64_t _seek(void *opaque, int64_t offset, int whence) { + FFMpegReaderImplementation *l = reinterpret_cast(opaque); + + switch (whence) { + case SEEK_SET: return l->_device->seek(offset) ? l->_device->pos() : -1; + case SEEK_CUR: return l->_device->seek(l->_device->pos() + offset) ? l->_device->pos() : -1; + case SEEK_END: return l->_device->seek(l->_device->size() + offset) ? l->_device->pos() : -1; + } + return -1; + } + +}; + +class ClipReaderPrivate { +public: + + ClipReaderPrivate(ClipReader *reader, const FileLocation &location, const QByteArray &data) : _interface(reader) + , _state(ClipReading) + , _data(data) + , _location(_data.isEmpty() ? new FileLocation(location) : 0) + , _accessed(false) + , _implementation(0) + , _frame(0) + , _width(0) + , _height(0) + , _nextFrameWhen(0) + , _paused(false) { + if (_data.isEmpty() && !_location->accessEnable()) { + error(); + return; + } + _accessed = true; + } + + ClipProcessResult start(uint64 ms) { + if (!_implementation && !init()) { + return error(); + } + if (frame() && frame()->original.isNull()) { + if (!_implementation->readNextFrame()) { + return error(); + } + if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize())) { + return error(); + } + _width = frame()->original.width(); + _height = frame()->original.height(); + return ClipProcessStarted; + } + return ClipProcessWait; + } + + ClipProcessResult process(uint64 ms) { // -1 - do nothing, 0 - update, 1 - reinit + if (_state == ClipError) return ClipProcessError; + + if (!_request.valid()) { + return start(ms); + } + + if (!_paused && ms >= _nextFrameWhen) { + return ClipProcessRepaint; + } + return ClipProcessWait; + } + + ClipProcessResult finishProcess(uint64 ms) { + if (!readNextFrame()) { + return error(); + } + if (ms >= _nextFrameWhen && !readNextFrame(true)) { + return error(); + } + if (!renderFrame()) { + return error(); + } + return ClipProcessCopyFrame; + } + + uint64 nextFrameDelay() { + int32 delay = _implementation->nextFrameDelay(); + return qMax(delay, 5); + } + + bool readNextFrame(bool keepup = false) { + if (!_implementation->readNextFrame()) { + return false; + } + _nextFrameWhen += nextFrameDelay(); + if (keepup) { + _nextFrameWhen = qMax(_nextFrameWhen, getms()); + } + return true; + } + + bool renderFrame() { + t_assert(frame() != 0 && _request.valid()); + if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize(_request.framew, _request.frameh))) { + return false; + } + frame()->original.setDevicePixelRatio(_request.factor); + frame()->pix = QPixmap(); + frame()->pix = _prepareFrame(_request, frame()->original, frame()->alpha, frame()->cache); + frame()->when = _nextFrameWhen; + return true; + } + + bool init() { + if (_data.isEmpty() && QFileInfo(_location->name()).size() <= AnimationInMemory) { + QFile f(_location->name()); + if (f.open(QIODevice::ReadOnly)) { + _data = f.readAll(); + if (f.error() != QFile::NoError) { + _data = QByteArray(); + } + } + } + + _implementation = new FFMpegReaderImplementation(_location, &_data); +// _implementation = new QtGifReaderImplementation(_location, &_data); + return _implementation->start(false); + } + + ClipProcessResult error() { + stop(); + _state = ClipError; + return ClipProcessError; + } + + void stop() { + delete _implementation; + _implementation = 0; + + if (_location) { + if (_accessed) { + _location->accessDisable(); + } + delete _location; + _location = 0; + } + _accessed = false; + } + + ~ClipReaderPrivate() { + stop(); + deleteAndMark(_location); + deleteAndMark(_implementation); + _data.clear(); + } + +private: + + ClipReader *_interface; + ClipState _state; + + QByteArray _data; + FileLocation *_location; + bool _accessed; + + QBuffer _buffer; + ClipReaderImplementation *_implementation; + + ClipFrameRequest _request; + struct Frame { + Frame() : alpha(true), when(0) { + } + QPixmap pix; + QImage original, cache; + bool alpha; + uint64 when; + }; + Frame _frames[3]; + int32 _frame; + Frame *frame() { + return _frames + _frame; + } + + int32 _width, _height; + + uint64 _nextFrameWhen; + + bool _paused; + + friend class ClipReadManager; + +}; + +ClipReadManager::ClipReadManager(QThread *thread) : _processingInThread(0), _needReProcess(false) { + moveToThread(thread); + connect(thread, SIGNAL(started()), this, SLOT(process())); + connect(thread, SIGNAL(finished()), this, SLOT(finish())); + connect(this, SIGNAL(processDelayed()), this, SLOT(process()), Qt::QueuedConnection); + + _timer.setSingleShot(true); + _timer.moveToThread(thread); + connect(&_timer, SIGNAL(timeout()), this, SLOT(process())); + + connect(this, SIGNAL(callback(ClipReader*,qint32,qint32)), _manager, SLOT(clipCallback(ClipReader*,qint32,qint32))); +} + +void ClipReadManager::append(ClipReader *reader, const FileLocation &location, const QByteArray &data) { + reader->_private = new ClipReaderPrivate(reader, location, data); + _loadLevel.fetchAndAddRelaxed(AverageGifSize); + update(reader); +} + +void ClipReadManager::start(ClipReader *reader) { + update(reader); +} + +void ClipReadManager::update(ClipReader *reader) { + QReadLocker lock(&_readerPointersMutex); + ReaderPointers::const_iterator i = _readerPointers.constFind(reader); + if (i == _readerPointers.cend()) { + lock.unlock(); + + QWriteLocker lock(&_readerPointersMutex); + _readerPointers.insert(reader, MutableAtomicInt(1)); + } else { + i->v.storeRelease(1); + } + emit processDelayed(); +} + +void ClipReadManager::stop(ClipReader *reader) { + if (!carries(reader)) return; + + QWriteLocker lock(&_readerPointersMutex); + _readerPointers.remove(reader); + emit processDelayed(); +} + +bool ClipReadManager::carries(ClipReader *reader) const { + QReadLocker lock(&_readerPointersMutex); + return _readerPointers.contains(reader); +} + +ClipReadManager::ReaderPointers::iterator ClipReadManager::unsafeFindReaderPointer(ClipReaderPrivate *reader) { + ReaderPointers::iterator it = _readerPointers.find(reader->_interface); + + // could be a new reader which was realloced in the same address + return (it == _readerPointers.cend() || it.key()->_private == reader) ? it : _readerPointers.end(); +} + +ClipReadManager::ReaderPointers::const_iterator ClipReadManager::constUnsafeFindReaderPointer(ClipReaderPrivate *reader) const { + ReaderPointers::const_iterator it = _readerPointers.constFind(reader->_interface); + + // could be a new reader which was realloced in the same address + return (it == _readerPointers.cend() || it.key()->_private == reader) ? it : _readerPointers.cend(); +} + +bool ClipReadManager::handleProcessResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms) { + QReadLocker lock(&_readerPointersMutex); + ReaderPointers::const_iterator it = constUnsafeFindReaderPointer(reader); + if (result == ClipProcessError) { + if (it != _readerPointers.cend()) { + it.key()->error(); + emit callback(it.key(), it.key()->threadIndex(), ClipReaderReinit); + + lock.unlock(); + QWriteLocker lock(&_readerPointersMutex); + ReaderPointers::iterator i = unsafeFindReaderPointer(reader); + if (i != _readerPointers.cend()) _readerPointers.erase(i); + } + return false; + } + if (it == _readerPointers.cend()) { + return false; + } + + if (result == ClipProcessStarted) { + _loadLevel.fetchAndAddRelaxed(reader->_width * reader->_height - AverageGifSize); + } + if (!reader->_paused && result == ClipProcessRepaint) { + int32 ishowing, iprevious; + ClipReader::Frame *showing = it.key()->frameToShow(&ishowing), *previous = it.key()->frameToWriteNext(false, &iprevious); + t_assert(previous != 0 && showing != 0 && ishowing >= 0 && iprevious >= 0); + if (reader->_frames[ishowing].when > 0 && showing->displayed.loadAcquire() <= 0) { // current frame was not shown + if (reader->_frames[ishowing].when + WaitBeforeGifPause < ms || (reader->_frames[iprevious].when && previous->displayed.loadAcquire() <= 0)) { + reader->_paused = true; + it.key()->_paused.storeRelease(1); + result = ClipProcessPaused; + } + } + } + if (result == ClipProcessStarted || result == ClipProcessCopyFrame) { + t_assert(reader->_frame >= 0); + ClipReader::Frame *frame = it.key()->_frames + reader->_frame; + frame->clear(); + frame->pix = reader->frame()->pix; + frame->original = reader->frame()->original; + frame->displayed.storeRelease(0); + if (result == ClipProcessStarted) { + reader->_nextFrameWhen = ms; + it.key()->moveToNextWrite(); + emit callback(it.key(), it.key()->threadIndex(), ClipReaderReinit); + } + } else if (result == ClipProcessPaused) { + it.key()->moveToNextWrite(); + emit callback(it.key(), it.key()->threadIndex(), ClipReaderReinit); + } else if (result == ClipProcessRepaint) { + it.key()->moveToNextWrite(); + emit callback(it.key(), it.key()->threadIndex(), ClipReaderRepaint); } return true; } -void AnimatedGif::start(HistoryItem *row, const FileLocation &f) { - stop(); - - file = new FileLocation(f); - if (!file->accessEnable()) { - stop(); - return; - } - access = true; - - reader = new QImageReader(file->name()); - if (!reader->canRead() || !reader->supportsAnimation()) { - stop(); - return; +ClipReadManager::ResultHandleState ClipReadManager::handleResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms) { + if (!handleProcessResult(reader, result, ms)) { + _loadLevel.fetchAndAddRelaxed(-1 * (reader->_width > 0 ? reader->_width * reader->_height : AverageGifSize)); + delete reader; + return ResultHandleRemove; } - QSize s = reader->size(); - w = s.width(); - h = s.height(); - framesCount = reader->imageCount(); - if (!w || !h || !framesCount) { - stop(); - return; + _processingInThread->eventDispatcher()->processEvents(QEventLoop::AllEvents); + if (_processingInThread->isInterruptionRequested()) { + return ResultHandleStop; } - frames.reserve(framesCount); - images.reserve(framesCount); - delays.reserve(framesCount); - - int32 sizeLeft = MediaViewImageSizeLimit, delay = 0; - for (bool read = reader->read(&img); read; read = reader->read(&img)) { - sizeLeft -= w * h * 4; - if (img.size() != s) img = img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - images.push_back(img); - frames.push_back(QPixmap()); - int32 d = reader->nextImageDelay(); - if (!d) d = 1; - delay += d; - delays.push_back(delay); - if (sizeLeft < 0) break; - } - - msg = row; - - anim::start(this); - if (msg) { - msg->initDimensions(); - if (App::main()) App::main()->itemResized(msg, true); - } -} - -void AnimatedGif::stop(bool onItemRemoved) { - if (file) { - if (access) { - file->accessDisable(); + if (result == ClipProcessRepaint) { + { + QReadLocker lock(&_readerPointersMutex); + ReaderPointers::const_iterator it = constUnsafeFindReaderPointer(reader); + if (it != _readerPointers.cend()) { + int32 index = 0; + ClipReader *r = it.key(); + ClipReader::Frame *frame = it.key()->frameToWrite(&index); + if (frame) { + frame->clear(); + } else { + t_assert(!reader->_request.valid()); + } + reader->_frame = index; + } } - delete file; - file = 0; + return handleResult(reader, reader->finishProcess(ms), ms); } - access = false; - if (isNull()) return; + return ResultHandleContinue; +} +void ClipReadManager::process() { + if (_processingInThread) { + _needReProcess = true; + return; + } + + _timer.stop(); + _processingInThread = thread(); + + uint64 ms = getms(), minms = ms + 86400 * 1000ULL; + { + QReadLocker lock(&_readerPointersMutex); + for (ReaderPointers::iterator it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) { + if (it->v.loadAcquire()) { + Readers::iterator i = _readers.find(it.key()->_private); + if (i == _readers.cend()) { + _readers.insert(it.key()->_private, 0); + } else { + i.value() = ms; + if (i.key()->_paused && !it.key()->_paused.loadAcquire()) { + i.key()->_paused = false; + } + } + ClipReader::Frame *frame = it.key()->frameToWrite(); + if (frame) it.key()->_private->_request = frame->request; + it->v.storeRelease(0); + } + } + } + + for (Readers::iterator i = _readers.begin(), e = _readers.end(); i != e;) { + ClipReaderPrivate *reader = i.key(); + if (i.value() <= ms) { + ResultHandleState state = handleResult(reader, reader->process(ms), ms); + if (state == ResultHandleRemove) { + i = _readers.erase(i); + continue; + } else if (state == ResultHandleStop) { + _processingInThread = 0; + return; + } + ms = getms(); + i.value() = reader->_nextFrameWhen ? reader->_nextFrameWhen : (ms + 86400 * 1000ULL); + } + if (!reader->_paused && i.value() < minms) { + minms = i.value(); + } + ++i; + } + + ms = getms(); + if (_needReProcess || minms <= ms) { + _needReProcess = false; + _timer.start(1); + } else { + _timer.start(minms - ms); + } + + _processingInThread = 0; +} + +void ClipReadManager::finish() { + _timer.stop(); + clear(); +} + +void ClipReadManager::clear() { + { + QWriteLocker lock(&_readerPointersMutex); + for (ReaderPointers::iterator it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) { + it.key()->_private = 0; + } + _readerPointers.clear(); + } + + for (Readers::iterator i = _readers.begin(), e = _readers.end(); i != e; ++i) { + delete i.key(); + } + _readers.clear(); +} + +ClipReadManager::~ClipReadManager() { + clear(); +} + +MTPDocumentAttribute clipReadAnimatedAttributes(const QString &fname, const QByteArray &data, QImage &cover) { + FileLocation localloc(StorageFilePartial, fname); + QByteArray localdata(data); + + FFMpegReaderImplementation *reader = new FFMpegReaderImplementation(&localloc, &localdata); + if (reader->start(true)) { + bool hasAlpha = false; + if (reader->readNextFrame() && reader->renderFrame(cover, hasAlpha, QSize())) { + if (cover.width() > 0 && cover.height() > 0 && cover.width() < cover.height() * 10 && cover.height() < cover.width() * 10) { + if (hasAlpha) { + QImage cacheForResize; + ClipFrameRequest request; + request.framew = request.outerw = cover.width(); + request.frameh = request.outerh = cover.height(); + request.factor = 1; + cover = _prepareFrame(request, cover, hasAlpha, cacheForResize).toImage(); + } + int32 duration = reader->duration(); + delete reader; + return MTP_documentAttributeVideo(MTP_int(duration), MTP_int(cover.width()), MTP_int(cover.height())); + } + } + } delete reader; - reader = 0; - HistoryItem *row = msg; - msg = 0; - frames.clear(); - images.clear(); - delays.clear(); - w = h = frame = framesCount = duration = 0; - - anim::stop(this); - if (row && !onItemRemoved) { - row->initDimensions(); - if (App::main()) App::main()->itemResized(row, true); - } -} - -const QPixmap &AnimatedGif::current(int32 width, int32 height, bool rounded) { - if (!width) width = w; - if (!height) height = h; - if ((frames[frame].isNull() || frames[frame].width() != width || frames[frame].height() != height) && !images[frame].isNull()) { - QImage img = images[frame]; - if (img.width() != width || img.height() != height) img = img.scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - if (rounded) imageRound(img); - frames[frame] = QPixmap::fromImage(img, Qt::ColorOnly); - frames[frame].setDevicePixelRatio(cRetinaFactor()); - } - return frames[frame]; + return MTP_documentAttributeFilename(MTP_string(fname)); } diff --git a/Telegram/SourceFiles/gui/animation.h b/Telegram/SourceFiles/gui/animation.h index 4f92671edc..b3aa52afa0 100644 --- a/Telegram/SourceFiles/gui/animation.h +++ b/Telegram/SourceFiles/gui/animation.h @@ -24,12 +24,10 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include #include -class Animated; - namespace anim { typedef float64 (*transition)(const float64 &delta, const float64 &dt); - + float64 linear(const float64 &delta, const float64 &dt); float64 sineInOut(const float64 &delta, const float64 &dt); float64 halfSine(const float64 &delta, const float64 &dt); @@ -74,6 +72,8 @@ namespace anim { _delta = 0; } + typedef float64 Type; + private: float64 _cur, _from, _delta; @@ -112,6 +112,8 @@ namespace anim { _delta = 0; } + typedef int32 Type; + private: int32 _cur; @@ -181,253 +183,486 @@ namespace anim { _delta_r = _delta_g = _delta_b = _delta_a = 0; } + typedef QColor Type; + private: QColor _cur; float64 _from_r, _from_g, _from_b, _from_a, _delta_r, _delta_g, _delta_b, _delta_a; }; - void start(Animated *obj); - void step(Animated *obj); - void stop(Animated *obj); - void startManager(); void stopManager(); }; -class Animated { +class Animation; + +class AnimationImplementation { public: + virtual void start() {} + virtual void step(Animation *a, uint64 ms, bool timer) = 0; + virtual ~AnimationImplementation() {} +}; +class AnimationCreator { +public: + AnimationCreator(AnimationImplementation *ptr) : _ptr(ptr) {} + AnimationCreator(const AnimationCreator &other) : _ptr(other.create()) {} + AnimationImplementation *create() const { return exchange(_ptr); } + ~AnimationCreator() { deleteAndMark(_ptr); } +private: + AnimationCreator &operator=(const AnimationCreator &other); + mutable AnimationImplementation *_ptr; +}; +class AnimationCallbacks { +public: + AnimationCallbacks(const AnimationCreator &creator) : _implementation(creator.create()) {} + void start() { _implementation->start(); } + void step(Animation *a, uint64 ms, bool timer) { _implementation->step(a, ms, timer); } + ~AnimationCallbacks() { deleteAndMark(_implementation); } +private: + AnimationCallbacks(const AnimationCallbacks &other); + AnimationCallbacks &operator=(const AnimationCallbacks &other); + AnimationImplementation *_implementation; +}; - Animated() : animStarted(0), animInProcess(false) { +class Animation { +public: + Animation(AnimationCreator cb) : _cb(cb), _animating(false) { } - virtual bool animStep(float64 ms) = 0; + void start(); + void stop(); - void animReset() { - animStarted = float64(getms()); + void step(uint64 ms, bool timer = false) { + _cb.step(this, ms, timer); } - virtual ~Animated() { - if (animating()) { - anim::stop(this); - } + void step() { + step(getms(), false); } bool animating() const { - return animInProcess; - } - -private: - - float64 animStarted; - bool animInProcess; - friend class AnimationManager; - -}; - -class AnimationFunc { -public: - virtual bool animStep(float64 ms) = 0; - virtual ~AnimationFunc() { - } -}; - -template -class AnimationFuncOwned : public AnimationFunc { -public: - typedef bool (Type::*Method)(float64); - - AnimationFuncOwned(Type *obj, Method method) : _obj(obj), _method(method) { - } - - bool animStep(float64 ms) { - return (_obj->*_method)(ms); - } - -private: - Type *_obj; - Method _method; - -}; - -template -AnimationFunc *animFunc(Type *obj, typename AnimationFuncOwned::Method method) { - return new AnimationFuncOwned(obj, method); -} - -class Animation : public Animated { -public: - - Animation(AnimationFunc *func) : _func(func) { - } - - void start() { - anim::start(this); - } - void stop() { - anim::stop(this); - } - - //Animation - bool animStep(float64 ms) { - return _func->animStep(ms); + return _animating; } ~Animation() { - delete _func; + if (_animating) stop(); } private: - AnimationFunc *_func; + AnimationCallbacks _cb; + bool _animating; }; +template +class AnimationCallbacksRelative : public AnimationImplementation { +public: + typedef void (Type::*Method)(float64, bool); + + AnimationCallbacksRelative(Type *obj, Method method) : _started(0), _obj(obj), _method(method) { + } + + void start() { + _started = float64(getms()); + } + + void step(Animation *a, uint64 ms, bool timer) { + (_obj->*_method)(ms - _started, timer); + } + +private: + float64 _started; + Type *_obj; + Method _method; + +}; +template +AnimationCreator animation(Type *obj, typename AnimationCallbacksRelative::Method method) { + return AnimationCreator(new AnimationCallbacksRelative(obj, method)); +} + +template +class AnimationCallbacksAbsolute : public AnimationImplementation { +public: + typedef void (Type::*Method)(uint64, bool); + + AnimationCallbacksAbsolute(Type *obj, Method method) : _obj(obj), _method(method) { + } + + void step(Animation *a, uint64 ms, bool timer) { + (_obj->*_method)(ms, timer); + } + +private: + Type *_obj; + Method _method; + +}; +template +AnimationCreator animation(Type *obj, typename AnimationCallbacksAbsolute::Method method) { + return AnimationCreator(new AnimationCallbacksAbsolute(obj, method)); +} + +template +class AnimationCallbacksRelativeWithParam : public AnimationImplementation { +public: + typedef void (Type::*Method)(Param, float64, bool); + + AnimationCallbacksRelativeWithParam(Param param, Type *obj, Method method) : _started(0), _param(param), _obj(obj), _method(method) { + } + + void start() { + _started = float64(getms()); + } + + void step(Animation *a, uint64 ms, bool timer) { + (_obj->*_method)(_param, ms - _started, timer); + } + +private: + float64 _started; + Param _param; + Type *_obj; + Method _method; + +}; +template +AnimationCreator animation(Param param, Type *obj, typename AnimationCallbacksRelativeWithParam::Method method) { + return AnimationCreator(new AnimationCallbacksRelativeWithParam(param, obj, method)); +} + +template +class AnimationCallbacksAbsoluteWithParam : public AnimationImplementation { +public: + typedef void (Type::*Method)(Param, uint64, bool); + + AnimationCallbacksAbsoluteWithParam(Param param, Type *obj, Method method) : _param(param), _obj(obj), _method(method) { + } + + void step(Animation *a, uint64 ms, bool timer) { + (_obj->*_method)(_param, ms, timer); + } + +private: + Param _param; + Type *_obj; + Method _method; + +}; +template +AnimationCreator animation(Param param, Type *obj, typename AnimationCallbacksAbsoluteWithParam::Method method) { + return AnimationCreator(new AnimationCallbacksAbsoluteWithParam(param, obj, method)); +} + +template +class SimpleAnimation { +public: + + typedef Function Callback; + + SimpleAnimation() : _data(0) { + } + + bool animating(uint64 ms) { + if (_data && _data->_a.animating()) { + _data->_a.step(ms); + return _data && _data->_a.animating(); + } + return false; + } + + bool isNull() { + return !_data; + } + + typename AnimType::Type current() { + return _data ? _data->a.current() : typename AnimType::Type(); + } + + typename AnimType::Type current(uint64 ms, const typename AnimType::Type &def) { + return animating(ms) ? current() : def; + } + + void setup(const typename AnimType::Type &from, Callback::Creator update) { + if (!_data) { + _data = new Data(from, update, animation(this, &SimpleAnimation::step)); + } else { + _data->a = AnimType(from, from); + } + } + + void start(const typename AnimType::Type &to, float64 duration, anim::transition transition = anim::linear) { + if (_data) { + _data->a.start(to); + _data->_a.start(); + _data->duration = duration; + _data->transition = transition; + } + } + + ~SimpleAnimation() { + deleteAndMark(_data); + } + +private: + typedef struct _Data { + _Data(const typename AnimType::Type &from, Callback::Creator update, AnimationCreator acb) + : a(from, from) + , _a(acb) + , update(update) + , duration(0) + , transition(anim::linear) { + } + AnimType a; + Animation _a; + Callback update; + float64 duration; + anim::transition transition; + } Data; + Data *_data; + + void step(float64 ms, bool timer) { + float64 dt = (ms >= _data->duration) ? 1 : (ms / _data->duration); + if (dt >= 1) { + _data->a.finish(); + _data->_a.stop(); + } else { + _data->a.update(dt, _data->transition); + } + if (timer) { + _data->update.call(); + } + if (!_data->_a.animating()) { + delete _data; + _data = 0; + } + } + +}; + +typedef SimpleAnimation FloatAnimation; +typedef SimpleAnimation IntAnimation; +typedef SimpleAnimation ColorAnimation; + +#define EnsureAnimation(animation, from, callback) if ((animation).isNull()) { (animation).setup((from), (callback)); } + +class ClipReader; + class AnimationManager : public QObject { Q_OBJECT public: + AnimationManager(); - AnimationManager() : timer(this), iterating(false) { - timer.setSingleShot(false); - connect(&timer, SIGNAL(timeout()), this, SLOT(timeout())); - } - - void start(Animated *obj) { - obj->animReset(); - if (iterating) { - toStart.insert(obj); - if (!toStop.isEmpty()) { - toStop.remove(obj); - } - } else { - if (!objs.size()) { - timer.start(AnimationTimerDelta); - } - objs.insert(obj); - } - obj->animInProcess = true; - } - - void step(Animated *obj) { - if (iterating) return; - - float64 ms = float64(getms()); - AnimObjs::iterator i = objs.find(obj); - if (i != objs.cend()) { - Animated *obj = *i; - if (!obj->animStep(ms - obj->animStarted)) { - objs.erase(i); - if (!objs.size()) { - timer.stop(); - } - obj->animInProcess = false; - } - } - } - - void stop(Animated *obj) { - if (iterating) { - toStop.insert(obj); - if (!toStart.isEmpty()) { - toStart.insert(obj); - } - } else { - AnimObjs::iterator i = objs.find(obj); - if (i != objs.cend()) { - objs.erase(i); - if (!objs.size()) { - timer.stop(); - } - } - } - obj->animInProcess = false; - } + void start(Animation *obj); + void stop(Animation *obj); public slots: - void timeout() { - iterating = true; - float64 ms = float64(getms()); - for (AnimObjs::iterator i = objs.begin(), e = objs.end(); i != e; ) { - Animated *obj = *i; - if (!obj->animStep(ms - obj->animStarted)) { - i = objs.erase(i); - obj->animInProcess = false; - } else { - ++i; - } - } - iterating = false; - if (!toStart.isEmpty()) { - for (AnimObjs::iterator i = toStart.begin(), e = toStart.end(); i != e; ++i) { - objs.insert(*i); - } - toStart.clear(); - } - if (!toStop.isEmpty()) { - for (AnimObjs::iterator i = toStop.begin(), e = toStop.end(); i != e; ++i) { - objs.remove(*i); - } - toStop.clear(); - } - if (!objs.size()) { - timer.stop(); - } - } + void timeout(); + + void clipCallback(ClipReader *reader, qint32 threadIndex, qint32 notification); private: - - typedef QSet AnimObjs; - AnimObjs objs; - AnimObjs toStart; - AnimObjs toStop; - QTimer timer; - bool iterating; + typedef QMap AnimatingObjects; + AnimatingObjects _objects, _starting, _stopping; + QTimer _timer; + bool _iterating; }; -class HistoryItem; class FileLocation; -class AnimatedGif : public QObject, public Animated { + +enum ClipState { + ClipReading, + ClipError, +}; + +struct ClipFrameRequest { + ClipFrameRequest() : factor(0), framew(0), frameh(0), outerw(0), outerh(0), rounded(false) { + } + bool valid() const { + return factor > 0; + } + int32 factor; + int32 framew, frameh; + int32 outerw, outerh; + bool rounded; +}; + +enum ClipReaderNotification { + ClipReaderReinit, + ClipReaderRepaint, +}; + +enum ClipReaderSteps { + WaitingForDimensionsStep = -3, // before ClipReaderPrivate read the first image and got the original frame size + WaitingForRequestStep = -2, // before ClipReader got the original frame size and prepared the frame request + WaitingForFirstFrameStep = -1, // before ClipReaderPrivate got the frame request and started waiting for the 1-2 delay +}; + +class ClipReaderPrivate; +class ClipReader { +public: + + typedef Function1 Callback; + + ClipReader(const FileLocation &location, const QByteArray &data, Callback::Creator cb); + static void callback(ClipReader *reader, int32 threadIndex, ClipReaderNotification notification); // reader can be deleted + + void setAutoplay() { + _autoplay = true; + } + bool autoplay() const { + return _autoplay; + } + + void start(int32 framew, int32 frameh, int32 outerw, int32 outerh, bool rounded); + QPixmap current(int32 framew, int32 frameh, int32 outerw, int32 outerh, uint64 ms); + QPixmap frameOriginal() const { + Frame *frame = frameToShow(); + if (!frame) return QPixmap(); + QPixmap result(frame ? QPixmap::fromImage(frame->original) : QPixmap()); + result.detach(); + return result; + } + bool currentDisplayed() const { + Frame *frame = frameToShow(); + return frame ? (frame->displayed.loadAcquire() != 0) : true; + } + bool paused() const { + return _paused.loadAcquire(); + } + int32 threadIndex() const { + return _threadIndex; + } + + int32 width() const; + int32 height() const; + + ClipState state() const; + bool started() const { + int32 step = _step.loadAcquire(); + return (step == WaitingForFirstFrameStep) || (step >= 0); + } + bool ready() const; + + void stop(); + void error(); + + ~ClipReader(); + +private: + + Callback _cb; + + ClipState _state; + + mutable int32 _width, _height; + + mutable QAtomicInt _step; // -2, -1 - init, 0-5 - work, show ((state + 1) / 2) % 3 state, write ((state + 3) / 2) % 3 + struct Frame { + Frame() : displayed(false) { + } + void clear() { + pix = QPixmap(); + original = QImage(); + } + QPixmap pix; + QImage original; + ClipFrameRequest request; + QAtomicInt displayed; + }; + mutable Frame _frames[3]; + Frame *frameToShow(int32 *index = 0) const; // 0 means not ready + Frame *frameToWrite(int32 *index = 0) const; // 0 means not ready + Frame *frameToWriteNext(bool check, int32 *index = 0) const; + void moveToNextShow() const; + void moveToNextWrite() const; + + QAtomicInt _paused; + int32 _threadIndex; + + bool _autoplay; + + friend class ClipReadManager; + + ClipReaderPrivate *_private; + +}; + +static ClipReader * const BadClipReader = SharedMemoryLocation(); + +enum ClipProcessResult { + ClipProcessError, + ClipProcessStarted, + ClipProcessPaused, + ClipProcessRepaint, + ClipProcessCopyFrame, + ClipProcessWait, +}; + +class ClipReadManager : public QObject { Q_OBJECT public: - AnimatedGif() : msg(0), file(0), access(false), reader(0), w(0), h(0), frame(0), framesCount(0), duration(0) { + ClipReadManager(QThread *thread); + int32 loadLevel() const { + return _loadLevel.load(); } - - bool animStep(float64 ms); - - void start(HistoryItem *row, const FileLocation &file); - void stop(bool onItemRemoved = false); - - bool isNull() const { - return !reader; - } - - ~AnimatedGif() { - stop(true); - } - - const QPixmap ¤t(int32 width = 0, int32 height = 0, bool rounded = false); + void append(ClipReader *reader, const FileLocation &location, const QByteArray &data); + void start(ClipReader *reader); + void update(ClipReader *reader); + void stop(ClipReader *reader); + bool carries(ClipReader *reader) const; + ~ClipReadManager(); signals: - void updated(); + void processDelayed(); -public: + void callback(ClipReader *reader, qint32 threadIndex, qint32 notification); - HistoryItem *msg; - QImage img; - FileLocation *file; - bool access; - QImageReader *reader; - int32 w, h, frame; +public slots: + + void process(); + void finish(); private: - QVector frames; - QVector images; - QVector delays; - int32 framesCount, duration; + void clear(); + + QAtomicInt _loadLevel; + struct MutableAtomicInt { + MutableAtomicInt(int value) : v(value) { + } + mutable QAtomicInt v; + }; + typedef QMap ReaderPointers; + ReaderPointers _readerPointers; + mutable QReadWriteLock _readerPointersMutex; + + ReaderPointers::const_iterator constUnsafeFindReaderPointer(ClipReaderPrivate *reader) const; + ReaderPointers::iterator unsafeFindReaderPointer(ClipReaderPrivate *reader); + + bool handleProcessResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms); + + enum ResultHandleState { + ResultHandleRemove, + ResultHandleStop, + ResultHandleContinue, + }; + ResultHandleState handleResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms); + + typedef QMap Readers; + Readers _readers; + + QTimer _timer; + QThread *_processingInThread; + bool _needReProcess; + }; + +MTPDocumentAttribute clipReadAnimatedAttributes(const QString &fname, const QByteArray &data, QImage &cover); diff --git a/Telegram/SourceFiles/gui/countryinput.cpp b/Telegram/SourceFiles/gui/countryinput.cpp index 53719fa5c9..5405737503 100644 --- a/Telegram/SourceFiles/gui/countryinput.cpp +++ b/Telegram/SourceFiles/gui/countryinput.cpp @@ -137,7 +137,7 @@ void CountryInput::mousePressEvent(QMouseEvent *e) { if (_active) { CountrySelectBox *box = new CountrySelectBox(); connect(box, SIGNAL(countryChosen(const QString&)), this, SLOT(onChooseCountry(const QString&))); - App::wnd()->showLayer(box); + Ui::showLayer(box); } } @@ -152,7 +152,7 @@ void CountryInput::leaveEvent(QEvent *e) { } void CountryInput::onChooseCode(const QString &code) { - App::wnd()->hideLayer(); + Ui::hideLayer(); if (code.length()) { CountriesByCode::const_iterator i = _countriesByCode.constFind(code); if (i != _countriesByCode.cend()) { @@ -169,7 +169,7 @@ void CountryInput::onChooseCode(const QString &code) { } bool CountryInput::onChooseCountry(const QString &iso) { - App::wnd()->hideLayer(); + Ui::hideLayer(); CountriesByISO2::const_iterator i = _countriesByISO2.constFind(iso); const CountryInfo *info = (i == _countriesByISO2.cend()) ? 0 : (*i); diff --git a/Telegram/SourceFiles/gui/emoji_config.h b/Telegram/SourceFiles/gui/emoji_config.h index e377374ba1..1734d3d827 100644 --- a/Telegram/SourceFiles/gui/emoji_config.h +++ b/Telegram/SourceFiles/gui/emoji_config.h @@ -83,7 +83,7 @@ inline EmojiPtr emojiFromUrl(const QString &url) { return emojiFromKey(url.midRef(10).toULongLong(0, 16)); // skip emoji://e. } -inline EmojiPtr emojiFromText(const QChar *ch, const QChar *e, int &len) { +inline EmojiPtr emojiFromText(const QChar *ch, const QChar *e, int *plen = 0) { EmojiPtr emoji = 0; if (ch + 1 < e && ((ch->isHighSurrogate() && (ch + 1)->isLowSurrogate()) || (((ch->unicode() >= 0x30 && ch->unicode() < 0x3A) || ch->unicode() == 0x23 || ch->unicode() == 0x2A) && (ch + 1)->unicode() == 0x20E3))) { uint32 code = (ch->unicode() << 16) | (ch + 1)->unicode(); @@ -108,15 +108,15 @@ inline EmojiPtr emojiFromText(const QChar *ch, const QChar *e, int &len) { } else if (ch + 2 < e && ((ch->unicode() >= 0x30 && ch->unicode() < 0x3A) || ch->unicode() == 0x23 || ch->unicode() == 0x2A) && (ch + 1)->unicode() == 0xFE0F && (ch + 2)->unicode() == 0x20E3) { uint32 code = (ch->unicode() << 16) | (ch + 2)->unicode(); emoji = emojiGet(code); - len = emoji->len + 1; + if (plen) *plen = emoji->len + 1; return emoji; } else if (ch < e) { emoji = emojiGet(ch->unicode()); - Q_ASSERT(emoji != TwoSymbolEmoji); + t_assert(emoji != TwoSymbolEmoji); } if (emoji) { - len = emoji->len + ((ch + emoji->len < e && (ch + emoji->len)->unicode() == 0xFE0F) ? 1 : 0); + int32 len = emoji->len + ((ch + emoji->len < e && (ch + emoji->len)->unicode() == 0xFE0F) ? 1 : 0); if (emoji->color && (ch + len + 1 < e && (ch + len)->isHighSurrogate() && (ch + len + 1)->isLowSurrogate())) { // color uint32 color = ((uint32((ch + len)->unicode()) << 16) | uint32((ch + len + 1)->unicode())); EmojiPtr col = emojiGet(emoji, color); @@ -128,8 +128,21 @@ inline EmojiPtr emojiFromText(const QChar *ch, const QChar *e, int &len) { } } } + if (plen) *plen = len; + } + + return emoji; +} + +inline EmojiPtr emojiFromText(const QString &text, int32 *plen = 0) { + return text.isEmpty() ? EmojiPtr(0) : emojiFromText(text.constBegin(), text.constEnd(), plen); +} + +inline EmojiPtr emojiGetNoColor(EmojiPtr emoji) { + if (emoji && emoji->color && (emoji->color & 0xFFFF0000U) != 0xFFFF0000U) { + EmojiPtr result = emojiGet(emoji->code); + return (result == TwoSymbolEmoji) ? emojiGet(emoji->code, emoji->code2) : result; } - return emoji; } @@ -180,7 +193,7 @@ inline QString replaceEmojis(const QString &text, EntitiesInText &entities) { if (canFindEmoji) { emojiFind(ch, e, newEmojiEnd, emojiCode); } - + while (currentEntity < entitiesCount && ch >= emojiStart + entities[currentEntity].offset + entities[currentEntity].length) { ++currentEntity; } diff --git a/Telegram/SourceFiles/gui/flatbutton.cpp b/Telegram/SourceFiles/gui/flatbutton.cpp index e881a5af11..1df0ac6642 100644 --- a/Telegram/SourceFiles/gui/flatbutton.cpp +++ b/Telegram/SourceFiles/gui/flatbutton.cpp @@ -21,10 +21,14 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "gui/flatbutton.h" -FlatButton::FlatButton(QWidget *parent, const QString &text, const style::flatButton &st) : Button(parent), - _text(text), - _st(st), _autoFontPadding(0), - a_bg(st.bgColor->c), a_text(st.color->c), _opacity(1) { +FlatButton::FlatButton(QWidget *parent, const QString &text, const style::flatButton &st) : Button(parent) +, _text(text) +, _st(st) +, _autoFontPadding(0) +, a_bg(st.bgColor->c) +, a_text(st.color->c) +, _a_appearance(animation(this, &FlatButton::step_appearance)) +, _opacity(1) { if (_st.width < 0) { _st.width = textWidth() - _st.width; } else if (!_st.width) { @@ -88,19 +92,17 @@ void FlatButton::resizeEvent(QResizeEvent *e) { return Button::resizeEvent(e); } -bool FlatButton::animStep(float64 ms) { +void FlatButton::step_appearance(float64 ms, bool timer) { float64 dt = ms / _st.duration; - bool res = true; if (dt >= 1) { + _a_appearance.stop(); a_bg.finish(); a_text.finish(); - res = false; } else { a_bg.update(dt, anim::linear); a_text.update(dt, anim::linear); } - update(); - return res; + if (timer) update(); } void FlatButton::onStateChange(int oldState, ButtonStateChangeSource source) { @@ -110,12 +112,12 @@ void FlatButton::onStateChange(int oldState, ButtonStateChangeSource source) { a_bg.start(bgColorTo->c); a_text.start(colorTo->c); if (source == ButtonByUser || source == ButtonByPress) { - anim::stop(this); + _a_appearance.stop(); a_bg.finish(); a_text.finish(); update(); } else { - anim::start(this); + _a_appearance.start(); } } @@ -164,8 +166,14 @@ void LinkButton::onStateChange(int oldState, ButtonStateChangeSource source) { LinkButton::~LinkButton() { } -IconedButton::IconedButton(QWidget *parent, const style::iconedButton &st, const QString &text) : Button(parent), - _text(text), _st(st), _width(_st.width), a_opacity(_st.opacity), a_bg(_st.bgColor->c), _opacity(1) { +IconedButton::IconedButton(QWidget *parent, const style::iconedButton &st, const QString &text) : Button(parent) +, _text(text) +, _st(st) +, _width(_st.width) +, a_opacity(_st.opacity) +, a_bg(_st.bgColor->c) +, _a_appearance(animation(this, &IconedButton::step_appearance)) +, _opacity(1) { if (_width < 0) { _width = _st.font->width(text) - _width; @@ -199,25 +207,23 @@ QString IconedButton::getText() const { return _text; } -bool IconedButton::animStep(float64 ms) { - bool res = true; +void IconedButton::step_appearance(float64 ms, bool timer) { if (_st.duration <= 1) { + _a_appearance.stop(); a_opacity.finish(); a_bg.finish(); - res = false; } else { float64 dt = ms / _st.duration; if (dt >= 1) { + _a_appearance.stop(); a_opacity.finish(); a_bg.finish(); - res = false; } else { a_opacity.update(dt, anim::linear); a_bg.update(dt, anim::linear); } } - update(); - return res; + if (timer) update(); } void IconedButton::onStateChange(int oldState, ButtonStateChangeSource source) { @@ -225,12 +231,12 @@ void IconedButton::onStateChange(int oldState, ButtonStateChangeSource source) { a_bg.start(((_state & (StateOver | StateDown)) ? _st.overBgColor : _st.bgColor)->c); if (source == ButtonByUser || source == ButtonByPress) { - anim::stop(this); + _a_appearance.stop(); a_opacity.finish(); a_bg.finish(); update(); } else { - anim::start(this); + _a_appearance.start(); } } @@ -283,10 +289,65 @@ void MaskedButton::paintEvent(QPaintEvent *e) { } } -BoxButton::BoxButton(QWidget *parent, const QString &text, const style::BoxButton &st) : Button(parent), -_text(text.toUpper()), _fullText(text.toUpper()), _textWidth(st.font->width(_text)), -_st(st), -a_textBgOverOpacity(0), a_textFg(st.textFg->c), _a_over(animFunc(this, &BoxButton::animStep_over)) { +EmojiButton::EmojiButton(QWidget *parent, const style::iconedButton &st) : IconedButton(parent, st) +, _loading(false) +, _a_loading(animation(this, &EmojiButton::step_loading)) { +} + +void EmojiButton::paintEvent(QPaintEvent *e) { + QPainter p(this); + + uint64 ms = getms(); + float64 loading = a_loading.current(ms, _loading ? 1 : 0); + p.setOpacity(_opacity * (1 - loading)); + + p.fillRect(e->rect(), a_bg.current()); + + p.setOpacity(a_opacity.current() * _opacity * (1 - loading)); + + const QRect &i((_state & StateDown) ? _st.downIcon : _st.icon); + if (i.width()) { + const QPoint &t((_state & StateDown) ? _st.downIconPos : _st.iconPos); + p.drawPixmap(t, App::sprite(), i); + } + + p.setOpacity(a_opacity.current() * _opacity); + p.setPen(QPen(st::emojiCircleFg, st::emojiCircleLine)); + p.setBrush(Qt::NoBrush); + + p.setRenderHint(QPainter::HighQualityAntialiasing); + QRect inner(QPoint((width() - st::emojiCircle.width()) / 2, st::emojiCircleTop), st::emojiCircle); + if (loading > 0) { + int32 full = 5760; + int32 start = qRound(full * float64(ms % uint64(st::emojiCirclePeriod)) / st::emojiCirclePeriod), part = qRound(loading * full / st::emojiCirclePart); + p.drawArc(inner, start, full - part); + } else { + p.drawEllipse(inner); + } + p.setRenderHint(QPainter::HighQualityAntialiasing, false); +} + +void EmojiButton::setLoading(bool loading) { + if (_loading != loading) { + EnsureAnimation(a_loading, _loading ? 1. : 0., func(this, &EmojiButton::update)); + a_loading.start(loading ? 1. : 0., st::emojiCircleDuration); + _loading = loading; + if (_loading) { + _a_loading.start(); + } else { + _a_loading.stop(); + } + } +} + +BoxButton::BoxButton(QWidget *parent, const QString &text, const style::BoxButton &st) : Button(parent) +, _text(text.toUpper()) +, _fullText(text.toUpper()) +, _textWidth(st.font->width(_text)) +, _st(st) +, a_textBgOverOpacity(0) +, a_textFg(st.textFg->c) +, _a_over(animation(this, &BoxButton::step_over)) { if (_st.width <= 0) { resize(_textWidth - _st.width, _st.height); } else { @@ -322,19 +383,17 @@ void BoxButton::paintEvent(QPaintEvent *e) { p.drawText((width() - _textWidth) / 2, _st.textTop + _st.font->ascent, _text); } -bool BoxButton::animStep_over(float64 ms) { +void BoxButton::step_over(float64 ms, bool timer) { float64 dt = ms / _st.duration; - bool res = true; if (dt >= 1) { + _a_over.stop(); a_textFg.finish(); a_textBgOverOpacity.finish(); - res = false; } else { a_textFg.update(dt, anim::linear); a_textBgOverOpacity.update(dt, anim::linear); } - update(); - return res; + if (timer) update(); } void BoxButton::onStateChange(int oldState, ButtonStateChangeSource source) { diff --git a/Telegram/SourceFiles/gui/flatbutton.h b/Telegram/SourceFiles/gui/flatbutton.h index c6f0dc54bc..f07b0c4a25 100644 --- a/Telegram/SourceFiles/gui/flatbutton.h +++ b/Telegram/SourceFiles/gui/flatbutton.h @@ -25,7 +25,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "gui/animation.h" #include "style.h" -class FlatButton : public Button, public Animated { +class FlatButton : public Button { Q_OBJECT public: @@ -34,7 +34,7 @@ public: void resizeEvent(QResizeEvent *e); - bool animStep(float64 ms); + void step_appearance(float64 ms, bool timer); void paintEvent(QPaintEvent *e); void setOpacity(float64 o); float64 opacity() const; @@ -63,7 +63,10 @@ private: style::font _autoFont; anim::cvalue a_bg, a_text; + Animation _a_appearance; + float64 _opacity; + }; class LinkButton : public Button { @@ -89,21 +92,21 @@ private: style::linkButton _st; }; -class IconedButton : public Button, public Animated { +class IconedButton : public Button { Q_OBJECT public: IconedButton(QWidget *parent, const style::iconedButton &st, const QString &text = QString()); - bool animStep(float64 ms); + void step_appearance(float64 ms, bool timer); void paintEvent(QPaintEvent *e); void setOpacity(float64 o); void setText(const QString &text); QString getText() const; - + public slots: void onStateChange(int oldState, ButtonStateChangeSource source); @@ -117,6 +120,7 @@ protected: anim::fvalue a_opacity; anim::cvalue a_bg; + Animation _a_appearance; float64 _opacity; }; @@ -132,6 +136,28 @@ public: }; +class EmojiButton : public IconedButton { + Q_OBJECT + +public: + EmojiButton(QWidget *parent, const style::iconedButton &st); + + void paintEvent(QPaintEvent *e); + void setLoading(bool loading); + +private: + bool _loading; + FloatAnimation a_loading; + Animation _a_loading; + + void step_loading(uint64 ms, bool timer) { + if (timer) { + update(); + } + } + +}; + class BoxButton : public Button { Q_OBJECT @@ -141,7 +167,7 @@ public: void paintEvent(QPaintEvent *e); - bool animStep_over(float64 ms); + void step_over(float64 ms, bool timer); public slots: diff --git a/Telegram/SourceFiles/gui/flatcheckbox.cpp b/Telegram/SourceFiles/gui/flatcheckbox.cpp index 7e4b8225ee..bace80fc7c 100644 --- a/Telegram/SourceFiles/gui/flatcheckbox.cpp +++ b/Telegram/SourceFiles/gui/flatcheckbox.cpp @@ -24,8 +24,13 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "flatcheckbox.h" -FlatCheckbox::FlatCheckbox(QWidget *parent, const QString &text, bool checked, const style::flatCheckbox &st) : Button(parent), - _st(st), a_over(0, 0), _text(text), _opacity(1), _checked(checked) { +FlatCheckbox::FlatCheckbox(QWidget *parent, const QString &text, bool checked, const style::flatCheckbox &st) : Button(parent) +, _st(st) +, a_over(0, 0) +, _a_appearance(animation(this, &FlatCheckbox::step_appearance)) +, _text(text) +, _opacity(1) +, _checked(checked) { connect(this, SIGNAL(clicked()), this, SLOT(onClicked())); connect(this, SIGNAL(stateChanged(int, ButtonStateChangeSource)), this, SLOT(onStateChange(int, ButtonStateChangeSource))); setCursor(_st.cursor); @@ -60,17 +65,17 @@ void FlatCheckbox::onClicked() { void FlatCheckbox::onStateChange(int oldState, ButtonStateChangeSource source) { if ((_state & StateOver) && !(oldState & StateOver)) { a_over.start(1); - anim::start(this); + _a_appearance.start(); } else if (!(_state & StateOver) && (oldState & StateOver)) { a_over.start(0); - anim::start(this); + _a_appearance.start(); } if ((_state & StateDisabled) && !(oldState & StateDisabled)) { setCursor(_st.disabledCursor); - anim::start(this); + _a_appearance.start(); } else if (!(_state & StateDisabled) && (oldState & StateDisabled)) { setCursor(_st.cursor); - anim::start(this); + _a_appearance.start(); } } @@ -114,17 +119,15 @@ void FlatCheckbox::paintEvent(QPaintEvent *e) { } } -bool FlatCheckbox::animStep(float64 ms) { +void FlatCheckbox::step_appearance(float64 ms, bool timer) { float64 dt = ms / _st.duration; - bool res = true; if (dt >= 1) { + _a_appearance.stop(); a_over.finish(); - res = false; } else { a_over.update(dt, _st.bgFunc); } - update(); - return res; + if (timer) update(); } template @@ -135,7 +138,8 @@ public: TemplateRadiobuttonsGroup(const QString &name) : _name(name), _val(0) { } - void remove(Type * const &radio); + void remove(Type * const &radio) { + } int32 val() const { return _val; } @@ -232,12 +236,16 @@ FlatRadiobutton::~FlatRadiobutton() { reinterpret_cast(_group)->remove(this); } -Checkbox::Checkbox(QWidget *parent, const QString &text, bool checked, const style::Checkbox &st) : Button(parent), -_st(st), -a_over(0), a_checked(checked ? 1 : 0), -_a_over(animFunc(this, &Checkbox::animStep_over)), _a_checked(animFunc(this, &Checkbox::animStep_checked)), -_text(text), _fullText(text), _textWidth(st.font->width(text)), -_checked(checked) { +Checkbox::Checkbox(QWidget *parent, const QString &text, bool checked, const style::Checkbox &st) : Button(parent) +, _st(st) +, a_over(0) +, a_checked(checked ? 1 : 0) +, _a_over(animation(this, &Checkbox::step_over)) +, _a_checked(animation(this, &Checkbox::step_checked)) +, _text(text) +, _fullText(text) +, _textWidth(st.font->width(text)) +, _checked(checked) { if (_st.width <= 0) { resize(_textWidth - _st.width, _st.height); } else { @@ -275,30 +283,26 @@ void Checkbox::setChecked(bool checked) { } } -bool Checkbox::animStep_over(float64 ms) { +void Checkbox::step_over(float64 ms, bool timer) { float64 dt = ms / _st.duration; - bool res = true; if (dt >= 1) { + _a_over.stop(); a_over.finish(); - res = false; } else { a_over.update(dt, anim::linear); } - update(_checkRect); - return res; + if (timer) update(_checkRect); } -bool Checkbox::animStep_checked(float64 ms) { +void Checkbox::step_checked(float64 ms, bool timer) { float64 dt = ms / _st.duration; - bool res = true; if (dt >= 1) { a_checked.finish(); - res = false; + _a_checked.stop(); } else { a_checked.update(dt, anim::linear); } - update(_checkRect); - return res; + if (timer) update(_checkRect); } void Checkbox::paintEvent(QPaintEvent *e) { @@ -372,12 +376,18 @@ void Checkbox::onStateChange(int oldState, ButtonStateChangeSource source) { } } -Radiobutton::Radiobutton(QWidget *parent, const QString &group, int32 value, const QString &text, bool checked, const style::Radiobutton &st) : Button(parent), -_st(st), -a_over(0), a_checked(checked ? 1 : 0), -_a_over(animFunc(this, &Radiobutton::animStep_over)), _a_checked(animFunc(this, &Radiobutton::animStep_checked)), -_text(text), _fullText(text), _textWidth(st.font->width(text)), -_checked(checked), _group(radiobuttons.reg(group)), _value(value) { +Radiobutton::Radiobutton(QWidget *parent, const QString &group, int32 value, const QString &text, bool checked, const style::Radiobutton &st) : Button(parent) +, _st(st) +, a_over(0) +, a_checked(checked ? 1 : 0) +, _a_over(animation(this, &Radiobutton::step_over)) +, _a_checked(animation(this, &Radiobutton::step_checked)) +, _text(text) +, _fullText(text) +, _textWidth(st.font->width(text)) +, _checked(checked) +, _group(radiobuttons.reg(group)) +, _value(value) { if (_st.width <= 0) { resize(_textWidth - _st.width, _st.height); } else { @@ -419,30 +429,26 @@ void Radiobutton::setChecked(bool checked) { } } -bool Radiobutton::animStep_over(float64 ms) { +void Radiobutton::step_over(float64 ms, bool timer) { float64 dt = ms / _st.duration; - bool res = true; if (dt >= 1) { + _a_over.stop(); a_over.finish(); - res = false; } else { a_over.update(dt, anim::linear); } - update(_checkRect); - return res; + if (timer) update(_checkRect); } -bool Radiobutton::animStep_checked(float64 ms) { +void Radiobutton::step_checked(float64 ms, bool timer) { float64 dt = ms / _st.duration; - bool res = true; if (dt >= 1) { a_checked.finish(); - res = false; + _a_checked.stop(); } else { a_checked.update(dt, anim::linear); } - update(_checkRect); - return res; + if (timer) update(_checkRect); } void Radiobutton::paintEvent(QPaintEvent *e) { diff --git a/Telegram/SourceFiles/gui/flatcheckbox.h b/Telegram/SourceFiles/gui/flatcheckbox.h index c0296e405e..560045fdfe 100644 --- a/Telegram/SourceFiles/gui/flatcheckbox.h +++ b/Telegram/SourceFiles/gui/flatcheckbox.h @@ -22,7 +22,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "gui/button.h" -class FlatCheckbox : public Button, public Animated { +class FlatCheckbox : public Button { Q_OBJECT public: @@ -32,7 +32,7 @@ public: bool checked() const; void setChecked(bool checked); - bool animStep(float64 ms); + void step_appearance(float64 ms, bool timer); void paintEvent(QPaintEvent *e); void setOpacity(float64 o); @@ -50,6 +50,8 @@ private: style::flatCheckbox _st; anim::fvalue a_over; + Animation _a_appearance; + QString _text; style::font _font; @@ -91,8 +93,8 @@ public: bool checked() const; void setChecked(bool checked); - bool animStep_over(float64 ms); - bool animStep_checked(float64 ms); + void step_over(float64 ms, bool timer); + void step_checked(float64 ms, bool timer); void paintEvent(QPaintEvent *e); @@ -133,8 +135,8 @@ public: return _value; } - bool animStep_over(float64 ms); - bool animStep_checked(float64 ms); + void step_over(float64 ms, bool timer); + void step_checked(float64 ms, bool timer); void paintEvent(QPaintEvent *e); diff --git a/Telegram/SourceFiles/gui/flatinput.cpp b/Telegram/SourceFiles/gui/flatinput.cpp index f217b16f73..a7f4241a02 100644 --- a/Telegram/SourceFiles/gui/flatinput.cpp +++ b/Telegram/SourceFiles/gui/flatinput.cpp @@ -51,12 +51,22 @@ namespace { InputStyle _inputFieldStyle; } -FlatInput::FlatInput(QWidget *parent, const style::flatInput &st, const QString &pholder, const QString &v) : QLineEdit(v, parent), -_oldtext(v), _fullph(pholder), _fastph(false), _customUpDown(false), _phVisible(!v.length()), -a_phLeft(_phVisible ? 0 : st.phShift), a_phAlpha(_phVisible ? 1 : 0), a_phColor(st.phColor->c), -a_borderColor(st.borderColor->c), a_bgColor(st.bgColor->c), _notingBene(0), _st(st) { +FlatInput::FlatInput(QWidget *parent, const style::flatInput &st, const QString &pholder, const QString &v) : QLineEdit(v, parent) +, _oldtext(v) +, _fullph(pholder) +, _fastph(false) +, _customUpDown(false) +, _phVisible(!v.length()) +, a_phLeft(_phVisible ? 0 : st.phShift) +, a_phAlpha(_phVisible ? 1 : 0) +, a_phColor(st.phColor->c) +, a_borderColor(st.borderColor->c) +, a_bgColor(st.bgColor->c) +, _a_appearance(animation(this, &FlatInput::step_appearance)) +, _notingBene(0) +, _st(st) { resize(_st.width, _st.height); - + setFont(_st.font->f); setAlignment(_st.align); @@ -158,7 +168,7 @@ void FlatInput::paintEvent(QPaintEvent *e) { } bool phDraw = _phVisible; - if (animating()) { + if (_a_appearance.animating()) { p.setOpacity(a_phAlpha.current()); phDraw = true; } @@ -180,7 +190,7 @@ void FlatInput::focusInEvent(QFocusEvent *e) { a_borderColor.start(_st.borderActive->c); } a_bgColor.start(_st.bgActive->c); - anim::start(this); + _a_appearance.start(); QLineEdit::focusInEvent(e); emit focused(); } @@ -191,7 +201,7 @@ void FlatInput::focusOutEvent(QFocusEvent *e) { a_borderColor.start(_st.borderColor->c); } a_bgColor.start(_st.bgColor->c); - anim::start(this); + _a_appearance.start(); QLineEdit::focusOutEvent(e); emit blurred(); } @@ -224,11 +234,10 @@ QSize FlatInput::minimumSizeHint() const { return geometry().size(); } -bool FlatInput::animStep(float64 ms) { +void FlatInput::step_appearance(float64 ms, bool timer) { float dt = ms / _st.phDuration; - bool res = true; if (dt >= 1) { - res = false; + _a_appearance.stop(); a_phLeft.finish(); a_phAlpha.finish(); a_phColor.finish(); @@ -236,8 +245,8 @@ bool FlatInput::animStep(float64 ms) { if (_notingBene > 0) { _notingBene = -1; a_borderColor.start((hasFocus() ? _st.borderActive : _st.borderColor)->c); - anim::start(this); - return true; + _a_appearance.start(); + return; } else if (_notingBene) { _notingBene = 0; } @@ -249,8 +258,7 @@ bool FlatInput::animStep(float64 ms) { a_bgColor.update(dt, _st.phColorFunc); a_borderColor.update(dt, _st.phColorFunc); } - update(); - return res; + if (timer) update(); } void FlatInput::setPlaceholder(const QString &ph) { @@ -279,7 +287,7 @@ void FlatInput::updatePlaceholder() { } else { a_phLeft.start(vis ? 0 : _st.phShift); a_phAlpha.start(vis ? 1 : 0); - anim::start(this); + _a_appearance.start(); } _phVisible = vis; } @@ -345,11 +353,11 @@ void FlatInput::notaBene() { _notingBene = 1; setFocus(); a_borderColor.start(_st.borderError->c); - anim::start(this); + _a_appearance.start(); } -CountryCodeInput::CountryCodeInput(QWidget *parent, const style::flatInput &st) : FlatInput(parent, st), _nosignal(false) { - +CountryCodeInput::CountryCodeInput(QWidget *parent, const style::flatInput &st) : FlatInput(parent, st) +, _nosignal(false) { } void CountryCodeInput::startErasing(QKeyEvent *e) { @@ -541,38 +549,39 @@ void PhonePartInput::onChooseCode(const QString &code) { updatePlaceholder(); } -InputArea::InputArea(QWidget *parent, const style::InputArea &st, const QString &ph, const QString &val) : TWidget(parent), -_maxLength(-1), -_inner(this), -_oldtext(val), +InputArea::InputArea(QWidget *parent, const style::InputArea &st, const QString &ph, const QString &val) : TWidget(parent) +, _maxLength(-1) +, _inner(this) +, _oldtext(val) -_ctrlEnterSubmit(CtrlEnterSubmitCtrlEnter), -_undoAvailable(false), -_redoAvailable(false), -_inHeightCheck(false), +, _ctrlEnterSubmit(CtrlEnterSubmitCtrlEnter) +, _undoAvailable(false) +, _redoAvailable(false) +, _inHeightCheck(false) -_customUpDown(false), +, _customUpDown(false) -_placeholderFull(ph), -_placeholderVisible(val.isEmpty()), -a_placeholderLeft(_placeholderVisible ? 0 : st.placeholderShift), -a_placeholderOpacity(_placeholderVisible ? 1 : 0), -a_placeholderFg(st.placeholderFg->c), -_a_placeholderFg(animFunc(this, &InputArea::animStep_placeholderFg)), -_a_placeholderShift(animFunc(this, &InputArea::animStep_placeholderShift)), +, _placeholderFull(ph) +, _placeholderVisible(val.isEmpty()) +, a_placeholderLeft(_placeholderVisible ? 0 : st.placeholderShift) +, a_placeholderOpacity(_placeholderVisible ? 1 : 0) +, a_placeholderFg(st.placeholderFg->c) +, _a_placeholderFg(animation(this, &InputArea::step_placeholderFg)) +, _a_placeholderShift(animation(this, &InputArea::step_placeholderShift)) -a_borderOpacityActive(0), -a_borderFg(st.borderFg->c), -_a_border(animFunc(this, &InputArea::animStep_border)), +, a_borderOpacityActive(0) +, a_borderFg(st.borderFg->c) +, _a_border(animation(this, &InputArea::step_border)) -_focused(false), _error(false), +, _focused(false) +, _error(false) -_st(st), +, _st(st) -_touchPress(false), -_touchRightButton(false), -_touchMove(false), -_correcting(false) { +, _touchPress(false) +, _touchRightButton(false) +, _touchMove(false) +, _correcting(false) { _inner.setAcceptRichText(false); resize(_st.width, _st.heightMin); @@ -950,7 +959,7 @@ void InputArea::processDocumentContentsChange(int position, int charsAdded) { const QChar *ch = t.constData(), *e = ch + t.size(); for (; ch != e; ++ch, ++fp) { int32 emojiLen = 0; - emoji = emojiFromText(ch, e, emojiLen); + emoji = emojiFromText(ch, e, &emojiLen); if (emoji) { if (replacePosition >= 0) { emoji = 0; // replace tilde char format first @@ -1106,47 +1115,42 @@ void InputArea::onRedoAvailable(bool avail) { if (App::wnd()) App::wnd()->updateGlobalMenu(); } -bool InputArea::animStep_placeholderFg(float64 ms) { - float dt = ms / _st.duration; - bool res = true; +void InputArea::step_placeholderFg(float64 ms, bool timer) { + float64 dt = ms / _st.duration; if (dt >= 1) { - res = false; + _a_placeholderFg.stop(); a_placeholderFg.finish(); } else { a_placeholderFg.update(dt, anim::linear); } - update(); - return res; + if (timer) update(); } -bool InputArea::animStep_placeholderShift(float64 ms) { - float dt = ms / _st.duration; - bool res = true; +void InputArea::step_placeholderShift(float64 ms, bool timer) { + float64 dt = ms / _st.duration; if (dt >= 1) { - res = false; + _a_placeholderShift.stop(); a_placeholderLeft.finish(); a_placeholderOpacity.finish(); } else { a_placeholderLeft.update(dt, anim::linear); a_placeholderOpacity.update(dt, anim::linear); } - update(); - return res; + if (timer) update(); } -bool InputArea::animStep_border(float64 ms) { - float dt = ms / _st.duration; +void InputArea::step_border(float64 ms, bool timer) { + float64 dt = ms / _st.duration; bool res = true; if (dt >= 1) { - res = false; + _a_border.stop(); a_borderFg.finish(); a_borderOpacityActive.finish(); } else { a_borderFg.update(dt, anim::linear); a_borderOpacityActive.update(dt, anim::linear); } - update(); - return res; + if (timer) update(); } void InputArea::updatePlaceholder() { @@ -1261,41 +1265,41 @@ void InputArea::showError() { } } -InputField::InputField(QWidget *parent, const style::InputField &st, const QString &ph, const QString &val) : TWidget(parent), -_maxLength(-1), -_inner(this), -_oldtext(val), +InputField::InputField(QWidget *parent, const style::InputField &st, const QString &ph, const QString &val) : TWidget(parent) +, _maxLength(-1) +, _inner(this) +, _oldtext(val) -_undoAvailable(false), -_redoAvailable(false), +, _undoAvailable(false) +, _redoAvailable(false) -_customUpDown(true), +, _customUpDown(true) -_placeholderFull(ph), -_placeholderVisible(val.isEmpty()), -a_placeholderLeft(_placeholderVisible ? 0 : st.placeholderShift), -a_placeholderOpacity(_placeholderVisible ? 1 : 0), -a_placeholderFg(st.placeholderFg->c), -_a_placeholderFg(animFunc(this, &InputField::animStep_placeholderFg)), -_a_placeholderShift(animFunc(this, &InputField::animStep_placeholderShift)), +, _placeholderFull(ph) +, _placeholderVisible(val.isEmpty()) +, a_placeholderLeft(_placeholderVisible ? 0 : st.placeholderShift) +, a_placeholderOpacity(_placeholderVisible ? 1 : 0) +, a_placeholderFg(st.placeholderFg->c) +, _a_placeholderFg(animation(this, &InputField::step_placeholderFg)) +, _a_placeholderShift(animation(this, &InputField::step_placeholderShift)) -a_borderOpacityActive(0), -a_borderFg(st.borderFg->c), -_a_border(animFunc(this, &InputField::animStep_border)), +, a_borderOpacityActive(0) +, a_borderFg(st.borderFg->c) +, _a_border(animation(this, &InputField::step_border)) -_focused(false), _error(false), +, _focused(false) +, _error(false) -_st(st), +, _st(st) -_touchPress(false), -_touchRightButton(false), -_touchMove(false), -_correcting(false) { +, _touchPress(false) +, _touchRightButton(false) +, _touchMove(false) +, _correcting(false) { _inner.setAcceptRichText(false); resize(_st.width, _st.height); _inner.setWordWrapMode(QTextOption::NoWrap); - _inner.setLineWrapMode(QTextEdit::NoWrap); setAttribute(Qt::WA_OpaquePaintEvent); @@ -1327,7 +1331,7 @@ _correcting(false) { connect(&_inner, SIGNAL(undoAvailable(bool)), this, SLOT(onUndoAvailable(bool))); connect(&_inner, SIGNAL(redoAvailable(bool)), this, SLOT(onRedoAvailable(bool))); if (App::wnd()) connect(&_inner, SIGNAL(selectionChanged()), App::wnd(), SLOT(updateGlobalMenu())); - + setCursor(style::cur_text); if (!val.isEmpty()) { _inner.setPlainText(val); @@ -1408,7 +1412,7 @@ void InputField::paintEvent(QPaintEvent *e) { if (_st.iconSprite.pxWidth()) { p.drawSpriteLeft(_st.iconPosition, width(), _st.iconSprite); } - + bool drawPlaceholder = _placeholderVisible; if (_a_placeholderShift.animating()) { p.setOpacity(a_placeholderOpacity.current()); @@ -1421,11 +1425,11 @@ void InputField::paintEvent(QPaintEvent *e) { QRect r(rect().marginsRemoved(_st.textMargins + _st.placeholderMargins)); r.moveLeft(r.left() + a_placeholderLeft.current()); if (rtl()) r.moveLeft(width() - r.left() - r.width()); - + p.setFont(_st.font); p.setPen(a_placeholderFg.current()); p.drawText(r, _placeholder, _st.placeholderAlign); - + p.restore(); } TWidget::paintEvent(e); @@ -1659,7 +1663,7 @@ void InputField::processDocumentContentsChange(int position, int charsAdded) { } int32 emojiLen = 0; - emoji = emojiFromText(ch, e, emojiLen); + emoji = emojiFromText(ch, e, &emojiLen); if (emoji) { if (replacePosition >= 0) { emoji = 0; // replace tilde char format first @@ -1834,47 +1838,41 @@ void InputField::selectAll() { _inner.setTextCursor(c); } -bool InputField::animStep_placeholderFg(float64 ms) { - float dt = ms / _st.duration; - bool res = true; +void InputField::step_placeholderFg(float64 ms, bool timer) { + float64 dt = ms / _st.duration; if (dt >= 1) { - res = false; + _a_placeholderFg.stop(); a_placeholderFg.finish(); } else { a_placeholderFg.update(dt, anim::linear); } - update(); - return res; + if (timer) update(); } -bool InputField::animStep_placeholderShift(float64 ms) { - float dt = ms / _st.duration; - bool res = true; +void InputField::step_placeholderShift(float64 ms, bool timer) { + float64 dt = ms / _st.duration; if (dt >= 1) { - res = false; + _a_placeholderShift.stop(); a_placeholderLeft.finish(); a_placeholderOpacity.finish(); } else { a_placeholderLeft.update(dt, anim::linear); a_placeholderOpacity.update(dt, anim::linear); } - update(); - return res; + if (timer) update(); } -bool InputField::animStep_border(float64 ms) { - float dt = ms / _st.duration; - bool res = true; +void InputField::step_border(float64 ms, bool timer) { + float64 dt = ms / _st.duration; if (dt >= 1) { - res = false; + _a_border.stop(); a_borderFg.finish(); a_borderOpacityActive.finish(); } else { a_borderFg.update(dt, anim::linear); a_borderOpacityActive.update(dt, anim::linear); } - update(); - return res; + if (timer) update(); } void InputField::updatePlaceholder() { @@ -1981,34 +1979,35 @@ void InputField::showError() { } } -MaskedInputField::MaskedInputField(QWidget *parent, const style::InputField &st, const QString &placeholder, const QString &val) : QLineEdit(val, parent), -_st(st), -_maxLength(-1), -_oldtext(val), +MaskedInputField::MaskedInputField(QWidget *parent, const style::InputField &st, const QString &placeholder, const QString &val) : QLineEdit(val, parent) +, _st(st) +, _maxLength(-1) +, _oldtext(val) -_undoAvailable(false), -_redoAvailable(false), +, _undoAvailable(false) +, _redoAvailable(false) -_customUpDown(false), +, _customUpDown(false) -_placeholderFull(placeholder), -_placeholderVisible(val.isEmpty()), -_placeholderFast(false), -a_placeholderLeft(_placeholderVisible ? 0 : st.placeholderShift), -a_placeholderOpacity(_placeholderVisible ? 1 : 0), -a_placeholderFg(st.placeholderFg->c), -_a_placeholderFg(animFunc(this, &MaskedInputField::animStep_placeholderFg)), -_a_placeholderShift(animFunc(this, &MaskedInputField::animStep_placeholderShift)), +, _placeholderFull(placeholder) +, _placeholderVisible(val.isEmpty()) +, _placeholderFast(false) +, a_placeholderLeft(_placeholderVisible ? 0 : st.placeholderShift) +, a_placeholderOpacity(_placeholderVisible ? 1 : 0) +, a_placeholderFg(st.placeholderFg->c) +, _a_placeholderFg(animation(this, &MaskedInputField::step_placeholderFg)) +, _a_placeholderShift(animation(this, &MaskedInputField::step_placeholderShift)) -a_borderOpacityActive(0), -a_borderFg(st.borderFg->c), -_a_border(animFunc(this, &MaskedInputField::animStep_border)), +, a_borderOpacityActive(0) +, a_borderFg(st.borderFg->c) +, _a_border(animation(this, &MaskedInputField::step_border)) -_focused(false), _error(false), +, _focused(false) +, _error(false) -_touchPress(false), -_touchRightButton(false), -_touchMove(false) { +, _touchPress(false) +, _touchRightButton(false) +, _touchMove(false) { resize(_st.width, _st.height); setFont(_st.font->f); @@ -2029,7 +2028,7 @@ _touchMove(false) { setStyle(&_inputFieldStyle); QLineEdit::setTextMargins(0, 0, 0, 0); setContentsMargins(0, 0, 0, 0); - + setAttribute(Qt::WA_AcceptTouchEvents); _touchTimer.setSingleShot(true); connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer())); @@ -2188,47 +2187,41 @@ QSize MaskedInputField::minimumSizeHint() const { return geometry().size(); } -bool MaskedInputField::animStep_placeholderFg(float64 ms) { - float dt = ms / _st.duration; - bool res = true; +void MaskedInputField::step_placeholderFg(float64 ms, bool timer) { + float64 dt = ms / _st.duration; if (dt >= 1) { - res = false; + _a_placeholderFg.stop(); a_placeholderFg.finish(); } else { a_placeholderFg.update(dt, anim::linear); } - update(); - return res; + if (timer) update(); } -bool MaskedInputField::animStep_placeholderShift(float64 ms) { - float dt = ms / _st.duration; - bool res = true; +void MaskedInputField::step_placeholderShift(float64 ms, bool timer) { + float64 dt = ms / _st.duration; if (dt >= 1) { - res = false; + _a_placeholderShift.stop(); a_placeholderLeft.finish(); a_placeholderOpacity.finish(); } else { a_placeholderLeft.update(dt, anim::linear); a_placeholderOpacity.update(dt, anim::linear); } - update(); - return res; + if (timer) update(); } -bool MaskedInputField::animStep_border(float64 ms) { - float dt = ms / _st.duration; - bool res = true; +void MaskedInputField::step_border(float64 ms, bool timer) { + float64 dt = ms / _st.duration; if (dt >= 1) { - res = false; + _a_border.stop(); a_borderFg.finish(); a_borderOpacityActive.finish(); } else { a_borderFg.update(dt, anim::linear); a_borderOpacityActive.update(dt, anim::linear); } - update(); - return res; + if (timer) update(); } bool MaskedInputField::setPlaceholder(const QString &placeholder) { diff --git a/Telegram/SourceFiles/gui/flatinput.h b/Telegram/SourceFiles/gui/flatinput.h index 95aa9b8cec..98b8d3be19 100644 --- a/Telegram/SourceFiles/gui/flatinput.h +++ b/Telegram/SourceFiles/gui/flatinput.h @@ -23,7 +23,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "style.h" #include "animation.h" -class FlatInput : public QLineEdit, public Animated { +class FlatInput : public QLineEdit { Q_OBJECT T_WIDGET @@ -50,7 +50,7 @@ public: QRect getTextRect() const; - bool animStep(float64 ms); + void step_appearance(float64 ms, bool timer); QSize sizeHint() const; QSize minimumSizeHint() const; @@ -98,6 +98,7 @@ private: anim::ivalue a_phLeft; anim::fvalue a_phAlpha; anim::cvalue a_phColor, a_borderColor, a_bgColor; + Animation _a_appearance; int _notingBene; style::flatInput _st; @@ -196,9 +197,9 @@ public: } void updatePlaceholder(); - bool animStep_placeholderFg(float64 ms); - bool animStep_placeholderShift(float64 ms); - bool animStep_border(float64 ms); + void step_placeholderFg(float64 ms, bool timer); + void step_placeholderShift(float64 ms, bool timer); + void step_border(float64 ms, bool timer); QSize sizeHint() const; QSize minimumSizeHint() const; @@ -354,9 +355,9 @@ public: } void updatePlaceholder(); - bool animStep_placeholderFg(float64 ms); - bool animStep_placeholderShift(float64 ms); - bool animStep_border(float64 ms); + void step_placeholderFg(float64 ms, bool timer); + void step_placeholderShift(float64 ms, bool timer); + void step_border(float64 ms, bool timer); QSize sizeHint() const; QSize minimumSizeHint() const; @@ -523,9 +524,9 @@ public: QRect getTextRect() const; - bool animStep_placeholderFg(float64 ms); - bool animStep_placeholderShift(float64 ms); - bool animStep_border(float64 ms); + void step_placeholderFg(float64 ms, bool timer); + void step_placeholderShift(float64 ms, bool timer); + void step_border(float64 ms, bool timer); QSize sizeHint() const; QSize minimumSizeHint() const; diff --git a/Telegram/SourceFiles/gui/flattextarea.cpp b/Telegram/SourceFiles/gui/flattextarea.cpp index 7dccc46b13..3340bb1ab5 100644 --- a/Telegram/SourceFiles/gui/flattextarea.cpp +++ b/Telegram/SourceFiles/gui/flattextarea.cpp @@ -24,18 +24,34 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "flattextarea.h" #include "window.h" -FlatTextarea::FlatTextarea(QWidget *parent, const style::flatTextarea &st, const QString &pholder, const QString &v) : QTextEdit(parent), -_minHeight(-1), _maxHeight(-1), _maxLength(-1), _ctrlEnterSubmit(true), -_oldtext(v), _phVisible(!v.length()), -a_phLeft(_phVisible ? 0 : st.phShift), a_phAlpha(_phVisible ? 1 : 0), a_phColor(st.phColor->c), -_st(st), _undoAvailable(false), _redoAvailable(false), _inDrop(false), _inHeightCheck(false), _fakeMargin(0), -_touchPress(false), _touchRightButton(false), _touchMove(false), _correcting(false) { +FlatTextarea::FlatTextarea(QWidget *parent, const style::flatTextarea &st, const QString &pholder, const QString &v) : QTextEdit(parent) +, _minHeight(-1) +, _maxHeight(-1) +, _maxLength(-1) +, _ctrlEnterSubmit(true) +, _oldtext(v) +, _phAfter(0) +, _phVisible(!v.length()) +, a_phLeft(_phVisible ? 0 : st.phShift) +, a_phAlpha(_phVisible ? 1 : 0) +, a_phColor(st.phColor->c) +, _a_appearance(animation(this, &FlatTextarea::step_appearance)) +, _st(st) +, _undoAvailable(false) +, _redoAvailable(false) +, _inDrop(false) +, _inHeightCheck(false) +, _fakeMargin(0) +, _touchPress(false) +, _touchRightButton(false) +, _touchMove(false) +, _correcting(false) { setAcceptRichText(false); resize(_st.width, _st.font->height); - + setFont(_st.font->f); setAlignment(_st.align); - + setPlaceholder(pholder); QPalette p(palette()); @@ -72,12 +88,25 @@ _touchPress(false), _touchRightButton(false), _touchMove(false), _correcting(fal } } -void FlatTextarea::setTextFast(const QString &text) { - setPlainText(text); - if (animating()) { +void FlatTextarea::setTextFast(const QString &text, bool clearUndoHistory) { + if (clearUndoHistory) { + setPlainText(text); + } else { + QTextCursor c(document()->docHandle(), 0); + c.joinPreviousEditBlock(); + c.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); + c.insertText(text); + c.movePosition(QTextCursor::End); + c.endEditBlock(); + } + finishPlaceholder(); +} + +void FlatTextarea::finishPlaceholder() { + if (_a_appearance.animating()) { a_phLeft.finish(); a_phAlpha.finish(); - anim::stop(this); + _a_appearance.stop(); update(); } } @@ -184,17 +213,21 @@ void FlatTextarea::paintEvent(QPaintEvent *e) { QRect r(rect().intersected(e->rect())); p.fillRect(r, _st.bgColor->b); bool phDraw = _phVisible; - if (animating()) { + if (_a_appearance.animating()) { p.setOpacity(a_phAlpha.current()); phDraw = true; } if (phDraw) { p.save(); p.setClipRect(r); - QRect phRect(_st.textMrg.left() - _fakeMargin + _st.phPos.x() + a_phLeft.current(), _st.textMrg.top() - _fakeMargin + _st.phPos.y(), width() - _st.textMrg.left() - _st.textMrg.right(), height() - _st.textMrg.top() - _st.textMrg.bottom()); - p.setFont(_st.font->f); + p.setFont(_st.font); p.setPen(a_phColor.current()); - p.drawText(phRect, _ph, QTextOption(_st.phAlign)); + if (_st.phAlign == style::al_topleft && _phAfter > 0) { + p.drawText(_st.textMrg.left() - _fakeMargin + a_phLeft.current() + _st.font->width(getLastText().mid(0, _phAfter)), _st.textMrg.top() - _fakeMargin - st::lineWidth + _st.font->ascent, _ph); + } else { + QRect phRect(_st.textMrg.left() - _fakeMargin + _st.phPos.x() + a_phLeft.current(), _st.textMrg.top() - _fakeMargin + _st.phPos.y(), width() - _st.textMrg.left() - _st.textMrg.right(), height() - _st.textMrg.top() - _st.textMrg.bottom()); + p.drawText(phRect, _ph, QTextOption(_st.phAlign)); + } p.restore(); p.setOpacity(1); } @@ -203,13 +236,13 @@ void FlatTextarea::paintEvent(QPaintEvent *e) { void FlatTextarea::focusInEvent(QFocusEvent *e) { a_phColor.start(_st.phFocusColor->c); - anim::start(this); + _a_appearance.start(); QTextEdit::focusInEvent(e); } void FlatTextarea::focusOutEvent(QFocusEvent *e) { a_phColor.start(_st.phColor->c); - anim::start(this); + _a_appearance.start(); QTextEdit::focusOutEvent(e); } @@ -226,7 +259,7 @@ EmojiPtr FlatTextarea::getSingleEmoji() const { QTextFragment fragment; getSingleEmojiFragment(text, fragment); - + if (!text.isEmpty()) { QTextCharFormat format = fragment.charFormat(); QString imageName = static_cast(&format)->name(); @@ -237,10 +270,62 @@ EmojiPtr FlatTextarea::getSingleEmoji() const { return 0; } -void FlatTextarea::getMentionHashtagBotCommandStart(QString &start) const { - int32 pos = textCursor().position(); - if (textCursor().anchor() != pos) return; +QString FlatTextarea::getInlineBotQuery(UserData *&inlineBot, QString &inlineBotUsername) const { + const QString &text(getLastText()); + int32 inlineUsernameStart = 1, inlineUsernameLength = 0, size = text.size(); + if (size > 2 && text.at(0) == '@' && text.at(1).isLetter()) { + inlineUsernameLength = 1; + for (int32 i = inlineUsernameStart + 1, l = text.size(); i < l; ++i) { + if (text.at(i).isLetterOrNumber() || text.at(i).unicode() == '_') { + ++inlineUsernameLength; + continue; + } + if (!text.at(i).isSpace()) { + inlineUsernameLength = 0; + } + break; + } + if (inlineUsernameLength && inlineUsernameStart + inlineUsernameLength < text.size() && text.at(inlineUsernameStart + inlineUsernameLength).isSpace()) { + QStringRef username = text.midRef(inlineUsernameStart, inlineUsernameLength); + if (username != inlineBotUsername) { + inlineBotUsername = username.toString(); + PeerData *peer = App::peerByName(inlineBotUsername); + if (peer) { + if (peer->isUser()) { + inlineBot = peer->asUser(); + } else { + inlineBot = 0; + } + } else { + inlineBot = InlineBotLookingUpData; + } + } + if (inlineBot == InlineBotLookingUpData) return QString(); + + if (inlineBot && (!inlineBot->botInfo || inlineBot->botInfo->inlinePlaceholder.isEmpty())) { + inlineBot = 0; + } else { + return text.mid(inlineUsernameStart + inlineUsernameLength + 1); + } + } else { + inlineUsernameLength = 0; + } + } + if (inlineUsernameLength < 3) { + inlineBot = 0; + inlineBotUsername = QString(); + } + return QString(); +} + +QString FlatTextarea::getMentionHashtagBotCommandPart(bool &start) const { + start = false; + + int32 pos = textCursor().position(); + if (textCursor().anchor() != pos) return QString(); + + // check mention / hashtag / bot command QTextDocument *doc(document()); QTextBlock block = doc->findBlock(pos); for (QTextBlock::Iterator iter = block.begin(); !iter.atEnd(); ++iter) { @@ -258,29 +343,33 @@ void FlatTextarea::getMentionHashtagBotCommandStart(QString &start) const { for (int i = pos - p; i > 0; --i) { if (t.at(i - 1) == '@') { if ((pos - p - i < 1 || t.at(i).isLetter()) && (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_'))) { - start = t.mid(i - 1, pos - p - i + 1); + start = (i == 1) && (p == 0); + return t.mid(i - 1, pos - p - i + 1); } else if ((pos - p - i < 1 || t.at(i).isLetter()) && i > 2 && (t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_') && !mentionInCommand) { mentionInCommand = true; --i; continue; } - return; + return QString(); } else if (t.at(i - 1) == '#') { if (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_')) { - start = t.mid(i - 1, pos - p - i + 1); + start = (i == 1) && (p == 0); + return t.mid(i - 1, pos - p - i + 1); } - return; + return QString(); } else if (t.at(i - 1) == '/') { if (i < 2) { - start = t.mid(i - 1, pos - p - i + 1); + start = (i == 1) && (p == 0); + return t.mid(i - 1, pos - p - i + 1); } - return; + return QString(); } if (pos - p - i > 127 || (!mentionInCommand && (pos - p - i > 63))) break; if (!t.at(i - 1).isLetterOrNumber() && t.at(i - 1) != '_') break; } - return; + break; } + return QString(); } void FlatTextarea::onMentionHashtagOrBotCommandInsert(QString str) { @@ -649,7 +738,7 @@ void FlatTextarea::processDocumentContentsChange(int position, int charsAdded) { const QChar *ch = t.constData(), *e = ch + t.size(); for (; ch != e; ++ch, ++fp) { int32 emojiLen = 0; - emoji = emojiFromText(ch, e, emojiLen); + emoji = emojiFromText(ch, e, &emojiLen); if (emoji) { if (replacePosition >= 0) { emoji = 0; // replace tilde char format first @@ -701,7 +790,7 @@ void FlatTextarea::processDocumentContentsChange(int position, int charsAdded) { emoji = 0; replacePosition = -1; - } else { + } else { break; } } @@ -807,11 +896,10 @@ void FlatTextarea::onRedoAvailable(bool avail) { if (App::wnd()) App::wnd()->updateGlobalMenu(); } -bool FlatTextarea::animStep(float64 ms) { +void FlatTextarea::step_appearance(float64 ms, bool timer) { float dt = ms / _st.phDuration; - bool res = true; if (dt >= 1) { - res = false; + _a_appearance.stop(); a_phLeft.finish(); a_phAlpha.finish(); a_phColor.finish(); @@ -823,23 +911,26 @@ bool FlatTextarea::animStep(float64 ms) { a_phAlpha.update(dt, _st.phAlphaFunc); a_phColor.update(dt, _st.phColorFunc); } - update(); - return res; + if (timer) update(); } -void FlatTextarea::setPlaceholder(const QString &ph) { +void FlatTextarea::setPlaceholder(const QString &ph, int32 afterSymbols) { _ph = ph; - _phelided = _st.font->elided(_ph, width() - _st.textMrg.left() - _st.textMrg.right() - _st.phPos.x() - 1); + if (_phAfter != afterSymbols) { + _phAfter = afterSymbols; + updatePlaceholder(); + } + _phelided = _st.font->elided(_ph, width() - _st.textMrg.left() - _st.textMrg.right() - _st.phPos.x() - 1 - (_phAfter ? _st.font->width(getLastText().mid(0, _phAfter)) : 0)); if (_phVisible) update(); } void FlatTextarea::updatePlaceholder() { - bool vis = getLastText().isEmpty(); + bool vis = (getLastText().size() <= _phAfter); if (vis == _phVisible) return; a_phLeft.start(vis ? 0 : _st.phShift); a_phAlpha.start(vis ? 1 : 0); - anim::start(this); + _a_appearance.start(); _phVisible = vis; } diff --git a/Telegram/SourceFiles/gui/flattextarea.h b/Telegram/SourceFiles/gui/flattextarea.h index 524838216e..14b9fa10dc 100644 --- a/Telegram/SourceFiles/gui/flattextarea.h +++ b/Telegram/SourceFiles/gui/flattextarea.h @@ -24,7 +24,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "style.h" #include "animation.h" -class FlatTextarea : public QTextEdit, public Animated { +class UserData; +class FlatTextarea : public QTextEdit { Q_OBJECT T_WIDGET @@ -50,19 +51,21 @@ public: const QString &getLastText() const { return _oldtext; } - void setPlaceholder(const QString &ph); + void setPlaceholder(const QString &ph, int32 afterSymbols = 0); void updatePlaceholder(); + void finishPlaceholder(); QRect getTextRect() const; int32 fakeMargin() const; - bool animStep(float64 ms); + void step_appearance(float64 ms, bool timer); QSize sizeHint() const; QSize minimumSizeHint() const; EmojiPtr getSingleEmoji() const; - void getMentionHashtagBotCommandStart(QString &start) const; + QString getMentionHashtagBotCommandPart(bool &start) const; + QString getInlineBotQuery(UserData *&contextBot, QString &contextBotUsername) const; void removeSingleEmoji(); bool hasText() const; @@ -77,7 +80,7 @@ public: QMimeData *createMimeDataFromSelection() const; void setCtrlEnterSubmit(bool ctrlEnterSubmit); - void setTextFast(const QString &text); + void setTextFast(const QString &text, bool clearUndoHistory = true); public slots: @@ -123,10 +126,13 @@ private: bool _ctrlEnterSubmit; QString _ph, _phelided, _oldtext; + int32 _phAfter; bool _phVisible; anim::ivalue a_phLeft; anim::fvalue a_phAlpha; anim::cvalue a_phColor; + Animation _a_appearance; + style::flatTextarea _st; bool _undoAvailable, _redoAvailable, _inDrop, _inHeightCheck; diff --git a/Telegram/SourceFiles/gui/images.cpp b/Telegram/SourceFiles/gui/images.cpp index 6b9b74756e..6437abf3f2 100644 --- a/Telegram/SourceFiles/gui/images.cpp +++ b/Telegram/SourceFiles/gui/images.cpp @@ -27,9 +27,12 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "pspecific.h" namespace { - typedef QMap LocalImages; + typedef QMap LocalImages; LocalImages localImages; + typedef QMap WebImages; + WebImages webImages; + Image *blank() { static Image *img = getImage(qsl(":/gui/art/blank.gif"), "GIF"); return img; @@ -38,7 +41,7 @@ namespace { typedef QMap StorageImages; StorageImages storageImages; - int64 globalAquiredSize = 0; + int64 globalAcquiredSize = 0; static const uint64 BlurredCacheSkip = 0x1000000000000000LLU; static const uint64 ColoredCacheSkip = 0x2000000000000000LLU; @@ -46,6 +49,8 @@ namespace { static const uint64 RoundedCacheSkip = 0x4000000000000000LLU; } +StorageImageLocation StorageImageLocation::Null; + bool Image::isNull() const { return (this == blank()); } @@ -57,8 +62,39 @@ ImagePtr::ImagePtr(int32 width, int32 height, const MTPFileLocation &location, I Parent((location.type() == mtpc_fileLocation) ? (Image*)(getImage(StorageImageLocation(width, height, location.c_fileLocation()))) : def.v()) { } +Image::Image(const QString &file, QByteArray fmt) : _forgot(false) { + _data = QPixmap::fromImage(App::readImage(file, &fmt, false, 0, &_saved), Qt::ColorOnly); + _format = fmt; + if (!_data.isNull()) { + globalAcquiredSize += int64(_data.width()) * _data.height() * 4; + } +} + +Image::Image(const QByteArray &filecontent, QByteArray fmt) : _forgot(false) { + _data = QPixmap::fromImage(App::readImage(filecontent, &fmt, false), Qt::ColorOnly); + _format = fmt; + _saved = filecontent; + if (!_data.isNull()) { + globalAcquiredSize += int64(_data.width()) * _data.height() * 4; + } +} + +Image::Image(const QPixmap &pixmap, QByteArray format) : _format(format), _forgot(false), _data(pixmap) { + if (!_data.isNull()) { + globalAcquiredSize += int64(_data.width()) * _data.height() * 4; + } +} + +Image::Image(const QByteArray &filecontent, QByteArray fmt, const QPixmap &pixmap) : _saved(filecontent), _format(fmt), _forgot(false), _data(pixmap) { + _data = pixmap; + _format = fmt; + _saved = filecontent; + if (!_data.isNull()) { + globalAcquiredSize += int64(_data.width()) * _data.height() * 4; + } +} + const QPixmap &Image::pix(int32 w, int32 h) const { - restore(); checkload(); if (w <= 0 || !width() || !height()) { @@ -74,14 +110,13 @@ const QPixmap &Image::pix(int32 w, int32 h) const { if (cRetina()) p.setDevicePixelRatio(cRetinaFactor()); i = _sizesCache.insert(k, p); if (!p.isNull()) { - globalAquiredSize += int64(p.width()) * p.height() * 4; + globalAcquiredSize += int64(p.width()) * p.height() * 4; } } return i.value(); } const QPixmap &Image::pixRounded(int32 w, int32 h) const { - restore(); checkload(); if (w <= 0 || !width() || !height()) { @@ -97,14 +132,13 @@ const QPixmap &Image::pixRounded(int32 w, int32 h) const { if (cRetina()) p.setDevicePixelRatio(cRetinaFactor()); i = _sizesCache.insert(k, p); if (!p.isNull()) { - globalAquiredSize += int64(p.width()) * p.height() * 4; + globalAcquiredSize += int64(p.width()) * p.height() * 4; } } return i.value(); } const QPixmap &Image::pixBlurred(int32 w, int32 h) const { - restore(); checkload(); if (w <= 0 || !width() || !height()) { @@ -120,14 +154,13 @@ const QPixmap &Image::pixBlurred(int32 w, int32 h) const { if (cRetina()) p.setDevicePixelRatio(cRetinaFactor()); i = _sizesCache.insert(k, p); if (!p.isNull()) { - globalAquiredSize += int64(p.width()) * p.height() * 4; + globalAcquiredSize += int64(p.width()) * p.height() * 4; } } return i.value(); } const QPixmap &Image::pixColored(const style::color &add, int32 w, int32 h) const { - restore(); checkload(); if (w <= 0 || !width() || !height()) { @@ -143,14 +176,13 @@ const QPixmap &Image::pixColored(const style::color &add, int32 w, int32 h) cons if (cRetina()) p.setDevicePixelRatio(cRetinaFactor()); i = _sizesCache.insert(k, p); if (!p.isNull()) { - globalAquiredSize += int64(p.width()) * p.height() * 4; + globalAcquiredSize += int64(p.width()) * p.height() * 4; } } return i.value(); } const QPixmap &Image::pixBlurredColored(const style::color &add, int32 w, int32 h) const { - restore(); checkload(); if (w <= 0 || !width() || !height()) { @@ -166,14 +198,13 @@ const QPixmap &Image::pixBlurredColored(const style::color &add, int32 w, int32 if (cRetina()) p.setDevicePixelRatio(cRetinaFactor()); i = _sizesCache.insert(k, p); if (!p.isNull()) { - globalAquiredSize += int64(p.width()) * p.height() * 4; + globalAcquiredSize += int64(p.width()) * p.height() * 4; } } return i.value(); } const QPixmap &Image::pixSingle(int32 w, int32 h, int32 outerw, int32 outerh) const { - restore(); checkload(); if (w <= 0 || !width() || !height()) { @@ -184,22 +215,21 @@ const QPixmap &Image::pixSingle(int32 w, int32 h, int32 outerw, int32 outerh) co } uint64 k = 0LL; Sizes::const_iterator i = _sizesCache.constFind(k); - if (i == _sizesCache.cend() || i->width() != w || (h && i->height() != h)) { + if (i == _sizesCache.cend() || i->width() != (outerw * cIntRetinaFactor()) || i->height() != (outerh * cIntRetinaFactor())) { if (i != _sizesCache.cend()) { - globalAquiredSize -= int64(i->width()) * i->height() * 4; + globalAcquiredSize -= int64(i->width()) * i->height() * 4; } QPixmap p(pixNoCache(w, h, true, false, true, outerw, outerh)); if (cRetina()) p.setDevicePixelRatio(cRetinaFactor()); i = _sizesCache.insert(k, p); if (!p.isNull()) { - globalAquiredSize += int64(p.width()) * p.height() * 4; + globalAcquiredSize += int64(p.width()) * p.height() * 4; } } return i.value(); } const QPixmap &Image::pixBlurredSingle(int32 w, int32 h, int32 outerw, int32 outerh) const { - restore(); checkload(); if (w <= 0 || !width() || !height()) { @@ -210,15 +240,15 @@ const QPixmap &Image::pixBlurredSingle(int32 w, int32 h, int32 outerw, int32 out } uint64 k = BlurredCacheSkip | 0LL; Sizes::const_iterator i = _sizesCache.constFind(k); - if (i == _sizesCache.cend() || i->width() != w || (h && i->height() != h)) { + if (i == _sizesCache.cend() || i->width() != (outerw * cIntRetinaFactor()) || i->height() != (outerh * cIntRetinaFactor())) { if (i != _sizesCache.cend()) { - globalAquiredSize -= int64(i->width()) * i->height() * 4; + globalAcquiredSize -= int64(i->width()) * i->height() * 4; } QPixmap p(pixNoCache(w, h, true, true, true, outerw, outerh)); if (cRetina()) p.setDevicePixelRatio(cRetinaFactor()); i = _sizesCache.insert(k, p); if (!p.isNull()) { - globalAquiredSize += int64(p.width()) * p.height() * 4; + globalAcquiredSize += int64(p.width()) * p.height() * 4; } } return i.value(); @@ -335,7 +365,7 @@ yi += stride; #undef update } - + delete[] rgb; } } @@ -396,16 +426,9 @@ QImage imageColored(const style::color &add, QImage img) { return img; } -QPixmap Image::pixNoCache(int32 w, int32 h, bool smooth, bool blurred, bool rounded, int32 outerw, int32 outerh) const { - restore(); - loaded(); - - const QPixmap &p(pixData()); - if (p.isNull()) return blank()->pix(); - - QImage img = p.toImage(); +QPixmap imagePix(QImage img, int32 w, int32 h, bool smooth, bool blurred, bool rounded, int32 outerw, int32 outerh) { if (blurred) img = imageBlur(img); - if (w <= 0 || !width() || !height() || (w == width() && (h <= 0 || h == height()))) { + if (w <= 0 || (w == img.width() && (h <= 0 || h == img.height()))) { } else if (h <= 0) { img = img.scaledToWidth(w, smooth ? Qt::SmoothTransformation : Qt::FastTransformation); } else { @@ -421,7 +444,7 @@ QPixmap Image::pixNoCache(int32 w, int32 h, bool smooth, bool blurred, bool roun { QPainter p(&result); if (w < outerw || h < outerh) { - p.fillRect(0, 0, result.width(), result.height(), st::black->b); + p.fillRect(0, 0, result.width(), result.height(), st::black); } p.drawImage((result.width() - img.width()) / (2 * cIntRetinaFactor()), (result.height() - img.height()) / (2 * cIntRetinaFactor()), img); } @@ -429,32 +452,65 @@ QPixmap Image::pixNoCache(int32 w, int32 h, bool smooth, bool blurred, bool roun } } if (rounded) imageRound(img); + img.setDevicePixelRatio(cRetinaFactor()); return QPixmap::fromImage(img, Qt::ColorOnly); } -QPixmap Image::pixColoredNoCache(const style::color &add, int32 w, int32 h, bool smooth) const { +QPixmap Image::pixNoCache(int32 w, int32 h, bool smooth, bool blurred, bool rounded, int32 outerw, int32 outerh) const { + if (!loading()) const_cast(this)->load(); restore(); - loaded(); + if (_data.isNull()) { + if (h <= 0 && height() > 0) { + h = qRound(width() * w / float64(height())); + } + return blank()->pixNoCache(w, h, smooth, blurred, rounded, outerw, outerh); + } - const QPixmap &p(pixData()); - if (p.isNull()) { - return blank()->pix(); + if (isNull() && outerw > 0 && outerh > 0) { + outerw *= cIntRetinaFactor(); + outerh *= cIntRetinaFactor(); + + QImage result(outerw, outerh, QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(cRetinaFactor()); + + { + QPainter p(&result); + if (w < outerw) { + p.fillRect(0, 0, (outerw - w) / 2, result.height(), st::black); + p.fillRect(((outerw - w) / 2) + w, 0, result.width() - (((outerw - w) / 2) + w), result.height(), st::black); + } + if (h < outerh) { + p.fillRect(qMax(0, (outerw - w) / 2), 0, qMin(result.width(), w), (outerh - h) / 2, st::black); + p.fillRect(qMax(0, (outerw - w) / 2), ((outerh - h) / 2) + h, qMin(result.width(), w), result.height() - (((outerh - h) / 2) + h), st::black); + } + p.fillRect(qMax(0, (outerw - w) / 2), qMax(0, (outerh - h) / 2), qMin(result.width(), w), qMin(result.height(), h), st::white); + } + + if (rounded) imageRound(result); + return QPixmap::fromImage(result, Qt::ColorOnly); } - if (w <= 0 || !width() || !height() || (w == width() && (h <= 0 || h == height()))) return QPixmap::fromImage(imageColored(add, p.toImage())); + return imagePix(_data.toImage(), w, h, smooth, blurred, rounded, outerw, outerh); +} + +QPixmap Image::pixColoredNoCache(const style::color &add, int32 w, int32 h, bool smooth) const { + const_cast(this)->load(); + restore(); + if (_data.isNull()) return blank()->pix(); + + QImage img = _data.toImage(); + if (w <= 0 || !width() || !height() || (w == width() && (h <= 0 || h == height()))) return QPixmap::fromImage(imageColored(add, img)); if (h <= 0) { - return QPixmap::fromImage(imageColored(add, p.toImage().scaledToWidth(w, smooth ? Qt::SmoothTransformation : Qt::FastTransformation)), Qt::ColorOnly); + return QPixmap::fromImage(imageColored(add, img.scaledToWidth(w, smooth ? Qt::SmoothTransformation : Qt::FastTransformation)), Qt::ColorOnly); } - return QPixmap::fromImage(imageColored(add, p.toImage().scaled(w, h, Qt::IgnoreAspectRatio, smooth ? Qt::SmoothTransformation : Qt::FastTransformation)), Qt::ColorOnly); + return QPixmap::fromImage(imageColored(add, img.scaled(w, h, Qt::IgnoreAspectRatio, smooth ? Qt::SmoothTransformation : Qt::FastTransformation)), Qt::ColorOnly); } QPixmap Image::pixBlurredColoredNoCache(const style::color &add, int32 w, int32 h) const { + const_cast(this)->load(); restore(); - loaded(); + if (_data.isNull()) return blank()->pix(); - const QPixmap &p(pixData()); - if (p.isNull()) return blank()->pix(); - - QImage img = imageBlur(p.toImage()); + QImage img = imageBlur(_data.toImage()); if (h <= 0) { img = img.scaledToWidth(w, Qt::SmoothTransformation); } else { @@ -465,121 +521,91 @@ QPixmap Image::pixBlurredColoredNoCache(const style::color &add, int32 w, int32 } void Image::forget() const { - if (forgot) return; + if (_forgot) return; - const QPixmap &p(pixData()); - if (p.isNull()) return; + if (_data.isNull()) return; invalidateSizeCache(); - if (saved.isEmpty()) { - QBuffer buffer(&saved); - if (format.toLower() == "webp") { - int a = 0; - } - if (!p.save(&buffer, format)) { - if (p.save(&buffer, "PNG")) { - format = "PNG"; + if (_saved.isEmpty()) { + QBuffer buffer(&_saved); + if (!_data.save(&buffer, _format)) { + if (_data.save(&buffer, "PNG")) { + _format = "PNG"; } else { return; } } } - globalAquiredSize -= int64(p.width()) * p.height() * 4; - doForget(); - forgot = true; + globalAcquiredSize -= int64(_data.width()) * _data.height() * 4; + _data = QPixmap(); + _forgot = true; } void Image::restore() const { - if (!forgot) return; - doRestore(); - const QPixmap &p(pixData()); - if (!p.isNull()) { - globalAquiredSize += int64(p.width()) * p.height() * 4; + if (!_forgot) return; + + QBuffer buffer(&_saved); + QImageReader reader(&buffer, _format); +#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) + reader.setAutoTransform(true); +#endif + _data = QPixmap::fromImageReader(&reader, Qt::ColorOnly); + + if (!_data.isNull()) { + globalAcquiredSize += int64(_data.width()) * _data.height() * 4; } - forgot = false; + _forgot = false; } void Image::invalidateSizeCache() const { for (Sizes::const_iterator i = _sizesCache.cbegin(), e = _sizesCache.cend(); i != e; ++i) { if (!i->isNull()) { - globalAquiredSize -= int64(i->width()) * i->height() * 4; + globalAcquiredSize -= int64(i->width()) * i->height() * 4; } } _sizesCache.clear(); } -LocalImage::LocalImage(const QString &file, QByteArray fmt) { - data = QPixmap::fromImage(App::readImage(file, &fmt, false, 0, &saved), Qt::ColorOnly); - format = fmt; - if (!data.isNull()) { - globalAquiredSize += int64(data.width()) * data.height() * 4; +Image::~Image() { + invalidateSizeCache(); + if (!_data.isNull()) { + globalAcquiredSize -= int64(_data.width()) * _data.height() * 4; } } -LocalImage::LocalImage(const QByteArray &filecontent, QByteArray fmt) { - data = QPixmap::fromImage(App::readImage(filecontent, &fmt, false), Qt::ColorOnly); - format = fmt; - saved = filecontent; - if (!data.isNull()) { - globalAquiredSize += int64(data.width()) * data.height() * 4; +Image *getImage(const QString &file, QByteArray format) { + if (file.startsWith(qstr("http://"), Qt::CaseInsensitive) || file.startsWith(qstr("https://"), Qt::CaseInsensitive)) { + QString key = file; + WebImages::const_iterator i = webImages.constFind(key); + if (i == webImages.cend()) { + i = webImages.insert(key, new WebImage(file)); + } + return i.value(); + } else { + QFileInfo f(file); + QString key = qsl("//:%1//:%2//:").arg(f.size()).arg(f.lastModified().toTime_t()) + file; + LocalImages::const_iterator i = localImages.constFind(key); + if (i == localImages.cend()) { + i = localImages.insert(key, new Image(file, format)); + } + return i.value(); } } -LocalImage::LocalImage(const QPixmap &pixmap, QByteArray format) : Image(format), data(pixmap) { - if (!data.isNull()) { - globalAquiredSize += int64(data.width()) * data.height() * 4; - } +Image *getImage(const QByteArray &filecontent, QByteArray format) { + return new Image(filecontent, format); } -LocalImage::LocalImage(const QByteArray &filecontent, QByteArray fmt, const QPixmap &pixmap) { - data = pixmap; - format = fmt; - saved = filecontent; - if (!data.isNull()) { - globalAquiredSize += int64(data.width()) * data.height() * 4; - } +Image *getImage(const QPixmap &pixmap, QByteArray format) { + return new Image(pixmap, format); } -const QPixmap &LocalImage::pixData() const { - return data; +Image *getImage(const QByteArray &filecontent, QByteArray format, const QPixmap &pixmap) { + return new Image(filecontent, format, pixmap); } -int32 LocalImage::width() const { - restore(); - return data.width(); -} - -int32 LocalImage::height() const { - restore(); - return data.height(); -} - -LocalImage::~LocalImage() { - if (!data.isNull()) { - globalAquiredSize -= int64(data.width()) * data.height() * 4; - } -} - -LocalImage *getImage(const QString &file, QByteArray format) { - QFileInfo f(file); - QString key = qsl("//:%1//:%2//:").arg(f.size()).arg(f.lastModified().toTime_t()) + file; - LocalImages::const_iterator i = localImages.constFind(key); - if (i == localImages.cend()) { - i = localImages.insert(key, new LocalImage(file, format)); - } - return i.value(); -} - -LocalImage *getImage(const QByteArray &filecontent, QByteArray format) { - return new LocalImage(filecontent, format); -} - -LocalImage *getImage(const QPixmap &pixmap, QByteArray format) { - return new LocalImage(pixmap, format); -} - -LocalImage *getImage(const QByteArray &filecontent, QByteArray format, const QPixmap &pixmap) { - return new LocalImage(filecontent, format, pixmap); +Image *getImage(int32 width, int32 height) { + return new DelayedStorageImage(width, height); } void clearStorageImages() { @@ -587,6 +613,10 @@ void clearStorageImages() { delete i.value(); } storageImages.clear(); + for (WebImages::const_iterator i = webImages.cbegin(), e = webImages.cend(); i != e; ++i) { + delete i.value(); + } + webImages.clear(); } void clearAllImages() { @@ -598,100 +628,268 @@ void clearAllImages() { } int64 imageCacheSize() { - return globalAquiredSize; + return globalAcquiredSize; } -StorageImage::StorageImage(const StorageImageLocation &location, int32 size) : w(location.width), h(location.height), loader(new mtpFileLoader(location.dc, location.volume, location.local, location.secret, size)) { +void RemoteImage::doCheckload() const { + if (!amLoading() || !_loader->done()) return; + + QPixmap data = _loader->imagePixmap(); + if (data.isNull()) { + _loader->deleteLater(); + _loader->stop(); + _loader = CancelledFileLoader; + return; + } + + if (!_data.isNull()) { + globalAcquiredSize -= int64(_data.width()) * _data.height() * 4; + } + + _format = _loader->imageFormat(); + _data = data; + _saved = _loader->bytes(); + const_cast(this)->setInformation(_saved.size(), _data.width(), _data.height()); + globalAcquiredSize += int64(_data.width()) * _data.height() * 4; + + invalidateSizeCache(); + + _loader->deleteLater(); + _loader->stop(); + _loader = 0; + + _forgot = false; } -StorageImage::StorageImage(const StorageImageLocation &location, QByteArray &bytes) : w(location.width), h(location.height), loader(0) { - setData(bytes); - if (location.dc) { - Local::writeImage(storageKey(location.dc, location.volume, location.local), StorageImageSaved(mtpToStorageType(mtpc_storage_filePartial), bytes)); +void RemoteImage::loadLocal() { + if (loaded() || amLoading()) return; + + _loader = createLoader(LoadFromLocalOnly, true); + if (_loader) _loader->start(); +} + +void RemoteImage::setData(QByteArray &bytes, const QByteArray &bytesFormat) { + QBuffer buffer(&bytes); + + if (!_data.isNull()) { + globalAcquiredSize -= int64(_data.width()) * _data.height() * 4; + } + QByteArray fmt(bytesFormat); + _data = QPixmap::fromImage(App::readImage(bytes, &fmt, false), Qt::ColorOnly); + if (!_data.isNull()) { + globalAcquiredSize += int64(_data.width()) * _data.height() * 4; + setInformation(bytes.size(), _data.width(), _data.height()); + } + + invalidateSizeCache(); + if (amLoading()) { + _loader->deleteLater(); + _loader->stop(); + _loader = 0; + } + _saved = bytes; + _format = fmt; + _forgot = false; +} + +void RemoteImage::automaticLoad(const HistoryItem *item) { + if (loaded()) return; + + if (_loader != CancelledFileLoader && item) { + bool loadFromCloud = false; + if (item->history()->peer->isUser()) { + loadFromCloud = !(cAutoDownloadPhoto() & dbiadNoPrivate); + } else { + loadFromCloud = !(cAutoDownloadPhoto() & dbiadNoGroups); + } + + if (_loader) { + if (loadFromCloud) _loader->permitLoadFromCloud(); + } else { + _loader = createLoader(loadFromCloud ? LoadFromCloudOrLocal : LoadFromLocalOnly, true); + if (_loader) _loader->start(); + } } } -const QPixmap &StorageImage::pixData() const { - return data; +void RemoteImage::automaticLoadSettingsChanged() { + if (loaded() || _loader != CancelledFileLoader) return; + _loader = 0; +} + +void RemoteImage::load(bool loadFirst, bool prior) { + if (loaded()) return; + + if (!_loader) { + _loader = createLoader(LoadFromCloudOrLocal, false); + } + if (amLoading()) { + _loader->start(loadFirst, prior); + } +} + +void RemoteImage::loadEvenCancelled(bool loadFirst, bool prior) { + if (_loader == CancelledFileLoader) _loader = 0; + return load(loadFirst, prior); +} + +RemoteImage::~RemoteImage() { + if (!_data.isNull()) { + globalAcquiredSize -= int64(_data.width()) * _data.height() * 4; + } + if (amLoading()) { + _loader->deleteLater(); + _loader->stop(); + _loader = 0; + } +} + +bool RemoteImage::loaded() const { + doCheckload(); + return (!_data.isNull() || !_saved.isNull()); +} + +bool RemoteImage::displayLoading() const { + return amLoading() && (!_loader->loadingLocal() || !_loader->autoLoading()); +} + +void RemoteImage::cancel() { + if (!amLoading()) return; + + FileLoader *l = _loader; + _loader = CancelledFileLoader; + if (l) { + l->cancel(); + l->deleteLater(); + l->stop(); + } +} + +float64 RemoteImage::progress() const { + return amLoading() ? _loader->currentProgress() : (loaded() ? 1 : 0); +} + +int32 RemoteImage::loadOffset() const { + return amLoading() ? _loader->currentOffset() : 0; +} + +StorageImage::StorageImage(const StorageImageLocation &location, int32 size) + : _location(location) + , _size(size) { +} + +StorageImage::StorageImage(const StorageImageLocation &location, QByteArray &bytes) + : _location(location) + , _size(bytes.size()) { + setData(bytes); + if (!_location.isNull()) { + Local::writeImage(storageKey(_location), StorageImageSaved(mtpToStorageType(mtpc_storage_filePartial), bytes)); + } } int32 StorageImage::width() const { - return w; + return _location.width(); } int32 StorageImage::height() const { - return h; + return _location.height(); } -bool StorageImage::check() const { - if (loader->done()) { - if (!data.isNull()) { - globalAquiredSize -= int64(data.width()) * data.height() * 4; +void StorageImage::setInformation(int32 size, int32 width, int32 height) { + _size = size; + _location.setSize(width, height); +} + +FileLoader *StorageImage::createLoader(LoadFromCloudSetting fromCloud, bool autoLoading) { + if (_location.isNull()) return 0; + return new mtpFileLoader(&_location, _size, fromCloud, autoLoading); +} + +DelayedStorageImage::DelayedStorageImage() : StorageImage(StorageImageLocation()) +, _loadRequested(false) +, _loadCancelled(false) +, _loadFromCloud(false) { +} + +DelayedStorageImage::DelayedStorageImage(int32 w, int32 h) : StorageImage(StorageImageLocation(w, h, 0, 0, 0, 0)) +, _loadRequested(false) +, _loadCancelled(false) +, _loadFromCloud(false) { +} + +DelayedStorageImage::DelayedStorageImage(QByteArray &bytes) : StorageImage(StorageImageLocation(), bytes) +, _loadRequested(false) +, _loadCancelled(false) +, _loadFromCloud(false) { +} + +void DelayedStorageImage::setStorageLocation(const StorageImageLocation location) { + _location = location; + if (_loadRequested) { + if (!_loadCancelled) { + if (_loadFromCloud) { + load(); + } else { + loadLocal(); + } } - format = loader->imageFormat(); - data = loader->imagePixmap(); - QByteArray bytes = loader->bytes(); - if (!data.isNull()) { - globalAquiredSize += int64(data.width()) * data.height() * 4; + _loadRequested = false; + } +} + +void DelayedStorageImage::automaticLoad(const HistoryItem *item) { + if (_location.isNull()) { + if (!_loadCancelled && item) { + bool loadFromCloud = false; + if (item->history()->peer->isUser()) { + loadFromCloud = !(cAutoDownloadPhoto() & dbiadNoPrivate); + } else { + loadFromCloud = !(cAutoDownloadPhoto() & dbiadNoGroups); + } + + if (_loadRequested) { + if (loadFromCloud) _loadFromCloud = loadFromCloud; + } else { + _loadFromCloud = loadFromCloud; + _loadRequested = true; + } } - - w = data.width(); - h = data.height(); - invalidateSizeCache(); - loader->deleteLater(); - loader->rpcInvalidate(); - loader = 0; - - saved = bytes; - forgot = false; - return true; - } - return false; -} - -void StorageImage::setData(QByteArray &bytes, const QByteArray &format) { - QBuffer buffer(&bytes); - - if (!data.isNull()) { - globalAquiredSize -= int64(data.width()) * data.height() * 4; - } - QByteArray fmt(format); - data = QPixmap::fromImage(App::readImage(bytes, &fmt, false), Qt::ColorOnly); - if (!data.isNull()) { - globalAquiredSize += int64(data.width()) * data.height() * 4; - } - - w = data.width(); - h = data.height(); - invalidateSizeCache(); - if (loader) { - loader->deleteLater(); - loader->rpcInvalidate(); - loader = 0; - } - this->saved = bytes; - this->format = fmt; - forgot = false; -} - -StorageImage::~StorageImage() { - if (!data.isNull()) { - globalAquiredSize -= int64(data.width()) * data.height() * 4; - } - if (loader) { - loader->deleteLater(); - loader->rpcInvalidate(); - loader = 0; + } else { + StorageImage::automaticLoad(item); } } -bool StorageImage::loaded() const { - if (!loader) return true; - return check(); +void DelayedStorageImage::automaticLoadSettingsChanged() { + if (_loadCancelled) _loadCancelled = false; + StorageImage::automaticLoadSettingsChanged(); +} + +void DelayedStorageImage::load(bool loadFirst, bool prior) { + if (_location.isNull()) { + _loadRequested = _loadFromCloud = true; + } else { + StorageImage::load(loadFirst, prior); + } +} + +void DelayedStorageImage::loadEvenCancelled(bool loadFirst, bool prior) { + _loadCancelled = false; + StorageImage::loadEvenCancelled(loadFirst, prior); +} + +bool DelayedStorageImage::displayLoading() const { + return _location.isNull() ? true : StorageImage::displayLoading(); +} + +void DelayedStorageImage::cancel() { + if (_loadRequested) { + _loadRequested = false; + } + StorageImage::cancel(); } StorageImage *getImage(const StorageImageLocation &location, int32 size) { - StorageKey key(storageKey(location.dc, location.volume, location.local)); + StorageKey key(storageKey(location)); StorageImages::const_iterator i = storageImages.constFind(key); if (i == storageImages.cend()) { i = storageImages.insert(key, new StorageImage(location, size)); @@ -700,7 +898,7 @@ StorageImage *getImage(const StorageImageLocation &location, int32 size) { } StorageImage *getImage(const StorageImageLocation &location, const QByteArray &bytes) { - StorageKey key(storageKey(location.dc, location.volume, location.local)); + StorageKey key(storageKey(location)); StorageImages::const_iterator i = storageImages.constFind(key); if (i == storageImages.cend()) { QByteArray bytesArr(bytes); @@ -708,13 +906,35 @@ StorageImage *getImage(const StorageImageLocation &location, const QByteArray &b } else if (!i.value()->loaded()) { QByteArray bytesArr(bytes); i.value()->setData(bytesArr); - if (location.dc) { - Local::writeImage(storageKey(location.dc, location.volume, location.local), StorageImageSaved(mtpToStorageType(mtpc_storage_filePartial), bytes)); + if (!location.isNull()) { + Local::writeImage(key, StorageImageSaved(mtpToStorageType(mtpc_storage_filePartial), bytes)); } } return i.value(); } + +WebImage::WebImage(const QString &url) : _url(url), _size(0), _width(0), _height(0) { +} + +int32 WebImage::width() const { + return _width; +} + +int32 WebImage::height() const { + return _height; +} + +void WebImage::setInformation(int32 size, int32 width, int32 height) { + _size = size; + _width = width; + _height = height; +} + +FileLoader *WebImage::createLoader(LoadFromCloudSetting fromCloud, bool autoLoading) { + return new webFileLoader(_url, QString(), fromCloud, autoLoading); +} + ReadAccessEnabler::ReadAccessEnabler(const PsFileBookmark *bookmark) : _bookmark(bookmark), _failed(_bookmark ? !_bookmark->enable() : false) { } diff --git a/Telegram/SourceFiles/gui/images.h b/Telegram/SourceFiles/gui/images.h index 384da3da14..5ef9d93282 100644 --- a/Telegram/SourceFiles/gui/images.h +++ b/Telegram/SourceFiles/gui/images.h @@ -25,87 +25,187 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org QImage imageBlur(QImage img); void imageRound(QImage &img); -struct StorageImageLocation { - StorageImageLocation() : width(0), height(0), dc(0), volume(0), local(0), secret(0) { +inline uint32 packInt(int32 a) { + return (a < 0) ? uint32(int64(a) + 0x100000000LL) : uint32(a); +} +inline int32 unpackInt(uint32 a) { + return (a > 0x7FFFFFFFU) ? int32(int64(a) - 0x100000000LL) : int32(a); +} +inline uint64 packUIntUInt(uint32 a, uint32 b) { + return (uint64(a) << 32) | uint64(b); +} +inline uint64 packUIntInt(uint32 a, int32 b) { + return packUIntUInt(a, packInt(b)); +} +inline uint64 packIntUInt(int32 a, uint32 b) { + return packUIntUInt(packInt(a), b); +} +inline uint64 packIntInt(int32 a, int32 b) { + return packUIntUInt(packInt(a), packInt(b)); +} +inline uint32 unpackUIntFirst(uint64 v) { + return uint32(v >> 32); +} +inline int32 unpackIntFirst(uint64 v) { + return unpackInt(unpackUIntFirst(v)); +} +inline uint32 unpackUIntSecond(uint64 v) { + return uint32(v & 0xFFFFFFFFULL); +} +inline int32 unpackIntSecond(uint64 v) { + return unpackInt(unpackUIntSecond(v)); +} + +class StorageImageLocation { +public: + StorageImageLocation() : _widthheight(0), _dclocal(0), _volume(0), _secret(0) { } - StorageImageLocation(int32 width, int32 height, int32 dc, const uint64 &volume, int32 local, const uint64 &secret) : width(width), height(height), dc(dc), volume(volume), local(local), secret(secret) { + StorageImageLocation(int32 width, int32 height, int32 dc, const uint64 &volume, int32 local, const uint64 &secret) : _widthheight(packIntInt(width, height)), _dclocal(packIntInt(dc, local)), _volume(volume), _secret(secret) { } - StorageImageLocation(int32 width, int32 height, const MTPDfileLocation &location) : width(width), height(height), dc(location.vdc_id.v), volume(location.vvolume_id.v), local(location.vlocal_id.v), secret(location.vsecret.v) { + StorageImageLocation(int32 width, int32 height, const MTPDfileLocation &location) : _widthheight(packIntInt(width, height)), _dclocal(packIntInt(location.vdc_id.v, location.vlocal_id.v)), _volume(location.vvolume_id.v), _secret(location.vsecret.v) { } bool isNull() const { - return !dc; + return !_dclocal; } - int32 width, height; - int32 dc; - uint64 volume; - int32 local; - uint64 secret; + int32 width() const { + return unpackIntFirst(_widthheight); + } + int32 height() const { + return unpackIntSecond(_widthheight); + } + void setSize(int32 width, int32 height) { + _widthheight = packIntInt(width, height); + } + int32 dc() const { + return unpackIntFirst(_dclocal); + } + uint64 volume() const { + return _volume; + } + int32 local() const { + return unpackIntSecond(_dclocal); + } + uint64 secret() const { + return _secret; + } + + static StorageImageLocation Null; + +private: + uint64 _widthheight; + uint64 _dclocal; + uint64 _volume; + uint64 _secret; + + friend inline bool operator==(const StorageImageLocation &a, const StorageImageLocation &b) { + return (a._dclocal == b._dclocal) && (a._volume == b._volume) && (a._secret == b._secret); + } + }; -inline bool operator==(const StorageImageLocation &a, const StorageImageLocation &b) { - return (a.width == b.width) && (a.height == b.height) && (a.dc == b.dc) && (a.volume == b.volume) && (a.local == b.local) && (a.secret == b.secret); -} inline bool operator!=(const StorageImageLocation &a, const StorageImageLocation &b) { return !(a == b); } +QPixmap imagePix(QImage img, int32 w, int32 h, bool smooth, bool blurred, bool rounded, int32 outerw, int32 outerh); + +class DelayedStorageImage; + +class HistoryItem; class Image { public: - Image(QByteArray format = "PNG") : format(format), forgot(false) { + Image(const QString &file, QByteArray format = QByteArray()); + Image(const QByteArray &filecontent, QByteArray format = QByteArray()); + Image(const QPixmap &pixmap, QByteArray format = QByteArray()); + Image(const QByteArray &filecontent, QByteArray format, const QPixmap &pixmap); + + virtual void automaticLoad(const HistoryItem *item) { // auto load photo } + virtual void automaticLoadSettingsChanged() { + } + virtual bool loaded() const { return true; } virtual bool loading() const { return false; } + virtual bool displayLoading() const { + return false; + } + virtual void cancel() { + } + virtual float64 progress() const { + return 1; + } + virtual int32 loadOffset() const { + return 0; + } + const QPixmap &pix(int32 w = 0, int32 h = 0) const; const QPixmap &pixRounded(int32 w = 0, int32 h = 0) const; const QPixmap &pixBlurred(int32 w = 0, int32 h = 0) const; const QPixmap &pixColored(const style::color &add, int32 w = 0, int32 h = 0) const; const QPixmap &pixBlurredColored(const style::color &add, int32 w = 0, int32 h = 0) const; - const QPixmap &pixSingle(int32 w, int32 y, int32 outerw, int32 outerh) const; + const QPixmap &pixSingle(int32 w, int32 h, int32 outerw, int32 outerh) const; const QPixmap &pixBlurredSingle(int32 w, int32 h, int32 outerw, int32 outerh) const; QPixmap pixNoCache(int32 w = 0, int32 h = 0, bool smooth = false, bool blurred = false, bool rounded = false, int32 outerw = -1, int32 outerh = -1) const; QPixmap pixColoredNoCache(const style::color &add, int32 w = 0, int32 h = 0, bool smooth = false) const; QPixmap pixBlurredColoredNoCache(const style::color &add, int32 w, int32 h = 0) const; - virtual int32 width() const = 0; - virtual int32 height() const = 0; - - virtual void load(bool /*loadFirst*/ = false, bool /*prior*/ = true) { + virtual int32 width() const { + restore(); + return _data.width(); } - virtual void checkload() const { + virtual int32 height() const { + restore(); + return _data.height(); + } + + virtual void load(bool loadFirst = false, bool prior = true) { + } + + virtual void loadEvenCancelled(bool loadFirst = false, bool prior = true) { + } + + virtual const StorageImageLocation &location() const { + return StorageImageLocation::Null; } bool isNull() const; - + void forget() const; - void restore() const; QByteArray savedFormat() const { - return format; + return _format; } QByteArray savedData() const { - return saved; + return _saved; } - virtual ~Image() { - invalidateSizeCache(); + virtual DelayedStorageImage *toDelayedStorageImage() { + return 0; } + virtual const DelayedStorageImage *toDelayedStorageImage() const { + return 0; + } + + virtual ~Image(); protected: + Image(QByteArray format = "PNG") : _format(format), _forgot(false) { + } - virtual const QPixmap &pixData() const = 0; - virtual void doForget() const = 0; - virtual void doRestore() const = 0; - + void restore() const; + virtual void checkload() const { + } void invalidateSizeCache() const; - mutable QByteArray saved, format; - mutable bool forgot; + mutable QByteArray _saved, _format; + mutable bool _forgot; + mutable QPixmap _data; private: @@ -114,40 +214,11 @@ private: }; -class LocalImage : public Image { -public: - - LocalImage(const QString &file, QByteArray format = QByteArray()); - LocalImage(const QByteArray &filecontent, QByteArray format = QByteArray()); - LocalImage(const QPixmap &pixmap, QByteArray format = QByteArray()); - LocalImage(const QByteArray &filecontent, QByteArray format, const QPixmap &pixmap); - - int32 width() const; - int32 height() const; - - ~LocalImage(); - -protected: - - const QPixmap &pixData() const; - void doForget() const { - data = QPixmap(); - } - void doRestore() const { - QBuffer buffer(&saved); - QImageReader reader(&buffer, format); - data = QPixmap::fromImageReader(&reader, Qt::ColorOnly); - } - -private: - - mutable QPixmap data; -}; - -LocalImage *getImage(const QString &file, QByteArray format); -LocalImage *getImage(const QByteArray &filecontent, QByteArray format); -LocalImage *getImage(const QPixmap &pixmap, QByteArray format); -LocalImage *getImage(const QByteArray &filecontent, QByteArray format, const QPixmap &pixmap); +Image *getImage(const QString &file, QByteArray format); +Image *getImage(const QByteArray &filecontent, QByteArray format); +Image *getImage(const QPixmap &pixmap, QByteArray format); +Image *getImage(const QByteArray &filecontent, QByteArray format, const QPixmap &pixmap); +Image *getImage(int32 width, int32 height); typedef QPair StorageKey; inline uint64 storageMix32To64(int32 a, int32 b) { @@ -160,64 +231,130 @@ inline StorageKey storageKey(const MTPDfileLocation &location) { return storageKey(location.vdc_id.v, location.vvolume_id.v, location.vlocal_id.v); } inline StorageKey storageKey(const StorageImageLocation &location) { - return storageKey(location.dc, location.volume, location.local); + return storageKey(location.dc(), location.volume(), location.local()); } -class StorageImage : public Image { +class RemoteImage : public Image { +public: + + RemoteImage() : _loader(0) { + } + + void automaticLoad(const HistoryItem *item); // auto load photo + void automaticLoadSettingsChanged(); + + bool loaded() const; + bool loading() const { + return amLoading(); + } + bool displayLoading() const; + void cancel(); + float64 progress() const; + int32 loadOffset() const; + + void setData(QByteArray &bytes, const QByteArray &format = QByteArray()); + + void load(bool loadFirst = false, bool prior = true); + void loadEvenCancelled(bool loadFirst = false, bool prior = true); + + virtual void setInformation(int32 size, int32 width, int32 height) = 0; + virtual FileLoader *createLoader(LoadFromCloudSetting fromCloud, bool autoLoading) = 0; + + ~RemoteImage(); + +protected: + void checkload() const { + doCheckload(); + } + void loadLocal(); + +private: + mutable FileLoader *_loader; + bool amLoading() const { + return _loader && _loader != CancelledFileLoader; + } + void doCheckload() const; + +}; + +class StorageImage : public RemoteImage { public: StorageImage(const StorageImageLocation &location, int32 size = 0); StorageImage(const StorageImageLocation &location, QByteArray &bytes); - + int32 width() const; int32 height() const; - bool loaded() const; - bool loading() const { - return loader ? loader->loading() : false; - } - void setData(QByteArray &bytes, const QByteArray &format = QByteArray()); - void load(bool loadFirst = false, bool prior = true) { - if (loader) { - loader->start(loadFirst, prior); - if (loader) check(); - } - } - void checkload() const { - if (loader) { - if (!loader->loading()) { - loader->start(true); - } - if (loader) check(); - } - } + virtual void setInformation(int32 size, int32 width, int32 height); + virtual FileLoader *createLoader(LoadFromCloudSetting fromCloud, bool autoLoading); - ~StorageImage(); + virtual const StorageImageLocation &location() const { + return _location; + } protected: + StorageImageLocation _location; + int32 _size; - const QPixmap &pixData() const; - bool check() const; - void doForget() const { - data = QPixmap(); +}; + +class DelayedStorageImage : public StorageImage { +public: + + DelayedStorageImage(); + DelayedStorageImage(int32 w, int32 h); + DelayedStorageImage(QByteArray &bytes); + + void setStorageLocation(const StorageImageLocation location); + + virtual DelayedStorageImage *toDelayedStorageImage() { + return this; } - void doRestore() const { - QBuffer buffer(&saved); - QImageReader reader(&buffer, format); - data = QPixmap::fromImageReader(&reader, Qt::ColorOnly); + virtual const DelayedStorageImage *toDelayedStorageImage() const { + return this; } + void automaticLoad(const HistoryItem *item); // auto load photo + void automaticLoadSettingsChanged(); + + bool loading() const { + return _location.isNull() ? _loadRequested : StorageImage::loading(); + } + bool displayLoading() const; + void cancel(); + + void load(bool loadFirst = false, bool prior = true); + void loadEvenCancelled(bool loadFirst = false, bool prior = true); + private: + bool _loadRequested, _loadCancelled, _loadFromCloud; - mutable QPixmap data; - mutable int32 w, h; - mutable mtpFileLoader *loader; }; StorageImage *getImage(const StorageImageLocation &location, int32 size = 0); StorageImage *getImage(const StorageImageLocation &location, const QByteArray &bytes); Image *getImage(int32 width, int32 height, const MTPFileLocation &location); +class WebImage : public RemoteImage { +public: + + WebImage(const QString &url); + + int32 width() const; + int32 height() const; + + virtual void setInformation(int32 size, int32 width, int32 height); + virtual FileLoader *createLoader(LoadFromCloudSetting fromCloud, bool autoLoading); + +private: + QString _url; + int32 _size, _width, _height; + +}; + +WebImage *getImage(const QUrl &url); + class ImagePtr : public ManagedPtr { public: ImagePtr(); @@ -234,8 +371,22 @@ public: ImagePtr(const StorageImageLocation &location, const QByteArray &bytes) : Parent(getImage(location, bytes)) { } ImagePtr(int32 width, int32 height, const MTPFileLocation &location, ImagePtr def = ImagePtr()); + ImagePtr(int32 width, int32 height) : Parent(getImage(width, height)) { + } }; +inline QSize resizeKeepAspect(int32 width, int32 height, int32 towidth, int32 toheight) { + int32 w = qMax(width, 1), h = qMax(height, 1); + if (w * toheight > h * towidth) { + h = qRound(h * towidth / float64(w)); + w = towidth; + } else { + w = qRound(w * toheight / float64(h)); + h = toheight; + } + return QSize(qMax(w, 1), qMax(h, 1)); +} + void clearStorageImages(); void clearAllImages(); int64 imageCacheSize(); diff --git a/Telegram/SourceFiles/gui/popupmenu.cpp b/Telegram/SourceFiles/gui/popupmenu.cpp index 17f96535b2..21fefa3e1d 100644 --- a/Telegram/SourceFiles/gui/popupmenu.cpp +++ b/Telegram/SourceFiles/gui/popupmenu.cpp @@ -36,7 +36,7 @@ PopupMenu::PopupMenu(const style::PopupMenu &st) : TWidget(0) , _selected(-1) , _childMenuIndex(-1) , a_opacity(1) -, _a_hide(animFunc(this, &PopupMenu::animStep_hide)) +, _a_hide(animation(this, &PopupMenu::step_hide)) , _deleteOnHide(true) , _triggering(false) , _deleteLater(false) { @@ -54,7 +54,7 @@ PopupMenu::PopupMenu(QMenu *menu, const style::PopupMenu &st) : TWidget(0) , _selected(-1) , _childMenuIndex(-1) , a_opacity(1) -, _a_hide(animFunc(this, &PopupMenu::animStep_hide)) +, _a_hide(animation(this, &PopupMenu::step_hide)) , _deleteOnHide(true) , _triggering(false) , _deleteLater(false) { @@ -440,18 +440,16 @@ void PopupMenu::hideFinish() { hide(); } -bool PopupMenu::animStep_hide(float64 ms) { +void PopupMenu::step_hide(float64 ms, bool timer) { float64 dt = ms / _st.duration; - bool res = true; if (dt >= 1) { + _a_hide.stop(); a_opacity.finish(); hideFinish(); - res = false; } else { a_opacity.update(dt, anim::linear); } - update(); - return res; + if (timer) update(); } void PopupMenu::deleteOnHide(bool del) { diff --git a/Telegram/SourceFiles/gui/popupmenu.h b/Telegram/SourceFiles/gui/popupmenu.h index 887f15bd6c..121984e830 100644 --- a/Telegram/SourceFiles/gui/popupmenu.h +++ b/Telegram/SourceFiles/gui/popupmenu.h @@ -59,7 +59,7 @@ private: void childHiding(PopupMenu *child); - bool animStep_hide(float64 ms); + void step_hide(float64 ms, bool timer); void init(); void hideFinish(); diff --git a/Telegram/SourceFiles/gui/scrollarea.cpp b/Telegram/SourceFiles/gui/scrollarea.cpp index 0f76852b68..9fdc86a76f 100644 --- a/Telegram/SourceFiles/gui/scrollarea.cpp +++ b/Telegram/SourceFiles/gui/scrollarea.cpp @@ -38,12 +38,20 @@ void ScrollShadow::changeVisibility(bool shown) { setVisible(shown); } -ScrollBar::ScrollBar(ScrollArea *parent, bool vert, const style::flatScroll *st) : QWidget(parent), _st(st), _vertical(vert), - _over(false), _overbar(false), _moving(false), _topSh(false), _bottomSh(false), - _connected(vert ? parent->verticalScrollBar() : parent->horizontalScrollBar()), - _scrollMax(_connected->maximum()), _hideIn(-1), - a_bg((_st->hiding ? st::transparent : _st->bgColor)->c), - a_bar((_st->hiding ? st::transparent : _st->barColor)->c) { +ScrollBar::ScrollBar(ScrollArea *parent, bool vert, const style::flatScroll *st) : QWidget(parent) +, _st(st) +, _vertical(vert) +, _over(false) +, _overbar(false) +, _moving(false) +, _topSh(false) +, _bottomSh(false) +, _connected(vert ? parent->verticalScrollBar() : parent->horizontalScrollBar()) +, _scrollMax(_connected->maximum()) +, _hideIn(-1) +, a_bg((_st->hiding ? st::transparent : _st->bgColor)->c) +, a_bar((_st->hiding ? st::transparent : _st->barColor)->c) +, _a_appearance(animation(this, &ScrollBar::step_appearance)) { recountSize(); _hideTimer.setSingleShot(true); @@ -115,7 +123,7 @@ void ScrollBar::onHideTimer() { _hideIn = -1; a_bg.start(QColor(a_bg.current().red(), a_bg.current().green(), a_bg.current().blue(), 0)); a_bar.start(QColor(a_bar.current().red(), a_bar.current().green(), a_bar.current().blue(), 0)); - anim::start(this); + _a_appearance.start(); } ScrollArea *ScrollBar::area() { @@ -144,26 +152,24 @@ void ScrollBar::paintEvent(QPaintEvent *e) { } } -bool ScrollBar::animStep(float64 ms) { +void ScrollBar::step_appearance(float64 ms, bool timer) { float64 dt = ms / _st->duration; - bool res = true; if (dt >= 1) { + _a_appearance.stop(); a_bg.finish(); a_bar.finish(); - res = false; } else { a_bg.update(dt, anim::linear); a_bar.update(dt, anim::linear); } - update(); - return res; + if (timer) update(); } void ScrollBar::hideTimeout(int64 dt) { if (_hideIn < 0) { a_bg.start((_over ? _st->bgOverColor : _st->bgColor)->c); a_bar.start((_overbar ? _st->barOverColor : _st->barColor)->c); - anim::start(this); + _a_appearance.start(); } _hideIn = dt; if (!_moving && _hideIn >= 0) { @@ -177,7 +183,7 @@ void ScrollBar::enterEvent(QEvent *e) { _over = true; a_bg.start(_st->bgOverColor->c); a_bar.start(_st->barColor->c); - anim::start(this); + _a_appearance.start(); } void ScrollBar::leaveEvent(QEvent *e) { @@ -185,7 +191,7 @@ void ScrollBar::leaveEvent(QEvent *e) { setMouseTracking(false); a_bg.start(_st->bgColor->c); a_bar.start(_st->barColor->c); - anim::start(this); + _a_appearance.start(); if (_hideIn >= 0) { _hideTimer.start(_hideIn); } else if (_st->hiding) { @@ -202,7 +208,7 @@ void ScrollBar::mouseMoveEvent(QMouseEvent *e) { if (!_moving) { a_bar.start((newOverBar ? _st->barOverColor : _st->barColor)->c); a_bg.start(_st->bgOverColor->c); - anim::start(this); + _a_appearance.start(); } } if (_moving) { @@ -232,7 +238,7 @@ void ScrollBar::mousePressEvent(QMouseEvent *e) { _overbar = true; a_bar.start(_st->barOverColor->c); a_bg.start(_st->bgOverColor->c); - anim::start(this); + _a_appearance.start(); } } emit area()->scrollStarted(); @@ -257,7 +263,7 @@ void ScrollBar::mouseReleaseEvent(QMouseEvent *e) { _hideTimer.start(_hideIn); } } - if (a) anim::start(this); + if (a) _a_appearance.start(); emit area()->scrollFinished(); } if (!_over) { diff --git a/Telegram/SourceFiles/gui/scrollarea.h b/Telegram/SourceFiles/gui/scrollarea.h index 4029ffc25c..76add3f2ba 100644 --- a/Telegram/SourceFiles/gui/scrollarea.h +++ b/Telegram/SourceFiles/gui/scrollarea.h @@ -50,7 +50,7 @@ private: }; -class ScrollBar : public QWidget, public Animated { +class ScrollBar : public QWidget { Q_OBJECT public: @@ -67,7 +67,7 @@ public: void mouseReleaseEvent(QMouseEvent *e); void resizeEvent(QResizeEvent *e); - bool animStep(float64 ms); + void step_appearance(float64 ms, bool timer); void hideTimeout(int64 dt); @@ -100,6 +100,8 @@ private: QTimer _hideTimer; anim::cvalue a_bg, a_bar; + Animation _a_appearance; + QRect _bar; }; diff --git a/Telegram/SourceFiles/gui/style_core.h b/Telegram/SourceFiles/gui/style_core.h index 8939118bc8..8642a701ac 100644 --- a/Telegram/SourceFiles/gui/style_core.h +++ b/Telegram/SourceFiles/gui/style_core.h @@ -304,6 +304,14 @@ namespace style { typedef Font font; typedef Color color; + inline QColor interpolate(const style::color &a, const style::color &b, float64 opacity_b) { + QColor result; + result.setRedF((a->c.redF() * (1. - opacity_b)) + (b->c.redF() * opacity_b)); + result.setGreenF((a->c.greenF() * (1. - opacity_b)) + (b->c.greenF() * opacity_b)); + result.setBlueF((a->c.blueF() * (1. - opacity_b)) + (b->c.blueF() * opacity_b)); + return result; + } + void startManager(); void stopManager(); diff --git a/Telegram/SourceFiles/gui/switcher.cpp b/Telegram/SourceFiles/gui/switcher.cpp deleted file mode 100644 index e0e9173c3e..0000000000 --- a/Telegram/SourceFiles/gui/switcher.cpp +++ /dev/null @@ -1,160 +0,0 @@ -/* -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-2015 John Preston, https://desktop.telegram.org -*/ -#include "stdafx.h" -#include "switcher.h" - -Switcher::Switcher(QWidget *parent, const style::switcher &st) : TWidget(parent) -, _selected(0) -, _over(-1) -, _wasOver(-1) -, _pressed(-1) -, _st(st) -, a_bgOver(_st.bgColor->c) -, a_bgWasOver(_st.bgHovered->c) { - resize(width(), _st.height); -} - -void Switcher::leaveEvent(QEvent *e) { - setOver(-1); - if (_pressed >= 0) return; - - setMouseTracking(false); - return TWidget::leaveEvent(e); -} - -void Switcher::enterEvent(QEvent *e) { - setMouseTracking(true); - return TWidget::enterEvent(e); -} - -void Switcher::mousePressEvent(QMouseEvent *e) { - if (e->buttons() & Qt::LeftButton) { - mouseMoveEvent(e); - if (_over != _pressed) { - _pressed = _over; - e->accept(); - } - } -} - -void Switcher::mouseMoveEvent(QMouseEvent *e) { - if (rect().contains(e->pos())) { - if (width()) { - setOver((e->pos().x() * _buttons.size()) / width()); - } - } else { - setOver(-1); - } -} - -void Switcher::mouseReleaseEvent(QMouseEvent *e) { - if (_pressed >= 0) { - if (_pressed == _over && _pressed != _selected) { - setSelected(_pressed); - } else { - setSelected(_selected); - } - } else { - leaveEvent(e); - } -} - -void Switcher::addButton(const QString &btn) { - _buttons.push_back(btn); - update(); -} - -bool Switcher::animStep(float64 ms) { - float64 dt = ms / _st.duration; - bool res = true; - if (dt >= 1) { - res = false; - a_bgOver.finish(); - a_bgWasOver.finish(); - } else { - a_bgOver.update(dt, anim::linear); - a_bgWasOver.update(dt, anim::linear); - } - update(); - return res; -} - -void Switcher::paintEvent(QPaintEvent *e) { - QPainter p(this); - - p.fillRect(rect(), _st.bgColor->b); - if (!_buttons.isEmpty()) { - p.setFont(_st.font->f); - float64 btnWidth = float64(width()) / _buttons.size(); - for (int i = 0; i < _buttons.size(); ++i) { - QRect btnRect(qRound(i * btnWidth), 0, qRound((i + 1) * btnWidth) - qRound(i * btnWidth), height()); - if (i == _selected) { - p.fillRect(btnRect, _st.bgActive->b); - } else if (i == _over) { - p.fillRect(btnRect, a_bgOver.current()); - } else if (i == _wasOver) { - p.fillRect(btnRect, a_bgWasOver.current()); - } - p.setPen((i == _selected ? _st.activeColor : _st.textColor)->p); - p.drawText(btnRect, _buttons[i], style::al_center); - } - } - if (_st.border) { - p.fillRect(0, 0, width() - _st.border, _st.border, _st.borderColor->b); - p.fillRect(width() - _st.border, 0, _st.border, height() - _st.border, _st.borderColor->b); - p.fillRect(_st.border, height() - _st.border, width() - _st.border, _st.border, _st.borderColor->b); - p.fillRect(0, _st.border, _st.border, height() - _st.border, _st.borderColor->b); - } -} - -int Switcher::selected() const { - return _selected; -} - -void Switcher::setSelected(int selected) { - if (selected != _selected) { - _selected = selected; - emit changed(); - } - _pressed = _over = _wasOver = -1; - anim::stop(this); - setCursor(style::cur_default); - update(); -} - -void Switcher::setOver(int over) { - if (over != _over) { - QColor c(a_bgOver.current()); - if (_wasOver == over) { - a_bgOver = anim::cvalue(a_bgWasOver.current(), _st.bgHovered->c); - } else { - a_bgOver = anim::cvalue(_st.bgColor->c, _st.bgHovered->c); - } - a_bgWasOver = anim::cvalue(c, _st.bgColor->c); - - _wasOver = _over; - _over = over; - - anim::start(this); - - setCursor((_over >= 0 && _over != _selected) ? style::cur_pointer : style::cur_default); - } -} diff --git a/Telegram/SourceFiles/gui/switcher.h b/Telegram/SourceFiles/gui/switcher.h deleted file mode 100644 index 5b633514ab..0000000000 --- a/Telegram/SourceFiles/gui/switcher.h +++ /dev/null @@ -1,65 +0,0 @@ -/* -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-2015 John Preston, https://desktop.telegram.org -*/ -#pragma once - -#include -#include "gui/twidget.h" - -class Switcher : public TWidget, public Animated { - Q_OBJECT - -public: - Switcher(QWidget *parent, const style::switcher &st); - - void mousePressEvent(QMouseEvent *e); - void mouseMoveEvent(QMouseEvent *e); - void mouseReleaseEvent(QMouseEvent *e); - - void paintEvent(QPaintEvent *e); - - void enterEvent(QEvent *e); - void leaveEvent(QEvent *e); - - void addButton(const QString &btn); - - bool animStep(float64 ms); - - int selected() const; - void setSelected(int selected); - -signals: - - void changed(); - -private: - - void setOver(int over); - - int _selected; - int _over, _wasOver, _pressed; - - typedef QVector Buttons; - Buttons _buttons; - - style::switcher _st; - anim::cvalue a_bgOver, a_bgWasOver; - -}; diff --git a/Telegram/SourceFiles/gui/text.cpp b/Telegram/SourceFiles/gui/text.cpp index 5621122078..0dad2f9f4c 100644 --- a/Telegram/SourceFiles/gui/text.cpp +++ b/Telegram/SourceFiles/gui/text.cpp @@ -36,10 +36,10 @@ namespace { const QRegularExpression _reMailName(qsl("[a-zA-Z\\-_\\.0-9]{1,256}$")); const QRegularExpression _reMailStart(qsl("^[a-zA-Z\\-_\\.0-9]{1,256}\\@")); const QRegularExpression _reHashtag(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])#[\\w]{2,64}([\\W]|$)"), QRegularExpression::UseUnicodePropertiesOption); - const QRegularExpression _reMention(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])@[A-Za-z_0-9]{5,32}([\\W]|$)"), QRegularExpression::UseUnicodePropertiesOption); + const QRegularExpression _reMention(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])@[A-Za-z_0-9]{3,32}([\\W]|$)"), QRegularExpression::UseUnicodePropertiesOption); const QRegularExpression _reBotCommand(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])/[A-Za-z_0-9]{1,64}(@[A-Za-z_0-9]{5,32})?([\\W]|$)")); - const QRegularExpression _rePre(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])(````?)[\\s\\S]+?(````?)([\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10]|$)"), QRegularExpression::UseUnicodePropertiesOption); - const QRegularExpression _reCode(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])(`)[^\\n]+?(`)([\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10]|$)"), QRegularExpression::UseUnicodePropertiesOption); + const QRegularExpression _rePre(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10])(````?)[\\s\\S]+?(````?)([\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10]|$)"), QRegularExpression::UseUnicodePropertiesOption); + const QRegularExpression _reCode(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10])(`)[^\\n]+?(`)([\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10]|$)"), QRegularExpression::UseUnicodePropertiesOption); QSet _validProtocols, _validTopDomains; const style::textStyle *_textStyle = 0; @@ -103,6 +103,10 @@ const TextLinkPtr &textlnkDown() { return _downLnk; } +bool textlnkDrawOver(const TextLinkPtr &lnk) { + return (_overLnk == lnk) && (!_downLnk || _downLnk == lnk); +} + QString textOneLine(const QString &text, bool trim, bool rich) { QString result(text); const QChar *s = text.unicode(), *ch = s, *e = text.unicode() + text.size(); @@ -259,7 +263,7 @@ const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink) class TextParser { public: - + static Qt::LayoutDirection stringDirection(const QString &str, int32 from, int32 to) { const ushort *p = reinterpret_cast(str.unicode()) + from; const ushort *end = p + (to - from); @@ -336,7 +340,7 @@ public: void getLinkData(const QString &original, QString &result, int32 &fullDisplayed) { if (!original.isEmpty() && original.at(0) == '/') { result = original; - fullDisplayed = -4; // bot command + fullDisplayed = -4; // bot command } else if (!original.isEmpty() && original.at(0) == '@') { result = original; fullDisplayed = -3; // mention @@ -450,7 +454,7 @@ public: while (waitingEntity != entitiesEnd && waitingEntity->length <= 0) ++waitingEntity; } } - + bool readCommand() { const QChar *afterCmd = textSkipCommand(ptr, end, links.size() < 0x7FFF); if (afterCmd == ptr) { @@ -594,7 +598,7 @@ public: void parseEmojiFromCurrent() { int len = 0; - EmojiPtr e = emojiFromText(ptr - emojiLookback, end, len); + EmojiPtr e = emojiFromText(ptr - emojiLookback, end, &len); if (!e) return; for (int l = len - emojiLookback - 1; l > 0; --l) { @@ -916,7 +920,7 @@ void EmailLink::onClick(Qt::MouseButton button) const { } void CustomTextLink::onClick(Qt::MouseButton button) const { - App::wnd()->showLayer(new ConfirmLinkBox(text())); + Ui::showLayer(new ConfirmLinkBox(text())); } void MentionLink::onClick(Qt::MouseButton button) const { @@ -971,7 +975,7 @@ public: void initParagraphBidi() { if (!_parLength || !_parAnalysis.isEmpty()) return; - + Text::TextBlocks::const_iterator i = _parStartBlock, e = _t->_blocks.cend(), n = i + 1; bool ignore = false; @@ -1027,6 +1031,9 @@ public: _y = top; _yFrom = yFrom + top; _yTo = (yTo < 0) ? -1 : (yTo + top); + if (_elideLast) { + _yToElide = _yTo; + } _selectedFrom = selectedFrom; _selectedTo = selectedTo; _wLeft = _w = w; @@ -1077,7 +1084,7 @@ public: last_rBearing = _rb; last_rPadding = b->f_rpadding(); _wLeft = _w - (b->f_width() - last_rBearing); - if (_elideLast && _elideRemoveFromEnd > 0 && (_y + blockHeight >= _yTo)) { + if (_elideLast && _elideRemoveFromEnd > 0 && (_y + blockHeight >= _yToElide)) { _wLeft -= _elideRemoveFromEnd; } @@ -1104,6 +1111,15 @@ public: if (_btype == TextBlockTText) { TextBlock *t = static_cast(b); + if (t->_words.isEmpty()) { // no words in this block, spaces only => layout this block in the same line + last_rPadding += lpadding; + + _lineHeight = qMax(_lineHeight, blockHeight); + + longWordLine = false; + continue; + } + QFixed f_wLeft = _wLeft; // vars for saving state of the last word start int32 f_lineHeight = _lineHeight; // f points to the last word-start element of t->_words for (TextBlock::TextWords::const_iterator j = t->_words.cbegin(), en = t->_words.cend(), f = j; j != en; ++j) { @@ -1131,7 +1147,7 @@ public: } int32 elidedLineHeight = qMax(_lineHeight, blockHeight); - bool elidedLine = _elideLast && (_y + elidedLineHeight >= _yTo); + bool elidedLine = _elideLast && (_y + elidedLineHeight >= _yToElide); if (elidedLine) { _lineHeight = elidedLineHeight; } else if (f != j) { @@ -1150,7 +1166,7 @@ public: last_rBearing = j->f_rbearing(); last_rPadding = j->rpadding; _wLeft = _w - (j_width - last_rBearing); - if (_elideLast && _elideRemoveFromEnd > 0 && (_y + blockHeight >= _yTo)) { + if (_elideLast && _elideRemoveFromEnd > 0 && (_y + blockHeight >= _yToElide)) { _wLeft -= _elideRemoveFromEnd; } @@ -1159,33 +1175,11 @@ public: f_wLeft = _wLeft; f_lineHeight = _lineHeight; } - if (lpadding > 0) { // no words in this block, spaces only - int32 elidedLineHeight = qMax(_lineHeight, blockHeight); - bool elidedLine = _elideLast && (_y + elidedLineHeight >= _yTo); - if (elidedLine) { - _lineHeight = elidedLineHeight; - } - ushort nextStart = _blockEnd(_t, i, e); - if (!drawLine(nextStart, i + 1, e)) return; - _y += _lineHeight; - _lineHeight = qMax(0, blockHeight); - _lineStart = nextStart; - _lineStartBlock = blockIndex + 1; - - last_rBearing = _rb; - last_rPadding = b->rpadding(); - _wLeft = _w; - if (_elideLast && _elideRemoveFromEnd > 0 && (_y + blockHeight >= _yTo)) { - _wLeft -= _elideRemoveFromEnd; - } - - longWordLine = true; - } continue; } int32 elidedLineHeight = qMax(_lineHeight, blockHeight); - bool elidedLine = _elideLast && (_y + elidedLineHeight >= _yTo); + bool elidedLine = _elideLast && (_y + elidedLineHeight >= _yToElide); if (elidedLine) { _lineHeight = elidedLineHeight; } @@ -1198,7 +1192,7 @@ public: last_rBearing = _rb; last_rPadding = b->f_rpadding(); _wLeft = _w - (b->f_width() - last_rBearing); - if (_elideLast && _elideRemoveFromEnd > 0 && (_y + blockHeight >= _yTo)) { + if (_elideLast && _elideRemoveFromEnd > 0 && (_y + blockHeight >= _yToElide)) { _wLeft -= _elideRemoveFromEnd; } @@ -1297,7 +1291,7 @@ public: } ITextBlock *_endBlock = (_endBlockIter == _end) ? 0 : (*_endBlockIter); - bool elidedLine = _elideLast && _endBlock && (_y + _lineHeight >= _yTo); + bool elidedLine = _elideLast && _endBlock && (_y + _lineHeight >= _yToElide); int blockIndex = _lineStartBlock; ITextBlock *currentBlock = _t->_blocks[blockIndex]; @@ -1333,7 +1327,7 @@ public: *_getSymbolAfter = false; *_getSymbolUpon = ((_lnkX >= _x) && (_lineStart > 0)) ? true : false; } - return false; + return false; } else if (_lnkX >= x + (_w - _wLeft)) { if (_parDirection == Qt::RightToLeft) { *_getSymbol = _lineStart; @@ -1385,6 +1379,7 @@ public: return true; } + int skipIndex = -1; QVarLengthArray visualOrder(nItems); QVarLengthArray levels(nItems); for (int i = 0; i < nItems; ++i) { @@ -1396,6 +1391,7 @@ public: TextBlockType _type = currentBlock->type(); if (_type == TextBlockTSkip) { levels[i] = si.analysis.bidiLevel = 0; + skipIndex = i; } else { levels[i] = si.analysis.bidiLevel; } @@ -1406,6 +1402,13 @@ public: } } QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data()); + if (rtl() && skipIndex == nItems - 1) { + for (int32 i = nItems; i > 1;) { + --i; + visualOrder[i] = visualOrder[i - 1]; + } + visualOrder[0] = skipIndex; + } blockIndex = _lineStartBlock; currentBlock = _t->_blocks[blockIndex]; @@ -2396,7 +2399,7 @@ private: int32 _elideRemoveFromEnd; style::align _align; QPen _originalPen; - int32 _yFrom, _yTo; + int32 _yFrom, _yTo, _yToElide; uint16 _selectedFrom, _selectedTo; const QChar *_str; @@ -2412,7 +2415,7 @@ private: style::font _f; QFixed _x, _w, _wLeft; int32 _y, _yDelta, _lineHeight, _fontHeight; - + // elided hack support int32 _blocksSize; int32 _elideSavedIndex; @@ -2746,8 +2749,8 @@ int32 Text::countHeight(int32 w) const { longWordLine = true; continue; } - widthLeft -= b->f_lpadding(); - QFixed newWidthLeft = widthLeft - last_rBearing - (last_rPadding + b->f_width() - _rb); + QFixed lpadding = b->f_lpadding(); + QFixed newWidthLeft = widthLeft - lpadding - last_rBearing - (last_rPadding + b->f_width() - _rb); if (newWidthLeft >= 0) { last_rBearing = _rb; last_rPadding = b->f_rpadding(); @@ -2761,13 +2764,23 @@ int32 Text::countHeight(int32 w) const { if (_btype == TextBlockTText) { TextBlock *t = static_cast(b); + if (t->_words.isEmpty()) { // no words in this block, spaces only => layout this block in the same line + last_rPadding += lpadding; + + lineHeight = qMax(lineHeight, blockHeight); + + longWordLine = false; + continue; + } + QFixed f_wLeft = widthLeft; int32 f_lineHeight = lineHeight; for (TextBlock::TextWords::const_iterator j = t->_words.cbegin(), e = t->_words.cend(), f = j; j != e; ++j) { bool wordEndsHere = (j->width >= 0); QFixed j_width = wordEndsHere ? j->width : -j->width; - QFixed newWidthLeft = widthLeft - last_rBearing - (last_rPadding + j_width - j->f_rbearing()); + QFixed newWidthLeft = widthLeft - lpadding - last_rBearing - (last_rPadding + j_width - j->f_rbearing()); + lpadding = 0; if (newWidthLeft >= 0) { last_rBearing = j->f_rbearing(); last_rPadding = j->rpadding; @@ -2897,7 +2910,7 @@ QString Text::original(uint16 selectedFrom, uint16 selectedTo, ExpandLinksMode m result.reserve(_text.size()); int32 lnkFrom = 0, lnkIndex = 0; - for (TextBlocks::const_iterator i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) { + for (TextBlocks::const_iterator i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) { int32 blockLnkIndex = (i == e) ? 0 : (*i)->lnkIndex(); int32 blockFrom = (i == e) ? _text.size() : (*i)->from(); if (blockLnkIndex != lnkIndex) { @@ -3140,7 +3153,8 @@ namespace { class BlockParser { public: - BlockParser(QTextEngine *e, TextBlock *b, QFixed minResizeWidth, int32 blockFrom) : block(b), eng(e) { + BlockParser(QTextEngine *e, TextBlock *b, QFixed minResizeWidth, int32 blockFrom, const QString &str) + : block(b), eng(e), str(str) { parseWords(minResizeWidth, blockFrom); } @@ -3218,7 +3232,7 @@ public: if (lbh.currentPosition >= eng->layoutData->string.length() || attributes[lbh.currentPosition].whiteSpace - || attributes[lbh.currentPosition].lineBreak) { + || isLineBreak(attributes, lbh.currentPosition)) { lbh.adjustRightBearing(); block->_words.push_back(TextWord(wordStart + blockFrom, lbh.tmpData.textWidth, qMin(QFixed(), lbh.rightBearing))); block->_width += lbh.tmpData.textWidth; @@ -3265,10 +3279,19 @@ public: } } + bool isLineBreak(const QCharAttributes *attributes, int32 index) { + bool lineBreak = attributes[index].lineBreak; + if (lineBreak && block->lnkIndex() > 0 && index > 0 && str.at(index - 1) == '/') { + return false; // don't break after / in links + } + return lineBreak; + } + private: TextBlock *block; QTextEngine *eng; + const QString &str; }; @@ -3302,14 +3325,15 @@ TextBlock::TextBlock(const style::font &font, const QString &str, QFixed minResi } } - QStackTextEngine engine(str.mid(_from, length), blockFont->f); + QString part = str.mid(_from, length); + QStackTextEngine engine(part, blockFont->f); engine.itemize(); QTextLayout layout(&engine); layout.beginLayout(); layout.createLine(); - BlockParser parser(&engine, this, minResizeWidth, _from); + BlockParser parser(&engine, this, minResizeWidth, _from, part); layout.endLayout(); } @@ -3671,7 +3695,7 @@ void initLinkSets() { namespace { // accent char list taken from https://github.com/aristus/accent-folding - inline QChar chNoAccent(int32 code) { + inline QChar chNoAccent(int32 code) { switch (code) { case 7834: return QChar(97); case 193: return QChar(97); @@ -4395,7 +4419,7 @@ QString textAccentFold(const QString &text) { result[i] = noAccent; } else { if (copying) result[i] = *ch; - ++ch, ++i; + ++ch, ++i; if (copying) result[i] = *ch; } } else { @@ -4478,8 +4502,7 @@ goodCanBreakEntity = canBreakEntity;\ #undef MARK_GOOD_AS_LEVEL int elen = 0; - EmojiPtr e = emojiFromText(ch, end, elen); - if (e) { + if (EmojiPtr e = emojiFromText(ch, end, &elen)) { for (int i = 0; i < elen; ++i, ++ch, ++s) { if (ch->isHighSurrogate() && i + 1 < elen && (ch + 1)->isLowSurrogate()) { ++ch; diff --git a/Telegram/SourceFiles/gui/text.h b/Telegram/SourceFiles/gui/text.h index c1ff5a911c..bd95be3163 100644 --- a/Telegram/SourceFiles/gui/text.h +++ b/Telegram/SourceFiles/gui/text.h @@ -323,6 +323,8 @@ public: virtual bool fullDisplayed() const { return true; } + virtual void setFullDisplayed(bool full) { + } virtual QString encoded() const { return QString(); } @@ -364,6 +366,10 @@ public: return _fullDisplayed; } + void setFullDisplayed(bool full) { + _fullDisplayed = full; + } + QString encoded() const { QUrl u(_url), good(u.isValid() ? u.toEncoded() : QString()); QString result(good.isValid() ? QString::fromUtf8(good.toEncoded()) : _url); @@ -680,6 +686,8 @@ const TextLinkPtr &textlnkOver(); void textlnkDown(const TextLinkPtr &lnk); const TextLinkPtr &textlnkDown(); +bool textlnkDrawOver(const TextLinkPtr &lnk); + // textcmd QString textcmdSkipBlock(ushort w, ushort h); QString textcmdStartLink(ushort lnkIndex); diff --git a/Telegram/SourceFiles/gui/twidget.h b/Telegram/SourceFiles/gui/twidget.h index 411762e1b7..d01504c2b7 100644 --- a/Telegram/SourceFiles/gui/twidget.h +++ b/Telegram/SourceFiles/gui/twidget.h @@ -148,6 +148,9 @@ QRect myrtlrect(const QRect &r) { \ void rtlupdate(const QRect &r) { \ update(myrtlrect(r)); \ } \ +void rtlupdate(int x, int y, int w, int h) { \ + update(myrtlrect(x, y, w, h)); \ +} \ protected: \ void enterEvent(QEvent *e) { \ TWidget *p(tparent()); \ diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 28854b3973..ef73e71ac3 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -22,53 +22,18 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "style.h" #include "lang.h" -#include "history.h" #include "mainwidget.h" #include "application.h" #include "fileuploader.h" #include "window.h" #include "gui/filedialog.h" +#include "boxes/addcontactbox.h" +#include "boxes/confirmbox.h" + #include "audio.h" #include "localstorage.h" -TextParseOptions _textNameOptions = { - 0, // flags - 4096, // maxw - 1, // maxh - Qt::LayoutDirectionAuto, // lang-dependent -}; -TextParseOptions _textDlgOptions = { - 0, // flags - 0, // maxw is style-dependent - 1, // maxh - Qt::LayoutDirectionAuto, // lang-dependent -}; -TextParseOptions _historyTextOptions = { - TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText | TextParseMono, // flags - 0, // maxw - 0, // maxh - Qt::LayoutDirectionAuto, // dir -}; -TextParseOptions _historyBotOptions = { - TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands | TextParseMultiline | TextParseRichText | TextParseMono, // flags - 0, // maxw - 0, // maxh - Qt::LayoutDirectionAuto, // dir -}; -TextParseOptions _historyTextNoMonoOptions = { - TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText, // flags - 0, // maxw - 0, // maxh - Qt::LayoutDirectionAuto, // dir -}; -TextParseOptions _historyBotNoMonoOptions = { - TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands | TextParseMultiline | TextParseRichText, // flags - 0, // maxw - 0, // maxh - Qt::LayoutDirectionAuto, // dir -}; - namespace { TextParseOptions _historySrvOptions = { TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText, // flags @@ -110,8 +75,6 @@ namespace { _webpageDescriptionOptions.maxh = st::webPageDescriptionFont->height * 3; } - AnimatedGif animated; - inline HistoryReply *toHistoryReply(HistoryItem *item) { return item ? item->toHistoryReply() : 0; } @@ -127,53 +90,15 @@ namespace { inline const TextParseOptions &itemTextOptions(HistoryItem *item) { return itemTextOptions(item->history(), item->from()); } - inline const TextParseOptions &itemTextNoMonoOptions(HistoryItem *item) { + inline const TextParseOptions &itemTextNoMonoOptions(const HistoryItem *item) { return itemTextNoMonoOptions(item->history(), item->from()); } } -const TextParseOptions &itemTextOptions(History *h, PeerData *f) { - if ((h->peer->isUser() && h->peer->asUser()->botInfo) || (f->isUser() && f->asUser()->botInfo) || (h->peer->isChat() && h->peer->asChat()->botStatus >= 0) || (h->peer->isMegagroup() && h->peer->asChannel()->mgInfo->botStatus >= 0)) { - return _historyBotOptions; - } - return _historyTextOptions; -} - -const TextParseOptions &itemTextNoMonoOptions(History *h, PeerData *f) { - if ((h->peer->isUser() && h->peer->asUser()->botInfo) || (f->isUser() && f->asUser()->botInfo) || (h->peer->isChat() && h->peer->asChat()->botStatus >= 0) || (h->peer->isMegagroup() && h->peer->asChannel()->mgInfo->botStatus >= 0)) { - return _historyBotNoMonoOptions; - } - return _historyTextNoMonoOptions; -} - void historyInit() { _initTextOptions(); } -void startGif(HistoryItem *row, const FileLocation &file) { - if (row == animated.msg) { - stopGif(); - } else { - animated.start(row, file); - } -} - -void itemReplacedGif(HistoryItem *oldItem, HistoryItem *newItem) { - if (oldItem == animated.msg) { - animated.msg = newItem; - } -} - -void itemRemovedGif(HistoryItem *item) { - if (item == animated.msg) { - animated.stop(true); - } -} - -void stopGif() { - animated.stop(); -} - 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); @@ -284,7 +209,7 @@ void FakeDialogRow::paint(Painter &p, int32 w, bool act, bool sel, bool onlyBack 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(); if (history->peer->migrateTo()) { p.drawPixmap(st::dlgPaddingHor, st::dlgPaddingVer, history->peer->migrateTo()->photo->pix(st::dlgPhotoSize)); @@ -396,8 +321,7 @@ void History::clearLastKeyboard() { lastKeyboardFrom = 0; } -bool History::updateTyping(uint64 ms, uint32 dots, bool force) { - if (!ms) ms = getms(true); +bool History::updateTyping(uint64 ms, bool force) { bool changed = force; for (TypingUsers::iterator i = typing.begin(), e = typing.end(); i != e;) { if (ms >= i.value()) { @@ -444,7 +368,7 @@ bool History::updateTyping(uint64 ms, uint32 dots, bool force) { } } if (!typingStr.isEmpty()) { - if (typingText.lastDots(dots % 4)) { + if (typingText.lastDots(typingDots % 4)) { changed = true; } } @@ -457,25 +381,6 @@ bool History::updateTyping(uint64 ms, uint32 dots, bool force) { return changed; } -void History::eraseFromOverview(MediaOverviewType type, MsgId msgId) { - if (overviewIds[type].isEmpty()) return; - - History::MediaOverviewIds::iterator i = overviewIds[type].find(msgId); - if (i == overviewIds[type].cend()) return; - - overviewIds[type].erase(i); - for (History::MediaOverview::iterator i = overview[type].begin(), e = overview[type].end(); i != e; ++i) { - if ((*i) == msgId) { - overview[type].erase(i); - if (overviewCountData[type] > 0) { - --overviewCountData[type]; - } - break; - } - } - if (App::wnd()) App::wnd()->mediaOverviewUpdated(peer, type); -} - ChannelHistory::ChannelHistory(const PeerId &peer) : History(peer), unreadCountAll(0), _onlyImportant(!isMegagroup()), @@ -601,7 +506,7 @@ void ChannelHistory::insertCollapseItem(MsgId wasMinId) { HistoryItem *item = block->items.at(itemIndex); if (insertAfter || item->id > wasMinId || (item->id == wasMinId && !item->isImportant())) { _collapseMessage = new HistoryCollapse(this, block, wasMinId, item->date); - if (!addNewInTheMiddle(_collapseMessage, blockIndex, itemIndex)) { + if (!addNewInTheMiddle(regItem(_collapseMessage), blockIndex, itemIndex)) { _collapseMessage = 0; } return; @@ -699,7 +604,7 @@ void ChannelHistory::addNewGroup(const MTPMessageGroup &group) { for (Blocks::iterator i = blocks.begin(), e = blocks.end(); i != e; ++i) { (*i)->y += dh; } - blocks.push_front(dateBlock); // date block + blocks.push_front(dateBlock); // date block CHECK height += dh; } } @@ -745,12 +650,12 @@ HistoryJoined *ChannelHistory::insertJoinedMessage(bool unread) { ++itemIndex; if (item->date.date() != inviteDate.date()) { HistoryDateMsg *joinedDateItem = new HistoryDateMsg(this, block, inviteDate.date()); - if (addNewInTheMiddle(joinedDateItem, blockIndex, itemIndex)) { + if (addNewInTheMiddle(regItem(joinedDateItem), blockIndex, itemIndex)) { ++itemIndex; } } _joinedMessage = new HistoryJoined(this, block, inviteDate, inviter, flags); - if (!addNewInTheMiddle(_joinedMessage, blockIndex, itemIndex)) { + if (!addNewInTheMiddle(regItem(_joinedMessage), blockIndex, itemIndex)) { _joinedMessage = 0; } if (lastSeenDateItem && lastSeenDateItem->date.date() == inviteDate.date()) { @@ -784,11 +689,7 @@ HistoryJoined *ChannelHistory::insertJoinedMessage(bool unread) { HistoryBlock *block = new HistoryBlock(this); _joinedMessage = new HistoryJoined(this, block, inviteDate, inviter, flags); - if (regItem(_joinedMessage)) { - addItemAfterPrevToBlock(_joinedMessage, 0, block); - } else { - _joinedMessage = 0; - } + addItemAfterPrevToBlock(regItem(_joinedMessage), 0, block); if (till && _joinedMessage && inviteDate.date() != till->date.date()) { HistoryItem *dayItem = createDayServiceMsg(this, block, till->date); block->items.push_back(dayItem); @@ -798,7 +699,7 @@ HistoryJoined *ChannelHistory::insertJoinedMessage(bool unread) { } } if (!block->items.isEmpty()) { - blocks.push_front(block); + blocks.push_front(block); // CHECK if (width) { addToH += block->height; ++skip; @@ -819,7 +720,7 @@ HistoryJoined *ChannelHistory::insertJoinedMessage(bool unread) { addToH += dh; ++skip; } - blocks.push_front(dateBlock); // date block + blocks.push_front(dateBlock); // date block CHECK } if (width && addToH) { for (Blocks::iterator i = blocks.begin(), e = blocks.end(); i != e; ++i) { @@ -963,7 +864,14 @@ HistoryItem *ChannelHistory::addNewToBlocks(const MTPMessage &msg, NewMessageTyp } else { to = blocks.back(); } - return addNewItem(to, newBlock, createItem(to, msg, (type == NewMessageUnread)), (type == NewMessageUnread)); + HistoryItem *item = createItem((type == NewMessageLast) ? 0 : to, msg, (type == NewMessageUnread)); + if (type == NewMessageLast) { + if (!item->detached()) { + return item; + } + item->attach(to); + } + return addNewItem(to, newBlock, item, (type == NewMessageUnread)); } void ChannelHistory::addNewToOther(HistoryItem *item, NewMessageType type) { @@ -1024,9 +932,11 @@ void ChannelHistory::switchMode() { item->attach(block); prev = addItemAfterPrevToBlock(item, prev, block); } - block->y = height; - blocks.push_back(block); - height += block->height; + blocks.push_back(block); // CHECK + if (width) { + block->y = height; + height += block->height; + } } } @@ -1334,7 +1244,7 @@ void Histories::regSendAction(History *history, UserData *user, const MTPSendMes return; } - uint64 ms = getms(true); + uint64 ms = getms(); switch (action.type()) { case mtpc_sendMessageTypingAction: history->typing[user] = ms + 6000; break; case mtpc_sendMessageRecordVideoAction: history->sendActions.insert(user, SendAction(SendActionRecordVideo, ms + 6000)); break; @@ -1353,25 +1263,25 @@ void Histories::regSendAction(History *history, UserData *user, const MTPSendMes TypingHistories::const_iterator i = typing.find(history); if (i == typing.cend()) { typing.insert(history, ms); - history->typingFrame = 0; + history->typingDots = 0; + _a_typings.start(); } - - history->updateTyping(ms, history->typingFrame, true); - anim::start(this); + history->updateTyping(ms, true); } -bool Histories::animStep(float64) { - uint64 ms = getms(true); +void Histories::step_typings(uint64 ms, bool timer) { for (TypingHistories::iterator i = typing.begin(), e = typing.end(); i != e;) { - uint32 typingFrame = (ms - i.value()) / 150; - i.key()->updateTyping(ms, typingFrame); + i.key()->typingDots = (ms - i.value()) / 150; + i.key()->updateTyping(ms); if (i.key()->typing.isEmpty() && i.key()->sendActions.isEmpty()) { i = typing.erase(i); } else { ++i; } } - return !typing.isEmpty(); + if (typing.isEmpty()) { + _a_typings.stop(); + } } void Histories::remove(const PeerId &peer) { @@ -1390,28 +1300,34 @@ HistoryItem *Histories::addNewMessage(const MTPMessage &msg, NewMessageType type return findOrInsert(peer, 0, 0)->addNewMessage(msg, type); } -HistoryItem *History::createItem(HistoryBlock *block, const MTPMessage &msg, bool applyServiceAction, bool returnExisting) { - HistoryItem *result = 0; - +HistoryItem *History::createItem(HistoryBlock *block, const MTPMessage &msg, bool applyServiceAction) { MsgId msgId = 0; switch (msg.type()) { case mtpc_messageEmpty: msgId = msg.c_messageEmpty().vid.v; break; case mtpc_message: msgId = msg.c_message().vid.v; break; case mtpc_messageService: msgId = msg.c_messageService().vid.v; break; } + if (!msgId) return 0; - HistoryItem *existing = App::histItemById(channelId(), msgId); - if (existing) { - bool regged = false; - if (existing->detached() && block) { - existing->attach(block); - regged = true; + HistoryItem *result = App::histItemById(channelId(), msgId); + if (result) { + if (block) { + if (!result->detached()) { + result->detach(); + } + result->attach(block); } - if (msg.type() == mtpc_message) { - existing->updateMedia(msg.c_message().has_media() ? (&msg.c_message().vmedia) : 0, (block ? false : true)); + result->updateMedia(msg.c_message().has_media() ? (&msg.c_message().vmedia) : 0); + result->initDimensions(); + if (!block) { + Notify::historyItemResized(result); + } + if (applyServiceAction) { + App::checkSavedGif(result); + } } - return (returnExisting || regged) ? existing : 0; + return result; } switch (msg.type()) { @@ -1478,8 +1394,13 @@ HistoryItem *History::createItem(HistoryBlock *block, const MTPMessage &msg, boo case mtpc_messageMediaUnsupported: default: badMedia = 1; break; } - if (badMedia) { - result = new HistoryServiceMsg(this, block, m.vid.v, date(m.vdate), lang((badMedia == 2) ? lng_message_empty : lng_media_unsupported), m.vflags.v, 0, m.has_from_id() ? m.vfrom_id.v : 0); + if (badMedia == 1) { + QString text(lng_message_unsupported(lt_link, qsl("https://desktop.telegram.org"))); + EntitiesInText entities = textParseEntities(text, _historyTextNoMonoOptions.flags); + entities.push_front(EntityInText(EntityInTextItalic, 0, text.size())); + result = new HistoryMessage(this, block, m.vid.v, m.vflags.v, m.vvia_bot_id.v, date(m.vdate), m.vfrom_id.v, text, entities, 0); + } else if (badMedia) { + result = new HistoryServiceMsg(this, block, m.vid.v, date(m.vdate), lang(lng_message_empty), m.vflags.v, 0, m.has_from_id() ? m.vfrom_id.v : 0); } else { if ((m.has_fwd_date() && m.vfwd_date.v > 0) || (m.has_fwd_from_id() && peerFromMTP(m.vfwd_from_id) != 0)) { result = new HistoryForwarded(this, block, m); @@ -1617,24 +1538,36 @@ HistoryItem *History::createItem(HistoryBlock *block, const MTPMessage &msg, boo } break; } - return regItem(result, returnExisting); -} - -HistoryItem *History::createItemForwarded(HistoryBlock *block, MsgId id, QDateTime date, int32 from, HistoryMessage *msg) { - HistoryItem *result = 0; - - result = new HistoryForwarded(this, block, id, date, from, msg); + if (applyServiceAction) { + App::checkSavedGif(result); + } return regItem(result); } -HistoryItem *History::createItemDocument(HistoryBlock *block, MsgId id, int32 flags, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc) { +HistoryItem *History::createItemForwarded(HistoryBlock *block, MsgId id, QDateTime date, int32 from, HistoryMessage *msg) { + return regItem(new HistoryForwarded(this, block, id, date, from, msg)); +} + +HistoryItem *History::createItemDocument(HistoryBlock *block, MsgId id, int32 flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption) { + HistoryItem *result = 0; + + if ((flags & MTPDmessage::flag_reply_to_msg_id) && replyTo > 0) { + result = new HistoryReply(this, block, id, flags, viaBotId, replyTo, date, from, doc, caption); + } else { + result = new HistoryMessage(this, block, id, flags, viaBotId, date, from, doc, caption); + } + + return regItem(result); +} + +HistoryItem *History::createItemPhoto(HistoryBlock *block, MsgId id, int32 flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption) { HistoryItem *result = 0; if (flags & MTPDmessage::flag_reply_to_msg_id && replyTo > 0) { - result = new HistoryReply(this, block, id, flags, replyTo, date, from, doc); + result = new HistoryReply(this, block, id, flags, viaBotId, replyTo, date, from, photo, caption); } else { - result = new HistoryMessage(this, block, id, flags, date, from, doc); + result = new HistoryMessage(this, block, id, flags, viaBotId, date, from, photo, caption); } return regItem(result); @@ -1649,7 +1582,8 @@ HistoryItem *History::addNewService(MsgId msgId, QDateTime date, const QString & to = blocks.back(); } - return addNewItem(to, newBlock, regItem(new HistoryServiceMsg(this, to, msgId, date, text, flags, media)), newMsg); + HistoryItem *result = new HistoryServiceMsg(this, to, msgId, date, text, flags, media); + return addNewItem(to, newBlock, regItem(result), newMsg); } HistoryItem *History::addNewMessage(const MTPMessage &msg, NewMessageType type) { @@ -1674,11 +1608,18 @@ HistoryItem *History::addNewMessage(const MTPMessage &msg, NewMessageType type) } else { to = blocks.back(); } - return addNewItem(to, newBlock, createItem(to, msg, (type == NewMessageUnread)), (type == NewMessageUnread)); + HistoryItem *item = createItem((type == NewMessageLast) ? 0 : to, msg, (type == NewMessageUnread)); + if (type == NewMessageLast) { + if (!item->detached()) { + return item; + } + item->attach(to); + } + return addNewItem(to, newBlock, item, (type == NewMessageUnread)); } HistoryItem *History::addToHistory(const MTPMessage &msg) { - return createItem(0, msg, false, true); + return createItem(0, msg, false); } HistoryItem *History::addNewForwarded(MsgId id, QDateTime date, int32 from, HistoryMessage *item) { @@ -1692,7 +1633,7 @@ HistoryItem *History::addNewForwarded(MsgId id, QDateTime date, int32 from, Hist return addNewItem(to, newBlock, createItemForwarded(to, id, date, from, item), true); } -HistoryItem *History::addNewDocument(MsgId id, int32 flags, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc) { +HistoryItem *History::addNewDocument(MsgId id, int32 flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption) { HistoryBlock *to = 0; bool newBlock = blocks.isEmpty(); if (newBlock) { @@ -1700,7 +1641,18 @@ HistoryItem *History::addNewDocument(MsgId id, int32 flags, MsgId replyTo, QDate } else { to = blocks.back(); } - return addNewItem(to, newBlock, createItemDocument(to, id, flags, replyTo, date, from, doc), true); + return addNewItem(to, newBlock, createItemDocument(to, id, flags, viaBotId, replyTo, date, from, doc, caption), true); +} + +HistoryItem *History::addNewPhoto(MsgId id, int32 flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption) { + HistoryBlock *to = 0; + bool newBlock = blocks.isEmpty(); + if (newBlock) { + to = new HistoryBlock(this); + } else { + to = blocks.back(); + } + return addNewItem(to, newBlock, createItemPhoto(to, id, flags, viaBotId, replyTo, date, from, photo, caption), true); } void History::createInitialDateBlock(const QDateTime &date) { @@ -1708,34 +1660,59 @@ void History::createInitialDateBlock(const QDateTime &date) { HistoryItem *dayItem = createDayServiceMsg(this, dateBlock, date); dateBlock->items.push_back(dayItem); if (width) { - int32 dh = dayItem->resize(width); - dateBlock->height = dh; - height += dh; - for (int32 i = 0, l = blocks.size(); i < l; ++i) { - blocks[i]->y += dh; + dateBlock->height += dayItem->resize(width); + } + + blocks.push_front(dateBlock); + if (width) { + height += dateBlock->height; + for (int32 i = 1, l = blocks.size(); i < l; ++i) { + blocks.at(i)->y += dateBlock->height; } } - blocks.push_front(dateBlock); } -void History::addToOverview(HistoryItem *item, MediaOverviewType type) { - if (overviewIds[type].constFind(item->id) == overviewIds[type].cend()) { - overview[type].push_back(item->id); - overviewIds[type].insert(item->id, NullType()); +bool History::addToOverview(MediaOverviewType type, MsgId msgId, AddToOverviewMethod method) { + bool adding = false; + switch (method) { + case AddToOverviewNew: + case AddToOverviewFront: adding = (overviewIds[type].constFind(msgId) == overviewIds[type].cend()); break; + case AddToOverviewBack: adding = (overviewCountData[type] != 0); break; + } + if (!adding) return false; + + overviewIds[type].insert(msgId, NullType()); + switch (method) { + case AddToOverviewNew: + case AddToOverviewBack: overview[type].push_back(msgId); break; + case AddToOverviewFront: overview[type].push_front(msgId); break; + } + if (method == AddToOverviewNew) { if (overviewCountData[type] > 0) { ++overviewCountData[type]; } if (App::wnd()) App::wnd()->mediaOverviewUpdated(peer, type); } + return true; } -bool History::addToOverviewFront(HistoryItem *item, MediaOverviewType type) { - if (overviewIds[type].constFind(item->id) == overviewIds[type].cend()) { - overview[type].push_front(item->id); - overviewIds[type].insert(item->id, NullType()); - return true; +void History::eraseFromOverview(MediaOverviewType type, MsgId msgId) { + if (overviewIds[type].isEmpty()) return; + + History::MediaOverviewIds::iterator i = overviewIds[type].find(msgId); + if (i == overviewIds[type].cend()) return; + + overviewIds[type].erase(i); + for (History::MediaOverview::iterator i = overview[type].begin(), e = overview[type].end(); i != e; ++i) { + if ((*i) == msgId) { + overview[type].erase(i); + if (overviewCountData[type] > 0) { + --overviewCountData[type]; + } + break; + } } - return false; + if (App::wnd()) App::wnd()->mediaOverviewUpdated(peer, type); } HistoryItem *History::addNewItem(HistoryBlock *to, bool newBlock, HistoryItem *adding, bool newMsg) { @@ -1752,8 +1729,9 @@ HistoryItem *History::addNewItem(HistoryBlock *to, bool newBlock, HistoryItem *a } else if (to->items.back()->date.date() != adding->date.date()) { HistoryItem *dayItem = createDayServiceMsg(this, to, adding->date); to->items.push_back(dayItem); - dayItem->y = to->height; if (width) { + dayItem->y = to->height; + int32 dh = dayItem->resize(width); to->height += dh; height += dh; @@ -1772,23 +1750,7 @@ HistoryItem *History::addNewItem(HistoryBlock *to, bool newBlock, HistoryItem *a newItemAdded(adding); } - if (adding->indexInOverview()) { - HistoryMedia *media = adding->getMedia(true); - if (media) { - HistoryMediaType mt = media->type(); - MediaOverviewType t = mediaToOverviewType(mt); - if (t != OverviewCount) { - if (mt == MediaTypeDocument && static_cast(media)->document()->song()) { - addToOverview(adding, OverviewAudioDocuments); - } else { - addToOverview(adding, t); - } - } - } - if (adding->hasTextLinks()) { - addToOverview(adding, OverviewLinks); - } - } + adding->addToOverview(AddToOverviewNew); if (adding->from()->id) { if (adding->from()->isUser()) { QList *lastAuthors = 0; @@ -1858,16 +1820,16 @@ void History::unregTyping(UserData *from) { uint64 updateAtMs = 0; TypingUsers::iterator i = typing.find(from); if (i != typing.end()) { - updateAtMs = getms(true); + updateAtMs = getms(); i.value() = updateAtMs; } SendActionUsers::iterator j = sendActions.find(from); if (j != sendActions.end()) { - if (!updateAtMs) updateAtMs = getms(true); + if (!updateAtMs) updateAtMs = getms(); j.value().until = updateAtMs; } if (updateAtMs) { - updateTyping(updateAtMs, 0, true); + updateTyping(updateAtMs, true); } } @@ -1933,8 +1895,10 @@ HistoryItem *History::addMessageGroupAfterPrev(HistoryItem *newItem, HistoryItem createInitialDateBlock(date); block = new HistoryBlock(this); - block->y = height; - blocks.push_back(block); + blocks.push_back(block); // CHECK + if (width) { + block->y = height; + } } return addItemAfterPrevToBlock(regItem(new HistoryGroup(this, block, newItem, date)), prev, block); } @@ -1953,13 +1917,11 @@ void History::addOlderSlice(const QVector &slice, const QVectorconstData() : 0, *groupsIt = groupsBegin, *groupsEnd = (isChannel() && collapsed) ? (groupsBegin + collapsed->size()) : 0; - int32 addToH = 0, skip = 0; - if (!blocks.isEmpty()) { // remove date block - if (width) addToH = -blocks.front()->height; - delete blocks.front(); - blocks.pop_front(); + HistoryItem *oldFirst = 0, *last = 0; + if (!blocks.isEmpty()) { + t_assert(blocks.size() > 1); + oldFirst = blocks.at(1)->items.front(); } - HistoryItem *till = blocks.isEmpty() ? 0 : blocks.front()->items.front(), *prev = 0; HistoryBlock *block = new HistoryBlock(this); block->items.reserve(slice.size() + (collapsed ? collapsed->size() : 0)); @@ -1973,41 +1935,58 @@ void History::addOlderSlice(const QVector &slice, const QVectorc_messageGroup()); if (group.vmin_id.v >= adding->id) break; - prev = addMessageGroupAfterPrevToBlock(group, prev, block); + last = addMessageGroupAfterPrevToBlock(group, last, block); } - prev = addItemAfterPrevToBlock(adding, prev, block); + last = addItemAfterPrevToBlock(adding, last, block); } for (; groupsIt != groupsEnd; ++groupsIt) { if (groupsIt->type() != mtpc_messageGroup) continue; const MTPDmessageGroup &group(groupsIt->c_messageGroup()); - prev = addMessageGroupAfterPrevToBlock(group, prev, block); + last = addMessageGroupAfterPrevToBlock(group, last, block); } - while (till && prev && till->type() == HistoryItemGroup && prev->type() == HistoryItemGroup) { - static_cast(prev)->uniteWith(static_cast(till)); - till->detach(); - delete till; - if (blocks.front()->items.isEmpty()) { - delete blocks.front(); - blocks.pop_front(); + while (oldFirst && last && oldFirst->type() == HistoryItemGroup && last->type() == HistoryItemGroup) { + static_cast(last)->uniteWith(static_cast(oldFirst)); + oldFirst->destroy(); + if (blocks.isEmpty()) { + oldFirst = 0; + } else { + t_assert(blocks.size() > 1); + oldFirst = blocks.at(1)->items.front(); } - till = blocks.isEmpty() ? 0 : blocks.front()->items.front(); } - if (till && prev && prev->date.date() != till->date.date()) { - HistoryItem *dayItem = createDayServiceMsg(this, block, till->date); + if (oldFirst && last && last->date.date() != oldFirst->date.date()) { + HistoryItem *dayItem = createDayServiceMsg(this, block, oldFirst->date); block->items.push_back(dayItem); if (width) { dayItem->y = block->height; block->height += dayItem->resize(width); } } - if (!block->items.isEmpty()) { - blocks.push_front(block); - if (width) { - addToH += block->height; - ++skip; + if (block->items.isEmpty()) { + oldLoaded = true; + delete block; + } else { + if (oldFirst) { + HistoryBlock *initial = blocks.at(0); + blocks[0] = block; + blocks.push_front(initial); + if (width) { + block->y = initial->height; + for (int32 i = 2, l = blocks.size(); i < l; ++i) { + blocks.at(i)->y += block->height; + } + height += block->height; + } + initial->items.at(0)->setDate(block->items.at(0)->date); + } else { + blocks.push_front(block); + if (width) { + height = block->height; + } + createInitialDateBlock(block->items.at(0)->date); } if (loadedAtBottom()) { // add photos to overview and authors to lastAuthors / lastParticipants @@ -2024,23 +2003,7 @@ void History::addOlderSlice(const QVector &slice, const QVectoritems.size(); i > 0; --i) { HistoryItem *item = block->items[i - 1]; - if (item->indexInOverview()) { - HistoryMedia *media = item->getMedia(true); - if (media) { - HistoryMediaType mt = media->type(); - MediaOverviewType t = mediaToOverviewType(mt); - if (t != OverviewCount) { - if (mt == MediaTypeDocument && static_cast(media)->document()->song()) { - if (addToOverviewFront(item, OverviewAudioDocuments)) mask |= (1 << OverviewAudioDocuments); - } else { - if (addToOverviewFront(item, t)) mask |= (1 << t); - } - } - } - if (item->hasTextLinks()) { - if (addToOverviewFront(item, OverviewLinks)) mask |= (1 << OverviewLinks); - } - } + mask |= item->addToOverview(AddToOverviewFront); if (item->from()->id) { if (lastAuthors) { // chats if (item->from()->isUser()) { @@ -2099,33 +2062,6 @@ void History::addOlderSlice(const QVector &slice, const QVectormediaOverviewUpdated(peer, MediaOverviewType(t)); } } - } else { - delete block; - } - if (!blocks.isEmpty()) { - HistoryBlock *dateBlock = new HistoryBlock(this); - HistoryItem *dayItem = createDayServiceMsg(this, dateBlock, blocks.front()->items.front()->date); - dateBlock->items.push_back(dayItem); - if (width) { - int32 dh = dayItem->resize(width); - dateBlock->height = dh; - if (skip) { - blocks.front()->y += dh; - } - addToH += dh; - ++skip; - } - blocks.push_front(dateBlock); // date block - } - if (width && addToH) { - for (Blocks::iterator i = blocks.begin(), e = blocks.end(); i != e; ++i) { - if (skip) { - --skip; - } else { - (*i)->y += addToH; - } - } - height += addToH; } if (isChannel()) { @@ -2172,16 +2108,22 @@ void History::addNewerSlice(const QVector &slice, const QVectoritems.size()) { - block->y = height; - blocks.push_back(block); - height += block->height; - } else { + if (block->items.isEmpty()) { newLoaded = true; setLastMessage(lastImportantMessage()); delete block; + } else { + blocks.push_back(block); + if (width) { + block->y = height; + height += block->height; + } + if (blocks.size() == 1) { + createInitialDateBlock(block->items.at(0)->date); + } } } + if (!wasLoadedAtBottom && loadedAtBottom()) { // add all loaded photos to overview int32 mask = 0; for (int32 i = 0; i < OverviewCount; ++i) { @@ -2196,56 +2138,13 @@ void History::addNewerSlice(const QVector &slice, const QVectoritems.size(); ++j) { - HistoryItem *item = b->items[j]; - if (!item->indexInOverview()) continue; - - HistoryMedia *media = item->getMedia(true); - if (media) { - HistoryMediaType mt = media->type(); - MediaOverviewType t = mediaToOverviewType(mt); - if (t != OverviewCount) { - if (mt == MediaTypeDocument && static_cast(media)->document()->song()) { - t = OverviewAudioDocuments; - if (overviewCountData[t] != 0) { - overview[t].push_back(item->id); - overviewIds[t].insert(item->id, NullType()); - mask |= (1 << t); - } - } else { - if (overviewCountData[t] != 0) { - overview[t].push_back(item->id); - overviewIds[t].insert(item->id, NullType()); - mask |= (1 << t); - } - } - } - } - if (item->hasTextLinks()) { - MediaOverviewType t = OverviewLinks; - if (overviewCountData[t] != 0) { - overview[t].push_back(item->id); - overviewIds[t].insert(item->id, NullType()); - mask |= (1 << t); - } - } + mask |= b->items[j]->addToOverview(AddToOverviewBack); } } for (int32 t = 0; t < OverviewCount; ++t) { if ((mask & (1 << t)) && App::wnd()) App::wnd()->mediaOverviewUpdated(peer, MediaOverviewType(t)); } } - if (wasEmpty && !isEmpty()) { - HistoryBlock *dateBlock = new HistoryBlock(this); - HistoryItem *dayItem = createDayServiceMsg(this, dateBlock, blocks.front()->items.front()->date); - dateBlock->items.push_back(dayItem); - int32 dh = dayItem->resize(width); - dateBlock->height = dh; - for (Blocks::iterator i = blocks.begin(), e = blocks.end(); i != e; ++i) { - (*i)->y += dh; - } - blocks.push_front(dateBlock); // date block - height += dh; - } if (isChannel()) asChannelHistory()->checkJoinedMessage(); } @@ -2357,7 +2256,7 @@ void History::setUnreadCount(int32 newUnreadCount, bool psUpdate) { App::histories().unreadFull += newUnreadCount - unreadCount; if (mute) App::histories().unreadMuted += newUnreadCount - unreadCount; unreadCount = newUnreadCount; - if (psUpdate && (!mute || cIncludeMuted())) App::wnd()->updateCounter(); + if (psUpdate && (!mute || cIncludeMuted()) && App::wnd()) App::wnd()->updateCounter(); if (unreadBar) { int32 count = unreadCount; if (peer->migrateTo()) { @@ -2407,7 +2306,7 @@ void History::getNextShowFrom(HistoryBlock *block, int32 i) { void History::addUnreadBar() { if (unreadBar || !showFrom || showFrom->detached() || !unreadCount) return; - + int32 count = unreadCount; if (peer->migrateTo()) { if (History *h = App::historyLoaded(peer->migrateTo()->id)) { @@ -2416,15 +2315,12 @@ void History::addUnreadBar() { } HistoryBlock *block = showFrom->block(); unreadBar = new HistoryUnreadBar(this, block, count, showFrom->date); - if (!addNewInTheMiddle(unreadBar, blocks.indexOf(block), block->items.indexOf(showFrom))) { + if (!addNewInTheMiddle(regItem(unreadBar), blocks.indexOf(block), block->items.indexOf(showFrom))) { unreadBar = 0; } } HistoryItem *History::addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, int32 itemIndex) { - if (!regItem(newItem)) { - return 0; - } if (blockIndex < 0 || itemIndex < 0 || blockIndex >= blocks.size() || itemIndex > blocks.at(blockIndex)->items.size()) { delete newItem; return 0; @@ -2600,9 +2496,10 @@ MsgId History::msgIdForRead() const { return result; } -int32 History::geomResize(int32 newWidth, int32 *ytransform, HistoryItem *resizedItem) { +int32 History::geomResize(int32 newWidth, int32 *ytransform, const HistoryItem *resizedItem) { if (width != newWidth) resizedItem = 0; // recount all items if (width != newWidth || resizedItem) { + width = newWidth; int32 y = 0; for (Blocks::iterator i = blocks.begin(), e = blocks.end(); i != e; ++i) { HistoryBlock *block = *i; @@ -2617,7 +2514,6 @@ int32 History::geomResize(int32 newWidth, int32 *ytransform, HistoryItem *resize ytransform = 0; } } - width = newWidth; height = y; } return height; @@ -2795,7 +2691,7 @@ void History::removeBlock(HistoryBlock *block) { delete block; } -int32 HistoryBlock::geomResize(int32 newWidth, int32 *ytransform, HistoryItem *resizedItem) { +int32 HistoryBlock::geomResize(int32 newWidth, int32 *ytransform, const HistoryItem *resizedItem) { int32 y = 0; for (Items::iterator i = items.begin(), e = items.end(); i != e; ++i) { HistoryItem *item = *i; @@ -2894,7 +2790,7 @@ void HistoryBlock::removeItem(HistoryItem *item) { nextCollapse->destroy(); } } - + // fix date items HistoryItem *nextItem = (i < items.size() - 1) ? items[i + 1] : ((myIndex < history->blocks.size() - 1) ? history->blocks[myIndex + 1]->items[0] : 0); if (nextItem && nextItem == history->unreadBar) { // skip unread bar @@ -2968,42 +2864,6 @@ void HistoryBlock::removeItem(HistoryItem *item) { } } -bool ItemAnimations::animStep(float64 ms) { - for (Animations::iterator i = _animations.begin(); i != _animations.end();) { - const HistoryItem *item = i.key(); - if (item->animating()) { - App::main()->msgUpdated(item); - ++i; - } else { - i = _animations.erase(i); - } - } - return !_animations.isEmpty(); -} - -uint64 ItemAnimations::animate(const HistoryItem *item, uint64 ms) { - if (_animations.isEmpty()) { - _animations.insert(item, ms); - anim::start(this); - return 0; - } - Animations::const_iterator i = _animations.constFind(item); - if (i == _animations.cend()) i = _animations.insert(item, ms); - return ms - i.value(); -} - -void ItemAnimations::remove(const HistoryItem *item) { - _animations.remove(item); -} - -namespace { - ItemAnimations _itemAnimations; -} - -ItemAnimations &itemAnimations() { - return _itemAnimations; -} - HistoryItem::HistoryItem(History *history, HistoryBlock *block, MsgId msgId, int32 flags, QDateTime msgDate, int32 from) : y(0) , id(msgId) , date(msgDate) @@ -3029,18 +2889,6 @@ void HistoryItem::destroy() { history()->clearLastKeyboard(); if (App::main()) App::main()->updateBotKeyboard(history()); } - HistoryMedia *m = getMedia(true); - MediaOverviewType t = m ? mediaToOverviewType(m->type()) : OverviewCount; - if (t != OverviewCount) { - if (m->type() == MediaTypeDocument && static_cast(m)->document()->song()) { - history()->eraseFromOverview(OverviewAudioDocuments, id); - } else { - history()->eraseFromOverview(t, id); - } - } - if (hasTextLinks()) { - history()->eraseFromOverview(OverviewLinks, id); - } delete this; } @@ -3076,99 +2924,296 @@ void HistoryItem::setId(MsgId newId) { id = newId; } -HistoryItem::~HistoryItem() { - itemAnimations().remove(this); - App::historyUnregItem(this); - if (id < 0) { - App::app()->uploader()->cancel(fullId()); +void HistoryItem::clipCallback(ClipReaderNotification notification) { + HistoryMedia *media = getMedia(); + if (!media) return; + + ClipReader *reader = media ? media->getClipReader() : 0; + if (!reader) return; + + switch (notification) { + case ClipReaderReinit: { + bool stopped = false; + if (reader->paused()) { + if (MainWidget *m = App::main()) { + if (!m->isItemVisible(this)) { // stop animation if it is not visible + media->stopInline(this); + if (DocumentData *document = media->getDocument()) { // forget data from memory + document->forget(); + } + stopped = true; + } + } + } + if (!stopped) { + initDimensions(); + Notify::historyItemResized(this); + Notify::historyItemLayoutChanged(this); + } + } break; + + case ClipReaderRepaint: { + if (!reader->currentDisplayed()) { + Ui::repaintHistoryItem(this); + } + } break; } } -HistoryItem *regItem(HistoryItem *item, bool returnExisting) { - if (!item) return 0; - - HistoryItem *existing = App::historyRegItem(item); - if (existing) { - delete item; - return returnExisting ? existing : 0; +HistoryItem::~HistoryItem() { + App::historyUnregItem(this); + if (id < 0 && App::uploader()) { + App::uploader()->cancel(fullId()); } +} - item->initDimensions(); +HistoryItem *regItem(HistoryItem *item) { + if (item) { + App::historyRegItem(item); + item->initDimensions(); + } return item; } -HistoryPhoto::HistoryPhoto(const MTPDphoto &photo, const QString &caption, HistoryItem *parent) : HistoryMedia() -, pixw(1), pixh(1) -, data(App::feedPhoto(photo)) -, _caption(st::minPhotoSize) -, openl(new PhotoLink(data)) { +RadialAnimation::RadialAnimation(AnimationCreator creator) +: _firstStart(0) +, _lastStart(0) +, _lastTime(0) +, _opacity(0) +, a_arcEnd(0, 0) +, a_arcStart(0, FullArcLength) +, _animation(creator) { + +} + +void RadialAnimation::start(float64 prg) { + _firstStart = _lastStart = _lastTime = getms(); + int32 iprg = qRound(qMax(prg, 0.0001) * AlmostFullArcLength), iprgstrict = qRound(prg * AlmostFullArcLength); + a_arcEnd = anim::ivalue(iprgstrict, iprg); + _animation.start(); +} + +void RadialAnimation::update(float64 prg, bool finished, uint64 ms) { + int32 iprg = qRound(qMax(prg, 0.0001) * AlmostFullArcLength); + if (iprg != a_arcEnd.to()) { + a_arcEnd.start(iprg); + _lastStart = _lastTime; + } + _lastTime = ms; + + float64 dt = float64(ms - _lastStart), fulldt = float64(ms - _firstStart); + _opacity = qMin(fulldt / st::radialDuration, 1.); + if (!finished) { + a_arcEnd.update(1. - (st::radialDuration / (st::radialDuration + dt)), anim::linear); + } else if (dt >= st::radialDuration) { + a_arcEnd.update(1, anim::linear); + stop(); + } else { + float64 r = dt / st::radialDuration; + a_arcEnd.update(r, anim::linear); + _opacity *= 1 - r; + } + float64 fromstart = fulldt / st::radialPeriod; + a_arcStart.update(fromstart - qFloor(fromstart), anim::linear); +} + +void RadialAnimation::stop() { + _firstStart = _lastStart = _lastTime = 0; + a_arcEnd = anim::ivalue(0, 0); + _animation.stop(); +} + +void RadialAnimation::step(uint64 ms) { + _animation.step(ms); +} + +void RadialAnimation::draw(Painter &p, const QRect &inner, int32 thickness, const style::color &color) { + float64 o = p.opacity(); + p.setOpacity(o * _opacity); + + QPen pen(color->p), was(p.pen()); + pen.setWidth(thickness); + p.setPen(pen); + + int32 len = MinArcLength + a_arcEnd.current(); + int32 from = QuarterArcLength - a_arcStart.current() - len; + if (rtl()) { + from = QuarterArcLength - (from - QuarterArcLength) - len; + if (from < 0) from += FullArcLength; + } + + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.drawArc(inner, from, len); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + + p.setPen(was); + p.setOpacity(o); +} + +namespace { + int32 videoMaxStatusWidth(VideoData *video) { + int32 result = st::normalFont->width(formatDownloadText(video->size, video->size)); + result = qMax(result, st::normalFont->width(formatDurationAndSizeText(video->duration, video->size))); + return result; + } + + int32 audioMaxStatusWidth(AudioData *audio) { + int32 result = st::normalFont->width(formatDownloadText(audio->size, audio->size)); + result = qMax(result, st::normalFont->width(formatPlayedText(audio->duration, audio->duration))); + result = qMax(result, st::normalFont->width(formatDurationAndSizeText(audio->duration, audio->size))); + return result; + } + + int32 documentMaxStatusWidth(DocumentData *document) { + int32 result = st::normalFont->width(formatDownloadText(document->size, document->size)); + if (SongData *song = document->song()) { + result = qMax(result, st::normalFont->width(formatPlayedText(song->duration, song->duration))); + result = qMax(result, st::normalFont->width(formatDurationAndSizeText(song->duration, document->size))); + } else { + result = qMax(result, st::normalFont->width(formatSizeText(document->size))); + } + return result; + } + + int32 gifMaxStatusWidth(DocumentData *document) { + int32 result = st::normalFont->width(formatDownloadText(document->size, document->size)); + result = qMax(result, st::normalFont->width(formatGifAndSizeText(document->size))); + return result; + } +} + +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::linkOut(HistoryItem *parent, const TextLinkPtr &lnk) { + if (_animation && (lnk == _savel || lnk == _cancell)) { + _animation->a_thumbOver.start(0); + _animation->_a_thumbOver.start(); + } +} + +void HistoryFileMedia::setLinks(ITextLink *openl, ITextLink *savel, ITextLink *cancell) { + _openl.reset(openl); + _savel.reset(savel); + _cancell.reset(cancell); +} + +void HistoryFileMedia::setStatusSize(int32 newSize, int32 fullSize, int32 duration, qint64 realDuration) const { + _statusSize = newSize; + if (_statusSize == FileStatusSizeReady) { + _statusText = (duration >= 0) ? formatDurationAndSizeText(duration, fullSize) : (duration < -1 ? formatGifAndSizeText(fullSize) : formatSizeText(fullSize)); + } else if (_statusSize == FileStatusSizeLoaded) { + _statusText = (duration >= 0) ? formatDurationText(duration) : (duration < -1 ? qsl("GIF") : formatSizeText(fullSize)); + } else if (_statusSize == FileStatusSizeFailed) { + _statusText = lang(lng_attach_failed); + } else if (_statusSize >= 0) { + _statusText = formatDownloadText(_statusSize, fullSize); + } else { + _statusText = formatPlayedText(-_statusSize - 1, realDuration); + } +} + +void HistoryFileMedia::step_thumbOver(const HistoryItem *parent, float64 ms, bool timer) { + float64 dt = ms / st::msgFileOverDuration; + if (dt >= 1) { + _animation->a_thumbOver.finish(); + _animation->_a_thumbOver.stop(); + checkAnimationFinished(); + } else if (!timer) { + _animation->a_thumbOver.update(dt, anim::linear); + } + if (timer) { + Ui::repaintHistoryItem(parent); + } +} + +void HistoryFileMedia::step_radial(const HistoryItem *parent, uint64 ms, bool timer) { + if (timer) { + Ui::repaintHistoryItem(parent); + } else { + _animation->radial.update(dataProgress(), dataFinished(), ms); + if (!_animation->radial.animating()) { + checkAnimationFinished(); + } + } +} + +void HistoryFileMedia::ensureAnimation(const HistoryItem *parent) const { + if (!_animation) { + _animation = new AnimationData( + animation(parent, const_cast(this), &HistoryFileMedia::step_thumbOver), + animation(parent, const_cast(this), &HistoryFileMedia::step_radial)); + } +} + +void HistoryFileMedia::checkAnimationFinished() { + if (_animation && !_animation->_a_thumbOver.animating() && !_animation->radial.animating()) { + if (dataLoaded()) { + delete _animation; + _animation = 0; + } + } +} + +HistoryFileMedia::~HistoryFileMedia() { + deleteAndMark(_animation); +} + +HistoryPhoto::HistoryPhoto(PhotoData *photo, const QString &caption, const HistoryItem *parent) : HistoryFileMedia() +, _data(photo) +, _pixw(1) +, _pixh(1) +, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { + setLinks(new PhotoLink(_data), new PhotoSaveLink(_data), new PhotoCancelLink(_data)); + if (!caption.isEmpty()) { _caption.setText(st::msgFont, caption + parent->skipBlock(), itemTextNoMonoOptions(parent)); } init(); } -HistoryPhoto::HistoryPhoto(PeerData *chat, const MTPDphoto &photo, int32 width) : HistoryMedia(width) -, pixw(1), pixh(1) -, data(App::feedPhoto(photo)) -, openl(new PhotoLink(data, chat)) { +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)); + + _width = width; + init(); +} + +HistoryPhoto::HistoryPhoto(const HistoryPhoto &other) : HistoryFileMedia() +, _data(other._data) +, _pixw(other._pixw) +, _pixh(other._pixh) +, _caption(other._caption) { + setLinks(new PhotoLink(_data), new PhotoSaveLink(_data), new PhotoCancelLink(_data)); + init(); } void HistoryPhoto::init() { - data->thumb->load(); + _data->thumb->load(); } void HistoryPhoto::initDimensions(const HistoryItem *parent) { - if (_caption.hasSkipBlock()) _caption.setSkipBlock(parent->skipBlockWidth(), parent->skipBlockHeight()); + if (_caption.hasSkipBlock()) { + _caption.setSkipBlock(parent->skipBlockWidth(), parent->skipBlockHeight()); + } - int32 tw = convertScale(data->full->width()), th = convertScale(data->full->height()); + int32 tw = convertScale(_data->full->width()), th = convertScale(_data->full->height()); if (!tw || !th) { tw = th = 1; } - int32 thumbw = tw; - int32 thumbh = th; - if (!w) { - w = thumbw; - } else { - thumbh = w; // square chat photo updates - } - _maxw = qMax(w, int32(st::minPhotoSize)); - _minh = qMax(thumbh, int32(st::minPhotoSize)); - const HistoryReply *reply = toHistoryReply(parent); - const HistoryForwarded *fwd = toHistoryForwarded(parent); - if (reply || (fwd && fwd->fromForwarded()->isChannel()) || !_caption.isEmpty()) { - _maxw += st::mediaPadding.left() + st::mediaPadding.right(); - if (reply) { - _minh += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - } else { - if (!parent->displayFromName() || !fwd) { - _minh += st::msgPadding.top(); - } - if (fwd) { - _minh += st::msgServiceNameFont->height + st::msgPadding.top(); - } - } - if (parent->displayFromName()) { - _minh += st::msgPadding.top() + st::msgNameFont->height; - } - if (!_caption.isEmpty()) { - _minh += st::webPagePhotoSkip + _caption.minHeight(); - } - _minh += st::mediaPadding.bottom(); - } -} - -int32 HistoryPhoto::resize(int32 width, const HistoryItem *parent) { - const HistoryReply *reply = toHistoryReply(parent); - const HistoryForwarded *fwd = reply ? 0 : toHistoryForwarded(parent); - - pixw = qMin(width, _maxw); - if (reply || (fwd && fwd->fromForwarded()->isChannel()) || !_caption.isEmpty()) { - pixw -= st::mediaPadding.left() + st::mediaPadding.right(); - } - - int32 tw = convertScale(data->full->width()), th = convertScale(data->full->height()); if (tw > st::maxMediaSize) { th = (st::maxMediaSize * th) / tw; tw = st::maxMediaSize; @@ -3177,43 +3222,274 @@ int32 HistoryPhoto::resize(int32 width, const HistoryItem *parent) { tw = (st::maxMediaSize * tw) / th; th = st::maxMediaSize; } - pixh = th; - if (tw > pixw) { - pixh = (pixw * pixh / tw); + + if (parent->toHistoryMessage()) { + bool bubble = parent->hasBubble(); + + 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)); + if (bubble) { + maxActualWidth += st::mediaPadding.left() + st::mediaPadding.right(); + _maxw += st::mediaPadding.left() + st::mediaPadding.right(); + _minh += st::mediaPadding.top() + st::mediaPadding.bottom(); + if (!_caption.isEmpty()) { + _minh += st::mediaCaptionSkip + _caption.countHeight(maxActualWidth - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); + } + } } else { - pixw = tw; + _maxw = _minh = _width; } - if (pixh > width) { - pixw = (pixw * width) / pixh; - pixh = width; +} + +int32 HistoryPhoto::resize(int32 width, const HistoryItem *parent) { + bool bubble = parent->hasBubble(); + + int32 tw = convertScale(_data->full->width()), th = convertScale(_data->full->height()); + if (tw > st::maxMediaSize) { + th = (st::maxMediaSize * th) / tw; + tw = st::maxMediaSize; } - if (pixw < 1) pixw = 1; - if (pixh < 1) pixh = 1; - w = qMax(pixw, int16(st::minPhotoSize)); - _height = qMax(pixh, int16(st::minPhotoSize)); - if (reply || (fwd && fwd->fromForwarded()->isChannel()) || !_caption.isEmpty()) { - if (reply) { - _height += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - } else { - if (!parent->displayFromName() || !fwd) { - _height += st::msgPadding.top(); - } - if (fwd) { - _height += st::msgServiceNameFont->height + st::msgPadding.top(); - } - } - if (parent->displayFromName()) { - _height += st::msgPadding.top() + st::msgNameFont->height; - } + if (th > st::maxMediaSize) { + tw = (st::maxMediaSize * tw) / th; + th = st::maxMediaSize; + } + + _pixw = qMin(width, _maxw); + if (bubble) { + _pixw -= st::mediaPadding.left() + st::mediaPadding.right(); + } + _pixh = th; + if (tw > _pixw) { + _pixh = (_pixw * _pixh / tw); + } else { + _pixw = tw; + } + if (_pixh > width) { + _pixw = (_pixw * width) / _pixh; + _pixh = width; + } + if (_pixw < 1) _pixw = 1; + if (_pixh < 1) _pixh = 1; + + int32 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()) { - _height += st::webPagePhotoSkip + _caption.countHeight(w); + int32 captionw = _width - st::msgPadding.left() - st::msgPadding.right(); + _height += st::mediaCaptionSkip + _caption.countHeight(captionw) + st::msgPadding.bottom(); } - _height += st::mediaPadding.bottom(); - w += st::mediaPadding.left() + st::mediaPadding.right(); } return _height; } +void HistoryPhoto::draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const { + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; + + _data->automaticLoad(parent); + bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); + + bool notChild = (parent->getMedia() == this); + int32 skipx = 0, skipy = 0, width = _width, height = _height; + bool bubble = parent->hasBubble(); + bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; + + int32 captionw = width - st::msgPadding.left() - st::msgPadding.right(); + + if (displayLoading) { + ensureAnimation(parent); + if (!_animation->radial.animating()) { + _animation->radial.start(_data->progress()); + } + } + bool radial = isRadialAnimation(ms); + + if (bubble) { + skipx = st::mediaPadding.left(); + skipy = st::mediaPadding.top(); + + width -= st::mediaPadding.left() + st::mediaPadding.right(); + height -= skipy + st::mediaPadding.bottom(); + if (!_caption.isEmpty()) { + height -= st::mediaCaptionSkip + _caption.countHeight(captionw) + st::msgPadding.bottom(); + } + } else { + App::roundShadow(p, 0, 0, width, height, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); + } + + QPixmap pix; + if (loaded) { + pix = _data->full->pixSingle(_pixw, _pixh, width, height); + } else { + pix = _data->thumb->pixBlurredSingle(_pixw, _pixh, width, height); + } + QRect rthumb(rtlrect(skipx, skipy, width, height, _width)); + p.drawPixmap(rthumb.topLeft(), pix); + if (selected) { + App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); + } + + if (notChild && (radial || (!loaded && !_data->loading()))) { + float64 radialOpacity = (radial && loaded && !_data->uploading()) ? _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) { + p.setBrush(st::msgDateImgBgSelected); + } else if (isThumbAnimation(ms)) { + float64 over = _animation->a_thumbOver.current(); + 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); + p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); + } + + p.setOpacity(radialOpacity * p.opacity()); + + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.drawEllipse(inner); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + + p.setOpacity(radial ? _animation->radial.opacity() : 1); + + p.setOpacity(radialOpacity); + style::sprite icon; + if (radial || _data->loading()) { + DelayedStorageImage *delayed = _data->full->toDelayedStorageImage(); + if (!delayed || !delayed->location().isNull()) { + icon = (selected ? st::msgFileInCancelSelected : st::msgFileInCancel); + } + } else { + icon = (selected ? st::msgFileInDownloadSelected : st::msgFileInDownload); + } + if (!icon.isEmpty()) { + p.drawSpriteCenter(inner, icon); + } + if (radial) { + p.setOpacity(1); + QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); + _animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::msgInBgSelected : st::msgInBg); + } + } + + // date + if (_caption.isEmpty()) { + if (notChild) { + int32 fullRight = skipx + width, fullBottom = skipy + height; + 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); + } +} + +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(); + + if (bubble) { + skipx = st::mediaPadding.left(); + skipy = st::mediaPadding.top(); + if (!_caption.isEmpty()) { + 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; + } + height -= st::mediaCaptionSkip; + } + width -= st::mediaPadding.left() + st::mediaPadding.right(); + height -= skipy + st::mediaPadding.bottom(); + } + if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height) { + if (_data->uploading()) { + lnk = _cancell; + } else if (_data->loaded()) { + lnk = _openl; + } else if (_data->loading()) { + DelayedStorageImage *delayed = _data->full->toDelayedStorageImage(); + if (!delayed || !delayed->location().isNull()) { + lnk = _cancell; + } + } else { + lnk = _savel; + } + if (_caption.isEmpty() && parent->getMedia() == this) { + int32 fullRight = skipx + width, fullBottom = skipy + height; + bool inDate = parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage); + if (inDate) { + state = HistoryInDateCursorState; + } + } + return; + } +} + +void HistoryPhoto::updateFrom(const MTPMessageMedia &media, HistoryItem *parent) { + if (media.type() == mtpc_messageMediaPhoto) { + const MTPPhoto &photo(media.c_messageMediaPhoto().vphoto); + App::feedPhoto(photo, _data); + + if (photo.type() == mtpc_photo) { + const QVector &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) { + char size = 0; + const MTPFileLocation *loc = 0; + switch (sizes.at(i).type()) { + case mtpc_photoSize: { + const string &s(sizes.at(i).c_photoSize().vtype.c_string().v); + loc = &sizes.at(i).c_photoSize().vlocation; + if (s.size()) size = s[0]; + } break; + + case mtpc_photoCachedSize: { + const string &s(sizes.at(i).c_photoCachedSize().vtype.c_string().v); + loc = &sizes.at(i).c_photoCachedSize().vlocation; + if (s.size()) size = s[0]; + } break; + } + if (!loc || loc->type() != mtpc_fileLocation) continue; + if (size == 's') { + Local::writeImage(storageKey(loc->c_fileLocation()), _data->thumb); + } else if (size == 'm') { + Local::writeImage(storageKey(loc->c_fileLocation()), _data->medium); + } else if (size == 'x' && max < 1) { + max = 1; + maxLocation = &loc->c_fileLocation(); + } else if (size == 'y' && max < 2) { + max = 2; + maxLocation = &loc->c_fileLocation(); + //} else if (size == 'w' && max < 3) { + // max = 3; + // maxLocation = &loc->c_fileLocation(); + } + } + if (maxLocation) { + Local::writeImage(storageKey(*maxLocation), _data->full); + } + } + } +} + +void HistoryPhoto::regItem(HistoryItem *item) { + App::regPhotoItem(_data, item); +} + +void HistoryPhoto::unregItem(HistoryItem *item) { + App::unregPhotoItem(_data, item); +} + const QString HistoryPhoto::inDialogsText() const { return _caption.isEmpty() ? lang(lng_in_dlg_photo) : _caption.original(0, 0xFFFF, Text::ExpandLinksNone); } @@ -3222,70 +3498,228 @@ const QString HistoryPhoto::inHistoryText() const { return qsl("[ ") + lang(lng_in_dlg_photo) + (_caption.isEmpty() ? QString() : (qsl(", ") + _caption.original(0, 0xFFFF, Text::ExpandLinksAll))) + qsl(" ]"); } -bool HistoryPhoto::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const { - if (width < 0) width = w; - return (x >= 0 && y >= 0 && x < width && y < _height); +ImagePtr HistoryPhoto::replyPreview() { + return _data->makeReplyPreview(); } -void HistoryPhoto::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width) const { - if (width < 0) width = w; - if (width < 1) return; +HistoryVideo::HistoryVideo(const MTPDvideo &video, const QString &caption, HistoryItem *parent) : HistoryFileMedia() +, _data(App::feedVideo(video)) +, _thumbw(1) +, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { + if (!caption.isEmpty()) { + _caption.setText(st::msgFont, caption + parent->skipBlock(), itemTextNoMonoOptions(parent)); + } - int skipx = 0, skipy = 0, height = _height; - const HistoryReply *reply = toHistoryReply(parent); - const HistoryForwarded *fwd = reply ? 0 : toHistoryForwarded(parent); - int replyFrom = 0, fwdFrom = 0; - if (reply || (fwd && fwd->fromForwarded()->isChannel()) || !_caption.isEmpty()) { - skipx = st::mediaPadding.left(); - if (reply) { - skipy = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - } else if (fwd) { - skipy = st::msgServiceNameFont->height + st::msgPadding.top(); - } - if (parent->displayFromName()) { - replyFrom = st::msgPadding.top() + st::msgNameFont->height; - fwdFrom = st::msgPadding.top() + st::msgNameFont->height; - skipy += replyFrom; - if (x >= st::mediaPadding.left() && y >= st::msgPadding.top() && x < width - st::mediaPadding.left() - st::mediaPadding.right() && x < st::mediaPadding.left() + parent->from()->nameText.maxWidth() && y < replyFrom) { - lnk = parent->from()->lnk; - return; - } - if (!reply && !fwd) skipy += st::msgPadding.top(); - } else if (!reply) { - fwdFrom = st::msgPadding.top(); - skipy += fwdFrom; - } - if (reply) { - if (x >= 0 && y >= replyFrom + st::msgReplyPadding.top() && x < width && y < skipy - st::msgReplyPadding.bottom()) { - lnk = reply->replyToLink(); - return; - } - } else if (fwd) { - if (y >= fwdFrom && y < fwdFrom + st::msgServiceNameFont->height) { - return fwd->getForwardedState(lnk, state, x - st::mediaPadding.left(), width - st::mediaPadding.left() - st::mediaPadding.right()); - } - } - height -= skipy + st::mediaPadding.bottom(); - width -= st::mediaPadding.left() + st::mediaPadding.right(); + setLinks(new VideoOpenLink(_data), new VideoSaveLink(_data), new VideoCancelLink(_data)); + + setStatusSize(FileStatusSizeReady); + + _data->thumb->load(); +} + +HistoryVideo::HistoryVideo(const HistoryVideo &other) : HistoryFileMedia() +, _data(other._data) +, _thumbw(other._thumbw) +, _caption(other._caption) { + setLinks(new VideoOpenLink(_data), new VideoSaveLink(_data), new VideoCancelLink(_data)); + + setStatusSize(other._statusSize); +} + +void HistoryVideo::initDimensions(const HistoryItem *parent) { + bool bubble = parent->hasBubble(); + + if (_caption.hasSkipBlock()) { + _caption.setSkipBlock(parent->skipBlockWidth(), parent->skipBlockHeight()); + } + + int32 tw = convertScale(_data->thumb->width()), th = convertScale(_data->thumb->height()); + if (!tw || !th) { + tw = th = 1; + } + if (tw * st::msgVideoSize.height() > th * st::msgVideoSize.width()) { + th = qRound((st::msgVideoSize.width() / float64(tw)) * th); + tw = st::msgVideoSize.width(); + } else { + tw = qRound((st::msgVideoSize.height() / float64(th)) * tw); + th = st::msgVideoSize.height(); + } + + _thumbw = qMax(tw, 1); + int32 minWidth = qMax(st::minPhotoSize, parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + minWidth = qMax(minWidth, videoMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); + _maxw = qMax(_thumbw, int16(minWidth)); + _minh = qMax(th, int32(st::minPhotoSize)); + if (bubble) { + _maxw += st::mediaPadding.left() + st::mediaPadding.right(); + _minh += st::mediaPadding.top() + st::mediaPadding.bottom(); if (!_caption.isEmpty()) { - int32 fullRight = skipx + width + st::mediaPadding.right(), fullBottom = _height; - bool inDate = parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayDefault); - if (inDate) { - state = HistoryInDateCursorState; - } - - height -= _caption.countHeight(width) + st::webPagePhotoSkip; - if (x >= skipx && y >= skipy + height + st::webPagePhotoSkip && x < skipx + width && y < _height) { - bool inText = false; - _caption.getState(lnk, inText, x - skipx, y - skipy - height - st::webPagePhotoSkip, width); - state = inDate ? HistoryInDateCursorState : (inText ? HistoryInTextCursorState : HistoryDefaultCursorState); - } + _minh += st::mediaCaptionSkip + _caption.countHeight(_maxw - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); } } +} + +int32 HistoryVideo::resize(int32 width, const HistoryItem *parent) { + bool bubble = parent->hasBubble(); + + int32 tw = convertScale(_data->thumb->width()), th = convertScale(_data->thumb->height()); + if (!tw || !th) { + tw = th = 1; + } + if (tw * st::msgVideoSize.height() > th * st::msgVideoSize.width()) { + th = qRound((st::msgVideoSize.width() / float64(tw)) * th); + tw = st::msgVideoSize.width(); + } else { + tw = qRound((st::msgVideoSize.height() / float64(th)) * tw); + th = st::msgVideoSize.height(); + } + + if (bubble) { + width -= st::mediaPadding.left() + st::mediaPadding.right(); + } + if (width < tw) { + th = qRound((width / float64(tw)) * th); + tw = width; + } + + int32 minWidth = qMax(st::minPhotoSize, parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + minWidth = qMax(minWidth, videoMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); + _width = qMax(_thumbw, int16(minWidth)); + _height = qMax(th, int32(st::minPhotoSize)); + 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(); + _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 { + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; + + _data->automaticLoad(parent); + bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); + + int32 skipx = 0, skipy = 0, width = _width, height = _height; + bool bubble = parent->hasBubble(); + bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; + + int32 captionw = width - st::msgPadding.left() - st::msgPadding.right(); + + if (displayLoading) { + ensureAnimation(parent); + if (!_animation->radial.animating()) { + _animation->radial.start(_data->progress()); + } + } + updateStatusText(parent); + bool radial = isRadialAnimation(ms); + + if (bubble) { + skipx = st::mediaPadding.left(); + skipy = st::mediaPadding.top(); + + width -= st::mediaPadding.left() + st::mediaPadding.right(); + height -= skipy + st::mediaPadding.bottom(); + if (!_caption.isEmpty()) { + height -= st::mediaCaptionSkip + _caption.countHeight(captionw) + st::msgPadding.bottom(); + } + } else { + App::roundShadow(p, 0, 0, width, height, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); + } + + QRect rthumb(rtlrect(skipx, skipy, width, height, _width)); + p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(_thumbw, 0, width, height)); + if (selected) { + App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); + } + + 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) { + p.setBrush(st::msgDateImgBgSelected); + } else if (isThumbAnimation(ms)) { + float64 over = _animation->a_thumbOver.current(); + 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); + p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); + } + + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.drawEllipse(inner); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + + if (!selected && _animation) { + p.setOpacity(1); + } + + style::sprite icon; + if (loaded) { + icon = (selected ? st::msgFileInPlaySelected : st::msgFileInPlay); + } else if (radial || _data->loading()) { + icon = (selected ? st::msgFileInCancelSelected : st::msgFileInCancel); + } else { + icon = (selected ? st::msgFileInDownloadSelected : st::msgFileInDownload); + } + p.drawSpriteCenter(inner, icon); + if (radial) { + QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); + _animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::msgInBgSelected : st::msgInBg); + } + + 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(); + App::roundRect(p, rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, _width), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); + p.setFont(st::normalFont); + p.setPen(st::white); + p.drawTextLeft(statusX, statusY, _width, _statusText, statusW - 2 * st::msgDateImgPadding.x()); + + // date + if (_caption.isEmpty()) { + if (parent->getMedia() == this) { + int32 fullRight = skipx + width, fullBottom = skipy + height; + 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); + } +} + +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; + + bool loaded = _data->loaded(); + + int32 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(); + 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; + } + height -= st::mediaCaptionSkip; + } + width -= st::mediaPadding.left() + st::mediaPadding.right(); + height -= skipy + st::mediaPadding.bottom(); + } if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height) { - lnk = openl; - if (_caption.isEmpty()) { - int32 fullRight = skipx + width, fullBottom = _height - (skipx ? st::mediaPadding.bottom() : 0); + lnk = 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); if (inDate) { state = HistoryInDateCursorState; @@ -3295,253 +3729,8 @@ void HistoryPhoto::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x } } -HistoryMedia *HistoryPhoto::clone() const { - return new HistoryPhoto(*this); -} - -void HistoryPhoto::updateFrom(const MTPMessageMedia &media) { - if (media.type() == mtpc_messageMediaPhoto) { - const MTPPhoto &photo(media.c_messageMediaPhoto().vphoto); - if (photo.type() == mtpc_photo) { - const QVector &sizes(photo.c_photo().vsizes.c_vector().v); - for (QVector::const_iterator i = sizes.cbegin(), e = sizes.cend(); i != e; ++i) { - char size = 0; - const MTPFileLocation *loc = 0; - switch (i->type()) { - case mtpc_photoSize: { - const string &s(i->c_photoSize().vtype.c_string().v); - loc = &i->c_photoSize().vlocation; - if (s.size()) size = s[0]; - } break; - - case mtpc_photoCachedSize: { - const string &s(i->c_photoCachedSize().vtype.c_string().v); - loc = &i->c_photoCachedSize().vlocation; - if (s.size()) size = s[0]; - } break; - } - if (!loc || loc->type() != mtpc_fileLocation) continue; - if (size == 's') { - Local::writeImage(storageKey(loc->c_fileLocation()), data->thumb); - } else if (size == 'm') { - Local::writeImage(storageKey(loc->c_fileLocation()), data->medium); - } else if (size == 'x') { - Local::writeImage(storageKey(loc->c_fileLocation()), data->full); - } - } - } - } -} - -void HistoryPhoto::draw(Painter &p, const HistoryItem *parent, bool selected, int32 width) const { - const HistoryReply *reply = toHistoryReply(parent); - const HistoryForwarded *fwd = reply ? 0 : toHistoryForwarded(parent); - bool fromChannel = parent->fromChannel(), out = parent->out(), outbg = out && !fromChannel; - - if (width < 0) width = w; - int skipx = 0, skipy = 0, height = _height; - if (reply || (fwd && fwd->fromForwarded()->isChannel()) || !_caption.isEmpty()) { - skipx = st::mediaPadding.left(); - - style::color bg(selected ? (outbg ? st::msgOutSelectBg : st::msgInSelectBg) : (outbg ? st::msgOutBg : st::msgInBg)); - style::color sh(selected ? (outbg ? st::msgOutSelectShadow : st::msgInSelectShadow) : (outbg ? st::msgOutShadow : st::msgInShadow)); - RoundCorners cors(selected ? (outbg ? MessageOutSelectedCorners : MessageInSelectedCorners) : (outbg ? MessageOutCorners : MessageInCorners)); - App::roundRect(p, 0, 0, width, _height, bg, cors, &sh); - - int replyFrom = 0, fwdFrom = 0; - if (parent->displayFromName()) { - replyFrom = st::msgPadding.top() + st::msgNameFont->height; - fwdFrom = st::msgPadding.top() + st::msgNameFont->height; - skipy += replyFrom; - p.setFont(st::msgNameFont->f); - if (fromChannel) { - p.setPen(selected ? st::msgInServiceSelColor : st::msgInServiceColor); - } else { - p.setPen(parent->from()->color); - } - parent->from()->nameText.drawElided(p, st::mediaPadding.left(), st::msgPadding.top(), width - st::mediaPadding.left() - st::mediaPadding.right()); - if (!fwd && !reply) skipy += st::msgPadding.top(); - } else if (!reply) { - fwdFrom = st::msgPadding.top(); - skipy += fwdFrom; - } - if (reply) { - skipy += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - reply->drawReplyTo(p, st::msgReplyPadding.left(), replyFrom, width - st::msgReplyPadding.left() - st::msgReplyPadding.right(), selected); - } else if (fwd) { - skipy += st::msgServiceNameFont->height + st::msgPadding.top(); - fwd->drawForwardedFrom(p, st::mediaPadding.left(), fwdFrom, width - st::mediaPadding.left() - st::mediaPadding.right(), selected); - } - width -= st::mediaPadding.left() + st::mediaPadding.right(); - height -= skipy + st::mediaPadding.bottom(); - if (!_caption.isEmpty()) { - height -= st::webPagePhotoSkip + _caption.countHeight(width); - } - } else { - App::roundShadow(p, 0, 0, width, _height, selected ? st::msgInSelectShadow : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); - } - data->full->load(false, false); - bool full = data->full->loaded(); - QPixmap pix; - if (full) { - pix = data->full->pixSingle(pixw, pixh, width, height); - } else { - pix = data->thumb->pixBlurredSingle(pixw, pixh, width, height); - } - p.drawPixmap(skipx, skipy, pix); - if (!full) { - uint64 dt = itemAnimations().animate(parent, getms()); - int32 cnt = int32(st::photoLoaderCnt), period = int32(st::photoLoaderPeriod), t = dt % period, delta = int32(st::photoLoaderDelta); - - int32 x = (width - st::photoLoader.width()) / 2, y = (height - st::photoLoader.height()) / 2; - p.fillRect(skipx + x, skipy + y, st::photoLoader.width(), st::photoLoader.height(), st::photoLoaderBg->b); - x += (st::photoLoader.width() - cnt * st::photoLoaderPoint.width() - (cnt - 1) * st::photoLoaderSkip) / 2; - y += (st::photoLoader.height() - st::photoLoaderPoint.height()) / 2; - QColor c(st::white->c); - QBrush b(c); - for (int32 i = 0; i < cnt; ++i) { - t -= delta; - while (t < 0) t += period; - - float64 alpha = (t >= st::photoLoaderDuration1 + st::photoLoaderDuration2) ? 0 : ((t > st::photoLoaderDuration1 ? ((st::photoLoaderDuration1 + st::photoLoaderDuration2 - t) / st::photoLoaderDuration2) : (t / st::photoLoaderDuration1))); - c.setAlphaF(st::photoLoaderAlphaMin + alpha * (1 - st::photoLoaderAlphaMin)); - b.setColor(c); - p.fillRect(skipx + x + i * (st::photoLoaderPoint.width() + st::photoLoaderSkip), skipy + y, st::photoLoaderPoint.width(), st::photoLoaderPoint.height(), b); - } - } - - if (selected) { - App::roundRect(p, skipx, skipy, width, height, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); - } - - // date - QString time(parent->timeText()); - if (_caption.isEmpty()) { - int32 fullRight = skipx + width, fullBottom = _height - (skipx ? st::mediaPadding.bottom() : 0); - parent->drawInfo(p, fullRight, fullBottom, selected, InfoDisplayOverImage); - } else { - p.setPen(st::black->p); - _caption.draw(p, skipx, skipy + height + st::webPagePhotoSkip, width); - - int32 fullRight = skipx + width + st::mediaPadding.right(), fullBottom = _height; - parent->drawInfo(p, fullRight, fullBottom, selected, InfoDisplayDefault); - } -} - -ImagePtr HistoryPhoto::replyPreview() { - return data->makeReplyPreview(); -} - -QString formatSizeText(qint64 size) { - if (size >= 1024 * 1024) { // more than 1 mb - qint64 sizeTenthMb = (size * 10 / (1024 * 1024)); - return QString::number(sizeTenthMb / 10) + '.' + QString::number(sizeTenthMb % 10) + qsl(" MB"); - } - if (size >= 1024) { - qint64 sizeTenthKb = (size * 10 / 1024); - return QString::number(sizeTenthKb / 10) + '.' + QString::number(sizeTenthKb % 10) + qsl(" KB"); - } - return QString::number(size) + qsl(" B"); -} - -QString formatDownloadText(qint64 ready, qint64 total) { - QString readyStr, totalStr, mb; - if (total >= 1024 * 1024) { // more than 1 mb - qint64 readyTenthMb = (ready * 10 / (1024 * 1024)), totalTenthMb = (total * 10 / (1024 * 1024)); - readyStr = QString::number(readyTenthMb / 10) + '.' + QString::number(readyTenthMb % 10); - totalStr = QString::number(totalTenthMb / 10) + '.' + QString::number(totalTenthMb % 10); - mb = qsl("MB"); - } else if (total >= 1024) { - qint64 readyKb = (ready / 1024), totalKb = (total / 1024); - readyStr = QString::number(readyKb); - totalStr = QString::number(totalKb); - mb = qsl("KB"); - } else { - readyStr = QString::number(ready); - totalStr = QString::number(total); - mb = qsl("B"); - } - return lng_save_downloaded(lt_ready, readyStr, lt_total, totalStr, lt_mb, mb); -} - -QString formatDurationText(qint64 duration) { - qint64 hours = (duration / 3600), minutes = (duration % 3600) / 60, seconds = duration % 60; - return (hours ? QString::number(hours) + ':' : QString()) + (minutes >= 10 ? QString() : QString('0')) + QString::number(minutes) + ':' + (seconds >= 10 ? QString() : QString('0')) + QString::number(seconds); -} - -QString formatDurationAndSizeText(qint64 duration, qint64 size) { - return lng_duration_and_size(lt_duration, formatDurationText(duration), lt_size, formatSizeText(size)); -} - -int32 _downloadWidth = 0, _openWithWidth = 0, _cancelWidth = 0, _buttonWidth = 0; - -HistoryVideo::HistoryVideo(const MTPDvideo &video, const QString &caption, HistoryItem *parent) : HistoryMedia() -, data(App::feedVideo(video)) -, _openl(new VideoOpenLink(data)) -, _savel(new VideoSaveLink(data)) -, _cancell(new VideoCancelLink(data)) -, _caption(st::minPhotoSize) -, _dldDone(0) -, _uplDone(0) -{ - if (!caption.isEmpty()) { - _caption.setText(st::msgFont, caption + parent->skipBlock(), itemTextNoMonoOptions(parent)); - } - - _size = formatDurationAndSizeText(data->duration, data->size); - - if (!_openWithWidth) { - _downloadWidth = st::mediaSaveButton.font->width(lang(lng_media_download)); - _openWithWidth = st::mediaSaveButton.font->width(lang(lng_media_open_with)); - _cancelWidth = st::mediaSaveButton.font->width(lang(lng_media_cancel)); - _buttonWidth = (st::mediaSaveButton.width > 0) ? st::mediaSaveButton.width : ((_downloadWidth > _openWithWidth ? (_downloadWidth > _cancelWidth ? _downloadWidth : _cancelWidth) : _openWithWidth) - st::mediaSaveButton.width); - } - - data->thumb->load(); - - int32 tw = data->thumb->width(), th = data->thumb->height(); - if (data->thumb->isNull() || !tw || !th) { - _thumbw = 0; - } else if (tw > th) { - _thumbw = (tw * st::mediaThumbSize) / th; - } else { - _thumbw = st::mediaThumbSize; - } -} - -void HistoryVideo::initDimensions(const HistoryItem *parent) { - if (_caption.hasSkipBlock()) _caption.setSkipBlock(parent->skipBlockWidth(), parent->skipBlockHeight()); - - _maxw = st::mediaMaxWidth; - int32 tleft = st::mediaPadding.left() + st::mediaThumbSize + st::mediaPadding.right(); - if (!parent->out() || parent->fromChannel()) { // add Download / Save As button - _maxw += st::mediaSaveDelta + _buttonWidth; - } - _minh = st::mediaPadding.top() + st::mediaThumbSize + st::mediaPadding.bottom(); - if (parent->displayFromName()) { - _minh += st::msgPadding.top() + st::msgNameFont->height; - } - if (const HistoryReply *reply = toHistoryReply(parent)) { - _minh += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - } else if (const HistoryForwarded *fwd = toHistoryForwarded(parent)) { - if (!parent->displayFromName()) { - _minh += st::msgPadding.top(); - } - _minh += st::msgServiceNameFont->height; - } - if (_caption.isEmpty()) { - _height = _minh; - } else { - _minh += st::webPagePhotoSkip + _caption.minHeight(); - } -} - -void HistoryVideo::regItem(HistoryItem *item) { - App::regVideoItem(data, item); -} - -void HistoryVideo::unregItem(HistoryItem *item) { - App::unregVideoItem(data, item); +void HistoryVideo::setStatusSize(int32 newSize) const { + HistoryFileMedia::setStatusSize(newSize, _data->size, _data->duration, 0); } const QString HistoryVideo::inDialogsText() const { @@ -3552,519 +3741,177 @@ const QString HistoryVideo::inHistoryText() const { return qsl("[ ") + lang(lng_in_dlg_video) + (_caption.isEmpty() ? QString() : (qsl(", ") + _caption.original(0, 0xFFFF, Text::ExpandLinksAll))) + qsl(" ]"); } -bool HistoryVideo::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const { - int32 height = _height; - if (width < 0) { - width = w; - } else if (!_caption.isEmpty()) { - height = countHeight(parent, width); - } - if (width >= _maxw) { - width = _maxw; - } - return (x >= 0 && y >= 0 && x < width && y < height); -} - -int32 HistoryVideo::countHeight(const HistoryItem *parent, int32 width) const { - if (_caption.isEmpty()) return _height; - - if (width < 0) width = w; - if (width >= _maxw) { - width = _maxw; - } - - int32 h = st::mediaPadding.top() + st::mediaThumbSize + st::mediaPadding.bottom(); - if (parent->displayFromName()) { - h += st::msgPadding.top() + st::msgNameFont->height; - } - if (const HistoryReply *reply = toHistoryReply(parent)) { - h += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - } else if (const HistoryForwarded *fwd = toHistoryForwarded(parent)) { - if (!parent->displayFromName()) { - h += st::msgPadding.top(); - } - h += st::msgServiceNameFont->height; - } - if (!_caption.isEmpty()) { - int32 textw = width - st::mediaPadding.left() - st::mediaPadding.right(); - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; - if (!outbg) { // substract Download / Save As button - textw -= st::mediaSaveDelta + _buttonWidth; - } - h += st::webPagePhotoSkip + _caption.countHeight(textw); - } - return h; -} - -void HistoryVideo::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width) const { - int32 height = _height; - if (width < 0) { - width = w; - } else if (!_caption.isEmpty()) { - height = countHeight(parent, width); - } - if (width < 1) return; - - const HistoryReply *reply = toHistoryReply(parent); - const HistoryForwarded *fwd = reply ? 0 : toHistoryForwarded(parent); - int skipy = 0, replyFrom = 0, fwdFrom = 0; - if (reply) { - skipy = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - } else if (fwd) { - skipy = st::msgServiceNameFont->height; - } - if (parent->displayFromName()) { - replyFrom = st::msgPadding.top() + st::msgNameFont->height; - fwdFrom = st::msgPadding.top() + st::msgNameFont->height; - skipy += replyFrom; - } else if (fwd) { - fwdFrom = st::msgPadding.top(); - skipy += fwdFrom; - } - - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel, hovered, pressed; - if (width >= _maxw) { - width = _maxw; - } - - if (!outbg) { // draw Download / Save As button - int32 h = height; - if (!_caption.isEmpty()) { - h -= st::webPagePhotoSkip + _caption.countHeight(width - _buttonWidth - st::mediaSaveDelta - st::mediaPadding.left() - st::mediaPadding.right()); - } - int32 btnw = _buttonWidth, btnh = st::mediaSaveButton.height, btnx = width - _buttonWidth, btny = skipy + (h - skipy - btnh) / 2; - if (x >= btnx && y >= btny && x < btnx + btnw && y < btny + btnh) { - lnk = data->loader ? _cancell : _savel; - return; - } - width -= btnw + st::mediaSaveDelta; - } - - if (parent->displayFromName()) { - if (x >= st::mediaPadding.left() && y >= st::msgPadding.top() && x < width - st::mediaPadding.left() - st::mediaPadding.right() && x < st::mediaPadding.left() + parent->from()->nameText.maxWidth() && y < replyFrom) { - lnk = parent->from()->lnk; - return; - } - } - if (reply) { - if (x >= 0 && y >= replyFrom + st::msgReplyPadding.top() && x < width && y < skipy - st::msgReplyPadding.bottom()) { - lnk = reply->replyToLink(); - return; - } - } else if (fwd) { - if (y >= fwdFrom && y < skipy) { - return fwd->getForwardedState(lnk, state, x - st::mediaPadding.left(), width - st::mediaPadding.left() - st::mediaPadding.right()); - } - } - - bool inDate = parent->pointInTime(width, height, x, y, InfoDisplayDefault); - if (inDate) { - state = HistoryInDateCursorState; - } - - int32 tw = width - st::mediaPadding.left() - st::mediaPadding.right(); - if (x >= st::mediaPadding.left() && y >= skipy + st::mediaPadding.top() && x < st::mediaPadding.left() + tw && y < skipy + st::mediaPadding.top() + st::mediaThumbSize && !data->loader && data->access) { - lnk = _openl; - return; - } - if (!_caption.isEmpty() && x >= st::mediaPadding.left() && x < st::mediaPadding.left() + tw && y >= skipy + st::mediaPadding.top() + st::mediaThumbSize + st::webPagePhotoSkip) { - bool inText = false; - _caption.getState(lnk, inText, x - st::mediaPadding.left(), y - skipy - st::mediaPadding.top() - st::mediaThumbSize - st::webPagePhotoSkip, tw); - state = inDate ? HistoryInDateCursorState : (inText ? HistoryInTextCursorState : HistoryDefaultCursorState); - } -} - -HistoryMedia *HistoryVideo::clone() const { - return new HistoryVideo(*this); -} - -void HistoryVideo::draw(Painter &p, const HistoryItem *parent, bool selected, int32 width) const { - int32 height = _height; - if (width < 0) { - width = w; - } else if (!_caption.isEmpty()) { - height = countHeight(parent, width); - } - if (width < 1) return; - - const HistoryReply *reply = toHistoryReply(parent); - const HistoryForwarded *fwd = reply ? 0 : toHistoryForwarded(parent); - int skipy = 0, replyFrom = 0, fwdFrom = 0; - if (reply) { - skipy = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - } else if (fwd) { - skipy = st::msgServiceNameFont->height; - } - if (parent->displayFromName()) { - replyFrom = st::msgPadding.top() + st::msgNameFont->height; - fwdFrom = st::msgPadding.top() + st::msgNameFont->height; - skipy += replyFrom; - } else if (fwd) { - fwdFrom = st::msgPadding.top(); - skipy += fwdFrom; - } - - data->thumb->checkload(); - - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel, hovered, pressed; - if (width >= _maxw) { - width = _maxw; - } - - if (!outbg) { // draw Download / Save As button - hovered = ((data->loader ? _cancell : _savel) == textlnkOver()); - pressed = hovered && ((data->loader ? _cancell : _savel) == textlnkDown()); - if (hovered && !pressed && textlnkDown()) hovered = false; - - int32 h = height; - if (!_caption.isEmpty()) { - h -= st::webPagePhotoSkip + _caption.countHeight(width - _buttonWidth - st::mediaSaveDelta - st::mediaPadding.left() - st::mediaPadding.right()); - } - - int32 btnw = _buttonWidth, btnh = st::mediaSaveButton.height, btnx = width - _buttonWidth, btny = skipy + (h - skipy - btnh) / 2; - style::color bg(selected ? st::msgInSelectBg : (hovered ? st::mediaSaveButton.overBgColor : st::mediaSaveButton.bgColor)); - style::color sh(selected ? st::msgInSelectShadow : st::msgInShadow); - RoundCorners cors(selected ? MessageInSelectedCorners : (hovered ? ButtonHoverCorners : MessageInCorners)); - App::roundRect(p, btnx, btny, btnw, btnh, bg, cors, &sh); - - p.setPen((hovered ? st::mediaSaveButton.overColor : st::mediaSaveButton.color)->p); - p.setFont(st::mediaSaveButton.font->f); - QString btnText(lang(data->loader ? lng_media_cancel : (data->already().isEmpty() ? lng_media_download : lng_media_open_with))); - int32 btnTextWidth = data->loader ? _cancelWidth : (data->already().isEmpty() ? _downloadWidth : _openWithWidth); - p.drawText(btnx + (btnw - btnTextWidth) / 2, btny + (pressed ? st::mediaSaveButton.downTextTop : st::mediaSaveButton.textTop) + st::mediaSaveButton.font->ascent, btnText); - width -= btnw + st::mediaSaveDelta; - } - - style::color bg(selected ? (outbg ? st::msgOutSelectBg : st::msgInSelectBg) : (outbg ? st::msgOutBg : st::msgInBg)); - style::color sh(selected ? (outbg ? st::msgOutSelectShadow : st::msgInSelectShadow) : (outbg ? st::msgOutShadow : st::msgInShadow)); - RoundCorners cors(selected ? (outbg ? MessageOutSelectedCorners : MessageInSelectedCorners) : (outbg ? MessageOutCorners : MessageInCorners)); - App::roundRect(p, 0, 0, width, height, bg, cors, &sh); - - if (parent->displayFromName()) { - p.setFont(st::msgNameFont->f); - if (fromChannel) { - p.setPen(selected ? st::msgInServiceSelColor : st::msgInServiceColor); - } else { - p.setPen(parent->from()->color); - } - parent->from()->nameText.drawElided(p, st::mediaPadding.left(), st::msgPadding.top(), width - st::mediaPadding.left() - st::mediaPadding.right()); - } - if (reply) { - reply->drawReplyTo(p, st::msgReplyPadding.left(), replyFrom, width - st::msgReplyPadding.left() - st::msgReplyPadding.right(), selected); - } else if (fwd) { - fwd->drawForwardedFrom(p, st::mediaPadding.left(), fwdFrom, width - st::mediaPadding.left() - st::mediaPadding.right(), selected); - } - - if (_thumbw) { - p.drawPixmap(QPoint(st::mediaPadding.left(), skipy + st::mediaPadding.top()), data->thumb->pixSingle(_thumbw, 0, st::mediaThumbSize, st::mediaThumbSize)); +void HistoryVideo::updateStatusText(const HistoryItem *parent) const { + bool showPause = false; + int32 statusSize = 0, realDuration = 0; + if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { + statusSize = FileStatusSizeFailed; + } else if (_data->status == FileUploading) { + statusSize = _data->uploadOffset; + } else if (_data->loading()) { + statusSize = _data->loadOffset(); + } else if (!_data->already().isEmpty()) { + statusSize = FileStatusSizeLoaded; } else { - p.drawPixmap(QPoint(st::mediaPadding.left(), skipy + st::mediaPadding.top()), App::sprite(), (outbg ? st::mediaDocOutImg : st::mediaDocInImg)); + statusSize = FileStatusSizeReady; } - if (selected) { - App::roundRect(p, st::mediaPadding.left(), skipy + st::mediaPadding.top(), st::mediaThumbSize, st::mediaThumbSize, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); + if (statusSize != _statusSize) { + setStatusSize(statusSize); } - - int32 tleft = st::mediaPadding.left() + st::mediaThumbSize + st::mediaPadding.right(); - int32 twidth = width - tleft - st::mediaPadding.right(); - int32 secondwidth = width - tleft - st::msgPadding.right() - parent->skipBlockWidth(); - - p.setFont(st::mediaFont->f); - p.setPen(st::black->c); - p.drawText(tleft, skipy + st::mediaPadding.top() + st::mediaNameTop + st::mediaFont->ascent, lang(lng_media_video)); - - QString statusText; - - style::color status(selected ? (outbg ? st::mediaOutSelectColor : st::mediaInSelectColor) : (outbg ? st::mediaOutColor : st::mediaInColor)); - p.setPen(status->p); - - if (data->loader) { - int32 offset = data->loader->currentOffset(); - if (_dldTextCache.isEmpty() || _dldDone != offset) { - _dldDone = offset; - _dldTextCache = formatDownloadText(_dldDone, data->size); - } - statusText = _dldTextCache; - } else { - if (data->status == FileFailed) { - statusText = lang(lng_attach_failed); - } else if (data->status == FileUploading) { - if (_uplTextCache.isEmpty() || _uplDone != data->uploadOffset) { - _uplDone = data->uploadOffset; - _uplTextCache = formatDownloadText(_uplDone, data->size); - } - statusText = _uplTextCache; - } else { - statusText = _size; - } - } - int32 texty = skipy + st::mediaPadding.top() + st::mediaThumbSize - st::mediaDetailsShift - st::mediaFont->height; - p.drawText(tleft, texty + st::mediaFont->ascent, statusText); - if (parent->isMediaUnread()) { - int32 w = st::mediaFont->width(statusText); - if (w + st::mediaUnreadSkip + st::mediaUnreadSize <= twidth) { - p.setRenderHint(QPainter::HighQualityAntialiasing, true); - p.setPen(Qt::NoPen); - p.setBrush((outbg ? (selected ? st::mediaOutUnreadSelectColor : st::mediaOutUnreadColor) : (selected ? st::mediaInUnreadSelectColor : st::mediaInUnreadColor))->b); - p.drawEllipse(QRect(tleft + w + st::mediaUnreadSkip, texty + ((st::mediaFont->height - st::mediaUnreadSize) / 2), st::mediaUnreadSize, st::mediaUnreadSize)); - p.setRenderHint(QPainter::HighQualityAntialiasing, false); - } - } - p.setFont(st::msgDateFont->f); - - if (!_caption.isEmpty()) { - p.setPen(st::black->p); - _caption.draw(p, st::mediaPadding.left(), skipy + st::mediaPadding.top() + st::mediaThumbSize + st::webPagePhotoSkip, width - st::mediaPadding.left() - st::mediaPadding.right()); - } - - int32 fullRight = width, fullBottom = height; - parent->drawInfo(p, fullRight, fullBottom, selected, InfoDisplayDefault); } -int32 HistoryVideo::resize(int32 width, const HistoryItem *parent) { - w = qMin(width, _maxw); - if (_caption.isEmpty()) return _height; +void HistoryVideo::regItem(HistoryItem *item) { + App::regVideoItem(_data, item); +} - _height = st::mediaPadding.top() + st::mediaThumbSize + st::mediaPadding.bottom(); - if (parent->displayFromName()) { - _height += st::msgPadding.top() + st::msgNameFont->height; - } - if (const HistoryReply *reply = toHistoryReply(parent)) { - _height += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - } else if (const HistoryForwarded *fwd = toHistoryForwarded(parent)) { - if (!parent->displayFromName()) { - _height += st::msgPadding.top(); - } - _height += st::msgServiceNameFont->height; - } - if (!_caption.isEmpty()) { - int32 textw = w - st::mediaPadding.left() - st::mediaPadding.right(); - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; - if (!outbg) { // substract Download / Save As button - textw -= st::mediaSaveDelta + _buttonWidth; - } - _height += st::webPagePhotoSkip + _caption.countHeight(textw); - } - return _height; +void HistoryVideo::unregItem(HistoryItem *item) { + App::unregVideoItem(_data, item); } ImagePtr HistoryVideo::replyPreview() { - if (data->replyPreview->isNull() && !data->thumb->isNull()) { - if (data->thumb->loaded()) { - int w = data->thumb->width(), h = data->thumb->height(); + if (_data->replyPreview->isNull() && !_data->thumb->isNull()) { + if (_data->thumb->loaded()) { + int w = _data->thumb->width(), h = _data->thumb->height(); if (w <= 0) w = 1; if (h <= 0) h = 1; - data->replyPreview = ImagePtr(w > h ? data->thumb->pix(w * st::msgReplyBarSize.height() / h, st::msgReplyBarSize.height()) : data->thumb->pix(st::msgReplyBarSize.height()), "PNG"); + _data->replyPreview = ImagePtr(w > h ? _data->thumb->pix(w * st::msgReplyBarSize.height() / h, st::msgReplyBarSize.height()) : _data->thumb->pix(st::msgReplyBarSize.height()), "PNG"); } else { - data->thumb->load(); + _data->thumb->load(); } } - return data->replyPreview; + return _data->replyPreview; } -HistoryAudio::HistoryAudio(const MTPDaudio &audio) : HistoryMedia() -, data(App::feedAudio(audio)) -, _openl(new AudioOpenLink(data)) -, _savel(new AudioSaveLink(data)) -, _cancell(new AudioCancelLink(data)) -, _dldDone(0) -, _uplDone(0) -{ - _size = formatDurationAndSizeText(data->duration, data->size); +HistoryAudio::HistoryAudio(const MTPDaudio &audio) : HistoryFileMedia() +, _data(App::feedAudio(audio)) { + setLinks(new AudioOpenLink(_data), new AudioOpenLink(_data), new AudioCancelLink(_data)); - if (!_openWithWidth) { - _downloadWidth = st::mediaSaveButton.font->width(lang(lng_media_download)); - _openWithWidth = st::mediaSaveButton.font->width(lang(lng_media_open_with)); - _cancelWidth = st::mediaSaveButton.font->width(lang(lng_media_cancel)); - _buttonWidth = (st::mediaSaveButton.width > 0) ? st::mediaSaveButton.width : ((_downloadWidth > _openWithWidth ? (_downloadWidth > _cancelWidth ? _downloadWidth : _cancelWidth) : _openWithWidth) - st::mediaSaveButton.width); - } + setStatusSize(FileStatusSizeReady); +} + +HistoryAudio::HistoryAudio(const HistoryAudio &other) : HistoryFileMedia() +, _data(other._data) { + setLinks(new AudioOpenLink(_data), new AudioOpenLink(_data), new AudioCancelLink(_data)); + + setStatusSize(other._statusSize); } void HistoryAudio::initDimensions(const HistoryItem *parent) { - _maxw = st::mediaMaxWidth; - int32 tleft = st::mediaPadding.left() + st::mediaThumbSize + st::mediaPadding.right(); - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; - if (!outbg) { // add Download / Save As button - _maxw += st::mediaSaveDelta + _buttonWidth; - } + _maxw = st::msgFileMinWidth; - _minh = st::mediaPadding.top() + st::mediaThumbSize + st::mediaPadding.bottom(); - if (parent->displayFromName()) { - _minh += st::msgPadding.top() + st::msgNameFont->height; - } - if (const HistoryReply *reply = toHistoryReply(parent)) { - _minh += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - } else if (const HistoryForwarded *fwd = toHistoryForwarded(parent)) { - if (!parent->displayFromName()) { - _minh += st::msgPadding.top(); - } - _minh += st::msgServiceNameFont->height; - } - _height = _minh; + int32 tleft = 0, tright = 0; + + tleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); + tright = st::msgFileThumbPadding.left(); + _maxw = qMax(_maxw, tleft + audioMaxStatusWidth(_data) + int(st::mediaUnreadSkip + st::mediaUnreadSize) + parent->skipBlockWidth() + st::msgPadding.right()); + + _maxw = qMax(tleft + st::semiboldFont->width(lang(lng_media_audio)) + tright, _maxw); + _maxw = qMin(_maxw, int(st::msgMaxWidth)); + + _height = _minh = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom(); } -void HistoryAudio::draw(Painter &p, const HistoryItem *parent, bool selected, int32 width) const { - if (width < 0) width = w; - if (width < 1) return; +void HistoryAudio::draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const { + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; - const HistoryReply *reply = toHistoryReply(parent); - const HistoryForwarded *fwd = reply ? 0 : toHistoryForwarded(parent); - int skipy = 0, replyFrom = 0, fwdFrom = 0; - if (reply) { - skipy = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - } else if (fwd) { - skipy = st::msgServiceNameFont->height; - } - if (parent->displayFromName()) { - replyFrom = st::msgPadding.top() + st::msgNameFont->height; - fwdFrom = st::msgPadding.top() + st::msgNameFont->height; - skipy += replyFrom; - } else if (fwd) { - fwdFrom = st::msgPadding.top(); - skipy += fwdFrom; - } + _data->automaticLoad(parent); + bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel, hovered, pressed; - bool already = !data->already().isEmpty(), hasdata = !data->data.isEmpty(); - if (width >= _maxw) { - width = _maxw; - } + bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; - if (!data->loader && data->status != FileFailed && !already && !hasdata && data->size < AudioVoiceMsgInMemory) { - data->save(QString()); - } - - if (!outbg) { // draw Download / Save As button - hovered = ((data->loader ? _cancell : _savel) == textlnkOver()); - pressed = hovered && ((data->loader ? _cancell : _savel) == textlnkDown()); - if (hovered && !pressed && textlnkDown()) hovered = false; - - int32 btnw = _buttonWidth, btnh = st::mediaSaveButton.height, btnx = width - _buttonWidth, btny = skipy + (_height - skipy - btnh) / 2; - style::color bg(selected ? st::msgInSelectBg : (hovered ? st::mediaSaveButton.overBgColor : st::mediaSaveButton.bgColor)); - style::color sh(selected ? st::msgInSelectShadow : st::msgInShadow); - RoundCorners cors(selected ? MessageInSelectedCorners : (hovered ? ButtonHoverCorners : MessageInCorners)); - App::roundRect(p, btnx, btny, btnw, btnh, bg, cors, &sh); - - p.setPen((hovered ? st::mediaSaveButton.overColor : st::mediaSaveButton.color)->p); - p.setFont(st::mediaSaveButton.font->f); - QString btnText(lang(data->loader ? lng_media_cancel : (already ? lng_media_open_with : lng_media_download))); - int32 btnTextWidth = data->loader ? _cancelWidth : (already ? _openWithWidth : _downloadWidth); - p.drawText(btnx + (btnw - btnTextWidth) / 2, btny + (pressed ? st::mediaSaveButton.downTextTop : st::mediaSaveButton.textTop) + st::mediaSaveButton.font->ascent, btnText); - width -= btnw + st::mediaSaveDelta; - } - - style::color bg(selected ? (outbg ? st::msgOutSelectBg : st::msgInSelectBg) : (outbg ? st::msgOutBg : st::msgInBg)); - style::color sh(selected ? (outbg ? st::msgOutSelectShadow : st::msgInSelectShadow) : (outbg ? st::msgOutShadow : st::msgInShadow)); - RoundCorners cors(selected ? (outbg ? MessageOutSelectedCorners : MessageInSelectedCorners) : (outbg ? MessageOutCorners : MessageInCorners)); - App::roundRect(p, 0, 0, width, _height, bg, cors, &sh); - - if (parent->displayFromName()) { - p.setFont(st::msgNameFont->f); - if (fromChannel) { - p.setPen(selected ? st::msgInServiceSelColor : st::msgInServiceColor); - } else { - p.setPen(parent->from()->color); + if (displayLoading) { + ensureAnimation(parent); + if (!_animation->radial.animating()) { + _animation->radial.start(_data->progress()); } - parent->from()->nameText.drawElided(p, st::mediaPadding.left(), st::msgPadding.top(), width - st::mediaPadding.left() - st::mediaPadding.right()); - } - if (reply) { - reply->drawReplyTo(p, st::msgReplyPadding.left(), replyFrom, width - st::msgReplyPadding.left() - st::msgReplyPadding.right(), selected); - } else if (fwd) { - fwd->drawForwardedFrom(p, st::mediaPadding.left(), fwdFrom, width - st::mediaPadding.left() - st::mediaPadding.right(), selected); } + bool showPause = updateStatusText(parent); + bool radial = isRadialAnimation(ms); - AudioMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - int64 playingPosition = 0, playingDuration = 0; - int32 playingFrequency = 0; - if (audioPlayer()) { - audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); - } + int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0; - QRect img; - QString statusText; - if (data->status == FileFailed) { - statusText = lang(lng_attach_failed); - img = outbg ? st::mediaAudioOutImg : st::mediaAudioInImg; - } else if (data->status == FileUploading) { - if (_uplTextCache.isEmpty() || _uplDone != data->uploadOffset) { - _uplDone = data->uploadOffset; - _uplTextCache = formatDownloadText(_uplDone, data->size); - } - statusText = _uplTextCache; - img = outbg ? st::mediaAudioOutImg : st::mediaAudioInImg; - } else if (already || hasdata) { - bool showPause = false; - if (playing.msgId == parent->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { - statusText = formatDurationText(playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)) + qsl(" / ") + formatDurationText(playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)); - showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); - } else { - statusText = formatDurationText(data->duration); - } - img = outbg ? (showPause ? st::mediaPauseOutImg : st::mediaPlayOutImg) : (showPause ? st::mediaPauseInImg : st::mediaPlayInImg); - } else { - if (data->loader) { - int32 offset = data->loader->currentOffset(); - if (_dldTextCache.isEmpty() || _dldDone != offset) { - _dldDone = offset; - _dldTextCache = formatDownloadText(_dldDone, data->size); - } - statusText = _dldTextCache; - } else { - statusText = _size; - } - img = outbg ? st::mediaAudioOutImg : st::mediaAudioInImg; - } + nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); + nametop = st::msgFileNameTop; + nameright = st::msgFilePadding.left(); + statustop = st::msgFileStatusTop; - p.drawPixmap(QPoint(st::mediaPadding.left(), skipy + st::mediaPadding.top()), App::sprite(), img); + QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top(), st::msgFileSize, st::msgFileSize, _width)); + p.setPen(Qt::NoPen); if (selected) { - App::roundRect(p, st::mediaPadding.left(), skipy + st::mediaPadding.top(), st::mediaThumbSize, st::mediaThumbSize, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); + p.setBrush(outbg ? st::msgFileOutBgSelected : st::msgFileInBgSelected); + } else if (isThumbAnimation(ms)) { + float64 over = _animation->a_thumbOver.current(); + p.setBrush(style::interpolate(outbg ? st::msgFileOutBg : st::msgFileInBg, outbg ? st::msgFileOutBgOver : st::msgFileInBgOver, over)); + } else { + bool over = textlnkDrawOver(_data->loading() ? _cancell : _savel); + p.setBrush(outbg ? (over ? st::msgFileOutBgOver : st::msgFileOutBg) : (over ? st::msgFileInBgOver : st::msgFileInBg)); } - int32 tleft = st::mediaPadding.left() + st::mediaThumbSize + st::mediaPadding.right(); - int32 twidth = width - tleft - st::mediaPadding.right(); - int32 secondwidth = width - tleft - st::msgPadding.right() - parent->skipBlockWidth(); + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.drawEllipse(inner); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); - p.setFont(st::mediaFont->f); - p.setPen(st::black->c); - p.drawText(tleft, skipy + st::mediaPadding.top() + st::mediaNameTop + st::mediaFont->ascent, lang(lng_media_audio)); + if (radial) { + QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); + style::color bg(outbg ? (selected ? st::msgOutBgSelected : st::msgOutBg) : (selected ? st::msgInBgSelected : st::msgInBg)); + _animation->radial.draw(p, rinner, st::msgFileRadialLine, bg); + } + + style::sprite icon; + if (showPause) { + icon = outbg ? (selected ? st::msgFileOutPauseSelected : st::msgFileOutPause) : (selected ? st::msgFileInPauseSelected : st::msgFileInPause); + } else if (radial || _data->loading()) { + icon = outbg ? (selected ? st::msgFileOutCancelSelected : st::msgFileOutCancel) : (selected ? st::msgFileInCancelSelected : st::msgFileInCancel); + } else if (loaded) { + icon = outbg ? (selected ? st::msgFileOutPlaySelected : st::msgFileOutPlay) : (selected ? st::msgFileInPlaySelected : st::msgFileInPlay); + } else { + icon = outbg ? (selected ? st::msgFileOutDownloadSelected : st::msgFileOutDownload) : (selected ? st::msgFileInDownloadSelected : st::msgFileInDownload); + } + p.drawSpriteCenter(inner, icon); + + int32 namewidth = _width - nameleft - nameright; + + p.setFont(st::semiboldFont); + p.setPen(st::black); + p.drawTextLeft(nameleft, nametop, _width, lang(lng_media_audio)); + + style::color status(outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg)); + p.setFont(st::normalFont); + p.setPen(status); + p.drawTextLeft(nameleft, statustop, _width, _statusText); - style::color status(selected ? (outbg ? st::mediaOutSelectColor : st::mediaInSelectColor) : (outbg ? st::mediaOutColor : st::mediaInColor)); - p.setPen(status->p); - int32 texty = skipy + st::mediaPadding.top() + st::mediaThumbSize - st::mediaDetailsShift - st::mediaFont->height; - p.drawText(tleft, texty + st::mediaFont->ascent, statusText); if (parent->isMediaUnread()) { - int32 w = st::mediaFont->width(statusText); - if (w + st::mediaUnreadSkip + st::mediaUnreadSize <= twidth) { - p.setRenderHint(QPainter::HighQualityAntialiasing, true); + int32 w = st::normalFont->width(_statusText); + if (w + st::mediaUnreadSkip + st::mediaUnreadSize <= namewidth) { p.setPen(Qt::NoPen); - p.setBrush((outbg ? (selected ? st::mediaOutUnreadSelectColor : st::mediaOutUnreadColor) : (selected ? st::mediaInUnreadSelectColor : st::mediaInUnreadColor))->b); - p.drawEllipse(QRect(tleft + w + st::mediaUnreadSkip, texty + ((st::mediaFont->height - st::mediaUnreadSize) / 2), st::mediaUnreadSize, st::mediaUnreadSize)); + p.setBrush(outbg ? (selected ? st::msgFileOutBgSelected : st::msgFileOutBg) : (selected ? st::msgFileInBgSelected : st::msgFileInBg)); + + p.setRenderHint(QPainter::HighQualityAntialiasing, true); + p.drawEllipse(rtlrect(nameleft + w + st::mediaUnreadSkip, statustop + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, _width)); p.setRenderHint(QPainter::HighQualityAntialiasing, false); } } - p.setFont(st::msgDateFont->f); - - style::color date(selected ? (outbg ? st::msgOutSelectDateColor : st::msgInSelectDateColor) : (outbg ? st::msgOutDateColor : st::msgInDateColor)); - p.setPen(date->p); - - int32 fullRight = width, fullBottom = _height; - parent->drawInfo(p, fullRight, fullBottom, selected, InfoDisplayDefault); } -void HistoryAudio::regItem(HistoryItem *item) { - App::regAudioItem(data, item); -} +void HistoryAudio::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const { + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; -void HistoryAudio::unregItem(HistoryItem *item) { - App::unregAudioItem(data, item); -} + bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; + bool loaded = _data->loaded(); -void HistoryAudio::updateFrom(const MTPMessageMedia &media) { - if (media.type() == mtpc_messageMediaAudio) { - App::feedAudio(media.c_messageMediaAudio().vaudio, data); - if (!data->data.isEmpty()) { - Local::writeAudio(mediaKey(mtpToLocationType(mtpc_inputAudioFileLocation), data->dc, data->id), data->data); - } + bool showPause = updateStatusText(parent); + + int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0; + + QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top(), st::msgFileSize, st::msgFileSize, _width)); + if ((_data->loading() || _data->status == FileUploading || !loaded) && inner.contains(x, y)) { + lnk = (_data->loading() || _data->status == FileUploading) ? _cancell : _savel; + return; + } + + if (x >= 0 && y >= 0 && x < _width && y < _height && _data->access && !_data->loading()) { + lnk = _openl; + return; } } @@ -4076,638 +3923,873 @@ const QString HistoryAudio::inHistoryText() const { return qsl("[ ") + lang(lng_in_dlg_audio) + qsl(" ]"); } -bool HistoryAudio::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const { - if (width < 0) width = w; - if (width >= _maxw) { - width = _maxw; - } - return (x >= 0 && y >= 0 && x < width && y < _height); +void HistoryAudio::regItem(HistoryItem *item) { + App::regAudioItem(_data, item); } -void HistoryAudio::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width) const { - if (width < 0) width = w; - if (width < 1) return; +void HistoryAudio::unregItem(HistoryItem *item) { + App::unregAudioItem(_data, item); +} - const HistoryReply *reply = toHistoryReply(parent); - const HistoryForwarded *fwd = reply ? 0 : toHistoryForwarded(parent); - int skipy = 0, replyFrom = 0, fwdFrom = 0; - if (reply) { - skipy = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - } else if (fwd) { - skipy = st::msgServiceNameFont->height; - } - if (parent->displayFromName()) { - replyFrom = st::msgPadding.top() + st::msgNameFont->height; - fwdFrom = st::msgPadding.top() + st::msgNameFont->height; - skipy += replyFrom; - } else if (fwd) { - fwdFrom = st::msgPadding.top(); - skipy += fwdFrom; - } - - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel, hovered, pressed; - if (width >= _maxw) { - width = _maxw; - } - - if (!outbg) { // draw Download / Save As button - int32 btnw = _buttonWidth, btnh = st::mediaSaveButton.height, btnx = width - _buttonWidth, btny = skipy + (_height - skipy - btnh) / 2; - if (x >= btnx && y >= btny && x < btnx + btnw && y < btny + btnh) { - lnk = data->loader ? _cancell : _savel; - return; +void HistoryAudio::updateFrom(const MTPMessageMedia &media, HistoryItem *parent) { + if (media.type() == mtpc_messageMediaAudio) { + App::feedAudio(media.c_messageMediaAudio().vaudio, _data); + if (!_data->data().isEmpty()) { + Local::writeAudio(mediaKey(AudioFileLocation, _data->dc, _data->id), _data->data()); } - width -= btnw + st::mediaSaveDelta; - } - - if (parent->displayFromName()) { - if (x >= st::mediaPadding.left() && y >= st::msgPadding.top() && x < width - st::mediaPadding.left() - st::mediaPadding.right() && x < st::mediaPadding.left() + parent->from()->nameText.maxWidth() && y < replyFrom) { - lnk = parent->from()->lnk; - return; - } - } - if (reply) { - if (x >= 0 && y >= replyFrom + st::msgReplyPadding.top() && x < width && y < skipy - st::msgReplyPadding.bottom()) { - lnk = reply->replyToLink(); - return; - } - } else if (fwd) { - if (y >= fwdFrom && y < skipy) { - return fwd->getForwardedState(lnk, state, x - st::mediaPadding.left(), width - st::mediaPadding.left() - st::mediaPadding.right()); - } - } - - if (x >= 0 && y >= skipy && x < width && y < _height && !data->loader && data->access) { - lnk = _openl; - - bool inDate = parent->pointInTime(width, _height, x, y, InfoDisplayDefault); - if (inDate) { - state = HistoryInDateCursorState; - } - - return; } } -HistoryMedia *HistoryAudio::clone() const { - return new HistoryAudio(*this); +void HistoryAudio::setStatusSize(int32 newSize, qint64 realDuration) const { + HistoryFileMedia::setStatusSize(newSize, _data->size, _data->duration, realDuration); } -namespace { - QString documentName(DocumentData *document) { - SongData *song = document->song(); - if (!song || (song->title.isEmpty() && song->performer.isEmpty())) return document->name; - if (song->performer.isEmpty()) return song->title; - return song->performer + QString::fromUtf8(" \xe2\x80\x93 ") + (song->title.isEmpty() ? qsl("Unknown Track") : song->title); - } -} +bool HistoryAudio::updateStatusText(const HistoryItem *parent) const { + bool showPause = false; + int32 statusSize = 0, realDuration = 0; + if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { + statusSize = FileStatusSizeFailed; + } else if (_data->status == FileUploading) { + statusSize = _data->uploadOffset; + } else if (_data->loading()) { + statusSize = _data->loadOffset(); + } else if (_data->loaded()) { + AudioMsgId playing; + AudioPlayerState playingState = AudioPlayerStopped; + int64 playingPosition = 0, playingDuration = 0; + int32 playingFrequency = 0; + if (audioPlayer()) { + audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); + } -HistoryDocument::HistoryDocument(DocumentData *document) : HistoryMedia() -, data(document) -, _openl(new DocumentOpenLink(data)) -, _savel(new DocumentSaveLink(data)) -, _cancell(new DocumentCancelLink(data)) -, _name(documentName(data)) -, _dldDone(0) -, _uplDone(0) -{ - _namew = st::mediaFont->width(_name.isEmpty() ? qsl("Document") : _name); - _size = document->song() ? formatDurationAndSizeText(document->song()->duration, data->size) : formatSizeText(data->size); - - _height = _minh = st::mediaPadding.top() + st::mediaThumbSize + st::mediaPadding.bottom(); - - if (!_openWithWidth) { - _downloadWidth = st::mediaSaveButton.font->width(lang(lng_media_download)); - _openWithWidth = st::mediaSaveButton.font->width(lang(lng_media_open_with)); - _cancelWidth = st::mediaSaveButton.font->width(lang(lng_media_cancel)); - _buttonWidth = (st::mediaSaveButton.width > 0) ? st::mediaSaveButton.width : ((_downloadWidth > _openWithWidth ? (_downloadWidth > _cancelWidth ? _downloadWidth : _cancelWidth) : _openWithWidth) - st::mediaSaveButton.width); - } - - data->thumb->load(); - - int32 tw = data->thumb->width(), th = data->thumb->height(); - if (data->thumb->isNull() || !tw || !th) { - _thumbw = 0; - } else if (tw > th) { - _thumbw = (tw * st::mediaThumbSize) / th; + if (playing.msgId == parent->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { + statusSize = -1 - (playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)); + realDuration = playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency); + showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); + } else { + statusSize = FileStatusSizeLoaded; + } } else { - _thumbw = st::mediaThumbSize; + statusSize = FileStatusSizeReady; } + if (statusSize != _statusSize) { + setStatusSize(statusSize, realDuration); + } + return showPause; +} + +HistoryDocument::HistoryDocument(DocumentData *document, const QString &caption, const HistoryItem *parent) : HistoryFileMedia() +, _data(document) +, _linksavel(new DocumentSaveLink(_data)) +, _linkcancell(new DocumentCancelLink(_data)) +, _name(documentName(_data)) +, _namew(st::semiboldFont->width(_name)) +, _caption(st::msgFileMinWidth - st::msgPadding.left() - st::msgPadding.right()) { + setLinks(new DocumentOpenLink(_data), new DocumentSaveLink(_data), new DocumentCancelLink(_data)); + + setStatusSize(FileStatusSizeReady); + + if (!caption.isEmpty()) { + _caption.setText(st::msgFont, caption + parent->skipBlock(), itemTextNoMonoOptions(parent)); + } +} + +HistoryDocument::HistoryDocument(const HistoryDocument &other) : HistoryFileMedia() +, _data(other._data) +, _linksavel(new DocumentSaveLink(_data)) +, _linkcancell(new DocumentCancelLink(_data)) +, _name(other._name) +, _namew(other._namew) +, _thumbw(other._thumbw) { + setLinks(new DocumentOpenLink(_data), new DocumentSaveLink(_data), new DocumentCancelLink(_data)); + + setStatusSize(other._statusSize); } void HistoryDocument::initDimensions(const HistoryItem *parent) { - if (parent == animated.msg) { - _maxw = animated.w / cIntRetinaFactor(); - _minh = animated.h / cIntRetinaFactor(); + if (_caption.hasSkipBlock()) { + _caption.setSkipBlock(parent->skipBlockWidth(), parent->skipBlockHeight()); + } + + if (withThumb()) { + _data->thumb->load(); + int32 tw = _data->thumb->width(), th = _data->thumb->height(); + if (tw > th) { + _thumbw = (tw * st::msgFileThumbSize) / th; + } else { + _thumbw = st::msgFileThumbSize; + } } else { - _maxw = st::mediaMaxWidth; - int32 tleft = st::mediaPadding.left() + st::mediaThumbSize + st::mediaPadding.right(); - if (_namew + tleft + st::mediaPadding.right() > _maxw) { - _maxw = _namew + tleft + st::mediaPadding.right(); - } - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; - if (!outbg) { // add Download / Save As button - _maxw += st::mediaSaveDelta + _buttonWidth; - } - _minh = st::mediaPadding.top() + st::mediaThumbSize + st::mediaPadding.bottom(); - - if (parent->displayFromName()) { - _minh += st::msgPadding.top() + st::msgNameFont->height; - } - if (const HistoryReply *reply = toHistoryReply(parent)) { - _minh += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - } else if (const HistoryForwarded *fwd = toHistoryForwarded(parent)) { - if (!data->song()) { - if (!parent->displayFromName()) { - _minh += st::msgPadding.top(); - } - _minh += st::msgServiceNameFont->height; - } - } - } - _height = _minh; -} - -void HistoryDocument::draw(Painter &p, const HistoryItem *parent, bool selected, int32 width) const { - if (width < 0) width = w; - if (width < 1) return; - - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel, hovered, pressed; - bool already = !data->already().isEmpty(), hasdata = !data->data.isEmpty(); - if (parent == animated.msg) { - int32 pw = animated.w / cIntRetinaFactor(), ph = animated.h / cIntRetinaFactor(); - if (width < pw) { - pw = width; - ph = (pw == w) ? _height : (pw * animated.h / animated.w); - if (ph < 1) ph = 1; - } - - App::roundShadow(p, 0, 0, pw, ph, selected ? st::msgInSelectShadow : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); - - p.drawPixmap(0, 0, animated.current(pw * cIntRetinaFactor(), ph * cIntRetinaFactor(), true)); - if (selected) { - App::roundRect(p, 0, 0, pw, ph, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); - } - return; + _thumbw = 0; } - const HistoryReply *reply = toHistoryReply(parent); - const HistoryForwarded *fwd = (reply || data->song()) ? 0 : toHistoryForwarded(parent); - int skipy = 0, replyFrom = 0, fwdFrom = 0; - if (reply) { - skipy = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - } else if (fwd) { - skipy = st::msgServiceNameFont->height; - } - if (parent->displayFromName()) { - replyFrom = st::msgPadding.top() + st::msgNameFont->height; - fwdFrom = st::msgPadding.top() + st::msgNameFont->height; - skipy += replyFrom; - } else if (fwd) { - fwdFrom = st::msgPadding.top(); - skipy += fwdFrom; - } + _maxw = st::msgFileMinWidth; - if (width >= _maxw) { - width = _maxw; - } - - if (!outbg) { // draw Download / Save As button - hovered = ((data->loader ? _cancell : _savel) == textlnkOver()); - pressed = hovered && ((data->loader ? _cancell : _savel) == textlnkDown()); - if (hovered && !pressed && textlnkDown()) hovered = false; - - int32 btnw = _buttonWidth, btnh = st::mediaSaveButton.height, btnx = width - _buttonWidth, btny = skipy + (_height - skipy - btnh) / 2; - style::color bg(selected ? st::msgInSelectBg : (hovered ? st::mediaSaveButton.overBgColor : st::mediaSaveButton.bgColor)); - style::color sh(selected ? st::msgInSelectShadow : st::msgInShadow); - RoundCorners cors(selected ? MessageInSelectedCorners : (hovered ? ButtonHoverCorners : MessageInCorners)); - App::roundRect(p, btnx, btny, btnw, btnh, bg, cors, &sh); - - p.setPen((hovered ? st::mediaSaveButton.overColor : st::mediaSaveButton.color)->p); - p.setFont(st::mediaSaveButton.font->f); - QString btnText(lang(data->loader ? lng_media_cancel : (already ? lng_media_open_with : lng_media_download))); - int32 btnTextWidth = data->loader ? _cancelWidth : (already ? _openWithWidth : _downloadWidth); - p.drawText(btnx + (btnw - btnTextWidth) / 2, btny + (pressed ? st::mediaSaveButton.downTextTop : st::mediaSaveButton.textTop) + st::mediaSaveButton.font->ascent, btnText); - width -= btnw + st::mediaSaveDelta; - } - - style::color bg(selected ? (outbg ? st::msgOutSelectBg : st::msgInSelectBg) : (outbg ? st::msgOutBg : st::msgInBg)); - style::color sh(selected ? (outbg ? st::msgOutSelectShadow : st::msgInSelectShadow) : (outbg ? st::msgOutShadow : st::msgInShadow)); - RoundCorners cors(selected ? (outbg ? MessageOutSelectedCorners : MessageInSelectedCorners) : (outbg ? MessageOutCorners : MessageInCorners)); - App::roundRect(p, 0, 0, width, _height, bg, cors, &sh); - - if (parent->displayFromName()) { - p.setFont(st::msgNameFont->f); - if (fromChannel) { - p.setPen(selected ? st::msgInServiceSelColor : st::msgInServiceColor); - } else { - p.setPen(parent->from()->color); - } - parent->from()->nameText.drawElided(p, st::mediaPadding.left(), st::msgPadding.top(), width - st::mediaPadding.left() - st::mediaPadding.right()); - } - if (reply) { - reply->drawReplyTo(p, st::msgReplyPadding.left(), replyFrom, width - st::msgReplyPadding.left() - st::msgReplyPadding.right(), selected); - } else if (fwd) { - fwd->drawForwardedFrom(p, st::mediaPadding.left(), fwdFrom, width - st::mediaPadding.left() - st::mediaPadding.right(), selected); - } - - QString statusText; - if (data->song()) { - SongMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - int64 playingPosition = 0, playingDuration = 0; - int32 playingFrequency = 0; - if (audioPlayer()) { - audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); - } - - QRect img; - if (data->status == FileFailed) { - statusText = lang(lng_attach_failed); - img = outbg ? st::mediaMusicOutImg : st::mediaMusicInImg; - } else if (data->status == FileUploading) { - if (_uplTextCache.isEmpty() || _uplDone != data->uploadOffset) { - _uplDone = data->uploadOffset; - _uplTextCache = formatDownloadText(_uplDone, data->size); - } - statusText = _uplTextCache; - img = outbg ? st::mediaMusicOutImg : st::mediaMusicInImg; - } else if (already || hasdata) { - bool showPause = false; - if (playing.msgId == parent->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { - statusText = formatDurationText(playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)) + qsl(" / ") + formatDurationText(playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)); - showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); - } else { - statusText = formatDurationText(data->song()->duration); - } - if (!showPause && playing.msgId == parent->fullId() && App::main() && App::main()->player()->seekingSong(playing)) showPause = true; - img = outbg ? (showPause ? st::mediaPauseOutImg : st::mediaPlayOutImg) : (showPause ? st::mediaPauseInImg : st::mediaPlayInImg); - } else { - if (data->loader) { - int32 offset = data->loader->currentOffset(); - if (_dldTextCache.isEmpty() || _dldDone != offset) { - _dldDone = offset; - _dldTextCache = formatDownloadText(_dldDone, data->size); - } - statusText = _dldTextCache; - } else { - statusText = _size; - } - img = outbg ? st::mediaMusicOutImg : st::mediaMusicInImg; - } - - p.drawPixmap(QPoint(st::mediaPadding.left(), skipy + st::mediaPadding.top()), App::sprite(), img); + int32 tleft = 0, tright = 0; + bool wthumb = withThumb(); + if (wthumb) { + tleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); + tright = st::msgFileThumbPadding.left(); + _maxw = qMax(_maxw, tleft + documentMaxStatusWidth(_data) + tright); } else { - if (data->status == FileFailed) { - statusText = lang(lng_attach_failed); - } else if (data->status == FileUploading) { - if (_uplTextCache.isEmpty() || _uplDone != data->uploadOffset) { - _uplDone = data->uploadOffset; - _uplTextCache = formatDownloadText(_uplDone, data->size); - } - statusText = _uplTextCache; - } else if (data->loader) { - int32 offset = data->loader->currentOffset(); - if (_dldTextCache.isEmpty() || _dldDone != offset) { - _dldDone = offset; - _dldTextCache = formatDownloadText(_dldDone, data->size); - } - statusText = _dldTextCache; - } else { - statusText = _size; - } - - if (_thumbw) { - data->thumb->checkload(); - p.drawPixmap(QPoint(st::mediaPadding.left(), skipy + st::mediaPadding.top()), data->thumb->pixSingle(_thumbw, 0, st::mediaThumbSize, st::mediaThumbSize)); - } else { - p.drawPixmap(QPoint(st::mediaPadding.left(), skipy + st::mediaPadding.top()), App::sprite(), (outbg ? st::mediaDocOutImg : st::mediaDocInImg)); - } - } - if (selected) { - App::roundRect(p, st::mediaPadding.left(), skipy + st::mediaPadding.top(), st::mediaThumbSize, st::mediaThumbSize, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); + tleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); + tright = st::msgFileThumbPadding.left(); + _maxw = qMax(_maxw, tleft + documentMaxStatusWidth(_data) + parent->skipBlockWidth() + st::msgPadding.right()); } - int32 tleft = st::mediaPadding.left() + st::mediaThumbSize + st::mediaPadding.right(); - int32 twidth = width - tleft - st::mediaPadding.right(); - int32 secondwidth = width - tleft - st::msgPadding.right() - parent->skipBlockWidth(); + _maxw = qMax(tleft + _namew + tright, _maxw); + _maxw = qMin(_maxw, int(st::msgMaxWidth)); - p.setFont(st::mediaFont->f); - p.setPen(st::black->c); - if (twidth < _namew) { - p.drawText(tleft, skipy + st::mediaPadding.top() + st::mediaNameTop + st::mediaFont->ascent, st::mediaFont->elided(_name, twidth)); + if (wthumb) { + _minh = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom(); } else { - p.drawText(tleft, skipy + st::mediaPadding.top() + st::mediaNameTop + st::mediaFont->ascent, _name); + _minh = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom(); } - style::color status(selected ? (outbg ? st::mediaOutSelectColor : st::mediaInSelectColor) : (outbg ? st::mediaOutColor : st::mediaInColor)); - p.setPen(status->p); - - p.drawText(tleft, skipy + st::mediaPadding.top() + st::mediaThumbSize - st::mediaDetailsShift - st::mediaFont->descent, statusText); - - p.setFont(st::msgDateFont->f); - - style::color date(selected ? (outbg ? st::msgOutSelectDateColor : st::msgInSelectDateColor) : (outbg ? st::msgOutDateColor : st::msgInDateColor)); - p.setPen(date->p); - - int32 fullRight = width, fullBottom = _height; - parent->drawInfo(p, fullRight, fullBottom, selected, InfoDisplayDefault); -} - -void HistoryDocument::drawInPlaylist(Painter &p, const HistoryItem *parent, bool selected, bool over, int32 width) const { - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; - bool already = !data->already().isEmpty(), hasdata = !data->data.isEmpty(); - int32 height = st::mediaPadding.top() + st::mediaThumbSize + st::mediaPadding.bottom(); - - style::color bg(selected ? st::msgInSelectBg : (over ? st::playlistHoverBg : st::msgInBg)); - p.fillRect(0, 0, width, height, bg->b); - - QString statusText; - if (data->song()) { - SongMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - int64 playingPosition = 0, playingDuration = 0; - int32 playingFrequency = 0; - if (audioPlayer()) { - audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); - } - - QRect img; - if (data->status == FileFailed) { - statusText = lang(lng_attach_failed); - img = st::mediaMusicInImg; - } else if (data->status == FileUploading) { - if (_uplTextCache.isEmpty() || _uplDone != data->uploadOffset) { - _uplDone = data->uploadOffset; - _uplTextCache = formatDownloadText(_uplDone, data->size); - } - statusText = _uplTextCache; - img = st::mediaMusicInImg; - } else if (already || hasdata) { - bool isPlaying = (playing.msgId == parent->fullId()); - bool showPause = false; - if (playing.msgId == parent->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { - statusText = formatDurationText(playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)) + qsl(" / ") + formatDurationText(playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)); - showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); - } else { - statusText = formatDurationText(data->song()->duration); - } - if (!showPause && playing.msgId == parent->fullId() && App::main() && App::main()->player()->seekingSong(playing)) showPause = true; - img = isPlaying ? (showPause ? st::mediaPauseOutImg : st::mediaPlayOutImg) : (showPause ? st::mediaPauseInImg : st::mediaPlayInImg); - } else { - if (data->loader) { - int32 offset = data->loader->currentOffset(); - if (_dldTextCache.isEmpty() || _dldDone != offset) { - _dldDone = offset; - _dldTextCache = formatDownloadText(_dldDone, data->size); - } - statusText = _dldTextCache; - } else { - statusText = _size; - } - img = st::mediaMusicInImg; - } - - p.drawPixmap(QPoint(st::mediaPadding.left(), st::mediaPadding.top()), App::sprite(), img); + if (_caption.isEmpty()) { + _height = _minh; } else { - if (data->status == FileFailed) { - statusText = lang(lng_attach_failed); - } else if (data->status == FileUploading) { - if (_uplTextCache.isEmpty() || _uplDone != data->uploadOffset) { - _uplDone = data->uploadOffset; - _uplTextCache = formatDownloadText(_uplDone, data->size); - } - statusText = _uplTextCache; - } else if (data->loader) { - int32 offset = data->loader->currentOffset(); - if (_dldTextCache.isEmpty() || _dldDone != offset) { - _dldDone = offset; - _dldTextCache = formatDownloadText(_dldDone, data->size); - } - statusText = _dldTextCache; - } else { - statusText = _size; - } - - if (_thumbw) { - data->thumb->checkload(); - p.drawPixmap(QPoint(st::mediaPadding.left(), st::mediaPadding.top()), data->thumb->pixSingle(_thumbw, 0, st::mediaThumbSize, st::mediaThumbSize)); - } else { - p.drawPixmap(QPoint(st::mediaPadding.left(), st::mediaPadding.top()), App::sprite(), st::mediaDocInImg); - } - } - if (selected) { - App::roundRect(p, st::mediaPadding.left(), st::mediaPadding.top(), st::mediaThumbSize, st::mediaThumbSize, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); - } - - int32 tleft = st::mediaPadding.left() + st::mediaThumbSize + st::mediaPadding.right(); - int32 twidth = width - tleft - st::mediaPadding.right(); - int32 secondwidth = width - tleft - st::msgPadding.right() - parent->skipBlockWidth(); - - p.setFont(st::mediaFont->f); - p.setPen(st::black->c); - if (twidth < _namew) { - p.drawText(tleft, st::mediaPadding.top() + st::mediaNameTop + st::mediaFont->ascent, st::mediaFont->elided(_name, twidth)); - } else { - p.drawText(tleft, st::mediaPadding.top() + st::mediaNameTop + st::mediaFont->ascent, _name); - } - - style::color status(selected ? st::mediaInSelectColor : st::mediaInColor); - p.setPen(status->p); - p.drawText(tleft, st::mediaPadding.top() + st::mediaThumbSize - st::mediaDetailsShift - st::mediaFont->descent, statusText); -} - -TextLinkPtr HistoryDocument::linkInPlaylist() { - if (!data->loader && data->access) { - return _openl; - } - return TextLinkPtr(); -} - -void HistoryDocument::regItem(HistoryItem *item) { - App::regDocumentItem(data, item); -} - -void HistoryDocument::unregItem(HistoryItem *item) { - App::unregDocumentItem(data, item); -} - -void HistoryDocument::updateFrom(const MTPMessageMedia &media) { - if (media.type() == mtpc_messageMediaDocument) { - App::feedDocument(media.c_messageMediaDocument().vdocument, data); + _minh += _caption.countHeight(_maxw - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); } } int32 HistoryDocument::resize(int32 width, const HistoryItem *parent) { - w = qMin(width, _maxw); - if (parent == animated.msg) { - if (w > st::maxMediaSize) { - w = st::maxMediaSize; + if (_caption.isEmpty()) { + return HistoryFileMedia::resize(width, parent); + } + + _width = qMin(width, _maxw); + bool wthumb = withThumb(); + if (wthumb) { + _height = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom(); + } else { + _height = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom(); + } + _height += _caption.countHeight(_width - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); + + return _height; +} + +void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const { + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; + + _data->automaticLoad(parent); + bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); + + int32 captionw = _width - st::msgPadding.left() - st::msgPadding.right(); + + bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; + + if (displayLoading) { + ensureAnimation(parent); + if (!_animation->radial.animating()) { + _animation->radial.start(_data->progress()); } - _height = animated.h / cIntRetinaFactor(); - if (animated.w / cIntRetinaFactor() > w) { - _height = (w * _height / (animated.w / cIntRetinaFactor())); - if (_height <= 0) _height = 1; + } + bool showPause = updateStatusText(parent); + bool radial = isRadialAnimation(ms); + + int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0, bottom = 0; + bool wthumb = withThumb(); + if (wthumb) { + nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); + nametop = st::msgFileThumbNameTop; + nameright = st::msgFileThumbPadding.left(); + statustop = st::msgFileThumbStatusTop; + linktop = st::msgFileThumbLinkTop; + bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom(); + + QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbSize, st::msgFileThumbSize, _width)); + QPixmap thumb = loaded ? _data->thumb->pixSingle(_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize) : _data->thumb->pixBlurredSingle(_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize); + p.drawPixmap(rthumb.topLeft(), thumb); + if (selected) { + App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); + } + + if (radial || (!loaded && !_data->loading())) { + float64 radialOpacity = (radial && loaded && !_data->uploading()) ? _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) { + p.setBrush(st::msgDateImgBgSelected); + } else if (isThumbAnimation(ms)) { + float64 over = _animation->a_thumbOver.current(); + 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); + p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); + } + p.setOpacity(radialOpacity * p.opacity()); + + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.drawEllipse(inner); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + + p.setOpacity(radialOpacity); + style::sprite icon; + if (radial || _data->loading()) { + icon = (selected ? st::msgFileInCancelSelected : st::msgFileInCancel); + } else { + icon = (selected ? st::msgFileInDownloadSelected : st::msgFileInDownload); + } + p.setOpacity((radial && loaded) ? _animation->radial.opacity() : 1); + p.drawSpriteCenter(inner, icon); + if (radial) { + p.setOpacity(1); + + QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); + _animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::msgInBgSelected : st::msgInBg); + } + } + + if (_data->status != FileUploadFailed) { + const TextLinkPtr &lnk((_data->loading() || _data->status == FileUploading) ? _linkcancell : _linksavel); + bool over = textlnkDrawOver(lnk); + p.setFont(over ? st::semiboldFont->underline() : st::semiboldFont); + p.setPen(outbg ? (selected ? st::msgFileThumbLinkOutFgSelected : st::msgFileThumbLinkOutFg) : (selected ? st::msgFileThumbLinkInFgSelected : st::msgFileThumbLinkInFg)); + p.drawTextLeft(nameleft, linktop, _width, _link, _linkw); } } else { - _height = _minh; + nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); + nametop = st::msgFileNameTop; + nameright = st::msgFilePadding.left(); + statustop = st::msgFileStatusTop; + bottom = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom(); + + QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top(), st::msgFileSize, st::msgFileSize, _width)); + p.setPen(Qt::NoPen); + if (selected) { + p.setBrush(outbg ? st::msgFileOutBgSelected : st::msgFileInBgSelected); + } else if (isThumbAnimation(ms)) { + float64 over = _animation->a_thumbOver.current(); + p.setBrush(style::interpolate(outbg ? st::msgFileOutBg : st::msgFileInBg, outbg ? st::msgFileOutBgOver : st::msgFileInBgOver, over)); + } else { + bool over = textlnkDrawOver(_data->loading() ? _cancell : _savel); + p.setBrush(outbg ? (over ? st::msgFileOutBgOver : st::msgFileOutBg) : (over ? st::msgFileInBgOver : st::msgFileInBg)); + } + + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.drawEllipse(inner); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + + if (radial) { + QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); + style::color bg(outbg ? (selected ? st::msgOutBgSelected : st::msgOutBg) : (selected ? st::msgInBgSelected : st::msgInBg)); + _animation->radial.draw(p, rinner, st::msgFileRadialLine, bg); + } + + style::sprite icon; + if (showPause) { + icon = outbg ? (selected ? st::msgFileOutPauseSelected : st::msgFileOutPause) : (selected ? st::msgFileInPauseSelected : st::msgFileInPause); + } else if (radial || _data->loading()) { + icon = outbg ? (selected ? st::msgFileOutCancelSelected : st::msgFileOutCancel) : (selected ? st::msgFileInCancelSelected : st::msgFileInCancel); + } else if (loaded) { + if (_data->song()) { + icon = outbg ? (selected ? st::msgFileOutPlaySelected : st::msgFileOutPlay) : (selected ? st::msgFileInPlaySelected : st::msgFileInPlay); + } else if (_data->isImage()) { + icon = outbg ? (selected ? st::msgFileOutImageSelected : st::msgFileOutImage) : (selected ? st::msgFileInImageSelected : st::msgFileInImage); + } else { + icon = outbg ? (selected ? st::msgFileOutFileSelected : st::msgFileOutFile) : (selected ? st::msgFileInFileSelected : st::msgFileInFile); + } + } else { + icon = outbg ? (selected ? st::msgFileOutDownloadSelected : st::msgFileOutDownload) : (selected ? st::msgFileInDownloadSelected : st::msgFileInDownload); + } + p.drawSpriteCenter(inner, icon); + } + int32 namewidth = _width - nameleft - nameright; + + p.setFont(st::semiboldFont); + p.setPen(st::black); + if (namewidth < _namew) { + p.drawTextLeft(nameleft, nametop, _width, st::semiboldFont->elided(_name, namewidth)); + } else { + p.drawTextLeft(nameleft, nametop, _width, _name, _namew); + } + + style::color status(outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg)); + p.setFont(st::normalFont); + p.setPen(status); + p.drawTextLeft(nameleft, statustop, _width, _statusText); + + if (!_caption.isEmpty()) { + p.setPen(st::black); + _caption.draw(p, st::msgPadding.left(), bottom, captionw); + } +} + +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; + + bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; + bool loaded = _data->loaded(); + + bool showPause = updateStatusText(parent); + + int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0, bottom = 0; + bool wthumb = withThumb(); + if (wthumb) { + nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); + linktop = st::msgFileThumbLinkTop; + bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom(); + + QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbSize, st::msgFileThumbSize, _width)); + + if ((_data->loading() || _data->uploading() || !loaded) && rthumb.contains(x, y)) { + lnk = (_data->loading() || _data->uploading()) ? _cancell : _savel; + return; + } + + if (_data->status != FileUploadFailed) { + if (rtlrect(nameleft, linktop, _linkw, st::semiboldFont->height, _width).contains(x, y)) { + lnk = (_data->loading() || _data->uploading()) ? _linkcancell : _linksavel; + return; + } + } + } else { + bottom = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom(); + + 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; + } + } + + int32 height = _height; + if (!_caption.isEmpty()) { + if (y >= bottom) { + bool inText = false; + _caption.getState(lnk, inText, x - st::msgPadding.left(), y - bottom, _width - st::msgPadding.left() - st::msgPadding.right()); + state = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; + return; + } + height -= _caption.countHeight(_width - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); + } + if (x >= 0 && y >= 0 && x < _width && y < height && !_data->loading() && !_data->uploading() && _data->access) { + lnk = _openl; + return; } - return _height; } const QString HistoryDocument::inDialogsText() const { - return _name.isEmpty() ? lang(lng_in_dlg_file) : _name; + return (_name.isEmpty() ? lang(lng_in_dlg_file) : _name) + (_caption.isEmpty() ? QString() : (' ' + _caption.original(0, 0xFFFF, Text::ExpandLinksNone))); } const QString HistoryDocument::inHistoryText() const { - return qsl("[ ") + lang(lng_in_dlg_file) + (_name.isEmpty() ? QString() : (qsl(" : ") + _name)) + qsl(" ]"); + return qsl("[ ") + lang(lng_in_dlg_file) + (_name.isEmpty() ? QString() : (qsl(" : ") + _name)) + (_caption.isEmpty() ? QString() : (qsl(", ") + _caption.original(0, 0xFFFF, Text::ExpandLinksAll))) + qsl(" ]"); } -bool HistoryDocument::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const { - if (width < 0) width = w; - if (width >= _maxw) { - width = _maxw; +void HistoryDocument::setStatusSize(int32 newSize, qint64 realDuration) const { + HistoryFileMedia::setStatusSize(newSize, _data->size, _data->song() ? _data->song()->duration : -1, realDuration); + + if (_statusSize == FileStatusSizeReady) { + _link = lang(lng_media_download).toUpper(); + } else if (_statusSize == FileStatusSizeLoaded) { + _link = lang(lng_media_open_with).toUpper(); + } else if (_statusSize == FileStatusSizeFailed) { + _link = lang(lng_media_download).toUpper(); + } else if (_statusSize >= 0) { + _link = lang(lng_media_cancel).toUpper(); + } else { + _link = lang(lng_media_open_with).toUpper(); } - if (parent == animated.msg) { - int32 h = (width == w) ? _height : (width * animated.h / animated.w); - if (h < 1) h = 1; - return (x >= 0 && y >= 0 && x < width && y < h); - } - return (x >= 0 && y >= 0 && x < width && y < _height); + _linkw = st::semiboldFont->width(_link); } -int32 HistoryDocument::countHeight(const HistoryItem *parent, int32 width) const { - if (width < 0) width = w; - if (width >= _maxw) { - width = _maxw; +bool HistoryDocument::updateStatusText(const HistoryItem *parent) const { + bool showPause = false; + int32 statusSize = 0, realDuration = 0; + if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { + statusSize = FileStatusSizeFailed; + } else if (_data->status == FileUploading) { + statusSize = _data->uploadOffset; + } else if (_data->loading()) { + statusSize = _data->loadOffset(); + } else if (_data->loaded()) { + if (_data->song()) { + SongMsgId playing; + AudioPlayerState playingState = AudioPlayerStopped; + int64 playingPosition = 0, playingDuration = 0; + int32 playingFrequency = 0; + if (audioPlayer()) { + audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); + } + + if (playing.msgId == parent->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { + statusSize = -1 - (playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)); + realDuration = playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency); + showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); + } else { + statusSize = FileStatusSizeLoaded; + } + if (!showPause && playing.msgId == parent->fullId() && App::main() && App::main()->player()->seekingSong(playing)) { + showPause = true; + } + } else { + statusSize = FileStatusSizeLoaded; + } + } else { + statusSize = FileStatusSizeReady; } - if (parent == animated.msg) { - int32 h = (width == w) ? _height : (width * animated.h / animated.w); - if (h < 1) h = 1; - return h; + if (statusSize != _statusSize) { + setStatusSize(statusSize, realDuration); } - return _height; + return showPause; } -void HistoryDocument::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width) const { - if (width < 0) width = w; - if (width < 1) return; - - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel, hovered, pressed; - if (width >= _maxw) { - width = _maxw; - } - if (parent == animated.msg) { - int32 h = (width == w) ? _height : (width * animated.h / animated.w); - if (h < 1) h = 1; - lnk = (x >= 0 && y >= 0 && x < width && y < h) ? _openl : TextLinkPtr(); - return; - } - - const HistoryReply *reply = toHistoryReply(parent); - const HistoryForwarded *fwd = (reply || data->song()) ? 0 : toHistoryForwarded(parent); - int skipy = 0, replyFrom = 0, fwdFrom = 0; - if (reply) { - skipy = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - } else if (fwd) { - skipy = st::msgServiceNameFont->height; - } - if (parent->displayFromName()) { - replyFrom = st::msgPadding.top() + st::msgNameFont->height; - fwdFrom = st::msgPadding.top() + st::msgNameFont->height; - skipy += replyFrom; - } else if (fwd) { - fwdFrom = st::msgPadding.top(); - skipy += fwdFrom; - } - - if (!outbg) { // draw Download / Save As button - int32 btnw = _buttonWidth, btnh = st::mediaSaveButton.height, btnx = width - _buttonWidth, btny = skipy + (_height - skipy - btnh) / 2; - if (x >= btnx && y >= btny && x < btnx + btnw && y < btny + btnh) { - lnk = data->loader ? _cancell : _savel; - return; - } - width -= btnw + st::mediaSaveDelta; - } - - if (parent->displayFromName()) { - if (x >= st::mediaPadding.left() && y >= st::msgPadding.top() && x < width - st::mediaPadding.left() - st::mediaPadding.right() && x < st::mediaPadding.left() + parent->from()->nameText.maxWidth() && y < replyFrom) { - lnk = parent->from()->lnk; - return; - } - } - if (reply) { - if (x >= 0 && y >= replyFrom + st::msgReplyPadding.top() && x < width && y < skipy - st::msgReplyPadding.bottom()) { - lnk = reply->replyToLink(); - return; - } - } else if (fwd) { - if (y >= fwdFrom && y < skipy) { - return fwd->getForwardedState(lnk, state, x - st::mediaPadding.left(), width - st::mediaPadding.left() - st::mediaPadding.right()); - } - } - - if (x >= 0 && y >= skipy && x < width && y < _height && !data->loader && data->access) { - lnk = _openl; - - bool inDate = parent->pointInTime(width, _height, x, y, InfoDisplayDefault); - if (inDate) { - state = HistoryInDateCursorState; - } - - return; - } +void HistoryDocument::regItem(HistoryItem *item) { + App::regDocumentItem(_data, item); } -HistoryMedia *HistoryDocument::clone() const { - return new HistoryDocument(*this); +void HistoryDocument::unregItem(HistoryItem *item) { + App::unregDocumentItem(_data, item); +} + +void HistoryDocument::updateFrom(const MTPMessageMedia &media, HistoryItem *parent) { + if (media.type() == mtpc_messageMediaDocument) { + App::feedDocument(media.c_messageMediaDocument().vdocument, _data); + } } ImagePtr HistoryDocument::replyPreview() { - return data->makeReplyPreview(); + return _data->makeReplyPreview(); +} + +HistoryGif::HistoryGif(DocumentData *document, const QString &caption, const HistoryItem *parent) : HistoryFileMedia() +, _data(document) +, _thumbw(1) +, _thumbh(1) +, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) +, _gif(0) { + setLinks(new GifOpenLink(_data), new GifOpenLink(_data), new DocumentCancelLink(_data)); + + setStatusSize(FileStatusSizeReady); + + if (!caption.isEmpty()) { + _caption.setText(st::msgFont, caption + parent->skipBlock(), itemTextNoMonoOptions(parent)); + } + + _data->thumb->load(); +} + +HistoryGif::HistoryGif(const HistoryGif &other) : HistoryFileMedia() +, _parent(0) +, _data(other._data) +, _thumbw(other._thumbw) +, _thumbh(other._thumbh) +, _gif(0) { + setLinks(new GifOpenLink(_data), new GifOpenLink(_data), new DocumentCancelLink(_data)); + + setStatusSize(other._statusSize); +} + +void HistoryGif::initDimensions(const HistoryItem *parent) { + _parent = parent; + if (_caption.hasSkipBlock()) { + _caption.setSkipBlock(parent->skipBlockWidth(), parent->skipBlockHeight()); + } + + bool bubble = parent->hasBubble(); + int32 tw = 0, th = 0; + if (gif() && _gif->state() == ClipError) { + if (!_gif->autoplay()) { + Ui::showLayer(new InformBox(lang(lng_gif_error))); + } + App::unregGifItem(_gif); + delete _gif; + _gif = BadClipReader; + } + + if (gif() && _gif->ready()) { + tw = convertScale(_gif->width()); + th = convertScale(_gif->height()); + } else { + tw = convertScale(_data->dimensions.width()), th = convertScale(_data->dimensions.height()); + if (!tw || !th) { + tw = convertScale(_data->thumb->width()); + th = convertScale(_data->thumb->height()); + } + } + if (tw > st::maxGifSize) { + th = (st::maxGifSize * th) / tw; + tw = st::maxGifSize; + } + if (th > st::maxGifSize) { + tw = (st::maxGifSize * tw) / th; + th = st::maxGifSize; + } + if (!tw || !th) { + tw = th = 1; + } + _thumbw = tw; + _thumbh = th; + _maxw = qMax(tw, int32(st::minPhotoSize)); + _minh = qMax(th, int32(st::minPhotoSize)); + if (!gif() || !_gif->ready()) { + _maxw = qMax(_maxw, parent->infoWidth() + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); + _maxw = qMax(_maxw, gifMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); + } + if (bubble) { + _maxw += st::mediaPadding.left() + st::mediaPadding.right(); + _minh += st::mediaPadding.top() + st::mediaPadding.bottom(); + if (!_caption.isEmpty()) { + _minh += st::mediaCaptionSkip + _caption.countHeight(_maxw - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); + } + } +} + +int32 HistoryGif::resize(int32 width, const HistoryItem *parent) { + bool bubble = parent->hasBubble(); + + int32 tw = 0, th = 0; + if (gif() && _gif->ready()) { + tw = convertScale(_gif->width()); + th = convertScale(_gif->height()); + } else { + tw = convertScale(_data->dimensions.width()), th = convertScale(_data->dimensions.height()); + if (!tw || !th) { + tw = convertScale(_data->thumb->width()); + th = convertScale(_data->thumb->height()); + } + } + if (tw > st::maxGifSize) { + th = (st::maxGifSize * th) / tw; + tw = st::maxGifSize; + } + if (th > st::maxGifSize) { + tw = (st::maxGifSize * tw) / th; + th = st::maxGifSize; + } + if (!tw || !th) { + tw = th = 1; + } + + if (bubble) { + width -= st::mediaPadding.left() + st::mediaPadding.right(); + } + if (width < tw) { + th = qRound((width / float64(tw)) * th); + tw = width; + } + _thumbw = tw; + _thumbh = th; + + _width = qMax(tw, int32(st::minPhotoSize)); + _height = qMax(th, int32(st::minPhotoSize)); + if (gif() && _gif->ready()) { + if (!_gif->started()) { + _gif->start(_thumbw, _thumbh, _width, _height, true); + } + } else { + _width = qMax(_width, parent->infoWidth() + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); + _width = qMax(_width, gifMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); + } + if (bubble) { + _width += st::mediaPadding.left() + st::mediaPadding.right(); + _height += st::mediaPadding.top() + st::mediaPadding.bottom(); + if (!_caption.isEmpty()) { + _height += st::mediaCaptionSkip + _caption.countHeight(_width - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); + } + } + + return _height; +} + +void HistoryGif::draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const { + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; + + _data->automaticLoad(parent); + bool loaded = _data->loaded(), displayLoading = (parent->id < 0) || _data->displayLoading(); + if (loaded && !gif() && _gif != BadClipReader && cAutoPlayGif()) { + const_cast(this)->playInline(const_cast(parent)); + if (gif()) _gif->setAutoplay(); + } + + int32 skipx = 0, skipy = 0, width = _width, height = _height; + bool bubble = parent->hasBubble(); + bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; + + int32 captionw = width - st::msgPadding.left() - st::msgPadding.right(); + + bool animating = (gif() && _gif->started()); + + if (!animating || parent->id < 0) { + if (displayLoading) { + ensureAnimation(parent); + if (!_animation->radial.animating()) { + _animation->radial.start(dataProgress()); + } + } + updateStatusText(parent); + } + bool radial = isRadialAnimation(ms); + + if (bubble) { + skipx = st::mediaPadding.left(); + skipy = st::mediaPadding.top(); + + width -= st::mediaPadding.left() + st::mediaPadding.right(); + height -= skipy + st::mediaPadding.bottom(); + if (!_caption.isEmpty()) { + height -= st::mediaCaptionSkip + _caption.countHeight(captionw) + st::msgPadding.bottom(); + } + } else { + App::roundShadow(p, 0, 0, width, _height, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); + } + + QRect rthumb(rtlrect(skipx, skipy, width, height, _width)); + + if (animating) { + p.drawPixmap(rthumb.topLeft(), _gif->current(_thumbw, _thumbh, width, height, (Ui::isLayerShown() || Ui::isMediaViewShown() || Ui::isInlineItemBeingChosen()) ? 0 : ms)); + } else { + p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(_thumbw, _thumbh, width, height)); + } + if (selected) { + App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); + } + + if (radial || (!_gif && ((!loaded && !_data->loading()) || !cAutoPlayGif())) || (_gif == BadClipReader)) { + 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) { + p.setBrush(st::msgDateImgBgSelected); + } else if (isThumbAnimation(ms)) { + float64 over = _animation->a_thumbOver.current(); + 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); + p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); + } + p.setOpacity(radialOpacity * p.opacity()); + + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.drawEllipse(inner); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + + p.setOpacity(radialOpacity); + style::sprite icon; + if (_data->loaded() && !radial) { + icon = (selected ? st::msgFileInPlaySelected : st::msgFileInPlay); + } else if (radial || _data->loading()) { + if (parent->id > 0 || _data->uploading()) { + icon = (selected ? st::msgFileInCancelSelected : st::msgFileInCancel); + } + } else { + icon = (selected ? st::msgFileInDownloadSelected : st::msgFileInDownload); + } + if (!icon.isEmpty()) { + p.drawSpriteCenter(inner, icon); + } + if (radial) { + p.setOpacity(1); + QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); + _animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::msgInBgSelected : st::msgInBg); + } + + 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(); + App::roundRect(p, rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, _width), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); + p.setFont(st::normalFont); + p.setPen(st::white); + p.drawTextLeft(statusX, statusY, _width, _statusText, statusW - 2 * st::msgDateImgPadding.x()); + } + } + + 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) { + int32 fullRight = skipx + width, fullBottom = skipy + height; + parent->drawInfo(p, fullRight, fullBottom, 2 * skipx + width, selected, InfoDisplayOverImage); + } +} + +void HistoryGif::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const { + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; + int32 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(); + 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; + } + height -= st::mediaCaptionSkip; + } + width -= st::mediaPadding.left() + st::mediaPadding.right(); + height -= skipy + st::mediaPadding.bottom(); + } + if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height) { + if (_data->uploading()) { + lnk = _cancell; + } else if (!gif() || !cAutoPlayGif()) { + lnk = _data->loaded() ? _openl : (_data->loading() ? _cancell : _savel); + } + if (parent->getMedia() == this) { + int32 fullRight = skipx + width, fullBottom = skipy + height; + bool inDate = parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage); + if (inDate) { + state = HistoryInDateCursorState; + } + } + return; + } +} + +const QString HistoryGif::inDialogsText() const { + return qsl("GIF") + (_caption.isEmpty() ? QString() : (' ' + _caption.original(0, 0xFFFF, Text::ExpandLinksNone))); +} + +const QString HistoryGif::inHistoryText() const { + return qsl("[ GIF ") + (_caption.isEmpty() ? QString() : (_caption.original(0, 0xFFFF, Text::ExpandLinksAll) + ' ')) + qsl(" ]"); +} + +void HistoryGif::setStatusSize(int32 newSize) const { + HistoryFileMedia::setStatusSize(newSize, _data->size, -2, 0); +} + +void HistoryGif::updateStatusText(const HistoryItem *parent) const { + bool showPause = false; + int32 statusSize = 0, realDuration = 0; + if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { + statusSize = FileStatusSizeFailed; + } else if (_data->status == FileUploading) { + statusSize = _data->uploadOffset; + } else if (_data->loading()) { + statusSize = _data->loadOffset(); + } else if (_data->loaded()) { + statusSize = FileStatusSizeLoaded; + } else { + statusSize = FileStatusSizeReady; + } + if (statusSize != _statusSize) { + setStatusSize(statusSize); + } +} + +void HistoryGif::regItem(HistoryItem *item) { + App::regDocumentItem(_data, item); +} + +void HistoryGif::unregItem(HistoryItem *item) { + App::unregDocumentItem(_data, item); +} + +void HistoryGif::updateFrom(const MTPMessageMedia &media, HistoryItem *parent) { + if (media.type() == mtpc_messageMediaDocument) { + App::feedDocument(media.c_messageMediaDocument().vdocument, _data); + } +} + +ImagePtr HistoryGif::replyPreview() { + return _data->makeReplyPreview(); +} + +bool HistoryGif::playInline(HistoryItem *parent) { + if (gif()) { + stopInline(parent); + } else { + if (!cAutoPlayGif()) { + App::stopGifItems(); + } + _gif = new ClipReader(_data->location(), _data->data(), func(parent, &HistoryItem::clipCallback)); + App::regGifItem(_gif, parent); + } + return true; +} + +void HistoryGif::stopInline(HistoryItem *parent) { + if (gif()) { + App::unregGifItem(_gif); + delete _gif; + _gif = 0; + } + + parent->initDimensions(); + Notify::historyItemResized(parent); + Notify::historyItemLayoutChanged(parent); +} + +HistoryGif::~HistoryGif() { + if (gif()) { + App::unregGifItem(_gif); + deleteAndMark(_gif); + } +} + +float64 HistoryGif::dataProgress() const { + return (_data->uploading() || !_parent || _parent->id > 0) ? _data->progress() : 0; +} + +bool HistoryGif::dataFinished() const { + return (!_parent || _parent->id > 0) ? (!_data->loading() && !_data->uploading()) : false; +} + +bool HistoryGif::dataLoaded() const { + return (!_parent || _parent->id > 0) ? _data->loaded() : false; } HistorySticker::HistorySticker(DocumentData *document) : HistoryMedia() -, pixw(1), pixh(1), data(document), lastw(0) -{ - data->thumb->load(); - if (!data->sticker()->alt.isEmpty()) { - _emoji = data->sticker()->alt; - int32 elen = 0; - if (EmojiPtr e = emojiFromText(_emoji.constData(), _emoji.constEnd(), elen)) { - _emoji = emojiString(e); - } +, _pixw(1) +, _pixh(1) +, _data(document) +, _emoji(_data->sticker()->alt) { + _data->thumb->load(); + if (EmojiPtr e = emojiFromText(_emoji)) { + _emoji = emojiString(e); } } void HistorySticker::initDimensions(const HistoryItem *parent) { - pixw = data->dimensions.width(); - pixh = data->dimensions.height(); - if (pixw > st::maxStickerSize) { - pixh = (st::maxStickerSize * pixh) / pixw; - pixw = st::maxStickerSize; + _pixw = _data->dimensions.width(); + _pixh = _data->dimensions.height(); + if (_pixw > st::maxStickerSize) { + _pixh = (st::maxStickerSize * _pixh) / _pixw; + _pixw = st::maxStickerSize; } - if (pixh > st::maxStickerSize) { - pixw = (st::maxStickerSize * pixw) / pixh; - pixh = st::maxStickerSize; + if (_pixh > st::maxStickerSize) { + _pixw = (st::maxStickerSize * _pixw) / _pixh; + _pixh = st::maxStickerSize; } - if (pixw < 1) pixw = 1; - if (pixh < 1) pixh = 1; - _maxw = qMax(pixw, int16(st::minPhotoSize)); - _minh = qMax(pixh, int16(st::minPhotoSize)); + if (_pixw < 1) _pixw = 1; + if (_pixh < 1) _pixh = 1; + _maxw = qMax(_pixw, int16(st::minPhotoSize)); + _minh = qMax(_pixh, int16(st::minPhotoSize)); if (const HistoryReply *reply = toHistoryReply(parent)) { _maxw += st::msgReplyPadding.left() + reply->replyToWidth(); } _height = _minh; - w = qMin(lastw, _maxw); } -void HistorySticker::draw(Painter &p, const HistoryItem *parent, bool selected, int32 width) const { - if (width < 0) width = w; - if (width < 1) return; - if (width > _maxw) width = _maxw; +int32 HistorySticker::resize(int32 width, const HistoryItem *parent) { // return new height + _width = qMin(width, _maxw); + if (const HistoryReply *reply = toHistoryReply(parent)) { + int32 usew = _maxw - st::msgReplyPadding.left() - reply->replyToWidth(); + int32 rw = _width - usew - st::msgReplyPadding.left() - st::msgReplyPadding.left() - st::msgReplyPadding.right(); + reply->resizeVia(rw); + } + return _height; +} + +void HistorySticker::draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const { + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; + + _data->checkSticker(); + bool loaded = _data->loaded(); bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel, hovered, pressed; - bool already = !data->already().isEmpty(), hasdata = !data->data.isEmpty(); int32 usew = _maxw, usex = 0; const HistoryReply *reply = toHistoryReply(parent); @@ -4715,65 +4797,68 @@ void HistorySticker::draw(Painter &p, const HistoryItem *parent, bool selected, usew -= st::msgReplyPadding.left() + reply->replyToWidth(); if (fromChannel) { } else if (out) { - usex = width - usew; + usex = _width - usew; } } + if (rtl()) usex = _width - usex - usew; - if (!data->loader && data->status != FileFailed && !already && !hasdata) { - data->save(QString()); - } - if (data->sticker()->img->isNull() && (already || hasdata)) { - if (already) { - data->sticker()->img = ImagePtr(data->already()); - } else { - data->sticker()->img = ImagePtr(data->data); - } - } if (selected) { - if (data->sticker()->img->isNull()) { - p.drawPixmap(QPoint(usex + (usew - pixw) / 2, (_minh - pixh) / 2), data->thumb->pixBlurredColored(st::msgStickerOverlay, pixw, pixh)); + if (_data->sticker()->img->isNull()) { + p.drawPixmap(QPoint(usex + (usew - _pixw) / 2, (_minh - _pixh) / 2), _data->thumb->pixBlurredColored(st::msgStickerOverlay, _pixw, _pixh)); } else { - p.drawPixmap(QPoint(usex + (usew - pixw) / 2, (_minh - pixh) / 2), data->sticker()->img->pixColored(st::msgStickerOverlay, pixw, pixh)); + p.drawPixmap(QPoint(usex + (usew - _pixw) / 2, (_minh - _pixh) / 2), _data->sticker()->img->pixColored(st::msgStickerOverlay, _pixw, _pixh)); } } else { - if (data->sticker()->img->isNull()) { - p.drawPixmap(QPoint(usex + (usew - pixw) / 2, (_minh - pixh) / 2), data->thumb->pixBlurred(pixw, pixh)); + if (_data->sticker()->img->isNull()) { + p.drawPixmap(QPoint(usex + (usew - _pixw) / 2, (_minh - _pixh) / 2), _data->thumb->pixBlurred(_pixw, _pixh)); } else { - p.drawPixmap(QPoint(usex + (usew - pixw) / 2, (_minh - pixh) / 2), data->sticker()->img->pix(pixw, pixh)); + p.drawPixmap(QPoint(usex + (usew - _pixw) / 2, (_minh - _pixh) / 2), _data->sticker()->img->pix(_pixw, _pixh)); } } - parent->drawInfo(p, usex + usew, _height, selected, InfoDisplayOverImage); + if (parent->getMedia() == this) { + parent->drawInfo(p, usex + usew, _height, usex * 2 + usew, selected, InfoDisplayOverImage); - if (reply) { - int32 rw = width - usew - st::msgReplyPadding.left(), rh = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - int32 rx = fromChannel ? (usew + st::msgReplyPadding.left()) : (out ? 0 : (usew + st::msgReplyPadding.left())), ry = _height - rh; - - App::roundRect(p, rx, ry, rw, rh, selected ? App::msgServiceSelectBg() : App::msgServiceBg(), selected ? ServiceSelectedCorners : ServiceCorners); + if (reply) { + int32 rw = _width - usew - st::msgReplyPadding.left(), rh = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); + int32 rx = fromChannel ? (usew + st::msgReplyPadding.left()) : (out ? 0 : (usew + st::msgReplyPadding.left())), ry = _height - rh; + if (rtl()) rx = _width - rx - rw; - reply->drawReplyTo(p, rx + st::msgReplyPadding.left(), ry, rw - st::msgReplyPadding.left() - st::msgReplyPadding.right(), selected, true); + App::roundRect(p, rx, ry, rw, rh, selected ? App::msgServiceSelectBg() : App::msgServiceBg(), selected ? ServiceSelectedCorners : ServiceCorners); + + reply->drawReplyTo(p, rx + st::msgReplyPadding.left(), ry, rw - st::msgReplyPadding.left() - st::msgReplyPadding.right(), selected, true); + } } } -int32 HistorySticker::resize(int32 width, const HistoryItem *parent) { - w = qMin(width, _maxw); - lastw = width; - return _height; -} +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; -void HistorySticker::regItem(HistoryItem *item) { - App::regDocumentItem(data, item); -} + bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; -void HistorySticker::unregItem(HistoryItem *item) { - App::unregDocumentItem(data, item); -} - -void HistorySticker::updateFrom(const MTPMessageMedia &media) { - if (media.type() == mtpc_messageMediaDocument) { - App::feedDocument(media.c_messageMediaDocument().vdocument, data); - if (!data->data.isEmpty()) { - Local::writeStickerImage(mediaKey(mtpToLocationType(mtpc_inputDocumentFileLocation), data->dc, data->id), data->data); + int32 usew = _maxw, usex = 0; + const HistoryReply *reply = toHistoryReply(parent); + if (reply) { + usew -= reply->replyToWidth(); + if (fromChannel) { + } else if (out) { + usex = _width - usew; + } + } + if (rtl()) usex = _width - usex - usew; + if (reply) { + int32 rw = _width - usew, rh = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); + int32 rx = fromChannel ? (usew + st::msgReplyPadding.left()) : (out ? 0 : (usew + st::msgReplyPadding.left())), ry = _height - rh; + if (rtl()) rx = _width - rx - rw; + if (x >= rx && y >= ry && x < rx + rw && y < ry + rh) { + lnk = reply->replyToLink(); + return; + } + } + if (parent->getMedia() == this) { + bool inDate = parent->pointInTime(usex + usew, _height, x, y, InfoDisplayOverImage); + if (inDate) { + state = HistoryInDateCursorState; } } } @@ -4786,546 +4871,555 @@ const QString HistorySticker::inHistoryText() const { return qsl("[ ") + inDialogsText() + qsl(" ]"); } -bool HistorySticker::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const { - return (x >= 0 && y >= 0 && x < _maxw && y < _minh); +void HistorySticker::regItem(HistoryItem *item) { + App::regDocumentItem(_data, item); } -int32 HistorySticker::countHeight(const HistoryItem *parent, int32 width) const { - return _minh; +void HistorySticker::unregItem(HistoryItem *item) { + App::unregDocumentItem(_data, item); } -void HistorySticker::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width) const { - if (width < 0) width = w; - if (width < 1) return; - - if (width > _maxw) width = _maxw; - - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; - - int32 usew = _maxw, usex = 0; - const HistoryReply *reply = toHistoryReply(parent); - if (reply) { - usew -= reply->replyToWidth(); - if (fromChannel) { - } else if (out) { - usex = width - usew; - } - - int32 rw = width - usew, rh = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - int32 rx = fromChannel ? (usew + st::msgReplyPadding.left()) : (out ? 0 : (usew + st::msgReplyPadding.left())), ry = _height - rh; - if (x >= rx && y >= ry && x < rx + rw && y < ry + rh) { - lnk = reply->replyToLink(); - return; +void HistorySticker::updateFrom(const MTPMessageMedia &media, HistoryItem *parent) { + if (media.type() == mtpc_messageMediaDocument) { + App::feedDocument(media.c_messageMediaDocument().vdocument, _data); + if (!_data->data().isEmpty()) { + Local::writeStickerImage(mediaKey(DocumentFileLocation, _data->dc, _data->id), _data->data()); } } - bool inDate = parent->pointInTime(usex + usew, _height, x, y, InfoDisplayOverImage); - if (inDate) { - state = HistoryInDateCursorState; +} + +void SendMessageLink::onClick(Qt::MouseButton button) const { + if (button == Qt::LeftButton) { + Ui::showPeerHistory(peer()->id, ShowAtUnreadMsgId); } } -HistoryMedia *HistorySticker::clone() const { - return new HistorySticker(*this); -} - -HistoryContact::HistoryContact(int32 userId, const QString &first, const QString &last, const QString &phone) : HistoryMedia(0) -, userId(userId) -, phone(App::formatPhone(phone)) -, contact(App::userLoaded(userId)) -{ - App::regSharedContactPhone(userId, phone); - - _maxw = st::mediaMaxWidth; - name.setText(st::mediaFont, lng_full_name(lt_first_name, first, lt_last_name, last).trimmed(), _textNameOptions); - - phonew = st::mediaFont->width(phone); - - if (contact) { - contact->photo->load(); +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)); + } + } + } } } -HistoryContact::HistoryContact(int32 userId, const QString &fullname, const QString &phone) : HistoryMedia(0) -, userId(userId) -, phone(App::formatPhone(phone)) -, contact(App::userLoaded(userId)) -{ - App::regSharedContactPhone(userId, phone); +HistoryContact::HistoryContact(int32 userId, const QString &first, const QString &last, const QString &phone) : HistoryMedia() +, _userId(userId) +, _contact(0) +, _phonew(0) +, _fname(first) +, _lname(last) +, _phone(App::formatPhone(phone)) +, _linkw(0) { + _name.setText(st::semiboldFont, lng_full_name(lt_first_name, first, lt_last_name, last).trimmed(), _textNameOptions); - _maxw = st::mediaMaxWidth; - name.setText(st::mediaFont, fullname.trimmed(), _textNameOptions); - - phonew = st::mediaFont->width(phone); - - if (contact) { - contact->photo->load(); - } + _phonew = st::normalFont->width(_phone); } void HistoryContact::initDimensions(const HistoryItem *parent) { - int32 tleft = st::mediaPadding.left() + st::mediaThumbSize + st::mediaPadding.right(); - int32 fullInfoWidth = parent->skipBlockWidth() + st::msgPadding.right(); - if (name.maxWidth() + tleft + fullInfoWidth > _maxw) { - _maxw = name.maxWidth() + tleft + fullInfoWidth; + _maxw = st::msgFileMinWidth; + + _contact = _userId ? App::userLoaded(_userId) : 0; + if (_contact) { + _contact->photo->load(); } - if (phonew + tleft + st::mediaPadding.right() > _maxw) { - _maxw = phonew + tleft + st::mediaPadding.right(); + if (_contact && _contact->contact > 0) { + _linkl.reset(new SendMessageLink(_contact)); + _link = lang(lng_profile_send_message).toUpper(); + } else if (_userId) { + _linkl.reset(new AddContactLink(parent->history()->peer->id, parent->id)); + _link = lang(lng_profile_add_contact).toUpper(); } - _minh = st::mediaPadding.top() + st::mediaThumbSize + st::mediaPadding.bottom(); - if (parent->displayFromName()) { - _minh += st::msgPadding.top() + st::msgNameFont->height; + _linkw = _link.isEmpty() ? 0 : st::semiboldFont->width(_link); + + int32 tleft = 0, tright = 0; + if (_userId) { + tleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); + tright = st::msgFileThumbPadding.left(); + _maxw = qMax(_maxw, tleft + _phonew + tright); + } else { + tleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); + tright = st::msgFileThumbPadding.left(); + _maxw = qMax(_maxw, tleft + _phonew + parent->skipBlockWidth() + st::msgPadding.right()); } - if (const HistoryReply *reply = toHistoryReply(parent)) { - _minh += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - } else if (const HistoryForwarded *fwd = toHistoryForwarded(parent)) { - if (!parent->displayFromName()) { - _minh += st::msgPadding.top(); - } - _minh += st::msgServiceNameFont->height; + + _maxw = qMax(tleft + _name.maxWidth() + tright, _maxw); + _maxw = qMin(_maxw, int(st::msgMaxWidth)); + + if (_userId) { + _minh = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom(); + } else { + _minh = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom(); } _height = _minh; } +void HistoryContact::draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const { + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; + int32 skipx = 0, skipy = 0, width = _width, height = _height; + + bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; + + if (width >= _maxw) { + width = _maxw; + } + + int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0; + if (_userId) { + nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); + nametop = st::msgFileThumbNameTop; + nameright = st::msgFileThumbPadding.left(); + statustop = st::msgFileThumbStatusTop; + linktop = st::msgFileThumbLinkTop; + + QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbSize, st::msgFileThumbSize, width)); + if (_contact && _contact->photo->loaded()) { + QPixmap thumb = _contact->photo->pixRounded(st::msgFileThumbSize, st::msgFileThumbSize); + p.drawPixmap(rthumb.topLeft(), thumb); + } else { + p.drawPixmap(rthumb.topLeft(), userDefPhoto(_contact ? _contact->colorIndex : (qAbs(_userId) % UserColorsCount))->pixRounded(st::msgFileThumbSize, st::msgFileThumbSize)); + } + if (selected) { + App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); + } + + bool over = textlnkDrawOver(_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); + } else { + nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); + nametop = st::msgFileNameTop; + nameright = st::msgFilePadding.left(); + statustop = st::msgFileStatusTop; + + QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top(), st::msgFileSize, st::msgFileSize, width)); + p.drawPixmap(inner.topLeft(), userDefPhoto(qAbs(parent->id) % UserColorsCount)->pixRounded(st::msgFileSize, st::msgFileSize)); + } + int32 namewidth = width - nameleft - nameright; + + p.setFont(st::semiboldFont); + p.setPen(st::black); + _name.drawLeftElided(p, nameleft, nametop, namewidth, width); + + style::color status(outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg)); + p.setFont(st::normalFont); + p.setPen(status); + p.drawTextLeft(nameleft, statustop, width, _phone); +} + +void HistoryContact::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const { + bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; + + 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; + } + } + if (x >= 0 && y >= 0 && x < _width && y < _height && _contact) { + lnk = _contact->lnk; + return; + } +} + const QString HistoryContact::inDialogsText() const { return lang(lng_in_dlg_contact); } const QString HistoryContact::inHistoryText() const { - return qsl("[ ") + lang(lng_in_dlg_contact) + qsl(" : ") + name.original() + qsl(", ") + phone + qsl(" ]"); + return qsl("[ ") + lang(lng_in_dlg_contact) + qsl(" : ") + _name.original() + qsl(", ") + _phone + qsl(" ]"); } -bool HistoryContact::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const { - if (width < 0) width = w; - return (x >= 0 && y <= 0 && x < w && y < _height); -} - -void HistoryContact::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width) const { - if (width < 0) width = w; - - const HistoryReply *reply = toHistoryReply(parent); - const HistoryForwarded *fwd = reply ? 0 : toHistoryForwarded(parent); - int skipy = 0, replyFrom = 0, fwdFrom = 0; - if (reply) { - skipy = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - } else if (fwd) { - skipy = st::msgServiceNameFont->height; - } - if (parent->displayFromName()) { - replyFrom = st::msgPadding.top() + st::msgNameFont->height; - fwdFrom = st::msgPadding.top() + st::msgNameFont->height; - skipy += replyFrom; - } else if (fwd) { - fwdFrom = st::msgPadding.top(); - skipy += fwdFrom; - } - - if (parent->displayFromName()) { - if (x >= st::mediaPadding.left() && y >= st::msgPadding.top() && x < width - st::mediaPadding.left() - st::mediaPadding.right() && x < st::mediaPadding.left() + parent->from()->nameText.maxWidth() && y < replyFrom) { - lnk = parent->from()->lnk; - return; - } - } - if (reply) { - if (x >= 0 && y >= replyFrom + st::msgReplyPadding.top() && x < width && y < skipy - st::msgReplyPadding.bottom()) { - lnk = reply->replyToLink(); - return; - } - } else if (fwd) { - if (y >= fwdFrom && y < skipy) { - return fwd->getForwardedState(lnk, state, x - st::mediaPadding.left(), width - st::mediaPadding.left() - st::mediaPadding.right()); - } - } - - if (x >= 0 && y >= skipy && x < width && y < _height && contact) { - lnk = contact->lnk; - - bool inDate = parent->pointInTime(width, _height, x, y, InfoDisplayDefault); - if (inDate) { - state = HistoryInDateCursorState; - } - - return; +void HistoryContact::regItem(HistoryItem *item) { + if (_userId) { + App::regSharedContactItem(_userId, item); } } -HistoryMedia *HistoryContact::clone() const { - return new HistoryContact(userId, name.original(), phone); +void HistoryContact::unregItem(HistoryItem *item) { + if (_userId) { + App::unregSharedContactItem(_userId, item); + } } -void HistoryContact::draw(Painter &p, const HistoryItem *parent, bool selected, int32 width) const { - if (width < 0) width = w; - if (width < 1) return; - - const HistoryReply *reply = toHistoryReply(parent); - const HistoryForwarded *fwd = reply ? 0 : toHistoryForwarded(parent); - int skipy = 0, replyFrom = 0, fwdFrom = 0; - if (reply) { - skipy = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - } else if (fwd) { - skipy = st::msgServiceNameFont->height; - } - if (parent->displayFromName()) { - replyFrom = st::msgPadding.top() + st::msgNameFont->height; - fwdFrom = st::msgPadding.top() + st::msgNameFont->height; - skipy += replyFrom; - } else if (fwd) { - fwdFrom = st::msgPadding.top(); - skipy += fwdFrom; - } - - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; - if (width >= _maxw) { - width = _maxw; - } - - style::color bg(selected ? (outbg ? st::msgOutSelectBg : st::msgInSelectBg) : (outbg ? st::msgOutBg : st::msgInBg)); - style::color sh(selected ? (outbg ? st::msgOutSelectShadow : st::msgInSelectShadow) : (outbg ? st::msgOutShadow : st::msgInShadow)); - RoundCorners cors(selected ? (outbg ? MessageOutSelectedCorners : MessageInSelectedCorners) : (outbg ? MessageOutCorners : MessageInCorners)); - App::roundRect(p, 0, 0, width, _height, bg, cors, &sh); - - if (parent->displayFromName()) { - p.setFont(st::msgNameFont->f); - if (fromChannel) { - p.setPen(selected ? st::msgInServiceSelColor : st::msgInServiceColor); - } else { - p.setPen(parent->from()->color); - } - parent->from()->nameText.drawElided(p, st::mediaPadding.left(), st::msgPadding.top(), width - st::mediaPadding.left() - st::mediaPadding.right()); - } - if (reply) { - reply->drawReplyTo(p, st::msgReplyPadding.left(), replyFrom, width - st::msgReplyPadding.left() - st::msgReplyPadding.right(), selected); - } else if (fwd) { - fwd->drawForwardedFrom(p, st::mediaPadding.left(), fwdFrom, width - st::mediaPadding.left() - st::mediaPadding.right(), selected); - } - - p.drawPixmap(st::mediaPadding.left(), skipy + st::mediaPadding.top(), (contact ? contact->photo : userDefPhoto(1))->pixRounded(st::mediaThumbSize)); - if (selected) { - App::roundRect(p, st::mediaPadding.left(), skipy + st::mediaPadding.top(), st::mediaThumbSize, st::mediaThumbSize, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); - } - - int32 tleft = st::mediaPadding.left() + st::mediaThumbSize + st::mediaPadding.right(); - int32 twidth = width - tleft - st::mediaPadding.right(); - int32 secondwidth = width - tleft - st::msgPadding.right() - parent->skipBlockWidth(); - - p.setFont(st::mediaFont->f); - p.setPen(st::black->c); - if (twidth < phonew) { - p.drawText(tleft, skipy + st::mediaPadding.top() + st::mediaNameTop + st::mediaFont->ascent, st::mediaFont->elided(phone, twidth)); - } else { - p.drawText(tleft, skipy + st::mediaPadding.top() + st::mediaNameTop + st::mediaFont->ascent, phone); - } - - style::color status(selected ? (outbg ? st::mediaOutSelectColor : st::mediaInSelectColor) : (outbg ? st::mediaOutColor : st::mediaInColor)); - p.setPen(status->p); - - name.drawElided(p, tleft, skipy + st::mediaPadding.top() + st::mediaThumbSize - st::mediaDetailsShift - st::mediaFont->height, secondwidth); - - int32 fullRight = width, fullBottom = _height; - parent->drawInfo(p, fullRight, fullBottom, selected, InfoDisplayDefault); -} - -void HistoryContact::updateFrom(const MTPMessageMedia &media) { +void HistoryContact::updateFrom(const MTPMessageMedia &media, HistoryItem *parent) { if (media.type() == mtpc_messageMediaContact) { - userId = media.c_messageMediaContact().vuser_id.v; - contact = App::userLoaded(userId); - if (contact) { - if (contact->phone.isEmpty()) { - contact->setPhone(phone); - } - if (contact->contact < 0) { - contact->contact = 0; - } - contact->photo->load(); + if (_userId != media.c_messageMediaContact().vuser_id.v) { + unregItem(parent); + _userId = media.c_messageMediaContact().vuser_id.v; + regItem(parent); } } } +namespace { + QString siteNameFromUrl(const QString &url) { + QUrl u(url); + QString pretty = u.isValid() ? u.toDisplayString() : url; + QRegularExpressionMatch m = QRegularExpression(qsl("^[a-zA-Z0-9]+://")).match(pretty); + if (m.hasMatch()) pretty = pretty.mid(m.capturedLength()); + int32 slash = pretty.indexOf('/'); + if (slash > 0) pretty = pretty.mid(0, slash); + QStringList components = pretty.split('.', QString::SkipEmptyParts); + if (components.size() >= 2) { + components = components.mid(components.size() - 2); + return components.at(0).at(0).toUpper() + components.at(0).mid(1) + '.' + components.at(1); + } + return QString(); + } + + int32 articleThumbWidth(PhotoData *thumb, int32 height) { + int32 w = thumb->medium->width(), h = thumb->medium->height(); + return qMax(qMin(height * w / h, height), 1); + } + + int32 articleThumbHeight(PhotoData *thumb, int32 width) { + return qMax(thumb->medium->height() * width / thumb->medium->width(), 1); + } + + int32 _lineHeight = 0; +} + HistoryWebPage::HistoryWebPage(WebPageData *data) : HistoryMedia() -, data(data) +, _data(data) , _openl(0) -, _attachl(0) +, _attach(0) , _asArticle(false) , _title(st::msgMinWidth - st::webPageLeft) , _description(st::msgMinWidth - st::webPageLeft) , _siteNameWidth(0) , _durationWidth(0) -, _docNameWidth(0) -, _docThumbWidth(0) -, _docDownloadDone(0) -, _pixw(0), _pixh(0) -{ +, _pixw(0) +, _pixh(0) { +} + +HistoryWebPage::HistoryWebPage(const HistoryWebPage &other) : HistoryMedia() +, _data(other._data) +, _openl(0) +, _attach(other._attach ? other._attach->clone() : 0) +, _asArticle(other._asArticle) +, _title(other._title) +, _description(other._description) +, _siteNameWidth(other._siteNameWidth) +, _durationWidth(other._durationWidth) +, _pixw(other._pixw) +, _pixh(other._pixh) { } void HistoryWebPage::initDimensions(const HistoryItem *parent) { - if (data->pendingTill) { + if (_data->pendingTill) { _maxw = _minh = _height = 0; - //_maxw = st::webPageLeft + st::linkFont->width(lang((data->pendingTill < 0) ? lng_attach_failed : lng_profile_loading)); - //_minh = st::replyHeight; - //_height = _minh; return; } - if (!_openl && !data->url.isEmpty()) _openl = TextLinkPtr(new TextLink(data->url)); - if (!_attachl && data->photo && data->type != WebPageVideo) _attachl = TextLinkPtr(new PhotoLink(data->photo)); - if (!_attachl && data->doc) _attachl = TextLinkPtr(new DocumentOpenLink(data->doc)); - if (data->photo && data->type != WebPagePhoto && data->type != WebPageVideo) { - if (data->type == WebPageProfile) { + if (!_lineHeight) _lineHeight = qMax(st::webPageTitleFont->height, st::webPageDescriptionFont->height); + + if (!_openl && !_data->url.isEmpty()) _openl = TextLinkPtr(new TextLink(_data->url)); + + // 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->type == WebPageProfile) { _asArticle = true; - } else if (data->siteName == qstr("Twitter") || data->siteName == qstr("Facebook")) { + } else if (_data->siteName == qstr("Twitter") || _data->siteName == qstr("Facebook")) { _asArticle = false; } else { _asArticle = true; } + if (_asArticle && (_data->description.isEmpty() || (title.isEmpty() && _data->siteName.isEmpty()))) { + _asArticle = false; + } } else { _asArticle = false; } - if (_asArticle) { - w = st::webPagePhotoSize; - _maxw = st::webPageLeft + st::webPagePhotoSize; - _minh = st::webPagePhotoSize; - _minh += st::webPagePhotoSkip + (st::msgDateFont->height - st::msgDateDelta.y()); - } else if (data->photo) { - int32 tw = convertScale(data->photo->full->width()), th = convertScale(data->photo->full->height()); - if (!tw || !th) { - tw = th = 1; - } - if (tw > st::maxMediaSize) { - th = (st::maxMediaSize * th) / tw; - tw = st::maxMediaSize; - } - if (th > st::maxMediaSize) { - tw = (st::maxMediaSize * tw) / th; - th = st::maxMediaSize; - } - int32 thumbw = tw; - int32 thumbh = th; - - w = thumbw; - - _maxw = st::webPageLeft + qMax(thumbh, qMax(w, int32(st::minPhotoSize))) + parent->skipBlockWidth(); - _minh = qMax(thumbh, int32(st::minPhotoSize)); - _minh += st::webPagePhotoSkip; - } else if (data->doc) { - if (!data->doc->thumb->isNull()) { - data->doc->thumb->load(); - - int32 tw = data->doc->thumb->width(), th = data->doc->thumb->height(); - if (data->doc->thumb->isNull() || !tw || !th) { - _docThumbWidth = 0; - } else if (tw > th) { - _docThumbWidth = (tw * st::mediaThumbSize) / th; + // 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 { - _docThumbWidth = st::mediaThumbSize; + _attach = new HistoryDocument(_data->doc, QString(), parent); } + } else if (_data->photo) { + _attach = new HistoryPhoto(_data->photo, QString(), parent); } - _docName = documentName(data->doc); - _docSize = data->doc->song() ? formatDurationAndSizeText(data->doc->song()->duration, data->doc->size) : formatSizeText(data->doc->size); - _docNameWidth = st::mediaFont->width(_docName.isEmpty() ? qsl("Document") : _docName); - - if (parent == animated.msg) { - _maxw = st::webPageLeft + (animated.w / cIntRetinaFactor()) + parent->skipBlockWidth(); - _minh = animated.h / cIntRetinaFactor(); - _minh += st::webPagePhotoSkip; - } else { - _maxw = qMax(st::webPageLeft + st::mediaThumbSize + st::mediaPadding.right() + _docNameWidth + st::mediaPadding.right(), st::mediaMaxWidth); - _minh = st::mediaThumbSize; - } - } else { - _maxw = st::webPageLeft; - _minh = 0; } - if (!data->siteName.isEmpty()) { - _siteNameWidth = st::webPageTitleFont->width(data->siteName); - if (_asArticle) { - _maxw = qMax(_maxw, int32(st::webPageLeft + _siteNameWidth + st::webPagePhotoDelta + st::webPagePhotoSize)); + // init strings + if (_description.isEmpty() && !_data->description.isEmpty()) { + QString text = textClean(_data->description); + if (text.isEmpty()) { + _data->description = QString(); } else { - _maxw = qMax(_maxw, int32(st::webPageLeft + _siteNameWidth + parent->skipBlockWidth())); - _minh += st::webPageTitleFont->height; + if (!_asArticle && !_attach) { + text += parent->skipBlock(); + } + const TextParseOptions *opts = &_webpageDescriptionOptions; + if (_data->siteName == qstr("Twitter")) { + opts = &_twitterDescriptionOptions; + } else if (_data->siteName == qstr("Instagram")) { + opts = &_instagramDescriptionOptions; + } + _description.setText(st::webPageDescriptionFont, text, *opts); } } - QString title(data->title.isEmpty() ? data->author : data->title); - if (!title.isEmpty()) { - title = textClean(title); - if (!_asArticle && !data->photo && data->description.isEmpty()) title += parent->skipBlock(); - _title.setText(st::webPageTitleFont, title, _webpageTitleOptions); - if (_asArticle) { - _maxw = qMax(_maxw, int32(st::webPageLeft + _title.maxWidth() + st::webPagePhotoDelta + st::webPagePhotoSize)); + if (_title.isEmpty() && !title.isEmpty()) { + title = textOneLine(textClean(title)); + if (title.isEmpty()) { + if (_data->title.isEmpty()) { + _data->author = QString(); + } else { + _data->title = QString(); + } } else { - _maxw = qMax(_maxw, int32(st::webPageLeft + _title.maxWidth())); - _minh += qMin(_title.minHeight(), 2 * st::webPageTitleFont->height); + if (!_asArticle && !_attach && _description.isEmpty()) { + title += parent->skipBlock(); + } + _title.setText(st::webPageTitleFont, title, _webpageTitleOptions); } } - if (!data->description.isEmpty()) { - QString text = textClean(data->description); - if (!_asArticle && !data->photo) text += parent->skipBlock(); - const TextParseOptions *opts = &_webpageDescriptionOptions; - if (data->siteName == qstr("Twitter")) { - opts = &_twitterDescriptionOptions; - } else if (data->siteName == qstr("Instagram")) { - opts = &_instagramDescriptionOptions; - } - _description.setText(st::webPageDescriptionFont, text, *opts); - if (_asArticle) { - _maxw = qMax(_maxw, int32(st::webPageLeft + _description.maxWidth() + st::webPagePhotoDelta + st::webPagePhotoSize)); + if (!_siteNameWidth && !_data->siteName.isEmpty()) { + _siteNameWidth = st::webPageTitleFont->width(_data->siteName); + } + + // init dimensions + int32 l = st::msgPadding.left() + st::webPageLeft, r = st::msgPadding.right(); + int32 skipBlockWidth = parent->skipBlockWidth(); + _maxw = skipBlockWidth; + _minh = 0; + + int32 siteNameHeight = _data->siteName.isEmpty() ? 0 : _lineHeight; + int32 titleMinHeight = _title.isEmpty() ? 0 : _lineHeight; + int32 descMaxLines = (3 + (siteNameHeight ? 0 : 1) + (titleMinHeight ? 0 : 1)); + int32 descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * _lineHeight); + int32 articleMinHeight = siteNameHeight + titleMinHeight + descriptionMinHeight; + int32 articlePhotoMaxWidth = 0; + if (_asArticle) { + articlePhotoMaxWidth = st::webPagePhotoDelta + qMax(articleThumbWidth(_data->photo, articleMinHeight), _lineHeight); + } + + if (_siteNameWidth) { + if (_title.isEmpty() && _description.isEmpty()) { + _maxw = qMax(_maxw, int32(_siteNameWidth + parent->skipBlockWidth())); } else { - _maxw = qMax(_maxw, int32(st::webPageLeft + _description.maxWidth())); - _minh += qMin(_description.minHeight(), 3 * st::webPageTitleFont->height); + _maxw = qMax(_maxw, int32(_siteNameWidth + articlePhotoMaxWidth)); } + _minh += _lineHeight; } - if (!_asArticle && (data->photo || data->doc) && (_siteNameWidth || !_title.isEmpty() || !_description.isEmpty())) { - _minh += st::webPagePhotoSkip; + if (!_title.isEmpty()) { + _maxw = qMax(_maxw, int32(_title.maxWidth() + articlePhotoMaxWidth)); + _minh += titleMinHeight; } - if (data->type == WebPageVideo && data->duration) { - _duration = formatDurationText(data->duration); + if (!_description.isEmpty()) { + _maxw = qMax(_maxw, int32(_description.maxWidth() + articlePhotoMaxWidth)); + _minh += descriptionMinHeight; + } + if (_attach) { + if (_minh) _minh += st::webPagePhotoSkip; + _attach->initDimensions(parent); + 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(); + } + if (_data->type == WebPageVideo && _data->duration) { + _duration = formatDurationText(_data->duration); _durationWidth = st::msgDateFont->width(_duration); } - _height = _minh; + _maxw += st::msgPadding.left() + st::webPageLeft + st::msgPadding.right(); + _minh += st::msgPadding.bottom(); + if (_asArticle) { + _minh = resize(_maxw, parent); // hack +// _minh += st::msgDateFont->height; + } } -void HistoryWebPage::draw(Painter &p, const HistoryItem *parent, bool selected, int32 width) const { - if (width < 0) width = w; - if (width < 1 || data->pendingTill) return; - - int16 animw = 0, animh = 0; - if (data->doc && animated.msg == parent) { - animw = animated.w / cIntRetinaFactor(); - animh = animated.h / cIntRetinaFactor(); - if (width - st::webPageLeft < animw) { - animw = width - st::webPageLeft; - animh = (animw * animated.h / animated.w); - if (animh < 1) animh = 1; - } +int32 HistoryWebPage::resize(int32 width, const HistoryItem *parent) { + if (_data->pendingTill) { + _width = width; + _height = _minh; + return _height; } - int32 bottomSkip = 0; - if (data->photo) { - bottomSkip += st::webPagePhotoSkip; - if (_asArticle || (st::webPageLeft + qMax(_pixw, int16(st::minPhotoSize)) + parent->skipBlockWidth() > width)) { - bottomSkip += (st::msgDateFont->height - st::msgDateDelta.y()); + _width = qMin(width, _maxw); + width -= st::msgPadding.left() + st::webPageLeft + st::msgPadding.right(); + + int32 linesMax = 5; + int32 siteNameLines = _siteNameWidth ? 1 : 0, siteNameHeight = _siteNameWidth ? _lineHeight : 0; + if (_asArticle) { + _pixh = linesMax * _lineHeight; + do { + _pixw = articleThumbWidth(_data->photo, _pixh); + int32 wleft = width - st::webPagePhotoDelta - qMax(_pixw, int16(_lineHeight)); + + _height = siteNameHeight; + + if (_title.isEmpty()) { + _titleLines = 0; + } else { + if (_title.countHeight(wleft) < 2 * st::webPageTitleFont->height) { + _titleLines = 1; + } else { + _titleLines = 2; + } + _height += _titleLines * _lineHeight; + } + + int32 descriptionHeight = _description.countHeight(wleft); + if (descriptionHeight < (linesMax - siteNameLines - _titleLines) * st::webPageDescriptionFont->height) { + _descriptionLines = (descriptionHeight / st::webPageDescriptionFont->height); + } else { + _descriptionLines = (linesMax - siteNameLines - _titleLines); + } + _height += _descriptionLines * _lineHeight; + + if (_height >= _pixh) { + break; + } + + _pixh -= _lineHeight; + } while (_pixh > _lineHeight); + _height += st::msgDateFont->height; + } else { + _height = siteNameHeight; + + if (_title.isEmpty()) { + _titleLines = 0; + } else { + if (_title.countHeight(width) < 2 * st::webPageTitleFont->height) { + _titleLines = 1; + } else { + _titleLines = 2; + } + _height += _titleLines * _lineHeight; } - } else if (data->doc && animated.msg == parent) { - bottomSkip += st::webPagePhotoSkip; - if (st::webPageLeft + qMax(animw, int16(st::minPhotoSize)) + parent->skipBlockWidth() > width) { - bottomSkip += (st::msgDateFont->height - st::msgDateDelta.y()); + + if (_description.isEmpty()) { + _descriptionLines = 0; + } else { + int32 descriptionHeight = _description.countHeight(width); + if (descriptionHeight < (linesMax - siteNameLines - _titleLines) * st::webPageDescriptionFont->height) { + _descriptionLines = (descriptionHeight / st::webPageDescriptionFont->height); + } else { + _descriptionLines = (linesMax - siteNameLines - _titleLines); + } + _height += _descriptionLines * _lineHeight; + } + + if (_attach) { + if (_height) _height += st::webPagePhotoSkip; + + QMargins bubble(_attach->bubbleMargins()); + + _attach->resize(width + bubble.left() + bubble.right(), parent); + _height += _attach->height() - bubble.top() - bubble.bottom(); + if (_attach->customInfoLayout() && _attach->currentWidth() + parent->skipBlockWidth() > width + bubble.left() + bubble.right()) { + _height += st::msgDateFont->height; + } } } + _height += st::msgPadding.bottom(); + + return _height; +} + +void HistoryWebPage::draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const { + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; + int32 skipx = 0, skipy = 0, width = _width, height = _height; bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; - style::color bar = (selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor)); - style::color semibold = (selected ? (outbg ? st::msgOutServiceSelColor : st::msgInServiceSelColor) : (outbg ? st::msgOutServiceColor : st::msgInServiceColor)); - style::color regular = (selected ? (outbg ? st::msgOutSelectDateColor : st::msgInSelectDateColor) : (outbg ? st::msgOutDateColor : st::msgInDateColor)); - p.fillRect(0, 0, st::webPageBar, _height - bottomSkip, bar->b); + 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)); + style::color regular = (selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg)); - p.save(); - p.translate(st::webPageLeft, 0); + 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())) { + bshift += st::msgDateFont->height; + } - width -= st::webPageLeft; + QRect bar(rtlrect(st::msgPadding.left(), 0, st::webPageBar, _height - bshift, _width)); + p.fillRect(bar, barfg); if (_asArticle) { - int32 pixwidth = st::webPagePhotoSize, pixheight = st::webPagePhotoSize; - data->photo->medium->load(false, false); - bool full = data->photo->medium->loaded(); + _data->photo->medium->load(false, false); + bool full = _data->photo->medium->loaded(); QPixmap pix; + int32 pw = qMax(_pixw, int16(_lineHeight)), ph = _pixh; + int32 pixw = _pixw, pixh = articleThumbHeight(_data->photo, _pixw); + int32 maxw = convertScale(_data->photo->medium->width()), maxh = convertScale(_data->photo->medium->height()); + if (pixw * ph != pixh * pw) { + float64 coef = (pixw * ph > pixh * pw) ? qMin(ph / float64(pixh), maxh / float64(pixh)) : qMin(pw / float64(pixw), maxw / float64(pixw)); + pixh = qRound(pixh * coef); + pixw = qRound(pixw * coef); + } if (full) { - pix = data->photo->medium->pixSingle(_pixw, _pixh, pixwidth, pixheight); + pix = _data->photo->medium->pixSingle(pixw, pixh, pw, ph); } else { - pix = data->photo->thumb->pixBlurredSingle(_pixw, _pixh, pixwidth, pixheight); + pix = _data->photo->thumb->pixBlurredSingle(pixw, pixh, pw, ph); } - p.drawPixmap(width - pixwidth, 0, pix); + p.drawPixmapLeft(lshift + width - pw, 0, _width, pix); if (selected) { - App::roundRect(p, width - pixwidth, 0, pixwidth, pixheight, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); + App::roundRect(p, rtlrect(lshift + width - pw, 0, pw, _pixh, _width), textstyleCurrent()->selectOverlay, SelectedOverlayCorners); } + width -= pw + st::webPagePhotoDelta; } - int32 articleLines = 5; + int32 tshift = 0; if (_siteNameWidth) { - int32 availw = width; - if (_asArticle) { - availw -= st::webPagePhotoSize + st::webPagePhotoDelta; - } else if (_title.isEmpty() && _description.isEmpty() && !data->photo) { - availw -= parent->skipBlockWidth(); - } - p.setFont(st::webPageTitleFont->f); - p.setPen(semibold->p); - p.drawText(0, st::webPageTitleFont->ascent, (availw >= _siteNameWidth) ? data->siteName : st::webPageTitleFont->elided(data->siteName, availw)); - p.translate(0, st::webPageTitleFont->height); - --articleLines; + p.setFont(st::webPageTitleFont); + p.setPen(semibold); + p.drawTextLeft(lshift, tshift, _width, (width >= _siteNameWidth) ? _data->siteName : st::webPageTitleFont->elided(_data->siteName, width)); + tshift += _lineHeight; } - if (!_title.isEmpty()) { - p.setPen(st::black->p); - int32 availw = width, endskip = 0; - if (_asArticle) { - availw -= st::webPagePhotoSize + st::webPagePhotoDelta; - } else if (_description.isEmpty() && !data->photo) { + if (_titleLines) { + p.setPen(st::black); + int32 endskip = 0; + if (_title.hasSkipBlock()) { endskip = parent->skipBlockWidth(); } - _title.drawElided(p, 0, 0, availw, 2, style::al_left, 0, -1, endskip); - int32 h = _title.countHeight(availw); - if (h > st::webPageTitleFont->height) { - articleLines -= 2; - p.translate(0, st::webPageTitleFont->height * 2); - } else { - --articleLines; - p.translate(0, h); - } + _title.drawLeftElided(p, lshift, tshift, width, _width, _titleLines, style::al_left, 0, -1, endskip); + tshift += _titleLines * _lineHeight; } - if (!_description.isEmpty()) { - p.setPen(st::black->p); - int32 availw = width, endskip = 0; - if (_asArticle) { - availw -= st::webPagePhotoSize + st::webPagePhotoDelta; - if (articleLines > 3) articleLines = 3; - } else { - if (!data->photo) endskip = parent->skipBlockWidth(); - articleLines = 3; + if (_descriptionLines) { + p.setPen(st::black); + int32 endskip = 0; + if (_description.hasSkipBlock()) { + endskip = parent->skipBlockWidth(); } - _description.drawElided(p, 0, 0, availw, articleLines, style::al_left, 0, -1, endskip); - p.translate(0, qMin(_description.countHeight(availw), st::webPageDescriptionFont->height * articleLines)); + _description.drawLeftElided(p, lshift, tshift, width, _width, _descriptionLines, style::al_left, 0, -1, endskip); + tshift += _descriptionLines * _lineHeight; } - if (!_asArticle && data->photo) { - if (_siteNameWidth || !_title.isEmpty() || !_description.isEmpty()) { - p.translate(0, st::webPagePhotoSkip); - } + if (_attach) { + if (tshift) tshift += st::webPagePhotoSkip; - int32 pixwidth = qMax(_pixw, int16(st::minPhotoSize)), pixheight = qMax(_pixh, int16(st::minPhotoSize)); - data->photo->full->load(false, false); - bool full = data->photo->full->loaded(); - QPixmap pix; - if (full) { - pix = data->photo->full->pixSingle(_pixw, _pixh, pixwidth, pixheight); - } else { - pix = data->photo->thumb->pixBlurredSingle(_pixw, _pixh, pixwidth, pixheight); - } - p.drawPixmap(0, 0, pix); - if (!full) { - uint64 dt = itemAnimations().animate(parent, getms()); - int32 cnt = int32(st::photoLoaderCnt), period = int32(st::photoLoaderPeriod), t = dt % period, delta = int32(st::photoLoaderDelta); + int32 attachLeft = lshift - bubble.left(), attachTop = tshift - bubble.top(); + if (rtl()) attachLeft = _width - attachLeft - _attach->currentWidth(); - int32 x = (pixwidth - st::photoLoader.width()) / 2, y = (pixheight - st::photoLoader.height()) / 2; - p.fillRect(x, y, st::photoLoader.width(), st::photoLoader.height(), st::photoLoaderBg->b); - x += (st::photoLoader.width() - cnt * st::photoLoaderPoint.width() - (cnt - 1) * st::photoLoaderSkip) / 2; - y += (st::photoLoader.height() - st::photoLoaderPoint.height()) / 2; - QColor c(st::white->c); - QBrush b(c); - for (int32 i = 0; i < cnt; ++i) { - t -= delta; - while (t < 0) t += period; + p.save(); + p.translate(attachLeft, attachTop); - float64 alpha = (t >= st::photoLoaderDuration1 + st::photoLoaderDuration2) ? 0 : ((t > st::photoLoaderDuration1 ? ((st::photoLoaderDuration1 + st::photoLoaderDuration2 - t) / st::photoLoaderDuration2) : (t / st::photoLoaderDuration1))); - c.setAlphaF(st::photoLoaderAlphaMin + alpha * (1 - st::photoLoaderAlphaMin)); - b.setColor(c); - p.fillRect(x + i * (st::photoLoaderPoint.width() + st::photoLoaderSkip), y, st::photoLoaderPoint.width(), st::photoLoaderPoint.height(), b); - } - } + _attach->draw(p, parent, r.translated(-attachLeft, -attachTop), selected, ms); + int32 pixwidth = _attach->currentWidth(), pixheight = _attach->height(); - if (selected) { - App::roundRect(p, 0, 0, pixwidth, pixheight, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); - } - - if (data->type == WebPageVideo) { - if (data->siteName == qstr("YouTube")) { + if (_data->type == WebPageVideo) { + if (_data->siteName == qstr("YouTube")) { p.drawPixmap(QPoint((pixwidth - st::youtubeIcon.pxWidth()) / 2, (pixheight - st::youtubeIcon.pxHeight()) / 2), App::sprite(), st::youtubeIcon); } else { p.drawPixmap(QPoint((pixwidth - st::videoIcon.pxWidth()) / 2, (pixheight - st::videoIcon.pxHeight()) / 2), App::sprite(), st::videoIcon); @@ -5336,216 +5430,93 @@ void HistoryWebPage::draw(Painter &p, const HistoryItem *parent, bool selected, int32 dateW = pixwidth - dateX - st::msgDateImgDelta; int32 dateH = pixheight - dateY - st::msgDateImgDelta; - App::roundRect(p, dateX, dateY, dateW, dateH, selected ? st::msgDateImgSelectBg : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); + App::roundRect(p, dateX, dateY, dateW, dateH, selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); - p.setFont(st::msgDateFont->f); - p.setPen(st::msgDateImgColor->p); - p.drawText(dateX + st::msgDateImgPadding.x(), dateY + st::msgDateImgPadding.y() + st::msgDateFont->ascent, _duration); + p.setFont(st::msgDateFont); + p.setPen(st::msgDateImgColor); + p.drawTextLeft(dateX + st::msgDateImgPadding.x(), dateY + st::msgDateImgPadding.y(), pixwidth, _duration); } } - p.translate(0, pixheight); - } else if (!_asArticle && data->doc) { - if (_siteNameWidth || !_title.isEmpty() || !_description.isEmpty()) { - p.translate(0, st::webPagePhotoSkip); - } - - if (parent == animated.msg) { - p.drawPixmap(0, 0, animated.current(animw * cIntRetinaFactor(), animh * cIntRetinaFactor(), true)); - if (selected) { - App::roundRect(p, 0, 0, animw, animh, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); - } - } else { - QString statusText; - if (data->doc->song()) { - SongMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - int64 playingPosition = 0, playingDuration = 0; - int32 playingFrequency = 0; - if (audioPlayer()) { - audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); - } - - bool already = !data->doc->already().isEmpty(), hasdata = !data->doc->data.isEmpty(); - QRect img; - if (data->doc->status == FileFailed) { - statusText = lang(lng_attach_failed); - img = outbg ? st::mediaMusicOutImg : st::mediaMusicInImg; - } else if (already || hasdata) { - bool showPause = false; - if (playing.msgId == parent->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { - statusText = formatDurationText(playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)) + qsl(" / ") + formatDurationText(playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)); - showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); - } else { - statusText = formatDurationText(data->doc->song()->duration); - } - if (!showPause && playing.msgId == parent->fullId() && App::main() && App::main()->player()->seekingSong(playing)) showPause = true; - img = outbg ? (showPause ? st::mediaPauseOutImg : st::mediaPlayOutImg) : (showPause ? st::mediaPauseInImg : st::mediaPlayInImg); - } else { - if (data->doc->loader) { - int32 offset = data->doc->loader->currentOffset(); - if (_docDownloadTextCache.isEmpty() || _docDownloadDone != offset) { - _docDownloadDone = offset; - _docDownloadTextCache = formatDownloadText(_docDownloadDone, data->doc->size); - } - statusText = _docDownloadTextCache; - } else { - statusText = _docSize; - } - img = outbg ? st::mediaMusicOutImg : st::mediaMusicInImg; - } - - p.drawPixmap(QPoint(0, 0), App::sprite(), img); - } else { - if (data->doc->status == FileFailed) { - statusText = lang(lng_attach_failed); - } else if (data->doc->loader) { - int32 offset = data->doc->loader->currentOffset(); - if (_docDownloadTextCache.isEmpty() || _docDownloadDone != offset) { - _docDownloadDone = offset; - _docDownloadTextCache = formatDownloadText(_docDownloadDone, data->doc->size); - } - statusText = _docDownloadTextCache; - } else { - statusText = _docSize; - } - - if (_docThumbWidth) { - data->doc->thumb->checkload(); - p.drawPixmap(QPoint(0, 0), data->doc->thumb->pixSingle(_docThumbWidth, 0, st::mediaThumbSize, st::mediaThumbSize)); - } else { - p.drawPixmap(QPoint(0, 0), App::sprite(), (outbg ? st::mediaDocOutImg : st::mediaDocInImg)); - } - } - if (selected) { - App::roundRect(p, 0, 0, st::mediaThumbSize, st::mediaThumbSize, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); - } - - int32 tleft = st::mediaPadding.left() + st::mediaThumbSize + st::mediaPadding.right(); - int32 twidth = width - tleft - st::mediaPadding.right(); - int32 secondwidth = width - tleft - st::msgPadding.right() - parent->skipBlockWidth(); - - p.setFont(st::mediaFont->f); - p.setPen(st::black->c); - if (twidth < _docNameWidth) { - p.drawText(tleft, st::mediaNameTop + st::mediaFont->ascent, st::mediaFont->elided(_docName, twidth)); - } else { - p.drawText(tleft, st::mediaNameTop + st::mediaFont->ascent, _docName); - } - - style::color status(selected ? (outbg ? st::mediaOutSelectColor : st::mediaInSelectColor) : (outbg ? st::mediaOutColor : st::mediaInColor)); - p.setPen(status->p); - - p.drawText(tleft, st::mediaThumbSize - st::mediaDetailsShift - st::mediaFont->descent, statusText); - } + p.restore(); } - - p.restore(); } -int32 HistoryWebPage::resize(int32 width, const HistoryItem *parent) { - if (data->pendingTill) { - w = width; - _height = _minh; - return _height; +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; + 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())) { + bshift += st::msgDateFont->height; } - w = width; - width -= st::webPageLeft; if (_asArticle) { - int32 tw = convertScale(data->photo->medium->width()), th = convertScale(data->photo->medium->height()); - if (tw > st::webPagePhotoSize) { - if (th > tw) { - th = th * st::webPagePhotoSize / tw; - tw = st::webPagePhotoSize; - } else if (th > st::webPagePhotoSize) { - tw = tw * st::webPagePhotoSize / th; - th = st::webPagePhotoSize; - } + int32 pw = qMax(_pixw, int16(_lineHeight)); + if (rtlrect(lshift + width - pw, 0, pw, _pixh, _width).contains(x, y)) { + lnk = _openl; + return; } - _pixw = tw; - _pixh = th; - if (_pixw < 1) _pixw = 1; - if (_pixh < 1) _pixh = 1; - _height = st::webPagePhotoSize; - _height += st::webPagePhotoSkip + (st::msgDateFont->height - st::msgDateDelta.y()); - } else if (data->photo) { - _pixw = qMin(width, int32(_maxw - st::webPageLeft - parent->skipBlockWidth())); - - int32 tw = convertScale(data->photo->full->width()), th = convertScale(data->photo->full->height()); - if (tw > st::maxMediaSize) { - th = (st::maxMediaSize * th) / tw; - tw = st::maxMediaSize; - } - if (th > st::maxMediaSize) { - tw = (st::maxMediaSize * tw) / th; - th = st::maxMediaSize; - } - _pixh = th; - if (tw > _pixw) { - _pixh = (_pixw * _pixh / tw); - } else { - _pixw = tw; - } - if (_pixh > width) { - _pixw = (_pixw * width) / _pixh; - _pixh = width; - } - if (_pixw < 1) _pixw = 1; - if (_pixh < 1) _pixh = 1; - _height = qMax(_pixh, int16(st::minPhotoSize)); - _height += st::webPagePhotoSkip; - if (qMax(_pixw, int16(st::minPhotoSize)) + parent->skipBlockWidth() > width) { - _height += (st::msgDateFont->height - st::msgDateDelta.y()); - } - } else if (data->doc) { - if (parent == animated.msg) { - int32 w = qMin(width, int32(animated.w / cIntRetinaFactor())); - if (w > st::maxMediaSize) { - w = st::maxMediaSize; - } - _height = animated.h / cIntRetinaFactor(); - if (animated.w / cIntRetinaFactor() > w) { - _height = (w * _height / (animated.w / cIntRetinaFactor())); - if (_height <= 0) _height = 1; - } - _height += st::webPagePhotoSkip; - if (w + parent->skipBlockWidth() > width) { - _height += (st::msgDateFont->height - st::msgDateDelta.y()); - } - } else { - _height = st::mediaThumbSize; - } - } else { - _height = 0; + width -= pw + st::webPagePhotoDelta; } + int32 tshift = 0; + if (_siteNameWidth) { + tshift += _lineHeight; + } + if (_titleLines) { + 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; + } + tshift += _descriptionLines * _lineHeight; + } + if (_attach) { + if (tshift) tshift += st::webPagePhotoSkip; - if (!_asArticle) { - if (!data->siteName.isEmpty()) { - _height += st::webPageTitleFont->height; - } - if (!_title.isEmpty()) { - _height += qMin(_title.countHeight(width), st::webPageTitleFont->height * 2); - } - if (!_description.isEmpty()) { - _height += qMin(_description.countHeight(width), st::webPageDescriptionFont->height * 3); - } - if ((data->photo || data->doc) && (_siteNameWidth || !_title.isEmpty() || !_description.isEmpty())) { - _height += 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) { + if (_data->type == WebPageProfile || _data->type == WebPageVideo) { + lnk = _openl; + } else if (_data->type == WebPagePhoto || _data->siteName == qstr("Twitter") || _data->siteName == qstr("Facebook")) { + // leave photo link + } else { + lnk = _openl; + } + } } } +} - return _height; +void HistoryWebPage::linkOver(HistoryItem *parent, const TextLinkPtr &lnk) { + if (_attach) { + _attach->linkOver(parent, lnk); + } +} + +void HistoryWebPage::linkOut(HistoryItem *parent, const TextLinkPtr &lnk) { + if (_attach) { + _attach->linkOut(parent, lnk); + } } void HistoryWebPage::regItem(HistoryItem *item) { - App::regWebPageItem(data, item); - if (data->doc) App::regDocumentItem(data->doc, item); + App::regWebPageItem(_data, item); + if (_attach) _attach->regItem(item); } void HistoryWebPage::unregItem(HistoryItem *item) { - App::unregWebPageItem(data, item); - if (data->doc) App::unregDocumentItem(data->doc, item); + App::unregWebPageItem(_data, item); + if (_attach) _attach->unregItem(item); } const QString HistoryWebPage::inDialogsText() const { @@ -5556,99 +5527,15 @@ const QString HistoryWebPage::inHistoryText() const { return QString(); } -bool HistoryWebPage::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const { - if (width < 0) width = w; - if (width >= _maxw) width = _maxw; - - return (x >= 0 && y >= 0 && x < width && y < _height); -} - -void HistoryWebPage::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width) const { - if (width < 0) width = w; - if (width < 1) return; - - width -= st::webPageLeft; - x -= st::webPageLeft; - - if (_asArticle) { - int32 pixwidth = st::webPagePhotoSize, pixheight = st::webPagePhotoSize; - if (x >= width - pixwidth && x < width && y >= 0 && y < pixheight) { - lnk = _openl; - return; - } - } - int32 articleLines = 5; - if (_siteNameWidth) { - y -= st::webPageTitleFont->height; - --articleLines; - } - if (!_title.isEmpty()) { - int32 availw = width; - if (_asArticle) { - availw -= st::webPagePhotoSize + st::webPagePhotoDelta; - } - int32 h = _title.countHeight(availw); - if (h > st::webPageTitleFont->height) { - articleLines -= 2; - y -= st::webPageTitleFont->height * 2; - } else { - --articleLines; - y -= h; - } - } - if (!_description.isEmpty()) { - int32 availw = width; - if (_asArticle) { - availw -= st::webPagePhotoSize + st::webPagePhotoDelta; - if (articleLines > 3) articleLines = 3; - } else if (!data->photo) { - articleLines = 3; - } - int32 desch = qMin(_description.countHeight(width), st::webPageDescriptionFont->height * articleLines); - if (y >= 0 && y < desch) { - bool inText = false; - _description.getState(lnk, inText, x, y, availw); - state = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; - return; - } - y -= desch; - } - if (_siteNameWidth || !_title.isEmpty() || !_description.isEmpty()) { - y -= st::webPagePhotoSkip; - } - if (!_asArticle) { - if (data->doc && parent == animated.msg) { - int32 h = (width == w) ? _height : (width * animated.h / animated.w); - if (h < 1) h = 1; - if (x >= 0 && y >= 0 && x < width && y < h) { - lnk = _attachl; - return; - } - } else { - int32 attachwidth = data->doc ? (width - st::mediaPadding.right()) : qMax(_pixw, int16(st::minPhotoSize)); - int32 attachheight = data->doc ? st::mediaThumbSize : qMax(_pixh, int16(st::minPhotoSize)); - if (x >= 0 && y >= 0 && x < attachwidth && y < attachheight) { - lnk = _attachl ? _attachl : _openl; - return; - } - } - } -} - -HistoryMedia *HistoryWebPage::clone() const { - return new HistoryWebPage(*this); -} - ImagePtr HistoryWebPage::replyPreview() { - return data->photo ? data->photo->makeReplyPreview() : (data->doc ? data->doc->makeReplyPreview() : ImagePtr()); + return _attach ? _attach->replyPreview() : (_data->photo ? _data->photo->makeReplyPreview() : ImagePtr()); +} + +HistoryWebPage::~HistoryWebPage() { + deleteAndMark(_attach); } namespace { - QRegularExpression reYouTube1(qsl("^(https?://)?(www\\.|m\\.)?youtube\\.com/watch\\?([^#]+&)?v=([a-z0-9_-]+)(&[^\\s]*)?$"), QRegularExpression::CaseInsensitiveOption); - QRegularExpression reYouTube2(qsl("^(https?://)?(www\\.)?youtu\\.be/([a-z0-9_-]+)([/\\?#][^\\s]*)?$"), QRegularExpression::CaseInsensitiveOption); - QRegularExpression reInstagram(qsl("^(https?://)?(www\\.)?instagram\\.com/p/([a-z0-9_-]+)([/\\?][^\\s]*)?$"), QRegularExpression::CaseInsensitiveOption); - QRegularExpression reVimeo(qsl("^(https?://)?(www\\.)?vimeo\\.com/([0-9]+)([/\\?][^\\s]*)?$"), QRegularExpression::CaseInsensitiveOption); - ImageLinkManager manager; } @@ -5708,38 +5595,22 @@ void ImageLinkManager::getData(ImageLinkData *data) { } QString url; switch (data->type) { - case YouTubeLink: { - url = qsl("https://gdata.youtube.com/feeds/api/videos/") + data->id.mid(8) + qsl("?v=2&alt=json"); - QNetworkReply *reply = manager->get(QNetworkRequest(QUrl(url))); - dataLoadings[reply] = data; - } break; - case VimeoLink: { - url = qsl("https://vimeo.com/api/v2/video/") + data->id.mid(6) + qsl(".json"); - QNetworkReply *reply = manager->get(QNetworkRequest(QUrl(url))); - dataLoadings[reply] = data; - } break; - case InstagramLink: { - //url = qsl("https://api.instagram.com/oembed?url=http://instagr.am/p/") + data->id.mid(10) + '/'; - url = qsl("https://instagram.com/p/") + data->id.mid(10) + qsl("/media/?size=l"); - QNetworkReply *reply = manager->get(QNetworkRequest(QUrl(url))); - imageLoadings[reply] = data; - } break; - case GoogleMapsLink: { - int32 w = st::locationSize.width(), h = st::locationSize.height(); - int32 zoom = 13, scale = 1; - if (cScale() == dbisTwo || cRetina()) { - scale = 2; - } else { - w = convertScale(w); - h = convertScale(h); - } - url = qsl("https://maps.googleapis.com/maps/api/staticmap?center=") + data->id.mid(9) + qsl("&zoom=%1&size=%2x%3&maptype=roadmap&scale=%4&markers=color:red|size:big|").arg(zoom).arg(w).arg(h).arg(scale) + data->id.mid(9) + qsl("&sensor=false"); - QNetworkReply *reply = manager->get(QNetworkRequest(QUrl(url))); - imageLoadings[reply] = data; - } break; - default: { - failed(data); - } break; + case GoogleMapsLink: { + int32 w = st::locationSize.width(), h = st::locationSize.height(); + int32 zoom = 13, scale = 1; + if (cScale() == dbisTwo || cRetina()) { + scale = 2; + } else { + w = convertScale(w); + h = convertScale(h); + } + url = qsl("https://maps.googleapis.com/maps/api/staticmap?center=") + data->id.mid(9) + qsl("&zoom=%1&size=%2x%3&maptype=roadmap&scale=%4&markers=color:red|size:big|").arg(zoom).arg(w).arg(h).arg(scale) + data->id.mid(9) + qsl("&sensor=false"); + QNetworkReply *reply = manager->get(QNetworkRequest(QUrl(url))); + imageLoadings[reply] = data; + } break; + default: { + failed(data); + } break; } } @@ -5798,139 +5669,7 @@ void ImageLinkManager::onFinished(QNetworkReply *reply) { return onFailed(reply); } switch (d->type) { - case YouTubeLink: { - QJsonObject obj = doc.object(); - QString thumb; - int32 seconds = 0; - QJsonObject::const_iterator entryIt = obj.constFind(qsl("entry")); - if (entryIt != obj.constEnd() && entryIt.value().isObject()) { - QJsonObject entry = entryIt.value().toObject(); - QJsonObject::const_iterator mediaIt = entry.constFind(qsl("media$group")); - if (mediaIt != entry.constEnd() && mediaIt.value().isObject()) { - QJsonObject media = mediaIt.value().toObject(); - - // title from media - QJsonObject::const_iterator titleIt = media.constFind(qsl("media$title")); - if (titleIt != media.constEnd() && titleIt.value().isObject()) { - QJsonObject title = titleIt.value().toObject(); - QJsonObject::const_iterator tIt = title.constFind(qsl("$t")); - if (tIt != title.constEnd() && tIt.value().isString()) { - d->title = tIt.value().toString(); - } - } - - // thumb - QJsonObject::const_iterator thumbnailsIt = media.constFind(qsl("media$thumbnail")); - int32 bestLevel = 0; - if (thumbnailsIt != media.constEnd() && thumbnailsIt.value().isArray()) { - QJsonArray thumbnails = thumbnailsIt.value().toArray(); - for (int32 i = 0, l = thumbnails.size(); i < l; ++i) { - QJsonValue thumbnailVal = thumbnails.at(i); - if (!thumbnailVal.isObject()) continue; - - QJsonObject thumbnail = thumbnailVal.toObject(); - QJsonObject::const_iterator urlIt = thumbnail.constFind(qsl("url")); - if (urlIt == thumbnail.constEnd() || !urlIt.value().isString()) continue; - - int32 level = 0; - if (thumbnail.constFind(qsl("time")) == thumbnail.constEnd()) { - level += 10; - } - QJsonObject::const_iterator wIt = thumbnail.constFind(qsl("width")); - if (wIt != thumbnail.constEnd()) { - int32 w = 0; - if (wIt.value().isDouble()) { - w = qMax(qRound(wIt.value().toDouble()), 0); - } else if (wIt.value().isString()) { - w = qMax(qRound(wIt.value().toString().toDouble()), 0); - } - switch (w) { - case 640: level += 4; break; - case 480: level += 3; break; - case 320: level += 2; break; - case 120: level += 1; break; - } - } - if (level > bestLevel) { - thumb = urlIt.value().toString(); - bestLevel = level; - } - } - } - - // duration - QJsonObject::const_iterator durationIt = media.constFind(qsl("yt$duration")); - if (durationIt != media.constEnd() && durationIt.value().isObject()) { - QJsonObject duration = durationIt.value().toObject(); - QJsonObject::const_iterator secondsIt = duration.constFind(qsl("seconds")); - if (secondsIt != duration.constEnd()) { - if (secondsIt.value().isDouble()) { - seconds = qRound(secondsIt.value().toDouble()); - } else if (secondsIt.value().isString()) { - seconds = qRound(secondsIt.value().toString().toDouble()); - } - } - } - } - - // title field - if (d->title.isEmpty()) { - QJsonObject::const_iterator titleIt = entry.constFind(qsl("title")); - if (titleIt != entry.constEnd() && titleIt.value().isObject()) { - QJsonObject title = titleIt.value().toObject(); - QJsonObject::const_iterator tIt = title.constFind(qsl("$t")); - if (tIt != title.constEnd() && tIt.value().isString()) { - d->title = tIt.value().toString(); - } - } - } - } - - if (seconds > 0) { - d->duration = formatDurationText(seconds); - } - if (thumb.isEmpty()) { - failed(d); - } else { - imageLoadings.insert(manager->get(QNetworkRequest(thumb)), d); - } - } break; - - case VimeoLink: { - QString thumb; - int32 seconds = 0; - QJsonArray arr = doc.array(); - if (!arr.isEmpty()) { - QJsonObject obj = arr.at(0).toObject(); - QJsonObject::const_iterator titleIt = obj.constFind(qsl("title")); - if (titleIt != obj.constEnd() && titleIt.value().isString()) { - d->title = titleIt.value().toString(); - } - QJsonObject::const_iterator thumbnailsIt = obj.constFind(qsl("thumbnail_large")); - if (thumbnailsIt != obj.constEnd() && thumbnailsIt.value().isString()) { - thumb = thumbnailsIt.value().toString(); - } - QJsonObject::const_iterator secondsIt = obj.constFind(qsl("duration")); - if (secondsIt != obj.constEnd()) { - if (secondsIt.value().isDouble()) { - seconds = qRound(secondsIt.value().toDouble()); - } else if (secondsIt.value().isString()) { - seconds = qRound(secondsIt.value().toString().toDouble()); - } - } - } - if (seconds > 0) { - d->duration = formatDurationText(seconds); - } - if (thumb.isEmpty()) { - failed(d); - } else { - imageLoadings.insert(manager->get(QNetworkRequest(thumb)), d); - } - } break; - - case InstagramLink: failed(d); break; - case GoogleMapsLink: failed(d); break; + case GoogleMapsLink: failed(d); break; } if (App::main()) App::main()->update(); @@ -5946,6 +5685,9 @@ void ImageLinkManager::onFinished(QNetworkReply *reply) { { QBuffer buffer(&data); QImageReader reader(&buffer); +#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) + reader.setAutoTransform(true); +#endif thumb = QPixmap::fromImageReader(&reader, Qt::ColorOnly); format = reader.format(); thumb.setDevicePixelRatio(cRetinaFactor()); @@ -6006,350 +5748,181 @@ _description(st::msgMinWidth) { if (url.startsWith(qsl("location:"))) { QString lnk = qsl("https://maps.google.com/maps?q=") + url.mid(9) + qsl("&ll=") + url.mid(9) + qsl("&z=17"); - link.reset(new TextLink(lnk)); + _link.reset(new TextLink(lnk)); - data = App::imageLinkSet(url, GoogleMapsLink, lnk); + _data = App::imageLinkSet(url, GoogleMapsLink, lnk); } else { - link.reset(new TextLink(url)); - - int matchIndex = 4; - QRegularExpressionMatch m = reYouTube1.match(url); - if (!m.hasMatch()) { - m = reYouTube2.match(url); - matchIndex = 3; - } - if (m.hasMatch()) { - data = App::imageLinkSet(qsl("youtube:") + m.captured(matchIndex), YouTubeLink, url); - } else { - m = reVimeo.match(url); - if (m.hasMatch()) { - data = App::imageLinkSet(qsl("vimeo:") + m.captured(3), VimeoLink, url); - } else { - m = reInstagram.match(url); - if (m.hasMatch()) { - data = App::imageLinkSet(qsl("instagram:") + m.captured(3), InstagramLink, url); - data->title = qsl("instagram.com/p/") + m.captured(3); - } else { - data = 0; - } - } - } + _link.reset(new TextLink(url)); } } -int32 HistoryImageLink::fullWidth() const { - if (data) { - switch (data->type) { - case YouTubeLink: return 640; - case VimeoLink: return 640; - case InstagramLink: return 640; - case GoogleMapsLink: return st::locationSize.width(); - } - } - return st::minPhotoSize; -} - -int32 HistoryImageLink::fullHeight() const { - if (data) { - switch (data->type) { - case YouTubeLink: return 480; - case VimeoLink: return 480; - case InstagramLink: return 640; - case GoogleMapsLink: return st::locationSize.height(); - } - } - return st::minPhotoSize; -} - void HistoryImageLink::initDimensions(const HistoryItem *parent) { - int32 tw = convertScale(fullWidth()), th = convertScale(fullHeight()); - int32 thumbw = qMax(tw, int32(st::minPhotoSize)), maxthumbh = thumbw; - int32 thumbh = qRound(th * float64(thumbw) / tw); - if (thumbh > maxthumbh) { - thumbw = qRound(thumbw * float64(maxthumbh) / thumbh); - thumbh = maxthumbh; - if (thumbw < st::minPhotoSize) { - thumbw = st::minPhotoSize; - } + bool bubble = parent->hasBubble(); + + int32 tw = fullWidth(), th = fullHeight(); + if (tw > st::maxMediaSize) { + th = (st::maxMediaSize * th) / tw; + tw = st::maxMediaSize; } - if (thumbh < st::minPhotoSize) { - thumbh = st::minPhotoSize; - } - if (!w) { - w = thumbw; - } - _maxw = w; - _minh = thumbh; - const HistoryReply *reply = toHistoryReply(parent); - const HistoryForwarded *fwd = toHistoryForwarded(parent); - if (reply || !_title.isEmpty() || !_description.isEmpty()) { + int32 minWidth = qMax(st::minPhotoSize, parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + _maxw = qMax(tw, int32(minWidth)); + _minh = qMax(th, int32(st::minPhotoSize)); + + if (bubble) { _maxw += st::mediaPadding.left() + st::mediaPadding.right(); - if (reply) { - _minh += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - } else { - if (!parent->displayFromName() || !fwd) { - _minh += st::msgPadding.top(); - } - if (fwd) { - _minh += st::msgServiceNameFont->height + st::msgPadding.top(); - } - } - if (parent->displayFromName()) { - _minh += st::msgPadding.top() + st::msgNameFont->height; - } if (!_title.isEmpty()) { - _maxw = qMax(_maxw, int32(st::webPageLeft + _title.maxWidth())); - _minh += qMin(_title.minHeight(), 2 * st::webPageTitleFont->height); + _minh += qMin(_title.countHeight(_maxw - st::msgPadding.left() - st::msgPadding.right()), 2 * st::webPageTitleFont->height); } if (!_description.isEmpty()) { - _maxw = qMax(_maxw, int32(st::webPageLeft + _description.maxWidth())); - _minh += qMin(_description.minHeight(), 3 * st::webPageTitleFont->height); + _maxw = qMax(_maxw, int32(st::msgPadding.left() + _description.maxWidth() + st::msgPadding.right())); + _minh += qMin(_description.countHeight(_maxw - st::msgPadding.left() - st::msgPadding.right()), 3 * st::webPageDescriptionFont->height); } + _minh += st::mediaPadding.top() + st::mediaPadding.bottom(); if (!_title.isEmpty() || !_description.isEmpty()) { _minh += st::webPagePhotoSkip; + if (!parent->toHistoryForwarded() && !parent->toHistoryReply()) { + _minh += st::msgPadding.top(); + } } - _minh += st::mediaPadding.bottom(); } - _height = _minh; } -void HistoryImageLink::draw(Painter &p, const HistoryItem *parent, bool selected, int32 width) const { - if (width < 0) width = w; - int skipx = 0, skipy = 0, height = _height; - const HistoryReply *reply = toHistoryReply(parent); - const HistoryForwarded *fwd = toHistoryForwarded(parent); - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; +int32 HistoryImageLink::resize(int32 width, const HistoryItem *parent) { + bool bubble = parent->hasBubble(); - if (reply || !_title.isEmpty() || !_description.isEmpty()) { - skipx = st::mediaPadding.left(); - - style::color bg(selected ? (outbg ? st::msgOutSelectBg : st::msgInSelectBg) : (outbg ? st::msgOutBg : st::msgInBg)); - style::color sh(selected ? (outbg ? st::msgOutSelectShadow : st::msgInSelectShadow) : (outbg ? st::msgOutShadow : st::msgInShadow)); - RoundCorners cors(selected ? (outbg ? MessageOutSelectedCorners : MessageInSelectedCorners) : (outbg ? MessageOutCorners : MessageInCorners)); - App::roundRect(p, 0, 0, width, _height, bg, cors, &sh); - - int replyFrom = 0, fwdFrom = 0; - if (parent->displayFromName()) { - replyFrom = st::msgPadding.top() + st::msgNameFont->height; - fwdFrom = st::msgPadding.top() + st::msgNameFont->height; - skipy += replyFrom; - p.setFont(st::msgNameFont->f); - if (fromChannel) { - p.setPen(selected ? st::msgInServiceSelColor : st::msgInServiceColor); - } else { - p.setPen(parent->from()->color); - } - parent->from()->nameText.drawElided(p, st::mediaPadding.left(), st::msgPadding.top(), width - st::mediaPadding.left() - st::mediaPadding.right()); - if (!fwd && !reply) skipy += st::msgPadding.top(); - } else if (!reply) { - fwdFrom = st::msgPadding.top(); - skipy += fwdFrom; - } - if (reply) { - skipy += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - reply->drawReplyTo(p, st::msgReplyPadding.left(), replyFrom, width - st::msgReplyPadding.left() - st::msgReplyPadding.right(), selected); - } else if (fwd) { - skipy += st::msgServiceNameFont->height + st::msgPadding.top(); - fwd->drawForwardedFrom(p, st::mediaPadding.left(), fwdFrom, width - st::mediaPadding.left() - st::mediaPadding.right(), selected); - } - width -= st::mediaPadding.left() + st::mediaPadding.right(); + _width = qMin(width, _maxw); + if (bubble) { + _width -= st::mediaPadding.left() + st::mediaPadding.right(); + } + int32 tw = fullWidth(), th = fullHeight(); + if (tw > st::maxMediaSize) { + th = (st::maxMediaSize * th) / tw; + tw = st::maxMediaSize; + } + _height = th; + if (tw > _width) { + _height = (_width * _height / tw); + } else { + _width = tw; + } + 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) { + _width += st::mediaPadding.left() + st::mediaPadding.right(); + _height += st::mediaPadding.top() + st::mediaPadding.bottom(); if (!_title.isEmpty()) { - p.setPen(st::black->p); - _title.drawElided(p, st::mediaPadding.left(), skipy, width, 2); - skipy += qMin(_title.countHeight(width), 2 * st::webPageTitleFont->height); + _height += qMin(_title.countHeight(_width - st::msgPadding.left() - st::msgPadding.right()), st::webPageTitleFont->height * 2); } if (!_description.isEmpty()) { - p.setPen(st::black->p); - _description.drawElided(p, st::mediaPadding.left(), skipy, width, 3); - skipy += qMin(_description.countHeight(width), 3 * st::webPageDescriptionFont->height); + _height += qMin(_description.countHeight(_width - st::msgPadding.left() - st::msgPadding.right()), st::webPageDescriptionFont->height * 3); + } + if (!_title.isEmpty() || !_description.isEmpty()) { + _height += st::webPagePhotoSkip; + if (!parent->toHistoryForwarded() && !parent->toHistoryReply()) { + _height += st::msgPadding.top(); + } + } + } + return _height; +} + +void HistoryImageLink::draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const { + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; + int32 skipx = 0, skipy = 0, width = _width, height = _height; + bool bubble = parent->hasBubble(); + bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; + + if (bubble) { + skipx = st::mediaPadding.left(); + skipy = st::mediaPadding.top(); + + if (!_title.isEmpty() || !_description.isEmpty()) { + if (!parent->toHistoryForwarded() && !parent->toHistoryReply()) { + skipy += st::msgPadding.top(); + } + } + + width -= st::mediaPadding.left() + st::mediaPadding.right(); + int32 textw = _width - st::msgPadding.left() - st::msgPadding.right(); + + p.setPen(st::black); + if (!_title.isEmpty()) { + _title.drawLeftElided(p, skipx + st::msgPadding.left(), skipy, textw, _width, 2); + skipy += qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height); + } + if (!_description.isEmpty()) { + _description.drawLeftElided(p, skipx + st::msgPadding.left(), skipy, textw, _width, 3); + skipy += qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height); } if (!_title.isEmpty() || !_description.isEmpty()) { skipy += st::webPagePhotoSkip; } height -= skipy + st::mediaPadding.bottom(); } else { - App::roundShadow(p, 0, 0, width, _height, selected ? st::msgInSelectShadow : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); + App::roundShadow(p, 0, 0, width, height, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); } - data->load(); + _data->load(); QPixmap toDraw; - if (data && !data->thumb->isNull()) { - int32 w = data->thumb->width(), h = data->thumb->height(); + if (_data && !_data->thumb->isNull()) { + int32 w = _data->thumb->width(), h = _data->thumb->height(); QPixmap pix; - if (width * h == height * w || (w == convertScale(fullWidth()) && h == convertScale(fullHeight()))) { - pix = data->thumb->pixSingle(width, height, width, height); + if (width * h == height * w || (w == fullWidth() && h == fullHeight())) { + pix = _data->thumb->pixSingle(width, height, width, height); } else if (width * h > height * w) { int32 nw = height * w / h; - pix = data->thumb->pixSingle(nw, height, width, height); + pix = _data->thumb->pixSingle(nw, height, width, height); } else { int32 nh = width * h / w; - pix = data->thumb->pixSingle(width, nh, width, height); + pix = _data->thumb->pixSingle(width, nh, width, height); } p.drawPixmap(QPoint(skipx, skipy), pix); } else { - App::roundRect(p, skipx, skipy, width, height, st::black, BlackCorners); - } - if (data) { - switch (data->type) { - case YouTubeLink: p.drawPixmap(QPoint(skipx + (width - st::youtubeIcon.pxWidth()) / 2, skipy + (height - st::youtubeIcon.pxHeight()) / 2), App::sprite(), st::youtubeIcon); break; - case VimeoLink: p.drawPixmap(QPoint(skipx + (width - st::vimeoIcon.pxWidth()) / 2, skipy + (height - st::vimeoIcon.pxHeight()) / 2), App::sprite(), st::vimeoIcon); break; - } - if (!data->title.isEmpty() || !data->duration.isEmpty()) { - p.fillRect(skipx, skipy, width, st::msgDateFont->height + 2 * st::msgDateImgPadding.y(), st::msgDateImgBg->b); - p.setFont(st::msgDateFont->f); - p.setPen(st::msgDateImgColor->p); - int32 titleWidth = width - 2 * st::msgDateImgPadding.x(); - if (!data->duration.isEmpty()) { - int32 durationWidth = st::msgDateFont->width(data->duration); - p.drawText(skipx + width - st::msgDateImgPadding.x() - durationWidth, skipy + st::msgDateImgPadding.y() + st::msgDateFont->ascent, data->duration); - titleWidth -= durationWidth + st::msgDateImgPadding.x(); - } - if (!data->title.isEmpty()) { - p.drawText(skipx + st::msgDateImgPadding.x(), skipy + st::msgDateImgPadding.y() + st::msgDateFont->ascent, st::msgDateFont->elided(data->title, titleWidth)); - } - } + App::roundRect(p, skipx, skipy, width, height, st::white, MessageInCorners); } if (selected) { App::roundRect(p, skipx, skipy, width, height, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); } - int32 fullRight = skipx + width, fullBottom = _height - (skipx ? st::mediaPadding.bottom() : 0); - parent->drawInfo(p, fullRight, fullBottom, selected, InfoDisplayOverImage); + 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); + } } -int32 HistoryImageLink::resize(int32 width, const HistoryItem *parent) { - const HistoryReply *reply = toHistoryReply(parent); - const HistoryForwarded *fwd = toHistoryForwarded(parent); +void HistoryImageLink::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(); - w = qMin(width, _maxw); - if (reply || !_title.isEmpty() || !_description.isEmpty()) { - w -= st::mediaPadding.left() + st::mediaPadding.right(); - } + if (bubble) { + skipx = st::mediaPadding.left(); + skipy = st::mediaPadding.top(); - int32 tw = convertScale(fullWidth()), th = convertScale(fullHeight()); - if (tw > st::maxMediaSize) { - th = (st::maxMediaSize * th) / tw; - tw = st::maxMediaSize; - } - _height = th; - if (tw > w) { - _height = (w * _height / tw); - } else { - w = tw; - } - if (_height > width) { - w = (w * width) / _height; - _height = width; - } - if (w < st::minPhotoSize) { - w = st::minPhotoSize; - } - if (_height < st::minPhotoSize) { - _height = st::minPhotoSize; - } - if (reply || !_title.isEmpty() || !_description.isEmpty()) { - if (parent->displayFromName()) { - _height += st::msgPadding.top() + st::msgNameFont->height; - } - if (reply) { - _height += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - } else { - if (!parent->displayFromName() || !fwd) { - _height += st::msgPadding.top(); - } - if (fwd) { - _height += st::msgServiceNameFont->height + st::msgPadding.top(); + if (!_title.isEmpty() || !_description.isEmpty()) { + if (!parent->toHistoryForwarded() && !parent->toHistoryReply()) { + skipy += st::msgPadding.top(); } } + + width -= st::mediaPadding.left() + st::mediaPadding.right(); + int32 textw = _width - st::msgPadding.left() - st::msgPadding.right(); + if (!_title.isEmpty()) { - _height += qMin(_title.countHeight(w), st::webPageTitleFont->height * 2); + skipy += qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height); } if (!_description.isEmpty()) { - _height += qMin(_description.countHeight(w), st::webPageDescriptionFont->height * 3); + skipy += qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height); } if (!_title.isEmpty() || !_description.isEmpty()) { - _height += st::webPagePhotoSkip; - } - _height += st::mediaPadding.bottom(); - w += st::mediaPadding.left() + st::mediaPadding.right(); - } - return _height; -} - -const QString HistoryImageLink::inDialogsText() const { - if (data) { - switch (data->type) { - case YouTubeLink: return qsl("YouTube Video"); - case VimeoLink: return qsl("Vimeo Video"); - case InstagramLink: return qsl("Instagram Link"); - case GoogleMapsLink: return lang(lng_maps_point); - } - } - return QString(); -} - -const QString HistoryImageLink::inHistoryText() const { - if (data) { - switch (data->type) { - case YouTubeLink: return qsl("[ YouTube Video : ") + link->text() + qsl(" ]"); - case VimeoLink: return qsl("[ Vimeo Video : ") + link->text() + qsl(" ]"); - case InstagramLink: return qsl("[ Instagram Link : ") + link->text() + qsl(" ]"); - case GoogleMapsLink: return qsl("[ ") + lang(lng_maps_point) + qsl(" : ") + link->text() + qsl(" ]"); - } - } - return qsl("[ Link : ") + link->text() + qsl(" ]"); -} - -bool HistoryImageLink::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const { - if (width < 0) width = w; - return (x >= 0 && y >= 0 && x < width && y < _height); -} - -void HistoryImageLink::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width) const { - if (width < 0) width = w; - - bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel; - - int skipx = 0, skipy = 0, height = _height; - const HistoryReply *reply = toHistoryReply(parent); - const HistoryForwarded *fwd = reply ? 0 : toHistoryForwarded(parent); - int replyFrom = 0, fwdFrom = 0; - if (reply || !_title.isEmpty() || !_description.isEmpty()) { - skipx = st::mediaPadding.left(); - if (reply) { - skipy = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - } else if (fwd) { - skipy = st::msgServiceNameFont->height + st::msgPadding.top(); - } - if (parent->displayFromName()) { - replyFrom = st::msgPadding.top() + st::msgNameFont->height; - fwdFrom = st::msgPadding.top() + st::msgNameFont->height; - skipy += replyFrom; - if (x >= st::mediaPadding.left() && y >= st::msgPadding.top() && x < width - st::mediaPadding.left() - st::mediaPadding.right() && x < st::mediaPadding.left() + parent->from()->nameText.maxWidth() && y < replyFrom) { - lnk = parent->from()->lnk; - return; - } - if (!fwd && !reply) skipy += st::msgPadding.top(); - } else if (!reply) { - fwdFrom = st::msgPadding.top(); - skipy += fwdFrom; - } - if (reply) { - if (x >= 0 && y >= replyFrom + st::msgReplyPadding.top() && x < width && y < skipy - st::msgReplyPadding.bottom()) { - lnk = reply->replyToLink(); - return; - } - } else if (fwd) { - if (y >= fwdFrom && y < fwdFrom + st::msgServiceNameFont->height) { - return fwd->getForwardedState(lnk, state, x - st::mediaPadding.left(), width - st::mediaPadding.left() - st::mediaPadding.right()); - } + skipy += st::webPagePhotoSkip; } height -= skipy + st::mediaPadding.bottom(); - width -= st::mediaPadding.left() + st::mediaPadding.right(); } - if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height && data) { - lnk = link; + if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height && _data) { + lnk = _link; int32 fullRight = skipx + width, fullBottom = _height - (skipx ? st::mediaPadding.bottom() : 0); bool inDate = parent->pointInTime(fullRight, fullBottom, x, y, InfoDisplayOverImage); @@ -6361,8 +5934,70 @@ void HistoryImageLink::getState(TextLinkPtr &lnk, HistoryCursorState &state, int } } -HistoryMedia *HistoryImageLink::clone() const { - return new HistoryImageLink(*this); +const QString HistoryImageLink::inDialogsText() const { + if (_data) { + switch (_data->type) { + case GoogleMapsLink: return lang(lng_maps_point); + } + } + return QString(); +} + +const QString HistoryImageLink::inHistoryText() const { + if (_data) { + switch (_data->type) { + case GoogleMapsLink: return qsl("[ ") + lang(lng_maps_point) + qsl(" : ") + _link->text() + qsl(" ]"); + } + } + return qsl("[ Link : ") + _link->text() + qsl(" ]"); +} + +int32 HistoryImageLink::fullWidth() const { + if (_data) { + switch (_data->type) { + case GoogleMapsLink: return st::locationSize.width(); + } + } + return st::minPhotoSize; +} + +int32 HistoryImageLink::fullHeight() const { + if (_data) { + switch (_data->type) { + case GoogleMapsLink: return st::locationSize.height(); + } + } + return st::minPhotoSize; +} + +void ViaInlineBotLink::onClick(Qt::MouseButton button) const { + App::insertBotCommand('@' + _bot->username); +} + +HistoryMessageVia::HistoryMessageVia(int32 userId) +: bot(App::userLoaded(peerFromUser(userId))) +, width(0) +, maxWidth(bot ? st::msgServiceNameFont->width(lng_inline_bot_via(lt_inline_bot, '@' + bot->username)) : 0) +, lnk(new ViaInlineBotLink(bot)) { +} + +bool HistoryMessageVia::isNull() const { + return !bot || bot->username.isEmpty(); +} + +void HistoryMessageVia::resize(int32 availw) { + if (availw < 0) { + text = QString(); + width = 0; + } else { + text = lng_inline_bot_via(lt_inline_bot, '@' + bot->username); + if (availw < maxWidth) { + text = st::msgServiceNameFont->elided(text, availw); + width = st::msgServiceNameFont->width(text); + } else if (width < maxWidth) { + width = maxWidth; + } + } } HistoryMessage::HistoryMessage(History *history, HistoryBlock *block, const MTPDmessage &msg) : @@ -6370,23 +6005,23 @@ HistoryMessage::HistoryMessage(History *history, HistoryBlock *block, const MTPD , _text(st::msgMinWidth) , _textWidth(0) , _textHeight(0) +, _via(msg.has_via_bot_id() ? new HistoryMessageVia(msg.vvia_bot_id.v) : 0) , _media(0) -, _views(msg.has_views() ? msg.vviews.v : -1) -{ +, _views(msg.has_views() ? msg.vviews.v : -1) { QString text(textClean(qs(msg.vmessage))); initTime(); initMedia(msg.has_media() ? (&msg.vmedia) : 0, text); setText(text, msg.has_entities() ? entitiesFromMTP(msg.ventities.c_vector().v) : EntitiesInText()); } -HistoryMessage::HistoryMessage(History *history, HistoryBlock *block, MsgId msgId, int32 flags, QDateTime date, int32 from, const QString &msg, const EntitiesInText &entities, HistoryMedia *fromMedia) : -HistoryItem(history, block, msgId, flags, date, from) +HistoryMessage::HistoryMessage(History *history, HistoryBlock *block, MsgId msgId, int32 flags, int32 viaBotId, QDateTime date, int32 from, const QString &msg, const EntitiesInText &entities, HistoryMedia *fromMedia) : +HistoryItem(history, block, msgId, flags, date, (flags & MTPDmessage::flag_from_id) ? from : 0) , _text(st::msgMinWidth) , _textWidth(0) , _textHeight(0) +, _via((flags & MTPDmessage::flag_via_bot_id) ? new HistoryMessageVia(viaBotId) : 0) , _media(0) -, _views(fromChannel() ? 1 : -1) -{ +, _views(fromChannel() ? 1 : -1) { initTime(); if (fromMedia) { _media = fromMedia->clone(); @@ -6395,16 +6030,30 @@ HistoryItem(history, block, msgId, flags, date, from) setText(msg, entities); } -HistoryMessage::HistoryMessage(History *history, HistoryBlock *block, MsgId msgId, int32 flags, QDateTime date, int32 from, DocumentData *doc) : -HistoryItem(history, block, msgId, flags, date, from) +HistoryMessage::HistoryMessage(History *history, HistoryBlock *block, MsgId msgId, int32 flags, int32 viaBotId, QDateTime date, int32 from, DocumentData *doc, const QString &caption) : +HistoryItem(history, block, msgId, flags, date, (flags & MTPDmessage::flag_from_id) ? from : 0) , _text(st::msgMinWidth) , _textWidth(0) , _textHeight(0) +, _via((flags & MTPDmessage::flag_via_bot_id) ? new HistoryMessageVia(viaBotId) : 0) , _media(0) -, _views(fromChannel() ? 1 : -1) -{ +, _views(fromChannel() ? 1 : -1) { initTime(); - initMediaFromDocument(doc); + initMediaFromDocument(doc, caption); + setText(QString(), EntitiesInText()); +} + +HistoryMessage::HistoryMessage(History *history, HistoryBlock *block, MsgId msgId, int32 flags, int32 viaBotId, QDateTime date, int32 from, PhotoData *photo, const QString &caption) : +HistoryItem(history, block, msgId, flags, date, (flags & MTPDmessage::flag_from_id) ? from : 0) +, _text(st::msgMinWidth) +, _textWidth(0) +, _textHeight(0) +, _via((flags & MTPDmessage::flag_via_bot_id) ? new HistoryMessageVia(viaBotId) : 0) +, _media(0) +, _views(fromChannel() ? 1 : -1) { + initTime(); + _media = new HistoryPhoto(photo, caption, this); + _media->regItem(this); setText(QString(), EntitiesInText()); } @@ -6458,7 +6107,7 @@ void HistoryMessage::initMedia(const MTPMessageMedia *media, QString ¤tTex case mtpc_messageMediaPhoto: { const MTPDmessageMediaPhoto &photo(media->c_messageMediaPhoto()); if (photo.vphoto.type() == mtpc_photo) { - _media = new HistoryPhoto(photo.vphoto.c_photo(), qs(photo.vcaption), this); + _media = new HistoryPhoto(App::feedPhoto(photo.vphoto.c_photo()), qs(photo.vcaption), this); } } break; case mtpc_messageMediaVideo: { @@ -6476,105 +6125,168 @@ void HistoryMessage::initMedia(const MTPMessageMedia *media, QString ¤tTex case mtpc_messageMediaDocument: { const MTPDocument &document(media->c_messageMediaDocument().vdocument); if (document.type() == mtpc_document) { - DocumentData *doc = App::feedDocument(document); - return initMediaFromDocument(doc); + return initMediaFromDocument(App::feedDocument(document), qs(media->c_messageMediaDocument().vcaption)); } } break; case mtpc_messageMediaWebPage: { const MTPWebPage &d(media->c_messageMediaWebPage().vwebpage); switch (d.type()) { - case mtpc_webPageEmpty: initMediaFromText(currentText); break; + case mtpc_webPageEmpty: break; case mtpc_webPagePending: { - WebPageData *webPage = App::feedWebPage(d.c_webPagePending()); - _media = new HistoryWebPage(webPage); + _media = new HistoryWebPage(App::feedWebPage(d.c_webPagePending())); } break; case mtpc_webPage: { _media = new HistoryWebPage(App::feedWebPage(d.c_webPage())); } break; } } break; - default: initMediaFromText(currentText); break; }; if (_media) _media->regItem(this); } -void HistoryMessage::initMediaFromText(QString ¤tText) { - QString lnk = currentText.trimmed(); - if (/*reYouTube1.match(currentText).hasMatch() || reYouTube2.match(currentText).hasMatch() || reInstagram.match(currentText).hasMatch() || */reVimeo.match(currentText).hasMatch()) { - _media = new HistoryImageLink(lnk); - currentText = QString(); - } -} - -void HistoryMessage::initMediaFromDocument(DocumentData *doc) { +void HistoryMessage::initMediaFromDocument(DocumentData *doc, const QString &caption) { if (doc->sticker()) { _media = new HistorySticker(doc); + } else if (doc->isAnimation()) { + _media = new HistoryGif(doc, caption, this); } else { - _media = new HistoryDocument(doc); + _media = new HistoryDocument(doc, caption, this); } _media->regItem(this); } +int32 HistoryMessage::plainMaxWidth() const { + return st::msgPadding.left() + _text.maxWidth() + st::msgPadding.right(); +} + void HistoryMessage::initDimensions() { - if (justMedia()) { - _media->initDimensions(this); - _maxw = _media->maxWidth(); - _minh = _media->minHeight(); - } else { - _maxw = _text.maxWidth(); - _minh = _text.minHeight(); - _maxw += st::msgPadding.left() + st::msgPadding.right(); + if (drawBubble()) { if (_media) { _media->initDimensions(this); - if (_media->isDisplayed() && _text.hasSkipBlock()) { - _text.removeSkipBlock(); - _textWidth = 0; - _textHeight = 0; - } else if (!_media->isDisplayed() && !_text.hasSkipBlock()) { + if (_media->isDisplayed()) { + if (_text.hasSkipBlock()) { + _text.removeSkipBlock(); + _textWidth = 0; + _textHeight = 0; + } + } else if (!_text.hasSkipBlock()) { _text.setSkipBlock(skipBlockWidth(), skipBlockHeight()); _textWidth = 0; _textHeight = 0; } - if (_media->isDisplayed()) { - int32 maxw = _media->maxWidth() + st::msgPadding.left() + st::msgPadding.right(); - if (maxw > _maxw) _maxw = maxw; - _minh += st::msgPadding.bottom() + _media->minHeight(); + } + + _maxw = plainMaxWidth(); + if (_text.isEmpty()) { + _minh = 0; + } else { + _minh = st::msgPadding.top() + _text.minHeight() + st::msgPadding.bottom(); + } + if (_media && _media->isDisplayed()) { + int32 maxw = _media->maxWidth(); + if (maxw > _maxw) _maxw = maxw; + _minh += _media->minHeight(); + } + if (!_media) { + if (displayFromName()) { + int32 namew = st::msgPadding.left() + _from->nameText.maxWidth() + st::msgPadding.right(); + if (via() && !toHistoryForwarded()) { + namew += st::msgServiceFont->spacew + via()->maxWidth; + } + if (namew > _maxw) _maxw = namew; + } else if (via() && !toHistoryForwarded()) { + if (st::msgPadding.left() + via()->maxWidth + st::msgPadding.right() > _maxw) { + _maxw = st::msgPadding.left() + via()->maxWidth + st::msgPadding.right(); + } + } + } + } else { + _media->initDimensions(this); + _maxw = _media->maxWidth(); + _minh = _media->minHeight(); + } +} + +void HistoryMessage::countPositionAndSize(int32 &left, int32 &width) const { + int32 mwidth = qMin(int(st::msgMaxWidth), _maxw); + if (_media && _media->currentWidth() < mwidth) { + mwidth = qMax(_media->currentWidth(), qMin(mwidth, plainMaxWidth())); + } + + left = (!fromChannel() && out()) ? st::msgMargin.right() : st::msgMargin.left(); + if (displayFromPhoto()) { + left += st::msgPhotoSkip; + } + + width = _history->width - st::msgMargin.left() - st::msgMargin.right(); + if (width > mwidth) { + if (!fromChannel() && out()) { + left += width - mwidth; + } + width = mwidth; + } +} + +void HistoryMessage::fromNameUpdated(int32 width) const { + _fromVersion = _from->nameVersion; + if (drawBubble() && displayFromName()) { + if (via() && !toHistoryForwarded()) { + via()->resize(width - st::msgPadding.left() - st::msgPadding.right() - _from->nameText.maxWidth() - st::msgServiceFont->spacew); + } + } +} + +int32 HistoryMessage::addToOverview(AddToOverviewMethod method) { + if (!indexInOverview()) return 0; + + int32 result = 0; + if (HistoryMedia *media = getMedia(true)) { + MediaOverviewType type = mediaToOverviewType(media); + if (type != OverviewCount) { + if (history()->addToOverview(type, id, method)) { + result |= (1 << type); } } } - fromNameUpdated(); + if (hasTextLinks()) { + if (history()->addToOverview(OverviewLinks, id, method)) { + result |= (1 << OverviewLinks); + } + } + return result; } -void HistoryMessage::fromNameUpdated() const { - if (_media) return; - int32 _namew = (displayFromName() ? _from->nameText.maxWidth() : 0) + st::msgPadding.left() + st::msgPadding.right(); - if (_namew > _maxw) _maxw = _namew; -} - -bool HistoryMessage::uploading() const { - return _media ? _media->uploading() : false; +void HistoryMessage::eraseFromOverview() { + if (HistoryMedia *media = getMedia(true)) { + MediaOverviewType type = mediaToOverviewType(media); + if (type != OverviewCount) { + history()->eraseFromOverview(type, id); + } + } + if (hasTextLinks()) { + history()->eraseFromOverview(OverviewLinks, id); + } } QString HistoryMessage::selectedText(uint32 selection) const { - if (_media && selection == FullItemSel) { + if (_media && selection == FullSelection) { QString text = _text.original(0, 0xFFFF, Text::ExpandLinksAll), mediaText = _media->inHistoryText(); return text.isEmpty() ? mediaText : (mediaText.isEmpty() ? text : (text + ' ' + mediaText)); } - uint16 selectedFrom = (selection == FullItemSel) ? 0 : ((selection >> 16) & 0xFFFF); - uint16 selectedTo = (selection == FullItemSel) ? 0xFFFF : (selection & 0xFFFF); + uint16 selectedFrom = (selection == FullSelection) ? 0 : ((selection >> 16) & 0xFFFF); + uint16 selectedTo = (selection == FullSelection) ? 0xFFFF : (selection & 0xFFFF); return _text.original(selectedFrom, selectedTo, Text::ExpandLinksAll); } QString HistoryMessage::inDialogsText() const { - QString result = _media ? _media->inDialogsText() : QString(); - return result.isEmpty() ? _text.original(0, 0xFFFF, Text::ExpandLinksNone) : result; + return emptyText() ? (_media ? _media->inDialogsText() : QString()) : _text.original(0, 0xFFFF, Text::ExpandLinksNone); } HistoryMedia *HistoryMessage::getMedia(bool inOverview) const { return _media; } -void HistoryMessage::setMedia(const MTPMessageMedia *media, bool allowEmitResize) { +void HistoryMessage::setMedia(const MTPMessageMedia *media) { if ((!_media || _media->isImageLink()) && (!media || media->type() == mtpc_messageMediaEmpty)) return; bool mediaWasDisplayed = false; @@ -6594,46 +6306,41 @@ void HistoryMessage::setMedia(const MTPMessageMedia *media, bool allowEmitResize _textWidth = 0; _textHeight = 0; } - initDimensions(); - if (allowEmitResize && App::main()) App::main()->itemResized(this); } void HistoryMessage::setText(const QString &text, const EntitiesInText &entities) { - if (!_media || !text.isEmpty()) { // !justMedia() - textstyleSet(&((out() && !fromChannel()) ? st::outTextStyle : st::inTextStyle)); - if (_media && _media->isDisplayed()) { - _text.setMarkedText(st::msgFont, text, entities, itemTextOptions(this)); - } else { - _text.setMarkedText(st::msgFont, text + skipBlock(), entities, itemTextOptions(this)); - } - textstyleRestore(); - if (id > 0) { - for (int32 i = 0, l = entities.size(); i != l; ++i) { - if (entities.at(i).type == EntityInTextUrl || entities.at(i).type == EntityInTextCustomUrl || entities.at(i).type == EntityInTextEmail) { - _flags |= MTPDmessage_flag_HAS_TEXT_LINKS; - break; - } - } - } - _textWidth = 0; - _textHeight = 0; + textstyleSet(&((out() && !fromChannel()) ? st::outTextStyle : st::inTextStyle)); + if (_media && _media->isDisplayed()) { + _text.setMarkedText(st::msgFont, text, entities, itemTextOptions(this)); + } else { + _text.setMarkedText(st::msgFont, text + skipBlock(), entities, itemTextOptions(this)); } + textstyleRestore(); + + for (int32 i = 0, l = entities.size(); i != l; ++i) { + if (entities.at(i).type == EntityInTextUrl || entities.at(i).type == EntityInTextCustomUrl || entities.at(i).type == EntityInTextEmail) { + _flags |= MTPDmessage_flag_HAS_TEXT_LINKS; + break; + } + } + _textWidth = 0; + _textHeight = 0; } QString HistoryMessage::originalText() const { - return _text.isEmpty() ? QString() : _text.original(); + return emptyText() ? QString() : _text.original(); } EntitiesInText HistoryMessage::originalEntities() const { - return _text.isEmpty() ? EntitiesInText() : _text.originalEntities(); + return emptyText() ? EntitiesInText() : _text.originalEntities(); } bool HistoryMessage::textHasLinks() { - return _text.hasLinks(); + return emptyText() ? false : _text.hasLinks(); } -void HistoryMessage::drawInfo(Painter &p, int32 right, int32 bottom, bool selected, InfoDisplayType type) const { - p.setFont(st::msgDateFont->f); +void HistoryMessage::drawInfo(Painter &p, int32 right, int32 bottom, int32 width, bool selected, InfoDisplayType type) const { + p.setFont(st::msgDateFont); bool outbg = out() && !fromChannel(), overimg = (type == InfoDisplayOverImage); int32 infoRight = right, infoBottom = bottom; @@ -6641,7 +6348,7 @@ void HistoryMessage::drawInfo(Painter &p, int32 right, int32 bottom, bool select case InfoDisplayDefault: infoRight -= st::msgPadding.right() - st::msgDateDelta.x(); infoBottom -= st::msgPadding.bottom() - st::msgDateDelta.y(); - p.setPen((selected ? (outbg ? st::msgOutSelectDateColor : st::msgInSelectDateColor) : (outbg ? st::msgOutDateColor : st::msgInDateColor))->p); + p.setPen((selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg))->p); break; case InfoDisplayOverImage: infoRight -= st::msgDateImgDelta + st::msgDateImgPadding.x(); @@ -6651,11 +6358,13 @@ void HistoryMessage::drawInfo(Painter &p, int32 right, int32 bottom, bool select } int32 infoW = HistoryMessage::infoWidth(); + if (rtl()) infoRight = width - infoRight + infoW; + int32 dateX = infoRight - infoW; int32 dateY = infoBottom - st::msgDateFont->height; 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::msgDateImgSelectBg : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); + App::roundRect(p, dateX - st::msgDateImgPadding.x(), dateY - st::msgDateImgPadding.y(), dateW, dateH, selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); } dateX += HistoryMessage::timeLeft(); @@ -6701,7 +6410,7 @@ void HistoryMessage::drawInfo(Painter &p, int32 right, int32 bottom, bool select } } -void HistoryMessage::setViewsCount(int32 count) { +void HistoryMessage::setViewsCount(int32 count, bool reinit) { if (_views == count || (count >= 0 && _views > count)) return; int32 was = _viewsWidth; @@ -6709,15 +6418,17 @@ void HistoryMessage::setViewsCount(int32 count) { _viewsText = (_views >= 0) ? formatViewsCount(_views) : QString(); _viewsWidth = _viewsText.isEmpty() ? 0 : st::msgDateFont->width(_viewsText); if (was == _viewsWidth) { - if (App::main()) App::main()->msgUpdated(this); + Ui::repaintHistoryItem(this); } else { if (_text.hasSkipBlock()) { _text.setSkipBlock(HistoryMessage::skipBlockWidth(), HistoryMessage::skipBlockHeight()); _textWidth = 0; _textHeight = 0; } - initDimensions(); - if (App::main()) App::main()->itemResized(this); + if (reinit) { + initDimensions(); + Notify::historyItemResized(this); + } } } @@ -6725,7 +6436,7 @@ void HistoryMessage::setId(MsgId newId) { bool wasPositive = (id > 0), positive = (newId > 0); HistoryItem::setId(newId); if (wasPositive == positive) { - if (App::main()) App::main()->msgUpdated(this); + Ui::repaintHistoryItem(this); } else { if (_text.hasSkipBlock()) { _text.setSkipBlock(HistoryMessage::skipBlockWidth(), HistoryMessage::skipBlockHeight()); @@ -6733,21 +6444,22 @@ void HistoryMessage::setId(MsgId newId) { _textHeight = 0; } initDimensions(); - if (App::main()) App::main()->itemResized(this); + Notify::historyItemResized(this); } } -void HistoryMessage::draw(Painter &p, uint32 selection) const { - bool outbg = out() && !fromChannel(); +void HistoryMessage::draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const { + bool outbg = out() && !fromChannel(), bubble = drawBubble(), selected = (selection == FullSelection); textstyleSet(&(outbg ? st::outTextStyle : st::inTextStyle)); - uint64 ms = App::main() ? App::main()->animActiveTime(this) : 0; - if (ms) { - if (ms > st::activeFadeInDuration + st::activeFadeOutDuration) { + uint64 animms = App::main() ? App::main()->animActiveTimeStart(this) : 0; + if (animms > 0 && animms <= ms) { + animms = ms - animms; + if (animms > st::activeFadeInDuration + st::activeFadeOutDuration) { App::main()->stopAnimActive(); } else { - float64 dt = (ms > st::activeFadeInDuration) ? (1 - (ms - st::activeFadeInDuration) / float64(st::activeFadeOutDuration)) : (ms / float64(st::activeFadeInDuration)); + float64 dt = (animms > st::activeFadeInDuration) ? (1 - (animms - st::activeFadeInDuration) / float64(st::activeFadeOutDuration)) : (animms / float64(st::activeFadeInDuration)); float64 o = p.opacity(); p.setOpacity(o * dt); p.fillRect(0, 0, _history->width, _height, textstyleCurrent()->selectOverlay->b); @@ -6755,154 +6467,154 @@ void HistoryMessage::draw(Painter &p, uint32 selection) const { } } - bool selected = (selection == FullItemSel); + int32 left = 0, width = 0; + countPositionAndSize(left, width); if (_from->nameVersion > _fromVersion) { - fromNameUpdated(); - _fromVersion = _from->nameVersion; - } - int32 left = fromChannel() ? (st::msgMargin.left() + st::msgMargin.left()) / 2 : (out() ? st::msgMargin.right() : st::msgMargin.left()), width = _history->width - st::msgMargin.left() - st::msgMargin.right(), mwidth = st::msgMaxWidth; - if (justMedia()) { - if (_media->maxWidth() > mwidth) mwidth = _media->maxWidth(); - if (_media->currentWidth() < mwidth) mwidth = _media->currentWidth(); - } - if (width > mwidth) { - if (fromChannel()) { -// left += (width - mwidth) / 2; - } else if (out()) { - left += width - mwidth; - } - width = mwidth; + fromNameUpdated(width); } if (displayFromPhoto()) { - p.drawPixmap(left, _height - st::msgMargin.bottom() - st::msgPhotoSize, _from->photo->pixRounded(st::msgPhotoSize)); -// width -= st::msgPhotoSkip; - left += st::msgPhotoSkip; + p.drawPixmap(left - st::msgPhotoSkip, _height - st::msgMargin.bottom() - st::msgPhotoSize, _from->photo->pixRounded(st::msgPhotoSize)); } if (width < 1) return; - if (width >= _maxw) { - if (fromChannel()) { -// left += (width - _maxw) / 2; - } else if (out()) { - left += width - _maxw; - } - width = _maxw; - } - if (justMedia()) { - p.save(); - p.translate(left, st::msgMargin.top()); - _media->draw(p, this, selected); - p.restore(); - } else { + if (bubble) { QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); - style::color bg(selected ? (outbg ? st::msgOutSelectBg : st::msgInSelectBg) : (outbg ? st::msgOutBg : st::msgInBg)); - style::color sh(selected ? (outbg ? st::msgOutSelectShadow : st::msgInSelectShadow) : (outbg ? st::msgOutShadow : st::msgInShadow)); + style::color bg(selected ? (outbg ? st::msgOutBgSelected : st::msgInBgSelected) : (outbg ? st::msgOutBg : st::msgInBg)); + style::color sh(selected ? (outbg ? st::msgOutShadowSelected : st::msgInShadowSelected) : (outbg ? st::msgOutShadow : st::msgInShadow)); RoundCorners cors(selected ? (outbg ? MessageOutSelectedCorners : MessageInSelectedCorners) : (outbg ? MessageOutCorners : MessageInCorners)); App::roundRect(p, r, bg, cors, &sh); if (displayFromName()) { - p.setFont(st::msgNameFont->f); + p.setFont(st::msgNameFont); if (fromChannel()) { - p.setPen(selected ? st::msgInServiceSelColor : st::msgInServiceColor); + p.setPen(selected ? st::msgInServiceFgSelected : st::msgInServiceFg); } else { p.setPen(_from->color); } _from->nameText.drawElided(p, r.left() + st::msgPadding.left(), r.top() + st::msgPadding.top(), width - st::msgPadding.left() - st::msgPadding.right()); + if (via() && !toHistoryForwarded() && width > st::msgPadding.left() + st::msgPadding.right() + _from->nameText.maxWidth() + st::msgServiceFont->spacew) { + p.setPen(selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg)); + p.drawText(r.left() + st::msgPadding.left() + _from->nameText.maxWidth() + st::msgServiceFont->spacew, r.top() + st::msgPadding.top() + st::msgServiceFont->ascent, via()->text); + } r.setTop(r.top() + st::msgNameFont->height); } + QRect trect(r.marginsAdded(-st::msgPadding)); drawMessageText(p, trect, selection); - if (_media) { + if (_media && _media->isDisplayed()) { p.save(); - p.translate(left + st::msgPadding.left(), _height - st::msgMargin.bottom() - st::msgPadding.bottom() - _media->height()); - _media->draw(p, this, selected); + int32 top = _height - st::msgMargin.bottom() - _media->height(); + p.translate(left, top); + _media->draw(p, this, r.translated(-left, -top), selected, ms); p.restore(); + if (!_media->customInfoLayout()) { + HistoryMessage::drawInfo(p, r.x() + r.width(), r.y() + r.height(), 2 * r.x() + r.width(), selected, InfoDisplayDefault); + } + } else { + HistoryMessage::drawInfo(p, r.x() + r.width(), r.y() + r.height(), 2 * r.x() + r.width(), selected, InfoDisplayDefault); } - HistoryMessage::drawInfo(p, r.x() + r.width(), r.y() + r.height(), selected, InfoDisplayDefault); + } else { + p.save(); + int32 top = st::msgMargin.top(); + p.translate(left, top); + _media->draw(p, this, r.translated(-left, -top), selected, ms); + p.restore(); } textstyleRestore(); } -void HistoryMessage::drawMessageText(Painter &p, const QRect &trect, uint32 selection) const { - p.setPen(st::msgColor->p); - p.setFont(st::msgFont->f); - uint16 selectedFrom = (selection == FullItemSel) ? 0 : (selection >> 16) & 0xFFFF; - uint16 selectedTo = (selection == FullItemSel) ? 0 : selection & 0xFFFF; - _text.draw(p, trect.x(), trect.y(), trect.width(), Qt::AlignLeft, 0, -1, selectedFrom, selectedTo); +void HistoryMessage::drawMessageText(Painter &p, QRect trect, uint32 selection) const { + bool outbg = out() && !fromChannel(), selected = (selection == FullSelection); + if (!displayFromName() && via() && !toHistoryForwarded()) { + p.setFont(st::msgServiceNameFont); + p.setPen(selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg)); + p.drawTextLeft(trect.left(), trect.top(), _history->width, via()->text); + trect.setY(trect.y() + st::msgServiceNameFont->height); + } + + p.setPen(st::msgColor); + p.setFont(st::msgFont); + uint16 selectedFrom = (selection == FullSelection) ? 0 : (selection >> 16) & 0xFFFF; + uint16 selectedTo = (selection == FullSelection) ? 0 : selection & 0xFFFF; + _text.draw(p, trect.x(), trect.y(), trect.width(), style::al_left, 0, -1, selectedFrom, selectedTo); +} + +void HistoryMessage::destroy() { + eraseFromOverview(); + HistoryItem::destroy(); } int32 HistoryMessage::resize(int32 width) { if (width < st::msgMinWidth) return _height; width -= st::msgMargin.left() + st::msgMargin.right(); - if (justMedia()) { - _height = _media->resize(width, this); - } else { - if (width < st::msgPadding.left() + st::msgPadding.right() + 1) { - width = st::msgPadding.left() + st::msgPadding.right() + 1; - } else if (width > st::msgMaxWidth) { - width = st::msgMaxWidth; - } - int32 nwidth = qMax(width - st::msgPadding.left() - st::msgPadding.right(), 0); - if (nwidth != _textWidth) { - _textWidth = nwidth; - textstyleSet(&((out() && !fromChannel()) ? st::outTextStyle : st::inTextStyle)); - _textHeight = _text.countHeight(nwidth); - textstyleRestore(); - } + if (width < st::msgPadding.left() + st::msgPadding.right() + 1) { + width = st::msgPadding.left() + st::msgPadding.right() + 1; + } else if (width > st::msgMaxWidth) { + width = st::msgMaxWidth; + } + if (drawBubble()) { + bool media = (_media && _media->isDisplayed()); if (width >= _maxw) { _height = _minh; - if (_media) _media->resize(_maxw - st::msgPadding.left() - st::msgPadding.right(), this); + if (media) _media->resize(_maxw, this); } else { - _height = _textHeight; - if (_media && _media->isDisplayed()) _height += st::msgPadding.bottom() + _media->resize(nwidth, this); + if (_text.isEmpty()) { + _height = 0; + } else { + int32 textWidth = qMax(width - st::msgPadding.left() - st::msgPadding.right(), 1); + if (textWidth != _textWidth) { + textstyleSet(&((out() && !fromChannel()) ? st::outTextStyle : st::inTextStyle)); + _textWidth = textWidth; + _textHeight = _text.countHeight(textWidth); + textstyleRestore(); + } + _height = st::msgPadding.top() + _textHeight + st::msgPadding.bottom(); + } + if (media) _height += _media->resize(width, this); } + if (displayFromName()) { - _height += st::msgNameFont->height; + if (emptyText()) { + _height += st::msgPadding.top() + st::msgNameFont->height + st::mediaHeaderSkip; + } else { + _height += st::msgNameFont->height; + } + int32 l = 0, w = 0; + countPositionAndSize(l, w); + fromNameUpdated(w); + } else if (via() && !toHistoryForwarded()) { + int32 l = 0, w = 0; + countPositionAndSize(l, w); + via()->resize(w - st::msgPadding.left() - st::msgPadding.right()); + if (emptyText() && !displayFromName()) { + _height += st::msgPadding.top() + st::msgNameFont->height + st::mediaHeaderSkip; + } else { + _height += st::msgNameFont->height; + } } - _height += st::msgPadding.top() + st::msgPadding.bottom(); + } else { + _height = _media->resize(width, this); } _height += st::msgMargin.top() + st::msgMargin.bottom(); return _height; } bool HistoryMessage::hasPoint(int32 x, int32 y) const { - int32 left = fromChannel() ? (st::msgMargin.left() + st::msgMargin.left()) / 2 : (out() ? st::msgMargin.right() : st::msgMargin.left()), width = _history->width - st::msgMargin.left() - st::msgMargin.right(), mwidth = st::msgMaxWidth; - if (justMedia()) { - if (_media->maxWidth() > mwidth) mwidth = _media->maxWidth(); - if (_media->currentWidth() < mwidth) mwidth = _media->currentWidth(); - } - if (width > mwidth) { - if (fromChannel()) { -// left += (width - mwidth) / 2; - } else if (out()) { - left += width - mwidth; - } - width = mwidth; - } - - if (displayFromPhoto()) { // from user left photo - left += st::msgPhotoSkip; - } + int32 left = 0, width = 0; + countPositionAndSize(left, width); if (width < 1) return false; - if (width >= _maxw) { - if (fromChannel()) { -// left += (width - _maxw) / 2; - } else if (out()) { - left += width - _maxw; - } - width = _maxw; - } - if (justMedia()) { + if (drawBubble()) { + QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); + return r.contains(x, y); + } else { return _media->hasPoint(x - left, y - st::msgMargin.top(), this); } - QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); - return r.contains(x, y); } bool HistoryMessage::pointInTime(int32 right, int32 bottom, int32 x, int32 y, InfoDisplayType type) const { @@ -6926,67 +6638,65 @@ void HistoryMessage::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 state = HistoryDefaultCursorState; lnk = TextLinkPtr(); - int32 left = fromChannel() ? (st::msgMargin.left() + st::msgMargin.left()) / 2 : (out() ? st::msgMargin.right() : st::msgMargin.left()), width = _history->width - st::msgMargin.left() - st::msgMargin.right(), mwidth = st::msgMaxWidth; - if (justMedia()) { - if (_media->maxWidth() > mwidth) mwidth = _media->maxWidth(); - if (_media->currentWidth() < mwidth) mwidth = _media->currentWidth(); - } - if (width > mwidth) { - if (fromChannel()) { -// left += (width - mwidth) / 2; - } else if (out()) { - left += width - mwidth; - } - width = mwidth; - } - - if (displayFromPhoto()) { // from user left photo - if (x >= left && x < left + st::msgPhotoSize && y >= _height - st::msgMargin.bottom() - st::msgPhotoSize && y < _height - st::msgMargin.bottom()) { + int32 left = 0, width = 0; + countPositionAndSize(left, width); + if (displayFromPhoto()) { + if (x >= left - st::msgPhotoSkip && x < left - st::msgPhotoSkip + st::msgPhotoSize && y >= _height - st::msgMargin.bottom() - st::msgPhotoSize && y < _height - st::msgMargin.bottom()) { lnk = _from->lnk; return; } -// width -= st::msgPhotoSkip; - left += st::msgPhotoSkip; } if (width < 1) return; - if (width >= _maxw) { - if (fromChannel()) { -// left += (width - _maxw) / 2; - } else if (out()) { - left += width - _maxw; + if (drawBubble()) { + QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); + if (displayFromName()) { // from user left name + if (y >= r.top() + st::msgPadding.top() && y < r.top() + st::msgPadding.top() + st::msgNameFont->height) { + if (x >= r.left() + st::msgPadding.left() && x < r.left() + r.width() - st::msgPadding.right() && x < r.left() + st::msgPadding.left() + _from->nameText.maxWidth()) { + lnk = _from->lnk; + return; + } + if (via() && !toHistoryForwarded() && x >= r.left() + st::msgPadding.left() + _from->nameText.maxWidth() + st::msgServiceFont->spacew && x < r.left() + st::msgPadding.left() + _from->nameText.maxWidth() + st::msgServiceFont->spacew + via()->width) { + lnk = via()->lnk; + return; + } + } + r.setTop(r.top() + st::msgNameFont->height); } - width = _maxw; - } - if (justMedia()) { + getStateFromMessageText(lnk, state, x, y, r); + } else { _media->getState(lnk, state, x - left, y - st::msgMargin.top(), this); - return; } - QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); - if (displayFromName()) { // from user left name - if (x >= r.left() + st::msgPadding.left() && y >= r.top() + st::msgPadding.top() && y < r.top() + st::msgPadding.top() + st::msgNameFont->height && x < r.left() + r.width() - st::msgPadding.right() && x < r.left() + st::msgPadding.left() + _from->nameText.maxWidth()) { - lnk = _from->lnk; - return; - } - r.setTop(r.top() + st::msgNameFont->height); - } - - getStateFromMessageText(lnk, state, x, y, r); } void HistoryMessage::getStateFromMessageText(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const QRect &r) const { - bool inDate = HistoryMessage::pointInTime(r.x() + r.width(), r.y() + r.height(), x, y, InfoDisplayDefault); + bool inDate = false; QRect trect(r.marginsAdded(-st::msgPadding)); + + if (!displayFromName() && via() && !toHistoryForwarded()) { + if (x >= trect.left() && y >= trect.top() && y < trect.top() + st::msgNameFont->height && x < trect.left() + via()->width) { + lnk = via()->lnk; + return; + } + trect.setTop(trect.top() + st::msgNameFont->height); + } + TextLinkPtr medialnk; if (_media && _media->isDisplayed()) { - if (y >= trect.bottom() - _media->height() && y < trect.bottom()) { - _media->getState(lnk, state, x - trect.left(), y + _media->height() - trect.bottom(), this); + 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() - st::msgPadding.bottom()); + trect.setBottom(trect.bottom() - _media->height()); + } else { + inDate = HistoryMessage::pointInTime(r.x() + r.width(), r.y() + r.height(), x, y, InfoDisplayDefault); } + textstyleSet(&((out() && !fromChannel()) ? st::outTextStyle : st::inTextStyle)); bool inText = false; _text.getState(lnk, inText, x - trect.x(), y - trect.y(), trect.width()); @@ -7005,43 +6715,26 @@ void HistoryMessage::getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, symbol = 0; after = false; upon = false; - if (justMedia()) return; + if (drawBubble()) { + int32 left = 0, width = 0; + countPositionAndSize(left, width); + if (width < 1) return; - int32 left = fromChannel() ? (st::msgMargin.left() + st::msgMargin.left()) / 2 : (out() ? st::msgMargin.right() : st::msgMargin.left()), width = _history->width - st::msgMargin.left() - st::msgMargin.right(), mwidth = st::msgMaxWidth; - if (width > mwidth) { - if (fromChannel()) { -// left += (width - mwidth) / 2; - } else if (out()) { - left += width - mwidth; + QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); + if (displayFromName()) { // from user left name + r.setTop(r.top() + st::msgNameFont->height); + } else if (via() && !toHistoryForwarded()) { + r.setTop(r.top() + st::msgNameFont->height); } - width = mwidth; - } - - if (displayFromPhoto()) { // from user left photo -// width -= st::msgPhotoSkip; - left += st::msgPhotoSkip; - } - if (width < 1) return; - - if (width >= _maxw) { - if (fromChannel()) { -// left += (width - _maxw) / 2; - } else if (out()) { - left += width - _maxw; + QRect trect(r.marginsAdded(-st::msgPadding)); + if (_media && _media->isDisplayed()) { + trect.setBottom(trect.bottom() - _media->height()); } - width = _maxw; + + textstyleSet(&((out() && !fromChannel()) ? st::outTextStyle : st::inTextStyle)); + _text.getSymbol(symbol, after, upon, x - trect.x(), y - trect.y(), trect.width()); + textstyleRestore(); } - QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); - if (displayFromName()) { // from user left name - r.setTop(r.top() + st::msgNameFont->height); - } - QRect trect(r.marginsAdded(-st::msgPadding)); - if (_media && _media->isDisplayed()) { - trect.setBottom(trect.bottom() - _media->height() - st::msgPadding.bottom()); - } - textstyleSet(&((out() && !fromChannel()) ? st::outTextStyle : st::inTextStyle)); - _text.getSymbol(symbol, after, upon, x - trect.x(), y - trect.y(), trect.width()); - textstyleRestore(); } void HistoryMessage::drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const { @@ -7060,7 +6753,7 @@ void HistoryMessage::drawInDialog(Painter &p, const QRect &r, bool act, const Hi if (r.width()) { textstyleSet(&(act ? st::dlgActiveTextStyle : st::dlgTextStyle)); p.setFont(st::dlgHistFont->f); - p.setPen((act ? st::dlgActiveColor : (justMedia() ? st::dlgSystemColor : st::dlgTextColor))->p); + p.setPen((act ? st::dlgActiveColor : (emptyText() ? st::dlgSystemColor : st::dlgTextColor))->p); cache.drawElided(p, r.left(), r.top(), r.width(), r.height() / st::dlgHistFont->height); textstyleRestore(); } @@ -7079,33 +6772,32 @@ QString HistoryMessage::notificationText() const { HistoryMessage::~HistoryMessage() { if (_media) { _media->unregItem(this); - delete _media; + deleteAndMark(_media); } + deleteAndMark(_via); if (_flags & MTPDmessage::flag_reply_markup) { App::clearReplyMarkup(channelId(), id); } } -HistoryForwarded::HistoryForwarded(History *history, HistoryBlock *block, const MTPDmessage &msg) : HistoryMessage(history, block, msg) +HistoryForwarded::HistoryForwarded(History *history, HistoryBlock *block, const MTPDmessage &msg) +: HistoryMessage(history, block, msg) , fwdDate(::date(msg.vfwd_date)) , fwdFrom(App::peer(peerFromMTP(msg.vfwd_from_id))) , fwdFromVersion(fwdFrom->nameVersion) -, fromWidth(st::msgServiceFont->width(lang(lng_forwarded_from)) + st::msgServiceFont->spacew) -{ - fwdNameUpdated(); +, fromWidth(st::msgServiceFont->width(lang(lng_forwarded_from)) + st::msgServiceFont->spacew) { } -HistoryForwarded::HistoryForwarded(History *history, HistoryBlock *block, MsgId id, QDateTime date, int32 from, HistoryMessage *msg) : HistoryMessage(history, block, id, newMessageFlags(history->peer) | (!history->peer->isChannel() && msg->getMedia() && (msg->getMedia()->type() == MediaTypeAudio/* || msg->getMedia()->type() == MediaTypeVideo*/) ? MTPDmessage::flag_media_unread : 0), date, from, msg->HistoryMessage::originalText(), msg->HistoryMessage::originalEntities(), msg->getMedia()) +HistoryForwarded::HistoryForwarded(History *history, HistoryBlock *block, MsgId id, QDateTime date, int32 from, HistoryMessage *msg) +: HistoryMessage(history, block, id, newForwardedFlags(history->peer, from, msg), msg->via() ? peerToUser(msg->viaBot()->id) : 0, date, from, msg->HistoryMessage::originalText(), msg->HistoryMessage::originalEntities(), msg->getMedia()) , fwdDate(msg->dateForwarded()) , fwdFrom(msg->fromForwarded()) , fwdFromVersion(fwdFrom->nameVersion) -, fromWidth(st::msgServiceFont->width(lang(lng_forwarded_from)) + st::msgServiceFont->spacew) -{ - fwdNameUpdated(); +, fromWidth(st::msgServiceFont->width(lang(lng_forwarded_from)) + st::msgServiceFont->spacew) { } QString HistoryForwarded::selectedText(uint32 selection) const { - if (selection != FullItemSel) return HistoryMessage::selectedText(selection); + if (selection != FullSelection) return HistoryMessage::selectedText(selection); QString result, original = HistoryMessage::selectedText(selection); result.reserve(lang(lng_forwarded_from).size() + fwdFrom->name.size() + 4 + original.size()); result.append('[').append(lang(lng_forwarded_from)).append(' ').append(fwdFrom->name).append(qsl("]\n")).append(original); @@ -7113,83 +6805,92 @@ QString HistoryForwarded::selectedText(uint32 selection) const { } void HistoryForwarded::initDimensions() { - HistoryMessage::initDimensions(); fwdNameUpdated(); + HistoryMessage::initDimensions(); + if (!_media) { + int32 _namew = st::msgPadding.left() + fromWidth + fwdFromName.maxWidth() + st::msgPadding.right(); + if (via()) { + _namew += st::msgServiceFont->spacew + via()->maxWidth; + } + if (_namew > _maxw) _maxw = _namew; + } } void HistoryForwarded::fwdNameUpdated() const { - fwdFromName.setText(st::msgServiceNameFont, App::peerName(fwdFrom), _textNameOptions); - if (justMedia()) return; - int32 _namew = fromWidth + fwdFromName.maxWidth() + st::msgPadding.left() + st::msgPadding.right(); - if (_namew > _maxw) _maxw = _namew; + QString fwdName((via() && fwdFrom->isUser()) ? fwdFrom->asUser()->firstName : App::peerName(fwdFrom)); + fwdFromName.setText(st::msgServiceNameFont, fwdName, _textNameOptions); + if (via()) { + int32 l = 0, w = 0; + countPositionAndSize(l, w); + via()->resize(w - st::msgPadding.left() - st::msgPadding.right() - fromWidth - fwdFromName.maxWidth() - st::msgServiceFont->spacew); + } } -void HistoryForwarded::draw(Painter &p, uint32 selection) const { - if (!justMedia() && fwdFrom->nameVersion > fwdFromVersion) { +void HistoryForwarded::draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const { + if (drawBubble() && fwdFrom->nameVersion > fwdFromVersion) { fwdNameUpdated(); fwdFromVersion = fwdFrom->nameVersion; } - HistoryMessage::draw(p, selection); + HistoryMessage::draw(p, r, selection, ms); } void HistoryForwarded::drawForwardedFrom(Painter &p, int32 x, int32 y, int32 w, bool selected) const { style::font serviceFont(st::msgServiceFont), serviceName(st::msgServiceNameFont); bool outbg = out() && !fromChannel(); - p.setPen((selected ? (outbg ? st::msgOutServiceSelColor : st::msgInServiceSelColor) : (outbg ? st::msgOutServiceColor : st::msgInServiceColor))->p); - p.setFont(serviceFont->f); + p.setPen((selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg))->p); + p.setFont(serviceFont); - if (w >= fromWidth) { + if (via() && w > fromWidth + fwdFromName.maxWidth() + serviceFont->spacew) { p.drawText(x, y + serviceFont->ascent, lang(lng_forwarded_from)); - p.setFont(serviceName->f); + p.setFont(serviceName); + fwdFromName.draw(p, x + fromWidth, y, w - fromWidth); + + p.drawText(x + fromWidth + fwdFromName.maxWidth() + serviceFont->spacew, y + serviceFont->ascent, via()->text); + } else if (w > fromWidth) { + p.drawText(x, y + serviceFont->ascent, lang(lng_forwarded_from)); + + p.setFont(serviceName); fwdFromName.drawElided(p, x + fromWidth, y, w - fromWidth); } else { p.drawText(x, y + serviceFont->ascent, serviceFont->elided(lang(lng_forwarded_from), w)); } } -void HistoryForwarded::drawMessageText(Painter &p, const QRect &trect, uint32 selection) const { - drawForwardedFrom(p, trect.x(), trect.y(), trect.width(), (selection == FullItemSel)); - - QRect realtrect(trect); - realtrect.setY(trect.y() + st::msgServiceNameFont->height); - HistoryMessage::drawMessageText(p, realtrect, selection); +void HistoryForwarded::drawMessageText(Painter &p, QRect trect, uint32 selection) const { + if (displayForwardedFrom()) { + drawForwardedFrom(p, trect.x(), trect.y(), trect.width(), (selection == FullSelection)); + trect.setY(trect.y() + st::msgServiceNameFont->height); + } + HistoryMessage::drawMessageText(p, trect, selection); } int32 HistoryForwarded::resize(int32 width) { HistoryMessage::resize(width); - - if (!justMedia()) _height += st::msgServiceNameFont->height; + if (drawBubble()) { + if (displayForwardedFrom()) { + if (emptyText() && !displayFromName()) { + _height += st::msgPadding.top() + st::msgServiceNameFont->height + st::mediaHeaderSkip; + } else { + _height += st::msgServiceNameFont->height; + } + if (via()) { + int32 l = 0, w = 0; + countPositionAndSize(l, w); + via()->resize(w - st::msgPadding.left() - st::msgPadding.right() - fromWidth - fwdFromName.maxWidth() - st::msgServiceFont->spacew); + } + } + } return _height; } bool HistoryForwarded::hasPoint(int32 x, int32 y) const { - if (!justMedia()) { - int32 left = fromChannel() ? (st::msgMargin.left() + st::msgMargin.left()) / 2 : (out() ? st::msgMargin.right() : st::msgMargin.left()), width = _history->width - st::msgMargin.left() - st::msgMargin.right(), mwidth = st::msgMaxWidth; - if (width > mwidth) { - if (fromChannel()) { -//f left += (width - mwidth) / 2; - } else if (out()) { - left += width - mwidth; - } - width = mwidth; - } - - if (displayFromPhoto()) { // from user left photo -// width -= st::msgPhotoSkip; - left += st::msgPhotoSkip; - } + if (drawBubble() && displayForwardedFrom()) { + int32 left = 0, width = 0; + countPositionAndSize(left, width); if (width < 1) return false; - if (width >= _maxw) { - if (fromChannel()) { -// left += (width - _maxw) / 2; - } else if (out()) { - left += width - _maxw; - } - width = _maxw; - } QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); return r.contains(x, y); } @@ -7200,34 +6901,16 @@ void HistoryForwarded::getState(TextLinkPtr &lnk, HistoryCursorState &state, int lnk = TextLinkPtr(); state = HistoryDefaultCursorState; - if (!justMedia()) { - int32 left = fromChannel() ? (st::msgMargin.left() + st::msgMargin.left()) / 2 : (out() ? st::msgMargin.right() : st::msgMargin.left()), width = _history->width - st::msgMargin.left() - st::msgMargin.right(), mwidth = st::msgMaxWidth; - if (width > mwidth) { - if (fromChannel()) { -// left += (width - mwidth) / 2; - } else if (out()) { - left += width - mwidth; - } - width = mwidth; - } - - if (displayFromPhoto()) { // from user left photo - if (x >= left && x < left + st::msgPhotoSize) { + if (drawBubble() && displayForwardedFrom()) { + int32 left = 0, width = 0; + countPositionAndSize(left, width); + if (displayFromPhoto()) { + if (x >= left - st::msgPhotoSkip && x < left - st::msgPhotoSkip + st::msgPhotoSize) { return HistoryMessage::getState(lnk, state, x, y); } -// width -= st::msgPhotoSkip; - left += st::msgPhotoSkip; } if (width < 1) return; - if (width >= _maxw) { - if (fromChannel()) { -// left += (width - _maxw) / 2; - } else if (out()) { - left += width - _maxw; - } - width = _maxw; - } QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); if (displayFromName()) { style::font nameFont(st::msgNameFont); @@ -7248,7 +6931,9 @@ void HistoryForwarded::getState(TextLinkPtr &lnk, HistoryCursorState &state, int void HistoryForwarded::getStateFromMessageText(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const QRect &r) const { QRect realr(r); - realr.setHeight(r.height() - st::msgServiceNameFont->height); + if (drawBubble() && displayForwardedFrom()) { + realr.setHeight(r.height() - st::msgServiceNameFont->height); + } HistoryMessage::getStateFromMessageText(lnk, state, x, y, realr); } @@ -7256,6 +6941,8 @@ void HistoryForwarded::getForwardedState(TextLinkPtr &lnk, HistoryCursorState &s state = HistoryDefaultCursorState; if (x >= fromWidth && x < w && x < fromWidth + fwdFromName.maxWidth()) { lnk = fwdFrom->lnk; + } else if (via() && x >= fromWidth + fwdFromName.maxWidth() + st::msgServiceFont->spacew && x < w && x < fromWidth + fwdFromName.maxWidth() + st::msgServiceFont->spacew + via()->maxWidth) { + lnk = via()->lnk; } else { lnk = TextLinkPtr(); } @@ -7266,31 +6953,11 @@ void HistoryForwarded::getSymbol(uint16 &symbol, bool &after, bool &upon, int32 after = false; upon = false; - if (!justMedia()) { - int32 left = fromChannel() ? (st::msgMargin.left() + st::msgMargin.left()) / 2 : (out() ? st::msgMargin.right() : st::msgMargin.left()), width = _history->width - st::msgMargin.left() - st::msgMargin.right(), mwidth = st::msgMaxWidth; - if (width > mwidth) { - if (fromChannel()) { -// left += (width - mwidth) / 2; - } else if (out()) { - left += width - mwidth; - } - width = mwidth; - } - - if (displayFromPhoto()) { // from user left photo -// width -= st::msgPhotoSkip; - left += st::msgPhotoSkip; - } + if (drawBubble() && displayForwardedFrom()) { + int32 left = 0, width = 0; + countPositionAndSize(left, width); if (width < 1) return; - if (width >= _maxw) { - if (fromChannel()) { -// left += (width - _maxw) / 2; - } else if (out()) { - left += width - _maxw; - } - width = _maxw; - } QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); if (displayFromName()) { style::font nameFont(st::msgNameFont); @@ -7311,25 +6978,39 @@ HistoryReply::HistoryReply(History *history, HistoryBlock *block, const MTPDmess , replyToMsg(0) , replyToVersion(0) , _maxReplyWidth(0) -{ +, _replyToVia(0) { if (!updateReplyTo() && App::api()) { App::api()->requestReplyTo(this, history->peer->asChannel(), replyToMsgId); } } -HistoryReply::HistoryReply(History *history, HistoryBlock *block, MsgId msgId, int32 flags, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc) : HistoryMessage(history, block, msgId, flags, date, from, doc) +HistoryReply::HistoryReply(History *history, HistoryBlock *block, MsgId msgId, int32 flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption) +: HistoryMessage(history, block, msgId, flags, viaBotId, date, from, doc, caption) , replyToMsgId(replyTo) , replyToMsg(0) , replyToVersion(0) , _maxReplyWidth(0) -{ +, _replyToVia(0) { if (!updateReplyTo() && App::api()) { App::api()->requestReplyTo(this, history->peer->asChannel(), replyToMsgId); } } +HistoryReply::HistoryReply(History *history, HistoryBlock *block, MsgId msgId, int32 flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption) +: HistoryMessage(history, block, msgId, flags, viaBotId, date, from, photo, caption) +, replyToMsgId(replyTo) +, replyToMsg(0) +, replyToVersion(0) +, _maxReplyWidth(0) +, _replyToVia(0) { + if (!updateReplyTo() && App::api()) { + App::api()->requestReplyTo(this, history->peer->asChannel(), replyToMsgId); + } + replyToNameUpdated(); +} + QString HistoryReply::selectedText(uint32 selection) const { - if (selection != FullItemSel || !replyToMsg) return HistoryMessage::selectedText(selection); + if (selection != FullSelection || !replyToMsg) return HistoryMessage::selectedText(selection); QString result, original = HistoryMessage::selectedText(selection); result.reserve(lang(lng_in_reply_to).size() + replyToMsg->from()->name.size() + 4 + original.size()); result.append('[').append(lang(lng_in_reply_to)).append(' ').append(replyToMsg->from()->name).append(qsl("]\n")).append(original); @@ -7337,52 +7018,60 @@ QString HistoryReply::selectedText(uint32 selection) const { } void HistoryReply::initDimensions() { - if (!replyToMsg) { - _maxReplyWidth = st::msgReplyBarSkip + st::msgDateFont->width(lang(replyToMsgId ? lng_profile_loading : lng_deleted_message)) + st::msgPadding.left() + st::msgPadding.right(); - } + replyToNameUpdated(); HistoryMessage::initDimensions(); - if (replyToMsg) { - replyToNameUpdated(); - } else if (!justMedia()) { - int maxw = _maxReplyWidth - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.left() + st::msgPadding.right(); - if (maxw > _maxw) _maxw = maxw; + if (!_media) { + int32 replyw = st::msgPadding.left() + _maxReplyWidth - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.right(); + if (replyToVia()) { + replyw += st::msgServiceFont->spacew + replyToVia()->maxWidth; + } + if (replyw > _maxw) _maxw = replyw; } } bool HistoryReply::updateReplyTo(bool force) { if (replyToMsg || !replyToMsgId) return true; replyToMsg = App::histItemById(channelId(), replyToMsgId); + if (replyToMsg) { App::historyRegReply(this, replyToMsg); replyToText.setText(st::msgFont, replyToMsg->inReplyText(), _textDlgOptions); replyToNameUpdated(); - + replyToLnk = TextLinkPtr(new MessageLink(replyToMsg->history()->peer->id, replyToMsg->id)); + if (!replyToMsg->toHistoryForwarded()) { + if (UserData *bot = replyToMsg->viaBot()) { + _replyToVia = new HistoryMessageVia(peerToUser(bot->id)); + } + } } else if (force) { replyToMsgId = 0; } if (force) { initDimensions(); - if (App::main()) App::main()->itemResized(this); + Notify::historyItemResized(this); } return (replyToMsg || !replyToMsgId); } void HistoryReply::replyToNameUpdated() const { - if (!replyToMsg) return; - replyToName.setText(st::msgServiceNameFont, App::peerName(replyToMsg->from()), _textNameOptions); - replyToVersion = replyToMsg->from()->nameVersion; - bool hasPreview = replyToMsg->getMedia() ? replyToMsg->getMedia()->hasReplyPreview() : false; - int previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0; + if (replyToMsg) { + QString name = (replyToVia() && replyToMsg->from()->isUser()) ? replyToMsg->from()->asUser()->firstName : App::peerName(replyToMsg->from()); + replyToName.setText(st::msgServiceNameFont, name, _textNameOptions); + replyToVersion = replyToMsg->from()->nameVersion; + bool hasPreview = replyToMsg->getMedia() ? replyToMsg->getMedia()->hasReplyPreview() : false; + int32 previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0; + int32 w = replyToName.maxWidth(); + if (replyToVia()) { + w += st::msgServiceFont->spacew + replyToVia()->maxWidth; + } - _maxReplyWidth = st::msgReplyPadding.left() + st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgReplyPadding.right(); - int32 _textw = st::msgReplyPadding.left() + st::msgReplyBarSkip + previewSkip + qMin(replyToText.maxWidth(), 4 * replyToName.maxWidth()) + st::msgReplyPadding.right(); - if (_textw > _maxReplyWidth) _maxReplyWidth = _textw; - if (!justMedia()) { - int maxw = _maxReplyWidth - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.left() + st::msgPadding.right(); - if (maxw > _maxw) _maxw = maxw; + _maxReplyWidth = previewSkip + qMax(w, qMin(replyToText.maxWidth(), 4 * w)); + } else { + _maxReplyWidth = st::msgDateFont->width(lang(replyToMsgId ? lng_profile_loading : lng_deleted_message)); } + _maxReplyWidth = st::msgReplyPadding.left() + st::msgReplyBarSkip + _maxReplyWidth + st::msgReplyPadding.right(); } int32 HistoryReply::replyToWidth() const { @@ -7403,19 +7092,25 @@ HistoryItem *HistoryReply::replyToMessage() const { void HistoryReply::replyToReplaced(HistoryItem *oldItem, HistoryItem *newItem) { if (replyToMsg == oldItem) { + delete _replyToVia; + _replyToVia = 0; replyToMsg = newItem; if (!newItem) { replyToMsgId = 0; initDimensions(); + } else if (!replyToMsg->toHistoryForwarded()) { + if (UserData *bot = replyToMsg->viaBot()) { + _replyToVia = new HistoryMessageVia(peerToUser(bot->id)); + } } } } -void HistoryReply::draw(Painter &p, uint32 selection) const { +void HistoryReply::draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const { if (replyToMsg && replyToMsg->from()->nameVersion > replyToVersion) { replyToNameUpdated(); } - HistoryMessage::draw(p, selection); + HistoryMessage::draw(p, r, selection, ms); } void HistoryReply::drawReplyTo(Painter &p, int32 x, int32 y, int32 w, bool selected, bool likeService) const { @@ -7426,7 +7121,8 @@ void HistoryReply::drawReplyTo(Painter &p, int32 x, int32 y, int32 w, bool selec } else { bar = (selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor)); } - p.fillRect(x + st::msgReplyBarPos.x(), y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.width(), st::msgReplyBarSize.height(), bar->b); + QRect rbar(rtlrect(x + st::msgReplyBarPos.x(), y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.width(), st::msgReplyBarSize.height(), w + 2 * x)); + p.fillRect(rbar, bar); if (w > st::msgReplyBarSkip) { if (replyToMsg) { @@ -7436,7 +7132,7 @@ void HistoryReply::drawReplyTo(Painter &p, int32 x, int32 y, int32 w, bool selec if (hasPreview) { ImagePtr replyPreview = replyToMsg->getMedia()->replyPreview(); if (!replyPreview->isNull()) { - QRect to(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height()); + QRect to(rtlrect(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height(), w + 2 * x)); p.drawPixmap(to.x(), to.y(), replyPreview->pixSingle(replyPreview->width() / cIntRetinaFactor(), replyPreview->height() / cIntRetinaFactor(), to.width(), to.height())); if (selected) { App::roundRect(p, to, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); @@ -7445,78 +7141,76 @@ void HistoryReply::drawReplyTo(Painter &p, int32 x, int32 y, int32 w, bool selec } if (w > st::msgReplyBarSkip + previewSkip) { if (likeService) { - p.setPen(st::white->p); + p.setPen(st::white); } else { - p.setPen((selected ? (outbg ? st::msgOutServiceSelColor : st::msgInServiceSelColor) : (outbg ? st::msgOutServiceColor : st::msgInServiceColor))->p); + p.setPen(selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg)); + } + replyToName.drawLeftElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top(), w - st::msgReplyBarSkip - previewSkip, w + 2 * x); + if (replyToVia() && w > st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew) { + p.setFont(st::msgServiceFont); + p.drawText(x + st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew, y + st::msgReplyPadding.top() + st::msgServiceFont->ascent, replyToVia()->text); } - replyToName.drawElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top(), w - st::msgReplyBarSkip - previewSkip); HistoryMessage *replyToAsMsg = replyToMsg->toHistoryMessage(); if (likeService) { - } else if ((replyToAsMsg && replyToAsMsg->justMedia()) || replyToMsg->serviceMsg()) { - style::color date(selected ? (outbg ? st::msgOutSelectDateColor : st::msgInSelectDateColor) : (outbg ? st::msgOutDateColor : st::msgInDateColor)); - p.setPen(date->p); + } else if ((replyToAsMsg && replyToAsMsg->emptyText()) || replyToMsg->serviceMsg()) { + style::color date(outbg ? (selected ? st::msgOutDateFgSelected : st::msgOutDateFg) : (selected ? st::msgInDateFgSelected : st::msgInDateFg)); + p.setPen(date); } else { - p.setPen(st::msgColor->p); + p.setPen(st::msgColor); } - replyToText.drawElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top() + st::msgServiceNameFont->height, w - st::msgReplyBarSkip - previewSkip); + replyToText.drawLeftElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top() + st::msgServiceNameFont->height, w - st::msgReplyBarSkip - previewSkip, w + 2 * x); } } else { - p.setFont(st::msgDateFont->f); - style::color date(selected ? (outbg ? st::msgOutSelectDateColor : st::msgInSelectDateColor) : (outbg ? st::msgOutDateColor : st::msgInDateColor)); - if (likeService) { - p.setPen(st::white->p); - } else { - p.setPen(date->p); - } - p.drawText(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2 + st::msgDateFont->ascent, st::msgDateFont->elided(lang(replyToMsgId ? lng_profile_loading : lng_deleted_message), w - st::msgReplyBarSkip)); + p.setFont(st::msgDateFont); + style::color date(outbg ? (selected ? st::msgOutDateFgSelected : st::msgOutDateFg) : (selected ? st::msgInDateFgSelected : st::msgInDateFg)); + p.setPen(likeService ? st::white : date); + p.drawTextLeft(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2, w + 2 * x, st::msgDateFont->elided(lang(replyToMsgId ? lng_profile_loading : lng_deleted_message), w - st::msgReplyBarSkip)); } } } -void HistoryReply::drawMessageText(Painter &p, const QRect &trect, uint32 selection) const { +void HistoryReply::drawMessageText(Painter &p, QRect trect, uint32 selection) const { int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - drawReplyTo(p, trect.x(), trect.y(), trect.width(), (selection == FullItemSel)); + drawReplyTo(p, trect.x(), trect.y(), trect.width(), (selection == FullSelection)); - QRect realtrect(trect); - realtrect.setY(trect.y() + h); - HistoryMessage::drawMessageText(p, realtrect, selection); + trect.setY(trect.y() + h); + HistoryMessage::drawMessageText(p, trect, selection); } int32 HistoryReply::resize(int32 width) { HistoryMessage::resize(width); - if (!justMedia()) _height += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); + if (drawBubble()) { + if (emptyText() && !displayFromName() && !via()) { + _height += st::msgPadding.top() + st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom() + st::mediaHeaderSkip; + } else { + _height += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); + } + if (replyToVia()) { + bool hasPreview = replyToMsg->getMedia() ? replyToMsg->getMedia()->hasReplyPreview() : false; + int previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0; + replyToVia()->resize(width - st::msgPadding.left() - st::msgPadding.right() - st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew); + } + } return _height; } -bool HistoryReply::hasPoint(int32 x, int32 y) const { - if (!justMedia()) { - int32 left = fromChannel() ? (st::msgMargin.left() + st::msgMargin.left()) / 2 : (out() ? st::msgMargin.right() : st::msgMargin.left()), width = _history->width - st::msgMargin.left() - st::msgMargin.right(), mwidth = st::msgMaxWidth; - if (width > mwidth) { - if (fromChannel()) { -// left += (width - mwidth) / 2; - } else if (out()) { - left += width - mwidth; - } - width = mwidth; - } +void HistoryReply::resizeVia(int32 w) const { + if (!replyToVia()) return; - if (displayFromPhoto()) { // from user left photo -// width -= st::msgPhotoSkip; - left += st::msgPhotoSkip; - } + bool hasPreview = replyToMsg->getMedia() ? replyToMsg->getMedia()->hasReplyPreview() : false; + int previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0; + replyToVia()->resize(w - st::msgReplyBarSkip - previewSkip - replyToName.maxWidth() - st::msgServiceFont->spacew); +} + +bool HistoryReply::hasPoint(int32 x, int32 y) const { + if (drawBubble()) { + int32 left = 0, width = 0; + countPositionAndSize(left, width); if (width < 1) return false; - if (width >= _maxw) { - if (fromChannel()) { -// left += (width - _maxw) / 2; - } else if (out()) { - left += width - _maxw; - } - width = _maxw; - } QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); return r.contains(x, y); } @@ -7527,34 +7221,16 @@ void HistoryReply::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x lnk = TextLinkPtr(); state = HistoryDefaultCursorState; - if (!justMedia()) { - int32 left = fromChannel() ? (st::msgMargin.left() + st::msgMargin.left()) / 2 : (out() ? st::msgMargin.right() : st::msgMargin.left()), width = _history->width - st::msgMargin.left() - st::msgMargin.right(), mwidth = st::msgMaxWidth; - if (width > mwidth) { - if (fromChannel()) { -// left += (width - mwidth) / 2; - } else if (out()) { - left += width - mwidth; - } - width = mwidth; - } - + if (drawBubble()) { + int32 left = 0, width = 0; + countPositionAndSize(left, width); if (displayFromPhoto()) { // from user left photo - if (x >= left && x < left + st::msgPhotoSize) { + if (x >= left - st::msgPhotoSkip && x < left - st::msgPhotoSkip + st::msgPhotoSize) { return HistoryMessage::getState(lnk, state, x, y); } -// width -= st::msgPhotoSkip; - left += st::msgPhotoSkip; } if (width < 1) return; - if (width >= _maxw) { - if (fromChannel()) { -// left += (width - _maxw) / 2; - } else if (out()) { - left += width - _maxw; - } - width = _maxw; - } QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); if (displayFromName()) { style::font nameFont(st::msgNameFont); @@ -7590,31 +7266,11 @@ void HistoryReply::getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, i after = false; upon = false; - if (!justMedia()) { - int32 left = fromChannel() ? (st::msgMargin.left() + st::msgMargin.left()) / 2 : (out() ? st::msgMargin.right() : st::msgMargin.left()), width = _history->width - st::msgMargin.left() - st::msgMargin.right(), mwidth = st::msgMaxWidth; - if (width > mwidth) { - if (fromChannel()) { -// left += (width - mwidth) / 2; - } else if (out()) { - left += width - mwidth; - } - width = mwidth; - } - - if (displayFromPhoto()) { // from user left photo -// width -= st::msgPhotoSkip; - left += st::msgPhotoSkip; - } + if (drawBubble()) { + int32 left = 0, width = 0; + countPositionAndSize(left, width); if (width < 1) return; - if (width >= _maxw) { - if (fromChannel()) { -// left += (width - _maxw) / 2; - } else if (out()) { - left += width - _maxw; - } - width = _maxw; - } QRect r(left, st::msgMargin.top(), width, _height - st::msgMargin.top() - st::msgMargin.bottom()); if (displayFromName()) { style::font nameFont(st::msgNameFont); @@ -7637,6 +7293,7 @@ HistoryReply::~HistoryReply() { } else if (replyToMsgId && App::api()) { App::api()->itemRemoved(this); } + deleteAndMark(_replyToVia); } void HistoryServiceMsg::setMessageByAction(const MTPmessageAction &action) { @@ -7807,8 +7464,8 @@ void HistoryServiceMsg::initDimensions() { } QString HistoryServiceMsg::selectedText(uint32 selection) const { - uint16 selectedFrom = (selection == FullItemSel) ? 0 : (selection >> 16) & 0xFFFF; - uint16 selectedTo = (selection == FullItemSel) ? 0xFFFF : (selection & 0xFFFF); + uint16 selectedFrom = (selection == FullSelection) ? 0 : (selection >> 16) & 0xFFFF; + uint16 selectedTo = (selection == FullSelection) ? 0xFFFF : (selection & 0xFFFF); return _text.original(selectedFrom, selectedTo); } @@ -7828,14 +7485,15 @@ void HistoryServiceMsg::setServiceText(const QString &text) { initDimensions(); } -void HistoryServiceMsg::draw(Painter &p, uint32 selection) const { - uint64 ms = App::main() ? App::main()->animActiveTime(this) : 0; - if (ms) { - if (ms > st::activeFadeInDuration + st::activeFadeOutDuration) { +void HistoryServiceMsg::draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const { + uint64 animms = App::main() ? App::main()->animActiveTimeStart(this) : 0; + if (animms > 0 && animms <= ms) { + animms = ms - animms; + if (animms > st::activeFadeInDuration + st::activeFadeOutDuration) { App::main()->stopAnimActive(); } else { textstyleSet(&st::inTextStyle); - float64 dt = (ms > st::activeFadeInDuration) ? (1 - (ms - st::activeFadeInDuration) / float64(st::activeFadeOutDuration)) : (ms / float64(st::activeFadeInDuration)); + float64 dt = (animms > st::activeFadeInDuration) ? (1 - (animms - st::activeFadeInDuration) / float64(st::activeFadeOutDuration)) : (animms / float64(st::activeFadeInDuration)); float64 o = p.opacity(); p.setOpacity(o * dt); p.fillRect(0, 0, _history->width, _height, textstyleCurrent()->selectOverlay->b); @@ -7851,8 +7509,9 @@ void HistoryServiceMsg::draw(Painter &p, uint32 selection) const { if (_media) { height -= st::msgServiceMargin.top() + _media->height(); p.save(); - p.translate(st::msgServiceMargin.left() + (width - _media->maxWidth()) / 2, st::msgServiceMargin.top() + height + st::msgServiceMargin.top()); - _media->draw(p, this, selection == FullItemSel); + int32 left = st::msgServiceMargin.left() + (width - _media->maxWidth()) / 2, top = st::msgServiceMargin.top() + height + st::msgServiceMargin.top(); + p.translate(left, top); + _media->draw(p, this, r.translated(-left, -top), selection == FullSelection, ms); p.restore(); } @@ -7862,13 +7521,13 @@ void HistoryServiceMsg::draw(Painter &p, uint32 selection) const { left += (width - _maxw) / 2; width = _maxw; } - App::roundRect(p, left, st::msgServiceMargin.top(), width, height, App::msgServiceBg(), (selection == FullItemSel) ? ServiceSelectedCorners : ServiceCorners); + App::roundRect(p, left, st::msgServiceMargin.top(), width, height, App::msgServiceBg(), (selection == FullSelection) ? ServiceSelectedCorners : ServiceCorners); p.setBrush(Qt::NoBrush); p.setPen(st::msgServiceColor->p); p.setFont(st::msgServiceFont->f); - uint16 selectedFrom = (selection == FullItemSel) ? 0 : (selection >> 16) & 0xFFFF; - uint16 selectedTo = (selection == FullItemSel) ? 0 : selection & 0xFFFF; + uint16 selectedFrom = (selection == FullSelection) ? 0 : (selection >> 16) & 0xFFFF; + uint16 selectedTo = (selection == FullSelection) ? 0 : selection & 0xFFFF; _text.draw(p, trect.x(), trect.y(), trect.width(), Qt::AlignCenter, 0, -1, selectedFrom, selectedTo); textstyleRestore(); } @@ -7970,7 +7629,14 @@ HistoryServiceMsg::~HistoryServiceMsg() { } HistoryDateMsg::HistoryDateMsg(History *history, HistoryBlock *block, const QDate &date) : -HistoryServiceMsg(history, block, clientMsgId(), QDateTime(date), langDayOfMonth(date)) { +HistoryServiceMsg(history, block, clientMsgId(), QDateTime(date), langDayOfMonthFull(date)) { +} + +void HistoryDateMsg::setDate(const QDateTime &date) { + if (this->date.date() != date.date()) { + setServiceText(langDayOfMonthFull(date.date())); + } + HistoryServiceMsg::setDate(date); } HistoryItem *createDayServiceMsg(History *history, HistoryBlock *block, QDateTime date) { @@ -8053,7 +7719,7 @@ HistoryServiceMsg(history, block, clientMsgId(), date, qsl("-")), _wasMinId(wasMinId) { } -void HistoryCollapse::draw(Painter &p, uint32 selection) const { +void HistoryCollapse::draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const { } void HistoryCollapse::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const { @@ -8089,7 +7755,7 @@ void HistoryUnreadBar::setCount(int32 count) { text = lng_unread_bar(lt_count, count); } -void HistoryUnreadBar::draw(Painter &p, uint32 selection) const { +void HistoryUnreadBar::draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const { p.fillRect(0, st::lineWidth, _history->width, st::unreadBarHeight - 2 * st::lineWidth, st::unreadBarBG->b); p.fillRect(0, st::unreadBarHeight - st::lineWidth, _history->width, st::lineWidth, st::unreadBarBorder->b); p.setFont(st::unreadBarFont->f); diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index 8abc9b95b3..360d2cedc7 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -24,18 +24,8 @@ void historyInit(); class HistoryItem; -void startGif(HistoryItem *row, const FileLocation &file); -void itemRemovedGif(HistoryItem *item); -void itemReplacedGif(HistoryItem *oldItem, HistoryItem *newItem); -void stopGif(); - -static const uint32 FullItemSel = 0xFFFFFFFF; - typedef QMap SelectedItemSet; -extern TextParseOptions _textNameOptions, _textDlgOptions; -extern TextParseOptions _historyTextOptions, _historyBotOptions, _historyTextNoMonoOptions, _historyBotNoMonoOptions; - #include "structs.h" enum NewMessageType { @@ -45,16 +35,16 @@ enum NewMessageType { }; class History; -class Histories : public Animated { +class Histories { public: typedef QHash Map; Map map; - Histories() : unreadFull(0), unreadMuted(0) { + Histories() : _a_typings(animation(this, &Histories::step_typings)), unreadFull(0), unreadMuted(0) { } void regSendAction(History *history, UserData *user, const MTPSendMessageAction &action); - bool animStep(float64 ms); + void step_typings(uint64 ms, bool timer); History *find(const PeerId &peerId); History *findOrInsert(const PeerId &peerId, int32 unreadCount, int32 maxInboxRead); @@ -62,7 +52,6 @@ public: void clear(); void remove(const PeerId &peer); ~Histories() { - clear(); unreadFull = unreadMuted = 0; } @@ -71,6 +60,7 @@ public: typedef QMap TypingHistories; // when typing in this history started TypingHistories typing; + Animation _a_typings; int32 unreadFull, unreadMuted; }; @@ -107,6 +97,7 @@ enum HistoryMediaType { MediaTypeContact, MediaTypeAudio, MediaTypeDocument, + MediaTypeGif, MediaTypeSticker, MediaTypeImageLink, MediaTypeWebPage, @@ -125,17 +116,6 @@ enum MediaOverviewType { OverviewCount }; -inline MediaOverviewType mediaToOverviewType(HistoryMediaType t) { - switch (t) { - case MediaTypePhoto: return OverviewPhotos; - case MediaTypeVideo: return OverviewVideos; - case MediaTypeDocument: return OverviewDocuments; -// case MediaTypeSticker: return OverviewDocuments; - case MediaTypeAudio: return OverviewAudios; - } - return OverviewCount; -} - inline MTPMessagesFilter typeToMediaFilter(MediaOverviewType &type) { switch (type) { case OverviewPhotos: return MTP_inputMessagesFilterPhotos(); @@ -172,6 +152,12 @@ class HistoryMedia; class HistoryMessage; class HistoryUnreadBar; +enum AddToOverviewMethod { + AddToOverviewNew, // when new message is added to history + AddToOverviewFront, // when old messages slice was received + AddToOverviewBack, // when new messages slice was received and it is the last one, we index all media +}; + class ChannelHistory; class History { public: @@ -201,20 +187,22 @@ public: clear(); } - HistoryItem *createItem(HistoryBlock *block, const MTPMessage &msg, bool applyServiceAction, bool returnExisting = false); + HistoryItem *createItem(HistoryBlock *block, const MTPMessage &msg, bool applyServiceAction); HistoryItem *createItemForwarded(HistoryBlock *block, MsgId id, QDateTime date, int32 from, HistoryMessage *msg); - HistoryItem *createItemDocument(HistoryBlock *block, MsgId id, int32 flags, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc); + HistoryItem *createItemDocument(HistoryBlock *block, MsgId id, int32 flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption); + HistoryItem *createItemPhoto(HistoryBlock *block, MsgId id, int32 flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption); HistoryItem *addNewService(MsgId msgId, QDateTime date, const QString &text, int32 flags = 0, HistoryMedia *media = 0, bool newMsg = true); HistoryItem *addNewMessage(const MTPMessage &msg, NewMessageType type); HistoryItem *addToHistory(const MTPMessage &msg); HistoryItem *addNewForwarded(MsgId id, QDateTime date, int32 from, HistoryMessage *item); - HistoryItem *addNewDocument(MsgId id, int32 flags, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc); + HistoryItem *addNewDocument(MsgId id, int32 flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption); + HistoryItem *addNewPhoto(MsgId id, int32 flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption); void addOlderSlice(const QVector &slice, const QVector *collapsed); void addNewerSlice(const QVector &slice, const QVector *collapsed); - void addToOverview(HistoryItem *item, MediaOverviewType type); - bool addToOverviewFront(HistoryItem *item, MediaOverviewType type); + bool addToOverview(MediaOverviewType type, MsgId msgId, AddToOverviewMethod method); + void eraseFromOverview(MediaOverviewType type, MsgId msgId); void newItemAdded(HistoryItem *item); void unregTyping(UserData *from); @@ -248,7 +236,7 @@ public: MsgId maxMsgId() const; MsgId msgIdForRead() const; - int32 geomResize(int32 newWidth, int32 *ytransform = 0, HistoryItem *resizedItem = 0); // return new size + int32 geomResize(int32 newWidth, int32 *ytransform = 0, const HistoryItem *resizedItem = 0); // return new size void removeNotification(HistoryItem *item) { if (!notifies.isEmpty()) { @@ -275,24 +263,8 @@ public: if (!notifies.isEmpty() && notifies.back() == item) notifies.pop_back(); } - void itemReplaced(HistoryItem *old, HistoryItem *item) { - if (!notifies.isEmpty()) { - for (NotifyQueue::iterator i = notifies.begin(), e = notifies.end(); i != e; ++i) { - if ((*i) == old) { - *i = item; - break; - } - } - } - if (lastMsg == old) { - lastMsg = item; - } - // showFrom can't be detached - } - void paintDialog(Painter &p, int32 w, bool sel) const; - void eraseFromOverview(MediaOverviewType type, MsgId msgId); - bool updateTyping(uint64 ms = 0, uint32 dots = 0, bool force = false); + bool updateTyping(uint64 ms, bool force = false); void clearLastKeyboard(); typedef QList Blocks; @@ -338,7 +310,7 @@ public: SendActionUsers sendActions; QString typingStr; Text typingText; - uint32 typingFrame; + uint32 typingDots; QMap mySendActions; typedef QList MediaOverview; @@ -759,7 +731,7 @@ public: } void removeItem(HistoryItem *item); - int32 geomResize(int32 newWidth, int32 *ytransform, HistoryItem *resizedItem); // return new size + int32 geomResize(int32 newWidth, int32 *ytransform, const HistoryItem *resizedItem); // return new size int32 y, height; History *history; }; @@ -767,42 +739,29 @@ public: class HistoryElem { public: - HistoryElem() : _height(0), _maxw(0) { + HistoryElem() : _maxw(0), _minh(0), _height(0) { } - int32 height() const { - return _height; - } int32 maxWidth() const { return _maxw; } int32 minHeight() const { return _minh; } + int32 height() const { + return _height; + } virtual ~HistoryElem() { } protected: - mutable int32 _height, _maxw, _minh; + mutable int32 _maxw, _minh, _height; + HistoryElem &operator=(const HistoryElem &); }; -class ItemAnimations : public Animated { -public: - - bool animStep(float64 ms); - uint64 animate(const HistoryItem *item, uint64 ms); - void remove(const HistoryItem *item); - -private: - typedef QMap Animations; - Animations _animations; -}; - -ItemAnimations &itemAnimations(); - class HistoryReply; // dynamic_cast optimize class HistoryMessage; // dynamic_cast optimize class HistoryForwarded; // dynamic_cast optimize @@ -839,12 +798,13 @@ public: virtual void initDimensions() = 0; virtual int32 resize(int32 width) = 0; // return new height - virtual void draw(Painter &p, uint32 selection) const = 0; + virtual void draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const = 0; - History *history() { - return _history; + virtual UserData *viaBot() const { + return 0; } - const History *history() const { + + History *history() const { return _history; } PeerData *from() const { @@ -856,7 +816,7 @@ public: const HistoryBlock *block() const { return _block; } - void destroy(); + virtual void destroy(); void detach(); void detachFast(); bool detached() const { @@ -906,7 +866,7 @@ public: return _history->isChannel() && isImportantChannelMessage(id, _flags); } bool indexInOverview() const { - return (!history()->isChannel() || history()->isMegagroup() || fromChannel()); + return (id > 0) && (!history()->isChannel() || history()->isMegagroup() || fromChannel()); } virtual bool needCheck() const { @@ -927,13 +887,23 @@ public: virtual uint32 adjustSelection(uint16 from, uint16 to, TextSelectType type) const { return (from << 16) | to; } + virtual void linkOver(const TextLinkPtr &lnk) { + } + virtual void linkOut(const TextLinkPtr &lnk) { + } virtual HistoryItemType type() const { return HistoryItemMsg; } virtual bool serviceMsg() const { return false; } - virtual void updateMedia(const MTPMessageMedia *media, bool allowEmitResize) { + virtual void updateMedia(const MTPMessageMedia *media) { + } + virtual int32 addToOverview(AddToOverviewMethod method) { + return 0; + } + virtual bool hasBubble() const { + return false; } virtual QString selectedText(uint32 selection) const { @@ -946,11 +916,14 @@ public: return inDialogsText(); } - virtual void drawInfo(Painter &p, int32 right, int32 bottom, bool selected, InfoDisplayType type) const { + virtual void drawInfo(Painter &p, int32 right, int32 bottom, int32 width, bool selected, InfoDisplayType type) const { } - virtual void setViewsCount(int32 count) { + virtual void setViewsCount(int32 count, bool reinit = true) { } virtual void setId(MsgId newId); + virtual void setDate(const QDateTime &date) { // for date items + this->date = date; + } virtual void drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const = 0; virtual QString notificationHeader() const { return QString(); @@ -1002,9 +975,6 @@ public: virtual int32 timeLeft() const { return 0; } - virtual QString timeText() const { - return QString(); - } virtual int32 timeWidth() const { return 0; } @@ -1028,10 +998,6 @@ public: return textcmdSkipBlock(skipBlockWidth(), skipBlockHeight()); } - virtual bool animating() const { - return false; - } - virtual HistoryMessage *toHistoryMessage() { // dynamic_cast optimize return 0; } @@ -1051,13 +1017,15 @@ public: return 0; } - bool displayFromName() const { + bool hasFromName() const { return (!out() || fromChannel()) && !history()->peer->isUser(); } bool displayFromPhoto() const { return !out() && !history()->peer->isUser() && !fromChannel(); } + void clipCallback(ClipReaderNotification notification); + virtual ~HistoryItem(); protected: @@ -1076,6 +1044,8 @@ class MessageLink : public ITextLink { public: MessageLink(PeerId peer, MsgId msgid) : _peer(peer), _msgid(msgid) { } + MessageLink(HistoryItem *item) : _peer(item->history()->peer->id), _msgid(item->id) { + } void onClick(Qt::MouseButton button) const; PeerId peer() const { return _peer; @@ -1096,50 +1066,102 @@ public: CommentsLink(HistoryItem *item) : _item(item) { } void onClick(Qt::MouseButton button) const; - + private: HistoryItem *_item; }; -HistoryItem *regItem(HistoryItem *item, bool returnExisting = false); +HistoryItem *regItem(HistoryItem *item); + +class RadialAnimation { +public: + + RadialAnimation(AnimationCreator creator); + + float64 opacity() const { + return _opacity; + } + bool animating() const { + return _animation.animating(); + } + + void start(float64 prg); + void update(float64 prg, bool finished, uint64 ms); + void stop(); + + void step(uint64 ms); + void step() { + step(getms()); + } + + void draw(Painter &p, const QRect &inner, int32 thickness, const style::color &color); + +private: + + uint64 _firstStart, _lastStart, _lastTime; + float64 _opacity; + anim::ivalue a_arcEnd, a_arcStart; + Animation _animation; + +}; class HistoryMedia : public HistoryElem { public: - HistoryMedia(int32 width = 0) : w(width) { + HistoryMedia() : _width(0) { } - HistoryMedia(const HistoryMedia &other) : w(0) { + HistoryMedia(const HistoryMedia &other) : _width(0) { } virtual HistoryMediaType type() const = 0; virtual const QString inDialogsText() const = 0; virtual const QString inHistoryText() const = 0; - virtual bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const = 0; + + bool hasPoint(int32 x, int32 y, const HistoryItem *parent) const { + return (x >= 0 && y >= 0 && x < _width && y < _height); + } + virtual bool isDisplayed() const { return true; } - virtual int32 countHeight(const HistoryItem *parent, int32 width = -1) const { - return height(); - } virtual void initDimensions(const HistoryItem *parent) = 0; virtual int32 resize(int32 width, const HistoryItem *parent) { // return new height - w = qMin(width, _maxw); + _width = qMin(width, _maxw); return _height; } - virtual void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const = 0; - virtual void draw(Painter &p, const HistoryItem *parent, bool selected, int32 width = -1) const = 0; + virtual void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const = 0; + virtual void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const = 0; + + virtual void linkOver(HistoryItem *parent, const TextLinkPtr &lnk) { + } + virtual void linkOut(HistoryItem *parent, const TextLinkPtr &lnk) { + } + virtual bool uploading() const { return false; } virtual HistoryMedia *clone() const = 0; + virtual DocumentData *getDocument() { + return 0; + } + virtual ClipReader *getClipReader() { + return 0; + } + + virtual bool playInline(HistoryItem *item) { + return false; + } + virtual void stopInline(HistoryItem *item) { + } + virtual void regItem(HistoryItem *item) { } virtual void unregItem(HistoryItem *item) { } - virtual void updateFrom(const MTPMessageMedia &media) { + virtual void updateFrom(const MTPMessageMedia &media, HistoryItem *parent) { } virtual bool isImageLink() const { @@ -1159,237 +1181,545 @@ public: virtual QString getCaption() const { return QString(); } + virtual bool needsBubble(const HistoryItem *parent) const = 0; + virtual bool customInfoLayout() const = 0; + virtual QMargins bubbleMargins() const { + return QMargins(); + } + virtual bool hideFromName() const { + return false; + } + virtual bool hideForwardedFrom() const { + return false; + } int32 currentWidth() const { - return qMin(w, _maxw); + return _width; } protected: - int32 w; + int32 _width; }; -class HistoryPhoto : public HistoryMedia { +inline MediaOverviewType mediaToOverviewType(HistoryMedia *media) { + switch (media->type()) { + case MediaTypePhoto: return OverviewPhotos; + case MediaTypeVideo: return OverviewVideos; + case MediaTypeDocument: return media->getDocument()->song() ? OverviewAudioDocuments : OverviewDocuments; + case MediaTypeGif: return media->getDocument()->isGifv() ? OverviewCount : OverviewDocuments; +// case MediaTypeSticker: return OverviewDocuments; + case MediaTypeAudio: return OverviewAudios; + } + return OverviewCount; +} + +class HistoryFileMedia : public HistoryMedia { public: - HistoryPhoto(const MTPDphoto &photo, const QString &caption, HistoryItem *parent); + HistoryFileMedia(); + + void linkOver(HistoryItem *parent, const TextLinkPtr &lnk); + void linkOut(HistoryItem *parent, const TextLinkPtr &lnk); + + ~HistoryFileMedia(); + +protected: + + TextLinkPtr _openl, _savel, _cancell; + void setLinks(ITextLink *openl, ITextLink *savel, ITextLink *cancell); + + // >= 0 will contain download / upload string, _statusSize = loaded bytes + // < 0 will contain played string, _statusSize = -(seconds + 1) played + // 0x7FFFFFF0 will contain status for not yet downloaded file + // 0x7FFFFFF1 will contain status for already downloaded file + // 0x7FFFFFF2 will contain status for failed to download / upload file + mutable int32 _statusSize; + mutable QString _statusText; + + // duration = -1 - no duration, duration = -2 - "GIF" duration + void setStatusSize(int32 newSize, int32 fullSize, int32 duration, qint64 realDuration) const; + + void step_thumbOver(const HistoryItem *parent, float64 ms, bool timer); + void step_radial(const HistoryItem *parent, uint64 ms, bool timer); + + void ensureAnimation(const HistoryItem *parent) const; + void checkAnimationFinished(); + + bool isRadialAnimation(uint64 ms) const { + if (!_animation || !_animation->radial.animating()) return false; + + _animation->radial.step(ms); + return _animation && _animation->radial.animating(); + } + bool isThumbAnimation(uint64 ms) const { + if (!_animation || !_animation->_a_thumbOver.animating()) return false; + + _animation->_a_thumbOver.step(ms); + return _animation && _animation->_a_thumbOver.animating(); + } + + virtual float64 dataProgress() const = 0; + virtual bool dataFinished() const = 0; + virtual bool dataLoaded() const = 0; + + struct AnimationData { + AnimationData(AnimationCreator thumbOverCallbacks, AnimationCreator radialCallbacks) : a_thumbOver(0, 0) + , _a_thumbOver(thumbOverCallbacks) + , radial(radialCallbacks) { + } + anim::fvalue a_thumbOver; + Animation _a_thumbOver; + + RadialAnimation radial; + }; + mutable AnimationData *_animation; + +private: + + HistoryFileMedia(const HistoryFileMedia &other); + +}; + +class HistoryPhoto : public HistoryFileMedia { +public: + + HistoryPhoto(PhotoData *photo, const QString &caption, const HistoryItem *parent); HistoryPhoto(PeerData *chat, const MTPDphoto &photo, int32 width = 0); - + HistoryPhoto(const HistoryPhoto &other); void init(); - void initDimensions(const HistoryItem *parent); - - void draw(Painter &p, const HistoryItem *parent, bool selected, int32 width = -1) const; - int32 resize(int32 width, const HistoryItem *parent); HistoryMediaType type() const { return MediaTypePhoto; } + HistoryMedia *clone() const { + return new HistoryPhoto(*this); + } + + void initDimensions(const HistoryItem *parent); + int32 resize(int32 width, const HistoryItem *parent); + + void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const; + void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const; + const QString inDialogsText() const; const QString inHistoryText() const; - bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const; - void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const; - HistoryMedia *clone() const; PhotoData *photo() const { - return data; + return _data; } - void updateFrom(const MTPMessageMedia &media); + void updateFrom(const MTPMessageMedia &media, HistoryItem *parent); - TextLinkPtr lnk() const { - return openl; - } - - virtual bool animating() const { - if (data->full->loaded()) return false; - return data->full->loading() ? true : !data->medium->loaded(); - } + void regItem(HistoryItem *item); + void unregItem(HistoryItem *item); bool hasReplyPreview() const { - return !data->thumb->isNull(); + return !_data->thumb->isNull(); } ImagePtr replyPreview(); QString getCaption() const { return _caption.original(); } + bool needsBubble(const HistoryItem *parent) const { + return !_caption.isEmpty() || parent->toHistoryForwarded() || parent->toHistoryReply() || parent->viaBot(); + } + bool customInfoLayout() const { + return _caption.isEmpty(); + } + bool hideFromName() const { + return true; + } + +protected: + + float64 dataProgress() const { + return _data->progress(); + } + bool dataFinished() const { + return !_data->loading() && !_data->uploading(); + } + bool dataLoaded() const { + return _data->loaded(); + } private: - int16 pixw, pixh; - PhotoData *data; + PhotoData *_data; + int16 _pixw, _pixh; Text _caption; - TextLinkPtr openl; }; -QString formatSizeText(qint64 size); -QString formatDownloadText(qint64 ready, qint64 total); -QString formatDurationText(qint64 duration); - -class HistoryVideo : public HistoryMedia { +class HistoryVideo : public HistoryFileMedia { public: HistoryVideo(const MTPDvideo &video, const QString &caption, HistoryItem *parent); - void initDimensions(const HistoryItem *parent); - - void draw(Painter &p, const HistoryItem *parent, bool selected, int32 width = -1) const; - int32 resize(int32 width, const HistoryItem *parent); + HistoryVideo(const HistoryVideo &other); HistoryMediaType type() const { return MediaTypeVideo; } + HistoryMedia *clone() const { + return new HistoryVideo(*this); + } + + void initDimensions(const HistoryItem *parent); + int32 resize(int32 width, const HistoryItem *parent); + + void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const; + void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const; + const QString inDialogsText() const; const QString inHistoryText() const; - bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const; - int32 countHeight(const HistoryItem *parent, int32 width = -1) const; - void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const; - bool uploading() const { - return (data->status == FileUploading); + + VideoData *video() const { + return _data; + } + + bool uploading() const { + return _data->uploading(); } - HistoryMedia *clone() const; void regItem(HistoryItem *item); void unregItem(HistoryItem *item); bool hasReplyPreview() const { - return !data->thumb->isNull(); + return !_data->thumb->isNull(); } ImagePtr replyPreview(); + bool needsBubble(const HistoryItem *parent) const { + return !_caption.isEmpty() || parent->toHistoryForwarded() || parent->toHistoryReply() || parent->viaBot(); + } + bool customInfoLayout() const { + return _caption.isEmpty(); + } + bool hideFromName() const { + return true; + } + +protected: + + float64 dataProgress() const { + return _data->progress(); + } + bool dataFinished() const { + return !_data->loading() && !_data->uploading(); + } + bool dataLoaded() const { + return _data->loaded(); + } + private: - VideoData *data; - TextLinkPtr _openl, _savel, _cancell; - + VideoData *_data; + int16 _thumbw; Text _caption; - QString _size; - int32 _thumbw; + void setStatusSize(int32 newSize) const; + void updateStatusText(const HistoryItem *parent) const; - mutable QString _dldTextCache, _uplTextCache; - mutable int32 _dldDone, _uplDone; }; -class HistoryAudio : public HistoryMedia { +class HistoryAudio : public HistoryFileMedia { public: HistoryAudio(const MTPDaudio &audio); - void initDimensions(const HistoryItem *parent); - - void draw(Painter &p, const HistoryItem *parent, bool selected, int32 width = -1) const; + HistoryAudio(const HistoryAudio &other); HistoryMediaType type() const { return MediaTypeAudio; } + HistoryMedia *clone() const { + return new HistoryAudio(*this); + } + + void initDimensions(const HistoryItem *parent); + + void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const; + void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const; + const QString inDialogsText() const; const QString inHistoryText() const; - bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const; - void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const; + bool uploading() const { - return (data->status == FileUploading); + return _data->uploading(); } - HistoryMedia *clone() const; AudioData *audio() { - return data; + return _data; } void regItem(HistoryItem *item); void unregItem(HistoryItem *item); - void updateFrom(const MTPMessageMedia &media); + void updateFrom(const MTPMessageMedia &media, HistoryItem *parent); + + bool needsBubble(const HistoryItem *parent) const { + return true; + } + bool customInfoLayout() const { + return false; + } + QMargins bubbleMargins() const { + return st::msgPadding; + } + +protected: + + float64 dataProgress() const { + return _data->progress(); + } + bool dataFinished() const { + return !_data->loading() && !_data->uploading(); + } + bool dataLoaded() const { + return _data->loaded(); + } private: - AudioData *data; - TextLinkPtr _openl, _savel, _cancell; + AudioData *_data; - QString _size; + void setStatusSize(int32 newSize, qint64 realDuration = 0) const; + bool updateStatusText(const HistoryItem *parent) const; // returns showPause - mutable QString _dldTextCache, _uplTextCache; - mutable int32 _dldDone, _uplDone; }; -class HistoryDocument : public HistoryMedia { +class HistoryDocument : public HistoryFileMedia { public: - HistoryDocument(DocumentData *document); - void initDimensions(const HistoryItem *parent); - - void draw(Painter &p, const HistoryItem *parent, bool selected, int32 width = -1) const; - int32 resize(int32 width, const HistoryItem *parent); + HistoryDocument(DocumentData *document, const QString &caption, const HistoryItem *parent); + HistoryDocument(const HistoryDocument &other); HistoryMediaType type() const { return MediaTypeDocument; } + HistoryMedia *clone() const { + return new HistoryDocument(*this); + } + + void initDimensions(const HistoryItem *parent); + int32 resize(int32 width, const HistoryItem *parent); + + void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const; + void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const; + const QString inDialogsText() const; const QString inHistoryText() const; - bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const; - int32 countHeight(const HistoryItem *parent, int32 width = -1) const; - bool uploading() const { - return (data->status == FileUploading); - } - void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const; - HistoryMedia *clone() const; - DocumentData *document() { - return data; + bool uploading() const { + return _data->uploading(); + } + + bool withThumb() const { + return !_data->song() && !_data->thumb->isNull() && _data->thumb->width() && _data->thumb->height(); + } + + DocumentData *getDocument() { + return _data; } void regItem(HistoryItem *item); void unregItem(HistoryItem *item); - void updateFrom(const MTPMessageMedia &media); + void updateFrom(const MTPMessageMedia &media, HistoryItem *parent); bool hasReplyPreview() const { - return !data->thumb->isNull(); + return !_data->thumb->isNull(); } ImagePtr replyPreview(); - void drawInPlaylist(Painter &p, const HistoryItem *parent, bool selected, bool over, int32 width) const; - TextLinkPtr linkInPlaylist(); + QString getCaption() const { + return _caption.original(); + } + bool needsBubble(const HistoryItem *parent) const { + return true; + } + bool customInfoLayout() const { + return false; + } + QMargins bubbleMargins() const { + return withThumb() ? QMargins(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbPadding.left(), st::msgFileThumbPadding.bottom()) : st::msgPadding; + } + bool hideForwardedFrom() const { + return _data->song(); + } + +protected: + + float64 dataProgress() const { + return _data->progress(); + } + bool dataFinished() const { + return !_data->loading() && !_data->uploading(); + } + bool dataLoaded() const { + return _data->loaded(); + } private: - DocumentData *data; - TextLinkPtr _openl, _savel, _cancell; + DocumentData *_data; + TextLinkPtr _linksavel, _linkcancell; + QString _name; int32 _namew; - QString _name, _size; - int32 _thumbw, _thumbx, _thumby; + int32 _thumbw; + + mutable int32 _linkw; + mutable QString _link; + + Text _caption; + + void setStatusSize(int32 newSize, qint64 realDuration = 0) const; + bool updateStatusText(const HistoryItem *parent) const; // returns showPause + +}; + +class HistoryGif : public HistoryFileMedia { +public: + + HistoryGif(DocumentData *document, const QString &caption, const HistoryItem *parent); + HistoryGif(const HistoryGif &other); + HistoryMediaType type() const { + return MediaTypeGif; + } + HistoryMedia *clone() const { + return new HistoryGif(*this); + } + + void initDimensions(const HistoryItem *parent); + int32 resize(int32 width, const HistoryItem *parent); + + void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const; + void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const; + + const QString inDialogsText() const; + const QString inHistoryText() const; + + bool uploading() const { + return _data->uploading(); + } + + DocumentData *getDocument() { + return _data; + } + ClipReader *getClipReader() { + return gif(); + } + + bool playInline(HistoryItem *item); + void stopInline(HistoryItem *item); + + void regItem(HistoryItem *item); + void unregItem(HistoryItem *item); + + void updateFrom(const MTPMessageMedia &media, HistoryItem *parent); + + bool hasReplyPreview() const { + return !_data->thumb->isNull(); + } + ImagePtr replyPreview(); + + QString getCaption() const { + return _caption.original(); + } + bool needsBubble(const HistoryItem *parent) const { + return !_caption.isEmpty() || parent->toHistoryForwarded() || parent->toHistoryReply() || parent->viaBot(); + } + bool customInfoLayout() const { + return _caption.isEmpty(); + } + bool hideFromName() const { + return true; + } + + ~HistoryGif(); + +protected: + + float64 dataProgress() const; + bool dataFinished() const; + bool dataLoaded() const; + +private: + + const HistoryItem *_parent; + DocumentData *_data; + int32 _thumbw, _thumbh; + Text _caption; + + ClipReader *_gif; + ClipReader *gif() { + return (_gif == BadClipReader) ? 0 : _gif; + } + const ClipReader *gif() const { + return (_gif == BadClipReader) ? 0 : _gif; + } + + void setStatusSize(int32 newSize) const; + void updateStatusText(const HistoryItem *parent) const; - mutable QString _dldTextCache, _uplTextCache; - mutable int32 _dldDone, _uplDone; }; class HistorySticker : public HistoryMedia { public: HistorySticker(DocumentData *document); - void initDimensions(const HistoryItem *parent); - - void draw(Painter &p, const HistoryItem *parent, bool selected, int32 width = -1) const; - int32 resize(int32 width, const HistoryItem *parent); HistoryMediaType type() const { return MediaTypeSticker; } + HistoryMedia *clone() const { + return new HistorySticker(*this); + } + + void initDimensions(const HistoryItem *parent); + int32 resize(int32 width, const HistoryItem *parent); + + void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const; + void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const; + const QString inDialogsText() const; const QString inHistoryText() const; - bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const; - int32 countHeight(const HistoryItem *parent, int32 width = -1) const; - void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const; - HistoryMedia *clone() const; - DocumentData *document() { - return data; + DocumentData *getDocument() { + return _data; } void regItem(HistoryItem *item); void unregItem(HistoryItem *item); - void updateFrom(const MTPMessageMedia &media); + void updateFrom(const MTPMessageMedia &media, HistoryItem *parent); + + bool needsBubble(const HistoryItem *parent) const { + return false; + } + bool customInfoLayout() const { + return true; + } private: - int16 pixw, pixh; - DocumentData *data; + int16 _pixw, _pixh; + DocumentData *_data; QString _emoji; - int32 lastw; + +}; + +class SendMessageLink : public PeerLink { + TEXT_LINK_CLASS(SendMessageLink) + +public: + SendMessageLink(PeerData *peer) : PeerLink(peer) { + } + void onClick(Qt::MouseButton button) const; + +}; + +class AddContactLink : public MessageLink { + TEXT_LINK_CLASS(AddContactLink) + +public: + AddContactLink(PeerId peer, MsgId msgid) : MessageLink(peer, msgid) { + } + void onClick(Qt::MouseButton button) const; }; @@ -1397,78 +1727,135 @@ class HistoryContact : public HistoryMedia { public: HistoryContact(int32 userId, const QString &first, const QString &last, const QString &phone); - HistoryContact(int32 userId, const QString &fullname, const QString &phone); - void initDimensions(const HistoryItem *parent); - - void draw(Painter &p, const HistoryItem *parent, bool selected, int32 width) const; HistoryMediaType type() const { return MediaTypeContact; } + HistoryMedia *clone() const { + return new HistoryContact(_userId, _fname, _lname, _phone); + } + + void initDimensions(const HistoryItem *parent); + + void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const; + void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const; + const QString inDialogsText() const; const QString inHistoryText() const; - bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const; - void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width) const; - HistoryMedia *clone() const; - void updateFrom(const MTPMessageMedia &media); + void regItem(HistoryItem *item); + void unregItem(HistoryItem *item); + + void updateFrom(const MTPMessageMedia &media, HistoryItem *parent); + + bool needsBubble(const HistoryItem *parent) const { + return true; + } + bool customInfoLayout() const { + return false; + } + + const QString &fname() const { + return _fname; + } + const QString &lname() const { + return _lname; + } + const QString &phone() const { + return _phone; + } private: - int32 userId; - int32 phonew; - Text name; - QString phone; - UserData *contact; + + int32 _userId; + UserData *_contact; + + int32 _phonew; + QString _fname, _lname, _phone; + Text _name; + + TextLinkPtr _linkl; + int32 _linkw; + QString _link; }; class HistoryWebPage : public HistoryMedia { public: HistoryWebPage(WebPageData *data); - void initDimensions(const HistoryItem *parent); - - void draw(Painter &p, const HistoryItem *parent, bool selected, int32 width = -1) const; - bool isDisplayed() const { - return !data->pendingTill; - } - int32 resize(int32 width, const HistoryItem *parent); + HistoryWebPage(const HistoryWebPage &other); HistoryMediaType type() const { return MediaTypeWebPage; } + HistoryMedia *clone() const { + return new HistoryWebPage(*this); + } + + void initDimensions(const HistoryItem *parent); + int32 resize(int32 width, const HistoryItem *parent); + + void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const; + void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const; + const QString inDialogsText() const; const QString inHistoryText() const; - bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const; - void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const; - HistoryMedia *clone() const; + + void linkOver(HistoryItem *parent, const TextLinkPtr &lnk); + void linkOut(HistoryItem *parent, const TextLinkPtr &lnk); + + bool isDisplayed() const { + return !_data->pendingTill; + } + DocumentData *getDocument() { + return _attach ? _attach->getDocument() : 0; + } + ClipReader *getClipReader() { + return _attach ? _attach->getClipReader() : 0; + } + bool playInline(HistoryItem *item) { + return _attach ? _attach->playInline(item) : false; + } + void stopInline(HistoryItem *item) { + if (_attach) _attach->stopInline(item); + } void regItem(HistoryItem *item); void unregItem(HistoryItem *item); bool hasReplyPreview() const { - return (data->photo && !data->photo->thumb->isNull()) || (data->doc && !data->doc->thumb->isNull()); + return (_data->photo && !_data->photo->thumb->isNull()) || (_data->doc && !_data->doc->thumb->isNull()); } ImagePtr replyPreview(); - virtual bool animating() const { - if (_asArticle || !data->photo || data->photo->full->loaded()) return false; - return data->photo->full->loading(); + WebPageData *webpage() { + return _data; } - WebPageData *webpage() { - return data; + bool needsBubble(const HistoryItem *parent) const { + return true; } + bool customInfoLayout() const { + return false; + } + + HistoryMedia *attach() const { + return _attach; + } + + ~HistoryWebPage(); private: - WebPageData *data; - TextLinkPtr _openl, _attachl; + WebPageData *_data; + TextLinkPtr _openl; + HistoryMedia *_attach; + bool _asArticle; + int32 _titleLines, _descriptionLines; Text _title, _description; int32 _siteNameWidth; - QString _duration, _docName, _docSize; - int32 _durationWidth, _docNameWidth, _docThumbWidth; - mutable QString _docDownloadTextCache; - mutable int32 _docDownloadDone; + QString _duration; + int32 _durationWidth; int16 _pixw, _pixh; }; @@ -1479,9 +1866,6 @@ void deinitImageLinkManager(); enum ImageLinkType { InvalidImageLink = 0, - YouTubeLink, - VimeoLink, - InstagramLink, GoogleMapsLink }; struct ImageLinkData { @@ -1489,7 +1873,6 @@ struct ImageLinkData { } QString id; - QString title, duration; ImagePtr thumb; ImageLinkType type; bool loading; @@ -1512,7 +1895,7 @@ public: deinit(); } -public slots: + public slots: void onFinished(QNetworkReply *reply); void onFailed(QNetworkReply *reply); @@ -1529,29 +1912,67 @@ class HistoryImageLink : public HistoryMedia { public: HistoryImageLink(const QString &url, const QString &title = QString(), const QString &description = QString()); - int32 fullWidth() const; - int32 fullHeight() const; - void initDimensions(const HistoryItem *parent); - - void draw(Painter &p, const HistoryItem *parent, bool selected, int32 width = -1) const; - int32 resize(int32 width, const HistoryItem *parent); HistoryMediaType type() const { return MediaTypeImageLink; } + HistoryMedia *clone() const { + return new HistoryImageLink(*this); + } + + void initDimensions(const HistoryItem *parent); + int32 resize(int32 width, const HistoryItem *parent); + + void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const; + void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const; + const QString inDialogsText() const; const QString inHistoryText() const; - bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const; - void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const; - HistoryMedia *clone() const; bool isImageLink() const { return true; } + bool needsBubble(const HistoryItem *parent) const { + return !_title.isEmpty() || !_description.isEmpty() || parent->toHistoryForwarded() || parent->toHistoryReply() || parent->viaBot(); + } + bool customInfoLayout() const { + return true; + } + private: - ImageLinkData *data; + ImageLinkData *_data; Text _title, _description; - TextLinkPtr link; + TextLinkPtr _link; + + int32 fullWidth() const; + int32 fullHeight() const; + +}; + +class ViaInlineBotLink : public ITextLink { + TEXT_LINK_CLASS(ViaInlineBotLink) + +public: + ViaInlineBotLink(UserData *bot) : _bot(bot) { + } + void onClick(Qt::MouseButton button) const; + +private: + UserData *_bot; + +}; + +class HistoryMessageVia { +public: + HistoryMessageVia(int32 userId); + + bool isNull() const; + void resize(int32 availw); + + UserData *bot; + QString text; + int32 width, maxWidth; + TextLinkPtr lnk; }; @@ -1559,27 +1980,50 @@ class HistoryMessage : public HistoryItem { public: HistoryMessage(History *history, HistoryBlock *block, const MTPDmessage &msg); - HistoryMessage(History *history, HistoryBlock *block, MsgId msgId, int32 flags, QDateTime date, int32 from, const QString &msg, const EntitiesInText &entities, HistoryMedia *media); // local forwarded - HistoryMessage(History *history, HistoryBlock *block, MsgId msgId, int32 flags, QDateTime date, int32 from, DocumentData *doc); // local sticker and reply sticker + HistoryMessage(History *history, HistoryBlock *block, MsgId msgId, int32 flags, int32 viaBotId, QDateTime date, int32 from, const QString &msg, const EntitiesInText &entities, HistoryMedia *media); // local forwarded + HistoryMessage(History *history, HistoryBlock *block, MsgId msgId, int32 flags, int32 viaBotId, QDateTime date, int32 from, DocumentData *doc, const QString &caption); // local document + HistoryMessage(History *history, HistoryBlock *block, MsgId msgId, int32 flags, int32 viaBotId, QDateTime date, int32 from, PhotoData *photo, const QString &caption); // local photo void initTime(); void initMedia(const MTPMessageMedia *media, QString ¤tText); - void initMediaFromText(QString ¤tText); - void initMediaFromDocument(DocumentData *doc); + void initMediaFromDocument(DocumentData *doc, const QString &caption); void initDimensions(); - void fromNameUpdated() const; + void fromNameUpdated(int32 width) const; - bool justMedia() const { - return _media && _text.isEmpty(); + virtual HistoryMessageVia *via() const { + return (_via && !_via->isNull()) ? _via : 0; + } + virtual UserData *viaBot() const { + return via() ? via()->bot : 0; } - bool uploading() const; + int32 plainMaxWidth() const; + void countPositionAndSize(int32 &left, int32 &width) const; - void drawInfo(Painter &p, int32 right, int32 bottom, bool selected, InfoDisplayType type) const; - void setViewsCount(int32 count); + bool emptyText() const { + return _text.isEmpty(); + } + bool drawBubble() const { + return _media ? (!emptyText() || _media->needsBubble(this)) : true; + } + bool hasBubble() const { + return drawBubble(); + } + bool displayFromName() const { + return hasFromName() && (!emptyText() || !_media || !_media->isDisplayed() || toHistoryReply() || viaBot() || !_media->hideFromName()); + } + bool uploading() const { + return _media && _media->uploading(); + } + + void drawInfo(Painter &p, int32 right, int32 bottom, int32 width, bool selected, InfoDisplayType type) const; + void setViewsCount(int32 count, bool reinit = true); void setId(MsgId newId); - void draw(Painter &p, uint32 selection) const; - virtual void drawMessageText(Painter &p, const QRect &trect, uint32 selection) const; + void draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const; + + virtual void drawMessageText(Painter &p, QRect trect, uint32 selection) const; + + void destroy(); int32 resize(int32 width); bool hasPoint(int32 x, int32 y) const; @@ -1592,23 +2036,31 @@ public: uint32 adjustSelection(uint16 from, uint16 to, TextSelectType type) const { return _text.adjustSelection(from, to, type); } + void linkOver(const TextLinkPtr &lnk) { + if (_media) _media->linkOver(this, lnk); + } + void linkOut(const TextLinkPtr &lnk) { + if (_media) _media->linkOut(this, lnk); + } void drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const; QString notificationHeader() const; QString notificationText() const; - - void updateMedia(const MTPMessageMedia *media, bool allowEmitResize) { + + void updateMedia(const MTPMessageMedia *media) { if (media && _media && _media->type() != MediaTypeWebPage) { - _media->updateFrom(*media); + _media->updateFrom(*media, this); } else { - setMedia(media, allowEmitResize); + setMedia(media); } } + int32 addToOverview(AddToOverviewMethod method); + void eraseFromOverview(); QString selectedText(uint32 selection) const; QString inDialogsText() const; HistoryMedia *getMedia(bool inOverview = false) const; - void setMedia(const MTPMessageMedia *media, bool allowEmitResize); + void setMedia(const MTPMessageMedia *media); void setText(const QString &text, const EntitiesInText &entities); QString originalText() const; EntitiesInText originalEntities() const; @@ -1635,9 +2087,6 @@ public: } return result; } - QString timeText() const { - return _timeText; - } int32 timeWidth() const { return _timeWidth; } @@ -1647,9 +2096,6 @@ public: int32 viewsWidth() const { return _viewsWidth; } - virtual bool animating() const { - return _media ? _media->animating() : false; - } virtual QDateTime dateForwarded() const { // dynamic_cast optimize return date; @@ -1672,11 +2118,12 @@ protected: Text _text; int32 _textWidth, _textHeight; + HistoryMessageVia *_via; HistoryMedia *_media; QString _timeText; int32 _timeWidth; - + QString _viewsText; int32 _views, _viewsWidth; @@ -1691,9 +2138,9 @@ public: void initDimensions(); void fwdNameUpdated() const; - void draw(Painter &p, uint32 selection) const; + void draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const; void drawForwardedFrom(Painter &p, int32 x, int32 y, int32 w, bool selected) const; - void drawMessageText(Painter &p, const QRect &trect, uint32 selection) const; + void drawMessageText(Painter &p, QRect trect, uint32 selection) const; int32 resize(int32 width); bool hasPoint(int32 x, int32 y) const; void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const; @@ -1708,6 +2155,9 @@ public: return fwdFrom; } QString selectedText(uint32 selection) const; + bool displayForwardedFrom() const { + return via() || !_media || !_media->isDisplayed() || (fwdFrom->isChannel() || !_media->hideForwardedFrom()); + } HistoryForwarded *toHistoryForwarded() { return this; @@ -1730,7 +2180,8 @@ class HistoryReply : public HistoryMessage { public: HistoryReply(History *history, HistoryBlock *block, const MTPDmessage &msg); - HistoryReply(History *history, HistoryBlock *block, MsgId msgId, int32 flags, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc); + HistoryReply(History *history, HistoryBlock *block, MsgId msgId, int32 flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption); + HistoryReply(History *history, HistoryBlock *block, MsgId msgId, int32 flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption); void initDimensions(); @@ -1744,10 +2195,11 @@ public: HistoryItem *replyToMessage() const; void replyToReplaced(HistoryItem *oldItem, HistoryItem *newItem); - void draw(Painter &p, uint32 selection) const; + void draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const; void drawReplyTo(Painter &p, int32 x, int32 y, int32 w, bool selected, bool likeService = false) const; - void drawMessageText(Painter &p, const QRect &trect, uint32 selection) const; + void drawMessageText(Painter &p, QRect trect, uint32 selection) const; int32 resize(int32 width); + void resizeVia(int32 w) const; bool hasPoint(int32 x, int32 y) const; void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const; void getStateFromMessageText(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const QRect &r) const; @@ -1775,10 +2227,21 @@ protected: mutable Text replyToName, replyToText; mutable int32 replyToVersion; mutable int32 _maxReplyWidth; + HistoryMessageVia *_replyToVia; + HistoryMessageVia *replyToVia() const { + return (_replyToVia && !_replyToVia->isNull()) ? _replyToVia : 0; + } int32 toWidth; }; +inline int32 newMessageFlags(PeerData *p) { + return p->isSelf() ? 0 : (((p->isChat() || (p->isUser() && !p->asUser()->botInfo)) ? MTPDmessage::flag_unread : 0) | MTPDmessage::flag_out); +} +inline int32 newForwardedFlags(PeerData *p, int32 from, HistoryMessage *msg) { + return newMessageFlags(p) | (from ? MTPDmessage::flag_from_id : 0) | (msg->via() ? MTPDmessage::flag_via_bot_id : 0) | (!p->isChannel() && msg->getMedia() && (msg->getMedia()->type() == MediaTypeAudio/* || msg->getMedia()->type() == MediaTypeVideo*/) ? MTPDmessage::flag_media_unread : 0); +} + class HistoryServiceMsg : public HistoryItem { public: @@ -1787,7 +2250,7 @@ public: void initDimensions(); - void draw(Painter &p, uint32 selection) const; + void draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const; int32 resize(int32 width); bool hasPoint(int32 x, int32 y) const; void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const; @@ -1796,6 +2259,13 @@ public: return _text.adjustSelection(from, to, type); } + void linkOver(const TextLinkPtr &lnk) { + if (_media) _media->linkOver(this, lnk); + } + void linkOut(const TextLinkPtr &lnk) { + if (_media) _media->linkOut(this, lnk); + } + void drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const; QString notificationText() const; @@ -1811,10 +2281,6 @@ public: HistoryMedia *getMedia(bool inOverview = false) const; - virtual bool animating() const { - return _media ? _media->animating() : false; - } - void setServiceText(const QString &text); ~HistoryServiceMsg(); @@ -1842,6 +2308,7 @@ public: after = false; upon = false; } + void setDate(const QDateTime &date); QString selectedText(uint32 selection) const { return QString(); } @@ -1898,7 +2365,7 @@ class HistoryCollapse : public HistoryServiceMsg { public: HistoryCollapse(History *history, HistoryBlock *block, MsgId wasMinId, const QDateTime &date); - void draw(Painter &p, uint32 selection) const; + void draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const; void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const; void getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y) const { symbol = 0xFFFF; @@ -1940,7 +2407,7 @@ public: void setCount(int32 count); - void draw(Painter &p, uint32 selection) const; + void draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const; int32 resize(int32 width); void drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const; @@ -1958,6 +2425,3 @@ protected: QString text; bool freezed; }; - -const TextParseOptions &itemTextOptions(History *h, PeerData *f); -const TextParseOptions &itemTextNoMonoOptions(History *h, PeerData *f); diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index afe6316dfd..e58661c29b 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -38,7 +38,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org // flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html -HistoryInner::HistoryInner(HistoryWidget *historyWidget, ScrollArea *scroll, History *history) : QWidget(0) +HistoryInner::HistoryInner(HistoryWidget *historyWidget, ScrollArea *scroll, History *history) : TWidget(0) , _peer(history->peer) , _migrated(history->peer->migrateFrom() ? App::history(history->peer->migrateFrom()->id) : 0) , _history(history) @@ -116,11 +116,11 @@ void HistoryInner::messagesReceivedDown(PeerData *peer, const QVectordetached() || !_history) return; - int32 msgy = itemTop(msg); +void HistoryInner::repaintItem(const HistoryItem *item) { + if (!item || item->detached() || !_history) return; + int32 msgy = itemTop(item); if (msgy >= 0) { - update(0, msgy, width(), msg->height()); + update(0, msgy, width(), item->height()); } } @@ -129,13 +129,13 @@ void HistoryInner::paintEvent(QPaintEvent *e) { if (!App::main()) return; + Painter p(this); QRect r(e->rect()); bool trivial = (rect() == r); - - Painter p(this); if (!trivial) { p.setClipRect(r); } + uint64 ms = getms(); if (!_firstLoading && _botInfo && !_botInfo->text.isEmpty() && _botDescHeight > 0) { if (r.y() < _botDescRect.y() + _botDescRect.height() && r.y() + r.height() > _botDescRect.y()) { @@ -182,14 +182,14 @@ void HistoryInner::paintEvent(QPaintEvent *e) { if (r.y() < y + item->height()) while (y < drawToY) { uint32 sel = 0; if (y >= selfromy && y < seltoy) { - sel = (_dragSelecting && !item->serviceMsg() && item->id > 0) ? FullItemSel : 0; + sel = (_dragSelecting && !item->serviceMsg() && item->id > 0) ? FullSelection : 0; } else if (hasSel) { SelectedItems::const_iterator i = _selected.constFind(item); if (i != selEnd) { sel = i.value(); } } - item->draw(p, sel); + item->draw(p, r.translated(0, -y), sel, ms); if (item->hasViews()) { App::main()->scheduleViewIncrement(item); @@ -226,14 +226,14 @@ void HistoryInner::paintEvent(QPaintEvent *e) { if (r.y() < y + h && hdrawtop < y + h) { uint32 sel = 0; if (y >= selfromy && y < seltoy) { - sel = (_dragSelecting && !item->serviceMsg() && item->id > 0) ? FullItemSel : 0; + sel = (_dragSelecting && !item->serviceMsg() && item->id > 0) ? FullSelection : 0; } else if (hasSel) { SelectedItems::const_iterator i = _selected.constFind(item); if (i != selEnd) { sel = i.value(); } } - item->draw(p, sel); + item->draw(p, r.translated(0, -y), sel, ms); if (item->hasViews()) { App::main()->scheduleViewIncrement(item); @@ -357,7 +357,7 @@ void HistoryInner::touchEvent(QTouchEvent *e) { _touchPrevPos = _touchPos; _touchPos = e->touchPoints().cbegin()->screenPos().toPoint(); } - + switch (e->type()) { case QEvent::TouchBegin: if (_menu) { @@ -475,16 +475,16 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt if (button != Qt::LeftButton) return; if (App::pressedItem() != App::hoveredItem()) { - updateMsg(App::pressedItem()); + repaintItem(App::pressedItem()); App::pressedItem(App::hoveredItem()); - updateMsg(App::pressedItem()); + repaintItem(App::pressedItem()); } if (textlnkDown() != textlnkOver()) { - updateMsg(App::pressedLinkItem()); + repaintItem(App::pressedLinkItem()); textlnkDown(textlnkOver()); App::pressedLinkItem(App::hoveredLinkItem()); - updateMsg(App::pressedLinkItem()); - updateMsg(App::pressedItem()); + repaintItem(App::pressedLinkItem()); + repaintItem(App::pressedItem()); } _dragAction = NoDrag; @@ -495,7 +495,7 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt if (textlnkDown()) { _dragAction = PrepareDrag; } else if (!_selected.isEmpty()) { - if (_selected.cbegin().value() == FullItemSel) { + if (_selected.cbegin().value() == FullSelection) { if (_selected.constFind(_dragItem) != _selected.cend() && App::hoveredItem()) { _dragAction = PrepareDrag; // start items drag } else if (!_dragWasInactive) { @@ -510,9 +510,9 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt _dragItem->getSymbol(symbol, afterDragSymbol, uponSymbol, _dragStartPos.x(), _dragStartPos.y()); if (uponSymbol) { uint32 selStatus = (symbol << 16) | symbol; - if (selStatus != FullItemSel && (_selected.isEmpty() || _selected.cbegin().value() != FullItemSel)) { + if (selStatus != FullSelection && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection)) { if (!_selected.isEmpty()) { - updateMsg(_selected.cbegin().key()); + repaintItem(_selected.cbegin().key()); _selected.clear(); } _selected.insert(_dragItem, selStatus); @@ -532,7 +532,7 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt bool uponSelected = uponSymbol; if (uponSelected) { if (_selected.isEmpty() || - _selected.cbegin().value() == FullItemSel || + _selected.cbegin().value() == FullSelection || _selected.cbegin().key() != _dragItem ) { uponSelected = false; @@ -551,14 +551,14 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt } else { if (afterDragSymbol) ++_dragSymbol; uint32 selStatus = (_dragSymbol << 16) | _dragSymbol; - if (selStatus != FullItemSel && (_selected.isEmpty() || _selected.cbegin().value() != FullItemSel)) { + if (selStatus != FullSelection && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection)) { if (!_selected.isEmpty()) { - updateMsg(_selected.cbegin().key()); + repaintItem(_selected.cbegin().key()); _selected.clear(); } _selected.insert(_dragItem, selStatus); _dragAction = Selecting; - updateMsg(_dragItem); + repaintItem(_dragItem); } else { _dragAction = PrepareSelect; } @@ -593,13 +593,13 @@ void HistoryInner::onDragExec() { if (_dragItem) { bool afterDragSymbol; uint16 symbol; - if (!_selected.isEmpty() && _selected.cbegin().value() == FullItemSel) { + if (!_selected.isEmpty() && _selected.cbegin().value() == FullSelection) { uponSelected = _selected.contains(_dragItem); } else { _dragItem->getSymbol(symbol, afterDragSymbol, uponSelected, _dragStartPos.x(), _dragStartPos.y()); if (uponSelected) { if (_selected.isEmpty() || - _selected.cbegin().value() == FullItemSel || + _selected.cbegin().value() == FullSelection || _selected.cbegin().key() != _dragItem ) { uponSelected = false; @@ -631,7 +631,7 @@ void HistoryInner::onDragExec() { mimeData->setText(sel); if (!urls.isEmpty()) mimeData->setUrls(urls); - if (uponSelected && !_selected.isEmpty() && _selected.cbegin().value() == FullItemSel && cWideMode()) { + if (uponSelected && !_selected.isEmpty() && _selected.cbegin().value() == FullSelection && cWideMode()) { mimeData->setData(qsl("application/x-td-forward-selected"), "1"); } drag->setMimeData(mimeData); @@ -644,7 +644,7 @@ void HistoryInner::onDragExec() { bool lnkPhoto = (lnkType == qstr("PhotoLink")), lnkVideo = (lnkType == qstr("VideoOpenLink")), lnkAudio = (lnkType == qstr("AudioOpenLink")), - lnkDocument = (lnkType == qstr("DocumentOpenLink")), + lnkDocument = (lnkType == qstr("DocumentOpenLink") || lnkType == qstr("GifOpenLink")), lnkContact = (lnkType == qstr("PeerLink") && dynamic_cast(pressedLnkItem->getMedia())), dragSticker = dynamic_cast(pressedItem ? pressedItem->getMedia() : 0), dragByDate = (_dragCursorState == HistoryInDateCursorState); @@ -682,7 +682,7 @@ void HistoryInner::itemRemoved(HistoryItem *item) { } if (_dragAction == NoDrag) return; - + if (_dragItem == item) { dragActionCancel(); } @@ -694,20 +694,6 @@ void HistoryInner::itemRemoved(HistoryItem *item) { updateDragSelection(_dragSelFrom, _dragSelTo, _dragSelecting, true); } -void HistoryInner::itemReplaced(HistoryItem *oldItem, HistoryItem *newItem) { - if (_dragItem == oldItem) _dragItem = newItem; - - SelectedItems::iterator i = _selected.find(oldItem); - if (i != _selected.cend()) { - uint32 v = i.value(); - _selected.erase(i); - _selected.insert(newItem, v); - } - - if (_dragSelFrom == oldItem) _dragSelFrom = newItem; - if (_dragSelTo == oldItem) _dragSelTo = newItem; -} - void HistoryInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton button) { TextLinkPtr needClick; @@ -721,9 +707,9 @@ void HistoryInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton but bool lnkPhoto = (lnkType == qstr("PhotoLink")), lnkVideo = (lnkType == qstr("VideoOpenLink")), lnkAudio = (lnkType == qstr("AudioOpenLink")), - lnkDocument = (lnkType == qstr("DocumentOpenLink")), + lnkDocument = (lnkType == qstr("DocumentOpenLink") || lnkType == qstr("GifOpenLink")), lnkContact = (lnkType == qstr("PeerLink") && dynamic_cast(App::pressedLinkItem() ? App::pressedLinkItem()->getMedia() : 0)); - if (_dragAction == PrepareDrag && !_dragWasInactive && !_selected.isEmpty() && _selected.cbegin().value() == FullItemSel && button != Qt::RightButton) { + if (_dragAction == PrepareDrag && !_dragWasInactive && !_selected.isEmpty() && _selected.cbegin().value() == FullSelection && button != Qt::RightButton) { if (lnkPhoto || lnkVideo || lnkAudio || lnkDocument || lnkContact) { needClick = TextLinkPtr(); } @@ -731,7 +717,7 @@ void HistoryInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton but } } if (textlnkDown()) { - updateMsg(App::pressedLinkItem()); + repaintItem(App::pressedLinkItem()); textlnkDown(TextLinkPtr()); App::pressedLinkItem(0); if (!textlnkOver() && _cursor != style::cur_default) { @@ -740,7 +726,7 @@ void HistoryInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton but } } if (App::pressedItem()) { - updateMsg(App::pressedItem()); + repaintItem(App::pressedItem()); App::pressedItem(0); } @@ -752,28 +738,28 @@ void HistoryInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton but dragActionCancel(); return; } - if (_dragAction == PrepareSelect && !_dragWasInactive && !_selected.isEmpty() && _selected.cbegin().value() == FullItemSel) { + if (_dragAction == PrepareSelect && !_dragWasInactive && !_selected.isEmpty() && _selected.cbegin().value() == FullSelection) { SelectedItems::iterator i = _selected.find(_dragItem); if (i == _selected.cend() && !_dragItem->serviceMsg() && _dragItem->id > 0) { if (_selected.size() < MaxSelectedItems) { - if (!_selected.isEmpty() && _selected.cbegin().value() != FullItemSel) { + if (!_selected.isEmpty() && _selected.cbegin().value() != FullSelection) { _selected.clear(); } - _selected.insert(_dragItem, FullItemSel); + _selected.insert(_dragItem, FullSelection); } } else { _selected.erase(i); } - updateMsg(_dragItem); + repaintItem(_dragItem); } else if (_dragAction == PrepareDrag && !_dragWasInactive && button != Qt::RightButton) { SelectedItems::iterator i = _selected.find(_dragItem); - if (i != _selected.cend() && i.value() == FullItemSel) { + if (i != _selected.cend() && i.value() == FullSelection) { _selected.erase(i); - updateMsg(_dragItem); - } else if (i == _selected.cend() && !_dragItem->serviceMsg() && _dragItem->id > 0 && !_selected.isEmpty() && _selected.cbegin().value() == FullItemSel) { + repaintItem(_dragItem); + } else if (i == _selected.cend() && !_dragItem->serviceMsg() && _dragItem->id > 0 && !_selected.isEmpty() && _selected.cbegin().value() == FullSelection) { if (_selected.size() < MaxSelectedItems) { - _selected.insert(_dragItem, FullItemSel); - updateMsg(_dragItem); + _selected.insert(_dragItem, FullSelection); + repaintItem(_dragItem); } } else { _selected.clear(); @@ -785,7 +771,7 @@ void HistoryInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton but _dragSelFrom = _dragSelTo = 0; } else if (!_selected.isEmpty() && !_dragWasInactive) { uint32 sel = _selected.cbegin().value(); - if (sel != FullItemSel && (sel & 0xFFFF) == ((sel >> 16) & 0xFFFF)) { + if (sel != FullSelection && (sel & 0xFFFF) == ((sel >> 16) & 0xFFFF)) { _selected.clear(); if (App::wnd()) App::wnd()->setInnerFocus(); } @@ -807,7 +793,7 @@ void HistoryInner::mouseReleaseEvent(QMouseEvent *e) { void HistoryInner::mouseDoubleClickEvent(QMouseEvent *e) { if (!_history) return; - if (((_dragAction == Selecting && !_selected.isEmpty() && _selected.cbegin().value() != FullItemSel) || (_dragAction == NoDrag && (_selected.isEmpty() || _selected.cbegin().value() != FullItemSel))) && _dragSelType == TextSelectLetters && _dragItem) { + if (((_dragAction == Selecting && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection) || (_dragAction == NoDrag && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection))) && _dragSelType == TextSelectLetters && _dragItem) { bool afterDragSymbol, uponSelected; uint16 symbol; _dragItem->getSymbol(symbol, afterDragSymbol, uponSelected, _dragStartPos.x(), _dragStartPos.y()); @@ -818,7 +804,7 @@ void HistoryInner::mouseDoubleClickEvent(QMouseEvent *e) { _dragAction = Selecting; uint32 selStatus = (symbol << 16) | symbol; if (!_selected.isEmpty()) { - updateMsg(_selected.cbegin().key()); + repaintItem(_selected.cbegin().key()); _selected.clear(); } _selected.insert(_dragItem, selStatus); @@ -850,7 +836,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { int32 isUponSelected = 0, hasSelected = 0;; if (!_selected.isEmpty()) { isUponSelected = -1; - if (_selected.cbegin().value() == FullItemSel) { + if (_selected.cbegin().value() == FullSelection) { hasSelected = 2; if (App::hoveredItem() && _selected.constFind(App::hoveredItem()) != _selected.cend()) { isUponSelected = 2; @@ -894,9 +880,12 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { _menu->addAction(lang(lng_context_save_image), this, SLOT(saveContextImage()))->setEnabled(true); _menu->addAction(lang(lng_context_copy_image), this, SLOT(copyContextImage()))->setEnabled(true); } else { - if ((lnkVideo && lnkVideo->video()->loader) || (lnkAudio && lnkAudio->audio()->loader) || (lnkDocument && lnkDocument->document()->loader)) { + if ((lnkVideo && lnkVideo->video()->loading()) || (lnkAudio && lnkAudio->audio()->loading()) || (lnkDocument && lnkDocument->document()->loading())) { _menu->addAction(lang(lng_context_cancel_download), this, SLOT(cancelContextDownload()))->setEnabled(true); } else { + if (lnkDocument && lnkDocument->document()->loaded() && lnkDocument->document()->isGifv()) { + _menu->addAction(lang(lng_context_save_gif), this, SLOT(saveContextGif()))->setEnabled(true); + } if ((lnkVideo && !lnkVideo->video()->already(true).isEmpty()) || (lnkAudio && !lnkAudio->audio()->already(true).isEmpty()) || (lnkDocument && !lnkDocument->document()->already(true).isEmpty())) { _menu->addAction(lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_context_show_in_finder : lng_context_show_in_folder), this, SLOT(showContextInFolder()))->setEnabled(true); } @@ -941,14 +930,36 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { _menu->addAction(lang(lng_context_reply_msg), _widget, SLOT(onReplyToMessage())); } if (item && !isUponSelected && !_contextMenuLnk) { - if (HistorySticker *sticker = dynamic_cast(msg ? msg->getMedia() : 0)) { - DocumentData *doc = sticker->document(); - if (doc && doc->sticker() && doc->sticker()->set.type() != mtpc_inputStickerSetEmpty) { - _menu->addAction(lang(doc->sticker()->setInstalled() ? lng_context_pack_info : lng_context_pack_add), _widget, SLOT(onStickerPackInfo())); + if (HistoryMedia *media = (msg ? msg->getMedia() : 0)) { + if (media->type() == MediaTypeWebPage && static_cast(media)->attach()) { + media = static_cast(media)->attach(); + } + if (media->type() == MediaTypeSticker) { + DocumentData *doc = media->getDocument(); + if (doc && doc->sticker() && doc->sticker()->set.type() != mtpc_inputStickerSetEmpty) { + _menu->addAction(lang(doc->sticker()->setInstalled() ? lng_context_pack_info : lng_context_pack_add), _widget, SLOT(onStickerPackInfo())); + } + + _menu->addAction(lang(lng_context_save_image), this, SLOT(saveContextFile()))->setEnabled(true); + } else if (media->type() == MediaTypeGif) { + DocumentData *doc = media->getDocument(); + if (doc) { + if (doc->loading()) { + _menu->addAction(lang(lng_context_cancel_download), this, SLOT(cancelContextDownload()))->setEnabled(true); + } else { + if (doc->isGifv()) { + _menu->addAction(lang(lng_context_save_gif), this, SLOT(saveContextGif()))->setEnabled(true); + } + if (!doc->already(true).isEmpty()) { + _menu->addAction(lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_context_show_in_finder : lng_context_show_in_folder), this, SLOT(showContextInFolder()))->setEnabled(true); + } + _menu->addAction(lang(lng_context_save_file), this, SLOT(saveContextFile()))->setEnabled(true); + } + } } } - QString contextMenuText = item->selectedText(FullItemSel); - if (!contextMenuText.isEmpty() && (!msg || !msg->getMedia() || msg->getMedia()->type() != MediaTypeSticker)) { + QString contextMenuText = item->selectedText(FullSelection); + if (!contextMenuText.isEmpty() && (!msg || !msg->getMedia() || (msg->getMedia()->type() != MediaTypeSticker && msg->getMedia()->type() != MediaTypeGif))) { _menu->addAction(lang(lng_context_copy_text), this, SLOT(copyContextText()))->setEnabled(true); } } @@ -1036,9 +1047,9 @@ void HistoryInner::copyContextUrl() { void HistoryInner::saveContextImage() { PhotoLink *lnk = dynamic_cast(_contextMenuLnk.data()); if (!lnk) return; - + PhotoData *photo = lnk->photo(); - if (!photo || !photo->date || !photo->full->loaded()) return; + if (!photo || !photo->date || !photo->loaded()) return; QString file; if (filedialogGetSaveFile(file, lang(lng_save_photo), qsl("JPEG Image (*.jpg);;All files (*.*)"), filedialogDefaultName(qsl("photo"), qsl(".jpg")))) { @@ -1051,45 +1062,83 @@ void HistoryInner::saveContextImage() { void HistoryInner::copyContextImage() { PhotoLink *lnk = dynamic_cast(_contextMenuLnk.data()); if (!lnk) return; - + PhotoData *photo = lnk->photo(); - if (!photo || !photo->date || !photo->full->loaded()) return; + if (!photo || !photo->date || !photo->loaded()) return; QApplication::clipboard()->setPixmap(photo->full->pix()); } void HistoryInner::cancelContextDownload() { - VideoLink *lnkVideo = dynamic_cast(_contextMenuLnk.data()); - AudioLink *lnkAudio = dynamic_cast(_contextMenuLnk.data()); - DocumentLink *lnkDocument = dynamic_cast(_contextMenuLnk.data()); - mtpFileLoader *loader = lnkVideo ? lnkVideo->video()->loader : (lnkAudio ? lnkAudio->audio()->loader : (lnkDocument ? lnkDocument->document()->loader : 0)); - if (loader) loader->cancel(); + if (VideoLink *lnkVideo = dynamic_cast(_contextMenuLnk.data())) { + lnkVideo->video()->cancel(); + } else if (AudioLink *lnkAudio = dynamic_cast(_contextMenuLnk.data())) { + lnkAudio->audio()->cancel(); + } else if (DocumentLink *lnkDocument = dynamic_cast(_contextMenuLnk.data())) { + lnkDocument->document()->cancel(); + } else if (HistoryItem *item = App::contextItem()) { + if (HistoryMedia *media = item->getMedia()) { + if (DocumentData *doc = media->getDocument()) { + doc->cancel(); + } + } + } } void HistoryInner::showContextInFolder() { - VideoLink *lnkVideo = dynamic_cast(_contextMenuLnk.data()); - AudioLink *lnkAudio = dynamic_cast(_contextMenuLnk.data()); - DocumentLink *lnkDocument = dynamic_cast(_contextMenuLnk.data()); - QString already = lnkVideo ? lnkVideo->video()->already(true) : (lnkAudio ? lnkAudio->audio()->already(true) : (lnkDocument ? lnkDocument->document()->already(true) : QString())); + QString already; + if (VideoLink *lnkVideo = dynamic_cast(_contextMenuLnk.data())) { + already = lnkVideo->video()->already(true); + } else if (AudioLink *lnkAudio = dynamic_cast(_contextMenuLnk.data())) { + already = lnkAudio->audio()->already(true); + } else if (DocumentLink *lnkDocument = dynamic_cast(_contextMenuLnk.data())) { + already = lnkDocument->document()->already(true); + } else if (HistoryItem *item = App::contextItem()) { + if (HistoryMedia *media = item->getMedia()) { + if (DocumentData *doc = media->getDocument()) { + already = doc->already(true); + } + } + } if (!already.isEmpty()) psShowInFolder(already); } void HistoryInner::openContextFile() { - VideoLink *lnkVideo = dynamic_cast(_contextMenuLnk.data()); + HistoryItem *was = App::hoveredLinkItem(); + App::hoveredLinkItem(App::contextItem()); + VideoLink *lnkVideo = dynamic_cast(_contextMenuLnk.data()); AudioLink *lnkAudio = dynamic_cast(_contextMenuLnk.data()); DocumentLink *lnkDocument = dynamic_cast(_contextMenuLnk.data()); if (lnkVideo) VideoOpenLink(lnkVideo->video()).onClick(Qt::LeftButton); if (lnkAudio) AudioOpenLink(lnkAudio->audio()).onClick(Qt::LeftButton); if (lnkDocument) DocumentOpenLink(lnkDocument->document()).onClick(Qt::LeftButton); + App::hoveredLinkItem(was); } void HistoryInner::saveContextFile() { - VideoLink *lnkVideo = dynamic_cast(_contextMenuLnk.data()); - AudioLink *lnkAudio = dynamic_cast(_contextMenuLnk.data()); - DocumentLink *lnkDocument = dynamic_cast(_contextMenuLnk.data()); - if (lnkVideo) VideoSaveLink::doSave(lnkVideo->video(), true); - if (lnkAudio) AudioSaveLink::doSave(lnkAudio->audio(), true); - if (lnkDocument) DocumentSaveLink::doSave(lnkDocument->document(), true); + if (VideoLink *lnkVideo = dynamic_cast(_contextMenuLnk.data())) { + VideoSaveLink::doSave(lnkVideo->video(), true); + } else if (AudioLink *lnkAudio = dynamic_cast(_contextMenuLnk.data())) { + AudioSaveLink::doSave(lnkAudio->audio(), true); + } else if (DocumentLink *lnkDocument = dynamic_cast(_contextMenuLnk.data())) { + DocumentSaveLink::doSave(lnkDocument->document(), true); + } else if (HistoryItem *item = App::contextItem()) { + if (HistoryMedia *media = item->getMedia()) { + if (DocumentData *doc = media->getDocument()) { + DocumentSaveLink::doSave(doc, true); + } + } + } +} + +void HistoryInner::saveContextGif() { + if (HistoryItem *item = App::contextItem()) { + if (HistoryMedia *media = item->getMedia()) { + if (DocumentData *doc = media->getDocument()) { + _widget->saveGif(doc); + } + } + } } void HistoryInner::copyContextText() { @@ -1098,7 +1147,7 @@ void HistoryInner::copyContextText() { return; } - QString contextMenuText = item->selectedText(FullItemSel); + QString contextMenuText = item->selectedText(FullSelection); if (!contextMenuText.isEmpty()) { QApplication::clipboard()->setText(contextMenuText); } @@ -1116,7 +1165,7 @@ QString HistoryInner::getSelectedText() const { } if (sel.isEmpty()) return QString(); - if (sel.cbegin().value() != FullItemSel) { + if (sel.cbegin().value() != FullSelection) { return sel.cbegin().key()->selectedText(sel.cbegin().value()); } @@ -1127,7 +1176,7 @@ QString HistoryInner::getSelectedText() const { HistoryItem *item = i.key(); if (item->detached()) continue; - QString text, sel = item->selectedText(FullItemSel), time = item->date.toString(timeFormat); + QString text, sel = item->selectedText(FullSelection), time = item->date.toString(timeFormat); int32 size = item->from()->name.size() + time.size() + sel.size(); text.reserve(size); @@ -1165,7 +1214,7 @@ void HistoryInner::keyPressEvent(QKeyEvent *e) { } } -int32 HistoryInner::recountHeight(HistoryItem *resizedItem) { +int32 HistoryInner::recountHeight(const HistoryItem *resizedItem) { int32 htop = historyTop(), mtop = migratedTop(); int32 st1 = (htop >= 0) ? (_history->lastScrollTop - htop) : -1, st2 = (_migrated && mtop >= 0) ? (_history->lastScrollTop - mtop) : -1; @@ -1348,12 +1397,17 @@ void HistoryInner::enterEvent(QEvent *e) { } void HistoryInner::leaveEvent(QEvent *e) { - if (textlnkOver()) { - updateMsg(App::hoveredItem()); - updateMsg(App::hoveredLinkItem()); - textlnkOver(TextLinkPtr()); - App::hoveredLinkItem(0); + if (HistoryItem *item = App::hoveredItem()) { + repaintItem(item); App::hoveredItem(0); + } + if (textlnkOver()) { + if (HistoryItem *item = App::hoveredLinkItem()) { + item->linkOut(textlnkOver()); + repaintItem(item); + App::hoveredLinkItem(0); + } + textlnkOver(TextLinkPtr()); if (!textlnkDown() && _cursor != style::cur_default) { _cursor = style::cur_default; setCursor(_cursor); @@ -1444,7 +1498,7 @@ bool HistoryInner::canCopySelected() const { } bool HistoryInner::canDeleteSelected() const { - if (_selected.isEmpty() || _selected.cbegin().value() != FullItemSel) return false; + if (_selected.isEmpty() || _selected.cbegin().value() != FullSelection) return false; int32 selectedForForward, selectedForDelete; getSelectionState(selectedForForward, selectedForDelete); return (selectedForForward == selectedForDelete); @@ -1453,7 +1507,7 @@ bool HistoryInner::canDeleteSelected() const { void HistoryInner::getSelectionState(int32 &selectedForForward, int32 &selectedForDelete) const { selectedForForward = selectedForDelete = 0; for (SelectedItems::const_iterator i = _selected.cbegin(), e = _selected.cend(); i != e; ++i) { - if (i.key()->type() == HistoryItemMsg && i.value() == FullItemSel) { + if (i.key()->type() == HistoryItemMsg && i.value() == FullSelection) { if (i.key()->canDelete()) { ++selectedForDelete; } @@ -1466,7 +1520,7 @@ void HistoryInner::getSelectionState(int32 &selectedForForward, int32 &selectedF } void HistoryInner::clearSelectedItems(bool onlyTextSelection) { - if (!_selected.isEmpty() && (!onlyTextSelection || _selected.cbegin().value() != FullItemSel)) { + if (!_selected.isEmpty() && (!onlyTextSelection || _selected.cbegin().value() != FullSelection)) { _selected.clear(); _widget->updateTopBarSelection(); _widget->update(); @@ -1474,7 +1528,7 @@ void HistoryInner::clearSelectedItems(bool onlyTextSelection) { } void HistoryInner::fillSelectedItems(SelectedItemSet &sel, bool forDelete) { - if (_selected.isEmpty() || _selected.cbegin().value() != FullItemSel) return; + if (_selected.isEmpty() || _selected.cbegin().value() != FullSelection) return; for (SelectedItems::const_iterator i = _selected.cbegin(), e = _selected.cend(); i != e; ++i) { HistoryItem *item = i.key(); @@ -1489,12 +1543,12 @@ void HistoryInner::fillSelectedItems(SelectedItemSet &sel, bool forDelete) { } void HistoryInner::selectItem(HistoryItem *item) { - if (!_selected.isEmpty() && _selected.cbegin().value() != FullItemSel) { + if (!_selected.isEmpty() && _selected.cbegin().value() != FullSelection) { _selected.clear(); } else if (_selected.size() == MaxSelectedItems && _selected.constFind(item) == _selected.cend()) { return; } - _selected.insert(item, FullItemSel); + _selected.insert(item, FullSelection); _widget->updateTopBarSelection(); _widget->update(); } @@ -1522,11 +1576,13 @@ void HistoryInner::onUpdateSelected() { App::mousedItem(item); m = mapMouseToItem(point, item); if (item->hasPoint(m.x(), m.y())) { - updateMsg(App::hoveredItem()); - App::hoveredItem(item); - updateMsg(App::hoveredItem()); + if (App::hoveredItem() != item) { + repaintItem(App::hoveredItem()); + App::hoveredItem(item); + repaintItem(App::hoveredItem()); + } } else if (App::hoveredItem()) { - updateMsg(App::hoveredItem()); + repaintItem(App::hoveredItem()); App::hoveredItem(0); } } @@ -1552,8 +1608,9 @@ void HistoryInner::onUpdateSelected() { if (lnk != textlnkOver()) { lnkChanged = true; if (textlnkOver()) { - if (App::hoveredLinkItem()) { - updateMsg(App::hoveredLinkItem()); + if (HistoryItem *item = App::hoveredLinkItem()) { + item->linkOut(textlnkOver()); + repaintItem(item); } else { update(_botDescRect); } @@ -1562,8 +1619,9 @@ void HistoryInner::onUpdateSelected() { QToolTip::hideText(); App::hoveredLinkItem((lnk && !lnkInDesc) ? item : 0); if (textlnkOver()) { - if (App::hoveredLinkItem()) { - updateMsg(App::hoveredLinkItem()); + if (HistoryItem *item = App::hoveredLinkItem()) { + item->linkOver(textlnkOver()); + repaintItem(item); } else { update(_botDescRect); } @@ -1580,12 +1638,12 @@ void HistoryInner::onUpdateSelected() { _dragCursorState = cursorState; if (lnk) { cur = style::cur_pointer; - } else if (_dragCursorState == HistoryInTextCursorState && (_selected.isEmpty() || _selected.cbegin().value() != FullItemSel)) { + } else if (_dragCursorState == HistoryInTextCursorState && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection)) { cur = style::cur_text; } else if (_dragCursorState == HistoryInDateCursorState) { // cur = style::cur_cross; } - } else if (item) { + } else if (item) { if (item != _dragItem || (m - _dragStartPos).manhattanLength() >= QApplication::startDragDistance()) { if (_dragAction == PrepareDrag) { _dragAction = Dragging; @@ -1597,14 +1655,17 @@ void HistoryInner::onUpdateSelected() { cur = textlnkDown() ? style::cur_pointer : style::cur_default; if (_dragAction == Selecting) { bool canSelectMany = (_history != 0); - if (item == _dragItem && item == App::hoveredItem() && !_selected.isEmpty() && _selected.cbegin().value() != FullItemSel) { + if (item == _dragItem && item == App::hoveredItem() && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection) { bool afterSymbol, uponSymbol; uint16 second; _dragItem->getSymbol(second, afterSymbol, uponSymbol, m.x(), m.y()); if (afterSymbol && _dragSelType == TextSelectLetters) ++second; uint32 selState = _dragItem->adjustSelection(qMin(second, _dragSymbol), qMax(second, _dragSymbol), _dragSelType); - _selected[_dragItem] = selState; - if (!_wasSelectedText && (selState == FullItemSel || (selState & 0xFFFF) != ((selState >> 16) & 0xFFFF))) { + if (_selected[_dragItem] != selState) { + _selected[_dragItem] = selState; + repaintItem(_dragItem); + } + if (!_wasSelectedText && (selState == FullSelection || (selState & 0xFFFF) != ((selState >> 16) & 0xFFFF))) { _wasSelectedText = true; setFocus(); } @@ -1641,7 +1702,7 @@ void HistoryInner::onUpdateSelected() { } if (dragFirstAffected) { SelectedItems::const_iterator i = _selected.constFind(dragFirstAffected); - dragSelecting = (i == _selected.cend() || i.value() != FullItemSel); + dragSelecting = (i == _selected.cend() || i.value() != FullSelection); } updateDragSelection(dragSelFrom, dragSelTo, dragSelecting); } @@ -1650,7 +1711,7 @@ void HistoryInner::onUpdateSelected() { if (textlnkDown()) { cur = style::cur_pointer; - } else if (_dragAction == Selecting && !_selected.isEmpty() && _selected.cbegin().value() != FullItemSel) { + } else if (_dragAction == Selecting && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection) { if (!_dragSelFrom || !_dragSelTo) { cur = style::cur_text; } @@ -1663,7 +1724,7 @@ void HistoryInner::onUpdateSelected() { _widget->noSelectingScroll(); } - if (lnkChanged || cur != _cursor) { + if (_dragAction == NoDrag && (lnkChanged || cur != _cursor)) { setCursor(_cursor = cur); } } @@ -1684,7 +1745,7 @@ void HistoryInner::updateDragSelection(HistoryItem *dragSelFrom, HistoryItem *dr force = true; } if (!force) return; - + update(); } @@ -1745,9 +1806,9 @@ void HistoryInner::addSelectionRange(SelectedItems *toItems, int32 fromblock, in if (item->id > 0 && !item->serviceMsg()) { if (i == toItems->cend()) { if (toItems->size() >= MaxSelectedItems) break; - toItems->insert(item, FullItemSel); - } else if (i.value() != FullItemSel) { - *i = FullItemSel; + toItems->insert(item, FullSelection); + } else if (i.value() != FullSelection) { + *i = FullSelection; } } else { if (i != toItems->cend()) { @@ -1768,7 +1829,7 @@ void HistoryInner::applyDragSelection(SelectedItems *toItems) const { } seltoy += _dragSelTo->height(); - if (!toItems->isEmpty() && toItems->cbegin().value() != FullItemSel) { + if (!toItems->isEmpty() && toItems->cbegin().value() != FullSelection) { toItems->clear(); } if (_dragSelecting) { @@ -1945,8 +2006,16 @@ void ReportSpamPanel::setReported(bool reported, PeerData *onPeer) { update(); } -BotKeyboard::BotKeyboard() : _height(0), _maxOuterHeight(0), _maximizeSize(false), _singleUse(false), _forceReply(false), -_sel(-1), _down(-1), _hoverAnim(animFunc(this, &BotKeyboard::hoverStep)), _st(&st::botKbButton) { +BotKeyboard::BotKeyboard() : TWidget() +, _height(0) +, _maxOuterHeight(0) +, _maximizeSize(false) +, _singleUse(false) +, _forceReply(false) +, _sel(-1) +, _down(-1) +, _a_selected(animation(this, &BotKeyboard::step_selected)) +, _st(&st::botKbButton) { setGeometry(0, 0, _st->margin, _st->margin); _height = _st->margin; setMouseTracking(true); @@ -2124,11 +2193,10 @@ bool BotKeyboard::forceReply() const { return _forceReply; } -bool BotKeyboard::hoverStep(float64 ms) { - uint64 now = getms(); +void BotKeyboard::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(now - i.value()) / st::botKbDuration; + float64 dt = float64(ms - i.value()) / st::botKbDuration; if (dt >= 1) { _btns[row][col].hover = (i.key() > 0) ? 1 : 0; i = _animations.erase(i); @@ -2137,8 +2205,10 @@ bool BotKeyboard::hoverStep(float64 ms) { ++i; } } - update(); - return !_animations.isEmpty(); + if (timer) update(); + if (_animations.isEmpty()) { + _a_selected.stop(); + } } void BotKeyboard::resizeToWidth(int32 width, int32 maxOuterHeight) { @@ -2183,7 +2253,7 @@ void BotKeyboard::clearSelection() { _btns[row][col].hover = 0; } _animations.clear(); - _hoverAnim.stop(); + _a_selected.stop(); if (_sel >= 0) { int row = (_sel / MatrixRowShift), col = _sel % MatrixRowShift; _btns[row][col].hover = 0; @@ -2246,7 +2316,7 @@ void BotKeyboard::updateSelected() { _animations.insert(_sel + 1, getms()); } } - if (startanim) _hoverAnim.start(); + if (startanim && !_a_selected.animating()) _a_selected.start(); } } @@ -2258,11 +2328,11 @@ HistoryHider::HistoryHider(MainWidget *parent, bool forwardSelected) : TWidget(p , _cancel(this, lang(lng_cancel), st::cancelBoxButton) , offered(0) , a_opacity(0, 1) +, _a_appearance(animation(this, &HistoryHider::step_appearance)) , hiding(false) , _forwardRequest(0) , toTextWidth(0) -, shadow(st::boxShadow) -{ +, shadow(st::boxShadow) { init(); } @@ -2274,11 +2344,11 @@ HistoryHider::HistoryHider(MainWidget *parent, UserData *sharedContact) : TWidge , _cancel(this, lang(lng_cancel), st::cancelBoxButton) , offered(0) , a_opacity(0, 1) +, _a_appearance(animation(this, &HistoryHider::step_appearance)) , hiding(false) , _forwardRequest(0) , toTextWidth(0) -, shadow(st::boxShadow) -{ +, shadow(st::boxShadow) { init(); } @@ -2290,11 +2360,11 @@ HistoryHider::HistoryHider(MainWidget *parent) : TWidget(parent) , _cancel(this, lang(lng_cancel), st::cancelBoxButton) , offered(0) , a_opacity(0, 1) +, _a_appearance(animation(this, &HistoryHider::step_appearance)) , hiding(false) , _forwardRequest(0) , toTextWidth(0) -, shadow(st::boxShadow) -{ +, shadow(st::boxShadow) { init(); } @@ -2308,11 +2378,11 @@ HistoryHider::HistoryHider(MainWidget *parent, const QString &url, const QString , _cancel(this, lang(lng_cancel), st::cancelBoxButton) , offered(0) , a_opacity(0, 1) +, _a_appearance(animation(this, &HistoryHider::step_appearance)) , hiding(false) , _forwardRequest(0) , toTextWidth(0) -, shadow(st::boxShadow) -{ +, shadow(st::boxShadow) { init(); } @@ -2324,24 +2394,22 @@ void HistoryHider::init() { _chooseWidth = st::forwardFont->width(lang(lng_forward_choose)); resizeEvent(0); - anim::start(this); + _a_appearance.start(); } -bool HistoryHider::animStep(float64 ms) { +void HistoryHider::step_appearance(float64 ms, bool timer) { float64 dt = ms / 200; - bool res = true; if (dt >= 1) { + _a_appearance.stop(); a_opacity.finish(); if (hiding) { QTimer::singleShot(0, this, SLOT(deleteLater())); } - res = false; } else { a_opacity.update(dt, anim::linear); } App::wnd()->getTitle()->setHideLevel(a_opacity.current()); - update(); - return res; + if (timer) update(); } bool HistoryHider::withConfirm() const { @@ -2411,7 +2479,7 @@ void HistoryHider::startHide() { a_opacity.start(0); _send.hide(); _cancel.hide(); - anim::start(this); + _a_appearance.start(); } else { QTimer::singleShot(0, this, SLOT(deleteLater())); } @@ -2503,7 +2571,7 @@ bool HistoryHider::offerPeer(PeerId peer) { if (toTextWidth > box.width() - st::boxPadding.left() - st::boxButtonPadding.right()) { toTextWidth = box.width() - st::boxPadding.left() - st::boxButtonPadding.right(); } - + resizeEvent(0); update(); setFocus(); @@ -2546,6 +2614,7 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) , _replyForwardPressed(false) , _replyReturn(0) , _stickersUpdateRequest(0) +, _savedGifsUpdateRequest(0) , _peer(0) , _clearPeer(0) , _channel(NoChannel) @@ -2561,9 +2630,13 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) , _migrated(0) , _history(0) , _histInited(false) +, _lastScroll(0) +, _lastScrolled(0) , _toHistoryEnd(this, st::historyToEnd) , _collapseComments(this) , _attachMention(this) +, _inlineBot(0) +, _inlineBotResolveRequestId(0) , _reportSpamPanel(this) , _send(this, lang(lng_send_button), st::btnSend) , _unblock(this, lang(lng_unblock_button), st::btnUnblock) @@ -2581,8 +2654,8 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) , _broadcast(this, QString(), true, st::broadcastToggle) , _cmdStartShown(false) , _field(this, st::taMsgField, lang(lng_message_ph)) -, _recordAnim(animFunc(this, &HistoryWidget::recordStep)) -, _recordingAnim(animFunc(this, &HistoryWidget::recordingStep)) +, _a_record(animation(this, &HistoryWidget::step_record)) +, _a_recording(animation(this, &HistoryWidget::step_recording)) , _recording(false), _inRecord(false), _inField(false), _inReply(false) , a_recordingLevel(0, 0), _recordingSamples(0) , a_recordOver(0, 0), a_recordDown(0, 0), a_recordCancel(st::recordCancel->c, st::recordCancel->c) @@ -2597,11 +2670,11 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) , _attachDragDocument(this) , _attachDragPhoto(this) , _fileLoader(this, FileLoaderQueueStopTimeout) -, _synthedTextUpdate(false) +, _textUpdateEventsFlags(TextUpdateEventsSaveDraft | TextUpdateEventsSendTyping) , _serviceImageCacheSize(0) , _confirmWithTextId(0) , _titlePeerTextWidth(0) -, _a_show(animFunc(this, &HistoryWidget::animStep_show)) +, _a_show(animation(this, &HistoryWidget::step_show)) , _scrollDelta(0) , _saveDraftStart(0) , _saveDraftText(false) @@ -2640,6 +2713,8 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) connect(&_scrollTimer, SIGNAL(timeout()), this, SLOT(onScrollTimer())); connect(&_emojiPan, SIGNAL(emojiSelected(EmojiPtr)), &_field, SLOT(onEmojiInsert(EmojiPtr))); connect(&_emojiPan, SIGNAL(stickerSelected(DocumentData*)), this, SLOT(onStickerSend(DocumentData*))); + connect(&_emojiPan, SIGNAL(photoSelected(PhotoData*)), this, SLOT(onPhotoSend(PhotoData*))); + connect(&_emojiPan, SIGNAL(inlineResultSelected(InlineResult*,UserData*)), this, SLOT(onInlineResultSend(InlineResult*,UserData*))); connect(&_emojiPan, SIGNAL(updateStickers()), this, SLOT(updateStickers())); connect(&_sendActionStopTimer, SIGNAL(timeout()), this, SLOT(onCancelSendAction())); connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreviewTimeout())); @@ -2649,6 +2724,9 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) connect(audioCapture(), SIGNAL(onDone(QByteArray,qint32)), this, SLOT(onRecordDone(QByteArray,qint32))); } + _updateHistoryItems.setSingleShot(true); + connect(&_updateHistoryItems, SIGNAL(timeout()), this, SLOT(onUpdateHistoryItems())); + _scrollTimer.setSingleShot(false); _sendActionStopTimer.setSingleShot(true); @@ -2659,7 +2737,8 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) _saveDraftTimer.setSingleShot(true); connect(&_saveDraftTimer, SIGNAL(timeout()), this, SLOT(onDraftSave())); connect(_field.verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onDraftSaveDelayed())); - connect(&_field, SIGNAL(cursorPositionChanged()), this, SLOT(onFieldCursorChanged())); + connect(&_field, SIGNAL(cursorPositionChanged()), this, SLOT(onDraftSaveDelayed())); + connect(&_field, SIGNAL(cursorPositionChanged()), this, SLOT(onCheckMentionDropdown()), Qt::QueuedConnection); _replyForwardPreviewCancel.hide(); @@ -2684,6 +2763,7 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) _attachMention.hide(); connect(&_attachMention, SIGNAL(chosen(QString)), this, SLOT(onMentionHashtagOrBotCommandInsert(QString))); + connect(&_attachMention, SIGNAL(stickerSelected(DocumentData*)), this, SLOT(onStickerSend(DocumentData*))); _field.installEventFilter(&_attachMention); _field.setCtrlEnterSubmit(cCtrlEnter()); @@ -2728,11 +2808,20 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) } void HistoryWidget::start() { - connect(App::main(), SIGNAL(stickersUpdated()), &_emojiPan, SLOT(refreshStickers())); + connect(App::main(), SIGNAL(stickersUpdated()), this, SLOT(onStickersUpdated())); + connect(App::main(), SIGNAL(savedGifsUpdated()), &_emojiPan, SLOT(refreshSavedGifs())); + updateRecentStickers(); + if (App::main()) emit App::main()->savedGifsUpdated(); + connect(App::api(), SIGNAL(fullPeerUpdated(PeerData*)), this, SLOT(onFullPeerUpdated(PeerData*))); } +void HistoryWidget::onStickersUpdated() { + _emojiPan.refreshStickers(); + updateStickersByEmoji(); +} + void HistoryWidget::onMentionHashtagOrBotCommandInsert(QString str) { if (str.at(0) == '/') { // bot command App::sendBotCommand(str); @@ -2742,9 +2831,70 @@ void HistoryWidget::onMentionHashtagOrBotCommandInsert(QString str) { } } +void HistoryWidget::updateInlineBotQuery() { + UserData *bot = _inlineBot; + bool start = false; + QString inlineBotUsername(_inlineBotUsername); + QString query = _field.getInlineBotQuery(_inlineBot, _inlineBotUsername); + if (inlineBotUsername != _inlineBotUsername) { + if (_inlineBotResolveRequestId) { + Notify::inlineBotRequesting(false); + MTP::cancel(_inlineBotResolveRequestId); + _inlineBotResolveRequestId = 0; + } + if (_inlineBot == InlineBotLookingUpData) { + Notify::inlineBotRequesting(true); + _inlineBotResolveRequestId = MTP::send(MTPcontacts_ResolveUsername(MTP_string(_inlineBotUsername)), rpcDone(&HistoryWidget::inlineBotResolveDone), rpcFail(&HistoryWidget::inlineBotResolveFail, _inlineBotUsername)); + return; + } + } else if (_inlineBot == InlineBotLookingUpData) { + return; + } + + if (_inlineBot) { + if (_inlineBot != bot) { + updateFieldPlaceholder(); + } + if (_inlineBot->username == cInlineGifBotUsername() && query.isEmpty()) { + _emojiPan.clearInlineBot(); + } else { + _emojiPan.queryInlineBot(_inlineBot, query); + } + if (!_attachMention.isHidden()) { + _attachMention.hideStart(); + } + } else { + if (_inlineBot != bot) { + updateFieldPlaceholder(); + _field.finishPlaceholder(); + } + _emojiPan.clearInlineBot(); + onCheckMentionDropdown(); + } +} + +void HistoryWidget::updateStickersByEmoji() { + int32 len = 0; + if (EmojiPtr emoji = emojiFromText(_field.getLastText(), &len)) { + if (_field.getLastText().size() <= len) { + _attachMention.showStickers(emoji); + } else { + len = 0; + } + } + if (!len) { + _attachMention.showStickers(EmojiPtr(0)); + } +} + void HistoryWidget::onTextChange() { + updateInlineBotQuery(); + updateStickersByEmoji(); + if (_peer && (!_peer->isChannel() || _peer->isMegagroup() || !_peer->asChannel()->canPublish() || (!_peer->asChannel()->isBroadcast() && !_broadcast.checked()))) { - updateSendAction(_history, SendActionTyping); + if (!_inlineBot && (_textUpdateEventsFlags & TextUpdateEventsSendTyping)) { + updateSendAction(_history, SendActionTyping); + } } if (cHasAudioCapture()) { @@ -2756,7 +2906,7 @@ void HistoryWidget::onTextChange() { } else if (!_field.isHidden() && _send.isHidden()) { _send.show(); setMouseTracking(false); - _recordAnim.stop(); + _a_record.stop(); _inRecord = _inField = false; a_recordOver = a_recordDown = anim::fvalue(0, 0); a_recordCancel = anim::cvalue(st::recordCancel->c, st::recordCancel->c); @@ -2768,13 +2918,13 @@ void HistoryWidget::onTextChange() { update(); } - if (!_peer || _synthedTextUpdate) return; + if (!_peer || !(_textUpdateEventsFlags & TextUpdateEventsSaveDraft)) return; _saveDraftText = true; onDraftSave(true); } void HistoryWidget::onDraftSaveDelayed() { - if (!_peer || _synthedTextUpdate) return; + if (!_peer || !(_textUpdateEventsFlags & TextUpdateEventsSaveDraft)) return; if (!_field.textCursor().anchor() && !_field.textCursor().position() && !_field.verticalScrollBar()->value()) { if (!Local::hasDraftPositions(_peer->id)) return; } @@ -2830,7 +2980,6 @@ void HistoryWidget::onCancelSendAction() { void HistoryWidget::updateSendAction(History *history, SendActionType type, int32 progress) { if (!history) return; - if (type == SendActionTyping && _synthedTextUpdate) return; bool doing = (progress >= 0); @@ -2921,7 +3070,7 @@ void HistoryWidget::onRecordUpdate(qint16 level, qint32 samples) { } a_recordingLevel.start(level); - _recordingAnim.start(); + _a_recording.start(); _recordingSamples = samples; if (samples < 0 || samples >= AudioVoiceMsgFrequency * AudioVoiceMsgMaxLength) { stopRecording(_peer && samples > 0 && _inField); @@ -2933,22 +3082,31 @@ void HistoryWidget::onRecordUpdate(qint16 level, qint32 samples) { } void HistoryWidget::updateStickers() { - if (cLastStickersUpdate() && getms(true) < cLastStickersUpdate() + StickersUpdateTimeout) return; - if (_stickersUpdateRequest) return; - - cSetStickersHash(stickersCountHash(true)); - _stickersUpdateRequest = MTP::send(MTPmessages_GetAllStickers(MTP_int(cStickersHash())), rpcDone(&HistoryWidget::stickersGot), rpcFail(&HistoryWidget::stickersFailed)); -} - -void HistoryWidget::notifyBotCommandsChanged(UserData *user) { - if (_peer && (_peer == user || !_peer->isUser())) { - if (_attachMention.clearFilteredCommands()) { - checkMentionDropdown(); + if (!cLastStickersUpdate() || getms(true) >= cLastStickersUpdate() + StickersUpdateTimeout) { + if (!_stickersUpdateRequest) { + _stickersUpdateRequest = MTP::send(MTPmessages_GetAllStickers(MTP_int(Local::countStickersHash(true))), rpcDone(&HistoryWidget::stickersGot), rpcFail(&HistoryWidget::stickersFailed)); + } + } + if (!cLastSavedGifsUpdate() || getms(true) >= cLastSavedGifsUpdate() + StickersUpdateTimeout) { + if (!_savedGifsUpdateRequest) { + _savedGifsUpdateRequest = MTP::send(MTPmessages_GetSavedGifs(MTP_int(Local::countSavedGifsHash())), rpcDone(&HistoryWidget::savedGifsGot), rpcFail(&HistoryWidget::savedGifsFailed)); } } } -void HistoryWidget::notifyUserIsBotChanged(UserData *user) { +void HistoryWidget::notify_botCommandsChanged(UserData *user) { + if (_peer && (_peer == user || !_peer->isUser())) { + if (_attachMention.clearFilteredBotCommands()) { + onCheckMentionDropdown(); + } + } +} + +void HistoryWidget::notify_inlineBotRequesting(bool requesting) { + _attachEmoji.setLoading(requesting); +} + +void HistoryWidget::notify_userIsBotChanged(UserData *user) { if (_peer && _peer == user) { _list->notifyIsBotChanged(); _list->updateBotInfo(); @@ -2957,7 +3115,7 @@ void HistoryWidget::notifyUserIsBotChanged(UserData *user) { } } -void HistoryWidget::notifyMigrateUpdated(PeerData *peer) { +void HistoryWidget::notify_migrateUpdated(PeerData *peer) { if (_peer) { if (_peer == peer) { if (peer->migrateTo()) { @@ -2978,6 +3136,14 @@ void HistoryWidget::notifyMigrateUpdated(PeerData *peer) { } } +void HistoryWidget::notify_clipStopperHidden(ClipStopperType type) { + if (_list) _list->update(); +} + +void HistoryWidget::notify_historyItemResized(const HistoryItem *row, bool scrollToIt) { + updateListSize(0, false, false, row, scrollToIt); +} + void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { cSetLastStickersUpdate(getms(true)); _stickersUpdateRequest = 0; @@ -2998,24 +3164,24 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { for (int32 i = 0, l = d_sets.size(); i != l; ++i) { if (d_sets.at(i).type() == mtpc_stickerSet) { const MTPDstickerSet &set(d_sets.at(i).c_stickerSet()); - StickerSets::iterator i = sets.find(set.vid.v); + StickerSets::iterator it = sets.find(set.vid.v); QString title = stickerSetTitle(set); - if (i == sets.cend()) { - i = sets.insert(set.vid.v, StickerSet(set.vid.v, set.vaccess_hash.v, title, qs(set.vshort_name), set.vcount.v, set.vhash.v, set.vflags.v | MTPDstickerSet_flag_NOT_LOADED)); + if (it == sets.cend()) { + it = sets.insert(set.vid.v, StickerSet(set.vid.v, set.vaccess_hash.v, title, qs(set.vshort_name), set.vcount.v, set.vhash.v, set.vflags.v | MTPDstickerSet_flag_NOT_LOADED)); } else { - i->access = set.vaccess_hash.v; - i->title = title; - i->shortName = qs(set.vshort_name); - i->flags = set.vflags.v; - if (i->count != set.vcount.v || i->hash != set.vhash.v) { - i->count = set.vcount.v; - i->hash = set.vhash.v; - i->flags |= MTPDstickerSet_flag_NOT_LOADED; // need to request this set + it->access = set.vaccess_hash.v; + it->title = title; + it->shortName = qs(set.vshort_name); + it->flags = set.vflags.v; + if (it->count != set.vcount.v || it->hash != set.vhash.v || it->emoji.isEmpty()) { + it->count = set.vcount.v; + it->hash = set.vhash.v; + it->flags |= MTPDstickerSet_flag_NOT_LOADED; // need to request this set } } - if (!(i->flags & MTPDstickerSet::flag_disabled) || (i->flags & MTPDstickerSet::flag_official)) { + if (!(it->flags & MTPDstickerSet::flag_disabled) || (it->flags & MTPDstickerSet::flag_official)) { setsOrder.push_back(set.vid.v); - if (i->stickers.isEmpty() || (i->flags & MTPDstickerSet_flag_NOT_LOADED)) { + if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_flag_NOT_LOADED)) { setsToRequest.insert(set.vid.v, set.vaccess_hash.v); } } @@ -3039,10 +3205,8 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { } } - int32 countedHash = stickersCountHash(); - cSetStickersHash(countedHash); - if (countedHash != d.vhash.v) { - LOG(("API Error: received stickers hash %1 while counted hash is %2").arg(d.vhash.v).arg(countedHash)); + if (Local::countStickersHash() != d.vhash.v) { + LOG(("API Error: received stickers hash %1 while counted hash is %2").arg(d.vhash.v).arg(Local::countStickersHash())); } if (!setsToRequest.isEmpty() && App::api()) { @@ -3068,6 +3232,59 @@ bool HistoryWidget::stickersFailed(const RPCError &error) { return true; } +void HistoryWidget::savedGifsGot(const MTPmessages_SavedGifs &gifs) { + cSetLastSavedGifsUpdate(getms(true)); + _savedGifsUpdateRequest = 0; + + if (gifs.type() != mtpc_messages_savedGifs) return; + const MTPDmessages_savedGifs &d(gifs.c_messages_savedGifs()); + + const QVector &d_gifs(d.vgifs.c_vector().v); + + SavedGifs &saved(cRefSavedGifs()); + saved.clear(); + + saved.reserve(d_gifs.size()); + for (int32 i = 0, l = d_gifs.size(); i != l; ++i) { + DocumentData *doc = App::feedDocument(d_gifs.at(i)); + if (!doc || !doc->isAnimation()) { + LOG(("API Error: bad document returned in HistoryWidget::savedGifsGot!")); + continue; + } + + saved.push_back(doc); + } + if (Local::countSavedGifsHash() != d.vhash.v) { + LOG(("API Error: received saved gifs hash %1 while counted hash is %2").arg(d.vhash.v).arg(Local::countSavedGifsHash())); + } + + Local::writeSavedGifs(); + + if (App::main()) emit App::main()->savedGifsUpdated(); +} + +void HistoryWidget::saveGif(DocumentData *doc) { + if (doc->isGifv() && cSavedGifs().indexOf(doc) != 0) { + MTP::send(MTPmessages_SaveGif(MTP_inputDocument(MTP_long(doc->id), MTP_long(doc->access)), MTP_bool(false)), rpcDone(&HistoryWidget::saveGifDone, doc)); + } +} + +void HistoryWidget::saveGifDone(DocumentData *doc, const MTPBool &result) { + if (mtpIsTrue(result)) { + App::addSavedGif(doc); + } +} + +bool HistoryWidget::savedGifsFailed(const RPCError &error) { + if (mtpIsFlood(error)) return false; + + LOG(("App Fail: Failed to get saved gifs!")); + + cSetLastSavedGifsUpdate(getms(true)); + _savedGifsUpdateRequest = 0; + return true; +} + void HistoryWidget::clearReplyReturns() { _replyReturns.clear(); _replyReturn = 0; @@ -3155,7 +3372,9 @@ void HistoryWidget::applyDraft(bool parseLinks) { if (!_history) return; setFieldText(_history->draft); _field.setFocus(); - _history->draftCursor.applyTo(_field, &_synthedTextUpdate); + _textUpdateEventsFlags = 0; + _history->draftCursor.applyTo(_field); + _textUpdateEventsFlags = TextUpdateEventsSaveDraft | TextUpdateEventsSendTyping; _previewCancelled = _history->draftPreviewCancelled; if (parseLinks) { onPreviewParse(); @@ -3209,7 +3428,9 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re } } - stopGif(); + if (!cAutoPlayGif()) { + App::stopGifItems(); + } clearReplyReturns(); clearAllLoadRequests(); @@ -3256,6 +3477,12 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re _scroll.takeWidget(); updateTopBarSelection(); + if (_inlineBot) { + _inlineBot = 0; + _emojiPan.clearInlineBot(); + updateFieldPlaceholder(); + } + _showAtMsgId = showAtMsgId; _histInited = false; @@ -3307,6 +3534,8 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re _scroll.setWidget(_list); _list->show(); + _updateHistoryItems.stop(); + if (_history->lastWidth || _history->isReadyFor(_showAtMsgId, _fixedInScrollMsgId, _fixedInScrollMsgTop)) { _fixedInScrollMsgId = 0; _fixedInScrollMsgTop = 0; @@ -3317,7 +3546,7 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re } App::main()->peerUpdated(_peer); - + if (_history->draftToId > 0 || !_history->draft.isEmpty()) { applyDraft(false); _replyToId = readyToForward() ? 0 : _history->draftToId; @@ -3340,7 +3569,9 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re _field.setFocus(); if (!draft.text.isEmpty()) { MessageCursor cur = Local::readDraftPositions(fromMigrated ? _migrated->peer->id : _peer->id); - cur.applyTo(_field, &_synthedTextUpdate); + _textUpdateEventsFlags = 0; + cur.applyTo(_field); + _textUpdateEventsFlags = TextUpdateEventsSaveDraft | TextUpdateEventsSendTyping; } _replyToId = readyToForward() ? 0 : draft.replyTo; _previewCancelled = draft.previewCancelled; @@ -3357,6 +3588,7 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re connect(&_scroll, SIGNAL(geometryChanged()), _list, SLOT(onParentGeometryChanged())); connect(&_scroll, SIGNAL(scrolled()), _list, SLOT(onUpdateSelected())); } else { + setFieldText(QString()); doneShow(); } @@ -3590,7 +3822,7 @@ void HistoryWidget::updateControlsVisibility() { update(); } } else if (_canSendMessages) { - checkMentionDropdown(); + onCheckMentionDropdown(); if (isBotStart()) { if (isBotStart()) { _unblock.hide(); @@ -3625,7 +3857,7 @@ void HistoryWidget::updateControlsVisibility() { } else { _send.show(); setMouseTracking(false); - _recordAnim.stop(); + _a_record.stop(); _inRecord = _inField = false; a_recordOver = anim::fvalue(0, 0); } @@ -3682,11 +3914,10 @@ void HistoryWidget::updateControlsVisibility() { } if (hasBroadcastToggle()) { _broadcast.show(); - _field.setPlaceholder(lang(_broadcast.checked() ? lng_broadcast_ph : lng_comment_ph)); } else { _broadcast.hide(); - _field.setPlaceholder(lang((_history && _history->isChannel() && !_history->isMegagroup()) ? (_peer->asChannel()->canPublish() ? lng_broadcast_ph : lng_comment_ph) : lng_message_ph)); } + updateFieldPlaceholder(); } if (_replyToId || readyToForward() || (_previewData && _previewData->pendingTill >= 0) || _kbReplyTo) { if (_replyForwardPreviewCancel.isHidden()) { @@ -3773,8 +4004,8 @@ bool HistoryWidget::messagesFailed(const RPCError &error, mtpRequestId requestId if (error.type() == qstr("CHANNEL_PRIVATE")) { PeerData *was = _peer; - App::main()->showDialogs(); - App::wnd()->showLayer(new InformBox(lang((was && was->isMegagroup()) ? lng_group_not_accessible : lng_channel_not_accessible))); + Ui::showChatsList(); + Ui::showLayer(new InformBox(lang((was && was->isMegagroup()) ? lng_group_not_accessible : lng_channel_not_accessible))); return true; } @@ -3785,7 +4016,7 @@ bool HistoryWidget::messagesFailed(const RPCError &error, mtpRequestId requestId _preloadDownRequest = 0; } else if (_firstLoadRequest == requestId) { _firstLoadRequest = 0; - App::main()->showDialogs(); + Ui::showChatsList(); } else if (_delayedShowAtRequest == requestId) { _delayedShowAtRequest = 0; } @@ -4046,7 +4277,7 @@ void HistoryWidget::loadMessagesDown() { if (_history->isEmpty() && _migrated && _migrated->isEmpty()) { return firstLoadMessages(); } - + bool loadMigrated = _migrated && !(_migrated->isEmpty() || _migrated->loadedAtBottom() || (!_history->isEmpty() && !_history->loadedAtTop())); History *from = loadMigrated ? _migrated : _history; if (from->loadedAtBottom()) { @@ -4136,7 +4367,7 @@ void HistoryWidget::onListScroll() { updateToEndVisibility(); updateCollapseCommentsVisibility(); - + int st = _scroll.scrollTop(), stm = _scroll.scrollTopMax(), sh = _scroll.height(); if (st + PreloadHeightsCount * sh > stm) { loadMessagesDown(); @@ -4157,6 +4388,11 @@ void HistoryWidget::onListScroll() { break; } } + + if (st != _lastScroll) { + _lastScrolled = getms(); + _lastScroll = st; + } } void HistoryWidget::onVisibleChanged() { @@ -4291,7 +4527,7 @@ bool HistoryWidget::joinFail(const RPCError &error, mtpRequestId req) { if (_unblockRequest == req) _unblockRequest = 0; if (error.type() == qstr("CHANNEL_PRIVATE")) { - App::wnd()->showLayer(new InformBox(lang((_peer && _peer->isMegagroup()) ? lng_group_not_accessible : lng_channel_not_accessible))); + Ui::showLayer(new InformBox(lang((_peer && _peer->isMegagroup()) ? lng_group_not_accessible : lng_channel_not_accessible))); return true; } return false; @@ -4302,13 +4538,13 @@ void HistoryWidget::onMuteUnmute() { } void HistoryWidget::onBroadcastChange() { - _field.setPlaceholder(lang(_broadcast.checked() ? lng_broadcast_ph : lng_comment_ph)); + updateFieldPlaceholder(); } void HistoryWidget::onShareContact(const PeerId &peer, UserData *contact) { if (!contact || contact->phone.isEmpty()) return; - App::main()->showPeerHistory(peer, ShowAtTheEndMsgId); + Ui::showPeerHistory(peer, ShowAtTheEndMsgId); if (!_history) return; shareContact(peer, contact->phone, contact->firstName, contact->lastName, replyToId(), peerToUser(contact->id)); @@ -4325,7 +4561,7 @@ void HistoryWidget::shareContact(const PeerId &peer, const QString &phone, const PeerData *p = App::peer(peer); int32 flags = newMessageFlags(p) | MTPDmessage::flag_media; // unread, out - + bool lastKeyboardUsed = lastForceReplyReplied(FullMsgId(peerToChannel(peer), replyTo)); int32 sendFlags = 0; @@ -4341,7 +4577,7 @@ void HistoryWidget::shareContact(const PeerId &peer, const QString &phone, const } else { flags |= MTPDmessage::flag_from_id; } - h->addNewMessage(MTP_message(MTP_int(flags), MTP_int(newId.msg), MTP_int(fromChannelName ? 0 : MTP::authedId()), peerToMTP(peer), MTPPeer(), MTPint(), MTP_int(replyToId()), MTP_int(unixtime()), MTP_string(""), MTP_messageMediaContact(MTP_string(phone), MTP_string(fname), MTP_string(lname), MTP_int(userId)), MTPnullMarkup, MTPnullEntities, MTP_int(1)), NewMessageUnread); + h->addNewMessage(MTP_message(MTP_int(flags), MTP_int(newId.msg), MTP_int(fromChannelName ? 0 : MTP::authedId()), peerToMTP(peer), MTPPeer(), MTPint(), MTPint(), MTP_int(replyToId()), MTP_int(unixtime()), MTP_string(""), MTP_messageMediaContact(MTP_string(phone), MTP_string(fname), MTP_string(lname), MTP_int(userId)), MTPnullMarkup, MTPnullEntities, MTP_int(1)), NewMessageUnread); h->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_int(sendFlags), p->input, MTP_int(replyTo), MTP_inputMediaContact(MTP_string(phone), MTP_string(fname), MTP_string(lname)), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, h->sendRequestId); App::historyRegRandom(randomId, newId); @@ -4351,7 +4587,7 @@ void HistoryWidget::shareContact(const PeerId &peer, const QString &phone, const } void HistoryWidget::onSendPaths(const PeerId &peer) { - App::main()->showPeerHistory(peer, ShowAtTheEndMsgId); + Ui::showPeerHistory(peer, ShowAtTheEndMsgId); if (!_history) return; if (cSendPaths().size() == 1) { @@ -4431,15 +4667,13 @@ void HistoryWidget::animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTo activate(); } -bool HistoryWidget::animStep_show(float64 ms) { +void HistoryWidget::step_show(float64 ms, bool timer) { float64 dt = ms / st::slideDuration; - bool res = true; if (dt >= 1) { _a_show.stop(); _sideShadow.setVisible(cWideMode()); _topShadow.setVisible(_peer ? true : false); - res = false; a_coordUnder.finish(); a_coordOver.finish(); a_shadow.finish(); @@ -4453,9 +4687,10 @@ bool HistoryWidget::animStep_show(float64 ms) { a_coordOver.update(dt, st::slideFunction); a_shadow.update(dt, st::slideFunction); } - update(); - App::main()->topBar()->update(); - return res; + if (timer) { + update(); + App::main()->topBar()->update(); + } } void HistoryWidget::doneShow() { @@ -4481,11 +4716,10 @@ void HistoryWidget::animStop() { _topShadow.setVisible(_peer ? true : false); } -bool HistoryWidget::recordStep(float64 ms) { +void HistoryWidget::step_record(float64 ms, bool timer) { float64 dt = ms / st::btnSend.duration; - bool res = true; if (dt >= 1 || !_send.isHidden() || isBotStart() || isBlocked()) { - res = false; + _a_record.stop(); a_recordOver.finish(); a_recordDown.finish(); a_recordCancel.finish(); @@ -4494,25 +4728,24 @@ bool HistoryWidget::recordStep(float64 ms) { a_recordDown.update(dt, anim::linear); a_recordCancel.update(dt, anim::linear); } - if (_recording) { - updateField(); - } else { - update(_send.geometry()); + if (timer) { + if (_recording) { + updateField(); + } else { + update(_send.geometry()); + } } - return res; } -bool HistoryWidget::recordingStep(float64 ms) { +void HistoryWidget::step_recording(float64 ms, bool timer) { float64 dt = ms / AudioVoiceMsgUpdateView; - bool res = true; if (dt >= 1) { - res = false; + _a_recording.stop(); a_recordingLevel.finish(); } else { a_recordingLevel.update(dt, anim::linear); } - update(_attachDocument.geometry()); - return res; + if (timer) update(_attachDocument.geometry()); } void HistoryWidget::onPhotoSelect() { @@ -4529,7 +4762,7 @@ void HistoryWidget::onPhotoSelect() { } QStringList photoExtensions(cPhotoExtensions()); - QStringList imgExtensions(cImgExtensions()); + QStringList imgExtensions(cImgExtensions()); QString filter(qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;Photo files (*") + photoExtensions.join(qsl(" *")) + qsl(");;All files (*.*)")); QStringList files; @@ -4557,7 +4790,7 @@ void HistoryWidget::onDocumentSelect() { } QStringList photoExtensions(cPhotoExtensions()); - QStringList imgExtensions(cImgExtensions()); + QStringList imgExtensions(cImgExtensions()); QString filter(qsl("All files (*.*);;Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;Photo files (*") + photoExtensions.join(qsl(" *")) + qsl(")")); QStringList files; @@ -4625,7 +4858,7 @@ void HistoryWidget::mouseMoveEvent(QMouseEvent *e) { _inReply = inReply; setCursor(inReply ? style::cur_pointer : style::cur_default); } - if (startAnim) _recordAnim.start(); + if (startAnim) _a_record.start(); } void HistoryWidget::leaveToChildEvent(QEvent *e) { // e -- from enterEvent() of child TWidget @@ -4650,7 +4883,7 @@ void HistoryWidget::stopRecording(bool send) { audioCapture()->stop(send); a_recordingLevel = anim::ivalue(0, 0); - _recordingAnim.stop(); + _a_recording.stop(); _recording = false; _recordingSamples = 0; @@ -4666,7 +4899,7 @@ void HistoryWidget::stopRecording(bool send) { a_recordDown.start(0); a_recordOver.restart(); a_recordCancel = anim::cvalue(st::recordCancel->c, st::recordCancel->c); - _recordAnim.start(); + _a_record.start(); } void HistoryWidget::sendBotCommand(const QString &cmd, MsgId replyTo) { // replyTo != 0 from ReplyKeyboardMarkup, == 0 from cmd links @@ -4695,31 +4928,48 @@ void HistoryWidget::sendBotCommand(const QString &cmd, MsgId replyTo) { // reply _field.setFocus(); } -void HistoryWidget::insertBotCommand(const QString &cmd) { - if (!_history) return; +bool HistoryWidget::insertBotCommand(const QString &cmd, bool specialGif) { + if (!_history) return false; QString toInsert = cmd; - PeerData *bot = _peer->isUser() ? _peer : (App::hoveredLinkItem() ? (App::hoveredLinkItem()->toHistoryForwarded() ? App::hoveredLinkItem()->toHistoryForwarded()->fromForwarded() : App::hoveredLinkItem()->from()) : 0); - if (!bot->isUser() || !bot->asUser()->botInfo) bot = 0; - QString username = bot ? bot->asUser()->username : QString(); - int32 botStatus = _peer->isChat() ? _peer->asChat()->botStatus : (_peer->isMegagroup() ? _peer->asChannel()->mgInfo->botStatus : -1); - if (toInsert.indexOf('@') < 2 && !username.isEmpty() && (botStatus == 0 || botStatus == 2)) { - toInsert += '@' + username; + if (!toInsert.isEmpty() && toInsert.at(0) != '@') { + PeerData *bot = _peer->isUser() ? _peer : (App::hoveredLinkItem() ? (App::hoveredLinkItem()->toHistoryForwarded() ? App::hoveredLinkItem()->toHistoryForwarded()->fromForwarded() : App::hoveredLinkItem()->from()) : 0); + if (!bot->isUser() || !bot->asUser()->botInfo) bot = 0; + QString username = bot ? bot->asUser()->username : QString(); + int32 botStatus = _peer->isChat() ? _peer->asChat()->botStatus : (_peer->isMegagroup() ? _peer->asChannel()->mgInfo->botStatus : -1); + if (toInsert.indexOf('@') < 0 && !username.isEmpty() && (botStatus == 0 || botStatus == 2)) { + toInsert += '@' + username; + } } toInsert += ' '; - QString text = _field.getLastText(); - QRegularExpressionMatch m = QRegularExpression(qsl("^/[A-Za-z_0-9]{0,64}(@[A-Za-z_0-9]{0,32})?(\\s|$)")).match(text); - if (m.hasMatch()) { - text = toInsert + text.mid(m.capturedLength()); - } else { - text = toInsert + text; - } - _field.setText(text); + if (toInsert.at(0) != '@') { + QString text = _field.getLastText(); + if (specialGif) { + if (text.trimmed() == '@' + cInlineGifBotUsername() && text.at(0) == '@') { + setFieldText(QString(), TextUpdateEventsSaveDraft, false); + } + } else { + QRegularExpressionMatch m = QRegularExpression(qsl("^/[A-Za-z_0-9]{0,64}(@[A-Za-z_0-9]{0,32})?(\\s|$)")).match(text); + if (m.hasMatch()) { + text = toInsert + text.mid(m.capturedLength()); + } else { + text = toInsert + text; + } + _field.setTextFast(text); - QTextCursor cur(_field.textCursor()); - cur.movePosition(QTextCursor::End); - _field.setTextCursor(cur); + QTextCursor cur(_field.textCursor()); + cur.movePosition(QTextCursor::End); + _field.setTextCursor(cur); + } + } else { + if (!specialGif || _field.getLastText().isEmpty()) { + setFieldText(toInsert, TextUpdateEventsSaveDraft, false); + _field.setFocus(); + return true; + } + } + return false; } bool HistoryWidget::eventFilter(QObject *obj, QEvent *e) { @@ -4815,6 +5065,30 @@ bool HistoryWidget::hasBroadcastToggle() const { return _peer && _peer->isChannel() && !_peer->isMegagroup() && _peer->asChannel()->canPublish() && !_peer->asChannel()->isBroadcast(); } +void HistoryWidget::inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result) { + _inlineBotResolveRequestId = 0; + Notify::inlineBotRequesting(false); + _inlineBotUsername = QString(); + if (result.type() == mtpc_contacts_resolvedPeer) { + const MTPDcontacts_resolvedPeer &d(result.c_contacts_resolvedPeer()); + App::feedUsers(d.vusers); + App::feedChats(d.vchats); + } + updateInlineBotQuery(); +} + +bool HistoryWidget::inlineBotResolveFail(QString name, const RPCError &error) { + if (mtpIsFlood(error)) return false; + + _inlineBotResolveRequestId = 0; + Notify::inlineBotRequesting(false); + if (name == _inlineBotUsername) { + _inlineBot = 0; + onCheckMentionDropdown(); + } + return true; +} + bool HistoryWidget::isBotStart() const { if (!_peer || !_peer->isUser() || !_peer->asUser()->botInfo || !_canSendMessages) return false; return !_peer->asUser()->botInfo->startToken.isEmpty() || (_history->isEmpty() && !_history->lastMsg); @@ -5056,7 +5330,7 @@ void HistoryWidget::topBarClick() { if (cWideMode()) { if (_history) App::main()->showPeerProfile(_peer); } else { - App::main()->showDialogs(); + Ui::showChatsList(); } } @@ -5148,7 +5422,7 @@ void HistoryWidget::onFieldResize() { _cmdStart.move(_attachEmoji.x() - _cmdStart.width(), height() - kbh - _cmdStart.height()); _attachType.move(0, _attachDocument.y() - _attachType.height()); - _emojiPan.move(width() - _emojiPan.width(), _attachEmoji.y() - _emojiPan.height()); + _emojiPan.moveBottom(_attachEmoji.y()); updateListSize(); updateField(); @@ -5158,24 +5432,27 @@ void HistoryWidget::onFieldFocused() { if (_list) _list->clearSelectedItems(true); } -void HistoryWidget::checkMentionDropdown() { +void HistoryWidget::onCheckMentionDropdown() { if (!_history || _a_show.animating()) return; - QString start; - _field.getMentionHashtagBotCommandStart(start); - if (!start.isEmpty()) { - if (start.at(0) == '#' && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) Local::readRecentHashtags(); - if (start.at(0) == '@' && _peer->isUser()) return; - if (start.at(0) == '/' && _peer->isUser() && !_peer->asUser()->botInfo) return; - _attachMention.showFiltered(_peer, start); - } else if (!_attachMention.isHidden()) { - _attachMention.hideStart(); + bool start = false; + QString query = _inlineBot ? QString() : _field.getMentionHashtagBotCommandPart(start); + if (!query.isEmpty()) { + if (query.at(0) == '#' && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) Local::readRecentHashtagsAndBots(); + if (query.at(0) == '@' && cRecentInlineBots().isEmpty()) Local::readRecentHashtagsAndBots(); + if (query.at(0) == '/' && _peer->isUser() && !_peer->asUser()->botInfo) return; } + _attachMention.showFiltered(_peer, query, start); } -void HistoryWidget::onFieldCursorChanged() { - checkMentionDropdown(); - onDraftSaveDelayed(); +void HistoryWidget::updateFieldPlaceholder() { + if (_inlineBot && _inlineBot != InlineBotLookingUpData) { + _field.setPlaceholder(_inlineBot->botInfo->inlinePlaceholder.mid(1), _inlineBot->username.size() + 2); + } else if (hasBroadcastToggle()) { + _field.setPlaceholder(lang(_broadcast.checked() ? lng_broadcast_ph : lng_comment_ph)); + } else { + _field.setPlaceholder(lang((_history && _history->isChannel() && !_history->isMegagroup()) ? (_peer->asChannel()->canPublish() ? lng_broadcast_ph : lng_comment_ph) : lng_message_ph)); + } } void HistoryWidget::uploadImage(const QImage &img, PrepareMediaType type, FileLoadForceConfirmType confirm, const QString &source, bool withText) { @@ -5232,7 +5509,7 @@ void HistoryWidget::shareContactWithConfirm(const QString &phone, const QString App::wnd()->activateWindow(); _confirmWithTextId = 0xFFFFFFFFFFFFFFFFL; - App::wnd()->showLayer(new PhotoSendBox(phone, fname, lname, replyTo)); + Ui::showLayer(new PhotoSendBox(phone, fname, lname, replyTo)); } void HistoryWidget::confirmSendFile(const FileLoadResultPtr &file, bool ctrlShiftEnter) { @@ -5269,14 +5546,14 @@ void HistoryWidget::confirmSendFile(const FileLoadResultPtr &file, bool ctrlShif flags |= MTPDmessage::flag_from_id; } if (file->type == PreparePhoto) { - h->addNewMessage(MTP_message(MTP_int(flags), MTP_int(newId.msg), MTP_int(fromChannelName ? 0 : MTP::authedId()), peerToMTP(file->to.peer), MTPPeer(), MTPint(), MTP_int(file->to.replyTo), MTP_int(unixtime()), MTP_string(""), MTP_messageMediaPhoto(file->photo, MTP_string(file->photoCaption)), MTPnullMarkup, MTPnullEntities, MTP_int(1)), NewMessageUnread); + h->addNewMessage(MTP_message(MTP_int(flags), MTP_int(newId.msg), MTP_int(fromChannelName ? 0 : MTP::authedId()), peerToMTP(file->to.peer), MTPPeer(), MTPint(), MTPint(), MTP_int(file->to.replyTo), MTP_int(unixtime()), MTP_string(""), MTP_messageMediaPhoto(file->photo, MTP_string(file->caption)), MTPnullMarkup, MTPnullEntities, MTP_int(1)), NewMessageUnread); } else if (file->type == PrepareDocument) { - h->addNewMessage(MTP_message(MTP_int(flags), MTP_int(newId.msg), MTP_int(fromChannelName ? 0 : MTP::authedId()), peerToMTP(file->to.peer), MTPPeer(), MTPint(), MTP_int(file->to.replyTo), MTP_int(unixtime()), MTP_string(""), MTP_messageMediaDocument(file->document), MTPnullMarkup, MTPnullEntities, MTP_int(1)), NewMessageUnread); + h->addNewMessage(MTP_message(MTP_int(flags), MTP_int(newId.msg), MTP_int(fromChannelName ? 0 : MTP::authedId()), peerToMTP(file->to.peer), MTPPeer(), MTPint(), MTPint(), MTP_int(file->to.replyTo), MTP_int(unixtime()), MTP_string(""), MTP_messageMediaDocument(file->document, MTP_string(file->caption)), MTPnullMarkup, MTPnullEntities, MTP_int(1)), NewMessageUnread); } else if (file->type == PrepareAudio) { if (!h->peer->isChannel()) { flags |= MTPDmessage::flag_media_unread; } - h->addNewMessage(MTP_message(MTP_int(flags), MTP_int(newId.msg), MTP_int(fromChannelName ? 0 : MTP::authedId()), peerToMTP(file->to.peer), MTPPeer(), MTPint(), MTP_int(file->to.replyTo), MTP_int(unixtime()), MTP_string(""), MTP_messageMediaAudio(file->audio), MTPnullMarkup, MTPnullEntities, MTP_int(1)), NewMessageUnread); + h->addNewMessage(MTP_message(MTP_int(flags), MTP_int(newId.msg), MTP_int(fromChannelName ? 0 : MTP::authedId()), peerToMTP(file->to.peer), MTPPeer(), MTPint(), MTPint(), MTP_int(file->to.replyTo), MTP_int(unixtime()), MTP_string(""), MTP_messageMediaAudio(file->audio), MTPnullMarkup, MTPnullEntities, MTP_int(1)), NewMessageUnread); } if (_peer && file->to.peer == _peer->id) { @@ -5340,7 +5617,12 @@ namespace { MTPVector _composeDocumentAttributes(DocumentData *document) { QVector attributes(1, MTP_documentAttributeFilename(MTP_string(document->name))); if (document->dimensions.width() > 0 && document->dimensions.height() > 0) { - attributes.push_back(MTP_documentAttributeImageSize(MTP_int(document->dimensions.width()), MTP_int(document->dimensions.height()))); + int32 duration = document->duration(); + if (duration >= 0) { + attributes.push_back(MTP_documentAttributeVideo(MTP_int(duration), MTP_int(document->dimensions.width()), MTP_int(document->dimensions.height()))); + } else { + attributes.push_back(MTP_documentAttributeImageSize(MTP_int(document->dimensions.width()), MTP_int(document->dimensions.height()))); + } } if (document->type == AnimatedDocument) { attributes.push_back(MTP_documentAttributeAnimated()); @@ -5357,12 +5639,7 @@ void HistoryWidget::onDocumentUploaded(const FullMsgId &newId, const MTPInputFil if (!MTP::authedId()) return; HistoryMessage *item = dynamic_cast(App::histItemById(newId)); if (item) { - DocumentData *document = 0; - if (HistoryDocument *media = dynamic_cast(item->getMedia())) { - document = media->document(); - } else if (HistorySticker *media = dynamic_cast(item->getMedia())) { - document = media->document(); - } + DocumentData *document = item->getMedia() ? item->getMedia()->getDocument() : 0; if (document) { uint64 randomId = MTP::nonce(); App::historyRegRandom(randomId, newId); @@ -5377,7 +5654,8 @@ void HistoryWidget::onDocumentUploaded(const FullMsgId &newId, const MTPInputFil if (fromChannelName) { sendFlags |= MTPmessages_SendMedia::flag_broadcast; } - hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_int(sendFlags), item->history()->peer->input, MTP_int(replyTo), MTP_inputMediaUploadedDocument(file, MTP_string(document->mime), _composeDocumentAttributes(document)), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId); + QString caption = item->getMedia() ? item->getMedia()->getCaption() : QString(); + hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_int(sendFlags), item->history()->peer->input, MTP_int(replyTo), MTP_inputMediaUploadedDocument(file, MTP_string(document->mime), _composeDocumentAttributes(document), MTP_string(caption)), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId); } } } @@ -5386,12 +5664,7 @@ void HistoryWidget::onThumbDocumentUploaded(const FullMsgId &newId, const MTPInp if (!MTP::authedId()) return; HistoryMessage *item = dynamic_cast(App::histItemById(newId)); if (item) { - DocumentData *document = 0; - if (HistoryDocument *media = dynamic_cast(item->getMedia())) { - document = media->document(); - } else if (HistorySticker *media = dynamic_cast(item->getMedia())) { - document = media->document(); - } + DocumentData *document = item->getMedia() ? item->getMedia()->getDocument() : 0; if (document) { uint64 randomId = MTP::nonce(); App::historyRegRandom(randomId, newId); @@ -5406,7 +5679,8 @@ void HistoryWidget::onThumbDocumentUploaded(const FullMsgId &newId, const MTPInp if (fromChannelName) { sendFlags |= MTPmessages_SendMedia::flag_broadcast; } - hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_int(sendFlags), item->history()->peer->input, MTP_int(replyTo), MTP_inputMediaUploadedThumbDocument(file, thumb, MTP_string(document->mime), _composeDocumentAttributes(document)), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId); + QString caption = item->getMedia() ? item->getMedia()->getCaption() : QString(); + hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_int(sendFlags), item->history()->peer->input, MTP_int(replyTo), MTP_inputMediaUploadedThumbDocument(file, thumb, MTP_string(document->mime), _composeDocumentAttributes(document), MTP_string(caption)), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId); } } } @@ -5445,18 +5719,19 @@ void HistoryWidget::onPhotoProgress(const FullMsgId &newId) { if (!item->fromChannel()) { updateSendAction(item->history(), SendActionUploadPhoto, 0); } -// msgUpdated(item); + Ui::repaintHistoryItem(item); } } void HistoryWidget::onDocumentProgress(const FullMsgId &newId) { if (!MTP::authedId()) return; if (HistoryItem *item = App::histItemById(newId)) { - DocumentData *doc = (item->getMedia() && item->getMedia()->type() == MediaTypeDocument) ? static_cast(item->getMedia())->document() : 0; + HistoryMedia *media = item->getMedia(); + DocumentData *doc = media ? media->getDocument() : 0; if (!item->fromChannel()) { updateSendAction(item->history(), SendActionUploadFile, doc ? doc->uploadOffset : 0); } - msgUpdated(item); + Ui::repaintHistoryItem(item); } } @@ -5467,7 +5742,7 @@ void HistoryWidget::onAudioProgress(const FullMsgId &newId) { if (!item->fromChannel()) { updateSendAction(item->history(), SendActionUploadAudio, audio ? audio->uploadOffset : 0); } - msgUpdated(item); + Ui::repaintHistoryItem(item); } } @@ -5478,7 +5753,7 @@ void HistoryWidget::onPhotoFailed(const FullMsgId &newId) { if (!item->fromChannel()) { updateSendAction(item->history(), SendActionUploadPhoto, -1); } -// msgUpdated(item); +// Ui::repaintHistoryItem(item); } } @@ -5489,7 +5764,7 @@ void HistoryWidget::onDocumentFailed(const FullMsgId &newId) { if (!item->fromChannel()) { updateSendAction(item->history(), SendActionUploadFile, -1); } - msgUpdated(item); + Ui::repaintHistoryItem(item); } } @@ -5500,21 +5775,21 @@ void HistoryWidget::onAudioFailed(const FullMsgId &newId) { if (!item->fromChannel()) { updateSendAction(item->history(), SendActionUploadAudio, -1); } - msgUpdated(item); + Ui::repaintHistoryItem(item); } } void HistoryWidget::onReportSpamClicked() { ConfirmBox *box = new ConfirmBox(lang(_peer->isUser() ? lng_report_spam_sure : ((_peer->isChat() || _peer->isMegagroup()) ? lng_report_spam_sure_group : lng_report_spam_sure_channel)), lang(lng_report_spam_ok), st::attentionBoxButton); connect(box, SIGNAL(confirmed()), this, SLOT(onReportSpamSure())); - App::wnd()->showLayer(box); + Ui::showLayer(box); _clearPeer = _peer; } void HistoryWidget::onReportSpamSure() { if (_reportSpamRequest) return; - App::wnd()->hideLayer(); + Ui::hideLayer(); if (_clearPeer->isUser()) MTP::send(MTPcontacts_Block(_clearPeer->asUser()->inputUser), rpcDone(&HistoryWidget::blockDone, _clearPeer), RPCFailHandlerPtr(), 0, 5); _reportSpamRequest = MTP::send(MTPmessages_ReportSpam(_clearPeer->input), rpcDone(&HistoryWidget::reportSpamDone, _clearPeer), rpcFail(&HistoryWidget::reportSpamFail)); } @@ -5554,10 +5829,10 @@ void HistoryWidget::onReportSpamClear() { if (_clearPeer->isUser()) { App::main()->deleteConversation(_clearPeer); } else if (_clearPeer->isChat()) { - App::main()->showDialogs(); + Ui::showChatsList(); MTP::send(MTPmessages_DeleteChatUser(_clearPeer->asChat()->inputChat, App::self()->inputUser), App::main()->rpcDone(&MainWidget::deleteHistoryAfterLeave, _clearPeer), App::main()->rpcFail(&MainWidget::leaveChatFailed, _clearPeer)); } else if (_clearPeer->isChannel()) { - App::main()->showDialogs(); + Ui::showChatsList(); if (_clearPeer->migrateFrom()) { App::main()->deleteConversation(_clearPeer->migrateFrom()); } @@ -5587,10 +5862,59 @@ void HistoryWidget::peerMessagesUpdated() { if (_list) peerMessagesUpdated(_peer->id); } -void HistoryWidget::msgUpdated(const HistoryItem *msg) { - if (_peer && _list && (msg->history() == _history || (_migrated && msg->history() == _migrated))) { - _list->updateMsg(msg); +bool HistoryWidget::isItemVisible(HistoryItem *item) { + if (isHidden() || _a_show.animating() || !_list) { + return false; } + int32 top = _list->itemTop(item), st = _scroll.scrollTop(); + if (top < 0 || top + item->height() <= st || top >= st + _scroll.height()) { + return false; + } + return true; +} + +void HistoryWidget::ui_repaintHistoryItem(const HistoryItem *item) { + if (_peer && _list && (item->history() == _history || (_migrated && item->history() == _migrated))) { + uint64 ms = getms(); + if (_lastScrolled + 100 <= ms) { + _list->repaintItem(item); + } else { + _updateHistoryItems.start(_lastScrolled + 100 - ms); + } + } +} + +void HistoryWidget::onUpdateHistoryItems() { + if (!_list) return; + + uint64 ms = getms(); + if (_lastScrolled + 100 <= ms) { + _list->update(); + } else { + _updateHistoryItems.start(_lastScrolled + 100 - ms); + } +} + +void HistoryWidget::ui_repaintInlineItem(const LayoutInlineItem *layout) { + _emojiPan.ui_repaintInlineItem(layout); +} + +bool HistoryWidget::ui_isInlineItemVisible(const LayoutInlineItem *layout) { + return _emojiPan.ui_isInlineItemVisible(layout); +} + +bool HistoryWidget::ui_isInlineItemBeingChosen() { + return _emojiPan.ui_isInlineItemBeingChosen(); +} + +void HistoryWidget::notify_historyItemLayoutChanged(const HistoryItem *item) { + if (_peer && _list && (item == App::mousedItem() || item == App::hoveredItem() || item == App::hoveredLinkItem())) { + _list->onUpdateSelected(); + } +} + +void HistoryWidget::notify_automaticLoadSettingsChangedGif() { + _emojiPan.notify_automaticLoadSettingsChangedGif(); } void HistoryWidget::resizeEvent(QResizeEvent *e) { @@ -5630,8 +5954,8 @@ void HistoryWidget::resizeEvent(QResizeEvent *e) { _cmdStart.move(_attachEmoji.x() - _cmdStart.width(), height() - kbh - _cmdStart.height()); _attachType.move(0, _attachDocument.y() - _attachType.height()); + _emojiPan.moveBottom(_attachEmoji.y()); _emojiPan.setMaxHeight(height() - st::dropdownDef.padding.top() - st::dropdownDef.padding.bottom() - _attachEmoji.height()); - _emojiPan.move(width() - _emojiPan.width(), _attachEmoji.y() - _emojiPan.height()); switch (_attachDrag) { case DragStateFiles: @@ -5670,17 +5994,6 @@ void HistoryWidget::itemRemoved(HistoryItem *item) { } } -void HistoryWidget::itemReplaced(HistoryItem *oldItem, HistoryItem *newItem) { - if (_list) _list->itemReplaced(oldItem, newItem); - if (_replyTo == oldItem) _replyTo = newItem; - if (_kbReplyTo == oldItem) _kbReplyTo = newItem; - if (_replyReturn == oldItem) _replyReturn = newItem; -} - -void HistoryWidget::itemResized(HistoryItem *row, bool scrollToIt) { - updateListSize(0, false, false, row, scrollToIt); -} - void HistoryWidget::updateScrollColors() { if (!App::historyScrollBarColor()) return; _scroll.updateColors(App::historyScrollBarColor(), App::historyScrollBgColor(), App::historyScrollBarOverColor(), App::historyScrollBgOverColor()); @@ -5690,7 +6003,7 @@ MsgId HistoryWidget::replyToId() const { return _replyToId ? _replyToId : (_kbReplyTo ? _kbReplyTo->id : 0); } -void HistoryWidget::updateListSize(int32 addToY, bool initial, bool loadedDown, HistoryItem *resizedItem, bool scrollToIt) { +void HistoryWidget::updateListSize(int32 addToY, bool initial, bool loadedDown, const HistoryItem *resizedItem, bool scrollToIt) { if (!_history || (initial && _histInited) || (!initial && !_histInited)) return; if (_firstLoadRequest) { if (resizedItem) _list->recountHeight(resizedItem); @@ -6008,9 +6321,9 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { a_recordDown.start(1); a_recordOver.restart(); - _recordAnim.start(); + _a_record.start(); } else if (_inReply) { - App::main()->showPeerHistory(_peer->id, replyToId()); + Ui::showPeerHistory(_peer, replyToId()); } } @@ -6021,13 +6334,14 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape) { e->ignore(); } else if (e->key() == Qt::Key_Back) { - onCancel(); + Ui::showChatsList(); + emit cancelled(); } else if (e->key() == Qt::Key_PageDown) { if ((e->modifiers() & Qt::ControlModifier) || (e->modifiers() & Qt::MetaModifier)) { PeerData *after = 0; MsgId afterMsgId = 0; App::main()->peerAfter(_peer, msgid, after, afterMsgId); - if (after) App::main()->showPeerHistory(after->id, afterMsgId); + if (after) Ui::showPeerHistory(after, afterMsgId); } else { _scroll.keyPressEvent(e); } @@ -6036,7 +6350,7 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) { PeerData *before = 0; MsgId beforeMsgId = 0; App::main()->peerBefore(_peer, msgid, before, beforeMsgId); - if (before) App::main()->showPeerHistory(before->id, beforeMsgId); + if (before) Ui::showPeerHistory(before, beforeMsgId); } else { _scroll.keyPressEvent(e); } @@ -6045,7 +6359,7 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) { PeerData *after = 0; MsgId afterMsgId = 0; App::main()->peerAfter(_peer, msgid, after, afterMsgId); - if (after) App::main()->showPeerHistory(after->id, afterMsgId); + if (after) Ui::showPeerHistory(after, afterMsgId); } else if (!(e->modifiers() & (Qt::ShiftModifier | Qt::MetaModifier | Qt::ControlModifier))) { _scroll.keyPressEvent(e); } @@ -6054,7 +6368,7 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) { PeerData *before = 0; MsgId beforeMsgId = 0; App::main()->peerBefore(_peer, msgid, before, beforeMsgId); - if (before) App::main()->showPeerHistory(before->id, beforeMsgId); + if (before) Ui::showPeerHistory(before, beforeMsgId); } else if (!(e->modifiers() & (Qt::ShiftModifier | Qt::MetaModifier | Qt::ControlModifier))) { _scroll.keyPressEvent(e); } @@ -6066,7 +6380,7 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) { } else { App::main()->peerAfter(_peer, msgid, p, m); } - if (p) App::main()->showPeerHistory(p->id, m); + if (p) Ui::showPeerHistory(p, m); } else if (_history && (e->key() == Qt::Key_Search || e == QKeySequence::Find)) { App::main()->searchInPeer(_peer); } else { @@ -6082,7 +6396,15 @@ void HistoryWidget::onFieldTabbed() { } void HistoryWidget::onStickerSend(DocumentData *sticker) { - if (!_history || !sticker || !canSendMessages(_peer)) return; + sendExistingDocument(sticker, QString()); +} + +void HistoryWidget::onPhotoSend(PhotoData *photo) { + sendExistingPhoto(photo, QString()); +} + +void HistoryWidget::onInlineResultSend(InlineResult *result, UserData *bot) { + if (!_history || !result || !canSendMessages(_peer)) return; App::main()->readServerHistory(_history, false); fastShowAtEnd(_history); @@ -6105,13 +6427,179 @@ void HistoryWidget::onStickerSend(DocumentData *sticker) { } else { flags |= MTPDmessage::flag_from_id; } - _history->addNewDocument(newId.msg, flags, replyToId(), date(MTP_int(unixtime())), fromChannelName ? 0 : MTP::authedId(), sticker); + if (bot) { + flags |= MTPDmessage::flag_via_bot_id; + } - _history->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_int(sendFlags), _peer->input, MTP_int(replyToId()), MTP_inputMediaDocument(MTP_inputDocument(MTP_long(sticker->id), MTP_long(sticker->access))), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, _history->sendRequestId); + if (result->message.isEmpty()) { + if (result->doc) { + _history->addNewDocument(newId.msg, flags, bot ? peerToUser(bot->id) : 0, replyToId(), date(MTP_int(unixtime())), fromChannelName ? 0 : MTP::authedId(), result->doc, result->caption); + } else if (result->photo) { + _history->addNewPhoto(newId.msg, flags, bot ? peerToUser(bot->id) : 0, replyToId(), date(MTP_int(unixtime())), fromChannelName ? 0 : MTP::authedId(), result->photo, result->caption); + } else if (result->type == qstr("gif")) { + MTPPhotoSize thumbSize; + QPixmap thumb; + int32 tw = result->thumb->width(), th = result->thumb->height(); + if (tw > 0 && th > 0 && tw < 20 * th && th < 20 * tw && result->thumb->loaded()) { + if (tw > th) { + if (tw > 90) { + th = th * 90 / tw; + tw = 90; + } + } else if (th > 90) { + tw = tw * 90 / th; + th = 90; + } + thumbSize = MTP_photoSize(MTP_string(""), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(tw), MTP_int(th), MTP_int(0)); + thumb = result->thumb->pixNoCache(tw, th, true, false, false); + } else { + tw = th = 0; + thumbSize = MTP_photoSizeEmpty(MTP_string("")); + } + uint64 docId = MTP::nonce(); + QVector attributes(1, MTP_documentAttributeFilename(MTP_string((result->content_type == qstr("video/mp4") ? "animation.gif.mp4" : "animation.gif")))); + attributes.push_back(MTP_documentAttributeAnimated()); + attributes.push_back(MTP_documentAttributeVideo(MTP_int(result->duration), MTP_int(result->width), MTP_int(result->height))); + MTPDocument document = MTP_document(MTP_long(docId), MTP_long(0), MTP_int(unixtime()), MTP_string(result->content_type), MTP_int(result->data().size()), thumbSize, MTP_int(MTP::maindc()), MTP_vector(attributes)); + if (tw > 0 && th > 0) { + App::feedDocument(document, thumb); + } + Local::writeStickerImage(mediaKey(DocumentFileLocation, MTP::maindc(), docId), result->data()); + _history->addNewMessage(MTP_message(MTP_int(flags), MTP_int(newId.msg), MTP_int(fromChannelName ? 0 : MTP::authedId()), peerToMTP(_history->peer->id), MTPPeer(), MTPint(), MTP_int(bot ? peerToUser(bot->id) : 0), MTP_int(replyToId()), MTP_int(unixtime()), MTP_string(""), MTP_messageMediaDocument(document, MTP_string(result->caption)), MTPnullMarkup, MTPnullEntities, MTP_int(1)), NewMessageUnread); + } else if (result->type == qstr("photo")) { + QImage fileThumb(result->thumb->pix().toImage()); + + QVector photoSizes; + + QPixmap thumb = (fileThumb.width() > 100 || fileThumb.height() > 100) ? QPixmap::fromImage(fileThumb.scaled(100, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly) : QPixmap::fromImage(fileThumb); + ImagePtr thumbPtr = ImagePtr(thumb, "JPG"); + photoSizes.push_back(MTP_photoSize(MTP_string("s"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(thumb.width()), MTP_int(thumb.height()), MTP_int(0))); + + QSize medium = resizeKeepAspect(result->width, result->height, 320, 320); + photoSizes.push_back(MTP_photoSize(MTP_string("m"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(medium.width()), MTP_int(medium.height()), MTP_int(0))); + + photoSizes.push_back(MTP_photoSize(MTP_string("x"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(result->width), MTP_int(result->height), MTP_int(0))); + + uint64 photoId = MTP::nonce(); + PhotoData *ph = App::photoSet(photoId, 0, 0, unixtime(), thumbPtr, ImagePtr(medium.width(), medium.height()), ImagePtr(result->width, result->height)); + MTPPhoto photo = MTP_photo(MTP_long(photoId), MTP_long(0), MTP_int(ph->date), MTP_vector(photoSizes)); + + _history->addNewMessage(MTP_message(MTP_int(flags), MTP_int(newId.msg), MTP_int(fromChannelName ? 0 : MTP::authedId()), peerToMTP(_history->peer->id), MTPPeer(), MTPint(), MTP_int(bot ? peerToUser(bot->id) : 0), MTP_int(replyToId()), MTP_int(unixtime()), MTP_string(""), MTP_messageMediaPhoto(photo, MTP_string(result->caption)), MTPnullMarkup, MTPnullEntities, MTP_int(1)), NewMessageUnread); + } + } else { + flags |= MTPDmessage::flag_entities; + if (result->noWebPage) { + sendFlags |= MTPmessages_SendMessage::flag_no_webpage; + } + _history->addNewMessage(MTP_message(MTP_int(flags), MTP_int(newId.msg), MTP_int(fromChannelName ? 0 : MTP::authedId()), peerToMTP(_history->peer->id), MTPPeer(), MTPint(), MTP_int(bot ? peerToUser(bot->id) : 0), MTP_int(replyToId()), MTP_int(unixtime()), MTP_string(result->message), MTP_messageMediaEmpty(), MTPnullMarkup, linksToMTP(result->entities), MTP_int(1)), NewMessageUnread); + } + _history->sendRequestId = MTP::send(MTPmessages_SendInlineBotResult(MTP_int(sendFlags), _peer->input, MTP_int(replyToId()), MTP_long(randomId), MTP_long(result->queryId), MTP_string(result->id)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, _history->sendRequestId); App::main()->finishForwarding(_history, _broadcast.checked()); cancelReply(lastKeyboardUsed); - if (sticker->sticker()) App::main()->incrementSticker(sticker); + App::historyRegRandom(randomId, newId); + + setFieldText(QString()); + _saveDraftText = true; + _saveDraftStart = getms(); + onDraftSave(); + + RecentInlineBots &bots(cRefRecentInlineBots()); + int32 index = bots.indexOf(bot); + if (index) { + if (index > 0) { + bots.removeAt(index); + } else if (bots.size() >= RecentInlineBotsLimit) { + bots.resize(RecentInlineBotsLimit - 1); + } + bots.push_front(bot); + Local::writeRecentHashtagsAndBots(); + } + + if (!_attachMention.isHidden()) _attachMention.hideStart(); + if (!_attachType.isHidden()) _attachType.hideStart(); + if (!_emojiPan.isHidden()) _emojiPan.hideStart(); + + _field.setFocus(); +} + +void HistoryWidget::sendExistingDocument(DocumentData *doc, const QString &caption) { + if (!_history || !doc || !canSendMessages(_peer)) return; + + App::main()->readServerHistory(_history, false); + fastShowAtEnd(_history); + + uint64 randomId = MTP::nonce(); + FullMsgId newId(_channel, clientMsgId()); + + bool lastKeyboardUsed = lastForceReplyReplied(); + + bool out = !_peer->isSelf(), unread = !_peer->isSelf(); + int32 flags = newMessageFlags(_peer) | MTPDmessage::flag_media; // unread, out + int32 sendFlags = 0; + if (replyToId()) { + flags |= MTPDmessage::flag_reply_to_msg_id; + sendFlags |= MTPmessages_SendMedia::flag_reply_to_msg_id; + } + bool fromChannelName = _peer->isChannel() && !_peer->isMegagroup() && _peer->asChannel()->canPublish() && (_peer->asChannel()->isBroadcast() || _broadcast.checked()); + if (fromChannelName) { + sendFlags |= MTPmessages_SendMedia::flag_broadcast; + } else { + flags |= MTPDmessage::flag_from_id; + } + _history->addNewDocument(newId.msg, flags, 0, replyToId(), date(MTP_int(unixtime())), fromChannelName ? 0 : MTP::authedId(), doc, caption); + + _history->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_int(sendFlags), _peer->input, MTP_int(replyToId()), MTP_inputMediaDocument(MTP_inputDocument(MTP_long(doc->id), MTP_long(doc->access)), MTP_string(caption)), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, _history->sendRequestId); + App::main()->finishForwarding(_history, _broadcast.checked()); + cancelReply(lastKeyboardUsed); + + if (doc->sticker()) App::main()->incrementSticker(doc); + + App::historyRegRandom(randomId, newId); + + if (_attachMention.stickersShown()) { + setFieldText(QString()); + _saveDraftText = true; + _saveDraftStart = getms(); + onDraftSave(); + } + + if (!_attachMention.isHidden()) _attachMention.hideStart(); + if (!_attachType.isHidden()) _attachType.hideStart(); + if (!_emojiPan.isHidden()) _emojiPan.hideStart(); + + _field.setFocus(); +} + +void HistoryWidget::sendExistingPhoto(PhotoData *photo, const QString &caption) { + if (!_history || !photo || !canSendMessages(_peer)) return; + + App::main()->readServerHistory(_history, false); + fastShowAtEnd(_history); + + uint64 randomId = MTP::nonce(); + FullMsgId newId(_channel, clientMsgId()); + + bool lastKeyboardUsed = lastForceReplyReplied(); + + bool out = !_peer->isSelf(), unread = !_peer->isSelf(); + int32 flags = newMessageFlags(_peer) | MTPDmessage::flag_media; // unread, out + int32 sendFlags = 0; + if (replyToId()) { + flags |= MTPDmessage::flag_reply_to_msg_id; + sendFlags |= MTPmessages_SendMedia::flag_reply_to_msg_id; + } + bool fromChannelName = _peer->isChannel() && !_peer->isMegagroup() && _peer->asChannel()->canPublish() && (_peer->asChannel()->isBroadcast() || _broadcast.checked()); + if (fromChannelName) { + sendFlags |= MTPmessages_SendMedia::flag_broadcast; + } else { + flags |= MTPDmessage::flag_from_id; + } + _history->addNewPhoto(newId.msg, flags, 0, replyToId(), date(MTP_int(unixtime())), fromChannelName ? 0 : MTP::authedId(), photo, caption); + + _history->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_int(sendFlags), _peer->input, MTP_int(replyToId()), MTP_inputMediaPhoto(MTP_inputPhoto(MTP_long(photo->id), MTP_long(photo->access)), MTP_string(caption)), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, _history->sendRequestId); + App::main()->finishForwarding(_history, _broadcast.checked()); + cancelReply(lastKeyboardUsed); App::historyRegRandom(randomId, newId); @@ -6122,10 +6610,10 @@ void HistoryWidget::onStickerSend(DocumentData *sticker) { _field.setFocus(); } -void HistoryWidget::setFieldText(const QString &text) { - _synthedTextUpdate = true; - _field.setTextFast(text); - _synthedTextUpdate = false; +void HistoryWidget::setFieldText(const QString &text, int32 textUpdateEventsFlags, bool clearUndoHistory) { + _textUpdateEventsFlags = textUpdateEventsFlags; + _field.setTextFast(text, clearUndoHistory); + _textUpdateEventsFlags = TextUpdateEventsSaveDraft | TextUpdateEventsSendTyping; _previewCancelled = false; _previewData = 0; @@ -6153,7 +6641,7 @@ void HistoryWidget::onReplyToMessage() { box = new ConfirmBox(lang(lng_reply_cant_forward), lang(lng_selected_forward)); connect(box, SIGNAL(confirmed()), this, SLOT(onForwardHere())); } - App::showLayer(box); + Ui::showLayer(box); } return; } @@ -6233,10 +6721,11 @@ void HistoryWidget::onReplyForwardPreviewCancel() { } void HistoryWidget::onStickerPackInfo() { - if (HistoryMessage *item = dynamic_cast(App::contextItem())) { - if (HistorySticker *sticker = dynamic_cast(item->getMedia())) { - if (sticker->document() && sticker->document()->sticker() && sticker->document()->sticker()->set.type() != mtpc_inputStickerSetEmpty) { - App::main()->stickersBox(sticker->document()->sticker()->set); + if (HistoryMedia *media = (App::contextItem() ? App::contextItem()->getMedia() : 0)) { + if (media->type() == MediaTypeSticker) { + DocumentData *doc = media->getDocument(); + if (doc && doc->sticker() && doc->sticker()->set.type() != mtpc_inputStickerSetEmpty) { + App::main()->stickersBox(doc->sticker()->set); } } } @@ -6357,8 +6846,14 @@ void HistoryWidget::updatePreview() { } void HistoryWidget::onCancel() { - if (App::main()) App::main()->showDialogs(); - emit cancelled(); + if (_inlineBot && _field.getLastText().startsWith('@' + _inlineBot->username + ' ')) { + setFieldText(QString(), TextUpdateEventsSaveDraft, false); + } else if (!_attachMention.isHidden()) { + _attachMention.hideStart(); + } else { + Ui::showChatsList(); + emit cancelled(); + } } void HistoryWidget::onFullPeerUpdated(PeerData *data) { @@ -6372,7 +6867,7 @@ void HistoryWidget::onFullPeerUpdated(PeerData *data) { } updateControlsVisibility(); } - checkMentionDropdown(); + onCheckMentionDropdown(); updateReportSpamStatus(); int32 lh = _list->height(), st = _scroll.scrollTop(); _list->updateBotInfo(); @@ -6398,7 +6893,7 @@ void HistoryWidget::onFullPeerUpdated(PeerData *data) { void HistoryWidget::peerUpdated(PeerData *data) { if (data && data == _peer) { if (data->migrateTo()) { - App::main()->showPeerHistory(data->migrateTo()->id, ShowAtUnreadMsgId); + Ui::showPeerHistory(data->migrateTo(), ShowAtUnreadMsgId); QTimer::singleShot(ReloadChannelMembersTimeout, App::api(), SLOT(delayedRequestParticipantsCount())); return; } @@ -6463,10 +6958,8 @@ void HistoryWidget::onDeleteSelectedSure() { for (SelectedItemSet::const_iterator i = sel.cbegin(), e = sel.cend(); i != e; ++i) { i.value()->destroy(); } - if (App::main() && App::main()->peer() == peer()) { - App::main()->itemResized(0); - } - App::wnd()->hideLayer(); + Notify::historyItemsResized(); + Ui::hideLayer(); for (QMap >::const_iterator i = ids.cbegin(), e = ids.cend(); i != e; ++i) { App::main()->deleteMessages(i.key(), i.value()); @@ -6487,10 +6980,8 @@ void HistoryWidget::onDeleteContextSure() { App::main()->checkPeerHistory(h->peer); } - if (App::main() && (App::main()->peer() == h->peer || (App::main()->peer() && h->peer->migrateTo() == App::main()->peer()))) { - App::main()->itemResized(0); - } - App::wnd()->hideLayer(); + Notify::historyItemsResized(); + Ui::hideLayer(); if (wasOnServer) { App::main()->deleteMessages(h->peer, toDelete); @@ -6520,14 +7011,14 @@ void HistoryWidget::onAnimActiveStep() { if (getms() - _animActiveStart > st::activeFadeInDuration + st::activeFadeOutDuration) { stopAnimActive(); } else { - App::main()->msgUpdated(item); + Ui::repaintHistoryItem(item); } } -uint64 HistoryWidget::animActiveTime(const HistoryItem *msg) const { +uint64 HistoryWidget::animActiveTimeStart(const HistoryItem *msg) const { if (!msg) return 0; if ((msg->history() == _history && msg->id == _activeAnimMsgId) || (_migrated && msg->history() == _migrated && msg->id == -_activeAnimMsgId)) { - return _animActiveTimer.isActive() ? (getms() - _animActiveStart) : 0; + return _animActiveTimer.isActive() ? _animActiveStart : 0; } return 0; } @@ -6553,7 +7044,7 @@ void HistoryWidget::updateTopBarSelection() { App::main()->topBar()->showSelected(_selCount > 0 ? _selCount : 0, (selectedForDelete == selectedForForward)); updateControlsVisibility(); updateListSize(); - if (!App::wnd()->layerShown() && !App::passcoded()) { + if (!Ui::isLayerShown() && !App::passcoded()) { if (_selCount || (_list && _list->wasSelectedText()) || _recording || isBotStart() || isBlocked() || !_canSendMessages) { _list->setFocus(); } else { @@ -6637,11 +7128,11 @@ void HistoryWidget::drawField(Painter &p) { } p.setPen(st::replyColor->p); _replyToName.drawElided(p, replyLeft, backy + st::msgReplyPadding.top(), width() - replyLeft - _replyForwardPreviewCancel.width() - st::msgReplyPadding.right()); - p.setPen((((drawReplyTo->toHistoryMessage() && drawReplyTo->toHistoryMessage()->justMedia()) || drawReplyTo->serviceMsg()) ? st::msgInDateColor : st::msgColor)->p); + p.setPen((((drawReplyTo->toHistoryMessage() && drawReplyTo->toHistoryMessage()->emptyText()) || drawReplyTo->serviceMsg()) ? st::msgInDateFg : st::msgColor)->p); _replyToText.drawElided(p, replyLeft, backy + st::msgReplyPadding.top() + st::msgServiceNameFont->height, width() - replyLeft - _replyForwardPreviewCancel.width() - st::msgReplyPadding.right()); } else { p.setFont(st::msgDateFont->f); - p.setPen(st::msgInDateColor->p); + p.setPen(st::msgInDateFg->p); p.drawText(replyLeft, backy + st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2 + st::msgDateFont->ascent, st::msgDateFont->elided(lang(lng_profile_loading), width() - replyLeft - _replyForwardPreviewCancel.width() - st::msgReplyPadding.right())); } } @@ -6661,7 +7152,7 @@ void HistoryWidget::drawField(Painter &p) { } p.setPen(st::replyColor->p); from->drawElided(p, forwardLeft, backy + st::msgReplyPadding.top(), width() - forwardLeft - _replyForwardPreviewCancel.width() - st::msgReplyPadding.right()); - p.setPen((serviceColor ? st::msgInDateColor : st::msgColor)->p); + p.setPen((serviceColor ? st::msgInDateFg : st::msgColor)->p); text->drawElided(p, forwardLeft, backy + st::msgReplyPadding.top() + st::msgServiceNameFont->height, width() - forwardLeft - _replyForwardPreviewCancel.width() - st::msgReplyPadding.right()); } } diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index 78212d1eca..6aae8e548e 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -33,7 +33,7 @@ enum DragState { }; class HistoryWidget; -class HistoryInner : public QWidget { +class HistoryInner : public TWidget { Q_OBJECT public: @@ -66,10 +66,10 @@ public: void touchScrollUpdated(const QPoint &screenPos); QPoint mapMouseToItem(QPoint p, HistoryItem *item); - int32 recountHeight(HistoryItem *resizedItem); + int32 recountHeight(const HistoryItem *resizedItem); void updateSize(); - void updateMsg(const HistoryItem *msg); + void repaintItem(const HistoryItem *item); bool canCopySelected() const; bool canDeleteSelected() const; @@ -80,7 +80,6 @@ public: void selectItem(HistoryItem *item); void itemRemoved(HistoryItem *item); - void itemReplaced(HistoryItem *oldItem, HistoryItem *newItem); void updateBotInfo(bool recount = true); @@ -99,7 +98,7 @@ public: void notifyMigrateUpdated(); ~HistoryInner(); - + public slots: void onUpdateSelected(); @@ -115,6 +114,7 @@ public slots: void showContextInFolder(); void openContextFile(); void saveContextFile(); + void saveContextGif(); void copyContextText(); void copySelectedText(); @@ -186,7 +186,7 @@ private: bool _touchScroll, _touchSelect, _touchInProgress; QPoint _touchStart, _touchPrevPos, _touchPos; QTimer _touchSelectTimer; - + TouchScrollState _touchScrollState; bool _touchPrevPosValid, _touchWaitingAcceleration; QPoint _touchSpeed; @@ -249,7 +249,7 @@ private: }; -class BotKeyboard : public QWidget { +class BotKeyboard : public TWidget { Q_OBJECT public: @@ -267,7 +267,7 @@ public: bool hasMarkup() const; bool forceReply() const; - bool hoverStep(float64 ms); + void step_selected(uint64 ms, bool timer); void resizeToWidth(int32 width, int32 maxOuterHeight); bool maximizeSize() const; @@ -308,13 +308,13 @@ private: typedef QMap Animations; Animations _animations; - Animation _hoverAnim; + Animation _a_selected; const style::botKeyboardButton *_st; }; -class HistoryHider : public TWidget, public Animated { +class HistoryHider : public TWidget { Q_OBJECT public: @@ -324,7 +324,7 @@ public: HistoryHider(MainWidget *parent); // send path from command line argument HistoryHider(MainWidget *parent, const QString &url, const QString &text); // share url - bool animStep(float64 ms); + void step_appearance(float64 ms, bool timer); bool withConfirm() const; void paintEvent(QPaintEvent *e); @@ -362,7 +362,10 @@ private: BoxButton _send, _cancel; PeerData *offered; + anim::fvalue a_opacity; + Animation _a_appearance; + QRect box; bool hiding; @@ -386,6 +389,11 @@ public: }; +enum TextUpdateEventsFlags { + TextUpdateEventsSaveDraft = 0x01, + TextUpdateEventsSendTyping = 0x02, +}; + class HistoryWidget : public TWidget, public RPCSender { Q_OBJECT @@ -415,7 +423,6 @@ public: void contextMenuEvent(QContextMenuEvent *e); void updateTopBarSelection(); - void checkMentionDropdown(); void paintTopBar(QPainter &p, float64 over, int32 decreaseWidth); void topBarClick(); @@ -427,7 +434,6 @@ public: void peerMessagesUpdated(PeerId peer); void peerMessagesUpdated(); - void msgUpdated(const HistoryItem *msg); void newUnreadMsg(History *history, HistoryItem *item); void historyToDown(History *history); void historyWasRead(bool force = true); @@ -444,6 +450,10 @@ public: void destroyData(); + void updateFieldPlaceholder(); + void updateInlineBotQuery(); + void updateStickersByEmoji(); + void uploadImage(const QImage &img, PrepareMediaType type, FileLoadForceConfirmType confirm = FileLoadNoForceConfirm, const QString &source = QString(), bool withText = false); void uploadFile(const QString &file, PrepareMediaType type, FileLoadForceConfirmType confirm = FileLoadNoForceConfirm, bool withText = false); // with confirmation void uploadFiles(const QStringList &files, PrepareMediaType type); @@ -471,7 +481,7 @@ public: HistoryItem *atTopImportantMsg(int32 &bottomUnderScrollTop) const; void animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTopBarCache, bool back = false); - bool animStep_show(float64 ms); + void step_show(float64 ms, bool timer); void animStop(); void updateWideMode(); @@ -483,14 +493,12 @@ public: void noSelectingScroll(); bool touchScroll(const QPoint &delta); - - uint64 animActiveTime(const HistoryItem *msg) const; + + uint64 animActiveTimeStart(const HistoryItem *msg) const; void stopAnimActive(); void fillSelectedItems(SelectedItemSet &sel, bool forDelete = true); void itemRemoved(HistoryItem *item); - void itemReplaced(HistoryItem *oldItem, HistoryItem *newItem); - void itemResized(HistoryItem *item, bool scrollToIt); void updateScrollColors(); @@ -510,14 +518,14 @@ public: void updatePreview(); void previewCancel(); - bool recordStep(float64 ms); - bool recordingStep(float64 ms); + void step_record(float64 ms, bool timer); + void step_recording(float64 ms, bool timer); void stopRecording(bool send); void onListEscapePressed(); void sendBotCommand(const QString &cmd, MsgId replyTo); - void insertBotCommand(const QString &cmd); + bool insertBotCommand(const QString &cmd, bool specialGif); bool eventFilter(QObject *obj, QEvent *e); void updateBotKeyboard(History *h = 0); @@ -542,6 +550,8 @@ public: void updateNotifySettings(); + void saveGif(DocumentData *doc); + bool contentOverlapped(const QRect &globalRect); void grabStart() { @@ -555,9 +565,21 @@ public: resizeEvent(0); } - void notifyBotCommandsChanged(UserData *user); - void notifyUserIsBotChanged(UserData *user); - void notifyMigrateUpdated(PeerData *peer); + bool isItemVisible(HistoryItem *item); + + void ui_repaintHistoryItem(const HistoryItem *item); + void ui_repaintInlineItem(const LayoutInlineItem *gif); + bool ui_isInlineItemVisible(const LayoutInlineItem *layout); + bool ui_isInlineItemBeingChosen(); + + void notify_historyItemLayoutChanged(const HistoryItem *item); + void notify_automaticLoadSettingsChangedGif(); + void notify_botCommandsChanged(UserData *user); + void notify_inlineBotRequesting(bool requesting); + void notify_userIsBotChanged(UserData *user); + void notify_migrateUpdated(PeerData *peer); + void notify_clipStopperHidden(ClipStopperType type); + void notify_historyItemResized(const HistoryItem *item, bool scrollToIt); ~HistoryWidget(); @@ -622,11 +644,14 @@ public slots: void onCmdStart(); void activate(); + void onStickersUpdated(); void onMentionHashtagOrBotCommandInsert(QString str); void onTextChange(); void onFieldTabbed(); void onStickerSend(DocumentData *sticker); + void onPhotoSend(PhotoData *photo); + void onInlineResultSend(InlineResult *result, UserData *bot); void onVisibleChanged(); @@ -638,7 +663,7 @@ public slots: void onFieldFocused(); void onFieldResize(); - void onFieldCursorChanged(); + void onCheckMentionDropdown(); void onScrollTimer(); void onForwardSelected(); @@ -659,6 +684,8 @@ public slots: void onRecordDone(QByteArray result, qint32 samples); void onRecordUpdate(qint16 level, qint32 samples); + void onUpdateHistoryItems(); + private: MsgId _replyToId; @@ -668,6 +695,9 @@ private: IconedButton _replyForwardPreviewCancel; void updateReplyToName(); + void sendExistingDocument(DocumentData *doc, const QString &caption); + void sendExistingPhoto(PhotoData *photo, const QString &caption); + void drawField(Painter &p); void drawRecordButton(Painter &p); void drawRecording(Painter &p); @@ -691,10 +721,12 @@ private: QList _replyReturns; bool messagesFailed(const RPCError &error, mtpRequestId requestId); - void updateListSize(int32 addToY = 0, bool initial = false, bool loadedDown = false, HistoryItem *resizedItem = 0, bool scrollToIt = false); + void updateListSize(int32 addToY = 0, bool initial = false, bool loadedDown = false, const HistoryItem *resizedItem = 0, bool scrollToIt = false); void addMessagesToFront(PeerData *peer, const QVector &messages, const QVector *collapsed); void addMessagesToBack(PeerData *peer, const QVector &messages, const QVector *collapsed); + void saveGifDone(DocumentData *doc, const MTPBool &result); + void reportSpamDone(PeerData *peer, const MTPBool &result, mtpRequestId request); bool reportSpamFail(const RPCError &error, mtpRequestId request); @@ -707,13 +739,16 @@ private: void countHistoryShowFrom(); + mtpRequestId _stickersUpdateRequest; void stickersGot(const MTPmessages_AllStickers &stickers); bool stickersFailed(const RPCError &error); - mtpRequestId _stickersUpdateRequest; + mtpRequestId _savedGifsUpdateRequest; + void savedGifsGot(const MTPmessages_SavedGifs &gifs); + bool savedGifsFailed(const RPCError &error); void writeDraft(MsgId *replyTo = 0, const QString *text = 0, const MessageCursor *cursor = 0, bool *previewCancelled = 0); - void setFieldText(const QString &text); + void setFieldText(const QString &text, int32 textUpdateEventsFlags = 0, bool clearUndoHistory = true); QStringList getMediasFromMime(const QMimeData *d); @@ -740,10 +775,19 @@ private: History *_migrated, *_history; bool _histInited; // initial updateListSize() called + int32 _lastScroll; + uint64 _lastScrolled; + QTimer _updateHistoryItems; // gifs optimization + IconedButton _toHistoryEnd; CollapseButton _collapseComments; MentionsDropdown _attachMention; + UserData *_inlineBot; + QString _inlineBotUsername; + mtpRequestId _inlineBotResolveRequestId; + void inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result); + bool inlineBotResolveFail(QString name, const RPCError &error); bool isBotStart() const; bool isBlocked() const; @@ -755,11 +799,13 @@ private: FlatButton _send, _unblock, _botStart, _joinChannel, _muteUnmute; mtpRequestId _unblockRequest, _reportSpamRequest; - IconedButton _attachDocument, _attachPhoto, _attachEmoji, _kbShow, _kbHide, _cmdStart; + IconedButton _attachDocument, _attachPhoto; + EmojiButton _attachEmoji; + IconedButton _kbShow, _kbHide, _cmdStart; FlatCheckbox _broadcast; bool _cmdStartShown; MessageField _field; - Animation _recordAnim, _recordingAnim; + Animation _a_record, _a_recording; bool _recording, _inRecord, _inField, _inReply; anim::ivalue a_recordingLevel; int32 _recordingSamples; @@ -782,7 +828,7 @@ private: int32 _selCount; // < 0 - text selected, focus list, not _field TaskQueue _fileLoader; - bool _synthedTextUpdate; + int32 _textUpdateEventsFlags; int64 _serviceImageCacheSize; QString _confirmSource; diff --git a/Telegram/SourceFiles/intro/intro.cpp b/Telegram/SourceFiles/intro/intro.cpp index 82d54b98ff..e1fff29629 100644 --- a/Telegram/SourceFiles/intro/intro.cpp +++ b/Telegram/SourceFiles/intro/intro.cpp @@ -52,27 +52,27 @@ namespace { } } -IntroWidget::IntroWidget(Window *window) : TWidget(window), -_langChangeTo(0), -_a_stage(animFunc(this, &IntroWidget::animStep_stage)), -_cacheHideIndex(0), -_cacheShowIndex(0), -_a_show(animFunc(this, &IntroWidget::animStep_show)), -wnd(window), -steps(new IntroSteps(this)), -phone(0), -code(0), -signup(0), -pwdcheck(0), -current(0), -moving(0), -_callTimeout(60), -_registered(false), -_hasRecovery(false), -_codeByTelegram(false), -_back(this, st::setClose), -_backFrom(0), _backTo(0) { - setGeometry(QRect(0, st::titleHeight, wnd->width(), wnd->height() - st::titleHeight)); +IntroWidget::IntroWidget(Window *window) : TWidget(window) +, _langChangeTo(0) +, _a_stage(animation(this, &IntroWidget::step_stage)) +, _cacheHideIndex(0) +, _cacheShowIndex(0) +, _a_show(animation(this, &IntroWidget::step_show)) +, steps(new IntroSteps(this)) +, phone(0) +, code(0) +, signup(0) +, pwdcheck(0) +, current(0) +, moving(0) +, _callTimeout(60) +, _registered(false) +, _hasRecovery(false) +, _codeByTelegram(false) +, _back(this, st::setClose) +, _backFrom(0) +, _backTo(0) { + setGeometry(QRect(0, st::titleHeight, App::wnd()->width(), App::wnd()->height() - st::titleHeight)); connect(&_back, SIGNAL(clicked()), this, SLOT(onIntroBack())); _back.hide(); @@ -162,7 +162,7 @@ void IntroWidget::prepareMove() { _backTo = stages[current + moving]->hasBack() ? 1 : 0; _backFrom = stages[current]->hasBack() ? 1 : 0; - animStep_stage(0); + _a_stage.step(); if (_backFrom > 0 || _backTo > 0) { _back.show(); } else { @@ -227,13 +227,11 @@ void IntroWidget::animShow(const QPixmap &bgAnimCache, bool back) { show(); } -bool IntroWidget::animStep_show(float64 ms) { +void IntroWidget::step_show(float64 ms, bool timer) { float64 dt = ms / st::slideDuration; - bool res = true; if (dt >= 1) { _a_show.stop(); - res = false; a_coordUnder.finish(); a_coordOver.finish(); a_shadow.finish(); @@ -253,21 +251,19 @@ bool IntroWidget::animStep_show(float64 ms) { a_coordOver.update(dt, st::slideFunction); a_shadow.update(dt, st::slideFunction); } - update(); - return res; + if (timer) update(); } -void IntroWidget::animStop_show() { +void IntroWidget::stop_show() { _a_show.stop(); } -bool IntroWidget::animStep_stage(float64 ms) { - bool res = true; - +void IntroWidget::step_stage(float64 ms, bool timer) { float64 fullDuration = st::introSlideDelta + st::introSlideDuration, dt = ms / fullDuration; float64 dt1 = (ms > st::introSlideDuration) ? 1 : (ms / st::introSlideDuration), dt2 = (ms > st::introSlideDelta) ? (ms - st::introSlideDelta) / (st::introSlideDuration) : 0; if (dt >= 1) { - res = false; + _a_stage.stop(); + a_coordShow.finish(); a_opacityShow.finish(); @@ -292,8 +288,7 @@ bool IntroWidget::animStep_stage(float64 ms) { _back.setOpacity(1); } } - update(); - return res; + if (timer) update(); } void IntroWidget::paintEvent(QPaintEvent *e) { @@ -414,7 +409,7 @@ void IntroWidget::mousePressEvent(QMouseEvent *e) { } void IntroWidget::finish(const MTPUser &user, const QImage &photo) { - wnd->setupMain(true, &user); + App::wnd()->setupMain(true, &user); if (!photo.isNull()) { App::app()->uploadProfilePhoto(photo, MTP::authedId()); } diff --git a/Telegram/SourceFiles/intro/intro.h b/Telegram/SourceFiles/intro/intro.h index bc2b634f77..ba21c7dab0 100644 --- a/Telegram/SourceFiles/intro/intro.h +++ b/Telegram/SourceFiles/intro/intro.h @@ -44,10 +44,10 @@ public: void updateWideMode(); void animShow(const QPixmap &bgAnimCache, bool back = false); - bool animStep_show(float64 ms); - void animStop_show(); + void step_show(float64 ms, bool timer); + void stop_show(); - bool animStep_stage(float64 ms); + void step_stage(float64 ms, bool timer); QRect innerRect() const; QString currentCountry() const; @@ -108,7 +108,6 @@ private: anim::ivalue a_coordUnder, a_coordOver; anim::fvalue a_shadow; - Window *wnd; IntroSteps *steps; IntroPhone *phone; IntroCode *code; diff --git a/Telegram/SourceFiles/intro/introcode.cpp b/Telegram/SourceFiles/intro/introcode.cpp index bf23e62354..71cd2bd949 100644 --- a/Telegram/SourceFiles/intro/introcode.cpp +++ b/Telegram/SourceFiles/intro/introcode.cpp @@ -72,12 +72,15 @@ void CodeInput::correctValue(const QString &was, QString &now) { if (strict) emit codeEntered(); } -IntroCode::IntroCode(IntroWidget *parent) : IntroStage(parent), errorAlpha(0), - next(this, lang(lng_intro_next), st::btnIntroNext), - _desc(st::introTextSize.width()), - _noTelegramCode(this, lang(lng_code_no_telegram), st::introLink), - _noTelegramCodeRequestId(0), - code(this, st::inpIntroCode, lang(lng_code_ph)), waitTillCall(intro()->getCallTimeout()) { +IntroCode::IntroCode(IntroWidget *parent) : IntroStage(parent) +, a_errorAlpha(0) +, _a_error(animation(this, &IntroCode::step_error)) +, next(this, lang(lng_intro_next), st::btnIntroNext) +, _desc(st::introTextSize.width()) +, _noTelegramCode(this, lang(lng_code_no_telegram), st::introLink) +, _noTelegramCodeRequestId(0) +, code(this, st::inpIntroCode, lang(lng_code_ph)) +, waitTillCall(intro()->getCallTimeout()) { setVisible(false); setGeometry(parent->innerRect()); @@ -132,8 +135,8 @@ void IntroCode::paintEvent(QPaintEvent *e) { } p.drawText(QRect(textRect.left(), code.y() + code.height() + st::introCallSkip, st::introTextSize.width(), st::introErrHeight), callText, style::al_center); } - if (animating() || error.length()) { - p.setOpacity(errorAlpha.current()); + if (_a_error.animating() || error.length()) { + p.setOpacity(a_errorAlpha.current()); p.setFont(st::introErrFont->f); p.setPen(st::introErrColor->p); p.drawText(QRect(textRect.left(), next.y() + next.height() + st::introErrTop, st::introTextSize.width(), st::introErrHeight), error, style::al_center); @@ -151,32 +154,30 @@ void IntroCode::resizeEvent(QResizeEvent *e) { void IntroCode::showError(const QString &err) { if (!err.isEmpty()) code.notaBene(); - if (!animating() && err == error) return; + if (!_a_error.animating() && err == error) return; if (err.length()) { error = err; - errorAlpha.start(1); + a_errorAlpha.start(1); } else { - errorAlpha.start(0); + a_errorAlpha.start(0); } - anim::start(this); + _a_error.start(); } -bool IntroCode::animStep(float64 ms) { +void IntroCode::step_error(float64 ms, bool timer) { float64 dt = ms / st::introErrDuration; - bool res = true; if (dt >= 1) { - res = false; - errorAlpha.finish(); - if (!errorAlpha.current()) { + _a_error.stop(); + a_errorAlpha.finish(); + if (!a_errorAlpha.current()) { error = ""; } } else { - errorAlpha.update(dt, st::introErrFunc); + a_errorAlpha.update(dt, st::introErrFunc); } - update(); - return res; + if (timer) update(); } void IntroCode::activate() { @@ -185,7 +186,7 @@ void IntroCode::activate() { callTimer.start(1000); } error = ""; - errorAlpha = anim::fvalue(0); + a_errorAlpha = anim::fvalue(0); sentCode = QString(); show(); code.setDisabled(false); diff --git a/Telegram/SourceFiles/intro/introcode.h b/Telegram/SourceFiles/intro/introcode.h index 68d8e557de..2ba90769d6 100644 --- a/Telegram/SourceFiles/intro/introcode.h +++ b/Telegram/SourceFiles/intro/introcode.h @@ -42,7 +42,7 @@ protected: }; -class IntroCode : public IntroStage, public Animated, public RPCSender { +class IntroCode : public IntroStage, public RPCSender { Q_OBJECT public: @@ -52,7 +52,7 @@ public: void paintEvent(QPaintEvent *e); void resizeEvent(QResizeEvent *e); - bool animStep(float64 ms); + void step_error(float64 ms, bool timer); void activate(); void prepareShow(); @@ -86,7 +86,8 @@ private: void stopCheck(); QString error; - anim::fvalue errorAlpha; + anim::fvalue a_errorAlpha; + Animation _a_error; FlatButton next; diff --git a/Telegram/SourceFiles/intro/introphone.cpp b/Telegram/SourceFiles/intro/introphone.cpp index a7cbbdc54a..21ea020ee1 100644 --- a/Telegram/SourceFiles/intro/introphone.cpp +++ b/Telegram/SourceFiles/intro/introphone.cpp @@ -45,13 +45,16 @@ namespace { }; } -IntroPhone::IntroPhone(IntroWidget *parent) : IntroStage(parent), - errorAlpha(0), changed(false), - next(this, lang(lng_intro_next), st::btnIntroNext), - country(this, st::introCountry), - phone(this, st::inpIntroPhone), code(this, st::inpIntroCountryCode), - _signup(this, lng_phone_notreg(lt_signup_start, textcmdStartLink(1), lt_signup_end, textcmdStopLink()), st::introErrLabel, st::introErrLabelTextStyle), - _showSignup(false) { +IntroPhone::IntroPhone(IntroWidget *parent) : IntroStage(parent) +, a_errorAlpha(0) +, _a_error(animation(this, &IntroPhone::step_error)) +, changed(false) +, next(this, lang(lng_intro_next), st::btnIntroNext) +, country(this, st::introCountry) +, phone(this, st::inpIntroPhone) +, code(this, st::inpIntroCountryCode) +, _signup(this, lng_phone_notreg(lt_signup_start, textcmdStartLink(1), lt_signup_end, textcmdStopLink()), st::introErrLabel, st::introErrLabelTextStyle) +, _showSignup(false) { setVisible(false); setGeometry(parent->innerRect()); @@ -92,9 +95,9 @@ void IntroPhone::paintEvent(QPaintEvent *e) { p.setFont(st::introFont->f); p.drawText(textRect, lang(lng_phone_desc), style::al_bottom); } - if (animating() || error.length()) { + if (_a_error.animating() || error.length()) { int32 errorY = _showSignup ? ((phone.y() + phone.height() + next.y() - st::introErrFont->height) / 2) : (next.y() + next.height() + st::introErrTop); - p.setOpacity(errorAlpha.current()); + p.setOpacity(a_errorAlpha.current()); p.setFont(st::introErrFont->f); p.setPen(st::introErrColor->p); p.drawText(QRect(textRect.x(), errorY, textRect.width(), st::introErrFont->height), error, style::al_top); @@ -123,36 +126,34 @@ void IntroPhone::showError(const QString &err, bool signUp) { _showSignup = signUp; } - if (!animating() && err == error) return; + if (!_a_error.animating() && err == error) return; if (err.length()) { error = err; - errorAlpha.start(1); + a_errorAlpha.start(1); } else { - errorAlpha.start(0); + a_errorAlpha.start(0); } _signup.hide(); - anim::start(this); + _a_error.start(); } -bool IntroPhone::animStep(float64 ms) { +void IntroPhone::step_error(float64 ms, bool timer) { float64 dt = ms / st::introErrDuration; - bool res = true; if (dt >= 1) { - res = false; - errorAlpha.finish(); - if (!errorAlpha.current()) { + _a_error.stop(); + a_errorAlpha.finish(); + if (!a_errorAlpha.current()) { error = ""; _signup.hide(); } else if (!error.isEmpty() && _showSignup) { _signup.show(); } } else { - errorAlpha.update(dt, st::introErrFunc); + a_errorAlpha.update(dt, st::introErrFunc); } - update(); - return res; + if (timer) update(); } void IntroPhone::countryChanged() { @@ -293,7 +294,7 @@ void IntroPhone::selectCountry(const QString &c) { void IntroPhone::activate() { error = ""; - errorAlpha = anim::fvalue(0); + a_errorAlpha = anim::fvalue(0); show(); enableAll(true); } diff --git a/Telegram/SourceFiles/intro/introphone.h b/Telegram/SourceFiles/intro/introphone.h index 22a5b963b5..8baa29d447 100644 --- a/Telegram/SourceFiles/intro/introphone.h +++ b/Telegram/SourceFiles/intro/introphone.h @@ -25,7 +25,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "gui/countryinput.h" #include "intro.h" -class IntroPhone : public IntroStage, public Animated, public RPCSender { +class IntroPhone : public IntroStage, public RPCSender { Q_OBJECT public: @@ -35,7 +35,7 @@ public: void paintEvent(QPaintEvent *e); void resizeEvent(QResizeEvent *e); - bool animStep(float64 ms); + void step_error(float64 ms, bool timer); void selectCountry(const QString &country); @@ -67,7 +67,8 @@ private: void showError(const QString &err, bool signUp = false); QString error; - anim::fvalue errorAlpha; + anim::fvalue a_errorAlpha; + Animation _a_error; bool changed; FlatButton next; diff --git a/Telegram/SourceFiles/intro/intropwdcheck.cpp b/Telegram/SourceFiles/intro/intropwdcheck.cpp index 211beffd46..2aa13f5734 100644 --- a/Telegram/SourceFiles/intro/intropwdcheck.cpp +++ b/Telegram/SourceFiles/intro/intropwdcheck.cpp @@ -30,18 +30,19 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "intro/intropwdcheck.h" #include "intro/intro.h" -IntroPwdCheck::IntroPwdCheck(IntroWidget *parent) : IntroStage(parent), -errorAlpha(0), -_next(this, lang(lng_intro_submit), st::btnIntroNext), -_salt(parent->getPwdSalt()), -_hasRecovery(parent->getHasRecovery()), -_hint(parent->getPwdHint()), -_pwdField(this, st::inpIntroPassword, lang(lng_signin_password)), -_codeField(this, st::inpIntroPassword, lang(lng_signin_code)), -_toRecover(this, lang(lng_signin_recover)), -_toPassword(this, lang(lng_signin_try_password)), -_reset(this, lang(lng_signin_reset_account), st::btnRedLink), -sentRequest(0) { +IntroPwdCheck::IntroPwdCheck(IntroWidget *parent) : IntroStage(parent) +, a_errorAlpha(0) +, _a_error(animation(this, &IntroPwdCheck::step_error)) +, _next(this, lang(lng_intro_submit), st::btnIntroNext) +, _salt(parent->getPwdSalt()) +, _hasRecovery(parent->getHasRecovery()) +, _hint(parent->getPwdHint()) +, _pwdField(this, st::inpIntroPassword, lang(lng_signin_password)) +, _codeField(this, st::inpIntroPassword, lang(lng_signin_code)) +, _toRecover(this, lang(lng_signin_recover)) +, _toPassword(this, lang(lng_signin_try_password)) +, _reset(this, lang(lng_signin_reset_account), st::btnRedLink) +, sentRequest(0) { setVisible(false); setGeometry(parent->innerRect()); @@ -86,8 +87,8 @@ void IntroPwdCheck::paintEvent(QPaintEvent *e) { } else if (!_hint.isEmpty()) { _hintText.drawElided(p, _pwdField.x(), _pwdField.y() + _pwdField.height() + st::introFinishSkip, _pwdField.width(), 1, style::al_top); } - if (animating() || error.length()) { - p.setOpacity(errorAlpha.current()); + if (_a_error.animating() || error.length()) { + p.setOpacity(a_errorAlpha.current()); QRect errRect((width() - st::introErrWidth) / 2, (_pwdField.y() + _pwdField.height() + st::introFinishSkip + st::introFont->height + _next.y() - st::introErrHeight) / 2, st::introErrWidth, st::introErrHeight); p.setFont(st::introErrFont->f); @@ -111,32 +112,30 @@ void IntroPwdCheck::resizeEvent(QResizeEvent *e) { } void IntroPwdCheck::showError(const QString &err) { - if (!animating() && err == error) return; + if (!_a_error.animating() && err == error) return; if (err.length()) { error = err; - errorAlpha.start(1); + a_errorAlpha.start(1); } else { - errorAlpha.start(0); + a_errorAlpha.start(0); } - anim::start(this); + _a_error.start(); } -bool IntroPwdCheck::animStep(float64 ms) { +void IntroPwdCheck::step_error(float64 ms, bool timer) { float64 dt = ms / st::introErrDuration; - bool res = true; if (dt >= 1) { - res = false; - errorAlpha.finish(); - if (!errorAlpha.current()) { + _a_error.stop(); + a_errorAlpha.finish(); + if (!a_errorAlpha.current()) { error = ""; } } else { - errorAlpha.update(dt, st::introErrFunc); + a_errorAlpha.update(dt, st::introErrFunc); } - update(); - return res; + if (timer) update(); } void IntroPwdCheck::activate() { @@ -289,14 +288,14 @@ void IntroPwdCheck::onToRecover() { update(); } else { ConfirmBox *box = new InformBox(lang(lng_signin_no_email_forgot)); - App::wnd()->showLayer(box); + Ui::showLayer(box); connect(box, SIGNAL(destroyed(QObject*)), this, SLOT(onToReset())); } } void IntroPwdCheck::onToPassword() { ConfirmBox *box = new InformBox(lang(lng_signin_cant_email_forgot)); - App::wnd()->showLayer(box); + Ui::showLayer(box); connect(box, SIGNAL(destroyed(QObject*)), this, SLOT(onToReset())); } @@ -319,7 +318,7 @@ void IntroPwdCheck::onReset() { if (sentRequest) return; ConfirmBox *box = new ConfirmBox(lang(lng_signin_sure_reset), lang(lng_signin_reset), st::attentionBoxButton); connect(box, SIGNAL(confirmed()), this, SLOT(onResetSure())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } void IntroPwdCheck::onResetSure() { @@ -335,7 +334,7 @@ bool IntroPwdCheck::deleteFail(const RPCError &error) { } void IntroPwdCheck::deleteDone(const MTPBool &v) { - App::wnd()->hideLayer(); + Ui::hideLayer(); intro()->onIntroNext(); } diff --git a/Telegram/SourceFiles/intro/intropwdcheck.h b/Telegram/SourceFiles/intro/intropwdcheck.h index 7925c31482..71081358a3 100644 --- a/Telegram/SourceFiles/intro/intropwdcheck.h +++ b/Telegram/SourceFiles/intro/intropwdcheck.h @@ -25,7 +25,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "gui/flatinput.h" #include "intro.h" -class IntroPwdCheck : public IntroStage, public Animated, public RPCSender { +class IntroPwdCheck : public IntroStage, public RPCSender { Q_OBJECT public: @@ -35,7 +35,7 @@ public: void paintEvent(QPaintEvent *e); void resizeEvent(QResizeEvent *e); - bool animStep(float64 ms); + void step_error(float64 ms, bool timer); void activate(); void deactivate(); @@ -69,7 +69,8 @@ private: bool deleteFail(const RPCError &error); QString error; - anim::fvalue errorAlpha; + anim::fvalue a_errorAlpha; + Animation _a_error; FlatButton _next; diff --git a/Telegram/SourceFiles/intro/introsignup.cpp b/Telegram/SourceFiles/intro/introsignup.cpp index fbb19cd452..42fbde07b6 100644 --- a/Telegram/SourceFiles/intro/introsignup.cpp +++ b/Telegram/SourceFiles/intro/introsignup.cpp @@ -30,12 +30,15 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "intro/introsignup.h" #include "intro/intro.h" -IntroSignup::IntroSignup(IntroWidget *parent) : IntroStage(parent), - errorAlpha(0), a_photo(0), - next(this, lang(lng_intro_finish), st::btnIntroNext), - first(this, st::inpIntroName, lang(lng_signup_firstname)), - last(this, st::inpIntroName, lang(lng_signup_lastname)), - _invertOrder(langFirstNameGoesSecond()) { +IntroSignup::IntroSignup(IntroWidget *parent) : IntroStage(parent) +, a_errorAlpha(0) +, a_photoOver(0) +, _a_error(animation(this, &IntroSignup::step_error)) +, _a_photo(animation(this, &IntroSignup::step_photo)) +, next(this, lang(lng_intro_finish), st::btnIntroNext) +, first(this, st::inpIntroName, lang(lng_signup_firstname)) +, last(this, st::inpIntroName, lang(lng_signup_lastname)) +, _invertOrder(langFirstNameGoesSecond()) { setVisible(false); setGeometry(parent->innerRect()); @@ -54,9 +57,8 @@ void IntroSignup::mouseMoveEvent(QMouseEvent *e) { if (photoOver != _photoOver) { _photoOver = photoOver; if (_photoSmall.isNull()) { - a_photo.start(_photoOver ? 1 : 0); - errorAlpha.restart(); - anim::start(this); + a_photoOver.start(_photoOver ? 1 : 0); + _a_photo.start(); } } @@ -90,7 +92,7 @@ void IntroSignup::mousePressEvent(QMouseEvent *e) { } PhotoCropBox *box = new PhotoCropBox(img, PeerId(0)); connect(box, SIGNAL(ready(const QImage &)), this, SLOT(onPhotoReady(const QImage &))); - App::wnd()->showLayer(box); + Ui::showLayer(box); } } @@ -107,8 +109,8 @@ void IntroSignup::paintEvent(QPaintEvent *e) { p.setFont(st::introFont->f); p.drawText(textRect, lang(lng_signup_desc), style::al_bottom); } - if (animating() || error.length()) { - p.setOpacity(errorAlpha.current()); + if (_a_error.animating() || error.length()) { + p.setOpacity(a_errorAlpha.current()); QRect errRect; if (_invertOrder) { @@ -124,17 +126,17 @@ void IntroSignup::paintEvent(QPaintEvent *e) { } if (_photoSmall.isNull()) { - if (a_photo.current() < 1) { + if (a_photoOver.current() < 1) { QRect pix(st::setPhotoImg); pix.moveTo(pix.x() + (pix.width() - st::introPhotoSize) / 2, pix.y() + (pix.height() - st::introPhotoSize) / 2); pix.setSize(QSize(st::introPhotoSize, st::introPhotoSize)); p.drawPixmap(QPoint(_phLeft, _phTop), App::sprite(), pix); } - if (a_photo.current() > 0) { + if (a_photoOver.current() > 0) { QRect pix(st::setOverPhotoImg); pix.moveTo(pix.x() + (pix.width() - st::introPhotoSize) / 2, pix.y() + (pix.height() - st::introPhotoSize) / 2); pix.setSize(QSize(st::introPhotoSize, st::introPhotoSize)); - p.setOpacity(a_photo.current()); + p.setOpacity(a_photoOver.current()); p.drawPixmap(QPoint(_phLeft, _phTop), App::sprite(), pix); p.setOpacity(1); } @@ -160,35 +162,42 @@ void IntroSignup::resizeEvent(QResizeEvent *e) { } void IntroSignup::showError(const QString &err) { - if (!animating() && err == error) return; + if (!_a_error.animating() && err == error) return; if (err.length()) { error = err; - errorAlpha.start(1); + a_errorAlpha.start(1); } else { - errorAlpha.start(0); + a_errorAlpha.start(0); } - a_photo.restart(); - anim::start(this); + _a_error.start(); } -bool IntroSignup::animStep(float64 ms) { +void IntroSignup::step_error(float64 ms, bool timer) { float64 dt = ms / st::introErrDuration; - bool res = true; if (dt >= 1) { - res = false; - errorAlpha.finish(); - if (!errorAlpha.current()) { + _a_error.stop(); + a_errorAlpha.finish(); + if (!a_errorAlpha.current()) { error = ""; } - a_photo.finish(); } else { - errorAlpha.update(dt, st::introErrFunc); - a_photo.update(dt, anim::linear); + a_errorAlpha.update(dt, st::introErrFunc); } - update(); - return res; + if (timer) update(); +} + +void IntroSignup::step_photo(float64 ms, bool timer) { + float64 dt = ms / st::introErrDuration; + + if (dt >= 1) { + _a_photo.stop(); + a_photoOver.finish(); + } else { + a_photoOver.update(dt, anim::linear); + } + if (timer) update(); } void IntroSignup::activate() { diff --git a/Telegram/SourceFiles/intro/introsignup.h b/Telegram/SourceFiles/intro/introsignup.h index c3ec33e543..b183f7fc62 100644 --- a/Telegram/SourceFiles/intro/introsignup.h +++ b/Telegram/SourceFiles/intro/introsignup.h @@ -25,7 +25,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "gui/flatinput.h" #include "intro.h" -class IntroSignup : public IntroStage, public Animated, public RPCSender { +class IntroSignup : public IntroStage, public RPCSender { Q_OBJECT public: @@ -37,7 +37,8 @@ public: void mouseMoveEvent(QMouseEvent *e); void mousePressEvent(QMouseEvent *e); - bool animStep(float64 ms); + void step_error(float64 ms, bool timer); + void step_photo(float64 ms, bool timer); void activate(); void deactivate(); @@ -60,7 +61,9 @@ private: void stopCheck(); QString error; - anim::fvalue errorAlpha, a_photo; + anim::fvalue a_errorAlpha, a_photoOver; + Animation _a_error; + Animation _a_photo; FlatButton next; diff --git a/Telegram/SourceFiles/lang.h b/Telegram/SourceFiles/lang.h index 508fcdb23c..08193e3ef6 100644 --- a/Telegram/SourceFiles/lang.h +++ b/Telegram/SourceFiles/lang.h @@ -81,6 +81,17 @@ LangString langCounted(ushort key0, ushort tag, float64 value); const char *langKeyName(LangKey key); inline LangString langDayOfMonth(const QDate &date) { + QDate c(QDate::currentDate()); + int32 month = date.month(), day = date.day(), year = date.year(), cyear = c.year(), cmonth = c.month(); + if (year != cyear) { + if (year > cyear + 1 || cyear > year + 1 || (year == cyear + 1 && month + 12 > cmonth + 3) || (cyear == year + 1 && cmonth + 12 > month + 3)) { + return (month > 0 && month <= 12) ? lng_month_day_year(lt_month, lang(LangKey(lng_month1_small + month - 1)), lt_day, QString::number(day), lt_year, QString::number(year)) : qsl("MONTH_ERR"); + } + } + return (month > 0 && month <= 12) ? lng_month_day(lt_month, lang(LangKey(lng_month1_small + month - 1)), lt_day, QString::number(day)) : qsl("MONTH_ERR"); +} + +inline LangString langDayOfMonthFull(const QDate &date) { QDate c(QDate::currentDate()); int32 month = date.month(), day = date.day(), year = date.year(), cyear = c.year(), cmonth = c.month(); if (year != cyear) { @@ -91,6 +102,28 @@ inline LangString langDayOfMonth(const QDate &date) { return (month > 0 && month <= 12) ? lng_month_day(lt_month, lang(LangKey(lng_month1 + month - 1)), lt_day, QString::number(day)) : qsl("MONTH_ERR"); } +inline LangString langMonth(const QDate &date) { + QDate c(QDate::currentDate()); + int32 month = date.month(), day = date.day(), year = date.year(), cyear = c.year(), cmonth = c.month(); + if (year != cyear) { + if (year > cyear + 1 || cyear > year + 1 || (year == cyear + 1 && month + 12 > cmonth + 3) || (cyear == year + 1 && cmonth + 12 > month + 3)) { + return (month > 0 && month <= 12) ? lng_month_year(lt_month, lang(LangKey(lng_month1_small + month - 1)), lt_year, QString::number(year)) : qsl("MONTH_ERR"); + } + } + return (month > 0 && month <= 12) ? lang(LangKey(lng_month1_small + month - 1)) : qsl("MONTH_ERR"); +} + +inline LangString langMonthFull(const QDate &date) { + QDate c(QDate::currentDate()); + int32 month = date.month(), day = date.day(), year = date.year(), cyear = c.year(), cmonth = c.month(); + if (year != cyear) { + if (year > cyear + 1 || cyear > year + 1 || (year == cyear + 1 && month + 12 > cmonth + 3) || (cyear == year + 1 && cmonth + 12 > month + 3)) { + return (month > 0 && month <= 12) ? lng_month_year(lt_month, lang(LangKey(lng_month1 + month - 1)), lt_year, QString::number(year)) : qsl("MONTH_ERR"); + } + } + return (month > 0 && month <= 12) ? lang(LangKey(lng_month1 + month - 1)) : qsl("MONTH_ERR"); +} + inline LangString langDayOfWeek(const QDate &date) { int32 day = date.dayOfWeek(); return (day > 0 && day <= 7) ? lang(LangKey(lng_weekday1 + day - 1)) : qsl("DAY_ERR"); @@ -101,6 +134,14 @@ inline LangString langDayOfWeekFull(const QDate &date) { return (day > 0 && day <= 7) ? lang(LangKey(lng_weekday1_full + day - 1)) : qsl("DAY_ERR"); } +inline LangString langDateTime(const QDateTime &date) { + return lng_mediaview_date_time(lt_date, langDayOfMonth(date.date()), lt_time, date.time().toString(cTimeFormat())); +} + +inline LangString langDateTimeFull(const QDateTime &date) { + return lng_mediaview_date_time(lt_date, langDayOfMonthFull(date.date()), lt_time, date.time().toString(cTimeFormat())); +} + class LangLoader { public: const QString &errors() const; diff --git a/Telegram/SourceFiles/langs/lang_de.strings b/Telegram/SourceFiles/langs/lang_de.strings index 851201e6ff..6764779b1a 100644 --- a/Telegram/SourceFiles/langs/lang_de.strings +++ b/Telegram/SourceFiles/langs/lang_de.strings @@ -48,6 +48,19 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_month11" = "November"; "lng_month12" = "Dezember"; +"lng_month1_small" = "Jan"; +"lng_month2_small" = "Feb"; +"lng_month3_small" = "Mär"; +"lng_month4_small" = "Apr"; +"lng_month5_small" = "Mai"; +"lng_month6_small" = "Jun"; +"lng_month7_small" = "Jul"; +"lng_month8_small" = "Aug"; +"lng_month9_small" = "Sept"; +"lng_month10_small" = "Okt"; +"lng_month11_small" = "Nov"; +"lng_month12_small" = "Dez"; + "lng_weekday1" = "Mo"; "lng_weekday2" = "Di"; "lng_weekday3" = "Mi"; @@ -66,6 +79,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_month_day" = "{day}. {month}"; "lng_month_day_year" = "{day} {month}, {year}"; +"lng_month_year" = "{month}, {year}"; "lng_box_ok" = "OK"; @@ -108,6 +122,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_server_error" = "Interner Serverfehler."; "lng_flood_error" = "Zu viele Versuche, bitte später erneut probieren."; +"lng_gif_error" = "Ein Fehler ist beim Laden der GIF-Animation aufgetreten :("; "lng_deleted" = "Gelöschter Kontakt"; "lng_deleted_message" = "Gelöschte Nachricht"; @@ -470,8 +485,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_create_channel_crop" = "Sichtbaren Bereich für Bild wählen"; "lng_failed_add_participant" = "Kann Teilnehmer nicht hinzufügen. Später erneut versuchen."; -"lng_failed_add_not_mutual" = "Wenn man eine Gruppe verlässt, kann nur\nein gemeinsamer Kontakt die Person erneut\neinladen (beide Seiten müssen die Nummer\ndes anderen gespeichert haben)."; -"lng_failed_add_not_mutual_channel" = "Wenn man einen Kanal verlässt, kann nur\nein gemeinsamer Kontakt die Person erneut\neinladen (beide Seiten müssen die Nummer\ndes anderen gespeichert haben)."; +"lng_failed_add_not_mutual" = "Wenn man eine Gruppe verlässt, kann nur ein gemeinsamer Kontakt die Person erneut einladen (beide Seiten müssen die Nummer \ndes anderen gespeichert haben)."; +"lng_failed_add_not_mutual_channel" = "Wenn man einen Kanal verlässt, kann nur ein gemeinsamer Kontakt die Person erneut einladen (beide Seiten müssen die Nummer \ndes anderen gespeichert haben)."; "lng_sure_delete_contact" = "Bist du sicher, dass du {contact} von deinen Kontakten löschen willst?"; "lng_sure_delete_history" = "Sicher, dass du den kompletten Verlauf mit {contact} löschen willst?\n\nDas kann man nicht rückgängig machen."; @@ -561,6 +576,14 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_media_video" = "Videodatei"; "lng_media_audio" = "Sprachnachricht"; +"lng_media_auto_settings" = "Einstellungen für Mediendownloads"; +"lng_media_auto_photo" = "Automatischer Bilder-Download"; +"lng_media_auto_audio" = "Automatischer Audio-Download"; +"lng_media_auto_gif" = "Automatischer GIF-Download"; +"lng_media_auto_private_chats" = "Chats"; +"lng_media_auto_groups" = "Gruppen und Kanäle"; +"lng_media_auto_play" = "Automatisch Abspielen"; + "lng_emoji_category0" = "Häufig genutzt"; "lng_emoji_category1" = "Personen"; "lng_emoji_category2" = "Natur"; @@ -571,8 +594,13 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_emoji_category7" = "Symbole & Flaggen"; "lng_switch_stickers" = "Sticker"; +"lng_switch_stickers_gifs" = "GIFs & Sticker"; "lng_switch_emoji" = "Emoji"; +"lng_saved_gifs" = "Gespeicherte GIFs"; +"lng_inline_bot_results" = "Ergebnisse von {inline_bot}"; +"lng_inline_bot_no_results" = "Keine Ergebnisse"; + "lng_box_remove" = "Entfernen"; "lng_custom_stickers" = "Eigene Sticker"; @@ -664,6 +692,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_save_file" = "Datei speichern"; "lng_save_downloaded" = "{ready} / {total} {mb}"; "lng_duration_and_size" = "{duration}, {size}"; +"lng_duration_played" = "{played} / {duration}"; +"lng_date_and_duration" = "{date}, {duration}"; "lng_choose_images" = "Bilder auswählen"; "lng_context_view_profile" = "Profil öffnen"; @@ -699,6 +729,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_context_delete_file" = "Datei löschen"; "lng_context_close_file" = "Datei schließen"; "lng_context_copy_text" = "Text kopieren"; +"lng_context_save_gif" = "GIF speichern"; "lng_context_to_msg" = "Zur Nachricht"; "lng_context_reply_msg" = "Antworten"; "lng_context_forward_msg" = "Nachricht weiterleiten"; @@ -801,7 +832,7 @@ Copyright (c) 2014-2015 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" = "— Sticker-Verwaltung: Ändere die Sortierung deiner Sticker-Pakete, Sortierung wird auf all deinen Geräten synchronisiert \n— Sticker gedrückt halten vor dem Versand für eine Vorschau\n— Neues Kontextmenü für Chats in der Chatliste \n— Neue Emoji werden unterstützt\n\nUnseren deutschsprachigen Infokanal findest du hier: https://telegram.me/TelegramDE"; +"lng_new_version_text" = "GIF-Revolution: 10x schnelleres Senden und Herunterladen, Autoplay und Speichern von GIFs in einem eigenen Tab im Sticker-Panel.\n\nMehr Infos über GIFs:\n{gifs_link}\n\nInline-Bots: Ein neuer Weg um Bot-Inhalte dem Chat hinzuzufügen. Einfach den Bot-Benutzernamen gefolgt vom Suchbegriff im Eingabefeld eintippen um Echtzeitsuchergebnisse zu erhalten und im Chat zu senden. Probier doch mal “@gif dog” im nächsten Chat aus. Beispiel-Bots: @gif, @wiki, @bing, @vid, @bold.\n\nAusführliche Informationen zu den neuen Inline-Bots:\n{bot_link}\n\nAußerdem neu: Ein neues niedliches Design für Medien, automatische Download-Einstellungen für Bilder, Sprachnachrichten und GIFs.\n\nPS: Unseren deutschsprachigen Infokanal findest du hier: https://telegram.me/TelegramDE"; "lng_menu_insert_unicode" = "Unicode-Steuerzeichen einfügen"; diff --git a/Telegram/SourceFiles/langs/lang_es.strings b/Telegram/SourceFiles/langs/lang_es.strings index f5453d70ee..20716ed5d5 100644 --- a/Telegram/SourceFiles/langs/lang_es.strings +++ b/Telegram/SourceFiles/langs/lang_es.strings @@ -48,6 +48,19 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_month11" = "noviembre"; "lng_month12" = "diciembre"; +"lng_month1_small" = "ene"; +"lng_month2_small" = "feb"; +"lng_month3_small" = "mar"; +"lng_month4_small" = "abr"; +"lng_month5_small" = "may"; +"lng_month6_small" = "jun"; +"lng_month7_small" = "jul"; +"lng_month8_small" = "ago"; +"lng_month9_small" = "sep"; +"lng_month10_small" = "oct"; +"lng_month11_small" = "nov"; +"lng_month12_small" = "dic"; + "lng_weekday1" = "lun"; "lng_weekday2" = "mar"; "lng_weekday3" = "mié"; @@ -66,6 +79,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_month_day" = "{day} de {month}"; "lng_month_day_year" = "{day} de {month} de {year}"; +"lng_month_year" = "{month}, {year}"; "lng_box_ok" = "OK"; @@ -108,6 +122,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_server_error" = "Error interno del servidor."; "lng_flood_error" = "Muchos intentos. Por favor, reinténtalo más tarde."; +"lng_gif_error" = "Ocurrió un error al leer la animación GIF :("; "lng_deleted" = "Desconocido"; "lng_deleted_message" = "Mensaje eliminado"; @@ -470,13 +485,13 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_create_channel_crop" = "Selecciona el área para la foto del canal"; "lng_failed_add_participant" = "No se pudo añadir al usuario. Por favor, reinténtalo más tarde."; -"lng_failed_add_not_mutual" = "Lo sentimos, si una persona deja un\ngrupo, sólo un contacto mutuo puede \ntraerlo de vuelta (ellos necesitan tener\ntu número, y tú el suyo)."; -"lng_failed_add_not_mutual_channel" = "Lo sentimos, si una persona deja el canal, \nsólo un contacto mutuo puede volver \na invitarlo (necesitan tener tu \nnúmero y tú el de ellos)."; +"lng_failed_add_not_mutual" = "Lo sentimos, si una persona deja el grupo, sólo un contacto mutuo puede volver a invitarlo (necesitan tener tu número y tú el suyo)."; +"lng_failed_add_not_mutual_channel" = "Lo sentimos, si una persona deja el canal, sólo un contacto mutuo puede volver a invitarlo (necesitan tener tu número y tú el suyo)."; "lng_sure_delete_contact" = "¿Quieres eliminar a {contact} de tu lista de contactos?"; -"lng_sure_delete_history" = "¿Quieres eliminar todo el historial de mensajes con {contact}?\n\nEsta acción no se puede deshacer."; +"lng_sure_delete_history" = "¿Quieres borrar todo el historial de mensajes con {contact}?\n\nEsta acción no se puede deshacer."; "lng_sure_delete_group_history" = "¿Quieres borrar todo el historial en «{group}»?\n\nEsta acción no se puede deshacer."; -"lng_sure_delete_and_exit" = "¿Quieres eliminar todo el historial de mensajes y dejar el grupo «{group}»?\n\nEsta acción no se puede deshacer."; +"lng_sure_delete_and_exit" = "¿Quieres borrar todo el historial de mensajes y dejar el grupo «{group}»?\n\nEsta acción no se puede deshacer."; "lng_sure_leave_channel" = "¿Quieres dejar este canal?"; "lng_sure_delete_channel" = "¿Quieres eliminar este canal? Todos los miembros y mensajes se perderán."; "lng_sure_leave_group" = "¿Quieres salir del grupo?\nNo puedes deshacer esta acción."; @@ -561,6 +576,14 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_media_video" = "Vídeo"; "lng_media_audio" = "Mensaje de voz"; +"lng_media_auto_settings" = "Ajustes de descarga automática de multimedia"; +"lng_media_auto_photo" = "Descarga automática de fotos"; +"lng_media_auto_audio" = "Descarga automática de audio"; +"lng_media_auto_gif" = "Descarga automática de GIF"; +"lng_media_auto_private_chats" = "Chats"; +"lng_media_auto_groups" = "Grupos y canales"; +"lng_media_auto_play" = "Autorreproducir"; + "lng_emoji_category0" = "Uso frecuente"; "lng_emoji_category1" = "Personas"; "lng_emoji_category2" = "Naturaleza"; @@ -571,8 +594,13 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_emoji_category7" = "Símbolos y banderas"; "lng_switch_stickers" = "Stickers"; +"lng_switch_stickers_gifs" = "GIF y stickers"; "lng_switch_emoji" = "Emoji"; +"lng_saved_gifs" = "GIF guardados"; +"lng_inline_bot_results" = "Resultados de {inline_bot}"; +"lng_inline_bot_no_results" = "Sin resultados"; + "lng_box_remove" = "Eliminar"; "lng_custom_stickers" = "Stickers personalizados"; @@ -616,7 +644,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_broadcast_ph" = "Difunde un mensaje..."; "lng_record_cancel" = "Suelta fuera de aquí para cancelar"; "lng_empty_history" = ""; -"lng_willbe_history" = "Por favor, selecciona un chat para comenzar"; +"lng_willbe_history" = "Selecciona un chat para comenzar"; "lng_message_with_from" = "[c]{from}:[/c] {message}"; "lng_from_you" = "Tú"; "lng_bot_description" = "¿Qué puede hacer este bot?"; @@ -664,6 +692,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_save_file" = "Guardar archivo"; "lng_save_downloaded" = "{ready} / {total} {mb}"; "lng_duration_and_size" = "{duration}, {size}"; +"lng_duration_played" = "{played} / {duration}"; +"lng_date_and_duration" = "{date}, {duration}"; "lng_choose_images" = "Elegir imágenes"; "lng_context_view_profile" = "Ver información"; @@ -699,6 +729,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_context_delete_file" = "Eliminar archivo"; "lng_context_close_file" = "Cerrar archivo"; "lng_context_copy_text" = "Copiar texto"; +"lng_context_save_gif" = "Guardar GIF"; "lng_context_to_msg" = "Ir al mensaje"; "lng_context_reply_msg" = "Responder"; "lng_context_forward_msg" = "Reenviar mensaje"; @@ -717,7 +748,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_send_image_too_large" = "No se pudo enviar el archivo, porque es más grande que 1.5 GB :("; "lng_send_folder" = "No se pudo enviar «{name}», porque es un directorio :("; -"lng_forward_choose" = "Elegir destinatario..."; +"lng_forward_choose" = "Elige un destinatario..."; "lng_forward_cant" = "Lo sentimos, no puedes reenviar aquí :("; "lng_forward_confirm" = "¿Reenviar a {recipient}?"; "lng_forward_share_contact" = "¿Compartir contacto con {recipient}?"; @@ -801,7 +832,7 @@ Copyright (c) 2014-2015 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" = "— Administra los stickers: ordena manualmente tus packs de stickers. El orden de los packs se sincronizará en todos tus dispositivos \n— Haz clic y mantén en un sticker para ver una vista previa antes de enviar\n— Nuevo menú contextual para los chats en la lista de chats \n— Soporte de todos los emojis existentes"; +"lng_new_version_text" = "Revolución del GIF: envío y descarga 10 veces más rápido, reproducción automática, guarda tus GIF favoritos en una pestaña especial en el panel de stickers.\n\nMás sobre los GIF:\n{gifs_link}\n\nBots integrados: una nueva forma para añadir contenidos en los chats. Escribe el alias del bot y tu solicitud, en el campo de escritura, para obtener resultados inmediatamente y enviarlos a tu compañero de chat. Prueba escribiendo “@gif dog” en un chat. Algunos ejemplos: @gif, @wiki, @bing, @vid, @bold.\n\nMás sobre los bots integrados:\n{bots_link}\n\nTambién encontrarás un lindo nuevo diseño para la multimedia y ajustes de descarga automática para fotos, mensajes de voz y GIF."; "lng_menu_insert_unicode" = "Insertar caracteres de control Unicode"; diff --git a/Telegram/SourceFiles/langs/lang_it.strings b/Telegram/SourceFiles/langs/lang_it.strings index 7613ae82f4..28d9b9eaee 100644 --- a/Telegram/SourceFiles/langs/lang_it.strings +++ b/Telegram/SourceFiles/langs/lang_it.strings @@ -48,6 +48,19 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_month11" = "Novembre"; "lng_month12" = "Dicembre"; +"lng_month1_small" = "Gen"; +"lng_month2_small" = "Feb"; +"lng_month3_small" = "Mar"; +"lng_month4_small" = "Apr"; +"lng_month5_small" = "Mag"; +"lng_month6_small" = "Giu"; +"lng_month7_small" = "Lug"; +"lng_month8_small" = "Ago"; +"lng_month9_small" = "Set"; +"lng_month10_small" = "Ott"; +"lng_month11_small" = "Nov"; +"lng_month12_small" = "Dic"; + "lng_weekday1" = "Lun"; "lng_weekday2" = "Mar"; "lng_weekday3" = "Mer"; @@ -66,6 +79,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_month_day" = "{day} {month}"; "lng_month_day_year" = "{day} {month} {year}"; +"lng_month_year" = "{month}, {year}"; "lng_box_ok" = "Ok"; @@ -108,6 +122,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_server_error" = "Errore interno del server."; "lng_flood_error" = "Troppi tentativi. Per favore riprova più tardi."; +"lng_gif_error" = "C'è stato un errore nel leggere la GIF :("; "lng_deleted" = "Sconosciuto"; "lng_deleted_message" = "Messaggio eliminato"; @@ -134,7 +149,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_code_ph" = "Codice"; "lng_code_desc" = "Abbiamo inviato un messaggio col codice\ndi attivazione al tuo telefono. Inseriscilo qui"; "lng_code_telegram" = "Per favore inserisci il codice che hai\nappena ricevuto nell'altra app di [b]Telegram[/b]."; -"lng_code_no_telegram" = "Invia codice tramite SMS"; +"lng_code_no_telegram" = "Invia codice via SMS"; "lng_code_call" = "Telegram ti chiamerà tra {minutes}:{seconds}"; "lng_code_calling" = "Richiedendo una telefonata da Telegram.."; "lng_code_called" = "Telegram ti ha chiamato"; @@ -470,8 +485,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_create_channel_crop" = "Seleziona un riquadro per la foto del canale"; "lng_failed_add_participant" = "Impossibile aggiungere l'utente. Riprova più tardi."; -"lng_failed_add_not_mutual" = "Spiacenti, se una persona lascia un gruppo,\nsolo un contatto in comune può reinvitarla\n(chi ti invita deve avere il tuo contatto\nsu Telegram, e viceversa)."; -"lng_failed_add_not_mutual_channel" = "Spiacenti, se una persona lascia un canale,\nsolo un contatto in comune può reinvitarla\n(chi ti invita deve avere il tuo contatto\nsu Telegram, e viceversa)."; +"lng_failed_add_not_mutual" = "Spiacenti, se una persona lascia un gruppo, solo un contatto in comune può reinvitarla (chi ti invita deve avere il tuo numero, e viceversa)."; +"lng_failed_add_not_mutual_channel" = "Spiacenti, se una persona lascia un canale, solo un contatto in comune può reinvitarla (chi ti invita deve avere il tuo numero, e viceversa)."; "lng_sure_delete_contact" = "Sicuro di volere eliminare {contact} dalla tua lista dei contatti?"; "lng_sure_delete_history" = "Sicuro di voler eliminare tutta la cronologia dei messaggi con {contact}?\n\nQuesta azione non può essere annullata."; @@ -496,7 +511,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_action_kick_user" = "{from} ha rimosso {user}"; "lng_action_user_left" = "{from} ha lasciato il gruppo"; "lng_action_user_joined" = "{from} si è unito al gruppo"; -"lng_action_user_joined_by_link" = "{from} si è unito al gruppo tramite link di invito"; +"lng_action_user_joined_by_link" = "{from} si è unito al gruppo via link di invito"; "lng_action_user_registered" = "{from} si è unito a Telegram"; "lng_action_removed_photo" = "{from} ha rimosso la foto del gruppo"; "lng_action_removed_photo_channel" = "Foto del canale rimossa"; @@ -556,11 +571,19 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_media_type_links" = "Link condivisi"; "lng_media_open_with" = "Apri con"; -"lng_media_download" = "Download"; +"lng_media_download" = "Scarica"; "lng_media_cancel" = "Annulla"; "lng_media_video" = "Video"; "lng_media_audio" = "Nota vocale"; +"lng_media_auto_settings" = "Impostazioni di download automatico"; +"lng_media_auto_photo" = "Download automatico foto"; +"lng_media_auto_audio" = "Download automatico audio"; +"lng_media_auto_gif" = "Download automatico GIF"; +"lng_media_auto_private_chats" = "Chat"; +"lng_media_auto_groups" = "Gruppi e canali"; +"lng_media_auto_play" = "Autoriproduzione"; + "lng_emoji_category0" = "Utilizzate di frequente"; "lng_emoji_category1" = "Persone"; "lng_emoji_category2" = "Natura"; @@ -571,8 +594,13 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_emoji_category7" = "Simboli e bandiere"; "lng_switch_stickers" = "Sticker"; +"lng_switch_stickers_gifs" = "GIF e Sticker"; "lng_switch_emoji" = "Emoji"; +"lng_saved_gifs" = "GIF salvate"; +"lng_inline_bot_results" = "Risultati da {inline_bot}"; +"lng_inline_bot_no_results" = "Nessun risultato"; + "lng_box_remove" = "Rimuovi"; "lng_custom_stickers" = "Sticker personalizzati"; @@ -664,6 +692,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_save_file" = "Salva file"; "lng_save_downloaded" = "{ready} / {total} {mb}"; "lng_duration_and_size" = "{duration}, {size}"; +"lng_duration_played" = "{played} / {duration}"; +"lng_date_and_duration" = "{date}, {duration}"; "lng_choose_images" = "Scegli immagini"; "lng_context_view_profile" = "Visualizza profilo"; @@ -699,6 +729,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_context_delete_file" = "Elimina file"; "lng_context_close_file" = "Chiudi file"; "lng_context_copy_text" = "Copia testo"; +"lng_context_save_gif" = "Salva GIF"; "lng_context_to_msg" = "Vai al messaggio"; "lng_context_reply_msg" = "Rispondi"; "lng_context_forward_msg" = "Inoltra messaggio"; @@ -801,7 +832,7 @@ Copyright (c) 2014-2015 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" = "— Bug fix e altri miglioramenti minori"; -"lng_new_version_text" = "— Gestione degli sticker: Riordina manualmente i tuoi set di sticker, ora sono sincronizzati tra i tuoi dispositivi\n— Clicca e tieni premuto su uno sticker per visualizzare l'anteprima prima di inviarlo\n— Nuovo menu contestuale per le chat nella lista chat\n— Tutte le nuove emoji ora sono supportate"; +"lng_new_version_text" = "Rivoluzione GIF: L'invio e il download delle GIF sono ora 10 volte più veloci, riproduci automaticamente le GIF e salva le tue GIF preferite in una pagina dedicata nel pannello sticker.\n\nPiù info sulle GIF:\n{gifs_link}\n\n@-Bot: Un nuovo modo di aggiungere contenuto dai bot in qualsiasi chat. Scrivi l'username di un bot e la tua domanda nel campo di scrittura per ricevere risultati immediati e inviarli nella chat. Prova a scrivere “@gif dog” nella tua prossima chat. Bot di esempio: @gif, @wiki, @bing, @vid, @bold.\n\nPiù info sui @-Bot:\n{bots_link}\n\nInoltre in questa versione: Nuovo design per i media, impostazioni di download automatico per foto, note vocali e GIF."; "lng_menu_insert_unicode" = "Inserisci carattere di controllo Unicode"; diff --git a/Telegram/SourceFiles/langs/lang_ko.strings b/Telegram/SourceFiles/langs/lang_ko.strings index 8d1dc19b2d..09113ebe73 100644 --- a/Telegram/SourceFiles/langs/lang_ko.strings +++ b/Telegram/SourceFiles/langs/lang_ko.strings @@ -48,6 +48,19 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_month11" = "11월"; "lng_month12" = "12월"; +"lng_month1_small" = "1월"; +"lng_month2_small" = "2월"; +"lng_month3_small" = "3월"; +"lng_month4_small" = "4월"; +"lng_month5_small" = "5월"; +"lng_month6_small" = "6월"; +"lng_month7_small" = "7월"; +"lng_month8_small" = "8월"; +"lng_month9_small" = "9월"; +"lng_month10_small" = "10월"; +"lng_month11_small" = "11월"; +"lng_month12_small" = "12월"; + "lng_weekday1" = "월"; "lng_weekday2" = "화"; "lng_weekday3" = "수"; @@ -66,6 +79,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_month_day" = "{month} {day}일"; "lng_month_day_year" = "{month} {day}, {year}"; +"lng_month_year" = "{month}, {year}"; "lng_box_ok" = "확인"; @@ -108,6 +122,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_server_error" = "내부 서버 오류"; "lng_flood_error" = "시도가 너무 많습니다. 나중에 다시 시도해주세요."; +"lng_gif_error" = "GIF 애니메이션을 읽는 동안 에러가 발생하였습니다."; "lng_deleted" = "알 수 없음"; "lng_deleted_message" = "삭제된 메시지"; @@ -470,8 +485,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_create_channel_crop" = "채널프로필 사진으로 사용할 사각영역을 선택하세요"; "lng_failed_add_participant" = "사용자를 추가 하지 못하였습니다. 나중에 다시 시도해주세요"; -"lng_failed_add_not_mutual" = "죄송합니다. 그룹방에서 대화상대방이 나갔을 경우, \n상대 전화번호가 있는 분만 초대가 가능합니다. \n(서로 전화번호가 등록되어져 있어야만 가능)"; -"lng_failed_add_not_mutual_channel" = "죄송합니다. 채널에서 대화상대방이 나갔을 경우,\n상대 전화번호가 있는 분만 초대가 가능합니다.\n(서로 전화번호가 등록되어져 있어야만 가능)"; +"lng_failed_add_not_mutual" = "죄송합니다. 그룹방에서 대화상대방이 나갔을 경우, 상대 전화번호가 있는 분만 초대가 가능합니다. (서로 전화번호가 등록되어져 있어야만 가능)"; +"lng_failed_add_not_mutual_channel" = "죄송합니다. 채널에서 대화상대방이 나갔을 경우, 상대 전화번호가 있는 분만 초대가 가능합니다. (서로 전화번호가 등록되어져 있어야만 가능)"; "lng_sure_delete_contact" = "{contact} 님을 주소록에서 \n삭제하시겠습니까?"; "lng_sure_delete_history" = "{contact} 님과 관련된 모든 메시지를 \n삭제하시겠습니까?\n\n삭제 하실 경우 취소가 불가능합니다."; @@ -561,6 +576,14 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_media_video" = "비디오 파일"; "lng_media_audio" = "음성 메시지"; +"lng_media_auto_settings" = "미디어 자동 다운로드 설정"; +"lng_media_auto_photo" = "사진 자동 다운로드"; +"lng_media_auto_audio" = "음성 메시지 자동 다운로드"; +"lng_media_auto_gif" = "GIF 자동 다운로드"; +"lng_media_auto_private_chats" = "비밀대화"; +"lng_media_auto_groups" = "그룹과 채널"; +"lng_media_auto_play" = "자동재생"; + "lng_emoji_category0" = "자주 사용"; "lng_emoji_category1" = "사람"; "lng_emoji_category2" = "자연"; @@ -571,8 +594,13 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_emoji_category7" = "심볼 및 국기"; "lng_switch_stickers" = "스티커"; +"lng_switch_stickers_gifs" = "GIF & 스티커"; "lng_switch_emoji" = "이모티콘"; +"lng_saved_gifs" = "저장된 GIF"; +"lng_inline_bot_results" = "{inline_bot} 결과"; +"lng_inline_bot_no_results" = "결과 없음"; + "lng_box_remove" = "삭제"; "lng_custom_stickers" = "커스텀 스티커"; @@ -664,6 +692,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_save_file" = "파일 저장"; "lng_save_downloaded" = "{ready} / {total} {mb}"; "lng_duration_and_size" = "{duration}, {size}"; +"lng_duration_played" = "{played} / {duration}"; +"lng_date_and_duration" = "{date}, {duration}"; "lng_choose_images" = "이미지 선택"; "lng_context_view_profile" = "프로필 보기"; @@ -699,6 +729,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_context_delete_file" = "파일 삭제"; "lng_context_close_file" = "파일 닫기"; "lng_context_copy_text" = "텍스트 복사"; +"lng_context_save_gif" = "GIF 저장"; "lng_context_to_msg" = "메시지로 이동"; "lng_context_reply_msg" = "답장"; "lng_context_forward_msg" = "메시지 전달"; @@ -801,7 +832,7 @@ Copyright (c) 2014-2015 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" = "— 스티커관리 : 스티커팩을 순서를 수동으로 변경하여, 연결된 모든 기기에 동기화 \n— 스티커를 지속하여 누를 경우 전송전 프리뷰 기능 추가\n— 채팅목록에 채팅관련 메뉴 추가\n— 기존 이모티콘 지원"; +"lng_new_version_text" = "GIF 혁명: 10배 빠른 전송, 다운로드 및 자동재생이 가능하며, 스티커 패널에서 즐겨찾는 GIF를 저장가능\n\nGIF에 대한 자세한 정보:\n{gifs_link}\n\n@-봇 : 모든 대화에 봇 기능을 추가 할 수 있습니다. 봇 아이디를 입력란에 같이 입력해주시면 즉시 결과값을 확인 할 수 있습니다.“@gif dog”와 같은 명령어를 입력란에 같이 입력해보세요. 예시:@gif, @wiki, @bing, @vid, @bold.\n\n@-봇에 대한 자세한 정보\n{bots_link}\n\n업데이트 추가사항: 미디어, 사진, 음성메시지 및 GIF 파일 자동다운로드 설정화면 새로운 디자인 적용"; "lng_menu_insert_unicode" = "유니코드 문자를 입력하세요."; diff --git a/Telegram/SourceFiles/langs/lang_nl.strings b/Telegram/SourceFiles/langs/lang_nl.strings index 4f21e3b0d4..fe6af5786c 100644 --- a/Telegram/SourceFiles/langs/lang_nl.strings +++ b/Telegram/SourceFiles/langs/lang_nl.strings @@ -48,6 +48,19 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_month11" = "november"; "lng_month12" = "december"; +"lng_month1_small" = "jan"; +"lng_month2_small" = "feb"; +"lng_month3_small" = "mrt"; +"lng_month4_small" = "apr"; +"lng_month5_small" = "mei"; +"lng_month6_small" = "jun"; +"lng_month7_small" = "jul"; +"lng_month8_small" = "aug"; +"lng_month9_small" = "sep"; +"lng_month10_small" = "okt"; +"lng_month11_small" = "nov"; +"lng_month12_small" = "dec"; + "lng_weekday1" = "ma"; "lng_weekday2" = "di"; "lng_weekday3" = "wo"; @@ -66,6 +79,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_month_day" = "{day} {month}"; "lng_month_day_year" = "{day} {month}, {year}"; +"lng_month_year" = "{month}, {year}"; "lng_box_ok" = "Ok"; @@ -108,6 +122,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_server_error" = "Interne serverfout."; "lng_flood_error" = "Teveel pogingen. Probeer het later opnieuw."; +"lng_gif_error" = "Er is iets een fout opgetreden bij het lezen van de GIF :("; "lng_deleted" = "Onbekend"; "lng_deleted_message" = "Verwijderd bericht"; @@ -217,7 +232,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_settings_section_general" = "Algemeen"; "lng_settings_change_lang" = "Taal wijzigen"; "lng_languages" = "Talen"; -"lng_sure_save_language" = "Telegram moet herstarten om de taal te wijzigen"; +"lng_sure_save_language" = "Opnieuw starten om de taal te wijzen?"; "lng_settings_auto_update" = "Automatisch bijwerken"; "lng_settings_current_version" = "Versie {version}"; "lng_settings_check_now" = "Controleer op updates"; @@ -470,8 +485,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_create_channel_crop" = "Kies een vierkant voor de kanaalfoto"; "lng_failed_add_participant" = "Gebruiker toevoegen mislukt. Probeer het later."; -"lng_failed_add_not_mutual" = "Iemand die de groep verlaat kan alleen\nDoor een wederzijds contact \nworden toegevoegd \n(opgeslagen nummers) "; -"lng_failed_add_not_mutual_channel" = "Iemand die het kanaal verlaat kan alleen\nDoor een wederzijds contact \nworden toegevoegd \n(opgeslagen nummers) "; +"lng_failed_add_not_mutual" = "Iemand die de groep verlaat kan alleen door een wederzijds contact worden toegevoegd (opgeslagen nummers)"; +"lng_failed_add_not_mutual_channel" = "Iemand die het kanaal verlaat kan alleen door een wederzijds contact worden toegevoegd (opgeslagen nummers)"; "lng_sure_delete_contact" = "{contact} echt verwijderen uit contacten?"; "lng_sure_delete_history" = "Geschiedenis met {contact} echt wissen? \n\nDeze actie kan niet ongedaan worden gemaakt."; @@ -561,6 +576,14 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_media_video" = "Video"; "lng_media_audio" = "Spraakbericht"; +"lng_media_auto_settings" = "Instellingen voor media-downloads"; +"lng_media_auto_photo" = "Foto's automatisch downloaden"; +"lng_media_auto_audio" = "Audio automatisch downloaden"; +"lng_media_auto_gif" = "GIF's automatisch downloaden"; +"lng_media_auto_private_chats" = "Chats"; +"lng_media_auto_groups" = "Groepen en kanalen"; +"lng_media_auto_play" = "Automatisch afspelen"; + "lng_emoji_category0" = "Veelgebruikt"; "lng_emoji_category1" = "Mensen"; "lng_emoji_category2" = "Natuur"; @@ -571,8 +594,13 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_emoji_category7" = "Symbolen en vlaggen"; "lng_switch_stickers" = "Stickers"; +"lng_switch_stickers_gifs" = "GIF's & stickers"; "lng_switch_emoji" = "Emoji"; +"lng_saved_gifs" = "Opgeslagen GIF's"; +"lng_inline_bot_results" = "Resultaten van {inline_bot}"; +"lng_inline_bot_no_results" = "Geen resultaten"; + "lng_box_remove" = "Verwijder"; "lng_custom_stickers" = "Aangepaste stickers"; @@ -664,6 +692,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_save_file" = "Bestand opslaan"; "lng_save_downloaded" = "{ready} / {total} {mb}"; "lng_duration_and_size" = "{duration}, {size}"; +"lng_duration_played" = "{played} / {duration}"; +"lng_date_and_duration" = "{date}, {duration}"; "lng_choose_images" = "Afbeeldingen kiezen"; "lng_context_view_profile" = "Profiel weergeven"; @@ -699,6 +729,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_context_delete_file" = "Bestand verwijderen"; "lng_context_close_file" = "Bestand sluiten"; "lng_context_copy_text" = "Tekst kopiëren"; +"lng_context_save_gif" = "GIF opslaan"; "lng_context_to_msg" = "Naar bericht gaan"; "lng_context_reply_msg" = "Antwoord"; "lng_context_forward_msg" = "Bericht doorsturen"; @@ -801,7 +832,7 @@ Copyright (c) 2014-2015 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" = "— Stickerbeheer: Je kunt nu handmatig je stickerbundels sorteren, de volgorde synchroniseren we voor je, over al je apparaten.\n— Klik en hou een sticker vast om een voorbeeld weer te geven voor het versturen.\n— Nieuw contextmenu voor chats in het chats-overzicht\n— Ondersteuning voor alle bestaande emoji"; +"lng_new_version_text" = "GIF-revolutie: 10 keer zo snel GIF's sturen en downloaden. Speel ze automatisch af en sla je favorieten op in het stickerpaneel.\n\nMeer over GIFs:\n{gifs_link}\n\nInline-bots: Voeg botcontent toe aan chats op een nieuwe manier! Geef in het invoerveld de naam van een bot en je commando in en stuur de resultaten naar je chatpartner. Probeer het eens met “@gif dog”. Voorbeelden: @gif, @wiki, @bing, @vid, @bold.\n\nMeer over inline-bots:\n{bots_link}\n\nIn deze release hebben we nog meer voor je. Een nieuw design voor media en automatische download-instellingen voor foto's, spraakberichten en GIF's."; "lng_menu_insert_unicode" = "Unicode-besturingsteken invoegen"; diff --git a/Telegram/SourceFiles/langs/lang_pt_BR.strings b/Telegram/SourceFiles/langs/lang_pt_BR.strings index de6d18440c..200c3741e5 100644 --- a/Telegram/SourceFiles/langs/lang_pt_BR.strings +++ b/Telegram/SourceFiles/langs/lang_pt_BR.strings @@ -48,6 +48,19 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_month11" = "Novembro"; "lng_month12" = "Dezembro"; +"lng_month1_small" = "Jan"; +"lng_month2_small" = "Fev"; +"lng_month3_small" = "Mar"; +"lng_month4_small" = "Abr"; +"lng_month5_small" = "Mai"; +"lng_month6_small" = "Jun"; +"lng_month7_small" = "Jul"; +"lng_month8_small" = "Ago"; +"lng_month9_small" = "Set"; +"lng_month10_small" = "Out"; +"lng_month11_small" = "Nov"; +"lng_month12_small" = "Dez"; + "lng_weekday1" = "Seg"; "lng_weekday2" = "Ter"; "lng_weekday3" = "Qua"; @@ -66,6 +79,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_month_day" = "{month} {day}"; "lng_month_day_year" = "{day} {month}, {year}"; +"lng_month_year" = "{month}, {year}"; "lng_box_ok" = "OK"; @@ -108,6 +122,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_server_error" = "Erro interno do servidor."; "lng_flood_error" = "Muitas tentativas. Por favor, tente novamente mais tarde."; +"lng_gif_error" = "Um erro ocorreu com a animação do GIF :("; "lng_deleted" = "Desconhecido"; "lng_deleted_message" = "Mensagem apagada"; @@ -342,7 +357,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_connection_try_ipv6" = "Tentando conexão via IPv6"; "lng_connection_host_ph" = "Nome do host"; "lng_connection_port_ph" = "Porta"; -"lng_connection_user_ph" = "Nome de usuário"; +"lng_connection_user_ph" = "Usuário"; "lng_connection_password_ph" = "Senha"; "lng_connection_save" = "Salvar"; "lng_settings_show_sessions" = "Exibir todas as sessões"; @@ -448,7 +463,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_create_group_next" = "Próximo"; "lng_create_group_create" = "Criar"; "lng_create_group_title" = "Novo Grupo"; -"lng_create_group_about" = "Grupos são ideais para comunidades menores, \neles podem ter até {count:_not_used|# membro|# membros}"; +"lng_create_group_about" = "Grupos são ideais para comunidades menores,\ncom até {count:_not_used|# membro|# membros}"; "lng_create_channel_title" = "Novo Canal"; "lng_create_channel_about" = "Canais são uma ferramenta para transmitir suas mensagens para audiências ilimitadas"; "lng_create_public_channel_title" = "Canal Público"; @@ -470,8 +485,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_create_channel_crop" = "Selecione uma área para a foto do canal"; "lng_failed_add_participant" = "Usuário não pôde ser adicionado. Tente novamente mais tarde."; -"lng_failed_add_not_mutual" = "Se uma pessoa deixou o grupo, apenas um\ncontato mútuo pode colocá-la novamente\n(eles precisam do seu número na agenda\ndo telefone e você do número deles)."; -"lng_failed_add_not_mutual_channel" = "Se uma pessoa deixou o canal, apenas um\ncontato mútuo pode colocá-la novamente\n(eles precisam do seu número na agenda\ndo telefone e você do número deles)."; +"lng_failed_add_not_mutual" = "Se uma pessoa deixou o grupo, apenas um contato mútuo pode colocá-la novamente (eles precisam do seu número na agenda do telefone e você do número deles)."; +"lng_failed_add_not_mutual_channel" = "Se uma pessoa deixou o canal, apenas um contato mútuo pode colocá-la novamente (eles precisam do seu número na agenda do telefone e você do número deles)."; "lng_sure_delete_contact" = "Você tem certeza que deseja remover {contact} da sua lista de contatos?"; "lng_sure_delete_history" = "Você tem certeza que deseja apagar todo o seu histórico de mensagens com {contact}?\n\nEssa ação não pode ser desfeita."; @@ -561,6 +576,14 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_media_video" = "Vídeo"; "lng_media_audio" = "Mensagem de voz"; +"lng_media_auto_settings" = "Configurações do download automático de mídia"; +"lng_media_auto_photo" = "Baixar foto automaticamente"; +"lng_media_auto_audio" = "Baixar mensagem de voz automaticamente"; +"lng_media_auto_gif" = "Baixar GIF autometicamente"; +"lng_media_auto_private_chats" = "Conversas privadas"; +"lng_media_auto_groups" = "Grupos e canais"; +"lng_media_auto_play" = "Reproduzir automaticamente"; + "lng_emoji_category0" = "Frequentemente usado"; "lng_emoji_category1" = "Pessoas"; "lng_emoji_category2" = "Natureza"; @@ -571,8 +594,13 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_emoji_category7" = "Símbolos e Bandeiras"; "lng_switch_stickers" = "Stickers"; +"lng_switch_stickers_gifs" = "GIFs e Stickers"; "lng_switch_emoji" = "Emoji"; +"lng_saved_gifs" = "GIFs Salvos"; +"lng_inline_bot_results" = "Resultados de {inline_bot}"; +"lng_inline_bot_no_results" = "Nenhum resultado"; + "lng_box_remove" = "Remover"; "lng_custom_stickers" = "Stickers customizados"; @@ -664,6 +692,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_save_file" = "Salvar arquivo"; "lng_save_downloaded" = "{ready} / {total} {mb}"; "lng_duration_and_size" = "{duration}, {size}"; +"lng_duration_played" = "{played} / {duration}"; +"lng_date_and_duration" = "{date}, {duration}"; "lng_choose_images" = "Escolher imagens"; "lng_context_view_profile" = "Ver perfil"; @@ -699,6 +729,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_context_delete_file" = "Apagar Arquivo"; "lng_context_close_file" = "Fechar Arquivo"; "lng_context_copy_text" = "Copiar Texto"; +"lng_context_save_gif" = "Salvar GIF"; "lng_context_to_msg" = "Ir Para Mensagem"; "lng_context_reply_msg" = "Responder"; "lng_context_forward_msg" = "Encaminhar Mensagem"; @@ -801,7 +832,7 @@ Copyright (c) 2014-2015 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" = "— Gerenciamento de stickers: rearranje manualmente seus pacotes de sticker, a ordem dos pacotes agora é sincronizada entre todos os dispositivos\n— Clique e segure sobre um sticker para pré-visualizar antes de enviar\n— Novo menu de contexto para os chats na lista de chats \n— Suporte para todos os emojis existentes"; +"lng_new_version_text" = "Revolução dos GIFs: download e envio 10x mais rápido, reprodução automática, salve seus GIFs favoritos em um guia dedicada no painel de stickers.\n\nMais sobre GIFs:\n{gifs_link}\n\nBots integrados: Uma nova maneira de adicionar conteúdo de um bot a qualquer conversa. Digite o nome de usuário do bot seguido do comando no campo de texto para obter resultados imediatos e enviá-los na conversa. Experimente digitar “@gif dog” na sua próxima conversa. Bots de exemplo: @gif, @wiki, @bing, @vid, @bold.\n\nMais sobre bots integrados:\n{bots_link}\n\nTambém nessa versão: Novo design para mídias, configuração de download automático para fotos, mensagens de voz e GIFs."; "lng_menu_insert_unicode" = "Inserir caractere de controle Unicode"; diff --git a/Telegram/SourceFiles/layerwidget.cpp b/Telegram/SourceFiles/layerwidget.cpp index 85f0fe6644..eda4dc89fc 100644 --- a/Telegram/SourceFiles/layerwidget.cpp +++ b/Telegram/SourceFiles/layerwidget.cpp @@ -29,15 +29,15 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org BackgroundWidget::BackgroundWidget(QWidget *parent, LayeredWidget *w) : TWidget(parent) , w(w) -, aBackground(0) -, aBackgroundFunc(anim::easeOutCirc) +, a_bg(0) +, _a_background(animation(this, &BackgroundWidget::step_background)) , hiding(false) , shadow(st::boxShadow) { w->setParent(this); if (App::app()) App::app()->mtpPause(); setGeometry(0, 0, App::wnd()->width(), App::wnd()->height()); - aBackground.start(1); - anim::start(this); + a_bg.start(1); + _a_background.start(); show(); connect(w, SIGNAL(closed()), this, SLOT(onInnerClose())); connect(w, SIGNAL(resized()), this, SLOT(update())); @@ -46,7 +46,7 @@ BackgroundWidget::BackgroundWidget(QWidget *parent, LayeredWidget *w) : TWidget( } void BackgroundWidget::showFast() { - animStep(st::layerSlideDuration + 1); + _a_background.step(getms() + st::layerSlideDuration + 1); update(); } @@ -58,10 +58,10 @@ void BackgroundWidget::paintEvent(QPaintEvent *e) { if (!trivial) { p.setClipRect(e->rect()); } - p.setOpacity(st::layerAlpha * aBackground.current()); + p.setOpacity(st::layerAlpha * a_bg.current()); p.fillRect(rect(), st::layerBg->b); - p.setOpacity(aBackground.current()); + p.setOpacity(a_bg.current()); shadow.paint(p, w->geometry(), st::boxShadowShift); } @@ -90,7 +90,7 @@ bool BackgroundWidget::onInnerClose() { _hidden.pop_back(); w->show(); resizeEvent(0); - w->animStep(1); + w->showStep(1); update(); return false; } @@ -101,8 +101,8 @@ void BackgroundWidget::startHide() { hiding = true; if (App::wnd()) App::wnd()->setInnerFocus(); - aBackground.start(0); - anim::start(this); + a_bg.start(0); + _a_background.start(); w->startHide(); } @@ -139,7 +139,7 @@ void BackgroundWidget::replaceInner(LayeredWidget *n) { connect(w, SIGNAL(destroyed(QObject*)), this, SLOT(boxDestroyed(QObject*))); w->show(); resizeEvent(0); - w->animStep(1); + w->showStep(1); update(); } @@ -150,28 +150,25 @@ void BackgroundWidget::showLayerLast(LayeredWidget *n) { connect(n, SIGNAL(resized()), this, SLOT(update())); connect(n, SIGNAL(destroyed(QObject*)), this, SLOT(boxDestroyed(QObject*))); n->parentResized(); - n->animStep(1); + n->showStep(1); n->hide(); update(); } -bool BackgroundWidget::animStep(float64 ms) { +void BackgroundWidget::step_background(float64 ms, bool timer) { float64 dt = ms / (hiding ? st::layerHideDuration : st::layerSlideDuration); - w->animStep(dt); - bool res = true; + w->showStep(dt); if (dt >= 1) { - aBackground.finish(); + a_bg.finish(); if (hiding) { App::wnd()->layerFinishedHide(this); } - anim::stop(this); - res = false; + _a_background.stop(); if (App::app()) App::app()->mtpUnpause(); } else { - aBackground.update(dt, aBackgroundFunc); + a_bg.update(dt, anim::easeOutCirc); } - update(); - return res; + if (timer) update(); } void BackgroundWidget::boxDestroyed(QObject *obj) { @@ -196,8 +193,9 @@ BackgroundWidget::~BackgroundWidget() { StickerPreviewWidget::StickerPreviewWidget(QWidget *parent) : TWidget(parent) , a_shown(0, 0) -, _a_shown(animFunc(this, &StickerPreviewWidget::animStep_shown)) +, _a_shown(animation(this, &StickerPreviewWidget::step_shown)) , _doc(0) +, _gif(0) , _cacheStatus(CacheNotLoaded) { setAttribute(Qt::WA_TransparentForMouseEvents); } @@ -222,21 +220,20 @@ void StickerPreviewWidget::resizeEvent(QResizeEvent *e) { update(); } -bool StickerPreviewWidget::animStep_shown(float64 ms) { +void StickerPreviewWidget::step_shown(float64 ms, bool timer) { float64 dt = ms / st::stickerPreviewDuration; if (dt >= 1) { - a_shown.finish(); _a_shown.stop(); + a_shown.finish(); if (a_shown.current() < 0.5) hide(); } else { a_shown.update(dt, anim::linear); } - update(); - return true; + if (timer) update(); } void StickerPreviewWidget::showPreview(DocumentData *sticker) { - if (sticker && !sticker->sticker()) sticker = 0; + if (sticker && !sticker->isAnimation() && !sticker->sticker()) sticker = 0; if (sticker) { _cache = QPixmap(); if (isHidden() || _a_shown.animating()) { @@ -249,10 +246,17 @@ void StickerPreviewWidget::showPreview(DocumentData *sticker) { } else if (isHidden()) { return; } else { + if (_gif) _cache = currentImage(); a_shown.start(0); _a_shown.start(); } _doc = sticker; + if (_gif) { + if (gif()) { + delete _gif; + } + _gif = 0; + } _cacheStatus = CacheNotLoaded; } @@ -263,7 +267,10 @@ void StickerPreviewWidget::hidePreview() { QSize StickerPreviewWidget::currentDimensions() const { if (!_doc) return QSize(_cache.width() / cIntRetinaFactor(), _cache.height() / cIntRetinaFactor()); - QSize result(qMax(_doc->dimensions.width(), 1), qMax(_doc->dimensions.height(), 1)); + QSize result(qMax(convertScale(_doc->dimensions.width()), 1), qMax(convertScale(_doc->dimensions.height()), 1)); + if (gif() && _gif->ready()) { + result = QSize(qMax(convertScale(_gif->width()), 1), qMax(convertScale(_gif->height()), 1)); + } if (result.width() > st::maxStickerSize) { result.setHeight(qMax(qRound((st::maxStickerSize * result.height()) / result.width()), 1)); result.setWidth(st::maxStickerSize); @@ -276,32 +283,68 @@ QSize StickerPreviewWidget::currentDimensions() const { } QPixmap StickerPreviewWidget::currentImage() const { - if (_doc && _cacheStatus != CacheLoaded) { - bool already = !_doc->already().isEmpty(), hasdata = !_doc->data.isEmpty(); - if (!_doc->loader && _doc->status != FileFailed && !already && !hasdata) { - _doc->save(QString()); - } - if (_doc->sticker()->img->isNull() && (already || hasdata)) { - if (already) { - _doc->sticker()->img = ImagePtr(_doc->already()); - } else { - _doc->sticker()->img = ImagePtr(_doc->data); + if (_doc) { + if (_doc->sticker()) { + if (_cacheStatus != CacheLoaded) { + _doc->checkSticker(); + if (_doc->sticker()->img->isNull()) { + if (_cacheStatus != CacheThumbLoaded && _doc->thumb->loaded()) { + QSize s = currentDimensions(); + _cache = _doc->thumb->pixBlurred(s.width(), s.height()); + _cacheStatus = CacheThumbLoaded; + } + } else { + QSize s = currentDimensions(); + _cache = _doc->sticker()->img->pix(s.width(), s.height()); + _cacheStatus = CacheLoaded; + } } - } - if (_doc->sticker()->img->isNull()) { - if (_cacheStatus != CacheThumbLoaded) { + } else { + _doc->automaticLoad(0); + if (_doc->loaded()) { + if (!_gif && _gif != BadClipReader) { + StickerPreviewWidget *that = const_cast(this); + that->_gif = new ClipReader(_doc->location(), _doc->data(), func(that, &StickerPreviewWidget::clipCallback)); + if (gif()) _gif->setAutoplay(); + } + } + if (gif() && _gif->started()) { + QSize s = currentDimensions(); + return _gif->current(s.width(), s.height(), s.width(), s.height(), getms()); + } + if (_cacheStatus != CacheThumbLoaded && _doc->thumb->loaded()) { QSize s = currentDimensions(); _cache = _doc->thumb->pixBlurred(s.width(), s.height()); _cacheStatus = CacheThumbLoaded; } - } else { - QSize s = currentDimensions(); - _cache = _doc->sticker()->img->pix(s.width(), s.height()); - _cacheStatus = CacheLoaded; } } return _cache; } +void StickerPreviewWidget::clipCallback(ClipReaderNotification notification) { + switch (notification) { + case ClipReaderReinit: { + if (gif() && _gif->state() == ClipError) { + delete _gif; + _gif = BadClipReader; + } + + if (gif() && _gif->ready() && !_gif->started()) { + QSize s = currentDimensions(); + _gif->start(s.width(), s.height(), s.width(), s.height(), false); + } + + update(); + } break; + + case ClipReaderRepaint: { + if (gif() && !_gif->currentDisplayed()) { + update(); + } + } break; + } +} + StickerPreviewWidget::~StickerPreviewWidget() { } diff --git a/Telegram/SourceFiles/layerwidget.h b/Telegram/SourceFiles/layerwidget.h index 98d6eb3b32..151597edee 100644 --- a/Telegram/SourceFiles/layerwidget.h +++ b/Telegram/SourceFiles/layerwidget.h @@ -27,7 +27,7 @@ class LayeredWidget : public TWidget { public: - virtual void animStep(float64 ms) { + virtual void showStep(float64 ms) { } virtual void parentResized() = 0; virtual void startHide() { @@ -57,7 +57,7 @@ signals: }; -class BackgroundWidget : public TWidget, public Animated { +class BackgroundWidget : public TWidget { Q_OBJECT public: @@ -76,7 +76,7 @@ public: void replaceInner(LayeredWidget *n); void showLayerLast(LayeredWidget *n); - bool animStep(float64 ms); + void step_background(float64 ms, bool timer); bool canSetFocus() const; void setInnerFocus(); @@ -98,8 +98,9 @@ private: LayeredWidget *w; typedef QList HiddenLayers; HiddenLayers _hidden; - anim::fvalue aBackground; - anim::transition aBackgroundFunc; + anim::fvalue a_bg; + Animation _a_background; + bool hiding; BoxShadow shadow; @@ -115,7 +116,7 @@ public: void paintEvent(QPaintEvent *e); void resizeEvent(QResizeEvent *e); - bool animStep_shown(float64 ms); + void step_shown(float64 ms, bool timer); void showPreview(DocumentData *sticker); void hidePreview(); @@ -130,6 +131,12 @@ private: anim::fvalue a_shown; Animation _a_shown; DocumentData *_doc; + ClipReader *_gif; + bool gif() const { + return (!_gif || _gif == BadClipReader) ? false : true; + } + + void clipCallback(ClipReaderNotification notification); enum CacheStatus { CacheNotLoaded, diff --git a/Telegram/SourceFiles/layout.cpp b/Telegram/SourceFiles/layout.cpp new file mode 100644 index 0000000000..e987cb60e3 --- /dev/null +++ b/Telegram/SourceFiles/layout.cpp @@ -0,0 +1,2086 @@ +/* +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-2015 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "style.h" +#include "lang.h" + +#include "mainwidget.h" +#include "application.h" +#include "fileuploader.h" +#include "window.h" +#include "gui/filedialog.h" + +#include "boxes/addcontactbox.h" +#include "boxes/confirmbox.h" + +#include "audio.h" +#include "localstorage.h" + +TextParseOptions _textNameOptions = { + 0, // flags + 4096, // maxw + 1, // maxh + Qt::LayoutDirectionAuto, // lang-dependent +}; +TextParseOptions _textDlgOptions = { + 0, // flags + 0, // maxw is style-dependent + 1, // maxh + Qt::LayoutDirectionAuto, // lang-dependent +}; +TextParseOptions _historyTextOptions = { + TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText | TextParseMono, // flags + 0, // maxw + 0, // maxh + Qt::LayoutDirectionAuto, // dir +}; +TextParseOptions _historyBotOptions = { + TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands | TextParseMultiline | TextParseRichText | TextParseMono, // flags + 0, // maxw + 0, // maxh + Qt::LayoutDirectionAuto, // dir +}; +TextParseOptions _historyTextNoMonoOptions = { + TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText, // flags + 0, // maxw + 0, // maxh + Qt::LayoutDirectionAuto, // dir +}; +TextParseOptions _historyBotNoMonoOptions = { + TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands | TextParseMultiline | TextParseRichText, // flags + 0, // maxw + 0, // maxh + Qt::LayoutDirectionAuto, // dir +}; + +const TextParseOptions &itemTextOptions(History *h, PeerData *f) { + if ((h->peer->isUser() && h->peer->asUser()->botInfo) || (f->isUser() && f->asUser()->botInfo) || (h->peer->isChat() && h->peer->asChat()->botStatus >= 0) || (h->peer->isMegagroup() && h->peer->asChannel()->mgInfo->botStatus >= 0)) { + return _historyBotOptions; + } + return _historyTextOptions; +} + +const TextParseOptions &itemTextNoMonoOptions(History *h, PeerData *f) { + if ((h->peer->isUser() && h->peer->asUser()->botInfo) || (f->isUser() && f->asUser()->botInfo) || (h->peer->isChat() && h->peer->asChat()->botStatus >= 0) || (h->peer->isMegagroup() && h->peer->asChannel()->mgInfo->botStatus >= 0)) { + return _historyBotNoMonoOptions; + } + return _historyTextNoMonoOptions; +} + +QString formatSizeText(qint64 size) { + if (size >= 1024 * 1024) { // more than 1 mb + qint64 sizeTenthMb = (size * 10 / (1024 * 1024)); + return QString::number(sizeTenthMb / 10) + '.' + QString::number(sizeTenthMb % 10) + qsl(" MB"); + } + if (size >= 1024) { + qint64 sizeTenthKb = (size * 10 / 1024); + return QString::number(sizeTenthKb / 10) + '.' + QString::number(sizeTenthKb % 10) + qsl(" KB"); + } + return QString::number(size) + qsl(" B"); +} + +QString formatDownloadText(qint64 ready, qint64 total) { + QString readyStr, totalStr, mb; + if (total >= 1024 * 1024) { // more than 1 mb + qint64 readyTenthMb = (ready * 10 / (1024 * 1024)), totalTenthMb = (total * 10 / (1024 * 1024)); + readyStr = QString::number(readyTenthMb / 10) + '.' + QString::number(readyTenthMb % 10); + totalStr = QString::number(totalTenthMb / 10) + '.' + QString::number(totalTenthMb % 10); + mb = qsl("MB"); + } else if (total >= 1024) { + qint64 readyKb = (ready / 1024), totalKb = (total / 1024); + readyStr = QString::number(readyKb); + totalStr = QString::number(totalKb); + mb = qsl("KB"); + } else { + readyStr = QString::number(ready); + totalStr = QString::number(total); + mb = qsl("B"); + } + return lng_save_downloaded(lt_ready, readyStr, lt_total, totalStr, lt_mb, mb); +} + +QString formatDurationText(qint64 duration) { + qint64 hours = (duration / 3600), minutes = (duration % 3600) / 60, seconds = duration % 60; + return (hours ? QString::number(hours) + ':' : QString()) + (minutes >= 10 ? QString() : QString('0')) + QString::number(minutes) + ':' + (seconds >= 10 ? QString() : QString('0')) + QString::number(seconds); +} + +QString formatDurationAndSizeText(qint64 duration, qint64 size) { + return lng_duration_and_size(lt_duration, formatDurationText(duration), lt_size, formatSizeText(size)); +} + +QString formatGifAndSizeText(qint64 size) { + return lng_duration_and_size(lt_duration, qsl("GIF"), lt_size, formatSizeText(size)); +} + +QString formatPlayedText(qint64 played, qint64 duration) { + return lng_duration_played(lt_played, formatDurationText(played), lt_duration, formatDurationText(duration)); +} + +QString documentName(DocumentData *document) { + SongData *song = document->song(); + if (!song || (song->title.isEmpty() && song->performer.isEmpty())) { + return document->name.isEmpty() ? qsl("Unknown File") : document->name; + } + + if (song->performer.isEmpty()) return song->title; + + return song->performer + QString::fromUtf8(" \xe2\x80\x93 ") + (song->title.isEmpty() ? qsl("Unknown Track") : song->title); +} + +int32 documentColorIndex(DocumentData *document, QString &ext) { + int32 colorIndex = 0; + + QString name = document ? (document->name.isEmpty() ? (document->sticker() ? lang(lng_in_dlg_sticker) : qsl("Unknown File")) : document->name) : lang(lng_message_empty); + name = name.toLower(); + int32 lastDot = name.lastIndexOf('.'); + QString mime = document ? document->mime.toLower() : QString(); + if (name.endsWith(qstr(".doc")) || + name.endsWith(qstr(".txt")) || + name.endsWith(qstr(".psd")) || + mime.startsWith(qstr("text/")) + ) { + colorIndex = 0; + } else if ( + name.endsWith(qstr(".xls")) || + name.endsWith(qstr(".csv")) + ) { + colorIndex = 1; + } else if ( + name.endsWith(qstr(".pdf")) || + name.endsWith(qstr(".ppt")) || + name.endsWith(qstr(".key")) + ) { + colorIndex = 2; + } else if ( + name.endsWith(qstr(".zip")) || + name.endsWith(qstr(".rar")) || + name.endsWith(qstr(".ai")) || + name.endsWith(qstr(".mp3")) || + name.endsWith(qstr(".mov")) || + name.endsWith(qstr(".avi")) + ) { + colorIndex = 3; + } else { + QChar ch = (lastDot >= 0 && lastDot + 1 < name.size()) ? name.at(lastDot + 1) : (name.isEmpty() ? (mime.isEmpty() ? '0' : mime.at(0)) : name.at(0)); + colorIndex = (ch.unicode() % 4); + } + + ext = document ? ((lastDot < 0 || lastDot + 2 > name.size()) ? name : name.mid(lastDot + 1)) : QString(); + + return colorIndex; +} + +style::color documentColor(int32 colorIndex) { + static style::color colors[] = { st::msgFileBlueColor, st::msgFileGreenColor, st::msgFileRedColor, st::msgFileYellowColor }; + return colors[colorIndex & 3]; +} + +style::color documentDarkColor(int32 colorIndex) { + static style::color colors[] = { st::msgFileBlueDark, st::msgFileGreenDark, st::msgFileRedDark, st::msgFileYellowDark }; + return colors[colorIndex & 3]; +} + +style::color documentOverColor(int32 colorIndex) { + static style::color colors[] = { st::msgFileBlueOver, st::msgFileGreenOver, st::msgFileRedOver, st::msgFileYellowOver }; + return colors[colorIndex & 3]; +} + +style::color documentSelectedColor(int32 colorIndex) { + static style::color colors[] = { st::msgFileBlueSelected, st::msgFileGreenSelected, st::msgFileRedSelected, st::msgFileYellowSelected }; + return colors[colorIndex & 3]; +} + +style::sprite documentCorner(int32 colorIndex) { + static style::sprite corners[] = { st::msgFileBlue, st::msgFileGreen, st::msgFileRed, st::msgFileYellow }; + return corners[colorIndex & 3]; +} + +RoundCorners documentCorners(int32 colorIndex) { + return RoundCorners(DocBlueCorners + (colorIndex & 3)); +} + +void LayoutRadialProgressItem::linkOver(const TextLinkPtr &lnk) { + if (lnk == _openl || lnk == _savel || lnk == _cancell) { + a_iconOver.start(1); + _a_iconOver.start(); + } +} + +void LayoutRadialProgressItem::linkOut(const TextLinkPtr &lnk) { + if (lnk == _openl || lnk == _savel || lnk == _cancell) { + a_iconOver.start(0); + _a_iconOver.start(); + } +} + +void LayoutRadialProgressItem::setLinks(ITextLink *openl, ITextLink *savel, ITextLink *cancell) { + _openl.reset(openl); + _savel.reset(savel); + _cancell.reset(cancell); +} + +void LayoutRadialProgressItem::step_iconOver(float64 ms, bool timer) { + float64 dt = ms / st::msgFileOverDuration; + if (dt >= 1) { + a_iconOver.finish(); + _a_iconOver.stop(); + } else if (!timer) { + a_iconOver.update(dt, anim::linear); + } + if (timer && iconAnimated()) { + Ui::repaintHistoryItem(_parent); + } +} + +void LayoutRadialProgressItem::step_radial(uint64 ms, bool timer) { + if (timer) { + Ui::repaintHistoryItem(_parent); + } else { + _radial->update(dataProgress(), dataFinished(), ms); + if (!_radial->animating()) { + checkRadialFinished(); + } + } +} + +void LayoutRadialProgressItem::ensureRadial() const { + if (!_radial) { + _radial = new RadialAnimation(animation(const_cast(this), &LayoutRadialProgressItem::step_radial)); + } +} + +void LayoutRadialProgressItem::checkRadialFinished() { + if (_radial && !_radial->animating() && dataLoaded()) { + delete _radial; + _radial = 0; + } +} + +LayoutRadialProgressItem::~LayoutRadialProgressItem() { + deleteAndMark(_radial); +} + +void LayoutAbstractFileItem::setStatusSize(int32 newSize, int32 fullSize, int32 duration, qint64 realDuration) const { + _statusSize = newSize; + if (_statusSize == FileStatusSizeReady) { + _statusText = (duration >= 0) ? formatDurationAndSizeText(duration, fullSize) : (duration < -1 ? formatGifAndSizeText(fullSize) : formatSizeText(fullSize)); + } else if (_statusSize == FileStatusSizeLoaded) { + _statusText = (duration >= 0) ? formatDurationText(duration) : (duration < -1 ? qsl("GIF") : formatSizeText(fullSize)); + } else if (_statusSize == FileStatusSizeFailed) { + _statusText = lang(lng_attach_failed); + } else if (_statusSize >= 0) { + _statusText = formatDownloadText(_statusSize, fullSize); + } else { + _statusText = formatPlayedText(-_statusSize - 1, realDuration); + } +} + +LayoutOverviewDate::LayoutOverviewDate(const QDate &date, bool month) + : _date(date) + , _text(month ? langMonthFull(date) : langDayOfMonthFull(date)) { +} + +void LayoutOverviewDate::initDimensions() { + _maxw = st::normalFont->width(_text); + _minh = st::linksDateMargin.top() + st::normalFont->height + st::linksDateMargin.bottom() + st::linksBorder; +} + +void LayoutOverviewDate::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { + if (clip.intersects(QRect(0, st::linksDateMargin.top(), _width, st::normalFont->height))) { + p.setPen(st::linksDateColor); + p.setFont(st::semiboldFont); + p.drawTextLeft(0, st::linksDateMargin.top(), _width, _text); + } +} + +LayoutOverviewPhoto::LayoutOverviewPhoto(PhotoData *photo, HistoryItem *parent) : LayoutMediaItem(parent) +, _data(photo) +, _link(new PhotoLink(photo)) +, _goodLoaded(false) { + +} + +void LayoutOverviewPhoto::initDimensions() { + _maxw = 2 * st::overviewPhotoMinSize; + _minh = _maxw; +} + +int32 LayoutOverviewPhoto::resizeGetHeight(int32 width) { + width = qMin(width, _maxw); + if (width != _width || width != _height) { + _width = qMin(width, _maxw); + _height = _width; + } + return _height; +} + +void LayoutOverviewPhoto::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { + bool good = _data->loaded(); + if (!good) { + _data->medium->automaticLoad(_parent); + good = _data->medium->loaded(); + } + if ((good && !_goodLoaded) || _pix.width() != _width * cIntRetinaFactor()) { + _goodLoaded = good; + + int32 size = _width * cIntRetinaFactor(); + if (_goodLoaded || _data->thumb->loaded()) { + QImage img = (_data->loaded() ? _data->full : (_data->medium->loaded() ? _data->medium : _data->thumb))->pix().toImage(); + if (!_goodLoaded) { + img = imageBlur(img); + } + if (img.width() == img.height()) { + if (img.width() != size) { + img = img.scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + } + } else if (img.width() > img.height()) { + img = img.copy((img.width() - img.height()) / 2, 0, img.height(), img.height()).scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + } else { + img = img.copy(0, (img.height() - img.width()) / 2, img.width(), img.width()).scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + } + img.setDevicePixelRatio(cRetinaFactor()); + _data->forget(); + + _pix = QPixmap::fromImage(img, Qt::ColorOnly); + } else if (!_pix.isNull()) { + _pix = QPixmap(); + } + } + + if (_pix.isNull()) { + p.fillRect(0, 0, _width, _height, st::overviewPhotoBg); + } else { + p.drawPixmap(0, 0, _pix); + } + + if (selection == FullSelection) { + p.fillRect(QRect(0, 0, _width, _height), st::overviewPhotoSelectOverlay); + p.drawSprite(QPoint(rtl() ? 0 : (_width - st::overviewPhotoChecked.pxWidth()), _height - st::overviewPhotoChecked.pxHeight()), st::overviewPhotoChecked); + } else if (context->selecting) { + p.drawSprite(QPoint(rtl() ? 0 : (_width - st::overviewPhotoCheck.pxWidth()), _height - st::overviewPhotoCheck.pxHeight()), st::overviewPhotoCheck); + } +} + +void LayoutOverviewPhoto::getState(TextLinkPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const { + if (hasPoint(x, y)) { + link = _link; + } +} + +LayoutOverviewVideo::LayoutOverviewVideo(VideoData *video, HistoryItem *parent) : LayoutAbstractFileItem(parent) +, _data(video) +, _duration(formatDurationText(_data->duration)) +, _thumbLoaded(false) { + setLinks(new VideoOpenLink(_data), new VideoSaveLink(_data), new VideoCancelLink(_data)); +} + +void LayoutOverviewVideo::initDimensions() { + _maxw = 2 * st::minPhotoSize; + _minh = _maxw; +} + +int32 LayoutOverviewVideo::resizeGetHeight(int32 width) { + _width = qMin(width, _maxw); + _height = _width; + return _height; +} + +void LayoutOverviewVideo::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { + bool selected = (selection == FullSelection), thumbLoaded = _data->thumb->loaded(); + + _data->automaticLoad(_parent); + bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); + if (displayLoading) { + ensureRadial(); + if (!_radial->animating()) { + _radial->start(_data->progress()); + } + } + updateStatusText(); + bool radial = isRadialAnimation(context->ms); + + if ((thumbLoaded && !_thumbLoaded) || (_pix.width() != _width * cIntRetinaFactor())) { + _thumbLoaded = thumbLoaded; + + if (_thumbLoaded && !_data->thumb->isNull()) { + int32 size = _width * cIntRetinaFactor(); + QImage img = _data->thumb->pix().toImage(); + img = imageBlur(img); + if (img.width() == img.height()) { + if (img.width() != size) { + img = img.scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + } + } else if (img.width() > img.height()) { + img = img.copy((img.width() - img.height()) / 2, 0, img.height(), img.height()).scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + } else { + img = img.copy(0, (img.height() - img.width()) / 2, img.width(), img.width()).scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + } + img.setDevicePixelRatio(cRetinaFactor()); + _data->forget(); + + _pix = QPixmap::fromImage(img, Qt::ColorOnly); + } else if (!_pix.isNull()) { + _pix = QPixmap(); + } + } + + if (_pix.isNull()) { + p.fillRect(0, 0, _width, _height, st::overviewPhotoBg); + } else { + p.drawPixmap(0, 0, _pix); + } + + if (selected) { + p.fillRect(QRect(0, 0, _width, _height), st::overviewPhotoSelectOverlay); + } + + if (!selected && !context->selecting && !loaded) { + if (clip.intersects(QRect(0, _height - st::normalFont->height, _width, st::normalFont->height))) { + int32 statusX = st::msgDateImgPadding.x(), statusY = _height - st::normalFont->height - st::msgDateImgPadding.y(); + int32 statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x(); + int32 statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y(); + statusX = _width - statusW + statusX; + p.fillRect(rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, _width), selected ? st::msgDateImgBgSelected : st::msgDateImgBg); + p.setFont(st::normalFont); + p.setPen(st::white); + p.drawTextLeft(statusX, statusY, _width, _statusText, statusW - 2 * st::msgDateImgPadding.x()); + } + } + if (clip.intersects(QRect(0, 0, _width, st::normalFont->height))) { + int32 statusX = st::msgDateImgPadding.x(), statusY = st::msgDateImgPadding.y(); + int32 statusW = st::normalFont->width(_duration) + 2 * st::msgDateImgPadding.x(); + int32 statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y(); + p.fillRect(rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, _width), selected ? st::msgDateImgBgSelected : st::msgDateImgBg); + p.setFont(st::normalFont); + p.setPen(st::white); + p.drawTextLeft(statusX, statusY, _width, _duration, statusW - 2 * st::msgDateImgPadding.x()); + } + + QRect inner((_width - st::msgFileSize) / 2, (_height - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); + if (clip.intersects(inner)) { + p.setPen(Qt::NoPen); + if (selected) { + p.setBrush(st::msgDateImgBgSelected); + } else if (_a_iconOver.animating()) { + _a_iconOver.step(context->ms); + float64 over = a_iconOver.current(); + p.setOpacity((st::msgDateImgBg->c.alphaF() * (1 - over)) + (st::msgDateImgBgOver->c.alphaF() * over)); + p.setBrush(st::black); + } else { + bool over = textlnkDrawOver(loaded ? _openl : (_data->loading() ? _cancell : _savel)); + p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); + } + + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.drawEllipse(inner); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + + p.setOpacity((radial && loaded) ? _radial->opacity() : 1); + style::sprite icon; + if (radial) { + icon = (selected ? st::msgFileInCancelSelected : st::msgFileInCancel); + } else if (loaded) { + icon = (selected ? st::msgFileInPlaySelected : st::msgFileInPlay); + } else { + icon = (selected ? st::msgFileInDownloadSelected : st::msgFileInDownload); + } + p.drawSpriteCenter(inner, icon); + if (radial) { + p.setOpacity(1); + QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); + _radial->draw(p, rinner, st::msgFileRadialLine, selected ? st::msgInBgSelected : st::msgInBg); + } + } + + if (selected) { + p.drawSprite(QPoint(rtl() ? 0 : (_width - st::overviewPhotoChecked.pxWidth()), _height - st::overviewPhotoChecked.pxHeight()), st::overviewPhotoChecked); + } else if (context->selecting) { + p.drawSprite(QPoint(rtl() ? 0 : (_width - st::overviewPhotoCheck.pxWidth()), _height - st::overviewPhotoCheck.pxHeight()), st::overviewPhotoCheck); + } +} + +void LayoutOverviewVideo::getState(TextLinkPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const { + bool loaded = _data->loaded(); + + if (hasPoint(x, y)) { + link = loaded ? _openl : (_data->loading() ? _cancell : _savel); + } +} + +void LayoutOverviewVideo::updateStatusText() const { + bool showPause = false; + int32 statusSize = 0; + if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { + statusSize = FileStatusSizeFailed; + } else if (_data->status == FileUploading) { + statusSize = _data->uploadOffset; + } else if (_data->loading()) { + statusSize = _data->loadOffset(); + } else if (!_data->already().isEmpty()) { + statusSize = FileStatusSizeLoaded; + } else { + statusSize = FileStatusSizeReady; + } + if (statusSize != _statusSize) { + int32 status = statusSize, size = _data->size; + if (statusSize >= 0 && statusSize < 0x7F000000) { + size = status; + status = FileStatusSizeReady; + } + setStatusSize(status, size, -1, 0); + _statusSize = statusSize; + } +} + +LayoutOverviewAudio::LayoutOverviewAudio(AudioData *audio, HistoryItem *parent) : LayoutAbstractFileItem(parent) +, _data(audio) +, _namel(new AudioOpenLink(_data)) { + setLinks(new AudioOpenLink(_data), new AudioOpenLink(_data), new AudioCancelLink(_data)); + updateName(); + QString d = textcmdLink(1, textRichPrepare(langDateTime(date(_data->date)))); + TextParseOptions opts = { TextParseRichText, 0, 0, Qt::LayoutDirectionAuto }; + _details.setText(st::normalFont, lng_date_and_duration(lt_date, d, lt_duration, formatDurationText(_data->duration)), opts); + _details.setLink(1, TextLinkPtr(new MessageLink(parent))); +} + +void LayoutOverviewAudio::initDimensions() { + _maxw = st::profileMaxWidth; + _minh = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() + st::lineWidth; +} + +void LayoutOverviewAudio::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { + bool selected = (selection == FullSelection); + + _data->automaticLoad(_parent); + bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); + + if (displayLoading) { + ensureRadial(); + if (!_radial->animating()) { + _radial->start(_data->progress()); + } + } + bool showPause = updateStatusText(); + int32 nameVersion = _parent->from()->nameVersion; + if (HistoryForwarded *fwd = _parent->toHistoryForwarded()) nameVersion = fwd->fromForwarded()->nameVersion; + if (nameVersion > _nameVersion) { + updateName(); + } + bool radial = isRadialAnimation(context->ms); + + int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, datetop = -1; + + nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); + nameright = st::msgFilePadding.left(); + nametop = st::msgFileNameTop; + statustop = st::msgFileStatusTop; + + if (selected) { + p.fillRect(clip.intersected(QRect(0, 0, _width, _height)), st::msgInBgSelected); + } + + QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top(), st::msgFileSize, st::msgFileSize, _width)); + if (clip.intersects(inner)) { + p.setPen(Qt::NoPen); + if (selected) { + p.setBrush(st::msgFileInBgSelected); + } else if (_a_iconOver.animating()) { + _a_iconOver.step(context->ms); + float64 over = a_iconOver.current(); + p.setBrush(style::interpolate(st::msgFileInBg, st::msgFileInBgOver, over)); + } else { + bool over = textlnkDrawOver(loaded ? _openl : (_data->loading() ? _cancell : _openl)); + p.setBrush(over ? st::msgFileInBgOver : st::msgFileInBg); + } + + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.drawEllipse(inner); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + + if (radial) { + QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); + style::color bg(selected ? st::msgInBgSelected : st::msgInBg); + _radial->draw(p, rinner, st::msgFileRadialLine, bg); + } + + style::sprite icon; + if (showPause) { + icon = selected ? st::msgFileInPauseSelected : st::msgFileInPause; + } else if (_statusSize < 0 || _statusSize == FileStatusSizeLoaded) { + icon = selected ? st::msgFileInPlaySelected : st::msgFileInPlay; + } else if (_data->loading()) { + icon = selected ? st::msgFileInCancelSelected : st::msgFileInCancel; + } else { + icon = selected ? st::msgFileInDownloadSelected : st::msgFileInDownload; + } + p.drawSpriteCenter(inner, icon); + } + + int32 namewidth = _width - nameleft - nameright; + + if (clip.intersects(rtlrect(nameleft, nametop, namewidth, st::semiboldFont->height, _width))) { + p.setPen(st::black); + _name.drawLeftElided(p, nameleft, nametop, namewidth, _width); + } + + if (clip.intersects(rtlrect(nameleft, statustop, namewidth, st::normalFont->height, _width))) { + p.setFont(st::normalFont); + p.setPen(selected ? st::mediaInFgSelected : st::mediaInFg); + int32 unreadx = nameleft; + if (_statusSize == FileStatusSizeLoaded || _statusSize == FileStatusSizeReady) { + textstyleSet(&(selected ? st::mediaInStyleSelected : st::mediaInStyle)); + _details.drawLeftElided(p, nameleft, statustop, namewidth, _width); + textstyleRestore(); + unreadx += _details.maxWidth(); + } else { + int32 statusw = st::normalFont->width(_statusText); + p.drawTextLeft(nameleft, statustop, _width, _statusText, statusw); + unreadx += statusw; + } + if (_parent->isMediaUnread() && unreadx + st::mediaUnreadSkip + st::mediaUnreadSize <= _width) { + p.setPen(Qt::NoPen); + p.setBrush(selected ? st::msgFileInBgSelected : st::msgFileInBg); + + p.setRenderHint(QPainter::HighQualityAntialiasing, true); + p.drawEllipse(rtlrect(unreadx + st::mediaUnreadSkip, statustop + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, _width)); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + } + } +} + +void LayoutOverviewAudio::getState(TextLinkPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const { + bool loaded = _data->loaded(); + + bool showPause = updateStatusText(); + + int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, datetop = 0; + + nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); + nameright = st::msgFilePadding.left(); + nametop = st::msgFileNameTop; + statustop = st::msgFileStatusTop; + + QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top(), st::msgFileSize, st::msgFileSize, _width)); + if (inner.contains(x, y)) { + link = loaded ? _openl : ((_data->loading() || _data->status == FileUploading) ? _cancell : _openl); + return; + } + if (rtlrect(nameleft, statustop, _width - nameleft - nameright, st::normalFont->height, _width).contains(x, y)) { + if (_statusSize == FileStatusSizeLoaded || _statusSize == FileStatusSizeReady) { + bool inText = false; + _details.getStateLeft(link, inText, x - nameleft, y - statustop, _width, _width); + cursor = inText ? HistoryInTextCursorState : HistoryDefaultCursorState; + } + } + if (hasPoint(x, y) && !link && !_data->loading()) { + link = _namel; + return; + } +} + +void LayoutOverviewAudio::updateName() const { + int32 version = 0; + if (HistoryForwarded *fwd = _parent->toHistoryForwarded()) { + _name.setText(st::semiboldFont, lang(lng_forwarded_from) + ' ' + App::peerName(fwd->fromForwarded()), _textNameOptions); + version = fwd->fromForwarded()->nameVersion; + } else { + _name.setText(st::semiboldFont, App::peerName(_parent->from()), _textNameOptions); + version = _parent->from()->nameVersion; + } + _nameVersion = version; +} + +bool LayoutOverviewAudio::updateStatusText() const { + bool showPause = false; + int32 statusSize = 0, realDuration = 0; + if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { + statusSize = FileStatusSizeFailed; + } else if (_data->loaded()) { + AudioMsgId playing; + AudioPlayerState playingState = AudioPlayerStopped; + int64 playingPosition = 0, playingDuration = 0; + int32 playingFrequency = 0; + if (audioPlayer()) { + audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); + } + + if (playing.msgId == _parent->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { + statusSize = -1 - (playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)); + realDuration = playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency); + showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); + } else { + statusSize = FileStatusSizeLoaded; + } + } else { + statusSize = FileStatusSizeReady; + } + if (statusSize != _statusSize) { + setStatusSize(statusSize, _data->size, _data->duration, realDuration); + } + return showPause; +} + +LayoutOverviewDocument::LayoutOverviewDocument(DocumentData *document, HistoryItem *parent) : LayoutAbstractFileItem(parent) +, _data(document) +, _msgl(new MessageLink(parent)) +, _namel(new DocumentOpenLink(_data)) +, _thumbForLoaded(false) +, _name(documentName(_data)) +, _date(langDateTime(date(_data->date))) +, _namew(st::semiboldFont->width(_name)) +, _datew(st::normalFont->width(_date)) +, _colorIndex(documentColorIndex(_data, _ext)) { + setLinks(new DocumentOpenLink(_data), new DocumentSaveLink(_data), new DocumentCancelLink(_data)); + + setStatusSize(FileStatusSizeReady, _data->size, _data->song() ? _data->song()->duration : -1, 0); + + if (withThumb()) { + _data->thumb->load(); + int32 tw = _data->thumb->width(), th = _data->thumb->height(); + if (tw > th) { + _thumbw = (tw * st::overviewFileSize) / th; + } else { + _thumbw = st::overviewFileSize; + } + } else { + _thumbw = 0; + } + + _extw = st::semiboldFont->width(_ext); + if (_extw > st::overviewFileSize - st::msgFileExtPadding * 2) { + _ext = st::semiboldFont->elided(_ext, st::overviewFileSize - st::msgFileExtPadding * 2, Qt::ElideMiddle); + _extw = st::semiboldFont->width(_ext); + } +} + +void LayoutOverviewDocument::initDimensions() { + _maxw = st::profileMaxWidth; + if (_data->song()) { + _minh = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom(); + } else { + _minh = st::overviewFilePadding.top() + st::overviewFileSize + st::overviewFilePadding.bottom() + st::lineWidth; + } +} + +void LayoutOverviewDocument::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { + bool selected = (selection == FullSelection); + + _data->automaticLoad(_parent); + bool loaded = _data->loaded() || Local::willStickerImageLoad(mediaKey(DocumentFileLocation, _data->dc, _data->id)), displayLoading = _data->displayLoading(); + + if (displayLoading) { + ensureRadial(); + if (!_radial->animating()) { + _radial->start(_data->progress()); + } + } + bool showPause = updateStatusText(); + bool radial = isRadialAnimation(context->ms); + + int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, datetop = -1; + bool wthumb = withThumb(); + + if (_data->song()) { + nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); + nameright = st::msgFilePadding.left(); + nametop = st::msgFileNameTop; + statustop = st::msgFileStatusTop; + + if (selected) { + p.fillRect(QRect(0, 0, _width, _height), st::msgInBgSelected); + } + + QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top(), st::msgFileSize, st::msgFileSize, _width)); + if (clip.intersects(inner)) { + p.setPen(Qt::NoPen); + if (selected) { + p.setBrush(st::msgFileInBgSelected); + } else if (_a_iconOver.animating()) { + _a_iconOver.step(context->ms); + float64 over = a_iconOver.current(); + p.setBrush(style::interpolate(st::msgFileInBg, st::msgFileInBgOver, over)); + } else { + bool over = textlnkDrawOver(loaded ? _openl : (_data->loading() ? _cancell : _openl)); + p.setBrush(over ? st::msgFileInBgOver : st::msgFileInBg); + } + + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.drawEllipse(inner); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + + if (radial) { + QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); + style::color bg(selected ? st::msgInBgSelected : st::msgInBg); + _radial->draw(p, rinner, st::msgFileRadialLine, bg); + } + + style::sprite icon; + if (showPause) { + icon = selected ? st::msgFileInPauseSelected : st::msgFileInPause; + } else if (loaded) { + icon = selected ? st::msgFileInPlaySelected : st::msgFileInPlay; + } else if (_data->loading()) { + icon = selected ? st::msgFileInCancelSelected : st::msgFileInCancel; + } else { + icon = selected ? st::msgFileInDownloadSelected : st::msgFileInDownload; + } + p.drawSpriteCenter(inner, icon); + } + } else { + nameleft = st::overviewFileSize + st::overviewFilePadding.right(); + nametop = st::linksBorder + st::overviewFileNameTop; + statustop = st::linksBorder + st::overviewFileStatusTop; + datetop = st::linksBorder + st::overviewFileDateTop; + + const OverviewPaintContext *pcontext = context->toOverviewPaintContext(); + t_assert(pcontext != 0); + QRect border(rtlrect(nameleft, 0, _width - nameleft, st::linksBorder, _width)); + if (!pcontext->isAfterDate && clip.intersects(border)) { + p.fillRect(clip.intersected(border), st::linksBorderFg); + } + + QRect rthumb(rtlrect(0, st::linksBorder + st::overviewFilePadding.top(), st::overviewFileSize, st::overviewFileSize, _width)); + if (clip.intersects(rthumb)) { + if (wthumb) { + if (_data->thumb->loaded()) { + if (_thumb.isNull() || loaded != _thumbForLoaded) { + _thumbForLoaded = loaded; + _thumb = _data->thumb->pixNoCache(_thumbw, 0, true, !_thumbForLoaded, false, st::overviewFileSize, st::overviewFileSize); + } + p.drawPixmap(rthumb.topLeft(), _thumb); + } else { + p.fillRect(rthumb, st::black); + } + } else { + p.fillRect(rthumb, documentColor(_colorIndex)); + if (!radial && loaded && !_ext.isEmpty()) { + p.setFont(st::semiboldFont); + p.setPen(st::white); + p.drawText(rthumb.left() + (rthumb.width() - _extw) / 2, rthumb.top() + st::msgFileExtTop + st::semiboldFont->ascent, _ext); + } + } + if (selected) { + p.fillRect(rthumb, textstyleCurrent()->selectOverlay); + } + + if (radial || (!loaded && !_data->loading())) { + QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); + if (clip.intersects(inner)) { + float64 radialOpacity = (radial && loaded && !_data->uploading()) ? _radial->opacity() : 1; + p.setPen(Qt::NoPen); + if (selected) { + p.setBrush(wthumb ? st::msgDateImgBgSelected : documentSelectedColor(_colorIndex)); + } else if (_a_iconOver.animating()) { + _a_iconOver.step(context->ms); + float64 over = a_iconOver.current(); + if (wthumb) { + p.setOpacity((st::msgDateImgBg->c.alphaF() * (1 - over)) + (st::msgDateImgBgOver->c.alphaF() * over)); + p.setBrush(st::black); + } else { + p.setBrush(style::interpolate(documentDarkColor(_colorIndex), documentOverColor(_colorIndex), over)); + } + } else { + bool over = textlnkDrawOver(_data->loading() ? _cancell : _savel); + p.setBrush(over ? (wthumb ? st::msgDateImgBgOver : documentOverColor(_colorIndex)) : (wthumb ? st::msgDateImgBg : documentDarkColor(_colorIndex))); + } + p.setOpacity(radialOpacity * p.opacity()); + + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.drawEllipse(inner); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + + p.setOpacity(radialOpacity); + style::sprite icon; + if (loaded || _data->loading()) { + icon = (selected ? st::msgFileInCancelSelected : st::msgFileInCancel); + } else { + icon = (selected ? st::msgFileInDownloadSelected : st::msgFileInDownload); + } + p.drawSpriteCenter(inner, icon); + if (radial) { + p.setOpacity(1); + + QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); + _radial->draw(p, rinner, st::msgFileRadialLine, selected ? st::msgInBgSelected : st::msgInBg); + } + } + } + if (selected || context->selecting) { + QRect check(rthumb.topLeft() + QPoint(rtl() ? 0 : (rthumb.width() - st::defaultCheckbox.diameter), rthumb.height() - st::defaultCheckbox.diameter), QSize(st::defaultCheckbox.diameter, st::defaultCheckbox.diameter)); + p.fillRect(check, selected ? st::overviewFileChecked : st::overviewFileCheck); + p.drawSpriteCenter(check, st::defaultCheckbox.checkIcon); + } + } + } + + int32 namewidth = _width - nameleft - nameright; + + if (clip.intersects(rtlrect(nameleft, nametop, qMin(namewidth, _namew), st::semiboldFont->height, _width))) { + p.setFont(st::semiboldFont); + p.setPen(st::black); + if (namewidth < _namew) { + p.drawTextLeft(nameleft, nametop, _width, st::semiboldFont->elided(_name, namewidth)); + } else { + p.drawTextLeft(nameleft, nametop, _width, _name, _namew); + } + } + + if (clip.intersects(rtlrect(nameleft, statustop, namewidth, st::normalFont->height, _width))) { + p.setFont(st::normalFont); + p.setPen(st::mediaInFg); + p.drawTextLeft(nameleft, statustop, _width, _statusText); + } + if (datetop >= 0 && clip.intersects(rtlrect(nameleft, datetop, _datew, st::normalFont->height, _width))) { + p.setFont(textlnkDrawOver(_msgl) ? st::normalFont->underline() : st::normalFont); + p.setPen(st::mediaInFg); + p.drawTextLeft(nameleft, datetop, _width, _date, _datew); + } +} + +void LayoutOverviewDocument::getState(TextLinkPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const { + bool loaded = _data->loaded() || Local::willStickerImageLoad(mediaKey(DocumentFileLocation, _data->dc, _data->id)); + + bool showPause = updateStatusText(); + + int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, datetop = 0; + bool wthumb = withThumb(); + + if (_data->song()) { + nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); + nameright = st::msgFilePadding.left(); + nametop = st::msgFileNameTop; + statustop = st::msgFileStatusTop; + + QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top(), st::msgFileSize, st::msgFileSize, _width)); + if (inner.contains(x, y)) { + link = loaded ? _openl : ((_data->loading() || _data->status == FileUploading) ? _cancell : _openl); + return; + } + if (hasPoint(x, y) && !_data->loading()) { + link = _namel; + return; + } + } else { + nameleft = st::overviewFileSize + st::overviewFilePadding.right(); + nametop = st::linksBorder + st::overviewFileNameTop; + statustop = st::linksBorder + st::overviewFileStatusTop; + datetop = st::linksBorder + st::overviewFileDateTop; + + QRect rthumb(rtlrect(0, st::linksBorder + st::overviewFilePadding.top(), st::overviewFileSize, st::overviewFileSize, _width)); + + if (rthumb.contains(x, y)) { + link = loaded ? _openl : ((_data->loading() || _data->status == FileUploading) ? _cancell : _savel); + return; + } + + if (_data->status != FileUploadFailed) { + if (rtlrect(nameleft, datetop, _datew, st::normalFont->height, _width).contains(x, y)) { + link = _msgl; + return; + } + } + if (!_data->loading() && _data->access) { + if (loaded && rtlrect(0, st::linksBorder, nameleft, _height - st::linksBorder, _width).contains(x, y)) { + link = _namel; + return; + } + if (rtlrect(nameleft, nametop, qMin(_width - nameleft - nameright, _namew), st::semiboldFont->height, _width).contains(x, y)) { + link = _namel; + return; + } + } + } +} + +bool LayoutOverviewDocument::updateStatusText() const { + bool showPause = false; + int32 statusSize = 0, realDuration = 0; + if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { + statusSize = FileStatusSizeFailed; + } else if (_data->status == FileUploading) { + statusSize = _data->uploadOffset; + } else if (_data->loading()) { + statusSize = _data->loadOffset(); + } else if (_data->loaded()) { + if (_data->song()) { + SongMsgId playing; + AudioPlayerState playingState = AudioPlayerStopped; + int64 playingPosition = 0, playingDuration = 0; + int32 playingFrequency = 0; + if (audioPlayer()) { + audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); + } + + if (playing.msgId == _parent->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { + statusSize = -1 - (playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)); + realDuration = playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency); + showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); + } else { + statusSize = FileStatusSizeLoaded; + } + if (!showPause && playing.msgId == _parent->fullId() && App::main() && App::main()->player()->seekingSong(playing)) { + showPause = true; + } + } else { + statusSize = FileStatusSizeLoaded; + } + } else { + statusSize = FileStatusSizeReady; + } + if (statusSize != _statusSize) { + setStatusSize(statusSize, _data->size, _data->song() ? _data->song()->duration : -1, realDuration); + } + return showPause; +} + +namespace { + ITextLink *linkFromUrl(const QString &url) { + int32 at = url.indexOf('@'), slash = url.indexOf('/'); + if ((at > 0) && (slash < 0 || slash > at)) { + return new EmailLink(url); + } + return new TextLink(url); + } +} + +LayoutOverviewLink::LayoutOverviewLink(HistoryMedia *media, HistoryItem *parent) : LayoutMediaItem(parent) +, _titlew(0) +, _page(0) +, _pixw(0) +, _pixh(0) +, _text(st::msgMinWidth) { + QString text = _parent->originalText(); + EntitiesInText entities = _parent->originalEntities(); + + int32 from = 0, till = text.size(), lnk = entities.size(); + for (int32 i = 0; i < lnk; ++i) { + if (entities[i].type != EntityInTextUrl && entities[i].type != EntityInTextCustomUrl && entities[i].type != EntityInTextEmail) { + continue; + } + QString u = entities[i].text, t = text.mid(entities[i].offset, entities[i].length); + _links.push_back(Link(u.isEmpty() ? t : u, t)); + } + while (lnk > 0 && till > from) { + --lnk; + if (entities[lnk].type != EntityInTextUrl && entities[lnk].type != EntityInTextCustomUrl && entities[lnk].type != EntityInTextEmail) { + ++lnk; + break; + } + int32 afterLinkStart = entities[lnk].offset + entities[lnk].length; + if (till > afterLinkStart) { + if (!QRegularExpression(qsl("^[,.\\s_=+\\-;:`'\"\\(\\)\\[\\]\\{\\}<>*&^%\\$#@!\\\\/]+$")).match(text.mid(afterLinkStart, till - afterLinkStart)).hasMatch()) { + ++lnk; + break; + } + } + till = entities[lnk].offset; + } + if (!lnk) { + if (QRegularExpression(qsl("^[,.\\s\\-;:`'\"\\(\\)\\[\\]\\{\\}<>*&^%\\$#@!\\\\/]+$")).match(text.mid(from, till - from)).hasMatch()) { + till = from; + } + } + + _page = (media && media->type() == MediaTypeWebPage) ? static_cast(media)->webpage() : 0; + if (_page) { + if (_page->doc) { + _photol = TextLinkPtr(new DocumentOpenLink(_page->doc)); + } else if (_page->photo) { + if (_page->type == WebPageProfile || _page->type == WebPageVideo) { + _photol = TextLinkPtr(linkFromUrl(_page->url)); + } else if (_page->type == WebPagePhoto || _page->siteName == qstr("Twitter") || _page->siteName == qstr("Facebook")) { + _photol = TextLinkPtr(new PhotoLink(_page->photo)); + } else { + _photol = TextLinkPtr(linkFromUrl(_page->url)); + } + } else { + _photol = TextLinkPtr(linkFromUrl(_page->url)); + } + } else if (!_links.isEmpty()) { + _photol = TextLinkPtr(linkFromUrl(_links.at(0).lnk->text())); + } + if (from >= till && _page) { + text = _page->description; + from = 0; + till = text.size(); + } + if (till > from) { + TextParseOptions opts = { TextParseMultiline, int32(st::linksMaxWidth), 3 * st::normalFont->height, Qt::LayoutDirectionAuto }; + _text.setText(st::normalFont, text.mid(from, till - from), opts); + } + int32 tw = 0, th = 0; + if (_page && _page->photo) { + if (!_page->photo->loaded()) _page->photo->thumb->load(false, false); + + tw = convertScale(_page->photo->thumb->width()); + th = convertScale(_page->photo->thumb->height()); + } else if (_page && _page->doc) { + if (!_page->doc->thumb->loaded()) _page->doc->thumb->load(false, false); + + tw = convertScale(_page->doc->thumb->width()); + th = convertScale(_page->doc->thumb->height()); + } + if (tw > st::dlgPhotoSize) { + if (th > tw) { + th = th * st::dlgPhotoSize / tw; + tw = st::dlgPhotoSize; + } else if (th > st::dlgPhotoSize) { + tw = tw * st::dlgPhotoSize / th; + th = st::dlgPhotoSize; + } + } + _pixw = qMax(tw, 1); + _pixh = qMax(th, 1); + + if (_page) { + _title = _page->title; + } + QString url(_page ? _page->url : (_links.isEmpty() ? QString() : _links.at(0).lnk->text())); + QVector parts = url.splitRef('/'); + if (!parts.isEmpty()) { + QStringRef domain = parts.at(0); + if (parts.size() > 2 && domain.endsWith(':') && parts.at(1).isEmpty()) { // http:// and others + domain = parts.at(2); + } + + parts = domain.split('@').back().split('.'); + if (parts.size() > 1) { + _letter = parts.at(parts.size() - 2).at(0).toUpper(); + if (_title.isEmpty()) { + _title.reserve(parts.at(parts.size() - 2).size()); + _title.append(_letter).append(parts.at(parts.size() - 2).mid(1)); + } + } + } + _titlew = st::semiboldFont->width(_title); +} + +void LayoutOverviewLink::initDimensions() { + _maxw = st::linksMaxWidth; + _minh = 0; + if (!_title.isEmpty()) { + _minh += st::semiboldFont->height; + } + if (!_text.isEmpty()) { + _minh += qMin(3 * st::normalFont->height, _text.countHeight(_maxw - st::dlgPhotoSize - st::dlgPhotoPadding)); + } + _minh += _links.size() * st::normalFont->height; + _minh = qMax(_minh, int32(st::dlgPhotoSize)) + st::linksMargin.top() + st::linksMargin.bottom() + st::linksBorder; +} + +int32 LayoutOverviewLink::resizeGetHeight(int32 width) { + _width = qMin(width, _maxw); + int32 w = _width - st::dlgPhotoSize - st::dlgPhotoPadding; + for (int32 i = 0, l = _links.size(); i < l; ++i) { + _links.at(i).lnk->setFullDisplayed(w >= _links.at(i).width); + } + + _height = 0; + if (!_title.isEmpty()) { + _height += st::semiboldFont->height; + } + if (!_text.isEmpty()) { + _height += qMin(3 * st::normalFont->height, _text.countHeight(_width - st::dlgPhotoSize - st::dlgPhotoPadding)); + } + _height += _links.size() * st::normalFont->height; + _height = qMax(_height, int32(st::dlgPhotoSize)) + st::linksMargin.top() + st::linksMargin.bottom() + st::linksBorder; + return _height; +} + +void LayoutOverviewLink::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { + int32 left = st::dlgPhotoSize + st::dlgPhotoPadding, top = st::linksMargin.top() + st::linksBorder, w = _width - left; + if (clip.intersects(rtlrect(0, top, st::dlgPhotoSize, st::dlgPhotoSize, _width))) { + if (_page && _page->photo) { + QPixmap pix; + if (_page->photo->loaded()) { + pix = _page->photo->full->pixSingle(_pixw, _pixh, st::dlgPhotoSize, st::dlgPhotoSize); + } else if (_page->photo->medium->loaded()) { + pix = _page->photo->medium->pixSingle(_pixw, _pixh, st::dlgPhotoSize, st::dlgPhotoSize); + } else { + pix = _page->photo->thumb->pixSingle(_pixw, _pixh, st::dlgPhotoSize, st::dlgPhotoSize); + } + p.drawPixmapLeft(0, top, _width, pix); + } else if (_page && _page->doc && !_page->doc->thumb->isNull()) { + p.drawPixmapLeft(0, top, _width, _page->doc->thumb->pixSingle(_pixw, _pixh, st::dlgPhotoSize, st::dlgPhotoSize)); + } else { + int32 index = _letter.isEmpty() ? 0 : (_letter.at(0).unicode() % 4); + switch (index) { + case 0: App::roundRect(p, rtlrect(0, top, st::dlgPhotoSize, st::dlgPhotoSize, _width), st::msgFileRedColor, DocRedCorners); break; + case 1: App::roundRect(p, rtlrect(0, top, st::dlgPhotoSize, st::dlgPhotoSize, _width), st::msgFileYellowColor, DocYellowCorners); break; + case 2: App::roundRect(p, rtlrect(0, top, st::dlgPhotoSize, st::dlgPhotoSize, _width), st::msgFileGreenColor, DocGreenCorners); break; + case 3: App::roundRect(p, rtlrect(0, top, st::dlgPhotoSize, st::dlgPhotoSize, _width), st::msgFileBlueColor, DocBlueCorners); break; + } + + if (!_letter.isEmpty()) { + p.setFont(st::linksLetterFont->f); + p.setPen(st::white->p); + p.drawText(rtlrect(0, top, st::dlgPhotoSize, st::dlgPhotoSize, _width), _letter, style::al_center); + } + } + + if (selection == FullSelection) { + App::roundRect(p, rtlrect(0, top, st::dlgPhotoSize, st::dlgPhotoSize, _width), st::overviewPhotoSelectOverlay, PhotoSelectOverlayCorners); + p.drawSpriteLeft(QPoint(st::dlgPhotoSize - st::linksPhotoCheck.pxWidth(), top + st::dlgPhotoSize - st::linksPhotoCheck.pxHeight()), _width, st::linksPhotoChecked); + } else if (context->selecting) { + p.drawSpriteLeft(QPoint(st::dlgPhotoSize - st::linksPhotoCheck.pxWidth(), top + st::dlgPhotoSize - st::linksPhotoCheck.pxHeight()), _width, st::linksPhotoCheck); + } + } + + if (!_title.isEmpty() && _text.isEmpty() && _links.size() == 1) { + top += (st::dlgPhotoSize - st::semiboldFont->height - st::normalFont->height) / 2; + } else { + top = st::linksTextTop; + } + + p.setPen(st::black); + p.setFont(st::semiboldFont); + if (!_title.isEmpty()) { + if (clip.intersects(rtlrect(left, top, qMin(w, _titlew), st::semiboldFont->height, _width))) { + p.drawTextLeft(left, top, _width, (w < _titlew) ? st::semiboldFont->elided(_title, w) : _title); + } + top += st::semiboldFont->height; + } + p.setFont(st::msgFont->f); + if (!_text.isEmpty()) { + int32 h = qMin(st::normalFont->height * 3, _text.countHeight(w)); + if (clip.intersects(rtlrect(left, top, w, h, _width))) { + _text.drawLeftElided(p, left, top, w, _width, 3); + } + top += h; + } + + p.setPen(st::btnYesColor); + for (int32 i = 0, l = _links.size(); i < l; ++i) { + if (clip.intersects(rtlrect(left, top, qMin(w, _links.at(i).width), st::normalFont->height, _width))) { + p.setFont(textlnkDrawOver(_links.at(i).lnk) ? st::normalFont->underline() : st::normalFont); + p.drawTextLeft(left, top, _width, (w < _links.at(i).width) ? st::normalFont->elided(_links.at(i).text, w) : _links.at(i).text); + } + top += st::normalFont->height; + } + + const OverviewPaintContext *pcontext = context->toOverviewPaintContext(); + t_assert(pcontext != 0); + QRect border(rtlrect(left, 0, w, st::linksBorder, _width)); + if (!pcontext->isAfterDate && clip.intersects(border)) { + p.fillRect(clip.intersected(border), st::linksBorderFg); + } +} + +void LayoutOverviewLink::getState(TextLinkPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const { + int32 left = st::dlgPhotoSize + st::dlgPhotoPadding, top = st::linksMargin.top() + st::linksBorder, w = _width - left; + if (rtlrect(0, top, st::dlgPhotoSize, st::dlgPhotoSize, _width).contains(x, y)) { + link = _photol; + return; + } + + if (!_title.isEmpty() && _text.isEmpty() && _links.size() == 1) { + top += (st::dlgPhotoSize - st::semiboldFont->height - st::normalFont->height) / 2; + } + if (!_title.isEmpty()) { + if (rtlrect(left, top, qMin(w, _titlew), st::semiboldFont->height, _width).contains(x, y)) { + link = _photol; + return; + } + top += st::webPageTitleFont->height; + } + if (!_text.isEmpty()) { + top += qMin(st::normalFont->height * 3, _text.countHeight(w)); + } + for (int32 i = 0, l = _links.size(); i < l; ++i) { + if (rtlrect(left, top, qMin(w, _links.at(i).width), st::normalFont->height, _width).contains(x, y)) { + link = _links.at(i).lnk; + return; + } + top += st::normalFont->height; + } +} + +LayoutOverviewLink::Link::Link(const QString &url, const QString &text) +: text(text) +, width(st::normalFont->width(text)) +, lnk(linkFromUrl(url)) { +} + +LayoutInlineItem::LayoutInlineItem(InlineResult *result, DocumentData *doc, PhotoData *photo) +: _result(result) +, _doc(doc) +, _photo(photo) +, _position(0) { +} + +void LayoutInlineItem::setPosition(int32 position) { + _position = position; +} + +int32 LayoutInlineItem::position() const { + return _position; +} + +InlineResult *LayoutInlineItem::result() const { + return _result; +} + +DocumentData *LayoutInlineItem::document() const { + return _doc; +} + +PhotoData *LayoutInlineItem::photo() const { + return _photo; +} + +void LayoutInlineItem::preload() { + if (_result) { + if (_result->photo) { + _result->photo->thumb->load(); + } else if (_result->doc) { + _result->doc->thumb->load(); + } else if (!_result->thumb->isNull()) { + _result->thumb->load(); + } + } else if (_doc) { + _doc->thumb->load(); + } else if (_photo) { + _photo->medium->load(); + } +} + +void LayoutInlineItem::update() { + if (_position >= 0) { + Ui::repaintInlineItem(this); + } +} + +LayoutInlineGif::LayoutInlineGif(InlineResult *result, DocumentData *doc, bool saved) : LayoutInlineItem(result, doc, 0) +, _state(0) +, _gif(0) +, _send(new SendInlineItemLink()) +, _delete((doc && saved) ? new DeleteSavedGifLink(doc) : 0) +, _animation(0) { +} + +void LayoutInlineGif::initDimensions() { + int32 w = content_width(), h = content_height(); + if (w <= 0 || h <= 0) { + _maxw = 0; + } else { + w = w * st::inlineMediaHeight / h; + _maxw = qMax(w, int32(st::inlineResultsMinWidth)); + } + _minh = st::inlineMediaHeight + st::inlineResultsSkip; +} + +void LayoutInlineGif::setPosition(int32 position) { + LayoutInlineItem::setPosition(position); + if (_position < 0) { + if (gif()) delete _gif; + _gif = 0; + } +} + +void DeleteSavedGifLink::onClick(Qt::MouseButton button) const { + if (button != Qt::LeftButton) return; + + int32 index = cSavedGifs().indexOf(_data); + if (index >= 0) { + cRefSavedGifs().remove(index); + Local::writeSavedGifs(); + + MTP::send(MTPmessages_SaveGif(MTP_inputDocument(MTP_long(_data->id), MTP_long(_data->access)), MTP_bool(true))); + } + if (App::main()) emit App::main()->savedGifsUpdated(); +} + +void LayoutInlineGif::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { + content_automaticLoad(); + + bool loaded = content_loaded(), loading = content_loading(), displayLoading = content_displayLoading(); + if (loaded && !gif() && _gif != BadClipReader) { + LayoutInlineGif *that = const_cast(this); + that->_gif = new ClipReader(content_location(), content_data(), func(that, &LayoutInlineGif::clipCallback)); + if (gif()) _gif->setAutoplay(); + } + + bool animating = (gif() && _gif->started()); + if (displayLoading) { + ensureAnimation(); + if (!_animation->radial.animating()) { + _animation->radial.start(content_progress()); + } + } + bool radial = isRadialAnimation(context->ms); + + int32 height = st::inlineMediaHeight; + QSize frame = countFrameSize(); + + QRect r(0, 0, _width, height); + if (animating) { + if (!_thumb.isNull()) _thumb = QPixmap(); + const InlinePaintContext *ctx = context->toInlinePaintContext(); + t_assert(ctx); + p.drawPixmap(r.topLeft(), _gif->current(frame.width(), frame.height(), _width, height, ctx->paused ? 0 : context->ms)); + } else { + prepareThumb(_width, height, frame); + if (_thumb.isNull()) { + p.fillRect(r, st::overviewPhotoBg); + } else { + p.drawPixmap(r.topLeft(), _thumb); + } + } + + if (radial || (!_gif && !loaded && !loading) || (_gif == BadClipReader)) { + float64 radialOpacity = (radial && loaded) ? _animation->radial.opacity() : 1; + if (_animation && _animation->_a_over.animating(context->ms)) { + float64 over = _animation->_a_over.current(); + p.setOpacity((st::msgDateImgBg->c.alphaF() * (1 - over)) + (st::msgDateImgBgOver->c.alphaF() * over)); + p.fillRect(r, st::black); + } else { + p.fillRect(r, (_state & StateOver) ? st::msgDateImgBgOver : st::msgDateImgBg); + } + p.setOpacity(radialOpacity * p.opacity()); + + p.setOpacity(radialOpacity); + style::sprite icon; + if (loaded && !radial) { + icon = st::msgFileInPlay; + } else if (radial || loading) { + icon = st::msgFileInCancel; + } else { + icon = st::msgFileInDownload; + } + QRect inner((_width - st::msgFileSize) / 2, (height - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); + p.drawSpriteCenter(inner, icon); + if (radial) { + p.setOpacity(1); + QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); + _animation->radial.draw(p, rinner, st::msgFileRadialLine, st::msgInBg); + } + } + + if (_delete && (_state & StateOver)) { + float64 deleteOver = _a_deleteOver.current(context->ms, (_state & StateDeleteOver) ? 1 : 0); + QPoint deletePos = QPoint(_width - st::stickerPanDelete.pxWidth(), 0); + p.setOpacity(deleteOver + (1 - deleteOver) * st::stickerPanDeleteOpacity); + p.drawSpriteLeft(deletePos, _width, st::stickerPanDelete); + p.setOpacity(1); + } +} + +void LayoutInlineGif::getState(TextLinkPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const { + if (x >= 0 && x < _width && y >= 0 && y < st::inlineMediaHeight) { + if (_delete && (rtl() ? _width - x : x) >= _width - st::stickerPanDelete.pxWidth() && y < st::stickerPanDelete.pxHeight()) { + link = _delete; + } else { + link = _send; + } + } +} + +void LayoutInlineGif::linkOver(const TextLinkPtr &link) { + if (_delete && link == _delete) { + if (!(_state & StateDeleteOver)) { + EnsureAnimation(_a_deleteOver, 0, func(this, &LayoutInlineGif::update)); + _state |= StateDeleteOver; + _a_deleteOver.start(1, st::stickersRowDuration); + } + } + if ((_delete && link == _delete) || link == _send) { + if (!content_loaded()) { + ensureAnimation(); + if (!(_state & StateOver)) { + EnsureAnimation(_animation->_a_over, 0, func(this, &LayoutInlineGif::update)); + _animation->_a_over.start(1, st::stickersRowDuration); + } + } + _state |= StateOver; + } +} + +void LayoutInlineGif::linkOut(const TextLinkPtr &link) { + if (_delete && link == _delete) { + if (_state & StateDeleteOver) { + update(); + EnsureAnimation(_a_deleteOver, 1, func(this, &LayoutInlineItem::update)); + _state &= ~StateDeleteOver; + _a_deleteOver.start(0, st::stickersRowDuration); + } + } + if ((_delete && link == _delete) || link == _send) { + if (!content_loaded()) { + ensureAnimation(); + if (_state & StateOver) { + EnsureAnimation(_animation->_a_over, 1, func(this, &LayoutInlineItem::update)); + _animation->_a_over.start(0, st::stickersRowDuration); + } + } + _state &= ~StateOver; + } +} + +QSize LayoutInlineGif::countFrameSize() const { + bool animating = (gif() && _gif->ready()); + int32 framew = animating ? _gif->width() : content_width(), frameh = animating ? _gif->height() : content_height(), height = st::inlineMediaHeight; + if (framew * height > frameh * _width) { + if (framew < st::maxStickerSize || frameh > height) { + if (frameh > height || (framew * height / frameh) <= st::maxStickerSize) { + framew = framew * height / frameh; + frameh = height; + } else { + frameh = int32(frameh * st::maxStickerSize) / framew; + framew = st::maxStickerSize; + } + } + } else { + if (frameh < st::maxStickerSize || framew > _width) { + if (framew > _width || (frameh * _width / framew) <= st::maxStickerSize) { + frameh = frameh * _width / framew; + framew = _width; + } else { + framew = int32(framew * st::maxStickerSize) / frameh; + frameh = st::maxStickerSize; + } + } + } + return QSize(framew, frameh); +} + +LayoutInlineGif::~LayoutInlineGif() { + if (gif()) deleteAndMark(_gif); + deleteAndMark(_animation); +} + +void LayoutInlineGif::prepareThumb(int32 width, int32 height, const QSize &frame) const { + DocumentData *doc = _doc ? _doc : (_result ? _result->doc : 0); + if (doc && !doc->thumb->isNull()) { + if (doc->thumb->loaded()) { + if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { + _thumb = doc->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), true, false, false, width, height); + } + } else { + doc->thumb->load(); + } + } else if (_result && !_result->thumb_url.isEmpty()) { + if (_result->thumb->loaded()) { + if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { + _thumb = _result->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), true, false, false, width, height); + } + } else { + _result->thumb->load(); + } + } +} + +void LayoutInlineGif::ensureAnimation() const { + if (!_animation) { + _animation = new AnimationData(animation(const_cast(this), &LayoutInlineGif::step_radial)); + } +} + +bool LayoutInlineGif::isRadialAnimation(uint64 ms) const { + if (!_animation || !_animation->radial.animating()) return false; + + _animation->radial.step(ms); + return _animation && _animation->radial.animating(); +} + +void LayoutInlineGif::step_radial(uint64 ms, bool timer) { + if (timer) { + update(); + } else { + _animation->radial.update(content_progress(), !content_loading() || content_loaded(), ms); + if (!_animation->radial.animating() && content_loaded()) { + delete _animation; + _animation = 0; + } + } +} + +void LayoutInlineGif::clipCallback(ClipReaderNotification notification) { + switch (notification) { + case ClipReaderReinit: { + if (gif()) { + if (_gif->state() == ClipError) { + delete _gif; + _gif = BadClipReader; + content_forget(); + } else if (_gif->ready() && !_gif->started()) { + int32 height = st::inlineMediaHeight; + QSize frame = countFrameSize(); + _gif->start(frame.width(), frame.height(), _width, height, false); + } else if (_gif->paused() && !Ui::isInlineItemVisible(this)) { + delete _gif; + _gif = 0; + content_forget(); + } + } + + update(); + } break; + + case ClipReaderRepaint: { + if (gif() && !_gif->currentDisplayed()) { + update(); + } + } break; + } +} + +int32 LayoutInlineGif::content_width() const { + DocumentData *doc = _doc ? _doc : (_result ? _result->doc : 0); + if (doc) { + if (doc->dimensions.width() > 0) { + return doc->dimensions.width(); + } + if (!doc->thumb->isNull()) { + return doc->thumb->width(); + } + } else if (_result) { + return _result->width; + } + return 0; +} + +int32 LayoutInlineGif::content_height() const { + DocumentData *doc = _doc ? _doc : (_result ? _result->doc : 0); + if (doc) { + if (doc->dimensions.height() > 0) { + return doc->dimensions.height(); + } + if (!doc->thumb->isNull()) { + return doc->thumb->height(); + } + } else if (_result) { + return _result->height; + } + return 0; +} + +bool LayoutInlineGif::content_loading() const { + DocumentData *doc = _doc ? _doc : (_result ? _result->doc : 0); + return doc ? doc->loading() : _result->loading(); +} + +bool LayoutInlineGif::content_displayLoading() const { + DocumentData *doc = _doc ? _doc : (_result ? _result->doc : 0); + return doc ? doc->displayLoading() : _result->displayLoading(); +} + +bool LayoutInlineGif::content_loaded() const { + DocumentData *doc = _doc ? _doc : (_result ? _result->doc : 0); + return doc ? doc->loaded() : _result->loaded(); +} + +float64 LayoutInlineGif::content_progress() const { + DocumentData *doc = _doc ? _doc : (_result ? _result->doc : 0); + return doc ? doc->progress() : _result->progress(); +} + +void LayoutInlineGif::content_automaticLoad() const { + DocumentData *doc = _doc ? _doc : (_result ? _result->doc : 0); + if (doc) { + doc->automaticLoad(0); + } else { + _result->automaticLoadGif(); + } +} + +void LayoutInlineGif::content_forget() { + DocumentData *doc = _doc ? _doc : (_result ? _result->doc : 0); + if (doc) { + doc->forget(); + } else { + _result->forget(); + } +} + +FileLocation LayoutInlineGif::content_location() const { + DocumentData *doc = _doc ? _doc : (_result ? _result->doc : 0); + return doc ? doc->location() : FileLocation(); +} + +QByteArray LayoutInlineGif::content_data() const { + DocumentData *doc = _doc ? _doc : (_result ? _result->doc : 0); + return doc ? doc->data() : _result->data(); +} + +LayoutInlinePhoto::LayoutInlinePhoto(InlineResult *result, PhotoData *photo) : LayoutInlineItem(result, 0, photo) +, _send(new SendInlineItemLink()) +, _thumbLoaded(false) { +} + +void LayoutInlinePhoto::initDimensions() { + int32 w = content_width(), h = content_height(); + if (w <= 0 || h <= 0) { + _maxw = 0; + } else { + w = w * st::inlineMediaHeight / h; + _maxw = qMax(w, int32(st::inlineResultsMinWidth)); + } + _minh = st::inlineMediaHeight + st::inlineResultsSkip; +} + +void LayoutInlinePhoto::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { + bool loaded = content_loaded(); + + int32 height = st::inlineMediaHeight; + QSize frame = countFrameSize(); + + QRect r(0, 0, _width, height); + + prepareThumb(_width, height, frame); + if (_thumb.isNull()) { + p.fillRect(r, st::overviewPhotoBg); + } else { + p.drawPixmap(r.topLeft(), _thumb); + } +} + +void LayoutInlinePhoto::getState(TextLinkPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const { + if (x >= 0 && x < _width && y >= 0 && y < st::inlineMediaHeight) { + link = _send; + } +} + +QSize LayoutInlinePhoto::countFrameSize() const { + int32 framew = content_width(), frameh = content_height(), height = st::inlineMediaHeight; + if (framew * height > frameh * _width) { + if (framew < st::maxStickerSize || frameh > height) { + if (frameh > height || (framew * height / frameh) <= st::maxStickerSize) { + framew = framew * height / frameh; + frameh = height; + } else { + frameh = int32(frameh * st::maxStickerSize) / framew; + framew = st::maxStickerSize; + } + } + } else { + if (frameh < st::maxStickerSize || framew > _width) { + if (framew > _width || (frameh * _width / framew) <= st::maxStickerSize) { + frameh = frameh * _width / framew; + framew = _width; + } else { + framew = int32(framew * st::maxStickerSize) / frameh; + frameh = st::maxStickerSize; + } + } + } + return QSize(framew, frameh); +} + +void LayoutInlinePhoto::prepareThumb(int32 width, int32 height, const QSize &frame) const { + PhotoData *photo = _photo ? _photo : (_result ? _result->photo : 0); + if (photo) { + if (photo->medium->loaded()) { + if (!_thumbLoaded || _thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { + _thumb = photo->medium->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), true, false, false, width, height); + } + _thumbLoaded = true; + } else { + if (photo->thumb->loaded()) { + if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { + _thumb = photo->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), true, false, false, width, height); + } + } + photo->medium->load(); + } + } else { + if (_result->thumb->loaded()) { + if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { + _thumb = _result->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), true, false, false, width, height); + } + } else { + _result->thumb->load(); + } + } +} + +int32 LayoutInlinePhoto::content_width() const { + PhotoData *photo = _photo ? _photo : (_result ? _result->photo : 0); + if (photo) { + return photo->full->width(); + } else if (_result) { + return _result->width; + } + return 0; +} + +int32 LayoutInlinePhoto::content_height() const { + PhotoData *photo = _photo ? _photo : (_result ? _result->photo : 0); + if (photo) { + return photo->full->height(); + } else if (_result) { + return _result->height; + } + return 0; +} + +bool LayoutInlinePhoto::content_loaded() const { + PhotoData *photo = _photo ? _photo : (_result ? _result->photo : 0); + return photo ? photo->loaded() : _result->loaded(); +} + +void LayoutInlinePhoto::content_forget() { + PhotoData *photo = _photo ? _photo : (_result ? _result->photo : 0); + if (photo) { + photo->forget(); + } else { + _result->forget(); + } +} + +LayoutInlineWebVideo::LayoutInlineWebVideo(InlineResult *result) : LayoutInlineItem(result, 0, 0) +, _send(new SendInlineItemLink()) +, _link(result->content_url.isEmpty() ? 0 : linkFromUrl(result->content_url)) +, _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) +, _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) { + if (_result->duration) { + _duration = formatDurationText(_result->duration); + } +} + +void LayoutInlineWebVideo::initDimensions() { + _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft; + int32 textWidth = _maxw - (_result->thumb->isNull() ? 0 : (st::inlineThumbSize + st::inlineThumbSkip)); + TextParseOptions titleOpts = { 0, _maxw, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto }; + _title.setText(st::semiboldFont, textOneLine(_result->title), titleOpts); + int32 titleHeight = qMin(_title.countHeight(_maxw), 2 * st::semiboldFont->height); + + int32 descriptionLines = _result->thumb->isNull() ? 3 : (titleHeight > st::semiboldFont->height ? 1 : 2); + + TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, descriptionLines * st::normalFont->height, Qt::LayoutDirectionAuto }; + _description.setText(st::normalFont, _result->description, descriptionOpts); + int32 descriptionHeight = qMin(_description.countHeight(_maxw), descriptionLines * st::normalFont->height); + + _minh = _result->thumb->isNull() ? titleHeight + descriptionHeight : st::inlineThumbSize; + _minh += st::inlineRowMargin * 2 + st::inlineRowBorder; +} + +void LayoutInlineWebVideo::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { + int32 left = st::inlineThumbSize + st::inlineThumbSkip; + prepareThumb(st::inlineThumbSize, st::inlineThumbSize); + if (_thumb.isNull()) { + p.fillRect(rtlrect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize, _width), _result->thumb->isNull() ? st::black : st::overviewPhotoBg); + } else { + p.drawPixmapLeft(0, st::inlineRowMargin, _width, _thumb); + } + + if (!_duration.isEmpty()) { + int32 durationTop = st::inlineRowMargin + st::inlineThumbSize - st::normalFont->height - st::inlineDurationMargin; + p.fillRect(rtlrect(0, durationTop - st::inlineDurationMargin, st::inlineThumbSize, st::normalFont->height + 2 * st::inlineDurationMargin, _width), st::msgDateImgBg); + p.setPen(st::white); + p.setFont(st::normalFont); + p.drawTextRight(_width - st::inlineThumbSize + st::inlineDurationMargin, durationTop, _width, _duration); + } + + p.setPen(st::black); + _title.drawLeftElided(p, left, st::inlineRowMargin, _width - left, _width, 2); + int32 titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2); + + p.setPen(st::inlineDescriptionFg); + int32 descriptionLines = _result->thumb->isNull() ? 3 : (titleHeight > st::semiboldFont->height ? 1 : 2); + _description.drawLeftElided(p, left, st::inlineRowMargin + titleHeight, _width - left, _width, descriptionLines); + + const InlinePaintContext *ctx = context->toInlinePaintContext(); + t_assert(ctx); + if (!ctx->lastRow) { + p.fillRect(rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg); + } +} + +void LayoutInlineWebVideo::getState(TextLinkPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const { + if (x >= 0 && x < st::inlineThumbSize && y >= st::inlineRowMargin && y < st::inlineRowMargin + st::inlineThumbSize) { + link = _link; + return; + } + if (x >= st::inlineThumbSize + st::inlineThumbSkip && x < _width && y >= 0 && y < _height) { + link = _send; + return; + } +} + +void LayoutInlineWebVideo::prepareThumb(int32 width, int32 height) const { + if (_result->thumb->loaded()) { + if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { + int32 w = qMax(_result->thumb->width(), 1), h = qMax(_result->thumb->height(), 1); + if (w * height > h * width) { + if (height < h) { + w = w * height / h; + h = height; + } + } else { + if (width < w) { + h = h * width / w; + w = width; + } + } + _thumb = _result->thumb->pixNoCache(w * cIntRetinaFactor(), h * cIntRetinaFactor(), true, false, false, width, height); + } + } else { + _result->thumb->load(); + } +} + +LayoutInlineArticle::LayoutInlineArticle(InlineResult *result, bool withThumb) : LayoutInlineItem(result, 0, 0) +, _send(new SendInlineItemLink()) +, _url(result->url.isEmpty() ? 0 : linkFromUrl(result->url)) +, _link(result->content_url.isEmpty() ? 0 : linkFromUrl(result->content_url)) +, _withThumb(withThumb) +, _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) +, _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) { + QVector parts = _result->url.splitRef('/'); + if (!parts.isEmpty()) { + QStringRef domain = parts.at(0); + if (parts.size() > 2 && domain.endsWith(':') && parts.at(1).isEmpty()) { // http:// and others + domain = parts.at(2); + } + + parts = domain.split('@').back().split('.'); + if (parts.size() > 1) { + _letter = parts.at(parts.size() - 2).at(0).toUpper(); + } + } + if (_letter.isEmpty() && !_result->title.isEmpty()) { + _letter = _result->title.at(0).toUpper(); + } +} + +void LayoutInlineArticle::initDimensions() { + _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft; + int32 textWidth = _maxw - (_withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0); + TextParseOptions titleOpts = { 0, _maxw, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto }; + _title.setText(st::semiboldFont, textOneLine(_result->title), titleOpts); + int32 titleHeight = qMin(_title.countHeight(_maxw), 2 * st::semiboldFont->height); + + int32 descriptionLines = (_withThumb || _url) ? 2 : 3; + + TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, descriptionLines * st::normalFont->height, Qt::LayoutDirectionAuto }; + _description.setText(st::normalFont, _result->description, descriptionOpts); + int32 descriptionHeight = qMin(_description.countHeight(_maxw), descriptionLines * st::normalFont->height); + + _minh = titleHeight + descriptionHeight; + if (_url) _minh += st::normalFont->height; + if (_withThumb) _minh = qMax(_minh, int32(st::inlineThumbSize)); + _minh += st::inlineRowMargin * 2 + st::inlineRowBorder; +} + +int32 LayoutInlineArticle::resizeGetHeight(int32 width) { + _width = qMin(width, _maxw); + if (_url) { + _urlText = _result->url; + _urlWidth = st::normalFont->width(_urlText); + if (_urlWidth > _width - st::inlineThumbSize - st::inlineThumbSkip) { + _urlText = st::normalFont->elided(_result->url, _width - st::inlineThumbSize - st::inlineThumbSkip); + _urlWidth = st::normalFont->width(_urlText); + } + } + _height = _minh; + return _height; +} + +void LayoutInlineArticle::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const { + int32 left = st::emojiPanHeaderLeft - st::inlineResultsLeft; + if (_withThumb) { + left = st::inlineThumbSize + st::inlineThumbSkip; + prepareThumb(st::inlineThumbSize, st::inlineThumbSize); + QRect rthumb(rtlrect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize, _width)); + if (_thumb.isNull()) { + if (_result->thumb->isNull() && !_letter.isEmpty()) { + int32 index = (_letter.at(0).unicode() % 4); + style::color colors[] = { st::msgFileRedColor, st::msgFileYellowColor, st::msgFileGreenColor, st::msgFileBlueColor }; + + p.fillRect(rthumb, colors[index]); + if (!_letter.isEmpty()) { + p.setFont(st::linksLetterFont); + p.setPen(st::white); + p.drawText(rthumb, _letter, style::al_center); + } + } else { + p.fillRect(rthumb, st::overviewPhotoBg); + } + } else { + p.drawPixmapLeft(rthumb.topLeft(), _width, _thumb); + } + } + + p.setPen(st::black); + _title.drawLeftElided(p, left, st::inlineRowMargin, _width - left, _width, 2); + int32 titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2); + + p.setPen(st::inlineDescriptionFg); + int32 descriptionLines = (_withThumb || _url) ? 2 : 3; + _description.drawLeftElided(p, left, st::inlineRowMargin + titleHeight, _width - left, _width, descriptionLines); + + if (_url) { + int32 descriptionHeight = qMin(_description.countHeight(_width - left), st::normalFont->height * descriptionLines); + p.drawTextLeft(left, st::inlineRowMargin + titleHeight + descriptionHeight, _width, _urlText, _urlWidth); + } + + const InlinePaintContext *ctx = context->toInlinePaintContext(); + t_assert(ctx); + if (!ctx->lastRow) { + p.fillRect(rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg); + } +} + +void LayoutInlineArticle::getState(TextLinkPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const { + int32 left = _withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0; + if (x >= 0 && x < left - st::inlineThumbSkip && y >= st::inlineRowMargin && y < st::inlineRowMargin + st::inlineThumbSize) { + link = _link; + return; + } + if (x >= left && x < _width && y >= 0 && y < _height) { + if (_url) { + int32 left = st::inlineThumbSize + st::inlineThumbSkip; + int32 titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2); + int32 descriptionLines = 2; + int32 descriptionHeight = qMin(_description.countHeight(_width - left), st::normalFont->height * descriptionLines); + if (rtlrect(left, st::inlineRowMargin + titleHeight + descriptionHeight, _urlWidth, st::normalFont->height, _width).contains(x, y)) { + link = _url; + return; + } + } + link = _send; + return; + } +} + +void LayoutInlineArticle::prepareThumb(int32 width, int32 height) const { + if (_result->thumb->isNull()) return; + + if (_result->thumb->loaded()) { + if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { + int32 w = qMax(_result->thumb->width(), 1), h = qMax(_result->thumb->height(), 1); + if (w * height > h * width) { + if (height < h) { + w = w * height / h; + h = height; + } + } else { + if (width < w) { + h = h * width / w; + w = width; + } + } + _thumb = _result->thumb->pixNoCache(w * cIntRetinaFactor(), h * cIntRetinaFactor(), true, false, false, width, height); + } + } else { + _result->thumb->load(); + } +} diff --git a/Telegram/SourceFiles/layout.h b/Telegram/SourceFiles/layout.h new file mode 100644 index 0000000000..0b346456c6 --- /dev/null +++ b/Telegram/SourceFiles/layout.h @@ -0,0 +1,702 @@ +/* +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-2015 John Preston, https://desktop.telegram.org +*/ +#pragma once + +static const uint32 FullSelection = 0xFFFFFFFF; + +extern TextParseOptions _textNameOptions, _textDlgOptions; +extern TextParseOptions _historyTextOptions, _historyBotOptions, _historyTextNoMonoOptions, _historyBotNoMonoOptions; + +const TextParseOptions &itemTextOptions(History *h, PeerData *f); +const TextParseOptions &itemTextNoMonoOptions(History *h, PeerData *f); + +enum RoundCorners { + NoneCorners = 0x00, // for images + BlackCorners, + ServiceCorners, + ServiceSelectedCorners, + SelectedOverlayCorners, + DateCorners, + DateSelectedCorners, + ForwardCorners, + MediaviewSaveCorners, + EmojiHoverCorners, + StickerHoverCorners, + BotKeyboardCorners, + BotKeyboardOverCorners, + BotKeyboardDownCorners, + PhotoSelectOverlayCorners, + + DocBlueCorners, + DocGreenCorners, + DocRedCorners, + DocYellowCorners, + + InShadowCorners, // for photos without bg + InSelectedShadowCorners, + + MessageInCorners, // with shadow + MessageInSelectedCorners, + MessageOutCorners, + MessageOutSelectedCorners, + + RoundCornersCount +}; + +static const int32 FileStatusSizeReady = 0x7FFFFFF0; +static const int32 FileStatusSizeLoaded = 0x7FFFFFF1; +static const int32 FileStatusSizeFailed = 0x7FFFFFF2; + +QString formatSizeText(qint64 size); +QString formatDownloadText(qint64 ready, qint64 total); +QString formatDurationText(qint64 duration); +QString formatDurationAndSizeText(qint64 duration, qint64 size); +QString formatGifAndSizeText(qint64 size); +QString formatPlayedText(qint64 played, qint64 duration); + +QString documentName(DocumentData *document); +int32 documentColorIndex(DocumentData *document, QString &ext); +style::color documentColor(int32 colorIndex); +style::color documentDarkColor(int32 colorIndex); +style::color documentOverColor(int32 colorIndex); +style::color documentSelectedColor(int32 colorIndex); +style::sprite documentCorner(int32 colorIndex); +RoundCorners documentCorners(int32 colorIndex); + +class OverviewPaintContext; +class InlinePaintContext; +class PaintContext { +public: + + PaintContext(uint64 ms, bool selecting) : ms(ms), selecting(selecting) { + } + uint64 ms; + bool selecting; + + virtual const OverviewPaintContext *toOverviewPaintContext() const { + return 0; + } + virtual const InlinePaintContext *toInlinePaintContext() const { + return 0; + } + +}; + +class LayoutMediaItem; +class OverviewItemInfo; + +class LayoutItem { +public: + LayoutItem() : _maxw(0), _minh(0) { + } + + int32 maxWidth() const { + return _maxw; + } + int32 minHeight() const { + return _minh; + } + virtual void initDimensions() = 0; + virtual int32 resizeGetHeight(int32 width) { + _width = qMin(width, _maxw); + _height = _minh; + return _height; + } + + virtual void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const = 0; + virtual void getState(TextLinkPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const { + link = TextLinkPtr(); + cursor = HistoryDefaultCursorState; + } + virtual void getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y) const { // from text + upon = hasPoint(x, y); + symbol = upon ? 0xFFFF : 0; + after = false; + } + virtual void linkOver(const TextLinkPtr &lnk) { + } + virtual void linkOut(const TextLinkPtr &lnk) { + } + + int32 width() const { + return _width; + } + int32 height() const { + return _height; + } + + bool hasPoint(int32 x, int32 y) const { + return (x >= 0 && y >= 0 && x < width() && y < height()); + } + + virtual ~LayoutItem() { + } + + virtual LayoutMediaItem *toLayoutMediaItem() { + return 0; + } + virtual const LayoutMediaItem *toLayoutMediaItem() const { + return 0; + } + + virtual HistoryItem *getItem() const { + return 0; + } + virtual DocumentData *getDocument() const { + return 0; + } + virtual OverviewItemInfo *getOverviewItemInfo() { + return 0; + } + virtual const OverviewItemInfo *getOverviewItemInfo() const { + return 0; + } + MsgId msgId() const { + const HistoryItem *item = getItem(); + return item ? item->id : 0; + } + +protected: + int32 _width, _height, _maxw, _minh; + LayoutItem &operator=(const LayoutItem &); + +}; + +class LayoutMediaItem : public LayoutItem { +public: + LayoutMediaItem(HistoryItem *parent) : _parent(parent) { + } + + virtual LayoutMediaItem *toLayoutMediaItem() { + return this; + } + virtual const LayoutMediaItem *toLayoutMediaItem() const { + return this; + } + virtual HistoryItem *getItem() const { + return _parent; + } + +protected: + HistoryItem *_parent; + +}; + +class LayoutRadialProgressItem : public LayoutMediaItem { +public: + LayoutRadialProgressItem(HistoryItem *parent) : LayoutMediaItem(parent) + , _radial(0) + , a_iconOver(0, 0) + , _a_iconOver(animation(this, &LayoutRadialProgressItem::step_iconOver)) { + } + + void linkOver(const TextLinkPtr &lnk); + void linkOut(const TextLinkPtr &lnk); + + ~LayoutRadialProgressItem(); + +protected: + TextLinkPtr _openl, _savel, _cancell; + void setLinks(ITextLink *openl, ITextLink *savel, ITextLink *cancell); + + void step_iconOver(float64 ms, bool timer); + void step_radial(uint64 ms, bool timer); + + void ensureRadial() const; + void checkRadialFinished(); + + bool isRadialAnimation(uint64 ms) const { + if (!_radial || !_radial->animating()) return false; + + _radial->step(ms); + return _radial && _radial->animating(); + } + + virtual float64 dataProgress() const = 0; + virtual bool dataFinished() const = 0; + virtual bool dataLoaded() const = 0; + virtual bool iconAnimated() const { + return false; + } + + mutable RadialAnimation *_radial; + anim::fvalue a_iconOver; + mutable Animation _a_iconOver; + +private: + LayoutRadialProgressItem(const LayoutRadialProgressItem &other); + +}; + +class LayoutAbstractFileItem : public LayoutRadialProgressItem { +public: + LayoutAbstractFileItem(HistoryItem *parent) : LayoutRadialProgressItem(parent) { + } + +protected: + // >= 0 will contain download / upload string, _statusSize = loaded bytes + // < 0 will contain played string, _statusSize = -(seconds + 1) played + // 0x7FFFFFF0 will contain status for not yet downloaded file + // 0x7FFFFFF1 will contain status for already downloaded file + // 0x7FFFFFF2 will contain status for failed to download / upload file + mutable int32 _statusSize; + mutable QString _statusText; + + // duration = -1 - no duration, duration = -2 - "GIF" duration + void setStatusSize(int32 newSize, int32 fullSize, int32 duration, qint64 realDuration) const; + +}; + +class OverviewPaintContext : public PaintContext { +public: + OverviewPaintContext(uint64 ms, bool selecting) : PaintContext(ms, selecting), isAfterDate(false) { + } + const OverviewPaintContext *toOverviewPaintContext() const { + return this; + } + bool isAfterDate; + +}; + +class OverviewItemInfo { +public: + OverviewItemInfo() : _top(0) { + } + int32 top() const { + return _top; + } + void setTop(int32 top) { + _top = top; + } + +private: + int32 _top; + +}; + +class LayoutOverviewDate : public LayoutItem { +public: + LayoutOverviewDate(const QDate &date, bool month); + + virtual void initDimensions(); + virtual void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const; + + virtual OverviewItemInfo *getOverviewItemInfo() { + return &_info; + } + virtual const OverviewItemInfo *getOverviewItemInfo() const { + return &_info; + } + +private: + OverviewItemInfo _info; + + QDate _date; + QString _text; + +}; + +class LayoutOverviewPhoto : public LayoutMediaItem { +public: + LayoutOverviewPhoto(PhotoData *photo, HistoryItem *parent); + + virtual void initDimensions(); + virtual int32 resizeGetHeight(int32 width); + virtual void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const; + virtual void getState(TextLinkPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const; + +private: + PhotoData *_data; + TextLinkPtr _link; + + mutable QPixmap _pix; + mutable bool _goodLoaded; + +}; + +class LayoutOverviewVideo : public LayoutAbstractFileItem { +public: + LayoutOverviewVideo(VideoData *photo, HistoryItem *parent); + + virtual void initDimensions(); + virtual int32 resizeGetHeight(int32 width); + virtual void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const; + virtual void getState(TextLinkPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const; + +protected: + virtual float64 dataProgress() const { + return _data->progress(); + } + virtual bool dataFinished() const { + return !_data->loading(); + } + virtual bool dataLoaded() const { + return _data->loaded(); + } + virtual bool iconAnimated() const { + return true; + } + +private: + VideoData *_data; + + QString _duration; + mutable QPixmap _pix; + mutable bool _thumbLoaded; + + void updateStatusText() const; + +}; + +class LayoutOverviewAudio : public LayoutAbstractFileItem { +public: + LayoutOverviewAudio(AudioData *audio, HistoryItem *parent); + + virtual void initDimensions(); + virtual void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const; + virtual void getState(TextLinkPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const; + + virtual OverviewItemInfo *getOverviewItemInfo() { + return &_info; + } + virtual const OverviewItemInfo *getOverviewItemInfo() const { + return &_info; + } + +protected: + virtual float64 dataProgress() const { + return _data->progress(); + } + virtual bool dataFinished() const { + return !_data->loading(); + } + virtual bool dataLoaded() const { + return _data->loaded(); + } + virtual bool iconAnimated() const { + return true; + } + +private: + OverviewItemInfo _info; + AudioData *_data; + TextLinkPtr _namel; + + mutable Text _name, _details; + mutable int32 _nameVersion; + + void updateName() const; + bool updateStatusText() const; + +}; + +class LayoutOverviewDocument : public LayoutAbstractFileItem { +public: + LayoutOverviewDocument(DocumentData *document, HistoryItem *parent); + + virtual void initDimensions(); + virtual void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const; + virtual void getState(TextLinkPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const; + + virtual DocumentData *getDocument() const { + return _data; + } + virtual OverviewItemInfo *getOverviewItemInfo() { + return &_info; + } + virtual const OverviewItemInfo *getOverviewItemInfo() const { + return &_info; + } + +protected: + virtual float64 dataProgress() const { + return _data->progress(); + } + virtual bool dataFinished() const { + return !_data->loading(); + } + virtual bool dataLoaded() const { + return _data->loaded(); + } + virtual bool iconAnimated() const { + return _data->song() || !_data->loaded() || (_radial && _radial->animating()); + } + +private: + OverviewItemInfo _info; + DocumentData *_data; + TextLinkPtr _msgl, _namel; + + mutable bool _thumbForLoaded; + mutable QPixmap _thumb; + + QString _name, _date, _ext; + int32 _namew, _datew, _extw; + int32 _thumbw, _colorIndex; + + bool withThumb() const { + return !_data->thumb->isNull() && _data->thumb->width() && _data->thumb->height(); + } + bool updateStatusText() const; + +}; + +class LayoutOverviewLink : public LayoutMediaItem { +public: + LayoutOverviewLink(HistoryMedia *media, HistoryItem *parent); + + virtual void initDimensions(); + virtual int32 resizeGetHeight(int32 width); + virtual void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const; + virtual void getState(TextLinkPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const; + + virtual OverviewItemInfo *getOverviewItemInfo() { + return &_info; + } + virtual const OverviewItemInfo *getOverviewItemInfo() const { + return &_info; + } + +private: + OverviewItemInfo _info; + TextLinkPtr _photol; + + QString _title, _letter; + int32 _titlew; + WebPageData *_page; + int32 _pixw, _pixh; + Text _text; + + struct Link { + Link() : width(0) { + } + Link(const QString &url, const QString &text); + QString text; + int32 width; + TextLinkPtr lnk; + }; + QVector _links; + +}; + +class InlinePaintContext : public PaintContext { +public: + InlinePaintContext(uint64 ms, bool selecting, bool paused, bool lastRow) + : PaintContext(ms, selecting) + , paused(paused) + , lastRow(lastRow) { + } + virtual const InlinePaintContext *toInlinePaintContext() const { + return this; + } + bool paused, lastRow; +}; + +class LayoutInlineItem : public LayoutItem { +public: + + LayoutInlineItem(InlineResult *result, DocumentData *doc, PhotoData *photo); + + virtual void setPosition(int32 position); + int32 position() const; + + virtual bool fullLine() const { + return true; + } + + InlineResult *result() const; + DocumentData *document() const; + PhotoData *photo() const; + void preload(); + + void update(); + +protected: + InlineResult *_result; + DocumentData *_doc; + PhotoData *_photo; + + int32 _position; // < 0 means removed from layout + +}; + +class SendInlineItemLink : public ITextLink { + TEXT_LINK_CLASS(SendInlineItemLink) + +public: + virtual void onClick(Qt::MouseButton) const { + } + +}; + +class DeleteSavedGifLink : public ITextLink { + TEXT_LINK_CLASS(DeleteSavedGifLink) + +public: + DeleteSavedGifLink(DocumentData *data) : _data(data) { + } + virtual void onClick(Qt::MouseButton) const; + +private: + DocumentData *_data; + +}; + +class LayoutInlineGif : public LayoutInlineItem { +public: + LayoutInlineGif(InlineResult *result, DocumentData *doc, bool saved); + + virtual void setPosition(int32 position); + virtual void initDimensions(); + + virtual bool fullLine() const { + return false; + } + + virtual void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const; + virtual void getState(TextLinkPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const; + virtual void linkOver(const TextLinkPtr &lnk); + virtual void linkOut(const TextLinkPtr &lnk); + + ~LayoutInlineGif(); + +private: + QSize countFrameSize() const; + + int32 content_width() const; + int32 content_height() const; + bool content_loading() const; + bool content_displayLoading() const; + bool content_loaded() const; + float64 content_progress() const; + void content_automaticLoad() const; + void content_forget(); + FileLocation content_location() const; + QByteArray content_data() const; + + enum StateFlags { + StateOver = 0x01, + StateDeleteOver = 0x02, + }; + int32 _state; + + ClipReader *_gif; + TextLinkPtr _send, _delete; + bool gif() const { + return (!_gif || _gif == BadClipReader) ? false : true; + } + mutable QPixmap _thumb; + void prepareThumb(int32 width, int32 height, const QSize &frame) const; + + void ensureAnimation() const; + bool isRadialAnimation(uint64 ms) const; + void step_radial(uint64 ms, bool timer); + + void clipCallback(ClipReaderNotification notification); + + struct AnimationData { + AnimationData(AnimationCreator creator) + : over(false) + , radial(creator) { + } + bool over; + FloatAnimation _a_over; + RadialAnimation radial; + }; + mutable AnimationData *_animation; + mutable FloatAnimation _a_deleteOver; + +}; + +class LayoutInlinePhoto : public LayoutInlineItem { +public: + LayoutInlinePhoto(InlineResult *result, PhotoData *photo); + + virtual void initDimensions(); + + virtual bool fullLine() const { + return false; + } + + virtual void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const; + virtual void getState(TextLinkPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const; + +private: + QSize countFrameSize() const; + + int32 content_width() const; + int32 content_height() const; + bool content_loaded() const; + void content_forget(); + + TextLinkPtr _send; + + mutable QPixmap _thumb; + mutable bool _thumbLoaded; + void prepareThumb(int32 width, int32 height, const QSize &frame) const; + +}; + +class LayoutInlineWebVideo : public LayoutInlineItem { +public: + LayoutInlineWebVideo(InlineResult *result); + + virtual void initDimensions(); + + virtual void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const; + virtual void getState(TextLinkPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const; + +private: + + TextLinkPtr _send, _link; + + mutable QPixmap _thumb; + Text _title, _description; + QString _duration; + int32 _durationWidth; + + void prepareThumb(int32 width, int32 height) const; + +}; + +class LayoutInlineArticle : public LayoutInlineItem { +public: + LayoutInlineArticle(InlineResult *result, bool withThumb); + + virtual void initDimensions(); + virtual int32 resizeGetHeight(int32 width); + + virtual void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const; + virtual void getState(TextLinkPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const; + +private: + + TextLinkPtr _send, _url, _link; + + bool _withThumb; + mutable QPixmap _thumb; + Text _title, _description; + QString _letter, _urlText; + int32 _urlWidth; + + void prepareThumb(int32 width, int32 height) const; + +}; diff --git a/Telegram/SourceFiles/localimageloader.cpp b/Telegram/SourceFiles/localimageloader.cpp index 95f0b87797..3b1f8d797a 100644 --- a/Telegram/SourceFiles/localimageloader.cpp +++ b/Telegram/SourceFiles/localimageloader.cpp @@ -135,7 +135,7 @@ TaskQueue::~TaskQueue() { void TaskQueueWorker::onTaskAdded() { if (_inTaskAdded) return; _inTaskAdded = true; - + bool someTasksLeft = false; do { TaskPtr task; @@ -294,13 +294,12 @@ void FileLoadTask::process() { MTPDocument document(MTP_documentEmpty(MTP_long(0))); MTPAudio audio(MTP_audioEmpty(MTP_long(0))); - bool song = false; + bool song = false, gif = false; if (_type != PrepareAudio) { if (filemime == qstr("audio/mp3") || filemime == qstr("audio/m4a") || filemime == qstr("audio/aac") || filemime == qstr("audio/ogg") || filemime == qstr("audio/flac") || filename.endsWith(qstr(".mp3"), Qt::CaseInsensitive) || filename.endsWith(qstr(".m4a"), Qt::CaseInsensitive) || filename.endsWith(qstr(".aac"), Qt::CaseInsensitive) || filename.endsWith(qstr(".ogg"), Qt::CaseInsensitive) || filename.endsWith(qstr(".flac"), Qt::CaseInsensitive)) { - QImage cover; QByteArray coverBytes, coverFormat; MTPDocumentAttribute audioAttribute = audioReadSongAttributes(_filepath, _content, cover, coverBytes, coverFormat); @@ -327,9 +326,39 @@ void FileLoadTask::process() { } } } + if (filemime == qstr("video/mp4") || filename.endsWith(qstr(".mp4"), Qt::CaseInsensitive) || animated) { + QImage cover; + MTPDocumentAttribute animatedAttribute = clipReadAnimatedAttributes(_filepath, _content, cover); + if (animatedAttribute.type() == mtpc_documentAttributeVideo) { + int32 cw = cover.width(), ch = cover.height(); + if (cw < 20 * ch && ch < 20 * cw) { + attributes.push_back(MTP_documentAttributeAnimated()); + attributes.push_back(animatedAttribute); + gif = true; + + QPixmap full = (cw > 90 || ch > 90) ? QPixmap::fromImage(cover.scaled(90, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly) : QPixmap::fromImage(cover, Qt::ColorOnly); + { + QByteArray thumbFormat = "JPG"; + int32 thumbQuality = 87; + + QBuffer buffer(&thumbdata); + full.save(&buffer, thumbFormat, thumbQuality); + } + + thumb = full; + thumbSize = MTP_photoSize(MTP_string(""), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0)); + + thumbId = MTP::nonce(); + + if (filename.endsWith(qstr(".mp4"), Qt::CaseInsensitive)) { + filemime = qstr("video/mp4"); + } + } + } + } } - if (!fullimage.isNull() && fullimage.width() > 0 && !song) { + if (!fullimage.isNull() && fullimage.width() > 0 && !song && !gif) { int32 w = fullimage.width(), h = fullimage.height(); attributes.push_back(MTP_documentAttributeImageSize(MTP_int(w), MTP_int(h))); @@ -387,12 +416,13 @@ void FileLoadTask::process() { _type = PrepareDocument; } } - + _result->type = _type; _result->filepath = _filepath; _result->content = _content; _result->filename = filename; + _result->filemime = filemime; _result->setFileData(filedata); _result->thumbId = thumbId; @@ -409,23 +439,23 @@ void FileLoadTask::process() { void FileLoadTask::finish() { if (!_result || !_result->filesize) { if (_result) App::main()->onSendFileCancel(_result); - App::wnd()->replaceLayer(new InformBox(lang(lng_send_image_empty))); + Ui::showLayer(new InformBox(lang(lng_send_image_empty)), KeepOtherLayers); return; } if (_result->filesize == -1) { // dir App::main()->onSendFileCancel(_result); - App::wnd()->replaceLayer(new InformBox(lng_send_folder(lt_name, QFileInfo(_filepath).dir().dirName()))); + Ui::showLayer(new InformBox(lng_send_folder(lt_name, QFileInfo(_filepath).dir().dirName())), KeepOtherLayers); return; } if (_result->filesize > MaxUploadDocumentSize) { App::main()->onSendFileCancel(_result); - App::wnd()->replaceLayer(new InformBox(lang(lng_send_image_too_large))); + Ui::showLayer(new InformBox(lang(lng_send_image_too_large)), KeepOtherLayers); return; } if (App::main()) { bool confirm = (_confirm == FileLoadAlwaysConfirm) || (_result->photo.type() != mtpc_photoEmpty && _confirm != FileLoadNeverConfirm); if (confirm) { - App::wnd()->showLayerLast(new PhotoSendBox(_result)); + Ui::showLayer(new PhotoSendBox(_result), ShowAfterOtherLayers); } else { if (_result->type == PrepareAuto) { _result->type = (_result->photo.type() != mtpc_photoEmpty) ? PreparePhoto : PrepareDocument; diff --git a/Telegram/SourceFiles/localimageloader.h b/Telegram/SourceFiles/localimageloader.h index 6690954092..47ef7441fa 100644 --- a/Telegram/SourceFiles/localimageloader.h +++ b/Telegram/SourceFiles/localimageloader.h @@ -190,9 +190,11 @@ struct FileLoadResult { QByteArray content; QString filename; + QString filemime; int32 filesize; UploadFileParts fileparts; QByteArray filemd5; + int32 partssize; uint64 thumbId; // id is always file-id of media, thumbId is file-id of thumb ( == id for photos) QString thumbname; @@ -205,14 +207,16 @@ struct FileLoadResult { MTPDocument document; PreparedPhotoThumbs photoThumbs; - QString photoCaption; + QString caption; QString originalText; // when pasted had an image mime save text mime here to insert if image send was cancelled void setFileData(const QByteArray &filedata) { - if (!filedata.isEmpty()) { - int32 size = filedata.size(); - for (int32 i = 0, part = 0; i < size; i += UploadPartSize, ++part) { + if (filedata.isEmpty()) { + partssize = 0; + } else { + partssize = filedata.size(); + for (int32 i = 0, part = 0; i < partssize; i += UploadPartSize, ++part) { fileparts.insert(part, filedata.mid(i, UploadPartSize)); } filemd5.resize(32); diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index 362798d5e4..6aaa61710a 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -329,6 +329,45 @@ namespace { } }; + bool fileExists(const QString &name, int options = UserPath | SafePath) { + if (options & UserPath) { + if (!_userWorking()) return false; + } else { + if (!_working()) return false; + } + + // detect order of read attempts + QString toTry[2]; + toTry[0] = ((options & UserPath) ? _userBasePath : _basePath) + name + '0'; + if (options & SafePath) { + QFileInfo toTry0(toTry[0]); + if (toTry0.exists()) { + toTry[1] = ((options & UserPath) ? _userBasePath : _basePath) + name + '1'; + QFileInfo toTry1(toTry[1]); + if (toTry1.exists()) { + QDateTime mod0 = toTry0.lastModified(), mod1 = toTry1.lastModified(); + if (mod0 < mod1) { + qSwap(toTry[0], toTry[1]); + } + } else { + toTry[1] = QString(); + } + } else { + toTry[0][toTry[0].size() - 1] = '1'; + } + } + for (int32 i = 0; i < 2; ++i) { + QString fname(toTry[i]); + if (fname.isEmpty()) break; + if (QFileInfo(fname).exists()) return true; + } + return false; + } + + bool fileExists(const FileKey &fkey, int options = UserPath | SafePath) { + return fileExists(toFilePart(fkey), options); + } + bool readFile(FileReadDescriptor &result, const QString &name, int options = UserPath | SafePath) { if (options & UserPath) { if (!_userWorking()) return false; @@ -499,20 +538,22 @@ namespace { FileKey _dataNameKey = 0; enum { // Local Storage Keys - lskUserMap = 0x00, - lskDraft = 0x01, // data: PeerId peer - lskDraftPosition = 0x02, // data: PeerId peer - lskImages = 0x03, // data: StorageKey location - lskLocations = 0x04, // no data - lskStickerImages = 0x05, // data: StorageKey location - lskAudios = 0x06, // data: StorageKey location - lskRecentStickersOld = 0x07, // no data - lskBackground = 0x08, // no data - lskUserSettings = 0x09, // no data - lskRecentHashtags = 0x0a, // no data - lskStickers = 0x0b, // no data - lskSavedPeers = 0x0c, // no data - lskReportSpamStatuses = 0x0d, // no data + lskUserMap = 0x00, + lskDraft = 0x01, // data: PeerId peer + lskDraftPosition = 0x02, // data: PeerId peer + lskImages = 0x03, // data: StorageKey location + lskLocations = 0x04, // no data + lskStickerImages = 0x05, // data: StorageKey location + lskAudios = 0x06, // data: StorageKey location + lskRecentStickersOld = 0x07, // no data + lskBackground = 0x08, // no data + lskUserSettings = 0x09, // no data + lskRecentHashtagsAndBots = 0x0a, // no data + lskStickers = 0x0b, // no data + lskSavedPeers = 0x0c, // no data + lskReportSpamStatuses = 0x0d, // no data + lskSavedGifsOld = 0x0e, // no data + lskSavedGifs = 0x0f, // no data }; typedef QMap DraftsMap; @@ -520,6 +561,8 @@ namespace { typedef QMap DraftsNotReadMap; DraftsNotReadMap _draftsNotReadMap; + typedef QPair FileDesc; // file, size + typedef QMultiMap FileLocations; FileLocations _fileLocations; typedef QPair FileLocationPair; @@ -527,20 +570,22 @@ namespace { FileLocationPairs _fileLocationPairs; typedef QMap FileLocationAliases; FileLocationAliases _fileLocationAliases; + typedef QMap WebFilesMap; + WebFilesMap _webFilesMap; + uint64 _storageWebFilesSize = 0; FileKey _locationsKey = 0, _reportSpamStatusesKey = 0; - - FileKey _recentStickersKeyOld = 0, _stickersKey = 0; - + + FileKey _recentStickersKeyOld = 0, _stickersKey = 0, _savedGifsKey = 0; + FileKey _backgroundKey = 0; bool _backgroundWasRead = false; FileKey _userSettingsKey = 0; - FileKey _recentHashtagsKey = 0; - bool _recentHashtagsWereRead = false; + FileKey _recentHashtagsAndBotsKey = 0; + bool _recentHashtagsAndBotsWereRead = false; FileKey _savedPeersKey = 0; - typedef QPair FileDesc; // file, size typedef QMap StorageMap; StorageMap _imagesMap, _stickerImagesMap, _audiosMap; int32 _storageImagesSize = 0, _storageStickersSize = 0, _storageAudiosSize = 0; @@ -564,7 +609,7 @@ namespace { if (!_working()) return; _manager->writingLocations(); - if (_fileLocations.isEmpty()) { + if (_fileLocations.isEmpty() && _webFilesMap.isEmpty()) { if (_locationsKey) { clearKey(_locationsKey); _locationsKey = 0; @@ -602,6 +647,12 @@ namespace { size += sizeof(quint64) * 2 + sizeof(quint64) * 2; } + size += sizeof(quint32); // web files count + for (WebFilesMap::const_iterator i = _webFilesMap.cbegin(), e = _webFilesMap.cend(); i != e; ++i) { + // url + filekey + size + size += _stringSize(i.key()) + sizeof(quint64) + sizeof(qint32); + } + EncryptedDescriptor data(size); for (FileLocations::const_iterator i = _fileLocations.cbegin(); i != _fileLocations.cend(); ++i) { data.stream << quint64(i.key().first) << quint64(i.key().second) << quint32(i.value().type) << i.value().name(); @@ -622,6 +673,11 @@ namespace { data.stream << quint64(i.key().first) << quint64(i.key().second) << quint64(i.value().first) << quint64(i.value().second); } + data.stream << quint32(_webFilesMap.size()); + for (WebFilesMap::const_iterator i = _webFilesMap.cbegin(), e = _webFilesMap.cend(); i != e; ++i) { + data.stream << i.key() << quint64(i.value().first) << qint32(i.value().second); + } + FileWriteDescriptor file(_locationsKey); file.writeEncrypted(data); } @@ -669,6 +725,22 @@ namespace { locations.stream >> kfirst >> ksecond >> vfirst >> vsecond; _fileLocationAliases.insert(MediaKey(kfirst, ksecond), MediaKey(vfirst, vsecond)); } + + if (!locations.stream.atEnd()) { + _storageWebFilesSize = 0; + _webFilesMap.clear(); + + quint32 webLocationsCount; + locations.stream >> webLocationsCount; + for (quint32 i = 0; i < webLocationsCount; ++i) { + QString url; + quint64 key; + qint32 size; + locations.stream >> url >> key >> size; + _webFilesMap.insert(url, FileDesc(key, size)); + _storageWebFilesSize += size; + } + } } } @@ -758,6 +830,14 @@ namespace { cSetMaxGroupCount(maxSize); } break; + case dbiSavedGifsLimit: { + qint32 limit; + stream >> limit; + if (!_checkStreamStatus(stream)) return false; + + cSetSavedGifsLimit(limit); + } break; + case dbiMaxMegaGroupCount: { qint32 maxSize; stream >> maxSize; @@ -824,6 +904,24 @@ namespace { cSetSoundNotify(v == 1); } break; + case dbiAutoDownload: { + qint32 photo, audio, gif; + stream >> photo >> audio >> gif; + if (!_checkStreamStatus(stream)) return false; + + cSetAutoDownloadPhoto(photo); + cSetAutoDownloadAudio(audio); + cSetAutoDownloadGif(gif); + } break; + + case dbiAutoPlay: { + qint32 gif; + stream >> gif; + if (!_checkStreamStatus(stream)) return false; + + cSetAutoPlayGif(gif == 1); + } break; + case dbiIncludeMuted: { qint32 v; stream >> v; @@ -832,6 +930,14 @@ namespace { cSetIncludeMuted(v == 1); } break; + case dbiShowingSavedGifs: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + cSetShowingSavedGifs(v == 1); + } break; + case dbiDesktopNotify: { qint32 v; stream >> v; @@ -1389,12 +1495,13 @@ namespace { _writeMap(WriteMapFast); } - uint32 size = 14 * (sizeof(quint32) + sizeof(qint32)); + uint32 size = 16 * (sizeof(quint32) + sizeof(qint32)); size += sizeof(quint32) + _stringSize(cAskDownloadPath() ? QString() : cDownloadPath()) + _bytearraySize(cAskDownloadPath() ? QByteArray() : cDownloadPathBookmark()); size += sizeof(quint32) + sizeof(qint32) + (cRecentEmojisPreload().isEmpty() ? cGetRecentEmojis().size() : cRecentEmojisPreload().size()) * (sizeof(uint64) + sizeof(ushort)); size += sizeof(quint32) + sizeof(qint32) + cEmojiVariants().size() * (sizeof(uint32) + sizeof(uint64)); size += sizeof(quint32) + sizeof(qint32) + (cRecentStickersPreload().isEmpty() ? cGetRecentStickers().size() : cRecentStickersPreload().size()) * (sizeof(uint64) + sizeof(ushort)); size += sizeof(quint32) + _stringSize(cDialogLastPath()); + size += sizeof(quint32) + 3 * sizeof(qint32); EncryptedDescriptor data(size); data.stream << quint32(dbiSendKey) << qint32(cCtrlEnter() ? dbiskCtrlEnter : dbiskEnter); @@ -1404,6 +1511,7 @@ namespace { data.stream << quint32(dbiDefaultAttach) << qint32(cDefaultAttach()); data.stream << quint32(dbiSoundNotify) << qint32(cSoundNotify()); data.stream << quint32(dbiIncludeMuted) << qint32(cIncludeMuted()); + data.stream << quint32(dbiShowingSavedGifs) << qint32(cShowingSavedGifs()); data.stream << quint32(dbiDesktopNotify) << qint32(cDesktopNotify()); data.stream << quint32(dbiNotifyView) << qint32(cNotifyView()); data.stream << quint32(dbiWindowsNotifications) << qint32(cWindowsNotifications()); @@ -1412,6 +1520,8 @@ namespace { data.stream << quint32(dbiCompressPastedImage) << qint32(cCompressPastedImage()); data.stream << quint32(dbiDialogLastPath) << cDialogLastPath(); data.stream << quint32(dbiSongVolume) << qint32(qRound(cSongVolume() * 1e6)); + data.stream << quint32(dbiAutoDownload) << qint32(cAutoDownloadPhoto()) << qint32(cAutoDownloadAudio()) << qint32(cAutoDownloadGif()); + data.stream << quint32(dbiAutoPlay) << qint32(cAutoPlayGif() ? 1 : 0); { RecentEmojisPreload v(cRecentEmojisPreload()); @@ -1557,7 +1667,9 @@ namespace { DraftsNotReadMap draftsNotReadMap; StorageMap imagesMap, stickerImagesMap, audiosMap; qint64 storageImagesSize = 0, storageStickersSize = 0, storageAudiosSize = 0; - quint64 locationsKey = 0, reportSpamStatusesKey = 0, recentStickersKeyOld = 0, stickersKey = 0, backgroundKey = 0, userSettingsKey = 0, recentHashtagsKey = 0, savedPeersKey = 0; + quint64 locationsKey = 0, reportSpamStatusesKey = 0; + quint64 recentStickersKeyOld = 0, stickersKey = 0, savedGifsKey = 0; + quint64 backgroundKey = 0, userSettingsKey = 0, recentHashtagsAndBotsKey = 0, savedPeersKey = 0; while (!map.stream.atEnd()) { quint32 keyType; map.stream >> keyType; @@ -1634,12 +1746,19 @@ namespace { case lskUserSettings: { map.stream >> userSettingsKey; } break; - case lskRecentHashtags: { - map.stream >> recentHashtagsKey; + case lskRecentHashtagsAndBots: { + map.stream >> recentHashtagsAndBotsKey; } break; case lskStickers: { map.stream >> stickersKey; } break; + case lskSavedGifsOld: { + quint64 key; + map.stream >> key; + } break; + case lskSavedGifs: { + map.stream >> savedGifsKey; + } break; case lskSavedPeers: { map.stream >> savedPeersKey; } break; @@ -1667,10 +1786,11 @@ namespace { _reportSpamStatusesKey = reportSpamStatusesKey; _recentStickersKeyOld = recentStickersKeyOld; _stickersKey = stickersKey; + _savedGifsKey = savedGifsKey; _savedPeersKey = savedPeersKey; _backgroundKey = backgroundKey; _userSettingsKey = userSettingsKey; - _recentHashtagsKey = recentHashtagsKey; + _recentHashtagsAndBotsKey = recentHashtagsAndBotsKey; _oldMapVersion = mapData.version; if (_oldMapVersion < AppVersion) { _mapChanged = true; @@ -1739,10 +1859,11 @@ namespace { if (_reportSpamStatusesKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_recentStickersKeyOld) mapSize += sizeof(quint32) + sizeof(quint64); if (_stickersKey) mapSize += sizeof(quint32) + sizeof(quint64); + if (_savedGifsKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_savedPeersKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_backgroundKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_userSettingsKey) mapSize += sizeof(quint32) + sizeof(quint64); - if (_recentHashtagsKey) mapSize += sizeof(quint32) + sizeof(quint64); + if (_recentHashtagsAndBotsKey) mapSize += sizeof(quint32) + sizeof(quint64); EncryptedDescriptor mapData(mapSize); if (!_draftsMap.isEmpty()) { mapData.stream << quint32(lskDraft) << quint32(_draftsMap.size()); @@ -1786,6 +1907,9 @@ namespace { if (_stickersKey) { mapData.stream << quint32(lskStickers) << quint64(_stickersKey); } + if (_savedGifsKey) { + mapData.stream << quint32(lskSavedGifs) << quint64(_savedGifsKey); + } if (_savedPeersKey) { mapData.stream << quint32(lskSavedPeers) << quint64(_savedPeersKey); } @@ -1795,8 +1919,8 @@ namespace { if (_userSettingsKey) { mapData.stream << quint32(lskUserSettings) << quint64(_userSettingsKey); } - if (_recentHashtagsKey) { - mapData.stream << quint32(lskRecentHashtags) << quint64(_recentHashtagsKey); + if (_recentHashtagsAndBotsKey) { + mapData.stream << quint32(lskRecentHashtagsAndBots) << quint64(_recentHashtagsAndBotsKey); } map.writeEncrypted(mapData); @@ -1989,7 +2113,7 @@ namespace Local { cSetDcOptions(dcOpts); } - quint32 size = 11 * (sizeof(quint32) + sizeof(qint32)); + quint32 size = 12 * (sizeof(quint32) + sizeof(qint32)); for (mtpDcOptions::const_iterator i = dcOpts.cbegin(), e = dcOpts.cend(); i != e; ++i) { size += sizeof(quint32) + sizeof(quint32) + sizeof(quint32); size += sizeof(quint32) + _stringSize(QString::fromUtf8(i->ip.data(), i->ip.size())); @@ -2007,6 +2131,7 @@ namespace Local { EncryptedDescriptor data(size); data.stream << quint32(dbiMaxGroupCount) << qint32(cMaxGroupCount()); data.stream << quint32(dbiMaxMegaGroupCount) << qint32(cMaxMegaGroupCount()); + data.stream << quint32(dbiSavedGifsLimit) << qint32(cSavedGifsLimit()); data.stream << quint32(dbiAutoStart) << qint32(cAutoStart()); data.stream << quint32(dbiStartMinimized) << qint32(cStartMinimized()); data.stream << quint32(dbiSendToMenu) << qint32(cSendToMenu()); @@ -2020,7 +2145,7 @@ namespace Local { data.stream << quint32(dbiDcOption) << quint32(i.key()); data.stream << quint32(i->flags) << QString::fromUtf8(i->ip.data(), i->ip.size()); data.stream << quint32(i->port); - } + } data.stream << quint32(dbiLangFile) << cLangFile(); data.stream << quint32(dbiConnectionType) << qint32(cConnectionType()); @@ -2060,7 +2185,11 @@ namespace Local { _stickerImagesMap.clear(); _audiosMap.clear(); _storageImagesSize = _storageStickersSize = _storageAudiosSize = 0; - _locationsKey = _reportSpamStatusesKey = _recentStickersKeyOld = _stickersKey = _backgroundKey = _userSettingsKey = _recentHashtagsKey = _savedPeersKey = 0; + _webFilesMap.clear(); + _storageWebFilesSize = 0; + _locationsKey = _reportSpamStatusesKey = 0; + _recentStickersKeyOld = _stickersKey = _savedGifsKey = 0; + _backgroundKey = _userSettingsKey = _recentHashtagsAndBotsKey = _savedPeersKey = 0; _oldMapVersion = _oldSettingsVersion = 0; _mapChanged = true; _writeMap(WriteMapNow); @@ -2341,9 +2470,10 @@ namespace Local { quint32 imageType; readFromStream(image.stream, locFirst, locSecond, imageType, imageData); - if (locFirst != _location.first || locSecond != _location.second) { - return; - } + // we're saving files now before we have actual location + //if (locFirst != _location.first || locSecond != _location.second) { + // return; + //} _result = new Result(StorageFileType(imageType), imageData, _readImageFlag); } @@ -2357,6 +2487,9 @@ namespace Local { } virtual void readFromStream(QDataStream &stream, quint64 &first, quint64 &second, quint32 &type, QByteArray &data) = 0; virtual void clearInMap() = 0; + virtual ~AbstractCachedLoadTask() { + deleteAndMark(_result); + } protected: FileKey _key; @@ -2407,7 +2540,7 @@ namespace Local { }; TaskId startImageLoad(const StorageKey &location, mtpFileLoader *loader) { - StorageMap::iterator j = _imagesMap.find(location); + StorageMap::const_iterator j = _imagesMap.constFind(location); if (j == _imagesMap.cend() || !_localLoader) { return 0; } @@ -2466,13 +2599,26 @@ namespace Local { }; TaskId startStickerImageLoad(const StorageKey &location, mtpFileLoader *loader) { - StorageMap::iterator j = _stickerImagesMap.find(location); + StorageMap::const_iterator j = _stickerImagesMap.constFind(location); if (j == _stickerImagesMap.cend() || !_localLoader) { return 0; } return _localLoader->addTask(new StickerImageLoadTask(j->first, location, loader)); } + bool willStickerImageLoad(const StorageKey &location) { + return _stickerImagesMap.constFind(location) != _stickerImagesMap.cend(); + } + + void copyStickerImage(const StorageKey &oldLocation, const StorageKey &newLocation) { + StorageMap::const_iterator i = _stickerImagesMap.constFind(oldLocation); + if (i != _stickerImagesMap.cend()) { + _stickerImagesMap.insert(newLocation, i.value()); + _mapChanged = true; + _writeMap(); + } + } + int32 hasStickers() { return _stickerImagesMap.size(); } @@ -2525,7 +2671,7 @@ namespace Local { }; TaskId startAudioLoad(const StorageKey &location, mtpFileLoader *loader) { - StorageMap::iterator j = _audiosMap.find(location); + StorageMap::const_iterator j = _audiosMap.constFind(location); if (j == _audiosMap.cend() || !_localLoader) { return 0; } @@ -2540,6 +2686,110 @@ namespace Local { return _storageAudiosSize; } + qint32 _storageWebFileSize(const QString &url, qint32 rawlen) { + // fulllen + url + len + data + qint32 result = sizeof(uint32) + _stringSize(url) + sizeof(quint32) + rawlen; + if (result & 0x0F) result += 0x10 - (result & 0x0F); + result += tdfMagicLen + sizeof(qint32) + sizeof(quint32) + 0x10 + 0x10; // magic + version + len of encrypted + part of sha1 + md5 + return result; + } + + void writeWebFile(const QString &url, const QByteArray &content, bool overwrite) { + if (!_working()) return; + + qint32 size = _storageWebFileSize(url, content.size()); + WebFilesMap::const_iterator i = _webFilesMap.constFind(url); + if (i == _webFilesMap.cend()) { + i = _webFilesMap.insert(url, FileDesc(genKey(UserPath), size)); + _storageWebFilesSize += size; + _writeLocations(); + } else if (!overwrite) { + return; + } + EncryptedDescriptor data(_stringSize(url) + sizeof(quint32) + sizeof(quint32) + content.size()); + data.stream << url << content; + FileWriteDescriptor file(i.value().first, UserPath); + file.writeEncrypted(data); + if (i.value().second != size) { + _storageWebFilesSize += size; + _storageWebFilesSize -= i.value().second; + _webFilesMap[url].second = size; + } + } + + class WebFileLoadTask : public Task { + public: + WebFileLoadTask(const FileKey &key, const QString &url, webFileLoader *loader) + : _key(key) + , _url(url) + , _loader(loader) + , _result(0) { + } + void process() { + FileReadDescriptor image; + if (!readEncryptedFile(image, _key, UserPath)) { + return; + } + + QByteArray imageData; + QString url; + image.stream >> url >> imageData; + + _result = new Result(StorageFilePartial, imageData); + } + void finish() { + if (_result) { + _loader->localLoaded(_result->image, _result->format, _result->pixmap); + } else { + WebFilesMap::iterator j = _webFilesMap.find(_url); + if (j != _webFilesMap.cend() && j->first == _key) { + clearKey(j.value().first, UserPath); + _storageWebFilesSize -= j.value().second; + _webFilesMap.erase(j); + } + _loader->localLoaded(StorageImageSaved()); + } + } + virtual ~WebFileLoadTask() { + deleteAndMark(_result); + } + + protected: + FileKey _key; + QString _url; + struct Result { + Result(StorageFileType type, const QByteArray &data) : image(type, data) { + QByteArray guessFormat; + pixmap = QPixmap::fromImage(App::readImage(data, &guessFormat, false), Qt::ColorOnly); + if (!pixmap.isNull()) { + format = guessFormat; + } + } + StorageImageSaved image; + QByteArray format; + QPixmap pixmap; + }; + webFileLoader *_loader; + Result *_result; + + }; + + TaskId startWebFileLoad(const QString &url, webFileLoader *loader) { + WebFilesMap::const_iterator j = _webFilesMap.constFind(url); + if (j == _webFilesMap.cend() || !_localLoader) { + return 0; + } + return _localLoader->addTask(new WebFileLoadTask(j->first, url, loader)); + } + + int32 hasWebFiles() { + return _webFilesMap.size(); + } + + qint64 storageWebFilesSize() { + return _storageWebFilesSize; + } + void cancelTask(TaskId id) { if (_localLoader) { _localLoader->cancelTask(id); @@ -2547,8 +2797,8 @@ namespace Local { } void _writeStorageImageLocation(QDataStream &stream, const StorageImageLocation &loc) { - stream << qint32(loc.width) << qint32(loc.height); - stream << qint32(loc.dc) << quint64(loc.volume) << qint32(loc.local) << quint64(loc.secret); + stream << qint32(loc.width()) << qint32(loc.height()); + stream << qint32(loc.dc()) << quint64(loc.volume()) << qint32(loc.local()) << quint64(loc.secret()); } uint32 _storageImageLocationSize() { @@ -2593,6 +2843,16 @@ namespace Local { } _writeStorageImageLocation(stream, doc->sticker()->loc); } + + if (AppVersion > 9018) { + stream << qint32(it->emoji.size()); + for (StickersByEmojiMap::const_iterator j = it->emoji.cbegin(), e = it->emoji.cend(); j != e; ++j) { + stream << emojiString(j.key()) << qint32(j->size()); + for (int32 k = 0, l = j->size(); k < l; ++k) { + stream << quint64(j->at(k)->id); + } + } + } } void writeStickers() { @@ -2608,7 +2868,7 @@ namespace Local { _writeMap(); } else { int32 setsCount = 0; - QByteArray hashToWrite = (qsl("%d:") + QString::number(cStickersHash())).toUtf8(); + QByteArray hashToWrite; quint32 size = sizeof(quint32) + _bytearraySize(hashToWrite); for (StickerSets::const_iterator i = sets.cbegin(); i != sets.cend(); ++i) { bool notLoaded = (i->flags & MTPDstickerSet_flag_NOT_LOADED); @@ -2631,6 +2891,14 @@ namespace Local { // loc size += _storageImageLocationSize(); } + + if (AppVersion > 9018) { + size += sizeof(qint32); // emojiCount + for (StickersByEmojiMap::const_iterator j = i->emoji.cbegin(), e = i->emoji.cend(); j != e; ++j) { + size += _stringSize(emojiString(j.key())) + sizeof(qint32) + (j->size() * sizeof(quint64)); + } + } + ++setsCount; } @@ -2670,8 +2938,6 @@ namespace Local { RecentStickerPack &recent(cRefRecentStickers()); recent.clear(); - cSetStickersHash(0); - StickerSet &def(sets.insert(DefaultStickerSetId, StickerSet(DefaultStickerSetId, 0, lang(lng_stickers_default_set), QString(), 0, 0, MTPDstickerSet::flag_official)).value()); StickerSet &custom(sets.insert(CustomStickerSetId, StickerSet(CustomStickerSetId, 0, lang(lng_custom_stickers), QString(), 0, 0, 0)).value()); @@ -2747,9 +3013,8 @@ namespace Local { quint32 cnt; QByteArray hash; - stickers.stream >> cnt >> hash; - if (stickers.version < 8019) { - hash.clear(); // bad data in old caches + stickers.stream >> cnt >> hash; // ignore hash, it is counted + if (stickers.version < 8019) { // bad data in old caches cnt += 2; // try to read at least something } for (uint32 i = 0; i < cnt; ++i) { @@ -2820,18 +3085,158 @@ namespace Local { attributes.push_back(MTP_documentAttributeImageSize(MTP_int(width), MTP_int(height))); } - DocumentData *doc = App::documentSet(id, 0, access, date, attributes, mime, thumb.dc ? ImagePtr(thumb) : ImagePtr(), dc, size, thumb); + DocumentData *doc = App::documentSet(id, 0, access, date, attributes, mime, thumb.isNull() ? ImagePtr() : ImagePtr(thumb), dc, size, thumb); if (!doc->sticker()) continue; set.stickers.push_back(doc); ++set.count; } + + if (stickers.version > 9018) { + qint32 emojiCount; + stickers.stream >> emojiCount; + for (int32 j = 0; j < emojiCount; ++j) { + QString emojiString; + qint32 stickersCount; + stickers.stream >> emojiString >> stickersCount; + StickerPack pack; + pack.reserve(stickersCount); + for (int32 k = 0; k < stickersCount; ++k) { + quint64 id; + stickers.stream >> id; + DocumentData *doc = App::document(id); + if (!doc || !doc->sticker()) continue; + + pack.push_back(doc); + } + if (EmojiPtr e = emojiGetNoColor(emojiFromText(emojiString))) { + set.emoji.insert(e, pack); + } + } + } + } + } + + int32 countStickersHash(bool checkOfficial) { + uint32 acc = 0; + bool foundOfficial = false, foundBad = false;; + const StickerSets &sets(cStickerSets()); + const StickerSetsOrder &order(cStickerSetsOrder()); + for (StickerSetsOrder::const_iterator i = order.cbegin(), e = order.cend(); i != e; ++i) { + StickerSets::const_iterator j = sets.constFind(*i); + if (j != sets.cend()) { + if (j->id == 0) { + foundBad = true; + } else if (j->flags & MTPDstickerSet::flag_official) { + foundOfficial = true; + } + if (!(j->flags & MTPDstickerSet::flag_disabled)) { + acc = (acc * 20261) + j->hash; + } + } + } + return (!checkOfficial || (!foundBad && foundOfficial)) ? int32(acc & 0x7FFFFFFF) : 0; + } + + int32 countSavedGifsHash() { + uint32 acc = 0; + const SavedGifs &saved(cSavedGifs()); + for (SavedGifs::const_iterator i = saved.cbegin(), e = saved.cend(); i != e; ++i) { + uint64 docId = (*i)->id; + + acc = (acc * 20261) + uint32(docId >> 32); + acc = (acc * 20261) + uint32(docId & 0xFFFFFFFF); + } + return int32(acc & 0x7FFFFFFF); + } + + void writeSavedGifs() { + if (!_working()) return; + + const SavedGifs &saved(cSavedGifs()); + if (saved.isEmpty()) { + if (_savedGifsKey) { + clearKey(_savedGifsKey); + _savedGifsKey = 0; + _mapChanged = true; + } + _writeMap(); + } else { + quint32 size = sizeof(quint32); // count + for (SavedGifs::const_iterator i = saved.cbegin(), e = saved.cend(); i != e; ++i) { + DocumentData *doc = *i; + + // id + access + date + namelen + name + mimelen + mime + dc + size + width + height + type + duration + size += sizeof(quint64) + sizeof(quint64) + sizeof(qint32) + _stringSize(doc->name) + _stringSize(doc->mime) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32); + + // thumb + size += _storageImageLocationSize(); + } + + if (!_savedGifsKey) { + _savedGifsKey = genKey(); + _mapChanged = true; + _writeMap(WriteMapFast); + } + EncryptedDescriptor data(size); + data.stream << quint32(saved.size()); + for (SavedGifs::const_iterator i = saved.cbegin(), e = saved.cend(); i != e; ++i) { + DocumentData *doc = *i; + + data.stream << quint64(doc->id) << quint64(doc->access) << qint32(doc->date) << doc->name << doc->mime << qint32(doc->dc) << qint32(doc->size) << qint32(doc->dimensions.width()) << qint32(doc->dimensions.height()) << qint32(doc->type) << qint32(doc->duration()); + _writeStorageImageLocation(data.stream, doc->thumb->location()); + } + FileWriteDescriptor file(_savedGifsKey); + file.writeEncrypted(data); + } + } + + void readSavedGifs() { + if (!_savedGifsKey) return; + + FileReadDescriptor gifs; + if (!readEncryptedFile(gifs, _savedGifsKey)) { + clearKey(_savedGifsKey); + _savedGifsKey = 0; + _writeMap(); + return; } - if (hash.startsWith(qsl("%d:").toUtf8())) { - cSetStickersHash(QString::fromUtf8(hash.mid(3)).toInt()); - } else { - cSetStickersHash(0); + SavedGifs &saved(cRefSavedGifs()); + saved.clear(); + + quint32 cnt; + gifs.stream >> cnt; + saved.reserve(cnt); + QMap read; + for (uint32 i = 0; i < cnt; ++i) { + quint64 id, access; + QString name, mime; + qint32 date, dc, size, width, height, type, duration; + gifs.stream >> id >> access >> date >> name >> mime >> dc >> size >> width >> height >> type >> duration; + + StorageImageLocation thumb(_readStorageImageLocation(gifs)); + + if (read.contains(id)) continue; + read.insert(id, NullType()); + + QVector attributes; + if (!name.isEmpty()) attributes.push_back(MTP_documentAttributeFilename(MTP_string(name))); + if (type == AnimatedDocument) { + attributes.push_back(MTP_documentAttributeAnimated()); + } + if (width > 0 && height > 0) { + if (duration >= 0) { + attributes.push_back(MTP_documentAttributeVideo(MTP_int(duration), MTP_int(width), MTP_int(height))); + } else { + attributes.push_back(MTP_documentAttributeImageSize(MTP_int(width), MTP_int(height))); + } + } + + DocumentData *doc = App::documentSet(id, 0, access, date, attributes, mime, thumb.isNull() ? ImagePtr() : ImagePtr(thumb), dc, size, thumb); + if (!doc->isAnimation()) continue; + + saved.push_back(doc); } } @@ -2886,6 +3291,9 @@ namespace Local { QImage img; QBuffer buf(&pngData); QImageReader reader(&buf); +#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) + reader.setAutoTransform(true); +#endif if (reader.read(&img)) { App::initBackground(id, img, true); return true; @@ -2893,89 +3301,6 @@ namespace Local { return false; } - void writeRecentHashtags() { - if (!_working()) return; - - const RecentHashtagPack &write(cRecentWriteHashtags()), &search(cRecentSearchHashtags()); - if (write.isEmpty() && search.isEmpty()) readRecentHashtags(); - if (write.isEmpty() && search.isEmpty()) { - if (_recentHashtagsKey) { - clearKey(_recentHashtagsKey); - _recentHashtagsKey = 0; - _mapChanged = true; - } - _writeMap(); - } else { - if (!_recentHashtagsKey) { - _recentHashtagsKey = genKey(); - _mapChanged = true; - _writeMap(WriteMapFast); - } - quint32 size = sizeof(quint32) * 2, writeCnt = 0, searchCnt = 0; - for (RecentHashtagPack::const_iterator i = write.cbegin(); i != write.cend(); ++i) { - if (!i->first.isEmpty()) { - size += _stringSize(i->first) + sizeof(quint16); - ++writeCnt; - } - } - for (RecentHashtagPack::const_iterator i = search.cbegin(); i != search.cend(); ++i) { - if (!i->first.isEmpty()) { - size += _stringSize(i->first) + sizeof(quint16); - ++searchCnt; - } - } - EncryptedDescriptor data(size); - data.stream << quint32(writeCnt) << quint32(searchCnt); - for (RecentHashtagPack::const_iterator i = write.cbegin(); i != write.cend(); ++i) { - if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second); - } - for (RecentHashtagPack::const_iterator i = search.cbegin(); i != search.cend(); ++i) { - if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second); - } - FileWriteDescriptor file(_recentHashtagsKey); - file.writeEncrypted(data); - } - } - - void readRecentHashtags() { - if (_recentHashtagsWereRead) return; - _recentHashtagsWereRead = true; - - if (!_recentHashtagsKey) return; - - FileReadDescriptor hashtags; - if (!readEncryptedFile(hashtags, _recentHashtagsKey)) { - clearKey(_recentHashtagsKey); - _recentHashtagsKey = 0; - _writeMap(); - return; - } - - quint32 writeCount = 0, searchCount = 0; - hashtags.stream >> writeCount >> searchCount; - - QString tag; - quint16 count; - - RecentHashtagPack write, search; - if (writeCount) { - write.reserve(writeCount); - for (uint32 i = 0; i < writeCount; ++i) { - hashtags.stream >> tag >> count; - write.push_back(qMakePair(tag.trimmed(), count)); - } - } - if (searchCount) { - search.reserve(searchCount); - for (uint32 i = 0; i < searchCount; ++i) { - hashtags.stream >> tag >> count; - search.push_back(qMakePair(tag.trimmed(), count)); - } - } - cSetRecentWriteHashtags(write); - cSetRecentSearchHashtags(search); - } - uint32 _peerSize(PeerData *peer) { uint32 result = sizeof(quint64) + sizeof(quint64) + _storageImageLocationSize(); if (peer->isUser()) { @@ -3005,7 +3330,7 @@ namespace Local { return result; } - void _writePeer(QDataStream &stream, PeerData *peer) { + void _writePeer(QDataStream &stream, PeerData *peer, int32 fileVersion = AppVersion) { stream << quint64(peer->id) << quint64(peer->photoId); _writeStorageImageLocation(stream, peer->photoLoc); if (peer->isUser()) { @@ -3015,6 +3340,9 @@ namespace Local { if (AppVersion >= 9012) { stream << qint32(user->flags); } + if (AppVersion >= 9016 || fileVersion >= 9016) { + stream << (user->botInfo ? user->botInfo->inlinePlaceholder : QString()); + } stream << qint32(user->onlineTill) << qint32(user->contact) << qint32(user->botInfo ? user->botInfo->version : -1); } else if (peer->isChat()) { ChatData *chat = peer->asChat(); @@ -3031,47 +3359,60 @@ namespace Local { } } - PeerData *_readPeer(FileReadDescriptor &from) { + PeerData *_readPeer(FileReadDescriptor &from, int32 fileVersion = 0) { PeerData *result = 0; quint64 peerId = 0, photoId = 0; from.stream >> peerId >> photoId; StorageImageLocation photoLoc(_readStorageImageLocation(from)); - result = App::peer(peerId); - result->loaded = true; + result = App::peerLoaded(peerId); + bool wasLoaded = (result && result->loaded); + + if (!wasLoaded) { + result = App::peer(peerId); + result->loaded = true; + } if (result->isUser()) { UserData *user = result->asUser(); - QString first, last, phone, username; + QString first, last, phone, username, inlinePlaceholder; quint64 access; qint32 flags = 0, onlineTill, contact, botInfoVersion; from.stream >> first >> last >> phone >> username >> access; if (from.version >= 9012) { from.stream >> flags; } + if (from.version >= 9016 || fileVersion >= 9016) { + from.stream >> inlinePlaceholder; + } from.stream >> onlineTill >> contact >> botInfoVersion; bool showPhone = !isServiceUser(user->id) && (peerToUser(user->id) != MTP::authedId()) && (contact <= 0); QString pname = (showPhone && !phone.isEmpty()) ? App::formatPhone(phone) : QString(); - user->setName(first, last, pname, username); + if (!wasLoaded) { + user->setName(first, last, pname, username); - user->access = access; - user->flags = flags; - user->onlineTill = onlineTill; - user->contact = contact; - user->setBotInfoVersion(botInfoVersion); + user->access = access; + user->flags = flags; + user->onlineTill = onlineTill; + user->contact = contact; + user->setBotInfoVersion(botInfoVersion); + if (!inlinePlaceholder.isEmpty() && user->botInfo) { + user->botInfo->inlinePlaceholder = inlinePlaceholder; + } - if (peerToUser(user->id) == MTP::authedId()) { - user->input = MTP_inputPeerSelf(); - user->inputUser = MTP_inputUserSelf(); - } else { - user->input = MTP_inputPeerUser(MTP_int(peerToUser(user->id)), MTP_long((user->access == UserNoAccess) ? 0 : user->access)); - user->inputUser = MTP_inputUser(MTP_int(peerToUser(user->id)), MTP_long((user->access == UserNoAccess) ? 0 : user->access)); + if (peerToUser(user->id) == MTP::authedId()) { + user->input = MTP_inputPeerSelf(); + user->inputUser = MTP_inputUserSelf(); + } else { + user->input = MTP_inputPeerUser(MTP_int(peerToUser(user->id)), MTP_long((user->access == UserNoAccess) ? 0 : user->access)); + user->inputUser = MTP_inputUser(MTP_int(peerToUser(user->id)), MTP_long((user->access == UserNoAccess) ? 0 : user->access)); + } + + user->photo = photoLoc.isNull() ? ImagePtr(userDefPhoto(user->colorIndex)) : ImagePtr(photoLoc); } - - user->photo = photoLoc.isNull() ? ImagePtr(userDefPhoto(user->colorIndex)) : ImagePtr(photoLoc); } else if (result->isChat()) { ChatData *chat = result->asChat(); @@ -3085,19 +3426,21 @@ namespace Local { // flagsData was haveLeft flags = (flagsData == 1 ? MTPDchat::flag_left : 0); } - chat->updateName(name, QString(), QString()); - chat->count = count; - chat->date = date; - chat->version = version; - chat->creator = creator; - chat->isForbidden = (forbidden == 1); - chat->flags = flags; - chat->invitationUrl = invitationUrl; + if (!wasLoaded) { + chat->updateName(name, QString(), QString()); + chat->count = count; + chat->date = date; + chat->version = version; + chat->creator = creator; + chat->isForbidden = (forbidden == 1); + chat->flags = flags; + chat->invitationUrl = invitationUrl; - chat->input = MTP_inputPeerChat(MTP_int(peerToChat(chat->id))); - chat->inputChat = MTP_int(peerToChat(chat->id)); + chat->input = MTP_inputPeerChat(MTP_int(peerToChat(chat->id))); + chat->inputChat = MTP_int(peerToChat(chat->id)); - chat->photo = photoLoc.isNull() ? ImagePtr(chatDefPhoto(chat->colorIndex)) : ImagePtr(photoLoc); + chat->photo = photoLoc.isNull() ? ImagePtr(chatDefPhoto(chat->colorIndex)) : ImagePtr(photoLoc); + } } else if (result->isChannel()) { ChannelData *channel = result->asChannel(); @@ -3106,24 +3449,135 @@ namespace Local { qint32 date, version, adminned, forbidden, flags; from.stream >> name >> access >> date >> version >> forbidden >> flags >> invitationUrl; - channel->updateName(name, QString(), QString()); - channel->access = access; - channel->date = date; - channel->version = version; - channel->isForbidden = (forbidden == 1); - channel->flags = flags; - channel->invitationUrl = invitationUrl; + if (!wasLoaded) { + channel->updateName(name, QString(), QString()); + channel->access = access; + channel->date = date; + channel->version = version; + channel->isForbidden = (forbidden == 1); + channel->flags = flags; + channel->invitationUrl = invitationUrl; - channel->input = MTP_inputPeerChannel(MTP_int(peerToChannel(channel->id)), MTP_long(access)); - channel->inputChannel = MTP_inputChannel(MTP_int(peerToChannel(channel->id)), MTP_long(access)); + channel->input = MTP_inputPeerChannel(MTP_int(peerToChannel(channel->id)), MTP_long(access)); + channel->inputChannel = MTP_inputChannel(MTP_int(peerToChannel(channel->id)), MTP_long(access)); - channel->photo = photoLoc.isNull() ? ImagePtr((channel->isMegagroup() ? chatDefPhoto(channel->colorIndex) : channelDefPhoto(channel->colorIndex))) : ImagePtr(photoLoc); + channel->photo = photoLoc.isNull() ? ImagePtr((channel->isMegagroup() ? chatDefPhoto(channel->colorIndex) : channelDefPhoto(channel->colorIndex))) : ImagePtr(photoLoc); + } + } + if (!wasLoaded) { + App::markPeerUpdated(result); + emit App::main()->peerPhotoChanged(result); } - App::markPeerUpdated(result); - emit App::main()->peerPhotoChanged(result); return result; } + void writeRecentHashtagsAndBots() { + if (!_working()) return; + + const RecentHashtagPack &write(cRecentWriteHashtags()), &search(cRecentSearchHashtags()); + const RecentInlineBots &bots(cRecentInlineBots()); + if (write.isEmpty() && search.isEmpty() && bots.isEmpty()) readRecentHashtagsAndBots(); + if (write.isEmpty() && search.isEmpty() && bots.isEmpty()) { + if (_recentHashtagsAndBotsKey) { + clearKey(_recentHashtagsAndBotsKey); + _recentHashtagsAndBotsKey = 0; + _mapChanged = true; + } + _writeMap(); + } else { + if (!_recentHashtagsAndBotsKey) { + _recentHashtagsAndBotsKey = genKey(); + _mapChanged = true; + _writeMap(WriteMapFast); + } + quint32 size = sizeof(quint32) * 3, writeCnt = 0, searchCnt = 0, botsCnt = cRecentInlineBots().size(); + for (RecentHashtagPack::const_iterator i = write.cbegin(), e = write.cend(); i != e; ++i) { + if (!i->first.isEmpty()) { + size += _stringSize(i->first) + sizeof(quint16); + ++writeCnt; + } + } + for (RecentHashtagPack::const_iterator i = search.cbegin(), e = search.cend(); i != e; ++i) { + if (!i->first.isEmpty()) { + size += _stringSize(i->first) + sizeof(quint16); + ++searchCnt; + } + } + for (RecentInlineBots::const_iterator i = bots.cbegin(), e = bots.cend(); i != e; ++i) { + size += _peerSize(*i); + } + + EncryptedDescriptor data(size); + data.stream << quint32(writeCnt) << quint32(searchCnt); + for (RecentHashtagPack::const_iterator i = write.cbegin(), e = write.cend(); i != e; ++i) { + if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second); + } + for (RecentHashtagPack::const_iterator i = search.cbegin(), e = search.cend(); i != e; ++i) { + if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second); + } + data.stream << quint32(botsCnt); + for (RecentInlineBots::const_iterator i = bots.cbegin(), e = bots.cend(); i != e; ++i) { + _writePeer(data.stream, *i, 9016); + } + FileWriteDescriptor file(_recentHashtagsAndBotsKey); + file.writeEncrypted(data); + } + } + + void readRecentHashtagsAndBots() { + if (_recentHashtagsAndBotsWereRead) return; + _recentHashtagsAndBotsWereRead = true; + + if (!_recentHashtagsAndBotsKey) return; + + FileReadDescriptor hashtags; + if (!readEncryptedFile(hashtags, _recentHashtagsAndBotsKey)) { + clearKey(_recentHashtagsAndBotsKey); + _recentHashtagsAndBotsKey = 0; + _writeMap(); + return; + } + + quint32 writeCount = 0, searchCount = 0, botsCount = 0; + hashtags.stream >> writeCount >> searchCount; + + QString tag; + quint16 count; + + RecentHashtagPack write, search; + RecentInlineBots bots; + if (writeCount) { + write.reserve(writeCount); + for (uint32 i = 0; i < writeCount; ++i) { + hashtags.stream >> tag >> count; + write.push_back(qMakePair(tag.trimmed(), count)); + } + } + if (searchCount) { + search.reserve(searchCount); + for (uint32 i = 0; i < searchCount; ++i) { + hashtags.stream >> tag >> count; + search.push_back(qMakePair(tag.trimmed(), count)); + } + } + cSetRecentWriteHashtags(write); + cSetRecentSearchHashtags(search); + + if (!hashtags.stream.atEnd()) { + hashtags.stream >> botsCount; + if (botsCount) { + bots.reserve(botsCount); + for (uint32 i = 0; i < botsCount; ++i) { + PeerData *peer = _readPeer(hashtags, 9016); + if (peer && peer->isUser() && peer->asUser()->botInfo && !peer->asUser()->botInfo->inlinePlaceholder.isEmpty() && !peer->asUser()->username.isEmpty()) { + bots.push_back(peer->asUser()); + } + } + } + cSetRecentInlineBots(bots); + } + } + void writeSavedPeers() { if (!_working()) return; @@ -3187,7 +3641,7 @@ namespace Local { QDateTime t; saved.stream >> t; - + cRefSavedPeers().insert(peer, t); cRefSavedPeersByTime().insert(t, peer); peers.push_back(peer); @@ -3229,6 +3683,7 @@ namespace Local { struct ClearManagerData { QThread *thread; StorageMap images, stickers, audios; + WebFilesMap webFiles; QMutex mutex; QList tasks; bool working; @@ -3285,8 +3740,8 @@ namespace Local { _stickersKey = 0; _mapChanged = true; } - if (_recentHashtagsKey) { - _recentHashtagsKey = 0; + if (_recentHashtagsAndBotsKey) { + _recentHashtagsAndBotsKey = 0; _mapChanged = true; } if (_savedPeersKey) { @@ -3328,6 +3783,22 @@ namespace Local { _storageStickersSize = 0; _mapChanged = true; } + if (data->webFiles.isEmpty()) { + data->webFiles = _webFilesMap; + } else { + for (WebFilesMap::const_iterator i = _webFilesMap.cbegin(), e = _webFilesMap.cend(); i != e; ++i) { + QString k = i.key(); + while (data->webFiles.constFind(k) != data->webFiles.cend()) { + k += '#'; + } + data->webFiles.insert(k, i.value()); + } + } + if (!_webFilesMap.isEmpty()) { + _webFilesMap.clear(); + _storageWebFilesSize = 0; + _writeLocations(); + } if (data->audios.isEmpty()) { data->audios = _audiosMap; } else { @@ -3380,6 +3851,7 @@ namespace Local { int task = 0; bool result = false; StorageMap images, stickers, audios; + WebFilesMap webFiles; { QMutexLocker lock(&data->mutex); if (data->tasks.isEmpty()) { @@ -3390,6 +3862,7 @@ namespace Local { images = data->images; stickers = data->stickers; audios = data->audios; + webFiles = data->webFiles; } switch (task) { case ClearManagerAll: { @@ -3421,6 +3894,9 @@ namespace Local { for (StorageMap::const_iterator i = audios.cbegin(), e = audios.cend(); i != e; ++i) { clearKey(i.value().first, UserPath); } + for (WebFilesMap::const_iterator i = webFiles.cbegin(), e = webFiles.cend(); i != e; ++i) { + clearKey(i.value().first, UserPath); + } result = true; break; } diff --git a/Telegram/SourceFiles/localstorage.h b/Telegram/SourceFiles/localstorage.h index c653bf85f3..6bf8e7c9dd 100644 --- a/Telegram/SourceFiles/localstorage.h +++ b/Telegram/SourceFiles/localstorage.h @@ -65,7 +65,7 @@ namespace Local { bool checkPasscode(const QByteArray &passcode); void setPasscode(const QByteArray &passcode); - + enum ClearManagerTask { ClearManagerAll = 0xFFFF, ClearManagerDownloads = 0x01, @@ -129,6 +129,8 @@ namespace Local { void writeStickerImage(const StorageKey &location, const QByteArray &data, bool overwrite = true); TaskId startStickerImageLoad(const StorageKey &location, mtpFileLoader *loader); + bool willStickerImageLoad(const StorageKey &location); + void copyStickerImage(const StorageKey &oldLocation, const StorageKey &newLocation); int32 hasStickers(); qint64 storageStickersSize(); @@ -137,16 +139,26 @@ namespace Local { int32 hasAudios(); qint64 storageAudiosSize(); + void writeWebFile(const QString &url, const QByteArray &data, bool overwrite = true); + TaskId startWebFileLoad(const QString &url, webFileLoader *loader); + int32 hasWebFiles(); + qint64 storageWebFilesSize(); + void cancelTask(TaskId id); void writeStickers(); void readStickers(); + int32 countStickersHash(bool checkOfficial = false); + + void writeSavedGifs(); + void readSavedGifs(); + int32 countSavedGifsHash(); void writeBackground(int32 id, const QImage &img); bool readBackground(); - void writeRecentHashtags(); - void readRecentHashtags(); + void writeRecentHashtagsAndBots(); + void readRecentHashtagsAndBots(); void addSavedPeer(PeerData *peer, const QDateTime &position); void removeSavedPeer(PeerData *peer); diff --git a/Telegram/SourceFiles/logs.cpp b/Telegram/SourceFiles/logs.cpp index e13de2b27a..9004d46abb 100644 --- a/Telegram/SourceFiles/logs.cpp +++ b/Telegram/SourceFiles/logs.cpp @@ -30,13 +30,6 @@ namespace { QMutex debugLogMutex, mainLogMutex; - class _StreamCreator { - public: - ~_StreamCreator() { - logsClose(); - } - }; - QString debugLogEntryStart() { static uint32 logEntry = 0; @@ -45,7 +38,7 @@ namespace { QThread *thread = QThread::currentThread(); MTPThread *mtpThread = qobject_cast(thread); uint32 threadId = mtpThread ? mtpThread->getThreadId() : 0; - + return QString("[%1 %2-%3]").arg(tm.toString("hh:mm:ss.zzz")).arg(QString("%1").arg(threadId, 2, 10, zero)).arg(++logEntry, 7, 10, zero); } } @@ -179,9 +172,8 @@ void moveOldDataFiles(const QString &wasDir) { } } -void logsInit() { - static _StreamCreator streamCreator; - if (mainLogStream) return; +bool logsInit() { + t_assert(mainLogStream == 0); QFile beta(cExeDir() + qsl("TelegramBeta_data/tdata/beta")); if (cBetaVersion()) { @@ -223,7 +215,9 @@ void logsInit() { #ifdef _DEBUG cForceWorkingDir(cExeDir()); #else - cForceWorkingDir(psAppDataPath()); + if(cWorkingDir().isEmpty()){ + cForceWorkingDir(psAppDataPath()); + } #endif #if (defined Q_OS_LINUX && !defined _DEBUG) // fix first version @@ -281,6 +275,7 @@ void logsInit() { } QDir().setCurrent(cWorkingDir()); + return true; } void logsInitDebug() { @@ -396,7 +391,7 @@ void logsClose() { } QString logVectorLong(const QVector &ids) { - if (!ids.size()) return "[void list]"; + if (!ids.size()) return "[]"; QString idsStr = QString("[%1").arg(ids.cbegin()->v); for (QVector::const_iterator i = ids.cbegin() + 1, e = ids.cend(); i != e; ++i) { idsStr += QString(", %2").arg(i->v); @@ -405,7 +400,7 @@ QString logVectorLong(const QVector &ids) { } QString logVectorLong(const QVector &ids) { - if (!ids.size()) return "[void list]"; + if (!ids.size()) return "[]"; QString idsStr = QString("[%1").arg(*ids.cbegin()); for (QVector::const_iterator i = ids.cbegin() + 1, e = ids.cend(); i != e; ++i) { idsStr += QString(", %2").arg(*i); diff --git a/Telegram/SourceFiles/logs.h b/Telegram/SourceFiles/logs.h index 56595b7a6d..ff75a6f21d 100644 --- a/Telegram/SourceFiles/logs.h +++ b/Telegram/SourceFiles/logs.h @@ -77,11 +77,21 @@ class MTPlong; QString logVectorLong(const QVector &ids); QString logVectorLong(const QVector &ids); +void logWrite(const QString &v); + #define LOG(msg) (logWrite(QString msg)) //usage LOG(("log: %1 %2").arg(1).arg(2)) -void logWrite(const QString &v); +static volatile int *t_assert_nullptr = 0; +inline void t_noop() {} +inline void t_assert_fail(const char *message, const char *file, int32 line) { + LOG(("Assertion Failed! %1 %2:%3").arg(message).arg(file).arg(line)); + *t_assert_nullptr = 0; +} +#define t_assert_full(condition, message, file, line) ((!(condition)) ? t_assert_fail(message, file, line) : t_noop()) +#define t_assert_c(condition, comment) t_assert_full(condition, "\"" #condition "\" (" comment ")", __FILE__, __LINE__) +#define t_assert(condition) t_assert_full(condition, "\"" #condition "\"", __FILE__, __LINE__) -void logsInit(); +bool logsInit(); void logsInitDebug(); void logsClose(); diff --git a/Telegram/SourceFiles/main.cpp b/Telegram/SourceFiles/main.cpp index 5fa9627cc0..13e86c4d15 100644 --- a/Telegram/SourceFiles/main.cpp +++ b/Telegram/SourceFiles/main.cpp @@ -25,14 +25,10 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "localstorage.h" int main(int argc, char *argv[]) { -#ifdef _NEED_WIN_GENERATE_DUMP +#ifdef Q_OS_WIN _oldWndExceptionFilter = SetUnhandledExceptionFilter(_exceptionFilter); +// CAPIHook apiHook("kernel32.dll", "SetUnhandledExceptionFilter", (PROC)RedirectedSetUnhandledExceptionFilter); #endif -#ifdef _NEED_LINUX_GENERATE_DUMP - //signal(SIGSEGV, _sigsegvHandler); -#endif - - InitOpenSSL _init; settingsParseArgs(argc, argv); for (int32 i = 0; i < argc; ++i) { @@ -42,7 +38,13 @@ int main(int argc, char *argv[]) { return psCleanup(); } } - logsInit(); + if (!logsInit()) { + return 0; + } + + installSignalHandlers(); + + Global::Initializer _init; Local::readSettings(); if (Local::oldSettingsVersion() < AppVersion) { @@ -58,7 +60,7 @@ int main(int argc, char *argv[]) { if (cDebug()) { LOG(("Application Info: Telegram started in debug mode")); for (int32 i = 0; i < argc; ++i) { - LOG(("Argument: %1").arg(QString::fromLocal8Bit(argv[i]))); + LOG(("Argument: %1").arg(fromUtf8Safe(argv[i]))); } QStringList logs = psInitLogs(); for (int32 i = 0, l = logs.size(); i < l; ++i) { diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index b5dd08893b..c1b69b3c2b 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -23,6 +23,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "lang.h" #include "boxes/addcontactbox.h" +#include "fileuploader.h" #include "application.h" #include "window.h" #include "settingswidget.h" @@ -38,6 +39,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org TopBarWidget::TopBarWidget(MainWidget *w) : TWidget(w) , a_over(0) +, _a_appearance(animation(this, &TopBarWidget::step_appearance)) , _selPeer(0) , _selCount(0) , _canDelete(false) @@ -89,18 +91,18 @@ void TopBarWidget::onInfoClicked() { void TopBarWidget::onAddContact() { PeerData *p = App::main() ? App::main()->profilePeer() : 0; UserData *u = p ? p->asUser() : 0; - if (u) App::wnd()->showLayer(new AddContactBox(u->firstName, u->lastName, u->phone.isEmpty() ? App::phoneFromSharedContact(peerToUser(u->id)) : u->phone)); + if (u) Ui::showLayer(new AddContactBox(u->firstName, u->lastName, u->phone.isEmpty() ? App::phoneFromSharedContact(peerToUser(u->id)) : u->phone)); } void TopBarWidget::onEdit() { PeerData *p = App::main() ? App::main()->profilePeer() : 0; if (p) { if (p->isChannel()) { - App::wnd()->showLayer(new EditChannelBox(p->asChannel())); + Ui::showLayer(new EditChannelBox(p->asChannel())); } else if (p->isChat()) { - App::wnd()->showLayer(new EditNameTitleBox(p)); + Ui::showLayer(new EditNameTitleBox(p)); } else if (p->isUser()) { - App::wnd()->showLayer(new AddContactBox(p->asUser())); + Ui::showLayer(new AddContactBox(p->asUser())); } } } @@ -111,7 +113,7 @@ void TopBarWidget::onDeleteContact() { if (u) { ConfirmBox *box = new ConfirmBox(lng_sure_delete_contact(lt_contact, p->name), lang(lng_box_delete)); connect(box, SIGNAL(confirmed()), this, SLOT(onDeleteContactSure())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } } @@ -119,8 +121,8 @@ void TopBarWidget::onDeleteContactSure() { PeerData *p = App::main() ? App::main()->profilePeer() : 0; UserData *u = p ? p->asUser() : 0; if (u) { - App::main()->showDialogs(); - App::wnd()->hideLayer(); + Ui::showChatsList(); + Ui::hideLayer(); MTP::send(MTPcontacts_DeleteContact(u->inputUser), App::main()->rpcDone(&MainWidget::deletedContact, u)); } } @@ -131,7 +133,7 @@ void TopBarWidget::onDeleteAndExit() { if (c) { ConfirmBox *box = new ConfirmBox(lng_sure_delete_and_exit(lt_group, p->name), lang(lng_box_leave), st::attentionBoxButton); connect(box, SIGNAL(confirmed()), this, SLOT(onDeleteAndExitSure())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } } @@ -139,43 +141,41 @@ void TopBarWidget::onDeleteAndExitSure() { PeerData *p = App::main() ? App::main()->profilePeer() : 0; ChatData *c = p ? p->asChat() : 0; if (c) { - App::main()->showDialogs(); - App::wnd()->hideLayer(); + Ui::showChatsList(); + Ui::hideLayer(); MTP::send(MTPmessages_DeleteChatUser(c->inputChat, App::self()->inputUser), App::main()->rpcDone(&MainWidget::deleteHistoryAfterLeave, p), App::main()->rpcFail(&MainWidget::leaveChatFailed, p)); } } void TopBarWidget::enterEvent(QEvent *e) { a_over.start(1); - anim::start(this); + _a_appearance.start(); } void TopBarWidget::enterFromChildEvent(QEvent *e) { a_over.start(1); - anim::start(this); + _a_appearance.start(); } void TopBarWidget::leaveEvent(QEvent *e) { a_over.start(0); - anim::start(this); + _a_appearance.start(); } void TopBarWidget::leaveToChildEvent(QEvent *e) { a_over.start(0); - anim::start(this); + _a_appearance.start(); } -bool TopBarWidget::animStep(float64 ms) { +void TopBarWidget::step_appearance(float64 ms, bool timer) { float64 dt = ms / st::topBarDuration; - bool res = true; if (dt >= 1) { + _a_appearance.stop(); a_over.finish(); - res = false; } else { a_over.update(dt, anim::linear); } - update(); - return res; + if (timer) update(); } void TopBarWidget::paintEvent(QPaintEvent *e) { @@ -383,7 +383,7 @@ MainWidget::MainWidget(Window *window) : TWidget(window) , _started(0) , failedObjId(0) , _toForwardNameVersion(0) -, _a_show(animFunc(this, &MainWidget::animStep_show)) +, _a_show(animation(this, &MainWidget::step_show)) , _dialogsWidth(st::dlgMinWidth) , dialogs(this) , history(this) @@ -422,7 +422,7 @@ MainWidget::MainWidget(Window *window) : TWidget(window) _ptsWaiter.setRequesting(true); updateScrollColors(); - connect(window, SIGNAL(resized(const QSize&)), this, SLOT(onParentResize(const QSize&))); + connect(App::wnd(), SIGNAL(resized(const QSize&)), this, SLOT(onParentResize(const QSize&))); connect(&dialogs, SIGNAL(cancelled()), this, SLOT(dialogsCancelled())); connect(&history, SIGNAL(cancelled()), &dialogs, SLOT(activate())); connect(this, SIGNAL(peerPhotoChanged(PeerData*)), this, SIGNAL(dialogsUpdated())); @@ -438,7 +438,6 @@ MainWidget::MainWidget(Window *window) : TWidget(window) connect(&_topBar, SIGNAL(clicked()), this, SLOT(onTopBarClick())); connect(&history, SIGNAL(historyShown(History*,MsgId)), this, SLOT(onHistoryShown(History*,MsgId))); connect(&updateNotifySettingTimer, SIGNAL(timeout()), this, SLOT(onUpdateNotifySettings())); - connect(this, SIGNAL(showPeerAsync(quint64,qint32)), this, SLOT(showPeerHistory(quint64,qint32)), Qt::QueuedConnection); if (audioPlayer()) { connect(audioPlayer(), SIGNAL(updated(const AudioMsgId&)), this, SLOT(audioPlayProgress(const AudioMsgId&))); connect(audioPlayer(), SIGNAL(stopped(const AudioMsgId&)), this, SLOT(audioPlayProgress(const AudioMsgId&))); @@ -480,7 +479,7 @@ MainWidget::MainWidget(Window *window) : TWidget(window) bool MainWidget::onForward(const PeerId &peer, ForwardWhatMessages what) { PeerData *p = App::peer(peer); if (!peer || (p->isChannel() && !p->asChannel()->canPublish() && p->asChannel()->isBroadcast()) || (p->isChat() && !p->asChat()->canWrite()) || (p->isUser() && p->asUser()->access == UserNoAccess)) { - App::wnd()->showLayer(new InformBox(lang(lng_forward_cant))); + Ui::showLayer(new InformBox(lang(lng_forward_cant))); return false; } history.cancelReply(); @@ -505,7 +504,7 @@ bool MainWidget::onForward(const PeerId &peer, ForwardWhatMessages what) { } } updateForwardingTexts(); - showPeerHistory(peer, ShowAtUnreadMsgId); + Ui::showPeerHistory(peer, ShowAtUnreadMsgId); history.onClearSelected(); history.updateForwarding(); return true; @@ -514,7 +513,7 @@ bool MainWidget::onForward(const PeerId &peer, ForwardWhatMessages what) { bool MainWidget::onShareUrl(const PeerId &peer, const QString &url, const QString &text) { PeerData *p = App::peer(peer); if (!peer || (p->isChannel() && !p->asChannel()->canPublish() && p->asChannel()->isBroadcast()) || (p->isChat() && !p->asChat()->canWrite()) || (p->isUser() && p->asUser()->access == UserNoAccess)) { - App::wnd()->showLayer(new InformBox(lang(lng_share_cant))); + Ui::showLayer(new InformBox(lang(lng_share_cant))); return false; } History *h = App::history(peer); @@ -526,7 +525,7 @@ bool MainWidget::onShareUrl(const PeerId &peer, const QString &url, const QStrin if (opened) { history.applyDraft(); } else { - showPeerHistory(peer, ShowAtUnreadMsgId); + Ui::showPeerHistory(peer, ShowAtUnreadMsgId); } return true; } @@ -602,7 +601,7 @@ void MainWidget::cancelForwarding() { void MainWidget::finishForwarding(History *hist, bool broadcast) { if (!hist) return; - + bool fromChannelName = hist->peer->isChannel() && !hist->peer->isMegagroup() && hist->peer->asChannel()->canPublish() && (hist->peer->asChannel()->isBroadcast() || broadcast); if (!_toForward.isEmpty()) { bool genClientSideMessage = (_toForward.size() < 2); @@ -621,8 +620,10 @@ void MainWidget::finishForwarding(History *hist, bool broadcast) { FullMsgId newId(peerToChannel(hist->peer->id), clientMsgId()); HistoryMessage *msg = static_cast(_toForward.cbegin().value()); hist->addNewForwarded(newId.msg, date(MTP_int(unixtime())), fromChannelName ? 0 : MTP::authedId(), msg); - if (HistorySticker *sticker = dynamic_cast(msg->getMedia())) { - App::main()->incrementSticker(sticker->document()); + if (HistoryMedia *media = msg->getMedia()) { + if (media->type() == MediaTypeSticker) { + App::main()->incrementSticker(media->getDocument()); + } } App::historyRegRandom(randomId, newId); } @@ -666,7 +667,7 @@ void MainWidget::webPagesUpdate() { if (j != items.cend()) { for (HistoryItemsMap::const_iterator k = j.value().cbegin(), e = j.value().cend(); k != e; ++k) { k.key()->initDimensions(); - itemResized(k.key()); + Notify::historyItemResized(k.key()); } } } @@ -684,18 +685,6 @@ void MainWidget::updateStickers() { history.updateStickers(); } -void MainWidget::notifyBotCommandsChanged(UserData *bot) { - history.notifyBotCommandsChanged(bot); -} - -void MainWidget::notifyUserIsBotChanged(UserData *bot) { - history.notifyUserIsBotChanged(bot); -} - -void MainWidget::notifyMigrateUpdated(PeerData *peer) { - history.notifyMigrateUpdated(peer); -} - void MainWidget::onUpdateMuted() { App::updateMuted(); } @@ -716,7 +705,7 @@ void MainWidget::onFilesOrForwardDrop(const PeerId &peer, const QMimeData *data) } else if (data->hasFormat(qsl("application/x-td-forward-pressed"))) { onForward(peer, ForwardPressedMessage); } else { - showPeerHistory(peer, ShowAtTheEndMsgId); + Ui::showPeerHistory(peer, ShowAtTheEndMsgId); history.onFilesDrop(data); } } @@ -735,6 +724,13 @@ QPixmap MainWidget::grabInner() { } } +bool MainWidget::isItemVisible(HistoryItem *item) { + if (isHidden() || _a_show.animating()) { + return false; + } + return history.isItemVisible(item); +} + QPixmap MainWidget::grabTopBar() { if (!_topBar.isHidden()) { return myGrab(&_topBar); @@ -746,7 +742,7 @@ QPixmap MainWidget::grabTopBar() { } void MainWidget::ui_showStickerPreview(DocumentData *sticker) { - if (!sticker || !sticker->sticker()) return; + if (!sticker || ((!sticker->isAnimation() || !sticker->loaded()) && !sticker->sticker())) return; if (!_stickerPreview) { _stickerPreview = new StickerPreviewWidget(this); resizeEvent(0); @@ -759,6 +755,86 @@ void MainWidget::ui_hideStickerPreview() { _stickerPreview->hidePreview(); } +void MainWidget::notify_botCommandsChanged(UserData *bot) { + history.notify_botCommandsChanged(bot); +} + +void MainWidget::notify_inlineBotRequesting(bool requesting) { + history.notify_inlineBotRequesting(requesting); +} + +void MainWidget::notify_userIsBotChanged(UserData *bot) { + history.notify_userIsBotChanged(bot); +} + +void MainWidget::notify_userIsContactChanged(UserData *user, bool fromThisApp) { + if (!user) return; + + dialogs.notify_userIsContactChanged(user, fromThisApp); + + const SharedContactItems &items(App::sharedContactItems()); + SharedContactItems::const_iterator i = items.constFind(peerToUser(user->id)); + if (i != items.cend()) { + for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) { + j.key()->initDimensions(); + Ui::repaintHistoryItem(j.key()); + } + } + + if (user->contact > 0 && fromThisApp) { + Ui::showPeerHistory(user->id, ShowAtTheEndMsgId); + } +} + +void MainWidget::notify_migrateUpdated(PeerData *peer) { + history.notify_migrateUpdated(peer); +} + +void MainWidget::notify_clipStopperHidden(ClipStopperType type) { + history.notify_clipStopperHidden(type); +} + +void MainWidget::ui_repaintHistoryItem(const HistoryItem *item) { + history.ui_repaintHistoryItem(item); + if (!item->history()->dialogs.isEmpty() && item->history()->lastMsg == item) { + dialogs.dlgUpdated(item->history()->dialogs[0]); + } + if (overview) overview->ui_repaintHistoryItem(item); +} + +void MainWidget::ui_repaintInlineItem(const LayoutInlineItem *layout) { + history.ui_repaintInlineItem(layout); +} + +bool MainWidget::ui_isInlineItemVisible(const LayoutInlineItem *layout) { + return history.ui_isInlineItemVisible(layout); +} + +bool MainWidget::ui_isInlineItemBeingChosen() { + return history.ui_isInlineItemBeingChosen(); +} + +void MainWidget::notify_historyItemLayoutChanged(const HistoryItem *item) { + history.notify_historyItemLayoutChanged(item); + if (overview) overview->notify_historyItemLayoutChanged(item); +} + +void MainWidget::notify_automaticLoadSettingsChangedGif() { + history.notify_automaticLoadSettingsChangedGif(); +} + +void MainWidget::notify_historyItemResized(const HistoryItem *item, bool scrollToIt) { + if (!item || ((history.peer() == item->history()->peer || (history.peer() && history.peer() == item->history()->peer->migrateTo())) && !item->detached())) { + history.notify_historyItemResized(item, scrollToIt); + } else if (item) { + item->history()->width = 0; + if (history.peer() == item->history()->peer || (history.peer() && history.peer() == item->history()->peer->migrateTo())) { + history.resizeEvent(0); + } + } + if (item) Ui::repaintHistoryItem(item); +} + void MainWidget::noHider(HistoryHider *destroyed) { if (_hider == destroyed) { _hider = 0; @@ -831,13 +907,20 @@ void MainWidget::forwardLayer(int32 forwardSelected) { void MainWidget::deleteLayer(int32 selectedCount) { QString str((selectedCount < 0) ? lang(selectedCount < -1 ? lng_selected_cancel_sure_this : lng_selected_delete_sure_this) : lng_selected_delete_sure(lt_count, selectedCount)); - ConfirmBox *box = new ConfirmBox((selectedCount < 0) ? str : str.arg(selectedCount), lang(lng_box_delete)); + QString btn(lang((selectedCount < -1) ? lng_selected_upload_stop : lng_box_delete)), cancel(lang((selectedCount < -1) ? lng_continue : lng_cancel)); + ConfirmBox *box = new ConfirmBox(str, btn, st::defaultBoxButton, cancel); if (selectedCount < 0) { + if (selectedCount < -1) { + if (HistoryItem *item = App::contextItem()) { + App::uploader()->pause(item->fullId()); + connect(box, SIGNAL(destroyed(QObject*)), App::uploader(), SLOT(unpause())); + } + } connect(box, SIGNAL(confirmed()), overview ? overview : static_cast(&history), SLOT(onDeleteContextSure())); } else { connect(box, SIGNAL(confirmed()), overview ? overview : static_cast(&history), SLOT(onDeleteSelectedSure())); } - App::wnd()->showLayer(box); + Ui::showLayer(box); } void MainWidget::shareContactLayer(UserData *contact) { @@ -853,13 +936,13 @@ bool MainWidget::selectingPeer(bool withConfirm) { } void MainWidget::offerPeer(PeerId peer) { - App::wnd()->hideLayer(); + Ui::hideLayer(); if (_hider->offerPeer(peer) && !cWideMode()) { _forwardConfirm = new ConfirmBox(_hider->offeredText(), lang(lng_forward_send)); connect(_forwardConfirm, SIGNAL(confirmed()), _hider, SLOT(forward())); connect(_forwardConfirm, SIGNAL(cancelled()), this, SLOT(onForwardCancel())); connect(_forwardConfirm, SIGNAL(destroyed(QObject*)), this, SLOT(onForwardCancel(QObject*))); - App::wnd()->showLayer(_forwardConfirm); + Ui::showLayer(_forwardConfirm); } } @@ -935,7 +1018,7 @@ void MainWidget::removeDialog(History *history) { void MainWidget::deleteConversation(PeerData *peer, bool deleteHistory) { if (activePeer() == peer) { - showDialogs(); + Ui::showChatsList(); } if (History *h = App::historyLoaded(peer->id)) { removeDialog(h); @@ -971,14 +1054,10 @@ void MainWidget::clearHistory(PeerData *peer) { h->clear(); h->newLoaded = h->oldLoaded = true; } - showPeerHistory(peer->id, ShowAtUnreadMsgId); + Ui::showPeerHistory(peer->id, ShowAtUnreadMsgId); MTP::send(MTPmessages_DeleteHistory(peer->input, MTP_int(0)), rpcDone(&MainWidget::deleteHistoryPart, peer)); } -void MainWidget::removeContact(UserData *user) { - dialogs.removeContact(user); -} - void MainWidget::addParticipants(PeerData *chatOrChannel, const QVector &users) { if (chatOrChannel->isChat()) { for (QVector::const_iterator i = users.cbegin(), e = users.cend(); i != e; ++i) { @@ -1012,7 +1091,7 @@ bool MainWidget::addParticipantFail(UserData *user, const RPCError &error) { } else if (error.type() == "PEER_FLOOD") { text = 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))); } - App::wnd()->showLayer(new InformBox(text)); + Ui::showLayer(new InformBox(text)); return false; } @@ -1026,14 +1105,14 @@ bool MainWidget::addParticipantsFail(ChannelData *channel, const RPCError &error } else if (error.type() == "PEER_FLOOD") { text = 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))); } - App::wnd()->showLayer(new InformBox(text)); + Ui::showLayer(new InformBox(text)); return false; } void MainWidget::kickParticipant(ChatData *chat, UserData *user) { MTP::send(MTPmessages_DeleteChatUser(chat->inputChat, user->inputUser), rpcDone(&MainWidget::sentUpdatesReceived), rpcFail(&MainWidget::kickParticipantFail, chat)); - App::wnd()->hideLayer(); - showPeerHistory(chat->id, ShowAtTheEndMsgId); + Ui::hideLayer(); + Ui::showPeerHistory(chat->id, ShowAtTheEndMsgId); } bool MainWidget::kickParticipantFail(ChatData *chat, const RPCError &error) { @@ -1131,7 +1210,7 @@ bool MainWidget::sendMessageFail(const RPCError &error) { if (mtpIsFlood(error)) return false; if (error.type() == qsl("PEER_FLOOD")) { - App::wnd()->showLayer(new InformBox(lng_cant_send_to_not_contact(lt_more_info, textcmdLink(qsl("https://telegram.org/faq?_hash=can-39t-send-messages-to-non-contacts"), lang(lng_cant_more_info))))); + Ui::showLayer(new InformBox(lng_cant_send_to_not_contact(lt_more_info, textcmdLink(qsl("https://telegram.org/faq?_hash=can-39t-send-messages-to-non-contacts"), lang(lng_cant_more_info))))); return true; } return false; @@ -1248,7 +1327,7 @@ void MainWidget::sendMessage(History *hist, const QString &text, MsgId replyTo, if (!sentEntities.c_vector().v.isEmpty()) { sendFlags |= MTPmessages_SendMessage::flag_entities; } - hist->addNewMessage(MTP_message(MTP_int(flags), MTP_int(newId.msg), MTP_int(fromChannelName ? 0 : MTP::authedId()), peerToMTP(hist->peer->id), MTPPeer(), MTPint(), MTP_int(replyTo), MTP_int(unixtime()), msgText, media, MTPnullMarkup, localEntities, MTP_int(1)), NewMessageUnread); + hist->addNewMessage(MTP_message(MTP_int(flags), MTP_int(newId.msg), MTP_int(fromChannelName ? 0 : MTP::authedId()), peerToMTP(hist->peer->id), MTPPeer(), MTPint(), MTPint(), MTP_int(replyTo), MTP_int(unixtime()), msgText, media, MTPnullMarkup, localEntities, MTP_int(1)), NewMessageUnread); hist->sendRequestId = MTP::send(MTPmessages_SendMessage(MTP_int(sendFlags), hist->peer->input, MTP_int(replyTo), msgText, MTP_long(randomId), MTPnullMarkup, sentEntities), rpcDone(&MainWidget::sentUpdatesReceived, randomId), rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId); } @@ -1271,7 +1350,7 @@ void MainWidget::saveRecentHashtags(const QString &text) { } } if (!found && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) { - Local::readRecentHashtags(); + Local::readRecentHashtagsAndBots(); recent = cRecentWriteHashtags(); } found = true; @@ -1279,13 +1358,13 @@ void MainWidget::saveRecentHashtags(const QString &text) { } if (found) { cSetRecentWriteHashtags(recent); - Local::writeRecentHashtags(); + Local::writeRecentHashtagsAndBots(); } } void MainWidget::readServerHistory(History *hist, bool force) { if (!hist || (!force && !hist->unreadCount)) return; - + MsgId upTo = hist->inboxRead(0); if (hist->isChannel() && !hist->peer->asChannel()->amIn()) { return; // no read request for channels that I didn't koin @@ -1304,8 +1383,8 @@ void MainWidget::readServerHistory(History *hist, bool force) { } } -uint64 MainWidget::animActiveTime(const HistoryItem *msg) const { - return history.animActiveTime(msg); +uint64 MainWidget::animActiveTimeStart(const HistoryItem *msg) const { + return history.animActiveTimeStart(msg); } void MainWidget::stopAnimActive() { @@ -1316,15 +1395,15 @@ void MainWidget::sendBotCommand(const QString &cmd, MsgId replyTo) { history.sendBotCommand(cmd, replyTo); } -void MainWidget::insertBotCommand(const QString &cmd) { - history.insertBotCommand(cmd); +bool MainWidget::insertBotCommand(const QString &cmd, bool specialGif) { + return history.insertBotCommand(cmd, specialGif); } void MainWidget::searchMessages(const QString &query, PeerData *inPeer) { App::wnd()->hideMediaview(); dialogs.searchMessages(query, inPeer); if (!cWideMode()) { - showDialogs(); + Ui::showChatsList(); } else { dialogs.activate(); } @@ -1427,7 +1506,6 @@ void MainWidget::itemRemoved(HistoryItem *item) { if (overview && (overview->peer() == item->history()->peer || (overview->peer() && overview->peer() == item->history()->peer->migrateTo()))) { overview->itemRemoved(item); } - itemRemovedGif(item); if (!_toForward.isEmpty()) { SelectedItemSet::iterator i = _toForward.find(item->id); if (i != _toForward.cend() && i.value() == item) { @@ -1443,41 +1521,6 @@ void MainWidget::itemRemoved(HistoryItem *item) { } } -void MainWidget::itemReplaced(HistoryItem *oldItem, HistoryItem *newItem) { - api()->itemReplaced(oldItem, newItem); - dialogs.itemReplaced(oldItem, newItem); - if (history.peer() == newItem->history()->peer || (history.peer() && history.peer() == newItem->history()->peer->migrateTo())) { - history.itemReplaced(oldItem, newItem); - } - itemReplacedGif(oldItem, newItem); - if (!_toForward.isEmpty()) { - SelectedItemSet::iterator i = _toForward.find(oldItem->id); - if (i != _toForward.cend() && i.value() == oldItem) { - i.value() = newItem; - } else { - i = _toForward.find(oldItem->id - ServerMaxMsgId); - if (i != _toForward.cend() && i.value() == oldItem) { - i.value() = newItem; - } - } - } -} - -void MainWidget::itemResized(HistoryItem *row, bool scrollToIt) { - if (!row || ((history.peer() == row->history()->peer || (history.peer() && history.peer() == row->history()->peer->migrateTo())) && !row->detached())) { - history.itemResized(row, scrollToIt); - } else if (row) { - row->history()->width = 0; - if (history.peer() == row->history()->peer || (history.peer() && history.peer() == row->history()->peer->migrateTo())) { - history.resizeEvent(0); - } - } - if (overview) { - overview->itemResized(row, scrollToIt); - } - if (row) msgUpdated(row); -} - bool MainWidget::overviewFailed(PeerData *peer, const RPCError &error, mtpRequestId req) { if (mtpIsFlood(error)) return false; @@ -1499,7 +1542,7 @@ void MainWidget::loadMediaBack(PeerData *peer, MediaOverviewType type, bool many if (history->overviewLoaded(type)) return; MsgId minId = history->overviewMinId(type); - int32 limit = many ? SearchManyPerPage : (history->overview[type].size() > MediaOverviewStartPerPage) ? SearchPerPage : MediaOverviewStartPerPage; + int32 limit = (many || history->overview[type].size() > MediaOverviewStartPerPage) ? SearchPerPage : MediaOverviewStartPerPage; MTPMessagesFilter filter = typeToMediaFilter(type); if (type == OverviewCount) return; @@ -1604,28 +1647,20 @@ void MainWidget::messagesAffected(PeerData *peer, const MTPmessages_AffectedMess } } -void MainWidget::videoLoadProgress(mtpFileLoader *loader) { - VideoData *video = App::video(loader->objId()); - if (video->loader) { - video->status = FileReady; - if (video->loader->done()) { - video->finish(); - QString already = video->already(); - if (!already.isEmpty() && video->openOnSave) { - QPoint pos(QCursor::pos()); - if (video->openOnSave < 0 && !psShowOpenWithMenu(pos.x(), pos.y(), already)) { - psOpenFile(already, true); - } else { - psOpenFile(already, video->openOnSave < 0); - } - } - } +void MainWidget::videoLoadProgress(FileLoader *loader) { + mtpFileLoader *l = loader ? loader->mtpLoader() : 0; + if (!l) return; + + VideoData *video = App::video(l->objId()); + if (video->loaded()) { + video->performActionOnLoad(); } + const VideoItems &items(App::videoItems()); VideoItems::const_iterator i = items.constFind(video); if (i != items.cend()) { for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) { - msgUpdated(j.key()); + Ui::repaintHistoryItem(j.key()); } } } @@ -1639,7 +1674,7 @@ void MainWidget::loadFailed(mtpFileLoader *loader, bool started, const char *ret } else { connect(box, SIGNAL(confirmed()), this, SLOT(onDownloadPathSettings())); } - App::wnd()->showLayer(box); + Ui::showLayer(box); } void MainWidget::onDownloadPathSettings() { @@ -1649,57 +1684,45 @@ void MainWidget::onDownloadPathSettings() { if (App::wnd() && App::wnd()->settingsWidget()) { connect(box, SIGNAL(closed()), App::wnd()->settingsWidget(), SLOT(onDownloadPathEdited())); } - App::wnd()->showLayer(box); + Ui::showLayer(box); } -void MainWidget::videoLoadFailed(mtpFileLoader *loader, bool started) { - loadFailed(loader, started, SLOT(videoLoadRetry())); - VideoData *video = App::video(loader->objId()); - if (video && video->loader) video->finish(); +void MainWidget::ui_showPeerHistoryAsync(quint64 peerId, qint32 showAtMsgId) { + Ui::showPeerHistory(peerId, showAtMsgId); +} + +void MainWidget::videoLoadFailed(FileLoader *loader, bool started) { + mtpFileLoader *l = loader ? loader->mtpLoader() : 0; + if (!l) return; + + loadFailed(l, started, SLOT(videoLoadRetry())); + VideoData *video = App::video(l->objId()); + if (video) { + if (video->loading()) video->cancel(); + video->status = FileDownloadFailed; + } } void MainWidget::videoLoadRetry() { - App::wnd()->hideLayer(); + Ui::hideLayer(); VideoData *video = App::video(failedObjId); if (video) video->save(failedFileName); } -void MainWidget::audioLoadProgress(mtpFileLoader *loader) { - AudioData *audio = App::audio(loader->objId()); - if (audio->loader) { - audio->status = FileReady; - if (audio->loader->done()) { - audio->finish(); - QString already = audio->already(); - bool play = audio->openOnSave > 0 && audio->openOnSaveMsgId.msg && audioPlayer(); - if ((!already.isEmpty() && audio->openOnSave) || (!audio->data.isEmpty() && play)) { - if (play) { - AudioMsgId playing; - AudioPlayerState state = AudioPlayerStopped; - audioPlayer()->currentState(&playing, &state); - if (playing.msgId == audio->openOnSaveMsgId && !(state & AudioPlayerStoppedMask) && state != AudioPlayerFinishing) { - audioPlayer()->pauseresume(OverviewAudios); - } else { - audioPlayer()->play(AudioMsgId(audio, audio->openOnSaveMsgId)); - if (App::main()) App::main()->audioMarkRead(audio); - } - } else { - QPoint pos(QCursor::pos()); - if (audio->openOnSave < 0 && !psShowOpenWithMenu(pos.x(), pos.y(), already)) { - psOpenFile(already, true); - } else { - psOpenFile(already, audio->openOnSave < 0); - } - if (App::main()) App::main()->audioMarkRead(audio); - } - } - } +void MainWidget::audioLoadProgress(FileLoader *loader) { + mtpFileLoader *l = loader ? loader->mtpLoader() : 0; + if (!l) return; + + AudioData *audio = App::audio(l->objId()); + if (audio->loaded()) { + audio->performActionOnLoad(); } + const AudioItems &items(App::audioItems()); AudioItems::const_iterator i = items.constFind(audio); if (i != items.cend()) { for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) { - msgUpdated(j.key()); + Ui::repaintHistoryItem(j.key()); } } } @@ -1713,13 +1736,13 @@ void MainWidget::audioPlayProgress(const AudioMsgId &audioId) { AudioData *audio = audioId.audio; QString already = audio->already(true); - if (already.isEmpty() && !audio->data.isEmpty()) { + if (already.isEmpty() && !audio->data().isEmpty()) { bool mp3 = (audio->mime == qstr("audio/mp3")); QString filename = saveFileName(lang(lng_save_audio), mp3 ? qsl("MP3 Audio (*.mp3);;All files (*.*)") : qsl("OGG Opus Audio (*.ogg);;All files (*.*)"), qsl("audio"), mp3 ? qsl(".mp3") : qsl(".ogg"), false); if (!filename.isEmpty()) { QFile f(filename); if (f.open(QIODevice::WriteOnly)) { - if (f.write(audio->data) == audio->data.size()) { + if (f.write(audio->data()) == audio->data().size()) { f.close(); already = filename; audio->setLocation(FileLocation(StorageFilePartial, filename)); @@ -1734,7 +1757,7 @@ void MainWidget::audioPlayProgress(const AudioMsgId &audioId) { } if (HistoryItem *item = App::histItemById(audioId.msgId)) { - msgUpdated(item); + Ui::repaintHistoryItem(item); } } @@ -1750,7 +1773,7 @@ void MainWidget::documentPlayProgress(const SongMsgId &songId) { DocumentData *document = songId.song; QString already = document->already(true); - if (already.isEmpty() && !document->data.isEmpty()) { + if (already.isEmpty() && !document->data().isEmpty()) { QString name = document->name, filter; MimeType mimeType = mimeTypeForName(document->mime); QStringList p = mimeType.globPatterns(); @@ -1767,7 +1790,7 @@ void MainWidget::documentPlayProgress(const SongMsgId &songId) { if (!filename.isEmpty()) { QFile f(filename); if (f.open(QIODevice::WriteOnly)) { - if (f.write(document->data) == document->data.size()) { + if (f.write(document->data()) == document->data().size()) { f.close(); already = filename; document->setLocation(FileLocation(StorageFilePartial, filename)); @@ -1795,7 +1818,7 @@ void MainWidget::documentPlayProgress(const SongMsgId &songId) { } if (HistoryItem *item = App::histItemById(songId.msgId)) { - msgUpdated(item); + Ui::repaintHistoryItem(item); } } @@ -1808,116 +1831,90 @@ void MainWidget::hidePlayer() { } } -void MainWidget::audioLoadFailed(mtpFileLoader *loader, bool started) { - loadFailed(loader, started, SLOT(audioLoadRetry())); - AudioData *audio = App::audio(loader->objId()); +void MainWidget::audioLoadFailed(FileLoader *loader, bool started) { + mtpFileLoader *l = loader ? loader->mtpLoader() : 0; + if (!l) return; + + loadFailed(l, started, SLOT(audioLoadRetry())); + AudioData *audio = App::audio(l->objId()); if (audio) { - audio->status = FileFailed; - if (audio->loader) audio->finish(); + if (audio->loading()) audio->cancel(); + audio->status = FileDownloadFailed; } } void MainWidget::audioLoadRetry() { - App::wnd()->hideLayer(); + Ui::hideLayer(); AudioData *audio = App::audio(failedObjId); if (audio) audio->save(failedFileName); } -void MainWidget::documentLoadProgress(mtpFileLoader *loader) { - bool songPlayActivated = false; - DocumentData *document = App::document(loader->objId()); - if (document->loader) { - document->status = FileReady; - if (document->loader->done()) { - document->finish(); - QString already = document->already(); +void MainWidget::documentLoadProgress(FileLoader *loader) { + mtpFileLoader *l = loader ? loader->mtpLoader() : 0; + if (!l) return; - HistoryItem *item = (document->openOnSave && document->openOnSaveMsgId.msg) ? App::histItemById(document->openOnSaveMsgId) : 0; - bool play = document->song() && audioPlayer() && document->openOnSave && item; - if ((!already.isEmpty() || (!document->data.isEmpty() && play)) && document->openOnSave) { - if (play) { - SongMsgId playing; - AudioPlayerState playingState = AudioPlayerStopped; - audioPlayer()->currentState(&playing, &playingState); - if (playing.msgId == item->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { - audioPlayer()->pauseresume(OverviewDocuments); - } else { - SongMsgId song(document, item->fullId()); - audioPlayer()->play(song); - if (App::main()) App::main()->documentPlayProgress(song); - } - - songPlayActivated = true; - } else if (document->openOnSave > 0 && document->size < MediaViewImageSizeLimit) { - const FileLocation &location(document->location(true)); - if (location.accessEnable()) { - QImageReader reader(location.name()); - if (reader.canRead()) { - if (reader.supportsAnimation() && reader.imageCount() > 1 && item) { - startGif(item, location); - } else if (item) { - App::wnd()->showDocument(document, item); - } else { - psOpenFile(already); - } - } else { - psOpenFile(already); - } - location.accessDisable(); - } else { - psOpenFile(already); - } - } else { - QPoint pos(QCursor::pos()); - if (document->openOnSave < 0 && !psShowOpenWithMenu(pos.x(), pos.y(), already)) { - psOpenFile(already, true); - } else { - psOpenFile(already, document->openOnSave < 0); - } - } - } - } + DocumentData *document = App::document(l->objId()); + if (document->loaded()) { + document->performActionOnLoad(); } + const DocumentItems &items(App::documentItems()); DocumentItems::const_iterator i = items.constFind(document); if (i != items.cend()) { for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) { - msgUpdated(j.key()); + Ui::repaintHistoryItem(j.key()); } } App::wnd()->documentUpdated(document); - if (!songPlayActivated && audioPlayer()) { + if (!document->loaded() && document->loading() && document->song() && audioPlayer()) { SongMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; int64 playingPosition = 0, playingDuration = 0; int32 playingFrequency = 0; audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); if (playing.song == document && !_player.isHidden()) { - if (document->loader) { - _player.updateState(playing, playingState, playingPosition, playingDuration, playingFrequency); - } else { - audioPlayer()->play(playing); - } + _player.updateState(playing, playingState, playingPosition, playingDuration, playingFrequency); } } } -void MainWidget::documentLoadFailed(mtpFileLoader *loader, bool started) { - loadFailed(loader, started, SLOT(documentLoadRetry())); - DocumentData *document = App::document(loader->objId()); +void MainWidget::documentLoadFailed(FileLoader *loader, bool started) { + mtpFileLoader *l = loader ? loader->mtpLoader() : 0; + if (!l) return; + + loadFailed(l, started, SLOT(documentLoadRetry())); + DocumentData *document = App::document(l->objId()); if (document) { - if (document->loader) document->finish(); - document->status = FileFailed; + if (document->loading()) document->cancel(); + document->status = FileDownloadFailed; } } void MainWidget::documentLoadRetry() { - App::wnd()->hideLayer(); + Ui::hideLayer(); DocumentData *document = App::document(failedObjId); if (document) document->save(failedFileName); } +void MainWidget::inlineResultLoadProgress(FileLoader *loader) { + //InlineResult *result = App::inlineResultFromLoader(loader); + //if (!result) return; + + //result->loaded(); + + //Ui::repaintInlineItem(); +} + +void MainWidget::inlineResultLoadFailed(FileLoader *loader, bool started) { + //InlineResult *result = App::inlineResultFromLoader(loader); + //if (!result) return; + + //result->loaded(); + + //Ui::repaintInlineItem(); +} + void MainWidget::audioMarkRead(AudioData *data) { const AudioItems &items(App::audioItems()); AudioItems::const_iterator i = items.constFind(data); @@ -1994,7 +1991,7 @@ void MainWidget::serviceNotification(const QString &msg, const MTPMessageMedia & HistoryItem *item = 0; while (textSplit(sendingText, sendingEntities, leftText, leftEntities, MaxMessageSize)) { MTPVector localEntities = linksToMTP(sendingEntities); - item = App::histories().addNewMessage(MTP_message(MTP_int(flags), MTP_int(clientMsgId()), MTP_int(ServiceUserId), MTP_peerUser(MTP_int(MTP::authedId())), MTPPeer(), MTPint(), MTPint(), MTP_int(unixtime()), MTP_string(sendingText), media, MTPnullMarkup, localEntities, MTPint()), NewMessageUnread); + item = App::histories().addNewMessage(MTP_message(MTP_int(flags), MTP_int(clientMsgId()), MTP_int(ServiceUserId), MTP_peerUser(MTP_int(MTP::authedId())), MTPPeer(), MTPint(), MTPint(), MTPint(), MTP_int(unixtime()), MTP_string(sendingText), media, MTPnullMarkup, localEntities, MTPint()), NewMessageUnread); } if (item) { history.peerMessagesUpdated(item->history()->peer->id); @@ -2094,7 +2091,7 @@ void MainWidget::updateScrollColors() { void MainWidget::setChatBackground(const App::WallPaper &wp) { _background = new App::WallPaper(wp); - _background->full->load(); + _background->full->loadEvenCancelled(); checkChatBackground(); } @@ -2243,7 +2240,7 @@ void MainWidget::choosePeer(PeerId peerId, MsgId showAtMsgId) { if (selectingPeer()) { offerPeer(peerId); } else { - showPeerHistory(peerId, showAtMsgId); + Ui::showPeerHistory(peerId, showAtMsgId); } } @@ -2269,7 +2266,7 @@ void MainWidget::ctrlEnterSubmitUpdated() { history.ctrlEnterSubmitUpdated(); } -void MainWidget::showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool back) { +void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool back) { if (PeerData *peer = App::peerLoaded(peerId)) { if (peer->migrateTo()) { peerId = peer->migrateTo()->id; @@ -2282,7 +2279,7 @@ void MainWidget::showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool back) PeerData *wasActivePeer = activePeer(); - App::wnd()->hideLayer(); + Ui::hideLayer(); if (_hider) { _hider->startHide(); _hider = 0; @@ -2544,7 +2541,7 @@ void MainWidget::showPeerProfile(PeerData *peer, bool back, int32 lastScrollTop) void MainWidget::showBackFromStack() { if (selectingPeer()) return; if (_stack.isEmpty()) { - showDialogs(); + Ui::showChatsList(); if (App::wnd()) QTimer::singleShot(0, App::wnd(), SLOT(setInnerFocus())); return; } @@ -2563,7 +2560,7 @@ void MainWidget::showBackFromStack() { } } StackItemHistory *histItem = static_cast(item); - showPeerHistory(histItem->peer->id, App::main()->activeMsgId(), true); + Ui::showPeerHistory(histItem->peer->id, App::main()->activeMsgId(), true); history.setReplyReturns(histItem->peer->id, histItem->replyReturns); } else if (item->type() == ProfileStackItem) { StackItemProfile *profItem = static_cast(item); @@ -2628,15 +2625,6 @@ void MainWidget::onActiveChannelUpdateFull() { } } -void MainWidget::msgUpdated(const HistoryItem *msg) { - if (!msg) return; - history.msgUpdated(msg); - if (!msg->history()->dialogs.isEmpty() && msg->history()->lastMsg == msg) { - dialogs.dlgUpdated(msg->history()->dialogs[0]); - } - if (overview) overview->msgUpdated(msg); -} - void MainWidget::historyToDown(History *hist) { history.historyToDown(hist); } @@ -2676,13 +2664,11 @@ void MainWidget::animShow(const QPixmap &bgAnimCache, bool back) { show(); } -bool MainWidget::animStep_show(float64 ms) { +void MainWidget::step_show(float64 ms, bool timer) { float64 dt = ms / st::slideDuration; - bool res = true; if (dt >= 1) { _a_show.stop(); - res = false; a_coordUnder.finish(); a_coordOver.finish(); a_shadow.finish(); @@ -2698,8 +2684,7 @@ bool MainWidget::animStep_show(float64 ms) { a_coordOver.update(dt, st::slideFunction); a_shadow.update(dt, st::slideFunction); } - update(); - return res; + if (timer) update(); } void MainWidget::animStop_show() { @@ -2741,13 +2726,13 @@ void MainWidget::hideAll() { void MainWidget::showAll() { if (cPasswordRecovered()) { cSetPasswordRecovered(false); - App::wnd()->showLayer(new InformBox(lang(lng_signin_password_removed))); + Ui::showLayer(new InformBox(lang(lng_signin_password_removed))); } if (cWideMode()) { if (_hider) { _hider->show(); if (_forwardConfirm) { - App::wnd()->hideLayer(true); + Ui::hideLayer(true); _forwardConfirm = 0; } } @@ -2770,7 +2755,7 @@ void MainWidget::showAll() { _forwardConfirm = new ConfirmBox(_hider->offeredText(), lang(lng_forward_send)); connect(_forwardConfirm, SIGNAL(confirmed()), _hider, SLOT(forward())); connect(_forwardConfirm, SIGNAL(cancelled()), this, SLOT(onForwardCancel())); - App::wnd()->showLayer(_forwardConfirm, true); + Ui::showLayer(_forwardConfirm, ForceFastShowLayer); } } if (selectingPeer()) { @@ -2866,10 +2851,6 @@ bool MainWidget::needBackButton() { return overview || profile || (history.peer() && history.peer()->id); } -void MainWidget::showDialogs() { - showPeerHistory(0, 0); -} - void MainWidget::paintTopBar(QPainter &p, float64 over, int32 decreaseWidth) { if (profile) { profile->paintTopBar(p, over, decreaseWidth); @@ -2948,7 +2929,7 @@ void MainWidget::searchInPeer(PeerData *peer) { dialogs.activate(); } else { dialogsToUp(); - showDialogs(); + Ui::showChatsList(); } } @@ -3016,7 +2997,7 @@ void MainWidget::updSetState(int32 pts, int32 date, int32 qts, int32 seq) { void MainWidget::gotChannelDifference(ChannelData *channel, const MTPupdates_ChannelDifference &diff) { _channelFailDifferenceTimeout.remove(channel); - + int32 timeout = 0; bool isFinal = true; switch (diff.type()) { @@ -3069,7 +3050,7 @@ void MainWidget::gotChannelDifference(ChannelData *channel, const MTPupdates_Cha App::feedUsers(d.vusers); App::feedChats(d.vchats, false); - + _handlingChannelDifference = true; feedMessageIds(d.vother_updates); @@ -3222,7 +3203,7 @@ void MainWidget::gotDifference(const MTPupdates_Difference &diff) { noUpdatesTimer.start(NoUpdatesTimeout); _ptsWaiter.setRequesting(false); - + App::emitPeerUpdated(); } break; case mtpc_updates_differenceSlice: { @@ -3489,6 +3470,7 @@ void MainWidget::start(const MTPUser &user) { _started = true; App::wnd()->sendServiceHistoryRequest(); Local::readStickers(); + Local::readSavedGifs(); history.start(); } @@ -3538,12 +3520,10 @@ void MainWidget::openPeerByName(const QString &username, bool toProfile, const Q PeerData *peer = App::peerByName(username); if (peer) { - if (toProfile) { + if (toProfile && !peer->isChannel()) { if (peer->isUser() && peer->asUser()->botInfo && !peer->asUser()->botInfo->cantJoinGroups && !startToken.isEmpty()) { peer->asUser()->botInfo->startGroupToken = startToken; - App::wnd()->showLayer(new ContactsBox(peer->asUser())); - } else if (peer->isChannel()) { - showPeerHistory(peer->id, ShowAtUnreadMsgId); + Ui::showLayer(new ContactsBox(peer->asUser())); } else { showPeerProfile(peer); } @@ -3555,7 +3535,7 @@ void MainWidget::openPeerByName(const QString &username, bool toProfile, const Q history.resizeEvent(0); } } - emit showPeerAsync(peer->id, 0); + Ui::showPeerHistoryAsync(peer->id, ShowAtUnreadMsgId); } } else { MTP::send(MTPcontacts_ResolveUsername(MTP_string(username)), rpcDone(&MainWidget::usernameResolveDone, qMakePair(toProfile, startToken)), rpcFail(&MainWidget::usernameResolveFail, username)); @@ -3571,7 +3551,7 @@ void MainWidget::stickersBox(const MTPInputStickerSet &set) { App::wnd()->hideMediaview(); StickerSetBox *box = new StickerSetBox(set); connect(box, SIGNAL(installed(uint64)), this, SLOT(onStickersInstalled(uint64))); - App::wnd()->showLayer(box); + Ui::showLayer(box); } void MainWidget::onStickersInstalled(uint64 setId) { @@ -3605,7 +3585,7 @@ bool MainWidget::contentOverlapped(const QRect &globalRect) { } void MainWidget::usernameResolveDone(QPair toProfileStartToken, const MTPcontacts_ResolvedPeer &result) { - App::wnd()->hideLayer(); + Ui::hideLayer(); if (result.type() != mtpc_contacts_resolvedPeer) return; const MTPDcontacts_resolvedPeer &d(result.c_contacts_resolvedPeer()); @@ -3618,9 +3598,9 @@ void MainWidget::usernameResolveDone(QPair toProfileStartToken, c if (toProfileStartToken.first) { if (peer->isUser() && peer->asUser()->botInfo && !peer->asUser()->botInfo->cantJoinGroups && !toProfileStartToken.second.isEmpty()) { peer->asUser()->botInfo->startGroupToken = toProfileStartToken.second; - App::wnd()->showLayer(new ContactsBox(peer->asUser())); + Ui::showLayer(new ContactsBox(peer->asUser())); } else if (peer->isChannel()) { - showPeerHistory(peer->id, ShowAtUnreadMsgId); + Ui::showPeerHistory(peer->id, ShowAtUnreadMsgId); } else { showPeerProfile(peer); } @@ -3632,7 +3612,7 @@ void MainWidget::usernameResolveDone(QPair toProfileStartToken, c history.resizeEvent(0); } } - showPeerHistory(peer->id, ShowAtUnreadMsgId); + Ui::showPeerHistory(peer->id, ShowAtUnreadMsgId); } } @@ -3640,7 +3620,7 @@ bool MainWidget::usernameResolveFail(QString name, const RPCError &error) { if (mtpIsFlood(error)) return false; if (error.code() == 400) { - App::wnd()->showLayer(new InformBox(lng_username_not_found(lt_user, name))); + Ui::showLayer(new InformBox(lng_username_not_found(lt_user, name))); } return true; } @@ -3652,14 +3632,14 @@ void MainWidget::inviteCheckDone(QString hash, const MTPChatInvite &invite) { ConfirmBox *box = new ConfirmBox(((d.is_channel() && !d.is_megagroup()) ? lng_group_invite_want_join_channel : lng_group_invite_want_join)(lt_title, qs(d.vtitle)), lang(lng_group_invite_join)); _inviteHash = hash; connect(box, SIGNAL(confirmed()), this, SLOT(onInviteImport())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } break; case mtpc_chatInviteAlready: { const MTPDchatInviteAlready &d(invite.c_chatInviteAlready()); PeerData *chat = App::feedChats(MTP_vector(1, d.vchat)); if (chat) { - showPeerHistory(chat->id, ShowAtUnreadMsgId); + Ui::showPeerHistory(chat->id, ShowAtUnreadMsgId); } } break; } @@ -3669,7 +3649,7 @@ bool MainWidget::inviteCheckFail(const RPCError &error) { if (mtpIsFlood(error)) return false; if (error.code() == 400) { - App::wnd()->showLayer(new InformBox(lang(lng_group_invite_bad_link))); + Ui::showLayer(new InformBox(lang(lng_group_invite_bad_link))); } return true; } @@ -3682,7 +3662,7 @@ void MainWidget::onInviteImport() { void MainWidget::inviteImportDone(const MTPUpdates &updates) { App::main()->sentUpdatesReceived(updates); - App::wnd()->hideLayer(); + Ui::hideLayer(); const QVector *v = 0; switch (updates.type()) { case mtpc_updates: v = &updates.c_updates().vchats.c_vector().v; break; @@ -3691,9 +3671,9 @@ void MainWidget::inviteImportDone(const MTPUpdates &updates) { } if (v && !v->isEmpty()) { if (v->front().type() == mtpc_chat) { - App::main()->showPeerHistory(peerFromChat(v->front().c_chat().vid.v), ShowAtTheEndMsgId); + Ui::showPeerHistory(peerFromChat(v->front().c_chat().vid.v), ShowAtTheEndMsgId); } else if (v->front().type() == mtpc_channel) { - App::main()->showPeerHistory(peerFromChannel(v->front().c_channel().vid.v), ShowAtTheEndMsgId); + Ui::showPeerHistory(peerFromChannel(v->front().c_channel().vid.v), ShowAtTheEndMsgId); } } } @@ -3702,7 +3682,7 @@ bool MainWidget::inviteImportFail(const RPCError &error) { if (mtpIsFlood(error)) return false; if (error.code() == 400) { - App::wnd()->showLayer(new InformBox(lang(error.type() == qsl("USERS_TOO_MUCH") ? lng_group_invite_no_room : lng_group_invite_bad_link))); + Ui::showLayer(new InformBox(lang(error.type() == qsl("USERS_TOO_MUCH") ? lng_group_invite_no_room : lng_group_invite_bad_link))); } return true; } @@ -3915,7 +3895,7 @@ void MainWidget::activate() { } else { dialogs.activate(); } - } else if (App::wnd() && !App::wnd()->layerShown()) { + } else if (App::wnd() && !Ui::isLayerShown()) { if (!cSendPaths().isEmpty()) { forwardLayer(-1); } else if (history.peer()) { @@ -3937,12 +3917,6 @@ void MainWidget::updateOnlineDisplayIn(int32 msecs) { _onlineUpdater.start(msecs); } -void MainWidget::addNewContact(int32 uid, bool show) { - if (dialogs.addNewContact(uid, show)) { - showPeerHistory(peerFromUser(uid), ShowAtTheEndMsgId); - } -} - bool MainWidget::isActive() const { return !_isIdle && isVisible() && !_a_show.animating(); } @@ -4030,7 +4004,7 @@ void MainWidget::updateReceived(const mtpPrime *from, const mtpPrime *end) { if (end <= from || !MTP::authedId()) return; App::wnd()->checkAutoLock(); - + if (mtpTypeId(*from) == mtpc_new_session_created) { MTPNewSession newSession(from, end); updSeq = 0; @@ -4098,7 +4072,7 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) { case mtpc_updateShortMessage: { const MTPDupdateShortMessage &d(updates.c_updateShortMessage()); - if (!App::userLoaded(d.vuser_id.v) || (d.has_fwd_from_id() && !App::peerLoaded(peerFromMTP(d.vfwd_from_id)))) { + if (!App::userLoaded(d.vuser_id.v) || (d.has_fwd_from_id() && !App::peerLoaded(peerFromMTP(d.vfwd_from_id))) || (d.has_via_bot_id() && !App::peerLoaded(peerFromUser(d.vvia_bot_id)))) { MTP_LOG(0, ("getDifference { good - getting user for updateShortMessage }%1").arg(cTestMode() ? " TESTMODE" : "")); return getDifference(); } @@ -4109,7 +4083,7 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) { // update before applying skipped int32 flags = d.vflags.v | MTPDmessage::flag_from_id; - HistoryItem *item = App::histories().addNewMessage(MTP_message(MTP_int(flags), d.vid, d.is_out() ? MTP_int(MTP::authedId()) : d.vuser_id, MTP_peerUser(d.is_out() ? d.vuser_id : MTP_int(MTP::authedId())), d.vfwd_from_id, d.vfwd_date, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty(), MTPnullMarkup, d.has_entities() ? d.ventities : MTPnullEntities, MTPint()), NewMessageUnread); + HistoryItem *item = App::histories().addNewMessage(MTP_message(MTP_int(flags), d.vid, d.is_out() ? MTP_int(MTP::authedId()) : d.vuser_id, MTP_peerUser(d.is_out() ? d.vuser_id : MTP_int(MTP::authedId())), d.vfwd_from_id, d.vfwd_date, d.vvia_bot_id, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty(), MTPnullMarkup, d.has_entities() ? d.ventities : MTPnullEntities, MTPint()), NewMessageUnread); if (item) { history.peerMessagesUpdated(item->history()->peer->id); } @@ -4122,7 +4096,7 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) { case mtpc_updateShortChatMessage: { const MTPDupdateShortChatMessage &d(updates.c_updateShortChatMessage()); bool noFrom = !App::userLoaded(d.vfrom_id.v); - if (!App::chatLoaded(d.vchat_id.v) || noFrom || (d.has_fwd_from_id() && !App::peerLoaded(peerFromMTP(d.vfwd_from_id)))) { + if (!App::chatLoaded(d.vchat_id.v) || noFrom || (d.has_fwd_from_id() && !App::peerLoaded(peerFromMTP(d.vfwd_from_id))) || (d.has_via_bot_id() && !App::peerLoaded(peerFromUser(d.vvia_bot_id)))) { MTP_LOG(0, ("getDifference { good - getting user for updateShortChatMessage }%1").arg(cTestMode() ? " TESTMODE" : "")); if (noFrom && App::api()) App::api()->requestFullPeer(App::chatLoaded(d.vchat_id.v)); return getDifference(); @@ -4134,7 +4108,7 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) { // update before applying skipped int32 flags = d.vflags.v | MTPDmessage::flag_from_id; - HistoryItem *item = App::histories().addNewMessage(MTP_message(MTP_int(flags), d.vid, d.vfrom_id, MTP_peerChat(d.vchat_id), d.vfwd_from_id, d.vfwd_date, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty(), MTPnullMarkup, d.has_entities() ? d.ventities : MTPnullEntities, MTPint()), NewMessageUnread); + HistoryItem *item = App::histories().addNewMessage(MTP_message(MTP_int(flags), d.vid, d.vfrom_id, MTP_peerChat(d.vchat_id), d.vfwd_from_id, d.vfwd_date, d.vvia_bot_id, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty(), MTPnullMarkup, d.has_entities() ? d.ventities : MTPnullEntities, MTPint()), NewMessageUnread); if (item) { history.peerMessagesUpdated(item->history()->peer->id); } @@ -4154,23 +4128,16 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) { feedUpdate(MTP_updateMessageID(d.vid, MTP_long(randomId))); // ignore real date if (peerId) { if (HistoryItem *item = App::histItemById(peerToChannel(peerId), d.vid.v)) { - if (!text.isEmpty()) { - bool hasLinks = d.has_entities() && !d.ventities.c_vector().v.isEmpty(); - if ((hasLinks && !item->hasTextLinks()) || (!hasLinks && item->textHasLinks())) { - item->setText(text, d.has_entities() ? entitiesFromMTP(d.ventities.c_vector().v) : EntitiesInText()); - item->initDimensions(); - itemResized(item); - if (item->hasTextLinks() && item->indexInOverview()) { - item->history()->addToOverview(item, OverviewLinks); - } - } - } + item->setText(text, d.has_entities() ? entitiesFromMTP(d.ventities.c_vector().v) : EntitiesInText()); + item->updateMedia(d.has_media() ? (&d.vmedia) : 0); + item->initDimensions(); + Notify::historyItemResized(item); - item->updateMedia(d.has_media() ? (&d.vmedia) : 0, true); + item->addToOverview(AddToOverviewNew); } } } - + if (!ptsUpdated(d.vpts.v, d.vpts_count.v, updates)) { return; } @@ -4221,15 +4188,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { if (msg.msg) { HistoryItem *msgRow = App::histItemById(msg); if (msgRow) { - App::historyUnregItem(msgRow); - if (App::wnd()) App::wnd()->changingMsgId(msgRow, d.vid.v); - msgRow->setId(d.vid.v); - if (msgRow->history()->peer->isSelf()) { - msgRow->history()->unregTyping(App::self()); - } - if (!App::historyRegItem(msgRow)) { - msgUpdated(msgRow); - } else { + if (App::histItemById(msg.channel, d.vid.v)) { History *h = msgRow->history(); bool wasLast = (h->lastMsg == msgRow); msgRow->destroy(); @@ -4237,6 +4196,15 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { checkPeerHistory(h->peer); } history.peerMessagesUpdated(); + } else { + App::historyUnregItem(msgRow); + if (App::wnd()) App::wnd()->changingMsgId(msgRow, d.vid.v); + msgRow->setId(d.vid.v); + if (msgRow->history()->peer->isSelf()) { + msgRow->history()->unregTyping(App::self()); + } + App::historyRegItem(msgRow); + Ui::repaintHistoryItem(msgRow); } } App::historyUnregRandom(d.vrandom_id.v); @@ -4257,7 +4225,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { if (HistoryItem *item = App::histItemById(NoChannel, v.at(i).v)) { if (item->isMediaUnread()) { item->markMediaRead(); - msgUpdated(item); + Ui::repaintHistoryItem(item); if (item->out() && item->history()->peer->isUser()) { item->history()->peer->asUser()->madeAction(); } @@ -4508,7 +4476,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { QDateTime datetime = date(d.vdate); QString name = App::self()->firstName; - QString day = langDayOfWeekFull(datetime.date()), date = langDayOfMonth(datetime.date()), time = datetime.time().toString(cTimeFormat()); + QString day = langDayOfWeekFull(datetime.date()), date = langDayOfMonthFull(datetime.date()), time = datetime.time().toString(cTimeFormat()); QString device = qs(d.vdevice), location = qs(d.vlocation); LangString text = lng_new_authorization(lt_name, App::self()->firstName, lt_day, day, lt_date, date, lt_time, time, lt_device, device, lt_location, location); App::wnd()->serviceNotification(text); @@ -4519,7 +4487,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { case mtpc_updateServiceNotification: { const MTPDupdateServiceNotification &d(update.c_updateServiceNotification()); if (mtpIsTrue(d.vpopup)) { - App::wnd()->showLayer(new InformBox(qs(d.vmessage))); + Ui::showLayer(new InformBox(qs(d.vmessage))); } else { App::wnd()->serviceNotification(qs(d.vmessage), d.vmedia); } @@ -4626,21 +4594,41 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { if (d.vstickerset.type() == mtpc_messages_stickerSet) { const MTPDmessages_stickerSet &set(d.vstickerset.c_messages_stickerSet()); if (set.vset.type() == mtpc_stickerSet) { + const MTPDstickerSet &s(set.vset.c_stickerSet()); + + StickerSets &sets(cRefStickerSets()); + StickerSets::iterator it = sets.find(s.vid.v); + if (it == sets.cend()) { + it = sets.insert(s.vid.v, StickerSet(s.vid.v, s.vaccess_hash.v, stickerSetTitle(s), qs(s.vshort_name), s.vcount.v, s.vhash.v, s.vflags.v)); + } + const QVector &v(set.vdocuments.c_vector().v); - StickerPack pack; - pack.reserve(v.size()); + it->stickers.clear(); + it->stickers.reserve(v.size()); for (int32 i = 0, l = v.size(); i < l; ++i) { DocumentData *doc = App::feedDocument(v.at(i)); if (!doc || !doc->sticker()) continue; - pack.push_back(doc); + it->stickers.push_back(doc); } + it->emoji.clear(); + const QVector &packs(set.vpacks.c_vector().v); + for (int32 i = 0, l = packs.size(); i < l; ++i) { + if (packs.at(i).type() != mtpc_stickerPack) continue; + const MTPDstickerPack &pack(packs.at(i).c_stickerPack()); + if (EmojiPtr e = emojiGetNoColor(emojiFromText(qs(pack.vemoticon)))) { + const QVector &stickers(pack.vdocuments.c_vector().v); + StickerPack p; + p.reserve(stickers.size()); + for (int32 j = 0, c = stickers.size(); j < c; ++j) { + DocumentData *doc = App::document(stickers.at(j).v); + if (!doc || !doc->sticker()) continue; - const MTPDstickerSet &s(set.vset.c_stickerSet()); - - StickerSets &sets(cRefStickerSets()); - - sets.insert(s.vid.v, StickerSet(s.vid.v, s.vaccess_hash.v, stickerSetTitle(s), qs(s.vshort_name), s.vcount.v, s.vhash.v, s.vflags.v)).value().stickers = pack; + p.push_back(doc); + } + it->emoji.insert(e, p); + } + } StickerSetsOrder &order(cRefStickerSetsOrder()); int32 insertAtIndex = 0, currentIndex = order.indexOf(s.vid.v); @@ -4653,15 +4641,14 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { StickerSets::iterator custom = sets.find(CustomStickerSetId); if (custom != sets.cend()) { - for (int32 i = 0, l = pack.size(); i < l; ++i) { - int32 removeIndex = custom->stickers.indexOf(pack.at(i)); + for (int32 i = 0, l = it->stickers.size(); i < l; ++i) { + int32 removeIndex = custom->stickers.indexOf(it->stickers.at(i)); if (removeIndex >= 0) custom->stickers.removeAt(removeIndex); } if (custom->stickers.isEmpty()) { sets.erase(custom); } } - cSetStickersHash(stickersCountHash()); Local::writeStickers(); emit stickersUpdated(); } @@ -4681,11 +4668,9 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { } if (result.size() != cStickerSetsOrder().size() || result.size() != order.size()) { cSetLastStickersUpdate(0); - cSetStickersHash(0); App::main()->updateStickers(); } else { cSetStickerSetsOrder(result); - cSetStickersHash(stickersCountHash()); Local::writeStickers(); emit stickersUpdated(); } @@ -4693,7 +4678,11 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { case mtpc_updateStickerSets: { cSetLastStickersUpdate(0); - cSetStickersHash(0); + App::main()->updateStickers(); + } break; + + case mtpc_updateSavedGifs: { + cSetLastSavedGifsUpdate(0); App::main()->updateStickers(); } break; diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index c8cbc6965e..3d8c6efa39 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -32,7 +32,7 @@ struct DialogRow; class MainWidget; class ConfirmBox; -class TopBarWidget : public TWidget, public Animated { +class TopBarWidget : public TWidget { Q_OBJECT public: @@ -47,7 +47,7 @@ public: void mousePressEvent(QMouseEvent *e); void resizeEvent(QResizeEvent *e); - bool animStep(float64 ms); + void step_appearance(float64 ms, bool timer); void enableShadow(bool enable = true); void startAnim(); @@ -87,13 +87,14 @@ private: MainWidget *main(); anim::fvalue a_over; + Animation _a_appearance; PeerData *_selPeer; uint32 _selCount; bool _canDelete; QString _selStr; int32 _selStrLeft, _selStrWidth; - + bool _animating; FlatButton _clearSelection; @@ -193,7 +194,6 @@ public: void updateWideMode(); bool needBackButton(); - void showDialogs(); void paintTopBar(QPainter &p, float64 over, int32 decreaseWidth); TopBarWidget *topBar(); @@ -202,7 +202,7 @@ public: int32 contentScrollAddToY() const; void animShow(const QPixmap &bgAnimCache, bool back = false); - bool animStep_show(float64 ms); + void step_show(float64 ms, bool timer); void animStop_show(); void start(const MTPUser &user); @@ -236,7 +236,6 @@ public: return sentUpdatesReceived(0, updates); } void inviteToChannelDone(ChannelData *channel, const MTPUpdates &updates); - void msgUpdated(const HistoryItem *msg); void historyToDown(History *hist); void dialogsToUp(); void newUnreadMsg(History *history, HistoryItem *item); @@ -268,8 +267,6 @@ public: void destroyData(); void updateOnlineDisplayIn(int32 msecs); - void addNewContact(int32 uid, bool show = true); - bool isActive() const; bool historyIsActive() const; bool lastWasOnline() const; @@ -301,7 +298,6 @@ public: void deletedContact(UserData *user, const MTPcontacts_Link &result); void deleteConversation(PeerData *peer, bool deleteHistory = true); void clearHistory(PeerData *peer); - void removeContact(UserData *user); void addParticipants(PeerData *chatOrChannel, const QVector &users); bool addParticipantFail(UserData *user, const RPCError &e); @@ -321,17 +317,17 @@ public: DialogsIndexed &contactsList(); DialogsIndexed &dialogsList(); - + void sendMessage(History *hist, const QString &text, MsgId replyTo, bool broadcast, WebPageId webPageId = 0); void saveRecentHashtags(const QString &text); - + void readServerHistory(History *history, bool force = true); - uint64 animActiveTime(const HistoryItem *msg) const; + uint64 animActiveTimeStart(const HistoryItem *msg) const; void stopAnimActive(); void sendBotCommand(const QString &cmd, MsgId msgId); - void insertBotCommand(const QString &cmd); + bool insertBotCommand(const QString &cmd, bool specialGif); void searchMessages(const QString &query, PeerData *inPeer); bool preloadOverview(PeerData *peer, MediaOverviewType type); @@ -339,8 +335,6 @@ public: void mediaOverviewUpdated(PeerData *peer, MediaOverviewType type); void changingMsgId(HistoryItem *row, MsgId newId); void itemRemoved(HistoryItem *item); - void itemReplaced(HistoryItem *oldItem, HistoryItem *newItem); - void itemResized(HistoryItem *row, bool scrollToIt = false); void loadMediaBack(PeerData *peer, MediaOverviewType type, bool many = false); void peerUsernameChanged(PeerData *peer); @@ -370,7 +364,7 @@ public: void updateBotKeyboard(History *h); void pushReplyReturn(HistoryItem *item); - + bool hasForwardingItems(); void fillForwardingInfo(Text *&from, Text *&text, bool &serviceColor, ImagePtr &preview); void updateForwardingTexts(); @@ -385,9 +379,6 @@ public: void updateMutedIn(int32 seconds); void updateStickers(); - void notifyBotCommandsChanged(UserData *bot); - void notifyUserIsBotChanged(UserData *bot); - void notifyMigrateUpdated(PeerData *peer); void choosePeer(PeerId peerId, MsgId showAtMsgId); // does offerPeer or showPeerHistory void clearBotStartToken(PeerData *peer); @@ -414,8 +405,25 @@ public: QPixmap grabTopBar(); QPixmap grabInner(); + bool isItemVisible(HistoryItem *item); + void ui_showStickerPreview(DocumentData *sticker); void ui_hideStickerPreview(); + void ui_repaintHistoryItem(const HistoryItem *item); + void ui_repaintInlineItem(const LayoutInlineItem *layout); + bool ui_isInlineItemVisible(const LayoutInlineItem *layout); + bool ui_isInlineItemBeingChosen(); + void ui_showPeerHistory(quint64 peer, qint32 msgId, bool back); + + void notify_botCommandsChanged(UserData *bot); + void notify_inlineBotRequesting(bool requesting); + void notify_userIsBotChanged(UserData *bot); + void notify_userIsContactChanged(UserData *user, bool fromThisApp); + void notify_migrateUpdated(PeerData *peer); + void notify_clipStopperHidden(ClipStopperType type); + void notify_historyItemResized(const HistoryItem *row, bool scrollToIt); + void notify_historyItemLayoutChanged(const HistoryItem *item); + void notify_automaticLoadSettingsChangedGif(); ~MainWidget(); @@ -426,24 +434,26 @@ signals: void peerPhotoChanged(PeerData *peer); void dialogRowReplaced(DialogRow *oldRow, DialogRow *newRow); void dialogsUpdated(); - void showPeerAsync(quint64 peerId, qint32 showAtMsgId); void stickersUpdated(); + void savedGifsUpdated(); public slots: void webPagesUpdate(); - void videoLoadProgress(mtpFileLoader *loader); - void videoLoadFailed(mtpFileLoader *loader, bool started); + void videoLoadProgress(FileLoader *loader); + void videoLoadFailed(FileLoader *loader, bool started); void videoLoadRetry(); - void audioLoadProgress(mtpFileLoader *loader); - void audioLoadFailed(mtpFileLoader *loader, bool started); + void audioLoadProgress(FileLoader *loader); + void audioLoadFailed(FileLoader *loader, bool started); void audioLoadRetry(); void audioPlayProgress(const AudioMsgId &audioId); - void documentLoadProgress(mtpFileLoader *loader); - void documentLoadFailed(mtpFileLoader *loader, bool started); + void documentLoadProgress(FileLoader *loader); + void documentLoadFailed(FileLoader *loader, bool started); void documentLoadRetry(); void documentPlayProgress(const SongMsgId &songId); + void inlineResultLoadProgress(FileLoader *loader); + void inlineResultLoadFailed(FileLoader *loader, bool started); void hidePlayer(); void dialogsCancelled(); @@ -458,7 +468,6 @@ public slots: void checkIdleFinish(); void updateOnlineDisplay(); - void showPeerHistory(quint64 peer, qint32 msgId, bool back = false); void onTopBarClick(); void onHistoryShown(History *history, MsgId atMsgId); @@ -489,6 +498,8 @@ public slots: void onDownloadPathSettings(); + void ui_showPeerHistoryAsync(quint64 peerId, qint32 showAtMsgId); + private: void sendReadRequest(PeerData *peer, MsgId upTo); @@ -606,7 +617,7 @@ private: QSet updateNotifySettingPeers; SingleTimer updateNotifySettingTimer; - + typedef QMap > ReadRequests; ReadRequests _readRequests; typedef QMap ReadRequestsPending; diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index e26a3bd399..11875e5435 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -81,17 +81,14 @@ MediaView::MediaView() : TWidget(App::wnd()) , _zoomToScreen(0) , _pressed(false) , _dragging(0) +, _gif(0) , _full(-1) , _docNameWidth(0) , _docSizeWidth(0) , _docThumbx(0) , _docThumby(0) , _docThumbw(0) -, _docRadialFirst(0) -, _docRadialStart(0) -, _docRadialLast(0) -, _docRadialOpacity(1) -, a_docRadialStart(0, 1) +, _docRadial(animation(this, &MediaView::step_radial)) , _docDownload(this, lang(lng_media_download), st::mvDocLink) , _docSaveAs(this, lang(lng_mediaview_save_as), st::mvDocLink) , _docCancel(this, lang(lng_cancel), st::mvDocLink) @@ -111,6 +108,7 @@ MediaView::MediaView() : TWidget(App::wnd()) , _down(OverNone) , _lastAction(-st::mvDeltaFromLastAction, -st::mvDeltaFromLastAction) , _ignoringDropdown(false) +, _a_state(animation(this, &MediaView::step_state)) , _controlsState(ControlsShown) , _controlsAnimStarted(0) , _menu(0) @@ -128,8 +126,6 @@ MediaView::MediaView() : TWidget(App::wnd()) _saveMsgText.setLink(1, TextLinkPtr(new SaveMsgLink(this))); _transparentBrush = QBrush(App::sprite().copy(st::mvTransparentBrush)); - _docRadialPen = QPen(st::white->p); - _docRadialPen.setWidth(st::radialLine); setWindowFlags(Qt::FramelessWindowHint | Qt::BypassWindowManagerHint | Qt::Tool | Qt::NoDropShadowWindowHint); moveToScreen(); @@ -139,7 +135,7 @@ MediaView::MediaView() : TWidget(App::wnd()) hide(); createWinId(); - + _saveMsgUpdater.setSingleShot(true); connect(&_saveMsgUpdater, SIGNAL(timeout()), this, SLOT(updateImage())); @@ -149,8 +145,6 @@ MediaView::MediaView() : TWidget(App::wnd()) _touchTimer.setSingleShot(true); connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer())); - connect(&_currentGif, SIGNAL(updated()), this, SLOT(onGifUpdated())); - _btns.push_back(_btnSaveCancel = _dropdown.addButton(new IconedButton(this, st::mvButton, lang(lng_cancel)))); connect(_btnSaveCancel, SIGNAL(clicked()), this, SLOT(onSaveCancel())); _btns.push_back(_btnToMessage = _dropdown.addButton(new IconedButton(this, st::mvButton, lang(lng_context_to_msg)))); @@ -189,7 +183,7 @@ void MediaView::moveToScreen() { if (avail != geometry()) { setGeometry(avail); } - + int32 navSkip = 2 * st::mvControlMargin + st::mvControlSize; _closeNav = myrtlrect(width() - st::mvControlMargin - st::mvControlSize, st::mvControlMargin, st::mvControlSize, st::mvControlSize); _closeNavIcon = centersprite(_closeNav, st::mvClose); @@ -237,21 +231,37 @@ void MediaView::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) { } } +bool MediaView::fileShown() const { + return !_current.isNull() || gifShown(); +} + +bool MediaView::gifShown() const { + if (_gif && _gif->ready()) { + if (!_gif->started()) { + _gif->start(_gif->width(), _gif->height(), _gif->width(), _gif->height(), false); + const_cast(this)->_current = QPixmap(); + } + return _gif->state() != ClipError; + } + return false; +} + +void MediaView::stopGif() { + delete _gif; + _gif = 0; +} + void MediaView::documentUpdated(DocumentData *doc) { - if (_doc && _doc == doc && _current.isNull() && _currentGif.isNull()) { - if ((_doc->loader && _docCancel.isHidden()) || (!_doc->loader && !_docCancel.isHidden())) { + if (_doc && _doc == doc && !fileShown()) { + if ((_doc->loading() && _docCancel.isHidden()) || (!_doc->loading() && !_docCancel.isHidden())) { updateControls(); - } else if (_doc->loader) { + } else if (_doc->loading()) { updateDocSize(); update(_docRect); } } } -void MediaView::onGifUpdated() { - update(_x, _y, _w, _h); -} - void MediaView::changingMsgId(HistoryItem *row, MsgId newId) { if (row->id == _msgid) { _msgid = newId; @@ -260,10 +270,10 @@ void MediaView::changingMsgId(HistoryItem *row, MsgId newId) { } void MediaView::updateDocSize() { - if (!_doc || !_current.isNull() || !_currentGif.isNull()) return; + if (!_doc || fileShown()) return; - if (_doc->loader) { - quint64 ready = _doc->loader->currentOffset(), total = _doc->size; + if (_doc->loading()) { + quint64 ready = _doc->loadOffset(), total = _doc->size; QString readyStr, totalStr, mb; if (total >= 1024 * 1024) { // more than 1 mb qint64 readyTenthMb = (ready * 10 / (1024 * 1024)), totalTenthMb = (total * 10 / (1024 * 1024)); @@ -285,7 +295,7 @@ void MediaView::updateDocSize() { _docSize = formatSizeText(_doc->size); } _docSizeWidth = st::mvFont->width(_docSize); - int32 maxw = st::mvDocSize.width() - st::mvDocBlue.pxWidth() - st::mvDocPadding * 3; + int32 maxw = st::mvDocSize.width() - st::mvDocIconSize - st::mvDocPadding * 3; if (_docSizeWidth > maxw) { _docSize = st::mvFont->elided(_docSize, maxw); _docSizeWidth = st::mvFont->width(_docSize); @@ -293,25 +303,25 @@ void MediaView::updateDocSize() { } void MediaView::updateControls() { - if (_doc && _current.isNull() && _currentGif.isNull()) { - if (_doc->loader) { + if (_doc && !fileShown()) { + if (_doc->loading()) { _docDownload.hide(); _docSaveAs.hide(); - _docCancel.moveToLeft(_docRect.x() + 2 * st::mvDocPadding + st::mvDocBlue.pxWidth(), _docRect.y() + st::mvDocPadding + st::mvDocLinksTop); + _docCancel.moveToLeft(_docRect.x() + 2 * st::mvDocPadding + st::mvDocIconSize, _docRect.y() + st::mvDocPadding + st::mvDocLinksTop); _docCancel.show(); - if (!_docRadialFirst) _docRadialFirst = _docRadialLast = _docRadialStart = getms(); - if (!animating()) anim::start(this); - anim::step(this); + if (!_docRadial.animating()) { + _docRadial.start(_doc->progress()); + } } else { - if (_doc->already(true).isEmpty()) { - _docDownload.moveToLeft(_docRect.x() + 2 * st::mvDocPadding + st::mvDocBlue.pxWidth(), _docRect.y() + st::mvDocPadding + st::mvDocLinksTop); - _docDownload.show(); - _docSaveAs.moveToLeft(_docRect.x() + 2.5 * st::mvDocPadding + st::mvDocBlue.pxWidth() + _docDownload.width(), _docRect.y() + st::mvDocPadding + st::mvDocLinksTop); + if (_doc->loaded(true)) { + _docDownload.hide(); + _docSaveAs.moveToLeft(_docRect.x() + 2 * st::mvDocPadding + st::mvDocIconSize, _docRect.y() + st::mvDocPadding + st::mvDocLinksTop); _docSaveAs.show(); _docCancel.hide(); } else { - _docDownload.hide(); - _docSaveAs.moveToLeft(_docRect.x() + 2 * st::mvDocPadding + st::mvDocBlue.pxWidth(), _docRect.y() + st::mvDocPadding + st::mvDocLinksTop); + _docDownload.moveToLeft(_docRect.x() + 2 * st::mvDocPadding + st::mvDocIconSize, _docRect.y() + st::mvDocPadding + st::mvDocLinksTop); + _docDownload.show(); + _docSaveAs.moveToLeft(_docRect.x() + 2.5 * st::mvDocPadding + st::mvDocIconSize + _docDownload.width(), _docRect.y() + st::mvDocPadding + st::mvDocLinksTop); _docSaveAs.show(); _docCancel.hide(); } @@ -323,7 +333,7 @@ void MediaView::updateControls() { _docCancel.hide(); } - _saveVisible = ((_photo && _photo->full->loaded()) || (_doc && (!_doc->already(true).isEmpty() || (_current.isNull() && _currentGif.isNull() && (_photo || _doc))))); + _saveVisible = ((_photo && _photo->loaded()) || (_doc && (_doc->loaded(true) || (!fileShown() && (_photo || _doc))))); _saveNav = myrtlrect(width() - st::mvIconSize.width() * 2, height() - st::mvIconSize.height(), st::mvIconSize.width(), st::mvIconSize.height()); _saveNavIcon = centersprite(_saveNav, st::mvSave); _moreNav = myrtlrect(width() - st::mvIconSize.width(), height() - st::mvIconSize.height(), st::mvIconSize.width(), st::mvIconSize.height()); @@ -345,7 +355,7 @@ void MediaView::updateControls() { _dateText = lng_mediaview_date_time(lt_date, d.date().toString(qsl("dd.MM.yy")), lt_time, d.time().toString(cTimeFormat())); } if (_from) { - _fromName.setText(st::mvFont, (_from->migrateTo() ? _from->migrateTo() : _from)->name); + _fromName.setText(st::mvFont, (_from->migrateTo() ? _from->migrateTo() : _from)->name, _textNameOptions); _nameNav = myrtlrect(st::mvTextLeft, height() - st::mvTextTop, qMin(_fromName.maxWidth(), width() / 3), st::mvFont->height); _dateNav = myrtlrect(st::mvTextLeft + _nameNav.width() + st::mvTextSkip, height() - st::mvTextTop, st::mvFont->width(_dateText), st::mvFont->height); } else { @@ -382,11 +392,11 @@ void MediaView::updateControls() { } void MediaView::updateDropdown() { - _btnSaveCancel->setVisible(_doc && _doc->loader); + _btnSaveCancel->setVisible(_doc && _doc->loading()); _btnToMessage->setVisible(_msgid > 0); _btnShowInFolder->setVisible(_doc && !_doc->already(true).isEmpty()); _btnSaveAs->setVisible(true); - _btnCopy->setVisible((_doc && (!_current.isNull() || !_currentGif.isNull())) || (_photo && _photo->full->loaded())); + _btnCopy->setVisible((_doc && fileShown()) || (_photo && _photo->loaded())); _btnForward->setVisible(_canForward); _btnDelete->setVisible(_canDelete || (_photo && App::self() && App::self()->photoId == _photo->id) || (_photo && _photo->peer && _photo->peer->photoId == _photo->id && (_photo->peer->isChat() || (_photo->peer->isChannel() && _photo->peer->asChannel()->amCreator())))); _btnViewAll->setVisible((_overview != OverviewCount) && _history); @@ -395,9 +405,8 @@ void MediaView::updateDropdown() { _dropdown.moveToRight(0, height() - _dropdown.height()); } -bool MediaView::animStep(float64 msp) { +void MediaView::step_state(uint64 ms, bool timer) { bool result = false; - uint64 ms = getms(); for (Showing::iterator i = _animations.begin(); i != _animations.end();) { int64 start = i.value(); switch (i.key()) { @@ -434,49 +443,39 @@ bool MediaView::animStep(float64 msp) { update(toUpdate); if (dt < 1) result = true; } - if (_doc && _docRadialStart > 0) { - float64 prg = _doc->loader ? qMax(_doc->loader->currentProgress(), 0.0001) : (_doc->status == FileFailed ? 0 : (_doc->already().isEmpty() ? 0 : 1)); - if (prg != a_docRadial.to()) { - a_docRadial.start(prg); - _docRadialStart = _docRadialLast; - } - _docRadialLast = ms; + if (!result && _animations.isEmpty()) { + _a_state.stop(); + } +} - float64 dt = float64(ms - _docRadialStart), fulldt = float64(ms - _docRadialFirst); - _docRadialOpacity = qMin(fulldt / st::radialDuration, 1.); - if (_doc->loader) { - a_docRadial.update(1. - (st::radialDuration / (st::radialDuration + dt)), anim::linear); - result = true; - } else if (dt >= st::radialDuration) { - a_docRadial.update(1, anim::linear); - result = true; - _docRadialFirst = _docRadialLast = _docRadialStart = 0; - a_docRadial = anim::fvalue(0, 0); - if (!_doc->already().isEmpty() && _doc->size < MediaViewImageSizeLimit) { - const FileLocation &location(_doc->location(true)); - if (location.accessEnable()) { - QImageReader reader(location.name()); - if (reader.canRead()) { - displayDocument(_doc, App::histItemById(_msgmigrated ? 0 : _channel, _msgid)); - } - location.accessDisable(); - } - } - } else { - float64 r = dt / st::radialDuration; - a_docRadial.update(r, anim::linear); - result = true; - _docRadialOpacity *= 1 - r; - } - float64 fromstart = fulldt / st::radialPeriod; - a_docRadialStart.update(fromstart - qFloor(fromstart), anim::linear); +void MediaView::step_radial(uint64 ms, bool timer) { + if (!_doc) { + _docRadial.stop(); + return; + } + _docRadial.update(_doc->progress(), !_doc->loading(), ms); + if (timer && _docRadial.animating()) { update(_docIconRect); } - return result || !_animations.isEmpty(); + if (_doc->loaded() && _doc->size < MediaViewImageSizeLimit && (!_docRadial.animating() || _doc->isAnimation())) { + if (!_doc->data().isEmpty() && _doc->isAnimation()) { + displayDocument(_doc, App::histItemById(_msgmigrated ? 0 : _channel, _msgid)); + } else { + const FileLocation &location(_doc->location(true)); + if (location.accessEnable()) { + if (_doc->isAnimation() || QImageReader(location.name()).canRead()) { + displayDocument(_doc, App::histItemById(_msgmigrated ? 0 : _channel, _msgid)); + } + location.accessDisable(); + } + } + + } } MediaView::~MediaView() { - delete _menu; + deleteAndMark(_gif); + deleteAndMark(_menu); } void MediaView::showSaveMsgFile() { @@ -486,27 +485,27 @@ void MediaView::showSaveMsgFile() { void MediaView::close() { if (_menu) _menu->hideMenu(true); if (App::wnd()) { - App::wnd()->hideLayer(true); + Ui::hideLayer(true); } } void MediaView::activateControls() { - _controlsHideTimer.start(int(st::mvWaitHide)); + if (!_menu) _controlsHideTimer.start(int(st::mvWaitHide)); if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) { _controlsState = ControlsShowing; _controlsAnimStarted = getms(); a_cOpacity.start(1); - if (!animating()) anim::start(this); + if (!_a_state.animating()) _a_state.start(); } } void MediaView::onHideControls(bool force) { - if (!force && !_dropdown.isHidden()) return; + if (!force && (!_dropdown.isHidden() || _menu)) return; if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) return; _controlsState = ControlsHiding; _controlsAnimStarted = getms(); a_cOpacity.start(0); - if (!animating()) anim::start(this); + if (!_a_state.animating()) _a_state.start(); } void MediaView::onDropdownHiding() { @@ -524,7 +523,7 @@ void MediaView::onToMessage() { if (HistoryItem *item = _msgid ? App::histItemById(_msgmigrated ? 0 : _channel, _msgid) : 0) { if (App::wnd()) { close(); - if (App::main()) App::main()->showPeerHistory(item->history()->peer->id, _msgid); + Ui::showPeerHistoryAtItem(item); } } } @@ -533,7 +532,7 @@ void MediaView::onSaveAs() { QString file; if (_doc) { const FileLocation &location(_doc->location(true)); - if (location.accessEnable()) { + if (!_doc->data().isEmpty() || location.accessEnable()) { QFileInfo alreadyInfo(location.name()); QDir alreadyDir(alreadyInfo.dir()); QString name = alreadyInfo.fileName(), filter; @@ -554,12 +553,18 @@ void MediaView::onSaveAs() { file = saveFileName(lang(lng_save_file), filter, qsl("doc"), name, true, alreadyDir); psShowOverAll(this); if (!file.isEmpty() && file != location.name()) { - QFile(location.name()).copy(file); + if (_doc->data().isEmpty()) { + QFile(location.name()).copy(file); + } else { + QFile f(file); + f.open(QIODevice::WriteOnly); + f.write(_doc->data()); + } } - location.accessDisable(); + if (_doc->data().isEmpty()) location.accessDisable(); } else { - if (_current.isNull() && _currentGif.isNull()) { + if (!fileShown()) { DocumentSaveLink::doSave(_doc, true); updateControls(); } else { @@ -569,7 +574,7 @@ void MediaView::onSaveAs() { updateOver(_lastMouseMovePos); } } else { - if (!_photo || !_photo->full->loaded()) return; + if (!_photo || !_photo->loaded()) return; psBringToBack(this); bool gotName = filedialogGetSaveFile(file, lang(lng_save_photo), qsl("JPEG Image (*.jpg);;All files (*.*)"), filedialogDefaultName(qsl("photo"), qsl(".jpg"))); @@ -586,15 +591,36 @@ void MediaView::onSaveAs() { } void MediaView::onDocClick() { - QString fname = _doc->already(true); - if (fname.isEmpty()) { - if (_doc->loader) { - onSaveCancel(); - } else { - onDownload(); - } + if (_doc->loading()) { + onSaveCancel(); } else { - psOpenFile(fname); + DocumentOpenLink::doOpen(_doc, ActionOnLoadNone); + if (_doc->loading() && !_docRadial.animating()) { + _docRadial.start(_doc->progress()); + } + } +} + +void MediaView::clipCallback(ClipReaderNotification notification) { + if (!_gif) return; + + switch (notification) { + case ClipReaderReinit: { + if (HistoryItem *item = App::histItemById(_msgmigrated ? 0 : _channel, _msgid)) { + if (_gif->state() == ClipError) { + _current = QPixmap(); + } + displayDocument(_doc, item); + } else { + stopGif(); + } + } break; + + case ClipReaderRepaint: { + if (!_gif->currentDisplayed()) { + update(_x, _y, _w, _h); + } + } break; } } @@ -622,7 +648,7 @@ void MediaView::onDownload() { } location.accessDisable(); } else { - if (_current.isNull() && _currentGif.isNull()) { + if (!fileShown()) { DocumentSaveLink::doSave(_doc); updateControls(); } else { @@ -632,7 +658,7 @@ void MediaView::onDownload() { updateOver(_lastMouseMovePos); } } else { - if (!_photo || !_photo->full->loaded()) { + if (!_photo || !_photo->loaded()) { _saveVisible = false; update(_saveNav); } else { @@ -652,8 +678,8 @@ void MediaView::onDownload() { } void MediaView::onSaveCancel() { - if (_doc && _doc->loader) { - _doc->loader->cancel(); + if (_doc && _doc->loading()) { + _doc->cancel(); } } @@ -711,11 +737,11 @@ void MediaView::onCopy() { if (_doc) { if (!_current.isNull()) { QApplication::clipboard()->setPixmap(_current); - } else if (!_currentGif.isNull()) { - QApplication::clipboard()->setPixmap(_currentGif.current(_currentGif.w, _currentGif.h, false)); + } else if (gifShown()) { + QApplication::clipboard()->setPixmap(_gif->frameOriginal()); } } else { - if (!_photo || !_photo->full->loaded()) return; + if (!_photo || !_photo->loaded()) return; QApplication::clipboard()->setPixmap(_photo->full->pix()); } @@ -743,7 +769,7 @@ void MediaView::showPhoto(PhotoData *photo, HistoryItem *context) { setCursor(style::cur_default); if (!_animations.isEmpty()) { _animations.clear(); - anim::stop(this); + _a_state.stop(); } if (!_animOpacities.isEmpty()) _animOpacities.clear(); @@ -774,7 +800,7 @@ void MediaView::showPhoto(PhotoData *photo, PeerData *context) { setCursor(style::cur_default); if (!_animations.isEmpty()) { _animations.clear(); - anim::stop(this); + _a_state.stop(); } if (!_animOpacities.isEmpty()) _animOpacities.clear(); @@ -828,7 +854,7 @@ void MediaView::showDocument(DocumentData *doc, HistoryItem *context) { setCursor(style::cur_default); if (!_animations.isEmpty()) { _animations.clear(); - anim::stop(this); + _a_state.stop(); } if (!_animOpacities.isEmpty()) _animOpacities.clear(); @@ -848,8 +874,10 @@ void MediaView::showDocument(DocumentData *doc, HistoryItem *context) { } void MediaView::displayPhoto(PhotoData *photo, HistoryItem *item) { - _photo = photo; + stopGif(); _doc = 0; + _photo = photo; + _zoom = 0; _caption = Text(); @@ -863,7 +891,6 @@ void MediaView::displayPhoto(PhotoData *photo, HistoryItem *item) { MTP::clearLoaderPriorities(); _full = -1; _current = QPixmap(); - _currentGif.stop(); _down = OverNone; _w = convertScale(photo->full->width()); _h = convertScale(photo->full->height()); @@ -891,7 +918,7 @@ void MediaView::displayPhoto(PhotoData *photo, HistoryItem *item) { _from = _user; } updateControls(); - _photo->full->load(); + _photo->download(); if (isHidden()) { psUpdateOverlayed(this); show(); @@ -902,80 +929,64 @@ void MediaView::displayPhoto(PhotoData *photo, HistoryItem *item) { } void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty messages shown as docs: doc can be NULL + if (!doc || !doc->isAnimation() || doc != _doc || (item && (item->id != _msgid || (item->history() != (_msgmigrated ? _migrated : _history))))) { + stopGif(); + } _doc = doc; _photo = 0; + _current = QPixmap(); + _caption = Text(); if (_doc) { - const FileLocation &location(_doc->location(true)); - if (_doc->sticker() && !_doc->sticker()->img->isNull() && _doc->sticker()->img->loaded()) { - _currentGif.stop(); - _current = _doc->sticker()->img->pix(); - } else if (location.accessEnable()) { - QImageReader reader(location.name()); - if (reader.canRead()) { - if (reader.supportsAnimation() && reader.imageCount() > 1) { - _currentGif.start(0, location); - _current = QPixmap(); - } else { - _currentGif.stop(); - QPixmap pix = QPixmap::fromImage(App::readImage(location.name(), 0, false), Qt::ColorOnly); - _current = pix; - } + if (_doc->sticker()) { + _doc->checkSticker(); + if (!_doc->sticker()->img->isNull()) { + _current = _doc->sticker()->img->pix(); } else { - _currentGif.stop(); - _current = QPixmap(); + _current = _doc->thumb->pixBlurred(_doc->dimensions.width(), _doc->dimensions.height()); } - location.accessDisable(); } else { - _currentGif.stop(); - _current = QPixmap(); + _doc->automaticLoad(item); + + const FileLocation &location(_doc->location(true)); + if (!_doc->data().isEmpty() && _doc->isAnimation()) { + if (!_gif) { + if (_doc->dimensions.width() && _doc->dimensions.height()) { + _current = _doc->thumb->pixNoCache(_doc->dimensions.width(), _doc->dimensions.height(), true, true, false, _doc->dimensions.width(), _doc->dimensions.height()); + } + _gif = new ClipReader(location, _doc->data(), func(this, &MediaView::clipCallback)); + } + } else if (location.accessEnable()) { + if (_doc->isAnimation()) { + if (!_gif) { + if (_doc->dimensions.width() && _doc->dimensions.height()) { + _current = _doc->thumb->pixNoCache(_doc->dimensions.width(), _doc->dimensions.height(), true, true, false, _doc->dimensions.width(), _doc->dimensions.height()); + } + _gif = new ClipReader(location, _doc->data(), func(this, &MediaView::clipCallback)); + } + } else { + if (QImageReader(location.name()).canRead()) { + _current = QPixmap::fromImage(App::readImage(location.name(), 0, false), Qt::ColorOnly); + } + } + location.accessDisable(); + } } - } else { - _currentGif.stop(); - _current = QPixmap(); } - if (_current.isNull() && _currentGif.isNull()) { + if (!fileShown()) { if (!_doc || _doc->thumb->isNull()) { + int32 colorIndex = documentColorIndex(_doc, _docExt); + _docIconColor = documentColor(colorIndex); style::sprite thumbs[] = { st::mvDocBlue, st::mvDocGreen, st::mvDocRed, st::mvDocYellow }; - style::color colors[] = { st::mvDocBlueColor, st::mvDocGreenColor, st::mvDocRedColor, st::mvDocYellowColor }; - QString name = _doc ? _doc->name.toLower() : QString(), mime = _doc ? _doc->mime.toLower() : QString(); - if (name.endsWith(qstr(".doc")) || - name.endsWith(qstr(".txt")) || - name.endsWith(qstr(".psd")) || - mime.startsWith(qstr("text/")) - ) { - _docIcon = thumbs[0]; - _docIconColor = colors[0]; - } else if ( - name.endsWith(qstr(".xls")) || - name.endsWith(qstr(".csv")) - ) { - _docIcon = thumbs[1]; - _docIconColor = colors[1]; - } else if ( - name.endsWith(qstr(".pdf")) || - name.endsWith(qstr(".ppt")) || - name.endsWith(qstr(".key")) - ) { - _docIcon = thumbs[2]; - _docIconColor = colors[2]; - } else if ( - name.endsWith(qstr(".zip")) || - name.endsWith(qstr(".rar")) || - name.endsWith(qstr(".ai")) || - name.endsWith(qstr(".mp3")) || - name.endsWith(qstr(".mov")) || - name.endsWith(qstr(".avi")) - ) { - _docIcon = thumbs[3]; - _docIconColor = colors[3]; - } else { - int ext = name.lastIndexOf('.'); - QChar ch = (ext >= 0 && ext + 1 < name.size()) ? name.at(ext + 1) : (name.isEmpty() ? (mime.isEmpty() ? '0' : mime.at(0)) : name.at(0)); - _docIcon = thumbs[ch.unicode() % 4]; - _docIconColor = colors[ch.unicode() % 4]; + _docIcon = thumbs[colorIndex]; + + int32 extmaxw = (st::mvDocIconSize - st::mvDocExtPadding * 2); + _docExtWidth = st::mvDocExtFont->width(_docExt); + if (_docExtWidth > extmaxw) { + _docExt = st::mvDocNameFont->elided(_docExt, extmaxw, Qt::ElideMiddle); + _docExtWidth = st::mvDocNameFont->width(_docExt); } } else { _doc->thumb->load(); @@ -983,50 +994,41 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty if (!tw || !th) { _docThumbx = _docThumby = _docThumbw = 0; } else if (tw > th) { - _docThumbw = (tw * st::mvDocBlue.pxHeight()) / th; - _docThumbx = (_docThumbw - st::mvDocBlue.pxWidth()) / 2; + _docThumbw = (tw * st::mvDocIconSize) / th; + _docThumbx = (_docThumbw - st::mvDocIconSize) / 2; _docThumby = 0; } else { - _docThumbw = st::mvDocBlue.pxWidth(); + _docThumbw = st::mvDocIconSize; _docThumbx = 0; - _docThumby = ((th * _docThumbw) / tw - st::mvDocBlue.pxHeight()) / 2; + _docThumby = ((th * _docThumbw) / tw - st::mvDocIconSize) / 2; } } - int32 maxw = st::mvDocSize.width() - st::mvDocBlue.pxWidth() - st::mvDocPadding * 3; + int32 maxw = st::mvDocSize.width() - st::mvDocIconSize - st::mvDocPadding * 3; - _docName = (!_doc || _doc->name.isEmpty()) ? lang(_doc ? (_doc->type == StickerDocument ? lng_in_dlg_sticker : lng_mediaview_doc_image) : lng_message_empty) : _doc->name; - int32 lastDot = _docName.lastIndexOf('.'); - _docExt = _doc ? ((lastDot < 0 || lastDot + 2 > _docName.size()) ? _docName : _docName.mid(lastDot + 1)) : QString(); + if (_doc) { + _docName = (_doc->type == StickerDocument) ? lang(lng_in_dlg_sticker) : (_doc->type == AnimatedDocument ? qsl("GIF") : (_doc->name.isEmpty() ? lang(lng_mediaview_doc_image) : _doc->name)); + } else { + _docName = lang(lng_message_empty); + } _docNameWidth = st::mvDocNameFont->width(_docName); if (_docNameWidth > maxw) { _docName = st::mvDocNameFont->elided(_docName, maxw, Qt::ElideMiddle); _docNameWidth = st::mvDocNameFont->width(_docName); } - int32 extmaxw = (st::mvDocBlue.pxWidth() - st::mvDocExtPadding * 2); - - _docExtWidth = st::mvDocExtFont->width(_docExt); - if (_docExtWidth > extmaxw) { - _docExt = st::mvDocNameFont->elided(_docExt, extmaxw, Qt::ElideMiddle); - _docExtWidth = st::mvDocNameFont->width(_docExt); - } - - _docRadialFirst = _docRadialLast = _docRadialStart = 0; - - float64 prg = (_doc && _doc->loader) ? _doc->loader->currentProgress() : 0; - a_docRadial = anim::fvalue(prg, qMax(prg, 0.0001)); + _docRadial.stop(); // _docSize is updated in updateControls() _docRect = QRect((width() - st::mvDocSize.width()) / 2, (height() - st::mvDocSize.height()) / 2, st::mvDocSize.width(), st::mvDocSize.height()); - _docIconRect = myrtlrect(_docRect.x() + st::mvDocPadding, _docRect.y() + st::mvDocPadding, st::mvDocBlue.pxWidth(), st::mvDocBlue.pxHeight()); + _docIconRect = myrtlrect(_docRect.x() + st::mvDocPadding, _docRect.y() + st::mvDocPadding, st::mvDocIconSize, st::mvDocIconSize); } else if (!_current.isNull()) { _current.setDevicePixelRatio(cRetinaFactor()); - _w = _current.width() / cIntRetinaFactor(); - _h = _current.height() / cIntRetinaFactor(); + _w = convertScale(_current.width()); + _h = convertScale(_current.height()); } else { - _w = _currentGif.w / cIntRetinaFactor(); - _h = _currentGif.h / cIntRetinaFactor(); + _w = convertScale(_gif->width()); + _h = convertScale(_gif->height()); } if (isHidden()) { moveToScreen(); @@ -1081,9 +1083,8 @@ void MediaView::paintEvent(QPaintEvent *e) { QRect r(e->rect()); QRegion region(e->region()); QVector rs(region.rects()); - if (rs.size() > 1) { - int a = 0; - } + + uint64 ms = getms(); Painter p(this); @@ -1103,7 +1104,7 @@ void MediaView::paintEvent(QPaintEvent *e) { // photo if (_photo) { int32 w = _width * cIntRetinaFactor(); - if (_full <= 0 && _photo->full->loaded()) { + if (_full <= 0 && _photo->loaded()) { int32 h = int((_photo->full->height() * (qreal(w) / qreal(_photo->full->width()))) + 0.9999); _current = _photo->full->pixNoCache(w, h, true); if (cRetina()) _current.setDevicePixelRatio(cRetinaFactor()); @@ -1117,23 +1118,25 @@ void MediaView::paintEvent(QPaintEvent *e) { int32 h = int((_photo->full->height() * (qreal(w) / qreal(_photo->full->width()))) + 0.9999); _current = _photo->thumb->pixNoCache(w, h, true, true); if (cRetina()) _current.setDevicePixelRatio(cRetinaFactor()); + } else if (_current.isNull()) { + _current = _photo->thumb->pix(); } } p.setOpacity(1); - if (_photo || !_current.isNull() || !_currentGif.isNull()) { + if (_photo || fileShown()) { QRect imgRect(_x, _y, _w, _h); - const QPixmap *toDraw = _currentGif.isNull() ? &_current : &_currentGif.current(_currentGif.w, _currentGif.h, false); if (imgRect.intersects(r)) { - if (toDraw->hasAlpha() && (!_doc || !_doc->sticker() || _doc->sticker()->img->isNull())) { + QPixmap toDraw = _current.isNull() ? _gif->current(_gif->width(), _gif->height(), _gif->width(), _gif->height(), ms) : _current; + if (!_gif && (!_doc || !_doc->sticker() || _doc->sticker()->img->isNull()) && toDraw.hasAlpha()) { p.fillRect(imgRect, _transparentBrush); } - if (_zoom) { + if (toDraw.width() != _w * cIntRetinaFactor()) { bool was = (p.renderHints() & QPainter::SmoothPixmapTransform); if (!was) p.setRenderHint(QPainter::SmoothPixmapTransform, true); - p.drawPixmap(QRect(_x, _y, _w, _h), *toDraw); + p.drawPixmap(QRect(_x, _y, _w, _h), toDraw); if (!was) p.setRenderHint(QPainter::SmoothPixmapTransform, false); } else { - p.drawPixmap(_x, _y, *toDraw); + p.drawPixmap(_x, _y, toDraw); } uint64 ms = 0; @@ -1194,52 +1197,52 @@ void MediaView::paintEvent(QPaintEvent *e) { if (_docRect.intersects(r)) { p.fillRect(_docRect, st::mvDocBg->b); if (_docIconRect.intersects(r)) { + bool radial = false; + float64 radialOpacity = 0; + if (_docRadial.animating()) { + _docRadial.step(ms); + radial = _docRadial.animating(); + radialOpacity = _docRadial.opacity(); + } icon = true; if (!_doc || _doc->thumb->isNull()) { - if ((!_doc || !_doc->already().isEmpty()) && (!_docRadialStart || _docRadialOpacity < 1)) { - p.drawPixmap(_docIconRect.topLeft(), App::sprite(), _docIcon); + p.fillRect(_docIconRect, _docIconColor->b); + if ((!_doc || _doc->loaded()) && (!radial || radialOpacity < 1)) { + p.drawSprite(_docIconRect.topLeft() + QPoint(rtl() ? 0 : (_docIconRect.width() - _docIcon.pxWidth()), 0), _docIcon); p.setPen(st::mvDocExtColor->p); p.setFont(st::mvDocExtFont->f); if (!_docExt.isEmpty()) { p.drawText(_docIconRect.x() + (_docIconRect.width() - _docExtWidth) / 2, _docIconRect.y() + st::mvDocExtTop + st::mvDocExtFont->ascent, _docExt); } - } else { - p.fillRect(_docIconRect, _docIconColor->b); } } else { int32 rf(cIntRetinaFactor()); - p.drawPixmap(_docIconRect.topLeft(), _doc->thumb->pix(_docThumbw), QRect(_docThumbx * rf, _docThumby * rf, st::mvDocBlue.pxWidth() * rf, st::mvDocBlue.pxHeight() * rf)); + p.drawPixmap(_docIconRect.topLeft(), _doc->thumb->pix(_docThumbw), QRect(_docThumbx * rf, _docThumby * rf, st::mvDocIconSize * rf, st::mvDocIconSize * rf)); } float64 o = overLevel(OverIcon); - if (_doc && _docRadialStart > 0) { - if (_doc->already().isEmpty() && _docRadialOpacity < 1) { - p.setOpacity((o * 1. + (1 - o) * st::radialDownloadOpacity) * (1 - _docRadialOpacity)); + if (radial) { + if (!_doc->loaded() && radialOpacity < 1) { + p.setOpacity((o * 1. + (1 - o) * st::radialDownloadOpacity) * (1 - radialOpacity)); p.drawSpriteCenter(_docIconRect, st::radialDownload); } - p.setRenderHint(QPainter::HighQualityAntialiasing); - QRect inner(QPoint(_docIconRect.x() + ((_docIconRect.width() - st::radialSize.width()) / 2), _docIconRect.y() + ((_docIconRect.height() - st::radialSize.height()) / 2)), st::radialSize); p.setPen(Qt::NoPen); - p.setBrush(st::black->b); - p.setOpacity(_docRadialOpacity * st::radialBgOpacity); - p.drawEllipse(inner); + p.setBrush(st::black); + p.setOpacity(radialOpacity * st::radialBgOpacity); - p.setOpacity((o * 1. + (1 - o) * st::radialCancelOpacity) * _docRadialOpacity); + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.drawEllipse(inner); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + + p.setOpacity((o * 1. + (1 - o) * st::radialCancelOpacity) * radialOpacity); p.drawSpriteCenter(_docIconRect, st::radialCancel); + p.setOpacity(1); QRect arc(inner.marginsRemoved(QMargins(st::radialLine, st::radialLine, st::radialLine, st::radialLine))); - - p.setOpacity(_docRadialOpacity); - p.setPen(_docRadialPen); - - int len = 16 + a_docRadial.current() * 5744; - p.drawArc(arc, 1440 - a_docRadialStart.current() * 5760 - len, len); - - p.setOpacity(1); - p.setRenderHint(QPainter::HighQualityAntialiasing, false); - } else if (_doc && _doc->already().isEmpty()) { + _docRadial.draw(p, arc, st::radialLine, st::white); + } else if (_doc && !_doc->loaded()) { p.setOpacity((o * 1. + (1 - o) * st::radialDownloadOpacity)); p.drawSpriteCenter(_docIconRect, st::radialDownload); } @@ -1249,11 +1252,11 @@ void MediaView::paintEvent(QPaintEvent *e) { name = true; p.setPen(st::mvDocNameColor->p); p.setFont(st::mvDocNameFont->f); - p.drawTextLeft(_docRect.x() + 2 * st::mvDocPadding + st::mvDocBlue.pxWidth(), _docRect.y() + st::mvDocPadding + st::mvDocNameTop, width(), _docName, _docNameWidth); + p.drawTextLeft(_docRect.x() + 2 * st::mvDocPadding + st::mvDocIconSize, _docRect.y() + st::mvDocPadding + st::mvDocNameTop, width(), _docName, _docNameWidth); p.setPen(st::mvDocSizeColor->p); p.setFont(st::mvFont->f); - p.drawTextLeft(_docRect.x() + 2 * st::mvDocPadding + st::mvDocBlue.pxWidth(), _docRect.y() + st::mvDocPadding + st::mvDocSizeTop, width(), _docSize, _docSizeWidth); + p.drawTextLeft(_docRect.x() + 2 * st::mvDocPadding + st::mvDocIconSize, _docRect.y() + st::mvDocPadding + st::mvDocSizeTop, width(), _docSize, _docSizeWidth); } } } @@ -1390,7 +1393,7 @@ void MediaView::keyPressEvent(QKeyEvent *e) { } else if (e->key() == Qt::Key_Copy || (e->key() == Qt::Key_C && e->modifiers().testFlag(Qt::ControlModifier))) { onCopy(); } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return || e->key() == Qt::Key_Space) { - if (_doc && !_doc->loader && _current.isNull() && _currentGif.isNull()) { + if (_doc && !_doc->loading() && !fileShown()) { onDocClick(); } } else if (e->key() == Qt::Key_Left) { @@ -1434,7 +1437,7 @@ void MediaView::keyPressEvent(QKeyEvent *e) { newZoom = 0; } _x = -_width / 2; - _y = -(((_currentGif.isNull() ? _current.height() : _currentGif.h) / cIntRetinaFactor()) / 2); + _y = -((gifShown() ? _gif->height() : (_current.height() / cIntRetinaFactor())) / 2); float64 z = (_zoom == ZoomToScreenLevel) ? _zoomToScreen : _zoom; if (z >= 0) { _x = qRound(_x * (z + 1)); @@ -1454,8 +1457,8 @@ void MediaView::keyPressEvent(QKeyEvent *e) { } if (_zoom != newZoom) { float64 nx, ny, z = (_zoom == ZoomToScreenLevel) ? _zoomToScreen : _zoom; - _w = (_currentGif.isNull() ? _current.width() : _currentGif.w) / cIntRetinaFactor(); - _h = (_currentGif.isNull() ? _current.height() : _currentGif.h) / cIntRetinaFactor(); + _w = gifShown() ? _gif->width() : (_current.width() / cIntRetinaFactor()); + _h = gifShown() ? _gif->height() : (_current.height() / cIntRetinaFactor()); if (z >= 0) { nx = (_x - width() / 2.) / (z + 1); ny = (_y - height() / 2.) / (z + 1); @@ -1508,11 +1511,13 @@ void MediaView::moveToNext(int32 delta) { _channel = _history ? _history->channelId() : NoChannel; _canForward = _msgid > 0; _canDelete = item->canDelete(); - if (item->getMedia()) { - switch (item->getMedia()->type()) { + stopGif(); + if (HistoryMedia *media = item->getMedia()) { + switch (media->type()) { case MediaTypePhoto: displayPhoto(static_cast(item->getMedia())->photo(), item); preloadData(delta); break; - case MediaTypeDocument: displayDocument(static_cast(item->getMedia())->document(), item); preloadData(delta); break; - case MediaTypeSticker: displayDocument(static_cast(item->getMedia())->document(), item); preloadData(delta); break; + case MediaTypeDocument: + case MediaTypeGif: + case MediaTypeSticker: displayDocument(media->getDocument(), item); preloadData(delta); break; } } else { displayDocument(0, item); @@ -1541,30 +1546,6 @@ void MediaView::preloadData(int32 delta) { int32 from = _index + (delta ? delta : -1), to = _index + (delta ? delta * MediaOverviewPreloadCount : 1); if (from > to) qSwap(from, to); if (_history && _overview != OverviewCount) { - for (int32 i = from; i <= to; ++i) { - History *previewHistory = _msgmigrated ? _migrated : _history; - int32 previewIndex = i; - if (_migrated) { - if (_msgmigrated && previewIndex >= _migrated->overview[_overview].size()) { - previewHistory = _history; - previewIndex -= _migrated->overview[_overview].size() + (_history->overviewCount(_overview) - _history->overview[_overview].size()); - } else if (!_msgmigrated && previewIndex < 0) { - previewHistory = _migrated; - previewIndex += _migrated->overview[_overview].size(); - } - } - if (previewIndex >= 0 && previewIndex < previewHistory->overview[_overview].size() && (previewHistory != (_msgmigrated ? _migrated : _history) || previewIndex != _index)) { - if (HistoryItem *item = App::histItemById(previewHistory->channelId(), previewHistory->overview[_overview][previewIndex])) { - if (HistoryMedia *media = item->getMedia()) { - switch (media->type()) { - case MediaTypePhoto: static_cast(media)->photo()->full->load(); break; - case MediaTypeDocument: static_cast(media)->document()->thumb->load(); break; - case MediaTypeSticker: static_cast(media)->document()->sticker()->img->load(); break; - } - } - } - } - } int32 forgetIndex = _index - delta * 2; History *forgetHistory = _msgmigrated ? _migrated : _history; if (_migrated) { @@ -1581,8 +1562,39 @@ void MediaView::preloadData(int32 delta) { if (HistoryMedia *media = item->getMedia()) { switch (media->type()) { case MediaTypePhoto: static_cast(media)->photo()->forget(); break; - case MediaTypeDocument: static_cast(media)->document()->forget(); break; - case MediaTypeSticker: static_cast(media)->document()->forget(); break; + case MediaTypeDocument: + case MediaTypeGif: + case MediaTypeSticker: media->getDocument()->forget(); break; + } + } + } + } + + for (int32 i = from; i <= to; ++i) { + History *previewHistory = _msgmigrated ? _migrated : _history; + int32 previewIndex = i; + if (_migrated) { + if (_msgmigrated && previewIndex >= _migrated->overview[_overview].size()) { + previewHistory = _history; + previewIndex -= _migrated->overview[_overview].size() + (_history->overviewCount(_overview) - _history->overview[_overview].size()); + } else if (!_msgmigrated && previewIndex < 0) { + previewHistory = _migrated; + previewIndex += _migrated->overview[_overview].size(); + } + } + if (previewIndex >= 0 && previewIndex < previewHistory->overview[_overview].size() && (previewHistory != (_msgmigrated ? _migrated : _history) || previewIndex != _index)) { + if (HistoryItem *item = App::histItemById(previewHistory->channelId(), previewHistory->overview[_overview][previewIndex])) { + if (HistoryMedia *media = item->getMedia()) { + switch (media->type()) { + case MediaTypePhoto: static_cast(media)->photo()->download(); break; + case MediaTypeDocument: + case MediaTypeGif: { + DocumentData *doc = media->getDocument(); + doc->thumb->load(); + doc->automaticLoad(item); + } break; + case MediaTypeSticker: media->getDocument()->sticker()->img->load(); break; + } } } } @@ -1595,7 +1607,7 @@ void MediaView::preloadData(int32 delta) { } for (int32 i = from; i <= to; ++i) { if (i >= 0 && i < _user->photos.size() && i != _index) { - _user->photos[i]->full->load(); + _user->photos[i]->download(); } } int32 forgetIndex = _index - delta * 2; @@ -1721,7 +1733,7 @@ bool MediaView::updateOverState(OverState newState) { } else { _animOpacities.insert(_over, anim::fvalue(1, 0)); } - if (!animating()) anim::start(this); + if (!_a_state.animating()) _a_state.start(); } else { result = false; } @@ -1734,7 +1746,7 @@ bool MediaView::updateOverState(OverState newState) { } else { _animOpacities.insert(_over, anim::fvalue(0, 1)); } - if (!animating()) anim::start(this); + if (!_a_state.animating()) _a_state.start(); setCursor(style::cur_pointer); } else { setCursor(style::cur_default); @@ -1781,7 +1793,7 @@ void MediaView::updateOver(QPoint pos) { updateOverState(OverHeader); } else if (_saveVisible && _saveNav.contains(pos)) { updateOverState(OverSave); - } else if (_doc && _current.isNull() && _currentGif.isNull() && _docIconRect.contains(pos)) { + } else if (_doc && !fileShown() && _docIconRect.contains(pos)) { updateOverState(OverIcon); } else if (_moreNav.contains(pos)) { updateOverState(OverMore); @@ -1803,7 +1815,7 @@ void MediaView::mouseReleaseEvent(QMouseEvent *e) { } else { if (reBotCommand().match(lnk->encoded()).hasMatch() && _history) { App::wnd()->hideMediaview(); - App::main()->showPeerHistory(_history->peer->id, ShowAtTheEndMsgId); + Ui::showPeerHistory(_history, ShowAtTheEndMsgId); } lnk->onClick(e->button()); } @@ -1836,7 +1848,7 @@ void MediaView::mouseReleaseEvent(QMouseEvent *e) { } _dragging = 0; setCursor(style::cur_default); - } else if ((e->pos() - _lastAction).manhattanLength() >= st::mvDeltaFromLastAction && (!_doc || !_current.isNull() || !_currentGif.isNull() || !_docRect.contains(e->pos()))) { + } else if ((e->pos() - _lastAction).manhattanLength() >= st::mvDeltaFromLastAction && (!_doc || fileShown() || !_docRect.contains(e->pos()))) { close(); } _pressed = false; @@ -1859,6 +1871,7 @@ void MediaView::contextMenuEvent(QContextMenuEvent *e) { connect(_menu, SIGNAL(destroyed(QObject*)), this, SLOT(onMenuDestroy(QObject*))); _menu->popup(e->globalPos()); e->accept(); + activateControls(); } } @@ -1948,11 +1961,15 @@ void MediaView::hide() { _controlsState = ControlsShown; a_cOpacity = anim::fvalue(1, 1); QWidget::hide(); + stopGif(); + + Notify::clipStopperHidden(ClipStopperMediaview); } void MediaView::onMenuDestroy(QObject *obj) { if (_menu == obj) { _menu = 0; + activateControls(); } _receiveMouse = false; QTimer::singleShot(0, this, SLOT(receiveMouse())); diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h index d87f2fec51..4a8c0e86cd 100644 --- a/Telegram/SourceFiles/mediaview.h +++ b/Telegram/SourceFiles/mediaview.h @@ -22,7 +22,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "dropdown.h" -class MediaView : public TWidget, public RPCSender, public Animated { +class MediaView : public TWidget, public RPCSender { Q_OBJECT public: @@ -65,14 +65,14 @@ public: void updateControls(); void updateDropdown(); - bool animStep(float64 dt); - void showSaveMsgFile(); void close(); void activateControls(); void onDocClick(); + void clipCallback(ClipReaderNotification notification); + ~MediaView(); public slots: @@ -98,7 +98,6 @@ public slots: void onTouchTimer(); void updateImage(); - void onGifUpdated(); private: @@ -113,6 +112,9 @@ private: void updateHeader(); void snapXY(); + void step_state(uint64 ms, bool timer); + void step_radial(uint64 ms, bool timer); + QBrush _transparentBrush; PhotoData *_photo; @@ -138,19 +140,20 @@ private: bool _pressed; int32 _dragging; QPixmap _current; - AnimatedGif _currentGif; + ClipReader *_gif; int32 _full; // -1 - thumb, 0 - medium, 1 - full + bool fileShown() const; + bool gifShown() const; + void stopGif(); + style::sprite _docIcon; style::color _docIconColor; QString _docName, _docSize, _docExt; int32 _docNameWidth, _docSizeWidth, _docExtWidth; QRect _docRect, _docIconRect; int32 _docThumbx, _docThumby, _docThumbw; - uint64 _docRadialFirst, _docRadialStart, _docRadialLast; - float64 _docRadialOpacity; - QPen _docRadialPen; - anim::fvalue a_docRadial, a_docRadialStart; + RadialAnimation _docRadial; LinkButton _docDownload, _docSaveAs, _docCancel; History *_migrated, *_history; // if conversation photos or files overview @@ -184,6 +187,8 @@ private: QPoint _lastAction, _lastMouseMovePos; bool _ignoringDropdown; + Animation _a_state; + enum ControlsState { ControlsShowing, ControlsShown, diff --git a/Telegram/SourceFiles/mtproto/mtp.cpp b/Telegram/SourceFiles/mtproto/mtp.cpp index ebc6b3853c..6b9b26cb0d 100644 --- a/Telegram/SourceFiles/mtproto/mtp.cpp +++ b/Telegram/SourceFiles/mtproto/mtp.cpp @@ -154,6 +154,9 @@ namespace { bool onErrorDefault(mtpRequestId requestId, const RPCError &error) { const QString &err(error.type()); int32 code = error.code(); + if (!mtpIsFlood(error)) { + int breakpoint = 0; + } bool badGuestDC = (code == 400) && (err == qsl("FILE_ID_INVALID")); QRegularExpressionMatch m; if ((m = QRegularExpression("^(FILE|PHONE|NETWORK|USER)_MIGRATE_(\\d+)$").match(err)).hasMatch()) { diff --git a/Telegram/SourceFiles/mtproto/mtpCoreTypes.h b/Telegram/SourceFiles/mtproto/mtpCoreTypes.h index 290c7716c1..d0ed98c4db 100644 --- a/Telegram/SourceFiles/mtproto/mtpCoreTypes.h +++ b/Telegram/SourceFiles/mtproto/mtpCoreTypes.h @@ -368,7 +368,7 @@ static const mtpTypeId mtpLayers[] = { mtpTypeId(mtpc_invokeWithLayer18), }; static const uint32 mtpLayerMaxSingle = sizeof(mtpLayers) / sizeof(mtpLayers[0]); -static const mtpPrime mtpCurrentLayer = 43; +static const mtpPrime mtpCurrentLayer = 45; template class MTPBoxed : public bareT { diff --git a/Telegram/SourceFiles/mtproto/mtpDC.cpp b/Telegram/SourceFiles/mtproto/mtpDC.cpp index 700bd3caa3..ee5a079ad4 100644 --- a/Telegram/SourceFiles/mtproto/mtpDC.cpp +++ b/Telegram/SourceFiles/mtproto/mtpDC.cpp @@ -154,6 +154,7 @@ namespace { mtpUpdateDcOptions(data.vdc_options.c_vector().v); cSetMaxGroupCount(data.vchat_size_max.v); cSetMaxMegaGroupCount(data.vmegagroup_size_max.v); + cSetSavedGifsLimit(data.vsaved_gifs_limit.v); configLoadedOnce = true; Local::writeSettings(); diff --git a/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp b/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp index 536d54bdef..3d3f8fec01 100644 --- a/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp +++ b/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp @@ -26,159 +26,376 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "localstorage.h" namespace { - int32 _priority = 1; + int32 GlobalPriority = 1; struct DataRequested { DataRequested() { memset(v, 0, sizeof(v)); } int64 v[MTPDownloadSessionsCount]; }; - QMap _dataRequested; + QMap DataRequestedMap; } -struct mtpFileLoaderQueue { - mtpFileLoaderQueue() : queries(0), start(0), end(0) { + +struct FileLoaderQueue { + FileLoaderQueue(int32 limit) : queries(0), limit(limit), start(0), end(0) { } - int32 queries; - mtpFileLoader *start, *end; + int32 queries, limit; + FileLoader *start, *end; }; namespace { - typedef QMap LoaderQueues; + typedef QMap LoaderQueues; LoaderQueues queues; -} -mtpFileLoader::mtpFileLoader(int32 dc, const uint64 &volume, int32 local, const uint64 &secret, int32 size) : prev(0), next(0), -priority(0), inQueue(false), complete(false), -_localStatus(LocalNotTried), skippedBytes(0), nextRequestOffset(0), lastComplete(false), -dc(dc), _locationType(UnknownFileLocation), volume(volume), local(local), secret(secret), -id(0), access(0), fileIsOpen(false), size(size), type(mtpc_storage_fileUnknown), _localTaskId(0) { - LoaderQueues::iterator i = queues.find(dc); - if (i == queues.cend()) { - i = queues.insert(dc, mtpFileLoaderQueue()); + FileLoaderQueue _webQueue(MaxWebFileQueries); + + QThread *_webLoadThread = 0; + WebLoadManager *_webLoadManager = 0; + WebLoadManager *webLoadManager() { + return (_webLoadManager && _webLoadManager != FinishedWebLoadManager) ? _webLoadManager : 0; } - queue = &i.value(); + WebLoadMainManager *_webLoadMainManager = 0; } -mtpFileLoader::mtpFileLoader(int32 dc, const uint64 &id, const uint64 &access, LocationType type, const QString &to, int32 size, bool todata) : prev(0), next(0), -priority(0), inQueue(false), complete(false), -_localStatus(LocalNotTried), skippedBytes(0), nextRequestOffset(0), lastComplete(false), -dc(dc), _locationType(type), volume(0), local(0), secret(0), -id(id), access(access), file(to), fname(to), fileIsOpen(false), duplicateInData(todata), size(size), type(mtpc_storage_fileUnknown), _localTaskId(0) { - LoaderQueues::iterator i = queues.find(MTP::dld[0] + dc); - if (i == queues.cend()) { - i = queues.insert(MTP::dld[0] + dc, mtpFileLoaderQueue()); - } - queue = &i.value(); +FileLoader::FileLoader(const QString &toFile, int32 size, LocationType locationType, LoadToCacheSetting toCache, LoadFromCloudSetting fromCloud, bool autoLoading) +: _prev(0) +, _next(0) +, _priority(0) +, _paused(false) +, _autoLoading(autoLoading) +, _inQueue(false) +, _complete(false) +, _localStatus(LocalNotTried) +, _file(toFile) +, _fname(toFile) +, _fileIsOpen(false) +, _toCache(toCache) +, _fromCloud(fromCloud) +, _size(size) +, _type(mtpc_storage_fileUnknown) +, _locationType(locationType) +, _localTaskId(0) { } -QByteArray mtpFileLoader::imageFormat() const { +QByteArray FileLoader::imageFormat() const { if (_imageFormat.isEmpty() && _locationType == UnknownFileLocation) { readImage(); } return _imageFormat; } -QPixmap mtpFileLoader::imagePixmap() const { +QPixmap FileLoader::imagePixmap() const { if (_imagePixmap.isNull() && _locationType == UnknownFileLocation) { readImage(); } return _imagePixmap; } -void mtpFileLoader::readImage() const { +void FileLoader::readImage() const { QByteArray format; - switch (type) { + switch (_type) { case mtpc_storage_fileGif: format = "GIF"; break; case mtpc_storage_fileJpeg: format = "JPG"; break; case mtpc_storage_filePng: format = "PNG"; break; default: format = QByteArray(); break; } - _imagePixmap = QPixmap::fromImage(App::readImage(data, &format, false), Qt::ColorOnly); + _imagePixmap = QPixmap::fromImage(App::readImage(_data, &format, false), Qt::ColorOnly); if (!_imagePixmap.isNull()) { _imageFormat = format; } } -float64 mtpFileLoader::currentProgress() const { - if (complete) return 1; +float64 FileLoader::currentProgress() const { + if (_complete) return 1; if (!fullSize()) return 0; return float64(currentOffset()) / fullSize(); } -int32 mtpFileLoader::currentOffset(bool includeSkipped) const { - return (fileIsOpen ? file.size() : data.size()) - (includeSkipped ? 0 : skippedBytes); +int32 FileLoader::fullSize() const { + return _size; } -int32 mtpFileLoader::fullSize() const { - return size; +bool FileLoader::setFileName(const QString &fileName) { + if (_toCache != LoadToCacheAsWell || !_fname.isEmpty()) return fileName.isEmpty(); + _fname = fileName; + _file.setFileName(_fname); + return true; } -void mtpFileLoader::setFileName(const QString &fileName) { - if (duplicateInData && fname.isEmpty()) { - file.setFileName(fname = fileName); - } +void FileLoader::permitLoadFromCloud() { + _fromCloud = LoadFromCloudOrLocal; } -uint64 mtpFileLoader::objId() const { - return id; -} - -void mtpFileLoader::loadNext() { - if (queue->queries >= MaxFileQueries) return; - for (mtpFileLoader *i = queue->start; i;) { +void FileLoader::loadNext() { + if (_queue->queries >= _queue->limit) return; + for (FileLoader *i = _queue->start; i;) { if (i->loadPart()) { - if (queue->queries >= MaxFileQueries) return; + if (_queue->queries >= _queue->limit) return; } else { - i = i->next; + i = i->_next; } } } -void mtpFileLoader::finishFail() { - bool started = currentOffset(true) > 0; - cancelRequests(); - type = mtpc_storage_fileUnknown; - complete = true; - if (fileIsOpen) { - file.close(); - fileIsOpen = false; - file.remove(); +void FileLoader::removeFromQueue() { + if (!_inQueue) return; + if (_next) { + _next->_prev = _prev; } - data = QByteArray(); - emit failed(this, started); - file.setFileName(fname = QString()); + if (_prev) { + _prev->_next = _next; + } + if (_queue->end == this) { + _queue->end = _prev; + } + if (_queue->start == this) { + _queue->start = _next; + } + _next = _prev = 0; + _inQueue = false; +} + +void FileLoader::pause() { + removeFromQueue(); + _paused = true; +} + +FileLoader::~FileLoader() { + if (_localTaskId) { + Local::cancelTask(_localTaskId); + } + removeFromQueue(); +} + +void FileLoader::localLoaded(const StorageImageSaved &result, const QByteArray &imageFormat, const QPixmap &imagePixmap) { + _localTaskId = 0; + if (result.type == StorageFileUnknown) { + _localStatus = LocalFailed; + start(true); + return; + } + _data = result.data; + _type = mtpFromStorageType(result.type); + if (!imagePixmap.isNull()) { + _imageFormat = imageFormat; + _imagePixmap = imagePixmap; + } + _localStatus = LocalLoaded; + if (!_fname.isEmpty() && _toCache == LoadToCacheAsWell) { + if (!_fileIsOpen) _fileIsOpen = _file.open(QIODevice::WriteOnly); + if (!_fileIsOpen) { + cancel(true); + return; + } + if (_file.write(_data) != qint64(_data.size())) { + cancel(true); + return; + } + } + + _complete = true; + if (_fileIsOpen) { + _file.close(); + _fileIsOpen = false; + psPostprocessFile(QFileInfo(_file).absoluteFilePath()); + } + emit App::wnd()->imageLoaded(); + emit progress(this); loadNext(); } +void FileLoader::start(bool loadFirst, bool prior) { + if (_paused) { + _paused = false; + } + if (_complete || tryLoadLocal()) return; + + if (_fromCloud == LoadFromLocalOnly) { + cancel(); + return; + } + + if (!_fname.isEmpty() && _toCache == LoadToFileOnly && !_fileIsOpen) { + _fileIsOpen = _file.open(QIODevice::WriteOnly); + if (!_fileIsOpen) { + return cancel(true); + } + } + + FileLoader *before = 0, *after = 0; + if (prior) { + if (_inQueue && _priority == GlobalPriority) { + if (loadFirst) { + if (!_prev) return startLoading(loadFirst, prior); + before = _queue->start; + } else { + if (!_next || _next->_priority < GlobalPriority) return startLoading(loadFirst, prior); + after = _next; + while (after->_next && after->_next->_priority == GlobalPriority) { + after = after->_next; + } + } + } else { + _priority = GlobalPriority; + if (loadFirst) { + if (_inQueue && !_prev) return startLoading(loadFirst, prior); + before = _queue->start; + } else { + if (_inQueue) { + if (_next && _next->_priority == GlobalPriority) { + after = _next; + } else if (_prev && _prev->_priority < GlobalPriority) { + before = _prev; + while (before->_prev && before->_prev->_priority < GlobalPriority) { + before = before->_prev; + } + } else { + return startLoading(loadFirst, prior); + } + } else { + if (_queue->start && _queue->start->_priority == GlobalPriority) { + after = _queue->start; + } else { + before = _queue->start; + } + } + if (after) { + while (after->_next && after->_next->_priority == GlobalPriority) { + after = after->_next; + } + } + } + } + } else { + if (loadFirst) { + if (_inQueue && (!_prev || _prev->_priority == GlobalPriority)) return startLoading(loadFirst, prior); + before = _prev; + while (before->_prev && before->_prev->_priority != GlobalPriority) { + before = before->_prev; + } + } else { + if (_inQueue && !_next) return startLoading(loadFirst, prior); + after = _queue->end; + } + } + + removeFromQueue(); + + _inQueue = true; + if (!_queue->start) { + _queue->start = _queue->end = this; + } else if (before) { + if (before != _next) { + _prev = before->_prev; + _next = before; + _next->_prev = this; + if (_prev) { + _prev->_next = this; + } + if (_queue->start->_prev) _queue->start = _queue->start->_prev; + } + } else if (after) { + if (after != _prev) { + _next = after->_next; + _prev = after; + after->_next = this; + if (_next) { + _next->_prev = this; + } + if (_queue->end->_next) _queue->end = _queue->end->_next; + } + } else { + LOG(("Queue Error: _start && !before && !after")); + } + return startLoading(loadFirst, prior); +} + +void FileLoader::cancel() { + cancel(false); +} + +void FileLoader::cancel(bool fail) { + bool started = currentOffset(true) > 0; + cancelRequests(); + _type = mtpc_storage_fileUnknown; + _complete = true; + if (_fileIsOpen) { + _file.close(); + _fileIsOpen = false; + _file.remove(); + } + _data = QByteArray(); + if (fail) { + emit failed(this, started); + } else { + emit progress(this); + } + _fname = QString(); + _file.setFileName(_fname); + loadNext(); +} + +void FileLoader::startLoading(bool loadFirst, bool prior) { + if ((_queue->queries >= _queue->limit && (!loadFirst || !prior)) || _complete) return; + loadPart(); +} + +mtpFileLoader::mtpFileLoader(const StorageImageLocation *location, int32 size, LoadFromCloudSetting fromCloud, bool autoLoading) +: FileLoader(QString(), size, UnknownFileLocation, LoadToCacheAsWell, fromCloud, autoLoading) +, _lastComplete(false) +, _skippedBytes(0) +, _nextRequestOffset(0) +, _dc(location->dc()) +, _location(location) +, _id(0) +, _access(0) { + LoaderQueues::iterator i = queues.find(MTP::dld[0] + _dc); + if (i == queues.cend()) { + i = queues.insert(MTP::dld[0] + _dc, FileLoaderQueue(MaxFileQueries)); + } + _queue = &i.value(); +} + +mtpFileLoader::mtpFileLoader(int32 dc, const uint64 &id, const uint64 &access, LocationType type, const QString &to, int32 size, LoadToCacheSetting toCache, LoadFromCloudSetting fromCloud, bool autoLoading) +: FileLoader(to, size, type, toCache, fromCloud, autoLoading) +, _lastComplete(false) +, _skippedBytes(0) +, _nextRequestOffset(0) +, _dc(dc) +, _location(0) +, _id(id) +, _access(access) { + LoaderQueues::iterator i = queues.find(MTP::dld[0] + _dc); + if (i == queues.cend()) { + i = queues.insert(MTP::dld[0] + _dc, FileLoaderQueue(MaxFileQueries)); + } + _queue = &i.value(); +} + +int32 mtpFileLoader::currentOffset(bool includeSkipped) const { + return (_fileIsOpen ? _file.size() : _data.size()) - (includeSkipped ? 0 : _skippedBytes); +} + bool mtpFileLoader::loadPart() { - if (complete || lastComplete || (!requests.isEmpty() && !size)) return false; - if (size && nextRequestOffset >= size) return false; + if (_complete || _lastComplete || (!_requests.isEmpty() && !_size)) return false; + if (_size && _nextRequestOffset >= _size) return false; int32 limit = DocumentDownloadPartSize; MTPInputFileLocation loc; - switch (_locationType) { - case UnknownFileLocation: - loc = MTP_inputFileLocation(MTP_long(volume), MTP_int(local), MTP_long(secret)); + if (_location) { + loc = MTP_inputFileLocation(MTP_long(_location->volume()), MTP_int(_location->local()), MTP_long(_location->secret())); limit = DownloadPartSize; - break; - case VideoFileLocation: - loc = MTP_inputVideoFileLocation(MTP_long(id), MTP_long(access)); - break; - case AudioFileLocation: - loc = MTP_inputAudioFileLocation(MTP_long(id), MTP_long(access)); - break; - case DocumentFileLocation: - loc = MTP_inputDocumentFileLocation(MTP_long(id), MTP_long(access)); - break; - default: - finishFail(); - return false; - break; + } else { + switch (_locationType) { + case VideoFileLocation: loc = MTP_inputVideoFileLocation(MTP_long(_id), MTP_long(_access)); break; + case AudioFileLocation: loc = MTP_inputAudioFileLocation(MTP_long(_id), MTP_long(_access)); break; + case DocumentFileLocation: loc = MTP_inputDocumentFileLocation(MTP_long(_id), MTP_long(_access)); break; + default: cancel(true); return false; break; + } } - - int32 offset = nextRequestOffset, dcIndex = 0; - DataRequested &dr(_dataRequested[dc]); - if (size) { + int32 offset = _nextRequestOffset, dcIndex = 0; + DataRequested &dr(DataRequestedMap[_dc]); + if (_size) { for (int32 i = 1; i < MTPDownloadSessionsCount; ++i) { if (dr.v[i] < dr.v[dcIndex]) { dcIndex = i; @@ -186,104 +403,103 @@ bool mtpFileLoader::loadPart() { } } - App::app()->killDownloadSessionsStop(dc); + App::app()->killDownloadSessionsStop(_dc); - mtpRequestId reqId = MTP::send(MTPupload_GetFile(MTPupload_getFile(loc, MTP_int(offset), MTP_int(limit))), rpcDone(&mtpFileLoader::partLoaded, offset), rpcFail(&mtpFileLoader::partFailed), MTP::dld[dcIndex] + dc, 50); + mtpRequestId reqId = MTP::send(MTPupload_GetFile(MTPupload_getFile(loc, MTP_int(offset), MTP_int(limit))), rpcDone(&mtpFileLoader::partLoaded, offset), rpcFail(&mtpFileLoader::partFailed), MTP::dld[dcIndex] + _dc, 50); - ++queue->queries; + ++_queue->queries; dr.v[dcIndex] += limit; - requests.insert(reqId, dcIndex); - nextRequestOffset += limit; + _requests.insert(reqId, dcIndex); + _nextRequestOffset += limit; return true; } void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRequestId req) { -// uint64 ms = getms(); - Requests::iterator i = requests.find(req); - if (i == requests.cend()) return loadNext(); + Requests::iterator i = _requests.find(req); + if (i == _requests.cend()) return loadNext(); int32 limit = (_locationType == UnknownFileLocation) ? DownloadPartSize : DocumentDownloadPartSize; int32 dcIndex = i.value(); - _dataRequested[dc].v[dcIndex] -= limit; + DataRequestedMap[_dc].v[dcIndex] -= limit; - --queue->queries; - requests.erase(i); + --_queue->queries; + _requests.erase(i); const MTPDupload_file &d(result.c_upload_file()); const string &bytes(d.vbytes.c_string().v); if (bytes.size()) { - if (fileIsOpen) { - int64 fsize = file.size(); + if (_fileIsOpen) { + int64 fsize = _file.size(); if (offset < fsize) { - skippedBytes -= bytes.size(); + _skippedBytes -= bytes.size(); } else if (offset > fsize) { - skippedBytes += offset - fsize; + _skippedBytes += offset - fsize; } - file.seek(offset); - if (file.write(bytes.data(), bytes.size()) != qint64(bytes.size())) { - return finishFail(); + _file.seek(offset); + if (_file.write(bytes.data(), bytes.size()) != qint64(bytes.size())) { + return cancel(true); } } else { - data.reserve(offset + bytes.size()); - if (offset > data.size()) { - skippedBytes += offset - data.size(); - data.resize(offset); + _data.reserve(offset + bytes.size()); + if (offset > _data.size()) { + _skippedBytes += offset - _data.size(); + _data.resize(offset); } - if (offset == data.size()) { - data.append(bytes.data(), bytes.size()); + if (offset == _data.size()) { + _data.append(bytes.data(), bytes.size()); } else { - skippedBytes -= bytes.size(); - if (int64(offset + bytes.size()) > data.size()) { - data.resize(offset + bytes.size()); + _skippedBytes -= bytes.size(); + if (int64(offset + bytes.size()) > _data.size()) { + _data.resize(offset + bytes.size()); } - memcpy(data.data() + offset, bytes.data(), bytes.size()); + memcpy(_data.data() + offset, bytes.data(), bytes.size()); } } } if (!bytes.size() || (bytes.size() % 1024)) { // bad next offset - lastComplete = true; + _lastComplete = true; } - if (requests.isEmpty() && (lastComplete || (size && nextRequestOffset >= size))) { - if (!fname.isEmpty() && duplicateInData) { - if (!fileIsOpen) fileIsOpen = file.open(QIODevice::WriteOnly); - if (!fileIsOpen) { - return finishFail(); + if (_requests.isEmpty() && (_lastComplete || (_size && _nextRequestOffset >= _size))) { + if (!_fname.isEmpty() && (_toCache == LoadToCacheAsWell)) { + if (!_fileIsOpen) _fileIsOpen = _file.open(QIODevice::WriteOnly); + if (!_fileIsOpen) { + return cancel(true); } - if (file.write(data) != qint64(data.size())) { - return finishFail(); + if (_file.write(_data) != qint64(_data.size())) { + return cancel(true); } } - type = d.vtype.type(); - complete = true; - if (fileIsOpen) { - file.close(); - fileIsOpen = false; - psPostprocessFile(QFileInfo(file).absoluteFilePath()); + _type = d.vtype.type(); + _complete = true; + if (_fileIsOpen) { + _file.close(); + _fileIsOpen = false; + psPostprocessFile(QFileInfo(_file).absoluteFilePath()); } removeFromQueue(); emit App::wnd()->imageLoaded(); - if (!queue->queries) { - App::app()->killDownloadSessionsStart(dc); + if (!_queue->queries) { + App::app()->killDownloadSessionsStart(_dc); } if (_localStatus == LocalNotFound || _localStatus == LocalFailed) { if (_locationType != UnknownFileLocation) { // audio, video, document - MediaKey mkey = mediaKey(_locationType, dc, id); - if (!fname.isEmpty()) { - Local::writeFileLocation(mkey, FileLocation(mtpToStorageType(type), fname)); + MediaKey mkey = mediaKey(_locationType, _dc, _id); + if (!_fname.isEmpty()) { + Local::writeFileLocation(mkey, FileLocation(mtpToStorageType(_type), _fname)); } - if (duplicateInData) { + if (_toCache == LoadToCacheAsWell) { if (_locationType == DocumentFileLocation) { - Local::writeStickerImage(mkey, data); + Local::writeStickerImage(mkey, _data); } else if (_locationType == AudioFileLocation) { - Local::writeAudio(mkey, data); + Local::writeAudio(mkey, _data); } } } else { - Local::writeImage(storageKey(dc, volume, local), StorageImageSaved(mtpToStorageType(type), data)); + Local::writeImage(storageKey(*_location), StorageImageSaved(mtpToStorageType(_type), _data)); } } } @@ -294,30 +510,26 @@ void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRe bool mtpFileLoader::partFailed(const RPCError &error) { if (mtpIsFlood(error)) return false; - finishFail(); + cancel(true); return true; } -void mtpFileLoader::removeFromQueue() { - if (!inQueue) return; - if (next) { - next->prev = prev; - } - if (prev) { - prev->next = next; - } - if (queue->end == this) { - queue->end = prev; - } - if (queue->start == this) { - queue->start = next; - } - next = prev = 0; - inQueue = false; -} +void mtpFileLoader::cancelRequests() { + if (_requests.isEmpty()) return; -void mtpFileLoader::pause() { - removeFromQueue(); + int32 limit = (_locationType == UnknownFileLocation) ? DownloadPartSize : DocumentDownloadPartSize; + DataRequested &dr(DataRequestedMap[_dc]); + for (Requests::const_iterator i = _requests.cbegin(), e = _requests.cend(); i != e; ++i) { + MTP::cancel(i.key()); + int32 dcIndex = i.value(); + dr.v[dcIndex] -= limit; + } + _queue->queries -= _requests.size(); + _requests.clear(); + + if (!_queue->queries) { + App::app()->killDownloadSessionsStart(_dc); + } } bool mtpFileLoader::tryLoadLocal() { @@ -328,15 +540,11 @@ bool mtpFileLoader::tryLoadLocal() { return true; } - if (_locationType == UnknownFileLocation) { - _localTaskId = Local::startImageLoad(storageKey(dc, volume, local), this); - if (_localTaskId) { - _localStatus = LocalLoading; - return true; - } + if (_location) { + _localTaskId = Local::startImageLoad(storageKey(*_location), this); } else { - if (duplicateInData) { - MediaKey mkey = mediaKey(_locationType, dc, id); + if (_toCache == LoadToCacheAsWell) { + MediaKey mkey = mediaKey(_locationType, _dc, _id); if (_locationType == DocumentFileLocation) { _localTaskId = Local::startStickerImageLoad(mkey, this); } else if (_locationType == AudioFileLocation) { @@ -345,221 +553,487 @@ bool mtpFileLoader::tryLoadLocal() { } } - if (data.isEmpty()) { - _localStatus = LocalNotFound; - return false; + if (_localStatus != LocalNotTried) { + return _complete; + } else if (_localTaskId) { + _localStatus = LocalLoading; + return true; } - - _localStatus = LocalLoaded; - if (!fname.isEmpty() && duplicateInData) { - if (!fileIsOpen) fileIsOpen = file.open(QIODevice::WriteOnly); - if (!fileIsOpen) { - finishFail(); - return true; - } - if (file.write(data) != qint64(data.size())) { - finishFail(); - return true; - } - } - complete = true; - if (fileIsOpen) { - file.close(); - fileIsOpen = false; - psPostprocessFile(QFileInfo(file).absoluteFilePath()); - } - emit App::wnd()->imageLoaded(); - emit progress(this); - loadNext(); - return true; -} - -void mtpFileLoader::localLoaded(const StorageImageSaved &result, const QByteArray &imageFormat, const QPixmap &imagePixmap) { - _localTaskId = 0; - if (result.type == StorageFileUnknown) { - _localStatus = LocalFailed; - start(true); - return; - } - data = result.data; - type = mtpFromStorageType(result.type); - if (!imagePixmap.isNull()) { - _imageFormat = imageFormat; - _imagePixmap = imagePixmap; - } - _localStatus = LocalLoaded; - if (!fname.isEmpty() && duplicateInData) { - if (!fileIsOpen) fileIsOpen = file.open(QIODevice::WriteOnly); - if (!fileIsOpen) { - finishFail(); - return; - } - if (file.write(data) != qint64(data.size())) { - finishFail(); - return; - } - } - complete = true; - if (fileIsOpen) { - file.close(); - fileIsOpen = false; - psPostprocessFile(QFileInfo(file).absoluteFilePath()); - } - emit App::wnd()->imageLoaded(); - emit progress(this); - loadNext(); -} - -void mtpFileLoader::start(bool loadFirst, bool prior) { - if (complete || tryLoadLocal()) return; - - if (!fname.isEmpty() && !duplicateInData && !fileIsOpen) { - fileIsOpen = file.open(QIODevice::WriteOnly); - if (!fileIsOpen) { - return finishFail(); - } - } - - mtpFileLoader *before = 0, *after = 0; - if (prior) { - if (inQueue && priority == _priority) { - if (loadFirst) { - if (!prev) return started(loadFirst, prior); - before = queue->start; - } else { - if (!next || next->priority < _priority) return started(loadFirst, prior); - after = next; - while (after->next && after->next->priority == _priority) { - after = after->next; - } - } - } else { - priority = _priority; - if (loadFirst) { - if (inQueue && !prev) return started(loadFirst, prior); - before = queue->start; - } else { - if (inQueue) { - if (next && next->priority == _priority) { - after = next; - } else if (prev && prev->priority < _priority) { - before = prev; - while (before->prev && before->prev->priority < _priority) { - before = before->prev; - } - } else { - return started(loadFirst, prior); - } - } else { - if (queue->start && queue->start->priority == _priority) { - after = queue->start; - } else { - before = queue->start; - } - } - if (after) { - while (after->next && after->next->priority == _priority) { - after = after->next; - } - } - } - } - } else { - if (loadFirst) { - if (inQueue && (!prev || prev->priority == _priority)) return started(loadFirst, prior); - before = prev; - while (before->prev && before->prev->priority != _priority) { - before = before->prev; - } - } else { - if (inQueue && !next) return started(loadFirst, prior); - after = queue->end; - } - } - - removeFromQueue(); - - inQueue = true; - if (!queue->start) { - queue->start = queue->end = this; - } else if (before) { - if (before != next) { - prev = before->prev; - next = before; - next->prev = this; - if (prev) { - prev->next = this; - } - if (queue->start->prev) queue->start = queue->start->prev; - } - } else if (after) { - if (after != prev) { - next = after->next; - prev = after; - after->next = this; - if (next) { - next->prev = this; - } - if (queue->end->next) queue->end = queue->end->next; - } - } else { - LOG(("Queue Error: _start && !before && !after")); - } - return started(loadFirst, prior); -} - -void mtpFileLoader::cancel() { - cancelRequests(); - type = mtpc_storage_fileUnknown; - complete = true; - if (fileIsOpen) { - file.close(); - fileIsOpen = false; - file.remove(); - } - data = QByteArray(); - file.setFileName(QString()); - emit progress(this); - loadNext(); -} - -void mtpFileLoader::cancelRequests() { - if (requests.isEmpty()) return; - - int32 limit = (_locationType == UnknownFileLocation) ? DownloadPartSize : DocumentDownloadPartSize; - DataRequested &dr(_dataRequested[dc]); - for (Requests::const_iterator i = requests.cbegin(), e = requests.cend(); i != e; ++i) { - MTP::cancel(i.key()); - int32 dcIndex = i.value(); - dr.v[dcIndex] -= limit; - } - queue->queries -= requests.size(); - requests.clear(); - - if (!queue->queries) { - App::app()->killDownloadSessionsStart(dc); - } -} - -bool mtpFileLoader::loading() const { - return inQueue; -} - -void mtpFileLoader::started(bool loadFirst, bool prior) { - if ((queue->queries >= MaxFileQueries && (!loadFirst || !prior)) || complete) return; - loadPart(); + _localStatus = LocalNotFound; + return false; } mtpFileLoader::~mtpFileLoader() { - if (_localTaskId) { - Local::cancelTask(_localTaskId); + cancelRequests(); +} + +webFileLoader::webFileLoader(const QString &url, const QString &to, LoadFromCloudSetting fromCloud, bool autoLoading) +: FileLoader(QString(), 0, UnknownFileLocation, LoadToCacheAsWell, fromCloud, autoLoading) +, _url(url) +, _requestSent(false) +, _already(0) { + _queue = &_webQueue; +} + +bool webFileLoader::loadPart() { + if (_complete || _requestSent || _webLoadManager == FinishedWebLoadManager) return false; + if (!_webLoadManager) { + _webLoadMainManager = new WebLoadMainManager(); + + _webLoadThread = new QThread(); + _webLoadManager = new WebLoadManager(_webLoadThread); + + _webLoadThread->start(); + } + + _requestSent = true; + _webLoadManager->append(this, _url); + return false; +} + +int32 webFileLoader::currentOffset(bool includeSkipped) const { + return _already; +} + +void webFileLoader::onProgress(qint64 already, qint64 size) { + _size = size; + _already = already; + emit progress(this); +} + +void webFileLoader::onFinished(const QByteArray &data) { + if (_fileIsOpen) { + if (_file.write(data.constData(), data.size()) != qint64(data.size())) { + return cancel(true); + } + } else { + _data = data; + } + if (!_fname.isEmpty() && (_toCache == LoadToCacheAsWell)) { + if (!_fileIsOpen) _fileIsOpen = _file.open(QIODevice::WriteOnly); + if (!_fileIsOpen) { + return cancel(true); + } + if (_file.write(_data) != qint64(_data.size())) { + return cancel(true); + } + } + _type = mtpc_storage_filePartial; + _complete = true; + if (_fileIsOpen) { + _file.close(); + _fileIsOpen = false; + psPostprocessFile(QFileInfo(_file).absoluteFilePath()); } removeFromQueue(); - cancelRequests(); + + emit App::wnd()->imageLoaded(); + + if (_localStatus == LocalNotFound || _localStatus == LocalFailed) { + Local::writeWebFile(_url, _data); + } + emit progress(this); + loadNext(); +} + +void webFileLoader::onError() { + cancel(true); +} + +bool webFileLoader::tryLoadLocal() { + if (_localStatus == LocalNotFound || _localStatus == LocalLoaded || _localStatus == LocalFailed) { + return false; + } + if (_localStatus == LocalLoading) { + return true; + } + + _localTaskId = Local::startWebFileLoad(_url, this); + if (_localStatus != LocalNotTried) { + return _complete; + } else if (_localTaskId) { + _localStatus = LocalLoading; + return true; + } + _localStatus = LocalNotFound; + return false; +} + +void webFileLoader::cancelRequests() { + if (!webLoadManager()) return; + webLoadManager()->stop(this); +} + +webFileLoader::~webFileLoader() { +} + +class webFileLoaderPrivate { +public: + webFileLoaderPrivate(webFileLoader *loader, const QString &url) + : _interface(loader) + , _url(url) + , _already(0) + , _size(0) + , _reply(0) + , _redirectsLeft(MaxHttpRedirects) { + } + + QNetworkReply *reply() { + return _reply; + } + + QNetworkReply *request(QNetworkAccessManager &manager, const QString &redirect) { + if (!redirect.isEmpty()) _url = redirect; + + QNetworkRequest req(_url); + QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(_already) + "-"; + req.setRawHeader("Range", rangeHeaderValue); + _reply = manager.get(req); + return _reply; + } + + bool oneMoreRedirect() { + if (_redirectsLeft) { + --_redirectsLeft; + return true; + } + return false; + } + + void setData(const QByteArray &data) { + _data = data; + } + void addData(const QByteArray &data) { + _data.append(data); + } + const QByteArray &data() { + return _data; + } + void setProgress(qint64 already, qint64 size) { + _already = already; + _size = qMax(size, 0LL); + } + + qint64 size() const { + return _size; + } + qint64 already() const { + return _already; + } + +private: + webFileLoader *_interface; + QUrl _url; + qint64 _already, _size; + QNetworkReply *_reply; + int32 _redirectsLeft; + QByteArray _data; + + friend class WebLoadManager; +}; + +void reinitWebLoadManager() { + if (webLoadManager()) { + webLoadManager()->setProxySettings(App::getHttpProxySettings()); + } +} + +void stopWebLoadManager() { + if (webLoadManager()) { + _webLoadThread->quit(); + _webLoadThread->wait(); + delete _webLoadManager; + delete _webLoadMainManager; + delete _webLoadThread; + _webLoadThread = 0; + _webLoadMainManager = 0; + _webLoadManager = FinishedWebLoadManager; + } +} + +void WebLoadManager::setProxySettings(const QNetworkProxy &proxy) { + QMutexLocker lock(&_loaderPointersMutex); + _proxySettings = proxy; + emit proxyApplyDelayed(); +} + +WebLoadManager::WebLoadManager(QThread *thread) { + moveToThread(thread); + _manager.moveToThread(thread); + connect(thread, SIGNAL(started()), this, SLOT(process())); + connect(thread, SIGNAL(finished()), this, SLOT(finish())); + connect(this, SIGNAL(processDelayed()), this, SLOT(process()), Qt::QueuedConnection); + connect(this, SIGNAL(proxyApplyDelayed()), this, SLOT(proxyApply()), Qt::QueuedConnection); + + connect(this, SIGNAL(progress(webFileLoader*,qint64,qint64)), _webLoadMainManager, SLOT(progress(webFileLoader*,qint64,qint64))); + connect(this, SIGNAL(finished(webFileLoader*,QByteArray)), _webLoadMainManager, SLOT(finished(webFileLoader*,QByteArray))); + connect(this, SIGNAL(error(webFileLoader*)), _webLoadMainManager, SLOT(error(webFileLoader*))); + + connect(&_manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), this, SLOT(onFailed(QNetworkReply*))); + connect(&_manager, SIGNAL(sslErrors(QNetworkReply*,const QList&)), this, SLOT(onFailed(QNetworkReply*))); +} + +void WebLoadManager::append(webFileLoader *loader, const QString &url) { + loader->_private = new webFileLoaderPrivate(loader, url); + + QMutexLocker lock(&_loaderPointersMutex); + _loaderPointers.insert(loader, loader->_private); + emit processDelayed(); +} + +void WebLoadManager::stop(webFileLoader *loader) { + QMutexLocker lock(&_loaderPointersMutex); + _loaderPointers.remove(loader); + emit processDelayed(); +} + +bool WebLoadManager::carries(webFileLoader *loader) const { + QMutexLocker lock(&_loaderPointersMutex); + return _loaderPointers.contains(loader); +} + +bool WebLoadManager::handleReplyResult(webFileLoaderPrivate *loader, WebReplyProcessResult result) { + QMutexLocker lock(&_loaderPointersMutex); + LoaderPointers::iterator it = _loaderPointers.find(loader->_interface); + if (it != _loaderPointers.cend() && it.key()->_private != loader) { + it = _loaderPointers.end(); // it is a new loader which was realloced in the same address + } + if (it == _loaderPointers.cend()) { + return false; + } + + if (result == WebReplyProcessProgress) { + if (loader->size() > AnimationInMemory) { + LOG(("API Error: too large file is loaded to cache: %1").arg(loader->size())); + result = WebReplyProcessError; + } + } + if (result == WebReplyProcessError) { + if (it != _loaderPointers.cend()) { + emit error(it.key()); + } + return false; + } + if (loader->already() < loader->size() || !loader->size()) { + emit progress(it.key(), loader->already(), loader->size()); + return true; + } + emit finished(it.key(), loader->data()); + return false; +} + +void WebLoadManager::onFailed(QNetworkReply::NetworkError error) { + onFailed(qobject_cast(QObject::sender())); +} + +void WebLoadManager::onFailed(QNetworkReply *reply) { + if (!reply) return; + reply->deleteLater(); + + Replies::iterator j = _replies.find(reply); + if (j == _replies.cend()) { // handled already + return; + } + webFileLoaderPrivate *loader = j.value(); + _replies.erase(j); + + LOG(("Network Error: Failed to request '%1', error %2 (%3)").arg(QString::fromLatin1(loader->_url.toEncoded())).arg(int(reply->error())).arg(reply->errorString())); + + if (!handleReplyResult(loader, WebReplyProcessError)) { + _loaders.remove(loader); + delete loader; + } +} + +void WebLoadManager::onProgress(qint64 already, qint64 size) { + QNetworkReply *reply = qobject_cast(QObject::sender()); + if (!reply) return; + + Replies::iterator j = _replies.find(reply); + if (j == _replies.cend()) { // handled already + return; + } + webFileLoaderPrivate *loader = j.value(); + + WebReplyProcessResult result = WebReplyProcessProgress; + QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); + int32 status = statusCode.isValid() ? statusCode.toInt() : 200; + if (status != 200 && status != 206 && status != 416) { + if (status == 301 || status == 302) { + QString loc = reply->header(QNetworkRequest::LocationHeader).toString(); + if (!loc.isEmpty()) { + if (loader->oneMoreRedirect()) { + sendRequest(loader, loc); + return; + } else { + LOG(("Network Error: Too many HTTP redirects in onFinished() for web file loader: %1").arg(loc)); + result = WebReplyProcessError; + } + } + } else { + LOG(("Network Error: Bad HTTP status received in WebLoadManager::onProgress(): %1").arg(statusCode.toInt())); + result = WebReplyProcessError; + } + } else { + loader->setProgress(already, size); + QByteArray r = reply->readAll(); + if (!r.isEmpty()) { + loader->addData(r); + } + if (size == 0) { + LOG(("Network Error: Zero size received for HTTP download progress in WebLoadManager::onProgress(): %1 / %2").arg(already).arg(size)); + result = WebReplyProcessError; + } + } + if (!handleReplyResult(loader, result)) { + _replies.erase(j); + _loaders.remove(loader); + delete loader; + + reply->abort(); + reply->deleteLater(); + } +} + +void WebLoadManager::onMeta() { + QNetworkReply *reply = qobject_cast(QObject::sender()); + if (!reply) return; + + Replies::iterator j = _replies.find(reply); + if (j == _replies.cend()) { // handled already + return; + } + webFileLoaderPrivate *loader = j.value(); + + typedef QList Pairs; + Pairs pairs = reply->rawHeaderPairs(); + for (Pairs::iterator i = pairs.begin(), e = pairs.end(); i != e; ++i) { + if (QString::fromUtf8(i->first).toLower() == "content-range") { + QRegularExpressionMatch m = QRegularExpression(qsl("/(\\d+)([^\\d]|$)")).match(QString::fromUtf8(i->second)); + if (m.hasMatch()) { + loader->setProgress(qMax(qint64(loader->data().size()), loader->already()), m.captured(1).toLongLong()); + if (!handleReplyResult(loader, WebReplyProcessProgress)) { + _replies.erase(j); + _loaders.remove(loader); + delete loader; + + reply->abort(); + reply->deleteLater(); + } + } + } + } +} + +void WebLoadManager::process() { + Loaders newLoaders; + { + QMutexLocker lock(&_loaderPointersMutex); + for (LoaderPointers::iterator i = _loaderPointers.begin(), e = _loaderPointers.end(); i != e; ++i) { + Loaders::iterator it = _loaders.find(i.value()); + if (i.value()) { + if (it == _loaders.cend()) { + _loaders.insert(i.value()); + newLoaders.insert(i.value()); + } + i.value() = 0; + } + } + for (Loaders::iterator i = _loaders.begin(), e = _loaders.end(); i != e;) { + LoaderPointers::iterator it = _loaderPointers.find(i.key()->_interface); + if (it != _loaderPointers.cend() && it.key()->_private != i.key()) { + it = _loaderPointers.end(); + } + if (it == _loaderPointers.cend()) { + if (QNetworkReply *reply = i.key()->reply()) { + _replies.remove(reply); + reply->abort(); + reply->deleteLater(); + } + delete i.key(); + i = _loaders.erase(i); + } else { + ++i; + } + } + } + for (Loaders::const_iterator i = newLoaders.cbegin(), e = newLoaders.cend(); i != e; ++i) { + if (_loaders.contains(i.key())) { + sendRequest(i.key()); + } + } +} + +void WebLoadManager::sendRequest(webFileLoaderPrivate *loader, const QString &redirect) { + Replies::iterator j = _replies.find(loader->reply()); + if (j != _replies.cend()) { + QNetworkReply *r = j.key(); + _replies.erase(j); + + r->abort(); + r->deleteLater(); + } + + QNetworkReply *r = loader->request(_manager, redirect); + connect(r, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(onProgress(qint64, qint64))); + connect(r, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onFailed(QNetworkReply::NetworkError))); + connect(r, SIGNAL(metaDataChanged()), this, SLOT(onMeta())); + _replies.insert(r, loader); +} + +void WebLoadManager::proxyApply() { + QMutexLocker lock(&_loaderPointersMutex); + _manager.setProxy(_proxySettings); +} + +void WebLoadManager::finish() { + clear(); +} + +void WebLoadManager::clear() { + QMutexLocker lock(&_loaderPointersMutex); + for (LoaderPointers::iterator i = _loaderPointers.begin(), e = _loaderPointers.end(); i != e; ++i) { + if (i.value()) { + i.key()->_private = 0; + } + } + _loaderPointers.clear(); + + for (Loaders::iterator i = _loaders.begin(), e = _loaders.end(); i != e; ++i) { + delete i.key(); + } + _loaders.clear(); + + for (Replies::iterator i = _replies.begin(), e = _replies.end(); i != e; ++i) { + delete i.key(); + } + _replies.clear(); +} + +WebLoadManager::~WebLoadManager() { + clear(); +} + +void WebLoadMainManager::progress(webFileLoader *loader, qint64 already, qint64 size) { + if (webLoadManager() && webLoadManager()->carries(loader)) { + loader->onProgress(already, size); + } +} + +void WebLoadMainManager::finished(webFileLoader *loader, QByteArray data) { + if (webLoadManager() && webLoadManager()->carries(loader)) { + loader->onFinished(data); + } +} + +void WebLoadMainManager::error(webFileLoader *loader) { + if (webLoadManager() && webLoadManager()->carries(loader)) { + loader->onError(); + } } namespace MTP { void clearLoaderPriorities() { - ++_priority; + ++GlobalPriority; } } diff --git a/Telegram/SourceFiles/mtproto/mtpFileLoader.h b/Telegram/SourceFiles/mtproto/mtpFileLoader.h index 4cbed0a84e..d2d55d2ab0 100644 --- a/Telegram/SourceFiles/mtproto/mtpFileLoader.h +++ b/Telegram/SourceFiles/mtproto/mtpFileLoader.h @@ -108,99 +108,298 @@ enum LocalLoadStatus { typedef void *TaskId; // no interface, just id -struct mtpFileLoaderQueue; -class mtpFileLoader : public QObject, public RPCSender { +enum LoadFromCloudSetting { + LoadFromCloudOrLocal, + LoadFromLocalOnly, +}; +enum LoadToCacheSetting { + LoadToFileOnly, + LoadToCacheAsWell, +}; + +class mtpFileLoader; +class webFileLoader; + +struct FileLoaderQueue; +class FileLoader : public QObject { Q_OBJECT public: - mtpFileLoader(int32 dc, const uint64 &volume, int32 local, const uint64 &secret, int32 size = 0); - mtpFileLoader(int32 dc, const uint64 &id, const uint64 &access, LocationType type, const QString &to, int32 size, bool todata = false); + FileLoader(const QString &toFile, int32 size, LocationType locationType, LoadToCacheSetting, LoadFromCloudSetting fromCloud, bool autoLoading); bool done() const { - return complete; + return _complete; } mtpTypeId fileType() const { - return type; + return _type; } const QByteArray &bytes() const { - return data; + return _data; } QByteArray imageFormat() const; QPixmap imagePixmap() const; QString fileName() const { - return fname; + return _fname; } float64 currentProgress() const; - int32 currentOffset(bool includeSkipped = false) const; + virtual int32 currentOffset(bool includeSkipped = false) const = 0; int32 fullSize() const; - void setFileName(const QString &filename); // set filename for duplicateInData loader + bool setFileName(const QString &filename); // set filename for loaders to cache + void permitLoadFromCloud(); void pause(); void start(bool loadFirst = false, bool prior = true); void cancel(); - bool loading() const; - uint64 objId() const; + bool loading() const { + return _inQueue; + } + bool paused() const { + return _paused; + } + bool started() const { + return _inQueue || _paused; + } + bool loadingLocal() const { + return (_localStatus == LocalLoading); + } + bool autoLoading() const { + return _autoLoading; + } - ~mtpFileLoader(); + virtual mtpFileLoader *mtpLoader() { + return 0; + } + virtual const mtpFileLoader *mtpLoader() const { + return 0; + } + virtual webFileLoader *webLoader() { + return 0; + } + virtual const webFileLoader *webLoader() const { + return 0; + } + virtual void stop() { + } + virtual ~FileLoader(); void localLoaded(const StorageImageSaved &result, const QByteArray &imageFormat = QByteArray(), const QPixmap &imagePixmap = QPixmap()); - mtpFileLoader *prev, *next; - int32 priority; - signals: - void progress(mtpFileLoader *loader); - void failed(mtpFileLoader *loader, bool started); + void progress(FileLoader *loader); + void failed(FileLoader *loader, bool started); -private: +protected: - mtpFileLoaderQueue *queue; - bool inQueue, complete; - LocalLoadStatus _localStatus; + FileLoader *_prev, *_next; + int32 _priority; + FileLoaderQueue *_queue; - bool tryLoadLocal(); - void cancelRequests(); + bool _paused, _autoLoading, _inQueue, _complete; + mutable LocalLoadStatus _localStatus; - typedef QMap Requests; - Requests requests; - int32 skippedBytes; - int32 nextRequestOffset; - bool lastComplete; + virtual bool tryLoadLocal() = 0; + virtual void cancelRequests() = 0; - void started(bool loadFirst, bool prior); + void startLoading(bool loadFirst, bool prior); void removeFromQueue(); + void cancel(bool failed); void loadNext(); - void finishFail(); - bool loadPart(); - void partLoaded(int32 offset, const MTPupload_File &result, mtpRequestId req); - bool partFailed(const RPCError &error); + virtual bool loadPart() = 0; - int32 dc; + QFile _file; + QString _fname; + bool _fileIsOpen; + + LoadToCacheSetting _toCache; + LoadFromCloudSetting _fromCloud; + + QByteArray _data; + + int32 _size; + mtpTypeId _type; LocationType _locationType; - uint64 volume; // for photo locations - int32 local; - uint64 secret; - - uint64 id; // for other locations - uint64 access; - QFile file; - QString fname; - bool fileIsOpen; - bool duplicateInData; - - QByteArray data; - - int32 size; - mtpTypeId type; - TaskId _localTaskId; mutable QByteArray _imageFormat; mutable QPixmap _imagePixmap; void readImage() const; }; + +class StorageImageLocation; +class mtpFileLoader : public FileLoader, public RPCSender { + Q_OBJECT + +public: + + mtpFileLoader(const StorageImageLocation *location, int32 size, LoadFromCloudSetting fromCloud, bool autoLoading); + mtpFileLoader(int32 dc, const uint64 &id, const uint64 &access, LocationType type, const QString &toFile, int32 size, LoadToCacheSetting toCache, LoadFromCloudSetting fromCloud, bool autoLoading); + + virtual int32 currentOffset(bool includeSkipped = false) const; + + uint64 objId() const { + return _id; + } + + virtual mtpFileLoader *mtpLoader() { + return this; + } + virtual const mtpFileLoader *mtpLoader() const { + return this; + } + + virtual void stop() { + rpcInvalidate(); + } + + ~mtpFileLoader(); + +protected: + + virtual bool tryLoadLocal(); + virtual void cancelRequests(); + + typedef QMap Requests; + Requests _requests; + + virtual bool loadPart(); + void partLoaded(int32 offset, const MTPupload_File &result, mtpRequestId req); + bool partFailed(const RPCError &error); + + bool _lastComplete; + int32 _skippedBytes; + int32 _nextRequestOffset; + + int32 _dc; + const StorageImageLocation *_location; + + uint64 _id; // for other locations + uint64 _access; + +}; + +class webFileLoaderPrivate; + +class webFileLoader : public FileLoader { + Q_OBJECT + +public: + + webFileLoader(const QString &url, const QString &to, LoadFromCloudSetting fromCloud, bool autoLoading); + + virtual int32 currentOffset(bool includeSkipped = false) const; + virtual webFileLoader *webLoader() { + return this; + } + virtual const webFileLoader *webLoader() const { + return this; + } + + void onProgress(qint64 already, qint64 size); + void onFinished(const QByteArray &data); + void onError(); + + virtual void stop() { + cancelRequests(); + } + + ~webFileLoader(); + +protected: + + virtual void cancelRequests(); + virtual bool tryLoadLocal(); + virtual bool loadPart(); + + QString _url; + + bool _requestSent; + int32 _already; + + friend class WebLoadManager; + webFileLoaderPrivate *_private; + +}; + +enum WebReplyProcessResult { + WebReplyProcessError, + WebReplyProcessProgress, + WebReplyProcessFinished, +}; + +class WebLoadManager : public QObject { + Q_OBJECT + +public: + + WebLoadManager(QThread *thread); + + void setProxySettings(const QNetworkProxy &proxy); + + void append(webFileLoader *loader, const QString &url); + void stop(webFileLoader *reader); + bool carries(webFileLoader *reader) const; + + ~WebLoadManager(); + +signals: + void processDelayed(); + void proxyApplyDelayed(); + + void progress(webFileLoader *loader, qint64 already, qint64 size); + void finished(webFileLoader *loader, QByteArray data); + void error(webFileLoader *loader); + +public slots: + void onFailed(QNetworkReply *reply); + void onFailed(QNetworkReply::NetworkError error); + void onProgress(qint64 already, qint64 size); + void onMeta(); + + void process(); + void proxyApply(); + void finish(); + +private: + void clear(); + void sendRequest(webFileLoaderPrivate *loader, const QString &redirect = QString()); + bool handleReplyResult(webFileLoaderPrivate *loader, WebReplyProcessResult result); + + QNetworkProxy _proxySettings; + QNetworkAccessManager _manager; + typedef QMap LoaderPointers; + LoaderPointers _loaderPointers; + mutable QMutex _loaderPointersMutex; + + typedef OrderedSet Loaders; + Loaders _loaders; + + typedef QMap Replies; + Replies _replies; + +}; + +class WebLoadMainManager : public QObject { + Q_OBJECT + +public: + +public slots: + + void progress(webFileLoader *loader, qint64 already, qint64 size); + void finished(webFileLoader *loader, QByteArray data); + void error(webFileLoader *loader); + +}; + +static FileLoader * const CancelledFileLoader = SharedMemoryLocation(); +static mtpFileLoader * const CancelledMtpFileLoader = static_cast(CancelledFileLoader); +static webFileLoader * const CancelledWebFileLoader = static_cast(CancelledFileLoader); +static WebLoadManager * const FinishedWebLoadManager = SharedMemoryLocation(); + +void reinitWebLoadManager(); +void stopWebLoadManager(); diff --git a/Telegram/SourceFiles/mtproto/mtpScheme.cpp b/Telegram/SourceFiles/mtproto/mtpScheme.cpp index 627f9d69ca..6693765ba6 100644 --- a/Telegram/SourceFiles/mtproto/mtpScheme.cpp +++ b/Telegram/SourceFiles/mtproto/mtpScheme.cpp @@ -734,6 +734,7 @@ void _serialize_inputMediaUploadedDocument(MTPStringLogger &to, int32 stage, int case 0: to.add(" file: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 1: to.add(" mime_type: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 2: to.add(" attributes: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" caption: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -750,6 +751,7 @@ void _serialize_inputMediaUploadedThumbDocument(MTPStringLogger &to, int32 stage case 1: to.add(" thumb: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 2: to.add(" mime_type: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 3: to.add(" attributes: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 4: to.add(" caption: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -763,6 +765,7 @@ void _serialize_inputMediaDocument(MTPStringLogger &to, int32 stage, int32 lev, } switch (stage) { case 0: to.add(" id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" caption: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -784,6 +787,20 @@ void _serialize_inputMediaVenue(MTPStringLogger &to, int32 stage, int32 lev, Typ } } +void _serialize_inputMediaGifExternal(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ inputMediaGifExternal"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" url: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" q: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_inputChatPhotoEmpty(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { to.add("{ inputChatPhotoEmpty }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); } @@ -1116,15 +1133,18 @@ void _serialize_user(MTPStringLogger &to, int32 stage, int32 lev, Types &types, case 6: to.add(" bot_chat_history: "); ++stages.back(); if (flag & MTPDuser::flag_bot_chat_history) { to.add("YES [ BY BIT 15 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 15 IN FIELD flags ]"); } break; case 7: to.add(" bot_nochats: "); ++stages.back(); if (flag & MTPDuser::flag_bot_nochats) { to.add("YES [ BY BIT 16 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 16 IN FIELD flags ]"); } break; case 8: to.add(" verified: "); ++stages.back(); if (flag & MTPDuser::flag_verified) { to.add("YES [ BY BIT 17 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 17 IN FIELD flags ]"); } break; - case 9: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 10: to.add(" access_hash: "); ++stages.back(); if (flag & MTPDuser::flag_access_hash) { types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; - case 11: to.add(" first_name: "); ++stages.back(); if (flag & MTPDuser::flag_first_name) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break; - case 12: to.add(" last_name: "); ++stages.back(); if (flag & MTPDuser::flag_last_name) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break; - case 13: to.add(" username: "); ++stages.back(); if (flag & MTPDuser::flag_username) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break; - case 14: to.add(" phone: "); ++stages.back(); if (flag & MTPDuser::flag_phone) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 4 IN FIELD flags ]"); } break; - case 15: to.add(" photo: "); ++stages.back(); if (flag & MTPDuser::flag_photo) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 5 IN FIELD flags ]"); } break; - case 16: to.add(" status: "); ++stages.back(); if (flag & MTPDuser::flag_status) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 6 IN FIELD flags ]"); } break; - case 17: to.add(" bot_info_version: "); ++stages.back(); if (flag & MTPDuser::flag_bot_info_version) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 14 IN FIELD flags ]"); } break; + case 9: to.add(" restricted: "); ++stages.back(); if (flag & MTPDuser::flag_restricted) { to.add("YES [ BY BIT 18 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 18 IN FIELD flags ]"); } break; + case 10: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 11: to.add(" access_hash: "); ++stages.back(); if (flag & MTPDuser::flag_access_hash) { types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 12: to.add(" first_name: "); ++stages.back(); if (flag & MTPDuser::flag_first_name) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break; + case 13: to.add(" last_name: "); ++stages.back(); if (flag & MTPDuser::flag_last_name) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break; + case 14: to.add(" username: "); ++stages.back(); if (flag & MTPDuser::flag_username) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break; + case 15: to.add(" phone: "); ++stages.back(); if (flag & MTPDuser::flag_phone) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 4 IN FIELD flags ]"); } break; + case 16: to.add(" photo: "); ++stages.back(); if (flag & MTPDuser::flag_photo) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 5 IN FIELD flags ]"); } break; + case 17: to.add(" status: "); ++stages.back(); if (flag & MTPDuser::flag_status) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 6 IN FIELD flags ]"); } break; + case 18: to.add(" bot_info_version: "); ++stages.back(); if (flag & MTPDuser::flag_bot_info_version) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 14 IN FIELD flags ]"); } break; + case 19: to.add(" restriction_reason: "); ++stages.back(); if (flag & MTPDuser::flag_restriction_reason) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 18 IN FIELD flags ]"); } break; + case 20: to.add(" bot_inline_placeholder: "); ++stages.back(); if (flag & MTPDuser::flag_bot_inline_placeholder) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 19 IN FIELD flags ]"); } break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -1260,13 +1280,15 @@ void _serialize_channel(MTPStringLogger &to, int32 stage, int32 lev, Types &type case 6: to.add(" broadcast: "); ++stages.back(); if (flag & MTPDchannel::flag_broadcast) { to.add("YES [ BY BIT 5 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 5 IN FIELD flags ]"); } break; case 7: to.add(" verified: "); ++stages.back(); if (flag & MTPDchannel::flag_verified) { to.add("YES [ BY BIT 7 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 7 IN FIELD flags ]"); } break; case 8: to.add(" megagroup: "); ++stages.back(); if (flag & MTPDchannel::flag_megagroup) { to.add("YES [ BY BIT 8 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 8 IN FIELD flags ]"); } break; - case 9: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 10: to.add(" access_hash: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 11: to.add(" title: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 12: to.add(" username: "); ++stages.back(); if (flag & MTPDchannel::flag_username) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 6 IN FIELD flags ]"); } break; - case 13: to.add(" photo: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 14: to.add(" date: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 15: to.add(" version: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 9: to.add(" restricted: "); ++stages.back(); if (flag & MTPDchannel::flag_restricted) { to.add("YES [ BY BIT 9 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 9 IN FIELD flags ]"); } break; + case 10: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 11: to.add(" access_hash: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 12: to.add(" title: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 13: to.add(" username: "); ++stages.back(); if (flag & MTPDchannel::flag_username) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 6 IN FIELD flags ]"); } break; + case 14: to.add(" photo: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 15: to.add(" date: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 16: to.add(" version: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 17: to.add(" restriction_reason: "); ++stages.back(); if (flag & MTPDchannel::flag_restriction_reason) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 9 IN FIELD flags ]"); } break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -1454,13 +1476,14 @@ void _serialize_message(MTPStringLogger &to, int32 stage, int32 lev, Types &type case 7: to.add(" to_id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 8: to.add(" fwd_from_id: "); ++stages.back(); if (flag & MTPDmessage::flag_fwd_from_id) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break; case 9: to.add(" fwd_date: "); ++stages.back(); if (flag & MTPDmessage::flag_fwd_date) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break; - case 10: to.add(" reply_to_msg_id: "); ++stages.back(); if (flag & MTPDmessage::flag_reply_to_msg_id) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break; - case 11: to.add(" date: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 12: to.add(" message: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 13: to.add(" media: "); ++stages.back(); if (flag & MTPDmessage::flag_media) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 9 IN FIELD flags ]"); } break; - case 14: to.add(" reply_markup: "); ++stages.back(); if (flag & MTPDmessage::flag_reply_markup) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 6 IN FIELD flags ]"); } break; - case 15: to.add(" entities: "); ++stages.back(); if (flag & MTPDmessage::flag_entities) { types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 7 IN FIELD flags ]"); } break; - case 16: to.add(" views: "); ++stages.back(); if (flag & MTPDmessage::flag_views) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 10 IN FIELD flags ]"); } break; + case 10: to.add(" via_bot_id: "); ++stages.back(); if (flag & MTPDmessage::flag_via_bot_id) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 11 IN FIELD flags ]"); } break; + case 11: to.add(" reply_to_msg_id: "); ++stages.back(); if (flag & MTPDmessage::flag_reply_to_msg_id) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break; + case 12: to.add(" date: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 13: to.add(" message: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 14: to.add(" media: "); ++stages.back(); if (flag & MTPDmessage::flag_media) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 9 IN FIELD flags ]"); } break; + case 15: to.add(" reply_markup: "); ++stages.back(); if (flag & MTPDmessage::flag_reply_markup) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 6 IN FIELD flags ]"); } break; + case 16: to.add(" entities: "); ++stages.back(); if (flag & MTPDmessage::flag_entities) { types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 7 IN FIELD flags ]"); } break; + case 17: to.add(" views: "); ++stages.back(); if (flag & MTPDmessage::flag_views) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 10 IN FIELD flags ]"); } break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -1561,6 +1584,7 @@ void _serialize_messageMediaDocument(MTPStringLogger &to, int32 stage, int32 lev } switch (stage) { case 0: to.add(" document: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" caption: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -2448,6 +2472,10 @@ void _serialize_inputMessagesFilterUrl(MTPStringLogger &to, int32 stage, int32 l to.add("{ inputMessagesFilterUrl }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); } +void _serialize_inputMessagesFilterGif(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + to.add("{ inputMessagesFilterGif }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); +} + void _serialize_updateNewMessage(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -3021,6 +3049,26 @@ void _serialize_updateStickerSets(MTPStringLogger &to, int32 stage, int32 lev, T to.add("{ updateStickerSets }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); } +void _serialize_updateSavedGifs(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + to.add("{ updateSavedGifs }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); +} + +void _serialize_updateBotInlineQuery(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ updateBotInlineQuery"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" query_id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" user_id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" query: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" offset: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_updates_state(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -3113,8 +3161,9 @@ void _serialize_updateShortMessage(MTPStringLogger &to, int32 stage, int32 lev, case 10: to.add(" date: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 11: to.add(" fwd_from_id: "); ++stages.back(); if (flag & MTPDupdateShortMessage::flag_fwd_from_id) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break; case 12: to.add(" fwd_date: "); ++stages.back(); if (flag & MTPDupdateShortMessage::flag_fwd_date) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break; - case 13: to.add(" reply_to_msg_id: "); ++stages.back(); if (flag & MTPDupdateShortMessage::flag_reply_to_msg_id) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break; - case 14: to.add(" entities: "); ++stages.back(); if (flag & MTPDupdateShortMessage::flag_entities) { types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 7 IN FIELD flags ]"); } break; + case 13: to.add(" via_bot_id: "); ++stages.back(); if (flag & MTPDupdateShortMessage::flag_via_bot_id) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 11 IN FIELD flags ]"); } break; + case 14: to.add(" reply_to_msg_id: "); ++stages.back(); if (flag & MTPDupdateShortMessage::flag_reply_to_msg_id) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break; + case 15: to.add(" entities: "); ++stages.back(); if (flag & MTPDupdateShortMessage::flag_entities) { types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 7 IN FIELD flags ]"); } break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -3141,8 +3190,9 @@ void _serialize_updateShortChatMessage(MTPStringLogger &to, int32 stage, int32 l case 11: to.add(" date: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 12: to.add(" fwd_from_id: "); ++stages.back(); if (flag & MTPDupdateShortChatMessage::flag_fwd_from_id) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break; case 13: to.add(" fwd_date: "); ++stages.back(); if (flag & MTPDupdateShortChatMessage::flag_fwd_date) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break; - case 14: to.add(" reply_to_msg_id: "); ++stages.back(); if (flag & MTPDupdateShortChatMessage::flag_reply_to_msg_id) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break; - case 15: to.add(" entities: "); ++stages.back(); if (flag & MTPDupdateShortChatMessage::flag_entities) { types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 7 IN FIELD flags ]"); } break; + case 14: to.add(" via_bot_id: "); ++stages.back(); if (flag & MTPDupdateShortChatMessage::flag_via_bot_id) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 11 IN FIELD flags ]"); } break; + case 15: to.add(" reply_to_msg_id: "); ++stages.back(); if (flag & MTPDupdateShortChatMessage::flag_reply_to_msg_id) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break; + case 16: to.add(" entities: "); ++stages.back(); if (flag & MTPDupdateShortChatMessage::flag_entities) { types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 7 IN FIELD flags ]"); } break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -3318,7 +3368,8 @@ void _serialize_config(MTPStringLogger &to, int32 stage, int32 lev, Types &types case 14: to.add(" chat_big_size: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 15: to.add(" push_chat_period_ms: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 16: to.add(" push_chat_limit: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 17: to.add(" disabled_features: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 17: to.add(" saved_gifs_limit: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 18: to.add(" disabled_features: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -5038,6 +5089,228 @@ void _serialize_help_termsOfService(MTPStringLogger &to, int32 stage, int32 lev, } } +void _serialize_foundGif(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ foundGif"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" url: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" thumb_url: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" content_url: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" content_type: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 4: to.add(" w: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 5: to.add(" h: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_foundGifCached(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ foundGifCached"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" url: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" photo: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" document: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_messages_foundGifs(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_foundGifs"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" next_offset: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" results: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_messages_savedGifsNotModified(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + to.add("{ messages_savedGifsNotModified }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); +} + +void _serialize_messages_savedGifs(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_savedGifs"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" hash: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" gifs: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_inputBotInlineMessageMediaAuto(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ inputBotInlineMessageMediaAuto"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" caption: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_inputBotInlineMessageText(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ inputBotInlineMessageText"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" no_webpage: "); ++stages.back(); if (flag & MTPDinputBotInlineMessageText::flag_no_webpage) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 2: to.add(" message: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" entities: "); ++stages.back(); if (flag & MTPDinputBotInlineMessageText::flag_entities) { types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_inputBotInlineResult(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ inputBotInlineResult"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" id: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" type: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" title: "); ++stages.back(); if (flag & MTPDinputBotInlineResult::flag_title) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break; + case 4: to.add(" description: "); ++stages.back(); if (flag & MTPDinputBotInlineResult::flag_description) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break; + case 5: to.add(" url: "); ++stages.back(); if (flag & MTPDinputBotInlineResult::flag_url) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break; + case 6: to.add(" thumb_url: "); ++stages.back(); if (flag & MTPDinputBotInlineResult::flag_thumb_url) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 4 IN FIELD flags ]"); } break; + case 7: to.add(" content_url: "); ++stages.back(); if (flag & MTPDinputBotInlineResult::flag_content_url) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 5 IN FIELD flags ]"); } break; + case 8: to.add(" content_type: "); ++stages.back(); if (flag & MTPDinputBotInlineResult::flag_content_type) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 5 IN FIELD flags ]"); } break; + case 9: to.add(" w: "); ++stages.back(); if (flag & MTPDinputBotInlineResult::flag_w) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 6 IN FIELD flags ]"); } break; + case 10: to.add(" h: "); ++stages.back(); if (flag & MTPDinputBotInlineResult::flag_h) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 6 IN FIELD flags ]"); } break; + case 11: to.add(" duration: "); ++stages.back(); if (flag & MTPDinputBotInlineResult::flag_duration) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 7 IN FIELD flags ]"); } break; + case 12: to.add(" send_message: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_botInlineMessageMediaAuto(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ botInlineMessageMediaAuto"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" caption: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_botInlineMessageText(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ botInlineMessageText"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" no_webpage: "); ++stages.back(); if (flag & MTPDbotInlineMessageText::flag_no_webpage) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 2: to.add(" message: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" entities: "); ++stages.back(); if (flag & MTPDbotInlineMessageText::flag_entities) { types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_botInlineMediaResultDocument(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ botInlineMediaResultDocument"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" type: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" document: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" send_message: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_botInlineMediaResultPhoto(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ botInlineMediaResultPhoto"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" type: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" photo: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" send_message: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_botInlineResult(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ botInlineResult"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" id: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" type: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" title: "); ++stages.back(); if (flag & MTPDbotInlineResult::flag_title) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break; + case 4: to.add(" description: "); ++stages.back(); if (flag & MTPDbotInlineResult::flag_description) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break; + case 5: to.add(" url: "); ++stages.back(); if (flag & MTPDbotInlineResult::flag_url) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break; + case 6: to.add(" thumb_url: "); ++stages.back(); if (flag & MTPDbotInlineResult::flag_thumb_url) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 4 IN FIELD flags ]"); } break; + case 7: to.add(" content_url: "); ++stages.back(); if (flag & MTPDbotInlineResult::flag_content_url) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 5 IN FIELD flags ]"); } break; + case 8: to.add(" content_type: "); ++stages.back(); if (flag & MTPDbotInlineResult::flag_content_type) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 5 IN FIELD flags ]"); } break; + case 9: to.add(" w: "); ++stages.back(); if (flag & MTPDbotInlineResult::flag_w) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 6 IN FIELD flags ]"); } break; + case 10: to.add(" h: "); ++stages.back(); if (flag & MTPDbotInlineResult::flag_h) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 6 IN FIELD flags ]"); } break; + case 11: to.add(" duration: "); ++stages.back(); if (flag & MTPDbotInlineResult::flag_duration) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 7 IN FIELD flags ]"); } break; + case 12: to.add(" send_message: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_messages_botResults(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_botResults"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" gallery: "); ++stages.back(); if (flag & MTPDmessages_botResults::flag_gallery) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 2: to.add(" query_id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" next_offset: "); ++stages.back(); if (flag & MTPDmessages_botResults::flag_next_offset) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break; + case 4: to.add(" results: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_req_pq(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -5552,6 +5825,39 @@ void _serialize_messages_reorderStickerSets(MTPStringLogger &to, int32 stage, in } } +void _serialize_messages_saveGif(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_saveGif"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" unsave: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_messages_setInlineBotResults(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_setInlineBotResults"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" gallery: "); ++stages.back(); if (flag & MTPmessages_setInlineBotResults::flag_gallery) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 2: to.add(" private: "); ++stages.back(); if (flag & MTPmessages_setInlineBotResults::flag_private) { to.add("YES [ BY BIT 1 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break; + case 3: to.add(" query_id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 4: to.add(" results: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 5: to.add(" cache_time: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 6: to.add(" next_offset: "); ++stages.back(); if (flag & MTPmessages_setInlineBotResults::flag_next_offset) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_upload_saveFilePart(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -6598,6 +6904,25 @@ void _serialize_messages_migrateChat(MTPStringLogger &to, int32 stage, int32 lev } } +void _serialize_messages_sendInlineBotResult(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_sendInlineBotResult"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" broadcast: "); ++stages.back(); if (flag & MTPmessages_sendInlineBotResult::flag_broadcast) { to.add("YES [ BY BIT 4 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 4 IN FIELD flags ]"); } break; + case 2: to.add(" peer: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" reply_to_msg_id: "); ++stages.back(); if (flag & MTPmessages_sendInlineBotResult::flag_reply_to_msg_id) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 4: to.add(" random_id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 5: to.add(" query_id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 6: to.add(" id: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_channels_createChannel(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -7000,6 +7325,63 @@ void _serialize_messages_getStickerSet(MTPStringLogger &to, int32 stage, int32 l } } +void _serialize_messages_getDocumentByHash(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_getDocumentByHash"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" sha256: "); ++stages.back(); types.push_back(mtpc_bytes); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" size: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" mime_type: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_messages_searchGifs(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_searchGifs"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" q: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" offset: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_messages_getSavedGifs(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_getSavedGifs"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" hash: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_messages_getInlineBotResults(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_getInlineBotResults"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" bot: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" query: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" offset: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_updates_getState(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { to.add("{ updates_getState }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); } @@ -7304,6 +7686,7 @@ namespace { _serializers.insert(mtpc_inputMediaUploadedThumbDocument, _serialize_inputMediaUploadedThumbDocument); _serializers.insert(mtpc_inputMediaDocument, _serialize_inputMediaDocument); _serializers.insert(mtpc_inputMediaVenue, _serialize_inputMediaVenue); + _serializers.insert(mtpc_inputMediaGifExternal, _serialize_inputMediaGifExternal); _serializers.insert(mtpc_inputChatPhotoEmpty, _serialize_inputChatPhotoEmpty); _serializers.insert(mtpc_inputChatUploadedPhoto, _serialize_inputChatUploadedPhoto); _serializers.insert(mtpc_inputChatPhoto, _serialize_inputChatPhoto); @@ -7447,6 +7830,7 @@ namespace { _serializers.insert(mtpc_inputMessagesFilterAudio, _serialize_inputMessagesFilterAudio); _serializers.insert(mtpc_inputMessagesFilterAudioDocuments, _serialize_inputMessagesFilterAudioDocuments); _serializers.insert(mtpc_inputMessagesFilterUrl, _serialize_inputMessagesFilterUrl); + _serializers.insert(mtpc_inputMessagesFilterGif, _serialize_inputMessagesFilterGif); _serializers.insert(mtpc_updateNewMessage, _serialize_updateNewMessage); _serializers.insert(mtpc_updateMessageID, _serialize_updateMessageID); _serializers.insert(mtpc_updateDeleteMessages, _serialize_updateDeleteMessages); @@ -7487,6 +7871,8 @@ namespace { _serializers.insert(mtpc_updateNewStickerSet, _serialize_updateNewStickerSet); _serializers.insert(mtpc_updateStickerSetsOrder, _serialize_updateStickerSetsOrder); _serializers.insert(mtpc_updateStickerSets, _serialize_updateStickerSets); + _serializers.insert(mtpc_updateSavedGifs, _serialize_updateSavedGifs); + _serializers.insert(mtpc_updateBotInlineQuery, _serialize_updateBotInlineQuery); _serializers.insert(mtpc_updates_state, _serialize_updates_state); _serializers.insert(mtpc_updates_differenceEmpty, _serialize_updates_differenceEmpty); _serializers.insert(mtpc_updates_difference, _serialize_updates_difference); @@ -7652,6 +8038,20 @@ namespace { _serializers.insert(mtpc_channels_channelParticipants, _serialize_channels_channelParticipants); _serializers.insert(mtpc_channels_channelParticipant, _serialize_channels_channelParticipant); _serializers.insert(mtpc_help_termsOfService, _serialize_help_termsOfService); + _serializers.insert(mtpc_foundGif, _serialize_foundGif); + _serializers.insert(mtpc_foundGifCached, _serialize_foundGifCached); + _serializers.insert(mtpc_messages_foundGifs, _serialize_messages_foundGifs); + _serializers.insert(mtpc_messages_savedGifsNotModified, _serialize_messages_savedGifsNotModified); + _serializers.insert(mtpc_messages_savedGifs, _serialize_messages_savedGifs); + _serializers.insert(mtpc_inputBotInlineMessageMediaAuto, _serialize_inputBotInlineMessageMediaAuto); + _serializers.insert(mtpc_inputBotInlineMessageText, _serialize_inputBotInlineMessageText); + _serializers.insert(mtpc_inputBotInlineResult, _serialize_inputBotInlineResult); + _serializers.insert(mtpc_botInlineMessageMediaAuto, _serialize_botInlineMessageMediaAuto); + _serializers.insert(mtpc_botInlineMessageText, _serialize_botInlineMessageText); + _serializers.insert(mtpc_botInlineMediaResultDocument, _serialize_botInlineMediaResultDocument); + _serializers.insert(mtpc_botInlineMediaResultPhoto, _serialize_botInlineMediaResultPhoto); + _serializers.insert(mtpc_botInlineResult, _serialize_botInlineResult); + _serializers.insert(mtpc_messages_botResults, _serialize_messages_botResults); _serializers.insert(mtpc_req_pq, _serialize_req_pq); _serializers.insert(mtpc_req_DH_params, _serialize_req_DH_params); @@ -7692,6 +8092,8 @@ namespace { _serializers.insert(mtpc_messages_uninstallStickerSet, _serialize_messages_uninstallStickerSet); _serializers.insert(mtpc_messages_editChatAdmin, _serialize_messages_editChatAdmin); _serializers.insert(mtpc_messages_reorderStickerSets, _serialize_messages_reorderStickerSets); + _serializers.insert(mtpc_messages_saveGif, _serialize_messages_saveGif); + _serializers.insert(mtpc_messages_setInlineBotResults, _serialize_messages_setInlineBotResults); _serializers.insert(mtpc_upload_saveFilePart, _serialize_upload_saveFilePart); _serializers.insert(mtpc_upload_saveBigFilePart, _serialize_upload_saveBigFilePart); _serializers.insert(mtpc_help_saveAppLog, _serialize_help_saveAppLog); @@ -7769,6 +8171,7 @@ namespace { _serializers.insert(mtpc_messages_startBot, _serialize_messages_startBot); _serializers.insert(mtpc_messages_toggleChatAdmins, _serialize_messages_toggleChatAdmins); _serializers.insert(mtpc_messages_migrateChat, _serialize_messages_migrateChat); + _serializers.insert(mtpc_messages_sendInlineBotResult, _serialize_messages_sendInlineBotResult); _serializers.insert(mtpc_channels_createChannel, _serialize_channels_createChannel); _serializers.insert(mtpc_channels_editAdmin, _serialize_channels_editAdmin); _serializers.insert(mtpc_channels_editTitle, _serialize_channels_editTitle); @@ -7798,6 +8201,10 @@ namespace { _serializers.insert(mtpc_channels_exportInvite, _serialize_channels_exportInvite); _serializers.insert(mtpc_messages_checkChatInvite, _serialize_messages_checkChatInvite); _serializers.insert(mtpc_messages_getStickerSet, _serialize_messages_getStickerSet); + _serializers.insert(mtpc_messages_getDocumentByHash, _serialize_messages_getDocumentByHash); + _serializers.insert(mtpc_messages_searchGifs, _serialize_messages_searchGifs); + _serializers.insert(mtpc_messages_getSavedGifs, _serialize_messages_getSavedGifs); + _serializers.insert(mtpc_messages_getInlineBotResults, _serialize_messages_getInlineBotResults); _serializers.insert(mtpc_updates_getState, _serialize_updates_getState); _serializers.insert(mtpc_updates_getDifference, _serialize_updates_getDifference); _serializers.insert(mtpc_updates_getChannelDifference, _serialize_updates_getChannelDifference); diff --git a/Telegram/SourceFiles/mtproto/mtpScheme.h b/Telegram/SourceFiles/mtproto/mtpScheme.h index 6b0d1c3a1f..bb52437c28 100644 --- a/Telegram/SourceFiles/mtproto/mtpScheme.h +++ b/Telegram/SourceFiles/mtproto/mtpScheme.h @@ -92,10 +92,11 @@ enum { mtpc_inputMediaVideo = 0x936a4ebd, mtpc_inputMediaUploadedAudio = 0x4e498cab, mtpc_inputMediaAudio = 0x89938781, - mtpc_inputMediaUploadedDocument = 0xffe76b78, - mtpc_inputMediaUploadedThumbDocument = 0x41481486, - mtpc_inputMediaDocument = 0xd184e841, + mtpc_inputMediaUploadedDocument = 0x1d89306d, + mtpc_inputMediaUploadedThumbDocument = 0xad613491, + mtpc_inputMediaDocument = 0x1a77f29c, mtpc_inputMediaVenue = 0x2827a81a, + mtpc_inputMediaGifExternal = 0x4843b0fd, mtpc_inputChatPhotoEmpty = 0x1ca48f57, mtpc_inputChatUploadedPhoto = 0x94254732, mtpc_inputChatPhoto = 0xb2e1bf08, @@ -129,7 +130,7 @@ enum { mtpc_fileLocationUnavailable = 0x7c596b46, mtpc_fileLocation = 0x53d69076, mtpc_userEmpty = 0x200250ba, - mtpc_user = 0x22e49072, + mtpc_user = 0xd10d979a, mtpc_userProfilePhotoEmpty = 0x4f11bae1, mtpc_userProfilePhoto = 0xd559d8c8, mtpc_userStatusEmpty = 0x9d05049, @@ -141,7 +142,7 @@ enum { mtpc_chatEmpty = 0x9ba2d800, mtpc_chat = 0xd91cdd54, mtpc_chatForbidden = 0x7328bdb, - mtpc_channel = 0x678e9587, + mtpc_channel = 0x4b1b7506, mtpc_channelForbidden = 0x2d85832c, mtpc_chatFull = 0x2e02a614, mtpc_channelFull = 0x9e341ddf, @@ -153,7 +154,7 @@ enum { mtpc_chatPhotoEmpty = 0x37c1011c, mtpc_chatPhoto = 0x6153276a, mtpc_messageEmpty = 0x83e5de54, - mtpc_message = 0x5ba66c13, + mtpc_message = 0xc992e15c, mtpc_messageService = 0xc06b9607, mtpc_messageMediaEmpty = 0x3ded6320, mtpc_messageMediaPhoto = 0x3d8ce53d, @@ -161,7 +162,7 @@ enum { mtpc_messageMediaGeo = 0x56e0d474, mtpc_messageMediaContact = 0x5e7d2f39, mtpc_messageMediaUnsupported = 0x9f84f49e, - mtpc_messageMediaDocument = 0x2fda2204, + mtpc_messageMediaDocument = 0xf3e02ea8, mtpc_messageMediaAudio = 0xc6b68300, mtpc_messageMediaWebPage = 0xa32dd600, mtpc_messageMediaVenue = 0x7912b71f, @@ -239,6 +240,7 @@ enum { mtpc_inputMessagesFilterAudio = 0xcfc87522, mtpc_inputMessagesFilterAudioDocuments = 0x5afbf764, mtpc_inputMessagesFilterUrl = 0x7ef0dd87, + mtpc_inputMessagesFilterGif = 0xffc86587, mtpc_updateNewMessage = 0x1f2b0afd, mtpc_updateMessageID = 0x4e90bfd6, mtpc_updateDeleteMessages = 0xa20db0e5, @@ -279,13 +281,15 @@ enum { mtpc_updateNewStickerSet = 0x688a30aa, mtpc_updateStickerSetsOrder = 0xf0dfb451, mtpc_updateStickerSets = 0x43ae3dec, + mtpc_updateSavedGifs = 0x9375341e, + mtpc_updateBotInlineQuery = 0xc01eea08, mtpc_updates_state = 0xa56c2a3e, mtpc_updates_differenceEmpty = 0x5d75a138, mtpc_updates_difference = 0xf49ca0, mtpc_updates_differenceSlice = 0xa8fb1981, mtpc_updatesTooLong = 0xe317af7e, - mtpc_updateShortMessage = 0xf7d91a46, - mtpc_updateShortChatMessage = 0xcac7fdd2, + mtpc_updateShortMessage = 0x13e4deaa, + mtpc_updateShortChatMessage = 0x248afa62, mtpc_updateShort = 0x78d4dec1, mtpc_updatesCombined = 0x725b04c3, mtpc_updates = 0x74ae4240, @@ -295,7 +299,7 @@ enum { mtpc_photos_photo = 0x20212ca8, mtpc_upload_file = 0x96a18d5, mtpc_dcOption = 0x5d8c6cc, - mtpc_config = 0x6cb6e65e, + mtpc_config = 0x6bbc5f8, mtpc_nearestDc = 0x8e1a1775, mtpc_help_appUpdate = 0x8987f311, mtpc_help_noAppUpdate = 0xc45a6536, @@ -444,6 +448,20 @@ enum { mtpc_channels_channelParticipants = 0xf56ee2a8, mtpc_channels_channelParticipant = 0xd0d9b163, mtpc_help_termsOfService = 0xf1ee3e90, + mtpc_foundGif = 0x162ecc1f, + mtpc_foundGifCached = 0x9c750409, + mtpc_messages_foundGifs = 0x450a1c0a, + mtpc_messages_savedGifsNotModified = 0xe8025ca2, + mtpc_messages_savedGifs = 0x2e0709a5, + mtpc_inputBotInlineMessageMediaAuto = 0x2e43e587, + mtpc_inputBotInlineMessageText = 0xadf0df71, + mtpc_inputBotInlineResult = 0x2cbbe15a, + mtpc_botInlineMessageMediaAuto = 0xfc56e87d, + mtpc_botInlineMessageText = 0xa56197a9, + mtpc_botInlineMediaResultDocument = 0xf897d33e, + mtpc_botInlineMediaResultPhoto = 0xc5528587, + mtpc_botInlineResult = 0x9bebaeb9, + mtpc_messages_botResults = 0x1170b0a3, mtpc_invokeAfterMsg = 0xcb9f372d, mtpc_invokeAfterMsgs = 0x3dc4b4f0, mtpc_initConnection = 0x69796de9, @@ -553,6 +571,13 @@ enum { mtpc_messages_migrateChat = 0x15a3b8e3, mtpc_messages_searchGlobal = 0x9e3cacb0, mtpc_messages_reorderStickerSets = 0x9fcfbc30, + mtpc_messages_getDocumentByHash = 0x338e2464, + mtpc_messages_searchGifs = 0xbf9a776b, + mtpc_messages_getSavedGifs = 0x83bf3d52, + mtpc_messages_saveGif = 0x327a30cb, + mtpc_messages_getInlineBotResults = 0x9324600d, + mtpc_messages_setInlineBotResults = 0x3f23ec12, + mtpc_messages_sendInlineBotResult = 0xb16e06fe, mtpc_updates_getState = 0xedd4882a, mtpc_updates_getDifference = 0xa041495, mtpc_updates_getChannelDifference = 0xbb32d7c0, @@ -706,6 +731,7 @@ class MTPDinputMediaUploadedDocument; class MTPDinputMediaUploadedThumbDocument; class MTPDinputMediaDocument; class MTPDinputMediaVenue; +class MTPDinputMediaGifExternal; class MTPinputChatPhoto; class MTPDinputChatUploadedPhoto; @@ -951,6 +977,7 @@ class MTPDupdateChatAdmins; class MTPDupdateChatParticipantAdmin; class MTPDupdateNewStickerSet; class MTPDupdateStickerSetsOrder; +class MTPDupdateBotInlineQuery; class MTPupdates_state; class MTPDupdates_state; @@ -1215,6 +1242,35 @@ class MTPDchannels_channelParticipant; class MTPhelp_termsOfService; class MTPDhelp_termsOfService; +class MTPfoundGif; +class MTPDfoundGif; +class MTPDfoundGifCached; + +class MTPmessages_foundGifs; +class MTPDmessages_foundGifs; + +class MTPmessages_savedGifs; +class MTPDmessages_savedGifs; + +class MTPinputBotInlineMessage; +class MTPDinputBotInlineMessageMediaAuto; +class MTPDinputBotInlineMessageText; + +class MTPinputBotInlineResult; +class MTPDinputBotInlineResult; + +class MTPbotInlineMessage; +class MTPDbotInlineMessageMediaAuto; +class MTPDbotInlineMessageText; + +class MTPbotInlineResult; +class MTPDbotInlineMediaResultDocument; +class MTPDbotInlineMediaResultPhoto; +class MTPDbotInlineResult; + +class MTPmessages_botResults; +class MTPDmessages_botResults; + // Boxed types definitions typedef MTPBoxed MTPResPQ; @@ -1374,6 +1430,14 @@ typedef MTPBoxed MTPChannelParticipantRole; typedef MTPBoxed MTPchannels_ChannelParticipants; typedef MTPBoxed MTPchannels_ChannelParticipant; typedef MTPBoxed MTPhelp_TermsOfService; +typedef MTPBoxed MTPFoundGif; +typedef MTPBoxed MTPmessages_FoundGifs; +typedef MTPBoxed MTPmessages_SavedGifs; +typedef MTPBoxed MTPInputBotInlineMessage; +typedef MTPBoxed MTPInputBotInlineResult; +typedef MTPBoxed MTPBotInlineMessage; +typedef MTPBoxed MTPBotInlineResult; +typedef MTPBoxed MTPmessages_BotResults; // Type classes definitions @@ -2600,6 +2664,18 @@ public: return *(const MTPDinputMediaVenue*)data; } + MTPDinputMediaGifExternal &_inputMediaGifExternal() { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_inputMediaGifExternal) throw mtpErrorWrongTypeId(_type, mtpc_inputMediaGifExternal); + split(); + return *(MTPDinputMediaGifExternal*)data; + } + const MTPDinputMediaGifExternal &c_inputMediaGifExternal() const { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_inputMediaGifExternal) throw mtpErrorWrongTypeId(_type, mtpc_inputMediaGifExternal); + return *(const MTPDinputMediaGifExternal*)data; + } + uint32 innerLength() const; mtpTypeId type() const; void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons); @@ -2622,6 +2698,7 @@ private: explicit MTPinputMedia(MTPDinputMediaUploadedThumbDocument *_data); explicit MTPinputMedia(MTPDinputMediaDocument *_data); explicit MTPinputMedia(MTPDinputMediaVenue *_data); + explicit MTPinputMedia(MTPDinputMediaGifExternal *_data); friend MTPinputMedia MTP_inputMediaEmpty(); friend MTPinputMedia MTP_inputMediaUploadedPhoto(const MTPInputFile &_file, const MTPstring &_caption); @@ -2633,10 +2710,11 @@ private: friend MTPinputMedia MTP_inputMediaVideo(const MTPInputVideo &_id, const MTPstring &_caption); friend MTPinputMedia MTP_inputMediaUploadedAudio(const MTPInputFile &_file, MTPint _duration, const MTPstring &_mime_type); friend MTPinputMedia MTP_inputMediaAudio(const MTPInputAudio &_id); - friend MTPinputMedia MTP_inputMediaUploadedDocument(const MTPInputFile &_file, const MTPstring &_mime_type, const MTPVector &_attributes); - friend MTPinputMedia MTP_inputMediaUploadedThumbDocument(const MTPInputFile &_file, const MTPInputFile &_thumb, const MTPstring &_mime_type, const MTPVector &_attributes); - friend MTPinputMedia MTP_inputMediaDocument(const MTPInputDocument &_id); + friend MTPinputMedia MTP_inputMediaUploadedDocument(const MTPInputFile &_file, const MTPstring &_mime_type, const MTPVector &_attributes, const MTPstring &_caption); + friend MTPinputMedia MTP_inputMediaUploadedThumbDocument(const MTPInputFile &_file, const MTPInputFile &_thumb, const MTPstring &_mime_type, const MTPVector &_attributes, const MTPstring &_caption); + friend MTPinputMedia MTP_inputMediaDocument(const MTPInputDocument &_id, const MTPstring &_caption); friend MTPinputMedia MTP_inputMediaVenue(const MTPInputGeoPoint &_geo_point, const MTPstring &_title, const MTPstring &_address, const MTPstring &_provider, const MTPstring &_venue_id); + friend MTPinputMedia MTP_inputMediaGifExternal(const MTPstring &_url, const MTPstring &_q); mtpTypeId _type; }; @@ -3164,7 +3242,7 @@ private: explicit MTPuser(MTPDuser *_data); friend MTPuser MTP_userEmpty(MTPint _id); - friend MTPuser MTP_user(MTPint _flags, MTPint _id, const MTPlong &_access_hash, const MTPstring &_first_name, const MTPstring &_last_name, const MTPstring &_username, const MTPstring &_phone, const MTPUserProfilePhoto &_photo, const MTPUserStatus &_status, MTPint _bot_info_version); + friend MTPuser MTP_user(MTPint _flags, MTPint _id, const MTPlong &_access_hash, const MTPstring &_first_name, const MTPstring &_last_name, const MTPstring &_username, const MTPstring &_phone, const MTPUserProfilePhoto &_photo, const MTPUserStatus &_status, MTPint _bot_info_version, const MTPstring &_restriction_reason, const MTPstring &_bot_inline_placeholder); mtpTypeId _type; }; @@ -3349,7 +3427,7 @@ private: friend MTPchat MTP_chatEmpty(MTPint _id); friend MTPchat MTP_chat(MTPint _flags, MTPint _id, const MTPstring &_title, const MTPChatPhoto &_photo, MTPint _participants_count, MTPint _date, MTPint _version, const MTPInputChannel &_migrated_to); friend MTPchat MTP_chatForbidden(MTPint _id, const MTPstring &_title); - friend MTPchat MTP_channel(MTPint _flags, MTPint _id, const MTPlong &_access_hash, const MTPstring &_title, const MTPstring &_username, const MTPChatPhoto &_photo, MTPint _date, MTPint _version); + friend MTPchat MTP_channel(MTPint _flags, MTPint _id, const MTPlong &_access_hash, const MTPstring &_title, const MTPstring &_username, const MTPChatPhoto &_photo, MTPint _date, MTPint _version, const MTPstring &_restriction_reason); friend MTPchat MTP_channelForbidden(MTPint _id, const MTPlong &_access_hash, const MTPstring &_title); mtpTypeId _type; @@ -3619,7 +3697,7 @@ private: explicit MTPmessage(MTPDmessageService *_data); friend MTPmessage MTP_messageEmpty(MTPint _id); - friend MTPmessage MTP_message(MTPint _flags, MTPint _id, MTPint _from_id, const MTPPeer &_to_id, const MTPPeer &_fwd_from_id, MTPint _fwd_date, MTPint _reply_to_msg_id, MTPint _date, const MTPstring &_message, const MTPMessageMedia &_media, const MTPReplyMarkup &_reply_markup, const MTPVector &_entities, MTPint _views); + friend MTPmessage MTP_message(MTPint _flags, MTPint _id, MTPint _from_id, const MTPPeer &_to_id, const MTPPeer &_fwd_from_id, MTPint _fwd_date, MTPint _via_bot_id, MTPint _reply_to_msg_id, MTPint _date, const MTPstring &_message, const MTPMessageMedia &_media, const MTPReplyMarkup &_reply_markup, const MTPVector &_entities, MTPint _views); friend MTPmessage MTP_messageService(MTPint _flags, MTPint _id, MTPint _from_id, const MTPPeer &_to_id, MTPint _date, const MTPMessageAction &_action); mtpTypeId _type; @@ -3754,7 +3832,7 @@ private: friend MTPmessageMedia MTP_messageMediaGeo(const MTPGeoPoint &_geo); friend MTPmessageMedia MTP_messageMediaContact(const MTPstring &_phone_number, const MTPstring &_first_name, const MTPstring &_last_name, MTPint _user_id); friend MTPmessageMedia MTP_messageMediaUnsupported(); - friend MTPmessageMedia MTP_messageMediaDocument(const MTPDocument &_document); + friend MTPmessageMedia MTP_messageMediaDocument(const MTPDocument &_document, const MTPstring &_caption); friend MTPmessageMedia MTP_messageMediaAudio(const MTPAudio &_audio); friend MTPmessageMedia MTP_messageMediaWebPage(const MTPWebPage &_webpage); friend MTPmessageMedia MTP_messageMediaVenue(const MTPGeoPoint &_geo, const MTPstring &_title, const MTPstring &_address, const MTPstring &_provider, const MTPstring &_venue_id); @@ -5168,6 +5246,7 @@ private: friend MTPmessagesFilter MTP_inputMessagesFilterAudio(); friend MTPmessagesFilter MTP_inputMessagesFilterAudioDocuments(); friend MTPmessagesFilter MTP_inputMessagesFilterUrl(); + friend MTPmessagesFilter MTP_inputMessagesFilterGif(); mtpTypeId _type; }; @@ -5649,6 +5728,18 @@ public: return *(const MTPDupdateStickerSetsOrder*)data; } + MTPDupdateBotInlineQuery &_updateBotInlineQuery() { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_updateBotInlineQuery) throw mtpErrorWrongTypeId(_type, mtpc_updateBotInlineQuery); + split(); + return *(MTPDupdateBotInlineQuery*)data; + } + const MTPDupdateBotInlineQuery &c_updateBotInlineQuery() const { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_updateBotInlineQuery) throw mtpErrorWrongTypeId(_type, mtpc_updateBotInlineQuery); + return *(const MTPDupdateBotInlineQuery*)data; + } + uint32 innerLength() const; mtpTypeId type() const; void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons); @@ -5697,6 +5788,7 @@ private: explicit MTPupdate(MTPDupdateChatParticipantAdmin *_data); explicit MTPupdate(MTPDupdateNewStickerSet *_data); explicit MTPupdate(MTPDupdateStickerSetsOrder *_data); + explicit MTPupdate(MTPDupdateBotInlineQuery *_data); friend MTPupdate MTP_updateNewMessage(const MTPMessage &_message, MTPint _pts, MTPint _pts_count); friend MTPupdate MTP_updateMessageID(MTPint _id, const MTPlong &_random_id); @@ -5738,6 +5830,8 @@ private: friend MTPupdate MTP_updateNewStickerSet(const MTPmessages_StickerSet &_stickerset); friend MTPupdate MTP_updateStickerSetsOrder(const MTPVector &_order); friend MTPupdate MTP_updateStickerSets(); + friend MTPupdate MTP_updateSavedGifs(); + friend MTPupdate MTP_updateBotInlineQuery(const MTPlong &_query_id, MTPint _user_id, const MTPstring &_query, const MTPstring &_offset); mtpTypeId _type; }; @@ -5936,8 +6030,8 @@ private: explicit MTPupdates(MTPDupdateShortSentMessage *_data); friend MTPupdates MTP_updatesTooLong(); - friend MTPupdates MTP_updateShortMessage(MTPint _flags, MTPint _id, MTPint _user_id, const MTPstring &_message, MTPint _pts, MTPint _pts_count, MTPint _date, const MTPPeer &_fwd_from_id, MTPint _fwd_date, MTPint _reply_to_msg_id, const MTPVector &_entities); - friend MTPupdates MTP_updateShortChatMessage(MTPint _flags, MTPint _id, MTPint _from_id, MTPint _chat_id, const MTPstring &_message, MTPint _pts, MTPint _pts_count, MTPint _date, const MTPPeer &_fwd_from_id, MTPint _fwd_date, MTPint _reply_to_msg_id, const MTPVector &_entities); + friend MTPupdates MTP_updateShortMessage(MTPint _flags, MTPint _id, MTPint _user_id, const MTPstring &_message, MTPint _pts, MTPint _pts_count, MTPint _date, const MTPPeer &_fwd_from_id, MTPint _fwd_date, MTPint _via_bot_id, MTPint _reply_to_msg_id, const MTPVector &_entities); + friend MTPupdates MTP_updateShortChatMessage(MTPint _flags, MTPint _id, MTPint _from_id, MTPint _chat_id, const MTPstring &_message, MTPint _pts, MTPint _pts_count, MTPint _date, const MTPPeer &_fwd_from_id, MTPint _fwd_date, MTPint _via_bot_id, MTPint _reply_to_msg_id, const MTPVector &_entities); friend MTPupdates MTP_updateShort(const MTPUpdate &_update, MTPint _date); friend MTPupdates MTP_updatesCombined(const MTPVector &_updates, const MTPVector &_users, const MTPVector &_chats, MTPint _date, MTPint _seq_start, MTPint _seq); friend MTPupdates MTP_updates(const MTPVector &_updates, const MTPVector &_users, const MTPVector &_chats, MTPint _date, MTPint _seq); @@ -6118,7 +6212,7 @@ public: private: explicit MTPconfig(MTPDconfig *_data); - friend MTPconfig MTP_config(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, const MTPVector &_disabled_features); + friend MTPconfig MTP_config(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, MTPint _saved_gifs_limit, const MTPVector &_disabled_features); }; typedef MTPBoxed MTPConfig; @@ -8907,6 +9001,355 @@ private: }; typedef MTPBoxed MTPhelp_TermsOfService; +class MTPfoundGif : private mtpDataOwner { +public: + MTPfoundGif() : mtpDataOwner(0), _type(0) { + } + MTPfoundGif(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) : mtpDataOwner(0), _type(0) { + read(from, end, cons); + } + + MTPDfoundGif &_foundGif() { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_foundGif) throw mtpErrorWrongTypeId(_type, mtpc_foundGif); + split(); + return *(MTPDfoundGif*)data; + } + const MTPDfoundGif &c_foundGif() const { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_foundGif) throw mtpErrorWrongTypeId(_type, mtpc_foundGif); + return *(const MTPDfoundGif*)data; + } + + MTPDfoundGifCached &_foundGifCached() { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_foundGifCached) throw mtpErrorWrongTypeId(_type, mtpc_foundGifCached); + split(); + return *(MTPDfoundGifCached*)data; + } + const MTPDfoundGifCached &c_foundGifCached() const { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_foundGifCached) throw mtpErrorWrongTypeId(_type, mtpc_foundGifCached); + return *(const MTPDfoundGifCached*)data; + } + + uint32 innerLength() const; + mtpTypeId type() const; + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons); + void write(mtpBuffer &to) const; + + typedef void ResponseType; + +private: + explicit MTPfoundGif(mtpTypeId type); + explicit MTPfoundGif(MTPDfoundGif *_data); + explicit MTPfoundGif(MTPDfoundGifCached *_data); + + friend MTPfoundGif MTP_foundGif(const MTPstring &_url, const MTPstring &_thumb_url, const MTPstring &_content_url, const MTPstring &_content_type, MTPint _w, MTPint _h); + friend MTPfoundGif MTP_foundGifCached(const MTPstring &_url, const MTPPhoto &_photo, const MTPDocument &_document); + + mtpTypeId _type; +}; +typedef MTPBoxed MTPFoundGif; + +class MTPmessages_foundGifs : private mtpDataOwner { +public: + MTPmessages_foundGifs(); + MTPmessages_foundGifs(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_foundGifs) : mtpDataOwner(0) { + read(from, end, cons); + } + + MTPDmessages_foundGifs &_messages_foundGifs() { + if (!data) throw mtpErrorUninitialized(); + split(); + return *(MTPDmessages_foundGifs*)data; + } + const MTPDmessages_foundGifs &c_messages_foundGifs() const { + if (!data) throw mtpErrorUninitialized(); + return *(const MTPDmessages_foundGifs*)data; + } + + uint32 innerLength() const; + mtpTypeId type() const; + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_foundGifs); + void write(mtpBuffer &to) const; + + typedef void ResponseType; + +private: + explicit MTPmessages_foundGifs(MTPDmessages_foundGifs *_data); + + friend MTPmessages_foundGifs MTP_messages_foundGifs(MTPint _next_offset, const MTPVector &_results); +}; +typedef MTPBoxed MTPmessages_FoundGifs; + +class MTPmessages_savedGifs : private mtpDataOwner { +public: + MTPmessages_savedGifs() : mtpDataOwner(0), _type(0) { + } + MTPmessages_savedGifs(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) : mtpDataOwner(0), _type(0) { + read(from, end, cons); + } + + MTPDmessages_savedGifs &_messages_savedGifs() { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_messages_savedGifs) throw mtpErrorWrongTypeId(_type, mtpc_messages_savedGifs); + split(); + return *(MTPDmessages_savedGifs*)data; + } + const MTPDmessages_savedGifs &c_messages_savedGifs() const { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_messages_savedGifs) throw mtpErrorWrongTypeId(_type, mtpc_messages_savedGifs); + return *(const MTPDmessages_savedGifs*)data; + } + + uint32 innerLength() const; + mtpTypeId type() const; + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons); + void write(mtpBuffer &to) const; + + typedef void ResponseType; + +private: + explicit MTPmessages_savedGifs(mtpTypeId type); + explicit MTPmessages_savedGifs(MTPDmessages_savedGifs *_data); + + friend MTPmessages_savedGifs MTP_messages_savedGifsNotModified(); + friend MTPmessages_savedGifs MTP_messages_savedGifs(MTPint _hash, const MTPVector &_gifs); + + mtpTypeId _type; +}; +typedef MTPBoxed MTPmessages_SavedGifs; + +class MTPinputBotInlineMessage : private mtpDataOwner { +public: + MTPinputBotInlineMessage() : mtpDataOwner(0), _type(0) { + } + MTPinputBotInlineMessage(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) : mtpDataOwner(0), _type(0) { + read(from, end, cons); + } + + MTPDinputBotInlineMessageMediaAuto &_inputBotInlineMessageMediaAuto() { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_inputBotInlineMessageMediaAuto) throw mtpErrorWrongTypeId(_type, mtpc_inputBotInlineMessageMediaAuto); + split(); + return *(MTPDinputBotInlineMessageMediaAuto*)data; + } + const MTPDinputBotInlineMessageMediaAuto &c_inputBotInlineMessageMediaAuto() const { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_inputBotInlineMessageMediaAuto) throw mtpErrorWrongTypeId(_type, mtpc_inputBotInlineMessageMediaAuto); + return *(const MTPDinputBotInlineMessageMediaAuto*)data; + } + + MTPDinputBotInlineMessageText &_inputBotInlineMessageText() { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_inputBotInlineMessageText) throw mtpErrorWrongTypeId(_type, mtpc_inputBotInlineMessageText); + split(); + return *(MTPDinputBotInlineMessageText*)data; + } + const MTPDinputBotInlineMessageText &c_inputBotInlineMessageText() const { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_inputBotInlineMessageText) throw mtpErrorWrongTypeId(_type, mtpc_inputBotInlineMessageText); + return *(const MTPDinputBotInlineMessageText*)data; + } + + uint32 innerLength() const; + mtpTypeId type() const; + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons); + void write(mtpBuffer &to) const; + + typedef void ResponseType; + +private: + explicit MTPinputBotInlineMessage(mtpTypeId type); + explicit MTPinputBotInlineMessage(MTPDinputBotInlineMessageMediaAuto *_data); + explicit MTPinputBotInlineMessage(MTPDinputBotInlineMessageText *_data); + + friend MTPinputBotInlineMessage MTP_inputBotInlineMessageMediaAuto(const MTPstring &_caption); + friend MTPinputBotInlineMessage MTP_inputBotInlineMessageText(MTPint _flags, const MTPstring &_message, const MTPVector &_entities); + + mtpTypeId _type; +}; +typedef MTPBoxed MTPInputBotInlineMessage; + +class MTPinputBotInlineResult : private mtpDataOwner { +public: + MTPinputBotInlineResult(); + MTPinputBotInlineResult(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_inputBotInlineResult) : mtpDataOwner(0) { + read(from, end, cons); + } + + MTPDinputBotInlineResult &_inputBotInlineResult() { + if (!data) throw mtpErrorUninitialized(); + split(); + return *(MTPDinputBotInlineResult*)data; + } + const MTPDinputBotInlineResult &c_inputBotInlineResult() const { + if (!data) throw mtpErrorUninitialized(); + return *(const MTPDinputBotInlineResult*)data; + } + + uint32 innerLength() const; + mtpTypeId type() const; + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_inputBotInlineResult); + void write(mtpBuffer &to) const; + + typedef void ResponseType; + +private: + explicit MTPinputBotInlineResult(MTPDinputBotInlineResult *_data); + + friend MTPinputBotInlineResult MTP_inputBotInlineResult(MTPint _flags, const MTPstring &_id, const MTPstring &_type, const MTPstring &_title, const MTPstring &_description, const MTPstring &_url, const MTPstring &_thumb_url, const MTPstring &_content_url, const MTPstring &_content_type, MTPint _w, MTPint _h, MTPint _duration, const MTPInputBotInlineMessage &_send_message); +}; +typedef MTPBoxed MTPInputBotInlineResult; + +class MTPbotInlineMessage : private mtpDataOwner { +public: + MTPbotInlineMessage() : mtpDataOwner(0), _type(0) { + } + MTPbotInlineMessage(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) : mtpDataOwner(0), _type(0) { + read(from, end, cons); + } + + MTPDbotInlineMessageMediaAuto &_botInlineMessageMediaAuto() { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_botInlineMessageMediaAuto) throw mtpErrorWrongTypeId(_type, mtpc_botInlineMessageMediaAuto); + split(); + return *(MTPDbotInlineMessageMediaAuto*)data; + } + const MTPDbotInlineMessageMediaAuto &c_botInlineMessageMediaAuto() const { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_botInlineMessageMediaAuto) throw mtpErrorWrongTypeId(_type, mtpc_botInlineMessageMediaAuto); + return *(const MTPDbotInlineMessageMediaAuto*)data; + } + + MTPDbotInlineMessageText &_botInlineMessageText() { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_botInlineMessageText) throw mtpErrorWrongTypeId(_type, mtpc_botInlineMessageText); + split(); + return *(MTPDbotInlineMessageText*)data; + } + const MTPDbotInlineMessageText &c_botInlineMessageText() const { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_botInlineMessageText) throw mtpErrorWrongTypeId(_type, mtpc_botInlineMessageText); + return *(const MTPDbotInlineMessageText*)data; + } + + uint32 innerLength() const; + mtpTypeId type() const; + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons); + void write(mtpBuffer &to) const; + + typedef void ResponseType; + +private: + explicit MTPbotInlineMessage(mtpTypeId type); + explicit MTPbotInlineMessage(MTPDbotInlineMessageMediaAuto *_data); + explicit MTPbotInlineMessage(MTPDbotInlineMessageText *_data); + + friend MTPbotInlineMessage MTP_botInlineMessageMediaAuto(const MTPstring &_caption); + friend MTPbotInlineMessage MTP_botInlineMessageText(MTPint _flags, const MTPstring &_message, const MTPVector &_entities); + + mtpTypeId _type; +}; +typedef MTPBoxed MTPBotInlineMessage; + +class MTPbotInlineResult : private mtpDataOwner { +public: + MTPbotInlineResult() : mtpDataOwner(0), _type(0) { + } + MTPbotInlineResult(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) : mtpDataOwner(0), _type(0) { + read(from, end, cons); + } + + MTPDbotInlineMediaResultDocument &_botInlineMediaResultDocument() { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_botInlineMediaResultDocument) throw mtpErrorWrongTypeId(_type, mtpc_botInlineMediaResultDocument); + split(); + return *(MTPDbotInlineMediaResultDocument*)data; + } + const MTPDbotInlineMediaResultDocument &c_botInlineMediaResultDocument() const { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_botInlineMediaResultDocument) throw mtpErrorWrongTypeId(_type, mtpc_botInlineMediaResultDocument); + return *(const MTPDbotInlineMediaResultDocument*)data; + } + + MTPDbotInlineMediaResultPhoto &_botInlineMediaResultPhoto() { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_botInlineMediaResultPhoto) throw mtpErrorWrongTypeId(_type, mtpc_botInlineMediaResultPhoto); + split(); + return *(MTPDbotInlineMediaResultPhoto*)data; + } + const MTPDbotInlineMediaResultPhoto &c_botInlineMediaResultPhoto() const { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_botInlineMediaResultPhoto) throw mtpErrorWrongTypeId(_type, mtpc_botInlineMediaResultPhoto); + return *(const MTPDbotInlineMediaResultPhoto*)data; + } + + MTPDbotInlineResult &_botInlineResult() { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_botInlineResult) throw mtpErrorWrongTypeId(_type, mtpc_botInlineResult); + split(); + return *(MTPDbotInlineResult*)data; + } + const MTPDbotInlineResult &c_botInlineResult() const { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_botInlineResult) throw mtpErrorWrongTypeId(_type, mtpc_botInlineResult); + return *(const MTPDbotInlineResult*)data; + } + + uint32 innerLength() const; + mtpTypeId type() const; + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons); + void write(mtpBuffer &to) const; + + typedef void ResponseType; + +private: + explicit MTPbotInlineResult(mtpTypeId type); + explicit MTPbotInlineResult(MTPDbotInlineMediaResultDocument *_data); + explicit MTPbotInlineResult(MTPDbotInlineMediaResultPhoto *_data); + explicit MTPbotInlineResult(MTPDbotInlineResult *_data); + + friend MTPbotInlineResult MTP_botInlineMediaResultDocument(const MTPstring &_id, const MTPstring &_type, const MTPDocument &_document, const MTPBotInlineMessage &_send_message); + friend MTPbotInlineResult MTP_botInlineMediaResultPhoto(const MTPstring &_id, const MTPstring &_type, const MTPPhoto &_photo, const MTPBotInlineMessage &_send_message); + friend MTPbotInlineResult MTP_botInlineResult(MTPint _flags, const MTPstring &_id, const MTPstring &_type, const MTPstring &_title, const MTPstring &_description, const MTPstring &_url, const MTPstring &_thumb_url, const MTPstring &_content_url, const MTPstring &_content_type, MTPint _w, MTPint _h, MTPint _duration, const MTPBotInlineMessage &_send_message); + + mtpTypeId _type; +}; +typedef MTPBoxed MTPBotInlineResult; + +class MTPmessages_botResults : private mtpDataOwner { +public: + MTPmessages_botResults(); + MTPmessages_botResults(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_botResults) : mtpDataOwner(0) { + read(from, end, cons); + } + + MTPDmessages_botResults &_messages_botResults() { + if (!data) throw mtpErrorUninitialized(); + split(); + return *(MTPDmessages_botResults*)data; + } + const MTPDmessages_botResults &c_messages_botResults() const { + if (!data) throw mtpErrorUninitialized(); + return *(const MTPDmessages_botResults*)data; + } + + uint32 innerLength() const; + mtpTypeId type() const; + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_botResults); + void write(mtpBuffer &to) const; + + typedef void ResponseType; + +private: + explicit MTPmessages_botResults(MTPDmessages_botResults *_data); + + friend MTPmessages_botResults MTP_messages_botResults(MTPint _flags, const MTPlong &_query_id, const MTPstring &_next_offset, const MTPVector &_results); +}; +typedef MTPBoxed MTPmessages_BotResults; + // Type constructors with data class MTPDresPQ : public mtpDataImpl { @@ -9433,35 +9876,38 @@ class MTPDinputMediaUploadedDocument : public mtpDataImpl &_attributes) : vfile(_file), vmime_type(_mime_type), vattributes(_attributes) { + MTPDinputMediaUploadedDocument(const MTPInputFile &_file, const MTPstring &_mime_type, const MTPVector &_attributes, const MTPstring &_caption) : vfile(_file), vmime_type(_mime_type), vattributes(_attributes), vcaption(_caption) { } MTPInputFile vfile; MTPstring vmime_type; MTPVector vattributes; + MTPstring vcaption; }; class MTPDinputMediaUploadedThumbDocument : public mtpDataImpl { public: MTPDinputMediaUploadedThumbDocument() { } - MTPDinputMediaUploadedThumbDocument(const MTPInputFile &_file, const MTPInputFile &_thumb, const MTPstring &_mime_type, const MTPVector &_attributes) : vfile(_file), vthumb(_thumb), vmime_type(_mime_type), vattributes(_attributes) { + MTPDinputMediaUploadedThumbDocument(const MTPInputFile &_file, const MTPInputFile &_thumb, const MTPstring &_mime_type, const MTPVector &_attributes, const MTPstring &_caption) : vfile(_file), vthumb(_thumb), vmime_type(_mime_type), vattributes(_attributes), vcaption(_caption) { } MTPInputFile vfile; MTPInputFile vthumb; MTPstring vmime_type; MTPVector vattributes; + MTPstring vcaption; }; class MTPDinputMediaDocument : public mtpDataImpl { public: MTPDinputMediaDocument() { } - MTPDinputMediaDocument(const MTPInputDocument &_id) : vid(_id) { + MTPDinputMediaDocument(const MTPInputDocument &_id, const MTPstring &_caption) : vid(_id), vcaption(_caption) { } MTPInputDocument vid; + MTPstring vcaption; }; class MTPDinputMediaVenue : public mtpDataImpl { @@ -9478,6 +9924,17 @@ public: MTPstring vvenue_id; }; +class MTPDinputMediaGifExternal : public mtpDataImpl { +public: + MTPDinputMediaGifExternal() { + } + MTPDinputMediaGifExternal(const MTPstring &_url, const MTPstring &_q) : vurl(_url), vq(_q) { + } + + MTPstring vurl; + MTPstring vq; +}; + class MTPDinputChatUploadedPhoto : public mtpDataImpl { public: MTPDinputChatUploadedPhoto() { @@ -9683,7 +10140,7 @@ class MTPDuser : public mtpDataImpl { public: MTPDuser() { } - MTPDuser(MTPint _flags, MTPint _id, const MTPlong &_access_hash, const MTPstring &_first_name, const MTPstring &_last_name, const MTPstring &_username, const MTPstring &_phone, const MTPUserProfilePhoto &_photo, const MTPUserStatus &_status, MTPint _bot_info_version) : vflags(_flags), vid(_id), vaccess_hash(_access_hash), vfirst_name(_first_name), vlast_name(_last_name), vusername(_username), vphone(_phone), vphoto(_photo), vstatus(_status), vbot_info_version(_bot_info_version) { + MTPDuser(MTPint _flags, MTPint _id, const MTPlong &_access_hash, const MTPstring &_first_name, const MTPstring &_last_name, const MTPstring &_username, const MTPstring &_phone, const MTPUserProfilePhoto &_photo, const MTPUserStatus &_status, MTPint _bot_info_version, const MTPstring &_restriction_reason, const MTPstring &_bot_inline_placeholder) : vflags(_flags), vid(_id), vaccess_hash(_access_hash), vfirst_name(_first_name), vlast_name(_last_name), vusername(_username), vphone(_phone), vphoto(_photo), vstatus(_status), vbot_info_version(_bot_info_version), vrestriction_reason(_restriction_reason), vbot_inline_placeholder(_bot_inline_placeholder) { } MTPint vflags; @@ -9696,6 +10153,8 @@ public: MTPUserProfilePhoto vphoto; MTPUserStatus vstatus; MTPint vbot_info_version; + MTPstring vrestriction_reason; + MTPstring vbot_inline_placeholder; enum { flag_self = (1 << 10), @@ -9706,6 +10165,7 @@ public: flag_bot_chat_history = (1 << 15), flag_bot_nochats = (1 << 16), flag_verified = (1 << 17), + flag_restricted = (1 << 18), flag_access_hash = (1 << 0), flag_first_name = (1 << 1), flag_last_name = (1 << 2), @@ -9714,6 +10174,8 @@ public: flag_photo = (1 << 5), flag_status = (1 << 6), flag_bot_info_version = (1 << 14), + flag_restriction_reason = (1 << 18), + flag_bot_inline_placeholder = (1 << 19), }; bool is_self() const { return vflags.v & flag_self; } @@ -9724,6 +10186,7 @@ public: bool is_bot_chat_history() const { return vflags.v & flag_bot_chat_history; } bool is_bot_nochats() const { return vflags.v & flag_bot_nochats; } bool is_verified() const { return vflags.v & flag_verified; } + bool is_restricted() const { return vflags.v & flag_restricted; } bool has_access_hash() const { return vflags.v & flag_access_hash; } bool has_first_name() const { return vflags.v & flag_first_name; } bool has_last_name() const { return vflags.v & flag_last_name; } @@ -9732,6 +10195,8 @@ public: bool has_photo() const { return vflags.v & flag_photo; } bool has_status() const { return vflags.v & flag_status; } bool has_bot_info_version() const { return vflags.v & flag_bot_info_version; } + bool has_restriction_reason() const { return vflags.v & flag_restriction_reason; } + bool has_bot_inline_placeholder() const { return vflags.v & flag_bot_inline_placeholder; } }; class MTPDuserProfilePhoto : public mtpDataImpl { @@ -9826,7 +10291,7 @@ class MTPDchannel : public mtpDataImpl { public: MTPDchannel() { } - MTPDchannel(MTPint _flags, MTPint _id, const MTPlong &_access_hash, const MTPstring &_title, const MTPstring &_username, const MTPChatPhoto &_photo, MTPint _date, MTPint _version) : vflags(_flags), vid(_id), vaccess_hash(_access_hash), vtitle(_title), vusername(_username), vphoto(_photo), vdate(_date), vversion(_version) { + MTPDchannel(MTPint _flags, MTPint _id, const MTPlong &_access_hash, const MTPstring &_title, const MTPstring &_username, const MTPChatPhoto &_photo, MTPint _date, MTPint _version, const MTPstring &_restriction_reason) : vflags(_flags), vid(_id), vaccess_hash(_access_hash), vtitle(_title), vusername(_username), vphoto(_photo), vdate(_date), vversion(_version), vrestriction_reason(_restriction_reason) { } MTPint vflags; @@ -9837,6 +10302,7 @@ public: MTPChatPhoto vphoto; MTPint vdate; MTPint vversion; + MTPstring vrestriction_reason; enum { flag_creator = (1 << 0), @@ -9847,7 +10313,9 @@ public: flag_broadcast = (1 << 5), flag_verified = (1 << 7), flag_megagroup = (1 << 8), + flag_restricted = (1 << 9), flag_username = (1 << 6), + flag_restriction_reason = (1 << 9), }; bool is_creator() const { return vflags.v & flag_creator; } @@ -9858,7 +10326,9 @@ public: bool is_broadcast() const { return vflags.v & flag_broadcast; } bool is_verified() const { return vflags.v & flag_verified; } bool is_megagroup() const { return vflags.v & flag_megagroup; } + bool is_restricted() const { return vflags.v & flag_restricted; } bool has_username() const { return vflags.v & flag_username; } + bool has_restriction_reason() const { return vflags.v & flag_restriction_reason; } }; class MTPDchannelForbidden : public mtpDataImpl { @@ -10017,7 +10487,7 @@ class MTPDmessage : public mtpDataImpl { public: MTPDmessage() { } - MTPDmessage(MTPint _flags, MTPint _id, MTPint _from_id, const MTPPeer &_to_id, const MTPPeer &_fwd_from_id, MTPint _fwd_date, MTPint _reply_to_msg_id, MTPint _date, const MTPstring &_message, const MTPMessageMedia &_media, const MTPReplyMarkup &_reply_markup, const MTPVector &_entities, MTPint _views) : vflags(_flags), vid(_id), vfrom_id(_from_id), vto_id(_to_id), vfwd_from_id(_fwd_from_id), vfwd_date(_fwd_date), vreply_to_msg_id(_reply_to_msg_id), vdate(_date), vmessage(_message), vmedia(_media), vreply_markup(_reply_markup), ventities(_entities), vviews(_views) { + MTPDmessage(MTPint _flags, MTPint _id, MTPint _from_id, const MTPPeer &_to_id, const MTPPeer &_fwd_from_id, MTPint _fwd_date, MTPint _via_bot_id, MTPint _reply_to_msg_id, MTPint _date, const MTPstring &_message, const MTPMessageMedia &_media, const MTPReplyMarkup &_reply_markup, const MTPVector &_entities, MTPint _views) : vflags(_flags), vid(_id), vfrom_id(_from_id), vto_id(_to_id), vfwd_from_id(_fwd_from_id), vfwd_date(_fwd_date), vvia_bot_id(_via_bot_id), vreply_to_msg_id(_reply_to_msg_id), vdate(_date), vmessage(_message), vmedia(_media), vreply_markup(_reply_markup), ventities(_entities), vviews(_views) { } MTPint vflags; @@ -10026,6 +10496,7 @@ public: MTPPeer vto_id; MTPPeer vfwd_from_id; MTPint vfwd_date; + MTPint vvia_bot_id; MTPint vreply_to_msg_id; MTPint vdate; MTPstring vmessage; @@ -10042,6 +10513,7 @@ public: flag_from_id = (1 << 8), flag_fwd_from_id = (1 << 2), flag_fwd_date = (1 << 2), + flag_via_bot_id = (1 << 11), flag_reply_to_msg_id = (1 << 3), flag_media = (1 << 9), flag_reply_markup = (1 << 6), @@ -10056,6 +10528,7 @@ public: bool has_from_id() const { return vflags.v & flag_from_id; } bool has_fwd_from_id() const { return vflags.v & flag_fwd_from_id; } bool has_fwd_date() const { return vflags.v & flag_fwd_date; } + bool has_via_bot_id() const { return vflags.v & flag_via_bot_id; } bool has_reply_to_msg_id() const { return vflags.v & flag_reply_to_msg_id; } bool has_media() const { return vflags.v & flag_media; } bool has_reply_markup() const { return vflags.v & flag_reply_markup; } @@ -10141,10 +10614,11 @@ class MTPDmessageMediaDocument : public mtpDataImpl { public: MTPDmessageMediaDocument() { } - MTPDmessageMediaDocument(const MTPDocument &_document) : vdocument(_document) { + MTPDmessageMediaDocument(const MTPDocument &_document, const MTPstring &_caption) : vdocument(_document), vcaption(_caption) { } MTPDocument vdocument; + MTPstring vcaption; }; class MTPDmessageMediaAudio : public mtpDataImpl { @@ -11233,6 +11707,19 @@ public: MTPVector vorder; }; +class MTPDupdateBotInlineQuery : public mtpDataImpl { +public: + MTPDupdateBotInlineQuery() { + } + MTPDupdateBotInlineQuery(const MTPlong &_query_id, MTPint _user_id, const MTPstring &_query, const MTPstring &_offset) : vquery_id(_query_id), vuser_id(_user_id), vquery(_query), voffset(_offset) { + } + + MTPlong vquery_id; + MTPint vuser_id; + MTPstring vquery; + MTPstring voffset; +}; + class MTPDupdates_state : public mtpDataImpl { public: MTPDupdates_state() { @@ -11292,7 +11779,7 @@ class MTPDupdateShortMessage : public mtpDataImpl { public: MTPDupdateShortMessage() { } - MTPDupdateShortMessage(MTPint _flags, MTPint _id, MTPint _user_id, const MTPstring &_message, MTPint _pts, MTPint _pts_count, MTPint _date, const MTPPeer &_fwd_from_id, MTPint _fwd_date, MTPint _reply_to_msg_id, const MTPVector &_entities) : vflags(_flags), vid(_id), vuser_id(_user_id), vmessage(_message), vpts(_pts), vpts_count(_pts_count), vdate(_date), vfwd_from_id(_fwd_from_id), vfwd_date(_fwd_date), vreply_to_msg_id(_reply_to_msg_id), ventities(_entities) { + MTPDupdateShortMessage(MTPint _flags, MTPint _id, MTPint _user_id, const MTPstring &_message, MTPint _pts, MTPint _pts_count, MTPint _date, const MTPPeer &_fwd_from_id, MTPint _fwd_date, MTPint _via_bot_id, MTPint _reply_to_msg_id, const MTPVector &_entities) : vflags(_flags), vid(_id), vuser_id(_user_id), vmessage(_message), vpts(_pts), vpts_count(_pts_count), vdate(_date), vfwd_from_id(_fwd_from_id), vfwd_date(_fwd_date), vvia_bot_id(_via_bot_id), vreply_to_msg_id(_reply_to_msg_id), ventities(_entities) { } MTPint vflags; @@ -11304,6 +11791,7 @@ public: MTPint vdate; MTPPeer vfwd_from_id; MTPint vfwd_date; + MTPint vvia_bot_id; MTPint vreply_to_msg_id; MTPVector ventities; @@ -11314,6 +11802,7 @@ public: flag_media_unread = (1 << 5), flag_fwd_from_id = (1 << 2), flag_fwd_date = (1 << 2), + flag_via_bot_id = (1 << 11), flag_reply_to_msg_id = (1 << 3), flag_entities = (1 << 7), }; @@ -11324,6 +11813,7 @@ public: bool is_media_unread() const { return vflags.v & flag_media_unread; } bool has_fwd_from_id() const { return vflags.v & flag_fwd_from_id; } bool has_fwd_date() const { return vflags.v & flag_fwd_date; } + bool has_via_bot_id() const { return vflags.v & flag_via_bot_id; } bool has_reply_to_msg_id() const { return vflags.v & flag_reply_to_msg_id; } bool has_entities() const { return vflags.v & flag_entities; } }; @@ -11332,7 +11822,7 @@ class MTPDupdateShortChatMessage : public mtpDataImpl &_entities) : vflags(_flags), vid(_id), vfrom_id(_from_id), vchat_id(_chat_id), vmessage(_message), vpts(_pts), vpts_count(_pts_count), vdate(_date), vfwd_from_id(_fwd_from_id), vfwd_date(_fwd_date), vreply_to_msg_id(_reply_to_msg_id), ventities(_entities) { + MTPDupdateShortChatMessage(MTPint _flags, MTPint _id, MTPint _from_id, MTPint _chat_id, const MTPstring &_message, MTPint _pts, MTPint _pts_count, MTPint _date, const MTPPeer &_fwd_from_id, MTPint _fwd_date, MTPint _via_bot_id, MTPint _reply_to_msg_id, const MTPVector &_entities) : vflags(_flags), vid(_id), vfrom_id(_from_id), vchat_id(_chat_id), vmessage(_message), vpts(_pts), vpts_count(_pts_count), vdate(_date), vfwd_from_id(_fwd_from_id), vfwd_date(_fwd_date), vvia_bot_id(_via_bot_id), vreply_to_msg_id(_reply_to_msg_id), ventities(_entities) { } MTPint vflags; @@ -11345,6 +11835,7 @@ public: MTPint vdate; MTPPeer vfwd_from_id; MTPint vfwd_date; + MTPint vvia_bot_id; MTPint vreply_to_msg_id; MTPVector ventities; @@ -11355,6 +11846,7 @@ public: flag_media_unread = (1 << 5), flag_fwd_from_id = (1 << 2), flag_fwd_date = (1 << 2), + flag_via_bot_id = (1 << 11), flag_reply_to_msg_id = (1 << 3), flag_entities = (1 << 7), }; @@ -11365,6 +11857,7 @@ public: bool is_media_unread() const { return vflags.v & flag_media_unread; } bool has_fwd_from_id() const { return vflags.v & flag_fwd_from_id; } bool has_fwd_date() const { return vflags.v & flag_fwd_date; } + bool has_via_bot_id() const { return vflags.v & flag_via_bot_id; } bool has_reply_to_msg_id() const { return vflags.v & flag_reply_to_msg_id; } bool has_entities() const { return vflags.v & flag_entities; } }; @@ -11508,7 +12001,7 @@ class MTPDconfig : public mtpDataImpl { public: MTPDconfig() { } - MTPDconfig(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, const MTPVector &_disabled_features) : vdate(_date), vexpires(_expires), vtest_mode(_test_mode), vthis_dc(_this_dc), vdc_options(_dc_options), vchat_size_max(_chat_size_max), vmegagroup_size_max(_megagroup_size_max), vforwarded_count_max(_forwarded_count_max), vonline_update_period_ms(_online_update_period_ms), voffline_blur_timeout_ms(_offline_blur_timeout_ms), voffline_idle_timeout_ms(_offline_idle_timeout_ms), vonline_cloud_timeout_ms(_online_cloud_timeout_ms), vnotify_cloud_delay_ms(_notify_cloud_delay_ms), vnotify_default_delay_ms(_notify_default_delay_ms), vchat_big_size(_chat_big_size), vpush_chat_period_ms(_push_chat_period_ms), vpush_chat_limit(_push_chat_limit), vdisabled_features(_disabled_features) { + MTPDconfig(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, MTPint _saved_gifs_limit, const MTPVector &_disabled_features) : vdate(_date), vexpires(_expires), vtest_mode(_test_mode), vthis_dc(_this_dc), vdc_options(_dc_options), vchat_size_max(_chat_size_max), vmegagroup_size_max(_megagroup_size_max), vforwarded_count_max(_forwarded_count_max), vonline_update_period_ms(_online_update_period_ms), voffline_blur_timeout_ms(_offline_blur_timeout_ms), voffline_idle_timeout_ms(_offline_idle_timeout_ms), vonline_cloud_timeout_ms(_online_cloud_timeout_ms), vnotify_cloud_delay_ms(_notify_cloud_delay_ms), vnotify_default_delay_ms(_notify_default_delay_ms), vchat_big_size(_chat_big_size), vpush_chat_period_ms(_push_chat_period_ms), vpush_chat_limit(_push_chat_limit), vsaved_gifs_limit(_saved_gifs_limit), vdisabled_features(_disabled_features) { } MTPint vdate; @@ -11528,6 +12021,7 @@ public: MTPint vchat_big_size; MTPint vpush_chat_period_ms; MTPint vpush_chat_limit; + MTPint vsaved_gifs_limit; MTPVector vdisabled_features; }; @@ -12857,6 +13351,250 @@ public: MTPstring vtext; }; +class MTPDfoundGif : public mtpDataImpl { +public: + MTPDfoundGif() { + } + MTPDfoundGif(const MTPstring &_url, const MTPstring &_thumb_url, const MTPstring &_content_url, const MTPstring &_content_type, MTPint _w, MTPint _h) : vurl(_url), vthumb_url(_thumb_url), vcontent_url(_content_url), vcontent_type(_content_type), vw(_w), vh(_h) { + } + + MTPstring vurl; + MTPstring vthumb_url; + MTPstring vcontent_url; + MTPstring vcontent_type; + MTPint vw; + MTPint vh; +}; + +class MTPDfoundGifCached : public mtpDataImpl { +public: + MTPDfoundGifCached() { + } + MTPDfoundGifCached(const MTPstring &_url, const MTPPhoto &_photo, const MTPDocument &_document) : vurl(_url), vphoto(_photo), vdocument(_document) { + } + + MTPstring vurl; + MTPPhoto vphoto; + MTPDocument vdocument; +}; + +class MTPDmessages_foundGifs : public mtpDataImpl { +public: + MTPDmessages_foundGifs() { + } + MTPDmessages_foundGifs(MTPint _next_offset, const MTPVector &_results) : vnext_offset(_next_offset), vresults(_results) { + } + + MTPint vnext_offset; + MTPVector vresults; +}; + +class MTPDmessages_savedGifs : public mtpDataImpl { +public: + MTPDmessages_savedGifs() { + } + MTPDmessages_savedGifs(MTPint _hash, const MTPVector &_gifs) : vhash(_hash), vgifs(_gifs) { + } + + MTPint vhash; + MTPVector vgifs; +}; + +class MTPDinputBotInlineMessageMediaAuto : public mtpDataImpl { +public: + MTPDinputBotInlineMessageMediaAuto() { + } + MTPDinputBotInlineMessageMediaAuto(const MTPstring &_caption) : vcaption(_caption) { + } + + MTPstring vcaption; +}; + +class MTPDinputBotInlineMessageText : public mtpDataImpl { +public: + MTPDinputBotInlineMessageText() { + } + MTPDinputBotInlineMessageText(MTPint _flags, const MTPstring &_message, const MTPVector &_entities) : vflags(_flags), vmessage(_message), ventities(_entities) { + } + + MTPint vflags; + MTPstring vmessage; + MTPVector ventities; + + enum { + flag_no_webpage = (1 << 0), + flag_entities = (1 << 1), + }; + + bool is_no_webpage() const { return vflags.v & flag_no_webpage; } + bool has_entities() const { return vflags.v & flag_entities; } +}; + +class MTPDinputBotInlineResult : public mtpDataImpl { +public: + MTPDinputBotInlineResult() { + } + MTPDinputBotInlineResult(MTPint _flags, const MTPstring &_id, const MTPstring &_type, const MTPstring &_title, const MTPstring &_description, const MTPstring &_url, const MTPstring &_thumb_url, const MTPstring &_content_url, const MTPstring &_content_type, MTPint _w, MTPint _h, MTPint _duration, const MTPInputBotInlineMessage &_send_message) : vflags(_flags), vid(_id), vtype(_type), vtitle(_title), vdescription(_description), vurl(_url), vthumb_url(_thumb_url), vcontent_url(_content_url), vcontent_type(_content_type), vw(_w), vh(_h), vduration(_duration), vsend_message(_send_message) { + } + + MTPint vflags; + MTPstring vid; + MTPstring vtype; + MTPstring vtitle; + MTPstring vdescription; + MTPstring vurl; + MTPstring vthumb_url; + MTPstring vcontent_url; + MTPstring vcontent_type; + MTPint vw; + MTPint vh; + MTPint vduration; + MTPInputBotInlineMessage vsend_message; + + enum { + flag_title = (1 << 1), + flag_description = (1 << 2), + flag_url = (1 << 3), + flag_thumb_url = (1 << 4), + flag_content_url = (1 << 5), + flag_content_type = (1 << 5), + flag_w = (1 << 6), + flag_h = (1 << 6), + flag_duration = (1 << 7), + }; + + bool has_title() const { return vflags.v & flag_title; } + bool has_description() const { return vflags.v & flag_description; } + bool has_url() const { return vflags.v & flag_url; } + bool has_thumb_url() const { return vflags.v & flag_thumb_url; } + bool has_content_url() const { return vflags.v & flag_content_url; } + bool has_content_type() const { return vflags.v & flag_content_type; } + bool has_w() const { return vflags.v & flag_w; } + bool has_h() const { return vflags.v & flag_h; } + bool has_duration() const { return vflags.v & flag_duration; } +}; + +class MTPDbotInlineMessageMediaAuto : public mtpDataImpl { +public: + MTPDbotInlineMessageMediaAuto() { + } + MTPDbotInlineMessageMediaAuto(const MTPstring &_caption) : vcaption(_caption) { + } + + MTPstring vcaption; +}; + +class MTPDbotInlineMessageText : public mtpDataImpl { +public: + MTPDbotInlineMessageText() { + } + MTPDbotInlineMessageText(MTPint _flags, const MTPstring &_message, const MTPVector &_entities) : vflags(_flags), vmessage(_message), ventities(_entities) { + } + + MTPint vflags; + MTPstring vmessage; + MTPVector ventities; + + enum { + flag_no_webpage = (1 << 0), + flag_entities = (1 << 1), + }; + + bool is_no_webpage() const { return vflags.v & flag_no_webpage; } + bool has_entities() const { return vflags.v & flag_entities; } +}; + +class MTPDbotInlineMediaResultDocument : public mtpDataImpl { +public: + MTPDbotInlineMediaResultDocument() { + } + MTPDbotInlineMediaResultDocument(const MTPstring &_id, const MTPstring &_type, const MTPDocument &_document, const MTPBotInlineMessage &_send_message) : vid(_id), vtype(_type), vdocument(_document), vsend_message(_send_message) { + } + + MTPstring vid; + MTPstring vtype; + MTPDocument vdocument; + MTPBotInlineMessage vsend_message; +}; + +class MTPDbotInlineMediaResultPhoto : public mtpDataImpl { +public: + MTPDbotInlineMediaResultPhoto() { + } + MTPDbotInlineMediaResultPhoto(const MTPstring &_id, const MTPstring &_type, const MTPPhoto &_photo, const MTPBotInlineMessage &_send_message) : vid(_id), vtype(_type), vphoto(_photo), vsend_message(_send_message) { + } + + MTPstring vid; + MTPstring vtype; + MTPPhoto vphoto; + MTPBotInlineMessage vsend_message; +}; + +class MTPDbotInlineResult : public mtpDataImpl { +public: + MTPDbotInlineResult() { + } + MTPDbotInlineResult(MTPint _flags, const MTPstring &_id, const MTPstring &_type, const MTPstring &_title, const MTPstring &_description, const MTPstring &_url, const MTPstring &_thumb_url, const MTPstring &_content_url, const MTPstring &_content_type, MTPint _w, MTPint _h, MTPint _duration, const MTPBotInlineMessage &_send_message) : vflags(_flags), vid(_id), vtype(_type), vtitle(_title), vdescription(_description), vurl(_url), vthumb_url(_thumb_url), vcontent_url(_content_url), vcontent_type(_content_type), vw(_w), vh(_h), vduration(_duration), vsend_message(_send_message) { + } + + MTPint vflags; + MTPstring vid; + MTPstring vtype; + MTPstring vtitle; + MTPstring vdescription; + MTPstring vurl; + MTPstring vthumb_url; + MTPstring vcontent_url; + MTPstring vcontent_type; + MTPint vw; + MTPint vh; + MTPint vduration; + MTPBotInlineMessage vsend_message; + + enum { + flag_title = (1 << 1), + flag_description = (1 << 2), + flag_url = (1 << 3), + flag_thumb_url = (1 << 4), + flag_content_url = (1 << 5), + flag_content_type = (1 << 5), + flag_w = (1 << 6), + flag_h = (1 << 6), + flag_duration = (1 << 7), + }; + + bool has_title() const { return vflags.v & flag_title; } + bool has_description() const { return vflags.v & flag_description; } + bool has_url() const { return vflags.v & flag_url; } + bool has_thumb_url() const { return vflags.v & flag_thumb_url; } + bool has_content_url() const { return vflags.v & flag_content_url; } + bool has_content_type() const { return vflags.v & flag_content_type; } + bool has_w() const { return vflags.v & flag_w; } + bool has_h() const { return vflags.v & flag_h; } + bool has_duration() const { return vflags.v & flag_duration; } +}; + +class MTPDmessages_botResults : public mtpDataImpl { +public: + MTPDmessages_botResults() { + } + MTPDmessages_botResults(MTPint _flags, const MTPlong &_query_id, const MTPstring &_next_offset, const MTPVector &_results) : vflags(_flags), vquery_id(_query_id), vnext_offset(_next_offset), vresults(_results) { + } + + MTPint vflags; + MTPlong vquery_id; + MTPstring vnext_offset; + MTPVector vresults; + + enum { + flag_gallery = (1 << 0), + flag_next_offset = (1 << 1), + }; + + bool is_gallery() const { return vflags.v & flag_gallery; } + bool has_next_offset() const { return vflags.v & flag_next_offset; } +}; + // RPC methods class MTPreq_pq { // RPC method 'req_pq' @@ -17807,6 +18545,342 @@ public: } }; +class MTPmessages_getDocumentByHash { // RPC method 'messages.getDocumentByHash' +public: + MTPbytes vsha256; + MTPint vsize; + MTPstring vmime_type; + + MTPmessages_getDocumentByHash() { + } + MTPmessages_getDocumentByHash(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getDocumentByHash) { + read(from, end, cons); + } + MTPmessages_getDocumentByHash(const MTPbytes &_sha256, MTPint _size, const MTPstring &_mime_type) : vsha256(_sha256), vsize(_size), vmime_type(_mime_type) { + } + + uint32 innerLength() const { + return vsha256.innerLength() + vsize.innerLength() + vmime_type.innerLength(); + } + mtpTypeId type() const { + return mtpc_messages_getDocumentByHash; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getDocumentByHash) { + vsha256.read(from, end); + vsize.read(from, end); + vmime_type.read(from, end); + } + void write(mtpBuffer &to) const { + vsha256.write(to); + vsize.write(to); + vmime_type.write(to); + } + + typedef MTPDocument ResponseType; +}; +class MTPmessages_GetDocumentByHash : public MTPBoxed { +public: + MTPmessages_GetDocumentByHash() { + } + MTPmessages_GetDocumentByHash(const MTPmessages_getDocumentByHash &v) : MTPBoxed(v) { + } + MTPmessages_GetDocumentByHash(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } + MTPmessages_GetDocumentByHash(const MTPbytes &_sha256, MTPint _size, const MTPstring &_mime_type) : MTPBoxed(MTPmessages_getDocumentByHash(_sha256, _size, _mime_type)) { + } +}; + +class MTPmessages_searchGifs { // RPC method 'messages.searchGifs' +public: + MTPstring vq; + MTPint voffset; + + MTPmessages_searchGifs() { + } + MTPmessages_searchGifs(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_searchGifs) { + read(from, end, cons); + } + MTPmessages_searchGifs(const MTPstring &_q, MTPint _offset) : vq(_q), voffset(_offset) { + } + + uint32 innerLength() const { + return vq.innerLength() + voffset.innerLength(); + } + mtpTypeId type() const { + return mtpc_messages_searchGifs; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_searchGifs) { + vq.read(from, end); + voffset.read(from, end); + } + void write(mtpBuffer &to) const { + vq.write(to); + voffset.write(to); + } + + typedef MTPmessages_FoundGifs ResponseType; +}; +class MTPmessages_SearchGifs : public MTPBoxed { +public: + MTPmessages_SearchGifs() { + } + MTPmessages_SearchGifs(const MTPmessages_searchGifs &v) : MTPBoxed(v) { + } + MTPmessages_SearchGifs(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } + MTPmessages_SearchGifs(const MTPstring &_q, MTPint _offset) : MTPBoxed(MTPmessages_searchGifs(_q, _offset)) { + } +}; + +class MTPmessages_getSavedGifs { // RPC method 'messages.getSavedGifs' +public: + MTPint vhash; + + MTPmessages_getSavedGifs() { + } + MTPmessages_getSavedGifs(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getSavedGifs) { + read(from, end, cons); + } + MTPmessages_getSavedGifs(MTPint _hash) : vhash(_hash) { + } + + uint32 innerLength() const { + return vhash.innerLength(); + } + mtpTypeId type() const { + return mtpc_messages_getSavedGifs; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getSavedGifs) { + vhash.read(from, end); + } + void write(mtpBuffer &to) const { + vhash.write(to); + } + + typedef MTPmessages_SavedGifs ResponseType; +}; +class MTPmessages_GetSavedGifs : public MTPBoxed { +public: + MTPmessages_GetSavedGifs() { + } + MTPmessages_GetSavedGifs(const MTPmessages_getSavedGifs &v) : MTPBoxed(v) { + } + MTPmessages_GetSavedGifs(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } + MTPmessages_GetSavedGifs(MTPint _hash) : MTPBoxed(MTPmessages_getSavedGifs(_hash)) { + } +}; + +class MTPmessages_saveGif { // RPC method 'messages.saveGif' +public: + MTPInputDocument vid; + MTPBool vunsave; + + MTPmessages_saveGif() { + } + MTPmessages_saveGif(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_saveGif) { + read(from, end, cons); + } + MTPmessages_saveGif(const MTPInputDocument &_id, MTPBool _unsave) : vid(_id), vunsave(_unsave) { + } + + uint32 innerLength() const { + return vid.innerLength() + vunsave.innerLength(); + } + mtpTypeId type() const { + return mtpc_messages_saveGif; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_saveGif) { + vid.read(from, end); + vunsave.read(from, end); + } + void write(mtpBuffer &to) const { + vid.write(to); + vunsave.write(to); + } + + typedef MTPBool ResponseType; +}; +class MTPmessages_SaveGif : public MTPBoxed { +public: + MTPmessages_SaveGif() { + } + MTPmessages_SaveGif(const MTPmessages_saveGif &v) : MTPBoxed(v) { + } + MTPmessages_SaveGif(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } + MTPmessages_SaveGif(const MTPInputDocument &_id, MTPBool _unsave) : MTPBoxed(MTPmessages_saveGif(_id, _unsave)) { + } +}; + +class MTPmessages_getInlineBotResults { // RPC method 'messages.getInlineBotResults' +public: + MTPInputUser vbot; + MTPstring vquery; + MTPstring voffset; + + MTPmessages_getInlineBotResults() { + } + MTPmessages_getInlineBotResults(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getInlineBotResults) { + read(from, end, cons); + } + MTPmessages_getInlineBotResults(const MTPInputUser &_bot, const MTPstring &_query, const MTPstring &_offset) : vbot(_bot), vquery(_query), voffset(_offset) { + } + + uint32 innerLength() const { + return vbot.innerLength() + vquery.innerLength() + voffset.innerLength(); + } + mtpTypeId type() const { + return mtpc_messages_getInlineBotResults; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getInlineBotResults) { + vbot.read(from, end); + vquery.read(from, end); + voffset.read(from, end); + } + void write(mtpBuffer &to) const { + vbot.write(to); + vquery.write(to); + voffset.write(to); + } + + typedef MTPmessages_BotResults ResponseType; +}; +class MTPmessages_GetInlineBotResults : public MTPBoxed { +public: + MTPmessages_GetInlineBotResults() { + } + MTPmessages_GetInlineBotResults(const MTPmessages_getInlineBotResults &v) : MTPBoxed(v) { + } + MTPmessages_GetInlineBotResults(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } + MTPmessages_GetInlineBotResults(const MTPInputUser &_bot, const MTPstring &_query, const MTPstring &_offset) : MTPBoxed(MTPmessages_getInlineBotResults(_bot, _query, _offset)) { + } +}; + +class MTPmessages_setInlineBotResults { // RPC method 'messages.setInlineBotResults' +public: + MTPint vflags; + MTPlong vquery_id; + MTPVector vresults; + MTPint vcache_time; + MTPstring vnext_offset; + + MTPmessages_setInlineBotResults() { + } + MTPmessages_setInlineBotResults(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_setInlineBotResults) { + read(from, end, cons); + } + MTPmessages_setInlineBotResults(MTPint _flags, const MTPlong &_query_id, const MTPVector &_results, MTPint _cache_time, const MTPstring &_next_offset) : vflags(_flags), vquery_id(_query_id), vresults(_results), vcache_time(_cache_time), vnext_offset(_next_offset) { + } + + enum { + flag_gallery = (1 << 0), + flag_private = (1 << 1), + flag_next_offset = (1 << 2), + }; + + bool is_gallery() const { return vflags.v & flag_gallery; } + bool is_private() const { return vflags.v & flag_private; } + bool has_next_offset() const { return vflags.v & flag_next_offset; } + + uint32 innerLength() const { + return vflags.innerLength() + vquery_id.innerLength() + vresults.innerLength() + vcache_time.innerLength() + (has_next_offset() ? vnext_offset.innerLength() : 0); + } + mtpTypeId type() const { + return mtpc_messages_setInlineBotResults; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_setInlineBotResults) { + vflags.read(from, end); + vquery_id.read(from, end); + vresults.read(from, end); + vcache_time.read(from, end); + if (has_next_offset()) { vnext_offset.read(from, end); } else { vnext_offset = MTPstring(); } + } + void write(mtpBuffer &to) const { + vflags.write(to); + vquery_id.write(to); + vresults.write(to); + vcache_time.write(to); + if (has_next_offset()) vnext_offset.write(to); + } + + typedef MTPBool ResponseType; +}; +class MTPmessages_SetInlineBotResults : public MTPBoxed { +public: + MTPmessages_SetInlineBotResults() { + } + MTPmessages_SetInlineBotResults(const MTPmessages_setInlineBotResults &v) : MTPBoxed(v) { + } + MTPmessages_SetInlineBotResults(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } + MTPmessages_SetInlineBotResults(MTPint _flags, const MTPlong &_query_id, const MTPVector &_results, MTPint _cache_time, const MTPstring &_next_offset) : MTPBoxed(MTPmessages_setInlineBotResults(_flags, _query_id, _results, _cache_time, _next_offset)) { + } +}; + +class MTPmessages_sendInlineBotResult { // RPC method 'messages.sendInlineBotResult' +public: + MTPint vflags; + MTPInputPeer vpeer; + MTPint vreply_to_msg_id; + MTPlong vrandom_id; + MTPlong vquery_id; + MTPstring vid; + + MTPmessages_sendInlineBotResult() { + } + MTPmessages_sendInlineBotResult(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_sendInlineBotResult) { + read(from, end, cons); + } + MTPmessages_sendInlineBotResult(MTPint _flags, const MTPInputPeer &_peer, MTPint _reply_to_msg_id, const MTPlong &_random_id, const MTPlong &_query_id, const MTPstring &_id) : vflags(_flags), vpeer(_peer), vreply_to_msg_id(_reply_to_msg_id), vrandom_id(_random_id), vquery_id(_query_id), vid(_id) { + } + + enum { + flag_broadcast = (1 << 4), + flag_reply_to_msg_id = (1 << 0), + }; + + bool is_broadcast() const { return vflags.v & flag_broadcast; } + bool has_reply_to_msg_id() const { return vflags.v & flag_reply_to_msg_id; } + + uint32 innerLength() const { + return vflags.innerLength() + vpeer.innerLength() + (has_reply_to_msg_id() ? vreply_to_msg_id.innerLength() : 0) + vrandom_id.innerLength() + vquery_id.innerLength() + vid.innerLength(); + } + mtpTypeId type() const { + return mtpc_messages_sendInlineBotResult; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_sendInlineBotResult) { + vflags.read(from, end); + vpeer.read(from, end); + if (has_reply_to_msg_id()) { vreply_to_msg_id.read(from, end); } else { vreply_to_msg_id = MTPint(); } + vrandom_id.read(from, end); + vquery_id.read(from, end); + vid.read(from, end); + } + void write(mtpBuffer &to) const { + vflags.write(to); + vpeer.write(to); + if (has_reply_to_msg_id()) vreply_to_msg_id.write(to); + vrandom_id.write(to); + vquery_id.write(to); + vid.write(to); + } + + typedef MTPUpdates ResponseType; +}; +class MTPmessages_SendInlineBotResult : public MTPBoxed { +public: + MTPmessages_SendInlineBotResult() { + } + MTPmessages_SendInlineBotResult(const MTPmessages_sendInlineBotResult &v) : MTPBoxed(v) { + } + MTPmessages_SendInlineBotResult(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } + MTPmessages_SendInlineBotResult(MTPint _flags, const MTPInputPeer &_peer, MTPint _reply_to_msg_id, const MTPlong &_random_id, const MTPlong &_query_id, const MTPstring &_id) : MTPBoxed(MTPmessages_sendInlineBotResult(_flags, _peer, _reply_to_msg_id, _random_id, _query_id, _id)) { + } +}; + class MTPupdates_getState { // RPC method 'updates.getState' public: MTPupdates_getState() { @@ -20904,20 +21978,24 @@ inline uint32 MTPinputMedia::innerLength() const { } case mtpc_inputMediaUploadedDocument: { const MTPDinputMediaUploadedDocument &v(c_inputMediaUploadedDocument()); - return v.vfile.innerLength() + v.vmime_type.innerLength() + v.vattributes.innerLength(); + return v.vfile.innerLength() + v.vmime_type.innerLength() + v.vattributes.innerLength() + v.vcaption.innerLength(); } case mtpc_inputMediaUploadedThumbDocument: { const MTPDinputMediaUploadedThumbDocument &v(c_inputMediaUploadedThumbDocument()); - return v.vfile.innerLength() + v.vthumb.innerLength() + v.vmime_type.innerLength() + v.vattributes.innerLength(); + return v.vfile.innerLength() + v.vthumb.innerLength() + v.vmime_type.innerLength() + v.vattributes.innerLength() + v.vcaption.innerLength(); } case mtpc_inputMediaDocument: { const MTPDinputMediaDocument &v(c_inputMediaDocument()); - return v.vid.innerLength(); + return v.vid.innerLength() + v.vcaption.innerLength(); } case mtpc_inputMediaVenue: { const MTPDinputMediaVenue &v(c_inputMediaVenue()); return v.vgeo_point.innerLength() + v.vtitle.innerLength() + v.vaddress.innerLength() + v.vprovider.innerLength() + v.vvenue_id.innerLength(); } + case mtpc_inputMediaGifExternal: { + const MTPDinputMediaGifExternal &v(c_inputMediaGifExternal()); + return v.vurl.innerLength() + v.vq.innerLength(); + } } return 0; } @@ -20998,6 +22076,7 @@ inline void MTPinputMedia::read(const mtpPrime *&from, const mtpPrime *end, mtpT v.vfile.read(from, end); v.vmime_type.read(from, end); v.vattributes.read(from, end); + v.vcaption.read(from, end); } break; case mtpc_inputMediaUploadedThumbDocument: _type = cons; { if (!data) setData(new MTPDinputMediaUploadedThumbDocument()); @@ -21006,11 +22085,13 @@ inline void MTPinputMedia::read(const mtpPrime *&from, const mtpPrime *end, mtpT v.vthumb.read(from, end); v.vmime_type.read(from, end); v.vattributes.read(from, end); + v.vcaption.read(from, end); } break; case mtpc_inputMediaDocument: _type = cons; { if (!data) setData(new MTPDinputMediaDocument()); MTPDinputMediaDocument &v(_inputMediaDocument()); v.vid.read(from, end); + v.vcaption.read(from, end); } break; case mtpc_inputMediaVenue: _type = cons; { if (!data) setData(new MTPDinputMediaVenue()); @@ -21021,6 +22102,12 @@ inline void MTPinputMedia::read(const mtpPrime *&from, const mtpPrime *end, mtpT v.vprovider.read(from, end); v.vvenue_id.read(from, end); } break; + case mtpc_inputMediaGifExternal: _type = cons; { + if (!data) setData(new MTPDinputMediaGifExternal()); + MTPDinputMediaGifExternal &v(_inputMediaGifExternal()); + v.vurl.read(from, end); + v.vq.read(from, end); + } break; default: throw mtpErrorUnexpected(cons, "MTPinputMedia"); } } @@ -21085,6 +22172,7 @@ inline void MTPinputMedia::write(mtpBuffer &to) const { v.vfile.write(to); v.vmime_type.write(to); v.vattributes.write(to); + v.vcaption.write(to); } break; case mtpc_inputMediaUploadedThumbDocument: { const MTPDinputMediaUploadedThumbDocument &v(c_inputMediaUploadedThumbDocument()); @@ -21092,10 +22180,12 @@ inline void MTPinputMedia::write(mtpBuffer &to) const { v.vthumb.write(to); v.vmime_type.write(to); v.vattributes.write(to); + v.vcaption.write(to); } break; case mtpc_inputMediaDocument: { const MTPDinputMediaDocument &v(c_inputMediaDocument()); v.vid.write(to); + v.vcaption.write(to); } break; case mtpc_inputMediaVenue: { const MTPDinputMediaVenue &v(c_inputMediaVenue()); @@ -21105,6 +22195,11 @@ inline void MTPinputMedia::write(mtpBuffer &to) const { v.vprovider.write(to); v.vvenue_id.write(to); } break; + case mtpc_inputMediaGifExternal: { + const MTPDinputMediaGifExternal &v(c_inputMediaGifExternal()); + v.vurl.write(to); + v.vq.write(to); + } break; } } inline MTPinputMedia::MTPinputMedia(mtpTypeId type) : mtpDataOwner(0), _type(type) { @@ -21123,6 +22218,7 @@ inline MTPinputMedia::MTPinputMedia(mtpTypeId type) : mtpDataOwner(0), _type(typ case mtpc_inputMediaUploadedThumbDocument: setData(new MTPDinputMediaUploadedThumbDocument()); break; case mtpc_inputMediaDocument: setData(new MTPDinputMediaDocument()); break; case mtpc_inputMediaVenue: setData(new MTPDinputMediaVenue()); break; + case mtpc_inputMediaGifExternal: setData(new MTPDinputMediaGifExternal()); break; default: throw mtpErrorBadTypeId(type, "MTPinputMedia"); } } @@ -21152,6 +22248,8 @@ inline MTPinputMedia::MTPinputMedia(MTPDinputMediaDocument *_data) : mtpDataOwne } inline MTPinputMedia::MTPinputMedia(MTPDinputMediaVenue *_data) : mtpDataOwner(_data), _type(mtpc_inputMediaVenue) { } +inline MTPinputMedia::MTPinputMedia(MTPDinputMediaGifExternal *_data) : mtpDataOwner(_data), _type(mtpc_inputMediaGifExternal) { +} inline MTPinputMedia MTP_inputMediaEmpty() { return MTPinputMedia(mtpc_inputMediaEmpty); } @@ -21182,18 +22280,21 @@ inline MTPinputMedia MTP_inputMediaUploadedAudio(const MTPInputFile &_file, MTPi inline MTPinputMedia MTP_inputMediaAudio(const MTPInputAudio &_id) { return MTPinputMedia(new MTPDinputMediaAudio(_id)); } -inline MTPinputMedia MTP_inputMediaUploadedDocument(const MTPInputFile &_file, const MTPstring &_mime_type, const MTPVector &_attributes) { - return MTPinputMedia(new MTPDinputMediaUploadedDocument(_file, _mime_type, _attributes)); +inline MTPinputMedia MTP_inputMediaUploadedDocument(const MTPInputFile &_file, const MTPstring &_mime_type, const MTPVector &_attributes, const MTPstring &_caption) { + return MTPinputMedia(new MTPDinputMediaUploadedDocument(_file, _mime_type, _attributes, _caption)); } -inline MTPinputMedia MTP_inputMediaUploadedThumbDocument(const MTPInputFile &_file, const MTPInputFile &_thumb, const MTPstring &_mime_type, const MTPVector &_attributes) { - return MTPinputMedia(new MTPDinputMediaUploadedThumbDocument(_file, _thumb, _mime_type, _attributes)); +inline MTPinputMedia MTP_inputMediaUploadedThumbDocument(const MTPInputFile &_file, const MTPInputFile &_thumb, const MTPstring &_mime_type, const MTPVector &_attributes, const MTPstring &_caption) { + return MTPinputMedia(new MTPDinputMediaUploadedThumbDocument(_file, _thumb, _mime_type, _attributes, _caption)); } -inline MTPinputMedia MTP_inputMediaDocument(const MTPInputDocument &_id) { - return MTPinputMedia(new MTPDinputMediaDocument(_id)); +inline MTPinputMedia MTP_inputMediaDocument(const MTPInputDocument &_id, const MTPstring &_caption) { + return MTPinputMedia(new MTPDinputMediaDocument(_id, _caption)); } inline MTPinputMedia MTP_inputMediaVenue(const MTPInputGeoPoint &_geo_point, const MTPstring &_title, const MTPstring &_address, const MTPstring &_provider, const MTPstring &_venue_id) { return MTPinputMedia(new MTPDinputMediaVenue(_geo_point, _title, _address, _provider, _venue_id)); } +inline MTPinputMedia MTP_inputMediaGifExternal(const MTPstring &_url, const MTPstring &_q) { + return MTPinputMedia(new MTPDinputMediaGifExternal(_url, _q)); +} inline uint32 MTPinputChatPhoto::innerLength() const { switch (_type) { @@ -21873,7 +22974,7 @@ inline uint32 MTPuser::innerLength() const { } case mtpc_user: { const MTPDuser &v(c_user()); - return v.vflags.innerLength() + v.vid.innerLength() + (v.has_access_hash() ? v.vaccess_hash.innerLength() : 0) + (v.has_first_name() ? v.vfirst_name.innerLength() : 0) + (v.has_last_name() ? v.vlast_name.innerLength() : 0) + (v.has_username() ? v.vusername.innerLength() : 0) + (v.has_phone() ? v.vphone.innerLength() : 0) + (v.has_photo() ? v.vphoto.innerLength() : 0) + (v.has_status() ? v.vstatus.innerLength() : 0) + (v.has_bot_info_version() ? v.vbot_info_version.innerLength() : 0); + return v.vflags.innerLength() + v.vid.innerLength() + (v.has_access_hash() ? v.vaccess_hash.innerLength() : 0) + (v.has_first_name() ? v.vfirst_name.innerLength() : 0) + (v.has_last_name() ? v.vlast_name.innerLength() : 0) + (v.has_username() ? v.vusername.innerLength() : 0) + (v.has_phone() ? v.vphone.innerLength() : 0) + (v.has_photo() ? v.vphoto.innerLength() : 0) + (v.has_status() ? v.vstatus.innerLength() : 0) + (v.has_bot_info_version() ? v.vbot_info_version.innerLength() : 0) + (v.has_restriction_reason() ? v.vrestriction_reason.innerLength() : 0) + (v.has_bot_inline_placeholder() ? v.vbot_inline_placeholder.innerLength() : 0); } } return 0; @@ -21903,6 +23004,8 @@ inline void MTPuser::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId if (v.has_photo()) { v.vphoto.read(from, end); } else { v.vphoto = MTPUserProfilePhoto(); } if (v.has_status()) { v.vstatus.read(from, end); } else { v.vstatus = MTPUserStatus(); } if (v.has_bot_info_version()) { v.vbot_info_version.read(from, end); } else { v.vbot_info_version = MTPint(); } + if (v.has_restriction_reason()) { v.vrestriction_reason.read(from, end); } else { v.vrestriction_reason = MTPstring(); } + if (v.has_bot_inline_placeholder()) { v.vbot_inline_placeholder.read(from, end); } else { v.vbot_inline_placeholder = MTPstring(); } } break; default: throw mtpErrorUnexpected(cons, "MTPuser"); } @@ -21925,6 +23028,8 @@ inline void MTPuser::write(mtpBuffer &to) const { if (v.has_photo()) v.vphoto.write(to); if (v.has_status()) v.vstatus.write(to); if (v.has_bot_info_version()) v.vbot_info_version.write(to); + if (v.has_restriction_reason()) v.vrestriction_reason.write(to); + if (v.has_bot_inline_placeholder()) v.vbot_inline_placeholder.write(to); } break; } } @@ -21942,8 +23047,8 @@ inline MTPuser::MTPuser(MTPDuser *_data) : mtpDataOwner(_data), _type(mtpc_user) inline MTPuser MTP_userEmpty(MTPint _id) { return MTPuser(new MTPDuserEmpty(_id)); } -inline MTPuser MTP_user(MTPint _flags, MTPint _id, const MTPlong &_access_hash, const MTPstring &_first_name, const MTPstring &_last_name, const MTPstring &_username, const MTPstring &_phone, const MTPUserProfilePhoto &_photo, const MTPUserStatus &_status, MTPint _bot_info_version) { - return MTPuser(new MTPDuser(_flags, _id, _access_hash, _first_name, _last_name, _username, _phone, _photo, _status, _bot_info_version)); +inline MTPuser MTP_user(MTPint _flags, MTPint _id, const MTPlong &_access_hash, const MTPstring &_first_name, const MTPstring &_last_name, const MTPstring &_username, const MTPstring &_phone, const MTPUserProfilePhoto &_photo, const MTPUserStatus &_status, MTPint _bot_info_version, const MTPstring &_restriction_reason, const MTPstring &_bot_inline_placeholder) { + return MTPuser(new MTPDuser(_flags, _id, _access_hash, _first_name, _last_name, _username, _phone, _photo, _status, _bot_info_version, _restriction_reason, _bot_inline_placeholder)); } inline uint32 MTPuserProfilePhoto::innerLength() const { @@ -22098,7 +23203,7 @@ inline uint32 MTPchat::innerLength() const { } case mtpc_channel: { const MTPDchannel &v(c_channel()); - return v.vflags.innerLength() + v.vid.innerLength() + v.vaccess_hash.innerLength() + v.vtitle.innerLength() + (v.has_username() ? v.vusername.innerLength() : 0) + v.vphoto.innerLength() + v.vdate.innerLength() + v.vversion.innerLength(); + return v.vflags.innerLength() + v.vid.innerLength() + v.vaccess_hash.innerLength() + v.vtitle.innerLength() + (v.has_username() ? v.vusername.innerLength() : 0) + v.vphoto.innerLength() + v.vdate.innerLength() + v.vversion.innerLength() + (v.has_restriction_reason() ? v.vrestriction_reason.innerLength() : 0); } case mtpc_channelForbidden: { const MTPDchannelForbidden &v(c_channelForbidden()); @@ -22148,6 +23253,7 @@ inline void MTPchat::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId v.vphoto.read(from, end); v.vdate.read(from, end); v.vversion.read(from, end); + if (v.has_restriction_reason()) { v.vrestriction_reason.read(from, end); } else { v.vrestriction_reason = MTPstring(); } } break; case mtpc_channelForbidden: _type = cons; { if (!data) setData(new MTPDchannelForbidden()); @@ -22191,6 +23297,7 @@ inline void MTPchat::write(mtpBuffer &to) const { v.vphoto.write(to); v.vdate.write(to); v.vversion.write(to); + if (v.has_restriction_reason()) v.vrestriction_reason.write(to); } break; case mtpc_channelForbidden: { const MTPDchannelForbidden &v(c_channelForbidden()); @@ -22229,8 +23336,8 @@ inline MTPchat MTP_chat(MTPint _flags, MTPint _id, const MTPstring &_title, cons inline MTPchat MTP_chatForbidden(MTPint _id, const MTPstring &_title) { return MTPchat(new MTPDchatForbidden(_id, _title)); } -inline MTPchat MTP_channel(MTPint _flags, MTPint _id, const MTPlong &_access_hash, const MTPstring &_title, const MTPstring &_username, const MTPChatPhoto &_photo, MTPint _date, MTPint _version) { - return MTPchat(new MTPDchannel(_flags, _id, _access_hash, _title, _username, _photo, _date, _version)); +inline MTPchat MTP_channel(MTPint _flags, MTPint _id, const MTPlong &_access_hash, const MTPstring &_title, const MTPstring &_username, const MTPChatPhoto &_photo, MTPint _date, MTPint _version, const MTPstring &_restriction_reason) { + return MTPchat(new MTPDchannel(_flags, _id, _access_hash, _title, _username, _photo, _date, _version, _restriction_reason)); } inline MTPchat MTP_channelForbidden(MTPint _id, const MTPlong &_access_hash, const MTPstring &_title) { return MTPchat(new MTPDchannelForbidden(_id, _access_hash, _title)); @@ -22557,7 +23664,7 @@ inline uint32 MTPmessage::innerLength() const { } case mtpc_message: { const MTPDmessage &v(c_message()); - return v.vflags.innerLength() + v.vid.innerLength() + (v.has_from_id() ? v.vfrom_id.innerLength() : 0) + v.vto_id.innerLength() + (v.has_fwd_from_id() ? v.vfwd_from_id.innerLength() : 0) + (v.has_fwd_date() ? v.vfwd_date.innerLength() : 0) + (v.has_reply_to_msg_id() ? v.vreply_to_msg_id.innerLength() : 0) + v.vdate.innerLength() + v.vmessage.innerLength() + (v.has_media() ? v.vmedia.innerLength() : 0) + (v.has_reply_markup() ? v.vreply_markup.innerLength() : 0) + (v.has_entities() ? v.ventities.innerLength() : 0) + (v.has_views() ? v.vviews.innerLength() : 0); + return v.vflags.innerLength() + v.vid.innerLength() + (v.has_from_id() ? v.vfrom_id.innerLength() : 0) + v.vto_id.innerLength() + (v.has_fwd_from_id() ? v.vfwd_from_id.innerLength() : 0) + (v.has_fwd_date() ? v.vfwd_date.innerLength() : 0) + (v.has_via_bot_id() ? v.vvia_bot_id.innerLength() : 0) + (v.has_reply_to_msg_id() ? v.vreply_to_msg_id.innerLength() : 0) + v.vdate.innerLength() + v.vmessage.innerLength() + (v.has_media() ? v.vmedia.innerLength() : 0) + (v.has_reply_markup() ? v.vreply_markup.innerLength() : 0) + (v.has_entities() ? v.ventities.innerLength() : 0) + (v.has_views() ? v.vviews.innerLength() : 0); } case mtpc_messageService: { const MTPDmessageService &v(c_messageService()); @@ -22587,6 +23694,7 @@ inline void MTPmessage::read(const mtpPrime *&from, const mtpPrime *end, mtpType v.vto_id.read(from, end); if (v.has_fwd_from_id()) { v.vfwd_from_id.read(from, end); } else { v.vfwd_from_id = MTPPeer(); } if (v.has_fwd_date()) { v.vfwd_date.read(from, end); } else { v.vfwd_date = MTPint(); } + if (v.has_via_bot_id()) { v.vvia_bot_id.read(from, end); } else { v.vvia_bot_id = MTPint(); } if (v.has_reply_to_msg_id()) { v.vreply_to_msg_id.read(from, end); } else { v.vreply_to_msg_id = MTPint(); } v.vdate.read(from, end); v.vmessage.read(from, end); @@ -22622,6 +23730,7 @@ inline void MTPmessage::write(mtpBuffer &to) const { v.vto_id.write(to); if (v.has_fwd_from_id()) v.vfwd_from_id.write(to); if (v.has_fwd_date()) v.vfwd_date.write(to); + if (v.has_via_bot_id()) v.vvia_bot_id.write(to); if (v.has_reply_to_msg_id()) v.vreply_to_msg_id.write(to); v.vdate.write(to); v.vmessage.write(to); @@ -22658,8 +23767,8 @@ inline MTPmessage::MTPmessage(MTPDmessageService *_data) : mtpDataOwner(_data), inline MTPmessage MTP_messageEmpty(MTPint _id) { return MTPmessage(new MTPDmessageEmpty(_id)); } -inline MTPmessage MTP_message(MTPint _flags, MTPint _id, MTPint _from_id, const MTPPeer &_to_id, const MTPPeer &_fwd_from_id, MTPint _fwd_date, MTPint _reply_to_msg_id, MTPint _date, const MTPstring &_message, const MTPMessageMedia &_media, const MTPReplyMarkup &_reply_markup, const MTPVector &_entities, MTPint _views) { - return MTPmessage(new MTPDmessage(_flags, _id, _from_id, _to_id, _fwd_from_id, _fwd_date, _reply_to_msg_id, _date, _message, _media, _reply_markup, _entities, _views)); +inline MTPmessage MTP_message(MTPint _flags, MTPint _id, MTPint _from_id, const MTPPeer &_to_id, const MTPPeer &_fwd_from_id, MTPint _fwd_date, MTPint _via_bot_id, MTPint _reply_to_msg_id, MTPint _date, const MTPstring &_message, const MTPMessageMedia &_media, const MTPReplyMarkup &_reply_markup, const MTPVector &_entities, MTPint _views) { + return MTPmessage(new MTPDmessage(_flags, _id, _from_id, _to_id, _fwd_from_id, _fwd_date, _via_bot_id, _reply_to_msg_id, _date, _message, _media, _reply_markup, _entities, _views)); } inline MTPmessage MTP_messageService(MTPint _flags, MTPint _id, MTPint _from_id, const MTPPeer &_to_id, MTPint _date, const MTPMessageAction &_action) { return MTPmessage(new MTPDmessageService(_flags, _id, _from_id, _to_id, _date, _action)); @@ -22685,7 +23794,7 @@ inline uint32 MTPmessageMedia::innerLength() const { } case mtpc_messageMediaDocument: { const MTPDmessageMediaDocument &v(c_messageMediaDocument()); - return v.vdocument.innerLength(); + return v.vdocument.innerLength() + v.vcaption.innerLength(); } case mtpc_messageMediaAudio: { const MTPDmessageMediaAudio &v(c_messageMediaAudio()); @@ -22740,6 +23849,7 @@ inline void MTPmessageMedia::read(const mtpPrime *&from, const mtpPrime *end, mt if (!data) setData(new MTPDmessageMediaDocument()); MTPDmessageMediaDocument &v(_messageMediaDocument()); v.vdocument.read(from, end); + v.vcaption.read(from, end); } break; case mtpc_messageMediaAudio: _type = cons; { if (!data) setData(new MTPDmessageMediaAudio()); @@ -22789,6 +23899,7 @@ inline void MTPmessageMedia::write(mtpBuffer &to) const { case mtpc_messageMediaDocument: { const MTPDmessageMediaDocument &v(c_messageMediaDocument()); v.vdocument.write(to); + v.vcaption.write(to); } break; case mtpc_messageMediaAudio: { const MTPDmessageMediaAudio &v(c_messageMediaAudio()); @@ -22857,8 +23968,8 @@ inline MTPmessageMedia MTP_messageMediaContact(const MTPstring &_phone_number, c inline MTPmessageMedia MTP_messageMediaUnsupported() { return MTPmessageMedia(mtpc_messageMediaUnsupported); } -inline MTPmessageMedia MTP_messageMediaDocument(const MTPDocument &_document) { - return MTPmessageMedia(new MTPDmessageMediaDocument(_document)); +inline MTPmessageMedia MTP_messageMediaDocument(const MTPDocument &_document, const MTPstring &_caption) { + return MTPmessageMedia(new MTPDmessageMediaDocument(_document, _caption)); } inline MTPmessageMedia MTP_messageMediaAudio(const MTPAudio &_audio) { return MTPmessageMedia(new MTPDmessageMediaAudio(_audio)); @@ -24648,6 +25759,7 @@ inline void MTPmessagesFilter::read(const mtpPrime *&from, const mtpPrime *end, case mtpc_inputMessagesFilterAudio: _type = cons; break; case mtpc_inputMessagesFilterAudioDocuments: _type = cons; break; case mtpc_inputMessagesFilterUrl: _type = cons; break; + case mtpc_inputMessagesFilterGif: _type = cons; break; default: throw mtpErrorUnexpected(cons, "MTPmessagesFilter"); } } @@ -24666,6 +25778,7 @@ inline MTPmessagesFilter::MTPmessagesFilter(mtpTypeId type) : _type(type) { case mtpc_inputMessagesFilterAudio: break; case mtpc_inputMessagesFilterAudioDocuments: break; case mtpc_inputMessagesFilterUrl: break; + case mtpc_inputMessagesFilterGif: break; default: throw mtpErrorBadTypeId(type, "MTPmessagesFilter"); } } @@ -24696,6 +25809,9 @@ inline MTPmessagesFilter MTP_inputMessagesFilterAudioDocuments() { inline MTPmessagesFilter MTP_inputMessagesFilterUrl() { return MTPmessagesFilter(mtpc_inputMessagesFilterUrl); } +inline MTPmessagesFilter MTP_inputMessagesFilterGif() { + return MTPmessagesFilter(mtpc_inputMessagesFilterGif); +} inline uint32 MTPupdate::innerLength() const { switch (_type) { @@ -24855,6 +25971,10 @@ inline uint32 MTPupdate::innerLength() const { const MTPDupdateStickerSetsOrder &v(c_updateStickerSetsOrder()); return v.vorder.innerLength(); } + case mtpc_updateBotInlineQuery: { + const MTPDupdateBotInlineQuery &v(c_updateBotInlineQuery()); + return v.vquery_id.innerLength() + v.vuser_id.innerLength() + v.vquery.innerLength() + v.voffset.innerLength(); + } } return 0; } @@ -25123,6 +26243,15 @@ inline void MTPupdate::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeI v.vorder.read(from, end); } break; case mtpc_updateStickerSets: _type = cons; break; + case mtpc_updateSavedGifs: _type = cons; break; + case mtpc_updateBotInlineQuery: _type = cons; { + if (!data) setData(new MTPDupdateBotInlineQuery()); + MTPDupdateBotInlineQuery &v(_updateBotInlineQuery()); + v.vquery_id.read(from, end); + v.vuser_id.read(from, end); + v.vquery.read(from, end); + v.voffset.read(from, end); + } break; default: throw mtpErrorUnexpected(cons, "MTPupdate"); } } @@ -25346,6 +26475,13 @@ inline void MTPupdate::write(mtpBuffer &to) const { const MTPDupdateStickerSetsOrder &v(c_updateStickerSetsOrder()); v.vorder.write(to); } break; + case mtpc_updateBotInlineQuery: { + const MTPDupdateBotInlineQuery &v(c_updateBotInlineQuery()); + v.vquery_id.write(to); + v.vuser_id.write(to); + v.vquery.write(to); + v.voffset.write(to); + } break; } } inline MTPupdate::MTPupdate(mtpTypeId type) : mtpDataOwner(0), _type(type) { @@ -25390,6 +26526,8 @@ inline MTPupdate::MTPupdate(mtpTypeId type) : mtpDataOwner(0), _type(type) { case mtpc_updateNewStickerSet: setData(new MTPDupdateNewStickerSet()); break; case mtpc_updateStickerSetsOrder: setData(new MTPDupdateStickerSetsOrder()); break; case mtpc_updateStickerSets: break; + case mtpc_updateSavedGifs: break; + case mtpc_updateBotInlineQuery: setData(new MTPDupdateBotInlineQuery()); break; default: throw mtpErrorBadTypeId(type, "MTPupdate"); } } @@ -25471,6 +26609,8 @@ inline MTPupdate::MTPupdate(MTPDupdateNewStickerSet *_data) : mtpDataOwner(_data } inline MTPupdate::MTPupdate(MTPDupdateStickerSetsOrder *_data) : mtpDataOwner(_data), _type(mtpc_updateStickerSetsOrder) { } +inline MTPupdate::MTPupdate(MTPDupdateBotInlineQuery *_data) : mtpDataOwner(_data), _type(mtpc_updateBotInlineQuery) { +} inline MTPupdate MTP_updateNewMessage(const MTPMessage &_message, MTPint _pts, MTPint _pts_count) { return MTPupdate(new MTPDupdateNewMessage(_message, _pts, _pts_count)); } @@ -25591,6 +26731,12 @@ inline MTPupdate MTP_updateStickerSetsOrder(const MTPVector &_order) { inline MTPupdate MTP_updateStickerSets() { return MTPupdate(mtpc_updateStickerSets); } +inline MTPupdate MTP_updateSavedGifs() { + return MTPupdate(mtpc_updateSavedGifs); +} +inline MTPupdate MTP_updateBotInlineQuery(const MTPlong &_query_id, MTPint _user_id, const MTPstring &_query, const MTPstring &_offset) { + return MTPupdate(new MTPDupdateBotInlineQuery(_query_id, _user_id, _query, _offset)); +} inline MTPupdates_state::MTPupdates_state() : mtpDataOwner(new MTPDupdates_state()) { } @@ -25735,11 +26881,11 @@ inline uint32 MTPupdates::innerLength() const { switch (_type) { case mtpc_updateShortMessage: { const MTPDupdateShortMessage &v(c_updateShortMessage()); - return v.vflags.innerLength() + v.vid.innerLength() + v.vuser_id.innerLength() + v.vmessage.innerLength() + v.vpts.innerLength() + v.vpts_count.innerLength() + v.vdate.innerLength() + (v.has_fwd_from_id() ? v.vfwd_from_id.innerLength() : 0) + (v.has_fwd_date() ? v.vfwd_date.innerLength() : 0) + (v.has_reply_to_msg_id() ? v.vreply_to_msg_id.innerLength() : 0) + (v.has_entities() ? v.ventities.innerLength() : 0); + return v.vflags.innerLength() + v.vid.innerLength() + v.vuser_id.innerLength() + v.vmessage.innerLength() + v.vpts.innerLength() + v.vpts_count.innerLength() + v.vdate.innerLength() + (v.has_fwd_from_id() ? v.vfwd_from_id.innerLength() : 0) + (v.has_fwd_date() ? v.vfwd_date.innerLength() : 0) + (v.has_via_bot_id() ? v.vvia_bot_id.innerLength() : 0) + (v.has_reply_to_msg_id() ? v.vreply_to_msg_id.innerLength() : 0) + (v.has_entities() ? v.ventities.innerLength() : 0); } case mtpc_updateShortChatMessage: { const MTPDupdateShortChatMessage &v(c_updateShortChatMessage()); - return v.vflags.innerLength() + v.vid.innerLength() + v.vfrom_id.innerLength() + v.vchat_id.innerLength() + v.vmessage.innerLength() + v.vpts.innerLength() + v.vpts_count.innerLength() + v.vdate.innerLength() + (v.has_fwd_from_id() ? v.vfwd_from_id.innerLength() : 0) + (v.has_fwd_date() ? v.vfwd_date.innerLength() : 0) + (v.has_reply_to_msg_id() ? v.vreply_to_msg_id.innerLength() : 0) + (v.has_entities() ? v.ventities.innerLength() : 0); + return v.vflags.innerLength() + v.vid.innerLength() + v.vfrom_id.innerLength() + v.vchat_id.innerLength() + v.vmessage.innerLength() + v.vpts.innerLength() + v.vpts_count.innerLength() + v.vdate.innerLength() + (v.has_fwd_from_id() ? v.vfwd_from_id.innerLength() : 0) + (v.has_fwd_date() ? v.vfwd_date.innerLength() : 0) + (v.has_via_bot_id() ? v.vvia_bot_id.innerLength() : 0) + (v.has_reply_to_msg_id() ? v.vreply_to_msg_id.innerLength() : 0) + (v.has_entities() ? v.ventities.innerLength() : 0); } case mtpc_updateShort: { const MTPDupdateShort &v(c_updateShort()); @@ -25780,6 +26926,7 @@ inline void MTPupdates::read(const mtpPrime *&from, const mtpPrime *end, mtpType v.vdate.read(from, end); if (v.has_fwd_from_id()) { v.vfwd_from_id.read(from, end); } else { v.vfwd_from_id = MTPPeer(); } if (v.has_fwd_date()) { v.vfwd_date.read(from, end); } else { v.vfwd_date = MTPint(); } + if (v.has_via_bot_id()) { v.vvia_bot_id.read(from, end); } else { v.vvia_bot_id = MTPint(); } if (v.has_reply_to_msg_id()) { v.vreply_to_msg_id.read(from, end); } else { v.vreply_to_msg_id = MTPint(); } if (v.has_entities()) { v.ventities.read(from, end); } else { v.ventities = MTPVector(); } } break; @@ -25796,6 +26943,7 @@ inline void MTPupdates::read(const mtpPrime *&from, const mtpPrime *end, mtpType v.vdate.read(from, end); if (v.has_fwd_from_id()) { v.vfwd_from_id.read(from, end); } else { v.vfwd_from_id = MTPPeer(); } if (v.has_fwd_date()) { v.vfwd_date.read(from, end); } else { v.vfwd_date = MTPint(); } + if (v.has_via_bot_id()) { v.vvia_bot_id.read(from, end); } else { v.vvia_bot_id = MTPint(); } if (v.has_reply_to_msg_id()) { v.vreply_to_msg_id.read(from, end); } else { v.vreply_to_msg_id = MTPint(); } if (v.has_entities()) { v.ventities.read(from, end); } else { v.ventities = MTPVector(); } } break; @@ -25851,6 +26999,7 @@ inline void MTPupdates::write(mtpBuffer &to) const { v.vdate.write(to); if (v.has_fwd_from_id()) v.vfwd_from_id.write(to); if (v.has_fwd_date()) v.vfwd_date.write(to); + if (v.has_via_bot_id()) v.vvia_bot_id.write(to); if (v.has_reply_to_msg_id()) v.vreply_to_msg_id.write(to); if (v.has_entities()) v.ventities.write(to); } break; @@ -25866,6 +27015,7 @@ inline void MTPupdates::write(mtpBuffer &to) const { v.vdate.write(to); if (v.has_fwd_from_id()) v.vfwd_from_id.write(to); if (v.has_fwd_date()) v.vfwd_date.write(to); + if (v.has_via_bot_id()) v.vvia_bot_id.write(to); if (v.has_reply_to_msg_id()) v.vreply_to_msg_id.write(to); if (v.has_entities()) v.ventities.write(to); } break; @@ -25930,11 +27080,11 @@ inline MTPupdates::MTPupdates(MTPDupdateShortSentMessage *_data) : mtpDataOwner( inline MTPupdates MTP_updatesTooLong() { return MTPupdates(mtpc_updatesTooLong); } -inline MTPupdates MTP_updateShortMessage(MTPint _flags, MTPint _id, MTPint _user_id, const MTPstring &_message, MTPint _pts, MTPint _pts_count, MTPint _date, const MTPPeer &_fwd_from_id, MTPint _fwd_date, MTPint _reply_to_msg_id, const MTPVector &_entities) { - return MTPupdates(new MTPDupdateShortMessage(_flags, _id, _user_id, _message, _pts, _pts_count, _date, _fwd_from_id, _fwd_date, _reply_to_msg_id, _entities)); +inline MTPupdates MTP_updateShortMessage(MTPint _flags, MTPint _id, MTPint _user_id, const MTPstring &_message, MTPint _pts, MTPint _pts_count, MTPint _date, const MTPPeer &_fwd_from_id, MTPint _fwd_date, MTPint _via_bot_id, MTPint _reply_to_msg_id, const MTPVector &_entities) { + return MTPupdates(new MTPDupdateShortMessage(_flags, _id, _user_id, _message, _pts, _pts_count, _date, _fwd_from_id, _fwd_date, _via_bot_id, _reply_to_msg_id, _entities)); } -inline MTPupdates MTP_updateShortChatMessage(MTPint _flags, MTPint _id, MTPint _from_id, MTPint _chat_id, const MTPstring &_message, MTPint _pts, MTPint _pts_count, MTPint _date, const MTPPeer &_fwd_from_id, MTPint _fwd_date, MTPint _reply_to_msg_id, const MTPVector &_entities) { - return MTPupdates(new MTPDupdateShortChatMessage(_flags, _id, _from_id, _chat_id, _message, _pts, _pts_count, _date, _fwd_from_id, _fwd_date, _reply_to_msg_id, _entities)); +inline MTPupdates MTP_updateShortChatMessage(MTPint _flags, MTPint _id, MTPint _from_id, MTPint _chat_id, const MTPstring &_message, MTPint _pts, MTPint _pts_count, MTPint _date, const MTPPeer &_fwd_from_id, MTPint _fwd_date, MTPint _via_bot_id, MTPint _reply_to_msg_id, const MTPVector &_entities) { + return MTPupdates(new MTPDupdateShortChatMessage(_flags, _id, _from_id, _chat_id, _message, _pts, _pts_count, _date, _fwd_from_id, _fwd_date, _via_bot_id, _reply_to_msg_id, _entities)); } inline MTPupdates MTP_updateShort(const MTPUpdate &_update, MTPint _date) { return MTPupdates(new MTPDupdateShort(_update, _date)); @@ -26116,7 +27266,7 @@ inline MTPconfig::MTPconfig() : mtpDataOwner(new MTPDconfig()) { inline uint32 MTPconfig::innerLength() const { const MTPDconfig &v(c_config()); - return v.vdate.innerLength() + v.vexpires.innerLength() + v.vtest_mode.innerLength() + v.vthis_dc.innerLength() + v.vdc_options.innerLength() + v.vchat_size_max.innerLength() + v.vmegagroup_size_max.innerLength() + v.vforwarded_count_max.innerLength() + v.vonline_update_period_ms.innerLength() + v.voffline_blur_timeout_ms.innerLength() + v.voffline_idle_timeout_ms.innerLength() + v.vonline_cloud_timeout_ms.innerLength() + v.vnotify_cloud_delay_ms.innerLength() + v.vnotify_default_delay_ms.innerLength() + v.vchat_big_size.innerLength() + v.vpush_chat_period_ms.innerLength() + v.vpush_chat_limit.innerLength() + v.vdisabled_features.innerLength(); + return v.vdate.innerLength() + v.vexpires.innerLength() + v.vtest_mode.innerLength() + v.vthis_dc.innerLength() + v.vdc_options.innerLength() + v.vchat_size_max.innerLength() + v.vmegagroup_size_max.innerLength() + v.vforwarded_count_max.innerLength() + v.vonline_update_period_ms.innerLength() + v.voffline_blur_timeout_ms.innerLength() + v.voffline_idle_timeout_ms.innerLength() + v.vonline_cloud_timeout_ms.innerLength() + v.vnotify_cloud_delay_ms.innerLength() + v.vnotify_default_delay_ms.innerLength() + v.vchat_big_size.innerLength() + v.vpush_chat_period_ms.innerLength() + v.vpush_chat_limit.innerLength() + v.vsaved_gifs_limit.innerLength() + v.vdisabled_features.innerLength(); } inline mtpTypeId MTPconfig::type() const { return mtpc_config; @@ -26143,6 +27293,7 @@ inline void MTPconfig::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeI v.vchat_big_size.read(from, end); v.vpush_chat_period_ms.read(from, end); v.vpush_chat_limit.read(from, end); + v.vsaved_gifs_limit.read(from, end); v.vdisabled_features.read(from, end); } inline void MTPconfig::write(mtpBuffer &to) const { @@ -26164,12 +27315,13 @@ inline void MTPconfig::write(mtpBuffer &to) const { v.vchat_big_size.write(to); v.vpush_chat_period_ms.write(to); v.vpush_chat_limit.write(to); + v.vsaved_gifs_limit.write(to); v.vdisabled_features.write(to); } inline MTPconfig::MTPconfig(MTPDconfig *_data) : mtpDataOwner(_data) { } -inline MTPconfig MTP_config(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, const MTPVector &_disabled_features) { - return MTPconfig(new MTPDconfig(_date, _expires, _test_mode, _this_dc, _dc_options, _chat_size_max, _megagroup_size_max, _forwarded_count_max, _online_update_period_ms, _offline_blur_timeout_ms, _offline_idle_timeout_ms, _online_cloud_timeout_ms, _notify_cloud_delay_ms, _notify_default_delay_ms, _chat_big_size, _push_chat_period_ms, _push_chat_limit, _disabled_features)); +inline MTPconfig MTP_config(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, MTPint _saved_gifs_limit, const MTPVector &_disabled_features) { + return MTPconfig(new MTPDconfig(_date, _expires, _test_mode, _this_dc, _dc_options, _chat_size_max, _megagroup_size_max, _forwarded_count_max, _online_update_period_ms, _offline_blur_timeout_ms, _offline_idle_timeout_ms, _online_cloud_timeout_ms, _notify_cloud_delay_ms, _notify_default_delay_ms, _chat_big_size, _push_chat_period_ms, _push_chat_limit, _saved_gifs_limit, _disabled_features)); } inline MTPnearestDc::MTPnearestDc() : mtpDataOwner(new MTPDnearestDc()) { @@ -29793,6 +30945,499 @@ inline MTPhelp_termsOfService MTP_help_termsOfService(const MTPstring &_text) { return MTPhelp_termsOfService(new MTPDhelp_termsOfService(_text)); } +inline uint32 MTPfoundGif::innerLength() const { + switch (_type) { + case mtpc_foundGif: { + const MTPDfoundGif &v(c_foundGif()); + return v.vurl.innerLength() + v.vthumb_url.innerLength() + v.vcontent_url.innerLength() + v.vcontent_type.innerLength() + v.vw.innerLength() + v.vh.innerLength(); + } + case mtpc_foundGifCached: { + const MTPDfoundGifCached &v(c_foundGifCached()); + return v.vurl.innerLength() + v.vphoto.innerLength() + v.vdocument.innerLength(); + } + } + return 0; +} +inline mtpTypeId MTPfoundGif::type() const { + if (!_type) throw mtpErrorUninitialized(); + return _type; +} +inline void MTPfoundGif::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) { + if (cons != _type) setData(0); + switch (cons) { + case mtpc_foundGif: _type = cons; { + if (!data) setData(new MTPDfoundGif()); + MTPDfoundGif &v(_foundGif()); + v.vurl.read(from, end); + v.vthumb_url.read(from, end); + v.vcontent_url.read(from, end); + v.vcontent_type.read(from, end); + v.vw.read(from, end); + v.vh.read(from, end); + } break; + case mtpc_foundGifCached: _type = cons; { + if (!data) setData(new MTPDfoundGifCached()); + MTPDfoundGifCached &v(_foundGifCached()); + v.vurl.read(from, end); + v.vphoto.read(from, end); + v.vdocument.read(from, end); + } break; + default: throw mtpErrorUnexpected(cons, "MTPfoundGif"); + } +} +inline void MTPfoundGif::write(mtpBuffer &to) const { + switch (_type) { + case mtpc_foundGif: { + const MTPDfoundGif &v(c_foundGif()); + v.vurl.write(to); + v.vthumb_url.write(to); + v.vcontent_url.write(to); + v.vcontent_type.write(to); + v.vw.write(to); + v.vh.write(to); + } break; + case mtpc_foundGifCached: { + const MTPDfoundGifCached &v(c_foundGifCached()); + v.vurl.write(to); + v.vphoto.write(to); + v.vdocument.write(to); + } break; + } +} +inline MTPfoundGif::MTPfoundGif(mtpTypeId type) : mtpDataOwner(0), _type(type) { + switch (type) { + case mtpc_foundGif: setData(new MTPDfoundGif()); break; + case mtpc_foundGifCached: setData(new MTPDfoundGifCached()); break; + default: throw mtpErrorBadTypeId(type, "MTPfoundGif"); + } +} +inline MTPfoundGif::MTPfoundGif(MTPDfoundGif *_data) : mtpDataOwner(_data), _type(mtpc_foundGif) { +} +inline MTPfoundGif::MTPfoundGif(MTPDfoundGifCached *_data) : mtpDataOwner(_data), _type(mtpc_foundGifCached) { +} +inline MTPfoundGif MTP_foundGif(const MTPstring &_url, const MTPstring &_thumb_url, const MTPstring &_content_url, const MTPstring &_content_type, MTPint _w, MTPint _h) { + return MTPfoundGif(new MTPDfoundGif(_url, _thumb_url, _content_url, _content_type, _w, _h)); +} +inline MTPfoundGif MTP_foundGifCached(const MTPstring &_url, const MTPPhoto &_photo, const MTPDocument &_document) { + return MTPfoundGif(new MTPDfoundGifCached(_url, _photo, _document)); +} + +inline MTPmessages_foundGifs::MTPmessages_foundGifs() : mtpDataOwner(new MTPDmessages_foundGifs()) { +} + +inline uint32 MTPmessages_foundGifs::innerLength() const { + const MTPDmessages_foundGifs &v(c_messages_foundGifs()); + return v.vnext_offset.innerLength() + v.vresults.innerLength(); +} +inline mtpTypeId MTPmessages_foundGifs::type() const { + return mtpc_messages_foundGifs; +} +inline void MTPmessages_foundGifs::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) { + if (cons != mtpc_messages_foundGifs) throw mtpErrorUnexpected(cons, "MTPmessages_foundGifs"); + + if (!data) setData(new MTPDmessages_foundGifs()); + MTPDmessages_foundGifs &v(_messages_foundGifs()); + v.vnext_offset.read(from, end); + v.vresults.read(from, end); +} +inline void MTPmessages_foundGifs::write(mtpBuffer &to) const { + const MTPDmessages_foundGifs &v(c_messages_foundGifs()); + v.vnext_offset.write(to); + v.vresults.write(to); +} +inline MTPmessages_foundGifs::MTPmessages_foundGifs(MTPDmessages_foundGifs *_data) : mtpDataOwner(_data) { +} +inline MTPmessages_foundGifs MTP_messages_foundGifs(MTPint _next_offset, const MTPVector &_results) { + return MTPmessages_foundGifs(new MTPDmessages_foundGifs(_next_offset, _results)); +} + +inline uint32 MTPmessages_savedGifs::innerLength() const { + switch (_type) { + case mtpc_messages_savedGifs: { + const MTPDmessages_savedGifs &v(c_messages_savedGifs()); + return v.vhash.innerLength() + v.vgifs.innerLength(); + } + } + return 0; +} +inline mtpTypeId MTPmessages_savedGifs::type() const { + if (!_type) throw mtpErrorUninitialized(); + return _type; +} +inline void MTPmessages_savedGifs::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) { + if (cons != _type) setData(0); + switch (cons) { + case mtpc_messages_savedGifsNotModified: _type = cons; break; + case mtpc_messages_savedGifs: _type = cons; { + if (!data) setData(new MTPDmessages_savedGifs()); + MTPDmessages_savedGifs &v(_messages_savedGifs()); + v.vhash.read(from, end); + v.vgifs.read(from, end); + } break; + default: throw mtpErrorUnexpected(cons, "MTPmessages_savedGifs"); + } +} +inline void MTPmessages_savedGifs::write(mtpBuffer &to) const { + switch (_type) { + case mtpc_messages_savedGifs: { + const MTPDmessages_savedGifs &v(c_messages_savedGifs()); + v.vhash.write(to); + v.vgifs.write(to); + } break; + } +} +inline MTPmessages_savedGifs::MTPmessages_savedGifs(mtpTypeId type) : mtpDataOwner(0), _type(type) { + switch (type) { + case mtpc_messages_savedGifsNotModified: break; + case mtpc_messages_savedGifs: setData(new MTPDmessages_savedGifs()); break; + default: throw mtpErrorBadTypeId(type, "MTPmessages_savedGifs"); + } +} +inline MTPmessages_savedGifs::MTPmessages_savedGifs(MTPDmessages_savedGifs *_data) : mtpDataOwner(_data), _type(mtpc_messages_savedGifs) { +} +inline MTPmessages_savedGifs MTP_messages_savedGifsNotModified() { + return MTPmessages_savedGifs(mtpc_messages_savedGifsNotModified); +} +inline MTPmessages_savedGifs MTP_messages_savedGifs(MTPint _hash, const MTPVector &_gifs) { + return MTPmessages_savedGifs(new MTPDmessages_savedGifs(_hash, _gifs)); +} + +inline uint32 MTPinputBotInlineMessage::innerLength() const { + switch (_type) { + case mtpc_inputBotInlineMessageMediaAuto: { + const MTPDinputBotInlineMessageMediaAuto &v(c_inputBotInlineMessageMediaAuto()); + return v.vcaption.innerLength(); + } + case mtpc_inputBotInlineMessageText: { + const MTPDinputBotInlineMessageText &v(c_inputBotInlineMessageText()); + return v.vflags.innerLength() + v.vmessage.innerLength() + (v.has_entities() ? v.ventities.innerLength() : 0); + } + } + return 0; +} +inline mtpTypeId MTPinputBotInlineMessage::type() const { + if (!_type) throw mtpErrorUninitialized(); + return _type; +} +inline void MTPinputBotInlineMessage::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) { + if (cons != _type) setData(0); + switch (cons) { + case mtpc_inputBotInlineMessageMediaAuto: _type = cons; { + if (!data) setData(new MTPDinputBotInlineMessageMediaAuto()); + MTPDinputBotInlineMessageMediaAuto &v(_inputBotInlineMessageMediaAuto()); + v.vcaption.read(from, end); + } break; + case mtpc_inputBotInlineMessageText: _type = cons; { + if (!data) setData(new MTPDinputBotInlineMessageText()); + MTPDinputBotInlineMessageText &v(_inputBotInlineMessageText()); + v.vflags.read(from, end); + v.vmessage.read(from, end); + if (v.has_entities()) { v.ventities.read(from, end); } else { v.ventities = MTPVector(); } + } break; + default: throw mtpErrorUnexpected(cons, "MTPinputBotInlineMessage"); + } +} +inline void MTPinputBotInlineMessage::write(mtpBuffer &to) const { + switch (_type) { + case mtpc_inputBotInlineMessageMediaAuto: { + const MTPDinputBotInlineMessageMediaAuto &v(c_inputBotInlineMessageMediaAuto()); + v.vcaption.write(to); + } break; + case mtpc_inputBotInlineMessageText: { + const MTPDinputBotInlineMessageText &v(c_inputBotInlineMessageText()); + v.vflags.write(to); + v.vmessage.write(to); + if (v.has_entities()) v.ventities.write(to); + } break; + } +} +inline MTPinputBotInlineMessage::MTPinputBotInlineMessage(mtpTypeId type) : mtpDataOwner(0), _type(type) { + switch (type) { + case mtpc_inputBotInlineMessageMediaAuto: setData(new MTPDinputBotInlineMessageMediaAuto()); break; + case mtpc_inputBotInlineMessageText: setData(new MTPDinputBotInlineMessageText()); break; + default: throw mtpErrorBadTypeId(type, "MTPinputBotInlineMessage"); + } +} +inline MTPinputBotInlineMessage::MTPinputBotInlineMessage(MTPDinputBotInlineMessageMediaAuto *_data) : mtpDataOwner(_data), _type(mtpc_inputBotInlineMessageMediaAuto) { +} +inline MTPinputBotInlineMessage::MTPinputBotInlineMessage(MTPDinputBotInlineMessageText *_data) : mtpDataOwner(_data), _type(mtpc_inputBotInlineMessageText) { +} +inline MTPinputBotInlineMessage MTP_inputBotInlineMessageMediaAuto(const MTPstring &_caption) { + return MTPinputBotInlineMessage(new MTPDinputBotInlineMessageMediaAuto(_caption)); +} +inline MTPinputBotInlineMessage MTP_inputBotInlineMessageText(MTPint _flags, const MTPstring &_message, const MTPVector &_entities) { + return MTPinputBotInlineMessage(new MTPDinputBotInlineMessageText(_flags, _message, _entities)); +} + +inline MTPinputBotInlineResult::MTPinputBotInlineResult() : mtpDataOwner(new MTPDinputBotInlineResult()) { +} + +inline uint32 MTPinputBotInlineResult::innerLength() const { + const MTPDinputBotInlineResult &v(c_inputBotInlineResult()); + return v.vflags.innerLength() + v.vid.innerLength() + v.vtype.innerLength() + (v.has_title() ? v.vtitle.innerLength() : 0) + (v.has_description() ? v.vdescription.innerLength() : 0) + (v.has_url() ? v.vurl.innerLength() : 0) + (v.has_thumb_url() ? v.vthumb_url.innerLength() : 0) + (v.has_content_url() ? v.vcontent_url.innerLength() : 0) + (v.has_content_type() ? v.vcontent_type.innerLength() : 0) + (v.has_w() ? v.vw.innerLength() : 0) + (v.has_h() ? v.vh.innerLength() : 0) + (v.has_duration() ? v.vduration.innerLength() : 0) + v.vsend_message.innerLength(); +} +inline mtpTypeId MTPinputBotInlineResult::type() const { + return mtpc_inputBotInlineResult; +} +inline void MTPinputBotInlineResult::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) { + if (cons != mtpc_inputBotInlineResult) throw mtpErrorUnexpected(cons, "MTPinputBotInlineResult"); + + if (!data) setData(new MTPDinputBotInlineResult()); + MTPDinputBotInlineResult &v(_inputBotInlineResult()); + v.vflags.read(from, end); + v.vid.read(from, end); + v.vtype.read(from, end); + if (v.has_title()) { v.vtitle.read(from, end); } else { v.vtitle = MTPstring(); } + if (v.has_description()) { v.vdescription.read(from, end); } else { v.vdescription = MTPstring(); } + if (v.has_url()) { v.vurl.read(from, end); } else { v.vurl = MTPstring(); } + if (v.has_thumb_url()) { v.vthumb_url.read(from, end); } else { v.vthumb_url = MTPstring(); } + if (v.has_content_url()) { v.vcontent_url.read(from, end); } else { v.vcontent_url = MTPstring(); } + if (v.has_content_type()) { v.vcontent_type.read(from, end); } else { v.vcontent_type = MTPstring(); } + if (v.has_w()) { v.vw.read(from, end); } else { v.vw = MTPint(); } + if (v.has_h()) { v.vh.read(from, end); } else { v.vh = MTPint(); } + if (v.has_duration()) { v.vduration.read(from, end); } else { v.vduration = MTPint(); } + v.vsend_message.read(from, end); +} +inline void MTPinputBotInlineResult::write(mtpBuffer &to) const { + const MTPDinputBotInlineResult &v(c_inputBotInlineResult()); + v.vflags.write(to); + v.vid.write(to); + v.vtype.write(to); + if (v.has_title()) v.vtitle.write(to); + if (v.has_description()) v.vdescription.write(to); + if (v.has_url()) v.vurl.write(to); + if (v.has_thumb_url()) v.vthumb_url.write(to); + if (v.has_content_url()) v.vcontent_url.write(to); + if (v.has_content_type()) v.vcontent_type.write(to); + if (v.has_w()) v.vw.write(to); + if (v.has_h()) v.vh.write(to); + if (v.has_duration()) v.vduration.write(to); + v.vsend_message.write(to); +} +inline MTPinputBotInlineResult::MTPinputBotInlineResult(MTPDinputBotInlineResult *_data) : mtpDataOwner(_data) { +} +inline MTPinputBotInlineResult MTP_inputBotInlineResult(MTPint _flags, const MTPstring &_id, const MTPstring &_type, const MTPstring &_title, const MTPstring &_description, const MTPstring &_url, const MTPstring &_thumb_url, const MTPstring &_content_url, const MTPstring &_content_type, MTPint _w, MTPint _h, MTPint _duration, const MTPInputBotInlineMessage &_send_message) { + return MTPinputBotInlineResult(new MTPDinputBotInlineResult(_flags, _id, _type, _title, _description, _url, _thumb_url, _content_url, _content_type, _w, _h, _duration, _send_message)); +} + +inline uint32 MTPbotInlineMessage::innerLength() const { + switch (_type) { + case mtpc_botInlineMessageMediaAuto: { + const MTPDbotInlineMessageMediaAuto &v(c_botInlineMessageMediaAuto()); + return v.vcaption.innerLength(); + } + case mtpc_botInlineMessageText: { + const MTPDbotInlineMessageText &v(c_botInlineMessageText()); + return v.vflags.innerLength() + v.vmessage.innerLength() + (v.has_entities() ? v.ventities.innerLength() : 0); + } + } + return 0; +} +inline mtpTypeId MTPbotInlineMessage::type() const { + if (!_type) throw mtpErrorUninitialized(); + return _type; +} +inline void MTPbotInlineMessage::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) { + if (cons != _type) setData(0); + switch (cons) { + case mtpc_botInlineMessageMediaAuto: _type = cons; { + if (!data) setData(new MTPDbotInlineMessageMediaAuto()); + MTPDbotInlineMessageMediaAuto &v(_botInlineMessageMediaAuto()); + v.vcaption.read(from, end); + } break; + case mtpc_botInlineMessageText: _type = cons; { + if (!data) setData(new MTPDbotInlineMessageText()); + MTPDbotInlineMessageText &v(_botInlineMessageText()); + v.vflags.read(from, end); + v.vmessage.read(from, end); + if (v.has_entities()) { v.ventities.read(from, end); } else { v.ventities = MTPVector(); } + } break; + default: throw mtpErrorUnexpected(cons, "MTPbotInlineMessage"); + } +} +inline void MTPbotInlineMessage::write(mtpBuffer &to) const { + switch (_type) { + case mtpc_botInlineMessageMediaAuto: { + const MTPDbotInlineMessageMediaAuto &v(c_botInlineMessageMediaAuto()); + v.vcaption.write(to); + } break; + case mtpc_botInlineMessageText: { + const MTPDbotInlineMessageText &v(c_botInlineMessageText()); + v.vflags.write(to); + v.vmessage.write(to); + if (v.has_entities()) v.ventities.write(to); + } break; + } +} +inline MTPbotInlineMessage::MTPbotInlineMessage(mtpTypeId type) : mtpDataOwner(0), _type(type) { + switch (type) { + case mtpc_botInlineMessageMediaAuto: setData(new MTPDbotInlineMessageMediaAuto()); break; + case mtpc_botInlineMessageText: setData(new MTPDbotInlineMessageText()); break; + default: throw mtpErrorBadTypeId(type, "MTPbotInlineMessage"); + } +} +inline MTPbotInlineMessage::MTPbotInlineMessage(MTPDbotInlineMessageMediaAuto *_data) : mtpDataOwner(_data), _type(mtpc_botInlineMessageMediaAuto) { +} +inline MTPbotInlineMessage::MTPbotInlineMessage(MTPDbotInlineMessageText *_data) : mtpDataOwner(_data), _type(mtpc_botInlineMessageText) { +} +inline MTPbotInlineMessage MTP_botInlineMessageMediaAuto(const MTPstring &_caption) { + return MTPbotInlineMessage(new MTPDbotInlineMessageMediaAuto(_caption)); +} +inline MTPbotInlineMessage MTP_botInlineMessageText(MTPint _flags, const MTPstring &_message, const MTPVector &_entities) { + return MTPbotInlineMessage(new MTPDbotInlineMessageText(_flags, _message, _entities)); +} + +inline uint32 MTPbotInlineResult::innerLength() const { + switch (_type) { + case mtpc_botInlineMediaResultDocument: { + const MTPDbotInlineMediaResultDocument &v(c_botInlineMediaResultDocument()); + return v.vid.innerLength() + v.vtype.innerLength() + v.vdocument.innerLength() + v.vsend_message.innerLength(); + } + case mtpc_botInlineMediaResultPhoto: { + const MTPDbotInlineMediaResultPhoto &v(c_botInlineMediaResultPhoto()); + return v.vid.innerLength() + v.vtype.innerLength() + v.vphoto.innerLength() + v.vsend_message.innerLength(); + } + case mtpc_botInlineResult: { + const MTPDbotInlineResult &v(c_botInlineResult()); + return v.vflags.innerLength() + v.vid.innerLength() + v.vtype.innerLength() + (v.has_title() ? v.vtitle.innerLength() : 0) + (v.has_description() ? v.vdescription.innerLength() : 0) + (v.has_url() ? v.vurl.innerLength() : 0) + (v.has_thumb_url() ? v.vthumb_url.innerLength() : 0) + (v.has_content_url() ? v.vcontent_url.innerLength() : 0) + (v.has_content_type() ? v.vcontent_type.innerLength() : 0) + (v.has_w() ? v.vw.innerLength() : 0) + (v.has_h() ? v.vh.innerLength() : 0) + (v.has_duration() ? v.vduration.innerLength() : 0) + v.vsend_message.innerLength(); + } + } + return 0; +} +inline mtpTypeId MTPbotInlineResult::type() const { + if (!_type) throw mtpErrorUninitialized(); + return _type; +} +inline void MTPbotInlineResult::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) { + if (cons != _type) setData(0); + switch (cons) { + case mtpc_botInlineMediaResultDocument: _type = cons; { + if (!data) setData(new MTPDbotInlineMediaResultDocument()); + MTPDbotInlineMediaResultDocument &v(_botInlineMediaResultDocument()); + v.vid.read(from, end); + v.vtype.read(from, end); + v.vdocument.read(from, end); + v.vsend_message.read(from, end); + } break; + case mtpc_botInlineMediaResultPhoto: _type = cons; { + if (!data) setData(new MTPDbotInlineMediaResultPhoto()); + MTPDbotInlineMediaResultPhoto &v(_botInlineMediaResultPhoto()); + v.vid.read(from, end); + v.vtype.read(from, end); + v.vphoto.read(from, end); + v.vsend_message.read(from, end); + } break; + case mtpc_botInlineResult: _type = cons; { + if (!data) setData(new MTPDbotInlineResult()); + MTPDbotInlineResult &v(_botInlineResult()); + v.vflags.read(from, end); + v.vid.read(from, end); + v.vtype.read(from, end); + if (v.has_title()) { v.vtitle.read(from, end); } else { v.vtitle = MTPstring(); } + if (v.has_description()) { v.vdescription.read(from, end); } else { v.vdescription = MTPstring(); } + if (v.has_url()) { v.vurl.read(from, end); } else { v.vurl = MTPstring(); } + if (v.has_thumb_url()) { v.vthumb_url.read(from, end); } else { v.vthumb_url = MTPstring(); } + if (v.has_content_url()) { v.vcontent_url.read(from, end); } else { v.vcontent_url = MTPstring(); } + if (v.has_content_type()) { v.vcontent_type.read(from, end); } else { v.vcontent_type = MTPstring(); } + if (v.has_w()) { v.vw.read(from, end); } else { v.vw = MTPint(); } + if (v.has_h()) { v.vh.read(from, end); } else { v.vh = MTPint(); } + if (v.has_duration()) { v.vduration.read(from, end); } else { v.vduration = MTPint(); } + v.vsend_message.read(from, end); + } break; + default: throw mtpErrorUnexpected(cons, "MTPbotInlineResult"); + } +} +inline void MTPbotInlineResult::write(mtpBuffer &to) const { + switch (_type) { + case mtpc_botInlineMediaResultDocument: { + const MTPDbotInlineMediaResultDocument &v(c_botInlineMediaResultDocument()); + v.vid.write(to); + v.vtype.write(to); + v.vdocument.write(to); + v.vsend_message.write(to); + } break; + case mtpc_botInlineMediaResultPhoto: { + const MTPDbotInlineMediaResultPhoto &v(c_botInlineMediaResultPhoto()); + v.vid.write(to); + v.vtype.write(to); + v.vphoto.write(to); + v.vsend_message.write(to); + } break; + case mtpc_botInlineResult: { + const MTPDbotInlineResult &v(c_botInlineResult()); + v.vflags.write(to); + v.vid.write(to); + v.vtype.write(to); + if (v.has_title()) v.vtitle.write(to); + if (v.has_description()) v.vdescription.write(to); + if (v.has_url()) v.vurl.write(to); + if (v.has_thumb_url()) v.vthumb_url.write(to); + if (v.has_content_url()) v.vcontent_url.write(to); + if (v.has_content_type()) v.vcontent_type.write(to); + if (v.has_w()) v.vw.write(to); + if (v.has_h()) v.vh.write(to); + if (v.has_duration()) v.vduration.write(to); + v.vsend_message.write(to); + } break; + } +} +inline MTPbotInlineResult::MTPbotInlineResult(mtpTypeId type) : mtpDataOwner(0), _type(type) { + switch (type) { + case mtpc_botInlineMediaResultDocument: setData(new MTPDbotInlineMediaResultDocument()); break; + case mtpc_botInlineMediaResultPhoto: setData(new MTPDbotInlineMediaResultPhoto()); break; + case mtpc_botInlineResult: setData(new MTPDbotInlineResult()); break; + default: throw mtpErrorBadTypeId(type, "MTPbotInlineResult"); + } +} +inline MTPbotInlineResult::MTPbotInlineResult(MTPDbotInlineMediaResultDocument *_data) : mtpDataOwner(_data), _type(mtpc_botInlineMediaResultDocument) { +} +inline MTPbotInlineResult::MTPbotInlineResult(MTPDbotInlineMediaResultPhoto *_data) : mtpDataOwner(_data), _type(mtpc_botInlineMediaResultPhoto) { +} +inline MTPbotInlineResult::MTPbotInlineResult(MTPDbotInlineResult *_data) : mtpDataOwner(_data), _type(mtpc_botInlineResult) { +} +inline MTPbotInlineResult MTP_botInlineMediaResultDocument(const MTPstring &_id, const MTPstring &_type, const MTPDocument &_document, const MTPBotInlineMessage &_send_message) { + return MTPbotInlineResult(new MTPDbotInlineMediaResultDocument(_id, _type, _document, _send_message)); +} +inline MTPbotInlineResult MTP_botInlineMediaResultPhoto(const MTPstring &_id, const MTPstring &_type, const MTPPhoto &_photo, const MTPBotInlineMessage &_send_message) { + return MTPbotInlineResult(new MTPDbotInlineMediaResultPhoto(_id, _type, _photo, _send_message)); +} +inline MTPbotInlineResult MTP_botInlineResult(MTPint _flags, const MTPstring &_id, const MTPstring &_type, const MTPstring &_title, const MTPstring &_description, const MTPstring &_url, const MTPstring &_thumb_url, const MTPstring &_content_url, const MTPstring &_content_type, MTPint _w, MTPint _h, MTPint _duration, const MTPBotInlineMessage &_send_message) { + return MTPbotInlineResult(new MTPDbotInlineResult(_flags, _id, _type, _title, _description, _url, _thumb_url, _content_url, _content_type, _w, _h, _duration, _send_message)); +} + +inline MTPmessages_botResults::MTPmessages_botResults() : mtpDataOwner(new MTPDmessages_botResults()) { +} + +inline uint32 MTPmessages_botResults::innerLength() const { + const MTPDmessages_botResults &v(c_messages_botResults()); + return v.vflags.innerLength() + v.vquery_id.innerLength() + (v.has_next_offset() ? v.vnext_offset.innerLength() : 0) + v.vresults.innerLength(); +} +inline mtpTypeId MTPmessages_botResults::type() const { + return mtpc_messages_botResults; +} +inline void MTPmessages_botResults::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) { + if (cons != mtpc_messages_botResults) throw mtpErrorUnexpected(cons, "MTPmessages_botResults"); + + if (!data) setData(new MTPDmessages_botResults()); + MTPDmessages_botResults &v(_messages_botResults()); + v.vflags.read(from, end); + v.vquery_id.read(from, end); + if (v.has_next_offset()) { v.vnext_offset.read(from, end); } else { v.vnext_offset = MTPstring(); } + v.vresults.read(from, end); +} +inline void MTPmessages_botResults::write(mtpBuffer &to) const { + const MTPDmessages_botResults &v(c_messages_botResults()); + v.vflags.write(to); + v.vquery_id.write(to); + if (v.has_next_offset()) v.vnext_offset.write(to); + v.vresults.write(to); +} +inline MTPmessages_botResults::MTPmessages_botResults(MTPDmessages_botResults *_data) : mtpDataOwner(_data) { +} +inline MTPmessages_botResults MTP_messages_botResults(MTPint _flags, const MTPlong &_query_id, const MTPstring &_next_offset, const MTPVector &_results) { + return MTPmessages_botResults(new MTPDmessages_botResults(_flags, _query_id, _next_offset, _results)); +} + // Human-readable text serialization #if (defined _DEBUG || defined _WITH_DEBUG) diff --git a/Telegram/SourceFiles/mtproto/scheme.tl b/Telegram/SourceFiles/mtproto/scheme.tl index db9481ad04..64bfa2eaa8 100644 --- a/Telegram/SourceFiles/mtproto/scheme.tl +++ b/Telegram/SourceFiles/mtproto/scheme.tl @@ -155,10 +155,11 @@ inputMediaUploadedThumbVideo#7780ddf9 file:InputFile thumb:InputFile duration:in inputMediaVideo#936a4ebd id:InputVideo caption:string = InputMedia; inputMediaUploadedAudio#4e498cab file:InputFile duration:int mime_type:string = InputMedia; inputMediaAudio#89938781 id:InputAudio = InputMedia; -inputMediaUploadedDocument#ffe76b78 file:InputFile mime_type:string attributes:Vector = InputMedia; -inputMediaUploadedThumbDocument#41481486 file:InputFile thumb:InputFile mime_type:string attributes:Vector = InputMedia; -inputMediaDocument#d184e841 id:InputDocument = InputMedia; +inputMediaUploadedDocument#1d89306d file:InputFile mime_type:string attributes:Vector caption:string = InputMedia; +inputMediaUploadedThumbDocument#ad613491 file:InputFile thumb:InputFile mime_type:string attributes:Vector caption:string = InputMedia; +inputMediaDocument#1a77f29c id:InputDocument caption:string = InputMedia; inputMediaVenue#2827a81a geo_point:InputGeoPoint title:string address:string provider:string venue_id:string = InputMedia; +inputMediaGifExternal#4843b0fd url:string q:string = InputMedia; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; inputChatUploadedPhoto#94254732 file:InputFile crop:InputPhotoCrop = InputChatPhoto; @@ -203,7 +204,7 @@ fileLocationUnavailable#7c596b46 volume_id:long local_id:int secret:long = FileL fileLocation#53d69076 dc_id:int volume_id:long local_id:int secret:long = FileLocation; userEmpty#200250ba id:int = User; -user#22e49072 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int = User; +user#d10d979a flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?string bot_inline_placeholder:flags.19?string = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; userProfilePhoto#d559d8c8 photo_id:long photo_small:FileLocation photo_big:FileLocation = UserProfilePhoto; @@ -218,7 +219,7 @@ userStatusLastMonth#77ebc742 = UserStatus; chatEmpty#9ba2d800 id:int = Chat; chat#d91cdd54 flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true admins_enabled:flags.3?true admin:flags.4?true deactivated:flags.5?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel = Chat; chatForbidden#7328bdb id:int title:string = Chat; -channel#678e9587 flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true editor:flags.3?true moderator:flags.4?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true id:int access_hash:long title:string username:flags.6?string photo:ChatPhoto date:int version:int = Chat; +channel#4b1b7506 flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true editor:flags.3?true moderator:flags.4?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true id:int access_hash:long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?string = Chat; channelForbidden#2d85832c id:int access_hash:long title:string = Chat; chatFull#2e02a614 id:int participants:ChatParticipants chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector = ChatFull; @@ -235,7 +236,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#6153276a photo_small:FileLocation photo_big:FileLocation = ChatPhoto; messageEmpty#83e5de54 id:int = Message; -message#5ba66c13 flags:# unread:flags.0?true out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true id:int from_id:flags.8?int to_id:Peer fwd_from_id:flags.2?Peer fwd_date:flags.2?int reply_to_msg_id:flags.3?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int = Message; +message#c992e15c flags:# unread:flags.0?true out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true id:int from_id:flags.8?int to_id:Peer fwd_from_id:flags.2?Peer fwd_date:flags.2?int via_bot_id:flags.11?int reply_to_msg_id:flags.3?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int = Message; messageService#c06b9607 flags:# unread:flags.0?true out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true id:int from_id:flags.8?int to_id:Peer date:int action:MessageAction = Message; messageMediaEmpty#3ded6320 = MessageMedia; @@ -244,7 +245,7 @@ messageMediaVideo#5bcf1675 video:Video caption:string = MessageMedia; messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia; messageMediaContact#5e7d2f39 phone_number:string first_name:string last_name:string user_id:int = MessageMedia; messageMediaUnsupported#9f84f49e = MessageMedia; -messageMediaDocument#2fda2204 document:Document = MessageMedia; +messageMediaDocument#f3e02ea8 document:Document caption:string = MessageMedia; messageMediaAudio#c6b68300 audio:Audio = MessageMedia; messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia; messageMediaVenue#7912b71f geo:GeoPoint title:string address:string provider:string venue_id:string = MessageMedia; @@ -356,6 +357,7 @@ inputMessagesFilterDocument#9eddf188 = MessagesFilter; inputMessagesFilterAudio#cfc87522 = MessagesFilter; inputMessagesFilterAudioDocuments#5afbf764 = MessagesFilter; inputMessagesFilterUrl#7ef0dd87 = MessagesFilter; +inputMessagesFilterGif#ffc86587 = MessagesFilter; updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = Update; updateMessageID#4e90bfd6 id:int random_id:long = Update; @@ -397,6 +399,8 @@ updateChatParticipantAdmin#b6901959 chat_id:int user_id:int is_admin:Bool versio updateNewStickerSet#688a30aa stickerset:messages.StickerSet = Update; updateStickerSetsOrder#f0dfb451 order:Vector = Update; updateStickerSets#43ae3dec = Update; +updateSavedGifs#9375341e = Update; +updateBotInlineQuery#c01eea08 query_id:long user_id:int query:string offset:string = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -405,8 +409,8 @@ updates.difference#f49ca0 new_messages:Vector new_encrypted_messages:Ve updates.differenceSlice#a8fb1981 new_messages:Vector new_encrypted_messages:Vector other_updates:Vector chats:Vector users:Vector intermediate_state:updates.State = updates.Difference; updatesTooLong#e317af7e = Updates; -updateShortMessage#f7d91a46 flags:# unread:flags.0?true out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true id:int user_id:int message:string pts:int pts_count:int date:int fwd_from_id:flags.2?Peer fwd_date:flags.2?int reply_to_msg_id:flags.3?int entities:flags.7?Vector = Updates; -updateShortChatMessage#cac7fdd2 flags:# unread:flags.0?true out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true id:int from_id:int chat_id:int message:string pts:int pts_count:int date:int fwd_from_id:flags.2?Peer fwd_date:flags.2?int reply_to_msg_id:flags.3?int entities:flags.7?Vector = Updates; +updateShortMessage#13e4deaa flags:# unread:flags.0?true out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true id:int user_id:int message:string pts:int pts_count:int date:int fwd_from_id:flags.2?Peer fwd_date:flags.2?int via_bot_id:flags.11?int reply_to_msg_id:flags.3?int entities:flags.7?Vector = Updates; +updateShortChatMessage#248afa62 flags:# unread:flags.0?true out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true id:int from_id:int chat_id:int message:string pts:int pts_count:int date:int fwd_from_id:flags.2?Peer fwd_date:flags.2?int via_bot_id:flags.11?int reply_to_msg_id:flags.3?int entities:flags.7?Vector = Updates; updateShort#78d4dec1 update:Update date:int = Updates; updatesCombined#725b04c3 updates:Vector users:Vector chats:Vector date:int seq_start:int seq:int = Updates; updates#74ae4240 updates:Vector users:Vector chats:Vector date:int seq:int = Updates; @@ -421,7 +425,7 @@ upload.file#96a18d5 type:storage.FileType mtime:int bytes:bytes = upload.File; dcOption#5d8c6cc flags:# ipv6:flags.0?true media_only:flags.1?true id:int ip_address:string port:int = DcOption; -config#6cb6e65e date:int expires:int test_mode:Bool this_dc:int dc_options:Vector chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int chat_big_size:int push_chat_period_ms:int push_chat_limit:int disabled_features:Vector = Config; +config#6bbc5f8 date:int expires:int test_mode:Bool this_dc:int dc_options:Vector chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int chat_big_size:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int disabled_features:Vector = Config; nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc; @@ -635,6 +639,28 @@ channels.channelParticipant#d0d9b163 participant:ChannelParticipant users:Vector help.termsOfService#f1ee3e90 text:string = help.TermsOfService; +foundGif#162ecc1f url:string thumb_url:string content_url:string content_type:string w:int h:int = FoundGif; +foundGifCached#9c750409 url:string photo:Photo document:Document = FoundGif; + +messages.foundGifs#450a1c0a next_offset:int results:Vector = messages.FoundGifs; + +messages.savedGifsNotModified#e8025ca2 = messages.SavedGifs; +messages.savedGifs#2e0709a5 hash:int gifs:Vector = messages.SavedGifs; + +inputBotInlineMessageMediaAuto#2e43e587 caption:string = InputBotInlineMessage; +inputBotInlineMessageText#adf0df71 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector = InputBotInlineMessage; + +inputBotInlineResult#2cbbe15a flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb_url:flags.4?string content_url:flags.5?string content_type:flags.5?string w:flags.6?int h:flags.6?int duration:flags.7?int send_message:InputBotInlineMessage = InputBotInlineResult; + +botInlineMessageMediaAuto#fc56e87d caption:string = BotInlineMessage; +botInlineMessageText#a56197a9 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector = BotInlineMessage; + +botInlineMediaResultDocument#f897d33e id:string type:string document:Document send_message:BotInlineMessage = BotInlineResult; +botInlineMediaResultPhoto#c5528587 id:string type:string photo:Photo send_message:BotInlineMessage = BotInlineResult; +botInlineResult#9bebaeb9 flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb_url:flags.4?string content_url:flags.5?string content_type:flags.5?string w:flags.6?int h:flags.6?int duration:flags.7?int send_message:BotInlineMessage = BotInlineResult; + +messages.botResults#1170b0a3 flags:# gallery:flags.0?true query_id:long next_offset:flags.1?string results:Vector = messages.BotResults; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -751,6 +777,13 @@ messages.editChatAdmin#a9e69f2e chat_id:int user_id:InputUser is_admin:Bool = Bo messages.migrateChat#15a3b8e3 chat_id:int = Updates; messages.searchGlobal#9e3cacb0 q:string offset_date:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; messages.reorderStickerSets#9fcfbc30 order:Vector = Bool; +messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document; +messages.searchGifs#bf9a776b q:string offset:int = messages.FoundGifs; +messages.getSavedGifs#83bf3d52 hash:int = messages.SavedGifs; +messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool; +messages.getInlineBotResults#9324600d bot:InputUser query:string offset:string = messages.BotResults; +messages.setInlineBotResults#3f23ec12 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector cache_time:int next_offset:flags.2?string = Bool; +messages.sendInlineBotResult#b16e06fe flags:# broadcast:flags.4?true peer:InputPeer reply_to_msg_id:flags.0?int random_id:long query_id:long id:string = Updates; updates.getState#edd4882a = updates.State; updates.getDifference#a041495 pts:int date:int qts:int = updates.Difference; diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index 78c6af5ebc..4f8f6dd03c 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -30,110 +30,6 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "application.h" #include "gui/filedialog.h" -OverviewInner::CachedLink::CachedLink(HistoryItem *item) : titleWidth(0), page(0), pixw(0), pixh(0), text(st::msgMinWidth) { - QString msgText = item->originalText(); - EntitiesInText msgEntities = item->originalEntities(); - - int32 from = 0, till = msgText.size(), lnk = msgEntities.size(); - for (int32 i = 0; i < lnk; ++i) { - if (msgEntities[i].type != EntityInTextUrl && msgEntities[i].type != EntityInTextCustomUrl && msgEntities[i].type != EntityInTextEmail) { - continue; - } - QString url = msgEntities[i].text, text = msgText.mid(msgEntities[i].offset, msgEntities[i].length); - urls.push_back(Link(url.isEmpty() ? text : url, text)); - } - while (lnk > 0 && till > from) { - --lnk; - if (msgEntities[lnk].type != EntityInTextUrl && msgEntities[lnk].type != EntityInTextCustomUrl && msgEntities[lnk].type != EntityInTextEmail) { - ++lnk; - break; - } - int32 afterLinkStart = msgEntities[lnk].offset + msgEntities[lnk].length; - if (till > afterLinkStart) { - if (!QRegularExpression(qsl("^[,.\\s_=+\\-;:`'\"\\(\\)\\[\\]\\{\\}<>*&^%\\$#@!\\\\/]+$")).match(msgText.mid(afterLinkStart, till - afterLinkStart)).hasMatch()) { - ++lnk; - break; - } - } - till = msgEntities[lnk].offset; - } - if (!lnk) { - if (QRegularExpression(qsl("^[,.\\s\\-;:`'\"\\(\\)\\[\\]\\{\\}<>*&^%\\$#@!\\\\/]+$")).match(msgText.mid(from, till - from)).hasMatch()) { - till = from; - } - } - - HistoryMedia *media = item->getMedia(); - page = (media && media->type() == MediaTypeWebPage) ? static_cast(media)->webpage() : 0; - if (from >= till && page) { - msgText = page->description; - from = 0; - till = msgText.size(); - } - if (till > from) { - TextParseOptions opts = { TextParseMultiline, int32(st::linksMaxWidth), 3 * st::msgFont->height, Qt::LayoutDirectionAuto }; - text.setText(st::msgFont, msgText.mid(from, till - from), opts); - } - int32 tw = 0, th = 0; - if (page && page->photo) { - if (!page->photo->full->loaded()) page->photo->medium->load(false, false); - - tw = convertScale(page->photo->medium->width()); - th = convertScale(page->photo->medium->height()); - } else if (page && page->doc) { - if (!page->doc->thumb->loaded()) page->doc->thumb->load(false, false); - - tw = convertScale(page->doc->thumb->width()); - th = convertScale(page->doc->thumb->height()); - } - if (tw > st::dlgPhotoSize) { - if (th > tw) { - th = th * st::dlgPhotoSize / tw; - tw = st::dlgPhotoSize; - } else if (th > st::dlgPhotoSize) { - tw = tw * st::dlgPhotoSize / th; - th = st::dlgPhotoSize; - } - } - pixw = tw; - pixh = th; - if (pixw < 1) pixw = 1; - if (pixh < 1) pixh = 1; - - if (page) { - title = page->title; - } - QVector parts = (page ? page->url : (urls.isEmpty() ? QString() : urls.at(0).url)).splitRef('/'); - if (!parts.isEmpty()) { - QStringRef domain = parts.at(0); - if (parts.size() > 2 && domain.endsWith(':') && parts.at(1).isEmpty()) { // http:// and others - domain = parts.at(2); - } - - parts = domain.split('@').back().split('.'); - if (parts.size() > 1) { - letter = parts.at(parts.size() - 2).at(0).toUpper(); - if (title.isEmpty()) { - title.reserve(parts.at(parts.size() - 2).size()); - title.append(letter).append(parts.at(parts.size() - 2).mid(1)); - } - } - } - titleWidth = st::webPageTitleFont->width(title); -} - -int32 OverviewInner::CachedLink::countHeight(int32 w) { - int32 result = 0; - if (!title.isEmpty()) { - result += st::webPageTitleFont->height; - } - if (!text.isEmpty()) { - result += qMin(3 * st::msgFont->height, text.countHeight(w - st::dlgPhotoSize - st::dlgPhotoPadding)); - } - result += urls.size() * st::msgFont->height; - return qMax(result, int(st::dlgPhotoSize)) + st::linksMargin * 2 + st::linksBorder; -} - // flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html OverviewInner::OverviewInner(OverviewWidget *overview, ScrollArea *scroll, PeerData *peer, MediaOverviewType type) : QWidget(0) @@ -143,20 +39,18 @@ OverviewInner::OverviewInner(OverviewWidget *overview, ScrollArea *scroll, PeerD , _resizeSkip(0) , _peer(peer->migrateTo() ? peer->migrateTo() : peer) , _type(type) +, _reversed(_type != OverviewDocuments && _type != OverviewLinks) , _migrated(_peer->migrateFrom() ? App::history(_peer->migrateFrom()->id) : 0) , _history(App::history(_peer->id)) , _channel(peerToChannel(_peer->id)) -, _photosInRow(1) -, _photosToAdd(0) , _selMode(false) -, _audioLeft(st::msgMargin.left()) -, _audioWidth(st::msgMinWidth) -, _audioHeight(st::mediaPadding.top() + st::mediaThumbSize + st::mediaPadding.bottom()) -, _linksLeft(st::linksSearchMargin.left()) -, _linksWidth(st::msgMinWidth) +, _rowsLeft(0) +, _rowWidth(st::msgMinWidth) , _search(this, st::dlgFilter, lang(lng_dlg_filter)) , _cancelSearch(this, st::btnCancelSearch) , _itemsToBeLoaded(LinksOverviewPerPage * 2) +, _photosInRow(1) +, _photosToAdd(0) , _inSearch(false) , _searchFull(false) , _searchFullMigrated(false) @@ -167,7 +61,8 @@ OverviewInner::OverviewInner(OverviewWidget *overview, ScrollArea *scroll, PeerD , _width(st::wndMinWidth) , _height(0) , _minHeight(0) -, _addToY(0) +, _marginTop(0) +, _marginBottom(0) , _cursor(style::cur_default) , _cursorState(HistoryDefaultCursorState) , _dragAction(NoDrag) @@ -176,8 +71,6 @@ OverviewInner::OverviewInner(OverviewWidget *overview, ScrollArea *scroll, PeerD , _dragItemIndex(-1) , _mousedItem(0) , _mousedItemIndex(-1) -, _lnkOverIndex(0) -, _lnkDownIndex(0) , _dragWasInactive(false) , _dragSelFrom(0) , _dragSelTo(0) @@ -194,7 +87,7 @@ OverviewInner::OverviewInner(OverviewWidget *overview, ScrollArea *scroll, PeerD , _menu(0) { connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update())); - resize(_width, height()); + resize(_width, st::wndMinHeight); App::contextItem(0); @@ -217,7 +110,7 @@ OverviewInner::OverviewInner(OverviewWidget *overview, ScrollArea *scroll, PeerD connect(&_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchMessages())); _cancelSearch.hide(); - if (_type == OverviewLinks) { + if (_type == OverviewLinks || _type == OverviewDocuments) { _search.show(); } else { _search.hide(); @@ -294,26 +187,12 @@ int32 OverviewInner::migratedIndexSkip() const { void OverviewInner::fixItemIndex(int32 ¤t, MsgId msgId) const { if (!msgId) { current = -1; - } else if (_type == OverviewPhotos || _type == OverviewAudioDocuments) { - History *history = itemMigrated(msgId) ? _migrated : _history; - int32 l = history->overview[_type].size(), indexskip = migratedIndexSkip(); - int32 index = (current >= 0 && history == _history) ? (current - indexskip) : current; - MsgId findMsgId = (history == _history ? 1 : -1) * msgId; - if (current < 0 || current >= l || history->overview[_type][current] != findMsgId) { - current = -1; - for (int32 i = 0; i < l; ++i) { - if (history->overview[_type][i] == findMsgId) { - current = i + (history == _history ? indexskip : 0); - break; - } - } - } } else { int32 l = _items.size(); - if (current < 0 || current >= l || _items[current].msgid != msgId) { + if (current < 0 || current >= l || complexMsgId(_items.at(current)->getItem()) != msgId) { current = -1; for (int32 i = 0; i < l; ++i) { - if (_items[i].msgid == msgId) { + if (complexMsgId(_items.at(i)->getItem()) == msgId) { current = i; break; } @@ -421,80 +300,29 @@ bool OverviewInner::searchFailed(SearchRequestType type, const RPCError &error, return true; } -OverviewInner::CachedLink *OverviewInner::cachedLink(HistoryItem *item) { - MsgId msgId = (item->history() == _migrated) ? -item->id : item->id; - CachedLinks::const_iterator i = _links.constFind(msgId); - if (i == _links.cend()) i = _links.insert(msgId, new CachedLink(item)); - return i.value(); -} - -QString OverviewInner::urlByIndex(MsgId msgid, int32 index, int32 lnkIndex, bool *fullShown) const { - fixItemIndex(index, msgid); - if (index < 0 || !_items[index].link) return QString(); - - if (lnkIndex < 0) { - if (fullShown) *fullShown = (_items[index].link->urls.size() == 1) && (_items[index].link->urls.at(0).width <= _linksWidth - (st::dlgPhotoSize + st::dlgPhotoPadding)); - if (_items[index].link->page) { - return _items[index].link->page->url; - } else if (!_items[index].link->urls.isEmpty()) { - return _items[index].link->urls.at(0).url; - } - } else if (lnkIndex > 0 && lnkIndex <= _items[index].link->urls.size()) { - if (fullShown) *fullShown = _items[index].link->urls.at(lnkIndex - 1).width <= _linksWidth - (st::dlgPhotoSize + st::dlgPhotoPadding); - return _items[index].link->urls.at(lnkIndex - 1).url; - } - return QString(); -} - -bool OverviewInner::urlIsEmail(const QString &url) const { - int32 at = url.indexOf('@'), slash = url.indexOf('/'); - return (at > 0) && (slash < 0 || slash > at); -} - bool OverviewInner::itemHasPoint(MsgId msgId, int32 index, int32 x, int32 y) const { fixItemIndex(index, msgId); if (index < 0) return false; - if (_type == OverviewPhotos) { - if (x >= 0 && x < _vsize && y >= 0 && y < _vsize) { - return true; - } - } else if (_type == OverviewAudioDocuments) { - if (x >= _audioLeft && x < _audioLeft + _audioWidth && y >= 0 && y < _audioHeight) { - return true; - } - } else if (_type == OverviewLinks) { - if (x >= _linksLeft && x < _linksLeft + _linksWidth && y >= 0 && y < itemHeight(msgId, index)) { + if (_type == OverviewPhotos || _type == OverviewVideos) { + if (x >= 0 && x < _rowWidth && y >= 0 && y < _rowWidth) { return true; } } else { - HistoryItem *item = App::histItemById(itemChannel(msgId), itemMsgId(msgId)); - HistoryMedia *media = item ? item->getMedia(true) : 0; - if (media) { - int32 w = _width - st::msgMargin.left() - st::msgMargin.right(); - bool out = item->out(), fromChannel = item->fromChannel(), outbg = out && !fromChannel; - int32 mw = media->maxWidth(), left = (fromChannel ? (st::msgMargin.left() + st::msgMargin.left()) / 2 : (out ? st::msgMargin.right() : st::msgMargin.left())) + ((mw < w) ? (fromChannel ? 0 : (out ? w - mw : 0)) : 0); - if (item->displayFromPhoto()) { - left += st::msgPhotoSkip; - } - return media->hasPoint(x - left, y - st::msgMargin.top(), item, w); + if (x >= _rowsLeft && x < _rowsLeft + _rowWidth && y >= 0 && y < itemHeight(msgId, index)) { + return true; } } return false; } int32 OverviewInner::itemHeight(MsgId msgId, int32 index) const { - if (_type == OverviewPhotos) { - return _vsize; - } else if (_type == OverviewAudioDocuments) { - return _audioHeight; + if (_type == OverviewPhotos || _type == OverviewVideos) { + return _rowWidth; } fixItemIndex(index, msgId); - if (_type == OverviewLinks) { - return (index < 0) ? 0 : ((index + 1 < _items.size() ? _items[index + 1].y : (_height - _addToY)) - _items[index].y); - } - return (index < 0) ? 0 : (_items[index].y - (index > 0 ? _items[index - 1].y : 0)); + return (index < 0) ? 0 : _items.at(index)->height(); } void OverviewInner::moveToNextItem(MsgId &msgId, int32 &index, MsgId upTo, int32 delta) const { @@ -506,47 +334,30 @@ void OverviewInner::moveToNextItem(MsgId &msgId, int32 &index, MsgId upTo, int32 } index += delta; - if (_type == OverviewPhotos || _type == OverviewAudioDocuments) { - int32 indexskip = migratedIndexSkip(); - if (index < 0 || index >= indexskip + _history->overview[_type].size()) { - msgId = 0; - index = -1; - } else { - msgId = (index >= indexskip) ? _history->overview[_type][index - indexskip] : (-_migrated->overview[_type][index]); - } + while (index >= 0 && index < _items.size() && !_items.at(index)->toLayoutMediaItem()) { + index += (delta > 0) ? 1 : -1; + } + if (index < 0 || index >= _items.size()) { + msgId = 0; + index = -1; } else { - while (index >= 0 && index < _items.size() && !_items[index].msgid) { - index += (delta > 0) ? 1 : -1; - } - if (index < 0 || index >= _items.size()) { - msgId = 0; - index = -1; - } else { - msgId = _items[index].msgid; - } + msgId = complexMsgId(_items.at(index)->getItem()); } } -void OverviewInner::updateMsg(HistoryItem *item) { - if (App::main() && item) { - App::main()->msgUpdated(item); - } -} - -void OverviewInner::updateMsg(MsgId itemId, int32 itemIndex) { +void OverviewInner::repaintItem(MsgId itemId, int32 itemIndex) { fixItemIndex(itemIndex, itemId); if (itemIndex >= 0) { - if (_type == OverviewPhotos) { + if (_type == OverviewPhotos || _type == OverviewVideos) { + int32 shownAtIndex = _items.size() - itemIndex - 1; float64 w = (float64(_width - st::overviewPhotoSkip) / _photosInRow); - int32 vsize = (_vsize + st::overviewPhotoSkip); - int32 row = (_photosToAdd + itemIndex) / _photosInRow, col = (_photosToAdd + itemIndex) % _photosInRow; - update(int32(col * w), _addToY + int32(row * vsize), qCeil(w), vsize); - } else if (_type == OverviewAudioDocuments) { - update(_audioLeft, _addToY + int32(itemIndex * _audioHeight), _audioWidth, _audioHeight); - } else if (_type == OverviewLinks) { - update(_linksLeft, _addToY + _items[itemIndex].y, _linksWidth, itemHeight(itemId, itemIndex)); + int32 vsize = (_rowWidth + st::overviewPhotoSkip); + int32 row = (_photosToAdd + shownAtIndex) / _photosInRow, col = (_photosToAdd + shownAtIndex) % _photosInRow; + update(int32(col * w), _marginTop + int32(row * vsize), qCeil(w), vsize); } else { - update(0, _addToY + _height - _items[itemIndex].y, _width, itemHeight(itemId, itemIndex)); + int32 top = _items.at(itemIndex)->getOverviewItemInfo()->top(); + if (_reversed) top = _height - top; + update(_rowsLeft, _marginTop + top, _rowWidth, _items.at(itemIndex)->height()); } } } @@ -665,15 +476,10 @@ void OverviewInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton but if (button != Qt::LeftButton) return; if (textlnkDown() != textlnkOver()) { - updateMsg(App::pressedLinkItem()); + repaintItem(App::pressedLinkItem()); textlnkDown(textlnkOver()); App::pressedLinkItem(App::hoveredLinkItem()); - updateMsg(App::pressedLinkItem()); - } - if (_lnkDownIndex != _lnkOverIndex) { - if (_dragItem) updateMsg(_dragItem, _dragItemIndex); - _lnkDownIndex = _lnkOverIndex; - if (_mousedItem) updateMsg(_mousedItem, _mousedItemIndex); + repaintItem(App::pressedLinkItem()); } _dragAction = NoDrag; @@ -682,11 +488,11 @@ void OverviewInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton but _dragStartPos = mapMouseToItem(mapFromGlobal(screenPos), _dragItem, _dragItemIndex); _dragWasInactive = App::wnd()->inactivePress(); if (_dragWasInactive) App::wnd()->inactivePress(false); - if ((textlnkDown() || _lnkDownIndex) && _selected.isEmpty()) { + if (textlnkDown() && _selected.isEmpty()) { _dragAction = PrepareDrag; } else if (!_selected.isEmpty()) { - if (_selected.cbegin().value() == FullItemSel) { - if (_selected.constFind(_dragItem) != _selected.cend() && (textlnkDown() || _lnkDownIndex)) { + if (_selected.cbegin().value() == FullSelection) { + if (_selected.constFind(_dragItem) != _selected.cend() && textlnkDown()) { _dragAction = PrepareDrag; // start items drag } else { _dragAction = PrepareSelect; // start items select @@ -697,17 +503,17 @@ void OverviewInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton but bool afterDragSymbol = false , uponSymbol = false; uint16 symbol = 0; if (!_dragWasInactive) { - if (textlnkDown() || _lnkDownIndex) { + if (textlnkDown()) { _dragSymbol = symbol; uint32 selStatus = (_dragSymbol << 16) | _dragSymbol; - if (selStatus != FullItemSel && (_selected.isEmpty() || _selected.cbegin().value() != FullItemSel)) { + if (selStatus != FullSelection && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection)) { if (!_selected.isEmpty()) { - updateMsg(_selected.cbegin().key(), -1); + repaintItem(_selected.cbegin().key(), -1); _selected.clear(); } _selected.insert(_dragItem, selStatus); _dragAction = Selecting; - updateMsg(_dragItem, _dragItemIndex); + repaintItem(_dragItem, _dragItemIndex); _overview->updateTopBarSelection(); } else { _dragAction = PrepareSelect; @@ -737,7 +543,6 @@ void OverviewInner::dragActionCancel() { void OverviewInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton button) { TextLinkPtr needClick; - int32 needClickIndex = 0; dragActionUpdate(screenPos); @@ -746,13 +551,8 @@ void OverviewInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton bu needClick = textlnkDown(); } } - if (_lnkOverIndex) { - if (_lnkDownIndex == _lnkOverIndex && _dragAction != Dragging && !_selMode) { - needClickIndex = _lnkDownIndex; - } - } if (textlnkDown()) { - updateMsg(App::pressedLinkItem()); + repaintItem(App::pressedLinkItem()); textlnkDown(TextLinkPtr()); App::pressedLinkItem(0); if (!textlnkOver() && _cursor != style::cur_default) { @@ -760,51 +560,33 @@ void OverviewInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton bu setCursor(_cursor); } } - if (_lnkDownIndex) { - updateMsg(_dragItem, _dragItemIndex); - _lnkDownIndex = 0; - if (!_lnkOverIndex && _cursor != style::cur_default) { - _cursor = style::cur_default; - setCursor(_cursor); - } - } if (needClick) { needClick->onClick(button); dragActionCancel(); return; } - if (needClickIndex) { - QString url = urlByIndex(_dragItem, _dragItemIndex, needClickIndex); - if (urlIsEmail(url)) { - EmailLink(url).onClick(button); - } else { - TextLink(url).onClick(button); - } - dragActionCancel(); - return; - } - if (_dragAction == PrepareSelect && !needClick && !_dragWasInactive && !_selected.isEmpty() && _selected.cbegin().value() == FullItemSel) { + if (_dragAction == PrepareSelect && !needClick && !_dragWasInactive && !_selected.isEmpty() && _selected.cbegin().value() == FullSelection) { SelectedItems::iterator i = _selected.find(_dragItem); if (i == _selected.cend() && itemMsgId(_dragItem) > 0) { if (_selected.size() < MaxSelectedItems) { - if (!_selected.isEmpty() && _selected.cbegin().value() != FullItemSel) { + if (!_selected.isEmpty() && _selected.cbegin().value() != FullSelection) { _selected.clear(); } - _selected.insert(_dragItem, FullItemSel); + _selected.insert(_dragItem, FullSelection); } } else { _selected.erase(i); } - updateMsg(_dragItem, _dragItemIndex); + repaintItem(_dragItem, _dragItemIndex); } else if (_dragAction == PrepareDrag && !needClick && !_dragWasInactive && button != Qt::RightButton) { SelectedItems::iterator i = _selected.find(_dragItem); - if (i != _selected.cend() && i.value() == FullItemSel) { + if (i != _selected.cend() && i.value() == FullSelection) { _selected.erase(i); - updateMsg(_dragItem, _dragItemIndex); - } else if (i == _selected.cend() && itemMsgId(_dragItem) > 0 && !_selected.isEmpty() && _selected.cbegin().value() == FullItemSel) { + repaintItem(_dragItem, _dragItemIndex); + } else if (i == _selected.cend() && itemMsgId(_dragItem) > 0 && !_selected.isEmpty() && _selected.cbegin().value() == FullSelection) { if (_selected.size() < MaxSelectedItems) { - _selected.insert(_dragItem, FullItemSel); - updateMsg(_dragItem, _dragItemIndex); + _selected.insert(_dragItem, FullSelection); + repaintItem(_dragItem, _dragItemIndex); } } else { _selected.clear(); @@ -815,7 +597,7 @@ void OverviewInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton bu applyDragSelection(); } else if (!_selected.isEmpty() && !_dragWasInactive) { uint32 sel = _selected.cbegin().value(); - if (sel != FullItemSel && (sel & 0xFFFF) == ((sel >> 16) & 0xFFFF)) { + if (sel != FullSelection && (sel & 0xFFFF) == ((sel >> 16) & 0xFFFF)) { _selected.clear(); App::main()->activate(); } @@ -833,7 +615,7 @@ void OverviewInner::onDragExec() { if (_dragItem) { bool afterDragSymbol; uint16 symbol; - if (!_selected.isEmpty() && _selected.cbegin().value() == FullItemSel) { + if (!_selected.isEmpty() && _selected.cbegin().value() == FullSelection) { uponSelected = _selected.contains(_dragItem); } else { uponSelected = false; @@ -843,20 +625,10 @@ void OverviewInner::onDragExec() { QList urls; bool forwardSelected = false; if (uponSelected) { - forwardSelected = !_selected.isEmpty() && _selected.cbegin().value() == FullItemSel && cWideMode(); + forwardSelected = !_selected.isEmpty() && _selected.cbegin().value() == FullSelection && cWideMode(); } else if (textlnkDown()) { sel = textlnkDown()->encoded(); if (!sel.isEmpty() && sel.at(0) != '/' && sel.at(0) != '@' && sel.at(0) != '#') { -// urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o - } - } else if (_lnkDownIndex) { - QString url = urlByIndex(_dragItem, _dragItemIndex, _lnkDownIndex); - if (urlIsEmail(url)) { - sel = EmailLink(url).encoded(); - } else { - sel = TextLink(url).encoded(); - } - if (!sel.isEmpty() && sel.at(0) != '/' && sel.at(0) != '@' && sel.at(0) != '#') { // urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o } } @@ -882,7 +654,7 @@ void OverviewInner::onDragExec() { bool lnkPhoto = (lnkType == qstr("PhotoLink")), lnkVideo = (lnkType == qstr("VideoOpenLink")), lnkAudio = (lnkType == qstr("AudioOpenLink")), - lnkDocument = (lnkType == qstr("DocumentOpenLink")); + lnkDocument = (lnkType == qstr("DocumentOpenLink") || lnkType == qstr("GifOpenLink")); if (lnkPhoto || lnkVideo || lnkAudio || lnkDocument) { QDrag *drag = new QDrag(App::wnd()); QMimeData *mimeData = new QMimeData; @@ -914,21 +686,16 @@ void OverviewInner::touchScrollUpdated(const QPoint &screenPos) { void OverviewInner::addSelectionRange(int32 selFrom, int32 selTo, History *history) { if (selFrom < 0 || selTo < 0) return; for (int32 i = selFrom; i <= selTo; ++i) { - MsgId msgid = 0; - if (_type == OverviewPhotos || _type == OverviewAudioDocuments) { - msgid = ((history == _history) ? 1 : -1) * history->overview[_type][i]; - } else { - msgid = _items[i].msgid; - } + MsgId msgid = complexMsgId(_items.at(i)->getItem()); if (!msgid) continue; SelectedItems::iterator j = _selected.find(msgid); if (_dragSelecting && itemMsgId(msgid) > 0) { if (j == _selected.cend()) { if (_selected.size() >= MaxSelectedItems) break; - _selected.insert(msgid, FullItemSel); - } else if (j.value() != FullItemSel) { - *j = FullItemSel; + _selected.insert(msgid, FullSelection); + } else if (j.value() != FullSelection) { + *j = FullSelection; } } else { if (j != _selected.cend()) { @@ -941,29 +708,10 @@ void OverviewInner::addSelectionRange(int32 selFrom, int32 selTo, History *histo void OverviewInner::applyDragSelection() { if (_dragSelFromIndex < 0 || _dragSelToIndex < 0) return; - if (!_selected.isEmpty() && _selected.cbegin().value() != FullItemSel) { + if (!_selected.isEmpty() && _selected.cbegin().value() != FullSelection) { _selected.clear(); } - int32 selfrom = _dragSelToIndex, selto = _dragSelFromIndex; - if (_migrated && (_type == OverviewPhotos || _type == OverviewAudioDocuments)) { - int32 indexskip = migratedIndexSkip(); - if (selfrom < indexskip) { - if (selto < indexskip) { - addSelectionRange(selfrom, selto, _migrated); - selto = -1; - } else { - addSelectionRange(selfrom, _migrated->overview[_type].size() - 1, _migrated); - selto -= indexskip; - } - selfrom = 0; - } else if (selto < indexskip) { // wtf - selfrom = selto = -1; - } else { - selfrom -= indexskip; - selto -= indexskip; - } - } - addSelectionRange(selfrom, selto, _history); + addSelectionRange(_dragSelToIndex, _dragSelFromIndex, _history); _dragSelFrom = _dragSelTo = 0; _dragSelFromIndex = _dragSelToIndex = -1; @@ -974,23 +722,22 @@ QPoint OverviewInner::mapMouseToItem(QPoint p, MsgId itemId, int32 itemIndex) { fixItemIndex(itemIndex, itemId); if (itemIndex < 0) return QPoint(0, 0); - if (_type == OverviewPhotos) { - int32 row = (_photosToAdd + itemIndex) / _photosInRow, col = (_photosToAdd + itemIndex) % _photosInRow; + if (_type == OverviewPhotos || _type == OverviewVideos) { + int32 shownAtIndex = _items.size() - itemIndex - 1; + int32 row = (_photosToAdd + shownAtIndex) / _photosInRow, col = (_photosToAdd + shownAtIndex) % _photosInRow; float64 w = (_width - st::overviewPhotoSkip) / float64(_photosInRow); p.setX(p.x() - int32(col * w) - st::overviewPhotoSkip); - p.setY(p.y() - _addToY - row * (_vsize + st::overviewPhotoSkip) - st::overviewPhotoSkip); - } else if (_type == OverviewAudioDocuments) { - p.setY(p.y() - _addToY - itemIndex * _audioHeight); - } else if (_type == OverviewLinks) { - p.setY(p.y() - _addToY - _items[itemIndex].y); + p.setY(p.y() - _marginTop - row * (_rowWidth + st::overviewPhotoSkip) - st::overviewPhotoSkip); } else { - p.setY(p.y() - _addToY - (_height - _items[itemIndex].y)); + int32 top = _items.at(itemIndex)->getOverviewItemInfo()->top(); + if (_reversed) top = _height - top; + p.setY(p.y() - _marginTop - top); } return p; } void OverviewInner::activate() { - if (_type == OverviewLinks) { + if (_type == OverviewLinks || _type == OverviewDocuments) { _search.setFocus(); } else { setFocus(); @@ -998,19 +745,29 @@ void OverviewInner::activate() { } void OverviewInner::clear() { - _cached.clear(); + _selected.clear(); + _dragItemIndex = _mousedItemIndex = _dragSelFromIndex = _dragSelToIndex = -1; + _dragItem = _mousedItem = _dragSelFrom = _dragSelTo = 0; + _dragAction = NoDrag; + for (LayoutItems::const_iterator i = _layoutItems.cbegin(), e = _layoutItems.cend(); i != e; ++i) { + delete i.value(); + } + _layoutItems.clear(); + for (LayoutDates::const_iterator i = _layoutDates.cbegin(), e = _layoutDates.cend(); i != e; ++i) { + delete i.value(); + } + _layoutDates.clear(); + _items.clear(); } int32 OverviewInner::itemTop(const FullMsgId &msgId) const { if (_type == OverviewAudioDocuments) { - if (msgId.channel == _channel) { - int32 index = _history->overview[_type].indexOf(msgId.msg); - if (index >= 0) { - return _addToY + int32((index + migratedIndexSkip()) * _audioHeight); - } - } else if (_migrated && msgId.channel == _migrated->channelId()) { - int32 index = _migrated->overview[_type].indexOf(msgId.msg); - return _addToY + int32(index * _audioHeight); + int32 itemIndex = -1; + fixItemIndex(itemIndex, (msgId.channel == _channel) ? msgId.msg : ((_migrated && msgId.channel == _migrated->channelId()) ? -msgId.msg : 0)); + if (itemIndex >= 0) { + int32 top = _items.at(itemIndex)->getOverviewItemInfo()->top(); + if (_reversed) top = _height - top; + return _marginTop + top; } } return -1; @@ -1019,53 +776,51 @@ int32 OverviewInner::itemTop(const FullMsgId &msgId) const { void OverviewInner::preloadMore() { if (_inSearch) { if (!_searchRequest) { + MTPmessagesFilter filter = (_type == OverviewLinks) ? MTP_inputMessagesFilterUrl() : MTP_inputMessagesFilterDocument(); if (!_searchFull) { int32 flags = (_history->peer->isChannel() && !_history->peer->isMegagroup()) ? MTPmessages_Search::flag_important_only : 0; - _searchRequest = MTP::send(MTPmessages_Search(MTP_int(flags), _history->peer->input, MTP_string(_searchQuery), MTP_inputMessagesFilterUrl(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(_lastSearchId), MTP_int(SearchPerPage)), rpcDone(&OverviewInner::searchReceived, _lastSearchId ? SearchFromOffset : SearchFromStart), rpcFail(&OverviewInner::searchFailed, _lastSearchId ? SearchFromOffset : SearchFromStart)); + _searchRequest = MTP::send(MTPmessages_Search(MTP_int(flags), _history->peer->input, MTP_string(_searchQuery), filter, MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(_lastSearchId), MTP_int(SearchPerPage)), rpcDone(&OverviewInner::searchReceived, _lastSearchId ? SearchFromOffset : SearchFromStart), rpcFail(&OverviewInner::searchFailed, _lastSearchId ? SearchFromOffset : SearchFromStart)); if (!_lastSearchId) { _searchQueries.insert(_searchRequest, _searchQuery); } } else if (_migrated && !_searchFullMigrated) { int32 flags = (_migrated->peer->isChannel() && !_migrated->peer->isMegagroup()) ? MTPmessages_Search::flag_important_only : 0; - _searchRequest = MTP::send(MTPmessages_Search(MTP_int(flags), _migrated->peer->input, MTP_string(_searchQuery), MTP_inputMessagesFilterUrl(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(_lastSearchMigratedId), MTP_int(SearchPerPage)), rpcDone(&OverviewInner::searchReceived, _lastSearchMigratedId ? SearchMigratedFromOffset : SearchMigratedFromStart), rpcFail(&OverviewInner::searchFailed, _lastSearchMigratedId ? SearchMigratedFromOffset : SearchMigratedFromStart)); + _searchRequest = MTP::send(MTPmessages_Search(MTP_int(flags), _migrated->peer->input, MTP_string(_searchQuery), filter, MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(_lastSearchMigratedId), MTP_int(SearchPerPage)), rpcDone(&OverviewInner::searchReceived, _lastSearchMigratedId ? SearchMigratedFromOffset : SearchMigratedFromStart), rpcFail(&OverviewInner::searchFailed, _lastSearchMigratedId ? SearchMigratedFromOffset : SearchMigratedFromStart)); } } } else if (App::main()) { if (_migrated && _history->overviewLoaded(_type)) { - App::main()->loadMediaBack(_migrated->peer, _type, _type != OverviewLinks); + App::main()->loadMediaBack(_migrated->peer, _type, true); } else { - App::main()->loadMediaBack(_history->peer, _type, _type != OverviewLinks); + App::main()->loadMediaBack(_history->peer, _type, true); } } } bool OverviewInner::preloadLocal() { - if (_type != OverviewLinks) return false; if (_itemsToBeLoaded >= migratedIndexSkip() + _history->overview[_type].size()) return false; _itemsToBeLoaded += LinksOverviewPerPage; mediaOverviewUpdated(); return true; } -QPixmap OverviewInner::genPix(PhotoData *photo, int32 size) { - size *= cIntRetinaFactor(); - QImage img = (photo->full->loaded() ? photo->full : (photo->medium->loaded() ? photo->medium : photo->thumb))->pix().toImage(); - if (!photo->full->loaded() && !photo->medium->loaded()) { - img = imageBlur(img); +uint32 OverviewInner::itemSelectedValue(int32 index) const { + int32 selfrom = -1, selto = -1; + if (_dragSelFromIndex >= 0 && _dragSelToIndex >= 0) { + selfrom = _dragSelToIndex; + selto = _dragSelFromIndex; } - if (img.width() == img.height()) { - if (img.width() != size) { - img = img.scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + if (_items.at(index)->toLayoutMediaItem()) { // draw item + if (index >= _dragSelToIndex && index <= _dragSelFromIndex && _dragSelToIndex >= 0) { + return (_dragSelecting && _items.at(index)->msgId() > 0) ? FullSelection : 0; + } else if (!_selected.isEmpty()) { + SelectedItems::const_iterator j = _selected.constFind(complexMsgId(_items.at(index)->getItem())); + if (j != _selected.cend()) { + return j.value(); + } } - } else if (img.width() > img.height()) { - img = img.copy((img.width() - img.height()) / 2, 0, img.height(), img.height()).scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); - } else { - img = img.copy(0, (img.height() - img.width()) / 2, img.width(), img.width()).scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); } -// imageRound(img); - img.setDevicePixelRatio(cRetinaFactor()); - photo->forget(); - return QPixmap::fromImage(img, Qt::ColorOnly); + return 0; } void OverviewInner::paintEvent(QPaintEvent *e) { @@ -1074,7 +829,12 @@ void OverviewInner::paintEvent(QPaintEvent *e) { Painter p(this); QRect r(e->rect()); - p.setClipRect(r); + bool trivial = (r == rect()); + if (!trivial) { + p.setClipRect(r); + } + uint64 ms = getms(); + OverviewPaintContext context(ms, _selMode); if (_history->overview[_type].isEmpty() && (!_migrated || !_history->overviewLoaded(_type) || _migrated->overview[_type].isEmpty())) { QPoint dogPos((_width - st::msgDogImg.pxWidth()) / 2, ((height() - st::msgDogImg.pxHeight()) * 4) / 9); @@ -1083,7 +843,7 @@ void OverviewInner::paintEvent(QPaintEvent *e) { } else if (_inSearch && _searchResults.isEmpty() && _searchFull && (!_migrated || _searchFullMigrated) && !_searchTimer.isActive()) { p.setFont(st::noContactsFont->f); p.setPen(st::noContactsColor->p); - p.drawText(QRect(_linksLeft, _addToY, _linksWidth, _addToY), lng_search_found_results(lt_count, 0), style::al_center); + p.drawText(QRect(_rowsLeft, _marginTop, _rowWidth, _marginTop), lng_search_found_results(lt_count, 0), style::al_center); return; } @@ -1096,271 +856,40 @@ void OverviewInner::paintEvent(QPaintEvent *e) { SelectedItems::const_iterator selEnd = _selected.cend(); bool hasSel = !_selected.isEmpty(); - if (_type == OverviewPhotos) { - History::MediaOverview &overview(_history->overview[_type]), *migratedOverview = _migrated ? &_migrated->overview[_type] : 0; - int32 migratedCount = migratedIndexSkip(); - int32 count = migratedCount + overview.size(); - int32 rowFrom = floorclamp(r.y() - _addToY - st::overviewPhotoSkip, _vsize + st::overviewPhotoSkip, 0, count); - int32 rowTo = ceilclamp(r.y() + r.height() - _addToY - st::overviewPhotoSkip, _vsize + st::overviewPhotoSkip, 0, count); + if (_type == OverviewPhotos || _type == OverviewVideos) { + int32 count = _items.size(), rowsCount = (_photosToAdd + count) / _photosInRow + (((_photosToAdd + count) % _photosInRow) ? 1 : 0); + int32 rowFrom = floorclamp(r.y() - _marginTop - st::overviewPhotoSkip, _rowWidth + st::overviewPhotoSkip, 0, rowsCount); + int32 rowTo = ceilclamp(r.y() + r.height() - _marginTop - st::overviewPhotoSkip, _rowWidth + st::overviewPhotoSkip, 0, rowsCount); float64 w = float64(_width - st::overviewPhotoSkip) / _photosInRow; for (int32 row = rowFrom; row < rowTo; ++row) { if (row * _photosInRow >= _photosToAdd + count) break; - for (int32 i = 0; i < _photosInRow; ++i) { - int32 index = row * _photosInRow + i - _photosToAdd; - if (index < 0) continue; - if (index >= count) break; + for (int32 col = 0; col < _photosInRow; ++col) { + int32 i = count - (row * _photosInRow + col - _photosToAdd) - 1; + if (i < 0) continue; + if (i >= count) break; - bool migratedindex = (index < migratedCount); - int32 bareindex = migratedindex ? index : (index - migratedCount); - - HistoryItem *item = App::histItemById(migratedindex ? _migrated->channelId() : _channel, (migratedindex ? *migratedOverview : overview)[bareindex]); - HistoryMedia *m = item ? item->getMedia(true) : 0; - if (!m) continue; - - switch (m->type()) { - case MediaTypePhoto: { - PhotoData *photo = static_cast(m)->photo(); - bool quality = photo->full->loaded(); - if (!quality) { - if (photo->thumb->loaded()) { - photo->medium->load(false, false); - quality = photo->medium->loaded(); - } else { - photo->thumb->load(); - } - } - CachedSizes::iterator it = _cached.find(photo); - if (it == _cached.cend()) { - CachedSize size; - size.medium = quality; - size.vsize = _vsize; - size.pix = genPix(photo, _vsize); - it = _cached.insert(photo, size); - } else if (it->medium != quality || it->vsize != _vsize) { - it->medium = quality; - it->vsize = _vsize; - it->pix = genPix(photo, _vsize); - } - QPoint pos(int32(i * w + st::overviewPhotoSkip), _addToY + row * (_vsize + st::overviewPhotoSkip) + st::overviewPhotoSkip); - p.drawPixmap(pos, it->pix); - if (!quality) { - uint64 dt = itemAnimations().animate(item, getms()); - int32 cnt = int32(st::photoLoaderCnt), period = int32(st::photoLoaderPeriod), t = dt % period, delta = int32(st::photoLoaderDelta); - - int32 x = pos.x() + (_vsize - st::overviewLoader.width()) / 2, y = pos.y() + (_vsize - st::overviewLoader.height()) / 2; - p.fillRect(x, y, st::overviewLoader.width(), st::overviewLoader.height(), st::photoLoaderBg->b); - x += (st::overviewLoader.width() - cnt * st::overviewLoaderPoint.width() - (cnt - 1) * st::overviewLoaderSkip) / 2; - y += (st::overviewLoader.height() - st::overviewLoaderPoint.height()) / 2; - QColor c(st::white->c); - QBrush b(c); - for (int32 i = 0; i < cnt; ++i) { - t -= delta; - while (t < 0) t += period; - - float64 alpha = (t >= st::photoLoaderDuration1 + st::photoLoaderDuration2) ? 0 : ((t > st::photoLoaderDuration1 ? ((st::photoLoaderDuration1 + st::photoLoaderDuration2 - t) / st::photoLoaderDuration2) : (t / st::photoLoaderDuration1))); - c.setAlphaF(st::photoLoaderAlphaMin + alpha * (1 - st::photoLoaderAlphaMin)); - b.setColor(c); - p.fillRect(x + i * (st::overviewLoaderPoint.width() + st::overviewLoaderSkip), y, st::overviewLoaderPoint.width(), st::overviewLoaderPoint.height(), b); - } - } - - uint32 sel = 0; - if (index >= selfrom && index <= selto) { - sel = (_dragSelecting && item->id > 0) ? FullItemSel : 0; - } else if (hasSel) { - SelectedItems::const_iterator i = _selected.constFind(migratedindex ? -item->id : item->id); - if (i != selEnd) { - sel = i.value(); - } - } - if (sel == FullItemSel) { - p.fillRect(QRect(pos.x(), pos.y(), _vsize, _vsize), st::overviewPhotoSelectOverlay->b); - p.drawPixmap(QPoint(pos.x() + _vsize - st::overviewPhotoCheck.pxWidth(), pos.y() + _vsize - st::overviewPhotoCheck.pxHeight()), App::sprite(), st::overviewPhotoChecked); - } else if (_selMode/* || (selfrom < count && selfrom <= selto && 0 <= selto)*/) { - p.drawPixmap(QPoint(pos.x() + _vsize - st::overviewPhotoCheck.pxWidth(), pos.y() + _vsize - st::overviewPhotoCheck.pxHeight()), App::sprite(), st::overviewPhotoCheck); - } - } break; - } - } - } - } else if (_type == OverviewAudioDocuments) { - History::MediaOverview &overview(_history->overview[_type]), *migratedOverview = _migrated ? &_migrated->overview[_type] : 0; - int32 migratedCount = migratedIndexSkip(); - int32 count = migratedCount + overview.size(); - int32 from = floorclamp(r.y() - _addToY, _audioHeight, 0, count); - int32 to = ceilclamp(r.y() + r.height() - _addToY, _audioHeight, 0, count); - p.translate(_audioLeft, _addToY + from * _audioHeight); - for (int32 index = from; index < to; ++index) { - if (index >= count) break; - - bool migratedindex = (index < migratedCount); - int32 bareindex = migratedindex ? index : (index - migratedCount); - - HistoryItem *item = App::histItemById(migratedindex ? _migrated->channelId() : _channel, (migratedindex ? *migratedOverview : overview)[bareindex]); - HistoryMedia *m = item ? item->getMedia(true) : 0; - if (!m || m->type() != MediaTypeDocument) continue; - - uint32 sel = 0; - if (index >= selfrom && index <= selto) { - sel = (_dragSelecting && item->id > 0) ? FullItemSel : 0; - } else if (hasSel) { - SelectedItems::const_iterator i = _selected.constFind(migratedindex ? -item->id : item->id); - if (i != selEnd) { - sel = i.value(); - } - } - - bool drawOver = _menu ? (App::contextItem() ? (App::contextItem() == item) : false) : (itemMsgId(_selectedMsgId) == item->id && itemChannel(_selectedMsgId) == item->channelId()); - static_cast(m)->drawInPlaylist(p, item, (sel == FullItemSel), drawOver, _audioWidth); - p.translate(0, _audioHeight); - } - } else if (_type == OverviewLinks) { - p.translate(_linksLeft, _addToY); - int32 y = 0, w = _linksWidth; - for (int32 i = 0, l = _items.size(); i < l; ++i) { - if (i + 1 == l || _addToY + _items[i + 1].y > r.top()) { - int32 left = st::dlgPhotoSize + st::dlgPhotoPadding, top = st::linksMargin + st::linksBorder, curY = _items[i].y; - if (_addToY + curY >= r.y() + r.height()) break; - - p.translate(0, curY - y); - if (_items[i].msgid) { // draw item - CachedLink *lnk = _items[i].link; - WebPageData *page = lnk->page; - if (page && page->photo) { - QPixmap pix; - if (page->photo->full->loaded()) { - pix = page->photo->full->pixSingle(lnk->pixw, lnk->pixh, st::dlgPhotoSize, st::dlgPhotoSize); - } else if (page->photo->medium->loaded()) { - pix = page->photo->medium->pixSingle(lnk->pixw, lnk->pixh, st::dlgPhotoSize, st::dlgPhotoSize); - } else { - pix = page->photo->thumb->pixBlurredSingle(lnk->pixw, lnk->pixh, st::dlgPhotoSize, st::dlgPhotoSize); - } - p.drawPixmap(0, top, pix); - } else if (page && page->doc && !page->doc->thumb->isNull()) { - p.drawPixmap(0, top, page->doc->thumb->pixSingle(lnk->pixw, lnk->pixh, st::dlgPhotoSize, st::dlgPhotoSize)); - } else { - int32 index = lnk->letter.isEmpty() ? 0 : (lnk->letter.at(0).unicode() % 4); - switch (index) { - case 0: App::roundRect(p, QRect(0, top, st::dlgPhotoSize, st::dlgPhotoSize), st::mvDocRedColor, DocRedCorners); break; - case 1: App::roundRect(p, QRect(0, top, st::dlgPhotoSize, st::dlgPhotoSize), st::mvDocYellowColor, DocYellowCorners); break; - case 2: App::roundRect(p, QRect(0, top, st::dlgPhotoSize, st::dlgPhotoSize), st::mvDocGreenColor, DocGreenCorners); break; - case 3: App::roundRect(p, QRect(0, top, st::dlgPhotoSize, st::dlgPhotoSize), st::mvDocBlueColor, DocBlueCorners); break; - } - - if (!lnk->letter.isEmpty()) { - p.setFont(st::linksLetterFont->f); - p.setPen(st::white->p); - p.drawText(QRect(0, top, st::dlgPhotoSize, st::dlgPhotoSize), lnk->letter, style::al_center); - } - } - - uint32 sel = 0; - if (i >= selfrom && i <= selto) { - sel = (_dragSelecting && itemMsgId(_items[i].msgid) > 0) ? FullItemSel : 0; - } else if (hasSel) { - SelectedItems::const_iterator j = _selected.constFind(_items[i].msgid); - if (j != selEnd) { - sel = j.value(); - } - } - if (sel == FullItemSel) { - App::roundRect(p, QRect(0, top, st::dlgPhotoSize, st::dlgPhotoSize), st::overviewPhotoSelectOverlay, PhotoSelectOverlayCorners); - p.drawPixmap(QPoint(st::dlgPhotoSize - st::linksPhotoCheck.pxWidth(), top + st::dlgPhotoSize - st::linksPhotoCheck.pxHeight()), App::sprite(), st::linksPhotoChecked); - } else if (_selMode/* || (selfrom < count && selfrom <= selto && 0 <= selto)*/) { - p.drawPixmap(QPoint(st::dlgPhotoSize - st::linksPhotoCheck.pxWidth(), top + st::dlgPhotoSize - st::linksPhotoCheck.pxHeight()), App::sprite(), st::linksPhotoCheck); - } - - if (!lnk->title.isEmpty() && lnk->text.isEmpty() && lnk->urls.size() == 1) { - top += (st::dlgPhotoSize - st::webPageTitleFont->height - st::msgFont->height) / 2; - } - - p.setPen(st::black->p); - p.setFont(st::webPageTitleFont->f); - if (!lnk->title.isEmpty()) { - p.drawText(left, top + st::webPageTitleFont->ascent, (_linksWidth - left < lnk->titleWidth) ? st::webPageTitleFont->elided(lnk->title, _linksWidth - left) : lnk->title); - top += st::webPageTitleFont->height; - } - p.setFont(st::msgFont->f); - if (!lnk->text.isEmpty()) { - lnk->text.drawElided(p, left, top, _linksWidth - left, 3); - top += qMin(st::msgFont->height * 3, lnk->text.countHeight(_linksWidth - left)); - } - - p.setPen(st::btnYesColor->p); - for (int32 j = 0, c = lnk->urls.size(); j < c; ++j) { - bool sel = (_mousedItem == _items[i].msgid && j + 1 == _lnkOverIndex); - if (sel) p.setFont(st::msgFont->underline()->f); - p.drawText(left, top + st::msgFont->ascent, (_linksWidth - left < lnk->urls[j].width) ? st::msgFont->elided(lnk->urls[j].text, _linksWidth - left) : lnk->urls[j].text); - if (sel) p.setFont(st::msgFont->f); - top += st::msgFont->height; - } - p.fillRect(left, _items[i].y - curY, _linksWidth - left, st::linksBorder, st::linksBorderColor->b); - } else { - QString str = langDayOfMonth(_items[i].date); - - p.setPen(st::linksDateColor->p); - p.setFont(st::msgFont->f); - p.drawText(0, st::linksDateMargin + st::msgFont->ascent, str); - } - y = curY; + QPoint pos(int32(col * w + st::overviewPhotoSkip), _marginTop + row * (_rowWidth + st::overviewPhotoSkip) + st::overviewPhotoSkip); + p.translate(pos.x(), pos.y()); + _items.at(i)->paint(p, r.translated(-pos.x(), -pos.y()), itemSelectedValue(i), &context); + p.translate(-pos.x(), -pos.y()); } } } else { - p.translate(0, st::msgMargin.top() + _addToY); - int32 y = 0, w = _width - st::msgMargin.left() - st::msgMargin.right(); - for (int32 i = _items.size(); i > 0;) { - --i; - if (!i || (_addToY + _height - _items[i - 1].y > r.top())) { - int32 curY = _height - _items[i].y; - if (_addToY + curY >= r.y() + r.height()) break; + p.translate(_rowsLeft, _marginTop); + int32 y = 0, w = _rowWidth; + for (int32 j = 0, l = _items.size(); j < l; ++j) { + int32 i = _reversed ? (l - j - 1) : j, nexti = _reversed ? (i - 1) : (i + 1); + int32 nextItemTop = (j + 1 == l) ? (_reversed ? 0 : _height) : _items.at(nexti)->getOverviewItemInfo()->top(); + if (_reversed) nextItemTop = _height - nextItemTop; + if (_marginTop + nextItemTop > r.top()) { + OverviewItemInfo *info = _items.at(i)->getOverviewItemInfo(); + int32 curY = info->top(); + if (_reversed) curY = _height - curY; + if (_marginTop + curY >= r.y() + r.height()) break; + context.isAfterDate = (j > 0) ? !_items.at(j - 1)->toLayoutMediaItem() : false; p.translate(0, curY - y); - if (_items[i].msgid) { // draw item - HistoryItem *item = App::histItemById(itemChannel(_items[i].msgid), itemMsgId(_items[i].msgid)); - HistoryMedia *media = item ? item->getMedia(true) : 0; - if (media) { - bool out = item->out(), fromChannel = item->fromChannel(), outbg = out && !fromChannel; - int32 mw = media->maxWidth(), left = (fromChannel ? (st::msgMargin.left() + st::msgMargin.left()) / 2 : (out ? st::msgMargin.right() : st::msgMargin.left())) + ((mw < w) ? (fromChannel ? 0 : (out ? w - mw : 0)) : 0); - if (item->displayFromPhoto()) { - p.drawPixmap(left, media->countHeight(item, w) - st::msgPhotoSize, item->from()->photo->pixRounded(st::msgPhotoSize)); - left += st::msgPhotoSkip; - } - - uint32 sel = 0; - if (i >= selfrom && i <= selto) { - sel = (_dragSelecting && item->id > 0) ? FullItemSel : 0; - } else if (hasSel) { - SelectedItems::const_iterator j = _selected.constFind(_items[i].msgid); - if (j != selEnd) { - sel = j.value(); - } - } - - p.save(); - p.translate(left, 0); - media->draw(p, item, (sel == FullItemSel), w); - p.restore(); - } - } else { - QString str = langDayOfMonth(_items[i].date); - - int32 left = st::msgServiceMargin.left(), width = _width - st::msgServiceMargin.left() - st::msgServiceMargin.left(), height = st::msgServiceFont->height + st::msgServicePadding.top() + st::msgServicePadding.bottom(); - if (width < 1) return; - - int32 strwidth = st::msgServiceFont->width(str) + st::msgServicePadding.left() + st::msgServicePadding.right(); - - QRect trect(QRect(left, st::msgServiceMargin.top(), width, height).marginsAdded(-st::msgServicePadding)); - left += (width - strwidth) / 2; - width = strwidth; - - QRect r(left, st::msgServiceMargin.top(), width, height); - App::roundRect(p, r, App::msgServiceBg(), ServiceCorners); - - p.setBrush(Qt::NoBrush); - p.setPen(st::msgServiceColor->p); - p.setFont(st::msgServiceFont->f); - p.drawText(r.x() + st::msgServicePadding.left(), r.y() + st::msgServicePadding.top() + st::msgServiceFont->ascent, str); - } + _items.at(i)->paint(p, r.translated(-_rowsLeft, -_marginTop - curY), itemSelectedValue(i), &context); y = curY; } } @@ -1381,22 +910,19 @@ void OverviewInner::onUpdateSelected() { QPoint m(_overview->clampMousePosition(mousePos)); TextLinkPtr lnk; - int32 lnkIndex = 0; // for OverviewLinks HistoryItem *item = 0; int32 index = -1; int32 newsel = 0; HistoryCursorState cursorState = HistoryDefaultCursorState; - if (_type == OverviewPhotos) { + if (_type == OverviewPhotos || _type == OverviewVideos) { float64 w = (float64(_width - st::overviewPhotoSkip) / _photosInRow); - int32 inRow = int32((m.x() - (st::overviewPhotoSkip / 2)) / w), vsize = (_vsize + st::overviewPhotoSkip); - int32 row = int32((m.y() - _addToY - (st::overviewPhotoSkip / 2)) / vsize); - if (inRow < 0) inRow = 0; + int32 col = int32((m.x() - (st::overviewPhotoSkip / 2)) / w), vsize = (_rowWidth + st::overviewPhotoSkip); + int32 row = int32((m.y() - _marginTop - (st::overviewPhotoSkip / 2)) / vsize); + if (col < 0) col = 0; if (row < 0) row = 0; bool upon = true; - History::MediaOverview &overview(_history->overview[_type]), *migratedOverview = _migrated ? &_migrated->overview[_type] : 0; - int32 migratedCount = migratedIndexSkip(); - int32 i = row * _photosInRow + inRow - _photosToAdd, count = migratedCount + overview.size(); + int32 count = _items.size(), i = count - (row * _photosInRow + col - _photosToAdd) - 1; if (i < 0) { i = 0; upon = false; @@ -1406,142 +932,46 @@ void OverviewInner::onUpdateSelected() { upon = false; } if (i >= 0) { - MsgId msgid = (i >= migratedCount) ? overview[i - migratedCount] : (*migratedOverview)[i]; - HistoryItem *histItem = App::histItemById((i >= migratedCount) ? _channel : _migrated->channelId(), msgid); - if (histItem) { - item = histItem; + if (LayoutMediaItem *media = _items.at(i)->toLayoutMediaItem()) { + item = media->getItem(); index = i; - if (upon && m.x() >= inRow * w + st::overviewPhotoSkip && m.x() < inRow * w + st::overviewPhotoSkip + _vsize) { - if (m.y() >= _addToY + row * vsize + st::overviewPhotoSkip && m.y() < _addToY + (row + 1) * vsize + st::overviewPhotoSkip) { - HistoryMedia *media = item->getMedia(true); - if (media && media->type() == MediaTypePhoto) { - lnk = static_cast(media)->lnk(); - } - } + if (upon) { + media->getState(lnk, cursorState, m.x() - col * w - st::overviewPhotoSkip, m.y() - _marginTop - row * vsize - st::overviewPhotoSkip); } } } - } else if (_type == OverviewAudioDocuments) { - History::MediaOverview &overview(_history->overview[_type]), *migratedOverview = _migrated ? &_migrated->overview[_type] : 0; - int32 migratedCount = migratedIndexSkip(); - int32 i = int32((m.y() - _addToY) / _audioHeight), count = migratedCount + overview.size(); - - bool upon = true; - if (m.y() < _addToY) { - i = 0; - upon = false; - } - if (i >= count) { - i = count - 1; - upon = false; - } - if (i >= 0) { - MsgId msgid = (i >= migratedCount) ? overview[i - migratedCount] : (*migratedOverview)[i]; - HistoryItem *histItem = App::histItemById((i >= migratedCount) ? _channel : _migrated->channelId(), msgid); - if (histItem) { - item = histItem; - index = i; - if (upon && m.x() >= _audioLeft && m.x() < _audioLeft + _audioWidth) { - HistoryMedia *media = item->getMedia(true); - if (media && media->type() == MediaTypeDocument) { - lnk = static_cast(media)->linkInPlaylist(); - newsel = (item->history() == _migrated) ? (-item->id) : item->id; - } - } - } - } - if (newsel != _selectedMsgId) { - if (_selectedMsgId) updateMsg(_selectedMsgId, -1); - _selectedMsgId = newsel; - updateMsg(item); - } - } else if (_type == OverviewLinks) { - int32 w = _width - st::msgMargin.left() - st::msgMargin.right(); - for (int32 i = 0, l = _items.size(); i < l; ++i) { - if ((i + 1 == l) || (_addToY + _items[i + 1].y > m.y())) { - int32 left = st::dlgPhotoSize + st::dlgPhotoPadding, y = _addToY + _items[i].y; - if (!_items[i].msgid) { // day item - int32 h = 2 * st::linksDateMargin + st::msgFont->height;// itemHeight(_items[i].msgid, i); - if (i > 0 && ((y + h / 2) >= m.y() || i == _items.size() - 1)) { - --i; - if (!_items[i].msgid) break; // wtf - y = _addToY + _items[i].y; - } else if (i < _items.size() - 1 && ((y + h / 2) < m.y() || !i)) { - ++i; - if (!_items[i].msgid) break; // wtf - y = _addToY + _items[i].y; - } else { - break; // wtf - } - } - - HistoryItem *histItem = App::histItemById(itemChannel(_items[i].msgid), itemMsgId(_items[i].msgid)); - if (histItem) { - item = histItem; - index = i; - - int32 top = y + st::linksMargin + st::linksBorder, left = _linksLeft + st::dlgPhotoSize + st::dlgPhotoPadding, w = _linksWidth - st::dlgPhotoSize - st::dlgPhotoPadding; - if (!_items[i].link->title.isEmpty() && _items[i].link->text.isEmpty() && _items[i].link->urls.size() == 1) { - top += (st::dlgPhotoSize - st::webPageTitleFont->height - st::msgFont->height) / 2; - } - if (QRect(_linksLeft, y + st::linksMargin + st::linksBorder, st::dlgPhotoSize, st::dlgPhotoSize).contains(m)) { - lnkIndex = -1; - } else if (!_items[i].link->title.isEmpty() && QRect(left, top, qMin(w, _items[i].link->titleWidth), st::webPageTitleFont->height).contains(m)) { - lnkIndex = -1; - } else { - if (!_items[i].link->title.isEmpty()) top += st::webPageTitleFont->height; - if (!_items[i].link->text.isEmpty()) top += qMin(st::msgFont->height * 3, _items[i].link->text.countHeight(w)); - for (int32 j = 0, c = _items[i].link->urls.size(); j < c; ++j) { - if (QRect(left, top, qMin(w, _items[i].link->urls[j].width), st::msgFont->height).contains(m)) { - lnkIndex = j + 1; - break; - } - top += st::msgFont->height; - } - } - } - break; - } - } } else { - int32 w = _width - st::msgMargin.left() - st::msgMargin.right(); - for (int32 i = _items.size(); i > 0;) { - --i; - if (!i || (_addToY + _height - _items[i - 1].y > m.y())) { - int32 y = _addToY + _height - _items[i].y; - if (!_items[i].msgid) { // day item - int32 h = st::msgServiceFont->height + st::msgServicePadding.top() + st::msgServicePadding.bottom() + st::msgServiceMargin.top() + st::msgServiceMargin.bottom(); // itemHeight(_items[i].msgid, i); - if (i > 0 && ((y + h / 2) < m.y() || i == _items.size() - 1)) { + for (int32 j = 0, l = _items.size(); j < l; ++j) { + bool lastItem = (j + 1 == l); + int32 i = _reversed ? (l - j - 1) : j, nexti = _reversed ? (i - 1) : (i + 1); + int32 nextItemTop = lastItem ? (_reversed ? 0 : _height) : _items.at(nexti)->getOverviewItemInfo()->top(); + if (_reversed) nextItemTop = _height - nextItemTop; + if (_marginTop + nextItemTop > m.y() || lastItem) { + int32 top = _items.at(i)->getOverviewItemInfo()->top(); + if (_reversed) top = _height - top; + if (!_items.at(i)->toLayoutMediaItem()) { // day item + int32 h = _items.at(i)->height(); + bool beforeItem = (_marginTop + top + h / 2) >= m.y(); + if (_reversed) beforeItem = !beforeItem; + if (i > 0 && (beforeItem || i == _items.size() - 1)) { --i; - if (!_items[i].msgid) break; // wtf - y = _addToY + _height - _items[i].y; - } else if (i < _items.size() - 1 && ((y + h / 2) >= m.y() || !i)) { + if (!_items.at(i)->toLayoutMediaItem()) break; // wtf + top = _items.at(i)->getOverviewItemInfo()->top(); + } else if (i < _items.size() - 1 && (!beforeItem || !i)) { ++i; - if (!_items[i].msgid) break; // wtf - y = _addToY + _height - _items[i].y; + if (!_items.at(i)->toLayoutMediaItem()) break; // wtf + top = _items.at(i)->getOverviewItemInfo()->top(); } else { break; // wtf } + if (_reversed) top = _height - top; + j = _reversed ? (l - i - 1) : i; } - HistoryItem *histItem = App::histItemById(itemChannel(_items[i].msgid), itemMsgId(_items[i].msgid)); - if (histItem) { - item = histItem; + if (LayoutMediaItem *media = _items.at(i)->toLayoutMediaItem()) { + item = media->getItem(); index = i; - HistoryMedia *media = item->getMedia(true); - if (media) { - bool out = item->out(), fromChannel = item->fromChannel(), outbg = out && !fromChannel; - int32 mw = media->maxWidth(), left = (fromChannel ? (st::msgMargin.left() + st::msgMargin.left()) / 2 : (out ? st::msgMargin.right() : st::msgMargin.left())) + ((mw < w) ? (fromChannel ? 0 : (out ? w - mw : 0)) : 0); - if (item->displayFromPhoto()) { - if (QRect(left, y + st::msgMargin.top() + media->countHeight(item, w) - st::msgPhotoSize, st::msgPhotoSize, st::msgPhotoSize).contains(m)) { - lnk = item->from()->lnk; - } - left += st::msgPhotoSkip; - } - TextLinkPtr link; - media->getState(link, cursorState, m.x() - left, m.y() - y - st::msgMargin.top(), item, w); - if (link) lnk = link; - } + media->getState(lnk, cursorState, m.x() - _rowsLeft, m.y() - _marginTop - top); } break; } @@ -1558,19 +988,33 @@ void OverviewInner::onUpdateSelected() { bool lnkChanged = false; if (lnk != textlnkOver()) { lnkChanged = true; - updateMsg(App::hoveredLinkItem()); + if (textlnkOver()) { + if (HistoryItem *item = App::hoveredLinkItem()) { + MsgId itemId = complexMsgId(item); + int32 itemIndex = oldMousedItemIndex; + fixItemIndex(itemIndex, itemId); + if (itemIndex >= 0) { + _items.at(itemIndex)->linkOut(textlnkOver()); + repaintItem(itemId, itemIndex); + } + } + } textlnkOver(lnk); - App::hoveredLinkItem(lnk ? item : 0); - updateMsg(App::hoveredLinkItem()); QToolTip::hideText(); + App::hoveredLinkItem(lnk ? item : 0); + if (textlnkOver()) { + if (item && index >= 0) { + _items.at(index)->linkOver(textlnkOver()); + repaintItem(complexMsgId(item), index); + } + } } else { App::mousedItem(item); } - if (lnkIndex != _lnkOverIndex || _mousedItem != oldMousedItem) { + if (_mousedItem != oldMousedItem) { lnkChanged = true; - if (oldMousedItem) updateMsg(oldMousedItem, oldMousedItemIndex); - _lnkOverIndex = lnkIndex; - if (item) updateMsg(item); + if (oldMousedItem) repaintItem(oldMousedItem, oldMousedItemIndex); + if (item) repaintItem(item); QToolTip::hideText(); } if (_cursorState == HistoryInDateCursorState && cursorState != HistoryInDateCursorState) { @@ -1579,14 +1023,14 @@ void OverviewInner::onUpdateSelected() { if (cursorState != _cursorState) { _cursorState = cursorState; } - if (lnk || lnkIndex || cursorState == HistoryInDateCursorState) { + if (lnk || cursorState == HistoryInDateCursorState) { _linkTipTimer.start(1000); } fixItemIndex(_dragItemIndex, _dragItem); fixItemIndex(_mousedItemIndex, _mousedItem); if (_dragAction == NoDrag) { - if (lnk || lnkIndex) { + if (lnk) { cur = style::cur_pointer; } } else { @@ -1602,92 +1046,60 @@ void OverviewInner::onUpdateSelected() { _dragAction = Selecting; } } - cur = (textlnkDown() || _lnkDownIndex) ? style::cur_pointer : style::cur_default; + cur = textlnkDown() ? style::cur_pointer : style::cur_default; if (_dragAction == Selecting) { bool canSelectMany = (_peer != 0); - if (_mousedItem == _dragItem && (lnk || lnkIndex) && !_selected.isEmpty() && _selected.cbegin().value() != FullItemSel) { + if (_mousedItem == _dragItem && lnk && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection) { bool afterSymbol = false, uponSymbol = false; uint16 second = 0; _selected[_dragItem] = 0; updateDragSelection(0, -1, 0, -1, false); } else if (canSelectMany) { - bool selectingDown = ((_type == OverviewPhotos || _type == OverviewAudioDocuments || _type == OverviewLinks) ? (_mousedItemIndex > _dragItemIndex) : (_mousedItemIndex < _dragItemIndex)) || (_mousedItemIndex == _dragItemIndex && (_type == OverviewPhotos ? (_dragStartPos.x() < m.x()) : (_dragStartPos.y() < m.y()))); + bool selectingDown = (_reversed ? (_mousedItemIndex < _dragItemIndex) : (_mousedItemIndex > _dragItemIndex)) || (_mousedItemIndex == _dragItemIndex && ((_type == OverviewPhotos || _type == OverviewVideos) ? (_dragStartPos.x() < m.x()) : (_dragStartPos.y() < m.y()))); MsgId dragSelFrom = _dragItem, dragSelTo = _mousedItem; int32 dragSelFromIndex = _dragItemIndex, dragSelToIndex = _mousedItemIndex; if (!itemHasPoint(dragSelFrom, dragSelFromIndex, _dragStartPos.x(), _dragStartPos.y())) { // maybe exclude dragSelFrom if (selectingDown) { - if (_type == OverviewPhotos) { - if (_dragStartPos.x() >= _vsize || ((_mousedItem == dragSelFrom) && (m.x() < _dragStartPos.x() + QApplication::startDragDistance()))) { - moveToNextItem(dragSelFrom, dragSelFromIndex, dragSelTo, 1); - } - } else if (_type == OverviewAudioDocuments) { - if (_dragStartPos.y() >= itemHeight(dragSelFrom, dragSelFromIndex) || ((_mousedItem == dragSelFrom) && (m.y() < _dragStartPos.y() + QApplication::startDragDistance()))) { - moveToNextItem(dragSelFrom, dragSelFromIndex, dragSelTo, 1); - } - } else if (_type == OverviewLinks) { - if (_dragStartPos.y() >= itemHeight(dragSelFrom, dragSelFromIndex) || ((_mousedItem == dragSelFrom) && (m.y() < _dragStartPos.y() + QApplication::startDragDistance()))) { - moveToNextItem(dragSelFrom, dragSelFromIndex, dragSelTo, 1); + if (_type == OverviewPhotos || _type == OverviewVideos) { + if (_dragStartPos.x() >= _rowWidth || ((_mousedItem == dragSelFrom) && (m.x() < _dragStartPos.x() + QApplication::startDragDistance()))) { + moveToNextItem(dragSelFrom, dragSelFromIndex, dragSelTo, -1); } } else { - if (_dragStartPos.y() >= (itemHeight(dragSelFrom, dragSelFromIndex) - st::msgMargin.bottom()) || ((_mousedItem == dragSelFrom) && (m.y() < _dragStartPos.y() + QApplication::startDragDistance()))) { - moveToNextItem(dragSelFrom, dragSelFromIndex, dragSelTo, -1); + if (_dragStartPos.y() >= itemHeight(dragSelFrom, dragSelFromIndex) || ((_mousedItem == dragSelFrom) && (m.y() < _dragStartPos.y() + QApplication::startDragDistance()))) { + moveToNextItem(dragSelFrom, dragSelFromIndex, dragSelTo, _reversed ? -1 : 1); } } } else { - if (_type == OverviewPhotos) { + if (_type == OverviewPhotos || _type == OverviewVideos) { if (_dragStartPos.x() < 0 || ((_mousedItem == dragSelFrom) && (m.x() >= _dragStartPos.x() - QApplication::startDragDistance()))) { - moveToNextItem(dragSelFrom, dragSelFromIndex, dragSelTo, -1); - } - } else if (_type == OverviewAudioDocuments) { - if (_dragStartPos.y() < 0 || ((_mousedItem == dragSelFrom) && (m.y() >= _dragStartPos.y() - QApplication::startDragDistance()))) { - moveToNextItem(dragSelFrom, dragSelFromIndex, dragSelTo, -1); - } - } else if (_type == OverviewLinks) { - if (_dragStartPos.y() < 0 || ((_mousedItem == dragSelFrom) && (m.y() >= _dragStartPos.y() - QApplication::startDragDistance()))) { - moveToNextItem(dragSelFrom, dragSelFromIndex, dragSelTo, -1); + moveToNextItem(dragSelFrom, dragSelFromIndex, dragSelTo, 1); } } else { - if (_dragStartPos.y() < st::msgMargin.top() || ((_mousedItem == dragSelFrom) && (m.y() >= _dragStartPos.y() - QApplication::startDragDistance()))) { - moveToNextItem(dragSelFrom, dragSelFromIndex, dragSelTo, 1); + if (_dragStartPos.y() < 0 || ((_mousedItem == dragSelFrom) && (m.y() >= _dragStartPos.y() - QApplication::startDragDistance()))) { + moveToNextItem(dragSelFrom, dragSelFromIndex, dragSelTo, _reversed ? 1 : -1); } } } } if (_dragItem != _mousedItem) { // maybe exclude dragSelTo if (selectingDown) { - if (_type == OverviewPhotos) { + if (_type == OverviewPhotos || _type == OverviewVideos) { if (m.x() < 0) { - moveToNextItem(dragSelTo, dragSelToIndex, dragSelFrom, -1); - } - } else if (_type == OverviewAudioDocuments) { - if (m.y() < 0) { moveToNextItem(dragSelTo, dragSelToIndex, dragSelFrom, 1); } - } else if (_type == OverviewLinks) { - if (m.y() < 0) { - moveToNextItem(dragSelTo, dragSelToIndex, dragSelFrom, -1); - } } else { - if (m.y() < st::msgMargin.top()) { - moveToNextItem(dragSelTo, dragSelToIndex, dragSelFrom, 1); + if (m.y() < 0) { + moveToNextItem(dragSelTo, dragSelToIndex, dragSelFrom, _reversed ? 1 : -1); } } } else { - if (_type == OverviewPhotos) { - if (m.x() >= _vsize) { - moveToNextItem(dragSelTo, dragSelToIndex, dragSelFrom, 1); - } - } else if (_type == OverviewAudioDocuments) { - if (m.y() >= itemHeight(dragSelTo, dragSelToIndex)) { + if (_type == OverviewPhotos || _type == OverviewVideos) { + if (m.x() >= _rowWidth) { moveToNextItem(dragSelTo, dragSelToIndex, dragSelFrom, -1); } - } else if (_type == OverviewLinks) { - if (m.y() >= itemHeight(dragSelTo, dragSelToIndex)) { - moveToNextItem(dragSelTo, dragSelToIndex, dragSelFrom, 1); - } } else { - if (m.y() >= itemHeight(dragSelTo, dragSelToIndex) - st::msgMargin.bottom()) { - moveToNextItem(dragSelTo, dragSelToIndex, dragSelFrom, -1); + if (m.y() >= itemHeight(dragSelTo, dragSelToIndex)) { + moveToNextItem(dragSelTo, dragSelToIndex, dragSelFrom, _reversed ? -1 : 1); } } } @@ -1696,20 +1108,20 @@ void OverviewInner::onUpdateSelected() { MsgId dragFirstAffected = dragSelFrom; int32 dragFirstAffectedIndex = dragSelFromIndex; while (dragFirstAffectedIndex >= 0 && itemMsgId(dragFirstAffected) <= 0) { - moveToNextItem(dragFirstAffected, dragFirstAffectedIndex, dragSelTo, ((selectingDown && (_type == OverviewPhotos || _type == OverviewAudioDocuments)) || (!selectingDown && (_type != OverviewPhotos && _type != OverviewAudioDocuments))) ? -1 : 1); + moveToNextItem(dragFirstAffected, dragFirstAffectedIndex, dragSelTo, selectingDown ? (_reversed ? -1 : 1) : (_reversed ? 1 : -1)); } if (dragFirstAffectedIndex >= 0) { SelectedItems::const_iterator i = _selected.constFind(dragFirstAffected); - dragSelecting = (i == _selected.cend() || i.value() != FullItemSel); + dragSelecting = (i == _selected.cend() || i.value() != FullSelection); } updateDragSelection(dragSelFrom, dragSelFromIndex, dragSelTo, dragSelToIndex, dragSelecting); } } else if (_dragAction == Dragging) { } - if (textlnkDown() || _lnkDownIndex) { + if (textlnkDown()) { cur = style::cur_pointer; - } else if (_dragAction == Selecting && !_selected.isEmpty() && _selected.cbegin().value() != FullItemSel) { + } else if (_dragAction == Selecting && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection) { if (!_dragSelFrom || !_dragSelTo) { cur = style::cur_text; } @@ -1735,12 +1147,6 @@ void OverviewInner::showLinkTip() { QRect r(dp.x() - dd, dp.y() - dd, 2 * dd, 2 * dd); if (lnk && !lnk->fullDisplayed()) { QToolTip::showText(_dragPos, lnk->readable(), this, r); - } else if (_lnkOverIndex) { - bool fullLink = false; - QString url = urlByIndex(_mousedItem, _mousedItemIndex, _lnkOverIndex, &fullLink); - if (!fullLink) { - QToolTip::showText(_dragPos, url, this, r); - } } else if (_cursorState == HistoryInDateCursorState && _dragAction == NoDrag && _mousedItem) { if (HistoryItem *item = App::histItemById(itemChannel(_mousedItem), itemMsgId(_mousedItem))) { QToolTip::showText(_dragPos, item->date.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)), this, r); @@ -1794,11 +1200,11 @@ void OverviewInner::enterEvent(QEvent *e) { void OverviewInner::leaveEvent(QEvent *e) { if (_selectedMsgId) { - updateMsg(_selectedMsgId, -1); + repaintItem(_selectedMsgId, -1); _selectedMsgId = 0; } if (textlnkOver()) { - updateMsg(App::hoveredLinkItem()); + repaintItem(App::hoveredLinkItem()); textlnkOver(TextLinkPtr()); App::hoveredLinkItem(0); if (!textlnkDown() && _cursor != style::cur_default) { @@ -1810,14 +1216,6 @@ void OverviewInner::leaveEvent(QEvent *e) { } void OverviewInner::resizeEvent(QResizeEvent *e) { - _width = width(); - _audioWidth = qMin(_width - st::profilePadding.left() - st::profilePadding.right(), int(st::profileMaxWidth)); - _audioLeft = (_width - _audioWidth) / 2; - _linksWidth = qMin(_width - st::linksSearchMargin.left() - st::linksSearchMargin.right(), int(st::linksMaxWidth)); - _linksLeft = (_width - _linksWidth) / 2; - _search.setGeometry(_linksLeft, st::linksSearchMargin.top(), _linksWidth, _search.height()); - _cancelSearch.move(_linksLeft + _linksWidth - _cancelSearch.width(), _search.y()); - showAll(true); onUpdateSelected(); update(); } @@ -1826,8 +1224,8 @@ void OverviewInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { if (_menu) { _menu->deleteLater(); _menu = 0; - updateMsg(App::contextItem()); - if (_selectedMsgId) updateMsg(_selectedMsgId, -1); + repaintItem(App::contextItem()); + if (_selectedMsgId) repaintItem(_selectedMsgId, -1); } if (e->reason() == QContextMenuEvent::Mouse) { dragActionUpdate(e->globalPos()); @@ -1848,7 +1246,7 @@ void OverviewInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { int32 isUponSelected = 0, hasSelected = 0; if (!_selected.isEmpty()) { isUponSelected = -1; - if (_selected.cbegin().value() == FullItemSel) { + if (_selected.cbegin().value() == FullSelection) { hasSelected = 2; if (!ignoreMousedItem && App::mousedItem() && _selected.constFind(App::mousedItem()->history() == _migrated ? -App::mousedItem()->id : App::mousedItem()->id) != _selected.cend()) { isUponSelected = 2; @@ -1874,7 +1272,7 @@ void OverviewInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { if (lnkPhoto) { _menu->addAction(lang(lng_context_open_image), this, SLOT(openContextUrl()))->setEnabled(true); } else { - if ((lnkVideo && lnkVideo->video()->loader) || (lnkAudio && lnkAudio->audio()->loader) || (lnkDocument && lnkDocument->document()->loader)) { + if ((lnkVideo && lnkVideo->video()->loading()) || (lnkAudio && lnkAudio->audio()->loading()) || (lnkDocument && lnkDocument->document()->loading())) { _menu->addAction(lang(lng_context_cancel_download), this, SLOT(cancelContextDownload()))->setEnabled(true); } else { if ((lnkVideo && !lnkVideo->video()->already(true).isEmpty()) || (lnkAudio && !lnkAudio->audio()->already(true).isEmpty()) || (lnkDocument && !lnkDocument->document()->already(true).isEmpty())) { @@ -1904,15 +1302,14 @@ void OverviewInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } } App::contextItem(App::hoveredLinkItem()); - updateMsg(App::contextItem()); - if (_selectedMsgId) updateMsg(_selectedMsgId, -1); + repaintItem(App::contextItem()); + if (_selectedMsgId) repaintItem(_selectedMsgId, -1); } else if (!ignoreMousedItem && App::mousedItem() && App::mousedItem()->channelId() == itemChannel(_mousedItem) && App::mousedItem()->id == itemMsgId(_mousedItem)) { - _contextMenuUrl = _lnkOverIndex ? urlByIndex(_mousedItem, _mousedItemIndex, _lnkOverIndex) : QString(); _menu = new PopupMenu(); - if ((_contextMenuLnk && dynamic_cast(_contextMenuLnk.data())) || (!_contextMenuUrl.isEmpty() && !urlIsEmail(_contextMenuUrl))) { + if ((_contextMenuLnk && dynamic_cast(_contextMenuLnk.data()))) { _menu->addAction(lang(lng_context_open_link), this, SLOT(openContextUrl()))->setEnabled(true); _menu->addAction(lang(lng_context_copy_link), this, SLOT(copyContextUrl()))->setEnabled(true); - } else if ((_contextMenuLnk && dynamic_cast(_contextMenuLnk.data())) || (!_contextMenuUrl.isEmpty() && urlIsEmail(_contextMenuUrl))) { + } else if ((_contextMenuLnk && dynamic_cast(_contextMenuLnk.data()))) { _menu->addAction(lang(lng_context_open_email), this, SLOT(openContextUrl()))->setEnabled(true); _menu->addAction(lang(lng_context_copy_email), this, SLOT(copyContextUrl()))->setEnabled(true); } else if (_contextMenuLnk && dynamic_cast(_contextMenuLnk.data())) { @@ -1944,8 +1341,8 @@ void OverviewInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } } App::contextItem(App::mousedItem()); - updateMsg(App::contextItem()); - if (_selectedMsgId) updateMsg(_selectedMsgId, -1); + repaintItem(App::contextItem()); + if (_selectedMsgId) repaintItem(_selectedMsgId, -1); } if (_menu) { connect(_menu, SIGNAL(destroyed(QObject*)), this, SLOT(onMenuDestroy(QObject*))); @@ -1954,25 +1351,52 @@ void OverviewInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } } -int32 OverviewInner::resizeToWidth(int32 nwidth, int32 scrollTop, int32 minHeight) { - if (width() == nwidth && minHeight == _minHeight) return scrollTop; +int32 OverviewInner::resizeToWidth(int32 nwidth, int32 scrollTop, int32 minHeight, bool force) { + if (!force && _width == nwidth && minHeight == _minHeight) return scrollTop; + + if ((_type == OverviewPhotos || _type == OverviewVideos) && _resizeIndex < 0) { + _resizeIndex = _photosInRow * ((scrollTop + minHeight) / int32(_rowWidth + st::overviewPhotoSkip)) + _photosInRow - 1; + _resizeSkip = (scrollTop + minHeight) - ((scrollTop + minHeight) / int32(_rowWidth + st::overviewPhotoSkip)) * int32(_rowWidth + st::overviewPhotoSkip); + } + + _width = nwidth; _minHeight = minHeight; - if (_type == OverviewAudioDocuments) { - _addToY = st::playlistPadding; + if (_type == OverviewPhotos || _type == OverviewVideos) { + _photosInRow = int32(_width - st::overviewPhotoSkip) / int32(st::overviewPhotoMinSize + st::overviewPhotoSkip); + _rowWidth = (int32(_width - st::overviewPhotoSkip) / _photosInRow) - st::overviewPhotoSkip; } else if (_type == OverviewLinks) { - _addToY = st::linksSearchMargin.top() + _search.height() + st::linksSearchMargin.bottom(); + _rowWidth = qMin(_width - st::linksSearchMargin.left() - st::linksSearchMargin.right(), int32(st::linksMaxWidth)); } else { - _addToY = (_height < _minHeight) ? (_minHeight - _height) : 0; + _rowWidth = qMin(_width - st::profilePadding.left() - st::profilePadding.right(), int32(st::profileMaxWidth)); } - if (_type == OverviewPhotos && _resizeIndex < 0) { - _resizeIndex = _photosInRow * ((scrollTop + minHeight) / int32(_vsize + st::overviewPhotoSkip)) + _photosInRow - 1; - _resizeSkip = (scrollTop + minHeight) - ((scrollTop + minHeight) / int32(_vsize + st::overviewPhotoSkip)) * int32(_vsize + st::overviewPhotoSkip); + _rowsLeft = (_width - _rowWidth) / 2; + + _search.setGeometry(_rowsLeft, st::linksSearchMargin.top(), _rowWidth, _search.height()); + _cancelSearch.moveToLeft(_rowsLeft + _rowWidth - _cancelSearch.width(), _search.y()); + + if (_type == OverviewPhotos || _type == OverviewVideos) { + for (int32 i = 0, l = _items.size(); i < l; ++i) { + _items.at(i)->resizeGetHeight(_rowWidth); + } + _height = countHeight(); + } else { + bool resize = (_type == OverviewLinks); + if (resize) _height = 0; + for (int32 i = 0, l = _items.size(); i < l; ++i) { + int32 h = _items.at(i)->resizeGetHeight(_rowWidth); + if (resize) { + _items.at(i)->getOverviewItemInfo()->setTop(_height + (_reversed ? h : 0)); + _height += h; + } + } } - resize(nwidth, height() > _minHeight ? height() : _minHeight); - showAll(); - if (_type == OverviewPhotos) { + recountMargins(); + + resize(_width, _marginTop + _height + _marginBottom); + + if (_type == OverviewPhotos || _type == OverviewVideos) { int32 newRow = _resizeIndex / _photosInRow; - return newRow * int32(_vsize + st::overviewPhotoSkip) + _resizeSkip - minHeight; + return newRow * int32(_rowWidth + st::overviewPhotoSkip) + _resizeSkip - minHeight; } return scrollTop; } @@ -1995,24 +1419,23 @@ MediaOverviewType OverviewInner::type() const { void OverviewInner::switchType(MediaOverviewType type) { if (_type != type) { - _selected.clear(); - _dragItemIndex = _mousedItemIndex = _dragSelFromIndex = _dragSelToIndex = -1; - _dragItem = _mousedItem = _dragSelFrom = _dragSelTo = 0; - _lnkOverIndex = _lnkDownIndex = 0; - _items.clear(); - _cached.clear(); + clear(); _type = type; - if (_type == OverviewLinks) { + _reversed = (_type != OverviewLinks && _type != OverviewDocuments); + if (_type == OverviewLinks || _type == OverviewDocuments) { _search.show(); } else { _search.hide(); } + if (!_search.getLastText().isEmpty()) { _search.setText(QString()); _search.updatePlaceholder(); onSearchUpdate(); } _cancelSearch.hide(); + + resizeToWidth(_width, 0, _minHeight, true); } mediaOverviewUpdated(); if (App::wnd()) App::wnd()->update(); @@ -2028,15 +1451,11 @@ void OverviewInner::openContextUrl() { App::hoveredLinkItem(App::contextItem()); _contextMenuLnk->onClick(Qt::LeftButton); App::hoveredLinkItem(was); - } else if (urlIsEmail(_contextMenuUrl)) { - EmailLink(_contextMenuUrl).onClick(Qt::LeftButton); - } else { - TextLink(_contextMenuUrl).onClick(Qt::LeftButton); } } void OverviewInner::copyContextUrl() { - QString enc = _contextMenuLnk ? _contextMenuLnk->encoded() : _contextMenuUrl; + QString enc = _contextMenuLnk ? _contextMenuLnk->encoded() : QString(); if (!enc.isEmpty()) { QApplication::clipboard()->setText(enc); } @@ -2046,7 +1465,7 @@ void OverviewInner::goToMessage() { HistoryItem *item = App::contextItem(); if (!item) return; - App::main()->showPeerHistory(item->history()->peer->id, item->id); + Ui::showPeerHistoryAtItem(item); } void OverviewInner::forwardMessage() { @@ -2064,17 +1483,20 @@ void OverviewInner::deleteMessage() { App::main()->deleteLayer((msg && msg->uploading()) ? -2 : -1); } +MsgId OverviewInner::complexMsgId(const HistoryItem *item) const { + return item ? ((item->history() == _migrated) ? -item->id : item->id) : 0; +} + void OverviewInner::selectMessage() { HistoryItem *item = App::contextItem(); if (!item || item->type() != HistoryItemMsg || item->serviceMsg()) return; - MsgId msgid = item->history() == _migrated ? -item->id : item->id; - if (!_selected.isEmpty() && _selected.cbegin().value() != FullItemSel) { + if (!_selected.isEmpty() && _selected.cbegin().value() != FullSelection) { _selected.clear(); - } else if (_selected.size() == MaxSelectedItems && _selected.constFind(msgid) == _selected.cend()) { + } else if (_selected.size() == MaxSelectedItems && _selected.constFind(complexMsgId(item)) == _selected.cend()) { return; } - _selected.insert(msgid, FullItemSel); + _selected.insert(complexMsgId(item), FullSelection); _overview->updateTopBarSelection(); _overview->update(); } @@ -2083,8 +1505,13 @@ void OverviewInner::cancelContextDownload() { VideoLink *lnkVideo = dynamic_cast(_contextMenuLnk.data()); AudioLink *lnkAudio = dynamic_cast(_contextMenuLnk.data()); DocumentLink *lnkDocument = dynamic_cast(_contextMenuLnk.data()); - mtpFileLoader *loader = lnkVideo ? lnkVideo->video()->loader : (lnkAudio ? lnkAudio->audio()->loader : (lnkDocument ? lnkDocument->document()->loader : 0)); - if (loader) loader->cancel(); + if (lnkVideo) { + lnkVideo->video()->cancel(); + } else if (lnkAudio) { + lnkAudio->audio()->cancel(); + } else if (lnkDocument) { + lnkDocument->document()->cancel(); + } } void OverviewInner::showContextInFolder() { @@ -2105,12 +1532,15 @@ void OverviewInner::saveContextFile() { } void OverviewInner::openContextFile() { + HistoryItem *was = App::hoveredLinkItem(); + App::hoveredLinkItem(App::contextItem()); VideoLink *lnkVideo = dynamic_cast(_contextMenuLnk.data()); AudioLink *lnkAudio = dynamic_cast(_contextMenuLnk.data()); DocumentLink *lnkDocument = dynamic_cast(_contextMenuLnk.data()); if (lnkVideo) VideoOpenLink(lnkVideo->video()).onClick(Qt::LeftButton); if (lnkAudio) AudioOpenLink(lnkAudio->audio()).onClick(Qt::LeftButton); if (lnkDocument) DocumentOpenLink(lnkDocument->document()).onClick(Qt::LeftButton); + App::hoveredLinkItem(was); } bool OverviewInner::onSearchMessages(bool searchCache) { @@ -2135,7 +1565,8 @@ bool OverviewInner::onSearchMessages(bool searchCache) { _searchQuery = q; _searchFull = _searchFullMigrated = false; int32 flags = (_history->peer->isChannel() && !_history->peer->isMegagroup()) ? MTPmessages_Search::flag_important_only : 0; - _searchRequest = MTP::send(MTPmessages_Search(MTP_int(flags), _history->peer->input, MTP_string(_searchQuery), MTP_inputMessagesFilterUrl(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(SearchPerPage)), rpcDone(&OverviewInner::searchReceived, SearchFromStart), rpcFail(&OverviewInner::searchFailed, SearchFromStart)); + MTPmessagesFilter filter = (_type == OverviewLinks) ? MTP_inputMessagesFilterUrl() : MTP_inputMessagesFilterDocument(); + _searchRequest = MTP::send(MTPmessages_Search(MTP_int(flags), _history->peer->input, MTP_string(_searchQuery), filter, MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(SearchPerPage)), rpcDone(&OverviewInner::searchReceived, SearchFromStart), rpcFail(&OverviewInner::searchFailed, SearchFromStart)); _searchQueries.insert(_searchRequest, _searchQuery); } return false; @@ -2151,13 +1582,13 @@ void OverviewInner::onNeedSearchMessages() { } void OverviewInner::onSearchUpdate() { - QString filterText = (_type == OverviewLinks) ? _search.text().trimmed() : QString(); + QString filterText = (_type == OverviewLinks || _type == OverviewDocuments) ? _search.text().trimmed() : QString(); bool inSearch = !filterText.isEmpty(), changed = (inSearch != _inSearch); _inSearch = inSearch; onNeedSearchMessages(); - if (filterText.isEmpty()) { + if (!_inSearch) { _searchCache.clear(); _searchQueries.clear(); _searchQuery = QString(); @@ -2197,15 +1628,15 @@ void OverviewInner::onMenuDestroy(QObject *obj) { if (_menu == obj) { _menu = 0; dragActionUpdate(QCursor::pos()); - updateMsg(App::contextItem()); - if (_selectedMsgId) updateMsg(_selectedMsgId, -1); + repaintItem(App::contextItem()); + if (_selectedMsgId) repaintItem(_selectedMsgId, -1); } } void OverviewInner::getSelectionState(int32 &selectedForForward, int32 &selectedForDelete) const { selectedForForward = selectedForDelete = 0; for (SelectedItems::const_iterator i = _selected.cbegin(), e = _selected.cend(); i != e; ++i) { - if (i.value() == FullItemSel) { + if (i.value() == FullSelection) { if (HistoryItem *item = App::histItemById(itemChannel(i.key()), itemMsgId(i.key()))) { if (item->canDelete()) { ++selectedForDelete; @@ -2220,7 +1651,7 @@ void OverviewInner::getSelectionState(int32 &selectedForForward, int32 &selected } void OverviewInner::clearSelectedItems(bool onlyTextSelection) { - if (!_selected.isEmpty() && (!onlyTextSelection || _selected.cbegin().value() != FullItemSel)) { + if (!_selected.isEmpty() && (!onlyTextSelection || _selected.cbegin().value() != FullSelection)) { _selected.clear(); _overview->updateTopBarSelection(); _overview->update(); @@ -2228,7 +1659,7 @@ void OverviewInner::clearSelectedItems(bool onlyTextSelection) { } void OverviewInner::fillSelectedItems(SelectedItemSet &sel, bool forDelete) { - if (_selected.isEmpty() || _selected.cbegin().value() != FullItemSel) return; + if (_selected.isEmpty() || _selected.cbegin().value() != FullSelection) return; for (SelectedItems::const_iterator i = _selected.cbegin(), e = _selected.cend(); i != e; ++i) { HistoryItem *item = App::histItemById(itemChannel(i.key()), itemMsgId(i.key())); @@ -2268,188 +1699,90 @@ void OverviewInner::onTouchScrollTimer() { } } -void OverviewInner::mediaOverviewUpdated(bool fromResize) { - int32 oldHeight = _height; - if (_type == OverviewLinks) { +void OverviewInner::mediaOverviewUpdated() { + if (_type == OverviewPhotos || _type == OverviewVideos) { + History::MediaOverview &o(_history->overview[_type]), *migratedOverview = _migrated ? &_migrated->overview[_type] : 0; + int32 migrateCount = migratedIndexSkip(); + int32 wasCount = _items.size(), fullCount = (migrateCount + o.size()); + int32 tocheck = qMin(fullCount, _itemsToBeLoaded); + _items.reserve(tocheck); + + int32 index = 0; + bool allGood = true; + for (int32 i = fullCount, l = fullCount - tocheck; i > l;) { + --i; + MsgId msgid = ((i < migrateCount) ? -migratedOverview->at(i) : o.at(i - migrateCount)); + if (allGood) { + if (_items.size() > index && complexMsgId(_items.at(index)->getItem()) == msgid) { + ++index; + continue; + } + allGood = false; + } + HistoryItem *item = App::histItemById(itemChannel(msgid), itemMsgId(msgid)); + LayoutMediaItem *layout = layoutPrepare(item); + if (!layout) continue; + + setLayoutItem(index, layout, 0); + ++index; + } + if (_items.size() > index) _items.resize(index); + + _height = countHeight(); + } else { + bool dateEveryMonth = (_type == OverviewDocuments), dateEveryDay = (_type == OverviewLinks); + bool withDates = (dateEveryMonth || dateEveryDay); + History::MediaOverview &o(_history->overview[_type]), *migratedOverview = _migrated ? &_migrated->overview[_type] : 0; int32 migrateCount = migratedIndexSkip(); int32 l = _inSearch ? _searchResults.size() : (migrateCount + o.size()), tocheck = qMin(l, _itemsToBeLoaded); - _items.reserve(2 * l); // day items + _items.reserve(withDates * tocheck); // day items - int32 y = 0, in = 0; + int32 top = 0, index = 0; bool allGood = true; QDate prevDate; for (int32 i = 0; i < tocheck; ++i) { - MsgId msgid = _inSearch ? _searchResults.at(l - i - 1) : ((l - i - 1 < migrateCount) ? -(*migratedOverview)[l - i - 1] : o.at(l - i - 1 - migrateCount)); + MsgId msgid = _inSearch ? _searchResults.at(l - i - 1) : ((l - i - 1 < migrateCount) ? -migratedOverview->at(l - i - 1) : o.at(l - i - 1 - migrateCount)); if (allGood) { - if (_items.size() > in && _items.at(in).msgid == msgid) { - prevDate = _items.at(in).date; - if (fromResize) { - _items[in].y = y; - y += _items[in].link->countHeight(_linksWidth); - } else { - y = (in + 1 < _items.size()) ? _items.at(in + 1).y : _height; + if (_items.size() > index && complexMsgId(_items.at(index)->getItem()) == msgid) { + if (withDates) prevDate = _items.at(index)->getItem()->date.date(); + top = _items.at(index)->getOverviewItemInfo()->top(); + if (!_reversed) { + top += _items.at(index)->height(); } - ++in; + ++index; continue; } - if (_items.size() > in + 1 && !_items.at(in).msgid && _items.at(in + 1).msgid == msgid) { // day item - if (fromResize) { - _items[in].y = y; - y += st::msgFont->height + st::linksDateMargin * 2 + st::linksBorder; + if (_items.size() > index + 1 && !_items.at(index)->toLayoutMediaItem() && complexMsgId(_items.at(index + 1)->getItem()) == msgid) { // day item + ++index; + if (withDates) prevDate = _items.at(index)->getItem()->date.date(); + top = _items.at(index)->getOverviewItemInfo()->top(); + if (!_reversed) { + top += _items.at(index)->height(); } - ++in; - prevDate = _items.at(in).date; - if (fromResize) { - _items[in].y = y; - y += _items[in].link->countHeight(_linksWidth); - } else { - y = (in + 1 < _items.size()) ? _items.at(in + 1).y : _height; - } - ++in; + ++index; continue; } allGood = false; } HistoryItem *item = App::histItemById(itemChannel(msgid), itemMsgId(msgid)); - if (!item) continue; + LayoutMediaItem *layout = layoutPrepare(item); + if (!layout) continue; - QDate date = item->date.date(); - if (!in || (in > 0 && date != prevDate)) { - if (_items.size() > in) { - _items[in].msgid = 0; - _items[in].date = date; - _items[in].y = y; - } else { - _items.push_back(CachedItem(0, date, y)); - } - y += st::msgFont->height + st::linksDateMargin * 2 + st::linksBorder; - ++in; - prevDate = date; - } - - if (_items.size() > in) { - _items[in] = CachedItem(msgid, item->date.date(), y); - _items[in].link = cachedLink(item); - y += _items[in].link->countHeight(_linksWidth); - } else { - _items.push_back(CachedItem(msgid, item->date.date(), y)); - _items.back().link = cachedLink(item); - y += _items.back().link->countHeight(_linksWidth); - } - ++in; - } - if (_items.size() != in) { - _items.resize(in); - } - if (_height != _addToY + y + st::linksSearchMargin.top()) { - _height = _addToY + y + st::linksSearchMargin.top(); - if (!fromResize) { - resize(width(), _minHeight > _height ? _minHeight : _height); - } - } - dragActionUpdate(QCursor::pos()); - update(); - } else if (_type != OverviewPhotos && _type != OverviewAudioDocuments) { - History::MediaOverview &o(_history->overview[_type]), *migratedOverview = _migrated ? &_migrated->overview[_type] : 0; - int32 migrateCount = migratedIndexSkip(); - int32 l = migrateCount + o.size(); - _items.reserve(2 * l); // day items - - int32 y = 0, in = 0; - int32 w = _width - st::msgMargin.left() - st::msgMargin.right(); - bool allGood = true; - QDate prevDate; - for (int32 i = 0; i < l; ++i) { - MsgId msgid = (l - i - 1 < migrateCount) ? -(*migratedOverview)[l - i - 1] : o.at(l - i - 1 - migrateCount); - if (allGood) { - if (_items.size() > in && _items.at(in).msgid == msgid) { - prevDate = _items.at(in).date; - if (fromResize) { - HistoryItem *item = App::histItemById(_channel, msgid); - HistoryMedia *media = item ? item->getMedia(true) : 0; - if (media) { - y += media->countHeight(item, w) + st::msgMargin.top() + st::msgMargin.bottom(); // item height - } - _items[in].y = y; - } else { - y = _items.at(in).y; - } - ++in; - continue; - } - if (_items.size() > in + 1 && !_items.at(in).msgid && _items.at(in + 1).msgid == msgid) { // day item - if (fromResize) { - y += st::msgServiceFont->height + st::msgServicePadding.top() + st::msgServicePadding.bottom() + st::msgServiceMargin.top() + st::msgServiceMargin.bottom(); // day item height - _items[in].y = y; - } - ++in; - prevDate = _items.at(in).date; - if (fromResize) { - HistoryItem *item = App::histItemById(_channel, msgid); - HistoryMedia *media = item ? item->getMedia(true) : 0; - if (media) { - y += media->countHeight(item, w) + st::msgMargin.top() + st::msgMargin.bottom(); // item height - } - _items[in].y = y; - } else { - y = _items.at(in).y; - } - ++in; - continue; - } - allGood = false; - } - HistoryItem *item = App::histItemById(itemChannel(msgid), itemMsgId(msgid)); - HistoryMedia *media = item ? item->getMedia(true) : 0; - if (!media) continue; - - QDate date = item->date.date(); - if (in > 0) { - if (date != prevDate) { // add day item - y += st::msgServiceFont->height + st::msgServicePadding.top() + st::msgServicePadding.bottom() + st::msgServiceMargin.top() + st::msgServiceMargin.bottom(); // day item height - if (_items.size() > in) { - _items[in].msgid = 0; - _items[in].date = prevDate; - _items[in].y = y; - } else { - _items.push_back(CachedItem(0, prevDate, y)); - } - ++in; + if (withDates) { + QDate date = item->date.date(); + if (!index || (index > 0 && (dateEveryMonth ? (date.month() != prevDate.month() || date.year() != prevDate.year()) : (date != prevDate)))) { + top += setLayoutItem(index, layoutPrepare(date, dateEveryMonth), top); + ++index; prevDate = date; } - } else { - prevDate = date; - } - media->initDimensions(item); - y += media->countHeight(item, w) + st::msgMargin.top() + st::msgMargin.bottom(); // item height - if (_items.size() > in) { - _items[in].msgid = msgid; - _items[in].date = date; - _items[in].y = y; - } else { - _items.push_back(CachedItem(msgid, date, y)); - } - ++in; - } - if (!_items.isEmpty()) { - y += st::msgServiceFont->height + st::msgServicePadding.top() + st::msgServicePadding.bottom() + st::msgServiceMargin.top() + st::msgServiceMargin.bottom(); // day item height - if (_items.size() > in) { - _items[in].msgid = 0; - _items[in].date = prevDate; - _items[in].y = y; - } else { - _items.push_back(CachedItem(0, prevDate, y)); - } - _items.resize(++in); - } - if (_height != y) { - _height = y; - if (!fromResize) { - _addToY = (_height < _minHeight) ? (_minHeight - _height) : 0; - resize(width(), _minHeight > _height ? _minHeight : _height); } + top += setLayoutItem(index, layout, top); + ++index; } + if (_items.size() > index) _items.resize(index); + + _height = top; } fixItemIndex(_dragSelFromIndex, _dragSelFrom); @@ -2457,17 +1790,23 @@ void OverviewInner::mediaOverviewUpdated(bool fromResize) { fixItemIndex(_mousedItemIndex, _mousedItem); fixItemIndex(_dragItemIndex, _dragItem); - if (!fromResize) { - resizeEvent(0); - if (_height != oldHeight && _type != OverviewLinks) { - _overview->scrollBy(_height - oldHeight); + recountMargins(); + int32 newHeight = _marginTop + _height + _marginBottom, deltaHeight = newHeight - height(); + if (deltaHeight) { + resize(_width, newHeight); + if (_type != OverviewLinks && _type != OverviewDocuments) { + _overview->scrollBy(deltaHeight); } + } else { + onUpdateSelected(); + update(); } } void OverviewInner::changingMsgId(HistoryItem *row, MsgId newId) { - MsgId oldId = (row->history() == _migrated) ? -row->id : row->id; + MsgId oldId = complexMsgId(row); if (row->history() == _migrated) newId = -newId; + if (_dragSelFrom == oldId) _dragSelFrom = newId; if (_dragSelTo == oldId) _dragSelTo = newId; if (_mousedItem == oldId) _mousedItem = newId; @@ -2481,25 +1820,6 @@ void OverviewInner::changingMsgId(HistoryItem *row, MsgId newId) { break; } } - if (_links.contains(oldId) && oldId != newId) { - if (_links.contains(newId)) { - for (CachedItems::iterator i = _items.begin(), e = _items.end(); i != e; ++i) { - if (i->msgid == newId && i->link) { - i->link = _links[oldId]; - break; - } - } - } - delete _links[newId]; - _links[newId] = _links[oldId]; - _links.remove(oldId); - } - for (CachedItems::iterator i = _items.begin(), e = _items.end(); i != e; ++i) { - if (i->msgid == oldId) { - i->msgid = newId; - break; - } - } } void OverviewInner::itemRemoved(HistoryItem *item) { @@ -2532,80 +1852,34 @@ void OverviewInner::itemRemoved(HistoryItem *item) { update(); } -void OverviewInner::itemResized(HistoryItem *item, bool scrollToIt) { - if (_type != OverviewPhotos && _type != OverviewAudioDocuments && _type != OverviewLinks) { - HistoryMedia *media = item ? item->getMedia(true) : 0; - if (!media) return; - - MsgId msgId = (item->history() == _migrated) ? -item->id : item->id; - for (int32 i = 0, l = _items.size(); i < l; ++i) { - if (_items[i].msgid == msgId) { - int32 from = 0; - if (i > 0) from = _items[i - 1].y; - - int32 oldh = _items[i].y - from; - int32 w = _width - st::msgMargin.left() - st::msgMargin.right(); - int32 newh = media->countHeight(item, w) + st::msgMargin.top() + st::msgMargin.bottom(); // item height - if (oldh != newh) { - newh -= oldh; - for (int32 j = i; j < l; ++j) { - _items[j].y += newh; - } - _height = _items[l - 1].y; - _addToY = (_height < _minHeight) ? (_minHeight - _height) : 0; - resize(width(), _minHeight > _height ? _minHeight : _height); - if (scrollToIt) { - if (_addToY + _height - from > _scroll->scrollTop() + _scroll->height()) { - _scroll->scrollToY(_addToY + _height - from - _scroll->height()); - } - if (_addToY + _height - _items[i].y < _scroll->scrollTop()) { - _scroll->scrollToY(_addToY + _height - _items[i].y); - } - } - update(); - } - break; - } - } - } -} - -void OverviewInner::msgUpdated(const HistoryItem *msg) { +void OverviewInner::repaintItem(const HistoryItem *msg) { if (!msg) return; + History *history = (msg->history() == _history) ? _history : (msg->history() == _migrated ? _migrated : 0); if (!history) return; int32 migrateindex = migratedIndexSkip(); MsgId msgid = msg->id; if (history->overviewHasMsgId(_type, msgid) && (history == _history || migrateindex > 0)) { - if (_type == OverviewPhotos) { - int32 index = history->overview[_type].indexOf(msgid); - if (index >= 0) { - if (history == _history) index += migrateindex; - float64 w = (float64(width() - st::overviewPhotoSkip) / _photosInRow); - int32 vsize = (_vsize + st::overviewPhotoSkip); - int32 row = (_photosToAdd + index) / _photosInRow, col = (_photosToAdd + index) % _photosInRow; - update(int32(col * w), _addToY + int32(row * vsize), qCeil(w), vsize); - } - } else if (_type == OverviewAudioDocuments) { - int32 index = history->overview[_type].indexOf(msgid); - if (index >= 0) { - if (history == _history) index += migrateindex; - update(_audioLeft, _addToY + int32(index * _audioHeight), _audioWidth, _audioHeight); - } - } else if (_type == OverviewLinks) { + if (_type == OverviewPhotos || _type == OverviewVideos) { if (history == _migrated) msgid = -msgid; for (int32 i = 0, l = _items.size(); i != l; ++i) { - if (_items[i].msgid == msgid) { - update(_linksLeft, _addToY + _items[i].y, _linksWidth, itemHeight(msgid, i)); + if (complexMsgId(_items.at(i)->getItem()) == msgid) { + int32 shownAtIndex = _items.size() - i - 1; + float64 w = (float64(width() - st::overviewPhotoSkip) / _photosInRow); + int32 vsize = (_rowWidth + st::overviewPhotoSkip); + int32 row = (_photosToAdd + shownAtIndex) / _photosInRow, col = (_photosToAdd + shownAtIndex) % _photosInRow; + update(int32(col * w), _marginTop + int32(row * vsize), qCeil(w), vsize); break; } } } else { if (history == _migrated) msgid = -msgid; for (int32 i = 0, l = _items.size(); i != l; ++i) { - if (_items[i].msgid == msgid) { - update(0, _addToY + _height - _items[i].y, _width, itemHeight(msgid, i)); + if (complexMsgId(_items.at(i)->getItem()) == msgid) { + int32 top = _items.at(i)->getOverviewItemInfo()->top(); + if (_reversed) top = _height - top; + update(_rowsLeft, _marginTop + top, _rowWidth, _items.at(i)->height()); break; } } @@ -2613,12 +1887,10 @@ void OverviewInner::msgUpdated(const HistoryItem *msg) { } } -void OverviewInner::showAll(bool recountHeights) { - int32 newHeight = height(); - if (_type == OverviewPhotos) { - _photosInRow = int32(width() - st::overviewPhotoSkip) / int32(st::overviewPhotoMinSize + st::overviewPhotoSkip); - _vsize = (int32(width() - st::overviewPhotoSkip) / _photosInRow) - st::overviewPhotoSkip; - int32 migratedCount = migratedIndexSkip(), count = migratedCount + _history->overview[_type].size(); +int32 OverviewInner::countHeight() { + int32 result = _height; + if (_type == OverviewPhotos || _type == OverviewVideos) { + int32 count = _items.size(); int32 migratedFullCount = _migrated ? _migrated->overviewCount(_type) : 0; int32 fullCount = migratedFullCount + _history->overviewCount(_type); if (fullCount > 0 && migratedFullCount >= 0) { @@ -2629,47 +1901,102 @@ void OverviewInner::showAll(bool recountHeights) { _photosToAdd = 0; } int32 rows = ((_photosToAdd + count) / _photosInRow) + (((_photosToAdd + count) % _photosInRow) ? 1 : 0); - newHeight = _height = (_vsize + st::overviewPhotoSkip) * rows + st::overviewPhotoSkip; - _addToY = (_height < _minHeight) ? (_minHeight - _height) : 0; - } else if (_type == OverviewAudioDocuments) { - int32 migratedCount = migratedIndexSkip(), count = migratedCount + _history->overview[_type].size(); - newHeight = _height = count * _audioHeight + 2 * st::playlistPadding; - _addToY = st::playlistPadding; - } else if (_type == OverviewLinks) { - if (recountHeights) { // recount heights because of texts - mediaOverviewUpdated(true); - } - newHeight = _height; - _addToY = st::linksSearchMargin.top() + _search.height() + st::linksSearchMargin.bottom(); - } else { - if (recountHeights && _type == OverviewVideos) { // recount heights because of captions - mediaOverviewUpdated(true); - } - newHeight = _height; - _addToY = (_height < _minHeight) ? (_minHeight - _height) : 0; + result = (_rowWidth + st::overviewPhotoSkip) * rows + st::overviewPhotoSkip; } + return result; +} - if (newHeight < _minHeight) { - newHeight = _minHeight; - } - if (height() != newHeight) { - resize(width(), newHeight); +void OverviewInner::recountMargins() { + if (_type == OverviewPhotos || _type == OverviewVideos) { + _marginBottom = 0; + _marginTop = qMax(_minHeight - _height - _marginBottom, 0); + } else if (_type == OverviewAudioDocuments) { + _marginTop = st::playlistPadding; + _marginBottom = qMax(_minHeight - _height - _marginTop, int32(st::playlistPadding)); + } else if (_type == OverviewLinks || _type == OverviewDocuments) { + _marginTop = st::linksSearchMargin.top() + _search.height() + st::linksSearchMargin.bottom(); + _marginBottom = qMax(_minHeight - _height - _marginTop, int32(st::playlistPadding)); + } else { + _marginBottom = st::playlistPadding; + _marginTop = qMax(_minHeight - _height - _marginBottom, int32(st::playlistPadding)); } } -OverviewInner::~OverviewInner() { - _dragAction = NoDrag; - for (CachedLinks::const_iterator i = _links.cbegin(), e = _links.cend(); i != e; ++i) { - delete i.value(); +LayoutMediaItem *OverviewInner::layoutPrepare(HistoryItem *item) { + if (!item) return 0; + + LayoutItems::const_iterator i = _layoutItems.cend(); + HistoryMedia *media = item->getMedia(); + if (_type == OverviewPhotos) { + if (media && media->type() == MediaTypePhoto) { + if ((i = _layoutItems.constFind(item)) == _layoutItems.cend()) { + i = _layoutItems.insert(item, new LayoutOverviewPhoto(static_cast(media)->photo(), item)); + i.value()->initDimensions(); + } + } + } else if (_type == OverviewVideos) { + if (media && media->type() == MediaTypeVideo) { + if ((i = _layoutItems.constFind(item)) == _layoutItems.cend()) { + i = _layoutItems.insert(item, new LayoutOverviewVideo(static_cast(media)->video(), item)); + i.value()->initDimensions(); + } + } + } else if (_type == OverviewAudios) { + if (media && media->type() == MediaTypeAudio) { + if ((i = _layoutItems.constFind(item)) == _layoutItems.cend()) { + i = _layoutItems.insert(item, new LayoutOverviewAudio(static_cast(media)->audio(), item)); + i.value()->initDimensions(); + } + } + } else if (_type == OverviewDocuments || _type == OverviewAudioDocuments) { + if (media && (media->type() == MediaTypeDocument || media->type() == MediaTypeGif)) { + if ((i = _layoutItems.constFind(item)) == _layoutItems.cend()) { + i = _layoutItems.insert(item, new LayoutOverviewDocument(media->getDocument(), item)); + i.value()->initDimensions(); + } + } + } else if (_type == OverviewLinks) { + if ((i = _layoutItems.constFind(item)) == _layoutItems.cend()) { + i = _layoutItems.insert(item, new LayoutOverviewLink(media, item)); + i.value()->initDimensions(); + } } - _links.clear(); + return (i == _layoutItems.cend()) ? 0 : i.value(); +} + +LayoutItem *OverviewInner::layoutPrepare(const QDate &date, bool month) { + int32 key = date.year() * 100 + date.month(); + if (!month) key = key * 100 + date.day(); + LayoutDates::const_iterator i = _layoutDates.constFind(key); + if (i == _layoutDates.cend()) { + i = _layoutDates.insert(key, new LayoutOverviewDate(date, month)); + i.value()->initDimensions(); + } + return i.value(); +} + +int32 OverviewInner::setLayoutItem(int32 index, LayoutItem *item, int32 top) { + if (_items.size() > index) { + _items[index] = item; + } else { + _items.push_back(item); + } + int32 h = item->resizeGetHeight(_rowWidth); + if (OverviewItemInfo *info = item->getOverviewItemInfo()) { + info->setTop(top + (_reversed ? h : 0)); + } + return h; +} + +OverviewInner::~OverviewInner() { + clear(); } OverviewWidget::OverviewWidget(QWidget *parent, PeerData *peer, MediaOverviewType type) : TWidget(parent) , _scroll(this, st::historyScroll, false) , _inner(this, &_scroll, peer, type) , _noDropResizeIndex(false) -, _a_show(animFunc(this, &OverviewWidget::animStep_show)) +, _a_show(animation(this, &OverviewWidget::step_show)) , _scrollSetAfterShow(0) , _scrollDelta(0) , _selCount(0) @@ -2706,7 +2033,7 @@ void OverviewWidget::onScroll() { int32 preloadThreshold = _scroll.height() * 5; bool needToPreload = false; do { - needToPreload = (type() == OverviewLinks) ? (_scroll.scrollTop() + preloadThreshold > _scroll.scrollTopMax()) : (_scroll.scrollTop() < preloadThreshold); + needToPreload = (type() == OverviewLinks || type() == OverviewDocuments) ? (_scroll.scrollTop() + preloadThreshold > _scroll.scrollTopMax()) : (_scroll.scrollTop() < preloadThreshold); if (!needToPreload || !_inner.preloadLocal()) { break; } @@ -2720,6 +2047,7 @@ void OverviewWidget::onScroll() { } void OverviewWidget::resizeEvent(QResizeEvent *e) { + _noDropResizeIndex = true; int32 st = _scroll.scrollTop(); _scroll.resize(size()); int32 newScrollTop = _inner.resizeToWidth(width(), st, height()); @@ -2727,10 +2055,9 @@ void OverviewWidget::resizeEvent(QResizeEvent *e) { newScrollTop += addToY; } if (newScrollTop != _scroll.scrollTop()) { - _noDropResizeIndex = true; _scroll.scrollToY(newScrollTop); - _noDropResizeIndex = false; } + _noDropResizeIndex = false; _topShadow.resize(width() - ((cWideMode() && !_inGrab) ? st::lineWidth : 0), st::lineWidth); _topShadow.moveToLeft((cWideMode() && !_inGrab) ? st::lineWidth : 0, 0); @@ -2755,40 +2082,7 @@ void OverviewWidget::paintEvent(QPaintEvent *e) { return; } - QRect r(e->rect()); - if (type() == OverviewPhotos || type() == OverviewAudioDocuments || type() == OverviewLinks) { - p.fillRect(r, st::white->b); - } else { - bool hasTopBar = !App::main()->topBar()->isHidden(), hasPlayer = !App::main()->player()->isHidden(); - QRect fill(0, 0, width(), App::main()->height()); - int fromy = (hasTopBar ? (-st::topBarHeight) : 0) + (hasPlayer ? (-st::playerHeight) : 0), x = 0, y = 0; - QPixmap cached = App::main()->cachedBackground(fill, x, y); - if (cached.isNull()) { - const QPixmap &pix(*cChatBackground()); - if (cTileBackground()) { - int left = r.left(), top = r.top(), right = r.left() + r.width(), bottom = r.top() + r.height(); - float64 w = pix.width() / cRetinaFactor(), h = pix.height() / cRetinaFactor(); - int sx = qFloor(left / w), sy = qFloor((top - fromy) / h), cx = qCeil(right / w), cy = qCeil((bottom - fromy) / h); - for (int i = sx; i < cx; ++i) { - for (int j = sy; j < cy; ++j) { - p.drawPixmap(QPointF(i * w, fromy + j * h), pix); - } - } - } else { - bool smooth = p.renderHints().testFlag(QPainter::SmoothPixmapTransform); - p.setRenderHint(QPainter::SmoothPixmapTransform); - - QRect to, from; - App::main()->backgroundParams(fill, to, from); - to.moveTop(to.top() + fromy); - p.drawPixmap(to, pix, from); - - if (!smooth) p.setRenderHint(QPainter::SmoothPixmapTransform, false); - } - } else { - p.drawPixmap(x, fromy + y, cached); - } - } + p.fillRect(e->rect(), st::white); } void OverviewWidget::contextMenuEvent(QContextMenuEvent *e) { @@ -2804,7 +2098,7 @@ void OverviewWidget::scrollBy(int32 add) { } void OverviewWidget::scrollReset() { - _scroll.scrollToY((type() == OverviewLinks) ? 0 : _scroll.scrollTopMax()); + _scroll.scrollToY((type() == OverviewLinks || type() == OverviewDocuments) ? 0 : _scroll.scrollTopMax()); } void OverviewWidget::paintTopBar(QPainter &p, float64 over, int32 decreaseWidth) { @@ -2875,7 +2169,7 @@ void OverviewWidget::updateTopBarSelection() { App::main()->topBar()->showSelected(_selCount > 0 ? _selCount : 0, (selectedForDelete == selectedForForward)); App::main()->topBar()->update(); } - if (App::wnd() && !App::wnd()->layerShown()) { + if (App::wnd() && !Ui::isLayerShown()) { _inner.activate(); } update(); @@ -2897,17 +2191,16 @@ int32 OverviewWidget::countBestScroll() const { if (playing) { int32 top = _inner.itemTop(playing.msgId); if (top >= 0) { - return snap(top - int(_scroll.height() - (st::mediaPadding.top() + st::mediaThumbSize + st::mediaPadding.bottom())) / 2, 0, _scroll.scrollTopMax()); + return snap(top - int(_scroll.height() - (st::msgPadding.top() + st::mediaThumbSize + st::msgPadding.bottom())) / 2, 0, _scroll.scrollTopMax()); } } - } else if (type() == OverviewLinks) { + } else if (type() == OverviewLinks || type() == OverviewDocuments) { return 0; } return _scroll.scrollTopMax(); } void OverviewWidget::fastShow(bool back, int32 lastScrollTop) { - stopGif(); resizeEvent(0); _scrollSetAfterShow = (lastScrollTop < 0 ? countBestScroll() : lastScrollTop); show(); @@ -2920,8 +2213,6 @@ void OverviewWidget::fastShow(bool back, int32 lastScrollTop) { void OverviewWidget::animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTopBarCache, bool back, int32 lastScrollTop) { if (App::app()) App::app()->mtpPause(); - stopGif(); - (back ? _cacheOver : _cacheUnder) = bgAnimCache; (back ? _cacheTopBarOver : _cacheTopBarUnder) = bgAnimTopBarCache; resizeEvent(0); @@ -2946,15 +2237,13 @@ void OverviewWidget::animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimT _inner.activate(); } -bool OverviewWidget::animStep_show(float64 ms) { +void OverviewWidget::step_show(float64 ms, bool timer) { float64 dt = ms / st::slideDuration; - bool res = true; if (dt >= 1) { _a_show.stop(); _sideShadow.setVisible(cWideMode()); _topShadow.show(); - res = false; a_coordUnder.finish(); a_coordOver.finish(); a_shadow.finish(); @@ -2969,9 +2258,10 @@ bool OverviewWidget::animStep_show(float64 ms) { a_coordOver.update(dt, st::slideFunction); a_shadow.update(dt, st::slideFunction); } - update(); - App::main()->topBar()->update(); - return res; + if (timer) { + update(); + App::main()->topBar()->update(); + } } void OverviewWidget::updateWideMode() { @@ -2999,9 +2289,15 @@ void OverviewWidget::changingMsgId(HistoryItem *row, MsgId newId) { } } -void OverviewWidget::msgUpdated(const HistoryItem *msg) { - if (peer() == msg->history()->peer || migratePeer() == msg->history()->peer) { - _inner.msgUpdated(msg); +void OverviewWidget::ui_repaintHistoryItem(const HistoryItem *item) { + if (peer() == item->history()->peer || migratePeer() == item->history()->peer) { + _inner.repaintItem(item); + } +} + +void OverviewWidget::notify_historyItemLayoutChanged(const HistoryItem *item) { + if (peer() == item->history()->peer || migratePeer() == item->history()->peer) { + _inner.onUpdateSelected(); } } @@ -3009,12 +2305,6 @@ void OverviewWidget::itemRemoved(HistoryItem *row) { _inner.itemRemoved(row); } -void OverviewWidget::itemResized(HistoryItem *row, bool scrollToIt) { - if (!row || row->history()->peer == peer() || row->history()->peer == migratePeer()) { - _inner.itemResized(row, scrollToIt); - } -} - void OverviewWidget::fillSelectedItems(SelectedItemSet &sel, bool forDelete) { _inner.fillSelectedItems(sel, forDelete); } @@ -3064,7 +2354,7 @@ void OverviewWidget::onPlayerSongChanged(const FullMsgId &msgId) { if (type() == OverviewAudioDocuments) { // int32 top = _inner.itemTop(msgId); // if (top > 0) { -// _scroll.scrollToY(snap(top - int(_scroll.height() - (st::mediaPadding.top() + st::mediaThumbSize + st::mediaPadding.bottom())) / 2, 0, _scroll.scrollTopMax())); +// _scroll.scrollToY(snap(top - int(_scroll.height() - (st::msgPadding.top() + st::mediaThumbSize + st::msgPadding.bottom())) / 2, 0, _scroll.scrollTopMax())); // } } } @@ -3124,10 +2414,8 @@ void OverviewWidget::onDeleteSelectedSure() { for (SelectedItemSet::const_iterator i = sel.cbegin(), e = sel.cend(); i != e; ++i) { i.value()->destroy(); } - if (App::main() && App::main()->peer() == peer()) { - App::main()->itemResized(0); - } - App::wnd()->hideLayer(); + Notify::historyItemsResized(); + Ui::hideLayer(); for (QMap >::const_iterator i = ids.cbegin(), e = ids.cend(); i != e; ++i) { App::main()->deleteMessages(i.key(), i.value()); @@ -3148,10 +2436,8 @@ void OverviewWidget::onDeleteContextSure() { App::main()->checkPeerHistory(h->peer); } - if (App::main() && (App::main()->peer() == h->peer || (App::main()->peer() && App::main()->peer() == h->peer->migrateTo()))) { - App::main()->itemResized(0); - } - App::wnd()->hideLayer(); + Notify::historyItemsResized(); + Ui::hideLayer(); if (wasOnServer) { App::main()->deleteMessages(h->peer, toDelete); diff --git a/Telegram/SourceFiles/overviewwidget.h b/Telegram/SourceFiles/overviewwidget.h index ac11901bb2..62ea9d915e 100644 --- a/Telegram/SourceFiles/overviewwidget.h +++ b/Telegram/SourceFiles/overviewwidget.h @@ -57,7 +57,7 @@ public: void touchScrollUpdated(const QPoint &screenPos); QPoint mapMouseToItem(QPoint p, MsgId itemId, int32 itemIndex); - int32 resizeToWidth(int32 nwidth, int32 scrollTop, int32 minHeight); // returns new scroll top + int32 resizeToWidth(int32 nwidth, int32 scrollTop, int32 minHeight, bool force = false); // returns new scroll top void dropResizeIndex(); PeerData *peer() const; @@ -67,12 +67,11 @@ public: void setSelectMode(bool enabled); - void mediaOverviewUpdated(bool fromResize = false); + void mediaOverviewUpdated(); void changingMsgId(HistoryItem *row, MsgId newId); - void msgUpdated(const HistoryItem *msg); + void repaintItem(const HistoryItem *msg); void itemRemoved(HistoryItem *item); - void itemResized(HistoryItem *item, bool scrollToIt); - + void getSelectionState(int32 &selectedForForward, int32 &selectedForDelete) const; void clearSelectedItems(bool onlyTextSelection = false); void fillSelectedItems(SelectedItemSet &sel, bool forDelete = true); @@ -111,6 +110,8 @@ public slots: private: + MsgId complexMsgId(const HistoryItem *item) const; + bool itemMigrated(MsgId msgId) const; ChannelId itemChannel(MsgId msgId) const; MsgId itemMsgId(MsgId msgId) const; @@ -123,8 +124,7 @@ private: void updateDragSelection(MsgId dragSelFrom, int32 dragSelFromIndex, MsgId dragSelTo, int32 dragSelToIndex, bool dragSelecting); - void updateMsg(HistoryItem *item); - void updateMsg(MsgId itemId, int32 itemIndex); + void repaintItem(MsgId itemId, int32 itemIndex); void touchResetSpeed(); void touchUpdateSpeed(); @@ -133,8 +133,8 @@ private: void applyDragSelection(); void addSelectionRange(int32 selFrom, int32 selTo, History *history); - QPixmap genPix(PhotoData *photo, int32 size); - void showAll(bool recountHeights = false); + void recountMargins(); + int32 countHeight(); OverviewWidget *_overview; ScrollArea *_scroll; @@ -142,53 +142,33 @@ private: PeerData *_peer; MediaOverviewType _type; + bool _reversed; History *_migrated, *_history; ChannelId _channel; - // photos - int32 _photosInRow, _photosToAdd, _vsize; - struct CachedSize { - int32 vsize; - bool medium; - QPixmap pix; - }; - typedef QMap CachedSizes; - CachedSizes _cached; bool _selMode; + uint32 itemSelectedValue(int32 index) const; - // audio documents - int32 _audioLeft, _audioWidth, _audioHeight; + int32 _rowsLeft, _rowWidth; - // shared links - int32 _linksLeft, _linksWidth; - struct Link { - Link() : width(0) { - } - Link(const QString &url, const QString &text) : url(url), text(text), width(st::msgFont->width(text)) { - } - QString url, text; - int32 width; - }; - struct CachedLink { - CachedLink() : titleWidth(0), page(0), pixw(0), pixh(0), text(st::msgMinWidth) { - } - CachedLink(HistoryItem *item); - int32 countHeight(int32 w); + typedef QVector Items; + Items _items; + typedef QMap LayoutItems; + LayoutItems _layoutItems; + typedef QMap LayoutDates; + LayoutDates _layoutDates; + LayoutMediaItem *layoutPrepare(HistoryItem *item); + LayoutItem *layoutPrepare(const QDate &date, bool month); + int32 setLayoutItem(int32 index, LayoutItem *item, int32 top); - QString title, letter; - int32 titleWidth; - WebPageData *page; - int32 pixw, pixh; - Text text; - QVector urls; - }; - typedef QMap CachedLinks; - CachedLinks _links; FlatInput _search; IconedButton _cancelSearch; QVector _results; int32 _itemsToBeLoaded; + // photos + int32 _photosInRow, _photosToAdd; + QTimer _searchTimer; QString _searchQuery; bool _inSearch, _searchFull, _searchFullMigrated; @@ -211,23 +191,7 @@ private: typedef QMap SearchQueries; SearchQueries _searchQueries; - CachedLink *cachedLink(HistoryItem *item); - - // other - struct CachedItem { - CachedItem() : msgid(0), y(0) { - } - CachedItem(MsgId msgid, const QDate &date, int32 y) : msgid(msgid), date(date), y(y) { - } - MsgId msgid; - QDate date; - int32 y; - CachedLink *link; - }; - typedef QVector CachedItems; - CachedItems _items; - - int32 _width, _height, _minHeight, _addToY; + int32 _width, _height, _minHeight, _marginTop, _marginBottom; QTimer _linkTipTimer; @@ -249,14 +213,9 @@ private: int32 _dragItemIndex; MsgId _mousedItem; int32 _mousedItemIndex; - int32 _lnkOverIndex, _lnkDownIndex; // for OverviewLinks, 0 - none, -1 - photo or title, > 0 - lnk index uint16 _dragSymbol; bool _dragWasInactive; - QString urlByIndex(MsgId msgid, int32 index, int32 lnkIndex, bool *fullShown = 0) const; - bool urlIsEmail(const QString &url) const; - - QString _contextMenuUrl; TextLinkPtr _contextMenuLnk; MsgId _dragSelFrom, _dragSelTo; @@ -307,17 +266,15 @@ public: void fastShow(bool back = false, int32 lastScrollTop = -1); void animShow(const QPixmap &oldAnimCache, const QPixmap &bgAnimTopBarCache, bool back = false, int32 lastScrollTop = -1); - bool animStep_show(float64 ms); + void step_show(float64 ms, bool timer); void updateWideMode(); void doneShow(); void mediaOverviewUpdated(PeerData *peer, MediaOverviewType type); void changingMsgId(HistoryItem *row, MsgId newId); - void msgUpdated(const HistoryItem *msg); void itemRemoved(HistoryItem *item); - void itemResized(HistoryItem *row, bool scrollToIt); - + QPoint clampMousePosition(QPoint point); void checkSelectingScroll(QPoint point); @@ -342,6 +299,10 @@ public: resizeEvent(0); } + void ui_repaintHistoryItem(const HistoryItem *item); + + void notify_historyItemLayoutChanged(const HistoryItem *item); + ~OverviewWidget(); public slots: diff --git a/Telegram/SourceFiles/passcodewidget.cpp b/Telegram/SourceFiles/passcodewidget.cpp index 5e97dc7a26..bb7bea235d 100644 --- a/Telegram/SourceFiles/passcodewidget.cpp +++ b/Telegram/SourceFiles/passcodewidget.cpp @@ -30,7 +30,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "gui/text.h" PasscodeWidget::PasscodeWidget(QWidget *parent) : TWidget(parent) -, _a_show(animFunc(this, &PasscodeWidget::animStep_show)) +, _a_show(animation(this, &PasscodeWidget::step_show)) , _passcode(this, st::passcodeInput) , _submit(this, lang(lng_passcode_submit), st::passcodeSubmit) , _logout(this, lang(lng_passcode_logout)) { @@ -129,13 +129,11 @@ void PasscodeWidget::animShow(const QPixmap &bgAnimCache, bool back) { show(); } -bool PasscodeWidget::animStep_show(float64 ms) { +void PasscodeWidget::step_show(float64 ms, bool timer) { float64 dt = ms / st::slideDuration; - bool res = true; if (dt >= 1) { _a_show.stop(); - res = false; a_coordUnder.finish(); a_coordOver.finish(); a_shadow.finish(); @@ -151,11 +149,10 @@ bool PasscodeWidget::animStep_show(float64 ms) { a_coordOver.update(dt, st::slideFunction); a_shadow.update(dt, st::slideFunction); } - update(); - return res; + if (timer) update(); } -void PasscodeWidget::animStop_show() { +void PasscodeWidget::stop_show() { _a_show.stop(); } diff --git a/Telegram/SourceFiles/passcodewidget.h b/Telegram/SourceFiles/passcodewidget.h index 6d824bff44..9fe8cbb547 100644 --- a/Telegram/SourceFiles/passcodewidget.h +++ b/Telegram/SourceFiles/passcodewidget.h @@ -34,8 +34,8 @@ public: void setInnerFocus(); void animShow(const QPixmap &bgAnimCache, bool back = false); - bool animStep_show(float64 ms); - void animStop_show(); + void step_show(float64 ms, bool timer); + void stop_show(); ~PasscodeWidget(); diff --git a/Telegram/SourceFiles/playerwidget.cpp b/Telegram/SourceFiles/playerwidget.cpp index 5a76d3f517..f55b0d9f42 100644 --- a/Telegram/SourceFiles/playerwidget.cpp +++ b/Telegram/SourceFiles/playerwidget.cpp @@ -41,7 +41,7 @@ PlayerWidget::PlayerWidget(QWidget *parent) : TWidget(parent) , _downCoord(0) , _downFrequency(AudioVoiceMsgFrequency) , _downProgress(0.) -, _stateAnim(animFunc(this, &PlayerWidget::stateStep)) +, _a_state(animation(this, &PlayerWidget::step_state)) , _msgmigrated(false) , _index(-1) , _migrated(0) @@ -54,7 +54,7 @@ PlayerWidget::PlayerWidget(QWidget *parent) : TWidget(parent) , _loaded(0) , a_progress(0., 0.) , a_loadProgress(0., 0.) -, _progressAnim(animFunc(this, &PlayerWidget::progressStep)) +, _a_progress(animation(this, &PlayerWidget::step_progress)) , _sideShadow(this, st::shadowColor) { resize(st::wndMinWidth, st::playerHeight); setMouseTracking(true); @@ -236,7 +236,7 @@ void PlayerWidget::updateOverState(OverState newState) { if (_over != OverNone) { _stateAnimations.remove(_over); _stateAnimations[-_over] = getms() - ((1. - _stateHovers[_over]) * st::playerDuration); - if (!_stateAnim.animating()) _stateAnim.start(); + if (!_a_state.animating()) _a_state.start(); } else { result = false; } @@ -244,7 +244,7 @@ void PlayerWidget::updateOverState(OverState newState) { if (newState != OverNone) { _stateAnimations.remove(-_over); _stateAnimations[_over] = getms() - (_stateHovers[_over] * st::playerDuration); - if (!_stateAnim.animating()) _stateAnim.start(); + if (!_a_state.animating()) _a_state.start(); setCursor(style::cur_pointer); } else { setCursor(style::cur_default); @@ -323,12 +323,9 @@ void PlayerWidget::preloadNext() { } if (next) { if (HistoryDocument *document = static_cast(next->getMedia())) { - if (document->document()->location(true).isEmpty() && document->document()->data.isEmpty()) { - if (!document->document()->loader) { - DocumentOpenLink::doOpen(document->document()); - document->document()->openOnSave = 0; - document->document()->openOnSaveMsgId = FullMsgId(); - } + DocumentData *d = document->getDocument(); + if (!d->loaded(true)) { + DocumentOpenLink::doOpen(d, ActionOnLoadNone); } } } @@ -337,7 +334,7 @@ void PlayerWidget::preloadNext() { void PlayerWidget::startPlay(const FullMsgId &msgId) { if (HistoryItem *item = App::histItemById(msgId)) { if (HistoryDocument *doc = static_cast(item->getMedia())) { - audioPlayer()->play(SongMsgId(doc->document(), item->fullId())); + audioPlayer()->play(SongMsgId(doc->getDocument(), item->fullId())); updateState(); } } @@ -375,9 +372,7 @@ bool PlayerWidget::seekingSong(const SongMsgId &song) const { return (_down == OverPlayback) && (song == _song); } -bool PlayerWidget::stateStep(float64 msc) { - bool result = false; - uint64 ms = getms(); +void PlayerWidget::step_state(uint64 ms, bool timer) { for (StateAnimations::iterator i = _stateAnimations.begin(); i != _stateAnimations.cend();) { int32 over = qAbs(i.key()); updateOverRect(OverState(over)); @@ -391,7 +386,9 @@ bool PlayerWidget::stateStep(float64 msc) { ++i; } } - return !_stateAnimations.isEmpty(); + if (_stateAnimations.isEmpty()) { + _a_state.stop(); + } } void PlayerWidget::mouseMoveEvent(QMouseEvent *e) { @@ -462,7 +459,7 @@ void PlayerWidget::mouseReleaseEvent(QMouseEvent *e) { _showPause = true; a_progress = anim::fvalue(_downProgress, _downProgress); - _progressAnim.stop(); + _a_progress.stop(); } update(); } else if (_down == OverClose && _over == OverClose) { @@ -573,19 +570,17 @@ void PlayerWidget::resizeEvent(QResizeEvent *e) { update(); } -bool PlayerWidget::progressStep(float64 ms) { +void PlayerWidget::step_progress(float64 ms, bool timer) { float64 dt = ms / (2 * AudioVoiceMsgUpdateView); - bool res = true; if (_duration && dt >= 1) { + _a_progress.stop(); a_progress.finish(); a_loadProgress.finish(); - res = false; } else { a_progress.update(qMin(dt, 1.), anim::linear); a_loadProgress.update(1. - (st::radialDuration / (st::radialDuration + ms)), anim::linear); } - rtlupdate(_playbackRect); - return res; + if (timer) rtlupdate(_playbackRect); } void PlayerWidget::updateState() { @@ -649,12 +644,12 @@ void PlayerWidget::updateState(SongMsgId playing, AudioPlayerState playingState, float64 progress = 0.; int32 loaded; float64 loadProgress = 1.; - if (duration || !_song || !_song.song || !_song.song->loader) { + if (duration || !_song || !_song.song || !_song.song->loading()) { time = (_down == OverPlayback) ? _time : formatDurationText(display); progress = duration ? snap(float64(position) / duration, 0., 1.) : 0.; loaded = duration ? _song.song->size : 0; } else { - loaded = _song.song->loader ? _song.song->loader->currentOffset() : 0; + loaded = _song.song->loading() ? _song.song->loadOffset() : 0; time = formatDownloadText(loaded, _song.song->size); loadProgress = snap(float64(loaded) / qMax(_song.song->size, 1), 0., 1.); } @@ -668,11 +663,11 @@ void PlayerWidget::updateState(SongMsgId playing, AudioPlayerState playingState, if (!songChanged && ((!stopped && duration && _duration) || (!duration && _loaded != loaded))) { a_progress.start(progress); a_loadProgress.start(loadProgress); - _progressAnim.start(); + _a_progress.start(); } else { a_progress = anim::fvalue(progress, progress); a_loadProgress = anim::fvalue(loadProgress, loadProgress); - _progressAnim.stop(); + _a_progress.stop(); } _position = position; _duration = duration; @@ -683,11 +678,11 @@ void PlayerWidget::updateState(SongMsgId playing, AudioPlayerState playingState, if (!songChanged && ((!stopped && duration && _duration) || (!duration && _loaded != loaded))) { a_progress.start(progress); a_loadProgress.start(loadProgress); - _progressAnim.start(); + _a_progress.start(); } else { a_progress = anim::fvalue(progress, progress); a_loadProgress = anim::fvalue(loadProgress, loadProgress); - _progressAnim.stop(); + _a_progress.stop(); } _position = position; _duration = duration; diff --git a/Telegram/SourceFiles/playerwidget.h b/Telegram/SourceFiles/playerwidget.h index fefac68acc..e854ffee79 100644 --- a/Telegram/SourceFiles/playerwidget.h +++ b/Telegram/SourceFiles/playerwidget.h @@ -43,8 +43,8 @@ public: void nextPressed(); void stopPressed(); - bool progressStep(float64 ms); - bool stateStep(float64 ms); + void step_progress(float64 ms, bool timer); + void step_state(uint64 ms, bool timer); void updateState(SongMsgId playing, AudioPlayerState playingState, int64 playingPosition, int64 playingDuration, int32 playingFrequency); void updateState(); @@ -97,7 +97,7 @@ private: float64 _stateHovers[OverStateCount]; typedef QMap StateAnimations; StateAnimations _stateAnimations; - Animation _stateAnim; + Animation _a_state; SongMsgId _song; bool _msgmigrated; @@ -114,7 +114,7 @@ private: int32 _loaded; anim::fvalue a_progress, a_loadProgress; - Animation _progressAnim; + Animation _a_progress; PlainShadow _sideShadow; diff --git a/Telegram/SourceFiles/profilewidget.cpp b/Telegram/SourceFiles/profilewidget.cpp index 79022abac8..c17b1c0e1c 100644 --- a/Telegram/SourceFiles/profilewidget.cpp +++ b/Telegram/SourceFiles/profilewidget.cpp @@ -67,7 +67,8 @@ ProfileInner::ProfileInner(ProfileWidget *profile, ScrollArea *scroll, PeerData , _aboutTop(0) , _aboutHeight(0) -, a_photo(0) +, a_photoOver(0) +, _a_photo(animation(this, &ProfileInner::step_photo)) , _photoOver(false) // migrate to megagroup @@ -232,11 +233,11 @@ void ProfileInner::onShareContact() { } void ProfileInner::onInviteToGroup() { - App::wnd()->showLayer(new ContactsBox(_peerUser)); + Ui::showLayer(new ContactsBox(_peerUser)); } void ProfileInner::onSendMessage() { - App::main()->showPeerHistory(_peer->id, ShowAtUnreadMsgId); + Ui::showPeerHistory(_peer, ShowAtUnreadMsgId); } void ProfileInner::onSearchInPeer() { @@ -302,37 +303,36 @@ void ProfileInner::onUpdatePhoto() { } PhotoCropBox *box = new PhotoCropBox(img, _peer); connect(box, SIGNAL(closed()), this, SLOT(onPhotoUpdateStart())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } void ProfileInner::onClearHistory() { if (_peerChannel) return; ConfirmBox *box = new ConfirmBox(_peer->isUser() ? lng_sure_delete_history(lt_contact, _peer->name) : lng_sure_delete_group_history(lt_group, _peer->name), lang(lng_box_delete), st::attentionBoxButton); connect(box, SIGNAL(confirmed()), this, SLOT(onClearHistorySure())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } void ProfileInner::onClearHistorySure() { - App::wnd()->hideLayer(); + Ui::hideLayer(); App::main()->clearHistory(_peer); } void ProfileInner::onDeleteConversation() { ConfirmBox *box = new ConfirmBox(_peer->isUser() ? lng_sure_delete_history(lt_contact, _peer->name) : (_peer->isChat() ? lng_sure_delete_and_exit(lt_group, _peer->name) : lang(_peer->isMegagroup() ? lng_sure_leave_group : lng_sure_leave_channel)), lang(_peer->isUser() ? lng_box_delete : lng_box_leave), _peer->isChannel() ? st::defaultBoxButton : st::attentionBoxButton); connect(box, SIGNAL(confirmed()), this, SLOT(onDeleteConversationSure())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } void ProfileInner::onDeleteConversationSure() { + Ui::hideLayer(); if (_peerUser) { App::main()->deleteConversation(_peer); } else if (_peerChat) { - App::wnd()->hideLayer(); - App::main()->showDialogs(); + Ui::showChatsList(); MTP::send(MTPmessages_DeleteChatUser(_peerChat->inputChat, App::self()->inputUser), App::main()->rpcDone(&MainWidget::deleteHistoryAfterLeave, _peer), App::main()->rpcFail(&MainWidget::leaveChatFailed, _peer)); } else if (_peerChannel) { - App::wnd()->hideLayer(); - App::main()->showDialogs(); + Ui::showChatsList(); if (_peerChannel->migrateFrom()) { App::main()->deleteConversation(_peerChannel->migrateFrom()); } @@ -344,13 +344,13 @@ void ProfileInner::onDeleteChannel() { if (!_peerChannel) return; ConfirmBox *box = new ConfirmBox(lang(_peer->isMegagroup() ? lng_sure_delete_group : lng_sure_delete_channel), lang(lng_box_delete), st::attentionBoxButton); connect(box, SIGNAL(confirmed()), this, SLOT(onDeleteChannelSure())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } void ProfileInner::onDeleteChannelSure() { if (_peerChannel) { - App::wnd()->hideLayer(); - App::main()->showDialogs(); + Ui::hideLayer(); + Ui::showChatsList(); if (_peerChannel->migrateFrom()) { App::main()->deleteConversation(_peerChannel->migrateFrom()); } @@ -382,13 +382,13 @@ bool ProfileInner::blockFail(const RPCError &error) { void ProfileInner::onAddParticipant() { if (_peerChat) { - App::wnd()->showLayer(new ContactsBox(_peerChat, MembersFilterRecent)); + Ui::showLayer(new ContactsBox(_peerChat, MembersFilterRecent)); } else if (_peerChannel && _peerChannel->mgInfo) { MembersAlreadyIn already; for (MegagroupInfo::LastParticipants::const_iterator i = _peerChannel->mgInfo->lastParticipants.cbegin(), e = _peerChannel->mgInfo->lastParticipants.cend(); i != e; ++i) { already.insert(*i, true); } - App::wnd()->showLayer(new ContactsBox(_peerChannel, MembersFilterRecent, already)); + Ui::showLayer(new ContactsBox(_peerChannel, MembersFilterRecent, already)); } } @@ -397,7 +397,7 @@ void ProfileInner::onMigrate() { ConfirmBox *box = new ConfirmBox(lang(lng_profile_migrate_sure)); connect(box, SIGNAL(confirmed()), this, SLOT(onMigrateSure())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } void ProfileInner::onMigrateSure() { @@ -459,7 +459,7 @@ void ProfileInner::onInvitationLink() { if (!_peerChat && !_peerChannel) return; QApplication::clipboard()->setText(_peerChat ? _peerChat->invitationUrl : (_peerChannel ? _peerChannel->invitationUrl : QString())); - App::wnd()->showLayer(new InformBox(lang(lng_group_invite_copied))); + Ui::showLayer(new InformBox(lang(lng_group_invite_copied))); } void ProfileInner::onPublicLink() { @@ -467,22 +467,22 @@ void ProfileInner::onPublicLink() { if (_peerChannel->isPublic()) { QApplication::clipboard()->setText(qsl("https://telegram.me/") + _peerChannel->username); - App::wnd()->showLayer(new InformBox(lang(lng_channel_public_link_copied))); + Ui::showLayer(new InformBox(lang(lng_channel_public_link_copied))); } else { - App::wnd()->showLayer(new SetupChannelBox(_peerChannel, true)); + Ui::showLayer(new SetupChannelBox(_peerChannel, true)); } } void ProfileInner::onMembers() { if (!_peerChannel) return; - App::wnd()->showLayer(new MembersBox(_peerChannel, MembersFilterRecent)); + Ui::showLayer(new MembersBox(_peerChannel, MembersFilterRecent)); } void ProfileInner::onAdmins() { if (_peerChannel) { - App::wnd()->showLayer(new MembersBox(_peerChannel, MembersFilterAdmins)); + Ui::showLayer(new MembersBox(_peerChannel, MembersFilterAdmins)); } else if (_peerChat) { - App::wnd()->showLayer(new ContactsBox(_peerChat, MembersFilterAdmins)); + Ui::showLayer(new ContactsBox(_peerChat, MembersFilterAdmins)); } } @@ -491,7 +491,7 @@ void ProfileInner::onCreateInvitationLink() { ConfirmBox *box = new ConfirmBox(lang(((_peerChat && _peerChat->invitationUrl.isEmpty()) || (_peerChannel && _peerChannel->invitationUrl.isEmpty())) ? lng_group_invite_about : lng_group_invite_about_new)); connect(box, SIGNAL(confirmed()), this, SLOT(onCreateInvitationLinkSure())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } void ProfileInner::onCreateInvitationLinkSure() { @@ -514,7 +514,7 @@ void ProfileInner::chatInviteDone(const MTPExportedChatInvite &result) { updateInvitationLink(); showAll(); resizeEvent(0); - App::wnd()->hideLayer(); + Ui::hideLayer(); } void ProfileInner::onFullPeerUpdated(PeerData *peer) { @@ -562,7 +562,7 @@ void ProfileInner::onBotSettings() { for (int32 i = 0, l = _peerUser->botInfo->commands.size(); i != l; ++i) { QString cmd = _peerUser->botInfo->commands.at(i).command; if (!cmd.compare(qsl("settings"), Qt::CaseInsensitive)) { - App::main()->showPeerHistory(_peer->id, ShowAtTheEndMsgId); + Ui::showPeerHistory(_peer, ShowAtTheEndMsgId); App::main()->sendBotCommand('/' + cmd, 0); return; } @@ -576,7 +576,7 @@ void ProfileInner::onBotHelp() { for (int32 i = 0, l = _peerUser->botInfo->commands.size(); i != l; ++i) { QString cmd = _peerUser->botInfo->commands.at(i).command; if (!cmd.compare(qsl("help"), Qt::CaseInsensitive)) { - App::main()->showPeerHistory(_peer->id, ShowAtTheEndMsgId); + Ui::showPeerHistory(_peer, ShowAtTheEndMsgId); App::main()->sendBotCommand('/' + cmd, 0); return; } @@ -767,11 +767,11 @@ void ProfileInner::paintEvent(QPaintEvent *e) { if (_photoLink || _peerUser || (_peerChat && !_peerChat->canEdit()) || (_peerChannel && !_amCreator)) { p.drawPixmap(_left, top, _peer->photo->pix(st::profilePhotoSize)); } else { - if (a_photo.current() < 1) { + if (a_photoOver.current() < 1) { p.drawPixmap(QPoint(_left, top), App::sprite(), st::setPhotoImg); } - if (a_photo.current() > 0) { - p.setOpacity(a_photo.current()); + if (a_photoOver.current() > 0) { + p.setOpacity(a_photoOver.current()); p.drawPixmap(QPoint(_left, top), App::sprite(), st::setOverPhotoImg); p.setOpacity(1); } @@ -991,8 +991,8 @@ void ProfileInner::mouseMoveEvent(QMouseEvent *e) { if (photoOver != _photoOver) { _photoOver = photoOver; if (!_photoLink && ((_peerChat && _peerChat->canEdit()) || (_peerChannel && _amCreator))) { - a_photo.start(_photoOver ? 1 : 0); - anim::start(this); + a_photoOver.start(_photoOver ? 1 : 0); + _a_photo.start(); } } if (!_photoLink && (_peerUser || (_peerChat && !_peerChat->canEdit()) || (_peerChannel && !_amCreator))) { @@ -1075,7 +1075,7 @@ void ProfileInner::mouseReleaseEvent(QMouseEvent *e) { _kickConfirm = _kickOver; ConfirmBox *box = new ConfirmBox(lng_profile_sure_kick(lt_user, _kickOver->firstName), lang(lng_box_remove)); connect(box, SIGNAL(confirmed()), this, SLOT(onKickConfirm())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } if (textlnkDown()) { TextLinkPtr lnk = textlnkDown(); @@ -1085,7 +1085,7 @@ void ProfileInner::mouseReleaseEvent(QMouseEvent *e) { App::searchByHashtag(lnk->encoded(), _peerChannel); } else { if (reBotCommand().match(lnk->encoded()).hasMatch()) { - App::main()->showPeerHistory(_peer->id, ShowAtTheEndMsgId); + Ui::showPeerHistory(_peer, ShowAtTheEndMsgId); } lnk->onClick(e->button()); } @@ -1104,7 +1104,7 @@ void ProfileInner::onKickConfirm() { if (_peerChat) { App::main()->kickParticipant(_peerChat, _kickConfirm); } else if (_peerChannel) { - App::wnd()->hideLayer(); + Ui::hideLayer(); App::api()->kickParticipant(_peerChannel, _kickConfirm); } } @@ -1236,7 +1236,7 @@ bool ProfileInner::updateMediaLinks(int32 *addToScroll) { } void ProfileInner::migrateDone(const MTPUpdates &updates) { - App::wnd()->hideLayer(); + Ui::hideLayer(); App::main()->sentUpdatesReceived(updates); const QVector *v = 0; switch (updates.type()) { @@ -1250,7 +1250,7 @@ void ProfileInner::migrateDone(const MTPUpdates &updates) { for (int32 i = 0, l = v->size(); i < l; ++i) { if (v->at(i).type() == mtpc_channel) { peer = App::channel(v->at(i).c_channel().vid.v); - App::main()->showPeerHistory(peer->id, ShowAtUnreadMsgId); + Ui::showPeerHistory(peer, ShowAtUnreadMsgId); QTimer::singleShot(ReloadChannelMembersTimeout, App::api(), SLOT(delayedRequestParticipantsCount())); } } @@ -1262,7 +1262,7 @@ void ProfileInner::migrateDone(const MTPUpdates &updates) { bool ProfileInner::migrateFail(const RPCError &error) { if (mtpIsFlood(error)) return false; - App::wnd()->hideLayer(); + Ui::hideLayer(); return true; } @@ -1425,17 +1425,15 @@ void ProfileInner::onCopyUsername() { QApplication::clipboard()->setText('@' + _peerUser->username); } -bool ProfileInner::animStep(float64 ms) { +void ProfileInner::step_photo(float64 ms, bool timer) { float64 dt = ms / st::setPhotoDuration; - bool res = true; if (dt >= 1) { - res = false; - a_photo.finish(); + _a_photo.stop(); + a_photoOver.finish(); } else { - a_photo.update(dt, anim::linear); + a_photoOver.update(dt, anim::linear); } - update(_left, st::profilePadding.top(), st::setPhotoSize, st::setPhotoSize); - return res; + if (timer) update(_left, st::profilePadding.top(), st::setPhotoSize, st::setPhotoSize); } PeerData *ProfileInner::peer() const { @@ -1724,7 +1722,7 @@ QString ProfileInner::overviewLinkText(int32 type, int32 count) { ProfileWidget::ProfileWidget(QWidget *parent, PeerData *peer) : TWidget(parent) , _scroll(this, st::setScroll) , _inner(this, &_scroll, peer) -, _a_show(animFunc(this, &ProfileWidget::animStep_show)) +, _a_show(animation(this, &ProfileWidget::step_show)) , _sideShadow(this, st::shadowColor) , _topShadow(this, st::shadowColor) , _inGrab(false) { @@ -1834,7 +1832,9 @@ int32 ProfileWidget::lastScrollTop() const { void ProfileWidget::animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTopBarCache, bool back, int32 lastScrollTop) { if (App::app()) App::app()->mtpPause(); - stopGif(); + if (!cAutoPlayGif()) { + App::stopGifItems(); + } (back ? _cacheOver : _cacheUnder) = bgAnimCache; (back ? _cacheTopBarOver : _cacheTopBarUnder) = bgAnimTopBarCache; @@ -1858,15 +1858,13 @@ void ProfileWidget::animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTo _inner.setFocus(); } -bool ProfileWidget::animStep_show(float64 ms) { +void ProfileWidget::step_show(float64 ms, bool timer) { float64 dt = ms / st::slideDuration; - bool res = true; if (dt >= 1) { _a_show.stop(); _sideShadow.setVisible(cWideMode()); _topShadow.show(); - res = false; a_coordUnder.finish(); a_coordOver.finish(); a_shadow.finish(); @@ -1883,9 +1881,10 @@ bool ProfileWidget::animStep_show(float64 ms) { a_coordOver.update(dt, st::slideFunction); a_shadow.update(dt, st::slideFunction); } - update(); - App::main()->topBar()->update(); - return res; + if (timer) { + update(); + App::main()->topBar()->update(); + } } void ProfileWidget::updateOnlineDisplay() { diff --git a/Telegram/SourceFiles/profilewidget.h b/Telegram/SourceFiles/profilewidget.h index d960e501d1..611f8a7a99 100644 --- a/Telegram/SourceFiles/profilewidget.h +++ b/Telegram/SourceFiles/profilewidget.h @@ -21,7 +21,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #pragma once class ProfileWidget; -class ProfileInner : public TWidget, public RPCSender, public Animated { +class ProfileInner : public TWidget, public RPCSender { Q_OBJECT public: @@ -44,7 +44,7 @@ public: void resizeEvent(QResizeEvent *e); void contextMenuEvent(QContextMenuEvent *e); - bool animStep(float64 ms); + void step_photo(float64 ms, bool timer); PeerData *peer() const; bool allMediaShown() const; @@ -162,7 +162,8 @@ private: Text _about; int32 _aboutTop, _aboutHeight; - anim::fvalue a_photo; + anim::fvalue a_photoOver; + Animation _a_photo; bool _photoOver; QString _errorText; @@ -235,7 +236,7 @@ public: int32 lastScrollTop() const; void animShow(const QPixmap &oldAnimCache, const QPixmap &bgAnimTopBarCache, bool back = false, int32 lastScrollTop = -1); - bool animStep_show(float64 ms); + void step_show(float64 ms, bool timer); void updateOnlineDisplay(); void updateOnlineDisplayTimer(); diff --git a/Telegram/SourceFiles/pspecific_linux.cpp b/Telegram/SourceFiles/pspecific_linux.cpp index 67137e9779..35e208f8ab 100644 --- a/Telegram/SourceFiles/pspecific_linux.cpp +++ b/Telegram/SourceFiles/pspecific_linux.cpp @@ -1,17 +1,17 @@ /* 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. - + Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org */ @@ -520,7 +520,7 @@ namespace { }; PsMainWindow::PsMainWindow(QWidget *parent) : QMainWindow(parent), -posInited(false), trayIcon(0), trayIconMenu(0), icon256(qsl(":/gui/art/icon256.png")), iconbig256(icon256), wndIcon(QPixmap::fromImage(icon256, Qt::ColorOnly)), _psCheckStatusIconLeft(100), _psLastIndicatorUpdate(0) { +posInited(false), trayIcon(0), trayIconMenu(0), icon256(qsl(":/gui/art/icon256.png")), iconbig256(icon256), wndIcon(QIcon::fromTheme("telegram", QIcon(QPixmap::fromImage(icon256, Qt::ColorOnly)))), _psCheckStatusIconLeft(100), _psLastIndicatorUpdate(0) { connect(&_psCheckStatusIconTimer, SIGNAL(timeout()), this, SLOT(psStatusIconCheck())); _psCheckStatusIconTimer.setSingleShot(false); @@ -667,7 +667,7 @@ void PsMainWindow::psUpdateCounter() { } else if (trayIcon) { int32 counter = App::histories().unreadFull - (cIncludeMuted() ? 0 : App::histories().unreadMuted); bool muted = cIncludeMuted() ? (App::histories().unreadMuted >= counter) : false; - + style::color bg = muted ? st::counterMuteBG : st::counterBG; QIcon iconSmall; iconSmall.addPixmap(QPixmap::fromImage(iconWithCounter(16, counter, bg, true), Qt::ColorOnly)); @@ -1072,7 +1072,7 @@ QString psCurrentLanguage() { namespace { QString _psHomeDir() { struct passwd *pw = getpwuid(getuid()); - return (pw && pw->pw_dir && strlen(pw->pw_dir)) ? (QString::fromLocal8Bit(pw->pw_dir) + '/') : QString(); + return (pw && pw->pw_dir && strlen(pw->pw_dir)) ? (fromUtf8Safe(pw->pw_dir) + '/') : QString(); } } @@ -1086,7 +1086,7 @@ QString psDownloadPath() { } QString psCurrentExeDirectory(int argc, char *argv[]) { - QString first = argc ? QString::fromLocal8Bit(argv[0]) : QString(); + QString first = argc ? fromUtf8Safe(argv[0]) : QString(); if (!first.isEmpty()) { QFileInfo info(first); if (info.isSymLink()) { @@ -1100,7 +1100,7 @@ QString psCurrentExeDirectory(int argc, char *argv[]) { } QString psCurrentExeName(int argc, char *argv[]) { - QString first = argc ? QString::fromLocal8Bit(argv[0]) : QString(); + QString first = argc ? fromUtf8Safe(argv[0]) : QString(); if (!first.isEmpty()) { QFileInfo info(first); if (info.isSymLink()) { @@ -1142,7 +1142,7 @@ void psOpenFile(const QString &name, bool openWith) { } void psShowInFolder(const QString &name) { - App::wnd()->hideLayer(true); + Ui::hideLayer(true); system((qsl("xdg-open ") + escapeShell(QFileInfo(name).absoluteDir().absolutePath())).toUtf8().constData()); } @@ -1172,13 +1172,15 @@ void psRegisterCustomScheme() { DEBUG_LOG(("App Info: placing .desktop file")); if (QDir(home + qsl(".local/")).exists()) { QString apps = home + qsl(".local/share/applications/"); + QString icons = home + qsl(".local/share/icons/"); if (!QDir(apps).exists()) QDir().mkpath(apps); + if (!QDir(icons).exists()) QDir().mkpath(icons); QString path = cWorkingDir() + qsl("tdata/"), file = path + qsl("telegramdesktop.desktop"); QDir().mkpath(path); QFile f(file); if (f.open(QIODevice::WriteOnly)) { - QString icon = path + qsl("icon.png"); + QString icon = icons + qsl("telegram.png"); if (!QFile(icon).exists()) { if (QFile(qsl(":/gui/art/icon256.png")).copy(icon)) { DEBUG_LOG(("App Info: Icon copied to 'tdata'")); @@ -1194,7 +1196,7 @@ void psRegisterCustomScheme() { s << "Name=Telegram Desktop\n"; s << "Comment=Official desktop version of Telegram messaging app\n"; s << "Exec=" << escapeShell(cExeDir() + cExeName()) << " -- %u\n"; - s << "Icon=" << icon << "\n"; + s << "Icon=telegram\n"; s << "Terminal=false\n"; s << "StartupWMClass=Telegram\n"; s << "Type=Application\n"; diff --git a/Telegram/SourceFiles/pspecific_mac.cpp b/Telegram/SourceFiles/pspecific_mac.cpp index 61cc6d1db4..266da76f12 100644 --- a/Telegram/SourceFiles/pspecific_mac.cpp +++ b/Telegram/SourceFiles/pspecific_mac.cpp @@ -1,17 +1,17 @@ /* 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. - + Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org */ @@ -76,14 +76,14 @@ void MacPrivate::notifyClicked(unsigned long long peer, int msgid) { tomsg = false; } } - App::main()->showPeerHistory(history->peer->id, tomsg ? msgid : ShowAtUnreadMsgId); + Ui::showPeerHistory(history, tomsg ? msgid : ShowAtUnreadMsgId); App::wnd()->notifyClear(history); } } void MacPrivate::notifyReplied(unsigned long long peer, int msgid, const char *str) { History *history = App::history(PeerId(peer)); - + App::main()->sendMessage(history, QString::fromUtf8(str), (msgid > 0 && !history->peer->isUser()) ? msgid : 0, false); } @@ -592,7 +592,7 @@ QString psDownloadPath() { } QString psCurrentExeDirectory(int argc, char *argv[]) { - QString first = argc ? QString::fromLocal8Bit(argv[0]) : QString(); + QString first = argc ? fromUtf8Safe(argv[0]) : QString(); if (!first.isEmpty()) { QFileInfo info(first); if (info.exists()) { @@ -603,7 +603,7 @@ QString psCurrentExeDirectory(int argc, char *argv[]) { } QString psCurrentExeName(int argc, char *argv[]) { - QString first = argc ? QString::fromLocal8Bit(argv[0]) : QString(); + QString first = argc ? fromUtf8Safe(argv[0]) : QString(); if (!first.isEmpty()) { QFileInfo info(first); if (info.exists()) { diff --git a/Telegram/SourceFiles/pspecific_mac_p.mm b/Telegram/SourceFiles/pspecific_mac_p.mm index edcf519488..f5013dd781 100644 --- a/Telegram/SourceFiles/pspecific_mac_p.mm +++ b/Telegram/SourceFiles/pspecific_mac_p.mm @@ -208,10 +208,10 @@ public: } - (void) userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification { - NSNumber *instObj = [[notification userInfo] objectForKey:@"inst"]; + NSNumber *instObj = [[notification userInfo] objectForKey:@"launch"]; unsigned long long instLong = instObj ? [instObj unsignedLongLongValue] : 0; DEBUG_LOG(("Received notification with instance %1").arg(instLong)); - if (instLong != cInstance()) { // other app instance notification + if (instLong != Global::LaunchId()) { // other app instance notification return; } if (notification.activationType == NSUserNotificationActivationTypeReplied) { @@ -283,8 +283,8 @@ void PsMacWindowPrivate::showNotify(uint64 peer, int32 msgId, const QPixmap &pix NSUserNotification *notification = [[NSUserNotification alloc] init]; NSImage *img = qt_mac_create_nsimage(pix); - DEBUG_LOG(("Sending notification with userinfo: peer %1, msgId %2 and instance %3").arg(peer).arg(msgId).arg(cInstance())); - [notification setUserInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedLongLong:peer],@"peer",[NSNumber numberWithInt:msgId],@"msgid",[NSNumber numberWithUnsignedLongLong:cInstance()],@"inst",nil]]; + DEBUG_LOG(("Sending notification with userinfo: peer %1, msgId %2 and instance %3").arg(peer).arg(msgId).arg(Global::LaunchId())); + [notification setUserInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedLongLong:peer],@"peer",[NSNumber numberWithInt:msgId],@"msgid",[NSNumber numberWithUnsignedLongLong:Global::LaunchId()],@"launch",nil]]; [notification setTitle:QNSString(title).s()]; [notification setSubtitle:QNSString(subtitle).s()]; @@ -352,7 +352,7 @@ void PsMacWindowPrivate::clearNotifies(unsigned long long peer) { NSArray *notifies = [center deliveredNotifications]; for (id notify in notifies) { NSDictionary *dict = [notify userInfo]; - if ([[dict objectForKey:@"peer"] unsignedLongLongValue] == peer && [[dict objectForKey:@"inst"] unsignedLongLongValue] == cInstance()) { + if ([[dict objectForKey:@"peer"] unsignedLongLongValue] == peer && [[dict objectForKey:@"launch"] unsignedLongLongValue] == Global::LaunchId()) { [center removeDeliveredNotification:notify]; } } diff --git a/Telegram/SourceFiles/pspecific_wnd.cpp b/Telegram/SourceFiles/pspecific_wnd.cpp index 8bfef97511..9db1afd292 100644 --- a/Telegram/SourceFiles/pspecific_wnd.cpp +++ b/Telegram/SourceFiles/pspecific_wnd.cpp @@ -106,7 +106,7 @@ namespace { bool sessionLoggedOff = false; UINT tbCreatedMsgId = 0; - + ComPtr taskbarList; ComPtr toastNotificationManager; @@ -132,7 +132,7 @@ namespace { HWND createTaskbarHider() { HINSTANCE appinst = (HINSTANCE)GetModuleHandle(0); HWND hWnd = 0; - + QString cn = QString("TelegramTaskbarHider"); LPCWSTR _cn = (LPCWSTR)cn.utf16(); WNDCLASSEX wc; @@ -153,7 +153,7 @@ namespace { DEBUG_LOG(("Application Error: could not register taskbar hider window class, error: %1").arg(GetLastError())); return hWnd; } - + hWnd = CreateWindowEx(WS_EX_TOOLWINDOW, _cn, 0, WS_POPUP, 0, 0, 0, 0, 0, 0, appinst, 0); if (!hWnd) { DEBUG_LOG(("Application Error: could not create taskbar hider window class, error: %1").arg(GetLastError())); @@ -186,12 +186,12 @@ namespace { hwnds[i] = 0; } } - + void setColor(QColor c) { r = c.red(); g = c.green(); b = c.blue(); - + if (!hwnds[0]) return; Gdiplus::SolidBrush brush(Gdiplus::Color(_alphas[0], r, g, b)); for (int i = 0; i < 4; ++i) { @@ -251,7 +251,7 @@ namespace { Gdiplus::GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; Gdiplus::Status gdiRes = Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); - + if (gdiRes != Gdiplus::Ok) { LOG(("Application Error: could not init GDI+, error: %1").arg((int)gdiRes)); return false; @@ -460,7 +460,7 @@ namespace { from = _fullsize - (_size - _shift); max_w *= 2; for (int i = 0; i < 4; i += 2) { - DeleteObject(bitmaps[i]); + DeleteObject(bitmaps[i]); bitmaps[i] = CreateCompatibleBitmap(screenDC, max_w, _size); SelectObject(dcs[i], bitmaps[i]); } @@ -549,7 +549,7 @@ namespace { _y = y; _w = w; _h = h; - + if (hidden && (changes & _PsShadowShown)) { for (int i = 0; i < 4; ++i) { SetWindowPos(hwnds[i], hwnd, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOACTIVATE); @@ -680,7 +680,7 @@ namespace { typedef HRESULT (FAR STDAPICALLTYPE *f_shOpenWithDialog)(HWND hwndParent, const OPENASINFO *poainfo); f_shOpenWithDialog shOpenWithDialog = 0; - + typedef HRESULT (FAR STDAPICALLTYPE *f_shAssocEnumHandlers)(PCWSTR pszExtra, ASSOC_FILTER afFilter, IEnumAssocHandlers **ppEnumHandler); f_shAssocEnumHandlers shAssocEnumHandlers = 0; @@ -695,7 +695,7 @@ namespace { typedef HRESULT (FAR STDAPICALLTYPE *f_shQueryUserNotificationState)(QUERY_USER_NOTIFICATION_STATE *pquns); f_shQueryUserNotificationState shQueryUserNotificationState = 0; - + typedef HRESULT (FAR STDAPICALLTYPE *f_setCurrentProcessExplicitAppUserModelID)(__in PCWSTR AppID); f_setCurrentProcessExplicitAppUserModelID setCurrentProcessExplicitAppUserModelID = 0; @@ -849,7 +849,7 @@ namespace { QTimer::singleShot(0, Application::wnd(), SLOT(updateCounter())); Application::wnd()->update(); } return false; - + case WM_NCPAINT: if (QSysInfo::WindowsVersion >= QSysInfo::WV_WINDOWS8) return false; *result = 0; return true; case WM_NCCALCSIZE: { @@ -936,7 +936,7 @@ namespace { case HitTestBottomLeft: *result = HTBOTTOMLEFT; break; case HitTestLeft: *result = HTLEFT; break; case HitTestTopLeft: *result = HTTOPLEFT; break; - case HitTestNone: + case HitTestNone: default: *result = HTTRANSPARENT; break; }; } return true; @@ -951,7 +951,7 @@ namespace { GetWindowRect(hWnd, &r); HitTestType res = Application::wnd()->hitTest(QPoint(p.x - r.left + dleft, p.y - r.top + dtop)); switch (res) { - case HitTestIcon: + case HitTestIcon: if (menuHidden && getms() < menuHidden + 10) { menuHidden = 0; if (getms() < menuShown + GetDoubleClickTime()) { @@ -1229,7 +1229,7 @@ void PsMainWindow::psInitFrameless() { if (!ps_hWnd) return; if (useWtsapi) wtsRegisterSessionNotification(ps_hWnd, NOTIFY_FOR_THIS_SESSION); - + if (frameless) { setWindowFlags(Qt::FramelessWindowHint); } @@ -1395,7 +1395,7 @@ void PsMainWindow::psUpdateMargins() { RECT w, m; GetWindowRect(ps_hWnd, &w); m = w; - + HMONITOR hMonitor = MonitorFromRect(&w, MONITOR_DEFAULTTONEAREST); if (hMonitor) { MONITORINFO mi; @@ -1915,7 +1915,7 @@ void psDoFixPrevious() { LSTATUS newKeyRes2 = RegOpenKeyEx(HKEY_CURRENT_USER, newKeyStr2.toStdWString().c_str(), 0, KEY_READ, &newKey2); LSTATUS oldKeyRes1 = RegOpenKeyEx(HKEY_LOCAL_MACHINE, oldKeyStr1.toStdWString().c_str(), 0, KEY_READ, &oldKey1); LSTATUS oldKeyRes2 = RegOpenKeyEx(HKEY_LOCAL_MACHINE, oldKeyStr2.toStdWString().c_str(), 0, KEY_READ, &oldKey2); - + bool existNew1 = (newKeyRes1 == ERROR_SUCCESS) && (RegQueryValueEx(newKey1, L"InstallDate", 0, &checkType, (BYTE*)checkStr, &checkSize) == ERROR_SUCCESS); checkSize = bufSize * 2; bool existNew2 = (newKeyRes2 == ERROR_SUCCESS) && (RegQueryValueEx(newKey2, L"InstallDate", 0, &checkType, (BYTE*)checkStr, &checkSize) == ERROR_SUCCESS); checkSize = bufSize * 2; bool existOld1 = (oldKeyRes1 == ERROR_SUCCESS) && (RegQueryValueEx(oldKey1, L"InstallDate", 0, &checkType, (BYTE*)checkStr, &checkSize) == ERROR_SUCCESS); checkSize = bufSize * 2; @@ -2048,7 +2048,7 @@ bool psShowOpenWithMenu(int x, int y, const QString &file) { ULONG ulFetched = 0; hr = assocHandlers->Next(1, &handler, &ulFetched); if (FAILED(hr) || hr == S_FALSE || !ulFetched) break; - + LPWSTR name = 0; if (SUCCEEDED(handler->GetUIName(&name))) { LPWSTR icon = 0; @@ -2063,7 +2063,7 @@ bool psShowOpenWithMenu(int x, int y, const QString &file) { } else { handler->Release(); } - } while (hr != S_FALSE); + } while (hr != S_FALSE); assocHandlers->Release(); } @@ -2201,7 +2201,7 @@ namespace { } return true; } - + bool _psSetKeyValue(HKEY rkey, LPCWSTR value, QString v) { static const int bufSize = 4096; DWORD defaultType, defaultSize = bufSize * 2; @@ -2352,7 +2352,6 @@ void psUpdateOverlayed(TWidget *widget) { if (!wv) widget->setAttribute(Qt::WA_WState_Visible, false); } -#ifdef _NEED_WIN_GENERATE_DUMP static const WCHAR *_programName = AppName; // folder in APPDATA, if current path is unavailable for writing static const WCHAR *_exeName = L"Telegram.exe"; @@ -2396,16 +2395,16 @@ HANDLE _generateDumpFileAtPath(const WCHAR *path) { GetLocalTime(&stLocalTime); if (cBetaVersion()) { - wsprintf(szFileName, L"%s%s-%ld-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp", - szPath, szExeName, cBetaVersion(), - stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay, - stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond, + wsprintf(szFileName, L"%s%s-%ld-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp", + szPath, szExeName, cBetaVersion(), + stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay, + stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond, GetCurrentProcessId(), GetCurrentThreadId()); } else { - wsprintf(szFileName, L"%s%s-%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp", - szPath, szExeName, AppVersionStr, - stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay, - stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond, + wsprintf(szFileName, L"%s%s-%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp", + szPath, szExeName, AppVersionStr, + stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay, + stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond, GetCurrentProcessId(), GetCurrentThreadId()); } return CreateFile(szFileName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0); @@ -2455,9 +2454,16 @@ void _generateDump(EXCEPTION_POINTERS* pExceptionPointers) { LONG CALLBACK _exceptionFilter(EXCEPTION_POINTERS* pExceptionPointers) { _generateDump(pExceptionPointers); - return _oldWndExceptionFilter ? (*_oldWndExceptionFilter)(pExceptionPointers) : EXCEPTION_CONTINUE_SEARCH; + return _oldWndExceptionFilter ? (*_oldWndExceptionFilter)(pExceptionPointers) : EXCEPTION_CONTINUE_SEARCH; +} + +// see http://www.codeproject.com/Articles/154686/SetUnhandledExceptionFilter-and-the-C-C-Runtime-Li +LPTOP_LEVEL_EXCEPTION_FILTER WINAPI RedirectedSetUnhandledExceptionFilter(_In_opt_ LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) { + // When the CRT calls SetUnhandledExceptionFilter with NULL parameter + // our handler will not get removed. + _oldWndExceptionFilter = lpTopLevelExceptionFilter; + return 0; } -#endif class StringReferenceWrapper { public: @@ -2595,7 +2601,7 @@ public: ~ToastEventHandler() { } - // DesktopToastActivatedEventHandler + // DesktopToastActivatedEventHandler IFACEMETHODIMP Invoke(_In_ IToastNotification *sender, _In_ IInspectable* args) { ToastNotifications::iterator i = toastNotifications.find(_peerId); if (i != toastNotifications.cend()) { @@ -2620,7 +2626,7 @@ public: tomsg = false; } } - App::main()->showPeerHistory(history->peer->id, tomsg ? _msgId : ShowAtUnreadMsgId); + Ui::showPeerHistory(history, tomsg ? _msgId : ShowAtUnreadMsgId); App::wnd()->notifyClear(history); } SetForegroundWindow(App::wnd()->psHwnd()); @@ -2867,7 +2873,7 @@ void CheckPinnedAppUserModelId() { QString path = pinnedPath(); std::wstring p = QDir::toNativeSeparators(path).toStdWString(); - + WCHAR src[MAX_PATH]; GetModuleFileName(GetModuleHandle(0), src, MAX_PATH); BY_HANDLE_FILE_INFORMATION srcinfo = { 0 }; @@ -2913,7 +2919,7 @@ void CheckPinnedAppUserModelId() { BOOL dstres = GetFileInformationByHandle(dstfile, &dstinfo); CloseHandle(dstfile); if (!dstres) continue; - + if (srcinfo.dwVolumeSerialNumber == dstinfo.dwVolumeSerialNumber && srcinfo.nFileIndexLow == dstinfo.nFileIndexLow && srcinfo.nFileIndexHigh == dstinfo.nFileIndexHigh) { ComPtr propertyStore; hr = shellLink.As(&propertyStore); @@ -3005,7 +3011,7 @@ void CleanupAppUserModelIdShortcut() { bool ValidateAppUserModelIdShortcutAt(const QString &path) { static const int maxFileLen = MAX_PATH * 10; - + std::wstring p = QDir::toNativeSeparators(path).toStdWString(); DWORD attributes = GetFileAttributes(p.c_str()); diff --git a/Telegram/SourceFiles/pspecific_wnd.h b/Telegram/SourceFiles/pspecific_wnd.h index 279de4c607..2d4232bf10 100644 --- a/Telegram/SourceFiles/pspecific_wnd.h +++ b/Telegram/SourceFiles/pspecific_wnd.h @@ -113,10 +113,9 @@ private: void psDestroyIcons(); }; -#ifdef _NEED_WIN_GENERATE_DUMP extern LPTOP_LEVEL_EXCEPTION_FILTER _oldWndExceptionFilter; LONG CALLBACK _exceptionFilter(EXCEPTION_POINTERS* pExceptionPointers); -#endif +LPTOP_LEVEL_EXCEPTION_FILTER WINAPI RedirectedSetUnhandledExceptionFilter(_In_opt_ LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter); class PsApplication : public QApplication { Q_OBJECT diff --git a/Telegram/SourceFiles/settings.cpp b/Telegram/SourceFiles/settings.cpp index 65387d7df8..0992e6a9fe 100644 --- a/Telegram/SourceFiles/settings.cpp +++ b/Telegram/SourceFiles/settings.cpp @@ -108,17 +108,21 @@ RecentEmojiPack gRecentEmojis; RecentEmojisPreload gRecentEmojisPreload; EmojiColorVariants gEmojiVariants; -int32 gStickersHash = 0; - RecentStickerPreload gRecentStickersPreload; RecentStickerPack gRecentStickers; StickerSets gStickerSets; StickerSetsOrder gStickerSetsOrder; - uint64 gLastStickersUpdate = 0; +SavedGifs gSavedGifs; +uint64 gLastSavedGifsUpdate = 0; +bool gShowingSavedGifs = false; +int32 gSavedGifsLimit = 100; + RecentHashtagPack gRecentWriteHashtags, gRecentSearchHashtags; +RecentInlineBots gRecentInlineBots; + bool gPasswordRecovered = false; int32 gPasscodeBadTries = 0; uint64 gPasscodeLastTry = 0; @@ -130,7 +134,6 @@ bool gRetina = false; float64 gRetinaFactor = 1.; int32 gIntRetinaFactor = 1; bool gCustomNotifies = true; -uint64 gInstance = 0.; #ifdef Q_OS_WIN DBIPlatform gPlatform = dbipWindows; @@ -171,6 +174,11 @@ SavedPeersByTime gSavedPeersByTime; ReportSpamStatuses gReportSpamStatuses; +int32 gAutoDownloadPhoto = 0; // all auto download +int32 gAutoDownloadAudio = 0; +int32 gAutoDownloadGif = 0; +bool gAutoPlayGif = true; + void settingsParseArgs(int argc, char *argv[]) { #ifdef Q_OS_MAC gIsElCapitan = (QSysInfo::macVersion() >= QSysInfo::MV_10_11); @@ -181,7 +189,6 @@ void settingsParseArgs(int argc, char *argv[]) { gCustomNotifies = false; } #endif - memset_rand(&gInstance, sizeof(gInstance)); gExeDir = psCurrentExeDirectory(argc, argv); gExeName = psCurrentExeName(argc, argv); for (int32 i = 0; i < argc; ++i) { @@ -192,7 +199,7 @@ void settingsParseArgs(int argc, char *argv[]) { } else if (string("-many") == argv[i]) { gManyInstance = true; } else if (string("-key") == argv[i] && i + 1 < argc) { - gKeyFile = QString::fromLocal8Bit(argv[++i]); + gKeyFile = fromUtf8Safe(argv[++i]); } else if (string("-autostart") == argv[i]) { gFromAutoStart = true; } else if (string("-noupdate") == argv[i]) { @@ -203,15 +210,15 @@ void settingsParseArgs(int argc, char *argv[]) { gStartInTray = true; } else if (string("-sendpath") == argv[i] && i + 1 < argc) { for (++i; i < argc; ++i) { - gSendPaths.push_back(QString::fromLocal8Bit(argv[i])); + gSendPaths.push_back(fromUtf8Safe(argv[i])); } } else if (string("-workdir") == argv[i] && i + 1 < argc) { - QString dir = QString::fromLocal8Bit(argv[++i]); + QString dir = fromUtf8Safe(argv[++i]); if (QDir().exists(dir)) { gWorkingDir = dir; } } else if (string("--") == argv[i] && i + 1 < argc) { - gStartUrl = QString::fromLocal8Bit(argv[++i]); + gStartUrl = fromUtf8Safe(argv[++i]); } } } diff --git a/Telegram/SourceFiles/settings.h b/Telegram/SourceFiles/settings.h index 18ee477538..e14f92ddc0 100644 --- a/Telegram/SourceFiles/settings.h +++ b/Telegram/SourceFiles/settings.h @@ -73,6 +73,9 @@ DeclareSetting(uint64, RealBetaVersion); DeclareSetting(QByteArray, BetaPrivateKey); DeclareSetting(bool, TestMode); +inline QString cInlineGifBotUsername() { + return cTestMode() ? qstr("contextbot") : qstr("gif"); +} DeclareSetting(QString, LoggedPhoneNumber); DeclareReadSetting(uint32, ConnectionsInSession); DeclareSetting(bool, AutoStart); @@ -195,9 +198,8 @@ DeclareRefSetting(EmojiColorVariants, EmojiVariants); RecentEmojiPack &cGetRecentEmojis(); -struct DocumentData; +class DocumentData; typedef QVector StickerPack; -DeclareSetting(int32, StickersHash); typedef QList > RecentStickerPackOld; typedef QVector > RecentStickerPreload; @@ -207,7 +209,7 @@ DeclareRefSetting(RecentStickerPack, RecentStickers); RecentStickerPack &cGetRecentStickers(); -DeclareSetting(uint64, LastStickersUpdate); +typedef QMap StickersByEmojiMap; static const uint64 DefaultStickerSetId = 0; // for backward compatibility static const uint64 CustomStickerSetId = 0xFFFFFFFFFFFFFFFFULL, RecentStickerSetId = 0xFFFFFFFFFFFFFFFEULL; @@ -219,16 +221,28 @@ struct StickerSet { QString title, shortName; int32 count, hash, flags; StickerPack stickers; + StickersByEmojiMap emoji; }; typedef QMap StickerSets; DeclareRefSetting(StickerSets, StickerSets); typedef QList StickerSetsOrder; DeclareRefSetting(StickerSetsOrder, StickerSetsOrder); +DeclareSetting(uint64, LastStickersUpdate); + +typedef QVector SavedGifs; +DeclareRefSetting(SavedGifs, SavedGifs); +DeclareSetting(uint64, LastSavedGifsUpdate); +DeclareSetting(bool, ShowingSavedGifs); +DeclareSetting(int32, SavedGifsLimit); typedef QList > RecentHashtagPack; -DeclareSetting(RecentHashtagPack, RecentWriteHashtags); +DeclareRefSetting(RecentHashtagPack, RecentWriteHashtags); DeclareSetting(RecentHashtagPack, RecentSearchHashtags); +class UserData; +typedef QVector RecentInlineBots; +DeclareRefSetting(RecentInlineBots, RecentInlineBots); + DeclareSetting(bool, PasswordRecovered); DeclareSetting(int32, PasscodeBadTries); @@ -295,8 +309,6 @@ DeclareSetting(float64, RetinaFactor); DeclareSetting(int32, IntRetinaFactor); DeclareSetting(bool, CustomNotifies); -DeclareReadSetting(uint64, Instance); - DeclareReadSetting(DBIPlatform, Platform); DeclareReadSetting(bool, IsElCapitan); DeclareReadSetting(QUrl, UpdateURL); @@ -327,4 +339,14 @@ DeclareRefSetting(SavedPeersByTime, SavedPeersByTime); typedef QMap ReportSpamStatuses; DeclareRefSetting(ReportSpamStatuses, ReportSpamStatuses); +enum DBIAutoDownloadFlags { + dbiadNoPrivate = 0x01, + dbiadNoGroups = 0x02, +}; + +DeclareSetting(int32, AutoDownloadPhoto); +DeclareSetting(int32, AutoDownloadAudio); +DeclareSetting(int32, AutoDownloadGif); +DeclareSetting(bool, AutoPlayGif); + void settingsParseArgs(int argc, char *argv[]); diff --git a/Telegram/SourceFiles/settingswidget.cpp b/Telegram/SourceFiles/settingswidget.cpp index 3cd1c4945d..ea3cfe7e85 100644 --- a/Telegram/SourceFiles/settingswidget.cpp +++ b/Telegram/SourceFiles/settingswidget.cpp @@ -111,97 +111,104 @@ bool scaleIs(DBIScale scale) { return cRealScale() == scale || (cRealScale() == dbisAuto && cScreenScale() == scale); } -SettingsInner::SettingsInner(SettingsWidget *parent) : QWidget(parent), - _self(App::self()), +SettingsInner::SettingsInner(SettingsWidget *parent) : TWidget(parent) +, _self(App::self()) - // profile - _nameCache(self() ? self()->name : QString()), - _uploadPhoto(this, lang(lng_settings_upload), st::btnSetUpload), - _cancelPhoto(this, lang(lng_cancel)), _nameOver(false), _photoOver(false), a_photo(0), +// profile +, _nameCache(self() ? self()->name : QString()) +, _uploadPhoto(this, lang(lng_settings_upload), st::btnSetUpload) +, _cancelPhoto(this, lang(lng_cancel)) +, _nameOver(false) +, _photoOver(false) +, a_photoOver(0) +, _a_photo(animation(this, &SettingsInner::step_photo)) - // contact info - _phoneText(self() ? App::formatPhone(self()->phone) : QString()), - _chooseUsername(this, (self() && !self()->username.isEmpty()) ? ('@' + self()->username) : lang(lng_settings_choose_username)), +// contact info +, _phoneText(self() ? App::formatPhone(self()->phone) : QString()) +, _chooseUsername(this, (self() && !self()->username.isEmpty()) ? ('@' + self()->username) : lang(lng_settings_choose_username)) - // notifications - _desktopNotify(this, lang(lng_settings_desktop_notify), cDesktopNotify()), - _senderName(this, lang(lng_settings_show_name), cNotifyView() <= dbinvShowName), - _messagePreview(this, lang(lng_settings_show_preview), cNotifyView() <= dbinvShowPreview), - _windowsNotifications(this, lang(lng_settings_use_windows), cWindowsNotifications()), - _soundNotify(this, lang(lng_settings_sound_notify), cSoundNotify()), - _includeMuted(this, lang(lng_settings_include_muted), cIncludeMuted()), +// notifications +, _desktopNotify(this, lang(lng_settings_desktop_notify), cDesktopNotify()) +, _senderName(this, lang(lng_settings_show_name), cNotifyView() <= dbinvShowName) +, _messagePreview(this, lang(lng_settings_show_preview), cNotifyView() <= dbinvShowPreview) +, _windowsNotifications(this, lang(lng_settings_use_windows), cWindowsNotifications()) +, _soundNotify(this, lang(lng_settings_sound_notify), cSoundNotify()) +, _includeMuted(this, lang(lng_settings_include_muted), cIncludeMuted()) - // general - _changeLanguage(this, lang(lng_settings_change_lang)), - #ifndef TDESKTOP_DISABLE_AUTOUPDATE - _autoUpdate(this, lang(lng_settings_auto_update), cAutoUpdate()), - _checkNow(this, lang(lng_settings_check_now)), - _restartNow(this, lang(lng_settings_update_now)), - #endif +// general +, _changeLanguage(this, lang(lng_settings_change_lang)) +#ifndef TDESKTOP_DISABLE_AUTOUPDATE +, _autoUpdate(this, lang(lng_settings_auto_update), cAutoUpdate()) +, _checkNow(this, lang(lng_settings_check_now)) +, _restartNow(this, lang(lng_settings_update_now)) +#endif - _supportTray(cSupportTray()), - _workmodeTray(this, lang(lng_settings_workmode_tray), (cWorkMode() == dbiwmTrayOnly || cWorkMode() == dbiwmWindowAndTray)), - _workmodeWindow(this, lang(lng_settings_workmode_window), (cWorkMode() == dbiwmWindowOnly || cWorkMode() == dbiwmWindowAndTray)), +, _supportTray(cSupportTray()) +, _workmodeTray(this, lang(lng_settings_workmode_tray), (cWorkMode() == dbiwmTrayOnly || cWorkMode() == dbiwmWindowAndTray)) +, _workmodeWindow(this, lang(lng_settings_workmode_window), (cWorkMode() == dbiwmWindowOnly || cWorkMode() == dbiwmWindowAndTray)) - _autoStart(this, lang(lng_settings_auto_start), cAutoStart()), - _startMinimized(this, lang(lng_settings_start_min), cStartMinimized()), - _sendToMenu(this, lang(lng_settings_add_sendto), cSendToMenu()), +, _autoStart(this, lang(lng_settings_auto_start), cAutoStart()) +, _startMinimized(this, lang(lng_settings_start_min), cStartMinimized()) +, _sendToMenu(this, lang(lng_settings_add_sendto), cSendToMenu()) - _dpiAutoScale(this, lng_settings_scale_auto(lt_cur, scaleLabel(cScreenScale())), (cConfigScale() == dbisAuto)), - _dpiSlider(this, st::dpiSlider, dbisScaleCount - 1, cEvalScale(cConfigScale()) - 1), - _dpiWidth1(st::dpiFont1->width(scaleLabel(dbisOne))), - _dpiWidth2(st::dpiFont2->width(scaleLabel(dbisOneAndQuarter))), - _dpiWidth3(st::dpiFont3->width(scaleLabel(dbisOneAndHalf))), - _dpiWidth4(st::dpiFont4->width(scaleLabel(dbisTwo))), +, _dpiAutoScale(this, lng_settings_scale_auto(lt_cur, scaleLabel(cScreenScale())), (cConfigScale() == dbisAuto)) +, _dpiSlider(this, st::dpiSlider, dbisScaleCount - 1, cEvalScale(cConfigScale()) - 1) +, _dpiWidth1(st::dpiFont1->width(scaleLabel(dbisOne))) +, _dpiWidth2(st::dpiFont2->width(scaleLabel(dbisOneAndQuarter))) +, _dpiWidth3(st::dpiFont3->width(scaleLabel(dbisOneAndHalf))) +, _dpiWidth4(st::dpiFont4->width(scaleLabel(dbisTwo))) - // chat options - _replaceEmojis(this, lang(lng_settings_replace_emojis), cReplaceEmojis()), - _viewEmojis(this, lang(lng_settings_view_emojis)), - _stickers(this, lang(lng_stickers_you_have)), +// chat options +, _replaceEmojis(this, lang(lng_settings_replace_emojis), cReplaceEmojis()) +, _viewEmojis(this, lang(lng_settings_view_emojis)) +, _stickers(this, lang(lng_stickers_you_have)) - _enterSend(this, qsl("send_key"), 0, lang(lng_settings_send_enter), !cCtrlEnter()), - _ctrlEnterSend(this, qsl("send_key"), 1, lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_settings_send_cmdenter : lng_settings_send_ctrlenter), cCtrlEnter()), +, _enterSend(this, qsl("send_key"), 0, lang(lng_settings_send_enter), !cCtrlEnter()) +, _ctrlEnterSend(this, qsl("send_key"), 1, lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_settings_send_cmdenter : lng_settings_send_ctrlenter), cCtrlEnter()) - _dontAskDownloadPath(this, lang(lng_download_path_dont_ask), !cAskDownloadPath()), - _downloadPathWidth(st::linkFont->width(lang(lng_download_path_label)) + st::linkFont->spacew), - _downloadPathEdit(this, cDownloadPath().isEmpty() ? lang(lng_download_path_default) : ((cDownloadPath() == qsl("tmp")) ? lang(lng_download_path_temp) : st::linkFont->elided(QDir::toNativeSeparators(cDownloadPath()), st::setWidth - st::setVersionLeft - _downloadPathWidth))), - _downloadPathClear(this, lang(lng_download_path_clear)), - _tempDirClearingWidth(st::linkFont->width(lang(lng_download_path_clearing))), - _tempDirClearedWidth(st::linkFont->width(lang(lng_download_path_cleared))), - _tempDirClearFailedWidth(st::linkFont->width(lang(lng_download_path_clear_failed))), +, _dontAskDownloadPath(this, lang(lng_download_path_dont_ask), !cAskDownloadPath()) +, _downloadPathWidth(st::linkFont->width(lang(lng_download_path_label)) + st::linkFont->spacew) +, _downloadPathEdit(this, cDownloadPath().isEmpty() ? lang(lng_download_path_default) : ((cDownloadPath() == qsl("tmp")) ? lang(lng_download_path_temp) : st::linkFont->elided(QDir::toNativeSeparators(cDownloadPath()), st::setWidth - st::cbDefFlat.textLeft - _downloadPathWidth))) +, _downloadPathClear(this, lang(lng_download_path_clear)) +, _tempDirClearingWidth(st::linkFont->width(lang(lng_download_path_clearing))) +, _tempDirClearedWidth(st::linkFont->width(lang(lng_download_path_cleared))) +, _tempDirClearFailedWidth(st::linkFont->width(lang(lng_download_path_clear_failed))) - // chat background - _backFromGallery(this, lang(lng_settings_bg_from_gallery)), - _backFromFile(this, lang(lng_settings_bg_from_file)), - _tileBackground(this, lang(lng_settings_bg_tile), cTileBackground()), - _needBackgroundUpdate(false), +, _autoDownload(this, lang(lng_media_auto_settings)) - // local storage - _localStorageClear(this, lang(lng_local_storage_clear)), - _localStorageHeight(1), - _storageClearingWidth(st::linkFont->width(lang(lng_local_storage_clearing))), - _storageClearedWidth(st::linkFont->width(lang(lng_local_storage_cleared))), - _storageClearFailedWidth(st::linkFont->width(lang(lng_local_storage_clear_failed))), +// local storage +, _localStorageClear(this, lang(lng_local_storage_clear)) +, _localStorageHeight(1) +, _storageClearingWidth(st::linkFont->width(lang(lng_local_storage_clearing))) +, _storageClearedWidth(st::linkFont->width(lang(lng_local_storage_cleared))) +, _storageClearFailedWidth(st::linkFont->width(lang(lng_local_storage_clear_failed))) - // advanced - _passcodeEdit(this, lang(cHasPasscode() ? lng_passcode_change : lng_passcode_turn_on)), - _passcodeTurnOff(this, lang(lng_passcode_turn_off)), - _autoLock(this, (cAutoLock() % 3600) ? lng_passcode_autolock_minutes(lt_count, cAutoLock() / 60) : lng_passcode_autolock_hours(lt_count, cAutoLock() / 3600)), - _autoLockText(lang(psIdleSupported() ? lng_passcode_autolock_away : lng_passcode_autolock_inactive) + ' '), - _autoLockWidth(st::linkFont->width(_autoLockText)), - _passwordEdit(this, lang(lng_cloud_password_set)), - _passwordTurnOff(this, lang(lng_passcode_turn_off)), - _hasPasswordRecovery(false), - _connectionType(this, lang(lng_connection_auto_connecting)), - _connectionTypeText(lang(lng_connection_type) + ' '), - _connectionTypeWidth(st::linkFont->width(_connectionTypeText)), - _showSessions(this, lang(lng_settings_show_sessions)), - _askQuestion(this, lang(lng_settings_ask_question)), - _telegramFAQ(this, lang(lng_settings_faq)), - _logOut(this, lang(lng_settings_logout), st::btnLogout), - _supportGetRequest(0) -{ +// chat background +, _backFromGallery(this, lang(lng_settings_bg_from_gallery)) +, _backFromFile(this, lang(lng_settings_bg_from_file)) +, _tileBackground(this, lang(lng_settings_bg_tile), cTileBackground()) +, _needBackgroundUpdate(false) + +// advanced +, _passcodeEdit(this, lang(cHasPasscode() ? lng_passcode_change : lng_passcode_turn_on)) +, _passcodeTurnOff(this, lang(lng_passcode_turn_off)) +, _autoLock(this, (cAutoLock() % 3600) ? lng_passcode_autolock_minutes(lt_count, cAutoLock() / 60) : lng_passcode_autolock_hours(lt_count, cAutoLock() / 3600)) +, _autoLockText(lang(psIdleSupported() ? lng_passcode_autolock_away : lng_passcode_autolock_inactive) + ' ') +, _autoLockWidth(st::linkFont->width(_autoLockText)) +, _passwordEdit(this, lang(lng_cloud_password_set)) +, _passwordTurnOff(this, lang(lng_passcode_turn_off)) +, _hasPasswordRecovery(false) +, _connectionType(this, lang(lng_connection_auto_connecting)) +, _connectionTypeText(lang(lng_connection_type) + ' ') +, _connectionTypeWidth(st::linkFont->width(_connectionTypeText)) +, _showSessions(this, lang(lng_settings_show_sessions)) +, _askQuestion(this, lang(lng_settings_ask_question)) +, _telegramFAQ(this, lang(lng_settings_faq)) +, _logOut(this, lang(lng_settings_logout), st::btnLogout) +, _supportGetRequest(0) { if (self()) { + self()->photo->load(); + connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update())); connect(App::api(), SIGNAL(fullPeerUpdated(PeerData*)), this, SLOT(onFullPeerUpdated(PeerData*))); @@ -287,13 +294,7 @@ SettingsInner::SettingsInner(SettingsWidget *parent) : QWidget(parent), } connect(App::wnd(), SIGNAL(tempDirCleared(int)), this, SLOT(onTempDirCleared(int))); connect(App::wnd(), SIGNAL(tempDirClearFailed(int)), this, SLOT(onTempDirClearFailed(int))); - - // chat background - if (!cChatBackground()) App::initBackground(); - updateChatBackground(); - connect(&_backFromGallery, SIGNAL(clicked()), this, SLOT(onBackFromGallery())); - connect(&_backFromFile, SIGNAL(clicked()), this, SLOT(onBackFromFile())); - connect(&_tileBackground, SIGNAL(changed()), this, SLOT(onTileBackground())); + connect(&_autoDownload, SIGNAL(clicked()), this, SLOT(onAutoDownload())); // local storage connect(&_localStorageClear, SIGNAL(clicked()), this, SLOT(onLocalStorageClear())); @@ -303,6 +304,13 @@ SettingsInner::SettingsInner(SettingsWidget *parent) : QWidget(parent), case Window::TempDirRemoving: _storageClearState = TempDirClearing; break; } + // chat background + if (!cChatBackground()) App::initBackground(); + updateChatBackground(); + connect(&_backFromGallery, SIGNAL(clicked()), this, SLOT(onBackFromGallery())); + connect(&_backFromFile, SIGNAL(clicked()), this, SLOT(onBackFromFile())); + connect(&_tileBackground, SIGNAL(changed()), this, SLOT(onTileBackground())); + // advanced connect(&_passcodeEdit, SIGNAL(clicked()), this, SLOT(onPasscode())); connect(&_passcodeTurnOff, SIGNAL(clicked()), this, SLOT(onPasscodeOff())); @@ -393,11 +401,11 @@ void SettingsInner::paintEvent(QPaintEvent *e) { if (_photoLink) { p.drawPixmap(_left, top, self()->photo->pix(st::setPhotoSize)); } else { - if (a_photo.current() < 1) { + if (a_photoOver.current() < 1) { p.drawPixmap(QPoint(_left, top), App::sprite(), st::setPhotoImg); } - if (a_photo.current() > 0) { - p.setOpacity(a_photo.current()); + if (a_photoOver.current() > 0) { + p.setOpacity(a_photoOver.current()); p.drawPixmap(QPoint(_left, top), App::sprite(), st::setOverPhotoImg); p.setOpacity(1); } @@ -454,7 +462,7 @@ void SettingsInner::paintEvent(QPaintEvent *e) { top += st::setHeaderSkip; #ifndef TDESKTOP_DISABLE_AUTOUPDATE - top += _autoUpdate.height(); + top += _autoUpdate.height(); QString textToDraw; if (cAutoUpdate()) { switch (_updatingState) { @@ -468,8 +476,8 @@ void SettingsInner::paintEvent(QPaintEvent *e) { } else { textToDraw = _curVersionText; } - p.setFont(st::linkFont->f); - p.setPen(st::setVersionColor->p); + p.setFont(st::linkFont); + p.setPen(st::setVersionColor); p.drawText(_left + st::setVersionLeft, top + st::setVersionTop + st::linkFont->ascent, textToDraw); top += st::setVersionHeight; #endif @@ -477,7 +485,7 @@ void SettingsInner::paintEvent(QPaintEvent *e) { if (cPlatform() == dbipWindows) { top += _workmodeTray.height() + st::setLittleSkip; top += _workmodeWindow.height() + st::setSectionSkip; - + top += _autoStart.height() + st::setLittleSkip; top += _startMinimized.height() + st::setSectionSkip; @@ -492,12 +500,12 @@ void SettingsInner::paintEvent(QPaintEvent *e) { p.drawText(_left + st::setHeaderLeft, top + st::setHeaderTop + st::setHeaderFont->ascent, lang(lng_settings_scale_label)); top += st::setHeaderSkip; top += _dpiAutoScale.height() + st::setLittleSkip; - + top += _dpiSlider.height() + st::dpiFont4->height; int32 sLeft = _dpiSlider.x() + _dpiWidth1 / 2, sWidth = _dpiSlider.width(); float64 sStep = (sWidth - _dpiWidth1 / 2 - _dpiWidth4 / 2) / float64(dbisScaleCount - 2); p.setFont(st::dpiFont1->f); - + p.setPen((scaleIs(dbisOne) ? st::dpiActive : st::dpiInactive)->p); p.drawText(sLeft + qRound(0 * sStep) - _dpiWidth1 / 2, top - (st::dpiFont4->height - st::dpiFont1->height) / 2 - st::dpiFont1->descent, scaleLabel(dbisOne)); p.setFont(st::dpiFont2->f); @@ -511,7 +519,7 @@ void SettingsInner::paintEvent(QPaintEvent *e) { p.drawText(sLeft + qRound(3 * sStep) - _dpiWidth4 / 2, top - (st::dpiFont4->height - st::dpiFont4->height) / 2 - st::dpiFont4->descent, scaleLabel(dbisTwo)); p.setFont(st::linkFont->f); } - + if (self()) { // chat options p.setFont(st::setHeaderFont->f); @@ -529,7 +537,7 @@ void SettingsInner::paintEvent(QPaintEvent *e) { top += st::setLittleSkip; p.setFont(st::linkFont->f); p.setPen(st::black->p); - p.drawText(_left + st::setVersionLeft, top + st::linkFont->ascent, lang(lng_download_path_label)); + p.drawText(_left + st::cbDefFlat.textLeft, top + st::linkFont->ascent, lang(lng_download_path_label)); if (cDownloadPath() == qsl("tmp")) { QString clearText; int32 clearWidth = 0; @@ -544,7 +552,52 @@ void SettingsInner::paintEvent(QPaintEvent *e) { } top += _downloadPathEdit.height(); } - top += st::setSectionSkip; + top += st::setLittleSkip; + top += _autoDownload.height(); + + // local storage + p.setFont(st::setHeaderFont->f); + p.setPen(st::setHeaderColor->p); + p.drawText(_left + st::setHeaderLeft, top + st::setHeaderTop + st::setHeaderFont->ascent, lang(lng_settings_section_cache)); + + p.setFont(st::linkFont->f); + p.setPen(st::black->p); + QString clearText; + int32 clearWidth = 0; + switch (_storageClearState) { + case TempDirClearing: clearText = lang(lng_local_storage_clearing); clearWidth = _storageClearingWidth; break; + case TempDirCleared: clearText = lang(lng_local_storage_cleared); clearWidth = _storageClearedWidth; break; + case TempDirClearFailed: clearText = lang(lng_local_storage_clear_failed); clearWidth = _storageClearFailedWidth; break; + } + if (clearWidth) { + p.drawText(_left + st::setWidth - clearWidth, top + st::setHeaderTop + st::setHeaderFont->ascent, clearText); + } + + top += st::setHeaderSkip; + + int32 cntImages = Local::hasImages() + Local::hasStickers() + Local::hasWebFiles(), cntAudios = Local::hasAudios(); + if (cntImages > 0 && cntAudios > 0) { + if (_localStorageHeight != 2) { + cntAudios = 0; + QTimer::singleShot(0, this, SLOT(onUpdateLocalStorage())); + } + } else { + if (_localStorageHeight != 1) { + QTimer::singleShot(0, this, SLOT(onUpdateLocalStorage())); + } + } + if (cntImages > 0) { + QString cnt = lng_settings_images_cached(lt_count, cntImages, lt_size, formatSizeText(Local::storageImagesSize() + Local::storageStickersSize() + Local::storageWebFilesSize())); + p.drawText(_left + st::setHeaderLeft, top + st::linkFont->ascent, cnt); + } + if (_localStorageHeight == 2) top += _localStorageClear.height() + st::setLittleSkip; + if (cntAudios > 0) { + QString cnt = lng_settings_audios_cached(lt_count, cntAudios, lt_size, formatSizeText(Local::storageAudiosSize())); + p.drawText(_left + st::setHeaderLeft, top + st::linkFont->ascent, cnt); + } else if (cntImages <= 0) { + p.drawText(_left + st::setHeaderLeft, top + st::linkFont->ascent, lang(lng_settings_no_data_cached)); + } + top += _localStorageClear.height(); // chat background p.setFont(st::setHeaderFont->f); @@ -584,50 +637,6 @@ void SettingsInner::paintEvent(QPaintEvent *e) { top += st::setBackgroundSize; top += st::setLittleSkip; top += _tileBackground.height(); - - // local storage - p.setFont(st::setHeaderFont->f); - p.setPen(st::setHeaderColor->p); - p.drawText(_left + st::setHeaderLeft, top + st::setHeaderTop + st::setHeaderFont->ascent, lang(lng_settings_section_cache)); - - p.setFont(st::linkFont->f); - p.setPen(st::black->p); - QString clearText; - int32 clearWidth = 0; - switch (_storageClearState) { - case TempDirClearing: clearText = lang(lng_local_storage_clearing); clearWidth = _storageClearingWidth; break; - case TempDirCleared: clearText = lang(lng_local_storage_cleared); clearWidth = _storageClearedWidth; break; - case TempDirClearFailed: clearText = lang(lng_local_storage_clear_failed); clearWidth = _storageClearFailedWidth; break; - } - if (clearWidth) { - p.drawText(_left + st::setWidth - clearWidth, top + st::setHeaderTop + st::setHeaderFont->ascent, clearText); - } - - top += st::setHeaderSkip; - - int32 cntImages = Local::hasImages() + Local::hasStickers(), cntAudios = Local::hasAudios(); - if (cntImages > 0 && cntAudios > 0) { - if (_localStorageHeight != 2) { - cntAudios = 0; - QTimer::singleShot(0, this, SLOT(onUpdateLocalStorage())); - } - } else { - if (_localStorageHeight != 1) { - QTimer::singleShot(0, this, SLOT(onUpdateLocalStorage())); - } - } - if (cntImages > 0) { - QString cnt = lng_settings_images_cached(lt_count, cntImages, lt_size, formatSizeText(Local::storageImagesSize() + Local::storageStickersSize())); - p.drawText(_left + st::setHeaderLeft, top + st::linkFont->ascent, cnt); - } - if (_localStorageHeight == 2) top += _localStorageClear.height() + st::setLittleSkip; - if (cntAudios > 0) { - QString cnt = lng_settings_audios_cached(lt_count, cntAudios, lt_size, formatSizeText(Local::storageAudiosSize())); - p.drawText(_left + st::setHeaderLeft, top + st::linkFont->ascent, cnt); - } else if (cntImages <= 0) { - p.drawText(_left + st::setHeaderLeft, top + st::linkFont->ascent, lang(lng_settings_no_data_cached)); - } - top += _localStorageClear.height(); } // advanced @@ -635,7 +644,7 @@ void SettingsInner::paintEvent(QPaintEvent *e) { p.setPen(st::setHeaderColor->p); p.drawText(_left + st::setHeaderLeft, top + st::setHeaderTop + st::setHeaderFont->ascent, lang(lng_settings_section_advanced)); top += st::setHeaderSkip; - + p.setFont(st::linkFont->f); p.setPen(st::black->p); if (self()) { @@ -693,7 +702,7 @@ void SettingsInner::resizeEvent(QResizeEvent *e) { if (cPlatform() == dbipWindows) { _workmodeTray.move(_left, top); top += _workmodeTray.height() + st::setLittleSkip; _workmodeWindow.move(_left, top); top += _workmodeWindow.height() + st::setSectionSkip; - + _autoStart.move(_left, top); top += _autoStart.height() + st::setLittleSkip; _startMinimized.move(_left, top); top += _startMinimized.height() + st::setSectionSkip; @@ -706,7 +715,7 @@ void SettingsInner::resizeEvent(QResizeEvent *e) { _dpiAutoScale.move(_left, top); top += _dpiAutoScale.height() + st::setLittleSkip; _dpiSlider.move(_left, top); top += _dpiSlider.height() + st::dpiFont4->height; } - + // chat options if (self()) { top += st::setHeaderSkip; @@ -718,13 +727,25 @@ void SettingsInner::resizeEvent(QResizeEvent *e) { _dontAskDownloadPath.move(_left, top); top += _dontAskDownloadPath.height(); if (!cAskDownloadPath()) { top += st::setLittleSkip; - _downloadPathEdit.move(_left + st::setVersionLeft + _downloadPathWidth, top); + _downloadPathEdit.move(_left + st::cbDefFlat.textLeft + _downloadPathWidth, top); if (cDownloadPath() == qsl("tmp")) { _downloadPathClear.move(_left + st::setWidth - _downloadPathClear.width(), top); } top += _downloadPathEdit.height(); } - top += st::setSectionSkip; + top += st::setLittleSkip; + _autoDownload.move(_left + st::cbDefFlat.textLeft, top); top += _autoDownload.height(); + + // local storage + _localStorageClear.move(_left + st::setWidth - _localStorageClear.width(), top + st::setHeaderTop + st::setHeaderFont->ascent - st::linkFont->ascent); + top += st::setHeaderSkip; + if ((Local::hasImages() || Local::hasStickers() || Local::hasWebFiles()) && Local::hasAudios()) { + _localStorageHeight = 2; + top += _localStorageClear.height() + st::setLittleSkip; + } else { + _localStorageHeight = 1; + } + top += _localStorageClear.height(); // chat background top += st::setHeaderSkip; @@ -734,17 +755,6 @@ void SettingsInner::resizeEvent(QResizeEvent *e) { top += st::setLittleSkip; _tileBackground.move(_left, top); top += _tileBackground.height(); - - // local storage - _localStorageClear.move(_left + st::setWidth - _localStorageClear.width(), top + st::setHeaderTop + st::setHeaderFont->ascent - st::linkFont->ascent); - top += st::setHeaderSkip; - if ((Local::hasImages() || Local::hasStickers()) && Local::hasAudios()) { - _localStorageHeight = 2; - top += _localStorageClear.height() + st::setLittleSkip; - } else { - _localStorageHeight = 1; - } - top += _localStorageClear.height(); } // advanced @@ -783,14 +793,14 @@ void SettingsInner::keyPressEvent(QKeyEvent *e) { QString text = cDebug() ? qsl("Do you want to disable DEBUG logs?") : qsl("Do you want to enable DEBUG logs?\n\nAll network events will be logged."); ConfirmBox *box = new ConfirmBox(text); connect(box, SIGNAL(confirmed()), App::app(), SLOT(onSwitchDebugMode())); - App::wnd()->showLayer(box); + Ui::showLayer(box); from = size; break; } else if (str == qstr("testmode")) { QString text = cTestMode() ? qsl("Do you want to disable TEST mode?") : qsl("Do you want to enable TEST mode?\n\nYou will be switched to test cloud."); ConfirmBox *box = new ConfirmBox(text); connect(box, SIGNAL(confirmed()), App::app(), SLOT(onSwitchTestMode())); - App::wnd()->showLayer(box); + Ui::showLayer(box); from = size; break; } else if (str == qstr("loadlang")) { @@ -816,8 +826,8 @@ void SettingsInner::mouseMoveEvent(QMouseEvent *e) { if (photoOver != _photoOver) { _photoOver = photoOver; if (!_photoLink) { - a_photo.start(_photoOver ? 1 : 0); - anim::start(this); + a_photoOver.start(_photoOver ? 1 : 0); + _a_photo.start(); } } @@ -831,10 +841,10 @@ void SettingsInner::mousePressEvent(QMouseEvent *e) { return; } if (QRect(_uploadPhoto.x() + st::setNameLeft, st::setTop + st::setNameTop, qMin(_uploadPhoto.width() - int(st::setNameLeft), _nameText.maxWidth()), st::setNameFont->height).contains(e->pos())) { - App::wnd()->showLayer(new EditNameTitleBox(self())); + Ui::showLayer(new EditNameTitleBox(self())); } else if (QRect(_left, st::setTop, st::setPhotoSize, st::setPhotoSize).contains(e->pos())) { if (_photoLink) { - App::photo(self()->photoId)->full->load(); + App::photo(self()->photoId)->download(); _photoLink->onClick(e->button()); } else { onUpdatePhoto(); @@ -845,17 +855,15 @@ void SettingsInner::mousePressEvent(QMouseEvent *e) { void SettingsInner::contextMenuEvent(QContextMenuEvent *e) { } -bool SettingsInner::animStep(float64 ms) { +void SettingsInner::step_photo(float64 ms, bool timer) { float64 dt = ms / st::setPhotoDuration; - bool res = true; if (dt >= 1) { - res = false; - a_photo.finish(); + _a_photo.stop(); + a_photoOver.finish(); } else { - a_photo.update(dt, anim::linear); + a_photoOver.update(dt, anim::linear); } - update(_left, st::setTop, st::setPhotoSize, st::setPhotoSize); - return res; + if (timer) update(_left, st::setTop, st::setPhotoSize, st::setPhotoSize); } void SettingsInner::updateSize(int32 newWidth) { @@ -1023,7 +1031,7 @@ void SettingsInner::showAll() { _workmodeTray.hide(); } _workmodeWindow.hide(); - + _autoStart.hide(); _startMinimized.hide(); @@ -1060,7 +1068,7 @@ void SettingsInner::showAll() { _downloadPathClear.hide(); } } - + _autoDownload.show(); } else { _replaceEmojis.hide(); _viewEmojis.hide(); @@ -1070,6 +1078,14 @@ void SettingsInner::showAll() { _dontAskDownloadPath.hide(); _downloadPathEdit.hide(); _downloadPathClear.hide(); + _autoDownload.hide(); + } + + // local storage + if (self() && _storageClearState == TempDirExists) { + _localStorageClear.show(); + } else { + _localStorageClear.hide(); } // chat background @@ -1083,13 +1099,6 @@ void SettingsInner::showAll() { _tileBackground.hide(); } - // local storage - if (self() && _storageClearState == TempDirExists) { - _localStorageClear.show(); - } else { - _localStorageClear.hide(); - } - // advanced if (self()) { _passcodeEdit.show(); @@ -1138,7 +1147,7 @@ void SettingsInner::supportGot(const MTPhelp_Support &support) { if (support.type() == mtpc_help_support) { const MTPDhelp_support &d(support.c_help_support()); UserData *u = App::feedUsers(MTP_vector(1, d.vuser)); - App::main()->showPeerHistory(u->id, ShowAtUnreadMsgId); + Ui::showPeerHistory(u, ShowAtUnreadMsgId); App::wnd()->hideSettings(); } } @@ -1154,7 +1163,7 @@ void SettingsInner::onUpdatePhotoCancel() { void SettingsInner::onUpdatePhoto() { saveError(); - QStringList imgExtensions(cImgExtensions()); + QStringList imgExtensions(cImgExtensions()); QString filter(qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;All files (*.*)")); QImage img; @@ -1178,12 +1187,12 @@ void SettingsInner::onUpdatePhoto() { } PhotoCropBox *box = new PhotoCropBox(img, self()); connect(box, SIGNAL(closed()), this, SLOT(onPhotoUpdateStart())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } void SettingsInner::onShowSessions() { SessionsBox *box = new SessionsBox(); - App::wnd()->showLayer(box); + Ui::showLayer(box); } void SettingsInner::onAskQuestion() { @@ -1192,7 +1201,7 @@ void SettingsInner::onAskQuestion() { ConfirmBox *box = new ConfirmBox(lang(lng_settings_ask_sure), lang(lng_settings_ask_ok), st::defaultBoxButton, lang(lng_settings_faq_button)); connect(box, SIGNAL(confirmed()), this, SLOT(onAskQuestionSure())); connect(box, SIGNAL(cancelPressed()), this, SLOT(onTelegramFAQ())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } void SettingsInner::onAskQuestionSure() { @@ -1217,9 +1226,9 @@ void SettingsInner::chooseCustomLang() { cancel = result.value(lng_cancel, langOriginal(lng_cancel)); ConfirmBox *box = new ConfirmBox(text, save, st::defaultBoxButton, cancel); connect(box, SIGNAL(confirmed()), this, SLOT(onSaveTestLang())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } else { - App::wnd()->showLayer(new InformBox("Custom lang failed :(\n\nError: " + loader.errors())); + Ui::showLayer(new InformBox("Custom lang failed :(\n\nError: " + loader.errors())); } } } @@ -1228,7 +1237,7 @@ void SettingsInner::onChangeLanguage() { if ((_changeLanguage.clickModifiers() & Qt::ShiftModifier) && (_changeLanguage.clickModifiers() & Qt::AltModifier)) { chooseCustomLang(); } else { - App::wnd()->showLayer(new LanguageBox()); + Ui::showLayer(new LanguageBox()); } } @@ -1293,19 +1302,19 @@ void SettingsInner::onRestartNow() { void SettingsInner::onPasscode() { PasscodeBox *box = new PasscodeBox(); connect(box, SIGNAL(closed()), this, SLOT(passcodeChanged())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } void SettingsInner::onPasscodeOff() { PasscodeBox *box = new PasscodeBox(true); connect(box, SIGNAL(closed()), this, SLOT(passcodeChanged())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } void SettingsInner::onPassword() { PasscodeBox *box = new PasscodeBox(_newPasswordSalt, _curPasswordSalt, _hasPasswordRecovery, _curPasswordHint); connect(box, SIGNAL(reloadPassword()), this, SLOT(onReloadPassword())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } void SettingsInner::onPasswordOff() { @@ -1319,7 +1328,7 @@ void SettingsInner::onPasswordOff() { } else { PasscodeBox *box = new PasscodeBox(_newPasswordSalt, _curPasswordSalt, _hasPasswordRecovery, _curPasswordHint, true); connect(box, SIGNAL(reloadPassword()), this, SLOT(onReloadPassword())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } } @@ -1332,19 +1341,19 @@ void SettingsInner::onReloadPassword(Qt::ApplicationState state) { void SettingsInner::onAutoLock() { AutoLockBox *box = new AutoLockBox(); connect(box, SIGNAL(closed()), this, SLOT(passcodeChanged())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } void SettingsInner::onConnectionType() { ConnectionBox *box = new ConnectionBox(); connect(box, SIGNAL(closed()), this, SLOT(updateConnectionType()), Qt::QueuedConnection); - App::wnd()->showLayer(box); + Ui::showLayer(box); } void SettingsInner::onUsername() { UsernameBox *box = new UsernameBox(); connect(box, SIGNAL(closed()), this, SLOT(usernameChanged())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } void SettingsInner::onWorkmodeTray() { @@ -1445,7 +1454,7 @@ void SettingsInner::setScale(DBIScale newScale) { if (cEvalScale(cConfigScale()) != cEvalScale(cRealScale())) { ConfirmBox *box = new ConfirmBox(lang(lng_settings_need_restart), lang(lng_settings_restart_now), st::defaultBoxButton, lang(lng_settings_restart_later)); connect(box, SIGNAL(confirmed()), this, SLOT(onRestartNow())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } } @@ -1528,11 +1537,11 @@ void SettingsInner::onReplaceEmojis() { } void SettingsInner::onViewEmojis() { - App::showLayer(new EmojiBox()); + Ui::showLayer(new EmojiBox()); } void SettingsInner::onStickers() { - App::showLayer(new StickersBox()); + Ui::showLayer(new StickersBox()); } void SettingsInner::onEnterSend() { @@ -1553,7 +1562,7 @@ void SettingsInner::onCtrlEnterSend() { void SettingsInner::onBackFromGallery() { BackgroundBox *box = new BackgroundBox(); - App::wnd()->showLayer(box); + Ui::showLayer(box); } void SettingsInner::onBackFromFile() { @@ -1631,7 +1640,7 @@ void SettingsInner::onDontAskDownloadPath() { void SettingsInner::onDownloadPathEdit() { DownloadPathBox *box = new DownloadPathBox(); connect(box, SIGNAL(closed()), this, SLOT(onDownloadPathEdited())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } void SettingsInner::onDownloadPathEdited() { @@ -1650,11 +1659,11 @@ void SettingsInner::onDownloadPathEdited() { void SettingsInner::onDownloadPathClear() { ConfirmBox *box = new ConfirmBox(lang(lng_sure_clear_downloads)); connect(box, SIGNAL(confirmed()), this, SLOT(onDownloadPathClearSure())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } void SettingsInner::onDownloadPathClearSure() { - App::wnd()->hideLayer(); + Ui::hideLayer(); App::wnd()->tempDirDelete(Local::ClearManagerDownloads); _tempDirClearState = TempDirClearing; showAll(); @@ -1688,6 +1697,10 @@ void SettingsInner::onTempDirClearFailed(int task) { update(); } +void SettingsInner::onAutoDownload() { + Ui::showLayer(new AutoDownloadBox()); +} + #ifndef TDESKTOP_DISABLE_AUTOUPDATE void SettingsInner::setUpdatingState(UpdatingState state, bool force) { if (_updatingState != state || force) { @@ -1763,7 +1776,7 @@ void SettingsInner::onPhotoUpdateDone(PeerId peer) { } SettingsWidget::SettingsWidget(Window *parent) : TWidget(parent) -, _a_show(animFunc(this, &SettingsWidget::animStep_show)) +, _a_show(animation(this, &SettingsWidget::step_show)) , _scroll(this, st::setScroll) , _inner(this) , _close(this, st::setClose) { @@ -1800,13 +1813,11 @@ void SettingsWidget::animShow(const QPixmap &bgAnimCache, bool back) { show(); } -bool SettingsWidget::animStep_show(float64 ms) { +void SettingsWidget::step_show(float64 ms, bool timer) { float64 dt = ms / st::slideDuration; - bool res = true; if (dt >= 1) { _a_show.stop(); - res = false; a_coordUnder.finish(); a_coordOver.finish(); a_shadow.finish(); @@ -1822,11 +1833,10 @@ bool SettingsWidget::animStep_show(float64 ms) { a_coordOver.update(dt, st::slideFunction); a_shadow.update(dt, st::slideFunction); } - update(); - return res; + if (timer) update(); } -void SettingsWidget::animStop_show() { +void SettingsWidget::stop_show() { _a_show.stop(); } diff --git a/Telegram/SourceFiles/settingswidget.h b/Telegram/SourceFiles/settingswidget.h index 1a8dcdfac0..821ce2a67d 100644 --- a/Telegram/SourceFiles/settingswidget.h +++ b/Telegram/SourceFiles/settingswidget.h @@ -57,7 +57,7 @@ private: }; -class SettingsInner : public QWidget, public RPCSender, public Animated { +class SettingsInner : public TWidget, public RPCSender { Q_OBJECT public: @@ -71,7 +71,7 @@ public: void mousePressEvent(QMouseEvent *e); void contextMenuEvent(QContextMenuEvent *e); - bool animStep(float64 ms); + void step_photo(float64 ms, bool timer); void updateSize(int32 newWidth); @@ -151,6 +151,8 @@ public slots: void onTempDirCleared(int task); void onTempDirClearFailed(int task); + void onAutoDownload(); + void onBackFromGallery(); void onBackFromFile(); void onTileBackground(); @@ -203,7 +205,8 @@ private: FlatButton _uploadPhoto; LinkButton _cancelPhoto; bool _nameOver, _photoOver; - anim::fvalue a_photo; + anim::fvalue a_photoOver; + Animation _a_photo; QString _errorText; @@ -257,12 +260,7 @@ private: TempDirCleared = 4, }; TempDirClearState _tempDirClearState; - - // chat background - QPixmap _background; - LinkButton _backFromGallery, _backFromFile; - FlatCheckbox _tileBackground; - bool _needBackgroundUpdate; + LinkButton _autoDownload; // local storage LinkButton _localStorageClear; @@ -270,6 +268,12 @@ private: int32 _storageClearingWidth, _storageClearedWidth, _storageClearFailedWidth; TempDirClearState _storageClearState; + // chat background + QPixmap _background; + LinkButton _backFromGallery, _backFromFile; + FlatCheckbox _tileBackground; + bool _needBackgroundUpdate; + // advanced LinkButton _passcodeEdit, _passcodeTurnOff, _autoLock; QString _autoLockText; @@ -315,8 +319,8 @@ public: void updateWideMode(); void animShow(const QPixmap &bgAnimCache, bool back = false); - bool animStep_show(float64 ms); - void animStop_show(); + void step_show(float64 ms, bool timer); + void stop_show(); void updateOnlineDisplay(); void updateConnectionType(); diff --git a/Telegram/SourceFiles/stdafx.h b/Telegram/SourceFiles/stdafx.h index aa28660a2d..bc67e11838 100644 --- a/Telegram/SourceFiles/stdafx.h +++ b/Telegram/SourceFiles/stdafx.h @@ -45,11 +45,15 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include #endif -#if defined Q_OS_WIN -#define _NEED_WIN_GENERATE_DUMP -#elif defined Q_OS_LINUX32 || defined Q_OS_LINUX64 -#define _NEED_LINUX_GENERATE_DUMP -#endif +extern "C" { + +#include +#include +#include +#include +#include + +} #include "types.h" #include "config.h" @@ -64,7 +68,6 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "gui/flatbutton.h" #include "gui/boxshadow.h" #include "gui/popupmenu.h" -#include "gui/switcher.h" #include "gui/scrollarea.h" #include "gui/images.h" #include "gui/text.h" diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index b7c3bfed0a..34bd47fba4 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -62,7 +62,7 @@ style::color peerColor(int32 index) { } ImagePtr userDefPhoto(int32 index) { - static const ImagePtr userDefPhotos[8] = { + static const ImagePtr userDefPhotos[UserColorsCount] = { ImagePtr(qsl(":/ava/art/usercolor1.png"), "PNG"), ImagePtr(qsl(":/ava/art/usercolor2.png"), "PNG"), ImagePtr(qsl(":/ava/art/usercolor3.png"), "PNG"), @@ -154,6 +154,13 @@ void PeerData::updateName(const QString &newName, const QString &newNameOrPhone, } } +const Text &BotCommand::descriptionText() const { + if (_descriptionText.isEmpty() && !_description.isEmpty()) { + _descriptionText.setText(st::mentionFont, _description, _textNameOptions); + } + return _descriptionText; +} + void UserData::setPhoto(const MTPUserProfilePhoto &p) { // see Local::readPeer as well PhotoId newPhotoId = photoId; ImagePtr newPhoto = photo; @@ -276,7 +283,7 @@ void UserData::setBotInfo(const MTPBotInfo &info) { case mtpc_botInfo: { const MTPDbotInfo &d(info.c_botInfo()); if (peerFromUser(d.vuser_id.v) != id) return; - + if (botInfo) { botInfo->version = d.vversion.v; } else { @@ -289,7 +296,7 @@ void UserData::setBotInfo(const MTPBotInfo &info) { botInfo->text = Text(st::msgMinWidth); } botInfo->shareText = qs(d.vshare_text); - + const QVector &v(d.vcommands.c_vector().v); botInfo->commands.reserve(v.size()); bool changedCommands = false; @@ -556,9 +563,138 @@ bool PtsWaiter::check(ChannelData *channel, int32 pts, int32 count) { // return return !count; } +PhotoData::PhotoData(const PhotoId &id, const uint64 &access, int32 date, const ImagePtr &thumb, const ImagePtr &medium, const ImagePtr &full) +: id(id) +, access(access) +, date(date) +, thumb(thumb) +, medium(medium) +, full(full) +, peer(0) +, uploadingData(0) { +} + +void PhotoData::automaticLoad(const HistoryItem *item) { + full->automaticLoad(item); +} + +void PhotoData::automaticLoadSettingsChanged() { + full->automaticLoadSettingsChanged(); +} + +void PhotoData::download() { + full->loadEvenCancelled(); + notifyLayoutChanged(); +} + +bool PhotoData::loaded() const { + bool wasLoading = loading(); + if (full->loaded()) { + if (wasLoading) { + notifyLayoutChanged(); + } + return true; + } + return false; +} + +bool PhotoData::loading() const { + return full->loading(); +} + +bool PhotoData::displayLoading() const { + return full->loading() ? full->displayLoading() : uploading(); +} + +void PhotoData::cancel() { + full->cancel(); + notifyLayoutChanged(); +} + +void PhotoData::notifyLayoutChanged() const { + const PhotoItems &items(App::photoItems()); + PhotoItems::const_iterator i = items.constFind(const_cast(this)); + if (i != items.cend()) { + for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) { + Notify::historyItemLayoutChanged(j.key()); + } + } +} + +float64 PhotoData::progress() const { + if (uploading()) { + if (uploadingData->size > 0) { + return float64(uploadingData->offset) / uploadingData->size; + } + return 0; + } + return full->progress(); +} + +int32 PhotoData::loadOffset() const { + return full->loadOffset(); +} + +bool PhotoData::uploading() const { + return uploadingData; +} + +void PhotoData::forget() { + thumb->forget(); + replyPreview->forget(); + medium->forget(); + full->forget(); +} + +ImagePtr PhotoData::makeReplyPreview() { + if (replyPreview->isNull() && !thumb->isNull()) { + if (thumb->loaded()) { + int w = thumb->width(), h = thumb->height(); + if (w <= 0) w = 1; + if (h <= 0) h = 1; + replyPreview = ImagePtr(w > h ? thumb->pix(w * st::msgReplyBarSize.height() / h, st::msgReplyBarSize.height()) : thumb->pix(st::msgReplyBarSize.height()), "PNG"); + } else { + thumb->load(); + } + } + return replyPreview; +} + +PhotoData::~PhotoData() { + deleteAndMark(uploadingData); +} + void PhotoLink::onClick(Qt::MouseButton button) const { if (button == Qt::LeftButton) { - App::wnd()->showPhoto(this, App::hoveredLinkItem()); + App::wnd()->showPhoto(this, App::hoveredLinkItem() ? App::hoveredLinkItem() : App::contextItem()); + } +} + +void PhotoSaveLink::onClick(Qt::MouseButton button) const { + if (button != Qt::LeftButton) return; + + PhotoData *data = photo(); + if (!data->date) return; + + data->download(); +} + +void PhotoCancelLink::onClick(Qt::MouseButton button) const { + if (button != Qt::LeftButton) return; + + PhotoData *data = photo(); + if (!data->date) return; + + if (data->uploading()) { + HistoryItem *item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : 0); + if (HistoryMessage *msg = item->toHistoryMessage()) { + if (msg->getMedia() && msg->getMedia()->type() == MediaTypePhoto && static_cast(msg->getMedia())->photo() == data) { + App::contextItem(item); + App::main()->deleteLayer(-2); + } + } + } else { + data->cancel(); } } @@ -650,31 +786,37 @@ QString saveFileName(const QString &title, const QString &filter, const QString } void VideoOpenLink::onClick(Qt::MouseButton button) const { + if (button != Qt::LeftButton) return; VideoData *data = video(); - if (!data->date || button != Qt::LeftButton) return; - QString already = data->already(true); - if (!already.isEmpty()) { - psOpenFile(already); + if (!data->date) return; + + HistoryItem *item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : 0); + + const FileLocation &location(data->location(true)); + if (!location.isEmpty()) { + psOpenFile(location.name()); if (App::main()) App::main()->videoMarkRead(data); return; } if (data->status != FileReady) return; - QString filename = saveFileName(lang(lng_save_video), qsl("MOV Video (*.mov);;All files (*.*)"), qsl("video"), qsl(".mov"), false); - if (!filename.isEmpty()) { - data->openOnSave = 1; - data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->fullId() : (App::contextItem() ? App::contextItem()->fullId() : FullMsgId()); - data->save(filename); + QString filename; + if (!data->saveToCache()) { + filename = saveFileName(lang(lng_save_video), qsl("MOV Video (*.mov);;All files (*.*)"), qsl("video"), qsl(".mov"), false); + if (filename.isEmpty()) return; } + + data->save(filename, ActionOnLoadOpen, item ? item->fullId() : FullMsgId()); } void VideoSaveLink::doSave(VideoData *data, bool forceSavingAs) { if (!data->date) return; QString already = data->already(true); - if (!already.isEmpty() && !forceSavingAs) { + bool openWith = !already.isEmpty(); + if (openWith && !forceSavingAs) { QPoint pos(QCursor::pos()); if (!psShowOpenWithMenu(pos.x(), pos.y(), already)) { psOpenFile(already, true); @@ -685,13 +827,9 @@ void VideoSaveLink::doSave(VideoData *data, bool forceSavingAs) { QString name = already.isEmpty() ? QString(".mov") : alreadyInfo.fileName(); QString filename = saveFileName(lang(lng_save_video), qsl("MOV Video (*.mov);;All files (*.*)"), qsl("video"), name, forceSavingAs, alreadyDir); if (!filename.isEmpty()) { - if (forceSavingAs) { - data->cancel(); - } else if (!already.isEmpty()) { - data->openOnSave = -1; - data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->fullId() : FullMsgId(); - } - data->save(filename); + ActionOnLoad action = already.isEmpty() ? ActionOnLoadNone : ActionOnLoadOpenWith; + FullMsgId actionMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->fullId() : (App::contextItem() ? App::contextItem()->fullId() : FullMsgId()); + data->save(filename, action, actionMsgId); } } } @@ -708,47 +846,200 @@ void VideoCancelLink::onClick(Qt::MouseButton button) const { data->cancel(); } -VideoData::VideoData(const VideoId &id, const uint64 &access, int32 date, int32 duration, int32 w, int32 h, const ImagePtr &thumb, int32 dc, int32 size) : -id(id), access(access), date(date), duration(duration), w(w), h(h), thumb(thumb), dc(dc), size(size), status(FileReady), uploadOffset(0), fileType(0), openOnSave(0), loader(0) { +VideoData::VideoData(const VideoId &id, const uint64 &access, int32 date, int32 duration, int32 w, int32 h, const ImagePtr &thumb, int32 dc, int32 size) +: id(id) +, access(access) +, date(date) +, duration(duration) +, w(w) +, h(h) +, thumb(thumb) +, dc(dc) +, size(size) +, status(FileReady) +, uploadOffset(0) +, _actionOnLoad(ActionOnLoadNone) +, _loader(0) { _location = Local::readFileLocation(mediaKey(VideoFileLocation, dc, id)); } -void VideoData::save(const QString &toFile) { - cancel(true); - loader = new mtpFileLoader(dc, id, access, VideoFileLocation, toFile, size); - loader->connect(loader, SIGNAL(progress(mtpFileLoader*)), App::main(), SLOT(videoLoadProgress(mtpFileLoader*))); - loader->connect(loader, SIGNAL(failed(mtpFileLoader*, bool)), App::main(), SLOT(videoLoadFailed(mtpFileLoader*, bool))); - loader->start(); +void VideoData::forget() { + replyPreview->forget(); + thumb->forget(); } -QString VideoData::already(bool check) { +void VideoData::performActionOnLoad() { + if (_actionOnLoad == ActionOnLoadNone) return; + + const FileLocation &loc(location(true)); + QString already = loc.name(); + if (already.isEmpty()) return; + + if (_actionOnLoad == ActionOnLoadOpenWith) { + QPoint pos(QCursor::pos()); + if (!psShowOpenWithMenu(pos.x(), pos.y(), already)) { + psOpenFile(already, true); + } + } else if (_actionOnLoad == ActionOnLoadOpen || _actionOnLoad == ActionOnLoadPlayInline) { + psOpenFile(already); + } + _actionOnLoad = ActionOnLoadNone; +} + +bool VideoData::loaded(bool check) const { + if (loading() && _loader->done()) { + if (_loader->fileType() == mtpc_storage_fileUnknown) { + _loader->deleteLater(); + _loader->rpcInvalidate(); + _loader = CancelledMtpFileLoader; + } else { + VideoData *that = const_cast(this); + that->_location = FileLocation(mtpToStorageType(_loader->fileType()), _loader->fileName()); + + _loader->deleteLater(); + _loader->rpcInvalidate(); + _loader = 0; + } + notifyLayoutChanged(); + } + return !already(check).isEmpty(); +} + +bool VideoData::loading() const { + return _loader && _loader != CancelledMtpFileLoader; +} + +bool VideoData::displayLoading() const { + return loading() ? (!_loader->loadingLocal() || !_loader->autoLoading()) : uploading(); +} + +float64 VideoData::progress() const { + if (uploading()) { + if (size > 0) { + return float64(uploadOffset) / size; + } + return 0; + } + return loading() ? _loader->currentProgress() : (loaded() ? 1 : 0); +} + +int32 VideoData::loadOffset() const { + return loading() ? _loader->currentOffset() : 0; +} + +bool VideoData::uploading() const { + return status == FileUploading; +} + +void VideoData::save(const QString &toFile, ActionOnLoad action, const FullMsgId &actionMsgId, LoadFromCloudSetting fromCloud, bool autoLoading) { + if (loaded(true)) { + const FileLocation &l(location(true)); + if (!toFile.isEmpty()) { + if (l.accessEnable()) { + QFile(l.name()).copy(toFile); + l.accessDisable(); + } + } + return; + } + + if (_loader == CancelledMtpFileLoader) _loader = 0; + if (_loader) { + if (!_loader->setFileName(toFile)) { + cancel(); + _loader = 0; + } + } + + _actionOnLoad = action; + _actionOnLoadMsgId = actionMsgId; + + if (_loader) { + if (fromCloud == LoadFromCloudOrLocal) _loader->permitLoadFromCloud(); + } else { + status = FileReady; + _loader = new mtpFileLoader(dc, id, access, VideoFileLocation, toFile, size, (saveToCache() ? LoadToCacheAsWell : LoadToFileOnly), fromCloud, autoLoading); + _loader->connect(_loader, SIGNAL(progress(FileLoader*)), App::main(), SLOT(videoLoadProgress(FileLoader*))); + _loader->connect(_loader, SIGNAL(failed(FileLoader*,bool)), App::main(), SLOT(videoLoadFailed(FileLoader*,bool))); + _loader->start(); + } + + notifyLayoutChanged(); +} + +void VideoData::cancel() { + if (!loading()) return; + + mtpFileLoader *l = _loader; + _loader = CancelledMtpFileLoader; + if (l) { + l->cancel(); + l->deleteLater(); + l->rpcInvalidate(); + + notifyLayoutChanged(); + } + _actionOnLoad = ActionOnLoadNone; +} + +void VideoData::notifyLayoutChanged() const { + const VideoItems &items(App::videoItems()); + VideoItems::const_iterator i = items.constFind(const_cast(this)); + if (i != items.cend()) { + for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) { + Notify::historyItemLayoutChanged(j.key()); + } + } +} + +QString VideoData::already(bool check) const { return location(check).name(); } -const FileLocation &VideoData::location(bool check) { - if (check && !_location.check()) _location = Local::readFileLocation(mediaKey(VideoFileLocation, dc, id)); +QByteArray VideoData::data() const { + return QByteArray(); +} + +const FileLocation &VideoData::location(bool check) const { + if (check && !_location.check()) { + const_cast(this)->_location = Local::readFileLocation(mediaKey(VideoFileLocation, dc, id)); + } return _location; } -void AudioOpenLink::onClick(Qt::MouseButton button) const { - AudioData *data = audio(); - if (!data->date || button != Qt::LeftButton) return; +void VideoData::setLocation(const FileLocation &loc) { + if (loc.check()) { + _location = loc; + } +} - QString already = data->already(true); - bool play = App::hoveredLinkItem() && audioPlayer(); - if (!already.isEmpty() || (!data->data.isEmpty() && play)) { +void AudioOpenLink::onClick(Qt::MouseButton button) const { + if (button != Qt::LeftButton) return; + AudioData *data = audio(); + + if (!data->date) return; + + HistoryItem *item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : 0); + + bool play = audioPlayer() && item; + const FileLocation &location(data->location(true)); + if (!location.isEmpty() || (!data->data().isEmpty() && play)) { if (play) { AudioMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; audioPlayer()->currentState(&playing, &playingState); - if (playing.msgId == App::hoveredLinkItem()->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { + if (playing.msgId == item->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { audioPlayer()->pauseresume(OverviewAudios); } else { - audioPlayer()->play(AudioMsgId(data, App::hoveredLinkItem()->fullId())); - if (App::main()) App::main()->audioMarkRead(data); + AudioMsgId audio(data, item->fullId()); + audioPlayer()->play(audio); + if (App::main()) { + App::main()->audioPlayProgress(audio); + App::main()->audioMarkRead(data); + } } } else { - psOpenFile(already); + psOpenFile(location.name()); if (App::main()) App::main()->audioMarkRead(data); } return; @@ -756,20 +1047,23 @@ void AudioOpenLink::onClick(Qt::MouseButton button) const { if (data->status != FileReady) return; - bool mp3 = (data->mime == qstr("audio/mp3")); - QString filename = saveFileName(lang(lng_save_audio), mp3 ? qsl("MP3 Audio (*.mp3);;All files (*.*)") : qsl("OGG Opus Audio (*.ogg);;All files (*.*)"), qsl("audio"), mp3 ? qsl(".mp3") : qsl(".ogg"), false); - if (!filename.isEmpty()) { - data->openOnSave = 1; - data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->fullId() : (App::contextItem() ? App::contextItem()->fullId() : FullMsgId()); - data->save(filename); + QString filename; + if (!data->saveToCache()) { + bool mp3 = (data->mime == qstr("audio/mp3")); + filename = saveFileName(lang(lng_save_audio), mp3 ? qsl("MP3 Audio (*.mp3);;All files (*.*)") : qsl("OGG Opus Audio (*.ogg);;All files (*.*)"), qsl("audio"), mp3 ? qsl(".mp3") : qsl(".ogg"), false); + + if (filename.isEmpty()) return; } + + data->save(filename, ActionOnLoadOpen, item ? item->fullId() : FullMsgId()); } void AudioSaveLink::doSave(AudioData *data, bool forceSavingAs) { if (!data->date) return; QString already = data->already(true); - if (!already.isEmpty() && !forceSavingAs) { + bool openWith = !already.isEmpty(); + if (openWith && !forceSavingAs) { QPoint pos(QCursor::pos()); if (!psShowOpenWithMenu(pos.x(), pos.y(), already)) { psOpenFile(already, true); @@ -781,13 +1075,9 @@ void AudioSaveLink::doSave(AudioData *data, bool forceSavingAs) { QString name = already.isEmpty() ? (mp3 ? qsl(".mp3") : qsl(".ogg")) : alreadyInfo.fileName(); QString filename = saveFileName(lang(lng_save_audio), mp3 ? qsl("MP3 Audio (*.mp3);;All files (*.*)") : qsl("OGG Opus Audio (*.ogg);;All files (*.*)"), qsl("audio"), name, forceSavingAs, alreadyDir); if (!filename.isEmpty()) { - if (forceSavingAs) { - data->cancel(); - } else if (!already.isEmpty()) { - data->openOnSave = -1; - data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->fullId() : (App::contextItem() ? App::contextItem()->fullId() : FullMsgId()); - } - data->save(filename); + ActionOnLoad action = already.isEmpty() ? ActionOnLoadNone : ActionOnLoadOpenWith; + FullMsgId actionMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->fullId() : (App::contextItem() ? App::contextItem()->fullId() : FullMsgId()); + data->save(filename, action, actionMsgId); } } } @@ -801,7 +1091,17 @@ void AudioCancelLink::onClick(Qt::MouseButton button) const { AudioData *data = audio(); if (!data->date || button != Qt::LeftButton) return; - data->cancel(); + if (data->uploading()) { + HistoryItem *item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : 0); + if (HistoryMessage *msg = item->toHistoryMessage()) { + if (msg->getMedia() && msg->getMedia()->type() == MediaTypeAudio && static_cast(msg->getMedia())->audio() == data) { + App::contextItem(item); + App::main()->deleteLayer(-2); + } + } + } else { + data->cancel(); + } } bool StickerData::setInstalled() const { @@ -822,59 +1122,260 @@ bool StickerData::setInstalled() const { return false; } -AudioData::AudioData(const AudioId &id, const uint64 &access, int32 date, const QString &mime, int32 duration, int32 dc, int32 size) : -id(id), access(access), date(date), mime(mime), duration(duration), dc(dc), size(size), status(FileReady), uploadOffset(0), openOnSave(0), loader(0) { +AudioData::AudioData(const AudioId &id, const uint64 &access, int32 date, const QString &mime, int32 duration, int32 dc, int32 size) +: id(id) +, access(access) +, date(date) +, mime(mime) +, duration(duration) +, dc(dc) +, size(size) +, status(FileReady) +, uploadOffset(0) +, _actionOnLoad(ActionOnLoadNone) +, _loader(0) { _location = Local::readFileLocation(mediaKey(AudioFileLocation, dc, id)); } -void AudioData::save(const QString &toFile) { - cancel(true); - loader = new mtpFileLoader(dc, id, access, AudioFileLocation, toFile, size, (size < AudioVoiceMsgInMemory)); - loader->connect(loader, SIGNAL(progress(mtpFileLoader*)), App::main(), SLOT(audioLoadProgress(mtpFileLoader*))); - loader->connect(loader, SIGNAL(failed(mtpFileLoader*, bool)), App::main(), SLOT(audioLoadFailed(mtpFileLoader*, bool))); - loader->start(); +bool AudioData::saveToCache() const { + return size < AudioVoiceMsgInMemory; } -QString AudioData::already(bool check) { +void AudioData::forget() { + _data.clear(); +} + +void AudioData::automaticLoad(const HistoryItem *item) { + if (loaded() || status != FileReady) return; + + if (saveToCache() && _loader != CancelledMtpFileLoader) { + if (item) { + bool loadFromCloud = false; + if (item->history()->peer->isUser()) { + loadFromCloud = !(cAutoDownloadAudio() & dbiadNoPrivate); + } else { + loadFromCloud = !(cAutoDownloadAudio() & dbiadNoGroups); + } + save(QString(), _actionOnLoad, _actionOnLoadMsgId, loadFromCloud ? LoadFromCloudOrLocal : LoadFromLocalOnly, true); + } + } +} + +void AudioData::automaticLoadSettingsChanged() { + if (loaded() || status != FileReady || !saveToCache() || _loader != CancelledMtpFileLoader) return; + _loader = 0; +} + +void AudioData::performActionOnLoad() { + if (_actionOnLoad == ActionOnLoadNone) return; + + const FileLocation &loc(location(true)); + QString already = loc.name(); + bool play = _actionOnLoadMsgId.msg && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen) && audioPlayer(); + + if (play) { + if (loaded()) { + AudioMsgId playing; + AudioPlayerState state = AudioPlayerStopped; + audioPlayer()->currentState(&playing, &state); + if (playing.msgId == _actionOnLoadMsgId && !(state & AudioPlayerStoppedMask) && state != AudioPlayerFinishing) { + audioPlayer()->pauseresume(OverviewAudios); + } else { + audioPlayer()->play(AudioMsgId(this, _actionOnLoadMsgId)); + if (App::main()) App::main()->audioMarkRead(this); + } + } + } else { + if (already.isEmpty()) return; + if (_actionOnLoad == ActionOnLoadOpenWith) { + if (already.isEmpty()) return; + + QPoint pos(QCursor::pos()); + if (!psShowOpenWithMenu(pos.x(), pos.y(), already)) { + psOpenFile(already, true); + } + if (App::main()) App::main()->audioMarkRead(this); + } else if (_actionOnLoad == ActionOnLoadOpen || _actionOnLoad == ActionOnLoadPlayInline) { + psOpenFile(already); + if (App::main()) App::main()->audioMarkRead(this); + } + } + _actionOnLoad = ActionOnLoadNone; +} + +bool AudioData::loaded(bool check) const { + if (loading() && _loader->done()) { + if (_loader->fileType() == mtpc_storage_fileUnknown) { + _loader->deleteLater(); + _loader->rpcInvalidate(); + _loader = CancelledMtpFileLoader; + } else { + AudioData *that = const_cast(this); + that->_location = FileLocation(mtpToStorageType(_loader->fileType()), _loader->fileName()); + that->_data = _loader->bytes(); + + _loader->deleteLater(); + _loader->rpcInvalidate(); + _loader = 0; + } + notifyLayoutChanged(); + } + return !_data.isEmpty() || !already(check).isEmpty(); +} + +bool AudioData::loading() const { + return _loader && _loader != CancelledMtpFileLoader; +} + +bool AudioData::displayLoading() const { + return loading() ? (!_loader->loadingLocal() || !_loader->autoLoading()) : uploading(); +} + +float64 AudioData::progress() const { + if (uploading()) { + if (size > 0) { + return float64(uploadOffset) / size; + } + return 0; + } + return loading() ? _loader->currentProgress() : (loaded() ? 1 : 0); +} + +int32 AudioData::loadOffset() const { + return loading() ? _loader->currentOffset() : 0; +} + +bool AudioData::uploading() const { + return status == FileUploading; +} + +void AudioData::save(const QString &toFile, ActionOnLoad action, const FullMsgId &actionMsgId, LoadFromCloudSetting fromCloud, bool autoLoading) { + if (loaded(true)) { + const FileLocation &l(location(true)); + if (!toFile.isEmpty()) { + if (!_data.isEmpty()) { + QFile f(toFile); + f.open(QIODevice::WriteOnly); + f.write(_data); + } else if (l.accessEnable()) { + QFile(l.name()).copy(toFile); + l.accessDisable(); + } + } + return; + } + + if (_loader == CancelledMtpFileLoader) _loader = 0; + if (_loader) { + if (!_loader->setFileName(toFile)) { + cancel(); + _loader = 0; + } + } + + _actionOnLoad = action; + _actionOnLoadMsgId = actionMsgId; + + if (_loader) { + if (fromCloud == LoadFromCloudOrLocal) _loader->permitLoadFromCloud(); + } else { + status = FileReady; + _loader = new mtpFileLoader(dc, id, access, AudioFileLocation, toFile, size, (saveToCache() ? LoadToCacheAsWell : LoadToFileOnly), fromCloud, autoLoading); + _loader->connect(_loader, SIGNAL(progress(FileLoader*)), App::main(), SLOT(audioLoadProgress(FileLoader*))); + _loader->connect(_loader, SIGNAL(failed(FileLoader*,bool)), App::main(), SLOT(audioLoadFailed(FileLoader*,bool))); + _loader->start(); + } + + notifyLayoutChanged(); +} + +void AudioData::cancel() { + if (!loading()) return; + + mtpFileLoader *l = _loader; + _loader = CancelledMtpFileLoader; + if (l) { + l->cancel(); + l->deleteLater(); + l->rpcInvalidate(); + + notifyLayoutChanged(); + } + _actionOnLoad = ActionOnLoadNone; +} + +void AudioData::notifyLayoutChanged() const { + const AudioItems &items(App::audioItems()); + AudioItems::const_iterator i = items.constFind(const_cast(this)); + if (i != items.cend()) { + for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) { + Notify::historyItemLayoutChanged(j.key()); + } + } +} + +QString AudioData::already(bool check) const { return location(check).name(); } -const FileLocation &AudioData::location(bool check) { - if (check && !_location.check()) _location = Local::readFileLocation(mediaKey(AudioFileLocation, dc, id)); +QByteArray AudioData::data() const { + return _data; +} + +const FileLocation &AudioData::location(bool check) const { + if (check && !_location.check()) { + const_cast(this)->_location = Local::readFileLocation(mediaKey(AudioFileLocation, dc, id)); + } return _location; } -void DocumentOpenLink::doOpen(DocumentData *data) { +void AudioData::setLocation(const FileLocation &loc) { + if (loc.check()) { + _location = loc; + } +} + +void DocumentOpenLink::doOpen(DocumentData *data, ActionOnLoad action) { if (!data->date) return; - bool play = data->song() && App::hoveredLinkItem() && audioPlayer(); + HistoryItem *item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : 0); + + bool playMusic = data->song() && audioPlayer() && item; + bool playAnimation = data->isAnimation() && item && item->getMedia(); const FileLocation &location(data->location(true)); - if (!location.isEmpty() || (!data->data.isEmpty() && play)) { - if (play) { + if (!location.isEmpty() || (!data->data().isEmpty() && (playMusic || playAnimation))) { + if (playMusic) { SongMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; audioPlayer()->currentState(&playing, &playingState); - if (playing.msgId == App::hoveredLinkItem()->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { + if (playing.msgId == item->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { audioPlayer()->pauseresume(OverviewDocuments); } else { - SongMsgId song(data, App::hoveredLinkItem()->fullId()); + SongMsgId song(data, item->fullId()); audioPlayer()->play(song); if (App::main()) App::main()->documentPlayProgress(song); } - } else if (data->size < MediaViewImageSizeLimit && location.accessEnable()) { - QImageReader reader(location.name()); - if (reader.canRead()) { - if (reader.supportsAnimation() && reader.imageCount() > 1 && App::hoveredLinkItem()) { - startGif(App::hoveredLinkItem(), location); - } else if (App::hoveredLinkItem() || App::contextItem()) { - App::wnd()->showDocument(data, App::hoveredLinkItem() ? App::hoveredLinkItem() : App::contextItem()); + } else if (data->size < MediaViewImageSizeLimit) { + if (!data->data().isEmpty() && playAnimation) { + if (action == ActionOnLoadPlayInline) { + item->getMedia()->playInline(item); + } else { + App::wnd()->showDocument(data, item); + } + } else if (location.accessEnable()) { + if ((App::hoveredLinkItem() || App::contextItem()) && (data->isAnimation() || QImageReader(location.name()).canRead())) { + if (action == ActionOnLoadPlayInline) { + item->getMedia()->playInline(item); + } else { + App::wnd()->showDocument(data, item); + } } else { psOpenFile(location.name()); } + location.accessDisable(); } else { psOpenFile(location.name()); } - location.accessDisable(); } else { psOpenFile(location.name()); } @@ -883,26 +1384,28 @@ void DocumentOpenLink::doOpen(DocumentData *data) { if (data->status != FileReady) return; - QString name = data->name, filter; - MimeType mimeType = mimeTypeForName(data->mime); - QStringList p = mimeType.globPatterns(); - QString pattern = p.isEmpty() ? QString() : p.front(); - if (name.isEmpty()) { - name = pattern.isEmpty() ? qsl(".unknown") : pattern.replace('*', QString()); + QString filename; + if (!data->saveToCache()) { + QString name = data->name, filter; + MimeType mimeType = mimeTypeForName(data->mime); + QStringList p = mimeType.globPatterns(); + QString pattern = p.isEmpty() ? QString() : p.front(); + if (name.isEmpty()) { + name = pattern.isEmpty() ? qsl(".unknown") : pattern.replace('*', QString()); + } + + if (pattern.isEmpty()) { + filter = QString(); + } else { + filter = mimeType.filterString() + qsl(";;All files (*.*)"); + } + + filename = saveFileName(lang(lng_save_file), filter, qsl("doc"), name, false); + + if (filename.isEmpty()) return; } - if (pattern.isEmpty()) { - filter = QString(); - } else { - filter = mimeType.filterString() + qsl(";;All files (*.*)"); - } - - QString filename = saveFileName(lang(lng_save_file), filter, qsl("doc"), name, false); - if (!filename.isEmpty()) { - data->openOnSave = 1; - data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->fullId() : (App::contextItem() ? App::contextItem()->fullId() : FullMsgId()); - data->save(filename); - } + data->save(filename, action, item ? item->fullId() : FullMsgId()); } void DocumentOpenLink::onClick(Qt::MouseButton button) const { @@ -910,11 +1413,21 @@ void DocumentOpenLink::onClick(Qt::MouseButton button) const { doOpen(document()); } +void GifOpenLink::doOpen(DocumentData *data) { + return DocumentOpenLink::doOpen(data, ActionOnLoadPlayInline); +} + +void GifOpenLink::onClick(Qt::MouseButton button) const { + if (button != Qt::LeftButton) return; + doOpen(document()); +} + void DocumentSaveLink::doSave(DocumentData *data, bool forceSavingAs) { if (!data->date) return; QString already = data->already(true); - if (!already.isEmpty() && !forceSavingAs) { + bool openWith = !already.isEmpty(); + if (openWith && !forceSavingAs) { QPoint pos(QCursor::pos()); if (!psShowOpenWithMenu(pos.x(), pos.y(), already)) { psOpenFile(already, true); @@ -938,13 +1451,9 @@ void DocumentSaveLink::doSave(DocumentData *data, bool forceSavingAs) { QString filename = saveFileName(lang(lng_save_file), filter, qsl("doc"), name, forceSavingAs, alreadyDir); if (!filename.isEmpty()) { - if (forceSavingAs) { - data->cancel(); - } else if (!already.isEmpty()) { - data->openOnSave = -1; - data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->fullId() : (App::contextItem() ? App::contextItem()->fullId() : FullMsgId()); - } - data->save(filename); + ActionOnLoad action = already.isEmpty() ? ActionOnLoadNone : ActionOnLoadOpenWith; + FullMsgId actionMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->fullId() : (App::contextItem() ? App::contextItem()->fullId() : FullMsgId()); + data->save(filename, action, actionMsgId); } } } @@ -955,16 +1464,40 @@ void DocumentSaveLink::onClick(Qt::MouseButton button) const { } void DocumentCancelLink::onClick(Qt::MouseButton button) const { - DocumentData *data = document(); - if (!data->date || button != Qt::LeftButton) return; + if (button != Qt::LeftButton) return; - data->cancel(); + DocumentData *data = document(); + if (!data->date) return; + + if (data->uploading()) { + HistoryItem *item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : 0); + if (HistoryMessage *msg = item->toHistoryMessage()) { + if (msg->getMedia() && msg->getMedia()->getDocument() == data) { + App::contextItem(item); + App::main()->deleteLayer(-2); + } + } + } else { + data->cancel(); + } } -DocumentData::DocumentData(const DocumentId &id, const uint64 &access, int32 date, const QVector &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size) : -id(id), type(FileDocument), access(access), date(date), mime(mime), thumb(thumb), dc(dc), size(size), status(FileReady), uploadOffset(0), openOnSave(0), loader(0), _additional(0) { - setattributes(attributes); +DocumentData::DocumentData(const DocumentId &id, const uint64 &access, int32 date, const QVector &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size) : id(id) +, type(FileDocument) +, access(access) +, date(date) +, mime(mime) +, thumb(thumb) +, dc(dc) +, size(size) +, status(FileReady) +, uploadOffset(0) +, _additional(0) +, _duration(-1) +, _actionOnLoad(ActionOnLoadNone) +, _loader(0) { _location = Local::readFileLocation(mediaKey(DocumentFileLocation, dc, id)); + setattributes(attributes); } void DocumentData::setattributes(const QVector &attributes) { @@ -974,7 +1507,7 @@ void DocumentData::setattributes(const QVector &attributes const MTPDdocumentAttributeImageSize &d(attributes[i].c_documentAttributeImageSize()); dimensions = QSize(d.vw.v, d.vh.v); } break; - case mtpc_documentAttributeAnimated: if (type == FileDocument || type == StickerDocument) { + case mtpc_documentAttributeAnimated: if (type == FileDocument || type == StickerDocument || type == VideoDocument) { type = AnimatedDocument; delete _additional; _additional = 0; @@ -985,24 +1518,32 @@ void DocumentData::setattributes(const QVector &attributes type = StickerDocument; StickerData *sticker = new StickerData(); _additional = sticker; - sticker->alt = qs(d.valt); - sticker->set = d.vstickerset; + } + if (sticker()) { + sticker()->alt = qs(d.valt); + sticker()->set = d.vstickerset; } } break; case mtpc_documentAttributeVideo: { const MTPDdocumentAttributeVideo &d(attributes[i].c_documentAttributeVideo()); - type = VideoDocument; -// duration = d.vduration.v; + if (type == FileDocument) { + type = VideoDocument; + } + _duration = d.vduration.v; dimensions = QSize(d.vw.v, d.vh.v); } break; case mtpc_documentAttributeAudio: { const MTPDdocumentAttributeAudio &d(attributes[i].c_documentAttributeAudio()); - type = SongDocument; - SongData *song = new SongData(); - _additional = song; - song->duration = d.vduration.v; - song->title = qs(d.vtitle); - song->performer = qs(d.vperformer); + if (type == FileDocument) { + type = SongDocument; + SongData *song = new SongData(); + _additional = song; + } + if (song()) { + song()->duration = d.vduration.v; + song()->title = qs(d.vtitle); + song()->performer = qs(d.vperformer); + } } break; case mtpc_documentAttributeFilename: name = qs(attributes[i].c_documentAttributeFilename().vfile_name); break; } @@ -1016,35 +1557,401 @@ void DocumentData::setattributes(const QVector &attributes } } -void DocumentData::save(const QString &toFile) { - cancel(true); - bool isSticker = (type == StickerDocument) && (dimensions.width() > 0) && (dimensions.height() > 0) && (size < StickerInMemory); - loader = new mtpFileLoader(dc, id, access, DocumentFileLocation, toFile, size, isSticker); - loader->connect(loader, SIGNAL(progress(mtpFileLoader*)), App::main(), SLOT(documentLoadProgress(mtpFileLoader*))); - loader->connect(loader, SIGNAL(failed(mtpFileLoader*, bool)), App::main(), SLOT(documentLoadFailed(mtpFileLoader*, bool))); - loader->start(); +bool DocumentData::saveToCache() const { + return (type == StickerDocument) || (isAnimation() && size < AnimationInMemory); } -QString DocumentData::already(bool check) { +void DocumentData::forget() { + thumb->forget(); + if (sticker()) sticker()->img->forget(); + replyPreview->forget(); + _data.clear(); +} + +void DocumentData::automaticLoad(const HistoryItem *item) { + if (loaded() || status != FileReady) return; + + if (saveToCache() && _loader != CancelledMtpFileLoader) { + if (type == StickerDocument) { + save(QString(), _actionOnLoad, _actionOnLoadMsgId); + } else if (isAnimation()) { + bool loadFromCloud = false; + if (item) { + if (item->history()->peer->isUser()) { + loadFromCloud = !(cAutoDownloadGif() & dbiadNoPrivate); + } else { + loadFromCloud = !(cAutoDownloadGif() & dbiadNoGroups); + } + } else { // if load at least anywhere + loadFromCloud = !(cAutoDownloadGif() & dbiadNoPrivate) || !(cAutoDownloadGif() & dbiadNoGroups); + } + save(QString(), _actionOnLoad, _actionOnLoadMsgId, loadFromCloud ? LoadFromCloudOrLocal : LoadFromLocalOnly, true); + } + } +} + +void DocumentData::automaticLoadSettingsChanged() { + if (loaded() || status != FileReady || !isAnimation() || !saveToCache() || _loader != CancelledMtpFileLoader) return; + _loader = 0; +} + +void DocumentData::performActionOnLoad() { + if (_actionOnLoad == ActionOnLoadNone) return; + + const FileLocation &loc(location(true)); + QString already = loc.name(); + HistoryItem *item = _actionOnLoadMsgId.msg ? App::histItemById(_actionOnLoadMsgId) : 0; + bool showImage = item && (size < MediaViewImageSizeLimit); + bool playMusic = song() && audioPlayer() && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen) && item; + bool playAnimation = isAnimation() && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen) && showImage && item->getMedia(); + if (playMusic) { + if (loaded()) { + SongMsgId playing; + AudioPlayerState playingState = AudioPlayerStopped; + audioPlayer()->currentState(&playing, &playingState); + if (playing.msgId == item->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) { + audioPlayer()->pauseresume(OverviewDocuments); + } else { + SongMsgId song(this, item->fullId()); + audioPlayer()->play(song); + if (App::main()) App::main()->documentPlayProgress(song); + } + } + } else if (playAnimation) { + if (loaded()) { + if (_actionOnLoad == ActionOnLoadPlayInline) { + item->getMedia()->playInline(item); + } else { + App::wnd()->showDocument(this, item); + } + } + } else { + if (already.isEmpty()) return; + + if (_actionOnLoad == ActionOnLoadOpenWith) { + if (already.isEmpty()) return; + + QPoint pos(QCursor::pos()); + if (!psShowOpenWithMenu(pos.x(), pos.y(), already)) { + psOpenFile(already, true); + } + } else if (_actionOnLoad == ActionOnLoadOpen || _actionOnLoad == ActionOnLoadPlayInline) { + if (loc.accessEnable()) { + if (showImage && QImageReader(loc.name()).canRead()) { + if (_actionOnLoad == ActionOnLoadPlayInline) { + item->getMedia()->playInline(item); + } else { + App::wnd()->showDocument(this, item); + } + } else { + psOpenFile(already); + } + loc.accessDisable(); + } else { + psOpenFile(already); + } + } + } + _actionOnLoad = ActionOnLoadNone; +} + +bool DocumentData::loaded(bool check) const { + if (loading() && _loader->done()) { + if (_loader->fileType() == mtpc_storage_fileUnknown) { + _loader->deleteLater(); + _loader->rpcInvalidate(); + _loader = CancelledMtpFileLoader; + } else { + DocumentData *that = const_cast(this); + that->_location = FileLocation(mtpToStorageType(_loader->fileType()), _loader->fileName()); + that->_data = _loader->bytes(); + if (that->sticker() && !_loader->imagePixmap().isNull()) { + that->sticker()->img = ImagePtr(_data, _loader->imageFormat(), _loader->imagePixmap()); + } + + _loader->deleteLater(); + _loader->rpcInvalidate(); + _loader = 0; + } + notifyLayoutChanged(); + } + return !_data.isEmpty() || !already(check).isEmpty(); +} + +bool DocumentData::loading() const { + return _loader && _loader != CancelledMtpFileLoader; +} + +bool DocumentData::displayLoading() const { + return loading() ? (!_loader->loadingLocal() || !_loader->autoLoading()) : uploading(); +} + +float64 DocumentData::progress() const { + if (uploading()) { + if (size > 0) { + return float64(uploadOffset) / size; + } + return 0; + } + return loading() ? _loader->currentProgress() : (loaded() ? 1 : 0); +} + +int32 DocumentData::loadOffset() const { + return loading() ? _loader->currentOffset() : 0; +} + +bool DocumentData::uploading() const { + return status == FileUploading; +} + +void DocumentData::save(const QString &toFile, ActionOnLoad action, const FullMsgId &actionMsgId, LoadFromCloudSetting fromCloud, bool autoLoading) { + if (loaded(true)) { + const FileLocation &l(location(true)); + if (!toFile.isEmpty()) { + if (!_data.isEmpty()) { + QFile f(toFile); + f.open(QIODevice::WriteOnly); + f.write(_data); + } else if (l.accessEnable()) { + QFile(l.name()).copy(toFile); + l.accessDisable(); + } + } + return; + } + + if (_loader == CancelledMtpFileLoader) _loader = 0; + if (_loader) { + if (!_loader->setFileName(toFile)) { + cancel(); + _loader = 0; + } + } + + _actionOnLoad = action; + _actionOnLoadMsgId = actionMsgId; + + if (_loader) { + if (fromCloud == LoadFromCloudOrLocal) _loader->permitLoadFromCloud(); + } else { + status = FileReady; + _loader = new mtpFileLoader(dc, id, access, DocumentFileLocation, toFile, size, (saveToCache() ? LoadToCacheAsWell : LoadToFileOnly), fromCloud, autoLoading); + _loader->connect(_loader, SIGNAL(progress(FileLoader*)), App::main(), SLOT(documentLoadProgress(FileLoader*))); + _loader->connect(_loader, SIGNAL(failed(FileLoader*,bool)), App::main(), SLOT(documentLoadFailed(FileLoader*,bool))); + _loader->start(); + } + + notifyLayoutChanged(); +} + +void DocumentData::cancel() { + if (!loading()) return; + + mtpFileLoader *l = _loader; + _loader = CancelledMtpFileLoader; + if (l) { + l->cancel(); + l->deleteLater(); + l->rpcInvalidate(); + + notifyLayoutChanged(); + } + _actionOnLoad = ActionOnLoadNone; +} + +void DocumentData::notifyLayoutChanged() const { + const DocumentItems &items(App::documentItems()); + DocumentItems::const_iterator i = items.constFind(const_cast(this)); + if (i != items.cend()) { + for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) { + Notify::historyItemLayoutChanged(j.key()); + } + } +} + +QString DocumentData::already(bool check) const { + if (check && _location.name().isEmpty()) return QString(); return location(check).name(); } -const FileLocation &DocumentData::location(bool check) { - if (check && !_location.check()) _location = Local::readFileLocation(mediaKey(DocumentFileLocation, dc, id)); +QByteArray DocumentData::data() const { + return _data; +} + +const FileLocation &DocumentData::location(bool check) const { + if (check && !_location.check()) { + const_cast(this)->_location = Local::readFileLocation(mediaKey(DocumentFileLocation, dc, id)); + } return _location; } -WebPageData::WebPageData(const WebPageId &id, WebPageType 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) : -id(id), type(type), url(url), displayUrl(displayUrl), siteName(siteName), title(title), description(description), duration(duration), author(author), photo(photo), doc(doc), pendingTill(pendingTill) { +void DocumentData::setLocation(const FileLocation &loc) { + if (loc.check()) { + _location = loc; + } +} + +ImagePtr DocumentData::makeReplyPreview() { + if (replyPreview->isNull() && !thumb->isNull()) { + if (thumb->loaded()) { + int w = thumb->width(), h = thumb->height(); + if (w <= 0) w = 1; + if (h <= 0) h = 1; + replyPreview = ImagePtr(w > h ? thumb->pix(w * st::msgReplyBarSize.height() / h, st::msgReplyBarSize.height()) : thumb->pix(st::msgReplyBarSize.height()), "PNG"); + } else { + thumb->load(); + } + } + return replyPreview; +} + +bool fileIsImage(const QString &name, const QString &mime) { + QString lowermime = mime.toLower(), namelower = name.toLower(); + if (lowermime.startsWith(qstr("image/"))) { + return true; + } else if (namelower.endsWith(qstr(".bmp")) + || namelower.endsWith(qstr(".jpg")) + || namelower.endsWith(qstr(".jpeg")) + || namelower.endsWith(qstr(".gif")) + || namelower.endsWith(qstr(".webp")) + || namelower.endsWith(qstr(".tga")) + || namelower.endsWith(qstr(".tiff")) + || namelower.endsWith(qstr(".tif")) + || namelower.endsWith(qstr(".psd")) + || namelower.endsWith(qstr(".png"))) { + return true; + } + return false; +} + +void DocumentData::recountIsImage() { + if (isAnimation() || type == VideoDocument) return; + _duration = fileIsImage(name, mime) ? 1 : -1; // hack +} + +DocumentData::~DocumentData() { + delete _additional; +} + +WebPageData::WebPageData(const WebPageId &id, WebPageType 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) : id(id) +, type(type) +, url(url) +, displayUrl(displayUrl) +, siteName(siteName) +, title(title) +, description(description) +, duration(duration) +, author(author) +, photo(photo) +, doc(doc) +, pendingTill(pendingTill) { +} + +void InlineResult::automaticLoadGif() { + if (loaded() || type != qstr("gif") || (content_type != qstr("video/mp4") && content_type != "image/gif")) return; + + if (_loader != CancelledWebFileLoader) { + // if load at least anywhere + bool loadFromCloud = !(cAutoDownloadGif() & dbiadNoPrivate) || !(cAutoDownloadGif() & dbiadNoGroups); + saveFile(QString(), loadFromCloud ? LoadFromCloudOrLocal : LoadFromLocalOnly, true); + } +} + +void InlineResult::automaticLoadSettingsChangedGif() { + if (loaded() || _loader != CancelledWebFileLoader) return; + _loader = 0; +} + +void InlineResult::saveFile(const QString &toFile, LoadFromCloudSetting fromCloud, bool autoLoading) { + if (loaded()) { + return; + } + + if (_loader == CancelledWebFileLoader) _loader = 0; + if (_loader) { + if (!_loader->setFileName(toFile)) { + cancelFile(); + _loader = 0; + } + } + + if (_loader) { + if (fromCloud == LoadFromCloudOrLocal) _loader->permitLoadFromCloud(); + } else { + _loader = new webFileLoader(content_url, toFile, fromCloud, autoLoading); + App::regInlineResultLoader(_loader, this); + + _loader->connect(_loader, SIGNAL(progress(FileLoader*)), App::main(), SLOT(inlineResultLoadProgress(FileLoader*))); + _loader->connect(_loader, SIGNAL(failed(FileLoader*,bool)), App::main(), SLOT(inlineResultLoadFailed(FileLoader*,bool))); + _loader->start(); + } +} + +void InlineResult::cancelFile() { + if (!loading()) return; + + App::unregInlineResultLoader(_loader); + + webFileLoader *l = _loader; + _loader = CancelledWebFileLoader; + if (l) { + l->cancel(); + l->deleteLater(); + l->stop(); + } +} + +QByteArray InlineResult::data() const { + return _data; +} + +bool InlineResult::loading() const { + return _loader && _loader != CancelledWebFileLoader; +} + +bool InlineResult::loaded() const { + if (loading() && _loader->done()) { + App::unregInlineResultLoader(_loader); + if (_loader->fileType() == mtpc_storage_fileUnknown) { + _loader->deleteLater(); + _loader->stop(); + _loader = CancelledWebFileLoader; + } else { + InlineResult *that = const_cast(this); + that->_data = _loader->bytes(); + + _loader->deleteLater(); + _loader->stop(); + _loader = 0; + } + } + return !_data.isEmpty(); +} + +bool InlineResult::displayLoading() const { + return loading() ? (!_loader->loadingLocal() || !_loader->autoLoading()) : false; +} + +void InlineResult::forget() { + thumb->forget(); + _data.clear(); +} + +float64 InlineResult::progress() const { + return loading() ? _loader->currentProgress() : (loaded() ? 1 : 0); return false; +} + +InlineResult::~InlineResult() { + cancelFile(); } void PeerLink::onClick(Qt::MouseButton button) const { if (button == Qt::LeftButton && App::main()) { if (peer() && peer()->isChannel() && App::main()->historyPeer() != peer()) { if (!peer()->asChannel()->isPublic() && !peer()->asChannel()->amIn()) { - App::wnd()->showLayer(new InformBox(lang((peer()->isMegagroup()) ? lng_group_not_accessible : lng_channel_not_accessible))); + Ui::showLayer(new InformBox(lang((peer()->isMegagroup()) ? lng_group_not_accessible : lng_channel_not_accessible))); } else { - App::main()->showPeerHistory(peer()->id, ShowAtUnreadMsgId); + Ui::showPeerHistory(peer(), ShowAtUnreadMsgId); } } else { App::main()->showPeerProfile(peer()); @@ -1058,13 +1965,13 @@ void MessageLink::onClick(Qt::MouseButton button) const { if (current && current->history()->peer->id == peer()) { App::main()->pushReplyReturn(current); } - App::main()->showPeerHistory(peer(), msgid()); + Ui::showPeerHistory(peer(), msgid()); } } void CommentsLink::onClick(Qt::MouseButton button) const { if (button == Qt::LeftButton && App::main() && _item->history()->isChannel()) { - App::main()->showPeerHistory(_item->history()->peer->id, _item->id); + Ui::showPeerHistoryAtItem(_item); } } diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index 1bc85bf058..8de9bf0c5d 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -186,6 +186,8 @@ inline bool isNotifyMuted(NotifySettingsPtr settings, int32 *changeIn = 0) { return false; } +static const int32 UserColorsCount = 8; + style::color peerColor(int32 index); ImagePtr userDefPhoto(int32 index); ImagePtr chatDefPhoto(int32 index); @@ -299,7 +301,6 @@ private: class BotCommand { public: BotCommand(const QString &command, const QString &description) : command(command), _description(description) { - } QString command; @@ -312,12 +313,7 @@ public: return false; } - const Text &descriptionText() const { - if (_descriptionText.isEmpty() && !_description.isEmpty()) { - _descriptionText.setText(st::mentionFont, _description, _textNameOptions); - } - return _descriptionText; - } + const Text &descriptionText() const; private: QString _description; @@ -331,7 +327,7 @@ struct BotInfo { bool inited; bool readsAllHistory, cantJoinGroups; int32 version; - QString shareText, description; + QString shareText, description, inlinePlaceholder; QList commands; Text text; // description @@ -344,7 +340,7 @@ enum UserBlockedStatus { UserIsNotBlocked, }; -struct PhotoData; +class PhotoData; class UserData : public PeerData { public: @@ -389,6 +385,7 @@ public: BotInfo *botInfo; }; +static UserData * const InlineBotLookingUpData = SharedMemoryLocation(); class ChatData : public PeerData { public: @@ -720,34 +717,35 @@ inline bool PeerData::canWrite() const { return isChannel() ? asChannel()->canWrite() : (isChat() ? asChat()->canWrite() : (isUser() ? asUser()->canWrite() : false)); } -inline int32 newMessageFlags(PeerData *p) { - return p->isSelf() ? 0 : (((p->isChat() || (p->isUser() && !p->asUser()->botInfo)) ? MTPDmessage::flag_unread : 0) | MTPDmessage::flag_out); -} +enum ActionOnLoad { + ActionOnLoadNone, + ActionOnLoadOpen, + ActionOnLoadOpenWith, + ActionOnLoadPlayInline +}; typedef QMap PreparedPhotoThumbs; -struct PhotoData { - PhotoData(const PhotoId &id, const uint64 &access = 0, int32 date = 0, const ImagePtr &thumb = ImagePtr(), const ImagePtr &medium = ImagePtr(), const ImagePtr &full = ImagePtr()) : - id(id), access(access), date(date), thumb(thumb), medium(medium), full(full), peer(0) { - } - void forget() { - thumb->forget(); - replyPreview->forget(); - medium->forget(); - full->forget(); - } - ImagePtr makeReplyPreview() { - if (replyPreview->isNull() && !thumb->isNull()) { - if (thumb->loaded()) { - int w = thumb->width(), h = thumb->height(); - if (w <= 0) w = 1; - if (h <= 0) h = 1; - replyPreview = ImagePtr(w > h ? thumb->pix(w * st::msgReplyBarSize.height() / h, st::msgReplyBarSize.height()) : thumb->pix(st::msgReplyBarSize.height()), "PNG"); - } else { - thumb->load(); - } - } - return replyPreview; - } +class PhotoData { +public: + PhotoData(const PhotoId &id, const uint64 &access = 0, int32 date = 0, const ImagePtr &thumb = ImagePtr(), const ImagePtr &medium = ImagePtr(), const ImagePtr &full = ImagePtr()); + + void automaticLoad(const HistoryItem *item); + void automaticLoadSettingsChanged(); + + void download(); + bool loaded() const; + bool loading() const; + bool displayLoading() const; + void cancel(); + float64 progress() const; + int32 loadOffset() const; + bool uploading() const; + + void forget(); + ImagePtr makeReplyPreview(); + + ~PhotoData(); + PhotoId id; uint64 access; int32 date; @@ -758,17 +756,23 @@ struct PhotoData { PeerData *peer; // for chat and channel photos connection // geo, caption - int32 cachew; - QPixmap cache; + struct UploadingData { + UploadingData(int32 size) : offset(0), size(size) { + } + int32 offset, size; + }; + UploadingData *uploadingData; + +private: + void notifyLayoutChanged() const; + }; class PhotoLink : public ITextLink { TEXT_LINK_CLASS(PhotoLink) public: - PhotoLink(PhotoData *photo) : _photo(photo), _peer(0) { - } - PhotoLink(PhotoData *photo, PeerData *peer) : _photo(photo), _peer(peer) { + PhotoLink(PhotoData *photo, PeerData *peer = 0) : _photo(photo), _peer(peer) { } void onClick(Qt::MouseButton button) const; PhotoData *photo() const { @@ -781,50 +785,66 @@ public: private: PhotoData *_photo; PeerData *_peer; + +}; + +class PhotoSaveLink : public PhotoLink { + TEXT_LINK_CLASS(PhotoSaveLink) + +public: + PhotoSaveLink(PhotoData *photo, PeerData *peer = 0) : PhotoLink(photo, peer) { + } + void onClick(Qt::MouseButton button) const; + +}; + +class PhotoCancelLink : public PhotoLink { + TEXT_LINK_CLASS(PhotoCancelLink) + +public: + PhotoCancelLink(PhotoData *photo, PeerData *peer = 0) : PhotoLink(photo, peer) { + } + void onClick(Qt::MouseButton button) const; + }; enum FileStatus { - FileFailed = -1, + FileDownloadFailed = -2, + FileUploadFailed = -1, FileUploading = 0, FileReady = 1, }; -struct VideoData { +class VideoData { +public: VideoData(const VideoId &id, const uint64 &access = 0, int32 date = 0, int32 duration = 0, int32 w = 0, int32 h = 0, const ImagePtr &thumb = ImagePtr(), int32 dc = 0, int32 size = 0); - void forget() { - thumb->forget(); - replyPreview->forget(); + void automaticLoad(const HistoryItem *item) { + } + void automaticLoadSettingsChanged() { } - void save(const QString &toFile); + bool loaded(bool check = false) const; + bool loading() const; + bool displayLoading() const; + void save(const QString &toFile, ActionOnLoad action = ActionOnLoadNone, const FullMsgId &actionMsgId = FullMsgId(), LoadFromCloudSetting fromCloud = LoadFromCloudOrLocal, bool autoLoading = false); + void cancel(); + float64 progress() const; + int32 loadOffset() const; + bool uploading() const; - void cancel(bool beforeDownload = false) { - mtpFileLoader *l = loader; - loader = 0; - if (l) { - l->cancel(); - l->deleteLater(); - l->rpcInvalidate(); - } - _location = FileLocation(); - if (!beforeDownload) { - openOnSave = 0; - openOnSaveMsgId = FullMsgId(); - } + QString already(bool check = false) const; + QByteArray data() const; + const FileLocation &location(bool check = false) const; + void setLocation(const FileLocation &loc); + + bool saveToCache() const { + return false; } - void finish() { - if (loader->done()) { - _location = FileLocation(mtpToStorageType(loader->fileType()), loader->fileName()); - } - loader->deleteLater(); - loader->rpcInvalidate(); - loader = 0; - } + void performActionOnLoad(); - QString already(bool check = false); - const FileLocation &location(bool check = false); + void forget(); VideoId id; uint64 access; @@ -838,14 +858,15 @@ struct VideoData { FileStatus status; int32 uploadOffset; - mtpTypeId fileType; - int32 openOnSave; - FullMsgId openOnSaveMsgId; - mtpFileLoader *loader; - private: FileLocation _location; + ActionOnLoad _actionOnLoad; + FullMsgId _actionOnLoadMsgId; + mutable mtpFileLoader *_loader; + + void notifyLayoutChanged() const; + }; class VideoLink : public ITextLink { @@ -860,6 +881,7 @@ public: private: VideoData *_video; + }; class VideoSaveLink : public VideoLink { @@ -879,6 +901,7 @@ public: VideoOpenLink(VideoData *video) : VideoLink(video) { } void onClick(Qt::MouseButton button) const; + }; class VideoCancelLink : public VideoLink { @@ -888,47 +911,37 @@ public: VideoCancelLink(VideoData *video) : VideoLink(video) { } void onClick(Qt::MouseButton button) const; + }; -struct AudioData { +class AudioData { +public: AudioData(const AudioId &id, const uint64 &access = 0, int32 date = 0, const QString &mime = QString(), int32 duration = 0, int32 dc = 0, int32 size = 0); - void forget() { - } + void automaticLoad(const HistoryItem *item); // auto load voice message + void automaticLoadSettingsChanged(); - void save(const QString &toFile); + bool loaded(bool check = false) const; + bool loading() const; + bool displayLoading() const; + void save(const QString &toFile, ActionOnLoad action = ActionOnLoadNone, const FullMsgId &actionMsgId = FullMsgId(), LoadFromCloudSetting fromCloud = LoadFromCloudOrLocal, bool autoLoading = false); + void cancel(); + float64 progress() const; + int32 loadOffset() const; + bool uploading() const; - void cancel(bool beforeDownload = false) { - mtpFileLoader *l = loader; - loader = 0; - if (l) { - l->cancel(); - l->deleteLater(); - l->rpcInvalidate(); - } - _location = FileLocation(); - if (!beforeDownload) { - openOnSave = 0; - openOnSaveMsgId = FullMsgId(); - } - } + QString already(bool check = false) const; + QByteArray data() const; + const FileLocation &location(bool check = false) const; + void setLocation(const FileLocation &loc); - void finish() { - if (loader->done()) { - _location = FileLocation(mtpToStorageType(loader->fileType()), loader->fileName()); - data = loader->bytes(); - } - loader->deleteLater(); - loader->rpcInvalidate(); - loader = 0; - } + bool saveToCache() const; - QString already(bool check = false); - const FileLocation &location(bool check = false); - void setLocation(const FileLocation &loc) { - if (loc.check()) { - _location = loc; - } + void performActionOnLoad(); + + void forget(); + void setData(const QByteArray &data) { + _data = data; } AudioId id; @@ -942,14 +955,17 @@ struct AudioData { FileStatus status; int32 uploadOffset; - int32 openOnSave; - FullMsgId openOnSaveMsgId; - mtpFileLoader *loader; - QByteArray data; int32 md5[8]; private: FileLocation _location; + QByteArray _data; + + ActionOnLoad _actionOnLoad; + FullMsgId _actionOnLoadMsgId; + mutable mtpFileLoader *_loader; + + void notifyLayoutChanged() const; }; @@ -965,7 +981,9 @@ struct AudioMsgId { } AudioData *audio; FullMsgId msgId; + }; + inline bool operator<(const AudioMsgId &a, const AudioMsgId &b) { return quintptr(a.audio) < quintptr(b.audio) || (quintptr(a.audio) == quintptr(b.audio) && a.msgId < b.msgId); } @@ -988,6 +1006,7 @@ public: private: AudioData *_audio; + }; class AudioSaveLink : public AudioLink { @@ -998,6 +1017,7 @@ public: } static void doSave(AudioData *audio, bool forceSavingAs = false); void onClick(Qt::MouseButton button) const; + }; class AudioOpenLink : public AudioLink { @@ -1007,6 +1027,7 @@ public: AudioOpenLink(AudioData *audio) : AudioLink(audio) { } void onClick(Qt::MouseButton button) const; + }; class AudioCancelLink : public AudioLink { @@ -1016,6 +1037,7 @@ public: AudioCancelLink(AudioData *audio) : AudioLink(audio) { } void onClick(Qt::MouseButton button) const; + }; enum DocumentType { @@ -1039,6 +1061,7 @@ struct StickerData : public DocumentAdditionalData { bool setInstalled() const; StorageImageLocation loc; // doc thumb location + }; struct SongData : public DocumentAdditionalData { @@ -1046,77 +1069,78 @@ struct SongData : public DocumentAdditionalData { } int32 duration; QString title, performer; + }; -struct DocumentData { +bool fileIsImage(const QString &name, const QString &mime); + +class DocumentData { +public: DocumentData(const DocumentId &id, const uint64 &access = 0, int32 date = 0, const QVector &attributes = QVector(), const QString &mime = QString(), const ImagePtr &thumb = ImagePtr(), int32 dc = 0, int32 size = 0); void setattributes(const QVector &attributes); - void forget() { - thumb->forget(); - if (sticker()) sticker()->img->forget(); - replyPreview->forget(); - } - ImagePtr makeReplyPreview() { - if (replyPreview->isNull() && !thumb->isNull()) { - if (thumb->loaded()) { - int w = thumb->width(), h = thumb->height(); - if (w <= 0) w = 1; - if (h <= 0) h = 1; - replyPreview = ImagePtr(w > h ? thumb->pix(w * st::msgReplyBarSize.height() / h, st::msgReplyBarSize.height()) : thumb->pix(st::msgReplyBarSize.height()), "PNG"); - } else { - thumb->load(); - } - } - return replyPreview; - } + void automaticLoad(const HistoryItem *item); // auto load sticker or video + void automaticLoadSettingsChanged(); - void save(const QString &toFile); + bool loaded(bool check = false) const; + bool loading() const; + bool displayLoading() const; + void save(const QString &toFile, ActionOnLoad action = ActionOnLoadNone, const FullMsgId &actionMsgId = FullMsgId(), LoadFromCloudSetting fromCloud = LoadFromCloudOrLocal, bool autoLoading = false); + void cancel(); + float64 progress() const; + int32 loadOffset() const; + bool uploading() const; - void cancel(bool beforeDownload = false) { - mtpFileLoader *l = loader; - loader = 0; - if (l) { - l->cancel(); - l->deleteLater(); - l->rpcInvalidate(); - } - _location = FileLocation(); - if (!beforeDownload) { - openOnSave = 0; - openOnSaveMsgId = FullMsgId(); - } - } + QString already(bool check = false) const; + QByteArray data() const; + const FileLocation &location(bool check = false) const; + void setLocation(const FileLocation &loc); - void finish() { - if (loader->done()) { - _location = FileLocation(mtpToStorageType(loader->fileType()), loader->fileName()); - data = loader->bytes(); - if (sticker() && !loader->imagePixmap().isNull()) { - sticker()->img = ImagePtr(data, loader->imageFormat(), loader->imagePixmap()); - } - } - loader->deleteLater(); - loader->rpcInvalidate(); - loader = 0; - } - ~DocumentData() { - delete _additional; - } + bool saveToCache() const; + + void performActionOnLoad(); + + void forget(); + ImagePtr makeReplyPreview(); - QString already(bool check = false); - const FileLocation &location(bool check = false); - void setLocation(const FileLocation &loc) { - if (loc.check()) { - _location = loc; - } - } StickerData *sticker() { return (type == StickerDocument) ? static_cast(_additional) : 0; } + void checkSticker() { + StickerData *s = sticker(); + if (!s) return; + + automaticLoad(0); + if (s->img->isNull() && loaded()) { + if (_data.isEmpty()) { + const FileLocation &loc(location(true)); + if (loc.accessEnable()) { + s->img = ImagePtr(loc.name()); + loc.accessDisable(); + } + } else { + s->img = ImagePtr(_data); + } + } + } SongData *song() { return (type == SongDocument) ? static_cast(_additional) : 0; } + bool isAnimation() const { + return (type == AnimatedDocument) || !mime.compare(qstr("image/gif"), Qt::CaseInsensitive); + } + bool isGifv() const { + return (type == AnimatedDocument) && !mime.compare(qstr("video/mp4"), Qt::CaseInsensitive); + } + int32 duration() const { + return (isAnimation() || type == VideoDocument) ? _duration : -1; + } + bool isImage() const { + return !isAnimation() && (type != VideoDocument) && (_duration > 0); + } + void recountIsImage(); + + ~DocumentData(); DocumentId id; DocumentType type; @@ -1131,18 +1155,21 @@ struct DocumentData { FileStatus status; int32 uploadOffset; - int32 openOnSave; - FullMsgId openOnSaveMsgId; - mtpFileLoader *loader; - - QByteArray data; - DocumentAdditionalData *_additional; - int32 md5[8]; private: FileLocation _location; + QByteArray _data; + DocumentAdditionalData *_additional; + int32 _duration; + + ActionOnLoad _actionOnLoad; + FullMsgId _actionOnLoadMsgId; + mutable mtpFileLoader *_loader; + + void notifyLayoutChanged() const; + }; struct SongMsgId { @@ -1157,6 +1184,7 @@ struct SongMsgId { } DocumentData *song; FullMsgId msgId; + }; inline bool operator<(const SongMsgId &a, const SongMsgId &b) { return quintptr(a.song) < quintptr(b.song) || (quintptr(a.song) == quintptr(b.song) && a.msgId < b.msgId); @@ -1180,6 +1208,7 @@ public: private: DocumentData *_document; + }; class DocumentSaveLink : public DocumentLink { @@ -1190,6 +1219,7 @@ public: } static void doSave(DocumentData *document, bool forceSavingAs = false); void onClick(Qt::MouseButton button) const; + }; class DocumentOpenLink : public DocumentLink { @@ -1198,8 +1228,20 @@ class DocumentOpenLink : public DocumentLink { public: DocumentOpenLink(DocumentData *document) : DocumentLink(document) { } + static void doOpen(DocumentData *document, ActionOnLoad action = ActionOnLoadOpen); + void onClick(Qt::MouseButton button) const; + +}; + +class GifOpenLink : public DocumentOpenLink { + TEXT_LINK_CLASS(GifOpenLink) + +public: + GifOpenLink(DocumentData *document) : DocumentOpenLink(document) { + } static void doOpen(DocumentData *document); void onClick(Qt::MouseButton button) const; + }; class DocumentCancelLink : public DocumentLink { @@ -1209,6 +1251,7 @@ public: DocumentCancelLink(DocumentData *document) : DocumentLink(document) { } void onClick(Qt::MouseButton button) const; + }; enum WebPageType { @@ -1226,7 +1269,7 @@ inline WebPageType toWebPageType(const QString &type) { struct WebPageData { WebPageData(const WebPageId &id, WebPageType type = WebPageArticle, const QString &url = QString(), const QString &displayUrl = QString(), const QString &siteName = QString(), const QString &title = QString(), const QString &description = QString(), PhotoData *photo = 0, DocumentData *doc = 0, int32 duration = 0, const QString &author = QString(), int32 pendingTill = -1); - + void forget() { if (photo) photo->forget(); } @@ -1239,8 +1282,57 @@ struct WebPageData { PhotoData *photo; DocumentData *doc; int32 pendingTill; + }; +class InlineResult { +public: + InlineResult(uint64 queryId) + : queryId(queryId) + , doc(0) + , photo(0) + , width(0) + , height(0) + , duration(0) + , noWebPage(false) + , _loader(0) { + } + uint64 queryId; + QString id, type; + DocumentData *doc; + PhotoData *photo; + QString title, description, url, thumb_url; + QString content_type, content_url; + int32 width, height, duration; + + QString message; // botContextMessageText + bool noWebPage; + EntitiesInText entities; + QString caption; // if message.isEmpty() use botContextMessageMediaAuto + + ImagePtr thumb; + + void automaticLoadGif(); + void automaticLoadSettingsChangedGif(); + void saveFile(const QString &toFile, LoadFromCloudSetting fromCloud, bool autoLoading); + void cancelFile(); + + QByteArray data() const; + bool loading() const; + bool loaded() const; + bool displayLoading() const; + void forget(); + float64 progress() const; + + ~InlineResult(); + +private: + QByteArray _data; + mutable webFileLoader *_loader; + +}; +typedef QList InlineResults; + QString saveFileName(const QString &title, const QString &filter, const QString &prefix, QString name, bool savingAs, const QDir &dir = QDir()); MsgId clientMsgId(); @@ -1259,15 +1351,13 @@ struct MessageCursor { QScrollBar *s = edit.verticalScrollBar(); scroll = (s && (s->value() != s->maximum())) ? s->value() : QFIXED_MAX; } - void applyTo(QTextEdit &edit, bool *lock = 0) { - if (lock) *lock = true; + void applyTo(QTextEdit &edit) { QTextCursor c = edit.textCursor(); c.setPosition(anchor, QTextCursor::MoveAnchor); c.setPosition(position, QTextCursor::KeepAnchor); edit.setTextCursor(c); QScrollBar *s = edit.verticalScrollBar(); if (s) s->setValue(scroll); - if (lock) *lock = false; } int position, anchor, scroll; }; diff --git a/Telegram/SourceFiles/sysbuttons.cpp b/Telegram/SourceFiles/sysbuttons.cpp index d84b6cdb7d..5aad225969 100644 --- a/Telegram/SourceFiles/sysbuttons.cpp +++ b/Telegram/SourceFiles/sysbuttons.cpp @@ -28,8 +28,12 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "application.h" #include "autoupdater.h" -SysBtn::SysBtn(QWidget *parent, const style::sysButton &st, const QString &text) : Button(parent), -_st(st), a_color(_st.color->c), _overLevel(0), _text(text) { +SysBtn::SysBtn(QWidget *parent, const style::sysButton &st, const QString &text) : Button(parent) +, _st(st) +, a_color(_st.color->c) +, _a_color(animation(this, &SysBtn::step_color)) +, _overLevel(0) +, _text(text) { int32 w = _st.size.width() + (_text.isEmpty() ? 0 : ((_st.size.width() - _st.img.pxWidth()) / 2 + st::titleTextButton.font->width(_text))); resize(w, _st.size.height()); setCursor(style::cur_default); @@ -51,11 +55,11 @@ void SysBtn::onStateChange(int oldState, ButtonStateChangeSource source) { a_color.start((_state & StateOver ? _st.overColor : _st.color)->c); if (source == ButtonByUser || source == ButtonByPress) { - anim::stop(this); + _a_color.stop(); a_color.finish(); update(); } else { - anim::start(this); + _a_color.start(); } } @@ -96,17 +100,15 @@ HitTestType SysBtn::hitTest(const QPoint &p) const { return HitTestNone; } -bool SysBtn::animStep(float64 ms) { +void SysBtn::step_color(float64 ms, bool timer) { float64 dt = ms / _st.duration; - bool res = true; if (dt >= 1) { + _a_color.stop(); a_color.finish(); - res = false; } else { a_color.update(dt, anim::linear); } - update(); - return res; + if (timer) update(); } MinimizeBtn::MinimizeBtn(QWidget *parent, Window *window) : SysBtn(parent, st::sysMin), wnd(window) { diff --git a/Telegram/SourceFiles/sysbuttons.h b/Telegram/SourceFiles/sysbuttons.h index 4b4a616a93..de7c3f93f1 100644 --- a/Telegram/SourceFiles/sysbuttons.h +++ b/Telegram/SourceFiles/sysbuttons.h @@ -26,7 +26,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org class Window; -class SysBtn : public Button, public Animated { +class SysBtn : public Button { Q_OBJECT public: @@ -41,7 +41,7 @@ public: void setOverLevel(float64 level); - bool animStep(float64 ms); + void step_color(float64 ms, bool timer); public slots: @@ -51,6 +51,8 @@ protected: style::sysButton _st; anim::cvalue a_color; + Animation _a_color; + float64 _overLevel; QString _text; diff --git a/Telegram/SourceFiles/title.cpp b/Telegram/SourceFiles/title.cpp index 364d233417..58a7c5506e 100644 --- a/Telegram/SourceFiles/title.cpp +++ b/Telegram/SourceFiles/title.cpp @@ -49,23 +49,23 @@ void TitleHider::setLevel(float64 level) { update(); } -TitleWidget::TitleWidget(Window *window) - : QWidget(window) - , wnd(window) - , hideLevel(0) - , hider(0) - , _back(this, st::titleBackButton, lang(lng_menu_back)) - , _cancel(this, lang(lng_cancel), st::titleTextButton) - , _settings(this, lang(lng_menu_settings), st::titleTextButton) - , _contacts(this, lang(lng_menu_contacts), st::titleTextButton) - , _about(this, lang(lng_menu_about), st::titleTextButton) - , _lock(this, window) - , _update(this, window, lang(lng_menu_update)) - , _minimize(this, window) - , _maximize(this, window) - , _restore(this, window) - , _close(this, window) - , lastMaximized(!(window->windowState() & Qt::WindowMaximized)) +TitleWidget::TitleWidget(Window *window) : TWidget(window) +, wnd(window) +, hideLevel(0) +, hider(0) +, _back(this, st::titleBackButton, lang(lng_menu_back)) +, _cancel(this, lang(lng_cancel), st::titleTextButton) +, _settings(this, lang(lng_menu_settings), st::titleTextButton) +, _contacts(this, lang(lng_menu_contacts), st::titleTextButton) +, _about(this, lang(lng_menu_about), st::titleTextButton) +, _lock(this, window) +, _update(this, window, lang(lng_menu_update)) +, _minimize(this, window) +, _maximize(this, window) +, _restore(this, window) +, _close(this, window) +, _a_update(animation(this, &TitleWidget::step_update)) +, lastMaximized(!(window->windowState() & Qt::WindowMaximized)) { setGeometry(0, 0, wnd->width(), st::titleHeight); setAttribute(Qt::WA_OpaquePaintEvent); @@ -110,11 +110,10 @@ void TitleWidget::paintEvent(QPaintEvent *e) { } } -bool TitleWidget::animStep(float64 ms) { +void TitleWidget::step_update(float64 ms, bool timer) { float64 phase = sin(M_PI_2 * (ms / st::updateBlinkDuration)); if (phase < 0) phase = -phase; _update.setOverLevel(phase); - return true; } void TitleWidget::setHideLevel(float64 level) { @@ -143,12 +142,12 @@ void TitleWidget::onContacts() { if (App::wnd() && App::wnd()->isHidden()) App::wnd()->showFromTray(); if (!App::self()) return; - App::wnd()->showLayer(new ContactsBox()); + Ui::showLayer(new ContactsBox()); } void TitleWidget::onAbout() { if (App::wnd() && App::wnd()->isHidden()) App::wnd()->showFromTray(); - App::wnd()->showLayer(new AboutBox()); + Ui::showLayer(new AboutBox()); } TitleWidget::~TitleWidget() { @@ -332,7 +331,7 @@ void TitleWidget::showUpdateBtn() { _restore.hide(); _maximize.hide(); _close.hide(); - anim::start(this); + _a_update.start(); } else { _update.hide(); if (cPlatform() == dbipWindows) { @@ -340,7 +339,7 @@ void TitleWidget::showUpdateBtn() { maximizedChanged(lastMaximized, true); _close.show(); } - anim::stop(this); + _a_update.stop(); } resizeEvent(0); update(); @@ -365,7 +364,7 @@ void TitleWidget::maximizedChanged(bool maximized, bool force) { } HitTestType TitleWidget::hitTest(const QPoint &p) { - if (App::wnd() && App::wnd()->layerShown()) return HitTestNone; + if (App::wnd() && Ui::isLayerShown()) return HitTestNone; int x(p.x()), y(p.y()), w(width()), h(height()); if (cWideMode() && hider && x >= App::main()->dlgsWidth()) return HitTestNone; diff --git a/Telegram/SourceFiles/title.h b/Telegram/SourceFiles/title.h index 1dc420a747..ff85f2528d 100644 --- a/Telegram/SourceFiles/title.h +++ b/Telegram/SourceFiles/title.h @@ -39,7 +39,7 @@ private: }; -class TitleWidget : public QWidget, public Animated { +class TitleWidget : public TWidget { Q_OBJECT public: @@ -61,7 +61,7 @@ public: void setHideLevel(float64 level); - bool animStep(float64 ms); + void step_update(float64 ms, bool timer); ~TitleWidget(); @@ -97,6 +97,8 @@ private: RestoreBtn _restore; CloseBtn _close; + Animation _a_update; + bool lastMaximized; QPixmap _counter; diff --git a/Telegram/SourceFiles/types.cpp b/Telegram/SourceFiles/types.cpp index e24c66977c..ca7c917b40 100644 --- a/Telegram/SourceFiles/types.cpp +++ b/Telegram/SourceFiles/types.cpp @@ -22,6 +22,10 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "application.h" +#include + +uint64 _SharedMemoryLocation[4] = { 0x00, 0x01, 0x02, 0x03 }; + #ifdef Q_OS_WIN #elif defined Q_OS_MAC #include @@ -97,7 +101,7 @@ namespace { clock_gettime(CLOCK_REALTIME, &ts); _msgIdMsStart = 1000000000 * uint64(ts.tv_sec) + uint64(ts.tv_nsec); #endif - + uint32 msgIdRand; memset_rand(&msgIdRand, sizeof(uint32)); _msgIdStart = (((uint64)((uint32)unixtime()) << 32) | (uint64)msgIdRand); @@ -185,6 +189,32 @@ namespace { delete l; } + int _ffmpegLockManager(void **mutex, AVLockOp op) { + switch (op) { + case AV_LOCK_CREATE: { + t_assert(*mutex == 0); + *mutex = reinterpret_cast(new QMutex()); + } break; + + case AV_LOCK_OBTAIN: { + t_assert(*mutex != 0); + reinterpret_cast(*mutex)->lock(); + } break; + + case AV_LOCK_RELEASE: { + t_assert(*mutex != 0); + reinterpret_cast(*mutex)->unlock(); + }; break; + + case AV_LOCK_DESTROY: { + t_assert(*mutex != 0); + delete reinterpret_cast(*mutex); + *mutex = 0; + } break; + } + return 0; + } + float64 _msFreq; float64 _msgIdCoef; int64 _msStart = 0, _msAddToMsStart = 0, _msAddToUnixtime = 0; @@ -236,7 +266,7 @@ namespace { _MsStarter _msStarter; } -InitOpenSSL::InitOpenSSL() { +void initThirdParty() { if (!RAND_status()) { // should be always inited in all modern OS char buf[16]; memcpy(buf, &_msStart, 8); @@ -260,14 +290,62 @@ InitOpenSSL::InitOpenSSL() { CRYPTO_set_dynlock_lock_callback(_sslLockFunction); CRYPTO_set_dynlock_destroy_callback(_sslDestroyFunction); + av_register_all(); + avcodec_register_all(); + + av_lockmgr_register(_ffmpegLockManager); + _sslInited = true; } -InitOpenSSL::~InitOpenSSL() { +void deinitThirdParty() { + av_lockmgr_register(0); + delete[] _sslLocks; _sslLocks = 0; } +namespace { + FILE *_crashDump = 0; + int _crashDumpNo = 0; +} + +void _signalHandler(int signum) { + const char* name = 0; + switch (signum) { + case SIGABRT: name = "SIGABRT"; break; + case SIGSEGV: name = "SIGSEGV"; break; + case SIGILL: name = "SIGILL"; break; + case SIGFPE: name = "SIGFPE"; break; +#ifndef Q_OS_WIN + case SIGBUS: name = "SIGBUS"; break; + case SIGSYS: name = "SIGSYS"; break; +#endif + } + LOG(("Caught signal %1").arg(name)); + if (name) + fprintf(stdout, "Caught signal %d (%s)\n", signum, name); + else + fprintf(stdout, "Caught signal %d\n", signum); + + + //printStackTrace(); +} + +void installSignalHandlers() { + _crashDump = fopen((cWorkingDir() + qsl("tdata/working")).toUtf8().constData(), "wb"); + if (_crashDump) _crashDumpNo = fileno(_crashDump); + + signal(SIGABRT, _signalHandler); + signal(SIGSEGV, _signalHandler); + signal(SIGILL, _signalHandler); + signal(SIGFPE, _signalHandler); +#ifndef Q_OS_WIN + signal(SIGBUS, _signalHandler); + signal(SIGSYS, _signalHandler); +#endif +} + bool checkms() { int64 unixms = (myunixtime() - _timeStart) * 1000LL + _msAddToUnixtime; int64 ms = int64(getms(true)); @@ -638,10 +716,7 @@ char *hashMd5Hex(const int32 *hashmd5, void *dest) { } void memset_rand(void *data, uint32 len) { - if (!_sslInited) { - LOG(("Critical Error: memset_rand() called before OpenSSL init!")); - exit(-1); - } + t_assert(_sslInited); RAND_bytes((uchar*)data, len); } diff --git a/Telegram/SourceFiles/types.h b/Telegram/SourceFiles/types.h index dae1346f91..3406ef1f08 100644 --- a/Telegram/SourceFiles/types.h +++ b/Telegram/SourceFiles/types.h @@ -20,9 +20,32 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org */ #pragma once +template +void deleteAndMark(T *&link) { + delete link; + link = reinterpret_cast(0x00000BAD); +} + +template +T *exchange(T *&ptr) { + T *result = 0; + qSwap(result, ptr); + return result; +} + struct NullType { }; +template +class OrderedSet : public QMap { +public: + + void insert(const T &v) { + QMap::insert(v, NullType()); + } + +}; + //typedef unsigned char uchar; // Qt has uchar typedef qint16 int16; typedef quint16 uint16; @@ -33,6 +56,13 @@ typedef quint64 uint64; static const int32 ScrollMax = INT_MAX; +extern uint64 _SharedMemoryLocation[]; +template +T *SharedMemoryLocation() { + static_assert(N < 4, "Only 4 shared memory locations!"); + return reinterpret_cast(_SharedMemoryLocation + N); +} + #ifdef Q_OS_WIN typedef float float32; typedef double float64; @@ -103,18 +133,17 @@ inline void mylocaltime(struct tm * _Tm, const time_t * _Time) { #endif } -class InitOpenSSL { -public: - InitOpenSSL(); - ~InitOpenSSL(); -}; +void installSignalHandlers(); + +void initThirdParty(); // called by Global::Initializer +void deinitThirdParty(); bool checkms(); // returns true if time has changed uint64 getms(bool checked = false); class SingleTimer : public QTimer { // single shot timer with check Q_OBJECT - + public: SingleTimer(); @@ -206,6 +235,19 @@ private: #define qsl(s) QStringLiteral(s) #define qstr(s) QLatin1String(s, sizeof(s) - 1) +inline QString fromUtf8Safe(const char *str, int32 size = -1) { + if (!str || !size) return QString(); + if (size < 0) size = int32(strlen(str)); + QString result(QString::fromUtf8(str, size)); + QByteArray back = result.toUtf8(); + if (back.size() != size || memcmp(back.constData(), str, size)) return QString::fromLocal8Bit(str, size); + return result; +} + +inline QString fromUtf8Safe(const QByteArray &str) { + return fromUtf8Safe(str.constData(), str.size()); +} + static const QRegularExpression::PatternOptions reMultiline(QRegularExpression::DotMatchesEverythingOption | QRegularExpression::MultilineOption); template @@ -283,6 +325,10 @@ enum DataBlockId { dbiIncludeMuted = 0x31, dbiMaxMegaGroupCount = 0x32, dbiDownloadPath = 0x33, + dbiAutoDownload = 0x34, + dbiSavedGifsLimit = 0x35, + dbiShowingSavedGifs = 0x36, + dbiAutoPlay = 0x37, dbiEncryptedWithSalt = 333, dbiEncrypted = 444, @@ -424,6 +470,9 @@ 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; +} inline int32 floorclamp(int32 value, int32 step, int32 lowest, int32 highest) { return qMin(qMax(value / step, lowest), highest); } @@ -443,3 +492,235 @@ enum ForwardWhatMessages { ForwardPressedMessage, ForwardPressedLinkMessage }; + +enum ShowLayerOption { + CloseOtherLayers = 0x00, + KeepOtherLayers = 0x01, + ShowAfterOtherLayers = 0x03, + + AnimatedShowLayer = 0x00, + ForceFastShowLayer = 0x04, +}; +typedef QFlags ShowLayerOptions; + +static int32 FullArcLength = 360 * 16; +static int32 QuarterArcLength = (FullArcLength / 4); +static int32 MinArcLength = (FullArcLength / 360); +static int32 AlmostFullArcLength = (FullArcLength - MinArcLength); + +template +inline void destroyImplementation(I *&ptr) { + if (ptr) { + ptr->destroy(); + ptr = 0; + } + deleteAndMark(ptr); +} + +template +class FunctionImplementation { +public: + virtual R call() = 0; + virtual void destroy() { delete this; } + virtual ~FunctionImplementation() {} +}; +template +class NullFunctionImplementation : public FunctionImplementation { +public: + virtual R call() { return R(); } + virtual void destroy() {} + static NullFunctionImplementation SharedInstance; +}; +template +NullFunctionImplementation NullFunctionImplementation::SharedInstance; +template +class FunctionCreator { +public: + FunctionCreator(FunctionImplementation *ptr) : _ptr(ptr) {} + FunctionCreator(const FunctionCreator &other) : _ptr(other.create()) {} + FunctionImplementation *create() const { return exchange(_ptr); } + ~FunctionCreator() { destroyImplementation(_ptr); } +private: + FunctionCreator &operator=(const FunctionCreator &other); + mutable FunctionImplementation *_ptr; +}; +template +class Function { +public: + typedef FunctionCreator Creator; + static Creator Null() { return Creator(&NullFunctionImplementation::SharedInstance); } + Function(const Creator &creator) : _implementation(creator.create()) {} + R call() { return _implementation->call(); } + ~Function() { destroyImplementation(_implementation); } +private: + Function(const Function &other); + Function &operator=(const Function &other); + FunctionImplementation *_implementation; +}; + +template +class WrappedFunction : public FunctionImplementation { +public: + typedef R(*Method)(); + WrappedFunction(Method method) : _method(method) {} + virtual R call() { return (*_method)(); } +private: + Method _method; +}; +template +inline FunctionCreator func(R(*method)()) { + return FunctionCreator(new WrappedFunction(method)); +} +template +class ObjectFunction : public FunctionImplementation { +public: + typedef R(I::*Method)(); + ObjectFunction(O *obj, Method method) : _obj(obj), _method(method) {} + virtual R call() { return (_obj->*_method)(); } +private: + O *_obj; + Method _method; +}; +template +inline FunctionCreator func(O *obj, R(I::*method)()) { + return FunctionCreator(new ObjectFunction(obj, method)); +} + +template +class Function1Implementation { +public: + virtual R call(A1 a1) = 0; + virtual void destroy() { delete this; } + virtual ~Function1Implementation() {} +}; +template +class NullFunction1Implementation : public Function1Implementation { +public: + virtual R call(A1 a1) { return R(); } + virtual void destroy() {} + static NullFunction1Implementation SharedInstance; +}; +template +NullFunction1Implementation NullFunction1Implementation::SharedInstance; +template +class Function1Creator { +public: + Function1Creator(Function1Implementation *ptr) : _ptr(ptr) {} + Function1Creator(const Function1Creator &other) : _ptr(other.create()) {} + Function1Implementation *create() const { return exchange(_ptr); } + ~Function1Creator() { destroyImplementation(_ptr); } +private: + Function1Creator &operator=(const Function1Creator &other); + mutable Function1Implementation *_ptr; +}; +template +class Function1 { +public: + typedef Function1Creator Creator; + static Creator Null() { return Creator(&NullFunction1Implementation::SharedInstance); } + Function1(const Creator &creator) : _implementation(creator.create()) {} + R call(A1 a1) { return _implementation->call(a1); } + ~Function1() { _implementation->destroy(); } +private: + Function1(const Function1 &other); + Function1 &operator=(const Function1 &other); + Function1Implementation *_implementation; +}; + +template +class WrappedFunction1 : public Function1Implementation { +public: + typedef R(*Method)(A1); + WrappedFunction1(Method method) : _method(method) {} + virtual R call(A1 a1) { return (*_method)(a1); } +private: + Method _method; +}; +template +inline Function1Creator func(R(*method)(A1)) { + return Function1Creator(new WrappedFunction1(method)); +} +template +class ObjectFunction1 : public Function1Implementation { +public: + typedef R(I::*Method)(A1); + ObjectFunction1(O *obj, Method method) : _obj(obj), _method(method) {} + virtual R call(A1 a1) { return (_obj->*_method)(a1); } +private: + O *_obj; + Method _method; +}; +template +Function1Creator func(O *obj, R(I::*method)(A1)) { + return Function1Creator(new ObjectFunction1(obj, method)); +} + +template +class Function2Implementation { +public: + virtual R call(A1 a1, A2 a2) = 0; + virtual void destroy() { delete this; } + virtual ~Function2Implementation() {} +}; +template +class NullFunction2Implementation : public Function2Implementation { +public: + virtual R call(A1 a1, A2 a2) { return R(); } + virtual void destroy() {} + static NullFunction2Implementation SharedInstance; +}; +template +NullFunction2Implementation NullFunction2Implementation::SharedInstance; +template +class Function2Creator { +public: + Function2Creator(Function2Implementation *ptr) : _ptr(ptr) {} + Function2Creator(const Function2Creator &other) : _ptr(other.create()) {} + Function2Implementation *create() const { return exchange(_ptr); } + ~Function2Creator() { destroyImplementation(_ptr); } +private: + Function2Creator &operator=(const Function2Creator &other); + mutable Function2Implementation *_ptr; +}; +template +class Function2 { +public: + typedef Function2Creator Creator; + static Creator Null() { return Creator(&NullFunction2Implementation::SharedInstance); } + Function2(const Creator &creator) : _implementation(creator.create()) {} + R call(A1 a1, A2 a2) { return _implementation->call(a1, a2); } + ~Function2() { destroyImplementation(_implementation); } +private: + Function2(const Function2 &other); + Function2 &operator=(const Function2 &other); + Function2Implementation *_implementation; +}; + +template +class WrappedFunction2 : public Function2Implementation { +public: + typedef R(*Method)(A1, A2); + WrappedFunction2(Method method) : _method(method) {} + virtual R call(A1 a1, A2 a2) { return (*_method)(a1, a2); } +private: + Method _method; +}; +template +Function2Creator func(R(*method)(A1, A2)) { + return Function2Creator(new WrappedFunction2(method)); +} + +template +class ObjectFunction2 : public Function2Implementation { +public: + typedef R(I::*Method)(A1, A2); + ObjectFunction2(O *obj, Method method) : _obj(obj), _method(method) {} + virtual R call(A1 a1, A2 a2) { return (_obj->*_method)(a1, a2); } +private: + O *_obj; + Method _method; +}; +template +Function2Creator func(O *obj, R(I::*method)(A1, A2)) { + return Function2Creator(new ObjectFunction2(obj, method)); +} diff --git a/Telegram/SourceFiles/window.cpp b/Telegram/SourceFiles/window.cpp index ada34c9e4e..9a35a4ef68 100644 --- a/Telegram/SourceFiles/window.cpp +++ b/Telegram/SourceFiles/window.cpp @@ -82,9 +82,10 @@ NotifyWindow::NotifyWindow(HistoryItem *msg, int32 x, int32 y, int32 fwdCount) : , posDuration(st::notifyFastAnim) , hiding(false) , _index(0) -, aOpacity(0) -, aOpacityFunc(st::notifyFastAnimFunc) -, aY(y + st::notifyHeight + st::notifyDeltaY) { +, a_opacity(0) +, a_func(anim::linear) +, a_y(y + st::notifyHeight + st::notifyDeltaY) +, _a_appearance(animation(this, &NotifyWindow::step_appearance)) { updateNotifyDisplay(); @@ -99,19 +100,19 @@ NotifyWindow::NotifyWindow(HistoryItem *msg, int32 x, int32 y, int32 fwdCount) : close.move(st::notifyWidth - st::notifyClose.width - st::notifyClosePos.x(), st::notifyClosePos.y()); close.show(); - aY.start(y); - setGeometry(x, aY.current(), st::notifyWidth, st::notifyHeight); + a_y.start(y); + setGeometry(x, a_y.current(), st::notifyWidth, st::notifyHeight); - aOpacity.start(1); + a_opacity.start(1); setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint); setAttribute(Qt::WA_MacAlwaysShowToolWindow); show(); - setWindowOpacity(aOpacity.current()); + setWindowOpacity(a_opacity.current()); alphaDuration = posDuration = st::notifyFastAnim; - anim::start(this); + _a_appearance.start(); checkLastInput(); } @@ -140,11 +141,11 @@ void NotifyWindow::moveTo(int32 x, int32 y, int32 index) { if (index >= 0) { _index = index; } - move(x, aY.current()); - aY.start(y); - aOpacity.restart(); + move(x, a_y.current()); + a_y.start(y); + a_opacity.restart(); posDuration = st::notifyFastAnim; - anim::start(this); + _a_appearance.start(); } void NotifyWindow::updateNotifyDisplay() { @@ -206,7 +207,7 @@ void NotifyWindow::updateNotifyDisplay() { item->drawInDialog(p, r, active, textCachedFor, itemTextCache); } else { p.setFont(st::dlgHistFont->f); - if (item->displayFromName() && !item->fromChannel()) { + if (item->hasFromName() && !item->fromChannel()) { itemTextCache.setText(st::dlgHistFont, item->from()->name); p.setPen(st::dlgSystemColor->p); itemTextCache.drawElided(p, r.left(), r.top(), r.width(), st::dlgHistFont->height); @@ -262,7 +263,7 @@ void NotifyWindow::unlinkHistoryAndNotify() { void NotifyWindow::unlinkHistory(History *hist) { if (!hist || hist == history) { - animHide(st::notifyFastAnim, st::notifyFastAnimFunc); + animHide(st::notifyFastAnim, anim::linear); history = 0; item = 0; } @@ -295,7 +296,7 @@ void NotifyWindow::mousePressEvent(QMouseEvent *e) { App::wnd()->notifyClear(); } else { App::wnd()->hideSettings(); - App::main()->showPeerHistory(peer, (!history->peer->isUser() && item && item->mentionsMe() && item->id > 0) ? item->id : ShowAtUnreadMsgId); + Ui::showPeerHistory(peer, (!history->peer->isUser() && item && item->mentionsMe() && item->id > 0) ? item->id : ShowAtUnreadMsgId); } e->ignore(); } @@ -309,22 +310,22 @@ void NotifyWindow::paintEvent(QPaintEvent *e) { void NotifyWindow::animHide(float64 duration, anim::transition func) { if (!history) return; alphaDuration = duration; - aOpacityFunc = func; - aOpacity.start(0); - aY.restart(); + a_func = func; + a_opacity.start(0); + a_y.restart(); hiding = true; - anim::start(this); + _a_appearance.start(); } void NotifyWindow::stopHiding() { if (!history) return; alphaDuration = st::notifyFastAnim; - aOpacityFunc = st::notifyFastAnimFunc; - aOpacity.start(1); - aY.restart(); + a_func = anim::linear; + a_opacity.start(1); + a_y.restart(); hiding = false; hideTimer.stop(); - anim::start(this); + _a_appearance.start(); } void NotifyWindow::hideByTimer() { @@ -332,25 +333,27 @@ void NotifyWindow::hideByTimer() { animHide(st::notifySlowHide, st::notifySlowHideFunc); } -bool NotifyWindow::animStep(float64 ms) { +void NotifyWindow::step_appearance(float64 ms, bool timer) { float64 dtAlpha = ms / alphaDuration, dtPos = ms / posDuration; if (dtAlpha >= 1) { - aOpacity.finish(); + a_opacity.finish(); if (hiding) { + _a_appearance.stop(); deleteLater(); + } else if (dtPos >= 1) { + _a_appearance.stop(); } } else { - aOpacity.update(dtAlpha, aOpacityFunc); + a_opacity.update(dtAlpha, a_func); } - setWindowOpacity(aOpacity.current()); + setWindowOpacity(a_opacity.current()); if (dtPos >= 1) { - aY.finish(); + a_y.finish(); } else { - aY.update(dtPos, anim::linear); + a_y.update(dtPos, anim::linear); } - move(x(), aY.current()); + move(x(), a_y.current()); update(); - return (dtAlpha < 1 || (!hiding && dtPos < 1)); } NotifyWindow::~NotifyWindow() { @@ -450,9 +453,9 @@ void Window::firstShow() { trayIconMenu = new QMenu(this); trayIconMenu->setFont(QFont("Tahoma")); #endif - QString notificationItem = lang(cDesktopNotify() + QString notificationItem = lang(cDesktopNotify() ? lng_disable_notifications_from_tray : lng_enable_notifications_from_tray); - + if (cPlatform() == dbipWindows || cPlatform() == dbipMac || cPlatform() == dbipMacOld) { trayIconMenu->addAction(lang(lng_minimize_to_tray), this, SLOT(minimizeToTray()))->setEnabled(true); trayIconMenu->addAction(notificationItem, this, SLOT(toggleDisplayNotifyFromTray()))->setEnabled(true); @@ -474,14 +477,14 @@ QWidget *Window::filedialogParent() { } void Window::clearWidgets() { - hideLayer(true); + Ui::hideLayer(true); if (_passcode) { _passcode->hide(); _passcode->deleteLater(); _passcode = 0; } if (settings) { - settings->animStop_show(); + settings->stop_show(); settings->hide(); settings->deleteLater(); settings->rpcInvalidate(); @@ -495,7 +498,7 @@ void Window::clearWidgets() { main = 0; } if (intro) { - intro->animStop_show(); + intro->stop_show(); intro->hide(); intro->deleteLater(); intro->rpcInvalidate(); @@ -524,7 +527,7 @@ void Window::clearPasscode() { QPixmap bg = grabInner(); - _passcode->animStop_show(); + _passcode->stop_show(); _passcode->hide(); _passcode->deleteLater(); _passcode = 0; @@ -544,7 +547,7 @@ void Window::setupPasscode(bool anim) { QPixmap bg = grabInner(); if (_passcode) { - _passcode->animStop_show(); + _passcode->stop_show(); _passcode->hide(); _passcode->deleteLater(); } @@ -638,14 +641,12 @@ void Window::sendServiceHistoryRequest() { UserData *user = App::userLoaded(ServiceUserId); if (!user) { int32 userFlags = MTPDuser::flag_first_name | MTPDuser::flag_phone | MTPDuser::flag_status | MTPDuser::flag_verified; - user = App::feedUsers(MTP_vector(1, MTP_user(MTP_int(userFlags), MTP_int(ServiceUserId), MTPlong(), MTP_string("Telegram"), MTPstring(), MTPstring(), MTP_string("42777"), MTP_userProfilePhotoEmpty(), MTP_userStatusRecently(), MTPint()))); + user = App::feedUsers(MTP_vector(1, MTP_user(MTP_int(userFlags), MTP_int(ServiceUserId), MTPlong(), MTP_string("Telegram"), MTPstring(), MTPstring(), MTP_string("42777"), MTP_userProfilePhotoEmpty(), MTP_userStatusRecently(), MTPint(), MTPstring(), MTPstring()))); } _serviceHistoryRequest = MTP::send(MTPmessages_GetHistory(user->input, MTP_int(0), MTP_int(0), MTP_int(1), MTP_int(0), MTP_int(0)), main->rpcDone(&MainWidget::serviceHistoryDone), main->rpcFail(&MainWidget::serviceHistoryFail)); } void Window::setupMain(bool anim, const MTPUser *self) { - Local::readStickers(); - QPixmap bg = anim ? grabInner() : QPixmap(); clearWidgets(); main = new MainWidget(this); @@ -679,14 +680,14 @@ void Window::showSettings() { if (isHidden()) showFromTray(); - App::wnd()->hideLayer(); + Ui::hideLayer(); if (settings) { return hideSettings(); } QPixmap bg = grabInner(); if (intro) { - intro->animStop_show(); + intro->stop_show(); intro->hide(); } else if (main) { main->animStop_show(); @@ -703,7 +704,7 @@ void Window::hideSettings(bool fast) { if (!settings || _passcode) return; if (fast) { - settings->animStop_show(); + settings->stop_show(); settings->hide(); settings->deleteLater(); settings->rpcInvalidate(); @@ -716,7 +717,7 @@ void Window::hideSettings(bool fast) { } else { QPixmap bg = grabInner(); - settings->animStop_show(); + settings->stop_show(); settings->hide(); settings->deleteLater(); settings->rpcInvalidate(); @@ -774,49 +775,69 @@ void Window::showPhoto(const PhotoLink *lnk, HistoryItem *item) { } void Window::showPhoto(PhotoData *photo, HistoryItem *item) { - hideLayer(true); + if (_mediaView->isHidden()) Ui::hideLayer(true); _mediaView->showPhoto(photo, item); _mediaView->activateWindow(); _mediaView->setFocus(); } void Window::showPhoto(PhotoData *photo, PeerData *peer) { - hideLayer(true); + if (_mediaView->isHidden()) Ui::hideLayer(true); _mediaView->showPhoto(photo, peer); _mediaView->activateWindow(); _mediaView->setFocus(); } void Window::showDocument(DocumentData *doc, HistoryItem *item) { - hideLayer(true); + if (_mediaView->isHidden()) Ui::hideLayer(true); _mediaView->showDocument(doc, item); _mediaView->activateWindow(); _mediaView->setFocus(); } -void Window::showLayer(LayeredWidget *w, bool forceFast) { - bool fast = forceFast || layerShown(); - hideLayer(true); - layerBg = new BackgroundWidget(this, w); - if (fast) { - layerBg->showFast(); +void Window::ui_showLayer(LayeredWidget *box, ShowLayerOptions options) { + if (box) { + bool fast = (options.testFlag(ForceFastShowLayer)) || Ui::isLayerShown(); + if (layerBg) { + if (options.testFlag(KeepOtherLayers)) { + if (options.testFlag(ShowAfterOtherLayers)) { + layerBg->showLayerLast(box); + return; + } else { + layerBg->replaceInner(box); + return; + } + } else { + layerBg->onClose(); + layerBg->hide(); + layerBg->deleteLater(); + layerBg = 0; + } + } + + layerBg = new BackgroundWidget(this, box); + if (fast) { + layerBg->showFast(); + } + } else { + if (layerBg) { + layerBg->onClose(); + if (options.testFlag(ForceFastShowLayer)) { + layerBg->hide(); + layerBg->deleteLater(); + layerBg = 0; + } + } + hideMediaview(); } } -void Window::replaceLayer(LayeredWidget *w) { - if (layerBg) { - layerBg->replaceInner(w); - } else { - layerBg = new BackgroundWidget(this, w); - } +bool Window::ui_isLayerShown() { + return !!layerBg; } -void Window::showLayerLast(LayeredWidget *w) { - if (layerBg) { - layerBg->showLayerLast(w); - } else { - layerBg = new BackgroundWidget(this, w); - } +bool Window::ui_isMediaViewShown() { + return _mediaView && !_mediaView->isHidden(); } void Window::showConnecting(const QString &text, const QString &reconnect) { @@ -843,29 +864,6 @@ void Window::hideConnecting() { if (settings) settings->update(); } -void Window::hideLayer(bool fast) { - if (layerBg) { - layerBg->onClose(); - if (fast) { - layerBg->hide(); - layerBg->deleteLater(); - layerBg = 0; - } - } - hideMediaview(); -} - -bool Window::hideInnerLayer() { - if (layerBg) { - return layerBg->onInnerClose(); - } - return true; -} - -bool Window::layerShown() { - return !!layerBg; -} - bool Window::historyIsActive() const { return isActive(false) && main && main->historyIsActive() && (!settings || !settings->isVisible()); } @@ -942,7 +940,7 @@ void Window::paintEvent(QPaintEvent *e) { HitTestType Window::hitTest(const QPoint &p) const { int x(p.x()), y(p.y()), w(width()), h(height()); - + const int32 raw = psResizeRowWidth(); if (!windowState().testFlag(Qt::WindowMaximized)) { if (y < raw) { @@ -1021,7 +1019,7 @@ void Window::mouseMoveEvent(QMouseEvent *e) { if (dragging) { if (windowState().testFlag(Qt::WindowMaximized)) { setWindowState(windowState() & ~Qt::WindowMaximized); - + dragStart = e->globalPos() - frameGeometry().topLeft(); } else { move(e->globalPos() - dragStart); @@ -1090,13 +1088,13 @@ void Window::onShowAddContact() { void Window::onShowNewGroup() { if (isHidden()) showFromTray(); - if (main) replaceLayer(new GroupInfoBox(CreatingGroupGroup, false)); + if (main) Ui::showLayer(new GroupInfoBox(CreatingGroupGroup, false), KeepOtherLayers); } void Window::onShowNewChannel() { if (isHidden()) showFromTray(); - if (main) replaceLayer(new GroupInfoBox(CreatingGroupChannel, false)); + if (main) Ui::showLayer(new GroupInfoBox(CreatingGroupChannel, false), KeepOtherLayers); } void Window::onLogout() { @@ -1104,7 +1102,7 @@ void Window::onLogout() { ConfirmBox *box = new ConfirmBox(lang(lng_sure_logout), lang(lng_settings_logout), st::attentionBoxButton); connect(box, SIGNAL(confirmed()), this, SLOT(onLogoutSure())); - App::wnd()->showLayer(box); + Ui::showLayer(box); } void Window::onLogoutSure() { @@ -1198,7 +1196,7 @@ void Window::toggleTray(QSystemTrayIcon::ActivationReason reason) { void Window::toggleDisplayNotifyFromTray() { if (App::passcoded()) { if (!isActive()) showFromTray(); - showLayer(new InformBox(lang(lng_passcode_need_unblock))); + Ui::showLayer(new InformBox(lang(lng_passcode_need_unblock))); return; } cSetDesktopNotify(!cDesktopNotify()); @@ -1262,7 +1260,7 @@ Window::TempDirState Window::localStorageState() { if (_clearManager && _clearManager->hasTask(Local::ClearManagerStorage)) { return TempDirRemoving; } - return (Local::hasImages() || Local::hasStickers() || Local::hasAudios()) ? TempDirExists : TempDirEmpty; + return (Local::hasImages() || Local::hasStickers() || Local::hasWebFiles() || Local::hasAudios()) ? TempDirExists : TempDirEmpty; } void Window::tempDirDelete(int task) { @@ -1781,9 +1779,7 @@ void Window::sendPaths() { if (settings) { hideSettings(); } else { - if (layerShown()) { - hideLayer(); - } + Ui::hideLayer(); if (main) { main->activate(); } diff --git a/Telegram/SourceFiles/window.h b/Telegram/SourceFiles/window.h index ba8b818252..9d514387b0 100644 --- a/Telegram/SourceFiles/window.h +++ b/Telegram/SourceFiles/window.h @@ -59,7 +59,7 @@ private: }; -class NotifyWindow : public QWidget, public Animated { +class NotifyWindow : public TWidget { Q_OBJECT public: @@ -71,7 +71,7 @@ public: void mousePressEvent(QMouseEvent *e); void paintEvent(QPaintEvent *e); - bool animStep(float64 ms); + void step_appearance(float64 ms, bool timer); void animHide(float64 duration, anim::transition func); void startHiding(); void stopHiding(); @@ -111,9 +111,11 @@ private: QTimer hideTimer, inputTimer; bool hiding; int32 _index; - anim::fvalue aOpacity; - anim::transition aOpacityFunc; - anim::ivalue aY; + anim::fvalue a_opacity; + anim::transition a_func; + anim::ivalue a_y; + Animation _a_appearance; + ImagePtr peerPhoto; }; @@ -182,15 +184,6 @@ public: void showPhoto(PhotoData *photo, PeerData *item); void showDocument(DocumentData *doc, HistoryItem *item); - void showLayer(LayeredWidget *w, bool forceFast = false); - void replaceLayer(LayeredWidget *w); - void showLayerLast(LayeredWidget *w); - - void hideLayer(bool fast = false); - bool hideInnerLayer(); - - bool layerShown(); - bool historyIsActive() const; void activate(); @@ -244,6 +237,10 @@ public: return contentOverlapped(QRect(w->mapToGlobal(r.boundingRect().topLeft()), r.boundingRect().size())); } + void ui_showLayer(LayeredWidget *box, ShowLayerOptions options); + bool ui_isLayerShown(); + bool ui_isMediaViewShown(); + public slots: void updateIsActive(int timeout = 0); diff --git a/Telegram/Telegram.plist b/Telegram/Telegram.plist index 5da30bd6da..77a056a54b 100644 --- a/Telegram/Telegram.plist +++ b/Telegram/Telegram.plist @@ -11,7 +11,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.9.15 + 0.9.19 CFBundleSignature ???? CFBundleURLTypes diff --git a/Telegram/Telegram.pro b/Telegram/Telegram.pro index 48077b5c44..6712fba13b 100644 --- a/Telegram/Telegram.pro +++ b/Telegram/Telegram.pro @@ -96,6 +96,7 @@ SOURCES += \ ./SourceFiles/lang.cpp \ ./SourceFiles/langloaderplain.cpp \ ./SourceFiles/layerwidget.cpp \ + ./SourceFiles/layout.cpp \ ./SourceFiles/mediaview.cpp \ ./SourceFiles/overviewwidget.cpp \ ./SourceFiles/passcodewidget.cpp \ @@ -138,7 +139,6 @@ SOURCES += \ ./SourceFiles/gui/style_core.cpp \ ./SourceFiles/gui/text.cpp \ ./SourceFiles/gui/twidget.cpp \ - ./SourceFiles/gui/switcher.cpp \ ./GeneratedFiles/lang_auto.cpp \ ./GeneratedFiles/style_auto.cpp \ ./GeneratedFiles/numbers.cpp \ @@ -184,6 +184,7 @@ HEADERS += \ ./SourceFiles/lang.h \ ./SourceFiles/langloaderplain.h \ ./SourceFiles/layerwidget.h \ + ./SourceFiles/layout.h \ ./SourceFiles/mediaview.h \ ./SourceFiles/numbers.h \ ./SourceFiles/overviewwidget.h \ @@ -231,7 +232,6 @@ HEADERS += \ ./SourceFiles/gui/style_core.h \ ./SourceFiles/gui/text.h \ ./SourceFiles/gui/twidget.h \ - ./SourceFiles/gui/switcher.h \ ./GeneratedFiles/lang_auto.h \ ./GeneratedFiles/style_auto.h \ ./GeneratedFiles/style_classes.h \ @@ -308,7 +308,7 @@ INCLUDEPATH += "/usr/include/atk-1.0" INCLUDEPATH += "/usr/include/dee-1.0" INCLUDEPATH += "/usr/include/libdbusmenu-glib-0.4" -LIBS += -lcrypto -lssl -lz -ldl -llzma -lexif -lopenal -lavformat -lavcodec -lswresample -lavutil -lopus +LIBS += -lcrypto -lssl -lz -ldl -llzma -lexif -lopenal -lavformat -lavcodec -lswresample -lswscale -lavutil -lopus -lva LIBS += ./../../../Libraries/QtStatic/qtbase/plugins/platforminputcontexts/libcomposeplatforminputcontextplugin.a \ ./../../../Libraries/QtStatic/qtbase/plugins/platforminputcontexts/libibusplatforminputcontextplugin.a \ ./../../../Libraries/QtStatic/qtbase/plugins/platforminputcontexts/libfcitxplatforminputcontextplugin.a diff --git a/Telegram/Telegram.rc b/Telegram/Telegram.rc index da9e0cb32f..e22500f40a 100644 Binary files a/Telegram/Telegram.rc and b/Telegram/Telegram.rc differ diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index 51fd57c4f4..598fe1ae4f 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -83,7 +83,7 @@ Windows $(OutDir)$(ProjectName).exe .\..\..\Libraries\lzma\C\Util\LzmaLib\Debug;.\..\..\Libraries\libexif-0.6.20\win32\Debug;.\..\..\Libraries\ffmpeg;.\..\..\Libraries\opus\win32\VS2010\Win32\Debug;.\..\..\Libraries\openal-soft\build\Debug;.\..\..\Libraries\zlib-1.2.8\contrib\vstudio\vc11\x86\ZlibStatDebug;.\..\..\Libraries\openssl_debug\Debug\lib;$(QTDIR)\lib;$(QTDIR)\plugins;%(AdditionalLibraryDirectories) - kernel32.lib;user32.lib;shell32.lib;uuid.lib;ole32.lib;advapi32.lib;ws2_32.lib;gdi32.lib;comdlg32.lib;oleaut32.lib;Shlwapi.lib;Gdiplus.lib;imm32.lib;winmm.lib;qtmaind.lib;glu32.lib;opengl32.lib;Strmiids.lib;Qt5Cored.lib;Qt5Guid.lib;qtharfbuzzngd.lib;qtpcred.lib;qtfreetyped.lib;Qt5Widgetsd.lib;Qt5Networkd.lib;Qt5PlatformSupportd.lib;platforms\qwindowsd.lib;imageformats\qwebpd.lib;libeay32.lib;ssleay32.lib;Crypt32.lib;zlibstat.lib;LzmaLib.lib;lib_exif.lib;UxTheme.lib;DbgHelp.lib;OpenAL32.lib;common.lib;libavformat\libavformat.a;libavcodec\libavcodec.a;libavutil\libavutil.a;libswresample\libswresample.a;opus.lib;celt.lib;silk_common.lib;silk_float.lib;%(AdditionalDependencies) + kernel32.lib;user32.lib;shell32.lib;uuid.lib;ole32.lib;advapi32.lib;ws2_32.lib;gdi32.lib;comdlg32.lib;oleaut32.lib;Shlwapi.lib;Gdiplus.lib;imm32.lib;winmm.lib;qtmaind.lib;glu32.lib;opengl32.lib;Strmiids.lib;Qt5Cored.lib;Qt5Guid.lib;qtharfbuzzngd.lib;qtpcred.lib;qtfreetyped.lib;Qt5Widgetsd.lib;Qt5Networkd.lib;Qt5PlatformSupportd.lib;platforms\qwindowsd.lib;imageformats\qwebpd.lib;libeay32.lib;ssleay32.lib;Crypt32.lib;zlibstat.lib;LzmaLib.lib;lib_exif.lib;UxTheme.lib;DbgHelp.lib;OpenAL32.lib;common.lib;libavformat\libavformat.a;libavcodec\libavcodec.a;libavutil\libavutil.a;libswresample\libswresample.a;libswscale\libswscale.a;opus.lib;celt.lib;silk_common.lib;silk_float.lib;%(AdditionalDependencies) true LIBCMT @@ -112,7 +112,7 @@ Windows $(OutDir)$(ProjectName).exe .\..\..\Libraries\lzma\C\Util\LzmaLib\Release;.\..\..\Libraries\libexif-0.6.20\win32\Release;.\..\..\Libraries\ffmpeg;.\..\..\Libraries\opus\win32\VS2010\Win32\Release;.\..\..\Libraries\openal-soft\build\Release;.\..\..\Libraries\zlib-1.2.8\contrib\vstudio\vc11\x86\ZlibStatRelease;.\..\..\Libraries\openssl\Release\lib;$(QTDIR)\lib;$(QTDIR)\plugins;%(AdditionalLibraryDirectories) - kernel32.lib;user32.lib;shell32.lib;uuid.lib;ole32.lib;advapi32.lib;ws2_32.lib;gdi32.lib;comdlg32.lib;oleaut32.lib;Shlwapi.lib;Gdiplus.lib;imm32.lib;winmm.lib;qtmain.lib;glu32.lib;opengl32.lib;Strmiids.lib;Qt5Core.lib;Qt5Gui.lib;qtharfbuzzng.lib;qtpcre.lib;qtfreetype.lib;Qt5Widgets.lib;Qt5Network.lib;Qt5PlatformSupport.lib;platforms\qwindows.lib;imageformats\qwebp.lib;libeay32.lib;ssleay32.lib;Crypt32.lib;zlibstat.lib;lib_exif.lib;UxTheme.lib;DbgHelp.lib;LzmaLib.lib;OpenAL32.lib;common.lib;libavformat\libavformat.a;libavcodec\libavcodec.a;libavutil\libavutil.a;libswresample\libswresample.a;opus.lib;celt.lib;silk_common.lib;silk_float.lib;%(AdditionalDependencies) + kernel32.lib;user32.lib;shell32.lib;uuid.lib;ole32.lib;advapi32.lib;ws2_32.lib;gdi32.lib;comdlg32.lib;oleaut32.lib;Shlwapi.lib;Gdiplus.lib;imm32.lib;winmm.lib;qtmain.lib;glu32.lib;opengl32.lib;Strmiids.lib;Qt5Core.lib;Qt5Gui.lib;qtharfbuzzng.lib;qtpcre.lib;qtfreetype.lib;Qt5Widgets.lib;Qt5Network.lib;Qt5PlatformSupport.lib;platforms\qwindows.lib;imageformats\qwebp.lib;libeay32.lib;ssleay32.lib;Crypt32.lib;zlibstat.lib;lib_exif.lib;UxTheme.lib;DbgHelp.lib;LzmaLib.lib;OpenAL32.lib;common.lib;libavformat\libavformat.a;libavcodec\libavcodec.a;libavutil\libavutil.a;libswresample\libswresample.a;libswscale\libswscale.a;opus.lib;celt.lib;silk_common.lib;silk_float.lib;%(AdditionalDependencies) $(SolutionDir)$(Platform)\$(Configuration)Intermediate\$(TargetName).lib $(IntDir)$(TargetName).pgd @@ -142,7 +142,7 @@ Windows $(OutDir)$(ProjectName).exe .\..\..\Libraries\lzma\C\Util\LzmaLib\Release;.\..\..\Libraries\libexif-0.6.20\win32\Release;.\..\..\Libraries\ffmpeg;.\..\..\Libraries\opus\win32\VS2010\Win32\Release;.\..\..\Libraries\openal-soft\build\Release;.\..\..\Libraries\zlib-1.2.8\contrib\vstudio\vc11\x86\ZlibStatRelease;.\..\..\Libraries\openssl\Release\lib;$(QTDIR)\lib;$(QTDIR)\plugins;%(AdditionalLibraryDirectories) - kernel32.lib;user32.lib;shell32.lib;uuid.lib;ole32.lib;advapi32.lib;ws2_32.lib;gdi32.lib;comdlg32.lib;oleaut32.lib;Shlwapi.lib;Gdiplus.lib;imm32.lib;winmm.lib;qtmain.lib;glu32.lib;opengl32.lib;Strmiids.lib;Qt5Core.lib;Qt5Gui.lib;qtharfbuzzng.lib;qtpcre.lib;qtfreetype.lib;Qt5Widgets.lib;Qt5Network.lib;Qt5PlatformSupport.lib;platforms\qwindows.lib;imageformats\qwebp.lib;libeay32.lib;ssleay32.lib;Crypt32.lib;zlibstat.lib;lib_exif.lib;UxTheme.lib;DbgHelp.lib;LzmaLib.lib;OpenAL32.lib;common.lib;libavformat\libavformat.a;libavcodec\libavcodec.a;libavutil\libavutil.a;libswresample\libswresample.a;opus.lib;celt.lib;silk_common.lib;silk_float.lib;%(AdditionalDependencies) + kernel32.lib;user32.lib;shell32.lib;uuid.lib;ole32.lib;advapi32.lib;ws2_32.lib;gdi32.lib;comdlg32.lib;oleaut32.lib;Shlwapi.lib;Gdiplus.lib;imm32.lib;winmm.lib;qtmain.lib;glu32.lib;opengl32.lib;Strmiids.lib;Qt5Core.lib;Qt5Gui.lib;qtharfbuzzng.lib;qtpcre.lib;qtfreetype.lib;Qt5Widgets.lib;Qt5Network.lib;Qt5PlatformSupport.lib;platforms\qwindows.lib;imageformats\qwebp.lib;libeay32.lib;ssleay32.lib;Crypt32.lib;zlibstat.lib;lib_exif.lib;UxTheme.lib;DbgHelp.lib;LzmaLib.lib;OpenAL32.lib;common.lib;libavformat\libavformat.a;libavcodec\libavcodec.a;libavutil\libavutil.a;libswresample\libswresample.a;libswscale\libswscale.a;opus.lib;celt.lib;silk_common.lib;silk_float.lib;%(AdditionalDependencies) $(SolutionDir)$(Platform)\$(Configuration)Intermediate\$(TargetName).lib @@ -389,10 +389,6 @@ true true - - true - true - true true @@ -651,10 +647,6 @@ true true - - true - true - true true @@ -939,10 +931,6 @@ true true - - true - true - true true @@ -1009,7 +997,6 @@ - @@ -1023,6 +1010,7 @@ + @@ -1635,34 +1623,20 @@ $(QTDIR)\bin\moc.exe;%(FullPath) - - $(QTDIR)\bin\moc.exe;%(FullPath) - Moc%27ing switcher.h... - .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp - "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -DUNICODE -D_WITH_DEBUG -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\openssl\Release\include" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.5.1\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.5.1\QtGui" "-fstdafx.h" "-f../../SourceFiles/gui/switcher.h" - $(QTDIR)\bin\moc.exe;%(FullPath) - Moc%27ing switcher.h... - .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp - "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -D_SCL_SECURE_NO_WARNINGS "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\openssl_debug\Debug\include" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.5.1\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.5.1\QtGui" "-fstdafx.h" "-f../../SourceFiles/gui/switcher.h" - $(QTDIR)\bin\moc.exe;%(FullPath) - Moc%27ing switcher.h... - .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp - "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DAL_LIBTYPE_STATIC -DUNICODE -D_WITH_DEBUG -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\openssl\Release\include" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.5.1\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.5.1\QtGui" "-fstdafx.h" "-f../../SourceFiles/gui/switcher.h" - - $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing history.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp - "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -DUNICODE -D_WITH_DEBUG -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\openssl\Release\include" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.5.1\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.5.1\QtGui" "-fstdafx.h" "-f../../SourceFiles/history.h" - $(QTDIR)\bin\moc.exe;%(FullPath) + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/history.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -DUNICODE -D_WITH_DEBUG -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\openssl\Release\include" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.5.1\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.5.1\QtGui" Moc%27ing history.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp - "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -D_SCL_SECURE_NO_WARNINGS "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\openssl_debug\Debug\include" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.5.1\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.5.1\QtGui" "-fstdafx.h" "-f../../SourceFiles/history.h" - $(QTDIR)\bin\moc.exe;%(FullPath) + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/history.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -D_SCL_SECURE_NO_WARNINGS "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\openssl_debug\Debug\include" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.5.1\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.5.1\QtGui" Moc%27ing history.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp - "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DAL_LIBTYPE_STATIC -DUNICODE -D_WITH_DEBUG -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\openssl\Release\include" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.5.1\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.5.1\QtGui" "-fstdafx.h" "-f../../SourceFiles/history.h" + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/history.h" -DAL_LIBTYPE_STATIC -DUNICODE -D_WITH_DEBUG -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\openssl\Release\include" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.5.1\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.5.1\QtGui" + $(QTDIR)\bin\moc.exe;%(FullPath) + $(QTDIR)\bin\moc.exe;%(FullPath) + $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing historywidget.h... @@ -1793,6 +1767,26 @@ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DAL_LIBTYPE_STATIC -DUNICODE -D_WITH_DEBUG -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\openssl\Release\include" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.5.1\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.5.1\QtGui" "-fstdafx.h" "-f../../SourceFiles/localstorage.h" + + + + + + + + + + + + + + + + + + + + Moc%27ing mtpConnection.h... @@ -2024,7 +2018,26 @@ true - + + + + + + + + + + + + + + + + + + + + Moc%27ing sysbuttons.h... diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 8363c74863..a5652079ea 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -609,18 +609,6 @@ Generated Files\Release - - gui - - - Generated Files\Deploy - - - Generated Files\Debug - - - Generated Files\Release - Source Files @@ -675,15 +663,6 @@ Generated Files\Release - - Generated Files\Deploy - - - Generated Files\Debug - - - Generated Files\Release - Source Files @@ -906,6 +885,18 @@ mtproto + + Source Files + + + Generated Files\Deploy + + + Generated Files\Debug + + + Generated Files\Release + @@ -986,9 +977,6 @@ Source Files - - Source Files - Source Files @@ -1129,9 +1117,6 @@ gui - - gui - Source Files @@ -1219,6 +1204,12 @@ gui + + Source Files + + + Source Files + diff --git a/Telegram/Telegram.xcodeproj/project.pbxproj b/Telegram/Telegram.xcodeproj/project.pbxproj index 05c73417b0..336683034d 100644 --- a/Telegram/Telegram.xcodeproj/project.pbxproj +++ b/Telegram/Telegram.xcodeproj/project.pbxproj @@ -52,6 +52,9 @@ 0749CE69194D723400345D61 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 07C3AF24194335ED0016CFF1 /* Images.xcassets */; }; 074FCB8E19D36851004C6EB2 /* popupmenu.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 074FCB8C19D36851004C6EB2 /* popupmenu.cpp */; }; 074FCB9119D36E60004C6EB2 /* moc_popupmenu.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 074FCB9019D36E60004C6EB2 /* moc_popupmenu.cpp */; }; + 0752F8701C2C84470026D0BC /* layout.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 0752F86E1C2C84470026D0BC /* layout.cpp */; }; + 0752F8731C2C89220026D0BC /* VideoDecodeAcceleration.framework in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 0752F8721C2C89220026D0BC /* VideoDecodeAcceleration.framework */; }; + 0752F8751C2C89F40026D0BC /* VideoToolbox.framework in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 0752F8741C2C89F40026D0BC /* VideoToolbox.framework */; }; 07539B1D1A1416AF00083EFC /* moc_history.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07539B1C1A1416AF00083EFC /* moc_history.cpp */; }; 0755AEDD1AD12A80004D738A /* moc_abstractbox.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 0755AEDA1AD12A80004D738A /* moc_abstractbox.cpp */; }; 0755AEDE1AD12A80004D738A /* moc_intropwdcheck.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 0755AEDB1AD12A80004D738A /* moc_intropwdcheck.cpp */; }; @@ -70,8 +73,6 @@ 07B604351B46A20900CA29FE /* moc_playerwidget.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07B604341B46A20900CA29FE /* moc_playerwidget.cpp */; }; 07BE850F1A2093C9008ACB9F /* localstorage.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07BE850D1A2093C9008ACB9F /* localstorage.cpp */; }; 07BE85121A20961F008ACB9F /* moc_localstorage.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07BE85111A20961F008ACB9F /* moc_localstorage.cpp */; }; - 07C4753B1967DF1C00CAAFE9 /* switcher.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07C475391967DF1C00CAAFE9 /* switcher.cpp */; }; - 07C4753F1967E37300CAAFE9 /* moc_switcher.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07C4753E1967E37300CAAFE9 /* moc_switcher.cpp */; }; 07C7596F1B1F7E0000662169 /* autoupdater.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07C7596D1B1F7E0000662169 /* autoupdater.cpp */; }; 07C759721B1F7E2800662169 /* moc_autoupdater.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07C759711B1F7E2800662169 /* moc_autoupdater.cpp */; }; 07CAACD81AEA64F00058E508 /* AudioUnit.framework in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 07CAACD71AEA64F00058E508 /* AudioUnit.framework */; }; @@ -287,6 +288,10 @@ 074FCB8C19D36851004C6EB2 /* popupmenu.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = popupmenu.cpp; path = SourceFiles/gui/popupmenu.cpp; sourceTree = SOURCE_ROOT; }; 074FCB8D19D36851004C6EB2 /* popupmenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = popupmenu.h; path = SourceFiles/gui/popupmenu.h; sourceTree = SOURCE_ROOT; }; 074FCB9019D36E60004C6EB2 /* moc_popupmenu.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_popupmenu.cpp; path = GeneratedFiles/Debug/moc_popupmenu.cpp; sourceTree = SOURCE_ROOT; }; + 0752F86E1C2C84470026D0BC /* layout.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = layout.cpp; path = SourceFiles/layout.cpp; sourceTree = SOURCE_ROOT; }; + 0752F86F1C2C84470026D0BC /* layout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = layout.h; path = SourceFiles/layout.h; sourceTree = SOURCE_ROOT; }; + 0752F8721C2C89220026D0BC /* VideoDecodeAcceleration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = VideoDecodeAcceleration.framework; path = System/Library/Frameworks/VideoDecodeAcceleration.framework; sourceTree = SDKROOT; }; + 0752F8741C2C89F40026D0BC /* VideoToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = VideoToolbox.framework; path = System/Library/Frameworks/VideoToolbox.framework; sourceTree = SDKROOT; }; 07539B1C1A1416AF00083EFC /* moc_history.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_history.cpp; path = GeneratedFiles/Debug/moc_history.cpp; sourceTree = SOURCE_ROOT; }; 0755AEDA1AD12A80004D738A /* moc_abstractbox.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_abstractbox.cpp; path = GeneratedFiles/Debug/moc_abstractbox.cpp; sourceTree = SOURCE_ROOT; }; 0755AEDB1AD12A80004D738A /* moc_intropwdcheck.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_intropwdcheck.cpp; path = GeneratedFiles/Debug/moc_intropwdcheck.cpp; sourceTree = SOURCE_ROOT; }; @@ -320,9 +325,6 @@ 07C3AF27194336B90016CFF1 /* pspecific_mac_p.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = pspecific_mac_p.h; path = SourceFiles/pspecific_mac_p.h; sourceTree = SOURCE_ROOT; }; 07C3AF2919433ABF0016CFF1 /* style_classes.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = style_classes.txt; path = Resources/style_classes.txt; sourceTree = SOURCE_ROOT; }; 07C3AF2A19433ABF0016CFF1 /* style.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = style.txt; path = Resources/style.txt; sourceTree = SOURCE_ROOT; }; - 07C475391967DF1C00CAAFE9 /* switcher.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = switcher.cpp; path = SourceFiles/gui/switcher.cpp; sourceTree = SOURCE_ROOT; }; - 07C4753A1967DF1C00CAAFE9 /* switcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = switcher.h; path = SourceFiles/gui/switcher.h; sourceTree = SOURCE_ROOT; }; - 07C4753E1967E37300CAAFE9 /* moc_switcher.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_switcher.cpp; path = GeneratedFiles/Debug/moc_switcher.cpp; sourceTree = SOURCE_ROOT; }; 07C7596D1B1F7E0000662169 /* autoupdater.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = autoupdater.cpp; path = SourceFiles/autoupdater.cpp; sourceTree = SOURCE_ROOT; }; 07C7596E1B1F7E0000662169 /* autoupdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = autoupdater.h; path = SourceFiles/autoupdater.h; sourceTree = SOURCE_ROOT; }; 07C759711B1F7E2800662169 /* moc_autoupdater.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_autoupdater.cpp; path = GeneratedFiles/Debug/moc_autoupdater.cpp; sourceTree = SOURCE_ROOT; }; @@ -698,6 +700,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 0752F8751C2C89F40026D0BC /* VideoToolbox.framework in Link Binary With Libraries */, + 0752F8731C2C89220026D0BC /* VideoDecodeAcceleration.framework in Link Binary With Libraries */, 07CAACD81AEA64F00058E508 /* AudioUnit.framework in Link Binary With Libraries */, 1BB705CDB741E2B7450201A5 /* Cocoa.framework in Link Binary With Libraries */, 328FD74542F6E2C873EE4D4B /* ApplicationServices.framework in Link Binary With Libraries */, @@ -846,7 +850,6 @@ 6A510365F9F6367ECB0DB065 /* images.cpp */, 6E1859D714E4471E053D90C9 /* scrollarea.cpp */, 420A06A32B66D250142B4B6D /* style_core.cpp */, - 07C475391967DF1C00CAAFE9 /* switcher.cpp */, 135FD3715BFDC50AD7B00E04 /* text.cpp */, BB1602EA641643DE565005B1 /* twidget.cpp */, 85FABD67716E36CD8B3CA4FA /* animation.h */, @@ -864,7 +867,6 @@ 0F8FFD87AEBAC448568570DC /* images.h */, 83A36F229E897566E011B79E /* scrollarea.h */, 0FC38EE7F29EF895925A2C49 /* style_core.h */, - 07C4753A1967DF1C00CAAFE9 /* switcher.h */, 6E8FD0ED1B60D43929944CD2 /* text.h */, 507CCEEC4CBA3E3BD6EEDED1 /* twidget.h */, ); @@ -909,6 +911,7 @@ 07080BD01A436A5000741A51 /* lang.cpp */, AF5776B0652744978B7DF6D3 /* langloaderplain.cpp */, 5A9B4C6C59856143F3D0DE53 /* layerwidget.cpp */, + 0752F86E1C2C84470026D0BC /* layout.cpp */, 07A69330199277BA0099CB9F /* mediaview.cpp */, 0732E4A7199E262300D50FE7 /* overviewwidget.cpp */, 07DE929F1AA4923200A18F6F /* passcodewidget.cpp */, @@ -949,6 +952,7 @@ 07080BD11A436A5000741A51 /* lang.h */, 25CA12A22B83B0B038C5B5DE /* langloaderplain.h */, 6C86B6E6AB1857B735B720D6 /* layerwidget.h */, + 0752F86F1C2C84470026D0BC /* layout.h */, 07A69331199277BA0099CB9F /* mediaview.h */, 07DC429D1B5EA0E600B6B888 /* numbers.h */, 0732E4A8199E262300D50FE7 /* overviewwidget.h */, @@ -1127,7 +1131,6 @@ 074FCB9019D36E60004C6EB2 /* moc_popupmenu.cpp */, 07D703BA19B88FB900C4EED2 /* moc_audio.cpp */, 0732E4AB199E268A00D50FE7 /* moc_overviewwidget.cpp */, - 07C4753E1967E37300CAAFE9 /* moc_switcher.cpp */, E181C525E21A16F2D4396CA7 /* moc_application.cpp */, 3B3ED09AB00290D78CF1181B /* moc_dialogswidget.cpp */, AC9B5F6FB4B984C8D76F7AE2 /* moc_dropdown.cpp */, @@ -1219,6 +1222,8 @@ AF39DD055C3EF8226FBE929D /* Frameworks */ = { isa = PBXGroup; children = ( + 0752F8741C2C89F40026D0BC /* VideoToolbox.framework */, + 0752F8721C2C89220026D0BC /* VideoDecodeAcceleration.framework */, 07CAACD71AEA64F00058E508 /* AudioUnit.framework */, 07055CC3194EE85B0008DEF6 /* libcrypto.a */, 07D795491B5544B200DE9598 /* qtpcre */, @@ -1564,7 +1569,6 @@ EBE29731916DB43BF49FE7A4 /* aboutbox.cpp in Compile Sources */, 4426AF526AAD86D6F73CE36F /* addcontactbox.cpp in Compile Sources */, 07D7034B19B8755A00C4EED2 /* audio.cpp in Compile Sources */, - 07C4753B1967DF1C00CAAFE9 /* switcher.cpp in Compile Sources */, A0A6B97F7DBEC81004EC9461 /* confirmbox.cpp in Compile Sources */, 4FEA8F51B7BC7CAC71347A1A /* connectionbox.cpp in Compile Sources */, 07C7596F1B1F7E0000662169 /* autoupdater.cpp in Compile Sources */, @@ -1572,7 +1576,6 @@ 298BFAB73BF182297584F96F /* contactsbox.cpp in Compile Sources */, BA41D511A9BBCA09365DF88C /* downloadpathbox.cpp in Compile Sources */, 07DB67511AD07CB800A51329 /* intropwdcheck.cpp in Compile Sources */, - 07C4753F1967E37300CAAFE9 /* moc_switcher.cpp in Compile Sources */, 3ABE4F9B2264F770D944106D /* emojibox.cpp in Compile Sources */, 07D703BB19B88FB900C4EED2 /* moc_audio.cpp in Compile Sources */, 77B998AC22A13EF3DDEE07AC /* photocropbox.cpp in Compile Sources */, @@ -1626,6 +1629,7 @@ 8B71D1C7BB9DCEE6511219C2 /* moc_flatlabel.cpp in Compile Sources */, 0710C9FE1B0B9376001B4272 /* stickersetbox.cpp in Compile Sources */, 0764D55D1ABAD71B00FBFEED /* moc_apiwrap.cpp in Compile Sources */, + 0752F8701C2C84470026D0BC /* layout.cpp in Compile Sources */, 07DE92AD1AA4928B00A18F6F /* moc_passcodebox.cpp in Compile Sources */, FCC949FEA178F9F5D7478027 /* moc_flattextarea.cpp in Compile Sources */, 07DB674D1AD07C9200A51329 /* abstractbox.cpp in Compile Sources */, @@ -1697,7 +1701,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 0.9.15; + CURRENT_PROJECT_VERSION = 0.9.19; DEBUG_INFORMATION_FORMAT = dwarf; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_OPTIMIZATION_LEVEL = 0; @@ -1716,7 +1720,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 0.9.15; + CURRENT_PROJECT_VERSION = 0.9.19; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_OPTIMIZATION_LEVEL = fast; GCC_PREFIX_HEADER = ./SourceFiles/stdafx.h; @@ -1743,10 +1747,10 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = ""; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 0.9.15; + CURRENT_PROJECT_VERSION = 0.9.19; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DYLIB_COMPATIBILITY_VERSION = 0.9; - DYLIB_CURRENT_VERSION = 0.9.15; + DYLIB_CURRENT_VERSION = 0.9.19; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ""; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; @@ -1852,6 +1856,7 @@ /usr/local/lib/libexif.a, /usr/local/lib/libavcodec.a, /usr/local/lib/libavformat.a, + /usr/local/lib/libswscale.a, /usr/local/lib/libswresample.a, /usr/local/lib/libavutil.a, /usr/local/lib/libiconv.a, @@ -1877,10 +1882,10 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = ""; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 0.9.15; + CURRENT_PROJECT_VERSION = 0.9.19; DEBUG_INFORMATION_FORMAT = dwarf; DYLIB_COMPATIBILITY_VERSION = 0.9; - DYLIB_CURRENT_VERSION = 0.9.15; + DYLIB_CURRENT_VERSION = 0.9.19; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = ""; @@ -1986,6 +1991,7 @@ /usr/local/lib/libexif.a, /usr/local/lib/libavcodec.a, /usr/local/lib/libavformat.a, + /usr/local/lib/libswscale.a, /usr/local/lib/libswresample.a, /usr/local/lib/libavutil.a, /usr/local/lib/libiconv.a, diff --git a/Telegram/Telegram.xcodeproj/qt_preprocess.mak b/Telegram/Telegram.xcodeproj/qt_preprocess.mak index 1bfebb9eba..109bb8a076 100644 --- a/Telegram/Telegram.xcodeproj/qt_preprocess.mak +++ b/Telegram/Telegram.xcodeproj/qt_preprocess.mak @@ -43,7 +43,7 @@ compilers: GeneratedFiles/qrc_telegram.cpp GeneratedFiles/qrc_telegram_emojis.cp GeneratedFiles/Debug/moc_popupmenu.cpp\ GeneratedFiles/Debug/moc_countryinput.cpp GeneratedFiles/Debug/moc_flatbutton.cpp GeneratedFiles/Debug/moc_flatcheckbox.cpp\ GeneratedFiles/Debug/moc_flatinput.cpp GeneratedFiles/Debug/moc_flatlabel.cpp GeneratedFiles/Debug/moc_flattextarea.cpp\ - GeneratedFiles/Debug/moc_switcher.cpp GeneratedFiles/Debug/moc_scrollarea.cpp GeneratedFiles/Debug/moc_twidget.cpp\ + GeneratedFiles/Debug/moc_scrollarea.cpp GeneratedFiles/Debug/moc_twidget.cpp\ GeneratedFiles/Debug/moc_aboutbox.cpp GeneratedFiles/Debug/moc_abstractbox.cpp GeneratedFiles/Debug/moc_addcontactbox.cpp\ GeneratedFiles/Debug/moc_autolockbox.cpp\ GeneratedFiles/Debug/moc_backgroundbox.cpp\ @@ -99,9 +99,9 @@ GeneratedFiles/qrc_telegram_mac.cpp: SourceFiles/telegram_mac.qrc \ SourceFiles/art/osxtray.png /usr/local/Qt-5.5.1/bin/rcc -name telegram_mac SourceFiles/telegram_mac.qrc -o GeneratedFiles/qrc_telegram_mac.cpp -compiler_moc_header_make_all: GeneratedFiles/Debug/moc_apiwrap.cpp GeneratedFiles/Debug/moc_application.cpp GeneratedFiles/Debug/moc_audio.cpp GeneratedFiles/Debug/moc_autoupdater.cpp GeneratedFiles/Debug/moc_dialogswidget.cpp GeneratedFiles/Debug/moc_dropdown.cpp GeneratedFiles/Debug/moc_fileuploader.cpp GeneratedFiles/Debug/moc_history.cpp GeneratedFiles/Debug/moc_historywidget.cpp GeneratedFiles/Debug/moc_layerwidget.cpp GeneratedFiles/Debug/moc_mediaview.cpp GeneratedFiles/Debug/moc_overviewwidget.cpp GeneratedFiles/Debug/moc_playerwidget.cpp GeneratedFiles/Debug/moc_profilewidget.cpp GeneratedFiles/Debug/moc_passcodewidget.cpp GeneratedFiles/Debug/moc_localimageloader.cpp GeneratedFiles/Debug/moc_localstorage.cpp GeneratedFiles/Debug/moc_mainwidget.cpp GeneratedFiles/Debug/moc_settingswidget.cpp GeneratedFiles/Debug/moc_sysbuttons.cpp GeneratedFiles/Debug/moc_title.cpp GeneratedFiles/Debug/moc_types.cpp GeneratedFiles/Debug/moc_window.cpp GeneratedFiles/Debug/moc_mtp.cpp GeneratedFiles/Debug/moc_mtpConnection.cpp GeneratedFiles/Debug/moc_mtpDC.cpp GeneratedFiles/Debug/moc_mtpFileLoader.cpp GeneratedFiles/Debug/moc_mtpSession.cpp GeneratedFiles/Debug/moc_animation.cpp GeneratedFiles/Debug/moc_button.cpp GeneratedFiles/Debug/moc_popupmenu.cpp GeneratedFiles/Debug/moc_countryinput.cpp GeneratedFiles/Debug/moc_flatbutton.cpp GeneratedFiles/Debug/moc_flatcheckbox.cpp GeneratedFiles/Debug/moc_flatinput.cpp GeneratedFiles/Debug/moc_flatlabel.cpp GeneratedFiles/Debug/moc_flattextarea.cpp GeneratedFiles/Debug/moc_switcher.cpp GeneratedFiles/Debug/moc_scrollarea.cpp GeneratedFiles/Debug/moc_twidget.cpp GeneratedFiles/Debug/moc_aboutbox.cpp GeneratedFiles/Debug/moc_abstractbox.cpp GeneratedFiles/Debug/moc_addcontactbox.cpp GeneratedFiles/Debug/moc_autolockbox.cpp GeneratedFiles/Debug/moc_backgroundbox.cpp GeneratedFiles/Debug/moc_confirmbox.cpp GeneratedFiles/Debug/moc_connectionbox.cpp GeneratedFiles/Debug/moc_contactsbox.cpp GeneratedFiles/Debug/moc_downloadpathbox.cpp GeneratedFiles/Debug/moc_emojibox.cpp GeneratedFiles/Debug/moc_languagebox.cpp GeneratedFiles/Debug/moc_passcodebox.cpp GeneratedFiles/Debug/moc_photocropbox.cpp GeneratedFiles/Debug/moc_photosendbox.cpp GeneratedFiles/Debug/moc_sessionsbox.cpp GeneratedFiles/Debug/moc_stickersetbox.cpp GeneratedFiles/Debug/moc_usernamebox.cpp GeneratedFiles/Debug/moc_intro.cpp GeneratedFiles/Debug/moc_introcode.cpp GeneratedFiles/Debug/moc_introphone.cpp GeneratedFiles/Debug/moc_intropwdcheck.cpp GeneratedFiles/Debug/moc_introsignup.cpp GeneratedFiles/Debug/moc_pspecific_mac.cpp +compiler_moc_header_make_all: GeneratedFiles/Debug/moc_apiwrap.cpp GeneratedFiles/Debug/moc_application.cpp GeneratedFiles/Debug/moc_audio.cpp GeneratedFiles/Debug/moc_autoupdater.cpp GeneratedFiles/Debug/moc_dialogswidget.cpp GeneratedFiles/Debug/moc_dropdown.cpp GeneratedFiles/Debug/moc_fileuploader.cpp GeneratedFiles/Debug/moc_history.cpp GeneratedFiles/Debug/moc_historywidget.cpp GeneratedFiles/Debug/moc_layerwidget.cpp GeneratedFiles/Debug/moc_mediaview.cpp GeneratedFiles/Debug/moc_overviewwidget.cpp GeneratedFiles/Debug/moc_playerwidget.cpp GeneratedFiles/Debug/moc_profilewidget.cpp GeneratedFiles/Debug/moc_passcodewidget.cpp GeneratedFiles/Debug/moc_localimageloader.cpp GeneratedFiles/Debug/moc_localstorage.cpp GeneratedFiles/Debug/moc_mainwidget.cpp GeneratedFiles/Debug/moc_settingswidget.cpp GeneratedFiles/Debug/moc_sysbuttons.cpp GeneratedFiles/Debug/moc_title.cpp GeneratedFiles/Debug/moc_types.cpp GeneratedFiles/Debug/moc_window.cpp GeneratedFiles/Debug/moc_mtp.cpp GeneratedFiles/Debug/moc_mtpConnection.cpp GeneratedFiles/Debug/moc_mtpDC.cpp GeneratedFiles/Debug/moc_mtpFileLoader.cpp GeneratedFiles/Debug/moc_mtpSession.cpp GeneratedFiles/Debug/moc_animation.cpp GeneratedFiles/Debug/moc_button.cpp GeneratedFiles/Debug/moc_popupmenu.cpp GeneratedFiles/Debug/moc_countryinput.cpp GeneratedFiles/Debug/moc_flatbutton.cpp GeneratedFiles/Debug/moc_flatcheckbox.cpp GeneratedFiles/Debug/moc_flatinput.cpp GeneratedFiles/Debug/moc_flatlabel.cpp GeneratedFiles/Debug/moc_flattextarea.cpp GeneratedFiles/Debug/moc_scrollarea.cpp GeneratedFiles/Debug/moc_twidget.cpp GeneratedFiles/Debug/moc_aboutbox.cpp GeneratedFiles/Debug/moc_abstractbox.cpp GeneratedFiles/Debug/moc_addcontactbox.cpp GeneratedFiles/Debug/moc_autolockbox.cpp GeneratedFiles/Debug/moc_backgroundbox.cpp GeneratedFiles/Debug/moc_confirmbox.cpp GeneratedFiles/Debug/moc_connectionbox.cpp GeneratedFiles/Debug/moc_contactsbox.cpp GeneratedFiles/Debug/moc_downloadpathbox.cpp GeneratedFiles/Debug/moc_emojibox.cpp GeneratedFiles/Debug/moc_languagebox.cpp GeneratedFiles/Debug/moc_passcodebox.cpp GeneratedFiles/Debug/moc_photocropbox.cpp GeneratedFiles/Debug/moc_photosendbox.cpp GeneratedFiles/Debug/moc_sessionsbox.cpp GeneratedFiles/Debug/moc_stickersetbox.cpp GeneratedFiles/Debug/moc_usernamebox.cpp GeneratedFiles/Debug/moc_intro.cpp GeneratedFiles/Debug/moc_introcode.cpp GeneratedFiles/Debug/moc_introphone.cpp GeneratedFiles/Debug/moc_intropwdcheck.cpp GeneratedFiles/Debug/moc_introsignup.cpp GeneratedFiles/Debug/moc_pspecific_mac.cpp compiler_moc_header_clean: - -$(DEL_FILE) GeneratedFiles/Debug/moc_apiwrap.cpp GeneratedFiles/Debug/moc_application.cpp GeneratedFiles/Debug/moc_audio.cpp GeneratedFiles/Debug/moc_autoupdater.cpp GeneratedFiles/Debug/moc_dialogswidget.cpp GeneratedFiles/Debug/moc_dropdown.cpp GeneratedFiles/Debug/moc_fileuploader.cpp GeneratedFiles/Debug/moc_history.cpp GeneratedFiles/Debug/moc_historywidget.cpp GeneratedFiles/Debug/moc_layerwidget.cpp GeneratedFiles/Debug/moc_mediaview.cpp GeneratedFiles/Debug/moc_overviewwidget.cpp GeneratedFiles/Debug/moc_playerwidget.cpp GeneratedFiles/Debug/moc_profilewidget.cpp GeneratedFiles/Debug/moc_passcodewidget.cpp GeneratedFiles/Debug/moc_localimageloader.cpp GeneratedFiles/Debug/moc_localstorage.cpp GeneratedFiles/Debug/moc_mainwidget.cpp GeneratedFiles/Debug/moc_settingswidget.cpp GeneratedFiles/Debug/moc_sysbuttons.cpp GeneratedFiles/Debug/moc_title.cpp GeneratedFiles/Debug/moc_types.cpp GeneratedFiles/Debug/moc_window.cpp GeneratedFiles/Debug/moc_mtp.cpp GeneratedFiles/Debug/moc_mtpConnection.cpp GeneratedFiles/Debug/moc_mtpDC.cpp GeneratedFiles/Debug/moc_mtpFileLoader.cpp GeneratedFiles/Debug/moc_mtpSession.cpp GeneratedFiles/Debug/moc_animation.cpp GeneratedFiles/Debug/moc_button.cpp GeneratedFiles/Debug/moc_popupmenu.cpp GeneratedFiles/Debug/moc_countryinput.cpp GeneratedFiles/Debug/moc_flatbutton.cpp GeneratedFiles/Debug/moc_flatcheckbox.cpp GeneratedFiles/Debug/moc_flatinput.cpp GeneratedFiles/Debug/moc_flatlabel.cpp GeneratedFiles/Debug/moc_flattextarea.cpp GeneratedFiles/Debug/moc_switcher.cpp GeneratedFiles/Debug/moc_scrollarea.cpp GeneratedFiles/Debug/moc_twidget.cpp GeneratedFiles/Debug/moc_aboutbox.cpp GeneratedFiles/Debug/moc_abstractbox.cpp GeneratedFiles/Debug/moc_addcontactbox.cpp GeneratedFiles/Debug/moc_autolockbox.cpp GeneratedFiles/Debug/moc_backgroundbox.cpp GeneratedFiles/Debug/moc_confirmbox.cpp GeneratedFiles/Debug/moc_connectionbox.cpp GeneratedFiles/Debug/moc_contactsbox.cpp GeneratedFiles/Debug/moc_downloadpathbox.cpp GeneratedFiles/Debug/moc_emojibox.cpp GeneratedFiles/Debug/moc_languagebox.cpp GeneratedFiles/Debug/moc_passcodebox.cpp GeneratedFiles/Debug/moc_photocropbox.cpp GeneratedFiles/Debug/moc_photosendbox.cpp GeneratedFiles/Debug/moc_sessionsbox.cpp GeneratedFiles/Debug/moc_stickersetbox.cpp GeneratedFiles/Debug/moc_usernamedbox.cpp GeneratedFiles/Debug/moc_intro.cpp GeneratedFiles/Debug/moc_introcode.cpp GeneratedFiles/Debug/moc_introphone.cpp GeneratedFiles/Debug/moc_intropwdcheck.cpp GeneratedFiles/Debug/moc_introsignup.cpp GeneratedFiles/Debug/moc_pspecific_mac.cpp + -$(DEL_FILE) GeneratedFiles/Debug/moc_apiwrap.cpp GeneratedFiles/Debug/moc_application.cpp GeneratedFiles/Debug/moc_audio.cpp GeneratedFiles/Debug/moc_autoupdater.cpp GeneratedFiles/Debug/moc_dialogswidget.cpp GeneratedFiles/Debug/moc_dropdown.cpp GeneratedFiles/Debug/moc_fileuploader.cpp GeneratedFiles/Debug/moc_history.cpp GeneratedFiles/Debug/moc_historywidget.cpp GeneratedFiles/Debug/moc_layerwidget.cpp GeneratedFiles/Debug/moc_mediaview.cpp GeneratedFiles/Debug/moc_overviewwidget.cpp GeneratedFiles/Debug/moc_playerwidget.cpp GeneratedFiles/Debug/moc_profilewidget.cpp GeneratedFiles/Debug/moc_passcodewidget.cpp GeneratedFiles/Debug/moc_localimageloader.cpp GeneratedFiles/Debug/moc_localstorage.cpp GeneratedFiles/Debug/moc_mainwidget.cpp GeneratedFiles/Debug/moc_settingswidget.cpp GeneratedFiles/Debug/moc_sysbuttons.cpp GeneratedFiles/Debug/moc_title.cpp GeneratedFiles/Debug/moc_types.cpp GeneratedFiles/Debug/moc_window.cpp GeneratedFiles/Debug/moc_mtp.cpp GeneratedFiles/Debug/moc_mtpConnection.cpp GeneratedFiles/Debug/moc_mtpDC.cpp GeneratedFiles/Debug/moc_mtpFileLoader.cpp GeneratedFiles/Debug/moc_mtpSession.cpp GeneratedFiles/Debug/moc_animation.cpp GeneratedFiles/Debug/moc_button.cpp GeneratedFiles/Debug/moc_popupmenu.cpp GeneratedFiles/Debug/moc_countryinput.cpp GeneratedFiles/Debug/moc_flatbutton.cpp GeneratedFiles/Debug/moc_flatcheckbox.cpp GeneratedFiles/Debug/moc_flatinput.cpp GeneratedFiles/Debug/moc_flatlabel.cpp GeneratedFiles/Debug/moc_flattextarea.cpp GeneratedFiles/Debug/moc_scrollarea.cpp GeneratedFiles/Debug/moc_twidget.cpp GeneratedFiles/Debug/moc_aboutbox.cpp GeneratedFiles/Debug/moc_abstractbox.cpp GeneratedFiles/Debug/moc_addcontactbox.cpp GeneratedFiles/Debug/moc_autolockbox.cpp GeneratedFiles/Debug/moc_backgroundbox.cpp GeneratedFiles/Debug/moc_confirmbox.cpp GeneratedFiles/Debug/moc_connectionbox.cpp GeneratedFiles/Debug/moc_contactsbox.cpp GeneratedFiles/Debug/moc_downloadpathbox.cpp GeneratedFiles/Debug/moc_emojibox.cpp GeneratedFiles/Debug/moc_languagebox.cpp GeneratedFiles/Debug/moc_passcodebox.cpp GeneratedFiles/Debug/moc_photocropbox.cpp GeneratedFiles/Debug/moc_photosendbox.cpp GeneratedFiles/Debug/moc_sessionsbox.cpp GeneratedFiles/Debug/moc_stickersetbox.cpp GeneratedFiles/Debug/moc_usernamedbox.cpp GeneratedFiles/Debug/moc_intro.cpp GeneratedFiles/Debug/moc_introcode.cpp GeneratedFiles/Debug/moc_introphone.cpp GeneratedFiles/Debug/moc_intropwdcheck.cpp GeneratedFiles/Debug/moc_introsignup.cpp GeneratedFiles/Debug/moc_pspecific_mac.cpp GeneratedFiles/Debug/moc_apiwrap.cpp: SourceFiles/types.h \ SourceFiles/logs.h \ SourceFiles/apiwrap.h @@ -428,20 +428,6 @@ GeneratedFiles/Debug/moc_flattextarea.cpp: ../../Libraries/QtStatic/qtbase/inclu SourceFiles/gui/flattextarea.h /usr/local/Qt-5.5.1/bin/moc $(DEFINES) -D__APPLE__ -D__GNUC__=4 -I/usr/local/Qt-5.5.1/mkspecs/macx-clang -I. -I/usr/local/Qt-5.5.1/include/QtGui/5.5.1/QtGui -I/usr/local/Qt-5.5.1/include/QtCore/5.5.1/QtCore -I/usr/local/Qt-5.5.1/include -I./SourceFiles -I./GeneratedFiles -I../../Libraries/lzma/C -I../../Libraries/libexif-0.6.20 -I/usr/local/Qt-5.5.1/include -I/usr/local/Qt-5.5.1/include/QtMultimedia -I/usr/local/Qt-5.5.1/include/QtWidgets -I/usr/local/Qt-5.5.1/include/QtNetwork -I/usr/local/Qt-5.5.1/include/QtGui -I/usr/local/Qt-5.5.1/include/QtCore -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/usr/include/c++/4.2.1 -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/usr/include/c++/4.2.1/backward -I/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/5.1/include -I/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/usr/include SourceFiles/gui/flattextarea.h -o GeneratedFiles/Debug/moc_flattextarea.cpp -GeneratedFiles/Debug/moc_switcher.cpp: ../../Libraries/QtStatic/qtbase/include/QtWidgets/QWidget \ - SourceFiles/gui/twidget.h \ - SourceFiles/style.h \ - GeneratedFiles/style_classes.h \ - GeneratedFiles/style_auto.h \ - SourceFiles/gui/animation.h \ - SourceFiles/types.h \ - ../../Libraries/QtStatic/qtbase/include/QtCore/QReadWriteLock \ - SourceFiles/logs.h \ - ../../Libraries/QtStatic/qtbase/include/QtCore/QTimer \ - ../../Libraries/QtStatic/qtbase/include/QtGui/QColor \ - SourceFiles/gui/switcher.h - /usr/local/Qt-5.5.1/bin/moc $(DEFINES) -D__APPLE__ -D__GNUC__=4 -I/usr/local/Qt-5.5.1/mkspecs/macx-clang -I. -I/usr/local/Qt-5.5.1/include/QtGui/5.5.1/QtGui -I/usr/local/Qt-5.5.1/include/QtCore/5.5.1/QtCore -I/usr/local/Qt-5.5.1/include -I./SourceFiles -I./GeneratedFiles -I../../Libraries/lzma/C -I../../Libraries/libexif-0.6.20 -I/usr/local/Qt-5.5.1/include -I/usr/local/Qt-5.5.1/include/QtMultimedia -I/usr/local/Qt-5.5.1/include/QtWidgets -I/usr/local/Qt-5.5.1/include/QtNetwork -I/usr/local/Qt-5.5.1/include/QtGui -I/usr/local/Qt-5.5.1/include/QtCore -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/usr/include/c++/4.2.1 -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/usr/include/c++/4.2.1/backward -I/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/5.1/include -I/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/usr/include SourceFiles/gui/switcher.h -o GeneratedFiles/Debug/moc_switcher.cpp - GeneratedFiles/Debug/moc_scrollarea.cpp: ../../Libraries/QtStatic/qtbase/include/QtWidgets/QScrollArea \ SourceFiles/style.h \ GeneratedFiles/style_classes.h \ diff --git a/Telegram/Updater.rc b/Telegram/Updater.rc index b55372ad83..4c166b9404 100644 Binary files a/Telegram/Updater.rc and b/Telegram/Updater.rc differ diff --git a/Telegram/Version b/Telegram/Version index 88016d82f2..9aa9665cef 100644 --- a/Telegram/Version +++ b/Telegram/Version @@ -1,6 +1,6 @@ -AppVersion 9015 +AppVersion 9019 AppVersionStrMajor 0.9 -AppVersionStrSmall 0.9.15 -AppVersionStr 0.9.15 -DevChannel 0 -BetaVersion 0 9014003 +AppVersionStrSmall 0.9.19 +AppVersionStr 0.9.19 +DevChannel 1 +BetaVersion 0 9015008 diff --git a/XCODE.md b/XCODE.md index 9c78f70fd1..96b94b667f 100644 --- a/XCODE.md +++ b/XCODE.md @@ -2,35 +2,59 @@ ###Prepare folder -Choose a folder for the future build, for example **/Users/user/TBuild** There you will have two folders, **Libraries** for third-party libs and **tdesktop** (or **tdesktop-master**) for the app. +Choose a folder for the future build, for example **/Users/user/TBuild** + +There you will have two folders, **Libraries** for third-party libs and **tdesktop** (or **tdesktop-master**) for the app. + +**You will need this hierarchy to be able to follow this README !** ###Clone source code -By git – in Terminal go to **/Users/user/TBuild** and run +By git – in Terminal go to **/Users/user/TBuild** and run: git clone https://github.com/telegramdesktop/tdesktop.git -or download in ZIP and extract to **/Users/user/TBuild** rename **tdesktop-master** to **tdesktop** to have **/Users/user/TBuild/tdesktop/Telegram/Telegram.xcodeproj** project +or: +* download in ZIP and extract to **/Users/user/TBuild** +* rename **tdesktop-master** to **tdesktop**. + +The path to Telegram.xcodeproj should now be: **/Users/user/TBuild/tdesktop/Telegram/Telegram.xcodeproj** ###Prepare libraries -In your build Terminal run +In your build Terminal run: - MACOSX_DEPLOYMENT_TARGET=10.8 + MACOSX_DEPLOYMENT_TARGET=10.8 to set minimal supported OS version to 10.8 for future console builds. ####OpenSSL 1.0.1g -Get sources from https://github.com/telegramdesktop/openssl-xcode, by git – in Terminal go to **/Users/user/TBuild/Libraries** and run +#####Get openssl-xcode project file - git clone https://github.com/telegramdesktop/openssl-xcode.git +From https://github.com/telegramdesktop/openssl-xcode with git in Terminal: -or download in ZIP and extract to **/Users/user/TBuild/Libraries**, rename **openssl-xcode-master** to **openssl-xcode** to have **/Users/user/TBuild/Libraries/openssl-xcode/openssl.xcodeproj** project +* go to **/Users/user/TBuild/Libraries +* run: + + git clone https://github.com/telegramdesktop/openssl-xcode.git -http://www.openssl.org/source/ > Download [**openssl-1.0.1h.tar.gz**](http://www.openssl.org/source/openssl-1.0.1h.tar.gz) (4.3 Mb) +or: -Extract openssl-1.0.1h.tar.gz and copy everything from **openssl-1.0.1h** to **/Users/user/TBuild/Libraries/openssl-xcode** to have **/Users/user/TBuild/Libraries/openssl-xcode/include** +* download in ZIP and extract to **/Users/user/TBuild/Libraries**, +* rename **openssl-xcode-master** to **openssl-xcode** + +The path to openssl.xcodeproj should now be: **/Users/user/TBuild/Libraries/openssl-xcode/openssl.xcodeproj** + +#####Get the source code: + +Download [**openssl-1.0.1h.tar.gz**](http://www.openssl.org/source/openssl-1.0.1h.tar.gz) (4.3 Mb) + +* Extract openssl-1.0.1h.tar.gz +* Copy everything from **openssl-1.0.1h** to **/Users/user/TBuild/Libraries/openssl-xcode** + +The folder include of openssl should be: +**/Users/user/TBuild/Libraries/openssl-xcode/include** #####Building library @@ -38,14 +62,15 @@ Extract openssl-1.0.1h.tar.gz and copy everything from **openssl-1.0.1h** to **/ * Product > Build ####liblzma +#####Get the source code -http://tukaani.org/xz/ > Download [**xz-5.0.5.tar.gz**](http://tukaani.org/xz/xz-5.0.5.tar.gz) +Download [**xz-5.0.5.tar.gz**](http://tukaani.org/xz/xz-5.0.5.tar.gz) Extract to **/Users/user/TBuild/Libraries** #####Building library -In Terminal go to **/Users/user/TBuild/Libraries/xz-5.0.5** and there run +In Terminal go to **/Users/user/TBuild/Libraries/xz-5.0.5** and there run: ./configure make @@ -53,15 +78,26 @@ In Terminal go to **/Users/user/TBuild/Libraries/xz-5.0.5** and there run ####zlib 1.2.8 -Using se system lib +Using the system lib ####libexif 0.6.20 +#####Get the source code -Get sources from https://github.com/telegramdesktop/libexif-0.6.20, by git – in Terminal go to **/Users/user/TBuild/Libraries** and run +From https://github.com/telegramdesktop/libexif-0.6.20 with git in Terminal: - git clone https://github.com/telegramdesktop/libexif-0.6.20.git +* go to **/Users/user/TBuild/Libraries** +* run: -or download in ZIP and extract to **/Users/user/TBuild/Libraries**, rename **libexif-0.6.20-master** to **libexif-0.6.20** to have **/Users/user/TBuild/Libraries/libexif-0.6.20/configure** script + git clone https://github.com/telegramdesktop/libexif-0.6.20.git + +or: + +* download in ZIP +* extract to **/Users/user/TBuild/Libraries** +* rename **libexif-0.6.20-master** to **libexif-0.6.20** + +The folder configure should have this path: +**/Users/user/TBuild/Libraries/libexif-0.6.20/configure** #####Building library @@ -88,40 +124,50 @@ In Terminal go to **/Users/user/TBuild/Libraries/openal-soft/build** and there r sudo make install ####Opus codec +#####Get the source code -Download sources [opus-1.1.tar.gz](http://downloads.xiph.org/releases/opus/opus-1.1.tar.gz) from http://www.opus-codec.org/downloads/, extract to **/Users/user/TBuild/Libraries** and rename to have **/Users/user/TBuild/Libraries/opus/configure** +* Download sources [opus-1.1.tar.gz](http://downloads.xiph.org/releases/opus/opus-1.1.tar.gz) from http://www.opus-codec.org/downloads/ +* Extract them to **/Users/user/TBuild/Libraries** +* Rename opus-1.1 to opus to have **/Users/user/TBuild/Libraries/opus/configure** -#####Building libraries +#####Building library -Download [pkg-config 0.28](http://pkgconfig.freedesktop.org/releases/pkg-config-0.28.tar.gz) from http://pkg-config.freedesktop.org, extract it to **/Users/user/TBuild/Libraries** +* Download [pkg-config 0.28](http://pkgconfig.freedesktop.org/releases/pkg-config-0.28.tar.gz) from http://pkg-config.freedesktop.org +* Extract it to **/Users/user/TBuild/Libraries** -In Terminal go to **/Users/user/TBuild/Libraries/pkg-config-0.28** and run +In Terminal go to **/Users/user/TBuild/Libraries/pkg-config-0.28** and run: ./configure --with-internal-glib make sudo make install -then go to **/Users/user/TBuild/Libraries/opus** and there run +then go to **/Users/user/TBuild/Libraries/opus** and run: ./configure make sudo make install -####FFmpeg +####FFmpeg and Libiconv +#####Get the source code -Download sources [ffmpeg-2.6.3.tar.bz2](http://ffmpeg.org/releases/ffmpeg-2.6.3.tar.bz2) from https://www.ffmpeg.org/download.html, extract to **/Users/user/TBuild/Libraries** to have **/Users/user/TBuild/Libraries/ffmpeg-2.6.3** +In Terminal go to **/Users/user/TBuild/Libraries** and run: -#####Building libraries + git clone https://github.com/FFmpeg/FFmpeg.git ffmpeg + cd ffmpeg + git checkout release/2.8 -Download [libiconv-1.14](http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.14.tar.gz) from http://www.gnu.org/software/libiconv/#downloading, extract it to **/Users/user/TBuild/Libraries** +* Download [libiconv-1.14](http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.14.tar.gz) from http://www.gnu.org/software/libiconv/#downloading +* Extract to **/Users/user/TBuild/Libraries** to have **/Users/user/TBuild/Libraries/ibiconv-1.14** -In Termianl go to **/Users/user/TBuild/Libraries/libiconv-1.14** and run +#####Building library + +In Terminal go to **/Users/user/TBuild/Libraries/libiconv-1.14** and run: ./configure --enable-static make sudo make install -Then in Terminal go to **/Users/user/TBuild/Libraries/ffmpeg-2.6.3** and run +Then in Terminal go to **/Users/user/TBuild/Libraries/ffmpeg** and run: ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" @@ -131,14 +177,15 @@ Then in Terminal go to **/Users/user/TBuild/Libraries/ffmpeg-2.6.3** and run LDFLAGS=`freetype-config --libs` PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig:/usr/X11/lib/pkgconfig - ./configure --prefix=/usr/local --disable-programs --disable-everything --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=wavpack --enable-decoder=opus --enable-decoder=vorbis --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-decoder=flac --enable-encoder=libopus --enable-parser=aac --enable-parser=aac_latm --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-parser=flac --enable-demuxer=aac --enable-demuxer=wav --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=mov --enable-demuxer=flac --enable-muxer=ogg --enable-muxer=opus --extra-cflags="-mmacosx-version-min=10.8" --extra-cxxflags="-mmacosx-version-min=10.8" --extra-ldflags="-mmacosx-version-min=10.8" + ./configure --prefix=/usr/local --disable-programs --disable-doc --disable-everything --disable-pthreads --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=flac --enable-decoder=gif --enable-decoder=h264 --enable-decoder=h264_vda --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=mpeg4 --enable-decoder=msmpeg4v2 --enable-decoder=msmpeg4v3 --enable-decoder=opus --enable-decoder=vorbis --enable-decoder=wavpack --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-encoder=libopus --enable-hwaccel=mpeg4_videotoolbox --enable-hwaccel=h264_vda --enable-hwaccel=h264_videotoolbox --enable-parser=aac --enable-parser=aac_latm --enable-parser=flac --enable-parser=h264 --enable-parser=mpeg4video --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-demuxer=aac --enable-demuxer=flac --enable-demuxer=gif --enable-demuxer=h264 --enable-demuxer=mov --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=wav --enable-muxer=ogg --enable-muxer=opus --extra-cflags="-mmacosx-version-min=10.8" --extra-cxxflags="-mmacosx-version-min=10.8" --extra-ldflags="-mmacosx-version-min=10.8" make sudo make install ####Qt 5.5.1, slightly patched +#####Get the source code -In Terminal go to **/Users/user/TBuild/Libraries** and run +In Terminal go to **/Users/user/TBuild/Libraries** and run: git clone git://code.qt.io/qt/qt5.git QtStatic cd QtStatic @@ -149,16 +196,18 @@ In Terminal go to **/Users/user/TBuild/Libraries** and run cd qtbase && git checkout v5.5.1 && cd .. #####Apply the patch +From **/Users/user/TBuild/Libraries/QtStatic/qtbase**, run: - cd qtbase && git apply ../../../tdesktop/Telegram/_qtbase_5_5_1_patch.diff && cd .. + git apply ../../../tdesktop/Telegram/_qtbase_5_5_1_patch.diff #####Building library +Go to **/Users/user/TBuild/Libraries/QtStatic** and run: ./configure -debug-and-release -opensource -confirm-license -static -opengl desktop -no-openssl -securetransport -nomake examples -nomake tests -platform macx-clang make -j4 sudo make -j4 install -building (**make** command) will take really long time. +Building (**make** command) will take a really long time. ###Building Telegram Desktop diff --git a/XCODEold.md b/XCODEold.md index 1649f61528..8decf2b10b 100644 --- a/XCODEold.md +++ b/XCODEold.md @@ -111,7 +111,11 @@ then go to **/Users/user/TBuild/Libraries/opus** and there run ####FFmpeg -Download sources [ffmpeg-2.6.3.tar.bz2](http://ffmpeg.org/releases/ffmpeg-2.6.3.tar.bz2) from https://www.ffmpeg.org/download.html, extract to **/Users/user/TBuild/Libraries** to have **/Users/user/TBuild/Libraries/ffmpeg-2.6.3** +In Terminal go to **/Users/user/TBuild/Libraries** and run: + + git clone https://github.com/FFmpeg/FFmpeg.git ffmpeg + cd ffmpeg + git checkout release/2.8 #####Building libraries @@ -123,7 +127,7 @@ In Termianl go to **/Users/user/TBuild/Libraries/libiconv-1.14** and run make sudo make install -Then in Terminal go to **/Users/user/TBuild/Libraries/ffmpeg-2.6.3** and run +Then in Terminal go to **/Users/user/TBuild/Libraries/ffmpeg** and run ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" @@ -133,7 +137,7 @@ Then in Terminal go to **/Users/user/TBuild/Libraries/ffmpeg-2.6.3** and run LDFLAGS=`freetype-config --libs` PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig:/usr/X11/lib/pkgconfig - ./configure --prefix=/usr/local --disable-programs --disable-everything --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=wavpack --enable-decoder=opus --enable-decoder=vorbis --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-decoder=flac --enable-encoder=libopus --enable-parser=aac --enable-parser=aac_latm --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-parser=flac --enable-demuxer=aac --enable-demuxer=wav --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=mov --enable-demuxer=flac --enable-muxer=ogg --enable-muxer=opus --extra-cflags="-mmacosx-version-min=10.6" --extra-cxxflags="-mmacosx-version-min=10.6" --extra-ldflags="-mmacosx-version-min=10.6" + ./configure --prefix=/usr/local/ffmpeg_old --disable-programs --disable-doc --disable-everything --disable-pthreads --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=flac --enable-decoder=gif --enable-decoder=h264 --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=mpeg4 --enable-decoder=msmpeg4v2 --enable-decoder=msmpeg4v3 --enable-decoder=opus --enable-decoder=vorbis --enable-decoder=wavpack --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-encoder=libopus --enable-parser=aac --enable-parser=aac_latm --enable-parser=flac --enable-parser=h264 --enable-parser=mpeg4video --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-demuxer=aac --enable-demuxer=flac --enable-demuxer=gif --enable-demuxer=h264 --enable-demuxer=mov --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=wav --enable-muxer=ogg --enable-muxer=opus --extra-cflags="-mmacosx-version-min=10.6" --extra-cxxflags="-mmacosx-version-min=10.6" --extra-ldflags="-mmacosx-version-min=10.6" make sudo make install