Merge branch 'profile' into drafts

Conflicts:
	Telegram/SourceFiles/app.cpp
	Telegram/SourceFiles/codegen/style/processor.cpp
	Telegram/SourceFiles/history.cpp
	Telegram/SourceFiles/historywidget.cpp
	Telegram/SourceFiles/mainwidget.cpp
	Telegram/SourceFiles/mainwidget.h
	Telegram/SourceFiles/profilewidget.cpp
	Telegram/SourceFiles/profilewidget.h
	Telegram/Telegram.vcxproj
	Telegram/Telegram.vcxproj.filters
This commit is contained in:
John Preston 2016-06-06 18:16:52 +03:00
commit 520260f207
165 changed files with 10452 additions and 4055 deletions

View file

@ -20,13 +20,13 @@ downloadLibs() {
info_msg "QT-Version: ${_qtver}, SRC-Dir: ${srcdir}" info_msg "QT-Version: ${_qtver}, SRC-Dir: ${srcdir}"
echo -e "Clone Qt\n" echo -e "Clone Qt\n"
git clone git://code.qt.io/qt/qt5.git qt5_6_0 git clone git://code.qt.io/qt/qt5.git qt${_qtver}
cd qt5_6_0 cd qt${_qtver}
git checkout 5.6 git checkout $(echo ${_qtver} | sed -e "s/\..$//")
perl init-repository --module-subset=qtbase,qtimageformats perl init-repository --module-subset=qtbase,qtimageformats
git checkout v5.6.0 git checkout v${_qtver}
cd qtbase && git checkout v5.6.0 && cd .. cd qtbase && git checkout v${_qtver} && cd ..
cd qtimageformats && git checkout v5.6.0 && cd .. cd qtimageformats && git checkout v${_qtver} && cd ..
cd .. cd ..
echo -e "Clone Breakpad\n" echo -e "Clone Breakpad\n"
@ -48,9 +48,9 @@ prepare() {
mkdir -p "$srcdir/Libraries" mkdir -p "$srcdir/Libraries"
ln -s "$srcdir/qt5_6_0" "$srcdir/Libraries/qt5_6_0" ln -s "$srcdir/qt${_qtver}" "$srcdir/Libraries/qt${_qtver}"
cd "$srcdir/Libraries/qt5_6_0/qtbase" cd "$srcdir/Libraries/qt${_qtver}/qtbase"
git apply "$srcdir/tdesktop/Telegram/Patches/qtbase_5_6_0.diff" git apply "$srcdir/tdesktop/Telegram/Patches/qtbase_$(echo ${_qtver} | sed -e "s/\./_/g").diff"
if [ ! -h "$srcdir/Libraries/breakpad" ]; then if [ ! -h "$srcdir/Libraries/breakpad" ]; then
ln -s "$srcdir/breakpad" "$srcdir/Libraries/breakpad" ln -s "$srcdir/breakpad" "$srcdir/Libraries/breakpad"
@ -60,7 +60,6 @@ prepare() {
sed -i 's/CUSTOM_API_ID//g' "$srcdir/tdesktop/Telegram/Telegram.pro" sed -i 's/CUSTOM_API_ID//g' "$srcdir/tdesktop/Telegram/Telegram.pro"
sed -i 's,LIBS += /usr/local/lib/libxkbcommon.a,,g' "$srcdir/tdesktop/Telegram/Telegram.pro" sed -i 's,LIBS += /usr/local/lib/libxkbcommon.a,,g' "$srcdir/tdesktop/Telegram/Telegram.pro"
sed -i 's,LIBS += /usr/local/lib/libz.a,LIBS += -lz,g' "$srcdir/tdesktop/Telegram/Telegram.pro" sed -i 's,LIBS += /usr/local/lib/libz.a,LIBS += -lz,g' "$srcdir/tdesktop/Telegram/Telegram.pro"
sed -i "s,/usr/local/tdesktop/Qt-5.6.0,$srcdir/qt,g" "$srcdir/tdesktop/Telegram/Telegram.pro"
local options="" local options=""
@ -98,7 +97,7 @@ build() {
info_msg "Build patched Qt" info_msg "Build patched Qt"
# Build patched Qt # Build patched Qt
cd "$srcdir/Libraries/qt5_6_0" cd "$srcdir/Libraries/qt${_qtver}"
./configure -prefix "$srcdir/qt" -release -opensource -confirm-license -qt-zlib \ ./configure -prefix "$srcdir/qt" -release -opensource -confirm-license -qt-zlib \
-qt-libpng -qt-libjpeg -qt-freetype -qt-harfbuzz -qt-pcre -qt-xcb \ -qt-libpng -qt-libjpeg -qt-freetype -qt-harfbuzz -qt-pcre -qt-xcb \
-qt-xkbcommon-x11 -no-opengl -static -nomake examples -nomake tests -qt-xkbcommon-x11 -no-opengl -static -nomake examples -nomake tests
@ -117,21 +116,21 @@ build() {
# Build codegen_style # Build codegen_style
mkdir -p "$srcdir/tdesktop/Linux/obj/codegen_style/Debug" mkdir -p "$srcdir/tdesktop/Linux/obj/codegen_style/Debug"
cd "$srcdir/tdesktop/Linux/obj/codegen_style/Debug" cd "$srcdir/tdesktop/Linux/obj/codegen_style/Debug"
qmake CONFIG+=debug "../../../../Telegram/build/qmake/codegen_style/codegen_style.pro" qmake QT_TDESKTOP_PATH=${srcdir}/qt QT_TDESKTOP_VERSION=${_qtver} CONFIG+=debug "../../../../Telegram/build/qmake/codegen_style/codegen_style.pro"
make --silent -j4 make --silent -j4
info_msg "Build codegen_numbers" info_msg "Build codegen_numbers"
# Build codegen_numbers # Build codegen_numbers
mkdir -p "$srcdir/tdesktop/Linux/obj/codegen_numbers/Debug" mkdir -p "$srcdir/tdesktop/Linux/obj/codegen_numbers/Debug"
cd "$srcdir/tdesktop/Linux/obj/codegen_numbers/Debug" cd "$srcdir/tdesktop/Linux/obj/codegen_numbers/Debug"
qmake CONFIG+=debug "../../../../Telegram/build/qmake/codegen_numbers/codegen_numbers.pro" qmake QT_TDESKTOP_PATH=${srcdir}/qt QT_TDESKTOP_VERSION=${_qtver} CONFIG+=debug "../../../../Telegram/build/qmake/codegen_numbers/codegen_numbers.pro"
make --silent -j4 make --silent -j4
info_msg "Build MetaLang" info_msg "Build MetaLang"
# Build MetaLang # Build MetaLang
mkdir -p "$srcdir/tdesktop/Linux/DebugIntermediateLang" mkdir -p "$srcdir/tdesktop/Linux/DebugIntermediateLang"
cd "$srcdir/tdesktop/Linux/DebugIntermediateLang" cd "$srcdir/tdesktop/Linux/DebugIntermediateLang"
qmake CONFIG+=debug "../../Telegram/MetaLang.pro" qmake QT_TDESKTOP_PATH=${srcdir}/qt QT_TDESKTOP_VERSION=${_qtver} CONFIG+=debug "../../Telegram/MetaLang.pro"
make --silent -j4 make --silent -j4
info_msg "Build Telegram Desktop" info_msg "Build Telegram Desktop"
@ -142,7 +141,7 @@ build() {
./../codegen/Debug/codegen_style "-I./../../Telegram/Resources" "-I./../../Telegram/SourceFiles" "-o./GeneratedFiles/styles" all_files.style --rebuild ./../codegen/Debug/codegen_style "-I./../../Telegram/Resources" "-I./../../Telegram/SourceFiles" "-o./GeneratedFiles/styles" all_files.style --rebuild
./../codegen/Debug/codegen_numbers "-o./GeneratedFiles" "./../../Telegram/Resources/numbers.txt" ./../codegen/Debug/codegen_numbers "-o./GeneratedFiles" "./../../Telegram/Resources/numbers.txt"
./../DebugLang/MetaLang -lang_in ./../../Telegram/Resources/langs/lang.strings -lang_out ./GeneratedFiles/lang_auto ./../DebugLang/MetaLang -lang_in ./../../Telegram/Resources/langs/lang.strings -lang_out ./GeneratedFiles/lang_auto
qmake CONFIG+=debug "../../Telegram/Telegram.pro" qmake QT_TDESKTOP_PATH=${srcdir}/qt QT_TDESKTOP_VERSION=${_qtver} CONFIG+=debug "../../Telegram/Telegram.pro"
make -j4 make -j4
} }

View file

@ -12,7 +12,7 @@ Cya='\e[0;36m'; BCya='\e[1;36m'; UCya='\e[4;36m'; ICya='\e[0;96m';
Whi='\e[0;37m'; BWhi='\e[1;37m'; UWhi='\e[4;37m'; IWhi='\e[0;97m'; BIWhi='\e[1;97m'; On_Whi='\e[47m'; On_IWhi='\e[0;107m'; Whi='\e[0;37m'; BWhi='\e[1;37m'; UWhi='\e[4;37m'; IWhi='\e[0;97m'; BIWhi='\e[1;97m'; On_Whi='\e[47m'; On_IWhi='\e[0;107m';
# Set variables # Set variables
_qtver=5.5.1 _qtver=5.6.0
srcdir=${PWD} srcdir=${PWD}
start_msg() { start_msg() {

View file

@ -11,7 +11,7 @@ The source code is published under GPLv3 with OpenSSL exception, the license is
* Windows XP - Windows 10 (**not** RT) * Windows XP - Windows 10 (**not** RT)
* Mac OS X 10.8 - Mac OS X 10.11 * Mac OS X 10.8 - Mac OS X 10.11
* Mac OS X 10.6 - Mac OS X 10.7 (separate build) * Mac OS X 10.6 - Mac OS X 10.7 (separate build)
* Ubuntu 12.04 - Ubuntu 15.04 * Ubuntu 12.04 - Ubuntu 16.04
* Fedora 22 * Fedora 22
## Third-party libraries ## Third-party libraries

View file

@ -27,7 +27,4 @@ HEADERS += \
./SourceFiles/_other/memain.h \ ./SourceFiles/_other/memain.h \
./SourceFiles/_other/genemoji.h \ ./SourceFiles/_other/genemoji.h \
INCLUDEPATH += ./../../Libraries/QtStatic/qtbase/include/QtGui/5.5.1/QtGui\ include(qt_static.pri)
./../../Libraries/QtStatic/qtbase/include/QtCore/5.5.1/QtCore\
./../../Libraries/QtStatic/qtbase/include\

View file

@ -27,7 +27,4 @@ HEADERS += \
./SourceFiles/_other/mlmain.h \ ./SourceFiles/_other/mlmain.h \
./SourceFiles/_other/genlang.h \ ./SourceFiles/_other/genlang.h \
INCLUDEPATH += ./../../Libraries/QtStatic/qtbase/include/QtGui/5.5.1/QtGui\ include(qt_static.pri)
./../../Libraries/QtStatic/qtbase/include/QtCore/5.5.1/QtCore\
./../../Libraries/QtStatic/qtbase/include\

View file

@ -35,8 +35,6 @@ unix {
} }
} }
INCLUDEPATH += ./../../Libraries/QtStatic/qtbase/include/QtGui/5.5.1/QtGui\ include(qt_static.pri)
./../../Libraries/QtStatic/qtbase/include/QtCore/5.5.1/QtCore\
./../../Libraries/QtStatic/qtbase/include
LIBS += -lcrypto -lssl -lz -llzma LIBS += -lcrypto -lssl -lz -llzma

View file

@ -24,3 +24,4 @@ using "basic_types.style";
using "basic.style"; using "basic.style";
using "overview/overview.style"; using "overview/overview.style";
using "profile/profile.style";

View file

@ -55,10 +55,15 @@ wndMinWidth: 380px;
adaptiveNormalWidth: 640px; adaptiveNormalWidth: 640px;
adaptiveWideWidth: 1366px; adaptiveWideWidth: 1366px;
windowBg: #fff; // fallback for background: white
windowTextFg: #000; // fallback for text color: black
windowSubTextFg: #8a8a8a; // fallback for subtext color: gray
windowActiveTextFg: #1485c2; // fallback for active color: blue online
windowShadowFg: #000; // fallback for shadow color
wndMinHeight: 480px; wndMinHeight: 480px;
wndDefWidth: 800px; wndDefWidth: 800px;
wndDefHeight: 600px; wndDefHeight: 600px;
wndBG: white;
wndShadow: sprite(209px, 46px, 19px, 19px); wndShadow: sprite(209px, 46px, 19px, 19px);
wndShadowShift: 1px; wndShadowShift: 1px;
@ -69,9 +74,10 @@ overBg: #edf2f5;
labelDefFlat: flatLabel { labelDefFlat: flatLabel {
font: font(fsize); font: font(fsize);
minWidth: 100px;
width: 0px; width: 0px;
maxHeight: 0px;
align: align(left); align: align(left);
textFg: windowTextFg;
} }
boxBg: white; boxBg: white;
@ -140,6 +146,29 @@ boxLabel: flatLabel(labelDefFlat) {
align: align(topleft); align: align(topleft);
} }
defaultLeftOutlineButton: OutlineButton {
outlineWidth: 3px;
outlineFg: windowBg;
outlineFgOver: #3fb0e4;
textBg: windowBg;
textBgOver: #f2f7fa;
textFg: windowActiveTextFg;
textFgOver: windowActiveTextFg;
font: normalFont;
padding: margins(11px, 6px, 11px, 6px);
}
attentionLeftOutlineButton: OutlineButton(defaultLeftOutlineButton) {
outlineFgOver: #e43f3f;
textBgOver: #faf2f2;
textFg: #d15948;
textFgOver: #d15948;
}
defaultInputArea: InputArea { defaultInputArea: InputArea {
textFg: black; textFg: black;
textMargins: margins(5px, 6px, 5px, 4px); textMargins: margins(5px, 6px, 5px, 4px);
@ -370,9 +399,10 @@ statusFont: font(fsize);
versionColor: #777; versionColor: #777;
shadowColor: rgba(0, 0, 0, 24); shadowColor: rgba(0, 0, 0, 24);
shadowToggleDuration: 200;
slideDuration: 240; slideDuration: 240;
slideShift: 0.3; slideShift: 100px;
slideFadeOut: 0.3; slideFadeOut: 0.3;
slideShadow: sprite(348px, 71px, 48px, 1px); slideShadow: sprite(348px, 71px, 48px, 1px);
slideFunction: transition(easeOutCirc); slideFunction: transition(easeOutCirc);
@ -1657,18 +1687,18 @@ confirmCompressedSkip: 10px;
profileMaxWidth: 410px; profileMaxWidth: 410px;
profilePadding: margins(28px, 30px, 28px, 0px); profilePadding: margins(28px, 30px, 28px, 0px);
profilePhotoSize: 120px; //profilePhotoSize: 120px;
profileNameLeft: 21px; //profileNameLeft: 21px;
profileNameTop: -1px; //profileNameTop: -1px;
profileNameFont: font(20px); //profileNameFont: font(20px);
profileStatusLeft: 22px; //profileStatusLeft: 22px;
profileStatusTop: 31px; //profileStatusTop: 31px;
profileStatusFont: font(fsize); //profileStatusFont: font(fsize);
profilePhoneLeft: 22px; profilePhoneLeft: 22px;
profilePhoneTop: 62px; profilePhoneTop: 62px;
profilePhoneFont: font(16px); profilePhoneFont: font(16px);
profileButtonTop: 18px; //profileButtonTop: 18px;
profileButtonSkip: 10px; //profileButtonSkip: 10px;
profileHeaderFont: font(20px); profileHeaderFont: font(20px);
profileHeaderColor: black; profileHeaderColor: black;
profileHeaderSkip: 59px; profileHeaderSkip: 59px;
@ -2444,7 +2474,7 @@ inlineResultsSkip: 3px;
inlineMediaHeight: 96px; inlineMediaHeight: 96px;
inlineThumbSize: 64px; inlineThumbSize: 64px;
inlineThumbSkip: 10px; inlineThumbSkip: 10px;
inlineDescriptionFg: #8a8a8a; inlineDescriptionFg: windowSubTextFg;
inlineRowMargin: 6px; inlineRowMargin: 6px;
inlineRowBorder: 1px; inlineRowBorder: 1px;
inlineRowBorderFg: #eaeaea; inlineRowBorderFg: #eaeaea;

View file

@ -210,9 +210,11 @@ slider {
flatLabel { flatLabel {
font: font; font: font;
minWidth: pixels; margin: margins;
width: pixels; width: pixels;
align: align; align: align;
textFg: color;
maxHeight: pixels;
} }
switcher { switcher {
@ -306,6 +308,8 @@ BoxButton {
textTop: pixels; textTop: pixels;
icon: icon;
font: font; font: font;
duration: int; duration: int;
} }
@ -411,3 +415,18 @@ PeerAvatarButton {
size: pixels; size: pixels;
photoSize: pixels; photoSize: pixels;
} }
OutlineButton {
outlineWidth: pixels;
outlineFg: color;
outlineFgOver: color;
textBg: color;
textBgOver: color;
textFg: color;
textFgOver: color;
font: font;
padding: margins;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 592 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

View file

@ -117,8 +117,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_channel_status" = "channel"; "lng_channel_status" = "channel";
"lng_group_status" = "group"; "lng_group_status" = "group";
"lng_channel_members_link" = "{count:_not_used_|# member|# members} »"; "lng_channel_members_link" = "{count:_not_used_|# member|# members}";
"lng_channel_admins_link" = "{count:Manage administrators|# administrator|# administrators} »"; "lng_channel_admins_link" = "{count:_not_used_|# administrator|# administrators}";
"lng_server_error" = "Internal server error."; "lng_server_error" = "Internal server error.";
"lng_flood_error" = "Too many tries. Please try again later."; "lng_flood_error" = "Too many tries. Please try again later.";
@ -405,12 +405,15 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_profile_actions_section" = "Actions"; "lng_profile_actions_section" = "Actions";
"lng_profile_bot_settings" = "Settings"; "lng_profile_bot_settings" = "Settings";
"lng_profile_bot_help" = "Help"; "lng_profile_bot_help" = "Help";
"lng_profile_invite_link_section" = "Invite link";
"lng_profile_create_public_link" = "Create public link"; "lng_profile_create_public_link" = "Create public link";
"lng_profile_edit_public_link" = "Edit public link"; "lng_profile_edit_public_link" = "Edit public link";
"lng_profile_manage_admins" = "Manage administrators";
"lng_profile_participants_section" = "Members"; "lng_profile_participants_section" = "Members";
"lng_profile_info" = "Contact info"; "lng_profile_info_section" = "Info";
"lng_profile_group_info" = "Group info"; "lng_profile_mobile_number" = "Mobile:";
"lng_profile_channel_info" = "Channel info"; "lng_profile_username" = "Username:";
"lng_profile_link" = "Link:";
"lng_profile_add_contact" = "Add Contact"; "lng_profile_add_contact" = "Add Contact";
"lng_profile_edit_contact" = "Edit"; "lng_profile_edit_contact" = "Edit";
"lng_profile_enable_notifications" = "Notifications"; "lng_profile_enable_notifications" = "Notifications";
@ -421,6 +424,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_profile_delete_channel" = "Delete channel"; "lng_profile_delete_channel" = "Delete channel";
"lng_profile_leave_group" = "Leave group"; "lng_profile_leave_group" = "Leave group";
"lng_profile_delete_group" = "Delete group"; "lng_profile_delete_group" = "Delete group";
"lng_profile_report" = "Report";
"lng_profile_search_messages" = "Search for messages"; "lng_profile_search_messages" = "Search for messages";
"lng_profile_block_user" = "Block user"; "lng_profile_block_user" = "Block user";
"lng_profile_unblock_user" = "Unblock user"; "lng_profile_unblock_user" = "Unblock user";
@ -432,6 +436,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_profile_delete_contact" = "Delete"; "lng_profile_delete_contact" = "Delete";
"lng_profile_set_group_photo" = "Set Photo"; "lng_profile_set_group_photo" = "Set Photo";
"lng_profile_add_participant" = "Add Members"; "lng_profile_add_participant" = "Add Members";
"lng_profile_view_channel" = "View Channel";
"lng_profile_join_channel" = "Join";
"lng_profile_delete_and_exit" = "Leave"; "lng_profile_delete_and_exit" = "Leave";
"lng_profile_kick" = "Remove"; "lng_profile_kick" = "Remove";
"lng_profile_admin" = "admin"; "lng_profile_admin" = "admin";
@ -441,20 +447,33 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_profile_loading" = "Loading..."; "lng_profile_loading" = "Loading...";
"lng_profile_shared_media" = "Shared media"; "lng_profile_shared_media" = "Shared media";
"lng_profile_no_media" = "No media in this conversation."; "lng_profile_no_media" = "No media in this conversation.";
"lng_profile_photos" = "{count:_not_used_|# photo|# photos} »"; "lng_profile_photos" = "{count:_not_used_|# photo|# photos}";
"lng_profile_photos_header" = "Photos overview"; "lng_profile_photos_header" = "Photos overview";
"lng_profile_videos" = "{count:_not_used_|# video file|# video files} »"; "lng_profile_videos" = "{count:_not_used_|# video file|# video files}";
"lng_profile_videos_header" = "Video files overview"; "lng_profile_videos_header" = "Video files overview";
"lng_profile_songs" = "{count:_not_used_|# audio file|# audio files} »"; "lng_profile_songs" = "{count:_not_used_|# audio file|# audio files}";
"lng_profile_songs_header" = "Audio files overview"; "lng_profile_songs_header" = "Audio files overview";
"lng_profile_files" = "{count:_not_used_|# file|# files} »"; "lng_profile_files" = "{count:_not_used_|# file|# files}";
"lng_profile_files_header" = "Files overview"; "lng_profile_files_header" = "Files overview";
"lng_profile_audios" = "{count:_not_used_|# voice message|# voice messages} »"; "lng_profile_audios" = "{count:_not_used_|# voice message|# voice messages}";
"lng_profile_audios_header" = "Voice messages overview"; "lng_profile_audios_header" = "Voice messages overview";
"lng_profile_shared_links" = "{count:_not_used_|# shared link|# shared links} »"; "lng_profile_shared_links" = "{count:_not_used_|# shared link|# shared links}";
"lng_profile_shared_links_header" = "Shared links overview"; "lng_profile_shared_links_header" = "Shared links overview";
"lng_profile_copy_phone" = "Copy phone number"; "lng_profile_copy_phone" = "Copy phone number";
"lng_profile_copy_fullname" = "Copy name"; "lng_profile_copy_fullname" = "Copy name";
"lng_profile_drop_area_title" = "Drop your image here";
"lng_profile_drop_area_subtitle" = "to set it as a group photo";
"lng_profile_drop_area_subtitle_channel" = "to set it as a channel photo";
"lng_profile_top_bar_share_contact" = "Share";
"lng_report_title" = "Report channel";
"lng_report_reason_spam" = "Spam";
"lng_report_reason_violence" = "Violence";
"lng_report_reason_pornography" = "Pornography";
"lng_report_reason_other" = "Other";
"lng_report_reason_description" = "Description";
"lng_report_button" = "Report";
"lng_report_thanks" = "Thank you! Your report will be reviewed by our team very soon.";
"lng_channel_add_admins" = "New administrator"; "lng_channel_add_admins" = "New administrator";
"lng_channel_add_members" = "Add members"; "lng_channel_add_members" = "Add members";
@ -550,6 +569,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_action_pinned_media_sticker" = "a sticker"; "lng_action_pinned_media_sticker" = "a sticker";
"lng_profile_migrate_reached" = "{count:_not_used_|# member|# members} limit reached"; "lng_profile_migrate_reached" = "{count:_not_used_|# member|# members} limit reached";
"lng_profile_migrate_body" = "To get over this limit, you can upgrade your group to a supergroup.";
"lng_profile_migrate_learn_more" = "Learn more »";
"lng_profile_migrate_about" = "If you'd like to go over this limit, you can upgrade your group to a supergroup. In supergroups:"; "lng_profile_migrate_about" = "If you'd like to go over this limit, you can upgrade your group to a supergroup. In supergroups:";
"lng_profile_migrate_feature1" = "— The members limit is {count:_not_used_|# user|# users}"; "lng_profile_migrate_feature1" = "— The members limit is {count:_not_used_|# user|# users}";
"lng_profile_migrate_feature2" = "— New members see the entire chat history"; "lng_profile_migrate_feature2" = "— New members see the entire chat history";
@ -583,7 +604,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_group_invite_create" = "Create an invite link"; "lng_group_invite_create" = "Create an invite link";
"lng_group_invite_about" = "Telegram users will be able to join\nyour group by following this link."; "lng_group_invite_about" = "Telegram users will be able to join\nyour group by following this link.";
"lng_group_invite_create_new" = "Revoke invite link"; "lng_group_invite_create_new" = "Revoke invite link";
"lng_group_invite_about_new" = "Your previous link will be deactivated\nand we'll generate a new invite link for you."; "lng_group_invite_about_new" = "Your previous link will be deactivated and we'll generate a new invite link for you.";
"lng_group_invite_copied" = "Invite link copied to clipboard."; "lng_group_invite_copied" = "Invite link copied to clipboard.";
"lng_group_invite_no_room" = "Unable to join this group because there are too many members in it already."; "lng_group_invite_no_room" = "Unable to join this group because there are too many members in it already.";
@ -902,6 +923,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
// Not used // Not used
"lng_topbar_info" = "Info"; "lng_topbar_info" = "Info";
"lng_profile_group_info" = "Group info";
"lng_profile_channel_info" = "Channel info";
// Wnd specific // Wnd specific

View file

@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 0,9,49,0 FILEVERSION 0,9,49,1
PRODUCTVERSION 0,9,49,0 PRODUCTVERSION 0,9,49,1
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -51,10 +51,10 @@ BEGIN
BLOCK "040904b0" BLOCK "040904b0"
BEGIN BEGIN
VALUE "CompanyName", "Telegram Messenger LLP" VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileVersion", "0.9.49.0" VALUE "FileVersion", "0.9.49.1"
VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "LegalCopyright", "Copyright (C) 2014-2016"
VALUE "ProductName", "Telegram Desktop" VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "0.9.49.0" VALUE "ProductVersion", "0.9.49.1"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 0,9,49,0 FILEVERSION 0,9,49,1
PRODUCTVERSION 0,9,49,0 PRODUCTVERSION 0,9,49,1
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -43,10 +43,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Telegram Messenger LLP" VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileDescription", "Telegram Updater" VALUE "FileDescription", "Telegram Updater"
VALUE "FileVersion", "0.9.49.0" VALUE "FileVersion", "0.9.49.1"
VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "LegalCopyright", "Copyright (C) 2014-2016"
VALUE "ProductName", "Telegram Desktop" VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "0.9.49.0" VALUE "ProductVersion", "0.9.49.1"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "stdafx.h" #include "stdafx.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "observer_peer.h"
#include "lang.h" #include "lang.h"
#include "application.h" #include "application.h"
#include "mainwindow.h" #include "mainwindow.h"
@ -96,6 +97,10 @@ void ApiWrap::resolveMessageDatas() {
} }
} }
void ApiWrap::updatesReceived(const MTPUpdates &updates) {
App::main()->sentUpdatesReceived(updates);
}
void ApiWrap::gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &msgs, mtpRequestId req) { void ApiWrap::gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &msgs, mtpRequestId req) {
switch (msgs.type()) { switch (msgs.type()) {
case mtpc_messages_messages: { case mtpc_messages_messages: {
@ -174,8 +179,8 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt
badVersion = (!vc.isEmpty() && vc.at(0).type() == mtpc_channel && vc.at(0).c_channel().vversion.v < peer->asChannel()->version); badVersion = (!vc.isEmpty() && vc.at(0).type() == mtpc_channel && vc.at(0).c_channel().vversion.v < peer->asChannel()->version);
} }
App::feedUsers(d.vusers, false); App::feedUsers(d.vusers);
App::feedChats(d.vchats, false); App::feedChats(d.vchats);
if (peer->isChat()) { if (peer->isChat()) {
if (d.vfull_chat.type() != mtpc_chatFull) { if (d.vfull_chat.type() != mtpc_chatFull) {
@ -206,9 +211,9 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt
} else { } else {
chat->photoId = 0; chat->photoId = 0;
} }
chat->invitationUrl = (f.vexported_invite.type() == mtpc_chatInviteExported) ? qs(f.vexported_invite.c_chatInviteExported().vlink) : QString(); chat->setInviteLink((f.vexported_invite.type() == mtpc_chatInviteExported) ? qs(f.vexported_invite.c_chatInviteExported().vlink) : QString());
App::main()->gotNotifySetting(MTP_inputNotifyPeer(peer->input), f.vnotify_settings); notifySettingReceived(MTP_inputNotifyPeer(peer->input), f.vnotify_settings);
} else if (peer->isChannel()) { } else if (peer->isChannel()) {
if (d.vfull_chat.type() != mtpc_channelFull) { if (d.vfull_chat.type() != mtpc_channelFull) {
LOG(("MTP Error: bad type in gotChatFull for channel: %1").arg(d.vfull_chat.type())); LOG(("MTP Error: bad type in gotChatFull for channel: %1").arg(d.vfull_chat.type()));
@ -217,6 +222,10 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt
auto &f(d.vfull_chat.c_channelFull()); auto &f(d.vfull_chat.c_channelFull());
PhotoData *photo = App::feedPhoto(f.vchat_photo); PhotoData *photo = App::feedPhoto(f.vchat_photo);
ChannelData *channel = peer->asChannel(); ChannelData *channel = peer->asChannel();
auto canViewAdmins = channel->canViewAdmins();
auto canViewMembers = channel->canViewMembers();
channel->flagsFull = f.vflags.v; channel->flagsFull = f.vflags.v;
if (photo) { if (photo) {
channel->photoId = photo->id; channel->photoId = photo->id;
@ -267,17 +276,10 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt
} break; } break;
} }
} }
channel->about = qs(f.vabout); channel->setAbout(qs(f.vabout));
int32 newCount = f.has_participants_count() ? f.vparticipants_count.v : 0; channel->setMembersCount(f.has_participants_count() ? f.vparticipants_count.v : 0);
if (newCount != channel->count) { channel->setAdminsCount(f.has_admins_count() ? f.vadmins_count.v : 0);
if (channel->isMegagroup() && !channel->mgInfo->lastParticipants.isEmpty()) { channel->setInviteLink((f.vexported_invite.type() == mtpc_chatInviteExported) ? qs(f.vexported_invite.c_chatInviteExported().vlink) : QString());
channel->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated;
channel->mgInfo->lastParticipantsCount = channel->count;
}
channel->count = newCount;
}
channel->adminsCount = f.has_admins_count() ? f.vadmins_count.v : 0;
channel->invitationUrl = (f.vexported_invite.type() == mtpc_chatInviteExported) ? qs(f.vexported_invite.c_chatInviteExported().vlink) : QString();
if (History *h = App::historyLoaded(channel->id)) { if (History *h = App::historyLoaded(channel->id)) {
if (h->inboxReadBefore < f.vread_inbox_max_id.v + 1) { if (h->inboxReadBefore < f.vread_inbox_max_id.v + 1) {
h->setUnreadCount(f.vunread_count.v); h->setUnreadCount(f.vunread_count.v);
@ -294,7 +296,10 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt
} }
channel->fullUpdated(); channel->fullUpdated();
App::main()->gotNotifySetting(MTP_inputNotifyPeer(peer->input), f.vnotify_settings); if (canViewAdmins != channel->canViewAdmins()) Notify::peerUpdatedDelayed(channel, Notify::PeerUpdate::Flag::ChannelCanViewAdmins);
if (canViewMembers != channel->canViewMembers()) Notify::peerUpdatedDelayed(channel, Notify::PeerUpdate::Flag::ChannelCanViewMembers);
notifySettingReceived(MTP_inputNotifyPeer(peer->input), f.vnotify_settings);
} }
if (req) { if (req) {
@ -313,18 +318,17 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt
} }
App::clearPeerUpdated(peer); App::clearPeerUpdated(peer);
emit fullPeerUpdated(peer); emit fullPeerUpdated(peer);
App::emitPeerUpdated();
} }
void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result, mtpRequestId req) { void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result, mtpRequestId req) {
const auto &d(result.c_userFull()); const auto &d(result.c_userFull());
App::feedUsers(MTP_vector<MTPUser>(1, d.vuser), false); App::feedUsers(MTP_vector<MTPUser>(1, d.vuser));
if (d.has_profile_photo()) { if (d.has_profile_photo()) {
App::feedPhoto(d.vprofile_photo); App::feedPhoto(d.vprofile_photo);
} }
App::feedUserLink(MTP_int(peerToUser(peer->id)), d.vlink.c_contacts_link().vmy_link, d.vlink.c_contacts_link().vforeign_link, false); App::feedUserLink(MTP_int(peerToUser(peer->id)), d.vlink.c_contacts_link().vmy_link, d.vlink.c_contacts_link().vforeign_link);
if (App::main()) { if (App::main()) {
App::main()->gotNotifySetting(MTP_inputNotifyPeer(peer->input), d.vnotify_settings); notifySettingReceived(MTP_inputNotifyPeer(peer->input), d.vnotify_settings);
} }
if (d.has_bot_info()) { if (d.has_bot_info()) {
@ -332,8 +336,8 @@ void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result, mtpRequestI
} else { } else {
peer->asUser()->setBotInfoVersion(-1); peer->asUser()->setBotInfoVersion(-1);
} }
peer->asUser()->blocked = d.is_blocked() ? UserIsBlocked : UserIsNotBlocked; peer->asUser()->setBlockStatus(d.is_blocked() ? UserData::BlockStatus::Blocked : UserData::BlockStatus::NotBlocked);
peer->asUser()->about = d.has_about() ? qs(d.vabout) : QString(); peer->asUser()->setAbout(d.has_about() ? qs(d.vabout) : QString());
if (req) { if (req) {
QMap<PeerData*, mtpRequestId>::iterator i = _fullPeerRequests.find(peer); QMap<PeerData*, mtpRequestId>::iterator i = _fullPeerRequests.find(peer);
@ -343,7 +347,6 @@ void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result, mtpRequestI
} }
App::clearPeerUpdated(peer); App::clearPeerUpdated(peer);
emit fullPeerUpdated(peer); emit fullPeerUpdated(peer);
App::emitPeerUpdated();
} }
bool ApiWrap::gotPeerFullFailed(PeerData *peer, const RPCError &error) { bool ApiWrap::gotPeerFullFailed(PeerData *peer, const RPCError &error) {
@ -538,14 +541,19 @@ void ApiWrap::lastParticipantsDone(ChannelData *peer, const MTPchannels_ChannelP
h->clearLastKeyboard(); h->clearLastKeyboard();
if (App::main()) App::main()->updateBotKeyboard(h); if (App::main()) App::main()->updateBotKeyboard(h);
} }
if (d.vcount.v > peer->count) { int newMembersCount = qMax(d.vcount.v, v.count());
peer->count = d.vcount.v; if (newMembersCount > peer->membersCount()) {
} else if (v.count() > peer->count) { peer->setMembersCount(newMembersCount);
peer->count = v.count();
} }
if (!bots && v.isEmpty()) { if (!bots) {
peer->count = peer->mgInfo->lastParticipants.size(); if (v.isEmpty()) {
peer->setMembersCount(peer->mgInfo->lastParticipants.size());
}
Notify::PeerUpdate update(peer);
update.flags |= Notify::PeerUpdate::Flag::MembersChanged | Notify::PeerUpdate::Flag::AdminsChanged;
Notify::peerUpdatedDelayed(update);
} }
peer->mgInfo->botStatus = botStatus; peer->mgInfo->botStatus = botStatus;
if (App::main()) emit fullPeerUpdated(peer); if (App::main()) emit fullPeerUpdated(peer);
} }
@ -626,27 +634,33 @@ void ApiWrap::kickParticipant(PeerData *peer, UserData *user) {
void ApiWrap::kickParticipantDone(KickRequest kick, const MTPUpdates &result, mtpRequestId req) { void ApiWrap::kickParticipantDone(KickRequest kick, const MTPUpdates &result, mtpRequestId req) {
_kickRequests.remove(kick); _kickRequests.remove(kick);
if (kick.first->isMegagroup()) { if (kick.first->isMegagroup()) {
int32 i = kick.first->asChannel()->mgInfo->lastParticipants.indexOf(kick.second); auto channel = kick.first->asChannel();
auto megagroupInfo = channel->mgInfo;
int32 i = megagroupInfo->lastParticipants.indexOf(kick.second);
if (i >= 0) { if (i >= 0) {
kick.first->asChannel()->mgInfo->lastParticipants.removeAt(i); megagroupInfo->lastParticipants.removeAt(i);
} }
if (kick.first->asChannel()->count > 1) {
--kick.first->asChannel()->count; if (channel->membersCount() > 1) {
channel->setMembersCount(channel->membersCount() - 1);
} else { } else {
kick.first->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated; megagroupInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated;
kick.first->asChannel()->mgInfo->lastParticipantsCount = 0; megagroupInfo->lastParticipantsCount = 0;
} }
if (kick.first->asChannel()->mgInfo->lastAdmins.contains(kick.second)) { if (megagroupInfo->lastAdmins.contains(kick.second)) {
kick.first->asChannel()->mgInfo->lastAdmins.remove(kick.second); megagroupInfo->lastAdmins.remove(kick.second);
if (kick.first->asChannel()->adminsCount > 1) { if (channel->adminsCount() > 1) {
--kick.first->asChannel()->adminsCount; channel->setAdminsCount(channel->adminsCount() - 1);
} }
Notify::peerUpdatedDelayed(channel, Notify::PeerUpdate::Flag::AdminsChanged);
} }
kick.first->asChannel()->mgInfo->bots.remove(kick.second); megagroupInfo->bots.remove(kick.second);
if (kick.first->asChannel()->mgInfo->bots.isEmpty() && kick.first->asChannel()->mgInfo->botStatus > 0) { if (megagroupInfo->bots.isEmpty() && megagroupInfo->botStatus > 0) {
kick.first->asChannel()->mgInfo->botStatus = -1; megagroupInfo->botStatus = -1;
} }
} }
Notify::peerUpdatedDelayed(kick.first, Notify::PeerUpdate::Flag::MembersChanged);
emit fullPeerUpdated(kick.first); emit fullPeerUpdated(kick.first);
} }
@ -672,6 +686,156 @@ void ApiWrap::requestStickerSets() {
} }
} }
void ApiWrap::joinChannel(ChannelData *channel) {
if (channel->amIn()) {
channelAmInUpdated(channel);
} else if (!_channelAmInRequests.contains(channel)) {
auto requestId = MTP::send(MTPchannels_JoinChannel(channel->inputChannel), rpcDone(&ApiWrap::channelAmInDone, channel), rpcFail(&ApiWrap::channelAmInFail, channel));
_channelAmInRequests.insert(channel, requestId);
}
}
void ApiWrap::leaveChannel(ChannelData *channel) {
if (!channel->amIn()) {
channelAmInUpdated(channel);
} else if (!_channelAmInRequests.contains(channel)) {
auto requestId = MTP::send(MTPchannels_LeaveChannel(channel->inputChannel), rpcDone(&ApiWrap::channelAmInDone, channel), rpcFail(&ApiWrap::channelAmInFail, channel));
_channelAmInRequests.insert(channel, requestId);
}
}
void ApiWrap::channelAmInUpdated(ChannelData *channel) {
Notify::peerUpdatedDelayed(channel, Notify::PeerUpdate::Flag::ChannelAmIn);
}
void ApiWrap::channelAmInDone(ChannelData *channel, const MTPUpdates &updates) {
_channelAmInRequests.remove(channel);
updatesReceived(updates);
}
bool ApiWrap::channelAmInFail(ChannelData *channel, const RPCError &error) {
if (MTP::isDefaultHandledError(error)) return false;
_channelAmInRequests.remove(channel);
return true;
}
void ApiWrap::blockUser(UserData *user) {
if (user->isBlocked()) {
Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserIsBlocked);
} else if (!_blockRequests.contains(user)) {
auto requestId = MTP::send(MTPcontacts_Block(user->inputUser), rpcDone(&ApiWrap::blockDone, user), rpcFail(&ApiWrap::blockFail, user));
_blockRequests.insert(user, requestId);
}
}
void ApiWrap::unblockUser(UserData *user) {
if (!user->isBlocked()) {
Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserIsBlocked);
} else if (!_blockRequests.contains(user)) {
auto requestId = MTP::send(MTPcontacts_Unblock(user->inputUser), rpcDone(&ApiWrap::unblockDone, user), rpcFail(&ApiWrap::blockFail, user));
_blockRequests.insert(user, requestId);
}
}
void ApiWrap::blockDone(UserData *user, const MTPBool &result) {
_blockRequests.remove(user);
user->setBlockStatus(UserData::BlockStatus::Blocked);
emit App::main()->peerUpdated(user);
}
void ApiWrap::unblockDone(UserData *user, const MTPBool &result) {
_blockRequests.remove(user);
user->setBlockStatus(UserData::BlockStatus::NotBlocked);
emit App::main()->peerUpdated(user);
}
bool ApiWrap::blockFail(UserData *user, const RPCError &error) {
if (MTP::isDefaultHandledError(error)) return false;
_blockRequests.remove(user);
return true;
}
void ApiWrap::exportInviteLink(PeerData *peer) {
if (_exportInviteRequests.contains(peer)) {
return;
}
mtpRequestId request = 0;
if (auto chat = peer->asChat()) {
request = MTP::send(MTPmessages_ExportChatInvite(chat->inputChat), rpcDone(&ApiWrap::exportInviteDone, peer), rpcFail(&ApiWrap::exportInviteFail, peer));
} else if (auto channel = peer->asChannel()) {
request = MTP::send(MTPchannels_ExportInvite(channel->inputChannel), rpcDone(&ApiWrap::exportInviteDone, peer), rpcFail(&ApiWrap::exportInviteFail, peer));
}
if (request) {
_exportInviteRequests.insert(peer, request);
}
}
void ApiWrap::exportInviteDone(PeerData *peer, const MTPExportedChatInvite &result) {
_exportInviteRequests.remove(peer);
if (auto chat = peer->asChat()) {
chat->setInviteLink((result.type() == mtpc_chatInviteExported) ? qs(result.c_chatInviteExported().vlink) : QString());
} else if (auto channel = peer->asChannel()) {
channel->setInviteLink((result.type() == mtpc_chatInviteExported) ? qs(result.c_chatInviteExported().vlink) : QString());
}
}
bool ApiWrap::exportInviteFail(PeerData *peer, const RPCError &error) {
if (MTP::isDefaultHandledError(error)) return false;
_exportInviteRequests.remove(peer);
return true;
}
void ApiWrap::requestNotifySetting(PeerData *peer) {
if (_notifySettingRequests.contains(peer)) return;
MTPInputNotifyPeer notifyPeer = MTP_inputNotifyPeer(peer->input);
auto requestId = MTP::send(MTPaccount_GetNotifySettings(notifyPeer), rpcDone(&ApiWrap::notifySettingDone, notifyPeer), rpcFail(&ApiWrap::notifySettingFail, peer));
_notifySettingRequests.insert(peer, requestId);
}
void ApiWrap::notifySettingDone(MTPInputNotifyPeer notifyPeer, const MTPPeerNotifySettings &result) {
if (auto requestedPeer = notifySettingReceived(notifyPeer, result)) {
_notifySettingRequests.remove(requestedPeer);
}
}
PeerData *ApiWrap::notifySettingReceived(MTPInputNotifyPeer notifyPeer, const MTPPeerNotifySettings &settings) {
PeerData *requestedPeer = nullptr;
switch (notifyPeer.type()) {
case mtpc_inputNotifyAll: App::main()->applyNotifySetting(MTP_notifyAll(), settings); break;
case mtpc_inputNotifyUsers: App::main()->applyNotifySetting(MTP_notifyUsers(), settings); break;
case mtpc_inputNotifyChats: App::main()->applyNotifySetting(MTP_notifyChats(), settings); break;
case mtpc_inputNotifyPeer: {
auto &peer = notifyPeer.c_inputNotifyPeer().vpeer;
switch (peer.type()) {
case mtpc_inputPeerEmpty: App::main()->applyNotifySetting(MTP_notifyPeer(MTP_peerUser(MTP_int(0))), settings); break;
case mtpc_inputPeerSelf: requestedPeer = App::self(); break;
case mtpc_inputPeerUser: requestedPeer = App::user(peerFromUser(peer.c_inputPeerUser().vuser_id)); break;
case mtpc_inputPeerChat: requestedPeer = App::chat(peerFromChat(peer.c_inputPeerChat().vchat_id)); break;
case mtpc_inputPeerChannel: requestedPeer = App::channel(peerFromChannel(peer.c_inputPeerChannel().vchannel_id)); break;
}
if (requestedPeer) {
App::main()->applyNotifySetting(MTP_notifyPeer(peerToMTP(requestedPeer->id)), settings);
}
} break;
}
App::wnd()->notifySettingGot();
return requestedPeer;
}
bool ApiWrap::notifySettingFail(PeerData *peer, const RPCError &error) {
if (MTP::isDefaultHandledError(error)) return false;
notifySettingReceived(MTP_inputNotifyPeer(peer->input), MTP_peerNotifySettingsEmpty());
_notifySettingRequests.remove(peer);
return true;
}
void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) { void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) {
_stickerSetRequests.remove(setId); _stickerSetRequests.remove(setId);

View file

@ -28,7 +28,7 @@ public:
ApiWrap(QObject *parent); ApiWrap(QObject *parent);
void init(); void init();
typedef SharedCallback<void, ChannelData*, MsgId> RequestMessageDataCallback; using RequestMessageDataCallback = SharedCallback<void, ChannelData*, MsgId>;
void requestMessageData(ChannelData *channel, MsgId msgId, std_::unique_ptr<RequestMessageDataCallback> callback); void requestMessageData(ChannelData *channel, MsgId msgId, std_::unique_ptr<RequestMessageDataCallback> callback);
void requestFullPeer(PeerData *peer); void requestFullPeer(PeerData *peer);
@ -50,6 +50,15 @@ public:
void scheduleStickerSetRequest(uint64 setId, uint64 access); void scheduleStickerSetRequest(uint64 setId, uint64 access);
void requestStickerSets(); void requestStickerSets();
void joinChannel(ChannelData *channel);
void leaveChannel(ChannelData *channel);
void blockUser(UserData *user);
void unblockUser(UserData *user);
void exportInviteLink(PeerData *peer);
void requestNotifySetting(PeerData *peer);
~ApiWrap(); ~ApiWrap();
signals: signals:
@ -65,6 +74,8 @@ public slots:
private: private:
void updatesReceived(const MTPUpdates &updates);
void gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId req); void gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId req);
struct MessageDataRequest { struct MessageDataRequest {
MessageDataRequest() : req(0) { MessageDataRequest() : req(0) {
@ -120,4 +131,24 @@ private:
void gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result); void gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result);
bool gotStickerSetFail(uint64 setId, const RPCError &error); bool gotStickerSetFail(uint64 setId, const RPCError &error);
QMap<ChannelData*, mtpRequestId> _channelAmInRequests;
void channelAmInUpdated(ChannelData *channel);
void channelAmInDone(ChannelData *channel, const MTPUpdates &updates);
bool channelAmInFail(ChannelData *channel, const RPCError &error);
QMap<UserData*, mtpRequestId> _blockRequests;
void blockDone(UserData *user, const MTPBool &result);
void unblockDone(UserData *user, const MTPBool &result);
bool blockFail(UserData *user, const RPCError &error);
QMap<PeerData*, mtpRequestId> _exportInviteRequests;
void exportInviteDone(PeerData *peer, const MTPExportedChatInvite &result);
bool exportInviteFail(PeerData *peer, const RPCError &error);
QMap<PeerData*, mtpRequestId> _notifySettingRequests;
void notifySettingDone(MTPInputNotifyPeer peer, const MTPPeerNotifySettings &settings);
PeerData *notifySettingReceived(MTPInputNotifyPeer peer, const MTPPeerNotifySettings &settings);
bool notifySettingFail(PeerData *peer, const RPCError &error);
}; };

View file

@ -35,6 +35,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "localstorage.h" #include "localstorage.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "numbers.h" #include "numbers.h"
#include "observer_peer.h"
namespace { namespace {
App::LaunchState _launchState = App::Launched; App::LaunchState _launchState = App::Launched;
@ -222,11 +223,11 @@ namespace {
} }
} }
int32 onlineForSort(UserData *user, int32 now) { TimeId onlineForSort(UserData *user, TimeId now) {
if (isServiceUser(user->id) || user->botInfo) { if (isServiceUser(user->id) || user->botInfo) {
return -1; return -1;
} }
int32 online = user->onlineTill; TimeId online = user->onlineTill;
if (online <= 0) { if (online <= 0) {
switch (online) { switch (online) {
case 0: case 0:
@ -252,11 +253,14 @@ namespace {
return online; return online;
} }
int32 onlineWillChangeIn(UserData *user, int32 now) { int32 onlineWillChangeIn(UserData *user, TimeId now) {
if (isServiceUser(user->id) || user->botInfo) { if (isServiceUser(user->id) || user->botInfo) {
return 86400; return 86400;
} }
int32 online = user->onlineTill; return onlineWillChangeIn(user->onlineTill, now);
}
int32 onlineWillChangeIn(TimeId online, TimeId now) {
if (online <= 0) { if (online <= 0) {
if (-online > now) return -online - now; if (-online > now) return -online - now;
return 86400; return 86400;
@ -276,7 +280,7 @@ namespace {
return dNow.secsTo(dTomorrow); return dNow.secsTo(dTomorrow);
} }
QString onlineText(UserData *user, int32 now, bool precise) { QString onlineText(UserData *user, TimeId now, bool precise) {
if (isNotificationsUser(user->id)) { if (isNotificationsUser(user->id)) {
return lang(lng_status_service_notifications); return lang(lng_status_service_notifications);
} else if (isServiceUser(user->id)) { } else if (isServiceUser(user->id)) {
@ -284,7 +288,10 @@ namespace {
} else if (user->botInfo) { } else if (user->botInfo) {
return lang(lng_status_bot); return lang(lng_status_bot);
} }
int32 online = user->onlineTill; return onlineText(user->onlineTill, now, precise);
}
QString onlineText(TimeId online, TimeId now, bool precise) {
if (online <= 0) { if (online <= 0) {
switch (online) { switch (online) {
case 0: return lang(lng_status_offline); case 0: return lang(lng_status_offline);
@ -346,11 +353,14 @@ namespace {
} }
} }
bool onlineColorUse(UserData *user, int32 now) { bool onlineColorUse(UserData *user, TimeId now) {
if (isServiceUser(user->id) || user->botInfo) { if (isServiceUser(user->id) || user->botInfo) {
return false; return false;
} }
int32 online = user->onlineTill; return onlineColorUse(user->onlineTill, now);
}
bool onlineColorUse(TimeId online, TimeId now) {
if (online <= 0) { if (online <= 0) {
switch (online) { switch (online) {
case 0: case 0:
@ -364,21 +374,25 @@ namespace {
return (online > now); return (online > now);
} }
UserData *feedUsers(const MTPVector<MTPUser> &users, bool emitPeerUpdated) { UserData *feedUsers(const MTPVector<MTPUser> &users) {
UserData *data = 0; UserData *result = nullptr;
const auto &v(users.c_vector().v); for_const (auto &user, users.c_vector().v) {
for (QVector<MTPUser>::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) { UserData *data = nullptr;
const auto &user(*i);
data = 0;
bool wasContact = false, minimal = false; bool wasContact = false, minimal = false;
const MTPUserStatus *status = 0, emptyStatus = MTP_userStatusEmpty(); const MTPUserStatus *status = 0, emptyStatus = MTP_userStatusEmpty();
Notify::PeerUpdate update;
using UpdateFlag = Notify::PeerUpdate::Flag;
switch (user.type()) { switch (user.type()) {
case mtpc_userEmpty: { case mtpc_userEmpty: {
const auto &d(user.c_userEmpty()); const auto &d(user.c_userEmpty());
PeerId peer(peerFromUser(d.vid.v)); PeerId peer(peerFromUser(d.vid.v));
data = App::user(peer); data = App::user(peer);
auto canShareThisContact = data->canShareThisContactFast();
wasContact = data->isContact();
data->input = MTP_inputPeerUser(d.vid, MTP_long(0)); data->input = MTP_inputPeerUser(d.vid, MTP_long(0));
data->inputUser = MTP_inputUser(d.vid, MTP_long(0)); data->inputUser = MTP_inputUser(d.vid, MTP_long(0));
data->setName(lang(lng_deleted), QString(), QString(), QString()); data->setName(lang(lng_deleted), QString(), QString(), QString());
@ -386,9 +400,11 @@ namespace {
data->access = UserNoAccess; data->access = UserNoAccess;
data->flags = 0; data->flags = 0;
data->setBotInfoVersion(-1); data->setBotInfoVersion(-1);
wasContact = (data->contact > 0);
status = &emptyStatus; status = &emptyStatus;
data->contact = -1; data->contact = -1;
if (canShareThisContact != data->canShareThisContactFast()) update.flags |= UpdateFlag::UserCanShareContact;
if (wasContact != data->isContact()) update.flags |= UpdateFlag::UserIsContact;
} break; } break;
case mtpc_user: { case mtpc_user: {
const auto &d(user.c_user()); const auto &d(user.c_user());
@ -396,6 +412,8 @@ namespace {
PeerId peer(peerFromUser(d.vid.v)); PeerId peer(peerFromUser(d.vid.v));
data = App::user(peer); data = App::user(peer);
auto canShareThisContact = data->canShareThisContactFast();
wasContact = data->isContact();
if (!minimal) { if (!minimal) {
data->flags = d.vflags.v; data->flags = d.vflags.v;
if (d.is_self()) { if (d.is_self()) {
@ -415,7 +433,10 @@ namespace {
} }
} }
if (d.is_deleted()) { if (d.is_deleted()) {
data->setPhone(QString()); if (!data->phone().isEmpty()) {
data->setPhone(QString());
update.flags |= UpdateFlag::UserPhoneChanged;
}
data->setName(lang(lng_deleted), QString(), QString(), QString()); data->setName(lang(lng_deleted), QString(), QString(), QString());
data->setPhoto(MTP_userProfilePhotoEmpty()); data->setPhoto(MTP_userProfilePhotoEmpty());
data->access = UserNoAccess; data->access = UserNoAccess;
@ -427,12 +448,14 @@ namespace {
QString fname = (!minimal || noLocalName) ? (d.has_first_name() ? textOneLine(qs(d.vfirst_name)) : QString()) : data->firstName; QString fname = (!minimal || noLocalName) ? (d.has_first_name() ? textOneLine(qs(d.vfirst_name)) : QString()) : data->firstName;
QString lname = (!minimal || noLocalName) ? (d.has_last_name() ? textOneLine(qs(d.vlast_name)) : QString()) : data->lastName; QString lname = (!minimal || noLocalName) ? (d.has_last_name() ? textOneLine(qs(d.vlast_name)) : QString()) : data->lastName;
QString phone = minimal ? data->phone : (d.has_phone() ? qs(d.vphone) : QString()); QString phone = minimal ? data->phone() : (d.has_phone() ? qs(d.vphone) : QString());
QString uname = minimal ? data->username : (d.has_username() ? textOneLine(qs(d.vusername)) : QString()); QString uname = minimal ? data->username : (d.has_username() ? textOneLine(qs(d.vusername)) : QString());
bool phoneChanged = (data->phone != phone); bool phoneChanged = (data->phone() != phone);
if (phoneChanged) data->setPhone(phone); if (phoneChanged) {
data->setPhone(phone);
update.flags |= UpdateFlag::UserPhoneChanged;
}
bool nameChanged = (data->firstName != fname) || (data->lastName != lname); bool nameChanged = (data->firstName != fname) || (data->lastName != lname);
bool showPhone = !isServiceUser(data->id) && !d.is_self() && !d.is_contact() && !d.is_mutual_contact(); bool showPhone = !isServiceUser(data->id) && !d.is_self() && !d.is_contact() && !d.is_mutual_contact();
@ -458,7 +481,6 @@ namespace {
if (d.has_access_hash()) data->access = d.vaccess_hash.v; if (d.has_access_hash()) data->access = d.vaccess_hash.v;
status = d.has_status() ? &d.vstatus : &emptyStatus; status = d.has_status() ? &d.vstatus : &emptyStatus;
} }
wasContact = (data->contact > 0);
if (!minimal) { if (!minimal) {
if (d.has_bot_info_version()) { if (d.has_bot_info_version()) {
data->setBotInfoVersion(d.vbot_info_version.v); data->setBotInfoVersion(d.vbot_info_version.v);
@ -468,7 +490,7 @@ namespace {
} else { } else {
data->setBotInfoVersion(-1); data->setBotInfoVersion(-1);
} }
data->contact = (d.is_contact() || d.is_mutual_contact()) ? 1 : (data->phone.isEmpty() ? -1 : 0); data->contact = (d.is_contact() || d.is_mutual_contact()) ? 1 : (data->phone().isEmpty() ? -1 : 0);
if (data->contact == 1 && cReportSpamStatuses().value(data->id, dbiprsHidden) != dbiprsHidden) { if (data->contact == 1 && cReportSpamStatuses().value(data->id, dbiprsHidden) != dbiprsHidden) {
cRefReportSpamStatuses().insert(data->id, dbiprsHidden); cRefReportSpamStatuses().insert(data->id, dbiprsHidden);
Local::writeReportSpamStatuses(); Local::writeReportSpamStatuses();
@ -478,6 +500,9 @@ namespace {
if (App::wnd()) App::wnd()->updateGlobalMenu(); if (App::wnd()) App::wnd()->updateGlobalMenu();
} }
} }
if (canShareThisContact != data->canShareThisContactFast()) update.flags |= UpdateFlag::UserCanShareContact;
if (wasContact != data->isContact()) update.flags |= UpdateFlag::UserIsContact;
} break; } break;
} }
@ -490,6 +515,8 @@ namespace {
} else if (data->loadedStatus != PeerData::FullLoaded) { } else if (data->loadedStatus != PeerData::FullLoaded) {
data->loadedStatus = PeerData::FullLoaded; data->loadedStatus = PeerData::FullLoaded;
} }
auto oldOnlineTill = data->onlineTill;
if (status && !minimal) switch (status->type()) { if (status && !minimal) switch (status->type()) {
case mtpc_userStatusEmpty: data->onlineTill = 0; break; case mtpc_userStatusEmpty: data->onlineTill = 0; break;
case mtpc_userStatusRecently: case mtpc_userStatusRecently:
@ -502,8 +529,11 @@ namespace {
case mtpc_userStatusOffline: data->onlineTill = status->c_userStatusOffline().vwas_online.v; break; case mtpc_userStatusOffline: data->onlineTill = status->c_userStatusOffline().vwas_online.v; break;
case mtpc_userStatusOnline: data->onlineTill = status->c_userStatusOnline().vexpires.v; break; case mtpc_userStatusOnline: data->onlineTill = status->c_userStatusOnline().vexpires.v; break;
} }
if (oldOnlineTill != data->onlineTill) {
update.flags |= UpdateFlag::UserOnlineChanged;
}
if (data->contact < 0 && !data->phone.isEmpty() && peerToUser(data->id) != MTP::authedId()) { if (data->contact < 0 && !data->phone().isEmpty() && peerToUser(data->id) != MTP::authedId()) {
data->contact = 0; data->contact = 0;
} }
if (App::main()) { if (App::main()) {
@ -511,34 +541,42 @@ namespace {
Notify::userIsContactChanged(data); Notify::userIsContactChanged(data);
} }
if (emitPeerUpdated) { markPeerUpdated(data);
App::main()->peerUpdated(data); if (update.flags) {
} else { update.peer = data;
markPeerUpdated(data); Notify::peerUpdatedDelayed(update);
} }
} }
result = data;
} }
return data; return result;
} }
PeerData *feedChats(const MTPVector<MTPChat> &chats, bool emitPeerUpdated) { PeerData *feedChats(const MTPVector<MTPChat> &chats) {
PeerData *data = 0; PeerData *result = nullptr;
const auto &v(chats.c_vector().v); for_const (auto &chat, chats.c_vector().v) {
for (QVector<MTPChat>::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) { PeerData *data = nullptr;
const auto &chat(*i);
data = 0;
bool minimal = false; bool minimal = false;
Notify::PeerUpdate update;
using UpdateFlag = Notify::PeerUpdate::Flag;
switch (chat.type()) { switch (chat.type()) {
case mtpc_chat: { case mtpc_chat: {
const auto &d(chat.c_chat()); auto &d(chat.c_chat());
data = App::chat(peerFromChat(d.vid.v)); data = App::chat(peerFromChat(d.vid.v));
auto cdata = data->asChat();
auto canEdit = cdata->canEdit();
if (cdata->version < d.vversion.v) {
cdata->version = d.vversion.v;
cdata->invalidateParticipants();
}
data->input = MTP_inputPeerChat(d.vid); data->input = MTP_inputPeerChat(d.vid);
cdata->setName(qs(d.vtitle));
data->updateName(qs(d.vtitle), QString(), QString());
ChatData *cdata = data->asChat();
cdata->setPhoto(d.vphoto); cdata->setPhoto(d.vphoto);
cdata->date = d.vdate.v; cdata->date = d.vdate.v;
@ -571,9 +609,11 @@ namespace {
} }
} }
Notify::migrateUpdated(channel); Notify::migrateUpdated(channel);
update.flags |= UpdateFlag::MigrationChanged;
} }
if (updatedTo) { if (updatedTo) {
Notify::migrateUpdated(cdata); Notify::migrateUpdated(cdata);
update.flags |= UpdateFlag::MigrationChanged;
} }
} }
@ -584,43 +624,52 @@ namespace {
cdata->count = d.vparticipants_count.v; cdata->count = d.vparticipants_count.v;
cdata->isForbidden = false; cdata->isForbidden = false;
if (cdata->version < d.vversion.v) { if (canEdit != cdata->canEdit()) {
cdata->version = d.vversion.v; update.flags |= UpdateFlag::ChatCanEdit;
cdata->invalidateParticipants();
} }
} break; } break;
case mtpc_chatForbidden: { case mtpc_chatForbidden: {
const auto &d(chat.c_chatForbidden()); auto &d(chat.c_chatForbidden());
data = App::chat(peerFromChat(d.vid.v)); data = App::chat(peerFromChat(d.vid.v));
auto cdata = data->asChat();
auto canEdit = cdata->canEdit();
data->input = MTP_inputPeerChat(d.vid); data->input = MTP_inputPeerChat(d.vid);
cdata->setName(qs(d.vtitle));
data->updateName(qs(d.vtitle), QString(), QString());
ChatData *cdata = data->asChat();
cdata->setPhoto(MTP_chatPhotoEmpty()); cdata->setPhoto(MTP_chatPhotoEmpty());
cdata->date = 0; cdata->date = 0;
cdata->count = -1; cdata->count = -1;
cdata->invalidateParticipants(); cdata->invalidateParticipants();
cdata->flags = 0; cdata->flags = 0;
cdata->isForbidden = true; cdata->isForbidden = true;
if (canEdit != cdata->canEdit()) {
update.flags |= UpdateFlag::ChatCanEdit;
}
} break; } break;
case mtpc_channel: { case mtpc_channel: {
const auto &d(chat.c_channel()); auto &d(chat.c_channel());
PeerId peer(peerFromChannel(d.vid.v)); auto peerId = peerFromChannel(d.vid.v);
minimal = d.is_min(); minimal = d.is_min();
if (minimal) { if (minimal) {
data = App::channelLoaded(peer); data = App::channelLoaded(peerId);
if (!data) { if (!data) {
continue; // minimal is not loaded, need to make getDifference continue; // minimal is not loaded, need to make getDifference
} }
} else { } else {
data = App::channel(peer); data = App::channel(peerId);
data->input = MTP_inputPeerChannel(d.vid, d.has_access_hash() ? d.vaccess_hash : MTP_long(0)); data->input = MTP_inputPeerChannel(d.vid, d.has_access_hash() ? d.vaccess_hash : MTP_long(0));
} }
ChannelData *cdata = data->asChannel(); auto cdata = data->asChannel();
auto wasInChannel = cdata->amIn();
auto canEditPhoto = cdata->canEditPhoto();
auto canViewAdmins = cdata->canViewAdmins();
auto canViewMembers = cdata->canViewMembers();
auto canAddMembers = cdata->canAddMembers();
auto wasEditor = cdata->amEditor();
if (minimal) { if (minimal) {
auto mask = MTPDchannel::Flag::f_broadcast | MTPDchannel::Flag::f_verified | MTPDchannel::Flag::f_megagroup | MTPDchannel::Flag::f_democracy; auto mask = MTPDchannel::Flag::f_broadcast | MTPDchannel::Flag::f_verified | MTPDchannel::Flag::f_megagroup | MTPDchannel::Flag::f_democracy;
cdata->flags = (cdata->flags & ~mask) | (d.vflags.v & mask); cdata->flags = (cdata->flags & ~mask) | (d.vflags.v & mask);
@ -644,15 +693,32 @@ namespace {
cdata->isForbidden = false; cdata->isForbidden = false;
cdata->flagsUpdated(); cdata->flagsUpdated();
cdata->setPhoto(d.vphoto); cdata->setPhoto(d.vphoto);
if (wasInChannel != cdata->amIn()) update.flags |= UpdateFlag::ChannelAmIn;
if (canEditPhoto != cdata->canEditPhoto()) update.flags |= UpdateFlag::ChannelCanEditPhoto;
if (canViewAdmins != cdata->canViewAdmins()) update.flags |= UpdateFlag::ChannelCanViewAdmins;
if (canViewMembers != cdata->canViewMembers()) update.flags |= UpdateFlag::ChannelCanViewMembers;
if (canAddMembers != cdata->canAddMembers()) update.flags |= UpdateFlag::ChannelCanAddMembers;
if (wasEditor != cdata->amEditor()) {
cdata->selfAdminUpdated();
update.flags |= (UpdateFlag::ChannelAmEditor | UpdateFlag::AdminsChanged);
}
} break; } break;
case mtpc_channelForbidden: { case mtpc_channelForbidden: {
const auto &d(chat.c_channelForbidden()); auto &d(chat.c_channelForbidden());
PeerId peer(peerFromChannel(d.vid.v)); auto peerId = peerFromChannel(d.vid.v);
data = App::channel(peer); data = App::channel(peerId);
data->input = MTP_inputPeerChannel(d.vid, d.vaccess_hash); data->input = MTP_inputPeerChannel(d.vid, d.vaccess_hash);
ChannelData *cdata = data->asChannel(); auto cdata = data->asChannel();
auto wasInChannel = cdata->amIn();
auto canEditPhoto = cdata->canEditPhoto();
auto canViewAdmins = cdata->canViewAdmins();
auto canViewMembers = cdata->canViewMembers();
auto canAddMembers = cdata->canAddMembers();
auto wasEditor = cdata->amEditor();
cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash); cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash);
auto mask = mtpCastFlags(MTPDchannelForbidden::Flag::f_broadcast | MTPDchannelForbidden::Flag::f_megagroup); auto mask = mtpCastFlags(MTPDchannelForbidden::Flag::f_broadcast | MTPDchannelForbidden::Flag::f_megagroup);
@ -663,9 +729,19 @@ namespace {
cdata->access = d.vaccess_hash.v; cdata->access = d.vaccess_hash.v;
cdata->setPhoto(MTP_chatPhotoEmpty()); cdata->setPhoto(MTP_chatPhotoEmpty());
cdata->date = 0; cdata->date = 0;
cdata->count = 0; cdata->setMembersCount(0);
cdata->isForbidden = true; cdata->isForbidden = true;
cdata->flagsUpdated(); cdata->flagsUpdated();
if (wasInChannel != cdata->amIn()) update.flags |= UpdateFlag::ChannelAmIn;
if (canEditPhoto != cdata->canEditPhoto()) update.flags |= UpdateFlag::ChannelCanEditPhoto;
if (canViewAdmins != cdata->canViewAdmins()) update.flags |= UpdateFlag::ChannelCanViewAdmins;
if (canViewMembers != cdata->canViewMembers()) update.flags |= UpdateFlag::ChannelCanViewMembers;
if (canAddMembers != cdata->canAddMembers()) update.flags |= UpdateFlag::ChannelCanAddMembers;
if (wasEditor != cdata->amEditor()) {
cdata->selfAdminUpdated();
update.flags |= (UpdateFlag::ChannelAmEditor | UpdateFlag::AdminsChanged);
}
} break; } break;
} }
if (!data) continue; if (!data) continue;
@ -678,14 +754,15 @@ namespace {
data->loadedStatus = PeerData::FullLoaded; data->loadedStatus = PeerData::FullLoaded;
} }
if (App::main()) { if (App::main()) {
if (emitPeerUpdated) { markPeerUpdated(data);
App::main()->peerUpdated(data); if (update.flags) {
} else { update.peer = data;
markPeerUpdated(data); Notify::peerUpdatedDelayed(update);
} }
} }
result = data;
} }
return data; return result;
} }
void feedParticipants(const MTPChatParticipants &p, bool requestBotInfos, bool emitPeerUpdated) { void feedParticipants(const MTPChatParticipants &p, bool requestBotInfos, bool emitPeerUpdated) {
@ -701,6 +778,7 @@ namespace {
case mtpc_chatParticipants: { case mtpc_chatParticipants: {
const auto &d(p.c_chatParticipants()); const auto &d(p.c_chatParticipants());
chat = App::chat(d.vchat_id.v); chat = App::chat(d.vchat_id.v);
auto canEdit = chat->canEdit();
if (!requestBotInfos || chat->version <= d.vversion.v) { // !requestBotInfos is true on getFullChat result if (!requestBotInfos || chat->version <= d.vversion.v) { // !requestBotInfos is true on getFullChat result
chat->version = d.vversion.v; chat->version = d.vversion.v;
const auto &v(d.vparticipants.c_vector().v); const auto &v(d.vparticipants.c_vector().v);
@ -772,8 +850,12 @@ namespace {
} }
} }
} }
if (canEdit != chat->canEdit()) {
Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::ChatCanEdit);
}
} break; } break;
} }
Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::MembersChanged | Notify::PeerUpdate::Flag::AdminsChanged);
if (chat && App::main()) { if (chat && App::main()) {
if (emitPeerUpdated) { if (emitPeerUpdated) {
App::main()->peerUpdated(chat); App::main()->peerUpdated(chat);
@ -820,6 +902,7 @@ namespace {
chat->invalidateParticipants(); chat->invalidateParticipants();
chat->count++; chat->count++;
} }
Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::MembersChanged);
if (App::main()) { if (App::main()) {
if (emitPeerUpdated) { if (emitPeerUpdated) {
App::main()->peerUpdated(chat); App::main()->peerUpdated(chat);
@ -845,6 +928,7 @@ namespace {
} }
} else if (chat->version <= d.vversion.v && chat->count > 0) { } else if (chat->version <= d.vversion.v && chat->count > 0) {
chat->version = d.vversion.v; chat->version = d.vversion.v;
auto canEdit = chat->canEdit();
UserData *user = App::userLoaded(d.vuser_id.v); UserData *user = App::userLoaded(d.vuser_id.v);
if (user) { if (user) {
if (chat->participants.isEmpty()) { if (chat->participants.isEmpty()) {
@ -886,6 +970,10 @@ namespace {
chat->invalidateParticipants(); chat->invalidateParticipants();
chat->count--; chat->count--;
} }
if (canEdit != chat->canEdit()) {
Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::ChatCanEdit);
}
Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::MembersChanged);
if (App::main()) { if (App::main()) {
if (emitPeerUpdated) { if (emitPeerUpdated) {
App::main()->peerUpdated(chat); App::main()->peerUpdated(chat);
@ -906,13 +994,14 @@ namespace {
} }
chat->version = d.vversion.v; chat->version = d.vversion.v;
if (mtpIsTrue(d.venabled)) { if (mtpIsTrue(d.venabled)) {
chat->flags |= MTPDchat::Flag::f_admins_enabled;
if (!badVersion) { if (!badVersion) {
chat->invalidateParticipants(); chat->invalidateParticipants();
} }
chat->flags |= MTPDchat::Flag::f_admins_enabled;
} else { } else {
chat->flags &= ~MTPDchat::Flag::f_admins_enabled; chat->flags &= ~MTPDchat::Flag::f_admins_enabled;
} }
Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::AdminsChanged);
if (emitPeerUpdated) { if (emitPeerUpdated) {
App::main()->peerUpdated(chat); App::main()->peerUpdated(chat);
} else { } else {
@ -936,6 +1025,7 @@ namespace {
} }
} else if (chat->version <= d.vversion.v && chat->count > 0) { } else if (chat->version <= d.vversion.v && chat->count > 0) {
chat->version = d.vversion.v; chat->version = d.vversion.v;
auto canEdit = chat->canEdit();
UserData *user = App::userLoaded(d.vuser_id.v); UserData *user = App::userLoaded(d.vuser_id.v);
if (user) { if (user) {
if (mtpIsTrue(d.vis_admin)) { if (mtpIsTrue(d.vis_admin)) {
@ -956,6 +1046,10 @@ namespace {
} else { } else {
chat->invalidateParticipants(); chat->invalidateParticipants();
} }
if (canEdit != chat->canEdit()) {
Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::ChatCanEdit);
}
Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::AdminsChanged);
if (App::main()) { if (App::main()) {
if (emitPeerUpdated) { if (emitPeerUpdated) {
App::main()->peerUpdated(chat); App::main()->peerUpdated(chat);
@ -1163,33 +1257,10 @@ namespace {
} }
} }
void feedUserLinks(const MTPVector<MTPcontacts_Link> &links, bool emitPeerUpdated) { void feedUserLink(MTPint userId, const MTPContactLink &myLink, const MTPContactLink &foreignLink) {
const auto &v(links.c_vector().v);
for (QVector<MTPcontacts_Link>::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) {
const auto &dv(i->c_contacts_link());
UserData *user = feedUsers(MTP_vector<MTPUser>(1, dv.vuser), false);
MTPint userId(MTP_int(0));
switch (dv.vuser.type()) {
case mtpc_userEmpty: userId = dv.vuser.c_userEmpty().vid; break;
case mtpc_user: userId = dv.vuser.c_user().vid; break;
}
if (userId.v) {
feedUserLink(userId, dv.vmy_link, dv.vforeign_link, false);
}
if (user && App::main()) {
if (emitPeerUpdated) {
App::main()->peerUpdated(user);
} else {
markPeerUpdated(user);
}
}
}
}
void feedUserLink(MTPint userId, const MTPContactLink &myLink, const MTPContactLink &foreignLink, bool emitPeerUpdated) {
UserData *user = userLoaded(userId.v); UserData *user = userLoaded(userId.v);
if (user) { if (user) {
bool wasContact = (user->contact > 0); auto wasContact = user->isContact();
bool wasShowPhone = !user->contact; bool wasShowPhone = !user->contact;
switch (myLink.type()) { switch (myLink.type()) {
case mtpc_contactLinkContact: case mtpc_contactLinkContact:
@ -1208,10 +1279,14 @@ namespace {
break; break;
} }
if (user->contact < 1) { if (user->contact < 1) {
if (user->contact < 0 && !user->phone.isEmpty() && peerToUser(user->id) != MTP::authedId()) { if (user->contact < 0 && !user->phone().isEmpty() && peerToUser(user->id) != MTP::authedId()) {
user->contact = 0; user->contact = 0;
} }
} }
if (wasContact != user->isContact()) {
Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserIsContact);
}
if ((user->contact > 0 && !wasContact) || (wasContact && user->contact < 1)) { if ((user->contact > 0 && !wasContact) || (wasContact && user->contact < 1)) {
Notify::userIsContactChanged(user); Notify::userIsContactChanged(user);
} }
@ -1219,15 +1294,9 @@ namespace {
bool showPhone = !isServiceUser(user->id) && !user->isSelf() && !user->contact; bool showPhone = !isServiceUser(user->id) && !user->isSelf() && !user->contact;
bool showPhoneChanged = !isServiceUser(user->id) && !user->isSelf() && ((showPhone && !wasShowPhone) || (!showPhone && wasShowPhone)); bool showPhoneChanged = !isServiceUser(user->id) && !user->isSelf() && ((showPhone && !wasShowPhone) || (!showPhone && wasShowPhone));
if (showPhoneChanged) { if (showPhoneChanged) {
user->setName(textOneLine(user->firstName), textOneLine(user->lastName), showPhone ? App::formatPhone(user->phone) : QString(), textOneLine(user->username)); user->setName(textOneLine(user->firstName), textOneLine(user->lastName), showPhone ? App::formatPhone(user->phone()) : QString(), textOneLine(user->username));
}
if (App::main()) {
if (emitPeerUpdated) {
App::main()->peerUpdated(user);
} else {
markPeerUpdated(user);
}
} }
markPeerUpdated(user);
} }
} }
@ -2272,6 +2341,10 @@ namespace {
return result; return result;
} }
QPixmap pixmapFromImageInPlace(QImage &&image) {
return QPixmap::fromImage(std_::forward<QImage>(image), Qt::ColorOnly);
}
void regPhotoItem(PhotoData *data, HistoryItem *item) { void regPhotoItem(PhotoData *data, HistoryItem *item) {
::photoItems[data].insert(item, NullType()); ::photoItems[data].insert(item, NullType());
} }
@ -2317,11 +2390,21 @@ namespace {
} }
void regSharedContactItem(int32 userId, HistoryItem *item) { void regSharedContactItem(int32 userId, HistoryItem *item) {
auto user = App::userLoaded(userId);
auto canShareThisContact = user ? user->canShareThisContact() : false;
::sharedContactItems[userId].insert(item, NullType()); ::sharedContactItems[userId].insert(item, NullType());
if (canShareThisContact != (user ? user->canShareThisContact() : false)) {
Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserCanShareContact);
}
} }
void unregSharedContactItem(int32 userId, HistoryItem *item) { void unregSharedContactItem(int32 userId, HistoryItem *item) {
auto user = App::userLoaded(userId);
auto canShareThisContact = user ? user->canShareThisContact() : false;
::sharedContactItems[userId].remove(item); ::sharedContactItems[userId].remove(item);
if (canShareThisContact != (user ? user->canShareThisContact() : false)) {
Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserCanShareContact);
}
} }
const SharedContactItems &sharedContactItems() { const SharedContactItems &sharedContactItems() {

View file

@ -59,13 +59,17 @@ namespace App {
QString formatPhone(QString phone); QString formatPhone(QString phone);
int32 onlineForSort(UserData *user, int32 now); TimeId onlineForSort(UserData *user, TimeId now);
int32 onlineWillChangeIn(UserData *user, int32 nowOnServer); int32 onlineWillChangeIn(UserData *user, TimeId now);
QString onlineText(UserData *user, int32 nowOnServer, bool precise = false); int32 onlineWillChangeIn(TimeId online, TimeId now);
bool onlineColorUse(UserData *user, int32 now); QString onlineText(UserData *user, TimeId now, bool precise = false);
QString onlineText(TimeId online, TimeId now, bool precise = false);
bool onlineColorUse(UserData *user, TimeId now);
bool onlineColorUse(TimeId online, TimeId now);
UserData *feedUsers(const MTPVector<MTPUser> &users); // returns last user
PeerData *feedChats(const MTPVector<MTPChat> &chats); // returns last chat
UserData *feedUsers(const MTPVector<MTPUser> &users, bool emitPeerUpdated = true); // returns last user
PeerData *feedChats(const MTPVector<MTPChat> &chats, bool emitPeerUpdated = true); // returns last chat
void feedParticipants(const MTPChatParticipants &p, bool requestBotInfos, bool emitPeerUpdated = true); void feedParticipants(const MTPChatParticipants &p, bool requestBotInfos, bool emitPeerUpdated = true);
void feedParticipantAdd(const MTPDupdateChatParticipantAdd &d, bool emitPeerUpdated = true); void feedParticipantAdd(const MTPDupdateChatParticipantAdd &d, bool emitPeerUpdated = true);
void feedParticipantDelete(const MTPDupdateChatParticipantDelete &d, bool emitPeerUpdated = true); void feedParticipantDelete(const MTPDupdateChatParticipantDelete &d, bool emitPeerUpdated = true);
@ -80,12 +84,10 @@ namespace App {
void feedInboxRead(const PeerId &peer, MsgId upTo); void feedInboxRead(const PeerId &peer, MsgId upTo);
void feedOutboxRead(const PeerId &peer, MsgId upTo); void feedOutboxRead(const PeerId &peer, MsgId upTo);
void feedWereDeleted(ChannelId channelId, const QVector<MTPint> &msgsIds); void feedWereDeleted(ChannelId channelId, const QVector<MTPint> &msgsIds);
void feedUserLinks(const MTPVector<MTPcontacts_Link> &links, bool emitPeerUpdated = true); void feedUserLink(MTPint userId, const MTPContactLink &myLink, const MTPContactLink &foreignLink);
void feedUserLink(MTPint userId, const MTPContactLink &myLink, const MTPContactLink &foreignLink, bool emitPeerUpdated = true);
void markPeerUpdated(PeerData *data); void markPeerUpdated(PeerData *data);
void clearPeerUpdated(PeerData *data); void clearPeerUpdated(PeerData *data);
void emitPeerUpdated();
ImagePtr image(const MTPPhotoSize &size); ImagePtr image(const MTPPhotoSize &size);
StorageImageLocation imageLocation(int32 w, int32 h, const MTPFileLocation &loc); StorageImageLocation imageLocation(int32 w, int32 h, const MTPFileLocation &loc);
@ -231,6 +233,7 @@ namespace App {
QImage readImage(QByteArray data, QByteArray *format = 0, bool opaque = true, bool *animated = 0); 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); QImage readImage(const QString &file, QByteArray *format = 0, bool opaque = true, bool *animated = 0, QByteArray *content = 0);
QPixmap pixmapFromImageInPlace(QImage &&image);
void regPhotoItem(PhotoData *data, HistoryItem *item); void regPhotoItem(PhotoData *data, HistoryItem *item);
void unregPhotoItem(PhotoData *data, HistoryItem *item); void unregPhotoItem(PhotoData *data, HistoryItem *item);

View file

@ -27,9 +27,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "mainwidget.h" #include "mainwidget.h"
#include "lang.h" #include "lang.h"
#include "boxes/confirmbox.h" #include "boxes/confirmbox.h"
#include "ui/filedialog.h"
#include "langloaderplain.h" #include "langloaderplain.h"
#include "localstorage.h" #include "localstorage.h"
#include "autoupdater.h" #include "autoupdater.h"
#include "core/observer.h"
#include "observer_peer.h"
namespace { namespace {
void mtpStateChanged(int32 dc, int32 state) { void mtpStateChanged(int32 dc, int32 state) {
@ -200,6 +203,7 @@ void Application::singleInstanceChecked() {
Logs::multipleInstances(); Logs::multipleInstances();
} }
Notify::startObservers();
Sandbox::start(); Sandbox::start();
if (!Logs::started() || (!cManyInstance() && !Logs::instanceChecked())) { if (!Logs::started() || (!cManyInstance() && !Logs::instanceChecked())) {
@ -336,6 +340,8 @@ void Application::closeApplication() {
if (_updateThread) _updateThread->quit(); if (_updateThread) _updateThread->quit();
_updateThread = 0; _updateThread = 0;
#endif #endif
Notify::finishObservers();
} }
#ifndef TDESKTOP_DISABLE_AUTOUPDATE #ifndef TDESKTOP_DISABLE_AUTOUPDATE
@ -902,6 +908,18 @@ void AppClass::call_handleUnreadCounterUpdate() {
} }
} }
void AppClass::call_handleFileDialogQueue() {
while (true) {
if (!FileDialog::processQuery()) {
return;
}
}
}
void AppClass::call_handleDelayedPeerUpdates() {
Notify::peerUpdatedSendDelayed();
}
void AppClass::killDownloadSessions() { void AppClass::killDownloadSessions() {
uint64 ms = getms(), left = MTPAckSendWaiting + MTPKillFileSessionTimeout; uint64 ms = getms(), left = MTPAckSendWaiting + MTPKillFileSessionTimeout;
for (QMap<int32, uint64>::iterator i = killDownloadSessionTimes.begin(); i != killDownloadSessionTimes.end(); ) { for (QMap<int32, uint64>::iterator i = killDownloadSessionTimes.begin(); i != killDownloadSessionTimes.end(); ) {

View file

@ -203,6 +203,8 @@ public slots:
void call_handleHistoryUpdate(); void call_handleHistoryUpdate();
void call_handleUnreadCounterUpdate(); void call_handleUnreadCounterUpdate();
void call_handleFileDialogQueue();
void call_handleDelayedPeerUpdates();
private: private:

View file

@ -32,9 +32,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
AboutBox::AboutBox() : AbstractBox(st::aboutWidth) AboutBox::AboutBox() : AbstractBox(st::aboutWidth)
, _version(this, lng_about_version(lt_version, QString::fromLatin1(AppVersionStr.c_str()) + (cAlphaVersion() ? " alpha" : "") + (cBetaVersion() ? qsl(" beta %1").arg(cBetaVersion()) : QString())), st::aboutVersionLink) , _version(this, lng_about_version(lt_version, QString::fromLatin1(AppVersionStr.c_str()) + (cAlphaVersion() ? " alpha" : "") + (cBetaVersion() ? qsl(" beta %1").arg(cBetaVersion()) : QString())), st::aboutVersionLink)
, _text1(this, lang(lng_about_text_1), st::aboutLabel, st::aboutTextStyle) , _text1(this, lang(lng_about_text_1), FlatLabel::InitType::Rich, st::aboutLabel, st::aboutTextStyle)
, _text2(this, lang(lng_about_text_2), st::aboutLabel, st::aboutTextStyle) , _text2(this, lang(lng_about_text_2), FlatLabel::InitType::Rich, st::aboutLabel, st::aboutTextStyle)
, _text3(this, QString(), st::aboutLabel, st::aboutTextStyle) , _text3(this,st::aboutLabel, st::aboutTextStyle)
, _done(this, lang(lng_close), st::defaultBoxButton) { , _done(this, lang(lng_close), st::defaultBoxButton) {
_text3.setRichText(lng_about_text_3(lt_faq_open, qsl("[a href=\"%1\"]").arg(telegramFaqLink()), lt_faq_close, qsl("[/a]"))); _text3.setRichText(lng_about_text_3(lt_faq_open, qsl("[a href=\"%1\"]").arg(telegramFaqLink()), lt_faq_close, qsl("[/a]")));
@ -109,10 +109,9 @@ void AboutBox::paintEvent(QPaintEvent *e) {
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS #ifndef TDESKTOP_DISABLE_CRASH_REPORTS
QString _getCrashReportFile(const QMimeData *m) { QString _getCrashReportFile(const QMimeData *m) {
if (!m || m->urls().size() != 1) return QString(); if (!m || m->urls().size() != 1 || !m->urls().at(0).isLocalFile()) return QString();
QString file(m->urls().at(0).toLocalFile()); auto file = psConvertFileUrl(m->urls().at(0));
if (file.startsWith(qsl("/.file/id="))) file = psConvertFileUrl(file);
return file.endsWith(qstr(".telegramcrash"), Qt::CaseInsensitive) ? file : QString(); return file.endsWith(qstr(".telegramcrash"), Qt::CaseInsensitive) ? file : QString();
} }

View file

@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#pragma once #pragma once
#include "abstractbox.h" #include "abstractbox.h"
#include "ui/flatlabel.h"
class AboutBox : public AbstractBox { class AboutBox : public AbstractBox {
Q_OBJECT Q_OBJECT

View file

@ -30,18 +30,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "mainwidget.h" #include "mainwidget.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "observer_peer.h"
AddContactBox::AddContactBox(QString fname, QString lname, QString phone) : AbstractBox(st::boxWidth) AddContactBox::AddContactBox(QString fname, QString lname, QString phone) : AbstractBox(st::boxWidth)
, _user(0)
, _save(this, lang(lng_add_contact), st::defaultBoxButton) , _save(this, lang(lng_add_contact), st::defaultBoxButton)
, _cancel(this, lang(lng_cancel), st::cancelBoxButton) , _cancel(this, lang(lng_cancel), st::cancelBoxButton)
, _retry(this, lang(lng_try_other_contact), st::defaultBoxButton) , _retry(this, lang(lng_try_other_contact), st::defaultBoxButton)
, _first(this, st::defaultInputField, lang(lng_signup_firstname), fname) , _first(this, st::defaultInputField, lang(lng_signup_firstname), fname)
, _last(this, st::defaultInputField, lang(lng_signup_lastname), lname) , _last(this, st::defaultInputField, lang(lng_signup_lastname), lname)
, _phone(this, st::defaultInputField, lang(lng_contact_phone), phone) , _phone(this, st::defaultInputField, lang(lng_contact_phone), phone)
, _invertOrder(langFirstNameGoesSecond()) , _invertOrder(langFirstNameGoesSecond()) {
, _contactId(0)
, _addRequest(0) {
if (!phone.isEmpty()) { if (!phone.isEmpty()) {
_phone.setDisabled(true); _phone.setDisabled(true);
} }
@ -56,10 +54,8 @@ AddContactBox::AddContactBox(UserData *user) : AbstractBox(st::boxWidth)
, _retry(this, lang(lng_try_other_contact), st::defaultBoxButton) , _retry(this, lang(lng_try_other_contact), st::defaultBoxButton)
, _first(this, st::defaultInputField, lang(lng_signup_firstname), user->firstName) , _first(this, st::defaultInputField, lang(lng_signup_firstname), user->firstName)
, _last(this, st::defaultInputField, lang(lng_signup_lastname), user->lastName) , _last(this, st::defaultInputField, lang(lng_signup_lastname), user->lastName)
, _phone(this, st::defaultInputField, lang(lng_contact_phone), user->phone) , _phone(this, st::defaultInputField, lang(lng_contact_phone), user->phone())
, _invertOrder(langFirstNameGoesSecond()) , _invertOrder(langFirstNameGoesSecond()) {
, _contactId(0)
, _addRequest(0) {
_phone.setDisabled(true); _phone.setDisabled(true);
initBox(); initBox();
} }
@ -190,7 +186,7 @@ void AddContactBox::onSave() {
_sentName = firstName; _sentName = firstName;
if (_user) { if (_user) {
_contactId = rand_value<uint64>(); _contactId = rand_value<uint64>();
QVector<MTPInputContact> v(1, MTP_inputPhoneContact(MTP_long(_contactId), MTP_string(_user->phone), MTP_string(firstName), MTP_string(lastName))); QVector<MTPInputContact> v(1, MTP_inputPhoneContact(MTP_long(_contactId), MTP_string(_user->phone()), MTP_string(firstName), MTP_string(lastName)));
_addRequest = MTP::send(MTPcontacts_ImportContacts(MTP_vector<MTPInputContact>(v), MTP_bool(false)), rpcDone(&AddContactBox::onSaveUserDone), rpcFail(&AddContactBox::onSaveUserFail)); _addRequest = MTP::send(MTPcontacts_ImportContacts(MTP_vector<MTPInputContact>(v), MTP_bool(false)), rpcDone(&AddContactBox::onSaveUserDone), rpcFail(&AddContactBox::onSaveUserFail));
} else { } else {
_contactId = rand_value<uint64>(); _contactId = rand_value<uint64>();
@ -206,7 +202,7 @@ bool AddContactBox::onSaveUserFail(const RPCError &error) {
QString err(error.type()); QString err(error.type());
QString firstName = _first.getLastText().trimmed(), lastName = _last.getLastText().trimmed(); QString firstName = _first.getLastText().trimmed(), lastName = _last.getLastText().trimmed();
if (err == "CHAT_TITLE_NOT_MODIFIED") { if (err == "CHAT_TITLE_NOT_MODIFIED") {
_user->updateName(firstName, QString(), QString()); _user->setName(firstName, lastName, _user->nameOrPhone, _user->username);
emit closed(); emit closed();
return true; return true;
} else if (err == "NO_CHAT_TITLE") { } else if (err == "NO_CHAT_TITLE") {
@ -548,7 +544,7 @@ bool GroupInfoBox::creationFail(const RPCError &error) {
void GroupInfoBox::exportDone(const MTPExportedChatInvite &result) { void GroupInfoBox::exportDone(const MTPExportedChatInvite &result) {
_creationRequestId = 0; _creationRequestId = 0;
if (result.type() == mtpc_chatInviteExported) { if (result.type() == mtpc_chatInviteExported) {
_createdChannel->invitationUrl = qs(result.c_chatInviteExported().vlink); _createdChannel->setInviteLink(qs(result.c_chatInviteExported().vlink));
} }
Ui::showLayer(new SetupChannelBox(_createdChannel)); Ui::showLayer(new SetupChannelBox(_createdChannel));
} }
@ -712,7 +708,7 @@ void SetupChannelBox::paintEvent(QPaintEvent *e) {
option.setWrapMode(QTextOption::WrapAnywhere); option.setWrapMode(QTextOption::WrapAnywhere);
p.setFont(_linkOver ? st::boxTextFont->underline() : st::boxTextFont); p.setFont(_linkOver ? st::boxTextFont->underline() : st::boxTextFont);
p.setPen(st::btnDefLink.color); p.setPen(st::btnDefLink.color);
p.drawText(_invitationLink, _channel->invitationUrl, option); p.drawText(_invitationLink, _channel->inviteLink(), option);
if (!_goodTextLink.isEmpty() && a_goodOpacity.current() > 0) { if (!_goodTextLink.isEmpty() && a_goodOpacity.current() > 0) {
p.setOpacity(a_goodOpacity.current()); p.setOpacity(a_goodOpacity.current());
p.setPen(st::setGoodColor); p.setPen(st::setGoodColor);
@ -753,7 +749,7 @@ void SetupChannelBox::mouseMoveEvent(QMouseEvent *e) {
void SetupChannelBox::mousePressEvent(QMouseEvent *e) { void SetupChannelBox::mousePressEvent(QMouseEvent *e) {
mouseMoveEvent(e); mouseMoveEvent(e);
if (_linkOver) { if (_linkOver) {
Application::clipboard()->setText(_channel->invitationUrl); Application::clipboard()->setText(_channel->inviteLink());
_goodTextLink = lang(lng_create_channel_link_copied); _goodTextLink = lang(lng_create_channel_link_copied);
a_goodOpacity = anim::fvalue(1, 0); a_goodOpacity = anim::fvalue(1, 0);
_a_goodFade.start(); _a_goodFade.start();
@ -1140,7 +1136,9 @@ bool EditNameTitleBox::onSaveChatFail(const RPCError &error) {
_requestId = 0; _requestId = 0;
QString err(error.type()); QString err(error.type());
if (err == qstr("CHAT_TITLE_NOT_MODIFIED") || err == qstr("CHAT_NOT_MODIFIED")) { if (err == qstr("CHAT_TITLE_NOT_MODIFIED") || err == qstr("CHAT_NOT_MODIFIED")) {
_peer->updateName(_sentName, QString(), QString()); if (auto chatData = _peer->asChat()) {
chatData->setName(_sentName);
}
emit closed(); emit closed();
return true; return true;
} else if (err == qstr("NO_CHAT_TITLE")) { } else if (err == qstr("NO_CHAT_TITLE")) {
@ -1162,7 +1160,7 @@ EditChannelBox::EditChannelBox(ChannelData *channel) : AbstractBox()
, _save(this, lang(lng_settings_save), st::defaultBoxButton) , _save(this, lang(lng_settings_save), st::defaultBoxButton)
, _cancel(this, lang(lng_cancel), st::cancelBoxButton) , _cancel(this, lang(lng_cancel), st::cancelBoxButton)
, _title(this, st::defaultInputField, lang(lng_dlg_new_channel_name), _channel->name) , _title(this, st::defaultInputField, lang(lng_dlg_new_channel_name), _channel->name)
, _description(this, st::newGroupDescription, lang(lng_create_group_description), _channel->about) , _description(this, st::newGroupDescription, lang(lng_create_group_description), _channel->about())
, _sign(this, lang(lng_edit_sign_messages), channel->addsSignature()) , _sign(this, lang(lng_edit_sign_messages), channel->addsSignature())
, _publicLink(this, lang(channel->isPublic() ? lng_profile_edit_public_link : lng_profile_create_public_link), st::defaultBoxLinkButton) , _publicLink(this, lang(channel->isPublic() ? lng_profile_edit_public_link : lng_profile_create_public_link), st::defaultBoxLinkButton)
, _saveTitleRequestId(0) , _saveTitleRequestId(0)
@ -1303,7 +1301,7 @@ void EditChannelBox::onPublicLink() {
} }
void EditChannelBox::saveDescription() { void EditChannelBox::saveDescription() {
if (_sentDescription == _channel->about) { if (_sentDescription == _channel->about()) {
saveSign(); saveSign();
} else { } else {
_saveDescriptionRequestId = MTP::send(MTPchannels_EditAbout(_channel->inputChannel, MTP_string(_sentDescription)), rpcDone(&EditChannelBox::onSaveDescriptionDone), rpcFail(&EditChannelBox::onSaveFail)); _saveDescriptionRequestId = MTP::send(MTPchannels_EditAbout(_channel->inputChannel, MTP_string(_sentDescription)), rpcDone(&EditChannelBox::onSaveDescriptionDone), rpcFail(&EditChannelBox::onSaveFail));
@ -1338,9 +1336,10 @@ bool EditChannelBox::onSaveFail(const RPCError &error, mtpRequestId req) {
} else if (req == _saveDescriptionRequestId) { } else if (req == _saveDescriptionRequestId) {
_saveDescriptionRequestId = 0; _saveDescriptionRequestId = 0;
if (err == qstr("CHAT_ABOUT_NOT_MODIFIED")) { if (err == qstr("CHAT_ABOUT_NOT_MODIFIED")) {
_channel->about = _sentDescription; if (_channel->setAbout(_sentDescription)) {
if (App::api()) { if (App::api()) {
emit App::api()->fullPeerUpdated(_channel); emit App::api()->fullPeerUpdated(_channel);
}
} }
saveSign(); saveSign();
return true; return true;
@ -1367,9 +1366,10 @@ void EditChannelBox::onSaveTitleDone(const MTPUpdates &updates) {
void EditChannelBox::onSaveDescriptionDone(const MTPBool &result) { void EditChannelBox::onSaveDescriptionDone(const MTPBool &result) {
_saveDescriptionRequestId = 0; _saveDescriptionRequestId = 0;
_channel->about = _sentDescription; if (_channel->setAbout(_sentDescription)) {
if (App::api()) { if (App::api()) {
emit App::api()->fullPeerUpdated(_channel); emit App::api()->fullPeerUpdated(_channel);
}
} }
saveSign(); saveSign();
} }

View file

@ -57,7 +57,7 @@ private:
void initBox(); void initBox();
UserData *_user; UserData *_user = nullptr;
QString _boxTitle; QString _boxTitle;
BoxButton _save, _cancel, _retry; BoxButton _save, _cancel, _retry;
@ -66,9 +66,9 @@ private:
bool _invertOrder; bool _invertOrder;
uint64 _contactId; uint64 _contactId = 0;
mtpRequestId _addRequest; mtpRequestId _addRequest = 0;
QString _sentName; QString _sentName;
}; };

View file

@ -381,11 +381,10 @@ void ConvertToSupergroupBox::resizeEvent(QResizeEvent *e) {
PinMessageBox::PinMessageBox(ChannelData *channel, MsgId msgId) : AbstractBox(st::boxWidth) PinMessageBox::PinMessageBox(ChannelData *channel, MsgId msgId) : AbstractBox(st::boxWidth)
, _channel(channel) , _channel(channel)
, _msgId(msgId) , _msgId(msgId)
, _text(this, lang(lng_pinned_pin_sure), st::boxLabel) , _text(this, lang(lng_pinned_pin_sure), FlatLabel::InitType::Simple, st::boxLabel)
, _notify(this, lang(lng_pinned_notify), true) , _notify(this, lang(lng_pinned_notify), true)
, _pin(this, lang(lng_pinned_pin), st::defaultBoxButton) , _pin(this, lang(lng_pinned_pin), st::defaultBoxButton)
, _cancel(this, lang(lng_cancel), st::cancelBoxButton) , _cancel(this, lang(lng_cancel), st::cancelBoxButton) {
, _requestId(0) {
_text.resizeToWidth(st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()); _text.resizeToWidth(st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right());
setMaxHeight(st::boxPadding.top() + _text.height() + st::boxMediumSkip + _notify.height() + st::boxPadding.bottom() + st::boxButtonPadding.top() + _pin.height() + st::boxButtonPadding.bottom()); setMaxHeight(st::boxPadding.top() + _text.height() + st::boxMediumSkip + _notify.height() + st::boxPadding.bottom() + st::boxButtonPadding.top() + _pin.height() + st::boxButtonPadding.bottom());
@ -441,7 +440,7 @@ RichDeleteMessageBox::RichDeleteMessageBox(ChannelData *channel, UserData *from,
, _channel(channel) , _channel(channel)
, _from(from) , _from(from)
, _msgId(msgId) , _msgId(msgId)
, _text(this, lang(lng_selected_delete_sure_this), st::boxLabel) , _text(this, lang(lng_selected_delete_sure_this), FlatLabel::InitType::Simple, st::boxLabel)
, _banUser(this, lang(lng_ban_user), false) , _banUser(this, lang(lng_ban_user), false)
, _reportSpam(this, lang(lng_report_spam), false) , _reportSpam(this, lang(lng_report_spam), false)
, _deleteAll(this, lang(lng_delete_all_from), false) , _deleteAll(this, lang(lng_delete_all_from), false)
@ -478,6 +477,7 @@ void RichDeleteMessageBox::onDelete() {
if (HistoryItem *item = App::histItemById(_channel ? peerToChannel(_channel->id) : 0, _msgId)) { if (HistoryItem *item = App::histItemById(_channel ? peerToChannel(_channel->id) : 0, _msgId)) {
bool wasLast = (item->history()->lastMsg == item); bool wasLast = (item->history()->lastMsg == item);
item->destroy(); item->destroy();
if (_msgId > 0) { if (_msgId > 0) {
App::main()->deleteMessages(_channel, QVector<MTPint>(1, MTP_int(_msgId))); App::main()->deleteMessages(_channel, QVector<MTPint>(1, MTP_int(_msgId)));
} else if (wasLast) { } else if (wasLast) {

View file

@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#pragma once #pragma once
#include "abstractbox.h" #include "abstractbox.h"
#include "ui/flatlabel.h"
class InformBox; class InformBox;
class ConfirmBox : public AbstractBox, public ClickHandlerHost { class ConfirmBox : public AbstractBox, public ClickHandlerHost {
@ -216,7 +217,7 @@ private:
BoxButton _pin, _cancel; BoxButton _pin, _cancel;
mtpRequestId _requestId; mtpRequestId _requestId = 0;
}; };

View file

@ -31,6 +31,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "ui/filedialog.h" #include "ui/filedialog.h"
#include "boxes/photocropbox.h" #include "boxes/photocropbox.h"
#include "boxes/confirmbox.h" #include "boxes/confirmbox.h"
#include "observer_peer.h"
#include "apiwrap.h" #include "apiwrap.h"
QString cantInviteError() { QString cantInviteError() {
@ -201,16 +202,20 @@ void ContactsInner::addAdminDone(const MTPUpdates &result, mtpRequestId req) {
_addAdminRequestId = 0; _addAdminRequestId = 0;
if (_addAdmin && _channel && _channel->isMegagroup()) { if (_addAdmin && _channel && _channel->isMegagroup()) {
Notify::PeerUpdate update(_channel);
if (_channel->mgInfo->lastParticipants.indexOf(_addAdmin) < 0) { if (_channel->mgInfo->lastParticipants.indexOf(_addAdmin) < 0) {
_channel->mgInfo->lastParticipants.push_front(_addAdmin); _channel->mgInfo->lastParticipants.push_front(_addAdmin);
update.flags |= Notify::PeerUpdate::Flag::MembersChanged;
} }
_channel->mgInfo->lastAdmins.insert(_addAdmin); _channel->mgInfo->lastAdmins.insert(_addAdmin);
update.flags |= Notify::PeerUpdate::Flag::AdminsChanged;
if (_addAdmin->botInfo) { if (_addAdmin->botInfo) {
_channel->mgInfo->bots.insert(_addAdmin); _channel->mgInfo->bots.insert(_addAdmin);
if (_channel->mgInfo->botStatus != 0 && _channel->mgInfo->botStatus < 2) { if (_channel->mgInfo->botStatus != 0 && _channel->mgInfo->botStatus < 2) {
_channel->mgInfo->botStatus = 2; _channel->mgInfo->botStatus = 2;
} }
} }
Notify::peerUpdatedDelayed(update);
} }
if (_addAdminBox) _addAdminBox->onClose(); if (_addAdminBox) _addAdminBox->onClose();
emit adminAdded(); emit adminAdded();
@ -224,7 +229,7 @@ bool ContactsInner::addAdminFail(const RPCError &error, mtpRequestId req) {
_addAdminRequestId = 0; _addAdminRequestId = 0;
if (_addAdminBox) _addAdminBox->onClose(); if (_addAdminBox) _addAdminBox->onClose();
if (error.type() == "USERS_TOO_MUCH") { if (error.type() == "USERS_TOO_MUCH") {
Ui::showLayer(new MaxInviteBox(_channel->invitationUrl), KeepOtherLayers); Ui::showLayer(new MaxInviteBox(_channel->inviteLink()), KeepOtherLayers);
} else if (error.type() == "ADMINS_TOO_MUCH") { } else if (error.type() == "ADMINS_TOO_MUCH") {
Ui::showLayer(new InformBox(lang(lng_channel_admins_too_much)), KeepOtherLayers); Ui::showLayer(new InformBox(lang(lng_channel_admins_too_much)), KeepOtherLayers);
} else if (error.type() == qstr("USER_RESTRICTED")) { } else if (error.type() == qstr("USER_RESTRICTED")) {
@ -728,7 +733,7 @@ void ContactsInner::changeCheckState(ContactData *data, PeerData *peer) {
_checkedContacts.insert(peer, true); _checkedContacts.insert(peer, true);
++_selCount; ++_selCount;
} else if (_channel && !_channel->isMegagroup()) { } else if (_channel && !_channel->isMegagroup()) {
Ui::showLayer(new MaxInviteBox(_channel->invitationUrl), KeepOtherLayers); Ui::showLayer(new MaxInviteBox(_channel->inviteLink()), KeepOtherLayers);
} else if (!_channel && selectedCount() >= Global::ChatSizeMax() && selectedCount() < Global::MegagroupSizeMax()) { } else if (!_channel && selectedCount() >= Global::ChatSizeMax() && selectedCount() < Global::MegagroupSizeMax()) {
Ui::showLayer(new InformBox(lng_profile_add_more_after_upgrade(lt_count, Global::MegagroupSizeMax())), KeepOtherLayers); Ui::showLayer(new InformBox(lng_profile_add_more_after_upgrade(lt_count, Global::MegagroupSizeMax())), KeepOtherLayers);
} }
@ -740,7 +745,7 @@ int32 ContactsInner::selectedCount() const {
if (_chat) { if (_chat) {
result += qMax(_chat->count, 1); result += qMax(_chat->count, 1);
} else if (_channel) { } else if (_channel) {
result += qMax(_channel->count, _already.size()); result += qMax(_channel->membersCount(), _already.size());
} else if (_creating == CreatingGroupGroup) { } else if (_creating == CreatingGroupGroup) {
result += 1; result += 1;
} }
@ -1739,7 +1744,7 @@ bool ContactsBox::creationFail(const RPCError &error) {
MembersInner::MembersInner(ChannelData *channel, MembersFilter filter) : TWidget() MembersInner::MembersInner(ChannelData *channel, MembersFilter filter) : TWidget()
, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom())
, _newItemHeight((channel->amCreator() && (channel->count < (channel->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax()) || (!channel->isMegagroup() && !channel->isPublic()) || filter == MembersFilterAdmins)) ? st::contactsNewItemHeight : 0) , _newItemHeight((channel->amCreator() && (channel->membersCount() < (channel->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax()) || (!channel->isMegagroup() && !channel->isPublic()) || filter == MembersFilterAdmins)) ? st::contactsNewItemHeight : 0)
, _newItemSel(false) , _newItemSel(false)
, _channel(channel) , _channel(channel)
, _filter(filter) , _filter(filter)
@ -1808,7 +1813,7 @@ void MembersInner::paintEvent(QPaintEvent *e) {
paintDialog(p, _rows[from], data(from), sel, kickSel, kickDown); paintDialog(p, _rows[from], data(from), sel, kickSel, kickDown);
p.translate(0, _rowHeight); p.translate(0, _rowHeight);
} }
if (to == _rows.size() && _filter == MembersFilterRecent && (_rows.size() < _channel->count || _rows.size() >= Global::ChatSizeMax())) { if (to == _rows.size() && _filter == MembersFilterRecent && (_rows.size() < _channel->membersCount() || _rows.size() >= Global::ChatSizeMax())) {
p.setPen(st::stickersReorderFg); p.setPen(st::stickersReorderFg);
_about.draw(p, st::contactsPadding.left(), st::stickersReorderPadding.top(), _aboutWidth, style::al_center); _about.draw(p, st::contactsPadding.left(), st::stickersReorderPadding.top(), _aboutWidth, style::al_center);
} }
@ -1976,7 +1981,7 @@ void MembersInner::chooseParticipant() {
if (_sel < 0 || _sel >= _rows.size()) return; if (_sel < 0 || _sel >= _rows.size()) return;
if (PeerData *peer = _rows[_sel]) { if (PeerData *peer = _rows[_sel]) {
Ui::hideLayer(); Ui::hideLayer();
App::main()->showPeerProfile(peer, ShowAtUnreadMsgId); Ui::showPeerProfile(peer);
} }
} }
@ -1987,7 +1992,7 @@ void MembersInner::refresh() {
} else { } else {
_about.setText(st::boxTextFont, lng_channel_only_last_shown(lt_count, _rows.size())); _about.setText(st::boxTextFont, lng_channel_only_last_shown(lt_count, _rows.size()));
_aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom(); _aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom();
if (_filter != MembersFilterRecent || (_rows.size() >= _channel->count && _rows.size() < Global::ChatSizeMax())) { if (_filter != MembersFilterRecent || (_rows.size() >= _channel->membersCount() && _rows.size() < Global::ChatSizeMax())) {
_aboutHeight = 0; _aboutHeight = 0;
} }
resize(width(), st::membersPadding.top() + _newItemHeight + _rows.size() * _rowHeight + st::membersPadding.bottom() + _aboutHeight); resize(width(), st::membersPadding.top() + _newItemHeight + _rows.size() * _rowHeight + st::membersPadding.bottom() + _aboutHeight);
@ -2003,11 +2008,11 @@ MembersFilter MembersInner::filter() const {
return _filter; return _filter;
} }
QMap<UserData*, bool> MembersInner::already() const { MembersAlreadyIn MembersInner::already() const {
MembersAlreadyIn result; MembersAlreadyIn result;
for (int32 i = 0, l = _rows.size(); i < l; ++i) { for_const (auto peer, _rows) {
if (_rows.at(i)->isUser()) { if (peer->isUser()) {
result.insert(_rows.at(i)->asUser(), true); result.insert(peer->asUser());
} }
} }
return result; return result;
@ -2115,14 +2120,16 @@ void MembersInner::membersReceived(const MTPchannels_ChannelParticipants &result
_datas.reserve(v.size()); _datas.reserve(v.size());
_dates.reserve(v.size()); _dates.reserve(v.size());
_roles.reserve(v.size()); _roles.reserve(v.size());
if (_filter == MembersFilterRecent && _channel->count < d.vcount.v) {
_channel->count = d.vcount.v; if (_filter == MembersFilterRecent && _channel->membersCount() < d.vcount.v) {
_channel->setMembersCount(d.vcount.v);
if (App::main()) emit App::main()->peerUpdated(_channel); if (App::main()) emit App::main()->peerUpdated(_channel);
} else if (_filter == MembersFilterAdmins && _channel->adminsCount < d.vcount.v) { } else if (_filter == MembersFilterAdmins && _channel->adminsCount() < d.vcount.v) {
_channel->adminsCount = d.vcount.v; _channel->setAdminsCount(d.vcount.v);
if (App::main()) emit App::main()->peerUpdated(_channel); if (App::main()) emit App::main()->peerUpdated(_channel);
} }
App::feedUsers(d.vusers); App::feedUsers(d.vusers);
for (QVector<MTPChannelParticipant>::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) { for (QVector<MTPChannelParticipant>::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) {
int32 userId = 0, addedTime = 0; int32 userId = 0, addedTime = 0;
MemberRole role = MemberRoleNone; MemberRole role = MemberRoleNone;
@ -2173,6 +2180,8 @@ void MembersInner::membersReceived(const MTPchannels_ChannelParticipants &result
_channel->mgInfo->lastAdmins.insert(_rows.at(i)); _channel->mgInfo->lastAdmins.insert(_rows.at(i));
} }
} }
Notify::peerUpdatedDelayed(_channel, Notify::PeerUpdate::Flag::AdminsChanged);
} }
} }
if (_rows.isEmpty()) { if (_rows.isEmpty()) {
@ -2229,11 +2238,11 @@ void MembersInner::removeKicked() {
_dates.removeAt(index); _dates.removeAt(index);
_roles.removeAt(index); _roles.removeAt(index);
clearSel(); clearSel();
if (_filter == MembersFilterRecent && _channel->count > 1) { if (_filter == MembersFilterRecent && _channel->membersCount() > 1) {
--_channel->count; _channel->setMembersCount(_channel->membersCount() - 1);
if (App::main()) emit App::main()->peerUpdated(_channel); if (App::main()) emit App::main()->peerUpdated(_channel);
} else if (_filter == MembersFilterAdmins && _channel->adminsCount > 1) { } else if (_filter == MembersFilterAdmins && _channel->adminsCount() > 1) {
--_channel->adminsCount; _channel->setAdminsCount(_channel->adminsCount() - 1);
if (App::main()) emit App::main()->peerUpdated(_channel); if (App::main()) emit App::main()->peerUpdated(_channel);
} }
refresh(); refresh();
@ -2290,8 +2299,8 @@ void MembersBox::onScroll() {
} }
void MembersBox::onAdd() { void MembersBox::onAdd() {
if (_inner.filter() == MembersFilterRecent && _inner.channel()->count >= (_inner.channel()->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax())) { if (_inner.filter() == MembersFilterRecent && _inner.channel()->membersCount() >= (_inner.channel()->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax())) {
Ui::showLayer(new MaxInviteBox(_inner.channel()->invitationUrl), KeepOtherLayers); Ui::showLayer(new MaxInviteBox(_inner.channel()->inviteLink()), KeepOtherLayers);
return; return;
} }
ContactsBox *box = new ContactsBox(_inner.channel(), _inner.filter(), _inner.already()); ContactsBox *box = new ContactsBox(_inner.channel(), _inner.filter(), _inner.already());

View file

@ -31,7 +31,7 @@ enum MembersFilter {
MembersFilterRecent, MembersFilterRecent,
MembersFilterAdmins, MembersFilterAdmins,
}; };
typedef QMap<UserData*, bool> MembersAlreadyIn; using MembersAlreadyIn = OrderedSet<UserData*>;
QString cantInviteError(); QString cantInviteError();
@ -318,7 +318,7 @@ public:
} }
void clearSel(); void clearSel();
QMap<UserData*, bool> already() const; MembersAlreadyIn already() const;
~MembersInner(); ~MembersInner();

View file

@ -0,0 +1,147 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "report_box.h"
#include "lang.h"
#include "styles/style_profile.h"
#include "boxes/confirmbox.h"
ReportBox::ReportBox(ChannelData *channel) : AbstractBox(st::boxWidth)
, _channel(channel)
, _reasonSpam(this, qsl("report_reason"), ReasonSpam, lang(lng_report_reason_spam), true)
, _reasonViolence(this, qsl("report_reason"), ReasonViolence, lang(lng_report_reason_violence))
, _reasonPornography(this, qsl("report_reason"), ReasonPornography, lang(lng_report_reason_pornography))
, _reasonOther(this, qsl("report_reason"), ReasonOther, lang(lng_report_reason_other))
, _report(this, lang(lng_report_button), st::defaultBoxButton)
, _cancel(this, lang(lng_cancel), st::cancelBoxButton) {
connect(_report, SIGNAL(clicked()), this, SLOT(onReport()));
connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose()));
connect(_reasonSpam, SIGNAL(changed()), this, SLOT(onChange()));
connect(_reasonViolence, SIGNAL(changed()), this, SLOT(onChange()));
connect(_reasonPornography, SIGNAL(changed()), this, SLOT(onChange()));
connect(_reasonOther, SIGNAL(changed()), this, SLOT(onChange()));
updateMaxHeight();
prepare();
}
void ReportBox::paintEvent(QPaintEvent *e) {
Painter p(this);
if (paint(p)) return;
paintTitle(p, lang(lng_report_title));
}
void ReportBox::resizeEvent(QResizeEvent *e) {
_reasonSpam->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), st::boxTitleHeight + st::boxOptionListPadding.top());
_reasonViolence->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonSpam->y() + _reasonSpam->height() + st::boxOptionListPadding.top());
_reasonPornography->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonViolence->y() + _reasonViolence->height() + st::boxOptionListPadding.top());
_reasonOther->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonPornography->y() + _reasonPornography->height() + st::boxOptionListPadding.top());
if (_reasonOtherText) {
_reasonOtherText->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left() - st::defaultInputField.textMargins.left(), _reasonOther->y() + _reasonOther->height() + st::newGroupDescriptionPadding.top());
}
_report->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _report->height());
_cancel->moveToRight(st::boxButtonPadding.right() + _report->width() + st::boxButtonPadding.left(), _report->y());
}
void ReportBox::onChange() {
if (_reasonOther->checked()) {
if (!_reasonOtherText) {
_reasonOtherText = new InputArea(this, st::profileReportReasonOther, lang(lng_report_reason_description));
_reasonOtherText->show();
_reasonOtherText->setCtrlEnterSubmit(CtrlEnterSubmitBoth);
_reasonOtherText->setMaxLength(MaxPhotoCaption);
_reasonOtherText->resize(width() - (st::boxPadding.left() + st::boxOptionListPadding.left() + st::boxPadding.right()), _reasonOtherText->height());
updateMaxHeight();
connect(_reasonOtherText, SIGNAL(resized()), this, SLOT(onDescriptionResized()));
connect(_reasonOtherText, SIGNAL(submitted(bool)), this, SLOT(onReport()));
connect(_reasonOtherText, SIGNAL(cancelled()), this, SLOT(onClose()));
}
} else if (_reasonOtherText) {
_reasonOtherText.destroy();
updateMaxHeight();
}
setInnerFocus();
}
void ReportBox::setInnerFocus() {
if (_reasonOtherText) {
_reasonOtherText->setFocus();
} else {
setFocus();
}
}
void ReportBox::onDescriptionResized() {
updateMaxHeight();
update();
}
void ReportBox::onReport() {
if (_requestId) return;
if (_reasonOtherText && _reasonOtherText->getLastText().trimmed().isEmpty()) {
_reasonOtherText->showError();
return;
}
auto getReason = [this]() {
if (_reasonViolence->checked()) {
return MTP_inputReportReasonViolence();
} else if (_reasonPornography->checked()) {
return MTP_inputReportReasonPornography();
} else if (_reasonOtherText) {
return MTP_inputReportReasonOther(MTP_string(_reasonOtherText->getLastText()));
} else {
return MTP_inputReportReasonSpam();
}
};
_requestId = MTP::send(MTPaccount_ReportPeer(_channel->input, getReason()), rpcDone(&ReportBox::reportDone), rpcFail(&ReportBox::reportFail));
}
void ReportBox::reportDone(const MTPBool &result) {
_requestId = 0;
Ui::showLayer(new InformBox(lang(lng_report_thanks)));
}
bool ReportBox::reportFail(const RPCError &error) {
if (MTP::isDefaultHandledError(error)) return false;
_requestId = 0;
if (_reasonOtherText) {
_reasonOtherText->showError();
}
return true;
}
void ReportBox::updateMaxHeight() {
int32 h = st::boxTitleHeight + 4 * (st::boxOptionListPadding.top() + _reasonSpam->height()) + st::boxButtonPadding.top() + _report->height() + st::boxButtonPadding.bottom();
if (_reasonOtherText) {
h += st::newGroupDescriptionPadding.top() + _reasonOtherText->height() + st::newGroupDescriptionPadding.bottom();
}
setMaxHeight(h);
}

View file

@ -0,0 +1,72 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "abstractbox.h"
class ReportBox : public AbstractBox, public RPCSender {
Q_OBJECT
public:
ReportBox(ChannelData *channel);
private slots:
void onReport();
void onChange();
void onDescriptionResized();
protected:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void showAll() override {
showChildren();
}
void hideAll() override {
hideChildren();
}
void setInnerFocus() override;
private:
void updateMaxHeight();
void reportDone(const MTPBool &result);
bool reportFail(const RPCError &error);
ChannelData *_channel;
ChildWidget<Radiobutton> _reasonSpam;
ChildWidget<Radiobutton> _reasonViolence;
ChildWidget<Radiobutton> _reasonPornography;
ChildWidget<Radiobutton> _reasonOther;
ChildWidget<InputArea> _reasonOtherText = { nullptr };
ChildWidget<BoxButton> _report, _cancel;
enum Reason {
ReasonSpam,
ReasonViolence,
ReasonPornography,
ReasonOther,
};
mtpRequestId _requestId = 0;
};

View file

@ -289,7 +289,7 @@ QString Generator::valueAssignmentCode(structure::Value value) const {
} break; } break;
case Tag::Icon: { case Tag::Icon: {
auto v(value.Icon()); auto v(value.Icon());
if (v.parts.empty()) return QString(); if (v.parts.empty()) return QString("{}");
QStringList parts; QStringList parts;
for (const auto &part : v.parts) { for (const auto &part : v.parts) {

View file

@ -535,8 +535,8 @@ structure::Value ParsedFile::readPointValue() {
if (tokenValue(font) == "point") { if (tokenValue(font) == "point") {
assertNextToken(BasicType::LeftParenthesis); assertNextToken(BasicType::LeftParenthesis);
auto x = readNumericValue(); assertNextToken(BasicType::Comma); auto x = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma);
auto y = readNumericValue(); auto y = readNumericOrNumericCopyValue();
if (x.type().tag != structure::TypeTag::Pixels || if (x.type().tag != structure::TypeTag::Pixels ||
y.type().tag != structure::TypeTag::Pixels) { y.type().tag != structure::TypeTag::Pixels) {
logErrorTypeMismatch() << "expected two px values for the point"; logErrorTypeMismatch() << "expected two px values for the point";
@ -581,8 +581,8 @@ structure::Value ParsedFile::readSizeValue() {
if (tokenValue(font) == "size") { if (tokenValue(font) == "size") {
assertNextToken(BasicType::LeftParenthesis); assertNextToken(BasicType::LeftParenthesis);
auto w = readNumericValue(); assertNextToken(BasicType::Comma); auto w = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma);
auto h = readNumericValue(); auto h = readNumericOrNumericCopyValue();
if (w.type().tag != structure::TypeTag::Pixels || if (w.type().tag != structure::TypeTag::Pixels ||
h.type().tag != structure::TypeTag::Pixels) { h.type().tag != structure::TypeTag::Pixels) {
logErrorTypeMismatch() << "expected two px values for the size"; logErrorTypeMismatch() << "expected two px values for the size";
@ -662,10 +662,10 @@ structure::Value ParsedFile::readMarginsValue() {
if (tokenValue(font) == "margins") { if (tokenValue(font) == "margins") {
assertNextToken(BasicType::LeftParenthesis); assertNextToken(BasicType::LeftParenthesis);
auto l = readNumericValue(); assertNextToken(BasicType::Comma); auto l = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma);
auto t = readNumericValue(); assertNextToken(BasicType::Comma); auto t = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma);
auto r = readNumericValue(); assertNextToken(BasicType::Comma); auto r = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma);
auto b = readNumericValue(); auto b = readNumericOrNumericCopyValue();
if (l.type().tag != structure::TypeTag::Pixels || if (l.type().tag != structure::TypeTag::Pixels ||
t.type().tag != structure::TypeTag::Pixels || t.type().tag != structure::TypeTag::Pixels ||
r.type().tag != structure::TypeTag::Pixels || r.type().tag != structure::TypeTag::Pixels ||
@ -701,18 +701,10 @@ structure::Value ParsedFile::readFontValue() {
file_.putBack(); file_.putBack();
} }
} }
if (auto familyValue = readStringValue()) { if (auto familyValue = readStringOrStringCopyValue()) {
family = familyValue; family = familyValue;
} else if (auto sizeValue = readNumericValue()) { } else if (auto sizeValue = readNumericOrNumericCopyValue()) {
size = sizeValue; size = sizeValue;
} else if (auto copyValue = readCopyValue()) {
if (copyValue.type().tag == structure::TypeTag::String) {
family = copyValue;
} else if (copyValue.type().tag == structure::TypeTag::Pixels) {
size = copyValue;
} else {
logErrorUnexpectedToken() << "font family, font size or ')'";
}
} else if (file_.getToken(BasicType::RightParenthesis)) { } else if (file_.getToken(BasicType::RightParenthesis)) {
break; break;
} else { } else {
@ -776,7 +768,37 @@ structure::Value ParsedFile::readCopyValue() {
if (auto variable = module_->findVariable(name)) { if (auto variable = module_->findVariable(name)) {
return variable->value.makeCopy(variable->name); return variable->value.makeCopy(variable->name);
} }
logError(kErrorIdentifierNotFound) << "identifier '" << logFullName(name) << "' not found"; file_.putBack();
}
return {};
}
structure::Value ParsedFile::readNumericOrNumericCopyValue() {
if (auto result = readNumericValue()) {
return result;
} else if (auto copy = readCopyValue()) {
auto type = copy.type().tag;
if (type == structure::TypeTag::Int
|| type == structure::TypeTag::Double
|| type == structure::TypeTag::Pixels) {
return copy;
} else {
file_.putBack();
}
}
return {};
}
structure::Value ParsedFile::readStringOrStringCopyValue() {
if (auto result = readStringValue()) {
return result;
} else if (auto copy = readCopyValue()) {
auto type = copy.type().tag;
if (type == structure::TypeTag::String) {
return copy;
} else {
file_.putBack();
}
} }
return {}; return {};
} }

View file

@ -98,6 +98,9 @@ private:
structure::Value readIconValue(); structure::Value readIconValue();
structure::Value readCopyValue(); structure::Value readCopyValue();
structure::Value readNumericOrNumericCopyValue();
structure::Value readStringOrStringCopyValue();
structure::data::monoicon readMonoIconFields(); structure::data::monoicon readMonoIconFields();
QString readMonoIconFilename(); QString readMonoIconFilename();
@ -131,6 +134,7 @@ private:
{ "align" , { structure::TypeTag::Align } }, { "align" , { structure::TypeTag::Align } },
{ "margins" , { structure::TypeTag::Margins } }, { "margins" , { structure::TypeTag::Margins } },
{ "font" , { structure::TypeTag::Font } }, { "font" , { structure::TypeTag::Font } },
{ "icon" , { structure::TypeTag::Icon } },
}; };
}; };

View file

@ -74,14 +74,15 @@ bool Processor::write(const structure::Module &module) const {
QFileInfo srcFile(module.filepath()); QFileInfo srcFile(module.filepath());
QString dstFilePath = dir.absolutePath() + '/' + destFileBaseName(module); QString dstFilePath = dir.absolutePath() + '/' + destFileBaseName(module);
bool forceReGenerate = false;// !options_.rebuildDependencies;
common::ProjectInfo project = { common::ProjectInfo project = {
"codegen_style", "codegen_style",
srcFile.fileName(), srcFile.fileName(),
"stdafx.h", "stdafx.h",
false,//!options_.rebuildDependencies, // forceReGenerate forceReGenerate
}; };
SpriteGenerator spriteGenerator(module); SpriteGenerator spriteGenerator(module, forceReGenerate);
if (!spriteGenerator.writeSprites()) { if (!spriteGenerator.writeSprites()) {
return false; return false;
} }

View file

@ -48,8 +48,9 @@ constexpr int kErrorCouldNotWrite = 845;
} // namespace } // namespace
SpriteGenerator::SpriteGenerator(const structure::Module &module) SpriteGenerator::SpriteGenerator(const structure::Module &module, bool forceReGenerate)
: module_(module) : module_(module)
, forceReGenerate_(forceReGenerate)
, basePath_(QFileInfo(module.filepath()).dir().absolutePath()) { , basePath_(QFileInfo(module.filepath()).dir().absolutePath()) {
} }
@ -84,7 +85,7 @@ bool SpriteGenerator::writeSprites() {
} }
} }
QFile file(filepath); QFile file(filepath);
if (file.open(QIODevice::ReadOnly)) { if (!forceReGenerate_ && file.open(QIODevice::ReadOnly)) {
if (file.readAll() == spriteData) { if (file.readAll() == spriteData) {
continue; continue;
} }

View file

@ -34,7 +34,7 @@ class Module;
class SpriteGenerator { class SpriteGenerator {
public: public:
SpriteGenerator(const structure::Module &module); SpriteGenerator(const structure::Module &module, bool forceReGenerate);
SpriteGenerator(const SpriteGenerator &other) = delete; SpriteGenerator(const SpriteGenerator &other) = delete;
SpriteGenerator &operator=(const SpriteGenerator &other) = delete; SpriteGenerator &operator=(const SpriteGenerator &other) = delete;
@ -46,6 +46,7 @@ private:
QImage generateSprite(int scale); // scale = 5 for 125% and 6 for 150%. QImage generateSprite(int scale); // scale = 5 for 125% and 6 for 150%.
const structure::Module &module_; const structure::Module &module_;
bool forceReGenerate_;
QString basePath_; QString basePath_;
QImage sprite2x_; QImage sprite2x_;
QList<structure::Variable> sprites_; QList<structure::Variable> sprites_;

View file

@ -44,7 +44,6 @@ uint64 _SharedMemoryLocation[4] = { 0x00, 0x01, 0x02, 0x03 };
#include <openssl/rand.h> #include <openssl/rand.h>
// Base types compile-time check // Base types compile-time check
static_assert(sizeof(char) == 1, "Basic types size check failed"); static_assert(sizeof(char) == 1, "Basic types size check failed");
static_assert(sizeof(uchar) == 1, "Basic types size check failed"); static_assert(sizeof(uchar) == 1, "Basic types size check failed");
static_assert(sizeof(int16) == 2, "Basic types size check failed"); static_assert(sizeof(int16) == 2, "Basic types size check failed");

View file

@ -33,6 +33,13 @@ T *getPointerAndReset(T *&ptr) {
return result; return result;
} }
template <typename T>
T createAndSwap(T &value) {
T result;
std::swap(result, value);
return result;
}
struct NullType { struct NullType {
}; };
@ -421,6 +428,24 @@ inline bool operator!=(std::nullptr_t a, const unique_ptr<T> &b) noexcept {
return !(a == b); return !(a == b);
} }
using _yes = char(&)[1];
using _no = char(&)[2];
template <typename Base, typename Derived>
struct _host {
operator Base*() const;
operator Derived*();
};
template <typename Base, typename Derived>
struct is_base_of {
template <typename T>
static _yes check(Derived*, T);
static _no check(Base*, int);
static constexpr bool value = sizeof(check(_host<Base, Derived>(), int())) == sizeof(_yes);
};
} // namespace std_ } // namespace std_
#include "logs.h" #include "logs.h"
@ -927,15 +952,6 @@ private:
}; };
template <typename I>
inline void destroyImplementation(I *&ptr) {
if (ptr) {
ptr->destroy();
ptr = 0;
}
deleteAndMark(ptr);
}
class Composer; class Composer;
typedef void(*ComponentConstruct)(void *location, Composer *composer); typedef void(*ComponentConstruct)(void *location, Composer *composer);
typedef void(*ComponentDestruct)(void *location); typedef void(*ComponentDestruct)(void *location);
@ -1170,213 +1186,96 @@ public:
virtual R call(Args... args) const = 0; virtual R call(Args... args) const = 0;
virtual ~SharedCallback() { virtual ~SharedCallback() {
} }
typedef QSharedPointer<SharedCallback<R, Args...>> Ptr; using Ptr = QSharedPointer<SharedCallback<R, Args...>>;
}; };
template <typename R> template <typename R, typename... Args>
class FunctionImplementation { class FunctionImplementation {
public: public:
virtual R call() = 0; virtual R call(Args... args) = 0;
virtual void destroy() { delete this; } virtual void destroy() { delete this; }
virtual ~FunctionImplementation() {} virtual ~FunctionImplementation() {}
}; };
template <typename R>
class NullFunctionImplementation : public FunctionImplementation<R> { template <typename R, typename... Args>
class NullFunctionImplementation : public FunctionImplementation<R, Args...> {
public: public:
virtual R call() { return R(); } virtual R call(Args... args) { return R(); }
virtual void destroy() {} virtual void destroy() {}
static NullFunctionImplementation<R> SharedInstance; static NullFunctionImplementation<R, Args...> SharedInstance;
}; };
template <typename R> template <typename R, typename... Args>
NullFunctionImplementation<R> NullFunctionImplementation<R>::SharedInstance; NullFunctionImplementation<R, Args...> NullFunctionImplementation<R, Args...>::SharedInstance;
template <typename R>
class FunctionCreator { template <typename R, typename... Args>
public:
FunctionCreator(FunctionImplementation<R> *ptr) : _ptr(ptr) {}
FunctionCreator(const FunctionCreator<R> &other) : _ptr(other.create()) {}
FunctionImplementation<R> *create() const { return getPointerAndReset(_ptr); }
~FunctionCreator() { destroyImplementation(_ptr); }
private:
FunctionCreator<R> &operator=(const FunctionCreator<R> &other);
mutable FunctionImplementation<R> *_ptr;
};
template <typename R>
class Function { class Function {
public: public:
typedef FunctionCreator<R> Creator; Function() : _implementation(nullImpl()) {}
static Creator Null() { return Creator(&NullFunctionImplementation<R>::SharedInstance); } Function(FunctionImplementation<R, Args...> *implementation) : _implementation(implementation) {}
Function(const Creator &creator) : _implementation(creator.create()) {} Function(const Function<R, Args...> &other) = delete;
R call() { return _implementation->call(); } Function<R, Args...> &operator=(const Function<R, Args...> &other) = delete;
~Function() { destroyImplementation(_implementation); } Function(Function<R, Args...> &&other) : _implementation(other._implementation) {
other._implementation = nullImpl();
}
Function<R, Args...> &operator=(Function<R, Args...> &&other) {
std::swap(_implementation, other._implementation);
return *this;
}
bool isNull() const {
return (_implementation == nullImpl());
}
R call(Args... args) { return _implementation->call(args...); }
~Function() {
if (_implementation) {
_implementation->destroy();
_implementation = nullptr;
}
deleteAndMark(_implementation);
}
private: private:
Function(const Function<R> &other); static FunctionImplementation<R, Args...> *nullImpl() {
Function<R> &operator=(const Function<R> &other); return &NullFunctionImplementation<R, Args...>::SharedInstance;
FunctionImplementation<R> *_implementation; }
FunctionImplementation<R, Args...> *_implementation;
}; };
template <typename R> template <typename R, typename... Args>
class WrappedFunction : public FunctionImplementation<R> { class WrappedFunction : public FunctionImplementation<R, Args...> {
public: public:
typedef R(*Method)(); using Method = R(*)(Args... args);
WrappedFunction(Method method) : _method(method) {} WrappedFunction(Method method) : _method(method) {}
virtual R call() { return (*_method)(); } virtual R call(Args... args) { return (*_method)(args...); }
private: private:
Method _method; Method _method;
}; };
template <typename R> template <typename R, typename... Args>
inline FunctionCreator<R> func(R(*method)()) { inline Function<R, Args...> func(R(*method)(Args... args)) {
return FunctionCreator<R>(new WrappedFunction<R>(method)); return Function<R, Args...>(new WrappedFunction<R, Args...>(method));
} }
template <typename O, typename I, typename R>
class ObjectFunction : public FunctionImplementation<R> { template <typename O, typename I, typename R, typename... Args>
class ObjectFunction : public FunctionImplementation<R, Args...> {
public: public:
typedef R(I::*Method)(); using Method = R(I::*)(Args... args);
ObjectFunction(O *obj, Method method) : _obj(obj), _method(method) {} ObjectFunction(O *obj, Method method) : _obj(obj), _method(method) {}
virtual R call() { return (_obj->*_method)(); } virtual R call(Args... args) { return (_obj->*_method)(args...); }
private: private:
O *_obj; O *_obj;
Method _method; Method _method;
};
template <typename O, typename I, typename R>
inline FunctionCreator<R> func(O *obj, R(I::*method)()) {
return FunctionCreator<R>(new ObjectFunction<O, I, R>(obj, method));
}
template <typename R, typename A1>
class Function1Implementation {
public:
virtual R call(A1 a1) = 0;
virtual void destroy() { delete this; }
virtual ~Function1Implementation() {}
}; };
template <typename R, typename A1> template <typename O, typename I, typename R, typename... Args>
class NullFunction1Implementation : public Function1Implementation<R, A1> { inline Function<R, Args...> func(O *obj, R(I::*method)(Args...)) {
public: return Function<R, Args...>(new ObjectFunction<O, I, R, Args...>(obj, method));
virtual R call(A1 a1) { return R(); }
virtual void destroy() {}
static NullFunction1Implementation<R, A1> SharedInstance;
};
template <typename R, typename A1>
NullFunction1Implementation<R, A1> NullFunction1Implementation<R, A1>::SharedInstance;
template <typename R, typename A1>
class Function1Creator {
public:
Function1Creator(Function1Implementation<R, A1> *ptr) : _ptr(ptr) {}
Function1Creator(const Function1Creator<R, A1> &other) : _ptr(other.create()) {}
Function1Implementation<R, A1> *create() const { return getPointerAndReset(_ptr); }
~Function1Creator() { destroyImplementation(_ptr); }
private:
Function1Creator<R, A1> &operator=(const Function1Creator<R, A1> &other);
mutable Function1Implementation<R, A1> *_ptr;
};
template <typename R, typename A1>
class Function1 {
public:
typedef Function1Creator<R, A1> Creator;
static Creator Null() { return Creator(&NullFunction1Implementation<R, A1>::SharedInstance); }
Function1(const Creator &creator) : _implementation(creator.create()) {}
R call(A1 a1) { return _implementation->call(a1); }
~Function1() { _implementation->destroy(); }
private:
Function1(const Function1<R, A1> &other);
Function1<R, A1> &operator=(const Function1<R, A1> &other);
Function1Implementation<R, A1> *_implementation;
};
template <typename R, typename A1>
class WrappedFunction1 : public Function1Implementation<R, A1> {
public:
typedef R(*Method)(A1);
WrappedFunction1(Method method) : _method(method) {}
virtual R call(A1 a1) { return (*_method)(a1); }
private:
Method _method;
};
template <typename R, typename A1>
inline Function1Creator<R, A1> func(R(*method)(A1)) {
return Function1Creator<R, A1>(new WrappedFunction1<R, A1>(method));
}
template <typename O, typename I, typename R, typename A1>
class ObjectFunction1 : public Function1Implementation<R, A1> {
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 <typename O, typename I, typename R, typename A1>
Function1Creator<R, A1> func(O *obj, R(I::*method)(A1)) {
return Function1Creator<R, A1>(new ObjectFunction1<O, I, R, A1>(obj, method));
}
template <typename R, typename A1, typename A2>
class Function2Implementation {
public:
virtual R call(A1 a1, A2 a2) = 0;
virtual void destroy() { delete this; }
virtual ~Function2Implementation() {}
};
template <typename R, typename A1, typename A2>
class NullFunction2Implementation : public Function2Implementation<R, A1, A2> {
public:
virtual R call(A1 a1, A2 a2) { return R(); }
virtual void destroy() {}
static NullFunction2Implementation<R, A1, A2> SharedInstance;
};
template <typename R, typename A1, typename A2>
NullFunction2Implementation<R, A1, A2> NullFunction2Implementation<R, A1, A2>::SharedInstance;
template <typename R, typename A1, typename A2>
class Function2Creator {
public:
Function2Creator(Function2Implementation<R, A1, A2> *ptr) : _ptr(ptr) {}
Function2Creator(const Function2Creator<R, A1, A2> &other) : _ptr(other.create()) {}
Function2Implementation<R, A1, A2> *create() const { return getPointerAndReset(_ptr); }
~Function2Creator() { destroyImplementation(_ptr); }
private:
Function2Creator<R, A1, A2> &operator=(const Function2Creator<R, A1, A2> &other);
mutable Function2Implementation<R, A1, A2> *_ptr;
};
template <typename R, typename A1, typename A2>
class Function2 {
public:
typedef Function2Creator<R, A1, A2> Creator;
static Creator Null() { return Creator(&NullFunction2Implementation<R, A1, A2>::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<R, A1, A2> &other);
Function2<R, A1, A2> &operator=(const Function2<R, A1, A2> &other);
Function2Implementation<R, A1, A2> *_implementation;
};
template <typename R, typename A1, typename A2>
class WrappedFunction2 : public Function2Implementation<R, A1, A2> {
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 <typename R, typename A1, typename A2>
Function2Creator<R, A1, A2> func(R(*method)(A1, A2)) {
return Function2Creator<R, A1, A2>(new WrappedFunction2<R, A1, A2>(method));
}
template <typename O, typename I, typename R, typename A1, typename A2>
class ObjectFunction2 : public Function2Implementation<R, A1, A2> {
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 <typename O, typename I, typename R, typename A1, typename A2>
Function2Creator<R, A1, A2> func(O *obj, R(I::*method)(A1, A2)) {
return Function2Creator<R, A1, A2>(new ObjectFunction2<O, I, R, A1, A2>(obj, method));
} }

View file

@ -27,6 +27,7 @@ enum ExpandLinksMode {
ExpandLinksNone, ExpandLinksNone,
ExpandLinksShortened, ExpandLinksShortened,
ExpandLinksAll, ExpandLinksAll,
ExpandLinksUrlOnly, // For custom urls leaves only url instead of text.
}; };
class ClickHandlerHost { class ClickHandlerHost {
@ -144,9 +145,11 @@ public:
} }
static void hostDestroyed(ClickHandlerHost *host) { static void hostDestroyed(ClickHandlerHost *host) {
if (_activeHost == host) { if (_activeHost == host) {
if (_active) (*_active).clear();
_activeHost = nullptr; _activeHost = nullptr;
} }
if (_pressedHost == host) { if (_pressedHost == host) {
if (_pressed) (*_pressed).clear();
_pressedHost = nullptr; _pressedHost = nullptr;
} }
} }

View file

@ -110,15 +110,22 @@ QString HiddenUrlClickHandler::getExpandedLinkText(ExpandLinksMode mode, const Q
QString result; QString result;
if (mode == ExpandLinksAll) { if (mode == ExpandLinksAll) {
result = textPart.toString() + qsl(" (") + url() + ')'; result = textPart.toString() + qsl(" (") + url() + ')';
} else if (mode == ExpandLinksUrlOnly) {
result = url();
} }
return result; return result;
} }
TextWithEntities HiddenUrlClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const { TextWithEntities HiddenUrlClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const {
TextWithEntities result; TextWithEntities result;
result.entities.push_back({ EntityInTextCustomUrl, entityOffset, textPart.size(), url() }); if (mode == ExpandLinksUrlOnly) {
if (mode == ExpandLinksAll) { result.text = url();
result.text = textPart.toString() + qsl(" (") + url() + ')'; result.entities.push_back({ EntityInTextUrl, entityOffset, result.text.size() });
} else {
result.entities.push_back({ EntityInTextCustomUrl, entityOffset, textPart.size(), url() });
if (mode == ExpandLinksAll) {
result.text = textPart.toString() + qsl(" (") + url() + ')';
}
} }
return result; return result;
} }
@ -174,9 +181,19 @@ TextWithEntities HashtagClickHandler::getExpandedLinkTextWithEntities(ExpandLink
return simpleTextWithEntity({ EntityInTextHashtag, entityOffset, textPart.size() }); return simpleTextWithEntity({ EntityInTextHashtag, entityOffset, textPart.size() });
} }
PeerData *BotCommandClickHandler::_peer = nullptr;
UserData *BotCommandClickHandler::_bot = nullptr;
void BotCommandClickHandler::onClick(Qt::MouseButton button) const { void BotCommandClickHandler::onClick(Qt::MouseButton button) const {
if (button == Qt::LeftButton || button == Qt::MiddleButton) { if (button == Qt::LeftButton || button == Qt::MiddleButton) {
if (PeerData *peer = Ui::getPeerForMouseAction()) { if (auto peer = peerForCommand()) {
if (auto bot = peer->isUser() ? peer->asUser() : botForCommand()) {
Ui::showPeerHistory(peer, ShowAtTheEndMsgId);
App::sendBotCommand(peer, bot, _cmd);
return;
}
}
if (auto peer = Ui::getPeerForMouseAction()) { // old way
UserData *bot = peer->isUser() ? peer->asUser() : nullptr; UserData *bot = peer->isUser() ? peer->asUser() : nullptr;
if (auto item = App::hoveredLinkItem()) { if (auto item = App::hoveredLinkItem()) {
if (!bot) { if (!bot) {

View file

@ -193,6 +193,8 @@ private:
}; };
class PeerData;
class UserData;
class BotCommandClickHandler : public TextClickHandler { class BotCommandClickHandler : public TextClickHandler {
public: public:
BotCommandClickHandler(const QString &cmd) : _cmd(cmd) { BotCommandClickHandler(const QString &cmd) : _cmd(cmd) {
@ -204,14 +206,30 @@ public:
return _cmd; return _cmd;
} }
static void setPeerForCommand(PeerData *peer) {
_peer = peer;
}
static void setBotForCommand(UserData *bot) {
_bot = bot;
}
TextWithEntities getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override; TextWithEntities getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override;
protected: protected:
QString url() const override { QString url() const override {
return _cmd; return _cmd;
} }
static PeerData *peerForCommand() {
return _peer;
}
static UserData *botForCommand() {
return _bot;
}
private: private:
QString _cmd; QString _cmd;
static PeerData *_peer;
static UserData *_bot;
}; };

View file

@ -0,0 +1,115 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "core/observer.h"
namespace Notify {
namespace internal {
namespace {
struct StartCallbackData {
void *that;
StartCallback call;
};
struct FinishCallbackData {
void *that;
FinishCallback call;
};
struct UnregisterCallbackData {
void *that;
UnregisterCallback call;
};
using StartCallbacksList = QVector<StartCallbackData>;
using FinishCallbacksList = QVector<FinishCallbackData>;
NeverFreedPointer<StartCallbacksList> StartCallbacks;
NeverFreedPointer<FinishCallbacksList> FinishCallbacks;
UnregisterCallbackData UnregisterCallbacks[256]/* = { nullptr }*/;
ObservedEvent LastRegisteredEvent/* = 0*/;
} // namespace
} // namespace internal
void startObservers() {
if (!internal::StartCallbacks) return;
for (auto &callback : *internal::StartCallbacks) {
callback.call(callback.that);
}
}
void finishObservers() {
if (!internal::FinishCallbacks) return;
for (auto &callback : *internal::FinishCallbacks) {
callback.call(callback.that);
}
internal::StartCallbacks.clear();
internal::FinishCallbacks.clear();
}
namespace internal {
BaseObservedEventRegistrator::BaseObservedEventRegistrator(void *that
, StartCallback startCallback
, FinishCallback finishCallback
, UnregisterCallback unregisterCallback) {
_event = LastRegisteredEvent++;
StartCallbacks.makeIfNull();
StartCallbacks->push_back({ that, startCallback });
FinishCallbacks.makeIfNull();
FinishCallbacks->push_back({ that, finishCallback });
UnregisterCallbacks[_event] = { that, unregisterCallback };
}
} // namespace internal
// Observer base interface.
Observer::~Observer() {
for_const (auto connection, _connections) {
unregisterObserver(connection);
}
}
void Observer::observerRegistered(ConnectionId connection) {
_connections.push_back(connection);
}
void unregisterObserver(ConnectionId connection) {
auto event = static_cast<internal::ObservedEvent>(connection >> 24);
auto connectionIndex = int(connection & 0x00FFFFFFU) - 1;
auto &callback = internal::UnregisterCallbacks[event];
if (connectionIndex >= 0 && callback.call && callback.that) {
callback.call(callback.that, connectionIndex);
}
}
namespace internal {
void observerRegisteredDefault(Observer *observer, ConnectionId connection) {
observer->observerRegistered(connection);
}
} // namespace internal
} // namespace Notify

View file

@ -0,0 +1,242 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "core/vector_of_moveable.h"
namespace Notify {
using ConnectionId = uint32;
// startObservers() must be called after main() started (not in a global variable constructor).
// finishObservers() must be called before main() finished (not in a global variable destructor).
void startObservers();
void finishObservers();
using StartObservedEventCallback = void(*)();
using FinishObservedEventCallback = void(*)();
namespace internal {
using ObservedEvent = uchar;
using StartCallback = void(*)(void*);
using FinishCallback = void(*)(void*);
using UnregisterCallback = void(*)(void*,int connectionIndex);
class BaseObservedEventRegistrator {
public:
BaseObservedEventRegistrator(void *that
, StartCallback startCallback
, FinishCallback finishCallback
, UnregisterCallback unregisterCallback);
protected:
inline ObservedEvent event() const {
return _event;
}
private:
ObservedEvent _event;
};
// Handler is one of Function<> instantiations.
template <typename Flags, typename Handler>
struct ObserversList {
struct Entry {
Flags flags;
Handler handler;
};
std_::vector_of_moveable<Entry> entries;
QVector<int> freeIndices;
};
// If no filtering by flags is done, you can use Flags=int and this value.
constexpr int UniversalFlag = 0x01;
} // namespace internal
// Objects of this class should be constructed in global scope.
// startCallback will be called from Notify::startObservers().
// finishCallback will be called from Notify::finishObservers().
template <typename Flags, typename Handler>
class ObservedEventRegistrator : public internal::BaseObservedEventRegistrator {
public:
ObservedEventRegistrator(StartObservedEventCallback startCallback,
FinishObservedEventCallback finishCallback) : internal::BaseObservedEventRegistrator(reinterpret_cast<void*>(this),
ObservedEventRegistrator<Flags, Handler>::start,
ObservedEventRegistrator<Flags, Handler>::finish,
ObservedEventRegistrator<Flags, Handler>::unregister)
, _startCallback(startCallback), _finishCallback(finishCallback) {
}
bool started() const {
return _list != nullptr;
}
ConnectionId registerObserver(Flags flags, Handler &&handler) {
t_assert(started());
int connectionIndex = doRegisterObserver(flags, std_::forward<Handler>(handler));
return (static_cast<uint32>(event()) << 24) | static_cast<uint32>(connectionIndex + 1);
}
template <typename... Args>
void notify(Flags flags, Args&&... args) {
t_assert(started());
for (auto &entry : _list->entries) {
if (!entry.handler.isNull() && (flags & entry.flags)) {
entry.handler.call(std_::forward<Args>(args)...);
}
}
}
private:
using Self = ObservedEventRegistrator<Flags, Handler>;
static void start(void *vthat) {
Self *that = reinterpret_cast<Self*>(vthat);
t_assert(!that->started());
if (that->_startCallback) that->_startCallback();
that->_list = new internal::ObserversList<Flags, Handler>();
}
static void finish(void *vthat) {
Self *that = reinterpret_cast<Self*>(vthat);
if (that->_finishCallback) that->_finishCallback();
delete that->_list;
that->_list = nullptr;
}
static void unregister(void *vthat, int connectionIndex) {
Self *that = reinterpret_cast<Self*>(vthat);
t_assert(that->started());
auto &entries(that->_list->entries);
if (entries.size() <= connectionIndex) return;
if (entries.size() == connectionIndex + 1) {
for (entries.pop_back(); !entries.isEmpty() && entries.back().handler.isNull();) {
entries.pop_back();
}
} else {
entries[connectionIndex].handler = Handler();
that->_list->freeIndices.push_back(connectionIndex);
}
}
int doRegisterObserver(Flags flags, Handler &&handler) {
while (!_list->freeIndices.isEmpty()) {
auto freeIndex = _list->freeIndices.back();
_list->freeIndices.pop_back();
if (freeIndex < _list->entries.size()) {
_list->entries[freeIndex] = { flags, std_::move(handler) };
return freeIndex;
}
}
_list->entries.push_back({ flags, std_::move(handler) });
return _list->entries.size() - 1;
}
StartObservedEventCallback _startCallback;
FinishObservedEventCallback _finishCallback;
internal::ObserversList<Flags, Handler> *_list = nullptr;
};
// If no filtering of notifications by Flags is intended use this class.
template <typename Handler>
class SimpleObservedEventRegistrator {
public:
SimpleObservedEventRegistrator(StartObservedEventCallback startCallback,
FinishObservedEventCallback finishCallback) : _implementation(startCallback, finishCallback) {
}
bool started() const {
return _implementation.started();
}
ConnectionId registerObserver(Handler &&handler) {
return _implementation.registerObserver(internal::UniversalFlag, std_::forward<Handler>(handler));
}
template <typename... Args>
void notify(Args&&... args) {
return _implementation.notify(internal::UniversalFlag, std_::forward<Args>(args)...);
}
private:
ObservedEventRegistrator<int, Handler> _implementation;
};
// Each observer type should have observerRegistered(Notify::ConnectionId connection) method.
// Usually it is done by deriving the type from the Notify::Observer base class.
// In destructor it should call Notify::unregisterObserver(connection) for all the connections.
class Observer;
namespace internal {
void observerRegisteredDefault(Observer *observer, ConnectionId connection);
} // namespace internal
void unregisterObserver(ConnectionId connection);
class Observer {
public:
virtual ~Observer() = 0;
private:
void observerRegistered(ConnectionId connection);
friend void internal::observerRegisteredDefault(Observer *observer, ConnectionId connection);
QVector<ConnectionId> _connections;
};
namespace internal {
template <typename ObserverType, int>
struct ObserverRegisteredGeneric {
static inline void call(ObserverType *observer, ConnectionId connection) {
observer->observerRegistered(connection);
}
};
template <typename ObserverType>
struct ObserverRegisteredGeneric<ObserverType, true> {
static inline void call(ObserverType *observer, ConnectionId connection) {
observerRegisteredDefault(observer, connection);
}
};
} // namespace internal
template <typename ObserverType>
inline void observerRegistered(ObserverType *observer, ConnectionId connection) {
// For derivatives of the Observer class we call special friend function observerRegistered().
// For all other classes we call just a member function observerRegistered().
using ObserverRegistered = internal::ObserverRegisteredGeneric<ObserverType, std_::is_base_of<Observer, ObserverType>::value>;
ObserverRegistered::call(observer, connection);
}
} // namespace Notify

View file

@ -0,0 +1,153 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
// some minimal implementation of std::vector() for moveable (but not copiable) types.
namespace std_ {
template <typename T>
class vector_of_moveable {
typedef vector_of_moveable<T> Self;
int _size = 0, _capacity = 0;
void *_plaindata = nullptr;
public:
inline T *data() {
return reinterpret_cast<T*>(_plaindata);
}
inline const T *data() const {
return reinterpret_cast<const T*>(_plaindata);
}
inline bool operator==(const Self &other) const {
if (this == &other) return true;
if (_size != other._size) return false;
for (int i = 0; i < _size; ++i) {
if (data()[i] != other.data()[i]) {
return false;
}
}
return true;
}
inline bool operator!=(const Self &other) const { return !(*this == other); }
inline int size() const { return _size; }
inline bool isEmpty() const { return _size == 0; }
inline void clear() {
for (int i = 0; i < _size; ++i) {
data()[i].~T();
}
_size = 0;
operator delete[](_plaindata);
_plaindata = nullptr;
_capacity = 0;
}
typedef T *iterator;
typedef const T *const_iterator;
// STL style
inline iterator begin() { return data(); }
inline const_iterator begin() const { return data(); }
inline const_iterator cbegin() const { return data(); }
inline iterator end() { return data() + _size; }
inline const_iterator end() const { return data() + _size; }
inline const_iterator cend() const { return data() + _size; }
inline iterator erase(iterator it) {
T tmp = std_::move(*it);
for (auto next = it + 1, e = end(); next != e; ++next) {
auto prev = next - 1;
*prev = std_::move(*next);
}
--_size;
return it;
}
inline iterator insert(const_iterator pos, T &&value) {
int insertAtIndex = pos - begin();
if (_size + 1 > _capacity) {
reallocate(_capacity + (_capacity > 1 ? _capacity / 2 : 1));
}
auto insertAt = begin() + insertAtIndex, e = end();
if (insertAt == e) {
new (&(*insertAt)) T(std_::move(value));
} else {
auto prev = e - 1;
new (&(*e)) T(std_::move(*prev));
for (auto it = prev; it != insertAt; --it) {
*it = std_::move(*--prev);
}
*insertAt = std_::move(value);
}
++_size;
return insertAt;
}
inline void push_back(T &&value) {
insert(end(), std_::forward<T>(value));
}
inline void pop_back() {
erase(end() - 1);
}
inline T &front() {
return *begin();
}
inline const T &front() const {
return *begin();
}
inline T &back() {
return *(end() - 1);
}
inline const T &back() const {
return *(end() - 1);
}
inline bool empty() const { return _size == 0; }
inline T &operator[](int index) {
return data()[index];
}
inline const T &operator[](int index) const {
return data()[index];
}
inline const T &at(int index) const {
if (index < 0 || index >= _size) {
throw std::out_of_range("");
}
return data()[index];
}
inline ~vector_of_moveable() {
clear();
}
private:
void reallocate(int newCapacity) {
auto newPlainData = operator new[](newCapacity * sizeof(T));
for (int i = 0; i < _size; ++i) {
*(reinterpret_cast<T*>(newPlainData) + i) = std_::move(*(data() + i));
}
std::swap(_plaindata, newPlainData);
_capacity = newCapacity;
operator delete[](newPlainData);
}
};
} // namespace std_

View file

@ -22,7 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "core/basic_types.h" #include "core/basic_types.h"
#define BETA_VERSION_MACRO (0ULL) #define BETA_VERSION_MACRO (9049001ULL)
constexpr int AppVersion = 9049; constexpr int AppVersion = 9049;
constexpr str_const AppVersionStr = "0.9.49"; constexpr str_const AppVersionStr = "0.9.49";

View file

@ -641,7 +641,7 @@ void DialogsInner::contextMenuEvent(QContextMenuEvent *e) {
_menu->addAction(lang(lng_profile_clear_history), this, SLOT(onContextClearHistory()))->setEnabled(true); _menu->addAction(lang(lng_profile_clear_history), this, SLOT(onContextClearHistory()))->setEnabled(true);
_menu->addAction(lang(lng_profile_delete_conversation), this, SLOT(onContextDeleteAndLeave()))->setEnabled(true); _menu->addAction(lang(lng_profile_delete_conversation), this, SLOT(onContextDeleteAndLeave()))->setEnabled(true);
if (_menuPeer->asUser()->access != UserNoAccess && _menuPeer != App::self()) { if (_menuPeer->asUser()->access != UserNoAccess && _menuPeer != App::self()) {
_menu->addAction(lang((_menuPeer->asUser()->blocked == UserIsBlocked) ? (_menuPeer->asUser()->botInfo ? lng_profile_unblock_bot : lng_profile_unblock_user) : (_menuPeer->asUser()->botInfo ? lng_profile_block_bot : lng_profile_block_user)), this, SLOT(onContextToggleBlock()))->setEnabled(true); _menu->addAction(lang(_menuPeer->asUser()->isBlocked() ? (_menuPeer->asUser()->botInfo ? lng_profile_unblock_bot : lng_profile_unblock_user) : (_menuPeer->asUser()->botInfo ? lng_profile_block_bot : lng_profile_block_user)), this, SLOT(onContextToggleBlock()))->setEnabled(true);
connect(App::main(), SIGNAL(peerUpdated(PeerData*)), this, SLOT(peerUpdated(PeerData*))); connect(App::main(), SIGNAL(peerUpdated(PeerData*)), this, SLOT(peerUpdated(PeerData*)));
} }
} else if (_menuPeer->isChat()) { } else if (_menuPeer->isChat()) {
@ -662,7 +662,7 @@ bool DialogsInner::menuPeerMuted() {
void DialogsInner::onContextProfile() { void DialogsInner::onContextProfile() {
if (!_menuPeer) return; if (!_menuPeer) return;
App::main()->showPeerProfile(_menuPeer); Ui::showPeerProfile(_menuPeer);
} }
void DialogsInner::onContextToggleNotifications() { void DialogsInner::onContextToggleNotifications() {
@ -718,7 +718,7 @@ void DialogsInner::onContextDeleteAndLeaveSure() {
void DialogsInner::onContextToggleBlock() { void DialogsInner::onContextToggleBlock() {
if (!_menuPeer || !_menuPeer->isUser()) return; if (!_menuPeer || !_menuPeer->isUser()) return;
if (_menuPeer->asUser()->blocked == UserIsBlocked) { if (_menuPeer->asUser()->isBlocked()) {
MTP::send(MTPcontacts_Unblock(_menuPeer->asUser()->inputUser), rpcDone(&DialogsInner::contextBlockDone, qMakePair(_menuPeer->asUser(), false))); MTP::send(MTPcontacts_Unblock(_menuPeer->asUser()->inputUser), rpcDone(&DialogsInner::contextBlockDone, qMakePair(_menuPeer->asUser(), false)));
} else { } else {
MTP::send(MTPcontacts_Block(_menuPeer->asUser()->inputUser), rpcDone(&DialogsInner::contextBlockDone, qMakePair(_menuPeer->asUser(), true))); MTP::send(MTPcontacts_Block(_menuPeer->asUser()->inputUser), rpcDone(&DialogsInner::contextBlockDone, qMakePair(_menuPeer->asUser(), true)));
@ -726,7 +726,7 @@ void DialogsInner::onContextToggleBlock() {
} }
void DialogsInner::contextBlockDone(QPair<UserData*, bool> data, const MTPBool &result) { void DialogsInner::contextBlockDone(QPair<UserData*, bool> data, const MTPBool &result) {
data.first->blocked = data.second ? UserIsBlocked : UserIsNotBlocked; data.first->setBlockStatus(data.second ? UserData::BlockStatus::Blocked : UserData::BlockStatus::NotBlocked);
emit App::main()->peerUpdated(data.first); emit App::main()->peerUpdated(data.first);
} }
@ -935,7 +935,7 @@ void DialogsInner::updateNotifySettings(PeerData *peer) {
void DialogsInner::peerUpdated(PeerData *peer) { void DialogsInner::peerUpdated(PeerData *peer) {
if (_menu && _menuPeer == peer && _menuPeer->isUser() && _menu->actions().size() > 5) { if (_menu && _menuPeer == peer && _menuPeer->isUser() && _menu->actions().size() > 5) {
_menu->actions().at(5)->setText(lang((_menuPeer->asUser()->blocked == UserIsBlocked) ? (_menuPeer->asUser()->botInfo ? lng_profile_unblock_bot : lng_profile_unblock_user) : (_menuPeer->asUser()->botInfo ? lng_profile_block_bot : lng_profile_block_user))); _menu->actions().at(5)->setText(lang(_menuPeer->asUser()->isBlocked() ? (_menuPeer->asUser()->botInfo ? lng_profile_unblock_bot : lng_profile_unblock_user) : (_menuPeer->asUser()->botInfo ? lng_profile_block_bot : lng_profile_block_user)));
} }
} }
@ -1842,27 +1842,32 @@ void DialogsWidget::dialogsToUp() {
} }
} }
void DialogsWidget::animShow(const QPixmap &bgAnimCache) { void DialogsWidget::showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams &params) {
if (App::app()) App::app()->mtpPause(); if (App::app()) App::app()->mtpPause();
bool back = true; _cacheUnder = params.oldContentCache;
(back ? _cacheOver : _cacheUnder) = bgAnimCache; show();
_cacheOver = App::main()->grabForShowAnimation(params);
_a_show.stop(); _a_show.stop();
(back ? _cacheUnder : _cacheOver) = myGrab(this);
_scroll.hide(); _scroll.hide();
_filter.hide(); _filter.hide();
_cancelSearch.hide(); _cancelSearch.hide();
_newGroup.hide(); _newGroup.hide();
a_coordUnder = back ? anim::ivalue(-qFloor(st::slideShift * width()), 0) : anim::ivalue(0, -qFloor(st::slideShift * width())); int delta = st::slideShift;
a_coordOver = back ? anim::ivalue(0, width()) : anim::ivalue(width(), 0); if (direction == Window::SlideDirection::FromLeft) {
a_shadow = back ? anim::fvalue(1, 0) : anim::fvalue(0, 1); a_progress = anim::fvalue(1, 0);
std::swap(_cacheUnder, _cacheOver);
a_coordUnder = anim::ivalue(-delta, 0);
a_coordOver = anim::ivalue(0, width());
} else {
a_progress = anim::fvalue(0, 1);
a_coordUnder = anim::ivalue(0, -delta);
a_coordOver = anim::ivalue(width(), 0);
}
_a_show.start(); _a_show.start();
show();
} }
void DialogsWidget::step_show(float64 ms, bool timer) { void DialogsWidget::step_show(float64 ms, bool timer) {
@ -1872,7 +1877,7 @@ void DialogsWidget::step_show(float64 ms, bool timer) {
a_coordUnder.finish(); a_coordUnder.finish();
a_coordOver.finish(); a_coordOver.finish();
a_shadow.finish(); a_progress.finish();
_cacheUnder = _cacheOver = QPixmap(); _cacheUnder = _cacheOver = QPixmap();
@ -1887,7 +1892,7 @@ void DialogsWidget::step_show(float64 ms, bool timer) {
} else { } else {
a_coordUnder.update(dt, st::slideFunction); a_coordUnder.update(dt, st::slideFunction);
a_coordOver.update(dt, st::slideFunction); a_coordOver.update(dt, st::slideFunction);
a_shadow.update(dt, st::slideFunction); a_progress.update(dt, st::slideFunction);
} }
if (timer) update(); if (timer) update();
} }
@ -2474,15 +2479,16 @@ void DialogsWidget::paintEvent(QPaintEvent *e) {
p.setClipRect(r); p.setClipRect(r);
} }
if (_a_show.animating()) { if (_a_show.animating()) {
int retina = cIntRetinaFactor();
if (a_coordOver.current() > 0) { if (a_coordOver.current() > 0) {
p.drawPixmap(QRect(0, 0, a_coordOver.current(), height()), _cacheUnder, QRect(-a_coordUnder.current() * cRetinaFactor(), 0, a_coordOver.current() * cRetinaFactor(), height() * cRetinaFactor())); p.drawPixmap(QRect(0, 0, a_coordOver.current(), _cacheUnder.height() / retina), _cacheUnder, QRect(-a_coordUnder.current() * retina, 0, a_coordOver.current() * retina, _cacheUnder.height()));
p.setOpacity(a_shadow.current() * st::slideFadeOut); p.setOpacity(a_progress.current() * st::slideFadeOut);
p.fillRect(0, 0, a_coordOver.current(), height(), st::black->b); p.fillRect(0, 0, a_coordOver.current(), _cacheUnder.height() / retina, st::black);
p.setOpacity(1); p.setOpacity(1);
} }
p.drawPixmap(a_coordOver.current(), 0, _cacheOver); p.drawPixmap(QRect(a_coordOver.current(), 0, _cacheOver.width() / retina, _cacheOver.height() / retina), _cacheOver, QRect(0, 0, _cacheOver.width(), _cacheOver.height()));
p.setOpacity(a_shadow.current()); p.setOpacity(a_progress.current());
p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), height()), App::sprite(), st::slideShadow.rect()); p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), _cacheOver.height() / retina), App::sprite(), st::slideShadow.rect());
return; return;
} }
QRect above(0, 0, width(), _scroll.y()); QRect above(0, 0, width(), _scroll.y());

View file

@ -20,6 +20,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/ */
#pragma once #pragma once
#include "window/section_widget.h"
class MainWidget; class MainWidget;
namespace Dialogs { namespace Dialogs {
class Row; class Row;
@ -260,7 +262,10 @@ public:
void dialogsToUp(); void dialogsToUp();
void animShow(const QPixmap &bgAnimCache); bool hasTopBarShadow() const {
return true;
}
void showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams &params);
void step_show(float64 ms, bool timer); void step_show(float64 ms, bool timer);
void destroyData(); void destroyData();
@ -338,7 +343,7 @@ private:
Animation _a_show; Animation _a_show;
QPixmap _cacheUnder, _cacheOver; QPixmap _cacheUnder, _cacheOver;
anim::ivalue a_coordUnder, a_coordOver; anim::ivalue a_coordUnder, a_coordOver;
anim::fvalue a_shadow; anim::fvalue a_progress;
PeerData *_searchInPeer, *_searchInMigrated; PeerData *_searchInPeer, *_searchInMigrated;

View file

@ -20,10 +20,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/ */
#include "stdafx.h" #include "stdafx.h"
#include "profile/profile_section_memento.h"
#include "core/vector_of_moveable.h"
#include "core/click_handler_types.h"
#include "observer_peer.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "application.h" #include "application.h"
#include "core/click_handler_types.h"
#include "boxes/confirmbox.h" #include "boxes/confirmbox.h"
#include "layerwidget.h" #include "layerwidget.h"
#include "lang.h" #include "lang.h"
@ -237,7 +240,15 @@ void autoplayMediaInlineAsync(const FullMsgId &msgId) {
} }
void showPeerProfile(const PeerId &peer) { void showPeerProfile(const PeerId &peer) {
if (MainWidget *m = App::main()) m->showPeerProfile(App::peer(peer)); if (auto m = App::main()) {
m->showWideSection(Profile::SectionMemento(App::peer(peer)));
}
}
void showPeerOverview(const PeerId &peer, MediaOverviewType type) {
if (auto m = App::main()) {
m->showMediaOverview(App::peer(peer), type);
}
} }
void showPeerHistory(const PeerId &peer, MsgId msgId, bool back) { void showPeerHistory(const PeerId &peer, MsgId msgId, bool back) {
@ -273,6 +284,15 @@ bool hideWindowNoQuit() {
return false; return false;
} }
bool skipPaintEvent(QWidget *widget, QPaintEvent *event) {
if (auto w = App::wnd()) {
if (w->contentOverlapped(widget, event)) {
return true;
}
}
return false;
}
} // namespace Ui } // namespace Ui
namespace Notify { namespace Notify {
@ -286,7 +306,10 @@ void userIsContactChanged(UserData *user, bool fromThisApp) {
} }
void botCommandsChanged(UserData *user) { void botCommandsChanged(UserData *user) {
if (MainWidget *m = App::main()) m->notify_botCommandsChanged(user); if (MainWidget *m = App::main()) {
m->notify_botCommandsChanged(user);
}
peerUpdatedDelayed(user, PeerUpdate::Flag::BotCommandsChanged);
} }
void inlineBotRequesting(bool requesting) { void inlineBotRequesting(bool requesting) {
@ -500,6 +523,8 @@ struct Data {
uint64 LaunchId = 0; uint64 LaunchId = 0;
SingleDelayedCall HandleHistoryUpdate = { App::app(), "call_handleHistoryUpdate" }; SingleDelayedCall HandleHistoryUpdate = { App::app(), "call_handleHistoryUpdate" };
SingleDelayedCall HandleUnreadCounterUpdate = { App::app(), "call_handleUnreadCounterUpdate" }; SingleDelayedCall HandleUnreadCounterUpdate = { App::app(), "call_handleUnreadCounterUpdate" };
SingleDelayedCall HandleFileDialogQueue = { App::app(), "call_handleFileDialogQueue" };
SingleDelayedCall HandleDelayedPeerUpdates = { App::app(), "call_handleDelayedPeerUpdates" };
Adaptive::Layout AdaptiveLayout = Adaptive::NormalLayout; Adaptive::Layout AdaptiveLayout = Adaptive::NormalLayout;
bool AdaptiveForWide = true; bool AdaptiveForWide = true;
@ -563,6 +588,8 @@ void finish() {
DefineReadOnlyVar(Global, uint64, LaunchId); DefineReadOnlyVar(Global, uint64, LaunchId);
DefineRefVar(Global, SingleDelayedCall, HandleHistoryUpdate); DefineRefVar(Global, SingleDelayedCall, HandleHistoryUpdate);
DefineRefVar(Global, SingleDelayedCall, HandleUnreadCounterUpdate); DefineRefVar(Global, SingleDelayedCall, HandleUnreadCounterUpdate);
DefineRefVar(Global, SingleDelayedCall, HandleFileDialogQueue);
DefineRefVar(Global, SingleDelayedCall, HandleDelayedPeerUpdates);
DefineVar(Global, Adaptive::Layout, AdaptiveLayout); DefineVar(Global, Adaptive::Layout, AdaptiveLayout);
DefineVar(Global, bool, AdaptiveForWide); DefineVar(Global, bool, AdaptiveForWide);

View file

@ -75,6 +75,14 @@ inline void showPeerProfile(const History *history) {
showPeerProfile(history->peer->id); showPeerProfile(history->peer->id);
} }
void showPeerOverview(const PeerId &peer, MediaOverviewType type);
inline void showPeerOverview(const PeerData *peer, MediaOverviewType type) {
showPeerOverview(peer->id, type);
}
inline void showPeerOverview(const History *history, MediaOverviewType type) {
showPeerOverview(history->peer->id, type);
}
void showPeerHistory(const PeerId &peer, MsgId msgId, bool back = false); void showPeerHistory(const PeerId &peer, MsgId msgId, bool back = false);
inline void showPeerHistory(const PeerData *peer, MsgId msgId, bool back = false) { inline void showPeerHistory(const PeerData *peer, MsgId msgId, bool back = false) {
showPeerHistory(peer->id, msgId, back); showPeerHistory(peer->id, msgId, back);
@ -96,6 +104,8 @@ PeerData *getPeerForMouseAction();
bool hideWindowNoQuit(); bool hideWindowNoQuit();
bool skipPaintEvent(QWidget *widget, QPaintEvent *event);
} // namespace Ui } // namespace Ui
enum ClipStopperType { enum ClipStopperType {
@ -194,6 +204,8 @@ void finish();
DeclareReadOnlyVar(uint64, LaunchId); DeclareReadOnlyVar(uint64, LaunchId);
DeclareRefVar(SingleDelayedCall, HandleHistoryUpdate); DeclareRefVar(SingleDelayedCall, HandleHistoryUpdate);
DeclareRefVar(SingleDelayedCall, HandleUnreadCounterUpdate); DeclareRefVar(SingleDelayedCall, HandleUnreadCounterUpdate);
DeclareRefVar(SingleDelayedCall, HandleFileDialogQueue);
DeclareRefVar(SingleDelayedCall, HandleDelayedPeerUpdates);
DeclareVar(Adaptive::Layout, AdaptiveLayout); DeclareVar(Adaptive::Layout, AdaptiveLayout);
DeclareVar(bool, AdaptiveForWide); DeclareVar(bool, AdaptiveForWide);

View file

@ -36,6 +36,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "apiwrap.h" #include "apiwrap.h"
#include "window/top_bar_widget.h" #include "window/top_bar_widget.h"
#include "playerwidget.h" #include "playerwidget.h"
#include "observer_peer.h"
namespace { namespace {
@ -751,6 +752,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
if (peer->asChannel()->mgInfo->lastParticipants.indexOf(user) < 0) { if (peer->asChannel()->mgInfo->lastParticipants.indexOf(user) < 0) {
peer->asChannel()->mgInfo->lastParticipants.push_front(user); peer->asChannel()->mgInfo->lastParticipants.push_front(user);
peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsAdminsOutdated; peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsAdminsOutdated;
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged);
} }
if (user->botInfo) { if (user->botInfo) {
peer->asChannel()->mgInfo->bots.insert(user); peer->asChannel()->mgInfo->bots.insert(user);
@ -769,6 +771,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
if (result->from()->isUser()) { if (result->from()->isUser()) {
if (peer->asChannel()->mgInfo->lastParticipants.indexOf(result->from()->asUser()) < 0) { if (peer->asChannel()->mgInfo->lastParticipants.indexOf(result->from()->asUser()) < 0) {
peer->asChannel()->mgInfo->lastParticipants.push_front(result->from()->asUser()); peer->asChannel()->mgInfo->lastParticipants.push_front(result->from()->asUser());
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged);
} }
if (result->from()->asUser()->botInfo) { if (result->from()->asUser()->botInfo) {
peer->asChannel()->mgInfo->bots.insert(result->from()->asUser()); peer->asChannel()->mgInfo->bots.insert(result->from()->asUser());
@ -793,26 +796,31 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
if (App::main()) App::main()->updateBotKeyboard(this); if (App::main()) App::main()->updateBotKeyboard(this);
} }
if (peer->isMegagroup()) { if (peer->isMegagroup()) {
if (UserData *user = App::userLoaded(uid)) { if (auto user = App::userLoaded(uid)) {
int32 index = peer->asChannel()->mgInfo->lastParticipants.indexOf(user); auto channel = peer->asChannel();
auto megagroupInfo = channel->mgInfo;
int32 index = megagroupInfo->lastParticipants.indexOf(user);
if (index >= 0) { if (index >= 0) {
peer->asChannel()->mgInfo->lastParticipants.removeAt(index); megagroupInfo->lastParticipants.removeAt(index);
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged);
} }
if (peer->asChannel()->count > 1) { if (peer->asChannel()->membersCount() > 1) {
--peer->asChannel()->count; peer->asChannel()->setMembersCount(channel->membersCount() - 1);
} else { } else {
peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated; megagroupInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated;
peer->asChannel()->mgInfo->lastParticipantsCount = 0; megagroupInfo->lastParticipantsCount = 0;
} }
if (peer->asChannel()->mgInfo->lastAdmins.contains(user)) { if (megagroupInfo->lastAdmins.contains(user)) {
peer->asChannel()->mgInfo->lastAdmins.remove(user); megagroupInfo->lastAdmins.remove(user);
if (peer->asChannel()->adminsCount > 1) { if (channel->adminsCount() > 1) {
--peer->asChannel()->adminsCount; channel->setAdminsCount(channel->adminsCount() - 1);
} }
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::AdminsChanged);
} }
peer->asChannel()->mgInfo->bots.remove(user); megagroupInfo->bots.remove(user);
if (peer->asChannel()->mgInfo->bots.isEmpty() && peer->asChannel()->mgInfo->botStatus > 0) { if (megagroupInfo->bots.isEmpty() && megagroupInfo->botStatus > 0) {
peer->asChannel()->mgInfo->botStatus = -1; megagroupInfo->botStatus = -1;
} }
} }
} }
@ -848,9 +856,10 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
} break; } break;
case mtpc_messageActionChatEditTitle: { case mtpc_messageActionChatEditTitle: {
const auto &d(action.c_messageActionChatEditTitle()); auto &d(action.c_messageActionChatEditTitle());
ChatData *chat = peer->asChat(); if (auto chat = peer->asChat()) {
if (chat) chat->updateName(qs(d.vtitle), QString(), QString()); chat->setName(qs(d.vtitle));
}
} break; } break;
case mtpc_messageActionChatMigrateTo: { case mtpc_messageActionChatMigrateTo: {
@ -1019,6 +1028,9 @@ HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) {
if (prev) { if (prev) {
lastAuthors->push_front(adding->from()->asUser()); lastAuthors->push_front(adding->from()->asUser());
} }
if (peer->isMegagroup()) {
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged);
}
} }
} }
if (adding->definesReplyKeyboard()) { if (adding->definesReplyKeyboard()) {
@ -1194,6 +1206,7 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
lastAuthors->push_back(item->from()->asUser()); lastAuthors->push_back(item->from()->asUser());
if (peer->isMegagroup()) { if (peer->isMegagroup()) {
peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsAdminsOutdated; peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsAdminsOutdated;
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged);
} }
} }
} }
@ -1704,6 +1717,7 @@ void History::getReadyFor(MsgId msgId) {
} }
if (!isReadyFor(msgId)) { if (!isReadyFor(msgId)) {
clear(true); clear(true);
if (msgId == ShowAtTheEndMsgId) { if (msgId == ShowAtTheEndMsgId) {
newLoaded = true; newLoaded = true;
} }
@ -2847,14 +2861,14 @@ HistoryItem::~HistoryItem() {
} }
} }
RadialAnimation::RadialAnimation(AnimationCreator creator) RadialAnimation::RadialAnimation(AnimationCallbacks &&callbacks)
: _firstStart(0) : _firstStart(0)
, _lastStart(0) , _lastStart(0)
, _lastTime(0) , _lastTime(0)
, _opacity(0) , _opacity(0)
, a_arcEnd(0, 0) , a_arcEnd(0, 0)
, a_arcStart(0, FullArcLength) , a_arcStart(0, FullArcLength)
, _animation(creator) { , _animation(std_::move(callbacks)) {
} }

View file

@ -1590,7 +1590,7 @@ protected:
class RadialAnimation { class RadialAnimation {
public: public:
RadialAnimation(AnimationCreator creator); RadialAnimation(AnimationCallbacks &&callbacks);
float64 opacity() const { float64 opacity() const {
return _opacity; return _opacity;
@ -1811,9 +1811,9 @@ protected:
virtual bool dataLoaded() const = 0; virtual bool dataLoaded() const = 0;
struct AnimationData { struct AnimationData {
AnimationData(AnimationCreator thumbOverCallbacks, AnimationCreator radialCallbacks) : a_thumbOver(0, 0) AnimationData(AnimationCallbacks &&thumbOverCallbacks, AnimationCallbacks &&radialCallbacks) : a_thumbOver(0, 0)
, _a_thumbOver(thumbOverCallbacks) , _a_thumbOver(std_::move(thumbOverCallbacks))
, radial(radialCallbacks) { , radial(std_::move(radialCallbacks)) {
} }
anim::fvalue a_thumbOver; anim::fvalue a_thumbOver;
Animation _a_thumbOver; Animation _a_thumbOver;

View file

@ -39,6 +39,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "localstorage.h" #include "localstorage.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "window/top_bar_widget.h" #include "window/top_bar_widget.h"
#include "observer_peer.h"
#include "playerwidget.h" #include "playerwidget.h"
namespace { namespace {
@ -658,7 +659,7 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt
_selected.insert(_dragItem, selStatus); _selected.insert(_dragItem, selStatus);
_dragSymbol = dragState.symbol; _dragSymbol = dragState.symbol;
_dragAction = Selecting; _dragAction = Selecting;
_dragSelType = TextSelectParagraphs; _dragSelType = TextSelectType::Paragraphs;
dragActionUpdate(_dragPos); dragActionUpdate(_dragPos);
_trippleClickTimer.start(QApplication::doubleClickInterval()); _trippleClickTimer.start(QApplication::doubleClickInterval());
} }
@ -668,7 +669,7 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt
request.flags = Text::StateRequest::Flag::LookupSymbol; request.flags = Text::StateRequest::Flag::LookupSymbol;
dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request); dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request);
} }
if (_dragSelType != TextSelectParagraphs) { if (_dragSelType != TextSelectType::Paragraphs) {
if (App::pressedItem()) { if (App::pressedItem()) {
_dragSymbol = dragState.symbol; _dragSymbol = dragState.symbol;
bool uponSelected = (dragState.cursor == HistoryInTextCursorState); bool uponSelected = (dragState.cursor == HistoryInTextCursorState);
@ -715,7 +716,7 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt
if (!_dragItem) { if (!_dragItem) {
_dragAction = NoDrag; _dragAction = NoDrag;
} else if (_dragAction == NoDrag) { } else if (_dragAction == NoDrag) {
_dragItem = 0; _dragItem = nullptr;
} }
} }
@ -776,6 +777,9 @@ void HistoryInner::onDragExec() {
mimeData->setData(qsl("application/x-td-forward-selected"), "1"); mimeData->setData(qsl("application/x-td-forward-selected"), "1");
} }
drag->setMimeData(mimeData); drag->setMimeData(mimeData);
// We don't receive mouseReleaseEvent when drag is finished.
ClickHandler::unpressed();
drag->exec(Qt::CopyAction); drag->exec(Qt::CopyAction);
if (App::main()) App::main()->updateAfterDrag(); if (App::main()) App::main()->updateAfterDrag();
return; return;
@ -810,6 +814,9 @@ void HistoryInner::onDragExec() {
} }
drag->setMimeData(mimeData); drag->setMimeData(mimeData);
// We don't receive mouseReleaseEvent when drag is finished.
ClickHandler::unpressed();
drag->exec(Qt::CopyAction); drag->exec(Qt::CopyAction);
if (App::main()) App::main()->updateAfterDrag(); if (App::main()) App::main()->updateAfterDrag();
return; return;
@ -906,7 +913,7 @@ void HistoryInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton but
} }
_dragAction = NoDrag; _dragAction = NoDrag;
_dragItem = 0; _dragItem = 0;
_dragSelType = TextSelectLetters; _dragSelType = TextSelectType::Letters;
_widget->noSelectingScroll(); _widget->noSelectingScroll();
_widget->updateTopBarSelection(); _widget->updateTopBarSelection();
} }
@ -922,13 +929,13 @@ void HistoryInner::mouseDoubleClickEvent(QMouseEvent *e) {
if (!_history) return; if (!_history) return;
dragActionStart(e->globalPos(), e->button()); dragActionStart(e->globalPos(), e->button());
if (((_dragAction == Selecting && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection) || (_dragAction == NoDrag && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection))) && _dragSelType == TextSelectLetters && _dragItem) { if (((_dragAction == Selecting && !_selected.isEmpty() && _selected.cbegin().value() != FullSelection) || (_dragAction == NoDrag && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection))) && _dragSelType == TextSelectType::Letters && _dragItem) {
HistoryStateRequest request; HistoryStateRequest request;
request.flags |= Text::StateRequest::Flag::LookupSymbol; request.flags |= Text::StateRequest::Flag::LookupSymbol;
auto dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request); auto dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request);
if (dragState.cursor == HistoryInTextCursorState) { if (dragState.cursor == HistoryInTextCursorState) {
_dragSymbol = dragState.symbol; _dragSymbol = dragState.symbol;
_dragSelType = TextSelectWords; _dragSelType = TextSelectType::Words;
if (_dragAction == NoDrag) { if (_dragAction == NoDrag) {
_dragAction = Selecting; _dragAction = Selecting;
TextSelection selStatus = { dragState.symbol, dragState.symbol }; TextSelection selStatus = { dragState.symbol, dragState.symbol };
@ -1787,7 +1794,7 @@ void HistoryInner::onUpdateSelected() {
bool canSelectMany = (_history != nullptr); bool canSelectMany = (_history != nullptr);
if (selectingText) { if (selectingText) {
uint16 second = dragState.symbol; uint16 second = dragState.symbol;
if (dragState.afterSymbol && _dragSelType == TextSelectLetters) { if (dragState.afterSymbol && _dragSelType == TextSelectType::Letters) {
++second; ++second;
} }
auto selState = _dragItem->adjustSelection({ qMin(second, _dragSymbol), qMax(second, _dragSymbol) }, _dragSelType); auto selState = _dragItem->adjustSelection({ qMin(second, _dragSymbol), qMax(second, _dragSymbol) }, _dragSelType);
@ -2816,7 +2823,6 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent)
, _attachDragPhoto(this) , _attachDragPhoto(this)
, _fileLoader(this, FileLoaderQueueStopTimeout) , _fileLoader(this, FileLoaderQueueStopTimeout)
, _a_show(animation(this, &HistoryWidget::step_show)) , _a_show(animation(this, &HistoryWidget::step_show))
, _sideShadow(this, st::shadowColor)
, _topShadow(this, st::shadowColor) { , _topShadow(this, st::shadowColor) {
_scroll.setFocusPolicy(Qt::NoFocus); _scroll.setFocusPolicy(Qt::NoFocus);
@ -2937,7 +2943,6 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent)
_attachDragPhoto.hide(); _attachDragPhoto.hide();
_topShadow.hide(); _topShadow.hide();
_sideShadow.setVisible(!Adaptive::OneColumn());
connect(&_attachDragDocument, SIGNAL(dropped(const QMimeData*)), this, SLOT(onDocumentDrop(const QMimeData*))); connect(&_attachDragDocument, SIGNAL(dropped(const QMimeData*)), this, SLOT(onDocumentDrop(const QMimeData*)));
connect(&_attachDragPhoto, SIGNAL(dropped(const QMimeData*)), this, SLOT(onPhotoDrop(const QMimeData*))); connect(&_attachDragPhoto, SIGNAL(dropped(const QMimeData*)), this, SLOT(onPhotoDrop(const QMimeData*)));
@ -3837,7 +3842,7 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re
if (_channel) { if (_channel) {
updateNotifySettings(); updateNotifySettings();
if (_peer->notify == UnknownNotifySettings) { if (_peer->notify == UnknownNotifySettings) {
App::wnd()->getNotifySetting(MTP_inputNotifyPeer(_peer->input)); App::api()->requestNotifySetting(_peer);
} }
} }
@ -4059,7 +4064,9 @@ bool HistoryWidget::reportSpamSettingFail(const RPCError &error, mtpRequestId re
} }
void HistoryWidget::updateControlsVisibility() { void HistoryWidget::updateControlsVisibility() {
_topShadow.setVisible(_peer ? true : false); if (!_a_show.animating()) {
_topShadow.setVisible(_peer ? true : false);
}
if (!_history || _a_show.animating()) { if (!_history || _a_show.animating()) {
_reportSpamPanel.hide(); _reportSpamPanel.hide();
_scroll.hide(); _scroll.hide();
@ -4455,6 +4462,7 @@ void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages
} else if (_migrated) { } else if (_migrated) {
_migrated->clear(true); _migrated->clear(true);
} }
_delayedShowAtRequest = 0; _delayedShowAtRequest = 0;
_history->getReadyFor(_delayedShowAtMsgId); _history->getReadyFor(_delayedShowAtMsgId);
if (_history->isEmpty()) { if (_history->isEmpty()) {
@ -4822,7 +4830,7 @@ void HistoryWidget::onSend(bool ctrlShiftEnter, MsgId replyTo) {
void HistoryWidget::onUnblock() { void HistoryWidget::onUnblock() {
if (_unblockRequest) return; if (_unblockRequest) return;
if (!_peer || !_peer->isUser() || _peer->asUser()->blocked != UserIsBlocked) { if (!_peer || !_peer->isUser() || !_peer->asUser()->isBlocked()) {
updateControlsVisibility(); updateControlsVisibility();
return; return;
} }
@ -4833,7 +4841,7 @@ void HistoryWidget::onUnblock() {
void HistoryWidget::unblockDone(PeerData *peer, const MTPBool &result, mtpRequestId req) { void HistoryWidget::unblockDone(PeerData *peer, const MTPBool &result, mtpRequestId req) {
if (!peer->isUser()) return; if (!peer->isUser()) return;
if (_unblockRequest == req) _unblockRequest = 0; if (_unblockRequest == req) _unblockRequest = 0;
peer->asUser()->blocked = UserIsNotBlocked; peer->asUser()->setBlockStatus(UserData::BlockStatus::NotBlocked);
emit App::main()->peerUpdated(peer); emit App::main()->peerUpdated(peer);
} }
@ -4847,7 +4855,7 @@ bool HistoryWidget::unblockFail(const RPCError &error, mtpRequestId req) {
void HistoryWidget::blockDone(PeerData *peer, const MTPBool &result) { void HistoryWidget::blockDone(PeerData *peer, const MTPBool &result) {
if (!peer->isUser()) return; if (!peer->isUser()) return;
peer->asUser()->blocked = UserIsBlocked; peer->asUser()->setBlockStatus(UserData::BlockStatus::Blocked);
emit App::main()->peerUpdated(peer); emit App::main()->peerUpdated(peer);
} }
@ -4911,12 +4919,14 @@ void HistoryWidget::onBroadcastSilentChange() {
} }
void HistoryWidget::onShareContact(const PeerId &peer, UserData *contact) { void HistoryWidget::onShareContact(const PeerId &peer, UserData *contact) {
if (!contact || contact->phone.isEmpty()) return; auto phone = contact->phone();
if (phone.isEmpty()) phone = App::phoneFromSharedContact(peerToUser(contact->id));
if (!contact || phone.isEmpty()) return;
Ui::showPeerHistory(peer, ShowAtTheEndMsgId); Ui::showPeerHistory(peer, ShowAtTheEndMsgId);
if (!_history) return; if (!_history) return;
shareContact(peer, contact->phone, contact->firstName, contact->lastName, replyToId(), peerToUser(contact->id)); shareContact(peer, phone, contact->firstName, contact->lastName, replyToId(), peerToUser(contact->id));
} }
void HistoryWidget::shareContact(const PeerId &peer, const QString &phone, const QString &fname, const QString &lname, MsgId replyTo, int32 userId) { void HistoryWidget::shareContact(const PeerId &peer, const QString &phone, const QString &fname, const QString &lname, MsgId replyTo, int32 userId) {
@ -4993,15 +5003,15 @@ MsgId HistoryWidget::msgId() const {
return _showAtMsgId; return _showAtMsgId;
} }
void HistoryWidget::animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTopBarCache, bool back) { void HistoryWidget::showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams &params) {
if (App::app()) App::app()->mtpPause(); if (App::app()) App::app()->mtpPause();
(back ? _cacheOver : _cacheUnder) = bgAnimCache; _cacheUnder = params.oldContentCache;
(back ? _cacheTopBarOver : _cacheTopBarUnder) = bgAnimTopBarCache; show();
(back ? _cacheUnder : _cacheOver) = myGrab(this); _topShadow.setVisible(params.withTopBarShadow ? false : true);
App::main()->topBar()->stopAnim(); _cacheOver = App::main()->grabForShowAnimation(params);
(back ? _cacheTopBarUnder : _cacheTopBarOver) = myGrab(App::main()->topBar());
App::main()->topBar()->startAnim(); App::main()->topBar()->startAnim();
_topShadow.setVisible(params.withTopBarShadow ? true : false);
_scroll.hide(); _scroll.hide();
_kbScroll.hide(); _kbScroll.hide();
@ -5023,18 +5033,26 @@ void HistoryWidget::animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTo
_botStart.hide(); _botStart.hide();
_joinChannel.hide(); _joinChannel.hide();
_muteUnmute.hide(); _muteUnmute.hide();
_topShadow.hide();
if (_pinnedBar) { if (_pinnedBar) {
_pinnedBar->shadow.hide(); _pinnedBar->shadow.hide();
_pinnedBar->cancel.hide(); _pinnedBar->cancel.hide();
} }
a_coordUnder = back ? anim::ivalue(-qFloor(st::slideShift * width()), 0) : anim::ivalue(0, -qFloor(st::slideShift * width())); int delta = st::slideShift;
a_coordOver = back ? anim::ivalue(0, width()) : anim::ivalue(width(), 0); if (direction == Window::SlideDirection::FromLeft) {
a_shadow = back ? anim::fvalue(1, 0) : anim::fvalue(0, 1); a_progress = anim::fvalue(1, 0);
std::swap(_cacheUnder, _cacheOver);
a_coordUnder = anim::ivalue(-delta, 0);
a_coordOver = anim::ivalue(0, width());
} else {
a_progress = anim::fvalue(0, 1);
a_coordUnder = anim::ivalue(0, -delta);
a_coordOver = anim::ivalue(width(), 0);
}
_a_show.start(); _a_show.start();
App::main()->topBar()->update(); App::main()->topBar()->update();
activate(); activate();
} }
@ -5042,13 +5060,12 @@ void HistoryWidget::step_show(float64 ms, bool timer) {
float64 dt = ms / st::slideDuration; float64 dt = ms / st::slideDuration;
if (dt >= 1) { if (dt >= 1) {
_a_show.stop(); _a_show.stop();
_sideShadow.setVisible(!Adaptive::OneColumn());
_topShadow.setVisible(_peer ? true : false); _topShadow.setVisible(_peer ? true : false);
a_coordUnder.finish(); a_coordUnder.finish();
a_coordOver.finish(); a_coordOver.finish();
a_shadow.finish(); a_progress.finish();
_cacheUnder = _cacheOver = _cacheTopBarUnder = _cacheTopBarOver = QPixmap(); _cacheUnder = _cacheOver = QPixmap();
App::main()->topBar()->stopAnim(); App::main()->topBar()->stopAnim();
doneShow(); doneShow();
@ -5056,7 +5073,7 @@ void HistoryWidget::step_show(float64 ms, bool timer) {
} else { } else {
a_coordUnder.update(dt, st::slideFunction); a_coordUnder.update(dt, st::slideFunction);
a_coordOver.update(dt, st::slideFunction); a_coordOver.update(dt, st::slideFunction);
a_shadow.update(dt, st::slideFunction); a_progress.update(dt, st::slideFunction);
} }
if (timer) { if (timer) {
update(); update();
@ -5081,14 +5098,12 @@ void HistoryWidget::doneShow() {
} }
void HistoryWidget::updateAdaptiveLayout() { void HistoryWidget::updateAdaptiveLayout() {
_sideShadow.setVisible(!Adaptive::OneColumn());
update(); update();
} }
void HistoryWidget::animStop() { void HistoryWidget::animStop() {
if (!_a_show.animating()) return; if (!_a_show.animating()) return;
_a_show.stop(); _a_show.stop();
_sideShadow.setVisible(!Adaptive::OneColumn());
_topShadow.setVisible(_peer ? true : false); _topShadow.setVisible(_peer ? true : false);
} }
@ -5450,8 +5465,7 @@ DragState HistoryWidget::getDragState(const QMimeData *d) {
for (QList<QUrl>::const_iterator i = urls.cbegin(), en = urls.cend(); i != en; ++i) { for (QList<QUrl>::const_iterator i = urls.cbegin(), en = urls.cend(); i != en; ++i) {
if (!i->isLocalFile()) return DragStateNone; if (!i->isLocalFile()) return DragStateNone;
QString file(i->toLocalFile()); auto file = psConvertFileUrl(*i);
if (file.startsWith(qsl("/.file/id="))) file = psConvertFileUrl(file);
QFileInfo info(file); QFileInfo info(file);
if (info.isDir()) return DragStateNone; if (info.isDir()) return DragStateNone;
@ -5569,7 +5583,7 @@ bool HistoryWidget::isBotStart() const {
} }
bool HistoryWidget::isBlocked() const { bool HistoryWidget::isBlocked() const {
return _peer && _peer->isUser() && _peer->asUser()->blocked == UserIsBlocked; return _peer && _peer->isUser() && _peer->asUser()->isBlocked();
} }
bool HistoryWidget::isJoinChannel() const { bool HistoryWidget::isJoinChannel() const {
@ -5769,9 +5783,15 @@ void HistoryWidget::onForwardHere() {
void HistoryWidget::paintTopBar(Painter &p, float64 over, int32 decreaseWidth) { void HistoryWidget::paintTopBar(Painter &p, float64 over, int32 decreaseWidth) {
if (_a_show.animating()) { if (_a_show.animating()) {
p.drawPixmap(a_coordUnder.current(), 0, _cacheTopBarUnder); int retina = cIntRetinaFactor();
p.drawPixmap(a_coordOver.current(), 0, _cacheTopBarOver); if (a_coordOver.current() > 0) {
p.setOpacity(a_shadow.current()); p.drawPixmap(QRect(0, 0, a_coordOver.current(), st::topBarHeight), _cacheUnder, QRect(-a_coordUnder.current() * retina, 0, a_coordOver.current() * retina, st::topBarHeight * retina));
p.setOpacity(a_progress.current() * st::slideFadeOut);
p.fillRect(0, 0, a_coordOver.current(), st::topBarHeight, st::black);
p.setOpacity(1);
}
p.drawPixmap(QRect(a_coordOver.current(), 0, _cacheOver.width() / retina, st::topBarHeight), _cacheOver, QRect(0, 0, _cacheOver.width(), st::topBarHeight * retina));
p.setOpacity(a_progress.current());
p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), st::topBarHeight), App::sprite(), st::slideShadow.rect()); p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), st::topBarHeight), App::sprite(), st::slideShadow.rect());
return; return;
} }
@ -5806,7 +5826,7 @@ void HistoryWidget::topBarClick() {
if (Adaptive::OneColumn()) { if (Adaptive::OneColumn()) {
Ui::showChatsList(); Ui::showChatsList();
} else { } else {
if (_history) App::main()->showPeerProfile(_peer); if (_history) Ui::showPeerProfile(_peer);
} }
} }
@ -5839,8 +5859,8 @@ void HistoryWidget::updateOnlineDisplay(int32 x, int32 w) {
} }
} }
} else if (_peer->isChannel()) { } else if (_peer->isChannel()) {
if (_peer->isMegagroup() && _peer->asChannel()->count > 0 && _peer->asChannel()->count <= Global::ChatSizeMax()) { if (_peer->isMegagroup() && _peer->asChannel()->membersCount() > 0 && _peer->asChannel()->membersCount() <= Global::ChatSizeMax()) {
if (_peer->asChannel()->mgInfo->lastParticipants.size() < _peer->asChannel()->count || _peer->asChannel()->lastParticipantsCountOutdated()) { if (_peer->asChannel()->mgInfo->lastParticipants.size() < _peer->asChannel()->membersCount() || _peer->asChannel()->lastParticipantsCountOutdated()) {
if (App::api()) App::api()->requestLastParticipants(_peer->asChannel()); if (App::api()) App::api()->requestLastParticipants(_peer->asChannel());
} }
int32 onlineCount = 0; int32 onlineCount = 0;
@ -5852,12 +5872,12 @@ void HistoryWidget::updateOnlineDisplay(int32 x, int32 w) {
} }
} }
if (onlineCount && !onlyMe) { if (onlineCount && !onlyMe) {
text = lng_chat_status_members_online(lt_count, _peer->asChannel()->count, lt_count_online, onlineCount); text = lng_chat_status_members_online(lt_count, _peer->asChannel()->membersCount(), lt_count_online, onlineCount);
} else { } else {
text = lng_chat_status_members(lt_count, _peer->asChannel()->count); text = lng_chat_status_members(lt_count, _peer->asChannel()->membersCount());
} }
} else { } else {
text = _peer->asChannel()->count ? lng_chat_status_members(lt_count, _peer->asChannel()->count) : lang(_peer->isMegagroup() ? lng_group_status : lng_channel_status); text = _peer->asChannel()->membersCount() ? lng_chat_status_members(lt_count, _peer->asChannel()->membersCount()) : lang(_peer->isMegagroup() ? lng_group_status : lng_channel_status);
} }
} }
if (_titlePeerText != text) { if (_titlePeerText != text) {
@ -6494,8 +6514,6 @@ void HistoryWidget::resizeEvent(QResizeEvent *e) {
_topShadow.resize(width() - ((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0), st::lineWidth); _topShadow.resize(width() - ((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0), st::lineWidth);
_topShadow.moveToLeft((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0, 0); _topShadow.moveToLeft((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0, 0);
_sideShadow.resize(st::lineWidth, height());
_sideShadow.moveToLeft(0, 0);
} }
void HistoryWidget::itemRemoved(HistoryItem *item) { void HistoryWidget::itemRemoved(HistoryItem *item) {
@ -6671,7 +6689,8 @@ void HistoryWidget::updateListSize(bool initial, bool loadedDown, const ScrollCh
} }
} else { } else {
} }
if (toY > _scroll.scrollTopMax()) toY = _scroll.scrollTopMax(); auto scrollMax = _scroll.scrollTopMax();
accumulate_min(toY, scrollMax);
if (_scroll.scrollTop() == toY) { if (_scroll.scrollTop() == toY) {
visibleAreaUpdated(); visibleAreaUpdated();
} else { } else {
@ -7041,7 +7060,6 @@ bool HistoryWidget::pinnedMsgVisibilityUpdated() {
} }
connect(&_pinnedBar->cancel, SIGNAL(clicked()), this, SLOT(onPinnedHide())); connect(&_pinnedBar->cancel, SIGNAL(clicked()), this, SLOT(onPinnedHide()));
_reportSpamPanel.raise(); _reportSpamPanel.raise();
_sideShadow.raise();
_topShadow.raise(); _topShadow.raise();
updatePinnedBar(); updatePinnedBar();
result = true; result = true;
@ -7679,7 +7697,7 @@ void HistoryWidget::peerUpdated(PeerData *data) {
if (App::api()) { if (App::api()) {
if (data->isChat() && data->asChat()->noParticipantInfo()) { if (data->isChat() && data->asChat()->noParticipantInfo()) {
App::api()->requestFullPeer(data); App::api()->requestFullPeer(data);
} else if (data->isUser() && data->asUser()->blocked == UserBlockUnknown) { } else if (data->isUser() && data->asUser()->blockStatus() == UserData::BlockStatus::Unknown) {
App::api()->requestFullPeer(data); App::api()->requestFullPeer(data);
} else if (data->isMegagroup() && !data->asChannel()->mgInfo->botStatus) { } else if (data->isMegagroup() && !data->asChannel()->mgInfo->botStatus) {
App::api()->requestBots(data->asChannel()); App::api()->requestBots(data->asChannel());
@ -7723,6 +7741,7 @@ void HistoryWidget::onDeleteSelected() {
} }
void HistoryWidget::onDeleteSelectedSure() { void HistoryWidget::onDeleteSelectedSure() {
Ui::hideLayer();
if (!_list) return; if (!_list) return;
SelectedItemSet sel; SelectedItemSet sel;
@ -7740,7 +7759,6 @@ void HistoryWidget::onDeleteSelectedSure() {
for (SelectedItemSet::const_iterator i = sel.cbegin(), e = sel.cend(); i != e; ++i) { for (SelectedItemSet::const_iterator i = sel.cbegin(), e = sel.cend(); i != e; ++i) {
i.value()->destroy(); i.value()->destroy();
} }
Ui::hideLayer();
for (QMap<PeerData*, QVector<MTPint> >::const_iterator i = ids.cbegin(), e = ids.cend(); i != e; ++i) { for (QMap<PeerData*, QVector<MTPint> >::const_iterator i = ids.cbegin(), e = ids.cend(); i != e; ++i) {
App::main()->deleteMessages(i.key(), i.value()); App::main()->deleteMessages(i.key(), i.value());
@ -7748,6 +7766,8 @@ void HistoryWidget::onDeleteSelectedSure() {
} }
void HistoryWidget::onDeleteContextSure() { void HistoryWidget::onDeleteContextSure() {
Ui::hideLayer();
HistoryItem *item = App::contextItem(); HistoryItem *item = App::contextItem();
if (!item || item->type() != HistoryItemMsg) { if (!item || item->type() != HistoryItemMsg) {
return; return;
@ -7757,12 +7777,11 @@ void HistoryWidget::onDeleteContextSure() {
History *h = item->history(); History *h = item->history();
bool wasOnServer = (item->id > 0), wasLast = (h->lastMsg == item); bool wasOnServer = (item->id > 0), wasLast = (h->lastMsg == item);
item->destroy(); item->destroy();
if (!wasOnServer && wasLast && !h->lastMsg) { if (!wasOnServer && wasLast && !h->lastMsg) {
App::main()->checkPeerHistory(h->peer); App::main()->checkPeerHistory(h->peer);
} }
Ui::hideLayer();
if (wasOnServer) { if (wasOnServer) {
App::main()->deleteMessages(h->peer, toDelete); App::main()->deleteMessages(h->peer, toDelete);
} }
@ -8117,20 +8136,23 @@ void HistoryWidget::paintEvent(QPaintEvent *e) {
if (r != rect()) { if (r != rect()) {
p.setClipRect(r); p.setClipRect(r);
} }
bool hasTopBar = !App::main()->topBar()->isHidden(), hasPlayer = !App::main()->player()->isHidden();
if (_a_show.animating()) { if (_a_show.animating()) {
int retina = cIntRetinaFactor();
int inCacheTop = hasTopBar ? st::topBarHeight : 0;
if (a_coordOver.current() > 0) { if (a_coordOver.current() > 0) {
p.drawPixmap(QRect(0, 0, a_coordOver.current(), height()), _cacheUnder, QRect(-a_coordUnder.current() * cRetinaFactor(), 0, a_coordOver.current() * cRetinaFactor(), height() * cRetinaFactor())); p.drawPixmap(QRect(0, 0, a_coordOver.current(), height()), _cacheUnder, QRect(-a_coordUnder.current() * retina, inCacheTop * retina, a_coordOver.current() * retina, height() * retina));
p.setOpacity(a_shadow.current() * st::slideFadeOut); p.setOpacity(a_progress.current() * st::slideFadeOut);
p.fillRect(0, 0, a_coordOver.current(), height(), st::black->b); p.fillRect(0, 0, a_coordOver.current(), height(), st::black);
p.setOpacity(1); p.setOpacity(1);
} }
p.drawPixmap(a_coordOver.current(), 0, _cacheOver); p.drawPixmap(QRect(a_coordOver.current(), 0, _cacheOver.width() / retina, height()), _cacheOver, QRect(0, inCacheTop * retina, _cacheOver.width(), height() * retina));
p.setOpacity(a_shadow.current()); p.setOpacity(a_progress.current());
p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), height()), App::sprite(), st::slideShadow.rect()); p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), height()), App::sprite(), st::slideShadow.rect());
return; return;
} }
bool hasTopBar = !App::main()->topBar()->isHidden(), hasPlayer = !App::main()->player()->isHidden();
QRect fill(0, 0, width(), App::main()->height()); QRect fill(0, 0, width(), App::main()->height());
int fromy = (hasTopBar ? (-st::topBarHeight) : 0) + (hasPlayer ? (-st::playerHeight) : 0), x = 0, y = 0; int fromy = (hasTopBar ? (-st::topBarHeight) : 0) + (hasPlayer ? (-st::playerHeight) : 0), x = 0, y = 0;
QPixmap cached = App::main()->cachedBackground(fill, x, y); QPixmap cached = App::main()->cachedBackground(fill, x, y);
@ -8208,8 +8230,7 @@ QStringList HistoryWidget::getMediasFromMime(const QMimeData *d) {
for (QList<QUrl>::const_iterator i = urls.cbegin(), en = urls.cend(); i != en; ++i) { for (QList<QUrl>::const_iterator i = urls.cbegin(), en = urls.cend(); i != en; ++i) {
if (!i->isLocalFile()) return QStringList(); if (!i->isLocalFile()) return QStringList();
QString file(i->toLocalFile()); auto file = psConvertFileUrl(*i);
if (file.startsWith(qsl("/.file/id="))) file = psConvertFileUrl(file);
QFileInfo info(file); QFileInfo info(file);
uint64 s = info.size(); uint64 s = info.size();

View file

@ -25,6 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "dropdown.h" #include "dropdown.h"
#include "history/history_common.h" #include "history/history_common.h"
#include "history/field_autocomplete.h" #include "history/field_autocomplete.h"
#include "window/section_widget.h"
namespace InlineBots { namespace InlineBots {
namespace Layout { namespace Layout {
@ -205,7 +206,7 @@ private:
Selecting = 0x04, Selecting = 0x04,
}; };
DragAction _dragAction = NoDrag; DragAction _dragAction = NoDrag;
TextSelectType _dragSelType = TextSelectLetters; TextSelectType _dragSelType = TextSelectType::Letters;
QPoint _dragStartPos, _dragPos; QPoint _dragStartPos, _dragPos;
HistoryItem *_dragItem = nullptr; HistoryItem *_dragItem = nullptr;
HistoryCursorState _dragCursorState = HistoryDefaultCursorState; HistoryCursorState _dragCursorState = HistoryDefaultCursorState;
@ -570,7 +571,10 @@ public:
void setMsgId(MsgId showAtMsgId); void setMsgId(MsgId showAtMsgId);
MsgId msgId() const; MsgId msgId() const;
void animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTopBarCache, bool back = false); bool hasTopBarShadow() const {
return peer() != nullptr;
}
void showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams &params);
void step_show(float64 ms, bool timer); void step_show(float64 ms, bool timer);
void animStop(); void animStop();
@ -652,14 +656,17 @@ public:
bool contentOverlapped(const QRect &globalRect); bool contentOverlapped(const QRect &globalRect);
void grabStart() override { void grabStart() override {
_sideShadow.hide();
_inGrab = true; _inGrab = true;
resizeEvent(0); resizeEvent(0);
} }
void grapWithoutTopBarShadow() {
grabStart();
_topShadow.hide();
}
void grabFinish() override { void grabFinish() override {
_sideShadow.setVisible(!Adaptive::OneColumn());
_inGrab = false; _inGrab = false;
resizeEvent(0); resizeEvent(0);
_topShadow.show();
} }
bool isItemVisible(HistoryItem *item); bool isItemVisible(HistoryItem *item);
@ -1077,9 +1084,9 @@ private:
int32 _titlePeerTextWidth = 0; int32 _titlePeerTextWidth = 0;
Animation _a_show; Animation _a_show;
QPixmap _cacheUnder, _cacheOver, _cacheTopBarUnder, _cacheTopBarOver; QPixmap _cacheUnder, _cacheOver;
anim::ivalue a_coordUnder, a_coordOver; anim::ivalue a_coordUnder, a_coordOver;
anim::fvalue a_shadow; anim::fvalue a_progress;
QTimer _scrollTimer; QTimer _scrollTimer;
int32 _scrollDelta = 0; int32 _scrollDelta = 0;
@ -1094,7 +1101,7 @@ private:
bool _saveDraftText = false; bool _saveDraftText = false;
QTimer _saveDraftTimer, _saveCloudDraftTimer; QTimer _saveDraftTimer, _saveCloudDraftTimer;
PlainShadow _sideShadow, _topShadow; PlainShadow _topShadow;
bool _inGrab = false; bool _inGrab = false;
}; };

View file

@ -216,9 +216,8 @@ void Gif::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
if (_delete && p == _delete) { if (_delete && p == _delete) {
bool wasactive = (_state & StateFlag::DeleteOver); bool wasactive = (_state & StateFlag::DeleteOver);
if (active != wasactive) { if (active != wasactive) {
float64 from = active ? 0 : 1, to = active ? 1 : 0; auto from = active ? 0. : 1., to = active ? 1. : 0.;
EnsureAnimation(_a_deleteOver, from, func(this, &Gif::update)); START_ANIMATION(_a_deleteOver, func(this, &Gif::update), from, to, st::stickersRowDuration, anim::linear);
_a_deleteOver.start(to, st::stickersRowDuration);
if (active) { if (active) {
_state |= StateFlag::DeleteOver; _state |= StateFlag::DeleteOver;
} else { } else {
@ -231,9 +230,8 @@ void Gif::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
if (active != wasactive) { if (active != wasactive) {
if (!getShownDocument()->loaded()) { if (!getShownDocument()->loaded()) {
ensureAnimation(); ensureAnimation();
float64 from = active ? 0 : 1, to = active ? 1 : 0; auto from = active ? 0. : 1., to = active ? 1. : 0.;
EnsureAnimation(_animation->_a_over, from, func(this, &Gif::update)); START_ANIMATION(_animation->_a_over, func(this, &Gif::update), from, to, st::stickersRowDuration, anim::linear);
_animation->_a_over.start(to, st::stickersRowDuration);
} }
if (active) { if (active) {
_state |= StateFlag::Over; _state |= StateFlag::Over;
@ -413,9 +411,8 @@ void Sticker::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
if (active != _active) { if (active != _active) {
_active = active; _active = active;
float64 from = _active ? 0 : 1, to = _active ? 1 : 0; auto from = active ? 0. : 1., to = active ? 1. : 0.;
EnsureAnimation(_a_over, from, func(this, &Sticker::update)); START_ANIMATION(_a_over, func(this, &Sticker::update), from, to, st::stickersRowDuration, anim::linear);
_a_over.start(to, st::stickersRowDuration);
} }
} }
ItemBase::clickHandlerActiveChanged(p, active); ItemBase::clickHandlerActiveChanged(p, active);

View file

@ -107,9 +107,9 @@ private:
void clipCallback(ClipReaderNotification notification); void clipCallback(ClipReaderNotification notification);
struct AnimationData { struct AnimationData {
AnimationData(AnimationCreator creator) AnimationData(AnimationCallbacks &&callbacks)
: over(false) : over(false)
, radial(creator) { , radial(std_::move(callbacks)) {
} }
bool over; bool over;
FloatAnimation _a_over; FloatAnimation _a_over;
@ -268,9 +268,9 @@ private:
} }
struct AnimationData { struct AnimationData {
AnimationData(AnimationCreator thumbOverCallbacks, AnimationCreator radialCallbacks) : a_thumbOver(0, 0) AnimationData(AnimationCallbacks &&thumbOverCallbacks, AnimationCallbacks &&radialCallbacks) : a_thumbOver(0, 0)
, _a_thumbOver(thumbOverCallbacks) , _a_thumbOver(std_::move(thumbOverCallbacks))
, radial(radialCallbacks) { , radial(std_::move(radialCallbacks)) {
} }
anim::fvalue a_thumbOver; anim::fvalue a_thumbOver;
Animation _a_thumbOver; Animation _a_thumbOver;

View file

@ -50,7 +50,7 @@ IntroPhone::IntroPhone(IntroWidget *parent) : IntroStep(parent)
, country(this, st::introCountry) , country(this, st::introCountry)
, phone(this, st::inpIntroPhone) , phone(this, st::inpIntroPhone)
, code(this, st::inpIntroCountryCode) , code(this, st::inpIntroCountryCode)
, _signup(this, lng_phone_notreg(lt_signup_start, textcmdStartLink(1), lt_signup_end, textcmdStopLink()), st::introErrLabel, st::introErrLabelTextStyle) , _signup(this, lng_phone_notreg(lt_signup_start, textcmdStartLink(1), lt_signup_end, textcmdStopLink()), FlatLabel::InitType::Rich, st::introErrLabel, st::introErrLabelTextStyle)
, _showSignup(false) , _showSignup(false)
, sentRequest(0) { , sentRequest(0) {
setVisible(false); setVisible(false);

View file

@ -20,9 +20,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/ */
#pragma once #pragma once
#include <QtWidgets/QWidget>
#include "ui/flatbutton.h" #include "ui/flatbutton.h"
#include "ui/countryinput.h" #include "ui/countryinput.h"
#include "ui/flatlabel.h"
#include "intro/introwidget.h" #include "intro/introwidget.h"
class IntroPhone final : public IntroStep { class IntroPhone final : public IntroStep {

View file

@ -27,7 +27,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "langloaderplain.h" #include "langloaderplain.h"
IntroStart::IntroStart(IntroWidget *parent) : IntroStep(parent) IntroStart::IntroStart(IntroWidget *parent) : IntroStep(parent)
, _intro(this, lang(lng_intro), st::introLabel, st::introLabelTextStyle) , _intro(this, lang(lng_intro), FlatLabel::InitType::Rich, st::introLabel, st::introLabelTextStyle)
, _changeLang(this, QString()) , _changeLang(this, QString())
, _next(this, lang(lng_start_msgs), st::btnIntroNext) { , _next(this, lang(lng_start_msgs), st::btnIntroNext) {
_changeLang.hide(); _changeLang.hide();

View file

@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#pragma once #pragma once
#include "intro/introwidget.h" #include "intro/introwidget.h"
#include "ui/flatlabel.h"
class IntroStart final : public IntroStep { class IntroStart final : public IntroStep {
public: public:

View file

@ -180,7 +180,7 @@ void IntroWidget::animShow(const QPixmap &bgAnimCache, bool back) {
step()->hide(); step()->hide();
_back.hide(); _back.hide();
a_coordUnder = back ? anim::ivalue(-qFloor(st::slideShift * width()), 0) : anim::ivalue(0, -qFloor(st::slideShift * width())); a_coordUnder = back ? anim::ivalue(-st::slideShift, 0) : anim::ivalue(0, -st::slideShift);
a_coordOver = back ? anim::ivalue(0, width()) : anim::ivalue(width(), 0); a_coordOver = back ? anim::ivalue(0, width()) : anim::ivalue(width(), 0);
a_shadow = back ? anim::fvalue(1, 0) : anim::fvalue(0, 1); a_shadow = back ? anim::fvalue(1, 0) : anim::fvalue(0, 1);
_a_show.start(); _a_show.start();

View file

@ -25,6 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "serialize/serialize_document.h" #include "serialize/serialize_document.h"
#include "serialize/serialize_common.h" #include "serialize/serialize_common.h"
#include "observer_peer.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "lang.h" #include "lang.h"
@ -3412,7 +3413,7 @@ namespace Local {
UserData *user = peer->asUser(); UserData *user = peer->asUser();
// first + last + phone + username + access // first + last + phone + username + access
result += Serialize::stringSize(user->firstName) + Serialize::stringSize(user->lastName) + Serialize::stringSize(user->phone) + Serialize::stringSize(user->username) + sizeof(quint64); result += Serialize::stringSize(user->firstName) + Serialize::stringSize(user->lastName) + Serialize::stringSize(user->phone()) + Serialize::stringSize(user->username) + sizeof(quint64);
// flags // flags
if (AppVersion >= 9012) { if (AppVersion >= 9012) {
@ -3424,13 +3425,13 @@ namespace Local {
} else if (peer->isChat()) { } else if (peer->isChat()) {
ChatData *chat = peer->asChat(); ChatData *chat = peer->asChat();
// name + count + date + version + admin + forbidden + left + invitationUrl // name + count + date + version + admin + forbidden + left + inviteLink
result += Serialize::stringSize(chat->name) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + Serialize::stringSize(chat->invitationUrl); result += Serialize::stringSize(chat->name) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + Serialize::stringSize(chat->inviteLink());
} else if (peer->isChannel()) { } else if (peer->isChannel()) {
ChannelData *channel = peer->asChannel(); ChannelData *channel = peer->asChannel();
// name + access + date + version + forbidden + flags + invitationUrl // name + access + date + version + forbidden + flags + inviteLink
result += Serialize::stringSize(channel->name) + sizeof(quint64) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + Serialize::stringSize(channel->invitationUrl); result += Serialize::stringSize(channel->name) + sizeof(quint64) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + Serialize::stringSize(channel->inviteLink());
} }
return result; return result;
} }
@ -3441,7 +3442,7 @@ namespace Local {
if (peer->isUser()) { if (peer->isUser()) {
UserData *user = peer->asUser(); UserData *user = peer->asUser();
stream << user->firstName << user->lastName << user->phone << user->username << quint64(user->access); stream << user->firstName << user->lastName << user->phone() << user->username << quint64(user->access);
if (AppVersion >= 9012) { if (AppVersion >= 9012) {
stream << qint32(user->flags); stream << qint32(user->flags);
} }
@ -3455,12 +3456,12 @@ namespace Local {
qint32 flagsData = (AppVersion >= 9012) ? chat->flags : (chat->haveLeft() ? 1 : 0); qint32 flagsData = (AppVersion >= 9012) ? chat->flags : (chat->haveLeft() ? 1 : 0);
stream << chat->name << qint32(chat->count) << qint32(chat->date) << qint32(chat->version) << qint32(chat->creator); stream << chat->name << qint32(chat->count) << qint32(chat->date) << qint32(chat->version) << qint32(chat->creator);
stream << qint32(chat->isForbidden ? 1 : 0) << qint32(flagsData) << chat->invitationUrl; stream << qint32(chat->isForbidden ? 1 : 0) << qint32(flagsData) << chat->inviteLink();
} else if (peer->isChannel()) { } else if (peer->isChannel()) {
ChannelData *channel = peer->asChannel(); ChannelData *channel = peer->asChannel();
stream << channel->name << quint64(channel->access) << qint32(channel->date) << qint32(channel->version); stream << channel->name << quint64(channel->access) << qint32(channel->date) << qint32(channel->version);
stream << qint32(channel->isForbidden ? 1 : 0) << qint32(channel->flags) << channel->invitationUrl; stream << qint32(channel->isForbidden ? 1 : 0) << qint32(channel->flags) << channel->inviteLink();
} }
} }
@ -3495,6 +3496,7 @@ namespace Local {
QString pname = (showPhone && !phone.isEmpty()) ? App::formatPhone(phone) : QString(); QString pname = (showPhone && !phone.isEmpty()) ? App::formatPhone(phone) : QString();
if (!wasLoaded) { if (!wasLoaded) {
user->setPhone(phone);
user->setName(first, last, pname, username); user->setName(first, last, pname, username);
user->access = access; user->access = access;
@ -3519,9 +3521,9 @@ namespace Local {
} else if (result->isChat()) { } else if (result->isChat()) {
ChatData *chat = result->asChat(); ChatData *chat = result->asChat();
QString name, invitationUrl; QString name, inviteLink;
qint32 count, date, version, creator, forbidden, flagsData, flags; qint32 count, date, version, creator, forbidden, flagsData, flags;
from.stream >> name >> count >> date >> version >> creator >> forbidden >> flagsData >> invitationUrl; from.stream >> name >> count >> date >> version >> creator >> forbidden >> flagsData >> inviteLink;
if (from.version >= 9012) { if (from.version >= 9012) {
flags = flagsData; flags = flagsData;
@ -3530,14 +3532,14 @@ namespace Local {
flags = (flagsData == 1) ? MTPDchat::Flags(MTPDchat::Flag::f_left) : MTPDchat::Flags(0); flags = (flagsData == 1) ? MTPDchat::Flags(MTPDchat::Flag::f_left) : MTPDchat::Flags(0);
} }
if (!wasLoaded) { if (!wasLoaded) {
chat->updateName(name, QString(), QString()); chat->setName(name);
chat->count = count; chat->count = count;
chat->date = date; chat->date = date;
chat->version = version; chat->version = version;
chat->creator = creator; chat->creator = creator;
chat->isForbidden = (forbidden == 1); chat->isForbidden = (forbidden == 1);
chat->flags = MTPDchat::Flags(flags); chat->flags = MTPDchat::Flags(flags);
chat->invitationUrl = invitationUrl; chat->setInviteLink(inviteLink);
chat->input = MTP_inputPeerChat(MTP_int(peerToChat(chat->id))); chat->input = MTP_inputPeerChat(MTP_int(peerToChat(chat->id)));
chat->inputChat = MTP_int(peerToChat(chat->id)); chat->inputChat = MTP_int(peerToChat(chat->id));
@ -3547,19 +3549,19 @@ namespace Local {
} else if (result->isChannel()) { } else if (result->isChannel()) {
ChannelData *channel = result->asChannel(); ChannelData *channel = result->asChannel();
QString name, invitationUrl; QString name, inviteLink;
quint64 access; quint64 access;
qint32 date, version, forbidden, flags; qint32 date, version, forbidden, flags;
from.stream >> name >> access >> date >> version >> forbidden >> flags >> invitationUrl; from.stream >> name >> access >> date >> version >> forbidden >> flags >> inviteLink;
if (!wasLoaded) { if (!wasLoaded) {
channel->updateName(name, QString(), QString()); channel->setName(name, QString());
channel->access = access; channel->access = access;
channel->date = date; channel->date = date;
channel->version = version; channel->version = version;
channel->isForbidden = (forbidden == 1); channel->isForbidden = (forbidden == 1);
channel->flags = MTPDchannel::Flags(flags); channel->flags = MTPDchannel::Flags(flags);
channel->invitationUrl = invitationUrl; channel->setInviteLink(inviteLink);
channel->input = MTP_inputPeerChannel(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->inputChannel = MTP_inputChannel(MTP_int(peerToChannel(channel->id)), MTP_long(access));
@ -3749,7 +3751,7 @@ namespace Local {
cRefSavedPeersByTime().insert(t, peer); cRefSavedPeersByTime().insert(t, peer);
peers.push_back(peer); peers.push_back(peer);
} }
App::emitPeerUpdated();
if (App::api()) App::api()->requestPeers(peers); if (App::api()) App::api()->requestPeers(peers);
} }

File diff suppressed because it is too large Load diff

View file

@ -33,6 +33,9 @@ class PeerAvatarButton;
namespace Window { namespace Window {
class TopBarWidget; class TopBarWidget;
class SectionMemento;
class SectionWidget;
struct SectionSlideParams;
} // namespace Window } // namespace Window
class MainWindow; class MainWindow;
@ -40,7 +43,6 @@ class ApiWrap;
class ConfirmBox; class ConfirmBox;
class DialogsWidget; class DialogsWidget;
class HistoryWidget; class HistoryWidget;
class ProfileWidget;
class OverviewWidget; class OverviewWidget;
class PlayerWidget; class PlayerWidget;
class HistoryHider; class HistoryHider;
@ -48,7 +50,7 @@ class Dropdown;
enum StackItemType { enum StackItemType {
HistoryStackItem, HistoryStackItem,
ProfileStackItem, SectionStackItem,
OverviewStackItem, OverviewStackItem,
}; };
@ -64,8 +66,9 @@ public:
class StackItemHistory : public StackItem { class StackItemHistory : public StackItem {
public: public:
StackItemHistory(PeerData *peer, MsgId msgId, QList<MsgId> replyReturns) : StackItem(peer), StackItemHistory(PeerData *peer, MsgId msgId, QList<MsgId> replyReturns) : StackItem(peer)
msgId(msgId), replyReturns(replyReturns) { , msgId(msgId)
, replyReturns(replyReturns) {
} }
StackItemType type() const { StackItemType type() const {
return HistoryStackItem; return HistoryStackItem;
@ -74,19 +77,29 @@ msgId(msgId), replyReturns(replyReturns) {
QList<MsgId> replyReturns; QList<MsgId> replyReturns;
}; };
class StackItemProfile : public StackItem { class StackItemSection : public StackItem {
public: public:
StackItemProfile(PeerData *peer, int32 lastScrollTop) : StackItem(peer), lastScrollTop(lastScrollTop) { StackItemSection(std_::unique_ptr<Window::SectionMemento> &&memento);
} ~StackItemSection();
StackItemType type() const { StackItemType type() const {
return ProfileStackItem; return SectionStackItem;
} }
int32 lastScrollTop; Window::SectionMemento *memento() const {
return _memento.get();
}
private:
std_::unique_ptr<Window::SectionMemento> _memento;
}; };
class StackItemOverview : public StackItem { class StackItemOverview : public StackItem {
public: public:
StackItemOverview(PeerData *peer, MediaOverviewType mediaType, int32 lastWidth, int32 lastScrollTop) : StackItem(peer), mediaType(mediaType), lastWidth(lastWidth), lastScrollTop(lastScrollTop) { StackItemOverview(PeerData *peer, MediaOverviewType mediaType, int32 lastWidth, int32 lastScrollTop) : StackItem(peer)
, mediaType(mediaType)
, lastWidth(lastWidth)
, lastScrollTop(lastScrollTop) {
} }
StackItemType type() const { StackItemType type() const {
return OverviewStackItem; return OverviewStackItem;
@ -173,8 +186,6 @@ public:
void startFull(const MTPVector<MTPUser> &users); void startFull(const MTPVector<MTPUser> &users);
bool started(); bool started();
void applyNotifySetting(const MTPNotifyPeer &peer, const MTPPeerNotifySettings &settings, History *history = 0); void applyNotifySetting(const MTPNotifyPeer &peer, const MTPPeerNotifySettings &settings, History *history = 0);
void gotNotifySetting(MTPInputNotifyPeer peer, const MTPPeerNotifySettings &settings);
bool failNotifySetting(MTPInputNotifyPeer peer, const RPCError &error);
void updateNotifySetting(PeerData *peer, NotifySettingStatus notify, SilentNotifiesStatus silent = SilentNotifiesDontChange); void updateNotifySetting(PeerData *peer, NotifySettingStatus notify, SilentNotifiesStatus silent = SilentNotifiesDontChange);
@ -210,14 +221,14 @@ public:
PeerData *activePeer(); PeerData *activePeer();
MsgId activeMsgId(); MsgId activeMsgId();
PeerData *profilePeer();
PeerData *overviewPeer(); PeerData *overviewPeer();
bool mediaTypeSwitch(); bool mediaTypeSwitch();
void showPeerProfile(PeerData *peer, bool back = false, int32 lastScrollTop = -1); void showWideSection(const Window::SectionMemento &memento);
void showMediaOverview(PeerData *peer, MediaOverviewType type, bool back = false, int32 lastScrollTop = -1); void showMediaOverview(PeerData *peer, MediaOverviewType type, bool back = false, int32 lastScrollTop = -1);
void showBackFromStack(); void showBackFromStack();
void orderWidgets(); void orderWidgets();
QRect historyRect() const; QRect historyRect() const;
QPixmap grabForShowAnimation(const Window::SectionSlideParams &params);
void onSendFileConfirm(const FileLoadResultPtr &file, bool ctrlShiftEnter); void onSendFileConfirm(const FileLoadResultPtr &file, bool ctrlShiftEnter);
void onSendFileCancel(const FileLoadResultPtr &file); void onSendFileCancel(const FileLoadResultPtr &file);
@ -262,6 +273,7 @@ public:
void deleteMessages(PeerData *peer, const QVector<MTPint> &ids); void deleteMessages(PeerData *peer, const QVector<MTPint> &ids);
void deletedContact(UserData *user, const MTPcontacts_Link &result); void deletedContact(UserData *user, const MTPcontacts_Link &result);
void deleteConversation(PeerData *peer, bool deleteHistory = true); void deleteConversation(PeerData *peer, bool deleteHistory = true);
void deleteAndExit(ChatData *chat);
void clearHistory(PeerData *peer); void clearHistory(PeerData *peer);
void deleteAllFromUser(ChannelData *channel, UserData *from); void deleteAllFromUser(ChannelData *channel, UserData *from);
@ -313,7 +325,6 @@ public:
void itemEdited(HistoryItem *item); void itemEdited(HistoryItem *item);
void loadMediaBack(PeerData *peer, MediaOverviewType type, bool many = false); void loadMediaBack(PeerData *peer, MediaOverviewType type, bool many = false);
void peerUsernameChanged(PeerData *peer);
void checkLastUpdate(bool afterSleep); void checkLastUpdate(bool afterSleep);
void showAddContact(); void showAddContact();
@ -483,10 +494,9 @@ public slots:
void ui_autoplayMediaInlineAsync(qint32 channelId, qint32 msgId); void ui_autoplayMediaInlineAsync(qint32 channelId, qint32 msgId);
private: private:
void sendReadRequest(PeerData *peer, MsgId upTo); void sendReadRequest(PeerData *peer, MsgId upTo);
void channelReadDone(PeerData *peer, const MTPBool &result); void channelReadDone(PeerData *peer, const MTPBool &result);
void historyReadDone(PeerData *peer, const MTPmessages_AffectedMessages &result); void historyReadDone(PeerData *peer, const MTPmessages_AffectedMessages &result);
bool readRequestFail(PeerData *peer, const RPCError &error); bool readRequestFail(PeerData *peer, const RPCError &error);
void readRequestDone(PeerData *peer); void readRequestDone(PeerData *peer);
@ -496,6 +506,15 @@ private:
void saveCloudDraftDone(PeerData *peer, const MTPBool &result, mtpRequestId requestId); void saveCloudDraftDone(PeerData *peer, const MTPBool &result, mtpRequestId requestId);
bool saveCloudDraftFail(PeerData *peer, const RPCError &error, mtpRequestId requestId); bool saveCloudDraftFail(PeerData *peer, const RPCError &error, mtpRequestId requestId);
Window::SectionSlideParams prepareShowAnimation(bool willHaveTopBarShadow);
void showWideSectionAnimated(const Window::SectionMemento *memento, bool back);
// All this methods use the prepareShowAnimation().
Window::SectionSlideParams prepareWideSectionAnimation(Window::SectionWidget *section);
Window::SectionSlideParams prepareHistoryAnimation(PeerId historyPeerId);
Window::SectionSlideParams prepareOverviewAnimation();
Window::SectionSlideParams prepareDialogsAnimation();
bool _started = false; bool _started = false;
uint64 failedObjId = 0; uint64 failedObjId = 0;
@ -565,9 +584,11 @@ private:
int _dialogsWidth = st::dlgMinWidth; int _dialogsWidth = st::dlgMinWidth;
PlainShadow _sideShadow;
ChildWidget<DialogsWidget> _dialogs; ChildWidget<DialogsWidget> _dialogs;
ChildWidget<HistoryWidget> _history; ChildWidget<HistoryWidget> _history;
ChildWidget<ProfileWidget> _profile = { nullptr }; ChildWidget<Window::SectionWidget> _wideSection = { nullptr };
ChildWidget<OverviewWidget> _overview = { nullptr }; ChildWidget<OverviewWidget> _overview = { nullptr };
ChildWidget<PlayerWidget> _player; ChildWidget<PlayerWidget> _player;
ChildWidget<Window::TopBarWidget> _topBar; ChildWidget<Window::TopBarWidget> _topBar;

View file

@ -35,9 +35,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "boxes/confirmbox.h" #include "boxes/confirmbox.h"
#include "boxes/contactsbox.h" #include "boxes/contactsbox.h"
#include "boxes/addcontactbox.h" #include "boxes/addcontactbox.h"
#include "observer_peer.h"
#include "autoupdater.h" #include "autoupdater.h"
#include "mediaview.h" #include "mediaview.h"
#include "localstorage.h" #include "localstorage.h"
#include "apiwrap.h"
ConnectingWidget::ConnectingWidget(QWidget *parent, const QString &text, const QString &reconnect) : QWidget(parent), _shadow(st::boxShadow), _reconnect(this, QString()) { ConnectingWidget::ConnectingWidget(QWidget *parent, const QString &text, const QString &reconnect) : QWidget(parent), _shadow(st::boxShadow), _reconnect(this, QString()) {
set(text, reconnect); set(text, reconnect);
@ -435,7 +437,7 @@ void MainWindow::init() {
connect(windowHandle(), SIGNAL(activeChanged()), this, SLOT(checkHistoryActivation()), Qt::QueuedConnection); connect(windowHandle(), SIGNAL(activeChanged()), this, SLOT(checkHistoryActivation()), Qt::QueuedConnection);
QPalette p(palette()); QPalette p(palette());
p.setColor(QPalette::Window, st::wndBG->c); p.setColor(QPalette::Window, st::windowBg->c);
setPalette(p); setPalette(p);
title = new TitleWidget(this); title = new TitleWidget(this);
@ -613,10 +615,6 @@ void MainWindow::setupIntro(bool anim) {
} }
} }
void MainWindow::getNotifySetting(const MTPInputNotifyPeer &peer, uint32 msWait) {
MTP::send(MTPaccount_GetNotifySettings(peer), main->rpcDone(&MainWidget::gotNotifySetting, peer), main->rpcFail(&MainWidget::failNotifySetting, peer), 0, msWait);
}
void MainWindow::serviceNotification(const QString &msg, const MTPMessageMedia &media, bool force) { void MainWindow::serviceNotification(const QString &msg, const MTPMessageMedia &media, bool force) {
History *h = (main && App::userLoaded(ServiceUserId)) ? App::history(ServiceUserId) : 0; History *h = (main && App::userLoaded(ServiceUserId)) ? App::history(ServiceUserId) : 0;
if (!h || (!force && h->isEmpty())) { if (!h || (!force && h->isEmpty())) {
@ -1374,7 +1372,7 @@ void MainWindow::onClearFailed(int task, void *manager) {
} }
void MainWindow::notifySchedule(History *history, HistoryItem *item) { void MainWindow::notifySchedule(History *history, HistoryItem *item) {
if (App::quitting() || !history->currentNotification() || !main) return; if (App::quitting() || !history->currentNotification() || !App::api()) return;
PeerData *notifyByFrom = (!history->peer->isUser() && item->mentionsMe()) ? item->from() : 0; PeerData *notifyByFrom = (!history->peer->isUser() && item->mentionsMe()) ? item->from() : 0;
@ -1394,7 +1392,7 @@ void MainWindow::notifySchedule(History *history, HistoryItem *item) {
return; return;
} }
} else { } else {
App::wnd()->getNotifySetting(MTP_inputNotifyPeer(notifyByFrom->input)); App::api()->requestNotifySetting(notifyByFrom);
} }
} else { } else {
history->popNotification(item); history->popNotification(item);
@ -1403,9 +1401,9 @@ void MainWindow::notifySchedule(History *history, HistoryItem *item) {
} }
} else { } else {
if (notifyByFrom && notifyByFrom->notify == UnknownNotifySettings) { if (notifyByFrom && notifyByFrom->notify == UnknownNotifySettings) {
App::wnd()->getNotifySetting(MTP_inputNotifyPeer(notifyByFrom->input), 10); App::api()->requestNotifySetting(notifyByFrom);
} }
App::wnd()->getNotifySetting(MTP_inputNotifyPeer(history->peer->input)); App::api()->requestNotifySetting(history->peer);
} }
if (!item->notificationReady()) { if (!item->notificationReady()) {
haveSetting = false; haveSetting = false;
@ -1420,6 +1418,7 @@ void MainWindow::notifySchedule(History *history, HistoryItem *item) {
} else if (cOtherOnline() >= t) { } else if (cOtherOnline() >= t) {
delay = Global::NotifyDefaultDelay(); delay = Global::NotifyDefaultDelay();
} }
// LOG(("Is online: %1, otherOnline: %2, currentTime: %3, otherNotOld: %4, otherLaterThanMe: %5").arg(Logs::b(isOnline)).arg(cOtherOnline()).arg(t).arg(Logs::b(otherNotOld)).arg(Logs::b(otherLaterThanMe)));
uint64 when = ms + delay; uint64 when = ms + delay;
notifyWhenAlerts[history].insert(when, notifyByFrom); notifyWhenAlerts[history].insert(when, notifyByFrom);
@ -1882,8 +1881,15 @@ void MainWindow::sendPaths() {
void MainWindow::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) { void MainWindow::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) {
if (main) main->mediaOverviewUpdated(peer, type); if (main) main->mediaOverviewUpdated(peer, type);
if (!_mediaView || _mediaView->isHidden()) return; if (_mediaView && !_mediaView->isHidden()) {
_mediaView->mediaOverviewUpdated(peer, type); _mediaView->mediaOverviewUpdated(peer, type);
}
if (type != OverviewCount) {
Notify::PeerUpdate update(peer);
update.flags |= Notify::PeerUpdate::Flag::SharedMediaChanged;
update.mediaTypesMask |= (1 << type);
Notify::peerUpdatedDelayed(update);
}
} }
void MainWindow::documentUpdated(DocumentData *doc) { void MainWindow::documentUpdated(DocumentData *doc) {

View file

@ -156,7 +156,6 @@ public:
void checkAutoLockIn(int msec); void checkAutoLockIn(int msec);
void setupIntro(bool anim); void setupIntro(bool anim);
void setupMain(bool anim, const MTPUser *user = 0); void setupMain(bool anim, const MTPUser *user = 0);
void getNotifySetting(const MTPInputNotifyPeer &peer, uint32 msWait = 0);
void serviceNotification(const QString &msg, const MTPMessageMedia &media = MTP_messageMediaEmpty(), bool force = false); void serviceNotification(const QString &msg, const MTPMessageMedia &media = MTP_messageMediaEmpty(), bool force = false);
void sendServiceHistoryRequest(); void sendServiceHistoryRequest();
void showDelayedServiceMsgs(); void showDelayedServiceMsgs();

View file

@ -1875,7 +1875,7 @@ void MediaView::mouseReleaseEvent(QMouseEvent *e) {
if (_over == OverName && _down == OverName) { if (_over == OverName && _down == OverName) {
if (App::wnd() && _from) { if (App::wnd() && _from) {
close(); close();
if (App::main()) App::main()->showPeerProfile(_from); Ui::showPeerProfile(_from);
} }
} else if (_over == OverDate && _down == OverDate) { } else if (_over == OverDate && _down == OverDate) {
onToMessage(); onToMessage();

View file

@ -22,7 +22,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "mtproto/core_types.h" #include "mtproto/core_types.h"
#include "mtproto/session.h" #include "mtproto/session.h"
#include "mtproto/file_download.h"
namespace MTP { namespace MTP {

View file

@ -208,6 +208,7 @@ void FileLoader::localLoaded(const StorageImageSaved &result, const QByteArray &
} }
emit App::wnd()->imageLoaded(); emit App::wnd()->imageLoaded();
emit progress(this); emit progress(this);
FileDownload::internal::notifyImageLoaded();
loadNext(); loadNext();
} }
@ -549,6 +550,9 @@ void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRe
if (DebugLogging::FileLoader() && _id) DEBUG_LOG(("FileLoader(%1): not done yet, _lastComplete=%2, _size=%3, _nextRequestOffset=%4, _requests=%5").arg(_id).arg(Logs::b(_lastComplete)).arg(_size).arg(_nextRequestOffset).arg(serializereqs(_requests))); if (DebugLogging::FileLoader() && _id) DEBUG_LOG(("FileLoader(%1): not done yet, _lastComplete=%2, _size=%3, _nextRequestOffset=%4, _requests=%5").arg(_id).arg(Logs::b(_lastComplete)).arg(_size).arg(_nextRequestOffset).arg(serializereqs(_requests)));
} }
emit progress(this); emit progress(this);
if (_complete) {
FileDownload::internal::notifyImageLoaded();
}
loadNext(); loadNext();
} }
@ -678,6 +682,7 @@ void webFileLoader::onFinished(const QByteArray &data) {
Local::writeWebFile(_url, _data); Local::writeWebFile(_url, _data);
} }
emit progress(this); emit progress(this);
FileDownload::internal::notifyImageLoaded();
loadNext(); loadNext();
} }
@ -1089,3 +1094,25 @@ namespace MTP {
++GlobalPriority; ++GlobalPriority;
} }
} }
namespace FileDownload {
namespace {
using internal::ImageLoadedHandler;
Notify::SimpleObservedEventRegistrator<ImageLoadedHandler> creator(nullptr, nullptr);
} // namespace
namespace internal {
Notify::ConnectionId plainRegisterImageLoadedObserver(ImageLoadedHandler &&handler) {
return creator.registerObserver(std_::forward<ImageLoadedHandler>(handler));
}
void notifyImageLoaded() {
creator.notify();
}
} // namespace internal
}

View file

@ -20,6 +20,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/ */
#pragma once #pragma once
#include "core/observer.h"
namespace MTP { namespace MTP {
void clearLoaderPriorities(); void clearLoaderPriorities();
} }
@ -391,3 +393,21 @@ static WebLoadManager * const FinishedWebLoadManager = SharedMemoryLocation<WebL
void reinitWebLoadManager(); void reinitWebLoadManager();
void stopWebLoadManager(); void stopWebLoadManager();
namespace FileDownload {
namespace internal {
using ImageLoadedHandler = Function<void>;
Notify::ConnectionId plainRegisterImageLoadedObserver(ImageLoadedHandler &&handler);
void notifyImageLoaded();
} // namespace internal
template <typename ObserverType>
void registerImageLoadedObserver(ObserverType *observer, void (ObserverType::*handler)()) {
auto connection = internal::plainRegisterImageLoadedObserver(func(observer, handler));
Notify::observerRegistered(observer, connection);
}
} // namespace FileDownload

View file

@ -0,0 +1,124 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "observer_peer.h"
#include "core/observer.h"
namespace App {
// Temp forward declaration (while all peer updates are not done through observers).
void emitPeerUpdated();
} // namespace App
namespace Notify {
namespace {
using internal::PeerUpdateHandler;
using SmallUpdatesList = QVector<PeerUpdate>;
NeverFreedPointer<SmallUpdatesList> SmallUpdates;
using AllUpdatesList = QMap<PeerData*, PeerUpdate>;
NeverFreedPointer<AllUpdatesList> AllUpdates;
void StartCallback() {
SmallUpdates.makeIfNull();
AllUpdates.makeIfNull();
}
void FinishCallback() {
SmallUpdates.clear();
AllUpdates.clear();
}
ObservedEventRegistrator<PeerUpdate::Flags, PeerUpdateHandler> creator(StartCallback, FinishCallback);
} // namespace
namespace internal {
ConnectionId plainRegisterPeerObserver(PeerUpdate::Flags events, PeerUpdateHandler &&handler) {
return creator.registerObserver(events, std_::forward<PeerUpdateHandler>(handler));
}
} // namespace internal
void mergePeerUpdate(PeerUpdate &mergeTo, const PeerUpdate &mergeFrom) {
if (!(mergeTo.flags & PeerUpdate::Flag::NameChanged)) {
if (mergeFrom.flags & PeerUpdate::Flag::NameChanged) {
mergeTo.oldNames = mergeFrom.oldNames;
mergeTo.oldNameFirstChars = mergeFrom.oldNameFirstChars;
}
}
if (mergeFrom.flags & PeerUpdate::Flag::SharedMediaChanged) {
mergeTo.mediaTypesMask |= mergeFrom.mediaTypesMask;
}
mergeTo.flags |= mergeFrom.flags;
}
void peerUpdatedDelayed(const PeerUpdate &update) {
t_assert(creator.started());
Global::RefHandleDelayedPeerUpdates().call();
int existingUpdatesCount = SmallUpdates->size();
for (int i = 0; i < existingUpdatesCount; ++i) {
auto &existingUpdate = (*SmallUpdates)[i];
if (existingUpdate.peer == update.peer) {
mergePeerUpdate(existingUpdate, update);
return;
}
}
if (AllUpdates->isEmpty()) {
if (existingUpdatesCount < 5) {
SmallUpdates->push_back(update);
} else {
AllUpdates->insert(update.peer, update);
}
} else {
auto it = AllUpdates->find(update.peer);
if (it != AllUpdates->cend()) {
mergePeerUpdate(it.value(), update);
return;
}
AllUpdates->insert(update.peer, update);
}
}
void peerUpdatedSendDelayed() {
if (!creator.started()) return;
App::emitPeerUpdated();
if (SmallUpdates->isEmpty()) return;
auto smallList = createAndSwap(*SmallUpdates);
auto allList = createAndSwap(*AllUpdates);
for_const (auto &update, smallList) {
creator.notify(update.flags, update);
}
for_const (auto &update, allList) {
creator.notify(update.flags, update);
}
if (SmallUpdates->isEmpty()) {
std::swap(smallList, *SmallUpdates);
SmallUpdates->resize(0);
}
}
} // namespace Notify

View file

@ -0,0 +1,100 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "core/observer.h"
namespace Notify {
// Generic notifications about updates of some PeerData.
// You can subscribe to them by Notify::registerPeerObserver().
// 0x0000FFFFU for general peer updates (valid for any peer).
// 0xFFFF0000U for specific peer updates (valid for user / chat / channel).
struct PeerUpdate {
PeerUpdate(PeerData *updated = nullptr) : peer(updated) {
}
PeerData *peer;
enum class Flag {
NameChanged = 0x00000001U,
UsernameChanged = 0x00000002U,
PhotoChanged = 0x00000004U,
AboutChanged = 0x00000008U,
NotificationsEnabled = 0x00000010U,
SharedMediaChanged = 0x00000020U,
MigrationChanged = 0x00000040U,
// For chats and channels
InviteLinkChanged = 0x00000020U,
MembersChanged = 0x00000040U,
AdminsChanged = 0x00000080U,
UserCanShareContact = 0x00010000U,
UserIsContact = 0x00020000U,
UserPhoneChanged = 0x00040000U,
UserIsBlocked = 0x00080000U,
BotCommandsChanged = 0x00100000U,
UserOnlineChanged = 0x00200000U,
ChatCanEdit = 0x00010000U,
ChannelAmIn = 0x00010000U,
ChannelAmEditor = 0x00020000U,
ChannelCanEditPhoto = 0x00040000U,
ChannelCanAddMembers = 0x00080000U,
ChannelCanViewAdmins = 0x00100000U,
ChannelCanViewMembers = 0x00200000U,
};
Q_DECLARE_FLAGS(Flags, Flag);
Flags flags = 0;
// NameChanged data
PeerData::Names oldNames;
PeerData::NameFirstChars oldNameFirstChars;
// SharedMediaChanged data
int32 mediaTypesMask = 0;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(PeerUpdate::Flags);
void peerUpdatedDelayed(const PeerUpdate &update);
inline void peerUpdatedDelayed(PeerData *peer, PeerUpdate::Flags events) {
PeerUpdate update(peer);
update.flags = events;
peerUpdatedDelayed(update);
}
void peerUpdatedSendDelayed();
namespace internal {
using PeerUpdateHandler = Function<void, const PeerUpdate&>;
ConnectionId plainRegisterPeerObserver(PeerUpdate::Flags events, PeerUpdateHandler &&handler);
} // namespace internal
template <typename ObserverType>
void registerPeerObserver(PeerUpdate::Flags events, ObserverType *observer, void (ObserverType::*handler)(const PeerUpdate &)) {
auto connection = internal::plainRegisterPeerObserver(events, func(observer, handler));
observerRegistered(observer, connection);
}
} // namespace Notify

View file

@ -611,6 +611,9 @@ void OverviewInner::onDragExec() {
mimeData->setData(qsl("application/x-td-forward-selected"), "1"); mimeData->setData(qsl("application/x-td-forward-selected"), "1");
} }
drag->setMimeData(mimeData); drag->setMimeData(mimeData);
// We don't receive mouseReleaseEvent when drag is finished.
ClickHandler::unpressed();
drag->exec(Qt::CopyAction); drag->exec(Qt::CopyAction);
if (App::main()) App::main()->updateAfterDrag(); if (App::main()) App::main()->updateAfterDrag();
return; return;
@ -639,6 +642,9 @@ void OverviewInner::onDragExec() {
} }
drag->setMimeData(mimeData); drag->setMimeData(mimeData);
// We don't receive mouseReleaseEvent when drag is finished.
ClickHandler::unpressed();
drag->exec(Qt::CopyAction); drag->exec(Qt::CopyAction);
if (App::main()) App::main()->updateAfterDrag(); if (App::main()) App::main()->updateAfterDrag();
return; return;
@ -1909,7 +1915,6 @@ OverviewWidget::OverviewWidget(QWidget *parent, PeerData *peer, MediaOverviewTyp
, _scrollSetAfterShow(0) , _scrollSetAfterShow(0)
, _scrollDelta(0) , _scrollDelta(0)
, _selCount(0) , _selCount(0)
, _sideShadow(this, st::shadowColor)
, _topShadow(this, st::shadowColor) , _topShadow(this, st::shadowColor)
, _inGrab(false) { , _inGrab(false) {
_scroll.setFocusPolicy(Qt::NoFocus); _scroll.setFocusPolicy(Qt::NoFocus);
@ -1917,8 +1922,6 @@ OverviewWidget::OverviewWidget(QWidget *parent, PeerData *peer, MediaOverviewTyp
_scroll.move(0, 0); _scroll.move(0, 0);
_inner.move(0, 0); _inner.move(0, 0);
_sideShadow.setVisible(!Adaptive::OneColumn());
updateScrollColors(); updateScrollColors();
_scroll.show(); _scroll.show();
@ -1970,8 +1973,6 @@ void OverviewWidget::resizeEvent(QResizeEvent *e) {
_topShadow.resize(width() - ((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0), st::lineWidth); _topShadow.resize(width() - ((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0), st::lineWidth);
_topShadow.moveToLeft((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0, 0); _topShadow.moveToLeft((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0, 0);
_sideShadow.resize(st::lineWidth, height());
_sideShadow.moveToLeft(0, 0);
} }
void OverviewWidget::paintEvent(QPaintEvent *e) { void OverviewWidget::paintEvent(QPaintEvent *e) {
@ -1979,14 +1980,16 @@ void OverviewWidget::paintEvent(QPaintEvent *e) {
Painter p(this); Painter p(this);
if (_a_show.animating()) { if (_a_show.animating()) {
int retina = cIntRetinaFactor();
int inCacheTop = st::topBarHeight;
if (a_coordOver.current() > 0) { if (a_coordOver.current() > 0) {
p.drawPixmap(QRect(0, 0, a_coordOver.current(), height()), _cacheUnder, QRect(-a_coordUnder.current() * cRetinaFactor(), 0, a_coordOver.current() * cRetinaFactor(), height() * cRetinaFactor())); p.drawPixmap(QRect(0, 0, a_coordOver.current(), height()), _cacheUnder, QRect(-a_coordUnder.current() * retina, inCacheTop * retina, a_coordOver.current() * retina, height() * retina));
p.setOpacity(a_shadow.current() * st::slideFadeOut); p.setOpacity(a_progress.current() * st::slideFadeOut);
p.fillRect(0, 0, a_coordOver.current(), height(), st::black->b); p.fillRect(0, 0, a_coordOver.current(), height(), st::black);
p.setOpacity(1); p.setOpacity(1);
} }
p.drawPixmap(a_coordOver.current(), 0, _cacheOver); p.drawPixmap(QRect(a_coordOver.current(), 0, _cacheOver.width() / retina, height()), _cacheOver, QRect(0, inCacheTop * retina, _cacheOver.width(), height() * retina));
p.setOpacity(a_shadow.current()); p.setOpacity(a_progress.current());
p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), height()), App::sprite(), st::slideShadow.rect()); p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), height()), App::sprite(), st::slideShadow.rect());
return; return;
} }
@ -2012,9 +2015,15 @@ void OverviewWidget::scrollReset() {
void OverviewWidget::paintTopBar(Painter &p, float64 over, int32 decreaseWidth) { void OverviewWidget::paintTopBar(Painter &p, float64 over, int32 decreaseWidth) {
if (_a_show.animating()) { if (_a_show.animating()) {
p.drawPixmap(a_coordUnder.current(), 0, _cacheTopBarUnder); int retina = cIntRetinaFactor();
p.drawPixmap(a_coordOver.current(), 0, _cacheTopBarOver); if (a_coordOver.current() > 0) {
p.setOpacity(a_shadow.current()); p.drawPixmap(QRect(0, 0, a_coordOver.current(), st::topBarHeight), _cacheUnder, QRect(-a_coordUnder.current() * retina, 0, a_coordOver.current() * retina, st::topBarHeight * retina));
p.setOpacity(a_progress.current() * st::slideFadeOut);
p.fillRect(0, 0, a_coordOver.current(), st::topBarHeight, st::black);
p.setOpacity(1);
}
p.drawPixmap(QRect(a_coordOver.current(), 0, _cacheOver.width() / retina, st::topBarHeight), _cacheOver, QRect(0, 0, _cacheOver.width(), st::topBarHeight * retina));
p.setOpacity(a_progress.current());
p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), st::topBarHeight), App::sprite(), st::slideShadow.rect()); p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), st::topBarHeight), App::sprite(), st::slideShadow.rect());
return; return;
} }
@ -2119,44 +2128,54 @@ void OverviewWidget::fastShow(bool back, int32 lastScrollTop) {
if (App::app()) App::app()->mtpUnpause(); if (App::app()) App::app()->mtpUnpause();
} }
void OverviewWidget::animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTopBarCache, bool back, int32 lastScrollTop) { void OverviewWidget::setLastScrollTop(int lastScrollTop) {
if (App::app()) App::app()->mtpPause();
(back ? _cacheOver : _cacheUnder) = bgAnimCache;
(back ? _cacheTopBarOver : _cacheTopBarUnder) = bgAnimTopBarCache;
resizeEvent(0); resizeEvent(0);
_scroll.scrollToY(lastScrollTop < 0 ? countBestScroll() : lastScrollTop); _scroll.scrollToY(lastScrollTop < 0 ? countBestScroll() : lastScrollTop);
(back ? _cacheUnder : _cacheOver) = myGrab(this); }
App::main()->topBar()->stopAnim();
(back ? _cacheTopBarUnder : _cacheTopBarOver) = myGrab(App::main()->topBar()); void OverviewWidget::showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams &params) {
if (App::app()) App::app()->mtpPause();
resizeEvent(0);
_cacheUnder = params.oldContentCache;
show();
_topShadow.setVisible(params.withTopBarShadow ? false : true);
_cacheOver = App::main()->grabForShowAnimation(params);
_topShadow.setVisible(params.withTopBarShadow ? true : false);
App::main()->topBar()->startAnim(); App::main()->topBar()->startAnim();
_scrollSetAfterShow = _scroll.scrollTop(); _scrollSetAfterShow = _scroll.scrollTop();
_scroll.hide(); _scroll.hide();
_topShadow.hide();
a_coordUnder = back ? anim::ivalue(-qFloor(st::slideShift * width()), 0) : anim::ivalue(0, -qFloor(st::slideShift * width())); int delta = st::slideShift;
a_coordOver = back ? anim::ivalue(0, width()) : anim::ivalue(width(), 0); if (direction == Window::SlideDirection::FromLeft) {
a_shadow = back ? anim::fvalue(1, 0) : anim::fvalue(0, 1); a_progress = anim::fvalue(1, 0);
std::swap(_cacheUnder, _cacheOver);
a_coordUnder = anim::ivalue(-delta, 0);
a_coordOver = anim::ivalue(0, width());
} else {
a_progress = anim::fvalue(0, 1);
a_coordUnder = anim::ivalue(0, -delta);
a_coordOver = anim::ivalue(width(), 0);
}
_a_show.start(); _a_show.start();
show();
App::main()->topBar()->update(); App::main()->topBar()->update();
_inner.activate();
activate();
} }
void OverviewWidget::step_show(float64 ms, bool timer) { void OverviewWidget::step_show(float64 ms, bool timer) {
float64 dt = ms / st::slideDuration; float64 dt = ms / st::slideDuration;
if (dt >= 1) { if (dt >= 1) {
_a_show.stop(); _a_show.stop();
_sideShadow.setVisible(!Adaptive::OneColumn());
_topShadow.show(); _topShadow.show();
a_coordUnder.finish(); a_coordUnder.finish();
a_coordOver.finish(); a_coordOver.finish();
a_shadow.finish(); a_progress.finish();
_cacheUnder = _cacheOver = _cacheTopBarUnder = _cacheTopBarOver = QPixmap(); _cacheUnder = _cacheOver = QPixmap();
App::main()->topBar()->stopAnim(); App::main()->topBar()->stopAnim();
doneShow(); doneShow();
@ -2165,7 +2184,7 @@ void OverviewWidget::step_show(float64 ms, bool timer) {
} else { } else {
a_coordUnder.update(dt, st::slideFunction); a_coordUnder.update(dt, st::slideFunction);
a_coordOver.update(dt, st::slideFunction); a_coordOver.update(dt, st::slideFunction);
a_shadow.update(dt, st::slideFunction); a_progress.update(dt, st::slideFunction);
} }
if (timer) { if (timer) {
update(); update();
@ -2173,10 +2192,6 @@ void OverviewWidget::step_show(float64 ms, bool timer) {
} }
} }
void OverviewWidget::updateAdaptiveLayout() {
_sideShadow.setVisible(!Adaptive::OneColumn());
}
void OverviewWidget::doneShow() { void OverviewWidget::doneShow() {
_scroll.show(); _scroll.show();
_scroll.scrollToY(_scrollSetAfterShow); _scroll.scrollToY(_scrollSetAfterShow);
@ -2308,6 +2323,8 @@ void OverviewWidget::onDeleteSelected() {
} }
void OverviewWidget::onDeleteSelectedSure() { void OverviewWidget::onDeleteSelectedSure() {
Ui::hideLayer();
SelectedItemSet sel; SelectedItemSet sel;
_inner.fillSelectedItems(sel); _inner.fillSelectedItems(sel);
if (sel.isEmpty()) return; if (sel.isEmpty()) return;
@ -2323,7 +2340,6 @@ void OverviewWidget::onDeleteSelectedSure() {
for (SelectedItemSet::const_iterator i = sel.cbegin(), e = sel.cend(); i != e; ++i) { for (SelectedItemSet::const_iterator i = sel.cbegin(), e = sel.cend(); i != e; ++i) {
i.value()->destroy(); i.value()->destroy();
} }
Ui::hideLayer();
for (QMap<PeerData*, QVector<MTPint> >::const_iterator i = ids.cbegin(), e = ids.cend(); i != e; ++i) { for (QMap<PeerData*, QVector<MTPint> >::const_iterator i = ids.cbegin(), e = ids.cend(); i != e; ++i) {
App::main()->deleteMessages(i.key(), i.value()); App::main()->deleteMessages(i.key(), i.value());
@ -2331,6 +2347,8 @@ void OverviewWidget::onDeleteSelectedSure() {
} }
void OverviewWidget::onDeleteContextSure() { void OverviewWidget::onDeleteContextSure() {
Ui::hideLayer();
HistoryItem *item = App::contextItem(); HistoryItem *item = App::contextItem();
if (!item || item->type() != HistoryItemMsg) { if (!item || item->type() != HistoryItemMsg) {
return; return;
@ -2340,12 +2358,11 @@ void OverviewWidget::onDeleteContextSure() {
History *h = item->history(); History *h = item->history();
bool wasOnServer = (item->id > 0), wasLast = (h->lastMsg == item); bool wasOnServer = (item->id > 0), wasLast = (h->lastMsg == item);
item->destroy(); item->destroy();
if (!wasOnServer && wasLast && !h->lastMsg) { if (!wasOnServer && wasLast && !h->lastMsg) {
App::main()->checkPeerHistory(h->peer); App::main()->checkPeerHistory(h->peer);
} }
Ui::hideLayer();
if (wasOnServer) { if (wasOnServer) {
App::main()->deleteMessages(h->peer, toDelete); App::main()->deleteMessages(h->peer, toDelete);
} }

View file

@ -20,6 +20,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/ */
#pragma once #pragma once
#include "window/section_widget.h"
namespace Overview { namespace Overview {
namespace Layout { namespace Layout {
@ -276,10 +278,13 @@ public:
int32 countBestScroll() const; int32 countBestScroll() const;
void fastShow(bool back = false, int32 lastScrollTop = -1); void fastShow(bool back = false, int32 lastScrollTop = -1);
void animShow(const QPixmap &oldAnimCache, const QPixmap &bgAnimTopBarCache, bool back = false, int32 lastScrollTop = -1); bool hasTopBarShadow() const {
return true;
}
void setLastScrollTop(int lastScrollTop);
void showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams &params);
void step_show(float64 ms, bool timer); void step_show(float64 ms, bool timer);
void updateAdaptiveLayout();
void doneShow(); void doneShow();
void mediaOverviewUpdated(PeerData *peer, MediaOverviewType type); void mediaOverviewUpdated(PeerData *peer, MediaOverviewType type);
@ -300,14 +305,17 @@ public:
void updateAfterDrag(); void updateAfterDrag();
void grabStart() override { void grabStart() override {
_sideShadow.hide();
_inGrab = true; _inGrab = true;
resizeEvent(0); resizeEvent(0);
} }
void grapWithoutTopBarShadow() {
grabStart();
_topShadow.hide();
}
void grabFinish() override { void grabFinish() override {
_sideShadow.setVisible(!Adaptive::OneColumn());
_inGrab = false; _inGrab = false;
resizeEvent(0); resizeEvent(0);
_topShadow.show();
} }
void rpcClear() override { void rpcClear() override {
_inner.rpcClear(); _inner.rpcClear();
@ -343,9 +351,9 @@ private:
QString _header; QString _header;
Animation _a_show; Animation _a_show;
QPixmap _cacheUnder, _cacheOver, _cacheTopBarUnder, _cacheTopBarOver; QPixmap _cacheUnder, _cacheOver;
anim::ivalue a_coordUnder, a_coordOver; anim::ivalue a_coordUnder, a_coordOver;
anim::fvalue a_shadow; anim::fvalue a_progress;
int32 _scrollSetAfterShow; int32 _scrollSetAfterShow;
@ -354,7 +362,7 @@ private:
int32 _selCount; int32 _selCount;
PlainShadow _sideShadow, _topShadow; PlainShadow _topShadow;
bool _inGrab; bool _inGrab;
}; };

View file

@ -119,7 +119,7 @@ void PasscodeWidget::animShow(const QPixmap &bgAnimCache, bool back) {
(back ? _cacheUnder : _cacheOver) = myGrab(this); (back ? _cacheUnder : _cacheOver) = myGrab(this);
hideAll(); hideAll();
a_coordUnder = back ? anim::ivalue(-qFloor(st::slideShift * width()), 0) : anim::ivalue(0, -qFloor(st::slideShift * width())); a_coordUnder = back ? anim::ivalue(-st::slideShift, 0) : anim::ivalue(0, -st::slideShift);
a_coordOver = back ? anim::ivalue(0, width()) : anim::ivalue(width(), 0); a_coordOver = back ? anim::ivalue(0, width()) : anim::ivalue(width(), 0);
a_shadow = back ? anim::fvalue(1, 0) : anim::fvalue(0, 1); a_shadow = back ? anim::fvalue(1, 0) : anim::fvalue(0, 1);
_a_show.start(); _a_show.start();

View file

@ -33,12 +33,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
PlayerWidget::PlayerWidget(QWidget *parent) : TWidget(parent) PlayerWidget::PlayerWidget(QWidget *parent) : TWidget(parent)
, _a_state(animation(this, &PlayerWidget::step_state)) , _a_state(animation(this, &PlayerWidget::step_state))
, _a_progress(animation(this, &PlayerWidget::step_progress)) , _a_progress(animation(this, &PlayerWidget::step_progress)) {
, _sideShadow(this, st::shadowColor) {
resize(st::wndMinWidth, st::playerHeight); resize(st::wndMinWidth, st::playerHeight);
setMouseTracking(true); setMouseTracking(true);
memset(_stateHovers, 0, sizeof(_stateHovers)); memset(_stateHovers, 0, sizeof(_stateHovers));
_sideShadow.setVisible(!Adaptive::OneColumn());
} }
void PlayerWidget::paintEvent(QPaintEvent *e) { void PlayerWidget::paintEvent(QPaintEvent *e) {
@ -343,10 +341,6 @@ void PlayerWidget::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type)
} }
} }
void PlayerWidget::updateAdaptiveLayout() {
_sideShadow.setVisible(!Adaptive::OneColumn());
}
bool PlayerWidget::seekingSong(const SongMsgId &song) const { bool PlayerWidget::seekingSong(const SongMsgId &song) const {
return (_down == OverPlayback) && (song == _song); return (_down == OverPlayback) && (song == _song);
} }
@ -570,9 +564,6 @@ void PlayerWidget::resizeEvent(QResizeEvent *e) {
int32 infoLeft = (_fullAvailable ? (_nextRect.x() + _nextRect.width()) : (_playRect.x() + _playRect.width())); int32 infoLeft = (_fullAvailable ? (_nextRect.x() + _nextRect.width()) : (_playRect.x() + _playRect.width()));
_infoRect = QRect(infoLeft + st::playerSkip / 2, 0, (_fullAvailable ? _fullRect.x() : _repeatRect.x()) - infoLeft - st::playerSkip, availh); _infoRect = QRect(infoLeft + st::playerSkip / 2, 0, (_fullAvailable ? _fullRect.x() : _repeatRect.x()) - infoLeft - st::playerSkip, availh);
_sideShadow.resize(st::lineWidth, height());
_sideShadow.moveToLeft(0, 0);
update(); update();
} }

View file

@ -52,7 +52,6 @@ public:
void clearSelection(); void clearSelection();
void mediaOverviewUpdated(PeerData *peer, MediaOverviewType type); void mediaOverviewUpdated(PeerData *peer, MediaOverviewType type);
void updateAdaptiveLayout();
bool seekingSong(const SongMsgId &song) const; bool seekingSong(const SongMsgId &song) const;
@ -136,6 +135,4 @@ private:
anim::fvalue a_loadProgress = { 0., 0. }; anim::fvalue a_loadProgress = { 0., 0. };
Animation _a_progress; Animation _a_progress;
PlainShadow _sideShadow;
}; };

View file

@ -0,0 +1,169 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
using "basic.style";
using "basic_types.style";
profileBg: windowBg;
profileTopBarHeight: topBarHeight;
profileTopBarBackIconFg: #51b3e0;
profileTopBarBackIcon: icon {
{ "topbar_back_arrow", profileTopBarBackIconFg },
};
profileTopBarBackIconPosition: point(15px, 19px);
profileTopBarBackFont: font(14px);
profileTopBarBackFg: #1485c2;
profileTopBarBackPosition: point(32px, 17px);
profileFixedBarButton: flatButton(topBarButton) {
}
profileMarginTop: 13px;
profilePhotoSize: 112px;
profilePhotoLeftMin: 18px;
profilePhotoLeftMax: 45px;
profilePhotoDuration: 500;
profileNameLeft: 26px;
profileNameTop: 9px;
profileNameLabel: flatLabel(labelDefFlat) {
margin: margins(10px, 5px, 10px, 5px);
font: font(16px);
width: 160px;
maxHeight: 24px;
}
profileNameTextStyle: textStyle(defaultTextStyle) {
}
profileStatusLeft: 27px;
profileStatusTop: 35px;
profileStatusFont: normalFont;
profileStatusFg: windowSubTextFg;
profileStatusFgActive: windowActiveTextFg;
profileMarginBottom: 30px;
profileActiveBg: #3fb0e4;
profileButtonLeft: 27px;
profileButtonTop: 88px;
profileButtonSkip: 10px;
profilePrimaryButton: BoxButton {
textFg: #ffffff;
textFgOver: #ffffff;
textBg: profileActiveBg;
textBgOver: profileActiveBg;
width: -34px;
height: 34px;
textTop: 8px;
font: semiboldFont;
duration: 200;
}
profileSecondaryButton: BoxButton(profilePrimaryButton) {
textFg: #189dda;
textFgOver: #189dda;
textBg: #ffffff;
textBgOver: #f2f7fa;
}
profileAddMemberIcon: icon {
{ "profile_add_member", profileActiveBg, point(20px, 10px) },
};
profileAddMemberButton: BoxButton(profileSecondaryButton) {
width: 62px;
icon: profileAddMemberIcon;
}
profileDropAreaBg: profileBg;
profileDropAreaFg: profileActiveBg;
profileDropAreaPadding: margins(25px, 3px, 25px, 20px);
profileDropAreaTitleFont: font(24px);
profileDropAreaTitleTop: 30px;
profileDropAreaSubtitleFont: font(16px);
profileDropAreaSubtitleTop: 68px;
profileDropAreaBorderFg: profileDropAreaFg;
profileDropAreaBorderWidth: 3px;
profileDropAreaDuration: 200;
profileDividerFg: windowShadowFg;
profileDividerLeft: icon {
{ "profile_divider_left", profileDividerFg },
};
profileDividerFill: icon {
{ "profile_divider_fill", profileDividerFg },
};
profileBlocksTop: 7px;
profileBlocksBottom: 20px;
profileBlockLeftMin: 8px;
profileBlockLeftMax: 25px;
profileBlockNarrowWidthMin: 220px;
profileBlockWideWidthMin: 300px;
profileBlockWideWidthMax: 340px;
profileBlockMarginTop: 14px;
profileBlockMarginRight: 10px;
profileBlockMarginBottom: 4px;
profileBlockTitleHeight: 25px;
profileBlockTitleFont: font(14px semibold);
profileBlockTitleFg: black;
profileBlockTitlePosition: point(24px, 0px);
profileBlockLabel: flatLabel(labelDefFlat) {
textFg: windowSubTextFg;
}
profileBlockTextPart: flatLabel(labelDefFlat) {
width: 180px;
margin: margins(5px, 5px, 5px, 5px);
}
profileBlockOneLineTextPart: flatLabel(profileBlockTextPart) {
width: 0px; // No need to set minWidth in one-line text.
maxHeight: 20px;
}
profileBlockOneLineSkip: 9px;
profileBlockOneLineWidthMax: 240px;
profileInviteLinkText: flatLabel(profileBlockTextPart) {
width: 1px; // Required for BreakEverywhere
}
profileLimitReachedSkip: 6px;
profileMemberHeight: 58px;
profileMemberPaddingLeft: 16px;
profileMemberPhotoSize: 46px;
profileMemberPhotoPosition: point(12px, 6px);
profileMemberNamePosition: point(68px, 11px);
profileMemberNameFg: windowTextFg;
profileMemberStatusPosition: point(68px, 31px);
profileMemberStatusFg: windowSubTextFg;
profileMemberStatusFgOver: windowSubTextFg;
profileMemberStatusFgActive: windowActiveTextFg;
profileMemberAdminIcon: icon {
{ "profile_admin_star", profileActiveBg, point(4px, 2px) },
};
profileLimitReachedLabel: flatLabel(labelDefFlat) {
width: 180px;
margin: margins(profileMemberPaddingLeft, 9px, profileMemberPaddingLeft, 6px);
}
profileLimitReachedStyle: textStyle(defaultTextStyle) {
lineHeight: 19px;
}
profileReportReasonOther: InputArea(defaultInputArea) {
textMargins: margins(1px, 6px, 1px, 4px);
heightMax: 115px;
}

View file

@ -0,0 +1,363 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "profile/profile_actions_widget.h"
#include "styles/style_profile.h"
#include "ui/buttons/left_outline_button.h"
#include "boxes/confirmbox.h"
#include "boxes/report_box.h"
#include "mainwidget.h"
#include "observer_peer.h"
#include "apiwrap.h"
#include "lang.h"
namespace Profile {
using UpdateFlag = Notify::PeerUpdate::Flag;
ActionsWidget::ActionsWidget(QWidget *parent, PeerData *peer) : BlockWidget(parent, peer, lang(lng_profile_actions_section)) {
auto observeEvents = UpdateFlag::ChannelAmIn
| UpdateFlag::UserIsBlocked
| UpdateFlag::BotCommandsChanged
| UpdateFlag::MembersChanged;
Notify::registerPeerObserver(observeEvents, this, &ActionsWidget::notifyPeerUpdated);
validateBlockStatus();
refreshButtons();
}
void ActionsWidget::notifyPeerUpdated(const Notify::PeerUpdate &update) {
if (update.peer != peer()) {
return;
}
auto needFullRefresh = [&update, this]() {
if (update.flags & UpdateFlag::BotCommandsChanged) {
if (_hasBotHelp != hasBotCommand(qsl("help")) || _hasBotSettings != hasBotCommand(qsl("settings"))) {
return true;
}
}
return false;
};
if (needFullRefresh()) {
refreshButtons();
} else {
if (update.flags & UpdateFlag::MembersChanged) {
refreshDeleteChannel();
}
if (update.flags & UpdateFlag::ChannelAmIn) {
refreshLeaveChannel();
}
if (update.flags & UpdateFlag::UserIsBlocked) {
refreshBlockUser();
}
refreshVisibility();
}
contentSizeUpdated();
}
void ActionsWidget::validateBlockStatus() const {
auto needFullPeer = [this]() {
if (auto user = peer()->asUser()) {
if (user->blockStatus() == UserData::BlockStatus::Unknown) {
return true;
} else if (user->botInfo && !user->botInfo->inited) {
return true;
}
}
return false;
};
if (needFullPeer()) {
if (App::api()) App::api()->requestFullPeer(peer());
}
}
Ui::LeftOutlineButton *ActionsWidget::addButton(const QString &text, const char *slot, const style::OutlineButton &st, int skipHeight) {
auto result = new Ui::LeftOutlineButton(this, text, st);
connect(result, SIGNAL(clicked()), this, slot);
result->show();
int top = buttonsBottom() + skipHeight;
resizeButton(result, top);
_buttons.push_back(result);
return result;
};
void ActionsWidget::resizeButton(Ui::LeftOutlineButton *button, int top) {
int left = defaultOutlineButtonLeft();
int availableWidth = width() - left - st::profileBlockMarginRight;
accumulate_min(availableWidth, st::profileBlockOneLineWidthMax);
button->resizeToWidth(availableWidth);
button->moveToLeft(left, top);
}
void ActionsWidget::refreshButtons() {
auto buttons = createAndSwap(_buttons);
for_const (auto &button, buttons) {
delete button;
}
_blockUser = _leaveChannel = nullptr;
if (auto user = peer()->asUser()) {
if ((_hasBotHelp = hasBotCommand(qsl("help")))) {
addButton(lang(lng_profile_bot_help), SLOT(onBotHelp()));
}
if ((_hasBotSettings = hasBotCommand(qsl("settings")))) {
addButton(lang(lng_profile_bot_settings), SLOT(onBotSettings()));
}
addButton(lang(lng_profile_clear_history), SLOT(onClearHistory()));
addButton(lang(lng_profile_delete_conversation), SLOT(onDeleteConversation()));
refreshBlockUser();
} else if (auto chat = peer()->asChat()) {
if (chat->amCreator()) {
addButton(lang(lng_profile_migrate_button), SLOT(onUpgradeToSupergroup()));
}
addButton(lang(lng_profile_clear_history), SLOT(onClearHistory()));
addButton(lang(lng_profile_clear_and_exit), SLOT(onDeleteConversation()));
} else if (auto channel = peer()->asChannel()) {
if (!channel->amCreator()) {
addButton(lang(lng_profile_report), SLOT(onReport()));
}
refreshDeleteChannel();
refreshLeaveChannel();
}
refreshVisibility();
}
void ActionsWidget::refreshVisibility() {
setVisible(!_buttons.isEmpty());
}
QString ActionsWidget::getBlockButtonText() const {
auto user = peer()->asUser();
if (!user || (user->id == peerFromUser(MTP::authedId()))) return QString();
if (user->blockStatus() == UserData::BlockStatus::Unknown) return QString();
if (user->isBlocked()) {
if (user->botInfo) {
return lang(lng_profile_unblock_bot);
}
return lang(lng_profile_unblock_user);
} else if (user->botInfo) {
return lang(lng_profile_block_bot);
}
return lang(lng_profile_block_user);
}
bool ActionsWidget::hasBotCommand(const QString &command) const {
auto user = peer()->asUser();
if (!user || !user->botInfo || user->botInfo->commands.isEmpty()) {
return false;
}
for_const (auto &cmd, user->botInfo->commands) {
if (!cmd.command.compare(command, Qt::CaseInsensitive)) {
return true;
}
}
return false;
}
void ActionsWidget::sendBotCommand(const QString &command) {
auto user = peer()->asUser();
if (user && user->botInfo && !user->botInfo->commands.isEmpty()) {
for_const (auto &cmd, user->botInfo->commands) {
if (!cmd.command.compare(command, Qt::CaseInsensitive)) {
Ui::showPeerHistory(user, ShowAtTheEndMsgId);
App::sendBotCommand(user, user, '/' + cmd.command);
return;
}
}
}
// Command was not found.
refreshButtons();
contentSizeUpdated();
}
void ActionsWidget::refreshBlockUser() {
if (auto user = peer()->asUser()) {
auto blockText = getBlockButtonText();
if (_blockUser) {
if (blockText.isEmpty()) {
_buttons.removeOne(_blockUser);
delete _blockUser;
_blockUser = nullptr;
} else {
_blockUser->setText(blockText);
}
} else if (!blockText.isEmpty()) {
_blockUser = addButton(blockText, SLOT(onBlockUser()), st::attentionLeftOutlineButton, st::profileBlockOneLineSkip);
}
}
}
void ActionsWidget::refreshDeleteChannel() {
if (auto channel = peer()->asChannel()) {
if (channel->canDelete() && !_deleteChannel) {
_deleteChannel = addButton(lang(channel->isMegagroup() ? lng_profile_delete_group : lng_profile_delete_channel), SLOT(onDeleteChannel()), st::attentionLeftOutlineButton);
} else if (!channel->canDelete() && _deleteChannel) {
_buttons.removeOne(_deleteChannel);
delete _deleteChannel;
_deleteChannel = nullptr;
}
}
}
void ActionsWidget::refreshLeaveChannel() {
if (auto channel = peer()->asChannel()) {
if (!channel->amCreator()) {
if (channel->amIn() && !_leaveChannel) {
_leaveChannel = addButton(lang(channel->isMegagroup() ? lng_profile_leave_group : lng_profile_leave_channel), SLOT(onLeaveChannel()));
} else if (!channel->amIn() && _leaveChannel) {
_buttons.removeOne(_leaveChannel);
delete _leaveChannel;
_leaveChannel = nullptr;
}
}
}
}
int ActionsWidget::resizeGetHeight(int newWidth) {
for_const (auto button, _buttons) {
resizeButton(button, button->y());
}
return buttonsBottom();
}
int ActionsWidget::buttonsBottom() const {
if (_buttons.isEmpty()) {
return contentTop();
}
auto lastButton = _buttons.back();
return lastButton->y() + lastButton->height();
}
void ActionsWidget::onBotHelp() {
sendBotCommand(qsl("help"));
}
void ActionsWidget::onBotSettings() {
sendBotCommand(qsl("settings"));
}
void ActionsWidget::onClearHistory() {
QString confirmation;
if (auto user = peer()->asUser()) {
confirmation = lng_sure_delete_history(lt_contact, App::peerName(peer()));
} else if (auto chat = peer()->asChat()) {
confirmation = lng_sure_delete_group_history(lt_group, App::peerName(peer()));
}
if (!confirmation.isEmpty()) {
auto box = new ConfirmBox(confirmation, lang(lng_box_delete), st::attentionBoxButton);
connect(box, SIGNAL(confirmed()), this, SLOT(onClearHistorySure()));
Ui::showLayer(box);
}
}
void ActionsWidget::onClearHistorySure() {
Ui::hideLayer();
App::main()->clearHistory(peer());
}
void ActionsWidget::onDeleteConversation() {
QString confirmation, confirmButton;
if (auto user = peer()->asUser()) {
confirmation = lng_sure_delete_history(lt_contact, App::peerName(peer()));
confirmButton = lang(lng_box_delete);
} else if (auto chat = peer()->asChat()) {
confirmation = lng_sure_delete_and_exit(lt_group, App::peerName(peer()));
confirmButton = lang(lng_box_leave);
}
if (!confirmation.isEmpty()) {
auto box = new ConfirmBox(confirmation, confirmButton, st::attentionBoxButton);
connect(box, SIGNAL(confirmed()), this, SLOT(onDeleteConversationSure()));
Ui::showLayer(box);
}
}
void ActionsWidget::onDeleteConversationSure() {
Ui::hideLayer();
Ui::showChatsList();
if (auto user = peer()->asUser()) {
App::main()->deleteConversation(peer());
} else if (auto chat = peer()->asChat()) {
App::main()->deleteAndExit(chat);
}
}
void ActionsWidget::onBlockUser() {
if (auto user = peer()->asUser()) {
if (user->isBlocked()) {
App::api()->unblockUser(user);
} else {
App::api()->blockUser(user);
}
}
}
void ActionsWidget::onUpgradeToSupergroup() {
if (auto chat = peer()->asChat()) {
Ui::showLayer(new ConvertToSupergroupBox(chat));
}
}
void ActionsWidget::onDeleteChannel() {
auto 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()));
Ui::showLayer(box);
}
void ActionsWidget::onDeleteChannelSure() {
Ui::hideLayer();
Ui::showChatsList();
if (auto chat = peer()->migrateFrom()) {
App::main()->deleteAndExit(chat);
}
if (auto channel = peer()->asChannel()) {
MTP::send(MTPchannels_DeleteChannel(channel->inputChannel), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::deleteChannelFailed));
}
}
void ActionsWidget::onLeaveChannel() {
auto channel = peer()->asChannel();
if (!channel) return;
auto box = new ConfirmBox(lang(channel->isMegagroup() ? lng_sure_leave_group : lng_sure_leave_channel), lang(lng_box_leave));
connect(box, SIGNAL(confirmed()), this, SLOT(onLeaveChannelSure()));
Ui::showLayer(box);
}
void ActionsWidget::onLeaveChannelSure() {
App::api()->leaveChannel(peer()->asChannel());
}
void ActionsWidget::onReport() {
if (auto channel = peer()->asChannel()) {
Ui::showLayer(new ReportBox(channel));
}
}
} // namespace Profile

View file

@ -0,0 +1,102 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "profile/profile_block_widget.h"
namespace Ui {
class LeftOutlineButton;
} // namespace Ui
namespace Notify {
struct PeerUpdate;
} // namespace Notify
namespace Profile {
class ActionsWidget : public BlockWidget {
Q_OBJECT
public:
ActionsWidget(QWidget *parent, PeerData *peer);
protected:
// Resizes content and counts natural widget height for the desired width.
int resizeGetHeight(int newWidth) override;
private slots:
void onBotHelp();
void onBotSettings();
void onClearHistory();
void onClearHistorySure();
void onDeleteConversation();
void onDeleteConversationSure();
void onBlockUser();
void onUpgradeToSupergroup();
void onDeleteChannel();
void onDeleteChannelSure();
void onLeaveChannel();
void onLeaveChannelSure();
void onReport();
private:
// Observed notifications.
void notifyPeerUpdated(const Notify::PeerUpdate &update);
void validateBlockStatus() const;
int buttonsBottom() const;
void refreshButtons();
void refreshBlockUser();
void refreshDeleteChannel();
void refreshLeaveChannel();
void refreshVisibility();
Ui::LeftOutlineButton *addButton(const QString &text, const char *slot
, const style::OutlineButton &st = st::defaultLeftOutlineButton, int skipHeight = 0);
void resizeButton(Ui::LeftOutlineButton *button, int top);
QString getBlockButtonText() const;
bool hasBotCommand(const QString &command) const;
void sendBotCommand(const QString &command);
QList<Ui::LeftOutlineButton*> _buttons;
//ChildWidget<Ui::LeftOutlineButton> _botHelp = { nullptr };
//ChildWidget<Ui::LeftOutlineButton> _botSettings = { nullptr };
//ChildWidget<Ui::LeftOutlineButton> _reportChannel = { nullptr };
//ChildWidget<Ui::LeftOutlineButton> _leaveChannel = { nullptr };
//ChildWidget<Ui::LeftOutlineButton> _deleteChannel = { nullptr };
//ChildWidget<Ui::LeftOutlineButton> _upgradeToSupergroup = { nullptr };
//ChildWidget<Ui::LeftOutlineButton> _clearHistory = { nullptr };
//ChildWidget<Ui::LeftOutlineButton> _deleteConversation = { nullptr };
//ChildWidget<Ui::LeftOutlineButton> _blockUser = { nullptr };
// Hold some button pointers to update / toggle them.
bool _hasBotHelp = false;
bool _hasBotSettings = false;
Ui::LeftOutlineButton *_blockUser = nullptr;
Ui::LeftOutlineButton *_deleteChannel = nullptr;
Ui::LeftOutlineButton *_leaveChannel = nullptr;
};
} // namespace Profile

View file

@ -0,0 +1,57 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "profile/profile_block_widget.h"
#include "styles/style_profile.h"
namespace Profile {
BlockWidget::BlockWidget(QWidget *parent, PeerData *peer, const QString &title) : TWidget(parent)
, _peer(peer)
, _title(title) {
}
void BlockWidget::resizeToWidth(int newWidth) {
resize(newWidth, resizeGetHeight(newWidth));
}
int BlockWidget::contentTop() const {
return st::profileBlockMarginTop + st::profileBlockTitleHeight;
}
void BlockWidget::paintEvent(QPaintEvent *e) {
Painter p(this);
p.setFont(st::profileBlockTitleFont);
p.setPen(st::profileBlockTitleFg);
int titleLeft = st::profileBlockTitlePosition.x();
int titleTop = st::profileBlockMarginTop + st::profileBlockTitlePosition.y();
p.drawTextLeft(titleLeft, titleTop, width(), _title);
paintContents(p);
}
int defaultOutlineButtonLeft() {
return st::profileBlockTitlePosition.x() - st::defaultLeftOutlineButton.padding.left();
}
} // namespace Profile

View file

@ -0,0 +1,74 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "core/observer.h"
namespace Profile {
class BlockWidget : public TWidget, public Notify::Observer {
Q_OBJECT
public:
BlockWidget(QWidget *parent, PeerData *peer, const QString &title);
// Count new height for width=newWidth and resize to it.
void resizeToWidth(int newWidth);
// Updates the area that is visible inside the scroll container.
virtual void setVisibleTopBottom(int visibleTop, int visibleBottom) {
}
virtual ~BlockWidget() {
}
signals:
void heightUpdated();
protected:
void paintEvent(QPaintEvent *e) override;
virtual void paintContents(Painter &p) {
}
// Where does the block content start (after the title).
int contentTop() const;
// Resizes content and counts natural widget height for the desired width.
virtual int resizeGetHeight(int newWidth) = 0;
void contentSizeUpdated() {
resizeToWidth(width());
emit heightUpdated();
}
PeerData *peer() const {
return _peer;
}
private:
PeerData *_peer;
QString _title;
};
int defaultOutlineButtonLeft();
} // namespace Profile

View file

@ -0,0 +1,499 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "profile/profile_cover.h"
#include "styles/style_profile.h"
#include "profile/profile_cover_drop_area.h"
#include "profile/profile_userpic_button.h"
#include "ui/buttons/round_button.h"
#include "ui/filedialog.h"
#include "observer_peer.h"
#include "boxes/confirmbox.h"
#include "boxes/contactsbox.h"
#include "boxes/photocropbox.h"
#include "lang.h"
#include "apiwrap.h"
#include "mainwidget.h"
#include "mainwindow.h"
namespace Profile {
namespace {
using UpdateFlag = Notify::PeerUpdate::Flag;
const auto ButtonsUpdateFlags = UpdateFlag::UserCanShareContact
| UpdateFlag::ChatCanEdit
| UpdateFlag::ChannelCanEditPhoto
| UpdateFlag::ChannelCanAddMembers
| UpdateFlag::ChannelAmIn;
} // namespace
CoverWidget::CoverWidget(QWidget *parent, PeerData *peer) : TWidget(parent)
, _peer(peer)
, _peerUser(peer->asUser())
, _peerChat(peer->asChat())
, _peerChannel(peer->asChannel())
, _peerMegagroup(peer->isMegagroup() ? _peerChannel : nullptr)
, _userpicButton(this, peer)
, _name(this, st::profileNameLabel) {
setAttribute(Qt::WA_OpaquePaintEvent);
setAcceptDrops(true);
_name.setSelectable(true);
_name.setContextCopyText(lang(lng_profile_copy_fullname));
auto observeEvents = ButtonsUpdateFlags
| UpdateFlag::NameChanged
| UpdateFlag::UserOnlineChanged;
Notify::registerPeerObserver(observeEvents, this, &CoverWidget::notifyPeerUpdated);
FileDialog::registerObserver(this, &CoverWidget::notifyFileQueryUpdated);
connect(_userpicButton, SIGNAL(clicked()), this, SLOT(onPhotoShow()));
validatePhoto();
refreshNameText();
refreshStatusText();
refreshButtons();
}
PhotoData *CoverWidget::validatePhoto() const {
PhotoData *photo = (_peer->photoId && _peer->photoId != UnknownPeerPhotoId) ? App::photo(_peer->photoId) : nullptr;
if ((_peer->photoId == UnknownPeerPhotoId) || (_peer->photoId && (!photo || !photo->date))) {
App::api()->requestFullPeer(_peer);
return nullptr;
}
return photo;
}
void CoverWidget::onPhotoShow() {
if (auto photo = validatePhoto()) {
App::wnd()->showPhoto(photo, _peer);
}
}
int CoverWidget::countPhotoLeft(int newWidth) const {
int result = st::profilePhotoLeftMin;
result += (newWidth - st::wndMinWidth) / 2;
return qMin(result, st::profilePhotoLeftMax);
}
void CoverWidget::resizeToWidth(int newWidth) {
int newHeight = 0;
newHeight += st::profileMarginTop;
_photoLeft = countPhotoLeft(newWidth);
_userpicButton->moveToLeft(_photoLeft, newHeight);
refreshNameGeometry(newWidth);
int infoLeft = _userpicButton->x() + _userpicButton->width();
_statusPosition = QPoint(infoLeft + st::profileStatusLeft, _userpicButton->y() + st::profileStatusTop);
moveAndToggleButtons(newWidth);
newHeight += st::profilePhotoSize;
newHeight += st::profileMarginBottom;
_dividerTop = newHeight;
newHeight += st::profileDividerFill.height();
newHeight += st::profileBlocksTop;
resizeDropArea();
resize(newWidth, newHeight);
update();
}
void CoverWidget::refreshNameGeometry(int newWidth) {
int infoLeft = _userpicButton->x() + _userpicButton->width();
int nameLeft = infoLeft + st::profileNameLeft - st::profileNameLabel.margin.left();
int nameTop = _userpicButton->y() + st::profileNameTop - st::profileNameLabel.margin.top();
int nameWidth = newWidth - infoLeft - st::profileNameLeft - st::profileButtonSkip;
int marginsAdd = st::profileNameLabel.margin.left() + st::profileNameLabel.margin.right();
_name.resizeToWidth(qMin(nameWidth, _name.naturalWidth()) + marginsAdd);
_name.moveToLeft(nameLeft, nameTop);
}
// A more generic solution would be allowing an optional icon button
// for each text button. But currently we use only one, so it is done easily:
// There can be primary + secondary + icon buttons. If primary + secondary fit,
// then icon is hidden, otherwise secondary is hidden and icon is shown.
void CoverWidget::moveAndToggleButtons(int newWiddth) {
int buttonLeft = _userpicButton->x() + _userpicButton->width() + st::profileButtonLeft;
int buttonsRight = newWiddth - st::profileButtonSkip;
for (int i = 0, count = _buttons.size(); i < count; ++i) {
auto &button = _buttons.at(i);
button.widget->moveToLeft(buttonLeft, st::profileButtonTop);
if (button.replacement) {
button.replacement->moveToLeft(buttonLeft, st::profileButtonTop);
if (buttonLeft + button.widget->width() > buttonsRight) {
button.widget->hide();
button.replacement->show();
buttonLeft += button.replacement->width() + st::profileButtonSkip;
} else {
button.widget->show();
button.replacement->hide();
buttonLeft += button.widget->width() + st::profileButtonSkip;
}
} else if (i == 1 && (buttonLeft + button.widget->width() > buttonsRight)) {
// If second button is not fitting.
button.widget->hide();
} else {
button.widget->show();
buttonLeft += button.widget->width() + st::profileButtonSkip;
}
}
}
void CoverWidget::showFinished() {
_userpicButton->showFinished();
}
bool CoverWidget::shareContactButtonShown() const {
return _peerUser && (_buttons.size() > 1) && !(_buttons.at(1).widget->isHidden());
}
void CoverWidget::paintEvent(QPaintEvent *e) {
Painter p(this);
p.fillRect(e->rect(), st::profileBg);
p.setFont(st::profileStatusFont);
p.setPen(_statusTextIsOnline ? st::profileStatusFgActive : st::profileStatusFg);
p.drawTextLeft(_statusPosition.x(), _statusPosition.y(), width(), _statusText);
paintDivider(p);
}
void CoverWidget::resizeDropArea() {
if (_dropArea) {
_dropArea->setGeometry(0, 0, width(), _dividerTop);
}
}
void CoverWidget::dropAreaHidden(CoverDropArea *dropArea) {
if (_dropArea == dropArea) {
_dropArea.destroyDelayed();
}
}
bool CoverWidget::canEditPhoto() const {
if (_peerChat && _peerChat->canEdit()) {
return true;
} else if (_peerMegagroup && _peerMegagroup->canEditPhoto()) {
return true;
} else if (_peerChannel && _peerChannel->canEditPhoto()) {
return true;
}
return false;
}
bool CoverWidget::mimeDataHasImage(const QMimeData *mimeData) const {
if (!mimeData) return false;
if (mimeData->hasImage()) return true;
auto uriListFormat = qsl("text/uri-list");
if (!mimeData->hasFormat(uriListFormat)) return false;
const auto &urls = mimeData->urls();
if (urls.size() != 1) return false;
auto &url = urls.at(0);
if (!url.isLocalFile()) return false;
auto file = psConvertFileUrl(url);
QFileInfo info(file);
if (info.isDir()) return false;
quint64 s = info.size();
if (s >= MaxUploadDocumentSize) return false;
for (auto &ext : cImgExtensions()) {
if (file.endsWith(ext, Qt::CaseInsensitive)) {
return true;
}
}
return false;
}
void CoverWidget::dragEnterEvent(QDragEnterEvent *e) {
if (!canEditPhoto() || !mimeDataHasImage(e->mimeData())) {
e->ignore();
return;
}
if (!_dropArea) {
auto title = lang(lng_profile_drop_area_title);
QString subtitle;
if (_peerChat || _peerMegagroup) {
subtitle = lang(lng_profile_drop_area_subtitle);
} else {
subtitle = lang(lng_profile_drop_area_subtitle_channel);
}
_dropArea = new CoverDropArea(this, title, subtitle);
resizeDropArea();
}
_dropArea->showAnimated();
e->setDropAction(Qt::CopyAction);
e->accept();
}
void CoverWidget::dragLeaveEvent(QDragLeaveEvent *e) {
if (_dropArea && !_dropArea->hiding()) {
_dropArea->hideAnimated(func(this, &CoverWidget::dropAreaHidden));
}
}
void CoverWidget::dropEvent(QDropEvent *e) {
auto mimeData = e->mimeData();
QImage img;
if (mimeData->hasImage()) {
img = qvariant_cast<QImage>(mimeData->imageData());
} else {
const auto &urls = mimeData->urls();
if (urls.size() == 1) {
auto &url = urls.at(0);
if (url.isLocalFile()) {
img = App::readImage(psConvertFileUrl(url));
}
}
}
if (!_dropArea->hiding()) {
_dropArea->hideAnimated(func(this, &CoverWidget::dropAreaHidden));
}
e->acceptProposedAction();
showSetPhotoBox(img);
}
void CoverWidget::paintDivider(Painter &p) {
st::profileDividerLeft.paint(p, QPoint(st::lineWidth, _dividerTop), width());
int toFillLeft = st::lineWidth + st::profileDividerLeft.width();
QRect toFill = rtlrect(toFillLeft, _dividerTop, width() - toFillLeft, st::profileDividerFill.height(), width());
st::profileDividerFill.fill(p, toFill);
}
void CoverWidget::notifyPeerUpdated(const Notify::PeerUpdate &update) {
if (update.peer != _peer) {
return;
}
if ((update.flags & ButtonsUpdateFlags) != 0) {
refreshButtons();
}
if (update.flags & UpdateFlag::NameChanged) {
refreshNameText();
}
if (update.flags & UpdateFlag::UserOnlineChanged) {
refreshStatusText();
}
}
void CoverWidget::refreshNameText() {
_name.setText(App::peerName(_peer));
refreshNameGeometry(width());
}
void CoverWidget::refreshStatusText() {
int currentTime = unixtime();
if (_peerUser) {
_statusText = App::onlineText(_peerUser, currentTime, true);
_statusTextIsOnline = App::onlineColorUse(_peerUser, currentTime);
} else if (_peerChat && _peerChat->amIn()) {
int fullCount = qMax(_peerChat->count, _peerChat->participants.size());
if (_onlineCount > 0 && _onlineCount <= fullCount) {
_statusText = lng_chat_status_members_online(lt_count, fullCount, lt_count_online, _onlineCount);
} else {
_statusText = lng_chat_status_members(lt_count, _peerChat->count);
}
} else if (_peerChannel) {
int fullCount = _peerChannel->membersCount();
if (_onlineCount > 0 && _onlineCount <= fullCount) {
_statusText = lng_chat_status_members_online(lt_count, fullCount, lt_count_online, _onlineCount);
} else if (fullCount > 0 ) {
_statusText = lng_chat_status_members(lt_count, _peerChannel->membersCount());
} else {
_statusText = lang(_peerChannel->isMegagroup() ? lng_group_status : lng_channel_status);
}
} else {
_statusText = lang(lng_chat_status_unaccessible);
}
update();
}
void CoverWidget::refreshButtons() {
clearButtons();
if (_peerUser) {
setUserButtons();
} else if (_peerChat) {
setChatButtons();
} else if (_peerMegagroup) {
setMegagroupButtons();
} else if (_peerChannel) {
setChannelButtons();
}
resizeToWidth(width());
}
void CoverWidget::setUserButtons() {
addButton(lang(lng_profile_send_message), SLOT(onSendMessage()));
if (_peerUser->canShareThisContact()) {
addButton(lang(lng_profile_share_contact), SLOT(onShareContact()));
}
}
void CoverWidget::setChatButtons() {
if (_peerChat->canEdit()) {
addButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto()));
addButton(lang(lng_profile_add_participant), SLOT(onAddMember()), &st::profileAddMemberButton);
}
}
void CoverWidget::setMegagroupButtons() {
if (_peerMegagroup->amIn()) {
if (_peerMegagroup->canEditPhoto()) {
addButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto()));
}
} else {
addButton(lang(lng_profile_join_channel), SLOT(onJoin()));
}
if (_peerMegagroup->canAddMembers()) {
addButton(lang(lng_profile_add_participant), SLOT(onAddMember()), &st::profileAddMemberButton);
}
}
void CoverWidget::setChannelButtons() {
if (_peerChannel->amCreator()) {
addButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto()));
} else if (_peerChannel->amIn()) {
addButton(lang(lng_profile_view_channel), SLOT(onViewChannel()));
} else {
addButton(lang(lng_profile_join_channel), SLOT(onJoin()));
}
}
void CoverWidget::clearButtons() {
auto buttons = createAndSwap(_buttons);
for_const (auto button, buttons) {
delete button.widget;
delete button.replacement;
}
}
void CoverWidget::addButton(const QString &text, const char *slot, const style::BoxButton *replacementStyle) {
auto &buttonStyle = _buttons.isEmpty() ? st::profilePrimaryButton : st::profileSecondaryButton;
auto button = new Ui::RoundButton(this, text, buttonStyle);
connect(button, SIGNAL(clicked()), this, slot);
button->show();
auto replacement = replacementStyle ? new Ui::RoundButton(this, QString(), *replacementStyle) : nullptr;
if (replacement) {
connect(replacement, SIGNAL(clicked()), this, slot);
replacement->hide();
}
_buttons.push_back({ button, replacement });
}
void CoverWidget::onOnlineCountUpdated(int onlineCount) {
_onlineCount = onlineCount;
refreshStatusText();
}
void CoverWidget::onSendMessage() {
Ui::showPeerHistory(_peer, ShowAtUnreadMsgId);
}
void CoverWidget::onShareContact() {
App::main()->shareContactLayer(_peerUser);
}
void CoverWidget::onSetPhoto() {
QStringList imgExtensions(cImgExtensions());
QString filter(qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;All files (*.*)"));
_setPhotoFileQueryId = FileDialog::queryReadFile(lang(lng_choose_images), filter);
}
void CoverWidget::notifyFileQueryUpdated(const FileDialog::QueryUpdate &update) {
if (_setPhotoFileQueryId != update.queryId) {
return;
}
_setPhotoFileQueryId = 0;
if (update.filePaths.isEmpty() && update.remoteContent.isEmpty()) {
return;
}
QImage img;
if (!update.remoteContent.isEmpty()) {
img = App::readImage(update.remoteContent);
} else {
img = App::readImage(update.filePaths.front());
}
showSetPhotoBox(img);
}
void CoverWidget::showSetPhotoBox(const QImage &img) {
if (img.isNull() || img.width() > 10 * img.height() || img.height() > 10 * img.width()) {
Ui::showLayer(new InformBox(lang(lng_bad_photo)));
return;
}
auto box = new PhotoCropBox(img, _peer);
connect(box, SIGNAL(closed()), this, SLOT(onPhotoUpdateStart()));
Ui::showLayer(box);
}
void CoverWidget::onAddMember() {
if (_peerChat) {
if (_peerChat->count >= Global::ChatSizeMax() && _peerChat->amCreator()) {
Ui::showLayer(new ConvertToSupergroupBox(_peerChat));
} else {
Ui::showLayer(new ContactsBox(_peerChat, MembersFilterRecent));
}
} else if (_peerChannel && _peerChannel->mgInfo) {
MembersAlreadyIn already;
for_const (auto user, _peerChannel->mgInfo->lastParticipants) {
already.insert(user);
}
Ui::showLayer(new ContactsBox(_peerChannel, MembersFilterRecent, already));
}
}
void CoverWidget::onJoin() {
if (!_peerChannel) return;
App::api()->joinChannel(_peerChannel);
}
void CoverWidget::onViewChannel() {
Ui::showPeerHistory(_peer, ShowAtUnreadMsgId);
}
} // namespace Profile

View file

@ -0,0 +1,137 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "core/observer.h"
#include "ui/filedialog.h"
#include "ui/flatlabel.h"
namespace Ui {
class RoundButton;
} // namespace Ui
namespace Notify {
struct PeerUpdate;
} // namespace Notify
namespace Profile {
class BackButton;
class UserpicButton;
class CoverDropArea;
class CoverWidget final : public TWidget, public Notify::Observer {
Q_OBJECT
public:
CoverWidget(QWidget *parent, PeerData *peer);
// Count new height for width=newWidth and resize to it.
void resizeToWidth(int newWidth);
void showFinished();
// Profile fixed top bar should use this flag to decide
// if it shows "Share contact" button or not.
// It should show it only if it is hidden in the cover.
bool shareContactButtonShown() const;
public slots:
void onOnlineCountUpdated(int onlineCount);
private slots:
void onPhotoShow();
void onSendMessage();
void onShareContact();
void onSetPhoto();
void onAddMember();
void onJoin();
void onViewChannel();
protected:
void paintEvent(QPaintEvent *e) override;
void dragEnterEvent(QDragEnterEvent *e) override;
void dragLeaveEvent(QDragLeaveEvent *e) override;
void dropEvent(QDropEvent *e) override;
private:
// Observed notifications.
void notifyPeerUpdated(const Notify::PeerUpdate &update);
void notifyFileQueryUpdated(const FileDialog::QueryUpdate &update);
// Counts userpic button left offset for a new widget width.
int countPhotoLeft(int newWidth) const;
PhotoData *validatePhoto() const;
void refreshNameGeometry(int newWidth);
void moveAndToggleButtons(int newWiddth);
void refreshNameText();
void refreshStatusText();
void refreshButtons();
void setUserButtons();
void setChatButtons();
void setMegagroupButtons();
void setChannelButtons();
void clearButtons();
void addButton(const QString &text, const char *slot, const style::BoxButton *replacementStyle = nullptr);
void paintDivider(Painter &p);
bool canEditPhoto() const;
void showSetPhotoBox(const QImage &img);
void resizeDropArea();
void dropAreaHidden(CoverDropArea *dropArea);
bool mimeDataHasImage(const QMimeData *mimeData) const;
PeerData *_peer;
UserData *_peerUser;
ChatData *_peerChat;
ChannelData *_peerChannel;
ChannelData *_peerMegagroup;
ChildWidget<UserpicButton> _userpicButton;
ChildWidget<CoverDropArea> _dropArea = { nullptr };
FlatLabel _name;
QPoint _statusPosition;
QString _statusText;
bool _statusTextIsOnline = false;
struct Button {
Ui::RoundButton *widget;
Ui::RoundButton *replacement;
};
QList<Button> _buttons;
int _photoLeft = 0; // Caching countPhotoLeft() result.
int _dividerTop = 0;
int _onlineCount = 0;
FileDialog::QueryId _setPhotoFileQueryId = 0;
};
} // namespace Profile

View file

@ -0,0 +1,99 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "profile/profile_cover_drop_area.h"
#include "styles/style_profile.h"
namespace Profile {
CoverDropArea::CoverDropArea(QWidget *parent, const QString &title, const QString &subtitle) : TWidget(parent)
, _title(title)
, _subtitle(subtitle)
, _titleWidth(st::profileDropAreaTitleFont->width(_title))
, _subtitleWidth(st::profileDropAreaSubtitleFont->width(_subtitle)) {
}
void CoverDropArea::showAnimated() {
show();
_hiding = false;
setupAnimation();
}
void CoverDropArea::hideAnimated(HideFinishCallback &&callback) {
_hideFinishCallback = std_::move(callback);
_hiding = true;
setupAnimation();
}
void CoverDropArea::paintEvent(QPaintEvent *e) {
Painter p(this);
if (_a_appearance.animating(getms())) {
p.setOpacity(_a_appearance.current());
p.drawPixmap(0, 0, _cache);
return;
}
if (!_cache.isNull()) {
_cache = QPixmap();
if (_hiding) {
hide();
_hideFinishCallback.call(this);
return;
}
}
p.fillRect(e->rect(), st::profileDropAreaBg);
if (width() < st::profileDropAreaPadding.left() + st::profileDropAreaPadding.right()) return;
if (height() < st::profileDropAreaPadding.top() + st::profileDropAreaPadding.bottom()) return;
auto border = st::profileDropAreaBorderWidth;
auto &borderFg = st::profileDropAreaBorderFg;
auto inner = rect().marginsRemoved(st::profileDropAreaPadding);
p.fillRect(inner.x(), inner.y(), inner.width(), border, borderFg);
p.fillRect(inner.x(), inner.y() + inner.height() - border, inner.width(), border, borderFg);
p.fillRect(inner.x(), inner.y() + border, border, inner.height() - 2 * border, borderFg);
p.fillRect(inner.x() + inner.width() - border, inner.y() + border, border, inner.height() - 2 * border, borderFg);
int titleLeft = inner.x() + (inner.width() - _titleWidth) / 2;
int titleTop = inner.y() + st::profileDropAreaTitleTop + st::profileDropAreaTitleFont->ascent;
p.setFont(st::profileDropAreaTitleFont);
p.setPen(st::profileDropAreaFg);
p.drawText(titleLeft, titleTop, _title);
int subtitleLeft = inner.x() + (inner.width() - _subtitleWidth) / 2;
int subtitleTop = inner.y() + st::profileDropAreaSubtitleTop + st::profileDropAreaSubtitleFont->ascent;
p.setFont(st::profileDropAreaSubtitleFont);
p.setPen(st::profileDropAreaFg);
p.drawText(subtitleLeft, subtitleTop, _subtitle);
}
void CoverDropArea::setupAnimation() {
if (_cache.isNull()) {
_cache = myGrab(this);
}
auto from = _hiding ? 1. : 0., to = _hiding ? 0. : 1.;
START_ANIMATION(_a_appearance, func(this, &CoverDropArea::refreshCallback), from, to, st::profileDropAreaDuration, anim::linear);
}
} // namespace Profile

View file

@ -0,0 +1,57 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
namespace Profile {
class CoverDropArea : public TWidget {
public:
CoverDropArea(QWidget *parent, const QString &title, const QString &subtitle);
void showAnimated();
using HideFinishCallback = Function<void, CoverDropArea*>;
void hideAnimated(HideFinishCallback &&callback);
bool hiding() const {
return _hiding;
}
protected:
void paintEvent(QPaintEvent *e) override;
private:
void refreshCallback() {
update();
}
void setupAnimation();
QString _title, _subtitle;
int _titleWidth, _subtitleWidth;
QPixmap _cache;
FloatAnimation _a_appearance;
bool _hiding = false;
HideFinishCallback _hideFinishCallback;
};
} // namespace Profile

View file

@ -0,0 +1,284 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "profile/profile_fixed_bar.h"
#include "styles/style_profile.h"
#include "lang.h"
#include "mainwidget.h"
#include "boxes/addcontactbox.h"
#include "boxes/confirmbox.h"
#include "observer_peer.h"
namespace Profile {
class BackButton final : public Button {
public:
BackButton(QWidget *parent) : Button(parent) {
setCursor(style::cur_pointer);
}
void resizeToWidth(int newWidth) {
resize(newWidth, st::profileTopBarHeight);
}
protected:
void paintEvent(QPaintEvent *e) override {
Painter p(this);
p.fillRect(e->rect(), st::profileBg);
st::profileTopBarBackIcon.paint(p, st::profileTopBarBackIconPosition, width());
p.setFont(st::profileTopBarBackFont);
p.setPen(st::profileTopBarBackFg);
p.drawTextLeft(st::profileTopBarBackPosition.x(), st::profileTopBarBackPosition.y(), width(), lang(lng_menu_back));
}
void onStateChanged(int oldState, ButtonStateChangeSource source) override {
if ((_state & Button::StateDown) && !(oldState & Button::StateDown)) {
emit clicked();
}
}
private:
};
namespace {
using UpdateFlag = Notify::PeerUpdate::Flag;
const auto ButtonsUpdateFlags = UpdateFlag::UserCanShareContact
| UpdateFlag::UserIsContact
| UpdateFlag::ChatCanEdit
| UpdateFlag::ChannelAmEditor;
} // namespace
FixedBar::FixedBar(QWidget *parent, PeerData *peer) : TWidget(parent)
, _peer(peer)
, _peerUser(peer->asUser())
, _peerChat(peer->asChat())
, _peerChannel(peer->asChannel())
, _peerMegagroup(peer->isMegagroup() ? _peerChannel : nullptr)
, _backButton(this) {
_backButton->moveToLeft(0, 0);
connect(_backButton, SIGNAL(clicked()), this, SLOT(onBack()));
auto observeEvents = ButtonsUpdateFlags
| UpdateFlag::MigrationChanged;
Notify::registerPeerObserver(observeEvents, this, &FixedBar::notifyPeerUpdate);
refreshRightActions();
}
void FixedBar::notifyPeerUpdate(const Notify::PeerUpdate &update) {
if (update.peer != _peer) {
return;
}
if ((update.flags & ButtonsUpdateFlags) != 0) {
refreshRightActions();
}
if (update.flags & UpdateFlag::MigrationChanged) {
if (_peerChat && _peerChat->migrateTo()) {
auto channel = _peerChat->migrateTo();
onBack();
Ui::showPeerProfile(channel);
}
}
}
void FixedBar::refreshRightActions() {
_currentAction = 0;
if (_peerUser) {
setUserActions();
} else if (_peerChat) {
setChatActions();
} else if (_peerMegagroup) {
setMegagroupActions();
} else if (_peerChannel) {
setChannelActions();
}
while (_rightActions.size() > _currentAction) {
delete _rightActions.back().button;
_rightActions.pop_back();
}
resizeToWidth(width());
}
void FixedBar::setUserActions() {
if (_peerUser->canShareThisContact()) {
addRightAction(RightActionType::ShareContact, lang(lng_profile_top_bar_share_contact), SLOT(onShareContact()));
}
if (_peerUser->isContact()) {
addRightAction(RightActionType::EditContact, lang(lng_profile_edit_contact), SLOT(onEditContact()));
addRightAction(RightActionType::DeleteContact, lang(lng_profile_delete_contact), SLOT(onDeleteContact()));
} else if (_peerUser->canAddContact()) {
addRightAction(RightActionType::AddContact, lang(lng_profile_add_contact), SLOT(onAddContact()));
}
}
void FixedBar::setChatActions() {
if (_peerChat->canEdit()) {
addRightAction(RightActionType::EditGroup, lang(lng_profile_edit_contact), SLOT(onEditGroup()));
}
addRightAction(RightActionType::LeaveGroup, lang(lng_profile_delete_and_exit), SLOT(onLeaveGroup()));
}
void FixedBar::setMegagroupActions() {
if (_peerMegagroup->amCreator() || _peerMegagroup->amEditor()) {
addRightAction(RightActionType::EditChannel, lang(lng_profile_edit_contact), SLOT(onEditChannel()));
}
}
void FixedBar::setChannelActions() {
if (_peerChannel->amCreator()) {
addRightAction(RightActionType::EditChannel, lang(lng_profile_edit_contact), SLOT(onEditChannel()));
}
}
void FixedBar::addRightAction(RightActionType type, const QString &text, const char *slot) {
if (_rightActions.size() > _currentAction) {
if (_rightActions.at(_currentAction).type == type) {
++_currentAction;
return;
}
} else {
t_assert(_rightActions.size() == _currentAction);
_rightActions.push_back(RightAction());
}
_rightActions[_currentAction].type = type;
delete _rightActions[_currentAction].button;
_rightActions[_currentAction].button = new FlatButton(this, text, st::profileFixedBarButton);
connect(_rightActions[_currentAction].button, SIGNAL(clicked()), this, slot);
bool showButton = !_animatingMode && (type != RightActionType::ShareContact || !_hideShareContactButton);
_rightActions[_currentAction].button->setVisible(showButton);
++_currentAction;
}
void FixedBar::onBack() {
App::main()->showBackFromStack();
}
void FixedBar::onEditChannel() {
Ui::showLayer(new EditChannelBox(_peerMegagroup ? _peerMegagroup : _peerChannel));
}
void FixedBar::onEditGroup() {
Ui::showLayer(new EditNameTitleBox(_peerChat));
}
void FixedBar::onAddContact() {
auto firstName = _peerUser->firstName;
auto lastName = _peerUser->lastName;
auto phone = _peerUser->phone().isEmpty() ? App::phoneFromSharedContact(peerToUser(_peer->id)) : _peerUser->phone();
Ui::showLayer(new AddContactBox(firstName, lastName, phone));
}
void FixedBar::onEditContact() {
Ui::showLayer(new AddContactBox(_peerUser));
}
void FixedBar::onShareContact() {
App::main()->shareContactLayer(_peerUser);
}
void FixedBar::onDeleteContact() {
ConfirmBox *box = new ConfirmBox(lng_sure_delete_contact(lt_contact, App::peerName(_peerUser)), lang(lng_box_delete));
connect(box, SIGNAL(confirmed()), this, SLOT(onDeleteContactSure()));
Ui::showLayer(box);
}
void FixedBar::onDeleteContactSure() {
Ui::showChatsList();
Ui::hideLayer();
MTP::send(MTPcontacts_DeleteContact(_peerUser->inputUser), App::main()->rpcDone(&MainWidget::deletedContact, _peerUser));
}
void FixedBar::onLeaveGroup() {
ConfirmBox *box = new ConfirmBox(lng_sure_delete_and_exit(lt_group, App::peerName(_peerChat)), lang(lng_box_leave), st::attentionBoxButton);
connect(box, SIGNAL(confirmed()), this, SLOT(onLeaveGroupSure()));
Ui::showLayer(box);
}
void FixedBar::onLeaveGroupSure() {
Ui::showChatsList();
Ui::hideLayer();
App::main()->deleteAndExit(_peerChat);
}
void FixedBar::resizeToWidth(int newWidth) {
int newHeight = 0;
int buttonLeft = newWidth;
for (auto i = _rightActions.cend(), b = _rightActions.cbegin(); i != b;) {
--i;
buttonLeft -= i->button->width();
i->button->moveToLeft(buttonLeft, 0);
}
_backButton->resizeToWidth(newWidth);
_backButton->moveToLeft(0, 0);
newHeight += _backButton->height();
resize(newWidth, newHeight);
}
void FixedBar::setAnimatingMode(bool enabled) {
if (_animatingMode != enabled) {
_animatingMode = enabled;
setCursor(_animatingMode ? style::cur_pointer : style::cur_default);
if (_animatingMode) {
setAttribute(Qt::WA_OpaquePaintEvent, false);
hideChildren();
} else {
setAttribute(Qt::WA_OpaquePaintEvent);
showChildren();
if (_hideShareContactButton) {
applyHideShareContactButton();
}
}
show();
}
}
void FixedBar::setHideShareContactButton(bool hideButton) {
_hideShareContactButton = hideButton;
if (!_animatingMode) {
applyHideShareContactButton();
}
}
void FixedBar::applyHideShareContactButton() {
for_const (auto &action, _rightActions) {
if (action.type == RightActionType::ShareContact) {
action.button->setVisible(!_hideShareContactButton);
}
}
}
void FixedBar::mousePressEvent(QMouseEvent *e) {
if (e->button() == Qt::LeftButton) {
onBack();
} else {
TWidget::mousePressEvent(e);
}
}
} // namespace Profile

View file

@ -0,0 +1,108 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "core/observer.h"
namespace Notify {
struct PeerUpdate;
} // namespace Notify
namespace Profile {
class BackButton;
class FixedBar final : public TWidget, public Notify::Observer {
Q_OBJECT
public:
FixedBar(QWidget *parent, PeerData *peer);
void resizeToWidth(int newWidth);
// When animating mode is enabled the content is hidden and the
// whole fixed bar acts like a back button.
void setAnimatingMode(bool enabled);
// The "Share contact" button should be hidden if it is shown in the profile cover.
void setHideShareContactButton(bool hideButton);
protected:
void mousePressEvent(QMouseEvent *e) override;
public slots:
void onBack();
private slots:
void onEditChannel();
void onEditGroup();
void onAddContact();
void onEditContact();
void onShareContact();
void onDeleteContact();
void onDeleteContactSure();
void onLeaveGroup();
void onLeaveGroupSure();
private:
void notifyPeerUpdate(const Notify::PeerUpdate &update);
void refreshRightActions();
void setUserActions();
void setChatActions();
void setMegagroupActions();
void setChannelActions();
enum class RightActionType {
None,
EditChannel,
EditGroup,
LeaveGroup,
AddContact,
EditContact,
DeleteContact,
ShareContact,
};
void addRightAction(RightActionType type, const QString &text, const char *slot);
void applyHideShareContactButton();
PeerData *_peer;
UserData *_peerUser;
ChatData *_peerChat;
ChannelData *_peerChannel;
ChannelData *_peerMegagroup;
ChildWidget<BackButton> _backButton;
int _currentAction = 0;
struct RightAction {
RightActionType type = RightActionType::None;
FlatButton *button = nullptr;
};
QList<RightAction> _rightActions;
bool _animatingMode = false;
bool _hideShareContactButton = false;
};
} // namespace Profile

View file

@ -0,0 +1,219 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "profile/profile_info_widget.h"
#include "styles/style_profile.h"
#include "ui/flatlabel.h"
#include "core/click_handler_types.h"
#include "observer_peer.h"
#include "lang.h"
namespace Profile {
using UpdateFlag = Notify::PeerUpdate::Flag;
InfoWidget::InfoWidget(QWidget *parent, PeerData *peer) : BlockWidget(parent, peer, lang(lng_profile_info_section)) {
auto observeEvents = UpdateFlag::AboutChanged
| UpdateFlag::UsernameChanged
| UpdateFlag::UserPhoneChanged
| UpdateFlag::UserCanShareContact;
Notify::registerPeerObserver(observeEvents, this, &InfoWidget::notifyPeerUpdated);
refreshLabels();
}
void InfoWidget::notifyPeerUpdated(const Notify::PeerUpdate &update) {
if (update.peer != peer()) {
return;
}
if (update.flags & UpdateFlag::AboutChanged) {
refreshAbout();
}
if (update.flags & UpdateFlag::UsernameChanged) {
refreshUsername();
refreshChannelLink();
}
if (update.flags & (UpdateFlag::UserPhoneChanged | UpdateFlag::UserCanShareContact)) {
refreshMobileNumber();
}
refreshVisibility();
contentSizeUpdated();
}
int InfoWidget::resizeGetHeight(int newWidth) {
int newHeight = contentTop();
int marginLeft = st::profileBlockTextPart.margin.left();
int marginRight = st::profileBlockTextPart.margin.right();
int left = st::profileBlockTitlePosition.x();
if (_about) {
int textWidth = _about->naturalWidth();
int availableWidth = newWidth - left - st::profileBlockMarginRight;
int maxWidth = st::msgMaxWidth;
accumulate_min(textWidth, availableWidth);
accumulate_min(textWidth, st::msgMaxWidth);
_about->resizeToWidth(textWidth + marginLeft + marginRight);
_about->moveToLeft(left - marginLeft, newHeight - st::profileBlockTextPart.margin.top());
newHeight += _about->height();
}
auto moveLabeledText = [&newHeight, left, newWidth, marginLeft, marginRight](FlatLabel *label, FlatLabel *text, FlatLabel *shortText) {
if (!label) return;
label->moveToLeft(left, newHeight);
int textLeft = left + label->width() + st::normalFont->spacew;
int textWidth = text->naturalWidth();
int availableWidth = newWidth - textLeft - st::profileBlockMarginRight;
bool doesNotFit = (textWidth > availableWidth);
accumulate_min(textWidth, availableWidth);
accumulate_min(textWidth, st::msgMaxWidth);
text->resizeToWidth(textWidth + marginLeft + marginRight);
text->moveToLeft(textLeft - marginLeft, newHeight - st::profileBlockOneLineTextPart.margin.top());
if (shortText) {
shortText->resizeToWidth(textWidth + marginLeft + marginRight);
shortText->moveToLeft(textLeft - marginLeft, newHeight - st::profileBlockOneLineTextPart.margin.top());
if (doesNotFit) {
shortText->show();
text->hide();
} else {
shortText->hide();
text->show();
}
}
newHeight += label->height() + st::profileBlockOneLineSkip;
};
moveLabeledText(_channelLinkLabel, _channelLink, _channelLinkShort);
moveLabeledText(_mobileNumberLabel, _mobileNumber, nullptr);
moveLabeledText(_usernameLabel, _username, nullptr);
newHeight += st::profileBlockMarginBottom;
return newHeight;
}
void InfoWidget::leaveEvent(QEvent *e) {
BotCommandClickHandler::setPeerForCommand(nullptr);
BotCommandClickHandler::setBotForCommand(nullptr);
}
void InfoWidget::refreshLabels() {
refreshAbout();
refreshMobileNumber();
refreshUsername();
refreshChannelLink();
refreshVisibility();
}
void InfoWidget::refreshVisibility() {
setVisible(_about || _mobileNumber || _username || _channelLink);
}
void InfoWidget::refreshAbout() {
auto getAboutText = [this]() -> QString {
if (auto user = peer()->asUser()) {
return user->about();
} else if (auto channel = peer()->asChannel()) {
return channel->about();
}
return QString();
};
_about.destroy();
auto aboutText = getAboutText();
if (!aboutText.isEmpty()) {
_about = new FlatLabel(this, st::profileBlockTextPart);
_about->show();
EntitiesInText aboutEntities;
textParseEntities(aboutText, TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands, &aboutEntities);
_about->setMarkedText({ aboutText, aboutEntities });
_about->setSelectable(true);
_about->setClickHandlerHook(func(this, &InfoWidget::aboutClickHandlerHook));
}
}
bool InfoWidget::aboutClickHandlerHook(const ClickHandlerPtr &handler, Qt::MouseButton button) {
BotCommandClickHandler::setPeerForCommand(peer());
return true;
}
void InfoWidget::refreshMobileNumber() {
TextWithEntities phoneText;
if (auto user = peer()->asUser()) {
if (!user->phone().isEmpty()) {
phoneText.text = App::formatPhone(user->phone());
} else {
phoneText.text = App::phoneFromSharedContact(peerToUser(user->id));
}
}
setLabeledText(&_mobileNumberLabel, lang(lng_profile_mobile_number), &_mobileNumber, phoneText, lang(lng_profile_copy_phone));
}
void InfoWidget::refreshUsername() {
TextWithEntities usernameText;
if (auto user = peer()->asUser()) {
if (!user->username.isEmpty()) {
usernameText.text = '@' + user->username;
}
}
setLabeledText(&_usernameLabel, lang(lng_profile_username), &_username, usernameText, lang(lng_context_copy_mention));
}
void InfoWidget::refreshChannelLink() {
TextWithEntities channelLinkText;
TextWithEntities channelLinkTextShort;
if (auto channel = peer()->asChannel()) {
if (!channel->username.isEmpty()) {
channelLinkText.text = qsl("https://telegram.me/") + channel->username;
channelLinkText.entities.push_back(EntityInText(EntityInTextUrl, 0, channelLinkText.text.size()));
channelLinkTextShort.text = qsl("telegram.me/") + channel->username;
channelLinkTextShort.entities.push_back(EntityInText(EntityInTextCustomUrl, 0, channelLinkTextShort.text.size(), qsl("https://telegram.me/") + channel->username));
}
}
setLabeledText(nullptr, lang(lng_profile_link), &_channelLink, channelLinkText, QString());
setLabeledText(&_channelLinkLabel, lang(lng_profile_link), &_channelLinkShort, channelLinkTextShort, QString());
if (_channelLinkShort) {
_channelLinkShort->setExpandLinksMode(ExpandLinksUrlOnly);
}
}
void InfoWidget::setLabeledText(ChildWidget<FlatLabel> *labelWidget, const QString &label,
ChildWidget<FlatLabel> *textWidget, const TextWithEntities &textWithEntities, const QString &copyText) {
if (labelWidget) labelWidget->destroy();
textWidget->destroy();
if (textWithEntities.text.isEmpty()) return;
if (labelWidget) {
*labelWidget = new FlatLabel(this, label, FlatLabel::InitType::Simple, st::profileBlockLabel);
(*labelWidget)->show();
}
*textWidget = new FlatLabel(this, QString(), FlatLabel::InitType::Simple, st::profileBlockOneLineTextPart);
(*textWidget)->show();
(*textWidget)->setMarkedText(textWithEntities);
(*textWidget)->setContextCopyText(copyText);
(*textWidget)->setSelectable(true);
(*textWidget)->setDoubleClickSelectsParagraph(true);
}
} // namespace Profile

View file

@ -0,0 +1,71 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "profile/profile_block_widget.h"
class FlatLabel;
namespace Notify {
struct PeerUpdate;
} // namespace Notify
namespace Profile {
class InfoWidget : public BlockWidget {
public:
InfoWidget(QWidget *parent, PeerData *peer);
protected:
// Resizes content and counts natural widget height for the desired width.
int resizeGetHeight(int newWidth) override;
void leaveEvent(QEvent *e) override;
private:
// Observed notifications.
void notifyPeerUpdated(const Notify::PeerUpdate &update);
void refreshLabels();
void refreshAbout();
void refreshMobileNumber();
void refreshUsername();
void refreshChannelLink();
void refreshVisibility();
bool aboutClickHandlerHook(const ClickHandlerPtr &handler, Qt::MouseButton button);
// labelWidget may be nullptr.
void setLabeledText(ChildWidget<FlatLabel> *labelWidget, const QString &label,
ChildWidget<FlatLabel> *textWidget, const TextWithEntities &textWithEntities, const QString &copyText);
ChildWidget<FlatLabel> _about = { nullptr };
ChildWidget<FlatLabel> _channelLinkLabel = { nullptr };
ChildWidget<FlatLabel> _channelLink = { nullptr };
ChildWidget<FlatLabel> _channelLinkShort = { nullptr };
ChildWidget<FlatLabel> _mobileNumberLabel = { nullptr };
ChildWidget<FlatLabel> _mobileNumber = { nullptr };
ChildWidget<FlatLabel> _usernameLabel = { nullptr };
ChildWidget<FlatLabel> _username = { nullptr };
};
} // namespace Profile

View file

@ -0,0 +1,257 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "profile/profile_inner_widget.h"
#include "styles/style_profile.h"
#include "profile/profile_cover.h"
#include "profile/profile_info_widget.h"
#include "profile/profile_settings_widget.h"
#include "profile/profile_invite_link_widget.h"
#include "profile/profile_shared_media_widget.h"
#include "profile/profile_actions_widget.h"
#include "profile/profile_members_widget.h"
#include "apiwrap.h"
namespace Profile {
InnerWidget::InnerWidget(QWidget *parent, PeerData *peer) : TWidget(parent)
, _peer(peer)
, _cover(this, peer) {
setAttribute(Qt::WA_OpaquePaintEvent);
createBlocks();
}
void InnerWidget::createBlocks() {
auto user = _peer->asUser();
auto chat = _peer->asChat();
auto channel = _peer->asChannel();
auto megagroup = _peer->isMegagroup() ? channel : nullptr;
if (user || channel || megagroup) {
_blocks.push_back({ new InfoWidget(this, _peer), BlockSide::Right });
}
_blocks.push_back({ new SettingsWidget(this, _peer), BlockSide::Right });
if (chat || channel || megagroup) {
_blocks.push_back({ new InviteLinkWidget(this, _peer), BlockSide::Right });
}
_blocks.push_back({ new SharedMediaWidget(this, _peer), BlockSide::Right });
if (channel && !megagroup) {
_blocks.push_back({ new ChannelMembersWidget(this, _peer), BlockSide::Right });
}
_blocks.push_back({ new ActionsWidget(this, _peer), BlockSide::Right });
if (chat || megagroup) {
auto membersWidget = new MembersWidget(this, _peer);
connect(membersWidget, SIGNAL(onlineCountUpdated(int)), _cover, SLOT(onOnlineCountUpdated(int)));
_cover->onOnlineCountUpdated(membersWidget->onlineCount());
_blocks.push_back({ membersWidget, BlockSide::Left });
}
for_const (auto &blockData, _blocks) {
connect(blockData.block, SIGNAL(heightUpdated()), this, SLOT(onBlockHeightUpdated()));
}
}
void InnerWidget::resizeToWidth(int newWidth, int minHeight) {
int naturalHeight = resizeGetHeight(newWidth);
_addedHeight = qMax(minHeight - naturalHeight, 0);
resize(newWidth, naturalHeight + _addedHeight);
}
void InnerWidget::setVisibleTopBottom(int visibleTop, int visibleBottom) {
_visibleTop = visibleTop;
_visibleBottom = visibleBottom;
int notDisplayedAtBottom = height() - _visibleBottom;
if (notDisplayedAtBottom > 0) {
decreaseAdditionalHeight(notDisplayedAtBottom);
}
for_const (auto blockData, _blocks) {
int blockY = blockData.block->y();
blockData.block->setVisibleTopBottom(visibleTop - blockY, visibleBottom - blockY);
}
}
bool InnerWidget::shareContactButtonShown() const {
return _cover->shareContactButtonShown();
}
void InnerWidget::showFinished() {
_cover->showFinished();
}
void InnerWidget::decreaseAdditionalHeight(int removeHeight) {
resizeToWidth(width(), height() - removeHeight);
}
void InnerWidget::paintEvent(QPaintEvent *e) {
Painter p(this);
p.fillRect(e->rect(), st::profileBg);
if (_mode == Mode::TwoColumn) {
int leftHeight = countBlocksHeight(BlockSide::Left);
int rightHeight = countBlocksHeight(BlockSide::Right);
int shadowHeight = rightHeight;// qMin(leftHeight, rightHeight);
int shadowLeft = _blocksLeft + _leftColumnWidth + _columnDivider;
int shadowTop = _blocksTop + st::profileBlockMarginTop;
p.fillRect(rtlrect(shadowLeft, shadowTop, st::lineWidth, shadowHeight - st::profileBlockMarginTop, width()), st::shadowColor);
}
}
void InnerWidget::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Escape) {
emit cancelled();
}
}
int InnerWidget::countBlocksHeight(BlockSide countSide) const {
int result = 0;
for_const (auto &blockData, _blocks) {
if (blockData.side != countSide || blockData.block->isHidden()) {
continue;
}
result += blockData.block->height();
}
return result;
}
int InnerWidget::countBlocksLeft(int newWidth) const {
int result = st::profileBlockLeftMin;
result += (newWidth - st::wndMinWidth) / 2;
return qMin(result, st::profileBlockLeftMax);
}
InnerWidget::Mode InnerWidget::countBlocksMode(int newWidth) const {
bool hasLeftWidget = false, hasRightWidget = false;
for_const (auto &blockData, _blocks) {
if (!blockData.block->isHidden()) {
if (blockData.side == BlockSide::Left) {
hasLeftWidget = true;
} else {
hasRightWidget = true;
}
}
}
if (!hasLeftWidget || !hasRightWidget) {
return Mode::OneColumn;
}
int availWidth = newWidth - _blocksLeft;
if (availWidth >= st::profileBlockWideWidthMin + _columnDivider + st::profileBlockNarrowWidthMin) {
return Mode::TwoColumn;
}
return Mode::OneColumn;
}
int InnerWidget::countLeftColumnWidth(int newWidth) const {
int result = st::profileBlockWideWidthMin;
int availWidth = newWidth - _blocksLeft;
int additionalWidth = (availWidth - st::profileBlockWideWidthMin - _columnDivider - st::profileBlockNarrowWidthMin);
if (additionalWidth > 0) {
result += (additionalWidth / 2);
accumulate_min(result, st::profileBlockWideWidthMax);
}
return result;
}
void InnerWidget::refreshBlocksPositions() {
auto layoutBlocks = [this](BlockSide layoutSide, int left) {
int top = _blocksTop;
for_const (auto &blockData, _blocks) {
if (_mode == Mode::TwoColumn && blockData.side != layoutSide) {
continue;
}
if (blockData.block->isHidden()) {
continue;
}
blockData.block->moveToLeft(left, top);
top += blockData.block->height();
}
};
layoutBlocks(BlockSide::Left, _blocksLeft);
if (_mode == Mode::TwoColumn) {
layoutBlocks(BlockSide::Right, _blocksLeft + _leftColumnWidth + _columnDivider);
}
}
void InnerWidget::resizeBlocks(int newWidth) {
for_const (auto &blockData, _blocks) {
int blockWidth = newWidth - _blocksLeft;
if (_mode == Mode::OneColumn) {
blockWidth -= _blocksLeft;
} else {
if (blockData.side == BlockSide::Left) {
blockWidth = _leftColumnWidth;
} else {
blockWidth -= _leftColumnWidth + _columnDivider;
}
}
blockData.block->resizeToWidth(blockWidth);
}
}
int InnerWidget::resizeGetHeight(int newWidth) {
_cover->resizeToWidth(newWidth);
_blocksTop = _cover->y() + _cover->height() + st::profileBlocksTop;
_blocksLeft = countBlocksLeft(newWidth);
_columnDivider = st::profileMemberPaddingLeft;
_mode = countBlocksMode(newWidth);
_leftColumnWidth = countLeftColumnWidth(newWidth);
resizeBlocks(newWidth);
refreshBlocksPositions();
update();
return countHeight();
}
int InnerWidget::countHeight() const {
int newHeight = _cover->height();
int leftHeight = countBlocksHeight(BlockSide::Left);
int rightHeight = countBlocksHeight(BlockSide::Right);
int blocksHeight = (_mode == Mode::OneColumn) ? (leftHeight + rightHeight) : qMax(leftHeight, rightHeight);
newHeight += st::profileBlocksTop + blocksHeight + st::profileBlocksBottom;
return newHeight;
}
void InnerWidget::onBlockHeightUpdated() {
refreshBlocksPositions();
int naturalHeight = countHeight();
int notDisplayedAtBottom = naturalHeight - _visibleBottom;
if (notDisplayedAtBottom < 0) {
_addedHeight = -notDisplayedAtBottom;
} else {
_addedHeight = 0;
}
if (naturalHeight + _addedHeight != height()) {
resize(width(), naturalHeight + _addedHeight);
}
}
} // namespace Profile

View file

@ -0,0 +1,113 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
namespace Profile {
class CoverWidget;
class BlockWidget;
class InnerWidget final : public TWidget {
Q_OBJECT
public:
InnerWidget(QWidget *parent, PeerData *peer);
PeerData *peer() const {
return _peer;
}
// Count new height for width=newWidth and resize to it.
void resizeToWidth(int newWidth, int minHeight);
// Updates the area that is visible inside the scroll container.
void setVisibleTopBottom(int visibleTop, int visibleBottom);
// Profile fixed top bar should use this flag to decide
// if it shows "Share contact" button or not.
// It should show it only if it is hidden in the cover.
bool shareContactButtonShown() const;
void showFinished();
signals:
void cancelled();
private slots:
void onBlockHeightUpdated();
protected:
void paintEvent(QPaintEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
private:
void createBlocks();
// Resizes content and counts natural widget height for the desired width.
int resizeGetHeight(int newWidth);
// Counts the natural widget height after resizing of child widgets.
int countHeight() const;
enum class Mode {
OneColumn,
TwoColumn,
};
enum class BlockSide {
Left,
Right,
};
int countBlocksLeft(int newWidth) const;
Mode countBlocksMode(int newWidth) const;
int countLeftColumnWidth(int newWidth) const;
int countBlocksHeight(BlockSide countSide) const;
void resizeBlocks(int newWidth);
void refreshBlocksPositions();
// Sometimes height of this widget is larger than it is required
// so that it is allowed to scroll down to the desired position.
// When resizing with scroll moving up the additional height may be decreased.
void decreaseAdditionalHeight(int removeHeight);
PeerData *_peer;
// Height that we added to the natural height so that it is allowed
// to scroll down to the desired position.
int _addedHeight = 0;
int _visibleTop = 0;
int _visibleBottom = 0;
ChildWidget<CoverWidget> _cover;
int _blocksLeft = 0; // Caching countBlocksLeft() result.
int _blocksTop = 0;
int _columnDivider = 0;
int _leftColumnWidth = 0; // Caching countLeftColumnWidth() result.
struct Block {
BlockWidget *block;
BlockSide side;
};
QList<Block> _blocks;
Mode _mode = Mode::OneColumn;
};
} // namespace Profile

View file

@ -0,0 +1,113 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "profile/profile_invite_link_widget.h"
#include "styles/style_profile.h"
#include "ui/flatlabel.h"
#include "boxes/confirmbox.h"
#include "observer_peer.h"
#include "lang.h"
namespace Profile {
InviteLinkWidget::InviteLinkWidget(QWidget *parent, PeerData *peer) : BlockWidget(parent, peer, lang(lng_profile_invite_link_section)) {
auto observeEvents = Notify::PeerUpdate::Flag::InviteLinkChanged;
Notify::registerPeerObserver(observeEvents, this, &InviteLinkWidget::notifyPeerUpdated);
refreshLink();
refreshVisibility();
}
void InviteLinkWidget::notifyPeerUpdated(const Notify::PeerUpdate &update) {
if (update.peer != peer()) {
return;
}
if (update.flags & Notify::PeerUpdate::Flag::InviteLinkChanged) {
refreshLink();
refreshVisibility();
contentSizeUpdated();
}
}
int InviteLinkWidget::resizeGetHeight(int newWidth) {
int newHeight = contentTop();
int marginLeft = st::profileBlockTextPart.margin.left();
int marginRight = st::profileBlockTextPart.margin.right();
int left = st::profileBlockTitlePosition.x();
if (_link) {
int textWidth = _link->naturalWidth();
int availableWidth = newWidth - left - st::profileBlockMarginRight;
int maxWidth = st::msgMaxWidth;
accumulate_min(textWidth, availableWidth);
accumulate_min(textWidth, st::msgMaxWidth);
_link->resizeToWidth(textWidth + marginLeft + marginRight);
_link->moveToLeft(left - marginLeft, newHeight - st::profileBlockTextPart.margin.top());
newHeight += _link->height();
}
newHeight += st::profileBlockMarginBottom;
return newHeight;
}
void InviteLinkWidget::refreshVisibility() {
setVisible(_link != nullptr);
}
QString InviteLinkWidget::getInviteLink() const {
if (auto chat = peer()->asChat()) {
return chat->inviteLink();
} else if (auto channel = peer()->asChannel()) {
return channel->inviteLink();
}
return QString();
};
void InviteLinkWidget::refreshLink() {
_link.destroy();
TextWithEntities linkData = { getInviteLink(), EntitiesInText() };
if (!linkData.text.isEmpty()) {
_link = new FlatLabel(this, QString(), FlatLabel::InitType::Simple, st::profileInviteLinkText);
_link->show();
linkData.entities.push_back(EntityInText(EntityInTextUrl, 0, linkData.text.size()));
_link->setMarkedText(linkData);
_link->setSelectable(true);
_link->setContextCopyText(QString());
_link->setClickHandlerHook(func(this, &InviteLinkWidget::clickHandlerHook));
}
}
bool InviteLinkWidget::clickHandlerHook(const ClickHandlerPtr &handler, Qt::MouseButton button) {
auto link = getInviteLink();
if (link.isEmpty()) {
return true;
}
QApplication::clipboard()->setText(link);
Ui::showLayer(new InformBox(lang(lng_group_invite_copied)));
return false;
}
} // namespace Profile

Some files were not shown because too many files have changed in this diff Show more