From 9d703f9519219b5358a165caa520573ef1f5a43e Mon Sep 17 00:00:00 2001 From: eegorov Date: Sun, 8 May 2016 14:14:44 +0600 Subject: [PATCH 01/30] Make possible to set a path to the patched Qt and his version during start qmake v2 Signed-off-by: Egor Y. Egorov (github: eegorov) --- Telegram/MetaEmoji.pro | 5 +---- Telegram/MetaLang.pro | 5 +---- Telegram/Packer.pro | 4 +--- Telegram/Telegram.pro | 12 ++++++------ Telegram/qt_static.pri | 20 ++++++++++++++++++++ 5 files changed, 29 insertions(+), 17 deletions(-) create mode 100644 Telegram/qt_static.pri diff --git a/Telegram/MetaEmoji.pro b/Telegram/MetaEmoji.pro index e859c30c8..02c5c7080 100644 --- a/Telegram/MetaEmoji.pro +++ b/Telegram/MetaEmoji.pro @@ -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) diff --git a/Telegram/MetaLang.pro b/Telegram/MetaLang.pro index 0dfa29a6f..cb60900f8 100644 --- a/Telegram/MetaLang.pro +++ b/Telegram/MetaLang.pro @@ -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) diff --git a/Telegram/Packer.pro b/Telegram/Packer.pro index 32fc691c3..ee5f36f28 100644 --- a/Telegram/Packer.pro +++ b/Telegram/Packer.pro @@ -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 diff --git a/Telegram/Telegram.pro b/Telegram/Telegram.pro index 7973bc234..e65d45766 100644 --- a/Telegram/Telegram.pro +++ b/Telegram/Telegram.pro @@ -356,9 +356,9 @@ CONFIG(debug, debug|release) { QMAKE_LFLAGS_DEBUG += -g -rdynamic -static-libstdc++ } -INCLUDEPATH += /usr/local/tdesktop/Qt-5.6.0/include/QtGui/5.6.0/QtGui\ - /usr/local/tdesktop/Qt-5.6.0/include/QtCore/5.6.0/QtCore\ - /usr/local/tdesktop/Qt-5.6.0/include\ +include(qt_static.pri) + +INCLUDEPATH += \ /usr/local/include\ /usr/local/include/opus\ ./SourceFiles\ @@ -382,9 +382,9 @@ INCLUDEPATH += "/usr/include/dee-1.0" INCLUDEPATH += "/usr/include/libdbusmenu-glib-0.4" LIBS += -ldl -llzma -lopenal -lavformat -lavcodec -lswresample -lswscale -lavutil -lopus -lva -LIBS += /usr/local/tdesktop/Qt-5.6.0/plugins/platforminputcontexts/libcomposeplatforminputcontextplugin.a \ - /usr/local/tdesktop/Qt-5.6.0/plugins/platforminputcontexts/libibusplatforminputcontextplugin.a \ - /usr/local/tdesktop/Qt-5.6.0/plugins/platforminputcontexts/libfcitxplatforminputcontextplugin.a +LIBS += $${QT_TDESKTOP_PATH}/plugins/platforminputcontexts/libcomposeplatforminputcontextplugin.a \ + $${QT_TDESKTOP_PATH}/plugins/platforminputcontexts/libibusplatforminputcontextplugin.a \ + $${QT_TDESKTOP_PATH}/plugins/platforminputcontexts/libfcitxplatforminputcontextplugin.a LIBS += /usr/local/lib/libz.a LIBS += /usr/local/lib/libxkbcommon.a LIBS += ./../../../Libraries/breakpad/src/client/linux/libbreakpad_client.a diff --git a/Telegram/qt_static.pri b/Telegram/qt_static.pri new file mode 100644 index 000000000..9873c80ad --- /dev/null +++ b/Telegram/qt_static.pri @@ -0,0 +1,20 @@ +QT_TDESKTOP_VERSION_DEFAULT = 5.6.0 +QT_TDESKTOP_PATH_DEFAULT = /usr/local/tdesktop/Qt-$${QT_TDESKTOP_VERSION_DEFAULT} + +QT_TDESKTOP_VERSION = $${QT_TDESKTOP_VERSION} +QT_TDESKTOP_PATH = $${QT_TDESKTOP_PATH} + +isEmpty(QT_TDESKTOP_PATH) { + message(QT_TDESKTOP_PATH is not set. Using default value $${QT_TDESKTOP_PATH_DEFAULT}) + QT_TDESKTOP_PATH = $${QT_TDESKTOP_PATH_DEFAULT} + +} +isEmpty(QT_TDESKTOP_VERSION) { + message(QT_TDESKTOP_VERSION is not set. Using default value $${QT_TDESKTOP_VERSION_DEFAULT}) + QT_TDESKTOP_VERSION = $${QT_TDESKTOP_VERSION_DEFAULT} +} + +INCLUDEPATH += $${QT_TDESKTOP_PATH}/include/QtGui/$${QT_TDESKTOP_VERSION}/QtGui \ + $${QT_TDESKTOP_PATH}/include/QtCore/$${QT_TDESKTOP_VERSION}/QtCore \ + $${QT_TDESKTOP_PATH}/include + From fe52b6075d460459482a2bc9c6dd4d7f1f119aa5 Mon Sep 17 00:00:00 2001 From: eegorov Date: Fri, 6 May 2016 17:06:41 +0600 Subject: [PATCH 02/30] Fix travis-scripts to support prev commit Signed-off-by: Egor Y. Egorov (github: eegorov) --- .travis/build.sh | 29 ++++++++++++++--------------- .travis/common.sh | 2 +- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/.travis/build.sh b/.travis/build.sh index eea05c509..8ecdfaa96 100755 --- a/.travis/build.sh +++ b/.travis/build.sh @@ -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 } diff --git a/.travis/common.sh b/.travis/common.sh index dd3bb1967..ef9da3867 100755 --- a/.travis/common.sh +++ b/.travis/common.sh @@ -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() { From 3d073eddd671eb25321218fb2ed57a4751367997 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 12 May 2016 19:05:20 +0300 Subject: [PATCH 03/30] New profiles started. Function<> made by variadic templates. --- Telegram/Resources/basic.style | 21 +- .../Resources/icons/topbar_back_arrow.png | Bin 0 -> 137 bytes .../Resources/icons/topbar_back_arrow@2x.png | Bin 0 -> 252 bytes Telegram/SourceFiles/core/basic_types.h | 246 ++++-------------- Telegram/SourceFiles/facades.cpp | 9 + Telegram/SourceFiles/facades.h | 2 + Telegram/SourceFiles/history.cpp | 4 +- Telegram/SourceFiles/history.h | 8 +- .../inline_bots/inline_bot_layout_internal.h | 10 +- Telegram/SourceFiles/mainwidget.cpp | 91 ++++--- Telegram/SourceFiles/mainwidget.h | 6 +- Telegram/SourceFiles/mainwindow.cpp | 3 +- Telegram/SourceFiles/profile/profile.style | 50 ++++ .../SourceFiles/profile/profile_cover.cpp | 202 ++++++++++++++ Telegram/SourceFiles/profile/profile_cover.h | 71 +++++ .../profile/profile_inner_widget.cpp | 59 +++++ .../profile/profile_inner_widget.h | 64 +++++ .../SourceFiles/profile/profile_widget.cpp | 119 +++++++++ Telegram/SourceFiles/profile/profile_widget.h | 69 +++++ Telegram/SourceFiles/profilewidget.cpp | 97 ++++--- Telegram/SourceFiles/profilewidget.h | 3 +- Telegram/SourceFiles/ui/animation.cpp | 8 +- Telegram/SourceFiles/ui/animation.h | 73 +++--- Telegram/SourceFiles/ui/flatbutton.cpp | 2 +- Telegram/SourceFiles/ui/flatbutton.h | 5 +- Telegram/SourceFiles/ui/slide_animation.cpp | 97 +++++++ Telegram/SourceFiles/ui/slide_animation.h | 58 +++++ Telegram/Telegram.vcxproj | 86 ++++++ Telegram/Telegram.vcxproj.filters | 63 +++++ 29 files changed, 1185 insertions(+), 341 deletions(-) create mode 100644 Telegram/Resources/icons/topbar_back_arrow.png create mode 100644 Telegram/Resources/icons/topbar_back_arrow@2x.png create mode 100644 Telegram/SourceFiles/profile/profile.style create mode 100644 Telegram/SourceFiles/profile/profile_cover.cpp create mode 100644 Telegram/SourceFiles/profile/profile_cover.h create mode 100644 Telegram/SourceFiles/profile/profile_inner_widget.cpp create mode 100644 Telegram/SourceFiles/profile/profile_inner_widget.h create mode 100644 Telegram/SourceFiles/profile/profile_widget.cpp create mode 100644 Telegram/SourceFiles/profile/profile_widget.h create mode 100644 Telegram/SourceFiles/ui/slide_animation.cpp create mode 100644 Telegram/SourceFiles/ui/slide_animation.h diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style index 866c89334..8245a6314 100644 --- a/Telegram/Resources/basic.style +++ b/Telegram/Resources/basic.style @@ -55,10 +55,13 @@ 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 + wndMinHeight: 480px; wndDefWidth: 800px; wndDefHeight: 600px; -wndBG: white; wndShadow: sprite(209px, 46px, 19px, 19px); wndShadowShift: 1px; @@ -1671,13 +1674,13 @@ 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); @@ -2458,7 +2461,7 @@ inlineResultsSkip: 3px; inlineMediaHeight: 96px; inlineThumbSize: 64px; inlineThumbSkip: 10px; -inlineDescriptionFg: #8a8a8a; +inlineDescriptionFg: windowSubTextFg; inlineRowMargin: 6px; inlineRowBorder: 1px; inlineRowBorderFg: #eaeaea; diff --git a/Telegram/Resources/icons/topbar_back_arrow.png b/Telegram/Resources/icons/topbar_back_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..7c62fd73de242761ad51e5898bfb1b434c7e92bb GIT binary patch literal 137 zcmeAS@N?(olHy`uVBq!ia0vp^oIotV!3HFcc!W9wsQ^zG#}JK)y_X!h4j6E_T=e&Q zRP?U?;LAlVmrQz%AG~W^#8eSJDd%=*s$ash<}ihqy>$ng`5w6O8t5`yX8NGuc|}jk lCeCrsnIAeI8_U-hF|Ikv+wm%^Wi`-j22WQ%mvv4FO#pR9F^vEK literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/topbar_back_arrow@2x.png b/Telegram/Resources/icons/topbar_back_arrow@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..990a5bb66e0509ab3e59a2951041901895460430 GIT binary patch literal 252 zcmVL9=bw82tu+Bsz9EhO5`3=AlHx*Ifo3$5i%mbPy_OX z8j&|dQc_ASh=8QzoZApUFiq0|i81OBKwj{Q+poDzcKL8wekbe?N2C!uAHn4sF=F?t z;d0d~vAUh>QjVT5Re`+1OeK4dZ66U`nJ8`c&zcqAbKJN!Z`dyY0000 -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); @@ -1154,213 +1145,88 @@ public: virtual R call(Args... args) const = 0; virtual ~SharedCallback() { } - typedef QSharedPointer> Ptr; + using Ptr = QSharedPointer>; + }; -template +template class FunctionImplementation { public: - virtual R call() = 0; + virtual R call(Args... args) = 0; virtual void destroy() { delete this; } virtual ~FunctionImplementation() {} + }; -template -class NullFunctionImplementation : public FunctionImplementation { + +template +class NullFunctionImplementation : public FunctionImplementation { public: - virtual R call() { return R(); } + virtual R call(Args... args) { return R(); } virtual void destroy() {} - static NullFunctionImplementation SharedInstance; + static NullFunctionImplementation SharedInstance; + }; -template -NullFunctionImplementation NullFunctionImplementation::SharedInstance; -template -class FunctionCreator { -public: - FunctionCreator(FunctionImplementation *ptr) : _ptr(ptr) {} - FunctionCreator(const FunctionCreator &other) : _ptr(other.create()) {} - FunctionImplementation *create() const { return getPointerAndReset(_ptr); } - ~FunctionCreator() { destroyImplementation(_ptr); } -private: - FunctionCreator &operator=(const FunctionCreator &other); - mutable FunctionImplementation *_ptr; -}; -template +template +NullFunctionImplementation NullFunctionImplementation::SharedInstance; + +template class Function { public: - typedef FunctionCreator Creator; - static Creator Null() { return Creator(&NullFunctionImplementation::SharedInstance); } - Function(const Creator &creator) : _implementation(creator.create()) {} - R call() { return _implementation->call(); } - ~Function() { destroyImplementation(_implementation); } + Function() : _implementation(&NullFunctionImplementation::SharedInstance) {} + Function(FunctionImplementation *implementation) : _implementation(implementation) {} + Function(const Function &other) = delete; + Function &operator=(const Function &other) = delete; + Function(Function &&other) : _implementation(other._implementation) { + other._implementation = &NullFunctionImplementation::SharedInstance; + } + Function &operator=(Function &&other) { + std::swap(_implementation, other._implementation); + return *this; + } + + R call(Args... args) { return _implementation->call(args...); } + ~Function() { + if (_implementation) { + _implementation->destroy(); + _implementation = nullptr; + } + deleteAndMark(_implementation); + } + private: - Function(const Function &other); - Function &operator=(const Function &other); - FunctionImplementation *_implementation; + FunctionImplementation *_implementation; + }; -template -class WrappedFunction : public FunctionImplementation { +template +class WrappedFunction : public FunctionImplementation { 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 -inline FunctionCreator func(R(*method)()) { - return FunctionCreator(new WrappedFunction(method)); +template +inline Function func(R(*method)(Args... args)) { + return Function(new WrappedFunction(method)); } -template -class ObjectFunction : public FunctionImplementation { + +template +class ObjectFunction : public FunctionImplementation { 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 -inline FunctionCreator func(O *obj, R(I::*method)()) { - return FunctionCreator(new ObjectFunction(obj, method)); -} -template -class Function1Implementation { -public: - virtual R call(A1 a1) = 0; - virtual void destroy() { delete this; } - virtual ~Function1Implementation() {} }; -template -class NullFunction1Implementation : public Function1Implementation { -public: - virtual R call(A1 a1) { return R(); } - virtual void destroy() {} - static NullFunction1Implementation SharedInstance; -}; -template -NullFunction1Implementation NullFunction1Implementation::SharedInstance; -template -class Function1Creator { -public: - Function1Creator(Function1Implementation *ptr) : _ptr(ptr) {} - Function1Creator(const Function1Creator &other) : _ptr(other.create()) {} - Function1Implementation *create() const { return getPointerAndReset(_ptr); } - ~Function1Creator() { destroyImplementation(_ptr); } -private: - Function1Creator &operator=(const Function1Creator &other); - mutable Function1Implementation *_ptr; -}; -template -class Function1 { -public: - typedef Function1Creator Creator; - static Creator Null() { return Creator(&NullFunction1Implementation::SharedInstance); } - Function1(const Creator &creator) : _implementation(creator.create()) {} - R call(A1 a1) { return _implementation->call(a1); } - ~Function1() { _implementation->destroy(); } -private: - Function1(const Function1 &other); - Function1 &operator=(const Function1 &other); - Function1Implementation *_implementation; -}; - -template -class WrappedFunction1 : public Function1Implementation { -public: - typedef R(*Method)(A1); - WrappedFunction1(Method method) : _method(method) {} - virtual R call(A1 a1) { return (*_method)(a1); } -private: - Method _method; -}; -template -inline Function1Creator func(R(*method)(A1)) { - return Function1Creator(new WrappedFunction1(method)); -} -template -class ObjectFunction1 : public Function1Implementation { -public: - typedef R(I::*Method)(A1); - ObjectFunction1(O *obj, Method method) : _obj(obj), _method(method) {} - virtual R call(A1 a1) { return (_obj->*_method)(a1); } -private: - O *_obj; - Method _method; -}; -template -Function1Creator func(O *obj, R(I::*method)(A1)) { - return Function1Creator(new ObjectFunction1(obj, method)); -} - -template -class Function2Implementation { -public: - virtual R call(A1 a1, A2 a2) = 0; - virtual void destroy() { delete this; } - virtual ~Function2Implementation() {} -}; -template -class NullFunction2Implementation : public Function2Implementation { -public: - virtual R call(A1 a1, A2 a2) { return R(); } - virtual void destroy() {} - static NullFunction2Implementation SharedInstance; -}; -template -NullFunction2Implementation NullFunction2Implementation::SharedInstance; -template -class Function2Creator { -public: - Function2Creator(Function2Implementation *ptr) : _ptr(ptr) {} - Function2Creator(const Function2Creator &other) : _ptr(other.create()) {} - Function2Implementation *create() const { return getPointerAndReset(_ptr); } - ~Function2Creator() { destroyImplementation(_ptr); } -private: - Function2Creator &operator=(const Function2Creator &other); - mutable Function2Implementation *_ptr; -}; -template -class Function2 { -public: - typedef Function2Creator Creator; - static Creator Null() { return Creator(&NullFunction2Implementation::SharedInstance); } - Function2(const Creator &creator) : _implementation(creator.create()) {} - R call(A1 a1, A2 a2) { return _implementation->call(a1, a2); } - ~Function2() { destroyImplementation(_implementation); } -private: - Function2(const Function2 &other); - Function2 &operator=(const Function2 &other); - Function2Implementation *_implementation; -}; - -template -class WrappedFunction2 : public Function2Implementation { -public: - typedef R(*Method)(A1, A2); - WrappedFunction2(Method method) : _method(method) {} - virtual R call(A1 a1, A2 a2) { return (*_method)(a1, a2); } -private: - Method _method; -}; -template -Function2Creator func(R(*method)(A1, A2)) { - return Function2Creator(new WrappedFunction2(method)); -} - -template -class ObjectFunction2 : public Function2Implementation { -public: - typedef R(I::*Method)(A1, A2); - ObjectFunction2(O *obj, Method method) : _obj(obj), _method(method) {} - virtual R call(A1 a1, A2 a2) { return (_obj->*_method)(a1, a2); } -private: - O *_obj; - Method _method; -}; -template -Function2Creator func(O *obj, R(I::*method)(A1, A2)) { - return Function2Creator(new ObjectFunction2(obj, method)); +template +inline Function func(O *obj, R(I::*method)(Args...)) { + return Function(new ObjectFunction(obj, method)); } diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index be5fba8a1..04a8a7329 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -269,6 +269,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 { diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index 412c0d65a..0f16b238c 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -88,6 +88,8 @@ PeerData *getPeerForMouseAction(); bool hideWindowNoQuit(); +bool skipPaintEvent(QWidget *widget, QPaintEvent *event); + } // namespace Ui enum ClipStopperType { diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index b9e922790..4cc6b7bec 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -3204,14 +3204,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)) { } diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index 9c850a53a..46137693d 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -1623,7 +1623,7 @@ protected: class RadialAnimation { public: - RadialAnimation(AnimationCreator creator); + RadialAnimation(AnimationCallbacks &&callbacks); float64 opacity() const { return _opacity; @@ -1857,9 +1857,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; diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h index e36bcc3c5..7095f8828 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h @@ -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; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 8565a53f6..11be42bbd 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -22,6 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include "ui/buttons/peer_avatar_button.h" +#include "profile/profile_widget.h" #include "window/top_bar_widget.h" #include "apiwrap.h" #include "dialogswidget.h" @@ -377,7 +378,6 @@ void MainWidget::onFilesOrForwardDrop(const PeerId &peer, const QMimeData *data) void MainWidget::rpcClear() { _history->rpcClear(); _dialogs->rpcClear(); - if (_profile) _profile->rpcClear(); if (_overview) _overview->rpcClear(); if (_api) _api->rpcClear(); RPCSender::rpcClear(); @@ -523,14 +523,23 @@ void MainWidget::noHider(HistoryHider *destroyed) { } onHistoryShown(_history->history(), _history->msgId()); if (_profile || _overview || (_history->peer() && _history->peer()->id)) { - QPixmap animCache = grabInner(), animTopBarCache = grabTopBar(); + QPixmap animCache, animTopBarCache; + if (_profile) { + if (Adaptive::OneColumn()) { + animCache = myGrab(this, QRect(0, _playerHeight, _dialogsWidth, height() - _playerHeight)); + } else { + animCache = myGrab(this, QRect(_dialogsWidth, _playerHeight, width() - _dialogsWidth, height() - _playerHeight)); + } + } else { + animCache = grabInner(); + animTopBarCache = grabTopBar(); + } _dialogs->hide(); if (_overview) { _overview->show(); _overview->animShow(animCache, animTopBarCache); } else if (_profile) { - _profile->show(); - _profile->animShow(animCache, animTopBarCache); + _profile->showAnimated(SlideDirection::FromRight, animCache); } else { _history->show(); _history->animShow(animCache, animTopBarCache); @@ -1187,11 +1196,11 @@ void MainWidget::readServerHistory(History *hist, bool force) { MsgId upTo = hist->inboxRead(0); if (hist->isChannel() && !hist->peer->asChannel()->amIn()) { - return; // no read request for channels that I didn't koin + return; // no read request for channels that I didn't join } ReadRequests::const_iterator i = _readRequests.constFind(hist->peer); - if (i == _readRequests.cend()) { + if (i == _readRequests.cend()) { sendReadRequest(hist->peer, upTo); } else { ReadRequestsPending::iterator i = _readRequestsPending.find(hist->peer); @@ -1282,7 +1291,7 @@ void MainWidget::overviewPreloaded(PeerData *peer, const MTPmessages_Messages &r } void MainWidget::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) { - if (_profile) _profile->mediaOverviewUpdated(peer, type); +// if (_profile) _profile->mediaOverviewUpdated(peer, type); TODO if (!_player->isHidden()) _player->mediaOverviewUpdated(peer, type); if (_overview && (_overview->peer() == peer || _overview->peer()->migrateFrom() == peer)) { _overview->mediaOverviewUpdated(peer, type); @@ -1386,9 +1395,9 @@ void MainWidget::loadMediaBack(PeerData *peer, MediaOverviewType type, bool many } void MainWidget::peerUsernameChanged(PeerData *peer) { - if (_profile && _profile->peer() == peer) { - _profile->peerUsernameChanged(); - } + //if (_profile && _profile->peer() == peer) { TODO + // _profile->peerUsernameChanged(); + //} if (App::settings() && peer == App::self()) { App::settings()->usernameChanged(); } @@ -1695,7 +1704,7 @@ void MainWidget::onParentResize(const QSize &newSize) { void MainWidget::updateOnlineDisplay() { if (this != App::main()) return; _history->updateOnlineDisplay(_history->x(), width() - _history->x() - st::sysBtnDelta * 2 - st::sysCls.img.pxWidth() - st::sysRes.img.pxWidth() - st::sysMin.img.pxWidth()); - if (_profile) _profile->updateOnlineDisplay(); +// if (_profile) _profile->updateOnlineDisplay(); TODO if (App::wnd()->settingsWidget()) App::wnd()->settingsWidget()->updateOnlineDisplay(); } @@ -1883,14 +1892,14 @@ void MainWidget::setInnerFocus() { } else if (_overview) { _overview->activate(); } else if (_profile) { - _profile->activate(); + _profile->setInnerFocus(); } else { dialogsActivate(); } } else if (_overview) { _overview->activate(); } else if (_profile) { - _profile->activate(); + _profile->setInnerFocus(); } else { _history->setInnerFocus(); } @@ -2055,9 +2064,7 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool bac if (_profile || _overview) { if (_profile) { _profile->hide(); - _profile->clear(); _profile->deleteLater(); - _profile->rpcClear(); _profile = nullptr; } if (_overview) { @@ -2117,7 +2124,7 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool bac PeerData *MainWidget::ui_getPeerForMouseAction() { if (_profile) { - return _profile->ui_getPeerForMouseAction(); + //return _profile->ui_getPeerForMouseAction(); TODO } return _history->ui_getPeerForMouseAction(); } @@ -2201,7 +2208,7 @@ void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool if (_overview) { _stack.push_back(new StackItemOverview(_overview->peer(), _overview->type(), _overview->lastWidth(), _overview->lastScrollTop())); } else if (_profile) { - _stack.push_back(new StackItemProfile(_profile->peer(), _profile->lastScrollTop())); +// _stack.push_back(new StackItemProfile(_profile->peer(), _profile->lastScrollTop())); TODO } else if (_history->peer()) { dlgUpdated(); _peerInStack = _history->peer(); @@ -2218,9 +2225,7 @@ void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool } if (_profile) { _profile->hide(); - _profile->clear(); _profile->deleteLater(); - _profile->rpcClear(); _profile = nullptr; } _overview = new OverviewWidget(this, peer, type); @@ -2252,12 +2257,18 @@ void MainWidget::showPeerProfile(PeerData *peer, bool back, int32 lastScrollTop) App::wnd()->hideSettings(); if (_profile && _profile->peer() == peer) return; - QPixmap animCache = grabInner(), animTopBarCache = grabTopBar(); + QPixmap animCache; + if (Adaptive::OneColumn()) { + animCache = myGrab(this, QRect(0, _playerHeight, _dialogsWidth, height() - _playerHeight)); + } else { + animCache = myGrab(this, QRect(_dialogsWidth, _playerHeight, width() - _dialogsWidth, height() - _playerHeight)); + } +// QPixmap animCache = grabInner(), animTopBarCache = grabTopBar(); if (!back) { if (_overview) { _stack.push_back(new StackItemOverview(_overview->peer(), _overview->type(), _overview->lastWidth(), _overview->lastScrollTop())); } else if (_profile) { - _stack.push_back(new StackItemProfile(_profile->peer(), _profile->lastScrollTop())); +// _stack.push_back(new StackItemProfile(_profile->peer(), _profile->lastScrollTop())); TODO } else if (_history->peer()) { dlgUpdated(); _peerInStack = _history->peer(); @@ -2275,14 +2286,13 @@ void MainWidget::showPeerProfile(PeerData *peer, bool back, int32 lastScrollTop) } if (_profile) { _profile->hide(); - _profile->clear(); _profile->deleteLater(); - _profile->rpcClear(); + _profile = nullptr; } - _profile = new ProfileWidget(this, peer); - _topBar->show(); + _profile = new Profile::Widget(this, peer); + _topBar->hide(); resizeEvent(0); - _profile->animShow(animCache, animTopBarCache, back, lastScrollTop); + _profile->showAnimated(SlideDirection::FromRight, animCache); _history->animStop(); if (back) clearBotStartToken(_history->peer()); _history->showHistory(0, 0); @@ -2526,7 +2536,9 @@ void MainWidget::showAll() { _dialogs->show(); _history->hide(); } - if (!selectingPeer() && (_profile || _overview || _history->peer())) { + if (_profile) { + _topBar->hide(); + } else if (!selectingPeer() && (_overview || _history->peer())) { _topBar->show(); _dialogs->hide(); } @@ -2547,7 +2559,9 @@ void MainWidget::showAll() { _history->show(); _history->resizeEvent(0); } - if (_profile || _overview || _history->peer()) { + if (_profile) { + _topBar->hide(); + } else if (_overview || _history->peer()) { _topBar->show(); } } @@ -2585,7 +2599,10 @@ void MainWidget::resizeEvent(QResizeEvent *e) { } } _mediaType->moveToLeft(width() - _mediaType->width(), _playerHeight + st::topBarHeight); - if (_profile) _profile->setGeometry(_history->geometry()); + if (_profile) { + QRect profileGeometry(_history->x(), _playerHeight, _history->width(), height() - _playerHeight); + _profile->setGeometryWithTopMoved(profileGeometry, _contentScrollAddToY); + } if (_overview) _overview->setGeometry(_history->geometry()); _contentScrollAddToY = 0; } @@ -2602,7 +2619,7 @@ void MainWidget::updateAdaptiveLayout() { _topBar->updateAdaptiveLayout(); _history->updateAdaptiveLayout(); if (_overview) _overview->updateAdaptiveLayout(); - if (_profile) _profile->updateAdaptiveLayout(); +// if (_profile) _profile->updateAdaptiveLayout(); TODO _player->updateAdaptiveLayout(); } @@ -2612,7 +2629,7 @@ bool MainWidget::needBackButton() { void MainWidget::paintTopBar(Painter &p, float64 over, int32 decreaseWidth) { if (_profile) { - _profile->paintTopBar(p, over, decreaseWidth); +// _profile->paintTopBar(p, over, decreaseWidth); } else if (_overview) { _overview->paintTopBar(p, over, decreaseWidth); } else { @@ -2660,7 +2677,7 @@ PlayerWidget *MainWidget::player() { void MainWidget::onTopBarClick() { if (_profile) { - _profile->topBarClick(); +// _profile->topBarClick(); } else if (_overview) { _overview->topBarClick(); } else { @@ -2669,7 +2686,7 @@ void MainWidget::onTopBarClick() { } void MainWidget::onHistoryShown(History *history, MsgId atMsgId) { - if ((!Adaptive::OneColumn() || !selectingPeer()) && (_profile || _overview || history)) { + if ((!Adaptive::OneColumn() || !selectingPeer()) && (_overview || history)) { _topBar->show(); } else { _topBar->hide(); @@ -3479,7 +3496,7 @@ void MainWidget::startFull(const MTPVector &users) { } void MainWidget::applyNotifySetting(const MTPNotifyPeer &peer, const MTPPeerNotifySettings &settings, History *h) { - PeerData *updatePeer = 0; + PeerData *updatePeer = nullptr; switch (settings.type()) { case mtpc_peerNotifySettingsEmpty: switch (peer.type()) { @@ -3541,9 +3558,9 @@ void MainWidget::applyNotifySetting(const MTPNotifyPeer &peer, const MTPPeerNoti _history->updateNotifySettings(); } _dialogs->updateNotifySettings(updatePeer); - if (_profile && _profile->peer() == updatePeer) { - _profile->updateNotifySettings(); - } + //if (_profile && _profile->peer() == updatePeer) { TODO + // _profile->updateNotifySettings(); + //} } } diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 537b56be5..7bc90af75 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -35,6 +35,10 @@ namespace Window { class TopBarWidget; } // namespace Window +namespace Profile { +class Widget; +} // namespace Profile + class MainWindow; class ApiWrap; class ConfirmBox; @@ -550,7 +554,7 @@ private: ChildWidget _dialogs; ChildWidget _history; - ChildWidget _profile = { nullptr }; + ChildWidget _profile = { nullptr }; ChildWidget _overview = { nullptr }; ChildWidget _player; ChildWidget _topBar; diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp index 94277b6a3..daf322fb5 100644 --- a/Telegram/SourceFiles/mainwindow.cpp +++ b/Telegram/SourceFiles/mainwindow.cpp @@ -435,7 +435,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); @@ -1420,6 +1420,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); diff --git a/Telegram/SourceFiles/profile/profile.style b/Telegram/SourceFiles/profile/profile.style new file mode 100644 index 000000000..5ec69df55 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile.style @@ -0,0 +1,50 @@ +/* +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"; + +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); + +profileMarginTop: 13px; +profilePhotoSize: 112px; +profilePhotoLeft: 35px; +profileNameLeft: 26px; +profileNameTop: 9px; +profileNameFont: font(16px); +profileNameFg: windowTextFg; +profileStatusLeft: 27px; +profileStatusTop: 35px; +profileStatusFont: normalFont; +profileStatusFg: windowSubTextFg; +profileMarginBottom: 30px; +profileSeparator: 10px; + +profileBlocksTop: 7px; +profileBlockMarginTop: 21px; diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp new file mode 100644 index 000000000..c73d0fa15 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_cover.cpp @@ -0,0 +1,202 @@ +/* +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 "lang.h" +#include "apiwrap.h" +#include "mainwidget.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) { + Painter p(this); + + 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)); + } + +private: + +}; + +class PhotoButton final : public Button { +public: + PhotoButton(QWidget *parent, PeerData *peer) : Button(parent), _peer(peer) { + resize(st::profilePhotoSize, st::profilePhotoSize); + } + +protected: + void paintEvent(QPaintEvent *e) { + Painter p(this); + + _peer->paintUserpic(p, st::profilePhotoSize, 0, 0); + } + +private: + PeerData *_peer; + +}; + +CoverWidget::CoverWidget(QWidget *parent, PeerData *peer) : TWidget(parent) +, _peer(peer) +, _peerUser(peer->asUser()) +, _peerChat(peer->asChat()) +, _peerChannel(peer->asChannel()) +, _peerMegagroup(peer->isMegagroup() ? _peerChannel : nullptr) +, _backButton(this) +, _photoButton(this, peer) { + setAttribute(Qt::WA_OpaquePaintEvent); + + _backButton->moveToLeft(0, 0); + connect(_backButton, SIGNAL(clicked()), this, SLOT(onBack())); + + _nameText.setText(st::profileNameFont, App::peerName(_peer)); + updateStatusText(); +} + +void CoverWidget::onBack() { + App::main()->showBackFromStack(); +} + +void CoverWidget::resizeToWidth(int newWidth) { + int newHeight = 0; + + // Top bar + _backButton->resizeToWidth(newWidth); + newHeight += _backButton->height(); + + // Cover content + newHeight += st::profileMarginTop; + _photoButton->moveToLeft(st::profilePhotoLeft, newHeight); + + int infoLeft = _photoButton->x() + _photoButton->width(); + _namePosition = QPoint(infoLeft + st::profileNameLeft, _photoButton->y() + st::profileNameTop); + _statusPosition = QPoint(infoLeft + st::profileStatusLeft, _photoButton->y() + st::profileStatusTop); + + newHeight += st::profilePhotoSize; + newHeight += st::profileMarginBottom; + newHeight += st::profileSeparator; + + newHeight += st::profileBlocksTop; + + resize(newWidth, newHeight); + update(); +} + +void CoverWidget::paintEvent(QPaintEvent *e) { + Painter p(this); + + p.fillRect(e->rect(), st::white); + + int availWidth = width() - _namePosition.x() - _photoButton->x(); + p.setFont(st::profileNameFont); + p.setPen(st::profileNameFg); + _nameText.drawLeftElided(p, _namePosition.x(), _namePosition.y(), availWidth, width()); + + p.setFont(st::profileStatusFont); + p.setPen(st::profileStatusFg); + p.drawTextLeft(_statusPosition.x(), _statusPosition.y(), width(), _statusText); +} + +void CoverWidget::updateStatusText() { + int currentTime = unixtime(); + if (_peerUser) { + _statusText = App::onlineText(_peerUser, currentTime, true); + } else if (_peerChat && _peerChat->amIn()) { + if (_peerChat->noParticipantInfo()) { + App::api()->requestFullPeer(_peer); + if (_statusText.isEmpty()) { + _statusText = lng_chat_status_members(lt_count, _peerChat->count); + } + } else { + int onlineCount = 0; + bool onlyMe = true; + for (auto i = _peerChat->participants.cbegin(), e = _peerChat->participants.cend(); i != e; ++i) { + auto onlineTill = App::onlineForSort(i.key(), currentTime); + if (onlineTill > currentTime) { + ++onlineCount; + } + } + if (onlineCount && !onlyMe) { + _statusText = lng_chat_status_members_online(lt_count, _peerChat->participants.size(), lt_count_online, onlineCount); + } else { + _statusText = lng_chat_status_members(lt_count, _peerChat->participants.size()); + } + } + } else if (isUsingMegagroupOnlineCount()) { + int onlineCount = 0; + bool onlyMe = true; + for_const (auto &user, _peerMegagroup->mgInfo->lastParticipants) { + auto onlineTill = App::onlineForSort(user, currentTime); + if (onlineTill > currentTime) { + ++onlineCount; + } + } + if (onlineCount && !onlyMe) { + _statusText = lng_chat_status_members_online(lt_count, _peerMegagroup->count, lt_count_online, onlineCount); + } else { + _statusText = lng_chat_status_members(lt_count, _peerMegagroup->count); + } + } else if (_peerChannel) { + if (_peerChannel->count > 0) { + _statusText = lng_chat_status_members(lt_count, _peerChannel->count); + } else { + _statusText = lang(_peerChannel->isMegagroup() ? lng_group_status : lng_channel_status); + } + } else { + _statusText = lang(lng_chat_status_unaccessible); + } +} + +bool CoverWidget::isUsingMegagroupOnlineCount() const { + if (!_peerMegagroup || !_peerMegagroup->amIn()) { + return false; + } + + if (_peerMegagroup->count <= 0 || _peerMegagroup->count > Global::ChatSizeMax()) { + return false; + } + + if (_peerMegagroup->mgInfo->lastParticipants.isEmpty() || _peerMegagroup->lastParticipantsCountOutdated()) { + App::api()->requestLastParticipants(_peerMegagroup); + return false; + } + + return true; +} + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_cover.h b/Telegram/SourceFiles/profile/profile_cover.h new file mode 100644 index 000000000..c09a309ca --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_cover.h @@ -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 + +namespace Profile { + +class BackButton; +class PhotoButton; + +class CoverWidget final : public TWidget { + Q_OBJECT + +public: + CoverWidget(QWidget *parent, PeerData *peer); + + // Count new height for width=newWidth and resize to it. + void resizeToWidth(int newWidth); + +public slots: + void onBack(); + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + void updateStatusText(); + bool isUsingMegagroupOnlineCount() const; + + PeerData *_peer; + UserData *_peerUser; + ChatData *_peerChat; + ChannelData *_peerChannel; + ChannelData *_peerMegagroup; + + // Top bar + ChildWidget _backButton; + QList _rightActions; + + // Cover content + ChildWidget _photoButton; + + QPoint _namePosition; + Text _nameText; + + QPoint _statusPosition; + QString _statusText; + + ChildWidget _primaryButton = { nullptr }; + ChildWidget _secondaryButton = { nullptr }; + +}; + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_inner_widget.cpp b/Telegram/SourceFiles/profile/profile_inner_widget.cpp new file mode 100644 index 000000000..7d62441d2 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_inner_widget.cpp @@ -0,0 +1,59 @@ +/* +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" + +namespace Profile { + +InnerWidget::InnerWidget(QWidget *parent, PeerData *peer) : TWidget(parent) +, _peer(peer) +, _cover(this, peer) { +} + +void InnerWidget::resizeToWidth(int newWidth, int minHeight) { + int naturalHeight = resizeGetHeight(newWidth); + _addedHeight = qMax(minHeight - naturalHeight, 0); + resize(newWidth, naturalHeight + _addedHeight); +} + +void InnerWidget::decreaseAdditionalHeight(int removeHeight) { + if (removeHeight > 0) { + resizeToWidth(width(), height() - removeHeight); + } +} + +void InnerWidget::paintEvent(QPaintEvent *e) { + Painter p(this); + + p.fillRect(e->rect(), st::white); +} + +int InnerWidget::resizeGetHeight(int newWidth) { + _cover->resizeToWidth(newWidth); + int newHeight = _cover->height(); + + return newHeight; +} + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_inner_widget.h b/Telegram/SourceFiles/profile/profile_inner_widget.h new file mode 100644 index 000000000..5a4368aa0 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_inner_widget.h @@ -0,0 +1,64 @@ +/* +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); + + // 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); + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + // Resizes content and counts natural widget height for the desired width. + int resizeGetHeight(int newWidth); + + 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; + + ChildWidget _cover; + QList _blocks; + +}; + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_widget.cpp b/Telegram/SourceFiles/profile/profile_widget.cpp new file mode 100644 index 000000000..5f7b1c7b6 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_widget.cpp @@ -0,0 +1,119 @@ +/* +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_widget.h" + +#include "profile/profile_inner_widget.h" +#include "mainwindow.h" +#include "application.h" + +namespace Profile { + +Widget::Widget(QWidget *parent, PeerData *peer) : TWidget(parent) +, _scroll(this, st::setScroll) +, _inner(this, peer) +, _sideShadow(this, st::shadowColor) { + _scroll->setWidget(_inner); + _scroll->move(0, 0); + _scroll->show(); + + _sideShadow->setVisible(!Adaptive::OneColumn()); + + connect(_scroll, SIGNAL(scrolled()), _inner, SLOT(updateSelected())); + connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); +} + +PeerData *Widget::peer() const { + return _inner->peer(); +} + +void Widget::setGeometryWithTopMoved(const QRect &newGeometry, int topDelta) { + _topDelta = topDelta; + bool willBeResized = (size() != newGeometry.size()); + if (geometry() != newGeometry) { + setGeometry(newGeometry); + } + if (!willBeResized) { + resizeEvent(nullptr); + } + _topDelta = 0; +} + +void Widget::showAnimated(SlideDirection direction, const QPixmap &oldContentCache) { + show(); + _scroll->show(); + _sideShadow->hide(); + auto myContentCache = myGrab(this); + _scroll->hide(); + _sideShadow->show(); + + _showAnimation = std_::make_unique(); + _showAnimation->setDirection(direction); + _showAnimation->setRepaintCallback(func(this, &Widget::repaintCallback)); + _showAnimation->setFinishedCallback(func(this, &Widget::showFinished)); + _showAnimation->setPixmaps(oldContentCache, myContentCache); + _showAnimation->start(); +} + +void Widget::setInnerFocus() { + _inner->setFocus(); +} + +void Widget::resizeEvent(QResizeEvent *e) { + int newScrollTop = _scroll->scrollTop() + _topDelta; + if (_scroll->size() != size()) { + _scroll->resize(size()); + if (_inner->width() != width()) { + _inner->resizeToWidth(width(), _scroll->height()); + } + } + if (!_scroll->isHidden()) { + if (_topDelta) { + _scroll->scrollToY(newScrollTop); + } + int notDisplayedAtBottom = _scroll->scrollTopMax() - _scroll->scrollTop(); + if (notDisplayedAtBottom > 0) { + _inner->decreaseAdditionalHeight(notDisplayedAtBottom); + } + } + + _sideShadow->resize(st::lineWidth, height()); + _sideShadow->moveToLeft(0, 0); +} + +void Widget::paintEvent(QPaintEvent *e) { + if (Ui::skipPaintEvent(this, e)) return; + + if (_showAnimation) { + Painter p(this); + _showAnimation->paintContents(p, e->rect()); + } +} + +void Widget::showFinished() { + if (isHidden()) return; + + App::app()->mtpUnpause(); + _scroll->show(); + setInnerFocus(); +} + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_widget.h b/Telegram/SourceFiles/profile/profile_widget.h new file mode 100644 index 000000000..13e765edc --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_widget.h @@ -0,0 +1,69 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "ui/slide_animation.h" + +class ScrollArea; + +namespace Profile { + +class InnerWidget; +class Widget final : public TWidget { + Q_OBJECT + +public: + Widget(QWidget *parent, PeerData *peer); + + PeerData *peer() const; + + // When resizing the widget with top edge moved up or down and we + // want to add this top movement to the scroll position, so inner + // content will not move. + void setGeometryWithTopMoved(const QRect &newGeometry, int topDelta); + + void showAnimated(SlideDirection direction, const QPixmap &oldContentCache); + + void setInnerFocus(); + +protected: + void resizeEvent(QResizeEvent *e) override; + void paintEvent(QPaintEvent *e) override; + +private: + // QWidget::update() method is overloaded and we need template deduction. + void repaintCallback() { + update(); + } + void showFinished(); + + ChildWidget _scroll; + ChildWidget _inner; + ChildWidget _sideShadow; + + std_::unique_ptr _showAnimation; + + // Saving here topDelta in resizeWithTopMoved() to get it passed to resizeEvent(). + int _topDelta = 0; + +}; + +} // namespace Profile diff --git a/Telegram/SourceFiles/profilewidget.cpp b/Telegram/SourceFiles/profilewidget.cpp index a5ad1cc3a..9dee29e7a 100644 --- a/Telegram/SourceFiles/profilewidget.cpp +++ b/Telegram/SourceFiles/profilewidget.cpp @@ -33,9 +33,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "apiwrap.h" #include "window/top_bar_widget.h" -ProfileInner::ProfileInner(ProfileWidget *profile, ScrollArea *scroll, PeerData *peer) : TWidget(0) +ProfileInner::ProfileInner(ProfileWidget *profile, PeerData *peer) : TWidget(0) , _profile(profile) -, _scroll(scroll) , _peer(peer->migrateTo() ? peer->migrateTo() : peer) , _peerUser(_peer->asUser()) , _peerChat(_peer->asChat()) @@ -145,7 +144,7 @@ ProfileInner::ProfileInner(ProfileWidget *profile, ScrollArea *scroll, PeerData } // profile - _nameText.setText(st::profileNameFont, _nameCache, _textNameOptions); +// _nameText.setText(st::profileNameFont, _nameCache, _textNameOptions); connect(&_uploadPhoto, SIGNAL(clicked()), this, SLOT(onUpdatePhoto())); connect(&_addParticipant, SIGNAL(clicked()), this, SLOT(onAddParticipant())); connect(&_sendMessage, SIGNAL(clicked()), this, SLOT(onSendMessage())); @@ -636,7 +635,7 @@ void ProfileInner::peerUpdated(PeerData *data) { } if (_peer->name != _nameCache) { _nameCache = _peer->name; - _nameText.setText(st::profileNameFont, _nameCache, _textNameOptions); +// _nameText.setText(st::profileNameFont, _nameCache, _textNameOptions); } } if (!_updateDelayed) { @@ -838,7 +837,7 @@ void ProfileInner::paintEvent(QPaintEvent *e) { // profile top += st::profilePadding.top(); if (_photoLink || _peerUser || (_peerChat && !_peerChat->canEdit()) || (_peerChannel && !_amCreator)) { - _peer->paintUserpic(p, st::profilePhotoSize, _left, top); +// _peer->paintUserpic(p, st::profilePhotoSize, _left, top); } else { if (a_photoOver.current() < 1) { p.drawSprite(QPoint(_left, top), st::setPhotoImg); @@ -850,44 +849,44 @@ void ProfileInner::paintEvent(QPaintEvent *e) { } } - int32 namew = _width - st::profilePhotoSize - st::profileNameLeft; +// int32 namew = _width - st::profilePhotoSize - st::profileNameLeft; p.setPen(st::black->p); if (_peer->isVerified()) { - namew -= st::verifiedCheckProfile.pxWidth() + st::verifiedCheckProfilePos.x(); - int32 cx = _left + st::profilePhotoSize + st::profileNameLeft + qMin(_nameText.maxWidth(), namew); - p.drawSprite(QPoint(cx, top + st::profileNameTop) + st::verifiedCheckProfilePos, st::verifiedCheckProfile); + //namew -= st::verifiedCheckProfile.pxWidth() + st::verifiedCheckProfilePos.x(); + //int32 cx = _left + st::profilePhotoSize + st::profileNameLeft + qMin(_nameText.maxWidth(), namew); + //p.drawSprite(QPoint(cx, top + st::profileNameTop) + st::verifiedCheckProfilePos, st::verifiedCheckProfile); } - _nameText.drawElided(p, _left + st::profilePhotoSize + st::profileNameLeft, top + st::profileNameTop, namew); +// _nameText.drawElided(p, _left + st::profilePhotoSize + st::profileNameLeft, top + st::profileNameTop, namew); - p.setFont(st::profileStatusFont->f); + //p.setFont(st::profileStatusFont->f); int32 addbyname = 0; if (_peerUser && !_peerUser->username.isEmpty()) { - addbyname = st::profileStatusTop + st::linkFont->ascent - (st::profileNameTop + st::profileNameFont->ascent); - p.setPen(st::black->p); - p.drawText(_left + st::profilePhotoSize + st::profileStatusLeft, top + st::profileStatusTop + st::linkFont->ascent, '@' + _peerUser->username); + //addbyname = st::profileStatusTop + st::linkFont->ascent - (st::profileNameTop + st::profileNameFont->ascent); + //p.setPen(st::black->p); + //p.drawText(_left + st::profilePhotoSize + st::profileStatusLeft, top + st::profileStatusTop + st::linkFont->ascent, '@' + _peerUser->username); } else if (_peerChannel && (_peerChannel->isPublic() || _peerChannel->canEditUsername())) { - addbyname = st::profileStatusTop + st::linkFont->ascent - (st::profileNameTop + st::profileNameFont->ascent); +// addbyname = st::profileStatusTop + st::linkFont->ascent - (st::profileNameTop + st::profileNameFont->ascent); } if (!_peerChannel || !_peerChannel->canViewParticipants() || _peerChannel->isMegagroup()) { - p.setPen((_peerUser && App::onlineColorUse(_peerUser, l_time) ? st::profileOnlineColor : st::profileOfflineColor)->p); - p.drawText(_left + st::profilePhotoSize + st::profileStatusLeft, top + addbyname + st::profileStatusTop + st::linkFont->ascent, _onlineText); + //p.setPen((_peerUser && App::onlineColorUse(_peerUser, l_time) ? st::profileOnlineColor : st::profileOfflineColor)->p); + //p.drawText(_left + st::profilePhotoSize + st::profileStatusLeft, top + addbyname + st::profileStatusTop + st::linkFont->ascent, _onlineText); } if (!_cancelPhoto.isHidden()) { - p.setPen(st::profileOfflineColor->p); - p.drawText(_left + st::profilePhotoSize + st::profilePhoneLeft, _cancelPhoto.y() + st::linkFont->ascent, lang(lng_settings_uploading_photo)); + //p.setPen(st::profileOfflineColor->p); + //p.drawText(_left + st::profilePhotoSize + st::profilePhoneLeft, _cancelPhoto.y() + st::linkFont->ascent, lang(lng_settings_uploading_photo)); } if (!_errorText.isEmpty()) { p.setFont(st::setErrFont->f); p.setPen(st::setErrColor->p); - p.drawText(_left + st::profilePhotoSize + st::profilePhoneLeft, _cancelPhoto.y() + st::profilePhoneFont->ascent, _errorText); +// p.drawText(_left + st::profilePhotoSize + st::profilePhoneLeft, _cancelPhoto.y() + st::profilePhoneFont->ascent, _errorText); } if (!_phoneText.isEmpty()) { p.setPen(st::black->p); p.setFont(st::linkFont->f); - p.drawText(_left + st::profilePhotoSize + st::profilePhoneLeft, top + addbyname + st::profilePhoneTop + st::profilePhoneFont->ascent, _phoneText); +// p.drawText(_left + st::profilePhotoSize + st::profilePhoneLeft, top + addbyname + st::profilePhoneTop + st::profilePhoneFont->ascent, _phoneText); } - top += st::profilePhotoSize; +// top += st::profilePhotoSize; top += st::profileButtonTop; if ((!_peerChat || _peerChat->canEdit()) && (!_peerChannel || _amCreator || (_peerChannel->canAddParticipants() && _peerChannel->isMegagroup()))) { @@ -1385,24 +1384,24 @@ void ProfileInner::resizeEvent(QResizeEvent *e) { top += st::profilePadding.top(); int32 addbyname = 0; if (_peerChannel && (_peerChannel->isPublic() || _peerChannel->canEditUsername())) { - _username.move(_left + st::profilePhotoSize + st::profileStatusLeft, top + st::profileStatusTop); - addbyname = st::profileStatusTop + st::linkFont->ascent - (st::profileNameTop + st::profileNameFont->ascent); + //_username.move(_left + st::profilePhotoSize + st::profileStatusLeft, top + st::profileStatusTop); + //addbyname = st::profileStatusTop + st::linkFont->ascent - (st::profileNameTop + st::profileNameFont->ascent); } - _members.move(_left + st::profilePhotoSize + st::profileStatusLeft, top + addbyname + st::profileStatusTop); - addbyname += st::profileStatusTop + st::linkFont->ascent - (st::profileNameTop + st::profileNameFont->ascent); + //_members.move(_left + st::profilePhotoSize + st::profileStatusLeft, top + addbyname + st::profileStatusTop); + //addbyname += st::profileStatusTop + st::linkFont->ascent - (st::profileNameTop + st::profileNameFont->ascent); if (!_admins.isHidden()) { - _admins.move(_left + st::profilePhotoSize + st::profileStatusLeft, top + addbyname + st::profileStatusTop); - addbyname += st::profileStatusTop + st::linkFont->ascent - (st::profileNameTop + st::profileNameFont->ascent); + //_admins.move(_left + st::profilePhotoSize + st::profileStatusLeft, top + addbyname + st::profileStatusTop); + //addbyname += st::profileStatusTop + st::linkFont->ascent - (st::profileNameTop + st::profileNameFont->ascent); } if ((_peerChat && _amCreator && _peerChat->canEdit()) || (_peerChannel && (_amCreator || _peerChannel->amEditor() || _peerChannel->amModerator()))) { - _cancelPhoto.move(_left + _width - _cancelPhoto.width(), top + st::profilePhotoSize - st::linkFont->height); +// _cancelPhoto.move(_left + _width - _cancelPhoto.width(), top + st::profilePhotoSize - st::linkFont->height); } else { _cancelPhoto.move(_left + _width - _cancelPhoto.width(), top + st::profilePhoneTop); - _botSettings.move(_left + st::profilePhotoSize + st::profilePhoneLeft, top + st::profileStatusTop + st::linkFont->ascent - (st::profileNameTop + st::profileNameFont->ascent) + st::profilePhoneTop); + // _botSettings.move(_left + st::profilePhotoSize + st::profilePhoneLeft, top + st::profileStatusTop + st::linkFont->ascent - (st::profileNameTop + st::profileNameFont->ascent) + st::profilePhoneTop); _botHelp.move(_botSettings.x() + (_botSettings.isHidden() ? 0 : _botSettings.width() + st::profilePhoneLeft), _botSettings.y()); } - _pinnedMessage.move(_left + st::profilePhotoSize + st::profileStatusLeft, top + addbyname + st::profileStatusTop); - top += st::profilePhotoSize; + //_pinnedMessage.move(_left + st::profilePhotoSize + st::profileStatusLeft, top + addbyname + st::profileStatusTop); + //top += st::profilePhotoSize; top += st::profileButtonTop; @@ -1514,22 +1513,22 @@ void ProfileInner::contextMenuEvent(QContextMenuEvent *e) { _menu = 0; } if (!_phoneText.isEmpty() || _peerUser) { - QRect info(_left + st::profilePhotoSize + st::profilePhoneLeft, st::profilePadding.top(), _width - st::profilePhotoSize - st::profilePhoneLeft, st::profilePhotoSize); - if (info.contains(mapFromGlobal(e->globalPos()))) { - _menu = new PopupMenu(); - if (_peerUser) { - _menu->addAction(lang(lng_profile_copy_fullname), this, SLOT(onCopyFullName()))->setEnabled(true); - } - if (!_phoneText.isEmpty()) { - _menu->addAction(lang(lng_profile_copy_phone), this, SLOT(onCopyPhone()))->setEnabled(true); - } - if (_peerUser && !_peerUser->username.isEmpty()) { - _menu->addAction(lang(lng_context_copy_mention), this, SLOT(onCopyUsername()))->setEnabled(true); - } - connect(_menu, SIGNAL(destroyed(QObject*)), this, SLOT(onMenuDestroy(QObject*))); - _menu->popup(e->globalPos()); - e->accept(); - } +// QRect info(_left + st::profilePhotoSize + st::profilePhoneLeft, st::profilePadding.top(), _width - st::profilePhotoSize - st::profilePhoneLeft, st::profilePhotoSize); + //if (info.contains(mapFromGlobal(e->globalPos()))) { + // _menu = new PopupMenu(); + // if (_peerUser) { + // _menu->addAction(lang(lng_profile_copy_fullname), this, SLOT(onCopyFullName()))->setEnabled(true); + // } + // if (!_phoneText.isEmpty()) { + // _menu->addAction(lang(lng_profile_copy_phone), this, SLOT(onCopyPhone()))->setEnabled(true); + // } + // if (_peerUser && !_peerUser->username.isEmpty()) { + // _menu->addAction(lang(lng_context_copy_mention), this, SLOT(onCopyUsername()))->setEnabled(true); + // } + // connect(_menu, SIGNAL(destroyed(QObject*)), this, SLOT(onMenuDestroy(QObject*))); + // _menu->popup(e->globalPos()); + // e->accept(); + //} } } @@ -1863,7 +1862,7 @@ QString ProfileInner::overviewLinkText(int32 type, int32 count) { ProfileWidget::ProfileWidget(QWidget *parent, PeerData *peer) : TWidget(parent) , _scroll(this, st::setScroll) -, _inner(this, &_scroll, peer) +, _inner(this, peer) , _a_show(animation(this, &ProfileWidget::step_show)) , _sideShadow(this, st::shadowColor) , _topShadow(this, st::shadowColor) diff --git a/Telegram/SourceFiles/profilewidget.h b/Telegram/SourceFiles/profilewidget.h index a98b09707..6a8e1debd 100644 --- a/Telegram/SourceFiles/profilewidget.h +++ b/Telegram/SourceFiles/profilewidget.h @@ -26,7 +26,7 @@ class ProfileInner : public TWidget, public RPCSender, public ClickHandlerHost { public: - ProfileInner(ProfileWidget *profile, ScrollArea *scroll, PeerData *peer); + ProfileInner(ProfileWidget *profile, PeerData *peer); void start(); @@ -147,7 +147,6 @@ private: bool migrateFail(const RPCError &error); ProfileWidget *_profile; - ScrollArea *_scroll; PeerData *_peer; UserData *_peerUser; diff --git a/Telegram/SourceFiles/ui/animation.cpp b/Telegram/SourceFiles/ui/animation.cpp index a9ce8f910..03b194fe9 100644 --- a/Telegram/SourceFiles/ui/animation.cpp +++ b/Telegram/SourceFiles/ui/animation.cpp @@ -115,7 +115,7 @@ namespace anim { void Animation::start() { if (!_manager) return; - _cb.start(); + _callbacks.start(); _manager->start(this); _animating = true; } @@ -237,8 +237,8 @@ QPixmap _prepareFrame(const ClipFrameRequest &request, const QImage &original, b return QPixmap::fromImage(original, Qt::ColorOnly); } -ClipReader::ClipReader(const FileLocation &location, const QByteArray &data, Callback::Creator cb) -: _cb(cb) +ClipReader::ClipReader(const FileLocation &location, const QByteArray &data, Callback &&callback) +: _callback(std_::move(callback)) , _state(ClipReading) , _width(0) , _height(0) @@ -334,7 +334,7 @@ void ClipReader::moveToNextWrite() const { void ClipReader::callback(ClipReader *reader, int32 threadIndex, ClipReaderNotification notification) { // check if reader is not deleted already if (_clipManagers.size() > threadIndex && _clipManagers.at(threadIndex)->carries(reader)) { - reader->_cb.call(notification); + reader->_callback.call(notification); } } diff --git a/Telegram/SourceFiles/ui/animation.h b/Telegram/SourceFiles/ui/animation.h index 68ccb9236..d7c3e4a14 100644 --- a/Telegram/SourceFiles/ui/animation.h +++ b/Telegram/SourceFiles/ui/animation.h @@ -203,39 +203,42 @@ public: virtual void start() {} virtual void step(Animation *a, uint64 ms, bool timer) = 0; virtual ~AnimationImplementation() {} + }; -class AnimationCreator { -public: - AnimationCreator(AnimationImplementation *ptr) : _ptr(ptr) {} - AnimationCreator(const AnimationCreator &other) : _ptr(other.create()) {} - AnimationImplementation *create() const { return getPointerAndReset(_ptr); } - ~AnimationCreator() { deleteAndMark(_ptr); } -private: - AnimationCreator &operator=(const AnimationCreator &other) = delete; - mutable AnimationImplementation *_ptr; -}; + class AnimationCallbacks { public: - AnimationCallbacks(const AnimationCreator &creator) : _implementation(creator.create()) {} + AnimationCallbacks(AnimationImplementation *implementation) : _implementation(implementation) {} + AnimationCallbacks(const AnimationCallbacks &other) = delete; + AnimationCallbacks &operator=(const AnimationCallbacks &other) = delete; + AnimationCallbacks(AnimationCallbacks &&other) : _implementation(other._implementation) { + other._implementation = nullptr; + } + AnimationCallbacks &operator=(AnimationCallbacks &&other) { + std::swap(_implementation, other._implementation); + } + void start() { _implementation->start(); } void step(Animation *a, uint64 ms, bool timer) { _implementation->step(a, ms, timer); } ~AnimationCallbacks() { deleteAndMark(_implementation); } + private: - AnimationCallbacks(const AnimationCallbacks &other); - AnimationCallbacks &operator=(const AnimationCallbacks &other) = delete; AnimationImplementation *_implementation; + }; class Animation { public: - Animation(AnimationCreator cb) : _cb(cb), _animating(false) { + Animation(AnimationCallbacks &&callbacks) + : _callbacks(std_::move(callbacks)) + , _animating(false) { } void start(); void stop(); void step(uint64 ms, bool timer = false) { - _cb.step(this, ms, timer); + _callbacks.step(this, ms, timer); } void step() { @@ -251,7 +254,7 @@ public: } private: - AnimationCallbacks _cb; + AnimationCallbacks _callbacks; bool _animating; }; @@ -279,8 +282,8 @@ private: }; template -AnimationCreator animation(Type *obj, typename AnimationCallbacksRelative::Method method) { - return AnimationCreator(new AnimationCallbacksRelative(obj, method)); +AnimationCallbacks animation(Type *obj, typename AnimationCallbacksRelative::Method method) { + return AnimationCallbacks(new AnimationCallbacksRelative(obj, method)); } template @@ -301,8 +304,8 @@ private: }; template -AnimationCreator animation(Type *obj, typename AnimationCallbacksAbsolute::Method method) { - return AnimationCreator(new AnimationCallbacksAbsolute(obj, method)); +AnimationCallbacks animation(Type *obj, typename AnimationCallbacksAbsolute::Method method) { + return AnimationCallbacks(new AnimationCallbacksAbsolute(obj, method)); } template @@ -329,8 +332,8 @@ private: }; template -AnimationCreator animation(Param param, Type *obj, typename AnimationCallbacksRelativeWithParam::Method method) { - return AnimationCreator(new AnimationCallbacksRelativeWithParam(param, obj, method)); +AnimationCallbacks animation(Param param, Type *obj, typename AnimationCallbacksRelativeWithParam::Method method) { + return AnimationCallbacks(new AnimationCallbacksRelativeWithParam(param, obj, method)); } template @@ -352,15 +355,15 @@ private: }; template -AnimationCreator animation(Param param, Type *obj, typename AnimationCallbacksAbsoluteWithParam::Method method) { - return AnimationCreator(new AnimationCallbacksAbsoluteWithParam(param, obj, method)); +AnimationCallbacks animation(Param param, Type *obj, typename AnimationCallbacksAbsoluteWithParam::Method method) { + return AnimationCallbacks(new AnimationCallbacksAbsoluteWithParam(param, obj, method)); } template class SimpleAnimation { public: - typedef Function Callback; + using Callback = Function; SimpleAnimation() : _data(0) { } @@ -385,9 +388,9 @@ public: return animating(ms) ? current() : def; } - void setup(const typename AnimType::Type &from, Callback::Creator update) { + void setup(const typename AnimType::Type &from, Callback &&update) { if (!_data) { - _data = new Data(from, update, animation(this, &SimpleAnimation::step)); + _data = new Data(from, std_::move(update), animation(this, &SimpleAnimation::step)); } else { _data->a = AnimType(from, from); } @@ -407,11 +410,11 @@ public: } private: - typedef struct _Data { - _Data(const typename AnimType::Type &from, Callback::Creator update, AnimationCreator acb) + struct Data { + Data(const typename AnimType::Type &from, Callback &&update, AnimationCallbacks &&acb) : a(from, from) - , _a(acb) - , update(update) + , _a(std_::move(acb)) + , update(std_::move(update)) , duration(0) , transition(anim::linear) { } @@ -420,7 +423,7 @@ private: Callback update; float64 duration; anim::transition transition; - } Data; + }; Data *_data; void step(float64 ms, bool timer) { @@ -506,9 +509,9 @@ class ClipReaderPrivate; class ClipReader { public: - typedef Function1 Callback; + using Callback = Function; - ClipReader(const FileLocation &location, const QByteArray &data, Callback::Creator cb); + ClipReader(const FileLocation &location, const QByteArray &data, Callback &&callback); static void callback(ClipReader *reader, int32 threadIndex, ClipReaderNotification notification); // reader can be deleted void setAutoplay() { @@ -555,7 +558,7 @@ public: private: - Callback _cb; + Callback _callback; ClipState _state; diff --git a/Telegram/SourceFiles/ui/flatbutton.cpp b/Telegram/SourceFiles/ui/flatbutton.cpp index ccf15f39c..5462b395c 100644 --- a/Telegram/SourceFiles/ui/flatbutton.cpp +++ b/Telegram/SourceFiles/ui/flatbutton.cpp @@ -335,7 +335,7 @@ void EmojiButton::paintEvent(QPaintEvent *e) { void EmojiButton::setLoading(bool loading) { if (_loading != loading) { - EnsureAnimation(a_loading, _loading ? 1. : 0., func(this, &EmojiButton::update)); + EnsureAnimation(a_loading, _loading ? 1. : 0., func(this, &EmojiButton::updateCallback)); a_loading.start(loading ? 1. : 0., st::emojiCircleDuration); _loading = loading; if (_loading) { diff --git a/Telegram/SourceFiles/ui/flatbutton.h b/Telegram/SourceFiles/ui/flatbutton.h index 4f6110843..b02bae140 100644 --- a/Telegram/SourceFiles/ui/flatbutton.h +++ b/Telegram/SourceFiles/ui/flatbutton.h @@ -151,9 +151,12 @@ private: void step_loading(uint64 ms, bool timer) { if (timer) { - update(); + updateCallback(); } } + void updateCallback() { + update(); + } }; diff --git a/Telegram/SourceFiles/ui/slide_animation.cpp b/Telegram/SourceFiles/ui/slide_animation.cpp new file mode 100644 index 000000000..ecd59f1d8 --- /dev/null +++ b/Telegram/SourceFiles/ui/slide_animation.cpp @@ -0,0 +1,97 @@ +/* +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 "ui/slide_animation.h" + +SlideAnimation::SlideAnimation() +: _animation(animation(this, &SlideAnimation::step)) { +} + +void SlideAnimation::paintContents(Painter &p, const QRect &update) const { + _animation.step(getms()); + if (a_progress.current() < 1) { + p.fillRect(update, st::white); + int retina = cIntRetinaFactor(); + int underLeft = a_coordUnder.current(); + int underWidth = _cacheUnder.width() / retina; + int underHeight = _cacheUnder.height() / retina; + QRect underDest(0, 0, underWidth + underLeft, underHeight); + QRect underSrc(-underLeft * retina, 0, (underWidth + underLeft) * retina, underHeight * retina); + p.setOpacity(1. - a_progress.current()); + p.drawPixmap(underDest, _cacheUnder, underSrc); + p.setOpacity(a_progress.current()); + } + p.drawPixmap(a_coordOver.current(), 0, _cacheOver); +} + +void SlideAnimation::setDirection(SlideDirection direction) { + _direction = direction; +} + +void SlideAnimation::setPixmaps(const QPixmap &oldContentCache, const QPixmap &newContentCache) { + _cacheUnder = oldContentCache; + _cacheOver = newContentCache; +} + +void SlideAnimation::setRepaintCallback(RepaintCallback &&callback) { + _repaintCallback = std_::move(callback); +} + +void SlideAnimation::setFinishedCallback(FinishedCallback &&callback) { + _finishedCallback = std_::move(callback); +} + +void SlideAnimation::start() { + int width = _cacheUnder.width() / cIntRetinaFactor(); + int delta = qFloor(st::slideShift * width); + a_progress = anim::fvalue(0, 1); + if (_direction == SlideDirection::FromLeft) { + std::swap(_cacheUnder, _cacheOver); + a_coordUnder = anim::ivalue(-delta, 0); + a_coordOver = anim::ivalue(0, delta); + } else { + a_coordUnder = anim::ivalue(0, -delta); + a_coordOver = anim::ivalue(delta, 0); + } + _animation.start(); +} + +void SlideAnimation::step(float64 ms, bool timer) { + float64 dt = ms / st::slideDuration; + if (dt >= 1) { + dt = 1; + if (timer) { + _animation.stop(); + a_coordUnder.finish(); + a_coordOver.finish(); + + _finishedCallback.call(); + return; + } + } + + a_coordUnder.update(dt, anim::linear); + a_coordOver.update(dt, anim::linear); + a_progress.update(dt, anim::linear); + if (timer) { + _repaintCallback.call(); + } +} diff --git a/Telegram/SourceFiles/ui/slide_animation.h b/Telegram/SourceFiles/ui/slide_animation.h new file mode 100644 index 000000000..0739cb081 --- /dev/null +++ b/Telegram/SourceFiles/ui/slide_animation.h @@ -0,0 +1,58 @@ +/* +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 + +enum class SlideDirection { + FromRight, + FromLeft, +}; + +class SlideAnimation { +public: + SlideAnimation(); + + void paintContents(Painter &p, const QRect &update) const; + + void setDirection(SlideDirection direction); + void setPixmaps(const QPixmap &oldContentCache, const QPixmap &newContentCache); + + using RepaintCallback = Function; + void setRepaintCallback(RepaintCallback &&callback); + + using FinishedCallback = Function; + void setFinishedCallback(FinishedCallback &&callback); + + void start(); + +private: + void step(float64 ms, bool timer); + + SlideDirection _direction = SlideDirection::FromRight; + + mutable Animation _animation; + QPixmap _cacheUnder, _cacheOver; + anim::ivalue a_coordUnder, a_coordOver; + anim::fvalue a_progress; + + RepaintCallback _repaintCallback; + FinishedCallback _finishedCallback; + +}; diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index cc6dc115f..71e8ad684 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -386,6 +386,18 @@ true true + + true + true + + + true + true + + + true + true + true true @@ -673,6 +685,18 @@ true true + + true + true + + + true + true + + + true + true + true true @@ -986,6 +1010,18 @@ true true + + true + true + + + true + true + + + true + true + true true @@ -1052,6 +1088,7 @@ + @@ -1125,6 +1162,9 @@ + + + true true @@ -1173,6 +1213,7 @@ + @@ -1257,6 +1298,7 @@ + @@ -1375,6 +1417,48 @@ "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" "-fstdafx.h" "-f../../SourceFiles/mtproto/session.h" + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing profile_widget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_widget.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing profile_widget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_widget.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl_debug\Debug\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing profile_widget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_widget.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" + + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing profile_inner_widget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_inner_widget.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing profile_inner_widget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_inner_widget.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl_debug\Debug\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing profile_inner_widget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_inner_widget.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" + + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing profile_cover.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_cover.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing profile_cover.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_cover.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl_debug\Debug\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing profile_cover.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_cover.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" + @@ -1537,6 +1621,7 @@ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" "-fstdafx.h" "-f../../SourceFiles/ui/twidget.h" + @@ -2391,6 +2476,7 @@ + diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 7cf860ae7..9abae748d 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -91,6 +91,9 @@ {6b33ddc0-a7e2-4961-81dc-e1b5de2e2c60} + + {b761f2a4-0e8c-4e52-b179-7a3185c046c4} + @@ -1104,6 +1107,48 @@ SourceFiles\ui\style + + SourceFiles\profile + + + GeneratedFiles\Deploy + + + GeneratedFiles\Debug + + + GeneratedFiles\Release + + + SourceFiles\ui + + + GeneratedFiles\Deploy + + + GeneratedFiles\Debug + + + GeneratedFiles\Release + + + SourceFiles\profile + + + GeneratedFiles\styles + + + SourceFiles\profile + + + GeneratedFiles\Deploy + + + GeneratedFiles\Debug + + + GeneratedFiles\Release + @@ -1277,6 +1322,12 @@ Resources\winrc + + SourceFiles\ui + + + GeneratedFiles\styles + @@ -1516,6 +1567,15 @@ Resources\langs + + SourceFiles\profile + + + SourceFiles\profile + + + SourceFiles\profile + @@ -1585,6 +1645,9 @@ SourceFiles\overview + + SourceFiles\profile + From bf8a98a13b66e44a7e688afc210c1117057e5849 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 14 May 2016 19:57:06 +0300 Subject: [PATCH 04/30] Side shadow moved to MainWidget. Testing slide animation with 3s duration. New profile widget has fixed top bar outside of scroll area. --- Telegram/Resources/basic.style | 2 +- Telegram/SourceFiles/dialogswidget.cpp | 2 +- Telegram/SourceFiles/historywidget.cpp | 12 +- Telegram/SourceFiles/historywidget.h | 4 +- Telegram/SourceFiles/intro/introwidget.cpp | 2 +- Telegram/SourceFiles/mainwidget.cpp | 20 +++- Telegram/SourceFiles/mainwidget.h | 2 + Telegram/SourceFiles/overviewwidget.cpp | 12 +- Telegram/SourceFiles/overviewwidget.h | 5 +- Telegram/SourceFiles/passcodewidget.cpp | 2 +- Telegram/SourceFiles/playerwidget.cpp | 11 +- Telegram/SourceFiles/playerwidget.h | 3 - .../SourceFiles/profile/profile_cover.cpp | 95 ++++++++-------- Telegram/SourceFiles/profile/profile_cover.h | 6 +- .../SourceFiles/profile/profile_fixed_bar.cpp | 103 ++++++++++++++++++ .../SourceFiles/profile/profile_fixed_bar.h | 59 ++++++++++ .../profile/profile_inner_widget.cpp | 22 +++- .../profile/profile_inner_widget.h | 13 ++- .../SourceFiles/profile/profile_widget.cpp | 58 ++++++---- Telegram/SourceFiles/profile/profile_widget.h | 6 +- Telegram/SourceFiles/profilewidget.cpp | 12 +- Telegram/SourceFiles/profilewidget.h | 5 +- Telegram/SourceFiles/pspecific_win.cpp | 1 - Telegram/SourceFiles/pspecific_winrt.cpp | 1 - Telegram/SourceFiles/settingswidget.cpp | 2 +- Telegram/SourceFiles/ui/slide_animation.cpp | 4 +- Telegram/SourceFiles/ui/twidget.h | 15 +++ .../SourceFiles/window/top_bar_widget.cpp | 8 +- Telegram/SourceFiles/window/top_bar_widget.h | 12 +- Telegram/Telegram.vcxproj | 27 +++++ Telegram/Telegram.vcxproj.filters | 15 +++ 31 files changed, 366 insertions(+), 175 deletions(-) create mode 100644 Telegram/SourceFiles/profile/profile_fixed_bar.cpp create mode 100644 Telegram/SourceFiles/profile/profile_fixed_bar.h diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style index 8245a6314..f3fba09aa 100644 --- a/Telegram/Resources/basic.style +++ b/Telegram/Resources/basic.style @@ -375,7 +375,7 @@ versionColor: #777; shadowColor: rgba(0, 0, 0, 24); slideDuration: 240; -slideShift: 0.3; +slideShift: 100px; slideFadeOut: 0.3; slideShadow: sprite(348px, 71px, 48px, 1px); slideFunction: transition(easeOutCirc); diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index b2de4040d..5d10a6bb0 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -1864,7 +1864,7 @@ void DialogsWidget::animShow(const QPixmap &bgAnimCache) { _cancelSearch.hide(); _newGroup.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(); diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index c8c21e839..bd80d2499 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -2767,7 +2767,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); @@ -2890,7 +2889,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*))); @@ -5038,7 +5036,7 @@ void HistoryWidget::animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTo _pinnedBar->cancel.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(); @@ -5048,10 +5046,9 @@ void HistoryWidget::animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTo } void HistoryWidget::step_show(float64 ms, bool timer) { - float64 dt = ms / st::slideDuration; + float64 dt = ms / 3000;// st::slideDuration; if (dt >= 1) { _a_show.stop(); - _sideShadow.setVisible(!Adaptive::OneColumn()); _topShadow.setVisible(_peer ? true : false); a_coordUnder.finish(); @@ -5090,14 +5087,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); } @@ -6489,8 +6484,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) { @@ -7081,7 +7074,6 @@ bool HistoryWidget::pinnedMsgVisibilityUpdated() { } connect(&_pinnedBar->cancel, SIGNAL(clicked()), this, SLOT(onPinnedHide())); _reportSpamPanel.raise(); - _sideShadow.raise(); _topShadow.raise(); updatePinnedBar(); result = true; diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index 667471617..3a5bda298 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -656,12 +656,10 @@ public: bool contentOverlapped(const QRect &globalRect); void grabStart() override { - _sideShadow.hide(); _inGrab = true; resizeEvent(0); } void grabFinish() override { - _sideShadow.setVisible(!Adaptive::OneColumn()); _inGrab = false; resizeEvent(0); } @@ -1086,7 +1084,7 @@ private: bool _saveDraftText = false; QTimer _saveDraftTimer; - PlainShadow _sideShadow, _topShadow; + PlainShadow _topShadow; bool _inGrab = false; }; diff --git a/Telegram/SourceFiles/intro/introwidget.cpp b/Telegram/SourceFiles/intro/introwidget.cpp index 8323ba7e0..09caacd8c 100644 --- a/Telegram/SourceFiles/intro/introwidget.cpp +++ b/Telegram/SourceFiles/intro/introwidget.cpp @@ -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(); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 11be42bbd..dd7f77a2f 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -48,6 +48,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org MainWidget::MainWidget(MainWindow *window) : TWidget(window) , _a_show(animation(this, &MainWidget::step_show)) +, _sideShadow(this, st::shadowColor) , _dialogs(this) , _history(this) , _player(this) @@ -387,7 +388,7 @@ QPixmap MainWidget::grabInner() { if (_overview && !_overview->isHidden()) { return myGrab(_overview); } else if (_profile && !_profile->isHidden()) { - return myGrab(_profile); + return myGrab(_profile, QRect(0, st::topBarHeight, _history->width(), _history->height() - st::topBarHeight)); } else if (Adaptive::OneColumn() && _history->isHidden()) { return myGrab(_dialogs, QRect(0, st::topBarHeight, _dialogs->width(), _dialogs->height() - st::topBarHeight)); } else if (_history->peer()) { @@ -407,6 +408,8 @@ bool MainWidget::isItemVisible(HistoryItem *item) { QPixmap MainWidget::grabTopBar() { if (!_topBar->isHidden()) { return myGrab(_topBar); + } else if (_profile) { + return myGrab(_profile, QRect(0, 0, _profile->width(), st::topBarHeight)); } else if (Adaptive::OneColumn() && _history->isHidden()) { return myGrab(_dialogs, QRect(0, 0, _dialogs->width(), st::topBarHeight)); } else { @@ -528,7 +531,9 @@ void MainWidget::noHider(HistoryHider *destroyed) { if (Adaptive::OneColumn()) { animCache = myGrab(this, QRect(0, _playerHeight, _dialogsWidth, height() - _playerHeight)); } else { + _sideShadow.hide(); animCache = myGrab(this, QRect(_dialogsWidth, _playerHeight, width() - _dialogsWidth, height() - _playerHeight)); + _sideShadow.show(); } } else { animCache = grabInner(); @@ -2050,7 +2055,9 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool bac } else if (Adaptive::OneColumn()) { animCache = myGrab(this, QRect(0, _playerHeight, _dialogsWidth, height() - _playerHeight)); } else { + _sideShadow.hide(); animCache = myGrab(this, QRect(_dialogsWidth, _playerHeight, width() - _dialogsWidth, height() - _playerHeight)); + _sideShadow.show(); } if (peerId || !Adaptive::OneColumn()) { animTopBarCache = grabTopBar(); @@ -2261,7 +2268,9 @@ void MainWidget::showPeerProfile(PeerData *peer, bool back, int32 lastScrollTop) if (Adaptive::OneColumn()) { animCache = myGrab(this, QRect(0, _playerHeight, _dialogsWidth, height() - _playerHeight)); } else { + _sideShadow.hide(); animCache = myGrab(this, QRect(_dialogsWidth, _playerHeight, width() - _dialogsWidth, height() - _playerHeight)); + _sideShadow.show(); } // QPixmap animCache = grabInner(), animTopBarCache = grabTopBar(); if (!back) { @@ -2343,6 +2352,7 @@ void MainWidget::orderWidgets() { _player->raise(); _dialogs->raise(); _mediaType->raise(); + _sideShadow.raise(); if (_hider) _hider->raise(); } @@ -2435,7 +2445,7 @@ void MainWidget::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(); @@ -2587,6 +2597,8 @@ void MainWidget::resizeEvent(QResizeEvent *e) { _dialogsWidth = chatsListWidth(width()); _dialogs->resize(_dialogsWidth, height()); _dialogs->moveToLeft(0, 0); + _sideShadow.resize(st::lineWidth, height()); + _sideShadow.moveToLeft(_dialogsWidth, 0); _player->resize(width() - _dialogsWidth, _player->height()); _player->moveToLeft(_dialogsWidth, 0); _topBar->resize(width() - _dialogsWidth, st::topBarHeight); @@ -2616,11 +2628,9 @@ void MainWidget::keyPressEvent(QKeyEvent *e) { void MainWidget::updateAdaptiveLayout() { showAll(); + _sideShadow.setVisible(!Adaptive::OneColumn()); _topBar->updateAdaptiveLayout(); _history->updateAdaptiveLayout(); - if (_overview) _overview->updateAdaptiveLayout(); -// if (_profile) _profile->updateAdaptiveLayout(); TODO - _player->updateAdaptiveLayout(); } bool MainWidget::needBackButton() { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 7bc90af75..84d9a79b3 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -552,6 +552,8 @@ private: int _dialogsWidth = st::dlgMinWidth; + PlainShadow _sideShadow; + ChildWidget _dialogs; ChildWidget _history; ChildWidget _profile = { nullptr }; diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index dcd166361..230eead86 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -1922,7 +1922,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); @@ -1930,8 +1929,6 @@ OverviewWidget::OverviewWidget(QWidget *parent, PeerData *peer, MediaOverviewTyp _scroll.move(0, 0); _inner.move(0, 0); - _sideShadow.setVisible(!Adaptive::OneColumn()); - updateScrollColors(); _scroll.show(); @@ -1983,8 +1980,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) { @@ -2148,7 +2143,7 @@ void OverviewWidget::animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimT _scroll.hide(); _topShadow.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(); @@ -2163,7 +2158,6 @@ 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(); @@ -2186,10 +2180,6 @@ void OverviewWidget::step_show(float64 ms, bool timer) { } } -void OverviewWidget::updateAdaptiveLayout() { - _sideShadow.setVisible(!Adaptive::OneColumn()); -} - void OverviewWidget::doneShow() { _scroll.show(); _scroll.scrollToY(_scrollSetAfterShow); diff --git a/Telegram/SourceFiles/overviewwidget.h b/Telegram/SourceFiles/overviewwidget.h index 001f81df4..75639b066 100644 --- a/Telegram/SourceFiles/overviewwidget.h +++ b/Telegram/SourceFiles/overviewwidget.h @@ -279,7 +279,6 @@ public: void animShow(const QPixmap &oldAnimCache, const QPixmap &bgAnimTopBarCache, bool back = false, int32 lastScrollTop = -1); void step_show(float64 ms, bool timer); - void updateAdaptiveLayout(); void doneShow(); void mediaOverviewUpdated(PeerData *peer, MediaOverviewType type); @@ -300,12 +299,10 @@ public: void updateAfterDrag(); void grabStart() override { - _sideShadow.hide(); _inGrab = true; resizeEvent(0); } void grabFinish() override { - _sideShadow.setVisible(!Adaptive::OneColumn()); _inGrab = false; resizeEvent(0); } @@ -354,7 +351,7 @@ private: int32 _selCount; - PlainShadow _sideShadow, _topShadow; + PlainShadow _topShadow; bool _inGrab; }; diff --git a/Telegram/SourceFiles/passcodewidget.cpp b/Telegram/SourceFiles/passcodewidget.cpp index 63e3e4810..ef4b8c9ed 100644 --- a/Telegram/SourceFiles/passcodewidget.cpp +++ b/Telegram/SourceFiles/passcodewidget.cpp @@ -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(); diff --git a/Telegram/SourceFiles/playerwidget.cpp b/Telegram/SourceFiles/playerwidget.cpp index a8511b6d4..ca697b771 100644 --- a/Telegram/SourceFiles/playerwidget.cpp +++ b/Telegram/SourceFiles/playerwidget.cpp @@ -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(); } diff --git a/Telegram/SourceFiles/playerwidget.h b/Telegram/SourceFiles/playerwidget.h index c5286fca5..4eda5b0d7 100644 --- a/Telegram/SourceFiles/playerwidget.h +++ b/Telegram/SourceFiles/playerwidget.h @@ -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; - }; diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp index c73d0fa15..98ba80f27 100644 --- a/Telegram/SourceFiles/profile/profile_cover.cpp +++ b/Telegram/SourceFiles/profile/profile_cover.cpp @@ -25,39 +25,49 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "lang.h" #include "apiwrap.h" #include "mainwidget.h" +#include "mainwindow.h" namespace Profile { +namespace { -class BackButton final : public Button { +class OnlineCounter { public: - BackButton(QWidget *parent) : Button(parent) { - setCursor(style::cur_pointer); + OnlineCounter() : _currentTime(unixtime()), _self(App::self()) { } - - void resizeToWidth(int newWidth) { - resize(newWidth, st::profileTopBarHeight); + void feedUser(UserData *user) { + if (App::onlineForSort(user, _currentTime) > _currentTime) { + ++_result; + if (user != _self) { + _onlyMe = false; + } + } } - -protected: - void paintEvent(QPaintEvent *e) { - Painter p(this); - - 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)); + QString result(int fullCount) const { + if (_result > 0 && !_onlyMe) { + return lng_chat_status_members_online(lt_count, fullCount, lt_count_online, _result); + } + return lng_chat_status_members(lt_count, fullCount); } private: + bool _onlyMe = true; + int _result = 0; + int _currentTime; + UserData *_self; }; +} // namespace + class PhotoButton final : public Button { public: PhotoButton(QWidget *parent, PeerData *peer) : Button(parent), _peer(peer) { resize(st::profilePhotoSize, st::profilePhotoSize); } + void photoUpdated() { + bool hasPhoto = (_peer->photoId && _peer->photoId != UnknownPeerPhotoId); + setCursor(hasPhoto ? style::cur_pointer : style::cur_default); + } protected: void paintEvent(QPaintEvent *e) { @@ -77,29 +87,29 @@ CoverWidget::CoverWidget(QWidget *parent, PeerData *peer) : TWidget(parent) , _peerChat(peer->asChat()) , _peerChannel(peer->asChannel()) , _peerMegagroup(peer->isMegagroup() ? _peerChannel : nullptr) -, _backButton(this) , _photoButton(this, peer) { setAttribute(Qt::WA_OpaquePaintEvent); - _backButton->moveToLeft(0, 0); - connect(_backButton, SIGNAL(clicked()), this, SLOT(onBack())); + _photoButton->photoUpdated(); + connect(_photoButton, SIGNAL(clicked()), this, SLOT(onPhotoShow())); _nameText.setText(st::profileNameFont, App::peerName(_peer)); updateStatusText(); } -void CoverWidget::onBack() { - App::main()->showBackFromStack(); +void CoverWidget::onPhotoShow() { + PhotoData *photo = (_peer->photoId && _peer->photoId != UnknownPeerPhotoId) ? App::photo(_peer->photoId) : nullptr; + if ((_peer->photoId == UnknownPeerPhotoId) || (_peer->photoId && !photo->date)) { + App::api()->requestFullPeer(_peer); + } + if (photo && photo->date) { + App::wnd()->showPhoto(photo, _peer); + } } void CoverWidget::resizeToWidth(int newWidth) { int newHeight = 0; - // Top bar - _backButton->resizeToWidth(newWidth); - newHeight += _backButton->height(); - - // Cover content newHeight += st::profileMarginTop; _photoButton->moveToLeft(st::profilePhotoLeft, newHeight); @@ -143,34 +153,19 @@ void CoverWidget::updateStatusText() { _statusText = lng_chat_status_members(lt_count, _peerChat->count); } } else { - int onlineCount = 0; - bool onlyMe = true; - for (auto i = _peerChat->participants.cbegin(), e = _peerChat->participants.cend(); i != e; ++i) { - auto onlineTill = App::onlineForSort(i.key(), currentTime); - if (onlineTill > currentTime) { - ++onlineCount; - } - } - if (onlineCount && !onlyMe) { - _statusText = lng_chat_status_members_online(lt_count, _peerChat->participants.size(), lt_count_online, onlineCount); - } else { - _statusText = lng_chat_status_members(lt_count, _peerChat->participants.size()); + OnlineCounter counter; + auto &participants = _peerChat->participants; + for (auto i = participants.cbegin(), e = participants.cend(); i != e; ++i) { + counter.feedUser(i.key()); } + _statusText = counter.result(participants.size()); } } else if (isUsingMegagroupOnlineCount()) { - int onlineCount = 0; - bool onlyMe = true; - for_const (auto &user, _peerMegagroup->mgInfo->lastParticipants) { - auto onlineTill = App::onlineForSort(user, currentTime); - if (onlineTill > currentTime) { - ++onlineCount; - } - } - if (onlineCount && !onlyMe) { - _statusText = lng_chat_status_members_online(lt_count, _peerMegagroup->count, lt_count_online, onlineCount); - } else { - _statusText = lng_chat_status_members(lt_count, _peerMegagroup->count); + OnlineCounter counter; + for_const (auto user, _peerMegagroup->mgInfo->lastParticipants) { + counter.feedUser(user); } + _statusText = counter.result(_peerMegagroup->count); } else if (_peerChannel) { if (_peerChannel->count > 0) { _statusText = lng_chat_status_members(lt_count, _peerChannel->count); diff --git a/Telegram/SourceFiles/profile/profile_cover.h b/Telegram/SourceFiles/profile/profile_cover.h index c09a309ca..1f06abcca 100644 --- a/Telegram/SourceFiles/profile/profile_cover.h +++ b/Telegram/SourceFiles/profile/profile_cover.h @@ -35,7 +35,7 @@ public: void resizeToWidth(int newWidth); public slots: - void onBack(); + void onPhotoShow(); protected: void paintEvent(QPaintEvent *e) override; @@ -50,10 +50,6 @@ private: ChannelData *_peerChannel; ChannelData *_peerMegagroup; - // Top bar - ChildWidget _backButton; - QList _rightActions; - // Cover content ChildWidget _photoButton; diff --git a/Telegram/SourceFiles/profile/profile_fixed_bar.cpp b/Telegram/SourceFiles/profile/profile_fixed_bar.cpp new file mode 100644 index 000000000..785f5971b --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_fixed_bar.cpp @@ -0,0 +1,103 @@ +/* +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" + +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) { + 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)); + } + +private: + +}; + +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())); +} + +void FixedBar::onBack() { + App::main()->showBackFromStack(); +} + +void FixedBar::resizeToWidth(int newWidth) { + int newHeight = 0; + + _backButton->resizeToWidth(newWidth); + 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(); + } + show(); + } +} + +void FixedBar::mousePressEvent(QMouseEvent *e) { + if (e->button() == Qt::LeftButton) { + onBack(); + } else { + TWidget::mousePressEvent(e); + } +} + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_fixed_bar.h b/Telegram/SourceFiles/profile/profile_fixed_bar.h new file mode 100644 index 000000000..2c42ff67a --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_fixed_bar.h @@ -0,0 +1,59 @@ +/* +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 BackButton; + +class FixedBar final : public TWidget { + 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); + +protected: + void mousePressEvent(QMouseEvent *e) override; + +public slots: + void onBack(); + +private: + PeerData *_peer; + UserData *_peerUser; + ChatData *_peerChat; + ChannelData *_peerChannel; + ChannelData *_peerMegagroup; + + ChildWidget _backButton; + QList _rightActions; + + bool _animatingMode = false; + +}; + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_inner_widget.cpp b/Telegram/SourceFiles/profile/profile_inner_widget.cpp index 7d62441d2..8a6cf4fc4 100644 --- a/Telegram/SourceFiles/profile/profile_inner_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_inner_widget.cpp @@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_profile.h" #include "profile/profile_cover.h" +#include "apiwrap.h" namespace Profile { @@ -37,10 +38,25 @@ void InnerWidget::resizeToWidth(int newWidth, int minHeight) { resize(newWidth, naturalHeight + _addedHeight); } -void InnerWidget::decreaseAdditionalHeight(int removeHeight) { - if (removeHeight > 0) { - resizeToWidth(width(), height() - removeHeight); +void InnerWidget::setVisibleTopBottom(int visibleTop, int visibleBottom) { + _visibleTop = visibleTop; + _visibleBottom = visibleBottom; + + int notDisplayedAtBottom = height() - _visibleBottom; + if (notDisplayedAtBottom > 0) { + decreaseAdditionalHeight(notDisplayedAtBottom); } + + //loadProfilePhotos(_visibleTop); + if (peer()->isMegagroup() && !peer()->asChannel()->mgInfo->lastParticipants.isEmpty() && peer()->asChannel()->mgInfo->lastParticipants.size() < peer()->asChannel()->count) { + if (_visibleTop + (PreloadHeightsCount + 1) * (_visibleBottom - _visibleTop) > height()) { + App::api()->requestLastParticipants(peer()->asChannel(), false); + } + } +} + +void InnerWidget::decreaseAdditionalHeight(int removeHeight) { + resizeToWidth(width(), height() - removeHeight); } void InnerWidget::paintEvent(QPaintEvent *e) { diff --git a/Telegram/SourceFiles/profile/profile_inner_widget.h b/Telegram/SourceFiles/profile/profile_inner_widget.h index 5a4368aa0..e91c018ea 100644 --- a/Telegram/SourceFiles/profile/profile_inner_widget.h +++ b/Telegram/SourceFiles/profile/profile_inner_widget.h @@ -38,10 +38,8 @@ public: // Count new height for width=newWidth and resize to it. void resizeToWidth(int newWidth, int minHeight); - // 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); + // Updates the area that is visible inside the scroll container. + void setVisibleTopBottom(int visibleTop, int visibleBottom); protected: void paintEvent(QPaintEvent *e) override; @@ -50,11 +48,18 @@ private: // Resizes content and counts natural widget height for the desired width. int resizeGetHeight(int newWidth); + // 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 _cover; QList _blocks; diff --git a/Telegram/SourceFiles/profile/profile_widget.cpp b/Telegram/SourceFiles/profile/profile_widget.cpp index 5f7b1c7b6..05458068d 100644 --- a/Telegram/SourceFiles/profile/profile_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_widget.cpp @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "profile/profile_widget.h" +#include "profile/profile_fixed_bar.h" #include "profile/profile_inner_widget.h" #include "mainwindow.h" #include "application.h" @@ -28,14 +29,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Profile { Widget::Widget(QWidget *parent, PeerData *peer) : TWidget(parent) +, _fixedBar(this, peer) , _scroll(this, st::setScroll) -, _inner(this, peer) -, _sideShadow(this, st::shadowColor) { - _scroll->setWidget(_inner); - _scroll->move(0, 0); - _scroll->show(); +, _inner(this, peer) { + _fixedBar->move(0, 0); + _fixedBar->resizeToWidth(width()); + _fixedBar->show(); - _sideShadow->setVisible(!Adaptive::OneColumn()); + _scroll->setWidget(_inner); + _scroll->move(0, _fixedBar->height()); + _scroll->show(); connect(_scroll, SIGNAL(scrolled()), _inner, SLOT(updateSelected())); connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); @@ -58,12 +61,13 @@ void Widget::setGeometryWithTopMoved(const QRect &newGeometry, int topDelta) { } void Widget::showAnimated(SlideDirection direction, const QPixmap &oldContentCache) { - show(); - _scroll->show(); - _sideShadow->hide(); + _showAnimation = nullptr; + + showChildren(); + _fixedBar->setAnimatingMode(false); auto myContentCache = myGrab(this); - _scroll->hide(); - _sideShadow->show(); + hideChildren(); + _fixedBar->setAnimatingMode(true); _showAnimation = std_::make_unique(); _showAnimation->setDirection(direction); @@ -71,6 +75,8 @@ void Widget::showAnimated(SlideDirection direction, const QPixmap &oldContentCac _showAnimation->setFinishedCallback(func(this, &Widget::showFinished)); _showAnimation->setPixmaps(oldContentCache, myContentCache); _showAnimation->start(); + + show(); } void Widget::setInnerFocus() { @@ -79,24 +85,20 @@ void Widget::setInnerFocus() { void Widget::resizeEvent(QResizeEvent *e) { int newScrollTop = _scroll->scrollTop() + _topDelta; - if (_scroll->size() != size()) { - _scroll->resize(size()); - if (_inner->width() != width()) { - _inner->resizeToWidth(width(), _scroll->height()); - } + _fixedBar->resizeToWidth(width()); + + QSize scrollSize(width(), height() - _fixedBar->height()); + if (_scroll->size() != scrollSize) { + _scroll->resize(scrollSize); + _inner->resizeToWidth(scrollSize.width(), _scroll->height()); } if (!_scroll->isHidden()) { if (_topDelta) { _scroll->scrollToY(newScrollTop); } - int notDisplayedAtBottom = _scroll->scrollTopMax() - _scroll->scrollTop(); - if (notDisplayedAtBottom > 0) { - _inner->decreaseAdditionalHeight(notDisplayedAtBottom); - } + int scrollTop = _scroll->scrollTop(); + _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); } - - _sideShadow->resize(st::lineWidth, height()); - _sideShadow->moveToLeft(0, 0); } void Widget::paintEvent(QPaintEvent *e) { @@ -108,11 +110,19 @@ void Widget::paintEvent(QPaintEvent *e) { } } +void Widget::onScroll() { + int scrollTop = _scroll->scrollTop(); + _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); +} + void Widget::showFinished() { if (isHidden()) return; App::app()->mtpUnpause(); - _scroll->show(); + + showChildren(); + _fixedBar->setAnimatingMode(false); + setInnerFocus(); } diff --git a/Telegram/SourceFiles/profile/profile_widget.h b/Telegram/SourceFiles/profile/profile_widget.h index 13e765edc..cb27e3fcf 100644 --- a/Telegram/SourceFiles/profile/profile_widget.h +++ b/Telegram/SourceFiles/profile/profile_widget.h @@ -27,6 +27,7 @@ class ScrollArea; namespace Profile { class InnerWidget; +class FixedBar; class Widget final : public TWidget { Q_OBJECT @@ -48,6 +49,9 @@ protected: void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; +private slots: + void onScroll(); + private: // QWidget::update() method is overloaded and we need template deduction. void repaintCallback() { @@ -55,9 +59,9 @@ private: } void showFinished(); + ChildWidget _fixedBar; ChildWidget _scroll; ChildWidget _inner; - ChildWidget _sideShadow; std_::unique_ptr _showAnimation; diff --git a/Telegram/SourceFiles/profilewidget.cpp b/Telegram/SourceFiles/profilewidget.cpp index 9dee29e7a..a6bd9b457 100644 --- a/Telegram/SourceFiles/profilewidget.cpp +++ b/Telegram/SourceFiles/profilewidget.cpp @@ -1864,7 +1864,6 @@ ProfileWidget::ProfileWidget(QWidget *parent, PeerData *peer) : TWidget(parent) , _scroll(this, st::setScroll) , _inner(this, peer) , _a_show(animation(this, &ProfileWidget::step_show)) -, _sideShadow(this, st::shadowColor) , _topShadow(this, st::shadowColor) , _inGrab(false) { _scroll.setWidget(&_inner); @@ -1872,8 +1871,6 @@ ProfileWidget::ProfileWidget(QWidget *parent, PeerData *peer) : TWidget(parent) _inner.move(0, 0); _scroll.show(); - _sideShadow.setVisible(!Adaptive::OneColumn()); - connect(&_scroll, SIGNAL(scrolled()), &_inner, SLOT(updateSelected())); connect(&_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); } @@ -1906,8 +1903,6 @@ void ProfileWidget::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 ProfileWidget::mousePressEvent(QMouseEvent *e) { @@ -1988,7 +1983,7 @@ void ProfileWidget::animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTo _scroll.hide(); _topShadow.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(); @@ -2003,7 +1998,6 @@ void ProfileWidget::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(); @@ -2055,10 +2049,6 @@ void ProfileWidget::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) } } -void ProfileWidget::updateAdaptiveLayout() { - _sideShadow.setVisible(!Adaptive::OneColumn()); -} - PeerData *ProfileWidget::ui_getPeerForMouseAction() { return _inner.peer(); } diff --git a/Telegram/SourceFiles/profilewidget.h b/Telegram/SourceFiles/profilewidget.h index 6a8e1debd..79de732d3 100644 --- a/Telegram/SourceFiles/profilewidget.h +++ b/Telegram/SourceFiles/profilewidget.h @@ -257,15 +257,12 @@ public: void updateNotifySettings(); void mediaOverviewUpdated(PeerData *peer, MediaOverviewType type); - void updateAdaptiveLayout(); void grabStart() override { - _sideShadow.hide(); _inGrab = true; resizeEvent(0); } void grabFinish() override { - _sideShadow.setVisible(!Adaptive::OneColumn()); _inGrab = false; resizeEvent(0); } @@ -294,7 +291,7 @@ private: anim::ivalue a_coordUnder, a_coordOver; anim::fvalue a_shadow; - PlainShadow _sideShadow, _topShadow; + PlainShadow _topShadow; bool _inGrab; }; diff --git a/Telegram/SourceFiles/pspecific_win.cpp b/Telegram/SourceFiles/pspecific_win.cpp index be6675c1d..6718db848 100644 --- a/Telegram/SourceFiles/pspecific_win.cpp +++ b/Telegram/SourceFiles/pspecific_win.cpp @@ -851,7 +851,6 @@ namespace { _psShadowWindows.setColor(_shInactive); } if (Global::started()) { - QMetaObject::invokeMethod(App::wnd(), "updateCounter", Qt::QueuedConnection); App::wnd()->update(); } } return false; diff --git a/Telegram/SourceFiles/pspecific_winrt.cpp b/Telegram/SourceFiles/pspecific_winrt.cpp index 76a8eb1cf..79858d2b7 100644 --- a/Telegram/SourceFiles/pspecific_winrt.cpp +++ b/Telegram/SourceFiles/pspecific_winrt.cpp @@ -852,7 +852,6 @@ namespace { // _psShadowWindows.setColor(_shInactive); //} if (Global::started()) { - QMetaObject::invokeMethod(App::wnd(), "updateCounter", Qt::QueuedConnection); App::wnd()->update(); } } return false; diff --git a/Telegram/SourceFiles/settingswidget.cpp b/Telegram/SourceFiles/settingswidget.cpp index 418de8405..c7aaeec12 100644 --- a/Telegram/SourceFiles/settingswidget.cpp +++ b/Telegram/SourceFiles/settingswidget.cpp @@ -1864,7 +1864,7 @@ void SettingsWidget::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(); diff --git a/Telegram/SourceFiles/ui/slide_animation.cpp b/Telegram/SourceFiles/ui/slide_animation.cpp index ecd59f1d8..266a1ad16 100644 --- a/Telegram/SourceFiles/ui/slide_animation.cpp +++ b/Telegram/SourceFiles/ui/slide_animation.cpp @@ -61,7 +61,7 @@ void SlideAnimation::setFinishedCallback(FinishedCallback &&callback) { void SlideAnimation::start() { int width = _cacheUnder.width() / cIntRetinaFactor(); - int delta = qFloor(st::slideShift * width); + int delta = st::slideShift; a_progress = anim::fvalue(0, 1); if (_direction == SlideDirection::FromLeft) { std::swap(_cacheUnder, _cacheOver); @@ -75,7 +75,7 @@ void SlideAnimation::start() { } void SlideAnimation::step(float64 ms, bool timer) { - float64 dt = ms / st::slideDuration; + float64 dt = ms / 3000;// st::slideDuration; if (dt >= 1) { dt = 1; if (timer) { diff --git a/Telegram/SourceFiles/ui/twidget.h b/Telegram/SourceFiles/ui/twidget.h index 8a803ad0a..3af3f6ec9 100644 --- a/Telegram/SourceFiles/ui/twidget.h +++ b/Telegram/SourceFiles/ui/twidget.h @@ -184,6 +184,21 @@ public: bool inFocusChain() const; + void hideChildren() { + for (auto child : children()) { + if (auto widget = qobject_cast(child)) { + widget->hide(); + } + } + } + void showChildren() { + for (auto child : children()) { + if (auto widget = qobject_cast(child)) { + widget->show(); + } + } + } + }; void myEnsureResized(QWidget *target); diff --git a/Telegram/SourceFiles/window/top_bar_widget.cpp b/Telegram/SourceFiles/window/top_bar_widget.cpp index 4f4ba9e76..0b1fe6e49 100644 --- a/Telegram/SourceFiles/window/top_bar_widget.cpp +++ b/Telegram/SourceFiles/window/top_bar_widget.cpp @@ -51,8 +51,7 @@ TopBarWidget::TopBarWidget(MainWidget *w) : TWidget(w) , _addContact(this, lang(lng_profile_add_contact), st::topBarButton) , _deleteContact(this, lang(lng_profile_delete_contact), st::topBarButton) , _mediaType(this, lang(lng_media_type), st::topBarButton) -, _search(this, st::topBarSearch) -, _sideShadow(this, st::shadowColor) { +, _search(this, st::topBarSearch) { connect(_forward, SIGNAL(clicked()), this, SLOT(onForwardSelection())); connect(_delete, SIGNAL(clicked()), this, SLOT(onDeleteSelection())); @@ -263,9 +262,6 @@ void TopBarWidget::resizeEvent(QResizeEvent *e) { if (!_addContact->isHidden()) _addContact->move(r -= _addContact->width(), 0); if (!_mediaType->isHidden()) _mediaType->move(r -= _mediaType->width(), 0); _search->move(width() - (_info->isHidden() ? st::topBarForwardPadding.right() : _info->width()) - _search->width(), 0); - - _sideShadow->resize(st::lineWidth, height()); - _sideShadow->moveToLeft(0, 0); } void TopBarWidget::startAnim() { @@ -285,7 +281,6 @@ void TopBarWidget::startAnim() { void TopBarWidget::stopAnim() { _animating = false; - _sideShadow->setVisible(!Adaptive::OneColumn()); showAll(); } @@ -363,7 +358,6 @@ void TopBarWidget::showAll() { _info->hide(); } } - _sideShadow->setVisible(!Adaptive::OneColumn()); resizeEvent(nullptr); } diff --git a/Telegram/SourceFiles/window/top_bar_widget.h b/Telegram/SourceFiles/window/top_bar_widget.h index 3c94114e6..69c416e50 100644 --- a/Telegram/SourceFiles/window/top_bar_widget.h +++ b/Telegram/SourceFiles/window/top_bar_widget.h @@ -47,7 +47,6 @@ public: void resizeEvent(QResizeEvent *e) override; void step_appearance(float64 ms, bool timer); - void enableShadow(bool enable = true); void startAnim(); void stopAnim(); @@ -58,14 +57,7 @@ public: FlatButton *mediaTypeButton(); - void grabStart() override { - _sideShadow->hide(); - } - void grabFinish() override { - _sideShadow->setVisible(!Adaptive::OneColumn()); - } - - public slots: +public slots: void onForwardSelection(); void onDeleteSelection(); @@ -107,8 +99,6 @@ private: ChildWidget _search; - ChildWidget _sideShadow; - }; } // namespace Window diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index 71e8ad684..96170141c 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -390,6 +390,10 @@ true true + + true + true + true true @@ -689,6 +693,10 @@ true true + + true + true + true true @@ -1014,6 +1022,10 @@ true true + + true + true + true true @@ -1163,6 +1175,7 @@ + @@ -1459,6 +1472,20 @@ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_cover.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing profile_fixed_bar.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_fixed_bar.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing profile_fixed_bar.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_fixed_bar.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl_debug\Debug\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing profile_fixed_bar.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_fixed_bar.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" + diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 9abae748d..219751604 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -1149,6 +1149,18 @@ GeneratedFiles\Release + + SourceFiles\profile + + + GeneratedFiles\Deploy + + + GeneratedFiles\Debug + + + GeneratedFiles\Release + @@ -1576,6 +1588,9 @@ SourceFiles\profile + + SourceFiles\profile + From 1d42144c95ea7986b93a0012a21bddae07b8671c Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 19 May 2016 15:03:51 +0300 Subject: [PATCH 05/30] Profile done as a new generic Window::SectionWidget. Slide animation reimplemented. --- README.md | 2 +- Telegram/Resources/basic.style | 3 +- Telegram/SourceFiles/boxes/contactsbox.cpp | 2 +- .../SourceFiles/codegen/style/processor.cpp | 5 +- .../codegen/style/sprite_generator.cpp | 7 +- .../codegen/style/sprite_generator.h | 3 +- Telegram/SourceFiles/dialogswidget.cpp | 54 +-- Telegram/SourceFiles/dialogswidget.h | 9 +- Telegram/SourceFiles/facades.cpp | 11 +- Telegram/SourceFiles/facades.h | 8 + Telegram/SourceFiles/historywidget.cpp | 91 +++-- Telegram/SourceFiles/historywidget.h | 15 +- Telegram/SourceFiles/mainwidget.cpp | 333 ++++++++++-------- Telegram/SourceFiles/mainwidget.h | 53 ++- Telegram/SourceFiles/mediaview.cpp | 2 +- Telegram/SourceFiles/overviewwidget.cpp | 93 +++-- Telegram/SourceFiles/overviewwidget.h | 17 +- .../profile/profile_inner_widget.cpp | 6 +- .../profile/profile_inner_widget.h | 1 + .../profile/profile_section_memento.cpp | 35 ++ .../profile/profile_section_memento.h | 44 +++ .../SourceFiles/profile/profile_widget.cpp | 103 +++--- Telegram/SourceFiles/profile/profile_widget.h | 44 +-- Telegram/SourceFiles/profilewidget.cpp | 2 +- Telegram/SourceFiles/structs.cpp | 2 +- Telegram/SourceFiles/ui/animation.h | 29 +- Telegram/SourceFiles/ui/twidget.cpp | 42 +++ Telegram/SourceFiles/ui/twidget.h | 35 +- Telegram/SourceFiles/window/section_memento.h | 33 ++ .../SourceFiles/window/section_widget.cpp | 82 +++++ Telegram/SourceFiles/window/section_widget.h | 108 ++++++ .../{ui => window}/slide_animation.cpp | 28 +- .../{ui => window}/slide_animation.h | 6 + .../SourceFiles/window/top_bar_widget.cpp | 46 +-- Telegram/SourceFiles/window/top_bar_widget.h | 1 - Telegram/Telegram.vcxproj | 34 +- Telegram/Telegram.vcxproj.filters | 36 +- 37 files changed, 1052 insertions(+), 373 deletions(-) create mode 100644 Telegram/SourceFiles/profile/profile_section_memento.cpp create mode 100644 Telegram/SourceFiles/profile/profile_section_memento.h create mode 100644 Telegram/SourceFiles/window/section_memento.h create mode 100644 Telegram/SourceFiles/window/section_widget.cpp create mode 100644 Telegram/SourceFiles/window/section_widget.h rename Telegram/SourceFiles/{ui => window}/slide_animation.cpp (83%) rename Telegram/SourceFiles/{ui => window}/slide_animation.h (93%) diff --git a/README.md b/README.md index eb79d16a7..c56329b1f 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style index f3fba09aa..72b2a6289 100644 --- a/Telegram/Resources/basic.style +++ b/Telegram/Resources/basic.style @@ -373,12 +373,13 @@ statusFont: font(fsize); versionColor: #777; shadowColor: rgba(0, 0, 0, 24); +shadowToggleDuration: 200; slideDuration: 240; slideShift: 100px; slideFadeOut: 0.3; slideShadow: sprite(348px, 71px, 48px, 1px); -slideFunction: transition(easeOutCirc); +slideFunction: transition(linear); btnDefIconed: iconedButton { color: white; diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp index 92540758a..b79f89819 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.cpp +++ b/Telegram/SourceFiles/boxes/contactsbox.cpp @@ -1976,7 +1976,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); } } diff --git a/Telegram/SourceFiles/codegen/style/processor.cpp b/Telegram/SourceFiles/codegen/style/processor.cpp index 5c385f25f..39f02c41e 100644 --- a/Telegram/SourceFiles/codegen/style/processor.cpp +++ b/Telegram/SourceFiles/codegen/style/processor.cpp @@ -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", - !options_.rebuildDependencies, // forceReGenerate + forceReGenerate }; - SpriteGenerator spriteGenerator(module); + SpriteGenerator spriteGenerator(module, forceReGenerate); if (!spriteGenerator.writeSprites()) { return false; } diff --git a/Telegram/SourceFiles/codegen/style/sprite_generator.cpp b/Telegram/SourceFiles/codegen/style/sprite_generator.cpp index cc3c2c1bc..2113fa833 100644 --- a/Telegram/SourceFiles/codegen/style/sprite_generator.cpp +++ b/Telegram/SourceFiles/codegen/style/sprite_generator.cpp @@ -48,8 +48,9 @@ constexpr int kErrorCouldNotWrite = 845; } // namespace -SpriteGenerator::SpriteGenerator(const structure::Module &module) -: module_(module) +SpriteGenerator::SpriteGenerator(const structure::Module &module, bool forceReGenerate) +: forceReGenerate_(forceReGenerate) +, module_(module) , 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; } diff --git a/Telegram/SourceFiles/codegen/style/sprite_generator.h b/Telegram/SourceFiles/codegen/style/sprite_generator.h index 832a71984..fc1458db3 100644 --- a/Telegram/SourceFiles/codegen/style/sprite_generator.h +++ b/Telegram/SourceFiles/codegen/style/sprite_generator.h @@ -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 sprites_; diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index 948ec9619..348bc8907 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -661,7 +661,7 @@ bool DialogsInner::menuPeerMuted() { void DialogsInner::onContextProfile() { if (!_menuPeer) return; - App::main()->showPeerProfile(_menuPeer); + Ui::showPeerProfile(_menuPeer); } void DialogsInner::onContextToggleNotifications() { @@ -1849,27 +1849,30 @@ 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(-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); + int delta = st::slideShift; + a_progress = anim::fvalue(0, 1); + if (direction == Window::SlideDirection::FromLeft) { + a_coordUnder = anim::ivalue(0, delta); + a_coordOver = anim::ivalue(-delta, 0); + } else { + a_coordUnder = anim::ivalue(0, -delta); + a_coordOver = anim::ivalue(delta, 0); + } _a_show.start(); - - show(); } void DialogsWidget::step_show(float64 ms, bool timer) { @@ -1879,7 +1882,7 @@ void DialogsWidget::step_show(float64 ms, bool timer) { a_coordUnder.finish(); a_coordOver.finish(); - a_shadow.finish(); + a_progress.finish(); _cacheUnder = _cacheOver = QPixmap(); @@ -1894,7 +1897,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(); } @@ -2535,15 +2538,24 @@ void DialogsWidget::paintEvent(QPaintEvent *e) { p.setClipRect(r); } if (_a_show.animating()) { - 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.setOpacity(1); + int retina = cIntRetinaFactor(); + if (a_progress.current() < 1) { + int underLeft = a_coordUnder.current(); + int underWidth = _cacheUnder.width() / retina; + int underHeight = _cacheUnder.height() / retina; + p.fillRect(r, st::white); + QRect underDst(0, 0, underWidth + underLeft, underHeight); + QRect underSrc(-underLeft * retina, 0, (underWidth + underLeft) * retina, underHeight * retina); + p.setOpacity(1. - a_progress.current()); + p.drawPixmap(underDst, _cacheUnder, underSrc); + p.setOpacity(a_progress.current()); } - 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()); + int overLeft = a_coordOver.current(); + int overWidth = _cacheOver.width() / retina; + int overHeight = _cacheOver.height() / retina; + QRect overDst(overLeft, 0, overWidth - overLeft, overHeight); + QRect overSrc(0, 0, (overWidth - overLeft) * retina, overHeight * retina); + p.drawPixmap(overDst, _cacheOver, overSrc); return; } QRect above(0, 0, width(), _scroll.y()); diff --git a/Telegram/SourceFiles/dialogswidget.h b/Telegram/SourceFiles/dialogswidget.h index fe5b33e62..b12e613c0 100644 --- a/Telegram/SourceFiles/dialogswidget.h +++ b/Telegram/SourceFiles/dialogswidget.h @@ -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; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 6cc869d32..defd46993 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -20,6 +20,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" +#include "profile/profile_section_memento.h" #include "mainwindow.h" #include "mainwidget.h" #include "application.h" @@ -237,7 +238,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) { diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index 4ee4a8998..0966f9d9b 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -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); diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 0544ead19..fe17b8cdd 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -4089,7 +4089,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(); @@ -5123,15 +5125,15 @@ HistoryItem *HistoryWidget::atTopImportantMsg(int32 &bottomUnderScrollTop) const return _list->atTopImportantMsg(_scroll.scrollTop(), _scroll.height(), bottomUnderScrollTop); } -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(); @@ -5155,31 +5157,37 @@ 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(-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); + int delta = st::slideShift; + a_progress = anim::fvalue(0, 1); + if (direction == Window::SlideDirection::FromLeft) { + a_coordUnder = anim::ivalue(0, delta); + a_coordOver = anim::ivalue(-delta, 0); + } else { + a_coordUnder = anim::ivalue(0, -delta); + a_coordOver = anim::ivalue(delta, 0); + } _a_show.start(); App::main()->topBar()->update(); + activate(); } void HistoryWidget::step_show(float64 ms, bool timer) { - float64 dt = ms / 3000;// st::slideDuration; + float64 dt = ms / st::slideDuration; if (dt >= 1) { _a_show.stop(); _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(); @@ -5187,7 +5195,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(); @@ -5903,10 +5911,24 @@ 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()); - p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), st::topBarHeight), App::sprite(), st::slideShadow.rect()); + int retina = cIntRetinaFactor(); + if (a_progress.current() < 1) { + int underLeft = a_coordUnder.current(); + int underWidth = _cacheUnder.width() / retina; + int underHeight = st::topBarHeight; + p.fillRect(0, 0, underWidth, underHeight, st::white); + QRect underDst(0, 0, underWidth + underLeft, underHeight); + QRect underSrc(-underLeft * retina, 0, (underWidth + underLeft) * retina, underHeight * retina); + p.setOpacity(1. - a_progress.current()); + p.drawPixmap(underDst, _cacheUnder, underSrc); + p.setOpacity(a_progress.current()); + } + int overLeft = a_coordOver.current(); + int overWidth = _cacheOver.width() / retina; + int overHeight = st::topBarHeight; + QRect overDst(overLeft, 0, overWidth - overLeft, overHeight); + QRect overSrc(0, 0, (overWidth - overLeft) * retina, overHeight * retina); + p.drawPixmap(overDst, _cacheOver, overSrc); return; } @@ -5940,7 +5962,7 @@ void HistoryWidget::topBarClick() { if (Adaptive::OneColumn()) { Ui::showChatsList(); } else { - if (_history) App::main()->showPeerProfile(_peer); + if (_history) Ui::showPeerProfile(_peer); } } @@ -8310,20 +8332,31 @@ 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()) { - 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.setOpacity(1); + int retina = cIntRetinaFactor(); + int inCacheTop = hasTopBar ? st::topBarHeight : 0; + if (a_progress.current() < 1) { + int underLeft = a_coordUnder.current(); + int underWidth = _cacheUnder.width() / retina; + int underHeight = height(); + p.fillRect(r, st::white); + QRect underDst(0, 0, underWidth + underLeft, underHeight); + QRect underSrc(-underLeft * retina, inCacheTop * retina, (underWidth + underLeft) * retina, underHeight * retina); + p.setOpacity(1. - a_progress.current()); + p.drawPixmap(underDst, _cacheUnder, underSrc); + p.setOpacity(a_progress.current()); } - 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()); + int overLeft = a_coordOver.current(); + int overWidth = _cacheOver.width() / retina; + int overHeight = height(); + QRect overDst(overLeft, 0, overWidth - overLeft, overHeight); + QRect overSrc(0, inCacheTop * retina, (overWidth - overLeft) * retina, overHeight * retina); + p.drawPixmap(overDst, _cacheOver, overSrc); 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); diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index 082d97e9f..f9e82b830 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -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 { @@ -576,7 +577,10 @@ public: MsgId msgId() const; HistoryItem *atTopImportantMsg(int32 &bottomUnderScrollTop) 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(); @@ -659,9 +663,14 @@ public: _inGrab = true; resizeEvent(0); } + void grapWithoutTopBarShadow() { + grabStart(); + _topShadow.hide(); + } void grabFinish() override { _inGrab = false; resizeEvent(0); + _topShadow.show(); } bool isItemVisible(HistoryItem *item); @@ -1084,9 +1093,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; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index b64e60a50..ae9599980 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -22,7 +22,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include "ui/buttons/peer_avatar_button.h" -#include "profile/profile_widget.h" +#include "window/section_memento.h" +#include "window/section_widget.h" #include "window/top_bar_widget.h" #include "apiwrap.h" #include "dialogswidget.h" @@ -45,6 +46,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "shortcuts.h" #include "audio.h" +StackItemSection::StackItemSection(std_::unique_ptr &&memento) : StackItem(nullptr) +, _memento(std_::move(memento)) { +} + +StackItemSection::~StackItemSection() { +} + MainWidget::MainWidget(MainWindow *window) : TWidget(window) , _a_show(animation(this, &MainWidget::step_show)) , _sideShadow(this, st::shadowColor) @@ -390,8 +398,8 @@ void MainWidget::rpcClear() { QPixmap MainWidget::grabInner() { if (_overview && !_overview->isHidden()) { return myGrab(_overview); - } else if (_profile && !_profile->isHidden()) { - return myGrab(_profile, QRect(0, st::topBarHeight, _history->width(), _history->height() - st::topBarHeight)); + } else if (_wideSection && !_wideSection->isHidden()) { + return myGrab(_wideSection, QRect(0, st::topBarHeight, _history->width(), _history->height() - st::topBarHeight)); } else if (Adaptive::OneColumn() && _history->isHidden()) { return myGrab(_dialogs, QRect(0, st::topBarHeight, _dialogs->width(), _dialogs->height() - st::topBarHeight)); } else if (_history->peer()) { @@ -411,8 +419,8 @@ bool MainWidget::isItemVisible(HistoryItem *item) { QPixmap MainWidget::grabTopBar() { if (!_topBar->isHidden()) { return myGrab(_topBar); - } else if (_profile) { - return myGrab(_profile, QRect(0, 0, _profile->width(), st::topBarHeight)); + } else if (_wideSection) { + return myGrab(_wideSection, QRect(0, 0, _wideSection->width(), st::topBarHeight)); } else if (Adaptive::OneColumn() && _history->isHidden()) { return myGrab(_dialogs, QRect(0, 0, _dialogs->width(), st::topBarHeight)); } else { @@ -528,29 +536,22 @@ void MainWidget::noHider(HistoryHider *destroyed) { _forwardConfirm = 0; } onHistoryShown(_history->history(), _history->msgId()); - if (_profile || _overview || (_history->peer() && _history->peer()->id)) { - QPixmap animCache, animTopBarCache; - if (_profile) { - if (Adaptive::OneColumn()) { - animCache = myGrab(this, QRect(0, _playerHeight, _dialogsWidth, height() - _playerHeight)); - } else { - _sideShadow.hide(); - animCache = myGrab(this, QRect(_dialogsWidth, _playerHeight, width() - _dialogsWidth, height() - _playerHeight)); - _sideShadow.show(); - } + if (_wideSection || _overview || (_history->peer() && _history->peer()->id)) { + Window::SectionSlideParams animationParams; + if (_overview) { + animationParams = prepareOverviewAnimation(); + } else if (_wideSection) { + animationParams = prepareWideSectionAnimation(_wideSection); } else { - animCache = grabInner(); - animTopBarCache = grabTopBar(); + animationParams = prepareHistoryAnimation(_history->peer() ? _history->peer()->id : 0); } _dialogs->hide(); if (_overview) { - _overview->show(); - _overview->animShow(animCache, animTopBarCache); - } else if (_profile) { - _profile->showAnimated(SlideDirection::FromRight, animCache); + _overview->showAnimated(Window::SlideDirection::FromRight, animationParams); + } else if (_wideSection) { + _wideSection->showAnimated(Window::SlideDirection::FromRight, animationParams); } else { - _history->show(); - _history->animShow(animCache, animTopBarCache); + _history->showAnimated(Window::SlideDirection::FromRight, animationParams); } } App::wnd()->getTitle()->updateBackButton(); @@ -575,19 +576,19 @@ void MainWidget::hiderLayer(HistoryHider *h) { dialogsToUp(); _hider->hide(); - QPixmap animCache = myGrab(this, QRect(0, _playerHeight, _dialogsWidth, height() - _playerHeight)); + auto animationParams = prepareDialogsAnimation(); onHistoryShown(0, 0); if (_overview) { _overview->hide(); - } else if (_profile) { - _profile->hide(); + } else if (_wideSection) { + _wideSection->hide(); } else { _history->hide(); } _dialogs->show(); resizeEvent(0); - _dialogs->animShow(animCache); + _dialogs->showAnimated(Window::SlideDirection::FromLeft, animationParams); App::wnd()->getTitle()->updateBackButton(); } else { _hider->show(); @@ -1878,15 +1879,15 @@ void MainWidget::setInnerFocus() { _hider->setFocus(); } else if (_overview) { _overview->activate(); - } else if (_profile) { - _profile->setInnerFocus(); + } else if (_wideSection) { + _wideSection->setInnerFocus(); } else { dialogsActivate(); } } else if (_overview) { _overview->activate(); - } else if (_profile) { - _profile->setInnerFocus(); + } else if (_wideSection) { + _wideSection->setInnerFocus(); } else { _history->setInnerFocus(); } @@ -2030,31 +2031,19 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool bac _hider = nullptr; } - QPixmap animCache, animTopBarCache; - if (!_a_show.animating() && ((_history->isHidden() && (_profile || _overview)) || (Adaptive::OneColumn() && (_history->isHidden() || !peerId)))) { - if (peerId) { - animCache = grabInner(); - } else if (Adaptive::OneColumn()) { - animCache = myGrab(this, QRect(0, _playerHeight, _dialogsWidth, height() - _playerHeight)); - } else { - _sideShadow.hide(); - animCache = myGrab(this, QRect(_dialogsWidth, _playerHeight, width() - _dialogsWidth, height() - _playerHeight)); - _sideShadow.show(); - } - if (peerId || !Adaptive::OneColumn()) { - animTopBarCache = grabTopBar(); - } - _history->show(); + Window::SectionSlideParams animationParams; + if (!_a_show.animating() && ((_history->isHidden() && (_wideSection || _overview)) || (Adaptive::OneColumn() && (_history->isHidden() || !peerId)))) { + animationParams = prepareHistoryAnimation(peerId); } if (_history->peer() && _history->peer()->id != peerId) clearBotStartToken(_history->peer()); _history->showHistory(peerId, showAtMsgId); bool noPeer = (!_history->peer() || !_history->peer()->id), onlyDialogs = noPeer && Adaptive::OneColumn(); - if (_profile || _overview) { - if (_profile) { - _profile->hide(); - _profile->deleteLater(); - _profile = nullptr; + if (_wideSection || _overview) { + if (_wideSection) { + _wideSection->hide(); + _wideSection->deleteLater(); + _wideSection = nullptr; } if (_overview) { _overview->hide(); @@ -2073,9 +2062,10 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool bac _topBar->hide(); _history->hide(); if (!_a_show.animating()) { - _dialogs->show(); - if (!animCache.isNull()) { - _dialogs->animShow(animCache); + if (!animationParams.oldContentCache.isNull()) { + _dialogs->showAnimated(back ? Window::SlideDirection::FromLeft : Window::SlideDirection::FromRight, animationParams); + } else { + _dialogs->show(); } } } else { @@ -2090,11 +2080,13 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool bac } if (Adaptive::OneColumn() && !_dialogs->isHidden()) _dialogs->hide(); if (!_a_show.animating()) { - if (_history->isHidden()) _history->show(); - if (!animCache.isNull()) { - _history->animShow(animCache, animTopBarCache, back); - } else if (App::wnd()) { - QTimer::singleShot(0, App::wnd(), SLOT(setInnerFocus())); + if (!animationParams.oldContentCache.isNull()) { + _history->showAnimated(back ? Window::SlideDirection::FromLeft : Window::SlideDirection::FromRight, animationParams); + } else { + _history->show(); + if (App::wnd()) { + QTimer::singleShot(0, App::wnd(), SLOT(setInnerFocus())); + } } } } @@ -2112,8 +2104,8 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool bac } PeerData *MainWidget::ui_getPeerForMouseAction() { - if (_profile) { - //return _profile->ui_getPeerForMouseAction(); TODO + if (_wideSection) { + //return _wideSection->ui_getPeerForMouseAction(); TODO } return _history->ui_getPeerForMouseAction(); } @@ -2152,10 +2144,6 @@ MsgId MainWidget::activeMsgId() { return _history->peer() ? _history->msgId() : _msgIdInStack; } -PeerData *MainWidget::profilePeer() { - return _profile ? _profile->peer() : 0; -} - PeerData *MainWidget::overviewPeer() { return _overview ? _overview->peer() : 0; } @@ -2186,18 +2174,15 @@ void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool return; } - QRect topBarRect = QRect(_topBar->x(), _topBar->y(), _topBar->width(), st::topBarHeight); - QRect historyRect = QRect(_history->x(), topBarRect.y() + topBarRect.height(), _history->width(), _history->y() + _history->height() - topBarRect.y() - topBarRect.height()); - QPixmap animCache, animTopBarCache; - if (!_a_show.animating() && (Adaptive::OneColumn() || _profile || _overview || _history->peer())) { - animCache = grabInner(); - animTopBarCache = grabTopBar(); + Window::SectionSlideParams animationParams; + if (!_a_show.animating() && (Adaptive::OneColumn() || _wideSection || _overview || _history->peer())) { + animationParams = prepareOverviewAnimation(); } if (!back) { if (_overview) { _stack.push_back(new StackItemOverview(_overview->peer(), _overview->type(), _overview->lastWidth(), _overview->lastScrollTop())); - } else if (_profile) { -// _stack.push_back(new StackItemProfile(_profile->peer(), _profile->lastScrollTop())); TODO + } else if (_wideSection) { + _stack.push_back(new StackItemSection(_wideSection->createMemento())); } else if (_history->peer()) { dlgUpdated(); _peerInStack = _history->peer(); @@ -2212,18 +2197,19 @@ void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool _overview->deleteLater(); _overview->rpcClear(); } - if (_profile) { - _profile->hide(); - _profile->deleteLater(); - _profile = nullptr; + if (_wideSection) { + _wideSection->hide(); + _wideSection->deleteLater(); + _wideSection = nullptr; } _overview = new OverviewWidget(this, peer, type); _mediaTypeMask = 0; _topBar->show(); resizeEvent(nullptr); mediaOverviewUpdated(peer, type); - if (!animCache.isNull()) { - _overview->animShow(animCache, animTopBarCache, back, lastScrollTop); + _overview->setLastScrollTop(lastScrollTop); + if (!animationParams.oldContentCache.isNull()) { + _overview->showAnimated(back ? Window::SlideDirection::FromLeft : Window::SlideDirection::FromRight, animationParams); } else { _overview->fastShow(); } @@ -2238,36 +2224,88 @@ void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool App::wnd()->getTitle()->updateBackButton(); } -void MainWidget::showPeerProfile(PeerData *peer, bool back, int32 lastScrollTop) { - if (peer->migrateTo()) { - peer = peer->migrateTo(); - } - +void MainWidget::showWideSection(Window::SectionMemento &memento) { App::wnd()->hideSettings(); - if (_profile && _profile->peer() == peer) return; + if (_wideSection && _wideSection->showInternal(&memento)) { + return; + } - QPixmap animCache; - if (Adaptive::OneColumn()) { - animCache = myGrab(this, QRect(0, _playerHeight, _dialogsWidth, height() - _playerHeight)); - } else { - _sideShadow.hide(); - animCache = myGrab(this, QRect(_dialogsWidth, _playerHeight, width() - _dialogsWidth, height() - _playerHeight)); - _sideShadow.show(); + if (_overview) { + _stack.push_back(new StackItemOverview(_overview->peer(), _overview->type(), _overview->lastWidth(), _overview->lastScrollTop())); + } else if (_wideSection) { + _stack.push_back(new StackItemSection(_wideSection->createMemento())); + } else if (_history->peer()) { + dlgUpdated(); + _peerInStack = _history->peer(); + _msgIdInStack = _history->msgId(); + dlgUpdated(); + _stack.push_back(new StackItemHistory(_peerInStack, _msgIdInStack, _history->replyReturns())); } -// QPixmap animCache = grabInner(), animTopBarCache = grabTopBar(); - if (!back) { - if (_overview) { - _stack.push_back(new StackItemOverview(_overview->peer(), _overview->type(), _overview->lastWidth(), _overview->lastScrollTop())); - } else if (_profile) { -// _stack.push_back(new StackItemProfile(_profile->peer(), _profile->lastScrollTop())); TODO - } else if (_history->peer()) { - dlgUpdated(); - _peerInStack = _history->peer(); - _msgIdInStack = _history->msgId(); - dlgUpdated(); - _stack.push_back(new StackItemHistory(_peerInStack, _msgIdInStack, _history->replyReturns())); + showWideSectionAnimated(&memento, false); +} + +Window::SectionSlideParams MainWidget::prepareShowAnimation(bool willHaveTopBarShadow) { + Window::SectionSlideParams result; + result.withTopBarShadow = willHaveTopBarShadow; + if (selectingPeer() && Adaptive::OneColumn()) { + result.withTopBarShadow = false; + } else if (_wideSection) { + if (!_wideSection->hasTopBarShadow()) { + result.withTopBarShadow = false; } + } else if (!_overview && !_history->peer()) { + result.withTopBarShadow = false; } + + if (selectingPeer() && Adaptive::OneColumn()) { + result.oldContentCache = myGrab(this, QRect(0, _playerHeight, _dialogsWidth, height() - _playerHeight)); + } else if (_wideSection) { + result.oldContentCache = _wideSection->grabForShowAnimation(result); + } else { + if (result.withTopBarShadow) { + if (_overview) _overview->grapWithoutTopBarShadow(); + _history->grapWithoutTopBarShadow(); + } else { + if (_overview) _overview->grabStart(); + _history->grabStart(); + } + if (Adaptive::OneColumn()) { + result.oldContentCache = myGrab(this, QRect(0, _playerHeight, _dialogsWidth, height() - _playerHeight)); + } else { + _sideShadow.hide(); + result.oldContentCache = myGrab(this, QRect(_dialogsWidth, _playerHeight, width() - _dialogsWidth, height() - _playerHeight)); + _sideShadow.show(); + } + if (_overview) _overview->grabFinish(); + _history->grabFinish(); + } + + return result; +} + +Window::SectionSlideParams MainWidget::prepareWideSectionAnimation(Window::SectionWidget *section) { + return prepareShowAnimation(section->hasTopBarShadow()); +} + +Window::SectionSlideParams MainWidget::prepareHistoryAnimation(PeerId historyPeerId) { + return prepareShowAnimation(historyPeerId != 0); +} + +Window::SectionSlideParams MainWidget::prepareOverviewAnimation() { + return prepareShowAnimation(true); +} + +Window::SectionSlideParams MainWidget::prepareDialogsAnimation() { + return prepareShowAnimation(false); +} + +void MainWidget::showWideSectionAnimated(Window::SectionMemento *memento, bool back) { + QPixmap animCache; + + auto newWideGeometry = QRect(_history->x(), _playerHeight, _history->width(), height() - _playerHeight); + auto newWideSection = memento->createWidget(this, newWideGeometry); + Window::SectionSlideParams animationParams = prepareWideSectionAnimation(newWideSection); + if (_overview) { _overview->hide(); _overview->clear(); @@ -2275,17 +2313,17 @@ void MainWidget::showPeerProfile(PeerData *peer, bool back, int32 lastScrollTop) _overview->rpcClear(); _overview = nullptr; } - if (_profile) { - _profile->hide(); - _profile->deleteLater(); - _profile = nullptr; + if (_wideSection) { + _wideSection->hide(); + _wideSection->deleteLater(); + _wideSection = nullptr; } - _profile = new Profile::Widget(this, peer); + _wideSection = newWideSection; _topBar->hide(); resizeEvent(0); - _profile->showAnimated(SlideDirection::FromRight, animCache); + auto direction = back ? Window::SlideDirection::FromLeft : Window::SlideDirection::FromRight; + _wideSection->showAnimated(direction, animationParams); _history->animStop(); - if (back) clearBotStartToken(_history->peer()); _history->showHistory(0, 0); _history->hide(); if (Adaptive::OneColumn()) _dialogs->hide(); @@ -2304,6 +2342,9 @@ void MainWidget::showBackFromStack() { } StackItem *item = _stack.back(); _stack.pop_back(); + if (auto currentHistoryPeer = _history->peer()) { + clearBotStartToken(currentHistoryPeer); + } if (item->type() == HistoryStackItem) { dlgUpdated(); _peerInStack = 0; @@ -2319,9 +2360,9 @@ void MainWidget::showBackFromStack() { StackItemHistory *histItem = static_cast(item); Ui::showPeerHistory(histItem->peer->id, App::main()->activeMsgId(), true); _history->setReplyReturns(histItem->peer->id, histItem->replyReturns); - } else if (item->type() == ProfileStackItem) { - StackItemProfile *profItem = static_cast(item); - showPeerProfile(profItem->peer, true, profItem->lastScrollTop); + } else if (item->type() == SectionStackItem) { + StackItemSection *sectionItem = static_cast(item); + showWideSectionAnimated(sectionItem->memento(), true); } else if (item->type() == OverviewStackItem) { StackItemOverview *overItem = static_cast(item); showMediaOverview(overItem->peer, overItem->mediaType, true, overItem->lastScrollTop); @@ -2345,6 +2386,19 @@ QRect MainWidget::historyRect() const { return r; } +QPixmap MainWidget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) { + _topBar->stopAnim(); + QPixmap result; + if (Adaptive::OneColumn()) { + result = myGrab(this, QRect(0, _playerHeight, _dialogsWidth, height() - _playerHeight)); + } else { + _sideShadow.hide(); + result = myGrab(this, QRect(_dialogsWidth, _playerHeight, width() - _dialogsWidth, height() - _playerHeight)); + _sideShadow.show(); + } + return result; +} + void MainWidget::dlgUpdated() { if (_peerInStack) { _dialogs->dlgUpdated(App::history(_peerInStack->id), _msgIdInStack); @@ -2482,12 +2536,13 @@ void MainWidget::paintEvent(QPaintEvent *e) { void MainWidget::hideAll() { _dialogs->hide(); _history->hide(); - if (_profile) { - _profile->hide(); + if (_wideSection) { + _wideSection->hide(); } if (_overview) { _overview->hide(); } + _sideShadow.hide(); _topBar->hide(); _mediaType->hide(); if (_player->isOpened() && !_player->isHidden()) { @@ -2501,6 +2556,7 @@ void MainWidget::showAll() { cSetPasswordRecovered(false); Ui::showLayer(new InformBox(lang(lng_signin_password_removed))); } + _sideShadow.show(); if (Adaptive::OneColumn()) { if (_hider) { _hider->hide(); @@ -2515,12 +2571,12 @@ void MainWidget::showAll() { _dialogs->show(); _history->hide(); if (_overview) _overview->hide(); - if (_profile) _profile->hide(); + if (_wideSection) _wideSection->hide(); _topBar->hide(); } else if (_overview) { _overview->show(); - } else if (_profile) { - _profile->show(); + } else if (_wideSection) { + _wideSection->show(); } else if (_history->peer()) { _history->show(); _history->resizeEvent(0); @@ -2528,7 +2584,7 @@ void MainWidget::showAll() { _dialogs->show(); _history->hide(); } - if (_profile) { + if (_wideSection) { _topBar->hide(); } else if (!selectingPeer() && (_overview || _history->peer())) { _topBar->show(); @@ -2545,13 +2601,13 @@ void MainWidget::showAll() { _dialogs->show(); if (_overview) { _overview->show(); - } else if (_profile) { - _profile->show(); + } else if (_wideSection) { + _wideSection->show(); } else { _history->show(); _history->resizeEvent(0); } - if (_profile) { + if (_wideSection) { _topBar->hide(); } else if (_overview || _history->peer()) { _topBar->show(); @@ -2593,9 +2649,9 @@ void MainWidget::resizeEvent(QResizeEvent *e) { } } _mediaType->moveToLeft(width() - _mediaType->width(), _playerHeight + st::topBarHeight); - if (_profile) { - QRect profileGeometry(_history->x(), _playerHeight, _history->width(), height() - _playerHeight); - _profile->setGeometryWithTopMoved(profileGeometry, _contentScrollAddToY); + if (_wideSection) { + QRect wideSectionGeometry(_history->x(), _playerHeight, _history->width(), height() - _playerHeight); + _wideSection->setGeometryWithTopMoved(wideSectionGeometry, _contentScrollAddToY); } if (_overview) _overview->setGeometry(_history->geometry()); _contentScrollAddToY = 0; @@ -2611,20 +2667,21 @@ void MainWidget::keyPressEvent(QKeyEvent *e) { void MainWidget::updateAdaptiveLayout() { showAll(); _sideShadow.setVisible(!Adaptive::OneColumn()); + if (_wideSection) { + _wideSection->updateAdaptiveLayout(); + } _topBar->updateAdaptiveLayout(); _history->updateAdaptiveLayout(); } bool MainWidget::needBackButton() { - return _overview || _profile || (_history->peer() && _history->peer()->id); + return _overview || _wideSection || _history->peer(); } void MainWidget::paintTopBar(Painter &p, float64 over, int32 decreaseWidth) { - if (_profile) { -// _profile->paintTopBar(p, over, decreaseWidth); - } else if (_overview) { + if (_overview) { _overview->paintTopBar(p, over, decreaseWidth); - } else { + } else if (!_wideSection) { _history->paintTopBar(p, over, decreaseWidth); } } @@ -2668,11 +2725,9 @@ PlayerWidget *MainWidget::player() { } void MainWidget::onTopBarClick() { - if (_profile) { -// _profile->topBarClick(); - } else if (_overview) { + if (_overview) { _overview->topBarClick(); - } else { + } else if (!_wideSection) { _history->topBarClick(); } } @@ -3308,7 +3363,7 @@ void MainWidget::openPeerByName(const QString &username, MsgId msgId, const QStr // Always open bot chats, even from mention links. Ui::showPeerHistoryAsync(peer->id, ShowAtUnreadMsgId); } else { - showPeerProfile(peer); + Ui::showPeerProfile(peer); } } else { if (msgId == ShowAtProfileMsgId || !peer->isChannel()) { // show specific posts only in channels / supergroups @@ -3391,7 +3446,7 @@ void MainWidget::usernameResolveDone(QPair msgIdAndStartToken, c // Always open bot chats, even from mention links. Ui::showPeerHistoryAsync(peer->id, ShowAtUnreadMsgId); } else { - showPeerProfile(peer); + Ui::showPeerProfile(peer); } } else { if (msgId == ShowAtProfileMsgId || !peer->isChannel()) { // show specific posts only in channels / supergroups @@ -3690,7 +3745,7 @@ void MainWidget::incrementSticker(DocumentData *sticker) { void MainWidget::activate() { if (_a_show.animating()) return; - if (!_profile && !_overview) { + if (!_wideSection && !_overview) { if (_hider) { if (_hider->wasOffered()) { _hider->setFocus(); @@ -3724,7 +3779,7 @@ bool MainWidget::isActive() const { } bool MainWidget::historyIsActive() const { - return isActive() && !_profile && !_overview && _history->isActive(); + return isActive() && !_wideSection && !_overview && _history->isActive(); } bool MainWidget::lastWasOnline() const { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index a95614bda..b395d2949 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -33,18 +33,16 @@ class PeerAvatarButton; namespace Window { class TopBarWidget; +class SectionMemento; +class SectionWidget; +struct SectionSlideParams; } // namespace Window -namespace Profile { -class Widget; -} // namespace Profile - class MainWindow; class ApiWrap; class ConfirmBox; class DialogsWidget; class HistoryWidget; -class ProfileWidget; class OverviewWidget; class PlayerWidget; class HistoryHider; @@ -52,7 +50,7 @@ class Dropdown; enum StackItemType { HistoryStackItem, - ProfileStackItem, + SectionStackItem, OverviewStackItem, }; @@ -68,8 +66,9 @@ public: class StackItemHistory : public StackItem { public: - StackItemHistory(PeerData *peer, MsgId msgId, QList replyReturns) : StackItem(peer), -msgId(msgId), replyReturns(replyReturns) { + StackItemHistory(PeerData *peer, MsgId msgId, QList replyReturns) : StackItem(peer) + , msgId(msgId) + , replyReturns(replyReturns) { } StackItemType type() const { return HistoryStackItem; @@ -78,19 +77,29 @@ msgId(msgId), replyReturns(replyReturns) { QList replyReturns; }; -class StackItemProfile : public StackItem { +class StackItemSection : public StackItem { public: - StackItemProfile(PeerData *peer, int32 lastScrollTop) : StackItem(peer), lastScrollTop(lastScrollTop) { - } + StackItemSection(std_::unique_ptr &&memento); + ~StackItemSection(); + StackItemType type() const { - return ProfileStackItem; + return SectionStackItem; } - int32 lastScrollTop; + Window::SectionMemento *memento() const { + return _memento.get(); + } + +private: + std_::unique_ptr _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; @@ -214,14 +223,14 @@ public: PeerData *activePeer(); MsgId activeMsgId(); - PeerData *profilePeer(); PeerData *overviewPeer(); bool mediaTypeSwitch(); - void showPeerProfile(PeerData *peer, bool back = false, int32 lastScrollTop = -1); + void showWideSection(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); @@ -485,7 +494,6 @@ public slots: void ui_autoplayMediaInlineAsync(qint32 channelId, qint32 msgId); private: - void sendReadRequest(PeerData *peer, MsgId upTo); void channelWasRead(PeerData *peer, const MTPBool &result); void historyWasRead(PeerData *peer, const MTPmessages_AffectedMessages &result); @@ -495,6 +503,15 @@ private: void messagesAffected(PeerData *peer, const MTPmessages_AffectedMessages &result); void overviewLoaded(History *history, const MTPmessages_Messages &result, mtpRequestId req); + Window::SectionSlideParams prepareShowAnimation(bool willHaveTopBarShadow); + void showWideSectionAnimated(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; @@ -564,7 +581,7 @@ private: ChildWidget _dialogs; ChildWidget _history; - ChildWidget _profile = { nullptr }; + ChildWidget _wideSection = { nullptr }; ChildWidget _overview = { nullptr }; ChildWidget _player; ChildWidget _topBar; diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 0c8c0b804..ce5c1ad81 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -1838,7 +1838,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(); diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index b011cba2f..85b682f05 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -1987,15 +1987,25 @@ void OverviewWidget::paintEvent(QPaintEvent *e) { Painter p(this); if (_a_show.animating()) { - 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.setOpacity(1); + int retina = cIntRetinaFactor(); + int inCacheTop = st::topBarHeight; + if (a_progress.current() < 1) { + int underLeft = a_coordUnder.current(); + int underWidth = _cacheUnder.width() / retina; + int underHeight = height(); + p.fillRect(e->rect(), st::white); + QRect underDst(0, 0, underWidth + underLeft, underHeight); + QRect underSrc(-underLeft * retina, inCacheTop * retina, (underWidth + underLeft) * retina, underHeight * retina); + p.setOpacity(1. - a_progress.current()); + p.drawPixmap(underDst, _cacheUnder, underSrc); + p.setOpacity(a_progress.current()); } - 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()); + int overLeft = a_coordOver.current(); + int overWidth = _cacheOver.width() / retina; + int overHeight = height(); + QRect overDst(overLeft, 0, overWidth - overLeft, overHeight); + QRect overSrc(0, inCacheTop * retina, (overWidth - overLeft) * retina, overHeight * retina); + p.drawPixmap(overDst, _cacheOver, overSrc); return; } @@ -2020,10 +2030,24 @@ 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()); - p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), st::topBarHeight), App::sprite(), st::slideShadow.rect()); + int retina = cIntRetinaFactor(); + if (a_progress.current() < 1) { + int underLeft = a_coordUnder.current(); + int underWidth = _cacheUnder.width() / retina; + int underHeight = st::topBarHeight; + p.fillRect(0, 0, underWidth, underHeight, st::white); + QRect underDst(0, 0, underWidth + underLeft, underHeight); + QRect underSrc(-underLeft * retina, 0, (underWidth + underLeft) * retina, underHeight * retina); + p.setOpacity(1. - a_progress.current()); + p.drawPixmap(underDst, _cacheUnder, underSrc); + p.setOpacity(a_progress.current()); + } + int overLeft = a_coordOver.current(); + int overWidth = _cacheOver.width() / retina; + int overHeight = st::topBarHeight; + QRect overDst(overLeft, 0, overWidth - overLeft, overHeight); + QRect overSrc(0, 0, (overWidth - overLeft) * retina, overHeight * retina); + p.drawPixmap(overDst, _cacheOver, overSrc); return; } p.setOpacity(st::topBarBackAlpha + (1 - st::topBarBackAlpha) * over); @@ -2127,31 +2151,40 @@ 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(-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); + int delta = st::slideShift; + a_progress = anim::fvalue(0, 1); + if (direction == Window::SlideDirection::FromLeft) { + a_coordUnder = anim::ivalue(0, delta); + a_coordOver = anim::ivalue(-delta, 0); + } else { + a_coordUnder = anim::ivalue(0, -delta); + a_coordOver = anim::ivalue(delta, 0); + } _a_show.start(); - show(); - App::main()->topBar()->update(); - _inner.activate(); + + activate(); } void OverviewWidget::step_show(float64 ms, bool timer) { @@ -2162,8 +2195,8 @@ void OverviewWidget::step_show(float64 ms, bool timer) { 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(); @@ -2172,7 +2205,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(); diff --git a/Telegram/SourceFiles/overviewwidget.h b/Telegram/SourceFiles/overviewwidget.h index 75639b066..c053cb57f 100644 --- a/Telegram/SourceFiles/overviewwidget.h +++ b/Telegram/SourceFiles/overviewwidget.h @@ -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,7 +278,11 @@ 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 doneShow(); @@ -302,9 +308,14 @@ public: _inGrab = true; resizeEvent(0); } + void grapWithoutTopBarShadow() { + grabStart(); + _topShadow.hide(); + } void grabFinish() override { _inGrab = false; resizeEvent(0); + _topShadow.show(); } void rpcClear() override { _inner.rpcClear(); @@ -340,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; diff --git a/Telegram/SourceFiles/profile/profile_inner_widget.cpp b/Telegram/SourceFiles/profile/profile_inner_widget.cpp index 8a6cf4fc4..49580822b 100644 --- a/Telegram/SourceFiles/profile/profile_inner_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_inner_widget.cpp @@ -44,7 +44,7 @@ void InnerWidget::setVisibleTopBottom(int visibleTop, int visibleBottom) { int notDisplayedAtBottom = height() - _visibleBottom; if (notDisplayedAtBottom > 0) { - decreaseAdditionalHeight(notDisplayedAtBottom); +// decreaseAdditionalHeight(notDisplayedAtBottom); } //loadProfilePhotos(_visibleTop); @@ -65,6 +65,10 @@ void InnerWidget::paintEvent(QPaintEvent *e) { p.fillRect(e->rect(), st::white); } +void InnerWidget::mousePressEvent(QMouseEvent *e) { // TEMP for testing + Ui::showPeerOverview(_peer, OverviewPhotos); +} + int InnerWidget::resizeGetHeight(int newWidth) { _cover->resizeToWidth(newWidth); int newHeight = _cover->height(); diff --git a/Telegram/SourceFiles/profile/profile_inner_widget.h b/Telegram/SourceFiles/profile/profile_inner_widget.h index e91c018ea..66540915f 100644 --- a/Telegram/SourceFiles/profile/profile_inner_widget.h +++ b/Telegram/SourceFiles/profile/profile_inner_widget.h @@ -43,6 +43,7 @@ public: protected: void paintEvent(QPaintEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; // TEMP for testing private: // Resizes content and counts natural widget height for the desired width. diff --git a/Telegram/SourceFiles/profile/profile_section_memento.cpp b/Telegram/SourceFiles/profile/profile_section_memento.cpp new file mode 100644 index 000000000..34f8ab410 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_section_memento.cpp @@ -0,0 +1,35 @@ +/* +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_section_memento.h" + +#include "profile/profile_widget.h" + +namespace Profile { + +Window::SectionWidget *SectionMemento::createWidget(QWidget *parent, const QRect &geometry) const { + auto result = new Widget(parent, _peer); + result->setGeometry(geometry); + result->setInternalState(this); + return result; +} + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_section_memento.h b/Telegram/SourceFiles/profile/profile_section_memento.h new file mode 100644 index 000000000..b73a1a845 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_section_memento.h @@ -0,0 +1,44 @@ +/* +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 "window/section_memento.h" + +namespace Profile { + +class Widget; + +class SectionMemento : public Window::SectionMemento { +public: + SectionMemento(PeerData *peer) : _peer(peer) { + } + + Window::SectionWidget *createWidget(QWidget *parent, const QRect &geometry) const override; + +private: + friend class Widget; + + PeerData *_peer; + int _scrollTop = 0; + +}; + +} // namespace Window diff --git a/Telegram/SourceFiles/profile/profile_widget.cpp b/Telegram/SourceFiles/profile/profile_widget.cpp index 05458068d..af7d1717e 100644 --- a/Telegram/SourceFiles/profile/profile_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_widget.cpp @@ -23,19 +23,25 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "profile/profile_fixed_bar.h" #include "profile/profile_inner_widget.h" +#include "profile/profile_section_memento.h" #include "mainwindow.h" #include "application.h" namespace Profile { -Widget::Widget(QWidget *parent, PeerData *peer) : TWidget(parent) +Widget::Widget(QWidget *parent, PeerData *peer) : Window::SectionWidget(parent) , _fixedBar(this, peer) +, _fixedBarShadow(this, st::shadowColor) , _scroll(this, st::setScroll) , _inner(this, peer) { _fixedBar->move(0, 0); _fixedBar->resizeToWidth(width()); _fixedBar->show(); + _fixedBarShadow->setMode(ToggleableShadow::Mode::HiddenFast); + _fixedBarShadow->raise(); + updateAdaptiveLayout(); + _scroll->setWidget(_inner); _scroll->move(0, _fixedBar->height()); _scroll->show(); @@ -44,86 +50,85 @@ Widget::Widget(QWidget *parent, PeerData *peer) : TWidget(parent) connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); } +void Widget::updateAdaptiveLayout() { + _fixedBarShadow->moveToLeft(Adaptive::OneColumn() ? 0 : st::lineWidth, _fixedBar->height()); +} + PeerData *Widget::peer() const { return _inner->peer(); } -void Widget::setGeometryWithTopMoved(const QRect &newGeometry, int topDelta) { - _topDelta = topDelta; - bool willBeResized = (size() != newGeometry.size()); - if (geometry() != newGeometry) { - setGeometry(newGeometry); - } - if (!willBeResized) { - resizeEvent(nullptr); - } - _topDelta = 0; -} - -void Widget::showAnimated(SlideDirection direction, const QPixmap &oldContentCache) { - _showAnimation = nullptr; - - showChildren(); - _fixedBar->setAnimatingMode(false); - auto myContentCache = myGrab(this); - hideChildren(); - _fixedBar->setAnimatingMode(true); - - _showAnimation = std_::make_unique(); - _showAnimation->setDirection(direction); - _showAnimation->setRepaintCallback(func(this, &Widget::repaintCallback)); - _showAnimation->setFinishedCallback(func(this, &Widget::showFinished)); - _showAnimation->setPixmaps(oldContentCache, myContentCache); - _showAnimation->start(); - - show(); +QPixmap Widget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) { + if (params.withTopBarShadow) _fixedBarShadow->hide(); + auto result = myGrab(this); + if (params.withTopBarShadow) _fixedBarShadow->show(); + return result; } void Widget::setInnerFocus() { _inner->setFocus(); } +bool Widget::showInternal(Window::SectionMemento *memento) { + if (auto profileMemento = dynamic_cast(memento)) { + if (profileMemento->_peer == peer()) { + // Perhaps no need to do that?.. + _scroll->scrollToY(profileMemento->_scrollTop); + + return true; + } + } + return false; +} + +void Widget::setInternalState(const SectionMemento *memento) { + myEnsureResized(this); + _scroll->scrollToY(memento->_scrollTop); + _fixedBarShadow->setMode(memento->_scrollTop > 0 ? ToggleableShadow::Mode::ShownFast : ToggleableShadow::Mode::HiddenFast); +} + +std_::unique_ptr Widget::createMemento() const { + auto result = std_::make_unique(peer()); + result->_scrollTop = _scroll->scrollTop(); + return std_::move(result); +} + void Widget::resizeEvent(QResizeEvent *e) { - int newScrollTop = _scroll->scrollTop() + _topDelta; + if (!width() || !height()) { + return; + } + + int newScrollTop = _scroll->scrollTop() + topDelta(); _fixedBar->resizeToWidth(width()); + _fixedBarShadow->resize(width(), st::lineWidth); QSize scrollSize(width(), height() - _fixedBar->height()); if (_scroll->size() != scrollSize) { _scroll->resize(scrollSize); - _inner->resizeToWidth(scrollSize.width(), _scroll->height()); + _inner->resizeToWidth(scrollSize.width(), _scroll->height() * 2); } if (!_scroll->isHidden()) { - if (_topDelta) { + if (topDelta()) { _scroll->scrollToY(newScrollTop); } int scrollTop = _scroll->scrollTop(); _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); - } -} - -void Widget::paintEvent(QPaintEvent *e) { - if (Ui::skipPaintEvent(this, e)) return; - - if (_showAnimation) { - Painter p(this); - _showAnimation->paintContents(p, e->rect()); + _fixedBarShadow->setMode((scrollTop > 0) ? ToggleableShadow::Mode::Shown : ToggleableShadow::Mode::Hidden); } } void Widget::onScroll() { int scrollTop = _scroll->scrollTop(); _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); + _fixedBarShadow->setMode((scrollTop > 0) ? ToggleableShadow::Mode::Shown : ToggleableShadow::Mode::Hidden); } -void Widget::showFinished() { - if (isHidden()) return; +void Widget::showAnimatedHook() { + _fixedBar->setAnimatingMode(true); +} - App::app()->mtpUnpause(); - - showChildren(); +void Widget::showFinishedHook() { _fixedBar->setAnimatingMode(false); - - setInnerFocus(); } } // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_widget.h b/Telegram/SourceFiles/profile/profile_widget.h index cb27e3fcf..c509f7f4d 100644 --- a/Telegram/SourceFiles/profile/profile_widget.h +++ b/Telegram/SourceFiles/profile/profile_widget.h @@ -20,53 +20,57 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "ui/slide_animation.h" +#include "window/section_widget.h" class ScrollArea; namespace Profile { +class SectionMemento; class InnerWidget; class FixedBar; -class Widget final : public TWidget { +class Widget final : public Window::SectionWidget { Q_OBJECT public: Widget(QWidget *parent, PeerData *peer); PeerData *peer() const; + PeerData *peerForDialogs() const override { + return peer(); + } - // When resizing the widget with top edge moved up or down and we - // want to add this top movement to the scroll position, so inner - // content will not move. - void setGeometryWithTopMoved(const QRect &newGeometry, int topDelta); + bool hasTopBarShadow() const override { + return _fixedBarShadow->isFullyShown(); + } - void showAnimated(SlideDirection direction, const QPixmap &oldContentCache); + QPixmap grabForShowAnimation(const Window::SectionSlideParams ¶ms) override; - void setInnerFocus(); + void setInnerFocus() override; + + void updateAdaptiveLayout() override; + + bool showInternal(Window::SectionMemento *memento) override; + std_::unique_ptr createMemento() const override; + + void setInternalState(const SectionMemento *memento); protected: void resizeEvent(QResizeEvent *e) override; - void paintEvent(QPaintEvent *e) override; + + void showAnimatedHook() override; + void showFinishedHook() override; private slots: void onScroll(); private: - // QWidget::update() method is overloaded and we need template deduction. - void repaintCallback() { - update(); - } - void showFinished(); + friend class SectionMemento; - ChildWidget _fixedBar; ChildWidget _scroll; ChildWidget _inner; - - std_::unique_ptr _showAnimation; - - // Saving here topDelta in resizeWithTopMoved() to get it passed to resizeEvent(). - int _topDelta = 0; + ChildWidget _fixedBar; + ChildWidget _fixedBarShadow; }; diff --git a/Telegram/SourceFiles/profilewidget.cpp b/Telegram/SourceFiles/profilewidget.cpp index 6cd270599..986fe86c5 100644 --- a/Telegram/SourceFiles/profilewidget.cpp +++ b/Telegram/SourceFiles/profilewidget.cpp @@ -1170,7 +1170,7 @@ void ProfileInner::mousePressEvent(QMouseEvent *e) { _kickDown = _kickOver; update(); } else if (_selectedRow >= 0 && _selectedRow < _participants.size()) { - App::main()->showPeerProfile(_participants[_selectedRow]); + Ui::showPeerProfile(_participants[_selectedRow]); } else if (QRect(_left, st::profilePadding.top(), st::setPhotoSize, st::setPhotoSize).contains(e->pos())) { if (_photoLink) { _photoLink->onClick(e->button()); diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index b42abb395..d1355380e 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -1502,7 +1502,7 @@ void PeerOpenClickHandler::onClickImpl() const { Ui::showPeerHistory(peer(), ShowAtUnreadMsgId); } } else { - App::main()->showPeerProfile(peer()); + Ui::showPeerProfile(peer()); } } } diff --git a/Telegram/SourceFiles/ui/animation.h b/Telegram/SourceFiles/ui/animation.h index d7c3e4a14..71796e7b3 100644 --- a/Telegram/SourceFiles/ui/animation.h +++ b/Telegram/SourceFiles/ui/animation.h @@ -365,7 +365,7 @@ public: using Callback = Function; - SimpleAnimation() : _data(0) { + SimpleAnimation() { } bool animating(uint64 ms) { @@ -376,7 +376,7 @@ public: return false; } - bool isNull() { + bool isNull() const { return !_data; } @@ -384,6 +384,10 @@ public: return _data ? _data->a.current() : typename AnimType::Type(); } + typename AnimType::Type current(const typename AnimType::Type &def) { + return _data ? _data->a.current() : def; + } + typename AnimType::Type current(uint64 ms, const typename AnimType::Type &def) { return animating(ms) ? current() : def; } @@ -405,6 +409,17 @@ public: } } + void finish() { + if (isNull()) { + return; + } + + _data->a.finish(); + _data->_a.stop(); + delete _data; + _data = nullptr; + } + ~SimpleAnimation() { deleteAndMark(_data); } @@ -424,7 +439,7 @@ private: float64 duration; anim::transition transition; }; - Data *_data; + Data *_data = nullptr; void step(float64 ms, bool timer) { float64 dt = (ms >= _data->duration) ? 1 : (ms / _data->duration); @@ -439,15 +454,15 @@ private: } if (!_data->_a.animating()) { delete _data; - _data = 0; + _data = nullptr; } } }; -typedef SimpleAnimation FloatAnimation; -typedef SimpleAnimation IntAnimation; -typedef SimpleAnimation ColorAnimation; +using FloatAnimation = SimpleAnimation; +using IntAnimation = SimpleAnimation; +using ColorAnimation = SimpleAnimation; #define EnsureAnimation(animation, from, callback) if ((animation).isNull()) { (animation).setup((from), (callback)); } diff --git a/Telegram/SourceFiles/ui/twidget.cpp b/Telegram/SourceFiles/ui/twidget.cpp index cb3ea81b7..fcccb15a6 100644 --- a/Telegram/SourceFiles/ui/twidget.cpp +++ b/Telegram/SourceFiles/ui/twidget.cpp @@ -77,3 +77,45 @@ QPixmap myGrab(TWidget *target, QRect rect) { return result; } + +enum class Mode { + Shown, + ShownFast, + Hidden, + HiddenFast +}; +void ToggleableShadow::setMode(Mode mode) { + if (mode == Mode::ShownFast || mode == Mode::HiddenFast) { + if (!_a_opacity.isNull()) { + _a_opacity.finish(); + update(); + } + } + if (_shown && (mode == Mode::Hidden || mode == Mode::HiddenFast)) { + _shown = false; + if (mode == Mode::Hidden) { + if (_a_opacity.isNull()) { + _a_opacity.setup(1., func(this, &ToggleableShadow::repaintCallback)); + } + _a_opacity.start(0., st::shadowToggleDuration); + } + } else if (!_shown && (mode == Mode::Shown || mode == Mode::ShownFast)) { + _shown = true; + if (mode == Mode::Shown) { + if (_a_opacity.isNull()) { + _a_opacity.setup(0., func(this, &ToggleableShadow::repaintCallback)); + } + _a_opacity.start(1., st::shadowToggleDuration); + } + } +} + +void ToggleableShadow::paintEvent(QPaintEvent *e) { + Painter p(this); + if (_a_opacity.animating(getms())) { + p.setOpacity(_a_opacity.current()); + } else if (!_shown) { + return; + } + p.fillRect(e->rect(), _color); +} diff --git a/Telegram/SourceFiles/ui/twidget.h b/Telegram/SourceFiles/ui/twidget.h index 3af3f6ec9..7b6935568 100644 --- a/Telegram/SourceFiles/ui/twidget.h +++ b/Telegram/SourceFiles/ui/twidget.h @@ -208,7 +208,9 @@ class PlainShadow : public TWidget { public: PlainShadow(QWidget *parent, const style::color &color) : TWidget(parent), _color(color) { } - void paintEvent(QPaintEvent *e) { + +protected: + void paintEvent(QPaintEvent *e) override { Painter(this).fillRect(e->rect(), _color->b); } @@ -217,6 +219,37 @@ private: }; +class ToggleableShadow : public TWidget { +public: + ToggleableShadow(QWidget *parent, const style::color &color) : TWidget(parent), _color(color) { + } + + enum class Mode { + Shown, + ShownFast, + Hidden, + HiddenFast + }; + void setMode(Mode mode); + + bool isFullyShown() const { + return _shown && _a_opacity.isNull(); + } + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + void repaintCallback() { + update(); + } + + const style::color &_color; + FloatAnimation _a_opacity; + bool _shown = true; + +}; + class SingleDelayedCall : public QObject { Q_OBJECT diff --git a/Telegram/SourceFiles/window/section_memento.h b/Telegram/SourceFiles/window/section_memento.h new file mode 100644 index 000000000..2d2e6ae09 --- /dev/null +++ b/Telegram/SourceFiles/window/section_memento.h @@ -0,0 +1,33 @@ +/* +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 Window { + +class SectionWidget; + +class SectionMemento { +public: + virtual SectionWidget *createWidget(QWidget *parent, const QRect &geometry) const = 0; + +}; + +} // namespace Window diff --git a/Telegram/SourceFiles/window/section_widget.cpp b/Telegram/SourceFiles/window/section_widget.cpp new file mode 100644 index 000000000..f9c3a1454 --- /dev/null +++ b/Telegram/SourceFiles/window/section_widget.cpp @@ -0,0 +1,82 @@ +/* +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 "window/section_widget.h" + +#include "application.h" + +namespace Window { + +SectionWidget::SectionWidget(QWidget *parent) : TWidget(parent) { +} + +void SectionWidget::setGeometryWithTopMoved(const QRect &newGeometry, int topDelta) { + _topDelta = topDelta; + bool willBeResized = (size() != newGeometry.size()); + if (geometry() != newGeometry) { + setGeometry(newGeometry); + } + if (!willBeResized) { + resizeEvent(nullptr); + } + _topDelta = 0; +} + +void SectionWidget::showAnimated(SlideDirection direction, const SectionSlideParams ¶ms) { + t_assert(_showAnimation == nullptr); + + showChildren(); + auto myContentCache = grabForShowAnimation(params); + hideChildren(); + showAnimatedHook(); + + _showAnimation = std_::make_unique(); + _showAnimation->setDirection(direction); + _showAnimation->setRepaintCallback(func(this, &SectionWidget::repaintCallback)); + _showAnimation->setFinishedCallback(func(this, &SectionWidget::showFinished)); + _showAnimation->setPixmaps(params.oldContentCache, myContentCache); + _showAnimation->setTopBarShadow(params.withTopBarShadow); + _showAnimation->start(); + + show(); +} + +void SectionWidget::paintEvent(QPaintEvent *e) { + if (Ui::skipPaintEvent(this, e)) return; + + if (_showAnimation) { + Painter p(this); + _showAnimation->paintContents(p, e->rect()); + } +} + +void SectionWidget::showFinished() { + if (isHidden()) return; + + App::app()->mtpUnpause(); + + showChildren(); + showFinishedHook(); + + setInnerFocus(); +} + +} // namespace Window diff --git a/Telegram/SourceFiles/window/section_widget.h b/Telegram/SourceFiles/window/section_widget.h new file mode 100644 index 000000000..be34fd924 --- /dev/null +++ b/Telegram/SourceFiles/window/section_widget.h @@ -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 "ui/twidget.h" +#include "window/slide_animation.h" + +namespace Window { + +class SectionMemento; + +struct SectionSlideParams { + QPixmap oldContentCache; + bool withTopBarShadow = false; +}; + +class SectionWidget : public TWidget { + Q_OBJECT + +public: + + SectionWidget(QWidget *parent); + + virtual PeerData *peerForDialogs() const { + return nullptr; + } + + // When resizing the widget with top edge moved up or down and we + // want to add this top movement to the scroll position, so inner + // content will not move. + void setGeometryWithTopMoved(const QRect &newGeometry, int topDelta); + + virtual bool hasTopBarShadow() const { + return false; + } + void showAnimated(SlideDirection direction, const SectionSlideParams ¶ms); + + // This can be used to grab with or without top bar shadow. + // This will be protected when animation preparation will be done inside. + virtual QPixmap grabForShowAnimation(const SectionSlideParams ¶ms) { + return myGrab(this); + } + + // Attempt to show the required section inside the existing one. + // For example if this section already shows exactly the required + // memento it can simply return true - it is shown already. + virtual bool showInternal(SectionMemento *memento) = 0; + + // Create a memento of that section to store it in the history stack. + virtual std_::unique_ptr createMemento() const = 0; + + virtual void setInnerFocus() { + setFocus(); + } + + virtual void updateAdaptiveLayout() { + } + +protected: + void paintEvent(QPaintEvent *e) override; + + // Temp variable used in resizeEvent() implementation, that is passed + // to setGeometryWithTopMoved() to adjust the scroll position with the resize. + int topDelta() const { + return _topDelta; + } + + // Called after the hideChildren() call in showAnimated(). + virtual void showAnimatedHook() { + } + + // Called after the showChildren() call in showFinished(). + virtual void showFinishedHook() { + } + +private: + // QWidget::update() method is overloaded and we need template deduction. + void repaintCallback() { + update(); + } + void showFinished(); + + std_::unique_ptr _showAnimation; + + // Saving here topDelta in resizeWithTopMoved() to get it passed to resizeEvent(). + int _topDelta = 0; + +}; + +} // namespace Window diff --git a/Telegram/SourceFiles/ui/slide_animation.cpp b/Telegram/SourceFiles/window/slide_animation.cpp similarity index 83% rename from Telegram/SourceFiles/ui/slide_animation.cpp rename to Telegram/SourceFiles/window/slide_animation.cpp index 266a1ad16..4aec4bf78 100644 --- a/Telegram/SourceFiles/ui/slide_animation.cpp +++ b/Telegram/SourceFiles/window/slide_animation.cpp @@ -19,17 +19,20 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "ui/slide_animation.h" +#include "window/slide_animation.h" + +namespace Window { SlideAnimation::SlideAnimation() -: _animation(animation(this, &SlideAnimation::step)) { + : _animation(animation(this, &SlideAnimation::step)) { } void SlideAnimation::paintContents(Painter &p, const QRect &update) const { + int retina = cIntRetinaFactor(); + _animation.step(getms()); if (a_progress.current() < 1) { p.fillRect(update, st::white); - int retina = cIntRetinaFactor(); int underLeft = a_coordUnder.current(); int underWidth = _cacheUnder.width() / retina; int underHeight = _cacheUnder.height() / retina; @@ -40,6 +43,11 @@ void SlideAnimation::paintContents(Painter &p, const QRect &update) const { p.setOpacity(a_progress.current()); } p.drawPixmap(a_coordOver.current(), 0, _cacheOver); + + if (_topBarShadowEnabled) { + p.setOpacity(1); + p.fillRect(0, st::topBarHeight, _cacheOver.width() / retina, st::lineWidth, st::shadowColor); + } } void SlideAnimation::setDirection(SlideDirection direction) { @@ -51,6 +59,10 @@ void SlideAnimation::setPixmaps(const QPixmap &oldContentCache, const QPixmap &n _cacheOver = newContentCache; } +void SlideAnimation::setTopBarShadow(bool enabled) { + _topBarShadowEnabled = enabled; +} + void SlideAnimation::setRepaintCallback(RepaintCallback &&callback) { _repaintCallback = std_::move(callback); } @@ -60,13 +72,11 @@ void SlideAnimation::setFinishedCallback(FinishedCallback &&callback) { } void SlideAnimation::start() { - int width = _cacheUnder.width() / cIntRetinaFactor(); int delta = st::slideShift; a_progress = anim::fvalue(0, 1); if (_direction == SlideDirection::FromLeft) { - std::swap(_cacheUnder, _cacheOver); - a_coordUnder = anim::ivalue(-delta, 0); - a_coordOver = anim::ivalue(0, delta); + a_coordUnder = anim::ivalue(0, delta); + a_coordOver = anim::ivalue(-delta, 0); } else { a_coordUnder = anim::ivalue(0, -delta); a_coordOver = anim::ivalue(delta, 0); @@ -75,7 +85,7 @@ void SlideAnimation::start() { } void SlideAnimation::step(float64 ms, bool timer) { - float64 dt = ms / 3000;// st::slideDuration; + float64 dt = ms / st::slideDuration; if (dt >= 1) { dt = 1; if (timer) { @@ -95,3 +105,5 @@ void SlideAnimation::step(float64 ms, bool timer) { _repaintCallback.call(); } } + +} // namespace Window diff --git a/Telegram/SourceFiles/ui/slide_animation.h b/Telegram/SourceFiles/window/slide_animation.h similarity index 93% rename from Telegram/SourceFiles/ui/slide_animation.h rename to Telegram/SourceFiles/window/slide_animation.h index 0739cb081..5ce4068ae 100644 --- a/Telegram/SourceFiles/ui/slide_animation.h +++ b/Telegram/SourceFiles/window/slide_animation.h @@ -20,6 +20,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once +namespace Window { + enum class SlideDirection { FromRight, FromLeft, @@ -33,6 +35,7 @@ public: void setDirection(SlideDirection direction); void setPixmaps(const QPixmap &oldContentCache, const QPixmap &newContentCache); + void setTopBarShadow(bool enabled); using RepaintCallback = Function; void setRepaintCallback(RepaintCallback &&callback); @@ -46,6 +49,7 @@ private: void step(float64 ms, bool timer); SlideDirection _direction = SlideDirection::FromRight; + bool _topBarShadowEnabled = false; mutable Animation _animation; QPixmap _cacheUnder, _cacheOver; @@ -56,3 +60,5 @@ private: FinishedCallback _finishedCallback; }; + +} // namespace Window diff --git a/Telegram/SourceFiles/window/top_bar_widget.cpp b/Telegram/SourceFiles/window/top_bar_widget.cpp index 0b1fe6e49..c175611f0 100644 --- a/Telegram/SourceFiles/window/top_bar_widget.cpp +++ b/Telegram/SourceFiles/window/top_bar_widget.cpp @@ -81,17 +81,17 @@ void TopBarWidget::onClearSelection() { void TopBarWidget::onInfoClicked() { PeerData *p = App::main() ? App::main()->historyPeer() : 0; - if (p) App::main()->showPeerProfile(p); + if (p) Ui::showPeerProfile(p); } void TopBarWidget::onAddContact() { - PeerData *p = App::main() ? App::main()->profilePeer() : 0; + PeerData *p = nullptr;// App::main() ? App::main()->profilePeer() : 0; UserData *u = p ? p->asUser() : 0; if (u) Ui::showLayer(new AddContactBox(u->firstName, u->lastName, u->phone.isEmpty() ? App::phoneFromSharedContact(peerToUser(u->id)) : u->phone)); } void TopBarWidget::onEdit() { - PeerData *p = App::main() ? App::main()->profilePeer() : 0; + PeerData *p = nullptr;// App::main() ? App::main()->profilePeer() : 0; if (p) { if (p->isChannel()) { Ui::showLayer(new EditChannelBox(p->asChannel())); @@ -104,7 +104,7 @@ void TopBarWidget::onEdit() { } void TopBarWidget::onDeleteContact() { - PeerData *p = App::main() ? App::main()->profilePeer() : 0; + PeerData *p = nullptr;// App::main() ? App::main()->profilePeer() : 0; UserData *u = p ? p->asUser() : 0; if (u) { ConfirmBox *box = new ConfirmBox(lng_sure_delete_contact(lt_contact, p->name), lang(lng_box_delete)); @@ -114,7 +114,7 @@ void TopBarWidget::onDeleteContact() { } void TopBarWidget::onDeleteContactSure() { - PeerData *p = App::main() ? App::main()->profilePeer() : 0; + PeerData *p = nullptr;// App::main() ? App::main()->profilePeer() : 0; UserData *u = p ? p->asUser() : 0; if (u) { Ui::showChatsList(); @@ -124,7 +124,7 @@ void TopBarWidget::onDeleteContactSure() { } void TopBarWidget::onDeleteAndExit() { - PeerData *p = App::main() ? App::main()->profilePeer() : 0; + PeerData *p = nullptr;// App::main() ? App::main()->profilePeer() : 0; ChatData *c = p ? p->asChat() : 0; if (c) { ConfirmBox *box = new ConfirmBox(lng_sure_delete_and_exit(lt_group, p->name), lang(lng_box_leave), st::attentionBoxButton); @@ -134,7 +134,7 @@ void TopBarWidget::onDeleteAndExit() { } void TopBarWidget::onDeleteAndExitSure() { - PeerData *p = App::main() ? App::main()->profilePeer() : 0; + PeerData *p = nullptr;// App::main() ? App::main()->profilePeer() : 0; ChatData *c = p ? p->asChat() : 0; if (c) { Ui::showChatsList(); @@ -181,22 +181,28 @@ void TopBarWidget::step_appearance(float64 ms, bool timer) { void TopBarWidget::paintEvent(QPaintEvent *e) { Painter p(this); - if (e->rect().top() < st::topBarHeight) { // optimize shadow-only drawing - p.fillRect(QRect(0, 0, width(), st::topBarHeight), st::topBarBG->b); - if (_clearSelection->isHidden()) { - p.save(); - main()->paintTopBar(p, a_over.current(), _info->isHidden() ? 0 : _info->width()); - p.restore(); - } else { - p.setFont(st::linkFont->f); - p.setPen(st::btnDefLink.color->p); - p.drawText(_selStrLeft, st::topBarButton.textTop + st::linkFont->ascent, _selStr); + p.fillRect(QRect(0, 0, width(), st::topBarHeight), st::topBarBG->b); + if (_clearSelection->isHidden()) { + p.save(); + int decreaseWidth = 0; + if (!_info->isHidden()) { + decreaseWidth += _info->width(); + decreaseWidth -= st::topBarForwardPadding.right(); } + if (!_search->isHidden()) { + decreaseWidth += _search->width(); + } + main()->paintTopBar(p, a_over.current(), decreaseWidth); + p.restore(); + } else { + p.setFont(st::linkFont); + p.setPen(st::btnDefLink.color); + p.drawText(_selStrLeft, st::topBarButton.textTop + st::linkFont->ascent, _selStr); } } void TopBarWidget::mousePressEvent(QMouseEvent *e) { - PeerData *p = App::main() ? App::main()->profilePeer() : 0; + PeerData *p = nullptr;// App::main() ? App::main()->profilePeer() : 0; if (e->button() == Qt::LeftButton && e->pos().y() < st::topBarHeight && (p || !_selCount)) { emit clicked(); } @@ -289,7 +295,7 @@ void TopBarWidget::showAll() { resizeEvent(0); return; } - PeerData *p = App::main() ? App::main()->profilePeer() : 0, *h = App::main() ? App::main()->historyPeer() : 0, *o = App::main() ? App::main()->overviewPeer() : 0; + PeerData *p = nullptr/*App::main() ? App::main()->profilePeer() : 0*/, *h = App::main() ? App::main()->historyPeer() : 0, *o = App::main() ? App::main()->overviewPeer() : 0; if (p && (p->isChat() || (p->isUser() && (p->asUser()->contact >= 0 || !App::phoneFromSharedContact(peerToUser(p->id)).isEmpty())))) { if (p->isChat()) { if (p->asChat()->canEdit()) { @@ -362,7 +368,7 @@ void TopBarWidget::showAll() { } void TopBarWidget::showSelected(uint32 selCount, bool canDelete) { - PeerData *p = App::main() ? App::main()->profilePeer() : 0; + PeerData *p = nullptr;// App::main() ? App::main()->profilePeer() : 0; _selPeer = App::main()->overviewPeer() ? App::main()->overviewPeer() : App::main()->peer(); _selCount = selCount; _canDelete = canDelete; diff --git a/Telegram/SourceFiles/window/top_bar_widget.h b/Telegram/SourceFiles/window/top_bar_widget.h index 69c416e50..ae428cf0d 100644 --- a/Telegram/SourceFiles/window/top_bar_widget.h +++ b/Telegram/SourceFiles/window/top_bar_widget.h @@ -27,7 +27,6 @@ class PeerAvatarButton; } // namespace Ui class FlatButton; class IconedButton; -class PlainShadow; namespace Window { diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index 3e80efccd..b96571968 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -429,6 +429,10 @@ true true + + true + true + true true @@ -736,6 +740,10 @@ true true + + true + true + true true @@ -1069,6 +1077,10 @@ true true + + true + true + true true @@ -1190,6 +1202,7 @@ + true @@ -1239,7 +1252,6 @@ - @@ -1253,6 +1265,8 @@ + + NotUsing @@ -1513,6 +1527,7 @@ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_fixed_bar.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" + @@ -1675,7 +1690,6 @@ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" "-fstdafx.h" "-f../../SourceFiles/ui/twidget.h" - @@ -1714,6 +1728,22 @@ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" "-fstdafx.h" "-f../../SourceFiles/window/top_bar_widget.h" + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing section_widget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/window/section_widget.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing section_widget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/window/section_widget.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl_debug\Debug\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing section_widget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/window/section_widget.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" + + + diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 807393a2c..0fec8a251 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -1119,9 +1119,6 @@ GeneratedFiles\Release - - SourceFiles\ui - GeneratedFiles\Deploy @@ -1173,6 +1170,24 @@ GeneratedFiles\Release + + SourceFiles\window + + + GeneratedFiles\Deploy + + + GeneratedFiles\Debug + + + GeneratedFiles\Release + + + SourceFiles\window + + + SourceFiles\profile + @@ -1346,12 +1361,18 @@ Resources\winrc - - SourceFiles\ui - GeneratedFiles\styles + + SourceFiles\window + + + SourceFiles\window + + + SourceFiles\profile + @@ -1609,6 +1630,9 @@ Resources + + SourceFiles\window + From 92c720ddc3db766418571e5e2452715b05d9848a Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 19 May 2016 15:38:09 +0300 Subject: [PATCH 06/30] Returned animation type to the old one. Fixed new profile display in single column layout. --- Telegram/Resources/basic.style | 2 +- Telegram/SourceFiles/dialogswidget.cpp | 34 +++++------ Telegram/SourceFiles/historywidget.cpp | 58 +++++++------------ Telegram/SourceFiles/mainwidget.cpp | 1 + Telegram/SourceFiles/overviewwidget.cpp | 58 +++++++------------ .../SourceFiles/window/slide_animation.cpp | 35 ++++++----- 6 files changed, 77 insertions(+), 111 deletions(-) diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style index 72b2a6289..1695b0218 100644 --- a/Telegram/Resources/basic.style +++ b/Telegram/Resources/basic.style @@ -379,7 +379,7 @@ slideDuration: 240; slideShift: 100px; slideFadeOut: 0.3; slideShadow: sprite(348px, 71px, 48px, 1px); -slideFunction: transition(linear); +slideFunction: transition(easeOutCirc); btnDefIconed: iconedButton { color: white; diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index 348bc8907..06c71e68c 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -1864,13 +1864,15 @@ void DialogsWidget::showAnimated(Window::SlideDirection direction, const Window: _newGroup.hide(); int delta = st::slideShift; - a_progress = anim::fvalue(0, 1); if (direction == Window::SlideDirection::FromLeft) { - a_coordUnder = anim::ivalue(0, delta); - a_coordOver = anim::ivalue(-delta, 0); + 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(delta, 0); + a_coordOver = anim::ivalue(width(), 0); } _a_show.start(); } @@ -2539,23 +2541,15 @@ void DialogsWidget::paintEvent(QPaintEvent *e) { } if (_a_show.animating()) { int retina = cIntRetinaFactor(); - if (a_progress.current() < 1) { - int underLeft = a_coordUnder.current(); - int underWidth = _cacheUnder.width() / retina; - int underHeight = _cacheUnder.height() / retina; - p.fillRect(r, st::white); - QRect underDst(0, 0, underWidth + underLeft, underHeight); - QRect underSrc(-underLeft * retina, 0, (underWidth + underLeft) * retina, underHeight * retina); - p.setOpacity(1. - a_progress.current()); - p.drawPixmap(underDst, _cacheUnder, underSrc); - p.setOpacity(a_progress.current()); + if (a_coordOver.current() > 0) { + 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); } - int overLeft = a_coordOver.current(); - int overWidth = _cacheOver.width() / retina; - int overHeight = _cacheOver.height() / retina; - QRect overDst(overLeft, 0, overWidth - overLeft, overHeight); - QRect overSrc(0, 0, (overWidth - overLeft) * retina, overHeight * retina); - p.drawPixmap(overDst, _cacheOver, overSrc); + 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()); diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index fe17b8cdd..1206f2d97 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -5163,13 +5163,15 @@ void HistoryWidget::showAnimated(Window::SlideDirection direction, const Window: } int delta = st::slideShift; - a_progress = anim::fvalue(0, 1); if (direction == Window::SlideDirection::FromLeft) { - a_coordUnder = anim::ivalue(0, delta); - a_coordOver = anim::ivalue(-delta, 0); + 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(delta, 0); + a_coordOver = anim::ivalue(width(), 0); } _a_show.start(); @@ -5912,23 +5914,15 @@ void HistoryWidget::onForwardHere() { void HistoryWidget::paintTopBar(Painter &p, float64 over, int32 decreaseWidth) { if (_a_show.animating()) { int retina = cIntRetinaFactor(); - if (a_progress.current() < 1) { - int underLeft = a_coordUnder.current(); - int underWidth = _cacheUnder.width() / retina; - int underHeight = st::topBarHeight; - p.fillRect(0, 0, underWidth, underHeight, st::white); - QRect underDst(0, 0, underWidth + underLeft, underHeight); - QRect underSrc(-underLeft * retina, 0, (underWidth + underLeft) * retina, underHeight * retina); - p.setOpacity(1. - a_progress.current()); - p.drawPixmap(underDst, _cacheUnder, underSrc); - p.setOpacity(a_progress.current()); + 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); } - int overLeft = a_coordOver.current(); - int overWidth = _cacheOver.width() / retina; - int overHeight = st::topBarHeight; - QRect overDst(overLeft, 0, overWidth - overLeft, overHeight); - QRect overSrc(0, 0, (overWidth - overLeft) * retina, overHeight * retina); - p.drawPixmap(overDst, _cacheOver, overSrc); + 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; } @@ -8337,23 +8331,15 @@ void HistoryWidget::paintEvent(QPaintEvent *e) { if (_a_show.animating()) { int retina = cIntRetinaFactor(); int inCacheTop = hasTopBar ? st::topBarHeight : 0; - if (a_progress.current() < 1) { - int underLeft = a_coordUnder.current(); - int underWidth = _cacheUnder.width() / retina; - int underHeight = height(); - p.fillRect(r, st::white); - QRect underDst(0, 0, underWidth + underLeft, underHeight); - QRect underSrc(-underLeft * retina, inCacheTop * retina, (underWidth + underLeft) * retina, underHeight * retina); - p.setOpacity(1. - a_progress.current()); - p.drawPixmap(underDst, _cacheUnder, underSrc); - p.setOpacity(a_progress.current()); + if (a_coordOver.current() > 0) { + 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); } - int overLeft = a_coordOver.current(); - int overWidth = _cacheOver.width() / retina; - int overHeight = height(); - QRect overDst(overLeft, 0, overWidth - overLeft, overHeight); - QRect overSrc(0, inCacheTop * retina, (overWidth - overLeft) * retina, overHeight * retina); - p.drawPixmap(overDst, _cacheOver, overSrc); + 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; } diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index ae9599980..8c7b3becb 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -2586,6 +2586,7 @@ void MainWidget::showAll() { } if (_wideSection) { _topBar->hide(); + _dialogs->hide(); } else if (!selectingPeer() && (_overview || _history->peer())) { _topBar->show(); _dialogs->hide(); diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index 85b682f05..6439ad90a 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -1989,23 +1989,15 @@ void OverviewWidget::paintEvent(QPaintEvent *e) { if (_a_show.animating()) { int retina = cIntRetinaFactor(); int inCacheTop = st::topBarHeight; - if (a_progress.current() < 1) { - int underLeft = a_coordUnder.current(); - int underWidth = _cacheUnder.width() / retina; - int underHeight = height(); - p.fillRect(e->rect(), st::white); - QRect underDst(0, 0, underWidth + underLeft, underHeight); - QRect underSrc(-underLeft * retina, inCacheTop * retina, (underWidth + underLeft) * retina, underHeight * retina); - p.setOpacity(1. - a_progress.current()); - p.drawPixmap(underDst, _cacheUnder, underSrc); - p.setOpacity(a_progress.current()); + if (a_coordOver.current() > 0) { + 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); } - int overLeft = a_coordOver.current(); - int overWidth = _cacheOver.width() / retina; - int overHeight = height(); - QRect overDst(overLeft, 0, overWidth - overLeft, overHeight); - QRect overSrc(0, inCacheTop * retina, (overWidth - overLeft) * retina, overHeight * retina); - p.drawPixmap(overDst, _cacheOver, overSrc); + 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; } @@ -2031,23 +2023,15 @@ void OverviewWidget::scrollReset() { void OverviewWidget::paintTopBar(Painter &p, float64 over, int32 decreaseWidth) { if (_a_show.animating()) { int retina = cIntRetinaFactor(); - if (a_progress.current() < 1) { - int underLeft = a_coordUnder.current(); - int underWidth = _cacheUnder.width() / retina; - int underHeight = st::topBarHeight; - p.fillRect(0, 0, underWidth, underHeight, st::white); - QRect underDst(0, 0, underWidth + underLeft, underHeight); - QRect underSrc(-underLeft * retina, 0, (underWidth + underLeft) * retina, underHeight * retina); - p.setOpacity(1. - a_progress.current()); - p.drawPixmap(underDst, _cacheUnder, underSrc); - p.setOpacity(a_progress.current()); + 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); } - int overLeft = a_coordOver.current(); - int overWidth = _cacheOver.width() / retina; - int overHeight = st::topBarHeight; - QRect overDst(overLeft, 0, overWidth - overLeft, overHeight); - QRect overSrc(0, 0, (overWidth - overLeft) * retina, overHeight * retina); - p.drawPixmap(overDst, _cacheOver, overSrc); + 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; } p.setOpacity(st::topBarBackAlpha + (1 - st::topBarBackAlpha) * over); @@ -2172,13 +2156,15 @@ void OverviewWidget::showAnimated(Window::SlideDirection direction, const Window _scroll.hide(); int delta = st::slideShift; - a_progress = anim::fvalue(0, 1); if (direction == Window::SlideDirection::FromLeft) { - a_coordUnder = anim::ivalue(0, delta); - a_coordOver = anim::ivalue(-delta, 0); + 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(delta, 0); + a_coordOver = anim::ivalue(width(), 0); } _a_show.start(); diff --git a/Telegram/SourceFiles/window/slide_animation.cpp b/Telegram/SourceFiles/window/slide_animation.cpp index 4aec4bf78..5f53e98fa 100644 --- a/Telegram/SourceFiles/window/slide_animation.cpp +++ b/Telegram/SourceFiles/window/slide_animation.cpp @@ -31,18 +31,15 @@ void SlideAnimation::paintContents(Painter &p, const QRect &update) const { int retina = cIntRetinaFactor(); _animation.step(getms()); - if (a_progress.current() < 1) { - p.fillRect(update, st::white); - int underLeft = a_coordUnder.current(); - int underWidth = _cacheUnder.width() / retina; - int underHeight = _cacheUnder.height() / retina; - QRect underDest(0, 0, underWidth + underLeft, underHeight); - QRect underSrc(-underLeft * retina, 0, (underWidth + underLeft) * retina, underHeight * retina); - p.setOpacity(1. - a_progress.current()); - p.drawPixmap(underDest, _cacheUnder, underSrc); - p.setOpacity(a_progress.current()); + if (a_coordOver.current() > 0) { + 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.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()); if (_topBarShadowEnabled) { p.setOpacity(1); @@ -73,13 +70,15 @@ void SlideAnimation::setFinishedCallback(FinishedCallback &&callback) { void SlideAnimation::start() { int delta = st::slideShift; - a_progress = anim::fvalue(0, 1); if (_direction == SlideDirection::FromLeft) { - a_coordUnder = anim::ivalue(0, delta); - a_coordOver = anim::ivalue(-delta, 0); + a_progress = anim::fvalue(1, 0); + std::swap(_cacheUnder, _cacheOver); + a_coordUnder = anim::ivalue(-delta, 0); + a_coordOver = anim::ivalue(0, _cacheOver.width() / cIntRetinaFactor()); } else { + a_progress = anim::fvalue(0, 1); a_coordUnder = anim::ivalue(0, -delta); - a_coordOver = anim::ivalue(delta, 0); + a_coordOver = anim::ivalue(_cacheOver.width() / cIntRetinaFactor(), 0); } _animation.start(); } @@ -98,9 +97,9 @@ void SlideAnimation::step(float64 ms, bool timer) { } } - a_coordUnder.update(dt, anim::linear); - a_coordOver.update(dt, anim::linear); - a_progress.update(dt, anim::linear); + a_coordUnder.update(dt, st::slideFunction); + a_coordOver.update(dt, st::slideFunction); + a_progress.update(dt, st::slideFunction); if (timer) { _repaintCallback.call(); } From c42b142884d472c2f8fe5e9f4ab4e2652abccc38 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 20 May 2016 18:35:58 +0300 Subject: [PATCH 07/30] Divider added to new profiles. Icons from new styles can fill rects. --- .../Resources/icons/profile_divider_fill.png | Bin 0 -> 96 bytes .../icons/profile_divider_fill@2x.png | Bin 0 -> 109 bytes .../Resources/icons/profile_divider_left.png | Bin 0 -> 114 bytes .../icons/profile_divider_left@2x.png | Bin 0 -> 155 bytes .../SourceFiles/codegen/style/parsed_file.cpp | 60 ++++++++++++------ .../SourceFiles/codegen/style/parsed_file.h | 3 + Telegram/SourceFiles/profile/profile.style | 12 +++- .../profile/profile_block_widget.cpp | 47 ++++++++++++++ .../profile/profile_block_widget.h | 56 ++++++++++++++++ .../SourceFiles/profile/profile_cover.cpp | 16 ++++- Telegram/SourceFiles/profile/profile_cover.h | 4 ++ .../profile/profile_inner_widget.cpp | 3 +- .../SourceFiles/ui/style/style_core_icon.cpp | 29 ++++++++- .../SourceFiles/ui/style/style_core_icon.h | 3 + Telegram/Telegram.vcxproj | 27 ++++++++ Telegram/Telegram.vcxproj.filters | 15 +++++ 16 files changed, 250 insertions(+), 25 deletions(-) create mode 100644 Telegram/Resources/icons/profile_divider_fill.png create mode 100644 Telegram/Resources/icons/profile_divider_fill@2x.png create mode 100644 Telegram/Resources/icons/profile_divider_left.png create mode 100644 Telegram/Resources/icons/profile_divider_left@2x.png create mode 100644 Telegram/SourceFiles/profile/profile_block_widget.cpp create mode 100644 Telegram/SourceFiles/profile/profile_block_widget.h diff --git a/Telegram/Resources/icons/profile_divider_fill.png b/Telegram/Resources/icons/profile_divider_fill.png new file mode 100644 index 0000000000000000000000000000000000000000..f212416e798b535b787b9b795d237ff51d2c7cf7 GIT binary patch literal 96 zcmeAS@N?(olHy`uVBq!ia0vp^j6lrA!3HEtFPV4&DRoa5#}JO0$rGkb`E&lu>+AY# v%kJ;5H#YeD_xJS1&c;SY>w{;`oMB+-oz7+REdAa_pcV#CS3j3^P6gTe~ HDWM4fI)5eg literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/profile_divider_left.png b/Telegram/Resources/icons/profile_divider_left.png new file mode 100644 index 0000000000000000000000000000000000000000..c4d4c3aa116da865dddfc792ee476b5ee83423c1 GIT binary patch literal 114 zcmeAS@N?(olHy`uVBq!ia0vp^%s|Y=!3HD?)t{6DDN9cm#}JO0b5Cp(Wl-cf9H827 zxat3-gxO8cUIl*-H5R_DlK6J-#4ie+#eCads@!lBI=kFHBGmPJwfMnCCDp9ApBRCL OFnGH9xvXg(GHD` z$d)eu-=}^t>i6kazlC21J+3{(RsGve*W5BPROkUqByXJ2{8@I=K-(BRUHx3vIVCg! E0PjyW5dZ)H literal 0 HcmV?d00001 diff --git a/Telegram/SourceFiles/codegen/style/parsed_file.cpp b/Telegram/SourceFiles/codegen/style/parsed_file.cpp index 1a397765f..859a7a428 100644 --- a/Telegram/SourceFiles/codegen/style/parsed_file.cpp +++ b/Telegram/SourceFiles/codegen/style/parsed_file.cpp @@ -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 {}; } diff --git a/Telegram/SourceFiles/codegen/style/parsed_file.h b/Telegram/SourceFiles/codegen/style/parsed_file.h index 158782c53..afe641da5 100644 --- a/Telegram/SourceFiles/codegen/style/parsed_file.h +++ b/Telegram/SourceFiles/codegen/style/parsed_file.h @@ -98,6 +98,9 @@ private: structure::Value readIconValue(); structure::Value readCopyValue(); + structure::Value readNumericOrNumericCopyValue(); + structure::Value readStringOrStringCopyValue(); + structure::data::monoicon readMonoIconFields(); QString readMonoIconFilename(); diff --git a/Telegram/SourceFiles/profile/profile.style b/Telegram/SourceFiles/profile/profile.style index 5ec69df55..214095fdb 100644 --- a/Telegram/SourceFiles/profile/profile.style +++ b/Telegram/SourceFiles/profile/profile.style @@ -44,7 +44,17 @@ profileStatusTop: 35px; profileStatusFont: normalFont; profileStatusFg: windowSubTextFg; profileMarginBottom: 30px; -profileSeparator: 10px; + +profileDividerFg: black; +profileDividerLeft: icon { + { "profile_divider_left", profileDividerFg }, +}; +profileDividerFill: icon { + { "profile_divider_fill", profileDividerFg }, +}; profileBlocksTop: 7px; profileBlockMarginTop: 21px; +profileBlockTitleFont: semiboldFont; +profileBlockTitleFg: black; +profileBlockTitlePosition: point(16px, profileBlockMarginTop); \ No newline at end of file diff --git a/Telegram/SourceFiles/profile/profile_block_widget.cpp b/Telegram/SourceFiles/profile/profile_block_widget.cpp new file mode 100644 index 000000000..abfe8453b --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_block_widget.cpp @@ -0,0 +1,47 @@ +/* +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)); +} + +void BlockWidget::paintEvent(QPaintEvent *e) { + Painter p(this); + + p.setFont(st::profileBlockTitleFont); + p.setPen(st::profileBlockTitleFg); + p.drawText(st::profileBlockTitlePosition, _title); + + paintContents(p); +} + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_block_widget.h b/Telegram/SourceFiles/profile/profile_block_widget.h new file mode 100644 index 000000000..313e0d747 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_block_widget.h @@ -0,0 +1,56 @@ +/* +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 BlockWidget : public TWidget { + 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) { + } + +protected: + void paintEvent(QPaintEvent *e) override; + virtual void paintContents(Painter &p) { + } + + // Resizes content and counts natural widget height for the desired width. + virtual int resizeGetHeight(int newWidth) = 0; + + PeerData *peer() const { + return _peer; + } + +private: + PeerData *_peer; + QString _title; + +}; + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp index 98ba80f27..08126174b 100644 --- a/Telegram/SourceFiles/profile/profile_cover.cpp +++ b/Telegram/SourceFiles/profile/profile_cover.cpp @@ -119,7 +119,9 @@ void CoverWidget::resizeToWidth(int newWidth) { newHeight += st::profilePhotoSize; newHeight += st::profileMarginBottom; - newHeight += st::profileSeparator; + + _dividerTop = newHeight; + newHeight += st::profileDividerFill.height(); newHeight += st::profileBlocksTop; @@ -130,7 +132,7 @@ void CoverWidget::resizeToWidth(int newWidth) { void CoverWidget::paintEvent(QPaintEvent *e) { Painter p(this); - p.fillRect(e->rect(), st::white); + p.fillRect(e->rect(), st::profileBg); int availWidth = width() - _namePosition.x() - _photoButton->x(); p.setFont(st::profileNameFont); @@ -140,6 +142,16 @@ void CoverWidget::paintEvent(QPaintEvent *e) { p.setFont(st::profileStatusFont); p.setPen(st::profileStatusFg); p.drawTextLeft(_statusPosition.x(), _statusPosition.y(), width(), _statusText); + + paintDivider(p); +} + +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::updateStatusText() { diff --git a/Telegram/SourceFiles/profile/profile_cover.h b/Telegram/SourceFiles/profile/profile_cover.h index 1f06abcca..de911a08a 100644 --- a/Telegram/SourceFiles/profile/profile_cover.h +++ b/Telegram/SourceFiles/profile/profile_cover.h @@ -44,6 +44,8 @@ private: void updateStatusText(); bool isUsingMegagroupOnlineCount() const; + void paintDivider(Painter &p); + PeerData *_peer; UserData *_peerUser; ChatData *_peerChat; @@ -59,6 +61,8 @@ private: QPoint _statusPosition; QString _statusText; + int _dividerTop; + ChildWidget _primaryButton = { nullptr }; ChildWidget _secondaryButton = { nullptr }; diff --git a/Telegram/SourceFiles/profile/profile_inner_widget.cpp b/Telegram/SourceFiles/profile/profile_inner_widget.cpp index 49580822b..408ecf18c 100644 --- a/Telegram/SourceFiles/profile/profile_inner_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_inner_widget.cpp @@ -30,6 +30,7 @@ namespace Profile { InnerWidget::InnerWidget(QWidget *parent, PeerData *peer) : TWidget(parent) , _peer(peer) , _cover(this, peer) { + setAttribute(Qt::WA_OpaquePaintEvent); } void InnerWidget::resizeToWidth(int newWidth, int minHeight) { @@ -62,7 +63,7 @@ void InnerWidget::decreaseAdditionalHeight(int removeHeight) { void InnerWidget::paintEvent(QPaintEvent *e) { Painter p(this); - p.fillRect(e->rect(), st::white); + p.fillRect(e->rect(), st::profileBg); } void InnerWidget::mousePressEvent(QMouseEvent *e) { // TEMP for testing diff --git a/Telegram/SourceFiles/ui/style/style_core_icon.cpp b/Telegram/SourceFiles/ui/style/style_core_icon.cpp index 203ef8bfc..83bd66a26 100644 --- a/Telegram/SourceFiles/ui/style/style_core_icon.cpp +++ b/Telegram/SourceFiles/ui/style/style_core_icon.cpp @@ -85,6 +85,11 @@ int MonoIcon::height() const { return _size.height(); } +QSize MonoIcon::size() const { + ensureLoaded(); + return _size; +} + QPoint MonoIcon::offset() const { return _offset; } @@ -103,6 +108,15 @@ void MonoIcon::paint(QPainter &p, const QPoint &pos, int outerw) const { } } +void MonoIcon::fill(QPainter &p, const QRect &rect) const { + ensureLoaded(); + if (_pixmap.isNull()) { + p.fillRect(rect, _color); + } else { + p.drawPixmap(rect, _pixmap, QRect(0, 0, _pixmap.width(), _pixmap.height())); + } +} + void MonoIcon::ensureLoaded() const { if (_size.isValid()) { return; @@ -162,15 +176,26 @@ MonoIcon::MonoIcon(const IconMask *mask, const Color &color, QPoint offset, Owni } void Icon::paint(QPainter &p, const QPoint &pos, int outerw) const { - for_const (const auto &part, _parts) { + for_const (auto &part, _parts) { part.paint(p, pos, outerw); } } +void Icon::fill(QPainter &p, const QRect &rect) const { + if (_parts.isEmpty()) return; + + auto partSize = _parts.at(0).size(); + for_const (auto &part, _parts) { + t_assert(part.offset() == QPoint(0, 0)); + t_assert(part.size() == partSize); + part.fill(p, rect); + } +} + int Icon::width() const { if (_width < 0) { _width = 0; - for_const (const auto &part, _parts) { + for_const (auto &part, _parts) { accumulate_max(_width, part.offset().x() + part.width()); } } diff --git a/Telegram/SourceFiles/ui/style/style_core_icon.h b/Telegram/SourceFiles/ui/style/style_core_icon.h index 7c424026f..446c6258c 100644 --- a/Telegram/SourceFiles/ui/style/style_core_icon.h +++ b/Telegram/SourceFiles/ui/style/style_core_icon.h @@ -52,9 +52,11 @@ public: int width() const; int height() const; + QSize size() const; QPoint offset() const; void paint(QPainter &p, const QPoint &pos, int outerw) const; + void fill(QPainter &p, const QRect &rect) const; MonoIcon clone(const Color &color) const { return MonoIcon(_mask, color ? color : _color, _offset, OwningPixmapTag()); @@ -104,6 +106,7 @@ public: } void paint(QPainter &p, const QPoint &pos, int outerw) const; + void fill(QPainter &p, const QRect &rect) const; int width() const; int height() const; diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index b96571968..89c43e219 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -390,6 +390,10 @@ true true + + true + true + true true @@ -701,6 +705,10 @@ true true + + true + true + true true @@ -1038,6 +1046,10 @@ true true + + true + true + true true @@ -1199,6 +1211,7 @@ + @@ -1527,6 +1540,20 @@ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_fixed_bar.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" + + $(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing profile_block_widget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_block_widget.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" + $(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing profile_block_widget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_block_widget.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl_debug\Debug\include" + $(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing profile_block_widget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_block_widget.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" + diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 0fec8a251..58b4e144e 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -1188,6 +1188,18 @@ SourceFiles\profile + + GeneratedFiles\Deploy + + + GeneratedFiles\Debug + + + GeneratedFiles\Release + + + SourceFiles\profile + @@ -1633,6 +1645,9 @@ SourceFiles\window + + SourceFiles\profile + From 41b330c5eac8b1af465a92db05fc9d15aa0d5b15 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 23 May 2016 15:41:09 +0300 Subject: [PATCH 08/30] Primary and secondary round buttons tested in new profiles. --- Telegram/Resources/basic.style | 4 +- Telegram/SourceFiles/profile/profile.style | 25 +++++ .../SourceFiles/profile/profile_cover.cpp | 38 +++++++ Telegram/SourceFiles/profile/profile_cover.h | 17 ++- .../SourceFiles/profile/profile_fixed_bar.cpp | 25 +++++ .../SourceFiles/profile/profile_fixed_bar.h | 19 ++++ Telegram/SourceFiles/profilewidget.cpp | 10 +- Telegram/SourceFiles/ui/button.cpp | 7 ++ Telegram/SourceFiles/ui/button.h | 2 + .../SourceFiles/ui/buttons/round_button.cpp | 105 ++++++++++++++++++ .../SourceFiles/ui/buttons/round_button.h | 54 +++++++++ Telegram/Telegram.vcxproj | 2 + Telegram/Telegram.vcxproj.filters | 6 + 13 files changed, 304 insertions(+), 10 deletions(-) create mode 100644 Telegram/SourceFiles/ui/buttons/round_button.cpp create mode 100644 Telegram/SourceFiles/ui/buttons/round_button.h diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style index 1695b0218..60adbe02f 100644 --- a/Telegram/Resources/basic.style +++ b/Telegram/Resources/basic.style @@ -1685,8 +1685,8 @@ profilePadding: margins(28px, 30px, 28px, 0px); profilePhoneLeft: 22px; profilePhoneTop: 62px; profilePhoneFont: font(16px); -profileButtonTop: 18px; -profileButtonSkip: 10px; +//profileButtonTop: 18px; +//profileButtonSkip: 10px; profileHeaderFont: font(20px); profileHeaderColor: black; profileHeaderSkip: 59px; diff --git a/Telegram/SourceFiles/profile/profile.style b/Telegram/SourceFiles/profile/profile.style index 214095fdb..ad27e820b 100644 --- a/Telegram/SourceFiles/profile/profile.style +++ b/Telegram/SourceFiles/profile/profile.style @@ -19,6 +19,7 @@ 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; @@ -45,6 +46,30 @@ profileStatusFont: normalFont; profileStatusFg: windowSubTextFg; profileMarginBottom: 30px; +profileButtonLeft: 27px; +profileButtonTop: 88px; +profileButtonSkip: 10px; +profilePrimaryButton: BoxButton { + textFg: #ffffff; + textFgOver: #ffffff; + textBg: #3fb0e4; + textBgOver: #3fb0e4; + + width: -34px; + height: 34px; + + textTop: 8px; + + font: semiboldFont; + duration: 200; +} +profileSecondaryButton: BoxButton(profilePrimaryButton) { + textFg: #189dda; + textFgOver: #189dda; + textBg: #ffffff; + textBgOver: #f2f7fa; +} + profileDividerFg: black; profileDividerLeft: icon { { "profile_divider_left", profileDividerFg }, diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp index 08126174b..33b213167 100644 --- a/Telegram/SourceFiles/profile/profile_cover.cpp +++ b/Telegram/SourceFiles/profile/profile_cover.cpp @@ -22,6 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "profile/profile_cover.h" #include "styles/style_profile.h" +#include "ui/buttons/round_button.h" #include "lang.h" #include "apiwrap.h" #include "mainwidget.h" @@ -95,6 +96,9 @@ CoverWidget::CoverWidget(QWidget *parent, PeerData *peer) : TWidget(parent) _nameText.setText(st::profileNameFont, App::peerName(_peer)); updateStatusText(); + + _primaryButton = new Ui::RoundButton(this, "SEND MESSAGE", st::profilePrimaryButton); + _secondaryButton = new Ui::RoundButton(this, "SHARE CONTACT", st::profileSecondaryButton); } void CoverWidget::onPhotoShow() { @@ -107,6 +111,31 @@ void CoverWidget::onPhotoShow() { } } +void CoverWidget::onSetPhoto() { + +} + +void CoverWidget::onAddMember() { + +} + +void CoverWidget::onSendMessage() { + +} + +void CoverWidget::onShareContact() { + +} + +void CoverWidget::onJoin() { + +} + +void CoverWidget::onViewChannel() { + +} + + void CoverWidget::resizeToWidth(int newWidth) { int newHeight = 0; @@ -117,6 +146,15 @@ void CoverWidget::resizeToWidth(int newWidth) { _namePosition = QPoint(infoLeft + st::profileNameLeft, _photoButton->y() + st::profileNameTop); _statusPosition = QPoint(infoLeft + st::profileStatusLeft, _photoButton->y() + st::profileStatusTop); + int buttonLeft = st::profilePhotoLeft + _photoButton->width() + st::profileButtonLeft; + if (_primaryButton) { + _primaryButton->moveToLeft(buttonLeft, st::profileButtonTop); + buttonLeft += _primaryButton->width() + st::profileButtonSkip; + } + if (_secondaryButton) { + _secondaryButton->moveToLeft(buttonLeft, st::profileButtonTop); + } + newHeight += st::profilePhotoSize; newHeight += st::profileMarginBottom; diff --git a/Telegram/SourceFiles/profile/profile_cover.h b/Telegram/SourceFiles/profile/profile_cover.h index de911a08a..2c6d1b7ec 100644 --- a/Telegram/SourceFiles/profile/profile_cover.h +++ b/Telegram/SourceFiles/profile/profile_cover.h @@ -20,6 +20,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once +namespace Ui { +class RoundButton; +} // namespace Ui + namespace Profile { class BackButton; @@ -34,9 +38,16 @@ public: // Count new height for width=newWidth and resize to it. void resizeToWidth(int newWidth); -public slots: +private slots: void onPhotoShow(); + void onSetPhoto(); + void onAddMember(); + void onSendMessage(); + void onShareContact(); + void onJoin(); + void onViewChannel(); + protected: void paintEvent(QPaintEvent *e) override; @@ -63,8 +74,8 @@ private: int _dividerTop; - ChildWidget _primaryButton = { nullptr }; - ChildWidget _secondaryButton = { nullptr }; + ChildWidget _primaryButton = { nullptr }; + ChildWidget _secondaryButton = { nullptr }; }; diff --git a/Telegram/SourceFiles/profile/profile_fixed_bar.cpp b/Telegram/SourceFiles/profile/profile_fixed_bar.cpp index 785f5971b..a640633c8 100644 --- a/Telegram/SourceFiles/profile/profile_fixed_bar.cpp +++ b/Telegram/SourceFiles/profile/profile_fixed_bar.cpp @@ -68,6 +68,31 @@ void FixedBar::onBack() { App::main()->showBackFromStack(); } +void FixedBar::onEditChannel() { + +} + +void FixedBar::onEditGroup() { + +} + +void FixedBar::onLeaveGroup() { + +} + +void FixedBar::onAddContact() { + +} + +void FixedBar::onEditContact() { + +} + +void FixedBar::onDeleteContact() { + +} + + void FixedBar::resizeToWidth(int newWidth) { int newHeight = 0; diff --git a/Telegram/SourceFiles/profile/profile_fixed_bar.h b/Telegram/SourceFiles/profile/profile_fixed_bar.h index 2c42ff67a..325506703 100644 --- a/Telegram/SourceFiles/profile/profile_fixed_bar.h +++ b/Telegram/SourceFiles/profile/profile_fixed_bar.h @@ -42,7 +42,25 @@ protected: public slots: void onBack(); +private slots: + void onEditChannel(); + void onEditGroup(); + void onLeaveGroup(); + void onAddContact(); + void onEditContact(); + void onDeleteContact(); + private: + void refreshRightActions(); + enum class RightActionType { + EditChannel, + EditGroup, + LeaveGroup, + AddContact, + EditContact, + DeleteContact + }; + PeerData *_peer; UserData *_peerUser; ChatData *_peerChat; @@ -51,6 +69,7 @@ private: ChildWidget _backButton; QList _rightActions; + QList _rightActionTypes; bool _animatingMode = false; diff --git a/Telegram/SourceFiles/profilewidget.cpp b/Telegram/SourceFiles/profilewidget.cpp index 986fe86c5..b6d5e993f 100644 --- a/Telegram/SourceFiles/profilewidget.cpp +++ b/Telegram/SourceFiles/profilewidget.cpp @@ -887,12 +887,12 @@ void ProfileInner::paintEvent(QPaintEvent *e) { // p.drawText(_left + st::profilePhotoSize + st::profilePhoneLeft, top + addbyname + st::profilePhoneTop + st::profilePhoneFont->ascent, _phoneText); } // top += st::profilePhotoSize; - top += st::profileButtonTop; +// top += st::profileButtonTop; if ((!_peerChat || _peerChat->canEdit()) && (!_peerChannel || _amCreator || (_peerChannel->canAddParticipants() && _peerChannel->isMegagroup()))) { top += _shareContact.height(); } else { - top -= st::profileButtonTop; +// top -= st::profileButtonTop; } // about @@ -1378,7 +1378,7 @@ void ProfileInner::resizeEvent(QResizeEvent *e) { _width = qMin(width() - st::profilePadding.left() - st::profilePadding.right(), int(st::profileMaxWidth)); _left = (width() - _width) / 2; - int32 top = 0, btnWidth = (_width - st::profileButtonSkip) / 2; + int32 top = 0, btnWidth = 0;// (_width - st::profileButtonSkip) / 2; // profile top += st::profilePadding.top(); @@ -1403,7 +1403,7 @@ void ProfileInner::resizeEvent(QResizeEvent *e) { //_pinnedMessage.move(_left + st::profilePhotoSize + st::profileStatusLeft, top + addbyname + st::profileStatusTop); //top += st::profilePhotoSize; - top += st::profileButtonTop; + //top += st::profileButtonTop; _uploadPhoto.setGeometry(_left, top, btnWidth, _uploadPhoto.height()); if (_peerChannel && _peerChannel->count < Global::MegagroupSizeMax() && _peerChannel->isMegagroup() && !_amCreator && !_peerChannel->amEditor() && _peerChannel->canAddParticipants()) { @@ -1419,7 +1419,7 @@ void ProfileInner::resizeEvent(QResizeEvent *e) { if ((!_peerChat || _peerChat->canEdit()) && (!_peerChannel || _amCreator || (_peerChannel->canAddParticipants() && _peerChannel->isMegagroup()))) { top += _shareContact.height(); } else { - top -= st::profileButtonTop; + //top -= st::profileButtonTop; } // about diff --git a/Telegram/SourceFiles/ui/button.cpp b/Telegram/SourceFiles/ui/button.cpp index 767bac625..8cdd88889 100644 --- a/Telegram/SourceFiles/ui/button.cpp +++ b/Telegram/SourceFiles/ui/button.cpp @@ -51,6 +51,7 @@ void Button::mousePressEvent(QMouseEvent *e) { int oldState = _state; _state |= StateDown; emit stateChanged(oldState, ButtonByPress); + onStateChanged(oldState, ButtonByPress); e->accept(); } @@ -70,6 +71,7 @@ void Button::mouseReleaseEvent(QMouseEvent *e) { int oldState = _state; _state &= ~StateDown; emit stateChanged(oldState, ButtonByPress); + onStateChanged(oldState, ButtonByPress); if (oldState & StateOver) { _modifiers = e->modifiers(); emit clicked(); @@ -84,10 +86,12 @@ void Button::setOver(bool over, ButtonStateChangeSource source) { int oldState = _state; _state |= StateOver; emit stateChanged(oldState, source); + onStateChanged(oldState, source); } else if (!over && (_state & StateOver)) { int oldState = _state; _state &= ~StateOver; emit stateChanged(oldState, source); + onStateChanged(oldState, source); } } @@ -96,9 +100,11 @@ void Button::setDisabled(bool disabled) { if (disabled && !(_state & StateDisabled)) { _state |= StateDisabled; emit stateChanged(oldState, ButtonByUser); + onStateChanged(oldState, ButtonByUser); } else if (!disabled && (_state & StateDisabled)) { _state &= ~StateDisabled; emit stateChanged(oldState, ButtonByUser); + onStateChanged(oldState, ButtonByUser); } } @@ -106,6 +112,7 @@ void Button::clearState() { int oldState = _state; _state = StateNone; emit stateChanged(oldState, ButtonByUser); + onStateChanged(oldState, ButtonByUser); } int Button::getState() const { diff --git a/Telegram/SourceFiles/ui/button.h b/Telegram/SourceFiles/ui/button.h index 03262f0d3..13afd8f84 100644 --- a/Telegram/SourceFiles/ui/button.h +++ b/Telegram/SourceFiles/ui/button.h @@ -70,6 +70,8 @@ signals: void stateChanged(int oldState, ButtonStateChangeSource source); protected: + virtual void onStateChanged(int oldState, ButtonStateChangeSource source) { + } Qt::KeyboardModifiers _modifiers; int _state; diff --git a/Telegram/SourceFiles/ui/buttons/round_button.cpp b/Telegram/SourceFiles/ui/buttons/round_button.cpp new file mode 100644 index 000000000..0edef5a04 --- /dev/null +++ b/Telegram/SourceFiles/ui/buttons/round_button.cpp @@ -0,0 +1,105 @@ +/* +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 "ui/buttons/round_button.h" + +namespace Ui { + +RoundButton::RoundButton(QWidget *parent, const QString &text, const style::BoxButton &st) : Button(parent) +, _text(text.toUpper()) +, _fullText(text.toUpper()) +, _textWidth(st.font->width(_text)) +, _st(st) +, a_textBgOverOpacity(0) +, a_textFg(st.textFg->c) +, _a_over(animation(this, &RoundButton::step_over)) { + resizeToText(); + + setCursor(style::cur_pointer); +} + +void RoundButton::setText(const QString &text) { + _text = text; + _fullText = text; + _textWidth = _st.font->width(_text); + resizeToText(); +} + +void RoundButton::resizeToText() { + if (_st.width <= 0) { + resize(_textWidth - _st.width, _st.height); + } else { + if (_st.width < _textWidth + (_st.height - _st.font->height)) { + _text = _st.font->elided(_fullText, qMax(_st.width - (_st.height - _st.font->height), 1)); + _textWidth = _st.font->width(_text); + } + resize(_st.width, _st.height); + } +} + +void RoundButton::paintEvent(QPaintEvent *e) { + Painter p(this); + + App::roundRect(p, rect(), _st.textBg); + + float64 o = a_textBgOverOpacity.current(); + if (o > 0) { + p.setOpacity(o); + App::roundRect(p, rect(), _st.textBgOver); + p.setOpacity(1); + p.setPen(a_textFg.current()); + } else { + p.setPen(_st.textFg); + } + p.setFont(_st.font); + p.drawText((width() - _textWidth) / 2, _st.textTop + _st.font->ascent, _text); +} + +void RoundButton::step_over(float64 ms, bool timer) { + float64 dt = ms / _st.duration; + if (dt >= 1) { + _a_over.stop(); + a_textFg.finish(); + a_textBgOverOpacity.finish(); + } else { + a_textFg.update(dt, anim::linear); + a_textBgOverOpacity.update(dt, anim::linear); + } + if (timer) update(); +} + +void RoundButton::onStateChanged(int oldState, ButtonStateChangeSource source) { + float64 textBgOverOpacity = (_state & StateOver) ? 1 : 0; + style::color textFg = (_state & StateOver) ? (_st.textFgOver) : _st.textFg; + + a_textBgOverOpacity.start(textBgOverOpacity); + a_textFg.start(textFg->c); + if (source == ButtonByUser || source == ButtonByPress) { + _a_over.stop(); + a_textBgOverOpacity.finish(); + a_textFg.finish(); + update(); + } else { + _a_over.start(); + } +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/buttons/round_button.h b/Telegram/SourceFiles/ui/buttons/round_button.h new file mode 100644 index 000000000..aa3e1cfaa --- /dev/null +++ b/Telegram/SourceFiles/ui/buttons/round_button.h @@ -0,0 +1,54 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "ui/button.h" + +namespace Ui { + +class RoundButton : public Button { +public: + RoundButton(QWidget *parent, const QString &text, const style::BoxButton &st); + + void setText(const QString &text); + +protected: + void paintEvent(QPaintEvent *e) override; + + void onStateChanged(int oldState, ButtonStateChangeSource source) override; + +private: + void step_over(float64 ms, bool timer); + + void resizeToText(); + + QString _text, _fullText; + int _textWidth; + + const style::BoxButton &_st; + + anim::fvalue a_textBgOverOpacity; + anim::cvalue a_textFg; + Animation _a_over; + +}; + +} // namespace Ui diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index 89c43e219..bc4ea1730 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -1254,6 +1254,7 @@ + @@ -1602,6 +1603,7 @@ "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" "-fstdafx.h" "-f../../SourceFiles/ui/countryinput.h" + diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 58b4e144e..6564b3fcb 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -1200,6 +1200,9 @@ SourceFiles\profile + + SourceFiles\ui\buttons + @@ -1385,6 +1388,9 @@ SourceFiles\profile + + SourceFiles\ui\buttons + From e3e49dbeb877b3dae35a32c0327430d4aedaa723 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 24 May 2016 19:13:07 +0300 Subject: [PATCH 09/30] New profile buttons started, not performing actions yet. New system of Observers. Subscriptions on PeerData updates. --- Telegram/Resources/langs/lang.strings | 2 + Telegram/SourceFiles/app.cpp | 73 +++++++-- Telegram/SourceFiles/boxes/addcontactbox.cpp | 3 +- Telegram/SourceFiles/core/basic_types.cpp | 1 - Telegram/SourceFiles/core/basic_types.h | 30 +++- Telegram/SourceFiles/core/observer.cpp | 62 +++++++ Telegram/SourceFiles/core/observer.h | 134 +++++++++++++++ .../SourceFiles/core/vector_of_moveable.h | 153 ++++++++++++++++++ Telegram/SourceFiles/facades.cpp | 3 +- Telegram/SourceFiles/observer_peer.cpp | 104 ++++++++++++ Telegram/SourceFiles/observer_peer.h | 70 ++++++++ .../SourceFiles/profile/profile_cover.cpp | 112 +++++++++++-- Telegram/SourceFiles/profile/profile_cover.h | 30 +++- .../SourceFiles/profile/profile_widget.cpp | 2 +- Telegram/SourceFiles/structs.h | 12 +- Telegram/SourceFiles/ui/scrollarea.cpp | 13 +- Telegram/SourceFiles/ui/scrollarea.h | 2 + Telegram/Telegram.vcxproj | 6 + Telegram/Telegram.vcxproj.filters | 16 ++ 19 files changed, 792 insertions(+), 36 deletions(-) create mode 100644 Telegram/SourceFiles/core/observer.cpp create mode 100644 Telegram/SourceFiles/core/observer.h create mode 100644 Telegram/SourceFiles/core/vector_of_moveable.h create mode 100644 Telegram/SourceFiles/observer_peer.cpp create mode 100644 Telegram/SourceFiles/observer_peer.h diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index c712b264f..c73defa5b 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -432,6 +432,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"; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 5c9ab777c..a43eced3e 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -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; @@ -365,20 +366,23 @@ namespace { } UserData *feedUsers(const MTPVector &users, bool emitPeerUpdated) { - UserData *data = 0; - const auto &v(users.c_vector().v); - for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) { - const auto &user(*i); - data = 0; - bool wasContact = false, minimal = false; + UserData *result = nullptr; + for_const (auto &user, users.c_vector().v) { + UserData *data = nullptr; + bool wasContact = false, canShareContact = false, minimal = false; const MTPUserStatus *status = 0, emptyStatus = MTP_userStatusEmpty(); + Notify::PeerUpdate update; + using UpdateFlag = Notify::PeerUpdateFlag; + 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->canShareThisContact(); + 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()); @@ -389,6 +393,10 @@ namespace { wasContact = (data->contact > 0); status = &emptyStatus; data->contact = -1; + + if (canShareThisContact != data->canShareThisContact()) { + update.flags |= UpdateFlag::UserCanShareContact; + } } break; case mtpc_user: { const auto &d(user.c_user()); @@ -396,6 +404,8 @@ namespace { PeerId peer(peerFromUser(d.vid.v)); data = App::user(peer); + auto canShareThisContact = data->canShareThisContact(); + if (!minimal) { data->flags = d.vflags.v; if (d.is_self()) { @@ -478,6 +488,10 @@ namespace { if (App::wnd()) App::wnd()->updateGlobalMenu(); } } + + if (canShareThisContact != data->canShareThisContact()) { + update.flags |= UpdateFlag::UserCanShareContact; + } } break; } @@ -513,22 +527,33 @@ namespace { if (emitPeerUpdated) { App::main()->peerUpdated(data); + if (update.flags) { + update.peer = data; + Notify::peerUpdated(update); + } } else { markPeerUpdated(data); + if (update.flags) { + update.peer = data; + Notify::peerUpdatedDelayed(update); + } } } + result = data; } - return data; + return result; } PeerData *feedChats(const MTPVector &chats, bool emitPeerUpdated) { - PeerData *data = 0; - const auto &v(chats.c_vector().v); - for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) { - const auto &chat(*i); - data = 0; + PeerData *result = nullptr; + for_const (auto &chat, chats.c_vector().v) { + PeerData *data = nullptr; bool minimal = false; + + Notify::PeerUpdate update; + using UpdateFlag = Notify::PeerUpdateFlag; + switch (chat.type()) { case mtpc_chat: { const auto &d(chat.c_chat()); @@ -621,6 +646,8 @@ namespace { } ChannelData *cdata = data->asChannel(); + auto wasInChannel = cdata->amIn(); + if (minimal) { int32 mask = MTPDchannel::Flag::f_broadcast | MTPDchannel::Flag::f_verified | MTPDchannel::Flag::f_megagroup | MTPDchannel::Flag::f_democracy; cdata->flags = (cdata->flags & ~mask) | (d.vflags.v & mask); @@ -644,6 +671,10 @@ namespace { cdata->isForbidden = false; cdata->flagsUpdated(); cdata->setPhoto(d.vphoto); + + if (wasInChannel != cdata->amIn() && !cdata->isMegagroup()) { + update.flags |= UpdateFlag::ChannelAmIn; + } } break; case mtpc_channelForbidden: { const auto &d(chat.c_channelForbidden()); @@ -653,6 +684,8 @@ namespace { data->input = MTP_inputPeerChannel(d.vid, d.vaccess_hash); ChannelData *cdata = data->asChannel(); + auto wasInChannel = cdata->amIn(); + cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash); cdata->setName(qs(d.vtitle), QString()); @@ -662,6 +695,10 @@ namespace { cdata->date = 0; cdata->count = 0; cdata->isForbidden = true; + + if (wasInChannel != cdata->amIn() && !cdata->isMegagroup()) { + update.flags |= UpdateFlag::ChannelAmIn; + } } break; } if (!data) continue; @@ -676,12 +713,21 @@ namespace { if (App::main()) { if (emitPeerUpdated) { App::main()->peerUpdated(data); + if (update.flags) { + update.peer = data; + Notify::peerUpdated(update); + } } else { 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) { @@ -1245,6 +1291,7 @@ namespace { App::main()->peerUpdated(i.key()); } } + Notify::peerUpdatedSendDelayed(); } PhotoData *feedPhoto(const MTPPhoto &photo, PhotoData *convert) { diff --git a/Telegram/SourceFiles/boxes/addcontactbox.cpp b/Telegram/SourceFiles/boxes/addcontactbox.cpp index 444742a81..2d37dff66 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.cpp +++ b/Telegram/SourceFiles/boxes/addcontactbox.cpp @@ -222,7 +222,8 @@ void AddContactBox::onImportDone(const MTPcontacts_ImportedContacts &res) { if (isHidden() || !App::main()) return; const auto &d(res.c_contacts_importedContacts()); - App::feedUsers(d.vusers); + App::feedUsers(d.vusers, false); + App::emitPeerUpdated(); const auto &v(d.vimported.c_vector().v); UserData *user = nullptr; diff --git a/Telegram/SourceFiles/core/basic_types.cpp b/Telegram/SourceFiles/core/basic_types.cpp index 79851ca37..e3fc72642 100644 --- a/Telegram/SourceFiles/core/basic_types.cpp +++ b/Telegram/SourceFiles/core/basic_types.cpp @@ -44,7 +44,6 @@ uint64 _SharedMemoryLocation[4] = { 0x00, 0x01, 0x02, 0x03 }; #include // 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"); diff --git a/Telegram/SourceFiles/core/basic_types.h b/Telegram/SourceFiles/core/basic_types.h index 13bcd4e99..4907c2855 100644 --- a/Telegram/SourceFiles/core/basic_types.h +++ b/Telegram/SourceFiles/core/basic_types.h @@ -421,6 +421,24 @@ inline bool operator!=(std::nullptr_t a, const unique_ptr &b) noexcept { return !(a == b); } +using _yes = char(&)[1]; +using _no = char(&)[2]; + +template +struct _host { + operator Base*() const; + operator Derived*(); +}; + +template +struct is_base_of { + template + static _yes check(Derived*, T); + static _no check(Base*, int); + + static constexpr bool value = sizeof(check(_host(), int())) == sizeof(_yes); +}; + } // namespace std_ #include "logs.h" @@ -1182,18 +1200,22 @@ NullFunctionImplementation NullFunctionImplementation::S template class Function { public: - Function() : _implementation(&NullFunctionImplementation::SharedInstance) {} + Function() : _implementation(nullImpl()) {} Function(FunctionImplementation *implementation) : _implementation(implementation) {} Function(const Function &other) = delete; Function &operator=(const Function &other) = delete; Function(Function &&other) : _implementation(other._implementation) { - other._implementation = &NullFunctionImplementation::SharedInstance; + other._implementation = nullImpl(); } Function &operator=(Function &&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) { @@ -1204,6 +1226,10 @@ public: } private: + static FunctionImplementation *nullImpl() { + return &NullFunctionImplementation::SharedInstance; + } + FunctionImplementation *_implementation; }; diff --git a/Telegram/SourceFiles/core/observer.cpp b/Telegram/SourceFiles/core/observer.cpp new file mode 100644 index 000000000..5a8f2d389 --- /dev/null +++ b/Telegram/SourceFiles/core/observer.cpp @@ -0,0 +1,62 @@ +/* +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 { + +UnregisterObserverCallback UnregisterCallbacks[256]; + +} // namespace + +// 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(connection >> 24); + auto connectionIndex = int(connection & 0x00FFFFFFU) - 1; + if (connectionIndex >= 0 && UnregisterCallbacks[event]) { + UnregisterCallbacks[event](connectionIndex); + } +} + +UnregisterObserverCallbackCreator::UnregisterObserverCallbackCreator(ObservedEvent event, UnregisterObserverCallback callback) { + UnregisterCallbacks[event] = callback; +} + +namespace internal { + +void observerRegisteredDefault(Observer *observer, ConnectionId connection) { + observer->observerRegistered(connection); +} + +} // namespace internal + +} // namespace Notify diff --git a/Telegram/SourceFiles/core/observer.h b/Telegram/SourceFiles/core/observer.h new file mode 100644 index 000000000..2f992cb0b --- /dev/null +++ b/Telegram/SourceFiles/core/observer.h @@ -0,0 +1,134 @@ +/* +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 { + +class Observer; +using ConnectionId = uint32; + +// 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. + +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 _connections; + +}; + +using ObservedEvent = uchar; +inline ConnectionId observerConnectionId(ObservedEvent event, int connectionIndex) { + t_assert(connectionIndex >= 0 && connectionIndex < 0x01000000); + return (static_cast(event) << 24) | (connectionIndex + 1); +} + +using UnregisterObserverCallback = void(*)(int connectionIndex); + +// Usage: UnregisterObserverCallbackCreator creator(myEvent, myCallback); in global scope. +class UnregisterObserverCallbackCreator { +public: + UnregisterObserverCallbackCreator(ObservedEvent event, UnregisterObserverCallback callback); + +}; + +// Handler is one of Function<> instantiations. +template +struct ObserversList { + struct Entry { + Flags flags; + Handler handler; + }; + std_::vector_of_moveable entries; + QVector freeIndices; +}; + +template +int registerObserver(ObserversList &list, 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; +} + +template +void unregisterObserver(ObserversList &list, int connectionIndex) { + auto &entries(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(); + list.freeIndices.push_back(connectionIndex); + } +} + +template +void notifyObservers(ObserversList &list, Flags flags, Args&&... args) { + for (auto &entry : list.entries) { + if (!entry.handler.isNull() && (flags & entry.flags)) { + entry.handler.call(std_::forward(args)...); + } + } +} + +namespace internal { + +template +struct ObserverRegisteredGeneric { + static void call(ObserverType *observer, ConnectionId connection) { + observer->observerRegistered(connection); + } +}; + +template +struct ObserverRegisteredGeneric { + static void call(ObserverType *observer, ConnectionId connection) { + observerRegisteredDefault(observer, connection); + } +}; + +} // namespace internal +} // namespace Notify diff --git a/Telegram/SourceFiles/core/vector_of_moveable.h b/Telegram/SourceFiles/core/vector_of_moveable.h new file mode 100644 index 000000000..412d87b35 --- /dev/null +++ b/Telegram/SourceFiles/core/vector_of_moveable.h @@ -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 +class vector_of_moveable { + typedef vector_of_moveable Self; + int _size = 0, _capacity = 0; + void *_plaindata = nullptr; + +public: + inline T *data() { + return reinterpret_cast(_plaindata); + } + inline const T *data() const { + return reinterpret_cast(_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(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(newPlainData) + i) = std_::move(*(data() + i)); + } + std::swap(_plaindata, newPlainData); + _capacity = newCapacity; + operator delete[](newPlainData); + } + +}; + +} // namespace std_ diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index defd46993..b1aebc849 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -21,10 +21,11 @@ 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 "mainwindow.h" #include "mainwidget.h" #include "application.h" -#include "core/click_handler_types.h" #include "boxes/confirmbox.h" #include "layerwidget.h" #include "lang.h" diff --git a/Telegram/SourceFiles/observer_peer.cpp b/Telegram/SourceFiles/observer_peer.cpp new file mode 100644 index 000000000..9094ce5c2 --- /dev/null +++ b/Telegram/SourceFiles/observer_peer.cpp @@ -0,0 +1,104 @@ +/* +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 Notify { +namespace internal { +namespace { + +constexpr ObservedEvent PeerUpdateEvent = 0x01; +ObserversList PeerUpdateObservers; + +void UnregisterCallback(int connectionIndex) { + unregisterObserver(PeerUpdateObservers, connectionIndex); +} +UnregisterObserverCallbackCreator creator(PeerUpdateEvent, UnregisterCallback); + +QVector SmallPeerUpdates; +QMap AllPeerUpdates; + +} // namespace + +ConnectionId plainRegisterPeerObserver(PeerUpdateFlags events, PeerUpdateHandler &&handler) { + auto connectionId = registerObserver(PeerUpdateObservers, events, std_::forward(handler)); + t_assert(connectionId >= 0 && connectionId < 0x01000000); + return (static_cast(PeerUpdateEvent) << 24) | static_cast(connectionId + 1); +} + +void mergePeerUpdate(PeerUpdate &mergeTo, const PeerUpdate &mergeFrom) { + mergeTo.flags |= mergeFrom.flags; + + // merge fields used in mergeFrom.flags +} + +} // namespace internal + +void peerUpdated(const PeerUpdate &update) { + notifyObservers(internal::PeerUpdateObservers, update.flags, update); +} + +void peerUpdatedDelayed(const PeerUpdate &update) { + int alreadySavedCount = internal::SmallPeerUpdates.size(); + for (int i = 0; i < alreadySavedCount; ++i) { + if (internal::SmallPeerUpdates.at(i).peer == update.peer) { + internal::mergePeerUpdate(internal::SmallPeerUpdates[i], update); + return; + } + } + if (internal::AllPeerUpdates.isEmpty()) { + if (alreadySavedCount < 5) { + internal::SmallPeerUpdates.push_back(update); + } else { + internal::AllPeerUpdates.insert(update.peer, update); + } + } else { + auto it = internal::AllPeerUpdates.find(update.peer); + if (it != internal::AllPeerUpdates.cend()) { + internal::mergePeerUpdate(it.value(), update); + return; + } + internal::AllPeerUpdates.insert(update.peer, update); + } +} + +void peerUpdatedSendDelayed() { + if (internal::SmallPeerUpdates.isEmpty()) return; + + decltype(internal::SmallPeerUpdates) smallList; + decltype(internal::AllPeerUpdates) allList; + std::swap(smallList, internal::SmallPeerUpdates); + std::swap(allList, internal::AllPeerUpdates); + for_const (auto &update, smallList) { + peerUpdated(update); + } + for_const (auto &update, allList) { + peerUpdated(update); + } + if (internal::SmallPeerUpdates.isEmpty()) { + std::swap(smallList, internal::SmallPeerUpdates); + internal::SmallPeerUpdates.resize(0); + } +} + +} // namespace Notify diff --git a/Telegram/SourceFiles/observer_peer.h b/Telegram/SourceFiles/observer_peer.h new file mode 100644 index 000000000..76a7514b9 --- /dev/null +++ b/Telegram/SourceFiles/observer_peer.h @@ -0,0 +1,70 @@ +/* +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(). + +enum class PeerUpdateFlag { + //PeerNameChanged = 0x0001, + + UserCanShareContact = 0x1001, + + ChatCanEdit = 0x2001, + + MegagroupCanEditPhoto = 0x4001, + MegagroupCanAddMembers = 0x4002, + + ChannelAmIn = 0x8001, +}; +Q_DECLARE_FLAGS(PeerUpdateFlags, PeerUpdateFlag); +Q_DECLARE_OPERATORS_FOR_FLAGS(PeerUpdateFlags); +struct PeerUpdate { + PeerData *peer = nullptr; + PeerUpdateFlags flags = 0; +}; + +void peerUpdated(const PeerUpdate &update); +void peerUpdatedDelayed(const PeerUpdate &update); +void peerUpdatedSendDelayed(); + +namespace internal { + +using PeerUpdateHandler = Function; +ConnectionId plainRegisterPeerObserver(PeerUpdateFlags events, PeerUpdateHandler &&handler); + +} // namespace internal + +template +void registerPeerObserver(PeerUpdateFlags events, ObserverType *observer, void (ObserverType::*handler)(const PeerUpdate &)) { + auto connection = internal::plainRegisterPeerObserver(events, func(observer, handler)); + + // 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::value>; + ObserverRegistered::call(observer, connection); +} + +} // namespace Notify diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp index 33b213167..24de8384a 100644 --- a/Telegram/SourceFiles/profile/profile_cover.cpp +++ b/Telegram/SourceFiles/profile/profile_cover.cpp @@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_profile.h" #include "ui/buttons/round_button.h" +#include "observer_peer.h" #include "lang.h" #include "apiwrap.h" #include "mainwidget.h" @@ -58,6 +59,12 @@ private: }; +const Notify::PeerUpdateFlags ButtonsUpdateFlags = Notify::PeerUpdateFlag::UserCanShareContact + | Notify::PeerUpdateFlag::ChatCanEdit + | Notify::PeerUpdateFlag::MegagroupCanEditPhoto + | Notify::PeerUpdateFlag::MegagroupCanAddMembers + | Notify::PeerUpdateFlag::ChannelAmIn; + } // namespace class PhotoButton final : public Button { @@ -91,14 +98,17 @@ CoverWidget::CoverWidget(QWidget *parent, PeerData *peer) : TWidget(parent) , _photoButton(this, peer) { setAttribute(Qt::WA_OpaquePaintEvent); + using Flag = Notify::PeerUpdateFlag; + auto observeEvents = ButtonsUpdateFlags; + Notify::registerPeerObserver(observeEvents, this, &CoverWidget::notifyPeerUpdated); + _photoButton->photoUpdated(); connect(_photoButton, SIGNAL(clicked()), this, SLOT(onPhotoShow())); _nameText.setText(st::profileNameFont, App::peerName(_peer)); updateStatusText(); - _primaryButton = new Ui::RoundButton(this, "SEND MESSAGE", st::profilePrimaryButton); - _secondaryButton = new Ui::RoundButton(this, "SHARE CONTACT", st::profileSecondaryButton); + updateButtons(); } void CoverWidget::onPhotoShow() { @@ -111,14 +121,6 @@ void CoverWidget::onPhotoShow() { } } -void CoverWidget::onSetPhoto() { - -} - -void CoverWidget::onAddMember() { - -} - void CoverWidget::onSendMessage() { } @@ -127,6 +129,14 @@ void CoverWidget::onShareContact() { } +void CoverWidget::onSetPhoto() { + +} + +void CoverWidget::onAddMember() { + +} + void CoverWidget::onJoin() { } @@ -244,4 +254,86 @@ bool CoverWidget::isUsingMegagroupOnlineCount() const { return true; } +void CoverWidget::notifyPeerUpdated(const Notify::PeerUpdate &update) { + if (update.flags & ButtonsUpdateFlags) { + updateButtons(); + } +} + +void CoverWidget::updateButtons() { + if (_peerUser) { + setUserButtons(); + } else if (_peerChat) { + setChatButtons(); + } else if (_peerMegagroup) { + setMegagroupButtons(); + } else if (_peerChannel) { + setChannelButtons(); + } + resizeToWidth(width()); +} + +void CoverWidget::setUserButtons() { + setPrimaryButton(lang(lng_profile_send_message), SLOT(onSendMessage())); + if (_peerUser->canShareThisContact()) { + setSecondaryButton(lang(lng_profile_share_contact), SLOT(onShareContact())); + } else { + clearSecondaryButton(); + } +} + +void CoverWidget::setChatButtons() { + if (_peerChat->canEdit()) { + setPrimaryButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto())); + setSecondaryButton(lang(lng_profile_add_participant), SLOT(onAddMember())); + } else { + clearPrimaryButton(); + clearSecondaryButton(); + } +} + +void CoverWidget::setMegagroupButtons() { + if (_peerMegagroup->canEditPhoto()) { + setPrimaryButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto())); + } else { + clearPrimaryButton(); + } + if (_peerMegagroup->canAddParticipants()) { + setSecondaryButton(lang(lng_profile_add_participant), SLOT(onAddMember())); + } else { + clearSecondaryButton(); + } +} + +void CoverWidget::setChannelButtons() { + if (_peerChannel->amCreator()) { + setPrimaryButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto())); + } else if (_peerChannel->amIn()) { + setPrimaryButton(lang(lng_profile_view_channel), SLOT(onViewChannel())); + } else { + setPrimaryButton(lang(lng_profile_join_channel), SLOT(onJoin())); + } + clearSecondaryButton(); +} + +void CoverWidget::setPrimaryButton(const QString &text, const char *slot) { + delete _primaryButton; + _primaryButton = nullptr; + if (!text.isEmpty()) { + _primaryButton = new Ui::RoundButton(this, text, st::profilePrimaryButton); + connect(_primaryButton, SIGNAL(clicked()), this, slot); + _primaryButton->show(); + } +} + +void CoverWidget::setSecondaryButton(const QString &text, const char *slot) { + delete _secondaryButton; + _secondaryButton = nullptr; + if (!text.isEmpty()) { + _secondaryButton = new Ui::RoundButton(this, text, st::profileSecondaryButton); + connect(_secondaryButton, SIGNAL(clicked()), this, slot); + _secondaryButton->show(); + } +} + } // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_cover.h b/Telegram/SourceFiles/profile/profile_cover.h index 2c6d1b7ec..ae84f15e1 100644 --- a/Telegram/SourceFiles/profile/profile_cover.h +++ b/Telegram/SourceFiles/profile/profile_cover.h @@ -20,16 +20,22 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once +#include "core/observer.h" + namespace Ui { class RoundButton; } // namespace Ui +namespace Notify { +struct PeerUpdate; +} // namespace Notify + namespace Profile { class BackButton; class PhotoButton; -class CoverWidget final : public TWidget { +class CoverWidget final : public TWidget, public Notify::Observer { Q_OBJECT public: @@ -41,10 +47,10 @@ public: private slots: void onPhotoShow(); - void onSetPhoto(); - void onAddMember(); void onSendMessage(); void onShareContact(); + void onSetPhoto(); + void onAddMember(); void onJoin(); void onViewChannel(); @@ -55,6 +61,24 @@ private: void updateStatusText(); bool isUsingMegagroupOnlineCount() const; + // Observed notifications. + void notifyPeerUpdated(const Notify::PeerUpdate &update); + + void updateButtons(); + void setUserButtons(); + void setChatButtons(); + void setMegagroupButtons(); + void setChannelButtons(); + + void setPrimaryButton(const QString &text, const char *slot); + void setSecondaryButton(const QString &text, const char *slot); + void clearPrimaryButton() { + setPrimaryButton(QString(), nullptr); + } + void clearSecondaryButton() { + setSecondaryButton(QString(), nullptr); + } + void paintDivider(Painter &p); PeerData *_peer; diff --git a/Telegram/SourceFiles/profile/profile_widget.cpp b/Telegram/SourceFiles/profile/profile_widget.cpp index af7d1717e..d943a3eaf 100644 --- a/Telegram/SourceFiles/profile/profile_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_widget.cpp @@ -42,7 +42,7 @@ Widget::Widget(QWidget *parent, PeerData *peer) : Window::SectionWidget(parent) _fixedBarShadow->raise(); updateAdaptiveLayout(); - _scroll->setWidget(_inner); + _scroll->setOwnedWidget(_inner); _scroll->move(0, _fixedBar->height()); _scroll->show(); diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index b9eb32b17..da3d2d293 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -419,6 +419,9 @@ public: bool canWrite() const { return access != UserNoAccess; } + bool canShareThisContact() const { + return contact >= 0; + } MTPInputUser inputUser; @@ -675,9 +678,6 @@ public: bool isPublic() const { return flags & MTPDchannel::Flag::f_username; } - bool canEditUsername() const { - return amCreator() && (flagsFull & MTPDchannelFull::Flag::f_can_set_username); - } bool amCreator() const { return flags & MTPDchannel::Flag::f_creator; } @@ -715,6 +715,12 @@ public: bool canAddParticipants() const { return amCreator() || amEditor() || (flags & MTPDchannel::Flag::f_democracy); } + bool canEditPhoto() const { + return amCreator() || (amEditor() && isMegagroup()); + } + bool canEditUsername() const { + return amCreator() && (flagsFull & MTPDchannelFull::Flag::f_can_set_username); + } // ImagePtr photoFull; QString invitationUrl; diff --git a/Telegram/SourceFiles/ui/scrollarea.cpp b/Telegram/SourceFiles/ui/scrollarea.cpp index aa0508a63..3f5a93c3e 100644 --- a/Telegram/SourceFiles/ui/scrollarea.cpp +++ b/Telegram/SourceFiles/ui/scrollarea.cpp @@ -718,6 +718,10 @@ void ScrollArea::setWidget(QWidget *w) { hor.raise(); vert.raise(); } + if (_ownsWidget) { + _ownsWidget = false; + delete takeWidget(); + } QScrollArea::setWidget(w); if (w) { w->setAutoFillBackground(false); @@ -738,6 +742,11 @@ void ScrollArea::setWidget(QWidget *w) { } } +void ScrollArea::setOwnedWidget(QWidget *widget) { + setWidget(widget); + _ownsWidget = true; +} + QWidget *ScrollArea::takeWidget() { if (_other) { delete _other; @@ -785,5 +794,7 @@ bool ScrollArea::focusNextPrevChild(bool next) { } ScrollArea::~ScrollArea() { - takeWidget(); + if (!_ownsWidget) { + takeWidget(); + } } diff --git a/Telegram/SourceFiles/ui/scrollarea.h b/Telegram/SourceFiles/ui/scrollarea.h index 6004df418..2e2504416 100644 --- a/Telegram/SourceFiles/ui/scrollarea.h +++ b/Telegram/SourceFiles/ui/scrollarea.h @@ -185,6 +185,7 @@ public: int scrollTop() const; void setWidget(QWidget *widget); + void setOwnedWidget(QWidget *widget); QWidget *takeWidget(); void rangeChanged(int oldMax, int newMax, bool vertical); @@ -237,6 +238,7 @@ private: void touchDeaccelerate(int32 elapsed); bool _disabled; + bool _ownsWidget = false; // if true, the widget is deleted in destructor. style::flatScroll _st; ScrollBar hor, vert; diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index bc4ea1730..ec36b0357 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -1162,6 +1162,7 @@ + @@ -1206,6 +1207,7 @@ + @@ -1356,6 +1358,8 @@ + + @@ -1484,6 +1488,7 @@ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" "-fstdafx.h" "-f../../SourceFiles/mtproto/session.h" + $(QTDIR)\bin\moc.exe;%(FullPath) @@ -2554,6 +2559,7 @@ + .\GeneratedFiles\lang_auto.h diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 6564b3fcb..7a28c5dd6 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -1203,6 +1203,12 @@ SourceFiles\ui\buttons + + SourceFiles\core + + + SourceFiles + @@ -1391,6 +1397,15 @@ SourceFiles\ui\buttons + + SourceFiles\core + + + SourceFiles\core + + + SourceFiles + @@ -1704,6 +1719,7 @@ Resources\langs + From a510bb54ec0eefd90c171f13c61d0a892212793b Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 25 May 2016 15:09:05 +0300 Subject: [PATCH 10/30] Peer observers get notified about name/username changes. All peer observer notifications are async (delayed). --- Telegram/SourceFiles/apiwrap.cpp | 15 +-- Telegram/SourceFiles/app.cpp | 100 ++++++------------ Telegram/SourceFiles/app.h | 13 ++- Telegram/SourceFiles/application.cpp | 4 + Telegram/SourceFiles/boxes/addcontactbox.cpp | 10 +- Telegram/SourceFiles/core/observer.cpp | 42 ++++++-- Telegram/SourceFiles/core/observer.h | 35 ++++-- Telegram/SourceFiles/history.cpp | 7 +- Telegram/SourceFiles/localstorage.cpp | 10 +- Telegram/SourceFiles/mainwidget.cpp | 85 +++++++-------- Telegram/SourceFiles/mainwidget.h | 1 - Telegram/SourceFiles/observer_peer.cpp | 99 +++++++++++------ Telegram/SourceFiles/observer_peer.h | 26 +++-- .../SourceFiles/profile/profile_cover.cpp | 94 +++++++++------- Telegram/SourceFiles/profile/profile_cover.h | 9 +- Telegram/SourceFiles/settingswidget.cpp | 19 ++-- Telegram/SourceFiles/settingswidget.h | 11 +- Telegram/SourceFiles/structs.cpp | 81 ++++++++------ Telegram/SourceFiles/structs.h | 69 ++++++------ 19 files changed, 413 insertions(+), 317 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index d49c68cc4..e555ccf91 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -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" @@ -178,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::feedUsersDelayed(d.vusers); + App::feedChatsDelayed(d.vchats); if (peer->isChat()) { if (d.vfull_chat.type() != mtpc_chatFull) { @@ -317,16 +318,17 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt } App::clearPeerUpdated(peer); emit fullPeerUpdated(peer); - App::emitPeerUpdated(); + + Notify::peerUpdatedSendDelayed(); } void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result, mtpRequestId req) { const auto &d(result.c_userFull()); - App::feedUsers(MTP_vector(1, d.vuser), false); + App::feedUsersDelayed(MTP_vector(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::feedUserLinkDelayed(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); } @@ -347,7 +349,8 @@ void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result, mtpRequestI } App::clearPeerUpdated(peer); emit fullPeerUpdated(peer); - App::emitPeerUpdated(); + + Notify::peerUpdatedSendDelayed(); } bool ApiWrap::gotPeerFullFailed(PeerData *peer, const RPCError &error) { diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index a43eced3e..bdb4ad3dd 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -365,7 +365,7 @@ namespace { return (online > now); } - UserData *feedUsers(const MTPVector &users, bool emitPeerUpdated) { + UserData *feedUsersDelayed(const MTPVector &users) { UserData *result = nullptr; for_const (auto &user, users.c_vector().v) { UserData *data = nullptr; @@ -385,7 +385,7 @@ namespace { 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()); + data->setNameDelayed(lang(lng_deleted), QString(), QString(), QString()); data->setPhoto(MTP_userProfilePhotoEmpty()); data->access = UserNoAccess; data->flags = 0; @@ -426,7 +426,7 @@ namespace { } if (d.is_deleted()) { data->setPhone(QString()); - data->setName(lang(lng_deleted), QString(), QString(), QString()); + data->setNameDelayed(lang(lng_deleted), QString(), QString(), QString()); data->setPhoto(MTP_userProfilePhotoEmpty()); data->access = UserNoAccess; status = &emptyStatus; @@ -459,7 +459,7 @@ namespace { if (!minimal && d.is_self() && uname != data->username) { SignalHandlers::setCrashAnnotation("Username", uname); } - data->setName(fname, lname, pname, uname); + data->setNameDelayed(fname, lname, pname, uname); if (d.has_photo()) { data->setPhoto(d.vphoto); } else { @@ -525,18 +525,10 @@ namespace { Notify::userIsContactChanged(data); } - if (emitPeerUpdated) { - App::main()->peerUpdated(data); - if (update.flags) { - update.peer = data; - Notify::peerUpdated(update); - } - } else { - markPeerUpdated(data); - if (update.flags) { - update.peer = data; - Notify::peerUpdatedDelayed(update); - } + markPeerUpdated(data); + if (update.flags) { + update.peer = data; + Notify::peerUpdatedDelayed(update); } } result = data; @@ -545,7 +537,7 @@ namespace { return result; } - PeerData *feedChats(const MTPVector &chats, bool emitPeerUpdated) { + PeerData *feedChatsDelayed(const MTPVector &chats) { PeerData *result = nullptr; for_const (auto &chat, chats.c_vector().v) { PeerData *data = nullptr; @@ -561,9 +553,8 @@ namespace { data = App::chat(peerFromChat(d.vid.v)); data->input = MTP_inputPeerChat(d.vid); - data->updateName(qs(d.vtitle), QString(), QString()); - ChatData *cdata = data->asChat(); + cdata->setNameDelayed(qs(d.vtitle)); cdata->setPhoto(d.vphoto); cdata->date = d.vdate.v; @@ -620,9 +611,8 @@ namespace { data = App::chat(peerFromChat(d.vid.v)); data->input = MTP_inputPeerChat(d.vid); - data->updateName(qs(d.vtitle), QString(), QString()); - ChatData *cdata = data->asChat(); + cdata->setNameDelayed(qs(d.vtitle)); cdata->setPhoto(MTP_chatPhotoEmpty()); cdata->date = 0; cdata->count = -1; @@ -666,7 +656,7 @@ namespace { } } QString uname = d.has_username() ? textOneLine(qs(d.vusername)) : QString(); - cdata->setName(qs(d.vtitle), uname); + cdata->setNameDelayed(qs(d.vtitle), uname); cdata->isForbidden = false; cdata->flagsUpdated(); @@ -688,7 +678,7 @@ namespace { cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash); - cdata->setName(qs(d.vtitle), QString()); + cdata->setNameDelayed(qs(d.vtitle), QString()); cdata->access = d.vaccess_hash.v; cdata->setPhoto(MTP_chatPhotoEmpty()); @@ -711,18 +701,10 @@ namespace { data->loadedStatus = PeerData::FullLoaded; } if (App::main()) { - if (emitPeerUpdated) { - App::main()->peerUpdated(data); - if (update.flags) { - update.peer = data; - Notify::peerUpdated(update); - } - } else { - markPeerUpdated(data); - if (update.flags) { - update.peer = data; - Notify::peerUpdatedDelayed(update); - } + markPeerUpdated(data); + if (update.flags) { + update.peer = data; + Notify::peerUpdatedDelayed(update); } } result = data; @@ -730,6 +712,18 @@ namespace { return result; } + UserData *feedUsers(const MTPVector &users) { + auto result = feedUsersDelayed(users); + Notify::peerUpdatedSendDelayed(); + return result; + } + + PeerData *feedChats(const MTPVector &chats) { + auto result = feedChatsDelayed(chats); + Notify::peerUpdatedSendDelayed(); + return result; + } + void feedParticipants(const MTPChatParticipants &p, bool requestBotInfos, bool emitPeerUpdated) { ChatData *chat = 0; switch (p.type()) { @@ -1206,30 +1200,7 @@ namespace { } } - void feedUserLinks(const MTPVector &links, bool emitPeerUpdated) { - const auto &v(links.c_vector().v); - for (QVector::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) { - const auto &dv(i->c_contacts_link()); - UserData *user = feedUsers(MTP_vector(1, dv.vuser), false); - MTPint userId(MTP_int(0)); - switch (dv.vuser.type()) { - 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 feedUserLinkDelayed(MTPint userId, const MTPContactLink &myLink, const MTPContactLink &foreignLink) { UserData *user = userLoaded(userId.v); if (user) { bool wasContact = (user->contact > 0); @@ -1262,15 +1233,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->setNameDelayed(textOneLine(user->firstName), textOneLine(user->lastName), showPhone ? App::formatPhone(user->phone) : QString(), textOneLine(user->username)); } + markPeerUpdated(user); } } @@ -1291,7 +1256,6 @@ namespace { App::main()->peerUpdated(i.key()); } } - Notify::peerUpdatedSendDelayed(); } PhotoData *feedPhoto(const MTPPhoto &photo, PhotoData *convert) { diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index 4e437b71b..4c58c332e 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -64,8 +64,13 @@ namespace App { QString onlineText(UserData *user, int32 nowOnServer, bool precise = false); bool onlineColorUse(UserData *user, int32 now); - UserData *feedUsers(const MTPVector &users, bool emitPeerUpdated = true); // returns last user - PeerData *feedChats(const MTPVector &chats, bool emitPeerUpdated = true); // returns last chat + UserData *feedUsers(const MTPVector &users); // returns last user + PeerData *feedChats(const MTPVector &chats); // returns last chat + + // Requires Notify::peerUpdatedSendDelayed() call after. + UserData *feedUsersDelayed(const MTPVector &users); // returns last user + PeerData *feedChatsDelayed(const MTPVector &chats); // 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 +85,10 @@ namespace App { void feedInboxRead(const PeerId &peer, MsgId upTo); void feedOutboxRead(const PeerId &peer, MsgId upTo); void feedWereDeleted(ChannelId channelId, const QVector &msgsIds); - void feedUserLinks(const MTPVector &links, bool emitPeerUpdated = true); - void feedUserLink(MTPint userId, const MTPContactLink &myLink, const MTPContactLink &foreignLink, bool emitPeerUpdated = true); + void feedUserLinkDelayed(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); diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index 7e9850c5b..b73ecca3a 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -30,6 +30,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "langloaderplain.h" #include "localstorage.h" #include "autoupdater.h" +#include "core/observer.h" namespace { void mtpStateChanged(int32 dc, int32 state) { @@ -200,6 +201,7 @@ void Application::singleInstanceChecked() { Logs::multipleInstances(); } + Notify::startObservers(); Sandbox::start(); if (!Logs::started() || (!cManyInstance() && !Logs::instanceChecked())) { @@ -336,6 +338,8 @@ void Application::closeApplication() { if (_updateThread) _updateThread->quit(); _updateThread = 0; #endif + + Notify::finishObservers(); } #ifndef TDESKTOP_DISABLE_AUTOUPDATE diff --git a/Telegram/SourceFiles/boxes/addcontactbox.cpp b/Telegram/SourceFiles/boxes/addcontactbox.cpp index 2d37dff66..f1bea515d 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.cpp +++ b/Telegram/SourceFiles/boxes/addcontactbox.cpp @@ -30,6 +30,7 @@ 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) @@ -206,7 +207,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") { @@ -222,8 +223,7 @@ void AddContactBox::onImportDone(const MTPcontacts_ImportedContacts &res) { if (isHidden() || !App::main()) return; const auto &d(res.c_contacts_importedContacts()); - App::feedUsers(d.vusers, false); - App::emitPeerUpdated(); + App::feedUsers(d.vusers); const auto &v(d.vimported.c_vector().v); UserData *user = nullptr; @@ -1157,7 +1157,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")) { diff --git a/Telegram/SourceFiles/core/observer.cpp b/Telegram/SourceFiles/core/observer.cpp index 5a8f2d389..607b28911 100644 --- a/Telegram/SourceFiles/core/observer.cpp +++ b/Telegram/SourceFiles/core/observer.cpp @@ -24,10 +24,45 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Notify { namespace { -UnregisterObserverCallback UnregisterCallbacks[256]; +using StartCallbacksList = QVector; +using FinishCallbacksList = QVector; +NeverFreedPointer StartCallbacks; +NeverFreedPointer FinishCallbacks; +UnregisterObserverCallback UnregisterCallbacks[256]/* = { nullptr }*/; } // namespace +void startObservers() { + if (!StartCallbacks) return; + + for (auto &callback : *StartCallbacks) { + callback(); + } +} + +void finishObservers() { + if (!FinishCallbacks) return; + + for (auto &callback : *FinishCallbacks) { + callback(); + } + StartCallbacks.clear(); + FinishCallbacks.clear(); +} + +ObservedEventRegistrator::ObservedEventRegistrator(ObservedEvent event +, StartObservedEventCallback startCallback +, FinishObservedEventCallback finishCallback +, UnregisterObserverCallback unregisterCallback) { + StartCallbacks.makeIfNull(); + StartCallbacks->push_back(startCallback); + + FinishCallbacks.makeIfNull(); + FinishCallbacks->push_back(finishCallback); + + UnregisterCallbacks[event] = unregisterCallback; +} + // Observer base interface. Observer::~Observer() { for_const (auto connection, _connections) { @@ -47,10 +82,6 @@ void unregisterObserver(ConnectionId connection) { } } -UnregisterObserverCallbackCreator::UnregisterObserverCallbackCreator(ObservedEvent event, UnregisterObserverCallback callback) { - UnregisterCallbacks[event] = callback; -} - namespace internal { void observerRegisteredDefault(Observer *observer, ConnectionId connection) { @@ -58,5 +89,4 @@ void observerRegisteredDefault(Observer *observer, ConnectionId connection) { } } // namespace internal - } // namespace Notify diff --git a/Telegram/SourceFiles/core/observer.h b/Telegram/SourceFiles/core/observer.h index 2f992cb0b..8ed722dbc 100644 --- a/Telegram/SourceFiles/core/observer.h +++ b/Telegram/SourceFiles/core/observer.h @@ -24,13 +24,36 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Notify { -class Observer; +using ObservedEvent = uchar; 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 UnregisterObserverCallback = void(*)(int connectionIndex); +using FinishObservedEventCallback = void(*)(); + +// Objects of this class should be constructed in global scope. +// startCallback will be called from Notify::startObservers(). +// finishCallback will be called from Notify::finishObservers(). +// unregisterCallback will be used to destroy connections. +class ObservedEventRegistrator { +public: + ObservedEventRegistrator(ObservedEvent event + , StartObservedEventCallback startCallback + , FinishObservedEventCallback finishCallback + , UnregisterObserverCallback unregisterCallback); + +}; + // 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 @@ -49,21 +72,11 @@ private: }; -using ObservedEvent = uchar; inline ConnectionId observerConnectionId(ObservedEvent event, int connectionIndex) { t_assert(connectionIndex >= 0 && connectionIndex < 0x01000000); return (static_cast(event) << 24) | (connectionIndex + 1); } -using UnregisterObserverCallback = void(*)(int connectionIndex); - -// Usage: UnregisterObserverCallbackCreator creator(myEvent, myCallback); in global scope. -class UnregisterObserverCallbackCreator { -public: - UnregisterObserverCallbackCreator(ObservedEvent event, UnregisterObserverCallback callback); - -}; - // Handler is one of Function<> instantiations. template struct ObserversList { diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 56ffe95f7..16917c74f 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -1153,9 +1153,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: { diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index 8df89e4c0..a94231f4a 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -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" @@ -3489,7 +3490,7 @@ namespace Local { QString pname = (showPhone && !phone.isEmpty()) ? App::formatPhone(phone) : QString(); if (!wasLoaded) { - user->setName(first, last, pname, username); + user->setNameDelayed(first, last, pname, username); user->access = access; user->flags = MTPDuser::Flags(flags); @@ -3524,7 +3525,7 @@ namespace Local { flags = (flagsData == 1) ? MTPDchat::Flags(MTPDchat::Flag::f_left) : MTPDchat::Flags(0); } if (!wasLoaded) { - chat->updateName(name, QString(), QString()); + chat->setNameDelayed(name); chat->count = count; chat->date = date; chat->version = version; @@ -3547,7 +3548,7 @@ namespace Local { from.stream >> name >> access >> date >> version >> forbidden >> flags >> invitationUrl; if (!wasLoaded) { - channel->updateName(name, QString(), QString()); + channel->setNameDelayed(name, QString()); channel->access = access; channel->date = date; channel->version = version; @@ -3743,7 +3744,8 @@ namespace Local { cRefSavedPeersByTime().insert(t, peer); peers.push_back(peer); } - App::emitPeerUpdated(); + + Notify::peerUpdatedSendDelayed(); if (App::api()) App::api()->requestPeers(peers); } diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 8c7b3becb..3a60bfb44 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -25,6 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "window/section_memento.h" #include "window/section_widget.h" #include "window/top_bar_widget.h" +#include "observer_peer.h" #include "apiwrap.h" #include "dialogswidget.h" #include "historywidget.h" @@ -696,12 +697,12 @@ void MainWidget::deleteHistoryPart(PeerData *peer, const MTPmessages_AffectedHis if (peer && peer->isChannel()) { if (peer->asChannel()->ptsUpdated(d.vpts.v, d.vpts_count.v)) { peer->asChannel()->ptsApplySkippedUpdates(); - App::emitPeerUpdated(); + Notify::peerUpdatedSendDelayed(); } } else { if (ptsUpdated(d.vpts.v, d.vpts_count.v)) { ptsApplySkippedUpdates(); - App::emitPeerUpdated(); + Notify::peerUpdatedSendDelayed(); } } @@ -725,10 +726,10 @@ void MainWidget::deleteMessages(PeerData *peer, const QVector &ids) { } void MainWidget::deletedContact(UserData *user, const MTPcontacts_Link &result) { - const auto &d(result.c_contacts_link()); - App::feedUsers(MTP_vector(1, d.vuser), false); - App::feedUserLink(MTP_int(peerToUser(user->id)), d.vmy_link, d.vforeign_link, false); - App::emitPeerUpdated(); + auto &d(result.c_contacts_link()); + App::feedUsersDelayed(MTP_vector(1, d.vuser)); + App::feedUserLinkDelayed(MTP_int(peerToUser(user->id)), d.vmy_link, d.vforeign_link); + Notify::peerUpdatedSendDelayed(); } void MainWidget::removeDialog(History *history) { @@ -790,7 +791,7 @@ void MainWidget::deleteAllFromUserPart(DeleteAllFromUserParams params, const MTP const auto &d(result.c_messages_affectedHistory()); if (params.channel->ptsUpdated(d.vpts.v, d.vpts_count.v)) { params.channel->ptsApplySkippedUpdates(); - App::emitPeerUpdated(); + Notify::peerUpdatedSendDelayed(); } int32 offset = d.voffset.v; @@ -1381,15 +1382,6 @@ void MainWidget::loadMediaBack(PeerData *peer, MediaOverviewType type, bool many _overviewLoad[type].insert(peer, MTP::send(MTPmessages_Search(MTP_flags(flags), peer->input, MTPstring(), filter, MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(minId), MTP_int(limit)), rpcDone(&MainWidget::overviewLoaded, history))); } -void MainWidget::peerUsernameChanged(PeerData *peer) { - //if (_profile && _profile->peer() == peer) { TODO - // _profile->peerUsernameChanged(); - //} - if (App::settings() && peer == App::self()) { - App::settings()->usernameChanged(); - } -} - void MainWidget::checkLastUpdate(bool afterSleep) { uint64 n = getms(true); if (_lastUpdateTime && n > _lastUpdateTime + (afterSleep ? NoUpdatesAfterSleepTimeout : NoUpdatesTimeout)) { @@ -1463,12 +1455,12 @@ void MainWidget::messagesAffected(PeerData *peer, const MTPmessages_AffectedMess if (peer && peer->isChannel()) { if (peer->asChannel()->ptsUpdated(d.vpts.v, d.vpts_count.v)) { peer->asChannel()->ptsApplySkippedUpdates(); - App::emitPeerUpdated(); + Notify::peerUpdatedSendDelayed(); } } else { if (ptsUpdated(d.vpts.v, d.vpts_count.v)) { ptsApplySkippedUpdates(); - App::emitPeerUpdated(); + Notify::peerUpdatedSendDelayed(); } } if (History *h = App::historyLoaded(peer ? peer->id : 0)) { @@ -2426,7 +2418,7 @@ void MainWidget::windowShown() { void MainWidget::sentUpdatesReceived(uint64 randomId, const MTPUpdates &result) { feedUpdates(result, randomId); - App::emitPeerUpdated(); + Notify::peerUpdatedSendDelayed(); } bool MainWidget::deleteChannelFailed(const RPCError &error) { @@ -2874,8 +2866,8 @@ void MainWidget::gotChannelDifference(ChannelData *channel, const MTPupdates_Cha case mtpc_updates_channelDifference: { const auto &d(diff.c_updates_channelDifference()); - App::feedUsers(d.vusers); - App::feedChats(d.vchats, false); + App::feedUsersDelayed(d.vusers); + App::feedChatsDelayed(d.vchats); _handlingChannelDifference = true; feedMessageIds(d.vother_updates); @@ -2945,7 +2937,7 @@ void MainWidget::gotChannelDifference(ChannelData *channel, const MTPupdates_Cha channel->ptsWaitingForShortPoll(timeout ? (timeout * 1000) : WaitForChannelGetDifference); } - App::emitPeerUpdated(); + Notify::peerUpdatedSendDelayed(); } void MainWidget::gotRangeDifference(ChannelData *channel, const MTPupdates_ChannelDifference &diff) { @@ -2961,8 +2953,8 @@ void MainWidget::gotRangeDifference(ChannelData *channel, const MTPupdates_Chann case mtpc_updates_channelDifferenceTooLong: { const auto &d(diff.c_updates_channelDifferenceTooLong()); - App::feedUsers(d.vusers); - App::feedChats(d.vchats); + App::feedUsersDelayed(d.vusers); + App::feedChatsDelayed(d.vchats); nextRequestPts = d.vpts.v; isFinal = d.is_final(); @@ -2971,8 +2963,8 @@ void MainWidget::gotRangeDifference(ChannelData *channel, const MTPupdates_Chann case mtpc_updates_channelDifference: { const auto &d(diff.c_updates_channelDifference()); - App::feedUsers(d.vusers); - App::feedChats(d.vchats, false); + App::feedUsersDelayed(d.vusers); + App::feedChatsDelayed(d.vchats); _handlingChannelDifference = true; feedMessageIds(d.vother_updates); @@ -2992,7 +2984,7 @@ void MainWidget::gotRangeDifference(ChannelData *channel, const MTPupdates_Chann } } - App::emitPeerUpdated(); + Notify::peerUpdatedSendDelayed(); } bool MainWidget::failChannelDifference(ChannelData *channel, const RPCError &error) { @@ -3014,7 +3006,7 @@ void MainWidget::gotState(const MTPupdates_State &state) { _dialogs->loadDialogs(); updateOnline(); - App::emitPeerUpdated(); + Notify::peerUpdatedSendDelayed(); } void MainWidget::gotDifference(const MTPupdates_Difference &diff) { @@ -3029,8 +3021,6 @@ void MainWidget::gotDifference(const MTPupdates_Difference &diff) { noUpdatesTimer.start(NoUpdatesTimeout); _ptsWaiter.setRequesting(false); - - App::emitPeerUpdated(); } break; case mtpc_updates_differenceSlice: { const auto &d(diff.c_updates_differenceSlice()); @@ -3043,8 +3033,6 @@ void MainWidget::gotDifference(const MTPupdates_Difference &diff) { MTP_LOG(0, ("getDifference { good - after a slice of difference was received }%1").arg(cTestMode() ? " TESTMODE" : "")); getDifference(); - - App::emitPeerUpdated(); } break; case mtpc_updates_difference: { const auto &d(diff.c_updates_difference()); @@ -3053,6 +3041,7 @@ void MainWidget::gotDifference(const MTPupdates_Difference &diff) { gotState(d.vstate); } break; }; + Notify::peerUpdatedSendDelayed(); } bool MainWidget::getDifferenceTimeChanged(ChannelData *channel, int32 ms, ChannelGetDifferenceTime &channelCurTime, uint64 &curTime) { @@ -3142,8 +3131,8 @@ void MainWidget::ptsApplySkippedUpdates() { void MainWidget::feedDifference(const MTPVector &users, const MTPVector &chats, const MTPVector &msgs, const MTPVector &other) { App::wnd()->checkAutoLock(); - App::feedUsers(users, false); - App::feedChats(chats, false); + App::feedUsersDelayed(users); + App::feedChatsDelayed(chats); feedMessageIds(other); App::feedMsgs(msgs, NewMessageUnread); feedUpdateVector(other, true); @@ -3875,7 +3864,7 @@ void MainWidget::updateReceived(const mtpPrime *from, const mtpPrime *end) { if (!_ptsWaiter.requesting()) { feedUpdates(updates); } - App::emitPeerUpdated(); + Notify::peerUpdatedSendDelayed(); } catch (mtpErrorUnexpected &) { // just some other type } } @@ -3995,8 +3984,8 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) { } } - App::feedUsers(d.vusers, false); - App::feedChats(d.vchats, false); + App::feedUsersDelayed(d.vusers); + App::feedChatsDelayed(d.vchats); feedUpdateVector(d.vupdates); updSetState(0, d.vdate.v, updQts, d.vseq.v); @@ -4012,8 +4001,8 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) { } } - App::feedUsers(d.vusers, false); - App::feedChats(d.vchats, false); + App::feedUsersDelayed(d.vusers); + App::feedChatsDelayed(d.vchats); feedUpdateVector(d.vupdates); updSetState(0, d.vdate.v, updQts, d.vseq.v); @@ -4338,13 +4327,12 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { } break; case mtpc_updateUserName: { - const auto &d(update.c_updateUserName()); - UserData *user = App::userLoaded(d.vuser_id.v); - if (user) { + auto &d(update.c_updateUserName()); + if (auto user = App::userLoaded(d.vuser_id.v)) { if (user->contact <= 0) { - user->setName(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), user->nameOrPhone, textOneLine(qs(d.vusername))); + user->setNameDelayed(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), user->nameOrPhone, textOneLine(qs(d.vusername))); } else { - user->setName(textOneLine(user->firstName), textOneLine(user->lastName), user->nameOrPhone, textOneLine(qs(d.vusername))); + user->setNameDelayed(textOneLine(user->firstName), textOneLine(user->lastName), user->nameOrPhone, textOneLine(qs(d.vusername))); } App::markPeerUpdated(user); } @@ -4385,7 +4373,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { case mtpc_updateContactLink: { const auto &d(update.c_updateContactLink()); - App::feedUserLink(d.vuser_id, d.vmy_link, d.vforeign_link, false); + App::feedUserLinkDelayed(d.vuser_id, d.vmy_link, d.vforeign_link); } break; case mtpc_updateNotifySettings: { @@ -4399,11 +4387,10 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { } break; case mtpc_updateUserPhone: { - const auto &d(update.c_updateUserPhone()); - UserData *user = App::userLoaded(d.vuser_id.v); - if (user) { + auto &d(update.c_updateUserPhone()); + if (auto user = App::userLoaded(d.vuser_id.v)) { user->setPhone(qs(d.vphone)); - user->setName(user->firstName, user->lastName, (user->contact || isServiceUser(user->id) || user->isSelf() || user->phone.isEmpty()) ? QString() : App::formatPhone(user->phone), user->username); + user->setNameDelayed(user->firstName, user->lastName, (user->contact || isServiceUser(user->id) || user->isSelf() || user->phone.isEmpty()) ? QString() : App::formatPhone(user->phone), user->username); App::markPeerUpdated(user); } } break; diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index b395d2949..081bcaa7e 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -322,7 +322,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(); diff --git a/Telegram/SourceFiles/observer_peer.cpp b/Telegram/SourceFiles/observer_peer.cpp index 9094ce5c2..c77ec90bf 100644 --- a/Telegram/SourceFiles/observer_peer.cpp +++ b/Telegram/SourceFiles/observer_peer.cpp @@ -23,81 +23,112 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #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 internal { namespace { constexpr ObservedEvent PeerUpdateEvent = 0x01; -ObserversList PeerUpdateObservers; +using PeerObserversList = ObserversList; +NeverFreedPointer PeerUpdateObservers; -void UnregisterCallback(int connectionIndex) { - unregisterObserver(PeerUpdateObservers, connectionIndex); +using SmallUpdatesList = QVector; +NeverFreedPointer SmallUpdates; +using AllUpdatesList = QMap; +NeverFreedPointer AllUpdates; + +void StartCallback() { + PeerUpdateObservers.makeIfNull(); + SmallUpdates.makeIfNull(); + AllUpdates.makeIfNull(); } -UnregisterObserverCallbackCreator creator(PeerUpdateEvent, UnregisterCallback); +void FinishCallback() { + PeerUpdateObservers.clear(); + SmallUpdates.clear(); + AllUpdates.clear(); +} +void UnregisterCallback(int connectionIndex) { + t_assert(!PeerUpdateObservers.isNull()); + unregisterObserver(*PeerUpdateObservers, connectionIndex); +} +ObservedEventRegistrator creator(PeerUpdateEvent, StartCallback, FinishCallback, UnregisterCallback); -QVector SmallPeerUpdates; -QMap AllPeerUpdates; +bool Started() { + return !PeerUpdateObservers.isNull(); +} } // namespace ConnectionId plainRegisterPeerObserver(PeerUpdateFlags events, PeerUpdateHandler &&handler) { - auto connectionId = registerObserver(PeerUpdateObservers, events, std_::forward(handler)); + t_assert(Started()); + auto connectionId = registerObserver(*PeerUpdateObservers, events, std_::forward(handler)); t_assert(connectionId >= 0 && connectionId < 0x01000000); return (static_cast(PeerUpdateEvent) << 24) | static_cast(connectionId + 1); } void mergePeerUpdate(PeerUpdate &mergeTo, const PeerUpdate &mergeFrom) { + if (!(mergeTo.flags & PeerUpdateFlag::NameChanged)) { + if (mergeFrom.flags & PeerUpdateFlag::NameChanged) { + mergeTo.oldNames = mergeFrom.oldNames; + mergeTo.oldNameFirstChars = mergeFrom.oldNameFirstChars; + } + } mergeTo.flags |= mergeFrom.flags; - - // merge fields used in mergeFrom.flags } } // namespace internal -void peerUpdated(const PeerUpdate &update) { - notifyObservers(internal::PeerUpdateObservers, update.flags, update); -} - void peerUpdatedDelayed(const PeerUpdate &update) { - int alreadySavedCount = internal::SmallPeerUpdates.size(); - for (int i = 0; i < alreadySavedCount; ++i) { - if (internal::SmallPeerUpdates.at(i).peer == update.peer) { - internal::mergePeerUpdate(internal::SmallPeerUpdates[i], update); + t_assert(internal::Started()); + + int existingUpdatesCount = internal::SmallUpdates->size(); + for (int i = 0; i < existingUpdatesCount; ++i) { + auto &existingUpdate = (*internal::SmallUpdates)[i]; + if (existingUpdate.peer == update.peer) { + internal::mergePeerUpdate(existingUpdate, update); return; } } - if (internal::AllPeerUpdates.isEmpty()) { - if (alreadySavedCount < 5) { - internal::SmallPeerUpdates.push_back(update); + if (internal::AllUpdates->isEmpty()) { + if (existingUpdatesCount < 5) { + internal::SmallUpdates->push_back(update); } else { - internal::AllPeerUpdates.insert(update.peer, update); + internal::AllUpdates->insert(update.peer, update); } } else { - auto it = internal::AllPeerUpdates.find(update.peer); - if (it != internal::AllPeerUpdates.cend()) { + auto it = internal::AllUpdates->find(update.peer); + if (it != internal::AllUpdates->cend()) { internal::mergePeerUpdate(it.value(), update); return; } - internal::AllPeerUpdates.insert(update.peer, update); + internal::AllUpdates->insert(update.peer, update); } } void peerUpdatedSendDelayed() { - if (internal::SmallPeerUpdates.isEmpty()) return; + App::emitPeerUpdated(); - decltype(internal::SmallPeerUpdates) smallList; - decltype(internal::AllPeerUpdates) allList; - std::swap(smallList, internal::SmallPeerUpdates); - std::swap(allList, internal::AllPeerUpdates); + t_assert(internal::Started()); + + if (internal::SmallUpdates->isEmpty()) return; + + internal::SmallUpdatesList smallList; + internal::AllUpdatesList allList; + std::swap(smallList, *internal::SmallUpdates); + std::swap(allList, *internal::AllUpdates); for_const (auto &update, smallList) { - peerUpdated(update); + notifyObservers(*internal::PeerUpdateObservers, update.flags, update); } for_const (auto &update, allList) { - peerUpdated(update); + notifyObservers(*internal::PeerUpdateObservers, update.flags, update); } - if (internal::SmallPeerUpdates.isEmpty()) { - std::swap(smallList, internal::SmallPeerUpdates); - internal::SmallPeerUpdates.resize(0); + if (internal::SmallUpdates->isEmpty()) { + std::swap(smallList, *internal::SmallUpdates); + internal::SmallUpdates->resize(0); } } diff --git a/Telegram/SourceFiles/observer_peer.h b/Telegram/SourceFiles/observer_peer.h index 76a7514b9..a490f0b11 100644 --- a/Telegram/SourceFiles/observer_peer.h +++ b/Telegram/SourceFiles/observer_peer.h @@ -26,27 +26,35 @@ 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). enum class PeerUpdateFlag { - //PeerNameChanged = 0x0001, + NameChanged = 0x00000001U, + UsernameChanged = 0x00000002U, - UserCanShareContact = 0x1001, + UserCanShareContact = 0x00010000U, - ChatCanEdit = 0x2001, + ChatCanEdit = 0x00010000U, - MegagroupCanEditPhoto = 0x4001, - MegagroupCanAddMembers = 0x4002, - - ChannelAmIn = 0x8001, + ChannelAmIn = 0x00010000U, + MegagroupCanEditPhoto = 0x00020000U, + MegagroupCanAddMembers = 0x00040000U, }; Q_DECLARE_FLAGS(PeerUpdateFlags, PeerUpdateFlag); Q_DECLARE_OPERATORS_FOR_FLAGS(PeerUpdateFlags); + struct PeerUpdate { - PeerData *peer = nullptr; + PeerUpdate(PeerData *updated = nullptr) : peer(updated) { + } + PeerData *peer; PeerUpdateFlags flags = 0; + + // NameChanged data + PeerData::Names oldNames; + PeerData::NameFirstChars oldNameFirstChars; }; -void peerUpdated(const PeerUpdate &update); void peerUpdatedDelayed(const PeerUpdate &update); void peerUpdatedSendDelayed(); diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp index 24de8384a..46620a7e5 100644 --- a/Telegram/SourceFiles/profile/profile_cover.cpp +++ b/Telegram/SourceFiles/profile/profile_cover.cpp @@ -24,6 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_profile.h" #include "ui/buttons/round_button.h" #include "observer_peer.h" +#include "boxes/contactsbox.h" #include "lang.h" #include "apiwrap.h" #include "mainwidget.h" @@ -98,17 +99,16 @@ CoverWidget::CoverWidget(QWidget *parent, PeerData *peer) : TWidget(parent) , _photoButton(this, peer) { setAttribute(Qt::WA_OpaquePaintEvent); - using Flag = Notify::PeerUpdateFlag; - auto observeEvents = ButtonsUpdateFlags; + auto observeEvents = ButtonsUpdateFlags | Notify::PeerUpdateFlag::NameChanged; Notify::registerPeerObserver(observeEvents, this, &CoverWidget::notifyPeerUpdated); _photoButton->photoUpdated(); connect(_photoButton, SIGNAL(clicked()), this, SLOT(onPhotoShow())); - _nameText.setText(st::profileNameFont, App::peerName(_peer)); - updateStatusText(); + refreshNameText(); + refreshStatusText(); - updateButtons(); + refreshButtons(); } void CoverWidget::onPhotoShow() { @@ -121,31 +121,6 @@ void CoverWidget::onPhotoShow() { } } -void CoverWidget::onSendMessage() { - -} - -void CoverWidget::onShareContact() { - -} - -void CoverWidget::onSetPhoto() { - -} - -void CoverWidget::onAddMember() { - -} - -void CoverWidget::onJoin() { - -} - -void CoverWidget::onViewChannel() { - -} - - void CoverWidget::resizeToWidth(int newWidth) { int newHeight = 0; @@ -202,7 +177,23 @@ void CoverWidget::paintDivider(Painter &p) { st::profileDividerFill.fill(p, toFill); } -void CoverWidget::updateStatusText() { +void CoverWidget::notifyPeerUpdated(const Notify::PeerUpdate &update) { + if (update.peer == _peer) { + if ((update.flags & ButtonsUpdateFlags) != 0) { + refreshButtons(); + } + if (update.flags & Notify::PeerUpdateFlag::NameChanged) { + refreshNameText(); + } + } +} + +void CoverWidget::refreshNameText() { + _nameText.setText(st::profileNameFont, App::peerName(_peer)); + update(); +} + +void CoverWidget::refreshStatusText() { int currentTime = unixtime(); if (_peerUser) { _statusText = App::onlineText(_peerUser, currentTime, true); @@ -235,6 +226,7 @@ void CoverWidget::updateStatusText() { } else { _statusText = lang(lng_chat_status_unaccessible); } + update(); } bool CoverWidget::isUsingMegagroupOnlineCount() const { @@ -254,13 +246,7 @@ bool CoverWidget::isUsingMegagroupOnlineCount() const { return true; } -void CoverWidget::notifyPeerUpdated(const Notify::PeerUpdate &update) { - if (update.flags & ButtonsUpdateFlags) { - updateButtons(); - } -} - -void CoverWidget::updateButtons() { +void CoverWidget::refreshButtons() { if (_peerUser) { setUserButtons(); } else if (_peerChat) { @@ -336,4 +322,36 @@ void CoverWidget::setSecondaryButton(const QString &text, const char *slot) { } } +void CoverWidget::onSendMessage() { + Ui::showPeerHistory(_peer, ShowAtUnreadMsgId); +} + +void CoverWidget::onShareContact() { + App::main()->shareContactLayer(_peerUser); +} + +void CoverWidget::onSetPhoto() { + +} + +void CoverWidget::onAddMember() { + if (_peerChat) { + Ui::showLayer(new ContactsBox(_peerChat, MembersFilterRecent)); + } else if (_peerChannel && _peerChannel->mgInfo) { + MembersAlreadyIn already; + for (MegagroupInfo::LastParticipants::const_iterator i = _peerChannel->mgInfo->lastParticipants.cbegin(), e = _peerChannel->mgInfo->lastParticipants.cend(); i != e; ++i) { + already.insert(*i, true); + } + Ui::showLayer(new ContactsBox(_peerChannel, MembersFilterRecent, already)); + } +} + +void CoverWidget::onJoin() { + +} + +void CoverWidget::onViewChannel() { + Ui::showPeerHistory(_peer, ShowAtUnreadMsgId); +} + } // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_cover.h b/Telegram/SourceFiles/profile/profile_cover.h index ae84f15e1..96b7d668f 100644 --- a/Telegram/SourceFiles/profile/profile_cover.h +++ b/Telegram/SourceFiles/profile/profile_cover.h @@ -58,13 +58,14 @@ protected: void paintEvent(QPaintEvent *e) override; private: - void updateStatusText(); - bool isUsingMegagroupOnlineCount() const; - // Observed notifications. void notifyPeerUpdated(const Notify::PeerUpdate &update); - void updateButtons(); + void refreshNameText(); + void refreshStatusText(); + bool isUsingMegagroupOnlineCount() const; + + void refreshButtons(); void setUserButtons(); void setChatButtons(); void setMegagroupButtons(); diff --git a/Telegram/SourceFiles/settingswidget.cpp b/Telegram/SourceFiles/settingswidget.cpp index c7aaeec12..c08a12ab6 100644 --- a/Telegram/SourceFiles/settingswidget.cpp +++ b/Telegram/SourceFiles/settingswidget.cpp @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "settingswidget.h" +#include "observer_peer.h" #include "lang.h" #include "boxes/aboutbox.h" #include "mainwidget.h" @@ -205,6 +206,8 @@ SettingsInner::SettingsInner(SettingsWidget *parent) : TWidget(parent) , _telegramFAQ(this, lang(lng_settings_faq)) , _logOut(this, lang(lng_settings_logout), st::btnRedLink) , _supportGetRequest(0) { + Notify::registerPeerObserver(Notify::PeerUpdateFlag::UsernameChanged, this, &SettingsInner::notifyPeerUpdated); + if (self()) { self()->loadUserpic(); @@ -269,13 +272,13 @@ SettingsInner::SettingsInner(SettingsWidget *parent) : TWidget(parent) _newVersionText = lang(lng_settings_update_ready) + ' '; _newVersionWidth = st::linkFont->width(_newVersionText); - #ifndef TDESKTOP_DISABLE_AUTOUPDATE +#ifndef TDESKTOP_DISABLE_AUTOUPDATE Sandbox::connect(SIGNAL(updateChecking()), this, SLOT(onUpdateChecking())); Sandbox::connect(SIGNAL(updateLatest()), this, SLOT(onUpdateLatest())); Sandbox::connect(SIGNAL(updateProgress(qint64,qint64)), this, SLOT(onUpdateDownloading(qint64,qint64))); Sandbox::connect(SIGNAL(updateFailed()), this, SLOT(onUpdateFailed())); Sandbox::connect(SIGNAL(updateReady()), this, SLOT(onUpdateReady())); - #endif +#endif // chat options connect(&_replaceEmojis, SIGNAL(changed()), this, SLOT(onReplaceEmojis())); @@ -349,6 +352,14 @@ SettingsInner::SettingsInner(SettingsWidget *parent) : TWidget(parent) setMouseTracking(true); } +void SettingsInner::notifyPeerUpdated(const Notify::PeerUpdate &update) { + if (update.peer == App::self()) { + if (update.flags & Notify::PeerUpdateFlag::UsernameChanged) { + usernameChanged(); + } + } +} + void SettingsInner::peerUpdated(PeerData *data) { if (self() && data == self()) { if (self()->photoId && self()->photoId != UnknownPeerPhotoId) { @@ -1977,10 +1988,6 @@ void SettingsWidget::rpcClear() { _inner.rpcClear(); } -void SettingsWidget::usernameChanged() { - _inner.usernameChanged(); -} - void SettingsWidget::setInnerFocus() { _inner.setFocus(); } diff --git a/Telegram/SourceFiles/settingswidget.h b/Telegram/SourceFiles/settingswidget.h index 4162c9844..2c1c13814 100644 --- a/Telegram/SourceFiles/settingswidget.h +++ b/Telegram/SourceFiles/settingswidget.h @@ -23,8 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "ui/flatbutton.h" #include "ui/flatcheckbox.h" #include "sysbuttons.h" - -#include +#include "core/observer.h" class MainWindow; class Settings; @@ -57,7 +56,11 @@ private: }; -class SettingsInner : public TWidget, public RPCSender { +namespace Notify { +struct PeerUpdate; +} // namespace Notify + +class SettingsInner : public TWidget, public RPCSender, public Notify::Observer { Q_OBJECT public: @@ -188,6 +191,7 @@ public slots: void onTelegramFAQ(); private: + void notifyPeerUpdated(const Notify::PeerUpdate &update); void saveError(const QString &str = QString()); @@ -332,7 +336,6 @@ public: void updateDisplayNotify(); void rpcClear(); - void usernameChanged(); void setInnerFocus(); void needBackgroundUpdate(bool tile); diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index d1355380e..25170670a 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "lang.h" #include "inline_bots/inline_bot_layout_item.h" +#include "observer_peer.h" #include "history.h" #include "mainwidget.h" #include "application.h" @@ -99,18 +100,14 @@ NotifySettings globalNotifyAll, globalNotifyUsers, globalNotifyChats; NotifySettingsPtr globalNotifyAllPtr = UnknownNotifySettings, globalNotifyUsersPtr = UnknownNotifySettings, globalNotifyChatsPtr = UnknownNotifySettings; PeerData::PeerData(const PeerId &id) : id(id) -, loadedStatus(NotLoaded) , colorIndex(peerColorIndex(id)) , color(peerColor(colorIndex)) -, photoId(UnknownPeerPhotoId) -, nameVersion(0) -, notify(UnknownNotifySettings) , _userpic(isUser() ? userDefPhoto(colorIndex) : ((isChat() || isMegagroup()) ? chatDefPhoto(colorIndex) : channelDefPhoto(colorIndex))) { - if (!peerIsUser(id) && !peerIsChannel(id)) updateName(QString(), QString(), QString()); + nameText.setText(st::msgNameFont, QString(), _textNameOptions); } -void PeerData::updateName(const QString &newName, const QString &newNameOrPhone, const QString &newUsername) { - if (name == newName && nameVersion > 0) { +void PeerData::updateNameDelayed(const QString &newName, const QString &newNameOrPhone, const QString &newUsername) { + if (name == newName) { if (isUser()) { if (asUser()->nameOrPhone == newNameOrPhone && asUser()->username == newUsername) { return; @@ -127,8 +124,17 @@ void PeerData::updateName(const QString &newName, const QString &newNameOrPhone, ++nameVersion; name = newName; nameText.setText(st::msgNameFont, name, _textNameOptions); + + Notify::PeerUpdate update(this); + update.flags |= Notify::PeerUpdateFlag::NameChanged; + update.oldNames = names; + update.oldNameFirstChars = chars; + if (isUser()) { - asUser()->username = newUsername; + if (asUser()->username != newUsername) { + asUser()->username = newUsername; + update.flags |= Notify::PeerUpdateFlag::UsernameChanged; + } asUser()->setNameOrPhone(newNameOrPhone); } else if (isChannel()) { if (asChannel()->username != newUsername) { @@ -138,19 +144,14 @@ void PeerData::updateName(const QString &newName, const QString &newNameOrPhone, } else { asChannel()->flags |= MTPDchannel::Flag::f_username; } - if (App::main()) { - App::main()->peerUsernameChanged(this); - } + update.flags |= Notify::PeerUpdateFlag::UsernameChanged; } } - - Names oldNames = names; - NameFirstChars oldChars = chars; fillNames(); - if (App::main()) { - emit App::main()->peerNameChanged(this, oldNames, oldChars); + emit App::main()->peerNameChanged(this, update.oldNames, update.oldNameFirstChars); } + Notify::peerUpdatedDelayed(update); } void PeerData::setUserpic(ImagePtr userpic) { @@ -253,25 +254,27 @@ void PeerData::fillNames() { } } -void UserData::setName(const QString &first, const QString &last, const QString &phoneName, const QString &usern) { - bool updName = !first.isEmpty() || !last.isEmpty(), updUsername = (username != usern); +void UserData::setName(const QString &newFirstName, const QString &newLastName, const QString &newPhoneName, const QString &newUsername) { + setNameDelayed(newFirstName, newLastName, newPhoneName, newUsername); + Notify::peerUpdatedSendDelayed(); +} - if (updName && first.trimmed().isEmpty()) { - firstName = last; +void UserData::setNameDelayed(const QString &newFirstName, const QString &newLastName, const QString &newPhoneName, const QString &newUsername) { + bool changeName = !newFirstName.isEmpty() || !newLastName.isEmpty(); + + QString newFullName; + if (changeName && newFirstName.trimmed().isEmpty()) { + firstName = newLastName; lastName = QString(); - updateName(firstName, phoneName, usern); + newFullName = firstName; } else { - if (updName) { - firstName = first; - lastName = last; - } - updateName(lastName.isEmpty() ? firstName : lng_full_name(lt_first_name, firstName, lt_last_name, lastName), phoneName, usern); - } - if (updUsername) { - if (App::main()) { - App::main()->peerUsernameChanged(this); + if (changeName) { + firstName = newFirstName; + lastName = newLastName; } + newFullName = lastName.isEmpty() ? firstName : lng_full_name(lt_first_name, firstName, lt_last_name, lastName); } + updateNameDelayed(newFullName, newPhoneName, newUsername); } void UserData::setPhone(const QString &newPhone) { @@ -401,6 +404,15 @@ void ChatData::setPhoto(const MTPChatPhoto &p, const PhotoId &phId) { // see Loc } } +void ChatData::setName(const QString &newName) { + setNameDelayed(newName); + Notify::peerUpdatedSendDelayed(); +} + +void ChatData::setNameDelayed(const QString &newName) { + updateNameDelayed(newName.isEmpty() ? name : newName, QString(), QString()); +} + void ChannelData::setPhoto(const MTPChatPhoto &p, const PhotoId &phId) { // see Local::readPeer as well PhotoId newPhotoId = photoId; ImagePtr newPhoto = _userpic; @@ -430,10 +442,13 @@ void ChannelData::setPhoto(const MTPChatPhoto &p, const PhotoId &phId) { // see } } -void ChannelData::setName(const QString &newName, const QString &usern) { - bool updName = !newName.isEmpty(), updUsername = (username != usern); +void ChannelData::setName(const QString &newName, const QString &newUsername) { + setNameDelayed(newName, newUsername); + Notify::peerUpdatedSendDelayed(); +} - updateName(newName.isEmpty() ? name : newName, QString(), usern); +void ChannelData::setNameDelayed(const QString &newName, const QString &newUsername) { + updateNameDelayed(newName.isEmpty() ? name : newName, QString(), newUsername); } void ChannelData::updateFull(bool force) { diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index da3d2d293..2b52bb830 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -274,10 +274,6 @@ public: ChatData *migrateFrom() const; ChannelData *migrateTo() const; - void updateName(const QString &newName, const QString &newNameOrPhone, const QString &newUsername); - - void fillNames(); - const Text &dialogName() const; const QString &shortName() const; const QString &userName() const; @@ -299,7 +295,7 @@ public: MinimalLoaded = 0x01, FullLoaded = 0x02, }; - LoadedStatus loadedStatus; + LoadedStatus loadedStatus = NotLoaded; MTPinputPeer input; int colorIndex; @@ -317,12 +313,12 @@ public: void saveUserpic(const QString &path) const; QPixmap genUserpic(int size) const; - PhotoId photoId; + PhotoId photoId = UnknownPeerPhotoId; StorageImageLocation photoLoc; - int nameVersion; + int nameVersion = 1; - NotifySettingsPtr notify; + NotifySettingsPtr notify = UnknownNotifySettings; // if this string is not empty we must not allow to open the // conversation and we must show this string instead @@ -338,10 +334,15 @@ public: } protected: + // Requires Notify::peerUpdatedSendDelayed() call after. + void updateNameDelayed(const QString &newName, const QString &newNameOrPhone, const QString &newUsername); + ImagePtr _userpic; ImagePtr currentUserpic() const; private: + void fillNames(); + ClickHandlerPtr _openLink; }; @@ -395,10 +396,16 @@ class UserData : public PeerData { public: UserData(const PeerId &id) : PeerData(id) { - setName(QString(), QString(), QString(), QString()); } void setPhoto(const MTPUserProfilePhoto &photo); - void setName(const QString &first, const QString &last, const QString &phoneName, const QString &username); + + void setName(const QString &newFirstName, const QString &newLastName + , const QString &newPhoneName, const QString &newUsername); + + // Requires Notify::peerUpdatedSendDelayed() call after. + void setNameDelayed(const QString &newFirstName, const QString &newLastName + , const QString &newPhoneName, const QString &newUsername); + void setPhone(const QString &newPhone); void setBotInfoVersion(int version); void setBotInfo(const MTPBotInfo &info); @@ -458,18 +465,15 @@ private: class ChatData : public PeerData { public: - ChatData(const PeerId &id) : PeerData(id) - , inputChat(MTP_int(bareId())) - , migrateToPtr(0) - , count(0) - , date(0) - , version(0) - , creator(0) - , flags(0) - , isForbidden(false) - , botStatus(0) { + ChatData(const PeerId &id) : PeerData(id), inputChat(MTP_int(bareId())) { } void setPhoto(const MTPChatPhoto &photo, const PhotoId &phId = UnknownPeerPhotoId); + + void setName(const QString &newName); + + // Requires Notify::peerUpdatedSendDelayed() call after. + void setNameDelayed(const QString &newName); + void invalidateParticipants() { participants = ChatData::Participants(); admins = ChatData::Admins(); @@ -483,15 +487,15 @@ public: MTPint inputChat; - ChannelData *migrateToPtr; + ChannelData *migrateToPtr = nullptr; - int count; - TimeId date; - int version; - UserId creator; + int count = 0; + TimeId date = 0; + int version = 0; + UserId creator = 0; - MTPDchat::Flags flags; - bool isForbidden; + MTPDchat::Flags flags = 0; + bool isForbidden = false; bool amIn() const { return !isForbidden && !haveLeft() && !wasKicked(); } @@ -532,7 +536,7 @@ public: LastAuthors lastAuthors; typedef OrderedSet MarkupSenders; MarkupSenders markupSenders; - int32 botStatus; // -1 - no bots, 0 - unknown, 1 - one bot, that sees all history, 2 - other + int botStatus = 0; // -1 - no bots, 0 - unknown, 1 - one bot, that sees all history, 2 - other // ImagePtr photoFull; QString invitationUrl; }; @@ -634,14 +638,15 @@ struct MegagroupInfo { class ChannelData : public PeerData { public: - ChannelData(const PeerId &id) : PeerData(id) - , inputChannel(MTP_inputChannel(MTP_int(bareId()), MTP_long(0))) - , mgInfo(nullptr) { - setName(QString(), QString()); + ChannelData(const PeerId &id) : PeerData(id), inputChannel(MTP_inputChannel(MTP_int(bareId()), MTP_long(0))) { } void setPhoto(const MTPChatPhoto &photo, const PhotoId &phId = UnknownPeerPhotoId); + void setName(const QString &name, const QString &username); + // Requires Notify::peerUpdatedSendDelayed() call after. + void setNameDelayed(const QString &name, const QString &username); + void updateFull(bool force = false); void fullUpdated(); From 46ad43bb1ea78c5d9971bdfe8aee5495723e2dea Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 25 May 2016 20:59:21 +0300 Subject: [PATCH 11/30] New profile cover actions by buttons done. Two new types of Observers: image loaded and async file dialog. --- Telegram/SourceFiles/apiwrap.cpp | 43 +++++ Telegram/SourceFiles/apiwrap.h | 12 +- Telegram/SourceFiles/app.cpp | 62 +++++-- Telegram/SourceFiles/application.cpp | 11 ++ Telegram/SourceFiles/application.h | 1 + Telegram/SourceFiles/boxes/contactsbox.cpp | 8 +- Telegram/SourceFiles/boxes/contactsbox.h | 4 +- Telegram/SourceFiles/core/basic_types.h | 7 + Telegram/SourceFiles/core/observer.cpp | 9 +- Telegram/SourceFiles/core/observer.h | 38 +++- Telegram/SourceFiles/facades.cpp | 2 + Telegram/SourceFiles/facades.h | 1 + Telegram/SourceFiles/mtproto/facade.h | 1 - .../SourceFiles/mtproto/file_download.cpp | 47 +++++ Telegram/SourceFiles/mtproto/file_download.h | 20 +++ Telegram/SourceFiles/observer_peer.cpp | 15 +- Telegram/SourceFiles/observer_peer.h | 21 +-- .../SourceFiles/profile/profile_cover.cpp | 170 +++++++++++------- Telegram/SourceFiles/profile/profile_cover.h | 19 +- .../SourceFiles/profile/profile_fixed_bar.cpp | 7 +- .../profile/profile_inner_widget.cpp | 8 +- .../profile/profile_inner_widget.h | 5 +- .../SourceFiles/profile/profile_widget.cpp | 1 + Telegram/SourceFiles/profilewidget.cpp | 4 +- Telegram/SourceFiles/structs.cpp | 12 ++ Telegram/SourceFiles/structs.h | 3 + Telegram/SourceFiles/ui/filedialog.cpp | 147 +++++++++++++++ Telegram/SourceFiles/ui/filedialog.h | 37 ++++ Telegram/SourceFiles/ui/images.h | 2 +- 29 files changed, 578 insertions(+), 139 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index e555ccf91..751757c63 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -97,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: { @@ -679,6 +683,45 @@ 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::PeerUpdate update(channel); + update.flags |= Notify::PeerUpdateFlag::ChannelAmIn; + Notify::peerUpdatedDelayed(update); + Notify::peerUpdatedSendDelayed(); +} + +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::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) { _stickerSetRequests.remove(setId); diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 606ef7f14..4e223d2a5 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -28,7 +28,7 @@ public: ApiWrap(QObject *parent); void init(); - typedef SharedCallback RequestMessageDataCallback; + using RequestMessageDataCallback = SharedCallback; void requestMessageData(ChannelData *channel, MsgId msgId, std_::unique_ptr callback); void requestFullPeer(PeerData *peer); @@ -50,6 +50,9 @@ public: void scheduleStickerSetRequest(uint64 setId, uint64 access); void requestStickerSets(); + void joinChannel(ChannelData *channel); + void leaveChannel(ChannelData *channel); + ~ApiWrap(); signals: @@ -65,6 +68,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 +125,9 @@ private: void gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result); bool gotStickerSetFail(uint64 setId, const RPCError &error); + QMap _channelAmInRequests; + void channelAmInUpdated(ChannelData *channel); + void channelAmInDone(ChannelData *channel, const MTPUpdates &updates); + bool channelAmInFail(ChannelData *channel, const RPCError &error); + }; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index bdb4ad3dd..093b8fba7 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -369,7 +369,7 @@ namespace { UserData *result = nullptr; for_const (auto &user, users.c_vector().v) { UserData *data = nullptr; - bool wasContact = false, canShareContact = false, minimal = false; + bool wasContact = false, minimal = false; const MTPUserStatus *status = 0, emptyStatus = MTP_userStatusEmpty(); Notify::PeerUpdate update; @@ -548,12 +548,13 @@ namespace { switch (chat.type()) { case mtpc_chat: { - const auto &d(chat.c_chat()); + auto &d(chat.c_chat()); data = App::chat(peerFromChat(d.vid.v)); - data->input = MTP_inputPeerChat(d.vid); + auto cdata = data->asChat(); + auto canEdit = cdata->canEdit(); - ChatData *cdata = data->asChat(); + data->input = MTP_inputPeerChat(d.vid); cdata->setNameDelayed(qs(d.vtitle)); cdata->setPhoto(d.vphoto); cdata->date = d.vdate.v; @@ -604,14 +605,18 @@ namespace { 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)); - data->input = MTP_inputPeerChat(d.vid); + auto cdata = data->asChat(); + auto canEdit = cdata->canEdit(); - ChatData *cdata = data->asChat(); + data->input = MTP_inputPeerChat(d.vid); cdata->setNameDelayed(qs(d.vtitle)); cdata->setPhoto(MTP_chatPhotoEmpty()); cdata->date = 0; @@ -619,27 +624,32 @@ namespace { 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 canAddMembers = cdata->canAddParticipants(); if (minimal) { - int32 mask = MTPDchannel::Flag::f_broadcast | MTPDchannel::Flag::f_verified | MTPDchannel::Flag::f_megagroup | MTPDchannel::Flag::f_democracy; + MTPDchannel::Flags mask = MTPDchannel::Flag::f_broadcast | MTPDchannel::Flag::f_verified | MTPDchannel::Flag::f_megagroup | MTPDchannel::Flag::f_democracy; cdata->flags = (cdata->flags & ~mask) | (d.vflags.v & mask); } else { cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash); @@ -662,19 +672,27 @@ namespace { cdata->flagsUpdated(); cdata->setPhoto(d.vphoto); - if (wasInChannel != cdata->amIn() && !cdata->isMegagroup()) { + if (wasInChannel != cdata->amIn()) { update.flags |= UpdateFlag::ChannelAmIn; } + if (canEditPhoto != cdata->canEditPhoto()) { + update.flags |= UpdateFlag::ChannelCanEditPhoto; + } + if (canAddMembers != cdata->canAddParticipants()) { + update.flags |= UpdateFlag::ChannelCanAddMembers; + } } 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 canAddMembers = cdata->canAddParticipants(); cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash); @@ -686,9 +704,15 @@ namespace { cdata->count = 0; cdata->isForbidden = true; - if (wasInChannel != cdata->amIn() && !cdata->isMegagroup()) { + if (wasInChannel != cdata->amIn()) { update.flags |= UpdateFlag::ChannelAmIn; } + if (canEditPhoto != cdata->canEditPhoto()) { + update.flags |= UpdateFlag::ChannelCanEditPhoto; + } + if (canAddMembers != cdata->canAddParticipants()) { + update.flags |= UpdateFlag::ChannelCanAddMembers; + } } break; } if (!data) continue; diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index b73ecca3a..4ff6daddf 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -27,10 +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) { @@ -817,6 +819,7 @@ void AppClass::doMtpUnpause() { void AppClass::selfPhotoCleared(const MTPUserProfilePhoto &result) { if (!App::self()) return; App::self()->setPhoto(result); + Notify::peerUpdatedSendDelayed(); emit peerPhotoDone(App::self()->id); } @@ -906,6 +909,14 @@ void AppClass::call_handleUnreadCounterUpdate() { } } +void AppClass::call_handleFileDialogQueue() { + while (true) { + if (!FileDialog::processQuery()) { + return; + } + } +} + void AppClass::killDownloadSessions() { uint64 ms = getms(), left = MTPAckSendWaiting + MTPKillFileSessionTimeout; for (QMap::iterator i = killDownloadSessionTimes.begin(); i != killDownloadSessionTimes.end(); ) { diff --git a/Telegram/SourceFiles/application.h b/Telegram/SourceFiles/application.h index f894102fe..953b503e5 100644 --- a/Telegram/SourceFiles/application.h +++ b/Telegram/SourceFiles/application.h @@ -203,6 +203,7 @@ public slots: void call_handleHistoryUpdate(); void call_handleUnreadCounterUpdate(); + void call_handleFileDialogQueue(); private: diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp index b79f89819..43dfe7a93 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.cpp +++ b/Telegram/SourceFiles/boxes/contactsbox.cpp @@ -2003,11 +2003,11 @@ MembersFilter MembersInner::filter() const { return _filter; } -QMap 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; diff --git a/Telegram/SourceFiles/boxes/contactsbox.h b/Telegram/SourceFiles/boxes/contactsbox.h index e1bb53673..d40ce5924 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.h +++ b/Telegram/SourceFiles/boxes/contactsbox.h @@ -31,7 +31,7 @@ enum MembersFilter { MembersFilterRecent, MembersFilterAdmins, }; -typedef QMap MembersAlreadyIn; +using MembersAlreadyIn = OrderedSet; QString cantInviteError(); @@ -318,7 +318,7 @@ public: } void clearSel(); - QMap already() const; + MembersAlreadyIn already() const; ~MembersInner(); diff --git a/Telegram/SourceFiles/core/basic_types.h b/Telegram/SourceFiles/core/basic_types.h index 4907c2855..c5240c1b9 100644 --- a/Telegram/SourceFiles/core/basic_types.h +++ b/Telegram/SourceFiles/core/basic_types.h @@ -33,6 +33,13 @@ T *getPointerAndReset(T *&ptr) { return result; } +template +T createAndSwap(T &value) { + T result; + std::swap(result, value); + return result; +} + struct NullType { }; diff --git a/Telegram/SourceFiles/core/observer.cpp b/Telegram/SourceFiles/core/observer.cpp index 607b28911..65a9ffd7c 100644 --- a/Telegram/SourceFiles/core/observer.cpp +++ b/Telegram/SourceFiles/core/observer.cpp @@ -30,6 +30,8 @@ NeverFreedPointer StartCallbacks; NeverFreedPointer FinishCallbacks; UnregisterObserverCallback UnregisterCallbacks[256]/* = { nullptr }*/; +ObservedEvent LastRegisteredEvent/* = 0*/; + } // namespace void startObservers() { @@ -50,17 +52,18 @@ void finishObservers() { FinishCallbacks.clear(); } -ObservedEventRegistrator::ObservedEventRegistrator(ObservedEvent event -, StartObservedEventCallback startCallback +ObservedEventRegistrator::ObservedEventRegistrator(StartObservedEventCallback startCallback , FinishObservedEventCallback finishCallback , UnregisterObserverCallback unregisterCallback) { + _event = LastRegisteredEvent++; + StartCallbacks.makeIfNull(); StartCallbacks->push_back(startCallback); FinishCallbacks.makeIfNull(); FinishCallbacks->push_back(finishCallback); - UnregisterCallbacks[event] = unregisterCallback; + UnregisterCallbacks[_event] = unregisterCallback; } // Observer base interface. diff --git a/Telegram/SourceFiles/core/observer.h b/Telegram/SourceFiles/core/observer.h index 8ed722dbc..9ffa55f76 100644 --- a/Telegram/SourceFiles/core/observer.h +++ b/Telegram/SourceFiles/core/observer.h @@ -42,10 +42,16 @@ using FinishObservedEventCallback = void(*)(); // unregisterCallback will be used to destroy connections. class ObservedEventRegistrator { public: - ObservedEventRegistrator(ObservedEvent event - , StartObservedEventCallback startCallback - , FinishObservedEventCallback finishCallback - , UnregisterObserverCallback unregisterCallback); + ObservedEventRegistrator(StartObservedEventCallback startCallback, + FinishObservedEventCallback finishCallback, + UnregisterObserverCallback unregisterCallback); + + inline ObservedEvent event() const { + return _event; + } + +private: + ObservedEvent _event; }; @@ -88,8 +94,12 @@ struct ObserversList { QVector freeIndices; }; +// If no filtering by flags is done, you can use this value in both +// Notify::registerObserver() and Notify::notifyObservers() +constexpr int UniversalFlag = 0x01; + template -int registerObserver(ObserversList &list, Flags flags, Handler &&handler) { +ConnectionId registerObserver(ObservedEvent event, ObserversList &list, Flags flags, Handler &&handler) { while (!list.freeIndices.isEmpty()) { auto freeIndex = list.freeIndices.back(); list.freeIndices.pop_back(); @@ -100,7 +110,8 @@ int registerObserver(ObserversList &list, Flags flags, Handler & } } list.entries.push_back({ flags, std_::move(handler) }); - return list.entries.size() - 1; + int connectionIndex = list.entries.size() - 1; + return (static_cast(event) << 24) | static_cast(connectionIndex + 1); } template @@ -131,17 +142,26 @@ namespace internal { template struct ObserverRegisteredGeneric { - static void call(ObserverType *observer, ConnectionId connection) { + static inline void call(ObserverType *observer, ConnectionId connection) { observer->observerRegistered(connection); } }; -template +template struct ObserverRegisteredGeneric { - static void call(ObserverType *observer, ConnectionId connection) { + static inline void call(ObserverType *observer, ConnectionId connection) { observerRegisteredDefault(observer, connection); } }; } // namespace internal + +template +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::value>; + ObserverRegistered::call(observer, connection); +} + } // namespace Notify diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index b1aebc849..1cc382d30 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -519,6 +519,7 @@ struct Data { uint64 LaunchId = 0; SingleDelayedCall HandleHistoryUpdate = { App::app(), "call_handleHistoryUpdate" }; SingleDelayedCall HandleUnreadCounterUpdate = { App::app(), "call_handleUnreadCounterUpdate" }; + SingleDelayedCall HandleFileDialogQueue = { App::app(), "call_handleFileDialogQueue" }; Adaptive::Layout AdaptiveLayout = Adaptive::NormalLayout; bool AdaptiveForWide = true; @@ -582,6 +583,7 @@ void finish() { DefineReadOnlyVar(Global, uint64, LaunchId); DefineRefVar(Global, SingleDelayedCall, HandleHistoryUpdate); DefineRefVar(Global, SingleDelayedCall, HandleUnreadCounterUpdate); +DefineRefVar(Global, SingleDelayedCall, HandleFileDialogQueue); DefineVar(Global, Adaptive::Layout, AdaptiveLayout); DefineVar(Global, bool, AdaptiveForWide); diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index 0966f9d9b..c6fae8f47 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -204,6 +204,7 @@ void finish(); DeclareReadOnlyVar(uint64, LaunchId); DeclareRefVar(SingleDelayedCall, HandleHistoryUpdate); DeclareRefVar(SingleDelayedCall, HandleUnreadCounterUpdate); +DeclareRefVar(SingleDelayedCall, HandleFileDialogQueue); DeclareVar(Adaptive::Layout, AdaptiveLayout); DeclareVar(bool, AdaptiveForWide); diff --git a/Telegram/SourceFiles/mtproto/facade.h b/Telegram/SourceFiles/mtproto/facade.h index dfd5f22ad..6634fb812 100644 --- a/Telegram/SourceFiles/mtproto/facade.h +++ b/Telegram/SourceFiles/mtproto/facade.h @@ -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 { diff --git a/Telegram/SourceFiles/mtproto/file_download.cpp b/Telegram/SourceFiles/mtproto/file_download.cpp index 73d61b131..71353c24a 100644 --- a/Telegram/SourceFiles/mtproto/file_download.cpp +++ b/Telegram/SourceFiles/mtproto/file_download.cpp @@ -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,45 @@ namespace MTP { ++GlobalPriority; } } + +namespace FileDownload { +namespace { + +using internal::ImageLoadedHandler; + +using ImageLoadedObserversList = Notify::ObserversList; +NeverFreedPointer ImageLoadedObservers; + +void StartCallback() { + ImageLoadedObservers.makeIfNull(); +} +void FinishCallback() { + ImageLoadedObservers.clear(); +} +void UnregisterCallback(int connectionIndex) { + t_assert(!ImageLoadedObservers.isNull()); + Notify::unregisterObserver(*ImageLoadedObservers, connectionIndex); +} +Notify::ObservedEventRegistrator creator(StartCallback, FinishCallback, UnregisterCallback); + +bool Started() { + return !ImageLoadedObservers.isNull(); +} + +} // namespace + +namespace internal { + +Notify::ConnectionId plainRegisterImageLoadedObserver(ImageLoadedHandler &&handler) { + t_assert(Started()); + auto connectionId = Notify::registerObserver(creator.event(), *ImageLoadedObservers + , Notify::UniversalFlag, std_::forward(handler)); + return connectionId; +} + +void notifyImageLoaded() { + Notify::notifyObservers(*ImageLoadedObservers, Notify::UniversalFlag); +} + +} // namespace internal +} \ No newline at end of file diff --git a/Telegram/SourceFiles/mtproto/file_download.h b/Telegram/SourceFiles/mtproto/file_download.h index 35c6a095c..f8a05d7ea 100644 --- a/Telegram/SourceFiles/mtproto/file_download.h +++ b/Telegram/SourceFiles/mtproto/file_download.h @@ -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; +Notify::ConnectionId plainRegisterImageLoadedObserver(ImageLoadedHandler &&handler); + +void notifyImageLoaded(); + +} // namespace internal + +template +void registerImageLoadedObserver(ObserverType *observer, void (ObserverType::*handler)()) { + auto connection = internal::plainRegisterImageLoadedObserver(func(observer, handler)); + Notify::observerRegistered(observer, connection); +} + +} // namespace FileDownload diff --git a/Telegram/SourceFiles/observer_peer.cpp b/Telegram/SourceFiles/observer_peer.cpp index c77ec90bf..d1cbbde79 100644 --- a/Telegram/SourceFiles/observer_peer.cpp +++ b/Telegram/SourceFiles/observer_peer.cpp @@ -32,7 +32,6 @@ namespace Notify { namespace internal { namespace { -constexpr ObservedEvent PeerUpdateEvent = 0x01; using PeerObserversList = ObserversList; NeverFreedPointer PeerUpdateObservers; @@ -55,7 +54,7 @@ void UnregisterCallback(int connectionIndex) { t_assert(!PeerUpdateObservers.isNull()); unregisterObserver(*PeerUpdateObservers, connectionIndex); } -ObservedEventRegistrator creator(PeerUpdateEvent, StartCallback, FinishCallback, UnregisterCallback); +ObservedEventRegistrator creator(StartCallback, FinishCallback, UnregisterCallback); bool Started() { return !PeerUpdateObservers.isNull(); @@ -65,9 +64,9 @@ bool Started() { ConnectionId plainRegisterPeerObserver(PeerUpdateFlags events, PeerUpdateHandler &&handler) { t_assert(Started()); - auto connectionId = registerObserver(*PeerUpdateObservers, events, std_::forward(handler)); - t_assert(connectionId >= 0 && connectionId < 0x01000000); - return (static_cast(PeerUpdateEvent) << 24) | static_cast(connectionId + 1); + auto connectionId = registerObserver(creator.event(), *PeerUpdateObservers + , events, std_::forward(handler)); + return connectionId; } void mergePeerUpdate(PeerUpdate &mergeTo, const PeerUpdate &mergeFrom) { @@ -116,10 +115,8 @@ void peerUpdatedSendDelayed() { if (internal::SmallUpdates->isEmpty()) return; - internal::SmallUpdatesList smallList; - internal::AllUpdatesList allList; - std::swap(smallList, *internal::SmallUpdates); - std::swap(allList, *internal::AllUpdates); + auto smallList = createAndSwap(*internal::SmallUpdates); + auto allList = createAndSwap(*internal::AllUpdates); for_const (auto &update, smallList) { notifyObservers(*internal::PeerUpdateObservers, update.flags, update); } diff --git a/Telegram/SourceFiles/observer_peer.h b/Telegram/SourceFiles/observer_peer.h index a490f0b11..18a425bcf 100644 --- a/Telegram/SourceFiles/observer_peer.h +++ b/Telegram/SourceFiles/observer_peer.h @@ -30,16 +30,17 @@ namespace Notify { // 0xFFFF0000U for specific peer updates (valid for user / chat / channel). enum class PeerUpdateFlag { - NameChanged = 0x00000001U, - UsernameChanged = 0x00000002U, + NameChanged = 0x00000001U, + UsernameChanged = 0x00000002U, + PhotoChanged = 0x00000004U, - UserCanShareContact = 0x00010000U, + UserCanShareContact = 0x00010000U, - ChatCanEdit = 0x00010000U, + ChatCanEdit = 0x00010000U, - ChannelAmIn = 0x00010000U, - MegagroupCanEditPhoto = 0x00020000U, - MegagroupCanAddMembers = 0x00040000U, + ChannelAmIn = 0x00010000U, + ChannelCanEditPhoto = 0x00020000U, + ChannelCanAddMembers = 0x00040000U, }; Q_DECLARE_FLAGS(PeerUpdateFlags, PeerUpdateFlag); Q_DECLARE_OPERATORS_FOR_FLAGS(PeerUpdateFlags); @@ -68,11 +69,7 @@ ConnectionId plainRegisterPeerObserver(PeerUpdateFlags events, PeerUpdateHandler template void registerPeerObserver(PeerUpdateFlags events, ObserverType *observer, void (ObserverType::*handler)(const PeerUpdate &)) { auto connection = internal::plainRegisterPeerObserver(events, func(observer, handler)); - - // 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::value>; - ObserverRegistered::call(observer, connection); + observerRegistered(observer, connection); } } // namespace Notify diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp index 46620a7e5..e9bf3666d 100644 --- a/Telegram/SourceFiles/profile/profile_cover.cpp +++ b/Telegram/SourceFiles/profile/profile_cover.cpp @@ -23,8 +23,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_profile.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" @@ -62,31 +65,61 @@ private: const Notify::PeerUpdateFlags ButtonsUpdateFlags = Notify::PeerUpdateFlag::UserCanShareContact | Notify::PeerUpdateFlag::ChatCanEdit - | Notify::PeerUpdateFlag::MegagroupCanEditPhoto - | Notify::PeerUpdateFlag::MegagroupCanAddMembers + | Notify::PeerUpdateFlag::ChannelCanEditPhoto + | Notify::PeerUpdateFlag::ChannelCanAddMembers | Notify::PeerUpdateFlag::ChannelAmIn; } // namespace -class PhotoButton final : public Button { +class PhotoButton final : public Button, public Notify::Observer { public: PhotoButton(QWidget *parent, PeerData *peer) : Button(parent), _peer(peer) { resize(st::profilePhotoSize, st::profilePhotoSize); - } - void photoUpdated() { - bool hasPhoto = (_peer->photoId && _peer->photoId != UnknownPeerPhotoId); - setCursor(hasPhoto ? style::cur_pointer : style::cur_default); + + processNewPeerPhoto(); + + Notify::registerPeerObserver(Notify::PeerUpdateFlag::PhotoChanged, this, &PhotoButton::notifyPeerUpdated); + FileDownload::registerImageLoadedObserver(this, &PhotoButton::notifyImageLoaded); } protected: void paintEvent(QPaintEvent *e) { Painter p(this); - - _peer->paintUserpic(p, st::profilePhotoSize, 0, 0); + p.drawPixmap(0, 0, _userpic); } private: + void notifyPeerUpdated(const Notify::PeerUpdate &update) { + if (update.peer != _peer) { + return; + } + + processNewPeerPhoto(); + this->update(); + } + + void notifyImageLoaded() { + if (_waiting && _peer->userpicLoaded()) { + _waiting = false; + _userpic = _peer->genUserpic(st::profilePhotoSize); + update(); + } + } + + void processNewPeerPhoto() { + bool hasPhoto = (_peer->photoId && _peer->photoId != UnknownPeerPhotoId); + setCursor(hasPhoto ? style::cur_pointer : style::cur_default); + _waiting = !_peer->userpicLoaded(); + if (_waiting) { + _peer->loadUserpic(true); + } else { + _userpic = _peer->genUserpic(st::profilePhotoSize); + } + } + PeerData *_peer; + bool _waiting = false; + QPixmap _userpic; }; @@ -101,8 +134,8 @@ CoverWidget::CoverWidget(QWidget *parent, PeerData *peer) : TWidget(parent) auto observeEvents = ButtonsUpdateFlags | Notify::PeerUpdateFlag::NameChanged; Notify::registerPeerObserver(observeEvents, this, &CoverWidget::notifyPeerUpdated); + FileDialog::registerObserver(this, &CoverWidget::notifyFileQueryUpdated); - _photoButton->photoUpdated(); connect(_photoButton, SIGNAL(clicked()), this, SLOT(onPhotoShow())); refreshNameText(); @@ -132,12 +165,9 @@ void CoverWidget::resizeToWidth(int newWidth) { _statusPosition = QPoint(infoLeft + st::profileStatusLeft, _photoButton->y() + st::profileStatusTop); int buttonLeft = st::profilePhotoLeft + _photoButton->width() + st::profileButtonLeft; - if (_primaryButton) { - _primaryButton->moveToLeft(buttonLeft, st::profileButtonTop); - buttonLeft += _primaryButton->width() + st::profileButtonSkip; - } - if (_secondaryButton) { - _secondaryButton->moveToLeft(buttonLeft, st::profileButtonTop); + for_const (auto button, _buttons) { + button->moveToLeft(buttonLeft, st::profileButtonTop); + buttonLeft += button->width() + st::profileButtonSkip; } newHeight += st::profilePhotoSize; @@ -178,13 +208,14 @@ void CoverWidget::paintDivider(Painter &p) { } void CoverWidget::notifyPeerUpdated(const Notify::PeerUpdate &update) { - if (update.peer == _peer) { - if ((update.flags & ButtonsUpdateFlags) != 0) { - refreshButtons(); - } - if (update.flags & Notify::PeerUpdateFlag::NameChanged) { - refreshNameText(); - } + if (update.peer != _peer) { + return; + } + if ((update.flags & ButtonsUpdateFlags) != 0) { + refreshButtons(); + } + if (update.flags & Notify::PeerUpdateFlag::NameChanged) { + refreshNameText(); } } @@ -247,6 +278,7 @@ bool CoverWidget::isUsingMegagroupOnlineCount() const { } void CoverWidget::refreshButtons() { + clearButtons(); if (_peerUser) { setUserButtons(); } else if (_peerChat) { @@ -260,65 +292,51 @@ void CoverWidget::refreshButtons() { } void CoverWidget::setUserButtons() { - setPrimaryButton(lang(lng_profile_send_message), SLOT(onSendMessage())); + addButton(lang(lng_profile_send_message), SLOT(onSendMessage())); if (_peerUser->canShareThisContact()) { - setSecondaryButton(lang(lng_profile_share_contact), SLOT(onShareContact())); - } else { - clearSecondaryButton(); + addButton(lang(lng_profile_share_contact), SLOT(onShareContact())); } } void CoverWidget::setChatButtons() { if (_peerChat->canEdit()) { - setPrimaryButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto())); - setSecondaryButton(lang(lng_profile_add_participant), SLOT(onAddMember())); - } else { - clearPrimaryButton(); - clearSecondaryButton(); + addButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto())); + addButton(lang(lng_profile_add_participant), SLOT(onAddMember())); } } void CoverWidget::setMegagroupButtons() { if (_peerMegagroup->canEditPhoto()) { - setPrimaryButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto())); - } else { - clearPrimaryButton(); + addButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto())); } if (_peerMegagroup->canAddParticipants()) { - setSecondaryButton(lang(lng_profile_add_participant), SLOT(onAddMember())); - } else { - clearSecondaryButton(); + addButton(lang(lng_profile_add_participant), SLOT(onAddMember())); } } void CoverWidget::setChannelButtons() { if (_peerChannel->amCreator()) { - setPrimaryButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto())); + addButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto())); } else if (_peerChannel->amIn()) { - setPrimaryButton(lang(lng_profile_view_channel), SLOT(onViewChannel())); + addButton(lang(lng_profile_view_channel), SLOT(onViewChannel())); } else { - setPrimaryButton(lang(lng_profile_join_channel), SLOT(onJoin())); - } - clearSecondaryButton(); -} - -void CoverWidget::setPrimaryButton(const QString &text, const char *slot) { - delete _primaryButton; - _primaryButton = nullptr; - if (!text.isEmpty()) { - _primaryButton = new Ui::RoundButton(this, text, st::profilePrimaryButton); - connect(_primaryButton, SIGNAL(clicked()), this, slot); - _primaryButton->show(); + addButton(lang(lng_profile_join_channel), SLOT(onJoin())); } } -void CoverWidget::setSecondaryButton(const QString &text, const char *slot) { - delete _secondaryButton; - _secondaryButton = nullptr; +void CoverWidget::clearButtons() { + auto buttons = createAndSwap(_buttons); + for_const (auto button, buttons) { + delete button; + } +} + +void CoverWidget::addButton(const QString &text, const char *slot) { if (!text.isEmpty()) { - _secondaryButton = new Ui::RoundButton(this, text, st::profileSecondaryButton); - connect(_secondaryButton, SIGNAL(clicked()), this, slot); - _secondaryButton->show(); + auto &buttonStyle = _buttons.isEmpty() ? st::profilePrimaryButton : st::profileSecondaryButton; + _buttons.push_back(new Ui::RoundButton(this, text, buttonStyle)); + connect(_buttons.back(), SIGNAL(clicked()), this, slot); + _buttons.back()->show(); } } @@ -331,7 +349,37 @@ void CoverWidget::onShareContact() { } 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()); + } + + 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() { @@ -339,15 +387,17 @@ void CoverWidget::onAddMember() { Ui::showLayer(new ContactsBox(_peerChat, MembersFilterRecent)); } else if (_peerChannel && _peerChannel->mgInfo) { MembersAlreadyIn already; - for (MegagroupInfo::LastParticipants::const_iterator i = _peerChannel->mgInfo->lastParticipants.cbegin(), e = _peerChannel->mgInfo->lastParticipants.cend(); i != e; ++i) { - already.insert(*i, true); + 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() { diff --git a/Telegram/SourceFiles/profile/profile_cover.h b/Telegram/SourceFiles/profile/profile_cover.h index 96b7d668f..da3a5dcd5 100644 --- a/Telegram/SourceFiles/profile/profile_cover.h +++ b/Telegram/SourceFiles/profile/profile_cover.h @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once #include "core/observer.h" +#include "ui/filedialog.h" namespace Ui { class RoundButton; @@ -60,6 +61,7 @@ protected: private: // Observed notifications. void notifyPeerUpdated(const Notify::PeerUpdate &update); + void notifyFileQueryUpdated(const FileDialog::QueryUpdate &update); void refreshNameText(); void refreshStatusText(); @@ -71,14 +73,8 @@ private: void setMegagroupButtons(); void setChannelButtons(); - void setPrimaryButton(const QString &text, const char *slot); - void setSecondaryButton(const QString &text, const char *slot); - void clearPrimaryButton() { - setPrimaryButton(QString(), nullptr); - } - void clearSecondaryButton() { - setSecondaryButton(QString(), nullptr); - } + void clearButtons(); + void addButton(const QString &text, const char *slot); void paintDivider(Painter &p); @@ -97,10 +93,11 @@ private: QPoint _statusPosition; QString _statusText; - int _dividerTop; + QList _buttons; - ChildWidget _primaryButton = { nullptr }; - ChildWidget _secondaryButton = { nullptr }; + int _dividerTop = 0; + + FileDialog::QueryId _setPhotoFileQueryId = 0; }; diff --git a/Telegram/SourceFiles/profile/profile_fixed_bar.cpp b/Telegram/SourceFiles/profile/profile_fixed_bar.cpp index a640633c8..9c3b30f64 100644 --- a/Telegram/SourceFiles/profile/profile_fixed_bar.cpp +++ b/Telegram/SourceFiles/profile/profile_fixed_bar.cpp @@ -38,7 +38,7 @@ public: } protected: - void paintEvent(QPaintEvent *e) { + void paintEvent(QPaintEvent *e) override { Painter p(this); p.fillRect(e->rect(), st::profileBg); @@ -48,6 +48,11 @@ protected: 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: diff --git a/Telegram/SourceFiles/profile/profile_inner_widget.cpp b/Telegram/SourceFiles/profile/profile_inner_widget.cpp index 408ecf18c..d606c7435 100644 --- a/Telegram/SourceFiles/profile/profile_inner_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_inner_widget.cpp @@ -45,7 +45,7 @@ void InnerWidget::setVisibleTopBottom(int visibleTop, int visibleBottom) { int notDisplayedAtBottom = height() - _visibleBottom; if (notDisplayedAtBottom > 0) { -// decreaseAdditionalHeight(notDisplayedAtBottom); + decreaseAdditionalHeight(notDisplayedAtBottom); } //loadProfilePhotos(_visibleTop); @@ -66,8 +66,10 @@ void InnerWidget::paintEvent(QPaintEvent *e) { p.fillRect(e->rect(), st::profileBg); } -void InnerWidget::mousePressEvent(QMouseEvent *e) { // TEMP for testing - Ui::showPeerOverview(_peer, OverviewPhotos); +void InnerWidget::keyPressEvent(QKeyEvent *e) { + if (e->key() == Qt::Key_Escape) { + emit cancelled(); + } } int InnerWidget::resizeGetHeight(int newWidth) { diff --git a/Telegram/SourceFiles/profile/profile_inner_widget.h b/Telegram/SourceFiles/profile/profile_inner_widget.h index 66540915f..6a1ad1500 100644 --- a/Telegram/SourceFiles/profile/profile_inner_widget.h +++ b/Telegram/SourceFiles/profile/profile_inner_widget.h @@ -41,9 +41,12 @@ public: // Updates the area that is visible inside the scroll container. void setVisibleTopBottom(int visibleTop, int visibleBottom); +signals: + void cancelled(); + protected: void paintEvent(QPaintEvent *e) override; - void mousePressEvent(QMouseEvent *e) override; // TEMP for testing + void keyPressEvent(QKeyEvent *e) override; private: // Resizes content and counts natural widget height for the desired width. diff --git a/Telegram/SourceFiles/profile/profile_widget.cpp b/Telegram/SourceFiles/profile/profile_widget.cpp index d943a3eaf..ca3672185 100644 --- a/Telegram/SourceFiles/profile/profile_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_widget.cpp @@ -48,6 +48,7 @@ Widget::Widget(QWidget *parent, PeerData *peer) : Window::SectionWidget(parent) connect(_scroll, SIGNAL(scrolled()), _inner, SLOT(updateSelected())); connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); + connect(_inner, SIGNAL(cancelled()), _fixedBar, SLOT(onBack())); } void Widget::updateAdaptiveLayout() { diff --git a/Telegram/SourceFiles/profilewidget.cpp b/Telegram/SourceFiles/profilewidget.cpp index b6d5e993f..6768a2b36 100644 --- a/Telegram/SourceFiles/profilewidget.cpp +++ b/Telegram/SourceFiles/profilewidget.cpp @@ -396,8 +396,8 @@ void ProfileInner::onAddParticipant() { Ui::showLayer(new ContactsBox(_peerChat, MembersFilterRecent)); } else if (_peerChannel && _peerChannel->mgInfo) { MembersAlreadyIn already; - for (MegagroupInfo::LastParticipants::const_iterator i = _peerChannel->mgInfo->lastParticipants.cbegin(), e = _peerChannel->mgInfo->lastParticipants.cend(); i != e; ++i) { - already.insert(*i, true); + for_const (auto user, _peerChannel->mgInfo->lastParticipants) { + already.insert(user); } Ui::showLayer(new ContactsBox(_peerChannel, MembersFilterRecent, already)); } diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index 25170670a..a3e3f72bd 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -229,6 +229,10 @@ void UserData::setPhoto(const MTPUserProfilePhoto &p) { // see Local::readPeer a if (App::main()) { emit App::main()->peerPhotoChanged(this); } + + Notify::PeerUpdate update(this); + update.flags = Notify::PeerUpdateFlag::PhotoChanged; + Notify::peerUpdatedDelayed(update); } } @@ -401,6 +405,10 @@ void ChatData::setPhoto(const MTPChatPhoto &p, const PhotoId &phId) { // see Loc setUserpic(newPhoto); photoLoc = newPhotoLoc; emit App::main()->peerPhotoChanged(this); + + Notify::PeerUpdate update(this); + update.flags = Notify::PeerUpdateFlag::PhotoChanged; + Notify::peerUpdatedDelayed(update); } } @@ -439,6 +447,10 @@ void ChannelData::setPhoto(const MTPChatPhoto &p, const PhotoId &phId) { // see setUserpic(newPhoto); photoLoc = newPhotoLoc; if (App::main()) emit App::main()->peerPhotoChanged(this); + + Notify::PeerUpdate update(this); + update.flags = Notify::PeerUpdateFlag::PhotoChanged; + Notify::peerUpdatedDelayed(update); } } diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index 2b52bb830..99698b27d 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -309,6 +309,9 @@ public: void loadUserpic(bool loadFirst = false, bool prior = true) { _userpic->load(loadFirst, prior); } + bool userpicLoaded() const { + return _userpic->loaded(); + } StorageKey userpicUniqueKey() const; void saveUserpic(const QString &path) const; QPixmap genUserpic(int size) const; diff --git a/Telegram/SourceFiles/ui/filedialog.cpp b/Telegram/SourceFiles/ui/filedialog.cpp index f3d9cc829..aaf02498c 100644 --- a/Telegram/SourceFiles/ui/filedialog.cpp +++ b/Telegram/SourceFiles/ui/filedialog.cpp @@ -234,3 +234,150 @@ QString filedialogNextFilename(const QString &name, const QString &cur, const QS } return result; } + +namespace FileDialog { +namespace { + +using internal::QueryUpdateHandler; + +using QueryObserversList = Notify::ObserversList; +NeverFreedPointer QueryUpdateObservers; + +struct Query { + enum class Type { + ReadFile, + ReadFiles, + WriteFile, + ReadFolder, + }; + Query(Type type + , const QString &caption = QString() + , const QString &filter = QString() + , const QString &filePath = QString()) : id(rand_value()) + , type(type) + , caption(caption) + , filter(filter) + , filePath(filePath) { + } + QueryId id; + Type type; + QString caption, filter, filePath; +}; + +using QueryList = QList; +NeverFreedPointer Queries; + +void StartCallback() { + QueryUpdateObservers.makeIfNull(); + Queries.makeIfNull(); +} +void FinishCallback() { + QueryUpdateObservers.clear(); + Queries.clear(); +} +void UnregisterCallback(int connectionIndex) { + t_assert(!QueryUpdateObservers.isNull()); + Notify::unregisterObserver(*QueryUpdateObservers, connectionIndex); +} +Notify::ObservedEventRegistrator creator(StartCallback, FinishCallback, UnregisterCallback); + +bool Started() { + return !QueryUpdateObservers.isNull(); +} + +} // namespace + +QueryId queryReadFile(const QString &caption, const QString &filter) { + t_assert(Started()); + Queries->push_back(Query(Query::Type::ReadFile, caption, filter)); + Global::RefHandleFileDialogQueue().call(); + return Queries->back().id; +} + +QueryId queryReadFiles(const QString &caption, const QString &filter) { + t_assert(Started()); + Queries->push_back(Query(Query::Type::ReadFiles, caption, filter)); + Global::RefHandleFileDialogQueue().call(); + return Queries->back().id; +} + +QueryId queryWriteFile(const QString &caption, const QString &filter, const QString &filePath) { + t_assert(Started()); + Queries->push_back(Query(Query::Type::WriteFile, caption, filter, filePath)); + Global::RefHandleFileDialogQueue().call(); + return Queries->back().id; +} + +QueryId queryReadFolder(const QString &caption) { + t_assert(Started()); + Queries->push_back(Query(Query::Type::ReadFolder, caption)); + Global::RefHandleFileDialogQueue().call(); + return Queries->back().id; +} + +bool processQuery() { + if (!Started() || !Global::started() || Queries->isEmpty()) return false; + + auto query = Queries->front(); + Queries->pop_front(); + + QueryUpdate update(query.id); + + switch (query.type) { + case Query::Type::ReadFile: { + QString file; + QByteArray remoteContent; + if (filedialogGetOpenFile(file, remoteContent, query.caption, query.filter)) { + if (!file.isEmpty()) { + update.filePaths.push_back(file); + } + update.remoteContent = remoteContent; + } + } break; + + case Query::Type::ReadFiles: { + QStringList files; + QByteArray remoteContent; + if (filedialogGetOpenFiles(files, remoteContent, query.caption, query.filter)) { + update.filePaths = files; + update.remoteContent = remoteContent; + } + } break; + + case Query::Type::WriteFile: { + QString file; + if (filedialogGetSaveFile(file, query.caption, query.filter, query.filePath)) { + if (!file.isEmpty()) { + update.filePaths.push_back(file); + } + } + } break; + + case Query::Type::ReadFolder: { + QString folder; + if (filedialogGetDir(folder, query.caption)) { + if (!folder.isEmpty()) { + update.filePaths.push_back(folder); + } + } + } break; + } + + // No one know what happened during filedialogGet*() call in the event loop. + if (!Started() || !Global::started()) return false; + + Notify::notifyObservers(*QueryUpdateObservers, Notify::UniversalFlag, update); + return true; +} + +namespace internal { + +Notify::ConnectionId plainRegisterObserver(QueryUpdateHandler &&handler) { + t_assert(Started()); + auto connectionId = Notify::registerObserver(creator.event(), *QueryUpdateObservers + , Notify::UniversalFlag, std_::forward(handler)); + return connectionId; +} + +} // namespace internal +} // namespace FileDialog diff --git a/Telegram/SourceFiles/ui/filedialog.h b/Telegram/SourceFiles/ui/filedialog.h index 33f3f1391..9a8317e37 100644 --- a/Telegram/SourceFiles/ui/filedialog.h +++ b/Telegram/SourceFiles/ui/filedialog.h @@ -20,6 +20,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once +#include "core/observer.h" + void filedialogInit(); bool filedialogGetOpenFiles(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter); bool filedialogGetOpenFile(QString &file, QByteArray &remoteContent, const QString &caption, const QString &filter); @@ -28,3 +30,38 @@ bool filedialogGetDir(QString &dir, const QString &caption); QString filedialogDefaultName(const QString &prefix, const QString &extension, const QString &path = QString(), bool skipExistance = false); QString filedialogNextFilename(const QString &name, const QString &cur, const QString &path = QString()); + +namespace FileDialog { + +using QueryId = uint64; +struct QueryUpdate { + QueryUpdate(QueryId id) : queryId(id) { + } + QueryId queryId; + QStringList filePaths; + QByteArray remoteContent; +}; + +QueryId queryReadFile(const QString &caption, const QString &filter); +QueryId queryReadFiles(const QString &caption, const QString &filter); +QueryId queryWriteFile(const QString &caption, const QString &filter, const QString &filePath); +QueryId queryReadFolder(const QString &caption); + +// Returns false if no need to call it anymore right now. +// NB! This function enters an event loop. +bool processQuery(); + +namespace internal { + +using QueryUpdateHandler = Function; +Notify::ConnectionId plainRegisterObserver(QueryUpdateHandler &&handler); + +} // namespace internal + +template +void registerObserver(ObserverType *observer, void (ObserverType::*handler)(const QueryUpdate &)) { + auto connection = internal::plainRegisterObserver(func(observer, handler)); + Notify::observerRegistered(observer, connection); +} + +} // namespace FileDialog diff --git a/Telegram/SourceFiles/ui/images.h b/Telegram/SourceFiles/ui/images.h index 036c48f33..3cc8f1f3b 100644 --- a/Telegram/SourceFiles/ui/images.h +++ b/Telegram/SourceFiles/ui/images.h @@ -20,7 +20,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include +#include "mtproto/file_download.h" QImage imageBlur(QImage img); void imageRound(QImage &img); From 1c13556b8d113f1ce770532fc8c6b33d796fe09f Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 25 May 2016 21:49:47 +0300 Subject: [PATCH 12/30] Simplified usage of Observer, new event types can be added easier. --- Telegram/SourceFiles/core/observer.cpp | 60 +++-- Telegram/SourceFiles/core/observer.h | 217 ++++++++++++------ .../SourceFiles/mtproto/file_download.cpp | 26 +-- Telegram/SourceFiles/observer_peer.cpp | 67 +++--- Telegram/SourceFiles/ui/filedialog.cpp | 38 ++- 5 files changed, 230 insertions(+), 178 deletions(-) diff --git a/Telegram/SourceFiles/core/observer.cpp b/Telegram/SourceFiles/core/observer.cpp index 65a9ffd7c..6ec33a341 100644 --- a/Telegram/SourceFiles/core/observer.cpp +++ b/Telegram/SourceFiles/core/observer.cpp @@ -22,50 +22,69 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "core/observer.h" namespace Notify { +namespace internal { namespace { -using StartCallbacksList = QVector; -using FinishCallbacksList = QVector; +struct StartCallbackData { + void *that; + StartCallback call; +}; +struct FinishCallbackData { + void *that; + FinishCallback call; +}; +struct UnregisterCallbackData { + void *that; + UnregisterCallback call; +}; +using StartCallbacksList = QVector; +using FinishCallbacksList = QVector; NeverFreedPointer StartCallbacks; NeverFreedPointer FinishCallbacks; -UnregisterObserverCallback UnregisterCallbacks[256]/* = { nullptr }*/; +UnregisterCallbackData UnregisterCallbacks[256]/* = { nullptr }*/; ObservedEvent LastRegisteredEvent/* = 0*/; } // namespace +} // namespace internal void startObservers() { - if (!StartCallbacks) return; + if (!internal::StartCallbacks) return; - for (auto &callback : *StartCallbacks) { - callback(); + for (auto &callback : *internal::StartCallbacks) { + callback.call(callback.that); } } void finishObservers() { - if (!FinishCallbacks) return; + if (!internal::FinishCallbacks) return; - for (auto &callback : *FinishCallbacks) { - callback(); + for (auto &callback : *internal::FinishCallbacks) { + callback.call(callback.that); } - StartCallbacks.clear(); - FinishCallbacks.clear(); + internal::StartCallbacks.clear(); + internal::FinishCallbacks.clear(); } -ObservedEventRegistrator::ObservedEventRegistrator(StartObservedEventCallback startCallback -, FinishObservedEventCallback finishCallback -, UnregisterObserverCallback unregisterCallback) { +namespace internal { + +BaseObservedEventRegistrator::BaseObservedEventRegistrator(void *that +, StartCallback startCallback +, FinishCallback finishCallback +, UnregisterCallback unregisterCallback) { _event = LastRegisteredEvent++; StartCallbacks.makeIfNull(); - StartCallbacks->push_back(startCallback); + StartCallbacks->push_back({ that, startCallback }); FinishCallbacks.makeIfNull(); - FinishCallbacks->push_back(finishCallback); + FinishCallbacks->push_back({ that, finishCallback }); - UnregisterCallbacks[_event] = unregisterCallback; + UnregisterCallbacks[_event] = { that, unregisterCallback }; } +} // namespace internal + // Observer base interface. Observer::~Observer() { for_const (auto connection, _connections) { @@ -78,10 +97,11 @@ void Observer::observerRegistered(ConnectionId connection) { } void unregisterObserver(ConnectionId connection) { - auto event = static_cast(connection >> 24); + auto event = static_cast(connection >> 24); auto connectionIndex = int(connection & 0x00FFFFFFU) - 1; - if (connectionIndex >= 0 && UnregisterCallbacks[event]) { - UnregisterCallbacks[event](connectionIndex); + auto &callback = internal::UnregisterCallbacks[event]; + if (connectionIndex >= 0 && callback.call && callback.that) { + callback.call(callback.that, connectionIndex); } } diff --git a/Telegram/SourceFiles/core/observer.h b/Telegram/SourceFiles/core/observer.h index 9ffa55f76..1530328f8 100644 --- a/Telegram/SourceFiles/core/observer.h +++ b/Telegram/SourceFiles/core/observer.h @@ -24,7 +24,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Notify { -using ObservedEvent = uchar; using ConnectionId = uint32; // startObservers() must be called after main() started (not in a global variable constructor). @@ -33,19 +32,23 @@ void startObservers(); void finishObservers(); using StartObservedEventCallback = void(*)(); -using UnregisterObserverCallback = void(*)(int connectionIndex); using FinishObservedEventCallback = void(*)(); -// Objects of this class should be constructed in global scope. -// startCallback will be called from Notify::startObservers(). -// finishCallback will be called from Notify::finishObservers(). -// unregisterCallback will be used to destroy connections. -class ObservedEventRegistrator { -public: - ObservedEventRegistrator(StartObservedEventCallback startCallback, - FinishObservedEventCallback finishCallback, - UnregisterObserverCallback unregisterCallback); +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; } @@ -55,6 +58,138 @@ private: }; +// Handler is one of Function<> instantiations. +template +struct ObserversList { + struct Entry { + Flags flags; + Handler handler; + }; + std_::vector_of_moveable entries; + QVector 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 +class ObservedEventRegistrator : public internal::BaseObservedEventRegistrator { +public: + ObservedEventRegistrator(StartObservedEventCallback startCallback, + FinishObservedEventCallback finishCallback) : internal::BaseObservedEventRegistrator(reinterpret_cast(this), + ObservedEventRegistrator::start, + ObservedEventRegistrator::finish, + ObservedEventRegistrator::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)); + return (static_cast(event()) << 24) | static_cast(connectionIndex + 1); + } + + template + 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)...); + } + } + } + +private: + using Self = ObservedEventRegistrator; + static void start(void *vthat) { + Self *that = reinterpret_cast(vthat); + + t_assert(!that->started()); + if (that->_startCallback) that->_startCallback(); + that->_list = new internal::ObserversList(); + } + static void finish(void *vthat) { + Self *that = reinterpret_cast(vthat); + + if (that->_finishCallback) that->_finishCallback(); + delete that->_list; + that->_list = nullptr; + } + static void unregister(void *vthat, int connectionIndex) { + Self *that = reinterpret_cast(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 *_list = nullptr; + +}; + +// If no filtering of notifications by Flags is intended use this class. +template +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)); + } + + template + void notify(Args&&... args) { + return _implementation.notify(internal::UniversalFlag, std_::forward(args)...); + } + +private: + ObservedEventRegistrator _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. @@ -78,66 +213,6 @@ private: }; -inline ConnectionId observerConnectionId(ObservedEvent event, int connectionIndex) { - t_assert(connectionIndex >= 0 && connectionIndex < 0x01000000); - return (static_cast(event) << 24) | (connectionIndex + 1); -} - -// Handler is one of Function<> instantiations. -template -struct ObserversList { - struct Entry { - Flags flags; - Handler handler; - }; - std_::vector_of_moveable entries; - QVector freeIndices; -}; - -// If no filtering by flags is done, you can use this value in both -// Notify::registerObserver() and Notify::notifyObservers() -constexpr int UniversalFlag = 0x01; - -template -ConnectionId registerObserver(ObservedEvent event, ObserversList &list, 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) }); - int connectionIndex = list.entries.size() - 1; - return (static_cast(event) << 24) | static_cast(connectionIndex + 1); -} - -template -void unregisterObserver(ObserversList &list, int connectionIndex) { - auto &entries(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(); - list.freeIndices.push_back(connectionIndex); - } -} - -template -void notifyObservers(ObserversList &list, Flags flags, Args&&... args) { - for (auto &entry : list.entries) { - if (!entry.handler.isNull() && (flags & entry.flags)) { - entry.handler.call(std_::forward(args)...); - } - } -} - namespace internal { template diff --git a/Telegram/SourceFiles/mtproto/file_download.cpp b/Telegram/SourceFiles/mtproto/file_download.cpp index 71353c24a..ab8a25af5 100644 --- a/Telegram/SourceFiles/mtproto/file_download.cpp +++ b/Telegram/SourceFiles/mtproto/file_download.cpp @@ -1100,38 +1100,18 @@ namespace { using internal::ImageLoadedHandler; -using ImageLoadedObserversList = Notify::ObserversList; -NeverFreedPointer ImageLoadedObservers; - -void StartCallback() { - ImageLoadedObservers.makeIfNull(); -} -void FinishCallback() { - ImageLoadedObservers.clear(); -} -void UnregisterCallback(int connectionIndex) { - t_assert(!ImageLoadedObservers.isNull()); - Notify::unregisterObserver(*ImageLoadedObservers, connectionIndex); -} -Notify::ObservedEventRegistrator creator(StartCallback, FinishCallback, UnregisterCallback); - -bool Started() { - return !ImageLoadedObservers.isNull(); -} +Notify::SimpleObservedEventRegistrator creator(nullptr, nullptr); } // namespace namespace internal { Notify::ConnectionId plainRegisterImageLoadedObserver(ImageLoadedHandler &&handler) { - t_assert(Started()); - auto connectionId = Notify::registerObserver(creator.event(), *ImageLoadedObservers - , Notify::UniversalFlag, std_::forward(handler)); - return connectionId; + return creator.registerObserver(std_::forward(handler)); } void notifyImageLoaded() { - Notify::notifyObservers(*ImageLoadedObservers, Notify::UniversalFlag); + creator.notify(); } } // namespace internal diff --git a/Telegram/SourceFiles/observer_peer.cpp b/Telegram/SourceFiles/observer_peer.cpp index d1cbbde79..ca52c4a26 100644 --- a/Telegram/SourceFiles/observer_peer.cpp +++ b/Telegram/SourceFiles/observer_peer.cpp @@ -29,11 +29,9 @@ void emitPeerUpdated(); } // namespace App namespace Notify { -namespace internal { namespace { -using PeerObserversList = ObserversList; -NeverFreedPointer PeerUpdateObservers; +using internal::PeerUpdateHandler; using SmallUpdatesList = QVector; NeverFreedPointer SmallUpdates; @@ -41,34 +39,25 @@ using AllUpdatesList = QMap; NeverFreedPointer AllUpdates; void StartCallback() { - PeerUpdateObservers.makeIfNull(); SmallUpdates.makeIfNull(); AllUpdates.makeIfNull(); } void FinishCallback() { - PeerUpdateObservers.clear(); SmallUpdates.clear(); AllUpdates.clear(); } -void UnregisterCallback(int connectionIndex) { - t_assert(!PeerUpdateObservers.isNull()); - unregisterObserver(*PeerUpdateObservers, connectionIndex); -} -ObservedEventRegistrator creator(StartCallback, FinishCallback, UnregisterCallback); - -bool Started() { - return !PeerUpdateObservers.isNull(); -} +ObservedEventRegistrator creator(StartCallback, FinishCallback); } // namespace +namespace internal { + ConnectionId plainRegisterPeerObserver(PeerUpdateFlags events, PeerUpdateHandler &&handler) { - t_assert(Started()); - auto connectionId = registerObserver(creator.event(), *PeerUpdateObservers - , events, std_::forward(handler)); - return connectionId; + return creator.registerObserver(events, std_::forward(handler)); } +} // namespace internal + void mergePeerUpdate(PeerUpdate &mergeTo, const PeerUpdate &mergeFrom) { if (!(mergeTo.flags & PeerUpdateFlag::NameChanged)) { if (mergeFrom.flags & PeerUpdateFlag::NameChanged) { @@ -79,53 +68,51 @@ void mergePeerUpdate(PeerUpdate &mergeTo, const PeerUpdate &mergeFrom) { mergeTo.flags |= mergeFrom.flags; } -} // namespace internal - void peerUpdatedDelayed(const PeerUpdate &update) { - t_assert(internal::Started()); + t_assert(creator.started()); - int existingUpdatesCount = internal::SmallUpdates->size(); + int existingUpdatesCount = SmallUpdates->size(); for (int i = 0; i < existingUpdatesCount; ++i) { - auto &existingUpdate = (*internal::SmallUpdates)[i]; + auto &existingUpdate = (*SmallUpdates)[i]; if (existingUpdate.peer == update.peer) { - internal::mergePeerUpdate(existingUpdate, update); + mergePeerUpdate(existingUpdate, update); return; } } - if (internal::AllUpdates->isEmpty()) { + if (AllUpdates->isEmpty()) { if (existingUpdatesCount < 5) { - internal::SmallUpdates->push_back(update); + SmallUpdates->push_back(update); } else { - internal::AllUpdates->insert(update.peer, update); + AllUpdates->insert(update.peer, update); } } else { - auto it = internal::AllUpdates->find(update.peer); - if (it != internal::AllUpdates->cend()) { - internal::mergePeerUpdate(it.value(), update); + auto it = AllUpdates->find(update.peer); + if (it != AllUpdates->cend()) { + mergePeerUpdate(it.value(), update); return; } - internal::AllUpdates->insert(update.peer, update); + AllUpdates->insert(update.peer, update); } } void peerUpdatedSendDelayed() { App::emitPeerUpdated(); - t_assert(internal::Started()); + t_assert(creator.started()); - if (internal::SmallUpdates->isEmpty()) return; + if (SmallUpdates->isEmpty()) return; - auto smallList = createAndSwap(*internal::SmallUpdates); - auto allList = createAndSwap(*internal::AllUpdates); + auto smallList = createAndSwap(*SmallUpdates); + auto allList = createAndSwap(*AllUpdates); for_const (auto &update, smallList) { - notifyObservers(*internal::PeerUpdateObservers, update.flags, update); + creator.notify(update.flags, update); } for_const (auto &update, allList) { - notifyObservers(*internal::PeerUpdateObservers, update.flags, update); + creator.notify(update.flags, update); } - if (internal::SmallUpdates->isEmpty()) { - std::swap(smallList, *internal::SmallUpdates); - internal::SmallUpdates->resize(0); + if (SmallUpdates->isEmpty()) { + std::swap(smallList, *SmallUpdates); + SmallUpdates->resize(0); } } diff --git a/Telegram/SourceFiles/ui/filedialog.cpp b/Telegram/SourceFiles/ui/filedialog.cpp index aaf02498c..28e065adf 100644 --- a/Telegram/SourceFiles/ui/filedialog.cpp +++ b/Telegram/SourceFiles/ui/filedialog.cpp @@ -240,9 +240,6 @@ namespace { using internal::QueryUpdateHandler; -using QueryObserversList = Notify::ObserversList; -NeverFreedPointer QueryUpdateObservers; - struct Query { enum class Type { ReadFile, @@ -268,55 +265,51 @@ using QueryList = QList; NeverFreedPointer Queries; void StartCallback() { - QueryUpdateObservers.makeIfNull(); Queries.makeIfNull(); } + void FinishCallback() { - QueryUpdateObservers.clear(); Queries.clear(); } -void UnregisterCallback(int connectionIndex) { - t_assert(!QueryUpdateObservers.isNull()); - Notify::unregisterObserver(*QueryUpdateObservers, connectionIndex); -} -Notify::ObservedEventRegistrator creator(StartCallback, FinishCallback, UnregisterCallback); -bool Started() { - return !QueryUpdateObservers.isNull(); -} +Notify::SimpleObservedEventRegistrator creator(StartCallback, FinishCallback); } // namespace QueryId queryReadFile(const QString &caption, const QString &filter) { - t_assert(Started()); + t_assert(creator.started()); + Queries->push_back(Query(Query::Type::ReadFile, caption, filter)); Global::RefHandleFileDialogQueue().call(); return Queries->back().id; } QueryId queryReadFiles(const QString &caption, const QString &filter) { - t_assert(Started()); + t_assert(creator.started()); + Queries->push_back(Query(Query::Type::ReadFiles, caption, filter)); Global::RefHandleFileDialogQueue().call(); return Queries->back().id; } QueryId queryWriteFile(const QString &caption, const QString &filter, const QString &filePath) { - t_assert(Started()); + t_assert(creator.started()); + Queries->push_back(Query(Query::Type::WriteFile, caption, filter, filePath)); Global::RefHandleFileDialogQueue().call(); return Queries->back().id; } QueryId queryReadFolder(const QString &caption) { - t_assert(Started()); + t_assert(creator.started()); + Queries->push_back(Query(Query::Type::ReadFolder, caption)); Global::RefHandleFileDialogQueue().call(); return Queries->back().id; } bool processQuery() { - if (!Started() || !Global::started() || Queries->isEmpty()) return false; + if (!creator.started() || !Global::started() || Queries->isEmpty()) return false; auto query = Queries->front(); Queries->pop_front(); @@ -364,19 +357,16 @@ bool processQuery() { } // No one know what happened during filedialogGet*() call in the event loop. - if (!Started() || !Global::started()) return false; + if (!creator.started() || !Global::started()) return false; - Notify::notifyObservers(*QueryUpdateObservers, Notify::UniversalFlag, update); + creator.notify(update); return true; } namespace internal { Notify::ConnectionId plainRegisterObserver(QueryUpdateHandler &&handler) { - t_assert(Started()); - auto connectionId = Notify::registerObserver(creator.event(), *QueryUpdateObservers - , Notify::UniversalFlag, std_::forward(handler)); - return connectionId; + return creator.registerObserver(std_::forward(handler)); } } // namespace internal From ab59ef8498994b4ddb1ad9e621776c53d19f5e5f Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 26 May 2016 18:31:20 +0300 Subject: [PATCH 13/30] FlatLabel now can allow to select text and copy to clipboard. Used for peer name in the new profile cover widget. --- Telegram/Resources/basic.style | 3 +- Telegram/Resources/basic_types.style | 4 +- Telegram/SourceFiles/boxes/aboutbox.h | 1 + Telegram/SourceFiles/boxes/confirmbox.h | 1 + Telegram/SourceFiles/historywidget.cpp | 14 +- Telegram/SourceFiles/historywidget.h | 2 +- Telegram/SourceFiles/intro/introphone.h | 2 +- Telegram/SourceFiles/intro/introstart.h | 1 + Telegram/SourceFiles/profile/profile.style | 10 +- .../SourceFiles/profile/profile_cover.cpp | 21 +- Telegram/SourceFiles/profile/profile_cover.h | 4 +- Telegram/SourceFiles/stdafx.h | 1 - Telegram/SourceFiles/ui/flatlabel.cpp | 437 ++++++++++++++++-- Telegram/SourceFiles/ui/flatlabel.h | 84 +++- Telegram/SourceFiles/ui/text/text.cpp | 12 +- Telegram/SourceFiles/ui/text/text.h | 11 +- 16 files changed, 540 insertions(+), 68 deletions(-) diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style index 60adbe02f..cf2a840a5 100644 --- a/Telegram/Resources/basic.style +++ b/Telegram/Resources/basic.style @@ -72,9 +72,10 @@ overBg: #edf2f5; labelDefFlat: flatLabel { font: font(fsize); - minWidth: 100px; width: 0px; + maxHeight: 0px; align: align(left); + textFg: windowTextFg; } boxBg: white; diff --git a/Telegram/Resources/basic_types.style b/Telegram/Resources/basic_types.style index 750a52cea..22e79f521 100644 --- a/Telegram/Resources/basic_types.style +++ b/Telegram/Resources/basic_types.style @@ -210,9 +210,11 @@ slider { flatLabel { font: font; - minWidth: pixels; + margin: margins; width: pixels; align: align; + textFg: color; + maxHeight: pixels; } switcher { diff --git a/Telegram/SourceFiles/boxes/aboutbox.h b/Telegram/SourceFiles/boxes/aboutbox.h index ceccef089..45af6b009 100644 --- a/Telegram/SourceFiles/boxes/aboutbox.h +++ b/Telegram/SourceFiles/boxes/aboutbox.h @@ -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 diff --git a/Telegram/SourceFiles/boxes/confirmbox.h b/Telegram/SourceFiles/boxes/confirmbox.h index 097e6f436..4af3c49af 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.h +++ b/Telegram/SourceFiles/boxes/confirmbox.h @@ -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 { diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 1206f2d97..a81ccd818 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -654,7 +654,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()); } @@ -664,7 +664,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); @@ -711,7 +711,7 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt if (!_dragItem) { _dragAction = NoDrag; } else if (_dragAction == NoDrag) { - _dragItem = 0; + _dragItem = nullptr; } } @@ -902,7 +902,7 @@ void HistoryInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton but } _dragAction = NoDrag; _dragItem = 0; - _dragSelType = TextSelectLetters; + _dragSelType = TextSelectType::Letters; _widget->noSelectingScroll(); _widget->updateTopBarSelection(); } @@ -918,13 +918,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 }; @@ -1815,7 +1815,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); diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index f9e82b830..4dd4bc49c 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -204,7 +204,7 @@ private: Selecting = 0x04, }; DragAction _dragAction = NoDrag; - TextSelectType _dragSelType = TextSelectLetters; + TextSelectType _dragSelType = TextSelectType::Letters; QPoint _dragStartPos, _dragPos; HistoryItem *_dragItem = nullptr; HistoryCursorState _dragCursorState = HistoryDefaultCursorState; diff --git a/Telegram/SourceFiles/intro/introphone.h b/Telegram/SourceFiles/intro/introphone.h index 8e3bc5e96..8c53bc6b4 100644 --- a/Telegram/SourceFiles/intro/introphone.h +++ b/Telegram/SourceFiles/intro/introphone.h @@ -20,9 +20,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include #include "ui/flatbutton.h" #include "ui/countryinput.h" +#include "ui/flatlabel.h" #include "intro/introwidget.h" class IntroPhone final : public IntroStep { diff --git a/Telegram/SourceFiles/intro/introstart.h b/Telegram/SourceFiles/intro/introstart.h index 8bd86075d..c9665476e 100644 --- a/Telegram/SourceFiles/intro/introstart.h +++ b/Telegram/SourceFiles/intro/introstart.h @@ -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: diff --git a/Telegram/SourceFiles/profile/profile.style b/Telegram/SourceFiles/profile/profile.style index ad27e820b..756d40781 100644 --- a/Telegram/SourceFiles/profile/profile.style +++ b/Telegram/SourceFiles/profile/profile.style @@ -38,8 +38,14 @@ profilePhotoSize: 112px; profilePhotoLeft: 35px; profileNameLeft: 26px; profileNameTop: 9px; -profileNameFont: font(16px); -profileNameFg: windowTextFg; +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; diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp index e9bf3666d..d1bc03036 100644 --- a/Telegram/SourceFiles/profile/profile_cover.cpp +++ b/Telegram/SourceFiles/profile/profile_cover.cpp @@ -129,9 +129,13 @@ CoverWidget::CoverWidget(QWidget *parent, PeerData *peer) : TWidget(parent) , _peerChat(peer->asChat()) , _peerChannel(peer->asChannel()) , _peerMegagroup(peer->isMegagroup() ? _peerChannel : nullptr) -, _photoButton(this, peer) { +, _photoButton(this, peer) +, _name(this, QString(), st::profileNameLabel) { setAttribute(Qt::WA_OpaquePaintEvent); + _name.setSelectable(true); + _name.setContextCopyText(lang(lng_profile_copy_fullname)); + auto observeEvents = ButtonsUpdateFlags | Notify::PeerUpdateFlag::NameChanged; Notify::registerPeerObserver(observeEvents, this, &CoverWidget::notifyPeerUpdated); FileDialog::registerObserver(this, &CoverWidget::notifyFileQueryUpdated); @@ -161,7 +165,13 @@ void CoverWidget::resizeToWidth(int newWidth) { _photoButton->moveToLeft(st::profilePhotoLeft, newHeight); int infoLeft = _photoButton->x() + _photoButton->width(); - _namePosition = QPoint(infoLeft + st::profileNameLeft, _photoButton->y() + st::profileNameTop); + int nameLeft = infoLeft + st::profileNameLeft - st::profileNameLabel.margin.left(); + int nameTop = _photoButton->y() + st::profileNameTop - st::profileNameLabel.margin.top(); + _name.moveToLeft(nameLeft, nameTop); + int nameWidth = width() - infoLeft - st::profileNameLeft - st::profileButtonSkip; + nameWidth += st::profileNameLabel.margin.left() + st::profileNameLabel.margin.right(); + _name.resizeToWidth(nameWidth); + _statusPosition = QPoint(infoLeft + st::profileStatusLeft, _photoButton->y() + st::profileStatusTop); int buttonLeft = st::profilePhotoLeft + _photoButton->width() + st::profileButtonLeft; @@ -187,11 +197,6 @@ void CoverWidget::paintEvent(QPaintEvent *e) { p.fillRect(e->rect(), st::profileBg); - int availWidth = width() - _namePosition.x() - _photoButton->x(); - p.setFont(st::profileNameFont); - p.setPen(st::profileNameFg); - _nameText.drawLeftElided(p, _namePosition.x(), _namePosition.y(), availWidth, width()); - p.setFont(st::profileStatusFont); p.setPen(st::profileStatusFg); p.drawTextLeft(_statusPosition.x(), _statusPosition.y(), width(), _statusText); @@ -220,7 +225,7 @@ void CoverWidget::notifyPeerUpdated(const Notify::PeerUpdate &update) { } void CoverWidget::refreshNameText() { - _nameText.setText(st::profileNameFont, App::peerName(_peer)); + _name.setText(App::peerName(_peer)); update(); } diff --git a/Telegram/SourceFiles/profile/profile_cover.h b/Telegram/SourceFiles/profile/profile_cover.h index da3a5dcd5..d0392a451 100644 --- a/Telegram/SourceFiles/profile/profile_cover.h +++ b/Telegram/SourceFiles/profile/profile_cover.h @@ -22,6 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "core/observer.h" #include "ui/filedialog.h" +#include "ui/flatlabel.h" namespace Ui { class RoundButton; @@ -87,8 +88,7 @@ private: // Cover content ChildWidget _photoButton; - QPoint _namePosition; - Text _nameText; + FlatLabel _name; QPoint _statusPosition; QString _statusText; diff --git a/Telegram/SourceFiles/stdafx.h b/Telegram/SourceFiles/stdafx.h index 1d9f12f90..d60e8f486 100644 --- a/Telegram/SourceFiles/stdafx.h +++ b/Telegram/SourceFiles/stdafx.h @@ -68,7 +68,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "ui/scrollarea.h" #include "ui/images.h" #include "ui/text/text.h" -#include "ui/flatlabel.h" #include "app.h" diff --git a/Telegram/SourceFiles/ui/flatlabel.cpp b/Telegram/SourceFiles/ui/flatlabel.cpp index d170ee92d..57f2e272d 100644 --- a/Telegram/SourceFiles/ui/flatlabel.cpp +++ b/Telegram/SourceFiles/ui/flatlabel.cpp @@ -21,6 +21,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "ui/flatlabel.h" +#include "mainwindow.h" +#include "lang.h" namespace { TextParseOptions _labelOptions = { @@ -34,30 +36,59 @@ namespace { FlatLabel::FlatLabel(QWidget *parent, const QString &text, const style::flatLabel &st, const style::textStyle &tst) : TWidget(parent), _text(st.width ? st.width : QFIXED_MAX), _st(st), _tst(tst), _opacity(1) { setRichText(text); + _trippleClickTimer.setSingleShot(true); + + _touchSelectTimer.setSingleShot(true); + connect(&_touchSelectTimer, SIGNAL(timeout()), this, SLOT(onTouchSelect())); } void FlatLabel::setText(const QString &text) { textstyleSet(&_tst); _text.setText(_st.font, text, _labelOptions); - int32 w = _st.width ? _st.width : _text.maxWidth(), h = _text.countHeight(w); + refreshSize(); textstyleRestore(); - resize(w, h); + setMouseTracking(_selectable || _text.hasLinks()); } void FlatLabel::setRichText(const QString &text) { textstyleSet(&_tst); _text.setRichText(_st.font, text, _labelOptions); - int32 w = _st.width ? _st.width : _text.maxWidth(), h = _text.countHeight(w); + refreshSize(); textstyleRestore(); - resize(w, h); - setMouseTracking(_text.hasLinks()); + setMouseTracking(_selectable || _text.hasLinks()); +} + +void FlatLabel::setSelectable(bool selectable) { + _selectable = selectable; + setMouseTracking(_selectable || _text.hasLinks()); +} + +void FlatLabel::setContextCopyText(const QString ©Text) { + _contextCopyText = copyText; } void FlatLabel::resizeToWidth(int32 width) { textstyleSet(&_tst); - int32 w = width, h = _text.countHeight(w); + _allowedWidth = width; + refreshSize(); textstyleRestore(); - resize(w, h); +} + +int FlatLabel::countTextWidth() const { + return _allowedWidth ? (_allowedWidth - _st.margin.left() - _st.margin.right()) : (_st.width ? _st.width : _text.maxWidth()); +} + +int FlatLabel::countTextHeight(int textWidth) { + _fullTextHeight = _text.countHeight(textWidth); + return _st.maxHeight ? qMin(_fullTextHeight, _st.maxHeight) : _fullTextHeight; +} + +void FlatLabel::refreshSize() { + int textWidth = countTextWidth(); + int textHeight = countTextHeight(textWidth); + int fullWidth = _st.margin.left() + textWidth + _st.margin.right(); + int fullHeight = _st.margin.top() + textHeight + _st.margin.bottom(); + resize(fullWidth, fullHeight); } void FlatLabel::setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk) { @@ -66,34 +97,301 @@ void FlatLabel::setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk) { void FlatLabel::mouseMoveEvent(QMouseEvent *e) { _lastMousePos = e->globalPos(); - updateHover(); + dragActionUpdate(); } void FlatLabel::mousePressEvent(QMouseEvent *e) { - _lastMousePos = e->globalPos(); - updateHover(); + if (_contextMenu) { + e->accept(); + return; // ignore mouse press, that was hiding context menu + } + dragActionStart(e->globalPos(), e->button()); +} + +Text::StateResult FlatLabel::dragActionStart(const QPoint &p, Qt::MouseButton button) { + _lastMousePos = p; + auto state = dragActionUpdate(); + + if (button != Qt::LeftButton) return state; + ClickHandler::pressed(); + _dragAction = NoDrag; + _dragWasInactive = App::wnd()->inactivePress(); + if (_dragWasInactive) App::wnd()->inactivePress(false); + + if (ClickHandler::getPressed()) { + _dragAction = PrepareDrag; + } + if (!_selectable || _dragAction != NoDrag) { + return state; + } + + if (_trippleClickTimer.isActive() && (_lastMousePos - _trippleClickPoint).manhattanLength() < QApplication::startDragDistance()) { + if (state.uponSymbol) { + _selection = { state.symbol, state.symbol }; + _savedSelection = { 0, 0 }; + _dragSymbol = state.symbol; + _dragAction = Selecting; + _selectionType = TextSelectType::Paragraphs; + updateHover(state); + _trippleClickTimer.start(QApplication::doubleClickInterval()); + update(); + } + } + if (_selectionType != TextSelectType::Paragraphs) { + _dragSymbol = state.symbol; + bool uponSelected = state.uponSymbol; + if (uponSelected) { + if (_dragSymbol < _selection.from || _dragSymbol >= _selection.to) { + uponSelected = false; + } + } + if (uponSelected) { + _dragAction = PrepareDrag; // start text drag + } else if (!_dragWasInactive) { + if (state.afterSymbol) ++_dragSymbol; + _selection = { _dragSymbol, _dragSymbol }; + _savedSelection = { 0, 0 }; + _dragAction = Selecting; + update(); + } + } + return state; +} + +Text::StateResult FlatLabel::dragActionFinish(const QPoint &p, Qt::MouseButton button) { + _lastMousePos = p; + auto state = dragActionUpdate(); + + ClickHandlerPtr activated = ClickHandler::unpressed(); + if (_dragAction == Dragging) { + activated.clear(); + } else if (_dragAction == PrepareDrag) { + _selection = { 0, 0 }; + _savedSelection = { 0, 0 }; + update(); + } + _dragAction = NoDrag; + _selectionType = TextSelectType::Letters; + + if (activated) { + App::activateClickHandler(activated, button); + } + return state; } void FlatLabel::mouseReleaseEvent(QMouseEvent *e) { - _lastMousePos = e->globalPos(); - updateHover(); - if (ClickHandlerPtr activated = ClickHandler::unpressed()) { - App::activateClickHandler(activated, e->button()); + dragActionFinish(e->globalPos(), e->button()); + if (!rect().contains(e->pos())) { + leaveEvent(e); + } +} + +void FlatLabel::mouseDoubleClickEvent(QMouseEvent *e) { + auto state = dragActionStart(e->globalPos(), e->button()); + if (((_dragAction == Selecting) || (_dragAction == NoDrag)) && _selectionType == TextSelectType::Letters) { + if (state.uponSymbol) { + _dragSymbol = state.symbol; + _selectionType = TextSelectType::Words; + if (_dragAction == NoDrag) { + _dragAction = Selecting; + _selection = { state.symbol, state.symbol }; + _savedSelection = { 0, 0 }; + } + mouseMoveEvent(e); + + _trippleClickPoint = e->globalPos(); + _trippleClickTimer.start(QApplication::doubleClickInterval()); + } } } void FlatLabel::enterEvent(QEvent *e) { _lastMousePos = QCursor::pos(); - updateHover(); + dragActionUpdate(); } void FlatLabel::leaveEvent(QEvent *e) { ClickHandler::clearActive(this); } +void FlatLabel::focusOutEvent(QFocusEvent *e) { + if (!_selection.empty()) { + _savedSelection = _selection; + _selection = { 0, 0 }; + update(); + } +} + +void FlatLabel::focusInEvent(QFocusEvent *e) { + if (!_savedSelection.empty()) { + _selection = _savedSelection; + _savedSelection = { 0, 0 }; + update(); + } +} + +void FlatLabel::keyPressEvent(QKeyEvent *e) { + e->ignore(); + if (e->key() == Qt::Key_Copy || (e->key() == Qt::Key_C && e->modifiers().testFlag(Qt::ControlModifier))) { + if (!_selection.empty()) { + onCopySelectedText(); + e->accept(); + } + } +} + +void FlatLabel::contextMenuEvent(QContextMenuEvent *e) { + showContextMenu(e, ContextMenuReason::FromEvent); +} + +bool FlatLabel::event(QEvent *e) { + if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) { + QTouchEvent *ev = static_cast(e); + if (ev->device()->type() == QTouchDevice::TouchScreen) { + touchEvent(ev); + return true; + } + } + return QWidget::event(e); +} + +void FlatLabel::touchEvent(QTouchEvent *e) { + const Qt::TouchPointStates &states(e->touchPointStates()); + if (e->type() == QEvent::TouchCancel) { // cancel + if (!_touchInProgress) return; + _touchInProgress = false; + _touchSelectTimer.stop(); + _touchSelect = false; + _dragAction = NoDrag; + return; + } + + if (!e->touchPoints().isEmpty()) { + _touchPrevPos = _touchPos; + _touchPos = e->touchPoints().cbegin()->screenPos().toPoint(); + } + + switch (e->type()) { + case QEvent::TouchBegin: + if (_contextMenu) { + e->accept(); + return; // ignore mouse press, that was hiding context menu + } + if (_touchInProgress) return; + if (e->touchPoints().isEmpty()) return; + + _touchInProgress = true; + _touchSelectTimer.start(QApplication::startDragTime()); + _touchSelect = false; + _touchStart = _touchPrevPos = _touchPos; + break; + + case QEvent::TouchUpdate: + if (!_touchInProgress) return; + if (_touchSelect) { + _lastMousePos = _touchPos; + dragActionUpdate(); + } + break; + + case QEvent::TouchEnd: + if (!_touchInProgress) return; + _touchInProgress = false; + if (_touchSelect) { + dragActionFinish(_touchPos, Qt::RightButton); + QContextMenuEvent contextMenu(QContextMenuEvent::Mouse, mapFromGlobal(_touchPos), _touchPos); + showContextMenu(&contextMenu, ContextMenuReason::FromTouch); + } else { // one short tap -- like mouse click + dragActionStart(_touchPos, Qt::LeftButton); + dragActionFinish(_touchPos, Qt::LeftButton); + } + _touchSelectTimer.stop(); + _touchSelect = false; + break; + } +} + +void FlatLabel::showContextMenu(QContextMenuEvent *e, ContextMenuReason reason) { + if (_contextMenu) { + _contextMenu->deleteLater(); + _contextMenu = nullptr; + } + + if (e->reason() == QContextMenuEvent::Mouse) { + _lastMousePos = e->globalPos(); + } else { + _lastMousePos = QCursor::pos(); + } + auto state = dragActionUpdate(); + + bool hasSelection = !_selection.empty(); + bool uponSelection = state.uponSymbol && (state.symbol >= _selection.from) && (state.symbol < _selection.to); + bool fullSelection = _text.isFullSelection(_selection); + if (reason == ContextMenuReason::FromTouch && hasSelection && !uponSelection) { + uponSelection = hasSelection; + } + + _contextMenu = new PopupMenu(); + + _contextMenuClickHandler = ClickHandler::getActive(); + + if (fullSelection) { + _contextMenu->addAction(contextCopyText(), this, SLOT(onCopyContextText()))->setEnabled(true); + } else if (uponSelection) { + _contextMenu->addAction(lang(lng_context_copy_selected), this, SLOT(onCopySelectedText()))->setEnabled(true); + } else if (!hasSelection) { + _contextMenu->addAction(contextCopyText(), this, SLOT(onCopyContextText()))->setEnabled(true); + } + + QString linkCopyToClipboardText = _contextMenuClickHandler ? _contextMenuClickHandler->copyToClipboardContextItemText() : QString(); + if (!linkCopyToClipboardText.isEmpty()) { + _contextMenu->addAction(linkCopyToClipboardText, this, SLOT(onCopyContextUrl()))->setEnabled(true); + } + + if (_contextMenu->actions().isEmpty()) { + delete _contextMenu; + _contextMenu = nullptr; + } else { + connect(_contextMenu, SIGNAL(destroyed(QObject*)), this, SLOT(onContextMenuDestroy(QObject*))); + _contextMenu->popup(e->globalPos()); + e->accept(); + } +} + +QString FlatLabel::contextCopyText() const { + return _contextCopyText.isEmpty() ? lang(lng_context_copy_text) : _contextCopyText; +} + +void FlatLabel::onCopySelectedText() { + auto selection = _selection.empty() ? (_contextMenu ? _savedSelection : _selection) : _selection; + if (!selection.empty()) { + QApplication::clipboard()->setText(_text.originalText(selection, ExpandLinksAll)); + } +} + +void FlatLabel::onCopyContextText() { + QApplication::clipboard()->setText(_text.originalText({ 0, 0xFFFF }, ExpandLinksAll)); +} + +void FlatLabel::onCopyContextUrl() { + if (_contextMenuClickHandler) { + _contextMenuClickHandler->copyToClipboard(); + } +} + +void FlatLabel::onTouchSelect() { + _touchSelect = true; + dragActionStart(_touchPos, Qt::LeftButton); +} + +void FlatLabel::onContextMenuDestroy(QObject *obj) { + if (obj == _contextMenu) { + _contextMenu = nullptr; + } +} + void FlatLabel::clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) { - setCursor(active ? style::cur_pointer : style::cur_default); update(); } @@ -101,21 +399,97 @@ void FlatLabel::clickHandlerPressedChanged(const ClickHandlerPtr &action, bool a update(); } -void FlatLabel::updateLink() { - _lastMousePos = QCursor::pos(); - updateHover(); +Text::StateResult FlatLabel::dragActionUpdate() { + QPoint m(mapFromGlobal(_lastMousePos)); + LOG(("DRAG ACTION UPDATE: %1 %2").arg(m.x()).arg(m.y())); + auto state = getTextState(m); + updateHover(state); + return state; } -void FlatLabel::updateHover() { - QPoint m(mapFromGlobal(_lastMousePos)); +void FlatLabel::updateHover(const Text::StateResult &state) { + bool lnkChanged = ClickHandler::setActive(state.link, this); + + if (!_selectable) { + refreshCursor(state.uponSymbol); + return; + } + + Qt::CursorShape cur = style::cur_default; + if (_dragAction == NoDrag) { + if (state.link) { + cur = style::cur_pointer; + } else if (state.uponSymbol) { + cur = style::cur_text; + } + } else { + if (_dragAction == Selecting) { + uint16 second = state.symbol; + if (state.afterSymbol && _selectionType == TextSelectType::Letters) { + ++second; + } + auto selection = _text.adjustSelection({ qMin(second, _dragSymbol), qMax(second, _dragSymbol) }, _selectionType); + if (_selection != selection) { + _selection = selection; + _savedSelection = { 0, 0 }; + setFocus(); + update(); + } + } else if (_dragAction == Dragging) { + } + + if (ClickHandler::getPressed()) { + cur = style::cur_pointer; + } else if (_dragAction == Selecting) { + cur = style::cur_text; + } + } + if (_dragAction == Selecting) { +// checkSelectingScroll(); + } else { +// noSelectingScroll(); + } + + if (_dragAction == NoDrag && (lnkChanged || cur != _cursor)) { + setCursor(_cursor = cur); + } +} + +void FlatLabel::refreshCursor(bool uponSymbol) { + if (_dragAction != NoDrag) { + return; + } + bool needTextCursor = _selectable && uponSymbol; + style::cursor newCursor = needTextCursor ? style::cur_text : style::cur_default; + if (ClickHandler::getActive()) { + newCursor = style::cur_pointer; + } + if (newCursor != _cursor) { + _cursor = newCursor; + setCursor(_cursor); + } +} + +Text::StateResult FlatLabel::getTextState(const QPoint &m) const { + Text::StateRequestElided request; + request.align = _st.align; + if (_selectable) { + request.flags |= Text::StateRequest::Flag::LookupSymbol; + } + int textWidth = width() - _st.margin.left() - _st.margin.right(); textstyleSet(&_tst); - Text::StateRequest request; - request.align = _st.align; - auto state = _text.getState(m.x(), m.y(), width(), request); + Text::StateResult state; + if (_st.maxHeight && _st.maxHeight < _fullTextHeight) { + auto lineHeight = qMax(_tst.lineHeight, _st.font->height); + request.lines = qMax(_st.maxHeight / lineHeight, 1); + state = _text.getStateElided(m.x() - _st.margin.left(), m.y() - _st.margin.top(), textWidth, request); + } else { + state = _text.getState(m.x() - _st.margin.left(), m.y() - _st.margin.top(), textWidth, request); + } textstyleRestore(); - ClickHandler::setActive(state.link, this); + return state; } void FlatLabel::setOpacity(float64 o) { @@ -124,9 +498,18 @@ void FlatLabel::setOpacity(float64 o) { } void FlatLabel::paintEvent(QPaintEvent *e) { - QPainter p(this); + Painter p(this); p.setOpacity(_opacity); + p.setPen(_st.textFg); textstyleSet(&_tst); - _text.draw(p, 0, 0, width(), _st.align, e->rect().y(), e->rect().bottom()); + int textWidth = width() - _st.margin.left() - _st.margin.right(); + auto selection = _selection.empty() ? (_contextMenu ? _savedSelection : _selection) : _selection; + if (_st.maxHeight && _st.maxHeight < _fullTextHeight) { + auto lineHeight = qMax(_tst.lineHeight, _st.font->height); + auto lines = qMax(_st.maxHeight / lineHeight, 1); + _text.drawElided(p, _st.margin.left(), _st.margin.top(), textWidth, lines, _st.align, e->rect().y(), e->rect().bottom(), 0, false, selection); + } else { + _text.draw(p, _st.margin.left(), _st.margin.top(), textWidth, _st.align, e->rect().y(), e->rect().bottom(), selection); + } textstyleRestore(); } diff --git a/Telegram/SourceFiles/ui/flatlabel.h b/Telegram/SourceFiles/ui/flatlabel.h index a881bd2ac..159ddf6a0 100644 --- a/Telegram/SourceFiles/ui/flatlabel.h +++ b/Telegram/SourceFiles/ui/flatlabel.h @@ -24,20 +24,14 @@ class FlatLabel : public TWidget, public ClickHandlerHost { Q_OBJECT public: - FlatLabel(QWidget *parent, const QString &text, const style::flatLabel &st = st::labelDefFlat, const style::textStyle &tst = st::defaultTextStyle); - void paintEvent(QPaintEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void mousePressEvent(QMouseEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; - void enterEvent(QEvent *e) override; - void leaveEvent(QEvent *e) override; - void updateLink(); void setOpacity(float64 o); void setText(const QString &text); void setRichText(const QString &text); + void setSelectable(bool selectable); + void setContextCopyText(const QString ©Text); void resizeToWidth(int32 width); @@ -47,15 +41,85 @@ public: void clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &action, bool pressed) override; -private: +protected: + void paintEvent(QPaintEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void mouseDoubleClickEvent(QMouseEvent *e) override; + void enterEvent(QEvent *e) override; + void leaveEvent(QEvent *e) override; + void focusOutEvent(QFocusEvent *e) override; + void focusInEvent(QFocusEvent *e) override; + void keyPressEvent(QKeyEvent *e) override; + void contextMenuEvent(QContextMenuEvent *e) override; + bool event(QEvent *e) override; // calls touchEvent when necessary + void touchEvent(QTouchEvent *e); - void updateHover(); +private slots: + void onCopySelectedText(); + void onCopyContextText(); + void onCopyContextUrl(); + + void onTouchSelect(); + void onContextMenuDestroy(QObject *obj); + +private: + Text::StateResult dragActionUpdate(); + Text::StateResult dragActionStart(const QPoint &p, Qt::MouseButton button); + Text::StateResult dragActionFinish(const QPoint &p, Qt::MouseButton button); + void updateHover(const Text::StateResult &state); + Text::StateResult getTextState(const QPoint &m) const; + void refreshCursor(bool uponSymbol); + + int countTextWidth() const; + int countTextHeight(int textWidth); + void refreshSize(); + + enum class ContextMenuReason { + FromEvent, + FromTouch, + }; + void showContextMenu(QContextMenuEvent *e, ContextMenuReason reason); + QString contextCopyText() const; Text _text; style::flatLabel _st; style::textStyle _tst; float64 _opacity; + int _allowedWidth = 0; + int _fullTextHeight = 0; + + style::cursor _cursor = style::cur_default; + bool _selectable = false; + TextSelection _selection, _savedSelection; + TextSelectType _selectionType = TextSelectType::Letters; + + enum DragAction { + NoDrag = 0x00, + PrepareDrag = 0x01, + Dragging = 0x02, + PrepareSelect = 0x03, + Selecting = 0x04, + }; + DragAction _dragAction = NoDrag; + uint16 _dragSymbol = 0; + bool _dragWasInactive = false; + QPoint _lastMousePos; + QPoint _trippleClickPoint; + QTimer _trippleClickTimer; + + PopupMenu *_contextMenu = nullptr; + ClickHandlerPtr _contextMenuClickHandler; + QString _contextCopyText; + + // text selection and context menu by touch support (at least Windows Surface tablets) + bool _touchSelect = false; + bool _touchInProgress = false; + QPoint _touchStart, _touchPrevPos, _touchPos; + QTimer _touchSelectTimer; + }; diff --git a/Telegram/SourceFiles/ui/text/text.cpp b/Telegram/SourceFiles/ui/text/text.cpp index f828a2f28..18b42542c 100644 --- a/Telegram/SourceFiles/ui/text/text.cpp +++ b/Telegram/SourceFiles/ui/text/text.cpp @@ -1181,7 +1181,13 @@ public: bool drawLine(uint16 _lineEnd, const Text::TextBlocks::const_iterator &_endBlockIter, const Text::TextBlocks::const_iterator &_end) { _yDelta = (_lineHeight - _fontHeight) / 2; if (_yTo >= 0 && _y + _yDelta >= _yTo) return false; - if (_y + _yDelta + _fontHeight <= _yFrom) return true; + if (_y + _yDelta + _fontHeight <= _yFrom) { + if (_lookupSymbol) { + _lookupResult.symbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart; + _lookupResult.afterSymbol = (_lineEnd > _lineStart) ? true : false; + } + return true; + } uint16 trimmedLineEnd = _lineEnd; for (; trimmedLineEnd > _lineStart; --trimmedLineEnd) { @@ -2898,7 +2904,7 @@ TextSelection Text::adjustSelection(TextSelection selection, TextSelectType sele uint16 from = selection.from, to = selection.to; if (from < _text.size() && from <= to) { if (to > _text.size()) to = _text.size(); - if (selectType == TextSelectParagraphs) { + if (selectType == TextSelectType::Paragraphs) { if (!chIsParagraphSeparator(_text.at(from))) { while (from > 0 && !chIsParagraphSeparator(_text.at(from - 1))) { --from; @@ -2913,7 +2919,7 @@ TextSelection Text::adjustSelection(TextSelection selection, TextSelectType sele } } } - } else if (selectType == TextSelectWords) { + } else if (selectType == TextSelectType::Words) { if (!chIsWordSeparator(_text.at(from))) { while (from > 0 && !chIsWordSeparator(_text.at(from - 1))) { --from; diff --git a/Telegram/SourceFiles/ui/text/text.h b/Telegram/SourceFiles/ui/text/text.h index d9ea6e798..c26189a9e 100644 --- a/Telegram/SourceFiles/ui/text/text.h +++ b/Telegram/SourceFiles/ui/text/text.h @@ -53,10 +53,10 @@ struct TextParseOptions { }; extern const TextParseOptions _defaultOptions, _textPlainOptions; -enum TextSelectType { - TextSelectLetters = 0x01, - TextSelectWords = 0x02, - TextSelectParagraphs = 0x03, +enum class TextSelectType { + Letters = 0x01, + Words = 0x02, + Paragraphs = 0x03, }; struct TextSelection { @@ -168,6 +168,9 @@ public: } TextSelection adjustSelection(TextSelection selection, TextSelectType selectType) const; + bool isFullSelection(TextSelection selection) const { + return (selection.from == 0) && (selection.to >= _text.size()); + } bool isEmpty() const { return _text.isEmpty(); From 329285a8a6f411d7ed3409c6e18ead573467c6a5 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 26 May 2016 19:05:39 +0300 Subject: [PATCH 14/30] Dragging text from FlatLabel is supported. Started drag-n-drop support for chat photo updating. --- Telegram/Resources/langs/lang.strings | 2 + Telegram/SourceFiles/profile/profile.style | 10 +++ .../SourceFiles/profile/profile_cover.cpp | 62 +++++++++++++++++++ Telegram/SourceFiles/profile/profile_cover.h | 6 +- Telegram/SourceFiles/ui/flatlabel.cpp | 38 +++++++++++- Telegram/SourceFiles/ui/flatlabel.h | 4 +- 6 files changed, 118 insertions(+), 4 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index c73defa5b..4c2760266 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -457,6 +457,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "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_channel_add_admins" = "New administrator"; "lng_channel_add_members" = "Add members"; diff --git a/Telegram/SourceFiles/profile/profile.style b/Telegram/SourceFiles/profile/profile.style index 756d40781..5173d4e17 100644 --- a/Telegram/SourceFiles/profile/profile.style +++ b/Telegram/SourceFiles/profile/profile.style @@ -76,6 +76,16 @@ profileSecondaryButton: BoxButton(profilePrimaryButton) { textBgOver: #f2f7fa; } +profileDropAreaBg: profileBg; +profileDropAreaFg: #3fb0e4; +profileDropAreaPadding: margins(30px, 20px, 30px, 20px); +profileDropAreaTitleFont: font(24px); +profileDropAreaTitleTop: 36px; +profileDropAreaSubtitleFont: font(16px); +profileDropAreaSubtitleTop: 72px; +profileDropAreaBorderFg: profileDropAreaFg; +profileDropAreaBorderWidth: 3px; + profileDividerFg: black; profileDividerLeft: icon { { "profile_divider_left", profileDividerFg }, diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp index d1bc03036..0a1c8556c 100644 --- a/Telegram/SourceFiles/profile/profile_cover.cpp +++ b/Telegram/SourceFiles/profile/profile_cover.cpp @@ -123,6 +123,50 @@ private: }; +class DropArea : public TWidget { +public: + DropArea(QWidget *parent) : TWidget(parent) { + } + + void showAnimated() { + show(); + } + +protected: + void paintEvent(QPaintEvent *e) override { + Painter p(this); + 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); + + auto title = lang(lng_profile_drop_area_title); + int titleWidth = st::profileDropAreaTitleFont->width(title); + 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); + + auto subtitle = lang(lng_profile_drop_area_subtitle); + int subtitleWidth = st::profileDropAreaSubtitleFont->width(subtitle); + 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); + } + +}; + CoverWidget::CoverWidget(QWidget *parent, PeerData *peer) : TWidget(parent) , _peer(peer) , _peerUser(peer->asUser()) @@ -132,6 +176,7 @@ CoverWidget::CoverWidget(QWidget *parent, PeerData *peer) : TWidget(parent) , _photoButton(this, peer) , _name(this, QString(), st::profileNameLabel) { setAttribute(Qt::WA_OpaquePaintEvent); + setAcceptDrops(true); _name.setSelectable(true); _name.setContextCopyText(lang(lng_profile_copy_fullname)); @@ -204,6 +249,23 @@ void CoverWidget::paintEvent(QPaintEvent *e) { paintDivider(p); } +void CoverWidget::dragEnterEvent(QDragEnterEvent *e) { + _dropArea = new DropArea(this); + _dropArea->setGeometry(0, 0, width(), _dividerTop); + _dropArea->showAnimated(); + e->accept(); +} + +void CoverWidget::dragLeaveEvent(QDragLeaveEvent *e) { + delete _dropArea; + _dropArea = nullptr; +} + +void CoverWidget::dropEvent(QDropEvent *e) { + delete _dropArea; + _dropArea = nullptr; +} + void CoverWidget::paintDivider(Painter &p) { st::profileDividerLeft.paint(p, QPoint(st::lineWidth, _dividerTop), width()); diff --git a/Telegram/SourceFiles/profile/profile_cover.h b/Telegram/SourceFiles/profile/profile_cover.h index d0392a451..b5b67ef24 100644 --- a/Telegram/SourceFiles/profile/profile_cover.h +++ b/Telegram/SourceFiles/profile/profile_cover.h @@ -36,6 +36,7 @@ namespace Profile { class BackButton; class PhotoButton; +class DropArea; class CoverWidget final : public TWidget, public Notify::Observer { Q_OBJECT @@ -58,6 +59,9 @@ private slots: protected: void paintEvent(QPaintEvent *e) override; + void dragEnterEvent(QDragEnterEvent *e) override; + void dragLeaveEvent(QDragLeaveEvent *e) override; + void dropEvent(QDropEvent *e) override; private: // Observed notifications. @@ -85,8 +89,8 @@ private: ChannelData *_peerChannel; ChannelData *_peerMegagroup; - // Cover content ChildWidget _photoButton; + ChildWidget _dropArea = { nullptr }; FlatLabel _name; diff --git a/Telegram/SourceFiles/ui/flatlabel.cpp b/Telegram/SourceFiles/ui/flatlabel.cpp index 57f2e272d..a4894247c 100644 --- a/Telegram/SourceFiles/ui/flatlabel.cpp +++ b/Telegram/SourceFiles/ui/flatlabel.cpp @@ -120,6 +120,7 @@ Text::StateResult FlatLabel::dragActionStart(const QPoint &p, Qt::MouseButton bu if (_dragWasInactive) App::wnd()->inactivePress(false); if (ClickHandler::getPressed()) { + _dragStartPosition = mapFromGlobal(_lastMousePos); _dragAction = PrepareDrag; } if (!_selectable || _dragAction != NoDrag) { @@ -147,6 +148,7 @@ Text::StateResult FlatLabel::dragActionStart(const QPoint &p, Qt::MouseButton bu } } if (uponSelected) { + _dragStartPosition = mapFromGlobal(_lastMousePos); _dragAction = PrepareDrag; // start text drag } else if (!_dragWasInactive) { if (state.afterSymbol) ++_dragSymbol; @@ -391,6 +393,33 @@ void FlatLabel::onContextMenuDestroy(QObject *obj) { } } +void FlatLabel::onExecuteDrag() { + if (_dragAction != Dragging) return; + + auto state = getTextState(_dragStartPosition); + bool uponSelected = state.uponSymbol && _selection.from <= state.symbol; + if (uponSelected) { + if (_dragSymbol < _selection.from || _dragSymbol >= _selection.to) { + uponSelected = false; + } + } + + ClickHandlerPtr pressedHandler = ClickHandler::getPressed(); + QString selectedText; + if (uponSelected) { + selectedText = _text.originalText(_selection, ExpandLinksAll); + } else if (pressedHandler) { + selectedText = pressedHandler->dragText(); + } + if (!selectedText.isEmpty()) { + auto mimeData = new QMimeData(); + mimeData->setText(selectedText); + auto drag = new QDrag(App::wnd()); + drag->setMimeData(mimeData); + drag->exec(Qt::CopyAction); + } +} + void FlatLabel::clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) { update(); } @@ -400,10 +429,15 @@ void FlatLabel::clickHandlerPressedChanged(const ClickHandlerPtr &action, bool a } Text::StateResult FlatLabel::dragActionUpdate() { - QPoint m(mapFromGlobal(_lastMousePos)); - LOG(("DRAG ACTION UPDATE: %1 %2").arg(m.x()).arg(m.y())); + auto m = mapFromGlobal(_lastMousePos); auto state = getTextState(m); updateHover(state); + + if (_dragAction == PrepareDrag && (m - _dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) { + _dragAction = Dragging; + QTimer::singleShot(1, this, SLOT(onExecuteDrag())); + } + return state; } diff --git a/Telegram/SourceFiles/ui/flatlabel.h b/Telegram/SourceFiles/ui/flatlabel.h index 159ddf6a0..592065229 100644 --- a/Telegram/SourceFiles/ui/flatlabel.h +++ b/Telegram/SourceFiles/ui/flatlabel.h @@ -64,6 +64,8 @@ private slots: void onTouchSelect(); void onContextMenuDestroy(QObject *obj); + void onExecuteDrag(); + private: Text::StateResult dragActionUpdate(); Text::StateResult dragActionStart(const QPoint &p, Qt::MouseButton button); @@ -100,10 +102,10 @@ private: NoDrag = 0x00, PrepareDrag = 0x01, Dragging = 0x02, - PrepareSelect = 0x03, Selecting = 0x04, }; DragAction _dragAction = NoDrag; + QPoint _dragStartPosition; uint16 _dragSymbol = 0; bool _dragWasInactive = false; From 3570a1cf9113d67775d88a067963bbd2ea560454 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 27 May 2016 13:57:11 +0300 Subject: [PATCH 15/30] New profile photo edit by drag-n-drop. Animated photo button. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/app.cpp | 4 + Telegram/SourceFiles/app.h | 1 + Telegram/SourceFiles/boxes/aboutbox.cpp | 5 +- Telegram/SourceFiles/historywidget.cpp | 6 +- .../inline_bot_layout_internal.cpp | 15 +- Telegram/SourceFiles/profile/profile.style | 8 +- .../SourceFiles/profile/profile_cover.cpp | 221 +++++++++--------- Telegram/SourceFiles/profile/profile_cover.h | 16 +- .../profile/profile_cover_drop_area.cpp | 99 ++++++++ .../profile/profile_cover_drop_area.h | 57 +++++ .../SourceFiles/profile/profile_fixed_bar.cpp | 1 - .../profile/profile_inner_widget.cpp | 4 + .../profile/profile_inner_widget.h | 2 + .../profile/profile_userpic_button.cpp | 117 ++++++++++ .../profile/profile_userpic_button.h | 64 +++++ .../SourceFiles/profile/profile_widget.cpp | 1 + Telegram/SourceFiles/pspecific_linux.h | 4 +- Telegram/SourceFiles/pspecific_mac.cpp | 8 +- Telegram/SourceFiles/pspecific_mac.h | 2 +- Telegram/SourceFiles/pspecific_win.h | 4 +- Telegram/SourceFiles/pspecific_winrt.h | 4 +- Telegram/SourceFiles/ui/animation.h | 12 +- Telegram/SourceFiles/ui/flatbutton.cpp | 6 +- Telegram/Telegram.vcxproj | 4 + Telegram/Telegram.vcxproj.filters | 12 + 26 files changed, 531 insertions(+), 147 deletions(-) create mode 100644 Telegram/SourceFiles/profile/profile_cover_drop_area.cpp create mode 100644 Telegram/SourceFiles/profile/profile_cover_drop_area.h create mode 100644 Telegram/SourceFiles/profile/profile_userpic_button.cpp create mode 100644 Telegram/SourceFiles/profile/profile_userpic_button.h diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 4c2760266..1532561ac 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -459,6 +459,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "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_channel_add_admins" = "New administrator"; "lng_channel_add_members" = "Add members"; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 093b8fba7..260a5268c 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -2304,6 +2304,10 @@ namespace { return result; } + QPixmap pixmapFromImageInPlace(QImage &&image) { + return QPixmap::fromImage(std_::forward(image), Qt::ColorOnly); + } + void regPhotoItem(PhotoData *data, HistoryItem *item) { ::photoItems[data].insert(item, NullType()); } diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index 4c58c332e..c4428e309 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -234,6 +234,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); diff --git a/Telegram/SourceFiles/boxes/aboutbox.cpp b/Telegram/SourceFiles/boxes/aboutbox.cpp index 0ad979dab..c5bc8cdf5 100644 --- a/Telegram/SourceFiles/boxes/aboutbox.cpp +++ b/Telegram/SourceFiles/boxes/aboutbox.cpp @@ -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(); } diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index a81ccd818..9d2e3123f 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -5590,8 +5590,7 @@ DragState HistoryWidget::getDragState(const QMimeData *d) { for (QList::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; @@ -8420,8 +8419,7 @@ QStringList HistoryWidget::getMediasFromMime(const QMimeData *d) { for (QList::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(); diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index 42fd4e156..c3fa01dd6 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -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); diff --git a/Telegram/SourceFiles/profile/profile.style b/Telegram/SourceFiles/profile/profile.style index 5173d4e17..89431c03f 100644 --- a/Telegram/SourceFiles/profile/profile.style +++ b/Telegram/SourceFiles/profile/profile.style @@ -36,6 +36,7 @@ profileTopBarBackPosition: point(32px, 17px); profileMarginTop: 13px; profilePhotoSize: 112px; profilePhotoLeft: 35px; +profilePhotoDuration: 500; profileNameLeft: 26px; profileNameTop: 9px; profileNameLabel: flatLabel(labelDefFlat) { @@ -78,13 +79,14 @@ profileSecondaryButton: BoxButton(profilePrimaryButton) { profileDropAreaBg: profileBg; profileDropAreaFg: #3fb0e4; -profileDropAreaPadding: margins(30px, 20px, 30px, 20px); +profileDropAreaPadding: margins(25px, 3px, 25px, 20px); profileDropAreaTitleFont: font(24px); -profileDropAreaTitleTop: 36px; +profileDropAreaTitleTop: 30px; profileDropAreaSubtitleFont: font(16px); -profileDropAreaSubtitleTop: 72px; +profileDropAreaSubtitleTop: 68px; profileDropAreaBorderFg: profileDropAreaFg; profileDropAreaBorderWidth: 3px; +profileDropAreaDuration: 200; profileDividerFg: black; profileDividerLeft: icon { diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp index 0a1c8556c..5a0e8741d 100644 --- a/Telegram/SourceFiles/profile/profile_cover.cpp +++ b/Telegram/SourceFiles/profile/profile_cover.cpp @@ -22,6 +22,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #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" @@ -71,109 +73,13 @@ const Notify::PeerUpdateFlags ButtonsUpdateFlags = Notify::PeerUpdateFlag::UserC } // namespace -class PhotoButton final : public Button, public Notify::Observer { -public: - PhotoButton(QWidget *parent, PeerData *peer) : Button(parent), _peer(peer) { - resize(st::profilePhotoSize, st::profilePhotoSize); - - processNewPeerPhoto(); - - Notify::registerPeerObserver(Notify::PeerUpdateFlag::PhotoChanged, this, &PhotoButton::notifyPeerUpdated); - FileDownload::registerImageLoadedObserver(this, &PhotoButton::notifyImageLoaded); - } - -protected: - void paintEvent(QPaintEvent *e) { - Painter p(this); - p.drawPixmap(0, 0, _userpic); - } - -private: - void notifyPeerUpdated(const Notify::PeerUpdate &update) { - if (update.peer != _peer) { - return; - } - - processNewPeerPhoto(); - this->update(); - } - - void notifyImageLoaded() { - if (_waiting && _peer->userpicLoaded()) { - _waiting = false; - _userpic = _peer->genUserpic(st::profilePhotoSize); - update(); - } - } - - void processNewPeerPhoto() { - bool hasPhoto = (_peer->photoId && _peer->photoId != UnknownPeerPhotoId); - setCursor(hasPhoto ? style::cur_pointer : style::cur_default); - _waiting = !_peer->userpicLoaded(); - if (_waiting) { - _peer->loadUserpic(true); - } else { - _userpic = _peer->genUserpic(st::profilePhotoSize); - } - } - - PeerData *_peer; - bool _waiting = false; - QPixmap _userpic; - -}; - -class DropArea : public TWidget { -public: - DropArea(QWidget *parent) : TWidget(parent) { - } - - void showAnimated() { - show(); - } - -protected: - void paintEvent(QPaintEvent *e) override { - Painter p(this); - 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); - - auto title = lang(lng_profile_drop_area_title); - int titleWidth = st::profileDropAreaTitleFont->width(title); - 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); - - auto subtitle = lang(lng_profile_drop_area_subtitle); - int subtitleWidth = st::profileDropAreaSubtitleFont->width(subtitle); - 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); - } - -}; - CoverWidget::CoverWidget(QWidget *parent, PeerData *peer) : TWidget(parent) , _peer(peer) , _peerUser(peer->asUser()) , _peerChat(peer->asChat()) , _peerChannel(peer->asChannel()) , _peerMegagroup(peer->isMegagroup() ? _peerChannel : nullptr) -, _photoButton(this, peer) +, _userpicButton(this, peer) , _name(this, QString(), st::profileNameLabel) { setAttribute(Qt::WA_OpaquePaintEvent); setAcceptDrops(true); @@ -185,7 +91,7 @@ CoverWidget::CoverWidget(QWidget *parent, PeerData *peer) : TWidget(parent) Notify::registerPeerObserver(observeEvents, this, &CoverWidget::notifyPeerUpdated); FileDialog::registerObserver(this, &CoverWidget::notifyFileQueryUpdated); - connect(_photoButton, SIGNAL(clicked()), this, SLOT(onPhotoShow())); + connect(_userpicButton, SIGNAL(clicked()), this, SLOT(onPhotoShow())); refreshNameText(); refreshStatusText(); @@ -207,19 +113,19 @@ void CoverWidget::resizeToWidth(int newWidth) { int newHeight = 0; newHeight += st::profileMarginTop; - _photoButton->moveToLeft(st::profilePhotoLeft, newHeight); + _userpicButton->moveToLeft(st::profilePhotoLeft, newHeight); - int infoLeft = _photoButton->x() + _photoButton->width(); + int infoLeft = _userpicButton->x() + _userpicButton->width(); int nameLeft = infoLeft + st::profileNameLeft - st::profileNameLabel.margin.left(); - int nameTop = _photoButton->y() + st::profileNameTop - st::profileNameLabel.margin.top(); + int nameTop = _userpicButton->y() + st::profileNameTop - st::profileNameLabel.margin.top(); _name.moveToLeft(nameLeft, nameTop); int nameWidth = width() - infoLeft - st::profileNameLeft - st::profileButtonSkip; nameWidth += st::profileNameLabel.margin.left() + st::profileNameLabel.margin.right(); _name.resizeToWidth(nameWidth); - _statusPosition = QPoint(infoLeft + st::profileStatusLeft, _photoButton->y() + st::profileStatusTop); + _statusPosition = QPoint(infoLeft + st::profileStatusLeft, _userpicButton->y() + st::profileStatusTop); - int buttonLeft = st::profilePhotoLeft + _photoButton->width() + st::profileButtonLeft; + int buttonLeft = st::profilePhotoLeft + _userpicButton->width() + st::profileButtonLeft; for_const (auto button, _buttons) { button->moveToLeft(buttonLeft, st::profileButtonTop); buttonLeft += button->width() + st::profileButtonSkip; @@ -233,10 +139,15 @@ void CoverWidget::resizeToWidth(int newWidth) { newHeight += st::profileBlocksTop; + resizeDropArea(); resize(newWidth, newHeight); update(); } +void CoverWidget::showFinished() { + _userpicButton->showFinished(); +} + void CoverWidget::paintEvent(QPaintEvent *e) { Painter p(this); @@ -249,21 +160,109 @@ void CoverWidget::paintEvent(QPaintEvent *e) { paintDivider(p); } +void CoverWidget::resizeDropArea() { + if (_dropArea) { + _dropArea->setGeometry(0, 0, width(), _dividerTop); + } +} + +void CoverWidget::dropAreaHidden(CoverDropArea *dropArea) { + if (_dropArea == dropArea) { + _dropArea->deleteLater(); + _dropArea = nullptr; + } +} + +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; + + 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) { - _dropArea = new DropArea(this); - _dropArea->setGeometry(0, 0, width(), _dividerTop); + 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) { - delete _dropArea; - _dropArea = nullptr; + if (_dropArea && !_dropArea->hiding()) { + _dropArea->hideAnimated(func(this, &CoverWidget::dropAreaHidden)); + } } void CoverWidget::dropEvent(QDropEvent *e) { - delete _dropArea; - _dropArea = nullptr; + auto mimeData = e->mimeData(); + + QImage img; + if (mimeData->hasImage()) { + img = qvariant_cast(mimeData->imageData()); + } else { + 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) { @@ -439,6 +438,10 @@ void CoverWidget::notifyFileQueryUpdated(const FileDialog::QueryUpdate &update) 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; diff --git a/Telegram/SourceFiles/profile/profile_cover.h b/Telegram/SourceFiles/profile/profile_cover.h index b5b67ef24..179050452 100644 --- a/Telegram/SourceFiles/profile/profile_cover.h +++ b/Telegram/SourceFiles/profile/profile_cover.h @@ -35,8 +35,8 @@ struct PeerUpdate; namespace Profile { class BackButton; -class PhotoButton; -class DropArea; +class UserpicButton; +class CoverDropArea; class CoverWidget final : public TWidget, public Notify::Observer { Q_OBJECT @@ -47,6 +47,8 @@ public: // Count new height for width=newWidth and resize to it. void resizeToWidth(int newWidth); + void showFinished(); + private slots: void onPhotoShow(); @@ -83,14 +85,20 @@ private: 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 _photoButton; - ChildWidget _dropArea = { nullptr }; + ChildWidget _userpicButton; + ChildWidget _dropArea = { nullptr }; FlatLabel _name; diff --git a/Telegram/SourceFiles/profile/profile_cover_drop_area.cpp b/Telegram/SourceFiles/profile/profile_cover_drop_area.cpp new file mode 100644 index 000000000..e3370f66d --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_cover_drop_area.cpp @@ -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 diff --git a/Telegram/SourceFiles/profile/profile_cover_drop_area.h b/Telegram/SourceFiles/profile/profile_cover_drop_area.h new file mode 100644 index 000000000..871523ff8 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_cover_drop_area.h @@ -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 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 diff --git a/Telegram/SourceFiles/profile/profile_fixed_bar.cpp b/Telegram/SourceFiles/profile/profile_fixed_bar.cpp index 9c3b30f64..5b79a34b3 100644 --- a/Telegram/SourceFiles/profile/profile_fixed_bar.cpp +++ b/Telegram/SourceFiles/profile/profile_fixed_bar.cpp @@ -97,7 +97,6 @@ void FixedBar::onDeleteContact() { } - void FixedBar::resizeToWidth(int newWidth) { int newHeight = 0; diff --git a/Telegram/SourceFiles/profile/profile_inner_widget.cpp b/Telegram/SourceFiles/profile/profile_inner_widget.cpp index d606c7435..4820efdbb 100644 --- a/Telegram/SourceFiles/profile/profile_inner_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_inner_widget.cpp @@ -56,6 +56,10 @@ void InnerWidget::setVisibleTopBottom(int visibleTop, int visibleBottom) { } } +void InnerWidget::showFinished() { + _cover->showFinished(); +} + void InnerWidget::decreaseAdditionalHeight(int removeHeight) { resizeToWidth(width(), height() - removeHeight); } diff --git a/Telegram/SourceFiles/profile/profile_inner_widget.h b/Telegram/SourceFiles/profile/profile_inner_widget.h index 6a1ad1500..8b2f63b40 100644 --- a/Telegram/SourceFiles/profile/profile_inner_widget.h +++ b/Telegram/SourceFiles/profile/profile_inner_widget.h @@ -41,6 +41,8 @@ public: // Updates the area that is visible inside the scroll container. void setVisibleTopBottom(int visibleTop, int visibleBottom); + void showFinished(); + signals: void cancelled(); diff --git a/Telegram/SourceFiles/profile/profile_userpic_button.cpp b/Telegram/SourceFiles/profile/profile_userpic_button.cpp new file mode 100644 index 000000000..dc117f7bf --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_userpic_button.cpp @@ -0,0 +1,117 @@ +/* +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_userpic_button.h" + +#include "styles/style_profile.h" +#include "observer_peer.h" +#include "mtproto/file_download.h" + +namespace Profile { + +UserpicButton::UserpicButton(QWidget *parent, PeerData *peer) : Button(parent), _peer(peer) { + resize(st::profilePhotoSize, st::profilePhotoSize); + + processPeerPhoto(); + _notShownYet = _waiting; + if (!_waiting) { + _userpic = prepareUserpicPixmap(); + } + + Notify::registerPeerObserver(Notify::PeerUpdateFlag::PhotoChanged, this, &UserpicButton::notifyPeerUpdated); + FileDownload::registerImageLoadedObserver(this, &UserpicButton::notifyImageLoaded); +} + +void UserpicButton::showFinished() { + if (_notShownYet && !_waiting) { + _notShownYet = false; + _a_appearance.finish(); + START_ANIMATION(_a_appearance, func(this, &UserpicButton::refreshCallback), 0, 1, st::profilePhotoDuration, anim::linear); + } +} + +void UserpicButton::paintEvent(QPaintEvent *e) { + Painter p(this); + if (_a_appearance.animating(getms())) { + p.drawPixmap(0, 0, _oldUserpic); + p.setOpacity(_a_appearance.current()); + } + p.drawPixmap(0, 0, _userpic); +} + +void UserpicButton::notifyPeerUpdated(const Notify::PeerUpdate &update) { + if (update.peer != _peer) { + return; + } + + processNewPeerPhoto(); + this->update(); +} + +void UserpicButton::notifyImageLoaded() { + if (_waiting && _peer->userpicLoaded()) { + _waiting = false; + startNewPhotoShowing(); + } +} + +void UserpicButton::processPeerPhoto() { + bool hasPhoto = (_peer->photoId && _peer->photoId != UnknownPeerPhotoId); + setCursor(hasPhoto ? style::cur_pointer : style::cur_default); + _waiting = !_peer->userpicLoaded(); + if (_waiting) { + _peer->loadUserpic(true); + } +} + +void UserpicButton::processNewPeerPhoto() { + processPeerPhoto(); + if (!_waiting) { + startNewPhotoShowing(); + } +} + +void UserpicButton::startNewPhotoShowing() { + _oldUserpic = myGrab(this); + _userpic = prepareUserpicPixmap(); + + if (_notShownYet) { + return; + } + + _a_appearance.finish(); + START_ANIMATION(_a_appearance, func(this, &UserpicButton::refreshCallback), 0, 1, st::profilePhotoDuration, anim::linear); + update(); +} + +QPixmap UserpicButton::prepareUserpicPixmap() const { + auto retina = cIntRetinaFactor(); + auto size = st::profilePhotoSize * retina; + QImage image(size, size, QImage::Format_ARGB32_Premultiplied); + { + Painter p(&image); + p.fillRect(0, 0, size, size, st::profileBg); + _peer->paintUserpic(p, st::profilePhotoSize, 0, 0); + } + return App::pixmapFromImageInPlace(std_::move(image)); +} + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_userpic_button.h b/Telegram/SourceFiles/profile/profile_userpic_button.h new file mode 100644 index 000000000..8dc63b1f6 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_userpic_button.h @@ -0,0 +1,64 @@ +/* +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 UserpicButton final : public Button, public Notify::Observer { +public: + UserpicButton(QWidget *parent, PeerData *peer); + + // If at the first moment the _userpic was not loaded, + // we need to show it animated after the profile is fully shown. + void showFinished(); + +protected: + void paintEvent(QPaintEvent *e); + +private: + void notifyPeerUpdated(const Notify::PeerUpdate &update); + void notifyImageLoaded(); + + void refreshCallback() { + update(); + } + + void processPeerPhoto(); + void processNewPeerPhoto(); + void startNewPhotoShowing(); + QPixmap prepareUserpicPixmap() const; + + bool _notShownYet; + + PeerData *_peer; + bool _waiting = false; + QPixmap _userpic, _oldUserpic; + FloatAnimation _a_appearance; + +}; + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_widget.cpp b/Telegram/SourceFiles/profile/profile_widget.cpp index ca3672185..3e80d6e69 100644 --- a/Telegram/SourceFiles/profile/profile_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_widget.cpp @@ -130,6 +130,7 @@ void Widget::showAnimatedHook() { void Widget::showFinishedHook() { _fixedBar->setAnimatingMode(false); + _inner->showFinished(); } } // namespace Profile diff --git a/Telegram/SourceFiles/pspecific_linux.h b/Telegram/SourceFiles/pspecific_linux.h index 0804e8deb..fa4c5a3d9 100644 --- a/Telegram/SourceFiles/pspecific_linux.h +++ b/Telegram/SourceFiles/pspecific_linux.h @@ -160,8 +160,8 @@ QAbstractNativeEventFilter *psNativeEventFilter(); void psNewVersion(); void psUpdateOverlayed(QWidget *widget); -inline QString psConvertFileUrl(const QString &url) { - return url; +inline QString psConvertFileUrl(const QUrl &url) { + return url.toLocalFile(); } inline QByteArray psDownloadPathBookmark(const QString &path) { return QByteArray(); diff --git a/Telegram/SourceFiles/pspecific_mac.cpp b/Telegram/SourceFiles/pspecific_mac.cpp index 7cac0e6c4..87d8ac1f8 100644 --- a/Telegram/SourceFiles/pspecific_mac.cpp +++ b/Telegram/SourceFiles/pspecific_mac.cpp @@ -898,8 +898,12 @@ void psSendToMenu(bool send, bool silent) { void psUpdateOverlayed(QWidget *widget) { } -QString psConvertFileUrl(const QString &url) { - return objc_convertFileUrl(url); +QString psConvertFileUrl(const QUrl &url) { + auto urlString = url.toLocalFile(); + if (urlString.startsWith(qsl("/.file/id="))) { + return objc_convertFileUrl(urlString); + } + return urlString; } void psDownloadPathEnableAccess() { diff --git a/Telegram/SourceFiles/pspecific_mac.h b/Telegram/SourceFiles/pspecific_mac.h index 596f9c866..cfeafabc8 100644 --- a/Telegram/SourceFiles/pspecific_mac.h +++ b/Telegram/SourceFiles/pspecific_mac.h @@ -187,7 +187,7 @@ QAbstractNativeEventFilter *psNativeEventFilter(); void psNewVersion(); void psUpdateOverlayed(QWidget *widget); -QString psConvertFileUrl(const QString &url); +QString psConvertFileUrl(const QUrl &url); void psDownloadPathEnableAccess(); QByteArray psDownloadPathBookmark(const QString &path); diff --git a/Telegram/SourceFiles/pspecific_win.h b/Telegram/SourceFiles/pspecific_win.h index e6303f27f..e85046568 100644 --- a/Telegram/SourceFiles/pspecific_win.h +++ b/Telegram/SourceFiles/pspecific_win.h @@ -164,8 +164,8 @@ QAbstractNativeEventFilter *psNativeEventFilter(); void psNewVersion(); void psUpdateOverlayed(TWidget *widget); -inline QString psConvertFileUrl(const QString &url) { - return url; +inline QString psConvertFileUrl(const QUrl &url) { + return url.toLocalFile(); } inline QByteArray psDownloadPathBookmark(const QString &path) { return QByteArray(); diff --git a/Telegram/SourceFiles/pspecific_winrt.h b/Telegram/SourceFiles/pspecific_winrt.h index 75238b789..06722c1ab 100644 --- a/Telegram/SourceFiles/pspecific_winrt.h +++ b/Telegram/SourceFiles/pspecific_winrt.h @@ -161,8 +161,8 @@ QAbstractNativeEventFilter *psNativeEventFilter(); void psNewVersion(); void psUpdateOverlayed(TWidget *widget); -inline QString psConvertFileUrl(const QString &url) { - return url; +inline QString psConvertFileUrl(const QUrl &url) { + return url.toLocalFile(); } inline QByteArray psDownloadPathBookmark(const QString &path) { return QByteArray(); diff --git a/Telegram/SourceFiles/ui/animation.h b/Telegram/SourceFiles/ui/animation.h index 71796e7b3..78996a17e 100644 --- a/Telegram/SourceFiles/ui/animation.h +++ b/Telegram/SourceFiles/ui/animation.h @@ -362,7 +362,6 @@ AnimationCallbacks animation(Param param, Type *obj, typename AnimationCallbacks template class SimpleAnimation { public: - using Callback = Function; SimpleAnimation() { @@ -464,7 +463,16 @@ using FloatAnimation = SimpleAnimation; using IntAnimation = SimpleAnimation; using ColorAnimation = SimpleAnimation; -#define EnsureAnimation(animation, from, callback) if ((animation).isNull()) { (animation).setup((from), (callback)); } + +// Macro allows us to lazily create updateCallback. +#define ENSURE_ANIMATION(animation, updateCallback, from) \ +if ((animation).isNull()) { \ + (animation).setup((from), (updateCallback)); \ +} + +#define START_ANIMATION(animation, updateCallback, from, to, duration, transition) \ +ENSURE_ANIMATION(animation, updateCallback, from); \ +(animation).start((to), (duration), (transition)) class ClipReader; diff --git a/Telegram/SourceFiles/ui/flatbutton.cpp b/Telegram/SourceFiles/ui/flatbutton.cpp index 5462b395c..691f6e9b5 100644 --- a/Telegram/SourceFiles/ui/flatbutton.cpp +++ b/Telegram/SourceFiles/ui/flatbutton.cpp @@ -335,10 +335,10 @@ void EmojiButton::paintEvent(QPaintEvent *e) { void EmojiButton::setLoading(bool loading) { if (_loading != loading) { - EnsureAnimation(a_loading, _loading ? 1. : 0., func(this, &EmojiButton::updateCallback)); - a_loading.start(loading ? 1. : 0., st::emojiCircleDuration); _loading = loading; - if (_loading) { + auto from = loading ? 0. : 1., to = loading ? 1. : 0.; + START_ANIMATION(a_loading, func(this, &EmojiButton::updateCallback), from, to, st::emojiCircleDuration, anim::linear); + if (loading) { _a_loading.start(); } else { _a_loading.stop(); diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index ec36b0357..17d801473 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -1215,9 +1215,11 @@ + + true @@ -1560,7 +1562,9 @@ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/profile/profile_block_widget.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" + + diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 7a28c5dd6..7412f0303 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -1209,6 +1209,12 @@ SourceFiles + + SourceFiles\profile + + + SourceFiles\profile + @@ -1406,6 +1412,12 @@ SourceFiles + + SourceFiles\profile + + + SourceFiles\profile + From 2c5c25962cfae4709ccfd9ab14cdb3f87d49f123 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 27 May 2016 16:56:35 +0300 Subject: [PATCH 16/30] Profile fixed bar actions done, adaptive layout for profile buttons. --- Telegram/Resources/basic.style | 1 + Telegram/Resources/basic_types.style | 2 + .../Resources/icons/profile_add_member.png | Bin 0 -> 319 bytes .../Resources/icons/profile_add_member@2x.png | Bin 0 -> 592 bytes Telegram/Resources/langs/lang.strings | 1 + .../SourceFiles/codegen/style/generator.cpp | 2 +- .../SourceFiles/codegen/style/parsed_file.h | 1 + Telegram/SourceFiles/historywidget.cpp | 3 +- Telegram/SourceFiles/profile/profile.style | 11 +- .../SourceFiles/profile/profile_cover.cpp | 57 ++++++-- Telegram/SourceFiles/profile/profile_cover.h | 7 + .../SourceFiles/profile/profile_fixed_bar.cpp | 133 +++++++++++++++++- .../SourceFiles/profile/profile_fixed_bar.h | 29 +++- .../profile/profile_inner_widget.cpp | 6 +- .../profile/profile_inner_widget.h | 5 + .../SourceFiles/profile/profile_widget.cpp | 5 +- .../SourceFiles/ui/buttons/round_button.cpp | 15 +- Telegram/SourceFiles/ui/scrollarea.cpp | 1 + 18 files changed, 246 insertions(+), 33 deletions(-) create mode 100644 Telegram/Resources/icons/profile_add_member.png create mode 100644 Telegram/Resources/icons/profile_add_member@2x.png diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style index cf2a840a5..369a259df 100644 --- a/Telegram/Resources/basic.style +++ b/Telegram/Resources/basic.style @@ -58,6 +58,7 @@ adaptiveWideWidth: 1366px; windowBg: #fff; // fallback for background: white windowTextFg: #000; // fallback for text color: black windowSubTextFg: #8a8a8a; // fallback for subtext color: gray +windowShadowFg: #000; // fallback for shadow color wndMinHeight: 480px; wndDefWidth: 800px; diff --git a/Telegram/Resources/basic_types.style b/Telegram/Resources/basic_types.style index 22e79f521..5cd91f1c6 100644 --- a/Telegram/Resources/basic_types.style +++ b/Telegram/Resources/basic_types.style @@ -308,6 +308,8 @@ BoxButton { textTop: pixels; + icon: icon; + font: font; duration: int; } diff --git a/Telegram/Resources/icons/profile_add_member.png b/Telegram/Resources/icons/profile_add_member.png new file mode 100644 index 0000000000000000000000000000000000000000..828236388742466bcfb3230d88838c51ba0e4098 GIT binary patch literal 319 zcmV-F0l@x=P)=(+X)ZuJKd8O_Ds%Lli{_f&i}T!nSPyz&MWRx(-!UAmhSX@fBY4P`aJBkPyX#!yZ}Q+0S`9b R6t@5X002ovPDHLkV1nh0k0$^C literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/profile_add_member@2x.png b/Telegram/Resources/icons/profile_add_member@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fa6ed0d2cdaf7254e34c2c3ed329fefa3794d488 GIT binary patch literal 592 zcmV-W0OiHkQGlB+bq( z-V=O2jY&+dT(}1oL}n&um)T4J05HiVNfJ^@(KJo;JP$q3L&GpoQ4|zKF-py3M0uXa z;c$q4FvgHliX6wiSD5E{oKB~{ve#&u7DhUL;b$j%4@FVlBP>Z$Xj#NR?GdD>KOkm0 zolc}YLI||m?Le`2cXG?4xZ7o#W~38FMAUWtX?^nd$N#&)fBz$3T zWD+PY)=MXfA~ME8UpU4XmdoYm&xm}b zeKtZV4P8f;@I{(Z>f0WSF_dLFxNMTb*F8NL3{cl~ zbX^x+*TwC2i~W8-SkERsIFjafa3sx-6aGx1Xg;5ZTCE1vY86VQ5)=vr$mMd7$z%Wk zz;PTn91dVv7Hl>fSg+SGo6TUq-v_E6%cPSe346WXOP`m9VPLo0#Y&~(_pETQmFq1 _scroll.scrollTopMax()) toY = _scroll.scrollTopMax(); + auto scrollMax = _scroll.scrollTopMax(); + accumulate_min(toY, scrollMax); if (_scroll.scrollTop() == toY) { visibleAreaUpdated(); } else { diff --git a/Telegram/SourceFiles/profile/profile.style b/Telegram/SourceFiles/profile/profile.style index 89431c03f..76fc87208 100644 --- a/Telegram/SourceFiles/profile/profile.style +++ b/Telegram/SourceFiles/profile/profile.style @@ -32,6 +32,8 @@ profileTopBarBackIconPosition: point(15px, 19px); profileTopBarBackFont: font(14px); profileTopBarBackFg: #1485c2; profileTopBarBackPosition: point(32px, 17px); +profileFixedBarButton: flatButton(topBarButton) { +} profileMarginTop: 13px; profilePhotoSize: 112px; @@ -76,6 +78,13 @@ profileSecondaryButton: BoxButton(profilePrimaryButton) { textBg: #ffffff; textBgOver: #f2f7fa; } +profileAddMemberIcon: icon { + { "profile_add_member", #3fb0e4, point(20px, 10px) }, +}; +profileAddMemberButton: BoxButton(profileSecondaryButton) { + width: 62px; + icon: profileAddMemberIcon; +} profileDropAreaBg: profileBg; profileDropAreaFg: #3fb0e4; @@ -88,7 +97,7 @@ profileDropAreaBorderFg: profileDropAreaFg; profileDropAreaBorderWidth: 3px; profileDropAreaDuration: 200; -profileDividerFg: black; +profileDividerFg: windowShadowFg; profileDividerLeft: icon { { "profile_divider_left", profileDividerFg }, }; diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp index 5a0e8741d..fd8937987 100644 --- a/Telegram/SourceFiles/profile/profile_cover.cpp +++ b/Telegram/SourceFiles/profile/profile_cover.cpp @@ -119,17 +119,13 @@ void CoverWidget::resizeToWidth(int newWidth) { int nameLeft = infoLeft + st::profileNameLeft - st::profileNameLabel.margin.left(); int nameTop = _userpicButton->y() + st::profileNameTop - st::profileNameLabel.margin.top(); _name.moveToLeft(nameLeft, nameTop); - int nameWidth = width() - infoLeft - st::profileNameLeft - st::profileButtonSkip; + int nameWidth = newWidth - infoLeft - st::profileNameLeft - st::profileButtonSkip; nameWidth += st::profileNameLabel.margin.left() + st::profileNameLabel.margin.right(); _name.resizeToWidth(nameWidth); _statusPosition = QPoint(infoLeft + st::profileStatusLeft, _userpicButton->y() + st::profileStatusTop); - int buttonLeft = st::profilePhotoLeft + _userpicButton->width() + st::profileButtonLeft; - for_const (auto button, _buttons) { - button->moveToLeft(buttonLeft, st::profileButtonTop); - buttonLeft += button->width() + st::profileButtonSkip; - } + moveAndToggleButtons(newWidth); newHeight += st::profilePhotoSize; newHeight += st::profileMarginBottom; @@ -144,10 +140,41 @@ void CoverWidget::resizeToWidth(int newWidth) { update(); } +// 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) { + bool showNextButton = true; + int buttonLeft = st::profilePhotoLeft + _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->moveToLeft(buttonLeft, st::profileButtonTop); + if (i == 1) { + // If second button is not fitting. + if (buttonLeft + button->width() > buttonsRight) { + button->hide(); + } else { + button->show(); + buttonLeft += button->width() + st::profileButtonSkip; + showNextButton = false; + } + } else { + button->setVisible(showNextButton); + buttonLeft += button->width() + st::profileButtonSkip; + } + } +} + void CoverWidget::showFinished() { _userpicButton->showFinished(); } +bool CoverWidget::shareContactButtonShown() const { + return _peerUser && (_buttons.size() > 1) && !(_buttons.at(1)->isHidden()); +} + void CoverWidget::paintEvent(QPaintEvent *e) { Painter p(this); @@ -368,6 +395,7 @@ void CoverWidget::setChatButtons() { if (_peerChat->canEdit()) { addButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto())); addButton(lang(lng_profile_add_participant), SLOT(onAddMember())); + addButton(st::profileAddMemberButton, SLOT(onAddMember())); } } @@ -377,6 +405,7 @@ void CoverWidget::setMegagroupButtons() { } if (_peerMegagroup->canAddParticipants()) { addButton(lang(lng_profile_add_participant), SLOT(onAddMember())); + addButton(st::profileAddMemberButton, SLOT(onAddMember())); } } @@ -398,12 +427,16 @@ void CoverWidget::clearButtons() { } void CoverWidget::addButton(const QString &text, const char *slot) { - if (!text.isEmpty()) { - auto &buttonStyle = _buttons.isEmpty() ? st::profilePrimaryButton : st::profileSecondaryButton; - _buttons.push_back(new Ui::RoundButton(this, text, buttonStyle)); - connect(_buttons.back(), SIGNAL(clicked()), this, slot); - _buttons.back()->show(); - } + auto &buttonStyle = _buttons.isEmpty() ? st::profilePrimaryButton : st::profileSecondaryButton; + _buttons.push_back(new Ui::RoundButton(this, text, buttonStyle)); + connect(_buttons.back(), SIGNAL(clicked()), this, slot); + _buttons.back()->show(); +} + +void CoverWidget::addButton(const style::BoxButton &buttonStyle, const char *slot) { + _buttons.push_back(new Ui::RoundButton(this, QString(), buttonStyle)); + connect(_buttons.back(), SIGNAL(clicked()), this, slot); + _buttons.back()->hide(); } void CoverWidget::onSendMessage() { diff --git a/Telegram/SourceFiles/profile/profile_cover.h b/Telegram/SourceFiles/profile/profile_cover.h index 179050452..47975485b 100644 --- a/Telegram/SourceFiles/profile/profile_cover.h +++ b/Telegram/SourceFiles/profile/profile_cover.h @@ -49,6 +49,11 @@ public: 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; + private slots: void onPhotoShow(); @@ -70,6 +75,7 @@ private: void notifyPeerUpdated(const Notify::PeerUpdate &update); void notifyFileQueryUpdated(const FileDialog::QueryUpdate &update); + void moveAndToggleButtons(int newWiddth); void refreshNameText(); void refreshStatusText(); bool isUsingMegagroupOnlineCount() const; @@ -82,6 +88,7 @@ private: void clearButtons(); void addButton(const QString &text, const char *slot); + void addButton(const style::BoxButton &buttonStyle, const char *slot); void paintDivider(Painter &p); diff --git a/Telegram/SourceFiles/profile/profile_fixed_bar.cpp b/Telegram/SourceFiles/profile/profile_fixed_bar.cpp index 5b79a34b3..4c09eb88c 100644 --- a/Telegram/SourceFiles/profile/profile_fixed_bar.cpp +++ b/Telegram/SourceFiles/profile/profile_fixed_bar.cpp @@ -24,6 +24,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_profile.h" #include "lang.h" #include "mainwidget.h" +#include "boxes/addcontactbox.h" +#include "boxes/confirmbox.h" namespace Profile { @@ -67,6 +69,74 @@ FixedBar::FixedBar(QWidget *parent, PeerData *peer) : TWidget(parent) , _backButton(this) { _backButton->moveToLeft(0, 0); connect(_backButton, SIGNAL(clicked()), this, SLOT(onBack())); + + refreshRightActions(); +} + +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->contact > 0) { + addRightAction(RightActionType::EditContact, lang(lng_profile_edit_contact), SLOT(onEditContact())); + addRightAction(RightActionType::DeleteContact, lang(lng_profile_delete_contact), SLOT(onDeleteContact())); + addRightAction(RightActionType::ShareContact, lang(lng_profile_top_bar_share_contact), SLOT(onShareContact())); + } else if (_peerUser->contact == 0 || !App::phoneFromSharedContact(peerToUser(_peer->id)).isEmpty()) { + addRightAction(RightActionType::AddContact, lang(lng_profile_add_contact), SLOT(onAddContact())); + addRightAction(RightActionType::ShareContact, lang(lng_profile_top_bar_share_contact), SLOT(onShareContact())); + } +} + +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({}); + } + _rightActions[_currentAction].type = type; + _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() { @@ -74,32 +144,63 @@ void FixedBar::onBack() { } void FixedBar::onEditChannel() { - + Ui::showLayer(new EditChannelBox(_peerMegagroup ? _peerMegagroup : _peerChannel)); } void FixedBar::onEditGroup() { - -} - -void FixedBar::onLeaveGroup() { - + 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(); + MTP::send(MTPmessages_DeleteChatUser(_peerChat->inputChat, App::self()->inputUser), App::main()->rpcDone(&MainWidget::deleteHistoryAfterLeave, _peer), App::main()->rpcFail(&MainWidget::leaveChatFailed, _peer)); } 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->moveToLeft(0, 0); _backButton->resizeToWidth(newWidth); newHeight += _backButton->height(); @@ -116,11 +217,29 @@ void FixedBar::setAnimatingMode(bool enabled) { } 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(); diff --git a/Telegram/SourceFiles/profile/profile_fixed_bar.h b/Telegram/SourceFiles/profile/profile_fixed_bar.h index 325506703..fdc685057 100644 --- a/Telegram/SourceFiles/profile/profile_fixed_bar.h +++ b/Telegram/SourceFiles/profile/profile_fixed_bar.h @@ -36,6 +36,9 @@ public: // 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; @@ -45,22 +48,34 @@ public slots: private slots: void onEditChannel(); void onEditGroup(); - void onLeaveGroup(); void onAddContact(); void onEditContact(); + void onShareContact(); void onDeleteContact(); + void onDeleteContactSure(); + void onLeaveGroup(); + void onLeaveGroupSure(); private: void refreshRightActions(); + void setUserActions(); + void setChatActions(); + void setMegagroupActions(); + void setChannelActions(); + enum class RightActionType { EditChannel, EditGroup, LeaveGroup, AddContact, EditContact, - DeleteContact + DeleteContact, + ShareContact, }; + void addRightAction(RightActionType type, const QString &text, const char *slot); + void applyHideShareContactButton(); + PeerData *_peer; UserData *_peerUser; ChatData *_peerChat; @@ -68,10 +83,16 @@ private: ChannelData *_peerMegagroup; ChildWidget _backButton; - QList _rightActions; - QList _rightActionTypes; + + int _currentAction = 0; + struct RightAction { + RightActionType type; + FlatButton *button; + }; + QList _rightActions; bool _animatingMode = false; + bool _hideShareContactButton = false; }; diff --git a/Telegram/SourceFiles/profile/profile_inner_widget.cpp b/Telegram/SourceFiles/profile/profile_inner_widget.cpp index 4820efdbb..77e701ac9 100644 --- a/Telegram/SourceFiles/profile/profile_inner_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_inner_widget.cpp @@ -45,7 +45,7 @@ void InnerWidget::setVisibleTopBottom(int visibleTop, int visibleBottom) { int notDisplayedAtBottom = height() - _visibleBottom; if (notDisplayedAtBottom > 0) { - decreaseAdditionalHeight(notDisplayedAtBottom); +// decreaseAdditionalHeight(notDisplayedAtBottom); // testing } //loadProfilePhotos(_visibleTop); @@ -56,6 +56,10 @@ void InnerWidget::setVisibleTopBottom(int visibleTop, int visibleBottom) { } } +bool InnerWidget::shareContactButtonShown() const { + return _cover->shareContactButtonShown(); +} + void InnerWidget::showFinished() { _cover->showFinished(); } diff --git a/Telegram/SourceFiles/profile/profile_inner_widget.h b/Telegram/SourceFiles/profile/profile_inner_widget.h index 8b2f63b40..e702b17cd 100644 --- a/Telegram/SourceFiles/profile/profile_inner_widget.h +++ b/Telegram/SourceFiles/profile/profile_inner_widget.h @@ -41,6 +41,11 @@ public: // 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: diff --git a/Telegram/SourceFiles/profile/profile_widget.cpp b/Telegram/SourceFiles/profile/profile_widget.cpp index 3e80d6e69..15a258e07 100644 --- a/Telegram/SourceFiles/profile/profile_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_widget.cpp @@ -106,8 +106,11 @@ void Widget::resizeEvent(QResizeEvent *e) { QSize scrollSize(width(), height() - _fixedBar->height()); if (_scroll->size() != scrollSize) { _scroll->resize(scrollSize); - _inner->resizeToWidth(scrollSize.width(), _scroll->height() * 2); +// _inner->resizeToWidth(scrollSize.width(), _scroll->height()); + _inner->resizeToWidth(scrollSize.width(), _scroll->height() * 2); // testing } + _fixedBar->setHideShareContactButton(!_inner->shareContactButtonShown()); + if (!_scroll->isHidden()) { if (topDelta()) { _scroll->scrollToY(newScrollTop); diff --git a/Telegram/SourceFiles/ui/buttons/round_button.cpp b/Telegram/SourceFiles/ui/buttons/round_button.cpp index 0edef5a04..f0ab81ce4 100644 --- a/Telegram/SourceFiles/ui/buttons/round_button.cpp +++ b/Telegram/SourceFiles/ui/buttons/round_button.cpp @@ -65,12 +65,17 @@ void RoundButton::paintEvent(QPaintEvent *e) { p.setOpacity(o); App::roundRect(p, rect(), _st.textBgOver); p.setOpacity(1); - p.setPen(a_textFg.current()); - } else { - p.setPen(_st.textFg); } - p.setFont(_st.font); - p.drawText((width() - _textWidth) / 2, _st.textTop + _st.font->ascent, _text); + if (!_text.isEmpty()) { + if (o > 0) { + p.setPen(a_textFg.current()); + } else { + p.setPen(_st.textFg); + } + p.setFont(_st.font); + p.drawText((width() - _textWidth) / 2, _st.textTop + _st.font->ascent, _text); + } + _st.icon.paint(p, QPoint(0, 0), width()); } void RoundButton::step_over(float64 ms, bool timer) { diff --git a/Telegram/SourceFiles/ui/scrollarea.cpp b/Telegram/SourceFiles/ui/scrollarea.cpp index 3f5a93c3e..a18614598 100644 --- a/Telegram/SourceFiles/ui/scrollarea.cpp +++ b/Telegram/SourceFiles/ui/scrollarea.cpp @@ -677,6 +677,7 @@ void ScrollArea::leaveEvent(QEvent *e) { void ScrollArea::scrollToY(int toTop, int toBottom) { myEnsureResized(widget()); + myEnsureResized(this); int toMin = 0, toMax = scrollTopMax(); if (toTop < toMin) { From a06a989f9717c226eca2ded745157cf8094990c0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 27 May 2016 18:45:35 +0300 Subject: [PATCH 17/30] New profile fixed top bar now is peer observer. Multiple showAnimated() calls for SectionWidget are now allowed. --- Telegram/SourceFiles/app.cpp | 49 ++++++++++++++++--- Telegram/SourceFiles/historywidget.cpp | 6 ++- Telegram/SourceFiles/observer_peer.h | 6 ++- .../SourceFiles/profile/profile_fixed_bar.cpp | 35 ++++++++++--- .../SourceFiles/profile/profile_fixed_bar.h | 15 ++++-- .../SourceFiles/profile/profile_widget.cpp | 2 +- Telegram/SourceFiles/structs.cpp | 6 ++- Telegram/SourceFiles/structs.h | 16 +++++- .../SourceFiles/window/section_widget.cpp | 3 +- 9 files changed, 112 insertions(+), 26 deletions(-) diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 260a5268c..be22abcf3 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -381,7 +381,8 @@ namespace { PeerId peer(peerFromUser(d.vid.v)); data = App::user(peer); - auto canShareThisContact = data->canShareThisContact(); + 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)); @@ -390,13 +391,15 @@ namespace { data->access = UserNoAccess; data->flags = 0; data->setBotInfoVersion(-1); - wasContact = (data->contact > 0); status = &emptyStatus; data->contact = -1; - if (canShareThisContact != data->canShareThisContact()) { + 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()); @@ -404,8 +407,8 @@ namespace { PeerId peer(peerFromUser(d.vid.v)); data = App::user(peer); - auto canShareThisContact = data->canShareThisContact(); - + auto canShareThisContact = data->canShareThisContactFast(); + wasContact = data->isContact(); if (!minimal) { data->flags = d.vflags.v; if (d.is_self()) { @@ -468,7 +471,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); @@ -489,9 +491,12 @@ namespace { } } - if (canShareThisContact != data->canShareThisContact()) { + if (canShareThisContact != data->canShareThisContactFast()) { update.flags |= UpdateFlag::UserCanShareContact; } + if (wasContact != data->isContact()) { + update.flags |= UpdateFlag::UserIsContact; + } } break; } @@ -647,6 +652,7 @@ namespace { auto wasInChannel = cdata->amIn(); auto canEditPhoto = cdata->canEditPhoto(); auto canAddMembers = cdata->canAddParticipants(); + auto wasEditor = cdata->amEditor(); if (minimal) { MTPDchannel::Flags mask = MTPDchannel::Flag::f_broadcast | MTPDchannel::Flag::f_verified | MTPDchannel::Flag::f_megagroup | MTPDchannel::Flag::f_democracy; @@ -681,6 +687,9 @@ namespace { if (canAddMembers != cdata->canAddParticipants()) { update.flags |= UpdateFlag::ChannelCanAddMembers; } + if (wasEditor != cdata->amEditor()) { + update.flags |= UpdateFlag::ChannelAmEditor; + } } break; case mtpc_channelForbidden: { auto &d(chat.c_channelForbidden()); @@ -693,6 +702,7 @@ namespace { auto wasInChannel = cdata->amIn(); auto canEditPhoto = cdata->canEditPhoto(); auto canAddMembers = cdata->canAddParticipants(); + auto wasEditor = cdata->amEditor(); cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash); @@ -713,6 +723,9 @@ namespace { if (canAddMembers != cdata->canAddParticipants()) { update.flags |= UpdateFlag::ChannelCanAddMembers; } + if (wasEditor != cdata->amEditor()) { + update.flags |= UpdateFlag::ChannelAmEditor; + } } break; } if (!data) continue; @@ -1227,7 +1240,7 @@ namespace { void feedUserLinkDelayed(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: @@ -1250,6 +1263,12 @@ namespace { user->contact = 0; } } + + if (wasContact != user->isContact()) { + Notify::PeerUpdate update(user); + update.flags |= Notify::PeerUpdateFlag::UserIsContact; + Notify::peerUpdatedDelayed(update); + } if ((user->contact > 0 && !wasContact) || (wasContact && user->contact < 1)) { Notify::userIsContactChanged(user); } @@ -2353,11 +2372,25 @@ 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::PeerUpdate update(user); + update.flags |= Notify::PeerUpdateFlag::UserCanShareContact; + Notify::peerUpdatedDelayed(update); + } } 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::PeerUpdate update(user); + update.flags |= Notify::PeerUpdateFlag::UserCanShareContact; + Notify::peerUpdatedDelayed(update); + } } const SharedContactItems &sharedContactItems() { diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 281848506..4375ede39 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -5034,12 +5034,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) { diff --git a/Telegram/SourceFiles/observer_peer.h b/Telegram/SourceFiles/observer_peer.h index 18a425bcf..06f07b73a 100644 --- a/Telegram/SourceFiles/observer_peer.h +++ b/Telegram/SourceFiles/observer_peer.h @@ -35,12 +35,14 @@ enum class PeerUpdateFlag { PhotoChanged = 0x00000004U, UserCanShareContact = 0x00010000U, + UserIsContact = 0x00020000U, ChatCanEdit = 0x00010000U, ChannelAmIn = 0x00010000U, - ChannelCanEditPhoto = 0x00020000U, - ChannelCanAddMembers = 0x00040000U, + ChannelAmEditor = 0x00020000U, + ChannelCanEditPhoto = 0x00040000U, + ChannelCanAddMembers = 0x00080000U, }; Q_DECLARE_FLAGS(PeerUpdateFlags, PeerUpdateFlag); Q_DECLARE_OPERATORS_FOR_FLAGS(PeerUpdateFlags); diff --git a/Telegram/SourceFiles/profile/profile_fixed_bar.cpp b/Telegram/SourceFiles/profile/profile_fixed_bar.cpp index 4c09eb88c..71980895f 100644 --- a/Telegram/SourceFiles/profile/profile_fixed_bar.cpp +++ b/Telegram/SourceFiles/profile/profile_fixed_bar.cpp @@ -26,6 +26,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include "boxes/addcontactbox.h" #include "boxes/confirmbox.h" +#include "observer_peer.h" namespace Profile { @@ -60,6 +61,15 @@ private: }; +namespace { + +const Notify::PeerUpdateFlags ButtonsUpdateFlags = Notify::PeerUpdateFlag::UserCanShareContact + | Notify::PeerUpdateFlag::UserIsContact + | Notify::PeerUpdateFlag::ChatCanEdit + | Notify::PeerUpdateFlag::ChannelAmEditor; + +} // namespace + FixedBar::FixedBar(QWidget *parent, PeerData *peer) : TWidget(parent) , _peer(peer) , _peerUser(peer->asUser()) @@ -70,9 +80,20 @@ FixedBar::FixedBar(QWidget *parent, PeerData *peer) : TWidget(parent) _backButton->moveToLeft(0, 0); connect(_backButton, SIGNAL(clicked()), this, SLOT(onBack())); + Notify::registerPeerObserver(ButtonsUpdateFlags, this, &FixedBar::notifyPeerUpdate); + refreshRightActions(); } +void FixedBar::notifyPeerUpdate(const Notify::PeerUpdate &update) { + if (update.peer != _peer) { + return; + } + if ((update.flags & ButtonsUpdateFlags) != 0) { + refreshRightActions(); + } +} + void FixedBar::refreshRightActions() { _currentAction = 0; if (_peerUser) { @@ -92,13 +113,14 @@ void FixedBar::refreshRightActions() { } void FixedBar::setUserActions() { - if (_peerUser->contact > 0) { + 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())); - addRightAction(RightActionType::ShareContact, lang(lng_profile_top_bar_share_contact), SLOT(onShareContact())); - } else if (_peerUser->contact == 0 || !App::phoneFromSharedContact(peerToUser(_peer->id)).isEmpty()) { + } else if (_peerUser->canAddContact()) { addRightAction(RightActionType::AddContact, lang(lng_profile_add_contact), SLOT(onAddContact())); - addRightAction(RightActionType::ShareContact, lang(lng_profile_top_bar_share_contact), SLOT(onShareContact())); } } @@ -129,9 +151,10 @@ void FixedBar::addRightAction(RightActionType type, const QString &text, const c } } else { t_assert(_rightActions.size() == _currentAction); - _rightActions.push_back({}); + _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); @@ -235,7 +258,7 @@ void FixedBar::setHideShareContactButton(bool hideButton) { void FixedBar::applyHideShareContactButton() { for_const (auto &action, _rightActions) { if (action.type == RightActionType::ShareContact) { - action.button->setVisible(_hideShareContactButton); + action.button->setVisible(!_hideShareContactButton); } } } diff --git a/Telegram/SourceFiles/profile/profile_fixed_bar.h b/Telegram/SourceFiles/profile/profile_fixed_bar.h index fdc685057..0b6d131e0 100644 --- a/Telegram/SourceFiles/profile/profile_fixed_bar.h +++ b/Telegram/SourceFiles/profile/profile_fixed_bar.h @@ -20,11 +20,17 @@ 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 { +class FixedBar final : public TWidget, public Notify::Observer { Q_OBJECT public: @@ -57,6 +63,8 @@ private slots: void onLeaveGroupSure(); private: + void notifyPeerUpdate(const Notify::PeerUpdate &update); + void refreshRightActions(); void setUserActions(); void setChatActions(); @@ -64,6 +72,7 @@ private: void setChannelActions(); enum class RightActionType { + None, EditChannel, EditGroup, LeaveGroup, @@ -86,8 +95,8 @@ private: int _currentAction = 0; struct RightAction { - RightActionType type; - FlatButton *button; + RightActionType type = RightActionType::None; + FlatButton *button = nullptr; }; QList _rightActions; diff --git a/Telegram/SourceFiles/profile/profile_widget.cpp b/Telegram/SourceFiles/profile/profile_widget.cpp index 15a258e07..2ee66562f 100644 --- a/Telegram/SourceFiles/profile/profile_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_widget.cpp @@ -109,7 +109,7 @@ void Widget::resizeEvent(QResizeEvent *e) { // _inner->resizeToWidth(scrollSize.width(), _scroll->height()); _inner->resizeToWidth(scrollSize.width(), _scroll->height() * 2); // testing } - _fixedBar->setHideShareContactButton(!_inner->shareContactButtonShown()); + _fixedBar->setHideShareContactButton(_inner->shareContactButtonShown()); if (!_scroll->isHidden()) { if (topDelta()) { diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index a3e3f72bd..2d0971297 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -198,6 +198,10 @@ const Text &BotCommand::descriptionText() const { return _descriptionText; } +bool UserData::canShareThisContact() const { + return canShareThisContactFast() || !App::phoneFromSharedContact(peerToUser(id)).isEmpty(); +} + void UserData::setPhoto(const MTPUserProfilePhoto &p) { // see Local::readPeer as well PhotoId newPhotoId = photoId; ImagePtr newPhoto = _userpic; @@ -490,7 +494,7 @@ void ChannelData::flagsUpdated() { } } else if (mgInfo) { delete mgInfo; - mgInfo = 0; + mgInfo = nullptr; } } diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index 99698b27d..e14eb290b 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -429,8 +429,20 @@ public: bool canWrite() const { return access != UserNoAccess; } - bool canShareThisContact() const { - return contact >= 0; + bool isContact() const { + return (contact > 0); + } + + bool canShareThisContact() const; + bool canAddContact() const { + return canShareThisContact() && !isContact(); + } + + // In feedUsers() we check only that. + // When actually trying to share contact we perform + // a full check by canShareThisContact() call. + bool canShareThisContactFast() const { + return !phone.isEmpty(); } MTPInputUser inputUser; diff --git a/Telegram/SourceFiles/window/section_widget.cpp b/Telegram/SourceFiles/window/section_widget.cpp index f9c3a1454..47c085446 100644 --- a/Telegram/SourceFiles/window/section_widget.cpp +++ b/Telegram/SourceFiles/window/section_widget.cpp @@ -41,7 +41,7 @@ void SectionWidget::setGeometryWithTopMoved(const QRect &newGeometry, int topDel } void SectionWidget::showAnimated(SlideDirection direction, const SectionSlideParams ¶ms) { - t_assert(_showAnimation == nullptr); + if (_showAnimation) return; showChildren(); auto myContentCache = grabForShowAnimation(params); @@ -69,6 +69,7 @@ void SectionWidget::paintEvent(QPaintEvent *e) { } void SectionWidget::showFinished() { + _showAnimation.reset(); if (isHidden()) return; App::app()->mtpUnpause(); From 41c8df029a66c1a04f17369cff233ad78f6def86 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 31 May 2016 22:27:11 +0300 Subject: [PATCH 18/30] New profile blocks started. Info block fully ready. All block widgets added (currently empty). About text and phone number PeerUpdateFlag added for observers. --- Telegram/Resources/langs/lang.strings | 10 +- Telegram/SourceFiles/apiwrap.cpp | 4 +- Telegram/SourceFiles/app.cpp | 23 +- Telegram/SourceFiles/boxes/aboutbox.cpp | 6 +- Telegram/SourceFiles/boxes/addcontactbox.cpp | 33 ++- Telegram/SourceFiles/boxes/addcontactbox.h | 6 +- Telegram/SourceFiles/boxes/confirmbox.cpp | 7 +- Telegram/SourceFiles/boxes/confirmbox.h | 2 +- Telegram/SourceFiles/core/click_handler.h | 1 + .../SourceFiles/core/click_handler_types.cpp | 25 +- .../SourceFiles/core/click_handler_types.h | 18 ++ Telegram/SourceFiles/historywidget.cpp | 2 +- Telegram/SourceFiles/intro/introphone.cpp | 2 +- Telegram/SourceFiles/intro/introstart.cpp | 2 +- Telegram/SourceFiles/localstorage.cpp | 5 +- Telegram/SourceFiles/mainwidget.cpp | 13 +- Telegram/SourceFiles/observer_peer.h | 2 + Telegram/SourceFiles/profile/profile.style | 28 ++- .../profile/profile_actions_widget.cpp | 40 ++++ .../profile/profile_actions_widget.h | 37 +++ .../profile/profile_block_widget.cpp | 8 +- .../profile/profile_block_widget.h | 11 + .../SourceFiles/profile/profile_cover.cpp | 97 ++++---- Telegram/SourceFiles/profile/profile_cover.h | 14 +- .../SourceFiles/profile/profile_fixed_bar.cpp | 4 +- .../profile/profile_info_widget.cpp | 217 ++++++++++++++++++ .../SourceFiles/profile/profile_info_widget.h | 72 ++++++ .../profile/profile_inner_widget.cpp | 168 +++++++++++++- .../profile/profile_inner_widget.h | 35 ++- .../profile/profile_invite_link_widget.cpp | 40 ++++ .../profile/profile_invite_link_widget.h | 37 +++ .../profile/profile_members_widget.cpp | 51 ++++ .../profile/profile_members_widget.h | 47 ++++ .../profile/profile_settings_widget.cpp | 40 ++++ .../profile/profile_settings_widget.h | 37 +++ .../profile/profile_shared_media_widget.cpp | 40 ++++ .../profile/profile_shared_media_widget.h | 37 +++ Telegram/SourceFiles/profilewidget.cpp | 26 +-- Telegram/SourceFiles/settingswidget.cpp | 2 +- Telegram/SourceFiles/structs.cpp | 26 ++- Telegram/SourceFiles/structs.h | 23 +- Telegram/SourceFiles/ui/flatinput.cpp | 4 +- Telegram/SourceFiles/ui/flatlabel.cpp | 87 +++++-- Telegram/SourceFiles/ui/flatlabel.h | 24 +- Telegram/SourceFiles/ui/twidget.h | 14 ++ .../SourceFiles/window/top_bar_widget.cpp | 2 +- Telegram/Telegram.vcxproj | 18 +- Telegram/Telegram.vcxproj.filters | 60 ++++- 48 files changed, 1342 insertions(+), 165 deletions(-) create mode 100644 Telegram/SourceFiles/profile/profile_actions_widget.cpp create mode 100644 Telegram/SourceFiles/profile/profile_actions_widget.h create mode 100644 Telegram/SourceFiles/profile/profile_info_widget.cpp create mode 100644 Telegram/SourceFiles/profile/profile_info_widget.h create mode 100644 Telegram/SourceFiles/profile/profile_invite_link_widget.cpp create mode 100644 Telegram/SourceFiles/profile/profile_invite_link_widget.h create mode 100644 Telegram/SourceFiles/profile/profile_members_widget.cpp create mode 100644 Telegram/SourceFiles/profile/profile_members_widget.h create mode 100644 Telegram/SourceFiles/profile/profile_settings_widget.cpp create mode 100644 Telegram/SourceFiles/profile/profile_settings_widget.h create mode 100644 Telegram/SourceFiles/profile/profile_shared_media_widget.cpp create mode 100644 Telegram/SourceFiles/profile/profile_shared_media_widget.h diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 7b9adcfee..041fd7827 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -405,12 +405,14 @@ 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_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"; @@ -912,6 +914,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 diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 751757c63..11f67b836 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -276,7 +276,7 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt } break; } } - channel->about = qs(f.vabout); + channel->setAbout(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()) { @@ -343,7 +343,7 @@ void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result, mtpRequestI peer->asUser()->setBotInfoVersion(-1); } peer->asUser()->blocked = d.is_blocked() ? UserIsBlocked : UserIsNotBlocked; - peer->asUser()->about = d.has_about() ? qs(d.vabout) : QString(); + peer->asUser()->setAbout(d.has_about() ? qs(d.vabout) : QString()); if (req) { QMap::iterator i = _fullPeerRequests.find(peer); diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index be22abcf3..c157dddc8 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -428,7 +428,10 @@ namespace { } } if (d.is_deleted()) { - data->setPhone(QString()); + if (!data->phone().isEmpty()) { + data->setPhone(QString()); + update.flags |= UpdateFlag::UserPhoneChanged; + } data->setNameDelayed(lang(lng_deleted), QString(), QString(), QString()); data->setPhoto(MTP_userProfilePhotoEmpty()); data->access = UserNoAccess; @@ -440,12 +443,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(); @@ -480,7 +485,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(); @@ -522,7 +527,7 @@ namespace { case mtpc_userStatusOnline: data->onlineTill = status->c_userStatusOnline().vexpires.v; break; } - 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()) { @@ -1259,7 +1264,7 @@ 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; } } @@ -1276,7 +1281,7 @@ namespace { bool showPhone = !isServiceUser(user->id) && !user->isSelf() && !user->contact; bool showPhoneChanged = !isServiceUser(user->id) && !user->isSelf() && ((showPhone && !wasShowPhone) || (!showPhone && wasShowPhone)); if (showPhoneChanged) { - user->setNameDelayed(textOneLine(user->firstName), textOneLine(user->lastName), showPhone ? App::formatPhone(user->phone) : QString(), textOneLine(user->username)); + user->setNameDelayed(textOneLine(user->firstName), textOneLine(user->lastName), showPhone ? App::formatPhone(user->phone()) : QString(), textOneLine(user->username)); } markPeerUpdated(user); } diff --git a/Telegram/SourceFiles/boxes/aboutbox.cpp b/Telegram/SourceFiles/boxes/aboutbox.cpp index c5bc8cdf5..7e64bd52d 100644 --- a/Telegram/SourceFiles/boxes/aboutbox.cpp +++ b/Telegram/SourceFiles/boxes/aboutbox.cpp @@ -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]"))); diff --git a/Telegram/SourceFiles/boxes/addcontactbox.cpp b/Telegram/SourceFiles/boxes/addcontactbox.cpp index f1bea515d..abe636529 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.cpp +++ b/Telegram/SourceFiles/boxes/addcontactbox.cpp @@ -33,16 +33,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #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); } @@ -57,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(); } @@ -191,7 +186,7 @@ void AddContactBox::onSave() { _sentName = firstName; if (_user) { _contactId = rand_value(); - QVector v(1, MTP_inputPhoneContact(MTP_long(_contactId), MTP_string(_user->phone), MTP_string(firstName), MTP_string(lastName))); + QVector v(1, MTP_inputPhoneContact(MTP_long(_contactId), MTP_string(_user->phone()), MTP_string(firstName), MTP_string(lastName))); _addRequest = MTP::send(MTPcontacts_ImportContacts(MTP_vector(v), MTP_bool(false)), rpcDone(&AddContactBox::onSaveUserDone), rpcFail(&AddContactBox::onSaveUserFail)); } else { _contactId = rand_value(); @@ -1181,7 +1176,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) @@ -1322,7 +1317,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)); @@ -1357,9 +1352,11 @@ 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); + } + Notify::peerUpdatedSendDelayed(); } saveSign(); return true; @@ -1386,9 +1383,11 @@ 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); + } + Notify::peerUpdatedSendDelayed(); } saveSign(); } diff --git a/Telegram/SourceFiles/boxes/addcontactbox.h b/Telegram/SourceFiles/boxes/addcontactbox.h index 90e55311c..e70b1ac93 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.h +++ b/Telegram/SourceFiles/boxes/addcontactbox.h @@ -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; }; diff --git a/Telegram/SourceFiles/boxes/confirmbox.cpp b/Telegram/SourceFiles/boxes/confirmbox.cpp index fcd6b5e1d..f4c044e53 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.cpp +++ b/Telegram/SourceFiles/boxes/confirmbox.cpp @@ -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) diff --git a/Telegram/SourceFiles/boxes/confirmbox.h b/Telegram/SourceFiles/boxes/confirmbox.h index 4af3c49af..e7af61470 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.h +++ b/Telegram/SourceFiles/boxes/confirmbox.h @@ -217,7 +217,7 @@ private: BoxButton _pin, _cancel; - mtpRequestId _requestId; + mtpRequestId _requestId = 0; }; diff --git a/Telegram/SourceFiles/core/click_handler.h b/Telegram/SourceFiles/core/click_handler.h index c0199fac9..8ceadfb81 100644 --- a/Telegram/SourceFiles/core/click_handler.h +++ b/Telegram/SourceFiles/core/click_handler.h @@ -27,6 +27,7 @@ enum ExpandLinksMode { ExpandLinksNone, ExpandLinksShortened, ExpandLinksAll, + ExpandLinksUrlOnly, // For custom urls leaves only url instead of text. }; class ClickHandlerHost { diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index 05043abf2..23b34fb76 100644 --- a/Telegram/SourceFiles/core/click_handler_types.cpp +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -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) { diff --git a/Telegram/SourceFiles/core/click_handler_types.h b/Telegram/SourceFiles/core/click_handler_types.h index d25078278..84deafe77 100644 --- a/Telegram/SourceFiles/core/click_handler_types.h +++ b/Telegram/SourceFiles/core/click_handler_types.h @@ -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; + }; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 4375ede39..3e41bac0a 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -5034,7 +5034,7 @@ void HistoryWidget::onBroadcastSilentChange() { } void HistoryWidget::onShareContact(const PeerId &peer, UserData *contact) { - auto phone = contact->phone; + auto phone = contact->phone(); if (phone.isEmpty()) phone = App::phoneFromSharedContact(peerToUser(contact->id)); if (!contact || phone.isEmpty()) return; diff --git a/Telegram/SourceFiles/intro/introphone.cpp b/Telegram/SourceFiles/intro/introphone.cpp index e5fcb4956..48cc83f8b 100644 --- a/Telegram/SourceFiles/intro/introphone.cpp +++ b/Telegram/SourceFiles/intro/introphone.cpp @@ -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); diff --git a/Telegram/SourceFiles/intro/introstart.cpp b/Telegram/SourceFiles/intro/introstart.cpp index 8753300fc..bc3c520ff 100644 --- a/Telegram/SourceFiles/intro/introstart.cpp +++ b/Telegram/SourceFiles/intro/introstart.cpp @@ -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(); diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index a94231f4a..9127b6abb 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -3407,7 +3407,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) { @@ -3436,7 +3436,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); } @@ -3490,6 +3490,7 @@ namespace Local { QString pname = (showPhone && !phone.isEmpty()) ? App::formatPhone(phone) : QString(); if (!wasLoaded) { + user->setPhone(phone); user->setNameDelayed(first, last, pname, username); user->access = access; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 3a60bfb44..b3c12ede1 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -4389,9 +4389,16 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { case mtpc_updateUserPhone: { auto &d(update.c_updateUserPhone()); if (auto user = App::userLoaded(d.vuser_id.v)) { - user->setPhone(qs(d.vphone)); - user->setNameDelayed(user->firstName, user->lastName, (user->contact || isServiceUser(user->id) || user->isSelf() || user->phone.isEmpty()) ? QString() : App::formatPhone(user->phone), user->username); - App::markPeerUpdated(user); + auto newPhone = qs(d.vphone); + if (newPhone != user->phone()) { + user->setPhone(newPhone); + user->setNameDelayed(user->firstName, user->lastName, (user->contact || isServiceUser(user->id) || user->isSelf() || user->phone().isEmpty()) ? QString() : App::formatPhone(user->phone()), user->username); + App::markPeerUpdated(user); + + Notify::PeerUpdate update(user); + update.flags |= Notify::PeerUpdateFlag::UserPhoneChanged; + Notify::peerUpdatedDelayed(update); + } } } break; diff --git a/Telegram/SourceFiles/observer_peer.h b/Telegram/SourceFiles/observer_peer.h index 06f07b73a..797dd562d 100644 --- a/Telegram/SourceFiles/observer_peer.h +++ b/Telegram/SourceFiles/observer_peer.h @@ -33,9 +33,11 @@ enum class PeerUpdateFlag { NameChanged = 0x00000001U, UsernameChanged = 0x00000002U, PhotoChanged = 0x00000004U, + AboutChanged = 0x00000008U, UserCanShareContact = 0x00010000U, UserIsContact = 0x00020000U, + UserPhoneChanged = 0x00040000U, ChatCanEdit = 0x00010000U, diff --git a/Telegram/SourceFiles/profile/profile.style b/Telegram/SourceFiles/profile/profile.style index 76fc87208..cd0577934 100644 --- a/Telegram/SourceFiles/profile/profile.style +++ b/Telegram/SourceFiles/profile/profile.style @@ -37,7 +37,8 @@ profileFixedBarButton: flatButton(topBarButton) { profileMarginTop: 13px; profilePhotoSize: 112px; -profilePhotoLeft: 35px; +profilePhotoLeftMin: 18px; +profilePhotoLeftMax: 45px; profilePhotoDuration: 500; profileNameLeft: 26px; profileNameTop: 9px; @@ -106,7 +107,28 @@ profileDividerFill: icon { }; profileBlocksTop: 7px; +profileBlocksBottom: 20px; +profileBlockLeftMin: 8px; +profileBlockLeftMax: 25px; +profileBlockNarrowWidthMin: 220px; +profileBlockWideWidthMin: 300px; +profileBlockWideWidthMax: 340px; profileBlockMarginTop: 21px; -profileBlockTitleFont: semiboldFont; +profileBlockMarginRight: 10px; +profileBlockMarginBottom: 4px; +profileBlockTitleHeight: 22px; +profileBlockTitleFont: font(14px semibold); profileBlockTitleFg: black; -profileBlockTitlePosition: point(16px, profileBlockMarginTop); \ No newline at end of file +profileBlockTitlePosition: point(24px, -7px); +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; diff --git a/Telegram/SourceFiles/profile/profile_actions_widget.cpp b/Telegram/SourceFiles/profile/profile_actions_widget.cpp new file mode 100644 index 000000000..aec40e5c8 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_actions_widget.cpp @@ -0,0 +1,40 @@ +/* +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 "lang.h" + +namespace Profile { + +ActionsWidget::ActionsWidget(QWidget *parent, PeerData *peer) : BlockWidget(parent, peer, lang(lng_profile_actions_section)) +{ + show(); +} + +int ActionsWidget::resizeGetHeight(int newWidth) { + int newHeight = contentTop(); + + return newHeight; +} + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_actions_widget.h b/Telegram/SourceFiles/profile/profile_actions_widget.h new file mode 100644 index 000000000..118c2d0d5 --- /dev/null +++ b/Telegram/SourceFiles/profile/profile_actions_widget.h @@ -0,0 +1,37 @@ +/* +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 Profile { + +class ActionsWidget : public BlockWidget { +public: + ActionsWidget(QWidget *parent, PeerData *peer); + +protected: + // Resizes content and counts natural widget height for the desired width. + int resizeGetHeight(int newWidth) override; + +}; + +} // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_block_widget.cpp b/Telegram/SourceFiles/profile/profile_block_widget.cpp index abfe8453b..6548fbb61 100644 --- a/Telegram/SourceFiles/profile/profile_block_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_block_widget.cpp @@ -34,12 +34,18 @@ 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); - p.drawText(st::profileBlockTitlePosition, _title); + int titleLeft = st::profileBlockTitlePosition.x(); + int titleTop = st::profileBlockMarginTop + st::profileBlockTitlePosition.y(); + p.drawTextLeft(titleLeft, titleTop, width(), _title); paintContents(p); } diff --git a/Telegram/SourceFiles/profile/profile_block_widget.h b/Telegram/SourceFiles/profile/profile_block_widget.h index 313e0d747..8607aa0b5 100644 --- a/Telegram/SourceFiles/profile/profile_block_widget.h +++ b/Telegram/SourceFiles/profile/profile_block_widget.h @@ -35,14 +35,25 @@ public: virtual void setVisibleTopBottom(int visibleTop, int visibleBottom) { } +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; } diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp index fd8937987..8ed491b80 100644 --- a/Telegram/SourceFiles/profile/profile_cover.cpp +++ b/Telegram/SourceFiles/profile/profile_cover.cpp @@ -80,7 +80,7 @@ CoverWidget::CoverWidget(QWidget *parent, PeerData *peer) : TWidget(parent) , _peerChannel(peer->asChannel()) , _peerMegagroup(peer->isMegagroup() ? _peerChannel : nullptr) , _userpicButton(this, peer) -, _name(this, QString(), st::profileNameLabel) { +, _name(this, st::profileNameLabel) { setAttribute(Qt::WA_OpaquePaintEvent); setAcceptDrops(true); @@ -109,20 +109,23 @@ void CoverWidget::onPhotoShow() { } } +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; - _userpicButton->moveToLeft(st::profilePhotoLeft, newHeight); + + _photoLeft = countPhotoLeft(newWidth); + _userpicButton->moveToLeft(_photoLeft, newHeight); + + refreshNameGeometry(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(); - _name.moveToLeft(nameLeft, nameTop); - int nameWidth = newWidth - infoLeft - st::profileNameLeft - st::profileButtonSkip; - nameWidth += st::profileNameLabel.margin.left() + st::profileNameLabel.margin.right(); - _name.resizeToWidth(nameWidth); - _statusPosition = QPoint(infoLeft + st::profileStatusLeft, _userpicButton->y() + st::profileStatusTop); moveAndToggleButtons(newWidth); @@ -140,29 +143,43 @@ void CoverWidget::resizeToWidth(int newWidth) { 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) { - bool showNextButton = true; - int buttonLeft = st::profilePhotoLeft + _userpicButton->width() + st::profileButtonLeft; + 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->moveToLeft(buttonLeft, st::profileButtonTop); - if (i == 1) { - // If second button is not fitting. - if (buttonLeft + button->width() > buttonsRight) { - button->hide(); + 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->show(); - buttonLeft += button->width() + st::profileButtonSkip; - showNextButton = false; + 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->setVisible(showNextButton); - buttonLeft += button->width() + st::profileButtonSkip; + button.widget->show(); + buttonLeft += button.widget->width() + st::profileButtonSkip; } } } @@ -172,7 +189,7 @@ void CoverWidget::showFinished() { } bool CoverWidget::shareContactButtonShown() const { - return _peerUser && (_buttons.size() > 1) && !(_buttons.at(1)->isHidden()); + return _peerUser && (_buttons.size() > 1) && !(_buttons.at(1).widget->isHidden()); } void CoverWidget::paintEvent(QPaintEvent *e) { @@ -195,8 +212,7 @@ void CoverWidget::resizeDropArea() { void CoverWidget::dropAreaHidden(CoverDropArea *dropArea) { if (_dropArea == dropArea) { - _dropArea->deleteLater(); - _dropArea = nullptr; + _dropArea.destroyDelayed(); } } @@ -314,7 +330,7 @@ void CoverWidget::notifyPeerUpdated(const Notify::PeerUpdate &update) { void CoverWidget::refreshNameText() { _name.setText(App::peerName(_peer)); - update(); + refreshNameGeometry(width()); } void CoverWidget::refreshStatusText() { @@ -394,8 +410,7 @@ void CoverWidget::setUserButtons() { void CoverWidget::setChatButtons() { if (_peerChat->canEdit()) { addButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto())); - addButton(lang(lng_profile_add_participant), SLOT(onAddMember())); - addButton(st::profileAddMemberButton, SLOT(onAddMember())); + addButton(lang(lng_profile_add_participant), SLOT(onAddMember()), &st::profileAddMemberButton); } } @@ -404,8 +419,7 @@ void CoverWidget::setMegagroupButtons() { addButton(lang(lng_profile_set_group_photo), SLOT(onSetPhoto())); } if (_peerMegagroup->canAddParticipants()) { - addButton(lang(lng_profile_add_participant), SLOT(onAddMember())); - addButton(st::profileAddMemberButton, SLOT(onAddMember())); + addButton(lang(lng_profile_add_participant), SLOT(onAddMember()), &st::profileAddMemberButton); } } @@ -422,21 +436,24 @@ void CoverWidget::setChannelButtons() { void CoverWidget::clearButtons() { auto buttons = createAndSwap(_buttons); for_const (auto button, buttons) { - delete button; + delete button.widget; + delete button.replacement; } } -void CoverWidget::addButton(const QString &text, const char *slot) { +void CoverWidget::addButton(const QString &text, const char *slot, const style::BoxButton *replacementStyle) { auto &buttonStyle = _buttons.isEmpty() ? st::profilePrimaryButton : st::profileSecondaryButton; - _buttons.push_back(new Ui::RoundButton(this, text, buttonStyle)); - connect(_buttons.back(), SIGNAL(clicked()), this, slot); - _buttons.back()->show(); -} + auto button = new Ui::RoundButton(this, text, buttonStyle); + connect(button, SIGNAL(clicked()), this, slot); + button->show(); -void CoverWidget::addButton(const style::BoxButton &buttonStyle, const char *slot) { - _buttons.push_back(new Ui::RoundButton(this, QString(), buttonStyle)); - connect(_buttons.back(), SIGNAL(clicked()), this, slot); - _buttons.back()->hide(); + 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::onSendMessage() { diff --git a/Telegram/SourceFiles/profile/profile_cover.h b/Telegram/SourceFiles/profile/profile_cover.h index 47975485b..2a53e31ec 100644 --- a/Telegram/SourceFiles/profile/profile_cover.h +++ b/Telegram/SourceFiles/profile/profile_cover.h @@ -75,6 +75,10 @@ private: 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; + + void refreshNameGeometry(int newWidth); void moveAndToggleButtons(int newWiddth); void refreshNameText(); void refreshStatusText(); @@ -87,8 +91,7 @@ private: void setChannelButtons(); void clearButtons(); - void addButton(const QString &text, const char *slot); - void addButton(const style::BoxButton &buttonStyle, const char *slot); + void addButton(const QString &text, const char *slot, const style::BoxButton *replacementStyle = nullptr); void paintDivider(Painter &p); @@ -112,8 +115,13 @@ private: QPoint _statusPosition; QString _statusText; - QList _buttons; + struct Button { + Ui::RoundButton *widget; + Ui::RoundButton *replacement; + }; + QList