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
|
@ -20,13 +20,13 @@ downloadLibs() {
|
|||
info_msg "QT-Version: ${_qtver}, SRC-Dir: ${srcdir}"
|
||||
|
||||
echo -e "Clone Qt\n"
|
||||
git clone git://code.qt.io/qt/qt5.git qt5_6_0
|
||||
cd qt5_6_0
|
||||
git checkout 5.6
|
||||
git clone git://code.qt.io/qt/qt5.git qt${_qtver}
|
||||
cd qt${_qtver}
|
||||
git checkout $(echo ${_qtver} | sed -e "s/\..$//")
|
||||
perl init-repository --module-subset=qtbase,qtimageformats
|
||||
git checkout v5.6.0
|
||||
cd qtbase && git checkout v5.6.0 && cd ..
|
||||
cd qtimageformats && git checkout v5.6.0 && cd ..
|
||||
git checkout v${_qtver}
|
||||
cd qtbase && git checkout v${_qtver} && cd ..
|
||||
cd qtimageformats && git checkout v${_qtver} && cd ..
|
||||
cd ..
|
||||
|
||||
echo -e "Clone Breakpad\n"
|
||||
|
@ -48,9 +48,9 @@ prepare() {
|
|||
|
||||
mkdir -p "$srcdir/Libraries"
|
||||
|
||||
ln -s "$srcdir/qt5_6_0" "$srcdir/Libraries/qt5_6_0"
|
||||
cd "$srcdir/Libraries/qt5_6_0/qtbase"
|
||||
git apply "$srcdir/tdesktop/Telegram/Patches/qtbase_5_6_0.diff"
|
||||
ln -s "$srcdir/qt${_qtver}" "$srcdir/Libraries/qt${_qtver}"
|
||||
cd "$srcdir/Libraries/qt${_qtver}/qtbase"
|
||||
git apply "$srcdir/tdesktop/Telegram/Patches/qtbase_$(echo ${_qtver} | sed -e "s/\./_/g").diff"
|
||||
|
||||
if [ ! -h "$srcdir/Libraries/breakpad" ]; then
|
||||
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,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,/usr/local/tdesktop/Qt-5.6.0,$srcdir/qt,g" "$srcdir/tdesktop/Telegram/Telegram.pro"
|
||||
|
||||
local options=""
|
||||
|
||||
|
@ -98,7 +97,7 @@ build() {
|
|||
|
||||
info_msg "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 \
|
||||
-qt-libpng -qt-libjpeg -qt-freetype -qt-harfbuzz -qt-pcre -qt-xcb \
|
||||
-qt-xkbcommon-x11 -no-opengl -static -nomake examples -nomake tests
|
||||
|
@ -117,21 +116,21 @@ build() {
|
|||
# Build codegen_style
|
||||
mkdir -p "$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
|
||||
|
||||
info_msg "Build codegen_numbers"
|
||||
# Build codegen_numbers
|
||||
mkdir -p "$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
|
||||
|
||||
info_msg "Build MetaLang"
|
||||
# Build MetaLang
|
||||
mkdir -p "$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
|
||||
|
||||
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_numbers "-o./GeneratedFiles" "./../../Telegram/Resources/numbers.txt"
|
||||
./../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
|
||||
}
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
# Set variables
|
||||
_qtver=5.5.1
|
||||
_qtver=5.6.0
|
||||
srcdir=${PWD}
|
||||
|
||||
start_msg() {
|
||||
|
|
|
@ -11,7 +11,7 @@ The source code is published under GPLv3 with OpenSSL exception, the license is
|
|||
* Windows XP - Windows 10 (**not** RT)
|
||||
* Mac OS X 10.8 - Mac OS X 10.11
|
||||
* Mac OS X 10.6 - Mac OS X 10.7 (separate build)
|
||||
* Ubuntu 12.04 - Ubuntu 15.04
|
||||
* Ubuntu 12.04 - Ubuntu 16.04
|
||||
* Fedora 22
|
||||
|
||||
## Third-party libraries
|
||||
|
|
|
@ -27,7 +27,4 @@ HEADERS += \
|
|||
./SourceFiles/_other/memain.h \
|
||||
./SourceFiles/_other/genemoji.h \
|
||||
|
||||
INCLUDEPATH += ./../../Libraries/QtStatic/qtbase/include/QtGui/5.5.1/QtGui\
|
||||
./../../Libraries/QtStatic/qtbase/include/QtCore/5.5.1/QtCore\
|
||||
./../../Libraries/QtStatic/qtbase/include\
|
||||
|
||||
include(qt_static.pri)
|
||||
|
|
|
@ -27,7 +27,4 @@ HEADERS += \
|
|||
./SourceFiles/_other/mlmain.h \
|
||||
./SourceFiles/_other/genlang.h \
|
||||
|
||||
INCLUDEPATH += ./../../Libraries/QtStatic/qtbase/include/QtGui/5.5.1/QtGui\
|
||||
./../../Libraries/QtStatic/qtbase/include/QtCore/5.5.1/QtCore\
|
||||
./../../Libraries/QtStatic/qtbase/include\
|
||||
|
||||
include(qt_static.pri)
|
||||
|
|
|
@ -35,8 +35,6 @@ unix {
|
|||
}
|
||||
}
|
||||
|
||||
INCLUDEPATH += ./../../Libraries/QtStatic/qtbase/include/QtGui/5.5.1/QtGui\
|
||||
./../../Libraries/QtStatic/qtbase/include/QtCore/5.5.1/QtCore\
|
||||
./../../Libraries/QtStatic/qtbase/include
|
||||
include(qt_static.pri)
|
||||
|
||||
LIBS += -lcrypto -lssl -lz -llzma
|
||||
|
|
|
@ -24,3 +24,4 @@ using "basic_types.style";
|
|||
using "basic.style";
|
||||
|
||||
using "overview/overview.style";
|
||||
using "profile/profile.style";
|
||||
|
|
|
@ -55,10 +55,15 @@ wndMinWidth: 380px;
|
|||
adaptiveNormalWidth: 640px;
|
||||
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;
|
||||
wndDefWidth: 800px;
|
||||
wndDefHeight: 600px;
|
||||
wndBG: white;
|
||||
wndShadow: sprite(209px, 46px, 19px, 19px);
|
||||
wndShadowShift: 1px;
|
||||
|
||||
|
@ -69,9 +74,10 @@ overBg: #edf2f5;
|
|||
|
||||
labelDefFlat: flatLabel {
|
||||
font: font(fsize);
|
||||
minWidth: 100px;
|
||||
width: 0px;
|
||||
maxHeight: 0px;
|
||||
align: align(left);
|
||||
textFg: windowTextFg;
|
||||
}
|
||||
|
||||
boxBg: white;
|
||||
|
@ -140,6 +146,29 @@ boxLabel: flatLabel(labelDefFlat) {
|
|||
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 {
|
||||
textFg: black;
|
||||
textMargins: margins(5px, 6px, 5px, 4px);
|
||||
|
@ -370,9 +399,10 @@ statusFont: font(fsize);
|
|||
versionColor: #777;
|
||||
|
||||
shadowColor: rgba(0, 0, 0, 24);
|
||||
shadowToggleDuration: 200;
|
||||
|
||||
slideDuration: 240;
|
||||
slideShift: 0.3;
|
||||
slideShift: 100px;
|
||||
slideFadeOut: 0.3;
|
||||
slideShadow: sprite(348px, 71px, 48px, 1px);
|
||||
slideFunction: transition(easeOutCirc);
|
||||
|
@ -1657,18 +1687,18 @@ confirmCompressedSkip: 10px;
|
|||
|
||||
profileMaxWidth: 410px;
|
||||
profilePadding: margins(28px, 30px, 28px, 0px);
|
||||
profilePhotoSize: 120px;
|
||||
profileNameLeft: 21px;
|
||||
profileNameTop: -1px;
|
||||
profileNameFont: font(20px);
|
||||
profileStatusLeft: 22px;
|
||||
profileStatusTop: 31px;
|
||||
profileStatusFont: font(fsize);
|
||||
//profilePhotoSize: 120px;
|
||||
//profileNameLeft: 21px;
|
||||
//profileNameTop: -1px;
|
||||
//profileNameFont: font(20px);
|
||||
//profileStatusLeft: 22px;
|
||||
//profileStatusTop: 31px;
|
||||
//profileStatusFont: font(fsize);
|
||||
profilePhoneLeft: 22px;
|
||||
profilePhoneTop: 62px;
|
||||
profilePhoneFont: font(16px);
|
||||
profileButtonTop: 18px;
|
||||
profileButtonSkip: 10px;
|
||||
//profileButtonTop: 18px;
|
||||
//profileButtonSkip: 10px;
|
||||
profileHeaderFont: font(20px);
|
||||
profileHeaderColor: black;
|
||||
profileHeaderSkip: 59px;
|
||||
|
@ -2444,7 +2474,7 @@ inlineResultsSkip: 3px;
|
|||
inlineMediaHeight: 96px;
|
||||
inlineThumbSize: 64px;
|
||||
inlineThumbSkip: 10px;
|
||||
inlineDescriptionFg: #8a8a8a;
|
||||
inlineDescriptionFg: windowSubTextFg;
|
||||
inlineRowMargin: 6px;
|
||||
inlineRowBorder: 1px;
|
||||
inlineRowBorderFg: #eaeaea;
|
||||
|
|
|
@ -210,9 +210,11 @@ slider {
|
|||
|
||||
flatLabel {
|
||||
font: font;
|
||||
minWidth: pixels;
|
||||
margin: margins;
|
||||
width: pixels;
|
||||
align: align;
|
||||
textFg: color;
|
||||
maxHeight: pixels;
|
||||
}
|
||||
|
||||
switcher {
|
||||
|
@ -306,6 +308,8 @@ BoxButton {
|
|||
|
||||
textTop: pixels;
|
||||
|
||||
icon: icon;
|
||||
|
||||
font: font;
|
||||
duration: int;
|
||||
}
|
||||
|
@ -411,3 +415,18 @@ PeerAvatarButton {
|
|||
size: pixels;
|
||||
photoSize: pixels;
|
||||
}
|
||||
|
||||
OutlineButton {
|
||||
outlineWidth: pixels;
|
||||
outlineFg: color;
|
||||
outlineFgOver: color;
|
||||
|
||||
textBg: color;
|
||||
textBgOver: color;
|
||||
|
||||
textFg: color;
|
||||
textFgOver: color;
|
||||
|
||||
font: font;
|
||||
padding: margins;
|
||||
}
|
||||
|
|
BIN
Telegram/Resources/icons/profile_add_member.png
Normal file
After Width: | Height: | Size: 319 B |
BIN
Telegram/Resources/icons/profile_add_member@2x.png
Normal file
After Width: | Height: | Size: 592 B |
BIN
Telegram/Resources/icons/profile_admin_star.png
Normal file
After Width: | Height: | Size: 313 B |
BIN
Telegram/Resources/icons/profile_admin_star@2x.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/profile_divider_fill.png
Normal file
After Width: | Height: | Size: 96 B |
BIN
Telegram/Resources/icons/profile_divider_fill@2x.png
Normal file
After Width: | Height: | Size: 109 B |
BIN
Telegram/Resources/icons/profile_divider_left.png
Normal file
After Width: | Height: | Size: 114 B |
BIN
Telegram/Resources/icons/profile_divider_left@2x.png
Normal file
After Width: | Height: | Size: 155 B |
BIN
Telegram/Resources/icons/topbar_back_arrow.png
Normal file
After Width: | Height: | Size: 137 B |
BIN
Telegram/Resources/icons/topbar_back_arrow@2x.png
Normal file
After Width: | Height: | Size: 252 B |
|
@ -117,8 +117,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_channel_status" = "channel";
|
||||
"lng_group_status" = "group";
|
||||
|
||||
"lng_channel_members_link" = "{count:_not_used_|# member|# members} »";
|
||||
"lng_channel_admins_link" = "{count:Manage administrators|# administrator|# administrators} »";
|
||||
"lng_channel_members_link" = "{count:_not_used_|# member|# members}";
|
||||
"lng_channel_admins_link" = "{count:_not_used_|# administrator|# administrators}";
|
||||
|
||||
"lng_server_error" = "Internal server error.";
|
||||
"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_bot_settings" = "Settings";
|
||||
"lng_profile_bot_help" = "Help";
|
||||
"lng_profile_invite_link_section" = "Invite link";
|
||||
"lng_profile_create_public_link" = "Create public link";
|
||||
"lng_profile_edit_public_link" = "Edit public link";
|
||||
"lng_profile_manage_admins" = "Manage administrators";
|
||||
"lng_profile_participants_section" = "Members";
|
||||
"lng_profile_info" = "Contact info";
|
||||
"lng_profile_group_info" = "Group info";
|
||||
"lng_profile_channel_info" = "Channel info";
|
||||
"lng_profile_info_section" = "Info";
|
||||
"lng_profile_mobile_number" = "Mobile:";
|
||||
"lng_profile_username" = "Username:";
|
||||
"lng_profile_link" = "Link:";
|
||||
"lng_profile_add_contact" = "Add Contact";
|
||||
"lng_profile_edit_contact" = "Edit";
|
||||
"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_leave_group" = "Leave group";
|
||||
"lng_profile_delete_group" = "Delete group";
|
||||
"lng_profile_report" = "Report";
|
||||
"lng_profile_search_messages" = "Search for messages";
|
||||
"lng_profile_block_user" = "Block 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_set_group_photo" = "Set Photo";
|
||||
"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_kick" = "Remove";
|
||||
"lng_profile_admin" = "admin";
|
||||
|
@ -441,20 +447,33 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_profile_loading" = "Loading...";
|
||||
"lng_profile_shared_media" = "Shared media";
|
||||
"lng_profile_no_media" = "No media in this conversation.";
|
||||
"lng_profile_photos" = "{count:_not_used_|# photo|# photos} »";
|
||||
"lng_profile_photos" = "{count:_not_used_|# photo|# photos}";
|
||||
"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_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_files" = "{count:_not_used_|# file|# files} »";
|
||||
"lng_profile_files" = "{count:_not_used_|# file|# files}";
|
||||
"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_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_copy_phone" = "Copy phone number";
|
||||
"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_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_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_feature1" = "— The members limit is {count:_not_used_|# user|# users}";
|
||||
"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_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_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_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
|
||||
|
||||
"lng_topbar_info" = "Info";
|
||||
"lng_profile_group_info" = "Group info";
|
||||
"lng_profile_channel_info" = "Channel info";
|
||||
|
||||
// Wnd specific
|
||||
|
||||
|
|
|
@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 0,9,49,0
|
||||
PRODUCTVERSION 0,9,49,0
|
||||
FILEVERSION 0,9,49,1
|
||||
PRODUCTVERSION 0,9,49,1
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
@ -51,10 +51,10 @@ BEGIN
|
|||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram Messenger LLP"
|
||||
VALUE "FileVersion", "0.9.49.0"
|
||||
VALUE "FileVersion", "0.9.49.1"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2016"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "0.9.49.0"
|
||||
VALUE "ProductVersion", "0.9.49.1"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
|
@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 0,9,49,0
|
||||
PRODUCTVERSION 0,9,49,0
|
||||
FILEVERSION 0,9,49,1
|
||||
PRODUCTVERSION 0,9,49,1
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
@ -43,10 +43,10 @@ BEGIN
|
|||
BEGIN
|
||||
VALUE "CompanyName", "Telegram Messenger LLP"
|
||||
VALUE "FileDescription", "Telegram Updater"
|
||||
VALUE "FileVersion", "0.9.49.0"
|
||||
VALUE "FileVersion", "0.9.49.1"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2016"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "0.9.49.0"
|
||||
VALUE "ProductVersion", "0.9.49.1"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
|
@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "stdafx.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
#include "observer_peer.h"
|
||||
#include "lang.h"
|
||||
#include "application.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) {
|
||||
switch (msgs.type()) {
|
||||
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);
|
||||
}
|
||||
|
||||
App::feedUsers(d.vusers, false);
|
||||
App::feedChats(d.vchats, false);
|
||||
App::feedUsers(d.vusers);
|
||||
App::feedChats(d.vchats);
|
||||
|
||||
if (peer->isChat()) {
|
||||
if (d.vfull_chat.type() != mtpc_chatFull) {
|
||||
|
@ -206,9 +211,9 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt
|
|||
} else {
|
||||
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()) {
|
||||
if (d.vfull_chat.type() != mtpc_channelFull) {
|
||||
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());
|
||||
PhotoData *photo = App::feedPhoto(f.vchat_photo);
|
||||
ChannelData *channel = peer->asChannel();
|
||||
|
||||
auto canViewAdmins = channel->canViewAdmins();
|
||||
auto canViewMembers = channel->canViewMembers();
|
||||
|
||||
channel->flagsFull = f.vflags.v;
|
||||
if (photo) {
|
||||
channel->photoId = photo->id;
|
||||
|
@ -267,17 +276,10 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt
|
|||
} break;
|
||||
}
|
||||
}
|
||||
channel->about = qs(f.vabout);
|
||||
int32 newCount = f.has_participants_count() ? f.vparticipants_count.v : 0;
|
||||
if (newCount != channel->count) {
|
||||
if (channel->isMegagroup() && !channel->mgInfo->lastParticipants.isEmpty()) {
|
||||
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();
|
||||
channel->setAbout(qs(f.vabout));
|
||||
channel->setMembersCount(f.has_participants_count() ? f.vparticipants_count.v : 0);
|
||||
channel->setAdminsCount(f.has_admins_count() ? f.vadmins_count.v : 0);
|
||||
channel->setInviteLink((f.vexported_invite.type() == mtpc_chatInviteExported) ? qs(f.vexported_invite.c_chatInviteExported().vlink) : QString());
|
||||
if (History *h = App::historyLoaded(channel->id)) {
|
||||
if (h->inboxReadBefore < f.vread_inbox_max_id.v + 1) {
|
||||
h->setUnreadCount(f.vunread_count.v);
|
||||
|
@ -294,7 +296,10 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt
|
|||
}
|
||||
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) {
|
||||
|
@ -313,18 +318,17 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt
|
|||
}
|
||||
App::clearPeerUpdated(peer);
|
||||
emit fullPeerUpdated(peer);
|
||||
App::emitPeerUpdated();
|
||||
}
|
||||
|
||||
void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result, mtpRequestId req) {
|
||||
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()) {
|
||||
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()) {
|
||||
App::main()->gotNotifySetting(MTP_inputNotifyPeer(peer->input), d.vnotify_settings);
|
||||
notifySettingReceived(MTP_inputNotifyPeer(peer->input), d.vnotify_settings);
|
||||
}
|
||||
|
||||
if (d.has_bot_info()) {
|
||||
|
@ -332,8 +336,8 @@ void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result, mtpRequestI
|
|||
} else {
|
||||
peer->asUser()->setBotInfoVersion(-1);
|
||||
}
|
||||
peer->asUser()->blocked = d.is_blocked() ? UserIsBlocked : UserIsNotBlocked;
|
||||
peer->asUser()->about = d.has_about() ? qs(d.vabout) : QString();
|
||||
peer->asUser()->setBlockStatus(d.is_blocked() ? UserData::BlockStatus::Blocked : UserData::BlockStatus::NotBlocked);
|
||||
peer->asUser()->setAbout(d.has_about() ? qs(d.vabout) : QString());
|
||||
|
||||
if (req) {
|
||||
QMap<PeerData*, mtpRequestId>::iterator i = _fullPeerRequests.find(peer);
|
||||
|
@ -343,7 +347,6 @@ void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result, mtpRequestI
|
|||
}
|
||||
App::clearPeerUpdated(peer);
|
||||
emit fullPeerUpdated(peer);
|
||||
App::emitPeerUpdated();
|
||||
}
|
||||
|
||||
bool ApiWrap::gotPeerFullFailed(PeerData *peer, const RPCError &error) {
|
||||
|
@ -538,14 +541,19 @@ void ApiWrap::lastParticipantsDone(ChannelData *peer, const MTPchannels_ChannelP
|
|||
h->clearLastKeyboard();
|
||||
if (App::main()) App::main()->updateBotKeyboard(h);
|
||||
}
|
||||
if (d.vcount.v > peer->count) {
|
||||
peer->count = d.vcount.v;
|
||||
} else if (v.count() > peer->count) {
|
||||
peer->count = v.count();
|
||||
int newMembersCount = qMax(d.vcount.v, v.count());
|
||||
if (newMembersCount > peer->membersCount()) {
|
||||
peer->setMembersCount(newMembersCount);
|
||||
}
|
||||
if (!bots && v.isEmpty()) {
|
||||
peer->count = peer->mgInfo->lastParticipants.size();
|
||||
if (!bots) {
|
||||
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;
|
||||
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) {
|
||||
_kickRequests.remove(kick);
|
||||
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) {
|
||||
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 {
|
||||
kick.first->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated;
|
||||
kick.first->asChannel()->mgInfo->lastParticipantsCount = 0;
|
||||
megagroupInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated;
|
||||
megagroupInfo->lastParticipantsCount = 0;
|
||||
}
|
||||
if (kick.first->asChannel()->mgInfo->lastAdmins.contains(kick.second)) {
|
||||
kick.first->asChannel()->mgInfo->lastAdmins.remove(kick.second);
|
||||
if (kick.first->asChannel()->adminsCount > 1) {
|
||||
--kick.first->asChannel()->adminsCount;
|
||||
if (megagroupInfo->lastAdmins.contains(kick.second)) {
|
||||
megagroupInfo->lastAdmins.remove(kick.second);
|
||||
if (channel->adminsCount() > 1) {
|
||||
channel->setAdminsCount(channel->adminsCount() - 1);
|
||||
}
|
||||
Notify::peerUpdatedDelayed(channel, Notify::PeerUpdate::Flag::AdminsChanged);
|
||||
}
|
||||
kick.first->asChannel()->mgInfo->bots.remove(kick.second);
|
||||
if (kick.first->asChannel()->mgInfo->bots.isEmpty() && kick.first->asChannel()->mgInfo->botStatus > 0) {
|
||||
kick.first->asChannel()->mgInfo->botStatus = -1;
|
||||
megagroupInfo->bots.remove(kick.second);
|
||||
if (megagroupInfo->bots.isEmpty() && megagroupInfo->botStatus > 0) {
|
||||
megagroupInfo->botStatus = -1;
|
||||
}
|
||||
}
|
||||
Notify::peerUpdatedDelayed(kick.first, Notify::PeerUpdate::Flag::MembersChanged);
|
||||
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) {
|
||||
_stickerSetRequests.remove(setId);
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ public:
|
|||
ApiWrap(QObject *parent);
|
||||
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 requestFullPeer(PeerData *peer);
|
||||
|
@ -50,6 +50,15 @@ public:
|
|||
void scheduleStickerSetRequest(uint64 setId, uint64 access);
|
||||
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();
|
||||
|
||||
signals:
|
||||
|
@ -65,6 +74,8 @@ public slots:
|
|||
|
||||
private:
|
||||
|
||||
void updatesReceived(const MTPUpdates &updates);
|
||||
|
||||
void gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId req);
|
||||
struct MessageDataRequest {
|
||||
MessageDataRequest() : req(0) {
|
||||
|
@ -120,4 +131,24 @@ private:
|
|||
void gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result);
|
||||
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);
|
||||
|
||||
|
||||
};
|
||||
|
|
|
@ -35,6 +35,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "localstorage.h"
|
||||
#include "apiwrap.h"
|
||||
#include "numbers.h"
|
||||
#include "observer_peer.h"
|
||||
|
||||
namespace {
|
||||
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) {
|
||||
return -1;
|
||||
}
|
||||
int32 online = user->onlineTill;
|
||||
TimeId online = user->onlineTill;
|
||||
if (online <= 0) {
|
||||
switch (online) {
|
||||
case 0:
|
||||
|
@ -252,11 +253,14 @@ namespace {
|
|||
return online;
|
||||
}
|
||||
|
||||
int32 onlineWillChangeIn(UserData *user, int32 now) {
|
||||
int32 onlineWillChangeIn(UserData *user, TimeId now) {
|
||||
if (isServiceUser(user->id) || user->botInfo) {
|
||||
return 86400;
|
||||
}
|
||||
int32 online = user->onlineTill;
|
||||
return onlineWillChangeIn(user->onlineTill, now);
|
||||
}
|
||||
|
||||
int32 onlineWillChangeIn(TimeId online, TimeId now) {
|
||||
if (online <= 0) {
|
||||
if (-online > now) return -online - now;
|
||||
return 86400;
|
||||
|
@ -276,7 +280,7 @@ namespace {
|
|||
return dNow.secsTo(dTomorrow);
|
||||
}
|
||||
|
||||
QString onlineText(UserData *user, int32 now, bool precise) {
|
||||
QString onlineText(UserData *user, TimeId now, bool precise) {
|
||||
if (isNotificationsUser(user->id)) {
|
||||
return lang(lng_status_service_notifications);
|
||||
} else if (isServiceUser(user->id)) {
|
||||
|
@ -284,7 +288,10 @@ namespace {
|
|||
} else if (user->botInfo) {
|
||||
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) {
|
||||
switch (online) {
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
int32 online = user->onlineTill;
|
||||
return onlineColorUse(user->onlineTill, now);
|
||||
}
|
||||
|
||||
bool onlineColorUse(TimeId online, TimeId now) {
|
||||
if (online <= 0) {
|
||||
switch (online) {
|
||||
case 0:
|
||||
|
@ -364,21 +374,25 @@ namespace {
|
|||
return (online > now);
|
||||
}
|
||||
|
||||
UserData *feedUsers(const MTPVector<MTPUser> &users, bool emitPeerUpdated) {
|
||||
UserData *data = 0;
|
||||
const auto &v(users.c_vector().v);
|
||||
for (QVector<MTPUser>::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) {
|
||||
const auto &user(*i);
|
||||
data = 0;
|
||||
UserData *feedUsers(const MTPVector<MTPUser> &users) {
|
||||
UserData *result = nullptr;
|
||||
for_const (auto &user, users.c_vector().v) {
|
||||
UserData *data = nullptr;
|
||||
bool wasContact = false, minimal = false;
|
||||
const MTPUserStatus *status = 0, emptyStatus = MTP_userStatusEmpty();
|
||||
|
||||
Notify::PeerUpdate update;
|
||||
using UpdateFlag = Notify::PeerUpdate::Flag;
|
||||
|
||||
switch (user.type()) {
|
||||
case mtpc_userEmpty: {
|
||||
const auto &d(user.c_userEmpty());
|
||||
|
||||
PeerId peer(peerFromUser(d.vid.v));
|
||||
data = App::user(peer);
|
||||
auto canShareThisContact = data->canShareThisContactFast();
|
||||
wasContact = data->isContact();
|
||||
|
||||
data->input = MTP_inputPeerUser(d.vid, MTP_long(0));
|
||||
data->inputUser = MTP_inputUser(d.vid, MTP_long(0));
|
||||
data->setName(lang(lng_deleted), QString(), QString(), QString());
|
||||
|
@ -386,9 +400,11 @@ namespace {
|
|||
data->access = UserNoAccess;
|
||||
data->flags = 0;
|
||||
data->setBotInfoVersion(-1);
|
||||
wasContact = (data->contact > 0);
|
||||
status = &emptyStatus;
|
||||
data->contact = -1;
|
||||
|
||||
if (canShareThisContact != data->canShareThisContactFast()) update.flags |= UpdateFlag::UserCanShareContact;
|
||||
if (wasContact != data->isContact()) update.flags |= UpdateFlag::UserIsContact;
|
||||
} break;
|
||||
case mtpc_user: {
|
||||
const auto &d(user.c_user());
|
||||
|
@ -396,6 +412,8 @@ namespace {
|
|||
|
||||
PeerId peer(peerFromUser(d.vid.v));
|
||||
data = App::user(peer);
|
||||
auto canShareThisContact = data->canShareThisContactFast();
|
||||
wasContact = data->isContact();
|
||||
if (!minimal) {
|
||||
data->flags = d.vflags.v;
|
||||
if (d.is_self()) {
|
||||
|
@ -415,7 +433,10 @@ namespace {
|
|||
}
|
||||
}
|
||||
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->setPhoto(MTP_userProfilePhotoEmpty());
|
||||
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 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());
|
||||
|
||||
bool phoneChanged = (data->phone != phone);
|
||||
if (phoneChanged) data->setPhone(phone);
|
||||
|
||||
bool phoneChanged = (data->phone() != phone);
|
||||
if (phoneChanged) {
|
||||
data->setPhone(phone);
|
||||
update.flags |= UpdateFlag::UserPhoneChanged;
|
||||
}
|
||||
bool nameChanged = (data->firstName != fname) || (data->lastName != lname);
|
||||
|
||||
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;
|
||||
status = d.has_status() ? &d.vstatus : &emptyStatus;
|
||||
}
|
||||
wasContact = (data->contact > 0);
|
||||
if (!minimal) {
|
||||
if (d.has_bot_info_version()) {
|
||||
data->setBotInfoVersion(d.vbot_info_version.v);
|
||||
|
@ -468,7 +490,7 @@ namespace {
|
|||
} else {
|
||||
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) {
|
||||
cRefReportSpamStatuses().insert(data->id, dbiprsHidden);
|
||||
Local::writeReportSpamStatuses();
|
||||
|
@ -478,6 +500,9 @@ namespace {
|
|||
if (App::wnd()) App::wnd()->updateGlobalMenu();
|
||||
}
|
||||
}
|
||||
|
||||
if (canShareThisContact != data->canShareThisContactFast()) update.flags |= UpdateFlag::UserCanShareContact;
|
||||
if (wasContact != data->isContact()) update.flags |= UpdateFlag::UserIsContact;
|
||||
} break;
|
||||
}
|
||||
|
||||
|
@ -490,6 +515,8 @@ namespace {
|
|||
} else if (data->loadedStatus != PeerData::FullLoaded) {
|
||||
data->loadedStatus = PeerData::FullLoaded;
|
||||
}
|
||||
|
||||
auto oldOnlineTill = data->onlineTill;
|
||||
if (status && !minimal) switch (status->type()) {
|
||||
case mtpc_userStatusEmpty: data->onlineTill = 0; break;
|
||||
case mtpc_userStatusRecently:
|
||||
|
@ -502,8 +529,11 @@ namespace {
|
|||
case mtpc_userStatusOffline: data->onlineTill = status->c_userStatusOffline().vwas_online.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;
|
||||
}
|
||||
if (App::main()) {
|
||||
|
@ -511,34 +541,42 @@ namespace {
|
|||
Notify::userIsContactChanged(data);
|
||||
}
|
||||
|
||||
if (emitPeerUpdated) {
|
||||
App::main()->peerUpdated(data);
|
||||
} else {
|
||||
markPeerUpdated(data);
|
||||
markPeerUpdated(data);
|
||||
if (update.flags) {
|
||||
update.peer = data;
|
||||
Notify::peerUpdatedDelayed(update);
|
||||
}
|
||||
}
|
||||
result = data;
|
||||
}
|
||||
|
||||
return data;
|
||||
return result;
|
||||
}
|
||||
|
||||
PeerData *feedChats(const MTPVector<MTPChat> &chats, bool emitPeerUpdated) {
|
||||
PeerData *data = 0;
|
||||
const auto &v(chats.c_vector().v);
|
||||
for (QVector<MTPChat>::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) {
|
||||
const auto &chat(*i);
|
||||
data = 0;
|
||||
PeerData *feedChats(const MTPVector<MTPChat> &chats) {
|
||||
PeerData *result = nullptr;
|
||||
for_const (auto &chat, chats.c_vector().v) {
|
||||
PeerData *data = nullptr;
|
||||
bool minimal = false;
|
||||
|
||||
Notify::PeerUpdate update;
|
||||
using UpdateFlag = Notify::PeerUpdate::Flag;
|
||||
|
||||
switch (chat.type()) {
|
||||
case mtpc_chat: {
|
||||
const auto &d(chat.c_chat());
|
||||
auto &d(chat.c_chat());
|
||||
|
||||
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->updateName(qs(d.vtitle), QString(), QString());
|
||||
|
||||
ChatData *cdata = data->asChat();
|
||||
cdata->setName(qs(d.vtitle));
|
||||
cdata->setPhoto(d.vphoto);
|
||||
cdata->date = d.vdate.v;
|
||||
|
||||
|
@ -571,9 +609,11 @@ namespace {
|
|||
}
|
||||
}
|
||||
Notify::migrateUpdated(channel);
|
||||
update.flags |= UpdateFlag::MigrationChanged;
|
||||
}
|
||||
if (updatedTo) {
|
||||
Notify::migrateUpdated(cdata);
|
||||
update.flags |= UpdateFlag::MigrationChanged;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -584,43 +624,52 @@ namespace {
|
|||
|
||||
cdata->count = d.vparticipants_count.v;
|
||||
cdata->isForbidden = false;
|
||||
if (cdata->version < d.vversion.v) {
|
||||
cdata->version = d.vversion.v;
|
||||
cdata->invalidateParticipants();
|
||||
if (canEdit != cdata->canEdit()) {
|
||||
update.flags |= UpdateFlag::ChatCanEdit;
|
||||
}
|
||||
} break;
|
||||
case mtpc_chatForbidden: {
|
||||
const auto &d(chat.c_chatForbidden());
|
||||
auto &d(chat.c_chatForbidden());
|
||||
|
||||
data = App::chat(peerFromChat(d.vid.v));
|
||||
auto cdata = data->asChat();
|
||||
auto canEdit = cdata->canEdit();
|
||||
|
||||
data->input = MTP_inputPeerChat(d.vid);
|
||||
|
||||
data->updateName(qs(d.vtitle), QString(), QString());
|
||||
|
||||
ChatData *cdata = data->asChat();
|
||||
cdata->setName(qs(d.vtitle));
|
||||
cdata->setPhoto(MTP_chatPhotoEmpty());
|
||||
cdata->date = 0;
|
||||
cdata->count = -1;
|
||||
cdata->invalidateParticipants();
|
||||
cdata->flags = 0;
|
||||
cdata->isForbidden = true;
|
||||
if (canEdit != cdata->canEdit()) {
|
||||
update.flags |= UpdateFlag::ChatCanEdit;
|
||||
}
|
||||
} break;
|
||||
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();
|
||||
if (minimal) {
|
||||
data = App::channelLoaded(peer);
|
||||
data = App::channelLoaded(peerId);
|
||||
if (!data) {
|
||||
continue; // minimal is not loaded, need to make getDifference
|
||||
}
|
||||
} 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));
|
||||
}
|
||||
|
||||
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) {
|
||||
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);
|
||||
|
@ -644,15 +693,32 @@ namespace {
|
|||
cdata->isForbidden = false;
|
||||
cdata->flagsUpdated();
|
||||
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;
|
||||
case mtpc_channelForbidden: {
|
||||
const auto &d(chat.c_channelForbidden());
|
||||
auto &d(chat.c_channelForbidden());
|
||||
|
||||
PeerId peer(peerFromChannel(d.vid.v));
|
||||
data = App::channel(peer);
|
||||
auto peerId = peerFromChannel(d.vid.v);
|
||||
data = App::channel(peerId);
|
||||
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);
|
||||
|
||||
auto mask = mtpCastFlags(MTPDchannelForbidden::Flag::f_broadcast | MTPDchannelForbidden::Flag::f_megagroup);
|
||||
|
@ -663,9 +729,19 @@ namespace {
|
|||
cdata->access = d.vaccess_hash.v;
|
||||
cdata->setPhoto(MTP_chatPhotoEmpty());
|
||||
cdata->date = 0;
|
||||
cdata->count = 0;
|
||||
cdata->setMembersCount(0);
|
||||
cdata->isForbidden = true;
|
||||
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;
|
||||
}
|
||||
if (!data) continue;
|
||||
|
@ -678,14 +754,15 @@ namespace {
|
|||
data->loadedStatus = PeerData::FullLoaded;
|
||||
}
|
||||
if (App::main()) {
|
||||
if (emitPeerUpdated) {
|
||||
App::main()->peerUpdated(data);
|
||||
} else {
|
||||
markPeerUpdated(data);
|
||||
markPeerUpdated(data);
|
||||
if (update.flags) {
|
||||
update.peer = data;
|
||||
Notify::peerUpdatedDelayed(update);
|
||||
}
|
||||
}
|
||||
result = data;
|
||||
}
|
||||
return data;
|
||||
return result;
|
||||
}
|
||||
|
||||
void feedParticipants(const MTPChatParticipants &p, bool requestBotInfos, bool emitPeerUpdated) {
|
||||
|
@ -701,6 +778,7 @@ namespace {
|
|||
case mtpc_chatParticipants: {
|
||||
const auto &d(p.c_chatParticipants());
|
||||
chat = App::chat(d.vchat_id.v);
|
||||
auto canEdit = chat->canEdit();
|
||||
if (!requestBotInfos || chat->version <= d.vversion.v) { // !requestBotInfos is true on getFullChat result
|
||||
chat->version = d.vversion.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;
|
||||
}
|
||||
Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::MembersChanged | Notify::PeerUpdate::Flag::AdminsChanged);
|
||||
if (chat && App::main()) {
|
||||
if (emitPeerUpdated) {
|
||||
App::main()->peerUpdated(chat);
|
||||
|
@ -820,6 +902,7 @@ namespace {
|
|||
chat->invalidateParticipants();
|
||||
chat->count++;
|
||||
}
|
||||
Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::MembersChanged);
|
||||
if (App::main()) {
|
||||
if (emitPeerUpdated) {
|
||||
App::main()->peerUpdated(chat);
|
||||
|
@ -845,6 +928,7 @@ namespace {
|
|||
}
|
||||
} else if (chat->version <= d.vversion.v && chat->count > 0) {
|
||||
chat->version = d.vversion.v;
|
||||
auto canEdit = chat->canEdit();
|
||||
UserData *user = App::userLoaded(d.vuser_id.v);
|
||||
if (user) {
|
||||
if (chat->participants.isEmpty()) {
|
||||
|
@ -886,6 +970,10 @@ namespace {
|
|||
chat->invalidateParticipants();
|
||||
chat->count--;
|
||||
}
|
||||
if (canEdit != chat->canEdit()) {
|
||||
Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::ChatCanEdit);
|
||||
}
|
||||
Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::MembersChanged);
|
||||
if (App::main()) {
|
||||
if (emitPeerUpdated) {
|
||||
App::main()->peerUpdated(chat);
|
||||
|
@ -906,13 +994,14 @@ namespace {
|
|||
}
|
||||
chat->version = d.vversion.v;
|
||||
if (mtpIsTrue(d.venabled)) {
|
||||
chat->flags |= MTPDchat::Flag::f_admins_enabled;
|
||||
if (!badVersion) {
|
||||
chat->invalidateParticipants();
|
||||
}
|
||||
chat->flags |= MTPDchat::Flag::f_admins_enabled;
|
||||
} else {
|
||||
chat->flags &= ~MTPDchat::Flag::f_admins_enabled;
|
||||
}
|
||||
Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::AdminsChanged);
|
||||
if (emitPeerUpdated) {
|
||||
App::main()->peerUpdated(chat);
|
||||
} else {
|
||||
|
@ -936,6 +1025,7 @@ namespace {
|
|||
}
|
||||
} else if (chat->version <= d.vversion.v && chat->count > 0) {
|
||||
chat->version = d.vversion.v;
|
||||
auto canEdit = chat->canEdit();
|
||||
UserData *user = App::userLoaded(d.vuser_id.v);
|
||||
if (user) {
|
||||
if (mtpIsTrue(d.vis_admin)) {
|
||||
|
@ -956,6 +1046,10 @@ namespace {
|
|||
} else {
|
||||
chat->invalidateParticipants();
|
||||
}
|
||||
if (canEdit != chat->canEdit()) {
|
||||
Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::ChatCanEdit);
|
||||
}
|
||||
Notify::peerUpdatedDelayed(chat, Notify::PeerUpdate::Flag::AdminsChanged);
|
||||
if (App::main()) {
|
||||
if (emitPeerUpdated) {
|
||||
App::main()->peerUpdated(chat);
|
||||
|
@ -1163,33 +1257,10 @@ namespace {
|
|||
}
|
||||
}
|
||||
|
||||
void feedUserLinks(const MTPVector<MTPcontacts_Link> &links, bool emitPeerUpdated) {
|
||||
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) {
|
||||
void feedUserLink(MTPint userId, const MTPContactLink &myLink, const MTPContactLink &foreignLink) {
|
||||
UserData *user = userLoaded(userId.v);
|
||||
if (user) {
|
||||
bool wasContact = (user->contact > 0);
|
||||
auto wasContact = user->isContact();
|
||||
bool wasShowPhone = !user->contact;
|
||||
switch (myLink.type()) {
|
||||
case mtpc_contactLinkContact:
|
||||
|
@ -1208,10 +1279,14 @@ namespace {
|
|||
break;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (wasContact != user->isContact()) {
|
||||
Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserIsContact);
|
||||
}
|
||||
if ((user->contact > 0 && !wasContact) || (wasContact && user->contact < 1)) {
|
||||
Notify::userIsContactChanged(user);
|
||||
}
|
||||
|
@ -1219,15 +1294,9 @@ namespace {
|
|||
bool showPhone = !isServiceUser(user->id) && !user->isSelf() && !user->contact;
|
||||
bool showPhoneChanged = !isServiceUser(user->id) && !user->isSelf() && ((showPhone && !wasShowPhone) || (!showPhone && wasShowPhone));
|
||||
if (showPhoneChanged) {
|
||||
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);
|
||||
}
|
||||
user->setName(textOneLine(user->firstName), textOneLine(user->lastName), showPhone ? App::formatPhone(user->phone()) : QString(), textOneLine(user->username));
|
||||
}
|
||||
markPeerUpdated(user);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2272,6 +2341,10 @@ namespace {
|
|||
return result;
|
||||
}
|
||||
|
||||
QPixmap pixmapFromImageInPlace(QImage &&image) {
|
||||
return QPixmap::fromImage(std_::forward<QImage>(image), Qt::ColorOnly);
|
||||
}
|
||||
|
||||
void regPhotoItem(PhotoData *data, HistoryItem *item) {
|
||||
::photoItems[data].insert(item, NullType());
|
||||
}
|
||||
|
@ -2317,11 +2390,21 @@ namespace {
|
|||
}
|
||||
|
||||
void regSharedContactItem(int32 userId, HistoryItem *item) {
|
||||
auto user = App::userLoaded(userId);
|
||||
auto canShareThisContact = user ? user->canShareThisContact() : false;
|
||||
::sharedContactItems[userId].insert(item, NullType());
|
||||
if (canShareThisContact != (user ? user->canShareThisContact() : false)) {
|
||||
Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserCanShareContact);
|
||||
}
|
||||
}
|
||||
|
||||
void unregSharedContactItem(int32 userId, HistoryItem *item) {
|
||||
auto user = App::userLoaded(userId);
|
||||
auto canShareThisContact = user ? user->canShareThisContact() : false;
|
||||
::sharedContactItems[userId].remove(item);
|
||||
if (canShareThisContact != (user ? user->canShareThisContact() : false)) {
|
||||
Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserCanShareContact);
|
||||
}
|
||||
}
|
||||
|
||||
const SharedContactItems &sharedContactItems() {
|
||||
|
|
|
@ -59,13 +59,17 @@ namespace App {
|
|||
|
||||
QString formatPhone(QString phone);
|
||||
|
||||
int32 onlineForSort(UserData *user, int32 now);
|
||||
int32 onlineWillChangeIn(UserData *user, int32 nowOnServer);
|
||||
QString onlineText(UserData *user, int32 nowOnServer, bool precise = false);
|
||||
bool onlineColorUse(UserData *user, int32 now);
|
||||
TimeId onlineForSort(UserData *user, TimeId now);
|
||||
int32 onlineWillChangeIn(UserData *user, TimeId now);
|
||||
int32 onlineWillChangeIn(TimeId online, TimeId 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 feedParticipantAdd(const MTPDupdateChatParticipantAdd &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 feedOutboxRead(const PeerId &peer, MsgId upTo);
|
||||
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, bool emitPeerUpdated = true);
|
||||
void feedUserLink(MTPint userId, const MTPContactLink &myLink, const MTPContactLink &foreignLink);
|
||||
|
||||
void markPeerUpdated(PeerData *data);
|
||||
void clearPeerUpdated(PeerData *data);
|
||||
void emitPeerUpdated();
|
||||
|
||||
ImagePtr image(const MTPPhotoSize &size);
|
||||
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(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 unregPhotoItem(PhotoData *data, HistoryItem *item);
|
||||
|
|
|
@ -27,9 +27,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "mainwidget.h"
|
||||
#include "lang.h"
|
||||
#include "boxes/confirmbox.h"
|
||||
#include "ui/filedialog.h"
|
||||
#include "langloaderplain.h"
|
||||
#include "localstorage.h"
|
||||
#include "autoupdater.h"
|
||||
#include "core/observer.h"
|
||||
#include "observer_peer.h"
|
||||
|
||||
namespace {
|
||||
void mtpStateChanged(int32 dc, int32 state) {
|
||||
|
@ -200,6 +203,7 @@ void Application::singleInstanceChecked() {
|
|||
Logs::multipleInstances();
|
||||
}
|
||||
|
||||
Notify::startObservers();
|
||||
Sandbox::start();
|
||||
|
||||
if (!Logs::started() || (!cManyInstance() && !Logs::instanceChecked())) {
|
||||
|
@ -336,6 +340,8 @@ void Application::closeApplication() {
|
|||
if (_updateThread) _updateThread->quit();
|
||||
_updateThread = 0;
|
||||
#endif
|
||||
|
||||
Notify::finishObservers();
|
||||
}
|
||||
|
||||
#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() {
|
||||
uint64 ms = getms(), left = MTPAckSendWaiting + MTPKillFileSessionTimeout;
|
||||
for (QMap<int32, uint64>::iterator i = killDownloadSessionTimes.begin(); i != killDownloadSessionTimes.end(); ) {
|
||||
|
|
|
@ -203,6 +203,8 @@ public slots:
|
|||
|
||||
void call_handleHistoryUpdate();
|
||||
void call_handleUnreadCounterUpdate();
|
||||
void call_handleFileDialogQueue();
|
||||
void call_handleDelayedPeerUpdates();
|
||||
|
||||
private:
|
||||
|
||||
|
|
|
@ -32,9 +32,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
|
||||
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)
|
||||
, _text1(this, lang(lng_about_text_1), st::aboutLabel, st::aboutTextStyle)
|
||||
, _text2(this, lang(lng_about_text_2), st::aboutLabel, st::aboutTextStyle)
|
||||
, _text3(this, QString(), 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), FlatLabel::InitType::Rich, st::aboutLabel, st::aboutTextStyle)
|
||||
, _text3(this,st::aboutLabel, st::aboutTextStyle)
|
||||
, _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]")));
|
||||
|
||||
|
@ -109,10 +109,9 @@ void AboutBox::paintEvent(QPaintEvent *e) {
|
|||
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
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());
|
||||
if (file.startsWith(qsl("/.file/id="))) file = psConvertFileUrl(file);
|
||||
auto file = psConvertFileUrl(m->urls().at(0));
|
||||
|
||||
return file.endsWith(qstr(".telegramcrash"), Qt::CaseInsensitive) ? file : QString();
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#pragma once
|
||||
|
||||
#include "abstractbox.h"
|
||||
#include "ui/flatlabel.h"
|
||||
|
||||
class AboutBox : public AbstractBox {
|
||||
Q_OBJECT
|
||||
|
|
|
@ -30,18 +30,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "apiwrap.h"
|
||||
#include "observer_peer.h"
|
||||
|
||||
AddContactBox::AddContactBox(QString fname, QString lname, QString phone) : AbstractBox(st::boxWidth)
|
||||
, _user(0)
|
||||
, _save(this, lang(lng_add_contact), st::defaultBoxButton)
|
||||
, _cancel(this, lang(lng_cancel), st::cancelBoxButton)
|
||||
, _retry(this, lang(lng_try_other_contact), st::defaultBoxButton)
|
||||
, _first(this, st::defaultInputField, lang(lng_signup_firstname), fname)
|
||||
, _last(this, st::defaultInputField, lang(lng_signup_lastname), lname)
|
||||
, _phone(this, st::defaultInputField, lang(lng_contact_phone), phone)
|
||||
, _invertOrder(langFirstNameGoesSecond())
|
||||
, _contactId(0)
|
||||
, _addRequest(0) {
|
||||
, _invertOrder(langFirstNameGoesSecond()) {
|
||||
if (!phone.isEmpty()) {
|
||||
_phone.setDisabled(true);
|
||||
}
|
||||
|
@ -56,10 +54,8 @@ AddContactBox::AddContactBox(UserData *user) : AbstractBox(st::boxWidth)
|
|||
, _retry(this, lang(lng_try_other_contact), st::defaultBoxButton)
|
||||
, _first(this, st::defaultInputField, lang(lng_signup_firstname), user->firstName)
|
||||
, _last(this, st::defaultInputField, lang(lng_signup_lastname), user->lastName)
|
||||
, _phone(this, st::defaultInputField, lang(lng_contact_phone), user->phone)
|
||||
, _invertOrder(langFirstNameGoesSecond())
|
||||
, _contactId(0)
|
||||
, _addRequest(0) {
|
||||
, _phone(this, st::defaultInputField, lang(lng_contact_phone), user->phone())
|
||||
, _invertOrder(langFirstNameGoesSecond()) {
|
||||
_phone.setDisabled(true);
|
||||
initBox();
|
||||
}
|
||||
|
@ -190,7 +186,7 @@ void AddContactBox::onSave() {
|
|||
_sentName = firstName;
|
||||
if (_user) {
|
||||
_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));
|
||||
} else {
|
||||
_contactId = rand_value<uint64>();
|
||||
|
@ -206,7 +202,7 @@ bool AddContactBox::onSaveUserFail(const RPCError &error) {
|
|||
QString err(error.type());
|
||||
QString firstName = _first.getLastText().trimmed(), lastName = _last.getLastText().trimmed();
|
||||
if (err == "CHAT_TITLE_NOT_MODIFIED") {
|
||||
_user->updateName(firstName, QString(), QString());
|
||||
_user->setName(firstName, lastName, _user->nameOrPhone, _user->username);
|
||||
emit closed();
|
||||
return true;
|
||||
} else if (err == "NO_CHAT_TITLE") {
|
||||
|
@ -548,7 +544,7 @@ bool GroupInfoBox::creationFail(const RPCError &error) {
|
|||
void GroupInfoBox::exportDone(const MTPExportedChatInvite &result) {
|
||||
_creationRequestId = 0;
|
||||
if (result.type() == mtpc_chatInviteExported) {
|
||||
_createdChannel->invitationUrl = qs(result.c_chatInviteExported().vlink);
|
||||
_createdChannel->setInviteLink(qs(result.c_chatInviteExported().vlink));
|
||||
}
|
||||
Ui::showLayer(new SetupChannelBox(_createdChannel));
|
||||
}
|
||||
|
@ -712,7 +708,7 @@ void SetupChannelBox::paintEvent(QPaintEvent *e) {
|
|||
option.setWrapMode(QTextOption::WrapAnywhere);
|
||||
p.setFont(_linkOver ? st::boxTextFont->underline() : st::boxTextFont);
|
||||
p.setPen(st::btnDefLink.color);
|
||||
p.drawText(_invitationLink, _channel->invitationUrl, option);
|
||||
p.drawText(_invitationLink, _channel->inviteLink(), option);
|
||||
if (!_goodTextLink.isEmpty() && a_goodOpacity.current() > 0) {
|
||||
p.setOpacity(a_goodOpacity.current());
|
||||
p.setPen(st::setGoodColor);
|
||||
|
@ -753,7 +749,7 @@ void SetupChannelBox::mouseMoveEvent(QMouseEvent *e) {
|
|||
void SetupChannelBox::mousePressEvent(QMouseEvent *e) {
|
||||
mouseMoveEvent(e);
|
||||
if (_linkOver) {
|
||||
Application::clipboard()->setText(_channel->invitationUrl);
|
||||
Application::clipboard()->setText(_channel->inviteLink());
|
||||
_goodTextLink = lang(lng_create_channel_link_copied);
|
||||
a_goodOpacity = anim::fvalue(1, 0);
|
||||
_a_goodFade.start();
|
||||
|
@ -1140,7 +1136,9 @@ bool EditNameTitleBox::onSaveChatFail(const RPCError &error) {
|
|||
_requestId = 0;
|
||||
QString err(error.type());
|
||||
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();
|
||||
return true;
|
||||
} else if (err == qstr("NO_CHAT_TITLE")) {
|
||||
|
@ -1162,7 +1160,7 @@ EditChannelBox::EditChannelBox(ChannelData *channel) : AbstractBox()
|
|||
, _save(this, lang(lng_settings_save), st::defaultBoxButton)
|
||||
, _cancel(this, lang(lng_cancel), st::cancelBoxButton)
|
||||
, _title(this, st::defaultInputField, lang(lng_dlg_new_channel_name), _channel->name)
|
||||
, _description(this, st::newGroupDescription, lang(lng_create_group_description), _channel->about)
|
||||
, _description(this, st::newGroupDescription, lang(lng_create_group_description), _channel->about())
|
||||
, _sign(this, lang(lng_edit_sign_messages), channel->addsSignature())
|
||||
, _publicLink(this, lang(channel->isPublic() ? lng_profile_edit_public_link : lng_profile_create_public_link), st::defaultBoxLinkButton)
|
||||
, _saveTitleRequestId(0)
|
||||
|
@ -1303,7 +1301,7 @@ void EditChannelBox::onPublicLink() {
|
|||
}
|
||||
|
||||
void EditChannelBox::saveDescription() {
|
||||
if (_sentDescription == _channel->about) {
|
||||
if (_sentDescription == _channel->about()) {
|
||||
saveSign();
|
||||
} else {
|
||||
_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) {
|
||||
_saveDescriptionRequestId = 0;
|
||||
if (err == qstr("CHAT_ABOUT_NOT_MODIFIED")) {
|
||||
_channel->about = _sentDescription;
|
||||
if (App::api()) {
|
||||
emit App::api()->fullPeerUpdated(_channel);
|
||||
if (_channel->setAbout(_sentDescription)) {
|
||||
if (App::api()) {
|
||||
emit App::api()->fullPeerUpdated(_channel);
|
||||
}
|
||||
}
|
||||
saveSign();
|
||||
return true;
|
||||
|
@ -1367,9 +1366,10 @@ void EditChannelBox::onSaveTitleDone(const MTPUpdates &updates) {
|
|||
|
||||
void EditChannelBox::onSaveDescriptionDone(const MTPBool &result) {
|
||||
_saveDescriptionRequestId = 0;
|
||||
_channel->about = _sentDescription;
|
||||
if (App::api()) {
|
||||
emit App::api()->fullPeerUpdated(_channel);
|
||||
if (_channel->setAbout(_sentDescription)) {
|
||||
if (App::api()) {
|
||||
emit App::api()->fullPeerUpdated(_channel);
|
||||
}
|
||||
}
|
||||
saveSign();
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ private:
|
|||
|
||||
void initBox();
|
||||
|
||||
UserData *_user;
|
||||
UserData *_user = nullptr;
|
||||
QString _boxTitle;
|
||||
|
||||
BoxButton _save, _cancel, _retry;
|
||||
|
@ -66,9 +66,9 @@ private:
|
|||
|
||||
bool _invertOrder;
|
||||
|
||||
uint64 _contactId;
|
||||
uint64 _contactId = 0;
|
||||
|
||||
mtpRequestId _addRequest;
|
||||
mtpRequestId _addRequest = 0;
|
||||
QString _sentName;
|
||||
};
|
||||
|
||||
|
|
|
@ -381,11 +381,10 @@ void ConvertToSupergroupBox::resizeEvent(QResizeEvent *e) {
|
|||
PinMessageBox::PinMessageBox(ChannelData *channel, MsgId msgId) : AbstractBox(st::boxWidth)
|
||||
, _channel(channel)
|
||||
, _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)
|
||||
, _pin(this, lang(lng_pinned_pin), st::defaultBoxButton)
|
||||
, _cancel(this, lang(lng_cancel), st::cancelBoxButton)
|
||||
, _requestId(0) {
|
||||
, _cancel(this, lang(lng_cancel), st::cancelBoxButton) {
|
||||
_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());
|
||||
|
||||
|
@ -441,7 +440,7 @@ RichDeleteMessageBox::RichDeleteMessageBox(ChannelData *channel, UserData *from,
|
|||
, _channel(channel)
|
||||
, _from(from)
|
||||
, _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)
|
||||
, _reportSpam(this, lang(lng_report_spam), 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)) {
|
||||
bool wasLast = (item->history()->lastMsg == item);
|
||||
item->destroy();
|
||||
|
||||
if (_msgId > 0) {
|
||||
App::main()->deleteMessages(_channel, QVector<MTPint>(1, MTP_int(_msgId)));
|
||||
} else if (wasLast) {
|
||||
|
|
|
@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#pragma once
|
||||
|
||||
#include "abstractbox.h"
|
||||
#include "ui/flatlabel.h"
|
||||
|
||||
class InformBox;
|
||||
class ConfirmBox : public AbstractBox, public ClickHandlerHost {
|
||||
|
@ -216,7 +217,7 @@ private:
|
|||
|
||||
BoxButton _pin, _cancel;
|
||||
|
||||
mtpRequestId _requestId;
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "ui/filedialog.h"
|
||||
#include "boxes/photocropbox.h"
|
||||
#include "boxes/confirmbox.h"
|
||||
#include "observer_peer.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
QString cantInviteError() {
|
||||
|
@ -201,16 +202,20 @@ void ContactsInner::addAdminDone(const MTPUpdates &result, mtpRequestId req) {
|
|||
|
||||
_addAdminRequestId = 0;
|
||||
if (_addAdmin && _channel && _channel->isMegagroup()) {
|
||||
Notify::PeerUpdate update(_channel);
|
||||
if (_channel->mgInfo->lastParticipants.indexOf(_addAdmin) < 0) {
|
||||
_channel->mgInfo->lastParticipants.push_front(_addAdmin);
|
||||
update.flags |= Notify::PeerUpdate::Flag::MembersChanged;
|
||||
}
|
||||
_channel->mgInfo->lastAdmins.insert(_addAdmin);
|
||||
update.flags |= Notify::PeerUpdate::Flag::AdminsChanged;
|
||||
if (_addAdmin->botInfo) {
|
||||
_channel->mgInfo->bots.insert(_addAdmin);
|
||||
if (_channel->mgInfo->botStatus != 0 && _channel->mgInfo->botStatus < 2) {
|
||||
_channel->mgInfo->botStatus = 2;
|
||||
}
|
||||
}
|
||||
Notify::peerUpdatedDelayed(update);
|
||||
}
|
||||
if (_addAdminBox) _addAdminBox->onClose();
|
||||
emit adminAdded();
|
||||
|
@ -224,7 +229,7 @@ bool ContactsInner::addAdminFail(const RPCError &error, mtpRequestId req) {
|
|||
_addAdminRequestId = 0;
|
||||
if (_addAdminBox) _addAdminBox->onClose();
|
||||
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") {
|
||||
Ui::showLayer(new InformBox(lang(lng_channel_admins_too_much)), KeepOtherLayers);
|
||||
} else if (error.type() == qstr("USER_RESTRICTED")) {
|
||||
|
@ -728,7 +733,7 @@ void ContactsInner::changeCheckState(ContactData *data, PeerData *peer) {
|
|||
_checkedContacts.insert(peer, true);
|
||||
++_selCount;
|
||||
} 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()) {
|
||||
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) {
|
||||
result += qMax(_chat->count, 1);
|
||||
} else if (_channel) {
|
||||
result += qMax(_channel->count, _already.size());
|
||||
result += qMax(_channel->membersCount(), _already.size());
|
||||
} else if (_creating == CreatingGroupGroup) {
|
||||
result += 1;
|
||||
}
|
||||
|
@ -1739,7 +1744,7 @@ bool ContactsBox::creationFail(const RPCError &error) {
|
|||
|
||||
MembersInner::MembersInner(ChannelData *channel, MembersFilter filter) : TWidget()
|
||||
, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom())
|
||||
, _newItemHeight((channel->amCreator() && (channel->count < (channel->isMegagroup() ? 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)
|
||||
, _channel(channel)
|
||||
, _filter(filter)
|
||||
|
@ -1808,7 +1813,7 @@ void MembersInner::paintEvent(QPaintEvent *e) {
|
|||
paintDialog(p, _rows[from], data(from), sel, kickSel, kickDown);
|
||||
p.translate(0, _rowHeight);
|
||||
}
|
||||
if (to == _rows.size() && _filter == MembersFilterRecent && (_rows.size() < _channel->count || _rows.size() >= Global::ChatSizeMax())) {
|
||||
if (to == _rows.size() && _filter == MembersFilterRecent && (_rows.size() < _channel->membersCount() || _rows.size() >= Global::ChatSizeMax())) {
|
||||
p.setPen(st::stickersReorderFg);
|
||||
_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 (PeerData *peer = _rows[_sel]) {
|
||||
Ui::hideLayer();
|
||||
App::main()->showPeerProfile(peer, ShowAtUnreadMsgId);
|
||||
Ui::showPeerProfile(peer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1987,7 +1992,7 @@ void MembersInner::refresh() {
|
|||
} else {
|
||||
_about.setText(st::boxTextFont, lng_channel_only_last_shown(lt_count, _rows.size()));
|
||||
_aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom();
|
||||
if (_filter != MembersFilterRecent || (_rows.size() >= _channel->count && _rows.size() < Global::ChatSizeMax())) {
|
||||
if (_filter != MembersFilterRecent || (_rows.size() >= _channel->membersCount() && _rows.size() < Global::ChatSizeMax())) {
|
||||
_aboutHeight = 0;
|
||||
}
|
||||
resize(width(), st::membersPadding.top() + _newItemHeight + _rows.size() * _rowHeight + st::membersPadding.bottom() + _aboutHeight);
|
||||
|
@ -2003,11 +2008,11 @@ MembersFilter MembersInner::filter() const {
|
|||
return _filter;
|
||||
}
|
||||
|
||||
QMap<UserData*, bool> MembersInner::already() const {
|
||||
MembersAlreadyIn MembersInner::already() const {
|
||||
MembersAlreadyIn result;
|
||||
for (int32 i = 0, l = _rows.size(); i < l; ++i) {
|
||||
if (_rows.at(i)->isUser()) {
|
||||
result.insert(_rows.at(i)->asUser(), true);
|
||||
for_const (auto peer, _rows) {
|
||||
if (peer->isUser()) {
|
||||
result.insert(peer->asUser());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
@ -2115,14 +2120,16 @@ void MembersInner::membersReceived(const MTPchannels_ChannelParticipants &result
|
|||
_datas.reserve(v.size());
|
||||
_dates.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);
|
||||
} else if (_filter == MembersFilterAdmins && _channel->adminsCount < d.vcount.v) {
|
||||
_channel->adminsCount = d.vcount.v;
|
||||
} else if (_filter == MembersFilterAdmins && _channel->adminsCount() < d.vcount.v) {
|
||||
_channel->setAdminsCount(d.vcount.v);
|
||||
if (App::main()) emit App::main()->peerUpdated(_channel);
|
||||
}
|
||||
App::feedUsers(d.vusers);
|
||||
|
||||
for (QVector<MTPChannelParticipant>::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) {
|
||||
int32 userId = 0, addedTime = 0;
|
||||
MemberRole role = MemberRoleNone;
|
||||
|
@ -2173,6 +2180,8 @@ void MembersInner::membersReceived(const MTPchannels_ChannelParticipants &result
|
|||
_channel->mgInfo->lastAdmins.insert(_rows.at(i));
|
||||
}
|
||||
}
|
||||
|
||||
Notify::peerUpdatedDelayed(_channel, Notify::PeerUpdate::Flag::AdminsChanged);
|
||||
}
|
||||
}
|
||||
if (_rows.isEmpty()) {
|
||||
|
@ -2229,11 +2238,11 @@ void MembersInner::removeKicked() {
|
|||
_dates.removeAt(index);
|
||||
_roles.removeAt(index);
|
||||
clearSel();
|
||||
if (_filter == MembersFilterRecent && _channel->count > 1) {
|
||||
--_channel->count;
|
||||
if (_filter == MembersFilterRecent && _channel->membersCount() > 1) {
|
||||
_channel->setMembersCount(_channel->membersCount() - 1);
|
||||
if (App::main()) emit App::main()->peerUpdated(_channel);
|
||||
} else if (_filter == MembersFilterAdmins && _channel->adminsCount > 1) {
|
||||
--_channel->adminsCount;
|
||||
} else if (_filter == MembersFilterAdmins && _channel->adminsCount() > 1) {
|
||||
_channel->setAdminsCount(_channel->adminsCount() - 1);
|
||||
if (App::main()) emit App::main()->peerUpdated(_channel);
|
||||
}
|
||||
refresh();
|
||||
|
@ -2290,8 +2299,8 @@ void MembersBox::onScroll() {
|
|||
}
|
||||
|
||||
void MembersBox::onAdd() {
|
||||
if (_inner.filter() == MembersFilterRecent && _inner.channel()->count >= (_inner.channel()->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax())) {
|
||||
Ui::showLayer(new MaxInviteBox(_inner.channel()->invitationUrl), KeepOtherLayers);
|
||||
if (_inner.filter() == MembersFilterRecent && _inner.channel()->membersCount() >= (_inner.channel()->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax())) {
|
||||
Ui::showLayer(new MaxInviteBox(_inner.channel()->inviteLink()), KeepOtherLayers);
|
||||
return;
|
||||
}
|
||||
ContactsBox *box = new ContactsBox(_inner.channel(), _inner.filter(), _inner.already());
|
||||
|
|
|
@ -31,7 +31,7 @@ enum MembersFilter {
|
|||
MembersFilterRecent,
|
||||
MembersFilterAdmins,
|
||||
};
|
||||
typedef QMap<UserData*, bool> MembersAlreadyIn;
|
||||
using MembersAlreadyIn = OrderedSet<UserData*>;
|
||||
|
||||
QString cantInviteError();
|
||||
|
||||
|
@ -318,7 +318,7 @@ public:
|
|||
}
|
||||
void clearSel();
|
||||
|
||||
QMap<UserData*, bool> already() const;
|
||||
MembersAlreadyIn already() const;
|
||||
|
||||
~MembersInner();
|
||||
|
||||
|
|
147
Telegram/SourceFiles/boxes/report_box.cpp
Normal 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);
|
||||
}
|
72
Telegram/SourceFiles/boxes/report_box.h
Normal 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;
|
||||
|
||||
};
|
|
@ -289,7 +289,7 @@ QString Generator::valueAssignmentCode(structure::Value value) const {
|
|||
} break;
|
||||
case Tag::Icon: {
|
||||
auto v(value.Icon());
|
||||
if (v.parts.empty()) return QString();
|
||||
if (v.parts.empty()) return QString("{}");
|
||||
|
||||
QStringList parts;
|
||||
for (const auto &part : v.parts) {
|
||||
|
|
|
@ -535,8 +535,8 @@ structure::Value ParsedFile::readPointValue() {
|
|||
if (tokenValue(font) == "point") {
|
||||
assertNextToken(BasicType::LeftParenthesis);
|
||||
|
||||
auto x = readNumericValue(); assertNextToken(BasicType::Comma);
|
||||
auto y = readNumericValue();
|
||||
auto x = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma);
|
||||
auto y = readNumericOrNumericCopyValue();
|
||||
if (x.type().tag != structure::TypeTag::Pixels ||
|
||||
y.type().tag != structure::TypeTag::Pixels) {
|
||||
logErrorTypeMismatch() << "expected two px values for the point";
|
||||
|
@ -581,8 +581,8 @@ structure::Value ParsedFile::readSizeValue() {
|
|||
if (tokenValue(font) == "size") {
|
||||
assertNextToken(BasicType::LeftParenthesis);
|
||||
|
||||
auto w = readNumericValue(); assertNextToken(BasicType::Comma);
|
||||
auto h = readNumericValue();
|
||||
auto w = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma);
|
||||
auto h = readNumericOrNumericCopyValue();
|
||||
if (w.type().tag != structure::TypeTag::Pixels ||
|
||||
h.type().tag != structure::TypeTag::Pixels) {
|
||||
logErrorTypeMismatch() << "expected two px values for the size";
|
||||
|
@ -662,10 +662,10 @@ structure::Value ParsedFile::readMarginsValue() {
|
|||
if (tokenValue(font) == "margins") {
|
||||
assertNextToken(BasicType::LeftParenthesis);
|
||||
|
||||
auto l = readNumericValue(); assertNextToken(BasicType::Comma);
|
||||
auto t = readNumericValue(); assertNextToken(BasicType::Comma);
|
||||
auto r = readNumericValue(); assertNextToken(BasicType::Comma);
|
||||
auto b = readNumericValue();
|
||||
auto l = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma);
|
||||
auto t = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma);
|
||||
auto r = readNumericOrNumericCopyValue(); assertNextToken(BasicType::Comma);
|
||||
auto b = readNumericOrNumericCopyValue();
|
||||
if (l.type().tag != structure::TypeTag::Pixels ||
|
||||
t.type().tag != structure::TypeTag::Pixels ||
|
||||
r.type().tag != structure::TypeTag::Pixels ||
|
||||
|
@ -701,18 +701,10 @@ structure::Value ParsedFile::readFontValue() {
|
|||
file_.putBack();
|
||||
}
|
||||
}
|
||||
if (auto familyValue = readStringValue()) {
|
||||
if (auto familyValue = readStringOrStringCopyValue()) {
|
||||
family = familyValue;
|
||||
} else if (auto sizeValue = readNumericValue()) {
|
||||
} else if (auto sizeValue = readNumericOrNumericCopyValue()) {
|
||||
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)) {
|
||||
break;
|
||||
} else {
|
||||
|
@ -776,7 +768,37 @@ structure::Value ParsedFile::readCopyValue() {
|
|||
if (auto variable = module_->findVariable(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 {};
|
||||
}
|
||||
|
|
|
@ -98,6 +98,9 @@ private:
|
|||
structure::Value readIconValue();
|
||||
structure::Value readCopyValue();
|
||||
|
||||
structure::Value readNumericOrNumericCopyValue();
|
||||
structure::Value readStringOrStringCopyValue();
|
||||
|
||||
structure::data::monoicon readMonoIconFields();
|
||||
QString readMonoIconFilename();
|
||||
|
||||
|
@ -131,6 +134,7 @@ private:
|
|||
{ "align" , { structure::TypeTag::Align } },
|
||||
{ "margins" , { structure::TypeTag::Margins } },
|
||||
{ "font" , { structure::TypeTag::Font } },
|
||||
{ "icon" , { structure::TypeTag::Icon } },
|
||||
};
|
||||
|
||||
};
|
||||
|
|
|
@ -74,14 +74,15 @@ bool Processor::write(const structure::Module &module) const {
|
|||
QFileInfo srcFile(module.filepath());
|
||||
QString dstFilePath = dir.absolutePath() + '/' + destFileBaseName(module);
|
||||
|
||||
bool forceReGenerate = false;// !options_.rebuildDependencies;
|
||||
common::ProjectInfo project = {
|
||||
"codegen_style",
|
||||
srcFile.fileName(),
|
||||
"stdafx.h",
|
||||
false,//!options_.rebuildDependencies, // forceReGenerate
|
||||
forceReGenerate
|
||||
};
|
||||
|
||||
SpriteGenerator spriteGenerator(module);
|
||||
SpriteGenerator spriteGenerator(module, forceReGenerate);
|
||||
if (!spriteGenerator.writeSprites()) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -48,8 +48,9 @@ constexpr int kErrorCouldNotWrite = 845;
|
|||
|
||||
} // namespace
|
||||
|
||||
SpriteGenerator::SpriteGenerator(const structure::Module &module)
|
||||
SpriteGenerator::SpriteGenerator(const structure::Module &module, bool forceReGenerate)
|
||||
: module_(module)
|
||||
, forceReGenerate_(forceReGenerate)
|
||||
, basePath_(QFileInfo(module.filepath()).dir().absolutePath()) {
|
||||
}
|
||||
|
||||
|
@ -84,7 +85,7 @@ bool SpriteGenerator::writeSprites() {
|
|||
}
|
||||
}
|
||||
QFile file(filepath);
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
if (!forceReGenerate_ && file.open(QIODevice::ReadOnly)) {
|
||||
if (file.readAll() == spriteData) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ class Module;
|
|||
|
||||
class SpriteGenerator {
|
||||
public:
|
||||
SpriteGenerator(const structure::Module &module);
|
||||
SpriteGenerator(const structure::Module &module, bool forceReGenerate);
|
||||
SpriteGenerator(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%.
|
||||
|
||||
const structure::Module &module_;
|
||||
bool forceReGenerate_;
|
||||
QString basePath_;
|
||||
QImage sprite2x_;
|
||||
QList<structure::Variable> sprites_;
|
||||
|
|
|
@ -44,7 +44,6 @@ uint64 _SharedMemoryLocation[4] = { 0x00, 0x01, 0x02, 0x03 };
|
|||
#include <openssl/rand.h>
|
||||
|
||||
// Base types compile-time check
|
||||
|
||||
static_assert(sizeof(char) == 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");
|
||||
|
|
|
@ -33,6 +33,13 @@ T *getPointerAndReset(T *&ptr) {
|
|||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T createAndSwap(T &value) {
|
||||
T result;
|
||||
std::swap(result, value);
|
||||
return result;
|
||||
}
|
||||
|
||||
struct NullType {
|
||||
};
|
||||
|
||||
|
@ -421,6 +428,24 @@ inline bool operator!=(std::nullptr_t a, const unique_ptr<T> &b) noexcept {
|
|||
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_
|
||||
|
||||
#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;
|
||||
typedef void(*ComponentConstruct)(void *location, Composer *composer);
|
||||
typedef void(*ComponentDestruct)(void *location);
|
||||
|
@ -1170,213 +1186,96 @@ public:
|
|||
virtual R call(Args... args) const = 0;
|
||||
virtual ~SharedCallback() {
|
||||
}
|
||||
typedef QSharedPointer<SharedCallback<R, Args...>> Ptr;
|
||||
using Ptr = QSharedPointer<SharedCallback<R, Args...>>;
|
||||
|
||||
};
|
||||
|
||||
template <typename R>
|
||||
template <typename R, typename... Args>
|
||||
class FunctionImplementation {
|
||||
public:
|
||||
virtual R call() = 0;
|
||||
virtual R call(Args... args) = 0;
|
||||
virtual void destroy() { delete this; }
|
||||
virtual ~FunctionImplementation() {}
|
||||
|
||||
};
|
||||
template <typename R>
|
||||
class NullFunctionImplementation : public FunctionImplementation<R> {
|
||||
|
||||
template <typename R, typename... Args>
|
||||
class NullFunctionImplementation : public FunctionImplementation<R, Args...> {
|
||||
public:
|
||||
virtual R call() { return R(); }
|
||||
virtual R call(Args... args) { return R(); }
|
||||
virtual void destroy() {}
|
||||
static NullFunctionImplementation<R> SharedInstance;
|
||||
static NullFunctionImplementation<R, Args...> SharedInstance;
|
||||
|
||||
};
|
||||
template <typename R>
|
||||
NullFunctionImplementation<R> NullFunctionImplementation<R>::SharedInstance;
|
||||
template <typename R>
|
||||
class FunctionCreator {
|
||||
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>
|
||||
template <typename R, typename... Args>
|
||||
NullFunctionImplementation<R, Args...> NullFunctionImplementation<R, Args...>::SharedInstance;
|
||||
|
||||
template <typename R, typename... Args>
|
||||
class Function {
|
||||
public:
|
||||
typedef FunctionCreator<R> Creator;
|
||||
static Creator Null() { return Creator(&NullFunctionImplementation<R>::SharedInstance); }
|
||||
Function(const Creator &creator) : _implementation(creator.create()) {}
|
||||
R call() { return _implementation->call(); }
|
||||
~Function() { destroyImplementation(_implementation); }
|
||||
Function() : _implementation(nullImpl()) {}
|
||||
Function(FunctionImplementation<R, Args...> *implementation) : _implementation(implementation) {}
|
||||
Function(const Function<R, Args...> &other) = delete;
|
||||
Function<R, Args...> &operator=(const Function<R, Args...> &other) = delete;
|
||||
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:
|
||||
Function(const Function<R> &other);
|
||||
Function<R> &operator=(const Function<R> &other);
|
||||
FunctionImplementation<R> *_implementation;
|
||||
static FunctionImplementation<R, Args...> *nullImpl() {
|
||||
return &NullFunctionImplementation<R, Args...>::SharedInstance;
|
||||
}
|
||||
|
||||
FunctionImplementation<R, Args...> *_implementation;
|
||||
|
||||
};
|
||||
|
||||
template <typename R>
|
||||
class WrappedFunction : public FunctionImplementation<R> {
|
||||
template <typename R, typename... Args>
|
||||
class WrappedFunction : public FunctionImplementation<R, Args...> {
|
||||
public:
|
||||
typedef R(*Method)();
|
||||
using Method = R(*)(Args... args);
|
||||
WrappedFunction(Method method) : _method(method) {}
|
||||
virtual R call() { return (*_method)(); }
|
||||
virtual R call(Args... args) { return (*_method)(args...); }
|
||||
|
||||
private:
|
||||
Method _method;
|
||||
|
||||
};
|
||||
template <typename R>
|
||||
inline FunctionCreator<R> func(R(*method)()) {
|
||||
return FunctionCreator<R>(new WrappedFunction<R>(method));
|
||||
template <typename R, typename... Args>
|
||||
inline Function<R, Args...> func(R(*method)(Args... args)) {
|
||||
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:
|
||||
typedef R(I::*Method)();
|
||||
using Method = R(I::*)(Args... args);
|
||||
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:
|
||||
O *_obj;
|
||||
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>
|
||||
class NullFunction1Implementation : public Function1Implementation<R, A1> {
|
||||
public:
|
||||
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));
|
||||
template <typename O, typename I, typename R, typename... Args>
|
||||
inline Function<R, Args...> func(O *obj, R(I::*method)(Args...)) {
|
||||
return Function<R, Args...>(new ObjectFunction<O, I, R, Args...>(obj, method));
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ enum ExpandLinksMode {
|
|||
ExpandLinksNone,
|
||||
ExpandLinksShortened,
|
||||
ExpandLinksAll,
|
||||
ExpandLinksUrlOnly, // For custom urls leaves only url instead of text.
|
||||
};
|
||||
|
||||
class ClickHandlerHost {
|
||||
|
@ -144,9 +145,11 @@ public:
|
|||
}
|
||||
static void hostDestroyed(ClickHandlerHost *host) {
|
||||
if (_activeHost == host) {
|
||||
if (_active) (*_active).clear();
|
||||
_activeHost = nullptr;
|
||||
}
|
||||
if (_pressedHost == host) {
|
||||
if (_pressed) (*_pressed).clear();
|
||||
_pressedHost = nullptr;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -110,15 +110,22 @@ QString HiddenUrlClickHandler::getExpandedLinkText(ExpandLinksMode mode, const Q
|
|||
QString result;
|
||||
if (mode == ExpandLinksAll) {
|
||||
result = textPart.toString() + qsl(" (") + url() + ')';
|
||||
} else if (mode == ExpandLinksUrlOnly) {
|
||||
result = url();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
TextWithEntities HiddenUrlClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const {
|
||||
TextWithEntities result;
|
||||
result.entities.push_back({ EntityInTextCustomUrl, entityOffset, textPart.size(), url() });
|
||||
if (mode == ExpandLinksAll) {
|
||||
result.text = textPart.toString() + qsl(" (") + url() + ')';
|
||||
if (mode == ExpandLinksUrlOnly) {
|
||||
result.text = 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;
|
||||
}
|
||||
|
@ -174,9 +181,19 @@ TextWithEntities HashtagClickHandler::getExpandedLinkTextWithEntities(ExpandLink
|
|||
return simpleTextWithEntity({ EntityInTextHashtag, entityOffset, textPart.size() });
|
||||
}
|
||||
|
||||
PeerData *BotCommandClickHandler::_peer = nullptr;
|
||||
UserData *BotCommandClickHandler::_bot = nullptr;
|
||||
void BotCommandClickHandler::onClick(Qt::MouseButton button) const {
|
||||
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;
|
||||
if (auto item = App::hoveredLinkItem()) {
|
||||
if (!bot) {
|
||||
|
|
|
@ -193,6 +193,8 @@ private:
|
|||
|
||||
};
|
||||
|
||||
class PeerData;
|
||||
class UserData;
|
||||
class BotCommandClickHandler : public TextClickHandler {
|
||||
public:
|
||||
BotCommandClickHandler(const QString &cmd) : _cmd(cmd) {
|
||||
|
@ -204,14 +206,30 @@ public:
|
|||
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;
|
||||
|
||||
protected:
|
||||
QString url() const override {
|
||||
return _cmd;
|
||||
}
|
||||
static PeerData *peerForCommand() {
|
||||
return _peer;
|
||||
}
|
||||
static UserData *botForCommand() {
|
||||
return _bot;
|
||||
}
|
||||
|
||||
private:
|
||||
QString _cmd;
|
||||
|
||||
static PeerData *_peer;
|
||||
static UserData *_bot;
|
||||
|
||||
};
|
||||
|
|
115
Telegram/SourceFiles/core/observer.cpp
Normal 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
|
242
Telegram/SourceFiles/core/observer.h
Normal 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
|
153
Telegram/SourceFiles/core/vector_of_moveable.h
Normal 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_
|
|
@ -22,7 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
|
||||
#include "core/basic_types.h"
|
||||
|
||||
#define BETA_VERSION_MACRO (0ULL)
|
||||
#define BETA_VERSION_MACRO (9049001ULL)
|
||||
|
||||
constexpr int AppVersion = 9049;
|
||||
constexpr str_const AppVersionStr = "0.9.49";
|
||||
|
|
|
@ -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_delete_conversation), this, SLOT(onContextDeleteAndLeave()))->setEnabled(true);
|
||||
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*)));
|
||||
}
|
||||
} else if (_menuPeer->isChat()) {
|
||||
|
@ -662,7 +662,7 @@ bool DialogsInner::menuPeerMuted() {
|
|||
|
||||
void DialogsInner::onContextProfile() {
|
||||
if (!_menuPeer) return;
|
||||
App::main()->showPeerProfile(_menuPeer);
|
||||
Ui::showPeerProfile(_menuPeer);
|
||||
}
|
||||
|
||||
void DialogsInner::onContextToggleNotifications() {
|
||||
|
@ -718,7 +718,7 @@ void DialogsInner::onContextDeleteAndLeaveSure() {
|
|||
|
||||
void DialogsInner::onContextToggleBlock() {
|
||||
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)));
|
||||
} else {
|
||||
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) {
|
||||
data.first->blocked = data.second ? UserIsBlocked : UserIsNotBlocked;
|
||||
data.first->setBlockStatus(data.second ? UserData::BlockStatus::Blocked : UserData::BlockStatus::NotBlocked);
|
||||
emit App::main()->peerUpdated(data.first);
|
||||
}
|
||||
|
||||
|
@ -935,7 +935,7 @@ void DialogsInner::updateNotifySettings(PeerData *peer) {
|
|||
|
||||
void DialogsInner::peerUpdated(PeerData *peer) {
|
||||
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 ¶ms) {
|
||||
if (App::app()) App::app()->mtpPause();
|
||||
|
||||
bool back = true;
|
||||
(back ? _cacheOver : _cacheUnder) = bgAnimCache;
|
||||
_cacheUnder = params.oldContentCache;
|
||||
show();
|
||||
_cacheOver = App::main()->grabForShowAnimation(params);
|
||||
|
||||
_a_show.stop();
|
||||
|
||||
(back ? _cacheUnder : _cacheOver) = myGrab(this);
|
||||
|
||||
_scroll.hide();
|
||||
_filter.hide();
|
||||
_cancelSearch.hide();
|
||||
_newGroup.hide();
|
||||
|
||||
a_coordUnder = back ? anim::ivalue(-qFloor(st::slideShift * width()), 0) : anim::ivalue(0, -qFloor(st::slideShift * width()));
|
||||
a_coordOver = back ? anim::ivalue(0, width()) : anim::ivalue(width(), 0);
|
||||
a_shadow = back ? anim::fvalue(1, 0) : anim::fvalue(0, 1);
|
||||
int delta = st::slideShift;
|
||||
if (direction == Window::SlideDirection::FromLeft) {
|
||||
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();
|
||||
|
||||
show();
|
||||
}
|
||||
|
||||
void DialogsWidget::step_show(float64 ms, bool timer) {
|
||||
|
@ -1872,7 +1877,7 @@ void DialogsWidget::step_show(float64 ms, bool timer) {
|
|||
|
||||
a_coordUnder.finish();
|
||||
a_coordOver.finish();
|
||||
a_shadow.finish();
|
||||
a_progress.finish();
|
||||
|
||||
_cacheUnder = _cacheOver = QPixmap();
|
||||
|
||||
|
@ -1887,7 +1892,7 @@ void DialogsWidget::step_show(float64 ms, bool timer) {
|
|||
} else {
|
||||
a_coordUnder.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();
|
||||
}
|
||||
|
@ -2474,15 +2479,16 @@ void DialogsWidget::paintEvent(QPaintEvent *e) {
|
|||
p.setClipRect(r);
|
||||
}
|
||||
if (_a_show.animating()) {
|
||||
int retina = cIntRetinaFactor();
|
||||
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.setOpacity(a_shadow.current() * st::slideFadeOut);
|
||||
p.fillRect(0, 0, a_coordOver.current(), height(), st::black->b);
|
||||
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_progress.current() * st::slideFadeOut);
|
||||
p.fillRect(0, 0, a_coordOver.current(), _cacheUnder.height() / retina, st::black);
|
||||
p.setOpacity(1);
|
||||
}
|
||||
p.drawPixmap(a_coordOver.current(), 0, _cacheOver);
|
||||
p.setOpacity(a_shadow.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(), 0, _cacheOver.width() / retina, _cacheOver.height() / retina), _cacheOver, QRect(0, 0, _cacheOver.width(), _cacheOver.height()));
|
||||
p.setOpacity(a_progress.current());
|
||||
p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), _cacheOver.height() / retina), App::sprite(), st::slideShadow.rect());
|
||||
return;
|
||||
}
|
||||
QRect above(0, 0, width(), _scroll.y());
|
||||
|
|
|
@ -20,6 +20,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include "window/section_widget.h"
|
||||
|
||||
class MainWidget;
|
||||
namespace Dialogs {
|
||||
class Row;
|
||||
|
@ -260,7 +262,10 @@ public:
|
|||
|
||||
void dialogsToUp();
|
||||
|
||||
void animShow(const QPixmap &bgAnimCache);
|
||||
bool hasTopBarShadow() const {
|
||||
return true;
|
||||
}
|
||||
void showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams ¶ms);
|
||||
void step_show(float64 ms, bool timer);
|
||||
|
||||
void destroyData();
|
||||
|
@ -338,7 +343,7 @@ private:
|
|||
Animation _a_show;
|
||||
QPixmap _cacheUnder, _cacheOver;
|
||||
anim::ivalue a_coordUnder, a_coordOver;
|
||||
anim::fvalue a_shadow;
|
||||
anim::fvalue a_progress;
|
||||
|
||||
PeerData *_searchInPeer, *_searchInMigrated;
|
||||
|
||||
|
|
|
@ -20,10 +20,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
*/
|
||||
#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 "mainwidget.h"
|
||||
#include "application.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "boxes/confirmbox.h"
|
||||
#include "layerwidget.h"
|
||||
#include "lang.h"
|
||||
|
@ -237,7 +240,15 @@ void autoplayMediaInlineAsync(const FullMsgId &msgId) {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
@ -273,6 +284,15 @@ bool hideWindowNoQuit() {
|
|||
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 Notify {
|
||||
|
@ -286,7 +306,10 @@ void userIsContactChanged(UserData *user, bool fromThisApp) {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
@ -500,6 +523,8 @@ struct Data {
|
|||
uint64 LaunchId = 0;
|
||||
SingleDelayedCall HandleHistoryUpdate = { App::app(), "call_handleHistoryUpdate" };
|
||||
SingleDelayedCall HandleUnreadCounterUpdate = { App::app(), "call_handleUnreadCounterUpdate" };
|
||||
SingleDelayedCall HandleFileDialogQueue = { App::app(), "call_handleFileDialogQueue" };
|
||||
SingleDelayedCall HandleDelayedPeerUpdates = { App::app(), "call_handleDelayedPeerUpdates" };
|
||||
|
||||
Adaptive::Layout AdaptiveLayout = Adaptive::NormalLayout;
|
||||
bool AdaptiveForWide = true;
|
||||
|
@ -563,6 +588,8 @@ void finish() {
|
|||
DefineReadOnlyVar(Global, uint64, LaunchId);
|
||||
DefineRefVar(Global, SingleDelayedCall, HandleHistoryUpdate);
|
||||
DefineRefVar(Global, SingleDelayedCall, HandleUnreadCounterUpdate);
|
||||
DefineRefVar(Global, SingleDelayedCall, HandleFileDialogQueue);
|
||||
DefineRefVar(Global, SingleDelayedCall, HandleDelayedPeerUpdates);
|
||||
|
||||
DefineVar(Global, Adaptive::Layout, AdaptiveLayout);
|
||||
DefineVar(Global, bool, AdaptiveForWide);
|
||||
|
|
|
@ -75,6 +75,14 @@ inline void showPeerProfile(const History *history) {
|
|||
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);
|
||||
inline void showPeerHistory(const PeerData *peer, MsgId msgId, bool back = false) {
|
||||
showPeerHistory(peer->id, msgId, back);
|
||||
|
@ -96,6 +104,8 @@ PeerData *getPeerForMouseAction();
|
|||
|
||||
bool hideWindowNoQuit();
|
||||
|
||||
bool skipPaintEvent(QWidget *widget, QPaintEvent *event);
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
enum ClipStopperType {
|
||||
|
@ -194,6 +204,8 @@ void finish();
|
|||
DeclareReadOnlyVar(uint64, LaunchId);
|
||||
DeclareRefVar(SingleDelayedCall, HandleHistoryUpdate);
|
||||
DeclareRefVar(SingleDelayedCall, HandleUnreadCounterUpdate);
|
||||
DeclareRefVar(SingleDelayedCall, HandleFileDialogQueue);
|
||||
DeclareRefVar(SingleDelayedCall, HandleDelayedPeerUpdates);
|
||||
|
||||
DeclareVar(Adaptive::Layout, AdaptiveLayout);
|
||||
DeclareVar(bool, AdaptiveForWide);
|
||||
|
|
|
@ -36,6 +36,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "apiwrap.h"
|
||||
#include "window/top_bar_widget.h"
|
||||
#include "playerwidget.h"
|
||||
#include "observer_peer.h"
|
||||
|
||||
namespace {
|
||||
|
||||
|
@ -751,6 +752,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
|
|||
if (peer->asChannel()->mgInfo->lastParticipants.indexOf(user) < 0) {
|
||||
peer->asChannel()->mgInfo->lastParticipants.push_front(user);
|
||||
peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsAdminsOutdated;
|
||||
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged);
|
||||
}
|
||||
if (user->botInfo) {
|
||||
peer->asChannel()->mgInfo->bots.insert(user);
|
||||
|
@ -769,6 +771,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
|
|||
if (result->from()->isUser()) {
|
||||
if (peer->asChannel()->mgInfo->lastParticipants.indexOf(result->from()->asUser()) < 0) {
|
||||
peer->asChannel()->mgInfo->lastParticipants.push_front(result->from()->asUser());
|
||||
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged);
|
||||
}
|
||||
if (result->from()->asUser()->botInfo) {
|
||||
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 (peer->isMegagroup()) {
|
||||
if (UserData *user = App::userLoaded(uid)) {
|
||||
int32 index = peer->asChannel()->mgInfo->lastParticipants.indexOf(user);
|
||||
if (auto user = App::userLoaded(uid)) {
|
||||
auto channel = peer->asChannel();
|
||||
auto megagroupInfo = channel->mgInfo;
|
||||
|
||||
int32 index = megagroupInfo->lastParticipants.indexOf(user);
|
||||
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) {
|
||||
--peer->asChannel()->count;
|
||||
if (peer->asChannel()->membersCount() > 1) {
|
||||
peer->asChannel()->setMembersCount(channel->membersCount() - 1);
|
||||
} else {
|
||||
peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated;
|
||||
peer->asChannel()->mgInfo->lastParticipantsCount = 0;
|
||||
megagroupInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated;
|
||||
megagroupInfo->lastParticipantsCount = 0;
|
||||
}
|
||||
if (peer->asChannel()->mgInfo->lastAdmins.contains(user)) {
|
||||
peer->asChannel()->mgInfo->lastAdmins.remove(user);
|
||||
if (peer->asChannel()->adminsCount > 1) {
|
||||
--peer->asChannel()->adminsCount;
|
||||
if (megagroupInfo->lastAdmins.contains(user)) {
|
||||
megagroupInfo->lastAdmins.remove(user);
|
||||
if (channel->adminsCount() > 1) {
|
||||
channel->setAdminsCount(channel->adminsCount() - 1);
|
||||
}
|
||||
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::AdminsChanged);
|
||||
}
|
||||
peer->asChannel()->mgInfo->bots.remove(user);
|
||||
if (peer->asChannel()->mgInfo->bots.isEmpty() && peer->asChannel()->mgInfo->botStatus > 0) {
|
||||
peer->asChannel()->mgInfo->botStatus = -1;
|
||||
megagroupInfo->bots.remove(user);
|
||||
if (megagroupInfo->bots.isEmpty() && megagroupInfo->botStatus > 0) {
|
||||
megagroupInfo->botStatus = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -848,9 +856,10 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
|
|||
} break;
|
||||
|
||||
case mtpc_messageActionChatEditTitle: {
|
||||
const auto &d(action.c_messageActionChatEditTitle());
|
||||
ChatData *chat = peer->asChat();
|
||||
if (chat) chat->updateName(qs(d.vtitle), QString(), QString());
|
||||
auto &d(action.c_messageActionChatEditTitle());
|
||||
if (auto chat = peer->asChat()) {
|
||||
chat->setName(qs(d.vtitle));
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_messageActionChatMigrateTo: {
|
||||
|
@ -1019,6 +1028,9 @@ HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) {
|
|||
if (prev) {
|
||||
lastAuthors->push_front(adding->from()->asUser());
|
||||
}
|
||||
if (peer->isMegagroup()) {
|
||||
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (adding->definesReplyKeyboard()) {
|
||||
|
@ -1194,6 +1206,7 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
|
|||
lastAuthors->push_back(item->from()->asUser());
|
||||
if (peer->isMegagroup()) {
|
||||
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)) {
|
||||
clear(true);
|
||||
|
||||
if (msgId == ShowAtTheEndMsgId) {
|
||||
newLoaded = true;
|
||||
}
|
||||
|
@ -2847,14 +2861,14 @@ HistoryItem::~HistoryItem() {
|
|||
}
|
||||
}
|
||||
|
||||
RadialAnimation::RadialAnimation(AnimationCreator creator)
|
||||
RadialAnimation::RadialAnimation(AnimationCallbacks &&callbacks)
|
||||
: _firstStart(0)
|
||||
, _lastStart(0)
|
||||
, _lastTime(0)
|
||||
, _opacity(0)
|
||||
, a_arcEnd(0, 0)
|
||||
, a_arcStart(0, FullArcLength)
|
||||
, _animation(creator) {
|
||||
, _animation(std_::move(callbacks)) {
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1590,7 +1590,7 @@ protected:
|
|||
class RadialAnimation {
|
||||
public:
|
||||
|
||||
RadialAnimation(AnimationCreator creator);
|
||||
RadialAnimation(AnimationCallbacks &&callbacks);
|
||||
|
||||
float64 opacity() const {
|
||||
return _opacity;
|
||||
|
@ -1811,9 +1811,9 @@ protected:
|
|||
virtual bool dataLoaded() const = 0;
|
||||
|
||||
struct AnimationData {
|
||||
AnimationData(AnimationCreator thumbOverCallbacks, AnimationCreator radialCallbacks) : a_thumbOver(0, 0)
|
||||
, _a_thumbOver(thumbOverCallbacks)
|
||||
, radial(radialCallbacks) {
|
||||
AnimationData(AnimationCallbacks &&thumbOverCallbacks, AnimationCallbacks &&radialCallbacks) : a_thumbOver(0, 0)
|
||||
, _a_thumbOver(std_::move(thumbOverCallbacks))
|
||||
, radial(std_::move(radialCallbacks)) {
|
||||
}
|
||||
anim::fvalue a_thumbOver;
|
||||
Animation _a_thumbOver;
|
||||
|
|
|
@ -39,6 +39,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "localstorage.h"
|
||||
#include "apiwrap.h"
|
||||
#include "window/top_bar_widget.h"
|
||||
#include "observer_peer.h"
|
||||
#include "playerwidget.h"
|
||||
|
||||
namespace {
|
||||
|
@ -658,7 +659,7 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt
|
|||
_selected.insert(_dragItem, selStatus);
|
||||
_dragSymbol = dragState.symbol;
|
||||
_dragAction = Selecting;
|
||||
_dragSelType = TextSelectParagraphs;
|
||||
_dragSelType = TextSelectType::Paragraphs;
|
||||
dragActionUpdate(_dragPos);
|
||||
_trippleClickTimer.start(QApplication::doubleClickInterval());
|
||||
}
|
||||
|
@ -668,7 +669,7 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt
|
|||
request.flags = Text::StateRequest::Flag::LookupSymbol;
|
||||
dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request);
|
||||
}
|
||||
if (_dragSelType != TextSelectParagraphs) {
|
||||
if (_dragSelType != TextSelectType::Paragraphs) {
|
||||
if (App::pressedItem()) {
|
||||
_dragSymbol = dragState.symbol;
|
||||
bool uponSelected = (dragState.cursor == HistoryInTextCursorState);
|
||||
|
@ -715,7 +716,7 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt
|
|||
if (!_dragItem) {
|
||||
_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");
|
||||
}
|
||||
drag->setMimeData(mimeData);
|
||||
|
||||
// We don't receive mouseReleaseEvent when drag is finished.
|
||||
ClickHandler::unpressed();
|
||||
drag->exec(Qt::CopyAction);
|
||||
if (App::main()) App::main()->updateAfterDrag();
|
||||
return;
|
||||
|
@ -810,6 +814,9 @@ void HistoryInner::onDragExec() {
|
|||
}
|
||||
|
||||
drag->setMimeData(mimeData);
|
||||
|
||||
// We don't receive mouseReleaseEvent when drag is finished.
|
||||
ClickHandler::unpressed();
|
||||
drag->exec(Qt::CopyAction);
|
||||
if (App::main()) App::main()->updateAfterDrag();
|
||||
return;
|
||||
|
@ -906,7 +913,7 @@ void HistoryInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton but
|
|||
}
|
||||
_dragAction = NoDrag;
|
||||
_dragItem = 0;
|
||||
_dragSelType = TextSelectLetters;
|
||||
_dragSelType = TextSelectType::Letters;
|
||||
_widget->noSelectingScroll();
|
||||
_widget->updateTopBarSelection();
|
||||
}
|
||||
|
@ -922,13 +929,13 @@ void HistoryInner::mouseDoubleClickEvent(QMouseEvent *e) {
|
|||
if (!_history) return;
|
||||
|
||||
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;
|
||||
request.flags |= Text::StateRequest::Flag::LookupSymbol;
|
||||
auto dragState = _dragItem->getState(_dragStartPos.x(), _dragStartPos.y(), request);
|
||||
if (dragState.cursor == HistoryInTextCursorState) {
|
||||
_dragSymbol = dragState.symbol;
|
||||
_dragSelType = TextSelectWords;
|
||||
_dragSelType = TextSelectType::Words;
|
||||
if (_dragAction == NoDrag) {
|
||||
_dragAction = Selecting;
|
||||
TextSelection selStatus = { dragState.symbol, dragState.symbol };
|
||||
|
@ -1787,7 +1794,7 @@ void HistoryInner::onUpdateSelected() {
|
|||
bool canSelectMany = (_history != nullptr);
|
||||
if (selectingText) {
|
||||
uint16 second = dragState.symbol;
|
||||
if (dragState.afterSymbol && _dragSelType == TextSelectLetters) {
|
||||
if (dragState.afterSymbol && _dragSelType == TextSelectType::Letters) {
|
||||
++second;
|
||||
}
|
||||
auto selState = _dragItem->adjustSelection({ qMin(second, _dragSymbol), qMax(second, _dragSymbol) }, _dragSelType);
|
||||
|
@ -2816,7 +2823,6 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent)
|
|||
, _attachDragPhoto(this)
|
||||
, _fileLoader(this, FileLoaderQueueStopTimeout)
|
||||
, _a_show(animation(this, &HistoryWidget::step_show))
|
||||
, _sideShadow(this, st::shadowColor)
|
||||
, _topShadow(this, st::shadowColor) {
|
||||
_scroll.setFocusPolicy(Qt::NoFocus);
|
||||
|
||||
|
@ -2937,7 +2943,6 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent)
|
|||
_attachDragPhoto.hide();
|
||||
|
||||
_topShadow.hide();
|
||||
_sideShadow.setVisible(!Adaptive::OneColumn());
|
||||
|
||||
connect(&_attachDragDocument, SIGNAL(dropped(const QMimeData*)), this, SLOT(onDocumentDrop(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) {
|
||||
updateNotifySettings();
|
||||
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() {
|
||||
_topShadow.setVisible(_peer ? true : false);
|
||||
if (!_a_show.animating()) {
|
||||
_topShadow.setVisible(_peer ? true : false);
|
||||
}
|
||||
if (!_history || _a_show.animating()) {
|
||||
_reportSpamPanel.hide();
|
||||
_scroll.hide();
|
||||
|
@ -4455,6 +4462,7 @@ void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages
|
|||
} else if (_migrated) {
|
||||
_migrated->clear(true);
|
||||
}
|
||||
|
||||
_delayedShowAtRequest = 0;
|
||||
_history->getReadyFor(_delayedShowAtMsgId);
|
||||
if (_history->isEmpty()) {
|
||||
|
@ -4822,7 +4830,7 @@ void HistoryWidget::onSend(bool ctrlShiftEnter, MsgId replyTo) {
|
|||
|
||||
void HistoryWidget::onUnblock() {
|
||||
if (_unblockRequest) return;
|
||||
if (!_peer || !_peer->isUser() || _peer->asUser()->blocked != UserIsBlocked) {
|
||||
if (!_peer || !_peer->isUser() || !_peer->asUser()->isBlocked()) {
|
||||
updateControlsVisibility();
|
||||
return;
|
||||
}
|
||||
|
@ -4833,7 +4841,7 @@ void HistoryWidget::onUnblock() {
|
|||
void HistoryWidget::unblockDone(PeerData *peer, const MTPBool &result, mtpRequestId req) {
|
||||
if (!peer->isUser()) return;
|
||||
if (_unblockRequest == req) _unblockRequest = 0;
|
||||
peer->asUser()->blocked = UserIsNotBlocked;
|
||||
peer->asUser()->setBlockStatus(UserData::BlockStatus::NotBlocked);
|
||||
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) {
|
||||
if (!peer->isUser()) return;
|
||||
|
||||
peer->asUser()->blocked = UserIsBlocked;
|
||||
peer->asUser()->setBlockStatus(UserData::BlockStatus::Blocked);
|
||||
emit App::main()->peerUpdated(peer);
|
||||
}
|
||||
|
||||
|
@ -4911,12 +4919,14 @@ void HistoryWidget::onBroadcastSilentChange() {
|
|||
}
|
||||
|
||||
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);
|
||||
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) {
|
||||
|
@ -4993,15 +5003,15 @@ MsgId HistoryWidget::msgId() const {
|
|||
return _showAtMsgId;
|
||||
}
|
||||
|
||||
void HistoryWidget::animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTopBarCache, bool back) {
|
||||
void HistoryWidget::showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams ¶ms) {
|
||||
if (App::app()) App::app()->mtpPause();
|
||||
|
||||
(back ? _cacheOver : _cacheUnder) = bgAnimCache;
|
||||
(back ? _cacheTopBarOver : _cacheTopBarUnder) = bgAnimTopBarCache;
|
||||
(back ? _cacheUnder : _cacheOver) = myGrab(this);
|
||||
App::main()->topBar()->stopAnim();
|
||||
(back ? _cacheTopBarUnder : _cacheTopBarOver) = myGrab(App::main()->topBar());
|
||||
_cacheUnder = params.oldContentCache;
|
||||
show();
|
||||
_topShadow.setVisible(params.withTopBarShadow ? false : true);
|
||||
_cacheOver = App::main()->grabForShowAnimation(params);
|
||||
App::main()->topBar()->startAnim();
|
||||
_topShadow.setVisible(params.withTopBarShadow ? true : false);
|
||||
|
||||
_scroll.hide();
|
||||
_kbScroll.hide();
|
||||
|
@ -5023,18 +5033,26 @@ void HistoryWidget::animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTo
|
|||
_botStart.hide();
|
||||
_joinChannel.hide();
|
||||
_muteUnmute.hide();
|
||||
_topShadow.hide();
|
||||
if (_pinnedBar) {
|
||||
_pinnedBar->shadow.hide();
|
||||
_pinnedBar->cancel.hide();
|
||||
}
|
||||
|
||||
a_coordUnder = back ? anim::ivalue(-qFloor(st::slideShift * width()), 0) : anim::ivalue(0, -qFloor(st::slideShift * width()));
|
||||
a_coordOver = back ? anim::ivalue(0, width()) : anim::ivalue(width(), 0);
|
||||
a_shadow = back ? anim::fvalue(1, 0) : anim::fvalue(0, 1);
|
||||
int delta = st::slideShift;
|
||||
if (direction == Window::SlideDirection::FromLeft) {
|
||||
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();
|
||||
|
||||
App::main()->topBar()->update();
|
||||
|
||||
activate();
|
||||
}
|
||||
|
||||
|
@ -5042,13 +5060,12 @@ void HistoryWidget::step_show(float64 ms, bool timer) {
|
|||
float64 dt = ms / st::slideDuration;
|
||||
if (dt >= 1) {
|
||||
_a_show.stop();
|
||||
_sideShadow.setVisible(!Adaptive::OneColumn());
|
||||
_topShadow.setVisible(_peer ? true : false);
|
||||
|
||||
a_coordUnder.finish();
|
||||
a_coordOver.finish();
|
||||
a_shadow.finish();
|
||||
_cacheUnder = _cacheOver = _cacheTopBarUnder = _cacheTopBarOver = QPixmap();
|
||||
a_progress.finish();
|
||||
_cacheUnder = _cacheOver = QPixmap();
|
||||
App::main()->topBar()->stopAnim();
|
||||
doneShow();
|
||||
|
||||
|
@ -5056,7 +5073,7 @@ void HistoryWidget::step_show(float64 ms, bool timer) {
|
|||
} else {
|
||||
a_coordUnder.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();
|
||||
|
@ -5081,14 +5098,12 @@ void HistoryWidget::doneShow() {
|
|||
}
|
||||
|
||||
void HistoryWidget::updateAdaptiveLayout() {
|
||||
_sideShadow.setVisible(!Adaptive::OneColumn());
|
||||
update();
|
||||
}
|
||||
|
||||
void HistoryWidget::animStop() {
|
||||
if (!_a_show.animating()) return;
|
||||
_a_show.stop();
|
||||
_sideShadow.setVisible(!Adaptive::OneColumn());
|
||||
_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) {
|
||||
if (!i->isLocalFile()) return DragStateNone;
|
||||
|
||||
QString file(i->toLocalFile());
|
||||
if (file.startsWith(qsl("/.file/id="))) file = psConvertFileUrl(file);
|
||||
auto file = psConvertFileUrl(*i);
|
||||
|
||||
QFileInfo info(file);
|
||||
if (info.isDir()) return DragStateNone;
|
||||
|
@ -5569,7 +5583,7 @@ bool HistoryWidget::isBotStart() const {
|
|||
}
|
||||
|
||||
bool HistoryWidget::isBlocked() const {
|
||||
return _peer && _peer->isUser() && _peer->asUser()->blocked == UserIsBlocked;
|
||||
return _peer && _peer->isUser() && _peer->asUser()->isBlocked();
|
||||
}
|
||||
|
||||
bool HistoryWidget::isJoinChannel() const {
|
||||
|
@ -5769,9 +5783,15 @@ void HistoryWidget::onForwardHere() {
|
|||
|
||||
void HistoryWidget::paintTopBar(Painter &p, float64 over, int32 decreaseWidth) {
|
||||
if (_a_show.animating()) {
|
||||
p.drawPixmap(a_coordUnder.current(), 0, _cacheTopBarUnder);
|
||||
p.drawPixmap(a_coordOver.current(), 0, _cacheTopBarOver);
|
||||
p.setOpacity(a_shadow.current());
|
||||
int retina = cIntRetinaFactor();
|
||||
if (a_coordOver.current() > 0) {
|
||||
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());
|
||||
return;
|
||||
}
|
||||
|
@ -5806,7 +5826,7 @@ void HistoryWidget::topBarClick() {
|
|||
if (Adaptive::OneColumn()) {
|
||||
Ui::showChatsList();
|
||||
} 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()) {
|
||||
if (_peer->isMegagroup() && _peer->asChannel()->count > 0 && _peer->asChannel()->count <= Global::ChatSizeMax()) {
|
||||
if (_peer->asChannel()->mgInfo->lastParticipants.size() < _peer->asChannel()->count || _peer->asChannel()->lastParticipantsCountOutdated()) {
|
||||
if (_peer->isMegagroup() && _peer->asChannel()->membersCount() > 0 && _peer->asChannel()->membersCount() <= Global::ChatSizeMax()) {
|
||||
if (_peer->asChannel()->mgInfo->lastParticipants.size() < _peer->asChannel()->membersCount() || _peer->asChannel()->lastParticipantsCountOutdated()) {
|
||||
if (App::api()) App::api()->requestLastParticipants(_peer->asChannel());
|
||||
}
|
||||
int32 onlineCount = 0;
|
||||
|
@ -5852,12 +5872,12 @@ void HistoryWidget::updateOnlineDisplay(int32 x, int32 w) {
|
|||
}
|
||||
}
|
||||
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 {
|
||||
text = lng_chat_status_members(lt_count, _peer->asChannel()->count);
|
||||
text = lng_chat_status_members(lt_count, _peer->asChannel()->membersCount());
|
||||
}
|
||||
} 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) {
|
||||
|
@ -6494,8 +6514,6 @@ void HistoryWidget::resizeEvent(QResizeEvent *e) {
|
|||
|
||||
_topShadow.resize(width() - ((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0), st::lineWidth);
|
||||
_topShadow.moveToLeft((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0, 0);
|
||||
_sideShadow.resize(st::lineWidth, height());
|
||||
_sideShadow.moveToLeft(0, 0);
|
||||
}
|
||||
|
||||
void HistoryWidget::itemRemoved(HistoryItem *item) {
|
||||
|
@ -6671,7 +6689,8 @@ void HistoryWidget::updateListSize(bool initial, bool loadedDown, const ScrollCh
|
|||
}
|
||||
} else {
|
||||
}
|
||||
if (toY > _scroll.scrollTopMax()) toY = _scroll.scrollTopMax();
|
||||
auto scrollMax = _scroll.scrollTopMax();
|
||||
accumulate_min(toY, scrollMax);
|
||||
if (_scroll.scrollTop() == toY) {
|
||||
visibleAreaUpdated();
|
||||
} else {
|
||||
|
@ -7041,7 +7060,6 @@ bool HistoryWidget::pinnedMsgVisibilityUpdated() {
|
|||
}
|
||||
connect(&_pinnedBar->cancel, SIGNAL(clicked()), this, SLOT(onPinnedHide()));
|
||||
_reportSpamPanel.raise();
|
||||
_sideShadow.raise();
|
||||
_topShadow.raise();
|
||||
updatePinnedBar();
|
||||
result = true;
|
||||
|
@ -7679,7 +7697,7 @@ void HistoryWidget::peerUpdated(PeerData *data) {
|
|||
if (App::api()) {
|
||||
if (data->isChat() && data->asChat()->noParticipantInfo()) {
|
||||
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);
|
||||
} else if (data->isMegagroup() && !data->asChannel()->mgInfo->botStatus) {
|
||||
App::api()->requestBots(data->asChannel());
|
||||
|
@ -7723,6 +7741,7 @@ void HistoryWidget::onDeleteSelected() {
|
|||
}
|
||||
|
||||
void HistoryWidget::onDeleteSelectedSure() {
|
||||
Ui::hideLayer();
|
||||
if (!_list) return;
|
||||
|
||||
SelectedItemSet sel;
|
||||
|
@ -7740,7 +7759,6 @@ void HistoryWidget::onDeleteSelectedSure() {
|
|||
for (SelectedItemSet::const_iterator i = sel.cbegin(), e = sel.cend(); i != e; ++i) {
|
||||
i.value()->destroy();
|
||||
}
|
||||
Ui::hideLayer();
|
||||
|
||||
for (QMap<PeerData*, QVector<MTPint> >::const_iterator i = ids.cbegin(), e = ids.cend(); i != e; ++i) {
|
||||
App::main()->deleteMessages(i.key(), i.value());
|
||||
|
@ -7748,6 +7766,8 @@ void HistoryWidget::onDeleteSelectedSure() {
|
|||
}
|
||||
|
||||
void HistoryWidget::onDeleteContextSure() {
|
||||
Ui::hideLayer();
|
||||
|
||||
HistoryItem *item = App::contextItem();
|
||||
if (!item || item->type() != HistoryItemMsg) {
|
||||
return;
|
||||
|
@ -7757,12 +7777,11 @@ void HistoryWidget::onDeleteContextSure() {
|
|||
History *h = item->history();
|
||||
bool wasOnServer = (item->id > 0), wasLast = (h->lastMsg == item);
|
||||
item->destroy();
|
||||
|
||||
if (!wasOnServer && wasLast && !h->lastMsg) {
|
||||
App::main()->checkPeerHistory(h->peer);
|
||||
}
|
||||
|
||||
Ui::hideLayer();
|
||||
|
||||
if (wasOnServer) {
|
||||
App::main()->deleteMessages(h->peer, toDelete);
|
||||
}
|
||||
|
@ -8117,20 +8136,23 @@ void HistoryWidget::paintEvent(QPaintEvent *e) {
|
|||
if (r != rect()) {
|
||||
p.setClipRect(r);
|
||||
}
|
||||
bool hasTopBar = !App::main()->topBar()->isHidden(), hasPlayer = !App::main()->player()->isHidden();
|
||||
|
||||
if (_a_show.animating()) {
|
||||
int retina = cIntRetinaFactor();
|
||||
int inCacheTop = hasTopBar ? st::topBarHeight : 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.setOpacity(a_shadow.current() * st::slideFadeOut);
|
||||
p.fillRect(0, 0, a_coordOver.current(), height(), st::black->b);
|
||||
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_progress.current() * st::slideFadeOut);
|
||||
p.fillRect(0, 0, a_coordOver.current(), height(), st::black);
|
||||
p.setOpacity(1);
|
||||
}
|
||||
p.drawPixmap(a_coordOver.current(), 0, _cacheOver);
|
||||
p.setOpacity(a_shadow.current());
|
||||
p.drawPixmap(QRect(a_coordOver.current(), 0, _cacheOver.width() / retina, height()), _cacheOver, QRect(0, inCacheTop * retina, _cacheOver.width(), height() * retina));
|
||||
p.setOpacity(a_progress.current());
|
||||
p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), height()), App::sprite(), st::slideShadow.rect());
|
||||
return;
|
||||
}
|
||||
|
||||
bool hasTopBar = !App::main()->topBar()->isHidden(), hasPlayer = !App::main()->player()->isHidden();
|
||||
QRect fill(0, 0, width(), App::main()->height());
|
||||
int fromy = (hasTopBar ? (-st::topBarHeight) : 0) + (hasPlayer ? (-st::playerHeight) : 0), x = 0, y = 0;
|
||||
QPixmap cached = App::main()->cachedBackground(fill, x, y);
|
||||
|
@ -8208,8 +8230,7 @@ QStringList HistoryWidget::getMediasFromMime(const QMimeData *d) {
|
|||
for (QList<QUrl>::const_iterator i = urls.cbegin(), en = urls.cend(); i != en; ++i) {
|
||||
if (!i->isLocalFile()) return QStringList();
|
||||
|
||||
QString file(i->toLocalFile());
|
||||
if (file.startsWith(qsl("/.file/id="))) file = psConvertFileUrl(file);
|
||||
auto file = psConvertFileUrl(*i);
|
||||
|
||||
QFileInfo info(file);
|
||||
uint64 s = info.size();
|
||||
|
|
|
@ -25,6 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "dropdown.h"
|
||||
#include "history/history_common.h"
|
||||
#include "history/field_autocomplete.h"
|
||||
#include "window/section_widget.h"
|
||||
|
||||
namespace InlineBots {
|
||||
namespace Layout {
|
||||
|
@ -205,7 +206,7 @@ private:
|
|||
Selecting = 0x04,
|
||||
};
|
||||
DragAction _dragAction = NoDrag;
|
||||
TextSelectType _dragSelType = TextSelectLetters;
|
||||
TextSelectType _dragSelType = TextSelectType::Letters;
|
||||
QPoint _dragStartPos, _dragPos;
|
||||
HistoryItem *_dragItem = nullptr;
|
||||
HistoryCursorState _dragCursorState = HistoryDefaultCursorState;
|
||||
|
@ -570,7 +571,10 @@ public:
|
|||
void setMsgId(MsgId showAtMsgId);
|
||||
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 ¶ms);
|
||||
void step_show(float64 ms, bool timer);
|
||||
void animStop();
|
||||
|
||||
|
@ -652,14 +656,17 @@ public:
|
|||
bool contentOverlapped(const QRect &globalRect);
|
||||
|
||||
void grabStart() override {
|
||||
_sideShadow.hide();
|
||||
_inGrab = true;
|
||||
resizeEvent(0);
|
||||
}
|
||||
void grapWithoutTopBarShadow() {
|
||||
grabStart();
|
||||
_topShadow.hide();
|
||||
}
|
||||
void grabFinish() override {
|
||||
_sideShadow.setVisible(!Adaptive::OneColumn());
|
||||
_inGrab = false;
|
||||
resizeEvent(0);
|
||||
_topShadow.show();
|
||||
}
|
||||
|
||||
bool isItemVisible(HistoryItem *item);
|
||||
|
@ -1077,9 +1084,9 @@ private:
|
|||
int32 _titlePeerTextWidth = 0;
|
||||
|
||||
Animation _a_show;
|
||||
QPixmap _cacheUnder, _cacheOver, _cacheTopBarUnder, _cacheTopBarOver;
|
||||
QPixmap _cacheUnder, _cacheOver;
|
||||
anim::ivalue a_coordUnder, a_coordOver;
|
||||
anim::fvalue a_shadow;
|
||||
anim::fvalue a_progress;
|
||||
|
||||
QTimer _scrollTimer;
|
||||
int32 _scrollDelta = 0;
|
||||
|
@ -1094,7 +1101,7 @@ private:
|
|||
bool _saveDraftText = false;
|
||||
QTimer _saveDraftTimer, _saveCloudDraftTimer;
|
||||
|
||||
PlainShadow _sideShadow, _topShadow;
|
||||
PlainShadow _topShadow;
|
||||
bool _inGrab = false;
|
||||
|
||||
};
|
||||
|
|
|
@ -216,9 +216,8 @@ void Gif::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
|
|||
if (_delete && p == _delete) {
|
||||
bool wasactive = (_state & StateFlag::DeleteOver);
|
||||
if (active != wasactive) {
|
||||
float64 from = active ? 0 : 1, to = active ? 1 : 0;
|
||||
EnsureAnimation(_a_deleteOver, from, func(this, &Gif::update));
|
||||
_a_deleteOver.start(to, st::stickersRowDuration);
|
||||
auto from = active ? 0. : 1., to = active ? 1. : 0.;
|
||||
START_ANIMATION(_a_deleteOver, func(this, &Gif::update), from, to, st::stickersRowDuration, anim::linear);
|
||||
if (active) {
|
||||
_state |= StateFlag::DeleteOver;
|
||||
} else {
|
||||
|
@ -231,9 +230,8 @@ void Gif::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
|
|||
if (active != wasactive) {
|
||||
if (!getShownDocument()->loaded()) {
|
||||
ensureAnimation();
|
||||
float64 from = active ? 0 : 1, to = active ? 1 : 0;
|
||||
EnsureAnimation(_animation->_a_over, from, func(this, &Gif::update));
|
||||
_animation->_a_over.start(to, st::stickersRowDuration);
|
||||
auto from = active ? 0. : 1., to = active ? 1. : 0.;
|
||||
START_ANIMATION(_animation->_a_over, func(this, &Gif::update), from, to, st::stickersRowDuration, anim::linear);
|
||||
}
|
||||
if (active) {
|
||||
_state |= StateFlag::Over;
|
||||
|
@ -413,9 +411,8 @@ void Sticker::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
|
|||
if (active != _active) {
|
||||
_active = active;
|
||||
|
||||
float64 from = _active ? 0 : 1, to = _active ? 1 : 0;
|
||||
EnsureAnimation(_a_over, from, func(this, &Sticker::update));
|
||||
_a_over.start(to, st::stickersRowDuration);
|
||||
auto from = active ? 0. : 1., to = active ? 1. : 0.;
|
||||
START_ANIMATION(_a_over, func(this, &Sticker::update), from, to, st::stickersRowDuration, anim::linear);
|
||||
}
|
||||
}
|
||||
ItemBase::clickHandlerActiveChanged(p, active);
|
||||
|
|
|
@ -107,9 +107,9 @@ private:
|
|||
void clipCallback(ClipReaderNotification notification);
|
||||
|
||||
struct AnimationData {
|
||||
AnimationData(AnimationCreator creator)
|
||||
AnimationData(AnimationCallbacks &&callbacks)
|
||||
: over(false)
|
||||
, radial(creator) {
|
||||
, radial(std_::move(callbacks)) {
|
||||
}
|
||||
bool over;
|
||||
FloatAnimation _a_over;
|
||||
|
@ -268,9 +268,9 @@ private:
|
|||
}
|
||||
|
||||
struct AnimationData {
|
||||
AnimationData(AnimationCreator thumbOverCallbacks, AnimationCreator radialCallbacks) : a_thumbOver(0, 0)
|
||||
, _a_thumbOver(thumbOverCallbacks)
|
||||
, radial(radialCallbacks) {
|
||||
AnimationData(AnimationCallbacks &&thumbOverCallbacks, AnimationCallbacks &&radialCallbacks) : a_thumbOver(0, 0)
|
||||
, _a_thumbOver(std_::move(thumbOverCallbacks))
|
||||
, radial(std_::move(radialCallbacks)) {
|
||||
}
|
||||
anim::fvalue a_thumbOver;
|
||||
Animation _a_thumbOver;
|
||||
|
|
|
@ -50,7 +50,7 @@ IntroPhone::IntroPhone(IntroWidget *parent) : IntroStep(parent)
|
|||
, country(this, st::introCountry)
|
||||
, phone(this, st::inpIntroPhone)
|
||||
, code(this, st::inpIntroCountryCode)
|
||||
, _signup(this, lng_phone_notreg(lt_signup_start, textcmdStartLink(1), lt_signup_end, textcmdStopLink()), st::introErrLabel, st::introErrLabelTextStyle)
|
||||
, _signup(this, lng_phone_notreg(lt_signup_start, textcmdStartLink(1), lt_signup_end, textcmdStopLink()), FlatLabel::InitType::Rich, st::introErrLabel, st::introErrLabelTextStyle)
|
||||
, _showSignup(false)
|
||||
, sentRequest(0) {
|
||||
setVisible(false);
|
||||
|
|
|
@ -20,9 +20,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include <QtWidgets/QWidget>
|
||||
#include "ui/flatbutton.h"
|
||||
#include "ui/countryinput.h"
|
||||
#include "ui/flatlabel.h"
|
||||
#include "intro/introwidget.h"
|
||||
|
||||
class IntroPhone final : public IntroStep {
|
||||
|
|
|
@ -27,7 +27,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "langloaderplain.h"
|
||||
|
||||
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())
|
||||
, _next(this, lang(lng_start_msgs), st::btnIntroNext) {
|
||||
_changeLang.hide();
|
||||
|
|
|
@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#pragma once
|
||||
|
||||
#include "intro/introwidget.h"
|
||||
#include "ui/flatlabel.h"
|
||||
|
||||
class IntroStart final : public IntroStep {
|
||||
public:
|
||||
|
|
|
@ -180,7 +180,7 @@ void IntroWidget::animShow(const QPixmap &bgAnimCache, bool back) {
|
|||
step()->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_shadow = back ? anim::fvalue(1, 0) : anim::fvalue(0, 1);
|
||||
_a_show.start();
|
||||
|
|
|
@ -25,6 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
|
||||
#include "serialize/serialize_document.h"
|
||||
#include "serialize/serialize_common.h"
|
||||
#include "observer_peer.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "lang.h"
|
||||
|
@ -3412,7 +3413,7 @@ namespace Local {
|
|||
UserData *user = peer->asUser();
|
||||
|
||||
// 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
|
||||
if (AppVersion >= 9012) {
|
||||
|
@ -3424,13 +3425,13 @@ namespace Local {
|
|||
} else if (peer->isChat()) {
|
||||
ChatData *chat = peer->asChat();
|
||||
|
||||
// name + count + date + version + admin + forbidden + left + invitationUrl
|
||||
result += Serialize::stringSize(chat->name) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + Serialize::stringSize(chat->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->inviteLink());
|
||||
} else if (peer->isChannel()) {
|
||||
ChannelData *channel = peer->asChannel();
|
||||
|
||||
// name + access + date + version + forbidden + flags + invitationUrl
|
||||
result += Serialize::stringSize(channel->name) + sizeof(quint64) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + Serialize::stringSize(channel->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->inviteLink());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -3441,7 +3442,7 @@ namespace Local {
|
|||
if (peer->isUser()) {
|
||||
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) {
|
||||
stream << qint32(user->flags);
|
||||
}
|
||||
|
@ -3455,12 +3456,12 @@ namespace Local {
|
|||
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 << qint32(chat->isForbidden ? 1 : 0) << qint32(flagsData) << chat->invitationUrl;
|
||||
stream << qint32(chat->isForbidden ? 1 : 0) << qint32(flagsData) << chat->inviteLink();
|
||||
} else if (peer->isChannel()) {
|
||||
ChannelData *channel = peer->asChannel();
|
||||
|
||||
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();
|
||||
|
||||
if (!wasLoaded) {
|
||||
user->setPhone(phone);
|
||||
user->setName(first, last, pname, username);
|
||||
|
||||
user->access = access;
|
||||
|
@ -3519,9 +3521,9 @@ namespace Local {
|
|||
} else if (result->isChat()) {
|
||||
ChatData *chat = result->asChat();
|
||||
|
||||
QString name, invitationUrl;
|
||||
QString name, inviteLink;
|
||||
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) {
|
||||
flags = flagsData;
|
||||
|
@ -3530,14 +3532,14 @@ namespace Local {
|
|||
flags = (flagsData == 1) ? MTPDchat::Flags(MTPDchat::Flag::f_left) : MTPDchat::Flags(0);
|
||||
}
|
||||
if (!wasLoaded) {
|
||||
chat->updateName(name, QString(), QString());
|
||||
chat->setName(name);
|
||||
chat->count = count;
|
||||
chat->date = date;
|
||||
chat->version = version;
|
||||
chat->creator = creator;
|
||||
chat->isForbidden = (forbidden == 1);
|
||||
chat->flags = MTPDchat::Flags(flags);
|
||||
chat->invitationUrl = invitationUrl;
|
||||
chat->setInviteLink(inviteLink);
|
||||
|
||||
chat->input = MTP_inputPeerChat(MTP_int(peerToChat(chat->id)));
|
||||
chat->inputChat = MTP_int(peerToChat(chat->id));
|
||||
|
@ -3547,19 +3549,19 @@ namespace Local {
|
|||
} else if (result->isChannel()) {
|
||||
ChannelData *channel = result->asChannel();
|
||||
|
||||
QString name, invitationUrl;
|
||||
QString name, inviteLink;
|
||||
quint64 access;
|
||||
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) {
|
||||
channel->updateName(name, QString(), QString());
|
||||
channel->setName(name, QString());
|
||||
channel->access = access;
|
||||
channel->date = date;
|
||||
channel->version = version;
|
||||
channel->isForbidden = (forbidden == 1);
|
||||
channel->flags = MTPDchannel::Flags(flags);
|
||||
channel->invitationUrl = invitationUrl;
|
||||
channel->setInviteLink(inviteLink);
|
||||
|
||||
channel->input = MTP_inputPeerChannel(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);
|
||||
peers.push_back(peer);
|
||||
}
|
||||
App::emitPeerUpdated();
|
||||
|
||||
if (App::api()) App::api()->requestPeers(peers);
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,9 @@ class PeerAvatarButton;
|
|||
|
||||
namespace Window {
|
||||
class TopBarWidget;
|
||||
class SectionMemento;
|
||||
class SectionWidget;
|
||||
struct SectionSlideParams;
|
||||
} // namespace Window
|
||||
|
||||
class MainWindow;
|
||||
|
@ -40,7 +43,6 @@ class ApiWrap;
|
|||
class ConfirmBox;
|
||||
class DialogsWidget;
|
||||
class HistoryWidget;
|
||||
class ProfileWidget;
|
||||
class OverviewWidget;
|
||||
class PlayerWidget;
|
||||
class HistoryHider;
|
||||
|
@ -48,7 +50,7 @@ class Dropdown;
|
|||
|
||||
enum StackItemType {
|
||||
HistoryStackItem,
|
||||
ProfileStackItem,
|
||||
SectionStackItem,
|
||||
OverviewStackItem,
|
||||
};
|
||||
|
||||
|
@ -64,8 +66,9 @@ public:
|
|||
|
||||
class StackItemHistory : public StackItem {
|
||||
public:
|
||||
StackItemHistory(PeerData *peer, MsgId msgId, QList<MsgId> replyReturns) : StackItem(peer),
|
||||
msgId(msgId), replyReturns(replyReturns) {
|
||||
StackItemHistory(PeerData *peer, MsgId msgId, QList<MsgId> replyReturns) : StackItem(peer)
|
||||
, msgId(msgId)
|
||||
, replyReturns(replyReturns) {
|
||||
}
|
||||
StackItemType type() const {
|
||||
return HistoryStackItem;
|
||||
|
@ -74,19 +77,29 @@ msgId(msgId), replyReturns(replyReturns) {
|
|||
QList<MsgId> replyReturns;
|
||||
};
|
||||
|
||||
class StackItemProfile : public StackItem {
|
||||
class StackItemSection : public StackItem {
|
||||
public:
|
||||
StackItemProfile(PeerData *peer, int32 lastScrollTop) : StackItem(peer), lastScrollTop(lastScrollTop) {
|
||||
}
|
||||
StackItemSection(std_::unique_ptr<Window::SectionMemento> &&memento);
|
||||
~StackItemSection();
|
||||
|
||||
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 {
|
||||
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 {
|
||||
return OverviewStackItem;
|
||||
|
@ -173,8 +186,6 @@ public:
|
|||
void startFull(const MTPVector<MTPUser> &users);
|
||||
bool started();
|
||||
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);
|
||||
|
||||
|
@ -210,14 +221,14 @@ public:
|
|||
PeerData *activePeer();
|
||||
MsgId activeMsgId();
|
||||
|
||||
PeerData *profilePeer();
|
||||
PeerData *overviewPeer();
|
||||
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 showBackFromStack();
|
||||
void orderWidgets();
|
||||
QRect historyRect() const;
|
||||
QPixmap grabForShowAnimation(const Window::SectionSlideParams ¶ms);
|
||||
|
||||
void onSendFileConfirm(const FileLoadResultPtr &file, bool ctrlShiftEnter);
|
||||
void onSendFileCancel(const FileLoadResultPtr &file);
|
||||
|
@ -262,6 +273,7 @@ public:
|
|||
void deleteMessages(PeerData *peer, const QVector<MTPint> &ids);
|
||||
void deletedContact(UserData *user, const MTPcontacts_Link &result);
|
||||
void deleteConversation(PeerData *peer, bool deleteHistory = true);
|
||||
void deleteAndExit(ChatData *chat);
|
||||
void clearHistory(PeerData *peer);
|
||||
void deleteAllFromUser(ChannelData *channel, UserData *from);
|
||||
|
||||
|
@ -313,7 +325,6 @@ public:
|
|||
void itemEdited(HistoryItem *item);
|
||||
|
||||
void loadMediaBack(PeerData *peer, MediaOverviewType type, bool many = false);
|
||||
void peerUsernameChanged(PeerData *peer);
|
||||
|
||||
void checkLastUpdate(bool afterSleep);
|
||||
void showAddContact();
|
||||
|
@ -483,10 +494,9 @@ public slots:
|
|||
void ui_autoplayMediaInlineAsync(qint32 channelId, qint32 msgId);
|
||||
|
||||
private:
|
||||
|
||||
void sendReadRequest(PeerData *peer, MsgId upTo);
|
||||
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);
|
||||
void readRequestDone(PeerData *peer);
|
||||
|
||||
|
@ -496,6 +506,15 @@ private:
|
|||
void saveCloudDraftDone(PeerData *peer, const MTPBool &result, 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;
|
||||
|
||||
uint64 failedObjId = 0;
|
||||
|
@ -565,9 +584,11 @@ private:
|
|||
|
||||
int _dialogsWidth = st::dlgMinWidth;
|
||||
|
||||
PlainShadow _sideShadow;
|
||||
|
||||
ChildWidget<DialogsWidget> _dialogs;
|
||||
ChildWidget<HistoryWidget> _history;
|
||||
ChildWidget<ProfileWidget> _profile = { nullptr };
|
||||
ChildWidget<Window::SectionWidget> _wideSection = { nullptr };
|
||||
ChildWidget<OverviewWidget> _overview = { nullptr };
|
||||
ChildWidget<PlayerWidget> _player;
|
||||
ChildWidget<Window::TopBarWidget> _topBar;
|
||||
|
|
|
@ -35,9 +35,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "boxes/confirmbox.h"
|
||||
#include "boxes/contactsbox.h"
|
||||
#include "boxes/addcontactbox.h"
|
||||
#include "observer_peer.h"
|
||||
#include "autoupdater.h"
|
||||
#include "mediaview.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()) {
|
||||
set(text, reconnect);
|
||||
|
@ -435,7 +437,7 @@ void MainWindow::init() {
|
|||
connect(windowHandle(), SIGNAL(activeChanged()), this, SLOT(checkHistoryActivation()), Qt::QueuedConnection);
|
||||
|
||||
QPalette p(palette());
|
||||
p.setColor(QPalette::Window, st::wndBG->c);
|
||||
p.setColor(QPalette::Window, st::windowBg->c);
|
||||
setPalette(p);
|
||||
|
||||
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) {
|
||||
History *h = (main && App::userLoaded(ServiceUserId)) ? App::history(ServiceUserId) : 0;
|
||||
if (!h || (!force && h->isEmpty())) {
|
||||
|
@ -1374,7 +1372,7 @@ void MainWindow::onClearFailed(int task, void *manager) {
|
|||
}
|
||||
|
||||
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;
|
||||
|
||||
|
@ -1394,7 +1392,7 @@ void MainWindow::notifySchedule(History *history, HistoryItem *item) {
|
|||
return;
|
||||
}
|
||||
} else {
|
||||
App::wnd()->getNotifySetting(MTP_inputNotifyPeer(notifyByFrom->input));
|
||||
App::api()->requestNotifySetting(notifyByFrom);
|
||||
}
|
||||
} else {
|
||||
history->popNotification(item);
|
||||
|
@ -1403,9 +1401,9 @@ void MainWindow::notifySchedule(History *history, HistoryItem *item) {
|
|||
}
|
||||
} else {
|
||||
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()) {
|
||||
haveSetting = false;
|
||||
|
@ -1420,6 +1418,7 @@ void MainWindow::notifySchedule(History *history, HistoryItem *item) {
|
|||
} else if (cOtherOnline() >= t) {
|
||||
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;
|
||||
notifyWhenAlerts[history].insert(when, notifyByFrom);
|
||||
|
@ -1882,8 +1881,15 @@ void MainWindow::sendPaths() {
|
|||
|
||||
void MainWindow::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) {
|
||||
if (main) main->mediaOverviewUpdated(peer, type);
|
||||
if (!_mediaView || _mediaView->isHidden()) return;
|
||||
_mediaView->mediaOverviewUpdated(peer, type);
|
||||
if (_mediaView && !_mediaView->isHidden()) {
|
||||
_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) {
|
||||
|
|
|
@ -156,7 +156,6 @@ public:
|
|||
void checkAutoLockIn(int msec);
|
||||
void setupIntro(bool anim);
|
||||
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 sendServiceHistoryRequest();
|
||||
void showDelayedServiceMsgs();
|
||||
|
|
|
@ -1875,7 +1875,7 @@ void MediaView::mouseReleaseEvent(QMouseEvent *e) {
|
|||
if (_over == OverName && _down == OverName) {
|
||||
if (App::wnd() && _from) {
|
||||
close();
|
||||
if (App::main()) App::main()->showPeerProfile(_from);
|
||||
Ui::showPeerProfile(_from);
|
||||
}
|
||||
} else if (_over == OverDate && _down == OverDate) {
|
||||
onToMessage();
|
||||
|
|
|
@ -22,7 +22,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
|
||||
#include "mtproto/core_types.h"
|
||||
#include "mtproto/session.h"
|
||||
#include "mtproto/file_download.h"
|
||||
|
||||
namespace MTP {
|
||||
|
||||
|
|
|
@ -208,6 +208,7 @@ void FileLoader::localLoaded(const StorageImageSaved &result, const QByteArray &
|
|||
}
|
||||
emit App::wnd()->imageLoaded();
|
||||
emit progress(this);
|
||||
FileDownload::internal::notifyImageLoaded();
|
||||
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)));
|
||||
}
|
||||
emit progress(this);
|
||||
if (_complete) {
|
||||
FileDownload::internal::notifyImageLoaded();
|
||||
}
|
||||
loadNext();
|
||||
}
|
||||
|
||||
|
@ -678,6 +682,7 @@ void webFileLoader::onFinished(const QByteArray &data) {
|
|||
Local::writeWebFile(_url, _data);
|
||||
}
|
||||
emit progress(this);
|
||||
FileDownload::internal::notifyImageLoaded();
|
||||
loadNext();
|
||||
}
|
||||
|
||||
|
@ -1089,3 +1094,25 @@ namespace MTP {
|
|||
++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
|
||||
}
|
|
@ -20,6 +20,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/observer.h"
|
||||
|
||||
namespace MTP {
|
||||
void clearLoaderPriorities();
|
||||
}
|
||||
|
@ -391,3 +393,21 @@ static WebLoadManager * const FinishedWebLoadManager = SharedMemoryLocation<WebL
|
|||
|
||||
void reinitWebLoadManager();
|
||||
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
|
||||
|
|
124
Telegram/SourceFiles/observer_peer.cpp
Normal 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
|
100
Telegram/SourceFiles/observer_peer.h
Normal 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
|
|
@ -611,6 +611,9 @@ void OverviewInner::onDragExec() {
|
|||
mimeData->setData(qsl("application/x-td-forward-selected"), "1");
|
||||
}
|
||||
drag->setMimeData(mimeData);
|
||||
|
||||
// We don't receive mouseReleaseEvent when drag is finished.
|
||||
ClickHandler::unpressed();
|
||||
drag->exec(Qt::CopyAction);
|
||||
if (App::main()) App::main()->updateAfterDrag();
|
||||
return;
|
||||
|
@ -639,6 +642,9 @@ void OverviewInner::onDragExec() {
|
|||
}
|
||||
|
||||
drag->setMimeData(mimeData);
|
||||
|
||||
// We don't receive mouseReleaseEvent when drag is finished.
|
||||
ClickHandler::unpressed();
|
||||
drag->exec(Qt::CopyAction);
|
||||
if (App::main()) App::main()->updateAfterDrag();
|
||||
return;
|
||||
|
@ -1909,7 +1915,6 @@ OverviewWidget::OverviewWidget(QWidget *parent, PeerData *peer, MediaOverviewTyp
|
|||
, _scrollSetAfterShow(0)
|
||||
, _scrollDelta(0)
|
||||
, _selCount(0)
|
||||
, _sideShadow(this, st::shadowColor)
|
||||
, _topShadow(this, st::shadowColor)
|
||||
, _inGrab(false) {
|
||||
_scroll.setFocusPolicy(Qt::NoFocus);
|
||||
|
@ -1917,8 +1922,6 @@ OverviewWidget::OverviewWidget(QWidget *parent, PeerData *peer, MediaOverviewTyp
|
|||
_scroll.move(0, 0);
|
||||
_inner.move(0, 0);
|
||||
|
||||
_sideShadow.setVisible(!Adaptive::OneColumn());
|
||||
|
||||
updateScrollColors();
|
||||
|
||||
_scroll.show();
|
||||
|
@ -1970,8 +1973,6 @@ void OverviewWidget::resizeEvent(QResizeEvent *e) {
|
|||
|
||||
_topShadow.resize(width() - ((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0), st::lineWidth);
|
||||
_topShadow.moveToLeft((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0, 0);
|
||||
_sideShadow.resize(st::lineWidth, height());
|
||||
_sideShadow.moveToLeft(0, 0);
|
||||
}
|
||||
|
||||
void OverviewWidget::paintEvent(QPaintEvent *e) {
|
||||
|
@ -1979,14 +1980,16 @@ void OverviewWidget::paintEvent(QPaintEvent *e) {
|
|||
|
||||
Painter p(this);
|
||||
if (_a_show.animating()) {
|
||||
int retina = cIntRetinaFactor();
|
||||
int inCacheTop = st::topBarHeight;
|
||||
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.setOpacity(a_shadow.current() * st::slideFadeOut);
|
||||
p.fillRect(0, 0, a_coordOver.current(), height(), st::black->b);
|
||||
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_progress.current() * st::slideFadeOut);
|
||||
p.fillRect(0, 0, a_coordOver.current(), height(), st::black);
|
||||
p.setOpacity(1);
|
||||
}
|
||||
p.drawPixmap(a_coordOver.current(), 0, _cacheOver);
|
||||
p.setOpacity(a_shadow.current());
|
||||
p.drawPixmap(QRect(a_coordOver.current(), 0, _cacheOver.width() / retina, height()), _cacheOver, QRect(0, inCacheTop * retina, _cacheOver.width(), height() * retina));
|
||||
p.setOpacity(a_progress.current());
|
||||
p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), height()), App::sprite(), st::slideShadow.rect());
|
||||
return;
|
||||
}
|
||||
|
@ -2012,9 +2015,15 @@ void OverviewWidget::scrollReset() {
|
|||
|
||||
void OverviewWidget::paintTopBar(Painter &p, float64 over, int32 decreaseWidth) {
|
||||
if (_a_show.animating()) {
|
||||
p.drawPixmap(a_coordUnder.current(), 0, _cacheTopBarUnder);
|
||||
p.drawPixmap(a_coordOver.current(), 0, _cacheTopBarOver);
|
||||
p.setOpacity(a_shadow.current());
|
||||
int retina = cIntRetinaFactor();
|
||||
if (a_coordOver.current() > 0) {
|
||||
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());
|
||||
return;
|
||||
}
|
||||
|
@ -2119,44 +2128,54 @@ void OverviewWidget::fastShow(bool back, int32 lastScrollTop) {
|
|||
if (App::app()) App::app()->mtpUnpause();
|
||||
}
|
||||
|
||||
void OverviewWidget::animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTopBarCache, bool back, int32 lastScrollTop) {
|
||||
if (App::app()) App::app()->mtpPause();
|
||||
|
||||
(back ? _cacheOver : _cacheUnder) = bgAnimCache;
|
||||
(back ? _cacheTopBarOver : _cacheTopBarUnder) = bgAnimTopBarCache;
|
||||
void OverviewWidget::setLastScrollTop(int lastScrollTop) {
|
||||
resizeEvent(0);
|
||||
_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 ¶ms) {
|
||||
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();
|
||||
|
||||
_scrollSetAfterShow = _scroll.scrollTop();
|
||||
_scroll.hide();
|
||||
_topShadow.hide();
|
||||
|
||||
a_coordUnder = back ? anim::ivalue(-qFloor(st::slideShift * width()), 0) : anim::ivalue(0, -qFloor(st::slideShift * width()));
|
||||
a_coordOver = back ? anim::ivalue(0, width()) : anim::ivalue(width(), 0);
|
||||
a_shadow = back ? anim::fvalue(1, 0) : anim::fvalue(0, 1);
|
||||
int delta = st::slideShift;
|
||||
if (direction == Window::SlideDirection::FromLeft) {
|
||||
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();
|
||||
|
||||
show();
|
||||
|
||||
App::main()->topBar()->update();
|
||||
_inner.activate();
|
||||
|
||||
activate();
|
||||
}
|
||||
|
||||
void OverviewWidget::step_show(float64 ms, bool timer) {
|
||||
float64 dt = ms / st::slideDuration;
|
||||
if (dt >= 1) {
|
||||
_a_show.stop();
|
||||
_sideShadow.setVisible(!Adaptive::OneColumn());
|
||||
_topShadow.show();
|
||||
|
||||
a_coordUnder.finish();
|
||||
a_coordOver.finish();
|
||||
a_shadow.finish();
|
||||
_cacheUnder = _cacheOver = _cacheTopBarUnder = _cacheTopBarOver = QPixmap();
|
||||
a_progress.finish();
|
||||
_cacheUnder = _cacheOver = QPixmap();
|
||||
App::main()->topBar()->stopAnim();
|
||||
|
||||
doneShow();
|
||||
|
@ -2165,7 +2184,7 @@ void OverviewWidget::step_show(float64 ms, bool timer) {
|
|||
} else {
|
||||
a_coordUnder.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();
|
||||
|
@ -2173,10 +2192,6 @@ void OverviewWidget::step_show(float64 ms, bool timer) {
|
|||
}
|
||||
}
|
||||
|
||||
void OverviewWidget::updateAdaptiveLayout() {
|
||||
_sideShadow.setVisible(!Adaptive::OneColumn());
|
||||
}
|
||||
|
||||
void OverviewWidget::doneShow() {
|
||||
_scroll.show();
|
||||
_scroll.scrollToY(_scrollSetAfterShow);
|
||||
|
@ -2308,6 +2323,8 @@ void OverviewWidget::onDeleteSelected() {
|
|||
}
|
||||
|
||||
void OverviewWidget::onDeleteSelectedSure() {
|
||||
Ui::hideLayer();
|
||||
|
||||
SelectedItemSet sel;
|
||||
_inner.fillSelectedItems(sel);
|
||||
if (sel.isEmpty()) return;
|
||||
|
@ -2323,7 +2340,6 @@ void OverviewWidget::onDeleteSelectedSure() {
|
|||
for (SelectedItemSet::const_iterator i = sel.cbegin(), e = sel.cend(); i != e; ++i) {
|
||||
i.value()->destroy();
|
||||
}
|
||||
Ui::hideLayer();
|
||||
|
||||
for (QMap<PeerData*, QVector<MTPint> >::const_iterator i = ids.cbegin(), e = ids.cend(); i != e; ++i) {
|
||||
App::main()->deleteMessages(i.key(), i.value());
|
||||
|
@ -2331,6 +2347,8 @@ void OverviewWidget::onDeleteSelectedSure() {
|
|||
}
|
||||
|
||||
void OverviewWidget::onDeleteContextSure() {
|
||||
Ui::hideLayer();
|
||||
|
||||
HistoryItem *item = App::contextItem();
|
||||
if (!item || item->type() != HistoryItemMsg) {
|
||||
return;
|
||||
|
@ -2340,12 +2358,11 @@ void OverviewWidget::onDeleteContextSure() {
|
|||
History *h = item->history();
|
||||
bool wasOnServer = (item->id > 0), wasLast = (h->lastMsg == item);
|
||||
item->destroy();
|
||||
|
||||
if (!wasOnServer && wasLast && !h->lastMsg) {
|
||||
App::main()->checkPeerHistory(h->peer);
|
||||
}
|
||||
|
||||
Ui::hideLayer();
|
||||
|
||||
if (wasOnServer) {
|
||||
App::main()->deleteMessages(h->peer, toDelete);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include "window/section_widget.h"
|
||||
|
||||
namespace Overview {
|
||||
namespace Layout {
|
||||
|
||||
|
@ -276,10 +278,13 @@ public:
|
|||
int32 countBestScroll() const;
|
||||
|
||||
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 ¶ms);
|
||||
void step_show(float64 ms, bool timer);
|
||||
|
||||
void updateAdaptiveLayout();
|
||||
void doneShow();
|
||||
|
||||
void mediaOverviewUpdated(PeerData *peer, MediaOverviewType type);
|
||||
|
@ -300,14 +305,17 @@ public:
|
|||
void updateAfterDrag();
|
||||
|
||||
void grabStart() override {
|
||||
_sideShadow.hide();
|
||||
_inGrab = true;
|
||||
resizeEvent(0);
|
||||
}
|
||||
void grapWithoutTopBarShadow() {
|
||||
grabStart();
|
||||
_topShadow.hide();
|
||||
}
|
||||
void grabFinish() override {
|
||||
_sideShadow.setVisible(!Adaptive::OneColumn());
|
||||
_inGrab = false;
|
||||
resizeEvent(0);
|
||||
_topShadow.show();
|
||||
}
|
||||
void rpcClear() override {
|
||||
_inner.rpcClear();
|
||||
|
@ -343,9 +351,9 @@ private:
|
|||
QString _header;
|
||||
|
||||
Animation _a_show;
|
||||
QPixmap _cacheUnder, _cacheOver, _cacheTopBarUnder, _cacheTopBarOver;
|
||||
QPixmap _cacheUnder, _cacheOver;
|
||||
anim::ivalue a_coordUnder, a_coordOver;
|
||||
anim::fvalue a_shadow;
|
||||
anim::fvalue a_progress;
|
||||
|
||||
int32 _scrollSetAfterShow;
|
||||
|
||||
|
@ -354,7 +362,7 @@ private:
|
|||
|
||||
int32 _selCount;
|
||||
|
||||
PlainShadow _sideShadow, _topShadow;
|
||||
PlainShadow _topShadow;
|
||||
bool _inGrab;
|
||||
|
||||
};
|
||||
|
|
|
@ -119,7 +119,7 @@ void PasscodeWidget::animShow(const QPixmap &bgAnimCache, bool back) {
|
|||
(back ? _cacheUnder : _cacheOver) = myGrab(this);
|
||||
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_shadow = back ? anim::fvalue(1, 0) : anim::fvalue(0, 1);
|
||||
_a_show.start();
|
||||
|
|
|
@ -33,12 +33,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
|
||||
PlayerWidget::PlayerWidget(QWidget *parent) : TWidget(parent)
|
||||
, _a_state(animation(this, &PlayerWidget::step_state))
|
||||
, _a_progress(animation(this, &PlayerWidget::step_progress))
|
||||
, _sideShadow(this, st::shadowColor) {
|
||||
, _a_progress(animation(this, &PlayerWidget::step_progress)) {
|
||||
resize(st::wndMinWidth, st::playerHeight);
|
||||
setMouseTracking(true);
|
||||
memset(_stateHovers, 0, sizeof(_stateHovers));
|
||||
_sideShadow.setVisible(!Adaptive::OneColumn());
|
||||
}
|
||||
|
||||
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 {
|
||||
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()));
|
||||
_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();
|
||||
}
|
||||
|
||||
|
|
|
@ -52,7 +52,6 @@ public:
|
|||
void clearSelection();
|
||||
|
||||
void mediaOverviewUpdated(PeerData *peer, MediaOverviewType type);
|
||||
void updateAdaptiveLayout();
|
||||
|
||||
bool seekingSong(const SongMsgId &song) const;
|
||||
|
||||
|
@ -136,6 +135,4 @@ private:
|
|||
anim::fvalue a_loadProgress = { 0., 0. };
|
||||
Animation _a_progress;
|
||||
|
||||
PlainShadow _sideShadow;
|
||||
|
||||
};
|
||||
|
|
169
Telegram/SourceFiles/profile/profile.style
Normal 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;
|
||||
}
|
363
Telegram/SourceFiles/profile/profile_actions_widget.cpp
Normal 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
|
102
Telegram/SourceFiles/profile/profile_actions_widget.h
Normal 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
|
57
Telegram/SourceFiles/profile/profile_block_widget.cpp
Normal 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
|
74
Telegram/SourceFiles/profile/profile_block_widget.h
Normal 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
|
499
Telegram/SourceFiles/profile/profile_cover.cpp
Normal 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
|
137
Telegram/SourceFiles/profile/profile_cover.h
Normal 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
|
99
Telegram/SourceFiles/profile/profile_cover_drop_area.cpp
Normal 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
|
57
Telegram/SourceFiles/profile/profile_cover_drop_area.h
Normal 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
|
284
Telegram/SourceFiles/profile/profile_fixed_bar.cpp
Normal 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
|
108
Telegram/SourceFiles/profile/profile_fixed_bar.h
Normal 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
|
219
Telegram/SourceFiles/profile/profile_info_widget.cpp
Normal 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 ©Text) {
|
||||
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
|
71
Telegram/SourceFiles/profile/profile_info_widget.h
Normal 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 ©Text);
|
||||
|
||||
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
|
257
Telegram/SourceFiles/profile/profile_inner_widget.cpp
Normal 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
|
113
Telegram/SourceFiles/profile/profile_inner_widget.h
Normal 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
|
113
Telegram/SourceFiles/profile/profile_invite_link_widget.cpp
Normal 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
|