diff --git a/.travis.yml b/.travis.yml index 05de5af4d..57e5165ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,6 +50,7 @@ addons: - libssl-dev - libunity-dev - libva-dev + - libvdpau-dev - libxcb-xkb-dev - libxkbcommon-dev - lintian diff --git a/.travis/build.sh b/.travis/build.sh index e84b64e46..4e0806042 100755 --- a/.travis/build.sh +++ b/.travis/build.sh @@ -28,6 +28,9 @@ GYP_PATCH="$UPSTREAM/Telegram/Patches/gyp.diff" VA_PATH="$BUILD/libva" VA_CACHE_VERSION="2" +VDPAU_PATH="$BUILD/libvdpau" +VDPAU_CACHE_VERSION="1" + FFMPEG_PATH="$BUILD/ffmpeg" FFMPEG_CACHE_VERSION="2" @@ -60,6 +63,9 @@ build() { # libva getVa + # libvdpau + getVdpau + # ffmpeg getFFmpeg @@ -205,6 +211,55 @@ buildVa() { sudo ldconfig } +getVdpau() { + travisStartFold "Getting libvdpau" + + local VDPAU_CACHE="$CACHE/libvdpau" + local VDPAU_CACHE_FILE="$VDPAU_CACHE/.cache.txt" + local VDPAU_CACHE_KEY="${VDPAU_CACHE_VERSION}" + local VDPAU_CACHE_OUTDATED="1" + + if [ ! -d "$VDPAU_CACHE" ]; then + mkdir -p "$VDPAU_CACHE" + fi + + ln -sf "$VDPAU_CACHE" "$VDPAU_PATH" + + if [ -f "$VDPAU_CACHE_FILE" ]; then + local VDPAU_CACHE_KEY_FOUND=`tail -n 1 $VDPAU_CACHE_FILE` + if [ "$VDPAU_CACHE_KEY" == "$VDPAU_CACHE_KEY_FOUND" ]; then + VDPAU_CACHE_OUTDATED="0" + else + info_msg "Cache key '$VDPAU_CACHE_KEY_FOUND' does not match '$VDPAU_CACHE_KEY', rebuilding libvdpau" + fi + fi + if [ "$VDPAU_CACHE_OUTDATED" == "1" ]; then + buildVdpau + sudo echo $VDPAU_CACHE_KEY > "$VDPAU_CACHE_FILE" + else + info_msg "Using cached libvdpau" + fi +} + +buildVdpau() { + info_msg "Downloading and building libvdpau" + + if [ -d "$EXTERNAL/libvdpau" ]; then + rm -rf "$EXTERNAL/libvdpau" + fi + cd $VDPAU_PATH + rm -rf * + + cd "$EXTERNAL" + git clone git://anongit.freedesktop.org/vdpau/libvdpau + + cd "$EXTERNAL/libvdpau" + ./autogen.sh --prefix=$VDPAU_PATH --enable-static + make $MAKE_ARGS + sudo make install + sudo ldconfig +} + getFFmpeg() { travisStartFold "Getting ffmpeg" @@ -541,6 +596,7 @@ buildTelegram() { -Dtravis_defines=${GYP_DEFINES:1} \ -Dlinux_path_xkbcommon=$XKB_PATH \ -Dlinux_path_va=$VA_PATH \ + -Dlinux_path_vdpau=$VDPAU_PATH \ -Dlinux_path_ffmpeg=$FFMPEG_PATH \ -Dlinux_path_openal=$OPENAL_PATH \ -Dlinux_path_qt=$QT_PATH \ diff --git a/Telegram/Patches/openal.diff b/Telegram/Patches/openal.diff index 7d8890cf0..b2a71ad17 100644 --- a/Telegram/Patches/openal.diff +++ b/Telegram/Patches/openal.diff @@ -1,22 +1,22 @@ diff --git a/Alc/backends/winmm.c b/Alc/backends/winmm.c -index bf97ef2..1cd5774 100644 +index 9d8f8e9..8c8e44a 100644 --- a/Alc/backends/winmm.c +++ b/Alc/backends/winmm.c -@@ -221,7 +221,7 @@ FORCE_ALIGN static int ALCwinmmPlayback_mixerProc(void *arg) +@@ -219,7 +219,7 @@ FORCE_ALIGN static int ALCwinmmPlayback_mixerProc(void *arg) SetRTPriority(); althrd_setname(althrd_current(), MIXER_THREAD_NAME); - while(GetMessage(&msg, NULL, 0, 0)) -+ if (!self->killNow) while(GetMessage(&msg, NULL, 0, 0)) ++ if (!self->killNow) while(GetMessage(&msg, NULL, 0, 0)) { if(msg.message != WOM_DONE) continue; -@@ -506,7 +506,7 @@ static int ALCwinmmCapture_captureProc(void *arg) +@@ -504,7 +504,7 @@ static int ALCwinmmCapture_captureProc(void *arg) althrd_setname(althrd_current(), RECORD_THREAD_NAME); - while(GetMessage(&msg, NULL, 0, 0)) -+ if (!self->killNow) while(GetMessage(&msg, NULL, 0, 0)) ++ if (!self->killNow) while(GetMessage(&msg, NULL, 0, 0)) { if(msg.message != WIM_DATA) continue; diff --git a/Telegram/Patches/qtbase_5_3_2.diff b/Telegram/Patches/qtbase_5_3_2.diff index 1fbbb5d1b..97d8ddc07 100644 --- a/Telegram/Patches/qtbase_5_3_2.diff +++ b/Telegram/Patches/qtbase_5_3_2.diff @@ -615,10 +615,49 @@ index c680764..e2a7aaf 100644 continue; diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp -index 7396808..87b4978 100644 +index 7396808..7178aec 100644 --- a/src/widgets/kernel/qwidget.cpp +++ b/src/widgets/kernel/qwidget.cpp -@@ -7983,7 +7983,8 @@ bool QWidget::event(QEvent *event) +@@ -4722,6 +4722,17 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset, + return; // Fully transparent. + + Q_D(QWidget); ++ ++ // Patch: save and restore dirtyOpaqueChildren field. ++ // ++ // Just like in QWidget::grab() this field should be restored ++ // after the d->render() call, because it will be set to 1 and ++ // opaqueChildren field will be filled with empty region in ++ // case the widget is hidden (because all the opaque children ++ // will be skipped in isVisible() check). ++ // ++ const bool oldDirtyOpaqueChildren = d->dirtyOpaqueChildren; ++ + const bool inRenderWithPainter = d->extra && d->extra->inRenderWithPainter; + const QRegion toBePainted = !inRenderWithPainter ? d->prepareToRender(sourceRegion, renderFlags) + : sourceRegion; +@@ -4743,6 +4754,10 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset, + if (!inRenderWithPainter && (opacity < 1.0 || (target->devType() == QInternal::Printer))) { + d->render_helper(painter, targetOffset, toBePainted, renderFlags); + d->extra->inRenderWithPainter = inRenderWithPainter; ++ ++ // Patch: save and restore dirtyOpaqueChildren field. ++ d->dirtyOpaqueChildren = oldDirtyOpaqueChildren; ++ + return; + } + +@@ -4774,6 +4789,9 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset, + d->setSharedPainter(oldPainter); + + d->extra->inRenderWithPainter = inRenderWithPainter; ++ ++ // Patch: save and restore dirtyOpaqueChildren field. ++ d->dirtyOpaqueChildren = oldDirtyOpaqueChildren; + } + + static void sendResizeEvents(QWidget *target) +@@ -7983,7 +8001,8 @@ bool QWidget::event(QEvent *event) case QEvent::KeyPress: { QKeyEvent *k = (QKeyEvent *)event; bool res = false; diff --git a/Telegram/Patches/qtbase_5_6_2.diff b/Telegram/Patches/qtbase_5_6_2.diff index aeb46ca3e..5bd013a69 100644 --- a/Telegram/Patches/qtbase_5_6_2.diff +++ b/Telegram/Patches/qtbase_5_6_2.diff @@ -11896,6 +11896,52 @@ index 6fffa1e..cb1c9c1 100644 void destroyWindow(); inline bool isDropSiteEnabled() const { return m_dropTarget != 0; } void setDropSiteEnabled(bool enabled); +diff --git a/src/plugins/platforms/xcb/qxcbnativeinterface.cpp b/src/plugins/platforms/xcb/qxcbnativeinterface.cpp +index 09e7ecf..c0f15a4 100644 +--- a/src/plugins/platforms/xcb/qxcbnativeinterface.cpp ++++ b/src/plugins/platforms/xcb/qxcbnativeinterface.cpp +@@ -79,7 +79,10 @@ static int resourceType(const QByteArray &key) + QByteArrayLiteral("rootwindow"), + QByteArrayLiteral("subpixeltype"), QByteArrayLiteral("antialiasingenabled"), + QByteArrayLiteral("nofonthinting"), +- QByteArrayLiteral("atspibus") ++ QByteArrayLiteral("atspibus"), ++ ++ // Patch: Backport compositing manager check from Qt 5.7 ++ QByteArrayLiteral("compositingenabled") + }; + const QByteArray *end = names + sizeof(names) / sizeof(names[0]); + const QByteArray *result = std::find(names, end, key); +@@ -252,6 +255,13 @@ void *QXcbNativeInterface::nativeResourceForScreen(const QByteArray &resourceStr + case RootWindow: + result = reinterpret_cast(xcbScreen->root()); + break; ++ ++ // Patch: Backport compositing manager check from Qt 5.7 ++ case CompositingEnabled: ++ if (QXcbVirtualDesktop *vd = xcbScreen->virtualDesktop()) ++ result = vd->compositingActive() ? this : Q_NULLPTR; ++ break; ++ + default: + break; + } +diff --git a/src/plugins/platforms/xcb/qxcbnativeinterface.h b/src/plugins/platforms/xcb/qxcbnativeinterface.h +index f88b710..6f818a5 100644 +--- a/src/plugins/platforms/xcb/qxcbnativeinterface.h ++++ b/src/plugins/platforms/xcb/qxcbnativeinterface.h +@@ -68,7 +68,10 @@ public: + ScreenSubpixelType, + ScreenAntialiasingEnabled, + NoFontHinting, +- AtspiBus ++ AtspiBus, ++ ++ // Patch: Backport compositing manager check from Qt 5.7 ++ CompositingEnabled + }; + + QXcbNativeInterface(); diff --git a/src/widgets/dialogs/qfiledialog.cpp b/src/widgets/dialogs/qfiledialog.cpp index bc2de89..aa8f8df 100644 --- a/src/widgets/dialogs/qfiledialog.cpp @@ -11987,10 +12033,49 @@ index f610e46..547a646 100644 { if (QPlatformFileDialogHelper *helper = platformFileDialogHelper()) diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp -index b1d80d7..4133ed3 100644 +index b1d80d7..42e32fd 100644 --- a/src/widgets/kernel/qwidget.cpp +++ b/src/widgets/kernel/qwidget.cpp -@@ -8769,7 +8769,8 @@ bool QWidget::event(QEvent *event) +@@ -5138,6 +5138,17 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset, + return; // Fully transparent. + + Q_D(QWidget); ++ ++ // Patch: save and restore dirtyOpaqueChildren field. ++ // ++ // Just like in QWidget::grab() this field should be restored ++ // after the d->render() call, because it will be set to 1 and ++ // opaqueChildren field will be filled with empty region in ++ // case the widget is hidden (because all the opaque children ++ // will be skipped in isVisible() check). ++ // ++ const bool oldDirtyOpaqueChildren = d->dirtyOpaqueChildren; ++ + const bool inRenderWithPainter = d->extra && d->extra->inRenderWithPainter; + const QRegion toBePainted = !inRenderWithPainter ? d->prepareToRender(sourceRegion, renderFlags) + : sourceRegion; +@@ -5159,6 +5170,10 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset, + if (!inRenderWithPainter && (opacity < 1.0 || (target->devType() == QInternal::Printer))) { + d->render_helper(painter, targetOffset, toBePainted, renderFlags); + d->extra->inRenderWithPainter = inRenderWithPainter; ++ ++ // Patch: save and restore dirtyOpaqueChildren field. ++ d->dirtyOpaqueChildren = oldDirtyOpaqueChildren; ++ + return; + } + +@@ -5190,6 +5205,9 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset, + d->setSharedPainter(oldPainter); + + d->extra->inRenderWithPainter = inRenderWithPainter; ++ ++ // Patch: save and restore dirtyOpaqueChildren field. ++ d->dirtyOpaqueChildren = oldDirtyOpaqueChildren; + } + + static void sendResizeEvents(QWidget *target) +@@ -8769,7 +8787,8 @@ bool QWidget::event(QEvent *event) case QEvent::KeyPress: { QKeyEvent *k = (QKeyEvent *)event; bool res = false; diff --git a/Telegram/Resources/art/bg.jpg b/Telegram/Resources/art/bg.jpg index f9ee1054a..7fa05b3ed 100644 Binary files a/Telegram/Resources/art/bg.jpg and b/Telegram/Resources/art/bg.jpg differ diff --git a/Telegram/Resources/art/bg0.png b/Telegram/Resources/art/bg_initial.png similarity index 100% rename from Telegram/Resources/art/bg0.png rename to Telegram/Resources/art/bg_initial.png diff --git a/Telegram/Resources/art/blank.gif b/Telegram/Resources/art/blank.gif deleted file mode 100644 index 75b945d25..000000000 Binary files a/Telegram/Resources/art/blank.gif and /dev/null differ diff --git a/Telegram/Resources/art/favicon.ico b/Telegram/Resources/art/favicon.ico index 11474318b..5c23320d6 100644 Binary files a/Telegram/Resources/art/favicon.ico and b/Telegram/Resources/art/favicon.ico differ diff --git a/Telegram/Resources/art/icon128.png b/Telegram/Resources/art/icon128.png index bc337cb5a..0a2c3b969 100644 Binary files a/Telegram/Resources/art/icon128.png and b/Telegram/Resources/art/icon128.png differ diff --git a/Telegram/Resources/art/icon128@2x.png b/Telegram/Resources/art/icon128@2x.png index 8ca330aeb..4a7bbb4e4 100644 Binary files a/Telegram/Resources/art/icon128@2x.png and b/Telegram/Resources/art/icon128@2x.png differ diff --git a/Telegram/Resources/art/icon16.png b/Telegram/Resources/art/icon16.png index d8c95f007..a3553a4bc 100644 Binary files a/Telegram/Resources/art/icon16.png and b/Telegram/Resources/art/icon16.png differ diff --git a/Telegram/Resources/art/icon16@2x.png b/Telegram/Resources/art/icon16@2x.png index 7281f6412..32b6a5686 100644 Binary files a/Telegram/Resources/art/icon16@2x.png and b/Telegram/Resources/art/icon16@2x.png differ diff --git a/Telegram/Resources/art/icon256.ico b/Telegram/Resources/art/icon256.ico index 51e896e63..3a53b3b51 100644 Binary files a/Telegram/Resources/art/icon256.ico and b/Telegram/Resources/art/icon256.ico differ diff --git a/Telegram/Resources/art/icon256.png b/Telegram/Resources/art/icon256.png index 88b743257..2e818f129 100644 Binary files a/Telegram/Resources/art/icon256.png and b/Telegram/Resources/art/icon256.png differ diff --git a/Telegram/Resources/art/icon256@2x.png b/Telegram/Resources/art/icon256@2x.png index 59b2dce23..dc2da2b33 100644 Binary files a/Telegram/Resources/art/icon256@2x.png and b/Telegram/Resources/art/icon256@2x.png differ diff --git a/Telegram/Resources/art/icon32.png b/Telegram/Resources/art/icon32.png index c40bb6b9d..231c1e335 100644 Binary files a/Telegram/Resources/art/icon32.png and b/Telegram/Resources/art/icon32.png differ diff --git a/Telegram/Resources/art/icon32@2x.png b/Telegram/Resources/art/icon32@2x.png index 0f5e77715..c4e635afe 100644 Binary files a/Telegram/Resources/art/icon32@2x.png and b/Telegram/Resources/art/icon32@2x.png differ diff --git a/Telegram/Resources/art/icon48.png b/Telegram/Resources/art/icon48.png index 360347222..ff2b2c759 100644 Binary files a/Telegram/Resources/art/icon48.png and b/Telegram/Resources/art/icon48.png differ diff --git a/Telegram/Resources/art/icon48@2x.png b/Telegram/Resources/art/icon48@2x.png index 06591cca7..c2d9bb58c 100644 Binary files a/Telegram/Resources/art/icon48@2x.png and b/Telegram/Resources/art/icon48@2x.png differ diff --git a/Telegram/Resources/art/icon512.png b/Telegram/Resources/art/icon512.png index daa293ba2..7dc492002 100644 Binary files a/Telegram/Resources/art/icon512.png and b/Telegram/Resources/art/icon512.png differ diff --git a/Telegram/Resources/art/icon512@2x.png b/Telegram/Resources/art/icon512@2x.png index 28455ade1..59ed2b9ab 100644 Binary files a/Telegram/Resources/art/icon512@2x.png and b/Telegram/Resources/art/icon512@2x.png differ diff --git a/Telegram/Resources/art/icon64.png b/Telegram/Resources/art/icon64.png index 6e2f53592..b4111e8db 100644 Binary files a/Telegram/Resources/art/icon64.png and b/Telegram/Resources/art/icon64.png differ diff --git a/Telegram/Resources/art/icon64@2x.png b/Telegram/Resources/art/icon64@2x.png index cd41f0a35..0a2c3b969 100644 Binary files a/Telegram/Resources/art/icon64@2x.png and b/Telegram/Resources/art/icon64@2x.png differ diff --git a/Telegram/Resources/art/icon_green.png b/Telegram/Resources/art/icon_green.png index 9b214e0df..d84881e26 100644 Binary files a/Telegram/Resources/art/icon_green.png and b/Telegram/Resources/art/icon_green.png differ diff --git a/Telegram/Resources/art/iconbig256.png b/Telegram/Resources/art/iconbig256.png index 1f1313834..1a94181b9 100644 Binary files a/Telegram/Resources/art/iconbig256.png and b/Telegram/Resources/art/iconbig256.png differ diff --git a/Telegram/Resources/art/iconbig_green.png b/Telegram/Resources/art/iconbig_green.png index a14b88e32..382a9c962 100644 Binary files a/Telegram/Resources/art/iconbig_green.png and b/Telegram/Resources/art/iconbig_green.png differ diff --git a/Telegram/Resources/art/mac_setup.tiff b/Telegram/Resources/art/mac_setup.tiff new file mode 100644 index 000000000..71d835469 Binary files /dev/null and b/Telegram/Resources/art/mac_setup.tiff differ diff --git a/Telegram/Resources/art/osxsetup.tif b/Telegram/Resources/art/osxsetup.tif deleted file mode 100644 index 8603fd35f..000000000 Binary files a/Telegram/Resources/art/osxsetup.tif and /dev/null differ diff --git a/Telegram/Resources/art/osxsetup.tiff b/Telegram/Resources/art/osxsetup.tiff deleted file mode 100644 index bbd73e042..000000000 Binary files a/Telegram/Resources/art/osxsetup.tiff and /dev/null differ diff --git a/Telegram/Resources/art/osxsetup@2x.tif b/Telegram/Resources/art/osxsetup@2x.tif deleted file mode 100644 index 89a9aa09f..000000000 Binary files a/Telegram/Resources/art/osxsetup@2x.tif and /dev/null differ diff --git a/Telegram/Resources/art/osxtray.png b/Telegram/Resources/art/osxtray.png deleted file mode 100644 index fe7b2b7a4..000000000 Binary files a/Telegram/Resources/art/osxtray.png and /dev/null differ diff --git a/Telegram/Resources/art/sprite.png b/Telegram/Resources/art/sprite.png deleted file mode 100644 index 04c9fa5a8..000000000 Binary files a/Telegram/Resources/art/sprite.png and /dev/null differ diff --git a/Telegram/Resources/art/sprite_200x.png b/Telegram/Resources/art/sprite_200x.png deleted file mode 100644 index 91644529f..000000000 Binary files a/Telegram/Resources/art/sprite_200x.png and /dev/null differ diff --git a/Telegram/Resources/art/sunrise.jpg b/Telegram/Resources/art/sunrise.jpg new file mode 100644 index 000000000..49b188bb0 Binary files /dev/null and b/Telegram/Resources/art/sunrise.jpg differ diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style index 9040e248e..d6487ec1c 100644 --- a/Telegram/Resources/basic.style +++ b/Telegram/Resources/basic.style @@ -18,800 +18,72 @@ 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_types.style"; +using "colors.palette"; + +TextPalette { + linkFg: color; + monoFg: color; + selectBg: color; + selectOverlay: color; +} + +TextStyle { + font: font; + linkFont: font; + linkFontOver: font; + lineHeight: pixels; +} semibold: "Open Sans Semibold"; fsize: 13px; normalFont: font(fsize); semiboldFont: font(fsize semibold); +boxFontSize: 14px; +boxTextFont: font(boxFontSize); emojiImgSize: 18px; // exceptional value for retina emojiSize: 18px; -emojiPadding: 0px; - -counterBG: #f23c34; -counterMuteBG: #888; -counterColor: #fff; -counterMacInvColor: #ffffff01; +emojiPadding: 1px; lineWidth: 1px; -transparent: #fff0; -white: #fff; -black: #000; - -wndMinWidth: 380px; - -adaptiveNormalWidth: 640px; -adaptiveWideWidth: 1366px; - -windowBg: #ffffff; // fallback for background: white -windowActiveBg: #40ace3; // fallback for blue filled active areas -windowTextFg: #000000; // fallback for text color: black -windowSubTextFg: #8a8a8a; // fallback for subtext color: gray -windowSubTextFgOver: #7c99b2; // fallback for subtext over color: gray over blue bg -windowActiveTextFg: #1485c2; // fallback for active color: blue online -windowShadowFg: #000000; // fallback for shadow color - -semiboldButtonBlueText: #2b99d5; - -wndMinHeight: 480px; -wndDefWidth: 800px; -wndDefHeight: 600px; -wndShadow: icon {{ "window_shadow", windowShadowFg }}; -wndShadowShift: 1px; - -layerAlpha: 0.5; -layerBg: black; - -overBg: #edf2f5; - -labelDefFlat: flatLabel { - font: font(fsize); - width: 0px; - maxHeight: 0px; - align: align(left); - textFg: windowTextFg; +defaultTextPalette: TextPalette { + linkFg: windowActiveTextFg; + monoFg: windowSubTextFg; + selectBg: msgInBgSelected; + selectOverlay: msgSelectOverlay; } - -boxBg: white; -boxVerticalMargin: 10px; -boxWidth: 320px; -boxWideWidth: 364px; -boxPadding: margins(26px, 30px, 34px, 8px); -boxMaxListHeight: 600px; -boxFontSize: 14px; -boxTextFont: font(boxFontSize); -boxLittleSkip: 10px; -boxMediumSkip: 20px; - -boxTitleFg: #444444; -boxTitleFont: font(boxFontSize bold); -boxTitlePosition: point(26px, 28px); -boxTitleHeight: 54px; - -boxButtonFont: font(boxFontSize semibold); -defaultBoxButton: RoundButton { - textFg: #2f9fea; - textFgOver: #2f9fea; - secondaryTextFg: #2f9fea; - secondaryTextFgOver: #2f9fea; - textBg: white; - textBgOver: #edf7ff; - - width: -24px; - height: 36px; - padding: margins(0px, 0px, 0px, 0px); - - textTop: 8px; - downTextTop: 9px; - - font: boxButtonFont; - duration: 200; -} -cancelBoxButton: RoundButton(defaultBoxButton) { - textFg: #aeaeae; -} -attentionBoxButton: RoundButton(defaultBoxButton) { - textFg: #ea4b2f; - textFgOver: #ea4b2f; - textBgOver: #fff0ed; -} -boxButtonPadding: margins(12px, 16px, 22px, 16px); -defaultBoxLinkButton: linkButton { - color: #0080c0; - overColor: #0080c0; - downColor: #0073ad; - font: boxTextFont; - overFont: font(boxFontSize underline); -} -redBoxLinkButton: linkButton(defaultBoxLinkButton) { - color: #d15948; - overColor: #d15948; - downColor: #db6352; -} -boxLabel: flatLabel(labelDefFlat) { - font: font(boxFontSize); - align: align(topleft); -} - -defaultLeftOutlineButton: OutlineButton { - outlineWidth: 3px; - outlineFg: windowBg; - outlineFgOver: windowActiveBg; - - textBg: windowBg; - textBgOver: #f2f7fa; - - textFg: windowActiveTextFg; - textFgOver: windowActiveTextFg; - +defaultTextStyle: TextStyle { font: normalFont; - padding: margins(11px, 5px, 11px, 5px); + linkFont: normalFont; + linkFontOver: font(fsize underline); + lineHeight: 0px; } -attentionLeftOutlineButton: OutlineButton(defaultLeftOutlineButton) { - outlineFgOver: #e43f3f; - - textBgOver: #faf2f2; - - textFg: #d15948; - textFgOver: #d15948; +semiboldTextStyle: TextStyle(defaultTextStyle) { + font: semiboldFont; + linkFont: semiboldFont; + linkFontOver: font(fsize semibold underline); } -defaultInputArea: InputArea { - textFg: black; - textMargins: margins(5px, 6px, 5px, 4px); - - placeholderFg: #999; - placeholderFgActive: #aaa; - placeholderMargins: margins(2px, 0px, 2px, 0px); - placeholderAlign: align(topleft); - placeholderShift: 50px; - duration: 120; - - borderFg: #e0e0e0; - borderFgActive: #62c0f7; - borderFgError: #e48383; - - border: 1px; - borderActive: 2px; - borderError: 2px; - - font: boxTextFont; - - heightMin: 32px; - heightMax: 128px; -} -defaultInputField: InputField { - textBg: white; - textFg: black; - textMargins: margins(0px, 6px, 0px, 4px); - textAlign: align(topleft); - - placeholderFg: #999; - placeholderFgActive: #aaa; - placeholderMargins: margins(2px, 0px, 2px, 0px); - placeholderAlign: align(topleft); - placeholderShift: 50px; - duration: 120; - - borderFg: #e0e0e0; - borderFgActive: #62c0f7; - borderFgError: #e48383; - - border: 1px; - borderActive: 2px; - borderError: 2px; - - font: boxTextFont; - - height: 32px; -} -defaultCheckbox: Checkbox { - textFg: black; - textBg: white; - - checkFg: #b3b3b3; - checkFgOver: #b3b3b3; - checkFgActive: #40ace3; - - width: -44px; - height: 22px; - - textPosition: point(32px, 2px); - diameter: 22px; - thickness: 2px; - checkIcon: icon {{ "default_checkbox_check", #ffffff, point(4px, 7px) }}; - - font: normalFont; - duration: 120; -} -defaultBoxCheckbox: Checkbox(defaultCheckbox) { - width: -46px; - textPosition: point(34px, 1px); - font: boxTextFont; -} -defaultRadiobutton: Radiobutton { - textFg: black; - textBg: white; - - checkFg: #b3b3b3; - checkFgOver: #bfbfbf; - checkFgActive: #4eb3ee; - - width: -46px; - height: 22px; - - textPosition: point(34px, 0px); - diameter: 22px; - thickness: 2px; - checkSkip: 65px; // * 0.1 - - font: boxTextFont; - duration: 120; -} -solidScroll: flatScroll { - barColor: #3f729734; - bgColor: #214f751a; - barOverColor: #3f729734; - bgOverColor: #214f751a; - - minHeight: 20px; - - round: 2px; - deltax: 5px; - width: 14px; - deltat: 6px; - deltab: 6px; - - topsh: 0px; - bottomsh: 0px; - shColor: rgba(0, 0, 0, 18); - - duration: 150; - hiding: 0; -} -defaultDropdownShadow: icon {{ "dropdown_shadow", windowShadowFg }}; -defaultPopupMenu: PopupMenu { - skip: 5px; - - shadow: defaultDropdownShadow; - shadowShift: 1px; - - itemBg: white; - itemBgOver: overBg; - itemFg: black; - itemFgOver: black; - itemFgDisabled: #ccc; - itemFgShortcut: #999; - itemFgShortcutOver: #7c99b2; - itemFgShortcutDisabled: #ccc; - - itemPadding: margins(17px, 8px, 17px, 7px); - itemFont: normalFont; - - separatorPadding: margins(0px, 5px, 0px, 5px); - separatorWidth: 1px; - separatorFg: #f1f1f1; - - arrow: icon {{ "dropdown_submenu_arrow", #373737 }}; - - duration: 120; - - widthMin: 180px; - widthMax: 300px; -} - -defaultTooltip: Tooltip { - textBg: #eef2f5; - textFg: #5d6c80; - textFont: normalFont; - textBorder: #c9d1db; - textPadding: margins(5px, 2px, 5px, 2px); - - shift: point(-20px, 20px); - skip: 10px; - - widthMax: 800px; - linesMax: 12; -} - -almostTransparent: #ffffff0d; -boxScroll: flatScroll(solidScroll) { - round: 3px; - width: 18px; - deltax: 6px; -} -boxScrollSkip: 6px; -boxScrollShadowBg: #00000012; - -titleBg: #6389a8; -titleHeight: 39px; -titleFont: font(17px); -titlePos: point(44px, 29px); -titleMenuOffset: 36px; - -titleRed: #ee4928; -titleGray: #777; -titleGreen: #41a903; - -titleStatusFg: #999; -titleStatusActiveFg: #0080c0; -titleTypingFg: #0080c0; - -statusFont: font(fsize); -versionColor: #777; - -shadowColor: rgba(0, 0, 0, 24); shadowToggleDuration: 200; slideDuration: 240; slideShift: 100px; -slideFadeOut: 0.3; -slideShadow: icon {{ "slide_shadow", #000000 }}; -slideFunction: transition(easeOutCirc); - -btnDefIconed: iconedButton { - color: white; - bgColor: white; - overBgColor: white; - font: font(fsize); - - opacity: 0.78; - overOpacity: 1.; - - textPos: point(0px, 0px); - downTextPos: point(0px, 0px); - - duration: 150; - cursor: cursor(pointer); -} - -titleButtonFg: #c4d8e9; -titleButtonActiveFg: #ffffff; -titleButtonDuration: 150; -sysBtnDelta: 6px; -sysUpd: sysButton { - size: size(31px, 39px); - icon: icon {{ "title_button_update", titleBg }}; - color: titleButtonFg; - overColor: titleButtonActiveFg; - duration: titleButtonDuration; -} -updateBlinkDuration: 500; -sysMin: sysButton(sysUpd) { - icon: icon {{ "title_button_minimize", titleBg }}; -} -sysMax: sysButton(sysUpd) { - icon: icon {{ "title_button_maximize", titleBg }}; -} -sysRes: sysButton(sysUpd) { - icon: icon {{ "title_button_restore", titleBg }}; -} -sysCls: sysButton(sysUpd) { - icon: icon {{ "title_button_close", titleBg }}; -} -sysLock: sysButton(sysUpd) { - icon: icon {{ "title_button_lock", titleBg }}; -} -sysUnlock: sysButton(sysUpd) { - icon: icon {{ "title_button_unlock", titleBg }}; -} - -btnWhiteHover: #f5f5f5; -btnBoxWhiteHover: #fafafa; -btnYesColor: #0080c0; -btnYesHover: #0073ad; -btnNoColor: #8b8b8b; -btnNoHover: #777; - -titleTextButton: flatButton { - color: #d4e3ef; - overColor: #fff; - downColor: #fff; - bgColor: transparent; - overBgColor: transparent; - downBgColor: transparent; - - width: -14px; - height: 39px; - - textTop: 10px; - overTextTop: 10px; - downTextTop: 11px; - - font: font(fsize); - overFont: font(fsize); - duration: 150; - cursor: cursor(default); -} - -btnDefFlat: flatButton { - duration: 200; - cursor: cursor(pointer); -} -btnDefBig: flatButton(btnDefFlat) { - textTop: 11px; - overTextTop: 11px; - downTextTop: 12px; - - font: font(23px); - overFont: font(23px); - height: 56px; -} -btnNextBG: #2fa9e2; -btnDefNext: flatButton(btnDefFlat) { - color: white; - overColor: white; - downColor: white; - bgColor: btnNextBG; - overBgColor: #279ad0; - downBgColor: #279ad0; -} -btnDefBack: flatButton(btnDefFlat) { - color: white; - overColor: white; - downColor: white; - bgColor: #c7c7c7; - overBgColor: #b9b9b9; - downBgColor: #b9b9b9; -} +slideShadow: icon {{ "slide_shadow", slideFadeOutShadowFg }}; linkCropLimit: 360px; linkFont: normalFont; linkOverFont: font(fsize underline); -btnDefLink: linkButton { - color: btnYesColor; - overColor: btnYesColor; - downColor: btnYesHover; - font: linkFont; - overFont: linkOverFont; -} -inpDefFont: font(17px); -inpDefFlat: flatInput { - textColor: #000; - bgColor: #FFF; - bgActive: #FFF; - width: 210px; - height: 40px; - align: align(left); - textMrg: margins(5px, 5px, 5px, 5px); - font: inpDefFont; - cursor: cursor(text); - - borderWidth: 0px; - borderColor: black; - borderActive: black; - borderError: black; - - phColor: #949494; - phFocusColor: #AAA; - phAlign: align(left); - phPos: point(2px, 0px); - phShift: 50px; - phDuration: 100; - phLeftFunc: transition(linear); - phAlphaFunc: transition(linear); - phColorFunc: transition(linear); -} - -inpDefGray: flatInput(inpDefFlat) { - bgColor: #f2f2f2; - borderWidth: 2px; - borderColor: #f2f2f2; - borderActive: #54c3f3; - borderError: #ed8080; - phColor: #808080; -} - -taDefFlat: flatTextarea { - textColor: #000; - bgColor: #FFF; - align: align(left); - textMrg: margins(5px, 5px, 5px, 5px); - font: inpDefFont; - cursor: cursor(text); - - phColor: #999; - phFocusColor: #AAA; - phAlign: align(topleft); - phPos: point(2px, 0px); - phShift: 50px; - phDuration: 100; - phLeftFunc: transition(linear); - phAlphaFunc: transition(linear); - phColorFunc: transition(linear); -} - -scrollDef: flatScroll { - barColor: rgba(0, 0, 0, 83); - bgColor: rgba(0, 0, 0, 26); - barOverColor: rgba(0, 0, 0, 122); - bgOverColor: rgba(0, 0, 0, 44); - - round: 2px; - - width: 10px; - minHeight: 20px; - deltax: 3px; - deltat: 3px; - deltab: 3px; - - topsh: 2px; - bottomsh: 2px; - shColor: rgba(0, 0, 0, 18); - - duration: 150; - hiding: 1000; -} - -dateRadius: 10px; +dateRadius: 6px; buttonRadius: 3px; -scrollCountries: flatScroll(scrollDef) { - topsh: 0px; - bottomsh: -2px; -} - -lnkText: #0f7dc7; - -introBtnTop: 288px; -introSkip: 45px; -introFinishSkip: 15px; -introPhotoSize: 98px; -introHeaderFont: font(24px); -introHeaderSkip: 14px; -introIconSkip: 54px; -introFont: font(16px); -introLink: linkButton(btnDefLink) { - font: introFont; - overFont: font(16px underline); -} -introColor: black; -introLabel: flatLabel(labelDefFlat) { - font: introFont; - align: align(center); -} - -introPointsTop: -30px; // intro steps bottom points -introPointWidth: 4px; -introPointHeight: 4px; -introPointHoverWidth: 10px; -introPointHoverHeight: 10px; -introPointLeft: 3px; -introPointTop: 3px; -introPointDelta: 10px; -introPointColor: rgb(0, 0, 0); -introPointAlpha: 0.5; -introPointHoverColor: #86b4e3; -introPointStepT: transition(sineInOut); -introPointAlphaT: transition(linear); -introPointShowStepT: transition(easeOutCirc); -introPointHideStepT: transition(easeInCirc); -introPointShowAlphaT: transition(easeInCirc); -introPointHideAlphaT: transition(easeOutCirc); - -introStepSize: size(400px, 200px); -introSize: size(400px, 400px); -introSlideShift: 500px; // intro hiding animation -introSlideDuration: 200; -introSlideDelta: 0; // between hide start and show start -introHideFunc: transition(easeInCirc); -introShowFunc: transition(easeOutCirc); -introAlphaHideFunc: transition(easeOutCirc); -introAlphaShowFunc: transition(easeInCirc); -introTextTop: 22px; -introTextSize: size(400px, 93px); -introCallSkip: 15px; -introPwdTextSize: size(400px, 73px); - -btnIntroSep: 12px; -btnIntroNext: flatButton(btnDefNext, btnDefBig) { - textTop: 16px; - overTextTop: 16px; - downTextTop: 17px; - - font: font(17px); - overFont: font(17px); - - width: 300px; - radius: buttonRadius; -} - -boxShadow: icon {{ "box_shadow", windowShadowFg }}; -boxShadowShift: 2px; - -introCountry: countryInput { - width: 300px; - height: 41px; - top: 33px; - bgColor: #f2f2f2; - ptrSize: size(15px, 8px); - textMrg: margins(16px, 5px, 16px, 15px); - font: inpDefFont; - align: align(left); -} - -introPhoneTop: 8px; -inpIntroCountryCode: flatInput(inpDefGray) { - width: 70px; - height: 41px; - align: align(center); -} -inpIntroPhone: flatInput(inpDefGray) { - textMrg: margins(12px, 5px, 12px, 6px); - width: 225px; - height: 41px; -} -inpIntroCode: flatInput(inpDefGray) { - textMrg: margins(12px, 5px, 12px, 6px); - width: 106px; - height: 41px; - align: align(center); - - phPos: point(0px, 0px); - phAlign: align(center); - phShift: 0px; -} -inpIntroName: flatInput(inpIntroPhone) { - width: 192px; -} -inpIntroPassword: flatInput(inpIntroPhone) { - width: 300px; -} - -introSelectDelta: 30px; -btnSelectSep: #e0e0e0; -btnRedLink: linkButton(btnDefLink) { - color: #d15948; - overColor: #d15948; - downColor: #db6352; -} - -countryRowHeight: 36px; -countryRowNameFont: semiboldFont; -countryRowPadding: margins(22px, 9px, 8px, 0px); -countryRowCodeFont: font(fsize); -countryRowBgOver: overBg; -countryRowCodeFg: #808080; -countryRowCodeFgOver: #7c99b2; -countriesSkip: 12px; -countriesScroll: flatScroll(boxScroll) { - deltat: 9px; - deltab: 3px; -} - -introErrWidth: 450px; -introErrDuration: 200; -introErrFunc: transition(linear); -introErrColor: black; -introErrTop: 15px; -introErrHeight: 40px; -introErrFont: font(16px); - -introErrLabel: flatLabel(labelDefFlat) { - font: introErrFont; - align: align(center); -} -introBackButton: IconButton { - width: 40px; - height: 40px; - - opacity: 0.71; - overOpacity: 1.; - - icon: icon { - { size(40px, 40px), #eeeeee }, - { "title_previous", #969696, point(12px, 12px) }, - }; - iconPosition: point(0px, 0px); - downIconPosition: point(0px, 0px); - - duration: 150; -} -introBackPosition: point(32px, 32px); - setLittleSkip: 9px; -setScroll: flatScroll(scrollDef) { - bottomsh: 0px; - topsh: 0px; -} -setPhotoDuration: 150; - -setErrColor: #d84d4d; -setGoodColor: #4ab44a; noContactsHeight: 100px; noContactsFont: font(fsize); -noContactsColor: #777; - -fieldSearchIcon: icon {{ "box_search", #aaaaaa, point(10px, 9px) }}; -dlgFilter: flatInput(inpDefGray) { - font: font(fsize); - bgColor: #f2f2f2; - phColor: #949494; - phFocusColor: #a4a4a4; - icon: fieldSearchIcon; - - width: 240px; - height: 34px; - textMrg: margins(34px, 2px, 34px, 4px); -} - -topBarHeight: 54px; -topBarBG: white; -topBarDuration: 200; -topBarForward: icon {{ "title_next", #a3a3a3 }}; -topBarBackward: icon {{ "title_previous", #a3a3a3 }}; -topBarForwardAlpha: 0.6; -topBarBack: icon {{ "title_previous", #259fd8 }}; -topBarBackAlpha: 0.8; -topBarBackColor: #005faf; -topBarBackFont: font(16px); -topBarArrowPadding: margins(39px, 8px, 17px, 8px); -topBarSearch: IconButton { - width: 44px; - height: topBarHeight; - - icon: icon {{ "title_search", #000000 }}; - iconPosition: point(13px, 18px); - downIconPosition: point(13px, 18px); - - opacity: 0.22; - overOpacity: 0.36; - - duration: 150; -} -topBarMinPadding: 5px; -topBarButton: RoundButton { - textFg: #0084c4; - textFgOver: #0084c4; - secondaryTextFg: #0084c4; - secondaryTextFgOver: #0084c4; - textBg: windowBg; - textBgOver: #edf4f7; - - width: -22px; - height: 28px; - padding: margins(0px, 14px, 12px, 12px); - - textTop: 6px; - downTextTop: 7px; - - font: font(fsize); - duration: 200; -} -defaultActiveButton: RoundButton { - textFg: #ffffff; - textFgOver: #ffffff; - secondaryTextFg: #cceeff; - secondaryTextFgOver: #cceeff; - textBg: windowActiveBg; - textBgOver: #46b4eb; - - secondarySkip: 7px; - - width: -34px; - height: 34px; - padding: margins(0px, 0px, 0px, 0px); - - textTop: 8px; - downTextTop: 9px; - - font: semiboldFont; - duration: 200; -} -topBarClearButton: RoundButton(defaultActiveButton) { - textFg: semiboldButtonBlueText; - textFgOver: semiboldButtonBlueText; - textBg: #ffffff; - textBgOver: #f2f7fa; - - width: -18px; -} -topBarActionSkip: 10px; +noContactsColor: windowSubTextFg; activeFadeInDuration: 500; activeFadeOutDuration: 3000; @@ -819,6 +91,7 @@ activeFadeOutDuration: 3000; msgMaxWidth: 430px; msgFont: font(fsize); msgNameFont: semiboldFont; +msgNameStyle: semiboldTextStyle; msgServiceFont: semiboldFont; msgServiceNameFont: semiboldFont; msgServicePhotoWidth: 100px; @@ -827,586 +100,90 @@ msgMinWidth: 190px; msgPhotoSize: 33px; msgPhotoSkip: 40px; msgPadding: margins(13px, 7px, 13px, 8px); -msgMargin: margins(13px, 10px, 53px, 2px); -msgMarginTopAttached: 3px; +msgMargin: margins(16px, 6px, 56px, 2px); +msgMarginTopAttached: 1px; msgLnkPadding: 2px; // for media open / save links -msgBorder: #f0f0f0; -msgInBg: #fff; -msgInBgSelected: #c2dcf2; // #358cd4 with 30% opacity -msgOutBg: #effdde; -msgOutBgSelected: #b7dbdb; -msgSelectOverlay: #358cd44c; -msgStickerOverlay: #358cd47f; -msgInServiceFg: #0e7acd; -msgInServiceFgSelected: #0e7acd; -msgOutServiceFg: #3a8e26; -msgOutServiceFgSelected: #367570; msgShadow: 2px; -msgInShadow: #748ea229; -msgInShadowSelected: #548dbb29; -msgOutShadow: #3ac34740; -msgOutShadowSelected: #37a78e40; -msgInDateFg: #a0acb6; -msgInDateFgSelected: #6a9cc5; -msgOutDateFg: #6cc264; -msgOutDateFgSelected: #50a79c; msgReplyPadding: margins(6px, 6px, 11px, 6px); msgReplyBarPos: point(1px, 0px); msgReplyBarSize: size(2px, 36px); msgReplyBarSkip: 10px; -msgInReplyBarColor: #2fa9e2; -msgInReplyBarSelColor: #2fa9e2; - -msgBotKbDuration: 200; -msgBotKbFont: semiboldFont; -msgBotKbOverOpacity: 0.1; -msgBotKbIconPadding: 2px; -msgBotKbUrlIcon: icon {{ "inline_button_url", #ffffff }}; -msgBotKbSwitchPmIcon: icon {{ "inline_button_switch", #ffffff }}; -msgBotKbButton: botKeyboardButton { - margin: 5px; - padding: 10px; - height: 36px; - textTop: 8px; - downTextTop: 9px; -} - -msgServiceBg: #89a0b47f; -msgServiceSelectBg: #bbc8d4a2; -msgServiceColor: #FFF; msgServicePadding: margins(12px, 3px, 12px, 4px); msgServiceMargin: margins(10px, 10px, 80px, 2px); -msgColor: #000; -msgDateColor: #000; -msgLinkColor: #2a6dc2; -msgPressedLinkColor: #004bad; -msgSkip: 40px; -msgPtr: 8px; -msgBG: ":/gui/art/bg.jpg"; -msgBG0: ":/gui/art/bg0.png"; - msgDateSpace: 12px; msgDateDelta: point(2px, 5px); msgDateImgDelta: 4px; -msgDateImgColor: #fff; -msgDateImgBg: #00000054; -msgDateImgBgOver: #00000074; -msgDateImgBgSelected: #1c4a7187; msgDateImgPadding: point(8px, 2px); msgDateImgCheckSpace: 4px; -collapseButton: flatButton(btnDefFlat) { +messageTextStyle: defaultTextStyle; +msgDateTextStyle: defaultTextStyle; +serviceTextPalette: TextPalette(defaultTextPalette) { + linkFg: msgServiceFg; + monoFg: msgServiceFg; + selectBg: msgServiceBgSelected; + selectOverlay: msgServiceBgSelected; +} +serviceTextStyle: TextStyle(defaultTextStyle) { font: msgServiceFont; - overFont: msgServiceFont; - width: -24px; - bgColor: transparent; - downBgColor: transparent; - overBgColor: transparent; - color: white; - overColor: white; - downColor: white; - textTop: 3px; - overTextTop: 3px; - downTextTop: 3px; - height: 25px; + linkFont: msgServiceFont; + linkFontOver: font(fsize semibold underline); } -collapseHideDuration: 200; -collapseShowDuration: 200; - -defaultTextStyle: textStyle { - linkFlags: font(fsize); - linkFlagsOver: font(fsize underline); - linkFg: btnYesColor; - linkFgDown: btnYesHover; - monoFg: #777; - selectBg: msgInBgSelected; - selectOverlay: msgSelectOverlay; - lineHeight: 0px; -} -boxTextStyle: textStyle(defaultTextStyle) { - lineHeight: 22px; -} -serviceTextStyle: textStyle(defaultTextStyle) { - linkFlags: msgServiceFont; - linkFlagsOver: font(fsize semibold underline); - linkFg: msgServiceColor; - linkFgDown: msgServiceColor; - monoFg: msgServiceColor; - selectBg: msgServiceSelectBg; - selectOverlay: msgServiceSelectBg; -} -inTextStyle: textStyle(defaultTextStyle) { - monoFg: #4e7391; +inTextPalette: TextPalette(defaultTextPalette) { + monoFg: msgInMonoFg; selectBg: msgInBgSelected; selectOverlay: msgSelectOverlay; } -outTextStyle: textStyle(defaultTextStyle) { - monoFg: #469165; +outTextPalette: TextPalette(defaultTextPalette) { + monoFg: msgOutMonoFg; selectBg: msgOutBgSelected; selectOverlay: msgSelectOverlay; } -inFwdTextStyle: textStyle(defaultTextStyle) { - linkFlags: semiboldFont; - linkFlagsOver: semiboldFont; +fwdTextStyle: TextStyle(semiboldTextStyle) { + linkFontOver: semiboldFont; +} +inFwdTextPalette: TextPalette(defaultTextPalette) { linkFg: msgInServiceFg; - linkFgDown: msgInServiceFg; } -outFwdTextStyle: textStyle(inFwdTextStyle) { +outFwdTextPalette: TextPalette(defaultTextPalette) { linkFg: msgOutServiceFg; - linkFgDown: msgOutServiceFg; } -inFwdTextStyleSelected: textStyle(inFwdTextStyle) { +inFwdTextPaletteSelected: TextPalette(defaultTextPalette) { linkFg: msgInServiceFgSelected; - linkFgDown: msgInServiceFgSelected; } -outFwdTextStyleSelected: textStyle(inFwdTextStyle) { +outFwdTextPaletteSelected: TextPalette(defaultTextPalette) { linkFg: msgOutServiceFgSelected; - linkFgDown: msgOutServiceFgSelected; -} -medviewSaveAsTextStyle: textStyle(defaultTextStyle) { - linkFg: #91d9ff; - linkFgDown: #91d9ff; } -introLabelTextStyle: textStyle(defaultTextStyle) { - lineHeight: 30px; -} -introErrLabelTextStyle: textStyle(defaultTextStyle) { - lineHeight: 27px; -} - -mediaPadding: margins(0px, 0px, 0px, 0px);//1px, 1px, 1px, 1px);//2px, 2px, 2px, 2px); +mediaPadding: margins(0px, 0px, 0px, 0px); mediaCaptionSkip: 5px; mediaInBubbleSkip: 5px; mediaThumbSize: 48px; mediaNameTop: 3px; mediaDetailsShift: 3px; -mediaInFg: msgInDateFg; -mediaInFgSelected: msgInDateFgSelected; -mediaOutFg: msgOutDateFg; -mediaOutFgSelected: msgOutDateFgSelected; -mediaInUnreadFg: #999; -mediaInUnreadFgSelected: #7b95aa; -mediaOutUnreadFg: #6aad60; -mediaOutUnreadFgSelected: #5aa382; mediaUnreadSize: 7px; mediaUnreadSkip: 5px; mediaUnreadTop: 6px; -mediaInStyle: textStyle(defaultTextStyle) { +mediaInPalette: TextPalette(defaultTextPalette) { linkFg: mediaInFg; - linkFgDown: mediaInFg; } -mediaInStyleSelected: textStyle(defaultTextStyle) { +mediaInPaletteSelected: TextPalette(defaultTextPalette) { linkFg: mediaInFgSelected; - linkFgDown: mediaInFgSelected; } -msgFileRedColor: #e47272; -msgFileYellowColor: #efc274; -msgFileGreenColor: #61b96e; -msgFileBlueColor: #72b1df; -msgFileRedDark: #cd5b5e; -msgFileYellowDark: #e6a561; -msgFileGreenDark: #4da859; -msgFileBlueDark: #5c9ece; -msgFileRedOver: #c35154; -msgFileYellowOver: #dc9c5a; -msgFileGreenOver: #44a050; -msgFileBlueOver: #5294c4; -msgFileRedSelected: #9f6a82; -msgFileYellowSelected: #b19d84; -msgFileGreenSelected: #46a07e; -msgFileBlueSelected: #5099d0; - -msgFileMenuSize: size(36px, 36px); -msgFileSize: 44px; -msgFilePadding: margins(14px, 12px, 11px, 12px); -msgFileThumbSize: 72px; -msgFileThumbPadding: margins(10px, 10px, 14px, 10px); -msgFileThumbNameTop: 12px; -msgFileThumbStatusTop: 32px; -msgFileThumbLinkTop: 60px; -msgFileThumbLinkInFg: semiboldButtonBlueText; -msgFileThumbLinkInFgSelected: semiboldButtonBlueText; -msgFileThumbLinkOutFg: #5eba5b; -msgFileThumbLinkOutFgSelected: #31a298; -msgFileNameTop: 16px; -msgFileStatusTop: 37px; -msgFileMinWidth: 294px; -msgFileInBg: windowActiveBg; -msgFileInBgOver: #4eade3; -msgFileInBgSelected: #51a3d3; -msgFileOutBg: #78c67f; -msgFileOutBgOver: #6bc272; -msgFileOutBgSelected: #5fb389; - -msgFileOverDuration: 200; -msgFileRadialLine: 3px; - -msgVideoSize: size(320px, 240px); - -msgWaveformBar: 2px; -msgWaveformSkip: 1px; -msgWaveformMin: 2px; -msgWaveformMax: 20px; -msgWaveformInActive: windowActiveBg; -msgWaveformInActiveSelected: #51a3d3; -msgWaveformInInactive: #d4dee6; -msgWaveformInInactiveSelected: #9cc1e1; -msgWaveformOutActive: #78c67f; -msgWaveformOutActiveSelected: #6badad; -msgWaveformOutInactive: #b3e2b4; -msgWaveformOutInactiveSelected: #91c3c3; - -sendPadding: 9px; -btnSend: flatButton(btnDefFlat) { - color: btnYesColor; - overColor: btnYesHover; - downColor: btnYesHover; - - bgColor: white; - overBgColor: btnWhiteHover; - downBgColor: btnWhiteHover; - - width: -32px; - height: 46px; - - textTop: 12px; - overTextTop: 12px; - downTextTop: 13px; - - font: font(16px); - overFont: font(16px); -} -btnUnblock: flatButton(btnSend) { - color: #d15948; - overColor: #d15948; - downColor: #db6352; -} - -btnAttachDocument: iconedButton(btnDefIconed) { - icon: sprite(218px, 68px, 24px, 24px); - iconPos: point(11px, 11px); - downIcon: sprite(218px, 68px, 24px, 24px); - downIconPos: point(11px, 12px); - - overBgColor: btnWhiteHover; - width: 46px; - height: 46px; -} -btnAttachPhoto: iconedButton(btnAttachDocument) { - icon: sprite(118px, 0px, 24px, 24px); - downIcon: sprite(118px, 0px, 24px, 24px); -} -btnAttachEmoji: iconedButton(btnAttachDocument) { - overBgColor: white; - icon: sprite(374px, 344px, 21px, 22px); - iconPos: point(6px, 12px); - downIcon: sprite(374px, 344px, 21px, 22px); - downIconPos: point(6px, 12px); - - width: 33px; -} -emojiCircle: size(19px, 19px); -emojiCirclePeriod: 1500; -emojiCircleDuration: 500; -emojiCircleTop: 13px; -emojiCircleLine: 2px; -emojiCircleFg: #b9b9b9; -emojiCirclePart: 3.5; -btnBotKbShow: iconedButton(btnAttachEmoji) { - icon: sprite(375px, 74px, 21px, 21px); - iconPos: point(6px, 12px); - downIcon: sprite(375px, 74px, 21px, 21px); - downIconPos: point(6px, 12px); -} -btnBotCmdStart: iconedButton(btnAttachEmoji) { - icon: sprite(354px, 74px, 21px, 21px); - iconPos: point(6px, 12px); - downIcon: sprite(354px, 74px, 21px, 21px); - downIconPos: point(6px, 12px); -} -btnBotKbHide: iconedButton(btnAttachEmoji) { - icon: sprite(373px, 95px, 23px, 14px); - iconPos: point(5px, 17px); - downIcon: sprite(373px, 95px, 23px, 14px); - downIconPos: point(5px, 17px); -} -silentToggle: flatCheckbox { - textColor: black; - bgColor: white; - disColor: black; - - width: 33px; - height: 46px; - duration: 200; - bgFunc: transition(easeOutCirc); - cursor: cursor(pointer); - - font: normalFont; - - imageRect: sprite(354px, 242px, 21px, 21px); - chkImageRect: sprite(354px, 221px, 21px, 21px); - overImageRect: sprite(375px, 242px, 21px, 21px); - chkOverImageRect: sprite(375px, 221px, 21px, 21px); - disImageRect: sprite(354px, 242px, 21px, 21px); - chkDisImageRect: sprite(354px, 221px, 21px, 21px); - - imagePos: point(6px, 12px); -} -btnRecordAudio: sprite(379px, 390px, 16px, 24px); -btnRecordAudioActive: sprite(379px, 366px, 16px, 24px); -recordSignalColor: #f17077; -recordSignalMin: 5px; -recordSignalMax: 12px; -recordCancel: #aaa; -recordCancelActive: #ec6466; -recordFont: font(13px); -recordTextTop: 14px; - -replySkip: 51px; -replyColor: #377aae; -replyHeight: 49px; -replyTop: 8px; -replyBottom: 6px; -replyIconPos: point(13px, 13px); -replyIcon: sprite(343px, 197px, 24px, 24px); -editIcon: sprite(371px, 286px, 24px, 24px); -replyCancel: iconedButton(btnDefIconed) { - icon: sprite(165px, 24px, 14px, 14px); - iconPos: point(17px, 17px); - downIcon: sprite(165px, 24px, 14px, 14px); - downIconPos: point(17px, 18px); - bgColor: white; - overBgColor: white; - width: 49px; - height: 49px; -} -inlineBotCancel: iconedButton(replyCancel) { - height: 46px; - iconPos: point(-1px, 16px); // < 0 means draw in the center of the button - downIconPos: point(-1px, 17px); -} -forwardIcon: sprite(368px, 197px, 24px, 24px); - -historyScroll: flatScroll(scrollDef) { - barColor: #89a0b47a; - bgColor: #89a0b44c; - barOverColor: #89a0b4bc; - bgOverColor: #89a0b46b; - - round: 3px; - - width: 12px; - deltax: 3px; - deltat: 3px; - deltab: 3px; - - topsh: 0px; - bottomsh: -1px; -} textRectMargins: margins(-2px, -1px, -2px, -1px); -taMsgField: flatTextarea(taDefFlat) { - font: msgFont; -} -maxFieldHeight: 224px; -// historyMinHeight: 56px; -reportSpamHide: flatButton(btnDefFlat) { - color: btnYesColor; - overColor: btnYesHover; - downColor: btnYesHover; +searchedBarHeight: 32px; +searchedBarFont: normalFont; +searchedBarPosition: point(17px, 7px); - bgColor: transparent; - overBgColor: transparent; - downBgColor: transparent; - - width: -40px; - height: 46px; - - textTop: 15px; - overTextTop: 15px; - downTextTop: 16px; - - font: font(fsize); - overFont: font(fsize underline); -} -reportSpamButton: flatButton(reportSpamHide) { - textTop: 6px; - overTextTop: 6px; - downTextTop: 7px; - - width: -50px; - height: 30px; - - bgColor: #888; - overBgColor: #7b7b7b; - downBgColor: #7b7b7b; -} -reportSpamSeparator: 30px; -reportSpamBg: #fffffff0; - -newMsgSound: ":/gui/art/newmsg.wav"; - -unreadBarHeight: 32px; -unreadBarMargin: 8px; -unreadBarFont: semiboldFont; -unreadBarBG: #fcfbfa; -unreadBarBorder: shadowColor; -unreadBarColor: #538bb4; - -searchedBarHeight: unreadBarHeight; -searchedBarFont: unreadBarFont; -searchedBarBG: #ebeef1; -searchedBarBorder: unreadBarBorder; -searchedBarColor: #a2aeb7; - -layerSlideDuration: 200; -layerHideDuration: 200; -layerPadding: margins(10px, 10px, 10px, 10px); - -contactPadding: margins(49px, 22px, 0px, 6px); -contactSkip: 13px; -contactPhoneSkip: 30px; - -contactsPhotoSize: 42px; -contactsPadding: margins(16px, 7px, 16px, 7px); -contactsNameTop: 2px; -contactsNameFont: semiboldFont; -contactsStatusTop: 23px; -contactsStatusFont: font(fsize); -contactsStatusFg: #999999; -contactsStatusFgOver: #7c99b2; -contactsStatusFgOnline: #3b8dcc; -contactsBgOver: overBg; -contactsCheckPosition: point(8px, 16px); -contactsAboutBg: #f7f7f7; -contactsAboutShadow: #0000001F; -contactsAdminCheckbox: Checkbox(defaultBoxCheckbox) { - font: semiboldFont; - textBg: #f7f7f7; - textPosition: point(34px, 1px); -} -contactsAboutHeight: 42px; -contactsAboutTop: 9px; -contactsScroll: flatScroll(boxScroll) { - deltab: 0px; -} - -btnAddContact: iconedButton(btnDefIconed) { - icon: sprite(188px, 93px, 18px, 18px); - iconPos: point(8px, 8px); - downIcon: sprite(188px, 93px, 18px, 18px); - downIconPos: point(8px, 9px); - - bgColor: transparent; - overBgColor: transparent; - width: 36px; - height: 36px; -} -btnCancelSearch: iconedButton(btnAddContact) { - icon: sprite(188px, 43px, 18px, 18px); - downIcon: sprite(188px, 43px, 18px, 18px); -} - -simpleClose: iconedButton(btnDefIconed) { - icon: sprite(167px, 130px, 10px, 10px); - iconPos: point(10px, 10px); - downIcon: sprite(167px, 130px, 10px, 10px); - downIconPos: point(10px, 11px); - - width: 30px; - height: 30px; -} - -boxPhotoPadding: margins(28px, 28px, 28px, 18px); -boxPhotoCompressedPadding: margins(0px, 2px, 0px, 22px); -boxPhotoTextFg: #808080; -cropPointSize: 10px; -cropSkip: 13px; -cropMinSize: 20px; -confirmCaptionArea: InputArea(defaultInputArea) { - textMargins: margins(1px, 6px, 1px, 4px); - heightMax: 56px; -} -confirmBg: #f2f2f2; -confirmMaxHeight: 245px; -confirmCompressedSkip: 10px; - -profileMaxWidth: 410px; -profilePadding: margins(28px, 30px, 28px, 0px); - -profileOnlineFg: titleStatusActiveFg; -profileOfflineFg: titleStatusFg; - -membersPadding: margins(0px, 10px, 0px, 10px); - -forwardMargins: margins(30px, 10px, 30px, 10px); -forwardFont: font(16px); -forwardBg: rgba(0, 0, 0, 76); - -newGroupAboutFg: #808080; -newGroupPadding: margins(4px, 6px, 4px, 3px); -newGroupSkip: 17px; -newGroupInfoPadding: margins(0px, -4px, 0px, 1px); - -newGroupLinkPadding: margins(4px, 27px, 4px, 12px); -newGroupLinkTop: 3px; -newGroupLinkFont: font(16px); - -newGroupPhotoSize: 76px; -newGroupPhotoBg: #4eb5f0; -newGroupPhotoBgOver: #3fa9e7; -newGroupPhotoIcon: sprite(74px, 104px, 30px, 27px); -newGroupPhotoIconPosition: point(23px, 25px); - -newGroupNamePosition: point(27px, 20px); - -newGroupDescriptionPadding: margins(0px, 23px, 0px, 14px); -newGroupDescription: InputArea(defaultInputArea) { - textMargins: margins(1px, 6px, 1px, 4px); - heightMax: 115px; -} - -newGroupPublicLinkPadding: margins(0px, 20px, 0px, 5px); -newGroupLinkFadeDuration: 5000; - -connectionHostInputField: InputField(defaultInputField) { - width: 160px; -} -connectionPortInputField: InputField(defaultInputField) { - width: 55px; -} -connectionUserInputField: InputField(defaultInputField) { - width: 95px; -} -connectionPasswordInputField: InputField(defaultInputField) { - width: 120px; -} -connectionIPv6Skip: 11px; - -aboutIcon: icon {{ "intro_logo", #008ed5 }}; -aboutWidth: 390px; -aboutVersionTop: -3px; -aboutVersionLink: linkButton(btnDefLink) { - color: #999; - overColor: #999; - downColor: #999; -} -aboutTextTop: 34px; -aboutSkip: 14px; -aboutLabel: flatLabel(labelDefFlat) { - font: normalFont; - width: 330px; - align: align(topleft); -} -aboutTextStyle: textStyle(defaultTextStyle) { - lineHeight: 22px; -} +smallCloseIcon: icon {{ "simple_close", smallCloseIconFg }}; +smallCloseIconOver: icon {{ "simple_close", smallCloseIconFgOver }}; +dialogsForwardCancelIcon: icon {{ "simple_close", dialogsForwardFg }}; emojiTextFont: font(15px); emojiReplaceWidth: 52px; @@ -1414,526 +191,49 @@ emojiReplaceHeight: 56px; emojiReplaceInnerHeight: 42px; emojiReplacePadding: 14px; -connectingBG: #fffe; -connectingColor: #777; connectingPadding: margins(5px, 5px, 5px, 5px); -dropdownDef: dropdown { - border: 1px; - borderColor: #ebebeb; - - padding: margins(10px, 10px, 10px, 10px); - shadow: defaultDropdownShadow; - shadowShift: 1px; - - duration: 150; - width: 0px; -} -defaultInnerDropdown: InnerDropdown { - padding: margins(10px, 10px, 10px, 10px); - shadow: defaultDropdownShadow; - shadowShift: 1px; - - duration: 150; -} - -dropdownAttachDocument: iconedButton(btnAttachDocument) { - iconPos: point(14px, 13px); - downIconPos: point(14px, 14px); - - width: 172px; - height: 49px; - - color: black; - - font: font(16px); - - textPos: point(50px, 13px); - downTextPos: point(50px, 14px); -} -dropdownAttachPhoto: iconedButton(dropdownAttachDocument) { - icon: sprite(118px, 0px, 24px, 24px); - downIcon: sprite(118px, 0px, 24px, 24px); -} -dropdownMediaPhotos: iconedButton(dropdownAttachPhoto) { - width: 200px; -} -dropdownMediaVideos: iconedButton(dropdownMediaPhotos) { - icon: sprite(92px, 348px, 24px, 24px); - downIcon: sprite(92px, 348px, 24px, 24px); -} -dropdownMediaSongs: iconedButton(dropdownMediaPhotos) { - icon: sprite(60px, 374px, 24px, 26px); - downIcon: sprite(60px, 374px, 24px, 26px); - iconPos: point(12px, 12px); - downIconPos: point(12px, 13px); -} -dropdownMediaDocuments: iconedButton(dropdownAttachDocument) { - width: 200px; -} -dropdownMediaAudios: iconedButton(dropdownMediaDocuments) { - icon: sprite(62px, 348px, 24px, 24px); - downIcon: sprite(62px, 348px, 24px, 24px); -} -dropdownMediaLinks: iconedButton(dropdownMediaDocuments) { - icon: sprite(372px, 414px, 24px, 24px); - downIcon: sprite(372px, 414px, 24px, 24px); -} - dragFont: font(28px semibold); dragSubfont: font(20px semibold); -dragColor: #777; -dragDropColor: btnYesColor; +dragColor: windowSubTextFg; +dragDropColor: windowActiveTextFg; dragMargin: margins(0px, 10px, 0px, 10px); dragPadding: margins(20px, 10px, 20px, 10px); dragHeight: 72px; -dpiActive: black; -dpiInactive: #999; -dpiFont1: linkFont; -dpiFont2: linkFont; -dpiFont3: linkFont; -dpiFont4: linkFont; - -stickersMaxHeight: 440px; -stickersPadding: margins(19px, 17px, 19px, 17px); -stickersSize: size(64px, 64px); -stickersScroll: flatScroll(boxScroll) { - round: 2px; - deltax: 7px; - deltat: 23px; - deltab: 9px; -} -stickersReorderPadding: margins(0px, 12px, 0px, 12px); -stickersReorderFg: #777; -stickersRowDisabledOpacity: 0.4; -stickersRowDuration: 200; - -emojiScroll: flatScroll(solidScroll) { - deltat: 48px; -} -emojiRecentOver: sprite(0px, 196px, 21px, 22px); -emojiRecentActive: sprite(245px, 264px, 21px, 22px); -emojiPeopleOver: sprite(21px, 196px, 21px, 22px); -emojiPeopleActive: sprite(266px, 264px, 21px, 22px); -emojiNatureOver: sprite(42px, 196px, 21px, 22px); -emojiNatureActive: sprite(245px, 286px, 21px, 22px); -emojiFoodOver: sprite(63px, 196px, 21px, 22px); -emojiFoodActive: sprite(266px, 286px, 21px, 22px); -emojiActivityOver: sprite(126px, 196px, 21px, 22px); -emojiActivityActive: sprite(287px, 264px, 21px, 22px); -emojiTravelOver: sprite(105px, 196px, 21px, 22px); -emojiTravelActive: sprite(308px, 286px, 21px, 22px); -emojiObjectsOver: sprite(147px, 196px, 21px, 22px); -emojiObjectsActive: sprite(308px, 264px, 21px, 22px); -emojiSymbolsOver: sprite(84px, 196px, 21px, 22px); -emojiSymbolsActive: sprite(287px, 286px, 21px, 22px); -stickersSettings: sprite(140px, 124px, 21px, 22px); -savedGifsOver: sprite(329px, 286px, 21px, 22px); -savedGifsActive: sprite(350px, 286px, 21px, 22px); -featuredStickersOver: sprite(329px, 264px, 21px, 22px); -featuredStickersActive: sprite(350px, 264px, 21px, 22px); - -stickersSettingsUnreadSize: 17px; -stickersSettingsUnreadPosition: point(4px, 5px); - -emojiPanCategories: #f7f7f7; - -rbEmoji: flatCheckbox { - textColor: transparent; - bgColor: emojiPanCategories; - disColor: emojiPanCategories; - - width: 42px; - height: 46px; - - textTop: 0px; - textLeft: 0px; - font: font(fsize); - duration: 200; - bgFunc: transition(easeOutCirc); - cursor: cursor(pointer); - - disabledCursor: cursor(default); - imagePos: point(11px, 12px); -} -rbEmojiRecent: flatCheckbox(rbEmoji) { - imageRect: emojiRecentOver; - chkImageRect: emojiRecentActive; - overImageRect: emojiRecentOver; - chkOverImageRect: emojiRecentActive; - disImageRect: emojiRecentOver; - chkDisImageRect: emojiRecentActive; -} -rbEmojiPeople: flatCheckbox(rbEmoji) { - imageRect: emojiPeopleOver; - chkImageRect: emojiPeopleActive; - overImageRect: emojiPeopleOver; - chkOverImageRect: emojiPeopleActive; - disImageRect: emojiPeopleOver; - chkDisImageRect: emojiPeopleActive; -} -rbEmojiNature: flatCheckbox(rbEmoji) { - imageRect: emojiNatureOver; - chkImageRect: emojiNatureActive; - overImageRect: emojiNatureOver; - chkOverImageRect: emojiNatureActive; - disImageRect: emojiNatureOver; - chkDisImageRect: emojiNatureActive; -} -rbEmojiFood: flatCheckbox(rbEmoji) { - imageRect: emojiFoodOver; - chkImageRect: emojiFoodActive; - overImageRect: emojiFoodOver; - chkOverImageRect: emojiFoodActive; - disImageRect: emojiFoodOver; - chkDisImageRect: emojiFoodActive; -} -rbEmojiActivity: flatCheckbox(rbEmoji) { - imageRect: emojiActivityOver; - chkImageRect: emojiActivityActive; - overImageRect: emojiActivityOver; - chkOverImageRect: emojiActivityActive; - disImageRect: emojiActivityOver; - chkDisImageRect: emojiActivityActive; -} -rbEmojiTravel: flatCheckbox(rbEmoji) { - imageRect: emojiTravelOver; - chkImageRect: emojiTravelActive; - overImageRect: emojiTravelOver; - chkOverImageRect: emojiTravelActive; - disImageRect: emojiTravelOver; - chkDisImageRect: emojiTravelActive; -} -rbEmojiObjects: flatCheckbox(rbEmoji) { - imageRect: emojiObjectsOver; - chkImageRect: emojiObjectsActive; - overImageRect: emojiObjectsOver; - chkOverImageRect: emojiObjectsActive; - disImageRect: emojiObjectsOver; - chkDisImageRect: emojiObjectsActive; -} -rbEmojiSymbols: flatCheckbox(rbEmoji) { - imageRect: emojiSymbolsOver; - chkImageRect: emojiSymbolsActive; - overImageRect: emojiSymbolsOver; - chkOverImageRect: emojiSymbolsActive; - disImageRect: emojiSymbolsOver; - chkDisImageRect: emojiSymbolsActive; -} -emojiPanPadding: 12px; -emojiPanSize: size(45px, 41px); -emojiPanWidth: 345px; -emojiPanMaxHeight: 366px; -emojiPanDuration: 200; -emojiPanHover: #f0f4f7; - -emojiPanHeader: 42px; -emojiPanHeaderFont: semiboldFont; -emojiPanHeaderColor: #999; -emojiPanHeaderLeft: 22px; -emojiPanHeaderTop: 12px; -emojiPanHeaderBg: #fffffff2; - -emojiColorsPadding: 5px; -emojiColorsSep: 1px; -emojiColorsSepColor: #d5d5d5; - -emojiSwitchSkip: 27px; -emojiSwitchImgSkip: 21px; -emojiSwitchStickers: sprite(318px, 328px, 8px, 12px); -emojiSwitchEmoji: sprite(310px, 328px, 8px, 12px); -emojiSwitchColor: #42a8db; - -stickerPanSize: size(64px, 64px); -stickerPanPadding: 11px; -stickerPanDelete: sprite(128px, 132px, 12px, 12px); -stickerPanDeleteOpacity: 0.5; -stickerIconPadding: 5px; -stickerIconOpacity: 0.7; -stickerIconSel: 2px; -stickerIconSelColor: #58b2ed; -stickerIconLeft: sprite(342px, 72px, 40px, 1px); -stickerIconRight: sprite(342px, 73px, 40px, 1px); -stickerIconMove: 400; -stickerPreviewDuration: 150; -stickerPreviewBg: #FFFFFFB0; -stickerPreviewMin: 0.1; - -botKbDuration: 200; -botKbBg: #edf1f5; -botKbOverBg: #d8e2ec; -botKbDownBg: #d8e2ec; -botKbColor: #4b565f; -botKbFont: font(15px semibold); -botKbButton: botKeyboardButton { - margin: 10px; - padding: 10px; - height: 38px; - textTop: 9px; - downTextTop: 9px; -} -botKbTinyButton: botKeyboardButton { - margin: 4px; - padding: 3px; - height: 25px; - textTop: 2px; - downTextTop: 2px; -} -botKbScroll: flatScroll(solidScroll) { - deltax: 3px; - width: 10px; -} -switchPmButton: RoundButton(defaultBoxButton) { - width: 320px; - height: 34px; - textTop: 7px; - downTextTop: 8px; -} - minPhotoSize: 100px; maxMediaSize: 420px; maxStickerSize: 256px; maxGifSize: 320px; maxSignatureSize: 144px; -mvBgColor: #222; -mvBgOpacity: 0.92; -mvThickFont: semiboldFont; -mvFont: font(fsize); - -mvTextLeft: 16px; -mvTextSkip: 10px; -mvHeaderTop: 48px; -mvTextTop: 24px; -mvTextColor: white; -mvTextOpacity: 0.5; -mvTextOverOpacity: 1; - -mvIconOpacity: 0.45; -mvIconOverOpacity: 1; -mvControlBgColor: black; -mvControlBgOpacity: 0.3; -mvControlMargin: 0px; -mvControlSize: 90px; -mvIconSize: size(60px, 56px); - -mvDropdown: dropdown(dropdownDef) { - shadow: icon {}; - padding: margins(11px, 12px, 11px, 12px); - - border: 0px; - width: 182px; -} -mvButton: iconedButton(btnDefIconed) { - bgColor: #383838; - overBgColor: #505050; - font: font(fsize); - - opacity: 1.; - overOpacity: 1.; - - width: -32px; - height: 36px; - - color: white; - - textPos: point(16px, 9px); - downTextPos: point(16px, 10px); - - duration: 0; -} -mvPopupMenu: PopupMenu(defaultPopupMenu) { - shadow: icon {}; - - itemBg: #383838; - itemBgOver: #505050; - itemFg: white; - itemFgOver: white; - itemFgDisabled: #999; - itemFgShortcut: #eee; - itemFgShortcutOver: #fff; - itemFgShortcutDisabled: #999; - - separatorFg: #484848; -} -mvContextButton: iconedButton(mvButton) { - bgColor: #383838E6; - overBgColor: #505050E7; -} -mvWaitHide: 2000; -mvHideDuration: 1000; -mvShowDuration: 200; -mvFadeDuration: 150; - -mvDocPadding: 18px; -mvDocSize: size(340px, 116px); -mvDocBg: white; -mvDocNameTop: 4px; -mvDocNameFont: font(semibold 14px); -mvDocNameColor: black; -mvDocSizeTop: 29px; -mvDocSizeColor: #808080; -mvDocExtTop: 35px; -mvDocExtFont: font(semibold 18px); -mvDocExtColor: white; -mvDocExtPadding: 10px; -mvDocLinksTop: 57px; -mvDocIconSize: 80px; - -mvDocLink: linkButton(btnDefLink) { - color: #4595d3; - overColor: #4595d3; - downColor: #4595d3; -} - -mvDeltaFromLastAction: 5px; -mvSwipeDistance: 80px; - -mvCaptionPadding: margins(18px, 10px, 18px, 10px); -mvCaptionMargin: size(11px, 11px); -mvCaptionRadius: 2px; -mvCaptionBg: #11111180; -mvCaptionFont: font(fsize); - -medviewSaveMsgCheck: icon {{ "mediaview_save_check", #ffffff }}; -medviewSaveMsgFont: font(16px); -medviewSaveMsgPadding: margins(55px, 19px, 29px, 20px); -medviewSaveMsgCheckPos: point(23px, 21px); -medviewSaveMsgShowing: 200; -medviewSaveMsgShown: 2000; -medviewSaveMsgHiding: 2500; -medviewSaveMsg: #000000b2; - -// Mac specific - -macAccessoryWidth: 450.; -macAccessoryHeight: 90.; -macEnableFilterAdd: 2; -macEnableFilterTop: 5; -macSelectorTop: 6; -macAlwaysThisAppTop: 4; -macAppHintTop: 8; -macCautionIconSize: 16; - -btnContext: iconedButton(btnDefIconed) { - bgColor: white; - overBgColor: btnWhiteHover; - font: font(14px); - - opacity: 1.; - overOpacity: 1.; - - width: -32px; - height: 36px; - - color: black; - - textPos: point(16px, 7px); - downTextPos: point(16px, 8px); -} - radialSize: size(50px, 50px); radialLine: 3px; radialDuration: 350; radialPeriod: 3000; -radialBgOpacity: 0.4; -radialDownload: sprite(346px, 0px, 50px, 50px); -radialDownloadOpacity: 0.8; -radialCancel: sprite(378px, 50px, 18px, 18px); -radialCancelOpacity: 1.0; -downloadPathSkip: 10px; - -usernamePadding: margins(23px, 22px, 21px, 12px); -usernameSkip: 49px; -usernameTextStyle: textStyle(defaultTextStyle) { - lineHeight: 20px; -} -usernameDefaultFg: #777; - -youtubeIcon: sprite(116px, 338px, 72px, 50px); -videoIcon: sprite(0px, 340px, 60px, 60px); +youtubeIcon: icon { + { "media_youtube_play_bg", youtubePlayIconBg }, + { "media_youtube_play", youtubePlayIconFg, point(24px, 12px) }, +}; +videoIcon: icon { + { "media_video_play_bg", videoPlayIconBg }, + { "media_video_play", videoPlayIconFg, point(12px, 12px) }, +}; locationSize: size(320px, 240px); -boxOptionListPadding: margins(2px, 20px, 2px, 2px); - -langsWidth: 256px; -langsButton: Radiobutton(defaultRadiobutton) { - width: 200px; -} - -backgroundPadding: 10px; -backgroundSize: size(108px, 193px); -backgroundScroll: flatScroll(boxScroll) { - round: 2px; - width: 10px; - deltax: 3px; - deltat: 10px; - deltab: 0px; -} - -passcodeHeaderFont: font(19px); -passcodeHeaderHeight: 80px; -passcodeInput: flatInput(inpIntroPhone) { -} -passcodeSubmit: flatButton(btnIntroNext) { - textTop: 15px; - overTextTop: 15px; - downTextTop: 16px; - width: 225px; - font: font(19px); - overFont: font(19px); -} -passcodeSubmitSkip: 40px; -passcodePadding: margins(0px, 22px, 0px, 3px); -passcodeSkip: 31px; - -mentionHeight: 40px; -mentionScroll: flatScroll(scrollDef) { - topsh: 0px; - bottomsh: 0px; -} -mentionPadding: margins(8px, 5px, 8px, 5px); -mentionTop: 11px; -mentionFont: linkFont; -mentionPhotoSize: msgPhotoSize; -mentionBgOver: #f5f5f5; -mentionFg: #777; -mentionFgOver: #707070; -mentionFgActive: #0080c0; -mentionFgOverActive: #0077b3; - -sessionsScroll: flatScroll(boxScroll) { - round: 2px; - deltax: 5px; - width: 14px; -} -sessionsHeight: 440px; -sessionHeight: 70px; -sessionCurrentPadding: margins(0px, 7px, 0px, 4px); -sessionCurrentHeight: 118px; -sessionPadding: margins(21px, 10px, 21px, 0px); -sessionNameFont: msgNameFont; -sessionActiveFont: msgDateFont; -sessionActiveColor: #aaa; -sessionInfoFont: msgFont; -sessionInfoColor: #888888; -sessionTerminateTop: 30px; -sessionTerminateSkip: 18px; -sessionTerminate: iconedButton(simpleClose) { - iconPos: point(3px, 3px); - downIconPos: point(3px, 4px); - width: 16px; - height: 16px; -} - webPageLeft: 10px; webPageBar: 2px; webPageTitleFont: semiboldFont; +webPageTitleStyle: semiboldTextStyle; +webPageTitleOutFg: historyTextOutFg; +webPageTitleInFg: historyTextInFg; +webPageDescriptionOutFg: historyTextOutFg; +webPageDescriptionInFg: historyTextInFg; webPageDescriptionFont: normalFont; +webPageDescriptionStyle: defaultTextStyle; webPagePhotoSize: 100px; webPagePhotoDelta: 8px; @@ -1949,34 +249,31 @@ inlineResultsSkip: 3px; inlineMediaHeight: 96px; inlineThumbSize: 64px; inlineThumbSkip: 10px; +inlineTitleFg: windowFg; inlineDescriptionFg: windowSubTextFg; inlineRowMargin: 6px; inlineRowBorder: 1px; -inlineRowBorderFg: #eaeaea; +inlineRowBorderFg: shadowFg; inlineRowFileNameTop: 2px; inlineRowFileDescriptionTop: 23px; inlineResultsMinWidth: 64px; inlineDurationMargin: 3px; -editTextArea: InputArea(defaultInputArea) { - textMargins: margins(1px, 6px, 1px, 4px); - heightMax: 256px; -} - -toastFont: normalFont; +toastTextStyle: defaultTextStyle; toastMaxWidth: 480px; toastMinMargin: 13px; -toastBg: medviewSaveMsg; -toastFg: #FFF; toastPadding: margins(19px, 13px, 19px, 12px); toastFadeInDuration: 200; toastFadeOutDuration: 1000; -infoButton: PeerAvatarButton { - size: topBarHeight; - photoSize: 42px; -} +historyReplyCancelIcon: icon {{ "box_button_close", historyReplyCancelFg }}; +historyReplyCancelIconOver: icon {{ "box_button_close", historyReplyCancelFgOver }}; +boxSearchCancelIcon: icon {{ "box_button_close", boxSearchCancelIconFg }}; +boxSearchCancelIconOver: icon {{ "box_button_close", boxSearchCancelIconFgOver }}; +boxTitleCloseIcon: icon {{ "box_button_close", boxTitleCloseFg }}; +boxTitleCloseIconOver: icon {{ "box_button_close", boxTitleCloseFgOver }}; -// forward declaration for single "title_previous" usage. -profileTopBarBackIconFg: #0290d7; -profileTopBarBackIcon: icon {{ "title_previous", profileTopBarBackIconFg }}; +notifyFadeRight: icon {{ "fade_horizontal", notificationBg }}; + +stickerIconLeft: icon {{ "fade_horizontal-flip_horizontal", emojiPanCategories }}; +stickerIconRight: icon {{ "fade_horizontal", emojiPanCategories }}; diff --git a/Telegram/Resources/basic_types.style b/Telegram/Resources/basic_types.style deleted file mode 100644 index f3baf7e69..000000000 --- a/Telegram/Resources/basic_types.style +++ /dev/null @@ -1,455 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -It is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the copyright holders give permission -to link the code of portions of this program with the OpenSSL library. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org -*/ - -textStyle { - linkFlags: font; - linkFlagsOver: font; - linkFg: color; - linkFgDown: color; - monoFg: color; - selectBg: color; - selectOverlay: color; - lineHeight: pixels; -} - -linkButton { - color: color; - overColor: color; - downColor: color; - font: font; - overFont: font; -} - -sysButton { - size: size; - icon: icon; - color: color; - overColor: color; - duration: int; -} - -flatButton { - color: color; - overColor: color; - downColor: color; - - bgColor: color; - overBgColor: color; - downBgColor: color; - - width: pixels; - height: pixels; - - textTop: pixels; - overTextTop: pixels; - downTextTop: pixels; - - font: font; - overFont: font; - duration: int; - cursor: cursor; - - radius: pixels; -} - -iconedButton { - icon: sprite; - iconPos: point; - downIcon: sprite; - downIconPos: point; - - color: color; - bgColor: color; - overBgColor: color; - width: pixels; - height: pixels; - font: font; - - opacity: double; - overOpacity: double; - - textPos: point; - downTextPos: point; - - duration: int; - cursor: cursor; -} - -flatCheckbox { - textColor: color; - bgColor: color; - disColor: color; - - width: pixels; - height: pixels; - textTop: pixels; - textLeft: pixels; - font: font; - duration: int; - bgFunc: transition; - cursor: cursor; - - disabledCursor: cursor; - - imageRect: sprite; - chkImageRect: sprite; - overImageRect: sprite; - chkOverImageRect: sprite; - disImageRect: sprite; - chkDisImageRect: sprite; - - imagePos: point; -} - -flatInput { - textColor: color; - bgColor: color; - bgActive: color; - width: pixels; - height: pixels; - textMrg: margins; - align: align; - font: font; - cursor: cursor; - - icon: icon; - - borderWidth: pixels; - borderColor: color; - borderActive: color; - borderError: color; - - phColor: color; - phFocusColor: color; - phPos: point; - phAlign: align; - phShift: pixels; - phDuration: int; - phLeftFunc: transition; - phAlphaFunc: transition; - phColorFunc: transition; -} - -flatTextarea { - textColor: color; - bgColor: color; - width: pixels; - textMrg: margins; - align: align; - font: font; - cursor: cursor; - - phColor: color; - phFocusColor: color; - phPos: point; - phAlign: align; - phShift: pixels; - phDuration: int; - phLeftFunc: transition; - phAlphaFunc: transition; - phColorFunc: transition; -} - -flatScroll { - barColor: color; - bgColor: color; - barOverColor: color; - bgOverColor: color; - - round: pixels; - - width: pixels; - minHeight: pixels; - deltax: pixels; - deltat: pixels; - deltab: pixels; - - topsh: pixels; - bottomsh: pixels; - shColor: color; - - duration: int; - hiding: int; -} - -countryInput { - width: pixels; - height: pixels; - top: pixels; - bgColor: color; - ptrSize: size; - textMrg: margins; - font: font; - align: align; -} - -flatLabel { - font: font; - margin: margins; - width: pixels; - align: align; - textFg: color; - maxHeight: pixels; -} - -switcher { - border: pixels; - borderColor: color; - - bgColor: color; - bgHovered: color; - bgActive: color; - - height: pixels; - - font: font; - textColor: color; - activeColor: color; - - duration: int; -} - -dropdown { - border: pixels; - borderColor: color; - - padding: margins; - shadow: icon; - shadowShift: pixels; - - duration: int; - width: pixels; -} - -InnerDropdown { - padding: margins; - shadow: icon; - shadowShift: pixels; - - duration: int; - width: pixels; - - scrollMargin: margins; - scrollPadding: margins; -} - -PopupMenu { - skip: pixels; - - shadow: icon; - shadowShift: pixels; - - itemBg: color; - itemBgOver: color; - itemFg: color; - itemFgOver: color; - itemFgDisabled: color; - itemFgShortcut: color; - itemFgShortcutOver: color; - itemFgShortcutDisabled: color; - itemPadding: margins; - itemFont: font; - - separatorPadding: margins; - separatorWidth: pixels; - separatorFg: color; - - arrow: icon; - - duration: int; - - widthMin: pixels; - widthMax: pixels; -} - -Tooltip { - textBg: color; - textFg: color; - textFont: font; - textBorder: color; - textPadding: margins; - - shift: point; - skip: pixels; - - widthMax: pixels; - linesMax: int; -} - -botKeyboardButton { - margin: pixels; - padding: pixels; - height: pixels; - textTop: pixels; - downTextTop: pixels; -} - -RoundButton { - textFg: color; - textFgOver: color; - textBg: color; // rect of textBg with rounded rect of textBgOver upon it - textBgOver: color; - - secondaryTextFg: color; - secondaryTextFgOver: color; - secondarySkip: pixels; - - width: pixels; - height: pixels; - padding: margins; - - textTop: pixels; - downTextTop: pixels; - - icon: icon; - - font: font; - duration: int; -} - -Checkbox { - textFg: color; - textBg: color; - - checkFg: color; - checkFgOver: color; - checkFgActive: color; - - width: pixels; - height: pixels; - - textPosition: point; - diameter: pixels; - thickness: pixels; - checkIcon: icon; - - font: font; - duration: int; -} - -Radiobutton { - textFg: color; - textBg: color; - - checkFg: color; - checkFgOver: color; - checkFgActive: color; - - width: pixels; - height: pixels; - - textPosition: point; - diameter: pixels; - thickness: pixels; - checkSkip: pixels; - - font: font; - duration: int; -} - -InputArea { - textFg: color; - textMargins: margins; - - placeholderFg: color; - placeholderFgActive: color; - placeholderMargins: margins; - placeholderAlign: align; - placeholderShift: pixels; - - duration: int; - - borderFg: color; - borderFgActive: color; - borderFgError: color; - - border: pixels; - borderActive: pixels; - borderError: pixels; - - font: font; - - width: pixels; - heightMin: pixels; - heightMax: pixels; -} - -InputField { - textBg: color; - textFg: color; - textMargins: margins; - textAlign: align; - - placeholderFg: color; - placeholderFgActive: color; - placeholderMargins: margins; - placeholderAlign: align; - placeholderShift: pixels; - - duration: int; - - borderFg: color; - borderFgActive: color; - borderFgError: color; - - border: pixels; - borderActive: pixels; - borderError: pixels; - - font: font; - - width: pixels; - height: pixels; -} - -PeerAvatarButton { - size: pixels; - photoSize: pixels; -} - -OutlineButton { - outlineWidth: pixels; - outlineFg: color; - outlineFgOver: color; - - textBg: color; - textBgOver: color; - - textFg: color; - textFgOver: color; - - font: font; - padding: margins; -} - -IconButton { - width: pixels; - height: pixels; - - opacity: double; - overOpacity: double; - - icon: icon; - iconPosition: point; - downIconPosition: point; - - duration: int; -} diff --git a/Telegram/Resources/colors.palette b/Telegram/Resources/colors.palette new file mode 100644 index 000000000..48635fbaf --- /dev/null +++ b/Telegram/Resources/colors.palette @@ -0,0 +1,469 @@ +/* +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 +*/ + +// basic +windowBg: #ffffff; // white: fallback for background +windowFg: #000000; // black: fallback for text color +windowBgOver: #f1f1f1; // light gray: fallback for over background +windowBgRipple: #e5e5e5; // darker gray: fallback for ripple effect +windowFgOver: windowFg; // black: fallback for over text color +windowSubTextFg: #999999; // gray: fallback for subtext color +windowSubTextFgOver: #919191; // gray over light blue: fallback for subtext over color +windowBoldFg: #222222; // dark gray: fallback for bold text color +windowBoldFgOver: #222222; // dark gray: fallback for over bold text color +windowBgActive: #40a7e3; // bright blue: fallback for blue filled active areas +windowFgActive: #ffffff; // text on bright blue: fallback for text on active areas +windowActiveTextFg: #168acd; // online blue: fallback for active color +windowShadowFg: #000000; // black: fallback for shadow color +windowShadowFgFallback: #f1f1f1; // gray: fallback for shadow without opacity + +shadowFg: #00000018; +slideFadeOutBg: #0000003c; +slideFadeOutShadowFg: windowShadowFg; + +imageBg: #000000; +imageBgTransparent: #ffffff; + +// widgets +activeButtonBg: windowBgActive; +activeButtonBgOver: #39a5db; +activeButtonBgRipple: #2095d0; +activeButtonFg: windowFgActive; +activeButtonFgOver: activeButtonFg; +activeButtonSecondaryFg: #cceeff; +activeButtonSecondaryFgOver: activeButtonSecondaryFg; +activeLineFg: #37a1de; +activeLineFgError: #e48383; + +lightButtonBg: windowBg; +lightButtonBgOver: #e3f1fa; +lightButtonBgRipple: #c9e4f6; +lightButtonFg: windowActiveTextFg; +lightButtonFgOver: lightButtonFg; + +attentionButtonFg: #d14e4e; +attentionButtonFgOver: #d14e4e; +attentionButtonBgOver: #fcdfde; +attentionButtonBgRipple: #f4c3c2; + +outlineButtonBg: windowBg; +outlineButtonBgOver: lightButtonBgOver; +outlineButtonOutlineFg: windowBgActive; +outlineButtonBgRipple: lightButtonBgRipple; + +menuBg: windowBg; +menuBgOver: windowBgOver; +menuBgRipple: windowBgRipple; +menuIconFg: #a8a8a8; +menuIconFgOver: #999999; +menuSubmenuArrowFg: #373737; +menuFgDisabled: #cccccc; +menuSeparatorFg: #f1f1f1; + +scrollBarBg: #00000053; +scrollBarBgOver: #0000007a; +scrollBg: #0000001a; +scrollBgOver: #0000002c; + +smallCloseIconFg: #c7c7c7; +smallCloseIconFgOver: #a3a3a3; + +radialFg: windowFgActive; +radialBg: #00000056; + +placeholderFg: windowSubTextFg; +placeholderFgActive: #aaaaaa; +inputBorderFg: #e0e0e0; +filterInputBorderFg: #54c3f3; +filterInputInactiveBg: windowBgOver; +checkboxFg: #b3b3b3; + +sliderBgInactive: #e1eaef; +sliderBgActive: windowBgActive; + +tooltipBg: #eef2f5; +tooltipFg: #5d6c80; +tooltipBorderFg: #c9d1db; + +// custom title bar for Windows and macOS +titleBg: windowBgOver; +titleShadow: #00000003; +titleButtonFg: #ababab; +titleButtonBgOver: #e5e5e5; +titleButtonFgOver: #9a9a9a; +titleButtonCloseBgOver: #e81123; +titleButtonCloseFgOver: windowFgActive; +titleFgActive: #3e3c3e; +titleFg: #acacac; + +// tray icon +trayCounterBg: #f23c34; +trayCounterBgMute: #888888; +trayCounterFg: #ffffff; +trayCounterBgMacInvert: #ffffff; +trayCounterFgMacInvert: #ffffff01; + +// layers +layerBg: #0000007f; + +cancelIconFg: menuIconFg; +cancelIconFgOver: menuIconFgOver; + +// boxes +boxBg: windowBg; +boxTextFg: windowFg; +boxTextFgGood: #4ab44a; +boxTextFgError: #d84d4d; +boxTitleFg: #404040; +boxSearchBg: boxBg; +boxSearchCancelIconFg: cancelIconFg; +boxSearchCancelIconFgOver: cancelIconFgOver; + +boxTitleAdditionalFg: #808080; +boxTitleCloseFg: cancelIconFg; +boxTitleCloseFgOver: cancelIconFgOver; + +membersAboutLimitFg: windowSubTextFgOver; + +contactsBg: windowBg; +contactsBgOver: windowBgOver; +contactsNameFg: boxTextFg; +contactsStatusFg: windowSubTextFg; +contactsStatusFgOver: windowSubTextFgOver; +contactsStatusFgOnline: windowActiveTextFg; + +photoCropFadeBg: layerBg; +photoCropPointFg: #ffffff7f; + +// intro +introBg: windowBg; +introTitleFg: windowBoldFg; +introDescriptionFg: windowSubTextFg; +introErrorFg: windowSubTextFg; + +introCoverTopBg: #0f89d0; +introCoverBottomBg: #39b0f0; +introCoverIconsFg: #5ec6ff; +introCoverPlaneTrace: #5ec6ff69; +introCoverPlaneInner: #c6d8e8; +introCoverPlaneOuter: #a1bed4; +introCoverPlaneTop: #ffffff; + +// dialogs +dialogsMenuIconFg: menuIconFg; +dialogsMenuIconFgOver: menuIconFgOver; + +dialogsBg: windowBg; +dialogsNameFg: windowBoldFg; +dialogsChatIconFg: dialogsNameFg; +dialogsDateFg: windowSubTextFg; +dialogsTextFg: windowSubTextFg; +dialogsTextFgService: windowActiveTextFg; +dialogsDraftFg: #dd4b39; +dialogsVerifiedIconBg: windowBgActive; +dialogsVerifiedIconFg: windowFgActive; +dialogsSendingIconFg: #c1c1c1; +dialogsSentIconFg: #5dc452; +dialogsUnreadBg: windowBgActive; +dialogsUnreadBgMuted: #bbbbbb; +dialogsUnreadFg: windowFgActive; + +dialogsBgOver: windowBgOver; +dialogsNameFgOver: windowBoldFgOver; +dialogsChatIconFgOver: dialogsNameFgOver; +dialogsDateFgOver: windowSubTextFgOver; +dialogsTextFgOver: windowSubTextFgOver; +dialogsTextFgServiceOver: dialogsTextFgService; +dialogsDraftFgOver: dialogsDraftFg; +dialogsVerifiedIconBgOver: dialogsVerifiedIconBg; +dialogsVerifiedIconFgOver: dialogsVerifiedIconFg; +dialogsSendingIconFgOver: dialogsSendingIconFg; +dialogsSentIconFgOver: dialogsSentIconFg; +dialogsUnreadBgOver: dialogsUnreadBg; +dialogsUnreadBgMutedOver: dialogsUnreadBgMuted; +dialogsUnreadFgOver: dialogsUnreadFg; + +dialogsBgActive: #419fd9; +dialogsNameFgActive: windowFgActive; +dialogsChatIconFgActive: dialogsNameFgActive; +dialogsDateFgActive: windowFgActive; +dialogsTextFgActive: windowFgActive; +dialogsTextFgServiceActive: dialogsTextFgActive; +dialogsDraftFgActive: #c6e1f7; +dialogsVerifiedIconBgActive: dialogsTextFgActive; +dialogsVerifiedIconFgActive: dialogsBgActive; +dialogsSendingIconFgActive: #ffffff99; +dialogsSentIconFgActive: dialogsTextFgActive; +dialogsUnreadBgActive: dialogsTextFgActive; +dialogsUnreadBgMutedActive: dialogsDraftFgActive; +dialogsUnreadFgActive: dialogsBgActive; + +dialogsForwardBg: dialogsBgActive; +dialogsForwardFg: dialogsNameFgActive; + +searchedBarBg: windowBgOver; +searchedBarBorder: shadowFg; +searchedBarFg: windowSubTextFgOver; + +// history +topBarBg: windowBg; + +emojiPanBg: windowBg; +emojiPanCategories: #f7f7f7 | windowBg; +emojiPanHeaderFg: windowSubTextFg; +emojiPanHeaderBg: #fffffff2 | emojiPanBg; +stickerPanDeleteBg: #000000cc; +stickerPanDeleteFg: windowFgActive; +stickerPreviewBg: #ffffffb0; + +historyTextInFg: windowFg; +historyTextOutFg: windowFg; +historyCaptionInFg: historyTextInFg; +historyCaptionOutFg: historyTextOutFg; +historyFileNameInFg: historyTextInFg; +historyFileNameOutFg: historyTextOutFg; +historyOutIconFg: dialogsSentIconFg; +historyOutIconFgSelected: #4da79f; +historyIconFgInverted: windowFgActive; +historySendingOutIconFg: #98d292; +historySendingInIconFg: #a0adb5; +historySendingInvertedIconFg: #ffffffc8; + +historySystemBg: #89a0b47f; +historySystemBgSelected: #bbc8d4a2; +historySystemFg: windowFgActive; + +historyUnreadBarBg: #fcfbfa; +historyUnreadBarBorder: shadowFg; +historyUnreadBarFg: #538bb4; + +historyForwardChooseBg: #0000004c; +historyForwardChooseFg: windowFgActive; + +historyPeer1NameFg: #c03d33; // red +historyPeer1UserpicBg: #e17076; +historyPeer2NameFg: #4fad2d; // green +historyPeer2UserpicBg: #7bc862; +historyPeer3NameFg: #d09306; // yellow +historyPeer3UserpicBg: #e5ca77; +historyPeer4NameFg: windowActiveTextFg; // blue +historyPeer4UserpicBg: #65aadd; +historyPeer5NameFg: #8544d6; // purple +historyPeer5UserpicBg: #a695e7; +historyPeer6NameFg: #cd4073; // pink +historyPeer6UserpicBg: #ee7aae; +historyPeer7NameFg: #2996ad; // sea +historyPeer7UserpicBg: #6ec9cb; +historyPeer8NameFg: #ce671b; // orange +historyPeer8UserpicBg: #faa774; +historyPeerUserpicFg: windowFgActive; + +historyScrollBarBg: #517c417a; +historyScrollBarBgOver: #517c41bc; +historyScrollBg: #517c414c; +historyScrollBgOver: #517c416b; + +msgInBg: windowBg; +msgInBgSelected: #c2dcf2; // #358cd4 with 30% opacity +msgOutBg: #effdde; +msgOutBgSelected: #b7dbdb; +msgSelectOverlay: #358cd44c; +msgStickerOverlay: #358cd47f; +msgInServiceFg: windowActiveTextFg; +msgInServiceFgSelected: windowActiveTextFg; +msgOutServiceFg: #3a8e26; +msgOutServiceFgSelected: #367570; +msgInShadow: #748ea229; +msgInShadowSelected: #548dbb29; +msgOutShadow: #3ac34740; +msgOutShadowSelected: #37a78e40; +msgInDateFg: #a0acb6; +msgInDateFgSelected: #6a9cc5; +msgOutDateFg: #6cc264; +msgOutDateFgSelected: #50a79c; +msgServiceFg: windowFgActive; +msgServiceBg: #517c417f; +msgServiceBgSelected: #96b38ba2; +msgInReplyBarColor: activeLineFg; +msgInReplyBarSelColor: activeLineFg; +msgOutReplyBarColor: historyOutIconFg; +msgOutReplyBarSelColor: historyOutIconFgSelected; +msgImgReplyBarColor: msgServiceFg; +msgInMonoFg: #4e7391; +msgOutMonoFg: #469165; +msgDateImgFg: msgServiceFg; +msgDateImgBg: #00000054; +msgDateImgBgOver: #00000074; +msgDateImgBgSelected: #1c4a7187; + +msgFileThumbLinkInFg: lightButtonFg; +msgFileThumbLinkInFgSelected: lightButtonFgOver; +msgFileThumbLinkOutFg: #5eba5b; +msgFileThumbLinkOutFgSelected: #31a298; +msgFileInBg: windowBgActive; +msgFileInBgOver: #4eade3; +msgFileInBgSelected: #51a3d3; +msgFileOutBg: #78c67f; +msgFileOutBgOver: #6bc272; +msgFileOutBgSelected: #5fb389; + +msgFile1Bg: #72b1df; // blue +msgFile1BgDark: #5c9ece; +msgFile1BgOver: #5294c4; +msgFile1BgSelected: #5099d0; +msgFile2Bg: #61b96e; // green +msgFile2BgDark: #4da859; +msgFile2BgOver: #44a050; +msgFile2BgSelected: #46a07e; +msgFile3Bg: #e47272; // red +msgFile3BgDark: #cd5b5e; +msgFile3BgOver: #c35154; +msgFile3BgSelected: #9f6a82; +msgFile4Bg: #efc274; // yellow +msgFile4BgDark: #e6a561; +msgFile4BgOver: #dc9c5a; +msgFile4BgSelected: #b19d84; + +historyFileInIconFg: msgInBg; +historyFileInIconFgSelected: msgInBgSelected; +historyFileInRadialFg: historyFileInIconFg; +historyFileInRadialFgSelected: historyFileInIconFgSelected; +historyFileOutIconFg: msgOutBg; +historyFileOutIconFgSelected: msgOutBgSelected; +historyFileOutRadialFg: historyFileOutIconFg; +historyFileOutRadialFgSelected: historyFileOutIconFgSelected; +historyFileThumbIconFg: msgInBg; +historyFileThumbIconFgSelected: msgInBgSelected; +historyFileThumbRadialFg: historyFileThumbIconFg; +historyFileThumbRadialFgSelected: historyFileThumbIconFgSelected; + +msgWaveformInActive: windowBgActive; +msgWaveformInActiveSelected: #51a3d3; +msgWaveformInInactive: #d4dee6; +msgWaveformInInactiveSelected: #9cc1e1; +msgWaveformOutActive: #78c67f; +msgWaveformOutActiveSelected: #6badad; +msgWaveformOutInactive: #b3e2b4; +msgWaveformOutInactiveSelected: #91c3c3; + +msgBotKbOverBgAdd: #ffffff20; +msgBotKbIconFg: msgServiceFg; +msgBotKbRippleBg: #00000020; + +mediaInFg: msgInDateFg; +mediaInFgSelected: msgInDateFgSelected; +mediaOutFg: msgOutDateFg; +mediaOutFgSelected: msgOutDateFgSelected; + +youtubePlayIconBg: #e83131c8; +youtubePlayIconFg: windowFgActive; +videoPlayIconBg: #0000007f; +videoPlayIconFg: #ffffff; +toastBg: #000000b2; +toastFg: windowFgActive; + +reportSpamBg: emojiPanHeaderBg; +reportSpamFg: windowFg; + +historyToDownShadow: #00000040; +historyComposeAreaBg: msgInBg; +historyComposeAreaFg: historyTextInFg; +historyComposeAreaFgService: msgInDateFg; +historyComposeIconFg: menuIconFg; +historyComposeIconFgOver: menuIconFgOver; +historySendIconFg: windowBgActive; +historySendIconFgOver: windowBgActive; +historyPinnedBg: historyComposeAreaBg; +historyReplyBg: historyComposeAreaBg; +historyReplyIconFg: windowBgActive; +historyReplyCancelFg: cancelIconFg; +historyReplyCancelFgOver: cancelIconFgOver; + +historyComposeButtonBg: historyComposeAreaBg; +historyComposeButtonBgOver: windowBgOver; +historyComposeButtonBgRipple: windowBgRipple; + +// overview +overviewCheckBg: #00000040; +overviewCheckFg: windowBg; +overviewCheckFgActive: windowBg; +overviewPhotoSelectOverlay: #40ace333; + +// profile +profileStatusFgOver: #7c99b2; +profileVerifiedCheckBg: windowBgActive; +profileVerifiedCheckFg: windowFgActive; + +// settings +notificationsBoxMonitorFg: windowFg; +notificationsBoxScreenBg: dialogsBgActive; // #6389a8; + +notificationSampleUserpicFg: windowBgActive; +notificationSampleCloseFg: #d7d7d7 | windowSubTextFg; +notificationSampleTextFg: #d7d7d7 | windowSubTextFg; +notificationSampleNameFg: #939393 | windowSubTextFg; + +mainMenuBg: windowBg; +mainMenuCoverBg: dialogsBgActive; +mainMenuCoverFg: windowFgActive; + +mediaPlayerBg: windowBg; +mediaPlayerActiveFg: windowBgActive; +mediaPlayerInactiveFg: sliderBgInactive; +mediaPlayerDisabledFg: #9dd1ef; + +// mediaview +mediaviewFileBg: windowBg; +mediaviewFileNameFg: windowFg; +mediaviewFileSizeFg: windowSubTextFg; +mediaviewFileRedCornerFg: #d55959; +mediaviewFileYellowCornerFg: #e8a659; +mediaviewFileGreenCornerFg: #49a957; +mediaviewFileBlueCornerFg: #599dcf; +mediaviewFileExtFg: activeButtonFg; + +mediaviewMenuBg: #383838; +mediaviewMenuBgOver: #505050; +mediaviewMenuBgRipple: #676767; +mediaviewMenuFg: windowFgActive; + +mediaviewBg: #222222eb; +mediaviewVideoBg: imageBg; +mediaviewControlBg: #0000003c; +mediaviewControlFg: windowFgActive; +mediaviewCaptionBg: #11111180; +mediaviewCaptionFg: mediaviewControlFg; +mediaviewTextLinkFg: #91d9ff; +mediaviewSaveMsgBg: toastBg; +mediaviewSaveMsgFg: toastFg; + +mediaviewPlaybackActive: #c7c7c7; +mediaviewPlaybackInactive: #252525; +mediaviewPlaybackActiveOver: #ffffff; +mediaviewPlaybackInactiveOver: #474747; +mediaviewPlaybackProgressFg: #ffffffc7; +mediaviewPlaybackIconFg: mediaviewPlaybackActive; +mediaviewPlaybackIconFgOver: mediaviewPlaybackActiveOver; +mediaviewTransparentBg: #ffffff; +mediaviewTransparentFg: #cccccc; + +// notification +notificationBg: windowBg; diff --git a/Telegram/Resources/default.tdesktop-theme b/Telegram/Resources/default.tdesktop-theme new file mode 100644 index 000000000..8e61fd3bd --- /dev/null +++ b/Telegram/Resources/default.tdesktop-theme @@ -0,0 +1,397 @@ +// +// This is a sample Telegram Desktop theme file. +// It was generated from the 'colors.palette' style file. +// +// To create a theme with a background image included you should +// put two files in a .zip archive: +// +// First one is the color scheme like the one you're viewing +// right now, this file should be named 'colors.tdesktop-theme'. +// +// Second one should be the background image and it can be named +// 'background.jpg', 'background.png', 'tiled.jpg' or 'tiled.png'. +// You should name it 'background' (if you'd like it not to be tiled), +// or it can be named 'tiled' (if you'd like it to be tiled). +// +// After that you need to change the extension of your .zip archive +// to 'tdesktop-theme', so you'll have: +// +// mytheme.tdesktop-theme +// |-colors.tdesktop-theme +// |-background.jpg (or tiled.jpg, background.png, tiled.png) +// + +windowBg: #ffffff; +windowFg: #000000; +windowBgOver: #f1f1f1; +windowBgRipple: #e5e5e5; +windowFgOver: windowFg; +windowSubTextFg: #999999; +windowSubTextFgOver: #919191; +windowBoldFg: #222222; +windowBoldFgOver: #222222; +windowBgActive: #40a7e3; +windowFgActive: #ffffff; +windowActiveTextFg: #168acd; +windowShadowFg: #000000; +windowShadowFgFallback: #f1f1f1; +shadowFg: #00000018; +slideFadeOutBg: #0000003c; +slideFadeOutShadowFg: windowShadowFg; +imageBg: #000000; +imageBgTransparent: #ffffff; +activeButtonBg: windowBgActive; +activeButtonBgOver: #39a5db; +activeButtonBgRipple: #2095d0; +activeButtonFg: windowFgActive; +activeButtonFgOver: activeButtonFg; +activeButtonSecondaryFg: #cceeff; +activeButtonSecondaryFgOver: activeButtonSecondaryFg; +activeLineFg: #37a1de; +activeLineFgError: #e48383; +lightButtonBg: windowBg; +lightButtonBgOver: #e3f1fa; +lightButtonBgRipple: #c9e4f6; +lightButtonFg: windowActiveTextFg; +lightButtonFgOver: lightButtonFg; +attentionButtonFg: #d14e4e; +attentionButtonFgOver: #d14e4e; +attentionButtonBgOver: #fcdfde; +attentionButtonBgRipple: #f4c3c2; +outlineButtonBg: windowBg; +outlineButtonBgOver: lightButtonBgOver; +outlineButtonOutlineFg: windowBgActive; +outlineButtonBgRipple: lightButtonBgRipple; +menuBg: windowBg; +menuBgOver: windowBgOver; +menuBgRipple: windowBgRipple; +menuIconFg: #a8a8a8; +menuIconFgOver: #999999; +menuSubmenuArrowFg: #373737; +menuFgDisabled: #cccccc; +menuSeparatorFg: #f1f1f1; +scrollBarBg: #00000053; +scrollBarBgOver: #0000007a; +scrollBg: #0000001a; +scrollBgOver: #0000002c; +smallCloseIconFg: #c7c7c7; +smallCloseIconFgOver: #a3a3a3; +radialFg: windowFgActive; +radialBg: #00000056; +placeholderFg: windowSubTextFg; +placeholderFgActive: #aaaaaa; +inputBorderFg: #e0e0e0; +filterInputBorderFg: #54c3f3; +filterInputInactiveBg: windowBgOver; +checkboxFg: #b3b3b3; +sliderBgInactive: #e1eaef; +sliderBgActive: windowBgActive; +tooltipBg: #eef2f5; +tooltipFg: #5d6c80; +tooltipBorderFg: #c9d1db; +titleBg: windowBgOver; +titleShadow: #00000003; +titleButtonFg: #ababab; +titleButtonBgOver: #e5e5e5; +titleButtonFgOver: #9a9a9a; +titleButtonCloseBgOver: #e81123; +titleButtonCloseFgOver: windowFgActive; +titleFgActive: #3e3c3e; +titleFg: #acacac; +trayCounterBg: #f23c34; +trayCounterBgMute: #888888; +trayCounterFg: #ffffff; +trayCounterBgMacInvert: #ffffff; +trayCounterFgMacInvert: #ffffff01; +layerBg: #0000007f; +cancelIconFg: menuIconFg; +cancelIconFgOver: menuIconFgOver; +boxBg: windowBg; +boxTextFg: windowFg; +boxTextFgGood: #4ab44a; +boxTextFgError: #d84d4d; +boxTitleFg: #404040; +boxSearchBg: boxBg; +boxSearchCancelIconFg: cancelIconFg; +boxSearchCancelIconFgOver: cancelIconFgOver; +boxTitleAdditionalFg: #808080; +boxTitleCloseFg: cancelIconFg; +boxTitleCloseFgOver: cancelIconFgOver; +membersAboutLimitFg: windowSubTextFgOver; +contactsBg: windowBg; +contactsBgOver: windowBgOver; +contactsNameFg: boxTextFg; +contactsStatusFg: windowSubTextFg; +contactsStatusFgOver: windowSubTextFgOver; +contactsStatusFgOnline: windowActiveTextFg; +photoCropFadeBg: layerBg; +photoCropPointFg: #ffffff7f; +introBg: windowBg; +introTitleFg: windowBoldFg; +introDescriptionFg: windowSubTextFg; +introErrorFg: windowSubTextFg; +introCoverTopBg: #0f89d0; +introCoverBottomBg: #39b0f0; +introCoverIconsFg: #5ec6ff; +introCoverPlaneTrace: #5ec6ff69; +introCoverPlaneInner: #c6d8e8; +introCoverPlaneOuter: #a1bed4; +introCoverPlaneTop: #ffffff; +dialogsMenuIconFg: menuIconFg; +dialogsMenuIconFgOver: menuIconFgOver; +dialogsBg: windowBg; +dialogsNameFg: windowBoldFg; +dialogsChatIconFg: dialogsNameFg; +dialogsDateFg: windowSubTextFg; +dialogsTextFg: windowSubTextFg; +dialogsTextFgService: windowActiveTextFg; +dialogsDraftFg: #dd4b39; +dialogsVerifiedIconBg: windowBgActive; +dialogsVerifiedIconFg: windowFgActive; +dialogsSendingIconFg: #c1c1c1; +dialogsSentIconFg: #5dc452; +dialogsUnreadBg: windowBgActive; +dialogsUnreadBgMuted: #bbbbbb; +dialogsUnreadFg: windowFgActive; +dialogsBgOver: windowBgOver; +dialogsNameFgOver: windowBoldFgOver; +dialogsChatIconFgOver: dialogsNameFgOver; +dialogsDateFgOver: windowSubTextFgOver; +dialogsTextFgOver: windowSubTextFgOver; +dialogsTextFgServiceOver: dialogsTextFgService; +dialogsDraftFgOver: dialogsDraftFg; +dialogsVerifiedIconBgOver: dialogsVerifiedIconBg; +dialogsVerifiedIconFgOver: dialogsVerifiedIconFg; +dialogsSendingIconFgOver: dialogsSendingIconFg; +dialogsSentIconFgOver: dialogsSentIconFg; +dialogsUnreadBgOver: dialogsUnreadBg; +dialogsUnreadBgMutedOver: dialogsUnreadBgMuted; +dialogsUnreadFgOver: dialogsUnreadFg; +dialogsBgActive: #419fd9; +dialogsNameFgActive: windowFgActive; +dialogsChatIconFgActive: dialogsNameFgActive; +dialogsDateFgActive: windowFgActive; +dialogsTextFgActive: windowFgActive; +dialogsTextFgServiceActive: dialogsTextFgActive; +dialogsDraftFgActive: #c6e1f7; +dialogsVerifiedIconBgActive: dialogsTextFgActive; +dialogsVerifiedIconFgActive: dialogsBgActive; +dialogsSendingIconFgActive: #ffffff99; +dialogsSentIconFgActive: dialogsTextFgActive; +dialogsUnreadBgActive: dialogsTextFgActive; +dialogsUnreadBgMutedActive: dialogsDraftFgActive; +dialogsUnreadFgActive: dialogsBgActive; +dialogsForwardBg: dialogsBgActive; +dialogsForwardFg: dialogsNameFgActive; +searchedBarBg: windowBgOver; +searchedBarBorder: shadowFg; +searchedBarFg: windowSubTextFgOver; +topBarBg: windowBg; +emojiPanBg: windowBg; +emojiPanCategories: #f7f7f7; // windowBg; +emojiPanHeaderFg: windowSubTextFg; +emojiPanHeaderBg: #fffffff2; // emojiPanBg; +stickerPanDeleteBg: #000000cc; +stickerPanDeleteFg: windowFgActive; +stickerPreviewBg: #ffffffb0; +historyTextInFg: windowFg; +historyTextOutFg: windowFg; +historyCaptionInFg: historyTextInFg; +historyCaptionOutFg: historyTextOutFg; +historyFileNameInFg: historyTextInFg; +historyFileNameOutFg: historyTextOutFg; +historyOutIconFg: dialogsSentIconFg; +historyOutIconFgSelected: #4da79f; +historyIconFgInverted: windowFgActive; +historySendingOutIconFg: #98d292; +historySendingInIconFg: #a0adb5; +historySendingInvertedIconFg: #ffffffc8; +historySystemBg: #89a0b47f; +historySystemBgSelected: #bbc8d4a2; +historySystemFg: windowFgActive; +historyUnreadBarBg: #fcfbfa; +historyUnreadBarBorder: shadowFg; +historyUnreadBarFg: #538bb4; +historyForwardChooseBg: #0000004c; +historyForwardChooseFg: windowFgActive; +historyPeer1NameFg: #c03d33; +historyPeer1UserpicBg: #e17076; +historyPeer2NameFg: #4fad2d; +historyPeer2UserpicBg: #7bc862; +historyPeer3NameFg: #d09306; +historyPeer3UserpicBg: #e5ca77; +historyPeer4NameFg: windowActiveTextFg; +historyPeer4UserpicBg: #65aadd; +historyPeer5NameFg: #8544d6; +historyPeer5UserpicBg: #a695e7; +historyPeer6NameFg: #cd4073; +historyPeer6UserpicBg: #ee7aae; +historyPeer7NameFg: #2996ad; +historyPeer7UserpicBg: #6ec9cb; +historyPeer8NameFg: #ce671b; +historyPeer8UserpicBg: #faa774; +historyPeerUserpicFg: windowFgActive; +historyScrollBarBg: #517c417a; +historyScrollBarBgOver: #517c41bc; +historyScrollBg: #517c414c; +historyScrollBgOver: #517c416b; +msgInBg: windowBg; +msgInBgSelected: #c2dcf2; +msgOutBg: #effdde; +msgOutBgSelected: #b7dbdb; +msgSelectOverlay: #358cd44c; +msgStickerOverlay: #358cd47f; +msgInServiceFg: windowActiveTextFg; +msgInServiceFgSelected: windowActiveTextFg; +msgOutServiceFg: #3a8e26; +msgOutServiceFgSelected: #367570; +msgInShadow: #748ea229; +msgInShadowSelected: #548dbb29; +msgOutShadow: #3ac34740; +msgOutShadowSelected: #37a78e40; +msgInDateFg: #a0acb6; +msgInDateFgSelected: #6a9cc5; +msgOutDateFg: #6cc264; +msgOutDateFgSelected: #50a79c; +msgServiceFg: windowFgActive; +msgServiceBg: #517c417f; +msgServiceBgSelected: #96b38ba2; +msgInReplyBarColor: activeLineFg; +msgInReplyBarSelColor: activeLineFg; +msgOutReplyBarColor: historyOutIconFg; +msgOutReplyBarSelColor: historyOutIconFgSelected; +msgImgReplyBarColor: msgServiceFg; +msgInMonoFg: #4e7391; +msgOutMonoFg: #469165; +msgDateImgFg: msgServiceFg; +msgDateImgBg: #00000054; +msgDateImgBgOver: #00000074; +msgDateImgBgSelected: #1c4a7187; +msgFileThumbLinkInFg: lightButtonFg; +msgFileThumbLinkInFgSelected: lightButtonFgOver; +msgFileThumbLinkOutFg: #5eba5b; +msgFileThumbLinkOutFgSelected: #31a298; +msgFileInBg: windowBgActive; +msgFileInBgOver: #4eade3; +msgFileInBgSelected: #51a3d3; +msgFileOutBg: #78c67f; +msgFileOutBgOver: #6bc272; +msgFileOutBgSelected: #5fb389; +msgFile1Bg: #72b1df; +msgFile1BgDark: #5c9ece; +msgFile1BgOver: #5294c4; +msgFile1BgSelected: #5099d0; +msgFile2Bg: #61b96e; +msgFile2BgDark: #4da859; +msgFile2BgOver: #44a050; +msgFile2BgSelected: #46a07e; +msgFile3Bg: #e47272; +msgFile3BgDark: #cd5b5e; +msgFile3BgOver: #c35154; +msgFile3BgSelected: #9f6a82; +msgFile4Bg: #efc274; +msgFile4BgDark: #e6a561; +msgFile4BgOver: #dc9c5a; +msgFile4BgSelected: #b19d84; +historyFileInIconFg: msgInBg; +historyFileInIconFgSelected: msgInBgSelected; +historyFileInRadialFg: historyFileInIconFg; +historyFileInRadialFgSelected: historyFileInIconFgSelected; +historyFileOutIconFg: msgOutBg; +historyFileOutIconFgSelected: msgOutBgSelected; +historyFileOutRadialFg: historyFileOutIconFg; +historyFileOutRadialFgSelected: historyFileOutIconFgSelected; +historyFileThumbIconFg: msgInBg; +historyFileThumbIconFgSelected: msgInBgSelected; +historyFileThumbRadialFg: historyFileThumbIconFg; +historyFileThumbRadialFgSelected: historyFileThumbIconFgSelected; +msgWaveformInActive: windowBgActive; +msgWaveformInActiveSelected: #51a3d3; +msgWaveformInInactive: #d4dee6; +msgWaveformInInactiveSelected: #9cc1e1; +msgWaveformOutActive: #78c67f; +msgWaveformOutActiveSelected: #6badad; +msgWaveformOutInactive: #b3e2b4; +msgWaveformOutInactiveSelected: #91c3c3; +msgBotKbOverBgAdd: #ffffff20; +msgBotKbIconFg: msgServiceFg; +msgBotKbRippleBg: #00000020; +mediaInFg: msgInDateFg; +mediaInFgSelected: msgInDateFgSelected; +mediaOutFg: msgOutDateFg; +mediaOutFgSelected: msgOutDateFgSelected; +youtubePlayIconBg: #e83131c8; +youtubePlayIconFg: windowFgActive; +videoPlayIconBg: #0000007f; +videoPlayIconFg: #ffffff; +toastBg: #000000b2; +toastFg: windowFgActive; +reportSpamBg: emojiPanHeaderBg; +reportSpamFg: windowFg; +historyToDownShadow: #00000040; +historyComposeAreaBg: msgInBg; +historyComposeAreaFg: historyTextInFg; +historyComposeAreaFgService: msgInDateFg; +historyComposeIconFg: menuIconFg; +historyComposeIconFgOver: menuIconFgOver; +historySendIconFg: windowBgActive; +historySendIconFgOver: windowBgActive; +historyPinnedBg: historyComposeAreaBg; +historyReplyBg: historyComposeAreaBg; +historyReplyIconFg: windowBgActive; +historyReplyCancelFg: cancelIconFg; +historyReplyCancelFgOver: cancelIconFgOver; +historyComposeButtonBg: historyComposeAreaBg; +historyComposeButtonBgOver: windowBgOver; +historyComposeButtonBgRipple: windowBgRipple; +overviewCheckBg: #00000040; +overviewCheckFg: windowBg; +overviewCheckFgActive: windowBg; +overviewPhotoSelectOverlay: #40ace333; +profileStatusFgOver: #7c99b2; +profileVerifiedCheckBg: windowBgActive; +profileVerifiedCheckFg: windowFgActive; +notificationsBoxMonitorFg: windowFg; +notificationsBoxScreenBg: dialogsBgActive; +notificationSampleUserpicFg: windowBgActive; +notificationSampleCloseFg: #d7d7d7; // windowSubTextFg; +notificationSampleTextFg: #d7d7d7; // windowSubTextFg; +notificationSampleNameFg: #939393; // windowSubTextFg; +mainMenuBg: windowBg; +mainMenuCoverBg: dialogsBgActive; +mainMenuCoverFg: windowFgActive; +mediaPlayerBg: windowBg; +mediaPlayerActiveFg: windowBgActive; +mediaPlayerInactiveFg: sliderBgInactive; +mediaPlayerDisabledFg: #9dd1ef; +mediaviewFileBg: windowBg; +mediaviewFileNameFg: windowFg; +mediaviewFileSizeFg: windowSubTextFg; +mediaviewFileRedCornerFg: #d55959; +mediaviewFileYellowCornerFg: #e8a659; +mediaviewFileGreenCornerFg: #49a957; +mediaviewFileBlueCornerFg: #599dcf; +mediaviewFileExtFg: activeButtonFg; +mediaviewMenuBg: #383838; +mediaviewMenuBgOver: #505050; +mediaviewMenuBgRipple: #676767; +mediaviewMenuFg: windowFgActive; +mediaviewBg: #222222eb; +mediaviewVideoBg: imageBg; +mediaviewControlBg: #0000003c; +mediaviewControlFg: windowFgActive; +mediaviewCaptionBg: #11111180; +mediaviewCaptionFg: mediaviewControlFg; +mediaviewTextLinkFg: #91d9ff; +mediaviewSaveMsgBg: toastBg; +mediaviewSaveMsgFg: toastFg; +mediaviewPlaybackActive: #c7c7c7; +mediaviewPlaybackInactive: #252525; +mediaviewPlaybackActiveOver: #ffffff; +mediaviewPlaybackInactiveOver: #474747; +mediaviewPlaybackProgressFg: #ffffffc7; +mediaviewPlaybackIconFg: mediaviewPlaybackActive; +mediaviewPlaybackIconFgOver: mediaviewPlaybackActiveOver; +mediaviewTransparentBg: #ffffff; +mediaviewTransparentFg: #cccccc; +notificationBg: windowBg; diff --git a/Telegram/Resources/icons/box_button_back.png b/Telegram/Resources/icons/box_button_back.png new file mode 100644 index 000000000..9db1870f6 Binary files /dev/null and b/Telegram/Resources/icons/box_button_back.png differ diff --git a/Telegram/Resources/icons/box_button_back@2x.png b/Telegram/Resources/icons/box_button_back@2x.png new file mode 100644 index 000000000..b729fc279 Binary files /dev/null and b/Telegram/Resources/icons/box_button_back@2x.png differ diff --git a/Telegram/Resources/icons/box_button_close.png b/Telegram/Resources/icons/box_button_close.png index 57abcfb82..3b0402893 100644 Binary files a/Telegram/Resources/icons/box_button_close.png and b/Telegram/Resources/icons/box_button_close.png differ diff --git a/Telegram/Resources/icons/box_button_close@2x.png b/Telegram/Resources/icons/box_button_close@2x.png index bf2a19e72..fea726bdb 100644 Binary files a/Telegram/Resources/icons/box_button_close@2x.png and b/Telegram/Resources/icons/box_button_close@2x.png differ diff --git a/Telegram/Resources/icons/box_search_cancel.png b/Telegram/Resources/icons/box_search_cancel.png deleted file mode 100644 index 7fa112a33..000000000 Binary files a/Telegram/Resources/icons/box_search_cancel.png and /dev/null differ diff --git a/Telegram/Resources/icons/box_search_cancel@2x.png b/Telegram/Resources/icons/box_search_cancel@2x.png deleted file mode 100644 index 40d3def3d..000000000 Binary files a/Telegram/Resources/icons/box_search_cancel@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/box_shadow.png b/Telegram/Resources/icons/box_shadow.png deleted file mode 100644 index 44d17254e..000000000 Binary files a/Telegram/Resources/icons/box_shadow.png and /dev/null differ diff --git a/Telegram/Resources/icons/box_shadow@2x.png b/Telegram/Resources/icons/box_shadow@2x.png deleted file mode 100644 index 06293b740..000000000 Binary files a/Telegram/Resources/icons/box_shadow@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/box_title_shadow.png b/Telegram/Resources/icons/box_title_shadow.png deleted file mode 100644 index bba09b240..000000000 Binary files a/Telegram/Resources/icons/box_title_shadow.png and /dev/null differ diff --git a/Telegram/Resources/icons/box_title_shadow@2x.png b/Telegram/Resources/icons/box_title_shadow@2x.png deleted file mode 100644 index 7d97eb59e..000000000 Binary files a/Telegram/Resources/icons/box_title_shadow@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/bubble_tail.png b/Telegram/Resources/icons/bubble_tail.png new file mode 100644 index 000000000..b953ce8f6 Binary files /dev/null and b/Telegram/Resources/icons/bubble_tail.png differ diff --git a/Telegram/Resources/icons/bubble_tail@2x.png b/Telegram/Resources/icons/bubble_tail@2x.png new file mode 100644 index 000000000..f946cce57 Binary files /dev/null and b/Telegram/Resources/icons/bubble_tail@2x.png differ diff --git a/Telegram/Resources/icons/contacts_add.png b/Telegram/Resources/icons/contacts_add.png index 98f770cc6..3f3efebd0 100644 Binary files a/Telegram/Resources/icons/contacts_add.png and b/Telegram/Resources/icons/contacts_add.png differ diff --git a/Telegram/Resources/icons/contacts_add@2x.png b/Telegram/Resources/icons/contacts_add@2x.png index 7fc3c170f..0b68e8665 100644 Binary files a/Telegram/Resources/icons/contacts_add@2x.png and b/Telegram/Resources/icons/contacts_add@2x.png differ diff --git a/Telegram/Resources/icons/dialogs_cancel_search.png b/Telegram/Resources/icons/dialogs_cancel_search.png new file mode 100644 index 000000000..211db7e35 Binary files /dev/null and b/Telegram/Resources/icons/dialogs_cancel_search.png differ diff --git a/Telegram/Resources/icons/dialogs_cancel_search@2x.png b/Telegram/Resources/icons/dialogs_cancel_search@2x.png new file mode 100644 index 000000000..f20842fbc Binary files /dev/null and b/Telegram/Resources/icons/dialogs_cancel_search@2x.png differ diff --git a/Telegram/Resources/icons/dialogs_chat.png b/Telegram/Resources/icons/dialogs_chat.png index a17196562..ed06e1647 100644 Binary files a/Telegram/Resources/icons/dialogs_chat.png and b/Telegram/Resources/icons/dialogs_chat.png differ diff --git a/Telegram/Resources/icons/dialogs_chat@2x.png b/Telegram/Resources/icons/dialogs_chat@2x.png index f49e61f08..a5e8674e9 100644 Binary files a/Telegram/Resources/icons/dialogs_chat@2x.png and b/Telegram/Resources/icons/dialogs_chat@2x.png differ diff --git a/Telegram/Resources/icons/dialogs_lock.png b/Telegram/Resources/icons/dialogs_lock.png new file mode 100644 index 000000000..5067efdfe Binary files /dev/null and b/Telegram/Resources/icons/dialogs_lock.png differ diff --git a/Telegram/Resources/icons/dialogs_lock@2x.png b/Telegram/Resources/icons/dialogs_lock@2x.png new file mode 100644 index 000000000..d8f8bfd74 Binary files /dev/null and b/Telegram/Resources/icons/dialogs_lock@2x.png differ diff --git a/Telegram/Resources/icons/dialogs_menu.png b/Telegram/Resources/icons/dialogs_menu.png new file mode 100644 index 000000000..9e3833ce6 Binary files /dev/null and b/Telegram/Resources/icons/dialogs_menu.png differ diff --git a/Telegram/Resources/icons/dialogs_menu@2x.png b/Telegram/Resources/icons/dialogs_menu@2x.png new file mode 100644 index 000000000..a5beed83c Binary files /dev/null and b/Telegram/Resources/icons/dialogs_menu@2x.png differ diff --git a/Telegram/Resources/icons/dialogs_pinned.png b/Telegram/Resources/icons/dialogs_pinned.png new file mode 100644 index 000000000..9ddb48747 Binary files /dev/null and b/Telegram/Resources/icons/dialogs_pinned.png differ diff --git a/Telegram/Resources/icons/dialogs_pinned@2x.png b/Telegram/Resources/icons/dialogs_pinned@2x.png new file mode 100644 index 000000000..231933b94 Binary files /dev/null and b/Telegram/Resources/icons/dialogs_pinned@2x.png differ diff --git a/Telegram/Resources/icons/dialogs_unlock.png b/Telegram/Resources/icons/dialogs_unlock.png new file mode 100644 index 000000000..d98ce184e Binary files /dev/null and b/Telegram/Resources/icons/dialogs_unlock.png differ diff --git a/Telegram/Resources/icons/dialogs_unlock@2x.png b/Telegram/Resources/icons/dialogs_unlock@2x.png new file mode 100644 index 000000000..a6c7bbd2f Binary files /dev/null and b/Telegram/Resources/icons/dialogs_unlock@2x.png differ diff --git a/Telegram/Resources/icons/dropdown_shadow.png b/Telegram/Resources/icons/dropdown_shadow.png deleted file mode 100644 index 7055a8b49..000000000 Binary files a/Telegram/Resources/icons/dropdown_shadow.png and /dev/null differ diff --git a/Telegram/Resources/icons/dropdown_shadow@2x.png b/Telegram/Resources/icons/dropdown_shadow@2x.png deleted file mode 100644 index 1931c7b11..000000000 Binary files a/Telegram/Resources/icons/dropdown_shadow@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/emoji_activity.png b/Telegram/Resources/icons/emoji_activity.png new file mode 100644 index 000000000..055ff9f33 Binary files /dev/null and b/Telegram/Resources/icons/emoji_activity.png differ diff --git a/Telegram/Resources/icons/emoji_activity@2x.png b/Telegram/Resources/icons/emoji_activity@2x.png new file mode 100644 index 000000000..681df82dd Binary files /dev/null and b/Telegram/Resources/icons/emoji_activity@2x.png differ diff --git a/Telegram/Resources/icons/emoji_delete.png b/Telegram/Resources/icons/emoji_delete.png new file mode 100644 index 000000000..8023fa423 Binary files /dev/null and b/Telegram/Resources/icons/emoji_delete.png differ diff --git a/Telegram/Resources/icons/emoji_delete@2x.png b/Telegram/Resources/icons/emoji_delete@2x.png new file mode 100644 index 000000000..bae82620a Binary files /dev/null and b/Telegram/Resources/icons/emoji_delete@2x.png differ diff --git a/Telegram/Resources/icons/emoji_delete_bg.png b/Telegram/Resources/icons/emoji_delete_bg.png new file mode 100644 index 000000000..6f973e511 Binary files /dev/null and b/Telegram/Resources/icons/emoji_delete_bg.png differ diff --git a/Telegram/Resources/icons/emoji_delete_bg@2x.png b/Telegram/Resources/icons/emoji_delete_bg@2x.png new file mode 100644 index 000000000..08c327685 Binary files /dev/null and b/Telegram/Resources/icons/emoji_delete_bg@2x.png differ diff --git a/Telegram/Resources/icons/emoji_food.png b/Telegram/Resources/icons/emoji_food.png new file mode 100644 index 000000000..aeeccb7e1 Binary files /dev/null and b/Telegram/Resources/icons/emoji_food.png differ diff --git a/Telegram/Resources/icons/emoji_food@2x.png b/Telegram/Resources/icons/emoji_food@2x.png new file mode 100644 index 000000000..4f3d8cf78 Binary files /dev/null and b/Telegram/Resources/icons/emoji_food@2x.png differ diff --git a/Telegram/Resources/icons/emoji_gif.png b/Telegram/Resources/icons/emoji_gif.png new file mode 100644 index 000000000..c8fa752c1 Binary files /dev/null and b/Telegram/Resources/icons/emoji_gif.png differ diff --git a/Telegram/Resources/icons/emoji_gif@2x.png b/Telegram/Resources/icons/emoji_gif@2x.png new file mode 100644 index 000000000..bed606f33 Binary files /dev/null and b/Telegram/Resources/icons/emoji_gif@2x.png differ diff --git a/Telegram/Resources/icons/emoji_nature.png b/Telegram/Resources/icons/emoji_nature.png new file mode 100644 index 000000000..810cdf233 Binary files /dev/null and b/Telegram/Resources/icons/emoji_nature.png differ diff --git a/Telegram/Resources/icons/emoji_nature@2x.png b/Telegram/Resources/icons/emoji_nature@2x.png new file mode 100644 index 000000000..308965ef3 Binary files /dev/null and b/Telegram/Resources/icons/emoji_nature@2x.png differ diff --git a/Telegram/Resources/icons/emoji_objects.png b/Telegram/Resources/icons/emoji_objects.png new file mode 100644 index 000000000..29532ed51 Binary files /dev/null and b/Telegram/Resources/icons/emoji_objects.png differ diff --git a/Telegram/Resources/icons/emoji_objects@2x.png b/Telegram/Resources/icons/emoji_objects@2x.png new file mode 100644 index 000000000..684c1cabc Binary files /dev/null and b/Telegram/Resources/icons/emoji_objects@2x.png differ diff --git a/Telegram/Resources/icons/emoji_people.png b/Telegram/Resources/icons/emoji_people.png new file mode 100644 index 000000000..370c1dcee Binary files /dev/null and b/Telegram/Resources/icons/emoji_people.png differ diff --git a/Telegram/Resources/icons/emoji_people@2x.png b/Telegram/Resources/icons/emoji_people@2x.png new file mode 100644 index 000000000..bd1cf36cb Binary files /dev/null and b/Telegram/Resources/icons/emoji_people@2x.png differ diff --git a/Telegram/Resources/icons/emoji_recent.png b/Telegram/Resources/icons/emoji_recent.png new file mode 100644 index 000000000..3a4e1854e Binary files /dev/null and b/Telegram/Resources/icons/emoji_recent.png differ diff --git a/Telegram/Resources/icons/emoji_recent@2x.png b/Telegram/Resources/icons/emoji_recent@2x.png new file mode 100644 index 000000000..4be3211d5 Binary files /dev/null and b/Telegram/Resources/icons/emoji_recent@2x.png differ diff --git a/Telegram/Resources/icons/emoji_settings.png b/Telegram/Resources/icons/emoji_settings.png new file mode 100644 index 000000000..b454439fb Binary files /dev/null and b/Telegram/Resources/icons/emoji_settings.png differ diff --git a/Telegram/Resources/icons/emoji_settings@2x.png b/Telegram/Resources/icons/emoji_settings@2x.png new file mode 100644 index 000000000..534b24ac8 Binary files /dev/null and b/Telegram/Resources/icons/emoji_settings@2x.png differ diff --git a/Telegram/Resources/icons/emoji_switch.png b/Telegram/Resources/icons/emoji_switch.png new file mode 100644 index 000000000..702e8ff36 Binary files /dev/null and b/Telegram/Resources/icons/emoji_switch.png differ diff --git a/Telegram/Resources/icons/emoji_switch@2x.png b/Telegram/Resources/icons/emoji_switch@2x.png new file mode 100644 index 000000000..5230585da Binary files /dev/null and b/Telegram/Resources/icons/emoji_switch@2x.png differ diff --git a/Telegram/Resources/icons/emoji_symbols.png b/Telegram/Resources/icons/emoji_symbols.png new file mode 100644 index 000000000..496b76640 Binary files /dev/null and b/Telegram/Resources/icons/emoji_symbols.png differ diff --git a/Telegram/Resources/icons/emoji_symbols@2x.png b/Telegram/Resources/icons/emoji_symbols@2x.png new file mode 100644 index 000000000..d1e8861e3 Binary files /dev/null and b/Telegram/Resources/icons/emoji_symbols@2x.png differ diff --git a/Telegram/Resources/icons/emoji_travel.png b/Telegram/Resources/icons/emoji_travel.png new file mode 100644 index 000000000..27c7674cf Binary files /dev/null and b/Telegram/Resources/icons/emoji_travel.png differ diff --git a/Telegram/Resources/icons/emoji_travel@2x.png b/Telegram/Resources/icons/emoji_travel@2x.png new file mode 100644 index 000000000..8958c7c62 Binary files /dev/null and b/Telegram/Resources/icons/emoji_travel@2x.png differ diff --git a/Telegram/Resources/icons/emoji_trending.png b/Telegram/Resources/icons/emoji_trending.png new file mode 100644 index 000000000..4c76cf74a Binary files /dev/null and b/Telegram/Resources/icons/emoji_trending.png differ diff --git a/Telegram/Resources/icons/emoji_trending@2x.png b/Telegram/Resources/icons/emoji_trending@2x.png new file mode 100644 index 000000000..3a3753703 Binary files /dev/null and b/Telegram/Resources/icons/emoji_trending@2x.png differ diff --git a/Telegram/Resources/icons/fade_horizontal_right.png b/Telegram/Resources/icons/fade_horizontal.png similarity index 100% rename from Telegram/Resources/icons/fade_horizontal_right.png rename to Telegram/Resources/icons/fade_horizontal.png diff --git a/Telegram/Resources/icons/fade_horizontal_right@2x.png b/Telegram/Resources/icons/fade_horizontal@2x.png similarity index 100% rename from Telegram/Resources/icons/fade_horizontal_right@2x.png rename to Telegram/Resources/icons/fade_horizontal@2x.png diff --git a/Telegram/Resources/icons/fade_horizontal_left.png b/Telegram/Resources/icons/fade_horizontal_left.png deleted file mode 100644 index 93db9ad4c..000000000 Binary files a/Telegram/Resources/icons/fade_horizontal_left.png and /dev/null differ diff --git a/Telegram/Resources/icons/fade_horizontal_left@2x.png b/Telegram/Resources/icons/fade_horizontal_left@2x.png deleted file mode 100644 index d4e9f55a1..000000000 Binary files a/Telegram/Resources/icons/fade_horizontal_left@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/history_action_edit.png b/Telegram/Resources/icons/history_action_edit.png new file mode 100644 index 000000000..27b1e77a9 Binary files /dev/null and b/Telegram/Resources/icons/history_action_edit.png differ diff --git a/Telegram/Resources/icons/history_action_edit@2x.png b/Telegram/Resources/icons/history_action_edit@2x.png new file mode 100644 index 000000000..13571d1b2 Binary files /dev/null and b/Telegram/Resources/icons/history_action_edit@2x.png differ diff --git a/Telegram/Resources/icons/history_action_forward.png b/Telegram/Resources/icons/history_action_forward.png new file mode 100644 index 000000000..b48285c29 Binary files /dev/null and b/Telegram/Resources/icons/history_action_forward.png differ diff --git a/Telegram/Resources/icons/history_action_forward@2x.png b/Telegram/Resources/icons/history_action_forward@2x.png new file mode 100644 index 000000000..f3eae397e Binary files /dev/null and b/Telegram/Resources/icons/history_action_forward@2x.png differ diff --git a/Telegram/Resources/icons/history_action_reply.png b/Telegram/Resources/icons/history_action_reply.png new file mode 100644 index 000000000..53f921e0f Binary files /dev/null and b/Telegram/Resources/icons/history_action_reply.png differ diff --git a/Telegram/Resources/icons/history_action_reply@2x.png b/Telegram/Resources/icons/history_action_reply@2x.png new file mode 100644 index 000000000..84fbad647 Binary files /dev/null and b/Telegram/Resources/icons/history_action_reply@2x.png differ diff --git a/Telegram/Resources/icons/history_down_circle.png b/Telegram/Resources/icons/history_down_circle.png index f94e8dd7b..001abe2dd 100644 Binary files a/Telegram/Resources/icons/history_down_circle.png and b/Telegram/Resources/icons/history_down_circle.png differ diff --git a/Telegram/Resources/icons/history_down_circle@2x.png b/Telegram/Resources/icons/history_down_circle@2x.png index 94492dada..d5d9bcda7 100644 Binary files a/Telegram/Resources/icons/history_down_circle@2x.png and b/Telegram/Resources/icons/history_down_circle@2x.png differ diff --git a/Telegram/Resources/icons/history_down_shadow.png b/Telegram/Resources/icons/history_down_shadow.png index 8317b07d1..17e6289c2 100644 Binary files a/Telegram/Resources/icons/history_down_shadow.png and b/Telegram/Resources/icons/history_down_shadow.png differ diff --git a/Telegram/Resources/icons/history_down_shadow@2x.png b/Telegram/Resources/icons/history_down_shadow@2x.png index 8127513da..c7a2d5f2d 100644 Binary files a/Telegram/Resources/icons/history_down_shadow@2x.png and b/Telegram/Resources/icons/history_down_shadow@2x.png differ diff --git a/Telegram/Resources/icons/history_empty_dog.png b/Telegram/Resources/icons/history_empty_dog.png deleted file mode 100644 index 3e6fef342..000000000 Binary files a/Telegram/Resources/icons/history_empty_dog.png and /dev/null differ diff --git a/Telegram/Resources/icons/history_empty_dog@2x.png b/Telegram/Resources/icons/history_empty_dog@2x.png deleted file mode 100644 index 248860d6b..000000000 Binary files a/Telegram/Resources/icons/history_empty_dog@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/intro_country_dropdown.png b/Telegram/Resources/icons/intro_country_dropdown.png new file mode 100644 index 000000000..e65ed5a5a Binary files /dev/null and b/Telegram/Resources/icons/intro_country_dropdown.png differ diff --git a/Telegram/Resources/icons/intro_country_dropdown@2x.png b/Telegram/Resources/icons/intro_country_dropdown@2x.png new file mode 100644 index 000000000..712294a4b Binary files /dev/null and b/Telegram/Resources/icons/intro_country_dropdown@2x.png differ diff --git a/Telegram/Resources/icons/intro_left.png b/Telegram/Resources/icons/intro_left.png new file mode 100644 index 000000000..4a8dcf3c0 Binary files /dev/null and b/Telegram/Resources/icons/intro_left.png differ diff --git a/Telegram/Resources/icons/intro_left@2x.png b/Telegram/Resources/icons/intro_left@2x.png new file mode 100644 index 000000000..6e7ffbcdf Binary files /dev/null and b/Telegram/Resources/icons/intro_left@2x.png differ diff --git a/Telegram/Resources/icons/intro_logo.png b/Telegram/Resources/icons/intro_logo.png deleted file mode 100644 index 4953b87d5..000000000 Binary files a/Telegram/Resources/icons/intro_logo.png and /dev/null differ diff --git a/Telegram/Resources/icons/intro_logo@2x.png b/Telegram/Resources/icons/intro_logo@2x.png deleted file mode 100644 index c43416ba2..000000000 Binary files a/Telegram/Resources/icons/intro_logo@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/intro_plane_inner.png b/Telegram/Resources/icons/intro_plane_inner.png new file mode 100644 index 000000000..cbebb1110 Binary files /dev/null and b/Telegram/Resources/icons/intro_plane_inner.png differ diff --git a/Telegram/Resources/icons/intro_plane_inner@2x.png b/Telegram/Resources/icons/intro_plane_inner@2x.png new file mode 100644 index 000000000..11300ea1f Binary files /dev/null and b/Telegram/Resources/icons/intro_plane_inner@2x.png differ diff --git a/Telegram/Resources/icons/intro_plane_outer.png b/Telegram/Resources/icons/intro_plane_outer.png new file mode 100644 index 000000000..6cd6308b6 Binary files /dev/null and b/Telegram/Resources/icons/intro_plane_outer.png differ diff --git a/Telegram/Resources/icons/intro_plane_outer@2x.png b/Telegram/Resources/icons/intro_plane_outer@2x.png new file mode 100644 index 000000000..3d7b9f56e Binary files /dev/null and b/Telegram/Resources/icons/intro_plane_outer@2x.png differ diff --git a/Telegram/Resources/icons/intro_plane_top.png b/Telegram/Resources/icons/intro_plane_top.png new file mode 100644 index 000000000..aa2e2bb75 Binary files /dev/null and b/Telegram/Resources/icons/intro_plane_top.png differ diff --git a/Telegram/Resources/icons/intro_plane_top@2x.png b/Telegram/Resources/icons/intro_plane_top@2x.png new file mode 100644 index 000000000..f2e80eb23 Binary files /dev/null and b/Telegram/Resources/icons/intro_plane_top@2x.png differ diff --git a/Telegram/Resources/icons/intro_plane_trace.png b/Telegram/Resources/icons/intro_plane_trace.png new file mode 100644 index 000000000..172485c2f Binary files /dev/null and b/Telegram/Resources/icons/intro_plane_trace.png differ diff --git a/Telegram/Resources/icons/intro_plane_trace@2x.png b/Telegram/Resources/icons/intro_plane_trace@2x.png new file mode 100644 index 000000000..9d16a3197 Binary files /dev/null and b/Telegram/Resources/icons/intro_plane_trace@2x.png differ diff --git a/Telegram/Resources/icons/intro_right.png b/Telegram/Resources/icons/intro_right.png new file mode 100644 index 000000000..511a3989f Binary files /dev/null and b/Telegram/Resources/icons/intro_right.png differ diff --git a/Telegram/Resources/icons/intro_right@2x.png b/Telegram/Resources/icons/intro_right@2x.png new file mode 100644 index 000000000..3a40127ae Binary files /dev/null and b/Telegram/Resources/icons/intro_right@2x.png differ diff --git a/Telegram/Resources/icons/mac_tray_icon.png b/Telegram/Resources/icons/mac_tray_icon.png new file mode 100644 index 000000000..a57fb5ad7 Binary files /dev/null and b/Telegram/Resources/icons/mac_tray_icon.png differ diff --git a/Telegram/Resources/icons/mac_tray_icon@2x.png b/Telegram/Resources/icons/mac_tray_icon@2x.png new file mode 100644 index 000000000..8a954a383 Binary files /dev/null and b/Telegram/Resources/icons/mac_tray_icon@2x.png differ diff --git a/Telegram/Resources/icons/mac_window_shadow_top_left.png b/Telegram/Resources/icons/mac_window_shadow_top_left.png new file mode 100644 index 000000000..bcf9c0451 Binary files /dev/null and b/Telegram/Resources/icons/mac_window_shadow_top_left.png differ diff --git a/Telegram/Resources/icons/mac_window_shadow_top_left@2x.png b/Telegram/Resources/icons/mac_window_shadow_top_left@2x.png new file mode 100644 index 000000000..71bb8ddfa Binary files /dev/null and b/Telegram/Resources/icons/mac_window_shadow_top_left@2x.png differ diff --git a/Telegram/Resources/icons/media_video_play.png b/Telegram/Resources/icons/media_video_play.png new file mode 100644 index 000000000..1d85bb194 Binary files /dev/null and b/Telegram/Resources/icons/media_video_play.png differ diff --git a/Telegram/Resources/icons/media_video_play@2x.png b/Telegram/Resources/icons/media_video_play@2x.png new file mode 100644 index 000000000..2fffaea0a Binary files /dev/null and b/Telegram/Resources/icons/media_video_play@2x.png differ diff --git a/Telegram/Resources/icons/media_video_play_bg.png b/Telegram/Resources/icons/media_video_play_bg.png new file mode 100644 index 000000000..151473b9b Binary files /dev/null and b/Telegram/Resources/icons/media_video_play_bg.png differ diff --git a/Telegram/Resources/icons/media_video_play_bg@2x.png b/Telegram/Resources/icons/media_video_play_bg@2x.png new file mode 100644 index 000000000..14d50e738 Binary files /dev/null and b/Telegram/Resources/icons/media_video_play_bg@2x.png differ diff --git a/Telegram/Resources/icons/media_youtube_play.png b/Telegram/Resources/icons/media_youtube_play.png new file mode 100644 index 000000000..9cf8ff40e Binary files /dev/null and b/Telegram/Resources/icons/media_youtube_play.png differ diff --git a/Telegram/Resources/icons/media_youtube_play@2x.png b/Telegram/Resources/icons/media_youtube_play@2x.png new file mode 100644 index 000000000..9aa7f5117 Binary files /dev/null and b/Telegram/Resources/icons/media_youtube_play@2x.png differ diff --git a/Telegram/Resources/icons/media_youtube_play_bg.png b/Telegram/Resources/icons/media_youtube_play_bg.png new file mode 100644 index 000000000..76e71cfe0 Binary files /dev/null and b/Telegram/Resources/icons/media_youtube_play_bg.png differ diff --git a/Telegram/Resources/icons/media_youtube_play_bg@2x.png b/Telegram/Resources/icons/media_youtube_play_bg@2x.png new file mode 100644 index 000000000..5a5698c6e Binary files /dev/null and b/Telegram/Resources/icons/media_youtube_play_bg@2x.png differ diff --git a/Telegram/Resources/icons/mediaview_previous.png b/Telegram/Resources/icons/mediaview_previous.png deleted file mode 100644 index 8fb04e034..000000000 Binary files a/Telegram/Resources/icons/mediaview_previous.png and /dev/null differ diff --git a/Telegram/Resources/icons/mediaview_previous@2x.png b/Telegram/Resources/icons/mediaview_previous@2x.png deleted file mode 100644 index d746a1056..000000000 Binary files a/Telegram/Resources/icons/mediaview_previous@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/menu_contacts.png b/Telegram/Resources/icons/menu_contacts.png new file mode 100644 index 000000000..5df86715b Binary files /dev/null and b/Telegram/Resources/icons/menu_contacts.png differ diff --git a/Telegram/Resources/icons/menu_contacts@2x.png b/Telegram/Resources/icons/menu_contacts@2x.png new file mode 100644 index 000000000..5b879588a Binary files /dev/null and b/Telegram/Resources/icons/menu_contacts@2x.png differ diff --git a/Telegram/Resources/icons/menu_help.png b/Telegram/Resources/icons/menu_help.png new file mode 100644 index 000000000..80de9d1ea Binary files /dev/null and b/Telegram/Resources/icons/menu_help.png differ diff --git a/Telegram/Resources/icons/menu_help@2x.png b/Telegram/Resources/icons/menu_help@2x.png new file mode 100644 index 000000000..1444e19c9 Binary files /dev/null and b/Telegram/Resources/icons/menu_help@2x.png differ diff --git a/Telegram/Resources/icons/menu_new_channel.png b/Telegram/Resources/icons/menu_new_channel.png new file mode 100644 index 000000000..03a1b1f40 Binary files /dev/null and b/Telegram/Resources/icons/menu_new_channel.png differ diff --git a/Telegram/Resources/icons/menu_new_channel@2x.png b/Telegram/Resources/icons/menu_new_channel@2x.png new file mode 100644 index 000000000..f84f7b6ac Binary files /dev/null and b/Telegram/Resources/icons/menu_new_channel@2x.png differ diff --git a/Telegram/Resources/icons/menu_new_group.png b/Telegram/Resources/icons/menu_new_group.png new file mode 100644 index 000000000..4d9b1380f Binary files /dev/null and b/Telegram/Resources/icons/menu_new_group.png differ diff --git a/Telegram/Resources/icons/menu_new_group@2x.png b/Telegram/Resources/icons/menu_new_group@2x.png new file mode 100644 index 000000000..3512799e6 Binary files /dev/null and b/Telegram/Resources/icons/menu_new_group@2x.png differ diff --git a/Telegram/Resources/icons/menu_settings.png b/Telegram/Resources/icons/menu_settings.png new file mode 100644 index 000000000..355f00e2e Binary files /dev/null and b/Telegram/Resources/icons/menu_settings.png differ diff --git a/Telegram/Resources/icons/menu_settings@2x.png b/Telegram/Resources/icons/menu_settings@2x.png new file mode 100644 index 000000000..17d626cb7 Binary files /dev/null and b/Telegram/Resources/icons/menu_settings@2x.png differ diff --git a/Telegram/Resources/icons/new_chat_photo.png b/Telegram/Resources/icons/new_chat_photo.png new file mode 100644 index 000000000..348785247 Binary files /dev/null and b/Telegram/Resources/icons/new_chat_photo.png differ diff --git a/Telegram/Resources/icons/new_chat_photo@2x.png b/Telegram/Resources/icons/new_chat_photo@2x.png new file mode 100644 index 000000000..ae3410ec2 Binary files /dev/null and b/Telegram/Resources/icons/new_chat_photo@2x.png differ diff --git a/Telegram/Resources/icons/overview_photo_check.png b/Telegram/Resources/icons/overview_photo_check.png index f9a406ce2..44347c6f7 100644 Binary files a/Telegram/Resources/icons/overview_photo_check.png and b/Telegram/Resources/icons/overview_photo_check.png differ diff --git a/Telegram/Resources/icons/overview_photo_check@2x.png b/Telegram/Resources/icons/overview_photo_check@2x.png index 6ab0cbd9c..d5ad0a834 100644 Binary files a/Telegram/Resources/icons/overview_photo_check@2x.png and b/Telegram/Resources/icons/overview_photo_check@2x.png differ diff --git a/Telegram/Resources/icons/player_panel_previous.png b/Telegram/Resources/icons/player_panel_previous.png deleted file mode 100644 index 4460c3daf..000000000 Binary files a/Telegram/Resources/icons/player_panel_previous.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_panel_previous@2x.png b/Telegram/Resources/icons/player_panel_previous@2x.png deleted file mode 100644 index bb31bf531..000000000 Binary files a/Telegram/Resources/icons/player_panel_previous@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_previous.png b/Telegram/Resources/icons/player_previous.png deleted file mode 100644 index cd71dc0ed..000000000 Binary files a/Telegram/Resources/icons/player_previous.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_previous@2x.png b/Telegram/Resources/icons/player_previous@2x.png deleted file mode 100644 index 3ddcc5ab5..000000000 Binary files a/Telegram/Resources/icons/player_previous@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/playlist_download.png b/Telegram/Resources/icons/playlist_download.png index 2e645d911..f02cb662a 100644 Binary files a/Telegram/Resources/icons/playlist_download.png and b/Telegram/Resources/icons/playlist_download.png differ diff --git a/Telegram/Resources/icons/playlist_download@2x.png b/Telegram/Resources/icons/playlist_download@2x.png index 6dad43718..b340ffe3a 100644 Binary files a/Telegram/Resources/icons/playlist_download@2x.png and b/Telegram/Resources/icons/playlist_download@2x.png differ diff --git a/Telegram/Resources/icons/playlist_play.png b/Telegram/Resources/icons/playlist_play.png index 780df063d..9e314e48d 100644 Binary files a/Telegram/Resources/icons/playlist_play.png and b/Telegram/Resources/icons/playlist_play.png differ diff --git a/Telegram/Resources/icons/playlist_play@2x.png b/Telegram/Resources/icons/playlist_play@2x.png index 2adbd4c11..6d5cf39bd 100644 Binary files a/Telegram/Resources/icons/playlist_play@2x.png and b/Telegram/Resources/icons/playlist_play@2x.png differ diff --git a/Telegram/Resources/icons/profile_divider_bottom.png b/Telegram/Resources/icons/profile_divider_bottom.png new file mode 100644 index 000000000..31fee84f9 Binary files /dev/null and b/Telegram/Resources/icons/profile_divider_bottom.png differ diff --git a/Telegram/Resources/icons/profile_divider_bottom@2x.png b/Telegram/Resources/icons/profile_divider_bottom@2x.png new file mode 100644 index 000000000..87fb65621 Binary files /dev/null and b/Telegram/Resources/icons/profile_divider_bottom@2x.png differ diff --git a/Telegram/Resources/icons/profile_divider_fill.png b/Telegram/Resources/icons/profile_divider_fill.png deleted file mode 100644 index f212416e7..000000000 Binary files a/Telegram/Resources/icons/profile_divider_fill.png and /dev/null differ diff --git a/Telegram/Resources/icons/profile_divider_fill@2x.png b/Telegram/Resources/icons/profile_divider_fill@2x.png deleted file mode 100644 index 9d4eaddc3..000000000 Binary files a/Telegram/Resources/icons/profile_divider_fill@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/profile_divider_left.png b/Telegram/Resources/icons/profile_divider_left.png index c4d4c3aa1..9bcfd455e 100644 Binary files a/Telegram/Resources/icons/profile_divider_left.png and b/Telegram/Resources/icons/profile_divider_left.png differ diff --git a/Telegram/Resources/icons/profile_divider_left@2x.png b/Telegram/Resources/icons/profile_divider_left@2x.png index 92b5fdae2..b4488556c 100644 Binary files a/Telegram/Resources/icons/profile_divider_left@2x.png and b/Telegram/Resources/icons/profile_divider_left@2x.png differ diff --git a/Telegram/Resources/icons/profile_divider_top.png b/Telegram/Resources/icons/profile_divider_top.png new file mode 100644 index 000000000..6dcdc143c Binary files /dev/null and b/Telegram/Resources/icons/profile_divider_top.png differ diff --git a/Telegram/Resources/icons/profile_divider_top@2x.png b/Telegram/Resources/icons/profile_divider_top@2x.png new file mode 100644 index 000000000..5bc32a739 Binary files /dev/null and b/Telegram/Resources/icons/profile_divider_top@2x.png differ diff --git a/Telegram/Resources/icons/round_shadow_bottom.png b/Telegram/Resources/icons/round_shadow_bottom.png new file mode 100644 index 000000000..28ff21173 Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_bottom.png differ diff --git a/Telegram/Resources/icons/round_shadow_bottom@2x.png b/Telegram/Resources/icons/round_shadow_bottom@2x.png new file mode 100644 index 000000000..a64ca5c8e Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_bottom@2x.png differ diff --git a/Telegram/Resources/icons/round_shadow_bottom_left.png b/Telegram/Resources/icons/round_shadow_bottom_left.png new file mode 100644 index 000000000..8beb9a7e1 Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_bottom_left.png differ diff --git a/Telegram/Resources/icons/round_shadow_bottom_left@2x.png b/Telegram/Resources/icons/round_shadow_bottom_left@2x.png new file mode 100644 index 000000000..c1704521b Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_bottom_left@2x.png differ diff --git a/Telegram/Resources/icons/round_shadow_box_bottom.png b/Telegram/Resources/icons/round_shadow_box_bottom.png new file mode 100644 index 000000000..379c612d9 Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_box_bottom.png differ diff --git a/Telegram/Resources/icons/round_shadow_box_bottom@2x.png b/Telegram/Resources/icons/round_shadow_box_bottom@2x.png new file mode 100644 index 000000000..27561668a Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_box_bottom@2x.png differ diff --git a/Telegram/Resources/icons/round_shadow_box_bottom_left.png b/Telegram/Resources/icons/round_shadow_box_bottom_left.png new file mode 100644 index 000000000..3bb8de356 Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_box_bottom_left.png differ diff --git a/Telegram/Resources/icons/round_shadow_box_bottom_left@2x.png b/Telegram/Resources/icons/round_shadow_box_bottom_left@2x.png new file mode 100644 index 000000000..f31b3eb92 Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_box_bottom_left@2x.png differ diff --git a/Telegram/Resources/icons/round_shadow_box_left.png b/Telegram/Resources/icons/round_shadow_box_left.png new file mode 100644 index 000000000..7f5e183c9 Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_box_left.png differ diff --git a/Telegram/Resources/icons/round_shadow_box_left@2x.png b/Telegram/Resources/icons/round_shadow_box_left@2x.png new file mode 100644 index 000000000..1e2807341 Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_box_left@2x.png differ diff --git a/Telegram/Resources/icons/round_shadow_box_top.png b/Telegram/Resources/icons/round_shadow_box_top.png new file mode 100644 index 000000000..f2ff11208 Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_box_top.png differ diff --git a/Telegram/Resources/icons/round_shadow_box_top@2x.png b/Telegram/Resources/icons/round_shadow_box_top@2x.png new file mode 100644 index 000000000..37eb98892 Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_box_top@2x.png differ diff --git a/Telegram/Resources/icons/round_shadow_box_top_left.png b/Telegram/Resources/icons/round_shadow_box_top_left.png new file mode 100644 index 000000000..a5736889a Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_box_top_left.png differ diff --git a/Telegram/Resources/icons/round_shadow_box_top_left@2x.png b/Telegram/Resources/icons/round_shadow_box_top_left@2x.png new file mode 100644 index 000000000..b67f40c1b Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_box_top_left@2x.png differ diff --git a/Telegram/Resources/icons/round_shadow_left.png b/Telegram/Resources/icons/round_shadow_left.png new file mode 100644 index 000000000..e7df24dc1 Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_left.png differ diff --git a/Telegram/Resources/icons/round_shadow_left@2x.png b/Telegram/Resources/icons/round_shadow_left@2x.png new file mode 100644 index 000000000..2f78de2ab Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_left@2x.png differ diff --git a/Telegram/Resources/icons/round_shadow_top.png b/Telegram/Resources/icons/round_shadow_top.png new file mode 100644 index 000000000..39edc4a4e Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_top.png differ diff --git a/Telegram/Resources/icons/round_shadow_top@2x.png b/Telegram/Resources/icons/round_shadow_top@2x.png new file mode 100644 index 000000000..af421f2a0 Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_top@2x.png differ diff --git a/Telegram/Resources/icons/round_shadow_top_left.png b/Telegram/Resources/icons/round_shadow_top_left.png new file mode 100644 index 000000000..555aeb74d Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_top_left.png differ diff --git a/Telegram/Resources/icons/round_shadow_top_left@2x.png b/Telegram/Resources/icons/round_shadow_top_left@2x.png new file mode 100644 index 000000000..f8f634c11 Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_top_left@2x.png differ diff --git a/Telegram/Resources/icons/send_control_attach.png b/Telegram/Resources/icons/send_control_attach.png new file mode 100644 index 000000000..a16628b77 Binary files /dev/null and b/Telegram/Resources/icons/send_control_attach.png differ diff --git a/Telegram/Resources/icons/send_control_attach@2x.png b/Telegram/Resources/icons/send_control_attach@2x.png new file mode 100644 index 000000000..8b0c4d3e1 Binary files /dev/null and b/Telegram/Resources/icons/send_control_attach@2x.png differ diff --git a/Telegram/Resources/icons/send_control_bot_command.png b/Telegram/Resources/icons/send_control_bot_command.png new file mode 100644 index 000000000..34cc9d6eb Binary files /dev/null and b/Telegram/Resources/icons/send_control_bot_command.png differ diff --git a/Telegram/Resources/icons/send_control_bot_command@2x.png b/Telegram/Resources/icons/send_control_bot_command@2x.png new file mode 100644 index 000000000..faaf97c16 Binary files /dev/null and b/Telegram/Resources/icons/send_control_bot_command@2x.png differ diff --git a/Telegram/Resources/icons/send_control_bot_keyboard.png b/Telegram/Resources/icons/send_control_bot_keyboard.png new file mode 100644 index 000000000..b9423a716 Binary files /dev/null and b/Telegram/Resources/icons/send_control_bot_keyboard.png differ diff --git a/Telegram/Resources/icons/send_control_bot_keyboard@2x.png b/Telegram/Resources/icons/send_control_bot_keyboard@2x.png new file mode 100644 index 000000000..482e40831 Binary files /dev/null and b/Telegram/Resources/icons/send_control_bot_keyboard@2x.png differ diff --git a/Telegram/Resources/icons/send_control_bot_keyboard_hide.png b/Telegram/Resources/icons/send_control_bot_keyboard_hide.png new file mode 100644 index 000000000..ceaaccf73 Binary files /dev/null and b/Telegram/Resources/icons/send_control_bot_keyboard_hide.png differ diff --git a/Telegram/Resources/icons/send_control_bot_keyboard_hide@2x.png b/Telegram/Resources/icons/send_control_bot_keyboard_hide@2x.png new file mode 100644 index 000000000..89cb528df Binary files /dev/null and b/Telegram/Resources/icons/send_control_bot_keyboard_hide@2x.png differ diff --git a/Telegram/Resources/icons/send_control_emoji.png b/Telegram/Resources/icons/send_control_emoji.png new file mode 100644 index 000000000..5698c8421 Binary files /dev/null and b/Telegram/Resources/icons/send_control_emoji.png differ diff --git a/Telegram/Resources/icons/send_control_emoji@2x.png b/Telegram/Resources/icons/send_control_emoji@2x.png new file mode 100644 index 000000000..8a1a772b5 Binary files /dev/null and b/Telegram/Resources/icons/send_control_emoji@2x.png differ diff --git a/Telegram/Resources/icons/send_control_record.png b/Telegram/Resources/icons/send_control_record.png new file mode 100644 index 000000000..36882d408 Binary files /dev/null and b/Telegram/Resources/icons/send_control_record.png differ diff --git a/Telegram/Resources/icons/send_control_record@2x.png b/Telegram/Resources/icons/send_control_record@2x.png new file mode 100644 index 000000000..3eba00f2e Binary files /dev/null and b/Telegram/Resources/icons/send_control_record@2x.png differ diff --git a/Telegram/Resources/icons/send_control_save.png b/Telegram/Resources/icons/send_control_save.png new file mode 100644 index 000000000..199ab2b0c Binary files /dev/null and b/Telegram/Resources/icons/send_control_save.png differ diff --git a/Telegram/Resources/icons/send_control_save@2x.png b/Telegram/Resources/icons/send_control_save@2x.png new file mode 100644 index 000000000..5412d1296 Binary files /dev/null and b/Telegram/Resources/icons/send_control_save@2x.png differ diff --git a/Telegram/Resources/icons/send_control_send.png b/Telegram/Resources/icons/send_control_send.png new file mode 100644 index 000000000..6414c8bfa Binary files /dev/null and b/Telegram/Resources/icons/send_control_send.png differ diff --git a/Telegram/Resources/icons/send_control_send@2x.png b/Telegram/Resources/icons/send_control_send@2x.png new file mode 100644 index 000000000..559248cea Binary files /dev/null and b/Telegram/Resources/icons/send_control_send@2x.png differ diff --git a/Telegram/Resources/icons/send_control_silent_off.png b/Telegram/Resources/icons/send_control_silent_off.png new file mode 100644 index 000000000..8e25352d7 Binary files /dev/null and b/Telegram/Resources/icons/send_control_silent_off.png differ diff --git a/Telegram/Resources/icons/send_control_silent_off@2x.png b/Telegram/Resources/icons/send_control_silent_off@2x.png new file mode 100644 index 000000000..4315d367d Binary files /dev/null and b/Telegram/Resources/icons/send_control_silent_off@2x.png differ diff --git a/Telegram/Resources/icons/send_control_silent_on.png b/Telegram/Resources/icons/send_control_silent_on.png new file mode 100644 index 000000000..a22728951 Binary files /dev/null and b/Telegram/Resources/icons/send_control_silent_on.png differ diff --git a/Telegram/Resources/icons/send_control_silent_on@2x.png b/Telegram/Resources/icons/send_control_silent_on@2x.png new file mode 100644 index 000000000..810b09470 Binary files /dev/null and b/Telegram/Resources/icons/send_control_silent_on@2x.png differ diff --git a/Telegram/Resources/icons/settings_close.png b/Telegram/Resources/icons/settings_close.png deleted file mode 100644 index 7fa112a33..000000000 Binary files a/Telegram/Resources/icons/settings_close.png and /dev/null differ diff --git a/Telegram/Resources/icons/settings_close@2x.png b/Telegram/Resources/icons/settings_close@2x.png deleted file mode 100644 index 40d3def3d..000000000 Binary files a/Telegram/Resources/icons/settings_close@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/dialogs_new_chat.png b/Telegram/Resources/icons/settings_edit_name.png similarity index 100% rename from Telegram/Resources/icons/dialogs_new_chat.png rename to Telegram/Resources/icons/settings_edit_name.png diff --git a/Telegram/Resources/icons/dialogs_new_chat@2x.png b/Telegram/Resources/icons/settings_edit_name@2x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_new_chat@2x.png rename to Telegram/Resources/icons/settings_edit_name@2x.png diff --git a/Telegram/Resources/icons/simple_close.png b/Telegram/Resources/icons/simple_close.png new file mode 100644 index 000000000..10f8b030e Binary files /dev/null and b/Telegram/Resources/icons/simple_close.png differ diff --git a/Telegram/Resources/icons/simple_close@2x.png b/Telegram/Resources/icons/simple_close@2x.png new file mode 100644 index 000000000..a53c4af64 Binary files /dev/null and b/Telegram/Resources/icons/simple_close@2x.png differ diff --git a/Telegram/Resources/icons/stickers_remove.png b/Telegram/Resources/icons/stickers_remove.png new file mode 100644 index 000000000..8a98e3293 Binary files /dev/null and b/Telegram/Resources/icons/stickers_remove.png differ diff --git a/Telegram/Resources/icons/stickers_remove@2x.png b/Telegram/Resources/icons/stickers_remove@2x.png new file mode 100644 index 000000000..d9f22acd2 Binary files /dev/null and b/Telegram/Resources/icons/stickers_remove@2x.png differ diff --git a/Telegram/Resources/icons/stickers_reorder.png b/Telegram/Resources/icons/stickers_reorder.png new file mode 100644 index 000000000..d49b58f44 Binary files /dev/null and b/Telegram/Resources/icons/stickers_reorder.png differ diff --git a/Telegram/Resources/icons/stickers_reorder@2x.png b/Telegram/Resources/icons/stickers_reorder@2x.png new file mode 100644 index 000000000..d0d660eda Binary files /dev/null and b/Telegram/Resources/icons/stickers_reorder@2x.png differ diff --git a/Telegram/Resources/icons/title_previous.png b/Telegram/Resources/icons/title_back.png similarity index 100% rename from Telegram/Resources/icons/title_previous.png rename to Telegram/Resources/icons/title_back.png diff --git a/Telegram/Resources/icons/title_previous@2x.png b/Telegram/Resources/icons/title_back@2x.png similarity index 100% rename from Telegram/Resources/icons/title_previous@2x.png rename to Telegram/Resources/icons/title_back@2x.png diff --git a/Telegram/Resources/icons/title_button_close.png b/Telegram/Resources/icons/title_button_close.png index 0686fc88a..22cfe520c 100644 Binary files a/Telegram/Resources/icons/title_button_close.png and b/Telegram/Resources/icons/title_button_close.png differ diff --git a/Telegram/Resources/icons/title_button_close@2x.png b/Telegram/Resources/icons/title_button_close@2x.png index f598165f0..e43b4d8a7 100644 Binary files a/Telegram/Resources/icons/title_button_close@2x.png and b/Telegram/Resources/icons/title_button_close@2x.png differ diff --git a/Telegram/Resources/icons/title_button_lock.png b/Telegram/Resources/icons/title_button_lock.png deleted file mode 100644 index 61984e5df..000000000 Binary files a/Telegram/Resources/icons/title_button_lock.png and /dev/null differ diff --git a/Telegram/Resources/icons/title_button_lock@2x.png b/Telegram/Resources/icons/title_button_lock@2x.png deleted file mode 100644 index 578eec430..000000000 Binary files a/Telegram/Resources/icons/title_button_lock@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/title_button_maximize.png b/Telegram/Resources/icons/title_button_maximize.png index a3522751f..2759b3f41 100644 Binary files a/Telegram/Resources/icons/title_button_maximize.png and b/Telegram/Resources/icons/title_button_maximize.png differ diff --git a/Telegram/Resources/icons/title_button_maximize@2x.png b/Telegram/Resources/icons/title_button_maximize@2x.png index 037bb98de..00daaae19 100644 Binary files a/Telegram/Resources/icons/title_button_maximize@2x.png and b/Telegram/Resources/icons/title_button_maximize@2x.png differ diff --git a/Telegram/Resources/icons/title_button_minimize.png b/Telegram/Resources/icons/title_button_minimize.png index 229cb7dc5..85934ddde 100644 Binary files a/Telegram/Resources/icons/title_button_minimize.png and b/Telegram/Resources/icons/title_button_minimize.png differ diff --git a/Telegram/Resources/icons/title_button_minimize@2x.png b/Telegram/Resources/icons/title_button_minimize@2x.png index ddfc4713a..d70a49734 100644 Binary files a/Telegram/Resources/icons/title_button_minimize@2x.png and b/Telegram/Resources/icons/title_button_minimize@2x.png differ diff --git a/Telegram/Resources/icons/title_button_restore.png b/Telegram/Resources/icons/title_button_restore.png index 4ad86be0e..c9f276b51 100644 Binary files a/Telegram/Resources/icons/title_button_restore.png and b/Telegram/Resources/icons/title_button_restore.png differ diff --git a/Telegram/Resources/icons/title_button_restore@2x.png b/Telegram/Resources/icons/title_button_restore@2x.png index f498810d7..63c3e1b1e 100644 Binary files a/Telegram/Resources/icons/title_button_restore@2x.png and b/Telegram/Resources/icons/title_button_restore@2x.png differ diff --git a/Telegram/Resources/icons/title_button_unlock.png b/Telegram/Resources/icons/title_button_unlock.png deleted file mode 100644 index 37031e3d7..000000000 Binary files a/Telegram/Resources/icons/title_button_unlock.png and /dev/null differ diff --git a/Telegram/Resources/icons/title_button_unlock@2x.png b/Telegram/Resources/icons/title_button_unlock@2x.png deleted file mode 100644 index 3e99f232b..000000000 Binary files a/Telegram/Resources/icons/title_button_unlock@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/title_button_update.png b/Telegram/Resources/icons/title_button_update.png deleted file mode 100644 index b7dc595fb..000000000 Binary files a/Telegram/Resources/icons/title_button_update.png and /dev/null differ diff --git a/Telegram/Resources/icons/title_button_update@2x.png b/Telegram/Resources/icons/title_button_update@2x.png deleted file mode 100644 index bf26691ea..000000000 Binary files a/Telegram/Resources/icons/title_button_update@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/title_icon.png b/Telegram/Resources/icons/title_icon.png deleted file mode 100644 index 12772e6a3..000000000 Binary files a/Telegram/Resources/icons/title_icon.png and /dev/null differ diff --git a/Telegram/Resources/icons/title_icon@2x.png b/Telegram/Resources/icons/title_icon@2x.png deleted file mode 100644 index ad40f1fea..000000000 Binary files a/Telegram/Resources/icons/title_icon@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/title_icon_bg.png b/Telegram/Resources/icons/title_icon_bg.png deleted file mode 100644 index ebdf86291..000000000 Binary files a/Telegram/Resources/icons/title_icon_bg.png and /dev/null differ diff --git a/Telegram/Resources/icons/title_icon_bg@2x.png b/Telegram/Resources/icons/title_icon_bg@2x.png deleted file mode 100644 index 33924433d..000000000 Binary files a/Telegram/Resources/icons/title_icon_bg@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/title_menu_dots.png b/Telegram/Resources/icons/title_menu_dots.png new file mode 100644 index 000000000..228a2a3bc Binary files /dev/null and b/Telegram/Resources/icons/title_menu_dots.png differ diff --git a/Telegram/Resources/icons/title_menu_dots@2x.png b/Telegram/Resources/icons/title_menu_dots@2x.png new file mode 100644 index 000000000..0020d07f1 Binary files /dev/null and b/Telegram/Resources/icons/title_menu_dots@2x.png differ diff --git a/Telegram/Resources/icons/title_next.png b/Telegram/Resources/icons/title_next.png deleted file mode 100644 index 62fd6f276..000000000 Binary files a/Telegram/Resources/icons/title_next.png and /dev/null differ diff --git a/Telegram/Resources/icons/title_next@2x.png b/Telegram/Resources/icons/title_next@2x.png deleted file mode 100644 index 04dd1b4c6..000000000 Binary files a/Telegram/Resources/icons/title_next@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/userpic_channel.png b/Telegram/Resources/icons/userpic_channel.png deleted file mode 100644 index 1f5fd21f2..000000000 Binary files a/Telegram/Resources/icons/userpic_channel.png and /dev/null differ diff --git a/Telegram/Resources/icons/userpic_channel@2x.png b/Telegram/Resources/icons/userpic_channel@2x.png deleted file mode 100644 index d3301baaf..000000000 Binary files a/Telegram/Resources/icons/userpic_channel@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/userpic_chat.png b/Telegram/Resources/icons/userpic_chat.png deleted file mode 100644 index 778e505c7..000000000 Binary files a/Telegram/Resources/icons/userpic_chat.png and /dev/null differ diff --git a/Telegram/Resources/icons/userpic_chat@2x.png b/Telegram/Resources/icons/userpic_chat@2x.png deleted file mode 100644 index cc2492297..000000000 Binary files a/Telegram/Resources/icons/userpic_chat@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/userpic_person.png b/Telegram/Resources/icons/userpic_person.png deleted file mode 100644 index fb28bf3c5..000000000 Binary files a/Telegram/Resources/icons/userpic_person.png and /dev/null differ diff --git a/Telegram/Resources/icons/userpic_person@2x.png b/Telegram/Resources/icons/userpic_person@2x.png deleted file mode 100644 index 4b63a7101..000000000 Binary files a/Telegram/Resources/icons/userpic_person@2x.png and /dev/null differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index a5407935a..77c9b8a9d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -110,7 +110,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_status_online" = "online"; "lng_status_connecting" = "connecting..."; -"lng_chat_status_unaccessible" = "group is unaccessible"; +"lng_chat_status_unaccessible" = "group is inaccessible"; "lng_chat_status_members" = "{count:no members|# member|# members}"; "lng_chat_status_members_online" = "{count:_not_used_|# member|# members}, {count_online:_not_used_|# online|# online}"; @@ -125,6 +125,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_gif_error" = "An error has occured while reading GIF animation :("; "lng_edit_error" = "You cannot edit this message"; "lng_join_channel_error" = "Sorry, you have joined too many channels and supergroups. Please leave some before joining."; +"lng_error_phone_flood" = "Sorry, you have deleted and re-created your account too many times recently. Please wait for a few days before signing up again."; +"lng_error_start_minimized_passcoded" = "You have set a local passcode, so the app can't be launched minimized. App will ask you to enter the passcode before it can start working."; +"lng_error_pinned_max" = "Sorry, you can pin no more than {count:_not_used_|# chat|# chats} to the top."; + "lng_edit_deleted" = "This message was deleted"; "lng_edit_too_long" = "Your message text is too long"; "lng_edit_message" = "Edit message"; @@ -138,7 +142,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_pinned_unpin" = "Unpin"; "lng_pinned_notify" = "Notify all members"; -"lng_intro" = "Welcome to the official [a href=\"https://telegram.org/\"]Telegram[/a] desktop app.\nIt's [b]fast[/b] and [b]secure[/b]."; +"lng_intro_about" = "Welcome to the official Telegram Desktop app.\nIt's fast and secure."; "lng_start_msgs" = "START MESSAGING"; "lng_intro_next" = "NEXT"; @@ -146,11 +150,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_intro_submit" = "SUBMIT"; "lng_photo_caption" = "Caption"; +"lng_photos_comment" = "Comment"; "lng_phone_ph" = "Your phone number"; "lng_phone_title" = "Your Phone"; "lng_phone_desc" = "Please confirm your country code and\nenter your phone number."; -"lng_phone_notreg" = "Note: if you don't have a Telegram account yet,\nplease [b]sign up[/b] with your [a href=\"https://telegram.org/\"]iOS / Android[/a] or {signup_start}here »{signup_end}"; +"lng_phone_notreg" = "If you don't have a Telegram account yet,\nplease [b]sign up[/b] with {link_start}Android / iPhone{link_end} or {signup_start}here{signup_end}"; "lng_country_code" = "Country Code"; "lng_bad_country_code" = "Invalid Country Code"; "lng_country_ph" = "Search"; @@ -160,7 +165,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_code_ph" = "Your code"; "lng_code_desc" = "We have sent you a message with activation\ncode to your phone. Please enter it below."; -"lng_code_telegram" = "Please enter the code you've just\nreceived in your previous [b]Telegram[/b] app."; +"lng_code_telegram" = "Please enter the code you've just received\nin your previous [b]Telegram[/b] app."; "lng_code_no_telegram" = "Send code via SMS"; "lng_code_call" = "Telegram will dial your number in {minutes}:{seconds}"; "lng_code_calling" = "Requesting a call from Telegram..."; @@ -174,7 +179,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_title" = "Cloud password check"; "lng_signin_desc" = "Please enter your cloud password."; -"lng_signin_recover_desc" = "Please enter the code from the e-mail."; +"lng_signin_recover_desc" = "Please enter the code from the e-mail\n{email}"; "lng_signin_password" = "Your cloud password"; "lng_signin_code" = "Code from e-mail"; "lng_signin_recover" = "Forgot password?"; @@ -196,7 +201,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_reset_in_minutes" = "{count_minutes:0 minutes|# minute|# minutes}"; "lng_signin_reset_cancelled" = "Your recent attempts to reset this account have been cancelled by its active user. Please try again in 7 days."; -"lng_signup_title" = "Information and photo"; +"lng_signup_title" = "Your Info"; "lng_signup_desc" = "Please enter your name and\nupload a photo."; "lng_signup_firstname" = "First Name"; @@ -212,6 +217,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_dlg_search_chat" = "Search in this chat"; "lng_dlg_search_channel" = "Search in this channel"; "lng_dlg_search_for_messages" = "Search for messages"; +"lng_update_telegram" = "Update Telegram"; "lng_settings_save" = "Save"; "lng_settings_upload" = "Set Profile Photo"; @@ -260,7 +266,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_settings_change_lang" = "Change language"; "lng_languages" = "Languages"; "lng_sure_save_language" = "Telegram will restart in order to change language"; -"lng_settings_update_automatically" = "Update automatically (ver. {version})"; +"lng_settings_update_automatically" = "Update automatically"; +"lng_settings_current_version_label" = "Version {version}:"; "lng_settings_current_version" = "Version {version}"; "lng_settings_check_now" = "Check for updates"; "lng_settings_update_checking" = "Checking for updates..."; @@ -286,12 +293,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_settings_send_cmdenter" = "Send by Cmd+Enter"; "lng_settings_section_background" = "Chat background"; +"lng_settings_bg_use_default" = "Use default color theme"; "lng_settings_bg_from_gallery" = "Choose from gallery"; "lng_settings_bg_from_file" = "Choose from file"; "lng_settings_bg_tile" = "Tile background"; "lng_settings_adaptive_wide" = "Adaptive layout for wide screens"; "lng_backgrounds_header" = "Choose your new chat background"; +"lng_theme_sure_keep" = "Keep this color theme?"; +"lng_theme_reverting" = "Reverting to the old color theme in {count:_not_used_|# second|# seconds}."; +"lng_theme_keep_changes" = "Keep changes"; +"lng_theme_revert" = "Revert"; "lng_download_path_dont_ask" = "Don't ask download path for each file"; "lng_download_path_label" = "Download path:"; @@ -340,11 +352,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_passcode_enter_first" = "Enter a passcode"; "lng_passcode_enter_new" = "Enter new passcode"; "lng_passcode_confirm_new" = "Re-enter new passcode"; -"lng_passcode_about" = "When a local passcode is set, a lock icon appears in the top right corner of the window. Click it to lock the app.\n\nNote: if you forget your local passcode, you'll need to relogin in Telegram Desktop."; +"lng_passcode_about" = "When a local passcode is set, a lock icon appears at the top of your chats list. Click it to lock the app.\n\nNote: if you forget your local passcode, you'll need to relogin in Telegram Desktop."; "lng_passcode_differ" = "Passcodes are different"; "lng_passcode_wrong" = "Wrong passcode"; "lng_passcode_is_same" = "Passcode was not changed"; "lng_passcode_enter" = "Enter your local passcode"; +"lng_passcode_ph" = "Your passcode"; "lng_passcode_submit" = "Submit"; "lng_passcode_logout" = "Log out"; "lng_passcode_need_unblock" = "You need to unlock me first."; @@ -417,7 +430,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_preview_loading" = "Getting Link Info..."; -"lng_profile_chat_unaccessible" = "Group is unaccessible"; +"lng_profile_chat_unaccessible" = "Group is inaccessible"; "lng_profile_about_section" = "About"; "lng_profile_description_section" = "Description"; "lng_profile_settings_section" = "Settings"; @@ -428,6 +441,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_create_public_link" = "Create public link"; "lng_profile_edit_public_link" = "Edit public link"; "lng_profile_manage_admins" = "Manage administrators"; +"lng_profile_common_groups" = "{count:_not_used_|# group|# groups} in common"; +"lng_profile_common_groups_section" = "Groups in common"; "lng_profile_participants_section" = "Members"; "lng_profile_info_section" = "Info"; "lng_profile_mobile_number" = "Mobile:"; @@ -467,17 +482,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_shared_media" = "Shared media"; "lng_profile_no_media" = "No media in this conversation."; "lng_profile_photos" = "{count:_not_used_|# photo|# photos}"; -"lng_profile_photos_header" = "Photos overview"; -"lng_profile_videos" = "{count:_not_used_|# video file|# video files}"; -"lng_profile_videos_header" = "Video files overview"; +"lng_profile_photos_header" = "Photos"; +"lng_profile_videos" = "{count:_not_used_|# video|# videos}"; +"lng_profile_videos_header" = "Videos"; "lng_profile_songs" = "{count:_not_used_|# audio file|# audio files}"; -"lng_profile_songs_header" = "Audio files overview"; +"lng_profile_songs_header" = "Audio files"; "lng_profile_files" = "{count:_not_used_|# file|# files}"; -"lng_profile_files_header" = "Files overview"; +"lng_profile_files_header" = "Files"; "lng_profile_audios" = "{count:_not_used_|# voice message|# voice messages}"; -"lng_profile_audios_header" = "Voice messages overview"; +"lng_profile_audios_header" = "Voice messages"; "lng_profile_shared_links" = "{count:_not_used_|# shared link|# shared links}"; -"lng_profile_shared_links_header" = "Shared links overview"; +"lng_profile_shared_links_header" = "Shared links"; "lng_profile_copy_phone" = "Copy Phone Number"; "lng_profile_copy_fullname" = "Copy Name"; "lng_profile_drop_area_title" = "Drop your image here"; @@ -487,6 +502,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_report_title" = "Report channel"; "lng_report_group_title" = "Report group"; +"lng_report_bot_title" = "Report bot"; "lng_report_reason_spam" = "Spam"; "lng_report_reason_violence" = "Violence"; "lng_report_reason_pornography" = "Pornography"; @@ -515,9 +531,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_create_group_next" = "Next"; "lng_create_group_create" = "Create"; "lng_create_group_title" = "New Group"; -"lng_create_group_about" = "Groups are ideal for limited communities,\nthey can have up to {count:_not_used|# member|# members}"; "lng_create_channel_title" = "New Channel"; -"lng_create_channel_about" = "Channels are a tool for broadcasting your messages to unlimited audiences"; "lng_create_public_channel_title" = "Public Channel"; "lng_create_public_channel_about" = "Anyone can find the channel in search and join"; "lng_create_private_channel_title" = "Private Channel"; @@ -579,7 +593,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_action_pinned_message" = "{from} pinned «{text}»"; "lng_action_pinned_media" = "{from} pinned {media}"; "lng_action_pinned_media_photo" = "a photo"; -"lng_action_pinned_media_video" = "a video file"; +"lng_action_pinned_media_video" = "a video"; "lng_action_pinned_media_audio" = "an audio file"; "lng_action_pinned_media_voice" = "a voice message"; "lng_action_pinned_media_file" = "a file"; @@ -663,7 +677,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_media_type" = "Media type"; "lng_media_type_photos" = "Photos"; -"lng_media_type_videos" = "Video files"; +"lng_media_type_videos" = "Videos"; "lng_media_type_songs" = "Audio files"; "lng_media_type_files" = "Files"; "lng_media_type_audios" = "Voice messages"; @@ -672,7 +686,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_media_open_with" = "Open With"; "lng_media_download" = "Download"; "lng_media_cancel" = "Cancel"; -"lng_media_video" = "Video file"; +"lng_media_video" = "Video"; "lng_media_audio" = "Voice message"; "lng_media_auto_settings" = "Automatic media download settings"; @@ -705,7 +719,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_box_remove" = "Remove"; -"lng_custom_stickers" = "Custom stickers"; +"lng_stickers_installed_tab" = "Stickers"; +"lng_stickers_featured_tab" = "Trending"; +"lng_stickers_archived_tab" = "Archived"; "lng_stickers_remove_pack" = "Remove «{sticker_pack}»?"; "lng_stickers_add_pack" = "Add stickers"; "lng_stickers_share_pack" = "Share Stickers"; @@ -715,19 +731,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_copied" = "Sticker pack link copied to clipboard."; "lng_stickers_default_set" = "Great Minds"; "lng_stickers_you_have" = "Manage and reorder sticker packs"; -"lng_stickers_packs" = "Sticker Packs"; -"lng_stickers_reorder" = "Click and drag to reorder sticker packs"; "lng_stickers_featured" = "Trending Stickers"; -"lng_stickers_clear_recent" = "Clear"; -"lng_stickers_clear_recent_sure" = "Are you sure you want to clear your frequently used stickers list?"; -"lng_stickers_remove" = "Delete"; "lng_stickers_return" = "Undo"; -"lng_stickers_restore" = "Restore"; "lng_stickers_count" = "{count:Loading...|# sticker|# stickers}"; "lng_stickers_masks_pack" = "This is a pack of mask stickers. You can use them in the photo editor on our mobile apps."; "lng_in_dlg_photo" = "Photo"; -"lng_in_dlg_video" = "Video file"; +"lng_in_dlg_video" = "Video"; "lng_in_dlg_audio_file" = "Audio file"; "lng_in_dlg_contact" = "Contact"; "lng_in_dlg_audio" = "Voice message"; @@ -746,7 +756,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_report_spam_ok" = "Report"; "lng_cant_send_to_not_contact" = "Sorry, you can only send messages to\nmutual contacts at the moment.\n{more_info}"; "lng_cant_invite_not_contact" = "Sorry, you can only add mutual contacts\nto groups at the moment.\n{more_info}"; -"lng_cant_invite_not_contact_channel" = "Sorry, you can only add mutual contacts\nto channels at the moment.\n{more_info}"; +"lng_cant_send_to_not_contact_flood" = "You have contacted too many non-contacts today, please try again tomorrow. You will be able to reply today if this user messages you first."; +"lng_cant_invite_not_contact_flood" = "You can't add this user because you have contacted too many non-contacts today. Please try again tomorrow. You can ask another member to add this user to the group."; "lng_cant_more_info" = "More info »"; "lng_cant_invite_banned" = "Sorry, only admin can add this user."; "lng_cant_invite_privacy" = "Sorry, you cannot add this user to groups because of their privacy settings."; @@ -774,6 +785,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_dialogs_text_from_wrapped" = "{from}:"; "lng_dialogs_text_media" = "{media_part} {caption}"; "lng_dialogs_text_media_wrapped" = "{media},"; +"lng_dialogs_show_all_chats" = "Show all chats"; +"lng_dialogs_hide_muted_chats" = "Hide muted chats"; "lng_open_this_link" = "Open this link?"; "lng_open_link" = "Open"; @@ -820,7 +833,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_maps_point" = "Location"; "lng_save_photo" = "Save image"; -"lng_save_video" = "Save video file"; +"lng_save_video" = "Save video"; "lng_save_audio_file" = "Save audio file"; "lng_save_audio" = "Save voice message"; "lng_save_file" = "Save file"; @@ -828,12 +841,19 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_duration_and_size" = "{duration}, {size}"; "lng_duration_played" = "{played} / {duration}"; "lng_date_and_duration" = "{date}, {duration}"; -"lng_choose_images" = "Choose images"; +"lng_choose_image" = "Choose an image"; +"lng_choose_files" = "Choose files"; "lng_game_tag" = "Game"; "lng_context_view_profile" = "View profile"; "lng_context_view_group" = "View group info"; "lng_context_view_channel" = "View channel info"; +"lng_context_pin_to_top" = "Pin to top"; +"lng_context_unpin_from_top" = "Unpin from top"; + +"lng_context_promote_admin" = "Promote to admin"; +"lng_context_remove_admin" = "Remove from admins"; +"lng_context_remove_from_group" = "Remove from group"; "lng_context_copy_link" = "Copy Link"; "lng_context_copy_post_link" = "Copy Post Link"; @@ -848,7 +868,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_context_cancel_download" = "Cancel Download"; "lng_context_show_in_folder" = "Show in Folder"; "lng_context_show_in_finder" = "Show in Finder"; -"lng_context_save_video" = "Save Video File As..."; +"lng_context_save_video" = "Save Video As..."; "lng_context_save_audio_file" = "Save Audio File As..."; "lng_context_save_audio" = "Save Voice Message As..."; "lng_context_pack_info" = "Pack Info"; @@ -875,10 +895,15 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_really_send_image" = "Do you want to send this image?"; "lng_really_send_file" = "Do you want to send this file?"; "lng_really_share_contact" = "Do you want to share this contact?"; -"lng_send_image_compressed" = "Send compressed image"; -"lng_send_image_empty" = "Could not send an empty file :("; -"lng_send_image_too_large" = "Could not send a file, because it is larger than 1.5 GB :("; +"lng_send_images_compress" = "Compress {count:_not_used_|image|images}"; +"lng_send_image_non_local" = "Could not send a non local file: {name}"; +"lng_send_image_empty" = "Could not send an empty file: {name}"; +"lng_send_image_too_large" = "Could not send a file, because it is larger than 1500 MB: {name}"; "lng_send_folder" = "Could not send «{name}» because it is a directory :("; +"lng_send_images_selected" = "{count:_not_used_|# image|# images} selected"; +"lng_send_photos" = "Send {count:_not_used_|# photo|# photos}"; +"lng_send_files_selected" = "{count:_not_used_|# file|# files} selected"; +"lng_send_files" = "Send {count:_not_used_|# file|# files}"; "lng_forward_choose" = "Choose recipient..."; "lng_forward_cant" = "Sorry, no way to forward here :("; @@ -940,6 +965,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_selected_delete_sure_this" = "Do you want to delete this message?"; "lng_selected_delete_sure" = "Do you want to delete {count:_not_used_|# message|# messages}?"; "lng_delete_photo_sure" = "Do you want to delete this photo?"; +"lng_delete_for_everyone_hint" = "This will delete {count:_not_used_|it|them} for everyone in this chat."; +"lng_delete_for_me_chat_hint" = "This will delete {count:_not_used_|it|them} just for you, not for other participants of the chat."; +"lng_delete_for_me_hint" = "This will delete {count:_not_used_|it|them} just for you."; +"lng_delete_for_everyone_check" = "Delete for everyone"; +"lng_delete_for_other_check" = "Delete for {user}"; "lng_box_delete" = "Delete"; "lng_box_leave" = "Leave"; @@ -972,6 +1002,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_mediaview_saved" = "Image was saved to your [c]Downloads[/c] folder"; +"lng_theme_preview_title" = "Theme Preview"; +"lng_theme_preview_generating" = "Generating color theme preview..."; +"lng_theme_preview_invalid" = "Invalid data in this theme file."; +"lng_theme_preview_apply" = "Apply this theme"; + "lng_new_authorization" = "{name},\nWe detected a login into your account from a new device on {day}, {date} at {time}\n\nDevice: {device}\nLocation: {location}\n\nIf this wasn't you, you can go to Settings — Show all sessions and terminate that session.\n\nIf you think that somebody logged in to your account against your will, you can enable two-step verification in Settings.\n\nSincerely,\nThe Telegram Team"; "lng_new_version_wrap" = "Telegram Desktop was updated to version {version}\n\n{changes}\n\nFull version history is available here:\n{link}"; diff --git a/Telegram/Resources/langs/lang_de.strings b/Telegram/Resources/langs/lang_de.strings index 95f207223..e8042d615 100644 --- a/Telegram/Resources/langs/lang_de.strings +++ b/Telegram/Resources/langs/lang_de.strings @@ -122,9 +122,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_server_error" = "Interner Serverfehler."; "lng_flood_error" = "Zu viele Versuche, bitte später erneut probieren."; -"lng_gif_error" = "Ein Fehler ist beim Laden der GIF-Animation aufgetreten :("; +"lng_gif_error" = "GIF-Animation kann nicht abgespielt werden :("; "lng_edit_error" = "Du kannst diese Nachricht nicht bearbeiten"; -"lng_join_channel_error" = "Du bist bereits in zuvielen Kanälen und Supergruppen. Bitte trenne dich zuvor von ein paar davon."; +"lng_join_channel_error" = "Du bist bereits in zu vielen Kanälen und Supergruppen. Bitte trenne dich zuvor von ein paar davon."; +"lng_error_phone_flood" = "Du hast dein Konto leider zu oft gelöscht. Bitte warte einige Tage, erst dann kannst du dich erneut registrieren."; +"lng_error_start_minimized_passcoded" = "Du hast den lokalen Pincode-Schutz aktiviert, deshalb kannst du die App nicht minimiert starten. Die App muss dich vorher nach dem Pincode fragen."; +"lng_error_pinned_max" = "Du kannst nicht mehr als {count:_not_used_|# Chat|# Chats} oben anheften."; + "lng_edit_deleted" = "Diese Nachricht wurde gelöscht"; "lng_edit_too_long" = "Der Text ist leider zu lang"; "lng_edit_message" = "Nachricht bearbeiten"; @@ -138,7 +142,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_pinned_unpin" = "Entfernen"; "lng_pinned_notify" = "Alle benachrichtigen"; -"lng_intro" = "Willkommen beim [a href=\"https://telegram.org/\"]offiziellen Desktop Messenger[/a].\n[b]Schnell[/b] und [b]sicher[/b] chatten leicht gemacht."; +"lng_intro_about" = "Willkommen in der offiziellen Telegram Desktop App.\nDie App ist schnell und sicher."; "lng_start_msgs" = "JETZT STARTEN"; "lng_intro_next" = "WEITER"; @@ -146,11 +150,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_intro_submit" = "SENDEN"; "lng_photo_caption" = "Beschriftung"; +"lng_photos_comment" = "Kommentar"; "lng_phone_ph" = "Deine Telefonnummer"; "lng_phone_title" = "Dein Telefon"; "lng_phone_desc" = "Bitte Land und Telefonnummer\nohne führende Null eintragen."; -"lng_phone_notreg" = "Hinweis: Wenn du noch kein Telegram Konto haben solltest,\n[b]registriere[/b] dich bitte über unsere [a href=\"https://telegram.org/\"]iOS / Android[/a] apps oder direkt {signup_start}hier »{signup_end}"; +"lng_phone_notreg" = "Wenn du noch kein Telegram Konto hast,\n[b]registriere[/b] dich über unsere {link_start}Android / iPhone Apps{link_end}\noder direkt {signup_start}hier{signup_end}"; "lng_country_code" = "Ländercode"; "lng_bad_country_code" = "Falscher Ländercode"; "lng_country_ph" = "Suche"; @@ -174,7 +179,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_title" = "Dein Kennwort"; "lng_signin_desc" = "Bitte Kennwort eingeben."; -"lng_signin_recover_desc" = "Bitte Code aus der E-Mail eingeben."; +"lng_signin_recover_desc" = "Bitte Code aus der E-Mail\n{email} eingeben."; "lng_signin_password" = "Dein Kennwort"; "lng_signin_code" = "Code aus der E-Mail"; "lng_signin_recover" = "Kennwort vergessen?"; @@ -196,7 +201,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_reset_in_minutes" = "{count_minutes:0 Minuten|# Minute|# Minuten}"; "lng_signin_reset_cancelled" = "Deine vorherigen Versuche das Konto zurückzusetzen wurden durch den aktiven Nutzer abgebrochen. Bitte in 7 Tagen erneut probieren."; -"lng_signup_title" = "Information und Bild"; +"lng_signup_title" = "Deine Info"; "lng_signup_desc" = "Bitte trage deinen Namen ein \nund lade ein Bild hoch."; "lng_signup_firstname" = "Vorname"; @@ -212,6 +217,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_dlg_search_chat" = "In diesem Chat suchen"; "lng_dlg_search_channel" = "In diesem Kanal suchen"; "lng_dlg_search_for_messages" = "Nach Nachrichten suchen"; +"lng_update_telegram" = "Aktualisiere Telegram"; "lng_settings_save" = "Speichern"; "lng_settings_upload" = "Profilbild festlegen"; @@ -260,7 +266,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_settings_change_lang" = "Sprache ändern"; "lng_languages" = "Sprachen"; "lng_sure_save_language" = "Telegram wird neugestartet, um in der neuen Sprache angezeigt werden zu können"; -"lng_settings_update_automatically" = "Automatisch aktualisieren (v{version})"; +"lng_settings_update_automatically" = "Automatisch aktualisieren"; +"lng_settings_current_version_label" = "Version {version}:"; "lng_settings_current_version" = "Version {version}"; "lng_settings_check_now" = "Auf Updates prüfen"; "lng_settings_update_checking" = "Prüfe auf Updates..."; @@ -286,12 +293,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_settings_send_cmdenter" = "Mit Cmd+Enter senden"; "lng_settings_section_background" = "Hintergrundbild"; +"lng_settings_bg_use_default" = "Benutze Standardthema"; "lng_settings_bg_from_gallery" = "Aus der Galerie"; "lng_settings_bg_from_file" = "Durchsuchen"; "lng_settings_bg_tile" = "Nebeneinander"; "lng_settings_adaptive_wide" = "Flexibles Layout für breite Bildschirme"; "lng_backgrounds_header" = "Neuen Chat-Hintergrund wählen"; +"lng_theme_sure_keep" = "Wirklich dieses Thema verwenden?"; +"lng_theme_reverting" = "Vorheriges Thema wird in {count:_not_used_|# Sekunde|# Sekunden} benutzt."; +"lng_theme_keep_changes" = "Anwenden"; +"lng_theme_revert" = "Zurücksetzen"; "lng_download_path_dont_ask" = "Speicherort merken"; "lng_download_path_label" = "Speicherort:"; @@ -340,11 +352,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_passcode_enter_first" = "Pincode festlegen"; "lng_passcode_enter_new" = "Neuer Pincode"; "lng_passcode_confirm_new" = "Pincode wiederholen"; -"lng_passcode_about" = "Bei aktivierter Code-Sperre erscheint ein Schloss oben in der Menüleiste. Klicke darauf, um die App zu sperren. \n\nÜbrigens: Vergisst du den Pincode, musst du dich abmelden und wieder anmelden."; +"lng_passcode_about" = "Sobald du einen Pincode einstellst, siehst du ein kleines Schloss über deiner Chatliste. Tippe darauf um die App zu sperren.\n\nWichtig: Vergisst du den Pincode, so musst du dich bei Telegram Desktop neu anmelden."; "lng_passcode_differ" = "Pincode ist nicht identisch"; "lng_passcode_wrong" = "Falscher Pincode"; "lng_passcode_is_same" = "Pincode wurde nicht geändert"; "lng_passcode_enter" = "Pincode eingeben"; +"lng_passcode_ph" = "Dein Pincode"; "lng_passcode_submit" = "Absenden"; "lng_passcode_logout" = "Abmelden"; "lng_passcode_need_unblock" = "Bitte erst entsperren."; @@ -417,7 +430,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_preview_loading" = "Lade Linkvorschau..."; -"lng_profile_chat_unaccessible" = "Gruppe nicht verfügbar"; +"lng_profile_chat_unaccessible" = "Gruppe ist nicht verfügbar"; "lng_profile_about_section" = "Info"; "lng_profile_description_section" = "Beschreibung"; "lng_profile_settings_section" = "Einstellungen"; @@ -428,6 +441,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_create_public_link" = "Öffentlichen Link erstellen"; "lng_profile_edit_public_link" = "Öffentlichen Link bearbeiten"; "lng_profile_manage_admins" = "Administratoren verwalten"; +"lng_profile_common_groups" = "{count:_not_used_|# gemeinsame Gruppe|# gemeinsame Gruppen}"; +"lng_profile_common_groups_section" = "Gemeinsame Gruppen"; "lng_profile_participants_section" = "Teilnehmer"; "lng_profile_info_section" = "Info"; "lng_profile_mobile_number" = "Telefon:"; @@ -436,7 +451,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_add_contact" = "Kontakt hinzufügen"; "lng_profile_edit_contact" = "Bearbeiten"; "lng_profile_enable_notifications" = "Benachrichtigungen"; -"lng_profile_clear_history" = "Chatverlauf löschen"; +"lng_profile_clear_history" = "Verlauf löschen"; "lng_profile_delete_conversation" = "Chat löschen"; "lng_profile_clear_and_exit" = "Löschen und verlassen"; "lng_profile_leave_channel" = "Kanal verlassen"; @@ -467,17 +482,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_shared_media" = "Geteilte Medien"; "lng_profile_no_media" = "Noch keine Medien in diesem Chat"; "lng_profile_photos" = "{count:_not_used_|# Bild|# Bilder}"; -"lng_profile_photos_header" = "Bilder aus dem Chat"; +"lng_profile_photos_header" = "Bilder"; "lng_profile_videos" = "{count:_not_used_|# Video|# Videos}"; -"lng_profile_videos_header" = "Videos aus dem Chat"; +"lng_profile_videos_header" = "Videos"; "lng_profile_songs" = "{count:_not_used_|# Audio|# Audios}"; -"lng_profile_songs_header" = "Audios aus dem Chat"; +"lng_profile_songs_header" = "Audiodateien"; "lng_profile_files" = "{count:_not_used_|# Datei|# Dateien}"; -"lng_profile_files_header" = "Dateien aus dem Chat"; +"lng_profile_files_header" = "Dateien"; "lng_profile_audios" = "{count:_not_used_|# Sprachnachricht|# Sprachnachrichten}"; -"lng_profile_audios_header" = "Sprachnachrichten aus dem Chat"; -"lng_profile_shared_links" = "{count:_not_used_|# Geteilter Link|# Geteilte Links} »"; -"lng_profile_shared_links_header" = "Links aus dem Chat"; +"lng_profile_audios_header" = "Sprachnachrichten"; +"lng_profile_shared_links" = "{count:_not_used_|# Geteilter Link|# Geteilte Links}"; +"lng_profile_shared_links_header" = "Geteilte Links"; "lng_profile_copy_phone" = "Telefonnummer kopieren"; "lng_profile_copy_fullname" = "Name kopieren"; "lng_profile_drop_area_title" = "Hierher ziehen"; @@ -487,6 +502,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_report_title" = "Kanal melden"; "lng_report_group_title" = "Gruppe melden"; +"lng_report_bot_title" = "Bot melden"; "lng_report_reason_spam" = "Spam"; "lng_report_reason_violence" = "Gewalt"; "lng_report_reason_pornography" = "Pornografie"; @@ -515,9 +531,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_create_group_next" = "Weiter"; "lng_create_group_create" = "Erstellen"; "lng_create_group_title" = "Neue Gruppe erstellen"; -"lng_create_group_about" = "{count:_not_used|# Mitglied|# Mitglieder} passen in jede Gruppe, praktisch für eine kleinere Gemeinschaft."; "lng_create_channel_title" = "Neuen Kanal erstellen"; -"lng_create_channel_about" = "In einen Kanal passen unbegrenzt viele Leute, also ideal für ein großes Publikum."; "lng_create_public_channel_title" = "Öffentlicher Kanal"; "lng_create_public_channel_about" = "Jeder kann deinen Kanal finden und beitreten"; "lng_create_private_channel_title" = "Privater Kanal"; @@ -672,7 +686,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_media_open_with" = "Öffnen mit"; "lng_media_download" = "Download"; "lng_media_cancel" = "Abbrechen"; -"lng_media_video" = "Videodatei"; +"lng_media_video" = "Video"; "lng_media_audio" = "Sprachnachricht"; "lng_media_auto_settings" = "Einstellungen für Mediendownloads"; @@ -705,7 +719,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_box_remove" = "Entfernen"; -"lng_custom_stickers" = "Eigene Sticker"; +"lng_stickers_installed_tab" = "Sticker"; +"lng_stickers_featured_tab" = "Angesagt"; +"lng_stickers_archived_tab" = "Archiviert"; "lng_stickers_remove_pack" = "«{sticker_pack}» entfernen?"; "lng_stickers_add_pack" = "Sticker hinzufügen"; "lng_stickers_share_pack" = "Sticker teilen"; @@ -715,19 +731,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_copied" = "Sticker-Paket Link in die Zwischenablage kopiert."; "lng_stickers_default_set" = "Große Denker"; "lng_stickers_you_have" = "Sticker-Pakete verwalten"; -"lng_stickers_packs" = "Sticker-Pakete"; -"lng_stickers_reorder" = "Paket gedrückt halten und verschieben um die Anordnung zu ändern"; "lng_stickers_featured" = "Angesagte Sticker"; -"lng_stickers_clear_recent" = "Leeren"; -"lng_stickers_clear_recent_sure" = "Zuletzt benutzte Sticker leeren?"; -"lng_stickers_remove" = "Löschen"; "lng_stickers_return" = "Rückgängig"; -"lng_stickers_restore" = "Zeigen"; "lng_stickers_count" = "{count:Lade...|# Sticker|# Sticker}"; "lng_stickers_masks_pack" = "Das ist ein Masken-Paket. Du kannst Masken nur im Editor unserer mobilen Apps verwenden."; "lng_in_dlg_photo" = "Bild"; -"lng_in_dlg_video" = "Videodatei"; +"lng_in_dlg_video" = "Video"; "lng_in_dlg_audio_file" = "Audiodatei"; "lng_in_dlg_contact" = "Kontakt"; "lng_in_dlg_audio" = "Sprachnachricht"; @@ -744,9 +754,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_report_spam_sure_group" = "Spam in dieser Gruppe melden?"; "lng_report_spam_sure_channel" = "Möchtest du wirklich Spam in diesem Kanal melden?"; "lng_report_spam_ok" = "Melden"; -"lng_cant_send_to_not_contact" = "Derzeit kannst du nur Personen schreiben,\nwenn ihr eure Nummern ausgetauscht habt.\n{more_info}"; -"lng_cant_invite_not_contact" = "Du kannst nur Personen hinzufügen,\nwenn ihr eure Nummern ausgetauscht habt.\n{more_info}"; -"lng_cant_invite_not_contact_channel" = "Du kannst nur Personen hinzufügen,\nwenn ihr eure Nummern ausgetauscht habt.\n{more_info}"; +"lng_cant_send_to_not_contact" = "Derzeit kannst du nur Personen schreiben, wenn ihr eure Nummern ausgetauscht habt. {more_info}"; +"lng_cant_invite_not_contact" = "Du kannst nur Personen hinzufügen, wenn ihr eure Nummern ausgetauscht habt. {more_info}"; +"lng_cant_send_to_not_contact_flood" = "Heute hast du zu viele nicht-gemeinsame Kontakte angeschrieben, versuche es bitte morgen wieder. Du kannst heute nur noch Leuten antworten, die dir zuerst eine Nachricht senden."; +"lng_cant_invite_not_contact_flood" = "Diesen Nutzer kannst du nicht hinzufügen, da du heute zu viele nicht-gemeinsame Kontakte angeschrieben hast. Versuche es morgen wieder oder frage ein anderes Mitglied ob es den Teilnehmer hinzufügen kann."; "lng_cant_more_info" = "Weitere Infos »"; "lng_cant_invite_banned" = "Nur Admins können diesen Nutzer hinzufügen."; "lng_cant_invite_privacy" = "Du kannst mit diesem Nutzer keine Gruppe erstellen, weil er es nicht erlaubt."; @@ -774,6 +785,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_dialogs_text_from_wrapped" = "{from}:"; "lng_dialogs_text_media" = "{media_part} {caption}"; "lng_dialogs_text_media_wrapped" = "{media},"; +"lng_dialogs_show_all_chats" = "Alle Chats zeigen"; +"lng_dialogs_hide_muted_chats" = "Stummgeschaltete Chats verstecken"; "lng_open_this_link" = "Diesen Link öffnen?"; "lng_open_link" = "Öffnen"; @@ -820,7 +833,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_maps_point" = "Standort"; "lng_save_photo" = "Bild speichern"; -"lng_save_video" = "Videodatei speichern"; +"lng_save_video" = "Video speichern"; "lng_save_audio_file" = "Audiodatei speichern"; "lng_save_audio" = "Sprachnachricht speichern"; "lng_save_file" = "Datei speichern"; @@ -828,12 +841,19 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_duration_and_size" = "{duration}, {size}"; "lng_duration_played" = "{played} / {duration}"; "lng_date_and_duration" = "{date}, {duration}"; -"lng_choose_images" = "Bilder auswählen"; +"lng_choose_image" = "Bild auswählen"; +"lng_choose_files" = "Dateien auswählen"; "lng_game_tag" = "Spiel"; "lng_context_view_profile" = "Profil öffnen"; "lng_context_view_group" = "Gruppeninfo zeigen"; "lng_context_view_channel" = "Kanalinfo anzeigen"; +"lng_context_pin_to_top" = "Anheften"; +"lng_context_unpin_from_top" = "Entfernen"; + +"lng_context_promote_admin" = "Zum Admin machen"; +"lng_context_remove_admin" = "Als Admin entfernen"; +"lng_context_remove_from_group" = "Aus Gruppe entfernen"; "lng_context_copy_link" = "Link kopieren"; "lng_context_copy_post_link" = "Nachrichtenlink kopieren"; @@ -848,7 +868,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_context_cancel_download" = "Download abbrechen"; "lng_context_show_in_folder" = "Im Ordner anzeigen"; "lng_context_show_in_finder" = "Im Finder zeigen"; -"lng_context_save_video" = "Videodatei speichern unter..."; +"lng_context_save_video" = "Video speichern unter..."; "lng_context_save_audio_file" = "Audiodatei speichern unter..."; "lng_context_save_audio" = "Sprachnachricht speichern unter..."; "lng_context_pack_info" = "Sticker-Paket"; @@ -875,10 +895,15 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_really_send_image" = "Willst du dieses Bild senden?"; "lng_really_send_file" = "Willst du diese Datei senden?"; "lng_really_share_contact" = "Willst du diesen Kontakt senden?"; -"lng_send_image_compressed" = "Bild komprimieren"; -"lng_send_image_empty" = "Datei kann nicht gesendet werden :("; -"lng_send_image_too_large" = "Nur Dateien bis maximal 1,5 GB können gesendet werden :("; +"lng_send_images_compress" = "Komprimiere {count:_not_used_|Bild|Bilder}"; +"lng_send_image_non_local" = "{name} befindet sich nicht direkt auf dem Gerät"; +"lng_send_image_empty" = "Leere Datei {name} konnte nicht gesendet werden"; +"lng_send_image_too_large" = "{name} ist größer als 1500 MB und kann daher nicht gesendet werden"; "lng_send_folder" = "Verzeichnis «{name}» kann nicht gesendet werden :("; +"lng_send_images_selected" = "{count:_not_used_|# Bild|# Bilder} ausgewählt"; +"lng_send_photos" = "{count:_not_used_|# Bild|# Bilder} senden"; +"lng_send_files_selected" = "{count:_not_used_|# Datei|# Dateien} ausgewählt"; +"lng_send_files" = "{count:_not_used_|# Datei|# Dateien} senden"; "lng_forward_choose" = "Empfänger wählen..."; "lng_forward_cant" = "Weiterleiten nicht möglich :("; @@ -940,6 +965,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_selected_delete_sure_this" = "Diese Nachricht wirklich löschen?"; "lng_selected_delete_sure" = "Willst du {count:_not_used_|# Nachricht|# Nachrichten} löschen?"; "lng_delete_photo_sure" = "Dieses Bild wirklich löschen?"; +"lng_delete_for_everyone_hint" = "Das wird {count:_not_used_|sie|sie} bei allen im Chat löschen."; +"lng_delete_for_me_chat_hint" = "Das wird {count:_not_used_|sie|sie} nur bei dir löschen und nicht bei anderen Teilnehmern."; +"lng_delete_for_me_hint" = "Das wird {count:_not_used_|sie|sie} nur bei dir löschen."; +"lng_delete_for_everyone_check" = "Bei allen löschen"; +"lng_delete_for_other_check" = "Bei {user} löschen"; "lng_box_delete" = "Löschen"; "lng_box_leave" = "Verlassen"; @@ -972,6 +1002,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_mediaview_saved" = "Bild wurde im [c]Downloads[/c] Ordner gespeichert"; +"lng_theme_preview_title" = "Themenvorschau"; +"lng_theme_preview_generating" = "Erstelle Vorschau des Farbthemas..."; +"lng_theme_preview_invalid" = "Themen-Datei beinhaltet ungültige Daten."; +"lng_theme_preview_apply" = "Thema anwenden"; + "lng_new_authorization" = "{name},\nwir haben eine Anmeldung von einem neuen Gerät am {day}, {date} um {time} festgestellt.\n\nGerät: {device}\nStandort: {location}\n\nWarst du das selbst? Wenn du das nicht selbst gewesen bist, melde alle anderen Sitzungen in den Telegram Einstellungen unverzüglich ab.\n\nBeachte unsere zweistufige Bestätigung, welche du in den Telegram Einstellungen optional aktivieren kannst.\n\nDanke,\nDein Telegram Team"; "lng_new_version_wrap" = "Telegram Desktop wurde aktualisiert auf Version {version}\n\n{changes}\n\nGesamter Versionsverlauf:\n{link}"; diff --git a/Telegram/Resources/langs/lang_es.strings b/Telegram/Resources/langs/lang_es.strings index 08e241115..1126e5f96 100644 --- a/Telegram/Resources/langs/lang_es.strings +++ b/Telegram/Resources/langs/lang_es.strings @@ -110,7 +110,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_status_online" = "en línea"; "lng_status_connecting" = "conectando..."; -"lng_chat_status_unaccessible" = "el grupo es inaccesible"; +"lng_chat_status_unaccessible" = "grupo inaccesible"; "lng_chat_status_members" = "{count:sin miembros|# miembro|# miembros}"; "lng_chat_status_members_online" = "{count:_not_used_|# miembro|# miembros}, {count_online:_not_used_|# en línea|# en línea}"; @@ -124,11 +124,15 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_flood_error" = "Muchos intentos. Por favor, reinténtalo más tarde."; "lng_gif_error" = "Ocurrió un error al leer la animación GIF :("; "lng_edit_error" = "No puedes editar este mensaje"; -"lng_join_channel_error" = "Lo sentimos, te has unido a demasiados canales y supergrupos. Por favor, deja algunos antes de unirte."; +"lng_join_channel_error" = "Lo sentimos, te has unido a demasiados canales y supergrupos. Por favor, sal de algunos antes de unirte."; +"lng_error_phone_flood" = "Has eliminado y vuelto a crear tu cuenta muchas veces recientemente. Por favor, espera algunos días antes de inscribirte de nuevo."; +"lng_error_start_minimized_passcoded" = "Has configurado un código de acceso, así que no puedes iniciar la app minimizada. La app te pedirá el código de acceso para que empiece a funcionar."; +"lng_error_pinned_max" = "Lo sentimos, no puedes anclar más de {count:_not_used_|# chat|# chats}."; + "lng_edit_deleted" = "Este mensaje fue eliminado"; "lng_edit_too_long" = "Tu texto es demasiado largo"; "lng_edit_message" = "Editar mensaje"; -"lng_edit_message_text" = "Nuevo mensaje..."; +"lng_edit_message_text" = "Mensaje nuevo..."; "lng_deleted" = "Desconocido"; "lng_deleted_message" = "Mensaje eliminado"; "lng_pinned_message" = "Mensaje anclado"; @@ -138,7 +142,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_pinned_unpin" = "Desanclar"; "lng_pinned_notify" = "Notificar a todos los miembros"; -"lng_intro" = "La app oficial para PC de [a href=\"https://telegram.org/\"]Telegram[/a].\nEs [b]rápida[/b] y [b]segura[/b]."; +"lng_intro_about" = "Te damos la bienvenida a la app oficial para PC.\nEs rápida y segura."; "lng_start_msgs" = "EMPEZAR A CONVERSAR"; "lng_intro_next" = "SIGUIENTE"; @@ -146,11 +150,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_intro_submit" = "ENVIAR"; "lng_photo_caption" = "Comentario"; +"lng_photos_comment" = "Comentario"; "lng_phone_ph" = "Tu número de teléfono"; "lng_phone_title" = "Tu teléfono"; "lng_phone_desc" = "Por favor, confirma el código de tu país\ny tu número de teléfono."; -"lng_phone_notreg" = "Si no tienes una cuenta de Telegram todavía, \npor favor, [b]regístrate[/b] con tu [a href=\"https://telegram.org/\"]iOS / Android[/a] o {signup_start}aquí »{signup_end}"; +"lng_phone_notreg" = "Si no tienes una cuenta de Telegram todavía, \npor favor, [b]regístrate[/b] con {link_start}Android / iPhone{link_end}\no aquí {signup_start}here{signup_end}"; "lng_country_code" = "Código de país"; "lng_bad_country_code" = "Código de país inválido"; "lng_country_ph" = "Buscar"; @@ -160,7 +165,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_code_ph" = "Tu código"; "lng_code_desc" = "Hemos enviado un mensaje con un código de \nactivación a tu teléfono. Por favor, ponlo abajo."; -"lng_code_telegram" = "Por favor, pon el código que acabas\nde recibir en tu otra aplicación de [b]Telegram[/b]."; +"lng_code_telegram" = "Por favor, pon el código que acabas de recibir \nen tu otra aplicación de [b]Telegram[/b]."; "lng_code_no_telegram" = "Enviar el código vía SMS"; "lng_code_call" = "Telegram marcará tu número en {minutes}:{seconds}"; "lng_code_calling" = "Solicitando una llamada de Telegram..."; @@ -174,7 +179,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_title" = "Contraseña"; "lng_signin_desc" = "Por favor, pon tu contraseña."; -"lng_signin_recover_desc" = "Por favor, pon el código desde el e-mail."; +"lng_signin_recover_desc" = "Por favor, pon el código desde el e-mail\n{email}"; "lng_signin_password" = "Tu contraseña"; "lng_signin_code" = "Código desde el e-mail"; "lng_signin_recover" = "¿Olvidaste la contraseña?"; @@ -196,7 +201,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_reset_in_minutes" = "{count_minutes:0 minutos|# minuto|# minutos}"; "lng_signin_reset_cancelled" = "Tus intentos recientes para restablecer la cuenta fueron cancelados por su usuario activo. Por favor, reinténtalo en 7 días."; -"lng_signup_title" = "Información y foto"; +"lng_signup_title" = "Tu información"; "lng_signup_desc" = "Por favor, pon tu nombre \ny una foto."; "lng_signup_firstname" = "Nombre"; @@ -212,6 +217,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_dlg_search_chat" = "Buscar en este chat"; "lng_dlg_search_channel" = "Buscar en este canal"; "lng_dlg_search_for_messages" = "Buscar mensajes"; +"lng_update_telegram" = "Actualizar Telegram"; "lng_settings_save" = "Guardar"; "lng_settings_upload" = "Poner foto de perfil"; @@ -251,7 +257,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_settings_sound_notify" = "Reproducir sonido"; "lng_settings_include_muted" = "Contar chats silenciados en los “no leídos”"; -"lng_notification_preview" = "Tienes un nuevo mensaje"; +"lng_notification_preview" = "Tienes un mensaje nuevo"; "lng_notification_reply" = "Responder"; "lng_notification_hide_all" = "Ocultar todas"; "lng_notification_sample" = "Esta es una notificación de ejemplo"; @@ -260,7 +266,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_settings_change_lang" = "Cambiar idioma"; "lng_languages" = "Idiomas"; "lng_sure_save_language" = "Telegram se reiniciará para cambiar el idioma"; -"lng_settings_update_automatically" = "Actualizar automáticamente (ver. {version})"; +"lng_settings_update_automatically" = "Actualizar automáticamente"; +"lng_settings_current_version_label" = "Versión {version}:"; "lng_settings_current_version" = "Versión {version}"; "lng_settings_check_now" = "Buscar actualizaciones"; "lng_settings_update_checking" = "Buscando actualizaciones..."; @@ -286,18 +293,23 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_settings_send_cmdenter" = "Enviar con Cmd+Intro"; "lng_settings_section_background" = "Fondo de chat"; +"lng_settings_bg_use_default" = "Usar el tema por defecto"; "lng_settings_bg_from_gallery" = "Elegir desde la galería"; "lng_settings_bg_from_file" = "Elegir desde archivos"; "lng_settings_bg_tile" = "Fondo con mosaico"; "lng_settings_adaptive_wide" = "Diseño adaptable para pantallas anchas"; "lng_backgrounds_header" = "Elige tu nuevo fondo de chat"; +"lng_theme_sure_keep" = "¿Mantener este tema?"; +"lng_theme_reverting" = "Regresar al tema anterior en {count:_not_used_|# segundo|# segundos}."; +"lng_theme_keep_changes" = "Mantener cambios"; +"lng_theme_revert" = "Revertir"; -"lng_download_path_dont_ask" = "No pedir la ruta de descarga para cada archivo"; +"lng_download_path_dont_ask" = "No preguntar dónde guardar archivos"; "lng_download_path_label" = "Ruta de descarga:"; "lng_download_path_temp" = "carpeta temporal"; "lng_download_path_default" = "carpeta por defecto"; -"lng_download_path_clear" = "Borrar todo"; +"lng_download_path_clear" = "Eliminar todo"; "lng_download_path_header" = "Elegir ruta de la descarga"; "lng_download_path_default_radio" = "Carpeta de Telegram en «Descargas»"; "lng_download_path_temp_radio" = "Carpeta temporal, borrada al cerrar sesión"; @@ -317,7 +329,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_settings_no_data_cached" = "¡No se encontraron datos en la caché!"; "lng_settings_images_cached" = "{count:_not_used_|# imagen|# imágenes}, {size}"; "lng_settings_audios_cached" = "{count:_not_used_|# mensaje de voz|# mensajes de voz}, {size}"; -"lng_local_storage_clear" = "Borrar todo"; +"lng_local_storage_clear" = "Eliminar todo"; "lng_local_storage_clearing" = "Borrando..."; "lng_local_storage_cleared" = "¡Borrado!"; "lng_local_storage_clear_failed" = "Borrado fallido :("; @@ -340,11 +352,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_passcode_enter_first" = "Pon un código de acceso"; "lng_passcode_enter_new" = "Pon tu nuevo código"; "lng_passcode_confirm_new" = "Repite el nuevo código"; -"lng_passcode_about" = "Cuando configuras un código, aparece un candado en la esquina derecha de la pantalla. Púlsalo para bloquear la aplicación. \n\nNota: si olvidas el código, tendrás que reiniciar la sesión en Telegram Desktop."; +"lng_passcode_about" = "Una vez que establezcas un código de acceso, un candado aparecerá en la lista de chats. Pulsa sobre él para bloquear la aplicación.\n\nImportante: si olvidas el código de acceso, tendrás que reiniciar la sesión."; "lng_passcode_differ" = "Los códigos de acceso son diferentes"; "lng_passcode_wrong" = "Código de acceso equivocado"; "lng_passcode_is_same" = "El código no fue cambiado"; "lng_passcode_enter" = "Pon tu código de acceso"; +"lng_passcode_ph" = "Tu código de acceso"; "lng_passcode_submit" = "Enviar"; "lng_passcode_logout" = "Cerrar sesión"; "lng_passcode_need_unblock" = "Necesitas desbloquearme primero."; @@ -364,7 +377,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_cloud_password_bad" = "La contraseña y la pista no pueden ser iguales."; "lng_cloud_password_email" = "Pon un e-mail de recuperación"; "lng_cloud_password_bad_email" = "E-mail incorrecto. Por favor, prueba otro."; -"lng_cloud_password_about" = "Necesitarás la contraseña al iniciar sesión en un nuevo dispositivo, además del código de activación."; +"lng_cloud_password_about" = "Necesitarás la contraseña al iniciar sesión, además del código de activación."; "lng_cloud_password_about_recover" = "¡Advertencia! ¿No quieres añadir un e-mail \nde recuperación para la contraseña?\n\nSi olvidas tu contraseña, perderás\nel acceso a tu cuenta de Telegram."; "lng_cloud_password_skip_email" = "Omitir e-mail"; "lng_cloud_password_almost" = "Un enlace de confirmación fue enviado \nal e-mail que estableciste. La verificación en dos pasos se activará en cuanto sigas ese enlace."; @@ -428,6 +441,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_create_public_link" = "Crear enlace público"; "lng_profile_edit_public_link" = "Editar enlace público"; "lng_profile_manage_admins" = "Gestionar administradores"; +"lng_profile_common_groups" = "{count:_not_used_|# grupo|# grupos} en común"; +"lng_profile_common_groups_section" = "Grupos en común"; "lng_profile_participants_section" = "Miembros"; "lng_profile_info_section" = "Información"; "lng_profile_mobile_number" = "Número:"; @@ -436,12 +451,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_add_contact" = "Añadir contacto"; "lng_profile_edit_contact" = "Editar"; "lng_profile_enable_notifications" = "Notificaciones"; -"lng_profile_clear_history" = "Borrar historial"; +"lng_profile_clear_history" = "Eliminar historial"; "lng_profile_delete_conversation" = "Eliminar chat"; "lng_profile_clear_and_exit" = "Eliminar y salir"; -"lng_profile_leave_channel" = "Dejar el canal"; +"lng_profile_leave_channel" = "Salir del canal"; "lng_profile_delete_channel" = "Eliminar el canal"; -"lng_profile_leave_group" = "Dejar el grupo"; +"lng_profile_leave_group" = "Salir del grupo"; "lng_profile_delete_group" = "Eliminar grupo"; "lng_profile_report" = "Reportar"; "lng_profile_search_messages" = "Buscar mensajes"; @@ -457,7 +472,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_add_participant" = "Añadir miembros"; "lng_profile_view_channel" = "Ver canal"; "lng_profile_join_channel" = "Unirme"; -"lng_profile_delete_and_exit" = "Dejar grupo"; +"lng_profile_delete_and_exit" = "Salir"; "lng_profile_kick" = "Eliminar"; "lng_profile_admin" = "administrador"; "lng_profile_sure_kick" = "¿Eliminar a {user} del grupo?"; @@ -467,7 +482,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_shared_media" = "Multimedia"; "lng_profile_no_media" = "No hay multimedia en este chat."; "lng_profile_photos" = "{count:_not_used_|# foto|# fotos}"; -"lng_profile_photos_header" = "Todas las fotos"; +"lng_profile_photos_header" = "Fotos"; "lng_profile_videos" = "{count:_not_used_|# vídeo|# vídeos}"; "lng_profile_videos_header" = "Vídeos"; "lng_profile_songs" = "{count:_not_used_|# audio|# audios}"; @@ -487,6 +502,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_report_title" = "Reportar canal"; "lng_report_group_title" = "Reportar grupo"; +"lng_report_bot_title" = "Reportar bot"; "lng_report_reason_spam" = "Spam"; "lng_report_reason_violence" = "Violencia"; "lng_report_reason_pornography" = "Pornografía"; @@ -515,9 +531,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_create_group_next" = "Siguiente"; "lng_create_group_create" = "Crear"; "lng_create_group_title" = "Nuevo grupo"; -"lng_create_group_about" = "Los grupos son ideales para las comunidades limitadas. Pueden tener hasta {count:_not_used|# miembro|# miembros}"; "lng_create_channel_title" = "Nuevo canal"; -"lng_create_channel_about" = "Los canales permiten difundir tus mensajes a audiencias ilimitadas"; "lng_create_public_channel_title" = "Canal público"; "lng_create_public_channel_about" = "Cualquiera puede encontrar el canal en la búsqueda y unirse"; "lng_create_private_channel_title" = "Canal privado"; @@ -539,14 +553,14 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_create_channel_crop" = "Selecciona el área para la foto del canal"; "lng_failed_add_participant" = "No se pudo añadir al usuario. Por favor, reinténtalo más tarde."; -"lng_failed_add_not_mutual" = "Lo sentimos, si una persona deja el grupo, sólo un contacto mutuo puede volver a invitarlo (necesitan tener tu número y tú el suyo)."; -"lng_failed_add_not_mutual_channel" = "Lo sentimos, si una persona deja el canal, sólo un contacto mutuo puede volver a invitarlo (necesitan tener tu número y tú el suyo)."; +"lng_failed_add_not_mutual" = "Lo sentimos, si una persona sale del grupo, sólo un contacto mutuo puede volver a invitarlo (necesitan tener tu número y tú el suyo)."; +"lng_failed_add_not_mutual_channel" = "Lo sentimos, si una persona sale del canal, sólo un contacto mutuo puede volver a invitarlo (necesitan tener tu número y tú el suyo)."; "lng_sure_delete_contact" = "¿Quieres eliminar a {contact} de tu lista de contactos?"; -"lng_sure_delete_history" = "¿Quieres borrar todo el historial de mensajes con {contact}?\n\nEsta acción no se puede deshacer."; -"lng_sure_delete_group_history" = "¿Quieres borrar todo el historial en «{group}»?\n\nEsta acción no se puede deshacer."; -"lng_sure_delete_and_exit" = "¿Quieres borrar todo el historial de mensajes y dejar el grupo «{group}»?\n\nEsta acción no se puede deshacer."; -"lng_sure_leave_channel" = "¿Quieres dejar este canal?"; +"lng_sure_delete_history" = "¿Quieres eliminar todo el historial de mensajes con {contact}?\n\nEsta acción no se puede deshacer."; +"lng_sure_delete_group_history" = "¿Quieres eliminar todo el historial en «{group}»?\n\nEsta acción no se puede deshacer."; +"lng_sure_delete_and_exit" = "¿Quieres eliminar todo el historial de mensajes y salir del grupo «{group}»?\n\nEsta acción no se puede deshacer."; +"lng_sure_leave_channel" = "¿Quieres salir de este canal?"; "lng_sure_delete_channel" = "¿Quieres eliminar este canal? Todos los miembros y mensajes se perderán."; "lng_sure_leave_group" = "¿Quieres salir del grupo?\nNo puedes deshacer esta acción."; "lng_sure_delete_group" = "¿Quieres eliminar este grupo? Todos los miembros y mensajes se perderán."; @@ -705,7 +719,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_box_remove" = "Eliminar"; -"lng_custom_stickers" = "Stickers personalizados"; +"lng_stickers_installed_tab" = "Stickers"; +"lng_stickers_featured_tab" = "Destacados"; +"lng_stickers_archived_tab" = "Archivados"; "lng_stickers_remove_pack" = "¿Quitar «{sticker_pack}»?"; "lng_stickers_add_pack" = "Añadir stickers"; "lng_stickers_share_pack" = "Compartir stickers"; @@ -715,14 +731,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_copied" = "Enlace del pack de stickers copiado al portapapeles."; "lng_stickers_default_set" = "Grandes personajes"; "lng_stickers_you_have" = "Administrar y ordenar los packs de stickers"; -"lng_stickers_packs" = "Packs de stickers"; -"lng_stickers_reorder" = "Haz clic y arrastra para ordenar los packs"; "lng_stickers_featured" = "Stickers destacados"; -"lng_stickers_clear_recent" = "Borrar"; -"lng_stickers_clear_recent_sure" = "¿Quieres borrar la lista de stickers usados frecuentemente?"; -"lng_stickers_remove" = "Eliminar"; "lng_stickers_return" = "Deshacer"; -"lng_stickers_restore" = "Restaurar"; "lng_stickers_count" = "{count:Cargando...|# sticker|# stickers}"; "lng_stickers_masks_pack" = "Este es un pack de stickers de máscaras. Puedes usarlas en el editor de fotos de nuestras aplicaciones en móviles."; @@ -744,9 +754,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_report_spam_sure_group" = "¿Quieres reportar el spam en este grupo?"; "lng_report_spam_sure_channel" = "¿Quieres reportar a este canal como spam?"; "lng_report_spam_ok" = "Reportar"; -"lng_cant_send_to_not_contact" = "Por ahora, sólo puedes enviar\nmensajes a contactos mutuos. \n{more_info}"; -"lng_cant_invite_not_contact" = "Por ahora, sólo puedes añadir \ncontactos mutuos a grupos. \n{more_info}"; -"lng_cant_invite_not_contact_channel" = "Por ahora, sólo puedes añadir \ncontactos mutuos a canales. \n{more_info}"; +"lng_cant_send_to_not_contact" = "Lo sentimos, por ahora sólo puedes \nenviar mensajes a contactos mutuos. \n{more_info}"; +"lng_cant_invite_not_contact" = "Lo sentimos, por ahora sólo puedes \nañadir contactos mutuos a grupos. \n{more_info}"; +"lng_cant_send_to_not_contact_flood" = "Has contactado a muchos usuarios que no son tus contactos hoy. Por favor, reinténtalo mañana. Por hoy podrás responderles si te escriben primero."; +"lng_cant_invite_not_contact_flood" = "No puedes añadir a este usuario porque has contactado a muchos que no son tus contactos hoy. Puedes pedir a otro miembro que añada a este usuario al grupo. "; "lng_cant_more_info" = "Más información »"; "lng_cant_invite_banned" = "Sólo el administrador puede añadir a este usuario."; "lng_cant_invite_privacy" = "No puedes añadir a este usuario a grupos por sus ajustes de privacidad."; @@ -774,6 +785,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_dialogs_text_from_wrapped" = "{from}:"; "lng_dialogs_text_media" = "{media_part} {caption}"; "lng_dialogs_text_media_wrapped" = "{media},"; +"lng_dialogs_show_all_chats" = "Mostrar todos los chats"; +"lng_dialogs_hide_muted_chats" = "Ocultar chats silenciados"; "lng_open_this_link" = "¿Abrir este enlace?"; "lng_open_link" = "Abrir"; @@ -828,12 +841,19 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_duration_and_size" = "{duration}, {size}"; "lng_duration_played" = "{played} / {duration}"; "lng_date_and_duration" = "{date}, {duration}"; -"lng_choose_images" = "Elegir imágenes"; +"lng_choose_image" = "Elegir una imagen"; +"lng_choose_files" = "Elegir archivos"; "lng_game_tag" = "Juego"; "lng_context_view_profile" = "Ver información"; "lng_context_view_group" = "Ver información"; "lng_context_view_channel" = "Ver información"; +"lng_context_pin_to_top" = "Anclar"; +"lng_context_unpin_from_top" = "Desanclar"; + +"lng_context_promote_admin" = "Nombrar administrador"; +"lng_context_remove_admin" = "Eliminar de los administradores"; +"lng_context_remove_from_group" = "Eliminar del grupo"; "lng_context_copy_link" = "Copiar enlace"; "lng_context_copy_post_link" = "Copiar enlace de la publicación"; @@ -871,14 +891,19 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_context_copy_selected" = "Copiar el texto seleccionado"; "lng_context_forward_selected" = "Reenviar lo seleccionado"; "lng_context_delete_selected" = "Eliminar lo seleccionado"; -"lng_context_clear_selection" = "Borrar selección"; +"lng_context_clear_selection" = "Eliminar selección"; "lng_really_send_image" = "¿Quieres enviar esta imagen?"; "lng_really_send_file" = "¿Quieres enviar este archivo?"; "lng_really_share_contact" = "¿Quieres compartir este contacto?"; -"lng_send_image_compressed" = "Enviar imagen comprimida"; -"lng_send_image_empty" = "No se pudo enviar un archivo vacío :("; -"lng_send_image_too_large" = "No se pudo enviar el archivo, porque es más grande que 1.5 GB :("; +"lng_send_images_compress" = "Comprimir {count:_not_used_|imagen|imágenes}"; +"lng_send_image_non_local" = "No se pudo enviar el archivo remoto: {name}"; +"lng_send_image_empty" = "No se pudo enviar el archivo vacío: {name}"; +"lng_send_image_too_large" = "No se pudo enviar el archivo porque supera los 1500 MB: {name}"; "lng_send_folder" = "No se pudo enviar «{name}», porque es un directorio :("; +"lng_send_images_selected" = "{count:_not_used_|# imagen seleccionada|# imágenes seleccionadas}"; +"lng_send_photos" = "Enviar {count:_not_used_|# foto|# fotos}"; +"lng_send_files_selected" = "{count:_not_used_|# archivo seleccionado|# archivos seleccionados}"; +"lng_send_files" = "Enviar {count:_not_used_|# archivo|# archivos}"; "lng_forward_choose" = "Elige un destinatario..."; "lng_forward_cant" = "Lo sentimos, no puedes reenviar aquí :("; @@ -940,11 +965,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_selected_delete_sure_this" = "¿Quieres eliminar este mensaje?"; "lng_selected_delete_sure" = "¿Quieres eliminar {count:_not_used_|# mensaje|# mensajes}?"; "lng_delete_photo_sure" = "¿Quieres eliminar esta foto?"; +"lng_delete_for_everyone_hint" = "Esto {count:_not_used_|lo|los} eliminará para todos en este chat."; +"lng_delete_for_me_chat_hint" = "Esto {count:_not_used_|lo|los} eliminará sólo para ti, y no para los otros participantes del chat."; +"lng_delete_for_me_hint" = "Esto {count:_not_used_|lo|los} eliminará sólo para ti."; +"lng_delete_for_everyone_check" = "Eliminar para todos"; +"lng_delete_for_other_check" = "Eliminar para {user}"; "lng_box_delete" = "Eliminar"; "lng_box_leave" = "Salir"; "lng_about_version" = "versión {version}"; -"lng_about_text_1" = "Aplicación oficial y gratuita basada en la [a href=\"https://core.telegram.org/api\"]API de Telegram[/a]\npara su velocidad y seguridad."; +"lng_about_text_1" = "Aplicación oficial y gratuita basada en la [a href=\"https://core.telegram.org/api\"]API de Telegram[/a] para su velocidad y seguridad."; "lng_about_text_2" = "Este software está licenciado bajo [a href=\"https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\"]GNU GPL[/a] versión 3.\nCódigo fuente disponible en [a href=\"https://github.com/telegramdesktop/tdesktop\"]GitHub[/a]."; "lng_about_text_3" = "Conoce más en las {faq_open}preguntas frecuentes de Telegram{faq_close}."; "lng_about_done" = "Hecho"; @@ -972,7 +1002,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_mediaview_saved" = "La imagen fue guardada en tu carpeta [c]Descargas[/c]"; -"lng_new_authorization" = "{name}:\nDetectamos un inicio de sesión en tu cuenta desde un nuevo dispositivo el {day}, {date} a las {time}\n\nDispositivo: {device}\nUbicación: {location}\n\nSi no eras tú, puedes ir a Ajustes > Mostrar todas las sesiones y cerrar esa sesión.\n\nSi crees que alguien inició tu sesión sin tu consentimiento, puedes activar la verificación en dos pasos en Ajustes.\n\nGracias.\nEl equipo de Telegram"; +"lng_theme_preview_title" = "Vista previa del tema"; +"lng_theme_preview_generating" = "Generando la vista previa del tema..."; +"lng_theme_preview_invalid" = "Hay datos inválidos en este archivo de tema."; +"lng_theme_preview_apply" = "Aplicar este tema"; + +"lng_new_authorization" = "{name}:\nDetectamos un inicio de sesión en tu cuenta desde un nuevo dispositivo el {day}, {date} a las {time}\n\nDispositivo: {device}\nUbicación: {location}\n\nSi no eras tú, puedes ir a Ajustes > Mostrar todas las sesiones, y cerrar esa sesión.\n\nSi crees que alguien inició tu sesión sin tu consentimiento, puedes activar la verificación en dos pasos en Ajustes.\n\nAtentamente,\nEl equipo de Telegram"; "lng_new_version_wrap" = "Telegram Desktop ha sido actualizada a la versión {version}\n\n{changes}\n\nEl historial completo está disponible aquí:\n{link}"; "lng_new_version_minor" = "— Corrección de errores y otras mejoras menores"; diff --git a/Telegram/Resources/langs/lang_it.strings b/Telegram/Resources/langs/lang_it.strings index 640b9b98f..96ddc1613 100644 --- a/Telegram/Resources/langs/lang_it.strings +++ b/Telegram/Resources/langs/lang_it.strings @@ -33,7 +33,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_open_from_tray" = "Apri Telegram"; "lng_minimize_to_tray" = "Riduci a icona"; "lng_quit_from_tray" = "Chiudi Telegram"; -"lng_tray_icon_text" = "Telegram è ancora attivo qui,\npuoi cambiarlo nelle impostazioni.\nSe l'icona scompare dall'area di notifica,\npuoi trascinarla qui dalle icone nascoste."; +"lng_tray_icon_text" = "Telegram è ancora attivo qui,\npuoi cambiarlo nelle impostazioni.\nSe l'icona di notifica scompare,\npuoi ripristinarla dalle icone nascoste."; "lng_month1" = "Gennaio"; "lng_month2" = "Febbraio"; @@ -91,7 +91,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_reconnecting_try_now" = "Prova ora"; "lng_status_service_notifications" = "notifiche di servizio"; -"lng_status_support" = "supporto"; +"lng_status_support" = "assistenza"; "lng_status_bot" = "bot"; "lng_status_bot_reads_all" = "ha accesso ai messaggi"; "lng_status_bot_not_reads_all" = "non ha accesso ai messaggi"; @@ -110,7 +110,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_status_online" = "in linea"; "lng_status_connecting" = "connetto..."; -"lng_chat_status_unaccessible" = "gruppo non accessibile"; +"lng_chat_status_unaccessible" = "gruppo inaccessibile"; "lng_chat_status_members" = "{count:nessun membro|# membro|# membri}"; "lng_chat_status_members_online" = "{count:_not_used_|# membro|# membri}, {count_online:_not_used_|# in linea|# in linea}"; @@ -125,6 +125,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_gif_error" = "C'è stato un errore nel leggere la GIF :("; "lng_edit_error" = "Non puoi modificare questo messaggio"; "lng_join_channel_error" = "Spiacenti, ti sei unito a troppi canali e supergruppi. Per favore lasciane qualcuno prima di unirti."; +"lng_error_phone_flood" = "Spiacenti, hai eliminato e ricreato il tuo account troppe volte di recente. Per favore attendi alcuni giorni prima di iscriverti di nuovo."; +"lng_error_start_minimized_passcoded" = "Hai inserito un codice di blocco, quindi l'app non può essere avviata minimizzata. L'app ti chiederà il codice prima di potersi avviare."; +"lng_error_pinned_max" = "Spiacenti, non puoi fissare in alto più di {count:_not_used_|# chat|# chat}."; + "lng_edit_deleted" = "Questo messaggio è stato eliminato"; "lng_edit_too_long" = "Il tuo messaggio è troppo lungo"; "lng_edit_message" = "Modifica messaggio"; @@ -135,22 +139,23 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_pinned_unpin_sure" = "Vuoi togliere questo messaggio?"; "lng_pinned_pin_sure" = "Vuoi fissare questo messaggio?"; "lng_pinned_pin" = "Fissa"; -"lng_pinned_unpin" = "Stacca"; +"lng_pinned_unpin" = "Togli"; "lng_pinned_notify" = "Notifica tutti i membri"; -"lng_intro" = "Benvenuti nell'app desktop ufficiale di [a href=\"https://telegram.org/\"]Telegram[/a].\nÈ [b]veloce[/b] e [b]sicura[/b]."; -"lng_start_msgs" = "INIZIA A CHATTARE"; +"lng_intro_about" = "Benvenuti nell'app desktop ufficiale Telegram Desktop.\nÈ veloce e sicura."; +"lng_start_msgs" = "INIZIA A MESSAGGIARE"; "lng_intro_next" = "AVANTI"; "lng_intro_finish" = "ISCRIVITI"; "lng_intro_submit" = "INVIA"; "lng_photo_caption" = "Didascalia"; +"lng_photos_comment" = "Commento"; "lng_phone_ph" = "Il tuo numero di telefono"; -"lng_phone_title" = "Tuo telefono"; -"lng_phone_desc" = "Per favore conferma il tuo prefisso nazionale e\ninserisci il tuo numero di telefono."; -"lng_phone_notreg" = "Nota: se non hai ancora un account Telegram,\nper favore [b]registrati[/b] con il tuo dispositivo [a href=\"https://telegram.org/\"]iOS / Android[/a] o {signup_start}qui »{signup_end}"; +"lng_phone_title" = "Il tuo telefono"; +"lng_phone_desc" = "Per favore conferma il tuo prefisso internazionale e\ninserisci il tuo numero di telefono."; +"lng_phone_notreg" = "Se non hai ancora un account Telegram,\nper favore [b]registrati[/b] con {link_start}Android / iPhone{link_end} o {signup_start}qui{signup_end}"; "lng_country_code" = "Prefisso nazionale"; "lng_bad_country_code" = "Prefisso nazionale non valido"; "lng_country_ph" = "Cerca"; @@ -160,7 +165,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_code_ph" = "Codice"; "lng_code_desc" = "Abbiamo inviato un messaggio col codice\ndi attivazione al tuo telefono. Inseriscilo qui"; -"lng_code_telegram" = "Per favore inserisci il codice che hai\nappena ricevuto nell'altra app di [b]Telegram[/b]."; +"lng_code_telegram" = "Per favore inserisci il codice che hai appena\nricevuto nell'altra app di [b]Telegram[/b]."; "lng_code_no_telegram" = "Invia codice tramite SMS"; "lng_code_call" = "Telegram ti chiamerà tra {minutes}:{seconds}"; "lng_code_calling" = "Richiedo una telefonata da Telegram..."; @@ -174,7 +179,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_title" = "Verifica password"; "lng_signin_desc" = "Per favore inserisci la tua password."; -"lng_signin_recover_desc" = "Per favore inserisci il codice dall'e-mail."; +"lng_signin_recover_desc" = "Per favore inserisci il codice dall'e-mail\n{email}"; "lng_signin_password" = "La tua password"; "lng_signin_code" = "Codice dall'e-mail"; "lng_signin_recover" = "Password dimenticata?"; @@ -196,7 +201,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_reset_in_minutes" = "{count_minutes:0 minuti|# minuto|# minuti}"; "lng_signin_reset_cancelled" = "I tuoi tentativi recenti di ripristinare questo account sono stati annullati dal suo utente attivo. Per favore riprova tra 7 giorni."; -"lng_signup_title" = "Info e foto"; +"lng_signup_title" = "Le tue info"; "lng_signup_desc" = "Inserisci il tuo nome e\ncarica una foto."; "lng_signup_firstname" = "Nome"; @@ -212,6 +217,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_dlg_search_chat" = "Cerca in questa chat"; "lng_dlg_search_channel" = "Cerca in questo canale"; "lng_dlg_search_for_messages" = "Cerca messaggi"; +"lng_update_telegram" = "Aggiorna Telegram"; "lng_settings_save" = "Salva"; "lng_settings_upload" = "Imposta foto profilo"; @@ -254,13 +260,14 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_notification_preview" = "Hai un nuovo messaggio"; "lng_notification_reply" = "Rispondi"; "lng_notification_hide_all" = "Nascondi tutte"; -"lng_notification_sample" = "Questa è un esempio di notifica"; +"lng_notification_sample" = "Questa è una notifica di esempio"; "lng_settings_section_general" = "Generali"; "lng_settings_change_lang" = "Cambia lingua"; "lng_languages" = "Lingue"; "lng_sure_save_language" = "Telegram si riavvierà per cambiare lingua"; -"lng_settings_update_automatically" = "Aggiorna automaticamente (ver. {version})"; +"lng_settings_update_automatically" = "Aggiorna automaticamente"; +"lng_settings_current_version_label" = "Versione {version}:"; "lng_settings_current_version" = "Versione {version}"; "lng_settings_check_now" = "Cerca aggiornamenti"; "lng_settings_update_checking" = "Cerco aggiornamenti..."; @@ -273,7 +280,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_settings_workmode_window" = "Mostra icona nella barra applicazioni"; "lng_settings_auto_start" = "Avvia Telegram all'avvio del sistema"; "lng_settings_start_min" = "Avvia ridotto a icona"; -"lng_settings_add_sendto" = "Inserisci Telegram nel menu «Invia a»"; +"lng_settings_add_sendto" = "Inserisci Telegram nel menù «Invia a»"; "lng_settings_section_scale" = "Scala interfaccia"; "lng_settings_scale_auto" = "Auto ({cur})"; @@ -286,12 +293,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_settings_send_cmdenter" = "Invia con Cmd+Invio"; "lng_settings_section_background" = "Sfondo chat"; +"lng_settings_bg_use_default" = "Usa tema predefinito"; "lng_settings_bg_from_gallery" = "Scegli dalla galleria"; "lng_settings_bg_from_file" = "Scegli da file"; "lng_settings_bg_tile" = "Affianca sfondo"; "lng_settings_adaptive_wide" = "Layout adattivo per grandi schermi"; "lng_backgrounds_header" = "Scegli un nuovo sfondo per la chat"; +"lng_theme_sure_keep" = "Mantenere questo tema?"; +"lng_theme_reverting" = "Ripristino il vecchio tema tra {count:_not_used_|# secondo|# secondi}."; +"lng_theme_keep_changes" = "Mantieni modifiche"; +"lng_theme_revert" = "Ripristina"; "lng_download_path_dont_ask" = "Non chiedere il percorso di download per ogni file"; "lng_download_path_label" = "Percorso di download:"; @@ -326,10 +338,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_passcode_remove_button" = "Rimuovi"; -"lng_passcode_turn_on" = "Attiva codice"; -"lng_passcode_change" = "Cambia codice"; -"lng_passcode_create" = "Codice locale"; -"lng_passcode_remove" = "Rimuovi codice"; +"lng_passcode_turn_on" = "Attiva codice di blocco"; +"lng_passcode_change" = "Cambia codice di blocco"; +"lng_passcode_create" = "Codice di blocco"; +"lng_passcode_remove" = "Rimuovi codice di blocco"; "lng_passcode_turn_off" = "Disattiva"; "lng_passcode_autolock" = "Blocco automatico"; "lng_passcode_autolock_away" = "Blocco automatico se lontano per:"; @@ -340,11 +352,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_passcode_enter_first" = "Inserisci un codice"; "lng_passcode_enter_new" = "Inserisci il nuovo codice"; "lng_passcode_confirm_new" = "Reinserisci il nuovo codice"; -"lng_passcode_about" = "Quando imposti un codice, un'icona col lucchetto apparirà nel menu in alto. Premi su di essa per bloccare l'app.\n\nNota: se dimentichi il codice, dovrai rifare l'accesso su Telegram Desktop."; +"lng_passcode_about" = "Quando un codice è attivo, appare un'icona col lucchetto sopra la lista delle chat. Premi su di essa per bloccare l'app.\n\nNota: se dimentichi il codice, dovrai accedere di nuovo su Telegram Desktop."; "lng_passcode_differ" = "I codici sono diversi"; "lng_passcode_wrong" = "Codice errato"; "lng_passcode_is_same" = "Il codice non è stato cambiato"; -"lng_passcode_enter" = "Inserisci il tuo codice"; +"lng_passcode_enter" = "Inserisci il tuo codice di blocco"; +"lng_passcode_ph" = "Il tuo codice"; "lng_passcode_submit" = "Invia"; "lng_passcode_logout" = "Esci"; "lng_passcode_need_unblock" = "Devi prima sbloccarmi."; @@ -398,7 +411,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_settings_reset_done" = "Altre sessioni terminate"; "lng_settings_manage_local_storage" = "Gestisci archivio locale"; "lng_settings_ask_question" = "Fai una domanda"; -"lng_settings_ask_sure" = "Per favore nota che il supporto di Telegram è fornito da volontari. Proviamo a rispondere quanto prima, ma potrebbe volerci del tempo.\n\nDai un'occhiata alle domande frequenti di Telegram: contengono suggerimenti importanti per risolvere i problemi e risposte a quasi tutte le domande."; +"lng_settings_ask_sure" = "Per favore nota che l'assistenza di Telegram è fornita da volontari. Proviamo a rispondere quanto prima, ma potrebbe volerci del tempo.\n\nDai un'occhiata alle domande frequenti di Telegram: contengono suggerimenti importanti per risolvere i problemi e risposte a quasi tutte le domande."; "lng_settings_faq_button" = "Domande frequenti"; "lng_settings_ask_ok" = "Chiedi"; "lng_settings_faq" = "Domande frequenti"; @@ -417,7 +430,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_preview_loading" = "Recupero le info del link..."; -"lng_profile_chat_unaccessible" = "Gruppo non accessibile"; +"lng_profile_chat_unaccessible" = "Gruppo inaccessibile"; "lng_profile_about_section" = "Info"; "lng_profile_description_section" = "Descrizione"; "lng_profile_settings_section" = "Impostazioni"; @@ -428,9 +441,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_create_public_link" = "Crea link pubblico"; "lng_profile_edit_public_link" = "Modifica link pubblico"; "lng_profile_manage_admins" = "Gestisci amministratori"; +"lng_profile_common_groups" = "{count:_not_used_|# gruppo|# gruppi} in comune"; +"lng_profile_common_groups_section" = "Gruppi in comune"; "lng_profile_participants_section" = "Membri"; "lng_profile_info_section" = "Info"; -"lng_profile_mobile_number" = "Numero di telefono:"; +"lng_profile_mobile_number" = "Telefono:"; "lng_profile_username" = "Username:"; "lng_profile_link" = "Link:"; "lng_profile_add_contact" = "Aggiungi contatto"; @@ -467,17 +482,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_shared_media" = "Media condivisi"; "lng_profile_no_media" = "Nessun media in questa chat."; "lng_profile_photos" = "{count:_not_used_|# foto|# foto}"; -"lng_profile_photos_header" = "Panoramica foto"; -"lng_profile_videos" = "{count:_not_used_|# file video|# file video}"; -"lng_profile_videos_header" = "Panoramica video"; +"lng_profile_photos_header" = "Foto"; +"lng_profile_videos" = "{count:_not_used_|# video|# video}"; +"lng_profile_videos_header" = "Video"; "lng_profile_songs" = "{count:_not_used_|# file audio|# file audio}"; -"lng_profile_songs_header" = "Panoramica file audio"; +"lng_profile_songs_header" = "File audio"; "lng_profile_files" = "{count:_not_used_|# file|# file}"; -"lng_profile_files_header" = "Panoramica file"; +"lng_profile_files_header" = "File"; "lng_profile_audios" = "{count:_not_used_|# messaggio vocale|# messaggi vocali}"; -"lng_profile_audios_header" = "Panoramica messaggi vocali"; +"lng_profile_audios_header" = "Messaggi vocali"; "lng_profile_shared_links" = "{count:_not_used_|# link condiviso|# link condivisi}"; -"lng_profile_shared_links_header" = "Panoramica link condivisi"; +"lng_profile_shared_links_header" = "Link condivisi"; "lng_profile_copy_phone" = "Copia numero di telefono"; "lng_profile_copy_fullname" = "Copia nome"; "lng_profile_drop_area_title" = "Trascina la tua immagine qui"; @@ -487,6 +502,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_report_title" = "Segnala canale"; "lng_report_group_title" = "Segnala gruppo"; +"lng_report_bot_title" = "Segnala bot"; "lng_report_reason_spam" = "Spam"; "lng_report_reason_violence" = "Violenza"; "lng_report_reason_pornography" = "Pornografia"; @@ -515,9 +531,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_create_group_next" = "Avanti"; "lng_create_group_create" = "Crea"; "lng_create_group_title" = "Nuovo gruppo"; -"lng_create_group_about" = "I gruppi sono ideali per le community limitate,\npossono avere fino a {count:_not_used|# membro|# membri}"; "lng_create_channel_title" = "Nuovo canale"; -"lng_create_channel_about" = "I canali sono uno strumento per diffondere i tuoi messaggi a un pubblico illimitato"; "lng_create_public_channel_title" = "Canale pubblico"; "lng_create_public_channel_about" = "Chiunque può cercare il canale nella ricerca e unirsi"; "lng_create_private_channel_title" = "Canale privato"; @@ -642,8 +656,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_forwarded" = "Inoltrato da {user}"; "lng_forwarded_channel" = "Inoltrato da {channel}"; -"lng_forwarded_via" = "Inoltrato da {user} tramite {inline_bot}"; -"lng_forwarded_channel_via" = "Inoltrato da {channel} tramite {inline_bot}"; +"lng_forwarded_via" = "Inoltrato da {user} via {inline_bot}"; +"lng_forwarded_channel_via" = "Inoltrato da {channel} via {inline_bot}"; "lng_forwarded_signed" = "{channel} ({user})"; "lng_in_reply_to" = "In risposta a"; "lng_edited" = "modificato"; @@ -701,11 +715,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_saved_gifs" = "GIF salvate"; "lng_inline_bot_results" = "Risultati da {inline_bot}"; "lng_inline_bot_no_results" = "Nessun risultato"; -"lng_inline_bot_via" = "tramite {inline_bot}"; +"lng_inline_bot_via" = "via {inline_bot}"; "lng_box_remove" = "Rimuovi"; -"lng_custom_stickers" = "Sticker personalizzati"; +"lng_stickers_installed_tab" = "Sticker"; +"lng_stickers_featured_tab" = "In primo piano"; +"lng_stickers_archived_tab" = "Archiviati"; "lng_stickers_remove_pack" = "Rimuovere «{sticker_pack}»?"; "lng_stickers_add_pack" = "Aggiungi sticker"; "lng_stickers_share_pack" = "Condividi sticker"; @@ -715,19 +731,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_copied" = "Link degli sticker copiato negli appunti."; "lng_stickers_default_set" = "Grandi menti"; "lng_stickers_you_have" = "Organizza e riordina i set di sticker"; -"lng_stickers_packs" = "Set di sticker"; -"lng_stickers_reorder" = "Clicca e trascina per riordinare i set di sticker"; "lng_stickers_featured" = "Sticker in primo piano"; -"lng_stickers_clear_recent" = "Cancella"; -"lng_stickers_clear_recent_sure" = "Sicuro di voler cancellare la lista degli sticker usati di recente?"; -"lng_stickers_remove" = "Elimina"; "lng_stickers_return" = "Annulla"; -"lng_stickers_restore" = "Ripristina"; "lng_stickers_count" = "{count:Carico...|# sticker|# sticker}"; "lng_stickers_masks_pack" = "Questo è un set di maschere. Puoi usarle nell'editor fotografico sulle nostre app mobili."; "lng_in_dlg_photo" = "Foto"; -"lng_in_dlg_video" = "File video"; +"lng_in_dlg_video" = "Video"; "lng_in_dlg_audio_file" = "File audio"; "lng_in_dlg_contact" = "Contatto"; "lng_in_dlg_audio" = "Messaggio vocale"; @@ -745,8 +755,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_report_spam_sure_channel" = "Sicuro di voler segnalare dello spam in questo canale?"; "lng_report_spam_ok" = "Segnala"; "lng_cant_send_to_not_contact" = "Spiacenti, al momento puoi scrivere\nsolo ai contatti reciproci.\n{more_info}"; -"lng_cant_invite_not_contact" = "Spiacenti, al momento puoi aggiungere\nai gruppi solo i contatti reciproci.\n{more_info}"; -"lng_cant_invite_not_contact_channel" = "Spiacenti, al momento puoi aggiungere\nai canali solo i contatti reciproci.\n{more_info}"; +"lng_cant_invite_not_contact" = "Spiacenti, al momento puoi aggiungere ai gruppi\nsolo i contatti reciproci.\n{more_info}"; +"lng_cant_send_to_not_contact_flood" = "Oggi hai scritto a troppi non-contatti, per favore riprova domani. Potrai rispondere oggi se questo utente ti scrive per primo."; +"lng_cant_invite_not_contact_flood" = "Non puoi aggiungere questo utente perché hai scritto a troppi non-contatti oggi. Per favore riprova domani. Puoi chiedere a un altro membro di aggiungere questo utente al gruppo. "; "lng_cant_more_info" = "Maggiori info »"; "lng_cant_invite_banned" = "Spiacenti, solo l'amministratore può aggiungere questo utente."; "lng_cant_invite_privacy" = "Spiacenti, non puoi aggiungere questo utente ai gruppi a causa delle sue impostazioni di privacy."; @@ -761,7 +772,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_will_be_notified" = "I post saranno notificati ai membri"; "lng_wont_be_notified" = "I post non saranno notificati ai membri"; "lng_empty_history" = ""; -"lng_willbe_history" = "Seleziona una chat per iniziare a chattare"; +"lng_willbe_history" = "Seleziona una chat per iniziare a messaggiare"; "lng_from_you" = "Tu"; "lng_from_draft" = "Bozza"; "lng_bot_description" = "Cosa può fare questo bot?"; @@ -774,6 +785,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_dialogs_text_from_wrapped" = "{from}:"; "lng_dialogs_text_media" = "{media_part} {caption}"; "lng_dialogs_text_media_wrapped" = "{media},"; +"lng_dialogs_show_all_chats" = "Mostra tutte le chat"; +"lng_dialogs_hide_muted_chats" = "Nascondi chat silenziate"; "lng_open_this_link" = "Aprire questo link?"; "lng_open_link" = "Apri"; @@ -820,7 +833,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_maps_point" = "Posizione"; "lng_save_photo" = "Salva immagine"; -"lng_save_video" = "Salva file video"; +"lng_save_video" = "Salva video"; "lng_save_audio_file" = "Salva file audio"; "lng_save_audio" = "Salva messaggio vocale"; "lng_save_file" = "Salva file"; @@ -828,12 +841,19 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_duration_and_size" = "{duration}, {size}"; "lng_duration_played" = "{played} / {duration}"; "lng_date_and_duration" = "{date}, {duration}"; -"lng_choose_images" = "Scegli immagini"; +"lng_choose_image" = "Scegli un'immagine"; +"lng_choose_files" = "Scegli file"; "lng_game_tag" = "Gioco"; "lng_context_view_profile" = "Visualizza profilo"; "lng_context_view_group" = "Visualizza info gruppo"; "lng_context_view_channel" = "Visualizza info canale"; +"lng_context_pin_to_top" = "Fissa in alto"; +"lng_context_unpin_from_top" = "Togli dall'alto"; + +"lng_context_promote_admin" = "Rendi amministratore"; +"lng_context_remove_admin" = "Rimuovi dagli amministratori"; +"lng_context_remove_from_group" = "Rimuovi dal gruppo"; "lng_context_copy_link" = "Copia link"; "lng_context_copy_post_link" = "Copia link post"; @@ -848,7 +868,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_context_cancel_download" = "Annulla download"; "lng_context_show_in_folder" = "Mostra nella cartella"; "lng_context_show_in_finder" = "Mostra nel Finder"; -"lng_context_save_video" = "Salva file video come..."; +"lng_context_save_video" = "Salva video come..."; "lng_context_save_audio_file" = "Salva file audio come..."; "lng_context_save_audio" = "Salva messaggio vocale come..."; "lng_context_pack_info" = "Mostra sticker"; @@ -875,10 +895,15 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_really_send_image" = "Vuoi inviare questa immagine?"; "lng_really_send_file" = "Vuoi inviare questo file?"; "lng_really_share_contact" = "Vuoi condividere questo contatto?"; -"lng_send_image_compressed" = "Invia immagine compressa"; -"lng_send_image_empty" = "Impossibile inviare un file vuoto :("; -"lng_send_image_too_large" = "Impossibile inviare il file, perchè è più grande di 1.5 GB :("; -"lng_send_folder" = "Impossibile inviare «{name}» perchè è una cartella :("; +"lng_send_images_compress" = "Comprimi {count:_not_used_|immagine|immagini}"; +"lng_send_image_non_local" = "Impossibile inviare un file non locale: {name}"; +"lng_send_image_empty" = "Impossibile inviare un file vuoto: {name}"; +"lng_send_image_too_large" = "Impossibile inviare il file, perché è più grande di 1500 MB: {name}"; +"lng_send_folder" = "Impossibile inviare «{name}» perché è una cartella :("; +"lng_send_images_selected" = "{count:_not_used_|# immagine selezionata|# immagini selezionate}"; +"lng_send_photos" = "Invia {count:_not_used_|# foto|# foto}"; +"lng_send_files_selected" = "{count:_not_used_|# file selezionato|# file selezionati}"; +"lng_send_files" = "Invia {count:_not_used_|# file|# file}"; "lng_forward_choose" = "Scegli destinatario..."; "lng_forward_cant" = "Spiacenti, impossibile inoltrare qui :("; @@ -940,6 +965,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_selected_delete_sure_this" = "Vuoi eliminare questo messaggio?"; "lng_selected_delete_sure" = "Vuoi eliminare {count:_not_used_|# messaggio|# messaggi}?"; "lng_delete_photo_sure" = "Vuoi eliminare questa foto?"; +"lng_delete_for_everyone_hint" = "Questo {count:_not_used_|lo eliminerà|li eliminerà} per chiunque in questa chat."; +"lng_delete_for_me_chat_hint" = "Questo {count:_not_used_|lo eliminerà|li eliminerà} solo per te, non per gli altri membri della chat."; +"lng_delete_for_me_hint" = "Questo {count:_not_used_|lo eliminerà|li eliminerà} solo per te."; +"lng_delete_for_everyone_check" = "Elimina per tutti"; +"lng_delete_for_other_check" = "Elimina per {user}"; "lng_box_delete" = "Elimina"; "lng_box_leave" = "Lascia"; @@ -972,7 +1002,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_mediaview_saved" = "L'immagine è stata salvata nella tua cartella [c]Download[/c]"; -"lng_new_authorization" = "{name},\nAbbiamo rilevato un accesso al tuo account da un nuovo dispositivo il {day}, {date} alle {time}\n\nDispositivo: {device}\nPosizione: {location}\n\nSe non sei tu, puoi andare su Impostazioni – Mostra tutte le sessioni e terminare quella sessione.\n\nSe credi che qualcuno si sia collegato al tuo account contro il tuo volere, puoi attivare la verifica in due passaggi nelle Impostazioni. \n\nGrazie, \nIl Team di Telegram"; +"lng_theme_preview_title" = "Anteprima tema"; +"lng_theme_preview_generating" = "Genero anteprima tema..."; +"lng_theme_preview_invalid" = "Dati non validi in questo file tema."; +"lng_theme_preview_apply" = "Applica questo tema"; + +"lng_new_authorization" = "{name},\nAbbiamo rilevato un accesso al tuo account da un nuovo dispositivo {day}, {date} alle {time}\n\nDispositivo: {device}\nPosizione: {location}\n\nSe non sei tu, puoi andare nelle Impostazioni – Mostra tutte le sessioni e terminare quella sessione.\n\nSe credi che qualcuno si sia collegato al tuo account contro il tuo volere, puoi attivare la verifica in due passaggi nelle Impostazioni. \n\nSinceramente, \nIl Team di Telegram"; "lng_new_version_wrap" = "Telegram Desktop si è aggiornato alla versione {version}\n\n{changes}\n\nLa cronologia degli aggiornamenti è disponibile qui:\n{link}"; "lng_new_version_minor" = "— Risoluzione di problemi e altri miglioramenti minori"; diff --git a/Telegram/Resources/langs/lang_ko.strings b/Telegram/Resources/langs/lang_ko.strings index 5ba18317e..83b890e8b 100644 --- a/Telegram/Resources/langs/lang_ko.strings +++ b/Telegram/Resources/langs/lang_ko.strings @@ -110,7 +110,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_status_online" = "온라인"; "lng_status_connecting" = "연결중..."; -"lng_chat_status_unaccessible" = "그룹 접근 불가"; +"lng_chat_status_unaccessible" = "그룹에 접근할 수 없습니다."; "lng_chat_status_members" = "{count:맴버 없음|#명|#명}"; "lng_chat_status_members_online" = "{count:_not_used_|#명|#명}중 {count_online:_not_used_|#명 접속중|#명 접속중}"; @@ -125,6 +125,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_gif_error" = "GIF 애니메이션을 읽는 동안 에러가 발생하였습니다."; "lng_edit_error" = "메시지를 수정 할 수 없습니다."; "lng_join_channel_error" = "너무 많은 채널과 슈퍼그룹에 참여하였습니다.\n기존 대화방을 나가주셔야 참여가 가능합니다."; +"lng_error_phone_flood" = "죄송합니다, 너무 많이 계정 재가입이 최근에 이루어졌습니다. 다시 재가입까지 몇일 기다려주시기 바랍니다."; +"lng_error_start_minimized_passcoded" = "잠금코드를 설정하였기 때문에 앱이 최소화된 상태에서 시작할 수 없습니다. 앱이 시작하기 전에 잠금코드 입력이 필요합니다."; +"lng_error_pinned_max" = "죄송합니다, {count:_not_used_|# 대화|# 대화} 이상은 맨위 고정을 할 수 없습니다."; + "lng_edit_deleted" = "메시지는 삭제 되었습니다."; "lng_edit_too_long" = "메시지 길이가 너무 깁니다."; "lng_edit_message" = "메시지 수정"; @@ -138,7 +142,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_pinned_unpin" = "고정해제"; "lng_pinned_notify" = "모두알림"; -"lng_intro" = "[a href=\"https://telegram.org/\"]텔레그램[/a] PC 공식버전에 오신 것을 환영합니다.\n[b]안전[/b]하고 [b]신속[/b]합니다."; +"lng_intro_about" = "PC 공식버전에 오신 것을 환영합니다. \n안전하고 신속합니다."; "lng_start_msgs" = "시작하기"; "lng_intro_next" = "다음"; @@ -146,11 +150,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_intro_submit" = "제출"; "lng_photo_caption" = "추가설명"; +"lng_photos_comment" = "코멘트"; "lng_phone_ph" = "휴대폰 번호"; "lng_phone_title" = "전화번호"; "lng_phone_desc" = "국가번호와 전화번호를 입력하세요. \n(대한민국 국가번호: 82)"; -"lng_phone_notreg" = "확인사항: 아직 텔레그램 계정이 없으실 경우 [a href=\"https://telegram.org/\"]iOS / 안드로이드[/a] 기기로 [b]회원가입[/b]을 해주시거나 {signup_start}여기 »{signup_end} 에서 회원가입을 해주세요."; +"lng_phone_notreg" = "아직 텔레그램 계정이 없으실 경우, \n{link_start}안드로이드 / 아이폰{link_end}로 [b]회원가입[/b]을 해주시거나 {signup_start}여기{signup_end} 에서 회원가입을 해주세요."; "lng_country_code" = "국가번호"; "lng_bad_country_code" = "올바른 국가번호가 아닙니다."; "lng_country_ph" = "검색"; @@ -160,7 +165,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_code_ph" = "코드번호"; "lng_code_desc" = "인증코드 메세지를 휴대폰으로 전송하였습니다.\n인증코드를 아래에 입력하여 주세요."; -"lng_code_telegram" = "[b]텔레그램[/b] 앱으로 부터 방금 수신받은,\n코드를 입력해주세요."; +"lng_code_telegram" = "기존 [b]Telegram[/b] 앱에서\n수신받으신 코드를 입력해주세요"; "lng_code_no_telegram" = "코드를 SMS로 전송"; "lng_code_call" = "텔레그램이 {minutes}:{seconds}후에는 전화를 겁니다."; "lng_code_calling" = "텔레그램으로부터 전화 요청을 하고 있습니다..."; @@ -174,7 +179,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_title" = "클라우드 비밀번호 확인"; "lng_signin_desc" = "클라우드 비밀번호 입력"; -"lng_signin_recover_desc" = "이메일에 기입된 코드를 입력해주세요"; +"lng_signin_recover_desc" = "이메일로 부터 받은 코드를 입력해주세요\n{email}"; "lng_signin_password" = "클라우드 비밀번호"; "lng_signin_code" = "이메일 코드"; "lng_signin_recover" = "비밀번호를 잊어버리셨습니까?"; @@ -196,7 +201,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_reset_in_minutes" = "{count_minutes:0 분|# 분|# 분}"; "lng_signin_reset_cancelled" = "현재 사용중인 사용자가 요청하신 계정 초기화를 취소 하였습니다.\n7일 이후에 다시 시도해주세요."; -"lng_signup_title" = "개인정보 및 사진"; +"lng_signup_title" = "정ㅂ"; "lng_signup_desc" = "이름을 입력해주시고,\n사진을 업로드해주세요."; "lng_signup_firstname" = "이름"; @@ -212,6 +217,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_dlg_search_chat" = "이 채팅에서 검색"; "lng_dlg_search_channel" = "이 채널방에서 검색"; "lng_dlg_search_for_messages" = "메시지 검색"; +"lng_update_telegram" = "텔레그램 업데이트"; "lng_settings_save" = "저장"; "lng_settings_upload" = "프로필 이미지 선택"; @@ -260,7 +266,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_settings_change_lang" = "언어 변경"; "lng_languages" = "언어"; "lng_sure_save_language" = "언어를 변경하기 위해 텔레그램을 재시작합니다."; -"lng_settings_update_automatically" = "자동 업데이트(ver. {version})"; +"lng_settings_update_automatically" = "자동 업데이트"; +"lng_settings_current_version_label" = "버전 {version}:"; "lng_settings_current_version" = " {version}"; "lng_settings_check_now" = "업데이트 확인"; "lng_settings_update_checking" = "업데이트 확인 중..."; @@ -286,12 +293,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_settings_send_cmdenter" = "Cmd+Enter로 메시지 전송"; "lng_settings_section_background" = "대화창 배경화면"; +"lng_settings_bg_use_default" = "기존 테마 색상 사용"; "lng_settings_bg_from_gallery" = "이미지 선택"; "lng_settings_bg_from_file" = "파일 선택"; "lng_settings_bg_tile" = "타일형 배경화면"; "lng_settings_adaptive_wide" = "와이드 스크린 레이아웃 적용"; "lng_backgrounds_header" = "새로운 대화창 배경화면을 선택"; +"lng_theme_sure_keep" = "이 테마 색상을 유지하시겠습니까?"; +"lng_theme_reverting" = " {count:_not_used_|# 초|# 초} 후에 이전 테마 색상으로 원복합니다."; +"lng_theme_keep_changes" = "변화 유지"; +"lng_theme_revert" = "원복"; "lng_download_path_dont_ask" = "다운로드 경로 묻지 않기"; "lng_download_path_label" = "다운로드 경로:"; @@ -345,6 +357,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_passcode_wrong" = "잘못된 비밀번호입니다."; "lng_passcode_is_same" = "비밀번호가 변경되지 않았습니다."; "lng_passcode_enter" = "잠금코드 입력"; +"lng_passcode_ph" = "현재 잠금코드"; "lng_passcode_submit" = "입력"; "lng_passcode_logout" = "로그아웃"; "lng_passcode_need_unblock" = "잠금코드를 먼저 해제해주세요."; @@ -428,6 +441,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_create_public_link" = "공개 링크 생성"; "lng_profile_edit_public_link" = "공개링크 수정"; "lng_profile_manage_admins" = "관리자 관리"; +"lng_profile_common_groups" = "{count:_not_used_|# 그룹|# 그룹} 공통"; +"lng_profile_common_groups_section" = "공통 그룹"; "lng_profile_participants_section" = "사용자"; "lng_profile_info_section" = "정보"; "lng_profile_mobile_number" = "전화번호:"; @@ -467,17 +482,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_shared_media" = "공유된 미디어"; "lng_profile_no_media" = "대화에 미디어가 존재하지 않습니다."; "lng_profile_photos" = "{count:_not_used_|# 사진|# 사진} "; -"lng_profile_photos_header" = "사진 내역"; -"lng_profile_videos" = "{count:_not_used_|# 비디오|# 비디오}"; -"lng_profile_videos_header" = "비디오 파일 내역"; +"lng_profile_photos_header" = "사진"; +"lng_profile_videos" = "{count:_not_used_|# 동영상|# 동영상}"; +"lng_profile_videos_header" = "동영상"; "lng_profile_songs" = "{count:_not_used_|# 음성|# 음성}"; -"lng_profile_songs_header" = "음성 파일 내역"; +"lng_profile_songs_header" = "음성 파일"; "lng_profile_files" = "{count:_not_used_|# 파일|# 파일}"; -"lng_profile_files_header" = "파일 내역"; +"lng_profile_files_header" = "파일"; "lng_profile_audios" = "{count:_not_used_|# 음성메시지|# 음성메시지}"; -"lng_profile_audios_header" = "음성 메시지 내역"; +"lng_profile_audios_header" = "음성 메시지"; "lng_profile_shared_links" = "{count:_not_used_|# 공유된 링크|# 공유된 링크}"; -"lng_profile_shared_links_header" = "공유된 링크 현황"; +"lng_profile_shared_links_header" = "공유된 링크"; "lng_profile_copy_phone" = "전화번호 복사"; "lng_profile_copy_fullname" = "이름 복사"; "lng_profile_drop_area_title" = "이미지를 여기에 놓으세요."; @@ -487,6 +502,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_report_title" = "채널 신고"; "lng_report_group_title" = "그룹 신고"; +"lng_report_bot_title" = "봇 신고"; "lng_report_reason_spam" = "스팸"; "lng_report_reason_violence" = "폭력성"; "lng_report_reason_pornography" = "음란성"; @@ -515,9 +531,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_create_group_next" = "다음"; "lng_create_group_create" = "만들기"; "lng_create_group_title" = "새로운 그룹"; -"lng_create_group_about" = "그룹방은 제한된 커뮤니티에 적합하며,\n{count:_not_used|# member|# members} 명까지 구성이 가능합니다."; "lng_create_channel_title" = "새로운 채널"; -"lng_create_channel_about" = "채널은 제한이 없는 구성원들에게 메시지를 전달하는 툴입니다."; "lng_create_public_channel_title" = "공개 채널"; "lng_create_public_channel_about" = "누구나 채널을 검색하고 입장할 수 있습니다."; "lng_create_private_channel_title" = "비공개 채널"; @@ -579,7 +593,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_action_pinned_message" = "{from} 님이 «{text}» 를 고정함"; "lng_action_pinned_media" = "{from} 님이 {media} 를 고정함"; "lng_action_pinned_media_photo" = "사진"; -"lng_action_pinned_media_video" = "비디오 파일"; +"lng_action_pinned_media_video" = "동영상"; "lng_action_pinned_media_audio" = "오디오 파일"; "lng_action_pinned_media_voice" = "음성 메시지"; "lng_action_pinned_media_file" = "파일"; @@ -663,7 +677,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_media_type" = "미디어 종류"; "lng_media_type_photos" = "사진"; -"lng_media_type_videos" = "비디오 파일"; +"lng_media_type_videos" = "동영상"; "lng_media_type_songs" = "음성 파일"; "lng_media_type_files" = "파일"; "lng_media_type_audios" = "음성 메시지"; @@ -672,7 +686,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_media_open_with" = "다음으로 열기"; "lng_media_download" = "다운로드"; "lng_media_cancel" = "취소"; -"lng_media_video" = "비디오 파일"; +"lng_media_video" = "동영상"; "lng_media_audio" = "음성 메시지"; "lng_media_auto_settings" = "미디어 자동 다운로드 설정"; @@ -705,7 +719,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_box_remove" = "삭제"; -"lng_custom_stickers" = "커스텀 스티커"; +"lng_stickers_installed_tab" = "스티커"; +"lng_stickers_featured_tab" = "인기"; +"lng_stickers_archived_tab" = "보관됨"; "lng_stickers_remove_pack" = "«{sticker_pack}»을 제거하시겠습니까?"; "lng_stickers_add_pack" = "스티커 추가"; "lng_stickers_share_pack" = "스티커 공유"; @@ -715,19 +731,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_copied" = "클립보드에 스티커 팩 링크가 복사 되었습니다."; "lng_stickers_default_set" = "Great Minds"; "lng_stickers_you_have" = "스티커팩 관리 및 변경"; -"lng_stickers_packs" = "스티커팩"; -"lng_stickers_reorder" = "클릭과 드래그를 통하여 스태커 팩을 변경하세요"; "lng_stickers_featured" = "인기 스티커"; -"lng_stickers_clear_recent" = "초기화"; -"lng_stickers_clear_recent_sure" = "자주 사용하는 스티커 리스트를 초기화 하겠습니까?"; -"lng_stickers_remove" = "삭제"; "lng_stickers_return" = "실행취소"; -"lng_stickers_restore" = "복구"; "lng_stickers_count" = "{count:Loading...|# 스티커|# 스티커}"; "lng_stickers_masks_pack" = "마스크 스티커 팩입니다. 모바일 기기에 있는 사진 에디터에서 사용 가능합니다."; "lng_in_dlg_photo" = "사진"; -"lng_in_dlg_video" = "비디오 파일"; +"lng_in_dlg_video" = "동영상"; "lng_in_dlg_audio_file" = "음성 파일"; "lng_in_dlg_contact" = "연락처"; "lng_in_dlg_audio" = "음성 메시지"; @@ -744,9 +754,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_report_spam_sure_group" = "선택한 그룹메시지를 스팸으로 신고하시겠습니까?"; "lng_report_spam_sure_channel" = "선택한 채널메시지를 스팸으로 신고하시겠습니까?"; "lng_report_spam_ok" = "신고하기"; -"lng_cant_send_to_not_contact" = "죄송하지만, 현재 서로 연락처가 추가된 \n회원들끼리만 전송이 가능합니다. \n{more_info}"; +"lng_cant_send_to_not_contact" = "죄송하지만, 현재 서로 연락처가 추가된 회원들끼리만 전송이 가능합니다. {more_info}"; "lng_cant_invite_not_contact" = "죄송하지만, 현재 그룹방에 서로 연락처가 추가된 \n회원들끼리만 추가 가능합니다. {more_info}"; -"lng_cant_invite_not_contact_channel" = "죄송하지만, 현재 채널방에 서로 연락처가 추가된 \n회원들끼리만 추가 가능합니다. \n{more_info}"; +"lng_cant_send_to_not_contact_flood" = "연락처가 없으신분들과 너무 많이 연락을 해주셔서, 내일 다시 시도를 부탁드립니다. 다른 회원이 먼저 메시지를 전송하였을 경우 답장이 가능합니다."; +"lng_cant_invite_not_contact_flood" = "연락처가 없으신분들과 너무 많이 연락을 해주셔서 연락처 추가를 할 수 없습니다. 내일 다시 시도를 부탁드립니다. 다른 회원이 이 그룹에 초대는 가능합니다."; "lng_cant_more_info" = "자세한 정보 »"; "lng_cant_invite_banned" = "죄송하지만, 관리자만 회원 추가가 가능합니다."; "lng_cant_invite_privacy" = "죄송합니다, 개인설정으로 인하여 이 사용자를 그룹에 초대할 수 없습니다."; @@ -774,6 +785,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_dialogs_text_from_wrapped" = "{from}:"; "lng_dialogs_text_media" = "{media_part} {caption}"; "lng_dialogs_text_media_wrapped" = "{media},"; +"lng_dialogs_show_all_chats" = "모든 대화 보기"; +"lng_dialogs_hide_muted_chats" = "음소거된 대화 숨기기"; "lng_open_this_link" = "이 링크로 이동하시겠나요?"; "lng_open_link" = "열기"; @@ -828,12 +841,19 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_duration_and_size" = "{duration}, {size}"; "lng_duration_played" = "{played} / {duration}"; "lng_date_and_duration" = "{date}, {duration}"; -"lng_choose_images" = "이미지 선택"; +"lng_choose_image" = "이미지 선택"; +"lng_choose_files" = "파일 선택"; "lng_game_tag" = "게임"; "lng_context_view_profile" = "프로필 보기"; "lng_context_view_group" = "그룹 정보 보기"; "lng_context_view_channel" = "채널 정보 보기"; +"lng_context_pin_to_top" = "상단에 고정"; +"lng_context_unpin_from_top" = "상단에서 고정해제"; + +"lng_context_promote_admin" = "관리자로 지정"; +"lng_context_remove_admin" = "관리자에서 제외"; +"lng_context_remove_from_group" = "그룹에서 추방"; "lng_context_copy_link" = "링크 복사"; "lng_context_copy_post_link" = "메시지 링크 복사"; @@ -848,7 +868,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_context_cancel_download" = "다운로드 취소"; "lng_context_show_in_folder" = "탐색기에서 보기"; "lng_context_show_in_finder" = "탐색기에서 보기"; -"lng_context_save_video" = "비디오를 다른 이름으로 저장..."; +"lng_context_save_video" = "비디오를 다른 이름으로 저장.."; "lng_context_save_audio_file" = "음성파일을 다른 이름으로 저장..."; "lng_context_save_audio" = "음성메시지를 다른 이름으로 저장..."; "lng_context_pack_info" = "팩 정보"; @@ -875,10 +895,15 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_really_send_image" = "선택한 이미지를 전송하시겠습니까?"; "lng_really_send_file" = "선택한 파일을 전송하시겠습니까?"; "lng_really_share_contact" = "선택한 연락처를 공유하시겠습니까?"; -"lng_send_image_compressed" = "압축된 이미지를 전송"; -"lng_send_image_empty" = "빈 파일을 전송 할 수 없습니다 :("; -"lng_send_image_too_large" = "파일이 1.5GB 보다 큼으로 전송 할 수 없습니다 :("; +"lng_send_images_compress" = "압축 {count:_not_used_|이미지|이미지}"; +"lng_send_image_non_local" = "로컬이 아닌 파일을 전송 할 수 없습니다 : {name}"; +"lng_send_image_empty" = "빈 파일을 전송 할 수 없습니다 : {name}"; +"lng_send_image_too_large" = "파일이 1500MB 이상이기 때문에 전송 할 수 없습니다: {name}"; "lng_send_folder" = " «{name}»은 폴더이기 때문에 전송 할 수 없습니다 :("; +"lng_send_images_selected" = "{count:_not_used_|# 이미지|# 이미지} 선택"; +"lng_send_photos" = "전송 {count:_not_used_|# 사진|# 사진}"; +"lng_send_files_selected" = "{count:_not_used_|# 파일|# 파일} 선택"; +"lng_send_files" = "전송 {count:_not_used_|# 파일|# 파일}"; "lng_forward_choose" = "수신자를 선택.."; "lng_forward_cant" = "이쪽으로 전달 할 수 없습니다 :("; @@ -940,6 +965,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_selected_delete_sure_this" = "메시지를 삭제하시겠습니까?"; "lng_selected_delete_sure" = "{count:_not_used_|# 메시지|# 메시지}를 삭제하시겠습니까?"; "lng_delete_photo_sure" = "사진을 삭제하시겠습니까?"; +"lng_delete_for_everyone_hint" = "This will delete {count:_not_used_|it|them} for everyone in this chat."; +"lng_delete_for_me_chat_hint" = "이 작업은 다른분들이 아닌 회원님에게만 {count:_not_used_|이것|이것들} 을 삭제하게됩니다."; +"lng_delete_for_me_hint" = "이 작업은 회원님에게만 {count:_not_used_|이것|이것들} 을 삭제하게됩니다."; +"lng_delete_for_everyone_check" = "모두에게 삭제"; +"lng_delete_for_other_check" = "{user}에게 메시지 삭제"; "lng_box_delete" = "삭제"; "lng_box_leave" = "나가기"; @@ -972,6 +1002,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_mediaview_saved" = "[c]다운로드[/c] 폴더로 이미지가 저장되었습니다."; +"lng_theme_preview_title" = "테마 미리보기"; +"lng_theme_preview_generating" = "테마 색상 미리보기 생성중.."; +"lng_theme_preview_invalid" = "테마 파일이 올바르지 않습니다."; +"lng_theme_preview_apply" = "이 테마 적용"; + "lng_new_authorization" = "{name}님,\n{time}, {date}.{day}에 새 기기에서 회원님의 계정 로그인이 감지되었습니다. \n\n기기: {device}\n위치: {location}\n\n본인의 접속이 아니라면 '설정' 창에서 '모든 세션 종료' 기능을 실행하세요.\n\n만약 강제접속 의심이 되신다면 2단계 인증을 설정 - 개인정보 및 보안에서 설정할 수 있습니다\n\n감사합니다.\n\n텔레그램 팀\n"; "lng_new_version_wrap" = "텔레그램 데스크탑은 {version} 버전으로 업데이트 되었습니다.\n\n{changes}\n\n전체 버전 히스토리는 아래에서 확인 가능합니다:\n{link}"; diff --git a/Telegram/Resources/langs/lang_nl.strings b/Telegram/Resources/langs/lang_nl.strings index 1ac90322d..b1f81ed53 100644 --- a/Telegram/Resources/langs/lang_nl.strings +++ b/Telegram/Resources/langs/lang_nl.strings @@ -110,7 +110,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_status_online" = "online"; "lng_status_connecting" = "verbinden..."; -"lng_chat_status_unaccessible" = "groep is ontoegankelijk"; +"lng_chat_status_unaccessible" = "Groep is ontoegankelijk"; "lng_chat_status_members" = "{count:geen leden|# lid|# leden}"; "lng_chat_status_members_online" = "{count:_not_used_|# lid|# leden}, {count_online:_not_used_|# online|# online}"; @@ -125,6 +125,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_gif_error" = "Er is iets een fout opgetreden bij het lezen van de GIF :("; "lng_edit_error" = "Je mag dit bericht niet wijzigen"; "lng_join_channel_error" = "Je bent lid van teveel kanalen of supergroepen, verlaat er wat om hier lid te worden."; +"lng_error_phone_flood" = "Je hebt in korte tijd je account veelvuldig verwijderd en opnieuw aangemaakt. Probeer het over een aantal dagen nog eens."; +"lng_error_start_minimized_passcoded" = "Je hebt een toegangscode ingesteld, deze zal ingegeven moeten worden na het starten, geminimaliseerd starten kan daarom niet."; +"lng_error_pinned_max" = "Sorry, je kunt niet meer dan {count:_not_used_|# chat|# chats} vastzetten."; + "lng_edit_deleted" = "Bericht is gewist"; "lng_edit_too_long" = "Je bericht is te lang"; "lng_edit_message" = "Bericht wijzigen"; @@ -138,7 +142,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_pinned_unpin" = "Losmaken"; "lng_pinned_notify" = "Leden informeren"; -"lng_intro" = "Welkom bij de officiële [a href=\"https://telegram.org/\"]Telegram[/a] desktop-app.\n[b]Snel[/b] en [b]veilig[/b]."; +"lng_intro_about" = "Welkom bij de officiële Telegram desktop-app.\nSnel en veilig."; "lng_start_msgs" = "BEGIN MET CHATTEN"; "lng_intro_next" = "VOLGENDE"; @@ -146,11 +150,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_intro_submit" = "VERSTUREN"; "lng_photo_caption" = "Onderschrift"; +"lng_photos_comment" = "Opmerking"; "lng_phone_ph" = "Je telefoonnummer"; "lng_phone_title" = "Je telefoon"; "lng_phone_desc" = "Bevestig je landnummer en \nvoer je telefoonnummer in."; -"lng_phone_notreg" = "Als je nog geen Telegram-account hebt,\nkun je je [b]aanmelden [/b] met [a href=\"https://telegram.org/\"]iOS / Android[/a] of {signup_start}hier »{signup_end}"; +"lng_phone_notreg" = "Als je nog geen Telegram-account hebt,\nkun je je [b]aanmelden [/b] met {link_start}Android / iPhone{link_end} of {signup_start}hier{signup_end}"; "lng_country_code" = "Landnummer"; "lng_bad_country_code" = "Ongeldig landnummer"; "lng_country_ph" = "Zoeken"; @@ -160,13 +165,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_code_ph" = "Je code"; "lng_code_desc" = "We hebben een bericht met activatiecode\nverstuurd naar je nummer, geef deze hieronder in."; -"lng_code_telegram" = "Voer de code in die je zojuist\nhebt ontvangen in je vorige [b]Telegram[/b]-app."; +"lng_code_telegram" = "Voer de code in die je zojuist ontvangen\nhebt in je vorige [b]Telegram[/b]-app."; "lng_code_no_telegram" = "Verstuur code via SMS"; "lng_code_call" = "Telegram belt je over {minutes}:{seconds}"; "lng_code_calling" = "Oproepverzoek naar Telegram..."; "lng_code_called" = "Telegram heeft je nummer gebeld"; -"lng_bad_phone" = "Ongeldig telefoonnummer. \nProbeer het opnieuw."; +"lng_bad_phone" = "Ongeldig telefoonnummer. "; "lng_bad_phone_noreg" = "Telefoonnummer is niet geregistreerd."; "lng_bad_code" = "Je hebt een ongeldige code ingevoerd."; "lng_bad_name" = "Voer je voor- en achternaam in."; @@ -174,7 +179,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_title" = "Cloud-wachtwoord:"; "lng_signin_desc" = "Cloud-wachtwoord invoeren"; -"lng_signin_recover_desc" = "Voer de code uit de e-mail in"; +"lng_signin_recover_desc" = "Voer de code uit de e-mail in\n{email}"; "lng_signin_password" = "Je cloud-wachtwoord"; "lng_signin_code" = "Code uit de e-mail"; "lng_signin_recover" = "Wachtwoord vergeten?"; @@ -196,7 +201,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_reset_in_minutes" = "{count_minutes:0 minuten|# minuut|# minuten}"; "lng_signin_reset_cancelled" = "De actieve gebruiker heeft de recente poging om dit account te resetten geannuleerd. Probeer het over 7 dagen nog eens."; -"lng_signup_title" = "Informatie en foto"; +"lng_signup_title" = "Jouw Informatie"; "lng_signup_desc" = "Voer je naam en\nupload een foto."; "lng_signup_firstname" = "Voornaam"; @@ -212,6 +217,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_dlg_search_chat" = "Zoek in deze chat"; "lng_dlg_search_channel" = "Zoek in dit kanaal"; "lng_dlg_search_for_messages" = "Zoek berichten"; +"lng_update_telegram" = "Telegram bijwerken"; "lng_settings_save" = "Opslaan"; "lng_settings_upload" = "Profielfoto instellen"; @@ -260,7 +266,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_settings_change_lang" = "Taal wijzigen"; "lng_languages" = "Talen"; "lng_sure_save_language" = "Opnieuw starten om de taal te wijzen?"; -"lng_settings_update_automatically" = "Automatisch bijwerken (ver. {version})"; +"lng_settings_update_automatically" = "Automatisch bijwerken"; +"lng_settings_current_version_label" = "Versie {version}:"; "lng_settings_current_version" = "Versie {version}"; "lng_settings_check_now" = "Controleer op updates"; "lng_settings_update_checking" = "Controleren op updates..."; @@ -286,12 +293,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_settings_send_cmdenter" = "Versturen met Cmd+Enter"; "lng_settings_section_background" = "Achtergrond"; +"lng_settings_bg_use_default" = "Standaardthema gebruiken"; "lng_settings_bg_from_gallery" = "Uit galerij kiezen"; "lng_settings_bg_from_file" = "Bestand kiezen"; "lng_settings_bg_tile" = "Naast elkaar"; "lng_settings_adaptive_wide" = "Adaptieve layout voor brede schermen"; "lng_backgrounds_header" = "Kies je nieuwe achtergrond"; +"lng_theme_sure_keep" = "Dit thema behouden?"; +"lng_theme_reverting" = "Herstellen naar het vorige thema over {count:_not_used_|# seconde|# seconden}."; +"lng_theme_keep_changes" = "Behouden"; +"lng_theme_revert" = "Herstellen"; "lng_download_path_dont_ask" = "Niet voor iedere download vragen"; "lng_download_path_label" = "Downloadpad:"; @@ -345,6 +357,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_passcode_wrong" = "Onjuiste toegangscode"; "lng_passcode_is_same" = "Toegangscode is niet gewijzigd"; "lng_passcode_enter" = "Toegangscode invoeren"; +"lng_passcode_ph" = "Je toegangscode"; "lng_passcode_submit" = "Versturen"; "lng_passcode_logout" = "Uitloggen"; "lng_passcode_need_unblock" = "Deblokkeer de app eerst."; @@ -428,6 +441,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_create_public_link" = "Publieke link maken"; "lng_profile_edit_public_link" = "Publieke link wijzigen"; "lng_profile_manage_admins" = "Beheerders wijzigen"; +"lng_profile_common_groups" = "gedeelde {count:_not_used_|# groep|# groepen}"; +"lng_profile_common_groups_section" = "Gedeelde groepen"; "lng_profile_participants_section" = "Leden"; "lng_profile_info_section" = "Informatie"; "lng_profile_mobile_number" = "Mobiel:"; @@ -467,17 +482,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_shared_media" = "Gedeelde media"; "lng_profile_no_media" = "Geen media in deze chat."; "lng_profile_photos" = "{count:_not_used_|# foto|# foto's}"; -"lng_profile_photos_header" = "Foto-overzicht"; +"lng_profile_photos_header" = "Foto's"; "lng_profile_videos" = "{count:_not_used_|# video|# video's}"; -"lng_profile_videos_header" = "Video-overzicht"; +"lng_profile_videos_header" = "Video's"; "lng_profile_songs" = "{count:_not_used_|# audiobestand|# audiobestanden}"; -"lng_profile_songs_header" = "Audio-overzicht"; +"lng_profile_songs_header" = "Audiobestanden"; "lng_profile_files" = "{count:_not_used_|# bestand|# bestanden}"; -"lng_profile_files_header" = "Bestandsoverzicht"; +"lng_profile_files_header" = "Bestanden"; "lng_profile_audios" = "{count:_not_used_|# spraakbericht|# spraakberichten}"; -"lng_profile_audios_header" = "Spraakberichten-overzicht"; +"lng_profile_audios_header" = "Spraakberichten"; "lng_profile_shared_links" = "{count:_not_used_|# gedeelde link|# gedeelde links}"; -"lng_profile_shared_links_header" = "Links-overzicht"; +"lng_profile_shared_links_header" = "Gedeelde links"; "lng_profile_copy_phone" = "Telefoonnummer kopiëren"; "lng_profile_copy_fullname" = "Naam kopiëren"; "lng_profile_drop_area_title" = "Sleep afbeelding hierheen"; @@ -487,6 +502,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_report_title" = "Kanaal melden"; "lng_report_group_title" = "Groep melden"; +"lng_report_bot_title" = "Spam melden"; "lng_report_reason_spam" = "Spam"; "lng_report_reason_violence" = "Geweld"; "lng_report_reason_pornography" = "Pornografie"; @@ -515,9 +531,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_create_group_next" = "Volgende"; "lng_create_group_create" = "Maak"; "lng_create_group_title" = "Nieuwe groep"; -"lng_create_group_about" = "Groepen zijn voor beperkte gemeenschappen,\nmet maximaal {count:_not_used|# lid|# leden}"; "lng_create_channel_title" = "Nieuw kanaal"; -"lng_create_channel_about" = "Kanalen kennen geen limiet en zijn geschikt om een groot publiek te bereiken"; "lng_create_public_channel_title" = "Publiek kanaal"; "lng_create_public_channel_about" = "Iedereen kan je kanaal vinden en er lid van worden"; "lng_create_private_channel_title" = "Privé-kanaal"; @@ -705,7 +719,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_box_remove" = "Verwijder"; -"lng_custom_stickers" = "Aangepaste stickers"; +"lng_stickers_installed_tab" = "Stickers"; +"lng_stickers_featured_tab" = "Populair"; +"lng_stickers_archived_tab" = "Gearchiveerd"; "lng_stickers_remove_pack" = " \"{sticker_pack}\" verwijderen?"; "lng_stickers_add_pack" = "Stickers toevoegen"; "lng_stickers_share_pack" = "Stickers delen"; @@ -715,14 +731,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_copied" = "Stickerbundel-link gekopieerd naar klembord"; "lng_stickers_default_set" = "Grote geesten"; "lng_stickers_you_have" = "Beheer en sorteer stickerbundels"; -"lng_stickers_packs" = "Stickerbundels"; -"lng_stickers_reorder" = "Klik en sleep om stickerbundels te herschikken"; "lng_stickers_featured" = "Populaire stickers"; -"lng_stickers_clear_recent" = "Wissen"; -"lng_stickers_clear_recent_sure" = "Lijst met veelgebruikte stickers echt wissen?"; -"lng_stickers_remove" = "Verwijder"; "lng_stickers_return" = "Ongedaan maken"; -"lng_stickers_restore" = "Herstellen"; "lng_stickers_count" = "{count:Laden..|# sticker|# stickers}"; "lng_stickers_masks_pack" = "Dit een set met maskers. Je kunt ze gebruiken als je een foto bewerkt in onze mobiele apps."; @@ -744,9 +754,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_report_spam_sure_group" = "Spam van deze groep echt melden?"; "lng_report_spam_sure_channel" = "Spam van dit kanaal echt melden?"; "lng_report_spam_ok" = "Melden"; -"lng_cant_send_to_not_contact" = "Je kunt momenteel alleen berichten\nversturen naar onderlinge contacten.\n{more_info}"; -"lng_cant_invite_not_contact" = "Je kunt momenteel alleen onderlinge\ncontacten aan kanalen toevoegen.\n{more_info}"; -"lng_cant_invite_not_contact_channel" = "Je kunt momenteel alleen onderlinge\ncontacten aan kanalen toevoegen.\n{more_info}"; +"lng_cant_send_to_not_contact" = "Je kunt momenteel alleen berichten versturen naar onderlinge contacten. {more_info}"; +"lng_cant_invite_not_contact" = "Je kunt momenteel alleen onderlinge contacten aan kanalen toevoegen. {more_info}"; +"lng_cant_send_to_not_contact_flood" = "Je kunt momenteel alleen berichten versturen naar onderlinge contacten. Probeer het later, berichten beantwoorden kan wel."; +"lng_cant_invite_not_contact_flood" = "Je kunt momenteel alleen onderlinge contacten aan groepen toevoegen. Probeer het later of vraag een ander lid om deze gebruiker aan de groep toe te voegen."; "lng_cant_more_info" = "Meer informatie »"; "lng_cant_invite_banned" = "Alleen beheerders kunnen deze gebruiker toevoegen."; "lng_cant_invite_privacy" = "Je kunt deze gebruiker niet toevoegen aan groepen door zijn/haar privacyinstellingen."; @@ -774,6 +785,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_dialogs_text_from_wrapped" = "{from}:"; "lng_dialogs_text_media" = "{media_part} {caption}"; "lng_dialogs_text_media_wrapped" = "{media},"; +"lng_dialogs_show_all_chats" = "Alle chats weergeven"; +"lng_dialogs_hide_muted_chats" = "Stille chats verbergen"; "lng_open_this_link" = "Link openen?"; "lng_open_link" = "Openen"; @@ -828,12 +841,19 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_duration_and_size" = "{duration}, {size}"; "lng_duration_played" = "{played} / {duration}"; "lng_date_and_duration" = "{date}, {duration}"; -"lng_choose_images" = "Afbeeldingen kiezen"; +"lng_choose_image" = "Afbeelding kiezen"; +"lng_choose_files" = "Bestanden kiezen"; "lng_game_tag" = "Spel"; "lng_context_view_profile" = "Profiel weergeven"; "lng_context_view_group" = "Groepsinformatie weergeven"; "lng_context_view_channel" = "Kanaalinformatie weergeven"; +"lng_context_pin_to_top" = "Vastzetten"; +"lng_context_unpin_from_top" = "Losmaken"; + +"lng_context_promote_admin" = "Beheerder maken"; +"lng_context_remove_admin" = "Ontslaan als beheerder"; +"lng_context_remove_from_group" = "Uit de groep verwijderen"; "lng_context_copy_link" = "Link kopiëren"; "lng_context_copy_post_link" = "Berichtlink kopiëren"; @@ -875,10 +895,15 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_really_send_image" = "Wil je deze afbeeldingen versturen?"; "lng_really_send_file" = "Wil je dit bestand versturen?"; "lng_really_share_contact" = "Wil je dit contact delen?"; -"lng_send_image_compressed" = "Gecomprimeerde afbeelding sturen"; -"lng_send_image_empty" = "Bestand bestaat niet of is leeg :("; -"lng_send_image_too_large" = "Dit bestand is groter dan 1,5 GB en kan niet worden verstuurd :("; +"lng_send_images_compress" = "{count:_not_used_|afbeelding|afbeeldingen} comprimeren"; +"lng_send_image_non_local" = "Verzenden mislukt, {name} is een netwerk-bestand."; +"lng_send_image_empty" = "Verzenden mislukt, {name} is leeg."; +"lng_send_image_too_large" = "Verzenden mislukt, {name} is groter dan 1500 MB."; "lng_send_folder" = "Kan de map «{name}» niet versturen, kies een bestand. :("; +"lng_send_images_selected" = "{count:_not_used_|# afbeelding|# afbeeldingen} gekozen"; +"lng_send_photos" = "{count:_not_used_|# foto|# foto's} versturen"; +"lng_send_files_selected" = "{count:_not_used_|# bestand|# bestanden} gekozen"; +"lng_send_files" = "{count:_not_used_|# bestand|# bestanden} versturen"; "lng_forward_choose" = "Ontvanger kiezen..."; "lng_forward_cant" = "Sorry, doorsturen hierheen kan niet :("; @@ -940,6 +965,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_selected_delete_sure_this" = "Wil je dit bericht verwijderen?"; "lng_selected_delete_sure" = "Wil je {count:_not_used_|# bericht|# berichten} verwijderen?"; "lng_delete_photo_sure" = "Wil je deze foto verwijderen?"; +"lng_delete_for_everyone_hint" = "Hiermee verwijder je {count:_not_used_|dit|deze} voor iedereen in de chat."; +"lng_delete_for_me_chat_hint" = "Hiermee verwijder je {count:_not_used_|dit|deze} alleen voor jezelf, niet voor andere leden."; +"lng_delete_for_me_hint" = "Hiermee verwijder je {count:_not_used_|dit|deze} alleen voor jezelf."; +"lng_delete_for_everyone_check" = "Verwijder voor iedereen"; +"lng_delete_for_other_check" = "Verwijder voor {user}"; "lng_box_delete" = "Verwijder"; "lng_box_leave" = "Verlaat"; @@ -972,6 +1002,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_mediaview_saved" = "Het bestand is opgeslagen in je [c]Downloads[/c]-map"; +"lng_theme_preview_title" = "Thema-voorvertoning"; +"lng_theme_preview_generating" = "Voorvertoning van thema maken..."; +"lng_theme_preview_invalid" = "Thema bevat ongeldige data."; +"lng_theme_preview_apply" = "Thema toepassen"; + "lng_new_authorization" = "{name},\nEr is op je account ingelogd vanaf een nieuw apparaat op {day}, {date} om {time}\n\nApparaat: {device}\nLocatie: {location}\n\nAls jij dit niet was, kun je alle sessies beëindigen via Instellingen – Alle sessies weergeven en de sessie beëindigen.\n\nAls je dat denkt dat iemand anders zonder jouw toestemming is ingelogd kun je twee-staps-verificatie activeren via instellingen.\n\nBedankt,\nHet Telegram-Team"; "lng_new_version_wrap" = "Telegram is bijgewerkt naar versie {version}\n\n{changes} \n\nVolledige versiegeschiedenis is hier te vinden:\n{link}"; diff --git a/Telegram/Resources/langs/lang_pt_BR.strings b/Telegram/Resources/langs/lang_pt_BR.strings index f4d9660e2..353723c8d 100644 --- a/Telegram/Resources/langs/lang_pt_BR.strings +++ b/Telegram/Resources/langs/lang_pt_BR.strings @@ -26,7 +26,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_menu_about" = "Sobre"; "lng_menu_update" = "Atualizar"; "lng_menu_restart" = "Reiniciar"; -"lng_menu_back" = "Anterior"; +"lng_menu_back" = "Voltar"; "lng_disable_notifications_from_tray" = "Desabilitar notificações"; "lng_enable_notifications_from_tray" = "Habilitar notificações"; @@ -110,7 +110,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_status_online" = "online"; "lng_status_connecting" = "conectando..."; -"lng_chat_status_unaccessible" = "grupo está inacessível"; +"lng_chat_status_unaccessible" = "grupo inacessível"; "lng_chat_status_members" = "{count:nenhum membro|# membro|# membros}"; "lng_chat_status_members_online" = "{count:_not_used_|# membro|# membros}, {count_online:_not_used_|# online|# online}"; @@ -121,10 +121,14 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_channel_admins_link" = "{count:_not_used_|# administrador|# administradores}"; "lng_server_error" = "Erro interno do servidor."; -"lng_flood_error" = "Muitas tentativas. Por favor, tente novamente mais tarde."; +"lng_flood_error" = "Muitas tentativas. Tente novamente mais tarde."; "lng_gif_error" = "Um erro ocorreu com a animação do GIF :("; "lng_edit_error" = "Você não pode editar essa mensagem"; "lng_join_channel_error" = "Desculpe, você já entrou em muitos canais e supergrupos. Por favor, saia de alguns antes de entrar."; +"lng_error_phone_flood" = "Desculpe, você apagou e recriou sua conta muitas vezes recentemente. Aguarde alguns dias antes de tentar novamente."; +"lng_error_start_minimized_passcoded" = "Você tem uma senha de bloqueio, então o aplicativo não pode iniciar minimizado. O app precisa de sua senha para iniciar."; +"lng_error_pinned_max" = "Você não pode fixar mais de {count:_not_used_|# conversa|# conversas} no topo."; + "lng_edit_deleted" = "Essa mensagem foi apagada"; "lng_edit_too_long" = "Sua mensagem está muito longa"; "lng_edit_message" = "Editar mensagem"; @@ -138,7 +142,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_pinned_unpin" = "Desafixar"; "lng_pinned_notify" = "Notificar todos os membros"; -"lng_intro" = "Bem vindo ao cliente oficial do [a href=\"https://telegram.org/\"]Telegram[/a].\nÉ [b]rápido[/b] e [b]seguro[/b]."; +"lng_intro_about" = "Bem vindo ao aplicativo oficial Telegram Desktop.\nÉ rápido e seguro."; "lng_start_msgs" = "COMECE A CONVERSAR"; "lng_intro_next" = "PRÓXIMO"; @@ -146,11 +150,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_intro_submit" = "ENVIAR"; "lng_photo_caption" = "Legenda"; +"lng_photos_comment" = "Comentário"; "lng_phone_ph" = "Seu número de telefone"; "lng_phone_title" = "Seu Telefone"; "lng_phone_desc" = "Confirme o código de seu país e\npreencha seu número de telefone."; -"lng_phone_notreg" = "Nota: Se você não tem uma conta no Telegram,\npor favor [b]cadastre[/b] com seu [a href=\"https://telegram.org/\"]iOS / Android[/a] ou {signup_start}aqui »{signup_end}"; +"lng_phone_notreg" = "Se você ainda não possui uma conta do Telegram,\nse [b]cadastre[/b] com um {link_start}Android / iPhone{link_end} ou {signup_start}aqui{signup_end}"; "lng_country_code" = "Código do País"; "lng_bad_country_code" = "Código do País Inválido"; "lng_country_ph" = "Busca"; @@ -160,9 +165,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_code_ph" = "Código"; "lng_code_desc" = "Enviamos uma SMS com um código de ativação\npara o seu telefone. Insira-o abaixo."; -"lng_code_telegram" = "Por favor, insira o código recebido\nem seu aplicativo prévio do [b]Telegram[/b]"; +"lng_code_telegram" = "Por favor, insira o código recebido\nem seu aplicativo prévio do [b]Telegram[/b]."; "lng_code_no_telegram" = "Enviar código via SMS"; -"lng_code_call" = "Telegram irá te ligar em {minutes}{seconds}"; +"lng_code_call" = "Telegram irá te ligar em {minutes}:{seconds}"; "lng_code_calling" = "Aguardando a ligação do Telegram.."; "lng_code_called" = "Telegram ligou para o seu número"; @@ -174,7 +179,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_title" = "Verificar senha"; "lng_signin_desc" = "Por favor, insira sua senha."; -"lng_signin_recover_desc" = "Por favor, insira o código enviado por e-mail."; +"lng_signin_recover_desc" = "Por favor, insira o código de seu e-mail\n{email}"; "lng_signin_password" = "Sua senha"; "lng_signin_code" = "Código do e-mail"; "lng_signin_recover" = "Esqueceu sua senha?"; @@ -196,7 +201,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_signin_reset_in_minutes" = "{count_minutes:0 minuto|# minuto|# minutos}"; "lng_signin_reset_cancelled" = "Suas tentativas recentes de restaurar essa conta foram canceladas pelo usuário ativo. Tente novamente em 7 dias."; -"lng_signup_title" = "Informação e foto"; +"lng_signup_title" = "Suas Informações"; "lng_signup_desc" = "Por favor, insira nome e\ncarregue uma foto."; "lng_signup_firstname" = "Nome"; @@ -212,6 +217,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_dlg_search_chat" = "Buscar nesse chat"; "lng_dlg_search_channel" = "Buscar nesse canal"; "lng_dlg_search_for_messages" = "Buscar por mensagens"; +"lng_update_telegram" = "Atualizar o Telegram"; "lng_settings_save" = "Salvar"; "lng_settings_upload" = "Definir Foto de Perfil"; @@ -260,7 +266,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_settings_change_lang" = "Alterar idioma"; "lng_languages" = "Idiomas"; "lng_sure_save_language" = "Telegram irá reiniciar para alterar o idioma"; -"lng_settings_update_automatically" = "Atualizar automaticamente (ver. {version})"; +"lng_settings_update_automatically" = "Atualizar automaticamente"; +"lng_settings_current_version_label" = "Versão {version}:"; "lng_settings_current_version" = "Versão {version}"; "lng_settings_check_now" = "Verificar atualizações"; "lng_settings_update_checking" = "Verificando atualizações..."; @@ -286,12 +293,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_settings_send_cmdenter" = "Enviar com Cmd+Enter"; "lng_settings_section_background" = "Papel de Parede"; +"lng_settings_bg_use_default" = "Usar o tema padrão de cores"; "lng_settings_bg_from_gallery" = "Escolher da galeria"; "lng_settings_bg_from_file" = "Escolher dos arquivos"; "lng_settings_bg_tile" = "Lado-a-lado"; "lng_settings_adaptive_wide" = "Layout adaptativo para wide screens"; "lng_backgrounds_header" = "Escolha o seu novo papel de parede"; +"lng_theme_sure_keep" = "Manter esse tema de cores?"; +"lng_theme_reverting" = "Revertendo ao antigo tema de cores em {count:_not_used_|# segundo|# segundos}."; +"lng_theme_keep_changes" = "Manter alterações"; +"lng_theme_revert" = "Reverter"; "lng_download_path_dont_ask" = "Não perguntar onde salvar cada arquivo"; "lng_download_path_label" = "Baixar em:"; @@ -340,11 +352,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_passcode_enter_first" = "Insira uma senha"; "lng_passcode_enter_new" = "Insira sua nova senha"; "lng_passcode_confirm_new" = "Re-insira sua nova senha"; -"lng_passcode_about" = "Quando uma senha de bloqueio é definida, um ícone de cadeado aparece acima, à direita. Clique para bloquear o app.\n\nNota: caso esqueça sua senha, terá de relogar no Telegram Desktop."; +"lng_passcode_about" = "Quando uma senha de bloqueio é definida, um cadeado aparecerá no topo de sua lista de conversas. Clique para travar o aplicativo.\n\nNota: Se você esquecer sua senha, terá que entrar novamente no Telegram Desktop."; "lng_passcode_differ" = "As senhas não correspondem"; "lng_passcode_wrong" = "Senha errada"; "lng_passcode_is_same" = "Senha não alterada"; "lng_passcode_enter" = "Insira sua senha de bloqueio"; +"lng_passcode_ph" = "Sua senha de bloqueio"; "lng_passcode_submit" = "Entrar"; "lng_passcode_logout" = "Sair"; "lng_passcode_need_unblock" = "Você precisa me desbloquear primeiro."; @@ -384,7 +397,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_connection_auto_rb" = "Auto (TCP se disponível ou HTTP)"; "lng_connection_http_proxy_rb" = "HTTP com http-proxy customizado"; "lng_connection_tcp_proxy_rb" = "TCP com socks5-proxy customizado"; -"lng_connection_try_ipv6" = "Tentando conexão via IPv6"; +"lng_connection_try_ipv6" = "Tentar conexão via IPv6"; "lng_connection_host_ph" = "Nome do host"; "lng_connection_port_ph" = "Porta"; "lng_connection_user_ph" = "Usuário"; @@ -428,6 +441,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_create_public_link" = "Criar link público"; "lng_profile_edit_public_link" = "Editar link público"; "lng_profile_manage_admins" = "Gerenciar administradores"; +"lng_profile_common_groups" = "{count:_not_used_|# grupo|# grupos} em comum"; +"lng_profile_common_groups_section" = "Grupos em comum"; "lng_profile_participants_section" = "Membros"; "lng_profile_info_section" = "Info"; "lng_profile_mobile_number" = "Celular:"; @@ -477,7 +492,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_profile_audios" = "{count:_not_used_|# mensagem de voz|# mensagens de voz}"; "lng_profile_audios_header" = "Mensagens de voz"; "lng_profile_shared_links" = "{count:_not_used_|# link compartilhado|# links compartilhados}"; -"lng_profile_shared_links_header" = "Links"; +"lng_profile_shared_links_header" = "Links compartilhados"; "lng_profile_copy_phone" = "Copiar Número de Telefone"; "lng_profile_copy_fullname" = "Copiar Nome"; "lng_profile_drop_area_title" = "Solte sua imagem aqui"; @@ -487,6 +502,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_report_title" = "Reportar canal"; "lng_report_group_title" = "Reportar grupo"; +"lng_report_bot_title" = "Reportar bot"; "lng_report_reason_spam" = "Spam"; "lng_report_reason_violence" = "Violência"; "lng_report_reason_pornography" = "Pornografia"; @@ -515,9 +531,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_create_group_next" = "Próximo"; "lng_create_group_create" = "Criar"; "lng_create_group_title" = "Novo Grupo"; -"lng_create_group_about" = "Grupos são ideais para comunidades limitadas,\neles podem conter até {count:_not_used|# membro|# membros}"; "lng_create_channel_title" = "Novo Canal"; -"lng_create_channel_about" = "Canais são uma ferramenta para transmitir suas mensagens para audiências ilimitadas"; "lng_create_public_channel_title" = "Canal Público"; "lng_create_public_channel_about" = "Qualquer um pode encontrar o canal na busca e entrar"; "lng_create_private_channel_title" = "Canal privado"; @@ -677,8 +691,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_media_auto_settings" = "Download automático de mídia"; "lng_media_auto_photo" = "Baixar foto automaticamente"; -"lng_media_auto_audio" = "Baixar mensagem de voz automaticamente"; -"lng_media_auto_gif" = "Baixar GIF autometicamente"; +"lng_media_auto_audio" = "Baixar áudio automaticamente"; +"lng_media_auto_gif" = "Baixar GIF automaticamente"; "lng_media_auto_private_chats" = "Conversas privadas"; "lng_media_auto_groups" = "Grupos e canais"; "lng_media_auto_play" = "Reproduzir automaticamente"; @@ -705,7 +719,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_box_remove" = "Remover"; -"lng_custom_stickers" = "Stickers customizados"; +"lng_stickers_installed_tab" = "Stickers"; +"lng_stickers_featured_tab" = "Populares"; +"lng_stickers_archived_tab" = "Arquivados"; "lng_stickers_remove_pack" = "Remover «{sticker_pack}»?"; "lng_stickers_add_pack" = "Adicionar aos Stickers"; "lng_stickers_share_pack" = "Compartilhar Stickers"; @@ -715,14 +731,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_stickers_copied" = "Link copiado para a área de transferência."; "lng_stickers_default_set" = "Grandes Mentes"; "lng_stickers_you_have" = "Gerenciar e reordenar os pacotes de sticker"; -"lng_stickers_packs" = "Pacotes de Sticker"; -"lng_stickers_reorder" = "Clique e arraste para reordenar os pacotes"; "lng_stickers_featured" = "Stickers Populares"; -"lng_stickers_clear_recent" = "Limpar"; -"lng_stickers_clear_recent_sure" = "Você tem certeza que deseja limpar seu histórico de stickers frequentes?"; -"lng_stickers_remove" = "Remover"; "lng_stickers_return" = "Desfazer"; -"lng_stickers_restore" = "Restaurar"; "lng_stickers_count" = "{count:Carregando...|# sticker|# stickers}"; "lng_stickers_masks_pack" = "Esse é um pacote de máscaras. Você pode usá-las no editor de fotos em nossos aplicativos para celular."; @@ -744,9 +754,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_report_spam_sure_group" = "Tem certeza que deseja reportar spam desse grupo?"; "lng_report_spam_sure_channel" = "Tem certeza que deseja reportar spam desse canal?"; "lng_report_spam_ok" = "Reportar"; -"lng_cant_send_to_not_contact" = "Desculpe, você só pode enviar mensagens\npara contatos mútuos no momento.\n{more_info}"; -"lng_cant_invite_not_contact" = "Desculpe, no momento você só pode adicionar\ncontatos mútuos em grupos.\n{more_info}"; -"lng_cant_invite_not_contact_channel" = "Desculpe, no momento você só pode adicionar\ncontatos mútuos nos canais.\n{more_info}"; +"lng_cant_send_to_not_contact" = "Desculpe, você só pode enviar mensagens para contatos mútuos no momento. {more_info}"; +"lng_cant_invite_not_contact" = "Desculpe, no momento você só pode adicionar contatos mútuos em grupos. {more_info}"; +"lng_cant_send_to_not_contact_flood" = "Você contatou muitos não-contatos hoje, tente novamente amanhã. Hoje você poderá responder hoje se esse usuário te enviar uma mensagem primeiro."; +"lng_cant_invite_not_contact_flood" = "Você não pode adicionar esse usuário porque você contatou muitos não-contatos hoje. Tente novamente amanhã. Você pode pedir para outro membro adicionar este usuário ao grupo."; "lng_cant_more_info" = "Informações »"; "lng_cant_invite_banned" = "Apenas um administrador pode adicionar esse usuário."; "lng_cant_invite_privacy" = "Você não pode adicionar esse usuário em grupos devido as configurações de privacidade."; @@ -774,6 +785,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_dialogs_text_from_wrapped" = "{from}:"; "lng_dialogs_text_media" = "{media_part} {caption}"; "lng_dialogs_text_media_wrapped" = "{media},"; +"lng_dialogs_show_all_chats" = "Mostrar todas as conversas"; +"lng_dialogs_hide_muted_chats" = "Esconder conversas silenciadas"; "lng_open_this_link" = "Abrir este link?"; "lng_open_link" = "Abrir"; @@ -828,12 +841,19 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_duration_and_size" = "{duration}, {size}"; "lng_duration_played" = "{played} / {duration}"; "lng_date_and_duration" = "{date}, {duration}"; -"lng_choose_images" = "Escolher imagens"; +"lng_choose_image" = "Escolha uma imagem"; +"lng_choose_files" = "Escolher arquivos"; "lng_game_tag" = "Jogar"; "lng_context_view_profile" = "Ver perfil"; "lng_context_view_group" = "Ver info do grupo"; "lng_context_view_channel" = "Ver info do canal"; +"lng_context_pin_to_top" = "Fixar no topo"; +"lng_context_unpin_from_top" = "Desafixar do topo"; + +"lng_context_promote_admin" = "Promover a administrador"; +"lng_context_remove_admin" = "Remover dos administradores"; +"lng_context_remove_from_group" = "Remover do grupo"; "lng_context_copy_link" = "Copiar Link"; "lng_context_copy_post_link" = "Copiar Link do Post"; @@ -875,10 +895,15 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_really_send_image" = "Você deseja enviar essa imagem?"; "lng_really_send_file" = "Você deseja enviar esse arquivo?"; "lng_really_share_contact" = "Você deseja compartilhar esse contato?"; -"lng_send_image_compressed" = "Enviar imagem comprimida"; -"lng_send_image_empty" = "Não posso enviar um arquivo inexistente :("; -"lng_send_image_too_large" = "Não pude enviar, o arquivo é maior que 1.5GB :("; +"lng_send_images_compress" = "Comprimir {count:_not_used_|imagem|imagens}"; +"lng_send_image_non_local" = "Não foi possível enviar um arquivo não local: {name}"; +"lng_send_image_empty" = "Não foi possível enviar um arquivo vazio: {name}"; +"lng_send_image_too_large" = "Não foi possível enviar um arquivo, por ser maior que 1500 MB: {name}"; "lng_send_folder" = "Não pude enviar «{name}» porque é um diretório :("; +"lng_send_images_selected" = "{count:_not_used_|# imagem selecionada|# imagens selecionadas}"; +"lng_send_photos" = "Enviar {count:_not_used_|# foto|# fotos}"; +"lng_send_files_selected" = "{count:_not_used_|# arquivo selecionado|# arquivos selecionados}"; +"lng_send_files" = "Enviar {count:_not_used_|# arquivo|# arquivos}"; "lng_forward_choose" = "Escolher recipiente..."; "lng_forward_cant" = "Desculpe, não há como encaminhar aqui :("; @@ -940,6 +965,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_selected_delete_sure_this" = "Você deseja apagar essa mensagem?"; "lng_selected_delete_sure" = "Você deseja apagar {count:_not_used_|# mensagem|# mensagens}?"; "lng_delete_photo_sure" = "Você deseja apagar essa foto?"; +"lng_delete_for_everyone_hint" = "Isso irá apagar {count:_not_used_|a mensagem|as mensagens} para todos nessa conversa."; +"lng_delete_for_me_chat_hint" = "Isso irá apagar {count:_not_used_|a mensagem|as mensagens} somente para você, não para os outros participantes da conversa."; +"lng_delete_for_me_hint" = "Isso irá apagar {count:_not_used_|a mensagem|as mensagens} apenas para você."; +"lng_delete_for_everyone_check" = "Apagar para todos"; +"lng_delete_for_other_check" = "Apagar para {user}"; "lng_box_delete" = "Apagar"; "lng_box_leave" = "Sair"; @@ -972,6 +1002,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_mediaview_saved" = "Imagem foi salva na sua pasta [c]Downloads[/c]"; +"lng_theme_preview_title" = "Visualizar Tema"; +"lng_theme_preview_generating" = "Gerando uma prévia do tema de cores..."; +"lng_theme_preview_invalid" = "Dados inválidos nesse arquivo de tema."; +"lng_theme_preview_apply" = "Aplicar esse tema"; + "lng_new_authorization" = "{name},\nDetectamos um acesso à sua conta de um novo dispositivo em {day}, {date} às {time}\n\nDispositivo: {device}\nLocalização: {location}\n\nCaso não tenha sido você, vá em Configurações – Mostrar todas as sessões e terminar essa sessão.\n\nSe você acha que alguém entrou em sua conta, você pode ativar a verificação em duas etapas nas Configurações.\n\nObrigado,\nEquipe Telegram"; "lng_new_version_wrap" = "Telegram Desktop foi atualizado para a versão {version}\n\n{changes}\n\nHistórico completo de mudanças disponível aqui:\n{link}"; diff --git a/Telegram/Resources/telegram.qrc b/Telegram/Resources/telegram.qrc index 9d916ea39..6fece6a45 100644 --- a/Telegram/Resources/telegram.qrc +++ b/Telegram/Resources/telegram.qrc @@ -5,13 +5,10 @@ art/fonts/OpenSans-Semibold.ttf art/newmsg.wav art/bg.jpg - art/bg0.png - art/sprite.png - art/sprite_125x.png - art/sprite_150x.png - art/sprite_200x.png + art/bg_initial.png art/icon256.png art/iconbig256.png + art/sunrise.jpg qmime/freedesktop.org.xml diff --git a/Telegram/Resources/telegram_mac.qrc b/Telegram/Resources/telegram_mac.qrc index 8feea0191..03585ec03 100644 --- a/Telegram/Resources/telegram_mac.qrc +++ b/Telegram/Resources/telegram_mac.qrc @@ -1,5 +1,2 @@ - - art/osxtray.png - diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 348614fec..10d1567bb 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,10,20,0 - PRODUCTVERSION 0,10,20,0 + FILEVERSION 0,10,27,0 + PRODUCTVERSION 0,10,27,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -51,10 +51,10 @@ BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Telegram Messenger LLP" - VALUE "FileVersion", "0.10.20.0" + VALUE "FileVersion", "0.10.27.0" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.10.20.0" + VALUE "ProductVersion", "0.10.27.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index c53259b83..5bec209f2 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,10,20,0 - PRODUCTVERSION 0,10,20,0 + FILEVERSION 0,10,27,0 + PRODUCTVERSION 0,10,27,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -43,10 +43,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram Messenger LLP" VALUE "FileDescription", "Telegram Updater" - VALUE "FileVersion", "0.10.20.0" + VALUE "FileVersion", "0.10.27.0" VALUE "LegalCopyright", "Copyright (C) 2014-2016" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.10.20.0" + VALUE "ProductVersion", "0.10.27.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/_other/genlang.cpp b/Telegram/SourceFiles/_other/genlang.cpp index 17cb66065..ea0f80706 100644 --- a/Telegram/SourceFiles/_other/genlang.cpp +++ b/Telegram/SourceFiles/_other/genlang.cpp @@ -542,7 +542,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\ tcpp << "\t\tfor (const char *v = key.constData() + from, *e = v + len; v != e; ++v, ++value) {\n"; tcpp << "\t\t\tif (*v != *value) return false;\n"; tcpp << "\t\t}\n"; - tcpp << "\t\treturn true; \n"; + tcpp << "\t\treturn true;\n"; tcpp << "\t}\n"; tcpp << "}\n\n"; diff --git a/Telegram/SourceFiles/_other/packer.cpp b/Telegram/SourceFiles/_other/packer.cpp index 8203e2019..48e0a8b05 100644 --- a/Telegram/SourceFiles/_other/packer.cpp +++ b/Telegram/SourceFiles/_other/packer.cpp @@ -56,79 +56,83 @@ QString countBetaVersionSignature(quint64 version); typedef unsigned char uchar; typedef unsigned int uint32; typedef signed int int32; + namespace{ - inline uint32 sha1Shift(uint32 v, uint32 shift) { - return ((v << shift) | (v >> (32 - shift))); - } - void sha1PartHash(uint32 *sha, uint32 *temp) - { - uint32 a = sha[0], b = sha[1], c = sha[2], d = sha[3], e = sha[4], round = 0; - #define _shiftswap(f, v) { \ - uint32 t = sha1Shift(a, 5) + (f) + e + v + temp[round]; \ - e = d; \ - d = c; \ - c = sha1Shift(b, 30); \ - b = a; \ - a = t; \ - ++round; \ - } - #define _shiftshiftswap(f, v) { \ - temp[round] = sha1Shift((temp[round - 3] ^ temp[round - 8] ^ temp[round - 14] ^ temp[round - 16]), 1); \ - _shiftswap(f, v) \ - } - - while (round < 16) _shiftswap((b & c) | (~b & d), 0x5a827999) - while (round < 20) _shiftshiftswap((b & c) | (~b & d), 0x5a827999) - while (round < 40) _shiftshiftswap(b ^ c ^ d, 0x6ed9eba1) - while (round < 60) _shiftshiftswap((b & c) | (b & d) | (c & d), 0x8f1bbcdc) - while (round < 80) _shiftshiftswap(b ^ c ^ d, 0xca62c1d6) - - #undef _shiftshiftswap - #undef _shiftswap - - sha[0] += a; - sha[1] += b; - sha[2] += c; - sha[3] += d; - sha[4] += e; - } +inline uint32 sha1Shift(uint32 v, uint32 shift) { + return ((v << shift) | (v >> (32 - shift))); } +void sha1PartHash(uint32 *sha, uint32 *temp) { + uint32 a = sha[0], b = sha[1], c = sha[2], d = sha[3], e = sha[4], round = 0; + +#define _shiftswap(f, v) { \ + uint32 t = sha1Shift(a, 5) + (f) + e + v + temp[round]; \ + e = d; \ + d = c; \ + c = sha1Shift(b, 30); \ + b = a; \ + a = t; \ + ++round; \ + } + +#define _shiftshiftswap(f, v) { \ + temp[round] = sha1Shift((temp[round - 3] ^ temp[round - 8] ^ temp[round - 14] ^ temp[round - 16]), 1); \ + _shiftswap(f, v) \ + } + + while (round < 16) _shiftswap((b & c) | (~b & d), 0x5a827999) + while (round < 20) _shiftshiftswap((b & c) | (~b & d), 0x5a827999) + while (round < 40) _shiftshiftswap(b ^ c ^ d, 0x6ed9eba1) + while (round < 60) _shiftshiftswap((b & c) | (b & d) | (c & d), 0x8f1bbcdc) + while (round < 80) _shiftshiftswap(b ^ c ^ d, 0xca62c1d6) + +#undef _shiftshiftswap +#undef _shiftswap + + sha[0] += a; + sha[1] += b; + sha[2] += c; + sha[3] += d; + sha[4] += e; +} + +} // namespace + int32 *hashSha1(const void *data, uint32 len, void *dest) { const uchar *buf = (const uchar *)data; - uint32 temp[80], block = 0, end; - uint32 sha[5] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0}; - for (end = block + 64; block + 64 <= len; end = block + 64) { - for (uint32 i = 0; block < end; block += 4) { - temp[i++] = (uint32) buf[block + 3] - | (((uint32) buf[block + 2]) << 8) - | (((uint32) buf[block + 1]) << 16) - | (((uint32) buf[block]) << 24); - } - sha1PartHash(sha, temp); - } + uint32 temp[80], block = 0, end; + uint32 sha[5] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0}; + for (end = block + 64; block + 64 <= len; end = block + 64) { + for (uint32 i = 0; block < end; block += 4) { + temp[i++] = (uint32) buf[block + 3] + | (((uint32) buf[block + 2]) << 8) + | (((uint32) buf[block + 1]) << 16) + | (((uint32) buf[block]) << 24); + } + sha1PartHash(sha, temp); + } - end = len - block; + end = len - block; memset(temp, 0, sizeof(uint32) * 16); - uint32 last = 0; - for (; last < end; ++last) { - temp[last >> 2] |= (uint32)buf[last + block] << ((3 - (last & 0x03)) << 3); - } - temp[last >> 2] |= 0x80 << ((3 - (last & 3)) << 3); - if (end >= 56) { - sha1PartHash(sha, temp); + uint32 last = 0; + for (; last < end; ++last) { + temp[last >> 2] |= (uint32)buf[last + block] << ((3 - (last & 0x03)) << 3); + } + temp[last >> 2] |= 0x80 << ((3 - (last & 3)) << 3); + if (end >= 56) { + sha1PartHash(sha, temp); memset(temp, 0, sizeof(uint32) * 16); - } - temp[15] = len << 3; - sha1PartHash(sha, temp); + } + temp[15] = len << 3; + sha1PartHash(sha, temp); uchar *sha1To = (uchar*)dest; - for (int32 i = 19; i >= 0; --i) { - sha1To[i] = (sha[i >> 2] >> (((3 - i) & 0x03) << 3)) & 0xFF; - } + for (int32 i = 19; i >= 0; --i) { + sha1To[i] = (sha[i >> 2] >> (((3 - i) & 0x03) << 3)) & 0xFF; + } return (int32*)sha1To; } diff --git a/Telegram/SourceFiles/_other/updater.cpp b/Telegram/SourceFiles/_other/updater.cpp index f70e65ca8..ef9f4a624 100644 --- a/Telegram/SourceFiles/_other/updater.cpp +++ b/Telegram/SourceFiles/_other/updater.cpp @@ -468,13 +468,13 @@ static const WCHAR *_exeName = L"Updater.exe"; LPTOP_LEVEL_EXCEPTION_FILTER _oldWndExceptionFilter = 0; typedef BOOL (FAR STDAPICALLTYPE *t_miniDumpWriteDump)( - _In_ HANDLE hProcess, - _In_ DWORD ProcessId, - _In_ HANDLE hFile, - _In_ MINIDUMP_TYPE DumpType, - _In_opt_ PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, - _In_opt_ PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, - _In_opt_ PMINIDUMP_CALLBACK_INFORMATION CallbackParam + _In_ HANDLE hProcess, + _In_ DWORD ProcessId, + _In_ HANDLE hFile, + _In_ MINIDUMP_TYPE DumpType, + _In_opt_ PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + _In_opt_ PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + _In_opt_ PMINIDUMP_CALLBACK_INFORMATION CallbackParam ); t_miniDumpWriteDump miniDumpWriteDump = 0; @@ -483,7 +483,7 @@ HANDLE _generateDumpFileAtPath(const WCHAR *path) { WCHAR szPath[maxFileLen]; wsprintf(szPath, L"%stdata\\", path); - if (!CreateDirectory(szPath, NULL)) { + if (!CreateDirectory(szPath, NULL)) { if (GetLastError() != ERROR_ALREADY_EXISTS) { return 0; } @@ -495,7 +495,7 @@ HANDLE _generateDumpFileAtPath(const WCHAR *path) { } } - WCHAR szFileName[maxFileLen]; + WCHAR szFileName[maxFileLen]; WCHAR szExeName[maxFileLen]; wcscpy_s(szExeName, _exeName); @@ -504,16 +504,16 @@ HANDLE _generateDumpFileAtPath(const WCHAR *path) { wsprintf(dotFrom, L""); } - SYSTEMTIME stLocalTime; + SYSTEMTIME stLocalTime; - GetLocalTime(&stLocalTime); + GetLocalTime(&stLocalTime); - wsprintf(szFileName, L"%s%s-%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp", - szPath, szExeName, updaterVersionStr, - stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay, - stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond, - GetCurrentProcessId(), GetCurrentThreadId()); - return CreateFile(szFileName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0); + wsprintf(szFileName, L"%s%s-%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp", + szPath, szExeName, updaterVersionStr, + stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay, + stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond, + GetCurrentProcessId(), GetCurrentThreadId()); + return CreateFile(szFileName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0); } void _generateDump(EXCEPTION_POINTERS* pExceptionPointers) { @@ -553,16 +553,16 @@ void _generateDump(EXCEPTION_POINTERS* pExceptionPointers) { } MINIDUMP_EXCEPTION_INFORMATION ExpParam = {0}; - ExpParam.ThreadId = GetCurrentThreadId(); - ExpParam.ExceptionPointers = pExceptionPointers; - ExpParam.ClientPointers = TRUE; + ExpParam.ThreadId = GetCurrentThreadId(); + ExpParam.ExceptionPointers = pExceptionPointers; + ExpParam.ClientPointers = TRUE; - miniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpWithDataSegs, &ExpParam, NULL, NULL); + miniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpWithDataSegs, &ExpParam, NULL, NULL); } LONG CALLBACK _exceptionFilter(EXCEPTION_POINTERS* pExceptionPointers) { _generateDump(pExceptionPointers); - return _oldWndExceptionFilter ? (*_oldWndExceptionFilter)(pExceptionPointers) : EXCEPTION_CONTINUE_SEARCH; + return _oldWndExceptionFilter ? (*_oldWndExceptionFilter)(pExceptionPointers) : EXCEPTION_CONTINUE_SEARCH; } // see http://www.codeproject.com/Articles/154686/SetUnhandledExceptionFilter-and-the-C-C-Runtime-Li diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 05c4cdef8..a646ae8c6 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -30,10 +30,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "historywidget.h" #include "localstorage.h" #include "boxes/confirmbox.h" +#include "window/window_theme.h" ApiWrap::ApiWrap(QObject *parent) : QObject(parent) , _messageDataResolveDelayed(new SingleDelayedCall(this, "resolveMessageDatas")) { - App::initBackground(); + Window::Theme::Background()->start(); connect(&_webPagesTimer, SIGNAL(timeout()), this, SLOT(resolveWebPages())); connect(&_draftsSaveTimer, SIGNAL(timeout()), this, SLOT(saveDraftsToCloud())); @@ -42,10 +43,10 @@ ApiWrap::ApiWrap(QObject *parent) : QObject(parent) void ApiWrap::init() { } -void ApiWrap::requestMessageData(ChannelData *channel, MsgId msgId, RequestMessageDataCallback &&callback) { +void ApiWrap::requestMessageData(ChannelData *channel, MsgId msgId, const RequestMessageDataCallback &callback) { MessageDataRequest &req(channel ? _channelMessageDataRequests[channel][msgId] : _messageDataRequests[msgId]); if (callback) { - req.callbacks.append(std_::move(callback)); + req.callbacks.append(callback); } if (!req.req) _messageDataResolveDelayed->call(); } @@ -324,7 +325,10 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt } void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result, mtpRequestId req) { - const auto &d(result.c_userFull()); + auto user = peer->asUser(); + t_assert(user != nullptr); + + auto &d = result.c_userFull(); App::feedUsers(MTP_vector(1, d.vuser)); if (d.has_profile_photo()) { App::feedPhoto(d.vprofile_photo); @@ -335,12 +339,13 @@ void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result, mtpRequestI } if (d.has_bot_info()) { - peer->asUser()->setBotInfo(d.vbot_info); + user->setBotInfo(d.vbot_info); } else { - peer->asUser()->setBotInfoVersion(-1); + user->setBotInfoVersion(-1); } - peer->asUser()->setBlockStatus(d.is_blocked() ? UserData::BlockStatus::Blocked : UserData::BlockStatus::NotBlocked); - peer->asUser()->setAbout(d.has_about() ? qs(d.vabout) : QString()); + user->setBlockStatus(d.is_blocked() ? UserData::BlockStatus::Blocked : UserData::BlockStatus::NotBlocked); + user->setAbout(d.has_about() ? qs(d.vabout) : QString()); + user->setCommonChatsCount(d.vcommon_chats_count.v); if (req) { QMap::iterator i = _fullPeerRequests.find(peer); @@ -421,15 +426,15 @@ void ApiWrap::requestBots(ChannelData *peer) { void ApiWrap::gotChat(PeerData *peer, const MTPmessages_Chats &result) { _peerRequests.remove(peer); - if (result.type() == mtpc_messages_chats) { - const auto &v(result.c_messages_chats().vchats.c_vector().v); + if (auto chats = Api::getChatsFromMessagesChats(result)) { + auto &v = chats->c_vector().v; bool badVersion = false; if (peer->isChat()) { badVersion = (!v.isEmpty() && v.at(0).type() == mtpc_chat && v.at(0).c_chat().vversion.v < peer->asChat()->version); } else if (peer->isChannel()) { badVersion = (!v.isEmpty() && v.at(0).type() == mtpc_channel && v.at(0).c_chat().vversion.v < peer->asChannel()->version); } - PeerData *chat = App::feedChats(result.c_messages_chats().vchats); + auto chat = App::feedChats(*chats); if (chat == peer) { if (badVersion) { if (peer->isChat()) { @@ -452,9 +457,8 @@ void ApiWrap::gotUser(PeerData *peer, const MTPVector &result) { } void ApiWrap::gotChats(const MTPmessages_Chats &result) { - if (result.type() == mtpc_messages_chats) { - auto &d = result.c_messages_chats(); - App::feedChats(d.vchats); + if (auto chats = Api::getChatsFromMessagesChats(result)) { + App::feedChats(*chats); } } @@ -691,6 +695,109 @@ void ApiWrap::requestStickerSets() { } } +void ApiWrap::saveStickerSets(const Stickers::Order &localOrder, const Stickers::Order &localRemoved) { + for (auto requestId : base::take(_stickerSetDisenableRequests)) { + MTP::cancel(requestId); + } + MTP::cancel(base::take(_stickersReorderRequestId)); + MTP::cancel(base::take(_stickersClearRecentRequestId)); + + auto writeInstalled = true, writeRecent = false, writeCloudRecent = false, writeArchived = false; + auto &recent = cGetRecentStickers(); + auto &sets = Global::RefStickerSets(); + + _stickersOrder = localOrder; + for_const (auto removedSetId, localRemoved) { + if (removedSetId == Stickers::CloudRecentSetId) { + if (sets.remove(Stickers::CloudRecentSetId) != 0) { + writeCloudRecent = true; + } + if (sets.remove(Stickers::CustomSetId)) { + writeInstalled = true; + } + if (!recent.isEmpty()) { + recent.clear(); + writeRecent = true; + } + + MTPmessages_ClearRecentStickers::Flags flags = 0; + _stickersClearRecentRequestId = MTP::send(MTPmessages_ClearRecentStickers(MTP_flags(flags)), rpcDone(&ApiWrap::stickersClearRecentDone), rpcFail(&ApiWrap::stickersClearRecentFail)); + continue; + } + + auto it = sets.find(removedSetId); + if (it != sets.cend()) { + for (auto i = recent.begin(); i != recent.cend();) { + if (it->stickers.indexOf(i->first) >= 0) { + i = recent.erase(i); + writeRecent = true; + } else { + ++i; + } + } + if (!(it->flags & MTPDstickerSet::Flag::f_archived)) { + MTPInputStickerSet setId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName)); + _stickerSetDisenableRequests.insert(MTP::send(MTPmessages_UninstallStickerSet(setId), rpcDone(&ApiWrap::stickerSetDisenableDone), rpcFail(&ApiWrap::stickerSetDisenableFail), 0, 5)); + int removeIndex = Global::StickerSetsOrder().indexOf(it->id); + if (removeIndex >= 0) Global::RefStickerSetsOrder().removeAt(removeIndex); + if (!(it->flags & MTPDstickerSet_ClientFlag::f_featured) && !(it->flags & MTPDstickerSet_ClientFlag::f_special)) { + sets.erase(it); + } else { + if (it->flags & MTPDstickerSet::Flag::f_archived) { + writeArchived = true; + } + it->flags &= ~(MTPDstickerSet::Flag::f_installed | MTPDstickerSet::Flag::f_archived); + } + } + } + } + + // Clear all installed flags, set only for sets from order. + for (auto &set : sets) { + if (!(set.flags & MTPDstickerSet::Flag::f_archived)) { + set.flags &= ~MTPDstickerSet::Flag::f_installed; + } + } + + auto &order(Global::RefStickerSetsOrder()); + order.clear(); + for_const (auto setId, _stickersOrder) { + auto it = sets.find(setId); + if (it != sets.cend()) { + if ((it->flags & MTPDstickerSet::Flag::f_archived) && !localRemoved.contains(it->id)) { + MTPInputStickerSet mtpSetId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName)); + _stickerSetDisenableRequests.insert(MTP::send(MTPmessages_InstallStickerSet(mtpSetId, MTP_boolFalse()), rpcDone(&ApiWrap::stickerSetDisenableDone), rpcFail(&ApiWrap::stickerSetDisenableFail), 0, 5)); + it->flags &= ~MTPDstickerSet::Flag::f_archived; + writeArchived = true; + } + order.push_back(setId); + it->flags |= MTPDstickerSet::Flag::f_installed; + } + } + for (auto it = sets.begin(); it != sets.cend();) { + if ((it->flags & MTPDstickerSet_ClientFlag::f_featured) + || (it->flags & MTPDstickerSet::Flag::f_installed) + || (it->flags & MTPDstickerSet::Flag::f_archived) + || (it->flags & MTPDstickerSet_ClientFlag::f_special)) { + ++it; + } else { + it = sets.erase(it); + } + } + + if (writeInstalled) Local::writeInstalledStickers(); + if (writeRecent) Local::writeUserSettings(); + if (writeArchived) Local::writeArchivedStickers(); + if (writeCloudRecent) Local::writeRecentStickers(); + emit App::main()->stickersUpdated(); + + if (_stickerSetDisenableRequests.isEmpty()) { + stickersSaveOrder(); + } else { + MTP::sendAnything(); + } +} + void ApiWrap::joinChannel(ChannelData *channel) { if (channel->amIn()) { channelAmInUpdated(channel); @@ -723,7 +830,7 @@ bool ApiWrap::channelAmInFail(ChannelData *channel, const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; if (error.type() == qstr("CHANNELS_TOO_MUCH")) { - Ui::showLayer(new InformBox(lang(lng_join_channel_error))); + Ui::show(Box(lang(lng_join_channel_error))); } _channelAmInRequests.remove(channel); return true; @@ -1177,5 +1284,57 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs } ApiWrap::~ApiWrap() { - App::clearHistories(); +} + +void ApiWrap::stickerSetDisenableDone(const MTPmessages_StickerSetInstallResult &result, mtpRequestId req) { + _stickerSetDisenableRequests.remove(req); + if (_stickerSetDisenableRequests.isEmpty()) { + stickersSaveOrder(); + } +} + +bool ApiWrap::stickerSetDisenableFail(const RPCError &error, mtpRequestId req) { + if (MTP::isDefaultHandledError(error)) return false; + _stickerSetDisenableRequests.remove(req); + if (_stickerSetDisenableRequests.isEmpty()) { + stickersSaveOrder(); + } + return true; +} + +void ApiWrap::stickersSaveOrder() { + if (_stickersOrder.size() > 1) { + QVector mtpOrder; + mtpOrder.reserve(_stickersOrder.size()); + for_const (auto setId, _stickersOrder) { + mtpOrder.push_back(MTP_long(setId)); + } + + MTPmessages_ReorderStickerSets::Flags flags = 0; + _stickersReorderRequestId = MTP::send(MTPmessages_ReorderStickerSets(MTP_flags(flags), MTP_vector(mtpOrder)), rpcDone(&ApiWrap::stickersReorderDone), rpcFail(&ApiWrap::stickersReorderFail)); + } else { + stickersReorderDone(MTP_boolTrue()); + } +} + +void ApiWrap::stickersReorderDone(const MTPBool &result) { + _stickersReorderRequestId = 0; +} + +bool ApiWrap::stickersReorderFail(const RPCError &result) { + if (MTP::isDefaultHandledError(result)) return false; + _stickersReorderRequestId = 0; + Global::SetLastStickersUpdate(0); + App::main()->updateStickers(); + return true; +} + +void ApiWrap::stickersClearRecentDone(const MTPBool &result) { + _stickersClearRecentRequestId = 0; +} + +bool ApiWrap::stickersClearRecentFail(const RPCError &result) { + if (MTP::isDefaultHandledError(result)) return false; + _stickersClearRecentRequestId = 0; + return true; } diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 23e453bee..74f9043a5 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -22,16 +22,27 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "core/single_timer.h" +namespace Api { + +inline const MTPVector *getChatsFromMessagesChats(const MTPmessages_Chats &chats) { + switch (chats.type()) { + case mtpc_messages_chats: return &chats.c_messages_chats().vchats; + case mtpc_messages_chatsSlice: return &chats.c_messages_chatsSlice().vchats; + } + return nullptr; +} + +} // namespace Api + class ApiWrap : public QObject, public RPCSender { Q_OBJECT public: - ApiWrap(QObject *parent); void init(); - using RequestMessageDataCallback = base::lambda_wrap; - void requestMessageData(ChannelData *channel, MsgId msgId, RequestMessageDataCallback &&callback); + using RequestMessageDataCallback = base::lambda_copy; + void requestMessageData(ChannelData *channel, MsgId msgId, const RequestMessageDataCallback &callback); void requestFullPeer(PeerData *peer); void requestPeer(PeerData *peer); @@ -51,6 +62,7 @@ public: void scheduleStickerSetRequest(uint64 setId, uint64 access); void requestStickerSets(); + void saveStickerSets(const Stickers::Order &localOrder, const Stickers::Order &localRemoved); void joinChannel(ChannelData *channel); void leaveChannel(ChannelData *channel); @@ -67,11 +79,9 @@ public: ~ApiWrap(); signals: - void fullPeerUpdated(PeerData *peer); public slots: - void resolveMessageDatas(); void resolveWebPages(); @@ -79,7 +89,6 @@ public slots: void saveDraftsToCloud(); private: - void updatesReceived(const MTPUpdates &updates); void gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId req); @@ -158,4 +167,16 @@ private: void saveCloudDraftDone(History *history, const MTPBool &result, mtpRequestId requestId); bool saveCloudDraftFail(History *history, const RPCError &error, mtpRequestId requestId); + OrderedSet _stickerSetDisenableRequests; + void stickerSetDisenableDone(const MTPmessages_StickerSetInstallResult &result, mtpRequestId req); + bool stickerSetDisenableFail(const RPCError &error, mtpRequestId req); + Stickers::Order _stickersOrder; + mtpRequestId _stickersReorderRequestId = 0; + void stickersSaveOrder(); + void stickersReorderDone(const MTPBool &result); + bool stickersReorderFail(const RPCError &result); + mtpRequestId _stickersClearRecentRequestId = 0; + void stickersClearRecentDone(const MTPBool &result); + bool stickersClearRecentFail(const RPCError &result); + }; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 6bfed1347..282bef664 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -27,6 +27,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_overview.h" #include "styles/style_mediaview.h" +#include "styles/style_stickers.h" +#include "styles/style_history.h" +#include "styles/style_boxes.h" #include "lang.h" #include "data/data_abstract_structure.h" #include "history/history_service_layout.h" @@ -41,7 +44,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "apiwrap.h" #include "numbers.h" #include "observer_peer.h" -#include "window/chat_background.h" +#include "window/window_theme.h" #include "window/notifications_manager.h" #include "platform/platform_notifications_manager.h" @@ -126,14 +129,6 @@ namespace { LastPhotosList lastPhotos; using LastPhotosMap = QHash; LastPhotosMap lastPhotosMap; - - style::color _msgServiceBg; - style::color _msgServiceSelectBg; - style::color _historyScrollBarColor; - style::color _historyScrollBgColor; - style::color _historyScrollBarOverColor; - style::color _historyScrollBgOverColor; - style::color _introPointHoverColor; } namespace App { @@ -206,10 +201,11 @@ namespace { if (auto w = wnd()) { w->tempDirDelete(Local::ClearManagerAll); w->notifyClearFast(); - w->setupIntro(true); + w->setupIntro(); } MTP::setAuthedId(0); Local::reset(); + Window::Theme::Background()->reset(); cSetOtherOnline(0); histories().clear(); @@ -220,7 +216,6 @@ namespace { clearStorageImages(); if (auto w = wnd()) { w->updateConnectingStatus(); - w->getTitle()->updateControlsVisibility(); } return true; } @@ -1252,7 +1247,7 @@ namespace { if (auto history = App::historyLoaded(peer)) { history->outboxRead(upTo); if (history->lastMsg && history->lastMsg->out() && history->lastMsg->id <= upTo) { - if (App::main()) App::main()->dlgUpdated(history, history->lastMsg->id); + if (App::main()) App::main()->dlgUpdated(history->peer, history->lastMsg->id); } history->updateChatListEntry(); @@ -1519,6 +1514,7 @@ namespace { return page; } break; case mtpc_webPagePending: return App::feedWebPage(webpage.c_webPagePending()); + case mtpc_webPageNotModified: LOG(("API Error: webPageNotModified is unexpected in feedWebPage().")); break; } return nullptr; } @@ -2099,7 +2095,10 @@ namespace { Global::SetLastStickersUpdate(0); Global::SetLastRecentStickersUpdate(0); Global::SetFeaturedStickerSetsOrder(Stickers::Order()); - Global::SetFeaturedStickerSetsUnreadCount(0); + if (Global::FeaturedStickerSetsUnreadCount() != 0) { + Global::SetFeaturedStickerSetsUnreadCount(0); + Global::RefFeaturedStickerSetsUnreadCountChanged().notify(); + } Global::SetLastFeaturedStickersUpdate(0); Global::SetArchivedStickerSetsOrder(Stickers::Order()); cSetSavedGifs(SavedGifs()); @@ -2164,21 +2163,22 @@ namespace { text = d.second; } - void prepareCorners(RoundCorners index, int32 radius, const style::color &color, const style::color *shadow = 0, QImage *cors = 0) { + void prepareCorners(RoundCorners index, int32 radius, const QBrush &brush, const style::color *shadow = nullptr, QImage *cors = nullptr) { int32 r = radius * cIntRetinaFactor(), s = st::msgShadow * cIntRetinaFactor(); QImage rect(r * 3, r * 3 + (shadow ? s : 0), QImage::Format_ARGB32_Premultiplied), localCors[4]; { - QPainter p(&rect); + Painter p(&rect); + PainterHighQualityEnabler hq(p); + p.setCompositionMode(QPainter::CompositionMode_Source); - p.fillRect(QRect(0, 0, rect.width(), rect.height()), st::transparent->b); + p.fillRect(QRect(0, 0, rect.width(), rect.height()), Qt::transparent); p.setCompositionMode(QPainter::CompositionMode_SourceOver); - p.setRenderHint(QPainter::HighQualityAntialiasing); p.setPen(Qt::NoPen); if (shadow) { p.setBrush((*shadow)->b); p.drawRoundedRect(0, s, r * 3, r * 3, r, r); } - p.setBrush(color->b); + p.setBrush(brush); p.drawRoundedRect(0, 0, r * 3, r * 3, r, r); } if (!cors) cors = localCors; @@ -2204,12 +2204,72 @@ namespace { int msgRadius() { static int MsgRadius = ([]() { + return st::historyMessageRadius; auto minMsgHeight = (st::msgPadding.top() + st::msgFont->height + st::msgPadding.bottom()); return minMsgHeight / 2; })(); return MsgRadius; } + void createCorners() { + QImage mask[4]; + prepareCorners(LargeMaskCorners, msgRadius(), QColor(255, 255, 255), nullptr, mask); + for (int i = 0; i < 4; ++i) { + ::cornersMaskLarge[i] = new QImage(mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied)); + ::cornersMaskLarge[i]->setDevicePixelRatio(cRetinaFactor()); + } + prepareCorners(SmallMaskCorners, st::buttonRadius, QColor(255, 255, 255), nullptr, mask); + for (int i = 0; i < 4; ++i) { + ::cornersMaskSmall[i] = new QImage(mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied)); + ::cornersMaskSmall[i]->setDevicePixelRatio(cRetinaFactor()); + } + prepareCorners(MenuCorners, st::buttonRadius, st::menuBg); + prepareCorners(BoxCorners, st::boxRadius, st::boxBg); + prepareCorners(BotKbOverCorners, st::dateRadius, st::msgBotKbOverBgAdd); + prepareCorners(StickerCorners, st::dateRadius, st::msgServiceBg); + prepareCorners(StickerSelectedCorners, st::dateRadius, st::msgServiceBgSelected); + prepareCorners(SelectedOverlaySmallCorners, st::buttonRadius, st::msgSelectOverlay); + prepareCorners(SelectedOverlayLargeCorners, msgRadius(), st::msgSelectOverlay); + prepareCorners(DateCorners, st::dateRadius, st::msgDateImgBg); + prepareCorners(DateSelectedCorners, st::dateRadius, st::msgDateImgBgSelected); + prepareCorners(InShadowCorners, msgRadius(), st::msgInShadow); + prepareCorners(InSelectedShadowCorners, msgRadius(), st::msgInShadowSelected); + prepareCorners(ForwardCorners, msgRadius(), st::historyForwardChooseBg); + prepareCorners(MediaviewSaveCorners, st::mediaviewControllerRadius, st::mediaviewSaveMsgBg); + prepareCorners(EmojiHoverCorners, st::buttonRadius, st::emojiPanHover); + prepareCorners(StickerHoverCorners, st::buttonRadius, st::emojiPanHover); + prepareCorners(BotKeyboardCorners, st::buttonRadius, st::botKbBg); + prepareCorners(BotKeyboardOverCorners, st::buttonRadius, st::botKbOverBg); + prepareCorners(BotKeyboardDownCorners, st::buttonRadius, st::botKbDownBg); + prepareCorners(PhotoSelectOverlayCorners, st::buttonRadius, st::overviewPhotoSelectOverlay); + + prepareCorners(Doc1Corners, st::buttonRadius, st::msgFile1Bg); + prepareCorners(Doc2Corners, st::buttonRadius, st::msgFile2Bg); + prepareCorners(Doc3Corners, st::buttonRadius, st::msgFile3Bg); + prepareCorners(Doc4Corners, st::buttonRadius, st::msgFile4Bg); + + prepareCorners(MessageInCorners, msgRadius(), st::msgInBg, &st::msgInShadow); + prepareCorners(MessageInSelectedCorners, msgRadius(), st::msgInBgSelected, &st::msgInShadowSelected); + prepareCorners(MessageOutCorners, msgRadius(), st::msgOutBg, &st::msgOutShadow); + prepareCorners(MessageOutSelectedCorners, msgRadius(), st::msgOutBgSelected, &st::msgOutShadowSelected); + } + + void clearCorners() { + for (int j = 0; j < 4; ++j) { + for (int i = 0; i < RoundCornersCount; ++i) { + delete ::corners[i].p[j]; ::corners[i].p[j] = nullptr; + } + delete ::cornersMaskSmall[j]; ::cornersMaskSmall[j] = nullptr; + delete ::cornersMaskLarge[j]; ::cornersMaskLarge[j] = nullptr; + } + for (auto i = ::cornersMap.cbegin(), e = ::cornersMap.cend(); i != e; ++i) { + for (int j = 0; j < 4; ++j) { + delete i->p[j]; + } + } + ::cornersMap.clear(); + } + void initMedia() { if (!::monofont) { QString family; @@ -2230,44 +2290,32 @@ namespace { if (cRetina()) ::emojiLarge->setDevicePixelRatio(cRetinaFactor()); } - QImage mask[4]; - prepareCorners(LargeMaskCorners, msgRadius(), st::white, nullptr, mask); - for (int i = 0; i < 4; ++i) { - ::cornersMaskLarge[i] = new QImage(mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied)); - ::cornersMaskLarge[i]->setDevicePixelRatio(cRetinaFactor()); - } - prepareCorners(SmallMaskCorners, st::buttonRadius, st::white, nullptr, mask); - for (int i = 0; i < 4; ++i) { - ::cornersMaskSmall[i] = new QImage(mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied)); - ::cornersMaskSmall[i]->setDevicePixelRatio(cRetinaFactor()); - } - prepareCorners(WhiteCorners, st::dateRadius, st::white); - prepareCorners(StickerCorners, st::dateRadius, st::msgServiceBg); - prepareCorners(StickerSelectedCorners, st::dateRadius, st::msgServiceSelectBg); - prepareCorners(SelectedOverlaySmallCorners, st::buttonRadius, st::msgSelectOverlay); - prepareCorners(SelectedOverlayLargeCorners, msgRadius(), st::msgSelectOverlay); - prepareCorners(DateCorners, st::dateRadius, st::msgDateImgBg); - prepareCorners(DateSelectedCorners, st::dateRadius, st::msgDateImgBgSelected); - prepareCorners(InShadowCorners, msgRadius(), st::msgInShadow); - prepareCorners(InSelectedShadowCorners, msgRadius(), st::msgInShadowSelected); - prepareCorners(ForwardCorners, msgRadius(), st::forwardBg); - prepareCorners(MediaviewSaveCorners, st::mediaviewControllerRadius, st::medviewSaveMsg); - prepareCorners(EmojiHoverCorners, st::buttonRadius, st::emojiPanHover); - prepareCorners(StickerHoverCorners, st::buttonRadius, st::emojiPanHover); - prepareCorners(BotKeyboardCorners, st::buttonRadius, st::botKbBg); - prepareCorners(BotKeyboardOverCorners, st::buttonRadius, st::botKbOverBg); - prepareCorners(BotKeyboardDownCorners, st::buttonRadius, st::botKbDownBg); - prepareCorners(PhotoSelectOverlayCorners, st::buttonRadius, st::overviewPhotoSelectOverlay); + createCorners(); - prepareCorners(DocBlueCorners, st::buttonRadius, st::msgFileBlueColor); - prepareCorners(DocGreenCorners, st::buttonRadius, st::msgFileGreenColor); - prepareCorners(DocRedCorners, st::buttonRadius, st::msgFileRedColor); - prepareCorners(DocYellowCorners, st::buttonRadius, st::msgFileYellowColor); + using Update = Window::Theme::BackgroundUpdate; + static auto subscription = Window::Theme::Background()->add_subscription([](const Update &update) { + if (update.paletteChanged()) { + clearCorners(); + createCorners(); - prepareCorners(MessageInCorners, msgRadius(), st::msgInBg, &st::msgInShadow); - prepareCorners(MessageInSelectedCorners, msgRadius(), st::msgInBgSelected, &st::msgInShadowSelected); - prepareCorners(MessageOutCorners, msgRadius(), st::msgOutBg, &st::msgOutShadow); - prepareCorners(MessageOutSelectedCorners, msgRadius(), st::msgOutBgSelected, &st::msgOutShadowSelected); + if (App::main()) { + App::main()->updateScrollColors(); + } + HistoryLayout::serviceColorsUpdated(); + } else if (update.type == Update::Type::New) { + for (int i = 0; i < 4; ++i) { + delete ::corners[StickerCorners].p[i]; ::corners[StickerCorners].p[i] = nullptr; + delete ::corners[StickerSelectedCorners].p[i]; ::corners[StickerSelectedCorners].p[i] = nullptr; + } + prepareCorners(StickerCorners, st::dateRadius, st::msgServiceBg); + prepareCorners(StickerSelectedCorners, st::dateRadius, st::msgServiceBgSelected); + + if (App::main()) { + App::main()->updateScrollColors(); + } + HistoryLayout::serviceColorsUpdated(); + } + }); } void clearHistories() { @@ -2284,22 +2332,12 @@ namespace { void deinitMedia() { delete ::emoji; - ::emoji = 0; + ::emoji = nullptr; delete ::emojiLarge; - ::emojiLarge = 0; - for (int j = 0; j < 4; ++j) { - for (int i = 0; i < RoundCornersCount; ++i) { - delete ::corners[i].p[j]; ::corners[i].p[j] = nullptr; - } - delete ::cornersMaskSmall[j]; ::cornersMaskSmall[j] = nullptr; - delete ::cornersMaskLarge[j]; ::cornersMaskLarge[j] = nullptr; - } - for (auto i = ::cornersMap.cbegin(), e = ::cornersMap.cend(); i != e; ++i) { - for (int j = 0; j < 4; ++j) { - delete i->p[j]; - } - } - ::cornersMap.clear(); + ::emojiLarge = nullptr; + + clearCorners(); + mainEmojiMap.clear(); otherEmojiMap.clear(); @@ -2369,10 +2407,6 @@ namespace { return ::monofont; } - const QPixmap &sprite() { - return style::spritePixmap(); - } - const QPixmap &emoji() { return *::emoji; } @@ -2382,7 +2416,7 @@ namespace { } const QPixmap &emojiSingle(EmojiPtr emoji, int32 fontHeight) { - EmojiMap *map = &(fontHeight == st::taDefFlat.font->height ? mainEmojiMap : otherEmojiMap[fontHeight]); + EmojiMap *map = &(fontHeight == st::msgFont->height ? mainEmojiMap : otherEmojiMap[fontHeight]); EmojiMap::const_iterator i = map->constFind(emojiKey(emoji)); if (i == map->cend()) { QImage img(ESize + st::emojiPadding * cIntRetinaFactor() * 2, fontHeight * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); @@ -2459,6 +2493,21 @@ namespace { _launchState = state; } + void restart() { +#ifndef TDESKTOP_DISABLE_AUTOUPDATE + bool updateReady = (Sandbox::updatingState() == Application::UpdatingReady); +#else // !TDESKTOP_DISABLE_AUTOUPDATE + bool updateReady = false; +#endif // else for !TDESKTOP_DISABLE_AUTOUPDATE + if (updateReady) { + cSetRestartingUpdate(true); + } else { + cSetRestarting(true); + cSetRestartingToSettings(true); + } + App::quit(); + } + QImage readImage(QByteArray data, QByteArray *format, bool opaque, bool *animated) { QByteArray tmpFormat; QImage result; @@ -2481,55 +2530,52 @@ namespace { if (!fmt.isEmpty()) *format = fmt; } buffer.seek(0); - QString fmt = QString::fromUtf8(*format).toLower(); + auto fmt = QString::fromUtf8(*format).toLower(); if (fmt == "jpg" || fmt == "jpeg") { #ifdef OS_MAC_OLD - ExifData *exifData = exif_data_new_from_data((const uchar*)(data.constData()), data.size()); - if (exifData) { - ExifByteOrder byteOrder = exif_data_get_byte_order(exifData); - ExifEntry *exifEntry = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION); - if (exifEntry) { - QTransform orientationFix; - int orientation = exif_get_short(exifEntry->data, byteOrder); - switch (orientation) { - case 2: orientationFix = QTransform(-1, 0, 0, 1, 0, 0); break; - case 3: orientationFix = QTransform(-1, 0, 0, -1, 0, 0); break; - case 4: orientationFix = QTransform(1, 0, 0, -1, 0, 0); break; - case 5: orientationFix = QTransform(0, -1, -1, 0, 0, 0); break; - case 6: orientationFix = QTransform(0, 1, -1, 0, 0, 0); break; - case 7: orientationFix = QTransform(0, 1, 1, 0, 0, 0); break; - case 8: orientationFix = QTransform(0, -1, 1, 0, 0, 0); break; - } - result = result.transformed(orientationFix); + if (auto exifData = exif_data_new_from_data((const uchar*)(data.constData()), data.size())) { + auto byteOrder = exif_data_get_byte_order(exifData); + if (auto exifEntry = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION)) { + auto orientationFix = [exifEntry, byteOrder] { + auto orientation = exif_get_short(exifEntry->data, byteOrder); + switch (orientation) { + case 2: return QTransform(-1, 0, 0, 1, 0, 0); + case 3: return QTransform(-1, 0, 0, -1, 0, 0); + case 4: return QTransform(1, 0, 0, -1, 0, 0); + case 5: return QTransform(0, -1, -1, 0, 0, 0); + case 6: return QTransform(0, 1, -1, 0, 0, 0); + case 7: return QTransform(0, 1, 1, 0, 0, 0); + case 8: return QTransform(0, -1, 1, 0, 0, 0); + } + return QTransform(); + }; + result = result.transformed(orientationFix()); } exif_data_free(exifData); } #endif // OS_MAC_OLD - } else if (opaque && result.hasAlphaChannel()) { - QImage solid(result.width(), result.height(), QImage::Format_ARGB32_Premultiplied); - solid.fill(st::white->c); - { - QPainter(&solid).drawImage(0, 0, result); - } - result = solid; + } else if (opaque) { + result = Images::prepareOpaque(std_::move(result)); } return result; } QImage readImage(const QString &file, QByteArray *format, bool opaque, bool *animated, QByteArray *content) { QFile f(file); - if (!f.open(QIODevice::ReadOnly)) { + if (f.size() > kImageSizeLimit || !f.open(QIODevice::ReadOnly)) { if (animated) *animated = false; return QImage(); } - QByteArray img = f.readAll(); - QImage result = readImage(img, format, opaque, animated); - if (content && !result.isNull()) *content = img; + auto imageBytes = f.readAll(); + auto result = readImage(imageBytes, format, opaque, animated); + if (content && !result.isNull()) { + *content = imageBytes; + } return result; } QPixmap pixmapFromImageInPlace(QImage &&image) { - return QPixmap::fromImage(std_::forward(image), Qt::ColorOnly); + return QPixmap::fromImage(std_::move(image), Qt::ColorOnly); } void regPhotoItem(PhotoData *data, HistoryItem *item) { @@ -2701,6 +2747,37 @@ namespace { #endif // !TDESKTOP_DISABLE_NETWORK_PROXY } + void complexAdjustRect(ImageRoundCorners corners, QRect &rect, RectParts &parts) { + if (corners & ImageRoundCorner::TopLeft) { + if (!(corners & ImageRoundCorner::BottomLeft)) { + parts = RectPart::NoTopBottom | RectPart::TopFull; + rect.setHeight(rect.height() + msgRadius()); + } + } else if (corners & ImageRoundCorner::BottomLeft) { + parts = RectPart::NoTopBottom | RectPart::BottomFull; + rect.setTop(rect.y() - msgRadius()); + } else { + parts = RectPart::NoTopBottom; + rect.setTop(rect.y() - msgRadius()); + rect.setHeight(rect.height() + msgRadius()); + } + } + + void complexOverlayRect(Painter &p, QRect rect, ImageRoundRadius radius, ImageRoundCorners corners) { + auto overlayCorners = (radius == ImageRoundRadius::Small) ? SelectedOverlaySmallCorners : SelectedOverlayLargeCorners; + auto overlayParts = RectPart::Full | RectPart::None; + if (radius == ImageRoundRadius::Large) { + complexAdjustRect(corners, rect, overlayParts); + } + roundRect(p, rect, p.textPalette().selectOverlay, overlayCorners, nullptr, overlayParts); + } + + void complexLocationRect(Painter &p, QRect rect, ImageRoundRadius radius, ImageRoundCorners corners) { + auto parts = RectPart::Full | RectPart::None; + complexAdjustRect(corners, rect, parts); + roundRect(p, rect, st::msgInBg, MessageInCorners, nullptr, parts); + } + QImage **cornersMask(ImageRoundRadius radius) { switch (radius) { case ImageRoundRadius::Large: return ::cornersMaskLarge; @@ -2709,40 +2786,75 @@ namespace { } return ::cornersMaskSmall; } - void roundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, const style::color &bg, const CornersPixmaps &c, const style::color *sh) { - int32 cw = c.p[0]->width() / cIntRetinaFactor(), ch = c.p[0]->height() / cIntRetinaFactor(); - if (w < 2 * cw || h < 2 * ch) return; - if (w > 2 * cw) { - p.fillRect(QRect(x + cw, y, w - 2 * cw, ch), bg->b); - p.fillRect(QRect(x + cw, y + h - ch, w - 2 * cw, ch), bg->b); - if (sh) p.fillRect(QRect(x + cw, y + h, w - 2 * cw, st::msgShadow), (*sh)->b); + + void roundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, const CornersPixmaps &corner, const style::color *shadow, RectParts parts) { + auto cornerWidth = corner.p[0]->width() / cIntRetinaFactor(); + auto cornerHeight = corner.p[0]->height() / cIntRetinaFactor(); + if (w < 2 * cornerWidth || h < 2 * cornerHeight) return; + if (w > 2 * cornerWidth) { + if (parts & RectPart::Top) { + p.fillRect(x + cornerWidth, y, w - 2 * cornerWidth, cornerHeight, bg); + } + if (parts & RectPart::Bottom) { + p.fillRect(x + cornerWidth, y + h - cornerHeight, w - 2 * cornerWidth, cornerHeight, bg); + if (shadow) { + p.fillRect(x + cornerWidth, y + h, w - 2 * cornerWidth, st::msgShadow, *shadow); + } + } } - if (h > 2 * ch) { - p.fillRect(QRect(x, y + ch, w, h - 2 * ch), bg->b); + if (h > 2 * cornerHeight) { + if ((parts & RectPart::NoTopBottom) == qFlags(RectPart::NoTopBottom)) { + p.fillRect(x, y + cornerHeight, w, h - 2 * cornerHeight, bg); + } else { + if (parts & RectPart::Left) { + p.fillRect(x, y + cornerHeight, cornerWidth, h - 2 * cornerHeight, bg); + } + if ((parts & RectPart::Center) && w > 2 * cornerWidth) { + p.fillRect(x + cornerWidth, y + cornerHeight, w - 2 * cornerWidth, h - 2 * cornerHeight, bg); + } + if (parts & RectPart::Right) { + p.fillRect(x + w - cornerWidth, y + cornerHeight, cornerWidth, h - 2 * cornerHeight, bg); + } + } + } + if (parts & RectPart::TopLeft) { + p.drawPixmap(x, y, *corner.p[0]); + } + if (parts & RectPart::TopRight) { + p.drawPixmap(x + w - cornerWidth, y, *corner.p[1]); + } + if (parts & RectPart::BottomLeft) { + p.drawPixmap(x, y + h - cornerHeight, *corner.p[2]); + } + if (parts & RectPart::BottomRight) { + p.drawPixmap(x + w - cornerWidth, y + h - cornerHeight, *corner.p[3]); } - p.drawPixmap(QPoint(x, y), *c.p[0]); - p.drawPixmap(QPoint(x + w - cw, y), *c.p[1]); - p.drawPixmap(QPoint(x, y + h - ch), *c.p[2]); - p.drawPixmap(QPoint(x + w - cw, y + h - ch), *c.p[3]); } - void roundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, const style::color &bg, RoundCorners index, const style::color *sh) { - roundRect(p, x, y, w, h, bg, ::corners[index], sh); + void roundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, RoundCorners index, const style::color *shadow, RectParts parts) { + roundRect(p, x, y, w, h, bg, ::corners[index], shadow, parts); } - void roundShadow(Painter &p, int32 x, int32 y, int32 w, int32 h, const style::color &sh, RoundCorners index) { - const CornersPixmaps &c = ::corners[index]; - int32 cw = c.p[0]->width() / cIntRetinaFactor(), ch = c.p[0]->height() / cIntRetinaFactor(); - p.fillRect(x + cw, y + h, w - 2 * cw, st::msgShadow, sh->b); - p.fillRect(x, y + h - ch, cw, st::msgShadow, sh->b); - p.fillRect(x + w - cw, y + h - ch, cw, st::msgShadow, sh->b); - p.drawPixmap(x, y + h - ch + st::msgShadow, *c.p[2]); - p.drawPixmap(x + w - cw, y + h - ch + st::msgShadow, *c.p[3]); + void roundShadow(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color shadow, RoundCorners index, RectParts parts) { + auto &corner = ::corners[index]; + auto cornerWidth = corner.p[0]->width() / cIntRetinaFactor(); + auto cornerHeight = corner.p[0]->height() / cIntRetinaFactor(); + if (parts & RectPart::Bottom) { + p.fillRect(x + cornerWidth, y + h, w - 2 * cornerWidth, st::msgShadow, shadow); + } + if (parts & RectPart::BottomLeft) { + p.fillRect(x, y + h - cornerHeight, cornerWidth, st::msgShadow, shadow); + p.drawPixmap(x, y + h - cornerHeight + st::msgShadow, *corner.p[2]); + } + if (parts & RectPart::BottomRight) { + p.fillRect(x + w - cornerWidth, y + h - cornerHeight, cornerWidth, st::msgShadow, shadow); + p.drawPixmap(x + w - cornerWidth, y + h - cornerHeight + st::msgShadow, *corner.p[3]); + } } - void roundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, const style::color &bg, ImageRoundRadius radius) { - uint32 colorKey = ((uint32(bg->c.alpha()) & 0xFF) << 24) | ((uint32(bg->c.red()) & 0xFF) << 16) | ((uint32(bg->c.green()) & 0xFF) << 8) | ((uint32(bg->c.blue()) & 0xFF) << 24); - CornersMap::const_iterator i = cornersMap.find(colorKey); + void roundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, ImageRoundRadius radius, RectParts parts) { + auto colorKey = ((uint32(bg->c.alpha()) & 0xFF) << 24) | ((uint32(bg->c.red()) & 0xFF) << 16) | ((uint32(bg->c.green()) & 0xFF) << 8) | ((uint32(bg->c.blue()) & 0xFF) << 24); + auto i = cornersMap.find(colorKey); if (i == cornersMap.cend()) { QImage images[4]; switch (radius) { @@ -2758,172 +2870,7 @@ namespace { } i = cornersMap.insert(colorKey, pixmaps); } - roundRect(p, x, y, w, h, bg, i.value(), 0); - } - - void initBackground(int32 id, const QImage &p, bool nowrite) { - if (Local::readBackground()) return; - - uint64 components[3] = { 0 }, componentsScroll[3] = { 0 }, componentsPoint[3] = { 0 }; - int size = 0; - - QImage img(p); - bool remove = false; - if (p.isNull()) { - if (id == DefaultChatBackground) { - img.load(st::msgBG); - } else { - img.load(st::msgBG0); - if (cRetina()) { - img = img.scaledToWidth(img.width() * 2, Qt::SmoothTransformation); - } else if (cScale() != dbisOne) { - img = img.scaledToWidth(convertScale(img.width()), Qt::SmoothTransformation); - } - id = 0; - } - remove = true; - } - if (img.format() != QImage::Format_ARGB32 && img.format() != QImage::Format_ARGB32_Premultiplied && img.format() != QImage::Format_RGB32) { - img = img.convertToFormat(QImage::Format_RGB32); - } - img.setDevicePixelRatio(cRetinaFactor()); - - if (!nowrite) { - Local::writeBackground(id, remove ? QImage() : img); - } - - int w = img.width(), h = img.height(); - size = w * h; - const uchar *pix = img.constBits(); - if (pix) { - for (int32 i = 0, l = size * 4; i < l; i += 4) { - components[2] += pix[i + 0]; - components[1] += pix[i + 1]; - components[0] += pix[i + 2]; - } - } - - if (size) { - for (int32 i = 0; i < 3; ++i) components[i] /= size; - } - int maxtomin[3] = { 0, 1, 2 }; - if (components[maxtomin[0]] < components[maxtomin[1]]) { - qSwap(maxtomin[0], maxtomin[1]); - } - if (components[maxtomin[1]] < components[maxtomin[2]]) { - qSwap(maxtomin[1], maxtomin[2]); - if (components[maxtomin[0]] < components[maxtomin[1]]) { - qSwap(maxtomin[0], maxtomin[1]); - } - } - - uint64 max = qMax(1ULL, components[maxtomin[0]]), mid = qMax(1ULL, components[maxtomin[1]]), min = qMax(1ULL, components[maxtomin[2]]); - - Window::chatBackground()->init(id, pixmapFromImageInPlace(std_::move(img))); - - memcpy(componentsScroll, components, sizeof(components)); - memcpy(componentsPoint, components, sizeof(components)); - - if (max != min) { - if (min > uint64(qRound(0.77 * max))) { - uint64 newmin = qRound(0.77 * max); // min saturation 23% - uint64 newmid = max - ((max - mid) * (max - newmin)) / (max - min); - components[maxtomin[1]] = newmid; - components[maxtomin[2]] = newmin; - } - uint64 newmin = qRound(0.77 * max); // saturation 23% for scroll - uint64 newmid = max - ((max - mid) * (max - newmin)) / (max - min); - componentsScroll[maxtomin[1]] = newmid; - componentsScroll[maxtomin[2]] = newmin; - - uint64 pmax = 227; // 89% brightness - uint64 pmin = qRound(0.75 * pmax); // 41% saturation - uint64 pmid = pmax - ((max - mid) * (pmax - pmin)) / (max - min); - componentsPoint[maxtomin[0]] = pmax; - componentsPoint[maxtomin[1]] = pmid; - componentsPoint[maxtomin[2]] = pmin; - } else { - componentsPoint[0] = componentsPoint[1] = componentsPoint[2] = 227; // 89% brightness - } - - float64 luminance = 0.299 * componentsScroll[0] + 0.587 * componentsScroll[1] + 0.114 * componentsScroll[2]; - uint64 maxScroll = max; - if (luminance < 0.5 * 0xFF) { - maxScroll += qRound(0.2 * 0xFF); - } else { - maxScroll -= qRound(0.2 * 0xFF); - } - componentsScroll[maxtomin[2]] = qMin(uint64(float64(componentsScroll[maxtomin[2]]) * maxScroll / float64(componentsScroll[maxtomin[0]])), 0xFFULL); - componentsScroll[maxtomin[1]] = qMin(uint64(float64(componentsScroll[maxtomin[1]]) * maxScroll / float64(componentsScroll[maxtomin[0]])), 0xFFULL); - componentsScroll[maxtomin[0]] = qMin(maxScroll, 0xFFULL); - - if (max > uint64(qRound(0.2 * 0xFF))) { // brightness greater than 20% - max -= qRound(0.2 * 0xFF); - } else { - max = 0; - } - components[maxtomin[2]] = uint64(float64(components[maxtomin[2]]) * max / float64(components[maxtomin[0]])); - components[maxtomin[1]] = uint64(float64(components[maxtomin[1]]) * max / float64(components[maxtomin[0]])); - components[maxtomin[0]] = max; - - uchar r = uchar(components[0]), g = uchar(components[1]), b = uchar(components[2]); - float64 alpha = st::msgServiceBg->c.alphaF(); - _msgServiceBg = style::color(r, g, b, qRound(alpha * 0xFF)); - - float64 alphaSel = st::msgServiceSelectBg->c.alphaF(), addSel = (1. - ((1. - alphaSel) / (1. - alpha))) * 0xFF; - uchar rsel = snap(qRound(((1. - alphaSel) * r + addSel) / alphaSel), 0, 0xFF); - uchar gsel = snap(qRound(((1. - alphaSel) * g + addSel) / alphaSel), 0, 0xFF); - uchar bsel = snap(qRound(((1. - alphaSel) * b + addSel) / alphaSel), 0, 0xFF); - _msgServiceSelectBg = style::color(r, g, b, qRound(alphaSel * 0xFF)); - - for (int i = 0; i < 4; ++i) { - delete ::corners[StickerCorners].p[i]; ::corners[StickerCorners].p[i] = nullptr; - delete ::corners[StickerSelectedCorners].p[i]; ::corners[StickerSelectedCorners].p[i] = nullptr; - } - prepareCorners(StickerCorners, st::dateRadius, _msgServiceBg); - prepareCorners(StickerSelectedCorners, st::dateRadius, _msgServiceSelectBg); - - uchar rScroll = uchar(componentsScroll[0]), gScroll = uchar(componentsScroll[1]), bScroll = uchar(componentsScroll[2]); - _historyScrollBarColor = style::color(rScroll, gScroll, bScroll, qRound(st::historyScroll.barColor->c.alphaF() * 0xFF)); - _historyScrollBgColor = style::color(rScroll, gScroll, bScroll, qRound(st::historyScroll.bgColor->c.alphaF() * 0xFF)); - _historyScrollBarOverColor = style::color(rScroll, gScroll, bScroll, qRound(st::historyScroll.barOverColor->c.alphaF() * 0xFF)); - _historyScrollBgOverColor = style::color(rScroll, gScroll, bScroll, qRound(st::historyScroll.bgOverColor->c.alphaF() * 0xFF)); - - uchar rPoint = uchar(componentsPoint[0]), gPoint = uchar(componentsPoint[1]), bPoint = uchar(componentsPoint[2]); - _introPointHoverColor = style::color(rPoint, gPoint, bPoint); - - if (App::main()) { - App::main()->updateScrollColors(); - } - HistoryLayout::serviceColorsUpdated(); - } - - const style::color &msgServiceBg() { - return _msgServiceBg; - } - - const style::color &msgServiceSelectBg() { - return _msgServiceSelectBg; - } - - const style::color &historyScrollBarColor() { - return _historyScrollBarColor; - } - - const style::color &historyScrollBgColor() { - return _historyScrollBgColor; - } - - const style::color &historyScrollBarOverColor() { - return _historyScrollBarOverColor; - } - - const style::color &historyScrollBgOverColor() { - return _historyScrollBgOverColor; - } - - const style::color &introPointHoverColor() { - return _introPointHoverColor; + roundRect(p, x, y, w, h, bg, i.value(), nullptr, parts); } WallPapers gServerBackgrounds; diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index ec27125c1..fdd9d7b85 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -212,7 +212,6 @@ namespace App { void clearMousedItems(); const style::font &monofont(); - const QPixmap &sprite(); const QPixmap &emoji(); const QPixmap &emojiLarge(); const QPixmap &emojiSingle(EmojiPtr emoji, int32 fontHeight); @@ -237,9 +236,12 @@ namespace App { void allDraftsSaved(); LaunchState launchState(); void setLaunchState(LaunchState state); + void restart(); - 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); + constexpr auto kFileSizeLimit = 1500 * 1024 * 1024; // Load files up to 1500mb + constexpr auto kImageSizeLimit = 64 * 1024 * 1024; // Open images up to 64mb jpg/png/gif + QImage readImage(QByteArray data, QByteArray *format = nullptr, bool opaque = true, bool *animated = nullptr); + QImage readImage(const QString &file, QByteArray *format = nullptr, bool opaque = true, bool *animated = nullptr, QByteArray *content = 0); QPixmap pixmapFromImageInPlace(QImage &&image); void regPhotoItem(PhotoData *data, HistoryItem *item); @@ -279,30 +281,45 @@ namespace App { #endif // !TDESKTOP_DISABLE_NETWORK_PROXY void setProxySettings(QTcpSocket &socket); + enum class RectPart { + None = 0x000, + TopLeft = 0x001, + Top = 0x002, + TopRight = 0x004, + Left = 0x008, + Center = 0x010, + Right = 0x020, + BottomLeft = 0x040, + Bottom = 0x080, + BottomRight = 0x100, + TopFull = 0x007, + LeftFull = 0x049, + RightFull = 0x124, + BottomFull = 0x1c0, + NoTopBottom = 0x038, + NoLeftRight = 0x092, + Full = 0x1ff, + }; + Q_DECLARE_FLAGS(RectParts, RectPart); + Q_DECLARE_OPERATORS_FOR_FLAGS(RectParts); + + void complexOverlayRect(Painter &p, QRect rect, ImageRoundRadius radius, ImageRoundCorners corners); + void complexLocationRect(Painter &p, QRect rect, ImageRoundRadius radius, ImageRoundCorners corners); + QImage **cornersMask(ImageRoundRadius radius); - void roundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, const style::color &bg, RoundCorners index, const style::color *sh = 0); - inline void roundRect(Painter &p, const QRect &rect, const style::color &bg, RoundCorners index, const style::color *sh = 0) { - return roundRect(p, rect.x(), rect.y(), rect.width(), rect.height(), bg, index, sh); + void roundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, RoundCorners index, const style::color *shadow = nullptr, RectParts parts = RectPart::Full); + inline void roundRect(Painter &p, const QRect &rect, style::color bg, RoundCorners index, const style::color *shadow = nullptr, RectParts parts = RectPart::Full) { + return roundRect(p, rect.x(), rect.y(), rect.width(), rect.height(), bg, index, shadow, parts); } - void roundShadow(Painter &p, int32 x, int32 y, int32 w, int32 h, const style::color &sh, RoundCorners index); - inline void roundShadow(Painter &p, const QRect &rect, const style::color &sh, RoundCorners index) { - return roundShadow(p, rect.x(), rect.y(), rect.width(), rect.height(), sh, index); + void roundShadow(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color shadow, RoundCorners index, RectParts parts = RectPart::Full); + inline void roundShadow(Painter &p, const QRect &rect, style::color shadow, RoundCorners index, RectParts parts = RectPart::Full) { + return roundShadow(p, rect.x(), rect.y(), rect.width(), rect.height(), shadow, index, parts); } - void roundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, const style::color &bg, ImageRoundRadius radius); - inline void roundRect(Painter &p, const QRect &rect, const style::color &bg, ImageRoundRadius radius) { - return roundRect(p, rect.x(), rect.y(), rect.width(), rect.height(), bg, radius); + void roundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, ImageRoundRadius radius, RectParts parts = RectPart::Full); + inline void roundRect(Painter &p, const QRect &rect, style::color bg, ImageRoundRadius radius, RectParts parts = RectPart::Full) { + return roundRect(p, rect.x(), rect.y(), rect.width(), rect.height(), bg, radius, parts); } - void initBackground(int32 id = DefaultChatBackground, const QImage &p = QImage(), bool nowrite = false); - - const style::color &msgServiceBg(); - const style::color &msgServiceSelectBg(); - const style::color &historyScrollBarColor(); - const style::color &historyScrollBgColor(); - const style::color &historyScrollBarOverColor(); - const style::color &historyScrollBgOverColor(); - const style::color &introPointHoverColor(); - struct WallPaper { WallPaper(int32 id, ImagePtr thumb, ImagePtr full) : id(id), thumb(thumb), full(full) { } diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index 11d57a58e..a7f244cf8 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -28,70 +28,73 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "lang.h" #include "boxes/confirmbox.h" #include "ui/filedialog.h" -#include "ui/popupmenu.h" +#include "ui/widgets/tooltip.h" #include "langloaderplain.h" #include "localstorage.h" #include "autoupdater.h" #include "core/observer.h" #include "observer_peer.h" -#include "window/chat_background.h" +#include "window/window_theme.h" #include "media/player/media_player_instance.h" #include "window/notifications_manager.h" #include "history/history_location_manager.h" +#include "core/task_queue.h" namespace { - void mtpStateChanged(int32 dc, int32 state) { - if (App::wnd()) { - App::wnd()->mtpStateChanged(dc, state); - } - } - void mtpSessionReset(int32 dc) { - if (App::main() && dc == MTP::maindc()) { - App::main()->getDifference(); - } - } - - QChar _toHex(ushort v) { - v = v & 0x000F; - return QChar::fromLatin1((v >= 10) ? ('a' + (v - 10)) : ('0' + v)); - } - ushort _fromHex(QChar c) { - return ((c.unicode() >= uchar('a')) ? (c.unicode() - uchar('a') + 10) : (c.unicode() - uchar('0'))) & 0x000F; - } - - QString _escapeTo7bit(const QString &str) { - QString result; - result.reserve(str.size() * 2); - for (int i = 0, l = str.size(); i != l; ++i) { - QChar ch(str.at(i)); - ushort uch(ch.unicode()); - if (uch < 32 || uch > 127 || uch == ushort(uchar('%'))) { - result.append('%').append(_toHex(uch >> 12)).append(_toHex(uch >> 8)).append(_toHex(uch >> 4)).append(_toHex(uch)); - } else { - result.append(ch); - } - } - return result; - } - - QString _escapeFrom7bit(const QString &str) { - QString result; - result.reserve(str.size()); - for (int i = 0, l = str.size(); i != l; ++i) { - QChar ch(str.at(i)); - if (ch == QChar::fromLatin1('%') && i + 4 < l) { - result.append(QChar(ushort((_fromHex(str.at(i + 1)) << 12) | (_fromHex(str.at(i + 2)) << 8) | (_fromHex(str.at(i + 3)) << 4) | _fromHex(str.at(i + 4))))); - i += 4; - } else { - result.append(ch); - } - } - return result; +void mtpStateChanged(int32 dc, int32 state) { + if (App::wnd()) { + App::wnd()->mtpStateChanged(dc, state); } } -AppClass *AppObject = 0; +void mtpSessionReset(int32 dc) { + if (App::main() && dc == MTP::maindc()) { + App::main()->getDifference(); + } +} + +QChar _toHex(ushort v) { + v = v & 0x000F; + return QChar::fromLatin1((v >= 10) ? ('a' + (v - 10)) : ('0' + v)); +} +ushort _fromHex(QChar c) { + return ((c.unicode() >= uchar('a')) ? (c.unicode() - uchar('a') + 10) : (c.unicode() - uchar('0'))) & 0x000F; +} + +QString _escapeTo7bit(const QString &str) { + QString result; + result.reserve(str.size() * 2); + for (int i = 0, l = str.size(); i != l; ++i) { + QChar ch(str.at(i)); + ushort uch(ch.unicode()); + if (uch < 32 || uch > 127 || uch == ushort(uchar('%'))) { + result.append('%').append(_toHex(uch >> 12)).append(_toHex(uch >> 8)).append(_toHex(uch >> 4)).append(_toHex(uch)); + } else { + result.append(ch); + } + } + return result; +} + +QString _escapeFrom7bit(const QString &str) { + QString result; + result.reserve(str.size()); + for (int i = 0, l = str.size(); i != l; ++i) { + QChar ch(str.at(i)); + if (ch == QChar::fromLatin1('%') && i + 4 < l) { + result.append(QChar(ushort((_fromHex(str.at(i + 1)) << 12) | (_fromHex(str.at(i + 2)) << 8) | (_fromHex(str.at(i + 3)) << 4) | _fromHex(str.at(i + 4))))); + i += 4; + } else { + result.append(ch); + } + } + return result; +} + +} // namespace + +AppClass *AppObject = nullptr; Application::Application(int &argc, char **argv) : QApplication(argc, argv) { QByteArray d(QFile::encodeName(QDir(cWorkingDir()).absolutePath())); @@ -362,6 +365,10 @@ void Application::closeApplication() { #endif // !TDESKTOP_DISABLE_AUTOUPDATE } +void Application::onMainThreadTask() { + base::TaskQueue::ProcessMainTasks(); +} + #ifndef TDESKTOP_DISABLE_AUTOUPDATE void Application::updateCheck() { startUpdateCheck(false); @@ -468,6 +475,8 @@ void Application::stopUpdate() { } void Application::startUpdateCheck(bool forceWait) { + if (!Sandbox::started()) return; + _updateCheckTimer.stop(); if (_updateThread || _updateReply || !cAutoUpdate()) return; @@ -676,11 +685,7 @@ namespace Sandbox { } -AppClass::AppClass() : QObject() -, _lastActionTime(0) -, _window(0) -, _uploader(0) -, _translator(0) { +AppClass::AppClass() : QObject() { AppObject = this; Fonts::start(); @@ -702,33 +707,7 @@ AppClass::AppClass() : QObject() cSetConfigScale(dbisOne); cSetRealScale(dbisOne); } - - if (cLang() < languageTest) { - cSetLang(Sandbox::LangSystem()); - } - if (cLang() == languageTest) { - if (QFileInfo(cLangFile()).exists()) { - LangLoaderPlain loader(cLangFile()); - cSetLangErrors(loader.errors()); - if (!cLangErrors().isEmpty()) { - LOG(("Lang load errors: %1").arg(cLangErrors())); - } else if (!loader.warnings().isEmpty()) { - LOG(("Lang load warnings: %1").arg(loader.warnings())); - } - } else { - cSetLang(languageDefault); - } - } else if (cLang() > languageDefault && cLang() < languageCount) { - LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[cLang()].c_str() + qsl(".strings")); - if (!loader.errors().isEmpty()) { - LOG(("Lang load errors: %1").arg(loader.errors())); - } else if (!loader.warnings().isEmpty()) { - LOG(("Lang load warnings: %1").arg(loader.warnings())); - } - } - - application()->installTranslator(_translator = new Translator()); - + loadLanguage(); style::startManager(); anim::startManager(); historyInit(); @@ -741,8 +720,6 @@ AppClass::AppClass() : QObject() cChangeTimeFormat(QLocale::system().timeFormat(QLocale::ShortFormat)); - connect(&_mtpUnpauseTimer, SIGNAL(timeout()), this, SLOT(doMtpUnpause())); - connect(&killDownloadSessionsTimer, SIGNAL(timeout()), this, SLOT(killDownloadSessions())); DEBUG_LOG(("Application Info: starting app...")); @@ -780,12 +757,12 @@ AppClass::AppClass() : QObject() DEBUG_LOG(("Application Info: showing.")); if (state == Local::ReadMapPassNeeded) { - _window->setupPasscode(false); + _window->setupPasscode(); } else { if (MTP::authedId()) { - _window->setupMain(false); + _window->setupMain(); } else { - _window->setupIntro(false); + _window->setupIntro(); } } _window->firstShow(); @@ -812,6 +789,33 @@ AppClass::AppClass() : QObject() } } +void AppClass::loadLanguage() { + if (cLang() < languageTest) { + cSetLang(Sandbox::LangSystem()); + } + if (cLang() == languageTest) { + if (QFileInfo(cLangFile()).exists()) { + LangLoaderPlain loader(cLangFile()); + cSetLangErrors(loader.errors()); + if (!cLangErrors().isEmpty()) { + LOG(("Lang load errors: %1").arg(cLangErrors())); + } else if (!loader.warnings().isEmpty()) { + LOG(("Lang load warnings: %1").arg(loader.warnings())); + } + } else { + cSetLang(languageDefault); + } + } else if (cLang() > languageDefault && cLang() < languageCount) { + LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[cLang()].c_str() + qsl(".strings")); + if (!loader.errors().isEmpty()) { + LOG(("Lang load errors: %1").arg(loader.errors())); + } else if (!loader.warnings().isEmpty()) { + LOG(("Lang load warnings: %1").arg(loader.warnings())); + } + } + application()->installTranslator(_translator = new Translator()); +} + void AppClass::regPhotoUpdate(const PeerId &peer, const FullMsgId &msgId) { photoUpdates.insert(msgId, peer); } @@ -835,19 +839,6 @@ void AppClass::cancelPhotoUpdate(const PeerId &peer) { } } -void AppClass::mtpPause() { - MTP::pause(); - _mtpUnpauseTimer.start(st::slideDuration * 2); -} - -void AppClass::mtpUnpause() { - _mtpUnpauseTimer.start(1); -} - -void AppClass::doMtpUnpause() { - MTP::unpause(); -} - void AppClass::selfPhotoCleared(const MTPUserProfilePhoto &result) { if (!App::self()) return; App::self()->setPhoto(result); @@ -926,7 +917,7 @@ void AppClass::onAppStateChanged(Qt::ApplicationState state) { _window->updateIsActive((state == Qt::ApplicationActive) ? Global::OnlineFocusTimeout() : Global::OfflineBlurTimeout()); } if (state != Qt::ApplicationActive) { - PopupTooltip::Hide(); + Ui::Tooltip::Hide(); } } @@ -935,9 +926,7 @@ void AppClass::call_handleHistoryUpdate() { } void AppClass::call_handleUnreadCounterUpdate() { - if (auto w = App::wnd()) { - w->updateUnreadCounter(); - } + Global::RefUnreadCounterUpdate().notify(true); } void AppClass::call_handleFileDialogQueue() { @@ -957,8 +946,8 @@ void AppClass::call_handleObservables() { } void AppClass::killDownloadSessions() { - uint64 ms = getms(), left = MTPAckSendWaiting + MTPKillFileSessionTimeout; - for (QMap::iterator i = killDownloadSessionTimes.begin(); i != killDownloadSessionTimes.end(); ) { + auto ms = getms(), left = static_cast(MTPAckSendWaiting) + MTPKillFileSessionTimeout; + for (auto i = killDownloadSessionTimes.begin(); i != killDownloadSessionTimes.end(); ) { if (i.value() <= ms) { for (int j = 0; j < MTPDownloadSessionsCount; ++j) { MTP::stopSession(MTP::dldDcId(i.key(), j)); @@ -998,9 +987,7 @@ void AppClass::onSwitchDebugMode() { if (cDebug()) { QFile(cWorkingDir() + qsl("tdata/withdebug")).remove(); cSetDebug(false); - cSetRestarting(true); - cSetRestartingToSettings(true); - App::quit(); + App::restart(); } else { cSetDebug(true); DEBUG_LOG(("Debug logs started.")); @@ -1017,9 +1004,7 @@ void AppClass::onSwitchWorkMode() { Global::SetDialogsModeEnabled(!Global::DialogsModeEnabled()); Global::SetDialogsMode(Dialogs::Mode::All); Local::writeUserSettings(); - cSetRestarting(true); - cSetRestartingToSettings(true); - App::quit(); + App::restart(); } void AppClass::onSwitchTestMode() { @@ -1034,9 +1019,7 @@ void AppClass::onSwitchTestMode() { } cSetTestMode(true); } - cSetRestarting(true); - cSetRestartingToSettings(true); - App::quit(); + App::restart(); } FileUploader *AppClass::uploader() { @@ -1048,15 +1031,15 @@ void AppClass::uploadProfilePhoto(const QImage &tosend, const PeerId &peerId) { PreparedPhotoThumbs photoThumbs; QVector photoSizes; - QPixmap thumb = App::pixmapFromImageInPlace(tosend.scaled(160, 160, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + auto thumb = App::pixmapFromImageInPlace(tosend.scaled(160, 160, Qt::KeepAspectRatio, Qt::SmoothTransformation)); photoThumbs.insert('a', thumb); photoSizes.push_back(MTP_photoSize(MTP_string("a"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(thumb.width()), MTP_int(thumb.height()), MTP_int(0))); - QPixmap medium = App::pixmapFromImageInPlace(tosend.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + auto medium = App::pixmapFromImageInPlace(tosend.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation)); photoThumbs.insert('b', medium); photoSizes.push_back(MTP_photoSize(MTP_string("b"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(medium.width()), MTP_int(medium.height()), MTP_int(0))); - QPixmap full = QPixmap::fromImage(tosend, Qt::ColorOnly); + auto full = QPixmap::fromImage(tosend, Qt::ColorOnly); photoThumbs.insert('c', full); photoSizes.push_back(MTP_photoSize(MTP_string("c"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0))); @@ -1073,7 +1056,7 @@ void AppClass::uploadProfilePhoto(const QImage &tosend, const PeerId &peerId) { int32 filesize = 0; QByteArray data; - ReadyLocalMedia ready(PreparePhoto, file, filename, filesize, data, id, id, qsl("jpg"), peerId, photo, photoThumbs, MTP_documentEmpty(MTP_long(0)), jpeg, false, 0); + SendMediaReady ready(SendMediaType::Photo, file, filename, filesize, data, id, id, qsl("jpg"), peerId, photo, photoThumbs, MTP_documentEmpty(MTP_long(0)), jpeg, 0); connect(App::uploader(), SIGNAL(photoReady(const FullMsgId&,bool,const MTPInputFile&)), App::app(), SLOT(photoUpdated(const FullMsgId&,bool,const MTPInputFile&)), Qt::UniqueConnection); @@ -1086,8 +1069,8 @@ void AppClass::checkMapVersion() { if (Local::oldMapVersion() < AppVersion) { if (Local::oldMapVersion()) { QString versionFeatures; - if ((cAlphaVersion() || cBetaVersion()) && Local::oldMapVersion() < 10017) { - versionFeatures = QString::fromUtf8("\xe2\x80\x94 New cute control for adding members to your groups"); + if ((cAlphaVersion() || cBetaVersion()) && Local::oldMapVersion() < 10027) { + versionFeatures = QString::fromUtf8("\xe2\x80\x94 Appoint admins in your supergroups from members list context menu\n\xe2\x80\x94 Bug fixes and other minor improvements"); } else if (!(cAlphaVersion() || cBetaVersion()) && Local::oldMapVersion() < 10018) { versionFeatures = langNewVersionText(); } else { @@ -1095,7 +1078,7 @@ void AppClass::checkMapVersion() { } if (!versionFeatures.isEmpty()) { versionFeatures = lng_new_version_wrap(lt_version, QString::fromLatin1(AppVersionStr.c_str()), lt_changes, versionFeatures, lt_link, qsl("https://desktop.telegram.org/#changelog")); - _window->serviceNotification(versionFeatures); + _window->serviceNotificationLocal(versionFeatures); } } } @@ -1105,6 +1088,7 @@ AppClass::~AppClass() { Shortcuts::finish(); delete base::take(_window); + App::clearHistories(); Window::Notifications::finish(); @@ -1120,7 +1104,7 @@ AppClass::~AppClass() { delete base::take(_uploader); delete base::take(_translator); - Window::chatBackground()->reset(); + Window::Theme::Unload(); Media::Player::finish(); style::stopManager(); diff --git a/Telegram/SourceFiles/application.h b/Telegram/SourceFiles/application.h index a18b4d9fa..c37dfe0f1 100644 --- a/Telegram/SourceFiles/application.h +++ b/Telegram/SourceFiles/application.h @@ -48,6 +48,8 @@ public slots: void startApplication(); // will be done in exec() void closeApplication(); // will be done in aboutToQuit() + void onMainThreadTask(); + private: typedef QPair LocalClient; typedef QList LocalClients; @@ -159,9 +161,6 @@ public: bool isPhotoUpdating(const PeerId &peer); void cancelPhotoUpdate(const PeerId &peer); - void mtpPause(); - void mtpUnpause(); - void selfPhotoCleared(const MTPUserProfilePhoto &result); void chatPhotoCleared(PeerId peer, const MTPUpdates &updates); void selfPhotoDone(const MTPphotos_Photo &result); @@ -169,7 +168,7 @@ public: bool peerPhotoFail(PeerId peerId, const RPCError &e); void peerClearPhoto(PeerId peer); - void writeUserConfigIn(uint64 ms); + void writeUserConfigIn(TimeMs ms); void killDownloadSessionsStart(int32 dc); void killDownloadSessionsStop(int32 dc); @@ -185,9 +184,6 @@ signals: void adjustSingleTimers(); public slots: - - void doMtpUnpause(); - void photoUpdated(const FullMsgId &msgId, bool silent, const MTPInputFile &file); void onSwitchDebugMode(); @@ -204,18 +200,17 @@ public slots: void call_handleObservables(); private: + void loadLanguage(); QMap photoUpdates; - QMap killDownloadSessionTimes; + QMap killDownloadSessionTimes; SingleTimer killDownloadSessionsTimer; - uint64 _lastActionTime; + TimeMs _lastActionTime = 0; - MainWindow *_window; - FileUploader *_uploader; - Translator *_translator; - - SingleTimer _mtpUnpauseTimer; + MainWindow *_window = nullptr; + FileUploader *_uploader = nullptr; + Translator *_translator = nullptr; }; diff --git a/Telegram/SourceFiles/boxes/aboutbox.cpp b/Telegram/SourceFiles/boxes/aboutbox.cpp index 83650f48e..f8c000ecf 100644 --- a/Telegram/SourceFiles/boxes/aboutbox.cpp +++ b/Telegram/SourceFiles/boxes/aboutbox.cpp @@ -19,55 +19,50 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "lang.h" +#include "boxes/aboutbox.h" -#include "aboutbox.h" +#include "lang.h" #include "mainwidget.h" #include "mainwindow.h" - #include "autoupdater.h" #include "boxes/confirmbox.h" - #include "application.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/labels.h" +#include "styles/style_boxes.h" +#include "platform/platform_file_dialog.h" -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), 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]"))); - - setMaxHeight(st::boxTitleHeight + st::aboutTextTop + _text1.height() + st::aboutSkip + _text2.height() + st::aboutSkip + _text3.height() + st::boxButtonPadding.top() + _done.height() + st::boxButtonPadding.bottom()); - - connect(&_version, SIGNAL(clicked()), this, SLOT(onVersion())); - connect(&_done, SIGNAL(clicked()), this, SLOT(onClose())); - - prepare(); - - setAcceptDrops(true); +AboutBox::AboutBox(QWidget *parent) +: _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), Ui::FlatLabel::InitType::Rich, st::aboutLabel) +, _text2(this, lang(lng_about_text_2), Ui::FlatLabel::InitType::Rich, st::aboutLabel) +, _text3(this, st::aboutLabel) { } -void AboutBox::showAll() { - _version.show(); - _text1.show(); - _text2.show(); - _text3.show(); - _done.show(); +void AboutBox::prepare() { + setTitle(qsl("Telegram Desktop")); + + addButton(lang(lng_close), [this] { closeBox(); }); + + _text3->setRichText(lng_about_text_3(lt_faq_open, qsl("[a href=\"%1\"]").arg(telegramFaqLink()), lt_faq_close, qsl("[/a]"))); + + _version->setClickedCallback([this] { showVersionHistory(); }); + + setDimensions(st::aboutWidth, st::aboutTextTop + _text1->height() + st::aboutSkip + _text2->height() + st::aboutSkip + _text3->height()); } void AboutBox::resizeEvent(QResizeEvent *e) { - _version.moveToLeft(st::boxPadding.left(), st::boxTitleHeight + st::aboutVersionTop); - _text1.moveToLeft(st::boxPadding.left(), st::boxTitleHeight + st::aboutTextTop); - _text2.moveToLeft(st::boxPadding.left(), _text1.y() + _text1.height() + st::aboutSkip); - _text3.moveToLeft(st::boxPadding.left(), _text2.y() + _text2.height() + st::aboutSkip); - _done.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _done.height()); - AbstractBox::resizeEvent(e); + BoxContent::resizeEvent(e); + + _version->moveToLeft(st::boxPadding.left(), st::aboutVersionTop); + _text1->moveToLeft(st::boxPadding.left(), st::aboutTextTop); + _text2->moveToLeft(st::boxPadding.left(), _text1->y() + _text1->height() + st::aboutSkip); + _text3->moveToLeft(st::boxPadding.left(), _text2->y() + _text2->height() + st::aboutSkip); } -void AboutBox::onVersion() { +void AboutBox::showVersionHistory() { if (cRealBetaVersion()) { - QString url = qsl("https://tdesktop.com/"); + auto url = qsl("https://tdesktop.com/"); switch (cPlatform()) { case dbipWindows: url += qsl("win/%1.zip"); break; case dbipMac: url += qsl("mac/%1.zip"); break; @@ -79,7 +74,7 @@ void AboutBox::onVersion() { Application::clipboard()->setText(url); - Ui::showLayer(new InformBox("The link to the current private beta version of Telegram Desktop was copied to the clipboard.")); + Ui::show(Box("The link to the current private beta version of Telegram Desktop was copied to the clipboard.")); } else { QDesktopServices::openUrl(qsl("https://desktop.telegram.org/?_hash=changelog")); } @@ -87,24 +82,17 @@ void AboutBox::onVersion() { void AboutBox::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { - onClose(); + closeBox(); } else { - AbstractBox::keyPressEvent(e); + BoxContent::keyPressEvent(e); } } -void AboutBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; - - paintTitle(p, qsl("Telegram Desktop")); -} - #ifndef TDESKTOP_DISABLE_CRASH_REPORTS QString _getCrashReportFile(const QMimeData *m) { if (!m || m->urls().size() != 1 || !m->urls().at(0).isLocalFile()) return QString(); - auto file = psConvertFileUrl(m->urls().at(0)); + auto file = Platform::FileDialog::UrlToLocal(m->urls().at(0)); return file.endsWith(qstr(".telegramcrash"), Qt::CaseInsensitive) ? file : QString(); } @@ -140,3 +128,14 @@ QString telegramFaqLink() { } return result; } + +QString currentVersionText() { + auto result = QString::fromLatin1(AppVersionStr.c_str()); + if (cAlphaVersion()) { + result += " alpha"; + } + if (cBetaVersion()) { + result += qsl(" beta %1").arg(cBetaVersion() % 1000); + } + return result; +} diff --git a/Telegram/SourceFiles/boxes/aboutbox.h b/Telegram/SourceFiles/boxes/aboutbox.h index 99d5571f5..5c6518b02 100644 --- a/Telegram/SourceFiles/boxes/aboutbox.h +++ b/Telegram/SourceFiles/boxes/aboutbox.h @@ -20,32 +20,34 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "abstractbox.h" -#include "ui/flatlabel.h" +#include "boxes/abstractbox.h" -class AboutBox : public AbstractBox { - Q_OBJECT +namespace Ui { +class LinkButton; +class FlatLabel; +} // namespace Ui +class AboutBox : public BoxContent { public: - AboutBox(); - -public slots: - void onVersion(); + AboutBox(QWidget*); protected: + void prepare() override; + void resizeEvent(QResizeEvent *e) override; void keyPressEvent(QKeyEvent *e) override; - void paintEvent(QPaintEvent *e) override; void dragEnterEvent(QDragEnterEvent *e) override; void dropEvent(QDropEvent *e) override; - void showAll() override; - private: - LinkButton _version; - FlatLabel _text1, _text2, _text3; - BoxButton _done; + void showVersionHistory(); + + object_ptr _version; + object_ptr _text1; + object_ptr _text2; + object_ptr _text3; }; QString telegramFaqLink(); +QString currentVersionText(); diff --git a/Telegram/SourceFiles/boxes/abstractbox.cpp b/Telegram/SourceFiles/boxes/abstractbox.cpp index 6df51ba70..c465c2138 100644 --- a/Telegram/SourceFiles/boxes/abstractbox.cpp +++ b/Telegram/SourceFiles/boxes/abstractbox.cpp @@ -19,208 +19,367 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "lang.h" +#include "boxes/abstractbox.h" +#include "styles/style_boxes.h" #include "localstorage.h" - -#include "abstractbox.h" +#include "lang.h" +#include "ui/effects/widget_fade_wrap.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/scroll_area.h" #include "mainwidget.h" #include "mainwindow.h" -#include "styles/style_boxes.h" -void BlueTitleShadow::paintEvent(QPaintEvent *e) { +BoxLayerTitleShadow::BoxLayerTitleShadow(QWidget *parent) : Ui::PlainShadow(parent, st::boxLayerTitleShadow) { +} + +QPointer BoxContent::addButton(const QString &text, base::lambda &&clickCallback) { + return addButton(text, std_::move(clickCallback), st::defaultBoxButton); +} + +QPointer BoxContent::addLeftButton(const QString &text, base::lambda &&clickCallback) { + return getDelegate()->addLeftButton(text, std_::move(clickCallback), st::defaultBoxButton); +} + +void BoxContent::setInner(object_ptr inner) { + setInner(std_::move(inner), st::boxLayerScroll); +} + +void BoxContent::setInner(object_ptr inner, const style::ScrollArea &st) { + if (inner) { + getDelegate()->setLayerType(true); + _scroll.create(this, st); + _scroll->setGeometryToLeft(0, _innerTopSkip, width(), 0); + _scroll->setOwnedWidget(std_::move(inner)); + if (_topShadow) { + _topShadow->raise(); + _bottomShadow->raise(); + } else { + _topShadow.create(this, object_ptr(this)); + _bottomShadow.create(this, object_ptr(this)); + } + updateScrollAreaGeometry(); + connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); + connect(_scroll, SIGNAL(innerResized()), this, SLOT(onInnerResize())); + } else { + getDelegate()->setLayerType(false); + _scroll.destroyDelayed(); + _topShadow.destroyDelayed(); + _bottomShadow.destroyDelayed(); + } +} + +void BoxContent::onScrollToY(int top, int bottom) { + if (_scroll) { + _scroll->scrollToY(top, bottom); + } +} + +void BoxContent::onDraggingScrollDelta(int delta) { + _draggingScrollDelta = _scroll ? delta : 0; + if (_draggingScrollDelta) { + if (!_draggingScrollTimer) { + _draggingScrollTimer.create(this); + _draggingScrollTimer->setSingleShot(false); + connect(_draggingScrollTimer, SIGNAL(timeout()), this, SLOT(onDraggingScrollTimer())); + } + _draggingScrollTimer->start(15); + } else { + _draggingScrollTimer.destroy(); + } +} + +void BoxContent::onDraggingScrollTimer() { + auto delta = (_draggingScrollDelta > 0) ? qMin(_draggingScrollDelta * 3 / 20 + 1, int32(MaxScrollSpeed)) : qMax(_draggingScrollDelta * 3 / 20 - 1, -int32(MaxScrollSpeed)); + _scroll->scrollToY(_scroll->scrollTop() + delta); +} + +void BoxContent::updateInnerVisibleTopBottom() { + if (auto widget = static_cast(_scroll ? _scroll->widget() : nullptr)) { + auto top = _scroll->scrollTop(); + widget->setVisibleTopBottom(top, top + _scroll->height()); + } +} + +void BoxContent::updateShadowsVisibility() { + if (!_scroll) return; + + auto top = _scroll->scrollTop(); + if (top > 0 || _innerTopSkip > 0) { + _topShadow->showAnimated(); + } else { + _topShadow->hideAnimated(); + } + if (top < _scroll->scrollTopMax()) { + _bottomShadow->showAnimated(); + } else { + _bottomShadow->hideAnimated(); + } +} + +void BoxContent::onScroll() { + updateInnerVisibleTopBottom(); + updateShadowsVisibility(); +} + +void BoxContent::onInnerResize() { + updateInnerVisibleTopBottom(); + updateShadowsVisibility(); +} + +void BoxContent::setInnerTopSkip(int innerTopSkip, bool scrollBottomFixed) { + if (_innerTopSkip != innerTopSkip) { + auto delta = innerTopSkip - _innerTopSkip; + _innerTopSkip = innerTopSkip; + if (_scroll) { + auto scrollTopWas = _scroll->scrollTop(); + updateScrollAreaGeometry(); + if (scrollBottomFixed) { + _scroll->scrollToY(scrollTopWas + delta); + } + } + } +} + +void BoxContent::setInnerVisible(bool scrollAreaVisible) { + if (_scroll) { + _scroll->setVisible(scrollAreaVisible); + } +} + +QPixmap BoxContent::grabInnerCache() { + auto isTopShadowVisible = !_topShadow->isHidden(); + auto isBottomShadowVisible = !_bottomShadow->isHidden(); + if (isTopShadowVisible) _topShadow->hide(); + if (isBottomShadowVisible) _bottomShadow->hide(); + auto result = myGrab(this, _scroll->geometry()); + if (isTopShadowVisible) _topShadow->show(); + if (isBottomShadowVisible) _bottomShadow->show(); + return std_::move(result); +} + +void BoxContent::resizeEvent(QResizeEvent *e) { + if (_scroll) { + updateScrollAreaGeometry(); + } +} + +void BoxContent::updateScrollAreaGeometry() { + auto newScrollHeight = height() - _innerTopSkip; + auto changed = (_scroll->height() != newScrollHeight); + _scroll->setGeometryToLeft(0, _innerTopSkip, width(), newScrollHeight); + _topShadow->entity()->resize(width(), st::lineWidth); + _topShadow->moveToLeft(0, _innerTopSkip); + _bottomShadow->entity()->resize(width(), st::lineWidth); + _bottomShadow->moveToLeft(0, height() - st::lineWidth); + if (changed) { + updateInnerVisibleTopBottom(); + + auto top = _scroll->scrollTop(); + if (top > 0 || _innerTopSkip > 0) { + _topShadow->showFast(); + } else { + _topShadow->hideFast(); + } + if (top < _scroll->scrollTopMax()) { + _bottomShadow->showFast(); + } else { + _bottomShadow->hideFast(); + } + } +} + +object_ptr BoxContent::doTakeInnerWidget() { + return _scroll->takeWidget(); +} + +void BoxContent::paintEvent(QPaintEvent *e) { Painter p(this); - QRect r(e->rect()); - st::boxBlueTitleShadow.fill(p, QRect(r.left(), 0, r.width(), height())); -} - -BlueTitleClose::BlueTitleClose(QWidget *parent) : Button(parent) -, a_iconFg(st::boxBlueCloseFg->c) -, _a_over(animation(this, &BlueTitleClose::step_over)) { - resize(st::boxTitleHeight, st::boxTitleHeight); - setCursor(style::cur_pointer); - connect(this, SIGNAL(stateChanged(int, ButtonStateChangeSource)), this, SLOT(onStateChange(int, ButtonStateChangeSource))); -} - -void BlueTitleClose::onStateChange(int oldState, ButtonStateChangeSource source) { - if ((oldState & StateOver) != (_state & StateOver)) { - a_iconFg.start(((_state & StateOver) ? st::boxBlueCloseOverFg : st::boxBlueCloseFg)->c); - _a_over.start(); + if (testAttribute(Qt::WA_OpaquePaintEvent)) { + for_const (auto rect, e->region().rects()) { + p.fillRect(rect, st::boxBg); + } } } -void BlueTitleClose::step_over(float64 ms, bool timer) { - float64 dt = ms / st::boxBlueCloseDuration; - if (dt >= 1) { - _a_over.stop(); - a_iconFg.finish(); - } else { - a_iconFg.update(dt, anim::linear); - } - if (timer) update((st::boxTitleHeight - st::boxBlueCloseIcon.width()) / 2, (st::boxTitleHeight - st::boxBlueCloseIcon.height()) / 2, st::boxBlueCloseIcon.width(), st::boxBlueCloseIcon.height()); +AbstractBox::AbstractBox(QWidget *parent, object_ptr content) : LayerWidget(parent) +, _content(std_::move(content)) { + _content->setParent(this); + _content->setDelegate(this); } -void BlueTitleClose::paintEvent(QPaintEvent *e) { +void AbstractBox::setLayerType(bool layerType) { + _layerType = layerType; +} + +int AbstractBox::titleHeight() const { + return _layerType ? st::boxLayerTitleHeight : st::boxTitleHeight; +} + +int AbstractBox::buttonsHeight() const { + auto padding = _layerType ? st::boxLayerButtonPadding : st::boxButtonPadding; + return padding.top() + st::defaultBoxButton.height + padding.bottom(); +} + +int AbstractBox::buttonsTop() const { + auto padding = _layerType ? st::boxLayerButtonPadding : st::boxButtonPadding; + return height() - padding.bottom() - st::defaultBoxButton.height; +} + +void AbstractBox::paintEvent(QPaintEvent *e) { Painter p(this); - - QRect r(e->rect()), s((st::boxTitleHeight - st::boxBlueCloseIcon.width()) / 2, (st::boxTitleHeight - st::boxBlueCloseIcon.height()) / 2, st::boxBlueCloseIcon.width(), st::boxBlueCloseIcon.height()); - if (!s.contains(r)) { - p.fillRect(r, st::boxBlueTitleBg); + auto clip = e->rect(); + auto paintTopRounded = clip.intersects(QRect(0, 0, width(), st::boxRadius)); + auto paintBottomRounded = clip.intersects(QRect(0, height() - st::boxRadius, width(), st::boxRadius)); + if (paintTopRounded || paintBottomRounded) { + auto parts = qFlags(App::RectPart::None); + if (paintTopRounded) parts |= App::RectPart::TopFull; + if (paintBottomRounded) parts |= App::RectPart::BottomFull; + App::roundRect(p, rect(), st::boxBg, BoxCorners, nullptr, parts); } - if (s.intersects(r)) { - p.fillRect(s.intersected(r), a_iconFg.current()); - st::boxBlueCloseIcon.paint(p, s.topLeft(), width()); + auto other = e->region().intersected(QRect(0, st::boxRadius, width(), height() - 2 * st::boxRadius)); + if (!other.isEmpty()) { + for_const (auto rect, other.rects()) { + p.fillRect(rect, st::boxBg); + } } -} - -AbstractBox::AbstractBox(int w) : LayerWidget() { - setAttribute(Qt::WA_OpaquePaintEvent); - resize(w, 0); -} - -void AbstractBox::prepare() { - showAll(); -} - -void AbstractBox::keyPressEvent(QKeyEvent *e) { - if (e->key() == Qt::Key_Escape) { - onClose(); - } else { - LayerWidget::keyPressEvent(e); + if (!_title.isEmpty() && clip.intersects(QRect(0, 0, width(), titleHeight()))) { + paintTitle(p, _title, _additionalTitle); } } -void AbstractBox::resizeEvent(QResizeEvent *e) { - if (_blueClose) { - _blueClose->moveToRight(0, 0); - } - if (_blueShadow) { - _blueShadow->moveToLeft(0, st::boxTitleHeight); - _blueShadow->resize(width(), st::boxBlueTitleShadow.height()); - } - LayerWidget::resizeEvent(e); -} - -void AbstractBox::parentResized() { - int32 newHeight = countHeight(); - setGeometry((App::wnd()->width() - width()) / 2, (App::wnd()->height() - newHeight) / 2, width(), newHeight); - update(); -} - -bool AbstractBox::paint(QPainter &p) { - p.fillRect(rect(), st::boxBg); - return false; -} - void AbstractBox::paintTitle(Painter &p, const QString &title, const QString &additional) { p.setFont(st::boxTitleFont); - if (_blueTitle) { - p.fillRect(0, 0, width(), st::boxTitleHeight, st::boxBlueTitleBg->b); - p.setPen(st::white); - - int32 titleWidth = st::boxTitleFont->width(title); - p.drawTextLeft(st::boxBlueTitlePosition.x(), st::boxBlueTitlePosition.y(), width(), title, titleWidth); - + p.setPen(st::boxTitleFg); + if (_layerType) { + auto titleWidth = st::boxTitleFont->width(title); + p.drawTextLeft(st::boxLayerTitlePosition.x(), st::boxLayerTitlePosition.y(), width(), title, titleWidth); if (!additional.isEmpty()) { - p.setFont(st::boxTextFont); - p.setPen(st::boxBlueTitleAdditionalFg); - p.drawTextLeft(st::boxBlueTitlePosition.x() + titleWidth + st::boxBlueTitleAdditionalSkip, st::boxBlueTitlePosition.y(), width(), additional); + p.setFont(st::boxLayerTitleAdditionalFont); + p.setPen(st::boxTitleAdditionalFg); + p.drawTextLeft(st::boxLayerTitlePosition.x() + titleWidth + st::boxLayerTitleAdditionalSkip, st::boxLayerTitlePosition.y() + st::boxTitleFont->ascent - st::boxLayerTitleAdditionalFont->ascent, width(), additional); } } else { - p.setPen(st::boxTitleFg); p.drawTextLeft(st::boxTitlePosition.x(), st::boxTitlePosition.y(), width(), title); } } -void AbstractBox::paintEvent(QPaintEvent *e) { - QPainter p(this); - if (paint(p)) return; +void AbstractBox::parentResized() { + auto newHeight = countRealHeight(); + auto parentSize = parentWidget()->size(); + setGeometry((parentSize.width() - width()) / 2, (parentSize.height() - newHeight) / 2, width(), newHeight); + update(); } -void AbstractBox::setMaxHeight(int32 maxHeight) { - resizeMaxHeight(width(), maxHeight); +void AbstractBox::setTitle(const QString &title, const QString &additional) { + auto wasTitle = hasTitle(); + _title = title; + _additionalTitle = additional; + update(); + if (wasTitle != hasTitle()) { + updateSize(); + } } -void AbstractBox::resizeMaxHeight(int32 newWidth, int32 maxHeight) { - if (width() != newWidth || _maxHeight != maxHeight) { - QRect g(geometry()); - _maxHeight = maxHeight; - resize(newWidth, countHeight()); - if (parentWidget()) { - QRect r = geometry(); - int32 parenth = parentWidget()->height(); - if (r.top() + r.height() + st::boxVerticalMargin > parenth) { - int32 newTop = qMax(parenth - int(st::boxVerticalMargin) - r.height(), (parenth - r.height()) / 2); - if (newTop != r.top()) { - move(r.left(), newTop); - } - } - parentWidget()->update(geometry().united(g).marginsAdded(QMargins(st::boxShadow.width(), st::boxShadow.height(), st::boxShadow.width(), st::boxShadow.height()))); +bool AbstractBox::hasTitle() const { + return !_title.isEmpty() || !_additionalTitle.isEmpty(); +} + +void AbstractBox::updateSize() { + setDimensions(width(), _maxContentHeight); +} + +void AbstractBox::updateButtonsPositions() { + if (!_buttons.isEmpty() || _leftButton) { + auto padding = _layerType ? st::boxLayerButtonPadding : st::boxButtonPadding; + auto right = padding.right(); + auto top = buttonsTop(); + if (_leftButton) { + _leftButton->moveToLeft(right, top); + } + for_const (auto &button, _buttons) { + button->moveToRight(right, top); + right += button->width() + padding.left(); } } } -int AbstractBox::countHeight() const { - return qMin(_maxHeight, App::wnd()->height() - 2 * st::boxVerticalMargin); -} - -void AbstractBox::onClose() { - if (!_closed) { - _closed = true; - closePressed(); +void AbstractBox::clearButtons() { + for (auto &button : base::take(_buttons)) { + button.destroy(); } - emit closed(this); + _leftButton.destroy(); } -void AbstractBox::setBlueTitle(bool blue) { - _blueTitle = blue; - delete _blueShadow; - _blueShadow = new BlueTitleShadow(this); - delete _blueClose; - _blueClose = new BlueTitleClose(this); - _blueClose->setAttribute(Qt::WA_OpaquePaintEvent); - connect(_blueClose, SIGNAL(clicked()), this, SLOT(onClose())); +QPointer AbstractBox::addButton(const QString &text, base::lambda &&clickCallback, const style::RoundButton &st) { + _buttons.push_back(object_ptr(this, text, st)); + auto result = QPointer(_buttons.back()); + result->setClickedCallback(std_::move(clickCallback)); + result->show(); + updateButtonsPositions(); + return result; } -void AbstractBox::raiseShadow() { - if (_blueShadow) { - _blueShadow->raise(); +QPointer AbstractBox::addLeftButton(const QString &text, base::lambda &&clickCallback, const style::RoundButton &st) { + _leftButton = object_ptr(this, text, st); + auto result = QPointer(_leftButton); + result->setClickedCallback(std_::move(clickCallback)); + result->show(); + updateButtonsPositions(); + return result; +} + +void AbstractBox::setDimensions(int newWidth, int maxHeight) { + _maxContentHeight = maxHeight; + + auto fullHeight = countFullHeight(); + if (width() != newWidth || _fullHeight != fullHeight) { + _fullHeight = fullHeight; + if (parentWidget()) { + auto oldGeometry = geometry(); + resize(newWidth, countRealHeight()); + auto newGeometry = geometry(); + auto parentHeight = parentWidget()->height(); + if (newGeometry.top() + newGeometry.height() + st::boxVerticalMargin > parentHeight) { + auto newTop = qMax(parentHeight - int(st::boxVerticalMargin) - newGeometry.height(), (parentHeight - newGeometry.height()) / 2); + if (newTop != newGeometry.top()) { + move(newGeometry.left(), newTop); + } + } + parentWidget()->update(oldGeometry.united(geometry()).marginsAdded(st::boxRoundShadow.extend)); + } else { + resize(newWidth, 0); + } } } -ScrollableBox::ScrollableBox(const style::flatScroll &scroll, int32 w) : AbstractBox(w) -, _scroll(this, scroll) -, _topSkip(st::boxTitleHeight) -, _bottomSkip(st::boxScrollSkip) { - setBlueTitle(true); +int AbstractBox::countRealHeight() const { + return qMin(_fullHeight, parentWidget()->height() - 2 * st::boxVerticalMargin); } -void ScrollableBox::resizeEvent(QResizeEvent *e) { - updateScrollGeometry(); - AbstractBox::resizeEvent(e); +int AbstractBox::countFullHeight() const { + return contentTop() + _maxContentHeight + buttonsHeight(); } -void ScrollableBox::init(ScrolledWidget *inner, int bottomSkip, int topSkip) { - _bottomSkip = bottomSkip; - _topSkip = topSkip; - _scroll->setOwnedWidget(inner); - _scroll->setFocusPolicy(Qt::NoFocus); - updateScrollGeometry(); +int AbstractBox::contentTop() const { + return hasTitle() ? titleHeight() : (_noContentMargin ? 0 : st::boxTopMargin); } -void ScrollableBox::setScrollSkips(int bottomSkip, int topSkip) { - if (_topSkip != topSkip || _bottomSkip != bottomSkip) { - _topSkip = topSkip; - _bottomSkip = bottomSkip; - updateScrollGeometry(); +void AbstractBox::resizeEvent(QResizeEvent *e) { + updateButtonsPositions(); + + auto top = contentTop(); + _content->resize(width(), height() - top - buttonsHeight()); + _content->moveToLeft(0, top); + + LayerWidget::resizeEvent(e); +} + +void AbstractBox::keyPressEvent(QKeyEvent *e) { + if (e->key() == Qt::Key_Escape) { + closeBox(); + } else { + LayerWidget::keyPressEvent(e); } } - -void ScrollableBox::updateScrollGeometry() { - _scroll->setGeometry(0, _topSkip, width(), height() - _topSkip - _bottomSkip); -} - -ItemListBox::ItemListBox(const style::flatScroll &scroll, int32 w) : ScrollableBox(scroll, w) { - setMaxHeight(st::boxMaxListHeight); -} diff --git a/Telegram/SourceFiles/boxes/abstractbox.h b/Telegram/SourceFiles/boxes/abstractbox.h index 4c6e28b1a..89fa7641b 100644 --- a/Telegram/SourceFiles/boxes/abstractbox.h +++ b/Telegram/SourceFiles/boxes/abstractbox.h @@ -23,110 +23,234 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "layerwidget.h" #include "ui/widgets/shadow.h" -class BlueTitleShadow : public TWidget { +namespace Ui { +class RoundButton; +class IconButton; +class ScrollArea; +template +class WidgetFadeWrap; +} // namespace Ui + +class BoxLayerTitleShadow : public Ui::PlainShadow { public: - BlueTitleShadow(QWidget *parent) : TWidget(parent) { - } - void paintEvent(QPaintEvent *e); + BoxLayerTitleShadow(QWidget *parent); + }; -class BlueTitleClose : public Button { +class BoxContentDelegate { +public: + virtual void setLayerType(bool layerType) = 0; + virtual void setTitle(const QString &title, const QString &additional) = 0; + + virtual void clearButtons() = 0; + virtual QPointer addButton(const QString &text, base::lambda &&clickCallback, const style::RoundButton &st) = 0; + virtual QPointer addLeftButton(const QString &text, base::lambda &&clickCallback, const style::RoundButton &st) = 0; + virtual void updateButtonsPositions() = 0; + + virtual void setDimensions(int newWidth, int maxHeight) = 0; + virtual void setNoContentMargin(bool noContentMargin) = 0; + virtual bool isBoxShown() const = 0; + virtual void closeBox() = 0; + +}; + +class BoxContent : public TWidget, protected base::Subscriber { Q_OBJECT public: - BlueTitleClose(QWidget *parent); - void paintEvent(QPaintEvent *e); + BoxContent() { + setAttribute(Qt::WA_OpaquePaintEvent); + } + + void setDelegate(BoxContentDelegate *newDelegate) { + _delegate = newDelegate; + prepare(); + setInnerFocus(); + } + virtual void setInnerFocus() { + setFocus(); + } + virtual void closeHook() { + } + + bool isBoxShown() const { + return getDelegate()->isBoxShown(); + } + void closeBox() { + getDelegate()->closeBox(); + } public slots: + void onScrollToY(int top, int bottom = -1); - void onStateChange(int oldState, ButtonStateChangeSource source); + void onDraggingScrollDelta(int delta); + +protected: + virtual void prepare() = 0; + + void setLayerType(bool layerType) { + getDelegate()->setLayerType(layerType); + } + void setTitle(const QString &title, const QString &additional = QString()) { + getDelegate()->setTitle(title, additional); + } + + void clearButtons() { + getDelegate()->clearButtons(); + } + QPointer addButton(const QString &text, base::lambda &&clickCallback); + QPointer addLeftButton(const QString &text, base::lambda &&clickCallback); + QPointer addButton(const QString &text, base::lambda &&clickCallback, const style::RoundButton &st) { + return getDelegate()->addButton(text, std_::move(clickCallback), st); + } + void updateButtonsGeometry() { + getDelegate()->updateButtonsPositions(); + } + + void setNoContentMargin(bool noContentMargin) { + if (_noContentMargin != noContentMargin) { + _noContentMargin = noContentMargin; + setAttribute(Qt::WA_OpaquePaintEvent, !_noContentMargin); + } + getDelegate()->setNoContentMargin(noContentMargin); + } + void setDimensions(int newWidth, int maxHeight) { + getDelegate()->setDimensions(newWidth, maxHeight); + } + void setInnerTopSkip(int topSkip, bool scrollBottomFixed = false); + + template + QPointer setInnerWidget(object_ptr inner, const style::ScrollArea &st, int topSkip = 0) { + auto result = QPointer(inner.data()); + setInnerTopSkip(topSkip); + setInner(std_::move(inner), st); + return result; + } + + template + QPointer setInnerWidget(object_ptr inner, int topSkip = 0) { + auto result = QPointer(inner.data()); + setInnerTopSkip(topSkip); + setInner(std_::move(inner)); + return result; + } + + template + object_ptr takeInnerWidget() { + return static_object_cast(doTakeInnerWidget()); + } + + void setInnerVisible(bool scrollAreaVisible); + QPixmap grabInnerCache(); + + void resizeEvent(QResizeEvent *e) override; + void paintEvent(QPaintEvent *e) override; + +private slots: + void onScroll(); + void onInnerResize(); + + void onDraggingScrollTimer(); private: - void step_over(float64 ms, bool timer); - anim::cvalue a_iconFg; - Animation _a_over; + void setInner(object_ptr inner); + void setInner(object_ptr inner, const style::ScrollArea &st); + void updateScrollAreaGeometry(); + void updateInnerVisibleTopBottom(); + void updateShadowsVisibility(); + object_ptr doTakeInnerWidget(); + + BoxContentDelegate *getDelegate() const { + t_assert(_delegate != nullptr); + return _delegate; + } + BoxContentDelegate *_delegate = nullptr; + + bool _noContentMargin = false; + int _innerTopSkip = 0; + object_ptr _scroll = { nullptr }; + object_ptr> _topShadow = { nullptr }; + object_ptr> _bottomShadow = { nullptr }; + + object_ptr _draggingScrollTimer = { nullptr }; + int _draggingScrollDelta = 0; }; -class AbstractBox : public LayerWidget, protected base::Subscriber { - Q_OBJECT - +class AbstractBox : public LayerWidget, public BoxContentDelegate, protected base::Subscriber { public: - AbstractBox(int w = st::boxWideWidth); + AbstractBox(QWidget *parent, object_ptr content); + void parentResized() override; - void showDone() override { - showAll(); + + void setLayerType(bool layerType) override; + void setTitle(const QString &title, const QString &additional) override; + + void clearButtons() override; + QPointer addButton(const QString &text, base::lambda &&clickCallback, const style::RoundButton &st) override; + QPointer addLeftButton(const QString &text, base::lambda &&clickCallback, const style::RoundButton &st) override; + void updateButtonsPositions() override; + + void setDimensions(int newWidth, int maxHeight) override; + + void setNoContentMargin(bool noContentMargin) override { + if (_noContentMargin != noContentMargin) { + _noContentMargin = noContentMargin; + updateSize(); + } } - void setBlueTitle(bool blue); - void raiseShadow(); - -public slots: - void onClose(); + bool isBoxShown() const override { + return !isHidden(); + } + void closeBox() override { + closeLayer(); + } protected: void keyPressEvent(QKeyEvent *e) override; void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; - void prepare(); - bool paint(QPainter &p); + void doSetInnerFocus() override { + _content->setInnerFocus(); + } + void closeHook() override { + _content->closeHook(); + } + +private: void paintTitle(Painter &p, const QString &title, const QString &additional = QString()); - void setMaxHeight(int32 maxHeight); - void resizeMaxHeight(int32 newWidth, int32 maxHeight); - virtual void closePressed() { - } - virtual void showAll() { - if (_blueClose) _blueClose->show(); - if (_blueShadow) _blueShadow->show(); - } + bool hasTitle() const; + int titleHeight() const; + int buttonsHeight() const; + int buttonsTop() const; + int contentTop() const; + int countFullHeight() const; + int countRealHeight() const; + void updateSize(); -private: - int _maxHeight = 0; - int countHeight() const; + int _fullHeight = 0; - bool _closed = false; + bool _noContentMargin = false; + int _maxContentHeight = 0; + object_ptr _content; - bool _blueTitle = false; - BlueTitleClose *_blueClose = nullptr; - BlueTitleShadow *_blueShadow = nullptr; + QString _title; + QString _additionalTitle; + bool _layerType = false; + + std_::vector_of_moveable> _buttons; + object_ptr _leftButton = { nullptr }; }; -class ScrollableBoxShadow : public Ui::PlainShadow { -public: - ScrollableBoxShadow(QWidget *parent) : Ui::PlainShadow(parent, st::boxScrollShadowBg) { - } -}; - -class ScrollableBox : public AbstractBox { -public: - ScrollableBox(const style::flatScroll &scroll, int w = st::boxWideWidth); - -protected: - void init(ScrolledWidget *inner, int bottomSkip = st::boxScrollSkip, int topSkip = st::boxTitleHeight); - void setScrollSkips(int bottomSkip = st::boxScrollSkip, int topSkip = st::boxTitleHeight); - - void resizeEvent(QResizeEvent *e) override; - - ScrollArea *scrollArea() { - return _scroll; - } - -private: - void updateScrollGeometry(); - - ChildWidget _scroll; - int _topSkip, _bottomSkip; - -}; - -class ItemListBox : public ScrollableBox { -public: - ItemListBox(const style::flatScroll &scroll, int32 w = st::boxWideWidth); - -}; +template +inline object_ptr Box(Args&&... args) { + auto parent = static_cast(nullptr); + return object_ptr(parent, std_::forward(args)...); +} enum CreatingGroupType { CreatingGroupNone, diff --git a/Telegram/SourceFiles/boxes/addcontactbox.cpp b/Telegram/SourceFiles/boxes/addcontactbox.cpp index af5f46f44..e6f75ba03 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.cpp +++ b/Telegram/SourceFiles/boxes/addcontactbox.cpp @@ -19,136 +19,116 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "lang.h" +#include "boxes/addcontactbox.h" +#include "styles/style_boxes.h" +#include "styles/style_dialogs.h" +#include "lang.h" #include "application.h" -#include "addcontactbox.h" -#include "contactsbox.h" -#include "confirmbox.h" -#include "photocropbox.h" +#include "boxes/contactsbox.h" +#include "boxes/confirmbox.h" +#include "boxes/photocropbox.h" #include "ui/filedialog.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" +#include "ui/widgets/labels.h" +#include "ui/toast/toast.h" +#include "ui/buttons/peer_avatar_button.h" #include "mainwidget.h" #include "mainwindow.h" #include "apiwrap.h" #include "observer_peer.h" -#include "styles/style_boxes.h" -#include "styles/style_dialogs.h" -AddContactBox::AddContactBox(QString fname, QString lname, QString phone) : AbstractBox(st::boxWidth) -, _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) +AddContactBox::AddContactBox(QWidget*, QString fname, QString lname, QString phone) +: _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()) { if (!phone.isEmpty()) { - _phone.setDisabled(true); + _phone->setDisabled(true); } - - initBox(); } -AddContactBox::AddContactBox(UserData *user) : AbstractBox(st::boxWidth) -, _user(user) -, _save(this, lang(lng_settings_save), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) -, _retry(this, lang(lng_try_other_contact), st::defaultBoxButton) +AddContactBox::AddContactBox(QWidget*, UserData *user) +: _user(user) , _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()) { - _phone.setDisabled(true); - initBox(); + _phone->setDisabled(true); } -void AddContactBox::initBox() { +void AddContactBox::prepare() { if (_invertOrder) { - setTabOrder(&_last, &_first); + setTabOrder(_last, _first); } if (_user) { - _boxTitle = lang(lng_edit_contact_title); + setTitle(lang(lng_edit_contact_title)); } else { - bool readyToAdd = !_phone.getLastText().isEmpty() && (!_first.getLastText().isEmpty() || !_last.getLastText().isEmpty()); - _boxTitle = lang(readyToAdd ? lng_confirm_contact_data : lng_enter_contact_data); + bool readyToAdd = !_phone->getLastText().isEmpty() && (!_first->getLastText().isEmpty() || !_last->getLastText().isEmpty()); + setTitle(lang(readyToAdd ? lng_confirm_contact_data : lng_enter_contact_data)); } - setMaxHeight(st::boxTitleHeight + st::contactPadding.top() + _first.height() + st::contactSkip + _last.height() + st::contactPhoneSkip + _phone.height() + st::contactPadding.bottom() + st::boxPadding.bottom() + st::boxButtonPadding.top() + _save.height() + st::boxButtonPadding.bottom()); - _retry.hide(); + updateButtons(); - connect(&_save, SIGNAL(clicked()), this, SLOT(onSave())); - connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); - connect(&_retry, SIGNAL(clicked()), this, SLOT(onRetry())); + connect(_first, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); + connect(_last, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); + connect(_phone, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); - connect(&_first, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); - connect(&_last, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); - connect(&_phone, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); - - prepare(); + setDimensions(st::boxWideWidth, st::contactPadding.top() + _first->height() + st::contactSkip + _last->height() + st::contactPhoneSkip + _phone->height() + st::contactPadding.bottom() + st::boxPadding.bottom()); } -void AddContactBox::showAll() { - _first.show(); - _last.show(); - _phone.show(); - _save.show(); - _cancel.show(); -} - -void AddContactBox::doSetInnerFocus() { - if ((_first.getLastText().isEmpty() && _last.getLastText().isEmpty()) || !_phone.isEnabled()) { - (_invertOrder ? _last : _first).setFocus(); +void AddContactBox::setInnerFocus() { + if ((_first->getLastText().isEmpty() && _last->getLastText().isEmpty()) || !_phone->isEnabled()) { + (_invertOrder ? _last : _first)->setFocusFast(); + _phone->finishAnimations(); } else { - _phone.setFocus(); + _phone->setFocusFast(); } } void AddContactBox::paintEvent(QPaintEvent *e) { + BoxContent::paintEvent(e); + Painter p(this); - if (paint(p)) return; - - paintTitle(p, _boxTitle); - - if (_retry.isHidden()) { - st::contactUserIcon.paint(p, st::boxPadding.left(), _first.y() + st::contactIconTop, width()); - st::contactPhoneIcon.paint(p, st::boxPadding.left(), _phone.y() + st::contactIconTop, width()); + if (_retrying) { + p.setPen(st::boxTextFg); + p.setFont(st::boxTextFont); + auto textHeight = height() - st::contactPadding.top() - st::contactPadding.bottom() - st::boxPadding.bottom(); + p.drawText(QRect(st::boxPadding.left(), st::contactPadding.top(), width() - st::boxPadding.left() - st::boxPadding.right(), textHeight), lng_contact_not_joined(lt_name, _sentName), style::al_topleft); } else { - p.setPen(st::black->p); - p.setFont(st::boxTextFont->f); - int32 h = height() - st::boxTitleHeight - st::contactPadding.top() - st::contactPadding.bottom() - st::boxPadding.bottom() - st::boxButtonPadding.top() - _retry.height() - st::boxButtonPadding.bottom(); - p.drawText(QRect(st::boxPadding.left(), st::boxTitleHeight + st::contactPadding.top(), width() - st::boxPadding.left() - st::boxPadding.right(), h), lng_contact_not_joined(lt_name, _sentName), style::al_topleft); + st::contactUserIcon.paint(p, st::boxPadding.left(), _first->y() + st::contactIconTop, width()); + st::contactPhoneIcon.paint(p, st::boxPadding.left(), _phone->y() + st::contactIconTop, width()); } } void AddContactBox::resizeEvent(QResizeEvent *e) { - _first.resize(width() - st::boxPadding.left() - st::contactPadding.left() - st::boxPadding.right(), _first.height()); - _last.resize(_first.width(), _last.height()); - _phone.resize(_first.width(), _last.height()); - if (_invertOrder) { - _last.moveToLeft(st::boxPadding.left() + st::contactPadding.left(), st::boxTitleHeight + st::contactPadding.top()); - _first.moveToLeft(st::boxPadding.left() + st::contactPadding.left(), _last.y() + _last.height() + st::contactSkip); - _phone.moveToLeft(st::boxPadding.left() + st::contactPadding.left(), _first.y() + _first.height() + st::contactPhoneSkip); - } else { - _first.moveToLeft(st::boxPadding.left() + st::contactPadding.left(), st::boxTitleHeight + st::contactPadding.top()); - _last.moveToLeft(st::boxPadding.left() + st::contactPadding.left(), _first.y() + _first.height() + st::contactSkip); - _phone.moveToLeft(st::boxPadding.left() + st::contactPadding.left(), _last.y() + _last.height() + st::contactPhoneSkip); - } + BoxContent::resizeEvent(e); - _save.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _save.height()); - _retry.moveToRight(st::boxButtonPadding.right(), _save.y()); - _cancel.moveToRight(st::boxButtonPadding.right() + (_retry.isHidden() ? _save.width() : _retry.width()) + st::boxButtonPadding.left(), _save.y()); - AbstractBox::resizeEvent(e); + _first->resize(width() - st::boxPadding.left() - st::contactPadding.left() - st::boxPadding.right(), _first->height()); + _last->resize(_first->width(), _last->height()); + _phone->resize(_first->width(), _last->height()); + if (_invertOrder) { + _last->moveToLeft(st::boxPadding.left() + st::contactPadding.left(), st::contactPadding.top()); + _first->moveToLeft(st::boxPadding.left() + st::contactPadding.left(), _last->y() + _last->height() + st::contactSkip); + _phone->moveToLeft(st::boxPadding.left() + st::contactPadding.left(), _first->y() + _first->height() + st::contactPhoneSkip); + } else { + _first->moveToLeft(st::boxPadding.left() + st::contactPadding.left(), st::contactPadding.top()); + _last->moveToLeft(st::boxPadding.left() + st::contactPadding.left(), _first->y() + _first->height() + st::contactSkip); + _phone->moveToLeft(st::boxPadding.left() + st::contactPadding.left(), _last->y() + _last->height() + st::contactPhoneSkip); + } } void AddContactBox::onSubmit() { - if (_first.hasFocus()) { - _last.setFocus(); - } else if (_last.hasFocus()) { - if (_phone.isEnabled()) { - _phone.setFocus(); + if (_first->hasFocus()) { + _last->setFocus(); + } else if (_last->hasFocus()) { + if (_phone->isEnabled()) { + _phone->setFocus(); } else { onSave(); } - } else if (_phone.hasFocus()) { + } else if (_phone->hasFocus()) { onSave(); } } @@ -156,21 +136,21 @@ void AddContactBox::onSubmit() { void AddContactBox::onSave() { if (_addRequest) return; - QString firstName = prepareText(_first.getLastText()); - QString lastName = prepareText(_last.getLastText()); - QString phone = _phone.getLastText().trimmed(); + QString firstName = prepareText(_first->getLastText()); + QString lastName = prepareText(_last->getLastText()); + QString phone = _phone->getLastText().trimmed(); if (firstName.isEmpty() && lastName.isEmpty()) { if (_invertOrder) { - _last.setFocus(); - _last.showError(); + _last->setFocus(); + _last->showError(); } else { - _first.setFocus(); - _first.showError(); + _first->setFocus(); + _first->showError(); } return; } else if (!_user && !App::isValidPhone(phone)) { - _phone.setFocus(); - _phone.showError(); + _phone->setFocus(); + _phone->showError(); return; } if (firstName.isEmpty()) { @@ -194,27 +174,27 @@ bool AddContactBox::onSaveUserFail(const RPCError &error) { _addRequest = 0; QString err(error.type()); - QString firstName = _first.getLastText().trimmed(), lastName = _last.getLastText().trimmed(); + QString firstName = _first->getLastText().trimmed(), lastName = _last->getLastText().trimmed(); if (err == "CHAT_TITLE_NOT_MODIFIED") { _user->setName(firstName, lastName, _user->nameOrPhone, _user->username); - onClose(); + closeBox(); return true; } else if (err == "NO_CHAT_TITLE") { - _first.setFocus(); - _first.showError(); + _first->setFocus(); + _first->showError(); return true; } - _first.setFocus(); + _first->setFocus(); return true; } void AddContactBox::onImportDone(const MTPcontacts_ImportedContacts &res) { - if (isHidden() || !App::main()) return; + if (!isBoxShown() || !App::main()) return; - const auto &d(res.c_contacts_importedContacts()); + auto &d = res.c_contacts_importedContacts(); App::feedUsers(d.vusers); - const auto &v(d.vimported.c_vector().v); + auto &v = d.vimported.c_vector().v; UserData *user = nullptr; if (!v.isEmpty()) { const auto &c(v.front().c_importedContact()); @@ -226,12 +206,9 @@ void AddContactBox::onImportDone(const MTPcontacts_ImportedContacts &res) { Notify::userIsContactChanged(user, true); Ui::hideLayer(); } else { - _save.hide(); - _first.hide(); - _last.hide(); - _phone.hide(); - _retry.show(); - resizeEvent(0); + hideChildren(); + _retrying = true; + updateButtons(); update(); } } @@ -239,240 +216,113 @@ void AddContactBox::onImportDone(const MTPcontacts_ImportedContacts &res) { void AddContactBox::onSaveUserDone(const MTPcontacts_ImportedContacts &res) { auto &d = res.c_contacts_importedContacts(); App::feedUsers(d.vusers); - onClose(); + closeBox(); } void AddContactBox::onRetry() { _addRequest = 0; _contactId = 0; - _save.show(); - _retry.hide(); - resizeEvent(0); - showAll(); - _first.setText(QString()); - _first.updatePlaceholder(); - _last.setText(QString()); - _last.updatePlaceholder(); - _phone.clearText(); - _phone.setDisabled(false); - _first.setFocus(); + showChildren(); + _retrying = false; + updateButtons(); + _first->setText(QString()); + _last->setText(QString()); + _phone->clearText(); + _phone->setDisabled(false); + _first->setFocus(); update(); } -NewGroupBox::NewGroupBox() : AbstractBox(), -_group(this, qsl("group_type"), 0, lang(lng_create_group_title), true), -_channel(this, qsl("group_type"), 1, lang(lng_create_channel_title)), -_aboutGroupWidth(width() - st::boxPadding.left() - st::boxButtonPadding.right() - st::newGroupPadding.left() - st::defaultRadiobutton.textPosition.x()), -_aboutGroup(st::normalFont, lng_create_group_about(lt_count, Global::MegagroupSizeMax()), _defaultOptions, _aboutGroupWidth), -_aboutChannel(st::normalFont, lang(lng_create_channel_about), _defaultOptions, _aboutGroupWidth), -_next(this, lang(lng_create_group_next), st::defaultBoxButton), -_cancel(this, lang(lng_cancel), st::cancelBoxButton) { - _aboutGroupHeight = _aboutGroup.countHeight(_aboutGroupWidth); - setMaxHeight(st::boxPadding.top() + st::newGroupPadding.top() + _group.height() + _aboutGroupHeight + st::newGroupSkip + _channel.height() + _aboutChannel.countHeight(_aboutGroupWidth) + st::newGroupPadding.bottom() + st::boxPadding.bottom() + st::boxButtonPadding.top() + _next.height() + st::boxButtonPadding.bottom()); - - connect(&_next, SIGNAL(clicked()), this, SLOT(onNext())); - connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); - - prepare(); -} - -void NewGroupBox::showAll() { - _group.show(); - _channel.show(); - _cancel.show(); - _next.show(); -} - -void NewGroupBox::keyPressEvent(QKeyEvent *e) { - if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { - onNext(); +void AddContactBox::updateButtons() { + clearButtons(); + if (_retrying) { + addButton(lang(lng_try_other_contact), [this] { onRetry(); }); } else { - AbstractBox::keyPressEvent(e); + addButton(lang(_user ? lng_settings_save : lng_add_contact), [this] { onSave(); }); + addButton(lang(lng_cancel), [this] { closeBox(); }); } } -void NewGroupBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; - - p.setPen(st::newGroupAboutFg->p); - - QRect aboutGroup(st::boxPadding.left() + st::newGroupPadding.left() + st::defaultRadiobutton.textPosition.x(), _group.y() + _group.height() + st::lineWidth, _aboutGroupWidth, _aboutGroupHeight); - _aboutGroup.drawLeft(p, aboutGroup.x(), aboutGroup.y(), aboutGroup.width(), width()); - - QRect aboutChannel(st::boxPadding.left() + st::newGroupPadding.left() + st::defaultRadiobutton.textPosition.x(), _channel.y() + _channel.height() + st::lineWidth, _aboutGroupWidth, _aboutGroupHeight); - _aboutChannel.drawLeft(p, aboutChannel.x(), aboutChannel.y(), aboutChannel.width(), width()); +GroupInfoBox::GroupInfoBox(QWidget*, CreatingGroupType creating, bool fromTypeChoose) +: _creating(creating) +, _fromTypeChoose(fromTypeChoose) +, _photo(this, st::newGroupPhotoSize, st::newGroupPhotoIconPosition) +, _title(this, st::defaultInputField, lang(_creating == CreatingGroupChannel ? lng_dlg_new_channel_name : lng_dlg_new_group_name)) { } -void NewGroupBox::resizeEvent(QResizeEvent *e) { - _group.moveToLeft(st::boxPadding.left() + st::newGroupPadding.left(), st::boxPadding.top() + st::newGroupPadding.top()); - _channel.moveToLeft(st::boxPadding.left() + st::newGroupPadding.left(), _group.y() + _group.height() + _aboutGroupHeight + st::newGroupSkip); - - _next.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _next.height()); - _cancel.moveToRight(st::boxButtonPadding.right() + _next.width() + st::boxButtonPadding.left(), _next.y()); - AbstractBox::resizeEvent(e); -} - -void NewGroupBox::onNext() { - Ui::showLayer(new GroupInfoBox(_group.checked() ? CreatingGroupGroup : CreatingGroupChannel, true), KeepOtherLayers); -} - -GroupInfoBox::GroupInfoBox(CreatingGroupType creating, bool fromTypeChoose) : AbstractBox(), -_creating(creating), -a_photoOver(0, 0), -_a_photoOver(animation(this, &GroupInfoBox::step_photoOver)), -_photoOver(false), -_title(this, st::defaultInputField, lang(_creating == CreatingGroupChannel ? lng_dlg_new_channel_name : lng_dlg_new_group_name)), -_description(this, st::newGroupDescription, lang(lng_create_group_description)), -_next(this, lang(_creating == CreatingGroupChannel ? lng_create_group_create : lng_create_group_next), st::defaultBoxButton), -_cancel(this, lang(fromTypeChoose ? lng_create_group_back : lng_cancel), st::cancelBoxButton), -_creationRequestId(0), _createdChannel(0) { - +void GroupInfoBox::prepare() { setMouseTracking(true); - _title.setMaxLength(MaxGroupChannelTitle); + _title->setMaxLength(MaxGroupChannelTitle); - _description.setMaxLength(MaxChannelDescription); - _description.resize(width() - st::boxPadding.left() - st::newGroupInfoPadding.left() - st::boxPadding.right(), _description.height()); + if (_creating == CreatingGroupChannel) { + _description.create(this, st::newGroupDescription, lang(lng_create_group_description)); + _description->show(); + _description->setMaxLength(MaxChannelDescription); - updateMaxHeight(); - connect(&_description, SIGNAL(resized()), this, SLOT(onDescriptionResized())); - connect(&_description, SIGNAL(submitted(bool)), this, SLOT(onNext())); - connect(&_description, SIGNAL(cancelled()), this, SLOT(onClose())); + connect(_description, SIGNAL(resized()), this, SLOT(onDescriptionResized())); + connect(_description, SIGNAL(submitted(bool)), this, SLOT(onNext())); + connect(_description, SIGNAL(cancelled()), this, SLOT(onClose())); + } - connect(&_title, SIGNAL(submitted(bool)), this, SLOT(onNameSubmit())); + connect(_title, SIGNAL(submitted(bool)), this, SLOT(onNameSubmit())); - connect(&_next, SIGNAL(clicked()), this, SLOT(onNext())); - connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); + addButton(lang(_creating == CreatingGroupChannel ? lng_create_group_create : lng_create_group_next), [this] { onNext(); }); + addButton(lang(_fromTypeChoose ? lng_create_group_back : lng_cancel), [this] { closeBox(); }); + _photo->setClickedCallback(App::LambdaDelayed(st::defaultActiveButton.ripple.hideDuration, this, [this] { + auto imgExtensions = cImgExtensions(); + auto filter = qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;") + filedialogAllFilesFilter(); + _setPhotoFileQueryId = FileDialog::queryReadFile(lang(lng_choose_image), filter); + })); subscribe(FileDialog::QueryDone(), [this](const FileDialog::QueryUpdate &update) { notifyFileQueryUpdated(update); }); - prepare(); + updateMaxHeight(); } -void GroupInfoBox::showAll() { - _title.show(); - if (_creating == CreatingGroupChannel) { - _description.show(); - } else { - _description.hide(); - } - _cancel.show(); - _next.show(); -} - -void GroupInfoBox::doSetInnerFocus() { - _title.setFocus(); -} - -void GroupInfoBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; - - QRect phRect(photoRect()); - if (phRect.intersects(e->rect())) { - if (_photoSmall.isNull()) { - float64 o = a_photoOver.current(); - if (o > 0) { - if (o < 1) { - QColor c; - c.setRedF(st::newGroupPhotoBg->c.redF() * (1. - o) + st::newGroupPhotoBgOver->c.redF() * o); - c.setGreenF(st::newGroupPhotoBg->c.greenF() * (1. - o) + st::newGroupPhotoBgOver->c.greenF() * o); - c.setBlueF(st::newGroupPhotoBg->c.blueF() * (1. - o) + st::newGroupPhotoBgOver->c.blueF() * o); - p.fillRect(phRect, c); - } else { - p.fillRect(phRect, st::newGroupPhotoBgOver->b); - } - } else { - p.fillRect(phRect, st::newGroupPhotoBg->b); - } - p.drawSprite(phRect.topLeft() + st::newGroupPhotoIconPosition, st::newGroupPhotoIcon); - } else { - p.drawPixmap(phRect.topLeft(), _photoSmall); - } - if (phRect.contains(e->rect())) { - return; - } - } +void GroupInfoBox::setInnerFocus() { + _title->setFocusFast(); } void GroupInfoBox::resizeEvent(QResizeEvent *e) { - int32 nameLeft = st::newGroupPhotoSize + st::newGroupNamePosition.x(); - _title.resize(width() - st::boxPadding.left() - st::newGroupInfoPadding.left() - st::boxPadding.right() - nameLeft, _title.height()); - _title.moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left() + nameLeft, st::boxPadding.top() + st::newGroupInfoPadding.top() + st::newGroupNamePosition.y()); + BoxContent::resizeEvent(e); - _description.moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), st::boxPadding.top() + st::newGroupInfoPadding.top() + st::newGroupPhotoSize + st::newGroupDescriptionPadding.top()); + _photo->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), st::boxPadding.top() + st::newGroupInfoPadding.top()); - _next.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _next.height()); - _cancel.moveToRight(st::boxButtonPadding.right() + _next.width() + st::boxButtonPadding.left(), _next.y()); - AbstractBox::resizeEvent(e); -} - -void GroupInfoBox::mouseMoveEvent(QMouseEvent *e) { - updateSelected(e->globalPos()); -} - -void GroupInfoBox::updateSelected(const QPoint &cursorGlobalPosition) { - QPoint p(mapFromGlobal(cursorGlobalPosition)); - - bool photoOver = photoRect().contains(p); - if (photoOver != _photoOver) { - _photoOver = photoOver; - if (_photoSmall.isNull()) { - a_photoOver.start(_photoOver ? 1 : 0); - _a_photoOver.start(); - } + auto nameLeft = st::newGroupPhotoSize + st::newGroupNamePosition.x(); + _title->resize(width() - st::boxPadding.left() - st::newGroupInfoPadding.left() - st::boxPadding.right() - nameLeft, _title->height()); + _title->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left() + nameLeft, st::boxPadding.top() + st::newGroupInfoPadding.top() + st::newGroupNamePosition.y()); + if (_description) { + _description->resize(width() - st::boxPadding.left() - st::newGroupInfoPadding.left() - st::boxPadding.right(), _description->height()); + _description->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), st::boxPadding.top() + st::newGroupInfoPadding.top() + st::newGroupPhotoSize + st::newGroupDescriptionPadding.top()); } - - setCursor(_photoOver ? style::cur_pointer : style::cur_default); -} - -void GroupInfoBox::mousePressEvent(QMouseEvent *e) { - mouseMoveEvent(e); - if (_photoOver) { - onPhoto(); - } -} - -void GroupInfoBox::leaveEvent(QEvent *e) { - updateSelected(QCursor::pos()); -} - -void GroupInfoBox::step_photoOver(float64 ms, bool timer) { - float64 dt = ms / st::setPhotoDuration; - if (dt >= 1) { - _a_photoOver.stop(); - a_photoOver.finish(); - } else { - a_photoOver.update(dt, anim::linear); - } - if (timer) update(photoRect()); } void GroupInfoBox::onNameSubmit() { - if (_title.getLastText().trimmed().isEmpty()) { - _title.setFocus(); - _title.showError(); - } else if (_description.isHidden()) { - onNext(); + if (_title->getLastText().trimmed().isEmpty()) { + _title->setFocus(); + _title->showError(); + } else if (_description) { + _description->setFocus(); } else { - _description.setFocus(); + onNext(); } } void GroupInfoBox::onNext() { if (_creationRequestId) return; - QString title = prepareText(_title.getLastText()), description = prepareText(_description.getLastText(), true); + auto title = prepareText(_title->getLastText()); + auto description = _description ? prepareText(_description->getLastText(), true) : QString(); if (title.isEmpty()) { - _title.setFocus(); - _title.showError(); + _title->setFocus(); + _title->showError(); return; } if (_creating == CreatingGroupGroup) { - Ui::showLayer(new ContactsBox(title, _photoBig), KeepOtherLayers); + Ui::show(Box(title, _photoImage), KeepOtherLayers); } else { bool mega = false; MTPchannels_CreateChannel::Flags flags = mega ? MTPchannels_CreateChannel::Flag::f_megagroup : MTPchannels_CreateChannel::Flag::f_broadcast; @@ -494,8 +344,8 @@ void GroupInfoBox::creationDone(const MTPUpdates &updates) { if (v && !v->isEmpty() && v->front().type() == mtpc_channel) { channel = App::channel(v->front().c_channel().vid.v); if (channel) { - if (!_photoBig.isNull()) { - App::app()->uploadProfilePhoto(_photoBig, channel->id); + if (!_photoImage.isNull()) { + App::app()->uploadProfilePhoto(_photoImage, channel->id); } _createdChannel = channel; _creationRequestId = MTP::send(MTPchannels_ExportInvite(_createdChannel->inputChannel), rpcDone(&GroupInfoBox::exportDone)); @@ -505,7 +355,7 @@ void GroupInfoBox::creationDone(const MTPUpdates &updates) { LOG(("API Error: channel not found in updates (GroupInfoBox::creationDone)")); } - onClose(); + closeBox(); } bool GroupInfoBox::creationFail(const RPCError &error) { @@ -513,11 +363,11 @@ bool GroupInfoBox::creationFail(const RPCError &error) { _creationRequestId = 0; if (error.type() == "NO_CHAT_TITLE") { - _title.setFocus(); - _title.showError(); + _title->setFocus(); + _title->showError(); return true; } else if (error.type() == qstr("USER_RESTRICTED")) { - Ui::showLayer(new InformBox(lang(lng_cant_do_this))); + Ui::show(Box(lang(lng_cant_do_this))); return true; } return false; @@ -528,7 +378,7 @@ void GroupInfoBox::exportDone(const MTPExportedChatInvite &result) { if (result.type() == mtpc_chatInviteExported) { _createdChannel->setInviteLink(qs(result.c_chatInviteExported().vlink)); } - Ui::showLayer(new SetupChannelBox(_createdChannel)); + Ui::show(Box(_createdChannel)); } void GroupInfoBox::onDescriptionResized() { @@ -536,22 +386,12 @@ void GroupInfoBox::onDescriptionResized() { update(); } -QRect GroupInfoBox::photoRect() const { - return myrtlrect(st::boxPadding.left() + st::newGroupInfoPadding.left(), st::boxPadding.top() + st::newGroupInfoPadding.top(), st::newGroupPhotoSize, st::newGroupPhotoSize); -} - void GroupInfoBox::updateMaxHeight() { - int32 h = st::boxPadding.top() + st::newGroupInfoPadding.top() + st::newGroupPhotoSize + st::boxPadding.bottom() + st::newGroupInfoPadding.bottom() + st::boxButtonPadding.top() + _next.height() + st::boxButtonPadding.bottom(); - if (_creating == CreatingGroupChannel) { - h += st::newGroupDescriptionPadding.top() + _description.height() + st::newGroupDescriptionPadding.bottom(); + auto newHeight = st::boxPadding.top() + st::newGroupInfoPadding.top() + st::newGroupPhotoSize + st::boxPadding.bottom() + st::newGroupInfoPadding.bottom(); + if (_description) { + newHeight += st::newGroupDescriptionPadding.top() + _description->height() + st::newGroupDescriptionPadding.bottom(); } - setMaxHeight(h); -} - -void GroupInfoBox::onPhoto() { - auto imgExtensions = cImgExtensions(); - auto filter = qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;") + filedialogAllFilesFilter(); - _setPhotoFileQueryId = FileDialog::queryReadFile(lang(lng_choose_images), filter); + setDimensions(st::boxWideWidth, newHeight); } void GroupInfoBox::notifyFileQueryUpdated(const FileDialog::QueryUpdate &update) { @@ -572,152 +412,127 @@ void GroupInfoBox::notifyFileQueryUpdated(const FileDialog::QueryUpdate &update) if (img.isNull() || img.width() > 10 * img.height() || img.height() > 10 * img.width()) { return; } - PhotoCropBox *box = new PhotoCropBox(img, (_creating == CreatingGroupChannel) ? peerFromChannel(0) : peerFromChat(0)); + auto box = Ui::show(Box(img, (_creating == CreatingGroupChannel) ? peerFromChannel(0) : peerFromChat(0)), KeepOtherLayers); connect(box, SIGNAL(ready(const QImage&)), this, SLOT(onPhotoReady(const QImage&))); - Ui::showLayer(box, KeepOtherLayers); } void GroupInfoBox::onPhotoReady(const QImage &img) { - _photoBig = img; - _photoSmall = App::pixmapFromImageInPlace(img.scaled(st::newGroupPhotoSize * cIntRetinaFactor(), st::newGroupPhotoSize * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - _photoSmall.setDevicePixelRatio(cRetinaFactor()); + _photoImage = img; + _photo->setImage(_photoImage); } -SetupChannelBox::SetupChannelBox(ChannelData *channel, bool existing) : AbstractBox() -, _channel(channel) +SetupChannelBox::SetupChannelBox(QWidget*, ChannelData *channel, bool existing) +: _channel(channel) , _existing(existing) -, _public(this, qsl("channel_privacy"), 0, lang(channel->isMegagroup() ? lng_create_public_group_title : lng_create_public_channel_title), true) -, _private(this, qsl("channel_privacy"), 1, lang(channel->isMegagroup() ? lng_create_private_group_title : lng_create_private_channel_title)) -, _aboutPublicWidth(width() - st::boxPadding.left() - st::boxButtonPadding.right() - st::newGroupPadding.left() - st::defaultRadiobutton.textPosition.x()) -, _aboutPublic(st::normalFont, lang(channel->isMegagroup() ? lng_create_public_group_about : lng_create_public_channel_about), _defaultOptions, _aboutPublicWidth) -, _aboutPrivate(st::normalFont, lang(channel->isMegagroup() ? lng_create_private_group_about : lng_create_private_channel_about), _defaultOptions, _aboutPublicWidth) -, _link(this, st::defaultInputField, QString(), channel->username, true) -, _linkOver(false) -, _save(this, lang(lng_settings_save), st::defaultBoxButton) -, _skip(this, lang(existing ? lng_cancel : lng_create_group_skip), st::cancelBoxButton) -, a_goodOpacity(0, 0) -, _a_goodFade(animation(this, &SetupChannelBox::step_goodFade)) { +, _public(this, qsl("channel_privacy"), 0, lang(channel->isMegagroup() ? lng_create_public_group_title : lng_create_public_channel_title), true, st::defaultBoxCheckbox) +, _private(this, qsl("channel_privacy"), 1, lang(channel->isMegagroup() ? lng_create_private_group_title : lng_create_private_channel_title), false, st::defaultBoxCheckbox) +, _aboutPublicWidth(width() - st::boxPadding.left() - st::boxButtonPadding.right() - st::newGroupPadding.left() - st::defaultBoxCheckbox.textPosition.x()) +, _aboutPublic(st::defaultTextStyle, lang(channel->isMegagroup() ? lng_create_public_group_about : lng_create_public_channel_about), _defaultOptions, _aboutPublicWidth) +, _aboutPrivate(st::defaultTextStyle, lang(channel->isMegagroup() ? lng_create_private_group_about : lng_create_private_channel_about), _defaultOptions, _aboutPublicWidth) +, _link(this, st::setupChannelLink, QString(), channel->username, true) { +} + +void SetupChannelBox::prepare() { + _aboutPublicHeight = _aboutPublic.countHeight(_aboutPublicWidth); + setMouseTracking(true); _checkRequestId = MTP::send(MTPchannels_CheckUsername(_channel->inputChannel, MTP_string("preston")), RPCDoneHandlerPtr(), rpcFail(&SetupChannelBox::onFirstCheckFail)); - _aboutPublicHeight = _aboutPublic.countHeight(_aboutPublicWidth); - updateMaxHeight(); + addButton(lang(lng_settings_save), [this] { onSave(); }); + addButton(lang(_existing ? lng_cancel : lng_create_group_skip), [this] { closeBox(); }); - connect(&_save, SIGNAL(clicked()), this, SLOT(onSave())); - connect(&_skip, SIGNAL(clicked()), this, SLOT(onClose())); - - connect(&_link, SIGNAL(changed()), this, SLOT(onChange())); + connect(_link, SIGNAL(changed()), this, SLOT(onChange())); + _link->setVisible(_public->checked()); _checkTimer.setSingleShot(true); connect(&_checkTimer, SIGNAL(timeout()), this, SLOT(onCheck())); - connect(&_public, SIGNAL(changed()), this, SLOT(onPrivacyChange())); - connect(&_private, SIGNAL(changed()), this, SLOT(onPrivacyChange())); + connect(_public, SIGNAL(changed()), this, SLOT(onPrivacyChange())); + connect(_private, SIGNAL(changed()), this, SLOT(onPrivacyChange())); - prepare(); + updateMaxHeight(); } -void SetupChannelBox::showAll() { - _public.show(); - _private.show(); - if (_public.checked()) { - _link.show(); - } else { - _link.hide(); - } - _save.show(); - _skip.show(); -} - -void SetupChannelBox::doSetInnerFocus() { - if (_link.isHidden()) { +void SetupChannelBox::setInnerFocus() { + if (_link->isHidden()) { setFocus(); } else { - _link.setFocus(); + _link->setFocusFast(); } } void SetupChannelBox::updateMaxHeight() { - if (!_channel->isMegagroup() || _public.checked()) { - setMaxHeight(st::boxPadding.top() + st::newGroupPadding.top() + _public.height() + _aboutPublicHeight + st::newGroupSkip + _private.height() + _aboutPrivate.countHeight(_aboutPublicWidth) + st::newGroupSkip + st::newGroupPadding.bottom() + st::newGroupLinkPadding.top() + _link.height() + st::newGroupLinkPadding.bottom() + st::boxButtonPadding.top() + _save.height() + st::boxButtonPadding.bottom()); - } else { - setMaxHeight(st::boxPadding.top() + st::newGroupPadding.top() + _public.height() + _aboutPublicHeight + st::newGroupSkip + _private.height() + _aboutPrivate.countHeight(_aboutPublicWidth) + st::newGroupSkip + st::newGroupPadding.bottom() + st::boxButtonPadding.top() + _save.height() + st::boxButtonPadding.bottom()); + auto newHeight = st::boxPadding.top() + st::newGroupPadding.top() + _public->heightNoMargins() + _aboutPublicHeight + st::newGroupSkip + _private->heightNoMargins() + _aboutPrivate.countHeight(_aboutPublicWidth) + st::newGroupSkip + st::newGroupPadding.bottom(); + if (!_channel->isMegagroup() || _public->checked()) { + newHeight += st::newGroupLinkPadding.top() + _link->height() + st::newGroupLinkPadding.bottom(); } + setDimensions(st::boxWideWidth, newHeight); } void SetupChannelBox::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { - if (_link.hasFocus()) { - if (_link.text().trimmed().isEmpty()) { - _link.setFocus(); - _link.showError(); + if (_link->hasFocus()) { + if (_link->text().trimmed().isEmpty()) { + _link->setFocus(); + _link->showError(); } else { onSave(); } } } else { - AbstractBox::keyPressEvent(e); + BoxContent::keyPressEvent(e); } } void SetupChannelBox::paintEvent(QPaintEvent *e) { Painter p(this); - if (paint(p)) return; + p.fillRect(e->rect(), st::boxBg); p.setPen(st::newGroupAboutFg); - QRect aboutPublic(st::boxPadding.left() + st::newGroupPadding.left() + st::defaultRadiobutton.textPosition.x(), _public.y() + _public.height(), _aboutPublicWidth, _aboutPublicHeight); + QRect aboutPublic(st::boxPadding.left() + st::newGroupPadding.left() + st::defaultBoxCheckbox.textPosition.x(), _public->bottomNoMargins(), _aboutPublicWidth, _aboutPublicHeight); _aboutPublic.drawLeft(p, aboutPublic.x(), aboutPublic.y(), aboutPublic.width(), width()); - QRect aboutPrivate(st::boxPadding.left() + st::newGroupPadding.left() + st::defaultRadiobutton.textPosition.x(), _private.y() + _private.height(), _aboutPublicWidth, _aboutPublicHeight); + QRect aboutPrivate(st::boxPadding.left() + st::newGroupPadding.left() + st::defaultBoxCheckbox.textPosition.x(), _private->bottomNoMargins(), _aboutPublicWidth, _aboutPublicHeight); _aboutPrivate.drawLeft(p, aboutPrivate.x(), aboutPrivate.y(), aboutPrivate.width(), width()); - if (!_channel->isMegagroup() || !_link.isHidden()) { - p.setPen(st::black); + if (!_channel->isMegagroup() || !_link->isHidden()) { + p.setPen(st::boxTextFg); p.setFont(st::newGroupLinkFont); - p.drawTextLeft(st::boxPadding.left() + st::newGroupPadding.left() + st::defaultInputField.textMargins.left(), _link.y() - st::newGroupLinkPadding.top() + st::newGroupLinkTop, width(), lang(_link.isHidden() ? lng_create_group_invite_link : lng_create_group_link)); + p.drawTextLeft(st::boxPadding.left() + st::newGroupPadding.left() + st::defaultInputField.textMargins.left(), _link->y() - st::newGroupLinkPadding.top() + st::newGroupLinkTop, width(), lang(_link->isHidden() ? lng_create_group_invite_link : lng_create_group_link)); } - if (_link.isHidden()) { + if (_link->isHidden()) { if (!_channel->isMegagroup()) { QTextOption option(style::al_left); option.setWrapMode(QTextOption::WrapAnywhere); p.setFont(_linkOver ? st::boxTextFont->underline() : st::boxTextFont); - p.setPen(st::btnDefLink.color); + p.setPen(st::defaultLinkButton.color); p.drawText(_invitationLink, _channel->inviteLink(), option); - if (!_goodTextLink.isEmpty() && a_goodOpacity.current() > 0) { - p.setOpacity(a_goodOpacity.current()); - p.setPen(st::setGoodColor); - p.setFont(st::boxTextFont); - p.drawTextRight(st::boxPadding.right(), _link.y() - st::newGroupLinkPadding.top() + st::newGroupLinkTop + st::newGroupLinkFont->ascent - st::boxTextFont->ascent, width(), _goodTextLink); - p.setOpacity(1); - } } } else { if (!_errorText.isEmpty()) { - p.setPen(st::setErrColor); + p.setPen(st::boxTextFgError); p.setFont(st::boxTextFont); - p.drawTextRight(st::boxPadding.right(), _link.y() - st::newGroupLinkPadding.top() + st::newGroupLinkTop + st::newGroupLinkFont->ascent - st::boxTextFont->ascent, width(), _errorText); + p.drawTextRight(st::boxPadding.right(), _link->y() - st::newGroupLinkPadding.top() + st::newGroupLinkTop + st::newGroupLinkFont->ascent - st::boxTextFont->ascent, width(), _errorText); } else if (!_goodText.isEmpty()) { - p.setPen(st::setGoodColor); + p.setPen(st::boxTextFgGood); p.setFont(st::boxTextFont); - p.drawTextRight(st::boxPadding.right(), _link.y() - st::newGroupLinkPadding.top() + st::newGroupLinkTop + st::newGroupLinkFont->ascent - st::boxTextFont->ascent, width(), _goodText); + p.drawTextRight(st::boxPadding.right(), _link->y() - st::newGroupLinkPadding.top() + st::newGroupLinkTop + st::newGroupLinkFont->ascent - st::boxTextFont->ascent, width(), _goodText); } } } void SetupChannelBox::resizeEvent(QResizeEvent *e) { - _public.moveToLeft(st::boxPadding.left() + st::newGroupPadding.left(), st::boxPadding.top() + st::newGroupPadding.top()); - _private.moveToLeft(st::boxPadding.left() + st::newGroupPadding.left(), _public.y() + _public.height() + _aboutPublicHeight + st::newGroupSkip); + BoxContent::resizeEvent(e); - _link.resize(width() - st::boxPadding.left() - st::newGroupLinkPadding.left() - st::boxPadding.right(), _link.height()); - _link.moveToLeft(st::boxPadding.left() + st::newGroupLinkPadding.left(), _private.y() + _private.height() + _aboutPrivate.countHeight(_aboutPublicWidth) + st::newGroupSkip + st::newGroupPadding.bottom() + st::newGroupLinkPadding.top()); - _invitationLink = QRect(_link.x(), _link.y() + (_link.height() / 2) - st::boxTextFont->height, _link.width(), 2 * st::boxTextFont->height); + _public->moveToLeft(st::boxPadding.left() + st::newGroupPadding.left(), st::boxPadding.top() + st::newGroupPadding.top()); + _private->moveToLeft(st::boxPadding.left() + st::newGroupPadding.left(), _public->bottomNoMargins() + _aboutPublicHeight + st::newGroupSkip); - _save.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _save.height()); - _skip.moveToRight(st::boxButtonPadding.right() + _save.width() + st::boxButtonPadding.left(), _save.y()); - AbstractBox::resizeEvent(e); + _link->resize(width() - st::boxPadding.left() - st::newGroupLinkPadding.left() - st::boxPadding.right(), _link->height()); + _link->moveToLeft(st::boxPadding.left() + st::newGroupLinkPadding.left(), _private->bottomNoMargins() + _aboutPrivate.countHeight(_aboutPublicWidth) + st::newGroupSkip + st::newGroupPadding.bottom() + st::newGroupLinkPadding.top()); + _invitationLink = QRect(_link->x(), _link->y() + (_link->height() / 2) - st::boxTextFont->height, _link->width(), 2 * st::boxTextFont->height); } void SetupChannelBox::mouseMoveEvent(QMouseEvent *e) { @@ -727,9 +542,10 @@ void SetupChannelBox::mouseMoveEvent(QMouseEvent *e) { void SetupChannelBox::mousePressEvent(QMouseEvent *e) { if (_linkOver) { Application::clipboard()->setText(_channel->inviteLink()); - _goodTextLink = lang(lng_create_channel_link_copied); - a_goodOpacity = anim::fvalue(1, 0); - _a_goodFade.start(); + + Ui::Toast::Config toast; + toast.text = lang(lng_create_channel_link_copied); + Ui::Toast::Show(App::wnd(), toast); } } @@ -748,39 +564,28 @@ void SetupChannelBox::updateSelected(const QPoint &cursorGlobalPosition) { } } -void SetupChannelBox::step_goodFade(float64 ms, bool timer) { - float dt = ms / st::newGroupLinkFadeDuration; - if (dt >= 1) { - _a_goodFade.stop(); - a_goodOpacity.finish(); - } else { - a_goodOpacity.update(dt, anim::linear); - } - if (timer) update(); -} - -void SetupChannelBox::closePressed() { +void SetupChannelBox::closeHook() { if (!_existing) { - Ui::showLayer(new ContactsBox(_channel)); + Ui::show(Box(_channel)); } } void SetupChannelBox::onSave() { - if (!_public.checked()) { + if (!_public->checked()) { if (_existing) { _sentUsername = QString(); _saveRequestId = MTP::send(MTPchannels_UpdateUsername(_channel->inputChannel, MTP_string(_sentUsername)), rpcDone(&SetupChannelBox::onUpdateDone), rpcFail(&SetupChannelBox::onUpdateFail)); } else { - onClose(); + closeBox(); } } if (_saveRequestId) return; - QString link = _link.text().trimmed(); + QString link = _link->text().trimmed(); if (link.isEmpty()) { - _link.setFocus(); - _link.showError(); + _link->setFocus(); + _link->showError(); return; } @@ -789,7 +594,7 @@ void SetupChannelBox::onSave() { } void SetupChannelBox::onChange() { - QString name = _link.text().trimmed(); + QString name = _link->text().trimmed(); if (name.isEmpty()) { if (!_errorText.isEmpty() || !_goodText.isEmpty()) { _errorText = _goodText = QString(); @@ -829,7 +634,7 @@ void SetupChannelBox::onCheck() { if (_checkRequestId) { MTP::cancel(_checkRequestId); } - QString link = _link.text().trimmed(); + QString link = _link->text().trimmed(); if (link.size() >= MinUsernameLength) { _checkUsername = link; _checkRequestId = MTP::send(MTPchannels_CheckUsername(_channel->inputChannel, MTP_string(link)), rpcDone(&SetupChannelBox::onCheckDone), rpcFail(&SetupChannelBox::onCheckFail)); @@ -837,21 +642,21 @@ void SetupChannelBox::onCheck() { } void SetupChannelBox::onPrivacyChange() { - if (_public.checked()) { + if (_public->checked()) { if (_tooMuchUsernames) { - _private.setChecked(true); - Ui::showLayer(new RevokePublicLinkBox([this, weak_this = weakThis()]() { - if (!weak_this) return; + _private->setChecked(true); + Ui::show(Box(base::lambda_guarded(this, [this] { _tooMuchUsernames = false; - _public.setChecked(true); + _public->setChecked(true); onCheck(); - }), KeepOtherLayers); + })), KeepOtherLayers); return; } - _link.show(); - _link.setFocus(); + _link->show(); + _link->setDisplayFocused(true); + _link->setFocus(); } else { - _link.hide(); + _link->hide(); setFocus(); } if (_channel->isMegagroup()) { @@ -862,7 +667,7 @@ void SetupChannelBox::onPrivacyChange() { void SetupChannelBox::onUpdateDone(const MTPBool &result) { _channel->setName(textOneLine(_channel->name), _sentUsername); - onClose(); + closeBox(); } bool SetupChannelBox::onUpdateFail(const RPCError &error) { @@ -872,22 +677,22 @@ bool SetupChannelBox::onUpdateFail(const RPCError &error) { QString err(error.type()); if (err == "USERNAME_NOT_MODIFIED" || _sentUsername == _channel->username) { _channel->setName(textOneLine(_channel->name), textOneLine(_sentUsername)); - onClose(); + closeBox(); return true; } else if (err == "USERNAME_INVALID") { - _link.setFocus(); - _link.showError(); + _link->setFocus(); + _link->showError(); _errorText = lang(lng_create_channel_link_invalid); update(); return true; } else if (err == "USERNAME_OCCUPIED" || err == "USERNAMES_UNAVAILABLE") { - _link.setFocus(); - _link.showError(); + _link->setFocus(); + _link->showError(); _errorText = lang(lng_create_channel_link_occupied); update(); return true; } - _link.setFocus(); + _link->setFocus(); return true; } @@ -915,7 +720,7 @@ bool SetupChannelBox::onCheckFail(const RPCError &error) { showRevokePublicLinkBoxForEdit(); } else { _tooMuchUsernames = true; - _private.setChecked(true); + _private->setChecked(true); onPrivacyChange(); } return true; @@ -929,14 +734,14 @@ bool SetupChannelBox::onCheckFail(const RPCError &error) { return true; } _goodText = QString(); - _link.setFocus(); + _link->setFocus(); return true; } void SetupChannelBox::showRevokePublicLinkBoxForEdit() { - onClose(); - Ui::showLayer(new RevokePublicLinkBox([channel = _channel, existing = _existing]() { - Ui::showLayer(new SetupChannelBox(channel, existing), KeepOtherLayers); + closeBox(); + Ui::show(Box([channel = _channel, existing = _existing]() { + Ui::show(Box(channel, existing), KeepOtherLayers); }), KeepOtherLayers); } @@ -953,123 +758,101 @@ bool SetupChannelBox::onFirstCheckFail(const RPCError &error) { showRevokePublicLinkBoxForEdit(); } else { _tooMuchUsernames = true; - _private.setChecked(true); + _private->setChecked(true); onPrivacyChange(); } return true; } _goodText = QString(); - _link.setFocus(); + _link->setFocus(); return true; } -EditNameTitleBox::EditNameTitleBox(PeerData *peer) : -_peer(peer), -_save(this, lang(lng_settings_save), st::defaultBoxButton), -_cancel(this, lang(lng_cancel), st::cancelBoxButton), -_first(this, st::defaultInputField, lang(peer->isUser() ? lng_signup_firstname : lng_dlg_new_group_name), peer->isUser() ? peer->asUser()->firstName : peer->name), -_last(this, st::defaultInputField, lang(lng_signup_lastname), peer->isUser() ? peer->asUser()->lastName : QString()), -_invertOrder(!peer->isChat() && langFirstNameGoesSecond()), -_requestId(0) { - if (_invertOrder) { - setTabOrder(&_last, &_first); - } - _first.setMaxLength(MaxGroupChannelTitle); - _last.setMaxLength(MaxGroupChannelTitle); +EditNameTitleBox::EditNameTitleBox(QWidget*, PeerData *peer) +: _peer(peer) +, _first(this, st::defaultInputField, lang(peer->isUser() ? lng_signup_firstname : lng_dlg_new_group_name), peer->isUser() ? peer->asUser()->firstName : peer->name) +, _last(this, st::defaultInputField, lang(lng_signup_lastname), peer->isUser() ? peer->asUser()->lastName : QString()) +, _invertOrder(!peer->isChat() && langFirstNameGoesSecond()) { +} - int32 h = st::boxTitleHeight + st::contactPadding.top() + _first.height(); +void EditNameTitleBox::prepare() { + auto newHeight = st::contactPadding.top() + _first->height(); if (_peer->isUser()) { - _boxTitle = lang(_peer == App::self() ? lng_edit_self_title : lng_edit_contact_title); - h += st::contactSkip + _last.height(); + setTitle(lang(_peer == App::self() ? lng_edit_self_title : lng_edit_contact_title)); + newHeight += st::contactSkip + _last->height(); } else if (_peer->isChat()) { - _boxTitle = lang(lng_edit_group_title); + setTitle(lang(lng_edit_group_title)); } - h += st::boxPadding.bottom() + st::contactPadding.bottom() + st::boxButtonPadding.top() + _save.height() + st::boxButtonPadding.bottom(); - setMaxHeight(h); + newHeight += st::boxPadding.bottom() + st::contactPadding.bottom(); + setDimensions(st::boxWideWidth, newHeight); - connect(&_save, SIGNAL(clicked()), this, SLOT(onSave())); - connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); + addButton(lang(lng_settings_save), [this] { onSave(); }); + addButton(lang(lng_cancel), [this] { closeBox(); }); + if (_invertOrder) { + setTabOrder(_last, _first); + } + _first->setMaxLength(MaxGroupChannelTitle); + _last->setMaxLength(MaxGroupChannelTitle); - connect(&_first, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); - connect(&_last, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); - - prepare(); + connect(_first, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); + connect(_last, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); + _last->setVisible(!_peer->isChat()); } -void EditNameTitleBox::showAll() { - _first.show(); - if (_peer->isChat()) { - _last.hide(); - } else { - _last.show(); - } - _save.show(); - _cancel.show(); -} - -void EditNameTitleBox::doSetInnerFocus() { - (_invertOrder ? _last : _first).setFocus(); +void EditNameTitleBox::setInnerFocus() { + (_invertOrder ? _last : _first)->setFocusFast(); } void EditNameTitleBox::onSubmit() { - if (_first.hasFocus()) { + if (_first->hasFocus()) { if (_peer->isChat()) { - if (_first.getLastText().trimmed().isEmpty()) { - _first.setFocus(); - _first.showError(); + if (_first->getLastText().trimmed().isEmpty()) { + _first->setFocus(); + _first->showError(); } else { onSave(); } } else { - _last.setFocus(); + _last->setFocus(); } - } else if (_last.hasFocus()) { - if (_first.getLastText().trimmed().isEmpty()) { - _first.setFocus(); - _first.showError(); - } else if (_last.getLastText().trimmed().isEmpty()) { - _last.setFocus(); - _last.showError(); + } else if (_last->hasFocus()) { + if (_first->getLastText().trimmed().isEmpty()) { + _first->setFocus(); + _first->showError(); + } else if (_last->getLastText().trimmed().isEmpty()) { + _last->setFocus(); + _last->showError(); } else { onSave(); } } } -void EditNameTitleBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; - - paintTitle(p, _boxTitle); -} - void EditNameTitleBox::resizeEvent(QResizeEvent *e) { - _first.resize(width() - st::boxPadding.left() - st::newGroupInfoPadding.left() - st::boxPadding.right(), _first.height()); - _last.resize(_first.size()); - if (_invertOrder) { - _last.moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), st::boxTitleHeight + st::contactPadding.top()); - _first.moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _last.y() + _last.height() + st::contactSkip); - } else { - _first.moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), st::boxTitleHeight + st::contactPadding.top()); - _last.moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _first.y() + _first.height() + st::contactSkip); - } + BoxContent::resizeEvent(e); - _save.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _save.height()); - _cancel.moveToRight(st::boxButtonPadding.right() + _save.width() + st::boxButtonPadding.left(), _save.y()); - AbstractBox::resizeEvent(e); + _first->resize(width() - st::boxPadding.left() - st::newGroupInfoPadding.left() - st::boxPadding.right(), _first->height()); + _last->resize(_first->size()); + if (_invertOrder) { + _last->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), st::contactPadding.top()); + _first->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _last->y() + _last->height() + st::contactSkip); + } else { + _first->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), st::contactPadding.top()); + _last->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _first->y() + _first->height() + st::contactSkip); + } } void EditNameTitleBox::onSave() { if (_requestId) return; - QString first = prepareText(_first.getLastText()), last = prepareText(_last.getLastText()); + QString first = prepareText(_first->getLastText()), last = prepareText(_last->getLastText()); if (first.isEmpty() && last.isEmpty()) { if (_invertOrder) { - _last.setFocus(); - _last.showError(); + _last->setFocus(); + _last->showError(); } else { - _first.setFocus(); - _first.showError(); + _first->setFocus(); + _first->showError(); } return; } @@ -1088,28 +871,28 @@ void EditNameTitleBox::onSave() { void EditNameTitleBox::onSaveSelfDone(const MTPUser &user) { App::feedUsers(MTP_vector(1, user)); - onClose(); + closeBox(); } bool EditNameTitleBox::onSaveSelfFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; QString err(error.type()); - QString first = textOneLine(_first.getLastText().trimmed()), last = textOneLine(_last.getLastText().trimmed()); + QString first = textOneLine(_first->getLastText().trimmed()), last = textOneLine(_last->getLastText().trimmed()); if (err == "NAME_NOT_MODIFIED") { App::self()->setName(first, last, QString(), textOneLine(App::self()->username)); - onClose(); + closeBox(); return true; } else if (err == "FIRSTNAME_INVALID") { - _first.setFocus(); - _first.showError(); + _first->setFocus(); + _first->showError(); return true; } else if (err == "LASTNAME_INVALID") { - _last.setFocus(); - _last.showError(); + _last->setFocus(); + _last->showError(); return true; } - _first.setFocus(); + _first->setFocus(); return true; } @@ -1122,97 +905,72 @@ bool EditNameTitleBox::onSaveChatFail(const RPCError &error) { if (auto chatData = _peer->asChat()) { chatData->setName(_sentName); } - onClose(); + closeBox(); return true; } else if (err == qstr("NO_CHAT_TITLE")) { - _first.setFocus(); - _first.showError(); + _first->setFocus(); + _first->showError(); return true; } - _first.setFocus(); + _first->setFocus(); return true; } void EditNameTitleBox::onSaveChatDone(const MTPUpdates &updates) { App::main()->sentUpdatesReceived(updates); - onClose(); + closeBox(); } -EditChannelBox::EditChannelBox(ChannelData *channel) : AbstractBox() -, _channel(channel) -, _save(this, lang(lng_settings_save), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) +EditChannelBox::EditChannelBox(QWidget*, ChannelData *channel) +: _channel(channel) , _title(this, st::defaultInputField, lang(lng_dlg_new_channel_name), _channel->name) , _description(this, st::newGroupDescription, lang(lng_create_group_description), _channel->about()) , _sign(this, lang(lng_edit_sign_messages), channel->addsSignature(), st::defaultBoxCheckbox) -, _publicLink(this, lang(channel->isPublic() ? lng_profile_edit_public_link : lng_profile_create_public_link), st::defaultBoxLinkButton) -, _saveTitleRequestId(0) -, _saveDescriptionRequestId(0) -, _saveSignRequestId(0) { - connect(App::main(), SIGNAL(peerNameChanged(PeerData*,const PeerData::Names&,const PeerData::NameFirstChars&)), this, SLOT(peerUpdated(PeerData*))); +, _publicLink(this, lang(channel->isPublic() ? lng_profile_edit_public_link : lng_profile_create_public_link), st::boxLinkButton) { +} + +void EditChannelBox::prepare() { + setTitle(lang(_channel->isMegagroup() ? lng_edit_group : lng_edit_channel_title)); + + addButton(lang(lng_settings_save), [this] { onSave(); }); + addButton(lang(lng_cancel), [this] { closeBox(); }); + + connect(App::main(), SIGNAL(peerNameChanged(PeerData*, const PeerData::Names&, const PeerData::NameFirstChars&)), this, SLOT(peerUpdated(PeerData*))); setMouseTracking(true); - _title.setMaxLength(MaxGroupChannelTitle); - _description.setMaxLength(MaxChannelDescription); - _description.resize(width() - st::boxPadding.left() - st::newGroupInfoPadding.left() - st::boxPadding.right(), _description.height()); - myEnsureResized(&_description); + _title->setMaxLength(MaxGroupChannelTitle); + _description->setMaxLength(MaxChannelDescription); + + connect(_description, SIGNAL(resized()), this, SLOT(onDescriptionResized())); + connect(_description, SIGNAL(submitted(bool)), this, SLOT(onSave())); + connect(_description, SIGNAL(cancelled()), this, SLOT(onClose())); + + connect(_publicLink, SIGNAL(clicked()), this, SLOT(onPublicLink())); + _publicLink->setVisible(_channel->canEditUsername()); + _sign->setVisible(!_channel->isMegagroup()); updateMaxHeight(); - connect(&_description, SIGNAL(resized()), this, SLOT(onDescriptionResized())); - connect(&_description, SIGNAL(submitted(bool)), this, SLOT(onSave())); - connect(&_description, SIGNAL(cancelled()), this, SLOT(onClose())); - - connect(&_save, SIGNAL(clicked()), this, SLOT(onSave())); - connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); - - connect(&_publicLink, SIGNAL(clicked()), this, SLOT(onPublicLink())); - - prepare(); } -void EditChannelBox::showAll() { - _title.show(); - _description.show(); - _save.show(); - _cancel.show(); - if (_channel->canEditUsername()) { - _publicLink.show(); - } else { - _publicLink.hide(); - } - if (_channel->isMegagroup()) { - _sign.hide(); - } else { - _sign.show(); - } -} - -void EditChannelBox::doSetInnerFocus() { - _title.setFocus(); +void EditChannelBox::setInnerFocus() { + _title->setFocusFast(); } void EditChannelBox::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { - if (_title.hasFocus()) { + if (_title->hasFocus()) { onSave(); } } else { - AbstractBox::keyPressEvent(e); + BoxContent::keyPressEvent(e); } } -void EditChannelBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; - - paintTitle(p, lang(_channel->isMegagroup() ? lng_edit_group : lng_edit_channel_title)); -} - void EditChannelBox::peerUpdated(PeerData *peer) { if (peer == _channel) { - _publicLink.setText(lang(_channel->isPublic() ? lng_profile_edit_public_link : lng_profile_create_public_link)); - _sign.setChecked(_channel->addsSignature()); + _publicLink->setText(lang(_channel->isPublic() ? lng_profile_edit_public_link : lng_profile_create_public_link)); + _sign->setChecked(_channel->addsSignature()); } } @@ -1222,44 +980,43 @@ void EditChannelBox::onDescriptionResized() { } void EditChannelBox::updateMaxHeight() { - int32 h = st::boxTitleHeight + st::newGroupInfoPadding.top() + _title.height(); - h += st::newGroupDescriptionPadding.top() + _description.height() + st::newGroupDescriptionPadding.bottom(); + auto newHeight = st::newGroupInfoPadding.top() + _title->height(); + newHeight += st::newGroupDescriptionPadding.top() + _description->height() + st::newGroupDescriptionPadding.bottom(); if (!_channel->isMegagroup()) { - h += st::newGroupPublicLinkPadding.top() + _sign.height() + st::newGroupPublicLinkPadding.bottom(); + newHeight += st::newGroupPublicLinkPadding.top() + _sign->heightNoMargins() + st::newGroupPublicLinkPadding.bottom(); } if (_channel->canEditUsername()) { - h += st::newGroupPublicLinkPadding.top() + _publicLink.height() + st::newGroupPublicLinkPadding.bottom(); + newHeight += st::newGroupPublicLinkPadding.top() + _publicLink->height() + st::newGroupPublicLinkPadding.bottom(); } - h += st::boxPadding.bottom() + st::newGroupInfoPadding.bottom() + st::boxButtonPadding.top() + _save.height() + st::boxButtonPadding.bottom(); - setMaxHeight(h); + newHeight += st::boxPadding.bottom() + st::newGroupInfoPadding.bottom(); + setDimensions(st::boxWideWidth, newHeight); } void EditChannelBox::resizeEvent(QResizeEvent *e) { - _title.resize(width() - st::boxPadding.left() - st::newGroupInfoPadding.left() - st::boxPadding.right(), _title.height()); - _title.moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), st::boxTitleHeight + st::newGroupInfoPadding.top() + st::newGroupNamePosition.y()); + BoxContent::resizeEvent(e); - _description.moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _title.y() + _title.height() + st::newGroupDescriptionPadding.top()); + _title->resize(width() - st::boxPadding.left() - st::newGroupInfoPadding.left() - st::boxPadding.right(), _title->height()); + _title->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), st::newGroupInfoPadding.top() + st::newGroupNamePosition.y()); - _sign.moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _description.y() + _description.height() + st::newGroupDescriptionPadding.bottom() + st::newGroupPublicLinkPadding.top()); + _description->resize(width() - st::boxPadding.left() - st::newGroupInfoPadding.left() - st::boxPadding.right(), _description->height()); + _description->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _title->y() + _title->height() + st::newGroupDescriptionPadding.top()); + + _sign->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _description->y() + _description->height() + st::newGroupDescriptionPadding.bottom() + st::newGroupPublicLinkPadding.top()); if (_channel->isMegagroup()) { - _publicLink.moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _description.y() + _description.height() + st::newGroupDescriptionPadding.bottom() + st::newGroupPublicLinkPadding.top()); + _publicLink->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _description->y() + _description->height() + st::newGroupDescriptionPadding.bottom() + st::newGroupPublicLinkPadding.top()); } else { - _publicLink.moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _sign.y() + _sign.height() + st::newGroupDescriptionPadding.bottom() + st::newGroupPublicLinkPadding.top()); + _publicLink->moveToLeft(st::boxPadding.left() + st::newGroupInfoPadding.left(), _sign->bottomNoMargins() + st::newGroupDescriptionPadding.bottom() + st::newGroupPublicLinkPadding.top()); } - - _save.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _save.height()); - _cancel.moveToRight(st::boxButtonPadding.right() + _save.width() + st::boxButtonPadding.left(), _save.y()); - AbstractBox::resizeEvent(e); } void EditChannelBox::onSave() { if (_saveTitleRequestId || _saveDescriptionRequestId || _saveSignRequestId) return; - QString title = prepareText(_title.getLastText()), description = prepareText(_description.getLastText(), true); + QString title = prepareText(_title->getLastText()), description = prepareText(_description->getLastText(), true); if (title.isEmpty()) { - _title.setFocus(); - _title.showError(); + _title->setFocus(); + _title->showError(); return; } _sentTitle = title; @@ -1272,7 +1029,7 @@ void EditChannelBox::onSave() { } void EditChannelBox::onPublicLink() { - Ui::showLayer(new SetupChannelBox(_channel, true), KeepOtherLayers); + Ui::show(Box(_channel, true), KeepOtherLayers); } void EditChannelBox::saveDescription() { @@ -1284,10 +1041,10 @@ void EditChannelBox::saveDescription() { } void EditChannelBox::saveSign() { - if (_channel->isMegagroup() || _channel->addsSignature() == _sign.checked()) { - onClose(); + if (_channel->isMegagroup() || _channel->addsSignature() == _sign->checked()) { + closeBox(); } else { - _saveSignRequestId = MTP::send(MTPchannels_ToggleSignatures(_channel->inputChannel, MTP_bool(_sign.checked())), rpcDone(&EditChannelBox::onSaveSignDone), rpcFail(&EditChannelBox::onSaveFail)); + _saveSignRequestId = MTP::send(MTPchannels_ToggleSignatures(_channel->inputChannel, MTP_bool(_sign->checked())), rpcDone(&EditChannelBox::onSaveSignDone), rpcFail(&EditChannelBox::onSaveFail)); } } @@ -1302,11 +1059,11 @@ bool EditChannelBox::onSaveFail(const RPCError &error, mtpRequestId req) { saveDescription(); return true; } else if (err == qstr("NO_CHAT_TITLE")) { - _title.setFocus(); - _title.showError(); + _title->setFocus(); + _title->showError(); return true; } else { - _title.setFocus(); + _title->setFocus(); } } else if (req == _saveDescriptionRequestId) { _saveDescriptionRequestId = 0; @@ -1319,12 +1076,12 @@ bool EditChannelBox::onSaveFail(const RPCError &error, mtpRequestId req) { saveSign(); return true; } else { - _description.setFocus(); + _description->setFocus(); } } else if (req == _saveSignRequestId) { _saveSignRequestId = 0; if (err == qstr("CHAT_NOT_MODIFIED")) { - onClose(); + closeBox(); return true; } } @@ -1354,30 +1111,31 @@ void EditChannelBox::onSaveSignDone(const MTPUpdates &updates) { if (App::main()) { App::main()->sentUpdatesReceived(updates); } - onClose(); + closeBox(); } -RevokePublicLinkBox::RevokePublicLinkBox(base::lambda_unique &&revokeCallback) : AbstractBox() -, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) +RevokePublicLinkBox::RevokePublicLinkBox(QWidget*, base::lambda &&revokeCallback) +: _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _revokeWidth(st::normalFont->width(lang(lng_channels_too_much_public_revoke))) -, _aboutRevoke(this, lang(lng_channels_too_much_public_about), FlatLabel::InitType::Simple, st::aboutRevokePublicLabel) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) +, _aboutRevoke(this, lang(lng_channels_too_much_public_about), Ui::FlatLabel::InitType::Simple, st::aboutRevokePublicLabel) , _revokeCallback(std_::move(revokeCallback)) { +} + +void RevokePublicLinkBox::prepare() { setMouseTracking(true); MTP::send(MTPchannels_GetAdminedPublicChannels(), rpcDone(&RevokePublicLinkBox::getPublicDone), rpcFail(&RevokePublicLinkBox::getPublicFail)); - updateMaxHeight(); + addButton(lang(lng_cancel), [this] { closeBox(); }); - connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose())); subscribe(FileDownload::ImageLoaded(), [this] { update(); }); - prepare(); + updateMaxHeight(); } void RevokePublicLinkBox::updateMaxHeight() { _rowsTop = st::boxPadding.top() + _aboutRevoke->height() + st::boxPadding.top(); - setMaxHeight(_rowsTop + (5 * _rowHeight) + st::boxButtonPadding.top() + _cancel->height() + st::boxButtonPadding.bottom()); + setDimensions(st::boxWideWidth, _rowsTop + (5 * _rowHeight)); } void RevokePublicLinkBox::mouseMoveEvent(QMouseEvent *e) { @@ -1415,45 +1173,37 @@ void RevokePublicLinkBox::mouseReleaseEvent(QMouseEvent *e) { setCursor((_selected || _pressed) ? style::cur_pointer : style::cur_default); if (pressed && pressed == _selected) { auto text_method = pressed->isMegagroup() ? lng_channels_too_much_public_revoke_confirm_group : lng_channels_too_much_public_revoke_confirm_channel; - auto text = text_method(lt_link, qsl("telegram.me/") + pressed->userName(), lt_group, pressed->name); - weakRevokeConfirmBox = new ConfirmBox(text, lang(lng_channels_too_much_public_revoke)); - struct Data { - Data(QPointer &&weakThis, PeerData *pressed) : weakThis(std_::move(weakThis)), pressed(pressed) { - } - QPointer weakThis; - PeerData *pressed; - }; - weakRevokeConfirmBox->setConfirmedCallback([this, data = std_::make_unique(weakThis(), pressed)]() { - if (!data->weakThis) return; + auto text = text_method(lt_link, CreateInternalLink(pressed->userName()), lt_group, pressed->name); + auto confirmText = lang(lng_channels_too_much_public_revoke); + _weakRevokeConfirmBox = Ui::show(Box(text, confirmText, base::lambda_guarded(this, [this, pressed]() { if (_revokeRequestId) return; - _revokeRequestId = MTP::send(MTPchannels_UpdateUsername(data->pressed->asChannel()->inputChannel, MTP_string("")), rpcDone(&RevokePublicLinkBox::revokeLinkDone), rpcFail(&RevokePublicLinkBox::revokeLinkFail)); - }); - Ui::showLayer(weakRevokeConfirmBox, KeepOtherLayers); + _revokeRequestId = MTP::send(MTPchannels_UpdateUsername(pressed->asChannel()->inputChannel, MTP_string("")), rpcDone(&RevokePublicLinkBox::revokeLinkDone), rpcFail(&RevokePublicLinkBox::revokeLinkFail)); + })), KeepOtherLayers); } } void RevokePublicLinkBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; + BoxContent::paintEvent(e); + Painter p(this); p.translate(0, _rowsTop); for_const (auto &row, _rows) { - paintChat(p, row, (row.peer == _selected), (row.peer == _pressed)); + paintChat(p, row, (row.peer == _selected)); p.translate(0, _rowHeight); } } void RevokePublicLinkBox::resizeEvent(QResizeEvent *e) { + BoxContent::resizeEvent(e); + _aboutRevoke->moveToLeft(st::boxPadding.left(), st::boxPadding.top()); - _cancel->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _cancel->height()); - AbstractBox::resizeEvent(e); } -void RevokePublicLinkBox::paintChat(Painter &p, const ChatRow &row, bool selected, bool pressed) const { +void RevokePublicLinkBox::paintChat(Painter &p, const ChatRow &row, bool selected) const { auto peer = row.peer; - peer->paintUserpicLeft(p, st::contactsPhotoSize, st::contactsPadding.left(), st::contactsPadding.top(), width()); + peer->paintUserpicLeft(p, st::contactsPadding.left(), st::contactsPadding.top(), width(), st::contactsPhotoSize); - p.setPen(st::black); + p.setPen(st::contactsNameFg); int32 namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); int32 namew = width() - namex - st::contactsPadding.right() - (_revokeWidth + st::contactsCheckPosition.x() * 2); @@ -1465,28 +1215,25 @@ void RevokePublicLinkBox::paintChat(Painter &p, const ChatRow &row, bool selecte row.name.drawLeftElided(p, namex, st::contactsPadding.top() + st::contactsNameTop, namew, width()); p.setFont(selected ? st::linkOverFont : st::linkFont); - p.setPen(pressed ? st::btnDefLink.downColor : st::btnDefLink.color->p); + p.setPen(selected ? st::defaultLinkButton.overColor : st::defaultLinkButton.color); p.drawTextRight(st::contactsPadding.right() + st::contactsCheckPosition.x(), st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, width(), lang(lng_channels_too_much_public_revoke), _revokeWidth); p.setPen(st::contactsStatusFg); - textstyleSet(&st::revokePublicLinkStatusStyle); + p.setTextPalette(st::revokePublicLinkStatusPalette); row.status.drawLeftElided(p, namex, st::contactsPadding.top() + st::contactsStatusTop, namew, width()); - textstyleRestore(); + p.restoreTextPalette(); } void RevokePublicLinkBox::getPublicDone(const MTPmessages_Chats &result) { - if (result.type() == mtpc_messages_chats) { - auto &chats = result.c_messages_chats().vchats; - for_const (auto &chat, chats.c_vector().v) { + if (auto chats = Api::getChatsFromMessagesChats(result)) { + for_const (auto &chat, chats->c_vector().v) { if (auto peer = App::feedChat(chat)) { if (!peer->isChannel() || peer->userName().isEmpty()) continue; ChatRow row; row.peer = peer; - row.name.setText(st::contactsNameFont, peer->name, _textNameOptions); - textstyleSet(&st::revokePublicLinkStatusStyle); - row.status.setText(st::normalFont, qsl("telegram.me/") + textcmdLink(1, peer->userName()), _textDlgOptions); - textstyleRestore(); + row.name.setText(st::contactsNameStyle, peer->name, _textNameOptions); + row.status.setText(st::defaultTextStyle, CreateInternalLink(textcmdLink(1, peer->userName())), _textDlgOptions); _rows.push_back(std_::move(row)); } } @@ -1503,10 +1250,10 @@ bool RevokePublicLinkBox::getPublicFail(const RPCError &error) { } void RevokePublicLinkBox::revokeLinkDone(const MTPBool &result) { - if (weakRevokeConfirmBox) { - weakRevokeConfirmBox->onClose(); + if (_weakRevokeConfirmBox) { + _weakRevokeConfirmBox->closeBox(); } - onClose(); + closeBox(); if (_revokeCallback) { _revokeCallback(); } diff --git a/Telegram/SourceFiles/boxes/addcontactbox.h b/Telegram/SourceFiles/boxes/addcontactbox.h index 09a28dc5b..456dc417a 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.h +++ b/Telegram/SourceFiles/boxes/addcontactbox.h @@ -20,165 +20,143 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "abstractbox.h" -#include "core/lambda_wrap.h" +#include "boxes/abstractbox.h" #include "ui/filedialog.h" -class FlatLabel; class ConfirmBox; -class AddContactBox : public AbstractBox, public RPCSender { +namespace Ui { +class FlatLabel; +class InputField; +class PhoneInput; +class InputArea; +class UsernameInput; +class Checkbox; +class Radiobutton; +class LinkButton; +class NewAvatarButton; +} // namespace Ui + +class AddContactBox : public BoxContent, public RPCSender { Q_OBJECT public: - AddContactBox(QString fname = QString(), QString lname = QString(), QString phone = QString()); - AddContactBox(UserData *user); + AddContactBox(QWidget*, QString fname = QString(), QString lname = QString(), QString phone = QString()); + AddContactBox(QWidget*, UserData *user); -public slots: +protected: + void prepare() override; + + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + + void setInnerFocus() override; + +private slots: void onSubmit(); void onSave(); void onRetry(); -protected: - void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - - void showAll() override; - void doSetInnerFocus() override; - private: + void updateButtons(); void onImportDone(const MTPcontacts_ImportedContacts &res); void onSaveUserDone(const MTPcontacts_ImportedContacts &res); bool onSaveUserFail(const RPCError &e); - void initBox(); - UserData *_user = nullptr; - QString _boxTitle; - BoxButton _save, _cancel, _retry; - InputField _first, _last; - PhoneInput _phone; + object_ptr _first; + object_ptr _last; + object_ptr _phone; - bool _invertOrder; + bool _retrying = false; + bool _invertOrder = false; uint64 _contactId = 0; mtpRequestId _addRequest = 0; QString _sentName; + }; -class NewGroupBox : public AbstractBox { +class GroupInfoBox : public BoxContent, public RPCSender { Q_OBJECT public: - NewGroupBox(); - -public slots: - void onNext(); + GroupInfoBox(QWidget*, CreatingGroupType creating, bool fromTypeChoose); protected: - void keyPressEvent(QKeyEvent *e) override; - void paintEvent(QPaintEvent *e) override; + void prepare() override; + void setInnerFocus() override; + void resizeEvent(QResizeEvent *e) override; - void showAll() override; - -private: - Radiobutton _group, _channel; - int32 _aboutGroupWidth, _aboutGroupHeight; - Text _aboutGroup, _aboutChannel; - BoxButton _next, _cancel; - -}; - -class GroupInfoBox : public AbstractBox, public RPCSender { - Q_OBJECT - -public: - GroupInfoBox(CreatingGroupType creating, bool fromTypeChoose); - -public slots: - void onPhoto(); +private slots: void onPhotoReady(const QImage &img); void onNext(); void onNameSubmit(); void onDescriptionResized(); + void onClose() { + closeBox(); + } + +private: + void notifyFileQueryUpdated(const FileDialog::QueryUpdate &update); + + void creationDone(const MTPUpdates &updates); + bool creationFail(const RPCError &e); + void exportDone(const MTPExportedChatInvite &result); + + void updateMaxHeight(); + void updateSelected(const QPoint &cursorGlobalPosition); + + CreatingGroupType _creating; + bool _fromTypeChoose = false; + + object_ptr _photo; + object_ptr _title; + object_ptr _description = { nullptr }; + + QImage _photoImage; + + // channel creation + mtpRequestId _creationRequestId = 0; + ChannelData *_createdChannel = nullptr; + + FileDialog::QueryId _setPhotoFileQueryId = 0; + +}; + +class SetupChannelBox : public BoxContent, public RPCSender { + Q_OBJECT + +public: + SetupChannelBox(QWidget*, ChannelData *channel, bool existing = false); + + void setInnerFocus() override; + void closeHook() override; protected: + void prepare() override; + + void keyPressEvent(QKeyEvent *e) override; void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void leaveEvent(QEvent *e) override; - void showAll() override; - void doSetInnerFocus() override; - -private: - void notifyFileQueryUpdated(const FileDialog::QueryUpdate &update); - - void step_photoOver(float64 ms, bool timer); - - QRect photoRect() const; - - void updateMaxHeight(); - void updateSelected(const QPoint &cursorGlobalPosition); - CreatingGroupType _creating; - - anim::fvalue a_photoOver; - Animation _a_photoOver; - bool _photoOver; - - InputField _title; - InputArea _description; - - QImage _photoBig; - QPixmap _photoSmall; - BoxButton _next, _cancel; - - // channel creation - int32 _creationRequestId; - ChannelData *_createdChannel; - - FileDialog::QueryId _setPhotoFileQueryId = 0; - - void creationDone(const MTPUpdates &updates); - bool creationFail(const RPCError &e); - void exportDone(const MTPExportedChatInvite &result); - -}; - -class SetupChannelBox : public AbstractBox, public RPCSender { - Q_OBJECT - -public: - SetupChannelBox(ChannelData *channel, bool existing = false); - -public slots: +private slots: void onSave(); void onChange(); void onCheck(); void onPrivacyChange(); -protected: - void keyPressEvent(QKeyEvent *e) override; - void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void mousePressEvent(QMouseEvent *e) override; - void leaveEvent(QEvent *e) override; - - void closePressed() override; - void showAll() override; - void doSetInnerFocus() override; - private: void updateSelected(const QPoint &cursorGlobalPosition); - void step_goodFade(float64 ms, bool timer); void onUpdateDone(const MTPBool &result); bool onUpdateFail(const RPCError &error); @@ -191,47 +169,43 @@ private: void showRevokePublicLinkBoxForEdit(); - ChannelData *_channel; - bool _existing; + ChannelData *_channel = nullptr; + bool _existing = false; - Radiobutton _public, _private; + object_ptr _public; + object_ptr _private; int32 _aboutPublicWidth, _aboutPublicHeight; Text _aboutPublic, _aboutPrivate; - UsernameInput _link; - QRect _invitationLink; - bool _linkOver; - BoxButton _save, _skip; + object_ptr _link; + + QRect _invitationLink; + bool _linkOver = false; bool _tooMuchUsernames = false; mtpRequestId _saveRequestId = 0; mtpRequestId _checkRequestId = 0; QString _sentUsername, _checkUsername, _errorText, _goodText; - QString _goodTextLink; - anim::fvalue a_goodOpacity; - Animation _a_goodFade; - QTimer _checkTimer; }; -class EditNameTitleBox : public AbstractBox, public RPCSender { +class EditNameTitleBox : public BoxContent, public RPCSender { Q_OBJECT public: - EditNameTitleBox(PeerData *peer); - -public slots: - void onSave(); - void onSubmit(); + EditNameTitleBox(QWidget*, PeerData *peer); protected: - void paintEvent(QPaintEvent *e) override; + void setInnerFocus() override; + void prepare() override; + void resizeEvent(QResizeEvent *e) override; - void showAll() override; - void doSetInnerFocus() override; +private slots: + void onSave(); + void onSubmit(); private: void onSaveSelfDone(const MTPUser &user); @@ -241,38 +215,39 @@ private: bool onSaveChatFail(const RPCError &e); PeerData *_peer; - QString _boxTitle; - BoxButton _save, _cancel; - InputField _first, _last; + object_ptr _first; + object_ptr _last; - bool _invertOrder; + bool _invertOrder = false; - mtpRequestId _requestId; + mtpRequestId _requestId = 0; QString _sentName; }; -class EditChannelBox : public AbstractBox, public RPCSender { +class EditChannelBox : public BoxContent, public RPCSender { Q_OBJECT public: - EditChannelBox(ChannelData *channel); + EditChannelBox(QWidget*, ChannelData *channel); -public slots: +protected: + void prepare() override; + void setInnerFocus() override; + + void keyPressEvent(QKeyEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + +private slots: void peerUpdated(PeerData *peer); void onSave(); void onDescriptionResized(); void onPublicLink(); - -protected: - void keyPressEvent(QKeyEvent *e) override; - void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - - void showAll() override; - void doSetInnerFocus() override; + void onClose() { + closeBox(); + } private: void updateMaxHeight(); @@ -287,25 +262,27 @@ private: ChannelData *_channel; - BoxButton _save, _cancel; - InputField _title; - InputArea _description; - Checkbox _sign; + object_ptr _title; + object_ptr _description; + object_ptr _sign; - LinkButton _publicLink; + object_ptr _publicLink; + + mtpRequestId _saveTitleRequestId = 0; + mtpRequestId _saveDescriptionRequestId = 0; + mtpRequestId _saveSignRequestId = 0; - mtpRequestId _saveTitleRequestId, _saveDescriptionRequestId, _saveSignRequestId; QString _sentTitle, _sentDescription; }; -class RevokePublicLinkBox : public AbstractBox, public RPCSender { - Q_OBJECT - +class RevokePublicLinkBox : public BoxContent, public RPCSender { public: - RevokePublicLinkBox(base::lambda_unique &&revokeCallback); + RevokePublicLinkBox(QWidget*, base::lambda &&revokeCallback); protected: + void prepare() override; + void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; @@ -320,7 +297,7 @@ private: PeerData *peer; Text name, status; }; - void paintChat(Painter &p, const ChatRow &row, bool selected, bool pressed) const; + void paintChat(Painter &p, const ChatRow &row, bool selected) const; void getPublicDone(const MTPmessages_Chats &result); bool getPublicFail(const RPCError &error); @@ -337,11 +314,10 @@ private: int _rowHeight = 0; int _revokeWidth = 0; - ChildWidget _aboutRevoke; - ChildWidget _cancel; + object_ptr _aboutRevoke; - base::lambda_unique _revokeCallback; + base::lambda _revokeCallback; mtpRequestId _revokeRequestId = 0; - QPointer weakRevokeConfirmBox; + QPointer _weakRevokeConfirmBox; }; diff --git a/Telegram/SourceFiles/boxes/autolockbox.cpp b/Telegram/SourceFiles/boxes/autolockbox.cpp index 238941a3a..223c94423 100644 --- a/Telegram/SourceFiles/boxes/autolockbox.cpp +++ b/Telegram/SourceFiles/boxes/autolockbox.cpp @@ -19,56 +19,38 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" +#include "boxes/autolockbox.h" + #include "lang.h" - #include "localstorage.h" - -#include "autolockbox.h" -#include "confirmbox.h" +#include "boxes/confirmbox.h" #include "mainwidget.h" #include "mainwindow.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/buttons.h" +#include "styles/style_boxes.h" -AutoLockBox::AutoLockBox() : -_close(this, lang(lng_box_ok), st::defaultBoxButton) { +void AutoLockBox::prepare() { + setTitle(lang(lng_passcode_autolock)); - bool haveTestLang = (cLang() == languageTest); + addButton(lang(lng_box_ok), [this] { closeBox(); }); - int32 opts[] = { 60, 300, 3600, 18000 }, cnt = sizeof(opts) / sizeof(opts[0]); - - resizeMaxHeight(st::langsWidth, st::boxTitleHeight + cnt * (st::boxOptionListPadding.top() + st::langsButton.height) + st::boxOptionListPadding.bottom() + st::boxPadding.bottom() + st::boxButtonPadding.top() + _close.height() + st::boxButtonPadding.bottom()); - - int32 y = st::boxTitleHeight + st::boxOptionListPadding.top(); + int opts[] = { 60, 300, 3600, 18000 }, cnt = sizeof(opts) / sizeof(opts[0]); + auto y = st::boxOptionListPadding.top(); _options.reserve(cnt); - for (int32 i = 0; i < cnt; ++i) { - int32 v = opts[i]; - _options.push_back(new Radiobutton(this, qsl("autolock"), v, (v % 3600) ? lng_passcode_autolock_minutes(lt_count, v / 60) : lng_passcode_autolock_hours(lt_count, v / 3600), (Global::AutoLock() == v), st::langsButton)); + for (auto i = 0; i != cnt; ++i) { + auto v = opts[i]; + _options.push_back(new Ui::Radiobutton(this, qsl("autolock"), v, (v % 3600) ? lng_passcode_autolock_minutes(lt_count, v / 60) : lng_passcode_autolock_hours(lt_count, v / 3600), (Global::AutoLock() == v), st::langsButton)); _options.back()->move(st::boxPadding.left() + st::boxOptionListPadding.left(), y); - y += _options.back()->height() + st::boxOptionListPadding.top(); + y += _options.back()->heightNoMargins() + st::boxOptionListSkip; connect(_options.back(), SIGNAL(changed()), this, SLOT(onChange())); } - connect(&_close, SIGNAL(clicked()), this, SLOT(onClose())); - - _close.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _close.height()); - prepare(); -} - -void AutoLockBox::showAll() { - _close.show(); - for (int32 i = 0, l = _options.size(); i < l; ++i) { - _options[i]->show(); - } -} - -void AutoLockBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; - - paintTitle(p, lang(lng_passcode_autolock)); + setDimensions(st::langsWidth, st::boxOptionListPadding.top() + cnt * st::langsButton.height + (cnt - 1) * st::boxOptionListSkip + st::boxOptionListPadding.bottom() + st::boxPadding.bottom()); } void AutoLockBox::onChange() { - if (isHidden()) return; + if (!isBoxShown()) return; for (int32 i = 0, l = _options.size(); i < l; ++i) { int32 v = _options[i]->val(); @@ -79,5 +61,5 @@ void AutoLockBox::onChange() { } } App::wnd()->checkAutoLock(); - onClose(); + closeBox(); } diff --git a/Telegram/SourceFiles/boxes/autolockbox.h b/Telegram/SourceFiles/boxes/autolockbox.h index 075576ad0..767f23e48 100644 --- a/Telegram/SourceFiles/boxes/autolockbox.h +++ b/Telegram/SourceFiles/boxes/autolockbox.h @@ -20,24 +20,26 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "abstractbox.h" +#include "boxes/abstractbox.h" -class AutoLockBox : public AbstractBox { +namespace Ui { +class Radiobutton; +} // namespace Ui + +class AutoLockBox : public BoxContent { Q_OBJECT public: - AutoLockBox(); - -public slots: - void onChange(); + AutoLockBox(QWidget*) { + } protected: - void paintEvent(QPaintEvent *e) override; + void prepare() override; - void showAll() override; +private slots: + void onChange(); private: - QVector _options; - BoxButton _close; + QVector _options; }; diff --git a/Telegram/SourceFiles/boxes/backgroundbox.cpp b/Telegram/SourceFiles/boxes/backgroundbox.cpp index 181ff3d49..176d227bf 100644 --- a/Telegram/SourceFiles/boxes/backgroundbox.cpp +++ b/Telegram/SourceFiles/boxes/backgroundbox.cpp @@ -19,46 +19,44 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "lang.h" +#include "boxes/backgroundbox.h" -#include "backgroundbox.h" +#include "lang.h" #include "mainwidget.h" #include "mainwindow.h" -#include "window/chat_background.h" +#include "window/window_theme.h" #include "styles/style_overview.h" +#include "styles/style_boxes.h" +#include "ui/effects/round_checkbox.h" -BackgroundBox::BackgroundBox() : ItemListBox(st::backgroundScroll) -, _inner(this) { - init(_inner); - - connect(_inner, SIGNAL(backgroundChosen(int)), this, SLOT(onBackgroundChosen(int))); - - prepare(); +BackgroundBox::BackgroundBox(QWidget*) { } -void BackgroundBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; +void BackgroundBox::prepare() { + setTitle(lang(lng_backgrounds_header)); - paintTitle(p, lang(lng_backgrounds_header)); + addButton(lang(lng_close), [this] { closeBox(); }); + + setDimensions(st::boxWideWidth, st::boxMaxListHeight); + + _inner = setInnerWidget(object_ptr(this), st::backgroundScroll); + _inner->setBackgroundChosenCallback([this](int index) { backgroundChosen(index); }); } -void BackgroundBox::onBackgroundChosen(int index) { +void BackgroundBox::backgroundChosen(int index) { if (index >= 0 && index < App::cServerBackgrounds().size()) { - const App::WallPaper &paper(App::cServerBackgrounds().at(index)); + auto &paper = App::cServerBackgrounds()[index]; if (App::main()) App::main()->setChatBackground(paper); - using Update = Window::ChatBackgroundUpdate; - Window::chatBackground()->notify(Update(Update::Type::Start, !paper.id)); + using Update = Window::Theme::BackgroundUpdate; + Window::Theme::Background()->notify(Update(Update::Type::Start, !paper.id)); } - onClose(); + closeBox(); } -BackgroundBox::Inner::Inner(QWidget *parent) : ScrolledWidget(parent) -, _bgCount(0) -, _rows(0) -, _over(-1) -, _overDown(-1) { +BackgroundBox::Inner::Inner(QWidget *parent) : TWidget(parent) +, _check(std_::make_unique(st::overviewCheck, [this] { update(); })) { + _check->setChecked(true, Ui::RoundCheckbox::SetStyle::Fast); if (App::cServerBackgrounds().isEmpty()) { resize(BackgroundsInRow * (st::backgroundSize.width() + st::backgroundPadding) + st::backgroundPadding, 2 * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding); MTP::send(MTPaccount_GetWallPapers(), rpcDone(&Inner::gotWallpapers)); @@ -67,20 +65,25 @@ BackgroundBox::Inner::Inner(QWidget *parent) : ScrolledWidget(parent) } subscribe(FileDownload::ImageLoaded(), [this] { update(); }); + subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) { + if (update.paletteChanged()) { + _check->invalidateCache(); + } + }); setMouseTracking(true); } void BackgroundBox::Inner::gotWallpapers(const MTPVector &result) { App::WallPapers wallpapers; - wallpapers.push_back(App::WallPaper(0, ImagePtr(st::msgBG0), ImagePtr(st::msgBG0))); - const auto &v(result.c_vector().v); - for (int i = 0, l = v.size(); i < l; ++i) { - const auto &w(v.at(i)); + auto oldBackground = ImagePtr(qsl(":/gui/art/bg_initial.png")); + wallpapers.push_back(App::WallPaper(Window::Theme::kInitialBackground, oldBackground, oldBackground)); + auto &v = result.c_vector().v; + for_const (auto &w, v) { switch (w.type()) { case mtpc_wallPaper: { - const auto &d(w.c_wallPaper()); - const auto &sizes(d.vsizes.c_vector().v); + auto &d = w.c_wallPaper(); + auto &sizes = d.vsizes.c_vector().v; const MTPPhotoSize *thumb = 0, *full = 0; int32 thumbLevel = -1, fullLevel = -1; for (QVector::const_iterator j = sizes.cbegin(), e = sizes.cend(); j != e; ++j) { @@ -88,14 +91,14 @@ void BackgroundBox::Inner::gotWallpapers(const MTPVector &result) int32 w = 0, h = 0; switch (j->type()) { case mtpc_photoSize: { - const auto &s(j->c_photoSize().vtype.c_string().v); + auto &s = j->c_photoSize().vtype.c_string().v; if (s.size()) size = s[0]; w = j->c_photoSize().vw.v; h = j->c_photoSize().vh.v; } break; case mtpc_photoCachedSize: { - const auto &s(j->c_photoCachedSize().vtype.c_string().v); + auto &s = j->c_photoCachedSize().vtype.c_string().v; if (s.size()) size = s[0]; w = j->c_photoCachedSize().vw.v; h = j->c_photoCachedSize().vh.v; @@ -119,7 +122,7 @@ void BackgroundBox::Inner::gotWallpapers(const MTPVector &result) } break; case mtpc_wallPaperSolid: { - const auto &d(w.c_wallPaperSolid()); + auto &d = w.c_wallPaperSolid(); } break; } } @@ -161,16 +164,16 @@ void BackgroundBox::Inner::paintEvent(QPaintEvent *e) { const QPixmap &pix(paper.thumb->pix(st::backgroundSize.width(), st::backgroundSize.height())); p.drawPixmap(x, y, pix); - if (paper.id == Window::chatBackground()->id()) { - int checkPosX = x + st::backgroundSize.width() - st::overviewPhotoChecked.width(); - int checkPosY = y + st::backgroundSize.height() - st::overviewPhotoChecked.height(); - st::overviewPhotoChecked.paint(p, QPoint(checkPosX, checkPosY), width()); + if (paper.id == Window::Theme::Background()->id()) { + auto checkLeft = x + st::backgroundSize.width() - st::overviewCheckSkip - st::overviewCheck.size; + auto checkTop = y + st::backgroundSize.height() - st::overviewCheckSkip - st::overviewCheck.size; + _check->paint(p, getms(), checkLeft, checkTop, width()); } } } } else { - p.setFont(st::noContactsFont->f); - p.setPen(st::noContactsColor->p); + p.setFont(st::noContactsFont); + p.setPen(st::noContactsColor); p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center); } } @@ -197,11 +200,12 @@ void BackgroundBox::Inner::mousePressEvent(QMouseEvent *e) { void BackgroundBox::Inner::mouseReleaseEvent(QMouseEvent *e) { if (_overDown == _over && _over >= 0) { - emit backgroundChosen(_over); + if (_backgroundChosenCallback) { + _backgroundChosenCallback(_over); + } } else if (_over < 0) { setCursor(style::cur_default); } } -void BackgroundBox::Inner::resizeEvent(QResizeEvent *e) { -} +BackgroundBox::Inner::~Inner() = default; diff --git a/Telegram/SourceFiles/boxes/backgroundbox.h b/Telegram/SourceFiles/boxes/backgroundbox.h index 798593658..c68c26170 100644 --- a/Telegram/SourceFiles/boxes/backgroundbox.h +++ b/Telegram/SourceFiles/boxes/backgroundbox.h @@ -20,49 +20,54 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "abstractbox.h" -#include "core/lambda_wrap.h" +#include "boxes/abstractbox.h" -class BackgroundBox : public ItemListBox { - Q_OBJECT +namespace Ui { +class RoundCheckbox; +} // namespace Ui +class BackgroundBox : public BoxContent { public: - BackgroundBox(); - -public slots: - void onBackgroundChosen(int index); + BackgroundBox(QWidget*); protected: - void paintEvent(QPaintEvent *e) override; + void prepare() override; private: + void backgroundChosen(int index); + class Inner; - ChildWidget _inner; + QPointer _inner; }; // This class is hold in header because it requires Qt preprocessing. -class BackgroundBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber { - Q_OBJECT - +class BackgroundBox::Inner : public TWidget, public RPCSender, private base::Subscriber { public: Inner(QWidget *parent); -signals: - void backgroundChosen(int index); + void setBackgroundChosenCallback(base::lambda &&callback) { + _backgroundChosenCallback = std_::move(callback); + } + + ~Inner(); protected: void paintEvent(QPaintEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; - void resizeEvent(QResizeEvent *e) override; private: void gotWallpapers(const MTPVector &result); void updateWallpapers(); - int32 _bgCount, _rows; - int32 _over, _overDown; + base::lambda _backgroundChosenCallback; + + int _bgCount = 0; + int _rows = 0; + int _over = -1; + int _overDown = -1; + std_::unique_ptr _check; // this is not a widget }; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 018503827..d893201d5 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -19,27 +19,148 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ using "basic.style"; + using "ui/widgets/widgets.style"; +using "intro/intro.style"; -boxBlueTitleBg: #6393b5; -boxBlueTitleAdditionalFg: #dae9f5; -boxBlueTitleAdditionalSkip: 12px; -boxBlueTitlePosition: point(23px, 18px); -boxBlueTitleShadow: icon {{ "box_title_shadow", windowShadowFg }}; -boxBlueCloseFg: #c8e1f0; -boxBlueCloseOverFg: #ffffff; -boxBlueCloseIcon: icon {{ "box_button_close", boxBlueTitleBg }}; -boxBlueCloseDuration: 150; +boxDuration: 200; +boxRadius: 3px; -confirmInviteTitle: flatLabel(labelDefFlat) { - font: font(16px semibold); +boxButtonFont: font(boxFontSize semibold); +defaultBoxButton: RoundButton(defaultLightButton) { + width: -24px; + height: 36px; + font: boxButtonFont; +} + +boxTextStyle: TextStyle(defaultTextStyle) { + font: font(boxFontSize); + linkFont: font(boxFontSize); + linkFontOver: font(boxFontSize underline); +} + +boxLabelStyle: TextStyle(boxTextStyle) { + lineHeight: 22px; +} + +attentionBoxButton: RoundButton(defaultBoxButton) { + textFg: attentionButtonFg; + textFgOver: attentionButtonFgOver; + textBgOver: attentionButtonBgOver; + + ripple: RippleAnimation(defaultRippleAnimation) { + color: attentionButtonBgRipple; + } +} + +defaultBoxCheckbox: Checkbox(defaultCheckbox) { + width: -46px; + textPosition: point(34px, 1px); + style: boxTextStyle; +} + +boxRoundShadow: Shadow { + left: icon {{ "round_shadow_box_left", windowShadowFg }}; + topLeft: icon {{ "round_shadow_box_top_left", windowShadowFg }}; + top: icon {{ "round_shadow_box_top", windowShadowFg }}; + topRight: icon {{ "round_shadow_box_top_left-flip_horizontal", windowShadowFg }}; + right: icon {{ "round_shadow_box_left-flip_horizontal", windowShadowFg }}; + bottomRight: icon {{ "round_shadow_box_bottom_left-flip_horizontal", windowShadowFg }}; + bottom: icon {{ "round_shadow_box_bottom", windowShadowFg }}; + bottomLeft: icon {{ "round_shadow_box_bottom_left", windowShadowFg }}; + extend: margins(10px, 10px, 10px, 10px); + fallback: windowShadowFgFallback; +} + +boxTitleFont: font(17px semibold); +boxTitlePosition: point(23px, 20px); +boxTitleHeight: 56px; +boxLayerTitlePosition: point(23px, 16px); +boxLayerTitleHeight: 56px; +boxLayerTitleAdditionalSkip: 9px; +boxLayerTitleAdditionalFont: normalFont; +boxLayerTitleShadow: shadowFg; +boxLayerScroll: defaultSolidScroll; + +boxTopMargin: 6px; + +boxTitleClose: IconButton(defaultIconButton) { + width: boxTitleHeight; + height: boxTitleHeight; + + icon: boxTitleCloseIcon; + iconOver: boxTitleCloseIconOver; + + rippleAreaPosition: point(6px, 6px); + rippleAreaSize: 44px; + ripple: RippleAnimation(defaultRippleAnimation) { + color: windowBgOver; + } +} + +boxLinkButton: LinkButton(defaultLinkButton) { + font: boxTextFont; + overFont: font(boxFontSize underline); +} + +boxOptionListPadding: margins(0px, 0px, 0px, 0px); +boxOptionListSkip: 20px; +boxOptionInputSkip: 6px; + +boxVerticalMargin: 10px; +boxWidth: 320px; +boxWideWidth: 364px; +boxPadding: margins(23px, 30px, 23px, 8px); +boxMaxListHeight: 492px; +boxLittleSkip: 10px; +boxMediumSkip: 20px; + +boxButtonPadding: margins(8px, 12px, 13px, 12px); +boxLayerButtonPadding: margins(8px, 8px, 8px, 8px); +boxLabel: FlatLabel(defaultFlatLabel) { + width: 285px; + align: align(topleft); + style: boxLabelStyle; +} + +countryRowHeight: 36px; +countryRowNameFont: semiboldFont; +countryRowNameFg: boxTextFg; +countryRowPadding: margins(22px, 9px, 8px, 0px); +countryRowCodeFont: font(fsize); +countryRowBg: windowBg; +countryRowBgOver: windowBgOver; +countryRowCodeFg: windowSubTextFg; +countryRowCodeFgOver: windowSubTextFgOver; +countriesSkip: 12px; +countriesScroll: ScrollArea(boxLayerScroll) { + deltat: 9px; + deltab: 3px; +} + +boxPhotoTitleFont: font(16px semibold); +boxPhotoTitlePosition: point(28px, 20px); +boxPhotoPadding: margins(28px, 28px, 28px, 18px); +boxPhotoCompressedSkip: 20px; +boxPhotoCaptionSkip: 8px; +boxPhotoTextFg: windowSubTextFg; + +cropPointSize: 10px; +cropSkip: 13px; +cropMinSize: 20px; + +confirmInviteTitle: FlatLabel(defaultFlatLabel) { align: align(center); width: 320px; maxHeight: 24px; - textFg: #333333; + textFg: windowBoldFg; + style: TextStyle(defaultTextStyle) { + font: font(16px semibold); + linkFont: font(16px semibold); + linkFontOver: font(16px semibold underline); + } } -confirmInviteStatus: flatLabel(labelDefFlat) { - font: font(boxFontSize); +confirmInviteStatus: FlatLabel(boxLabel) { align: align(center); width: 320px; maxHeight: 20px; @@ -49,48 +170,78 @@ confirmInviteTitleTop: 106px; confirmInvitePhotoSize: 76px; confirmInvitePhotoTop: 20px; confirmInviteStatusTop: 136px; -confirmInviteUserHeight: 80px; +confirmInviteUserHeight: 84px; confirmInviteUserPhotoSize: 56px; confirmInviteUserPhotoTop: 166px; -confirmInviteUserName: flatLabel(labelDefFlat) { - font: normalFont; +confirmInviteUserName: FlatLabel(defaultFlatLabel) { align: align(center); width: 66px; maxHeight: 20px; } confirmInviteUserNameTop: 227px; -confirmPhoneAboutLabel: flatLabel(labelDefFlat) { +confirmPhoneAboutLabel: FlatLabel(defaultFlatLabel) { width: 282px; } confirmPhoneCodeField: InputField(defaultInputField) { } -revokePublicLinkStatusStyle: textStyle(defaultTextStyle) { +revokePublicLinkStatusPalette: TextPalette(defaultTextPalette) { linkFg: contactsStatusFgOnline; - linkFgDown: contactsStatusFgOnline; - linkFlagsOver: font(fsize); } -aboutRevokePublicLabel: flatLabel(labelDefFlat) { - font: normalFont; +aboutRevokePublicLabel: FlatLabel(defaultFlatLabel) { align: align(topleft); width: 320px; - textFg: windowTextFg; } -contactUserIcon: icon {{ "add_contact_user", #999999 }}; -contactPhoneIcon: icon {{ "add_contact_phone", #999999 }}; -contactIconTop: 10px; +contactUserIcon: icon {{ "add_contact_user", menuIconFg }}; +contactPhoneIcon: icon {{ "add_contact_phone", menuIconFg }}; +contactIconTop: 28px; -contactsNewItemHeight: 53px; -contactsNewItemIcon: icon {{ "contacts_add", #749fc2, point(29px, 19px) }}; -contactsNewItemTop: 18px; -contactsNewItemFg: #4b82af; +contactsAddIconAbove: icon {{ "contacts_add", activeButtonFg, point(18px, 18px) }}; +contactsAdd: TwoIconButton { + width: 52px; + height: 52px; + + iconBelow: contactsAddIconBelow; + iconBelowOver: contactsAddIconBelowOver; + iconAbove: contactsAddIconAbove; + iconAboveOver: contactsAddIconAbove; + + rippleAreaPosition: point(5px, 5px); + rippleAreaSize: 42px; + ripple: RippleAnimation(defaultRippleAnimation) { + color: activeButtonBgRipple; + } +} +contactsAddPosition: point(14px, 8px); + +contactPadding: margins(49px, 2px, 0px, 12px); +contactSkip: 6px; +contactPhoneSkip: 30px; + +contactsPhotoSize: 42px; +contactsPadding: margins(16px, 7px, 16px, 7px); +contactsNameTop: 2px; +contactsNameStyle: TextStyle(defaultTextStyle) { + font: semiboldFont; + linkFont: semiboldFont; + linkFontOver: semiboldFont; +} +contactsStatusTop: 23px; +contactsStatusFont: font(fsize); +contactsCheckPosition: point(8px, 16px); +contactsAllAdminsTop: 15px; +contactsAboutBg: windowBgOver; +contactsAboutFg: windowSubTextFgOver; +contactsAboutTop: 60px; +contactsAboutBottom: 19px; contactsMultiSelect: MultiSelect { - padding: margins(8px, 8px, 8px, 8px); + bg: boxSearchBg; + padding: margins(8px, 6px, 8px, 6px); maxHeight: 104px; - scroll: flatScroll(solidScroll) { + scroll: ScrollArea(defaultSolidScroll) { deltat: 3px; deltab: 3px; round: 1px; @@ -103,14 +254,18 @@ contactsMultiSelect: MultiSelect { padding: margins(6px, 7px, 12px, 0px); maxWidth: 128px; height: 32px; - font: normalFont; + style: defaultTextStyle; textBg: contactsBgOver; - textFg: windowTextFg; - textActiveBg: windowActiveBg; - textActiveFg: white; - deleteFg: white; - deleteLeft: 10px; - deleteStroke: 2px; + textFg: windowFg; + textActiveBg: activeButtonBg; + textActiveFg: activeButtonFg; + deleteFg: activeButtonFg; + deleteCross: CrossAnimation { + size: 32px; + skip: 10px; + stroke: 2px; + minScale: 0.3; + } duration: 150; minScale: 0.3; } @@ -120,51 +275,65 @@ contactsMultiSelect: MultiSelect { textBg: transparent; textMargins: margins(2px, 7px, 2px, 0px); - placeholderFg: #999; - placeholderFgActive: #aaa; + placeholderFg: placeholderFg; + placeholderFgActive: placeholderFgActive; + placeholderFgError: placeholderFgActive; placeholderMargins: margins(2px, 0px, 2px, 0px); + placeholderScale: 0.; + placeholderFont: normalFont; border: 0px; borderActive: 0px; - borderError: 0px; - height: 32px; + heightMin: 32px; font: normalFont; } fieldMinWidth: 42px; - fieldIcon: fieldSearchIcon; + fieldIcon: boxFieldSearchIcon; fieldIconSkip: 36px; - fieldCancel: IconButton { - width: 41px; - height: 48px; + fieldCancel: CrossButton { + width: 44px; + height: 44px; - opacity: 0.3; - overOpacity: 0.4; - - icon: icon {{ "box_search_cancel", #000000 }}; - iconPosition: point(8px, 18px); - downIconPosition: point(8px, 19px); + cross: CrossAnimation { + size: 36px; + skip: 12px; + stroke: 2px; + minScale: 0.3; + } + crossFg: boxTitleCloseFg; + crossFgOver: boxTitleCloseFgOver; + crossPosition: point(4px, 4px); duration: 150; + ripple: RippleAnimation(defaultRippleAnimation) { + color: windowBgOver; + } } - fieldCancelSkip: 34px; + fieldCancelSkip: 40px; } contactsPhotoCheckbox: RoundImageCheckbox { imageRadius: 21px; imageSmallRadius: 18px; selectWidth: 2px; - selectFg: windowActiveBg; + selectFg: windowBgActive; selectDuration: 150; - checkBorder: windowBg; - checkBg: windowActiveBg; - checkRadius: 10px; - checkSmallRadius: 3px; - checkIcon: icon {{ "default_checkbox_check", windowBg, point(3px, 6px) }}; + check: RoundCheckbox(defaultRoundCheckbox) { + size: 20px; + sizeSmall: 0.3; + check: icon {{ "default_checkbox_check", windowFgActive, point(3px, 6px) }}; + } } -contactsPhotoDisabledCheckFg: #bbbbbb; -contactsNameCheckedFg: #2b88b8; +contactsPhotoDisabledCheckFg: menuIconFg; +contactsNameCheckedFg: windowActiveTextFg; +contactsRipple: defaultRippleAnimation; + +contactsMarginTop: 4px; +contactsMarginBottom: 4px; +membersMarginTop: 10px; +membersMarginBottom: 10px; localStorageBoxSkip: 10px; @@ -175,20 +344,23 @@ sharePhotoCheckbox: RoundImageCheckbox(contactsPhotoCheckbox) { imageRadius: 28px; imageSmallRadius: 24px; } -shareNameFont: font(11px); -shareNameFg: windowTextFg; -shareNameActiveFg: btnYesColor; +shareNameStyle: TextStyle(defaultTextStyle) { + font: font(11px); + linkFont: font(11px); + linkFontOver: font(11px); +} +shareNameFg: windowFg; +shareNameActiveFg: windowActiveTextFg; shareNameTop: 6px; shareColumnSkip: 6px; shareActivateDuration: 150; shareScrollDuration: 300; -notificationsBoxHeight: 450px; +notificationsBoxHeight: 420px; notificationsBoxMonitorTop: 63px; -notificationsBoxMonitor: icon {{ "monitor", #000000 }}; +notificationsBoxMonitor: icon {{ "monitor", notificationsBoxMonitorFg }}; notificationsBoxScreenTop: 10px; notificationsBoxScreenSize: size(280px, 160px); -notificationsBoxScreenBg: titleBg; notificationsBoxCountLabelTop: 80px; notificationsBoxCountTop: 30px; @@ -199,7 +371,162 @@ notificationsSampleMargin: 2px; notificationSampleOpacity: 0.5; notificationSampleSize: size(64px, 16px); -notificationSampleUserpicFg: #40ace3; -notificationSampleCloseFg: #d7d7d7; -notificationSampleTextFg: #d7d7d7; -notificationSampleNameFg: #939393; + +membersAboutLimitPadding: margins(0px, 12px, 0px, 12px); + +sessionsScroll: boxLayerScroll; +sessionsHeight: 350px; +sessionHeight: 70px; +sessionCurrentPadding: margins(0px, 7px, 0px, 4px); +sessionCurrentHeight: 118px; +sessionPadding: margins(21px, 10px, 21px, 0px); +sessionNameFont: msgNameFont; +sessionNameFg: boxTextFg; +sessionWhenFont: msgDateFont; +sessionWhenFg: windowSubTextFg; +sessionInfoFont: msgFont; +sessionInfoFg: windowSubTextFg; +sessionTerminateTop: 28px; +sessionTerminateSkip: 22px; +sessionTerminate: IconButton { + width: 20px; + height: 20px; + + icon: smallCloseIcon; + iconOver: smallCloseIconOver; + iconPosition: point(5px, 5px); + + rippleAreaPosition: point(0px, 0px); + rippleAreaSize: 20px; + ripple: RippleAnimation(defaultRippleAnimation) { + color: windowBgOver; + } +} +sessionTerminateAllButton: LinkButton(boxLinkButton) { + color: attentionButtonFg; + overColor: attentionButtonFg; +} + +passcodeHeaderFont: font(19px); +passcodeHeaderHeight: 80px; +passcodeInput: InputField(introPhone) { + textMargins: margins(1px, 27px, 1px, 6px); +} +passcodeSubmit: RoundButton(introNextButton) { + width: 225px; +} +passcodeSubmitSkip: 40px; +passcodePadding: margins(0px, 0px, 0px, 5px); +passcodeTextLine: 28px; +passcodeLittleSkip: 5px; +passcodeAboutSkip: 7px; +passcodeSkip: 20px; + +newGroupAboutFg: windowSubTextFg; +newGroupPadding: margins(4px, 6px, 4px, 3px); +newGroupSkip: 27px; +newGroupInfoPadding: margins(0px, -4px, 0px, 1px); + +newGroupLinkPadding: margins(4px, 27px, 4px, 21px); +newGroupLinkTop: 3px; +newGroupLinkFont: font(16px); + +newGroupPhotoSize: 76px; +newGroupPhotoIcon: icon {{ "new_chat_photo", activeButtonFg }}; +newGroupPhotoIconPosition: point(23px, 25px); +newGroupPhotoDuration: 150; + +newGroupNamePosition: point(27px, 5px); + +newGroupDescriptionPadding: margins(0px, 13px, 0px, 4px); +newGroupDescription: InputField(defaultInputField) { + textMargins: margins(1px, 26px, 1px, 4px); + heightMax: 135px; +} + +setupChannelLink: InputField(defaultInputField) { + textMargins: margins(0px, 6px, 0px, 4px); + heightMin: 32px; +} + +newGroupPublicLinkPadding: margins(0px, 20px, 0px, 5px); + +themeWarningWidth: boxWideWidth; +themeWarningHeight: 150px; +themeWarningTextTop: 60px; + +aboutWidth: 390px; +aboutVersionTop: -3px; +aboutVersionLink: LinkButton(defaultLinkButton) { + color: windowSubTextFg; + overColor: windowSubTextFg; +} +aboutTextTop: 34px; +aboutSkip: 14px; +aboutLabel: FlatLabel(defaultFlatLabel) { + width: 330px; + align: align(topleft); + style: TextStyle(defaultTextStyle) { + lineHeight: 22px; + } +} + +autoDownloadTopDelta: 10px; +autoDownloadTitlePosition: point(23px, 18px); +autoDownloadTitleFont: font(15px semibold); + +editTextArea: InputField(defaultInputField) { + textMargins: margins(1px, 26px, 1px, 4px); + heightMax: 276px; +} + +confirmCaptionArea: InputField(defaultInputField) { + textMargins: margins(1px, 26px, 1px, 4px); + heightMax: 78px; +} +confirmBg: windowBgOver; +confirmMaxHeight: 245px; +confirmCompressedSkip: 10px; + +connectionHostInputField: InputField(defaultInputField) { + width: 160px; +} +connectionPortInputField: InputField(defaultInputField) { + width: 55px; +} +connectionUserInputField: InputField(defaultInputField) { + width: 95px; +} +connectionPasswordInputField: InputField(defaultInputField) { + width: 120px; +} +connectionIPv6Skip: 11px; + +langsWidth: 256px; +langsButton: Checkbox(defaultBoxCheckbox) { + width: 200px; +} + +backgroundPadding: 10px; +backgroundSize: size(108px, 193px); +backgroundScroll: ScrollArea(boxLayerScroll) { + deltax: 3px; + width: 10px; + deltat: 10px; + deltab: 10px; +} + +passcodeTextStyle: TextStyle(defaultTextStyle) { + lineHeight: 20px; +} + +usernamePadding: margins(23px, 6px, 21px, 12px); +usernameSkip: 49px; +usernameTextStyle: TextStyle(passcodeTextStyle) { + font: boxTextFont; + linkFont: boxTextFont; + linkFontOver: font(boxFontSize underline); +} +usernameDefaultFg: windowSubTextFg; + +downloadPathSkip: 10px; diff --git a/Telegram/SourceFiles/boxes/confirmbox.cpp b/Telegram/SourceFiles/boxes/confirmbox.cpp index 25265a48a..d001e814e 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.cpp +++ b/Telegram/SourceFiles/boxes/confirmbox.cpp @@ -21,13 +21,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "boxes/confirmbox.h" +#include "styles/style_boxes.h" #include "lang.h" #include "mainwidget.h" #include "mainwindow.h" #include "apiwrap.h" #include "application.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/labels.h" +#include "ui/toast/toast.h" #include "core/click_handler_types.h" -#include "styles/style_boxes.h" #include "localstorage.h" TextParseOptions _confirmBoxTextOptions = { @@ -37,56 +41,109 @@ TextParseOptions _confirmBoxTextOptions = { Qt::LayoutDirectionAuto, // dir }; -ConfirmBox::ConfirmBox(const QString &text, const QString &doneText, const style::RoundButton &doneStyle, const QString &cancelText, const style::RoundButton &cancelStyle) : AbstractBox(st::boxWidth) -, _informative(false) -, _text(100) -, _confirm(this, doneText.isEmpty() ? lang(lng_box_ok) : doneText, doneStyle) -, _cancel(this, cancelText.isEmpty() ? lang(lng_cancel) : cancelText, cancelStyle) { +ConfirmBox::ConfirmBox(QWidget*, const QString &text, base::lambda &&confirmedCallback, base::lambda &&cancelledCallback) +: _confirmText(lang(lng_box_ok)) +, _cancelText(lang(lng_cancel)) +, _confirmStyle(st::defaultBoxButton) +, _text(st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()) +, _confirmedCallback(std_::move(confirmedCallback)) +, _cancelledCallback(std_::move(cancelledCallback)) { init(text); } -ConfirmBox::ConfirmBox(const QString &text, const QString &doneText, const style::RoundButton &doneStyle, bool informative) : AbstractBox(st::boxWidth) -, _informative(true) -, _text(100) -, _confirm(this, doneText.isEmpty() ? lang(lng_box_ok) : doneText, doneStyle) -, _cancel(this, QString(), st::cancelBoxButton) { +ConfirmBox::ConfirmBox(QWidget*, const QString &text, const QString &confirmText, base::lambda &&confirmedCallback, base::lambda &&cancelledCallback) +: _confirmText(confirmText) +, _cancelText(lang(lng_cancel)) +, _confirmStyle(st::defaultBoxButton) +, _text(st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()) +, _confirmedCallback(std_::move(confirmedCallback)) +, _cancelledCallback(std_::move(cancelledCallback)) { init(text); } +ConfirmBox::ConfirmBox(QWidget*, const QString &text, const QString &confirmText, const style::RoundButton &confirmStyle, base::lambda &&confirmedCallback, base::lambda &&cancelledCallback) +: _confirmText(confirmText) +, _cancelText(lang(lng_cancel)) +, _confirmStyle(confirmStyle) +, _text(st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()) +, _confirmedCallback(std_::move(confirmedCallback)) +, _cancelledCallback(std_::move(cancelledCallback)) { + init(text); +} + +ConfirmBox::ConfirmBox(QWidget*, const QString &text, const QString &confirmText, const QString &cancelText, base::lambda &&confirmedCallback, base::lambda &&cancelledCallback) +: _confirmText(confirmText) +, _cancelText(cancelText) +, _confirmStyle(st::defaultBoxButton) +, _text(st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()) +, _confirmedCallback(std_::move(confirmedCallback)) +, _cancelledCallback(std_::move(cancelledCallback)) { + init(text); +} + +ConfirmBox::ConfirmBox(QWidget*, const QString &text, const QString &confirmText, const style::RoundButton &confirmStyle, const QString &cancelText, base::lambda &&confirmedCallback, base::lambda &&cancelledCallback) +: _confirmText(confirmText) +, _cancelText(cancelText) +, _confirmStyle(st::defaultBoxButton) +, _text(st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()) +, _confirmedCallback(std_::move(confirmedCallback)) +, _cancelledCallback(std_::move(cancelledCallback)) { + init(text); +} + +ConfirmBox::ConfirmBox(const InformBoxTag &, const QString &text, const QString &doneText, base::lambda_copy &&closedCallback) +: _confirmText(doneText) +, _confirmStyle(st::defaultBoxButton) +, _informative(true) +, _text(st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()) +, _confirmedCallback(generateInformCallback(closedCallback)) +, _cancelledCallback(generateInformCallback(closedCallback)) { + init(text); +} + +base::lambda ConfirmBox::generateInformCallback(const base::lambda_copy &closedCallback) { + auto callback = closedCallback; + return base::lambda_guarded(this, [this, callback] { + closeBox(); + if (callback) { + callback(); + } + }); +} + void ConfirmBox::init(const QString &text) { - _text.setText(st::boxTextFont, text, _informative ? _confirmBoxTextOptions : _textPlainOptions); - - connect(&_confirm, SIGNAL(clicked()), this, SLOT(onConfirmPressed())); - connect(&_cancel, SIGNAL(clicked()), this, SLOT(onCancel())); - if (_informative) { - _cancel.hide(); - connect(this, SIGNAL(confirmed()), this, SLOT(onCancel())); - } - onTextUpdated(); - - prepare(); + _text.setText(st::boxLabelStyle, text, _informative ? _confirmBoxTextOptions : _textPlainOptions); } -void ConfirmBox::onConfirmPressed() { - if (_confirmedCallback) { - _confirmedCallback(); +void ConfirmBox::prepare() { + addButton(_confirmText, [this] { confirmed(); }, _confirmStyle); + if (!_informative) { + addButton(_cancelText, [this] { _cancelled = true; closeBox(); }); } - emit confirmed(); + textUpdated(); } -void ConfirmBox::onTextUpdated() { - textstyleSet(&st::boxTextStyle); +void ConfirmBox::textUpdated() { _textWidth = st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right(); - _textHeight = qMin(_text.countHeight(_textWidth), 16 * int(st::boxTextStyle.lineHeight)); - setMaxHeight(st::boxPadding.top() + _textHeight + st::boxPadding.bottom() + st::boxButtonPadding.top() + _confirm.height() + st::boxButtonPadding.bottom()); - textstyleRestore(); + _textHeight = qMin(_text.countHeight(_textWidth), 16 * st::boxLabelStyle.lineHeight); + setDimensions(st::boxWidth, st::boxPadding.top() + _textHeight + st::boxPadding.bottom()); setMouseTracking(_text.hasLinks()); } -void ConfirmBox::onCancel() { - emit cancelPressed(); - onClose(); +void ConfirmBox::closeHook() { + if (!_confirmed && (!_strictCancel || _cancelled) && _cancelledCallback) { + _cancelledCallback(); + } +} + +void ConfirmBox::confirmed() { + if (!_confirmed) { + _confirmed = true; + if (_confirmedCallback) { + _confirmedCallback(); + } + } } void ConfirmBox::mouseMoveEvent(QMouseEvent *e) { @@ -98,7 +155,7 @@ void ConfirmBox::mousePressEvent(QMouseEvent *e) { _lastMousePos = e->globalPos(); updateHover(); ClickHandler::pressed(); - return LayerWidget::mousePressEvent(e); + return BoxContent::mousePressEvent(e); } void ConfirmBox::mouseReleaseEvent(QMouseEvent *e) { @@ -108,6 +165,7 @@ void ConfirmBox::mouseReleaseEvent(QMouseEvent *e) { Ui::hideLayer(); App::activateClickHandler(activated, e->button()); } + return BoxContent::mouseReleaseEvent(e); } void ConfirmBox::leaveEvent(QEvent *e) { @@ -131,99 +189,48 @@ void ConfirmBox::updateLink() { void ConfirmBox::updateHover() { QPoint m(mapFromGlobal(_lastMousePos)); - textstyleSet(&st::boxTextStyle); auto state = _text.getStateLeft(m.x() - st::boxPadding.left(), m.y() - st::boxPadding.top(), _textWidth, width()); - textstyleRestore(); ClickHandler::setActive(state.link, this); } -void ConfirmBox::closePressed() { - emit cancelled(); -} - -void ConfirmBox::showAll() { - if (_informative) { - _confirm.show(); - } else { - _confirm.show(); - _cancel.show(); - } -} - void ConfirmBox::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { - onConfirmPressed(); + confirmed(); } else { - AbstractBox::keyPressEvent(e); + BoxContent::keyPressEvent(e); } } void ConfirmBox::paintEvent(QPaintEvent *e) { - QPainter p(this); - if (paint(p)) return; + BoxContent::paintEvent(e); + + Painter p(this); // draw box title / text - p.setPen(st::black->p); - textstyleSet(&st::boxTextStyle); + p.setPen(st::boxTextFg); _text.drawLeftElided(p, st::boxPadding.left(), st::boxPadding.top(), _textWidth, width(), 16, style::al_left); - textstyleRestore(); } -void ConfirmBox::resizeEvent(QResizeEvent *e) { - _confirm.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _confirm.height()); - _cancel.moveToRight(st::boxButtonPadding.right() + _confirm.width() + st::boxButtonPadding.left(), _confirm.y()); - AbstractBox::resizeEvent(e); +InformBox::InformBox(QWidget*, const QString &text, base::lambda_copy &&closedCallback) : ConfirmBox(ConfirmBox::InformBoxTag(), text, lang(lng_box_ok), std_::move(closedCallback)) { } -SharePhoneConfirmBox::SharePhoneConfirmBox(PeerData *recipient) -: ConfirmBox(lang(lng_bot_share_phone), lang(lng_bot_share_phone_confirm)) -, _recipient(recipient) { - connect(this, SIGNAL(confirmed()), this, SLOT(onConfirm())); +InformBox::InformBox(QWidget*, const QString &text, const QString &doneText, base::lambda_copy &&closedCallback) : ConfirmBox(ConfirmBox::InformBoxTag(), text, doneText, std_::move(closedCallback)) { } -void SharePhoneConfirmBox::onConfirm() { - emit confirmed(_recipient); +MaxInviteBox::MaxInviteBox(QWidget*, const QString &link) +: _text(st::boxLabelStyle, lng_participant_invite_sorry(lt_count, Global::ChatSizeMax()), _confirmBoxTextOptions, st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()) +, _link(link) { } -ConfirmLinkBox::ConfirmLinkBox(const QString &url) : ConfirmBox(lang(lng_open_this_link) + qsl("\n\n") + url, lang(lng_open_link)) -, _url(url) { - connect(this, SIGNAL(confirmed()), this, SLOT(onOpenLink())); -} - -void ConfirmLinkBox::onOpenLink() { - Ui::hideLayer(); - UrlClickHandler::doOpen(_url); -} - -ConfirmBotGameBox::ConfirmBotGameBox(UserData *bot, const QString &url) : ConfirmBox(lng_allow_bot_pass(lt_bot_name, bot->name), lang(lng_allow_bot)) -, _bot(bot) -, _url(url) { - connect(this, SIGNAL(confirmed()), this, SLOT(onOpenLink())); -} - -void ConfirmBotGameBox::onOpenLink() { - Ui::hideLayer(); - Local::makeBotTrusted(_bot); - UrlClickHandler::doOpen(_url); -} - -MaxInviteBox::MaxInviteBox(const QString &link) : AbstractBox(st::boxWidth) -, _close(this, lang(lng_box_ok), st::defaultBoxButton) -, _text(st::boxTextFont, lng_participant_invite_sorry(lt_count, Global::ChatSizeMax()), _confirmBoxTextOptions, st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()) -, _link(link) -, _linkOver(false) -, a_goodOpacity(0, 0) -, _a_good(animation(this, &MaxInviteBox::step_good)) { +void MaxInviteBox::prepare() { setMouseTracking(true); + addButton(lang(lng_box_ok), [this] { closeBox(); }); + _textWidth = st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right(); - _textHeight = qMin(_text.countHeight(_textWidth), 16 * int(st::boxTextStyle.lineHeight)); - setMaxHeight(st::boxPadding.top() + _textHeight + st::boxTextFont->height + st::boxTextFont->height * 2 + st::newGroupLinkPadding.bottom() + st::boxButtonPadding.top() + _close.height() + st::boxButtonPadding.bottom()); - - connect(&_close, SIGNAL(clicked()), this, SLOT(onClose())); - - prepare(); + _textHeight = qMin(_text.countHeight(_textWidth), 16 * st::boxLabelStyle.lineHeight); + setDimensions(st::boxWidth, st::boxPadding.top() + _textHeight + st::boxTextFont->height + st::boxTextFont->height * 2 + st::newGroupLinkPadding.bottom()); } void MaxInviteBox::mouseMoveEvent(QMouseEvent *e) { @@ -234,9 +241,10 @@ void MaxInviteBox::mousePressEvent(QMouseEvent *e) { mouseMoveEvent(e); if (_linkOver) { Application::clipboard()->setText(_link); - _goodTextLink = lang(lng_create_channel_link_copied); - a_goodOpacity = anim::fvalue(1, 0); - _a_good.start(); + + Ui::Toast::Config toast; + toast.text = lang(lng_create_channel_link_copied); + Ui::Toast::Show(App::wnd(), toast); } } @@ -255,76 +263,53 @@ void MaxInviteBox::updateSelected(const QPoint &cursorGlobalPosition) { } } -void MaxInviteBox::step_good(float64 ms, bool timer) { - float dt = ms / st::newGroupLinkFadeDuration; - if (dt >= 1) { - _a_good.stop(); - a_goodOpacity.finish(); - } else { - a_goodOpacity.update(dt, anim::linear); - } - if (timer) update(); -} - -void MaxInviteBox::showAll() { - _close.show(); -} - void MaxInviteBox::paintEvent(QPaintEvent *e) { + BoxContent::paintEvent(e); + Painter p(this); - if (paint(p)) return; // draw box title / text - p.setPen(st::black->p); + p.setPen(st::boxTextFg); _text.drawLeftElided(p, st::boxPadding.left(), st::boxPadding.top(), _textWidth, width(), 16, style::al_left); QTextOption option(style::al_left); option.setWrapMode(QTextOption::WrapAnywhere); p.setFont(_linkOver ? st::defaultInputField.font->underline() : st::defaultInputField.font); - p.setPen(st::btnDefLink.color); + p.setPen(st::defaultLinkButton.color); p.drawText(_invitationLink, _link, option); - if (!_goodTextLink.isEmpty() && a_goodOpacity.current() > 0) { - p.setOpacity(a_goodOpacity.current()); - p.setPen(st::setGoodColor); - p.setFont(st::boxTextFont); - p.drawTextLeft(st::boxPadding.left(), height() - st::boxButtonPadding.bottom() - _close.height() + st::defaultBoxButton.textTop + st::defaultBoxButton.font->ascent - st::boxTextFont->ascent, width(), _goodTextLink); - p.setOpacity(1); - } } void MaxInviteBox::resizeEvent(QResizeEvent *e) { - _close.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _close.height()); + BoxContent::resizeEvent(e); _invitationLink = myrtlrect(st::boxPadding.left(), st::boxPadding.top() + _textHeight + st::boxTextFont->height, width() - st::boxPadding.left() - st::boxPadding.right(), 2 * st::boxTextFont->height); - AbstractBox::resizeEvent(e); } -ConvertToSupergroupBox::ConvertToSupergroupBox(ChatData *chat) : AbstractBox(st::boxWideWidth) -, _chat(chat) +ConvertToSupergroupBox::ConvertToSupergroupBox(QWidget*, ChatData *chat) +: _chat(chat) , _text(100) -, _note(100) -, _convert(this, lang(lng_profile_convert_confirm), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) { +, _note(100) { +} + +void ConvertToSupergroupBox::prepare() { QStringList text; text.push_back(lang(lng_profile_convert_feature1)); text.push_back(lang(lng_profile_convert_feature2)); text.push_back(lang(lng_profile_convert_feature3)); text.push_back(lang(lng_profile_convert_feature4)); - textstyleSet(&st::boxTextStyle); - _text.setText(st::boxTextFont, text.join('\n'), _confirmBoxTextOptions); - _note.setText(st::boxTextFont, lng_profile_convert_warning(lt_bold_start, textcmdStartSemibold(), lt_bold_end, textcmdStopSemibold()), _confirmBoxTextOptions); + setTitle(lang(lng_profile_convert_title)); + + addButton(lang(lng_profile_convert_confirm), [this] { convertToSupergroup(); }); + addButton(lang(lng_cancel), [this] { closeBox(); }); + + _text.setText(st::boxLabelStyle, text.join('\n'), _confirmBoxTextOptions); + _note.setText(st::boxLabelStyle, lng_profile_convert_warning(lt_bold_start, textcmdStartSemibold(), lt_bold_end, textcmdStopSemibold()), _confirmBoxTextOptions); _textWidth = st::boxWideWidth - st::boxPadding.left() - st::boxButtonPadding.right(); _textHeight = _text.countHeight(_textWidth); - setMaxHeight(st::boxTitleHeight + _textHeight + st::boxPadding.bottom() + _note.countHeight(_textWidth) + st::boxButtonPadding.top() + _convert.height() + st::boxButtonPadding.bottom()); - textstyleRestore(); - - connect(&_convert, SIGNAL(clicked()), this, SLOT(onConvert())); - connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); - - prepare(); + setDimensions(st::boxWideWidth, _textHeight + st::boxPadding.bottom() + _note.countHeight(_textWidth)); } -void ConvertToSupergroupBox::onConvert() { +void ConvertToSupergroupBox::convertToSupergroup() { MTP::send(MTPmessages_MigrateChat(_chat->inputChat), rpcDone(&ConvertToSupergroupBox::convertDone), rpcFail(&ConvertToSupergroupBox::convertFail)); } @@ -359,78 +344,63 @@ bool ConvertToSupergroupBox::convertFail(const RPCError &error) { return true; } -void ConvertToSupergroupBox::showAll() { - _convert.show(); - _cancel.show(); -} - void ConvertToSupergroupBox::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { - onConvert(); + convertToSupergroup(); } else { - AbstractBox::keyPressEvent(e); + BoxContent::keyPressEvent(e); } } void ConvertToSupergroupBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; + BoxContent::paintEvent(e); - paintTitle(p, lang(lng_profile_convert_title)); + Painter p(this); // draw box title / text - p.setPen(st::black); - textstyleSet(&st::boxTextStyle); - _text.drawLeft(p, st::boxPadding.left(), st::boxTitleHeight, _textWidth, width()); - _note.drawLeft(p, st::boxPadding.left(), st::boxTitleHeight + _textHeight + st::boxPadding.bottom(), _textWidth, width()); - textstyleRestore(); + p.setPen(st::boxTextFg); + _text.drawLeft(p, st::boxPadding.left(), 0, _textWidth, width()); + _note.drawLeft(p, st::boxPadding.left(), _textHeight + st::boxPadding.bottom(), _textWidth, width()); } -void ConvertToSupergroupBox::resizeEvent(QResizeEvent *e) { - _convert.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _convert.height()); - _cancel.moveToRight(st::boxButtonPadding.right() + _convert.width() + st::boxButtonPadding.left(), _convert.y()); - AbstractBox::resizeEvent(e); -} - -PinMessageBox::PinMessageBox(ChannelData *channel, MsgId msgId) : AbstractBox(st::boxWidth) -, _channel(channel) +PinMessageBox::PinMessageBox(QWidget*, ChannelData *channel, MsgId msgId) +: _channel(channel) , _msgId(msgId) -, _text(this, lang(lng_pinned_pin_sure), FlatLabel::InitType::Simple, st::boxLabel) -, _notify(this, lang(lng_pinned_notify), true, st::defaultBoxCheckbox) -, _pin(this, lang(lng_pinned_pin), st::defaultBoxButton) -, _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()); +, _text(this, lang(lng_pinned_pin_sure), Ui::FlatLabel::InitType::Simple, st::boxLabel) +, _notify(this, lang(lng_pinned_notify), true, st::defaultBoxCheckbox) { +} - connect(&_pin, SIGNAL(clicked()), this, SLOT(onPin())); - connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); +void PinMessageBox::prepare() { + addButton(lang(lng_pinned_pin), [this] { pinMessage(); }); + addButton(lang(lng_cancel), [this] { closeBox(); }); + + setDimensions(st::boxWidth, st::boxPadding.top() + _text->height() + st::boxMediumSkip + _notify->heightNoMargins() + st::boxPadding.bottom()); } void PinMessageBox::resizeEvent(QResizeEvent *e) { - _text.moveToLeft(st::boxPadding.left(), st::boxPadding.top()); - _notify.moveToLeft(st::boxPadding.left(), _text.y() + _text.height() + st::boxMediumSkip); - _pin.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _pin.height()); - _cancel.moveToRight(st::boxButtonPadding.right() + _pin.width() + st::boxButtonPadding.left(), _pin.y()); - AbstractBox::resizeEvent(e); + BoxContent::resizeEvent(e); + _text->moveToLeft(st::boxPadding.left(), st::boxPadding.top()); + _notify->moveToLeft(st::boxPadding.left(), _text->y() + _text->height() + st::boxMediumSkip); } -void PinMessageBox::onPin() { +void PinMessageBox::keyPressEvent(QKeyEvent *e) { + if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { + pinMessage(); + } else { + BoxContent::keyPressEvent(e); + } +} + +void PinMessageBox::pinMessage() { if (_requestId) return; MTPchannels_UpdatePinnedMessage::Flags flags = 0; - if (!_notify.checked()) { + if (!_notify->checked()) { flags |= MTPchannels_UpdatePinnedMessage::Flag::f_silent; } _requestId = MTP::send(MTPchannels_UpdatePinnedMessage(MTP_flags(flags), _channel->inputChannel, MTP_int(_msgId)), rpcDone(&PinMessageBox::pinDone), rpcFail(&PinMessageBox::pinFail)); } -void PinMessageBox::showAll() { - _text.show(); - _notify.show(); - _pin.show(); - _cancel.show(); -} - void PinMessageBox::pinDone(const MTPUpdates &updates) { if (App::main()) { App::main()->sentUpdatesReceived(updates); @@ -444,94 +414,151 @@ bool PinMessageBox::pinFail(const RPCError &error) { return true; } -RichDeleteMessageBox::RichDeleteMessageBox(ChannelData *channel, UserData *from, MsgId msgId) : AbstractBox(st::boxWidth) -, _channel(channel) -, _from(from) -, _msgId(msgId) -, _text(this, lang(lng_selected_delete_sure_this), FlatLabel::InitType::Simple, st::boxLabel) -, _banUser(this, lang(lng_ban_user), false, st::defaultBoxCheckbox) -, _reportSpam(this, lang(lng_report_spam), false, st::defaultBoxCheckbox) -, _deleteAll(this, lang(lng_delete_all_from), false, st::defaultBoxCheckbox) -, _delete(this, lang(lng_box_delete), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) { - t_assert(_channel != nullptr); - - _text.resizeToWidth(st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()); - setMaxHeight(st::boxPadding.top() + _text.height() + st::boxMediumSkip + _banUser.height() + st::boxLittleSkip + _reportSpam.height() + st::boxLittleSkip + _deleteAll.height() + st::boxPadding.bottom() + st::boxButtonPadding.top() + _delete.height() + st::boxButtonPadding.bottom()); - - connect(&_delete, SIGNAL(clicked()), this, SLOT(onDelete())); - connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); +DeleteMessagesBox::DeleteMessagesBox(QWidget*, HistoryItem *item, bool suggestModerateActions) : _singleItem(true) { + _ids.push_back(item->fullId()); + if (suggestModerateActions && item->suggestBanReportDeleteAll()) { + _moderateFrom = item->from()->asUser(); + _moderateInChannel = item->history()->peer->asChannel(); + } } -void RichDeleteMessageBox::resizeEvent(QResizeEvent *e) { - _text.moveToLeft(st::boxPadding.left(), st::boxPadding.top()); - _banUser.moveToLeft(st::boxPadding.left(), _text.y() + _text.height() + st::boxMediumSkip); - _reportSpam.moveToLeft(st::boxPadding.left(), _banUser.y() + _banUser.height() + st::boxLittleSkip); - _deleteAll.moveToLeft(st::boxPadding.left(), _reportSpam.y() + _reportSpam.height() + st::boxLittleSkip); - _delete.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _delete.height()); - _cancel.moveToRight(st::boxButtonPadding.right() + _delete.width() + st::boxButtonPadding.left(), _delete.y()); - AbstractBox::resizeEvent(e); +DeleteMessagesBox::DeleteMessagesBox(QWidget*, const SelectedItemSet &selected) { + auto count = selected.size(); + t_assert(count > 0); + _ids.reserve(count); + for_const (auto item, selected) { + _ids.push_back(item->fullId()); + } } -void RichDeleteMessageBox::onDelete() { - if (_banUser.checked()) { - MTP::send(MTPchannels_KickFromChannel(_channel->inputChannel, _from->inputUser, MTP_boolTrue()), App::main()->rpcDone(&MainWidget::sentUpdatesReceived)); - } - if (_reportSpam.checked()) { - MTP::send(MTPchannels_ReportSpam(_channel->inputChannel, _from->inputUser, MTP_vector(1, MTP_int(_msgId)))); - } - if (_deleteAll.checked()) { - App::main()->deleteAllFromUser(_channel, _from); - } - if (HistoryItem *item = App::histItemById(_channel ? peerToChannel(_channel->id) : 0, _msgId)) { - bool wasLast = (item->history()->lastMsg == item); - item->destroy(); - - if (_msgId > 0) { - App::main()->deleteMessages(_channel, QVector(1, MTP_int(_msgId))); - } else if (wasLast) { - App::main()->checkPeerHistory(_channel); +void DeleteMessagesBox::prepare() { + auto text = QString(); + if (_moderateFrom) { + t_assert(_moderateInChannel != nullptr); + text = lang(lng_selected_delete_sure_this); + _banUser.create(this, lang(lng_ban_user), false, st::defaultBoxCheckbox); + _reportSpam.create(this, lang(lng_report_spam), false, st::defaultBoxCheckbox); + _deleteAll.create(this, lang(lng_delete_all_from), false, st::defaultBoxCheckbox); + } else { + text = _singleItem ? lang(lng_selected_delete_sure_this) : lng_selected_delete_sure(lt_count, _ids.size()); + auto canDeleteAllForEveryone = true; + auto now = ::date(unixtime()); + auto deleteForUser = (UserData*)nullptr; + auto peer = (PeerData*)nullptr; + auto forEveryoneText = lang(lng_delete_for_everyone_check); + for_const (auto fullId, _ids) { + if (auto item = App::histItemById(fullId)) { + peer = item->history()->peer; + if (!item->canDeleteForEveryone(now)) { + canDeleteAllForEveryone = false; + break; + } else if (auto user = item->history()->peer->asUser()) { + if (!deleteForUser || deleteForUser == user) { + deleteForUser = user; + forEveryoneText = lng_delete_for_other_check(lt_user, user->firstName); + } else { + forEveryoneText = lang(lng_delete_for_everyone_check); + } + } + } else { + canDeleteAllForEveryone = false; + } + } + auto count = qMax(1, _ids.size()); + if (canDeleteAllForEveryone) { + _forEveryone.create(this, forEveryoneText, false, st::defaultBoxCheckbox); + } else if (peer && peer->isChannel()) { + if (peer->isMegagroup()) { + text += qsl("\n\n") + lng_delete_for_everyone_hint(lt_count, count); + } + } else if (peer->isChat()) { + text += qsl("\n\n") + lng_delete_for_me_chat_hint(lt_count, count); + } else if (!peer->isSelf()) { + text += qsl("\n\n") + lng_delete_for_me_hint(lt_count, count); } } - Ui::hideLayer(); + _text.create(this, text, Ui::FlatLabel::InitType::Simple, st::boxLabel); + + addButton(lang(lng_box_delete), [this] { deleteAndClear(); }); + addButton(lang(lng_cancel), [this] { closeBox(); }); + + auto fullHeight = st::boxPadding.top() + _text->height() + st::boxPadding.bottom(); + if (_moderateFrom) { + fullHeight += st::boxMediumSkip + _banUser->heightNoMargins() + st::boxLittleSkip + _reportSpam->heightNoMargins() + st::boxLittleSkip + _deleteAll->heightNoMargins(); + } else if (_forEveryone) { + fullHeight += st::boxMediumSkip + _forEveryone->heightNoMargins(); + } + setDimensions(st::boxWidth, fullHeight); } -void RichDeleteMessageBox::showAll() { - _text.show(); - _banUser.show(); - _reportSpam.show(); - _deleteAll.show(); - _delete.show(); - _cancel.show(); -} - -KickMemberBox::KickMemberBox(PeerData *chat, UserData *member) -: ConfirmBox(lng_profile_sure_kick(lt_user, member->firstName), lang(lng_box_remove)) -, _chat(chat) -, _member(member) { - connect(this, SIGNAL(confirmed()), this, SLOT(onConfirm())); -} - -void KickMemberBox::onConfirm() { - Ui::hideLayer(); - if (auto chat = _chat->asChat()) { - App::main()->kickParticipant(chat, _member); - } else if (auto channel = _chat->asChannel()) { - App::api()->kickParticipant(channel, _member); +void DeleteMessagesBox::resizeEvent(QResizeEvent *e) { + BoxContent::resizeEvent(e); + _text->moveToLeft(st::boxPadding.left(), st::boxPadding.top()); + if (_moderateFrom) { + _banUser->moveToLeft(st::boxPadding.left(), _text->bottomNoMargins() + st::boxMediumSkip); + _reportSpam->moveToLeft(st::boxPadding.left(), _banUser->bottomNoMargins() + st::boxLittleSkip); + _deleteAll->moveToLeft(st::boxPadding.left(), _reportSpam->bottomNoMargins() + st::boxLittleSkip); + } else if (_forEveryone) { + _forEveryone->moveToLeft(st::boxPadding.left(), _text->bottomNoMargins() + st::boxMediumSkip); } } -ConfirmInviteBox::ConfirmInviteBox(const QString &title, const MTPChatPhoto &photo, int count, const QVector &participants) : AbstractBox() -, _title(this, st::confirmInviteTitle) +void DeleteMessagesBox::keyPressEvent(QKeyEvent *e) { + if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { + deleteAndClear(); + } else { + BoxContent::keyPressEvent(e); + } +} + +void DeleteMessagesBox::deleteAndClear() { + if (!App::main()) { + return; + } + + if (_moderateFrom) { + if (_banUser->checked()) { + MTP::send(MTPchannels_KickFromChannel(_moderateInChannel->inputChannel, _moderateFrom->inputUser, MTP_boolTrue()), App::main()->rpcDone(&MainWidget::sentUpdatesReceived)); + } + if (_reportSpam->checked()) { + MTP::send(MTPchannels_ReportSpam(_moderateInChannel->inputChannel, _moderateFrom->inputUser, MTP_vector(1, MTP_int(_ids[0].msg)))); + } + if (_deleteAll->checked()) { + App::main()->deleteAllFromUser(_moderateInChannel, _moderateFrom); + } + } + + if (!_singleItem) { + App::main()->clearSelectedItems(); + } + + QMap> idsByPeer; + for_const (auto fullId, _ids) { + if (auto item = App::histItemById(fullId)) { + auto history = item->history(); + auto wasOnServer = (item->id > 0); + auto wasLast = (history->lastMsg == item); + item->destroy(); + + if (wasOnServer) { + idsByPeer[history->peer].push_back(MTP_int(fullId.msg)); + } else if (wasLast) { + App::main()->checkPeerHistory(history->peer); + } + } + } + + auto forEveryone = _forEveryone ? _forEveryone->checked() : false; + for (auto i = idsByPeer.cbegin(), e = idsByPeer.cend(); i != e; ++i) { + App::main()->deleteMessages(i.key(), i.value(), forEveryone); + } + Ui::hideLayer(); +} + +ConfirmInviteBox::ConfirmInviteBox(QWidget*, const QString &title, const MTPChatPhoto &photo, int count, const QVector &participants) +: _title(this, st::confirmInviteTitle) , _status(this, st::confirmInviteStatus) -, _photo(chatDefPhoto(0)) -, _participants(participants) -, _join(this, lang(lng_group_invite_join), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) { - if (_participants.size() > 4) { - _participants.resize(4); - } - +, _participants(participants) { _title->setText(title); QString status; if (_participants.isEmpty() || _participants.size() >= count) { @@ -551,52 +578,64 @@ ConfirmInviteBox::ConfirmInviteBox(const QString &title, const MTPChatPhoto &pho } } } + if (!_photo) { + _photoEmpty.set(0, title); + } +} - int h = st::confirmInviteStatusTop + _status->height() + st::boxPadding.bottom() + st::boxButtonPadding.top() + _join->height() + st::boxButtonPadding.bottom(); +void ConfirmInviteBox::prepare() { + addButton(lang(lng_group_invite_join), [this] { + if (auto main = App::main()) { + main->onInviteImport(); + } + }); + addButton(lang(lng_cancel), [this] { closeBox(); }); + + if (_participants.size() > 4) { + _participants.resize(4); + } + + auto newHeight = st::confirmInviteStatusTop + _status->height() + st::boxPadding.bottom(); if (!_participants.isEmpty()) { - int skip = (width() - 4 * st::confirmInviteUserPhotoSize) / 5; + int skip = (st::boxWideWidth - 4 * st::confirmInviteUserPhotoSize) / 5; int padding = skip / 2; _userWidth = (st::confirmInviteUserPhotoSize + 2 * padding); int sumWidth = _participants.size() * _userWidth; - int left = (width() - sumWidth) / 2; + int left = (st::boxWideWidth - sumWidth) / 2; for_const (auto user, _participants) { - auto name = new FlatLabel(this, st::confirmInviteUserName); + auto name = new Ui::FlatLabel(this, st::confirmInviteUserName); name->resizeToWidth(st::confirmInviteUserPhotoSize + padding); name->setText(user->firstName.isEmpty() ? App::peerName(user) : user->firstName); name->moveToLeft(left + (padding / 2), st::confirmInviteUserNameTop); left += _userWidth; } - h += st::confirmInviteUserHeight; + newHeight += st::confirmInviteUserHeight; } - setMaxHeight(h); - - connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose())); - connect(_join, SIGNAL(clicked()), App::main(), SLOT(onInviteImport())); + setDimensions(st::boxWideWidth, newHeight); } void ConfirmInviteBox::resizeEvent(QResizeEvent *e) { + BoxContent::resizeEvent(e); _title->move((width() - _title->width()) / 2, st::confirmInviteTitleTop); _status->move((width() - _status->width()) / 2, st::confirmInviteStatusTop); - _join->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _join->height()); - _cancel->moveToRight(st::boxButtonPadding.right() + _join->width() + st::boxButtonPadding.left(), _join->y()); - AbstractBox::resizeEvent(e); } void ConfirmInviteBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; + BoxContent::paintEvent(e); - p.drawPixmap((width() - st::confirmInvitePhotoSize) / 2, st::confirmInvitePhotoTop, _photo->pixCircled(st::confirmInvitePhotoSize, st::confirmInvitePhotoSize)); + Painter p(this); + + if (_photo) { + p.drawPixmap((width() - st::confirmInvitePhotoSize) / 2, st::confirmInvitePhotoTop, _photo->pixCircled(st::confirmInvitePhotoSize, st::confirmInvitePhotoSize)); + } else { + _photoEmpty.paint(p, (width() - st::confirmInvitePhotoSize) / 2, st::confirmInvitePhotoTop, width(), st::confirmInvitePhotoSize); + } int sumWidth = _participants.size() * _userWidth; int left = (width() - sumWidth) / 2; for_const (auto user, _participants) { - user->paintUserpicLeft(p, st::confirmInviteUserPhotoSize, left + (_userWidth - st::confirmInviteUserPhotoSize) / 2, st::confirmInviteUserPhotoTop, width()); + user->paintUserpicLeft(p, left + (_userWidth - st::confirmInviteUserPhotoSize) / 2, st::confirmInviteUserPhotoTop, width(), st::confirmInviteUserPhotoSize); left += _userWidth; } } - -void ConfirmInviteBox::showAll() { - showChildren(); -} diff --git a/Telegram/SourceFiles/boxes/confirmbox.h b/Telegram/SourceFiles/boxes/confirmbox.h index ecd8525d3..d2762e4da 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.h +++ b/Telegram/SourceFiles/boxes/confirmbox.h @@ -20,178 +20,123 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "abstractbox.h" -#include "ui/flatlabel.h" -#include "core/lambda_wrap.h" +#include "boxes/abstractbox.h" + +namespace Ui { +class Checkbox; +class FlatLabel; +} // namespace Ui class InformBox; -class ConfirmBox : public AbstractBox, public ClickHandlerHost { - Q_OBJECT - +class ConfirmBox : public BoxContent, public ClickHandlerHost { public: - ConfirmBox(const QString &text, const QString &doneText = QString(), const style::RoundButton &doneStyle = st::defaultBoxButton, const QString &cancelText = QString(), const style::RoundButton &cancelStyle = st::cancelBoxButton); + ConfirmBox(QWidget*, const QString &text, base::lambda &&confirmedCallback = base::lambda(), base::lambda &&cancelledCallback = base::lambda()); + ConfirmBox(QWidget*, const QString &text, const QString &confirmText, base::lambda &&confirmedCallback = base::lambda(), base::lambda &&cancelledCallback = base::lambda()); + ConfirmBox(QWidget*, const QString &text, const QString &confirmText, const style::RoundButton &confirmStyle, base::lambda &&confirmedCallback = base::lambda(), base::lambda &&cancelledCallback = base::lambda()); + ConfirmBox(QWidget*, const QString &text, const QString &confirmText, const QString &cancelText, base::lambda &&confirmedCallback = base::lambda(), base::lambda &&cancelledCallback = base::lambda()); + ConfirmBox(QWidget*, const QString &text, const QString &confirmText, const style::RoundButton &confirmStyle, const QString &cancelText, base::lambda &&confirmedCallback = base::lambda(), base::lambda &&cancelledCallback = base::lambda()); void updateLink(); - // You can use this instead of connecting to "confirmed()" signal. - void setConfirmedCallback(base::lambda_unique &&callback) { - _confirmedCallback = std_::move(callback); + // If strict cancel is set the cancelledCallback is only called if the cancel button was pressed. + void setStrictCancel(bool strictCancel) { + _strictCancel = strictCancel; } // ClickHandlerHost interface void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; -public slots: - void onCancel(); - -signals: - void confirmed(); - void cancelled(); - void cancelPressed(); + void closeHook() override; protected: + void prepare() override; + void keyPressEvent(QKeyEvent *e) override; void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void leaveEvent(QEvent *e) override; - void closePressed() override; - void showAll() override; - -private slots: - void onConfirmPressed(); - private: - ConfirmBox(const QString &text, const QString &doneText, const style::RoundButton &doneStyle, bool informative); + struct InformBoxTag { + }; + ConfirmBox(const InformBoxTag &, const QString &text, const QString &doneText, base::lambda_copy &&closedCallback); + base::lambda generateInformCallback(const base::lambda_copy &closedCallback); friend class InformBox; + void confirmed(); void init(const QString &text); - void onTextUpdated(); + void textUpdated(); - bool _informative; + QString _confirmText; + QString _cancelText; + const style::RoundButton &_confirmStyle; + bool _informative = false; Text _text; - int32 _textWidth, _textHeight; + int _textWidth = 0; + int _textHeight = 0; void updateHover(); QPoint _lastMousePos; - BoxButton _confirm, _cancel; - - base::lambda_unique _confirmedCallback; + bool _confirmed = false; + bool _cancelled = false; + bool _strictCancel = false; + base::lambda _confirmedCallback; + base::lambda _cancelledCallback; }; class InformBox : public ConfirmBox { public: - InformBox(const QString &text, const QString &doneText = QString(), const style::RoundButton &doneStyle = st::defaultBoxButton) : ConfirmBox(text, doneText, doneStyle, true) { - } + InformBox(QWidget*, const QString &text, base::lambda_copy &&closedCallback = base::lambda_copy()); + InformBox(QWidget*, const QString &text, const QString &doneText, base::lambda_copy &&closedCallback = base::lambda_copy()); }; -class SharePhoneConfirmBox : public ConfirmBox { - Q_OBJECT - +class MaxInviteBox : public BoxContent { public: - SharePhoneConfirmBox(PeerData *recipient); - -signals: - void confirmed(PeerData *recipient); - -private slots: - void onConfirm(); - -private: - PeerData *_recipient; - -}; - -class ConfirmLinkBox : public ConfirmBox { - Q_OBJECT - -public: - ConfirmLinkBox(const QString &url); - -public slots: - void onOpenLink(); - -private: - QString _url; - -}; - -class ConfirmBotGameBox : public ConfirmBox { - Q_OBJECT - -public: - ConfirmBotGameBox(UserData *bot, const QString &url); - -public slots: - void onOpenLink(); - -private: - UserData *_bot; - QString _url; - -}; - -class MaxInviteBox : public AbstractBox { - Q_OBJECT - -public: - MaxInviteBox(const QString &link); + MaxInviteBox(QWidget*, const QString &link); protected: + void prepare() override; + void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void leaveEvent(QEvent *e) override; - void showAll() override; - private: void updateSelected(const QPoint &cursorGlobalPosition); - void step_good(float64 ms, bool timer); - BoxButton _close; Text _text; int32 _textWidth, _textHeight; QString _link; QRect _invitationLink; - bool _linkOver; + bool _linkOver = false; QPoint _lastMousePos; - QString _goodTextLink; - anim::fvalue a_goodOpacity; - Animation _a_good; - }; -class ConvertToSupergroupBox : public AbstractBox, public RPCSender { - Q_OBJECT - +class ConvertToSupergroupBox : public BoxContent, public RPCSender { public: - ConvertToSupergroupBox(ChatData *chat); - -public slots: - void onConvert(); + ConvertToSupergroupBox(QWidget*, ChatData *chat); protected: + void prepare() override; + void keyPressEvent(QKeyEvent *e) override; void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - - void showAll() override; private: + void convertToSupergroup(); void convertDone(const MTPUpdates &updates); bool convertFail(const RPCError &error); @@ -199,99 +144,77 @@ private: Text _text, _note; int32 _textWidth, _textHeight; - BoxButton _convert, _cancel; - }; -class PinMessageBox : public AbstractBox, public RPCSender { - Q_OBJECT - +class PinMessageBox : public BoxContent, public RPCSender { public: - PinMessageBox(ChannelData *channel, MsgId msgId); - -public slots: - void onPin(); + PinMessageBox(QWidget*, ChannelData *channel, MsgId msgId); protected: - void resizeEvent(QResizeEvent *e) override; + void prepare() override; - void showAll() override; + void resizeEvent(QResizeEvent *e) override; + void keyPressEvent(QKeyEvent *e) override; private: + void pinMessage(); void pinDone(const MTPUpdates &updates); bool pinFail(const RPCError &error); ChannelData *_channel; MsgId _msgId; - FlatLabel _text; - Checkbox _notify; - - BoxButton _pin, _cancel; + object_ptr _text; + object_ptr _notify; mtpRequestId _requestId = 0; }; -class RichDeleteMessageBox : public AbstractBox, public RPCSender { - Q_OBJECT - +class DeleteMessagesBox : public BoxContent, public RPCSender { public: - RichDeleteMessageBox(ChannelData *channel, UserData *from, MsgId msgId); - -public slots: - void onDelete(); + DeleteMessagesBox(QWidget*, HistoryItem *item, bool suggestModerateActions); + DeleteMessagesBox(QWidget*, const SelectedItemSet &selected); protected: + void prepare() override; + void resizeEvent(QResizeEvent *e) override; - - void showAll() override; + void keyPressEvent(QKeyEvent *e) override; private: - ChannelData *_channel; - UserData *_from; - MsgId _msgId; + void deleteAndClear(); - FlatLabel _text; - Checkbox _banUser, _reportSpam, _deleteAll; + QVector _ids; + bool _singleItem = false; + UserData *_moderateFrom = nullptr; + ChannelData *_moderateInChannel = nullptr; - BoxButton _delete, _cancel; + object_ptr _text = { nullptr }; + object_ptr _forEveryone = { nullptr }; + object_ptr _banUser = { nullptr }; + object_ptr _reportSpam = { nullptr }; + object_ptr _deleteAll = { nullptr }; }; -class KickMemberBox : public ConfirmBox { - Q_OBJECT - +class ConfirmInviteBox : public BoxContent, public RPCSender { public: - KickMemberBox(PeerData *chat, UserData *member); - -private slots: - void onConfirm(); - -private: - PeerData *_chat; - UserData *_member; - -}; - -class ConfirmInviteBox : public AbstractBox, public RPCSender { - Q_OBJECT - -public: - ConfirmInviteBox(const QString &title, const MTPChatPhoto &photo, int count, const QVector &participants); + ConfirmInviteBox(QWidget*, const QString &title, const MTPChatPhoto &photo, int count, const QVector &participants); protected: + void prepare() override; + void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; - void showAll() override; - private: - ChildWidget _title, _status; + object_ptr _title; + object_ptr _status; ImagePtr _photo; + EmptyUserpic _photoEmpty; QVector _participants; - ChildWidget _join, _cancel; int _userWidth = 0; }; diff --git a/Telegram/SourceFiles/boxes/confirmphonebox.cpp b/Telegram/SourceFiles/boxes/confirmphonebox.cpp index af9c48d85..7805b0426 100644 --- a/Telegram/SourceFiles/boxes/confirmphonebox.cpp +++ b/Telegram/SourceFiles/boxes/confirmphonebox.cpp @@ -23,30 +23,38 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_boxes.h" #include "boxes/confirmbox.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" +#include "ui/widgets/labels.h" #include "mainwidget.h" #include "lang.h" namespace { -QPointer CurrentConfirmPhoneBox = nullptr; +object_ptr CurrentConfirmPhoneBox = { nullptr }; } // namespace void ConfirmPhoneBox::start(const QString &phone, const QString &hash) { - if (CurrentConfirmPhoneBox) { - if (CurrentConfirmPhoneBox->getPhone() == phone) return; - delete CurrentConfirmPhoneBox; + if (CurrentConfirmPhoneBox && CurrentConfirmPhoneBox->getPhone() != phone) { + CurrentConfirmPhoneBox.destroyDelayed(); } - if (auto main = App::main()) { - CurrentConfirmPhoneBox = new ConfirmPhoneBox(main, phone, hash); + if (!CurrentConfirmPhoneBox) { + CurrentConfirmPhoneBox = Box(phone, hash); } + CurrentConfirmPhoneBox->checkPhoneAndHash(); } -ConfirmPhoneBox::ConfirmPhoneBox(QWidget *parent, const QString &phone, const QString &hash) : AbstractBox(st::boxWidth) -, _phone(phone) -, _hash(hash) { - setParent(parent); +ConfirmPhoneBox::ConfirmPhoneBox(QWidget*, const QString &phone, const QString &hash) +: _phone(phone) +, _hash(hash) +, _callTimer(this) { +} +void ConfirmPhoneBox::checkPhoneAndHash() { + if (_sendCodeRequestId) { + return; + } MTPaccount_SendConfirmPhoneCode::Flags flags = 0; _sendCodeRequestId = MTP::send(MTPaccount_SendConfirmPhoneCode(MTP_flags(flags), MTP_string(_hash), MTPBool()), rpcDone(&ConfirmPhoneBox::sendCodeDone), rpcFail(&ConfirmPhoneBox::sendCodeFail)); } @@ -80,22 +88,30 @@ bool ConfirmPhoneBox::sendCodeFail(const RPCError &error) { errorText = lang(lng_confirm_phone_link_invalid); } _sendCodeRequestId = 0; - Ui::showLayer(new InformBox(errorText)); - deleteLater(); + Ui::show(Box(errorText)); + if (this == CurrentConfirmPhoneBox) { + CurrentConfirmPhoneBox.destroyDelayed(); + } else { + deleteLater(); + } return true; } void ConfirmPhoneBox::setCallStatus(const CallStatus &status) { _callStatus = status; if (_callStatus.state == CallState::Waiting) { - _callTimer.start(1000); + _callTimer->start(1000); } } void ConfirmPhoneBox::launch() { - setBlueTitle(true); + if (!CurrentConfirmPhoneBox) return; + Ui::show(std_::move(CurrentConfirmPhoneBox)); +} - _about = new FlatLabel(this, st::confirmPhoneAboutLabel); +void ConfirmPhoneBox::prepare() { + + _about.create(this, st::confirmPhoneAboutLabel); TextWithEntities aboutText; auto formattedPhone = App::formatPhone(_phone); aboutText.text = lng_confirm_phone_about(lt_phone, formattedPhone); @@ -105,31 +121,28 @@ void ConfirmPhoneBox::launch() { } _about->setMarkedText(aboutText); - _code = new InputField(this, st::confirmPhoneCodeField, lang(lng_code_ph)); + _code.create(this, st::confirmPhoneCodeField, lang(lng_code_ph)); - _send = new BoxButton(this, lang(lng_confirm_phone_send), st::defaultBoxButton); - _cancel = new BoxButton(this, lang(lng_cancel), st::cancelBoxButton); + setTitle(lang(lng_confirm_phone_title)); - setMaxHeight(st::boxTitleHeight + st::usernamePadding.top() + _code->height() + st::usernameSkip + _about->height() + st::usernameSkip + _send->height() + st::boxButtonPadding.bottom()); + addButton(lang(lng_confirm_phone_send), [this] { onSendCode(); }); + addButton(lang(lng_cancel), [this] { closeBox(); }); - connect(_send, SIGNAL(clicked()), this, SLOT(onSendCode())); - connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose())); + setDimensions(st::boxWidth, st::usernamePadding.top() + _code->height() + st::usernameSkip + _about->height() + st::usernameSkip); connect(_code, SIGNAL(changed()), this, SLOT(onCodeChanged())); connect(_code, SIGNAL(submitted(bool)), this, SLOT(onSendCode())); - connect(&_callTimer, SIGNAL(timeout()), this, SLOT(onCallStatusTimer())); + connect(_callTimer, SIGNAL(timeout()), this, SLOT(onCallStatusTimer())); - prepare(); - - Ui::showLayer(this); + showChildren(); } void ConfirmPhoneBox::onCallStatusTimer() { if (_callStatus.state == CallState::Waiting) { if (--_callStatus.timeout <= 0) { _callStatus.state = CallState::Calling; - _callTimer.stop(); + _callTimer->stop(); MTP::send(MTPauth_ResendCode(MTP_string(_phone), MTP_string(_phoneHash)), rpcDone(&ConfirmPhoneBox::callDone)); } } @@ -163,7 +176,7 @@ void ConfirmPhoneBox::onSendCode() { void ConfirmPhoneBox::confirmDone(const MTPBool &result) { _sendCodeRequestId = 0; - Ui::showLayer(new InformBox(lng_confirm_phone_success(lt_phone, App::formatPhone(_phone)))); + Ui::show(Box(lng_confirm_phone_success(lt_phone, App::formatPhone(_phone)))); } bool ConfirmPhoneBox::confirmFail(const RPCError &error) { @@ -244,10 +257,9 @@ void ConfirmPhoneBox::showError(const QString &error) { } void ConfirmPhoneBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; + BoxContent::paintEvent(e); - paintTitle(p, lang(lng_confirm_phone_title)); + Painter p(this); p.setFont(st::boxTextFont); auto callText = getCallText(); @@ -264,7 +276,7 @@ void ConfirmPhoneBox::paintEvent(QPaintEvent *e) { p.setPen(st::usernameDefaultFg); errorText = lang(lng_confirm_phone_enter_code); } else { - p.setPen(st::setErrColor); + p.setPen(st::boxTextFgError); } auto errorTextRectLeft = st::usernamePadding.left(); auto errorTextRectTop = _code->y() + _code->height(); @@ -288,15 +300,16 @@ QString ConfirmPhoneBox::getCallText() const { } void ConfirmPhoneBox::resizeEvent(QResizeEvent *e) { + BoxContent::resizeEvent(e); + _code->resize(width() - st::usernamePadding.left() - st::usernamePadding.right(), _code->height()); - _code->moveToLeft(st::usernamePadding.left(), st::boxTitleHeight + st::usernamePadding.top()); + _code->moveToLeft(st::usernamePadding.left(), st::usernamePadding.top()); _about->moveToLeft(st::usernamePadding.left(), _code->y() + _code->height() + st::usernameSkip); +} - _send->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _send->height()); - _cancel->moveToRight(st::boxButtonPadding.right() + _send->width() + st::boxButtonPadding.left(), _send->y()); - - AbstractBox::resizeEvent(e); +void ConfirmPhoneBox::setInnerFocus() { + _code->setFocusFast(); } ConfirmPhoneBox::~ConfirmPhoneBox() { diff --git a/Telegram/SourceFiles/boxes/confirmphonebox.h b/Telegram/SourceFiles/boxes/confirmphonebox.h index f759824e9..87c7803cf 100644 --- a/Telegram/SourceFiles/boxes/confirmphonebox.h +++ b/Telegram/SourceFiles/boxes/confirmphonebox.h @@ -22,9 +22,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "boxes/abstractbox.h" +namespace Ui { +class InputField; class FlatLabel; +} // namespace Ui -class ConfirmPhoneBox : public AbstractBox, public RPCSender { +class ConfirmPhoneBox : public BoxContent, public RPCSender { Q_OBJECT public: @@ -38,17 +41,18 @@ private slots: void onCodeChanged(); protected: + void prepare() override; + void setInnerFocus() override; + void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; - void showAll() override { - showChildren(); - } - void doSetInnerFocus() override { - _code->setFocus(); - } private: - ConfirmPhoneBox(QWidget *parent, const QString &phone, const QString &hash); + ConfirmPhoneBox(QWidget*, const QString &phone, const QString &hash); + friend class object_ptr; + + void checkPhoneAndHash(); + void sendCodeDone(const MTPauth_SentCode &result); bool sendCodeFail(const RPCError &error); @@ -89,16 +93,14 @@ private: mtpRequestId _checkCodeRequestId = 0; - ChildWidget _about = { nullptr }; - ChildWidget _send = { nullptr }; - ChildWidget _cancel = { nullptr }; - ChildWidget _code = { nullptr }; + object_ptr _about = { nullptr }; + object_ptr _code = { nullptr }; // Flag for not calling onTextChanged() recursively. bool _fixing = false; QString _error; CallStatus _callStatus; - QTimer _callTimer; + object_ptr _callTimer; }; diff --git a/Telegram/SourceFiles/boxes/connectionbox.cpp b/Telegram/SourceFiles/boxes/connectionbox.cpp index cc9e59743..86ad6f0d0 100644 --- a/Telegram/SourceFiles/boxes/connectionbox.cpp +++ b/Telegram/SourceFiles/boxes/connectionbox.cpp @@ -19,147 +19,142 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" +#include "boxes/connectionbox.h" + #include "lang.h" - #include "localstorage.h" - -#include "connectionbox.h" #include "mainwidget.h" #include "mainwindow.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" #include "history/history_location_manager.h" +#include "styles/style_boxes.h" -ConnectionBox::ConnectionBox() : AbstractBox(st::boxWidth) -, _hostInput(this, st::connectionHostInputField, lang(lng_connection_host_ph), Global::ConnectionProxy().host) +ConnectionBox::ConnectionBox(QWidget *parent) +: _hostInput(this, st::connectionHostInputField, lang(lng_connection_host_ph), Global::ConnectionProxy().host) , _portInput(this, st::connectionPortInputField, lang(lng_connection_port_ph), QString::number(Global::ConnectionProxy().port)) , _userInput(this, st::connectionUserInputField, lang(lng_connection_user_ph), Global::ConnectionProxy().user) , _passwordInput(this, st::connectionPasswordInputField, lang(lng_connection_password_ph), Global::ConnectionProxy().password) -, _autoRadio(this, qsl("conn_type"), dbictAuto, lang(lng_connection_auto_rb), (Global::ConnectionType() == dbictAuto)) -, _httpProxyRadio(this, qsl("conn_type"), dbictHttpProxy, lang(lng_connection_http_proxy_rb), (Global::ConnectionType() == dbictHttpProxy)) -, _tcpProxyRadio(this, qsl("conn_type"), dbictTcpProxy, lang(lng_connection_tcp_proxy_rb), (Global::ConnectionType() == dbictTcpProxy)) -, _tryIPv6(this, lang(lng_connection_try_ipv6), Global::TryIPv6(), st::defaultBoxCheckbox) -, _save(this, lang(lng_connection_save), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) { - - connect(&_save, SIGNAL(clicked()), this, SLOT(onSave())); - connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); - - connect(&_autoRadio, SIGNAL(changed()), this, SLOT(onChange())); - connect(&_httpProxyRadio, SIGNAL(changed()), this, SLOT(onChange())); - connect(&_tcpProxyRadio, SIGNAL(changed()), this, SLOT(onChange())); - - connect(&_hostInput, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); - connect(&_portInput, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); - connect(&_userInput, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); - connect(&_passwordInput, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); - - prepare(); +, _autoRadio(this, qsl("conn_type"), dbictAuto, lang(lng_connection_auto_rb), (Global::ConnectionType() == dbictAuto), st::defaultBoxCheckbox) +, _httpProxyRadio(this, qsl("conn_type"), dbictHttpProxy, lang(lng_connection_http_proxy_rb), (Global::ConnectionType() == dbictHttpProxy), st::defaultBoxCheckbox) +, _tcpProxyRadio(this, qsl("conn_type"), dbictTcpProxy, lang(lng_connection_tcp_proxy_rb), (Global::ConnectionType() == dbictTcpProxy), st::defaultBoxCheckbox) +, _tryIPv6(this, lang(lng_connection_try_ipv6), Global::TryIPv6(), st::defaultBoxCheckbox) { } -void ConnectionBox::showAll() { - _autoRadio.show(); - _httpProxyRadio.show(); - _tcpProxyRadio.show(); - _tryIPv6.show(); +void ConnectionBox::prepare() { + setTitle(lang(lng_connection_header)); - int32 h = st::boxTitleHeight + st::boxOptionListPadding.top() + _autoRadio.height() + st::boxOptionListPadding.top() + _httpProxyRadio.height() + st::boxOptionListPadding.top() + _tcpProxyRadio.height() + st::boxOptionListPadding.top() + st::connectionIPv6Skip + _tryIPv6.height() + st::boxOptionListPadding.bottom() + st::boxPadding.bottom() + st::boxButtonPadding.top() + _save.height() + st::boxButtonPadding.bottom(); - if (_httpProxyRadio.checked() || _tcpProxyRadio.checked()) { - h += 2 * st::boxOptionListPadding.top() + 2 * _hostInput.height(); - _hostInput.show(); - _portInput.show(); - _userInput.show(); - _passwordInput.show(); + addButton(lang(lng_connection_save), [this] { onSave(); }); + addButton(lang(lng_cancel), [this] { closeBox(); }); + + connect(_autoRadio, SIGNAL(changed()), this, SLOT(onChange())); + connect(_httpProxyRadio, SIGNAL(changed()), this, SLOT(onChange())); + connect(_tcpProxyRadio, SIGNAL(changed()), this, SLOT(onChange())); + + connect(_hostInput, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); + connect(_portInput, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); + connect(_userInput, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); + connect(_passwordInput, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); + + updateControlsVisibility(); +} + +void ConnectionBox::updateControlsVisibility() { + auto newHeight = st::boxOptionListPadding.top() + _autoRadio->heightNoMargins() + st::boxOptionListSkip + _httpProxyRadio->heightNoMargins() + st::boxOptionListSkip + _tcpProxyRadio->heightNoMargins() + st::boxOptionListSkip + st::connectionIPv6Skip + _tryIPv6->heightNoMargins() + st::boxOptionListPadding.bottom() + st::boxPadding.bottom(); + if (_httpProxyRadio->checked() || _tcpProxyRadio->checked()) { + newHeight += 2 * st::boxOptionInputSkip + 2 * _hostInput->height(); + _hostInput->show(); + _portInput->show(); + _userInput->show(); + _passwordInput->show(); } else { - _hostInput.hide(); - _portInput.hide(); - _userInput.hide(); - _passwordInput.hide(); + _hostInput->hide(); + _portInput->hide(); + _userInput->hide(); + _passwordInput->hide(); } - _save.show(); - _cancel.show(); - - setMaxHeight(h); - resizeEvent(0); + setDimensions(st::boxWidth, newHeight); + updateControlsPosition(); } -void ConnectionBox::doSetInnerFocus() { - if (!_hostInput.isHidden()) { - _hostInput.setFocus(); +void ConnectionBox::setInnerFocus() { + if (_hostInput->isHidden()) { + setFocus(); + } else { + _hostInput->setFocusFast(); } } -void ConnectionBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; - - paintTitle(p, lang(lng_connection_header)); -} - void ConnectionBox::resizeEvent(QResizeEvent *e) { - _autoRadio.moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), st::boxTitleHeight + st::boxOptionListPadding.top()); - _httpProxyRadio.moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _autoRadio.y() + _autoRadio.height() + st::boxOptionListPadding.top()); + BoxContent::resizeEvent(e); + + updateControlsPosition(); +} + +void ConnectionBox::updateControlsPosition() { + _autoRadio->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), st::boxOptionListPadding.top()); + _httpProxyRadio->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _autoRadio->bottomNoMargins() + st::boxOptionListSkip); int32 inputy = 0; - if (_httpProxyRadio.checked()) { - inputy = _httpProxyRadio.y() + _httpProxyRadio.height() + st::boxOptionListPadding.top(); - _tcpProxyRadio.moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), inputy + st::boxOptionListPadding.top() + 2 * _hostInput.height() + st::boxOptionListPadding.top()); + if (_httpProxyRadio->checked()) { + inputy = _httpProxyRadio->bottomNoMargins() + st::boxOptionInputSkip; + _tcpProxyRadio->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), inputy + st::boxOptionInputSkip + 2 * _hostInput->height() + st::boxOptionListSkip); } else { - _tcpProxyRadio.moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _httpProxyRadio.y() + _httpProxyRadio.height() + st::boxOptionListPadding.top()); - if (_tcpProxyRadio.checked()) { - inputy = _tcpProxyRadio.y() + _tcpProxyRadio.height() + st::boxOptionListPadding.top(); + _tcpProxyRadio->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _httpProxyRadio->bottomNoMargins() + st::boxOptionListSkip); + if (_tcpProxyRadio->checked()) { + inputy = _tcpProxyRadio->bottomNoMargins() + st::boxOptionInputSkip; } } if (inputy) { - _hostInput.moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left() + st::defaultRadiobutton.textPosition.x() - st::defaultInputField.textMargins.left(), inputy); - _portInput.moveToRight(st::boxPadding.right(), inputy); - _userInput.moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left() + st::defaultRadiobutton.textPosition.x() - st::defaultInputField.textMargins.left(), _hostInput.y() + _hostInput.height() + st::boxOptionListPadding.top()); - _passwordInput.moveToRight(st::boxPadding.right(), _userInput.y()); + _hostInput->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left() + st::defaultBoxCheckbox.textPosition.x() - st::defaultInputField.textMargins.left(), inputy); + _portInput->moveToRight(st::boxPadding.right(), inputy); + _userInput->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left() + st::defaultBoxCheckbox.textPosition.x() - st::defaultInputField.textMargins.left(), _hostInput->y() + _hostInput->height() + st::boxOptionInputSkip); + _passwordInput->moveToRight(st::boxPadding.right(), _userInput->y()); } - int32 tryipv6y = (_tcpProxyRadio.checked() ? (_userInput.y() + _userInput.height()) : (_tcpProxyRadio.y() + _tcpProxyRadio.height())) + st::boxOptionListPadding.top() + st::connectionIPv6Skip; - _tryIPv6.moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), tryipv6y); - - _save.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _save.height()); - _cancel.moveToRight(st::boxButtonPadding.right() + _save.width() + st::boxButtonPadding.left(), _save.y()); - - AbstractBox::resizeEvent(e); + auto tryipv6y = (_tcpProxyRadio->checked() ? _userInput->bottomNoMargins() : _tcpProxyRadio->bottomNoMargins()) + st::boxOptionListSkip + st::connectionIPv6Skip; + _tryIPv6->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), tryipv6y); } void ConnectionBox::onChange() { - showAll(); - if (_httpProxyRadio.checked() || _tcpProxyRadio.checked()) { - _hostInput.setFocus(); - if (_httpProxyRadio.checked() && !_portInput.getLastText().toInt()) { - _portInput.setText(qsl("80")); - _portInput.updatePlaceholder(); + updateControlsVisibility(); + if (_httpProxyRadio->checked() || _tcpProxyRadio->checked()) { + if (!_hostInput->hasFocus() && !_portInput->hasFocus() && !_userInput->hasFocus() && !_passwordInput->hasFocus()) { + _hostInput->setFocusFast(); + } + if (_httpProxyRadio->checked() && !_portInput->getLastText().toInt()) { + _portInput->setText(qsl("80")); + _portInput->finishAnimations(); } } update(); } void ConnectionBox::onSubmit() { - if (_hostInput.hasFocus()) { - if (!_hostInput.getLastText().trimmed().isEmpty()) { - _portInput.setFocus(); + if (_hostInput->hasFocus()) { + if (!_hostInput->getLastText().trimmed().isEmpty()) { + _portInput->setFocus(); } else { - _hostInput.showError(); + _hostInput->showError(); } - } else if (_portInput.hasFocus()) { - if (_portInput.getLastText().trimmed().toInt() > 0) { - _userInput.setFocus(); + } else if (_portInput->hasFocus()) { + if (_portInput->getLastText().trimmed().toInt() > 0) { + _userInput->setFocus(); } else { - _portInput.showError(); + _portInput->showError(); } - } else if (_userInput.hasFocus()) { - _passwordInput.setFocus(); - } else if (_passwordInput.hasFocus()) { - if (_hostInput.getLastText().trimmed().isEmpty()) { - _hostInput.setFocus(); - _hostInput.showError(); - } else if (_portInput.getLastText().trimmed().toInt() <= 0) { - _portInput.setFocus(); - _portInput.showError(); + } else if (_userInput->hasFocus()) { + _passwordInput->setFocus(); + } else if (_passwordInput->hasFocus()) { + if (_hostInput->getLastText().trimmed().isEmpty()) { + _hostInput->setFocus(); + _hostInput->showError(); + } else if (_portInput->getLastText().trimmed().toInt() <= 0) { + _portInput->setFocus(); + _portInput->showError(); } else { onSave(); } @@ -167,20 +162,20 @@ void ConnectionBox::onSubmit() { } void ConnectionBox::onSave() { - if (_httpProxyRadio.checked() || _tcpProxyRadio.checked()) { + if (_httpProxyRadio->checked() || _tcpProxyRadio->checked()) { ProxyData p; - p.host = _hostInput.getLastText().trimmed(); - p.user = _userInput.getLastText().trimmed(); - p.password = _passwordInput.getLastText().trimmed(); - p.port = _portInput.getLastText().toInt(); + p.host = _hostInput->getLastText().trimmed(); + p.user = _userInput->getLastText().trimmed(); + p.password = _passwordInput->getLastText().trimmed(); + p.port = _portInput->getLastText().toInt(); if (p.host.isEmpty()) { - _hostInput.setFocus(); + _hostInput->setFocus(); return; } else if (!p.port) { - _portInput.setFocus(); + _portInput->setFocus(); return; } - if (_httpProxyRadio.checked()) { + if (_httpProxyRadio->checked()) { Global::SetConnectionType(dbictHttpProxy); } else { Global::SetConnectionType(dbictTcpProxy); @@ -194,90 +189,72 @@ void ConnectionBox::onSave() { QNetworkProxyFactory::setUseSystemConfiguration(true); #endif // !TDESKTOP_DISABLE_NETWORK_PROXY } - if (cPlatform() == dbipWindows && Global::TryIPv6() != _tryIPv6.checked()) { - Global::SetTryIPv6(_tryIPv6.checked()); + if (cPlatform() == dbipWindows && Global::TryIPv6() != _tryIPv6->checked()) { + Global::SetTryIPv6(_tryIPv6->checked()); Local::writeSettings(); Global::RefConnectionTypeChanged().notify(); - cSetRestarting(true); - cSetRestartingToSettings(true); - App::quit(); + App::restart(); } else { - Global::SetTryIPv6(_tryIPv6.checked()); + Global::SetTryIPv6(_tryIPv6->checked()); Local::writeSettings(); Global::RefConnectionTypeChanged().notify(); MTP::restart(); reinitLocationManager(); reinitWebLoadManager(); - onClose(); + closeBox(); } } -AutoDownloadBox::AutoDownloadBox() : AbstractBox(st::boxWidth) -, _photoPrivate(this, lang(lng_media_auto_private_chats), !(cAutoDownloadPhoto() & dbiadNoPrivate), st::defaultBoxCheckbox) +AutoDownloadBox::AutoDownloadBox(QWidget *parent) +: _photoPrivate(this, lang(lng_media_auto_private_chats), !(cAutoDownloadPhoto() & dbiadNoPrivate), st::defaultBoxCheckbox) , _photoGroups(this, lang(lng_media_auto_groups), !(cAutoDownloadPhoto() & dbiadNoGroups), st::defaultBoxCheckbox) , _audioPrivate(this, lang(lng_media_auto_private_chats), !(cAutoDownloadAudio() & dbiadNoPrivate), st::defaultBoxCheckbox) , _audioGroups(this, lang(lng_media_auto_groups), !(cAutoDownloadAudio() & dbiadNoGroups), st::defaultBoxCheckbox) , _gifPrivate(this, lang(lng_media_auto_private_chats), !(cAutoDownloadGif() & dbiadNoPrivate), st::defaultBoxCheckbox) , _gifGroups(this, lang(lng_media_auto_groups), !(cAutoDownloadGif() & dbiadNoGroups), st::defaultBoxCheckbox) , _gifPlay(this, lang(lng_media_auto_play), cAutoPlayGif(), st::defaultBoxCheckbox) -, _sectionHeight(st::boxTitleHeight + 2 * (st::defaultBoxCheckbox.height + st::setLittleSkip)) -, _save(this, lang(lng_connection_save), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) { - - setMaxHeight(3 * _sectionHeight + st::setLittleSkip + _gifPlay.height() + st::setLittleSkip + st::boxButtonPadding.top() + _save.height() + st::boxButtonPadding.bottom()); - - connect(&_save, SIGNAL(clicked()), this, SLOT(onSave())); - connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); - - prepare(); +, _sectionHeight(st::boxTitleHeight + 2 * (st::defaultBoxCheckbox.height + st::setLittleSkip)) { } -void AutoDownloadBox::showAll() { - _photoPrivate.show(); - _photoGroups.show(); - _audioPrivate.show(); - _audioGroups.show(); - _gifPrivate.show(); - _gifGroups.show(); - _gifPlay.show(); +void AutoDownloadBox::prepare() { + addButton(lang(lng_connection_save), [this] { onSave(); }); + addButton(lang(lng_cancel), [this] { closeBox(); }); - _save.show(); - _cancel.show(); + setDimensions(st::boxWidth, 3 * _sectionHeight - st::autoDownloadTopDelta + st::setLittleSkip + _gifPlay->heightNoMargins() + st::setLittleSkip); } void AutoDownloadBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; + BoxContent::paintEvent(e); - p.setPen(st::black); - p.setFont(st::semiboldFont); - p.drawTextLeft(st::boxTitlePosition.x(), st::boxTitlePosition.y(), width(), lang(lng_media_auto_photo)); - p.drawTextLeft(st::boxTitlePosition.x(), _sectionHeight + st::boxTitlePosition.y(), width(), lang(lng_media_auto_audio)); - p.drawTextLeft(st::boxTitlePosition.x(), 2 * _sectionHeight + st::boxTitlePosition.y(), width(), lang(lng_media_auto_gif)); + Painter p(this); + + p.setPen(st::boxTitleFg); + p.setFont(st::autoDownloadTitleFont); + p.drawTextLeft(st::autoDownloadTitlePosition.x(), st::autoDownloadTitlePosition.y(), width(), lang(lng_media_auto_photo)); + p.drawTextLeft(st::autoDownloadTitlePosition.x(), _sectionHeight + st::autoDownloadTitlePosition.y(), width(), lang(lng_media_auto_audio)); + p.drawTextLeft(st::autoDownloadTitlePosition.x(), 2 * _sectionHeight + st::autoDownloadTitlePosition.y(), width(), lang(lng_media_auto_gif)); } void AutoDownloadBox::resizeEvent(QResizeEvent *e) { - _photoPrivate.moveToLeft(st::boxTitlePosition.x(), st::boxTitleHeight + st::setLittleSkip); - _photoGroups.moveToLeft(st::boxTitlePosition.x(), _photoPrivate.y() + _photoPrivate.height() + st::setLittleSkip); + BoxContent::resizeEvent(e); - _audioPrivate.moveToLeft(st::boxTitlePosition.x(), _sectionHeight + st::boxTitleHeight + st::setLittleSkip); - _audioGroups.moveToLeft(st::boxTitlePosition.x(), _audioPrivate.y() + _audioPrivate.height() + st::setLittleSkip); + auto top = st::boxTitleHeight - st::autoDownloadTopDelta; + _photoPrivate->moveToLeft(st::boxTitlePosition.x(), top + st::setLittleSkip); + _photoGroups->moveToLeft(st::boxTitlePosition.x(), _photoPrivate->bottomNoMargins() + st::setLittleSkip); - _gifPrivate.moveToLeft(st::boxTitlePosition.x(), 2 * _sectionHeight + st::boxTitleHeight + st::setLittleSkip); - _gifGroups.moveToLeft(st::boxTitlePosition.x(), _gifPrivate.y() + _gifPrivate.height() + st::setLittleSkip); - _gifPlay.moveToLeft(st::boxTitlePosition.x(), _gifGroups.y() + _gifGroups.height() + st::setLittleSkip); + _audioPrivate->moveToLeft(st::boxTitlePosition.x(), _sectionHeight + top + st::setLittleSkip); + _audioGroups->moveToLeft(st::boxTitlePosition.x(), _audioPrivate->bottomNoMargins() + st::setLittleSkip); - _save.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _save.height()); - _cancel.moveToRight(st::boxButtonPadding.right() + _save.width() + st::boxButtonPadding.left(), _save.y()); - - AbstractBox::resizeEvent(e); + _gifPrivate->moveToLeft(st::boxTitlePosition.x(), 2 * _sectionHeight + top + st::setLittleSkip); + _gifGroups->moveToLeft(st::boxTitlePosition.x(), _gifPrivate->bottomNoMargins() + st::setLittleSkip); + _gifPlay->moveToLeft(st::boxTitlePosition.x(), _gifGroups->bottomNoMargins() + st::setLittleSkip); } void AutoDownloadBox::onSave() { bool changed = false; - int32 autoDownloadPhoto = (_photoPrivate.checked() ? 0 : dbiadNoPrivate) | (_photoGroups.checked() ? 0 : dbiadNoGroups); + int32 autoDownloadPhoto = (_photoPrivate->checked() ? 0 : dbiadNoPrivate) | (_photoGroups->checked() ? 0 : dbiadNoGroups); if (cAutoDownloadPhoto() != autoDownloadPhoto) { bool enabledPrivate = ((cAutoDownloadPhoto() & dbiadNoPrivate) && !(autoDownloadPhoto & dbiadNoPrivate)); bool enabledGroups = ((cAutoDownloadPhoto() & dbiadNoGroups) && !(autoDownloadPhoto & dbiadNoGroups)); @@ -290,7 +267,7 @@ void AutoDownloadBox::onSave() { } changed = true; } - int32 autoDownloadAudio = (_audioPrivate.checked() ? 0 : dbiadNoPrivate) | (_audioGroups.checked() ? 0 : dbiadNoGroups); + int32 autoDownloadAudio = (_audioPrivate->checked() ? 0 : dbiadNoPrivate) | (_audioGroups->checked() ? 0 : dbiadNoGroups); if (cAutoDownloadAudio() != autoDownloadAudio) { bool enabledPrivate = ((cAutoDownloadAudio() & dbiadNoPrivate) && !(autoDownloadAudio & dbiadNoPrivate)); bool enabledGroups = ((cAutoDownloadAudio() & dbiadNoGroups) && !(autoDownloadAudio & dbiadNoGroups)); @@ -305,7 +282,7 @@ void AutoDownloadBox::onSave() { } changed = true; } - int32 autoDownloadGif = (_gifPrivate.checked() ? 0 : dbiadNoPrivate) | (_gifGroups.checked() ? 0 : dbiadNoGroups); + int32 autoDownloadGif = (_gifPrivate->checked() ? 0 : dbiadNoPrivate) | (_gifGroups->checked() ? 0 : dbiadNoGroups); if (cAutoDownloadGif() != autoDownloadGif) { bool enabledPrivate = ((cAutoDownloadGif() & dbiadNoPrivate) && !(autoDownloadGif & dbiadNoPrivate)); bool enabledGroups = ((cAutoDownloadGif() & dbiadNoGroups) && !(autoDownloadGif & dbiadNoGroups)); @@ -320,13 +297,13 @@ void AutoDownloadBox::onSave() { } changed = true; } - if (cAutoPlayGif() != _gifPlay.checked()) { - cSetAutoPlayGif(_gifPlay.checked()); + if (cAutoPlayGif() != _gifPlay->checked()) { + cSetAutoPlayGif(_gifPlay->checked()); if (!cAutoPlayGif()) { App::stopGifItems(); } changed = true; } if (changed) Local::writeUserSettings(); - onClose(); + closeBox(); } diff --git a/Telegram/SourceFiles/boxes/connectionbox.h b/Telegram/SourceFiles/boxes/connectionbox.h index 98f69e5b1..e9982ccb8 100644 --- a/Telegram/SourceFiles/boxes/connectionbox.h +++ b/Telegram/SourceFiles/boxes/connectionbox.h @@ -20,60 +20,72 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "abstractbox.h" +#include "boxes/abstractbox.h" -class ConnectionBox : public AbstractBox { +namespace Ui { +class InputField; +class PortInput; +class PasswordInput; +class Checkbox; +class Radiobutton; +} // namespace Ui + +class ConnectionBox : public BoxContent { Q_OBJECT public: - ConnectionBox(); + ConnectionBox(QWidget *parent); -public slots: +protected: + void prepare() override; + void setInnerFocus() override; + + void resizeEvent(QResizeEvent *e) override; + +private slots: void onChange(); void onSubmit(); void onSave(); -protected: - void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - - void showAll() override; - void doSetInnerFocus() override; - private: - InputField _hostInput; - PortInput _portInput; - InputField _userInput; - PasswordField _passwordInput; - Radiobutton _autoRadio, _httpProxyRadio, _tcpProxyRadio; - Checkbox _tryIPv6; + void updateControlsVisibility(); + void updateControlsPosition(); - BoxButton _save, _cancel; + object_ptr _hostInput; + object_ptr _portInput; + object_ptr _userInput; + object_ptr _passwordInput; + object_ptr _autoRadio; + object_ptr _httpProxyRadio; + object_ptr _tcpProxyRadio; + object_ptr _tryIPv6; }; -class AutoDownloadBox : public AbstractBox { +class AutoDownloadBox : public BoxContent { Q_OBJECT public: - AutoDownloadBox(); - -public slots: - void onSave(); + AutoDownloadBox(QWidget *parent); protected: + void prepare() override; + void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; - void showAll() override; +private slots: + void onSave(); private: - Checkbox _photoPrivate, _photoGroups; - Checkbox _audioPrivate, _audioGroups; - Checkbox _gifPrivate, _gifGroups, _gifPlay; + object_ptr _photoPrivate; + object_ptr _photoGroups; + object_ptr _audioPrivate; + object_ptr _audioGroups; + object_ptr _gifPrivate; + object_ptr _gifGroups; + object_ptr _gifPlay; - int32 _sectionHeight; - - BoxButton _save, _cancel; + int _sectionHeight = 0; }; diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp index 8a5e09674..c56c8726e 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.cpp +++ b/Telegram/SourceFiles/boxes/contactsbox.cpp @@ -22,92 +22,116 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "boxes/contactsbox.h" #include "dialogs/dialogs_indexed_list.h" +#include "styles/style_boxes.h" #include "styles/style_dialogs.h" #include "styles/style_history.h" -#include "styles/style_boxes.h" +#include "styles/style_profile.h" #include "lang.h" #include "boxes/addcontactbox.h" #include "mainwidget.h" #include "mainwindow.h" #include "application.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/buttons.h" #include "ui/filedialog.h" #include "ui/widgets/multi_select.h" +#include "ui/widgets/scroll_area.h" #include "ui/effects/widget_slide_wrap.h" +#include "ui/effects/ripple_animation.h" #include "boxes/photocropbox.h" #include "boxes/confirmbox.h" +#include "window/window_theme.h" #include "observer_peer.h" #include "apiwrap.h" -QString cantInviteError() { - return lng_cant_invite_not_contact(lt_more_info, textcmdLink(qsl("https://telegram.me/spambot"), lang(lng_cant_more_info))); +QString PeerFloodErrorText(PeerFloodType type) { + auto link = textcmdLink(CreateInternalLinkHttps(qsl("spambot")), lang(lng_cant_more_info)); + if (type == PeerFloodType::InviteGroup) { + return lng_cant_invite_not_contact(lt_more_info, link); + } + return lng_cant_send_to_not_contact(lt_more_info, link); } -ContactsBox::ContactsBox() : ItemListBox(st::contactsScroll) -, _inner(this, CreatingGroupNone) -, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); }) -, _next(this, lang(lng_create_group_next), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) -, _topShadow(this) { - init(); +ContactsBox::ContactsBox(QWidget*, ChatData *chat, MembersFilter filter) +: _chat(chat) +, _membersFilter(filter) +, _select(createMultiSelect()) +, _searchTimer(this) { } -ContactsBox::ContactsBox(const QString &name, const QImage &photo) : ItemListBox(st::boxScroll) -, _inner(this, CreatingGroupGroup) -, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); }) -, _next(this, lang(lng_create_group_create), st::defaultBoxButton) -, _cancel(this, lang(lng_create_group_back), st::cancelBoxButton) -, _topShadow(this) +ContactsBox::ContactsBox(QWidget*, ChannelData *channel) +: _channel(channel) +, _creating(CreatingGroupChannel) +, _select(createMultiSelect()) +, _searchTimer(this) { +} + +ContactsBox::ContactsBox(QWidget*, ChannelData *channel, MembersFilter filter, const MembersAlreadyIn &already) +: _channel(channel) +, _membersFilter(filter) +, _alreadyIn(already) +, _select(createMultiSelect()) +, _searchTimer(this) { +} + +ContactsBox::ContactsBox(QWidget*, UserData *bot) +: _bot(bot) +, _select(createMultiSelect()) +, _searchTimer(this) { +} + +ContactsBox::ContactsBox(QWidget*, const QString &name, const QImage &photo) +: _creating(CreatingGroupGroup) +, _select(createMultiSelect()) +, _searchTimer(this) , _creationName(name) , _creationPhoto(photo) { - init(); } -ContactsBox::ContactsBox(ChannelData *channel) : ItemListBox(st::boxScroll) -, _inner(this, channel, MembersFilter::Recent, MembersAlreadyIn()) -, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); }) -, _next(this, lang(lng_participant_invite), st::defaultBoxButton) -, _cancel(this, lang(lng_create_group_skip), st::cancelBoxButton) -, _topShadow(this) { - init(); +ContactsBox::ContactsBox(QWidget*) +: _select(createMultiSelect()) +, _searchTimer(this) { } -ContactsBox::ContactsBox(ChannelData *channel, MembersFilter filter, const MembersAlreadyIn &already) : ItemListBox((filter == MembersFilter::Admins) ? st::contactsScroll : st::boxScroll) -, _inner(this, channel, filter, already) -, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); }) -, _next(this, lang(lng_participant_invite), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) -, _topShadow(this) { - init(); -} - -ContactsBox::ContactsBox(ChatData *chat, MembersFilter filter) : ItemListBox(st::boxScroll) -, _inner(this, chat, filter) -, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); }) -, _next(this, lang((filter == MembersFilter::Admins) ? lng_settings_save : lng_participant_invite), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) -, _topShadow(this) { - init(); -} - -ContactsBox::ContactsBox(UserData *bot) : ItemListBox(st::contactsScroll) -, _inner(this, bot) -, _select(this, new Ui::MultiSelect(this, st::contactsMultiSelect, lang(lng_participant_filter)), QMargins(0, 0, 0, 0), [this] { updateScrollSkips(); }) -, _next(this, lang(lng_create_group_next), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) -, _topShadow(this) { - init(); -} - -void ContactsBox::init() { +void ContactsBox::prepare() { _select->resizeToWidth(st::boxWideWidth); myEnsureResized(_select); - auto inviting = (_inner->creating() == CreatingGroupGroup) || (_inner->channel() && _inner->membersFilter() == MembersFilter::Recent) || _inner->chat(); - auto topSkip = getTopScrollSkip(); - auto bottomSkip = inviting ? (st::boxButtonPadding.top() + _next.height() + st::boxButtonPadding.bottom()) : st::boxScrollSkip; - ItemListBox::init(_inner, bottomSkip, topSkip); + auto createInner = [this] { + if (_chat) { + return object_ptr(this, _chat, _membersFilter); + } else if (_channel) { + return object_ptr(this, _channel, _membersFilter, _alreadyIn); + } else if (_bot) { + return object_ptr(this, _bot); + } + return object_ptr(this, _creating); + }; + _inner = setInnerWidget(createInner(), getTopScrollSkip()); + + updateTitle(); + if (_chat) { + if (_membersFilter == MembersFilter::Admins) { + addButton(lang(lng_settings_save), [this] { saveChatAdmins(); }); + } else { + addButton(lang(lng_participant_invite), [this] { inviteParticipants(); }); + } + addButton(lang(lng_cancel), [this] { closeBox(); }); + } else if (_channel) { + if (_membersFilter != MembersFilter::Admins) { + addButton(lang(lng_participant_invite), [this] { inviteParticipants(); }); + } + addButton(lang((_creating == CreatingGroupChannel) ? lng_create_group_skip : lng_cancel), [this] { closeBox(); }); + } else if (_bot) { + addButton(lang(lng_close), [this] { closeBox(); }); + } else if (_creating == CreatingGroupGroup) { + addButton(lang(lng_create_group_create), [this] { createGroup(); }); + addButton(lang(lng_create_group_back), [this] { closeBox(); }); + } else { + addButton(lang(lng_close), [this] { closeBox(); }); + addLeftButton(lang(lng_profile_add_contact), [] { App::wnd()->onShowAddContact(); }); + } - connect(_inner, SIGNAL(addRequested()), App::wnd(), SLOT(onShowAddContact())); _inner->setPeerSelectedChangedCallback([this](PeerData *peer, bool checked) { onPeerSelectedChanged(peer, checked); }); @@ -125,25 +149,11 @@ void ContactsBox::init() { } updateScrollSkips(); }); - - if (_inner->channel() && _inner->membersFilter() == MembersFilter::Admins) { - _next.hide(); - _cancel.hide(); - } else if (_inner->chat() && _inner->membersFilter() == MembersFilter::Admins) { - connect(&_next, SIGNAL(clicked()), this, SLOT(onSaveAdmins())); - _bottomShadow = new ScrollableBoxShadow(this); - } else if (_inner->chat() || _inner->channel()) { - connect(&_next, SIGNAL(clicked()), this, SLOT(onInvite())); - _bottomShadow = new ScrollableBoxShadow(this); - } else if (_inner->creating() != CreatingGroupNone) { - connect(&_next, SIGNAL(clicked()), this, SLOT(onCreate())); - _bottomShadow = new ScrollableBoxShadow(this); + if (_inner->chat() && _inner->membersFilter() == MembersFilter::Admins && _inner->allAdmins()) { + _select->hideFast(); } else { - _next.hide(); - _cancel.hide(); + _select->showFast(); } - connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); - connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); _select->entity()->setQueryChangedCallback([this](const QString &query) { onFilterUpdate(query); }); _select->entity()->setItemRemovedCallback([this](uint64 itemId) { if (auto peer = App::peerLoaded(itemId)) { @@ -152,14 +162,16 @@ void ContactsBox::init() { } }); _select->entity()->setSubmittedCallback([this](bool) { onSubmit(); }); - connect(_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int))); + connect(_inner, SIGNAL(mustScrollTo(int, int)), this, SLOT(onScrollToY(int, int))); connect(_inner, SIGNAL(searchByUsername()), this, SLOT(onNeedSearchByUsername())); connect(_inner, SIGNAL(adminAdded()), this, SIGNAL(adminAdded())); - _searchTimer.setSingleShot(true); - connect(&_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchByUsername())); + _searchTimer->setSingleShot(true); + connect(_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchByUsername())); - prepare(); + setDimensions(st::boxWideWidth, st::boxMaxListHeight); + + _select->raise(); } bool ContactsBox::onSearchByUsername(bool searchCache) { @@ -189,9 +201,26 @@ bool ContactsBox::onSearchByUsername(bool searchCache) { return false; } +void ContactsBox::updateTitle() { + if (_chat && _membersFilter == MembersFilter::Admins) { + setTitle(lang(lng_channel_admins)); + } else if (_chat || _creating != CreatingGroupNone) { + auto addingAdmin = _channel && (_membersFilter == MembersFilter::Admins); + auto title = lang(addingAdmin ? lng_channel_add_admin : lng_profile_add_participant); + auto additional = (addingAdmin || (_inner->channel() && !_inner->channel()->isMegagroup())) ? QString() : QString("%1 / %2").arg(_inner->selectedCount()).arg(Global::MegagroupSizeMax()); + setTitle(title, additional); + } else if (_inner->sharingBotGame()) { + setTitle(lang(lng_bot_choose_chat)); + } else if (_inner->bot()) { + setTitle(lang(lng_bot_choose_group)); + } else { + setTitle(lang(lng_contacts_header)); + } +} + void ContactsBox::onNeedSearchByUsername() { if (!onSearchByUsername(true)) { - _searchTimer.start(AutoSearchTimeout); + _searchTimer->start(AutoSearchTimeout); } } @@ -216,7 +245,6 @@ void ContactsBox::peopleReceived(const MTPcontacts_Found &result, mtpRequestId r _peopleRequest = 0; _inner->updateSelection(); - onScroll(); } } @@ -230,31 +258,7 @@ bool ContactsBox::peopleFailed(const RPCError &error, mtpRequestId req) { return true; } -void ContactsBox::showAll() { - if (_inner->chat() && _inner->membersFilter() == MembersFilter::Admins && _inner->allAdmins()) { - _select->hideFast(); - } else { - _select->showFast(); - } - if (_inner->channel() && _inner->membersFilter() == MembersFilter::Admins) { - _next.hide(); - _cancel.hide(); - } else if (_inner->chat() || _inner->channel()) { - _next.show(); - _cancel.show(); - } else if (_inner->creating() != CreatingGroupNone) { - _next.show(); - _cancel.show(); - } else { - _next.hide(); - _cancel.hide(); - } - _topShadow.show(); - if (_bottomShadow) _bottomShadow->show(); - ItemListBox::showAll(); -} - -void ContactsBox::doSetInnerFocus() { +void ContactsBox::setInnerFocus() { if (_select->isHidden()) { _inner->setFocus(); } else { @@ -274,39 +278,26 @@ void ContactsBox::keyPressEvent(QKeyEvent *e) { } else if (e->key() == Qt::Key_Up) { _inner->selectSkip(-1); } else if (e->key() == Qt::Key_PageDown) { - _inner->selectSkipPage(scrollArea()->height(), 1); + _inner->selectSkipPage(height() - getTopScrollSkip(), 1); } else if (e->key() == Qt::Key_PageUp) { - _inner->selectSkipPage(scrollArea()->height(), -1); + _inner->selectSkipPage(height() - getTopScrollSkip(), -1); } else { - ItemListBox::keyPressEvent(e); + BoxContent::keyPressEvent(e); } } else { - ItemListBox::keyPressEvent(e); + BoxContent::keyPressEvent(e); } } -void ContactsBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; - - bool addingAdmin = _inner->channel() && _inner->membersFilter() == MembersFilter::Admins; - if (_inner->chat() && _inner->membersFilter() == MembersFilter::Admins) { - paintTitle(p, lang(lng_channel_admins)); - } else if (_inner->chat() || _inner->creating() != CreatingGroupNone) { - QString title(lang(addingAdmin ? lng_channel_add_admin : lng_profile_add_participant)); - QString additional((addingAdmin || (_inner->channel() && !_inner->channel()->isMegagroup())) ? QString() : QString("%1 / %2").arg(_inner->selectedCount()).arg(Global::MegagroupSizeMax())); - paintTitle(p, title, additional); - } else if (_inner->sharingBotGame()) { - paintTitle(p, lang(lng_bot_choose_chat)); - } else if (_inner->bot()) { - paintTitle(p, lang(lng_bot_choose_group)); - } else { - paintTitle(p, lang(lng_contacts_header)); - } +object_ptr> ContactsBox::createMultiSelect() { + auto entity = object_ptr(this, st::contactsMultiSelect, lang(lng_participant_filter)); + auto margins = style::margins(0, 0, 0, 0); + auto callback = [this] { updateScrollSkips(); }; + return object_ptr>(this, std_::move(entity), margins, std_::move(callback)); } int ContactsBox::getTopScrollSkip() const { - auto result = st::boxTitleHeight; + auto result = 0; if (!_select->isHidden()) { result += _select->height(); } @@ -314,48 +305,35 @@ int ContactsBox::getTopScrollSkip() const { } void ContactsBox::updateScrollSkips() { - auto oldScrollHeight = scrollArea()->height(); - auto inviting = (_inner->creating() == CreatingGroupGroup) || (_inner->channel() && _inner->membersFilter() == MembersFilter::Recent) || _inner->chat(); - auto topSkip = getTopScrollSkip(); - auto bottomSkip = inviting ? (st::boxButtonPadding.top() + _next.height() + st::boxButtonPadding.bottom()) : st::boxScrollSkip; - setScrollSkips(bottomSkip, topSkip); - auto scrollHeightDelta = scrollArea()->height() - oldScrollHeight; - if (scrollHeightDelta) { - scrollArea()->scrollToY(scrollArea()->scrollTop() - scrollHeightDelta); - } - - _topShadow.setGeometry(0, topSkip, width(), st::lineWidth); + setInnerTopSkip(getTopScrollSkip(), true); } void ContactsBox::resizeEvent(QResizeEvent *e) { - ItemListBox::resizeEvent(e); + BoxContent::resizeEvent(e); _select->resizeToWidth(width()); - _select->moveToLeft(0, st::boxTitleHeight); + _select->moveToLeft(0, 0); updateScrollSkips(); _inner->resize(width(), _inner->height()); - _next.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _next.height()); - _cancel.moveToRight(st::boxButtonPadding.right() + _next.width() + st::boxButtonPadding.left(), _next.y()); - if (_bottomShadow) _bottomShadow->setGeometry(0, height() - st::boxButtonPadding.bottom() - _next.height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth); } -void ContactsBox::closePressed() { - if (_inner->channel() && !_inner->hasAlreadyMembersInChannel()) { - Ui::showPeerHistory(_inner->channel(), ShowAtTheEndMsgId); +void ContactsBox::closeHook() { + if (_channel && _creating == CreatingGroupChannel) { + Ui::showPeerHistory(_channel, ShowAtTheEndMsgId); } } void ContactsBox::onFilterUpdate(const QString &filter) { - scrollArea()->scrollToY(0); + onScrollToY(0); _inner->updateFilter(filter); } void ContactsBox::addPeerToMultiSelect(PeerData *peer, bool skipAnimation) { using AddItemWay = Ui::MultiSelect::AddItemWay; auto addItemWay = skipAnimation ? AddItemWay::SkipAnimation : AddItemWay::Default; - _select->entity()->addItem(peer->id, peer->shortName(), st::windowActiveBg, PaintUserpicCallback(peer), addItemWay); + _select->entity()->addItem(peer->id, peer->shortName(), st::activeButtonBg, PaintUserpicCallback(peer), addItemWay); } void ContactsBox::onPeerSelectedChanged(PeerData *peer, bool checked) { @@ -365,10 +343,10 @@ void ContactsBox::onPeerSelectedChanged(PeerData *peer, bool checked) { } else { _select->entity()->removeItem(peer->id); } - update(); + updateTitle(); } -void ContactsBox::onInvite() { +void ContactsBox::inviteParticipants() { QVector users(_inner->selected()); if (users.isEmpty()) { _select->entity()->setInnerFocus(); @@ -380,11 +358,11 @@ void ContactsBox::onInvite() { Ui::hideLayer(); Ui::showPeerHistory(_inner->chat(), ShowAtTheEndMsgId); } else { - onClose(); + closeBox(); } } -void ContactsBox::onCreate() { +void ContactsBox::createGroup() { if (_saveRequestId) return; auto users = _inner->selectedInputs(); @@ -395,7 +373,7 @@ void ContactsBox::onCreate() { _saveRequestId = MTP::send(MTPmessages_CreateChat(MTP_vector(users), MTP_string(_creationName)), rpcDone(&ContactsBox::creationDone), rpcFail(&ContactsBox::creationFail)); } -void ContactsBox::onSaveAdmins() { +void ContactsBox::saveChatAdmins() { if (_saveRequestId) return; _inner->saving(true); @@ -409,7 +387,7 @@ void ContactsBox::saveAdminsDone(const MTPUpdates &result) { void ContactsBox::saveSelectedAdmins() { if (_inner->allAdmins() && !_inner->chat()->participants.isEmpty()) { - onClose(); + closeBox(); } else { _saveRequestId = MTP::send(MTPmessages_GetFullChat(_inner->chat()->inputChat), rpcDone(&ContactsBox::getAdminsDone), rpcFail(&ContactsBox::saveAdminsFail)); } @@ -418,7 +396,7 @@ void ContactsBox::saveSelectedAdmins() { void ContactsBox::getAdminsDone(const MTPmessages_ChatFull &result) { App::api()->processFullPeer(_inner->chat(), result); if (_inner->allAdmins()) { - onClose(); + closeBox(); return; } ChatData::Admins curadmins = _inner->chat()->admins; @@ -448,7 +426,7 @@ void ContactsBox::getAdminsDone(const MTPmessages_ChatFull &result) { _saveRequestId = curadmins.size() + appoint.size(); if (!_saveRequestId) { - onClose(); + closeBox(); } } @@ -463,7 +441,7 @@ void ContactsBox::setAdminDone(UserData *user, const MTPBool &result) { --_saveRequestId; if (!_saveRequestId) { emit App::main()->peerUpdated(_inner->chat()); - onClose(); + closeBox(); } } @@ -474,7 +452,7 @@ void ContactsBox::removeAdminDone(UserData *user, const MTPBool &result) { --_saveRequestId; if (!_saveRequestId) { emit App::main()->peerUpdated(_inner->chat()); - onClose(); + closeBox(); } } @@ -494,18 +472,14 @@ bool ContactsBox::editAdminFail(const RPCError &error) { _inner->chat()->invalidateParticipants(); if (!_saveRequestId) { if (error.type() == qstr("USER_RESTRICTED")) { - Ui::showLayer(new InformBox(lang(lng_cant_do_this))); + Ui::show(Box(lang(lng_cant_do_this))); return true; } - onClose(); + closeBox(); } return false; } -void ContactsBox::onScroll() { - _inner->loadProfilePhotos(scrollArea()->scrollTop()); -} - void ContactsBox::creationDone(const MTPUpdates &updates) { Ui::hideLayer(); @@ -536,42 +510,45 @@ bool ContactsBox::creationFail(const RPCError &error) { _saveRequestId = 0; if (error.type() == "NO_CHAT_TITLE") { - onClose(); + closeBox(); return true; } else if (error.type() == "USERS_TOO_FEW") { _select->entity()->setInnerFocus(); return true; } else if (error.type() == "PEER_FLOOD") { - Ui::showLayer(new InformBox(cantInviteError()), KeepOtherLayers); + Ui::show(Box(PeerFloodErrorText(PeerFloodType::InviteGroup)), KeepOtherLayers); return true; } else if (error.type() == qstr("USER_RESTRICTED")) { - Ui::showLayer(new InformBox(lang(lng_cant_do_this))); + Ui::show(Box(lang(lng_cant_do_this))); return true; } return false; } -ContactsBox::Inner::ContactData::ContactData(PeerData *peer, base::lambda_wrap updateCallback) -: checkbox(std_::make_unique(st::contactsPhotoCheckbox, std_::move(updateCallback), PaintUserpicCallback(peer))) { +ContactsBox::Inner::ContactData::ContactData() = default; + +ContactsBox::Inner::ContactData::ContactData(PeerData *peer, const base::lambda_copy &updateCallback) +: checkbox(std_::make_unique(st::contactsPhotoCheckbox, updateCallback, PaintUserpicCallback(peer))) { } -ContactsBox::Inner::Inner(QWidget *parent, CreatingGroupType creating) : ScrolledWidget(parent) +ContactsBox::Inner::ContactData::~ContactData() = default; + +ContactsBox::Inner::Inner(QWidget *parent, CreatingGroupType creating) : TWidget(parent) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) -, _newItemHeight(creating == CreatingGroupNone ? st::contactsNewItemHeight : 0) , _creating(creating) -, _allAdmins(this, lang(lng_chat_all_members_admins), false, st::contactsAdminCheckbox) +, _allAdmins(this, lang(lng_chat_all_members_admins), false, st::defaultBoxCheckbox) , _contacts(App::main()->contactsList()) , _addContactLnk(this, lang(lng_add_contact_button)) { init(); } -ContactsBox::Inner::Inner(QWidget *parent, ChannelData *channel, MembersFilter membersFilter, const MembersAlreadyIn &already) : ScrolledWidget(parent) +ContactsBox::Inner::Inner(QWidget *parent, ChannelData *channel, MembersFilter membersFilter, const MembersAlreadyIn &already) : TWidget(parent) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _channel(channel) , _membersFilter(membersFilter) , _creating(CreatingGroupChannel) , _already(already) -, _allAdmins(this, lang(lng_chat_all_members_admins), false, st::contactsAdminCheckbox) +, _allAdmins(this, lang(lng_chat_all_members_admins), false, st::defaultBoxCheckbox) , _contacts(App::main()->contactsList()) , _addContactLnk(this, lang(lng_add_contact_button)) { init(); @@ -583,20 +560,20 @@ namespace { } } -ContactsBox::Inner::Inner(QWidget *parent, ChatData *chat, MembersFilter membersFilter) : ScrolledWidget(parent) +ContactsBox::Inner::Inner(QWidget *parent, ChatData *chat, MembersFilter membersFilter) : TWidget(parent) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _chat(chat) , _membersFilter(membersFilter) -, _allAdmins(this, lang(lng_chat_all_members_admins), !_chat->adminsEnabled(), st::contactsAdminCheckbox) +, _allAdmins(this, lang(lng_chat_all_members_admins), !_chat->adminsEnabled(), st::defaultBoxCheckbox) , _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.right()) -, _aboutAllAdmins(st::boxTextFont, lang(lng_chat_about_all_admins), _defaultOptions, _aboutWidth) -, _aboutAdmins(st::boxTextFont, lang(lng_chat_about_admins), _defaultOptions, _aboutWidth) +, _aboutAllAdmins(st::defaultTextStyle, lang(lng_chat_about_all_admins), _defaultOptions, _aboutWidth) +, _aboutAdmins(st::defaultTextStyle, lang(lng_chat_about_admins), _defaultOptions, _aboutWidth) , _customList((membersFilter == MembersFilter::Recent) ? std_::unique_ptr() : std_::make_unique(Dialogs::SortMode::Add)) , _contacts((membersFilter == MembersFilter::Recent) ? App::main()->contactsList() : _customList.get()) , _addContactLnk(this, lang(lng_add_contact_button)) { initList(); if (membersFilter == MembersFilter::Admins) { - _newItemHeight = st::contactsNewItemHeight + qMax(_aboutAllAdmins.countHeight(_aboutWidth), _aboutAdmins.countHeight(_aboutWidth)) + st::contactsAboutHeight; + _aboutHeight = st::contactsAboutTop + qMax(_aboutAllAdmins.countHeight(_aboutWidth), _aboutAdmins.countHeight(_aboutWidth)) + st::contactsAboutBottom; if (_contacts->isEmpty()) { App::api()->requestFullPeer(_chat); } @@ -615,10 +592,10 @@ void ContactsBox::Inner::addDialogsToList(FilterCallback callback) { } } -ContactsBox::Inner::Inner(QWidget *parent, UserData *bot) : ScrolledWidget(parent) +ContactsBox::Inner::Inner(QWidget *parent, UserData *bot) : TWidget(parent) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _bot(bot) -, _allAdmins(this, lang(lng_chat_all_members_admins), false, st::contactsAdminCheckbox) +, _allAdmins(this, lang(lng_chat_all_members_admins), false, st::defaultBoxCheckbox) , _customList(std_::make_unique(Dialogs::SortMode::Add)) , _contacts(_customList.get()) , _addContactLnk(this, lang(lng_add_contact_button)) { @@ -647,9 +624,10 @@ ContactsBox::Inner::Inner(QWidget *parent, UserData *bot) : ScrolledWidget(paren void ContactsBox::Inner::init() { subscribe(FileDownload::ImageLoaded(), [this] { update(); }); - connect(&_addContactLnk, SIGNAL(clicked()), App::wnd(), SLOT(onShowAddContact())); - connect(&_allAdmins, SIGNAL(changed()), this, SLOT(onAllAdminsChanged())); + connect(_addContactLnk, SIGNAL(clicked()), App::wnd(), SLOT(onShowAddContact())); + connect(_allAdmins, SIGNAL(changed()), this, SLOT(onAllAdminsChanged())); + _rowsTop = st::contactsMarginTop; setAttribute(Qt::WA_OpaquePaintEvent); for_const (auto row, _contacts->all()) { @@ -663,6 +641,30 @@ void ContactsBox::Inner::init() { connect(App::main(), SIGNAL(peerUpdated(PeerData*)), this, SLOT(peerUpdated(PeerData *))); connect(App::main(), SIGNAL(peerNameChanged(PeerData*,const PeerData::Names&,const PeerData::NameFirstChars&)), this, SLOT(onPeerNameChanged(PeerData*,const PeerData::Names&,const PeerData::NameFirstChars&))); connect(App::main(), SIGNAL(peerPhotoChanged(PeerData*)), this, SLOT(peerUpdated(PeerData*))); + + subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) { + if (update.paletteChanged()) { + invalidateCache(); + } + }); +} + +void ContactsBox::Inner::invalidateCache() { + for_const (auto data, _contactsData) { + if (data->checkbox) { + data->checkbox->invalidateCache(); + } + } + for_const (auto data, _byUsernameDatas) { + if (data->checkbox) { + data->checkbox->invalidateCache(); + } + } + for_const (auto data, d_byUsername) { + if (data->checkbox) { + data->checkbox->invalidateCache(); + } + } } void ContactsBox::Inner::initList() { @@ -674,9 +676,9 @@ void ContactsBox::Inner::initList() { others.reserve(_chat->participants.size()); } - for (ChatData::Participants::const_iterator i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) { + for (auto i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) { if (i.key()->id == peerFromUser(_chat->creator)) continue; - if (!_allAdmins.checked() && _chat->admins.contains(i.key())) { + if (!_allAdmins->checked() && _chat->admins.contains(i.key())) { admins.push_back(i.key()); if (!_checkedContacts.contains(i.key())) { _checkedContacts.insert(i.key()); @@ -687,16 +689,16 @@ void ContactsBox::Inner::initList() { } std::sort(admins.begin(), admins.end(), _sortByName); std::sort(others.begin(), others.end(), _sortByName); - if (UserData *creator = App::userLoaded(_chat->creator)) { + if (auto creator = App::userLoaded(_chat->creator)) { if (_chat->participants.contains(creator)) { admins.push_front(creator); } } - for (int32 i = 0, l = admins.size(); i < l; ++i) { - _contacts->addToEnd(App::history(admins.at(i)->id)); + for_const (auto user, admins) { + _contacts->addToEnd(App::history(user->id)); } - for (int32 i = 0, l = others.size(); i < l; ++i) { - _contacts->addToEnd(App::history(others.at(i)->id)); + for_const (auto user, others) { + _contacts->addToEnd(App::history(user->id)); } } @@ -707,7 +709,7 @@ void ContactsBox::Inner::onPeerNameChanged(PeerData *peer, const PeerData::Names peerUpdated(peer); } -void ContactsBox::Inner::onAddBot() { +void ContactsBox::Inner::addBot() { if (auto &info = _bot->botInfo) { if (!info->shareGameShortName.isEmpty()) { MTPmessages_SendMedia::Flags sendFlags = 0; @@ -720,7 +722,7 @@ void ContactsBox::Inner::onAddBot() { history->sendRequestId = requestId; } } else if (!info->startGroupToken.isEmpty()) { - MTP::send(MTPmessages_StartBot(_bot->inputUser, _addToPeer->input, MTP_long(rand_value()), MTP_string(info->startGroupToken)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::addParticipantFail, _bot)); + MTP::send(MTPmessages_StartBot(_bot->inputUser, _addToPeer->input, MTP_long(rand_value()), MTP_string(info->startGroupToken)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::addParticipantFail, { _bot, _addToPeer })); } else { App::main()->addParticipants(_addToPeer, QVector(1, _bot)); } @@ -731,20 +733,9 @@ void ContactsBox::Inner::onAddBot() { Ui::showPeerHistory(_addToPeer, ShowAtUnreadMsgId); } -void ContactsBox::Inner::onAddAdmin() { - if (_addAdminRequestId) return; - _addAdminRequestId = MTP::send(MTPchannels_EditAdmin(_channel->inputChannel, _addAdmin->inputUser, MTP_channelRoleEditor()), rpcDone(&Inner::addAdminDone), rpcFail(&Inner::addAdminFail)); -} - -void ContactsBox::Inner::onNoAddAdminBox(QObject *obj) { - if (obj == _addAdminBox) { - _addAdminBox = 0; - } -} - void ContactsBox::Inner::onAllAdminsChanged() { - if (_saving && _allAdmins.checked() != _allAdminsChecked) { - _allAdmins.setChecked(_allAdminsChecked); + if (_saving && _allAdmins->checked() != _allAdminsChecked) { + _allAdmins->setChecked(_allAdminsChecked); } else if (_allAdminsChangedCallback) { _allAdminsChangedCallback(); } @@ -772,7 +763,7 @@ void ContactsBox::Inner::addAdminDone(const MTPUpdates &result, mtpRequestId req } Notify::peerUpdatedDelayed(update); } - if (_addAdminBox) _addAdminBox->onClose(); + if (_addAdminBox) _addAdminBox->closeBox(); emit adminAdded(); } @@ -782,13 +773,13 @@ bool ContactsBox::Inner::addAdminFail(const RPCError &error, mtpRequestId req) { if (req != _addAdminRequestId) return true; _addAdminRequestId = 0; - if (_addAdminBox) _addAdminBox->onClose(); + if (_addAdminBox) _addAdminBox->closeBox(); if (error.type() == "USERS_TOO_MUCH") { - Ui::showLayer(new MaxInviteBox(_channel->inviteLink()), KeepOtherLayers); + Ui::show(Box(_channel->inviteLink()), KeepOtherLayers); } else if (error.type() == "ADMINS_TOO_MUCH") { - Ui::showLayer(new InformBox(lang(lng_channel_admins_too_much)), KeepOtherLayers); + Ui::show(Box(lang(lng_channel_admins_too_much)), KeepOtherLayers); } else if (error.type() == qstr("USER_RESTRICTED")) { - Ui::showLayer(new InformBox(lang(lng_cant_do_this)), KeepOtherLayers); + Ui::show(Box(lang(lng_cant_do_this)), KeepOtherLayers); } else { emit adminAdded(); } @@ -797,7 +788,7 @@ bool ContactsBox::Inner::addAdminFail(const RPCError &error, mtpRequestId req) { void ContactsBox::Inner::saving(bool flag) { _saving = flag; - _allAdminsChecked = _allAdmins.checked(); + _allAdminsChecked = _allAdmins->checked(); update(); } @@ -835,14 +826,14 @@ void ContactsBox::Inner::peerUpdated(PeerData *peer) { for_const (auto row, _contacts->all()) { if (row->attached == i.value()) { row->attached = nullptr; - update(0, _newItemHeight + _rowHeight * row->pos(), width(), _rowHeight); + update(0, _rowsTop + _aboutHeight + _rowHeight * row->pos(), width(), _rowHeight); } } if (!_filter.isEmpty()) { for (int32 j = 0, s = _filtered.size(); j < s; ++j) { if (_filtered[j]->attached == i.value()) { _filtered[j]->attached = 0; - update(0, _rowHeight * j, width(), _rowHeight); + update(0, _rowsTop + _rowHeight * j, width(), _rowHeight); } } } @@ -852,8 +843,11 @@ void ContactsBox::Inner::peerUpdated(PeerData *peer) { } } -void ContactsBox::Inner::loadProfilePhotos(int32 yFrom) { - int32 yTo = yFrom + (parentWidget() ? parentWidget()->height() : App::wnd()->height()) * 5; +void ContactsBox::Inner::loadProfilePhotos() { + if (_visibleTop >= _visibleBottom) return; + + auto yFrom = _visibleTop - _rowsTop; + auto yTo = yFrom + (_visibleBottom - _visibleTop) * 5; MTP::clearLoaderPriorities(); if (yTo < 0) return; @@ -861,19 +855,19 @@ void ContactsBox::Inner::loadProfilePhotos(int32 yFrom) { if (_filter.isEmpty()) { if (!_contacts->isEmpty()) { - auto i = _contacts->cfind(yFrom - _newItemHeight, _rowHeight); + auto i = _contacts->cfind(yFrom - _aboutHeight, _rowHeight); for (auto end = _contacts->cend(); i != end; ++i) { - if ((_newItemHeight + (*i)->pos() * _rowHeight) >= yTo) { + if ((_aboutHeight + (*i)->pos() * _rowHeight) >= yTo) { break; } (*i)->history()->peer->loadUserpic(); } } } else if (!_filtered.isEmpty()) { - int32 from = yFrom / _rowHeight; + auto from = yFrom / _rowHeight; if (from < 0) from = 0; if (from < _filtered.size()) { - int32 to = (yTo / _rowHeight) + 1; + auto to = (yTo / _rowHeight) + 1; if (to > _filtered.size()) to = _filtered.size(); for (; from < to; ++from) { @@ -905,7 +899,7 @@ ContactsBox::Inner::ContactData *ContactsBox::Inner::contactData(Dialogs::Row *r if (usingMultiSelect() && _checkedContacts.contains(peer)) { data->checkbox->setChecked(true, Ui::RoundImageCheckbox::SetStyle::Fast); } - data->name.setText(st::contactsNameFont, peer->name, _textNameOptions); + data->name.setText(st::contactsNameStyle, peer->name, _textNameOptions); if (peer->isUser()) { data->statusText = App::onlineText(peer->asUser(), _time); data->statusHasOnlineColor = App::onlineColorUse(peer->asUser(), _time); @@ -929,35 +923,42 @@ ContactsBox::Inner::ContactData *ContactsBox::Inner::contactData(Dialogs::Row *r return data; } -void ContactsBox::Inner::paintDialog(Painter &p, uint64 ms, PeerData *peer, ContactData *data, bool sel) { - UserData *user = peer->asUser(); - +bool ContactsBox::Inner::isRowDisabled(PeerData *peer, ContactData *data) const { if (_chat && _membersFilter == MembersFilter::Admins) { - if (_allAdmins.checked() || peer->id == peerFromUser(_chat->creator) || _saving) { - sel = false; - } - } else { - if (data->disabledChecked || selectedCount() >= Global::MegagroupSizeMax()) { - sel = false; - } + return (_saving || _allAdmins->checked() || peer->id == peerFromUser(_chat->creator)); + } + return (data->disabledChecked || selectedCount() >= Global::MegagroupSizeMax()); +} + +void ContactsBox::Inner::paintDialog(Painter &p, TimeMs ms, PeerData *peer, ContactData *data, bool selected) { + auto user = peer->asUser(); + + if (isRowDisabled(peer, data)) { + selected = false; } auto paintDisabledCheck = data->disabledChecked; if (_chat && _membersFilter == MembersFilter::Admins) { - if (peer->id == peerFromUser(_chat->creator) || _allAdmins.checked()) { + if (peer->id == peerFromUser(_chat->creator) || _allAdmins->checked()) { paintDisabledCheck = true; } } auto checkedRatio = 0.; - p.fillRect(0, 0, width(), _rowHeight, sel ? st::contactsBgOver : st::white); + p.fillRect(0, 0, width(), _rowHeight, selected ? st::contactsBgOver : st::contactsBg); + if (data->ripple) { + data->ripple->paint(p, 0, 0, width(), ms); + if (data->ripple->empty()) { + data->ripple.reset(); + } + } if (paintDisabledCheck) { paintDisabledCheckUserpic(p, peer, st::contactsPadding.left(), st::contactsPadding.top(), width()); } else if (usingMultiSelect()) { checkedRatio = data->checkbox->checkedAnimationRatio(); data->checkbox->paint(p, ms, st::contactsPadding.left(), st::contactsPadding.top(), width()); } else { - peer->paintUserpicLeft(p, st::contactsPhotoSize, st::contactsPadding.left(), st::contactsPadding.top(), width()); + peer->paintUserpicLeft(p, st::contactsPadding.left(), st::contactsPadding.top(), width(), st::contactsPhotoSize); } int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); @@ -967,19 +968,11 @@ void ContactsBox::Inner::paintDialog(Painter &p, uint64 ms, PeerData *peer, Cont namew -= icon->width(); icon->paint(p, namex + qMin(data->name.maxWidth(), namew), st::contactsPadding.top() + st::contactsNameTop, width()); } - if (checkedRatio > 0) { - if (checkedRatio < 1) { - p.setPen(style::interpolate(st::black, st::contactsNameCheckedFg, checkedRatio)); - } else { - p.setPen(st::contactsNameCheckedFg); - } - } else { - p.setPen(st::black); - } + p.setPen(anim::pen(st::contactsNameFg, st::contactsNameCheckedFg, checkedRatio)); data->name.drawLeftElided(p, namex, st::contactsPadding.top() + st::contactsNameTop, namew, width()); bool uname = (user || peer->isChannel()) && (data->statusText.at(0) == '@'); - p.setFont(st::contactsStatusFont->f); + p.setFont(st::contactsStatusFont); if (uname && !_lastQuery.isEmpty() && peer->userName().startsWith(_lastQuery, Qt::CaseInsensitive)) { int availw = width() - namex - st::contactsPadding.right(); QString first = '@' + peer->userName().mid(0, _lastQuery.size()), second = peer->userName().mid(_lastQuery.size()); @@ -992,14 +985,14 @@ void ContactsBox::Inner::paintDialog(Painter &p, uint64 ms, PeerData *peer, Cont int32 secondw = st::contactsStatusFont->width(second); p.setPen(st::contactsStatusFgOnline); p.drawTextLeft(namex, st::contactsPadding.top() + st::contactsStatusTop, width() - secondw, first); - p.setPen(sel ? st::contactsStatusFgOver : st::contactsStatusFg); + p.setPen(selected ? st::contactsStatusFgOver : st::contactsStatusFg); p.drawTextLeft(namex + w, st::contactsPadding.top() + st::contactsStatusTop, width() + w, second); } } else { if ((user && (uname || data->statusHasOnlineColor)) || (peer->isChannel() && uname)) { p.setPen(st::contactsStatusFgOnline); } else { - p.setPen(sel ? st::contactsStatusFgOver : st::contactsStatusFg); + p.setPen(selected ? st::contactsStatusFgOver : st::contactsStatusFg); } p.drawTextLeft(namex, st::contactsPadding.top() + st::contactsStatusTop, width(), data->statusText); } @@ -1016,28 +1009,28 @@ void ContactsBox::Inner::paintDisabledCheckUserpic(Painter &p, PeerData *peer, i auto userpicBorderPen = st::contactsPhotoDisabledCheckFg->p; userpicBorderPen.setWidth(st::contactsPhotoCheckbox.selectWidth); - auto iconDiameter = 2 * st::contactsPhotoCheckbox.checkRadius; + auto iconDiameter = st::contactsPhotoCheckbox.check.size; auto iconLeft = x + userpicDiameter + st::contactsPhotoCheckbox.selectWidth - iconDiameter; auto iconTop = y + userpicDiameter + st::contactsPhotoCheckbox.selectWidth - iconDiameter; auto iconEllipse = rtlrect(iconLeft, iconTop, iconDiameter, iconDiameter, outerWidth); - auto iconBorderPen = st::contactsPhotoCheckbox.checkBorder->p; + auto iconBorderPen = st::contactsPhotoCheckbox.check.border->p; iconBorderPen.setWidth(st::contactsPhotoCheckbox.selectWidth); - peer->paintUserpicLeft(p, userpicRadius * 2, userpicLeft, userpicTop, width()); + peer->paintUserpicLeft(p, userpicLeft, userpicTop, width(), userpicRadius * 2); - p.setRenderHint(QPainter::HighQualityAntialiasing, true); + { + PainterHighQualityEnabler hq(p); - p.setPen(userpicBorderPen); - p.setBrush(Qt::NoBrush); - p.drawEllipse(userpicEllipse); + p.setPen(userpicBorderPen); + p.setBrush(Qt::NoBrush); + p.drawEllipse(userpicEllipse); - p.setPen(iconBorderPen); - p.setBrush(st::contactsPhotoDisabledCheckFg); - p.drawEllipse(iconEllipse); + p.setPen(iconBorderPen); + p.setBrush(st::contactsPhotoDisabledCheckFg); + p.drawEllipse(iconEllipse); + } - p.setRenderHint(QPainter::HighQualityAntialiasing, false); - - st::contactsPhotoCheckbox.checkIcon.paint(p, iconEllipse.topLeft(), outerWidth); + st::contactsPhotoCheckbox.check.check.paint(p, iconEllipse.topLeft(), outerWidth); } void ContactsBox::Inner::paintEvent(QPaintEvent *e) { @@ -1046,31 +1039,31 @@ void ContactsBox::Inner::paintEvent(QPaintEvent *e) { p.setClipRect(r); _time = unixtime(); - p.fillRect(r, st::white); + p.fillRect(r, st::contactsBg); - uint64 ms = getms(); - int32 yFrom = r.y(), yTo = r.y() + r.height(); + auto ms = getms(); + auto yFrom = r.y(), yTo = r.y() + r.height(); + auto skip = _rowsTop; if (_filter.isEmpty()) { + skip += _aboutHeight; if (!_contacts->isEmpty() || !_byUsername.isEmpty()) { - if (_newItemHeight) { - if (_chat) { - p.fillRect(0, 0, width(), _newItemHeight - st::contactsPadding.bottom() - st::lineWidth, st::contactsAboutBg); - p.fillRect(0, _newItemHeight - st::contactsPadding.bottom() - st::lineWidth, width(), st::lineWidth, st::shadowColor); - p.setPen(st::black); - p.drawTextLeft(st::contactsPadding.left(), st::contactsNewItemTop, width(), lang(lng_chat_all_members_admins)); - int aboutw = width() - st::contactsPadding.left() - st::contactsPadding.right(); - (_allAdmins.checked() ? _aboutAllAdmins : _aboutAdmins).draw(p, st::contactsPadding.left(), st::contactsNewItemHeight + st::contactsAboutTop, aboutw); - } else { - p.fillRect(0, 0, width(), st::contactsNewItemHeight, (_newItemSel ? st::contactsBgOver : st::white)->b); - p.setFont(st::contactsNameFont); - st::contactsNewItemIcon.paint(p, 0, 0, width()); - p.setPen(st::contactsNewItemFg); - p.drawTextLeft(st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(), st::contactsNewItemTop, width(), lang(lng_add_contact_button)); - } - yFrom -= _newItemHeight; - yTo -= _newItemHeight; - p.translate(0, _newItemHeight); + if (_aboutHeight) { + auto infoTop = _allAdmins->bottomNoMargins() + st::contactsAllAdminsTop - st::lineWidth; + + auto infoRect = rtlrect(0, infoTop, width(), _aboutHeight - infoTop - st::contactsPadding.bottom(), width()); + p.fillRect(infoRect, st::contactsAboutBg); + auto dividerFillTop = rtlrect(0, infoRect.y(), width(), st::profileDividerTop.height(), width()); + st::profileDividerTop.fill(p, dividerFillTop); + auto dividerFillBottom = rtlrect(0, infoRect.y() + infoRect.height() - st::profileDividerBottom.height(), width(), st::profileDividerBottom.height(), width()); + st::profileDividerBottom.fill(p, dividerFillBottom); + + int aboutw = width() - st::contactsPadding.left() - st::contactsPadding.right(); + p.setPen(st::contactsAboutFg); + (_allAdmins->checked() ? _aboutAllAdmins : _aboutAdmins).draw(p, st::contactsPadding.left(), st::contactsAboutTop, aboutw); } + yFrom -= skip; + yTo -= skip; + p.translate(0, skip); if (!_contacts->isEmpty()) { auto i = _contacts->cfind(yFrom, _rowHeight); p.translate(0, (*i)->pos() * _rowHeight); @@ -1078,44 +1071,45 @@ void ContactsBox::Inner::paintEvent(QPaintEvent *e) { if ((*i)->pos() * _rowHeight >= yTo) { break; } - paintDialog(p, ms, (*i)->history()->peer, contactData(*i), (*i == _sel)); + auto selected = _pressed ? (*i == _pressed) : (*i == _selected); + paintDialog(p, ms, (*i)->history()->peer, contactData(*i), selected); p.translate(0, _rowHeight); } yFrom -= _contacts->size() * _rowHeight; yTo -= _contacts->size() * _rowHeight; } if (!_byUsername.isEmpty()) { - p.fillRect(0, 0, width(), st::searchedBarHeight, st::searchedBarBG->b); - p.setFont(st::searchedBarFont->f); - p.setPen(st::searchedBarColor->p); - p.drawText(QRect(0, 0, width(), st::searchedBarHeight), lang(lng_search_global_results), style::al_center); + p.fillRect(0, 0, width(), st::searchedBarHeight, st::searchedBarBg); + p.setFont(st::searchedBarFont); + p.setPen(st::searchedBarFg); + p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), lang(lng_search_global_results), style::al_center); yFrom -= st::searchedBarHeight; yTo -= st::searchedBarHeight; p.translate(0, st::searchedBarHeight); - int32 from = floorclamp(yFrom, _rowHeight, 0, _byUsername.size()); - int32 to = ceilclamp(yTo, _rowHeight, 0, _byUsername.size()); + auto from = floorclamp(yFrom, _rowHeight, 0, _byUsername.size()); + auto to = ceilclamp(yTo, _rowHeight, 0, _byUsername.size()); p.translate(0, from * _rowHeight); for (; from < to; ++from) { - paintDialog(p, ms, _byUsername[from], d_byUsername[from], (_byUsernameSel == from)); + auto selected = (_searchedPressed >= 0) ? (_searchedPressed == from) : (_searchedSelected == from); + paintDialog(p, ms, _byUsername[from], d_byUsername[from], selected); p.translate(0, _rowHeight); } } } else { QString text; - int32 skip = 0; + skip = 0; if (bot()) { text = lang((cDialogsReceived() && !_searching) ? (sharingBotGame() ? lng_bot_no_chats : lng_bot_no_groups) : lng_contacts_loading); } else if (_chat && _membersFilter == MembersFilter::Admins) { text = lang(lng_contacts_loading); - p.fillRect(0, 0, width(), _newItemHeight - st::contactsPadding.bottom() - st::lineWidth, st::contactsAboutBg); - p.fillRect(0, _newItemHeight - st::contactsPadding.bottom() - st::lineWidth, width(), st::lineWidth, st::shadowColor); - p.setPen(st::black); - p.drawTextLeft(st::contactsPadding.left(), st::contactsNewItemTop, width(), lang(lng_chat_all_members_admins)); + p.fillRect(0, 0, width(), _aboutHeight - st::contactsPadding.bottom() - st::lineWidth, st::contactsAboutBg); + p.fillRect(0, _aboutHeight - st::contactsPadding.bottom() - st::lineWidth, width(), st::lineWidth, st::shadowFg); + int aboutw = width() - st::contactsPadding.left() - st::contactsPadding.right(); - (_allAdmins.checked() ? _aboutAllAdmins : _aboutAdmins).draw(p, st::contactsPadding.left(), st::contactsNewItemHeight + st::contactsAboutTop, aboutw); - p.translate(0, _newItemHeight); + (_allAdmins->checked() ? _aboutAllAdmins : _aboutAdmins).draw(p, st::contactsPadding.left(), st::contactsAboutTop, aboutw); + p.translate(0, _aboutHeight); } else if (cContactsReceived() && !_searching) { text = lang(lng_no_contacts); skip = st::noContactsFont->height; @@ -1128,8 +1122,8 @@ void ContactsBox::Inner::paintEvent(QPaintEvent *e) { } } else { if (_filtered.isEmpty() && _byUsernameFiltered.isEmpty()) { - p.setFont(st::noContactsFont->f); - p.setPen(st::noContactsColor->p); + p.setFont(st::noContactsFont); + p.setPen(st::noContactsColor); QString text; if (bot()) { text = lang((cDialogsReceived() && !_searching) ? (sharingBotGame() ? lng_bot_chats_not_found : lng_bot_groups_not_found) : lng_contacts_loading); @@ -1140,20 +1134,24 @@ void ContactsBox::Inner::paintEvent(QPaintEvent *e) { } p.drawText(QRect(0, 0, width(), st::noContactsHeight), text, style::al_center); } else { + yFrom -= skip; + yTo -= skip; + p.translate(0, skip); if (!_filtered.isEmpty()) { int32 from = floorclamp(yFrom, _rowHeight, 0, _filtered.size()); int32 to = ceilclamp(yTo, _rowHeight, 0, _filtered.size()); p.translate(0, from * _rowHeight); for (; from < to; ++from) { - paintDialog(p, ms, _filtered[from]->history()->peer, contactData(_filtered[from]), (_filteredSel == from)); + auto selected = (_filteredPressed >= 0) ? (_filteredPressed == from) : (_filteredSelected == from); + paintDialog(p, ms, _filtered[from]->history()->peer, contactData(_filtered[from]), selected); p.translate(0, _rowHeight); } } if (!_byUsernameFiltered.isEmpty()) { - p.fillRect(0, 0, width(), st::searchedBarHeight, st::searchedBarBG->b); - p.setFont(st::searchedBarFont->f); - p.setPen(st::searchedBarColor->p); - p.drawText(QRect(0, 0, width(), st::searchedBarHeight), lang(lng_search_global_results), style::al_center); + p.fillRect(0, 0, width(), st::searchedBarHeight, st::searchedBarBg); + p.setFont(st::searchedBarFont); + p.setPen(st::searchedBarFg); + p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), lang(lng_search_global_results), style::al_center); p.translate(0, st::searchedBarHeight); yFrom -= _filtered.size() * _rowHeight + st::searchedBarHeight; @@ -1162,7 +1160,8 @@ void ContactsBox::Inner::paintEvent(QPaintEvent *e) { int32 to = ceilclamp(yTo, _rowHeight, 0, _byUsernameFiltered.size()); p.translate(0, from * _rowHeight); for (; from < to; ++from) { - paintDialog(p, ms, _byUsernameFiltered[from], d_byUsernameFiltered[from], (_byUsernameSel == from)); + auto selected = (_searchedPressed >= 0) ? (_searchedPressed == from) : (_searchedSelected == from); + paintDialog(p, ms, _byUsernameFiltered[from], d_byUsernameFiltered[from], selected); p.translate(0, _rowHeight); } } @@ -1176,29 +1175,25 @@ void ContactsBox::Inner::enterEvent(QEvent *e) { int ContactsBox::Inner::getSelectedRowTop() const { if (_filter.isEmpty()) { - if (_sel) { - return _newItemHeight + (_sel->pos() * _rowHeight); - } else if (_byUsernameSel >= 0) { - return _newItemHeight + (_contacts->size() * _rowHeight) + st::searchedBarHeight + (_byUsernameSel * _rowHeight); + if (_selected) { + return _rowsTop + _aboutHeight + (_selected->pos() * _rowHeight); + } else if (_searchedSelected >= 0) { + return _rowsTop + _aboutHeight + (_contacts->size() * _rowHeight) + st::searchedBarHeight + (_searchedSelected * _rowHeight); } } else { - if (_filteredSel >= 0) { - return (_filteredSel * _rowHeight); - } else if (_byUsernameSel >= 0) { - return (_filtered.size() * _rowHeight + st::searchedBarHeight + _byUsernameSel * _rowHeight); + if (_filteredSelected >= 0) { + return _rowsTop + (_filteredSelected * _rowHeight); + } else if (_searchedSelected >= 0) { + return _rowsTop + (_filtered.size() * _rowHeight + st::searchedBarHeight + _searchedSelected * _rowHeight); } } return -1; } void ContactsBox::Inner::updateSelectedRow() { - if (_filter.isEmpty() && _newItemSel) { - update(0, 0, width(), st::contactsNewItemHeight); - } else { - auto rowTop = getSelectedRowTop(); - if (rowTop >= 0) { - updateRowWithTop(rowTop); - } + auto rowTop = getSelectedRowTop(); + if (rowTop >= 0) { + updateRowWithTop(rowTop); } } @@ -1210,23 +1205,23 @@ int ContactsBox::Inner::getRowTopWithPeer(PeerData *peer) const { if (_filter.isEmpty()) { for (auto i = _contacts->cbegin(), end = _contacts->cend(); i != end; ++i) { if ((*i)->history()->peer == peer) { - return _newItemHeight + ((*i)->pos() * _rowHeight); + return _rowsTop + _aboutHeight + ((*i)->pos() * _rowHeight); } } for (auto i = 0, count = _byUsername.size(); i != count; ++i) { if (_byUsername[i] == peer) { - return _newItemHeight + (_contacts->size() * _rowHeight) + st::searchedBarHeight + (i * _rowHeight); + return _rowsTop + _aboutHeight + (_contacts->size() * _rowHeight) + st::searchedBarHeight + (i * _rowHeight); } } } else { for (auto i = 0, count = _filtered.size(); i != count; ++i) { if (_filtered[i]->history()->peer == peer) { - return (i * _rowHeight); + return _rowsTop + (i * _rowHeight); } } for (auto i = 0, count = _byUsernameFiltered.size(); i != count; ++i) { if (_byUsernameFiltered[i] == peer) { - return (_contacts->size() * _rowHeight) + st::searchedBarHeight + (i * _rowHeight); + return _rowsTop + (_contacts->size() * _rowHeight) + st::searchedBarHeight + (i * _rowHeight); } } } @@ -1241,54 +1236,132 @@ void ContactsBox::Inner::updateRowWithPeer(PeerData *peer) { } void ContactsBox::Inner::leaveEvent(QEvent *e) { - _mouseSel = false; + _mouseSelection = false; setMouseTracking(false); - if (_newItemSel || _sel || _filteredSel >= 0 || _byUsernameSel >= 0) { + if (_selected || _filteredSelected >= 0 || _searchedSelected >= 0) { updateSelectedRow(); - _sel = 0; - _newItemSel = false; - _filteredSel = _byUsernameSel = -1; + _selected = nullptr; + _filteredSelected = _searchedSelected = -1; } } void ContactsBox::Inner::mouseMoveEvent(QMouseEvent *e) { - _mouseSel = true; + _mouseSelection = true; _lastMousePos = e->globalPos(); updateSelection(); } void ContactsBox::Inner::mousePressEvent(QMouseEvent *e) { - _mouseSel = true; + _mouseSelection = true; _lastMousePos = e->globalPos(); updateSelection(); - if (e->button() == Qt::LeftButton) { - chooseParticipant(); + + setPressed(_selected); + setFilteredPressed(_filteredSelected); + setSearchedPressed(_searchedSelected); + if (_selected) { + addRipple(_selected->history()->peer, contactData(_selected)); + } else if (_filteredSelected >= 0 && _filteredSelected < _filtered.size()) { + addRipple(_filtered[_filteredSelected]->history()->peer, contactData(_filtered[_filteredSelected])); + } else if (_searchedSelected >= 0) { + if (_filter.isEmpty() && _searchedSelected < d_byUsername.size()) { + addRipple(_byUsername[_searchedSelected], d_byUsername[_searchedSelected]); + } else if (!_filter.isEmpty() && _searchedSelected < d_byUsernameFiltered.size()) { + addRipple(_byUsernameFiltered[_searchedSelected], d_byUsernameFiltered[_searchedSelected]); + } } } +void ContactsBox::Inner::mouseReleaseEvent(QMouseEvent *e) { + auto pressed = _pressed; + setPressed(nullptr); + auto filteredPressed = _filteredPressed; + setFilteredPressed(-1); + auto searchedPressed = _searchedPressed; + setSearchedPressed(-1); + updateSelectedRow(); + if (e->button() == Qt::LeftButton) { + if (pressed && pressed == _selected) { + chooseParticipant(); + } else if (filteredPressed >= 0 && filteredPressed == _filteredSelected) { + chooseParticipant(); + } else if (searchedPressed >= 0 && searchedPressed == _searchedSelected) { + chooseParticipant(); + } + } +} + +void ContactsBox::Inner::addRipple(PeerData *peer, ContactData *data) { + if (isRowDisabled(peer, data)) return; + + auto rowTop = getSelectedRowTop(); + if (!data->ripple) { + auto mask = Ui::RippleAnimation::rectMask(QSize(width(), _rowHeight)); + data->ripple = std_::make_unique(st::contactsRipple, std_::move(mask), [this, data] { + updateRowWithTop(data->rippleRowTop); + }); + } + data->rippleRowTop = rowTop; + data->ripple->add(mapFromGlobal(QCursor::pos()) - QPoint(0, rowTop)); +} + +void ContactsBox::Inner::stopLastRipple(ContactData *data) { + if (data->ripple) { + data->ripple->lastStop(); + } +} + +void ContactsBox::Inner::setPressed(Dialogs::Row *pressed) { + if (_pressed != pressed) { + if (_pressed) { + stopLastRipple(contactData(_pressed)); + } + _pressed = pressed; + } +} + +void ContactsBox::Inner::setFilteredPressed(int pressed) { + if (_filteredPressed >= 0 && _filteredPressed < _filtered.size()) { + stopLastRipple(contactData(_filtered[_filteredPressed])); + } + _filteredPressed = pressed; +} + +void ContactsBox::Inner::setSearchedPressed(int pressed) { + if (_searchedPressed >= 0) { + if (_searchedPressed < d_byUsername.size()) { + stopLastRipple(d_byUsername[_searchedPressed]); + } + if (_searchedPressed < d_byUsernameFiltered.size()) { + stopLastRipple(d_byUsernameFiltered[_searchedPressed]); + } + } + _searchedPressed = pressed; +} + void ContactsBox::Inner::chooseParticipant() { if (_saving) return; bool addingAdmin = (_channel && _membersFilter == MembersFilter::Admins); if (!addingAdmin && usingMultiSelect()) { _time = unixtime(); if (_filter.isEmpty()) { - if (_byUsernameSel >= 0 && _byUsernameSel < _byUsername.size()) { - auto data = d_byUsername[_byUsernameSel]; - auto peer = _byUsername[_byUsernameSel]; + if (_searchedSelected >= 0 && _searchedSelected < _byUsername.size()) { + auto data = d_byUsername[_searchedSelected]; + auto peer = _byUsername[_searchedSelected]; if (data->disabledChecked) return; changeCheckState(data, peer); - } else if (_sel) { - auto data = contactData(_sel); - auto peer = _sel->history()->peer; + } else if (_selected) { + auto data = contactData(_selected); + auto peer = _selected->history()->peer; if (data->disabledChecked) return; - changeCheckState(_sel); + changeCheckState(_selected); } } else { - if (_byUsernameSel >= 0 && _byUsernameSel < _byUsernameFiltered.size()) { - auto data = d_byUsernameFiltered[_byUsernameSel]; - auto peer = _byUsernameFiltered[_byUsernameSel]; + if (_searchedSelected >= 0 && _searchedSelected < _byUsernameFiltered.size()) { + auto data = d_byUsernameFiltered[_searchedSelected]; + auto peer = _byUsernameFiltered[_searchedSelected]; if (data->disabledChecked) return; int i = 0, l = d_byUsername.size(); @@ -1311,9 +1384,9 @@ void ContactsBox::Inner::chooseParticipant() { } changeCheckState(data, peer); - } else if (_filteredSel >= 0 && _filteredSel < _filtered.size()) { - auto data = contactData(_filtered[_filteredSel]); - auto peer = _filtered[_filteredSel]->history()->peer; + } else if (_filteredSelected >= 0 && _filteredSelected < _filtered.size()) { + auto data = contactData(_filtered[_filteredSelected]); + auto peer = _filtered[_filteredSelected]->history()->peer; if (data->disabledChecked) return; changeCheckState(data, peer); @@ -1322,21 +1395,17 @@ void ContactsBox::Inner::chooseParticipant() { } else { PeerData *peer = 0; if (_filter.isEmpty()) { - if (_newItemSel) { - emit addRequested(); - return; - } - if (_byUsernameSel >= 0 && _byUsernameSel < _byUsername.size()) { - peer = _byUsername[_byUsernameSel]; - } else if (_sel) { - peer = _sel->history()->peer; + if (_searchedSelected >= 0 && _searchedSelected < _byUsername.size()) { + peer = _byUsername[_searchedSelected]; + } else if (_selected) { + peer = _selected->history()->peer; } } else { - if (_byUsernameSel >= 0 && _byUsernameSel < _byUsernameFiltered.size()) { - peer = _byUsernameFiltered[_byUsernameSel]; + if (_searchedSelected >= 0 && _searchedSelected < _byUsernameFiltered.size()) { + peer = _byUsernameFiltered[_searchedSelected]; } else { - if (_filteredSel < 0 || _filteredSel >= _filtered.size()) return; - peer = _filtered[_filteredSel]->history()->peer; + if (_filteredSelected < 0 || _filteredSelected >= _filtered.size()) return; + peer = _filtered[_filteredSelected]->history()->peer; } } if (peer) { @@ -1347,10 +1416,10 @@ void ContactsBox::Inner::chooseParticipant() { _addAdminRequestId = 0; } if (_addAdminBox) _addAdminBox->deleteLater(); - _addAdminBox = new ConfirmBox(lng_channel_admin_sure(lt_user, _addAdmin->firstName)); - connect(_addAdminBox, SIGNAL(confirmed()), this, SLOT(onAddAdmin())); - connect(_addAdminBox, SIGNAL(destroyed(QObject*)), this, SLOT(onNoAddAdminBox(QObject*))); - Ui::showLayer(_addAdminBox, KeepOtherLayers); + _addAdminBox = Ui::show(Box(lng_channel_admin_sure(lt_user, _addAdmin->firstName), base::lambda_guarded(this, [this] { + if (_addAdminRequestId) return; + _addAdminRequestId = MTP::send(MTPchannels_EditAdmin(_channel->inputChannel, _addAdmin->inputUser, MTP_channelRoleEditor()), rpcDone(&Inner::addAdminDone), rpcFail(&Inner::addAdminFail)); + })), KeepOtherLayers); } else if (sharingBotGame()) { _addToPeer = peer; auto confirmText = [peer] { @@ -1359,14 +1428,14 @@ void ContactsBox::Inner::chooseParticipant() { } return lng_bot_sure_share_game_group(lt_group, peer->name); }; - auto box = std_::make_unique(confirmText()); - connect(box.get(), SIGNAL(confirmed()), this, SLOT(onAddBot())); - Ui::showLayer(box.release(), KeepOtherLayers); + Ui::show(Box(confirmText(), base::lambda_guarded(this, [this] { + addBot(); + })), KeepOtherLayers); } else if (bot() && (peer->isChat() || peer->isMegagroup())) { _addToPeer = peer; - auto box = std_::make_unique(lng_bot_sure_invite(lt_group, peer->name)); - connect(box.get(), SIGNAL(confirmed()), this, SLOT(onAddBot())); - Ui::showLayer(box.release(), KeepOtherLayers); + Ui::show(Box(lng_bot_sure_invite(lt_group, peer->name), base::lambda_guarded(this, [this] { + addBot(); + })), KeepOtherLayers); } else { Ui::hideSettingsAndLayer(true); App::main()->choosePeer(peer->id, ShowAtUnreadMsgId); @@ -1383,15 +1452,15 @@ void ContactsBox::Inner::changeCheckState(Dialogs::Row *row) { void ContactsBox::Inner::changeCheckState(ContactData *data, PeerData *peer) { t_assert(usingMultiSelect()); - if (_chat && _membersFilter == MembersFilter::Admins && _allAdmins.checked()) { + if (isRowDisabled(peer, data)) { } else if (data->checkbox->checked()) { changePeerCheckState(data, peer, false); } else if (selectedCount() < ((_channel && _channel->isMegagroup()) ? Global::MegagroupSizeMax() : Global::ChatSizeMax())) { changePeerCheckState(data, peer, true); } else if (_channel && !_channel->isMegagroup()) { - Ui::showLayer(new MaxInviteBox(_channel->inviteLink()), KeepOtherLayers); + Ui::show(Box(_channel->inviteLink()), KeepOtherLayers); } else if (!_channel && selectedCount() >= Global::ChatSizeMax() && selectedCount() < Global::MegagroupSizeMax()) { - Ui::showLayer(new InformBox(lng_profile_add_more_after_upgrade(lt_count, Global::MegagroupSizeMax())), KeepOtherLayers); + Ui::show(Box(lng_profile_add_more_after_upgrade(lt_count, Global::MegagroupSizeMax())), KeepOtherLayers); } } @@ -1401,7 +1470,7 @@ void ContactsBox::Inner::peerUnselected(PeerData *peer) { changePeerCheckState(data, peer, false, ChangeStateWay::SkipCallback); } -void ContactsBox::Inner::setPeerSelectedChangedCallback(base::lambda_unique callback) { +void ContactsBox::Inner::setPeerSelectedChangedCallback(base::lambda &&callback) { _peerSelectedChangedCallback = std_::move(callback); } @@ -1419,7 +1488,7 @@ void ContactsBox::Inner::changePeerCheckState(ContactData *data, PeerData *peer, } } -int32 ContactsBox::Inner::selectedCount() const { +int ContactsBox::Inner::selectedCount() const { auto result = _checkedContacts.size(); if (_chat) { result += qMax(_chat->count, 1); @@ -1431,37 +1500,43 @@ int32 ContactsBox::Inner::selectedCount() const { return result; } -void ContactsBox::Inner::updateSelection() { - if (!_mouseSel) return; +void ContactsBox::Inner::setVisibleTopBottom(int visibleTop, int visibleBottom) { + _visibleTop = visibleTop; + _visibleBottom = visibleBottom; + loadProfilePhotos(); +} - QPoint p(mapFromGlobal(_lastMousePos)); - bool in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(_lastMousePos)); +void ContactsBox::Inner::updateSelection() { + if (!_mouseSelection) return; + + auto p = mapFromGlobal(_lastMousePos); + auto in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(_lastMousePos)); + p.setY(p.y() - _rowsTop); if (_filter.isEmpty()) { - bool newItemSel = false; - if (_newItemHeight) { - if (in && (p.y() >= 0) && (p.y() < _newItemHeight) && !(_chat && _membersFilter == MembersFilter::Admins)) { - newItemSel = true; - } - p.setY(p.y() - _newItemHeight); + _filteredSelected = -1; + setFilteredPressed(-1); + if (_aboutHeight) { + p.setY(p.y() - _aboutHeight); } - Dialogs::Row *newSel = (in && !newItemSel && (p.y() >= 0) && (p.y() < _contacts->size() * _rowHeight)) ? _contacts->rowAtY(p.y(), _rowHeight) : nullptr; - int32 byUsernameSel = (in && !newItemSel && p.y() >= _contacts->size() * _rowHeight + st::searchedBarHeight) ? ((p.y() - _contacts->size() * _rowHeight - st::searchedBarHeight) / _rowHeight) : -1; - if (byUsernameSel >= _byUsername.size()) byUsernameSel = -1; - if (newSel != _sel || byUsernameSel != _byUsernameSel || newItemSel != _newItemSel) { + auto selected = (in && (p.y() >= 0) && (p.y() < _contacts->size() * _rowHeight)) ? _contacts->rowAtY(p.y(), _rowHeight) : nullptr; + auto searchedSelected = (in && (p.y() >= _contacts->size() * _rowHeight + st::searchedBarHeight)) ? ((p.y() - _contacts->size() * _rowHeight - st::searchedBarHeight) / _rowHeight) : -1; + if (searchedSelected >= _byUsername.size()) searchedSelected = -1; + if (_selected != selected || _searchedSelected != searchedSelected) { updateSelectedRow(); - _newItemSel = newItemSel; - _sel = newSel; - _byUsernameSel = byUsernameSel; + _selected = selected; + _searchedSelected = searchedSelected; updateSelectedRow(); } } else { - int32 newFilteredSel = (in && p.y() >= 0 && p.y() < _filtered.size() * _rowHeight) ? (p.y() / _rowHeight) : -1; - int32 byUsernameSel = (in && p.y() >= _filtered.size() * _rowHeight + st::searchedBarHeight) ? ((p.y() - _filtered.size() * _rowHeight - st::searchedBarHeight) / _rowHeight) : -1; - if (byUsernameSel >= _byUsernameFiltered.size()) byUsernameSel = -1; - if (newFilteredSel != _filteredSel || byUsernameSel != _byUsernameSel) { + _selected = nullptr; + setPressed(nullptr); + auto filteredSelected = (in && (p.y() >= 0) && (p.y() < _filtered.size() * _rowHeight)) ? (p.y() / _rowHeight) : -1; + auto searchedSelected = (in && (p.y() >= _filtered.size() * _rowHeight + st::searchedBarHeight)) ? ((p.y() - _filtered.size() * _rowHeight - st::searchedBarHeight) / _rowHeight) : -1; + if (searchedSelected >= _byUsernameFiltered.size()) searchedSelected = -1; + if (_filteredSelected != filteredSelected || _searchedSelected != searchedSelected) { updateSelectedRow(); - _filteredSel = newFilteredSel; - _byUsernameSel = byUsernameSel; + _filteredSelected = filteredSelected; + _searchedSelected = searchedSelected; updateSelectedRow(); } } @@ -1490,17 +1565,19 @@ void ContactsBox::Inner::updateFilter(QString filter) { _byUsernameFiltered.clear(); d_byUsernameFiltered.clear(); - for (int i = 0, l = _byUsernameDatas.size(); i < l; ++i) { - delete _byUsernameDatas[i]; - } - _byUsernameDatas.clear(); + clearSearchedContactDatas(); + _selected = nullptr; + setPressed(nullptr); + _filteredSelected = -1; + setFilteredPressed(-1); + _searchedSelected = -1; + setSearchedPressed(-1); if (_filter.isEmpty()) { - _sel = 0; refresh(); } else { - if (!_addContactLnk.isHidden()) _addContactLnk.hide(); - if (!_allAdmins.isHidden()) _allAdmins.hide(); + if (!_addContactLnk->isHidden()) _addContactLnk->hide(); + if (!_allAdmins->isHidden()) _allAdmins->hide(); QStringList::const_iterator fb = f.cbegin(), fe = f.cend(), fi; _filtered.clear(); @@ -1563,21 +1640,19 @@ void ContactsBox::Inner::updateFilter(QString filter) { } } } - _filteredSel = -1; if (!_filtered.isEmpty()) { - for (_filteredSel = 0; (_filteredSel < _filtered.size()) && contactData(_filtered[_filteredSel])->disabledChecked;) { - ++_filteredSel; + for (_filteredSelected = 0; (_filteredSelected < _filtered.size()) && contactData(_filtered[_filteredSelected])->disabledChecked;) { + ++_filteredSelected; } - if (_filteredSel == _filtered.size()) _filteredSel = -1; + if (_filteredSelected == _filtered.size()) _filteredSelected = -1; } - _byUsernameSel = -1; - if (_filteredSel < 0 && !_byUsernameFiltered.isEmpty()) { - for (_byUsernameSel = 0; (_byUsernameSel < _byUsernameFiltered.size()) && d_byUsernameFiltered[_byUsernameSel]->disabledChecked;) { - ++_byUsernameSel; + if (_filteredSelected < 0 && !_byUsernameFiltered.isEmpty()) { + for (_searchedSelected = 0; (_searchedSelected < _byUsernameFiltered.size()) && d_byUsernameFiltered[_searchedSelected]->disabledChecked;) { + ++_searchedSelected; } - if (_byUsernameSel == _byUsernameFiltered.size()) _byUsernameSel = -1; + if (_searchedSelected == _byUsernameFiltered.size()) _searchedSelected = -1; } - _mouseSel = false; + _mouseSelection = false; refresh(); if ((!bot() || sharingBotGame()) && (!_chat || _membersFilter != MembersFilter::Admins)) { @@ -1586,13 +1661,19 @@ void ContactsBox::Inner::updateFilter(QString filter) { } } update(); - loadProfilePhotos(0); + loadProfilePhotos(); + } +} + +void ContactsBox::Inner::clearSearchedContactDatas() { + for (auto data : base::take(_byUsernameDatas)) { + delete data; } } void ContactsBox::Inner::onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow) { if (!_filter.isEmpty()) { - for (FilteredDialogs::iterator i = _filtered.begin(), e = _filtered.end(); i != e;) { + for (auto i = _filtered.begin(), e = _filtered.end(); i != e;) { if (*i == oldRow) { // this row is shown in filtered and maybe is in contacts! if (newRow) { *i = newRow; @@ -1604,18 +1685,21 @@ void ContactsBox::Inner::onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row ++i; } } - if (_filteredSel >= _filtered.size()) { - _filteredSel = -1; + if (_filteredSelected >= _filtered.size()) { + _filteredSelected = -1; + } + if (_filteredPressed >= _filtered.size()) { + _filteredPressed = -1; } } else { - if (_sel == oldRow) { - _sel = newRow; + if (_selected == oldRow) { + _selected = newRow; + } + if (_pressed == oldRow) { + setPressed(newRow); } } - _mouseSel = false; - int cnt = (_filter.isEmpty() ? _contacts->size() : _filtered.size()); - int newh = cnt ? (cnt * _rowHeight) : st::noContactsHeight; - resize(width(), newh); + refresh(); } void ContactsBox::Inner::peopleReceived(const QString &query, const QVector &people) { @@ -1664,7 +1748,7 @@ void ContactsBox::Inner::peopleReceived(const QString &query, const QVectorcheckbox->setChecked(true, Ui::RoundImageCheckbox::SetStyle::Fast); } - data->name.setText(st::contactsNameFont, peer->name, _textNameOptions); + data->name.setText(st::contactsNameStyle, peer->name, _textNameOptions); data->statusText = '@' + peer->userName(); _byUsernameFiltered.push_back(peer); @@ -1678,33 +1762,34 @@ void ContactsBox::Inner::peopleReceived(const QString &query, const QVectorisHidden()) _allAdmins->show(); } else { - if (!_allAdmins.isHidden()) _allAdmins.hide(); + if (!_allAdmins->isHidden()) _allAdmins->hide(); } if (!_contacts->isEmpty() || !_byUsername.isEmpty()) { - if (!_addContactLnk.isHidden()) _addContactLnk.hide(); - resize(width(), _newItemHeight + (_contacts->size() * _rowHeight) + (_byUsername.isEmpty() ? 0 : (st::searchedBarHeight + _byUsername.size() * _rowHeight))); + if (!_addContactLnk->isHidden()) _addContactLnk->hide(); + resize(width(), _rowsTop + _aboutHeight + (_contacts->size() * _rowHeight) + (_byUsername.isEmpty() ? 0 : (st::searchedBarHeight + _byUsername.size() * _rowHeight)) + st::contactsMarginBottom); } else if (_chat && _membersFilter == MembersFilter::Admins) { - if (!_addContactLnk.isHidden()) _addContactLnk.hide(); - resize(width(), _newItemHeight + st::noContactsHeight); + if (!_addContactLnk->isHidden()) _addContactLnk->hide(); + resize(width(), _rowsTop + _aboutHeight + st::noContactsHeight + st::contactsMarginBottom); } else { if (cContactsReceived() && !bot()) { - if (_addContactLnk.isHidden()) _addContactLnk.show(); + if (_addContactLnk->isHidden()) _addContactLnk->show(); } else { - if (!_addContactLnk.isHidden()) _addContactLnk.hide(); + if (!_addContactLnk->isHidden()) _addContactLnk->hide(); } resize(width(), st::noContactsHeight); } } else { - if (!_allAdmins.isHidden()) _allAdmins.hide(); + if (!_allAdmins->isHidden()) _allAdmins->hide(); if (_filtered.isEmpty() && _byUsernameFiltered.isEmpty()) { - if (!_addContactLnk.isHidden()) _addContactLnk.hide(); + if (!_addContactLnk->isHidden()) _addContactLnk->hide(); resize(width(), st::noContactsHeight); } else { - resize(width(), (_filtered.size() * _rowHeight) + (_byUsernameFiltered.isEmpty() ? 0 : (st::searchedBarHeight + _byUsernameFiltered.size() * _rowHeight))); + resize(width(), _rowsTop + (_filtered.size() * _rowHeight) + (_byUsernameFiltered.isEmpty() ? 0 : (st::searchedBarHeight + _byUsernameFiltered.size() * _rowHeight)) + st::contactsMarginBottom); } } + loadProfilePhotos(); update(); } @@ -1736,6 +1821,10 @@ ContactsBox::Inner::~Inner() { for (auto contactData : base::take(_contactsData)) { delete contactData; } + clearSearchedContactDatas(); + for (auto data : base::take(d_byUsername)) { + delete data; + } if (_bot) { if (auto &info = _bot->botInfo) { info->startGroupToken = QString(); @@ -1745,141 +1834,133 @@ ContactsBox::Inner::~Inner() { } void ContactsBox::Inner::resizeEvent(QResizeEvent *e) { - _addContactLnk.move((width() - _addContactLnk.width()) / 2, (st::noContactsHeight + st::noContactsFont->height) / 2); - _allAdmins.moveToLeft(st::contactsPadding.left(), st::contactsNewItemTop); + _addContactLnk->move((width() - _addContactLnk->width()) / 2, (st::noContactsHeight + st::noContactsFont->height) / 2); + _allAdmins->moveToLeft(st::contactsPadding.left(), st::contactsAllAdminsTop); } void ContactsBox::Inner::selectSkip(int32 dir) { _time = unixtime(); - _mouseSel = false; + _mouseSelection = false; if (_filter.isEmpty()) { int cur = 0; - if (_sel) { - for (auto i = _contacts->cbegin(); *i != _sel; ++i) { + if (_selected) { + for (auto i = _contacts->cbegin(); *i != _selected; ++i) { ++cur; } - if (_newItemHeight) ++cur; - } else if (_byUsernameSel >= 0) { - cur = (_contacts->size() + _byUsernameSel); - if (_newItemHeight) ++cur; - } else if (!_newItemSel) { + } else if (_searchedSelected >= 0) { + cur = (_contacts->size() + _searchedSelected); + } else { cur = -1; } cur += dir; if (cur <= 0) { - _newItemSel = (_chat && _membersFilter == MembersFilter::Admins) ? false : (_newItemHeight ? true : false); - _sel = (!_newItemHeight && !_contacts->isEmpty()) ? *_contacts->cbegin() : nullptr; - _byUsernameSel = (!_newItemHeight && _contacts->isEmpty() && !_byUsername.isEmpty()) ? 0 : -1; - } else if (cur >= _contacts->size() + (_newItemHeight ? 1 : 0)) { - _newItemSel = false; + _selected = (!_contacts->isEmpty()) ? *_contacts->cbegin() : nullptr; + _searchedSelected = (_contacts->isEmpty() && !_byUsername.isEmpty()) ? 0 : -1; + } else if (cur >= _contacts->size()) { if (_byUsername.isEmpty()) { - _sel = _contacts->isEmpty() ? nullptr : *(_contacts->cend() - 1); - _byUsernameSel = -1; + _selected = _contacts->isEmpty() ? nullptr : *(_contacts->cend() - 1); + _searchedSelected = -1; } else { - _sel = nullptr; - _byUsernameSel = cur - _contacts->size(); - if (_byUsernameSel >= _byUsername.size()) _byUsernameSel = _byUsername.size() - 1; + _selected = nullptr; + _searchedSelected = cur - _contacts->size(); + if (_searchedSelected >= _byUsername.size()) _searchedSelected = _byUsername.size() - 1; } } else { - _newItemSel = false; - if (_newItemHeight) --cur; for (auto i = _contacts->cbegin(); ; ++i) { - _sel = *i; + _selected = *i; if (!cur) { break; } else { --cur; } } - _byUsernameSel = -1; + _searchedSelected = -1; } if (dir > 0) { - for (auto i = _contacts->cfind(_sel), end = _contacts->cend(); i != end && contactData(*i)->disabledChecked; ++i) { - _sel = *i; + for (auto i = _contacts->cfind(_selected), end = _contacts->cend(); i != end && contactData(*i)->disabledChecked; ++i) { + _selected = *i; } - if (_sel && contactData(_sel)->disabledChecked) { - _sel = nullptr; + if (_selected && contactData(_selected)->disabledChecked) { + _selected = nullptr; } - if (!_sel) { + if (!_selected) { if (!_byUsername.isEmpty()) { - if (_byUsernameSel < 0) _byUsernameSel = 0; - for (; _byUsernameSel < _byUsername.size() && d_byUsername[_byUsernameSel]->disabledChecked;) { - ++_byUsernameSel; + if (_searchedSelected < 0) _searchedSelected = 0; + for (; _searchedSelected < _byUsername.size() && d_byUsername[_searchedSelected]->disabledChecked;) { + ++_searchedSelected; } - if (_byUsernameSel == _byUsername.size()) _byUsernameSel = -1; + if (_searchedSelected == _byUsername.size()) _searchedSelected = -1; } } } else { - while (_byUsernameSel >= 0 && d_byUsername[_byUsernameSel]->disabledChecked) { - --_byUsernameSel; + while (_searchedSelected >= 0 && d_byUsername[_searchedSelected]->disabledChecked) { + --_searchedSelected; } - if (_byUsernameSel < 0) { + if (_searchedSelected < 0) { if (!_contacts->isEmpty()) { - if (!_newItemSel && !_sel) _sel = *(_contacts->cend() - 1); - if (_sel) { - for (auto i = _contacts->cfind(_sel), b = _contacts->cbegin(); i != b && contactData(*i)->disabledChecked; --i) { - _sel = *i; + if (!_selected) _selected = *(_contacts->cend() - 1); + if (_selected) { + for (auto i = _contacts->cfind(_selected), b = _contacts->cbegin(); i != b && contactData(*i)->disabledChecked; --i) { + _selected = *i; } - if (contactData(_sel)->disabledChecked) { - _sel = nullptr; + if (contactData(_selected)->disabledChecked) { + _selected = nullptr; } } } } } - if (_newItemSel) { - emit mustScrollTo(0, _newItemHeight); - } else if (_sel) { - emit mustScrollTo(_newItemHeight + _sel->pos() * _rowHeight, _newItemHeight + (_sel->pos() + 1) * _rowHeight); - } else if (_byUsernameSel >= 0) { - emit mustScrollTo(_newItemHeight + (_contacts->size() + _byUsernameSel) * _rowHeight + st::searchedBarHeight, _newItemHeight + (_contacts->size() + _byUsernameSel + 1) * _rowHeight + st::searchedBarHeight); + if (_selected) { + emit mustScrollTo(_rowsTop + _aboutHeight + _selected->pos() * _rowHeight, _rowsTop + _aboutHeight + (_selected->pos() + 1) * _rowHeight); + } else if (_searchedSelected >= 0) { + emit mustScrollTo(_rowsTop + _aboutHeight + (_contacts->size() + _searchedSelected) * _rowHeight + st::searchedBarHeight, _rowsTop + _aboutHeight + (_contacts->size() + _searchedSelected + 1) * _rowHeight + st::searchedBarHeight); } } else { - int cur = (_filteredSel >= 0) ? _filteredSel : ((_byUsernameSel >= 0) ? (_filtered.size() + _byUsernameSel) : -1); + int cur = (_filteredSelected >= 0) ? _filteredSelected : ((_searchedSelected >= 0) ? (_filtered.size() + _searchedSelected) : -1); cur += dir; if (cur <= 0) { - _filteredSel = _filtered.isEmpty() ? -1 : 0; - _byUsernameSel = (_filtered.isEmpty() && !_byUsernameFiltered.isEmpty()) ? 0 : -1; + _filteredSelected = _filtered.isEmpty() ? -1 : 0; + _searchedSelected = (_filtered.isEmpty() && !_byUsernameFiltered.isEmpty()) ? 0 : -1; } else if (cur >= _filtered.size()) { - _filteredSel = -1; - _byUsernameSel = cur - _filtered.size(); - if (_byUsernameSel >= _byUsernameFiltered.size()) _byUsernameSel = _byUsernameFiltered.size() - 1; + _filteredSelected = -1; + _searchedSelected = cur - _filtered.size(); + if (_searchedSelected >= _byUsernameFiltered.size()) _searchedSelected = _byUsernameFiltered.size() - 1; } else { - _filteredSel = cur; - _byUsernameSel = -1; + _filteredSelected = cur; + _searchedSelected = -1; } if (dir > 0) { - while (_filteredSel >= 0 && _filteredSel < _filtered.size() && contactData(_filtered[_filteredSel])->disabledChecked) { - ++_filteredSel; + while (_filteredSelected >= 0 && _filteredSelected < _filtered.size() && contactData(_filtered[_filteredSelected])->disabledChecked) { + ++_filteredSelected; } - if (_filteredSel < 0 || _filteredSel >= _filtered.size()) { - _filteredSel = -1; + if (_filteredSelected < 0 || _filteredSelected >= _filtered.size()) { + _filteredSelected = -1; if (!_byUsernameFiltered.isEmpty()) { - if (_byUsernameSel < 0) _byUsernameSel = 0; - for (; _byUsernameSel < _byUsernameFiltered.size() && d_byUsernameFiltered[_byUsernameSel]->disabledChecked;) { - ++_byUsernameSel; + if (_searchedSelected < 0) _searchedSelected = 0; + for (; _searchedSelected < _byUsernameFiltered.size() && d_byUsernameFiltered[_searchedSelected]->disabledChecked;) { + ++_searchedSelected; } - if (_byUsernameSel == _byUsernameFiltered.size()) _byUsernameSel = -1; + if (_searchedSelected == _byUsernameFiltered.size()) _searchedSelected = -1; } } } else { - while (_byUsernameSel >= 0 && d_byUsernameFiltered[_byUsernameSel]->disabledChecked) { - --_byUsernameSel; + while (_searchedSelected >= 0 && d_byUsernameFiltered[_searchedSelected]->disabledChecked) { + --_searchedSelected; } - if (_byUsernameSel < 0) { + if (_searchedSelected < 0) { if (!_filtered.isEmpty()) { - if (_filteredSel < 0) _filteredSel = _filtered.size() - 1; - for (; _filteredSel >= 0 && contactData(_filtered[_filteredSel])->disabledChecked;) { - --_filteredSel; + if (_filteredSelected < 0) _filteredSelected = _filtered.size() - 1; + for (; _filteredSelected >= 0 && contactData(_filtered[_filteredSelected])->disabledChecked;) { + --_filteredSelected; } } } } - if (_filteredSel >= 0) { - emit mustScrollTo(_filteredSel * _rowHeight, (_filteredSel + 1) * _rowHeight); - } else if (_byUsernameSel >= 0) { + if (_filteredSelected >= 0) { + emit mustScrollTo(_rowsTop + _filteredSelected * _rowHeight, _rowsTop + (_filteredSelected + 1) * _rowHeight); + } else if (_searchedSelected >= 0) { int skip = _filtered.size() * _rowHeight + st::searchedBarHeight; - emit mustScrollTo(skip + _byUsernameSel * _rowHeight, skip + (_byUsernameSel + 1) * _rowHeight); + emit mustScrollTo(_rowsTop + skip + _searchedSelected * _rowHeight, _rowsTop + skip + (_searchedSelected + 1) * _rowHeight); } } update(); @@ -1940,3 +2021,7 @@ QVector ContactsBox::Inner::selectedInputs() { } return result; } + +bool ContactsBox::Inner::allAdmins() const { + return _allAdmins->checked(); +} diff --git a/Telegram/SourceFiles/boxes/contactsbox.h b/Telegram/SourceFiles/boxes/contactsbox.h index 0db72c0dd..830fe3c5c 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.h +++ b/Telegram/SourceFiles/boxes/contactsbox.h @@ -20,9 +20,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "abstractbox.h" +#include "boxes/abstractbox.h" #include "core/single_timer.h" -#include "ui/effects/round_image_checkbox.h" +#include "ui/effects/round_checkbox.h" #include "boxes/members_box.h" namespace Dialogs { @@ -31,76 +31,100 @@ class IndexedList; } // namespace Dialogs namespace Ui { +class RippleAnimation; +class LinkButton; +class Checkbox; class MultiSelect; template class WidgetSlideWrap; } // namespace Ui -QString cantInviteError(); +enum class PeerFloodType { + Send, + InviteGroup, + InviteChannel, +}; +QString PeerFloodErrorText(PeerFloodType type); inline Ui::RoundImageCheckbox::PaintRoundImage PaintUserpicCallback(PeerData *peer) { return [peer](Painter &p, int x, int y, int outerWidth, int size) { - peer->paintUserpicLeft(p, size, x, y, outerWidth); + peer->paintUserpicLeft(p, x, y, outerWidth, size); }; } -class ContactsBox : public ItemListBox, public RPCSender { +class ContactsBox : public BoxContent, public RPCSender { Q_OBJECT public: - ContactsBox(); - ContactsBox(const QString &name, const QImage &photo); // group creation - ContactsBox(ChannelData *channel); // channel setup - ContactsBox(ChannelData *channel, MembersFilter filter, const MembersAlreadyIn &already); - ContactsBox(ChatData *chat, MembersFilter filter); - ContactsBox(UserData *bot); + ContactsBox(QWidget*); + ContactsBox(QWidget*, const QString &name, const QImage &photo); // group creation + ContactsBox(QWidget*, ChannelData *channel); // channel setup + ContactsBox(QWidget*, ChannelData *channel, MembersFilter filter, const MembersAlreadyIn &already); + ContactsBox(QWidget*, ChatData *chat, MembersFilter filter); + ContactsBox(QWidget*, UserData *bot); + + void closeHook() override; signals: void adminAdded(); private slots: - void onScroll(); - - void onInvite(); - void onCreate(); - void onSaveAdmins(); - void onSubmit(); bool onSearchByUsername(bool searchCache = false); void onNeedSearchByUsername(); protected: + void prepare() override; + void setInnerFocus() override; + void keyPressEvent(QKeyEvent *e) override; - void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; - void closePressed() override; - void showAll() override; - void doSetInnerFocus() override; - private: - void init(); + object_ptr> createMultiSelect(); + + void updateTitle(); int getTopScrollSkip() const; void updateScrollSkips(); void onFilterUpdate(const QString &filter); void onPeerSelectedChanged(PeerData *peer, bool checked); void addPeerToMultiSelect(PeerData *peer, bool skipAnimation = false); - class Inner; - ChildWidget _inner; - ChildWidget> _select; - - BoxButton _next, _cancel; - MembersFilter _membersFilter; - - ScrollableBoxShadow _topShadow; - ScrollableBoxShadow *_bottomShadow = nullptr; + void saveChatAdmins(); + void inviteParticipants(); + void createGroup(); + // global search void peopleReceived(const MTPcontacts_Found &result, mtpRequestId req); bool peopleFailed(const RPCError &error, mtpRequestId req); - QTimer _searchTimer; + // saving admins + void saveAdminsDone(const MTPUpdates &result); + void saveSelectedAdmins(); + void getAdminsDone(const MTPmessages_ChatFull &result); + void setAdminDone(UserData *user, const MTPBool &result); + void removeAdminDone(UserData *user, const MTPBool &result); + bool saveAdminsFail(const RPCError &error); + bool editAdminFail(const RPCError &error); + + // group creation + void creationDone(const MTPUpdates &updates); + bool creationFail(const RPCError &e); + + ChatData *_chat = nullptr; + ChannelData *_channel = nullptr; + MembersFilter _membersFilter = MembersFilter::Recent; + UserData *_bot = nullptr; + CreatingGroupType _creating = CreatingGroupNone; + MembersAlreadyIn _alreadyIn; + + object_ptr> _select; + + class Inner; + QPointer _inner; + + object_ptr _searchTimer; QString _peopleQuery; bool _peopleFull; mtpRequestId _peopleRequest; @@ -113,26 +137,13 @@ private: mtpRequestId _saveRequestId = 0; - // saving admins - void saveAdminsDone(const MTPUpdates &result); - void saveSelectedAdmins(); - void getAdminsDone(const MTPmessages_ChatFull &result); - void setAdminDone(UserData *user, const MTPBool &result); - void removeAdminDone(UserData *user, const MTPBool &result); - bool saveAdminsFail(const RPCError &error); - bool editAdminFail(const RPCError &error); - - // group creation QString _creationName; QImage _creationPhoto; - void creationDone(const MTPUpdates &updates); - bool creationFail(const RPCError &e); - }; // This class is hold in header because it requires Qt preprocessing. -class ContactsBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber { +class ContactsBox::Inner : public TWidget, public RPCSender, private base::Subscriber { Q_OBJECT public: @@ -141,7 +152,7 @@ public: Inner(QWidget *parent, ChatData *chat, MembersFilter membersFilter); Inner(QWidget *parent, UserData *bot); - void setPeerSelectedChangedCallback(base::lambda_unique callback); + void setPeerSelectedChangedCallback(base::lambda &&callback); void peerUnselected(PeerData *peer); void updateFilter(QString filter = QString()); @@ -152,14 +163,11 @@ public: QVector selected(); QVector selectedInputs(); - bool allAdmins() const { - return _allAdmins.checked(); - } - void setAllAdminsChangedCallback(base::lambda_unique allAdminsChangedCallback) { + bool allAdmins() const; + void setAllAdminsChangedCallback(base::lambda &&allAdminsChangedCallback) { _allAdminsChangedCallback = std_::move(allAdminsChangedCallback); } - void loadProfilePhotos(int32 yFrom); void chooseParticipant(); void peopleReceived(const QString &query, const QVector &people); @@ -174,20 +182,21 @@ public: bool sharingBotGame() const; - int32 selectedCount() const; + int selectedCount() const; bool hasAlreadyMembersInChannel() const { return !_already.isEmpty(); } void saving(bool flag); + void setVisibleTopBottom(int visibleTop, int visibleBottom) override; + ~Inner(); signals: void mustScrollTo(int ymin, int ymax); void searchByUsername(); void adminAdded(); - void addRequested(); private slots: void onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow); @@ -195,10 +204,6 @@ private slots: void peerUpdated(PeerData *peer); void onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars); - void onAddBot(); - void onAddAdmin(); - void onNoAddAdminBox(QObject *obj); - void onAllAdminsChanged(); protected: @@ -207,22 +212,37 @@ protected: void leaveEvent(QEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; void resizeEvent(QResizeEvent *e) override; private: struct ContactData { - ContactData() = default; - ContactData(PeerData *peer, base::lambda_wrap updateCallback); + ContactData(); + ContactData(PeerData *peer, const base::lambda_copy &updateCallback); + ~ContactData(); std_::unique_ptr checkbox; + std_::unique_ptr ripple; + int rippleRowTop = 0; Text name; QString statusText; bool statusHasOnlineColor = false; bool disabledChecked = false; }; + void addRipple(PeerData *peer, ContactData *data); + void stopLastRipple(ContactData *data); + void setPressed(Dialogs::Row *pressed); + void setFilteredPressed(int pressed); + void setSearchedPressed(int pressed); + void clearSearchedContactDatas(); + + bool isRowDisabled(PeerData *peer, ContactData *data) const; + void loadProfilePhotos(); + void addBot(); void init(); void initList(); + void invalidateCache(); void updateRowWithTop(int rowTop); int getSelectedRowTop() const; @@ -232,7 +252,7 @@ private: void addAdminDone(const MTPUpdates &result, mtpRequestId req); bool addAdminFail(const RPCError &error, mtpRequestId req); - void paintDialog(Painter &p, uint64 ms, PeerData *peer, ContactData *data, bool sel); + void paintDialog(Painter &p, TimeMs ms, PeerData *peer, ContactData *data, bool sel); void paintDisabledCheckUserpic(Painter &p, PeerData *peer, int x, int y, int outerWidth) const; void changeCheckState(Dialogs::Row *row); @@ -250,11 +270,13 @@ private: return (_chat != nullptr) || (_creating != CreatingGroupNone && (!_channel || _membersFilter != MembersFilter::Admins)); } - base::lambda_unique _peerSelectedChangedCallback; + base::lambda _peerSelectedChangedCallback; - int32 _rowHeight; - int _newItemHeight = 0; - bool _newItemSel = false; + int _visibleTop = 0; + int _visibleBottom = 0; + int _rowHeight = 0; + int _rowsTop = 0; + int _aboutHeight = 0; ChatData *_chat = nullptr; ChannelData *_channel = nullptr; @@ -263,26 +285,28 @@ private: CreatingGroupType _creating = CreatingGroupNone; MembersAlreadyIn _already; - Checkbox _allAdmins; + object_ptr _allAdmins; int32 _aboutWidth; Text _aboutAllAdmins, _aboutAdmins; - base::lambda_unique _allAdminsChangedCallback; + base::lambda _allAdminsChangedCallback; PeerData *_addToPeer = nullptr; UserData *_addAdmin = nullptr; mtpRequestId _addAdminRequestId = 0; - ConfirmBox *_addAdminBox = nullptr; + QPointer _addAdminBox; int32 _time; std_::unique_ptr _customList; Dialogs::IndexedList *_contacts = nullptr; - Dialogs::Row *_sel = nullptr; + Dialogs::Row *_selected = nullptr; + Dialogs::Row *_pressed = nullptr; QString _filter; using FilteredDialogs = QVector; FilteredDialogs _filtered; - int _filteredSel = -1; - bool _mouseSel = false; + int _filteredSelected = -1; + int _filteredPressed = -1; + bool _mouseSelection = false; using ContactsData = QMap; ContactsData _contactsData; @@ -298,10 +322,11 @@ private: ByUsernameRows _byUsername, _byUsernameFiltered; ByUsernameDatas d_byUsername, d_byUsernameFiltered; // filtered is partly subset of d_byUsername, partly subset of _byUsernameDatas ByUsernameDatas _byUsernameDatas; - int _byUsernameSel = -1; + int _searchedSelected = -1; + int _searchedPressed = -1; QPoint _lastMousePos; - LinkButton _addContactLnk; + object_ptr _addContactLnk; bool _saving = false; bool _allAdminsChecked = false; diff --git a/Telegram/SourceFiles/boxes/downloadpathbox.cpp b/Telegram/SourceFiles/boxes/downloadpathbox.cpp index 980263537..fdfeee1cf 100644 --- a/Telegram/SourceFiles/boxes/downloadpathbox.cpp +++ b/Telegram/SourceFiles/boxes/downloadpathbox.cpp @@ -19,97 +19,83 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" +#include "boxes/downloadpathbox.h" + #include "lang.h" - #include "localstorage.h" - -#include "downloadpathbox.h" #include "ui/filedialog.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/buttons.h" #include "pspecific.h" +#include "styles/style_boxes.h" -DownloadPathBox::DownloadPathBox() : AbstractBox() -, _path(Global::DownloadPath()) +DownloadPathBox::DownloadPathBox(QWidget *parent) +: _path(Global::DownloadPath()) , _pathBookmark(Global::DownloadPathBookmark()) -, _default(this, qsl("dir_type"), 0, lang(lng_download_path_default_radio), _path.isEmpty()) -, _temp(this, qsl("dir_type"), 1, lang(lng_download_path_temp_radio), _path == qsl("tmp")) -, _dir(this, qsl("dir_type"), 2, lang(lng_download_path_dir_radio), !_path.isEmpty() && _path != qsl("tmp")) -, _pathLink(this, QString(), st::defaultBoxLinkButton) -, _save(this, lang(lng_connection_save), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) { +, _default(this, qsl("dir_type"), 0, lang(lng_download_path_default_radio), _path.isEmpty(), st::defaultBoxCheckbox) +, _temp(this, qsl("dir_type"), 1, lang(lng_download_path_temp_radio), (_path == qsl("tmp")), st::defaultBoxCheckbox) +, _dir(this, qsl("dir_type"), 2, lang(lng_download_path_dir_radio), (!_path.isEmpty() && _path != qsl("tmp")), st::defaultBoxCheckbox) +, _pathLink(this, QString(), st::boxLinkButton) { +} - connect(&_save, SIGNAL(clicked()), this, SLOT(onSave())); - connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); +void DownloadPathBox::prepare() { + addButton(lang(lng_connection_save), [this] { save(); }); + addButton(lang(lng_cancel), [this] { closeBox(); }); - connect(&_default, SIGNAL(changed()), this, SLOT(onChange())); - connect(&_temp, SIGNAL(changed()), this, SLOT(onChange())); - connect(&_dir, SIGNAL(changed()), this, SLOT(onChange())); + setTitle(lang(lng_download_path_header)); - connect(&_pathLink, SIGNAL(clicked()), this, SLOT(onEditPath())); + connect(_default, SIGNAL(changed()), this, SLOT(onChange())); + connect(_temp, SIGNAL(changed()), this, SLOT(onChange())); + connect(_dir, SIGNAL(changed()), this, SLOT(onChange())); + + connect(_pathLink, SIGNAL(clicked()), this, SLOT(onEditPath())); if (!_path.isEmpty() && _path != qsl("tmp")) { setPathText(QDir::toNativeSeparators(_path)); } - prepare(); + updateControlsVisibility(); } -void DownloadPathBox::showAll() { - _default.show(); - _temp.show(); - _dir.show(); +void DownloadPathBox::updateControlsVisibility() { + _pathLink->setVisible(_dir->checked()); - if (_dir.checked()) { - _pathLink.show(); - } else { - _pathLink.hide(); + auto newHeight = st::boxOptionListPadding.top() + _default->heightNoMargins() + st::boxOptionListSkip + _temp->heightNoMargins() + st::boxOptionListSkip + _dir->heightNoMargins(); + if (_dir->checked()) { + newHeight += st::downloadPathSkip + _pathLink->height(); } + newHeight += st::boxOptionListPadding.bottom(); - _save.show(); - _cancel.show(); - - int32 h = st::boxTitleHeight + st::boxOptionListPadding.top() + _default.height() + st::boxOptionListPadding.top() + _temp.height() + st::boxOptionListPadding.top() + _dir.height(); - if (_dir.checked()) h += st::downloadPathSkip + _pathLink.height(); - h += st::boxOptionListPadding.bottom() + st::boxButtonPadding.top() + _save.height() + st::boxButtonPadding.bottom(); - - setMaxHeight(h); -} - -void DownloadPathBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; - - paintTitle(p, lang(lng_download_path_header)); + setDimensions(st::boxWideWidth, newHeight); } void DownloadPathBox::resizeEvent(QResizeEvent *e) { - _default.moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), st::boxTitleHeight + st::boxOptionListPadding.top()); - _temp.moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _default.y() + _default.height() + st::boxOptionListPadding.top()); - _dir.moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _temp.y() + _temp.height() + st::boxOptionListPadding.top()); - int32 inputx = st::boxPadding.left() + st::boxOptionListPadding.left() + st::defaultRadiobutton.textPosition.x(); - int32 inputy = _dir.y() + _dir.height() + st::downloadPathSkip; + BoxContent::resizeEvent(e); - _pathLink.moveToLeft(inputx, inputy); + _default->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), st::boxOptionListPadding.top()); + _temp->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _default->bottomNoMargins() + st::boxOptionListSkip); + _dir->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _temp->bottomNoMargins() + st::boxOptionListSkip); + auto inputx = st::boxPadding.left() + st::boxOptionListPadding.left() + st::defaultBoxCheckbox.textPosition.x(); + auto inputy = _dir->bottomNoMargins() + st::downloadPathSkip; - _save.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _save.height()); - _cancel.moveToRight(st::boxButtonPadding.right() + _save.width() + st::boxButtonPadding.left(), _save.y()); - AbstractBox::resizeEvent(e); + _pathLink->moveToLeft(inputx, inputy); } void DownloadPathBox::onChange() { - if (_dir.checked()) { + if (_dir->checked()) { if (_path.isEmpty() || _path == qsl("tmp")) { - (_path.isEmpty() ? _default : _temp).setChecked(true); + (_path.isEmpty() ? _default : _temp)->setChecked(true); onEditPath(); if (!_path.isEmpty() && _path != qsl("tmp")) { - _dir.setChecked(true); + _dir->setChecked(true); } } else { setPathText(QDir::toNativeSeparators(_path)); } - } else if (_temp.checked()) { + } else if (_temp->checked()) { _path = qsl("tmp"); } else { _path = QString(); } - showAll(); + updateControlsVisibility(); update(); } @@ -129,15 +115,15 @@ void DownloadPathBox::onEditPath() { cSetDialogLastPath(lastPath); } -void DownloadPathBox::onSave() { - Global::SetDownloadPath(_default.checked() ? QString() : (_temp.checked() ? qsl("tmp") : _path)); - Global::SetDownloadPathBookmark((_default.checked() || _temp.checked()) ? QByteArray() : _pathBookmark); +void DownloadPathBox::save() { + Global::SetDownloadPath(_default->checked() ? QString() : (_temp->checked() ? qsl("tmp") : _path)); + Global::SetDownloadPathBookmark((_default->checked() || _temp->checked()) ? QByteArray() : _pathBookmark); Local::writeUserSettings(); Global::RefDownloadPathChanged().notify(); - onClose(); + closeBox(); } void DownloadPathBox::setPathText(const QString &text) { - int32 availw = st::boxWideWidth - st::boxPadding.left() - st::defaultRadiobutton.textPosition.x() - st::boxPadding.right(); - _pathLink.setText(st::boxTextFont->elided(text, availw)); + auto availw = st::boxWideWidth - st::boxPadding.left() - st::defaultBoxCheckbox.textPosition.x() - st::boxPadding.right(); + _pathLink->setText(st::boxTextFont->elided(text, availw)); } diff --git a/Telegram/SourceFiles/boxes/downloadpathbox.h b/Telegram/SourceFiles/boxes/downloadpathbox.h index 7c4f26a3b..a1a3d318e 100644 --- a/Telegram/SourceFiles/boxes/downloadpathbox.h +++ b/Telegram/SourceFiles/boxes/downloadpathbox.h @@ -20,34 +20,40 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "abstractbox.h" +#include "boxes/abstractbox.h" #include "core/observer.h" -class DownloadPathBox : public AbstractBox { +namespace Ui { +class Radiobutton; +class LinkButton; +} // namespace Ui + +class DownloadPathBox : public BoxContent { Q_OBJECT public: - DownloadPathBox(); - -public slots: - void onChange(); - void onEditPath(); - void onSave(); + DownloadPathBox(QWidget *parent); protected: - void paintEvent(QPaintEvent *e) override; + void prepare() override; + void resizeEvent(QResizeEvent *e) override; - void showAll() override; +private slots: + void onChange(); + void onEditPath(); private: + void save(); + void updateControlsVisibility(); void setPathText(const QString &text); QString _path; QByteArray _pathBookmark; - Radiobutton _default, _temp, _dir; - LinkButton _pathLink; - BoxButton _save, _cancel; + object_ptr _default; + object_ptr _temp; + object_ptr _dir; + object_ptr _pathLink; }; diff --git a/Telegram/SourceFiles/boxes/emojibox.cpp b/Telegram/SourceFiles/boxes/emojibox.cpp index 9aca04bc0..be499a798 100644 --- a/Telegram/SourceFiles/boxes/emojibox.cpp +++ b/Telegram/SourceFiles/boxes/emojibox.cpp @@ -19,9 +19,9 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "lang.h" +#include "boxes/emojibox.h" -#include "emojibox.h" +#include "lang.h" #include "mainwidget.h" #include "mainwindow.h" @@ -71,16 +71,18 @@ namespace { const uint32 replacesCount = sizeof(replaces) / sizeof(EmojiReplace), replacesInRow = 7; } -EmojiBox::EmojiBox() : _esize(EmojiSizes[EIndex + 1]) { - setBlueTitle(true); +EmojiBox::EmojiBox(QWidget*) : _esize(EmojiSizes[EIndex + 1]) { +} +void EmojiBox::prepare() { + setTitle(lang(lng_settings_emoji_list)); fillBlocks(); - _blockHeight = st::emojiReplaceInnerHeight; - - resizeMaxHeight(_blocks[0].size() * st::emojiReplaceWidth + 2 * st::emojiReplacePadding, st::boxTitleHeight + st::emojiReplacePadding + _blocks.size() * st::emojiReplaceHeight + (st::emojiReplaceHeight - _blockHeight) + st::emojiReplacePadding); + addButton(lang(lng_close), [this] { closeBox(); }); - prepare(); + _blockHeight = st::emojiReplaceInnerHeight; + + setDimensions(_blocks[0].size() * st::emojiReplaceWidth + 2 * st::emojiReplacePadding, st::emojiReplacePadding + _blocks.size() * st::emojiReplaceHeight + (st::emojiReplaceHeight - _blockHeight) + st::emojiReplacePadding); } void EmojiBox::fillBlocks() { @@ -115,21 +117,20 @@ void EmojiBox::fillBlocks() { void EmojiBox::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { - onClose(); + closeBox(); } else { - AbstractBox::keyPressEvent(e); + BoxContent::keyPressEvent(e); } } void EmojiBox::paintEvent(QPaintEvent *e) { + BoxContent::paintEvent(e); + Painter p(this); - if (paint(p)) return; - paintTitle(p, lang(lng_settings_emoji_list)); - - p.setFont(st::emojiTextFont->f); - p.setPen(st::black->p); - int32 top = st::boxTitleHeight + st::emojiReplacePadding + (st::emojiReplaceHeight - _blockHeight) / 2; + p.setFont(st::emojiTextFont); + p.setPen(st::boxTextFg); + auto top = st::emojiReplacePadding + (st::emojiReplaceHeight - _blockHeight) / 2; for (Blocks::const_iterator i = _blocks.cbegin(), e = _blocks.cend(); i != e; ++i) { int32 rowSize = i->size(), left = (width() - rowSize * st::emojiReplaceWidth) / 2; for (BlockRow::const_iterator j = i->cbegin(), en = i->cend(); j != en; ++j) { diff --git a/Telegram/SourceFiles/boxes/emojibox.h b/Telegram/SourceFiles/boxes/emojibox.h index a04b49378..e6bf46a38 100644 --- a/Telegram/SourceFiles/boxes/emojibox.h +++ b/Telegram/SourceFiles/boxes/emojibox.h @@ -20,15 +20,15 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "abstractbox.h" - -class EmojiBox : public AbstractBox { - Q_OBJECT +#include "boxes/abstractbox.h" +class EmojiBox : public BoxContent { public: - EmojiBox(); + EmojiBox(QWidget*); protected: + void prepare() override; + void keyPressEvent(QKeyEvent *e) override; void paintEvent(QPaintEvent *e) override; diff --git a/Telegram/SourceFiles/boxes/languagebox.cpp b/Telegram/SourceFiles/boxes/languagebox.cpp index f79bdaed6..16a789905 100644 --- a/Telegram/SourceFiles/boxes/languagebox.cpp +++ b/Telegram/SourceFiles/boxes/languagebox.cpp @@ -19,125 +19,109 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" +#include "boxes/languagebox.h" + #include "lang.h" - +#include "ui/widgets/checkbox.h" +#include "ui/widgets/buttons.h" #include "localstorage.h" - -#include "languagebox.h" -#include "confirmbox.h" +#include "boxes/confirmbox.h" #include "mainwidget.h" #include "mainwindow.h" - #include "langloaderplain.h" +#include "styles/style_boxes.h" -LanguageBox::LanguageBox() : -_close(this, lang(lng_box_ok), st::defaultBoxButton) { +void LanguageBox::prepare() { + addButton(lang(lng_box_ok), [this] { closeBox(); }); - bool haveTestLang = (cLang() == languageTest); + setTitle(lang(lng_languages)); - int32 y = st::boxTitleHeight + st::boxOptionListPadding.top(); + auto haveTestLang = (cLang() == languageTest); + + auto y = st::boxOptionListPadding.top(); _langs.reserve(languageCount + (haveTestLang ? 1 : 0)); if (haveTestLang) { - _langs.push_back(new Radiobutton(this, qsl("lang"), languageTest, qsl("Custom Lang"), (cLang() == languageTest), st::langsButton)); + _langs.push_back(new Ui::Radiobutton(this, qsl("lang"), languageTest, qsl("Custom Lang"), (cLang() == languageTest), st::langsButton)); _langs.back()->move(st::boxPadding.left() + st::boxOptionListPadding.left(), y); - y += _langs.back()->height() + st::boxOptionListPadding.top(); + y += _langs.back()->heightNoMargins() + st::boxOptionListSkip; connect(_langs.back(), SIGNAL(changed()), this, SLOT(onChange())); } - for (int32 i = 0; i < languageCount; ++i) { + for (auto i = 0; i != languageCount; ++i) { LangLoaderResult result; if (i) { - LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[i].c_str() + qsl(".strings"), LangLoaderRequest(lng_language_name)); + LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[i].c_str() + qsl(".strings"), langLoaderRequest(lng_language_name)); result = loader.found(); } else { result.insert(lng_language_name, langOriginal(lng_language_name)); } - _langs.push_back(new Radiobutton(this, qsl("lang"), i, result.value(lng_language_name, LanguageCodes[i].c_str() + qsl(" language")), (cLang() == i), st::langsButton)); + _langs.push_back(new Ui::Radiobutton(this, qsl("lang"), i, result.value(lng_language_name, LanguageCodes[i].c_str() + qsl(" language")), (cLang() == i), st::langsButton)); _langs.back()->move(st::boxPadding.left() + st::boxOptionListPadding.left(), y); - y += _langs.back()->height() + st::boxOptionListPadding.top(); + y += _langs.back()->heightNoMargins() + st::boxOptionListSkip; connect(_langs.back(), SIGNAL(changed()), this, SLOT(onChange())); } - resizeMaxHeight(st::langsWidth, st::boxTitleHeight + (languageCount + (haveTestLang ? 1 : 0)) * (st::boxOptionListPadding.top() + st::langsButton.height) + st::boxOptionListPadding.bottom() + st::boxPadding.bottom() + st::boxButtonPadding.top() + _close.height() + st::boxButtonPadding.bottom()); - - connect(&_close, SIGNAL(clicked()), this, SLOT(onClose())); - - _close.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _close.height()); - prepare(); -} - -void LanguageBox::showAll() { - _close.show(); - for (int32 i = 0, l = _langs.size(); i < l; ++i) { - _langs[i]->show(); - } + auto optionsCount = languageCount + (haveTestLang ? 1 : 0); + setDimensions(st::langsWidth, st::boxOptionListPadding.top() + optionsCount * st::langsButton.height + (optionsCount - 1) * st::boxOptionListSkip + st::boxOptionListPadding.bottom() + st::boxPadding.bottom()); } void LanguageBox::mousePressEvent(QMouseEvent *e) { if ((e->modifiers() & Qt::CTRL) && (e->modifiers() & Qt::ALT) && (e->modifiers() & Qt::SHIFT)) { for (int32 i = 1; i < languageCount; ++i) { - LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[i].c_str() + qsl(".strings"), LangLoaderRequest(lngkeys_cnt)); + LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[i].c_str() + qsl(".strings"), langLoaderRequest(lngkeys_cnt)); if (!loader.errors().isEmpty()) { - Ui::showLayer(new InformBox(qsl("Lang \"") + LanguageCodes[i].c_str() + qsl("\" error :(\n\nError: ") + loader.errors())); + Ui::show(Box(qsl("Lang \"") + LanguageCodes[i].c_str() + qsl("\" error :(\n\nError: ") + loader.errors())); return; } else if (!loader.warnings().isEmpty()) { QString warn = loader.warnings(); if (warn.size() > 256) warn = warn.mid(0, 253) + qsl("..."); - Ui::showLayer(new InformBox(qsl("Lang \"") + LanguageCodes[i].c_str() + qsl("\" warnings :(\n\nWarnings: ") + warn)); + Ui::show(Box(qsl("Lang \"") + LanguageCodes[i].c_str() + qsl("\" warnings :(\n\nWarnings: ") + warn)); return; } } - Ui::showLayer(new InformBox(qsl("Everything seems great in all %1 languages!").arg(languageCount - 1))); + Ui::show(Box(qsl("Everything seems great in all %1 languages!").arg(languageCount - 1))); } } -void LanguageBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; - - paintTitle(p, lang(lng_languages)); -} - void LanguageBox::onChange() { - if (isHidden()) return; + if (!isBoxShown()) return; for (int32 i = 0, l = _langs.size(); i < l; ++i) { int32 langId = _langs[i]->val(); if (_langs[i]->checked() && langId != cLang()) { LangLoaderResult result; if (langId > 0) { - LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[langId].c_str() + qsl(".strings"), LangLoaderRequest(lng_sure_save_language, lng_cancel, lng_box_ok)); + LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[langId].c_str() + qsl(".strings"), langLoaderRequest(lng_sure_save_language, lng_cancel, lng_box_ok)); result = loader.found(); } else if (langId == languageTest) { - LangLoaderPlain loader(cLangFile(), LangLoaderRequest(lng_sure_save_language, lng_cancel, lng_box_ok)); + LangLoaderPlain loader(cLangFile(), langLoaderRequest(lng_sure_save_language, lng_cancel, lng_box_ok)); result = loader.found(); } - QString text = result.value(lng_sure_save_language, langOriginal(lng_sure_save_language)), - save = result.value(lng_box_ok, langOriginal(lng_box_ok)), - cancel = result.value(lng_cancel, langOriginal(lng_cancel)); - ConfirmBox *box = new ConfirmBox(text, save, st::defaultBoxButton, cancel); - connect(box, SIGNAL(confirmed()), this, SLOT(onSave())); - connect(box, SIGNAL(cancelled()), this, SLOT(onRestore())); - Ui::showLayer(box, KeepOtherLayers); + auto text = result.value(lng_sure_save_language, langOriginal(lng_sure_save_language)), + save = result.value(lng_box_ok, langOriginal(lng_box_ok)), + cancel = result.value(lng_cancel, langOriginal(lng_cancel)); + Ui::show(Box(text, save, cancel, base::lambda_guarded(this, [this] { + saveLanguage(); + }), base::lambda_guarded(this, [this] { + restoreLanguage(); + })), KeepOtherLayers); } } } -void LanguageBox::onRestore() { - for (int32 i = 0, l = _langs.size(); i < l; ++i) { +void LanguageBox::restoreLanguage() { + for (auto i = 0, l = _langs.size(); i != l; ++i) { if (_langs[i]->val() == cLang()) { _langs[i]->setChecked(true); } } } -void LanguageBox::onSave() { - for (int32 i = 0, l = _langs.size(); i < l; ++i) { +void LanguageBox::saveLanguage() { + for (auto i = 0, l = _langs.size(); i != l; ++i) { if (_langs[i]->checked()) { cSetLang(_langs[i]->val()); Local::writeSettings(); - cSetRestarting(true); - cSetRestartingToSettings(true); - App::quit(); + App::restart(); } } } diff --git a/Telegram/SourceFiles/boxes/languagebox.h b/Telegram/SourceFiles/boxes/languagebox.h index 8b0332e39..e1e666383 100644 --- a/Telegram/SourceFiles/boxes/languagebox.h +++ b/Telegram/SourceFiles/boxes/languagebox.h @@ -20,27 +20,31 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "abstractbox.h" +#include "boxes/abstractbox.h" -class LanguageBox : public AbstractBox { +namespace Ui { +class Radiobutton; +} // namespace Ui + +class LanguageBox : public BoxContent { Q_OBJECT public: - LanguageBox(); - -public slots: - void onChange(); - void onRestore(); - void onSave(); + LanguageBox(QWidget*) { + } protected: - void mousePressEvent(QMouseEvent *e) override; - void paintEvent(QPaintEvent *e) override; + void prepare() override; - void showAll() override; + void mousePressEvent(QMouseEvent *e) override; + +private slots: + void onChange(); private: - QVector _langs; - BoxButton _close; + void saveLanguage(); + void restoreLanguage(); + + QVector _langs; }; diff --git a/Telegram/SourceFiles/boxes/localstoragebox.cpp b/Telegram/SourceFiles/boxes/localstoragebox.cpp index 23057c10a..1ce948406 100644 --- a/Telegram/SourceFiles/boxes/localstoragebox.cpp +++ b/Telegram/SourceFiles/boxes/localstoragebox.cpp @@ -21,46 +21,46 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "boxes/localstoragebox.h" -#include "localstorage.h" -#include "ui/flatbutton.h" -#include "lang.h" #include "styles/style_boxes.h" +#include "ui/widgets/buttons.h" +#include "localstorage.h" +#include "lang.h" #include "mainwindow.h" -LocalStorageBox::LocalStorageBox() : AbstractBox() -, _clear(this, lang(lng_local_storage_clear), st::defaultBoxLinkButton) -, _close(this, lang(lng_box_ok), st::defaultBoxButton) { - connect(_clear, SIGNAL(clicked()), this, SLOT(onClear())); - connect(_close, SIGNAL(clicked()), this, SLOT(onClose())); +LocalStorageBox::LocalStorageBox(QWidget *parent) +: _clear(this, lang(lng_local_storage_clear), st::boxLinkButton) { +} + +void LocalStorageBox::prepare() { + setTitle(lang(lng_local_storage_title)); + + addButton(lang(lng_box_ok), [this] { closeBox(); }); + + _clear->setClickedCallback([this] { clearStorage(); }); connect(App::wnd(), SIGNAL(tempDirCleared(int)), this, SLOT(onTempDirCleared(int))); connect(App::wnd(), SIGNAL(tempDirClearFailed(int)), this, SLOT(onTempDirClearFailed(int))); subscribe(FileDownload::ImageLoaded(), [this] { update(); }); + updateControls(); + checkLocalStoredCounts(); - prepare(); } void LocalStorageBox::updateControls() { - int rowsHeight = 0; + auto rowsHeight = 0; if (_imagesCount > 0 && _audiosCount > 0) { rowsHeight = 2 * (st::linkFont->height + st::localStorageBoxSkip); } else { rowsHeight = st::linkFont->height + st::localStorageBoxSkip; } _clear->setVisible(_imagesCount > 0 || _audiosCount > 0); - setMaxHeight(st::boxTitleHeight + st::localStorageBoxSkip + rowsHeight + _clear->height() + st::boxButtonPadding.top() + _close->height() + st::boxButtonPadding.bottom()); - _clear->moveToLeft(st::boxPadding.left(), st::boxTitleHeight + st::localStorageBoxSkip + rowsHeight); - _close->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _close->height()); + setDimensions(st::boxWidth, st::localStorageBoxSkip + rowsHeight + _clear->height()); + _clear->moveToLeft(st::boxPadding.left(), st::localStorageBoxSkip + rowsHeight); update(); } -void LocalStorageBox::showAll() { - showChildren(); - _clear->setVisible(_imagesCount > 0 || _audiosCount > 0); -} - void LocalStorageBox::checkLocalStoredCounts() { int imagesCount = Local::hasImages() + Local::hasStickers() + Local::hasWebFiles(); int audiosCount = Local::hasAudios(); @@ -75,15 +75,14 @@ void LocalStorageBox::checkLocalStoredCounts() { } void LocalStorageBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; + BoxContent::paintEvent(e); - paintTitle(p, lang(lng_local_storage_title)); + Painter p(this); p.setFont(st::boxTextFont); - p.setPen(st::windowTextFg); + p.setPen(st::windowFg); checkLocalStoredCounts(); - int top = st::boxTitleHeight + st::localStorageBoxSkip; + auto top = st::localStorageBoxSkip; if (_imagesCount > 0) { auto text = lng_settings_images_cached(lt_count, _imagesCount, lt_size, formatSizeText(Local::storageImagesSize() + Local::storageStickersSize() + Local::storageWebFilesSize())); p.drawTextLeft(st::boxPadding.left(), top, width(), text); @@ -111,7 +110,7 @@ void LocalStorageBox::paintEvent(QPaintEvent *e) { } } -void LocalStorageBox::onClear() { +void LocalStorageBox::clearStorage() { App::wnd()->tempDirDelete(Local::ClearManagerStorage); _state = State::Clearing; updateControls(); diff --git a/Telegram/SourceFiles/boxes/localstoragebox.h b/Telegram/SourceFiles/boxes/localstoragebox.h index ef175bfec..17e87bf8f 100644 --- a/Telegram/SourceFiles/boxes/localstoragebox.h +++ b/Telegram/SourceFiles/boxes/localstoragebox.h @@ -20,28 +20,29 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "abstractbox.h" +#include "boxes/abstractbox.h" -class BoxButton; +namespace Ui { class LinkButton; +} // namespace Ui -class LocalStorageBox : public AbstractBox { +class LocalStorageBox : public BoxContent { Q_OBJECT public: - LocalStorageBox(); + LocalStorageBox(QWidget*); private slots: - void onClear(); void onTempDirCleared(int task); void onTempDirClearFailed(int task); protected: + void prepare() override; + void paintEvent(QPaintEvent *e) override; - void showAll() override; - private: + void clearStorage(); void updateControls(); void checkLocalStoredCounts(); @@ -53,8 +54,7 @@ private: }; State _state = State::Normal; - ChildWidget _clear; - ChildWidget _close; + object_ptr _clear; int _imagesCount = -1; int _audiosCount = -1; diff --git a/Telegram/SourceFiles/boxes/members_box.cpp b/Telegram/SourceFiles/boxes/members_box.cpp index 8290a5bec..ccd91902b 100644 --- a/Telegram/SourceFiles/boxes/members_box.cpp +++ b/Telegram/SourceFiles/boxes/members_box.cpp @@ -28,20 +28,58 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwindow.h" #include "boxes/contactsbox.h" #include "boxes/confirmbox.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/scroll_area.h" +#include "ui/effects/ripple_animation.h" #include "observer_peer.h" -MembersBox::MembersBox(ChannelData *channel, MembersFilter filter) : ItemListBox(st::boxScroll) -, _inner(this, channel, filter) { - ItemListBox::init(_inner); - connect(_inner, SIGNAL(addRequested()), this, SLOT(onAdd())); +MembersAddButton::MembersAddButton(QWidget *parent, const style::TwoIconButton &st) : RippleButton(parent, st.ripple) +, _st(st) { + resize(_st.width, _st.height); + setCursor(style::cur_pointer); +} - connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); - connect(_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int))); +void MembersAddButton::paintEvent(QPaintEvent *e) { + Painter p(this); - connect(&_loadTimer, SIGNAL(timeout()), _inner, SLOT(load())); + auto ms = getms(); + auto over = isOver(); + auto down = isDown(); - prepare(); + ((over || down) ? _st.iconBelowOver : _st.iconBelow).paint(p, _st.iconPosition, width()); + paintRipple(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y(), ms); + ((over || down) ? _st.iconAboveOver : _st.iconAbove).paint(p, _st.iconPosition, width()); +} + +QImage MembersAddButton::prepareRippleMask() const { + return Ui::RippleAnimation::ellipseMask(QSize(_st.rippleAreaSize, _st.rippleAreaSize)); +} + +QPoint MembersAddButton::prepareRippleStartPosition() const { + return mapFromGlobal(QCursor::pos()) - _st.rippleAreaPosition; +} + +MembersBox::MembersBox(QWidget*, ChannelData *channel, MembersFilter filter) +: _channel(channel) +, _filter(filter) { +} + +void MembersBox::prepare() { + setTitle(lang(_filter == MembersFilter::Recent ? lng_channel_members : lng_channel_admins)); + + _inner = setInnerWidget(object_ptr(this, _channel, _filter), st::boxLayerScroll); + + setDimensions(st::boxWideWidth, st::boxMaxListHeight); + addButton(lang(lng_close), [this] { closeBox(); }); + if (_channel->amCreator() && (_channel->membersCount() < (_channel->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax()) || (!_channel->isMegagroup() && !_channel->isPublic()) || _filter == MembersFilter::Admins)) { + addLeftButton(lang((_filter == MembersFilter::Admins) ? lng_channel_add_admin : lng_channel_add_members), [this] { onAdd(); }); + } + + connect(_inner, SIGNAL(mustScrollTo(int, int)), this, SLOT(onScrollToY(int, int))); + + _loadTimer.create(this); + connect(_loadTimer, SIGNAL(timeout()), _inner, SLOT(load())); } void MembersBox::keyPressEvent(QKeyEvent *e) { @@ -50,74 +88,51 @@ void MembersBox::keyPressEvent(QKeyEvent *e) { } else if (e->key() == Qt::Key_Up) { _inner->selectSkip(-1); } else if (e->key() == Qt::Key_PageDown) { - _inner->selectSkipPage(scrollArea()->height(), 1); + _inner->selectSkipPage(height(), 1); } else if (e->key() == Qt::Key_PageUp) { - _inner->selectSkipPage(scrollArea()->height(), -1); + _inner->selectSkipPage(height(), -1); } else { - ItemListBox::keyPressEvent(e); + BoxContent::keyPressEvent(e); } } -void MembersBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; - - QString title(lang(_inner->filter() == MembersFilter::Recent ? lng_channel_members : lng_channel_admins)); - paintTitle(p, title); -} - void MembersBox::resizeEvent(QResizeEvent *e) { - ItemListBox::resizeEvent(e); - _inner->resize(width(), _inner->height()); -} + BoxContent::resizeEvent(e); -void MembersBox::onScroll() { - _inner->loadProfilePhotos(scrollArea()->scrollTop()); + _inner->resize(width(), _inner->height()); } void MembersBox::onAdd() { if (_inner->filter() == MembersFilter::Recent && _inner->channel()->membersCount() >= (_inner->channel()->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax())) { - Ui::showLayer(new MaxInviteBox(_inner->channel()->inviteLink()), KeepOtherLayers); + Ui::show(Box(_inner->channel()->inviteLink()), KeepOtherLayers); return; } - ContactsBox *box = new ContactsBox(_inner->channel(), _inner->filter(), _inner->already()); + auto box = Box(_inner->channel(), _inner->filter(), _inner->already()); if (_inner->filter() == MembersFilter::Recent) { - Ui::showLayer(box); + Ui::show(std_::move(box)); } else { - _addBox = box; - connect(_addBox, SIGNAL(adminAdded()), this, SLOT(onAdminAdded())); - Ui::showLayer(_addBox, KeepOtherLayers); + _addBox = Ui::show(std_::move(box), KeepOtherLayers); + if (_addBox) { + connect(_addBox, SIGNAL(adminAdded()), this, SLOT(onAdminAdded())); + } } } void MembersBox::onAdminAdded() { if (!_addBox) return; - _addBox->onClose(); - _addBox = 0; - _loadTimer.start(ReloadChannelMembersTimeout); + _addBox->closeBox(); + _addBox = nullptr; + _loadTimer->start(ReloadChannelMembersTimeout); } -MembersBox::Inner::Inner(QWidget *parent, ChannelData *channel, MembersFilter filter) : ScrolledWidget(parent) +MembersBox::Inner::Inner(QWidget *parent, ChannelData *channel, MembersFilter filter) : TWidget(parent) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) -, _newItemHeight((channel->amCreator() && (channel->membersCount() < (channel->isMegagroup() ? Global::MegagroupSizeMax() : Global::ChatSizeMax()) || (!channel->isMegagroup() && !channel->isPublic()) || filter == MembersFilter::Admins)) ? st::contactsNewItemHeight : 0) -, _newItemSel(false) , _channel(channel) , _filter(filter) , _kickText(lang(lng_profile_kick)) -, _time(0) , _kickWidth(st::normalFont->width(_kickText)) -, _sel(-1) -, _kickSel(-1) -, _kickDown(-1) -, _mouseSel(false) -, _kickConfirm(0) -, _kickRequestId(0) -, _kickBox(0) -, _loading(true) -, _loadingRequestId(0) , _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.right()) -, _about(_aboutWidth) -, _aboutHeight(0) { +, _about(_aboutWidth) { subscribe(FileDownload::ImageLoaded(), [this] { update(); }); connect(App::main(), SIGNAL(peerNameChanged(PeerData*,const PeerData::Names&,const PeerData::NameFirstChars&)), this, SLOT(onPeerNameChanged(PeerData*, const PeerData::Names&, const PeerData::NameFirstChars&))); @@ -139,39 +154,29 @@ void MembersBox::Inner::paintEvent(QPaintEvent *e) { Painter p(this); _time = unixtime(); - p.fillRect(r, st::white->b); + p.fillRect(r, st::contactsBg); - int32 yFrom = r.y() - st::membersPadding.top(), yTo = r.y() + r.height() - st::membersPadding.top(); - p.translate(0, st::membersPadding.top()); + auto ms = getms(); + auto yFrom = r.y() - st::membersMarginTop; + auto yTo = r.y() + r.height() - st::membersMarginTop; + p.translate(0, st::membersMarginTop); if (_rows.isEmpty()) { - p.setFont(st::noContactsFont->f); - p.setPen(st::noContactsColor->p); + p.setFont(st::noContactsFont); + p.setPen(st::noContactsColor); p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center); } else { - if (_newItemHeight) { - p.fillRect(0, 0, width(), _newItemHeight, (_newItemSel ? st::contactsBgOver : st::white)->b); - st::contactsNewItemIcon.paint(p, 0, 0, width()); - p.setFont(st::contactsNameFont); - p.setPen(st::contactsNewItemFg); - p.drawTextLeft(st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(), st::contactsNewItemTop, width(), lang(_filter == MembersFilter::Admins ? lng_channel_add_admins : lng_channel_add_members)); - - yFrom -= _newItemHeight; - yTo -= _newItemHeight; - p.translate(0, _newItemHeight); - } int32 from = floorclamp(yFrom, _rowHeight, 0, _rows.size()); int32 to = ceilclamp(yTo, _rowHeight, 0, _rows.size()); p.translate(0, from * _rowHeight); for (; from < to; ++from) { - bool sel = (from == _sel); - bool kickSel = (from == _kickSel && (_kickDown < 0 || from == _kickDown)); - bool kickDown = kickSel && (from == _kickDown); - paintDialog(p, _rows[from], data(from), sel, kickSel, kickDown); + auto selected = (_pressed >= 0) ? (from == _pressed) : (from == _selected); + auto kickSelected = (_pressed >= 0) ? (from == _kickPressed && from == _kickSelected) : (from == _kickSelected); + paintDialog(p, ms, _rows[from], data(from), selected, kickSelected); p.translate(0, _rowHeight); } if (to == _rows.size() && _filter == MembersFilter::Recent && (_rows.size() < _channel->membersCount() || _rows.size() >= Global::ChatSizeMax())) { - p.setPen(st::stickersReorderFg); - _about.draw(p, st::contactsPadding.left(), st::stickersReorderPadding.top(), _aboutWidth, style::al_center); + p.setPen(st::membersAboutLimitFg); + _about.draw(p, st::contactsPadding.left(), st::membersAboutLimitPadding.top(), _aboutWidth, style::al_center); } } } @@ -181,66 +186,94 @@ void MembersBox::Inner::enterEvent(QEvent *e) { } void MembersBox::Inner::leaveEvent(QEvent *e) { - _mouseSel = false; + _mouseSelection = false; setMouseTracking(false); - if (_sel >= 0) { + if (_selected >= 0) { clearSel(); } } void MembersBox::Inner::mouseMoveEvent(QMouseEvent *e) { - _mouseSel = true; + _mouseSelection = true; _lastMousePos = e->globalPos(); - updateSel(); + updateSelection(); } void MembersBox::Inner::mousePressEvent(QMouseEvent *e) { - _mouseSel = true; + _mouseSelection = true; _lastMousePos = e->globalPos(); - updateSel(); - if (e->button() == Qt::LeftButton && _kickSel < 0) { - chooseParticipant(); + updateSelection(); + setPressed(_selected); + _kickPressed = _kickSelected; + if (_selected >= 0 && _selected < _datas.size() && _kickSelected < 0) { + addRipple(_datas[_selected]); } - _kickDown = _kickSel; - update(); } void MembersBox::Inner::mouseReleaseEvent(QMouseEvent *e) { - _mouseSel = true; - _lastMousePos = e->globalPos(); - updateSel(); - if (_kickDown >= 0 && _kickDown == _kickSel && !_kickRequestId) { - _kickConfirm = _rows.at(_kickSel); - if (_kickBox) _kickBox->deleteLater(); - _kickBox = new ConfirmBox((_filter == MembersFilter::Recent ? (_channel->isMegagroup() ? lng_profile_sure_kick : lng_profile_sure_kick_channel) : lng_profile_sure_kick_admin)(lt_user, _kickConfirm->firstName)); - connect(_kickBox, SIGNAL(confirmed()), this, SLOT(onKickConfirm())); - connect(_kickBox, SIGNAL(destroyed(QObject*)), this, SLOT(onKickBoxDestroyed(QObject*))); - Ui::showLayer(_kickBox, KeepOtherLayers); - } - _kickDown = -1; -} - -void MembersBox::Inner::onKickBoxDestroyed(QObject *obj) { - if (_kickBox == obj) { - _kickBox = 0; + auto pressed = _pressed; + auto kickPressed = _kickPressed; + setPressed(-1); + if (e->button() == Qt::LeftButton) { + if (pressed == _selected && kickPressed == _kickSelected) { + if (kickPressed >= 0) { + if (!_kickRequestId) { + _kickConfirm = _rows.at(_kickSelected); + if (_kickBox) _kickBox->deleteLater(); + auto text = (_filter == MembersFilter::Recent ? (_channel->isMegagroup() ? lng_profile_sure_kick : lng_profile_sure_kick_channel) : lng_profile_sure_kick_admin)(lt_user, _kickConfirm->firstName); + _kickBox = Ui::show(Box(text, base::lambda_guarded(this, [this] { + if (_filter == MembersFilter::Recent) { + _kickRequestId = MTP::send(MTPchannels_KickFromChannel(_channel->inputChannel, _kickConfirm->inputUser, MTP_bool(true)), rpcDone(&Inner::kickDone), rpcFail(&Inner::kickFail)); + } else { + _kickRequestId = MTP::send(MTPchannels_EditAdmin(_channel->inputChannel, _kickConfirm->inputUser, MTP_channelRoleEmpty()), rpcDone(&Inner::kickAdminDone), rpcFail(&Inner::kickFail)); + } + })), KeepOtherLayers); + } + } else if (pressed >= 0) { + chooseParticipant(); + } + } } } -void MembersBox::Inner::onKickConfirm() { - if (_filter == MembersFilter::Recent) { - _kickRequestId = MTP::send(MTPchannels_KickFromChannel(_channel->inputChannel, _kickConfirm->inputUser, MTP_bool(true)), rpcDone(&Inner::kickDone), rpcFail(&Inner::kickFail)); - } else { - _kickRequestId = MTP::send(MTPchannels_EditAdmin(_channel->inputChannel, _kickConfirm->inputUser, MTP_channelRoleEmpty()), rpcDone(&Inner::kickAdminDone), rpcFail(&Inner::kickFail)); +void MembersBox::Inner::addRipple(MemberData *data) { + auto rowTop = getSelectedRowTop(); + if (!data->ripple) { + auto mask = Ui::RippleAnimation::rectMask(QSize(width(), _rowHeight)); + data->ripple = std_::make_unique(st::contactsRipple, std_::move(mask), [this, data] { + updateRowWithTop(data->rippleRowTop); + }); + } + data->rippleRowTop = rowTop; + data->ripple->add(mapFromGlobal(QCursor::pos()) - QPoint(0, rowTop)); +} + +void MembersBox::Inner::stopLastRipple(MemberData *data) { + if (data->ripple) { + data->ripple->lastStop(); } } -void MembersBox::Inner::paintDialog(Painter &p, PeerData *peer, MemberData *data, bool sel, bool kickSel, bool kickDown) { +void MembersBox::Inner::setPressed(int pressed) { + if (_pressed >= 0 && _pressed < _datas.size()) { + stopLastRipple(_datas[_pressed]); + } + _pressed = pressed; +} + +void MembersBox::Inner::paintDialog(Painter &p, TimeMs ms, PeerData *peer, MemberData *data, bool selected, bool kickSelected) { UserData *user = peer->asUser(); - p.fillRect(0, 0, width(), _rowHeight, (sel ? st::contactsBgOver : st::white)->b); - peer->paintUserpicLeft(p, st::contactsPhotoSize, st::contactsPadding.left(), st::contactsPadding.top(), width()); + p.fillRect(0, 0, width(), _rowHeight, selected ? st::contactsBgOver : st::contactsBg); + if (data->ripple) { + data->ripple->paint(p, 0, 0, width(), ms); + if (data->ripple->empty()) { + data->ripple.reset(); + } + } + peer->paintUserpicLeft(p, st::contactsPadding.left(), st::contactsPadding.top(), width(), st::contactsPhotoSize); - p.setPen(st::black); + p.setPen(st::contactsNameFg); int32 namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); int32 namew = width() - namex - st::contactsPadding.right() - (data->canKick ? (_kickWidth + st::contactsCheckPosition.x() * 2) : 0); @@ -252,52 +285,43 @@ void MembersBox::Inner::paintDialog(Painter &p, PeerData *peer, MemberData *data data->name.drawLeftElided(p, namex, st::contactsPadding.top() + st::contactsNameTop, namew, width()); if (data->canKick) { - p.setFont((kickSel ? st::linkOverFont : st::linkFont)->f); - if (kickDown) { - p.setPen(st::btnDefLink.downColor->p); - } else { - p.setPen(st::btnDefLink.color->p); - } + p.setFont(kickSelected ? st::linkOverFont : st::linkFont); + p.setPen(kickSelected ? st::defaultLinkButton.overColor : st::defaultLinkButton.color); p.drawTextRight(st::contactsPadding.right() + st::contactsCheckPosition.x(), st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, width(), _kickText, _kickWidth); } p.setFont(st::contactsStatusFont->f); - p.setPen(data->onlineColor ? st::contactsStatusFgOnline : (sel ? st::contactsStatusFgOver : st::contactsStatusFg)); + p.setPen(data->onlineColor ? st::contactsStatusFgOnline : (selected ? st::contactsStatusFgOver : st::contactsStatusFg)); p.drawTextLeft(namex, st::contactsPadding.top() + st::contactsStatusTop, width(), data->online); } void MembersBox::Inner::selectSkip(int32 dir) { _time = unixtime(); - _mouseSel = false; + _mouseSelection = false; int cur = -1; - if (_newItemHeight && _newItemSel) { - cur = 0; - } else if (_sel >= 0) { - cur = _sel + (_newItemHeight ? 1 : 0); + if (_selected >= 0) { + cur = _selected; } cur += dir; if (cur <= 0) { - _newItemSel = _newItemHeight ? true : false; - _sel = (_newItemSel || _rows.isEmpty()) ? -1 : 0; - } else if (cur >= _rows.size() + (_newItemHeight ? 1 : 0)) { - _sel = -1; + _selected = _rows.isEmpty() ? -1 : 0; + } else if (cur >= _rows.size()) { + _selected = -1; } else { - _sel = cur - (_newItemHeight ? 1 : 0); + _selected = cur; } if (dir > 0) { - if (_sel < 0 || _sel >= _rows.size()) { - _sel = -1; + if (_selected < 0 || _selected >= _rows.size()) { + _selected = -1; } } else { if (!_rows.isEmpty()) { - if (_sel < 0 && !_newItemSel) _sel = _rows.size() - 1; + if (_selected < 0) _selected = _rows.size() - 1; } } - if (_newItemSel) { - emit mustScrollTo(0, _newItemHeight); - } else if (_sel >= 0) { - emit mustScrollTo(_newItemHeight + _sel * _rowHeight, _newItemHeight + (_sel + 1) * _rowHeight); + if (_selected >= 0) { + emit mustScrollTo(st::membersMarginTop + _selected * _rowHeight, st::membersMarginTop + (_selected + 1) * _rowHeight); } update(); @@ -309,18 +333,25 @@ void MembersBox::Inner::selectSkipPage(int32 h, int32 dir) { selectSkip(points * dir); } -void MembersBox::Inner::loadProfilePhotos(int32 yFrom) { - int32 yTo = yFrom + (parentWidget() ? parentWidget()->height() : App::wnd()->height()) * 5; +MembersBox::Inner::MemberData::MemberData() = default; + +MembersBox::Inner::MemberData::~MemberData() = default; + +void MembersBox::Inner::loadProfilePhotos() { + if (_visibleTop >= _visibleBottom) return; + + auto yFrom = _visibleTop; + auto yTo = yFrom + (_visibleBottom - _visibleTop) * 5; MTP::clearLoaderPriorities(); if (yTo < 0) return; if (yFrom < 0) yFrom = 0; if (!_rows.isEmpty()) { - int32 from = (yFrom - _newItemHeight) / _rowHeight; + int32 from = yFrom / _rowHeight; if (from < 0) from = 0; if (from < _rows.size()) { - int32 to = ((yTo - _newItemHeight) / _rowHeight) + 1; + int32 to = (yTo / _rowHeight) + 1; if (to > _rows.size()) to = _rows.size(); for (; from < to; ++from) { @@ -331,12 +362,8 @@ void MembersBox::Inner::loadProfilePhotos(int32 yFrom) { } void MembersBox::Inner::chooseParticipant() { - if (_newItemSel) { - emit addRequested(); - return; - } - if (_sel < 0 || _sel >= _rows.size()) return; - if (PeerData *peer = _rows[_sel]) { + if (_selected < 0 || _selected >= _rows.size()) return; + if (auto peer = _rows[_selected]) { Ui::hideLayer(); Ui::showPeerProfile(peer); } @@ -344,15 +371,15 @@ void MembersBox::Inner::chooseParticipant() { void MembersBox::Inner::refresh() { if (_rows.isEmpty()) { - resize(width(), st::membersPadding.top() + st::noContactsHeight + st::membersPadding.bottom()); + resize(width(), st::membersMarginTop + st::noContactsHeight + st::membersMarginBottom); _aboutHeight = 0; } else { - _about.setText(st::boxTextFont, lng_channel_only_last_shown(lt_count, _rows.size())); - _aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom(); + _about.setText(st::boxLabelStyle, lng_channel_only_last_shown(lt_count, _rows.size())); + _aboutHeight = st::membersAboutLimitPadding.top() + _about.countHeight(_aboutWidth) + st::membersAboutLimitPadding.bottom(); if (_filter != MembersFilter::Recent || (_rows.size() >= _channel->membersCount() && _rows.size() < Global::ChatSizeMax())) { _aboutHeight = 0; } - resize(width(), st::membersPadding.top() + _newItemHeight + _rows.size() * _rowHeight + st::membersPadding.bottom() + _aboutHeight); + resize(width(), st::membersMarginTop + _aboutHeight + _rows.size() * _rowHeight + st::membersMarginBottom); } update(); } @@ -375,12 +402,17 @@ MembersAlreadyIn MembersBox::Inner::already() const { return result; } +void MembersBox::Inner::setVisibleTopBottom(int visibleTop, int visibleBottom) { + _visibleTop = visibleTop; + _visibleBottom = visibleBottom; + loadProfilePhotos(); +} + void MembersBox::Inner::clearSel() { updateSelectedRow(); - _newItemSel = false; - _sel = _kickSel = _kickDown = -1; + _selected = _kickSelected = -1; _lastMousePos = QCursor::pos(); - updateSel(); + updateSelection(); } MembersBox::Inner::MemberData *MembersBox::Inner::data(int32 index) { @@ -388,7 +420,7 @@ MembersBox::Inner::MemberData *MembersBox::Inner::data(int32 index) { return result; } MemberData *result = _datas[index] = new MemberData(); - result->name.setText(st::contactsNameFont, _rows[index]->name, _textNameOptions); + result->name.setText(st::contactsNameStyle, _rows[index]->name, _textNameOptions); int32 t = unixtime(); result->online = App::onlineText(_rows[index], t);// lng_mediaview_date_time(lt_date, _dates[index].date().toString(qsl("dd.MM.yy")), lt_time, _dates[index].time().toString(cTimeFormat())); result->onlineColor = App::onlineColorUse(_rows[index], t); @@ -418,25 +450,23 @@ MembersBox::Inner::~Inner() { clear(); } -void MembersBox::Inner::updateSel() { - if (!_mouseSel) return; +void MembersBox::Inner::updateSelection() { + if (!_mouseSelection) return; QPoint p(mapFromGlobal(_lastMousePos)); - p.setY(p.y() - st::membersPadding.top()); + p.setY(p.y() - st::membersMarginTop); bool in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(_lastMousePos)); - bool newItemSel = (in && p.y() >= 0 && p.y() < _newItemHeight); - int32 newSel = (in && !newItemSel && p.y() >= _newItemHeight && p.y() < _newItemHeight + _rows.size() * _rowHeight) ? ((p.y() - _newItemHeight) / _rowHeight) : -1; - int32 newKickSel = newSel; - if (newSel >= 0 && (!data(newSel)->canKick || !QRect(width() - _kickWidth - st::contactsPadding.right() - st::contactsCheckPosition.x(), _newItemHeight + newSel * _rowHeight + st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, _kickWidth, st::normalFont->height).contains(p))) { - newKickSel = -1; + auto selected = (in && p.y() >= 0 && p.y() < _rows.size() * _rowHeight) ? (p.y() / _rowHeight) : -1; + auto kickSelected = selected; + if (selected >= 0 && (!data(selected)->canKick || !QRect(width() - _kickWidth - st::contactsPadding.right() - st::contactsCheckPosition.x(), selected * _rowHeight + st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, _kickWidth, st::normalFont->height).contains(p))) { + kickSelected = -1; } - if (newSel != _sel || newKickSel != _kickSel || newItemSel != _newItemSel) { + if (_selected != selected || _kickSelected != kickSelected) { updateSelectedRow(); - _newItemSel = newItemSel; - _sel = newSel; - _kickSel = newKickSel; + _selected = selected; + _kickSelected = kickSelected; updateSelectedRow(); - setCursor(_kickSel >= 0 ? style::cur_pointer : style::cur_default); + setCursor(_kickSelected >= 0 ? style::cur_pointer : style::cur_default); } } @@ -444,12 +474,21 @@ void MembersBox::Inner::peerUpdated(PeerData *peer) { update(); } -void MembersBox::Inner::updateSelectedRow() { - if (_newItemSel) { - update(0, st::membersPadding.top(), width(), _newItemHeight); +int MembersBox::Inner::getSelectedRowTop() const { + if (_selected >= 0) { + return st::membersMarginTop + _selected * _rowHeight; } - if (_sel >= 0) { - update(0, st::membersPadding.top() + _newItemHeight + _sel * _rowHeight, width(), _rowHeight); + return -1; +} + +void MembersBox::Inner::updateRowWithTop(int rowTop) { + update(0, rowTop, width(), _rowHeight); +} + +void MembersBox::Inner::updateSelectedRow() { + auto rowTop = getSelectedRowTop(); + if (rowTop >= 0) { + updateRowWithTop(rowTop); } } @@ -457,8 +496,8 @@ void MembersBox::Inner::onPeerNameChanged(PeerData *peer, const PeerData::Names for (int32 i = 0, l = _rows.size(); i < l; ++i) { if (_rows.at(i) == peer) { if (_datas.at(i)) { - _datas.at(i)->name.setText(st::contactsNameFont, peer->name, _textNameOptions); - update(0, st::membersPadding.top() + i * _rowHeight, width(), _rowHeight); + _datas.at(i)->name.setText(st::contactsNameStyle, peer->name, _textNameOptions); + update(0, st::membersMarginTop + i * _rowHeight, width(), _rowHeight); } else { break; } @@ -567,20 +606,20 @@ void MembersBox::Inner::kickDone(const MTPUpdates &result, mtpRequestId req) { if (_kickRequestId != req) return; removeKicked(); - if (_kickBox) _kickBox->onClose(); + if (_kickBox) _kickBox->closeBox(); } void MembersBox::Inner::kickAdminDone(const MTPUpdates &result, mtpRequestId req) { if (_kickRequestId != req) return; if (App::main()) App::main()->sentUpdatesReceived(result); removeKicked(); - if (_kickBox) _kickBox->onClose(); + if (_kickBox) _kickBox->closeBox(); } bool MembersBox::Inner::kickFail(const RPCError &error, mtpRequestId req) { if (MTP::isDefaultHandledError(error)) return false; - if (_kickBox) _kickBox->onClose(); + if (_kickBox) _kickBox->closeBox(); load(); return true; } diff --git a/Telegram/SourceFiles/boxes/members_box.h b/Telegram/SourceFiles/boxes/members_box.h index f93f7a7b2..e40376259 100644 --- a/Telegram/SourceFiles/boxes/members_box.h +++ b/Telegram/SourceFiles/boxes/members_box.h @@ -20,9 +20,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "abstractbox.h" +#include "boxes/abstractbox.h" #include "core/single_timer.h" -#include "ui/effects/round_image_checkbox.h" +#include "ui/effects/round_checkbox.h" +#include "ui/widgets/buttons.h" class ContactsBox; class ConfirmBox; @@ -33,35 +34,53 @@ enum class MembersFilter { }; using MembersAlreadyIn = OrderedSet; -class MembersBox : public ItemListBox { +class MembersAddButton : public Ui::RippleButton { +public: + MembersAddButton(QWidget *parent, const style::TwoIconButton &st); + +protected: + void paintEvent(QPaintEvent *e) override; + + QImage prepareRippleMask() const override; + QPoint prepareRippleStartPosition() const override; + +private: + const style::TwoIconButton &_st; + +}; + +class MembersBox : public BoxContent { Q_OBJECT public: - MembersBox(ChannelData *channel, MembersFilter filter); + MembersBox(QWidget*, ChannelData *channel, MembersFilter filter); public slots: - void onScroll(); - - void onAdd(); void onAdminAdded(); protected: + void prepare() override; + void keyPressEvent(QKeyEvent *e) override; - void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; private: + void onAdd(); + + ChannelData *_channel = nullptr; + MembersFilter _filter = MembersFilter::Recent; + class Inner; - ChildWidget _inner; + QPointer _inner; - ContactsBox *_addBox = nullptr; + QPointer _addBox; - SingleTimer _loadTimer; + object_ptr _loadTimer = { nullptr }; }; // This class is hold in header because it requires Qt preprocessing. -class MembersBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber { +class MembersBox::Inner : public TWidget, public RPCSender, private base::Subscriber { Q_OBJECT public: @@ -70,7 +89,6 @@ public: void selectSkip(int32 dir); void selectSkipPage(int32 h, int32 dir); - void loadProfilePhotos(int32 yFrom); void chooseParticipant(); void refresh(); @@ -84,22 +102,19 @@ public: void clearSel(); MembersAlreadyIn already() const; + void setVisibleTopBottom(int visibleTop, int visibleBottom) override; ~Inner(); signals: void mustScrollTo(int ymin, int ymax); - void addRequested(); void loaded(); public slots: void load(); - void updateSel(); void peerUpdated(PeerData *peer); void onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars); - void onKickConfirm(); - void onKickBoxDestroyed(QObject *obj); protected: void paintEvent(QPaintEvent *e) override; @@ -111,16 +126,29 @@ protected: private: struct MemberData { + MemberData(); + ~MemberData(); + + std_::unique_ptr ripple; + int rippleRowTop = 0; Text name; QString online; bool onlineColor; bool canKick; }; + void addRipple(MemberData *data); + void stopLastRipple(MemberData *data); + void setPressed(int pressed); + void updateSelection(); + void loadProfilePhotos(); + + void updateRowWithTop(int rowTop); + int getSelectedRowTop() const; void updateSelectedRow(); MemberData *data(int32 index); - void paintDialog(Painter &p, PeerData *peer, MemberData *data, bool sel, bool kickSel, bool kickDown); + void paintDialog(Painter &p, TimeMs ms, PeerData *peer, MemberData *data, bool selected, bool kickSelected); void membersReceived(const MTPchannels_ChannelParticipants &result, mtpRequestId req); bool membersFailed(const RPCError &error, mtpRequestId req); @@ -132,22 +160,27 @@ private: void clear(); - int32 _rowHeight, _newItemHeight; - bool _newItemSel; + int _rowHeight = 0; + int _visibleTop = 0; + int _visibleBottom = 0; - ChannelData *_channel; + ChannelData *_channel = nullptr; MembersFilter _filter; QString _kickText; - int32 _time, _kickWidth; + TimeId _time = 0; + int _kickWidth = 0; - int32 _sel, _kickSel, _kickDown; - bool _mouseSel; + int _selected = -1; + int _pressed = -1; + int _kickSelected = -1; + int _kickPressed = -1; + bool _mouseSelection = false; - UserData *_kickConfirm; - mtpRequestId _kickRequestId; + UserData *_kickConfirm = nullptr; + mtpRequestId _kickRequestId = 0; - ConfirmBox *_kickBox; + QPointer _kickBox; enum class MemberRole { None, @@ -158,8 +191,8 @@ private: Kicked }; - bool _loading; - mtpRequestId _loadingRequestId; + bool _loading = true; + mtpRequestId _loadingRequestId = 0; typedef QVector MemberRows; typedef QVector MemberDates; typedef QVector MemberRoles; @@ -169,9 +202,9 @@ private: MemberRoles _roles; MemberDatas _datas; - int32 _aboutWidth; + int _aboutWidth = 0; Text _about; - int32 _aboutHeight; + int _aboutHeight = 0; QPoint _lastMousePos; diff --git a/Telegram/SourceFiles/boxes/notifications_box.cpp b/Telegram/SourceFiles/boxes/notifications_box.cpp index 98e9a4fe6..66f9b817b 100644 --- a/Telegram/SourceFiles/boxes/notifications_box.cpp +++ b/Telegram/SourceFiles/boxes/notifications_box.cpp @@ -22,8 +22,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "boxes/notifications_box.h" #include "lang.h" -#include "ui/buttons/round_button.h" -#include "ui/widgets/discrete_slider.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/discrete_sliders.h" #include "styles/style_boxes.h" #include "styles/style_dialogs.h" #include "styles/style_window.h" @@ -102,38 +102,41 @@ private: NotificationsBox *_owner; QPixmap _cache; - FloatAnimation _opacity; + Animation _opacity; bool _hiding = false; bool _deleted = false; }; -NotificationsBox::NotificationsBox() : AbstractBox() -, _chosenCorner(Global::NotificationsCorner()) +NotificationsBox::NotificationsBox(QWidget *parent) +: _chosenCorner(Global::NotificationsCorner()) , _oldCount(snap(Global::NotificationsCount(), 1, kMaxNotificationsCount)) -, _countSlider(this) -, _done(this, lang(lng_about_done), st::defaultBoxButton) { +, _countSlider(this) { +} + +void NotificationsBox::prepare() { + addButton(lang(lng_close), [this] { closeBox(); }); + _sampleOpacities.reserve(kMaxNotificationsCount); for (int i = 0; i != kMaxNotificationsCount; ++i) { _countSlider->addSection(QString::number(i + 1)); - _sampleOpacities.push_back(FloatAnimation()); + _sampleOpacities.push_back(Animation()); } _countSlider->setActiveSectionFast(_oldCount - 1); _countSlider->setSectionActivatedCallback([this] { countChanged(); }); setMouseTracking(true); - _done->setClickedCallback([this] { onClose(); }); prepareNotificationSampleSmall(); prepareNotificationSampleLarge(); - setMaxHeight(st::notificationsBoxHeight); - prepare(); + setDimensions(st::boxWideWidth, st::notificationsBoxHeight); } void NotificationsBox::paintEvent(QPaintEvent *e) { + BoxContent::paintEvent(e); + Painter p(this); - if (paint(p)) return; auto contentLeft = getContentLeft(); @@ -204,14 +207,13 @@ QRect NotificationsBox::getScreenRect() const { } void NotificationsBox::resizeEvent(QResizeEvent *e) { - _done->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _done->height()); + BoxContent::resizeEvent(e); auto screenRect = getScreenRect(); auto sliderTop = screenRect.y() + screenRect.height() + st::notificationsBoxCountLabelTop + st::notificationsBoxCountTop; auto contentLeft = getContentLeft(); _countSlider->resizeToWidth(width() - 2 * contentLeft); _countSlider->move(contentLeft, sliderTop); - AbstractBox::resizeEvent(e); } void NotificationsBox::prepareNotificationSampleSmall() { @@ -219,11 +221,12 @@ void NotificationsBox::prepareNotificationSampleSmall() { auto height = st::notificationSampleSize.height(); auto sampleImage = QImage(width * cIntRetinaFactor(), height * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); sampleImage.setDevicePixelRatio(cRetinaFactor()); - sampleImage.fill(st::notifyBg->c); + sampleImage.fill(st::notificationBg->c); { Painter p(&sampleImage); + PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); - p.setRenderHint(QPainter::HighQualityAntialiasing); auto padding = height / 8; auto userpicSize = height - 2 * padding; @@ -262,7 +265,7 @@ void NotificationsBox::prepareNotificationSampleLarge() { int w = st::notifyWidth, h = st::notifyMinHeight; auto sampleImage = QImage(w * cIntRetinaFactor(), h * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); sampleImage.setDevicePixelRatio(cRetinaFactor()); - sampleImage.fill(st::notifyBg->c); + sampleImage.fill(st::notificationBg->c); { Painter p(&sampleImage); p.fillRect(0, 0, w - st::notifyBorderWidth, st::notifyBorderWidth, st::notifyBorder->b); @@ -288,8 +291,7 @@ void NotificationsBox::prepareNotificationSampleLarge() { auto notifyTitle = st::msgNameFont->elided(qsl("Telegram Desktop"), rectForName.width()); p.drawText(rectForName.left(), rectForName.top() + st::msgNameFont->ascent, notifyTitle); - p.setOpacity(st::notifyClose.opacity); - p.drawSpriteLeft(w - st::notifyClosePos.x() - st::notifyClose.width + st::notifyClose.iconPos.x(), st::notifyClosePos.y() + st::notifyClose.iconPos.y(), w, st::notifyClose.icon); + st::notifyClose.icon.paint(p, w - st::notifyClosePos.x() - st::notifyClose.width + st::notifyClose.iconPosition.x(), st::notifyClosePos.y() + st::notifyClose.iconPosition.y(), w); } _notificationSampleLarge = App::pixmapFromImageInPlace(std_::move(sampleImage)); diff --git a/Telegram/SourceFiles/boxes/notifications_box.h b/Telegram/SourceFiles/boxes/notifications_box.h index 36bcfdbce..92c3e32a1 100644 --- a/Telegram/SourceFiles/boxes/notifications_box.h +++ b/Telegram/SourceFiles/boxes/notifications_box.h @@ -20,21 +20,21 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "abstractbox.h" - -class BoxButton; -class LinkButton; +#include "boxes/abstractbox.h" namespace Ui { -class DiscreteSlider; +class LinkButton; +class SettingsSlider; } // namespace Ui -class NotificationsBox : public AbstractBox { +class NotificationsBox : public BoxContent { public: - NotificationsBox(); + NotificationsBox(QWidget*); ~NotificationsBox(); protected: + void prepare() override; + void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void mousePressEvent(QMouseEvent *e) override; @@ -63,7 +63,7 @@ private: QPixmap _notificationSampleSmall; QPixmap _notificationSampleLarge; ScreenCorner _chosenCorner; - std_::vector_of_moveable _sampleOpacities; + std_::vector_of_moveable _sampleOpacities; bool _isOverCorner = false; ScreenCorner _overCorner = ScreenCorner::TopLeft; @@ -71,8 +71,7 @@ private: ScreenCorner _downCorner = ScreenCorner::TopLeft; int _oldCount; - ChildWidget _countSlider; - ChildWidget _done; + object_ptr _countSlider; QVector _cornerSamples[4]; diff --git a/Telegram/SourceFiles/boxes/passcodebox.cpp b/Telegram/SourceFiles/boxes/passcodebox.cpp index d0ef9b754..767612349 100644 --- a/Telegram/SourceFiles/boxes/passcodebox.cpp +++ b/Telegram/SourceFiles/boxes/passcodebox.cpp @@ -19,302 +19,244 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "lang.h" +#include "boxes/passcodebox.h" -#include "passcodebox.h" +#include "lang.h" #include "confirmbox.h" #include "mainwindow.h" - #include "localstorage.h" +#include "styles/style_boxes.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" -PasscodeBox::PasscodeBox(bool turningOff) : AbstractBox(st::boxWidth) -, _replacedBy(0) -, _turningOff(turningOff) -, _cloudPwd(false) -, _setRequest(0) -, _hasRecovery(false) -, _skipEmailWarning(false) -, _aboutHeight(0) +PasscodeBox::PasscodeBox(QWidget*, bool turningOff) +: _turningOff(turningOff) , _about(st::boxWidth - st::boxPadding.left() * 1.5) -, _saveButton(this, lang(_turningOff ? lng_passcode_remove_button : lng_settings_save), st::defaultBoxButton) -, _cancelButton(this, lang(lng_cancel), st::cancelBoxButton) , _oldPasscode(this, st::defaultInputField, lang(lng_passcode_enter_old)) , _newPasscode(this, st::defaultInputField, lang(Global::LocalPasscode() ? lng_passcode_enter_new : lng_passcode_enter_first)) , _reenterPasscode(this, st::defaultInputField, lang(lng_passcode_confirm_new)) , _passwordHint(this, st::defaultInputField, lang(lng_cloud_password_hint)) , _recoverEmail(this, st::defaultInputField, lang(lng_cloud_password_email)) , _recover(this, lang(lng_signin_recover)) { - init(); - prepare(); } -PasscodeBox::PasscodeBox(const QByteArray &newSalt, const QByteArray &curSalt, bool hasRecovery, const QString &hint, bool turningOff) : AbstractBox(st::boxWidth) -, _replacedBy(0) -, _turningOff(turningOff) +PasscodeBox::PasscodeBox(QWidget*, const QByteArray &newSalt, const QByteArray &curSalt, bool hasRecovery, const QString &hint, bool turningOff) +: _turningOff(turningOff) , _cloudPwd(true) -, _setRequest(0) , _newSalt(newSalt) , _curSalt(curSalt) , _hasRecovery(hasRecovery) -, _skipEmailWarning(false) -, _aboutHeight(0) , _about(st::boxWidth - st::boxPadding.left() * 1.5) -, _saveButton(this, lang(_turningOff ? lng_passcode_remove_button : lng_settings_save), st::defaultBoxButton) -, _cancelButton(this, lang(lng_cancel), st::cancelBoxButton) , _oldPasscode(this, st::defaultInputField, lang(lng_cloud_password_enter_old)) , _newPasscode(this, st::defaultInputField, lang(curSalt.isEmpty() ? lng_cloud_password_enter_first : lng_cloud_password_enter_new)) , _reenterPasscode(this, st::defaultInputField, lang(lng_cloud_password_confirm_new)) , _passwordHint(this, st::defaultInputField, lang(curSalt.isEmpty() ? lng_cloud_password_hint : lng_cloud_password_change_hint)) , _recoverEmail(this, st::defaultInputField, lang(lng_cloud_password_email)) , _recover(this, lang(lng_signin_recover)) { - textstyleSet(&st::usernameTextStyle); - if (!hint.isEmpty()) _hintText.setText(st::normalFont, lng_signin_hint(lt_password_hint, hint)); - textstyleRestore(); - init(); - prepare(); + if (!hint.isEmpty()) _hintText.setText(st::passcodeTextStyle, lng_signin_hint(lt_password_hint, hint)); } -void PasscodeBox::init() { - setBlueTitle(true); +void PasscodeBox::prepare() { + addButton(lang(_turningOff ? lng_passcode_remove_button : lng_settings_save), [this] { onSave(); }); + addButton(lang(lng_cancel), [this] { closeBox(); }); - textstyleSet(&st::usernameTextStyle); - _about.setRichText(st::normalFont, lang(_cloudPwd ? lng_cloud_password_about : lng_passcode_about)); + _about.setRichText(st::passcodeTextStyle, lang(_cloudPwd ? lng_cloud_password_about : lng_passcode_about)); _aboutHeight = _about.countHeight(st::boxWidth - st::boxPadding.left() * 1.5); - textstyleRestore(); if (_turningOff) { - _oldPasscode.show(); - _boxTitle = lang(_cloudPwd ? lng_cloud_password_remove : lng_passcode_remove); - setMaxHeight(st::boxTitleHeight + st::passcodePadding.top() + _oldPasscode.height() + st::passcodeSkip + ((_hasRecovery && !_hintText.isEmpty()) ? st::passcodeSkip : 0) + _aboutHeight + st::passcodePadding.bottom() + st::boxButtonPadding.top() + _saveButton.height() + st::boxButtonPadding.bottom()); + _oldPasscode->show(); + setTitle(lang(_cloudPwd ? lng_cloud_password_remove : lng_passcode_remove)); + setDimensions(st::boxWidth, st::passcodePadding.top() + _oldPasscode->height() + st::passcodeTextLine + ((_hasRecovery && !_hintText.isEmpty()) ? st::passcodeTextLine : 0) + st::passcodeAboutSkip + _aboutHeight + st::passcodePadding.bottom()); } else { - bool has = _cloudPwd ? (!_curSalt.isEmpty()) : Global::LocalPasscode(); + auto has = _cloudPwd ? (!_curSalt.isEmpty()) : Global::LocalPasscode(); if (has) { - _oldPasscode.show(); - _boxTitle = lang(_cloudPwd ? lng_cloud_password_change : lng_passcode_change); - setMaxHeight(st::boxTitleHeight + st::passcodePadding.top() + _oldPasscode.height() + st::passcodeSkip + ((_hasRecovery && !_hintText.isEmpty()) ? st::passcodeSkip : 0) + _newPasscode.height() + st::contactSkip + _reenterPasscode.height() + st::passcodeSkip + (_cloudPwd ? _passwordHint.height() + st::contactSkip : 0) + _aboutHeight + st::passcodePadding.bottom() + st::boxButtonPadding.top() + _saveButton.height() + st::boxButtonPadding.bottom()); + _oldPasscode->show(); + setTitle(lang(_cloudPwd ? lng_cloud_password_change : lng_passcode_change)); + setDimensions(st::boxWidth, st::passcodePadding.top() + _oldPasscode->height() + st::passcodeTextLine + ((_hasRecovery && !_hintText.isEmpty()) ? st::passcodeTextLine : 0) + _newPasscode->height() + st::passcodeLittleSkip + _reenterPasscode->height() + st::passcodeSkip + (_cloudPwd ? _passwordHint->height() + st::passcodeLittleSkip : 0) + st::passcodeAboutSkip + _aboutHeight + st::passcodePadding.bottom()); } else { - _oldPasscode.hide(); - _boxTitle = lang(_cloudPwd ? lng_cloud_password_create : lng_passcode_create); - setMaxHeight(st::boxTitleHeight + st::passcodePadding.top() + _newPasscode.height() + st::contactSkip + _reenterPasscode.height() + st::passcodeSkip + (_cloudPwd ? _passwordHint.height() + st::contactSkip : 0) + _aboutHeight + (_cloudPwd ? st::contactSkip + _recoverEmail.height() + st::passcodeSkip : st::passcodePadding.bottom()) + st::boxButtonPadding.top() + _saveButton.height() + st::boxButtonPadding.bottom()); + _oldPasscode->hide(); + setTitle(lang(_cloudPwd ? lng_cloud_password_create : lng_passcode_create)); + setDimensions(st::boxWidth, st::passcodePadding.top() + _newPasscode->height() + st::passcodeLittleSkip + _reenterPasscode->height() + st::passcodeSkip + (_cloudPwd ? _passwordHint->height() + st::passcodeLittleSkip : 0) + st::passcodeAboutSkip + _aboutHeight + (_cloudPwd ? (st::passcodeLittleSkip + _recoverEmail->height() + st::passcodeSkip) : st::passcodePadding.bottom())); } } - connect(&_saveButton, SIGNAL(clicked()), this, SLOT(onSave())); - connect(&_cancelButton, SIGNAL(clicked()), this, SLOT(onClose())); + connect(_oldPasscode, SIGNAL(changed()), this, SLOT(onOldChanged())); + connect(_newPasscode, SIGNAL(changed()), this, SLOT(onNewChanged())); + connect(_reenterPasscode, SIGNAL(changed()), this, SLOT(onNewChanged())); + connect(_passwordHint, SIGNAL(changed()), this, SLOT(onNewChanged())); + connect(_recoverEmail, SIGNAL(changed()), this, SLOT(onEmailChanged())); - connect(&_oldPasscode, SIGNAL(changed()), this, SLOT(onOldChanged())); - connect(&_newPasscode, SIGNAL(changed()), this, SLOT(onNewChanged())); - connect(&_reenterPasscode, SIGNAL(changed()), this, SLOT(onNewChanged())); - connect(&_passwordHint, SIGNAL(changed()), this, SLOT(onNewChanged())); - connect(&_recoverEmail, SIGNAL(changed()), this, SLOT(onEmailChanged())); + connect(_oldPasscode, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); + connect(_newPasscode, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); + connect(_reenterPasscode, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); + connect(_passwordHint, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); + connect(_recoverEmail, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); - connect(&_oldPasscode, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); - connect(&_newPasscode, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); - connect(&_reenterPasscode, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); - connect(&_passwordHint, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); - connect(&_recoverEmail, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); + connect(_recover, SIGNAL(clicked()), this, SLOT(onRecoverByEmail())); - connect(&_recover, SIGNAL(clicked()), this, SLOT(onRecoverByEmail())); -} - -void PasscodeBox::showAll() { bool has = _cloudPwd ? (!_curSalt.isEmpty()) : Global::LocalPasscode(); - if (_turningOff) { - _oldPasscode.show(); - if (_cloudPwd && _hasRecovery) { - _recover.show(); - } else { - _recover.hide(); - } - _newPasscode.hide(); - _reenterPasscode.hide(); - _passwordHint.hide(); - _recoverEmail.hide(); - } else { - if (has) { - _oldPasscode.show(); - if (_cloudPwd && _hasRecovery) { - _recover.show(); - } else { - _recover.hide(); - } - } else { - _oldPasscode.hide(); - _recover.hide(); - } - _newPasscode.show(); - _reenterPasscode.show(); - if (_cloudPwd) { - _passwordHint.show(); - } else { - _passwordHint.hide(); - } - if (_cloudPwd && _curSalt.isEmpty()) { - _recoverEmail.show(); - } else { - _recoverEmail.hide(); - } - } - _saveButton.show(); - _cancelButton.show(); - AbstractBox::showAll(); + _oldPasscode->setVisible(_turningOff || has); + _recover->setVisible((_turningOff || has) && _cloudPwd && _hasRecovery); + _newPasscode->setVisible(!_turningOff); + _reenterPasscode->setVisible(!_turningOff); + _passwordHint->setVisible(!_turningOff && _cloudPwd); + _recoverEmail->setVisible(!_turningOff && _cloudPwd && _curSalt.isEmpty()); } void PasscodeBox::onSubmit() { bool has = _cloudPwd ? (!_curSalt.isEmpty()) : Global::LocalPasscode(); - if (_oldPasscode.hasFocus()) { + if (_oldPasscode->hasFocus()) { if (_turningOff) { onSave(); } else { - _newPasscode.setFocus(); + _newPasscode->setFocus(); } - } else if (_newPasscode.hasFocus()) { - _reenterPasscode.setFocus(); - } else if (_reenterPasscode.hasFocus()) { - if (has && _oldPasscode.text().isEmpty()) { - _oldPasscode.setFocus(); - _oldPasscode.showError(); - } else if (_newPasscode.text().isEmpty()) { - _newPasscode.setFocus(); - _newPasscode.showError(); - } else if (_reenterPasscode.text().isEmpty()) { - _reenterPasscode.showError(); - } else if (!_passwordHint.isHidden()) { - _passwordHint.setFocus(); + } else if (_newPasscode->hasFocus()) { + _reenterPasscode->setFocus(); + } else if (_reenterPasscode->hasFocus()) { + if (has && _oldPasscode->text().isEmpty()) { + _oldPasscode->setFocus(); + _oldPasscode->showError(); + } else if (_newPasscode->text().isEmpty()) { + _newPasscode->setFocus(); + _newPasscode->showError(); + } else if (_reenterPasscode->text().isEmpty()) { + _reenterPasscode->showError(); + } else if (!_passwordHint->isHidden()) { + _passwordHint->setFocus(); } else { onSave(); } - } else if (_passwordHint.hasFocus()) { - if (_recoverEmail.isHidden()) { + } else if (_passwordHint->hasFocus()) { + if (_recoverEmail->isHidden()) { onSave(); } else { - _recoverEmail.setFocus(); + _recoverEmail->setFocus(); } - } else if (_recoverEmail.hasFocus()) { + } else if (_recoverEmail->hasFocus()) { onSave(); } } void PasscodeBox::paintEvent(QPaintEvent *e) { + BoxContent::paintEvent(e); + Painter p(this); - if (paint(p)) return; - - paintTitle(p, _boxTitle); - - textstyleSet(&st::usernameTextStyle); int32 w = st::boxWidth - st::boxPadding.left() * 1.5; - int32 abouty = (_passwordHint.isHidden() ? (_reenterPasscode.isHidden() ? (_oldPasscode.y() + (_hasRecovery && !_hintText.isEmpty() ? st::passcodeSkip : 0)) : _reenterPasscode.y()) + st::passcodeSkip : _passwordHint.y() + st::contactSkip) + _oldPasscode.height(); - p.setPen(st::black); + int32 abouty = (_passwordHint->isHidden() ? ((_reenterPasscode->isHidden() ? (_oldPasscode->y() + (_hasRecovery && !_hintText.isEmpty() ? st::passcodeTextLine : 0)) : _reenterPasscode->y()) + st::passcodeSkip) : (_passwordHint->y() + st::passcodeLittleSkip)) + _oldPasscode->height() + st::passcodeLittleSkip + st::passcodeAboutSkip; + p.setPen(st::boxTextFg); _about.drawLeft(p, st::boxPadding.left(), abouty, w, width()); if (!_hintText.isEmpty() && _oldError.isEmpty()) { - p.setPen(st::black->p); - _hintText.drawLeftElided(p, st::boxPadding.left(), _oldPasscode.y() + _oldPasscode.height() + ((st::passcodeSkip - st::normalFont->height) / 2), w, width(), 1, style::al_topleft); + _hintText.drawLeftElided(p, st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height() + ((st::passcodeTextLine - st::normalFont->height) / 2), w, width(), 1, style::al_topleft); } if (!_oldError.isEmpty()) { - p.setPen(st::setErrColor->p); - p.drawText(QRect(st::boxPadding.left(), _oldPasscode.y() + _oldPasscode.height(), w, st::passcodeSkip), _oldError, style::al_left); + p.setPen(st::boxTextFgError); + p.drawText(QRect(st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height(), w, st::passcodeTextLine), _oldError, style::al_left); } if (!_newError.isEmpty()) { - p.setPen(st::setErrColor->p); - p.drawText(QRect(st::boxPadding.left(), _reenterPasscode.y() + _reenterPasscode.height(), w, st::passcodeSkip), _newError, style::al_left); + p.setPen(st::boxTextFgError); + p.drawText(QRect(st::boxPadding.left(), _reenterPasscode->y() + _reenterPasscode->height(), w, st::passcodeTextLine), _newError, style::al_left); } if (!_emailError.isEmpty()) { - p.setPen(st::setErrColor->p); - p.drawText(QRect(st::boxPadding.left(), _recoverEmail.y() + _recoverEmail.height(), w, st::passcodeSkip), _emailError, style::al_left); + p.setPen(st::boxTextFgError); + p.drawText(QRect(st::boxPadding.left(), _recoverEmail->y() + _recoverEmail->height(), w, st::passcodeTextLine), _emailError, style::al_left); } - - textstyleRestore(); } void PasscodeBox::resizeEvent(QResizeEvent *e) { + BoxContent::resizeEvent(e); + bool has = _cloudPwd ? (!_curSalt.isEmpty()) : Global::LocalPasscode(); int32 w = st::boxWidth - st::boxPadding.left() - st::boxPadding.right(); - _oldPasscode.resize(w, _oldPasscode.height()); - _oldPasscode.moveToLeft(st::boxPadding.left(), st::boxTitleHeight + st::passcodePadding.top()); - _newPasscode.resize(w, _newPasscode.height()); - _newPasscode.moveToLeft(st::boxPadding.left(), _oldPasscode.y() + ((_turningOff || has) ? (_oldPasscode.height() + st::passcodeSkip + ((_hasRecovery && !_hintText.isEmpty()) ? st::passcodeSkip : 0)) : 0)); - _reenterPasscode.resize(w, _reenterPasscode.height()); - _reenterPasscode.moveToLeft(st::boxPadding.left(), _newPasscode.y() + _newPasscode.height() + st::contactSkip); - _passwordHint.resize(w, _passwordHint.height()); - _passwordHint.moveToLeft(st::boxPadding.left(), _reenterPasscode.y() + _reenterPasscode.height() + st::passcodeSkip); - _recoverEmail.resize(w, _passwordHint.height()); - _recoverEmail.moveToLeft(st::boxPadding.left(), _passwordHint.y() + _passwordHint.height() + st::contactSkip + _aboutHeight + st::contactSkip); + _oldPasscode->resize(w, _oldPasscode->height()); + _oldPasscode->moveToLeft(st::boxPadding.left(), st::passcodePadding.top()); + _newPasscode->resize(w, _newPasscode->height()); + _newPasscode->moveToLeft(st::boxPadding.left(), _oldPasscode->y() + ((_turningOff || has) ? (_oldPasscode->height() + st::passcodeTextLine + ((_hasRecovery && !_hintText.isEmpty()) ? st::passcodeTextLine : 0)) : 0)); + _reenterPasscode->resize(w, _reenterPasscode->height()); + _reenterPasscode->moveToLeft(st::boxPadding.left(), _newPasscode->y() + _newPasscode->height() + st::passcodeLittleSkip); + _passwordHint->resize(w, _passwordHint->height()); + _passwordHint->moveToLeft(st::boxPadding.left(), _reenterPasscode->y() + _reenterPasscode->height() + st::passcodeSkip); + _recoverEmail->resize(w, _passwordHint->height()); + _recoverEmail->moveToLeft(st::boxPadding.left(), _passwordHint->y() + _passwordHint->height() + st::passcodeLittleSkip + _aboutHeight + st::passcodeLittleSkip); - if (!_recover.isHidden()) { - _recover.moveToLeft(st::boxPadding.left(), _oldPasscode.y() + _oldPasscode.height() + (_hintText.isEmpty() ? ((st::passcodeSkip - _recover.height()) / 2) : st::passcodeSkip)); + if (!_recover->isHidden()) { + _recover->moveToLeft(st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height() + (_hintText.isEmpty() ? ((st::passcodeTextLine - _recover->height()) / 2) : st::passcodeTextLine)); } - - _saveButton.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _saveButton.height()); - _cancelButton.moveToRight(st::boxButtonPadding.right() + _saveButton.width() + st::boxButtonPadding.left(), _saveButton.y()); - - AbstractBox::resizeEvent(e); } -void PasscodeBox::doSetInnerFocus() { - if (_skipEmailWarning && !_recoverEmail.isHidden()) { - _recoverEmail.setFocus(); - } else if (_oldPasscode.isHidden()) { - _newPasscode.setFocus(); +void PasscodeBox::setInnerFocus() { + if (_skipEmailWarning && !_recoverEmail->isHidden()) { + _recoverEmail->setFocusFast(); + } else if (_oldPasscode->isHidden()) { + _newPasscode->setFocusFast(); } else { - _oldPasscode.setFocus(); + _oldPasscode->setFocusFast(); } } void PasscodeBox::setPasswordDone(const MTPBool &result) { _setRequest = 0; emit reloadPassword(); - ConfirmBox *box = new InformBox(lang(_reenterPasscode.isHidden() ? lng_cloud_password_removed : (_oldPasscode.isHidden() ? lng_cloud_password_was_set : lng_cloud_password_updated))); - Ui::showLayer(box); + auto text = lang(_reenterPasscode->isHidden() ? lng_cloud_password_removed : (_oldPasscode->isHidden() ? lng_cloud_password_was_set : lng_cloud_password_updated)); + Ui::show(Box(text)); +} + +void PasscodeBox::closeReplacedBy() { + if (isHidden()) { + if (_replacedBy && !_replacedBy->isHidden()) { + _replacedBy->closeBox(); + } + } } bool PasscodeBox::setPasswordFail(const RPCError &error) { if (MTP::isFloodError(error)) { - if (_oldPasscode.isHidden()) return false; + if (_oldPasscode->isHidden()) return false; - if (isHidden() && _replacedBy && !_replacedBy->isHidden()) _replacedBy->onClose(); + closeReplacedBy(); _setRequest = 0; - _oldPasscode.selectAll(); - _oldPasscode.setFocus(); - _oldPasscode.showError(); + _oldPasscode->selectAll(); + _oldPasscode->setFocus(); + _oldPasscode->showError(); _oldError = lang(lng_flood_error); if (_hasRecovery && _hintText.isEmpty()) { - _recover.hide(); + _recover->hide(); } update(); return true; } if (MTP::isDefaultHandledError(error)) return false; - if (isHidden() && _replacedBy && !_replacedBy->isHidden()) _replacedBy->onClose(); + closeReplacedBy(); _setRequest = 0; QString err = error.type(); if (err == qstr("PASSWORD_HASH_INVALID")) { - if (_oldPasscode.isHidden()) { + if (_oldPasscode->isHidden()) { emit reloadPassword(); - onClose(); + closeBox(); } else { onBadOldPasscode(); } } else if (err == qstr("NEW_PASSWORD_BAD")) { - _newPasscode.setFocus(); - _newPasscode.showError(); + _newPasscode->setFocus(); + _newPasscode->showError(); _newError = lang(lng_cloud_password_bad); update(); } else if (err == qstr("NEW_SALT_INVALID")) { emit reloadPassword(); - onClose(); + closeBox(); } else if (err == qstr("EMAIL_INVALID")) { _emailError = lang(lng_cloud_password_bad_email); - _recoverEmail.setFocus(); - _recoverEmail.showError(); + _recoverEmail->setFocus(); + _recoverEmail->showError(); update(); } else if (err == qstr("EMAIL_UNCONFIRMED")) { - Ui::showLayer(new InformBox(lang(lng_cloud_password_almost))); + Ui::show(Box(lang(lng_cloud_password_almost))); emit reloadPassword(); } return true; @@ -323,13 +265,13 @@ bool PasscodeBox::setPasswordFail(const RPCError &error) { void PasscodeBox::onSave(bool force) { if (_setRequest) return; - QString old = _oldPasscode.text(), pwd = _newPasscode.text(), conf = _reenterPasscode.text(); + QString old = _oldPasscode->text(), pwd = _newPasscode->text(), conf = _reenterPasscode->text(); bool has = _cloudPwd ? (!_curSalt.isEmpty()) : Global::LocalPasscode(); if (!_cloudPwd && (_turningOff || has)) { if (!passcodeCanTry()) { _oldError = lang(lng_flood_error); - _oldPasscode.setFocus(); - _oldPasscode.showError(); + _oldPasscode->setFocus(); + _oldPasscode->showError(); update(); return; } @@ -345,42 +287,41 @@ void PasscodeBox::onSave(bool force) { } } if (!_turningOff && pwd.isEmpty()) { - _newPasscode.setFocus(); - _newPasscode.showError(); - if (isHidden() && _replacedBy && !_replacedBy->isHidden()) _replacedBy->onClose(); + _newPasscode->setFocus(); + _newPasscode->showError(); + closeReplacedBy(); return; } if (pwd != conf) { - _reenterPasscode.selectAll(); - _reenterPasscode.setFocus(); - _reenterPasscode.showError(); + _reenterPasscode->selectAll(); + _reenterPasscode->setFocus(); + _reenterPasscode->showError(); if (!conf.isEmpty()) { _newError = lang(_cloudPwd ? lng_cloud_password_differ : lng_passcode_differ); update(); } - if (isHidden() && _replacedBy && !_replacedBy->isHidden()) _replacedBy->onClose(); + closeReplacedBy(); } else if (!_turningOff && has && old == pwd) { - _newPasscode.setFocus(); - _newPasscode.showError(); + _newPasscode->setFocus(); + _newPasscode->showError(); _newError = lang(_cloudPwd ? lng_cloud_password_is_same : lng_passcode_is_same); update(); - if (isHidden() && _replacedBy && !_replacedBy->isHidden()) _replacedBy->onClose(); + closeReplacedBy(); } else if (_cloudPwd) { - QString hint = _passwordHint.getLastText(), email = _recoverEmail.getLastText().trimmed(); - if (_cloudPwd && pwd == hint && !_passwordHint.isHidden() && !_newPasscode.isHidden()) { - _newPasscode.setFocus(); - _newPasscode.showError(); + QString hint = _passwordHint->getLastText(), email = _recoverEmail->getLastText().trimmed(); + if (_cloudPwd && pwd == hint && !_passwordHint->isHidden() && !_newPasscode->isHidden()) { + _newPasscode->setFocus(); + _newPasscode->showError(); _newError = lang(lng_cloud_password_bad); update(); - if (isHidden() && _replacedBy && !_replacedBy->isHidden()) _replacedBy->onClose(); + closeReplacedBy(); return; } - if (!_recoverEmail.isHidden() && email.isEmpty() && !force) { + if (!_recoverEmail->isHidden() && email.isEmpty() && !force) { _skipEmailWarning = true; - _replacedBy = new ConfirmBox(lang(lng_cloud_password_about_recover), lang(lng_cloud_password_skip_email), st::attentionBoxButton); - connect(_replacedBy, SIGNAL(confirmed()), this, SLOT(onForceNoMail())); - connect(_replacedBy, SIGNAL(destroyed(QObject*)), this, SLOT(onBoxDestroyed(QObject*))); - Ui::showLayer(_replacedBy, KeepOtherLayers); + _replacedBy = Ui::show(Box(lang(lng_cloud_password_about_recover), lang(lng_cloud_password_skip_email), st::attentionBoxButton, base::lambda_guarded(this, [this] { + onSave(true); + })), KeepOtherLayers); } else { QByteArray newPasswordData = pwd.isEmpty() ? QByteArray() : (_newSalt + pwd.toUtf8() + _newSalt); QByteArray newPasswordHash = pwd.isEmpty() ? QByteArray() : QByteArray(32, Qt::Uninitialized); @@ -390,13 +331,13 @@ void PasscodeBox::onSave(bool force) { } else { hashSha256(newPasswordData.constData(), newPasswordData.size(), newPasswordHash.data()); } - QByteArray oldPasswordData = _oldPasscode.isHidden() ? QByteArray() : (_curSalt + old.toUtf8() + _curSalt); - QByteArray oldPasswordHash = _oldPasscode.isHidden() ? QByteArray() : QByteArray(32, Qt::Uninitialized); - if (!_oldPasscode.isHidden()) { + QByteArray oldPasswordData = _oldPasscode->isHidden() ? QByteArray() : (_curSalt + old.toUtf8() + _curSalt); + QByteArray oldPasswordHash = _oldPasscode->isHidden() ? QByteArray() : QByteArray(32, Qt::Uninitialized); + if (!_oldPasscode->isHidden()) { hashSha256(oldPasswordData.constData(), oldPasswordData.size(), oldPasswordHash.data()); } MTPDaccount_passwordInputSettings::Flags flags = MTPDaccount_passwordInputSettings::Flag::f_new_salt | MTPDaccount_passwordInputSettings::Flag::f_new_password_hash | MTPDaccount_passwordInputSettings::Flag::f_hint; - if (_oldPasscode.isHidden() || _newPasscode.isHidden()) { + if (_oldPasscode->isHidden() || _newPasscode->isHidden()) { flags |= MTPDaccount_passwordInputSettings::Flag::f_email; } MTPaccount_PasswordInputSettings settings(MTP_account_passwordInputSettings(MTP_flags(flags), MTP_bytes(_newSalt), MTP_bytes(newPasswordHash), MTP_string(hint), MTP_string(email))); @@ -406,18 +347,17 @@ void PasscodeBox::onSave(bool force) { cSetPasscodeBadTries(0); Local::setPasscode(pwd.toUtf8()); App::wnd()->checkAutoLock(); - App::wnd()->getTitle()->updateControlsVisibility(); - onClose(); + closeBox(); } } void PasscodeBox::onBadOldPasscode() { - _oldPasscode.selectAll(); - _oldPasscode.setFocus(); - _oldPasscode.showError(); + _oldPasscode->selectAll(); + _oldPasscode->setFocus(); + _oldPasscode->showError(); _oldError = lang(_cloudPwd ? lng_cloud_password_wrong : lng_passcode_wrong); if (_hasRecovery && _hintText.isEmpty()) { - _recover.hide(); + _recover->hide(); } update(); } @@ -426,7 +366,7 @@ void PasscodeBox::onOldChanged() { if (!_oldError.isEmpty()) { _oldError = QString(); if (_hasRecovery && _hintText.isEmpty()) { - _recover.show(); + _recover->show(); } update(); } @@ -446,16 +386,6 @@ void PasscodeBox::onEmailChanged() { } } -void PasscodeBox::onForceNoMail() { - onSave(true); -} - -void PasscodeBox::onBoxDestroyed(QObject *obj) { - if (obj == _replacedBy) { - _replacedBy = 0; - } -} - void PasscodeBox::onRecoverByEmail() { if (_pattern.isEmpty()) { _pattern = "-"; @@ -472,11 +402,9 @@ void PasscodeBox::onRecoverExpired() { void PasscodeBox::recover() { if (_pattern == "-") return; - _replacedBy = new RecoverBox(_pattern); + _replacedBy = Ui::show(Box(_pattern), KeepOtherLayers); connect(_replacedBy, SIGNAL(reloadPassword()), this, SIGNAL(reloadPassword())); connect(_replacedBy, SIGNAL(recoveryExpired()), this, SLOT(onRecoverExpired())); - connect(_replacedBy, SIGNAL(destroyed(QObject*)), this, SLOT(onBoxDestroyed(QObject*))); - Ui::showLayer(_replacedBy, KeepOtherLayers); } void PasscodeBox::recoverStarted(const MTPauth_PasswordRecovery &result) { @@ -488,74 +416,61 @@ bool PasscodeBox::recoverStartFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; _pattern = QString(); - onClose(); + closeBox(); return true; } -RecoverBox::RecoverBox(const QString &pattern) : AbstractBox(st::boxWidth) -, _submitRequest(0) -, _pattern(st::normalFont->elided(lng_signin_recover_hint(lt_recover_email, pattern), st::boxWidth - st::boxPadding.left() * 1.5)) -, _saveButton(this, lang(lng_passcode_submit), st::defaultBoxButton) -, _cancelButton(this, lang(lng_cancel), st::cancelBoxButton) +RecoverBox::RecoverBox(QWidget*, const QString &pattern) +: _pattern(st::normalFont->elided(lng_signin_recover_hint(lt_recover_email, pattern), st::boxWidth - st::boxPadding.left() * 1.5)) , _recoverCode(this, st::defaultInputField, lang(lng_signin_code)) { - setBlueTitle(true); - - setMaxHeight(st::boxTitleHeight + st::passcodePadding.top() + st::passcodeSkip + _recoverCode.height() + st::passcodeSkip + st::passcodePadding.bottom() + st::boxButtonPadding.top() + _saveButton.height() + st::boxButtonPadding.bottom()); - - connect(&_saveButton, SIGNAL(clicked()), this, SLOT(onSubmit())); - connect(&_cancelButton, SIGNAL(clicked()), this, SLOT(onClose())); - - connect(&_recoverCode, SIGNAL(changed()), this, SLOT(onCodeChanged())); - connect(&_recoverCode, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); - - prepare(); } -void RecoverBox::showAll() { - _recoverCode.show(); - _saveButton.show(); - _cancelButton.show(); - AbstractBox::showAll(); +void RecoverBox::prepare() { + setTitle(lang(lng_signin_recover_title)); + + addButton(lang(lng_passcode_submit), [this] { onSubmit(); }); + addButton(lang(lng_cancel), [this] { closeBox(); }); + + setDimensions(st::boxWidth, st::passcodePadding.top() + st::passcodePadding.bottom() + st::passcodeTextLine + _recoverCode->height() + st::passcodeTextLine); + + connect(_recoverCode, SIGNAL(changed()), this, SLOT(onCodeChanged())); + connect(_recoverCode, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); } void RecoverBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; + BoxContent::paintEvent(e); - paintTitle(p, lang(lng_signin_recover_title)); + Painter p(this); p.setFont(st::normalFont); - p.setPen(st::black); + p.setPen(st::boxTextFg); int32 w = st::boxWidth - st::boxPadding.left() * 1.5; - p.drawText(QRect(st::boxPadding.left(), _recoverCode.y() - st::passcodeSkip - st::passcodePadding.top(), w, st::passcodePadding.top() + st::passcodeSkip), _pattern, style::al_left); + p.drawText(QRect(st::boxPadding.left(), _recoverCode->y() - st::passcodeTextLine - st::passcodePadding.top(), w, st::passcodePadding.top() + st::passcodeTextLine), _pattern, style::al_left); if (!_error.isEmpty()) { - p.setPen(st::setErrColor->p); - p.drawText(QRect(st::boxPadding.left(), _recoverCode.y() + _recoverCode.height(), w, st::passcodeSkip), _error, style::al_left); + p.setPen(st::boxTextFgError); + p.drawText(QRect(st::boxPadding.left(), _recoverCode->y() + _recoverCode->height(), w, st::passcodeTextLine), _error, style::al_left); } } void RecoverBox::resizeEvent(QResizeEvent *e) { - _recoverCode.resize(st::boxWidth - st::boxPadding.left() - st::boxPadding.right(), _recoverCode.height()); - _recoverCode.moveToLeft(st::boxPadding.left(), st::boxTitleHeight + st::passcodePadding.top() + st::passcodeSkip); + BoxContent::resizeEvent(e); - _saveButton.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _saveButton.height()); - _cancelButton.moveToRight(st::boxButtonPadding.right() + _saveButton.width() + st::boxButtonPadding.left(), _saveButton.y()); - - AbstractBox::resizeEvent(e); + _recoverCode->resize(st::boxWidth - st::boxPadding.left() - st::boxPadding.right(), _recoverCode->height()); + _recoverCode->moveToLeft(st::boxPadding.left(), st::passcodePadding.top() + st::passcodePadding.bottom() + st::passcodeTextLine); } -void RecoverBox::doSetInnerFocus() { - _recoverCode.setFocus(); +void RecoverBox::setInnerFocus() { + _recoverCode->setFocusFast(); } void RecoverBox::onSubmit() { if (_submitRequest) return; - QString code = _recoverCode.getLastText().trimmed(); + QString code = _recoverCode->getLastText().trimmed(); if (code.isEmpty()) { - _recoverCode.setFocus(); - _recoverCode.showError(); + _recoverCode->setFocus(); + _recoverCode->showError(); return; } @@ -571,7 +486,7 @@ void RecoverBox::codeSubmitDone(bool recover, const MTPauth_Authorization &resul _submitRequest = 0; emit reloadPassword(); - Ui::showLayer(new InformBox(lang(lng_cloud_password_removed))); + Ui::show(Box(lang(lng_cloud_password_removed))); } bool RecoverBox::codeSubmitFail(const RPCError &error) { @@ -579,7 +494,7 @@ bool RecoverBox::codeSubmitFail(const RPCError &error) { _submitRequest = 0; _error = lang(lng_flood_error); update(); - _recoverCode.showError(); + _recoverCode->showError(); return true; } if (MTP::isDefaultHandledError(error)) return false; @@ -589,21 +504,21 @@ bool RecoverBox::codeSubmitFail(const RPCError &error) { const QString &err = error.type(); if (err == qstr("PASSWORD_EMPTY")) { emit reloadPassword(); - Ui::showLayer(new InformBox(lang(lng_cloud_password_removed))); + Ui::show(Box(lang(lng_cloud_password_removed))); return true; } else if (err == qstr("PASSWORD_RECOVERY_NA")) { - onClose(); + closeBox(); return true; } else if (err == qstr("PASSWORD_RECOVERY_EXPIRED")) { emit recoveryExpired(); - onClose(); + closeBox(); return true; } else if (err == qstr("CODE_INVALID")) { _error = lang(lng_signin_wrong_code); update(); - _recoverCode.selectAll(); - _recoverCode.setFocus(); - _recoverCode.showError(); + _recoverCode->selectAll(); + _recoverCode->setFocus(); + _recoverCode->showError(); return true; } if (cDebug()) { // internal server error @@ -612,6 +527,6 @@ bool RecoverBox::codeSubmitFail(const RPCError &error) { _error = lang(lng_server_error); } update(); - _recoverCode.setFocus(); + _recoverCode->setFocus(); return false; } diff --git a/Telegram/SourceFiles/boxes/passcodebox.h b/Telegram/SourceFiles/boxes/passcodebox.h index 4e1c212df..8847163f7 100644 --- a/Telegram/SourceFiles/boxes/passcodebox.h +++ b/Telegram/SourceFiles/boxes/passcodebox.h @@ -20,14 +20,20 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "abstractbox.h" +#include "boxes/abstractbox.h" -class PasscodeBox : public AbstractBox, public RPCSender { +namespace Ui { +class InputField; +class PasswordInput; +class LinkButton; +} // namespace Ui + +class PasscodeBox : public BoxContent, public RPCSender { Q_OBJECT public: - PasscodeBox(bool turningOff = false); - PasscodeBox(const QByteArray &newSalt, const QByteArray &curSalt, bool hasRecovery, const QString &hint, bool turningOff = false); + PasscodeBox(QWidget*, bool turningOff); + PasscodeBox(QWidget*, const QByteArray &newSalt, const QByteArray &curSalt, bool hasRecovery, const QString &hint, bool turningOff = false); private slots: void onSave(bool force = false); @@ -35,8 +41,6 @@ private slots: void onOldChanged(); void onNewChanged(); void onEmailChanged(); - void onForceNoMail(); - void onBoxDestroyed(QObject *obj); void onRecoverByEmail(); void onRecoverExpired(); void onSubmit(); @@ -45,13 +49,14 @@ signals: void reloadPassword(); protected: + void prepare() override; + void setInnerFocus() override; + void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; - void showAll() override; - void doSetInnerFocus() override; private: - void init(); + void closeReplacedBy(); void setPasswordDone(const MTPBool &result); bool setPasswordFail(const RPCError &error); @@ -62,32 +67,35 @@ private: void recover(); QString _pattern; - AbstractBox *_replacedBy; - bool _turningOff, _cloudPwd; - mtpRequestId _setRequest; + QPointer _replacedBy; + bool _turningOff = false; + bool _cloudPwd = false; + mtpRequestId _setRequest = 0; QByteArray _newSalt, _curSalt; - bool _hasRecovery, _skipEmailWarning = false; + bool _hasRecovery = false; + bool _skipEmailWarning = false; - int32 _aboutHeight; + int _aboutHeight = 0; - QString _boxTitle; Text _about, _hintText; - BoxButton _saveButton, _cancelButton; - PasswordField _oldPasscode, _newPasscode, _reenterPasscode; - InputField _passwordHint, _recoverEmail; - LinkButton _recover; + object_ptr _oldPasscode; + object_ptr _newPasscode; + object_ptr _reenterPasscode; + object_ptr _passwordHint; + object_ptr _recoverEmail; + object_ptr _recover; QString _oldError, _newError, _emailError; }; -class RecoverBox : public AbstractBox, public RPCSender { +class RecoverBox : public BoxContent, public RPCSender { Q_OBJECT public: - RecoverBox(const QString &pattern); + RecoverBox(QWidget*, const QString &pattern); public slots: void onSubmit(); @@ -98,22 +106,21 @@ signals: void recoveryExpired(); protected: + void prepare() override; + void setInnerFocus() override; + void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; - void showAll() override; - void doSetInnerFocus() override; - private: void codeSubmitDone(bool recover, const MTPauth_Authorization &result); bool codeSubmitFail(const RPCError &error); - mtpRequestId _submitRequest; + mtpRequestId _submitRequest = 0; QString _pattern; - BoxButton _saveButton, _cancelButton; - InputField _recoverCode; + object_ptr _recoverCode; QString _error; diff --git a/Telegram/SourceFiles/boxes/photocropbox.cpp b/Telegram/SourceFiles/boxes/photocropbox.cpp index 1e0e0de3e..d684bce18 100644 --- a/Telegram/SourceFiles/boxes/photocropbox.cpp +++ b/Telegram/SourceFiles/boxes/photocropbox.cpp @@ -26,21 +26,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include "photocropbox.h" #include "fileuploader.h" +#include "ui/widgets/buttons.h" +#include "styles/style_boxes.h" -PhotoCropBox::PhotoCropBox(const QImage &img, const PeerId &peer) : AbstractBox() -, _downState(0) -, _done(this, lang(lng_settings_save), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) -, _img(img) +PhotoCropBox::PhotoCropBox(QWidget*, const QImage &img, const PeerId &peer) +: _img(img) , _peerId(peer) { - init(img, 0); + init(img, nullptr); } -PhotoCropBox::PhotoCropBox(const QImage &img, PeerData *peer) : AbstractBox() -, _downState(0) -, _done(this, lang(lng_settings_save), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) -, _img(img) +PhotoCropBox::PhotoCropBox(QWidget*, const QImage &img, PeerData *peer) +: _img(img) , _peerId(peer->id) { init(img, peer); } @@ -53,16 +49,22 @@ void PhotoCropBox::init(const QImage &img, PeerData *peer) { } else { _title = lang(lng_settings_crop_profile); } +} - connect(&_done, SIGNAL(clicked()), this, SLOT(onSend())); - connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); +void PhotoCropBox::prepare() { + addButton(lang(lng_settings_save), [this] { sendPhoto(); }); + addButton(lang(lng_cancel), [this] { closeBox(); }); if (peerToBareInt(_peerId)) { connect(this, SIGNAL(ready(const QImage&)), this, SLOT(onReady(const QImage&))); } int32 s = st::boxWideWidth - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); - _thumb = App::pixmapFromImageInPlace(img.scaled(s * cIntRetinaFactor(), s * cIntRetinaFactor(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); + _thumb = App::pixmapFromImageInPlace(_img.scaled(s * cIntRetinaFactor(), s * cIntRetinaFactor(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); _thumb.setDevicePixelRatio(cRetinaFactor()); + _mask = QImage(_thumb.size(), QImage::Format_ARGB32_Premultiplied); + _mask.setDevicePixelRatio(cRetinaFactor()); + _fade = QImage(_thumb.size(), QImage::Format_ARGB32_Premultiplied); + _fade.setDevicePixelRatio(cRetinaFactor()); _thumbw = _thumb.width() / cIntRetinaFactor(); _thumbh = _thumb.height() / cIntRetinaFactor(); if (_thumbw > _thumbh) { @@ -77,23 +79,22 @@ void PhotoCropBox::init(const QImage &img, PeerData *peer) { _thumby = st::boxPhotoPadding.top(); setMouseTracking(true); - resizeMaxHeight(st::boxWideWidth, st::boxPhotoPadding.top() + _thumbh + st::boxPhotoPadding.bottom() + st::boxTextFont->height + st::cropSkip + st::boxButtonPadding.top() + _done.height() + st::boxButtonPadding.bottom()); + setDimensions(st::boxWideWidth, st::boxPhotoPadding.top() + _thumbh + st::boxPhotoPadding.bottom() + st::boxTextFont->height + st::cropSkip); } void PhotoCropBox::mousePressEvent(QMouseEvent *e) { - if (e->button() != Qt::LeftButton) return LayerWidget::mousePressEvent(e); - - _downState = mouseState(e->pos()); - _fromposx = e->pos().x(); - _fromposy = e->pos().y(); - _fromcropx = _cropx; - _fromcropy = _cropy; - _fromcropw = _cropw; - - return LayerWidget::mousePressEvent(e); + if (e->button() == Qt::LeftButton) { + _downState = mouseState(e->pos()); + _fromposx = e->pos().x(); + _fromposy = e->pos().y(); + _fromcropx = _cropx; + _fromcropy = _cropy; + _fromcropw = _cropw; + } + return BoxContent::mousePressEvent(e); } -int32 PhotoCropBox::mouseState(QPoint p) { +int PhotoCropBox::mouseState(QPoint p) { p -= QPoint(_thumbx, _thumby); int32 delta = st::cropPointSize, mdelta(-delta / 2); if (QRect(_cropx + mdelta, _cropy + mdelta, delta, delta).contains(p)) { @@ -219,15 +220,16 @@ void PhotoCropBox::mouseMoveEvent(QMouseEvent *e) { void PhotoCropBox::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { - onSend(); + sendPhoto(); } else { - AbstractBox::keyPressEvent(e); + BoxContent::keyPressEvent(e); } } void PhotoCropBox::paintEvent(QPaintEvent *e) { + BoxContent::paintEvent(e); + Painter p(this); - if (paint(p)) return; p.setFont(st::boxTextFont); p.setPen(st::boxPhotoTextFg); @@ -235,35 +237,28 @@ void PhotoCropBox::paintEvent(QPaintEvent *e) { p.translate(_thumbx, _thumby); p.drawPixmap(0, 0, _thumb); - p.setOpacity(0.5); - if (_cropy > 0) { - p.fillRect(QRect(0, 0, _cropx + _cropw, _cropy), st::black->b); - } - if (_cropx + _cropw < _thumbw) { - p.fillRect(QRect(_cropx + _cropw, 0, _thumbw - _cropx - _cropw, _cropy + _cropw), st::black->b); - } - if (_cropy + _cropw < _thumbh) { - p.fillRect(QRect(_cropx, _cropy + _cropw, _thumbw - _cropx, _thumbh - _cropy - _cropw), st::black->b); - } - if (_cropx > 0) { - p.fillRect(QRect(0, _cropy, _cropx, _thumbh - _cropy), st::black->b); - } + _mask.fill(Qt::white); + { + Painter p(&_mask); + PainterHighQualityEnabler hq(p); - int32 delta = st::cropPointSize, mdelta(-delta / 2); - p.fillRect(QRect(_cropx + mdelta, _cropy + mdelta, delta, delta), st::white->b); - p.fillRect(QRect(_cropx + _cropw + mdelta, _cropy + mdelta, delta, delta), st::white->b); - p.fillRect(QRect(_cropx + _cropw + mdelta, _cropy + _cropw + mdelta, delta, delta), st::white->b); - p.fillRect(QRect(_cropx + mdelta, _cropy + _cropw + mdelta, delta, delta), st::white->b); + p.setPen(Qt::NoPen); + p.setBrush(Qt::black); + p.drawEllipse(_cropx, _cropy, _cropw, _cropw); + } + style::colorizeImage(_mask, st::photoCropFadeBg->c, &_fade); + p.drawImage(0, 0, _fade); + + int delta = st::cropPointSize; + int mdelta = -delta / 2; + p.fillRect(QRect(_cropx + mdelta, _cropy + mdelta, delta, delta), st::photoCropPointFg); + p.fillRect(QRect(_cropx + _cropw + mdelta, _cropy + mdelta, delta, delta), st::photoCropPointFg); + p.fillRect(QRect(_cropx + _cropw + mdelta, _cropy + _cropw + mdelta, delta, delta), st::photoCropPointFg); + p.fillRect(QRect(_cropx + mdelta, _cropy + _cropw + mdelta, delta, delta), st::photoCropPointFg); } -void PhotoCropBox::resizeEvent(QResizeEvent *e) { - _done.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _done.height()); - _cancel.moveToRight(st::boxButtonPadding.right() + _done.width() + st::boxButtonPadding.left(), _done.y()); - AbstractBox::resizeEvent(e); -} - -void PhotoCropBox::onSend() { - QImage from(_img); +void PhotoCropBox::sendPhoto() { + auto from = _img; if (_img.width() < _thumb.width()) { from = _thumb.toImage(); } @@ -296,14 +291,9 @@ void PhotoCropBox::onSend() { } emit ready(tosend); - onClose(); + closeBox(); } void PhotoCropBox::onReady(const QImage &tosend) { App::app()->uploadProfilePhoto(tosend, _peerId); } - -void PhotoCropBox::showAll() { - _done.show(); - _cancel.show(); -} diff --git a/Telegram/SourceFiles/boxes/photocropbox.h b/Telegram/SourceFiles/boxes/photocropbox.h index 2a424bf5e..3c30b752a 100644 --- a/Telegram/SourceFiles/boxes/photocropbox.h +++ b/Telegram/SourceFiles/boxes/photocropbox.h @@ -20,45 +20,49 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "abstractbox.h" +#include "boxes/abstractbox.h" -class PhotoCropBox : public AbstractBox { +class PhotoCropBox : public BoxContent { Q_OBJECT public: - PhotoCropBox(const QImage &img, const PeerId &peer); - PhotoCropBox(const QImage &img, PeerData *peer); + PhotoCropBox(QWidget*, const QImage &img, const PeerId &peer); + PhotoCropBox(QWidget*, const QImage &img, PeerData *peer); int32 mouseState(QPoint p); -public slots: - void onSend(); - void onReady(const QImage &tosend); + void closeHook() override { + emit closed(); + } signals: void ready(const QImage &tosend); + void closed(); + +private slots: + void onReady(const QImage &tosend); protected: + void prepare() override; + void keyPressEvent(QKeyEvent *e) override; void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; - void showAll() override; - private: void init(const QImage &img, PeerData *peer); + void sendPhoto(); QString _title; - int32 _downState; + int32 _downState = 0; int32 _thumbx, _thumby, _thumbw, _thumbh; int32 _cropx, _cropy, _cropw; int32 _fromposx, _fromposy, _fromcropx, _fromcropy, _fromcropw; - BoxButton _done, _cancel; QImage _img; QPixmap _thumb; + QImage _mask, _fade; PeerId _peerId; }; diff --git a/Telegram/SourceFiles/boxes/photosendbox.cpp b/Telegram/SourceFiles/boxes/photosendbox.cpp deleted file mode 100644 index 98aac9648..000000000 --- a/Telegram/SourceFiles/boxes/photosendbox.cpp +++ /dev/null @@ -1,688 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -It is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the copyright holders give permission -to link the code of portions of this program with the OpenSSL library. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org -*/ -#include "stdafx.h" -#include "boxes/photosendbox.h" - -#include "lang.h" -#include "localstorage.h" -#include "mainwidget.h" -#include "photosendbox.h" -#include "history/history_media_types.h" -#include "styles/style_history.h" - -PhotoSendBox::PhotoSendBox(const FileLoadResultPtr &file) : AbstractBox(st::boxWideWidth) -, _file(file) -, _animated(false) -, _caption(this, st::confirmCaptionArea, lang(lng_photo_caption)) -, _compressedFromSettings(_file->type == PrepareAuto) -, _compressed(this, lang(lng_send_image_compressed), _compressedFromSettings ? cCompressPastedImage() : true, st::defaultBoxCheckbox) -, _send(this, lang(lng_send_button), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) -, _thumbx(0) -, _thumby(0) -, _thumbw(0) -, _thumbh(0) -, _statusw(0) -, _isImage(false) -, _replyTo(_file->to.replyTo) -, _confirmed(false) { - connect(&_send, SIGNAL(clicked()), this, SLOT(onSend())); - connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); - - _animated = false; - QSize dimensions; - if (_file->photo.type() != mtpc_photoEmpty) { - _file->type = PreparePhoto; - } else if (_file->document.type() == mtpc_document) { - const auto &document(_file->document.c_document()); - const auto &attributes(document.vattributes.c_vector().v); - for (int32 i = 0, l = attributes.size(); i < l; ++i) { - if (attributes.at(i).type() == mtpc_documentAttributeAnimated) { - _animated = true; - } else if (attributes.at(i).type() == mtpc_documentAttributeImageSize) { - dimensions = QSize(attributes.at(i).c_documentAttributeImageSize().vw.v, attributes.at(i).c_documentAttributeImageSize().vh.v); - } else if (attributes.at(i).type() == mtpc_documentAttributeVideo) { - dimensions = QSize(attributes.at(i).c_documentAttributeVideo().vw.v, attributes.at(i).c_documentAttributeVideo().vh.v); - } - } - if (dimensions.isEmpty()) _animated = false; - } - if (_file->type == PreparePhoto || _animated) { - int32 maxW = 0, maxH = 0; - if (_animated) { - int32 limitW = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); - int32 limitH = st::confirmMaxHeight; - maxW = qMax(dimensions.width(), 1); - maxH = qMax(dimensions.height(), 1); - if (maxW * limitH > maxH * limitW) { - if (maxW < limitW) { - maxH = maxH * limitW / maxW; - maxW = limitW; - } - } else { - if (maxH < limitH) { - maxW = maxW * limitH / maxH; - maxH = limitH; - } - } - _thumb = imagePix(_file->thumb.toImage(), maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), ImagePixSmooth | ImagePixBlurred, maxW, maxH); - } else { - for (PreparedPhotoThumbs::const_iterator i = _file->photoThumbs.cbegin(), e = _file->photoThumbs.cend(); i != e; ++i) { - if (i->width() >= maxW && i->height() >= maxH) { - _thumb = *i; - maxW = _thumb.width(); - maxH = _thumb.height(); - } - } - } - int32 tw = _thumb.width(), th = _thumb.height(); - if (!tw || !th) { - tw = th = 1; - } - _thumbw = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); - if (_thumb.width() < _thumbw) { - _thumbw = (_thumb.width() > 20) ? _thumb.width() : 20; - } - int32 maxthumbh = qMin(qRound(1.5 * _thumbw), int(st::confirmMaxHeight)); - _thumbh = qRound(th * float64(_thumbw) / tw); - if (_thumbh > maxthumbh) { - _thumbw = qRound(_thumbw * float64(maxthumbh) / _thumbh); - _thumbh = maxthumbh; - if (_thumbw < 10) { - _thumbw = 10; - } - } - _thumbx = (width() - _thumbw) / 2; - - _thumb = App::pixmapFromImageInPlace(_thumb.toImage().scaled(_thumbw * cIntRetinaFactor(), _thumbh * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - _thumb.setDevicePixelRatio(cRetinaFactor()); - } else { - if (_file->thumb.isNull()) { - _thumbw = 0; - } else { - _thumb = _file->thumb; - int32 tw = _thumb.width(), th = _thumb.height(); - if (tw > th) { - _thumbw = (tw * st::msgFileThumbSize) / th; - } else { - _thumbw = st::msgFileThumbSize; - } - _thumb = imagePix(_thumb.toImage(), _thumbw * cIntRetinaFactor(), 0, ImagePixSmooth | ImagePixRoundedSmall, st::msgFileThumbSize, st::msgFileThumbSize); - } - - _name.setText(st::semiboldFont, _file->filename, _textNameOptions); - _status = formatSizeText(_file->filesize); - _statusw = qMax(_name.maxWidth(), st::normalFont->width(_status)); - _isImage = fileIsImage(_file->filename, _file->filemime); - } - if (_file->type != PreparePhoto) { - _compressed.hide(); - } - - updateBoxSize(); - _caption.setMaxLength(MaxPhotoCaption); - _caption.setCtrlEnterSubmit(CtrlEnterSubmitBoth); - connect(&_compressed, SIGNAL(changed()), this, SLOT(onCompressedChange())); - connect(&_caption, SIGNAL(resized()), this, SLOT(onCaptionResized())); - connect(&_caption, SIGNAL(submitted(bool)), this, SLOT(onSend(bool))); - connect(&_caption, SIGNAL(cancelled()), this, SLOT(onClose())); - - prepare(); -} - -PhotoSendBox::PhotoSendBox(const QString &phone, const QString &fname, const QString &lname, MsgId replyTo) : AbstractBox(st::boxWideWidth) -, _caption(this, st::confirmCaptionArea, lang(lng_photo_caption)) -, _compressed(this, lang(lng_send_image_compressed), true) -, _send(this, lang(lng_send_button), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) -, _thumbx(0) -, _thumby(0) -, _thumbw(0) -, _thumbh(0) -, _statusw(0) -, _isImage(false) -, _phone(phone) -, _fname(fname) -, _lname(lname) -, _replyTo(replyTo) -, _confirmed(false) { - connect(&_send, SIGNAL(clicked()), this, SLOT(onSend())); - connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); - - _compressed.hide(); - - _name.setText(st::semiboldFont, lng_full_name(lt_first_name, _fname, lt_last_name, _lname), _textNameOptions); - _status = _phone; - _statusw = qMax(_name.maxWidth(), st::normalFont->width(_status)); - - updateBoxSize(); - prepare(); -} - -void PhotoSendBox::onCompressedChange() { - showAll(); - if (_caption.isHidden()) { - setFocus(); - } else { - _caption.setFocus(); - } - updateBoxSize(); - resizeEvent(0); - update(); -} - -void PhotoSendBox::onCaptionResized() { - updateBoxSize(); - resizeEvent(0); - update(); -} - -void PhotoSendBox::updateBoxSize() { - if (_file && (_file->type == PreparePhoto || _animated)) { - setMaxHeight(st::boxPhotoPadding.top() + _thumbh + st::boxPhotoPadding.bottom() + (_animated ? 0 : (st::boxPhotoCompressedPadding.top() + _compressed.height())) + st::boxPhotoCompressedPadding.bottom() + _caption.height() + st::boxButtonPadding.top() + _send.height() + st::boxButtonPadding.bottom()); - } else if (_thumbw) { - setMaxHeight(st::boxPhotoPadding.top() + st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom() + (_file ? (st::boxPhotoCompressedPadding.bottom() + _caption.height()) : 0) + st::boxPhotoPadding.bottom() + st::boxButtonPadding.top() + _send.height() + st::boxButtonPadding.bottom()); - } else { - setMaxHeight(st::boxPhotoPadding.top() + st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() + (_file ? (st::boxPhotoCompressedPadding.bottom() + _caption.height()) : 0) + st::boxPhotoPadding.bottom() + st::boxButtonPadding.top() + _send.height() + st::boxButtonPadding.bottom()); - } -} - -void PhotoSendBox::keyPressEvent(QKeyEvent *e) { - if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { - onSend((e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::MetaModifier)) && e->modifiers().testFlag(Qt::ShiftModifier)); - } else { - AbstractBox::keyPressEvent(e); - } -} - -void PhotoSendBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; - - if (_file && (_file->type == PreparePhoto || _animated)) { - if (_thumbx > st::boxPhotoPadding.left()) { - p.fillRect(st::boxPhotoPadding.left(), st::boxPhotoPadding.top(), _thumbx - st::boxPhotoPadding.left(), _thumbh, st::confirmBg->b); - } - if (_thumbx + _thumbw < width() - st::boxPhotoPadding.right()) { - p.fillRect(_thumbx + _thumbw, st::boxPhotoPadding.top(), width() - st::boxPhotoPadding.right() - _thumbx - _thumbw, _thumbh, st::confirmBg->b); - } - p.drawPixmap(_thumbx, st::boxPhotoPadding.top(), _thumb); - if (_animated) { - QRect inner(_thumbx + (_thumbw - st::msgFileSize) / 2, st::boxPhotoPadding.top() + (_thumbh - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); - p.setPen(Qt::NoPen); - p.setBrush(st::msgDateImgBg); - - p.setRenderHint(QPainter::HighQualityAntialiasing); - p.drawEllipse(inner); - p.setRenderHint(QPainter::HighQualityAntialiasing, false); - - auto icon = &st::historyFileInPlay; - icon->paintInCenter(p, inner); - } - } else { - int32 w = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); - int32 h = _thumbw ? (st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom()) : (st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom()); - int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0; - if (_thumbw) { - nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); - nametop = st::msgFileThumbNameTop; - nameright = st::msgFileThumbPadding.left(); - statustop = st::msgFileThumbStatusTop; - linktop = st::msgFileThumbLinkTop; - } else { - nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); - nametop = st::msgFileNameTop; - nameright = st::msgFilePadding.left(); - statustop = st::msgFileStatusTop; - } - int32 namewidth = w - nameleft - (_thumbw ? st::msgFileThumbPadding.left() : st::msgFilePadding.left()); - if (namewidth > _statusw) { - w -= (namewidth - _statusw); - namewidth = _statusw; - } - int32 x = (width() - w) / 2, y = st::boxPhotoPadding.top(); - - App::roundRect(p, x, y, w, h, st::msgOutBg, MessageOutCorners, &st::msgOutShadow); - - if (_thumbw) { - QRect rthumb(rtlrect(x + st::msgFileThumbPadding.left(), y + st::msgFileThumbPadding.top(), st::msgFileThumbSize, st::msgFileThumbSize, width())); - p.drawPixmap(rthumb.topLeft(), _thumb); - } else if (_file) { - QRect inner(rtlrect(x + st::msgFilePadding.left(), y + st::msgFilePadding.top(), st::msgFileSize, st::msgFileSize, width())); - p.setPen(Qt::NoPen); - p.setBrush(st::msgFileOutBg); - - p.setRenderHint(QPainter::HighQualityAntialiasing); - p.drawEllipse(inner); - p.setRenderHint(QPainter::HighQualityAntialiasing, false); - - auto icon = &(_isImage ? st::historyFileOutImage : st::historyFileOutDocument); - icon->paintInCenter(p, inner); - } else { - p.drawPixmapLeft(x + st::msgFilePadding.left(), y + st::msgFilePadding.top(), width(), userDefPhoto(1)->pixCircled(st::msgFileSize)); - } - p.setFont(st::semiboldFont); - p.setPen(st::black); - _name.drawLeftElided(p, x + nameleft, y + nametop, namewidth, width()); - - style::color status(st::mediaOutFg); - p.setFont(st::normalFont); - p.setPen(status); - p.drawTextLeft(x + nameleft, y + statustop, width(), _status); - } -} - -void PhotoSendBox::resizeEvent(QResizeEvent *e) { - _send.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _send.height()); - _cancel.moveToRight(st::boxButtonPadding.right() + _send.width() + st::boxButtonPadding.left(), _send.y()); - _caption.resize(st::boxWideWidth - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(), _caption.height()); - _caption.moveToLeft(st::boxPhotoPadding.left(), _send.y() - st::boxButtonPadding.top() - _caption.height()); - _compressed.moveToLeft(st::boxPhotoPadding.left(), st::boxPhotoPadding.top() + _thumbh + st::boxPhotoPadding.bottom() + st::boxPhotoCompressedPadding.top()); - AbstractBox::resizeEvent(e); -} - -void PhotoSendBox::closePressed() { - if (!_confirmed && App::main()) { - if (_file) { - App::main()->onSendFileCancel(_file); - } else { - App::main()->onShareContactCancel(); - } - } -} - -void PhotoSendBox::showAll() { - _send.show(); - _cancel.show(); - if (_file) { - if (_file->type == PreparePhoto) { - _compressed.show(); - } - _caption.show(); - } else { - _caption.hide(); - _compressed.hide(); - } -} - -void PhotoSendBox::doSetInnerFocus() { - if (_caption.isHidden()) { - setFocus(); - } else { - _caption.setFocus(); - } -} - -void PhotoSendBox::onSend(bool ctrlShiftEnter) { - if (App::main()) { - if (_file) { - if (_compressed.isHidden()) { - if (_file->type == PrepareAuto) { - _file->type = PrepareDocument; - } - } else { - if (_compressedFromSettings && _compressed.checked() != cCompressPastedImage()) { - cSetCompressPastedImage(_compressed.checked()); - Local::writeUserSettings(); - } - if (_compressed.checked()) { - _file->type = PreparePhoto; - } else { - _file->type = PrepareDocument; - } - } - if (!_caption.isHidden()) { - _file->caption = prepareText(_caption.getLastText(), true); - } - App::main()->onSendFileConfirm(_file, ctrlShiftEnter); - } else { - App::main()->onShareContactConfirm(_phone, _fname, _lname, _replyTo, ctrlShiftEnter); - } - } - _confirmed = true; - onClose(); -} - -EditCaptionBox::EditCaptionBox(HistoryItem *msg) : AbstractBox(st::boxWideWidth) -, _msgId(msg->fullId()) -, _animated(false) -, _photo(false) -, _doc(false) -, _field(0) -, _save(this, lang(lng_settings_save), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) -, _thumbx(0) -, _thumby(0) -, _thumbw(0) -, _thumbh(0) -, _statusw(0) -, _isImage(false) -, _previewCancelled(false) -, _saveRequestId(0) { - connect(&_save, SIGNAL(clicked()), this, SLOT(onSave())); - connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); - - QSize dimensions; - ImagePtr image; - QString caption; - DocumentData *doc = 0; - if (HistoryMedia *media = msg->getMedia()) { - HistoryMediaType t = media->type(); - switch (t) { - case MediaTypeGif: { - _animated = true; - doc = static_cast(media)->getDocument(); - dimensions = doc->dimensions; - image = doc->thumb; - } break; - - case MediaTypePhoto: { - _photo = true; - PhotoData *photo = static_cast(media)->photo(); - dimensions = QSize(photo->full->width(), photo->full->height()); - image = photo->full; - } break; - - case MediaTypeVideo: { - _animated = true; - doc = static_cast(media)->getDocument(); - dimensions = doc->dimensions; - image = doc->thumb; - } break; - - case MediaTypeFile: - case MediaTypeMusicFile: - case MediaTypeVoiceFile: { - _doc = true; - doc = static_cast(media)->getDocument(); - image = doc->thumb; - } break; - } - caption = media->getCaption().text; - } - if ((!_animated && (dimensions.isEmpty() || doc)) || image->isNull()) { - _animated = false; - if (image->isNull()) { - _thumbw = 0; - } else { - int32 tw = image->width(), th = image->height(); - if (tw > th) { - _thumbw = (tw * st::msgFileThumbSize) / th; - } else { - _thumbw = st::msgFileThumbSize; - } - _thumb = imagePix(image->pix().toImage(), _thumbw * cIntRetinaFactor(), 0, ImagePixSmooth | ImagePixRoundedSmall, st::msgFileThumbSize, st::msgFileThumbSize); - } - - if (doc) { - if (doc->voice()) { - _name.setText(st::semiboldFont, lang(lng_media_audio), _textNameOptions); - } else { - _name.setText(st::semiboldFont, documentName(doc), _textNameOptions); - } - _status = formatSizeText(doc->size); - _statusw = qMax(_name.maxWidth(), st::normalFont->width(_status)); - _isImage = doc->isImage(); - } - } else { - int32 maxW = 0, maxH = 0; - if (_animated) { - int32 limitW = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); - int32 limitH = st::confirmMaxHeight; - maxW = qMax(dimensions.width(), 1); - maxH = qMax(dimensions.height(), 1); - if (maxW * limitH > maxH * limitW) { - if (maxW < limitW) { - maxH = maxH * limitW / maxW; - maxW = limitW; - } - } else { - if (maxH < limitH) { - maxW = maxW * limitH / maxH; - maxH = limitH; - } - } - _thumb = image->pixNoCache(maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), ImagePixSmooth | ImagePixBlurred, maxW, maxH); - } else { - maxW = dimensions.width(); - maxH = dimensions.height(); - _thumb = image->pixNoCache(maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), ImagePixSmooth, maxW, maxH); - } - int32 tw = _thumb.width(), th = _thumb.height(); - if (!tw || !th) { - tw = th = 1; - } - _thumbw = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); - if (_thumb.width() < _thumbw) { - _thumbw = (_thumb.width() > 20) ? _thumb.width() : 20; - } - int32 maxthumbh = qMin(qRound(1.5 * _thumbw), int(st::confirmMaxHeight)); - _thumbh = qRound(th * float64(_thumbw) / tw); - if (_thumbh > maxthumbh) { - _thumbw = qRound(_thumbw * float64(maxthumbh) / _thumbh); - _thumbh = maxthumbh; - if (_thumbw < 10) { - _thumbw = 10; - } - } - _thumbx = (width() - _thumbw) / 2; - - _thumb = App::pixmapFromImageInPlace(_thumb.toImage().scaled(_thumbw * cIntRetinaFactor(), _thumbh * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - _thumb.setDevicePixelRatio(cRetinaFactor()); - } - - if (_animated || _photo || _doc) { - _field = new InputArea(this, st::confirmCaptionArea, lang(lng_photo_caption), caption); - _field->setMaxLength(MaxPhotoCaption); - _field->setCtrlEnterSubmit(CtrlEnterSubmitBoth); - } else { - auto original = msg->originalText(); - QString text = textApplyEntities(original.text, original.entities); - _field = new InputArea(this, st::editTextArea, lang(lng_photo_caption), text); -// _field->setMaxLength(MaxMessageSize); // entities can make text in input field larger but still valid - _field->setCtrlEnterSubmit(cCtrlEnter() ? CtrlEnterSubmitCtrlEnter : CtrlEnterSubmitEnter); - } - updateBoxSize(); - connect(_field, SIGNAL(submitted(bool)), this, SLOT(onSave(bool))); - connect(_field, SIGNAL(cancelled()), this, SLOT(onClose())); - connect(_field, SIGNAL(resized()), this, SLOT(onCaptionResized())); - - QTextCursor c(_field->textCursor()); - c.movePosition(QTextCursor::End); - _field->setTextCursor(c); - - prepare(); -} - -bool EditCaptionBox::captionFound() const { - return _animated || _photo || _doc; -} - -void EditCaptionBox::onCaptionResized() { - updateBoxSize(); - resizeEvent(0); - update(); -} - -void EditCaptionBox::updateBoxSize() { - int32 bottomh = st::boxPhotoCompressedPadding.bottom() + _field->height() + st::normalFont->height + st::boxButtonPadding.top() + _save.height() + st::boxButtonPadding.bottom(); - if (_photo || _animated) { - setMaxHeight(st::boxPhotoPadding.top() + _thumbh + bottomh); - } else if (_thumbw) { - setMaxHeight(st::boxPhotoPadding.top() + 0 + st::msgFileThumbSize + 0 + bottomh); - } else if (_doc) { - setMaxHeight(st::boxPhotoPadding.top() + 0 + st::msgFileSize + 0 + bottomh); - } else { - setMaxHeight(st::boxPhotoPadding.top() + st::boxTitleFont->height + bottomh); - } -} - -void EditCaptionBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; - - if (_photo || _animated) { - if (_thumbx > st::boxPhotoPadding.left()) { - p.fillRect(st::boxPhotoPadding.left(), st::boxPhotoPadding.top(), _thumbx - st::boxPhotoPadding.left(), _thumbh, st::confirmBg->b); - } - if (_thumbx + _thumbw < width() - st::boxPhotoPadding.right()) { - p.fillRect(_thumbx + _thumbw, st::boxPhotoPadding.top(), width() - st::boxPhotoPadding.right() - _thumbx - _thumbw, _thumbh, st::confirmBg->b); - } - p.drawPixmap(_thumbx, st::boxPhotoPadding.top(), _thumb); - if (_animated) { - QRect inner(_thumbx + (_thumbw - st::msgFileSize) / 2, st::boxPhotoPadding.top() + (_thumbh - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); - p.setPen(Qt::NoPen); - p.setBrush(st::msgDateImgBg); - - p.setRenderHint(QPainter::HighQualityAntialiasing); - p.drawEllipse(inner); - p.setRenderHint(QPainter::HighQualityAntialiasing, false); - - auto icon = &st::historyFileInPlay; - icon->paintInCenter(p, inner); - } - } else if (_doc) { - int32 w = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); - int32 h = _thumbw ? (0 + st::msgFileThumbSize + 0) : (0 + st::msgFileSize + 0); - int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0; - if (_thumbw) { - nameleft = 0 + st::msgFileThumbSize + st::msgFileThumbPadding.right(); - nametop = st::msgFileThumbNameTop - st::msgFileThumbPadding.top(); - nameright = 0; - statustop = st::msgFileThumbStatusTop - st::msgFileThumbPadding.top(); - } else { - nameleft = 0 + st::msgFileSize + st::msgFilePadding.right(); - nametop = st::msgFileNameTop - st::msgFilePadding.top(); - nameright = 0; - statustop = st::msgFileStatusTop - st::msgFilePadding.top(); - } - int32 namewidth = w - nameleft - 0; - if (namewidth > _statusw) { - //w -= (namewidth - _statusw); - //namewidth = _statusw; - } - int32 x = (width() - w) / 2, y = st::boxPhotoPadding.top(); - -// App::roundRect(p, x, y, w, h, st::msgInBg, MessageInCorners, &st::msgInShadow); - - if (_thumbw) { - QRect rthumb(rtlrect(x + 0, y + 0, st::msgFileThumbSize, st::msgFileThumbSize, width())); - p.drawPixmap(rthumb.topLeft(), _thumb); - } else { - QRect inner(rtlrect(x + 0, y + 0, st::msgFileSize, st::msgFileSize, width())); - p.setPen(Qt::NoPen); - p.setBrush(st::msgFileInBg); - - p.setRenderHint(QPainter::HighQualityAntialiasing); - p.drawEllipse(inner); - p.setRenderHint(QPainter::HighQualityAntialiasing, false); - - auto icon = &(_isImage ? st::historyFileInImage : st::historyFileInDocument); - icon->paintInCenter(p, inner); - } - p.setFont(st::semiboldFont); - p.setPen(st::black); - _name.drawLeftElided(p, x + nameleft, y + nametop, namewidth, width()); - - style::color status(st::mediaInFg); - p.setFont(st::normalFont); - p.setPen(status); - p.drawTextLeft(x + nameleft, y + statustop, width(), _status); - } else { - p.setFont(st::boxTitleFont); - p.setPen(st::black); - p.drawTextLeft(_field->x(), st::boxPhotoPadding.top(), width(), lang(lng_edit_message)); - } - - if (!_error.isEmpty()) { - p.setFont(st::normalFont); - p.setPen(st::setErrColor); - p.drawTextLeft(_field->x(), _field->y() + _field->height() + (st::boxButtonPadding.top() / 2), width(), _error); - } -} - -void EditCaptionBox::resizeEvent(QResizeEvent *e) { - _save.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _save.height()); - _cancel.moveToRight(st::boxButtonPadding.right() + _save.width() + st::boxButtonPadding.left(), _save.y()); - _field->resize(st::boxWideWidth - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(), _field->height()); - _field->moveToLeft(st::boxPhotoPadding.left(), _save.y() - st::boxButtonPadding.top() - st::normalFont->height - _field->height()); - AbstractBox::resizeEvent(e); -} - -void EditCaptionBox::showAll() { - _save.show(); - _cancel.show(); - _field->show(); -} - -void EditCaptionBox::doSetInnerFocus() { - _field->setFocus(); -} - -void EditCaptionBox::onSave(bool ctrlShiftEnter) { - if (_saveRequestId) return; - - HistoryItem *item = App::histItemById(_msgId); - if (!item) { - _error = lang(lng_edit_deleted); - update(); - return; - } - - MTPmessages_EditMessage::Flags flags = MTPmessages_EditMessage::Flag::f_message; - if (_previewCancelled) { - flags |= MTPmessages_EditMessage::Flag::f_no_webpage; - } - MTPVector sentEntities; - if (!sentEntities.c_vector().v.isEmpty()) { - flags |= MTPmessages_EditMessage::Flag::f_entities; - } - auto text = prepareText(_field->getLastText(), true); - _saveRequestId = MTP::send(MTPmessages_EditMessage(MTP_flags(flags), item->history()->peer->input, MTP_int(item->id), MTP_string(text), MTPnullMarkup, sentEntities), rpcDone(&EditCaptionBox::saveDone), rpcFail(&EditCaptionBox::saveFail)); -} - -void EditCaptionBox::saveDone(const MTPUpdates &updates) { - _saveRequestId = 0; - onClose(); - if (App::main()) { - App::main()->sentUpdatesReceived(updates); - } -} - -bool EditCaptionBox::saveFail(const RPCError &error) { - if (MTP::isDefaultHandledError(error)) return false; - - _saveRequestId = 0; - QString err = error.type(); - if (err == qstr("MESSAGE_ID_INVALID") || err == qstr("CHAT_ADMIN_REQUIRED") || err == qstr("MESSAGE_EDIT_TIME_EXPIRED")) { - _error = lang(lng_edit_error); - } else if (err == qstr("MESSAGE_NOT_MODIFIED")) { - onClose(); - return true; - } else if (err == qstr("MESSAGE_EMPTY")) { - _field->setFocus(); - _field->showError(); - } else { - _error = lang(lng_edit_error); - } - update(); - return true; -} diff --git a/Telegram/SourceFiles/boxes/photosendbox.h b/Telegram/SourceFiles/boxes/photosendbox.h deleted file mode 100644 index 4856131d3..000000000 --- a/Telegram/SourceFiles/boxes/photosendbox.h +++ /dev/null @@ -1,118 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -It is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the copyright holders give permission -to link the code of portions of this program with the OpenSSL library. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org -*/ -#pragma once - -#include "abstractbox.h" -#include "localimageloader.h" - -class PhotoSendBox : public AbstractBox { - Q_OBJECT - -public: - PhotoSendBox(const FileLoadResultPtr &file); - PhotoSendBox(const QString &phone, const QString &fname, const QString &lname, MsgId replyTo); - -public slots: - void onCompressedChange(); - void onCaptionResized(); - void onSend(bool ctrlShiftEnter = false); - -protected: - void keyPressEvent(QKeyEvent *e) override; - void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - - void closePressed() override; - void showAll() override; - void doSetInnerFocus() override; - -private: - void updateBoxSize(); - - FileLoadResultPtr _file; - bool _animated; - - QPixmap _thumb; - - InputArea _caption; - bool _compressedFromSettings; - Checkbox _compressed; - BoxButton _send, _cancel; - - int32 _thumbx, _thumby, _thumbw, _thumbh; - Text _name; - QString _status; - int32 _statusw; - bool _isImage; - - QString _phone, _fname, _lname; - - MsgId _replyTo; - - bool _confirmed; - -}; - -class EditCaptionBox : public AbstractBox, public RPCSender { - Q_OBJECT - -public: - EditCaptionBox(HistoryItem *msg); - - bool captionFound() const; - -public slots: - void onCaptionResized(); - void onSave(bool ctrlShiftEnter = false); - -protected: - void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - - void showAll() override; - void doSetInnerFocus() override; - -private: - void updateBoxSize(); - - void saveDone(const MTPUpdates &updates); - bool saveFail(const RPCError &error); - - FullMsgId _msgId; - bool _animated, _photo, _doc; - - QPixmap _thumb; - - InputArea *_field; - BoxButton _save, _cancel; - - int32 _thumbx, _thumby, _thumbw, _thumbh; - Text _name; - QString _status; - int32 _statusw; - bool _isImage; - - bool _previewCancelled; - mtpRequestId _saveRequestId; - - QString _error; - -}; diff --git a/Telegram/SourceFiles/boxes/report_box.cpp b/Telegram/SourceFiles/boxes/report_box.cpp index 42cb287ff..09fc3df8c 100644 --- a/Telegram/SourceFiles/boxes/report_box.cpp +++ b/Telegram/SourceFiles/boxes/report_box.cpp @@ -19,23 +19,29 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "report_box.h" +#include "boxes/report_box.h" #include "lang.h" +#include "styles/style_boxes.h" #include "styles/style_profile.h" #include "boxes/confirmbox.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" #include "mainwindow.h" -ReportBox::ReportBox(ChannelData *channel) : AbstractBox(st::boxWidth) -, _channel(channel) -, _reasonSpam(this, qsl("report_reason"), ReasonSpam, lang(lng_report_reason_spam), true) -, _reasonViolence(this, qsl("report_reason"), ReasonViolence, lang(lng_report_reason_violence)) -, _reasonPornography(this, qsl("report_reason"), ReasonPornography, lang(lng_report_reason_pornography)) -, _reasonOther(this, qsl("report_reason"), ReasonOther, lang(lng_report_reason_other)) -, _report(this, lang(lng_report_button), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) { - connect(_report, SIGNAL(clicked()), this, SLOT(onReport())); - connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose())); +ReportBox::ReportBox(QWidget*, PeerData *peer) : _peer(peer) +, _reasonSpam(this, qsl("report_reason"), ReasonSpam, lang(lng_report_reason_spam), true, st::defaultBoxCheckbox) +, _reasonViolence(this, qsl("report_reason"), ReasonViolence, lang(lng_report_reason_violence), false, st::defaultBoxCheckbox) +, _reasonPornography(this, qsl("report_reason"), ReasonPornography, lang(lng_report_reason_pornography), false, st::defaultBoxCheckbox) +, _reasonOther(this, qsl("report_reason"), ReasonOther, lang(lng_report_reason_other), false, st::defaultBoxCheckbox) { +} + +void ReportBox::prepare() { + setTitle(lang(_peer->isUser() ? lng_report_bot_title : (_peer->isMegagroup() ? lng_report_group_title : lng_report_title))); + + addButton(lang(lng_report_button), [this] { onReport(); }); + addButton(lang(lng_cancel), [this] { closeBox(); }); connect(_reasonSpam, SIGNAL(changed()), this, SLOT(onChange())); connect(_reasonViolence, SIGNAL(changed()), this, SLOT(onChange())); @@ -43,38 +49,27 @@ ReportBox::ReportBox(ChannelData *channel) : AbstractBox(st::boxWidth) connect(_reasonOther, SIGNAL(changed()), this, SLOT(onChange())); updateMaxHeight(); - - prepare(); -} - -void ReportBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; - - paintTitle(p, lang(_channel->isMegagroup() ? lng_report_group_title : lng_report_title)); } void ReportBox::resizeEvent(QResizeEvent *e) { - _reasonSpam->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), st::boxTitleHeight + st::boxOptionListPadding.top()); - _reasonViolence->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonSpam->y() + _reasonSpam->height() + st::boxOptionListPadding.top()); - _reasonPornography->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonViolence->y() + _reasonViolence->height() + st::boxOptionListPadding.top()); - _reasonOther->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonPornography->y() + _reasonPornography->height() + st::boxOptionListPadding.top()); + BoxContent::resizeEvent(e); + + _reasonSpam->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), st::boxOptionListPadding.top()); + _reasonViolence->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonSpam->bottomNoMargins() + st::boxOptionListSkip); + _reasonPornography->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonViolence->bottomNoMargins() + st::boxOptionListSkip); + _reasonOther->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonPornography->bottomNoMargins() + st::boxOptionListSkip); if (_reasonOtherText) { - _reasonOtherText->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left() - st::defaultInputField.textMargins.left(), _reasonOther->y() + _reasonOther->height() + st::newGroupDescriptionPadding.top()); + _reasonOtherText->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left() - st::defaultInputField.textMargins.left(), _reasonOther->bottomNoMargins() + st::newGroupDescriptionPadding.top()); } - - _report->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _report->height()); - _cancel->moveToRight(st::boxButtonPadding.right() + _report->width() + st::boxButtonPadding.left(), _report->y()); - AbstractBox::resizeEvent(e); } void ReportBox::onChange() { if (_reasonOther->checked()) { if (!_reasonOtherText) { - _reasonOtherText = new InputArea(this, st::profileReportReasonOther, lang(lng_report_reason_description)); + _reasonOtherText.create(this, st::profileReportReasonOther, lang(lng_report_reason_description)); _reasonOtherText->show(); - _reasonOtherText->setCtrlEnterSubmit(CtrlEnterSubmitBoth); + _reasonOtherText->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both); _reasonOtherText->setMaxLength(MaxPhotoCaption); _reasonOtherText->resize(width() - (st::boxPadding.left() + st::boxOptionListPadding.left() + st::boxPadding.right()), _reasonOtherText->height()); @@ -83,16 +78,16 @@ void ReportBox::onChange() { connect(_reasonOtherText, SIGNAL(submitted(bool)), this, SLOT(onReport())); connect(_reasonOtherText, SIGNAL(cancelled()), this, SLOT(onClose())); } - _reasonOtherText->setFocus(); + _reasonOtherText->setFocusFast(); } else if (_reasonOtherText) { _reasonOtherText.destroy(); updateMaxHeight(); } } -void ReportBox::doSetInnerFocus() { +void ReportBox::setInnerFocus() { if (_reasonOtherText) { - _reasonOtherText->setFocus(); + _reasonOtherText->setFocusFast(); } else { setFocus(); } @@ -122,12 +117,12 @@ void ReportBox::onReport() { return MTP_inputReportReasonSpam(); } }; - _requestId = MTP::send(MTPaccount_ReportPeer(_channel->input, getReason()), rpcDone(&ReportBox::reportDone), rpcFail(&ReportBox::reportFail)); + _requestId = MTP::send(MTPaccount_ReportPeer(_peer->input, getReason()), rpcDone(&ReportBox::reportDone), rpcFail(&ReportBox::reportFail)); } void ReportBox::reportDone(const MTPBool &result) { _requestId = 0; - Ui::showLayer(new InformBox(lang(lng_report_thanks))); + Ui::show(Box(lang(lng_report_thanks))); } bool ReportBox::reportFail(const RPCError &error) { @@ -141,9 +136,9 @@ bool ReportBox::reportFail(const RPCError &error) { } void ReportBox::updateMaxHeight() { - int32 h = st::boxTitleHeight + 4 * (st::boxOptionListPadding.top() + _reasonSpam->height()) + st::boxButtonPadding.top() + _report->height() + st::boxButtonPadding.bottom(); + auto newHeight = st::boxOptionListPadding.top() + 4 * _reasonSpam->heightNoMargins() + 3 * st::boxOptionListSkip + st::boxOptionListPadding.bottom(); if (_reasonOtherText) { - h += st::newGroupDescriptionPadding.top() + _reasonOtherText->height() + st::newGroupDescriptionPadding.bottom(); + newHeight += st::newGroupDescriptionPadding.top() + _reasonOtherText->height() + st::newGroupDescriptionPadding.bottom(); } - setMaxHeight(h); + setDimensions(st::boxWidth, newHeight); } diff --git a/Telegram/SourceFiles/boxes/report_box.h b/Telegram/SourceFiles/boxes/report_box.h index 39954c348..b64e8b6ea 100644 --- a/Telegram/SourceFiles/boxes/report_box.h +++ b/Telegram/SourceFiles/boxes/report_box.h @@ -20,27 +20,32 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "abstractbox.h" +#include "boxes/abstractbox.h" -class ReportBox : public AbstractBox, public RPCSender { +namespace Ui { +class Radiobutton; +class InputArea; +} // namespace Ui + +class ReportBox : public BoxContent, public RPCSender { Q_OBJECT public: - ReportBox(ChannelData *channel); + ReportBox(QWidget*, PeerData *peer); private slots: void onReport(); void onChange(); void onDescriptionResized(); + void onClose() { + closeBox(); + } protected: - void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; + void prepare() override; + void setInnerFocus() override; - void showAll() override { - showChildren(); - } - void doSetInnerFocus() override; + void resizeEvent(QResizeEvent *e) override; private: void updateMaxHeight(); @@ -48,15 +53,13 @@ private: void reportDone(const MTPBool &result); bool reportFail(const RPCError &error); - ChannelData *_channel; + PeerData *_peer; - ChildWidget _reasonSpam; - ChildWidget _reasonViolence; - ChildWidget _reasonPornography; - ChildWidget _reasonOther; - ChildWidget _reasonOtherText = { nullptr }; - - ChildWidget _report, _cancel; + object_ptr _reasonSpam; + object_ptr _reasonViolence; + object_ptr _reasonPornography; + object_ptr _reasonOther; + object_ptr _reasonOtherText = { nullptr }; enum Reason { ReasonSpam, diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp new file mode 100644 index 000000000..8848a29e3 --- /dev/null +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -0,0 +1,665 @@ +/* +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 "boxes/send_files_box.h" + +#include "lang.h" +#include "localstorage.h" +#include "mainwidget.h" +#include "history/history_media_types.h" +#include "ui/filedialog.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" +#include "styles/style_history.h" +#include "styles/style_boxes.h" + +namespace { + +constexpr auto kMinPreviewWidth = 20; + +} // namespace + +SendFilesBox::SendFilesBox(QWidget*, const QString &filepath, QImage image, CompressConfirm compressed, bool animated) +: _files(filepath) +, _image(image) +, _compressConfirm(compressed) +, _animated(image.isNull() ? false : animated) +, _caption(this, st::confirmCaptionArea, lang(lng_photo_caption)) { + if (!image.isNull()) { + if (!_animated && _compressConfirm == CompressConfirm::None) { + auto originalWidth = image.width(); + auto originalHeight = image.height(); + auto thumbWidth = st::msgFileThumbSize; + if (originalWidth > originalHeight) { + thumbWidth = (originalWidth * st::msgFileThumbSize) / originalHeight; + } + auto options = Images::Option::Smooth | Images::Option::RoundedSmall | Images::Option::RoundedTopLeft | Images::Option::RoundedTopRight | Images::Option::RoundedBottomLeft | Images::Option::RoundedBottomRight; + _fileThumb = Images::pixmap(image, thumbWidth * cIntRetinaFactor(), 0, options, st::msgFileThumbSize, st::msgFileThumbSize); + } else { + auto maxW = 0; + auto maxH = 0; + if (_animated) { + auto limitW = st::boxWideWidth - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); + auto limitH = st::confirmMaxHeight; + maxW = qMax(image.width(), 1); + maxH = qMax(image.height(), 1); + if (maxW * limitH > maxH * limitW) { + if (maxW < limitW) { + maxH = maxH * limitW / maxW; + maxW = limitW; + } + } else { + if (maxH < limitH) { + maxW = maxW * limitH / maxH; + maxH = limitH; + } + } + image = Images::prepare(image, maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), Images::Option::Smooth | Images::Option::Blurred, maxW, maxH); + } + auto originalWidth = image.width(); + auto originalHeight = image.height(); + if (!originalWidth || !originalHeight) { + originalWidth = originalHeight = 1; + } + _previewWidth = st::boxWideWidth - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); + if (image.width() < _previewWidth) { + _previewWidth = qMax(image.width(), kMinPreviewWidth); + } + auto maxthumbh = qMin(qRound(1.5 * _previewWidth), st::confirmMaxHeight); + _previewHeight = qRound(originalHeight * float64(_previewWidth) / originalWidth); + if (_previewHeight > maxthumbh) { + _previewWidth = qRound(_previewWidth * float64(maxthumbh) / _previewHeight); + accumulate_max(_previewWidth, kMinPreviewWidth); + _previewHeight = maxthumbh; + } + _previewLeft = (st::boxWideWidth - _previewWidth) / 2; + + image = std_::move(image).scaled(_previewWidth * cIntRetinaFactor(), _previewHeight * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + image = Images::prepareOpaque(std_::move(image)); + _preview = App::pixmapFromImageInPlace(std_::move(image)); + _preview.setDevicePixelRatio(cRetinaFactor()); + } + } + if (_preview.isNull()) { + if (filepath.isEmpty()) { + auto filename = filedialogDefaultName(qsl("image"), qsl(".png"), QString(), true); + _nameText.setText(st::semiboldTextStyle, filename, _textNameOptions); + _statusText = qsl("%1x%2").arg(_image.width()).arg(_image.height()); + _statusWidth = qMax(_nameText.maxWidth(), st::normalFont->width(_statusText)); + _fileIsImage = true; + } else { + auto fileinfo = QFileInfo(filepath); + auto filename = fileinfo.fileName(); + _nameText.setText(st::semiboldTextStyle, filename, _textNameOptions); + _statusText = formatSizeText(fileinfo.size()); + _statusWidth = qMax(_nameText.maxWidth(), st::normalFont->width(_statusText)); + _fileIsImage = fileIsImage(filename, mimeTypeForFile(fileinfo).name()); + } + } +} + +SendFilesBox::SendFilesBox(QWidget*, const QStringList &files, CompressConfirm compressed) +: _files(files) +, _compressConfirm(compressed) +, _caption(this, st::confirmCaptionArea, lang(lng_photos_comment)) { +} + +SendFilesBox::SendFilesBox(QWidget*, const QString &phone, const QString &firstname, const QString &lastname) +: _contactPhone(phone) +, _contactFirstName(firstname) +, _contactLastName(lastname) { + auto name = lng_full_name(lt_first_name, _contactFirstName, lt_last_name, _contactLastName); + _nameText.setText(st::semiboldTextStyle, name, _textNameOptions); + _statusText = _contactPhone; + _statusWidth = qMax(_nameText.maxWidth(), st::normalFont->width(_statusText)); + _contactPhotoEmpty.set(0, name); +} + +void SendFilesBox::prepare() { + if (_files.size() > 1) { + updateTitleText(); + } + + _send = addButton(lang(lng_send_button), [this] { onSend(); }); + addButton(lang(lng_cancel), [this] { closeBox(); }); + + if (_compressConfirm != CompressConfirm::None) { + auto compressed = (_compressConfirm == CompressConfirm::Auto) ? cCompressPastedImage() : (_compressConfirm == CompressConfirm::Yes); + auto text = lng_send_images_compress(lt_count, _files.size()); + _compressed.create(this, text, compressed, st::defaultBoxCheckbox); + connect(_compressed, SIGNAL(changed()), this, SLOT(onCompressedChange())); + } + if (_caption) { + _caption->setMaxLength(MaxPhotoCaption); + _caption->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both); + connect(_caption, SIGNAL(resized()), this, SLOT(onCaptionResized())); + connect(_caption, SIGNAL(submitted(bool)), this, SLOT(onSend(bool))); + connect(_caption, SIGNAL(cancelled()), this, SLOT(onClose())); + } + _send->setText(getSendButtonText()); + updateButtonsGeometry(); + updateBoxSize(); +} + +QString SendFilesBox::getSendButtonText() const { + if (!_contactPhone.isEmpty()) { + return lang(lng_send_button); + } else if (_compressed && _compressed->checked()) { + return lng_send_photos(lt_count, _files.size()); + } + return lng_send_files(lt_count, _files.size()); +} + +void SendFilesBox::onCompressedChange() { + setInnerFocus(); + _send->setText(getSendButtonText()); + updateButtonsGeometry(); + updateControlsGeometry(); +} + +void SendFilesBox::onCaptionResized() { + updateBoxSize(); + updateControlsGeometry(); + update(); +} + +void SendFilesBox::updateTitleText() { + _titleText = (_compressConfirm == CompressConfirm::None) ? lng_send_files_selected(lt_count, _files.size()) : lng_send_images_selected(lt_count, _files.size()); + update(); +} + +void SendFilesBox::updateBoxSize() { + auto newHeight = _titleText.isEmpty() ? 0 : st::boxTitleHeight; + if (!_preview.isNull()) { + newHeight += st::boxPhotoPadding.top() + _previewHeight; + } else if (!_fileThumb.isNull()) { + newHeight += st::boxPhotoPadding.top() + st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom(); + } else if (_files.size() > 1) { + newHeight += 0; + } else { + newHeight += st::boxPhotoPadding.top() + st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom(); + } + if (_compressed) { + newHeight += st::boxPhotoCompressedSkip + _compressed->heightNoMargins(); + } + if (_caption) { + newHeight += st::boxPhotoCaptionSkip + _caption->height(); + } + setDimensions(st::boxWideWidth, newHeight); +} + +void SendFilesBox::keyPressEvent(QKeyEvent *e) { + if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { + onSend((e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::MetaModifier)) && e->modifiers().testFlag(Qt::ShiftModifier)); + } else { + BoxContent::keyPressEvent(e); + } +} + +void SendFilesBox::paintEvent(QPaintEvent *e) { + BoxContent::paintEvent(e); + + Painter p(this); + + if (!_titleText.isEmpty()) { + p.setFont(st::boxPhotoTitleFont); + p.setPen(st::boxTitleFg); + p.drawTextLeft(st::boxPhotoTitlePosition.x(), st::boxPhotoTitlePosition.y(), width(), _titleText); + } + + if (!_preview.isNull()) { + if (_previewLeft > st::boxPhotoPadding.left()) { + p.fillRect(st::boxPhotoPadding.left(), st::boxPhotoPadding.top(), _previewLeft - st::boxPhotoPadding.left(), _previewHeight, st::confirmBg); + } + if (_previewLeft + _previewWidth < width() - st::boxPhotoPadding.right()) { + p.fillRect(_previewLeft + _previewWidth, st::boxPhotoPadding.top(), width() - st::boxPhotoPadding.right() - _previewLeft - _previewWidth, _previewHeight, st::confirmBg); + } + p.drawPixmap(_previewLeft, st::boxPhotoPadding.top(), _preview); + if (_animated) { + auto inner = QRect(_previewLeft + (_previewWidth - st::msgFileSize) / 2, st::boxPhotoPadding.top() + (_previewHeight - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); + p.setPen(Qt::NoPen); + p.setBrush(st::msgDateImgBg); + + { + PainterHighQualityEnabler hq(p); + p.drawEllipse(inner); + } + + auto icon = &st::historyFileInPlay; + icon->paintInCenter(p, inner); + } + } else if (_files.size() < 2) { + auto w = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); + auto h = _fileThumb.isNull() ? (st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom()) : (st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom()); + auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0; + if (_fileThumb.isNull()) { + nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); + nametop = st::msgFileNameTop; + nameright = st::msgFilePadding.left(); + statustop = st::msgFileStatusTop; + } else { + nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); + nametop = st::msgFileThumbNameTop; + nameright = st::msgFileThumbPadding.left(); + statustop = st::msgFileThumbStatusTop; + linktop = st::msgFileThumbLinkTop; + } + auto namewidth = w - nameleft - (_fileThumb.isNull() ? st::msgFilePadding.left() : st::msgFileThumbPadding.left()); + int32 x = (width() - w) / 2, y = st::boxPhotoPadding.top(); + + App::roundRect(p, x, y, w, h, st::msgOutBg, MessageOutCorners, &st::msgOutShadow); + + if (_fileThumb.isNull()) { + if (_contactPhone.isNull()) { + QRect inner(rtlrect(x + st::msgFilePadding.left(), y + st::msgFilePadding.top(), st::msgFileSize, st::msgFileSize, width())); + p.setPen(Qt::NoPen); + p.setBrush(st::msgFileOutBg); + + { + PainterHighQualityEnabler hq(p); + p.drawEllipse(inner); + } + + auto &icon = _fileIsImage ? st::historyFileOutImage : st::historyFileOutDocument; + icon.paintInCenter(p, inner); + } else { + _contactPhotoEmpty.paint(p, x + st::msgFilePadding.left(), y + st::msgFilePadding.top(), width(), st::msgFileSize); + } + } else { + QRect rthumb(rtlrect(x + st::msgFileThumbPadding.left(), y + st::msgFileThumbPadding.top(), st::msgFileThumbSize, st::msgFileThumbSize, width())); + p.drawPixmap(rthumb.topLeft(), _fileThumb); + } + p.setFont(st::semiboldFont); + p.setPen(st::historyFileNameOutFg); + _nameText.drawLeftElided(p, x + nameleft, y + nametop, namewidth, width()); + + auto &status = st::mediaOutFg; + p.setFont(st::normalFont); + p.setPen(status); + p.drawTextLeft(x + nameleft, y + statustop, width(), _statusText); + } +} + +void SendFilesBox::resizeEvent(QResizeEvent *e) { + BoxContent::resizeEvent(e); + updateControlsGeometry(); +} + +void SendFilesBox::updateControlsGeometry() { + auto bottom = height(); + if (_caption) { + _caption->resize(st::boxWideWidth - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(), _caption->height()); + _caption->moveToLeft(st::boxPhotoPadding.left(), bottom - _caption->height()); + bottom -= st::boxPhotoCaptionSkip + _caption->height(); + } + if (_compressed) { + _compressed->moveToLeft(st::boxPhotoPadding.left(), bottom - _compressed->heightNoMargins()); + bottom -= st::boxPhotoCompressedSkip + _compressed->heightNoMargins(); + } +} + +void SendFilesBox::setInnerFocus() { + if (!_caption || _caption->isHidden()) { + setFocus(); + } else { + _caption->setFocusFast(); + } +} + +void SendFilesBox::onSend(bool ctrlShiftEnter) { + if (_compressed && _compressConfirm == CompressConfirm::Auto && _compressed->checked() != cCompressPastedImage()) { + cSetCompressPastedImage(_compressed->checked()); + Local::writeUserSettings(); + } + _confirmed = true; + if (_confirmedCallback) { + auto compressed = _compressed ? _compressed->checked() : false; + auto caption = _caption ? prepareText(_caption->getLastText(), true) : QString(); + _confirmedCallback(_files, compressed, caption, ctrlShiftEnter); + } + closeBox(); +} + +void SendFilesBox::closeHook() { + if (!_confirmed && _cancelledCallback) { + _cancelledCallback(); + } +} + +EditCaptionBox::EditCaptionBox(QWidget*, HistoryItem *msg) +: _msgId(msg->fullId()) { + QSize dimensions; + ImagePtr image; + QString caption; + DocumentData *doc = 0; + if (HistoryMedia *media = msg->getMedia()) { + HistoryMediaType t = media->type(); + switch (t) { + case MediaTypeGif: { + _animated = true; + doc = static_cast(media)->getDocument(); + dimensions = doc->dimensions; + image = doc->thumb; + } break; + + case MediaTypePhoto: { + _photo = true; + PhotoData *photo = static_cast(media)->photo(); + dimensions = QSize(photo->full->width(), photo->full->height()); + image = photo->full; + } break; + + case MediaTypeVideo: { + _animated = true; + doc = static_cast(media)->getDocument(); + dimensions = doc->dimensions; + image = doc->thumb; + } break; + + case MediaTypeFile: + case MediaTypeMusicFile: + case MediaTypeVoiceFile: { + _doc = true; + doc = static_cast(media)->getDocument(); + image = doc->thumb; + } break; + } + caption = media->getCaption().text; + } + if ((!_animated && (dimensions.isEmpty() || doc)) || image->isNull()) { + _animated = false; + if (image->isNull()) { + _thumbw = 0; + } else { + int32 tw = image->width(), th = image->height(); + if (tw > th) { + _thumbw = (tw * st::msgFileThumbSize) / th; + } else { + _thumbw = st::msgFileThumbSize; + } + auto options = Images::Option::Smooth | Images::Option::RoundedSmall | Images::Option::RoundedTopLeft | Images::Option::RoundedTopRight | Images::Option::RoundedBottomLeft | Images::Option::RoundedBottomRight; + _thumb = Images::pixmap(image->pix().toImage(), _thumbw * cIntRetinaFactor(), 0, options, st::msgFileThumbSize, st::msgFileThumbSize); + } + + if (doc) { + if (doc->voice()) { + _name.setText(st::semiboldTextStyle, lang(lng_media_audio), _textNameOptions); + } else { + _name.setText(st::semiboldTextStyle, documentName(doc), _textNameOptions); + } + _status = formatSizeText(doc->size); + _statusw = qMax(_name.maxWidth(), st::normalFont->width(_status)); + _isImage = doc->isImage(); + } + } else { + int32 maxW = 0, maxH = 0; + if (_animated) { + int32 limitW = st::boxWideWidth - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); + int32 limitH = st::confirmMaxHeight; + maxW = qMax(dimensions.width(), 1); + maxH = qMax(dimensions.height(), 1); + if (maxW * limitH > maxH * limitW) { + if (maxW < limitW) { + maxH = maxH * limitW / maxW; + maxW = limitW; + } + } else { + if (maxH < limitH) { + maxW = maxW * limitH / maxH; + maxH = limitH; + } + } + _thumb = image->pixNoCache(maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), Images::Option::Smooth | Images::Option::Blurred, maxW, maxH); + } else { + maxW = dimensions.width(); + maxH = dimensions.height(); + _thumb = image->pixNoCache(maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), Images::Option::Smooth, maxW, maxH); + } + int32 tw = _thumb.width(), th = _thumb.height(); + if (!tw || !th) { + tw = th = 1; + } + _thumbw = st::boxWideWidth - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); + if (_thumb.width() < _thumbw) { + _thumbw = (_thumb.width() > 20) ? _thumb.width() : 20; + } + int32 maxthumbh = qMin(qRound(1.5 * _thumbw), int(st::confirmMaxHeight)); + _thumbh = qRound(th * float64(_thumbw) / tw); + if (_thumbh > maxthumbh) { + _thumbw = qRound(_thumbw * float64(maxthumbh) / _thumbh); + _thumbh = maxthumbh; + if (_thumbw < 10) { + _thumbw = 10; + } + } + _thumbx = (st::boxWideWidth - _thumbw) / 2; + + _thumb = App::pixmapFromImageInPlace(_thumb.toImage().scaled(_thumbw * cIntRetinaFactor(), _thumbh * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + _thumb.setDevicePixelRatio(cRetinaFactor()); + } + if (_animated || _photo || _doc) { + _field.create(this, st::confirmCaptionArea, lang(lng_photo_caption), caption); + _field->setMaxLength(MaxPhotoCaption); + _field->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both); + } else { + auto original = msg->originalText(); + auto text = textApplyEntities(original.text, original.entities); + _field.create(this, st::editTextArea, lang(lng_photo_caption), text); +// _field->setMaxLength(MaxMessageSize); // entities can make text in input field larger but still valid + _field->setCtrlEnterSubmit(cCtrlEnter() ? Ui::CtrlEnterSubmit::CtrlEnter : Ui::CtrlEnterSubmit::Enter); + } +} + +bool EditCaptionBox::canEdit(HistoryItem *message) { + if (auto media = message->getMedia()) { + switch (media->type()) { + case MediaTypeGif: + case MediaTypePhoto: + case MediaTypeVideo: + case MediaTypeFile: + case MediaTypeMusicFile: + case MediaTypeVoiceFile: return true; + } + } + return false; +} + +void EditCaptionBox::prepare() { + addButton(lang(lng_settings_save), [this] { onSave(); }); + addButton(lang(lng_cancel), [this] { closeBox(); }); + + updateBoxSize(); + connect(_field, SIGNAL(submitted(bool)), this, SLOT(onSave(bool))); + connect(_field, SIGNAL(cancelled()), this, SLOT(onClose())); + connect(_field, SIGNAL(resized()), this, SLOT(onCaptionResized())); + + auto cursor = _field->textCursor(); + cursor.movePosition(QTextCursor::End); + _field->setTextCursor(cursor); +} + +void EditCaptionBox::onCaptionResized() { + updateBoxSize(); + resizeEvent(0); + update(); +} + +void EditCaptionBox::updateBoxSize() { + auto newHeight = st::boxPhotoPadding.top() + st::boxPhotoCaptionSkip + _field->height() + st::normalFont->height; + if (_photo || _animated) { + newHeight += _thumbh; + } else if (_thumbw) { + newHeight += 0 + st::msgFileThumbSize + 0; + } else if (_doc) { + newHeight += 0 + st::msgFileSize + 0; + } else { + newHeight += st::boxTitleFont->height; + } + setDimensions(st::boxWideWidth, newHeight); +} + +void EditCaptionBox::paintEvent(QPaintEvent *e) { + BoxContent::paintEvent(e); + + Painter p(this); + + if (_photo || _animated) { + if (_thumbx > st::boxPhotoPadding.left()) { + p.fillRect(st::boxPhotoPadding.left(), st::boxPhotoPadding.top(), _thumbx - st::boxPhotoPadding.left(), _thumbh, st::confirmBg); + } + if (_thumbx + _thumbw < width() - st::boxPhotoPadding.right()) { + p.fillRect(_thumbx + _thumbw, st::boxPhotoPadding.top(), width() - st::boxPhotoPadding.right() - _thumbx - _thumbw, _thumbh, st::confirmBg); + } + p.drawPixmap(_thumbx, st::boxPhotoPadding.top(), _thumb); + if (_animated) { + QRect inner(_thumbx + (_thumbw - st::msgFileSize) / 2, st::boxPhotoPadding.top() + (_thumbh - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); + p.setPen(Qt::NoPen); + p.setBrush(st::msgDateImgBg); + + { + PainterHighQualityEnabler hq(p); + p.drawEllipse(inner); + } + + auto icon = &st::historyFileInPlay; + icon->paintInCenter(p, inner); + } + } else if (_doc) { + int32 w = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(); + int32 h = _thumbw ? (0 + st::msgFileThumbSize + 0) : (0 + st::msgFileSize + 0); + int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0; + if (_thumbw) { + nameleft = 0 + st::msgFileThumbSize + st::msgFileThumbPadding.right(); + nametop = st::msgFileThumbNameTop - st::msgFileThumbPadding.top(); + nameright = 0; + statustop = st::msgFileThumbStatusTop - st::msgFileThumbPadding.top(); + } else { + nameleft = 0 + st::msgFileSize + st::msgFilePadding.right(); + nametop = st::msgFileNameTop - st::msgFilePadding.top(); + nameright = 0; + statustop = st::msgFileStatusTop - st::msgFilePadding.top(); + } + int32 namewidth = w - nameleft - 0; + if (namewidth > _statusw) { + //w -= (namewidth - _statusw); + //namewidth = _statusw; + } + int32 x = (width() - w) / 2, y = st::boxPhotoPadding.top(); + +// App::roundRect(p, x, y, w, h, st::msgInBg, MessageInCorners, &st::msgInShadow); + + if (_thumbw) { + QRect rthumb(rtlrect(x + 0, y + 0, st::msgFileThumbSize, st::msgFileThumbSize, width())); + p.drawPixmap(rthumb.topLeft(), _thumb); + } else { + QRect inner(rtlrect(x + 0, y + 0, st::msgFileSize, st::msgFileSize, width())); + p.setPen(Qt::NoPen); + p.setBrush(st::msgFileInBg); + + { + PainterHighQualityEnabler hq(p); + p.drawEllipse(inner); + } + + auto icon = &(_isImage ? st::historyFileInImage : st::historyFileInDocument); + icon->paintInCenter(p, inner); + } + p.setFont(st::semiboldFont); + p.setPen(st::historyFileNameInFg); + _name.drawLeftElided(p, x + nameleft, y + nametop, namewidth, width()); + + auto &status = st::mediaInFg; + p.setFont(st::normalFont); + p.setPen(status); + p.drawTextLeft(x + nameleft, y + statustop, width(), _status); + } else { + p.setFont(st::boxTitleFont); + p.setPen(st::boxTextFg); + p.drawTextLeft(_field->x(), st::boxPhotoPadding.top(), width(), lang(lng_edit_message)); + } + + if (!_error.isEmpty()) { + p.setFont(st::normalFont); + p.setPen(st::boxTextFgError); + p.drawTextLeft(_field->x(), _field->y() + _field->height() + (st::boxButtonPadding.top() / 2), width(), _error); + } +} + +void EditCaptionBox::resizeEvent(QResizeEvent *e) { + BoxContent::resizeEvent(e); + _field->resize(st::boxWideWidth - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(), _field->height()); + _field->moveToLeft(st::boxPhotoPadding.left(), height() - st::normalFont->height - _field->height()); +} + +void EditCaptionBox::setInnerFocus() { + _field->setFocusFast(); +} + +void EditCaptionBox::onSave(bool ctrlShiftEnter) { + if (_saveRequestId) return; + + HistoryItem *item = App::histItemById(_msgId); + if (!item) { + _error = lang(lng_edit_deleted); + update(); + return; + } + + MTPmessages_EditMessage::Flags flags = MTPmessages_EditMessage::Flag::f_message; + if (_previewCancelled) { + flags |= MTPmessages_EditMessage::Flag::f_no_webpage; + } + MTPVector sentEntities; + if (!sentEntities.c_vector().v.isEmpty()) { + flags |= MTPmessages_EditMessage::Flag::f_entities; + } + auto text = prepareText(_field->getLastText(), true); + _saveRequestId = MTP::send(MTPmessages_EditMessage(MTP_flags(flags), item->history()->peer->input, MTP_int(item->id), MTP_string(text), MTPnullMarkup, sentEntities), rpcDone(&EditCaptionBox::saveDone), rpcFail(&EditCaptionBox::saveFail)); +} + +void EditCaptionBox::saveDone(const MTPUpdates &updates) { + _saveRequestId = 0; + closeBox(); + if (App::main()) { + App::main()->sentUpdatesReceived(updates); + } +} + +bool EditCaptionBox::saveFail(const RPCError &error) { + if (MTP::isDefaultHandledError(error)) return false; + + _saveRequestId = 0; + QString err = error.type(); + if (err == qstr("MESSAGE_ID_INVALID") || err == qstr("CHAT_ADMIN_REQUIRED") || err == qstr("MESSAGE_EDIT_TIME_EXPIRED")) { + _error = lang(lng_edit_error); + } else if (err == qstr("MESSAGE_NOT_MODIFIED")) { + closeBox(); + return true; + } else if (err == qstr("MESSAGE_EMPTY")) { + _field->setFocus(); + _field->showError(); + } else { + _error = lang(lng_edit_error); + } + update(); + return true; +} diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h new file mode 100644 index 000000000..62ab8a922 --- /dev/null +++ b/Telegram/SourceFiles/boxes/send_files_box.h @@ -0,0 +1,155 @@ +/* +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 "boxes/abstractbox.h" +#include "localimageloader.h" + +namespace Ui { +class Checkbox; +class RoundButton; +class InputArea; +} // namespace Ui + +class SendFilesBox : public BoxContent { + Q_OBJECT + +public: + SendFilesBox(QWidget*, const QString &filepath, QImage image, CompressConfirm compressed, bool animated = false); + SendFilesBox(QWidget*, const QStringList &files, CompressConfirm compressed); + SendFilesBox(QWidget*, const QString &phone, const QString &firstname, const QString &lastname); + + void setConfirmedCallback(base::lambda &&callback) { + _confirmedCallback = std_::move(callback); + } + void setCancelledCallback(base::lambda &&callback) { + _cancelledCallback = std_::move(callback); + } + + void closeHook() override; + +protected: + void prepare() override; + void setInnerFocus() override; + + void keyPressEvent(QKeyEvent *e) override; + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + +private slots: + void onCompressedChange(); + void onSend(bool ctrlShiftEnter = false); + void onCaptionResized(); + void onClose() { + closeBox(); + } + +private: + void updateTitleText(); + void updateBoxSize(); + void updateControlsGeometry(); + QString getSendButtonText() const; + + QString _titleText; + QStringList _files; + const QImage _image; + + CompressConfirm _compressConfirm = CompressConfirm::None; + bool _animated = false; + + QPixmap _preview; + int _previewLeft = 0; + int _previewWidth = 0; + int _previewHeight = 0; + + QPixmap _fileThumb; + Text _nameText; + bool _fileIsImage = false; + QString _statusText; + int _statusWidth = 0; + + QString _contactPhone; + QString _contactFirstName; + QString _contactLastName; + EmptyUserpic _contactPhotoEmpty; + + base::lambda _confirmedCallback; + base::lambda _cancelledCallback; + bool _confirmed = false; + + object_ptr _caption = { nullptr }; + object_ptr _compressed = { nullptr }; + + QPointer _send; + +}; + +class EditCaptionBox : public BoxContent, public RPCSender { + Q_OBJECT + +public: + EditCaptionBox(QWidget*, HistoryItem *msg); + static bool canEdit(HistoryItem *message); + +public slots: + void onCaptionResized(); + void onSave(bool ctrlShiftEnter = false); + void onClose() { + closeBox(); + } + +protected: + void prepare() override; + void setInnerFocus() override; + + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + +private: + void updateBoxSize(); + + void saveDone(const MTPUpdates &updates); + bool saveFail(const RPCError &error); + + FullMsgId _msgId; + bool _animated = false; + bool _photo = false; + bool _doc = false; + + QPixmap _thumb; + + object_ptr _field = { nullptr }; + + int _thumbx = 0; + int _thumby = 0; + int _thumbw = 0; + int _thumbh = 0; + Text _name; + QString _status; + int _statusw = 0; + bool _isImage = false; + + bool _previewCancelled = false; + mtpRequestId _saveRequestId = 0; + + QString _error; + +}; diff --git a/Telegram/SourceFiles/boxes/sessionsbox.cpp b/Telegram/SourceFiles/boxes/sessionsbox.cpp index 52003f6ff..b26fbbb06 100644 --- a/Telegram/SourceFiles/boxes/sessionsbox.cpp +++ b/Telegram/SourceFiles/boxes/sessionsbox.cpp @@ -19,88 +19,89 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" +#include "boxes/sessionsbox.h" + #include "lang.h" - #include "localstorage.h" - -#include "sessionsbox.h" #include "mainwidget.h" #include "mainwindow.h" - #include "countries.h" -#include "confirmbox.h" +#include "boxes/confirmbox.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/scroll_area.h" +#include "styles/style_boxes.h" -SessionsBox::SessionsBox() : ScrollableBox(st::sessionsScroll) -, _loading(true) -, _inner(this, &_list, &_current) -, _shadow(this) -, _done(this, lang(lng_about_done), st::defaultBoxButton) -, _shortPollRequest(0) { - setMaxHeight(st::sessionsHeight); +SessionsBox::SessionsBox(QWidget*) +: _shortPollTimer(this) { +} + +void SessionsBox::prepare() { + setTitle(lang(lng_sessions_other_header)); + + addButton(lang(lng_close), [this] { closeBox(); }); + + setDimensions(st::boxWideWidth, st::sessionsHeight); - connect(&_done, SIGNAL(clicked()), this, SLOT(onClose())); connect(_inner, SIGNAL(oneTerminated()), this, SLOT(onOneTerminated())); connect(_inner, SIGNAL(allTerminated()), this, SLOT(onAllTerminated())); connect(_inner, SIGNAL(terminateAll()), this, SLOT(onTerminateAll())); - connect(App::wnd(), SIGNAL(newAuthorization()), this, SLOT(onNewAuthorization())); - connect(&_shortPollTimer, SIGNAL(timeout()), this, SLOT(onShortPollAuthorizations())); + connect(App::wnd(), SIGNAL(checkNewAuthorization()), this, SLOT(onCheckNewAuthorization())); + connect(_shortPollTimer, SIGNAL(timeout()), this, SLOT(onShortPollAuthorizations())); - init(_inner, st::boxButtonPadding.bottom() + _done.height() + st::boxButtonPadding.top(), st::boxTitleHeight); + _inner = setInnerWidget(object_ptr(this, &_list, &_current), st::sessionsScroll); _inner->resize(width(), st::noContactsHeight); - prepare(); + setLoading(true); MTP::send(MTPaccount_GetAuthorizations(), rpcDone(&SessionsBox::gotAuthorizations)); } -void SessionsBox::resizeEvent(QResizeEvent *e) { - ScrollableBox::resizeEvent(e); - _shadow.setGeometry(0, height() - st::boxButtonPadding.bottom() - _done.height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth); - _done.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _done.height()); +void SessionsBox::setLoading(bool loading) { + if (_loading != loading) { + _loading = loading; + setInnerVisible(!_loading); + } } -void SessionsBox::showAll() { - _done.show(); - if (_loading) { - scrollArea()->hide(); - _shadow.hide(); - } else { - scrollArea()->show(); - _shadow.show(); - } - ScrollableBox::showAll(); +void SessionsBox::resizeEvent(QResizeEvent *e) { + BoxContent::resizeEvent(e); + + _inner->resize(width(), _inner->height()); } void SessionsBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; + BoxContent::paintEvent(e); - paintTitle(p, lang(lng_sessions_other_header)); - p.translate(0, st::boxTitleHeight); + Painter p(this); if (_loading) { - p.setFont(st::noContactsFont->f); - p.setPen(st::noContactsColor->p); + p.setFont(st::noContactsFont); + p.setPen(st::noContactsColor); p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center); } } void SessionsBox::gotAuthorizations(const MTPaccount_Authorizations &result) { - _loading = false; _shortPollRequest = 0; + setLoading(false); - int32 availCurrent = st::boxWideWidth - st::sessionPadding.left() - st::sessionTerminateSkip; - int32 availOther = availCurrent - st::sessionTerminate.iconPos.x();// -st::sessionTerminate.width - st::sessionTerminateSkip; + auto availCurrent = st::boxWideWidth - st::sessionPadding.left() - st::sessionTerminateSkip; + auto availOther = availCurrent - st::sessionTerminate.iconPosition.x();// -st::sessionTerminate.width - st::sessionTerminateSkip; _list.clear(); - const auto &v(result.c_account_authorizations().vauthorizations.c_vector().v); - int32 l = v.size(); - if (l > 1) _list.reserve(l - 1); + if (result.type() != mtpc_account_authorizations) { + return; + } + auto &v = result.c_account_authorizations().vauthorizations.c_vector().v; + _list.reserve(v.size()); const CountriesByISO2 &countries(countriesByISO2()); - for (int32 i = 0; i < l; ++i) { - const auto &d(v.at(i).c_authorization()); + for_const (auto &auth, v) { + if (auth.type() != mtpc_authorization) { + continue; + } + auto &d = auth.c_authorization(); Data data; data.hash = d.vhash.v; @@ -139,7 +140,7 @@ void SessionsBox::gotAuthorizations(const MTPaccount_Authorizations &result) { data.ip = qs(d.vip) + (country.isEmpty() ? QString() : QString::fromUtf8(" \xe2\x80\x93 ") + country); if (!data.hash || (d.vflags.v & 1)) { data.active = lang(lng_sessions_header); - data.activeWidth = st::sessionActiveFont->width(lang(lng_sessions_header)); + data.activeWidth = st::sessionWhenFont->width(lang(lng_sessions_header)); int32 availForName = availCurrent - st::sessionPadding.right() - data.activeWidth; if (data.nameWidth > availForName) { data.name = st::sessionNameFont->elided(data.name, availForName); @@ -167,7 +168,7 @@ void SessionsBox::gotAuthorizations(const MTPaccount_Authorizations &result) { } else { data.active = lastDate.toString(qsl("d.MM.yy")); } - data.activeWidth = st::sessionActiveFont->width(data.active); + data.activeWidth = st::sessionWhenFont->width(data.active); int32 availForName = availOther - st::sessionPadding.right() - data.activeWidth; if (data.nameWidth > availForName) { data.name = st::sessionNameFont->elided(data.name, availForName); @@ -194,34 +195,24 @@ void SessionsBox::gotAuthorizations(const MTPaccount_Authorizations &result) { } } _inner->listUpdated(); - if (!_done.isHidden()) { - showAll(); - update(); - } - _shortPollTimer.start(SessionsShortPollTimeout); + update(); + + _shortPollTimer->start(SessionsShortPollTimeout); } void SessionsBox::onOneTerminated() { - if (_list.isEmpty()) { - if (!_done.isHidden()) { - showAll(); - update(); - } - } + update(); } void SessionsBox::onShortPollAuthorizations() { if (!_shortPollRequest) { _shortPollRequest = MTP::send(MTPaccount_GetAuthorizations(), rpcDone(&SessionsBox::gotAuthorizations)); - if (!_done.isHidden()) { - showAll(); - update(); - } + update(); } } -void SessionsBox::onNewAuthorization() { +void SessionsBox::onCheckNewAuthorization() { onShortPollAuthorizations(); // _shortPollTimer.start(1000); } @@ -235,21 +226,15 @@ void SessionsBox::onAllTerminated() { } void SessionsBox::onTerminateAll() { - _loading = true; - if (!_done.isHidden()) { - showAll(); - update(); - } + setLoading(true); } -SessionsBox::Inner::Inner(QWidget *parent, SessionsBox::List *list, SessionsBox::Data *current) : ScrolledWidget(parent) +SessionsBox::Inner::Inner(QWidget *parent, SessionsBox::List *list, SessionsBox::Data *current) : TWidget(parent) , _list(list) , _current(current) -, _terminating(0) -, _terminateAll(this, lang(lng_sessions_terminate_all), st::redBoxLinkButton) -, _terminateBox(0) { - connect(&_terminateAll, SIGNAL(clicked()), this, SLOT(onTerminateAll())); - _terminateAll.hide(); +, _terminateAll(this, lang(lng_sessions_terminate_all), st::sessionTerminateAllButton) { + connect(_terminateAll, SIGNAL(clicked()), this, SLOT(onTerminateAll())); + _terminateAll->hide(); setAttribute(Qt::WA_OpaquePaintEvent); } @@ -257,8 +242,8 @@ void SessionsBox::Inner::paintEvent(QPaintEvent *e) { QRect r(e->rect()); Painter p(this); - p.fillRect(r, st::white->b); - int32 x = st::sessionPadding.left(), xact = st::sessionTerminateSkip + st::sessionTerminate.iconPos.x();// st::sessionTerminateSkip + st::sessionTerminate.width + st::sessionTerminateSkip; + p.fillRect(r, st::boxBg); + int32 x = st::sessionPadding.left(), xact = st::sessionTerminateSkip + st::sessionTerminate.iconPosition.x();// st::sessionTerminateSkip + st::sessionTerminate.width + st::sessionTerminateSkip; int32 w = width(); if (_current->active.isEmpty() && _list->isEmpty()) { @@ -270,24 +255,24 @@ void SessionsBox::Inner::paintEvent(QPaintEvent *e) { if (r.y() <= st::sessionCurrentHeight) { p.translate(0, st::sessionCurrentPadding.top()); - p.setFont(st::sessionNameFont->f); - p.setPen(st::black->p); + p.setFont(st::sessionNameFont); + p.setPen(st::sessionNameFg); p.drawTextLeft(x, st::sessionPadding.top(), w, _current->name, _current->nameWidth); - p.setFont(st::sessionActiveFont->f); - p.setPen(st::sessionActiveColor->p); + p.setFont(st::sessionWhenFont); + p.setPen(st::sessionWhenFg); p.drawTextRight(x, st::sessionPadding.top(), w, _current->active, _current->activeWidth); - p.setFont(st::sessionInfoFont->f); - p.setPen(st::black->p); + p.setFont(st::sessionInfoFont); + p.setPen(st::boxTextFg); p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height, w, _current->info, _current->infoWidth); - p.setPen(st::sessionInfoColor->p); + p.setPen(st::sessionInfoFg); p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height + st::sessionInfoFont->height, w, _current->ip, _current->ipWidth); } p.translate(0, st::sessionCurrentHeight - st::sessionCurrentPadding.top()); if (_list->isEmpty()) { - p.setFont(st::sessionInfoFont->f); - p.setPen(st::sessionInfoColor->p); + p.setFont(st::sessionInfoFont); + p.setPen(st::sessionInfoFg); p.drawText(QRect(st::sessionPadding.left(), 0, width() - st::sessionPadding.left() - st::sessionPadding.right(), st::noContactsHeight), lang(lng_sessions_other_desc), style::al_topleft); return; } @@ -300,18 +285,18 @@ void SessionsBox::Inner::paintEvent(QPaintEvent *e) { for (int32 i = from; i < to; ++i) { const SessionsBox::Data &auth(_list->at(i)); - p.setFont(st::sessionNameFont->f); - p.setPen(st::black->p); + p.setFont(st::sessionNameFont); + p.setPen(st::sessionNameFg); p.drawTextLeft(x, st::sessionPadding.top(), w, auth.name, auth.nameWidth); - p.setFont(st::sessionActiveFont->f); - p.setPen(st::sessionActiveColor->p); + p.setFont(st::sessionWhenFont); + p.setPen(st::sessionWhenFg); p.drawTextRight(xact, st::sessionPadding.top(), w, auth.active, auth.activeWidth); - p.setFont(st::sessionInfoFont->f); - p.setPen(st::black->p); + p.setFont(st::sessionInfoFont); + p.setPen(st::boxTextFg); p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height, w, auth.info, auth.infoWidth); - p.setPen(st::sessionInfoColor->p); + p.setPen(st::sessionInfoFg); p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height + st::sessionInfoFont->height, w, auth.ip, auth.ipWidth); p.translate(0, st::sessionHeight); @@ -319,51 +304,35 @@ void SessionsBox::Inner::paintEvent(QPaintEvent *e) { } void SessionsBox::Inner::onTerminate() { - for (TerminateButtons::iterator i = _terminateButtons.begin(), e = _terminateButtons.end(); i != e; ++i) { - if (i.value()->getState() & Button::StateOver) { - _terminating = i.key(); - + for (auto i = _terminateButtons.begin(), e = _terminateButtons.end(); i != e; ++i) { + if (i.value()->isOver()) { if (_terminateBox) _terminateBox->deleteLater(); - _terminateBox = new ConfirmBox(lang(lng_settings_reset_one_sure), lang(lng_settings_reset_button), st::attentionBoxButton); - connect(_terminateBox, SIGNAL(confirmed()), this, SLOT(onTerminateSure())); - connect(_terminateBox, SIGNAL(destroyed(QObject*)), this, SLOT(onNoTerminateBox(QObject*))); - Ui::showLayer(_terminateBox, KeepOtherLayers); + _terminateBox = Ui::show(Box(lang(lng_settings_reset_one_sure), lang(lng_settings_reset_button), st::attentionBoxButton, base::lambda_guarded(this, [this, terminating = i.key()] { + if (_terminateBox) { + _terminateBox->closeBox(); + _terminateBox = nullptr; + } + MTP::send(MTPaccount_ResetAuthorization(MTP_long(terminating)), rpcDone(&Inner::terminateDone, terminating), rpcFail(&Inner::terminateFail, terminating)); + auto i = _terminateButtons.find(terminating); + if (i != _terminateButtons.cend()) { + i.value()->clearState(); + i.value()->hide(); + } + })), KeepOtherLayers); } } } -void SessionsBox::Inner::onTerminateSure() { - if (_terminateBox) { - _terminateBox->onClose(); - _terminateBox = 0; - } - MTP::send(MTPaccount_ResetAuthorization(MTP_long(_terminating)), rpcDone(&Inner::terminateDone, _terminating), rpcFail(&Inner::terminateFail, _terminating)); - TerminateButtons::iterator i = _terminateButtons.find(_terminating); - if (i != _terminateButtons.cend()) { - i.value()->clearState(); - i.value()->hide(); - } -} - void SessionsBox::Inner::onTerminateAll() { if (_terminateBox) _terminateBox->deleteLater(); - _terminateBox = new ConfirmBox(lang(lng_settings_reset_sure), lang(lng_settings_reset_button), st::attentionBoxButton); - connect(_terminateBox, SIGNAL(confirmed()), this, SLOT(onTerminateAllSure())); - connect(_terminateBox, SIGNAL(destroyed(QObject*)), this, SLOT(onNoTerminateBox(QObject*))); - Ui::showLayer(_terminateBox, KeepOtherLayers); -} - -void SessionsBox::Inner::onTerminateAllSure() { - if (_terminateBox) { - _terminateBox->onClose(); - _terminateBox = 0; - } - MTP::send(MTPauth_ResetAuthorizations(), rpcDone(&Inner::terminateAllDone), rpcFail(&Inner::terminateAllFail)); - emit terminateAll(); -} - -void SessionsBox::Inner::onNoTerminateBox(QObject *obj) { - if (obj == _terminateBox) _terminateBox = 0; + _terminateBox = Ui::show(Box(lang(lng_settings_reset_sure), lang(lng_settings_reset_button), st::attentionBoxButton, base::lambda_guarded(this, [this] { + if (_terminateBox) { + _terminateBox->closeBox(); + _terminateBox = nullptr; + } +// MTP::send(MTPauth_ResetAuthorizations(), rpcDone(&Inner::terminateAllDone), rpcFail(&Inner::terminateAllFail)); + emit terminateAll(); + })), KeepOtherLayers); } void SessionsBox::Inner::terminateDone(uint64 hash, const MTPBool &result) { @@ -399,14 +368,14 @@ bool SessionsBox::Inner::terminateAllFail(const RPCError &error) { } void SessionsBox::Inner::resizeEvent(QResizeEvent *e) { - _terminateAll.moveToLeft(st::sessionPadding.left(), st::sessionCurrentPadding.top() + st::sessionHeight + st::sessionCurrentPadding.bottom()); + _terminateAll->moveToLeft(st::sessionPadding.left(), st::sessionCurrentPadding.top() + st::sessionHeight + st::sessionCurrentPadding.bottom()); } void SessionsBox::Inner::listUpdated() { if (_list->isEmpty()) { - _terminateAll.hide(); + _terminateAll->hide(); } else { - _terminateAll.show(); + _terminateAll->show(); } for (TerminateButtons::iterator i = _terminateButtons.begin(), e = _terminateButtons.end(); i != e; ++i) { i.value()->move(0, -1); @@ -414,7 +383,7 @@ void SessionsBox::Inner::listUpdated() { for (int32 i = 0, l = _list->size(); i < l; ++i) { TerminateButtons::iterator j = _terminateButtons.find(_list->at(i).hash); if (j == _terminateButtons.cend()) { - j = _terminateButtons.insert(_list->at(i).hash, new IconedButton(this, st::sessionTerminate)); + j = _terminateButtons.insert(_list->at(i).hash, new Ui::IconButton(this, st::sessionTerminate)); connect(j.value(), SIGNAL(clicked()), this, SLOT(onTerminate())); } j.value()->moveToRight(st::sessionTerminateSkip, st::sessionCurrentHeight + i * st::sessionHeight + st::sessionTerminateTop, width()); diff --git a/Telegram/SourceFiles/boxes/sessionsbox.h b/Telegram/SourceFiles/boxes/sessionsbox.h index f591464f4..8ede15969 100644 --- a/Telegram/SourceFiles/boxes/sessionsbox.h +++ b/Telegram/SourceFiles/boxes/sessionsbox.h @@ -20,31 +20,37 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "abstractbox.h" +#include "boxes/abstractbox.h" #include "core/single_timer.h" class ConfirmBox; -class SessionsBox : public ScrollableBox, public RPCSender { +namespace Ui { +class IconButton; +class LinkButton; +} // namespace Ui + +class SessionsBox : public BoxContent, public RPCSender { Q_OBJECT public: - SessionsBox(); + SessionsBox(QWidget*); -public slots: +protected: + void prepare() override; + + void resizeEvent(QResizeEvent *e) override; + void paintEvent(QPaintEvent *e) override; + +private slots: void onOneTerminated(); void onAllTerminated(); void onTerminateAll(); void onShortPollAuthorizations(); - void onNewAuthorization(); - -protected: - void resizeEvent(QResizeEvent *e) override; - void paintEvent(QPaintEvent *e) override; - - void showAll() override; + void onCheckNewAuthorization(); private: + void setLoading(bool loading); struct Data { uint64 hash; @@ -56,23 +62,21 @@ private: void gotAuthorizations(const MTPaccount_Authorizations &result); - bool _loading; + bool _loading = false; Data _current; List _list; class Inner; - ChildWidget _inner; - ScrollableBoxShadow _shadow; - BoxButton _done; + QPointer _inner; - SingleTimer _shortPollTimer; - mtpRequestId _shortPollRequest; + object_ptr _shortPollTimer; + mtpRequestId _shortPollRequest = 0; }; // This class is hold in header because it requires Qt preprocessing. -class SessionsBox::Inner : public ScrolledWidget, public RPCSender { +class SessionsBox::Inner : public TWidget, public RPCSender { Q_OBJECT public: @@ -91,10 +95,7 @@ signals: public slots: void onTerminate(); - void onTerminateSure(); void onTerminateAll(); - void onTerminateAllSure(); - void onNoTerminateBox(QObject *obj); private: void terminateDone(uint64 hash, const MTPBool &result); @@ -106,11 +107,10 @@ private: SessionsBox::List *_list; SessionsBox::Data *_current; - typedef QMap TerminateButtons; + typedef QMap TerminateButtons; TerminateButtons _terminateButtons; - uint64 _terminating; - LinkButton _terminateAll; - ConfirmBox *_terminateBox; + object_ptr _terminateAll; + QPointer _terminateBox; }; diff --git a/Telegram/SourceFiles/boxes/sharebox.cpp b/Telegram/SourceFiles/boxes/sharebox.cpp index a0054c2d2..b0dd5c678 100644 --- a/Telegram/SourceFiles/boxes/sharebox.cpp +++ b/Telegram/SourceFiles/boxes/sharebox.cpp @@ -35,30 +35,32 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "ui/toast/toast.h" #include "ui/widgets/multi_select.h" #include "history/history_media_types.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/scroll_area.h" +#include "window/window_theme.h" #include "boxes/contactsbox.h" -ShareBox::ShareBox(CopyCallback &©Callback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback) : ItemListBox(st::boxScroll) -, _copyCallback(std_::move(copyCallback)) +ShareBox::ShareBox(QWidget*, CopyCallback &©Callback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback) +: _copyCallback(std_::move(copyCallback)) , _submitCallback(std_::move(submitCallback)) -, _inner(this, std_::move(filterCallback)) +, _filterCallback(std_::move(filterCallback)) , _select(this, st::contactsMultiSelect, lang(lng_participant_filter)) -, _copy(this, lang(lng_share_copy_link), st::defaultBoxButton) -, _share(this, lang(lng_share_confirm), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) -, _topShadow(this) -, _bottomShadow(this) { +, _searchTimer(this) { +} + +void ShareBox::prepare() { _select->resizeToWidth(st::boxWideWidth); myEnsureResized(_select); - auto topSkip = getTopScrollSkip(); - auto bottomSkip = st::boxButtonPadding.top() + _share->height() + st::boxButtonPadding.bottom(); - init(_inner, bottomSkip, topSkip); + setTitle(lang(lng_share_title)); + _inner = setInnerWidget(object_ptr(this, std_::move(_filterCallback)), getTopScrollSkip()); connect(_inner, SIGNAL(mustScrollTo(int,int)), this, SLOT(onMustScrollTo(int,int))); - connect(_copy, SIGNAL(clicked()), this, SLOT(onCopyLink())); - connect(_share, SIGNAL(clicked()), this, SLOT(onSubmit())); - connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose())); - connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); + + createButtons(); + + setDimensions(st::boxWideWidth, st::boxMaxListHeight); + _select->setQueryChangedCallback([this](const QString &query) { onFilterUpdate(query); }); _select->setItemRemovedCallback([this](uint64 itemId) { if (auto peer = App::peerLoaded(itemId)) { @@ -74,16 +76,14 @@ ShareBox::ShareBox(CopyCallback &©Callback, SubmitCallback &&submitCallback, onPeerSelectedChanged(peer, checked); }); - _searchTimer.setSingleShot(true); - connect(&_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchByUsername())); + _searchTimer->setSingleShot(true); + connect(_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchByUsername())); - updateButtonsVisibility(); - - prepare(); + _select->raise(); } int ShareBox::getTopScrollSkip() const { - auto result = st::boxTitleHeight; + auto result = 0; if (!_select->isHidden()) { result += _select->height(); } @@ -91,16 +91,7 @@ int ShareBox::getTopScrollSkip() const { } void ShareBox::updateScrollSkips() { - auto oldScrollHeight = scrollArea()->height(); - auto topSkip = getTopScrollSkip(); - auto bottomSkip = st::boxButtonPadding.top() + _share->height() + st::boxButtonPadding.bottom(); - setScrollSkips(bottomSkip, topSkip); - auto scrollHeightDelta = scrollArea()->height() - oldScrollHeight; - if (scrollHeightDelta) { - scrollArea()->scrollToY(scrollArea()->scrollTop() - scrollHeightDelta); - } - - _topShadow->setGeometry(0, topSkip, width(), st::lineWidth); + setInnerTopSkip(getTopScrollSkip(), true); } bool ShareBox::onSearchByUsername(bool searchCache) { @@ -132,7 +123,7 @@ bool ShareBox::onSearchByUsername(bool searchCache) { void ShareBox::onNeedSearchByUsername() { if (!onSearchByUsername(true)) { - _searchTimer.start(AutoSearchTimeout); + _searchTimer->start(AutoSearchTimeout); } } @@ -157,7 +148,6 @@ void ShareBox::peopleReceived(const MTPcontacts_Found &result, mtpRequestId requ } _peopleRequest = 0; - onScroll(); } } @@ -171,29 +161,19 @@ bool ShareBox::peopleFailed(const RPCError &error, mtpRequestId requestId) { return true; } -void ShareBox::doSetInnerFocus() { +void ShareBox::setInnerFocus() { _select->setInnerFocus(); } -void ShareBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; - - paintTitle(p, lang(lng_share_title)); -} - void ShareBox::resizeEvent(QResizeEvent *e) { - ItemListBox::resizeEvent(e); + BoxContent::resizeEvent(e); _select->resizeToWidth(width()); - _select->moveToLeft(0, st::boxTitleHeight); + _select->moveToLeft(0, 0); updateScrollSkips(); _inner->resizeToWidth(width()); - moveButtons(); - _topShadow->setGeometry(0, getTopScrollSkip(), width(), st::lineWidth); - _bottomShadow->setGeometry(0, height() - st::boxButtonPadding.bottom() - _share->height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth); } void ShareBox::keyPressEvent(QKeyEvent *e) { @@ -204,39 +184,44 @@ void ShareBox::keyPressEvent(QKeyEvent *e) { } else if (e->key() == Qt::Key_Down) { _inner->activateSkipColumn(1); } else if (e->key() == Qt::Key_PageUp) { - _inner->activateSkipPage(scrollArea()->height(), -1); + _inner->activateSkipPage(height() - getTopScrollSkip(), -1); } else if (e->key() == Qt::Key_PageDown) { - _inner->activateSkipPage(scrollArea()->height(), 1); + _inner->activateSkipPage(height() - getTopScrollSkip(), 1); } else { - ItemListBox::keyPressEvent(e); + BoxContent::keyPressEvent(e); } } else { - ItemListBox::keyPressEvent(e); + BoxContent::keyPressEvent(e); } } -void ShareBox::moveButtons() { - _copy->moveToRight(st::boxButtonPadding.right(), _share->y()); - _share->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _share->height()); - _cancel->moveToRight(st::boxButtonPadding.right() + _share->width() + st::boxButtonPadding.left(), _share->y()); +void ShareBox::updateButtons() { + auto hasSelected = _inner->hasSelected(); + if (_hasSelected != hasSelected) { + _hasSelected = hasSelected; + createButtons(); + } } -void ShareBox::updateButtonsVisibility() { - auto hasSelected = _inner->hasSelected(); - _copy->setVisible(!hasSelected); - _share->setVisible(hasSelected); - _cancel->setVisible(hasSelected); +void ShareBox::createButtons() { + clearButtons(); + if (_hasSelected) { + addButton(lang(lng_share_confirm), [this] { onSubmit(); }); + } else { + addButton(lang(lng_share_copy_link), [this] { onCopyLink(); }); + } + addButton(lang(lng_cancel), [this] { closeBox(); }); } void ShareBox::onFilterUpdate(const QString &query) { - scrollArea()->scrollToY(0); + onScrollToY(0); _inner->updateFilter(query); } void ShareBox::addPeerToMultiSelect(PeerData *peer, bool skipAnimation) { using AddItemWay = Ui::MultiSelect::AddItemWay; auto addItemWay = skipAnimation ? AddItemWay::SkipAnimation : AddItemWay::Default; - _select->addItem(peer->id, peer->shortName(), st::windowActiveBg, PaintUserpicCallback(peer), addItemWay); + _select->addItem(peer->id, peer->shortName(), st::activeButtonBg, PaintUserpicCallback(peer), addItemWay); } void ShareBox::onPeerSelectedChanged(PeerData *peer, bool checked) { @@ -263,33 +248,30 @@ void ShareBox::onCopyLink() { } void ShareBox::onSelectedChanged() { - updateButtonsVisibility(); - moveButtons(); + updateButtons(); update(); } void ShareBox::onMustScrollTo(int top, int bottom) { - auto scrollTop = scrollArea()->scrollTop(), scrollBottom = scrollTop + scrollArea()->height(); - auto from = scrollTop, to = scrollTop; - if (scrollTop > top) { - to = top; - } else if (scrollBottom < bottom) { - to = bottom - (scrollBottom - scrollTop); - } - if (from != to) { - _scrollAnimation.start([this]() { - scrollArea()->scrollToY(_scrollAnimation.current(scrollArea()->scrollTop())); - }, from, to, st::shareScrollDuration, anim::sineInOut); - } + onScrollToY(top, bottom); + //auto scrollTop = scrollArea()->scrollTop(), scrollBottom = scrollTop + scrollArea()->height(); + //auto from = scrollTop, to = scrollTop; + //if (scrollTop > top) { + // to = top; + //} else if (scrollBottom < bottom) { + // to = bottom - (scrollBottom - scrollTop); + //} + //if (from != to) { + // _scrollAnimation.start([this]() { scrollAnimationCallback(); }, from, to, st::shareScrollDuration, anim::sineInOut); + //} } -void ShareBox::onScroll() { - auto scroll = scrollArea(); - auto scrollTop = scroll->scrollTop(); - _inner->setVisibleTopBottom(scrollTop, scrollTop + scroll->height()); +void ShareBox::scrollAnimationCallback() { + //auto scrollTop = qRound(_scrollAnimation.current(scrollArea()->scrollTop())); + //scrollArea()->scrollToY(scrollTop); } -ShareBox::Inner::Inner(QWidget *parent, ShareBox::FilterCallback &&filterCallback) : ScrolledWidget(parent) +ShareBox::Inner::Inner(QWidget *parent, ShareBox::FilterCallback &&filterCallback) : TWidget(parent) , _filterCallback(std_::move(filterCallback)) , _chatsIndexed(std_::make_unique(Dialogs::SortMode::Add)) { _rowsTop = st::shareRowsTop; @@ -313,6 +295,18 @@ ShareBox::Inner::Inner(QWidget *parent, ShareBox::FilterCallback &&filterCallbac notifyPeerUpdated(update); })); subscribe(FileDownload::ImageLoaded(), [this] { update(); }); + + subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) { + if (update.paletteChanged()) { + invalidateCache(); + } + }); +} + +void ShareBox::Inner::invalidateCache() { + for_const (auto data, _dataMap) { + data->checkbox.invalidateCache(); + } } void ShareBox::Inner::setVisibleTopBottom(int visibleTop, int visibleBottom) { @@ -366,7 +360,7 @@ void ShareBox::Inner::updateChat(PeerData *peer) { } void ShareBox::Inner::updateChatName(Chat *chat, PeerData *peer) { - chat->name.setText(st::shareNameFont, peer->name, _textNameOptions); + chat->name.setText(st::shareNameStyle, peer->name, _textNameOptions); } void ShareBox::Inner::repaintChatAtIndex(int index) { @@ -427,13 +421,14 @@ int ShareBox::Inner::chatIndex(PeerData *peer) const { } void ShareBox::Inner::loadProfilePhotos(int yFrom) { + if (!parentWidget()) return; if (yFrom < 0) { yFrom = 0; } if (auto part = (yFrom % _rowHeight)) { yFrom -= part; } - int yTo = yFrom + (parentWidget() ? parentWidget()->height() : App::wnd()->height()) * 5 * _columnCount; + int yTo = yFrom + parentWidget()->height() * 5 * _columnCount; if (!yTo) { return; } @@ -484,22 +479,22 @@ ShareBox::Inner::Chat *ShareBox::Inner::getChat(Dialogs::Row *row) { void ShareBox::Inner::setActive(int active) { if (active != _active) { - auto changeNameFg = [this](int index, style::color from, style::color to) { + auto changeNameFg = [this](int index, float64 from, float64 to) { if (auto chat = getChatAtIndex(index)) { - chat->nameFg.start([this, peer = chat->peer] { + chat->nameActive.start([this, peer = chat->peer] { repaintChat(peer); - }, from->c, to->c, st::shareActivateDuration); + }, from, to, st::shareActivateDuration); } }; - changeNameFg(_active, st::shareNameActiveFg, st::shareNameFg); + changeNameFg(_active, 1., 0.); _active = active; - changeNameFg(_active, st::shareNameFg, st::shareNameActiveFg); + changeNameFg(_active, 0., 1.); } auto y = (_active < _columnCount) ? 0 : (_rowsTop + ((_active / _columnCount) * _rowHeight)); emit mustScrollTo(y, y + _rowHeight); } -void ShareBox::Inner::paintChat(Painter &p, uint64 ms, Chat *chat, int index) { +void ShareBox::Inner::paintChat(Painter &p, TimeMs ms, Chat *chat, int index) { auto x = _rowsLeft + qFloor((index % _columnCount) * _rowWidthReal); auto y = _rowsTop + (index / _columnCount) * _rowHeight; @@ -508,11 +503,8 @@ void ShareBox::Inner::paintChat(Painter &p, uint64 ms, Chat *chat, int index) { auto photoTop = st::sharePhotoTop; chat->checkbox.paint(p, ms, x + photoLeft, y + photoTop, outerWidth); - if (chat->nameFg.animating()) { - p.setPen(chat->nameFg.current()); - } else { - p.setPen((index == _active) ? st::shareNameActiveFg : st::shareNameFg); - } + auto nameActive = chat->nameActive.current(ms, (index == _active) ? 1. : 0.); + p.setPen(anim::pen(st::shareNameFg, st::shareNameActiveFg, nameActive)); auto nameWidth = (_rowWidth - st::shareColumnSkip); auto nameLeft = st::shareColumnSkip / 2; @@ -520,9 +512,9 @@ void ShareBox::Inner::paintChat(Painter &p, uint64 ms, Chat *chat, int index) { chat->name.drawLeftElided(p, x + nameLeft, y + nameTop, nameWidth, outerWidth, 2, style::al_top, 0, -1, 0, true); } -ShareBox::Inner::Chat::Chat(PeerData *peer, base::lambda_wrap updateCallback) +ShareBox::Inner::Chat::Chat(PeerData *peer, const base::lambda_copy &updateCallback) : peer(peer) -, checkbox(st::sharePhotoCheckbox, std_::move(updateCallback), PaintUserpicCallback(peer)) +, checkbox(st::sharePhotoCheckbox, updateCallback, PaintUserpicCallback(peer)) , name(st::sharePhotoCheckbox.imageRadius * 2) { } @@ -532,7 +524,7 @@ void ShareBox::Inner::paintEvent(QPaintEvent *e) { auto ms = getms(); auto r = e->rect(); p.setClipRect(r); - p.fillRect(r, st::white); + p.fillRect(r, st::boxBg); auto yFrom = r.y(), yTo = r.y() + r.height(); auto rowFrom = yFrom / _rowHeight; auto rowTo = (yTo + _rowHeight - 1) / _rowHeight; @@ -606,7 +598,7 @@ void ShareBox::Inner::updateUpon(const QPoint &pos) { auto left = _rowsLeft + qFloor(column * _rowWidthReal) + st::shareColumnSkip / 2; auto top = _rowsTop + row * _rowHeight + st::sharePhotoTop; auto xupon = (x >= left) && (x < left + (_rowWidth - st::shareColumnSkip)); - auto yupon = (y >= top) && (y < top + st::sharePhotoCheckbox.imageRadius * 2 + st::shareNameTop + st::shareNameFont->height * 2); + auto yupon = (y >= top) && (y < top + st::sharePhotoCheckbox.imageRadius * 2 + st::shareNameTop + st::shareNameStyle.font->height * 2); auto upon = (xupon && yupon) ? (row * _columnCount + column) : -1; if (upon >= displayedChatsCount()) { upon = -1; @@ -656,7 +648,7 @@ void ShareBox::Inner::peerUnselected(PeerData *peer) { changePeerCheckState(chat, false, ChangeStateWay::SkipCallback); } -void ShareBox::Inner::setPeerSelectedChangedCallback(base::lambda_unique callback) { +void ShareBox::Inner::setPeerSelectedChangedCallback(base::lambda &&callback) { _peerSelectedChangedCallback = std_::move(callback); } @@ -878,7 +870,7 @@ void shareGameScoreFromItem(HistoryItem *item) { if (media->type() == MediaTypeGame) { auto shortName = static_cast(media)->game()->shortName; - QApplication::clipboard()->setText(qsl("https://telegram.me/") + bot->username + qsl("?game=") + shortName); + QApplication::clipboard()->setText(CreateInternalLinkHttps(bot->username + qsl("?game=") + shortName)); Ui::Toast::Config toast; toast.text = lang(lng_share_game_link_copied); @@ -931,7 +923,7 @@ void shareGameScoreFromItem(HistoryItem *item) { } return false; }; - Ui::showLayer(new ShareBox(std_::move(copyCallback), std_::move(submitCallback), std_::move(filterCallback))); + Ui::show(Box(std_::move(copyCallback), std_::move(submitCallback), std_::move(filterCallback))); } } // namespace @@ -941,7 +933,7 @@ void shareGameScoreByHash(const QString &hash) { auto hashEncrypted = QByteArray::fromBase64(hash.toLatin1(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); if (hashEncrypted.size() <= key128Size || (hashEncrypted.size() % 0x10) != 0) { - Ui::showLayer(new InformBox(lang(lng_confirm_phone_link_invalid))); + Ui::show(Box(lang(lng_confirm_phone_link_invalid))); return; } @@ -961,20 +953,20 @@ void shareGameScoreByHash(const QString &hash) { // Check next 64 bits of SHA1() of data. auto skipSha1Part = sizeof(channelAccessHash); if (memcmp(dataSha1 + skipSha1Part, hashEncrypted.constData() + skipSha1Part, key128Size - skipSha1Part) != 0) { - Ui::showLayer(new InformBox(lang(lng_share_wrong_user))); + Ui::show(Box(lang(lng_share_wrong_user))); return; } auto hashDataInts = reinterpret_cast(hashData.data()); if (hashDataInts[0] != MTP::authedId()) { - Ui::showLayer(new InformBox(lang(lng_share_wrong_user))); + Ui::show(Box(lang(lng_share_wrong_user))); return; } // Check first 32 bits of channel access hash. auto channelAccessHashInts = reinterpret_cast(&channelAccessHash); if (channelAccessHashInts[0] != hashDataInts[3]) { - Ui::showLayer(new InformBox(lang(lng_share_wrong_user))); + Ui::show(Box(lang(lng_share_wrong_user))); return; } @@ -982,7 +974,7 @@ void shareGameScoreByHash(const QString &hash) { auto msgId = hashDataInts[2]; if (!channelId && channelAccessHash) { // If there is no channel id, there should be no channel access_hash. - Ui::showLayer(new InformBox(lang(lng_share_wrong_user))); + Ui::show(Box(lang(lng_share_wrong_user))); return; } @@ -994,7 +986,7 @@ void shareGameScoreByHash(const QString &hash) { if (auto item = App::histItemById(channel, msgId)) { shareGameScoreFromItem(item); } else { - Ui::showLayer(new InformBox(lang(lng_edit_deleted))); + Ui::show(Box(lang(lng_edit_deleted))); } }); }; @@ -1006,8 +998,8 @@ void shareGameScoreByHash(const QString &hash) { auto requestChannelIds = MTP_vector(1, MTP_inputChannel(MTP_int(channelId), MTP_long(channelAccessHash))); auto requestChannel = MTPchannels_GetChannels(requestChannelIds); MTP::send(requestChannel, rpcDone([channelId, resolveMessageAndShareScore](const MTPmessages_Chats &result) { - if (result.type() == mtpc_messages_chats) { - App::feedChats(result.c_messages_chats().vchats); + if (auto chats = Api::getChatsFromMessagesChats(result)) { + App::feedChats(*chats); } if (auto channel = App::channelLoaded(channelId)) { resolveMessageAndShareScore(channel); diff --git a/Telegram/SourceFiles/boxes/sharebox.h b/Telegram/SourceFiles/boxes/sharebox.h index 449c56242..60549c90a 100644 --- a/Telegram/SourceFiles/boxes/sharebox.h +++ b/Telegram/SourceFiles/boxes/sharebox.h @@ -20,11 +20,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "abstractbox.h" -#include "core/lambda_wrap.h" +#include "boxes/abstractbox.h" #include "core/observer.h" #include "core/vector_of_moveable.h" -#include "ui/effects/round_image_checkbox.h" +#include "ui/effects/round_checkbox.h" namespace Dialogs { class Row; @@ -42,18 +41,23 @@ class MultiSelect; QString appendShareGameScoreUrl(const QString &url, const FullMsgId &fullId); void shareGameScoreByHash(const QString &hash); -class ShareBox : public ItemListBox, public RPCSender { +class ShareBox : public BoxContent, public RPCSender { Q_OBJECT public: - using CopyCallback = base::lambda_unique; - using SubmitCallback = base::lambda_unique &)>; - using FilterCallback = base::lambda_unique; - ShareBox(CopyCallback &©Callback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback); + using CopyCallback = base::lambda; + using SubmitCallback = base::lambda &)>; + using FilterCallback = base::lambda; + ShareBox(QWidget*, CopyCallback &©Callback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback); + +protected: + void prepare() override; + void setInnerFocus() override; + + void resizeEvent(QResizeEvent *e) override; + void keyPressEvent(QKeyEvent *e) override; private slots: - void onScroll(); - bool onSearchByUsername(bool searchCache = false); void onNeedSearchByUsername(); @@ -62,18 +66,13 @@ private slots: void onMustScrollTo(int top, int bottom); -protected: - void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - void keyPressEvent(QKeyEvent *e) override; - - void doSetInnerFocus() override; - private: + void scrollAnimationCallback(); + void onFilterUpdate(const QString &query); void onSelectedChanged(); - void moveButtons(); - void updateButtonsVisibility(); + void updateButtons(); + void createButtons(); int getTopScrollSkip() const; void updateScrollSkips(); @@ -85,19 +84,16 @@ private: CopyCallback _copyCallback; SubmitCallback _submitCallback; + FilterCallback _filterCallback; + + object_ptr _select; class Inner; - ChildWidget _inner; - ChildWidget _select; + QPointer _inner; - ChildWidget _copy; - ChildWidget _share; - ChildWidget _cancel; + bool _hasSelected = false; - ChildWidget _topShadow; - ChildWidget _bottomShadow; - - QTimer _searchTimer; + object_ptr _searchTimer; QString _peopleQuery; bool _peopleFull = false; mtpRequestId _peopleRequest = 0; @@ -108,18 +104,18 @@ private: using PeopleQueries = QMap; PeopleQueries _peopleQueries; - IntAnimation _scrollAnimation; + Animation _scrollAnimation; }; // This class is hold in header because it requires Qt preprocessing. -class ShareBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber { +class ShareBox::Inner : public TWidget, public RPCSender, private base::Subscriber { Q_OBJECT public: Inner(QWidget *parent, ShareBox::FilterCallback &&filterCallback); - void setPeerSelectedChangedCallback(base::lambda_unique callback); + void setPeerSelectedChangedCallback(base::lambda &&callback); void peerUnselected(PeerData *peer); QVector selected() const; @@ -153,18 +149,19 @@ protected: private: // Observed notifications. void notifyPeerUpdated(const Notify::PeerUpdate &update); + void invalidateCache(); int displayedChatsCount() const; struct Chat { - Chat(PeerData *peer, base::lambda_wrap updateCallback); + Chat(PeerData *peer, const base::lambda_copy &updateCallback); PeerData *peer; Ui::RoundImageCheckbox checkbox; Text name; - ColorAnimation nameFg; + Animation nameActive; }; - void paintChat(Painter &p, uint64 ms, Chat *chat, int index); + void paintChat(Painter &p, TimeMs ms, Chat *chat, int index); void updateChat(PeerData *peer); void updateChatName(Chat *chat, PeerData *peer); void repaintChat(PeerData *peer); @@ -207,7 +204,7 @@ private: using SelectedChats = OrderedSet; SelectedChats _selected; - base::lambda_unique _peerSelectedChangedCallback; + base::lambda _peerSelectedChangedCallback; ChatData *data(Dialogs::Row *row); diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp index 907a5f9d8..809ee84bd 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.cpp +++ b/Telegram/SourceFiles/boxes/stickers_box.cpp @@ -29,7 +29,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "apiwrap.h" #include "localstorage.h" #include "dialogs/dialogs_layout.h" +#include "styles/style_boxes.h" #include "styles/style_stickers.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/scroll_area.h" +#include "ui/effects/ripple_animation.h" +#include "ui/effects/slide_animation.h" +#include "ui/widgets/discrete_sliders.h" namespace { @@ -38,14 +44,14 @@ constexpr int kArchivedLimitPerPage = 30; } // namespace -int32 stickerPacksCount(bool includeDisabledOfficial) { - int32 result = 0; +int stickerPacksCount(bool includeArchivedOfficial) { + auto result = 0; auto &order = Global::StickerSetsOrder(); auto &sets = Global::StickerSets(); - for (int i = 0, l = order.size(); i < l; ++i) { + for (auto i = 0, l = order.size(); i < l; ++i) { auto it = sets.constFind(order.at(i)); if (it != sets.cend()) { - if (!(it->flags & MTPDstickerSet::Flag::f_archived) || ((it->flags & MTPDstickerSet::Flag::f_official) && includeDisabledOfficial)) { + if (!(it->flags & MTPDstickerSet::Flag::f_archived) || ((it->flags & MTPDstickerSet::Flag::f_official) && includeArchivedOfficial)) { ++result; } } @@ -53,24 +59,101 @@ int32 stickerPacksCount(bool includeDisabledOfficial) { return result; } -StickersBox::StickersBox(Section section) : ItemListBox(st::boxScroll) -, _section(section) -, _inner(this, section) -, _aboutWidth(st::boxWideWidth - 2 * st::stickersReorderPadding.top()) -, _about(st::boxTextFont, lang((section == Section::Archived) ? lng_stickers_packs_archived : lng_stickers_reorder), _defaultOptions, _aboutWidth) { - setup(); +class StickersBox::CounterWidget : public TWidget, private base::Subscriber { +public: + CounterWidget(QWidget *parent); + + void setCounter(int counter); + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + void updateCounter(); + + QString _text; + Dialogs::Layout::UnreadBadgeStyle _st; + +}; + +StickersBox::CounterWidget::CounterWidget(QWidget *parent) : TWidget(parent) { + setAttribute(Qt::WA_TransparentForMouseEvents); + + _st.sizeId = Dialogs::Layout::UnreadBadgeInStickersBox; + _st.textTop = st::stickersFeaturedBadgeTextTop; + _st.size = st::stickersFeaturedBadgeSize; + _st.padding = st::stickersFeaturedBadgePadding; + _st.font = st::stickersFeaturedBadgeFont; + + subscribe(Global::RefFeaturedStickerSetsUnreadCountChanged(), [this] { updateCounter(); }); + updateCounter(); } -StickersBox::StickersBox(const Stickers::Order &archivedIds) : ItemListBox(st::boxScroll) -, _section(Section::ArchivedPart) -, _inner(this, archivedIds) +void StickersBox::CounterWidget::setCounter(int counter) { + _text = (counter > 0) ? QString::number(counter) : QString(); + auto dummy = QImage(1, 1, QImage::Format_ARGB32_Premultiplied); + Painter p(&dummy); + + auto newWidth = 0; + Dialogs::Layout::paintUnreadCount(p, _text, 0, 0, _st, &newWidth); + + resize(newWidth, st::stickersFeaturedBadgeSize); +} + +void StickersBox::CounterWidget::paintEvent(QPaintEvent *e) { + Painter p(this); + + if (!_text.isEmpty()) { + auto unreadRight = rtl() ? 0 : width(); + auto unreadTop = 0; + Dialogs::Layout::paintUnreadCount(p, _text, unreadRight, unreadTop, _st); + } +} + +void StickersBox::CounterWidget::updateCounter() { + setCounter(Global::FeaturedStickerSetsUnreadCount()); + update(); +} + +template +StickersBox::Tab::Tab(int index, Args&&... args) +: _index(index) +, _widget(std_::forward(args)...) +, _weak(_widget) { +} + +object_ptr StickersBox::Tab::takeWidget() { + return std_::move(_widget); +} + +void StickersBox::Tab::returnWidget(object_ptr widget) { + _widget = std_::move(widget); + t_assert(_widget == _weak); +} + +void StickersBox::Tab::saveScrollTop() { + _scrollTop = widget()->getVisibleTop(); +} + +StickersBox::StickersBox(QWidget*, Section section) +: _tabs(this, st::stickersTabs) +, _unreadBadge(this) +, _section(section) +, _installed(0, this, Section::Installed) +, _featured(1, this, Section::Featured) +, _archived(2, this, Section::Archived) { +} + +StickersBox::StickersBox(QWidget*, const Stickers::Order &archivedIds) +: _section(Section::ArchivedPart) +, _archived(0, this, archivedIds) , _aboutWidth(st::boxWideWidth - 2 * st::stickersReorderPadding.top()) -, _about(st::boxTextFont, lang(lng_stickers_packs_archived), _defaultOptions, _aboutWidth) { - setup(); +, _about(st::boxLabelStyle, lang(lng_stickers_packs_archived), _defaultOptions, _aboutWidth) { } void StickersBox::getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedStickers &result) { _archivedRequestId = 0; + _archivedLoaded = true; if (result.type() != mtpc_messages_archivedStickers) { return; } @@ -86,7 +169,8 @@ void StickersBox::getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedSti archived.clear(); } - bool addedSet = false; + auto addedSet = false; + auto changedSets = false; auto &v = stickers.vsets.c_vector().v; for_const (auto &stickerSet, v) { const MTPDstickerSet *setData = nullptr; @@ -109,472 +193,408 @@ void StickersBox::getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedSti if (auto set = Stickers::feedSet(*setData)) { auto index = archived.indexOf(set->id); if (archived.isEmpty() || index != archived.size() - 1) { + changedSets = true; if (index < archived.size() - 1) { archived.removeAt(index); } archived.push_back(set->id); } - if (_section == Section::Archived) { - if (_inner->appendSet(*set)) { - addedSet = true; - if (set->stickers.isEmpty() || (set->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { - App::api()->scheduleStickerSetRequest(set->id, set->access); - } + if (_archived.widget()->appendSet(*set)) { + addedSet = true; + if (set->stickers.isEmpty() || (set->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { + App::api()->scheduleStickerSetRequest(set->id, set->access); } } } } - if (_section == Section::Installed && !archived.isEmpty()) { - Local::writeArchivedStickers(); - rebuildList(); - } else if (_section == Section::Archived) { - if (addedSet) { - _inner->updateSize(); - setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight))); - _inner->setVisibleScrollbar((scrollArea()->scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); - App::api()->requestStickerSets(); - } else { - _allArchivedLoaded = v.isEmpty() || (offsetId != 0); + if (addedSet) { + _archived.widget()->updateSize(); + } else { + _allArchivedLoaded = v.isEmpty() || (!changedSets && offsetId != 0); + if (changedSets) { + loadMoreArchived(); } } - checkLoadMoreArchived(); -} -void StickersBox::setup() { - if (_section == Section::Installed) { - Local::readArchivedStickers(); - if (Global::ArchivedStickerSetsOrder().isEmpty()) { - MTPmessages_GetArchivedStickers::Flags flags = 0; - _archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_flags(flags), MTP_long(0), MTP_int(kArchivedLimitFirstRequest)), rpcDone(&StickersBox::getArchivedDone, 0ULL)); - } - } else if (_section == Section::Archived) { - // Reload the archived list. - MTPmessages_GetArchivedStickers::Flags flags = 0; - _archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_flags(flags), MTP_long(0), MTP_int(kArchivedLimitFirstRequest)), rpcDone(&StickersBox::getArchivedDone, 0ULL)); - - auto &sets = Global::StickerSets(); - for_const (auto setId, Global::ArchivedStickerSetsOrder()) { - auto it = sets.constFind(setId); - if (it != sets.cend()) { - if (it->stickers.isEmpty() && (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { - App::api()->scheduleStickerSetRequest(setId, it->access); - } - } - } + refreshTabs(); + _someArchivedLoaded = true; + if (_section == Section::Archived && addedSet) { App::api()->requestStickerSets(); } +} - int bottomSkip = st::boxPadding.bottom(); +void StickersBox::prepare() { if (_section == Section::Installed) { - _aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom(); - _topShadow.create(this, st::contactsAboutShadow); + Local::readArchivedStickers(); + } else if (_section == Section::Archived) { + requestArchivedSets(); + } else if (_section == Section::ArchivedPart) { + setTitle(lang(lng_stickers_archived)); + } + if (Global::ArchivedStickerSetsOrder().isEmpty()) { + preloadArchivedSets(); + } + if (_tabs) { + setNoContentMargin(true); + _tabs->setSectionActivatedCallback([this] { + switchTab(); + }); + refreshTabs(); + } + if (_installed.widget() && _section != Section::Installed) _installed.widget()->hide(); + if (_featured.widget() && _section != Section::Featured) _featured.widget()->hide(); + if (_section != Section::Archived && _section != Section::ArchivedPart) _archived.widget()->hide(); - _save.create(this, lang(lng_settings_save), st::defaultBoxButton); - connect(_save, SIGNAL(clicked()), this, SLOT(onSave())); + if (_featured.widget()) { + _featured.widget()->setInstallSetCallback([this](uint64 setId) { installSet(setId); }); + } + _archived.widget()->setInstallSetCallback([this](uint64 setId) { installSet(setId); }); + _archived.widget()->setLoadMoreCallback([this] { loadMoreArchived(); }); - _cancel.create(this, lang(lng_cancel), st::cancelBoxButton); - connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose())); + addButton(lang(lng_about_done), [this] { closeBox(); }); - _bottomShadow.create(this); - bottomSkip = st::boxButtonPadding.top() + _save->height() + st::boxButtonPadding.bottom(); + if (_section == Section::Installed) { + _tab = &_installed; } else if (_section == Section::ArchivedPart) { _aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom(); - _topShadow.create(this, st::contactsAboutShadow); - - _save.create(this, lang(lng_box_ok), st::defaultBoxButton); - connect(_save, SIGNAL(clicked()), this, SLOT(onClose())); + _titleShadow.create(this); + _tab = &_archived; } else if (_section == Section::Archived) { - _aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom(); - _topShadow.create(this, st::contactsAboutShadow); + _tab = &_archived; + } else { // _section == Section::Featured + _tab = &_featured; } - ItemListBox::init(_inner, bottomSkip, st::boxTitleHeight + _aboutHeight); - setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight))); + setInnerWidget(_tab->takeWidget(), getTopSkip()); + setDimensions(st::boxWideWidth, (_section == Section::ArchivedPart) ? st::sessionsHeight : st::boxMaxListHeight); connect(App::main(), SIGNAL(stickersUpdated()), this, SLOT(onStickersUpdated())); App::main()->updateStickers(); - connect(_inner, SIGNAL(checkDraggingScroll(int)), this, SLOT(onCheckDraggingScroll(int))); - connect(_inner, SIGNAL(noDraggingScroll()), this, SLOT(onNoDraggingScroll())); - connect(&_scrollTimer, SIGNAL(timeout()), this, SLOT(onScrollTimer())); - connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); - _scrollTimer.setSingleShot(false); + if (_installed.widget()) { + connect(_installed.widget(), SIGNAL(draggingScrollDelta(int)), this, SLOT(onDraggingScrollDelta(int))); + } + if (_tabs) { + _tabs->raise(); + _unreadBadge->raise(); + } rebuildList(); - - prepare(); } -void StickersBox::onScroll() { - updateVisibleTopBottom(); - checkLoadMoreArchived(); +void StickersBox::refreshTabs() { + if (!_tabs) return; + + _tabIndices.clear(); + auto sections = QStringList(); + sections.push_back(lang(lng_stickers_installed_tab).toUpper()); + _tabIndices.push_back(Section::Installed); + if (!Global::FeaturedStickerSetsOrder().isEmpty()) { + sections.push_back(lang(lng_stickers_featured_tab).toUpper()); + _tabIndices.push_back(Section::Featured); + } + if (!Global::ArchivedStickerSetsOrder().isEmpty()) { + sections.push_back(lang(lng_stickers_archived_tab).toUpper()); + _tabIndices.push_back(Section::Archived); + } + _tabs->setSections(sections); + if ((_tab == &_archived && !_tabIndices.contains(Section::Archived)) + || (_tab == &_featured && !_tabIndices.contains(Section::Featured))) { + switchTab(); + } + updateTabsGeometry(); } -void StickersBox::updateVisibleTopBottom() { - auto visibleTop = scrollArea()->scrollTop(); - auto visibleBottom = visibleTop + scrollArea()->height(); - _inner->setVisibleTopBottom(visibleTop, visibleBottom); -} +void StickersBox::loadMoreArchived() { + if (_section != Section::Archived || _allArchivedLoaded || _archivedRequestId) { + return; + } -void StickersBox::checkLoadMoreArchived() { - if (_section != Section::Archived) return; - - int scrollTop = scrollArea()->scrollTop(), scrollTopMax = scrollArea()->scrollTopMax(); - if (scrollTop + PreloadHeightsCount * scrollArea()->height() >= scrollTopMax) { - if (!_archivedRequestId && !_allArchivedLoaded) { - uint64 lastId = 0; - for (auto setId = Global::ArchivedStickerSetsOrder().cend(), e = Global::ArchivedStickerSetsOrder().cbegin(); setId != e;) { - --setId; - auto it = Global::StickerSets().constFind(*setId); - if (it != Global::StickerSets().cend()) { - if (it->flags & MTPDstickerSet::Flag::f_archived) { - lastId = it->id; - break; - } - } + uint64 lastId = 0; + for (auto setIt = Global::ArchivedStickerSetsOrder().cend(), e = Global::ArchivedStickerSetsOrder().cbegin(); setIt != e;) { + --setIt; + auto it = Global::StickerSets().constFind(*setIt); + if (it != Global::StickerSets().cend()) { + if (it->flags & MTPDstickerSet::Flag::f_archived) { + lastId = it->id; + break; } - MTPmessages_GetArchivedStickers::Flags flags = 0; - _archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_flags(flags), MTP_long(lastId), MTP_int(kArchivedLimitPerPage)), rpcDone(&StickersBox::getArchivedDone, lastId)); } } -} - -int32 StickersBox::countHeight() const { - int bottomSkip = st::boxPadding.bottom(); - if (_section == Section::Installed) { - bottomSkip = st::boxButtonPadding.top() + _save->height() + st::boxButtonPadding.bottom(); - } - return st::boxTitleHeight + _aboutHeight + _inner->height() + bottomSkip; -} - -void StickersBox::disenableDone(const MTPmessages_StickerSetInstallResult &result, mtpRequestId req) { - _disenableRequests.remove(req); - if (_disenableRequests.isEmpty()) { - saveOrder(); - } -} - -bool StickersBox::disenableFail(const RPCError &error, mtpRequestId req) { - if (MTP::isDefaultHandledError(error)) return false; - _disenableRequests.remove(req); - if (_disenableRequests.isEmpty()) { - saveOrder(); - } - return true; -} - -void StickersBox::saveOrder() { - auto order = _inner->getOrder(); - if (order.size() > 1) { - QVector mtpOrder; - mtpOrder.reserve(order.size()); - for (int i = 0, l = order.size(); i < l; ++i) { - mtpOrder.push_back(MTP_long(order.at(i))); - } - - MTPmessages_ReorderStickerSets::Flags flags = 0; - _reorderRequest = MTP::send(MTPmessages_ReorderStickerSets(MTP_flags(flags), MTP_vector(mtpOrder)), rpcDone(&StickersBox::reorderDone), rpcFail(&StickersBox::reorderFail)); - } else { - reorderDone(MTP_boolTrue()); - } -} - -void StickersBox::reorderDone(const MTPBool &result) { - _reorderRequest = 0; - onClose(); -} - -bool StickersBox::reorderFail(const RPCError &result) { - if (MTP::isDefaultHandledError(result)) return false; - _reorderRequest = 0; - Global::SetLastStickersUpdate(0); - App::main()->updateStickers(); - onClose(); - return true; + MTPmessages_GetArchivedStickers::Flags flags = 0; + _archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_flags(flags), MTP_long(lastId), MTP_int(kArchivedLimitPerPage)), rpcDone(&StickersBox::getArchivedDone, lastId)); } void StickersBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; + BoxContent::paintEvent(e); - auto title = ([this]() { - if (_section == Section::Installed) { - return lang(lng_stickers_packs); - } else if (_section == Section::Featured) { - return lang(lng_stickers_featured); - } - return lang(lng_stickers_archived); - })(); - paintTitle(p, title); - p.translate(0, st::boxTitleHeight); + Painter p(this); if (_aboutHeight > 0) { - p.fillRect(0, 0, width(), _aboutHeight, st::contactsAboutBg); + p.fillRect(0, st::lineWidth, width(), _aboutHeight - st::lineWidth, st::contactsAboutBg); p.setPen(st::stickersReorderFg); _about.draw(p, st::stickersReorderPadding.top(), st::stickersReorderPadding.top(), _aboutWidth, style::al_center); } -} -void StickersBox::closePressed() { - if (!_disenableRequests.isEmpty()) { - for_const (auto requestId, _disenableRequests) { - MTP::cancel(requestId); + if (_slideAnimation) { + _slideAnimation->paintFrame(p, 0, getTopSkip(), width(), getms()); + if (!_slideAnimation->animating()) { + _slideAnimation.reset(); + setInnerVisible(true); + update(); } - _disenableRequests.clear(); - Global::SetLastStickersUpdate(0); - App::main()->updateStickers(); - } else if (_reorderRequest) { - MTP::cancel(_reorderRequest); - _reorderRequest = 0; - Global::SetLastStickersUpdate(0); - App::main()->updateStickers(); } } -StickersBox::~StickersBox() { - if (_section == Section::Archived) { - Local::writeArchivedStickers(); +void StickersBox::updateTabsGeometry() { + if (!_tabs) return; + + _tabs->resizeToWidth(_tabIndices.size() * width() / 3); + _unreadBadge->setVisible(_tabIndices.contains(Section::Featured)); + + setInnerTopSkip(getTopSkip()); + + auto featuredLeft = width() / 3; + auto featuredRight = 2 * width() / 3; + auto featuredTextWidth = st::stickersTabs.labelFont->width(lang(lng_stickers_featured_tab).toUpper()); + auto featuredTextRight = featuredLeft + (featuredRight - featuredLeft - featuredTextWidth) / 2 + featuredTextWidth; + auto unreadBadgeLeft = featuredTextRight - st::stickersFeaturedBadgeSkip; + auto unreadBadgeTop = st::stickersFeaturedBadgeTop; + if (unreadBadgeLeft + _unreadBadge->width() > featuredRight) { + unreadBadgeLeft = featuredRight - _unreadBadge->width(); } + _unreadBadge->moveToLeft(unreadBadgeLeft, unreadBadgeTop); + + _tabs->moveToLeft(0, 0); +} + +int StickersBox::getTopSkip() const { + return (_tabs ? (_tabs->height() - st::lineWidth) : 0) + _aboutHeight; +} + +void StickersBox::switchTab() { + if (!_tabs) return; + + auto tab = _tabs->activeSection(); + t_assert(tab >= 0 && tab < _tabIndices.size()); + auto newSection = _tabIndices[tab]; + + auto newTab = _tab; + if (newSection == Section::Installed) { + newTab = &_installed; + } else if (newSection == Section::Featured) { + newTab = &_featured; + } else if (newSection == Section::Archived) { + newTab = &_archived; + requestArchivedSets(); + } + if (_tab != newTab) { + if (_tab == &_installed) { + _localOrder = _tab->widget()->getFullOrder(); + _localRemoved = _tab->widget()->getRemovedSets(); + } + auto wasCache = grabContentCache(); + auto wasIndex = _tab->index(); + _tab->saveScrollTop(); + auto widget = takeInnerWidget(); + widget->setParent(this); + widget->hide(); + _tab->returnWidget(std_::move(widget)); + _tab = newTab; + _section = newSection; + setInnerWidget(_tab->takeWidget(), getTopSkip()); + _tabs->raise(); + _unreadBadge->raise(); + _tab->widget()->show(); + rebuildList(); + onScrollToY(_tab->getScrollTop()); + auto nowCache = grabContentCache(); + auto nowIndex = _tab->index(); + + _slideAnimation = std_::make_unique(); + _slideAnimation->setSnapshots(std_::move(wasCache), std_::move(nowCache)); + auto slideLeft = wasIndex > nowIndex; + _slideAnimation->start(slideLeft, [this] { update(); }, st::slideDuration); + setInnerVisible(false); + + setFocus(); + update(); + } +} + +QPixmap StickersBox::grabContentCache() { + _tabs->hide(); + auto result = grabInnerCache(); + _tabs->show(); + return std_::move(result); +} + +void StickersBox::installSet(uint64 setId) { + auto &sets = Global::RefStickerSets(); + auto it = sets.find(setId); + if (it == sets.cend()) { + rebuildList(); + return; + } + + if (_localRemoved.contains(setId)) { + _localRemoved.removeOne(setId); + if (_installed.widget()) _installed.widget()->setRemovedSets(_localRemoved); + if (_featured.widget()) _featured.widget()->setRemovedSets(_localRemoved); + _archived.widget()->setRemovedSets(_localRemoved); + } + if (!(it->flags & MTPDstickerSet::Flag::f_installed) || (it->flags & MTPDstickerSet::Flag::f_archived)) { + MTP::send(MTPmessages_InstallStickerSet(Stickers::inputSetId(*it), MTP_boolFalse()), rpcDone(&StickersBox::installDone), rpcFail(&StickersBox::installFail, setId)); + + Stickers::installLocally(setId); + } +} + +void StickersBox::installDone(const MTPmessages_StickerSetInstallResult &result) { + if (result.type() == mtpc_messages_stickerSetInstallResultArchive) { + Stickers::applyArchivedResult(result.c_messages_stickerSetInstallResultArchive()); + } +} + +bool StickersBox::installFail(uint64 setId, const RPCError &error) { + if (MTP::isDefaultHandledError(error)) return false; + + auto &sets = Global::RefStickerSets(); + auto it = sets.find(setId); + if (it == sets.cend()) { + rebuildList(); + return true; + } + + Stickers::undoInstallLocally(setId); + return true; +} + +void StickersBox::preloadArchivedSets() { + if (!_archivedRequestId) { + MTPmessages_GetArchivedStickers::Flags flags = 0; + _archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_flags(flags), MTP_long(0), MTP_int(kArchivedLimitFirstRequest)), rpcDone(&StickersBox::getArchivedDone, 0ULL)); + } +} + +void StickersBox::requestArchivedSets() { + // Reload the archived list. + if (!_archivedLoaded) { + preloadArchivedSets(); + } + + auto &sets = Global::StickerSets(); + for_const (auto setId, Global::ArchivedStickerSetsOrder()) { + auto it = sets.constFind(setId); + if (it != sets.cend()) { + if (it->stickers.isEmpty() && (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { + App::api()->scheduleStickerSetRequest(setId, it->access); + } + } + } + App::api()->requestStickerSets(); } void StickersBox::resizeEvent(QResizeEvent *e) { - ItemListBox::resizeEvent(e); - _inner->resize(width(), _inner->height()); - _inner->setVisibleScrollbar((scrollArea()->scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); - updateVisibleTopBottom(); - if (_topShadow) { - _topShadow->setGeometry(0, st::boxTitleHeight + _aboutHeight, width(), st::lineWidth); + BoxContent::resizeEvent(e); + + if (_tabs) { + updateTabsGeometry(); } - if (_save) { - _save->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _save->height()); - } - if (_cancel) { - _cancel->moveToRight(st::boxButtonPadding.right() + _save->width() + st::boxButtonPadding.left(), _save->y()); - _bottomShadow->setGeometry(0, height() - st::boxButtonPadding.bottom() - _save->height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth); + if (_titleShadow) { + _titleShadow->setGeometry(0, 0, width(), st::lineWidth); } + if (_installed.widget()) _installed.widget()->resize(width(), _installed.widget()->height()); + if (_featured.widget()) _featured.widget()->resize(width(), _featured.widget()->height()); + _archived.widget()->resize(width(), _archived.widget()->height()); } void StickersBox::onStickersUpdated() { if (_section == Section::Installed || _section == Section::Featured) { rebuildList(); } else { - _inner->updateRows(); + _tab->widget()->updateRows(); } -} - -void StickersBox::rebuildList() { - _inner->rebuild(); - setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight))); - _inner->setVisibleScrollbar((scrollArea()->scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); -} - -void StickersBox::onCheckDraggingScroll(int localY) { - if (localY < scrollArea()->scrollTop()) { - _scrollDelta = localY - scrollArea()->scrollTop(); - } else if (localY >= scrollArea()->scrollTop() + scrollArea()->height()) { - _scrollDelta = localY - scrollArea()->scrollTop() - scrollArea()->height() + 1; + if (Global::ArchivedStickerSetsOrder().isEmpty()) { + preloadArchivedSets(); } else { - _scrollDelta = 0; - } - if (_scrollDelta) { - _scrollTimer.start(15); - } else { - _scrollTimer.stop(); + refreshTabs(); } } -void StickersBox::onNoDraggingScroll() { - _scrollTimer.stop(); +void StickersBox::rebuildList(Tab *tab) { + if (!tab) tab = _tab; + + if (tab == &_installed) { + _localOrder = tab->widget()->getFullOrder(); + _localRemoved = tab->widget()->getRemovedSets(); + } + tab->widget()->rebuild(); + if (tab == &_installed) { + tab->widget()->setFullOrder(_localOrder); + } + tab->widget()->setRemovedSets(_localRemoved); } -void StickersBox::onScrollTimer() { - int32 d = (_scrollDelta > 0) ? qMin(_scrollDelta * 3 / 20 + 1, int32(MaxScrollSpeed)) : qMax(_scrollDelta * 3 / 20 - 1, -int32(MaxScrollSpeed)); - scrollArea()->scrollToY(scrollArea()->scrollTop() + d); -} - -void StickersBox::onSave() { - if (!_inner->savingStart()) { +void StickersBox::closeHook() { + if (!_installed.widget()) { return; } - bool writeRecent = false, writeArchived = false; - auto &recent = cGetRecentStickers(); - auto &sets = Global::RefStickerSets(); + // Make sure that our changes in other tabs are applied in the Installed tab. + rebuildList(&_installed); - auto reorder = _inner->getOrder(), disabled = _inner->getDisabledSets(); - for (int32 i = 0, l = disabled.size(); i < l; ++i) { - auto it = sets.find(disabled.at(i)); - if (it != sets.cend()) { - for (RecentStickerPack::iterator i = recent.begin(); i != recent.cend();) { - if (it->stickers.indexOf(i->first) >= 0) { - i = recent.erase(i); - writeRecent = true; - } else { - ++i; - } - } - if (!(it->flags & MTPDstickerSet::Flag::f_archived)) { - MTPInputStickerSet setId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName)); - if (it->flags & MTPDstickerSet::Flag::f_official) { - _disenableRequests.insert(MTP::send(MTPmessages_InstallStickerSet(setId, MTP_boolTrue()), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5)); - it->flags |= MTPDstickerSet::Flag::f_archived; - auto index = Global::RefArchivedStickerSetsOrder().indexOf(it->id); - if (index < 0) { - Global::RefArchivedStickerSetsOrder().push_front(it->id); - writeArchived = true; - } - } else { - _disenableRequests.insert(MTP::send(MTPmessages_UninstallStickerSet(setId), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5)); - int removeIndex = Global::StickerSetsOrder().indexOf(it->id); - if (removeIndex >= 0) Global::RefStickerSetsOrder().removeAt(removeIndex); - if (!(it->flags & MTPDstickerSet_ClientFlag::f_featured) && !(it->flags & MTPDstickerSet_ClientFlag::f_special)) { - sets.erase(it); - } else { - if (it->flags & MTPDstickerSet::Flag::f_archived) { - writeArchived = true; - } - it->flags &= ~(MTPDstickerSet::Flag::f_installed | MTPDstickerSet::Flag::f_archived); - } - } - } - } + if (_someArchivedLoaded) { + Local::writeArchivedStickers(); } - - // Clear all installed flags, set only for sets from order. - for (auto &set : sets) { - if (!(set.flags & MTPDstickerSet::Flag::f_archived)) { - set.flags &= ~MTPDstickerSet::Flag::f_installed; - } - } - - auto &order(Global::RefStickerSetsOrder()); - order.clear(); - for (int i = 0, l = reorder.size(); i < l; ++i) { - auto it = sets.find(reorder.at(i)); - if (it != sets.cend()) { - if ((it->flags & MTPDstickerSet::Flag::f_archived) && !disabled.contains(it->id)) { - MTPInputStickerSet setId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName)); - _disenableRequests.insert(MTP::send(MTPmessages_InstallStickerSet(setId, MTP_boolFalse()), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5)); - it->flags &= ~MTPDstickerSet::Flag::f_archived; - writeArchived = true; - } - order.push_back(reorder.at(i)); - it->flags |= MTPDstickerSet::Flag::f_installed; - } - } - for (auto it = sets.begin(); it != sets.cend();) { - if ((it->flags & MTPDstickerSet_ClientFlag::f_featured) - || (it->flags & MTPDstickerSet::Flag::f_installed) - || (it->flags & MTPDstickerSet::Flag::f_archived) - || (it->flags & MTPDstickerSet_ClientFlag::f_special)) { - ++it; - } else { - it = sets.erase(it); - } - } - - Local::writeInstalledStickers(); - if (writeRecent) Local::writeUserSettings(); - if (writeArchived) Local::writeArchivedStickers(); - emit App::main()->stickersUpdated(); - - if (_disenableRequests.isEmpty()) { - saveOrder(); - } else { - MTP::sendAnything(); + if (auto api = App::api()) { + api->saveStickerSets(_installed.widget()->getOrder(), _installed.widget()->getRemovedSets()); } } -void StickersBox::showAll() { - if (_topShadow) { - _topShadow->show(); - } - if (_save) { - _save->show(); - } - if (_cancel) { - _cancel->show(); - _bottomShadow->show(); - } - ItemListBox::showAll(); -} +StickersBox::~StickersBox() = default; -StickersBox::Inner::Inner(QWidget *parent, StickersBox::Section section) : ScrolledWidget(parent) +StickersBox::Inner::Inner(QWidget *parent, StickersBox::Section section) : TWidget(parent) , _section(section) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _a_shifting(animation(this, &Inner::step_shifting)) -, _itemsTop(st::membersPadding.top()) -, _clearWidth(st::normalFont->width(lang(lng_stickers_clear_recent))) -, _removeWidth(st::normalFont->width(lang(lng_stickers_remove))) -, _returnWidth(st::normalFont->width(lang(lng_stickers_return))) -, _restoreWidth(st::normalFont->width(lang(lng_stickers_restore))) -, _aboveShadow(st::boxShadow) { +, _itemsTop(st::membersMarginTop) +, _addText(lang(lng_stickers_featured_add).toUpper()) +, _addWidth(st::stickersTrendingAdd.font->width(_addText)) +, _undoText(lang(lng_stickers_return).toUpper()) +, _undoWidth(st::stickersUndoRemove.font->width(_undoText)) { setup(); } -StickersBox::Inner::Inner(QWidget *parent, const Stickers::Order &archivedIds) : ScrolledWidget(parent) +StickersBox::Inner::Inner(QWidget *parent, const Stickers::Order &archivedIds) : TWidget(parent) , _section(StickersBox::Section::ArchivedPart) , _archivedIds(archivedIds) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _a_shifting(animation(this, &Inner::step_shifting)) -, _itemsTop(st::membersPadding.top()) -, _clearWidth(st::normalFont->width(lang(lng_stickers_clear_recent))) -, _removeWidth(st::normalFont->width(lang(lng_stickers_remove))) -, _returnWidth(st::normalFont->width(lang(lng_stickers_return))) -, _restoreWidth(st::normalFont->width(lang(lng_stickers_restore))) -, _aboveShadow(st::boxShadow) { +, _itemsTop(st::membersMarginTop) +, _addText(lang(lng_stickers_featured_add).toUpper()) +, _addWidth(st::stickersTrendingAdd.font->width(_addText)) { setup(); } void StickersBox::Inner::setup() { - subscribe(FileDownload::ImageLoaded(), [this] { update(); }); + subscribe(FileDownload::ImageLoaded(), [this] { + update(); + readVisibleSets(); + }); setMouseTracking(true); } -void StickersBox::Inner::onImageLoaded() { - update(); - readVisibleSets(); -} - -void StickersBox::Inner::paintButton(Painter &p, int y, bool selected, const QString &text, int badgeCounter) const { - if (selected) { - p.fillRect(0, y, width(), _buttonHeight, st::contactsBgOver); - } - p.setFont(st::stickersFeaturedFont); - p.setPen(st::stickersFeaturedPen); - p.drawTextLeft(st::stickersFeaturedPosition.x(), y + st::stickersFeaturedPosition.y(), width(), text); - - if (badgeCounter) { - Dialogs::Layout::UnreadBadgeStyle unreadSt; - unreadSt.sizeId = Dialogs::Layout::UnreadBadgeInStickersBox; - unreadSt.size = st::stickersFeaturedBadgeSize; - int unreadRight = width() - (st::contactsPadding.right() + st::contactsCheckPosition.x()); - if (rtl()) unreadRight = width() - unreadRight; - int unreadTop = y + (_buttonHeight - st::stickersFeaturedBadgeSize) / 2; - Dialogs::Layout::paintUnreadCount(p, QString::number(badgeCounter), unreadRight, unreadTop, unreadSt); - } -} - void StickersBox::Inner::paintEvent(QPaintEvent *e) { QRect r(e->rect()); Painter p(this); _a_shifting.step(); - p.fillRect(r, st::white); + auto ms = getms(); + p.fillRect(r, st::boxBg); p.setClipRect(r); - int y = st::membersPadding.top(); - if (_hasFeaturedButton) { - auto selected = (_selected == -2); - paintButton(p, y, selected, lang(lng_stickers_featured), Global::FeaturedStickerSetsUnreadCount()); - y += _buttonHeight; - } - if (_hasArchivedButton) { - auto selected = (_selected == -1); - paintButton(p, y, selected, lang(lng_stickers_archived), 0); - y += _buttonHeight; - } - + auto y = _itemsTop; if (_rows.isEmpty()) { p.setFont(st::noContactsFont); p.setPen(st::noContactsColor); @@ -588,96 +608,103 @@ void StickersBox::Inner::paintEvent(QPaintEvent *e) { p.translate(0, from * _rowHeight); for (int32 i = from; i < to; ++i) { if (i != _above) { - paintRow(p, i); + paintRow(p, i, ms); } p.translate(0, _rowHeight); } if (from <= _above && _above < to) { p.translate(0, (_above - to) * _rowHeight); - paintRow(p, _above); + paintRow(p, _above, ms); } } } -void StickersBox::Inner::paintRow(Painter &p, int32 index) { - const StickerSetRow *s(_rows.at(index)); +QRect StickersBox::Inner::relativeButtonRect(bool removeButton) const { + auto buttonw = st::stickersRemove.width; + auto buttonh = st::stickersRemove.height; + auto buttonshift = st::stickersRemoveSkip; + if (!removeButton) { + auto &st = (_section == Section::Installed) ? st::stickersUndoRemove : st::stickersTrendingAdd; + auto textWidth = (_section == Section::Installed) ? _undoWidth : _addWidth; + buttonw = textWidth - st.width; + buttonh = st.height; + buttonshift = 0; + } + auto buttonx = width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - buttonw + buttonshift; + auto buttony = st::contactsPadding.top() + (st::contactsPhotoSize - buttonh) / 2; + return QRect(buttonx, buttony, buttonw, buttonh); +} - int32 xadd = 0, yadd = s->yadd.current(); +void StickersBox::Inner::paintRow(Painter &p, int index, TimeMs ms) { + auto s = _rows.at(index); + + auto xadd = 0, yadd = qRound(s->yadd.current()); if (xadd || yadd) p.translate(xadd, yadd); if (_section == Section::Installed) { - bool removeSel = (index == _actionSel && (_actionDown < 0 || index == _actionDown)); - bool removeDown = removeSel && (index == _actionDown); - - p.setFont(removeSel ? st::linkOverFont : st::linkFont); - if (removeDown) { - p.setPen(st::btnDefLink.downColor); - } else { - p.setPen(st::btnDefLink.color); - } - int32 remWidth = s->recent ? _clearWidth : (s->disabled ? (s->official ? _restoreWidth : _returnWidth) : _removeWidth); - QString remText = lang(s->recent ? lng_stickers_clear_recent : (s->disabled ? (s->official ? lng_stickers_restore : lng_stickers_return) : lng_stickers_remove)); - p.drawTextRight(st::contactsPadding.right() + st::contactsCheckPosition.x(), st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, width(), remText, remWidth); - if (index == _above) { - float64 current = _aboveShadowFadeOpacity.current(); + auto current = _aboveShadowFadeOpacity.current(); if (_started >= 0) { - float64 o = aboveShadowOpacity(); - if (o > current) { - _aboveShadowFadeOpacity = anim::fvalue(o, o); - current = o; + auto reachedOpacity = aboveShadowOpacity(); + if (reachedOpacity > current) { + _aboveShadowFadeOpacity = anim::value(reachedOpacity, reachedOpacity); + current = reachedOpacity; } } + auto row = myrtlrect(st::contactsPadding.left() / 2, st::contactsPadding.top() / 2, width() - (st::contactsPadding.left() / 2) - _scrollbar - st::contactsPadding.left() / 2, _rowHeight - ((st::contactsPadding.top() + st::contactsPadding.bottom()) / 2)); p.setOpacity(current); - QRect row(myrtlrect(_aboveShadow.getDimensions(st::boxShadowShift).left(), st::contactsPadding.top() / 2, width() - (st::contactsPadding.left() / 2) - _scrollbar - _aboveShadow.getDimensions(st::boxShadowShift).right(), _rowHeight - ((st::contactsPadding.top() + st::contactsPadding.bottom()) / 2))); - _aboveShadow.paint(p, row, st::boxShadowShift); - p.fillRect(row, st::white); + Ui::Shadow::paint(p, row, width(), st::boxRoundShadow); p.setOpacity(1); - } - } else if (s->installed && !s->disabled) { - int addw = st::stickersAddSize.width(); - int checkx = width() - (st::contactsPadding.right() + st::contactsCheckPosition.x() + (addw + st::stickersFeaturedInstalled.width()) / 2); - int checky = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersFeaturedInstalled.height()) / 2; - st::stickersFeaturedInstalled.paint(p, QPoint(checkx, checky), width()); - } else { - int addw = st::stickersAddSize.width(); - int addx = width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - addw; - int addy = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersAddSize.height()) / 2; - QRect add(myrtlrect(addx, addy, addw, st::stickersAddSize.height())); - auto textBg = (_actionSel == index) ? st::defaultActiveButton.textBgOver : st::defaultActiveButton.textBg; - App::roundRect(p, add, textBg, ImageRoundRadius::Small); - int iconx = addx + (st::stickersAddSize.width() - st::stickersAddIcon.width()) / 2; - int icony = addy + (st::stickersAddSize.height() - st::stickersAddIcon.height()) / 2; - icony += (_actionSel == index && _actionDown == index) ? (st::defaultActiveButton.downTextTop - st::defaultActiveButton.textTop) : 0; - st::stickersAddIcon.paint(p, QPoint(iconx, icony), width()); + App::roundRect(p, row, st::boxBg, BoxCorners); + + p.setOpacity(1. - current); + paintFakeButton(p, index, ms); + p.setOpacity(1.); + } else { + paintFakeButton(p, index, ms); + } + } else { + paintFakeButton(p, index, ms); } - if (s->disabled && _section == Section::Installed) { + if (s->removed && _section == Section::Installed) { p.setOpacity(st::stickersRowDisabledOpacity); } + + auto stickerx = st::contactsPadding.left(); + + if (_section == Section::Installed) { + stickerx += st::stickersReorderIcon.width() + st::stickersReorderSkip; + if (!s->isRecentSet()) { + st::stickersReorderIcon.paint(p, st::contactsPadding.left(), (_rowHeight - st::stickersReorderIcon.height()) / 2, width()); + } + } + if (s->sticker) { s->sticker->thumb->load(); QPixmap pix(s->sticker->thumb->pix(s->pixw, s->pixh)); - p.drawPixmapLeft(st::contactsPadding.left() + (st::contactsPhotoSize - s->pixw) / 2, st::contactsPadding.top() + (st::contactsPhotoSize - s->pixh) / 2, width(), pix); + p.drawPixmapLeft(stickerx + (st::contactsPhotoSize - s->pixw) / 2, st::contactsPadding.top() + (st::contactsPhotoSize - s->pixh) / 2, width(), pix); } - int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); + int namex = stickerx + st::contactsPhotoSize + st::contactsPadding.left(); int namey = st::contactsPadding.top() + st::contactsNameTop; + int statusx = namex; int statusy = st::contactsPadding.top() + st::contactsStatusTop; - p.setFont(st::contactsNameFont); - p.setPen(st::black); + p.setFont(st::contactsNameStyle.font); + p.setPen(st::contactsNameFg); p.drawTextLeft(namex, namey, width(), s->title, s->titleWidth); if (s->unread) { p.setPen(Qt::NoPen); p.setBrush(st::stickersFeaturedUnreadBg); - p.setRenderHint(QPainter::HighQualityAntialiasing, true); - p.drawEllipse(rtlrect(namex + s->titleWidth + st::stickersFeaturedUnreadSkip, namey + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width())); - p.setRenderHint(QPainter::HighQualityAntialiasing, false); + { + PainterHighQualityEnabler hq(p); + p.drawEllipse(rtlrect(namex + s->titleWidth + st::stickersFeaturedUnreadSkip, namey + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width())); + } } p.setFont(st::contactsStatusFont); @@ -688,50 +715,138 @@ void StickersBox::Inner::paintRow(Painter &p, int32 index) { if (xadd || yadd) p.translate(-xadd, -yadd); } +void StickersBox::Inner::paintFakeButton(Painter &p, int index, TimeMs ms) { + auto set = _rows[index]; + auto removeButton = (_section == Section::Installed && !set->removed); + auto rect = relativeButtonRect(removeButton); + if (_section != Section::Installed && set->installed && !set->archived && !set->removed) { + // Checkbox after installed from Trending or Archived. + int checkx = width() - (st::contactsPadding.right() + st::contactsCheckPosition.x() + (rect.width() + st::stickersFeaturedInstalled.width()) / 2); + int checky = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersFeaturedInstalled.height()) / 2; + st::stickersFeaturedInstalled.paint(p, QPoint(checkx, checky), width()); + } else { + auto selected = (index == _actionSel && _actionDown < 0) || (index == _actionDown); + if (removeButton) { + // Trash icon button when not disabled in Installed. + if (set->ripple) { + set->ripple->paint(p, rect.x(), rect.y(), width(), ms); + if (set->ripple->empty()) { + set->ripple.reset(); + } + } + auto &icon = selected ? st::stickersRemove.iconOver : st::stickersRemove.icon; + auto position = st::stickersRemove.iconPosition; + if (position.x() < 0) position.setX((rect.width() - icon.width()) / 2); + if (position.y() < 0) position.setY((rect.height() - icon.height()) / 2); + icon.paint(p, rect.topLeft() + position, ms); + } else { + // Round button ADD when not installed from Trending or Archived. + // Or round button UNDO after disabled from Installed. + auto &st = (_section == Section::Installed) ? st::stickersUndoRemove : st::stickersTrendingAdd; + auto textWidth = (_section == Section::Installed) ? _undoWidth : _addWidth; + auto &text = (_section == Section::Installed) ? _undoText : _addText; + auto &textBg = selected ? st.textBgOver : st.textBg; + App::roundRect(p, myrtlrect(rect), textBg, ImageRoundRadius::Small); + if (set->ripple) { + set->ripple->paint(p, rect.x(), rect.y(), width(), ms); + if (set->ripple->empty()) { + set->ripple.reset(); + } + } + p.setFont(st.font); + p.setPen(selected ? st.textFgOver : st.textFg); + p.drawTextLeft(rect.x() - (st.width / 2), rect.y() + st.textTop, width(), text, textWidth); + } + } +} + void StickersBox::Inner::mousePressEvent(QMouseEvent *e) { - if (_saving) return; if (_dragging >= 0) mouseReleaseEvent(e); _mouse = e->globalPos(); onUpdateSelected(); _pressed = _selected; if (_actionSel >= 0) { - _actionDown = _actionSel; + setActionDown(_actionSel); update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight); - } else if (_selected >= 0 && _section == Section::Installed && !_rows.at(_selected)->recent) { + } else if (_selected >= 0 && _section == Section::Installed && !_rows.at(_selected)->isRecentSet() && _inDragArea) { _above = _dragging = _started = _selected; _dragStart = mapFromGlobal(_mouse); } } +void StickersBox::Inner::setActionDown(int newActionDown) { + if (_actionDown == newActionDown) { + return; + } + if (_actionDown >= 0 && _actionDown < _rows.size()) { + update(0, _itemsTop + _actionDown * _rowHeight, width(), _rowHeight); + auto set = _rows[_actionDown]; + if (set->ripple) { + set->ripple->lastStop(); + } + } + _actionDown = newActionDown; + if (_actionDown >= 0 && _actionDown < _rows.size()) { + update(0, _itemsTop + _actionDown * _rowHeight, width(), _rowHeight); + auto set = _rows[_actionDown]; + auto removeButton = (_section == Section::Installed && !set->removed); + if (!set->ripple) { + if (_section == Section::Installed) { + if (set->removed) { + auto rippleSize = QSize(_undoWidth - st::stickersUndoRemove.width, st::stickersUndoRemove.height); + auto rippleMask = Ui::RippleAnimation::roundRectMask(rippleSize, st::buttonRadius); + ensureRipple(st::stickersUndoRemove.ripple, std_::move(rippleMask), removeButton); + } else { + auto rippleSize = st::stickersRemove.rippleAreaSize; + auto rippleMask = Ui::RippleAnimation::ellipseMask(QSize(rippleSize, rippleSize)); + ensureRipple(st::stickersRemove.ripple, std_::move(rippleMask), removeButton); + } + } else if (!set->installed || set->archived || set->removed) { + auto rippleSize = QSize(_addWidth - st::stickersTrendingAdd.width, st::stickersTrendingAdd.height); + auto rippleMask = Ui::RippleAnimation::roundRectMask(rippleSize, st::buttonRadius); + ensureRipple(st::stickersTrendingAdd.ripple, std_::move(rippleMask), removeButton); + } + } + if (set->ripple) { + auto rect = relativeButtonRect(removeButton); + set->ripple->add(mapFromGlobal(QCursor::pos()) - QPoint(myrtlrect(rect).x(), _itemsTop + _actionDown * _rowHeight + rect.y())); + } + } +} + +void StickersBox::Inner::ensureRipple(const style::RippleAnimation &st, QImage mask, bool removeButton) { + _rows[_actionDown]->ripple = MakeShared(st, std_::move(mask), [this, index = _actionDown, removeButton] { + update(myrtlrect(relativeButtonRect(removeButton).translated(0, _itemsTop + index * _rowHeight))); + }); +} + void StickersBox::Inner::mouseMoveEvent(QMouseEvent *e) { - if (_saving) return; _mouse = e->globalPos(); onUpdateSelected(); } void StickersBox::Inner::onUpdateSelected() { - if (_saving) return; - QPoint local(mapFromGlobal(_mouse)); + auto local = mapFromGlobal(_mouse); if (_dragging >= 0) { - int32 shift = 0; - uint64 ms = getms(); + auto shift = 0; + auto ms = getms(); int firstSetIndex = 0; - if (_rows.at(firstSetIndex)->recent) { + if (_rows.at(firstSetIndex)->isRecentSet()) { ++firstSetIndex; } if (_dragStart.y() > local.y() && _dragging > 0) { shift = -floorclamp(_dragStart.y() - local.y() + (_rowHeight / 2), _rowHeight, 0, _dragging - firstSetIndex); for (int32 from = _dragging, to = _dragging + shift; from > to; --from) { qSwap(_rows[from], _rows[from - 1]); - _rows.at(from)->yadd = anim::ivalue(_rows.at(from)->yadd.current() - _rowHeight, 0); + _rows[from]->yadd = anim::value(_rows[from]->yadd.current() - _rowHeight, 0); _animStartTimes[from] = ms; } } else if (_dragStart.y() < local.y() && _dragging + 1 < _rows.size()) { shift = floorclamp(local.y() - _dragStart.y() + (_rowHeight / 2), _rowHeight, 0, _rows.size() - _dragging - 1); for (int32 from = _dragging, to = _dragging + shift; from < to; ++from) { qSwap(_rows[from], _rows[from + 1]); - _rows.at(from)->yadd = anim::ivalue(_rows.at(from)->yadd.current() + _rowHeight, 0); + _rows[from]->yadd = anim::value(_rows[from]->yadd.current() + _rowHeight, 0); _animStartTimes[from] = ms; } } @@ -743,144 +858,117 @@ void StickersBox::Inner::onUpdateSelected() { _a_shifting.start(); } } - _rows.at(_dragging)->yadd = anim::ivalue(local.y() - _dragStart.y(), local.y() - _dragStart.y()); + _rows[_dragging]->yadd = anim::value(local.y() - _dragStart.y(), local.y() - _dragStart.y()); _animStartTimes[_dragging] = 0; _a_shifting.step(getms(), true); - emit checkDraggingScroll(local.y()); - } else { - bool in = rect().marginsRemoved(QMargins(0, _itemsTop, 0, st::membersPadding.bottom())).contains(local); - int selected = -2; - int actionSel = -1; - if (in) { - selected = floorclamp(local.y() - _itemsTop, _rowHeight, 0, _rows.size() - 1); - - if (_section == Section::Installed) { - int remw = _rows.at(selected)->recent ? _clearWidth : (_rows.at(selected)->disabled ? (_rows.at(selected)->official ? _restoreWidth : _returnWidth) : _removeWidth); - QRect rem(myrtlrect(width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - remw, st::contactsPadding.top() + (st::contactsPhotoSize - st::normalFont->height) / 2, remw, st::normalFont->height)); - actionSel = rem.contains(local.x(), local.y() - _itemsTop - selected * _rowHeight) ? selected : -1; - } else if (_rows.at(selected)->installed && !_rows.at(selected)->disabled) { - actionSel = -1; - } else { - int addw = st::stickersAddSize.width(); - int addx = width() - st::contactsPadding.right() - st::contactsCheckPosition.x() - addw; - int addy = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersAddSize.height()) / 2; - QRect add(myrtlrect(addx, addy, addw, st::stickersAddSize.height())); - actionSel = add.contains(local.x(), local.y() - _itemsTop - selected * _rowHeight) ? selected : -1; + auto countDraggingScrollDelta = [this, local] { + if (local.y() < _visibleTop) { + return local.y() - _visibleTop; + } else if (local.y() >= _visibleBottom) { + return local.y() + 1 - _visibleBottom; + } + return 0; + }; + + emit draggingScrollDelta(countDraggingScrollDelta()); + } else { + bool in = rect().marginsRemoved(QMargins(0, _itemsTop, 0, st::membersMarginBottom)).contains(local); + auto selected = -1; + auto actionSel = -1; + auto inDragArea = false; + if (in && !_rows.isEmpty()) { + selected = floorclamp(local.y() - _itemsTop, _rowHeight, 0, _rows.size() - 1); + local.setY(local.y() - _itemsTop - selected * _rowHeight); + auto set = _rows[selected]; + if (_section == Section::Installed || !set->installed || set->archived || set->removed) { + auto removeButton = (_section == Section::Installed && !set->removed); + auto rect = myrtlrect(relativeButtonRect(removeButton)); + actionSel = rect.contains(local) ? selected : -1; + } else { + actionSel = -1; + } + if (_section == Section::Installed && !set->isRecentSet()) { + auto dragAreaWidth = st::contactsPadding.left() + st::stickersReorderIcon.width() + st::stickersReorderSkip; + auto dragArea = myrtlrect(0, 0, dragAreaWidth, _rowHeight); + inDragArea = dragArea.contains(local); } - } else if (_hasFeaturedButton && QRect(0, st::membersPadding.top(), width(), _buttonHeight).contains(local)) { - selected = -2; - } else if (_hasArchivedButton && QRect(0, st::membersPadding.top() + (_hasFeaturedButton ? _buttonHeight : 0), width(), _buttonHeight).contains(local)) { - selected = -1; } else { - selected = -3; + selected = -1; } if (_selected != selected) { - if (((_selected == -1) != (selected == -1)) || ((_selected == -2) != (selected == -2))) { - update(); - } if (_section != Section::Installed && ((_selected >= 0 || _pressed >= 0) != (selected >= 0 || _pressed >= 0))) { - setCursor((selected >= 0 || _pressed >= 0) ? style::cur_pointer : style::cur_default); + if (!inDragArea) { + setCursor((selected >= 0 || _pressed >= 0) ? style::cur_pointer : style::cur_default); + } } _selected = selected; } + if (_inDragArea != inDragArea) { + _inDragArea = inDragArea; + setCursor(_inDragArea ? style::cur_sizeall : (_selected >= 0 || _pressed >= 0) ? style::cur_pointer : style::cur_default); + } setActionSel(actionSel); - emit noDraggingScroll(); - } -} - -void StickersBox::Inner::onClearRecent() { - if (_clearBox) { - _clearBox->onClose(); - } - - auto &sets = Global::RefStickerSets(); - bool removedCloud = (sets.remove(Stickers::CloudRecentSetId) != 0); - bool removedCustom = (sets.remove(Stickers::CustomSetId) != 0); - - auto &recent = cGetRecentStickers(); - if (!recent.isEmpty()) { - recent.clear(); - Local::writeUserSettings(); - } - - if (removedCustom) Local::writeInstalledStickers(); - if (removedCloud) Local::writeRecentStickers(); - emit App::main()->updateStickers(); - rebuild(); - - MTPmessages_ClearRecentStickers::Flags flags = 0; - MTP::send(MTPmessages_ClearRecentStickers(MTP_flags(flags))); -} - -void StickersBox::Inner::onClearBoxDestroyed(QObject *box) { - if (box == _clearBox) { - _clearBox = nullptr; + emit draggingScrollDelta(0); } } float64 StickersBox::Inner::aboveShadowOpacity() const { if (_above < 0) return 0; - int32 dx = 0; - int32 dy = qAbs(_above * _rowHeight + _rows.at(_above)->yadd.current() - _started * _rowHeight); + auto dx = 0; + auto dy = qAbs(_above * _rowHeight + qRound(_rows[_above]->yadd.current()) - _started * _rowHeight); return qMin((dx + dy) * 2. / _rowHeight, 1.); } void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) { - auto pressed = _pressed; - _pressed = -2; + auto pressed = base::take(_pressed, -1); if (_section != Section::Installed && _selected < 0 && pressed >= 0) { setCursor(style::cur_default); } - if (_saving) return; - _mouse = e->globalPos(); onUpdateSelected(); if (_actionDown == _actionSel && _actionSel >= 0) { if (_section == Section::Installed) { - if (_rows[_actionDown]->recent) { - _clearBox = new ConfirmBox(lang(lng_stickers_clear_recent_sure), lang(lng_stickers_clear_recent)); - connect(_clearBox, SIGNAL(confirmed()), this, SLOT(onClearRecent())); - connect(_clearBox, SIGNAL(destroyed(QObject*)), this, SLOT(onClearBoxDestroyed(QObject*))); - Ui::showLayer(_clearBox, KeepOtherLayers); - } else { - _rows[_actionDown]->disabled = !_rows[_actionDown]->disabled; - } - } else { - installSet(_rows[_actionDown]->id); + setRowRemoved(_actionDown, !_rows[_actionDown]->removed); + } else if (_installSetCallback) { + _installSetCallback(_rows[_actionDown]->id); } } else if (_dragging >= 0) { QPoint local(mapFromGlobal(_mouse)); - _rows[_dragging]->yadd.start(0); + _rows[_dragging]->yadd.start(0.); _aboveShadowFadeStart = _animStartTimes[_dragging] = getms(); - _aboveShadowFadeOpacity = anim::fvalue(aboveShadowOpacity(), 0); + _aboveShadowFadeOpacity = anim::value(aboveShadowOpacity(), 0); if (!_a_shifting.animating()) { _a_shifting.start(); } _dragging = _started = -1; } else if (pressed == _selected && _actionSel < 0 && _actionDown < 0) { - if (_selected == -2) { - _selected = -3; - Ui::showLayer(new StickersBox(Section::Featured), KeepOtherLayers); - } else if (_selected == -1) { - _selected = -3; - Ui::showLayer(new StickersBox(Section::Archived), KeepOtherLayers); - } else if (_selected >= 0 && _section != Section::Installed) { + if (_selected >= 0 && !_inDragArea) { auto &sets = Global::RefStickerSets(); - auto it = sets.find(_rows.at(pressed)->id); - if (it != sets.cend()) { - _selected = -3; - Ui::showLayer(new StickerSetBox(Stickers::inputSetId(*it)), KeepOtherLayers); + auto row = _rows[pressed]; + if (!row->isRecentSet()) { + auto it = sets.find(row->id); + if (it != sets.cend()) { + _selected = -1; + Ui::show(Box(Stickers::inputSetId(*it)), KeepOtherLayers); + } } } } - if (_actionDown >= 0) { - update(0, _itemsTop + _actionDown * _rowHeight, width(), _rowHeight); - _actionDown = -1; + setActionDown(-1); +} + +void StickersBox::Inner::setRowRemoved(int index, bool removed) { + auto row = _rows[index]; + if (row->removed != removed) { + row->removed = removed; + row->ripple.reset(); + update(0, _itemsTop + index * _rowHeight, width(), _rowHeight); + onUpdateSelected(); } } @@ -889,52 +977,20 @@ void StickersBox::Inner::leaveEvent(QEvent *e) { onUpdateSelected(); } -void StickersBox::Inner::installSet(uint64 setId) { - auto &sets = Global::RefStickerSets(); - auto it = sets.find(setId); - if (it == sets.cend()) { - rebuild(); - return; - } - - MTP::send(MTPmessages_InstallStickerSet(Stickers::inputSetId(*it), MTP_boolFalse()), rpcDone(&Inner::installDone), rpcFail(&Inner::installFail, setId)); - - Stickers::installLocally(setId); -} - -void StickersBox::Inner::installDone(const MTPmessages_StickerSetInstallResult &result) { - if (result.type() == mtpc_messages_stickerSetInstallResultArchive) { - Stickers::applyArchivedResult(result.c_messages_stickerSetInstallResultArchive()); - } -} - -bool StickersBox::Inner::installFail(uint64 setId, const RPCError &error) { - if (MTP::isDefaultHandledError(error)) return false; - - auto &sets = Global::RefStickerSets(); - auto it = sets.find(setId); - if (it == sets.cend()) { - rebuild(); - return true; - } - - Stickers::undoInstallLocally(setId); - return true; -} - -void StickersBox::Inner::step_shifting(uint64 ms, bool timer) { - bool animating = false; - int32 updateMin = -1, updateMax = 0; - for (int32 i = 0, l = _animStartTimes.size(); i < l; ++i) { - uint64 start = _animStartTimes.at(i); +void StickersBox::Inner::step_shifting(TimeMs ms, bool timer) { + auto animating = false; + auto updateMin = -1; + auto updateMax = 0; + for (auto i = 0, l = _animStartTimes.size(); i < l; ++i) { + auto start = _animStartTimes.at(i); if (start) { if (updateMin < 0) updateMin = i; updateMax = i; if (start + st::stickersRowDuration > ms && ms >= start) { - _rows.at(i)->yadd.update(float64(ms - start) / st::stickersRowDuration, anim::sineInOut); + _rows[i]->yadd.update(float64(ms - start) / st::stickersRowDuration, anim::sineInOut); animating = true; } else { - _rows.at(i)->yadd.finish(); + _rows[i]->yadd.finish(); _animStartTimes[i] = 0; } } @@ -952,9 +1008,14 @@ void StickersBox::Inner::step_shifting(uint64 ms, bool timer) { } if (timer) { if (_dragging >= 0) { - if (updateMin < 0 || updateMin > _dragging) updateMin = _dragging; + if (updateMin < 0 || updateMin > _dragging) { + updateMin = _dragging; + } if (updateMax < _dragging) updateMax = _dragging; } + if (updateMin == 1 && _rows[0]->isRecentSet()) { + updateMin = 0; // Repaint from the very top of the content. + } if (updateMin >= 0) { update(0, _itemsTop + _rowHeight * (updateMin - 1), width(), _rowHeight * (updateMax - updateMin + 3)); } @@ -972,11 +1033,11 @@ void StickersBox::Inner::clear() { _rows.clear(); _animStartTimes.clear(); _aboveShadowFadeStart = 0; - _aboveShadowFadeOpacity = anim::fvalue(0, 0); + _aboveShadowFadeOpacity = anim::value(); _a_shifting.stop(); _above = _dragging = _started = -1; - _selected = -3; - _pressed = -3; + _selected = -1; + _pressed = -1; _actionDown = -1; setActionSel(-1); update(); @@ -994,22 +1055,7 @@ void StickersBox::Inner::setActionSel(int32 actionSel) { } void StickersBox::Inner::rebuild() { - _hasFeaturedButton = _hasArchivedButton = false; - _itemsTop = st::membersPadding.top(); - _buttonHeight = st::stickersFeaturedHeight; - if (_section == Section::Installed) { - if (!Global::FeaturedStickerSetsOrder().isEmpty()) { - _itemsTop += _buttonHeight; - _hasFeaturedButton = true; - } - if (!Global::ArchivedStickerSetsOrder().isEmpty()) { - _itemsTop += _buttonHeight; - _hasArchivedButton = true; - } - if (_itemsTop > st::membersPadding.top()) { - _itemsTop += st::membersPadding.top(); - } - } + _itemsTop = st::membersMarginTop; int maxNameWidth = countMaxNameWidth(); @@ -1051,7 +1097,8 @@ void StickersBox::Inner::rebuild() { } void StickersBox::Inner::updateSize() { - resize(width(), _itemsTop + _rows.size() * _rowHeight + st::membersPadding.bottom()); + resize(width(), _itemsTop + _rows.size() * _rowHeight + st::membersMarginBottom); + checkLoadMore(); } void StickersBox::Inner::updateRows() { @@ -1071,9 +1118,16 @@ void StickersBox::Inner::updateRows() { row->pixh = pixh; } } - fillSetFlags(set, &row->recent, &row->installed, &row->official, &row->unread, &row->disabled); - if (_section == Section::Installed) { - row->disabled = false; + if (!row->isRecentSet()) { + auto wasInstalled = row->installed; + auto wasArchived = row->archived; + fillSetFlags(set, &row->installed, &row->official, &row->unread, &row->archived); + if (_section == Section::Installed) { + row->archived = false; + } + if (row->installed != wasInstalled || row->archived != wasArchived) { + row->ripple.reset(); + } } row->title = fillSetTitle(set, maxNameWidth, &row->titleWidth); row->count = fillSetCount(set); @@ -1096,18 +1150,22 @@ int StickersBox::Inner::countMaxNameWidth() const { int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left(); int namew = st::boxWideWidth - namex - st::contactsPadding.right() - st::contactsCheckPosition.x(); if (_section == Section::Installed) { - namew -= qMax(qMax(qMax(_returnWidth, _removeWidth), _restoreWidth), _clearWidth); + namew -= _undoWidth - st::stickersUndoRemove.width; } else { - namew -= st::stickersAddIcon.width() - st::defaultActiveButton.width; - namew -= st::stickersFeaturedUnreadSize + st::stickersFeaturedUnreadSkip; + namew -= _addWidth - st::stickersTrendingAdd.width; + if (_section == Section::Featured) { + namew -= st::stickersFeaturedUnreadSize + st::stickersFeaturedUnreadSkip; + } } return namew; } void StickersBox::Inner::rebuildAppendSet(const Stickers::Set &set, int maxNameWidth) { - bool recent = false, installed = false, official = false, unread = false, disabled = false; - fillSetFlags(set, &recent, &installed, &official, &unread, &disabled); - if (_section == Section::Installed && disabled) { + bool installed = true, official = true, unread = false, archived = false, removed = false; + if (set.id != Stickers::CloudRecentSetId) { + fillSetFlags(set, &installed, &official, &unread, &archived); + } + if (_section == Section::Installed && archived) { return; } @@ -1119,7 +1177,7 @@ void StickersBox::Inner::rebuildAppendSet(const Stickers::Set &set, int maxNameW QString title = fillSetTitle(set, maxNameWidth, &titleWidth); int count = fillSetCount(set); - _rows.push_back(new StickerSetRow(set.id, sticker, count, title, titleWidth, installed, official, unread, disabled, recent, pixw, pixh)); + _rows.push_back(new Row(set.id, sticker, count, title, titleWidth, installed, official, unread, archived, removed, pixw, pixh)); _animStartTimes.push_back(0); } @@ -1169,10 +1227,10 @@ int StickersBox::Inner::fillSetCount(const Stickers::Set &set) const { QString StickersBox::Inner::fillSetTitle(const Stickers::Set &set, int maxNameWidth, int *outTitleWidth) const { auto result = set.title; - int titleWidth = st::contactsNameFont->width(result); + int titleWidth = st::contactsNameStyle.font->width(result); if (titleWidth > maxNameWidth) { - result = st::contactsNameFont->elided(result, maxNameWidth); - titleWidth = st::contactsNameFont->width(result); + result = st::contactsNameStyle.font->elided(result, maxNameWidth); + titleWidth = st::contactsNameStyle.font->width(result); } if (outTitleWidth) { *outTitleWidth = titleWidth; @@ -1180,51 +1238,95 @@ QString StickersBox::Inner::fillSetTitle(const Stickers::Set &set, int maxNameWi return result; } -void StickersBox::Inner::fillSetFlags(const Stickers::Set &set, bool *outRecent, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outDisabled) { - *outRecent = (set.id == Stickers::CloudRecentSetId); - *outInstalled = true; - *outOfficial = true; - *outUnread = false; - *outDisabled = false; - if (!*outRecent) { - *outInstalled = (set.flags & MTPDstickerSet::Flag::f_installed); - *outOfficial = (set.flags & MTPDstickerSet::Flag::f_official); - *outDisabled = (set.flags & MTPDstickerSet::Flag::f_archived); - if (_section == Section::Featured) { - *outUnread = (set.flags & MTPDstickerSet_ClientFlag::f_unread); +void StickersBox::Inner::fillSetFlags(const Stickers::Set &set, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outArchived) { + *outInstalled = (set.flags & MTPDstickerSet::Flag::f_installed); + *outOfficial = (set.flags & MTPDstickerSet::Flag::f_official); + *outArchived = (set.flags & MTPDstickerSet::Flag::f_archived); + if (_section == Section::Featured) { + *outUnread = (set.flags & MTPDstickerSet_ClientFlag::f_unread); + } else { + *outUnread = false; + } +} + +template +Stickers::Order StickersBox::Inner::collectSets(Check check) const { + Stickers::Order result; + result.reserve(_rows.size()); + for_const (auto row, _rows) { + if (check(row)) { + result.push_back(row->id); } } + return result; } Stickers::Order StickersBox::Inner::getOrder() const { - Stickers::Order result; - result.reserve(_rows.size()); - for (int32 i = 0, l = _rows.size(); i < l; ++i) { - if (_rows.at(i)->disabled || _rows.at(i)->recent) { - continue; - } - result.push_back(_rows.at(i)->id); - } - return result; + return collectSets([](Row *row) { + return !row->archived && !row->removed && !row->isRecentSet(); + }); } -Stickers::Order StickersBox::Inner::getDisabledSets() const { - Stickers::Order result; - result.reserve(_rows.size()); - for (int32 i = 0, l = _rows.size(); i < l; ++i) { - if (_rows.at(i)->disabled) { - result.push_back(_rows.at(i)->id); +Stickers::Order StickersBox::Inner::getFullOrder() const { + return collectSets([](Row *row) { + return !row->isRecentSet(); + }); +} + +Stickers::Order StickersBox::Inner::getRemovedSets() const { + return collectSets([](Row *row) { + return row->removed; + }); +} + +int StickersBox::Inner::getRowIndex(uint64 setId) const { + for (auto i = 0, count = _rows.size(); i != count; ++i) { + auto row = _rows[i]; + if (row->id == setId) { + return i; } } - return result; + return -1; +} + +void StickersBox::Inner::setFullOrder(const Stickers::Order &order) { + for_const (auto setId, order) { + auto index = getRowIndex(setId); + if (index >= 0) { + auto row = _rows[index]; + auto count = _rows.size(); + for (auto i = index + 1; i != count; ++i) { + _rows[i - 1] = _rows[i]; + } + _rows[count - 1] = row; + } + } +} + +void StickersBox::Inner::setRemovedSets(const Stickers::Order &removed) { + for (auto i = 0, count = _rows.size(); i != count; ++i) { + setRowRemoved(i, removed.contains(_rows[i]->id)); + } } void StickersBox::Inner::setVisibleTopBottom(int visibleTop, int visibleBottom) { + _visibleTop = visibleTop; + _visibleBottom = visibleBottom; + updateScrollbarWidth(); if (_section == Section::Featured) { - _visibleTop = visibleTop; - _visibleBottom = visibleBottom; readVisibleSets(); } + checkLoadMore(); +} + +void StickersBox::Inner::checkLoadMore() { + if (_loadMoreCallback) { + auto scrollHeight = (_visibleBottom - _visibleTop); + int scrollTop = _visibleTop, scrollTopMax = height() - scrollHeight; + if (scrollTop + PreloadHeightsCount * scrollHeight >= scrollTopMax) { + _loadMoreCallback(); + } + } } void StickersBox::Inner::readVisibleSets() { @@ -1245,8 +1347,12 @@ void StickersBox::Inner::readVisibleSets() { } } -void StickersBox::Inner::setVisibleScrollbar(int32 width) { - _scrollbar = width; +void StickersBox::Inner::updateScrollbarWidth() { + auto width = (_visibleBottom - _visibleTop < height()) ? (st::boxLayerScroll.width - st::boxLayerScroll.deltax) : 0; + if (_scrollbar != width) { + _scrollbar = width; + update(); + } } StickersBox::Inner::~Inner() { diff --git a/Telegram/SourceFiles/boxes/stickers_box.h b/Telegram/SourceFiles/boxes/stickers_box.h index 3524a7db8..417659282 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.h +++ b/Telegram/SourceFiles/boxes/stickers_box.h @@ -20,15 +20,22 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "abstractbox.h" +#include "boxes/abstractbox.h" class ConfirmBox; +namespace style { +struct RippleAnimation; +} // namespace style + namespace Ui { class PlainShadow; +class RippleAnimation; +class SettingsSlider; +class SlideAnimation; } // namespace Ui -class StickersBox : public ItemListBox, public RPCSender { +class StickersBox : public BoxContent, public RPCSender { Q_OBJECT public: @@ -38,72 +45,105 @@ public: Archived, ArchivedPart, }; - StickersBox(Section section = Section::Installed); - StickersBox(const Stickers::Order &archivedIds); + StickersBox(QWidget*, Section section); + StickersBox(QWidget*, const Stickers::Order &archivedIds); + + void closeHook() override; ~StickersBox(); -public slots: - void onStickersUpdated(); - - void onCheckDraggingScroll(int localY); - void onNoDraggingScroll(); - void onScrollTimer(); - - void onSave(); - -private slots: - void onScroll(); - protected: + void prepare() override; + void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; - void closePressed() override; - void showAll() override; +private slots: + void onStickersUpdated(); private: - void setup(); - int32 countHeight() const; - void rebuildList(); + class Inner; + class Tab { + public: + Tab() = default; - void disenableDone(const MTPmessages_StickerSetInstallResult &result, mtpRequestId req); - bool disenableFail(const RPCError &error, mtpRequestId req); - void reorderDone(const MTPBool &result); - bool reorderFail(const RPCError &result); - void saveOrder(); + template + Tab(int index, Args&&... args); - void updateVisibleTopBottom(); - void checkLoadMoreArchived(); + object_ptr takeWidget(); + void returnWidget(object_ptr widget); + + Inner *widget() { + return _weak; + } + int index() const { + return _index; + } + + void saveScrollTop(); + int getScrollTop() const { + return _scrollTop; + } + + private: + int _index = 0; + object_ptr _widget = { nullptr }; + QPointer _weak; + int _scrollTop = 0; + + }; + + void refreshTabs(); + void rebuildList(Tab *tab = nullptr); + void updateTabsGeometry(); + void switchTab(); + void installSet(uint64 setId); + int getTopSkip() const; + + QPixmap grabContentCache(); + + void installDone(const MTPmessages_StickerSetInstallResult &result); + bool installFail(uint64 setId, const RPCError &error); + + void preloadArchivedSets(); + void requestArchivedSets(); + void loadMoreArchived(); void getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedStickers &result); + object_ptr _tabs = { nullptr }; + QList
_tabIndices; + + class CounterWidget; + object_ptr _unreadBadge = { nullptr }; + Section _section; - class Inner; - ChildWidget _inner; - ChildWidget _save = { nullptr }; - ChildWidget _cancel = { nullptr }; - OrderedSet _disenableRequests; - mtpRequestId _reorderRequest = 0; - ChildWidget _topShadow = { nullptr }; - ChildWidget _bottomShadow = { nullptr }; + Tab _installed; + Tab _featured; + Tab _archived; + Tab *_tab = nullptr; - QTimer _scrollTimer; - int32 _scrollDelta = 0; + std_::unique_ptr _slideAnimation; + object_ptr _titleShadow = { nullptr }; int _aboutWidth = 0; Text _about; int _aboutHeight = 0; mtpRequestId _archivedRequestId = 0; + bool _archivedLoaded = false; bool _allArchivedLoaded = false; + bool _someArchivedLoaded = false; + + Stickers::Order _localOrder; + Stickers::Order _localRemoved; }; -int32 stickerPacksCount(bool includeDisabledOfficial = false); +int stickerPacksCount(bool includeArchivedOfficial = false); // This class is hold in header because it requires Qt preprocessing. -class StickersBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber { +class StickersBox::Inner : public TWidget, private base::Subscriber { Q_OBJECT public: @@ -115,18 +155,27 @@ public: void updateSize(); void updateRows(); // refresh only pack cover stickers bool appendSet(const Stickers::Set &set); - bool savingStart() { - if (_saving) return false; - _saving = true; - return true; - } Stickers::Order getOrder() const; - Stickers::Order getDisabledSets() const; + Stickers::Order getFullOrder() const; + Stickers::Order getRemovedSets() const; + + void setFullOrder(const Stickers::Order &order); + void setRemovedSets(const Stickers::Order &removed); + + void setInstallSetCallback(base::lambda &&callback) { + _installSetCallback = std_::move(callback); + } + void setLoadMoreCallback(base::lambda &&callback) { + _loadMoreCallback = std_::move(callback); + } - void setVisibleScrollbar(int32 width); void setVisibleTopBottom(int visibleTop, int visibleBottom) override; + int getVisibleTop() const { + return _visibleTop; + } + ~Inner(); protected: @@ -137,39 +186,40 @@ protected: void leaveEvent(QEvent *e) override; signals: - void checkDraggingScroll(int localY); - void noDraggingScroll(); + void draggingScrollDelta(int delta); public slots: void onUpdateSelected(); - void onClearRecent(); - void onClearBoxDestroyed(QObject *box); - -private slots: - void onImageLoaded(); private: - void setup(); - void paintButton(Painter &p, int y, bool selected, const QString &text, int badgeCounter) const; + template + Stickers::Order collectSets(Check check) const; - void step_shifting(uint64 ms, bool timer); - void paintRow(Painter &p, int32 index); + void checkLoadMore(); + void updateScrollbarWidth(); + int getRowIndex(uint64 setId) const; + void setRowRemoved(int index, bool removed); + + void setActionDown(int newActionDown); + void setup(); + QRect relativeButtonRect(bool removeButton) const; + void ensureRipple(const style::RippleAnimation &st, QImage mask, bool removeButton); + + void step_shifting(TimeMs ms, bool timer); + void paintRow(Painter &p, int index, TimeMs ms); + void paintFakeButton(Painter &p, int index, TimeMs ms); void clear(); void setActionSel(int32 actionSel); float64 aboveShadowOpacity() const; void readVisibleSets(); - void installSet(uint64 setId); - void installDone(const MTPmessages_StickerSetInstallResult &result); - bool installFail(uint64 setId, const RPCError &error); - Section _section; Stickers::Order _archivedIds; int32 _rowHeight; - struct StickerSetRow { - StickerSetRow(uint64 id, DocumentData *sticker, int32 count, const QString &title, int titleWidth, bool installed, bool official, bool unread, bool disabled, bool recent, int32 pixw, int32 pixh) : id(id) + struct Row { + Row(uint64 id, DocumentData *sticker, int32 count, const QString &title, int titleWidth, bool installed, bool official, bool unread, bool archived, bool removed, int32 pixw, int32 pixh) : id(id) , sticker(sticker) , count(count) , title(title) @@ -177,63 +227,67 @@ private: , installed(installed) , official(official) , unread(unread) - , disabled(disabled) - , recent(recent) + , archived(archived) + , removed(removed) , pixw(pixw) , pixh(pixh) , yadd(0, 0) { } + bool isRecentSet() const { + return (id == Stickers::CloudRecentSetId); + } uint64 id; DocumentData *sticker; int32 count; QString title; int titleWidth; - bool installed, official, unread, disabled, recent; + bool installed, official, unread, archived, removed; int32 pixw, pixh; - anim::ivalue yadd; + anim::value yadd; + QSharedPointer ripple; }; - using StickerSetRows = QList; + using Rows = QList; void rebuildAppendSet(const Stickers::Set &set, int maxNameWidth); void fillSetCover(const Stickers::Set &set, DocumentData **outSticker, int *outWidth, int *outHeight) const; int fillSetCount(const Stickers::Set &set) const; QString fillSetTitle(const Stickers::Set &set, int maxNameWidth, int *outTitleWidth) const; - void fillSetFlags(const Stickers::Set &set, bool *outRecent, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outDisabled); + void fillSetFlags(const Stickers::Set &set, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outArchived); int countMaxNameWidth() const; - StickerSetRows _rows; - QList _animStartTimes; - uint64 _aboveShadowFadeStart = 0; - anim::fvalue _aboveShadowFadeOpacity = { 0., 0. }; - Animation _a_shifting; + Rows _rows; + QList _animStartTimes; + TimeMs _aboveShadowFadeStart = 0; + anim::value _aboveShadowFadeOpacity; + BasicAnimation _a_shifting; + + base::lambda _installSetCallback; + base::lambda _loadMoreCallback; int _visibleTop = 0; int _visibleBottom = 0; int _itemsTop = 0; - bool _saving = false; - int _actionSel = -1; int _actionDown = -1; - int _clearWidth, _removeWidth, _returnWidth, _restoreWidth; - - ConfirmBox *_clearBox = nullptr; + QString _addText; + int _addWidth = 0; + QString _undoText; + int _undoWidth = 0; int _buttonHeight = 0; - bool _hasFeaturedButton = false; - bool _hasArchivedButton = false; QPoint _mouse; - int _selected = -3; // -2 - featured stickers button, -1 - archived stickers button - int _pressed = -2; + bool _inDragArea = false; + int _selected = -1; + int _pressed = -1; QPoint _dragStart; int _started = -1; int _dragging = -1; int _above = -1; - Ui::RectShadow _aboveShadow; + int _scrollbar = 0; - int32 _scrollbar = 0; }; diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index 3bf665a98..83892600b 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -31,43 +31,36 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "dialogs/dialogs_layout.h" #include "styles/style_boxes.h" #include "styles/style_stickers.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/scroll_area.h" -StickerSetBox::StickerSetBox(const MTPInputStickerSet &set) : ScrollableBox(st::stickersScroll) -, _inner(this, set) -, _shadow(this) -, _add(this, lang(lng_stickers_add_pack), st::defaultBoxButton) -, _share(this, lang(lng_stickers_share_pack), st::defaultBoxButton) -, _cancel(this, lang(lng_cancel), st::cancelBoxButton) -, _done(this, lang(lng_about_done), st::defaultBoxButton) { - setMaxHeight(st::stickersMaxHeight); +StickerSetBox::StickerSetBox(QWidget*, const MTPInputStickerSet &set) +: _set(set) { +} + +void StickerSetBox::prepare() { + setTitle(lang(lng_contacts_loading)); + + _inner = setInnerWidget(object_ptr(this, _set), st::stickersScroll); connect(App::main(), SIGNAL(stickersUpdated()), this, SLOT(onStickersUpdated())); - init(_inner, st::boxButtonPadding.bottom() + _cancel.height() + st::boxButtonPadding.top()); + setDimensions(st::boxWideWidth, st::stickersMaxHeight); - connect(&_add, SIGNAL(clicked()), this, SLOT(onAddStickers())); - connect(&_share, SIGNAL(clicked()), this, SLOT(onShareStickers())); - connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); - connect(&_done, SIGNAL(clicked()), this, SLOT(onClose())); + onUpdateButtons(); connect(_inner, SIGNAL(updateButtons()), this, SLOT(onUpdateButtons())); - connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); - connect(_inner, SIGNAL(installed(uint64)), this, SLOT(onInstalled(uint64))); onStickersUpdated(); - - onScroll(); - - prepare(); } void StickerSetBox::onInstalled(uint64 setId) { emit installed(setId); - onClose(); + closeBox(); } void StickerSetBox::onStickersUpdated() { - showAll(); + updateButtons(); } void StickerSetBox::onAddStickers() { @@ -75,79 +68,40 @@ void StickerSetBox::onAddStickers() { } void StickerSetBox::onShareStickers() { - QString url = qsl("https://telegram.me/addstickers/") + _inner->shortName(); + auto url = CreateInternalLinkHttps(qsl("addstickers/") + _inner->shortName()); QApplication::clipboard()->setText(url); - Ui::showLayer(new InformBox(lang(lng_stickers_copied))); + Ui::show(Box(lang(lng_stickers_copied))); } void StickerSetBox::onUpdateButtons() { - if (!_cancel.isHidden() || !_done.isHidden()) { - showAll(); - } + setTitle(_inner->title()); + updateButtons(); } -void StickerSetBox::onScroll() { - auto scroll = scrollArea(); - auto scrollTop = scroll->scrollTop(); - _inner->setVisibleTopBottom(scrollTop, scrollTop + scroll->height()); -} - -void StickerSetBox::showAll() { - ScrollableBox::showAll(); - int32 cnt = _inner->notInstalled(); +void StickerSetBox::updateButtons() { + clearButtons(); if (_inner->loaded()) { - _shadow.show(); if (_inner->notInstalled()) { - _add.show(); - _cancel.show(); - _share.hide(); - _done.hide(); + addButton(lang(lng_stickers_add_pack), [this] { onAddStickers(); }); + addButton(lang(lng_cancel), [this] { closeBox(); }); } else if (_inner->official()) { - _add.hide(); - _share.hide(); - _cancel.hide(); - _done.show(); + addButton(lang(lng_about_done), [this] { closeBox(); }); } else { - _share.show(); - _cancel.show(); - _add.hide(); - _done.hide(); + addButton(lang(lng_stickers_share_pack), [this] { onShareStickers(); }); + addButton(lang(lng_cancel), [this] { closeBox(); }); } } else { - _shadow.hide(); - _add.hide(); - _share.hide(); - _cancel.show(); - _done.hide(); + addButton(lang(lng_cancel), [this] { closeBox(); }); } - resizeEvent(0); update(); } -void StickerSetBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; - - paintTitle(p, _inner->title()); -} - void StickerSetBox::resizeEvent(QResizeEvent *e) { - ScrollableBox::resizeEvent(e); + BoxContent::resizeEvent(e); _inner->resize(width(), _inner->height()); - _shadow.setGeometry(0, height() - st::boxButtonPadding.bottom() - _cancel.height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth); - _add.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _add.height()); - _share.moveToRight(st::boxButtonPadding.right(), _add.y()); - _done.moveToRight(st::boxButtonPadding.right(), _add.y()); - if (_add.isHidden() && _share.isHidden()) { - _cancel.moveToRight(st::boxButtonPadding.right(), _add.y()); - } else if (_add.isHidden()) { - _cancel.moveToRight(st::boxButtonPadding.right() + _share.width() + st::boxButtonPadding.left(), _add.y()); - } else { - _cancel.moveToRight(st::boxButtonPadding.right() + _add.width() + st::boxButtonPadding.left(), _add.y()); - } } -StickerSetBox::Inner::Inner(QWidget *parent, const MTPInputStickerSet &set) : ScrolledWidget(parent) +StickerSetBox::Inner::Inner(QWidget *parent, const MTPInputStickerSet &set) : TWidget(parent) , _input(set) { switch (set.type()) { case mtpc_inputStickerSetID: _setId = set.c_inputStickerSetID().vid.v; _setAccess = set.c_inputStickerSetID().vaccess_hash.v; break; @@ -180,7 +134,7 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) { if (!doc || !doc->sticker()) continue; _pack.push_back(doc); - _packOvers.push_back(FloatAnimation()); + _packOvers.push_back(Animation()); } auto &packs(d.vpacks.c_vector().v); for (int i = 0, l = packs.size(); i < l; ++i) { @@ -200,7 +154,7 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) { } } if (d.vset.type() == mtpc_stickerSet) { - auto &s(d.vset.c_stickerSet()); + auto &s = d.vset.c_stickerSet(); _setTitle = stickerSetTitle(s); _title = st::boxTitleFont->elided(_setTitle, width() - st::boxTitlePosition.x() - st::boxTitleHeight); _setShortName = qs(s.vshort_name); @@ -222,7 +176,7 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) { } if (_pack.isEmpty()) { - Ui::showLayer(new InformBox(lang(lng_stickers_not_found))); + Ui::show(Box(lang(lng_stickers_not_found))); } else { int32 rows = _pack.size() / StickerPanPerRow + ((_pack.size() % StickerPanPerRow) ? 1 : 0); resize(st::stickersPadding.left() + StickerPanPerRow * st::stickersSize.width(), st::stickersPadding.top() + rows * st::stickersSize.height() + st::stickersPadding.bottom()); @@ -239,7 +193,7 @@ bool StickerSetBox::Inner::failedSet(const RPCError &error) { _loaded = true; - Ui::showLayer(new InformBox(lang(lng_stickers_not_found))); + Ui::show(Box(lang(lng_stickers_not_found))); return true; } @@ -300,7 +254,7 @@ void StickerSetBox::Inner::installDone(const MTPmessages_StickerSetInstallResult bool StickerSetBox::Inner::installFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; - Ui::showLayer(new InformBox(lang(lng_stickers_not_found))); + Ui::show(Box(lang(lng_stickers_not_found))); return true; } @@ -323,6 +277,10 @@ void StickerSetBox::Inner::mouseMoveEvent(QMouseEvent *e) { } } +void StickerSetBox::Inner::leaveEvent(QEvent *e) { + setSelected(-1); +} + void StickerSetBox::Inner::mouseReleaseEvent(QMouseEvent *e) { if (_previewShown >= 0) { _previewShown = -1; @@ -331,7 +289,7 @@ void StickerSetBox::Inner::mouseReleaseEvent(QMouseEvent *e) { if (_previewTimer.isActive()) { _previewTimer.stop(); int index = stickerFromGlobalPos(e->globalPos()); - if (index >= 0 && index < _pack.size()) { + if (index >= 0 && index < _pack.size() && !isMasksSet()) { if (auto main = App::main()) { if (main->onSendSticker(_pack.at(index))) { Ui::hideSettingsAndLayer(); @@ -342,13 +300,14 @@ void StickerSetBox::Inner::mouseReleaseEvent(QMouseEvent *e) { } void StickerSetBox::Inner::updateSelected() { - auto index = stickerFromGlobalPos(QCursor::pos()); - if (isMasksSet()) { - index = -1; - } - if (index != _selected) { + auto selected = stickerFromGlobalPos(QCursor::pos()); + setSelected(isMasksSet() ? -1 : selected); +} + +void StickerSetBox::Inner::setSelected(int selected) { + if (_selected != selected) { startOverAnimation(_selected, 1., 0.); - _selected = index; + _selected = selected; startOverAnimation(_selected, 0., 1.); setCursor(_selected >= 0 ? style::cur_pointer : style::cur_default); } @@ -392,6 +351,7 @@ void StickerSetBox::Inner::paintEvent(QPaintEvent *e) { if (_pack.isEmpty()) return; + auto ms = getms(); int32 rows = _pack.size() / StickerPanPerRow + ((_pack.size() % StickerPanPerRow) ? 1 : 0); int32 from = qFloor(e->rect().top() / st::stickersSize.height()), to = qFloor(e->rect().bottom() / st::stickersSize.height()) + 1; @@ -404,7 +364,7 @@ void StickerSetBox::Inner::paintEvent(QPaintEvent *e) { DocumentData *doc = _pack.at(index); QPoint pos(st::stickersPadding.left() + j * st::stickersSize.width(), st::stickersPadding.top() + i * st::stickersSize.height()); - if (auto over = _packOvers[index].current((index == _selected) ? 1. : 0.)) { + if (auto over = _packOvers[index].current(ms, (index == _selected) ? 1. : 0.)) { p.setOpacity(over); QPoint tl(pos); if (rtl()) tl.setX(width() - tl.x() - st::stickersSize.width()); @@ -469,7 +429,7 @@ QString StickerSetBox::Inner::shortName() const { void StickerSetBox::Inner::install() { if (isMasksSet()) { - Ui::showLayer(new InformBox(lang(lng_stickers_masks_pack)), KeepOtherLayers); + Ui::show(Box(lang(lng_stickers_masks_pack)), KeepOtherLayers); return; } if (_installRequest) return; diff --git a/Telegram/SourceFiles/boxes/stickersetbox.h b/Telegram/SourceFiles/boxes/stickersetbox.h index 8252ab4ab..dd11173ff 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.h +++ b/Telegram/SourceFiles/boxes/stickersetbox.h @@ -20,7 +20,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "abstractbox.h" +#include "boxes/abstractbox.h" #include "core/vector_of_moveable.h" class ConfirmBox; @@ -29,43 +29,42 @@ namespace Ui { class PlainShadow; } // namespace Ui -class StickerSetBox : public ScrollableBox, public RPCSender { +class StickerSetBox : public BoxContent, public RPCSender { Q_OBJECT public: - StickerSetBox(const MTPInputStickerSet &set); - -public slots: - void onStickersUpdated(); - void onAddStickers(); - void onShareStickers(); - void onUpdateButtons(); - - void onScroll(); - -private slots: - void onInstalled(uint64 id); + StickerSetBox(QWidget*, const MTPInputStickerSet &set); signals: void installed(uint64 id); protected: - void paintEvent(QPaintEvent *e) override; + void prepare() override; + void resizeEvent(QResizeEvent *e) override; - void showAll() override; +private slots: + void onStickersUpdated(); + void onAddStickers(); + void onShareStickers(); + void onUpdateButtons(); + + void onInstalled(uint64 id); private: + void updateButtons(); + + MTPInputStickerSet _set; + class Inner; - ChildWidget _inner; - ScrollableBoxShadow _shadow; - BoxButton _add, _share, _cancel, _done; + QPointer _inner; + QString _title; }; // This class is hold in header because it requires Qt preprocessing. -class StickerSetBox::Inner : public ScrolledWidget, public RPCSender, private base::Subscriber { +class StickerSetBox::Inner : public TWidget, public RPCSender, private base::Subscriber { Q_OBJECT public: @@ -87,6 +86,7 @@ protected: void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void paintEvent(QPaintEvent *e) override; + void leaveEvent(QEvent *e) override; private slots: void onPreview(); @@ -97,6 +97,7 @@ signals: private: void updateSelected(); + void setSelected(int selected); void startOverAnimation(int index, float64 from, float64 to); int stickerFromGlobalPos(const QPoint &p) const; @@ -110,7 +111,7 @@ private: return (_setFlags & MTPDstickerSet::Flag::f_masks); } - std_::vector_of_moveable _packOvers; + std_::vector_of_moveable _packOvers; StickerPack _pack; StickersByEmojiMap _emoji; bool _loaded = false; diff --git a/Telegram/SourceFiles/boxes/usernamebox.cpp b/Telegram/SourceFiles/boxes/usernamebox.cpp index c4ae6190c..4ffa16b48 100644 --- a/Telegram/SourceFiles/boxes/usernamebox.cpp +++ b/Telegram/SourceFiles/boxes/usernamebox.cpp @@ -19,105 +19,88 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "lang.h" +#include "boxes/usernamebox.h" +#include "lang.h" #include "application.h" -#include "usernamebox.h" #include "mainwidget.h" #include "mainwindow.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" +#include "ui/toast/toast.h" +#include "styles/style_boxes.h" -UsernameBox::UsernameBox() : AbstractBox(st::boxWidth), -_save(this, lang(lng_settings_save), st::defaultBoxButton), -_cancel(this, lang(lng_cancel), st::cancelBoxButton), -_username(this, st::defaultInputField, qsl("@username"), App::self()->username, false), -_link(this, QString(), st::defaultBoxLinkButton), -_saveRequestId(0), _checkRequestId(0), -_about(st::boxWidth - st::usernamePadding.left()) { - setBlueTitle(true); +UsernameBox::UsernameBox(QWidget*) +: _username(this, st::defaultInputField, qsl("@username"), App::self()->username, false) +, _link(this, QString(), st::boxLinkButton) +, _about(st::boxWidth - st::usernamePadding.left()) +, _checkTimer(this) { +} +void UsernameBox::prepare() { _goodText = App::self()->username.isEmpty() ? QString() : lang(lng_username_available); - textstyleSet(&st::usernameTextStyle); - _about.setRichText(st::boxTextFont, lang(lng_username_about)); - resizeMaxHeight(st::boxWidth, st::boxTitleHeight + st::usernamePadding.top() + _username.height() + st::usernameSkip + _about.countHeight(st::boxWidth - st::usernamePadding.left()) + 3 * st::usernameTextStyle.lineHeight + st::usernamePadding.bottom() + st::boxButtonPadding.top() + _save.height() + st::boxButtonPadding.bottom()); - textstyleRestore(); + setTitle(lang(lng_username_title)); - connect(&_save, SIGNAL(clicked()), this, SLOT(onSave())); - connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); - connect(&_username, SIGNAL(changed()), this, SLOT(onChanged())); - connect(&_username, SIGNAL(submitted(bool)), this, SLOT(onSave())); + addButton(lang(lng_settings_save), [this] { onSave(); }); + addButton(lang(lng_cancel), [this] { closeBox(); }); - connect(&_link, SIGNAL(clicked()), this, SLOT(onLinkClick())); + connect(_username, SIGNAL(changed()), this, SLOT(onChanged())); + connect(_username, SIGNAL(submitted(bool)), this, SLOT(onSave())); + connect(_link, SIGNAL(clicked()), this, SLOT(onLinkClick())); - _checkTimer.setSingleShot(true); - connect(&_checkTimer, SIGNAL(timeout()), this, SLOT(onCheck())); + _about.setRichText(st::usernameTextStyle, lang(lng_username_about)); + setDimensions(st::boxWidth, st::usernamePadding.top() + _username->height() + st::usernameSkip + _about.countHeight(st::boxWidth - st::usernamePadding.left()) + 3 * st::usernameTextStyle.lineHeight + st::usernamePadding.bottom()); - prepare(); -} + _checkTimer->setSingleShot(true); + connect(_checkTimer, SIGNAL(timeout()), this, SLOT(onCheck())); -void UsernameBox::showAll() { - _username.show(); - _save.show(); - _cancel.show(); updateLinkText(); - - AbstractBox::showAll(); } -void UsernameBox::doSetInnerFocus() { - _username.setFocus(); +void UsernameBox::setInnerFocus() { + _username->setFocusFast(); } void UsernameBox::paintEvent(QPaintEvent *e) { - Painter p(this); - if (paint(p)) return; + BoxContent::paintEvent(e); - paintTitle(p, lang(lng_username_title)); + Painter p(this); p.setFont(st::boxTextFont); - if (!_copiedTextLink.isEmpty()) { - p.setPen(st::usernameDefaultFg); - p.drawTextLeft(st::usernamePadding.left(), _username.y() + _username.height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), _copiedTextLink); - } else if (!_errorText.isEmpty()) { - p.setPen(st::setErrColor); - p.drawTextLeft(st::usernamePadding.left(), _username.y() + _username.height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), _errorText); + if (!_errorText.isEmpty()) { + p.setPen(st::boxTextFgError); + p.drawTextLeft(st::usernamePadding.left(), _username->y() + _username->height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), _errorText); } else if (!_goodText.isEmpty()) { - p.setPen(st::setGoodColor); - p.drawTextLeft(st::usernamePadding.left(), _username.y() + _username.height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), _goodText); + p.setPen(st::boxTextFgGood); + p.drawTextLeft(st::usernamePadding.left(), _username->y() + _username->height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), _goodText); } else { p.setPen(st::usernameDefaultFg); - p.drawTextLeft(st::usernamePadding.left(), _username.y() + _username.height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), lang(lng_username_choose)); + p.drawTextLeft(st::usernamePadding.left(), _username->y() + _username->height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), lang(lng_username_choose)); } - p.setPen(st::black); - textstyleSet(&st::usernameTextStyle); + p.setPen(st::boxTextFg); int32 availw = st::boxWidth - st::usernamePadding.left(), h = _about.countHeight(availw); - _about.drawLeft(p, st::usernamePadding.left(), _username.y() + _username.height() + st::usernameSkip, availw, width()); - textstyleRestore(); + _about.drawLeft(p, st::usernamePadding.left(), _username->y() + _username->height() + st::usernameSkip, availw, width()); - int32 linky = _username.y() + _username.height() + st::usernameSkip + h + st::usernameTextStyle.lineHeight + ((st::usernameTextStyle.lineHeight - st::boxTextFont->height) / 2); - if (_link.isHidden()) { + int32 linky = _username->y() + _username->height() + st::usernameSkip + h + st::usernameTextStyle.lineHeight + ((st::usernameTextStyle.lineHeight - st::boxTextFont->height) / 2); + if (_link->isHidden()) { p.drawTextLeft(st::usernamePadding.left(), linky, width(), lang(lng_username_link_willbe)); p.setPen(st::usernameDefaultFg); - p.drawTextLeft(st::usernamePadding.left(), linky + st::usernameTextStyle.lineHeight + ((st::usernameTextStyle.lineHeight - st::boxTextFont->height) / 2), width(), qsl("https://telegram.me/username")); + p.drawTextLeft(st::usernamePadding.left(), linky + st::usernameTextStyle.lineHeight + ((st::usernameTextStyle.lineHeight - st::boxTextFont->height) / 2), width(), CreateInternalLinkHttps(qsl("username"))); } else { p.drawTextLeft(st::usernamePadding.left(), linky, width(), lang(lng_username_link)); } } void UsernameBox::resizeEvent(QResizeEvent *e) { - _username.resize(width() - st::usernamePadding.left() - st::usernamePadding.right(), _username.height()); - _username.moveToLeft(st::usernamePadding.left(), st::boxTitleHeight + st::usernamePadding.top()); + BoxContent::resizeEvent(e); + + _username->resize(width() - st::usernamePadding.left() - st::usernamePadding.right(), _username->height()); + _username->moveToLeft(st::usernamePadding.left(), st::usernamePadding.top()); - textstyleSet(&st::usernameTextStyle); int32 availw = st::boxWidth - st::usernamePadding.left(), h = _about.countHeight(availw); - textstyleRestore(); - int32 linky = _username.y() + _username.height() + st::usernameSkip + h + st::usernameTextStyle.lineHeight + ((st::usernameTextStyle.lineHeight - st::boxTextFont->height) / 2); - _link.moveToLeft(st::usernamePadding.left(), linky + st::usernameTextStyle.lineHeight + ((st::usernameTextStyle.lineHeight - st::boxTextFont->height) / 2)); - - _save.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _save.height()); - _cancel.moveToRight(st::boxButtonPadding.right() + _save.width() + st::boxButtonPadding.left(), _save.y()); - - AbstractBox::resizeEvent(e); + int32 linky = _username->y() + _username->height() + st::usernameSkip + h + st::usernameTextStyle.lineHeight + ((st::usernameTextStyle.lineHeight - st::boxTextFont->height) / 2); + _link->moveToLeft(st::usernamePadding.left(), linky + st::usernameTextStyle.lineHeight + ((st::usernameTextStyle.lineHeight - st::boxTextFont->height) / 2)); } void UsernameBox::onSave() { @@ -143,50 +126,50 @@ void UsernameBox::onChanged() { QString name = getName(); if (name.isEmpty()) { if (!_errorText.isEmpty() || !_goodText.isEmpty()) { - _copiedTextLink = _errorText = _goodText = QString(); + _errorText = _goodText = QString(); update(); } - _checkTimer.stop(); + _checkTimer->stop(); } else { int32 len = name.size(); for (int32 i = 0; i < len; ++i) { QChar ch = name.at(i); if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z') && (ch < '0' || ch > '9') && ch != '_' && (ch != '@' || i > 0)) { - if (_errorText != lang(lng_username_bad_symbols) || !_copiedTextLink.isEmpty()) { - _copiedTextLink = QString(); + if (_errorText != lang(lng_username_bad_symbols)) { _errorText = lang(lng_username_bad_symbols); update(); } - _checkTimer.stop(); + _checkTimer->stop(); return; } } if (name.size() < MinUsernameLength) { - if (_errorText != lang(lng_username_too_short) || !_copiedTextLink.isEmpty()) { - _copiedTextLink = QString(); + if (_errorText != lang(lng_username_too_short)) { _errorText = lang(lng_username_too_short); update(); } - _checkTimer.stop(); + _checkTimer->stop(); } else { - if (!_errorText.isEmpty() || !_goodText.isEmpty() || !_copiedTextLink.isEmpty()) { - _copiedTextLink = _errorText = _goodText = QString(); + if (!_errorText.isEmpty() || !_goodText.isEmpty()) { + _errorText = _goodText = QString(); update(); } - _checkTimer.start(UsernameCheckTimeout); + _checkTimer->start(UsernameCheckTimeout); } } } void UsernameBox::onLinkClick() { - Application::clipboard()->setText(qsl("https://telegram.me/") + getName()); - _copiedTextLink = lang(lng_username_copied); - update(); + Application::clipboard()->setText(CreateInternalLinkHttps(getName())); + + Ui::Toast::Config toast; + toast.text = lang(lng_username_copied); + Ui::Toast::Show(App::wnd(), toast); } void UsernameBox::onUpdateDone(const MTPUser &user) { App::feedUsers(MTP_vector(1, user)); - onClose(); + closeBox(); } bool UsernameBox::onUpdateFail(const RPCError &error) { @@ -196,24 +179,22 @@ bool UsernameBox::onUpdateFail(const RPCError &error) { QString err(error.type()); if (err == qstr("USERNAME_NOT_MODIFIED") || _sentUsername == App::self()->username) { App::self()->setName(textOneLine(App::self()->firstName), textOneLine(App::self()->lastName), textOneLine(App::self()->nameOrPhone), textOneLine(_sentUsername)); - onClose(); + closeBox(); return true; } else if (err == qstr("USERNAME_INVALID")) { - _username.setFocus(); - _username.showError(); - _copiedTextLink = QString(); + _username->setFocus(); + _username->showError(); _errorText = lang(lng_username_invalid); update(); return true; } else if (err == qstr("USERNAME_OCCUPIED") || err == qstr("USERNAMES_UNAVAILABLE")) { - _username.setFocus(); - _username.showError(); - _copiedTextLink = QString(); + _username->setFocus(); + _username->showError(); _errorText = lang(lng_username_occupied); update(); return true; } - _username.setFocus(); + _username->setFocus(); return true; } @@ -221,10 +202,9 @@ void UsernameBox::onCheckDone(const MTPBool &result) { _checkRequestId = 0; QString newError = (mtpIsTrue(result) || _checkUsername == App::self()->username) ? QString() : lang(lng_username_occupied); QString newGood = newError.isEmpty() ? lang(lng_username_available) : QString(); - if (_errorText != newError || _goodText != newGood || !_copiedTextLink.isEmpty()) { + if (_errorText != newError || _goodText != newGood) { _errorText = newError; _goodText = newGood; - _copiedTextLink = QString(); update(); } } @@ -244,26 +224,25 @@ bool UsernameBox::onCheckFail(const RPCError &error) { return true; } _goodText = QString(); - _copiedTextLink = QString(); - _username.setFocus(); + _username->setFocus(); return true; } QString UsernameBox::getName() const { - return _username.text().replace('@', QString()).trimmed(); + return _username->text().replace('@', QString()).trimmed(); } void UsernameBox::updateLinkText() { QString uname = getName(); - _link.setText(st::boxTextFont->elided(qsl("https://telegram.me/") + uname, st::boxWidth - st::usernamePadding.left() - st::usernamePadding.right())); + _link->setText(st::boxTextFont->elided(CreateInternalLinkHttps(uname), st::boxWidth - st::usernamePadding.left() - st::usernamePadding.right())); if (uname.isEmpty()) { - if (!_link.isHidden()) { - _link.hide(); + if (!_link->isHidden()) { + _link->hide(); update(); } } else { - if (_link.isHidden()) { - _link.show(); + if (_link->isHidden()) { + _link->show(); update(); } } diff --git a/Telegram/SourceFiles/boxes/usernamebox.h b/Telegram/SourceFiles/boxes/usernamebox.h index 1af61399d..7feb989a9 100644 --- a/Telegram/SourceFiles/boxes/usernamebox.h +++ b/Telegram/SourceFiles/boxes/usernamebox.h @@ -20,15 +20,27 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "abstractbox.h" +#include "boxes/abstractbox.h" -class UsernameBox : public AbstractBox, public RPCSender { +namespace Ui { +class UsernameInput; +class LinkButton; +} // namespace Ui + +class UsernameBox : public BoxContent, public RPCSender { Q_OBJECT public: - UsernameBox(); + UsernameBox(QWidget*); -public slots: +protected: + void prepare() override; + void setInnerFocus() override; + + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + +private slots: void onSave(); void onCheck(); @@ -36,13 +48,6 @@ public slots: void onLinkClick(); -protected: - void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - - void showAll() override; - void doSetInnerFocus() override; - private: void onUpdateDone(const MTPUser &result); bool onUpdateFail(const RPCError &error); @@ -53,14 +58,14 @@ private: QString getName() const; void updateLinkText(); - BoxButton _save, _cancel; - UsernameInput _username; - LinkButton _link; + object_ptr _username; + object_ptr _link; - mtpRequestId _saveRequestId, _checkRequestId; - QString _sentUsername, _checkUsername, _errorText, _goodText, _copiedTextLink; + mtpRequestId _saveRequestId = 0; + mtpRequestId _checkRequestId = 0; + QString _sentUsername, _checkUsername, _errorText, _goodText; Text _about; - QTimer _checkTimer; + object_ptr _checkTimer; }; diff --git a/Telegram/SourceFiles/codegen/common/basic_tokenized_file.h b/Telegram/SourceFiles/codegen/common/basic_tokenized_file.h index fcfcd27a4..d19ad1868 100644 --- a/Telegram/SourceFiles/codegen/common/basic_tokenized_file.h +++ b/Telegram/SourceFiles/codegen/common/basic_tokenized_file.h @@ -62,6 +62,8 @@ public: Plus, Minus, Equals, + And, + Or, Name, // [0-9a-zA-Z_]+ with at least one letter. }; Type type; @@ -144,6 +146,8 @@ private: { '+', Type::Plus }, { '-', Type::Minus }, { '=', Type::Equals }, + { '&', Type::And }, + { '|', Type::Or }, }; }; diff --git a/Telegram/SourceFiles/codegen/style/generator.cpp b/Telegram/SourceFiles/codegen/style/generator.cpp index 4dbd47fc1..f2e0b7b08 100644 --- a/Telegram/SourceFiles/codegen/style/generator.cpp +++ b/Telegram/SourceFiles/codegen/style/generator.cpp @@ -38,8 +38,56 @@ namespace codegen { namespace style { namespace { -constexpr int kErrorBadIconSize = 861; -constexpr int kErrorBadIconFormat = 862; +constexpr int kErrorBadIconSize = 861; +constexpr int kErrorBadIconFormat = 862; + +// crc32 hash, taken somewhere from the internet + +class Crc32Table { +public: + Crc32Table() { + quint32 poly = 0x04c11db7; + for (auto i = 0; i != 256; ++i) { + _data[i] = reflect(i, 8) << 24; + for (auto j = 0; j != 8; ++j) { + _data[i] = (_data[i] << 1) ^ (_data[i] & (1 << 31) ? poly : 0); + } + _data[i] = reflect(_data[i], 32); + } + } + + inline quint32 operator[](int index) const { + return _data[index]; + } + +private: + quint32 reflect(quint32 val, char ch) { + quint32 result = 0; + for (int i = 1; i < (ch + 1); ++i) { + if (val & 1) { + result |= 1 << (ch - i); + } + val >>= 1; + } + return result; + } + + quint32 _data[256]; + +}; + +qint32 hashCrc32(const void *data, int len) { + static Crc32Table table; + + const uchar *buffer = static_cast(data); + + quint32 crc = 0xffffffff; + for (int i = 0; i != len; ++i) { + crc = (crc >> 8) ^ table[(crc & 0xFF) ^ buffer[i]]; + } + + return static_cast(crc ^ 0xffffffff); +} char hexChar(uchar ch) { if (ch < 10) { @@ -118,13 +166,46 @@ QString pxValueName(int value) { return result + QString::number(value); } +QString moduleBaseName(const structure::Module &module) { + auto moduleInfo = QFileInfo(module.filepath()); + auto moduleIsPalette = (moduleInfo.suffix() == "palette"); + return moduleIsPalette ? "palette" : "style_" + moduleInfo.baseName(); +} + +QString colorFallbackName(structure::Value value) { + auto copy = value.copyOf(); + if (!copy.isEmpty()) { + return copy.back(); + } + return value.Color().fallback; +} + +QChar paletteColorPart(uchar part) { + part = (part & 0x0F); + if (part >= 10) { + return 'a' + (part - 10); + } + return '0' + part; +} + +QString paletteColorComponent(uchar value) { + return QString() + paletteColorPart(value >> 4) + paletteColorPart(value); +} + +QString paletteColorValue(const structure::data::color &value) { + auto result = paletteColorComponent(value.red) + paletteColorComponent(value.green) + paletteColorComponent(value.blue); + if (value.alpha != 255) result += paletteColorComponent(value.alpha); + return result; +} + } // namespace -Generator::Generator(const structure::Module &module, const QString &destBasePath, const common::ProjectInfo &project) +Generator::Generator(const structure::Module &module, const QString &destBasePath, const common::ProjectInfo &project, bool isPalette) : module_(module) , basePath_(destBasePath) , baseName_(QFileInfo(basePath_).baseName()) -, project_(project) { +, project_(project) +, isPalette_(isPalette) { } bool Generator::writeHeader() { @@ -164,8 +245,13 @@ public:\n\ }\n\ };\n\ Module_" << baseName_ << " registrator;\n"; - if (!writeVariableDefinitions()) { - return false; + if (isPalette_) { + source_->newline(); + source_->stream() << "style::palette _palette;\n"; + } else { + if (!writeVariableDefinitions()) { + return false; + } } source_->newline().popNamespace(); @@ -174,8 +260,11 @@ Module_" << baseName_ << " registrator;\n"; return false; } - source_->popNamespace().newline(); - source_->newline().pushNamespace("style").pushNamespace("internal").newline(); + source_->popNamespace().newline().pushNamespace("style"); + if (isPalette_) { + writeSetPaletteColor(); + } + source_->pushNamespace("internal").newline(); if (!writeVariableInit()) { return false; } @@ -194,10 +283,7 @@ QString Generator::typeToString(structure::Type type) const { case Tag::String: return "QString"; case Tag::Color: return "style::color"; case Tag::Point: return "style::point"; - case Tag::Sprite: return "style::sprite"; case Tag::Size: return "style::size"; - case Tag::Transition: return "style::transition"; - case Tag::Cursor: return "style::cursor"; case Tag::Align: return "style::align"; case Tag::Margins: return "style::margins"; case Tag::Font: return "style::font"; @@ -217,10 +303,7 @@ QString Generator::typeToDefaultValue(structure::Type type) const { case Tag::String: return "QString()"; case Tag::Color: return "{ Qt::Uninitialized }"; case Tag::Point: return "{ 0, 0 }"; - case Tag::Sprite: return "{ 0, 0, 0, 0 }"; case Tag::Size: return "{ 0, 0 }"; - case Tag::Transition: return "anim::linear"; - case Tag::Cursor: return "style::cur_default"; case Tag::Align: return "style::al_topleft"; case Tag::Margins: return "{ 0, 0, 0, 0 }"; case Tag::Font: return "{ Qt::Uninitialized }"; @@ -254,22 +337,23 @@ QString Generator::valueAssignmentCode(structure::Value value) const { case Tag::String: return QString("qsl(%1)").arg(stringToEncodedString(value.String())); case Tag::Color: { auto v(value.Color()); - return QString("{ %1, %2, %3, %4 }").arg(v.red).arg(v.green).arg(v.blue).arg(v.alpha); + if (v.red == v.green && v.red == v.blue && v.red == 0 && v.alpha == 255) { + return QString("st::windowFg"); + } else if (v.red == v.green && v.red == v.blue && v.red == 255 && v.alpha == 0) { + return QString("st::transparent"); + } else { + common::logError(common::kErrorInternal, "") << "bad color value"; + return QString(); + } } break; case Tag::Point: { auto v(value.Point()); return QString("{ %1, %2 }").arg(pxValueName(v.x)).arg(pxValueName(v.y)); } break; - case Tag::Sprite: { - auto v(value.Sprite()); - return QString("{ %1, %2, %3, %4 }").arg(pxValueName(v.left)).arg(pxValueName(v.top)).arg(pxValueName(v.width)).arg(pxValueName(v.height)); - } break; case Tag::Size: { auto v(value.Size()); return QString("{ %1, %2 }").arg(pxValueName(v.width)).arg(pxValueName(v.height)); } break; - case Tag::Transition: return QString("anim::%1").arg(value.String().c_str()); - case Tag::Cursor: return QString("style::cur_%1").arg(value.String().c_str()); case Tag::Align: return QString("style::al_%1").arg(value.String().c_str()); case Tag::Margins: { auto v(value.Margins()); @@ -335,12 +419,162 @@ bool Generator::writeHeaderStyleNamespace() { if (!writeStructsDefinitions()) { return false; } + } else if (isPalette_) { + if (!wroteForwardDeclarations) { + header_->newline(); + } + if (!writePaletteDefinition()) { + return false; + } } header_->popNamespace().newline(); return true; } +bool Generator::writePaletteDefinition() { + header_->stream() << "\ +class palette {\n\ +public:\n\ + palette() = default;\n\ + palette(const palette &other) = delete;\n\ +\n\ + QByteArray save() const;\n\ + bool load(const QByteArray &cache);\n\ + bool setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a);\n\ + bool setColor(QLatin1String name, QLatin1String from);\n\ + void reset() {\n\ + clear();\n\ + finalize();\n\ + }\n\ +\n\ + // Created not inited, should be finalized before usage.\n\ + void finalize();\n\ +\n\ + int indexOfColor(color c) const;\n\ + color colorAtIndex(int index) const;\n\ +\n\ + inline const color &get_transparent() const { return _colors[0]; }; // special color\n"; + + int indexInPalette = 1; + if (!module_.enumVariables([this, &indexInPalette](const Variable &variable) -> bool { + auto name = variable.name.back(); + if (variable.value.type().tag != structure::TypeTag::Color) { + return false; + } + + auto index = (indexInPalette++); + header_->stream() << "\tinline const color &get_" << name << "() const { return _colors[" << index << "]; };\n"; + return true; + })) return false; + + auto count = indexInPalette; + header_->stream() << "\ +\n\ + palette &operator=(const palette &other) {\n\ + auto wasReady = _ready;\n\ + for (int i = 0; i != kCount; ++i) {\n\ + if (other._status[i] == Status::Loaded) {\n\ + if (_status[i] == Status::Initial) {\n\ + new (data(i)) internal::ColorData(*other.data(i));\n\ + } else {\n\ + *data(i) = *other.data(i);\n\ + }\n\ + } else if (_status[i] != Status::Initial) {\n\ + data(i)->~ColorData();\n\ + _status[i] = Status::Initial;\n\ + _ready = false;\n\ + }\n\ + }\n\ + if (wasReady && !_ready) {\n\ + finalize();\n\ + }\n\ + return *this;\n\ + }\n\ +\n\ + static int32 Checksum();\n\ +\n\ + ~palette() {\n\ + clear();\n\ + }\n\ +\n\ +private:\n\ + static constexpr auto kCount = " << count << ";\n\ +\n\ + void clear() {\n\ + for (int i = 0; i != kCount; ++i) {\n\ + if (_status[i] != Status::Initial) {\n\ + data(i)->~ColorData();\n\ + _status[i] = Status::Initial;\n\ + _ready = false;\n\ + }\n\ + }\n\ + }\n\ +\n\ + struct TempColorData { uchar r, g, b, a; };\n\ + void compute(int index, int fallbackIndex, TempColorData value) {\n\ + if (_status[index] == Status::Initial) {\n\ + if (fallbackIndex >= 0 && _status[fallbackIndex] == Status::Loaded) {\n\ + _status[index] = Status::Loaded;\n\ + new (data(index)) internal::ColorData(*data(fallbackIndex));\n\ + } else {\n\ + _status[index] = Status::Created;\n\ + new (data(index)) internal::ColorData(value.r, value.g, value.b, value.a);\n\ + }\n\ + }\n\ + }\n\ +\n\ + internal::ColorData *data(int index) {\n\ + return reinterpret_cast(_data) + index;\n\ + }\n\ +\n\ + const internal::ColorData *data(int index) const {\n\ + return reinterpret_cast(_data) + index;\n\ + }\n\ +\n\ + void setData(int index, const internal::ColorData &value) {\n\ + if (_status[index] == Status::Initial) {\n\ + new (data(index)) internal::ColorData(value);\n\ + } else {\n\ + *data(index) = value;\n\ + }\n\ + _status[index] = Status::Loaded;\n\ + }\n\ +\n\ + enum class Status {\n\ + Initial,\n\ + Created,\n\ + Loaded,\n\ + };\n\ +\n\ + alignas(alignof(internal::ColorData)) char _data[sizeof(internal::ColorData) * kCount];\n\ +\n\ + color _colors[kCount] = {\n"; + for (int i = 0; i != count; ++i) { + header_->stream() << "\t\tdata(" << i << "),\n"; + } + header_->stream() << "\ + };\n\ + Status _status[kCount] = { Status::Initial };\n\ + bool _ready = false;\n\ +\n\ +};\n\ +\n\ +namespace main_palette {\n\ +\n\ +QByteArray save();\n\ +bool load(const QByteArray &cache);\n\ +bool setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a);\n\ +bool setColor(QLatin1String name, QLatin1String from);\n\ +void apply(const palette &other);\n\ +void reset();\n\ +int indexOfColor(color c);\n\ +\n\ +} // namespace main_palette\n"; + + return true; +} + bool Generator::writeStructsForwardDeclarations() { bool hasNoExternalStructs = module_.enumVariables([this](const Variable &value) -> bool { if (value.value.type().tag == structure::TypeTag::Struct) { @@ -373,15 +607,17 @@ bool Generator::writeStructsDefinitions() { } bool result = module_.enumStructs([this](const Struct &value) -> bool { - header_->stream() << "struct " << value.name.back() << " {\n"; - for (const auto &field : value.fields) { + header_->stream() << "\ +struct " << value.name.back() << " {\n"; + for (auto &field : value.fields) { auto type = typeToString(field.type); if (type.isEmpty()) { return false; } header_->stream() << "\t" << type << " " << field.name.back() << ";\n"; } - header_->stream() << "};\n\n"; + header_->stream() << "\ +};\n\n"; return true; }); @@ -395,6 +631,9 @@ bool Generator::writeRefsDeclarations() { header_->pushNamespace("st"); + if (isPalette_) { + header_->stream() << "extern const style::color &transparent; // special color\n"; + } bool result = module_.enumVariables([this](const Variable &value) -> bool { auto name = value.name.back(); auto type = typeToString(value.value.type()); @@ -416,10 +655,19 @@ bool Generator::writeIncludesInSource() { return true; } - bool result = module_.enumIncludes([this](const Module &module) -> bool { - source_->include("style_" + QFileInfo(module.filepath()).baseName() + ".h"); + auto includes = QStringList(); + std::function collector = [this, &collector, &includes](const Module &module) { + module.enumIncludes(collector); + auto base = moduleBaseName(module); + if (!includes.contains(base)) { + includes.push_back(base); + } return true; - }); + }; + auto result = module_.enumIncludes(collector); + for (auto base : includes) { + source_->include(base + ".h"); + } source_->newline(); return result; } @@ -447,19 +695,226 @@ bool Generator::writeRefsDefinition() { return true; } - source_->newline(); + if (isPalette_) { + source_->stream() << "const style::color &transparent(_palette.get_transparent()); // special color\n"; + } bool result = module_.enumVariables([this](const Variable &variable) -> bool { auto name = variable.name.back(); auto type = typeToString(variable.value.type()); if (type.isEmpty()) { return false; } - source_->stream() << "const " << type << " &" << name << "(_" << name << ");\n"; + source_->stream() << "const " << type << " &" << name << "("; + if (isPalette_) { + source_->stream() << "_palette.get_" << name << "()"; + } else { + source_->stream() << "_" << name; + } + source_->stream() << ");\n"; return true; }); return result; } +bool Generator::writeSetPaletteColor() { + source_->newline(); + source_->stream() << "\n\ +int palette::indexOfColor(style::color c) const {\n\ + auto start = data(0);\n\ + if (c._data >= start && c._data < start + kCount) {\n\ + return static_cast(c._data - start);\n\ + }\n\ + return -1;\n\ +}\n\ +\n\ +color palette::colorAtIndex(int index) const {\n\ + t_assert(_ready);\n\ + t_assert(index >= 0 && index < kCount);\n\ + return _colors[index];\n\ +}\n\ +\n\ +void palette::finalize() {\n\ + if (_ready) return;\n\ + _ready = true;\n\ +\n\ + compute(0, -1, { 255, 255, 255, 0}); // special color\n"; + + int indexInPalette = 1; + QByteArray checksumString; + checksumString.append("&transparent:{ 255, 255, 255, 0 }"); + bool result = module_.enumVariables([this, &indexInPalette, &checksumString](const Variable &variable) -> bool { + auto name = variable.name.back(); + auto index = indexInPalette++; + paletteIndices_[name] = index; + if (variable.value.type().tag != structure::TypeTag::Color) { + return false; + } + auto color = variable.value.Color(); + auto fallbackIndex = paletteIndices_.value(colorFallbackName(variable.value), -1); + auto assignment = QString("{ %1, %2, %3, %4 }").arg(color.red).arg(color.green).arg(color.blue).arg(color.alpha); + source_->stream() << "\tcompute(" << index << ", " << fallbackIndex << ", " << assignment << ");\n"; + checksumString.append('&' + name + ':' + assignment); + return true; + }); + auto count = indexInPalette; + auto checksum = hashCrc32(checksumString.constData(), checksumString.size()); + + source_->stream() << "\ +}\n\ +\n\ +int32 palette::Checksum() {\n\ + return " << checksum << ";\n\ +}\n"; + + source_->newline().pushNamespace().newline(); + source_->stream() << "\ +int getPaletteIndex(QLatin1String name) {\n\ + auto size = name.size();\n\ + auto data = name.data();\n"; + + int already = 0; + QString prefix; + QString tabs; + for (auto i = paletteIndices_.end(), b = paletteIndices_.begin(); i != b;) { + --i; + auto name = i.key(); + auto index = i.value(); + auto prev = i; + auto next = (i == b) ? QString() : (--prev).key(); + while ((prefix.size() > name.size()) || (!prefix.isEmpty() && prefix.mid(0, already - 1) != name.mid(0, already - 1))) { + source_->stream() << "\n" << tabs << "};"; + prefix.chop(1); + tabs.chop(1); + --already; + } + if (!prefix.isEmpty() && prefix[already - 1] != name[already - 1]) { + source_->stream() << "\n" << tabs << "case '" << name[already - 1] << "':"; + prefix[already - 1] = name[already - 1]; + } + while (name.size() > already) { + if (name.mid(0, already) != next.mid(0, already)) { + break; + } else if (next.size() <= already) { + source_->stream() << "\n" << tabs << "\tif (size == " << name.size() << ")"; + break; + } + source_->stream() << "\n" << tabs << "\tif (size > " << already << ") switch (data[" << already << "]) {\n"; + prefix.append(name[already]); + tabs.append('\t'); + ++already; + source_->stream() << tabs << "case '" << name[already - 1] << "':"; + } + if (name.size() == already || name.mid(0, already) != next.mid(0, already)) { + source_->stream() << " return (size == " << name.size(); + if (name.size() != already) { + source_->stream() << " && "; + } + } else { + source_->stream() << " return ("; + } + if (already != name.size()) { + source_->stream() << "!memcmp(data + " << already << ", \"" << name.mid(already) << "\", " << (name.size() - already) << ")"; + } + source_->stream() << ") ? " << index << " : -1;"; + } + while (!prefix.isEmpty()) { + source_->stream() << "\n" << tabs << "};"; + prefix.chop(1); + tabs.chop(1); + --already; + } + + source_->stream() << "\ +\n\ + return -1;\n\ +}\n"; + + source_->newline().popNamespace().newline(); + source_->stream() << "\ +QByteArray palette::save() const {\n\ + if (!_ready) const_cast(this)->finalize();\n\ +\n\ + auto result = QByteArray(" << (count * 4) << ", Qt::Uninitialized);\n\ + for (auto i = 0, index = 0; i != " << count << "; ++i) {\n\ + result[index++] = static_cast(data(i)->c.red());\n\ + result[index++] = static_cast(data(i)->c.green());\n\ + result[index++] = static_cast(data(i)->c.blue());\n\ + result[index++] = static_cast(data(i)->c.alpha());\n\ + }\n\ + return result;\n\ +}\n\ +\n\ +bool palette::load(const QByteArray &cache) {\n\ + if (cache.size() != " << (count * 4) << ") return false;\n\ +\n\ + auto p = reinterpret_cast(cache.constData());\n\ + for (auto i = 0; i != " << count << "; ++i) {\n\ + setData(i, { p[i * 4 + 0], p[i * 4 + 1], p[i * 4 + 2], p[i * 4 + 3] });\n\ + }\n\ + return true;\n\ +}\n\ +\n\ +bool palette::setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a) {\n\ + auto index = getPaletteIndex(name);\n\ + if (index >= 0) {\n\ + setData(index, { r, g, b, a });\n\ + return true;\n\ + }\n\ + return false;\n\ +}\n\ +\n\ +bool palette::setColor(QLatin1String name, QLatin1String from) {\n\ + auto nameIndex = getPaletteIndex(name);\n\ + auto fromIndex = getPaletteIndex(from);\n\ + if (nameIndex >= 0 && fromIndex >= 0 && _status[fromIndex] == Status::Loaded) {\n\ + setData(nameIndex, *data(fromIndex));\n\ + return true;\n\ + }\n\ + return false;\n\ +}\n\ +\n\ +namespace main_palette {\n\ +\n\ +QByteArray save() {\n\ + return _palette.save();\n\ +}\n\ +\n\ +bool load(const QByteArray &cache) {\n\ + if (_palette.load(cache)) {\n\ + style::internal::resetIcons();\n\ + return true;\n\ + }\n\ + return false;\n\ +}\n\ +\n\ +bool setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a) {\n\ + return _palette.setColor(name, r, g, b, a);\n\ +}\n\ +\n\ +bool setColor(QLatin1String name, QLatin1String from) {\n\ + return _palette.setColor(name, from);\n\ +}\n\ +\n\ +void apply(const palette &other) {\n\ + _palette = other;\n\ + style::internal::resetIcons();\n\ +}\n\ +\n\ +void reset() {\n\ + _palette.reset();\n\ + style::internal::resetIcons();\n\ +}\n\ +\n\ +int indexOfColor(color c) {\n\ + return _palette.indexOfColor(c);\n\ +}\n\ +\n\ +} // namespace main_palette\n\ +\n"; + + return result; +} + bool Generator::writeVariableInit() { if (!module_.hasVariables()) { return true; @@ -492,7 +947,7 @@ void init_" << baseName_ << "() {\n\ bool writtenAtLeastOne = false; bool result = module_.enumIncludes([this,&writtenAtLeastOne](const Module &module) -> bool { if (module.hasVariables()) { - source_->stream() << "\tinit_style_" + QFileInfo(module.filepath()).baseName() + "();\n"; + source_->stream() << "\tinit_" + moduleBaseName(module) + "();\n"; writtenAtLeastOne = true; } return true; @@ -515,7 +970,9 @@ void init_" << baseName_ << "() {\n\ source_->newline(); } - bool result = module_.enumVariables([this](const Variable &variable) -> bool { + if (isPalette_) { + source_->stream() << "\t_palette.finalize();\n"; + } else if (!module_.enumVariables([this](const Variable &variable) -> bool { auto name = variable.name.back(); auto value = valueAssignmentCode(variable.value); if (value.isEmpty()) { @@ -523,11 +980,12 @@ void init_" << baseName_ << "() {\n\ } source_->stream() << "\t_" << name << " = " << value << ";\n"; return true; - }); - + })) { + return false; + } source_->stream() << "\ }\n\n"; - return result; + return true; } bool Generator::writePxValuesInit() { @@ -596,9 +1054,13 @@ QByteArray iconMaskValueSize(int width, int height) { return result; } -QByteArray iconMaskValuePng(const QString &filepath) { +QByteArray iconMaskValuePng(QString filepath) { QByteArray result; + auto pathAndModifiers = filepath.split('-'); + filepath = pathAndModifiers[0]; + auto modifiers = pathAndModifiers.mid(1); + QImage png100x(filepath + ".png"); QImage png200x(filepath + "@2x.png"); png100x.setDevicePixelRatio(1.); @@ -619,6 +1081,14 @@ QByteArray iconMaskValuePng(const QString &filepath) { common::logError(kErrorBadIconSize, filepath + ".png") << "bad icons size, 1x: " << png100x.width() << "x" << png100x.height() << ", 2x: " << png200x.width() << "x" << png200x.height(); return result; } + for (auto modifierName : modifiers) { + if (auto modifier = GetModifier(modifierName)) { + modifier(png100x, png200x); + } else { + common::logError(common::kErrorInternal, filepath) << "modifier should be valid here, name: " << modifierName.toStdString(); + return result; + } + } QImage png125x = png200x.scaled(structure::data::pxAdjust(png100x.width(), 5), structure::data::pxAdjust(png100x.height(), 5), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); QImage png150x = png200x.scaled(structure::data::pxAdjust(png100x.width(), 6), structure::data::pxAdjust(png100x.height(), 6), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); @@ -635,7 +1105,6 @@ QByteArray iconMaskValuePng(const QString &filepath) { { QBuffer buffer(&result); composed.save(&buffer, "PNG"); -// composed.save(filePath + "@final.png", "PNG"); } return result; } @@ -685,8 +1154,6 @@ bool Generator::collectUniqueValues() { case Tag::Double: case Tag::String: case Tag::Color: - case Tag::Transition: - case Tag::Cursor: case Tag::Align: break; case Tag::Pixels: pxValues_.insert(value.Int(), true); break; case Tag::Point: { @@ -694,13 +1161,6 @@ bool Generator::collectUniqueValues() { pxValues_.insert(v.x, true); pxValues_.insert(v.y, true); } break; - case Tag::Sprite: { - auto v(value.Sprite()); - pxValues_.insert(v.left, true); - pxValues_.insert(v.top, true); - pxValues_.insert(v.width, true); - pxValues_.insert(v.height, true); - } break; case Tag::Size: { auto v(value.Size()); pxValues_.insert(v.width, true); @@ -722,7 +1182,7 @@ bool Generator::collectUniqueValues() { } break; case Tag::Icon: { auto v(value.Icon()); - for (const auto &part : v.parts) { + for (auto &part : v.parts) { pxValues_.insert(part.offset.Point().x, true); pxValues_.insert(part.offset.Point().y, true); if (!iconMasks_.contains(part.filename)) { @@ -748,5 +1208,91 @@ bool Generator::collectUniqueValues() { return module_.enumVariables(collector); } +bool Generator::writeSampleTheme(const QString &filepath) { + QByteArray content; + QTextStream stream(&content); + + stream << "\ +//\n\ +// This is a sample Telegram Desktop theme file.\n\ +// It was generated from the 'colors.palette' style file.\n\ +//\n\ +// To create a theme with a background image included you should\n\ +// put two files in a .zip archive:\n\ +//\n\ +// First one is the color scheme like the one you're viewing\n\ +// right now, this file should be named 'colors.tdesktop-theme'.\n\ +//\n\ +// Second one should be the background image and it can be named\n\ +// 'background.jpg', 'background.png', 'tiled.jpg' or 'tiled.png'.\n\ +// You should name it 'background' (if you'd like it not to be tiled),\n\ +// or it can be named 'tiled' (if you'd like it to be tiled).\n\ +//\n\ +// After that you need to change the extension of your .zip archive\n\ +// to 'tdesktop-theme', so you'll have:\n\ +//\n\ +// mytheme.tdesktop-theme\n\ +// |-colors.tdesktop-theme\n\ +// |-background.jpg (or tiled.jpg, background.png, tiled.png)\n\ +//\n\n"; + + QList names; + module_.enumVariables([this, &names](const Variable &variable) -> bool { + names.push_back(variable.name); + return true; + }); + bool result = module_.enumVariables([this, &names, &stream](const Variable &variable) -> bool { + auto name = variable.name.back(); + if (variable.value.type().tag != structure::TypeTag::Color) { + return false; + } + auto color = variable.value.Color(); + //color.red = uchar(rand() % 256); + //color.green = uchar(rand() % 256); + //color.blue = uchar(rand() % 256); + //auto fallbackIndex = -1; + auto fallbackIndex = paletteIndices_.value(colorFallbackName(variable.value), -1); + auto colorString = paletteColorValue(color); + if (fallbackIndex >= 0) { + auto fallbackVariable = module_.findVariableInModule(names[fallbackIndex - 1], module_); + if (!fallbackVariable || fallbackVariable->value.type().tag != structure::TypeTag::Color) { + return false; + } + auto fallbackName = fallbackVariable->name.back(); + auto fallbackColor = fallbackVariable->value.Color(); + if (colorString == paletteColorValue(fallbackColor)) { + stream << name << ": " << fallbackName << ";\n"; + } else { + stream << name << ": #" << colorString << "; // " << fallbackName << ";\n"; + } + } else { + stream << name << ": #" << colorString << ";\n"; + } + return true; + }); + if (!result) { + return result; + } + + stream.flush(); + + QFile file(filepath); + if (file.open(QIODevice::ReadOnly)) { + if (file.readAll() == content) { + file.close(); + return true; + } + file.close(); + } + + if (!file.open(QIODevice::WriteOnly)) { + return false; + } + if (file.write(content) != content.size()) { + return false; + } + return true; +} + } // namespace style } // namespace codegen diff --git a/Telegram/SourceFiles/codegen/style/generator.h b/Telegram/SourceFiles/codegen/style/generator.h index 4d7319dae..746be4213 100644 --- a/Telegram/SourceFiles/codegen/style/generator.h +++ b/Telegram/SourceFiles/codegen/style/generator.h @@ -34,12 +34,13 @@ class Module; class Generator { public: - Generator(const structure::Module &module, const QString &destBasePath, const common::ProjectInfo &project); + Generator(const structure::Module &module, const QString &destBasePath, const common::ProjectInfo &project, bool isPalette); Generator(const Generator &other) = delete; Generator &operator=(const Generator &other) = delete; bool writeHeader(); bool writeSource(); + bool writeSampleTheme(const QString &filepath); private: QString typeToString(structure::Type type) const; @@ -49,11 +50,13 @@ private: bool writeHeaderStyleNamespace(); bool writeStructsForwardDeclarations(); bool writeStructsDefinitions(); + bool writePaletteDefinition(); bool writeRefsDeclarations(); bool writeIncludesInSource(); bool writeVariableDefinitions(); bool writeRefsDefinition(); + bool writeSetPaletteColor(); bool writeVariableInit(); bool writePxValuesInit(); bool writeFontFamiliesInit(); @@ -66,10 +69,12 @@ private: QString basePath_, baseName_; const common::ProjectInfo &project_; std::unique_ptr source_, header_; + bool isPalette_ = false; QMap pxValues_; QMap fontFamilies_; QMap iconMasks_; // icon file -> index + QMap paletteIndices_; std::vector scales = { 4, 5, 6, 8 }; // scale / 4 gives our 1.00, 1.25, 1.50, 2.00 std::vectorscaleNames = { "dbisOne", "dbisOneAndQuarter", "dbisOneAndHalf", "dbisTwo" }; diff --git a/Telegram/SourceFiles/codegen/style/options.cpp b/Telegram/SourceFiles/codegen/style/options.cpp index 2793e3cf7..705818df7 100644 --- a/Telegram/SourceFiles/codegen/style/options.cpp +++ b/Telegram/SourceFiles/codegen/style/options.cpp @@ -41,24 +41,12 @@ using common::logError; Options parseOptions() { Options result; - auto args(QCoreApplication::instance()->arguments()); + auto args = QCoreApplication::instance()->arguments(); for (int i = 1, count = args.size(); i < count; ++i) { // skip first - const auto &arg(args.at(i)); - - // Rebuild all dependencies - if (arg == "--rebuild") { - result.rebuildDependencies = true; - - // Skip generating style modules - } else if (arg == "--skip-styles") { - result.skipStyles = true; - - // Skip generating sprite_125x.png and sprite_150x.png - } else if (arg == "--skip-sprites") { - result.skipSprites = true; + auto &arg = args.at(i); // Include paths - } else if (arg == "-I") { + if (arg == "-I") { if (++i == count) { logError(kErrorIncludePathExpected, "Command Line") << "include path expected after -I"; return Options(); @@ -104,6 +92,7 @@ Options parseOptions() { logError(kErrorInputPathExpected, "Command Line") << "input path expected"; return Options(); } + result.isPalette = (QFileInfo(result.inputPath).suffix() == "palette"); return result; } diff --git a/Telegram/SourceFiles/codegen/style/options.h b/Telegram/SourceFiles/codegen/style/options.h index f1f591b7c..93af08ace 100644 --- a/Telegram/SourceFiles/codegen/style/options.h +++ b/Telegram/SourceFiles/codegen/style/options.h @@ -30,9 +30,7 @@ struct Options { QStringList includePaths = { "." }; QString outputPath = "."; QString inputPath; - bool rebuildDependencies = false; - bool skipStyles = false; - bool skipSprites = false; + bool isPalette = false; }; // Parsing failed if inputPath is empty in the result. diff --git a/Telegram/SourceFiles/codegen/style/parsed_file.cpp b/Telegram/SourceFiles/codegen/style/parsed_file.cpp index 1f5ab35f9..beaf36b9d 100644 --- a/Telegram/SourceFiles/codegen/style/parsed_file.cpp +++ b/Telegram/SourceFiles/codegen/style/parsed_file.cpp @@ -44,6 +44,7 @@ constexpr int kErrorIdentifierNotFound = 804; constexpr int kErrorAlreadyDefined = 805; constexpr int kErrorBadString = 806; constexpr int kErrorIconDuplicate = 807; +constexpr int kErrorBadIconModifier = 808; QString findInputFile(const Options &options) { for (const auto &dir : options.includePaths) { @@ -64,7 +65,7 @@ QString tokenValue(const BasicToken &token) { bool isValidColor(const QString &str) { auto len = str.size(); - if (len != 3 && len != 4 && len != 6 && len != 8) { + if (len != 6 && len != 8) { return false; } @@ -77,6 +78,10 @@ bool isValidColor(const QString &str) { return true; } +uchar toGray(uchar r, uchar g, uchar b) { + return qMax(qMin(int(0.21 * r + 0.72 * g + 0.07 * b), 255), 0); +} + uchar readHexUchar(QChar ch) { auto code = ch.unicode(); return (code >= '0' && code <= '9') ? ((code - '0') & 0xFF) : ((code + 10 - 'a') & 0xFF); @@ -86,23 +91,17 @@ uchar readHexUchar(QChar char1, QChar char2) { return ((readHexUchar(char1) & 0x0F) << 4) | (readHexUchar(char2) & 0x0F); } -structure::data::color convertWebColor(const QString &str) { +structure::data::color convertWebColor(const QString &str, const QString &fallback = QString()) { uchar r = 0, g = 0, b = 0, a = 255; if (isValidColor(str)) { - auto len = str.size(); - if (len == 3 || len == 4) { - r = readHexUchar(str.at(0), str.at(0)); - g = readHexUchar(str.at(1), str.at(1)); - b = readHexUchar(str.at(2), str.at(2)); - if (len == 4) a = readHexUchar(str.at(3), str.at(3)); - } else { - r = readHexUchar(str.at(0), str.at(1)); - g = readHexUchar(str.at(2), str.at(3)); - b = readHexUchar(str.at(4), str.at(5)); - if (len == 8) a = readHexUchar(str.at(6), str.at(7)); + r = readHexUchar(str.at(0), str.at(1)); + g = readHexUchar(str.at(2), str.at(3)); + b = readHexUchar(str.at(4), str.at(5)); + if (str.size() == 8) { + a = readHexUchar(str.at(6), str.at(7)); } } - return { r, g, b, a }; + return { r, g, b, a, fallback }; } structure::data::color convertIntColor(int r, int g, int b, int a) { @@ -120,10 +119,7 @@ std::string logType(const structure::Type &type) { { structure::TypeTag::String , "string" }, { structure::TypeTag::Color , "color" }, { structure::TypeTag::Point , "point" }, - { structure::TypeTag::Sprite , "sprite" }, { structure::TypeTag::Size , "size" }, - { structure::TypeTag::Transition, "transition" }, - { structure::TypeTag::Cursor , "cursor" }, { structure::TypeTag::Align , "align" }, { structure::TypeTag::Margins , "margins" }, { structure::TypeTag::Font , "font" }, @@ -140,20 +136,31 @@ bool validateAnsiString(const QString &value) { return true; } -bool validateTransitionString(const QString &value) { - return QRegularExpression("^[a-zA-Z_]+$").match(value).hasMatch(); -} - -bool validateCursorString(const QString &value) { - return QRegularExpression("^[a-z_]+$").match(value).hasMatch(); -} - bool validateAlignString(const QString &value) { return QRegularExpression("^[a-z_]+$").match(value).hasMatch(); } } // namespace +Modifier GetModifier(const QString &name) { + static QMap modifiers; + if (modifiers.empty()) { + modifiers.insert("invert", [](QImage &png100x, QImage &png200x) { + png100x.invertPixels(); + png200x.invertPixels(); + }); + modifiers.insert("flip_horizontal", [](QImage &png100x, QImage &png200x) { + png100x = png100x.mirrored(true, false); + png200x = png200x.mirrored(true, false); + }); + modifiers.insert("flip_vertical", [](QImage &png100x, QImage &png200x) { + png100x = png100x.mirrored(false, true); + png200x = png200x.mirrored(false, true); + }); + } + return modifiers.value(name); +} + ParsedFile::ParsedFile(const Options &options) : filePath_(findInputFile(options)) , file_(filePath_) @@ -223,6 +230,11 @@ ParsedFile::ModulePtr ParsedFile::readIncluded() { } structure::Struct ParsedFile::readStruct(const QString &name) { + if (options_.isPalette) { + logErrorUnexpectedToken() << "unique color variable for the palette"; + return {}; + } + structure::Struct result = { composeFullName(name) }; do { if (auto fieldName = file_.getToken(BasicType::Name)) { @@ -243,6 +255,10 @@ structure::Variable ParsedFile::readVariable(const QString &name) { structure::Variable result = { composeFullName(name) }; if (auto value = readValue()) { result.value = value; + if (options_.isPalette && value.type().tag != structure::TypeTag::Color) { + logErrorUnexpectedToken() << "unique color variable for the palette"; + return {}; + } if (value.type().tag != structure::TypeTag::Struct || !value.copyOf().empty()) { assertNextToken(BasicType::Semicolon); } @@ -285,14 +301,8 @@ structure::Value ParsedFile::readValue() { return colorValue; } else if (auto pointValue = readPointValue()) { return pointValue; - } else if (auto spriteValue = readSpriteValue()) { - return spriteValue; } else if (auto sizeValue = readSizeValue()) { return sizeValue; - } else if (auto transitionValue = readTransitionValue()) { - return transitionValue; - } else if (auto cursorValue = readCursorValue()) { - return cursorValue; } else if (auto alignValue = readAlignValue()) { return alignValue; } else if (auto marginsValue = readMarginsValue()) { @@ -500,51 +510,43 @@ structure::Value ParsedFile::readStringValue() { structure::Value ParsedFile::readColorValue() { if (auto numberSign = file_.getToken(BasicType::Number)) { - auto color = file_.getAnyToken(); - if (color.type == BasicType::Int || color.type == BasicType::Name) { - auto chars = tokenValue(color).toLower(); - if (isValidColor(chars)) { - return { convertWebColor(chars) }; + if (options_.isPalette) { + auto color = file_.getAnyToken(); + if (color.type == BasicType::Int || color.type == BasicType::Name) { + auto chars = tokenValue(color).toLower(); + if (isValidColor(chars)) { + if (auto fallbackSeparator = file_.getToken(BasicType::Or)) { + if (options_.isPalette) { + if (auto fallbackName = file_.getToken(BasicType::Name)) { + structure::FullName name = { tokenValue(fallbackName) }; + if (auto variable = module_->findVariableInModule(name, *module_)) { + return { convertWebColor(chars, tokenValue(fallbackName)) }; + } else { + logError(kErrorIdentifierNotFound) << "fallback color name"; + } + } else { + logErrorUnexpectedToken() << "fallback color name"; + } + } else { + logErrorUnexpectedToken() << "';', color fallbacks are only allowed in palette module"; + } + } else { + return { convertWebColor(chars) }; + } + } + } else { + logErrorUnexpectedToken() << "color value in #ccc, #ccca, #cccccc or #ccccccaa format"; } } else { - logErrorUnexpectedToken() << "color value in #ccc, #ccca, #cccccc or #ccccccaa format"; + logErrorUnexpectedToken() << "color value alias, unique color values are only allowed in palette module"; } - } else if (auto rgbaToken = file_.getToken(BasicType::Name)) { - if (tokenValue(rgbaToken) == "rgba") { - assertNextToken(BasicType::LeftParenthesis); - - auto r = readNumericValue(); assertNextToken(BasicType::Comma); - auto g = readNumericValue(); assertNextToken(BasicType::Comma); - auto b = readNumericValue(); assertNextToken(BasicType::Comma); - auto a = readNumericValue(); - if (r.type().tag != structure::TypeTag::Int || r.Int() < 0 || r.Int() > 255 || - g.type().tag != structure::TypeTag::Int || g.Int() < 0 || g.Int() > 255 || - b.type().tag != structure::TypeTag::Int || b.Int() < 0 || b.Int() > 255 || - a.type().tag != structure::TypeTag::Int || a.Int() < 0 || a.Int() > 255) { - logErrorTypeMismatch() << "expected four 0-255 values for the rgba color"; - } - - assertNextToken(BasicType::RightParenthesis); - - return { convertIntColor(r.Int(), g.Int(), b.Int(), a.Int()) }; - } else if (tokenValue(rgbaToken) == "rgb") { - assertNextToken(BasicType::LeftParenthesis); - - auto r = readNumericValue(); assertNextToken(BasicType::Comma); - auto g = readNumericValue(); assertNextToken(BasicType::Comma); - auto b = readNumericValue(); - if (r.type().tag != structure::TypeTag::Int || r.Int() < 0 || r.Int() > 255 || - g.type().tag != structure::TypeTag::Int || g.Int() < 0 || g.Int() > 255 || - b.type().tag != structure::TypeTag::Int || b.Int() < 0 || b.Int() > 255) { - logErrorTypeMismatch() << "expected three int values for the rgb color"; - } - - assertNextToken(BasicType::RightParenthesis); - - return { convertIntColor(r.Int(), g.Int(), b.Int(), 255) }; + } else if (auto transparentName = file_.getToken(BasicType::Name)) { + if (tokenValue(transparentName) == "transparent") { + return { structure::data::color { 255, 255, 255, 0 } }; } file_.putBack(); } + return {}; } @@ -569,31 +571,6 @@ structure::Value ParsedFile::readPointValue() { return {}; } -structure::Value ParsedFile::readSpriteValue() { - if (auto font = file_.getToken(BasicType::Name)) { - if (tokenValue(font) == "sprite") { - assertNextToken(BasicType::LeftParenthesis); - - auto x = readNumericValue(); assertNextToken(BasicType::Comma); - auto y = readNumericValue(); assertNextToken(BasicType::Comma); - auto w = readNumericValue(); assertNextToken(BasicType::Comma); - auto h = readNumericValue(); - if (x.type().tag != structure::TypeTag::Pixels || - y.type().tag != structure::TypeTag::Pixels || - w.type().tag != structure::TypeTag::Pixels || - h.type().tag != structure::TypeTag::Pixels) { - logErrorTypeMismatch() << "expected four px values for the sprite"; - } - - assertNextToken(BasicType::RightParenthesis); - - return { structure::data::sprite { x.Int(), y.Int(), w.Int(), h.Int() } }; - } - file_.putBack(); - } - return {}; -} - structure::Value ParsedFile::readSizeValue() { if (auto font = file_.getToken(BasicType::Name)) { if (tokenValue(font) == "size") { @@ -615,46 +592,6 @@ structure::Value ParsedFile::readSizeValue() { return {}; } -structure::Value ParsedFile::readTransitionValue() { - if (auto font = file_.getToken(BasicType::Name)) { - if (tokenValue(font) == "transition") { - assertNextToken(BasicType::LeftParenthesis); - - auto transition = tokenValue(assertNextToken(BasicType::Name)); - - assertNextToken(BasicType::RightParenthesis); - - if (validateTransitionString(transition)) { - return { structure::TypeTag::Transition, transition.toStdString() }; - } else { - logError(kErrorBadString) << "bad transition value"; - } - } - file_.putBack(); - } - return {}; -} - -structure::Value ParsedFile::readCursorValue() { - if (auto font = file_.getToken(BasicType::Name)) { - if (tokenValue(font) == "cursor") { - assertNextToken(BasicType::LeftParenthesis); - - auto cursor = tokenValue(assertNextToken(BasicType::Name)); - - assertNextToken(BasicType::RightParenthesis); - - if (validateCursorString(cursor)) { - return { structure::TypeTag::Cursor, cursor.toStdString() }; - } else { - logError(kErrorBadString) << "bad cursor string"; - } - } - file_.putBack(); - } - return {}; -} - structure::Value ParsedFile::readAlignValue() { if (auto font = file_.getToken(BasicType::Name)) { if (tokenValue(font) == "align") { @@ -850,17 +787,26 @@ structure::data::monoicon ParsedFile::readMonoIconFields() { QString ParsedFile::readMonoIconFilename() { if (auto filename = readValue()) { if (filename.type().tag == structure::TypeTag::String) { - auto filepath = QString::fromStdString(filename.String()); - for (const auto &path : options_.includePaths) { - QFileInfo fileinfo(path + '/' + filepath + ".png"); - if (fileinfo.exists()) { - return path + '/' + filepath; + auto fullpath = QString::fromStdString(filename.String()); + auto pathAndModifiers = fullpath.split('-'); + auto filepath = pathAndModifiers[0]; + auto modifiers = pathAndModifiers.mid(1); + for (auto modifierName : modifiers) { + if (!GetModifier(modifierName)) { + logError(kErrorBadIconModifier) << "unknown modifier: " << modifierName.toStdString(); + return QString(); } } - for (const auto &path : options_.includePaths) { + for (auto &path : options_.includePaths) { + QFileInfo fileinfo(path + '/' + filepath + ".png"); + if (fileinfo.exists()) { + return path + '/' + fullpath; + } + } + for (auto &path : options_.includePaths) { QFileInfo fileinfo(path + "/icons/" + filepath + ".png"); if (fileinfo.exists()) { - return path + "/icons/" + filepath; + return path + "/icons/" + fullpath; } } logError(common::kErrorFileNotFound) << "could not open icon file '" << filename.String() << "'"; @@ -884,6 +830,7 @@ Options ParsedFile::includedOptions(const QString &filepath) { auto result = options_; result.inputPath = filepath; result.includePaths[0] = QFileInfo(filePath_).dir().absolutePath(); + result.isPalette = (QFileInfo(filepath).suffix() == "palette"); return result; } diff --git a/Telegram/SourceFiles/codegen/style/parsed_file.h b/Telegram/SourceFiles/codegen/style/parsed_file.h index 8eea16483..c9c5a7b12 100644 --- a/Telegram/SourceFiles/codegen/style/parsed_file.h +++ b/Telegram/SourceFiles/codegen/style/parsed_file.h @@ -22,6 +22,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include #include +#include +#include #include "codegen/common/basic_tokenized_file.h" #include "codegen/style/options.h" #include "codegen/style/module.h" @@ -29,6 +31,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace codegen { namespace style { +using Modifier = std::function; +Modifier GetModifier(const QString &name); + // Parses an input file to the internal struct. class ParsedFile { public: @@ -44,7 +49,6 @@ public: } private: - bool failed() const { return failed_ || file_.failed(); } @@ -88,10 +92,7 @@ private: structure::Value readStringValue(); structure::Value readColorValue(); structure::Value readPointValue(); - structure::Value readSpriteValue(); structure::Value readSizeValue(); - structure::Value readTransitionValue(); - structure::Value readCursorValue(); structure::Value readAlignValue(); structure::Value readMarginsValue(); structure::Value readFontValue(); @@ -127,10 +128,7 @@ private: { "string" , { structure::TypeTag::String } }, { "color" , { structure::TypeTag::Color } }, { "point" , { structure::TypeTag::Point } }, - { "sprite" , { structure::TypeTag::Sprite } }, { "size" , { structure::TypeTag::Size } }, - { "transition", { structure::TypeTag::Transition } }, - { "cursor" , { structure::TypeTag::Cursor } }, { "align" , { structure::TypeTag::Align } }, { "margins" , { structure::TypeTag::Margins } }, { "font" , { structure::TypeTag::Font } }, diff --git a/Telegram/SourceFiles/codegen/style/processor.cpp b/Telegram/SourceFiles/codegen/style/processor.cpp index 4b534e7c2..f486c3c6f 100644 --- a/Telegram/SourceFiles/codegen/style/processor.cpp +++ b/Telegram/SourceFiles/codegen/style/processor.cpp @@ -25,7 +25,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "codegen/common/cpp_file.h" #include "codegen/style/parsed_file.h" #include "codegen/style/generator.h" -#include "codegen/style/sprite_generator.h" namespace codegen { namespace style { @@ -50,14 +49,7 @@ int Processor::launch() { } auto module = parser_->getResult(); - if (options_.rebuildDependencies) { - bool result = module->enumIncludes([this](const structure::Module &included) -> bool { - return write(included); - }); - if (!result) { - return -1; - } - } else if (!write(*module)) { + if (!write(*module)) { return -1; } @@ -66,38 +58,32 @@ int Processor::launch() { bool Processor::write(const structure::Module &module) const { bool forceReGenerate = false; - bool onlyStyles = options_.skipSprites; - bool onlySprites = options_.skipStyles; - if (!onlyStyles) { - SpriteGenerator spriteGenerator(module, forceReGenerate); - if (!spriteGenerator.writeSprites()) { - return false; - } + QDir dir(options_.outputPath); + if (!dir.mkpath(".")) { + common::logError(kErrorCantWritePath, "Command Line") << "can not open path for writing: " << dir.absolutePath().toStdString(); + return false; } - if (!onlySprites) { - QDir dir(options_.outputPath); - if (!dir.mkpath(".")) { - common::logError(kErrorCantWritePath, "Command Line") << "can not open path for writing: " << dir.absolutePath().toStdString(); - return false; - } - QFileInfo srcFile(module.filepath()); - QString dstFilePath = dir.absolutePath() + '/' + destFileBaseName(module); + QFileInfo srcFile(module.filepath()); + QString dstFilePath = dir.absolutePath() + '/' + (options_.isPalette ? "palette" : destFileBaseName(module)); - common::ProjectInfo project = { - "codegen_style", - srcFile.fileName(), - "stdafx.h", - forceReGenerate - }; + common::ProjectInfo project = { + "codegen_style", + srcFile.fileName(), + "stdafx.h", + forceReGenerate + }; - Generator generator(module, dstFilePath, project); - if (!generator.writeHeader()) { - return false; - } - if (!generator.writeSource()) { - return false; - } + Generator generator(module, dstFilePath, project, options_.isPalette); + if (!generator.writeHeader()) { + return false; + } + if (!generator.writeSource()) { + return false; + } + auto themePath = srcFile.absoluteDir().absolutePath() + "/default.tdesktop-theme"; + if (options_.isPalette && !generator.writeSampleTheme(themePath)) { + return false; } return true; } diff --git a/Telegram/SourceFiles/codegen/style/sprite_generator.cpp b/Telegram/SourceFiles/codegen/style/sprite_generator.cpp deleted file mode 100644 index c7d07be97..000000000 --- a/Telegram/SourceFiles/codegen/style/sprite_generator.cpp +++ /dev/null @@ -1,180 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -It is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the copyright holders give permission -to link the code of portions of this program with the OpenSSL library. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org -*/ -#include "codegen/style/sprite_generator.h" - -#include -#include -#include -#include -#include -#include -#include "codegen/style/parsed_file.h" - -using Module = codegen::style::structure::Module; -using Struct = codegen::style::structure::Struct; -using Variable = codegen::style::structure::Variable; -using Tag = codegen::style::structure::TypeTag; - -namespace codegen { -namespace style { - -using structure::logFullName; - -namespace { - -constexpr int kErrorSpritesIntersect = 841; -constexpr int kErrorCouldNotGenerate = 842; -constexpr int kErrorCouldNotSerialize = 843; -constexpr int kErrorCouldNotOpen = 844; -constexpr int kErrorCouldNotWrite = 845; - -} // namespace - -SpriteGenerator::SpriteGenerator(const structure::Module &module, bool forceReGenerate) -: module_(module) -, forceReGenerate_(forceReGenerate) -, basePath_(QFileInfo(module.filepath()).dir().absolutePath()) { -} - -bool SpriteGenerator::writeSprites() { - if (!collectSprites()) { - return false; - } - if (sprites_.isEmpty()) { - return true; - } - - sprite2x_ = QImage(basePath_ + "/art/sprite_200x.png"); - if (sprite2x_.isNull()) { - common::logError(common::kErrorFileNotFound, "/art/sprite_200x.png") << "sprite file was not found"; - return false; - } - std::vector sizes = { 5, 6 }; - std::vector postfixes = { "125", "150" }; - for (int i = 0, l = sizes.size(); i < l; ++i) { - auto sprite = generateSprite(sizes[i]); - QString filepath = basePath_ + "/art/sprite_" + postfixes[i] + "x.png"; - if (sprite.isNull()) { - common::logError(kErrorCouldNotGenerate, filepath) << "could not generate sprite file"; - return false; - } - QByteArray spriteData; - { - QBuffer spriteBuffer(&spriteData); - if (!sprite.save(&spriteBuffer, "PNG")) { - common::logError(kErrorCouldNotSerialize, filepath) << "could not serialize sprite file"; - return false; - } - } - QFile file(filepath); - if (!forceReGenerate_ && file.open(QIODevice::ReadOnly)) { - if (file.readAll() == spriteData) { - continue; - } - file.close(); - } - if (!file.open(QIODevice::WriteOnly)) { - common::logError(kErrorCouldNotOpen, filepath) << "could not open sprite file for write"; - return false; - } - if (file.write(spriteData) != spriteData.size()) { - common::logError(kErrorCouldNotWrite, filepath) << "could not write sprite file"; - return false; - } - - // Touch resource file. - filepath = basePath_ + "/telegram.qrc"; - QFile qrc(filepath); - if (qrc.open(QIODevice::ReadOnly)) { - auto qrcContent = qrc.readAll(); - qrc.close(); - if (!qrc.open(QIODevice::WriteOnly)) { - common::logError(kErrorCouldNotOpen, filepath) << "could not open .qrc file for write"; - return false; - } - if (qrc.write(qrcContent) != qrcContent.size()) { - common::logError(kErrorCouldNotWrite, filepath) << "could not write .qrc file"; - return false; - } - } - } - - return true; -} - -bool SpriteGenerator::collectSprites() { - std::function collector = [this, &collector](const Variable &variable) { - auto value = variable.value; - if (value.type().tag == Tag::Sprite) { - auto v(value.Sprite()); - if (!v.width || !v.height) return true; - - QRect vRect(v.left, v.top, v.width, v.height); - bool found = false; - for (auto var : sprites_) { - auto sprite = var.value.Sprite(); - QRect spriteRect(sprite.left, sprite.top, sprite.width, sprite.height); - if (spriteRect == vRect) { - found = true; - } else if (spriteRect.intersects(vRect)) { - common::logError(kErrorSpritesIntersect, module_.filepath()) << "sprite '" << logFullName(variable.name) << "' intersects with '" << logFullName(var.name) << "'"; - return false; - } - } - if (!found) { - sprites_.push_back(variable); - } - } else if (value.type().tag == Tag::Struct) { - auto fields = variable.value.Fields(); - if (!fields) { - return false; - } - - for (auto field : *fields) { - if (!collector(field.variable)) { - return false; - } - } - } - return true; - }; - return module_.enumVariables(collector); -} - -QImage SpriteGenerator::generateSprite(int scale) { - auto convert = [scale](int value) -> int { return structure::data::pxAdjust(value, scale); }; - QImage result(convert(sprite2x_.width() / 2), convert(sprite2x_.height() / 2), sprite2x_.format()); - { - QPainter p(&result); - p.setCompositionMode(QPainter::CompositionMode_Source); - p.fillRect(0, 0, result.width(), result.height(), QColor(0, 0, 0, 0)); - for (auto variable : sprites_) { - auto sprite = variable.value.Sprite(); - auto copy = sprite2x_.copy(sprite.left * 2, sprite.top * 2, sprite.width * 2, sprite.height * 2); - copy = copy.scaled(convert(sprite.width), convert(sprite.height), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - p.drawImage(convert(sprite.left), convert(sprite.top), copy); - } - } - return result; -} - -} // namespace style -} // namespace codegen diff --git a/Telegram/SourceFiles/codegen/style/sprite_generator.h b/Telegram/SourceFiles/codegen/style/sprite_generator.h deleted file mode 100644 index fc1458db3..000000000 --- a/Telegram/SourceFiles/codegen/style/sprite_generator.h +++ /dev/null @@ -1,57 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -It is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the copyright holders give permission -to link the code of portions of this program with the OpenSSL library. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org -*/ -#pragma once - -#include -#include -#include -#include -#include "codegen/style/structure_types.h" - -namespace codegen { -namespace style { -namespace structure { -class Module; -} // namespace structure - -class SpriteGenerator { -public: - SpriteGenerator(const structure::Module &module, bool forceReGenerate); - SpriteGenerator(const SpriteGenerator &other) = delete; - SpriteGenerator &operator=(const SpriteGenerator &other) = delete; - - bool writeSprites(); - -private: - - bool collectSprites(); - QImage generateSprite(int scale); // scale = 5 for 125% and 6 for 150%. - - const structure::Module &module_; - bool forceReGenerate_; - QString basePath_; - QImage sprite2x_; - QList sprites_; - -}; - -} // namespace style -} // namespace codegen diff --git a/Telegram/SourceFiles/codegen/style/structure_types.cpp b/Telegram/SourceFiles/codegen/style/structure_types.cpp index cec0f0c48..9c3a4ae54 100644 --- a/Telegram/SourceFiles/codegen/style/structure_types.cpp +++ b/Telegram/SourceFiles/codegen/style/structure_types.cpp @@ -69,17 +69,6 @@ struct Value::DataTypes { }; - class TSprite : public DataBase { - public: - TSprite(data::sprite value) : value_(value) { - } - data::sprite Sprite() const override { return value_; } - - private: - data::sprite value_; - - }; - class TSize : public DataBase { public: TSize(data::size value) : value_(value) { @@ -154,9 +143,6 @@ Value::Value() : Value(TypeTag::Invalid, std::make_shared()) { Value::Value(data::point value) : Value(TypeTag::Point, std::make_shared(value)) { } -Value::Value(data::sprite value) : Value(TypeTag::Sprite, std::make_shared(value)) { -} - Value::Value(data::size value) : Value(TypeTag::Size, std::make_shared(value)) { } @@ -193,8 +179,6 @@ Value::Value(TypeTag type, int value) : Value(type, std::make_shared(value)) { if (type_.tag != TypeTag::String && - type_.tag != TypeTag::Transition && - type_.tag != TypeTag::Cursor && type_.tag != TypeTag::Align) { type_.tag = TypeTag::Invalid; data_ = std::make_shared(); @@ -210,10 +194,7 @@ Value::Value(Type type, Qt::Initialization) : type_(type) { case TypeTag::String: data_ = std::make_shared(""); break; case TypeTag::Color: data_ = std::make_shared(data::color { 0, 0, 0, 255 }); break; case TypeTag::Point: data_ = std::make_shared(data::point { 0, 0 }); break; - case TypeTag::Sprite: data_ = std::make_shared(data::sprite { 0, 0, 0, 0 }); break; case TypeTag::Size: data_ = std::make_shared(data::size { 0, 0 }); break; - case TypeTag::Transition: data_ = std::make_shared("linear"); break; - case TypeTag::Cursor: data_ = std::make_shared("default"); break; case TypeTag::Align: data_ = std::make_shared("topleft"); break; case TypeTag::Margins: data_ = std::make_shared(data::margins { 0, 0, 0, 0 }); break; case TypeTag::Font: data_ = std::make_shared(data::font { "", 13, 0 }); break; diff --git a/Telegram/SourceFiles/codegen/style/structure_types.h b/Telegram/SourceFiles/codegen/style/structure_types.h index 848acb32a..76f83d8c7 100644 --- a/Telegram/SourceFiles/codegen/style/structure_types.h +++ b/Telegram/SourceFiles/codegen/style/structure_types.h @@ -47,10 +47,7 @@ enum class TypeTag { String, Color, Point, - Sprite, Size, - Transition, - Cursor, Align, Margins, Font, @@ -86,16 +83,13 @@ struct point { int x, y; }; -struct sprite { - int left, top, width, height; -}; - struct size { int width, height; }; struct color { uchar red, green, blue, alpha; + QString fallback; }; struct margins { @@ -127,7 +121,6 @@ class Value { public: Value(); Value(data::point value); - Value(data::sprite value); Value(data::size value); Value(data::color value); Value(data::margins value); @@ -141,7 +134,7 @@ public: // Can be int / pixels. Value(TypeTag type, int value); - // Can be string / transition / cursor / align. + // Can be string / align. Value(TypeTag type, std::string value); // Default constructed value (uninitialized). @@ -152,7 +145,6 @@ public: double Double() const { return data_->Double(); } std::string String() const { return data_->String(); } data::point Point() const { return data_->Point(); } - data::sprite Sprite() const { return data_->Sprite(); }; data::size Size() const { return data_->Size(); }; data::color Color() const { return data_->Color(); }; data::margins Margins() const { return data_->Margins(); }; @@ -182,7 +174,6 @@ private: virtual double Double() const { return 0.; } virtual std::string String() const { return std::string(); } virtual data::point Point() const { return {}; }; - virtual data::sprite Sprite() const { return {}; }; virtual data::size Size() const { return {}; }; virtual data::color Color() const { return {}; }; virtual data::margins Margins() const { return {}; }; diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index a9ef7f033..a9323ae08 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -120,7 +120,6 @@ enum { AnimationInMemory = 10 * 1024 * 1024, // 10 Mb gif and mp4 animations held in memory while playing - MediaViewImageSizeLimit = 100 * 1024 * 1024, // show up to 100mb jpg/png/gif docs in app MaxZoomLevel = 7, // x8 ZoomToScreenLevel = 1024, // just constant @@ -168,8 +167,6 @@ enum { ChoosePeerByDragTimeout = 1000, // 1 second mouse not moved to choose dialog when dragging a file ReloadChannelMembersTimeout = 1000, // 1 second wait before reload members in channel after adding - - PinnedMessageTextLimit = 16, }; inline bool isNotificationsUser(uint64 id) { @@ -325,6 +322,16 @@ inline QString cApiAppVersion() { return QString::number(AppVersion); } +constexpr str_const AppLinksDomain = "t.me"; + +inline QString CreateInternalLink(const QString &query) { + return str_const_toString(AppLinksDomain) + '/' + query; +} + +inline QString CreateInternalLinkHttps(const QString &query) { + return qsl("https://") + CreateInternalLink(query); +} + extern QString gKeyFile; inline const QString &cDataFile() { if (!gKeyFile.isEmpty()) return gKeyFile; @@ -341,8 +348,6 @@ static const char *DefaultCountry = "US"; static const char *DefaultLanguage = "en"; enum { - DefaultChatBackground = 21, - DialogsFirstLoad = 20, // first dialogs part size requested DialogsPerPage = 500, // next dialogs part size @@ -353,8 +358,6 @@ enum { DownloadPartSize = 64 * 1024, // 64kb for photo DocumentDownloadPartSize = 128 * 1024, // 128kb for document - MaxUploadPhotoSize = 256 * 1024 * 1024, // 256mb photos max - MaxUploadDocumentSize = 1500 * 1024 * 1024, // 1500mb documents max UseBigFilesFrom = 10 * 1024 * 1024, // mtp big files methods used for files greater than 10mb MaxFileQueries = 16, // max 16 file parts downloaded at the same time MaxWebFileQueries = 8, // max 8 http[s] files downloaded at the same time @@ -396,23 +399,24 @@ inline const QRegularExpression &cRussianLetters() { return regexp; } -inline QStringList cImgExtensions() { - static QStringList imgExtensions; - if (imgExtensions.isEmpty()) { - imgExtensions.reserve(4); - imgExtensions.push_back(qsl(".jpg")); - imgExtensions.push_back(qsl(".jpeg")); - imgExtensions.push_back(qsl(".png")); - imgExtensions.push_back(qsl(".gif")); +inline const QStringList &cImgExtensions() { + static QStringList result; + if (result.isEmpty()) { + result.reserve(4); + result.push_back(qsl(".jpg")); + result.push_back(qsl(".jpeg")); + result.push_back(qsl(".png")); + result.push_back(qsl(".gif")); } - return imgExtensions; + return result; } -inline QStringList cPhotoExtensions() { - static QStringList photoExtensions; - if (photoExtensions.isEmpty()) { - photoExtensions.push_back(qsl(".jpg")); - photoExtensions.push_back(qsl(".jpeg")); +inline const QStringList &cExtensionsForCompress() { + static QStringList result; + if (result.isEmpty()) { + result.push_back(qsl(".jpg")); + result.push_back(qsl(".jpeg")); + result.push_back(qsl(".png")); } - return photoExtensions; + return result; } diff --git a/Telegram/SourceFiles/core/basic_types.h b/Telegram/SourceFiles/core/basic_types.h index 9487daec9..1f56b04f9 100644 --- a/Telegram/SourceFiles/core/basic_types.h +++ b/Telegram/SourceFiles/core/basic_types.h @@ -26,6 +26,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include +#include "core/build_config.h" #include "core/stl_subset.h" #include "core/ordered_set.h" @@ -65,9 +66,3 @@ friend Q_DECL_CONSTEXPR QFlags operator|(Flags::enum_type f1, Q_DECLARE_FRIEND_INCOMPATIBLE_FLAGS(Flags) #endif // OS_MAC_OLD - -// using for_const instead of plain range-based for loop to ensure usage of const_iterator -// it is important for the copy-on-write Qt containers -// if you have "QVector v" then "for (T * const p : v)" will still call QVector::detach(), -// while "for_const (T *p, v)" won't and "for_const (T *&p, v)" won't compile -#define for_const(range_declaration, range_expression) for (range_declaration : std_::as_const(range_expression)) diff --git a/Telegram/SourceFiles/core/build_config.h b/Telegram/SourceFiles/core/build_config.h new file mode 100644 index 000000000..ff80136ea --- /dev/null +++ b/Telegram/SourceFiles/core/build_config.h @@ -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 +*/ +#pragma once + +#include + +// thanks Chromium + +#if defined(__APPLE__) +#define OS_MAC 1 +#elif defined(__linux__) // __APPLE__ +#define OS_LINUX 1 +#elif defined(_WIN32) // __APPLE__ || __linux__ +#define OS_WIN 1 +#else // __APPLE__ || __linux__ || _WIN32 +#error Please add support for your platform in core/build_config.h +#endif // else for __APPLE__ || __linux__ || _WIN32 + +// For access to standard POSIXish features, use OS_POSIX instead of a +// more specific macro. +#if defined(OS_MAC) || defined(OS_LINUX) +#define OS_POSIX 1 +#endif // OS_MAC || OS_LINUX + +// Compiler detection. +#if defined(__clang__) +#define COMPILER_CLANG 1 +#elif defined(__GNUC__) // __clang__ +#define COMPILER_GCC 1 +#elif defined(_MSC_VER) // __clang__ || __GNUC__ +#define COMPILER_MSVC 1 +#else // _MSC_VER || __clang__ || __GNUC__ +#error Please add support for your compiler in core/build_config.h +#endif // else for _MSC_VER || __clang__ || __GNUC__ + +// Processor architecture detection. +#if defined(_M_X64) || defined(__x86_64__) +#define ARCH_CPU_X86_FAMILY 1 +#define ARCH_CPU_X86_64 1 +#define ARCH_CPU_64_BITS 1 +#elif defined(_M_IX86) || defined(__i386__) +#define ARCH_CPU_X86_FAMILY 1 +#define ARCH_CPU_X86 1 +#define ARCH_CPU_32_BITS 1 +#else +#error Please add support for your architecture in core/build_config.h +#endif + +#if defined(COMPILER_GCC) || defined(COMPILER_CLANG) +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define WARN_UNUSED_RESULT +#endif + +#if defined(__GNUC__) +#define FORCE_INLINE inline __attribute__((always_inline)) +#elif defined(_MSC_VER) +#define FORCE_INLINE __forceinline +#else +#define FORCE_INLINE inline +#endif + +#include +static_assert(CHAR_BIT == 8, "Not supported char size."); diff --git a/Telegram/SourceFiles/core/click_handler.h b/Telegram/SourceFiles/core/click_handler.h index 7fb8796a4..867b32f1c 100644 --- a/Telegram/SourceFiles/core/click_handler.h +++ b/Telegram/SourceFiles/core/click_handler.h @@ -178,3 +178,18 @@ protected: virtual void onClickImpl() const = 0; }; + +class LambdaClickHandler : public ClickHandler { +public: + LambdaClickHandler(base::lambda &&handler) : _handler(std_::move(handler)) { + } + void onClick(Qt::MouseButton button) const override final { + if (button == Qt::LeftButton && _handler) { + _handler(); + } + } + +private: + base::lambda _handler; + +}; diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index 53ea0b47d..a273d95f4 100644 --- a/Telegram/SourceFiles/core/click_handler_types.cpp +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -27,7 +27,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "core/qthelp_regex.h" #include "core/qthelp_url.h" #include "localstorage.h" -#include "ui/popupmenu.h" +#include "ui/widgets/tooltip.h" QString UrlClickHandler::copyToClipboardContextItemText() const { return lang(isEmail() ? lng_context_copy_email : lng_context_copy_link); @@ -40,8 +40,9 @@ QString tryConvertUrlToLocal(QString url) { using namespace qthelp; auto matchOptions = RegExOption::CaseInsensitive; - if (auto telegramMeMatch = regex_match(qsl("https?://telegram\\.me/(.+)$"), url, matchOptions)) { - auto query = telegramMeMatch->capturedRef(1); + auto telegramMeMatch = regex_match(qsl("https?://(www\\.)?(telegram|t)\\.me/(.+)$"), url, matchOptions); + if (telegramMeMatch) { + auto query = telegramMeMatch->capturedRef(3); if (auto joinChatMatch = regex_match(qsl("^joinchat/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"), query, matchOptions)) { return qsl("tg://join?invite=") + url_encode(joinChatMatch->captured(1)); } else if (auto stickerSetMatch = regex_match(qsl("^addstickers/([a-zA-Z0-9\\.\\_]+)(\\?|$)"), query, matchOptions)) { @@ -64,7 +65,7 @@ QString tryConvertUrlToLocal(QString url) { } // namespace void UrlClickHandler::doOpen(QString url) { - PopupTooltip::Hide(); + Ui::Tooltip::Hide(); if (isEmail(url)) { QUrl u(qstr("mailto:") + url); @@ -103,25 +104,34 @@ TextWithEntities UrlClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMod return result; } -void HiddenUrlClickHandler::onClick(Qt::MouseButton button) const { - auto u = tryConvertUrlToLocal(url()); +void HiddenUrlClickHandler::doOpen(QString url) { + auto urlText = tryConvertUrlToLocal(url); - if (u.startsWith(qstr("tg://"))) { - App::openLocalUrl(u); + if (urlText.startsWith(qstr("tg://"))) { + App::openLocalUrl(urlText); } else { - Ui::showLayer(new ConfirmLinkBox(u)); + auto parsedUrl = QUrl::fromUserInput(urlText); + auto displayUrl = parsedUrl.isValid() ? parsedUrl.toDisplayString() : urlText; + Ui::show(Box(lang(lng_open_this_link) + qsl("\n\n") + displayUrl, lang(lng_open_link), [urlText] { + Ui::hideLayer(); + UrlClickHandler::doOpen(urlText); + })); } } void BotGameUrlClickHandler::onClick(Qt::MouseButton button) const { - auto u = tryConvertUrlToLocal(url()); + auto urlText = tryConvertUrlToLocal(url()); - if (u.startsWith(qstr("tg://"))) { - App::openLocalUrl(u); + if (urlText.startsWith(qstr("tg://"))) { + App::openLocalUrl(urlText); } else if (!_bot || _bot->isVerified() || Local::isBotTrusted(_bot)) { - doOpen(u); + doOpen(urlText); } else { - Ui::showLayer(new ConfirmBotGameBox(_bot, u)); + Ui::show(Box(lng_allow_bot_pass(lt_bot_name, _bot->name), lang(lng_allow_bot), [bot = _bot, urlText] { + Ui::hideLayer(); + Local::makeBotTrusted(bot); + UrlClickHandler::doOpen(urlText); + })); } } diff --git a/Telegram/SourceFiles/core/click_handler_types.h b/Telegram/SourceFiles/core/click_handler_types.h index d8231da31..f68de6bff 100644 --- a/Telegram/SourceFiles/core/click_handler_types.h +++ b/Telegram/SourceFiles/core/click_handler_types.h @@ -115,7 +115,13 @@ class HiddenUrlClickHandler : public UrlClickHandler { public: HiddenUrlClickHandler(QString url) : UrlClickHandler(url, false) { } - void onClick(Qt::MouseButton button) const override; + + static void doOpen(QString url); + void onClick(Qt::MouseButton button) const override { + if (button == Qt::LeftButton || button == Qt::MiddleButton) { + doOpen(url()); + } + } QString getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const override; TextWithEntities getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override; diff --git a/Telegram/SourceFiles/core/lambda.h b/Telegram/SourceFiles/core/lambda.h new file mode 100644 index 000000000..1933a3da3 --- /dev/null +++ b/Telegram/SourceFiles/core/lambda.h @@ -0,0 +1,587 @@ +/* +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/stl_subset.h" + +namespace base { +namespace internal { + + template + struct lambda_wrap_helper_base { + using construct_copy_other_type = void(*)(void *, const void *); // dst, src + using construct_move_other_type = void(*)(void *, void *); // dst, src + using call_type = Return(*)(const void *, Args...); + using destruct_type = void(*)(const void *); + + lambda_wrap_helper_base() = delete; + lambda_wrap_helper_base(const lambda_wrap_helper_base &other) = delete; + lambda_wrap_helper_base &operator=(const lambda_wrap_helper_base &other) = delete; + + lambda_wrap_helper_base( + construct_copy_other_type construct_copy_other, + construct_move_other_type construct_move_other, + call_type call, + destruct_type destruct) + : construct_copy_other(construct_copy_other) + , construct_move_other(construct_move_other) + , call(call) + , destruct(destruct) { + } + + const construct_copy_other_type construct_copy_other; + const construct_move_other_type construct_move_other; + const call_type call; + const destruct_type destruct; + + static constexpr size_t kFullStorageSize = 24U + sizeof(void*); + static constexpr size_t kStorageSize = kFullStorageSize - sizeof(void*); + using alignment = uint64; + + template + using IsLarge = std_::integral_constant) <= kStorageSize)>; + + protected: + static void bad_construct_copy(void *lambda, const void *source) { + t_assert(!"base::lambda bad_construct_copy() called!"); + } + + }; + + template + struct lambda_wrap_empty : public lambda_wrap_helper_base { + static void construct_copy_other_method(void *lambda, const void *source) { + } + static void construct_move_other_method(void *lambda, void *source) { + } + static Return call_method(const void *lambda, Args... args) { + t_assert(!"base::lambda empty call_method() called!"); + return Return(); + } + static void destruct_method(const void *lambda) { + } + lambda_wrap_empty() : lambda_wrap_helper_base( + &lambda_wrap_empty::construct_copy_other_method, + &lambda_wrap_empty::construct_move_other_method, + &lambda_wrap_empty::call_method, + &lambda_wrap_empty::destruct_method) { + } + + static const lambda_wrap_empty instance; + + }; + + template + const lambda_wrap_empty lambda_wrap_empty::instance = {}; + + template struct lambda_wrap_helper_move_impl; + + template + struct lambda_wrap_helper_move_impl : public lambda_wrap_helper_base { + using JustLambda = std_::decay_simple_t; + using LambdaPtr = std_::unique_ptr; + using Parent = lambda_wrap_helper_base; + static void construct_move_other_method(void *lambda, void *source) { + auto source_lambda = static_cast(source); + new (lambda) LambdaPtr(std_::move(*source_lambda)); + } + static void construct_move_lambda_method(void *lambda, void *source) { + auto source_lambda = static_cast(source); + new (lambda) LambdaPtr(std_::make_unique(static_cast(*source_lambda))); + } + static Return call_method(const void *lambda, Args... args) { + return (**static_cast(lambda))(std_::forward(args)...); + } + static void destruct_method(const void *lambda) { + static_cast(lambda)->~LambdaPtr(); + } + lambda_wrap_helper_move_impl() : Parent( + &Parent::bad_construct_copy, + &lambda_wrap_helper_move_impl::construct_move_other_method, + &lambda_wrap_helper_move_impl::call_method, + &lambda_wrap_helper_move_impl::destruct_method) { + } + + protected: + lambda_wrap_helper_move_impl( + typename Parent::construct_copy_other_type construct_copy_other + ) : Parent( + construct_copy_other, + &lambda_wrap_helper_move_impl::construct_move_other_method, + &lambda_wrap_helper_move_impl::call_method, + &lambda_wrap_helper_move_impl::destruct_method) { + } + + }; + + template + struct lambda_wrap_helper_move_impl : public lambda_wrap_helper_base { + using JustLambda = std_::decay_simple_t; + using Parent = lambda_wrap_helper_base; + static void construct_move_other_method(void *lambda, void *source) { + auto source_lambda = static_cast(source); + new (lambda) JustLambda(static_cast(*source_lambda)); + } + static void construct_move_lambda_method(void *lambda, void *source) { + static_assert(alignof(JustLambda) <= alignof(typename Parent::alignment), "Bad lambda alignment."); + auto space = sizeof(JustLambda); + auto aligned = std_::align(alignof(JustLambda), space, lambda, space); + t_assert(aligned == lambda); + auto source_lambda = static_cast(source); + new (lambda) JustLambda(static_cast(*source_lambda)); + } + static Return call_method(const void *lambda, Args... args) { + return (*static_cast(lambda))(std_::forward(args)...); + } + static void destruct_method(const void *lambda) { + static_cast(lambda)->~JustLambda(); + } + lambda_wrap_helper_move_impl() : Parent( + &Parent::bad_construct_copy, + &lambda_wrap_helper_move_impl::construct_move_other_method, + &lambda_wrap_helper_move_impl::call_method, + &lambda_wrap_helper_move_impl::destruct_method) { + } + + protected: + lambda_wrap_helper_move_impl( + typename Parent::construct_copy_other_type construct_copy_other + ) : Parent( + construct_copy_other, + &lambda_wrap_helper_move_impl::construct_move_other_method, + &lambda_wrap_helper_move_impl::call_method, + &lambda_wrap_helper_move_impl::destruct_method) { + } + + }; + + template + struct lambda_wrap_helper_move : public lambda_wrap_helper_move_impl::template IsLarge + , Return, Args...> { + static const lambda_wrap_helper_move instance; + }; + + template + const lambda_wrap_helper_move lambda_wrap_helper_move::instance = {}; + + template struct lambda_wrap_helper_copy_impl; + + template + struct lambda_wrap_helper_copy_impl : public lambda_wrap_helper_move_impl { + using JustLambda = std_::decay_simple_t; + using LambdaPtr = std_::unique_ptr; + using Parent = lambda_wrap_helper_move_impl; + static void construct_copy_other_method(void *lambda, const void *source) { + auto source_lambda = static_cast(source); + new (lambda) LambdaPtr(std_::make_unique(*source_lambda->get())); + } + static void construct_copy_lambda_method(void *lambda, const void *source) { + auto source_lambda = static_cast(source); + new (lambda) LambdaPtr(std_::make_unique(static_cast(*source_lambda))); + } + lambda_wrap_helper_copy_impl() : Parent(&lambda_wrap_helper_copy_impl::construct_copy_other_method) { + } + + }; + + template + struct lambda_wrap_helper_copy_impl : public lambda_wrap_helper_move_impl { + using JustLambda = std_::decay_simple_t; + using Parent = lambda_wrap_helper_move_impl; + static void construct_copy_other_method(void *lambda, const void *source) { + auto source_lambda = static_cast(source); + new (lambda) JustLambda(static_cast(*source_lambda)); + } + static void construct_copy_lambda_method(void *lambda, const void *source) { + static_assert(alignof(JustLambda) <= alignof(typename Parent::alignment), "Bad lambda alignment."); + auto space = sizeof(JustLambda); + auto aligned = std_::align(alignof(JustLambda), space, lambda, space); + t_assert(aligned == lambda); + auto source_lambda = static_cast(source); + new (lambda) JustLambda(static_cast(*source_lambda)); + } + lambda_wrap_helper_copy_impl() : Parent(&lambda_wrap_helper_copy_impl::construct_copy_other_method) { + } + + }; + + template + struct lambda_wrap_helper_copy : public lambda_wrap_helper_copy_impl::template IsLarge + , Return, Args...> { + static const lambda_wrap_helper_copy instance; + }; + + template + const lambda_wrap_helper_copy lambda_wrap_helper_copy::instance = {}; + +} // namespace internal + +template class lambda; +template class lambda_copy; + +template +class lambda { + using BaseHelper = internal::lambda_wrap_helper_base; + using EmptyHelper = internal::lambda_wrap_empty; + + template + using IsUnique = std_::is_same>; + template + using IsWrap = std_::is_same, std_::decay_simple_t>; + template + using IsOther = std_::enable_if_t::value && !IsWrap::value>; + template + using IsRvalue = std_::enable_if_t::value>; + +public: + using return_type = Return; + + lambda() : helper_(&EmptyHelper::instance) { + } + + lambda(const lambda &other) = delete; + lambda &operator=(const lambda &other) = delete; + + lambda(lambda &&other) : helper_(other.helper_) { + helper_->construct_move_other(storage_, other.storage_); + } + lambda &operator=(lambda &&other) { + auto temp = std_::move(other); + helper_->destruct(storage_); + helper_ = temp.helper_; + helper_->construct_move_other(storage_, temp.storage_); + return *this; + } + + void swap(lambda &other) { + if (this != &other) std_::swap_moveable(*this, other); + } + + template , typename = IsRvalue> + lambda(Lambda &&other) : helper_(&internal::lambda_wrap_helper_move::instance) { + internal::lambda_wrap_helper_move::construct_move_lambda_method(storage_, &other); + } + + template , typename = IsRvalue> + lambda &operator=(Lambda &&other) { + auto temp = std_::move(other); + helper_->destruct(storage_); + helper_ = &internal::lambda_wrap_helper_move::instance; + internal::lambda_wrap_helper_move::construct_move_lambda_method(storage_, &temp); + return *this; + } + + inline Return operator()(Args... args) const { + return helper_->call(storage_, std_::forward(args)...); + } + + explicit operator bool() const { + return (helper_ != &EmptyHelper::instance); + } + + ~lambda() { + helper_->destruct(storage_); + } + +protected: + struct Private { + }; + lambda(const BaseHelper *helper, const Private &) : helper_(helper) { + } + + using alignment = typename BaseHelper::alignment; + static_assert(BaseHelper::kStorageSize % sizeof(alignment) == 0, "Bad storage size."); + alignas(typename BaseHelper::alignment) alignment storage_[BaseHelper::kStorageSize / sizeof(alignment)]; + const BaseHelper *helper_; + +}; + +template +class lambda_copy : public lambda { + using BaseHelper = internal::lambda_wrap_helper_base; + using Parent = lambda; + + template + using IsOther = std_::enable_if_t>::value>; + template + using IsRvalue = std_::enable_if_t::value>; + template + using IsNotRvalue = std_::enable_if_t::value>; + +public: + lambda_copy() = default; + + lambda_copy(const lambda_copy &other) : Parent(other.helper_, typename Parent::Private()) { + this->helper_->construct_copy_other(this->storage_, other.storage_); + } + lambda_copy &operator=(const lambda_copy &other) { + auto temp = other; + temp.swap(*this); + return *this; + } + + lambda_copy(lambda_copy &&other) = default; + lambda_copy &operator=(lambda_copy &&other) = default; + + void swap(lambda_copy &other) { + if (this != &other) std_::swap_moveable(*this, other); + } + + lambda_copy clone() const { + return *this; + } + + template > + lambda_copy(const Lambda &other) : Parent(&internal::lambda_wrap_helper_copy::instance, typename Parent::Private()) { + internal::lambda_wrap_helper_copy::construct_copy_lambda_method(this->storage_, &other); + } + + template , typename = IsRvalue> + lambda_copy(Lambda &&other) : Parent(&internal::lambda_wrap_helper_copy::instance, typename Parent::Private()) { + internal::lambda_wrap_helper_copy::construct_move_lambda_method(this->storage_, &other); + } + + template > + lambda_copy &operator=(const Lambda &other) { + auto temp = other; + this->helper_->destruct(this->storage_); + this->helper_ = &internal::lambda_wrap_helper_copy::instance; + internal::lambda_wrap_helper_copy::construct_copy_lambda_method(this->storage_, &other); + return *this; + } + + template , typename = IsRvalue> + lambda_copy &operator=(Lambda &&other) { + auto temp = std_::move(other); + this->helper_->destruct(this->storage_); + this->helper_ = &internal::lambda_wrap_helper_copy::instance; + internal::lambda_wrap_helper_copy::construct_move_lambda_method(this->storage_, &other); + return *this; + } + +}; + +// Get lambda type from a lambda template parameter. + +namespace internal { + +template +struct lambda_type_resolver; + +template +struct lambda_type_resolver { + using type = lambda; + static constexpr auto is_mutable = false; +}; + +template +struct lambda_type_resolver { + using type = lambda; + static constexpr auto is_mutable = true; +}; + +template +struct lambda_type_helper { + using type = typename lambda_type_resolver::type; +}; + +} // namespace internal + +template +using lambda_type = typename internal::lambda_type_helper::type; + +// Guard lambda call by one or many QObject* weak pointers. + +namespace internal { + +template +class lambda_guard_creator; + +template +class lambda_guard_data { +public: + using return_type = typename lambda_type::return_type; + + template + inline lambda_guard_data(PointersAndLambda&&... qobjectsAndLambda) : _lambda(init(_pointers, std_::forward(qobjectsAndLambda)...)) { + } + + inline lambda_guard_data(const lambda_guard_data &other) : _lambda(other._lambda) { + for (auto i = 0; i != N; ++i) { + _pointers[i] = other._pointers[i]; + } + } + + template + inline return_type operator()(Args&&... args) const { + for (int i = 0; i != N; ++i) { + if (!_pointers[i]) { + return return_type(); + } + } + return _lambda(std_::forward(args)...); + } + +private: + template + Lambda init(QPointer *pointers, QObject *qobject, PointersAndLambda&&... qobjectsAndLambda) { + *pointers = qobject; + return init(++pointers, std_::forward(qobjectsAndLambda)...); + } + Lambda init(QPointer *pointers, Lambda &&lambda) { + return std_::move(lambda); + } + + QPointer _pointers[N]; + Lambda _lambda; + +}; + +template +class lambda_guard { +public: + using return_type = typename lambda_type::return_type; + + template + inline lambda_guard(PointersAndLambda&&... qobjectsAndLambda) : _data(std_::make_unique>(std_::forward(qobjectsAndLambda)...)) { + static_assert(sizeof...(PointersAndLambda) == N + 1, "Wrong argument count!"); + } + + inline lambda_guard(const lambda_guard &&other) : _data(std_::move(other._data)) { + } + + inline lambda_guard(lambda_guard &&other) : _data(std_::move(other._data)) { + } + + inline lambda_guard &operator=(const lambda_guard &&other) { + _data = std_::move(other._data); + return *this; + } + + inline lambda_guard &operator=(lambda_guard &&other) { + _data = std_::move(other._data); + return *this; + } + + template + inline return_type operator()(Args&&... args) const { + return (*_data)(std_::forward(args)...); + } + + bool isNull() const { + return !_data; + } + + lambda_guard clone() const { + return lambda_guard(*this); + } + +private: + inline lambda_guard(const lambda_guard &other) : _data(std_::make_unique>(static_cast &>(*other._data))) { + } + + mutable std_::unique_ptr> _data; + +}; + +template +struct lambda_guard_type; + +template +struct lambda_guard_type { + using type = typename lambda_guard_type::type; +}; + +template +struct lambda_guard_type { + using type = lambda_guard; +}; + +template +struct lambda_guard_type_helper { + static constexpr int N = sizeof...(PointersAndLambda); + using type = typename lambda_guard_type::type; +}; + +template +using lambda_guard_t = typename lambda_guard_type_helper::type; + +template +struct lambda_type_helper> { + using type = typename lambda_type_helper::type; +}; + +} // namespace internal + +template +inline internal::lambda_guard_t lambda_guarded(PointersAndLambda&&... qobjectsAndLambda) { + static_assert(sizeof...(PointersAndLambda) > 0, "Lambda should be passed here."); + return internal::lambda_guard_t(std_::forward(qobjectsAndLambda)...); +} + +// Pass lambda instead of a Qt void() slot. + +class lambda_slot_wrap : public QObject { + Q_OBJECT + +public: + lambda_slot_wrap(QObject *parent, lambda &&lambda) : QObject(parent), _lambda(std_::move(lambda)) { + } + +public slots: + void action() { + _lambda(); + } + +private: + lambda _lambda; + +}; + +inline lambda_slot_wrap *lambda_slot(QObject *parent, lambda &&lambda) { + return new lambda_slot_wrap(parent, std_::move(lambda)); +} + +class lambda_slot_once_wrap : public QObject { + Q_OBJECT + +public: + lambda_slot_once_wrap(QObject *parent, lambda &&lambda) : QObject(parent), _lambda(std_::move(lambda)) { + } + +public slots : + void action() { + _lambda(); + delete this; + } + +private: + lambda _lambda; + +}; + +inline lambda_slot_once_wrap *lambda_slot_once(QObject *parent, lambda &&lambda) { + return new lambda_slot_once_wrap(parent, std_::move(lambda)); +} + +} // namespace base diff --git a/Telegram/SourceFiles/core/lambda_wrap.h b/Telegram/SourceFiles/core/lambda_wrap.h deleted file mode 100644 index 3e211781d..000000000 --- a/Telegram/SourceFiles/core/lambda_wrap.h +++ /dev/null @@ -1,397 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -It is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the copyright holders give permission -to link the code of portions of this program with the OpenSSL library. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org -*/ -#pragma once - -#include "core/stl_subset.h" - -namespace base { -namespace internal { - -template -struct lambda_wrap_helper_base { - using construct_copy_other_type = void(*)(void *, const void *); // dst, src - using construct_move_other_type = void(*)(void *, void *); // dst, src - using call_type = Return(*)(const void *, Args...); - using destruct_type = void(*)(const void *); - - lambda_wrap_helper_base() = delete; - lambda_wrap_helper_base(const lambda_wrap_helper_base &other) = delete; - lambda_wrap_helper_base &operator=(const lambda_wrap_helper_base &other) = delete; - - lambda_wrap_helper_base( - construct_copy_other_type construct_copy_other, - construct_move_other_type construct_move_other, - call_type call, - destruct_type destruct) - : construct_copy_other(construct_copy_other) - , construct_move_other(construct_move_other) - , call(call) - , destruct(destruct) { - } - - const construct_copy_other_type construct_copy_other; - const construct_move_other_type construct_move_other; - const call_type call; - const destruct_type destruct; - - static constexpr size_t kFullStorageSize = sizeof(void*) + 24U; - static constexpr size_t kStorageSize = kFullStorageSize - sizeof(void*); - - template - using IsLarge = std_::integral_constant) <= kStorageSize)>; - -protected: - static void bad_construct_copy(void *lambda, const void *source) { - t_assert(!"base::lambda bad_construct_copy() called!"); - } - -}; - -template -struct lambda_wrap_empty : public lambda_wrap_helper_base { - static void construct_copy_other_method(void *lambda, const void *source) { - } - static void construct_move_other_method(void *lambda, void *source) { - } - static Return call_method(const void *lambda, Args... args) { - t_assert(!"base::lambda empty call_method() called!"); - return Return(); - } - static void destruct_method(const void *lambda) { - } - lambda_wrap_empty() : lambda_wrap_helper_base( - &lambda_wrap_empty::construct_copy_other_method, - &lambda_wrap_empty::construct_move_other_method, - &lambda_wrap_empty::call_method, - &lambda_wrap_empty::destruct_method) { - } - - static const lambda_wrap_empty instance; - -}; - -template -const lambda_wrap_empty lambda_wrap_empty::instance = {}; - -template struct lambda_wrap_helper_move_impl; - -// -// Disable large lambda support. -// If you really need it, just store data in some std_::unique_ptr. -// -//template -//struct lambda_wrap_helper_move_impl : public lambda_wrap_helper_base { -// using JustLambda = std_::decay_simple_t; -// using LambdaPtr = std_::unique_ptr; -// using Parent = lambda_wrap_helper_base; -// static void construct_move_other_method(void *lambda, void *source) { -// auto source_lambda = static_cast(source); -// new (lambda) LambdaPtr(std_::move(*source_lambda)); -// } -// static void construct_move_lambda_method(void *lambda, void *source) { -// auto source_lambda = static_cast(source); -// new (lambda) LambdaPtr(std_::make_unique(static_cast(*source_lambda))); -// } -// static Return call_method(const void *lambda, Args... args) { -// return (**static_cast(lambda))(std_::forward(args)...); -// } -// static void destruct_method(const void *lambda) { -// static_cast(lambda)->~LambdaPtr(); -// } -// lambda_wrap_helper_move_impl() : Parent( -// &Parent::bad_construct_copy, -// &lambda_wrap_helper_move_impl::construct_move_other_method, -// &lambda_wrap_helper_move_impl::call_method, -// &lambda_wrap_helper_move_impl::destruct_method) { -// } -// -//protected: -// lambda_wrap_helper_move_impl( -// typename Parent::construct_copy_other_type construct_copy_other -// ) : Parent( -// construct_copy_other, -// &lambda_wrap_helper_move_impl::construct_move_other_method, -// &lambda_wrap_helper_move_impl::call_method, -// &lambda_wrap_helper_move_impl::destruct_method) { -// } -// -//}; - -template -struct lambda_wrap_helper_move_impl : public lambda_wrap_helper_base { - using JustLambda = std_::decay_simple_t; - using Parent = lambda_wrap_helper_base; - static void construct_move_other_method(void *lambda, void *source) { - auto source_lambda = static_cast(source); - new (lambda) JustLambda(static_cast(*source_lambda)); - } - static void construct_move_lambda_method(void *lambda, void *source) { - static_assert(alignof(JustLambda) <= alignof(void*), "Bad lambda alignment."); - auto space = sizeof(JustLambda); - auto aligned = std_::align(alignof(JustLambda), space, lambda, space); - t_assert(aligned == lambda); - auto source_lambda = static_cast(source); - new (lambda) JustLambda(static_cast(*source_lambda)); - } - static Return call_method(const void *lambda, Args... args) { - return (*static_cast(lambda))(std_::forward(args)...); - } - static void destruct_method(const void *lambda) { - static_cast(lambda)->~JustLambda(); - } - lambda_wrap_helper_move_impl() : Parent( - &Parent::bad_construct_copy, - &lambda_wrap_helper_move_impl::construct_move_other_method, - &lambda_wrap_helper_move_impl::call_method, - &lambda_wrap_helper_move_impl::destruct_method) { - } - -protected: - lambda_wrap_helper_move_impl( - typename Parent::construct_copy_other_type construct_copy_other - ) : Parent( - construct_copy_other, - &lambda_wrap_helper_move_impl::construct_move_other_method, - &lambda_wrap_helper_move_impl::call_method, - &lambda_wrap_helper_move_impl::destruct_method) { - } - -}; - -template -struct lambda_wrap_helper_move : public lambda_wrap_helper_move_impl::template IsLarge - , Return, Args...> { - static const lambda_wrap_helper_move instance; -}; - -template -const lambda_wrap_helper_move lambda_wrap_helper_move::instance = {}; - -template struct lambda_wrap_helper_copy_impl; - -// -// Disable large lambda support. -// If you really need it, just store data in some QSharedPointer. -// -//template -//struct lambda_wrap_helper_copy_impl : public lambda_wrap_helper_move_impl { -// using JustLambda = std_::decay_simple_t; -// using LambdaPtr = std_::unique_ptr; -// using Parent = lambda_wrap_helper_move_impl; -// static void construct_copy_other_method(void *lambda, const void *source) { -// auto source_lambda = static_cast(source); -// new (lambda) LambdaPtr(std_::make_unique(*source_lambda->get())); -// } -// static void construct_copy_lambda_method(void *lambda, const void *source) { -// auto source_lambda = static_cast(source); -// new (lambda) LambdaPtr(std_::make_unique(static_cast(*source_lambda))); -// } -// lambda_wrap_helper_copy_impl() : Parent(&lambda_wrap_helper_copy_impl::construct_copy_other_method) { -// } -// -//}; - -template -struct lambda_wrap_helper_copy_impl : public lambda_wrap_helper_move_impl { - using JustLambda = std_::decay_simple_t; - using Parent = lambda_wrap_helper_move_impl; - static void construct_copy_other_method(void *lambda, const void *source) { - auto source_lambda = static_cast(source); - new (lambda) JustLambda(static_cast(*source_lambda)); - } - static void construct_copy_lambda_method(void *lambda, const void *source) { - static_assert(alignof(JustLambda) <= alignof(void*), "Bad lambda alignment."); - auto space = sizeof(JustLambda); - auto aligned = std_::align(alignof(JustLambda), space, lambda, space); - t_assert(aligned == lambda); - auto source_lambda = static_cast(source); - new (lambda) JustLambda(static_cast(*source_lambda)); - } - lambda_wrap_helper_copy_impl() : Parent(&lambda_wrap_helper_copy_impl::construct_copy_other_method) { - } - -}; - -template -struct lambda_wrap_helper_copy : public lambda_wrap_helper_copy_impl::template IsLarge - , Return, Args...> { - static const lambda_wrap_helper_copy instance; -}; - -template -const lambda_wrap_helper_copy lambda_wrap_helper_copy::instance = {}; - -} // namespace internal - -template class lambda_unique; -template class lambda_wrap; - -template -class lambda_unique { - using BaseHelper = internal::lambda_wrap_helper_base; - using EmptyHelper = internal::lambda_wrap_empty; - - template - using IsUnique = std_::is_same>; - template - using IsWrap = std_::is_same, std_::decay_simple_t>; - template - using IsOther = std_::enable_if_t::value && !IsWrap::value>; - template - using IsRvalue = std_::enable_if_t::value>; - -public: - lambda_unique() : helper_(&EmptyHelper::instance) { - } - - lambda_unique(const lambda_unique &other) = delete; - lambda_unique &operator=(const lambda_unique &other) = delete; - - lambda_unique(lambda_unique &&other) : helper_(other.helper_) { - helper_->construct_move_other(storage_, other.storage_); - } - lambda_unique &operator=(lambda_unique &&other) { - auto temp = std_::move(other); - helper_->destruct(storage_); - helper_ = temp.helper_; - helper_->construct_move_other(storage_, temp.storage_); - return *this; - } - - void swap(lambda_unique &other) { - if (this != &other) { - lambda_unique temp = std_::move(other); - other = std_::move(*this); - *this = std_::move(other); - } - } - - template , typename = IsRvalue> - lambda_unique(Lambda &&other) : helper_(&internal::lambda_wrap_helper_move::instance) { - internal::lambda_wrap_helper_move::construct_move_lambda_method(storage_, &other); - } - - template , typename = IsRvalue> - lambda_unique &operator=(Lambda &&other) { - auto temp = std_::move(other); - helper_->destruct(storage_); - helper_ = &internal::lambda_wrap_helper_move::instance; - internal::lambda_wrap_helper_move::construct_move_lambda_method(storage_, &temp); - return *this; - } - - inline Return operator()(Args... args) const { - return helper_->call(storage_, std_::forward(args)...); - } - - explicit operator bool() const { - return (helper_ != &EmptyHelper::instance); - } - - ~lambda_unique() { - helper_->destruct(storage_); - } - -protected: - struct Private { - }; - lambda_unique(const BaseHelper *helper, const Private &) : helper_(helper) { - } - - const BaseHelper *helper_; - - static_assert(BaseHelper::kStorageSize % sizeof(void*) == 0, "Bad pointer size."); - void *(storage_[BaseHelper::kStorageSize / sizeof(void*)]); - -}; - -template -class lambda_wrap : public lambda_unique { - using BaseHelper = internal::lambda_wrap_helper_base; - using Parent = lambda_unique; - - template - using IsOther = std_::enable_if_t>::value>; - template - using IsRvalue = std_::enable_if_t::value>; - template - using IsNotRvalue = std_::enable_if_t::value>; - -public: - lambda_wrap() = default; - - lambda_wrap(const lambda_wrap &other) : Parent(other.helper_, typename Parent::Private()) { - this->helper_->construct_copy_other(this->storage_, other.storage_); - } - lambda_wrap &operator=(const lambda_wrap &other) { - auto temp = other; - temp.swap(*this); - return *this; - } - - lambda_wrap(lambda_wrap &&other) = default; - lambda_wrap &operator=(lambda_wrap &&other) = default; - - void swap(lambda_wrap &other) { - if (this != &other) { - lambda_wrap temp = std_::move(other); - other = std_::move(*this); - *this = std_::move(other); - } - } - - lambda_wrap clone() const { - return *this; - } - - template > - lambda_wrap(const Lambda &other) : Parent(&internal::lambda_wrap_helper_copy::instance, typename Parent::Private()) { - internal::lambda_wrap_helper_copy::construct_copy_lambda_method(this->storage_, &other); - } - - template , typename = IsRvalue> - lambda_wrap(Lambda &&other) : Parent(&internal::lambda_wrap_helper_copy::instance, typename Parent::Private()) { - internal::lambda_wrap_helper_copy::construct_move_lambda_method(this->storage_, &other); - } - - template > - lambda_wrap &operator=(const Lambda &other) { - auto temp = other; - this->helper_->destruct(this->storage_); - this->helper_ = &internal::lambda_wrap_helper_copy::instance; - internal::lambda_wrap_helper_copy::construct_copy_lambda_method(this->storage_, &other); - return *this; - } - - template , typename = IsRvalue> - lambda_wrap &operator=(Lambda &&other) { - auto temp = std_::move(other); - this->helper_->destruct(this->storage_); - this->helper_ = &internal::lambda_wrap_helper_copy::instance; - internal::lambda_wrap_helper_copy::construct_move_lambda_method(this->storage_, &other); - return *this; - } - -}; - -} // namespace base diff --git a/Telegram/SourceFiles/core/observer.h b/Telegram/SourceFiles/core/observer.h index c7c724125..5a432ed76 100644 --- a/Telegram/SourceFiles/core/observer.h +++ b/Telegram/SourceFiles/core/observer.h @@ -26,7 +26,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace base { namespace internal { -using ObservableCallHandlers = base::lambda_unique; +using ObservableCallHandlers = base::lambda; void RegisterPendingObservable(ObservableCallHandlers *handlers); void UnregisterActiveObservable(ObservableCallHandlers *handlers); void UnregisterObservable(ObservableCallHandlers *handlers); @@ -36,12 +36,12 @@ using EventParamType = typename base::type_traits::parameter_type; template struct SubscriptionHandlerHelper { - using type = base::lambda_unique)>; + using type = base::lambda)>; }; template <> struct SubscriptionHandlerHelper { - using type = base::lambda_unique; + using type = base::lambda; }; template @@ -120,7 +120,7 @@ public: if (!_data) { _data = MakeShared>(this); } - return _data->append(std_::forward(handler)); + return _data->append(std_::move(handler)); } private: @@ -169,7 +169,7 @@ public: } Subscription append(Handler &&handler) { - auto node = new Node(_observable->_data, std_::forward(handler)); + auto node = new Node(_observable->_data, std_::move(handler)); if (_begin) { _end->next = node; node->prev = _end; diff --git a/Telegram/SourceFiles/core/parse_helper.cpp b/Telegram/SourceFiles/core/parse_helper.cpp new file mode 100644 index 000000000..7457d1311 --- /dev/null +++ b/Telegram/SourceFiles/core/parse_helper.cpp @@ -0,0 +1,115 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "core/parse_helper.h" + +namespace base { +namespace parse { + +// inspired by https://github.com/sindresorhus/strip-json-comments +QByteArray stripComments(const QByteArray &content) { + enum class InsideComment { + None, + SingleLine, + MultiLine, + }; + auto insideComment = InsideComment::None; + auto insideString = false; + + QByteArray result; + auto begin = content.cbegin(), end = content.cend(), offset = begin; + auto feedContent = [&result, &offset, end](const char *ch) { + if (ch > offset) { + if (result.isEmpty()) result.reserve(end - offset - 2); + result.append(offset, ch - offset); + offset = ch; + } + }; + auto feedComment = [&result, &offset, end](const char *ch) { + if (ch > offset) { + if (result.isEmpty()) result.reserve(end - offset - 2); + result.append(' '); + offset = ch; + } + }; + for (auto ch = offset; ch != end;) { + auto currentChar = *ch; + auto nextChar = (ch + 1 == end) ? 0 : *(ch + 1); + + if (insideComment == InsideComment::None && currentChar == '"') { + auto escaped = ((ch > begin) && *(ch - 1) == '\\') && ((ch - 1 < begin) || *(ch - 2) != '\\'); + if (!escaped) { + insideString = !insideString; + } + } + if (insideString) { + ++ch; + continue; + } + + if (insideComment == InsideComment::None && currentChar == '/' && nextChar == '/') { + feedContent(ch); + insideComment = InsideComment::SingleLine; + ch += 2; + } else if (insideComment == InsideComment::SingleLine && currentChar == '\r' && nextChar == '\n') { + feedComment(ch); + ch += 2; + insideComment = InsideComment::None; + } else if (insideComment == InsideComment::SingleLine && currentChar == '\n') { + feedComment(ch); + ++ch; + insideComment = InsideComment::None; + } else if (insideComment == InsideComment::None && currentChar == '/' && nextChar == '*') { + feedContent(ch); + ch += 2; + insideComment = InsideComment::MultiLine; + } else if (insideComment == InsideComment::MultiLine && currentChar == '*' && nextChar == '/') { + ch += 2; + feedComment(ch); + insideComment = InsideComment::None; + } else if (insideComment == InsideComment::MultiLine && currentChar == '\r' && nextChar == '\n') { + feedComment(ch); + ch += 2; + feedContent(ch); + } else if (insideComment == InsideComment::MultiLine && currentChar == '\n') { + feedComment(ch); + ++ch; + feedContent(ch); + } else { + ++ch; + } + } + + if (insideComment == InsideComment::MultiLine) { + // unexpected end of content + } + if (insideComment == InsideComment::None && end > offset) { + if (result.isEmpty()) { + return content; + } else { + result.append(offset, end - offset); + } + } + return result; +} + +} // namespace parse +} // namespace base diff --git a/Telegram/SourceFiles/window/chat_background.h b/Telegram/SourceFiles/core/parse_helper.h similarity index 55% rename from Telegram/SourceFiles/window/chat_background.h rename to Telegram/SourceFiles/core/parse_helper.h index 6840dbc2f..9c7006d04 100644 --- a/Telegram/SourceFiles/window/chat_background.h +++ b/Telegram/SourceFiles/core/parse_helper.h @@ -20,40 +20,36 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -namespace Window { +namespace base { +namespace parse { -struct ChatBackgroundUpdate { - enum class Type { - New, - Changed, - Start, - }; +// Strip all C-style comments. +QByteArray stripComments(const QByteArray &content); - ChatBackgroundUpdate(Type type, bool tiled) : type(type), tiled(tiled) { +inline bool skipWhitespaces(const char *&from, const char *end) { + t_assert(from <= end); + while (from != end && ( + (*from == ' ') || + (*from == '\n') || + (*from == '\t') || + (*from == '\r'))) { + ++from; } - Type type; - bool tiled; -}; + return (from != end); +} -class ChatBackground : public base::Observable { -public: - bool empty() const; - void initIfEmpty(); - void init(int32 id, QPixmap &&image); - void reset(); +inline QLatin1String readName(const char *&from, const char *end) { + t_assert(from <= end); + auto start = from; + while (from != end && ( + (*from >= 'a' && *from <= 'z') || + (*from >= 'A' && *from <= 'Z') || + (*from >= '0' && *from <= '9') || + (*from == '_'))) { + ++from; + } + return QLatin1String(start, from - start); +} - int32 id() const; - const QPixmap &image() const; - bool tile() const; - void setTile(bool tile); - -private: - int32 _id = 0; - QPixmap _image; - bool _tile = false; - -}; - -ChatBackground *chatBackground(); - -} // namespace Window +} // namespace parse +} // namespace base diff --git a/Telegram/SourceFiles/core/qthelp_regex.h b/Telegram/SourceFiles/core/qthelp_regex.h index 899e88b17..a70d197e9 100644 --- a/Telegram/SourceFiles/core/qthelp_regex.h +++ b/Telegram/SourceFiles/core/qthelp_regex.h @@ -28,6 +28,14 @@ public: } RegularExpressionMatch(RegularExpressionMatch &&other) : data_(std_::move(other.data_)) { } + RegularExpressionMatch &operator=(QRegularExpressionMatch &&match) { + data_ = std_::move(match); + return *this; + } + RegularExpressionMatch &operator=(RegularExpressionMatch &&other) { + data_ = std_::move(other.data_); + return *this; + } QRegularExpressionMatch *operator->() { return &data_; } diff --git a/Telegram/SourceFiles/core/qthelp_url.cpp b/Telegram/SourceFiles/core/qthelp_url.cpp index 8acd3791d..3beb98b2b 100644 --- a/Telegram/SourceFiles/core/qthelp_url.cpp +++ b/Telegram/SourceFiles/core/qthelp_url.cpp @@ -43,7 +43,10 @@ QMap url_parse_params(const QString ¶ms, UrlParamNameTrans paramName = param.mid(0, separatorPosition); paramValue = url_decode(param.mid(separatorPosition + 1)); } - result.insert(transformParamName(paramName), paramValue); + paramName = transformParamName(paramName); + if (!result.contains(paramName)) { + result.insert(paramName, paramValue); + } } } return result; diff --git a/Telegram/SourceFiles/core/single_timer.cpp b/Telegram/SourceFiles/core/single_timer.cpp index 7507a8f14..af467b95f 100644 --- a/Telegram/SourceFiles/core/single_timer.cpp +++ b/Telegram/SourceFiles/core/single_timer.cpp @@ -23,7 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "application.h" -SingleTimer::SingleTimer() { +SingleTimer::SingleTimer(QObject *parent) : QTimer(parent) { QTimer::setSingleShot(true); if (App::app()) { connect(App::app(), SIGNAL(adjustSingleTimers()), this, SLOT(adjust())); @@ -31,7 +31,7 @@ SingleTimer::SingleTimer() { } } -void SingleTimer::setTimeoutHandler(base::lambda_unique &&handler) { +void SingleTimer::setTimeoutHandler(base::lambda &&handler) { if (_handler && !handler) { disconnect(this, SIGNAL(timeout()), this, SLOT(onTimeout())); } else if (handler && !_handler) { @@ -41,7 +41,7 @@ void SingleTimer::setTimeoutHandler(base::lambda_unique &&handler) { } void SingleTimer::adjust() { - uint64 n = getms(true); + auto n = getms(true); if (isActive()) { if (n >= _finishing) { start(0); @@ -58,7 +58,7 @@ void SingleTimer::onTimeout() { } void SingleTimer::start(int msec) { - _finishing = getms(true) + (msec < 0 ? 0 : uint64(msec)); + _finishing = getms(true) + (msec < 0 ? 0 : msec); if (!_inited && App::app()) { connect(App::app(), SIGNAL(adjustSingleTimers()), this, SLOT(adjust())); _inited = true; diff --git a/Telegram/SourceFiles/core/single_timer.h b/Telegram/SourceFiles/core/single_timer.h index 3aa89647a..bd0bd0edd 100644 --- a/Telegram/SourceFiles/core/single_timer.h +++ b/Telegram/SourceFiles/core/single_timer.h @@ -21,18 +21,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once #include "core/basic_types.h" -#include "core/lambda_wrap.h" class SingleTimer : public QTimer { // single shot timer with check Q_OBJECT public: - SingleTimer(); + SingleTimer(QObject *parent = nullptr); void setSingleShot(bool); // is not available void start(); // is not available - void setTimeoutHandler(base::lambda_unique &&handler); + void setTimeoutHandler(base::lambda &&handler); public slots: void start(int msec); @@ -43,8 +42,8 @@ private slots: void onTimeout(); private: - uint64 _finishing = 0; + TimeMs _finishing = 0; bool _inited = false; - base::lambda_unique _handler; + base::lambda _handler; }; diff --git a/Telegram/SourceFiles/core/stl_subset.h b/Telegram/SourceFiles/core/stl_subset.h index 26596b22b..8053e4165 100644 --- a/Telegram/SourceFiles/core/stl_subset.h +++ b/Telegram/SourceFiles/core/stl_subset.h @@ -59,6 +59,8 @@ template struct remove_reference { using type = T; }; +template +using remove_reference_t = typename remove_reference::type; template struct is_lvalue_reference : false_type { @@ -156,14 +158,9 @@ template struct add_const { using type = const T; }; + template using add_const_t = typename add_const::type; -template -constexpr add_const_t &as_const(T& t) noexcept { - return t; -} -template -void as_const(const T&&) = delete; // This is not full unique_ptr, but at least with std interface. template @@ -203,6 +200,7 @@ public: } ~unique_ptr() noexcept { if (_p) { + static_assert(sizeof(T) > 0, "can't delete an incomplete type"); delete _p; _p = nullptr; } @@ -231,6 +229,7 @@ public: auto old = _p; _p = p; if (old) { + static_assert(sizeof(T) > 0, "can't delete an incomplete type"); delete old; } } diff --git a/Telegram/SourceFiles/core/task_queue.cpp b/Telegram/SourceFiles/core/task_queue.cpp new file mode 100644 index 000000000..0835ddf09 --- /dev/null +++ b/Telegram/SourceFiles/core/task_queue.cpp @@ -0,0 +1,415 @@ +/* +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/task_queue.h" + +namespace base { +namespace { + +auto MainThreadId = QThread::currentThreadId(); +const auto MaxThreadsCount = qMax(QThread::idealThreadCount(), 2); + +template +class Thread : public QThread { +public: + Thread(Lambda code) : _code(std_::move(code)) { + } + void run() override { + _code(); + } + +private: + Lambda _code; + +}; + +template +object_ptr> MakeThread(Lambda code) { + return object_ptr>(std_::move(code)); +} + +} // namespace + +class TaskQueue::TaskQueueList { +public: + TaskQueueList(); + + void Register(TaskQueue *queue); + void Unregister(TaskQueue *queue); + bool IsInList(TaskQueue *queue) const; + void Clear(); + bool Empty(int list_index_) const; + TaskQueue *TakeFirst(int list_index_); + +private: + void Insert(TaskQueue *queue, int list_index_); + void Remove(TaskQueue *queue, int list_index_); + + TaskQueue *Tail() { return &tail_; } + const TaskQueue *Tail() const { return &tail_; } + + TaskQueue tail_ = { Type::Special, Priority::Normal }; + TaskQueue *(lists_[kQueuesListsCount]); + +}; + +class TaskQueue::TaskThreadPool { + struct Private { + }; + +public: + TaskThreadPool(const Private &) { } + static const QSharedPointer &Instance(); + + void AddQueueTask(TaskQueue *queue, Task &&task); + void RemoveQueue(TaskQueue *queue); + + ~TaskThreadPool(); + +private: + + void ThreadFunction(); + + std_::vector_of_moveable> threads_; + QMutex queues_mutex_; + + // queues_mutex_ must be locked when working with the list. + TaskQueueList queue_list_; + + QWaitCondition thread_condition_; + bool stopped_ = false; + int tasks_in_process_ = 0; + int background_tasks_in_process_ = 0; + +}; + +TaskQueue::TaskQueueList::TaskQueueList() { + for (auto &list : lists_) { + list = &tail_; + } +} + +void TaskQueue::TaskQueueList::Register(TaskQueue *queue) { + t_assert(!queue->SerialTaskInProcess()); + + Insert(queue, kAllQueuesList); + if (queue->priority_ == Priority::Normal) { + Insert(queue, kOnlyNormalQueuesList); + } +} + +void TaskQueue::TaskQueueList::Unregister(TaskQueue *queue) { + Remove(queue, kAllQueuesList); + if (queue->priority_ == Priority::Normal) { + Remove(queue, kOnlyNormalQueuesList); + } +} + +void TaskQueue::TaskQueueList::Insert(TaskQueue *queue, int list_index_) { + t_assert(list_index_ < kQueuesListsCount); + + auto tail = Tail(); + if (lists_[list_index_] == tail) { + lists_[list_index_] = queue; + } + + auto &list_entry = queue->list_entries_[list_index_]; + t_assert(list_entry.after == nullptr); + if ((list_entry.before = tail->list_entries_[list_index_].before)) { + list_entry.before->list_entries_[list_index_].after = queue; + } + list_entry.after = tail; + tail->list_entries_[list_index_].before = queue; +} + +void TaskQueue::TaskQueueList::Remove(TaskQueue *queue, int list_index_) { + t_assert(list_index_ < kQueuesListsCount); + + auto &list_entry = queue->list_entries_[list_index_]; + t_assert(list_entry.after != nullptr); + if (lists_[list_index_] == queue) { + lists_[list_index_] = list_entry.after; + } else { + t_assert(list_entry.before != nullptr); + list_entry.before->list_entries_[list_index_].after = list_entry.after; + } + list_entry.after->list_entries_[list_index_].before = list_entry.before; + list_entry.before = list_entry.after = nullptr; +} + +bool TaskQueue::TaskQueueList::IsInList(TaskQueue *queue) const { + if (queue->list_entries_[kAllQueuesList].after) { + return true; + } + t_assert(queue->list_entries_[kOnlyNormalQueuesList].after == nullptr); + return false; +} + +void TaskQueue::TaskQueueList::Clear() { + auto tail = Tail(); + for (int i = 0; i < kQueuesListsCount; ++i) { + for (auto j = lists_[i], next = j; j != tail; j = next) { + auto &list_entry = j->list_entries_[i]; + next = list_entry.after; + list_entry.before = list_entry.after = nullptr; + } + lists_[i] = tail; + } +} + +bool TaskQueue::TaskQueueList::Empty(int list_index_) const { + t_assert(list_index_ < kQueuesListsCount); + + auto list = lists_[list_index_]; + t_assert(list != nullptr); + return (list->list_entries_[list_index_].after == nullptr); +} + +TaskQueue *TaskQueue::TaskQueueList::TakeFirst(int list_index_) { + t_assert(!Empty(list_index_)); + + auto queue = lists_[list_index_]; + Unregister(queue); +// log_msgs.push_back("Unregistered from list in TakeFirst"); + return queue; +} + +void TaskQueue::TaskThreadPool::AddQueueTask(TaskQueue *queue, Task &&task) { + QMutexLocker lock(&queues_mutex_); + + queue->tasks_.push_back(new Task(std_::move(task))); + auto list_was_empty = queue_list_.Empty(kAllQueuesList); + auto threads_count = threads_.size(); + auto all_threads_processing = (threads_count == tasks_in_process_); + auto some_threads_are_vacant = !all_threads_processing && list_was_empty; + auto will_create_thread = !some_threads_are_vacant && (threads_count < MaxThreadsCount); + + if (!queue->SerialTaskInProcess()) { + if (!queue_list_.IsInList(queue)) { + queue_list_.Register(queue); + } + } + if (will_create_thread) { + threads_.push_back(MakeThread([this]() { + ThreadFunction(); + })); + threads_.back()->start(); + } else if (some_threads_are_vacant) { + t_assert(threads_count > tasks_in_process_); + thread_condition_.wakeOne(); + } +} + +void TaskQueue::TaskThreadPool::RemoveQueue(TaskQueue *queue) { + QMutexLocker lock(&queues_mutex_); + if (queue_list_.IsInList(queue)) { + queue_list_.Unregister(queue); + } + if (queue->destroyed_flag_) { + *queue->destroyed_flag_ = true; + } +} + +TaskQueue::TaskThreadPool::~TaskThreadPool() { + { + QMutexLocker lock(&queues_mutex_); + queue_list_.Clear(); + stopped_ = true; + } + thread_condition_.wakeAll(); + for (auto &thread : threads_) { + thread->wait(); + } +} + +const QSharedPointer &TaskQueue::TaskThreadPool::Instance() { // static + static auto Pool = MakeShared(Private()); + return Pool; +} + +void TaskQueue::TaskThreadPool::ThreadFunction() { + // Flag marking that the previous processed task was + // with a Background priority. We count all the background + // tasks being processed. + bool background_task = false; + + // Saved serial queue pointer. When we process a serial + // queue task we don't return the queue to the list until + // the task is processed and we return it on the next cycle. + TaskQueue *serial_queue = nullptr; + bool serial_queue_destroyed = false; + bool task_was_processed = false; + while (true) { + std_::unique_ptr task; + { + QMutexLocker lock(&queues_mutex_); + + // Finish the previous task processing. + if (task_was_processed) { + --tasks_in_process_; + } + if (background_task) { + --background_tasks_in_process_; + background_task = false; + } + if (serial_queue) { + if (!serial_queue_destroyed) { + serial_queue->destroyed_flag_ = nullptr; + if (!serial_queue->tasks_.empty()) { + queue_list_.Register(serial_queue); + } + } + serial_queue = nullptr; + serial_queue_destroyed = false; + } + + // Wait for a task to appear in the queues list. + while (queue_list_.Empty(kAllQueuesList)) { + if (stopped_) { + return; + } + thread_condition_.wait(&queues_mutex_); + } + + // Select a task we will be processing. + auto processing_background = (background_tasks_in_process_ > 0); + auto take_only_normal = processing_background && !queue_list_.Empty(kOnlyNormalQueuesList); + auto take_from_list_ = take_only_normal ? kOnlyNormalQueuesList : kAllQueuesList; + auto queue = queue_list_.TakeFirst(take_from_list_); + + t_assert(!queue->tasks_.empty()); + + task.reset(queue->tasks_.front()); + queue->tasks_.pop_front(); + + if (queue->type_ == Type::Serial) { + // Serial queues are returned in the list for processing + // only after the task is finished. + serial_queue = queue; + t_assert(serial_queue->destroyed_flag_ == nullptr); + serial_queue->destroyed_flag_ = &serial_queue_destroyed; + } else if (!queue->tasks_.empty()) { + queue_list_.Register(queue); + } + + ++tasks_in_process_; + task_was_processed = true; + if (queue->priority_ == Priority::Background) { + ++background_tasks_in_process_; + background_task = true; + } + } + + (*task)(); + } +} + +TaskQueue::TaskQueue(Type type, Priority priority) +: type_(type) +, priority_(priority) { + if (type_ != Type::Main && type_ != Type::Special) { + weak_thread_pool_ = TaskThreadPool::Instance(); + } +} + +TaskQueue::~TaskQueue() { + if (type_ != Type::Main && type_ != Type::Special) { + if (auto thread_pool = weak_thread_pool_.toStrongRef()) { + thread_pool->RemoveQueue(this); + } + } + for (auto task : take(tasks_)) { + delete task; + } +} + +void TaskQueue::Put(Task &&task) { + if (type_ == Type::Main) { + QMutexLocker lock(&tasks_mutex_); + tasks_.push_back(new Task(std_::move(task))); + + Sandbox::MainThreadTaskAdded(); + } else { + t_assert(type_ != Type::Special); + TaskThreadPool::Instance()->AddQueueTask(this, std_::move(task)); + } +} + +void TaskQueue::ProcessMainTasks() { // static + t_assert(QThread::currentThreadId() == MainThreadId); + + while (ProcessOneMainTask()) { + } +} + +void TaskQueue::ProcessMainTasks(TimeMs max_time_spent) { // static + t_assert(QThread::currentThreadId() == MainThreadId); + + auto start_time = getms(); + while (ProcessOneMainTask()) { + if (getms() >= start_time + max_time_spent) { + break; + } + } +} + +bool TaskQueue::ProcessOneMainTask() { // static + std_::unique_ptr task; + { + QMutexLocker lock(&Main().tasks_mutex_); + auto &tasks = Main().tasks_; + if (tasks.empty()) { + return false; + } + + task.reset(tasks.front()); + tasks.pop_front(); + } + + (*task)(); + return true; +} + +bool TaskQueue::IsMyThread() const { + if (type_ == Type::Main) { + return (QThread::currentThreadId() == MainThreadId); + } + t_assert(type_ != Type::Special); + return false; +} + +// Default queues. +TaskQueue &TaskQueue::Main() { // static + static TaskQueue MainQueue { Type::Main, Priority::Normal }; + return MainQueue; +} + +TaskQueue &TaskQueue::Normal() { // static + static TaskQueue NormalQueue { Type::Concurrent, Priority::Normal }; + return NormalQueue; +} + +TaskQueue &TaskQueue::Background() { // static + static TaskQueue BackgroundQueue { Type::Concurrent, Priority::Background }; + return BackgroundQueue; +} + +} // namespace base diff --git a/Telegram/SourceFiles/core/task_queue.h b/Telegram/SourceFiles/core/task_queue.h new file mode 100644 index 000000000..58aa71cc5 --- /dev/null +++ b/Telegram/SourceFiles/core/task_queue.h @@ -0,0 +1,100 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +namespace base { + +using Task = lambda; + +// An attempt to create/use a TaskQueue or one of the default queues +// after the main() has returned leads to an undefined behaviour. +class TaskQueue { + enum class Type { + Main, // Unique queue for main thread tasks. + Serial, + Concurrent, + Special, // Unique special queue for thread pool lists terminal item. + }; + +public: + enum class Priority { + Normal, + Background, + }; + + // Creating custom serial queues. + TaskQueue(Priority priority) : TaskQueue(Type::Serial, priority) { + } + + // Default main and two concurrent queues. + static TaskQueue &Main(); + static TaskQueue &Normal(); + static TaskQueue &Background(); + + void Put(Task &&task); + + static void ProcessMainTasks(); + static void ProcessMainTasks(TimeMs max_time_spent); + + ~TaskQueue(); + +private: + static bool ProcessOneMainTask(); + + TaskQueue(Type type, Priority priority); + + bool IsMyThread() const; + bool SerialTaskInProcess() const { + return (destroyed_flag_ != nullptr); + } + + const Type type_; + const Priority priority_; + + QList tasks_; // TODO: std_::deque_of_moveable + QMutex tasks_mutex_; // Only for the main queue. + + // Only for the other queues, not main. + class TaskThreadPool; + QWeakPointer weak_thread_pool_; + + class TaskQueueList; + + struct TaskQueueListEntry { + TaskQueue *before = nullptr; + TaskQueue *after = nullptr; + }; + + // Thread pool queues linked list. + static constexpr int kAllQueuesList = 0; + + // Thread pool queues linked list with excluded Background queues. + static constexpr int kOnlyNormalQueuesList = 1; + + static constexpr int kQueuesListsCount = 2; + TaskQueueListEntry list_entries_[kQueuesListsCount]; + + // Only for Serial queues: non-null value means a task is currently processed. + bool *destroyed_flag_ = nullptr; + +}; + +} // namespace base diff --git a/Telegram/SourceFiles/core/utils.cpp b/Telegram/SourceFiles/core/utils.cpp index 63959c272..73ddbbb7c 100644 --- a/Telegram/SourceFiles/core/utils.cpp +++ b/Telegram/SourceFiles/core/utils.cpp @@ -206,7 +206,7 @@ namespace { float64 _msFreq; float64 _msgIdCoef; - int64 _msStart = 0, _msAddToMsStart = 0, _msAddToUnixtime = 0; + TimeMs _msStart = 0, _msAddToMsStart = 0, _msAddToUnixtime = 0; int32 _timeStart = 0; class _MsInitializer { @@ -235,7 +235,7 @@ namespace { clock_gettime(CLOCK_MONOTONIC, &ts); //_msFreq = 1 / 1000000.; _msgIdCoef = float64(0xFFFF0000L) / 1000000000.; - _msStart = 1000 * uint64(ts.tv_sec) + (uint64(ts.tv_nsec) / 1000000); + _msStart = 1000LL * static_cast(ts.tv_sec) + (static_cast(ts.tv_nsec) / 1000000LL); #endif _timeStart = myunixtime(); srand((uint32)(_msStart & 0xFFFFFFFFL)); @@ -312,8 +312,8 @@ namespace ThirdParty { } bool checkms() { - int64 unixms = (myunixtime() - _timeStart) * 1000LL + _msAddToUnixtime; - int64 ms = int64(getms(true)); + auto unixms = (myunixtime() - _timeStart) * 1000LL + _msAddToUnixtime; + auto ms = getms(true); if (ms > unixms + 1000LL) { _msAddToUnixtime = ((ms - unixms) / 1000LL) * 1000LL; } else if (unixms > ms + 1000LL) { @@ -324,24 +324,24 @@ bool checkms() { return false; } -uint64 getms(bool checked) { +TimeMs getms(bool checked) { _msInitialize(); #ifdef Q_OS_WIN LARGE_INTEGER li; QueryPerformanceCounter(&li); - return (uint64)((li.QuadPart - _msStart) * _msFreq) + (checked ? _msAddToMsStart : 0); + return ((li.QuadPart - _msStart) * _msFreq) + (checked ? _msAddToMsStart : 0LL); #elif defined Q_OS_MAC - uint64 msCount = mach_absolute_time(); - return (uint64)((msCount - _msStart) * _msFreq) + (checked ? _msAddToMsStart : 0); + auto msCount = static_cast(mach_absolute_time()); + return ((msCount - _msStart) * _msFreq) + (checked ? _msAddToMsStart : 0LL); #else timespec ts; - int res = clock_gettime(CLOCK_MONOTONIC, &ts); + auto res = clock_gettime(CLOCK_MONOTONIC, &ts); if (res != 0) { LOG(("Bad clock_gettime result: %1").arg(res)); return 0; } - uint64 msCount = 1000 * uint64(ts.tv_sec) + (uint64(ts.tv_nsec) / 1000000); - return (uint64)(msCount - _msStart) + (checked ? _msAddToMsStart : 0); + auto msCount = 1000LL * static_cast(ts.tv_sec) + (static_cast(ts.tv_nsec) / 1000000LL); + return (msCount - _msStart) + (checked ? _msAddToMsStart : 0LL); #endif } @@ -938,41 +938,52 @@ QString rusKeyboardLayoutSwitch(const QString &from) { QStringList MimeType::globPatterns() const { switch (_type) { - case WebP: return QStringList(qsl("*.webp")); + case Known::WebP: return QStringList(qsl("*.webp")); + case Known::TDesktopTheme: return QStringList(qsl("*.tdesktop-theme")); default: break; } return _typeStruct.globPatterns(); } QString MimeType::filterString() const { switch (_type) { - case WebP: return qsl("WebP image (*.webp)"); + case Known::WebP: return qsl("WebP image (*.webp)"); + case Known::TDesktopTheme: return qsl("Theme files (*.tdesktop-theme)"); default: break; } return _typeStruct.filterString(); } QString MimeType::name() const { switch (_type) { - case WebP: return qsl("image/webp"); + case Known::WebP: return qsl("image/webp"); + case Known::TDesktopTheme: return qsl("application/x-tdesktop-theme"); default: break; } return _typeStruct.name(); } MimeType mimeTypeForName(const QString &mime) { - if (mime == qsl("image/webp")) return MimeType(MimeType::WebP); + if (mime == qsl("image/webp")) { + return MimeType(MimeType::Known::WebP); + } else if (mime == qsl("application/x-tdesktop-theme")) { + return MimeType(MimeType::Known::TDesktopTheme); + } return MimeType(QMimeDatabase().mimeTypeForName(mime)); } MimeType mimeTypeForFile(const QFileInfo &file) { QString path = file.absoluteFilePath(); - if (path.endsWith(qsl(".webp"), Qt::CaseInsensitive)) return MimeType(MimeType::WebP); + if (path.endsWith(qsl(".webp"), Qt::CaseInsensitive)) { + return MimeType(MimeType::Known::WebP); + } else if (path.endsWith(qsl(".tdesktop-theme"), Qt::CaseInsensitive)) { + return MimeType(MimeType::Known::TDesktopTheme); + } { QFile f(path); if (f.open(QIODevice::ReadOnly)) { QByteArray magic = f.read(12); if (magic.size() >= 12) { if (!memcmp(magic.constData(), "RIFF", 4) && !memcmp(magic.constData() + 8, "WEBP", 4)) { - return MimeType(MimeType::WebP); + return MimeType(MimeType::Known::WebP); } } f.close(); @@ -984,7 +995,7 @@ MimeType mimeTypeForFile(const QFileInfo &file) { MimeType mimeTypeForData(const QByteArray &data) { if (data.size() >= 12) { if (!memcmp(data.constData(), "RIFF", 4) && !memcmp(data.constData() + 8, "WEBP", 4)) { - return MimeType(MimeType::WebP); + return MimeType(MimeType::Known::WebP); } } return MimeType(QMimeDatabase().mimeTypeForData(data)); diff --git a/Telegram/SourceFiles/core/utils.h b/Telegram/SourceFiles/core/utils.h index f14625ddf..9a588d2a6 100644 --- a/Telegram/SourceFiles/core/utils.h +++ b/Telegram/SourceFiles/core/utils.h @@ -25,7 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace base { template -inline constexpr size_t array_size(T(&)[N]) { +inline constexpr size_t array_size(const T(&)[N]) { return N; } @@ -35,8 +35,65 @@ inline T take(T &source, T &&new_value = T()) { return std_::move(new_value); } +namespace internal { + +template +inline constexpr D up_cast_helper(std_::true_type, T object) { + return object; +} + +template +inline constexpr D up_cast_helper(std_::false_type, T object) { + return nullptr; +} + +template +constexpr std_::add_const_t &any_as_const(T &&value) noexcept { + return value; +} + +} // namespace internal + +template +inline constexpr D up_cast(T object) { + using DV = std_::decay_simple_t; + using TV = std_::decay_simple_t; + return internal::up_cast_helper(std_::integral_constant::value || std_::is_same::value>(), object); +} + +template +class scope_guard_helper { +public: + scope_guard_helper(Lambda on_scope_exit) : _handler(std_::move(on_scope_exit)) { + } + void dismiss() { + _dismissed = true; + } + ~scope_guard_helper() { + if (!_dismissed) { + _handler(); + } + } + +private: + Lambda _handler; + bool _dismissed = false; + +}; + +template +scope_guard_helper scope_guard(Lambda on_scope_exit) { + return scope_guard_helper(std_::move(on_scope_exit)); +} + } // namespace base +// using for_const instead of plain range-based for loop to ensure usage of const_iterator +// it is important for the copy-on-write Qt containers +// if you have "QVector v" then "for (T * const p : v)" will still call QVector::detach(), +// while "for_const (T *p, v)" won't and "for_const (T *&p, v)" won't compile +#define for_const(range_declaration, range_expression) for (range_declaration : base::internal::any_as_const(range_expression)) + template inline QFlags qFlags(Enum v) { return QFlags(v); @@ -78,6 +135,10 @@ inline QString str_const_toString(const str_const &str) { return QString::fromUtf8(str.c_str(), str.size()); } +inline QByteArray str_const_toByteArray(const str_const &str) { + return QByteArray::fromRawData(str.c_str(), str.size()); +} + template inline void accumulate_max(T &a, const T &b) { if (a < b) a = b; } @@ -159,8 +220,9 @@ void finish(); } +using TimeMs = int64; bool checkms(); // returns true if time has changed -uint64 getms(bool checked = false); +TimeMs getms(bool checked = false); const static uint32 _md5_block_size = 64; class HashMd5 { @@ -258,21 +320,24 @@ inline T snap(const T &v, const T &_min, const T &_max) { template class ManagedPtr { public: - ManagedPtr() : ptr(0) { - } - ManagedPtr(T *p) : ptr(p) { + ManagedPtr() = default; + ManagedPtr(T *p) : _data(p) { } T *operator->() const { - return ptr; + return _data; } T *v() const { - return ptr; + return _data; + } + + explicit operator bool() const { + return _data != nullptr; } protected: + using Parent = ManagedPtr; + T *_data = nullptr; - T *ptr; - typedef ManagedPtr Parent; }; QString translitRusEng(const QString &rus); @@ -302,11 +367,6 @@ enum DBIConnectionType { dbictTcpProxy = 3, }; -enum DBIDefaultAttach { - dbidaDocument = 0, - dbidaPhoto = 1, -}; - struct ProxyData { QString host; uint32 port = 0; @@ -369,24 +429,23 @@ inline QString strMakeFromLetters(const uint32 *letters, int32 len) { class MimeType { public: - - enum TypeEnum { + enum class Known { Unknown, + TDesktopTheme, WebP, }; - MimeType(const QMimeType &type) : _typeStruct(type), _type(Unknown) { + MimeType(const QMimeType &type) : _typeStruct(type) { } - MimeType(TypeEnum type) : _type(type) { + MimeType(Known type) : _type(type) { } QStringList globPatterns() const; QString filterString() const; QString name() const; private: - QMimeType _typeStruct; - TypeEnum _type; + Known _type = Known::Unknown; }; diff --git a/Telegram/SourceFiles/core/vector_of_moveable.h b/Telegram/SourceFiles/core/vector_of_moveable.h index 727bbac2d..c32e01312 100644 --- a/Telegram/SourceFiles/core/vector_of_moveable.h +++ b/Telegram/SourceFiles/core/vector_of_moveable.h @@ -119,7 +119,7 @@ public: return insertAt; } inline void push_back(T &&value) { - insert(end(), std_::forward(value)); + insert(end(), std_::move(value)); } inline void pop_back() { erase(end() - 1); diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index bc397ab9b..e623144c2 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -24,7 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #define BETA_VERSION_MACRO (0ULL) -constexpr int AppVersion = 10020; -constexpr str_const AppVersionStr = "0.10.20"; -constexpr bool AppAlphaVersion = false; +constexpr int AppVersion = 10027; +constexpr str_const AppVersionStr = "0.10.27"; +constexpr bool AppAlphaVersion = true; constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO; diff --git a/Telegram/SourceFiles/core/zlib_help.h b/Telegram/SourceFiles/core/zlib_help.h new file mode 100644 index 000000000..1198e7539 --- /dev/null +++ b/Telegram/SourceFiles/core/zlib_help.h @@ -0,0 +1,390 @@ +/* +WARNING! All changes made in this file will be lost! +Created from 'colors.palette' by 'codegen_style' + +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 "zip.h" +#include "unzip.h" + +namespace zlib { +namespace internal { + +class InMemoryFile { +public: + InMemoryFile(const QByteArray &data = QByteArray()) : _data(data) { + } + + zlib_filefunc_def funcs() { + zlib_filefunc_def result; + result.opaque = this; + result.zopen_file = &InMemoryFile::Open; + result.zerror_file = &InMemoryFile::Error; + result.zread_file = &InMemoryFile::Read; + result.zwrite_file = &InMemoryFile::Write; + result.zclose_file = &InMemoryFile::Close; + result.zseek_file = &InMemoryFile::Seek; + result.ztell_file = &InMemoryFile::Tell; + return result; + } + + int error() const { + return _error; + } + + QByteArray result() const { + return _data; + } + +private: + voidpf open(const char *filename, int mode) { + if (mode & ZLIB_FILEFUNC_MODE_WRITE) { + if (mode & ZLIB_FILEFUNC_MODE_CREATE) { + _data.clear(); + } + _position = _data.size(); + _data.reserve(2 * 1024 * 1024); + } else if (mode & ZLIB_FILEFUNC_MODE_READ) { + _position = 0; + } + _error = 0; + return this; + } + + uLong read(voidpf stream, void* buf, uLong size) { + uLong toRead = 0; + if (!_error) { + if (_data.size() > int(_position)) { + toRead = qMin(size, uLong(_data.size() - _position)); + memcpy(buf, _data.constData() + _position, toRead); + _position += toRead; + } + if (toRead < size) { + _error = -1; + } + } + return toRead; + } + + uLong write(voidpf stream, const void* buf, uLong size) { + if (_data.size() < int(_position + size)) { + _data.resize(_position + size); + } + memcpy(_data.data() + _position, buf, size); + _position += size; + return size; + } + + int close(voidpf stream) { + auto result = _error; + _position = 0; + _error = 0; + return result; + } + + int error(voidpf stream) const { + return _error; + } + + long tell(voidpf stream) const { + return _position; + } + + long seek(voidpf stream, uLong offset, int origin) { + if (!_error) { + switch (origin) { + case ZLIB_FILEFUNC_SEEK_SET: _position = offset; break; + case ZLIB_FILEFUNC_SEEK_CUR: _position += offset; break; + case ZLIB_FILEFUNC_SEEK_END: _position = _data.size() + offset; break; + } + if (int(_position) > _data.size()) { + _error = -1; + } + } + return _error; + } + + static voidpf Open(voidpf opaque, const char* filename, int mode) { + return static_cast(opaque)->open(filename, mode); + } + + static uLong Read(voidpf opaque, voidpf stream, void* buf, uLong size) { + return static_cast(opaque)->read(stream, buf, size); + } + + static uLong Write(voidpf opaque, voidpf stream, const void* buf, uLong size) { + return static_cast(opaque)->write(stream, buf, size); + } + + static int Close(voidpf opaque, voidpf stream) { + return static_cast(opaque)->close(stream); + } + + static int Error(voidpf opaque, voidpf stream) { + return static_cast(opaque)->error(stream); + } + + static long Tell(voidpf opaque, voidpf stream) { + return static_cast(opaque)->tell(stream); + } + + static long Seek(voidpf opaque, voidpf stream, uLong offset, int origin) { + return static_cast(opaque)->seek(stream, offset, origin); + } + + uLong _position = 0; + int _error = 0; + QByteArray _data; + +}; + +} // namespace internal + +constexpr int kCaseSensitive = 1; +constexpr int kCaseInsensitive = 2; + +class FileToRead { +public: + FileToRead(const QByteArray &content) : _data(content) { + auto funcs = _data.funcs(); + if (!(_handle = unzOpen2(nullptr, &funcs))) { + _error = -1; + } + } + + int getGlobalInfo(unz_global_info *pglobal_info) { + if (error() == UNZ_OK) { + _error = _handle ? unzGetGlobalInfo(_handle, pglobal_info) : -1; + } + return _error; + } + + int locateFile(const char *szFileName, int iCaseSensitivity) { + if (error() == UNZ_OK) { + _error = _handle ? unzLocateFile(_handle, szFileName, iCaseSensitivity) : -1; + } + return error(); + } + + int getCurrentFileInfo( + unz_file_info *pfile_info, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize + ) { + if (error() == UNZ_OK) { + _error = _handle ? unzGetCurrentFileInfo( + _handle, + pfile_info, + szFileName, + fileNameBufferSize, + extraField, + extraFieldBufferSize, + szComment, + commentBufferSize + ) : -1; + } + return error(); + } + + int openCurrentFile() { + if (error() == UNZ_OK) { + _error = _handle ? unzOpenCurrentFile(_handle) : -1; + } + return error(); + } + + int readCurrentFile(voidp buf, unsigned len) { + if (error() == UNZ_OK) { + auto result = _handle ? unzReadCurrentFile(_handle, buf, len) : -1; + if (result >= 0) { + return result; + } else { + _error = result; + } + } + return error(); + } + + int closeCurrentFile() { + if (error() == UNZ_OK) { + _error = _handle ? unzCloseCurrentFile(_handle) : -1; + } + return error(); + } + + QByteArray readCurrentFileContent(int fileSizeLimit) { + unz_file_info fileInfo = { 0 }; + if (getCurrentFileInfo(&fileInfo, nullptr, 0, nullptr, 0, nullptr, 0) != UNZ_OK) { + LOG(("Error: could not get current file info in a zip file.")); + return QByteArray(); + } + + auto size = fileInfo.uncompressed_size; + if (size > static_cast(fileSizeLimit)) { + if (_error == UNZ_OK) _error = -1; + LOG(("Error: current file is too large (should be less than %1, got %2) in a zip file.").arg(fileSizeLimit).arg(size)); + return QByteArray(); + } + if (openCurrentFile() != UNZ_OK) { + LOG(("Error: could not open current file in a zip file.")); + return QByteArray(); + } + + QByteArray result; + result.resize(size); + + auto couldRead = readCurrentFile(result.data(), size); + if (couldRead != static_cast(size)) { + LOG(("Error: could not read current file in a zip file, got %1.").arg(couldRead)); + return QByteArray(); + } + + if (closeCurrentFile() != UNZ_OK) { + LOG(("Error: could not close current file in a zip file.")); + return QByteArray(); + } + + return result; + } + + QByteArray readFileContent(const char *szFileName, int iCaseSensitivity, int fileSizeLimit) { + if (locateFile(szFileName, iCaseSensitivity) != UNZ_OK) { + LOG(("Error: could not locate '%1' in a zip file.").arg(szFileName)); + return QByteArray(); + } + return readCurrentFileContent(fileSizeLimit); + } + + void close() { + if (_handle && unzClose(_handle) != UNZ_OK && _error == UNZ_OK) { + _error = -1; + } + _handle = nullptr; + } + + int error() const { + if (auto dataError = _data.error()) { + return dataError; + } + return _error; + } + + void clearError() { + _error = UNZ_OK; + } + + ~FileToRead() { + close(); + } + +private: + internal::InMemoryFile _data; + unzFile _handle = nullptr; + int _error = 0; + +}; + +class FileToWrite { +public: + FileToWrite() { + auto funcs = _data.funcs(); + if (!(_handle = zipOpen2(nullptr, APPEND_STATUS_CREATE, nullptr, &funcs))) { + _error = -1; + } + } + + int openNewFile( + const char *filename, + const zip_fileinfo *zipfi, + const void *extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level + ) { + if (error() == ZIP_OK) { + _error = _handle ? zipOpenNewFileInZip( + _handle, + filename, + zipfi, + extrafield_local, + size_extrafield_local, + extrafield_global, + size_extrafield_global, + comment, + method, + level + ) : -1; + } + return error(); + } + + int writeInFile(const void* buf, unsigned len) { + if (error() == ZIP_OK) { + _error = _handle ? zipWriteInFileInZip(_handle, buf, len) : -1; + } + return error(); + } + + int closeFile() { + if (error() == ZIP_OK) { + _error = _handle ? zipCloseFileInZip(_handle) : -1; + } + return error(); + } + + void close() { + if (_handle && zipClose(_handle, nullptr) != ZIP_OK && _error == ZIP_OK) { + _error = -1; + } + _handle = nullptr; + } + + int error() const { + if (auto dataError = _data.error()) { + return dataError; + } + return _error; + } + + QByteArray result() const { + return _data.result(); + } + + ~FileToWrite() { + close(); + } + +private: + internal::InMemoryFile _data; + zipFile _handle = nullptr; + int _error = 0; + +}; + +} // namespace zlib diff --git a/Telegram/SourceFiles/data/data_drafts.cpp b/Telegram/SourceFiles/data/data_drafts.cpp index 6179e45b2..9cd3c617d 100644 --- a/Telegram/SourceFiles/data/data_drafts.cpp +++ b/Telegram/SourceFiles/data/data_drafts.cpp @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "data/data_drafts.h" +#include "ui/widgets/input_fields.h" #include "historywidget.h" #include "mainwidget.h" #include "localstorage.h" @@ -30,6 +31,13 @@ namespace { } // namespace +Draft::Draft(const Ui::FlatTextarea *field, MsgId msgId, bool previewCancelled, mtpRequestId saveRequestId) + : textWithTags(field->getTextWithTags()) + , msgId(msgId) + , cursor(field) + , previewCancelled(previewCancelled) { +} + void applyPeerCloudDraft(PeerId peerId, const MTPDdraftMessage &draft) { auto history = App::history(peerId); auto text = qs(draft.vmessage); diff --git a/Telegram/SourceFiles/data/data_drafts.h b/Telegram/SourceFiles/data/data_drafts.h index b40aa1c51..d5b369bc7 100644 --- a/Telegram/SourceFiles/data/data_drafts.h +++ b/Telegram/SourceFiles/data/data_drafts.h @@ -20,6 +20,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once +namespace Ui { +class FlatTextarea; +} // namespace Ui + namespace Data { void applyPeerCloudDraft(PeerId peerId, const MTPDdraftMessage &draft); @@ -35,12 +39,8 @@ struct Draft { , previewCancelled(previewCancelled) , saveRequestId(saveRequestId) { } - Draft(const FlatTextarea &field, MsgId msgId, bool previewCancelled, mtpRequestId saveRequestId = 0) - : textWithTags(field.getTextWithTags()) - , msgId(msgId) - , cursor(field) - , previewCancelled(previewCancelled) { - } + Draft(const Ui::FlatTextarea *field, MsgId msgId, bool previewCancelled, mtpRequestId saveRequestId = 0); + QDateTime date; TextWithTags textWithTags; MsgId msgId = 0; // replyToId for message draft, editMsgId for edit draft diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index abf2223dc..024233007 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -19,34 +19,30 @@ 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"; -dialogsUnreadFg: #ffffff; -dialogsUnreadFgActive: #5b94bf; -dialogsUnreadBg: windowActiveBg; -dialogsUnreadBgMuted: #bbbbbb; -dialogsUnreadBgActive: #ffffff; -dialogsUnreadBgMutedActive: #d3e2ee; +using "ui/widgets/widgets.style"; + dialogsUnreadFont: font(12px bold); dialogsUnreadHeight: 19px; dialogsUnreadPadding: 5px; -dialogsBg: windowBg; -dialogsBgOver: #f5f5f5; -dialogsBgActive: #6a91b1; +dialogsRipple: defaultRippleAnimation; +dialogsRippleBg: windowBgRipple; +dialogsRippleBgActive: activeButtonBgRipple; + dialogsTextFont: font(fsize); -dialogsTextFg: #888888; -dialogsTextFgService: #4981af; -dialogsTextFgActive: #ffffff; +dialogsTextStyle: TextStyle(defaultTextStyle) { + font: dialogsTextFont; + linkFont: dialogsTextFont; + linkFontOver: dialogsTextFont; +} dialogsDateFont: font(13px); -dialogsDateFgActive: #ffffff; -dialogsDateFg: #a8a8a8; dialogsDateSkip: 5px; -dialogsNameFg: #000; dialogsNameTop: 2px; dialogsRowHeight: 62px; -dialogsFilterPadding: 10px; +dialogsFilterPadding: point(7px, 7px); +dialogsFilterSkip: 4px; dialogsPhotoSize: 46px; dialogsPhotoPadding: 12px; dialogsPadding: point(10px, 8px); @@ -58,73 +54,152 @@ dialogsSkip: 8px; dialogsWidthMin: 260px; dialogsWidthMax: 540px; dialogsTextWidthMin: 150px; -dialogsScroll: flatScroll(scrollDef) { +dialogsScroll: ScrollArea(defaultScrollArea) { topsh: 0px; bottomsh: 0px; } -dialogsTextStyle: textStyle(defaultTextStyle) { +dialogsTextPalette: TextPalette(defaultTextPalette) { linkFg: dialogsTextFgService; - linkFgDown: dialogsTextFgService; - linkFlagsOver: font(fsize); } -dialogsTextStyleDraft: textStyle(dialogsTextStyle) { - linkFg: #dd4b39; - linkFgDown: #dd4b39; +dialogsTextPaletteOver: TextPalette(defaultTextPalette) { + linkFg: dialogsTextFgServiceOver; } -dialogsTextStyleActive: textStyle(dialogsTextStyle) { - linkFg: dialogsTextFgActive; - linkFgDown: dialogsTextFgActive; +dialogsTextPaletteActive: TextPalette(defaultTextPalette) { + linkFg: dialogsTextFgServiceActive; } -dialogsTextStyleDraftActive: textStyle(dialogsTextStyle) { - linkFg: #c6e1f7; - linkFgDown: #c6e1f7; +dialogsTextPaletteDraft: TextPalette(defaultTextPalette) { + linkFg: dialogsDraftFg; +} +dialogsTextPaletteDraftOver: TextPalette(defaultTextPalette) { + linkFg: dialogsDraftFgOver; +} +dialogsTextPaletteDraftActive: TextPalette(defaultTextPalette) { + linkFg: dialogsDraftFgActive; } -dialogsNewChatIcon: icon {{ "dialogs_new_chat", #b7b7b7, point(9px, 10px) }}; -dialogsNewChatButton: RoundButton { - width: 36px; - height: 36px; - icon: dialogsNewChatIcon; +dialogsMenuToggle: IconButton { + width: 40px; + height: 40px; - textTop: 5px; - downTextTop: 6px; + icon: icon {{ "dialogs_menu", dialogsMenuIconFg }}; + iconOver: icon {{ "dialogs_menu", dialogsMenuIconFgOver }}; + iconPosition: point(10px, 10px); - textFg: transparent; - textFgOver: transparent; - secondaryTextFg: transparent; - secondaryTextFgOver: transparent; - textBg: transparent; - textBgOver: transparent; + rippleAreaPosition: point(0px, 0px); + rippleAreaSize: 40px; + ripple: RippleAnimation(defaultRippleAnimation) { + color: windowBgOver; + } +} +dialogsLock: IconButton(dialogsMenuToggle) { + icon: icon {{ "dialogs_lock", dialogsMenuIconFg }}; + iconOver: icon {{ "dialogs_lock", dialogsMenuIconFgOver }}; +} +dialogsUnlockIcon: icon {{ "dialogs_unlock", dialogsMenuIconFg }}; +dialogsUnlockIconOver: icon {{ "dialogs_unlock", dialogsMenuIconFgOver }}; + +dialogsFilter: FlatInput(defaultFlatInput) { + font: font(fsize); + phColor: placeholderFg; + phFocusColor: placeholderFgActive; + + width: 240px; + height: 32px; + textMrg: margins(12px, 3px, 30px, 3px); +} +dialogsCancelSearchInPeer: IconButton(dialogsMenuToggle) { + icon: icon {{ "dialogs_cancel_search", dialogsMenuIconFg }}; + iconOver: icon {{ "dialogs_cancel_search", dialogsMenuIconFgOver }}; + iconPosition: point(11px, 11px); +} +dialogsCancelSearch: CrossButton { + width: 32px; + height: 32px; + + cross: CrossAnimation { + size: 32px; + skip: 10px; + stroke: 2px; + minScale: 0.3; + } + crossFg: dialogsMenuIconFg; + crossFgOver: dialogsMenuIconFgOver; + crossPosition: point(0px, 0px); + + duration: 150; + ripple: emptyRippleAnimation; } dialogsChatTypeSkip: 22px; -dialogsChatIcon: icon {{ "dialogs_chat", #373737, point(1px, 4px) }}; -dialogsChatActiveIcon: icon {{ "dialogs_chat", #ffffff, point(1px, 4px) }}; -dialogsChannelIcon: icon {{ "dialogs_channel", #373737, point(3px, 4px) }}; -dialogsChannelActiveIcon: icon {{ "dialogs_channel", #ffffff, point(3px, 4px) }}; +dialogsChatIcon: icon {{ "dialogs_chat", dialogsChatIconFg, point(1px, 4px) }}; +dialogsChatIconOver: icon {{ "dialogs_chat", dialogsChatIconFgOver, point(1px, 4px) }}; +dialogsChatIconActive: icon {{ "dialogs_chat", dialogsChatIconFgActive, point(1px, 4px) }}; +dialogsChannelIcon: icon {{ "dialogs_channel", dialogsChatIconFg, point(3px, 4px) }}; +dialogsChannelIconOver: icon {{ "dialogs_channel", dialogsChatIconFgOver, point(3px, 4px) }}; +dialogsChannelIconActive: icon {{ "dialogs_channel", dialogsChatIconFgActive, point(3px, 4px) }}; -dialogsSentStateFg: #5dc452; dialogsSendStateSkip: 20px; -dialogsSendingIcon: icon {{ "dialogs_sending", #c1c1c1, point(8px, 4px) }}; -dialogsSendingActiveIcon: icon {{ "dialogs_sending", #ffffff99, point(8px, 4px) }}; -dialogsSentIcon: icon {{ "dialogs_sent", dialogsSentStateFg, point(10px, 4px) }}; -dialogsSentActiveIcon: icon {{ "dialogs_sent", #ffffff, point(10px, 4px) }}; -dialogsReceivedIcon: icon {{ "dialogs_received", dialogsSentStateFg, point(5px, 4px) }}; -dialogsReceivedActiveIcon: icon {{ "dialogs_received", #ffffff, point(5px, 4px) }}; +dialogsSendingIcon: icon {{ "dialogs_sending", dialogsSendingIconFg, point(8px, 4px) }}; +dialogsSendingIconOver: icon {{ "dialogs_sending", dialogsSendingIconFgOver, point(8px, 4px) }}; +dialogsSendingIconActive: icon {{ "dialogs_sending", dialogsSendingIconFgActive, point(8px, 4px) }}; +dialogsSentIcon: icon {{ "dialogs_sent", dialogsSentIconFg, point(10px, 4px) }}; +dialogsSentIconOver: icon {{ "dialogs_sent", dialogsSentIconFgOver, point(10px, 4px) }}; +dialogsSentIconActive: icon {{ "dialogs_sent", dialogsSentIconFgActive, point(10px, 4px) }}; +dialogsReceivedIcon: icon {{ "dialogs_received", dialogsSentIconFg, point(5px, 4px) }}; +dialogsReceivedIconOver: icon {{ "dialogs_received", dialogsSentIconFgOver, point(5px, 4px) }}; +dialogsReceivedIconActive: icon {{ "dialogs_received", dialogsSentIconFgActive, point(5px, 4px) }}; +dialogsPinnedIcon: icon {{ "dialogs_pinned", dialogsUnreadBgMuted }}; +dialogsPinnedIconOver: icon {{ "dialogs_pinned", dialogsUnreadBgMutedOver }}; +dialogsPinnedIconActive: icon {{ "dialogs_pinned", dialogsUnreadBgMutedActive }}; dialogsVerifiedIcon: icon { - { "dialogs_verified_star", #4abcf1, point(4px, 2px) }, - { "dialogs_verified_check", #ffffff, point(7px, 7px) }, + { "dialogs_verified_star", dialogsVerifiedIconBg, point(4px, 2px) }, + { "dialogs_verified_check", dialogsVerifiedIconFg, point(7px, 7px) }, }; -dialogsVerifiedActiveIcon: icon { - { "dialogs_verified_star", #ffffff, point(4px, 2px) }, - { "dialogs_verified_check", #6a91b1, point(7px, 7px) }, +dialogsVerifiedIconOver: icon { + { "dialogs_verified_star", dialogsVerifiedIconBgOver, point(4px, 2px) }, + { "dialogs_verified_check", dialogsVerifiedIconFgOver, point(7px, 7px) }, +}; +dialogsVerifiedIconActive: icon { + { "dialogs_verified_star", dialogsVerifiedIconBgActive, point(4px, 2px) }, + { "dialogs_verified_check", dialogsVerifiedIconFgActive, point(7px, 7px) }, }; -historySendingIcon: icon {{ "dialogs_sending", #98d292, point(5px, 5px) }}; -historySendingInvertedIcon: icon {{ "dialogs_sending", #ffffffc8, point(5px, 5px) }}; -historyViewsSendingIcon: icon {{ "dialogs_sending", #a0adb5, point(3px, 0px) }}; -historyViewsSendingInvertedIcon: icon {{ "dialogs_sending", #ffffffc8, point(3px, 0px) }}; +historySendingIcon: icon {{ "dialogs_sending", historySendingOutIconFg, point(5px, 5px) }}; +historySendingInvertedIcon: icon {{ "dialogs_sending", historySendingInvertedIconFg, point(5px, 5px) }}; +historyViewsSendingIcon: icon {{ "dialogs_sending", historySendingInIconFg, point(3px, 0px) }}; +historyViewsSendingInvertedIcon: icon {{ "dialogs_sending", historySendingInvertedIconFg, point(3px, 0px) }}; -settingsEditIcon: icon {{ "dialogs_new_chat", #b7b7b7, point(3px, 9px) }}; +dialogsUpdateButton: FlatButton { + color: activeButtonFg; + overColor: activeButtonFgOver; + + bgColor: activeButtonBg; + overBgColor: activeButtonBgOver; + + width: -34px; + height: 46px; + + textTop: 14px; + + font: semiboldFont; + overFont: semiboldFont; + + ripple: RippleAnimation(defaultRippleAnimation) { + color: activeButtonBgRipple; + } +} + +dialogsForwardHeight: 32px; +dialogsForwardTextLeft: 35px; +dialogsForwardTextTop: 6px; +dialogsForwardCancel: IconButton { + width: 34px; + height: dialogsForwardHeight; + + icon: dialogsForwardCancelIcon; + iconOver: dialogsForwardCancelIcon; + iconPosition: point(12px, 11px); +} +dialogsForwardFont: semiboldFont; diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp index dd6d7c230..2cf41f060 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp @@ -35,9 +35,12 @@ namespace { // Show all dates that are in the last 20 hours in time format. constexpr int kRecentlyInSeconds = 20 * 3600; -void paintRowDate(Painter &p, const QDateTime &date, QRect &rectForName, bool active) { - QDateTime now(QDateTime::currentDateTime()), lastTime(date); - QDate nowDate(now.date()), lastDate(lastTime.date()); +void paintRowDate(Painter &p, const QDateTime &date, QRect &rectForName, bool active, bool selected) { + auto now = QDateTime::currentDateTime(); + auto lastTime = date; + auto nowDate = now.date(); + auto lastDate = lastTime.date(); + QString dt; bool wasSameDay = (lastDate == nowDate); bool wasRecently = qAbs(lastTime.secsTo(now)) < kRecentlyInSeconds; @@ -51,100 +54,111 @@ void paintRowDate(Painter &p, const QDateTime &date, QRect &rectForName, bool ac int32 dtWidth = st::dialogsDateFont->width(dt); rectForName.setWidth(rectForName.width() - dtWidth - st::dialogsDateSkip); p.setFont(st::dialogsDateFont); - p.setPen(active ? st::dialogsDateFgActive : st::dialogsDateFg); + p.setPen(active ? st::dialogsDateFgActive : (selected ? st::dialogsDateFgOver : st::dialogsDateFg)); p.drawText(rectForName.left() + rectForName.width() + st::dialogsDateSkip, rectForName.top() + st::msgNameFont->height - st::msgDateFont->descent, dt); } template -void paintRow(Painter &p, History *history, HistoryItem *item, Data::Draft *draft, QDateTime date, int w, bool active, bool selected, bool onlyBackground, PaintItemCallback paintItemCallback) { - QRect fullRect(0, 0, w, st::dialogsRowHeight); +void paintRow(Painter &p, const RippleRow *row, History *history, HistoryItem *item, Data::Draft *draft, QDateTime date, int fullWidth, bool active, bool selected, bool onlyBackground, TimeMs ms, PaintItemCallback paintItemCallback) { + QRect fullRect(0, 0, fullWidth, st::dialogsRowHeight); p.fillRect(fullRect, active ? st::dialogsBgActive : (selected ? st::dialogsBgOver : st::dialogsBg)); + row->paintRipple(p, 0, 0, fullWidth, ms, &(active ? st::dialogsRippleBgActive : st::dialogsRippleBg)->c); if (onlyBackground) return; - PeerData *userpicPeer = (history->peer->migrateTo() ? history->peer->migrateTo() : history->peer); - userpicPeer->paintUserpicLeft(p, st::dialogsPhotoSize, st::dialogsPadding.x(), st::dialogsPadding.y(), w); + auto userpicPeer = (history->peer->migrateTo() ? history->peer->migrateTo() : history->peer); + userpicPeer->paintUserpicLeft(p, st::dialogsPadding.x(), st::dialogsPadding.y(), fullWidth, st::dialogsPhotoSize); - int32 nameleft = st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPhotoPadding; - int32 namewidth = w - nameleft - st::dialogsPadding.x(); + auto nameleft = st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPhotoPadding; + auto namewidth = fullWidth - nameleft - st::dialogsPadding.x(); QRect rectForName(nameleft, st::dialogsPadding.y() + st::dialogsNameTop, namewidth, st::msgNameFont->height); - if (auto chatTypeIcon = ChatTypeIcon(history->peer, active)) { - chatTypeIcon->paint(p, rectForName.topLeft(), w); + if (auto chatTypeIcon = ChatTypeIcon(history->peer, active, selected)) { + chatTypeIcon->paint(p, rectForName.topLeft(), fullWidth); rectForName.setLeft(rectForName.left() + st::dialogsChatTypeSkip); } int texttop = st::dialogsPadding.y() + st::msgNameFont->height + st::dialogsSkip; if (draft) { - paintRowDate(p, date, rectForName, active); + paintRowDate(p, date, rectForName, active, selected); + + auto availableWidth = namewidth; + if (history->isPinnedDialog()) { + auto &icon = (active ? st::dialogsPinnedIconActive : (selected ? st::dialogsPinnedIconOver : st::dialogsPinnedIcon)); + icon.paint(p, fullWidth - st::dialogsPadding.x() - icon.width(), texttop, fullWidth); + availableWidth -= icon.width() + st::dialogsUnreadPadding; + } p.setFont(st::dialogsTextFont); - p.setPen(active ? st::dialogsTextFgActive : st::dialogsTextFgService); - if (history->typing.isEmpty() && history->sendActions.isEmpty()) { + auto &color = active ? st::dialogsTextFgServiceActive : (selected ? st::dialogsTextFgServiceOver : st::dialogsTextFgService); + if (!history->paintSendAction(p, nameleft, texttop, availableWidth, fullWidth, color, ms)) { if (history->cloudDraftTextCache.isEmpty()) { auto draftWrapped = textcmdLink(1, lng_dialogs_text_from_wrapped(lt_from, lang(lng_from_draft))); auto draftText = lng_dialogs_text_with_from(lt_from_part, draftWrapped, lt_message, textClean(draft->textWithTags.text)); - history->cloudDraftTextCache.setText(st::dialogsTextFont, draftText, _textDlgOptions); + history->cloudDraftTextCache.setText(st::dialogsTextStyle, draftText, _textDlgOptions); } - textstyleSet(&(active ? st::dialogsTextStyleDraftActive : st::dialogsTextStyleDraft)); - p.setFont(st::dialogsTextFont); - p.setPen(active ? st::dialogsTextFgActive : st::dialogsTextFg); - history->cloudDraftTextCache.drawElided(p, nameleft, texttop, namewidth, 1); - textstyleRestore(); - } else { - history->typingText.drawElided(p, nameleft, texttop, namewidth); + p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg)); + p.setTextPalette(active ? st::dialogsTextPaletteDraftActive : (selected ? st::dialogsTextPaletteDraftOver : st::dialogsTextPaletteDraft)); + history->cloudDraftTextCache.drawElided(p, nameleft, texttop, availableWidth, 1); + p.restoreTextPalette(); } } else if (!item) { + auto &color = active ? st::dialogsTextFgServiceActive : (selected ? st::dialogsTextFgServiceOver : st::dialogsTextFgService); p.setFont(st::dialogsTextFont); - p.setPen(active ? st::dialogsTextFgActive : st::dialogsTextFgService); - if (history->typing.isEmpty() && history->sendActions.isEmpty()) { + if (!history->paintSendAction(p, nameleft, texttop, namewidth, fullWidth, color, ms)) { + p.setPen(color); p.drawText(nameleft, texttop + st::msgNameFont->ascent, lang(lng_empty_history)); - } else { - history->typingText.drawElided(p, nameleft, texttop, namewidth); } } else if (!item->isEmpty()) { - paintRowDate(p, date, rectForName, active); + paintRowDate(p, date, rectForName, active, selected); paintItemCallback(nameleft, namewidth, item); } - auto sendStateIcon = ([draft, item, active]() -> const style::icon* { + auto sendStateIcon = ([draft, item, active, selected]() -> const style::icon* { if (draft) { if (draft->saveRequestId) { - return &(active ? st::dialogsSendingActiveIcon : st::dialogsSendingIcon); + return &(active ? st::dialogsSendingIconActive : (selected ? st::dialogsSendingIconOver : st::dialogsSendingIcon)); } } else if (item && !item->isEmpty() && item->needCheck()) { if (item->id > 0) { if (item->unread()) { - return &(active ? st::dialogsSentActiveIcon : st::dialogsSentIcon); + return &(active ? st::dialogsSentIconActive : (selected ? st::dialogsSentIconOver : st::dialogsSentIcon)); } - return &(active ? st::dialogsReceivedActiveIcon : st::dialogsReceivedIcon); + return &(active ? st::dialogsReceivedIconActive : (selected ? st::dialogsReceivedIconOver : st::dialogsReceivedIcon)); } - return &(active ? st::dialogsSendingActiveIcon : st::dialogsSendingIcon); + return &(active ? st::dialogsSendingIconActive : (selected ? st::dialogsSendingIconOver : st::dialogsSendingIcon)); } return nullptr; })(); if (sendStateIcon) { rectForName.setWidth(rectForName.width() - st::dialogsSendStateSkip); - sendStateIcon->paint(p, rectForName.topLeft() + QPoint(rectForName.width(), 0), w); + sendStateIcon->paint(p, rectForName.topLeft() + QPoint(rectForName.width(), 0), fullWidth); } - if (history->peer->isUser() && history->peer->isVerified()) { - auto icon = &(active ? st::dialogsVerifiedActiveIcon : st::dialogsVerifiedIcon); + if (history->peer->isVerified()) { + auto icon = &(active ? st::dialogsVerifiedIconActive : (selected ? st::dialogsVerifiedIconOver : st::dialogsVerifiedIcon)); rectForName.setWidth(rectForName.width() - icon->width()); - icon->paint(p, rectForName.topLeft() + QPoint(qMin(history->peer->dialogName().maxWidth(), rectForName.width()), 0), w); + icon->paint(p, rectForName.topLeft() + QPoint(qMin(history->peer->dialogName().maxWidth(), rectForName.width()), 0), fullWidth); } - p.setPen(active ? st::dialogsTextFgActive : st::dialogsNameFg); + p.setPen(active ? st::dialogsNameFgActive : (selected ? st::dialogsNameFgOver : st::dialogsNameFg)); history->peer->dialogName().drawElided(p, rectForName.left(), rectForName.top(), rectForName.width()); } struct UnreadBadgeSizeData { QImage circle; - QPixmap left[4], right[4]; + QPixmap left[6], right[6]; }; class UnreadBadgeStyleData : public Data::AbstractStructure { public: UnreadBadgeSizeData sizes[UnreadBadgeSizesCount]; - style::color bg[4] = { st::dialogsUnreadBg, st::dialogsUnreadBgActive, st::dialogsUnreadBgMuted, st::dialogsUnreadBgMutedActive }; + style::color bg[6] = { + st::dialogsUnreadBg, + st::dialogsUnreadBgOver, + st::dialogsUnreadBgActive, + st::dialogsUnreadBgMuted, + st::dialogsUnreadBgMutedOver, + st::dialogsUnreadBgMutedActive + }; }; Data::GlobalStructurePointer unreadBadgeStyle; @@ -162,11 +176,11 @@ QImage colorizeCircleHalf(UnreadBadgeSizeData *data, int size, int half, int xof } // namepsace -const style::icon *ChatTypeIcon(PeerData *peer, bool active) { +const style::icon *ChatTypeIcon(PeerData *peer, bool active, bool selected) { if (peer->isChat() || peer->isMegagroup()) { - return &(active ? st::dialogsChatActiveIcon : st::dialogsChatIcon); + return &(active ? st::dialogsChatIconActive : (selected ? st::dialogsChatIconOver : st::dialogsChatIcon)); } else if (peer->isChannel()) { - return &(active ? st::dialogsChannelActiveIcon : st::dialogsChannelIcon); + return &(active ? st::dialogsChannelIconActive : (selected ? st::dialogsChannelIconOver : st::dialogsChannelIcon)); } return nullptr; } @@ -174,7 +188,7 @@ const style::icon *ChatTypeIcon(PeerData *peer, bool active) { void paintUnreadBadge(Painter &p, const QRect &rect, const UnreadBadgeStyle &st) { t_assert(rect.height() == st.size); - int index = (st.active ? 0x01 : 0x00) | (st.muted ? 0x02 : 0x00); + int index = (st.muted ? 0x03 : 0x00) + (st.active ? 0x02 : (st.selected ? 0x01 : 0x00)); int size = st.size, sizehalf = size / 2; unreadBadgeStyle.createIfNull(); @@ -183,7 +197,7 @@ void paintUnreadBadge(Painter &p, const QRect &rect, const UnreadBadgeStyle &st) t_assert(st.sizeId < UnreadBadgeSizesCount); badgeData = &unreadBadgeStyle->sizes[st.sizeId]; } - style::color bg = unreadBadgeStyle->bg[index]; + auto bg = unreadBadgeStyle->bg[index]; if (badgeData->left[index].isNull()) { int imgsize = size * cIntRetinaFactor(), imgsizehalf = sizehalf * cIntRetinaFactor(); createCircleMask(badgeData, size); @@ -202,15 +216,17 @@ void paintUnreadBadge(Painter &p, const QRect &rect, const UnreadBadgeStyle &st) UnreadBadgeStyle::UnreadBadgeStyle() : align(style::al_right) , active(false) +, selected(false) , muted(false) , size(st::dialogsUnreadHeight) +, padding(st::dialogsUnreadPadding) , sizeId(UnreadBadgeInDialogs) , font(st::dialogsUnreadFont) { } void paintUnreadCount(Painter &p, const QString &text, int x, int y, const UnreadBadgeStyle &st, int *outUnreadWidth) { int unreadWidth = st.font->width(text); - int unreadRectWidth = unreadWidth + 2 * st::dialogsUnreadPadding; + int unreadRectWidth = unreadWidth + 2 * st.padding; int unreadRectHeight = st.size; accumulate_max(unreadRectWidth, unreadRectHeight); @@ -227,12 +243,13 @@ void paintUnreadCount(Painter &p, const QString &text, int x, int y, const Unrea paintUnreadBadge(p, QRect(unreadRectLeft, unreadRectTop, unreadRectWidth, unreadRectHeight), st); + auto textTop = st.textTop ? st.textTop : (unreadRectHeight - st.font->height) / 2; p.setFont(st.font); - p.setPen(st.active ? st::dialogsUnreadFgActive : st::dialogsUnreadFg); - p.drawText(unreadRectLeft + (unreadRectWidth - unreadWidth) / 2, unreadRectTop + (unreadRectHeight - st.font->height) / 2 + st.font->ascent, text); + p.setPen(st.active ? st::dialogsUnreadFgActive : (st.selected ? st::dialogsUnreadFgOver : st::dialogsUnreadFg)); + p.drawText(unreadRectLeft + (unreadRectWidth - unreadWidth) / 2, unreadRectTop + textTop + st.font->ascent, text); } -void RowPainter::paint(Painter &p, const Row *row, int w, bool active, bool selected, bool onlyBackground) { +void RowPainter::paint(Painter &p, const Row *row, int fullWidth, bool active, bool selected, bool onlyBackground, TimeMs ms) { auto history = row->history(); auto item = history->lastMsg; auto cloudDraft = history->cloudDraft(); @@ -258,58 +275,67 @@ void RowPainter::paint(Painter &p, const Row *row, int w, bool active, bool sele if (item && cloudDraft && unreadCount > 0) { cloudDraft = nullptr; // Draw item, if draft is older. } - paintRow(p, history, item, cloudDraft, displayDate(), w, active, selected, onlyBackground, [&p, w, active, history, unreadCount](int nameleft, int namewidth, HistoryItem *item) { + paintRow(p, row, history, item, cloudDraft, displayDate(), fullWidth, active, selected, onlyBackground, ms, [&p, fullWidth, active, selected, ms, history, unreadCount](int nameleft, int namewidth, HistoryItem *item) { int availableWidth = namewidth; int texttop = st::dialogsPadding.y() + st::msgNameFont->height + st::dialogsSkip; if (unreadCount) { auto counter = QString::number(unreadCount); auto mutedCounter = history->mute(); - int unreadRight = w - st::dialogsPadding.x(); - int unreadTop = texttop + st::dialogsTextFont->ascent - st::dialogsUnreadFont->ascent - (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2; - int unreadWidth = 0; + auto unreadRight = fullWidth - st::dialogsPadding.x(); + auto unreadTop = texttop + st::dialogsTextFont->ascent - st::dialogsUnreadFont->ascent - (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2; + auto unreadWidth = 0; UnreadBadgeStyle st; st.active = active; st.muted = history->mute(); paintUnreadCount(p, counter, unreadRight, unreadTop, st, &unreadWidth); - availableWidth -= unreadWidth + st::dialogsUnreadPadding; + availableWidth -= unreadWidth + st.padding; + } else if (history->isPinnedDialog()) { + auto &icon = (active ? st::dialogsPinnedIconActive : (selected ? st::dialogsPinnedIconOver : st::dialogsPinnedIcon)); + icon.paint(p, fullWidth - st::dialogsPadding.x() - icon.width(), texttop, fullWidth); + availableWidth -= icon.width() + st::dialogsUnreadPadding; } - if (history->typing.isEmpty() && history->sendActions.isEmpty()) { - item->drawInDialog(p, QRect(nameleft, texttop, availableWidth, st::dialogsTextFont->height), active, history->textCachedFor, history->lastItemTextCache); - } else { - p.setPen(active ? st::dialogsTextFgActive : st::dialogsTextFgService); - history->typingText.drawElided(p, nameleft, texttop, availableWidth); + auto &color = active ? st::dialogsTextFgServiceActive : (selected ? st::dialogsTextFgServiceOver : st::dialogsTextFgService); + if (!history->paintSendAction(p, nameleft, texttop, availableWidth, fullWidth, color, ms)) { + item->drawInDialog(p, QRect(nameleft, texttop, availableWidth, st::dialogsTextFont->height), active, selected, history->textCachedFor, history->lastItemTextCache); } }); } -void RowPainter::paint(Painter &p, const FakeRow *row, int w, bool active, bool selected, bool onlyBackground) { +void RowPainter::paint(Painter &p, const FakeRow *row, int fullWidth, bool active, bool selected, bool onlyBackground, TimeMs ms) { auto item = row->item(); auto history = item->history(); - paintRow(p, history, item, nullptr, item->date, w, active, selected, onlyBackground, [&p, row, active](int nameleft, int namewidth, HistoryItem *item) { + paintRow(p, row, history, item, nullptr, item->date, fullWidth, active, selected, onlyBackground, ms, [&p, row, active, selected](int nameleft, int namewidth, HistoryItem *item) { int lastWidth = namewidth, texttop = st::dialogsPadding.y() + st::msgNameFont->height + st::dialogsSkip; - item->drawInDialog(p, QRect(nameleft, texttop, lastWidth, st::dialogsTextFont->height), active, row->_cacheFor, row->_cache); + item->drawInDialog(p, QRect(nameleft, texttop, lastWidth, st::dialogsTextFont->height), active, selected, row->_cacheFor, row->_cache); }); } -void paintImportantSwitch(Painter &p, Mode current, int w, bool selected, bool onlyBackground) { - p.fillRect(0, 0, w, st::dialogsImportantBarHeight, selected ? st::dialogsBgOver : st::white); +QRect RowPainter::sendActionAnimationRect(int animationWidth, int animationHeight, int fullWidth, bool textUpdated) { + auto nameleft = st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPhotoPadding; + auto namewidth = fullWidth - nameleft - st::dialogsPadding.x(); + auto texttop = st::dialogsPadding.y() + st::msgNameFont->height + st::dialogsSkip; + return QRect(nameleft, texttop, textUpdated ? namewidth : animationWidth, animationHeight); +} + +void paintImportantSwitch(Painter &p, Mode current, int fullWidth, bool selected, bool onlyBackground) { + p.fillRect(0, 0, fullWidth, st::dialogsImportantBarHeight, selected ? st::dialogsBgOver : st::dialogsBg); if (onlyBackground) { return; } p.setFont(st::semiboldFont); - p.setPen(st::black); + p.setPen(st::dialogsNameFg); int unreadTop = (st::dialogsImportantBarHeight - st::dialogsUnreadHeight) / 2; bool mutedHidden = (current == Dialogs::Mode::Important); - QString text = mutedHidden ? qsl("Show all chats") : qsl("Hide muted chats"); + QString text = lang(mutedHidden ? lng_dialogs_show_all_chats : lng_dialogs_hide_muted_chats); int textBaseline = unreadTop + (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2 + st::dialogsUnreadFont->ascent; p.drawText(st::dialogsPadding.x(), textBaseline, text); if (mutedHidden) { if (int32 unread = App::histories().unreadMutedCount()) { - int unreadRight = w - st::dialogsPadding.x(); + int unreadRight = fullWidth - st::dialogsPadding.x(); UnreadBadgeStyle st; st.muted = true; paintUnreadCount(p, QString::number(unread), unreadRight, unreadTop, st, nullptr); @@ -317,5 +343,18 @@ void paintImportantSwitch(Painter &p, Mode current, int w, bool selected, bool o } } +void clearUnreadBadgesCache() { + if (unreadBadgeStyle) { + for (auto &data : unreadBadgeStyle->sizes) { + for (auto &left : data.left) { + left = QPixmap(); + } + for (auto &right : data.right) { + right = QPixmap(); + } + } + } +} + } // namespace Layout } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.h b/Telegram/SourceFiles/dialogs/dialogs_layout.h index 6e81e317b..4e8325409 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_layout.h +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.h @@ -27,15 +27,17 @@ class FakeRow; namespace Layout { -const style::icon *ChatTypeIcon(PeerData *peer, bool active); +const style::icon *ChatTypeIcon(PeerData *peer, bool active, bool selected); class RowPainter { public: - static void paint(Painter &p, const Row *row, int w, bool active, bool selected, bool onlyBackground); - static void paint(Painter &p, const FakeRow *row, int w, bool active, bool selected, bool onlyBackground); + static void paint(Painter &p, const Row *row, int fullWidth, bool active, bool selected, bool onlyBackground, TimeMs ms); + static void paint(Painter &p, const FakeRow *row, int fullWidth, bool active, bool selected, bool onlyBackground, TimeMs ms); + static QRect sendActionAnimationRect(int animationWidth, int animationHeight, int fullWidth, bool textUpdated); + }; -void paintImportantSwitch(Painter &p, Mode current, int w, bool selected, bool onlyBackground); +void paintImportantSwitch(Painter &p, Mode current, int fullWidth, bool selected, bool onlyBackground); enum UnreadBadgeSize { UnreadBadgeInDialogs = 0, @@ -50,12 +52,17 @@ struct UnreadBadgeStyle { style::align align; bool active; + bool selected; bool muted; + int textTop = 0; int size; + int padding; UnreadBadgeSize sizeId; style::font font; }; void paintUnreadCount(Painter &p, const QString &text, int x, int y, const UnreadBadgeStyle &st, int *outUnreadWidth = nullptr); +void clearUnreadBadgesCache(); + } // namespace Layout } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_list.cpp index 63eff845b..f9347e5e3 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_list.cpp @@ -47,7 +47,7 @@ void List::adjustCurrent(int32 y, int32 h) const { } } -void List::paint(Painter &p, int32 w, int32 hFrom, int32 hTo, PeerData *act, PeerData *sel, bool onlyBackground) const { +void List::paint(Painter &p, int32 w, int32 hFrom, int32 hTo, PeerData *act, PeerData *sel, bool onlyBackground, TimeMs ms) const { adjustCurrent(hFrom, st::dialogsRowHeight); Row *row = _current; @@ -55,7 +55,7 @@ void List::paint(Painter &p, int32 w, int32 hFrom, int32 hTo, PeerData *act, Pee while (row != _end && row->_pos * st::dialogsRowHeight < hTo) { bool active = (row->history()->peer == act) || (row->history()->peer->migrateTo() && row->history()->peer->migrateTo() == act); bool selected = (row->history()->peer == sel); - Layout::RowPainter::paint(p, row, w, active, selected, onlyBackground); + Layout::RowPainter::paint(p, row, w, active, selected, onlyBackground, ms); row = row->_next; p.translate(0, st::dialogsRowHeight); } @@ -201,7 +201,7 @@ bool List::del(PeerId peerId, Row *replacedBy) { auto i = _rowByPeer.find(peerId); if (i == _rowByPeer.cend()) return false; - Row *row = i.value(); + auto row = i.value(); if (App::main()) { emit App::main()->dialogRowReplaced(row, replacedBy); } @@ -209,7 +209,7 @@ bool List::del(PeerId peerId, Row *replacedBy) { if (row == _current) { _current = row->_next; } - for (Row *change = row->_next; change != _end; change = change->_next) { + for (auto change = row->_next; change != _end; change = change->_next) { --change->_pos; } --_end->_pos; diff --git a/Telegram/SourceFiles/dialogs/dialogs_list.h b/Telegram/SourceFiles/dialogs/dialogs_list.h index c6c40950a..3bf402b81 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_list.h +++ b/Telegram/SourceFiles/dialogs/dialogs_list.h @@ -51,7 +51,7 @@ public: return *i; } - void paint(Painter &p, int32 w, int32 hFrom, int32 hTo, PeerData *act, PeerData *sel, bool onlyBackground) const; + void paint(Painter &p, int32 w, int32 hFrom, int32 hTo, PeerData *act, PeerData *sel, bool onlyBackground, TimeMs ms) const; Row *addToEnd(History *history); Row *adjustByName(const PeerData *peer); Row *addByName(History *history); diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.cpp b/Telegram/SourceFiles/dialogs/dialogs_row.cpp index 74de183a6..88311ed15 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_row.cpp @@ -22,9 +22,37 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "dialogs/dialogs_row.h" #include "styles/style_dialogs.h" +#include "ui/effects/ripple_animation.h" +#include "mainwidget.h" namespace Dialogs { +RippleRow::RippleRow() = default; +RippleRow::~RippleRow() = default; + +void RippleRow::addRipple(QPoint origin, QSize size, base::lambda_copy &&updateCallback) { + if (!_ripple) { + auto mask = Ui::RippleAnimation::rectMask(size); + _ripple = std_::make_unique(st::dialogsRipple, std_::move(mask), std_::move(updateCallback)); + } + _ripple->add(origin); +} + +void RippleRow::stopLastRipple() { + if (_ripple) { + _ripple->lastStop(); + } +} + +void RippleRow::paintRipple(Painter &p, int x, int y, int outerWidth, TimeMs ms, const QColor *colorOverride) const { + if (_ripple) { + _ripple->paint(p, x, y, outerWidth, ms, colorOverride); + if (_ripple->empty()) { + _ripple.reset(); + } + } +} + FakeRow::FakeRow(HistoryItem *item) : _item(item), _cache(st::dialogsTextWidthMin) { } diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.h b/Telegram/SourceFiles/dialogs/dialogs_row.h index 7327f094e..48426c5c3 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.h +++ b/Telegram/SourceFiles/dialogs/dialogs_row.h @@ -25,13 +25,32 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org class History; class HistoryItem; +namespace Ui { +class RippleAnimation; +} // namespace Ui + namespace Dialogs { namespace Layout { class RowPainter; } // namespace Layout +class RippleRow { +public: + RippleRow(); + ~RippleRow(); + + void addRipple(QPoint origin, QSize size, base::lambda_copy &&updateCallback); + void stopLastRipple(); + + void paintRipple(Painter &p, int x, int y, int outerWidth, TimeMs ms, const QColor *colorOverride = nullptr) const; + +private: + mutable std_::unique_ptr _ripple; + +}; + class List; -class Row { +class Row : public RippleRow { public: Row(History *history, Row *prev, Row *next, int pos) : _history(history) @@ -57,7 +76,7 @@ private: }; -class FakeRow { +class FakeRow : public RippleRow { public: FakeRow(HistoryItem *item); diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index 55d84da20..b14918268 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -24,8 +24,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "dialogs/dialogs_indexed_list.h" #include "dialogs/dialogs_layout.h" #include "styles/style_dialogs.h" -#include "ui/buttons/round_button.h" -#include "ui/popupmenu.h" +#include "styles/style_stickers.h" +#include "styles/style_history.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/popup_menu.h" #include "data/data_drafts.h" #include "lang.h" #include "application.h" @@ -35,47 +37,87 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "boxes/addcontactbox.h" #include "boxes/contactsbox.h" #include "boxes/confirmbox.h" +#include "boxes/aboutbox.h" #include "localstorage.h" #include "apiwrap.h" +#include "ui/widgets/dropdown_menu.h" +#include "ui/widgets/input_fields.h" +#include "window/window_theme.h" +#include "autoupdater.h" -DialogsInner::DialogsInner(QWidget *parent, MainWidget *main) : SplittedWidget(parent) -, dialogs(std_::make_unique(Dialogs::SortMode::Date)) -, contactsNoDialogs(std_::make_unique(Dialogs::SortMode::Name)) -, contacts(std_::make_unique(Dialogs::SortMode::Name)) +namespace { + +constexpr auto kHashtagResultsLimit = 5; + +} // namespace + +struct DialogsInner::ImportantSwitch { + Dialogs::RippleRow row; +}; + +struct DialogsInner::HashtagResult { + HashtagResult(const QString &tag) : tag(tag) { + } + QString tag; + Dialogs::RippleRow row; +}; + +struct DialogsInner::PeerSearchResult { + PeerSearchResult(PeerData *peer) : peer(peer) { + } + PeerData *peer; + Dialogs::RippleRow row; +}; + +DialogsInner::DialogsInner(QWidget *parent, QWidget *main) : SplittedWidget(parent) +, _dialogs(std_::make_unique(Dialogs::SortMode::Date)) +, _contactsNoDialogs(std_::make_unique(Dialogs::SortMode::Name)) +, _contacts(std_::make_unique(Dialogs::SortMode::Name)) , _addContactLnk(this, lang(lng_add_contact_button)) -, _cancelSearchInPeer(this, st::btnCancelSearch) { +, _cancelSearchInPeer(this, st::dialogsCancelSearchInPeer) { if (Global::DialogsModeEnabled()) { - importantDialogs = std_::make_unique(Dialogs::SortMode::Date); + _dialogsImportant = std_::make_unique(Dialogs::SortMode::Date); + _importantSwitch = std_::make_unique(); } connect(main, SIGNAL(peerNameChanged(PeerData*, const PeerData::Names&, const PeerData::NameFirstChars&)), this, SLOT(onPeerNameChanged(PeerData*, const PeerData::Names&, const PeerData::NameFirstChars&))); connect(main, SIGNAL(peerPhotoChanged(PeerData*)), this, SLOT(onPeerPhotoChanged(PeerData*))); connect(main, SIGNAL(dialogRowReplaced(Dialogs::Row*,Dialogs::Row*)), this, SLOT(onDialogRowReplaced(Dialogs::Row*,Dialogs::Row*))); - connect(&_addContactLnk, SIGNAL(clicked()), App::wnd(), SLOT(onShowAddContact())); - connect(&_cancelSearchInPeer, SIGNAL(clicked()), this, SIGNAL(cancelSearchInPeer())); - _cancelSearchInPeer.hide(); + connect(_addContactLnk, SIGNAL(clicked()), App::wnd(), SLOT(onShowAddContact())); + connect(_cancelSearchInPeer, SIGNAL(clicked()), this, SIGNAL(cancelSearchInPeer())); + _cancelSearchInPeer->hide(); subscribe(FileDownload::ImageLoaded(), [this] { update(); }); subscribe(Global::RefItemRemoved(), [this](HistoryItem *item) { itemRemoved(item); }); + subscribe(App::histories().sendActionAnimationUpdated(), [this](const Histories::SendActionAnimationUpdate &update) { + auto updateRect = Dialogs::Layout::RowPainter::sendActionAnimationRect(update.width, update.height, getFullWidth(), update.textUpdated); + updateDialogRow(update.history->peer, MsgId(0), updateRect, UpdateRowSection::Default | UpdateRowSection::Filtered); + }); + + subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &data) { + if (data.paletteChanged()) { + Dialogs::Layout::clearUnreadBadgesCache(); + } + }); refresh(); } int DialogsInner::dialogsOffset() const { - return importantDialogs ? st::dialogsImportantBarHeight : 0; + return _dialogsImportant ? st::dialogsImportantBarHeight : 0; } int DialogsInner::filteredOffset() const { return _hashtagResults.size() * st::mentionHeight; } -int DialogsInner::peopleOffset() const { +int DialogsInner::peerSearchOffset() const { return filteredOffset() + (_filterResults.size() * st::dialogsRowHeight) + st::searchedBarHeight; } int DialogsInner::searchedOffset() const { - int result = peopleOffset() + (_peopleResults.isEmpty() ? 0 : ((_peopleResults.size() * st::dialogsRowHeight) + st::searchedBarHeight)); + int result = peerSearchOffset() + (_peerSearchResults.isEmpty() ? 0 : ((_peerSearchResults.size() * st::dialogsRowHeight) + st::searchedBarHeight)); if (_searchInPeer) result += st::dialogsRowHeight; return result; } @@ -86,29 +128,32 @@ void DialogsInner::paintRegion(Painter &p, const QRegion ®ion, bool paintingO if (!App::main()) return; - QRect r(region.boundingRect()); + auto r = region.boundingRect(); if (!paintingOther) { p.setClipRect(r); } - + auto fullWidth = getFullWidth(); + auto ms = getms(); if (_state == DefaultState) { QRect dialogsClip = r; - if (importantDialogs) { - Dialogs::Layout::paintImportantSwitch(p, Global::DialogsMode(), fullWidth(), _importantSwitchSel, paintingOther); + if (_dialogsImportant) { + auto selected = isPressed() ? _importantSwitchPressed : _importantSwitchSelected; + Dialogs::Layout::paintImportantSwitch(p, Global::DialogsMode(), fullWidth, selected, paintingOther); dialogsClip.translate(0, -st::dialogsImportantBarHeight); p.translate(0, st::dialogsImportantBarHeight); } - int32 otherStart = shownDialogs()->size() * st::dialogsRowHeight; - PeerData *active = App::main()->activePeer(), *selected = _menuPeer ? _menuPeer : (_sel ? _sel->history()->peer : 0); + auto otherStart = shownDialogs()->size() * st::dialogsRowHeight; + auto active = App::main()->activePeer(); + auto selected = _menuPeer ? _menuPeer : (isPressed() ? (_pressed ? _pressed->history()->peer : nullptr) : (_selected ? _selected->history()->peer : nullptr)); if (otherStart) { - shownDialogs()->all().paint(p, fullWidth(), dialogsClip.top(), dialogsClip.top() + dialogsClip.height(), active, selected, paintingOther); + shownDialogs()->all().paint(p, fullWidth, dialogsClip.top(), dialogsClip.top() + dialogsClip.height(), active, selected, paintingOther, ms); } if (!otherStart) { - p.fillRect(dialogsClip, st::white); + p.fillRect(dialogsClip, st::dialogsBg); if (!paintingOther) { p.setFont(st::noContactsFont); p.setPen(st::noContactsColor); - p.drawText(QRect(0, 0, fullWidth(), st::noContactsHeight - (cContactsReceived() ? st::noContactsFont->height : 0)), lang(cContactsReceived() ? lng_no_chats : lng_contacts_loading), style::al_center); + p.drawText(QRect(0, 0, fullWidth, st::noContactsHeight - (cContactsReceived() ? st::noContactsFont->height : 0)), lang(cContactsReceived() ? lng_no_chats : lng_contacts_loading), style::al_center); } } } else if (_state == FilteredState || _state == SearchedState) { @@ -117,20 +162,24 @@ void DialogsInner::paintRegion(Painter &p, const QRegion ®ion, bool paintingO int32 to = ceilclamp(r.y() + r.height(), st::mentionHeight, 0, _hashtagResults.size()); p.translate(0, from * st::mentionHeight); if (from < _hashtagResults.size()) { - int32 w = fullWidth(), htagwidth = w - st::dialogsPadding.x() * 2; + auto htagwidth = fullWidth - st::dialogsPadding.x() * 2; - p.setFont(st::mentionFont->f); - p.setPen(st::black->p); + p.setFont(st::mentionFont); for (; from < to; ++from) { - bool selected = (from == _hashtagSel); - p.fillRect(0, 0, w, st::mentionHeight, (selected ? st::mentionBgOver : st::white)->b); + auto &result = _hashtagResults[from]; + bool selected = (from == (isPressed() ? _hashtagPressed : _hashtagSelected)); + p.fillRect(0, 0, fullWidth, st::mentionHeight, selected ? st::mentionBgOver : st::dialogsBg); + result->row.paintRipple(p, 0, 0, fullWidth, ms); if (!paintingOther) { + auto &tag = result->tag; if (selected) { - int skip = (st::mentionHeight - st::simpleClose.icon.pxHeight()) / 2; - p.drawSprite(QPoint(w - st::simpleClose.icon.pxWidth() - skip, skip), st::simpleClose.icon); + int skip = (st::mentionHeight - st::smallCloseIconOver.height()) / 2; + st::smallCloseIconOver.paint(p, QPoint(fullWidth - st::smallCloseIconOver.width() - skip, skip), width()); } - QString first = (_hashtagFilter.size() < 2) ? QString() : ('#' + _hashtagResults.at(from).mid(0, _hashtagFilter.size() - 1)), second = (_hashtagFilter.size() < 2) ? ('#' + _hashtagResults.at(from)) : _hashtagResults.at(from).mid(_hashtagFilter.size() - 1); - int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second); + auto first = (_hashtagFilter.size() < 2) ? QString() : ('#' + tag.mid(0, _hashtagFilter.size() - 1)); + auto second = (_hashtagFilter.size() < 2) ? ('#' + tag) : tag.mid(_hashtagFilter.size() - 1); + auto firstwidth = st::mentionFont->width(first); + auto secondwidth = st::mentionFont->width(second); if (htagwidth < firstwidth + secondwidth) { if (htagwidth < firstwidth + st::mentionFont->elidew) { first = st::mentionFont->elided(first + second, htagwidth); @@ -140,13 +189,13 @@ void DialogsInner::paintRegion(Painter &p, const QRegion ®ion, bool paintingO } } - p.setFont(st::mentionFont->f); + p.setFont(st::mentionFont); if (!first.isEmpty()) { - p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p); + p.setPen(selected ? st::mentionFgOverActive : st::mentionFgActive); p.drawText(st::dialogsPadding.x(), st::mentionTop + st::mentionFont->ascent, first); } if (!second.isEmpty()) { - p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p); + p.setPen(selected ? st::mentionFgOver : st::mentionFg); p.drawText(st::dialogsPadding.x() + firstwidth, st::mentionTop + st::mentionFont->ascent, second); } } @@ -155,58 +204,60 @@ void DialogsInner::paintRegion(Painter &p, const QRegion ®ion, bool paintingO } } if (!_filterResults.isEmpty()) { - int32 skip = filteredOffset(); - int32 from = floorclamp(r.y() - skip, st::dialogsRowHeight, 0, _filterResults.size()); - int32 to = ceilclamp(r.y() + r.height() - skip, st::dialogsRowHeight, 0, _filterResults.size()); + auto skip = filteredOffset(); + auto from = floorclamp(r.y() - skip, st::dialogsRowHeight, 0, _filterResults.size()); + auto to = ceilclamp(r.y() + r.height() - skip, st::dialogsRowHeight, 0, _filterResults.size()); p.translate(0, from * st::dialogsRowHeight); if (from < _filterResults.size()) { - int32 w = fullWidth(); - PeerData *act = App::main()->activePeer(); - MsgId actId = App::main()->activeMsgId(); + auto activePeer = App::main()->activePeer(); + auto activeMsgId = App::main()->activeMsgId(); for (; from < to; ++from) { - bool active = ((_filterResults[from]->history()->peer == act) || (_filterResults[from]->history()->peer->migrateTo() && _filterResults[from]->history()->peer->migrateTo() == act)) && !actId; - bool selected = (from == _filteredSel) || (_filterResults[from]->history()->peer == _menuPeer); - Dialogs::Layout::RowPainter::paint(p, _filterResults[from], w, active, selected, paintingOther); + auto row = _filterResults[from]; + auto peer = row->history()->peer; + auto active = ((peer == activePeer) || (peer->migrateTo() && peer->migrateTo() == activePeer)) && !activeMsgId; + auto selected = _menuPeer ? (peer == _menuPeer) : (from == (isPressed() ? _filteredPressed : _filteredSelected)); + Dialogs::Layout::RowPainter::paint(p, _filterResults[from], fullWidth, active, selected, paintingOther, ms); p.translate(0, st::dialogsRowHeight); } } } - if (!_peopleResults.isEmpty()) { - p.fillRect(0, 0, fullWidth(), st::searchedBarHeight, st::searchedBarBG->b); + if (!_peerSearchResults.isEmpty()) { + p.fillRect(0, 0, fullWidth, st::searchedBarHeight, st::searchedBarBg); if (!paintingOther) { - p.setFont(st::searchedBarFont->f); - p.setPen(st::searchedBarColor->p); - p.drawText(QRect(0, 0, fullWidth(), st::searchedBarHeight), lang(lng_search_global_results), style::al_center); + p.setFont(st::searchedBarFont); + p.setPen(st::searchedBarFg); + p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), lang(lng_search_global_results), style::al_center); } p.translate(0, st::searchedBarHeight); - int32 skip = peopleOffset(); - int32 from = floorclamp(r.y() - skip, st::dialogsRowHeight, 0, _peopleResults.size()); - int32 to = ceilclamp(r.y() + r.height() - skip, st::dialogsRowHeight, 0, _peopleResults.size()); + auto skip = peerSearchOffset(); + auto from = floorclamp(r.y() - skip, st::dialogsRowHeight, 0, _peerSearchResults.size()); + auto to = ceilclamp(r.y() + r.height() - skip, st::dialogsRowHeight, 0, _peerSearchResults.size()); p.translate(0, from * st::dialogsRowHeight); - if (from < _peopleResults.size()) { - int32 w = fullWidth(); - PeerData *act = App::main()->activePeer(); - MsgId actId = App::main()->activeMsgId(); + if (from < _peerSearchResults.size()) { + auto activePeer = App::main()->activePeer(); + auto activeMsgId = App::main()->activeMsgId(); for (; from < to; ++from) { - bool active = ((_peopleResults[from] == act) || (_peopleResults[from]->migrateTo() && _peopleResults[from]->migrateTo() == act)) && !actId; - bool selected = (from == _peopleSel); - peopleResultPaint(_peopleResults[from], p, w, active, selected, paintingOther); + auto &result = _peerSearchResults[from]; + auto peer = result->peer; + auto active = ((peer == activePeer) || (peer->migrateTo() && peer->migrateTo() == activePeer)) && !activeMsgId; + auto selected = false ? (peer == _menuPeer) : (from == (isPressed() ? _peerSearchPressed : _peerSearchSelected)); + paintPeerSearchResult(p, result.get(), fullWidth, active, selected, paintingOther, ms); p.translate(0, st::dialogsRowHeight); } } } if (_searchInPeer) { - searchInPeerPaint(p, fullWidth(), paintingOther); + paintSearchInPeer(p, fullWidth, paintingOther); p.translate(0, st::dialogsRowHeight); if (_state == FilteredState && _searchResults.isEmpty()) { - p.fillRect(0, 0, fullWidth(), st::searchedBarHeight, st::searchedBarBG->b); + p.fillRect(0, 0, fullWidth, st::searchedBarHeight, st::searchedBarBg); if (!paintingOther) { - p.setFont(st::searchedBarFont->f); - p.setPen(st::searchedBarColor->p); - p.drawText(QRect(0, 0, fullWidth(), st::searchedBarHeight), lang(lng_dlg_search_for_messages), style::al_center); + p.setFont(st::searchedBarFont); + p.setPen(st::searchedBarFg); + p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), lang(lng_dlg_search_for_messages), style::al_center); } p.translate(0, st::searchedBarHeight); } @@ -214,29 +265,28 @@ void DialogsInner::paintRegion(Painter &p, const QRegion ®ion, bool paintingO if (_state == SearchedState || !_searchResults.isEmpty()) { QString text = lng_search_found_results(lt_count, _searchResults.isEmpty() ? 0 : (_searchedMigratedCount + _searchedCount)); - p.fillRect(0, 0, fullWidth(), st::searchedBarHeight, st::searchedBarBG->b); + p.fillRect(0, 0, fullWidth, st::searchedBarHeight, st::searchedBarBg); if (!paintingOther) { - p.setFont(st::searchedBarFont->f); - p.setPen(st::searchedBarColor->p); - p.drawText(QRect(0, 0, fullWidth(), st::searchedBarHeight), text, style::al_center); + p.setFont(st::searchedBarFont); + p.setPen(st::searchedBarFg); + p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), text, style::al_center); } p.translate(0, st::searchedBarHeight); - int32 skip = searchedOffset(); - int32 from = floorclamp(r.y() - skip, st::dialogsRowHeight, 0, _searchResults.size()); - int32 to = ceilclamp(r.y() + r.height() - skip, st::dialogsRowHeight, 0, _searchResults.size()); + auto skip = searchedOffset(); + auto from = floorclamp(r.y() - skip, st::dialogsRowHeight, 0, _searchResults.size()); + auto to = ceilclamp(r.y() + r.height() - skip, st::dialogsRowHeight, 0, _searchResults.size()); p.translate(0, from * st::dialogsRowHeight); if (from < _searchResults.size()) { - int32 w = fullWidth(); - PeerData *act = App::main()->activePeer(); - MsgId actId = App::main()->activeMsgId(); + auto activePeer = App::main()->activePeer(); + auto activeMsgId = App::main()->activeMsgId(); for (; from < to; ++from) { - auto result = _searchResults[from]; + auto &result = _searchResults[from]; auto item = result->item(); - auto history = item->history(); - bool active = (history->peer == act && item->id == actId) || (history->peer->migrateTo() && history->peer->migrateTo() == act && item->id == -actId); - bool selected = (from == _searchedSel); - Dialogs::Layout::RowPainter::paint(p, result, w, active, selected, paintingOther); + auto peer = item->history()->peer; + auto active = (peer == activePeer && item->id == activeMsgId) || (peer->migrateTo() && peer->migrateTo() == activePeer && item->id == -activeMsgId); + auto selected = false ? (peer == _menuPeer) : (from == (isPressed() ? _searchedPressed : _searchedSelected)); + Dialogs::Layout::RowPainter::paint(p, result.get(), fullWidth, active, selected, paintingOther, ms); p.translate(0, st::dialogsRowHeight); } } @@ -244,35 +294,40 @@ void DialogsInner::paintRegion(Painter &p, const QRegion ®ion, bool paintingO } } -void DialogsInner::peopleResultPaint(PeerData *peer, Painter &p, int32 w, bool active, bool selected, bool onlyBackground) const { - QRect fullRect(0, 0, w, st::dialogsRowHeight); +void DialogsInner::paintPeerSearchResult(Painter &p, const PeerSearchResult *result, int fullWidth, bool active, bool selected, bool onlyBackground, TimeMs ms) const { + QRect fullRect(0, 0, fullWidth, st::dialogsRowHeight); p.fillRect(fullRect, active ? st::dialogsBgActive : (selected ? st::dialogsBgOver : st::dialogsBg)); + if (!active) { + result->row.paintRipple(p, 0, 0, fullWidth, ms); + } if (onlyBackground) return; - PeerData *userpicPeer = (peer->migrateTo() ? peer->migrateTo() : peer); - userpicPeer->paintUserpicLeft(p, st::dialogsPhotoSize, st::dialogsPadding.x(), st::dialogsPadding.y(), fullWidth()); + auto peer = result->peer; + auto userpicPeer = (peer->migrateTo() ? peer->migrateTo() : peer); + userpicPeer->paintUserpicLeft(p, st::dialogsPadding.x(), st::dialogsPadding.y(), getFullWidth(), st::dialogsPhotoSize); - int32 nameleft = st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPhotoPadding; - int32 namewidth = w - nameleft - st::dialogsPadding.x(); + auto nameleft = st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPhotoPadding; + auto namewidth = fullWidth - nameleft - st::dialogsPadding.x(); QRect rectForName(nameleft, st::dialogsPadding.y() + st::dialogsNameTop, namewidth, st::msgNameFont->height); // draw chat icon - if (auto chatTypeIcon = Dialogs::Layout::ChatTypeIcon(peer, active)) { - chatTypeIcon->paint(p, rectForName.topLeft(), w); + if (auto chatTypeIcon = Dialogs::Layout::ChatTypeIcon(peer, active, selected)) { + chatTypeIcon->paint(p, rectForName.topLeft(), fullWidth); rectForName.setLeft(rectForName.left() + st::dialogsChatTypeSkip); } if (peer->isVerified()) { - auto icon = &(active ? st::dialogsVerifiedActiveIcon : st::dialogsVerifiedIcon); + auto icon = &(active ? st::dialogsVerifiedIconActive : (selected ? st::dialogsVerifiedIconOver : st::dialogsVerifiedIcon)); rectForName.setWidth(rectForName.width() - icon->width()); - icon->paint(p, rectForName.topLeft() + QPoint(qMin(peer->dialogName().maxWidth(), rectForName.width()), 0), w); + icon->paint(p, rectForName.topLeft() + QPoint(qMin(peer->dialogName().maxWidth(), rectForName.width()), 0), fullWidth); } QRect tr(nameleft, st::dialogsPadding.y() + st::msgNameFont->height + st::dialogsSkip, namewidth, st::dialogsTextFont->height); p.setFont(st::dialogsTextFont); QString username = peer->userName(); - if (!active && username.toLower().startsWith(_peopleQuery)) { - QString first = '@' + username.mid(0, _peopleQuery.size()), second = username.mid(_peopleQuery.size()); - int32 w = st::dialogsTextFont->width(first); + if (!active && username.toLower().startsWith(_peerSearchQuery)) { + auto first = '@' + username.mid(0, _peerSearchQuery.size()); + auto second = username.mid(_peerSearchQuery.size()); + auto w = st::dialogsTextFont->width(first); if (w >= tr.width()) { p.setPen(st::dialogsTextFgService); p.drawText(tr.left(), tr.top() + st::dialogsTextFont->ascent, st::dialogsTextFont->elided(first, tr.width())); @@ -291,19 +346,19 @@ void DialogsInner::peopleResultPaint(PeerData *peer, Painter &p, int32 w, bool a peer->dialogName().drawElided(p, rectForName.left(), rectForName.top(), rectForName.width()); } -void DialogsInner::searchInPeerPaint(Painter &p, int32 w, bool onlyBackground) const { - QRect fullRect(0, 0, w, st::dialogsRowHeight); +void DialogsInner::paintSearchInPeer(Painter &p, int fullWidth, bool onlyBackground) const { + QRect fullRect(0, 0, fullWidth, st::dialogsRowHeight); p.fillRect(fullRect, st::dialogsBg); if (onlyBackground) return; - _searchInPeer->paintUserpicLeft(p, st::dialogsPhotoSize, st::dialogsPadding.x(), st::dialogsPadding.y(), fullWidth()); + _searchInPeer->paintUserpicLeft(p, st::dialogsPadding.x(), st::dialogsPadding.y(), getFullWidth(), st::dialogsPhotoSize); - int32 nameleft = st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPhotoPadding; - int32 namewidth = w - nameleft - st::dialogsPadding.x() * 2 - st::btnCancelSearch.width; + auto nameleft = st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPhotoPadding; + auto namewidth = fullWidth - nameleft - st::dialogsPadding.x() * 2 - st::dialogsCancelSearch.width; QRect rectForName(nameleft, st::dialogsPadding.y() + st::dialogsNameTop, namewidth, st::msgNameFont->height); - if (auto chatTypeIcon = Dialogs::Layout::ChatTypeIcon(_searchInPeer, false)) { - chatTypeIcon->paint(p, rectForName.topLeft(), w); + if (auto chatTypeIcon = Dialogs::Layout::ChatTypeIcon(_searchInPeer, false, false)) { + chatTypeIcon->paint(p, rectForName.topLeft(), fullWidth); rectForName.setLeft(rectForName.left() + st::dialogsChatTypeSkip); } @@ -320,95 +375,240 @@ void DialogsInner::activate() { } void DialogsInner::mouseMoveEvent(QMouseEvent *e) { - lastMousePos = mapToGlobal(e->pos()); - _selByMouse = true; - onUpdateSelected(true); + _mouseSelection = true; + updateSelected(e->pos()); } -void DialogsInner::onUpdateSelected(bool force) { - QPoint mouse(mapFromGlobal(lastMousePos)); - if ((!force && !rect().contains(mouse)) || !_selByMouse) return; - - int w = width(), mouseY = mouse.y(); - _overDelete = false; +void DialogsInner::clearIrrelevantState() { if (_state == DefaultState) { - auto newImportantSwitchSel = (importantDialogs && mouseY >= 0 && mouseY < dialogsOffset()); + _hashtagSelected = -1; + setHashtagPressed(-1); + _hashtagDeleteSelected = _hashtagDeletePressed = false; + _filteredSelected = -1; + setFilteredPressed(-1); + _peerSearchSelected = -1; + setPeerSearchPressed(-1); + _searchedSelected = -1; + setSearchedPressed(-1); + } else if (_state == FilteredState || _state == SearchedState) { + _importantSwitchSelected = false; + setImportantSwitchPressed(false); + _selected = nullptr; + setPressed(nullptr); + } +} + +void DialogsInner::updateSelected(QPoint localPos) { + if (!_mouseSelection) return; + + int w = width(), mouseY = localPos.y(); + clearIrrelevantState(); + if (_state == DefaultState) { + auto importantSwitchSelected = (_dialogsImportant && mouseY >= 0 && mouseY < dialogsOffset()); mouseY -= dialogsOffset(); - auto newSel = newImportantSwitchSel ? nullptr : shownDialogs()->rowAtY(mouseY, st::dialogsRowHeight); - if (newSel != _sel || newImportantSwitchSel != _importantSwitchSel) { + auto selected = importantSwitchSelected ? nullptr : shownDialogs()->rowAtY(mouseY, st::dialogsRowHeight); + if (_selected != selected || _importantSwitchSelected != importantSwitchSelected) { updateSelectedRow(); - _sel = newSel; - _importantSwitchSel = newImportantSwitchSel; + _selected = selected; + _importantSwitchSelected = importantSwitchSelected; updateSelectedRow(); - setCursor(_sel ? style::cur_pointer : style::cur_default); + setCursor((_selected || _importantSwitchSelected) ? style::cur_pointer : style::cur_default); } } else if (_state == FilteredState || _state == SearchedState) { - if (!_hashtagResults.isEmpty()) { - int32 skip = 0, newHashtagSel = (mouseY >= skip) ? ((mouseY - skip) / int32(st::mentionHeight)) : -1; - if (newHashtagSel < 0 || newHashtagSel >= _hashtagResults.size()) { - newHashtagSel = -1; + auto wasSelected = isSelected(); + if (_hashtagResults.isEmpty()) { + _hashtagSelected = -1; + _hashtagDeleteSelected = false; + } else { + auto skip = 0; + auto hashtagSelected = (mouseY >= skip) ? ((mouseY - skip) / st::mentionHeight) : -1; + if (hashtagSelected < 0 || hashtagSelected >= _hashtagResults.size()) { + hashtagSelected = -1; } - if (newHashtagSel != _hashtagSel) { + if (_hashtagSelected != hashtagSelected) { updateSelectedRow(); - _hashtagSel = newHashtagSel; + _hashtagSelected = hashtagSelected; updateSelectedRow(); - setCursor((_hashtagSel >= 0) ? style::cur_pointer : style::cur_default); - } - if (_hashtagSel >= 0) { - _overDelete = (mouse.x() >= w - st::mentionHeight); } + _hashtagDeleteSelected = (_hashtagSelected >= 0) && (localPos.x() >= w - st::mentionHeight); } if (!_filterResults.isEmpty()) { - int32 skip = filteredOffset(), newFilteredSel = (mouseY >= skip) ? ((mouseY - skip) / int32(st::dialogsRowHeight)) : -1; - if (newFilteredSel < 0 || newFilteredSel >= _filterResults.size()) { - newFilteredSel = -1; + auto skip = filteredOffset(); + auto filteredSelected = (mouseY >= skip) ? ((mouseY - skip) / st::dialogsRowHeight) : -1; + if (filteredSelected < 0 || filteredSelected >= _filterResults.size()) { + filteredSelected = -1; } - if (newFilteredSel != _filteredSel) { + if (_filteredSelected != filteredSelected) { updateSelectedRow(); - _filteredSel = newFilteredSel; + _filteredSelected = filteredSelected; updateSelectedRow(); - setCursor((_filteredSel >= 0) ? style::cur_pointer : style::cur_default); } } - if (!_peopleResults.isEmpty()) { - int32 skip = peopleOffset(), newPeopleSel = (mouseY >= skip) ? ((mouseY - skip) / int32(st::dialogsRowHeight)) : -1; - if (newPeopleSel < 0 || newPeopleSel >= _peopleResults.size()) { - newPeopleSel = -1; + if (!_peerSearchResults.isEmpty()) { + auto skip = peerSearchOffset(); + auto peerSearchSelected = (mouseY >= skip) ? ((mouseY - skip) / st::dialogsRowHeight) : -1; + if (peerSearchSelected < 0 || peerSearchSelected >= _peerSearchResults.size()) { + peerSearchSelected = -1; } - if (newPeopleSel != _peopleSel) { + if (_peerSearchSelected != peerSearchSelected) { updateSelectedRow(); - _peopleSel = newPeopleSel; + _peerSearchSelected = peerSearchSelected; updateSelectedRow(); - setCursor((_peopleSel >= 0) ? style::cur_pointer : style::cur_default); } } if (_state == SearchedState && !_searchResults.isEmpty()) { - int32 skip = searchedOffset(), newSearchedSel = (mouseY >= skip) ? ((mouseY - skip) / int32(st::dialogsRowHeight)) : -1; - if (newSearchedSel < 0 || newSearchedSel >= _searchResults.size()) { - newSearchedSel = -1; + auto skip = searchedOffset(); + auto searchedSelected = (mouseY >= skip) ? ((mouseY - skip) / st::dialogsRowHeight) : -1; + if (searchedSelected < 0 || searchedSelected >= _searchResults.size()) { + searchedSelected = -1; } - if (newSearchedSel != _searchedSel) { + if (_searchedSelected != searchedSelected) { updateSelectedRow(); - _searchedSel = newSearchedSel; + _searchedSelected = searchedSelected; updateSelectedRow(); - setCursor((_searchedSel >= 0) ? style::cur_pointer : style::cur_default); } } + if (wasSelected != isSelected()) { + setCursor(wasSelected ? style::cur_default : style::cur_pointer); + } } } void DialogsInner::mousePressEvent(QMouseEvent *e) { - lastMousePos = mapToGlobal(e->pos()); - _selByMouse = true; - onUpdateSelected(true); - if (e->button() == Qt::LeftButton) { - choosePeer(); + _mouseSelection = true; + updateSelected(e->pos()); + + _pressButton = e->button(); + setPressed(_selected); + setImportantSwitchPressed(_importantSwitchSelected); + setHashtagPressed(_hashtagSelected); + _hashtagDeletePressed = _hashtagDeleteSelected; + setFilteredPressed(_filteredSelected); + setPeerSearchPressed(_peerSearchSelected); + setSearchedPressed(_searchedSelected); + if (_importantSwitchPressed) { + _importantSwitch->row.addRipple(e->pos(), QSize(getFullWidth(), st::dialogsImportantBarHeight), [this] { + update(0, 0, getFullWidth(), st::dialogsImportantBarHeight); + }); + } else if (_pressed) { + auto row = _pressed; + row->addRipple(e->pos() - QPoint(0, dialogsOffset() + _pressed->pos() * st::dialogsRowHeight), QSize(getFullWidth(), st::dialogsRowHeight), [row] { + row->history()->updateChatListEntry(); + }); + } else if (_hashtagPressed >= 0 && _hashtagPressed < _hashtagResults.size() && !_hashtagDeletePressed) { + auto row = &_hashtagResults[_hashtagPressed]->row; + row->addRipple(e->pos(), QSize(getFullWidth(), st::mentionHeight), [this, index = _hashtagPressed] { + update(0, index * st::mentionHeight, getFullWidth(), st::mentionHeight); + }); + } else if (_filteredPressed >= 0 && _filteredPressed < _filterResults.size()) { + auto row = _filterResults[_filteredPressed]; + row->addRipple(e->pos() - QPoint(0, filteredOffset() + _filteredPressed * st::dialogsRowHeight), QSize(getFullWidth(), st::dialogsRowHeight), [row] { + if (auto main = App::main()) { + main->dlgUpdated(row->history()->peer, 0); + } + }); + } else if (_peerSearchPressed >= 0 && _peerSearchPressed < _peerSearchResults.size()) { + auto &result = _peerSearchResults[_peerSearchPressed]; + auto row = &result->row; + row->addRipple(e->pos() - QPoint(0, peerSearchOffset() + _peerSearchPressed * st::dialogsRowHeight), QSize(getFullWidth(), st::dialogsRowHeight), [peer = result->peer] { + if (auto main = App::main()) { + main->dlgUpdated(peer, 0); + } + }); + } else if (_searchedPressed >= 0 && _searchedPressed < _searchResults.size()) { + auto &row = _searchResults[_searchedPressed]; + row->addRipple(e->pos() - QPoint(0, searchedOffset() + _searchedPressed * st::dialogsRowHeight), QSize(getFullWidth(), st::dialogsRowHeight), [this, index = _searchedPressed] { + rtlupdate(0, searchedOffset() + index * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); + }); } } +void DialogsInner::mouseReleaseEvent(QMouseEvent *e) { + mousePressReleased(e->button()); +} + +void DialogsInner::mousePressReleased(Qt::MouseButton button) { + auto importantSwitchPressed = _importantSwitchPressed; + setImportantSwitchPressed(false); + auto pressed = _pressed; + setPressed(nullptr); + auto hashtagPressed = _hashtagPressed; + setHashtagPressed(-1); + auto hashtagDeletePressed = _hashtagDeletePressed; + _hashtagDeletePressed = false; + auto filteredPressed = _filteredPressed; + setFilteredPressed(-1); + auto peerSearchPressed = _peerSearchPressed; + setPeerSearchPressed(-1); + auto searchedPressed = _searchedPressed; + setSearchedPressed(-1); + updateSelectedRow(); + if (button == Qt::LeftButton) { + if (importantSwitchPressed && importantSwitchPressed == _importantSwitchSelected) { + choosePeer(); + } else if (pressed && pressed == _selected) { + choosePeer(); + } else if (hashtagPressed >= 0 && hashtagPressed == _hashtagSelected && hashtagDeletePressed == _hashtagDeleteSelected) { + choosePeer(); + } else if (filteredPressed >= 0 && filteredPressed == _filteredSelected) { + choosePeer(); + } else if (peerSearchPressed >= 0 && peerSearchPressed == _peerSearchSelected) { + choosePeer(); + } else if (searchedPressed >= 0 && searchedPressed == _searchedSelected) { + choosePeer(); + } + } +} + +void DialogsInner::setImportantSwitchPressed(bool pressed) { + if (_importantSwitchPressed != pressed) { + if (_importantSwitchPressed) { + _importantSwitch->row.stopLastRipple(); + } + _importantSwitchPressed = pressed; + } +} + +void DialogsInner::setPressed(Dialogs::Row *pressed) { + if (_pressed != pressed) { + if (_pressed) { + _pressed->stopLastRipple(); + } + _pressed = pressed; + } +} + +void DialogsInner::setHashtagPressed(int pressed) { + if (_hashtagPressed >= 0 && _hashtagPressed < _hashtagResults.size()) { + _hashtagResults[_hashtagPressed]->row.stopLastRipple(); + } + _hashtagPressed = pressed; +} + +void DialogsInner::setFilteredPressed(int pressed) { + if (_filteredPressed >= 0 && _filteredPressed < _filterResults.size()) { + _filterResults[_filteredPressed]->stopLastRipple(); + } + _filteredPressed = pressed; +} + +void DialogsInner::setPeerSearchPressed(int pressed) { + if (_peerSearchPressed >= 0 && _peerSearchPressed < _peerSearchResults.size()) { + _peerSearchResults[_peerSearchPressed]->row.stopLastRipple(); + } + _peerSearchPressed = pressed; +} + +void DialogsInner::setSearchedPressed(int pressed) { + if (_searchedPressed >= 0 && _searchedPressed < _searchResults.size()) { + _searchResults[_searchedPressed]->stopLastRipple(); + } + _searchedPressed = pressed; +} + void DialogsInner::resizeEvent(QResizeEvent *e) { - _addContactLnk.move((width() - _addContactLnk.width()) / 2, (st::noContactsHeight + st::noContactsFont->height) / 2); - _cancelSearchInPeer.move(width() - st::dialogsPadding.x() - st::btnCancelSearch.width, (st::dialogsRowHeight - st::btnCancelSearch.height) / 2); + _addContactLnk->move((width() - _addContactLnk->width()) / 2, (st::noContactsHeight + st::noContactsFont->height) / 2); + _cancelSearchInPeer->moveToRight(st::dialogsFilterSkip + st::dialogsFilterPadding.x() - otherWidth(), (st::dialogsRowHeight - st::dialogsCancelSearchInPeer.height) / 2); } void DialogsInner::onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow) { @@ -426,8 +626,11 @@ void DialogsInner::onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRo } } } - if (_sel == oldRow) { - _sel = newRow; + if (_selected == oldRow) { + _selected = newRow; + } + if (_pressed == oldRow) { + setPressed(newRow); } } @@ -439,25 +642,25 @@ void DialogsInner::createDialog(History *history) { bool creating = !history->inChatList(Dialogs::Mode::All); if (creating) { - auto mainRow = history->addToChatList(Dialogs::Mode::All, dialogs.get()); - contactsNoDialogs->del(history->peer, mainRow); + auto mainRow = history->addToChatList(Dialogs::Mode::All, _dialogs.get()); + _contactsNoDialogs->del(history->peer, mainRow); } - if (importantDialogs && !history->inChatList(Dialogs::Mode::Important) && !history->mute()) { + if (_dialogsImportant && !history->inChatList(Dialogs::Mode::Important) && !history->mute()) { if (Global::DialogsMode() == Dialogs::Mode::Important) { creating = true; } - history->addToChatList(Dialogs::Mode::Important, importantDialogs.get()); + history->addToChatList(Dialogs::Mode::Important, _dialogsImportant.get()); } - auto changed = history->adjustByPosInChatList(Dialogs::Mode::All, dialogs.get()); + auto changed = history->adjustByPosInChatList(Dialogs::Mode::All, _dialogs.get()); - if (importantDialogs) { + if (_dialogsImportant) { if (history->mute()) { if (Global::DialogsMode() == Dialogs::Mode::Important) { return; } } else { - auto importantChanged = history->adjustByPosInChatList(Dialogs::Mode::Important, importantDialogs.get()); + auto importantChanged = history->adjustByPosInChatList(Dialogs::Mode::Important, _dialogsImportant.get()); if (Global::DialogsMode() == Dialogs::Mode::Important) { changed = importantChanged; } @@ -471,7 +674,7 @@ void DialogsInner::createDialog(History *history) { if (creating) { refresh(); } else if (_state == DefaultState && changed.movedFrom != changed.movedTo) { - update(0, qMin(from, to), fullWidth(), qAbs(from - to) + st::dialogsRowHeight); + update(0, qMin(from, to), getFullWidth(), qAbs(from - to) + st::dialogsRowHeight); } } @@ -480,17 +683,20 @@ void DialogsInner::removeDialog(History *history) { if (history->peer == _menuPeer && _menu) { _menu->deleteLater(); } - if (_sel && _sel->history() == history) { - _sel = nullptr; + if (_selected && _selected->history() == history) { + _selected = nullptr; } - history->removeFromChatList(Dialogs::Mode::All, dialogs.get()); - if (importantDialogs) { - history->removeFromChatList(Dialogs::Mode::Important, importantDialogs.get()); + if (_pressed && _pressed->history() == history) { + setPressed(nullptr); + } + history->removeFromChatList(Dialogs::Mode::All, _dialogs.get()); + if (_dialogsImportant) { + history->removeFromChatList(Dialogs::Mode::Important, _dialogsImportant.get()); } if (App::wnd()) App::wnd()->notifyClear(history); - if (contacts->contains(history->peer->id)) { - if (!contactsNoDialogs->contains(history->peer->id)) { - contactsNoDialogs->addByName(history); + if (_contacts->contains(history->peer->id)) { + if (!_contactsNoDialogs->contains(history->peer->id)) { + _contactsNoDialogs->addByName(history); } } @@ -504,13 +710,13 @@ void DialogsInner::removeDialog(History *history) { void DialogsInner::dlgUpdated(Dialogs::Mode list, Dialogs::Row *row) { if (_state == DefaultState) { if (Global::DialogsMode() == list) { - update(0, dialogsOffset() + row->pos() * st::dialogsRowHeight, fullWidth(), st::dialogsRowHeight); + update(0, dialogsOffset() + row->pos() * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); } } else if (_state == FilteredState || _state == SearchedState) { if (list == Dialogs::Mode::All) { for (int32 i = 0, l = _filterResults.size(); i < l; ++i) { if (_filterResults.at(i)->history() == row->history()) { - update(0, i * st::dialogsRowHeight, fullWidth(), st::dialogsRowHeight); + update(0, filteredOffset() + i * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); break; } } @@ -518,38 +724,50 @@ void DialogsInner::dlgUpdated(Dialogs::Mode list, Dialogs::Row *row) { } } -void DialogsInner::dlgUpdated(History *history, MsgId msgId) { +void DialogsInner::dlgUpdated(PeerData *peer, MsgId msgId) { + updateDialogRow(peer, msgId, QRect(0, 0, getFullWidth(), st::dialogsRowHeight)); +} + +void DialogsInner::updateDialogRow(PeerData *peer, MsgId msgId, QRect updateRect, UpdateRowSections sections) { + auto updateRow = [this, updateRect](int rowTop) { + rtlupdate(updateRect.x(), rowTop + updateRect.y(), updateRect.width(), updateRect.height()); + }; if (_state == DefaultState) { - if (auto row = shownDialogs()->getRow(history->peer->id)) { - update(0, dialogsOffset() + row->pos() * st::dialogsRowHeight, fullWidth(), st::dialogsRowHeight); + if (sections & UpdateRowSection::Default) { + if (auto row = shownDialogs()->getRow(peer->id)) { + updateRow(dialogsOffset() + row->pos() * st::dialogsRowHeight); + } } } else if (_state == FilteredState || _state == SearchedState) { - int32 cnt = 0, add = filteredOffset(); - for (FilteredDialogs::const_iterator i = _filterResults.cbegin(), e = _filterResults.cend(); i != e; ++i) { - if ((*i)->history() == history) { - update(0, add + cnt * st::dialogsRowHeight, fullWidth(), st::dialogsRowHeight); - break; - } - ++cnt; - } - if (!_peopleResults.isEmpty()) { - int32 cnt = 0, add = peopleOffset(); - for (PeopleResults::const_iterator i = _peopleResults.cbegin(), e = _peopleResults.cend(); i != e; ++i) { - if ((*i) == history->peer) { - update(0, add + cnt * st::dialogsRowHeight, fullWidth(), st::dialogsRowHeight); + if ((sections & UpdateRowSection::Filtered) && !_filterResults.isEmpty()) { + auto index = 0, add = filteredOffset(); + for_const (auto row, _filterResults) { + if (row->history()->peer == peer) { + updateRow(add + index * st::dialogsRowHeight); break; } - ++cnt; + ++index; } } - if (!_searchResults.isEmpty()) { - int32 cnt = 0, add = searchedOffset(); - for (SearchResults::const_iterator i = _searchResults.cbegin(), e = _searchResults.cend(); i != e; ++i) { - if ((*i)->item()->history() == history && (*i)->item()->id == msgId) { - update(0, add + cnt * st::dialogsRowHeight, fullWidth(), st::dialogsRowHeight); + if ((sections & UpdateRowSection::PeerSearch) && !_peerSearchResults.isEmpty()) { + auto index = 0, add = peerSearchOffset(); + for_const (auto &result, _peerSearchResults) { + if (result->peer == peer) { + updateRow(add + index * st::dialogsRowHeight); break; } - ++cnt; + ++index; + } + } + if ((sections & UpdateRowSection::MessageSearch) && !_searchResults.isEmpty()) { + auto index = 0, add = searchedOffset(); + for_const (auto &result, _searchResults) { + auto item = result->item(); + if (item->history()->peer == peer && item->id == msgId) { + updateRow(add + index * st::dialogsRowHeight); + break; + } + ++index; } } } @@ -557,8 +775,7 @@ void DialogsInner::dlgUpdated(History *history, MsgId msgId) { void DialogsInner::enterEvent(QEvent *e) { setMouseTracking(true); - lastMousePos = QCursor::pos(); - onUpdateSelected(true); + updateSelected(); } void DialogsInner::updateSelectedRow(PeerData *peer) { @@ -566,30 +783,30 @@ void DialogsInner::updateSelectedRow(PeerData *peer) { if (peer) { if (History *h = App::historyLoaded(peer->id)) { if (h->inChatList(Global::DialogsMode())) { - update(0, dialogsOffset() + h->posInChatList(Global::DialogsMode()) * st::dialogsRowHeight, fullWidth(), st::dialogsRowHeight); + update(0, dialogsOffset() + h->posInChatList(Global::DialogsMode()) * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); } } - } else if (_sel) { - update(0, dialogsOffset() + _sel->pos() * st::dialogsRowHeight, fullWidth(), st::dialogsRowHeight); - } else if (_importantSwitchSel) { - update(0, 0, fullWidth(), st::dialogsImportantBarHeight); + } else if (_selected) { + update(0, dialogsOffset() + _selected->pos() * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); + } else if (_importantSwitchSelected) { + update(0, 0, getFullWidth(), st::dialogsImportantBarHeight); } } else if (_state == FilteredState || _state == SearchedState) { if (peer) { for (int32 i = 0, l = _filterResults.size(); i != l; ++i) { if (_filterResults.at(i)->history()->peer == peer) { - update(0, filteredOffset() + i * st::dialogsRowHeight, fullWidth(), st::dialogsRowHeight); + update(0, filteredOffset() + i * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); break; } } - } else if (_hashtagSel >= 0) { - update(0, _hashtagSel * st::mentionHeight, fullWidth(), st::mentionHeight); - } else if (_filteredSel >= 0) { - update(0, filteredOffset() + _filteredSel * st::dialogsRowHeight, fullWidth(), st::dialogsRowHeight); - } else if (_peopleSel >= 0) { - update(0, peopleOffset() + _peopleSel * st::dialogsRowHeight, fullWidth(), st::dialogsRowHeight); - } else if (_searchedSel >= 0) { - update(0, searchedOffset() + _searchedSel * st::dialogsRowHeight, fullWidth(), st::dialogsRowHeight); + } else if (_hashtagSelected >= 0) { + update(0, _hashtagSelected * st::mentionHeight, getFullWidth(), st::mentionHeight); + } else if (_filteredSelected >= 0) { + update(0, filteredOffset() + _filteredSelected * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); + } else if (_peerSearchSelected >= 0) { + update(0, peerSearchOffset() + _peerSearchSelected * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); + } else if (_searchedSelected >= 0) { + update(0, searchedOffset() + _searchedSelected * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); } } @@ -600,13 +817,18 @@ void DialogsInner::leaveEvent(QEvent *e) { clearSelection(); } +void DialogsInner::dragLeft() { + setMouseTracking(false); + clearSelection(); +} + void DialogsInner::clearSelection() { - _selByMouse = false; - if (_importantSwitchSel || _sel || _filteredSel >= 0 || _hashtagSel >= 0 || _searchedSel >= 0 || _peopleSel >= 0) { + _mouseSelection = false; + if (_importantSwitchSelected || _selected || _filteredSelected >= 0 || _hashtagSelected >= 0 || _peerSearchSelected >= 0 || _searchedSelected >= 0) { updateSelectedRow(); - _sel = nullptr; - _importantSwitchSel = false; - _filteredSel = _searchedSel = _peopleSel = _hashtagSel = -1; + _importantSwitchSelected = false; + _selected = nullptr; + _filteredSelected = _searchedSelected = _peerSearchSelected = _hashtagSelected = -1; setCursor(style::cur_default); } } @@ -614,160 +836,72 @@ void DialogsInner::clearSelection() { void DialogsInner::contextMenuEvent(QContextMenuEvent *e) { if (_menu) { _menu->deleteLater(); - _menu = 0; + _menu = nullptr; } if (_menuPeer) { updateSelectedRow(_menuPeer); - _menuPeer = 0; - disconnect(App::main(), SIGNAL(peerUpdated(PeerData*)), this, SLOT(peerUpdated(PeerData*))); + _menuPeer = nullptr; } if (e->reason() == QContextMenuEvent::Mouse) { - lastMousePos = e->globalPos(); - _selByMouse = true; - onUpdateSelected(true); + _mouseSelection = true; + updateSelected(); } - History *history = 0; + History *history = nullptr; if (_state == DefaultState) { - if (_sel) history = _sel->history(); + if (_selected) history = _selected->history(); } else if (_state == FilteredState || _state == SearchedState) { - if (_filteredSel >= 0 && _filteredSel < _filterResults.size()) { - history = _filterResults[_filteredSel]->history(); + if (_filteredSelected >= 0 && _filteredSelected < _filterResults.size()) { + history = _filterResults[_filteredSelected]->history(); } } if (!history) return; _menuPeer = history->peer; - _menu = new PopupMenu(); - _menu->addAction(lang((_menuPeer->isChat() || _menuPeer->isMegagroup()) ? lng_context_view_group : (_menuPeer->isUser() ? lng_context_view_profile : lng_context_view_channel)), this, SLOT(onContextProfile()))->setEnabled(true); - _menu->addAction(lang(menuPeerMuted() ? lng_enable_notifications_from_tray : lng_disable_notifications_from_tray), this, SLOT(onContextToggleNotifications()))->setEnabled(true); - _menu->addAction(lang(lng_profile_search_messages), this, SLOT(onContextSearch()))->setEnabled(true); - if (_menuPeer->isUser()) { - _menu->addAction(lang(lng_profile_clear_history), this, SLOT(onContextClearHistory()))->setEnabled(true); - _menu->addAction(lang(lng_profile_delete_conversation), this, SLOT(onContextDeleteAndLeave()))->setEnabled(true); - if (_menuPeer->asUser()->access != UserNoAccess && _menuPeer != App::self()) { - _menu->addAction(lang(_menuPeer->asUser()->isBlocked() ? (_menuPeer->asUser()->botInfo ? lng_profile_unblock_bot : lng_profile_unblock_user) : (_menuPeer->asUser()->botInfo ? lng_profile_block_bot : lng_profile_block_user)), this, SLOT(onContextToggleBlock()))->setEnabled(true); - connect(App::main(), SIGNAL(peerUpdated(PeerData*)), this, SLOT(peerUpdated(PeerData*))); - } - } else if (_menuPeer->isChat()) { - _menu->addAction(lang(lng_profile_clear_history), this, SLOT(onContextClearHistory()))->setEnabled(true); - _menu->addAction(lang(lng_profile_clear_and_exit), this, SLOT(onContextDeleteAndLeave()))->setEnabled(true); - } else if (_menuPeer->isChannel() && _menuPeer->asChannel()->amIn() && !_menuPeer->asChannel()->amCreator()) { - _menu->addAction(lang(_menuPeer->isMegagroup() ? lng_profile_leave_group : lng_profile_leave_channel), this, SLOT(onContextDeleteAndLeave()))->setEnabled(true); + if (_pressButton != Qt::LeftButton) { + mousePressReleased(_pressButton); } + _menu = new Ui::PopupMenu(nullptr); + App::main()->fillPeerMenu(_menuPeer, [this](const QString &text, base::lambda &&callback) { + return _menu->addAction(text, std_::move(callback)); + }, true); connect(_menu, SIGNAL(destroyed(QObject*)), this, SLOT(onMenuDestroyed(QObject*))); _menu->popup(e->globalPos()); e->accept(); } -bool DialogsInner::menuPeerMuted() { - return _menuPeer && _menuPeer->notify != EmptyNotifySettings && _menuPeer->notify != UnknownNotifySettings && _menuPeer->notify->mute >= unixtime(); -} - -void DialogsInner::onContextProfile() { - if (!_menuPeer) return; - Ui::showPeerProfile(_menuPeer); -} - -void DialogsInner::onContextToggleNotifications() { - if (!_menuPeer) return; - App::main()->updateNotifySetting(_menuPeer, menuPeerMuted() ? NotifySettingSetNotify : NotifySettingSetMuted); -} - -void DialogsInner::onContextSearch() { - if (!_menuPeer) return; - App::main()->searchInPeer(_menuPeer); -} - -void DialogsInner::onContextClearHistory() { - if (!_menuPeer || _menuPeer->isChannel()) return; - - _menuActionPeer = _menuPeer; - ConfirmBox *box = new ConfirmBox(_menuPeer->isUser() ? lng_sure_delete_history(lt_contact, _menuPeer->name) : lng_sure_delete_group_history(lt_group, _menuPeer->name), lang(lng_box_delete), st::attentionBoxButton); - connect(box, SIGNAL(confirmed()), this, SLOT(onContextClearHistorySure())); - Ui::showLayer(box); -} - -void DialogsInner::onContextClearHistorySure() { - if (!_menuActionPeer || _menuActionPeer->isChannel()) return; - Ui::hideLayer(); - App::main()->clearHistory(_menuActionPeer); -} - -void DialogsInner::onContextDeleteAndLeave() { - if (!_menuPeer) return; - - _menuActionPeer = _menuPeer; - ConfirmBox *box = new ConfirmBox(_menuPeer->isUser() ? lng_sure_delete_history(lt_contact, _menuPeer->name) : (_menuPeer->isChat() ? lng_sure_delete_and_exit(lt_group, _menuPeer->name) : lang(_menuPeer->isMegagroup() ? lng_sure_leave_group : lng_sure_leave_channel)), lang(_menuPeer->isUser() ? lng_box_delete : lng_box_leave), _menuPeer->isChannel() ? st::defaultBoxButton : st::attentionBoxButton); - connect(box, SIGNAL(confirmed()), this, SLOT(onContextDeleteAndLeaveSure())); - Ui::showLayer(box); -} - -void DialogsInner::onContextDeleteAndLeaveSure() { - if (!_menuActionPeer) return; - - Ui::hideLayer(); - Ui::showChatsList(); - if (_menuActionPeer->isUser()) { - App::main()->deleteConversation(_menuActionPeer); - } else if (_menuActionPeer->isChat()) { - MTP::send(MTPmessages_DeleteChatUser(_menuActionPeer->asChat()->inputChat, App::self()->inputUser), App::main()->rpcDone(&MainWidget::deleteHistoryAfterLeave, _menuActionPeer), App::main()->rpcFail(&MainWidget::leaveChatFailed, _menuActionPeer)); - } else if (_menuActionPeer->isChannel()) { - if (_menuActionPeer->migrateFrom()) { - App::main()->deleteConversation(_menuActionPeer->migrateFrom()); - } - MTP::send(MTPchannels_LeaveChannel(_menuActionPeer->asChannel()->inputChannel), App::main()->rpcDone(&MainWidget::sentUpdatesReceived)); - } -} - -void DialogsInner::onContextToggleBlock() { - if (!_menuPeer || !_menuPeer->isUser()) return; - if (_menuPeer->asUser()->isBlocked()) { - MTP::send(MTPcontacts_Unblock(_menuPeer->asUser()->inputUser), rpcDone(&DialogsInner::contextBlockDone, qMakePair(_menuPeer->asUser(), false))); - } else { - MTP::send(MTPcontacts_Block(_menuPeer->asUser()->inputUser), rpcDone(&DialogsInner::contextBlockDone, qMakePair(_menuPeer->asUser(), true))); - } -} - -void DialogsInner::contextBlockDone(QPair data, const MTPBool &result) { - data.first->setBlockStatus(data.second ? UserData::BlockStatus::Blocked : UserData::BlockStatus::NotBlocked); - emit App::main()->peerUpdated(data.first); -} - void DialogsInner::onMenuDestroyed(QObject *obj) { if (_menu == obj) { - _menu = 0; + _menu = nullptr; if (_menuPeer) { - updateSelectedRow(_menuPeer); - _menuPeer = 0; - disconnect(App::main(), SIGNAL(peerUpdated(PeerData*)), this, SLOT(peerUpdated(PeerData*))); + updateSelectedRow(base::take(_menuPeer)); } - lastMousePos = QCursor::pos(); - if (rect().contains(mapFromGlobal(lastMousePos))) { - _selByMouse = true; + auto localPos = mapFromGlobal(QCursor::pos()); + if (rect().contains(localPos)) { + _mouseSelection = true; setMouseTracking(true); - onUpdateSelected(true); + updateSelected(localPos); } } } void DialogsInner::onParentGeometryChanged() { - lastMousePos = QCursor::pos(); - if (rect().contains(mapFromGlobal(lastMousePos))) { + auto localPos = mapFromGlobal(QCursor::pos()); + if (rect().contains(localPos)) { setMouseTracking(true); - onUpdateSelected(true); + updateSelected(localPos); } } void DialogsInner::onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) { - dialogs->peerNameChanged(Dialogs::Mode::All, peer, oldNames, oldChars); - if (importantDialogs) { - importantDialogs->peerNameChanged(Dialogs::Mode::Important, peer, oldNames, oldChars); + _dialogs->peerNameChanged(Dialogs::Mode::All, peer, oldNames, oldChars); + if (_dialogsImportant) { + _dialogsImportant->peerNameChanged(Dialogs::Mode::Important, peer, oldNames, oldChars); } - contactsNoDialogs->peerNameChanged(peer, oldNames, oldChars); - contacts->peerNameChanged(peer, oldNames, oldChars); + _contactsNoDialogs->peerNameChanged(peer, oldNames, oldChars); + _contacts->peerNameChanged(peer, oldNames, oldChars); update(); } @@ -797,7 +931,7 @@ void DialogsInner::onFilterUpdate(QString newFilter, bool force) { _state = DefaultState; _hashtagResults.clear(); _filterResults.clear(); - _peopleResults.clear(); + _peerSearchResults.clear(); _searchResults.clear(); _lastSearchDate = 0; _lastSearchPeer = 0; @@ -809,9 +943,9 @@ void DialogsInner::onFilterUpdate(QString newFilter, bool force) { _filterResults.clear(); if (!_searchInPeer && !f.isEmpty()) { const Dialogs::List *toFilter = nullptr; - if (!dialogs->isEmpty()) { + if (!_dialogs->isEmpty()) { for (fi = fb; fi != fe; ++fi) { - auto found = dialogs->filtered(fi->at(0)); + auto found = _dialogs->filtered(fi->at(0)); if (found->isEmpty()) { toFilter = nullptr; break; @@ -822,9 +956,9 @@ void DialogsInner::onFilterUpdate(QString newFilter, bool force) { } } const Dialogs::List *toFilterContacts = nullptr; - if (!contactsNoDialogs->isEmpty()) { + if (!_contactsNoDialogs->isEmpty()) { for (fi = fb; fi != fe; ++fi) { - auto found = contactsNoDialogs->filtered(fi->at(0)); + auto found = _contactsNoDialogs->filtered(fi->at(0)); if (found->isEmpty()) { toFilterContacts = nullptr; break; @@ -879,7 +1013,7 @@ void DialogsInner::onFilterUpdate(QString newFilter, bool force) { } } refresh(true); - setMouseSel(false, true); + setMouseSelection(false, true); } if (_state != DefaultState) { emit searchMessages(); @@ -892,7 +1026,7 @@ void DialogsInner::onHashtagFilterUpdate(QStringRef newFilter) { if (!_hashtagResults.isEmpty()) { _hashtagResults.clear(); refresh(true); - setMouseSel(false, true); + setMouseSelection(false, true); } return; } @@ -900,74 +1034,66 @@ void DialogsInner::onHashtagFilterUpdate(QStringRef newFilter) { if (cRecentSearchHashtags().isEmpty() && cRecentWriteHashtags().isEmpty()) { Local::readRecentHashtagsAndBots(); } - const RecentHashtagPack &recent(cRecentSearchHashtags()); + auto &recent = cRecentSearchHashtags(); _hashtagResults.clear(); if (!recent.isEmpty()) { - _hashtagResults.reserve(qMin(recent.size(), 5)); - for (RecentHashtagPack::const_iterator i = recent.cbegin(), e = recent.cend(); i != e; ++i) { + _hashtagResults.reserve(qMin(recent.size(), kHashtagResultsLimit)); + for (auto i = recent.cbegin(), e = recent.cend(); i != e; ++i) { if (i->first.startsWith(_hashtagFilter.midRef(1), Qt::CaseInsensitive) && i->first.size() + 1 != newFilter.size()) { - _hashtagResults.push_back(i->first); - if (_hashtagResults.size() == 5) break; + _hashtagResults.push_back(std_::make_unique(i->first)); + if (_hashtagResults.size() == kHashtagResultsLimit) break; } } } refresh(true); - setMouseSel(false, true); + setMouseSelection(false, true); } DialogsInner::~DialogsInner() { clearSearchResults(); } -void DialogsInner::clearSearchResults(bool clearPeople) { - if (clearPeople) _peopleResults.clear(); - if (!_searchResults.isEmpty()) { - for (SearchResults::const_iterator i = _searchResults.cbegin(), e = _searchResults.cend(); i != e; ++i) { - delete *i; - } - _searchResults.clear(); - } +void DialogsInner::clearSearchResults(bool clearPeerSearchResults) { + if (clearPeerSearchResults) _peerSearchResults.clear(); + _searchResults.clear(); _searchedCount = _searchedMigratedCount = 0; _lastSearchDate = 0; _lastSearchPeer = 0; _lastSearchId = _lastSearchMigratedId = 0; } -void DialogsInner::updateNotifySettings(PeerData *peer) { - if (_menu && _menuPeer == peer && _menu->actions().size() > 1) { - _menu->actions().at(1)->setText(lang(menuPeerMuted() ? lng_enable_notifications_from_tray : lng_disable_notifications_from_tray)); - } -} - -void DialogsInner::peerUpdated(PeerData *peer) { - if (_menu && _menuPeer == peer && _menuPeer->isUser() && _menu->actions().size() > 5) { - _menu->actions().at(5)->setText(lang(_menuPeer->asUser()->isBlocked() ? (_menuPeer->asUser()->botInfo ? lng_profile_unblock_bot : lng_profile_unblock_user) : (_menuPeer->asUser()->botInfo ? lng_profile_block_bot : lng_profile_block_user))); - } -} - PeerData *DialogsInner::updateFromParentDrag(QPoint globalPos) { - lastMousePos = globalPos; - _selByMouse = true; - onUpdateSelected(true); + _mouseSelection = true; + updateSelected(mapFromGlobal(globalPos)); if (_state == DefaultState) { - if (_sel) return _sel->history()->peer; + if (_selected) return _selected->history()->peer; } else if (_state == FilteredState || _state == SearchedState) { - if (_filteredSel >= 0 && _filteredSel < _filterResults.size()) { - return _filterResults[_filteredSel]->history()->peer; - } else if (_peopleSel >= 0 && _peopleSel < _peopleResults.size()) { - return _peopleResults[_peopleSel]; - } else if (_searchedSel >= 0 && _searchedSel < _searchResults.size()) { - return _searchResults[_searchedSel]->item()->history()->peer; + if (_filteredSelected >= 0 && _filteredSelected < _filterResults.size()) { + return _filterResults[_filteredSelected]->history()->peer; + } else if (_peerSearchSelected >= 0 && _peerSearchSelected < _peerSearchResults.size()) { + return _peerSearchResults[_peerSearchSelected]->peer; + } else if (_searchedSelected >= 0 && _searchedSelected < _searchResults.size()) { + return _searchResults[_searchedSelected]->item()->history()->peer; + } + } + return nullptr; +} + +void DialogsInner::setVisibleTopBottom(int visibleTop, int visibleBottom) { + _visibleAreaHeight = visibleBottom - visibleTop; + loadPeerPhotos(visibleTop); + if (visibleTop + PreloadHeightsCount * (visibleBottom - visibleTop) >= height()) { + if (_loadMoreCallback) { + _loadMoreCallback(); } } - return 0; } void DialogsInner::itemRemoved(HistoryItem *item) { int wasCount = _searchResults.size(); - for (int i = 0; i < _searchResults.size();) { - if (_searchResults[i]->item() == item) { - _searchResults.remove(i); + for (auto i = _searchResults.begin(); i != _searchResults.end();) { + if ((*i)->item() == item) { + i = _searchResults.erase(i); if (item->history()->peer == _searchInMigrated) { if (_searchedMigratedCount > 0) --_searchedMigratedCount; } else { @@ -1010,10 +1136,10 @@ void DialogsInner::dialogsReceived(const QVector &added) { } App::main()->applyNotifySetting(MTP_notifyPeer(d.vpeer), d.vnotify_settings, history); - if (!history->lastMsgDate.isNull()) { + if (!history->isPinnedDialog() && !history->lastMsgDate.isNull()) { addSavedPeersAfter(history->lastMsgDate); } - contactsNoDialogs->del(peer); + _contactsNoDialogs->del(peer); if (peer->migrateFrom()) { removeDialog(App::historyLoaded(peer->migrateFrom()->id)); } else if (peer->migrateTo() && peer->migrateTo()->amIn()) { @@ -1026,10 +1152,6 @@ void DialogsInner::dialogsReceived(const QVector &added) { } } Notify::unreadCounterUpdated(); - if (!_sel && !shownDialogs()->isEmpty()) { - _sel = *shownDialogs()->cbegin(); - _importantSwitchSel = false; - } refresh(); } @@ -1038,7 +1160,7 @@ void DialogsInner::addSavedPeersAfter(const QDateTime &date) { while (!saved.isEmpty() && (date.isNull() || date < saved.lastKey())) { History *history = App::history(saved.last()->id); history->setChatsListDate(saved.lastKey()); - contactsNoDialogs->del(history->peer); + _contactsNoDialogs->del(history->peer); saved.remove(saved.lastKey(), saved.last()); } } @@ -1062,7 +1184,7 @@ bool DialogsInner::searchReceived(const QVector &messages, DialogsSe if (auto peer = App::peerLoaded(peerId)) { if (lastDate) { auto item = App::histories().addNewMessage(message, NewMessageExisting); - _searchResults.push_back(new Dialogs::FakeRow(item)); + _searchResults.push_back(std_::make_unique(item)); lastDateFound = lastDate; if (isGlobalSearch) { _lastSearchDate = lastDateFound; @@ -1095,11 +1217,11 @@ bool DialogsInner::searchReceived(const QVector &messages, DialogsSe return lastDateFound != 0; } -void DialogsInner::peopleReceived(const QString &query, const QVector &people) { - _peopleQuery = query.toLower().trimmed(); - _peopleResults.clear(); - _peopleResults.reserve(people.size()); - for (auto i = people.cbegin(), e = people.cend(); i != e; ++i) { +void DialogsInner::peerSearchReceived(const QString &query, const QVector &result) { + _peerSearchQuery = query.toLower().trimmed(); + _peerSearchResults.clear(); + _peerSearchResults.reserve(result.size()); + for (auto i = result.cbegin(), e = result.cend(); i != e; ++i) { auto peerId = peerFromMTP(*i); if (auto history = App::historyLoaded(peerId)) { if (history->inChatList(Dialogs::Mode::All)) { @@ -1107,7 +1229,7 @@ void DialogsInner::peopleReceived(const QString &query, const QVector & } } if (auto peer = App::peerLoaded(peerId)) { - _peopleResults.push_back(App::peer(peerId)); + _peerSearchResults.push_back(std_::make_unique(App::peer(peerId))); } else { LOG(("API Error: user %1 was not loaded in DialogsInner::peopleReceived()").arg(peerId)); } @@ -1115,10 +1237,12 @@ void DialogsInner::peopleReceived(const QString &query, const QVector & refresh(); } -void DialogsInner::contactsReceived(const QVector &contacts) { - for (QVector::const_iterator i = contacts.cbegin(), e = contacts.cend(); i != e; ++i) { - int32 uid = i->c_contact().vuser_id.v; - if (uid == MTP::authedId() && App::self()) { +void DialogsInner::contactsReceived(const QVector &result) { + for_const (auto contact, result) { + if (contact.type() != mtpc_contact) continue; + + auto userId = contact.c_contact().vuser_id.v; + if (userId == MTP::authedId() && App::self()) { if (App::self()->contact < 1) { App::self()->contact = 1; Notify::userIsContactChanged(App::self()); @@ -1135,33 +1259,41 @@ void DialogsInner::notify_userIsContactChanged(UserData *user, bool fromThisApp) } if (user->contact > 0) { auto history = App::history(user->id); - contacts->addByName(history); + _contacts->addByName(history); if (auto row = shownDialogs()->getRow(user->id)) { if (fromThisApp) { - _sel = row; - _importantSwitchSel = false; + _selected = row; + _importantSwitchSelected = false; } - } else if (!dialogs->contains(user->id)) { - contactsNoDialogs->addByName(history); + } else if (!_dialogs->contains(user->id)) { + _contactsNoDialogs->addByName(history); } } else { - if (_sel && _sel->history()->peer == user) { - _sel = nullptr; + if (_selected && _selected->history()->peer == user) { + _selected = nullptr; } - contactsNoDialogs->del(user); - contacts->del(user); + if (_pressed && _pressed->history()->peer == user) { + setPressed(nullptr); + } + _contactsNoDialogs->del(user); + _contacts->del(user); } refresh(); } void DialogsInner::notify_historyMuteUpdated(History *history) { - if (!importantDialogs || !history->inChatList(Dialogs::Mode::All)) return; + if (!_dialogsImportant || !history->inChatList(Dialogs::Mode::All)) return; if (history->mute()) { - if (_sel && _sel->history() == history && Global::DialogsMode() == Dialogs::Mode::Important) { - _sel = nullptr; + if (Global::DialogsMode() == Dialogs::Mode::Important) { + if (_selected && _selected->history() == history) { + _selected = nullptr; + } + if (_pressed && _pressed->history() == history) { + setPressed(nullptr); + } } - history->removeFromChatList(Dialogs::Mode::Important, importantDialogs.get()); + history->removeFromChatList(Dialogs::Mode::Important, _dialogsImportant.get()); if (Global::DialogsMode() != Dialogs::Mode::Important) { return; } @@ -1169,10 +1301,10 @@ void DialogsInner::notify_historyMuteUpdated(History *history) { } else { bool creating = !history->inChatList(Dialogs::Mode::Important); if (creating) { - history->addToChatList(Dialogs::Mode::Important, importantDialogs.get()); + history->addToChatList(Dialogs::Mode::Important, _dialogsImportant.get()); } - auto changed = history->adjustByPosInChatList(Dialogs::Mode::All, dialogs.get()); + auto changed = history->adjustByPosInChatList(Dialogs::Mode::All, _dialogs.get()); if (Global::DialogsMode() != Dialogs::Mode::Important) { return; @@ -1185,7 +1317,7 @@ void DialogsInner::notify_historyMuteUpdated(History *history) { if (creating) { refresh(); } else if (_state == DefaultState && changed.movedFrom != changed.movedTo) { - update(0, qMin(from, to), fullWidth(), qAbs(from - to) + st::dialogsRowHeight); + update(0, qMin(from, to), getFullWidth(), qAbs(from - to) + st::dialogsRowHeight); } } } @@ -1196,20 +1328,20 @@ void DialogsInner::refresh(bool toTop) { if (shownDialogs()->isEmpty()) { h = st::noContactsHeight; if (cContactsReceived()) { - if (_addContactLnk.isHidden()) _addContactLnk.show(); + if (_addContactLnk->isHidden()) _addContactLnk->show(); } else { - if (!_addContactLnk.isHidden()) _addContactLnk.hide(); + if (!_addContactLnk->isHidden()) _addContactLnk->hide(); } } else { h = dialogsOffset() + shownDialogs()->size() * st::dialogsRowHeight; - if (!_addContactLnk.isHidden()) _addContactLnk.hide(); + if (!_addContactLnk->isHidden()) _addContactLnk->hide(); } } else { - if (!_addContactLnk.isHidden()) _addContactLnk.hide(); + if (!_addContactLnk->isHidden()) _addContactLnk->hide(); if (_state == FilteredState) { - h = searchedOffset() + (_searchResults.count() * st::dialogsRowHeight) + ((_searchResults.isEmpty() && !_searchInPeer) ? -st::searchedBarHeight : 0); + h = searchedOffset() + (_searchResults.size() * st::dialogsRowHeight) + ((_searchResults.isEmpty() && !_searchInPeer) ? -st::searchedBarHeight : 0); } else if (_state == SearchedState) { - h = searchedOffset() + (_searchResults.count() * st::dialogsRowHeight); + h = searchedOffset() + (_searchResults.size() * st::dialogsRowHeight); } } setHeight(h); @@ -1220,14 +1352,14 @@ void DialogsInner::refresh(bool toTop) { update(); } -void DialogsInner::setMouseSel(bool msel, bool toTop) { - _selByMouse = msel; - if (!_selByMouse && toTop) { +void DialogsInner::setMouseSelection(bool mouseSelection, bool toTop) { + _mouseSelection = mouseSelection; + if (!_mouseSelection && toTop) { if (_state == DefaultState) { - _sel = !shownDialogs()->isEmpty() ? *shownDialogs()->cbegin() : nullptr; - _importantSwitchSel = false; + _selected = nullptr; + _importantSwitchSelected = false; } else if (_state == FilteredState || _state == SearchedState) { // don't select first elem in search - _filteredSel = _peopleSel = _searchedSel = _hashtagSel = -1; + _filteredSelected = _peerSearchSelected = _searchedSelected = _hashtagSelected = -1; setCursor(style::cur_default); } } @@ -1235,14 +1367,14 @@ void DialogsInner::setMouseSel(bool msel, bool toTop) { void DialogsInner::setState(State newState) { _state = newState; + clearIrrelevantState(); if (_state == DefaultState) { clearSearchResults(); - _searchedSel = _peopleSel = _filteredSel = _hashtagSel = -1; - } else if (_state == DefaultState || _state == SearchedState) { + } else if (_state == FilteredState || _state == SearchedState) { _hashtagResults.clear(); - _hashtagSel = -1; + _hashtagSelected = -1; _filterResults.clear(); - _filteredSel = -1; + _filteredSelected = -1; } onFilterUpdate(_filter, true); } @@ -1256,13 +1388,13 @@ bool DialogsInner::hasFilteredResults() const { } void DialogsInner::searchInPeer(PeerData *peer) { - _searchInPeer = peer ? (peer->migrateTo() ? peer->migrateTo() : peer) : 0; - _searchInMigrated = _searchInPeer ? _searchInPeer->migrateFrom() : 0; + _searchInPeer = peer ? (peer->migrateTo() ? peer->migrateTo() : peer) : nullptr; + _searchInMigrated = _searchInPeer ? _searchInPeer->migrateFrom() : nullptr; if (_searchInPeer) { onHashtagFilterUpdate(QStringRef()); - _cancelSearchInPeer.show(); + _cancelSearchInPeer->show(); } else { - _cancelSearchInPeer.hide(); + _cancelSearchInPeer->hide(); } } @@ -1275,7 +1407,7 @@ void DialogsInner::clearFilter() { } _hashtagResults.clear(); _filterResults.clear(); - _peopleResults.clear(); + _peerSearchResults.clear(); _searchResults.clear(); _lastSearchDate = 0; _lastSearchPeer = 0; @@ -1287,79 +1419,79 @@ void DialogsInner::clearFilter() { void DialogsInner::selectSkip(int32 direction) { if (_state == DefaultState) { - if (_importantSwitchSel) { + if (_importantSwitchSelected) { if (!shownDialogs()->isEmpty() && direction > 0) { - _sel = *shownDialogs()->cbegin(); - _importantSwitchSel = false; + _selected = *shownDialogs()->cbegin(); + _importantSwitchSelected = false; } else { return; } - } else if (!_sel) { - if (importantDialogs) { - _importantSwitchSel = true; + } else if (!_selected) { + if (_dialogsImportant) { + _importantSwitchSelected = true; } else if (!shownDialogs()->isEmpty() && direction > 0) { - _sel = *shownDialogs()->cbegin(); + _selected = *shownDialogs()->cbegin(); } else { return; } } else if (direction > 0) { - auto next = shownDialogs()->cfind(_sel); + auto next = shownDialogs()->cfind(_selected); if (++next != shownDialogs()->cend()) { - _sel = *next; + _selected = *next; } } else { - auto prev = shownDialogs()->cfind(_sel); + auto prev = shownDialogs()->cfind(_selected); if (prev != shownDialogs()->cbegin()) { - _sel = *(--prev); - } else if (importantDialogs) { - _importantSwitchSel = true; - _sel = nullptr; + _selected = *(--prev); + } else if (_dialogsImportant) { + _importantSwitchSelected = true; + _selected = nullptr; } } - if (_importantSwitchSel || _sel) { - int fromY = _importantSwitchSel ? 0 : (dialogsOffset() + _sel->pos() * st::dialogsRowHeight); + if (_importantSwitchSelected || _selected) { + int fromY = _importantSwitchSelected ? 0 : (dialogsOffset() + _selected->pos() * st::dialogsRowHeight); emit mustScrollTo(fromY, fromY + st::dialogsRowHeight); } } else if (_state == FilteredState || _state == SearchedState) { - if (_hashtagResults.isEmpty() && _filterResults.isEmpty() && _peopleResults.isEmpty() && _searchResults.isEmpty()) return; - if ((_hashtagSel < 0 || _hashtagSel >= _hashtagResults.size()) && - (_filteredSel < 0 || _filteredSel >= _filterResults.size()) && - (_peopleSel < 0 || _peopleSel >= _peopleResults.size()) && - (_searchedSel < 0 || _searchedSel >= _searchResults.size())) { - if (_hashtagResults.isEmpty() && _filterResults.isEmpty() && _peopleResults.isEmpty()) { - _searchedSel = 0; + if (_hashtagResults.isEmpty() && _filterResults.isEmpty() && _peerSearchResults.isEmpty() && _searchResults.isEmpty()) return; + if ((_hashtagSelected < 0 || _hashtagSelected >= _hashtagResults.size()) && + (_filteredSelected < 0 || _filteredSelected >= _filterResults.size()) && + (_peerSearchSelected < 0 || _peerSearchSelected >= _peerSearchResults.size()) && + (_searchedSelected < 0 || _searchedSelected >= _searchResults.size())) { + if (_hashtagResults.isEmpty() && _filterResults.isEmpty() && _peerSearchResults.isEmpty()) { + _searchedSelected = 0; } else if (_hashtagResults.isEmpty() && _filterResults.isEmpty()) { - _peopleSel = 0; + _peerSearchSelected = 0; } else if (_hashtagResults.isEmpty()) { - _filteredSel = 0; + _filteredSelected = 0; } else { - _hashtagSel = 0; + _hashtagSelected = 0; } } else { - int32 cur = (_hashtagSel >= 0 && _hashtagSel < _hashtagResults.size()) ? _hashtagSel : ((_filteredSel >= 0 && _filteredSel < _filterResults.size()) ? (_hashtagResults.size() + _filteredSel) : ((_peopleSel >= 0 && _peopleSel < _peopleResults.size()) ? (_peopleSel + _filterResults.size() + _hashtagResults.size()) : (_searchedSel + _peopleResults.size() + _filterResults.size() + _hashtagResults.size()))); - cur = snap(cur + direction, 0, _hashtagResults.size() + _filterResults.size() + _peopleResults.size() + _searchResults.size() - 1); + int32 cur = (_hashtagSelected >= 0 && _hashtagSelected < _hashtagResults.size()) ? _hashtagSelected : ((_filteredSelected >= 0 && _filteredSelected < _filterResults.size()) ? (_hashtagResults.size() + _filteredSelected) : ((_peerSearchSelected >= 0 && _peerSearchSelected < _peerSearchResults.size()) ? (_peerSearchSelected + _filterResults.size() + _hashtagResults.size()) : (_searchedSelected + _peerSearchResults.size() + _filterResults.size() + _hashtagResults.size()))); + cur = snap(cur + direction, 0, _hashtagResults.size() + _filterResults.size() + _peerSearchResults.size() + _searchResults.size() - 1); if (cur < _hashtagResults.size()) { - _hashtagSel = cur; - _filteredSel = _peopleSel = _searchedSel = -1; + _hashtagSelected = cur; + _filteredSelected = _peerSearchSelected = _searchedSelected = -1; } else if (cur < _hashtagResults.size() + _filterResults.size()) { - _filteredSel = cur - _hashtagResults.size(); - _hashtagSel = _peopleSel = _searchedSel = -1; - } else if (cur < _hashtagResults.size() + _filterResults.size() + _peopleResults.size()) { - _peopleSel = cur - _hashtagResults.size() - _filterResults.size(); - _hashtagSel = _filteredSel = _searchedSel = -1; + _filteredSelected = cur - _hashtagResults.size(); + _hashtagSelected = _peerSearchSelected = _searchedSelected = -1; + } else if (cur < _hashtagResults.size() + _filterResults.size() + _peerSearchResults.size()) { + _peerSearchSelected = cur - _hashtagResults.size() - _filterResults.size(); + _hashtagSelected = _filteredSelected = _searchedSelected = -1; } else { - _hashtagSel = _filteredSel = _peopleSel = -1; - _searchedSel = cur - _hashtagResults.size() - _filterResults.size() - _peopleResults.size(); + _hashtagSelected = _filteredSelected = _peerSearchSelected = -1; + _searchedSelected = cur - _hashtagResults.size() - _filterResults.size() - _peerSearchResults.size(); } } - if (_hashtagSel >= 0 && _hashtagSel < _hashtagResults.size()) { - emit mustScrollTo(_hashtagSel * st::mentionHeight, (_hashtagSel + 1) * st::mentionHeight); - } else if (_filteredSel >= 0 && _filteredSel < _filterResults.size()) { - emit mustScrollTo(filteredOffset() + _filteredSel * st::dialogsRowHeight, filteredOffset() + (_filteredSel + 1) * st::dialogsRowHeight); - } else if (_peopleSel >= 0 && _peopleSel < _peopleResults.size()) { - emit mustScrollTo(peopleOffset() + _peopleSel * st::dialogsRowHeight + (_peopleSel ? 0 : -st::searchedBarHeight), peopleOffset() + (_peopleSel + 1) * st::dialogsRowHeight); + if (_hashtagSelected >= 0 && _hashtagSelected < _hashtagResults.size()) { + emit mustScrollTo(_hashtagSelected * st::mentionHeight, (_hashtagSelected + 1) * st::mentionHeight); + } else if (_filteredSelected >= 0 && _filteredSelected < _filterResults.size()) { + emit mustScrollTo(filteredOffset() + _filteredSelected * st::dialogsRowHeight, filteredOffset() + (_filteredSelected + 1) * st::dialogsRowHeight); + } else if (_peerSearchSelected >= 0 && _peerSearchSelected < _peerSearchResults.size()) { + emit mustScrollTo(peerSearchOffset() + _peerSearchSelected * st::dialogsRowHeight + (_peerSearchSelected ? 0 : -st::searchedBarHeight), peerSearchOffset() + (_peerSearchSelected + 1) * st::dialogsRowHeight); } else { - emit mustScrollTo(searchedOffset() + _searchedSel * st::dialogsRowHeight + (_searchedSel ? 0 : -st::searchedBarHeight), searchedOffset() + (_searchedSel + 1) * st::dialogsRowHeight); + emit mustScrollTo(searchedOffset() + _searchedSelected * st::dialogsRowHeight + (_searchedSelected ? 0 : -st::searchedBarHeight), searchedOffset() + (_searchedSelected + 1) * st::dialogsRowHeight); } } update(); @@ -1397,29 +1529,29 @@ void DialogsInner::scrollToPeer(const PeerId &peer, MsgId msgId) { void DialogsInner::selectSkipPage(int32 pixels, int32 direction) { int toSkip = pixels / int(st::dialogsRowHeight); if (_state == DefaultState) { - if (!_sel) { + if (!_selected) { if (direction > 0 && !shownDialogs()->isEmpty()) { - _sel = *shownDialogs()->cbegin(); - _importantSwitchSel = false; + _selected = *shownDialogs()->cbegin(); + _importantSwitchSelected = false; } else { return; } } if (direction > 0) { - for (auto i = shownDialogs()->cfind(_sel), end = shownDialogs()->cend(); i != end && (toSkip--); ++i) { - _sel = *i; + for (auto i = shownDialogs()->cfind(_selected), end = shownDialogs()->cend(); i != end && (toSkip--); ++i) { + _selected = *i; } } else { - for (auto i = shownDialogs()->cfind(_sel), b = shownDialogs()->cbegin(); i != b && (toSkip--);) { - _sel = *(--i); + for (auto i = shownDialogs()->cfind(_selected), b = shownDialogs()->cbegin(); i != b && (toSkip--);) { + _selected = *(--i); } - if (toSkip && importantDialogs) { - _importantSwitchSel = true; - _sel = nullptr; + if (toSkip && _dialogsImportant) { + _importantSwitchSelected = true; + _selected = nullptr; } } - if (_importantSwitchSel || _sel) { - int fromY = (_importantSwitchSel ? 0 : (dialogsOffset() + _sel->pos() * st::dialogsRowHeight)); + if (_importantSwitchSelected || _selected) { + int fromY = (_importantSwitchSelected ? 0 : (dialogsOffset() + _selected->pos() * st::dialogsRowHeight)); emit mustScrollTo(fromY, fromY + st::dialogsRowHeight); } } else { @@ -1428,13 +1560,14 @@ void DialogsInner::selectSkipPage(int32 pixels, int32 direction) { update(); } -void DialogsInner::loadPeerPhotos(int32 yFrom) { +void DialogsInner::loadPeerPhotos(int visibleTop) { if (!parentWidget()) return; - int32 yTo = yFrom + parentWidget()->height() * 5; + auto yFrom = visibleTop; + auto yTo = visibleTop + _visibleAreaHeight * (PreloadHeightsCount + 1); MTP::clearLoaderPriorities(); if (_state == DefaultState) { - int32 otherStart = shownDialogs()->size() * st::dialogsRowHeight; + auto otherStart = shownDialogs()->size() * st::dialogsRowHeight; if (yFrom < otherStart) { for (auto i = shownDialogs()->cfind(yFrom, st::dialogsRowHeight), end = shownDialogs()->cend(); i != end; ++i) { if (((*i)->pos() * st::dialogsRowHeight) >= yTo) { @@ -1461,18 +1594,18 @@ void DialogsInner::loadPeerPhotos(int32 yFrom) { from = (yFrom > filteredOffset() + st::searchedBarHeight ? ((yFrom - filteredOffset() - st::searchedBarHeight) / int32(st::dialogsRowHeight)) : 0) - _filterResults.size(); if (from < 0) from = 0; - if (from < _peopleResults.size()) { + if (from < _peerSearchResults.size()) { int32 to = (yTo > filteredOffset() + st::searchedBarHeight ? ((yTo - filteredOffset() - st::searchedBarHeight) / int32(st::dialogsRowHeight)) : 0) - _filterResults.size() + 1, w = width(); - if (to > _peopleResults.size()) to = _peopleResults.size(); + if (to > _peerSearchResults.size()) to = _peerSearchResults.size(); for (; from < to; ++from) { - _peopleResults[from]->loadUserpic(); + _peerSearchResults[from]->peer->loadUserpic(); } } - from = (yFrom > filteredOffset() + ((_peopleResults.isEmpty() ? 0 : st::searchedBarHeight) + st::searchedBarHeight) ? ((yFrom - filteredOffset() - (_peopleResults.isEmpty() ? 0 : st::searchedBarHeight) - st::searchedBarHeight) / int32(st::dialogsRowHeight)) : 0) - _filterResults.size() - _peopleResults.size(); + from = (yFrom > filteredOffset() + ((_peerSearchResults.isEmpty() ? 0 : st::searchedBarHeight) + st::searchedBarHeight) ? ((yFrom - filteredOffset() - (_peerSearchResults.isEmpty() ? 0 : st::searchedBarHeight) - st::searchedBarHeight) / int32(st::dialogsRowHeight)) : 0) - _filterResults.size() - _peerSearchResults.size(); if (from < 0) from = 0; if (from < _searchResults.size()) { - int32 to = (yTo > filteredOffset() + (_peopleResults.isEmpty() ? 0 : st::searchedBarHeight) + st::searchedBarHeight ? ((yTo - filteredOffset() - (_peopleResults.isEmpty() ? 0 : st::searchedBarHeight) - st::searchedBarHeight) / int32(st::dialogsRowHeight)) : 0) - _filterResults.size() - _peopleResults.size() + 1, w = width(); + int32 to = (yTo > filteredOffset() + (_peerSearchResults.isEmpty() ? 0 : st::searchedBarHeight) + st::searchedBarHeight ? ((yTo - filteredOffset() - (_peerSearchResults.isEmpty() ? 0 : st::searchedBarHeight) - st::searchedBarHeight) / int32(st::dialogsRowHeight)) : 0) - _filterResults.size() - _peerSearchResults.size() + 1, w = width(); if (to > _searchResults.size()) to = _searchResults.size(); for (; from < to; ++from) { @@ -1486,7 +1619,7 @@ bool DialogsInner::choosePeer() { History *history = nullptr; MsgId msgId = ShowAtUnreadMsgId; if (_state == DefaultState) { - if (_importantSwitchSel && importantDialogs) { + if (_importantSwitchSelected && _dialogsImportant) { clearSelection(); if (Global::DialogsMode() == Dialogs::Mode::All) { Global::SetDialogsMode(Dialogs::Mode::Important); @@ -1495,20 +1628,18 @@ bool DialogsInner::choosePeer() { } Local::writeUserSettings(); refresh(); - _importantSwitchSel = true; + _importantSwitchSelected = true; return true; - } else if (_sel) { - history = _sel->history(); + } else if (_selected) { + history = _selected->history(); } } else if (_state == FilteredState || _state == SearchedState) { - if (_hashtagSel >= 0 && _hashtagSel < _hashtagResults.size()) { - QString hashtag = _hashtagResults.at(_hashtagSel); - if (_overDelete) { - lastMousePos = QCursor::pos(); - + if (_hashtagSelected >= 0 && _hashtagSelected < _hashtagResults.size()) { + auto &hashtag = _hashtagResults[_hashtagSelected]; + if (_hashtagDeleteSelected) { RecentHashtagPack recent(cRecentSearchHashtags()); for (RecentHashtagPack::iterator i = recent.begin(); i != recent.cend();) { - if (i->first == hashtag) { + if (i->first == hashtag->tag) { i = recent.erase(i); } else { ++i; @@ -1518,35 +1649,35 @@ bool DialogsInner::choosePeer() { Local::writeRecentHashtagsAndBots(); emit refreshHashtags(); - _selByMouse = true; - onUpdateSelected(true); + _mouseSelection = true; + updateSelected(); } else { - saveRecentHashtags('#' + hashtag); - emit completeHashtag(hashtag); + saveRecentHashtags('#' + hashtag->tag); + emit completeHashtag(hashtag->tag); } return true; } - if (_filteredSel >= 0 && _filteredSel < _filterResults.size()) { - history = _filterResults[_filteredSel]->history(); - } else if (_peopleSel >= 0 && _peopleSel < _peopleResults.size()) { - history = App::history(_peopleResults[_peopleSel]->id); - } else if (_searchedSel >= 0 && _searchedSel < _searchResults.size()) { - history = _searchResults[_searchedSel]->item()->history(); - msgId = _searchResults[_searchedSel]->item()->id; + if (_filteredSelected >= 0 && _filteredSelected < _filterResults.size()) { + history = _filterResults[_filteredSelected]->history(); + } else if (_peerSearchSelected >= 0 && _peerSearchSelected < _peerSearchResults.size()) { + history = App::history(_peerSearchResults[_peerSearchSelected]->peer->id); + } else if (_searchedSelected >= 0 && _searchedSelected < _searchResults.size()) { + history = _searchResults[_searchedSelected]->item()->history(); + msgId = _searchResults[_searchedSelected]->item()->id; } } if (history) { if (msgId > 0) { saveRecentHashtags(_filter); } - bool chosen = (!App::main()->selectingPeer(true) && (_state == FilteredState || _state == SearchedState) && _filteredSel >= 0 && _filteredSel < _filterResults.size()); + bool chosen = (!App::main()->selectingPeer(true) && (_state == FilteredState || _state == SearchedState) && _filteredSelected >= 0 && _filteredSelected < _filterResults.size()); App::main()->choosePeer(history->peer->id, msgId); if (chosen) { emit searchResultChosen(); } updateSelectedRow(); - _sel = nullptr; - _filteredSel = _peopleSel = _searchedSel = _hashtagSel = -1; + _selected = nullptr; + _hashtagSelected = _filteredSelected = _peerSearchSelected = _searchedSelected = -1; return true; } return false; @@ -1581,19 +1712,19 @@ void DialogsInner::saveRecentHashtags(const QString &text) { } void DialogsInner::destroyData() { - _sel = nullptr; - _hashtagSel = -1; + _selected = nullptr; + _hashtagSelected = -1; _hashtagResults.clear(); - _filteredSel = -1; + _filteredSelected = -1; _filterResults.clear(); _filter.clear(); - _searchedSel = _peopleSel = -1; + _searchedSelected = _peerSearchSelected = -1; clearSearchResults(); - contacts = nullptr; - contactsNoDialogs = nullptr; - dialogs = nullptr; - if (importantDialogs) { - importantDialogs = nullptr; + _contacts = nullptr; + _contactsNoDialogs = nullptr; + _dialogs = nullptr; + if (_dialogsImportant) { + _dialogsImportant = nullptr; } } @@ -1617,9 +1748,9 @@ void DialogsInner::peerBefore(const PeerData *inPeer, MsgId inMsg, PeerData *&ou return; } else if (_state == FilteredState || _state == SearchedState) { if (inMsg && !_searchResults.isEmpty()) { - for (SearchResults::const_iterator b = _searchResults.cbegin(), i = b + 1, e = _searchResults.cend(); i != e; ++i) { + for (auto b = _searchResults.cbegin(), i = b + 1, e = _searchResults.cend(); i != e; ++i) { if ((*i)->item()->history()->peer == inPeer && (*i)->item()->id == inMsg) { - SearchResults::const_iterator j = i - 1; + auto j = i - 1; outPeer = (*j)->item()->history()->peer; outMsg = (*j)->item()->id; return; @@ -1627,27 +1758,27 @@ void DialogsInner::peerBefore(const PeerData *inPeer, MsgId inMsg, PeerData *&ou } if (_searchResults.at(0)->item()->history()->peer == inPeer && _searchResults.at(0)->item()->id == inMsg) { outMsg = ShowAtUnreadMsgId; - if (_peopleResults.isEmpty()) { + if (_peerSearchResults.isEmpty()) { if (_filterResults.isEmpty()) { outPeer = nullptr; } else { outPeer = _filterResults.back()->history()->peer; } } else { - outPeer = _peopleResults.back(); + outPeer = _peerSearchResults.back()->peer; } return; } } - if (!_peopleResults.isEmpty() && _peopleResults.at(0) == inPeer) { + if (!_peerSearchResults.isEmpty() && _peerSearchResults[0]->peer == inPeer) { outPeer = _filterResults.isEmpty() ? 0 : _filterResults.back()->history()->peer; outMsg = ShowAtUnreadMsgId; return; } - if (!_peopleResults.isEmpty()) { - for (PeopleResults::const_iterator b = _peopleResults.cbegin(), i = b + 1, e = _peopleResults.cend(); i != e; ++i) { - if ((*i) == inPeer) { - outPeer = (*(i - 1)); + if (!_peerSearchResults.isEmpty()) { + for (auto b = _peerSearchResults.cbegin(), i = b + 1, e = _peerSearchResults.cend(); i != e; ++i) { + if ((*i)->peer == inPeer) { + outPeer = (*(i - 1))->peer; outMsg = ShowAtUnreadMsgId; return; } @@ -1659,7 +1790,7 @@ void DialogsInner::peerBefore(const PeerData *inPeer, MsgId inMsg, PeerData *&ou return; } - for (FilteredDialogs::const_iterator b = _filterResults.cbegin(), i = b + 1, e = _filterResults.cend(); i != e; ++i) { + for (auto b = _filterResults.cbegin(), i = b + 1, e = _filterResults.cend(); i != e; ++i) { if ((*i)->history()->peer == inPeer) { outPeer = (*(i - 1))->history()->peer; outMsg = ShowAtUnreadMsgId; @@ -1691,7 +1822,7 @@ void DialogsInner::peerAfter(const PeerData *inPeer, MsgId inMsg, PeerData *&out return; } else if (_state == FilteredState || _state == SearchedState) { if (inMsg) { - for (SearchResults::const_iterator i = _searchResults.cbegin(), e = _searchResults.cend(); i != e; ++i) { + for (auto i = _searchResults.cbegin(), e = _searchResults.cend(); i != e; ++i) { if ((*i)->item()->history()->peer == inPeer && (*i)->item()->id == inMsg) { ++i; outPeer = (i == e) ? nullptr : (*i)->item()->history()->peer; @@ -1700,14 +1831,14 @@ void DialogsInner::peerAfter(const PeerData *inPeer, MsgId inMsg, PeerData *&out } } } - for (PeopleResults::const_iterator i = _peopleResults.cbegin(), e = _peopleResults.cend(); i != e; ++i) { - if ((*i) == inPeer) { + for (auto i = _peerSearchResults.cbegin(), e = _peerSearchResults.cend(); i != e; ++i) { + if ((*i)->peer == inPeer) { ++i; if (i == e && !_searchResults.isEmpty()) { outPeer = _searchResults.front()->item()->history()->peer; outMsg = _searchResults.front()->item()->id; } else { - outPeer = (i == e) ? nullptr : (*i); + outPeer = (i == e) ? nullptr : (*i)->peer; outMsg = ShowAtUnreadMsgId; } return; @@ -1716,8 +1847,8 @@ void DialogsInner::peerAfter(const PeerData *inPeer, MsgId inMsg, PeerData *&out for (FilteredDialogs::const_iterator i = _filterResults.cbegin(), e = _filterResults.cend(); i != e; ++i) { if ((*i)->history()->peer == inPeer) { ++i; - if (i == e && !_peopleResults.isEmpty()) { - outPeer = _peopleResults.front(); + if (i == e && !_peerSearchResults.isEmpty()) { + outPeer = _peerSearchResults.front()->peer; outMsg = ShowAtUnreadMsgId; } else if (i == e && !_searchResults.isEmpty()) { outPeer = _searchResults.front()->item()->history()->peer; @@ -1735,23 +1866,11 @@ void DialogsInner::peerAfter(const PeerData *inPeer, MsgId inMsg, PeerData *&out } Dialogs::IndexedList *DialogsInner::contactsList() { - return contacts.get(); + return _contacts.get(); } Dialogs::IndexedList *DialogsInner::dialogsList() { - return dialogs.get(); -} - -DialogsInner::FilteredDialogs &DialogsInner::filteredList() { - return _filterResults; -} - -DialogsInner::PeopleResults &DialogsInner::peopleList() { - return _peopleResults; -} - -DialogsInner::SearchResults &DialogsInner::searchList() { - return _searchResults; + return _dialogs.get(); } int32 DialogsInner::lastSearchDate() const { @@ -1770,46 +1889,44 @@ MsgId DialogsInner::lastSearchMigratedId() const { return _lastSearchMigratedId; } -DialogsWidget::DialogsWidget(MainWidget *parent) : TWidget(parent) -, _dragInScroll(false) -, _dragForward(false) -, _dialogsFull(false) -, _dialogsOffsetDate(0) -, _dialogsOffsetId(0) -, _dialogsOffsetPeer(0) -, _dialogsRequest(0) -, _contactsRequest(0) -, _filter(this, st::dlgFilter, lang(lng_dlg_filter)) -, _newGroup(this, QString(), st::dialogsNewChatButton) -, _addContact(this, st::btnAddContact) -, _cancelSearch(this, st::btnCancelSearch) -, _scroll(this, st::dialogsScroll) -, _inner(&_scroll, parent) -, _a_show(animation(this, &DialogsWidget::step_show)) -, _searchInPeer(0) -, _searchInMigrated(0) -, _searchFull(false) -, _searchFullMigrated(false) -, _peopleFull(false) -{ - _scroll.setWidget(&_inner); - _scroll.setFocusPolicy(Qt::NoFocus); - connect(&_inner, SIGNAL(mustScrollTo(int,int)), &_scroll, SLOT(scrollToY(int,int))); - connect(&_inner, SIGNAL(dialogMoved(int,int)), this, SLOT(onDialogMoved(int,int))); - connect(&_inner, SIGNAL(searchMessages()), this, SLOT(onNeedSearchMessages())); - connect(&_inner, SIGNAL(searchResultChosen()), this, SLOT(onCancel())); - connect(&_inner, SIGNAL(completeHashtag(QString)), this, SLOT(onCompleteHashtag(QString))); - connect(&_inner, SIGNAL(refreshHashtags()), this, SLOT(onFilterCursorMoved())); - connect(&_inner, SIGNAL(cancelSearchInPeer()), this, SLOT(onCancelSearchInPeer())); - connect(&_scroll, SIGNAL(geometryChanged()), &_inner, SLOT(onParentGeometryChanged())); - connect(&_scroll, SIGNAL(scrolled()), this, SLOT(onListScroll())); - connect(&_filter, SIGNAL(cancelled()), this, SLOT(onCancel())); - connect(&_filter, SIGNAL(changed()), this, SLOT(onFilterUpdate())); - connect(&_filter, SIGNAL(cursorPositionChanged(int,int)), this, SLOT(onFilterCursorMoved(int,int))); - connect(parent, SIGNAL(dialogsUpdated()), this, SLOT(onListScroll())); - connect(&_addContact, SIGNAL(clicked()), this, SLOT(onAddContact())); - connect(_newGroup, SIGNAL(clicked()), this, SLOT(onNewGroup())); - connect(&_cancelSearch, SIGNAL(clicked()), this, SLOT(onCancelSearch())); +DialogsWidget::DialogsWidget(QWidget *parent) : TWidget(parent) +, _mainMenuToggle(this, st::dialogsMenuToggle) +, _filter(this, st::dialogsFilter, lang(lng_dlg_filter)) +, _cancelSearch(this, st::dialogsCancelSearch) +, _lockUnlock(this, st::dialogsLock) +, _scroll(this, st::dialogsScroll) { + _inner = _scroll->setOwnedWidget(object_ptr(this, parent)); + connect(_inner, SIGNAL(mustScrollTo(int,int)), _scroll, SLOT(scrollToY(int,int))); + connect(_inner, SIGNAL(dialogMoved(int,int)), this, SLOT(onDialogMoved(int,int))); + connect(_inner, SIGNAL(searchMessages()), this, SLOT(onNeedSearchMessages())); + connect(_inner, SIGNAL(searchResultChosen()), this, SLOT(onCancel())); + connect(_inner, SIGNAL(completeHashtag(QString)), this, SLOT(onCompleteHashtag(QString))); + connect(_inner, SIGNAL(refreshHashtags()), this, SLOT(onFilterCursorMoved())); + connect(_inner, SIGNAL(cancelSearchInPeer()), this, SLOT(onCancelSearchInPeer())); + connect(_scroll, SIGNAL(geometryChanged()), _inner, SLOT(onParentGeometryChanged())); + connect(_scroll, SIGNAL(scrolled()), this, SLOT(onListScroll())); + connect(_filter, SIGNAL(cancelled()), this, SLOT(onCancel())); + connect(_filter, SIGNAL(changed()), this, SLOT(onFilterUpdate())); + connect(_filter, SIGNAL(cursorPositionChanged(int,int)), this, SLOT(onFilterCursorMoved(int,int))); + +#ifndef TDESKTOP_DISABLE_AUTOUPDATE + Sandbox::connect(SIGNAL(updateLatest()), this, SLOT(onCheckUpdateStatus())); + Sandbox::connect(SIGNAL(updateFailed()), this, SLOT(onCheckUpdateStatus())); + Sandbox::connect(SIGNAL(updateReady()), this, SLOT(onCheckUpdateStatus())); + onCheckUpdateStatus(); +#endif // !TDESKTOP_DISABLE_AUTOUPDATE + + subscribe(Adaptive::Changed(), [this] { updateForwardBar(); }); + + _cancelSearch->setClickedCallback([this] { onCancelSearch(); }); + _lockUnlock->setVisible(Global::LocalPasscode()); + subscribe(Global::RefLocalPasscodeChanged(), [this] { updateLockUnlockVisibility(); }); + _lockUnlock->setClickedCallback([this] { + _lockUnlock->setIconOverride(&st::dialogsUnlockIcon, &st::dialogsUnlockIconOver); + App::wnd()->setupPasscode(); + _lockUnlock->setIconOverride(nullptr); + }); + _mainMenuToggle->setClickedCallback([this] { showMainMenu(); }); _chooseByDragTimer.setSingleShot(true); connect(&_chooseByDragTimer, SIGNAL(timeout()), this, SLOT(onChooseByDrag())); @@ -1819,103 +1936,110 @@ DialogsWidget::DialogsWidget(MainWidget *parent) : TWidget(parent) _searchTimer.setSingleShot(true); connect(&_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchMessages())); - _scroll.show(); - _filter.show(); - _filter.move(st::dialogsPadding.x(), st::dialogsFilterPadding); - _filter.setFocusPolicy(Qt::StrongFocus); - _filter.customUpDown(true); - _addContact.hide(); - _newGroup->show(); - _cancelSearch.hide(); - _newGroup->move(width() - _newGroup->width() - st::dialogsPadding.x(), 0); - _addContact.move(width() - _addContact.width() - st::dialogsPadding.x(), 0); - _cancelSearch.move(width() - _cancelSearch.width() - st::dialogsPadding.x(), 0); + _inner->setLoadMoreCallback([this] { + if (_inner->state() == DialogsInner::SearchedState || (_inner->state() == DialogsInner::FilteredState && _searchInMigrated && _searchFull && !_searchFullMigrated)) { + onSearchMore(); + } else { + loadDialogs(); + } + }); + + _filter->setFocusPolicy(Qt::StrongFocus); + _filter->customUpDown(true); } +#ifndef TDESKTOP_DISABLE_AUTOUPDATE +void DialogsWidget::onCheckUpdateStatus() { + if (Sandbox::updatingState() == Application::UpdatingReady) { + if (_updateTelegram) return; + _updateTelegram.create(this, lang(lng_update_telegram).toUpper(), st::dialogsUpdateButton); + _updateTelegram->show(); + _updateTelegram->setClickedCallback([] { + checkReadyUpdate(); + App::restart(); + }); + } else { + if (!_updateTelegram) return; + _updateTelegram.destroy(); + } + updateControlsGeometry(); +} +#endif // TDESKTOP_DISABLE_AUTOUPDATE + void DialogsWidget::activate() { - _filter.setFocus(); - _inner.activate(); + _filter->setFocus(); + _inner->activate(); } void DialogsWidget::createDialog(History *history) { - bool creating = !history->inChatList(Dialogs::Mode::All); - _inner.createDialog(history); + auto creating = !history->inChatList(Dialogs::Mode::All); + _inner->createDialog(history); if (creating && history->peer->migrateFrom()) { - if (History *h = App::historyLoaded(history->peer->migrateFrom()->id)) { - if (h->inChatList(Dialogs::Mode::All)) { - removeDialog(h); + if (auto migrated = App::historyLoaded(history->peer->migrateFrom()->id)) { + if (migrated->inChatList(Dialogs::Mode::All)) { + removeDialog(migrated); } } } } void DialogsWidget::dlgUpdated(Dialogs::Mode list, Dialogs::Row *row) { - _inner.dlgUpdated(list, row); + _inner->dlgUpdated(list, row); } -void DialogsWidget::dlgUpdated(History *row, MsgId msgId) { - _inner.dlgUpdated(row, msgId); +void DialogsWidget::dlgUpdated(PeerData *peer, MsgId msgId) { + _inner->dlgUpdated(peer, msgId); } void DialogsWidget::dialogsToUp() { - if (_filter.getLastText().trimmed().isEmpty()) { - _scroll.scrollToY(0); + if (_filter->getLastText().trimmed().isEmpty()) { + _scroll->scrollToY(0); } } +void DialogsWidget::showFast() { + show(); + updateForwardBar(); +} + void DialogsWidget::showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams ¶ms) { - if (App::app()) App::app()->mtpPause(); + _showDirection = direction; + + _a_show.finish(); _cacheUnder = params.oldContentCache; show(); + updateForwardBar(); _cacheOver = App::main()->grabForShowAnimation(params); - _a_show.stop(); - - _scroll.hide(); - _filter.hide(); - _cancelSearch.hide(); - _newGroup->hide(); + _scroll->hide(); + _mainMenuToggle->hide(); + if (_forwardCancel) _forwardCancel->hide(); + _filter->hide(); + _cancelSearch->hideFast(); + _lockUnlock->hide(); int delta = st::slideShift; - if (direction == Window::SlideDirection::FromLeft) { - a_progress = anim::fvalue(1, 0); + if (_showDirection == Window::SlideDirection::FromLeft) { std::swap(_cacheUnder, _cacheOver); - a_coordUnder = anim::ivalue(-delta, 0); - a_coordOver = anim::ivalue(0, width()); - } else { - a_progress = anim::fvalue(0, 1); - a_coordUnder = anim::ivalue(0, -delta); - a_coordOver = anim::ivalue(width(), 0); } - _a_show.start(); + _a_show.start([this] { animationCallback(); }, 0., 1., st::slideDuration, Window::SlideAnimation::transition()); } -void DialogsWidget::step_show(float64 ms, bool timer) { - float64 dt = ms / st::slideDuration; - if (dt >= 1) { - _a_show.stop(); - - a_coordUnder.finish(); - a_coordOver.finish(); - a_progress.finish(); - +void DialogsWidget::animationCallback() { + update(); + if (!_a_show.animating()) { _cacheUnder = _cacheOver = QPixmap(); - _scroll.show(); - _filter.show(); - _a_show.stop(); + _scroll->show(); + _mainMenuToggle->show(); + if (_forwardCancel) _forwardCancel->show(); + _filter->show(); + updateLockUnlockVisibility(); onFilterUpdate(); if (App::wnd()) App::wnd()->setInnerFocus(); - - if (App::app()) App::app()->mtpUnpause(); - } else { - a_coordUnder.update(dt, st::slideFunction); - a_coordOver.update(dt, st::slideFunction); - a_progress.update(dt, st::slideFunction); } - if (timer) update(); } void DialogsWidget::onCancel() { @@ -1924,34 +2048,30 @@ void DialogsWidget::onCancel() { } } -void DialogsWidget::updateNotifySettings(PeerData *peer) { - _inner.updateNotifySettings(peer); -} - void DialogsWidget::notify_userIsContactChanged(UserData *user, bool fromThisApp) { if (fromThisApp) { - _filter.setText(QString()); - _filter.updatePlaceholder(); + _filter->setText(QString()); + _filter->updatePlaceholder(); onFilterUpdate(); } - _inner.notify_userIsContactChanged(user, fromThisApp); + _inner->notify_userIsContactChanged(user, fromThisApp); } void DialogsWidget::notify_historyMuteUpdated(History *history) { - _inner.notify_historyMuteUpdated(history); + _inner->notify_historyMuteUpdated(history); } void DialogsWidget::unreadCountsReceived(const QVector &dialogs) { } -void DialogsWidget::dialogsReceived(const MTPmessages_Dialogs &dialogs, mtpRequestId req) { - if (_dialogsRequest != req) return; +void DialogsWidget::dialogsReceived(const MTPmessages_Dialogs &dialogs, mtpRequestId requestId) { + if (_dialogsRequestId != requestId) return; const QVector *dialogsList = 0; const QVector *messagesList = 0; switch (dialogs.type()) { case mtpc_messages_dialogs: { - const auto &data(dialogs.c_messages_dialogs()); + auto &data = dialogs.c_messages_dialogs(); App::feedUsers(data.vusers); App::feedChats(data.vchats); messagesList = &data.vmessages.c_vector().v; @@ -1959,7 +2079,7 @@ void DialogsWidget::dialogsReceived(const MTPmessages_Dialogs &dialogs, mtpReque _dialogsFull = true; } break; case mtpc_messages_dialogsSlice: { - const auto &data(dialogs.c_messages_dialogsSlice()); + auto &data = dialogs.c_messages_dialogsSlice(); App::feedUsers(data.vusers); App::feedChats(data.vchats); messagesList = &data.vmessages.c_vector().v; @@ -1967,18 +2087,11 @@ void DialogsWidget::dialogsReceived(const MTPmessages_Dialogs &dialogs, mtpReque } break; } - if (!_contactsRequest) { - _contactsRequest = MTP::send(MTPcontacts_GetContacts(MTP_string("")), rpcDone(&DialogsWidget::contactsReceived), rpcFail(&DialogsWidget::contactsFailed)); + if (!cContactsReceived() && !_contactsRequestId) { + _contactsRequestId = MTP::send(MTPcontacts_GetContacts(MTP_string("")), rpcDone(&DialogsWidget::contactsReceived), rpcFail(&DialogsWidget::contactsFailed)); } - if (messagesList) { - App::feedMsgs(*messagesList, NewMessageLast); - } if (dialogsList) { - unreadCountsReceived(*dialogsList); - _inner.dialogsReceived(*dialogsList); - onListScroll(); - TimeId lastDate = 0; PeerId lastPeer = 0; MsgId lastMsgId = 0; @@ -1988,20 +2101,25 @@ void DialogsWidget::dialogsReceived(const MTPmessages_Dialogs &dialogs, mtpReque continue; } - if (auto peer = peerFromMTP(dialog.c_dialog().vpeer)) { - if (!lastPeer) lastPeer = peer; - if (auto msgId = dialog.c_dialog().vtop_message.v) { - if (!lastMsgId) lastMsgId = msgId; - for (int j = messagesList->size(); j > 0;) { - auto &message = messagesList->at(--j); - if (idFromMessage(message) == msgId && peerFromMessage(message) == peer) { - if (auto date = dateFromMessage(message)) { - lastDate = date; + auto &dialogData = dialog.c_dialog(); + if (auto peer = peerFromMTP(dialogData.vpeer)) { + auto history = App::history(peer); + history->setPinnedDialog(dialogData.is_pinned()); + + if (!lastDate) { + if (!lastPeer) lastPeer = peer; + if (auto msgId = dialogData.vtop_message.v) { + if (!lastMsgId) lastMsgId = msgId; + for (int j = messagesList->size(); j > 0;) { + auto &message = messagesList->at(--j); + if (idFromMessage(message) == msgId && peerFromMessage(message) == peer) { + if (auto date = dateFromMessage(message)) { + lastDate = date; + } + break; } - break; } } - if (lastDate) break; } } } @@ -2012,35 +2130,70 @@ void DialogsWidget::dialogsReceived(const MTPmessages_Dialogs &dialogs, mtpReque } else { _dialogsFull = true; } + + t_assert(messagesList != nullptr); + App::feedMsgs(*messagesList, NewMessageLast); + + unreadCountsReceived(*dialogsList); + _inner->dialogsReceived(*dialogsList); + onListScroll(); } else { _dialogsFull = true; } - _dialogsRequest = 0; + _dialogsRequestId = 0; loadDialogs(); } -bool DialogsWidget::dialogsFailed(const RPCError &error, mtpRequestId req) { +void DialogsWidget::pinnedDialogsReceived(const MTPmessages_PeerDialogs &dialogs, mtpRequestId requestId) { + if (_pinnedDialogsRequestId != requestId) return; + + if (dialogs.type() == mtpc_messages_peerDialogs) { + App::histories().clearPinned(); + + auto &dialogsData = dialogs.c_messages_peerDialogs(); + App::feedUsers(dialogsData.vusers); + App::feedChats(dialogsData.vchats); + auto &list = dialogsData.vdialogs.c_vector().v; + for (auto i = list.size(); i > 0;) { + auto &dialog = list[--i]; + if (dialog.type() != mtpc_dialog) { + continue; + } + + auto &dialogData = dialog.c_dialog(); + if (auto peer = peerFromMTP(dialogData.vpeer)) { + auto history = App::history(peer); + history->setPinnedDialog(dialogData.is_pinned()); + } + } + App::feedMsgs(dialogsData.vmessages, NewMessageLast); + unreadCountsReceived(list); + _inner->dialogsReceived(list); + onListScroll(); + } + + _pinnedDialogsRequestId = 0; + _pinnedDialogsReceived = true; +} + +bool DialogsWidget::dialogsFailed(const RPCError &error, mtpRequestId requestId) { if (MTP::isDefaultHandledError(error)) return false; LOG(("RPC Error: %1 %2: %3").arg(error.code()).arg(error.type()).arg(error.description())); - if (_dialogsRequest == req) { - _dialogsRequest = 0; + if (_dialogsRequestId == requestId) { + _dialogsRequestId = 0; + } else if (_pinnedDialogsRequestId == requestId) { + _pinnedDialogsRequestId = 0; } return true; } bool DialogsWidget::onSearchMessages(bool searchCache) { - QString q = _filter.getLastText().trimmed(); + QString q = _filter->getLastText().trimmed(); if (q.isEmpty()) { - if (_searchRequest) { - MTP::cancel(_searchRequest); - _searchRequest = 0; - } - if (_peopleRequest) { - MTP::cancel(_peopleRequest); - _peopleRequest = 0; - } + MTP::cancel(base::take(_searchRequest)); + MTP::cancel(base::take(_peerSearchRequest)); return true; } if (searchCache) { @@ -2048,19 +2201,14 @@ bool DialogsWidget::onSearchMessages(bool searchCache) { if (i != _searchCache.cend()) { _searchQuery = q; _searchFull = _searchFullMigrated = false; - if (_searchRequest) { - MTP::cancel(_searchRequest); - _searchRequest = 0; - } + MTP::cancel(base::take(_searchRequest)); searchReceived(_searchInPeer ? DialogsSearchPeerFromStart : DialogsSearchFromStart, i.value(), 0); return true; } } else if (_searchQuery != q) { _searchQuery = q; _searchFull = _searchFullMigrated = false; - if (_searchRequest) { - MTP::cancel(_searchRequest); - } + MTP::cancel(base::take(_searchRequest)); if (_searchInPeer) { MTPmessages_Search::Flags flags = 0; _searchRequest = MTP::send(MTPmessages_Search(MTP_flags(flags), _searchInPeer->input, MTP_string(_searchQuery), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, DialogsSearchPeerFromStart)); @@ -2071,18 +2219,18 @@ bool DialogsWidget::onSearchMessages(bool searchCache) { } if (!_searchInPeer && q.size() >= MinUsernameLength) { if (searchCache) { - auto i = _peopleCache.constFind(q); - if (i != _peopleCache.cend()) { - _peopleQuery = q; - _peopleRequest = 0; - peopleReceived(i.value(), 0); + auto i = _peerSearchCache.constFind(q); + if (i != _peerSearchCache.cend()) { + _peerSearchQuery = q; + _peerSearchRequest = 0; + peerSearchReceived(i.value(), 0); return true; } - } else if (_peopleQuery != q) { - _peopleQuery = q; - _peopleFull = false; - _peopleRequest = MTP::send(MTPcontacts_Search(MTP_string(_peopleQuery), MTP_int(SearchPeopleLimit)), rpcDone(&DialogsWidget::peopleReceived), rpcFail(&DialogsWidget::peopleFailed)); - _peopleQueries.insert(_peopleRequest, _peopleQuery); + } else if (_peerSearchQuery != q) { + _peerSearchQuery = q; + _peerSearchFull = false; + _peerSearchRequest = MTP::send(MTPcontacts_Search(MTP_string(_peerSearchQuery), MTP_int(SearchPeopleLimit)), rpcDone(&DialogsWidget::peerSearchReceived), rpcFail(&DialogsWidget::peopleFailed)); + _peerSearchQueries.insert(_peerSearchRequest, _peerSearchQuery); } } return false; @@ -2095,33 +2243,35 @@ void DialogsWidget::onNeedSearchMessages() { } void DialogsWidget::onChooseByDrag() { - _inner.choosePeer(); + _inner->choosePeer(); +} + +void DialogsWidget::showMainMenu() { + App::wnd()->showMainMenu(); } void DialogsWidget::searchMessages(const QString &query, PeerData *inPeer) { - if ((_filter.getLastText() != query) || (inPeer && inPeer != _searchInPeer && inPeer->migrateTo() != _searchInPeer)) { + if ((_filter->getLastText() != query) || (inPeer && inPeer != _searchInPeer && inPeer->migrateTo() != _searchInPeer)) { if (inPeer) { onCancelSearch(); - _searchInPeer = inPeer->migrateTo() ? inPeer->migrateTo() : inPeer; - _searchInMigrated = _searchInPeer ? _searchInPeer->migrateFrom() : 0; - _inner.searchInPeer(_searchInPeer); + setSearchInPeer(inPeer); } - _filter.setText(query); - _filter.updatePlaceholder(); + _filter->setText(query); + _filter->updatePlaceholder(); onFilterUpdate(true); _searchTimer.stop(); onSearchMessages(); - _inner.saveRecentHashtags(query); + _inner->saveRecentHashtags(query); } } void DialogsWidget::onSearchMore() { if (!_searchRequest) { if (!_searchFull) { - int32 offsetDate = _inner.lastSearchDate(); - PeerData *offsetPeer = _inner.lastSearchPeer(); - MsgId offsetId = _inner.lastSearchId(); + int32 offsetDate = _inner->lastSearchDate(); + PeerData *offsetPeer = _inner->lastSearchPeer(); + MsgId offsetId = _inner->lastSearchId(); if (_searchInPeer) { MTPmessages_Search::Flags flags = 0; _searchRequest = MTP::send(MTPmessages_Search(MTP_flags(flags), _searchInPeer->input, MTP_string(_searchQuery), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(offsetId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart)); @@ -2132,7 +2282,7 @@ void DialogsWidget::onSearchMore() { _searchQueries.insert(_searchRequest, _searchQuery); } } else if (_searchInMigrated && !_searchFullMigrated) { - MsgId offsetMigratedId = _inner.lastSearchMigratedId(); + MsgId offsetMigratedId = _inner->lastSearchMigratedId(); MTPmessages_Search::Flags flags = 0; _searchRequest = MTP::send(MTPmessages_Search(MTP_flags(flags), _searchInMigrated->input, MTP_string(_searchQuery), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(offsetMigratedId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart), rpcFail(&DialogsWidget::searchFailed, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart)); } @@ -2140,35 +2290,48 @@ void DialogsWidget::onSearchMore() { } void DialogsWidget::loadDialogs() { - if (_dialogsRequest) return; + if (_dialogsRequestId) return; if (_dialogsFull) { - _inner.addAllSavedPeers(); + _inner->addAllSavedPeers(); cSetDialogsReceived(true); return; } - int32 loadCount = _dialogsOffsetDate ? DialogsPerPage : DialogsFirstLoad; - _dialogsRequest = MTP::send(MTPmessages_GetDialogs(MTP_int(_dialogsOffsetDate), MTP_int(_dialogsOffsetId), _dialogsOffsetPeer ? _dialogsOffsetPeer->input : MTP_inputPeerEmpty(), MTP_int(loadCount)), rpcDone(&DialogsWidget::dialogsReceived), rpcFail(&DialogsWidget::dialogsFailed)); + auto firstLoad = !_dialogsOffsetDate; + auto loadCount = firstLoad ? DialogsFirstLoad : DialogsPerPage; + auto flags = qFlags(MTPmessages_GetDialogs::Flag::f_exclude_pinned); + _dialogsRequestId = MTP::send(MTPmessages_GetDialogs(MTP_flags(flags), MTP_int(_dialogsOffsetDate), MTP_int(_dialogsOffsetId), _dialogsOffsetPeer ? _dialogsOffsetPeer->input : MTP_inputPeerEmpty(), MTP_int(loadCount)), rpcDone(&DialogsWidget::dialogsReceived), rpcFail(&DialogsWidget::dialogsFailed)); + if (!_pinnedDialogsReceived) { + loadPinnedDialogs(); + } } -void DialogsWidget::contactsReceived(const MTPcontacts_Contacts &contacts) { +void DialogsWidget::loadPinnedDialogs() { + if (_pinnedDialogsRequestId) return; + + _pinnedDialogsReceived = false; + _pinnedDialogsRequestId = MTP::send(MTPmessages_GetPinnedDialogs(), rpcDone(&DialogsWidget::pinnedDialogsReceived), rpcFail(&DialogsWidget::dialogsFailed)); +} + +void DialogsWidget::contactsReceived(const MTPcontacts_Contacts &result) { + _contactsRequestId = 0; cSetContactsReceived(true); - if (contacts.type() == mtpc_contacts_contacts) { - const auto &d(contacts.c_contacts_contacts()); + if (result.type() == mtpc_contacts_contacts) { + auto &d = result.c_contacts_contacts(); App::feedUsers(d.vusers); - _inner.contactsReceived(d.vcontacts.c_vector().v); + _inner->contactsReceived(d.vcontacts.c_vector().v); } if (App::main()) App::main()->contactsReceived(); } bool DialogsWidget::contactsFailed(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; - + _contactsRequestId = 0; return true; } void DialogsWidget::searchReceived(DialogsSearchRequestType type, const MTPmessages_Messages &result, mtpRequestId req) { - if (_inner.state() == DialogsInner::FilteredState || _inner.state() == DialogsInner::SearchedState) { + if (_inner->state() == DialogsInner::FilteredState || _inner->state() == DialogsInner::SearchedState) { if (type == DialogsSearchFromStart || type == DialogsSearchPeerFromStart) { SearchQueries::iterator i = _searchQueries.find(req); if (i != _searchQueries.cend()) { @@ -2185,7 +2348,7 @@ void DialogsWidget::searchReceived(DialogsSearchRequestType type, const MTPmessa App::feedUsers(d.vusers); App::feedChats(d.vchats); auto &msgs(d.vmessages.c_vector().v); - if (!_inner.searchReceived(msgs, type, msgs.size())) { + if (!_inner->searchReceived(msgs, type, msgs.size())) { if (type == DialogsSearchMigratedFromStart || type == DialogsSearchMigratedFromOffset) { _searchFullMigrated = true; } else { @@ -2199,7 +2362,7 @@ void DialogsWidget::searchReceived(DialogsSearchRequestType type, const MTPmessa App::feedUsers(d.vusers); App::feedChats(d.vchats); auto &msgs(d.vmessages.c_vector().v); - if (!_inner.searchReceived(msgs, type, d.vcount.v)) { + if (!_inner->searchReceived(msgs, type, d.vcount.v)) { if (type == DialogsSearchMigratedFromStart || type == DialogsSearchMigratedFromOffset) { _searchFullMigrated = true; } else { @@ -2218,7 +2381,7 @@ void DialogsWidget::searchReceived(DialogsSearchRequestType type, const MTPmessa App::feedUsers(d.vusers); App::feedChats(d.vchats); auto &msgs(d.vmessages.c_vector().v); - if (!_inner.searchReceived(msgs, type, d.vcount.v)) { + if (!_inner->searchReceived(msgs, type, d.vcount.v)) { if (type == DialogsSearchMigratedFromStart || type == DialogsSearchMigratedFromOffset) { _searchFullMigrated = true; } else { @@ -2234,27 +2397,27 @@ void DialogsWidget::searchReceived(DialogsSearchRequestType type, const MTPmessa } } -void DialogsWidget::peopleReceived(const MTPcontacts_Found &result, mtpRequestId req) { - auto q = _peopleQuery; - if (_inner.state() == DialogsInner::FilteredState || _inner.state() == DialogsInner::SearchedState) { - auto i = _peopleQueries.find(req); - if (i != _peopleQueries.cend()) { +void DialogsWidget::peerSearchReceived(const MTPcontacts_Found &result, mtpRequestId req) { + auto q = _peerSearchQuery; + if (_inner->state() == DialogsInner::FilteredState || _inner->state() == DialogsInner::SearchedState) { + auto i = _peerSearchQueries.find(req); + if (i != _peerSearchQueries.cend()) { q = i.value(); - _peopleCache[q] = result; - _peopleQueries.erase(i); + _peerSearchCache[q] = result; + _peerSearchQueries.erase(i); } } - if (_peopleRequest == req) { + if (_peerSearchRequest == req) { switch (result.type()) { case mtpc_contacts_found: { auto &d = result.c_contacts_found(); App::feedUsers(d.vusers); App::feedChats(d.vchats); - _inner.peopleReceived(q, d.vresults.c_vector().v); + _inner->peerSearchReceived(q, d.vresults.c_vector().v); } break; } - _peopleRequest = 0; + _peerSearchRequest = 0; onListScroll(); } } @@ -2276,9 +2439,9 @@ bool DialogsWidget::searchFailed(DialogsSearchRequestType type, const RPCError & bool DialogsWidget::peopleFailed(const RPCError &error, mtpRequestId req) { if (MTP::isDefaultHandledError(error)) return false; - if (_peopleRequest == req) { - _peopleRequest = 0; - _peopleFull = true; + if (_peerSearchRequest == req) { + _peerSearchRequest = 0; + _peerSearchFull = true; } return true; } @@ -2294,7 +2457,7 @@ void DialogsWidget::dragEnterEvent(QDragEnterEvent *e) { if (_dragForward) { e->setDropAction(Qt::CopyAction); e->accept(); - updateDragInScroll(_scroll.geometry().contains(e->pos())); + updateDragInScroll(_scroll->geometry().contains(e->pos())); } else if (App::main() && App::main()->getDragState(e->mimeData()) != DragStateNone) { e->setDropAction(Qt::CopyAction); e->accept(); @@ -2303,13 +2466,13 @@ void DialogsWidget::dragEnterEvent(QDragEnterEvent *e) { } void DialogsWidget::dragMoveEvent(QDragMoveEvent *e) { - if (_scroll.geometry().contains(e->pos())) { + if (_scroll->geometry().contains(e->pos())) { if (_dragForward) { updateDragInScroll(true); } else { _chooseByDragTimer.start(ChoosePeerByDragTimeout); } - PeerData *p = _inner.updateFromParentDrag(mapToGlobal(e->pos())); + PeerData *p = _inner->updateFromParentDrag(mapToGlobal(e->pos())); if (p) { e->setDropAction(Qt::CopyAction); } else { @@ -2317,7 +2480,7 @@ void DialogsWidget::dragMoveEvent(QDragMoveEvent *e) { } } else { if (_dragForward) updateDragInScroll(false); - _inner.leaveEvent(0); + _inner->dragLeft(); e->setDropAction(Qt::IgnoreAction); } e->accept(); @@ -2329,7 +2492,7 @@ void DialogsWidget::dragLeaveEvent(QDragLeaveEvent *e) { } else { _chooseByDragTimer.stop(); } - _inner.leaveEvent(0); + _inner->dragLeft(); e->accept(); } @@ -2346,8 +2509,8 @@ void DialogsWidget::updateDragInScroll(bool inScroll) { void DialogsWidget::dropEvent(QDropEvent *e) { _chooseByDragTimer.stop(); - if (_scroll.geometry().contains(e->pos())) { - PeerData *p = _inner.updateFromParentDrag(mapToGlobal(e->pos())); + if (_scroll->geometry().contains(e->pos())) { + PeerData *p = _inner->updateFromParentDrag(mapToGlobal(e->pos())); if (p) { e->acceptProposedAction(); App::main()->onFilesOrForwardDrop(p->id, e->mimeData()); @@ -2356,51 +2519,49 @@ void DialogsWidget::dropEvent(QDropEvent *e) { } void DialogsWidget::onListScroll() { -// if (!App::self()) return; - - _inner.loadPeerPhotos(_scroll.scrollTop()); - if (_inner.state() == DialogsInner::SearchedState || (_inner.state() == DialogsInner::FilteredState && _searchInMigrated && _searchFull && !_searchFullMigrated)) { - if (_scroll.scrollTop() > (_inner.searchList().size() + _inner.filteredList().size() + _inner.peopleList().size()) * st::dialogsRowHeight - PreloadHeightsCount * _scroll.height()) { - onSearchMore(); - } - } else if (_scroll.scrollTop() > _inner.dialogsList()->size() * st::dialogsRowHeight - PreloadHeightsCount * _scroll.height()) { - loadDialogs(); - } + auto scrollTop = _scroll->scrollTop(); + _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); } void DialogsWidget::onFilterUpdate(bool force) { if (_a_show.animating() && !force) return; - QString filterText = _filter.getLastText(); - _inner.onFilterUpdate(filterText, force); + QString filterText = _filter->getLastText(); + _inner->onFilterUpdate(filterText, force); if (filterText.isEmpty()) { _searchCache.clear(); _searchQueries.clear(); _searchQuery = QString(); - _cancelSearch.hide(); - _newGroup->show(); - } else if (_cancelSearch.isHidden()) { - _cancelSearch.show(); - _newGroup->hide(); + _cancelSearch->hideAnimated(); + } else { + _cancelSearch->showAnimated(); } if (filterText.size() < MinUsernameLength) { - _peopleCache.clear(); - _peopleQueries.clear(); - _peopleQuery = QString(); + _peerSearchCache.clear(); + _peerSearchQueries.clear(); + _peerSearchQuery = QString(); } } void DialogsWidget::searchInPeer(PeerData *peer) { onCancelSearch(); - _searchInPeer = peer ? (peer->migrateTo() ? peer->migrateTo() : peer) : 0; - _searchInMigrated = _searchInPeer ? _searchInPeer->migrateFrom() : 0; - _inner.searchInPeer(_searchInPeer); + setSearchInPeer(peer); onFilterUpdate(true); } +void DialogsWidget::setSearchInPeer(PeerData *peer) { + auto newSearchInPeer = peer ? (peer->migrateTo() ? peer->migrateTo() : peer) : nullptr; + _searchInMigrated = newSearchInPeer ? newSearchInPeer->migrateFrom() : nullptr; + if (newSearchInPeer != _searchInPeer) { + _searchInPeer = newSearchInPeer; + App::main()->searchInPeerChanged().notify(_searchInPeer, true); + } + _inner->searchInPeer(_searchInPeer); +} + void DialogsWidget::onFilterCursorMoved(int from, int to) { - if (to < 0) to = _filter.cursorPosition(); - QString t = _filter.getLastText(); + if (to < 0) to = _filter->cursorPosition(); + QString t = _filter->getLastText(); QStringRef r; for (int start = to; start > 0;) { --start; @@ -2411,12 +2572,12 @@ void DialogsWidget::onFilterCursorMoved(int from, int to) { } if (!t.at(start).isLetterOrNumber() && t.at(start) != '_') break; } - _inner.onHashtagFilterUpdate(r); + _inner->onHashtagFilterUpdate(r); } void DialogsWidget::onCompleteHashtag(QString tag) { - QString t = _filter.getLastText(), r; - int cur = _filter.cursorPosition(); + QString t = _filter->getLastText(), r; + int cur = _filter->cursorPosition(); for (int start = cur; start > 0;) { --start; if (t.size() <= start) break; @@ -2427,8 +2588,8 @@ void DialogsWidget::onCompleteHashtag(QString tag) { } if (cur - start - 1 == tag.size() && cur < t.size() && t.at(cur) == ' ') ++cur; r = t.mid(0, start + 1) + tag + ' ' + t.mid(cur); - _filter.setText(r); - _filter.setCursorPosition(start + 1 + tag.size() + 1); + _filter->setText(r); + _filter->setCursorPosition(start + 1 + tag.size() + 1); onFilterUpdate(true); return; } @@ -2436,53 +2597,98 @@ void DialogsWidget::onCompleteHashtag(QString tag) { } if (!t.at(start).isLetterOrNumber() && t.at(start) != '_') break; } - _filter.setText(t.mid(0, cur) + '#' + tag + ' ' + t.mid(cur)); - _filter.setCursorPosition(cur + 1 + tag.size() + 1); + _filter->setText(t.mid(0, cur) + '#' + tag + ' ' + t.mid(cur)); + _filter->setCursorPosition(cur + 1 + tag.size() + 1); onFilterUpdate(true); } void DialogsWidget::resizeEvent(QResizeEvent *e) { - int32 w = width(); - _filter.setGeometry(st::dialogsPadding.x(), st::dialogsFilterPadding, w - 2 * st::dialogsPadding.x(), _filter.height()); - _newGroup->move(w - _newGroup->width() - st::dialogsPadding.x(), _filter.y()); - _addContact.move(w - _addContact.width() - st::dialogsPadding.x(), _filter.y()); - _cancelSearch.move(w - _cancelSearch.width() - st::dialogsPadding.x(), _filter.y()); - _scroll.move(0, _filter.height() + 2 * st::dialogsFilterPadding); + updateControlsGeometry(); +} - int32 addToY = App::main() ? App::main()->contentScrollAddToY() : 0; - int32 newScrollY = _scroll.scrollTop() + addToY; - _scroll.resize(w, height() - _filter.y() - _filter.height() - st::dialogsFilterPadding - st::dialogsPadding.y()); - if (addToY) { - _scroll.scrollToY(newScrollY); +void DialogsWidget::updateLockUnlockVisibility() { + if (!_a_show.animating()) { + _lockUnlock->setVisible(Global::LocalPasscode()); + } + updateControlsGeometry(); +} + +void DialogsWidget::updateControlsGeometry() { + auto filterAreaTop = 0; + if (_forwardCancel) { + _forwardCancel->moveToLeft(0, filterAreaTop); + filterAreaTop += st::dialogsForwardHeight; + } + auto filterLeft = st::dialogsFilterPadding.x() + _mainMenuToggle->width() + st::dialogsFilterPadding.x(); + auto filterRight = (Global::LocalPasscode() ? (st::dialogsFilterPadding.x() + _lockUnlock->width()) : st::dialogsFilterSkip) + st::dialogsFilterPadding.x(); + auto filterWidth = width() - filterLeft - filterRight; + auto filterAreaHeight = st::dialogsFilterPadding.y() + _mainMenuToggle->height() + st::dialogsFilterPadding.y(); + auto filterTop = filterAreaTop + (filterAreaHeight - _filter->height()) / 2; + _filter->setGeometryToLeft(filterLeft, filterTop, filterWidth, _filter->height()); + _mainMenuToggle->moveToLeft(st::dialogsFilterPadding.x(), filterAreaTop + st::dialogsFilterPadding.y()); + _lockUnlock->moveToRight(st::dialogsFilterPadding.x(), filterAreaTop + st::dialogsFilterPadding.y()); + _cancelSearch->moveToLeft(filterLeft + filterWidth - _cancelSearch->width(), _filter->y()); + + auto scrollTop = filterAreaTop + filterAreaHeight; + auto addToScroll = App::main() ? App::main()->contentScrollAddToY() : 0; + auto newScrollTop = _scroll->scrollTop() + addToScroll; + auto scrollHeight = height() - scrollTop; + if (_updateTelegram) { + auto updateHeight = _updateTelegram->height(); + _updateTelegram->setGeometry(0, height() - updateHeight, width(), updateHeight); + scrollHeight -= updateHeight; + } else { + scrollHeight -= st::dialogsPadding.y(); + } + _scroll->setGeometry(0, scrollTop, width(), scrollHeight); + if (addToScroll) { + _scroll->scrollToY(newScrollTop); } else { onListScroll(); } } +void DialogsWidget::updateForwardBar() { + auto selecting = App::main()->selectingPeer(); + auto oneColumnSelecting = (Adaptive::OneColumn() && selecting); + if (!oneColumnSelecting == !_forwardCancel) { + return; + } + if (oneColumnSelecting) { + _forwardCancel.create(this, st::dialogsForwardCancel); + _forwardCancel->setClickedCallback([] { Global::RefPeerChooseCancel().notify(true); }); + if (!_a_show.animating()) _forwardCancel->show(); + } else { + _forwardCancel.destroyDelayed(); + } + updateControlsGeometry(); + update(); +} + void DialogsWidget::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape) { e->ignore(); } else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { - if (!_inner.choosePeer()) { - if (_inner.state() == DialogsInner::DefaultState || _inner.state() == DialogsInner::SearchedState || (_inner.state() == DialogsInner::FilteredState && _inner.hasFilteredResults())) { - _inner.selectSkip(1); - _inner.choosePeer(); + if (!_inner->choosePeer()) { + if (_inner->state() == DialogsInner::DefaultState || _inner->state() == DialogsInner::SearchedState || (_inner->state() == DialogsInner::FilteredState && _inner->hasFilteredResults())) { + _inner->selectSkip(1); + _inner->choosePeer(); } else { onSearchMessages(); } } } else if (e->key() == Qt::Key_Down) { - _inner.setMouseSel(false); - _inner.selectSkip(1); + _inner->setMouseSelection(false); + _inner->selectSkip(1); } else if (e->key() == Qt::Key_Up) { - _inner.setMouseSel(false); - _inner.selectSkip(-1); + _inner->setMouseSelection(false); + _inner->selectSkip(-1); } else if (e->key() == Qt::Key_PageDown) { - _inner.setMouseSel(false); - _inner.selectSkipPage(_scroll.height(), 1); + _inner->setMouseSelection(false); + _inner->selectSkipPage(_scroll->height(), 1); } else if (e->key() == Qt::Key_PageUp) { - _inner.setMouseSel(false); - _inner.selectSkipPage(_scroll.height(), -1); + _inner->setMouseSelection(false); + _inner->selectSkipPage(_scroll->height(), -1); } else { e->ignore(); } @@ -2496,68 +2702,73 @@ void DialogsWidget::paintEvent(QPaintEvent *e) { if (r != rect()) { p.setClipRect(r); } + auto progress = _a_show.current(getms(), 1.); if (_a_show.animating()) { - int retina = cIntRetinaFactor(); - 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); + auto retina = cIntRetinaFactor(); + auto fromLeft = (_showDirection == Window::SlideDirection::FromLeft); + auto coordUnder = fromLeft ? anim::interpolate(-st::slideShift, 0, progress) : anim::interpolate(0, -st::slideShift, progress); + auto coordOver = fromLeft ? anim::interpolate(0, width(), progress) : anim::interpolate(width(), 0, progress); + auto shadow = fromLeft ? (1. - progress) : progress; + if (coordOver > 0) { + p.drawPixmap(QRect(0, 0, coordOver, _cacheUnder.height() / retina), _cacheUnder, QRect(-coordUnder * retina, 0, coordOver * retina, _cacheUnder.height())); + p.setOpacity(shadow); + p.fillRect(0, 0, coordOver, _cacheUnder.height() / retina, st::slideFadeOutBg); p.setOpacity(1); } - 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()); - st::slideShadow.fill(p, QRect(a_coordOver.current() - st::slideShadow.width(), 0, st::slideShadow.width(), _cacheOver.height() / retina)); + p.drawPixmap(QRect(coordOver, 0, _cacheOver.width() / retina, _cacheOver.height() / retina), _cacheOver, QRect(0, 0, _cacheOver.width(), _cacheOver.height())); + p.setOpacity(shadow); + st::slideShadow.fill(p, QRect(coordOver - st::slideShadow.width(), 0, st::slideShadow.width(), _cacheOver.height() / retina)); return; } - QRect above(0, 0, width(), _scroll.y()); - if (above.intersects(r)) { - p.fillRect(above.intersected(r), st::white->b); + auto aboveTop = 0; + if (_forwardCancel) { + p.fillRect(0, aboveTop, width(), st::dialogsForwardHeight, st::dialogsForwardBg); + p.setPen(st::dialogsForwardFg); + p.setFont(st::dialogsForwardFont); + p.drawTextLeft(st::dialogsForwardTextLeft, st::dialogsForwardTextTop, width(), lang(lng_forward_choose)); + aboveTop += st::dialogsForwardHeight; } - QRect below(0, _scroll.y() + qMin(_scroll.height(), _inner.height()), width(), height()); + auto above = QRect(0, aboveTop, width(), _scroll->y()); + if (above.intersects(r)) { + p.fillRect(above.intersected(r), st::dialogsBg); + } + auto below = QRect(0, _scroll->y() + qMin(_scroll->height(), _inner->height()), width(), height()); if (below.intersects(r)) { - p.fillRect(below.intersected(r), st::white->b); + p.fillRect(below.intersected(r), st::dialogsBg); } } void DialogsWidget::destroyData() { - _inner.destroyData(); + _inner->destroyData(); } void DialogsWidget::peerBefore(const PeerData *inPeer, MsgId inMsg, PeerData *&outPeer, MsgId &outMsg) const { - return _inner.peerBefore(inPeer, inMsg, outPeer, outMsg); + return _inner->peerBefore(inPeer, inMsg, outPeer, outMsg); } void DialogsWidget::peerAfter(const PeerData *inPeer, MsgId inMsg, PeerData *&outPeer, MsgId &outMsg) const { - return _inner.peerAfter(inPeer, inMsg, outPeer, outMsg); + return _inner->peerAfter(inPeer, inMsg, outPeer, outMsg); } void DialogsWidget::scrollToPeer(const PeerId &peer, MsgId msgId) { - _inner.scrollToPeer(peer, msgId); + _inner->scrollToPeer(peer, msgId); } void DialogsWidget::removeDialog(History *history) { - _inner.removeDialog(history); + _inner->removeDialog(history); onFilterUpdate(); } Dialogs::IndexedList *DialogsWidget::contactsList() { - return _inner.contactsList(); + return _inner->contactsList(); } Dialogs::IndexedList *DialogsWidget::dialogsList() { - return _inner.dialogsList(); -} - -void DialogsWidget::onAddContact() { - Ui::showLayer(new AddContactBox(), KeepOtherLayers); -} - -void DialogsWidget::onNewGroup() { - Ui::showLayer(new NewGroupBox()); + return _inner->dialogsList(); } bool DialogsWidget::onCancelSearch() { - bool clearing = !_filter.getLastText().isEmpty(); + bool clearing = !_filter->getLastText().isEmpty(); if (_searchRequest) { MTP::cancel(_searchRequest); _searchRequest = 0; @@ -2566,13 +2777,12 @@ bool DialogsWidget::onCancelSearch() { if (Adaptive::OneColumn()) { Ui::showPeerHistory(_searchInPeer, ShowAtUnreadMsgId); } - _searchInPeer = _searchInMigrated = 0; - _inner.searchInPeer(0); + setSearchInPeer(nullptr); clearing = true; } - _inner.clearFilter(); - _filter.clear(); - _filter.updatePlaceholder(); + _inner->clearFilter(); + _filter->clear(); + _filter->updatePlaceholder(); onFilterUpdate(); return clearing; } @@ -2586,12 +2796,11 @@ void DialogsWidget::onCancelSearchInPeer() { if (Adaptive::OneColumn() && !App::main()->selectingPeer()) { Ui::showPeerHistory(_searchInPeer, ShowAtUnreadMsgId); } - _searchInPeer = _searchInMigrated = 0; - _inner.searchInPeer(0); + setSearchInPeer(nullptr); } - _inner.clearFilter(); - _filter.clear(); - _filter.updatePlaceholder(); + _inner->clearFilter(); + _filter->clear(); + _filter->updatePlaceholder(); onFilterUpdate(); if (!Adaptive::OneColumn() && !App::main()->selectingPeer()) { emit cancelled(); @@ -2599,8 +2808,8 @@ void DialogsWidget::onCancelSearchInPeer() { } void DialogsWidget::onDialogMoved(int movedFrom, int movedTo) { - int32 st = _scroll.scrollTop(); + int32 st = _scroll->scrollTop(); if (st > movedTo && st < movedFrom) { - _scroll.scrollToY(st + st::dialogsRowHeight); + _scroll->scrollToY(st + st::dialogsRowHeight); } } diff --git a/Telegram/SourceFiles/dialogswidget.h b/Telegram/SourceFiles/dialogswidget.h index 7a6631c4f..c79d045b5 100644 --- a/Telegram/SourceFiles/dialogswidget.h +++ b/Telegram/SourceFiles/dialogswidget.h @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once #include "window/section_widget.h" +#include "ui/widgets/scroll_area.h" namespace Dialogs { class Row; @@ -29,11 +30,14 @@ class IndexedList; } // namespace Dialogs namespace Ui { -class RoundButton; -} // namespace Ui - -class MainWidget; +class IconButton; class PopupMenu; +class DropdownMenu; +class FlatButton; +class LinkButton; +class FlatInput; +class CrossButton; +} // namespace Ui enum DialogsSearchRequestType { DialogsSearchFromStart, @@ -44,39 +48,33 @@ enum DialogsSearchRequestType { DialogsSearchMigratedFromOffset, }; -class DialogsInner : public SplittedWidget, public RPCSender, private base::Subscriber { +class DialogsInner : public Ui::SplittedWidget, public RPCSender, private base::Subscriber { Q_OBJECT public: - DialogsInner(QWidget *parent, MainWidget *main); + DialogsInner(QWidget *parent, QWidget *main); void dialogsReceived(const QVector &dialogs); void addSavedPeersAfter(const QDateTime &date); void addAllSavedPeers(); - bool searchReceived(const QVector &messages, DialogsSearchRequestType type, int32 fullCount); - void peopleReceived(const QString &query, const QVector &people); + bool searchReceived(const QVector &result, DialogsSearchRequestType type, int32 fullCount); + void peerSearchReceived(const QString &query, const QVector &result); void showMore(int32 pixels); void activate(); - void contactsReceived(const QVector &contacts); - - void mouseMoveEvent(QMouseEvent *e); - void mousePressEvent(QMouseEvent *e); - void resizeEvent(QResizeEvent *e); - void enterEvent(QEvent *e); - void leaveEvent(QEvent *e); - void contextMenuEvent(QContextMenuEvent *e); + void contactsReceived(const QVector &result); void selectSkip(int32 direction); void selectSkipPage(int32 pixels, int32 direction); void createDialog(History *history); void dlgUpdated(Dialogs::Mode list, Dialogs::Row *row); - void dlgUpdated(History *row, MsgId msgId); + void dlgUpdated(PeerData *peer, MsgId msgId); void removeDialog(History *history); - void loadPeerPhotos(int32 yFrom); + void dragLeft(); + void clearFilter(); void refresh(bool toTop = false); @@ -89,21 +87,14 @@ public: void peerAfter(const PeerData *inPeer, MsgId inMsg, PeerData *&outPeer, MsgId &outMsg) const; void scrollToPeer(const PeerId &peer, MsgId msgId); - typedef QVector FilteredDialogs; - typedef QVector PeopleResults; - typedef QVector SearchResults; - Dialogs::IndexedList *contactsList(); Dialogs::IndexedList *dialogsList(); - FilteredDialogs &filteredList(); - PeopleResults &peopleList(); - SearchResults &searchList(); int32 lastSearchDate() const; PeerData *lastSearchPeer() const; MsgId lastSearchId() const; MsgId lastSearchMigratedId() const; - void setMouseSel(bool msel, bool toTop = false); + void setMouseSelection(bool mouseSelection, bool toTop = false); enum State { DefaultState = 0, @@ -121,7 +112,10 @@ public: PeerData *updateFromParentDrag(QPoint globalPos); - void updateNotifySettings(PeerData *peer); + void setLoadMoreCallback(base::lambda &&callback) { + _loadMoreCallback = std_::move(callback); + } + void setVisibleTopBottom(int visibleTop, int visibleBottom) override; void notify_userIsContactChanged(UserData *user, bool fromThisApp); void notify_historyMuteUpdated(History *history); @@ -129,25 +123,13 @@ public: ~DialogsInner(); public slots: - void onUpdateSelected(bool force = false); void onParentGeometryChanged(); void onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars); void onPeerPhotoChanged(PeerData *peer); void onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow); - void onContextProfile(); - void onContextToggleNotifications(); - void onContextSearch(); - void onContextClearHistory(); - void onContextClearHistorySure(); - void onContextDeleteAndLeave(); - void onContextDeleteAndLeaveSure(); - void onContextToggleBlock(); - void onMenuDestroyed(QObject*); - void peerUpdated(PeerData *peer); - signals: void mustScrollTo(int scrollToTop, int scrollToBottom); void dialogMoved(int movedFrom, int movedTo); @@ -158,56 +140,112 @@ signals: void refreshHashtags(); protected: - void paintRegion(Painter &p, const QRegion ®ion, bool paintingOther); + void paintRegion(Painter &p, const QRegion ®ion, bool paintingOther) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + void enterEvent(QEvent *e) override; + void leaveEvent(QEvent *e) override; + void contextMenuEvent(QContextMenuEvent *e) override; private: + struct ImportantSwitch; + using DialogsList = std_::unique_ptr; + using FilteredDialogs = QVector; + using SearchResults = std_::vector_of_moveable>; + struct HashtagResult; + using HashtagResults = std_::vector_of_moveable>; + struct PeerSearchResult; + using PeerSearchResults = std_::vector_of_moveable>; + + void mousePressReleased(Qt::MouseButton button); + void clearIrrelevantState(); + void updateSelected() { + updateSelected(mapFromGlobal(QCursor::pos())); + } + void updateSelected(QPoint localPos); + void loadPeerPhotos(int visibleTop); + void setImportantSwitchPressed(bool pressed); + void setPressed(Dialogs::Row *pressed); + void setHashtagPressed(int pressed); + void setFilteredPressed(int pressed); + void setPeerSearchPressed(int pressed); + void setSearchedPressed(int pressed); + bool isPressed() const { + return _importantSwitchPressed || _pressed || (_hashtagPressed >= 0) || (_filteredPressed >= 0) || (_peerSearchPressed >= 0) || (_searchedPressed >= 0); + } + bool isSelected() const { + return _importantSwitchSelected || _selected || (_hashtagSelected >= 0) || (_filteredSelected>= 0) || (_peerSearchSelected >= 0) || (_searchedSelected >= 0); + } + void itemRemoved(HistoryItem *item); + enum class UpdateRowSection { + Default = 0x01, + Filtered = 0x02, + PeerSearch = 0x04, + MessageSearch = 0x08, + All = 0x0F, + }; + Q_DECLARE_FLAGS(UpdateRowSections, UpdateRowSection); + Q_DECLARE_FRIEND_OPERATORS_FOR_FLAGS(UpdateRowSections); + void updateDialogRow(PeerData *peer, MsgId msgId, QRect updateRect, UpdateRowSections sections = UpdateRowSection::All); int dialogsOffset() const; int filteredOffset() const; - int peopleOffset() const; + int peerSearchOffset() const; int searchedOffset() const; - void peopleResultPaint(PeerData *peer, Painter &p, int32 w, bool active, bool selected, bool onlyBackground) const; - void searchInPeerPaint(Painter &p, int32 w, bool onlyBackground) const; + void paintDialog(QPainter &p, Dialogs::Row *dialog); + void paintPeerSearchResult(Painter &p, const PeerSearchResult *result, int32 w, bool active, bool selected, bool onlyBackground, TimeMs ms) const; + void paintSearchInPeer(Painter &p, int32 w, bool onlyBackground) const; void clearSelection(); - void clearSearchResults(bool clearPeople = true); + void clearSearchResults(bool clearPeerSearchResults = true); void updateSelectedRow(PeerData *peer = 0); - bool menuPeerMuted(); - void contextBlockDone(QPair data, const MTPBool &result); Dialogs::IndexedList *shownDialogs() const { - return (Global::DialogsMode() == Dialogs::Mode::Important) ? importantDialogs.get() : dialogs.get(); + return (Global::DialogsMode() == Dialogs::Mode::Important) ? _dialogsImportant.get() : _dialogs.get(); } - using DialogsList = std_::unique_ptr; - DialogsList dialogs; - DialogsList importantDialogs; + DialogsList _dialogs; + DialogsList _dialogsImportant; - DialogsList contactsNoDialogs; - DialogsList contacts; + DialogsList _contactsNoDialogs; + DialogsList _contacts; - bool _importantSwitchSel = false; - Dialogs::Row *_sel = nullptr; - bool _selByMouse = false; + bool _mouseSelection = false; + Qt::MouseButton _pressButton = Qt::LeftButton; + std_::unique_ptr _importantSwitch; + bool _importantSwitchSelected = false; + bool _importantSwitchPressed = false; + Dialogs::Row *_selected = nullptr; + Dialogs::Row *_pressed = nullptr; + + int _visibleAreaHeight = 0; QString _filter, _hashtagFilter; - QStringList _hashtagResults; - int _hashtagSel = -1; + HashtagResults _hashtagResults; + int _hashtagSelected = -1; + int _hashtagPressed = -1; + bool _hashtagDeleteSelected = false; + bool _hashtagDeletePressed = false; FilteredDialogs _filterResults; - int _filteredSel = -1; + int _filteredSelected = -1; + int _filteredPressed = -1; + + QString _peerSearchQuery; + PeerSearchResults _peerSearchResults; + int _peerSearchSelected = -1; + int _peerSearchPressed = -1; SearchResults _searchResults; int _searchedCount = 0; int _searchedMigratedCount = 0; - int _searchedSel = -1; - - QString _peopleQuery; - PeopleResults _peopleResults; - int _peopleSel = -1; + int _searchedSelected = -1; + int _searchedPressed = -1; int _lastSearchDate = 0; PeerData *_lastSearchPeer = nullptr; @@ -216,51 +254,36 @@ private: State _state = DefaultState; - QPoint lastMousePos; - - void paintDialog(QPainter &p, Dialogs::Row *dialog); - - LinkButton _addContactLnk; - IconedButton _cancelSearchInPeer; - - bool _overDelete = false; + object_ptr _addContactLnk; + object_ptr _cancelSearchInPeer; PeerData *_searchInPeer = nullptr; PeerData *_searchInMigrated = nullptr; PeerData *_menuPeer = nullptr; - PeerData *_menuActionPeer = nullptr; - PopupMenu *_menu = nullptr; + Ui::PopupMenu *_menu = nullptr; + + base::lambda _loadMoreCallback; }; -class DialogsWidget : public TWidget, public RPCSender { +Q_DECLARE_OPERATORS_FOR_FLAGS(DialogsInner::UpdateRowSections); + +class DialogsWidget : public TWidget, public RPCSender, private base::Subscriber { Q_OBJECT public: - DialogsWidget(MainWidget *parent); + DialogsWidget(QWidget *parent); - void dialogsReceived(const MTPmessages_Dialogs &dialogs, mtpRequestId req); - void contactsReceived(const MTPcontacts_Contacts &contacts); - void searchReceived(DialogsSearchRequestType type, const MTPmessages_Messages &result, mtpRequestId req); - void peopleReceived(const MTPcontacts_Found &result, mtpRequestId req); - - void dragEnterEvent(QDragEnterEvent *e) override; - void dragMoveEvent(QDragMoveEvent *e) override; - void dragLeaveEvent(QDragLeaveEvent *e) override; - void dropEvent(QDropEvent *e) override; void updateDragInScroll(bool inScroll); - void resizeEvent(QResizeEvent *e) override; - void keyPressEvent(QKeyEvent *e) override; - void paintEvent(QPaintEvent *e) override; - void searchInPeer(PeerData *peer); void loadDialogs(); + void loadPinnedDialogs(); void createDialog(History *history); void dlgUpdated(Dialogs::Mode list, Dialogs::Row *row); - void dlgUpdated(History *row, MsgId msgId); + void dlgUpdated(PeerData *peer, MsgId msgId); void dialogsToUp(); @@ -268,7 +291,7 @@ public: return true; } void showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams ¶ms); - void step_show(float64 ms, bool timer); + void showFast(); void destroyData(); @@ -284,10 +307,8 @@ public: void searchMessages(const QString &query, PeerData *inPeer = 0); void onSearchMore(); - void updateNotifySettings(PeerData *peer); - void rpcClear() override { - _inner.rpcClear(); + _inner->rpcClear(); RPCSender::rpcClear(); } @@ -295,17 +316,13 @@ public: void notify_historyMuteUpdated(History *history); signals: - void cancelled(); public slots: - void onCancel(); void onListScroll(); void activate(); void onFilterUpdate(bool force = false); - void onAddContact(); - void onNewGroup(); bool onCancelSearch(); void onCancelSearchInPeer(); @@ -318,9 +335,36 @@ public slots: void onChooseByDrag(); -private: +#ifndef TDESKTOP_DISABLE_AUTOUPDATE +private slots: + void onCheckUpdateStatus(); +#endif // TDESKTOP_DISABLE_AUTOUPDATE - bool _dragInScroll, _dragForward; +protected: + void dragEnterEvent(QDragEnterEvent *e) override; + void dragMoveEvent(QDragMoveEvent *e) override; + void dragLeaveEvent(QDragLeaveEvent *e) override; + void dropEvent(QDropEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + void keyPressEvent(QKeyEvent *e) override; + void paintEvent(QPaintEvent *e) override; + +private: + void animationCallback(); + void dialogsReceived(const MTPmessages_Dialogs &dialogs, mtpRequestId requestId); + void pinnedDialogsReceived(const MTPmessages_PeerDialogs &dialogs, mtpRequestId requestId); + void contactsReceived(const MTPcontacts_Contacts &result); + void searchReceived(DialogsSearchRequestType type, const MTPmessages_Messages &result, mtpRequestId requestId); + void peerSearchReceived(const MTPcontacts_Found &result, mtpRequestId requestId); + + void setSearchInPeer(PeerData *peer); + void showMainMenu(); + void updateLockUnlockVisibility(); + void updateControlsGeometry(); + void updateForwardBar(); + + bool _dragInScroll = false; + bool _dragForward = false; QTimer _chooseByDragTimer; void unreadCountsReceived(const QVector &dialogs); @@ -329,40 +373,52 @@ private: bool searchFailed(DialogsSearchRequestType type, const RPCError &error, mtpRequestId req); bool peopleFailed(const RPCError &error, mtpRequestId req); - bool _dialogsFull; - int32 _dialogsOffsetDate; - MsgId _dialogsOffsetId; - PeerData *_dialogsOffsetPeer; - mtpRequestId _dialogsRequest, _contactsRequest; + bool _dialogsFull = false; + int32 _dialogsOffsetDate = 0; + MsgId _dialogsOffsetId = 0; + PeerData *_dialogsOffsetPeer = nullptr; + mtpRequestId _dialogsRequestId = 0; + mtpRequestId _pinnedDialogsRequestId = 0; + mtpRequestId _contactsRequestId = 0; + bool _pinnedDialogsReceived = false; - FlatInput _filter; - ChildWidget _newGroup; - IconedButton _addContact, _cancelSearch; - ScrollArea _scroll; - DialogsInner _inner; + object_ptr _forwardCancel = { nullptr }; + object_ptr _mainMenuToggle; + object_ptr _filter; + object_ptr _cancelSearch; + object_ptr _lockUnlock; + object_ptr _scroll; + QPointer _inner; + object_ptr _updateTelegram = { nullptr }; Animation _a_show; + Window::SlideDirection _showDirection; QPixmap _cacheUnder, _cacheOver; - anim::ivalue a_coordUnder, a_coordOver; - anim::fvalue a_progress; - PeerData *_searchInPeer, *_searchInMigrated; + PeerData *_searchInPeer = nullptr; + PeerData *_searchInMigrated = nullptr; QTimer _searchTimer; - QString _searchQuery, _peopleQuery; - bool _searchFull, _searchFullMigrated, _peopleFull; - mtpRequestId _searchRequest, _peopleRequest; - typedef QMap SearchCache; + QString _peerSearchQuery; + bool _peerSearchFull = false; + mtpRequestId _peerSearchRequest = 0; + + QString _searchQuery; + bool _searchFull = false; + bool _searchFullMigrated = false; + mtpRequestId _searchRequest = 0; + + using SearchCache = QMap; SearchCache _searchCache; - typedef QMap SearchQueries; + using SearchQueries = QMap; SearchQueries _searchQueries; - typedef QMap PeopleCache; - PeopleCache _peopleCache; + using PeerSearchCache = QMap; + PeerSearchCache _peerSearchCache; - typedef QMap PeopleQueries; - PeopleQueries _peopleQueries; + using PeerSearchQueries = QMap; + PeerSearchQueries _peerSearchQueries; }; diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp deleted file mode 100644 index 5876eeff9..000000000 --- a/Telegram/SourceFiles/dropdown.cpp +++ /dev/null @@ -1,456 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -It is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the copyright holders give permission -to link the code of portions of this program with the OpenSSL library. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org -*/ -#include "stdafx.h" -#include "dropdown.h" - -#include "styles/style_stickers.h" -#include "boxes/confirmbox.h" -#include "boxes/stickersetbox.h" -#include "inline_bots/inline_bot_result.h" -#include "inline_bots/inline_bot_layout_item.h" -#include "dialogs/dialogs_layout.h" -#include "historywidget.h" -#include "localstorage.h" -#include "lang.h" -#include "mainwindow.h" -#include "apiwrap.h" -#include "mainwidget.h" - -Dropdown::Dropdown(QWidget *parent, const style::dropdown &st) : TWidget(parent) -, _st(st) -, _width(_st.width) -, a_opacity(0) -, _a_appearance(animation(this, &Dropdown::step_appearance)) -, _shadow(_st.shadow) { - resetButtons(); - - _hideTimer.setSingleShot(true); - connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideStart())); - - if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { - connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged())); - } -} - -void Dropdown::ignoreShow(bool ignore) { - _ignore = ignore; -} - -void Dropdown::onWndActiveChanged() { - if (!App::wnd()->windowHandle()->isActive() && !isHidden()) { - leaveEvent(0); - } -} - -IconedButton *Dropdown::addButton(IconedButton *button) { - button->setParent(this); - - int32 nw = _st.padding.left() + _st.padding.right() + button->width(); - if (nw > _width) { - _width = nw; - for (int32 i = 0, l = _buttons.size(); i < l; ++i) _buttons[i]->resize(_width - _st.padding.left() - _st.padding.right(), _buttons[i]->height()); - } else { - button->resize(_width - _st.padding.left() - _st.padding.right(), button->height()); - } - if (!button->isHidden()) { - if (_height > _st.padding.top() + _st.padding.bottom()) { - _height += _st.border; - } - _height += button->height(); - } - _buttons.push_back(button); - connect(button, SIGNAL(stateChanged(int, ButtonStateChangeSource)), this, SLOT(buttonStateChanged(int, ButtonStateChangeSource))); - - resize(_width, _height); - - return button; -} - -void Dropdown::resetButtons() { - _width = qMax(_st.padding.left() + _st.padding.right(), int(_st.width)); - _height = _st.padding.top() + _st.padding.bottom(); - for (int32 i = 0, l = _buttons.size(); i < l; ++i) { - delete _buttons[i]; - } - _buttons.clear(); - resize(_width, _height); - - _selected = -1; -} - -void Dropdown::updateButtons() { - int32 top = _st.padding.top(), starttop = top; - for (Buttons::const_iterator i = _buttons.cbegin(), e = _buttons.cend(); i != e; ++i) { - if (!(*i)->isHidden()) { - (*i)->move(_st.padding.left(), top); - if ((*i)->width() != _width - _st.padding.left() - _st.padding.right()) { - (*i)->resize(_width - _st.padding.left() - _st.padding.right(), (*i)->height()); - } - top += (*i)->height() + _st.border; - } - } - _height = top + _st.padding.bottom() - (top > starttop ? _st.border : 0); - resize(_width, _height); -} - -void Dropdown::resizeEvent(QResizeEvent *e) { - int32 top = _st.padding.top(); - for (Buttons::const_iterator i = _buttons.cbegin(), e = _buttons.cend(); i != e; ++i) { - if (!(*i)->isHidden()) { - (*i)->move(_st.padding.left(), top); - top += (*i)->height() + _st.border; - } - } -} - -void Dropdown::paintEvent(QPaintEvent *e) { - Painter p(this); - - if (_a_appearance.animating()) { - p.setOpacity(a_opacity.current()); - } - - // draw shadow - QRect r(_st.padding.left(), _st.padding.top(), _width - _st.padding.left() - _st.padding.right(), _height - _st.padding.top() - _st.padding.bottom()); - _shadow.paint(p, r, _st.shadowShift); - - if (!_buttons.isEmpty() && _st.border > 0) { // paint separators - p.setPen(_st.borderColor->p); - int32 top = _st.padding.top(), i = 0, l = _buttons.size(); - for (; i < l; ++i) { - if (!_buttons.at(i)->isHidden()) break; - } - if (i < l) { - top += _buttons.at(i)->height(); - for (++i; i < l; ++i) { - if (!_buttons.at(i)->isHidden()) { - p.fillRect(_st.padding.left(), top, _width - _st.padding.left() - _st.padding.right(), _st.border, _st.borderColor->b); - top += _st.border + _buttons.at(i)->height(); - } - } - } - } -} - -void Dropdown::enterEvent(QEvent *e) { - _hideTimer.stop(); - if (_hiding) showStart(); - return TWidget::enterEvent(e); -} - -void Dropdown::leaveEvent(QEvent *e) { - if (_a_appearance.animating()) { - hideStart(); - } else { - _hideTimer.start(300); - } - return TWidget::leaveEvent(e); -} - -void Dropdown::keyPressEvent(QKeyEvent *e) { - if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { - if (_selected >= 0 && _selected < _buttons.size()) { - emit _buttons[_selected]->clicked(); - return; - } - } else if (e->key() == Qt::Key_Escape) { - hideStart(); - return; - } - if ((e->key() != Qt::Key_Up && e->key() != Qt::Key_Down) || _buttons.size() < 1) return; - - bool none = (_selected < 0 || _selected >= _buttons.size()); - int32 delta = (e->key() == Qt::Key_Down ? 1 : -1); - int32 newSelected = none ? (e->key() == Qt::Key_Down ? 0 : _buttons.size() - 1) : (_selected + delta); - if (newSelected < 0) { - newSelected = _buttons.size() - 1; - } else if (newSelected >= _buttons.size()) { - newSelected = 0; - } - int32 startFrom = newSelected; - while (_buttons.at(newSelected)->isHidden()) { - newSelected += delta; - if (newSelected < 0) { - newSelected = _buttons.size() - 1; - } else if (newSelected >= _buttons.size()) { - newSelected = 0; - } - if (newSelected == startFrom) return; - } - if (!none) { - _buttons[_selected]->setOver(false); - } - _selected = newSelected; - _buttons[_selected]->setOver(true); -} - -void Dropdown::buttonStateChanged(int oldState, ButtonStateChangeSource source) { - if (source == ButtonByUser) { - for (int32 i = 0, l = _buttons.size(); i < l; ++i) { - if (_buttons[i]->getState() & Button::StateOver) { - if (i != _selected) { - _buttons[i]->setOver(false); - } - } - } - } else if (source == ButtonByHover) { - bool found = false; - for (int32 i = 0, l = _buttons.size(); i < l; ++i) { - if (_buttons[i]->getState() & Button::StateOver) { - found = true; - if (i != _selected) { - int32 sel = _selected; - _selected = i; - if (sel >= 0 && sel < _buttons.size()) { - _buttons[sel]->setOver(false); - } - } - } - } - if (!found) { - _selected = -1; - } - } -} - -void Dropdown::otherEnter() { - _hideTimer.stop(); - showStart(); -} - -void Dropdown::otherLeave() { - if (_a_appearance.animating()) { - hideStart(); - } else { - _hideTimer.start(0); - } -} - -void Dropdown::fastHide() { - if (_a_appearance.animating()) { - _a_appearance.stop(); - } - a_opacity = anim::fvalue(0, 0); - _hideTimer.stop(); - hide(); -} - -void Dropdown::adjustButtons() { - for (Buttons::const_iterator i = _buttons.cbegin(), e = _buttons.cend(); i != e; ++i) { - (*i)->setOpacity(a_opacity.current()); - } -} - -void Dropdown::hideStart() { - _hiding = true; - a_opacity.start(0); - _a_appearance.start(); -} - -void Dropdown::hideFinish() { - emit hiding(); - hide(); - for (Buttons::const_iterator i = _buttons.cbegin(), e = _buttons.cend(); i != e; ++i) { - (*i)->clearState(); - } - _selected = -1; -} - -void Dropdown::showStart() { - if (!isHidden() && a_opacity.current() == 1) { - return; - } - _selected = -1; - _hiding = false; - show(); - a_opacity.start(1); - _a_appearance.start(); -} - -void Dropdown::step_appearance(float64 ms, bool timer) { - float64 dt = ms / _st.duration; - if (dt >= 1) { - _a_appearance.stop(); - a_opacity.finish(); - if (_hiding) { - hideFinish(); - } - } else { - a_opacity.update(dt, anim::linear); - } - adjustButtons(); - if (timer) update(); -} - -bool Dropdown::eventFilter(QObject *obj, QEvent *e) { - if (e->type() == QEvent::Enter) { - otherEnter(); - } else if (e->type() == QEvent::Leave) { - otherLeave(); - } else if (e->type() == QEvent::MouseButtonPress && static_cast(e)->button() == Qt::LeftButton) { - if (isHidden() || _hiding) { - otherEnter(); - } else { - otherLeave(); - } - } - return false; -} - -DragArea::DragArea(QWidget *parent) : TWidget(parent) -, _hiding(false) -, _in(false) -, a_opacity(0) -, a_color(st::dragColor->c) -, _a_appearance(animation(this, &DragArea::step_appearance)) -, _shadow(st::boxShadow) { - setMouseTracking(true); - setAcceptDrops(true); -} - -void DragArea::mouseMoveEvent(QMouseEvent *e) { - if (_hiding) return; - - bool newIn = QRect(st::dragPadding.left(), st::dragPadding.top(), width() - st::dragPadding.left() - st::dragPadding.right(), height() - st::dragPadding.top() - st::dragPadding.bottom()).contains(e->pos()); - if (newIn != _in) { - _in = newIn; - a_opacity.start(1); - a_color.start((_in ? st::dragDropColor : st::dragColor)->c); - _a_appearance.start(); - } -} - -void DragArea::dragMoveEvent(QDragMoveEvent *e) { - QRect r(st::dragPadding.left(), st::dragPadding.top(), width() - st::dragPadding.left() - st::dragPadding.right(), height() - st::dragPadding.top() - st::dragPadding.bottom()); - bool newIn = r.contains(e->pos()); - if (newIn != _in) { - _in = newIn; - a_opacity.start(1); - a_color.start((_in ? st::dragDropColor : st::dragColor)->c); - _a_appearance.start(); - } - e->setDropAction(_in ? Qt::CopyAction : Qt::IgnoreAction); - e->accept(); -} - -void DragArea::setText(const QString &text, const QString &subtext) { - _text = text; - _subtext = subtext; - update(); -} - -void DragArea::paintEvent(QPaintEvent *e) { - Painter p(this); - - if (_a_appearance.animating()) { - p.setOpacity(a_opacity.current()); - } - - QRect r(st::dragPadding.left(), st::dragPadding.top(), width() - st::dragPadding.left() - st::dragPadding.right(), height() - st::dragPadding.top() - st::dragPadding.bottom()); - - // draw shadow - _shadow.paint(p, r, st::boxShadowShift); - - p.fillRect(r, st::white->b); - - p.setPen(a_color.current()); - - p.setFont(st::dragFont->f); - p.drawText(QRect(0, (height() - st::dragHeight) / 2, width(), st::dragFont->height), _text, QTextOption(style::al_top)); - - p.setFont(st::dragSubfont->f); - p.drawText(QRect(0, (height() + st::dragHeight) / 2 - st::dragSubfont->height, width(), st::dragSubfont->height * 2), _subtext, QTextOption(style::al_top)); -} - -void DragArea::dragEnterEvent(QDragEnterEvent *e) { - static_cast(parentWidget())->dragEnterEvent(e); - e->setDropAction(Qt::IgnoreAction); - e->accept(); -} - -void DragArea::dragLeaveEvent(QDragLeaveEvent *e) { - static_cast(parentWidget())->dragLeaveEvent(e); - _in = false; - a_opacity.start(_hiding ? 0 : 1); - a_color.start((_in ? st::dragDropColor : st::dragColor)->c); - _a_appearance.start(); -} - -void DragArea::dropEvent(QDropEvent *e) { - static_cast(parentWidget())->dropEvent(e); - if (e->isAccepted()) { - emit dropped(e->mimeData()); - } -} - -void DragArea::otherEnter() { - showStart(); -} - -void DragArea::otherLeave() { - hideStart(); -} - -void DragArea::fastHide() { - if (_a_appearance.animating()) { - _a_appearance.stop(); - } - a_opacity = anim::fvalue(0, 0); - hide(); -} - -void DragArea::hideStart() { - _hiding = true; - _in = false; - a_opacity.start(0); - a_color.start((_in ? st::dragDropColor : st::dragColor)->c); - _a_appearance.start(); -} - -void DragArea::hideFinish() { - hide(); - _in = false; - a_color = anim::cvalue(st::dragColor->c); -} - -void DragArea::showStart() { - _hiding = false; - show(); - a_opacity.start(1); - a_color.start((_in ? st::dragDropColor : st::dragColor)->c); - _a_appearance.start(); -} - -void DragArea::step_appearance(float64 ms, bool timer) { - float64 dt = ms / st::dropdownDef.duration; - if (dt >= 1) { - a_opacity.finish(); - a_color.finish(); - if (_hiding) { - hideFinish(); - } - _a_appearance.stop(); - } else { - a_opacity.update(dt, anim::linear); - a_color.update(dt, anim::linear); - } - if (timer) update(); -} diff --git a/Telegram/SourceFiles/dropdown.h b/Telegram/SourceFiles/dropdown.h deleted file mode 100644 index a9ed3f699..000000000 --- a/Telegram/SourceFiles/dropdown.h +++ /dev/null @@ -1,151 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -It is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the copyright holders give permission -to link the code of portions of this program with the OpenSSL library. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org -*/ -#pragma once - -#include "ui/twidget.h" -#include "ui/effects/rect_shadow.h" - -class Dropdown : public TWidget { - Q_OBJECT - -public: - Dropdown(QWidget *parent, const style::dropdown &st = st::dropdownDef); - - IconedButton *addButton(IconedButton *button); - void resetButtons(); - void updateButtons(); - - void resizeEvent(QResizeEvent *e); - void paintEvent(QPaintEvent *e); - - void enterEvent(QEvent *e); - void leaveEvent(QEvent *e); - void keyPressEvent(QKeyEvent *e); - void otherEnter(); - void otherLeave(); - - void fastHide(); - void ignoreShow(bool ignore = true); - - void step_appearance(float64 ms, bool timer); - - bool eventFilter(QObject *obj, QEvent *e); - - bool overlaps(const QRect &globalRect) { - if (isHidden() || _a_appearance.animating()) return false; - - return QRect(_st.padding.left(), - _st.padding.top(), - _width - _st.padding.left() - _st.padding.right(), - _height - _st.padding.top() - _st.padding.bottom() - ).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); - } - -signals: - void hiding(); - -public slots: - void hideStart(); - void hideFinish(); - - void showStart(); - void onWndActiveChanged(); - - void buttonStateChanged(int oldState, ButtonStateChangeSource source); - -private: - void adjustButtons(); - - bool _ignore = false; - - typedef QVector Buttons; - Buttons _buttons; - - int32 _selected = -1; - - const style::dropdown &_st; - - int32 _width, _height; - bool _hiding = false; - - anim::fvalue a_opacity; - Animation _a_appearance; - - QTimer _hideTimer; - - Ui::RectShadow _shadow; - -}; - -class DragArea : public TWidget { - Q_OBJECT - -public: - DragArea(QWidget *parent); - - void setText(const QString &text, const QString &subtext); - - void otherEnter(); - void otherLeave(); - - void fastHide(); - - void step_appearance(float64 ms, bool timer); - - bool overlaps(const QRect &globalRect) { - if (isHidden() || _a_appearance.animating()) return false; - - return QRect(st::dragPadding.left(), - st::dragPadding.top(), - width() - st::dragPadding.left() - st::dragPadding.right(), - height() - st::dragPadding.top() - st::dragPadding.bottom() - ).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); - } - -protected: - void paintEvent(QPaintEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void dragEnterEvent(QDragEnterEvent *e) override; - void dragLeaveEvent(QDragLeaveEvent *e) override; - void dropEvent(QDropEvent *e) override; - void dragMoveEvent(QDragMoveEvent *e) override; - -signals: - void dropped(const QMimeData *data); - -public slots: - void hideStart(); - void hideFinish(); - - void showStart(); - -private: - bool _hiding, _in; - - anim::fvalue a_opacity; - anim::cvalue a_color; - Animation _a_appearance; - - Ui::RectShadow _shadow; - - QString _text, _subtext; - -}; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 8457a0775..e8cef2e77 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -37,6 +37,13 @@ Q_DECLARE_METATYPE(Qt::MouseButton); Q_DECLARE_METATYPE(Ui::ShowWay); namespace App { +namespace internal { + +void CallDelayed(int duration, base::lambda &&lambda) { + QTimer::singleShot(duration, base::lambda_slot_once(App::app(), std_::move(lambda)), SLOT(action())); +} + +} // namespace internal void sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo) { if (auto m = main()) { @@ -44,6 +51,12 @@ void sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId rep } } +void hideSingleUseKeyboard(const HistoryItem *msg) { + if (auto m = main()) { + m->hideSingleUseKeyboard(msg->history()->peer, msg->id); + } +} + bool insertBotCommand(const QString &cmd, bool specialGif) { if (auto m = main()) { return m->insertBotCommand(cmd, specialGif); @@ -81,17 +94,31 @@ void activateBotCommand(const HistoryItem *msg, int row, int col) { case ButtonType::Url: { auto url = QString::fromUtf8(button->data); - HiddenUrlClickHandler(url).onClick(Qt::LeftButton); + auto skipConfirmation = false; + if (auto bot = msg->getMessageBot()) { + if (bot->isVerified()) { + skipConfirmation = true; + } + } + if (skipConfirmation) { + UrlClickHandler::doOpen(url); + } else { + HiddenUrlClickHandler::doOpen(url); + } } break; case ButtonType::RequestLocation: { - Ui::showLayer(new InformBox(lang(lng_bot_share_location_unavailable))); + hideSingleUseKeyboard(msg); + Ui::show(Box(lang(lng_bot_share_location_unavailable))); } break; case ButtonType::RequestPhone: { - SharePhoneConfirmBox *box = new SharePhoneConfirmBox(msg->history()->peer); - box->connect(box, SIGNAL(confirmed(PeerData*)), App::main(), SLOT(onSharePhoneWithBot(PeerData*))); - Ui::showLayer(box); + hideSingleUseKeyboard(msg); + Ui::show(Box(lang(lng_bot_share_phone), lang(lng_bot_share_phone_confirm), [peerId = msg->history()->peer->id] { + if (auto m = App::main()) { + m->onShareContact(peerId, App::self()); + } + })); } break; case ButtonType::SwitchInlineSame: @@ -165,14 +192,23 @@ void activateClickHandler(ClickHandlerPtr handler, Qt::MouseButton button) { } void logOutDelayed() { - if (auto w = App::wnd()) { - QMetaObject::invokeMethod(w, "onLogoutSure", Qt::QueuedConnection); - } + App::CallDelayed(1, App::app(), [] { + App::logOut(); + }); } } // namespace App namespace Ui { +namespace internal { + +void showBox(object_ptr content, ShowLayerOptions options) { + if (auto w = App::wnd()) { + w->ui_showBox(std_::move(content), options); + } +} + +} // namespace internal void showMediaPreview(DocumentData *document) { if (auto w = App::wnd()) { @@ -192,20 +228,16 @@ void hideMediaPreview() { } } -void showLayer(LayerWidget *box, ShowLayerOptions options) { +void hideLayer(bool fast) { if (auto w = App::wnd()) { - w->ui_showLayer(box, options); - } else { - delete box; + w->ui_showBox({ nullptr }, CloseOtherLayers | (fast ? ForceFastShowLayer : AnimatedShowLayer)); } } -void hideLayer(bool fast) { - if (auto w = App::wnd()) w->ui_showLayer(0, CloseOtherLayers | (fast ? ForceFastShowLayer : AnimatedShowLayer)); -} - void hideSettingsAndLayer(bool fast) { - if (auto w = App::wnd()) w->ui_hideSettingsAndLayer(fast ? ForceFastShowLayer : AnimatedShowLayer); + if (auto w = App::wnd()) { + w->ui_hideSettingsAndLayer(fast ? ForceFastShowLayer : AnimatedShowLayer); + } } bool isLayerShown() { @@ -284,18 +316,7 @@ PeerData *getPeerForMouseAction() { bool hideWindowNoQuit() { if (!App::quitting()) { if (auto w = App::wnd()) { - if (cWorkMode() == dbiwmTrayOnly || cWorkMode() == dbiwmWindowAndTray) { - if (w->minimizeToTray()) { - Ui::showChatsList(); - return true; - } - } else if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { - w->closeWithoutDestroy(); - w->updateIsActive(Global::OfflineBlurTimeout()); - w->updateGlobalMenu(); - Ui::showChatsList(); - return true; - } + return w->hideNoQuit(); } } return false; @@ -503,13 +524,24 @@ void WorkingDirReady() { } } +object_ptr MainThreadTaskHandler = { nullptr }; + +void MainThreadTaskAdded() { + if (!started()) { + return; + } + + MainThreadTaskHandler->call(); +} + void start() { + MainThreadTaskHandler.create(QCoreApplication::instance(), "onMainThreadTask"); SandboxData = new internal::Data(); SandboxData->LangSystemISO = psCurrentLanguage(); if (SandboxData->LangSystemISO.isEmpty()) SandboxData->LangSystemISO = qstr("en"); - QByteArray l = LangSystemISO().toLatin1(); - for (int32 i = 0; i < languageCount; ++i) { + auto l = LangSystemISO().toLatin1(); + for (auto i = 0; i < languageCount; ++i) { if (l.at(0) == LanguageCodes[i][0] && l.at(1) == LanguageCodes[i][1]) { SandboxData->LangSystem = i; break; @@ -517,9 +549,14 @@ void start() { } } +bool started() { + return (SandboxData != nullptr); +} + void finish() { delete SandboxData; - SandboxData = 0; + SandboxData = nullptr; + MainThreadTaskHandler.destroy(); } uint64 UserTag() { @@ -618,6 +655,7 @@ struct Data { int32 SavedGifsLimit = 200; int32 EditTimeLimit = 172800; int32 StickersRecentLimit = 30; + int32 PinnedDialogsCountMax = 5; HiddenPinnedMessagesMap HiddenPinnedMessages; @@ -625,11 +663,12 @@ struct Data { Stickers::Sets StickerSets; Stickers::Order StickerSetsOrder; - uint64 LastStickersUpdate = 0; - uint64 LastRecentStickersUpdate = 0; + TimeMs LastStickersUpdate = 0; + TimeMs LastRecentStickersUpdate = 0; Stickers::Order FeaturedStickerSetsOrder; int FeaturedStickerSetsUnreadCount = 0; - uint64 LastFeaturedStickersUpdate = 0; + base::Observable FeaturedStickerSetsUnreadCountChanged; + TimeMs LastFeaturedStickersUpdate = 0; Stickers::Order ArchivedStickerSetsOrder; MTP::DcOptions DcOptions; @@ -666,6 +705,8 @@ struct Data { base::Observable LocalPasscodeChanged; base::Observable ItemRemoved; + base::Observable UnreadCounterUpdate; + base::Observable PeerChooseCancel; }; @@ -733,6 +774,7 @@ DefineVar(Global, int32, PushChatLimit); DefineVar(Global, int32, SavedGifsLimit); DefineVar(Global, int32, EditTimeLimit); DefineVar(Global, int32, StickersRecentLimit); +DefineVar(Global, int32, PinnedDialogsCountMax); DefineVar(Global, HiddenPinnedMessagesMap, HiddenPinnedMessages); @@ -740,11 +782,12 @@ DefineRefVar(Global, PendingItemsMap, PendingRepaintItems); DefineVar(Global, Stickers::Sets, StickerSets); DefineVar(Global, Stickers::Order, StickerSetsOrder); -DefineVar(Global, uint64, LastStickersUpdate); -DefineVar(Global, uint64, LastRecentStickersUpdate); +DefineVar(Global, TimeMs, LastStickersUpdate); +DefineVar(Global, TimeMs, LastRecentStickersUpdate); DefineVar(Global, Stickers::Order, FeaturedStickerSetsOrder); DefineVar(Global, int, FeaturedStickerSetsUnreadCount); -DefineVar(Global, uint64, LastFeaturedStickersUpdate); +DefineRefVar(Global, base::Observable, FeaturedStickerSetsUnreadCountChanged); +DefineVar(Global, TimeMs, LastFeaturedStickersUpdate); DefineVar(Global, Stickers::Order, ArchivedStickerSetsOrder); DefineVar(Global, MTP::DcOptions, DcOptions); @@ -781,5 +824,7 @@ DefineVar(Global, bool, LocalPasscode); DefineRefVar(Global, base::Observable, LocalPasscodeChanged); DefineRefVar(Global, base::Observable, ItemRemoved); +DefineRefVar(Global, base::Observable, UnreadCounterUpdate); +DefineRefVar(Global, base::Observable, PeerChooseCancel); } // namespace Global diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index dc3eaed73..a1888c89e 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -24,6 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "core/observer.h" class LayerWidget; +class BoxContent; namespace InlineBots { namespace Layout { @@ -32,6 +33,30 @@ class ItemBase; } // namespace InlineBots namespace App { +namespace internal { + +void CallDelayed(int duration, base::lambda &&lambda); + +} // namespace internal + +template +inline void CallDelayed(int duration, base::internal::lambda_guard &&guarded) { + return internal::CallDelayed(duration, [guarded = std_::move(guarded)] { guarded(); }); +} + +template +inline void CallDelayed(int duration, Pointer &&qobject, PointersAndLambda&&... qobjectsAndLambda) { + auto guarded = base::lambda_guarded(std_::forward(qobject), std_::forward(qobjectsAndLambda)...); + return CallDelayed(duration, std_::move(guarded)); +} + +template +inline base::lambda LambdaDelayed(int duration, PointersAndLambda&&... qobjectsAndLambda) { + auto guarded = base::lambda_guarded(std_::forward(qobjectsAndLambda)...); + return [guarded = std_::move(guarded), duration] { + CallDelayed(duration, guarded.clone()); + }; +} void sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo = 0); bool insertBotCommand(const QString &cmd, bool specialGif = false); @@ -52,12 +77,23 @@ void logOutDelayed(); } // namespace App namespace Ui { +namespace internal { + +void showBox(object_ptr content, ShowLayerOptions options); + +} // namespace internal void showMediaPreview(DocumentData *document); void showMediaPreview(PhotoData *photo); void hideMediaPreview(); -void showLayer(LayerWidget *box, ShowLayerOptions options = CloseOtherLayers); +template +QPointer show(object_ptr content, ShowLayerOptions options = CloseOtherLayers) { + auto result = QPointer(content.data()); + internal::showBox(std_::move(content), options); + return result; +} + void hideLayer(bool fast = false); void hideSettingsAndLayer(bool fast = false); bool isLayerShown(); @@ -189,7 +225,10 @@ namespace Sandbox { bool CheckBetaVersionDir(); void WorkingDirReady(); +void MainThreadTaskAdded(); + void start(); +bool started(); void finish(); uint64 UserTag(); @@ -304,6 +343,7 @@ DeclareVar(int32, PushChatLimit); DeclareVar(int32, SavedGifsLimit); DeclareVar(int32, EditTimeLimit); DeclareVar(int32, StickersRecentLimit); +DeclareVar(int32, PinnedDialogsCountMax); typedef QMap HiddenPinnedMessagesMap; DeclareVar(HiddenPinnedMessagesMap, HiddenPinnedMessages); @@ -313,11 +353,12 @@ DeclareRefVar(PendingItemsMap, PendingRepaintItems); DeclareVar(Stickers::Sets, StickerSets); DeclareVar(Stickers::Order, StickerSetsOrder); -DeclareVar(uint64, LastStickersUpdate); -DeclareVar(uint64, LastRecentStickersUpdate); +DeclareVar(TimeMs, LastStickersUpdate); +DeclareVar(TimeMs, LastRecentStickersUpdate); DeclareVar(Stickers::Order, FeaturedStickerSetsOrder); DeclareVar(int, FeaturedStickerSetsUnreadCount); -DeclareVar(uint64, LastFeaturedStickersUpdate); +DeclareRefVar(base::Observable, FeaturedStickerSetsUnreadCountChanged); +DeclareVar(TimeMs, LastFeaturedStickersUpdate); DeclareVar(Stickers::Order, ArchivedStickerSetsOrder); DeclareVar(MTP::DcOptions, DcOptions); @@ -355,6 +396,8 @@ DeclareVar(bool, LocalPasscode); DeclareRefVar(base::Observable, LocalPasscodeChanged); DeclareRefVar(base::Observable, ItemRemoved); +DeclareRefVar(base::Observable, UnreadCounterUpdate); +DeclareRefVar(base::Observable, PeerChooseCancel); } // namespace Global diff --git a/Telegram/SourceFiles/fileuploader.cpp b/Telegram/SourceFiles/fileuploader.cpp index 1487168ab..2b2fc3046 100644 --- a/Telegram/SourceFiles/fileuploader.cpp +++ b/Telegram/SourceFiles/fileuploader.cpp @@ -29,10 +29,10 @@ FileUploader::FileUploader() : sentSize(0) { connect(&killSessionsTimer, SIGNAL(timeout()), this, SLOT(killSessions())); } -void FileUploader::uploadMedia(const FullMsgId &msgId, const ReadyLocalMedia &media) { - if (media.type == PreparePhoto) { +void FileUploader::uploadMedia(const FullMsgId &msgId, const SendMediaReady &media) { + if (media.type == SendMediaType::Photo) { App::feedPhoto(media.photo, media.photoThumbs); - } else if (media.type == PrepareDocument || media.type == PrepareAudio) { + } else if (media.type == SendMediaType::File || media.type == SendMediaType::Audio) { DocumentData *document; if (media.photoThumbs.isEmpty()) { document = App::feedDocument(media.document); @@ -52,10 +52,10 @@ void FileUploader::uploadMedia(const FullMsgId &msgId, const ReadyLocalMedia &me } void FileUploader::upload(const FullMsgId &msgId, const FileLoadResultPtr &file) { - if (file->type == PreparePhoto) { + if (file->type == SendMediaType::Photo) { PhotoData *photo = App::feedPhoto(file->photo, file->photoThumbs); photo->uploadingData = new PhotoData::UploadingData(file->partssize); - } else if (file->type == PrepareDocument || file->type == PrepareAudio) { + } else if (file->type == SendMediaType::File || file->type == SendMediaType::Audio) { DocumentData *document; if (file->thumb.isNull()) { document = App::feedDocument(file->document); @@ -77,9 +77,9 @@ void FileUploader::upload(const FullMsgId &msgId, const FileLoadResultPtr &file) void FileUploader::currentFailed() { Queue::iterator j = queue.find(uploading); if (j != queue.end()) { - if (j->type() == PreparePhoto) { + if (j->type() == SendMediaType::Photo) { emit photoFailed(j.key()); - } else if (j->type() == PrepareDocument) { + } else if (j->type() == SendMediaType::File) { DocumentData *doc = App::document(j->id()); if (doc->status == FileUploading) { doc->status = FileUploadFailed; @@ -135,15 +135,15 @@ void FileUploader::sendNext() { } } - UploadFileParts &parts(i->file ? (i->type() == PreparePhoto ? i->file->fileparts : i->file->thumbparts) : i->media.parts); - uint64 partsOfId(i->file ? (i->type() == PreparePhoto ? i->file->id : i->file->thumbId) : i->media.thumbId); + UploadFileParts &parts(i->file ? (i->type() == SendMediaType::Photo ? i->file->fileparts : i->file->thumbparts) : i->media.parts); + uint64 partsOfId(i->file ? (i->type() == SendMediaType::Photo ? i->file->id : i->file->thumbId) : i->media.thumbId); if (parts.isEmpty()) { if (i->docSentParts >= i->docPartsCount) { if (requestsSent.isEmpty() && docRequestsSent.isEmpty()) { bool silent = i->file && i->file->to.silent; - if (i->type() == PreparePhoto) { + if (i->type() == SendMediaType::Photo) { emit photoReady(uploading, silent, MTP_inputFile(MTP_long(i->id()), MTP_int(i->partsCount), MTP_string(i->filename()), MTP_bytes(i->file ? i->file->filemd5 : i->media.jpeg_md5))); - } else if (i->type() == PrepareDocument || i->type() == PrepareAudio) { + } else if (i->type() == SendMediaType::File || i->type() == SendMediaType::Audio) { QByteArray docMd5(32, Qt::Uninitialized); hashMd5Hex(i->md5Hash.result(), docMd5.data()); @@ -177,7 +177,7 @@ void FileUploader::sendNext() { } } else { toSend = content.mid(i->docSentParts * i->docPartSize, i->docPartSize); - if ((i->type() == PrepareDocument || i->type() == PrepareAudio) && i->docSentParts <= UseBigFilesFrom) { + if ((i->type() == SendMediaType::File || i->type() == SendMediaType::Audio) && i->docSentParts <= UseBigFilesFrom) { i->md5Hash.feed(toSend.constData(), toSend.size()); } } @@ -282,7 +282,7 @@ void FileUploader::partLoaded(const MTPBool &result, mtpRequestId requestId) { } sentSize -= sentPartSize; sentSizes[dc] -= sentPartSize; - if (k->type() == PreparePhoto) { + if (k->type() == SendMediaType::Photo) { k->fileSentSize += sentPartSize; PhotoData *photo = App::photo(k->id()); if (photo->uploading() && k->file) { @@ -290,7 +290,7 @@ void FileUploader::partLoaded(const MTPBool &result, mtpRequestId requestId) { photo->uploadingData->offset = k->fileSentSize; } emit photoProgress(k.key()); - } else if (k->type() == PrepareDocument || k->type() == PrepareAudio) { + } else if (k->type() == SendMediaType::File || k->type() == SendMediaType::Audio) { DocumentData *doc = App::document(k->id()); if (doc->uploading()) { doc->uploadOffset = (k->docSentParts - docRequestsSent.size()) * k->docPartSize; diff --git a/Telegram/SourceFiles/fileuploader.h b/Telegram/SourceFiles/fileuploader.h index 5ae1c6dbc..68d0b1328 100644 --- a/Telegram/SourceFiles/fileuploader.h +++ b/Telegram/SourceFiles/fileuploader.h @@ -26,9 +26,8 @@ class FileUploader : public QObject, public RPCSender { Q_OBJECT public: - FileUploader(); - void uploadMedia(const FullMsgId &msgId, const ReadyLocalMedia &image); + void uploadMedia(const FullMsgId &msgId, const SendMediaReady &image); void upload(const FullMsgId &msgId, const FileLoadResultPtr &file); int32 currentOffset(const FullMsgId &msgId) const; // -1 means file not found @@ -41,13 +40,11 @@ public: void clear(); public slots: - void unpause(); void sendNext(); void killSessions(); signals: - void photoReady(const FullMsgId &msgId, bool silent, const MTPInputFile &file); void documentReady(const FullMsgId &msgId, bool silent, const MTPInputFile &file); void thumbDocumentReady(const FullMsgId &msgId, bool silent, const MTPInputFile &file, const MTPInputFile &thumb); @@ -59,19 +56,18 @@ signals: void documentFailed(const FullMsgId &msgId); private: - struct File { - File(const ReadyLocalMedia &media) : media(media), docSentParts(0) { + File(const SendMediaReady &media) : media(media), docSentParts(0) { partsCount = media.parts.size(); - if (type() == PrepareDocument || type() == PrepareAudio) { + if (type() == SendMediaType::File || type() == SendMediaType::Audio) { setDocSize(media.file.isEmpty() ? media.data.size() : media.filesize); } else { docSize = docPartSize = docPartsCount = 0; } } File(const FileLoadResultPtr &file) : file(file), docSentParts(0) { - partsCount = (type() == PreparePhoto) ? file->fileparts.size() : file->thumbparts.size(); - if (type() == PrepareDocument || type() == PrepareAudio) { + partsCount = (type() == SendMediaType::Photo) ? file->fileparts.size() : file->thumbparts.size(); + if (type() == SendMediaType::File || type() == SendMediaType::Audio) { setDocSize(file->filesize); } else { docSize = docPartSize = docPartsCount = 0; @@ -98,14 +94,14 @@ private: } FileLoadResultPtr file; - ReadyLocalMedia media; + SendMediaReady media; int32 partsCount; mutable int32 fileSentSize; uint64 id() const { return file ? file->id : media.id; } - PrepareMediaType type() const { + SendMediaType type() const { return file ? file->type : media.type; } uint64 thumbId() const { diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 038a91514..ed096cb79 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -34,6 +34,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace { +constexpr int kStatusShowClientsideTyping = 6000; constexpr int kStatusShowClientsideRecordVideo = 6000; constexpr int kStatusShowClientsideUploadVideo = 6000; constexpr int kStatusShowClientsideRecordVoice = 6000; @@ -43,6 +44,17 @@ constexpr int kStatusShowClientsideUploadFile = 6000; constexpr int kStatusShowClientsideChooseLocation = 6000; constexpr int kStatusShowClientsideChooseContact = 6000; constexpr int kStatusShowClientsidePlayGame = 10000; +constexpr int kSetMyActionForMs = 10000; + +auto GlobalPinnedIndex = 0; + +HistoryItem *createUnsupportedMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from) { + QString text(lng_message_unsupported(lt_link, qsl("https://desktop.telegram.org"))); + EntitiesInText entities; + textParseEntities(text, _historyTextNoMonoOptions.flags, &entities); + entities.push_front(EntityInText(EntityInTextItalic, 0, text.size())); + return HistoryMessage::create(history, msgId, flags, replyTo, viaBotId, date, from, { text, entities }); +} } // namespace @@ -54,9 +66,9 @@ void historyInit() { History::History(const PeerId &peerId) : peer(App::peer(peerId)) , lastItemTextCache(st::dialogsTextWidthMin) -, typingText(st::dialogsTextWidthMin) , cloudDraftTextCache(st::dialogsTextWidthMin) -, _mute(isNotifyMuted(peer->notify)) { +, _mute(isNotifyMuted(peer->notify)) +, _sendActionText(st::dialogsTextWidthMin) { if (peer->isUser() && peer->asUser()->botInfo) { outboxReadBefore = INT_MAX; } @@ -183,87 +195,156 @@ void History::draftSavedToCloud() { if (App::main()) App::main()->writeDrafts(this); } -bool History::updateTyping(uint64 ms, bool force) { - bool changed = force; - for (auto i = typing.begin(), e = typing.end(); i != e;) { +bool History::updateSendActionNeedsAnimating(UserData *user, const MTPSendMessageAction &action) { + using Type = SendAction::Type; + if (action.type() == mtpc_sendMessageCancelAction) { + unregSendAction(user); + return false; + } + + auto ms = getms(); + switch (action.type()) { + case mtpc_sendMessageTypingAction: _typing.insert(user, ms + kStatusShowClientsideTyping); break; + case mtpc_sendMessageRecordVideoAction: _sendActions.insert(user, { Type::RecordVideo, ms + kStatusShowClientsideRecordVideo }); break; + case mtpc_sendMessageUploadVideoAction: _sendActions.insert(user, { Type::UploadVideo, ms + kStatusShowClientsideUploadVideo, action.c_sendMessageUploadVideoAction().vprogress.v }); break; + case mtpc_sendMessageRecordAudioAction: _sendActions.insert(user, { Type::RecordVoice, ms + kStatusShowClientsideRecordVoice }); break; + case mtpc_sendMessageUploadAudioAction: _sendActions.insert(user, { Type::UploadVoice, ms + kStatusShowClientsideUploadVoice, action.c_sendMessageUploadAudioAction().vprogress.v }); break; + case mtpc_sendMessageUploadPhotoAction: _sendActions.insert(user, { Type::UploadPhoto, ms + kStatusShowClientsideUploadPhoto, action.c_sendMessageUploadPhotoAction().vprogress.v }); break; + case mtpc_sendMessageUploadDocumentAction: _sendActions.insert(user, { Type::UploadFile, ms + kStatusShowClientsideUploadFile, action.c_sendMessageUploadDocumentAction().vprogress.v }); break; + case mtpc_sendMessageGeoLocationAction: _sendActions.insert(user, { Type::ChooseLocation, ms + kStatusShowClientsideChooseLocation }); break; + case mtpc_sendMessageChooseContactAction: _sendActions.insert(user, { Type::ChooseContact, ms + kStatusShowClientsideChooseContact }); break; + case mtpc_sendMessageGamePlayAction: { + auto it = _sendActions.find(user); + if (it == _sendActions.end() || it->type == Type::PlayGame || it->until <= ms) { + _sendActions.insert(user, { Type::PlayGame, ms + kStatusShowClientsidePlayGame }); + } + } break; + default: return false; + } + return updateSendActionNeedsAnimating(ms, true); +} + +bool History::mySendActionUpdated(SendAction::Type type, bool doing) { + auto ms = getms(true); + auto i = _mySendActions.find(type); + if (doing) { + if (i == _mySendActions.cend()) { + _mySendActions.insert(type, ms + kSetMyActionForMs); + } else if (i.value() > ms + (kSetMyActionForMs / 2)) { + return false; + } else { + i.value() = ms + kSetMyActionForMs; + } + } else { + if (i == _mySendActions.cend()) { + return false; + } else if (i.value() <= ms) { + return false; + } else { + _mySendActions.erase(i); + } + } + return true; +} + +bool History::paintSendAction(Painter &p, int x, int y, int availableWidth, int outerWidth, style::color color, TimeMs ms) { + if (_sendActionAnimation) { + _sendActionAnimation.paint(p, color, x, y + st::normalFont->ascent, outerWidth, ms); + auto animationWidth = _sendActionAnimation.width(); + x += animationWidth; + availableWidth -= animationWidth; + p.setPen(color); + _sendActionText.drawElided(p, x, y, availableWidth); + return true; + } + return false; +} + +bool History::updateSendActionNeedsAnimating(TimeMs ms, bool force) { + auto changed = force; + for (auto i = _typing.begin(), e = _typing.end(); i != e;) { if (ms >= i.value()) { - i = typing.erase(i); + i = _typing.erase(i); changed = true; } else { ++i; } } - for (auto i = sendActions.begin(); i != sendActions.cend();) { + for (auto i = _sendActions.begin(); i != _sendActions.cend();) { if (ms >= i.value().until) { - i = sendActions.erase(i); + i = _sendActions.erase(i); changed = true; } else { ++i; } } if (changed) { - QString newTypingStr; - int typingCount = typing.size(); + QString newTypingString; + auto typingCount = _typing.size(); if (typingCount > 2) { - newTypingStr = lng_many_typing(lt_count, typingCount); + newTypingString = lng_many_typing(lt_count, typingCount); } else if (typingCount > 1) { - newTypingStr = lng_users_typing(lt_user, typing.begin().key()->firstName, lt_second_user, (typing.end() - 1).key()->firstName); + newTypingString = lng_users_typing(lt_user, _typing.begin().key()->firstName, lt_second_user, (_typing.end() - 1).key()->firstName); } else if (typingCount) { - newTypingStr = peer->isUser() ? lang(lng_typing) : lng_user_typing(lt_user, typing.begin().key()->firstName); - } else if (!sendActions.isEmpty()) { + newTypingString = peer->isUser() ? lang(lng_typing) : lng_user_typing(lt_user, _typing.begin().key()->firstName); + } else if (!_sendActions.isEmpty()) { // Handles all actions except game playing. - auto sendActionString = [](SendActionType type, const QString &name) -> QString { + using Type = SendAction::Type; + auto sendActionString = [](Type type, const QString &name) -> QString { switch (type) { - case SendActionRecordVideo: return name.isEmpty() ? lang(lng_send_action_record_video) : lng_user_action_record_video(lt_user, name); - case SendActionUploadVideo: return name.isEmpty() ? lang(lng_send_action_upload_video) : lng_user_action_upload_video(lt_user, name); - case SendActionRecordVoice: return name.isEmpty() ? lang(lng_send_action_record_audio) : lng_user_action_record_audio(lt_user, name); - case SendActionUploadVoice: return name.isEmpty() ? lang(lng_send_action_upload_audio) : lng_user_action_upload_audio(lt_user, name); - case SendActionUploadPhoto: return name.isEmpty() ? lang(lng_send_action_upload_photo) : lng_user_action_upload_photo(lt_user, name); - case SendActionUploadFile: return name.isEmpty() ? lang(lng_send_action_upload_file) : lng_user_action_upload_file(lt_user, name); - case SendActionChooseLocation: return name.isEmpty() ? lang(lng_send_action_geo_location) : lng_user_action_geo_location(lt_user, name); - case SendActionChooseContact: return name.isEmpty() ? lang(lng_send_action_choose_contact) : lng_user_action_choose_contact(lt_user, name); + case Type::RecordVideo: return name.isEmpty() ? lang(lng_send_action_record_video) : lng_user_action_record_video(lt_user, name); + case Type::UploadVideo: return name.isEmpty() ? lang(lng_send_action_upload_video) : lng_user_action_upload_video(lt_user, name); + case Type::RecordVoice: return name.isEmpty() ? lang(lng_send_action_record_audio) : lng_user_action_record_audio(lt_user, name); + case Type::UploadVoice: return name.isEmpty() ? lang(lng_send_action_upload_audio) : lng_user_action_upload_audio(lt_user, name); + case Type::UploadPhoto: return name.isEmpty() ? lang(lng_send_action_upload_photo) : lng_user_action_upload_photo(lt_user, name); + case Type::UploadFile: return name.isEmpty() ? lang(lng_send_action_upload_file) : lng_user_action_upload_file(lt_user, name); + case Type::ChooseLocation: return name.isEmpty() ? lang(lng_send_action_geo_location) : lng_user_action_geo_location(lt_user, name); + case Type::ChooseContact: return name.isEmpty() ? lang(lng_send_action_choose_contact) : lng_user_action_choose_contact(lt_user, name); default: break; }; return QString(); }; - for (auto i = sendActions.cbegin(), e = sendActions.cend(); i != e; ++i) { - newTypingStr = sendActionString(i->type, peer->isUser() ? QString() : i.key()->firstName); - if (!newTypingStr.isEmpty()) { + for (auto i = _sendActions.cbegin(), e = _sendActions.cend(); i != e; ++i) { + newTypingString = sendActionString(i->type, peer->isUser() ? QString() : i.key()->firstName); + if (!newTypingString.isEmpty()) { + _sendActionAnimation.start(i->type); break; } } // Everyone in sendActions are playing a game. - if (newTypingStr.isEmpty()) { - int playingCount = sendActions.size(); + if (newTypingString.isEmpty()) { + int playingCount = _sendActions.size(); if (playingCount > 2) { - newTypingStr = lng_many_playing_game(lt_count, playingCount); + newTypingString = lng_many_playing_game(lt_count, playingCount); } else if (playingCount > 1) { - newTypingStr = lng_users_playing_game(lt_user, sendActions.begin().key()->firstName, lt_second_user, (sendActions.end() - 1).key()->firstName); + newTypingString = lng_users_playing_game(lt_user, _sendActions.begin().key()->firstName, lt_second_user, (_sendActions.end() - 1).key()->firstName); } else { - newTypingStr = peer->isUser() ? lang(lng_playing_game) : lng_user_playing_game(lt_user, sendActions.begin().key()->firstName); + newTypingString = peer->isUser() ? lang(lng_playing_game) : lng_user_playing_game(lt_user, _sendActions.begin().key()->firstName); } + _sendActionAnimation.start(Type::PlayGame); } } - if (!newTypingStr.isEmpty()) { - newTypingStr += qsl("..."); + if (typingCount > 0) { + _sendActionAnimation.start(SendAction::Type::Typing); + } else if (newTypingString.isEmpty()) { + _sendActionAnimation.stop(); } - if (typingStr != newTypingStr) { - typingText.setText(st::dialogsTextFont, (typingStr = newTypingStr), _textNameOptions); + if (_sendActionString != newTypingString) { + _sendActionString = newTypingString; + _sendActionText.setText(st::dialogsTextStyle, _sendActionString, _textNameOptions); } } - if (!typingStr.isEmpty()) { - if (typingText.lastDots(typingDots % 4)) { - changed = true; - } + auto result = (!_typing.isEmpty() || !_sendActions.isEmpty()); + if (changed || result) { + App::histories().sendActionAnimationUpdated().notify({ + this, + _sendActionAnimation.width(), + st::normalFont->height, + changed + }); } - if (changed && App::main()) { - updateChatListEntry(); - if (App::main()->historyPeer() == peer) { - App::main()->topBar()->update(); - } - } - return changed; + return result; } ChannelHistory::ChannelHistory(const PeerId &peer) : History(peer) @@ -310,8 +391,11 @@ void ChannelHistory::getRangeDifference() { void ChannelHistory::getRangeDifferenceNext(int32 pts) { if (!App::main() || _rangeDifferenceToId < _rangeDifferenceFromId) return; - int32 limit = _rangeDifferenceToId + 1 - _rangeDifferenceFromId; - _rangeDifferenceRequestId = MTP::send(MTPupdates_GetChannelDifference(peer->asChannel()->inputChannel, MTP_channelMessagesFilter(MTP_flags(MTPDchannelMessagesFilter::Flags(0)), MTP_vector(1, MTP_messageRange(MTP_int(_rangeDifferenceFromId), MTP_int(_rangeDifferenceToId)))), MTP_int(pts), MTP_int(limit)), App::main()->rpcDone(&MainWidget::gotRangeDifference, peer->asChannel())); + int limit = _rangeDifferenceToId + 1 - _rangeDifferenceFromId; + + auto filter = MTP_channelMessagesFilter(MTP_flags(MTPDchannelMessagesFilter::Flags(0)), MTP_vector(1, MTP_messageRange(MTP_int(_rangeDifferenceFromId), MTP_int(_rangeDifferenceToId)))); + MTPupdates_GetChannelDifference::Flags flags = MTPupdates_GetChannelDifference::Flag::f_force; + _rangeDifferenceRequestId = MTP::send(MTPupdates_GetChannelDifference(MTP_flags(flags), peer->asChannel()->inputChannel, filter, MTP_int(pts), MTP_int(limit)), App::main()->rpcDone(&MainWidget::gotRangeDifference, peer->asChannel())); } HistoryJoined *ChannelHistory::insertJoinedMessage(bool unread) { @@ -548,8 +632,8 @@ History *Histories::findOrInsert(const PeerId &peerId, int32 unreadCount, int32 void Histories::clear() { App::historyClearMsgs(); - Map temp; - std::swap(temp, map); + _pinnedDialogs.clear(); + auto temp = base::take(map); for_const (auto history, temp) { delete history; } @@ -561,56 +645,23 @@ void Histories::clear() { } void Histories::regSendAction(History *history, UserData *user, const MTPSendMessageAction &action, TimeId when) { - if (action.type() == mtpc_sendMessageCancelAction) { - history->unregTyping(user); - return; - } else if (action.type() == mtpc_sendMessageGameStopAction) { - auto it = history->sendActions.find(user); - if (it != history->sendActions.end() && it->type == SendActionPlayGame) { - history->unregTyping(user); + if (history->updateSendActionNeedsAnimating(user, action)) { + user->madeAction(when); + + auto i = typing.find(history); + if (i == typing.cend()) { + typing.insert(history, getms()); + _a_typings.start(); } - return; } - - uint64 ms = getms(); - switch (action.type()) { - case mtpc_sendMessageTypingAction: history->typing[user] = ms + 6000; break; - case mtpc_sendMessageRecordVideoAction: history->sendActions.insert(user, SendAction(SendActionRecordVideo, ms + kStatusShowClientsideRecordVideo)); break; - case mtpc_sendMessageUploadVideoAction: history->sendActions.insert(user, SendAction(SendActionUploadVideo, ms + kStatusShowClientsideUploadVideo, action.c_sendMessageUploadVideoAction().vprogress.v)); break; - case mtpc_sendMessageRecordAudioAction: history->sendActions.insert(user, SendAction(SendActionRecordVoice, ms + kStatusShowClientsideRecordVoice)); break; - case mtpc_sendMessageUploadAudioAction: history->sendActions.insert(user, SendAction(SendActionUploadVoice, ms + kStatusShowClientsideUploadVoice, action.c_sendMessageUploadAudioAction().vprogress.v)); break; - case mtpc_sendMessageUploadPhotoAction: history->sendActions.insert(user, SendAction(SendActionUploadPhoto, ms + kStatusShowClientsideUploadPhoto, action.c_sendMessageUploadPhotoAction().vprogress.v)); break; - case mtpc_sendMessageUploadDocumentAction: history->sendActions.insert(user, SendAction(SendActionUploadFile, ms + kStatusShowClientsideUploadFile, action.c_sendMessageUploadDocumentAction().vprogress.v)); break; - case mtpc_sendMessageGeoLocationAction: history->sendActions.insert(user, SendAction(SendActionChooseLocation, ms + kStatusShowClientsideChooseLocation)); break; - case mtpc_sendMessageChooseContactAction: history->sendActions.insert(user, SendAction(SendActionChooseContact, ms + kStatusShowClientsideChooseContact)); break; - case mtpc_sendMessageGamePlayAction: { - auto it = history->sendActions.find(user); - if (it == history->sendActions.end() || it->type == SendActionPlayGame || it->until <= ms) { - history->sendActions.insert(user, SendAction(SendActionPlayGame, ms + kStatusShowClientsidePlayGame)); - } - } break; - default: return; - } - - user->madeAction(when); - - auto i = typing.find(history); - if (i == typing.cend()) { - typing.insert(history, ms); - history->typingDots = 0; - _a_typings.start(); - } - history->updateTyping(ms, true); } -void Histories::step_typings(uint64 ms, bool timer) { - for (TypingHistories::iterator i = typing.begin(), e = typing.end(); i != e;) { - i.key()->typingDots = (ms - i.value()) / 150; - i.key()->updateTyping(ms); - if (i.key()->typing.isEmpty() && i.key()->sendActions.isEmpty()) { - i = typing.erase(i); - } else { +void Histories::step_typings(TimeMs ms, bool timer) { + for (auto i = typing.begin(), e = typing.end(); i != e;) { + if (i.key()->updateSendActionNeedsAnimating(ms)) { ++i; + } else { + i = typing.erase(i); } } if (typing.isEmpty()) { @@ -671,8 +722,38 @@ bool Histories::unreadOnlyMuted() const { return Global::IncludeMuted() ? (_unreadMuted >= _unreadFull) : false; } +void Histories::setIsPinned(History *history, bool isPinned) { + if (isPinned) { + _pinnedDialogs.insert(history); + if (_pinnedDialogs.size() > Global::PinnedDialogsCountMax()) { + auto minIndex = GlobalPinnedIndex + 1; + auto minIndexHistory = (History*)nullptr; + for_const (auto pinned, _pinnedDialogs) { + if (pinned->getPinnedIndex() < minIndex) { + minIndex = pinned->getPinnedIndex(); + minIndexHistory = pinned; + } + } + t_assert(minIndexHistory != nullptr); + minIndexHistory->setPinnedDialog(false); + } + } else { + _pinnedDialogs.remove(history); + } +} + +void Histories::clearPinned() { + for (auto pinned : base::take(_pinnedDialogs)) { + pinned->setPinnedDialog(false); + } +} + +int Histories::pinnedCount() const { + return _pinnedDialogs.size(); +} + HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem) { - MsgId msgId = 0; + auto msgId = MsgId(0); switch (msg.type()) { case mtpc_messageEmpty: msgId = msg.c_messageEmpty().vid.v; break; case mtpc_message: msgId = msg.c_message().vid.v; break; @@ -680,7 +761,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, } if (!msgId) return nullptr; - HistoryItem *result = App::histItemById(channelId(), msgId); + auto result = App::histItemById(channelId(), msgId); if (result) { if (!result->detached() && detachExistingItem) { result->detach(); @@ -700,37 +781,42 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, break; case mtpc_message: { - const auto &m(msg.c_message()); - int badMedia = 0; // 1 - unsupported, 2 - empty + auto &m = msg.c_message(); + enum class MediaCheckResult { + Good, + Unsupported, + Empty, + }; + auto badMedia = MediaCheckResult::Good; if (m.has_media()) switch (m.vmedia.type()) { case mtpc_messageMediaEmpty: case mtpc_messageMediaContact: break; case mtpc_messageMediaGeo: switch (m.vmedia.c_messageMediaGeo().vgeo.type()) { case mtpc_geoPoint: break; - case mtpc_geoPointEmpty: badMedia = 2; break; - default: badMedia = 1; break; + case mtpc_geoPointEmpty: badMedia = MediaCheckResult::Empty; break; + default: badMedia = MediaCheckResult::Unsupported; break; } break; case mtpc_messageMediaVenue: switch (m.vmedia.c_messageMediaVenue().vgeo.type()) { case mtpc_geoPoint: break; - case mtpc_geoPointEmpty: badMedia = 2; break; - default: badMedia = 1; break; + case mtpc_geoPointEmpty: badMedia = MediaCheckResult::Empty; break; + default: badMedia = MediaCheckResult::Unsupported; break; } break; case mtpc_messageMediaPhoto: switch (m.vmedia.c_messageMediaPhoto().vphoto.type()) { case mtpc_photo: break; - case mtpc_photoEmpty: badMedia = 2; break; - default: badMedia = 1; break; + case mtpc_photoEmpty: badMedia = MediaCheckResult::Empty; break; + default: badMedia = MediaCheckResult::Unsupported; break; } break; case mtpc_messageMediaDocument: switch (m.vmedia.c_messageMediaDocument().vdocument.type()) { case mtpc_document: break; - case mtpc_documentEmpty: badMedia = 2; break; - default: badMedia = 1; break; + case mtpc_documentEmpty: badMedia = MediaCheckResult::Empty; break; + default: badMedia = MediaCheckResult::Unsupported; break; } break; case mtpc_messageMediaWebPage: @@ -738,25 +824,22 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, case mtpc_webPage: case mtpc_webPageEmpty: case mtpc_webPagePending: break; - default: badMedia = 1; break; + case mtpc_webPageNotModified: + default: badMedia = MediaCheckResult::Unsupported; break; } break; case mtpc_messageMediaGame: switch (m.vmedia.c_messageMediaGame().vgame.type()) { case mtpc_game: break; - default: badMedia = 1; break; + default: badMedia = MediaCheckResult::Unsupported; break; } break; case mtpc_messageMediaUnsupported: - default: badMedia = 1; break; + default: badMedia = MediaCheckResult::Unsupported; break; } - if (badMedia == 1) { - QString text(lng_message_unsupported(lt_link, qsl("https://desktop.telegram.org"))); - EntitiesInText entities; - textParseEntities(text, _historyTextNoMonoOptions.flags, &entities); - entities.push_front(EntityInText(EntityInTextItalic, 0, text.size())); - result = HistoryMessage::create(this, m.vid.v, m.vflags.v, m.vreply_to_msg_id.v, m.vvia_bot_id.v, date(m.vdate), m.vfrom_id.v, { text, entities }); - } else if (badMedia) { + if (badMedia == MediaCheckResult::Unsupported) { + result = createUnsupportedMessage(this, m.vid.v, m.vflags.v, m.vreply_to_msg_id.v, m.vvia_bot_id.v, date(m.vdate), m.vfrom_id.v); + } else if (badMedia == MediaCheckResult::Empty) { result = HistoryService::create(this, m.vid.v, date(m.vdate), lang(lng_message_empty), m.vflags.v, m.has_from_id() ? m.vfrom_id.v : 0); } else { result = HistoryMessage::create(this, m); @@ -764,18 +847,23 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, } break; case mtpc_messageService: { - const auto &d(msg.c_messageService()); - result = HistoryService::create(this, d); + auto &m = msg.c_messageService(); + if (m.vaction.type() == mtpc_messageActionPhoneCall) { + auto viaBotId = 0; + result = createUnsupportedMessage(this, m.vid.v, mtpCastFlags(m.vflags.v), m.vreply_to_msg_id.v, viaBotId, date(m.vdate), m.vfrom_id.v); + } else { + result = HistoryService::create(this, m); + } if (applyServiceAction) { - const auto &action(d.vaction); - switch (d.vaction.type()) { + auto &action = m.vaction; + switch (action.type()) { case mtpc_messageActionChatAddUser: { - const auto &d(action.c_messageActionChatAddUser()); + auto &d = action.c_messageActionChatAddUser(); if (peer->isMegagroup()) { - const auto &v(d.vusers.c_vector().v); - for (int32 i = 0, l = v.size(); i < l; ++i) { - if (UserData *user = App::userLoaded(peerFromUser(v.at(i)))) { + auto &v = d.vusers.c_vector().v; + for (auto i = 0, l = v.size(); i != l; ++i) { + if (auto user = App::userLoaded(peerFromUser(v[i]))) { if (peer->asChannel()->mgInfo->lastParticipants.indexOf(user) < 0) { peer->asChannel()->mgInfo->lastParticipants.push_front(user); peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsAdminsOutdated; @@ -793,7 +881,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, } break; case mtpc_messageActionChatJoinedByLink: { - const auto &d(action.c_messageActionChatJoinedByLink()); + auto &d = action.c_messageActionChatJoinedByLink(); if (peer->isMegagroup()) { if (result->from()->isUser()) { if (peer->asChannel()->mgInfo->lastParticipants.indexOf(result->from()->asUser()) < 0) { @@ -811,13 +899,13 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, } break; case mtpc_messageActionChatDeletePhoto: { - ChatData *chat = peer->asChat(); + auto chat = peer->asChat(); if (chat) chat->setPhoto(MTP_chatPhotoEmpty()); } break; case mtpc_messageActionChatDeleteUser: { - const auto &d(action.c_messageActionChatDeleteUser()); - PeerId uid = peerFromUser(d.vuser_id); + auto &d = action.c_messageActionChatDeleteUser(); + auto uid = peerFromUser(d.vuser_id); if (lastKeyboardFrom == uid) { clearLastKeyboard(); } @@ -882,7 +970,7 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, } break; case mtpc_messageActionChatEditTitle: { - auto &d(action.c_messageActionChatEditTitle()); + auto &d = action.c_messageActionChatEditTitle(); if (auto chat = peer->asChat()) { chat->setName(qs(d.vtitle)); } @@ -891,18 +979,18 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, case mtpc_messageActionChatMigrateTo: { peer->asChat()->flags |= MTPDchat::Flag::f_deactivated; - //const auto &d(action.c_messageActionChatMigrateTo()); - //PeerData *channel = App::channelLoaded(d.vchannel_id.v); + //auto &d = action.c_messageActionChatMigrateTo(); + //auto channel = App::channelLoaded(d.vchannel_id.v); } break; case mtpc_messageActionChannelMigrateFrom: { - //const auto &d(action.c_messageActionChannelMigrateFrom()); - //PeerData *chat = App::chatLoaded(d.vchat_id.v); + //auto &d = action.c_messageActionChannelMigrateFrom(); + //auto chat = App::chatLoaded(d.vchat_id.v); } break; case mtpc_messageActionPinMessage: { - if (d.has_reply_to_msg_id() && result && result->history()->peer->isMegagroup()) { - result->history()->peer->asChannel()->mgInfo->pinnedMsgId = d.vreply_to_msg_id.v; + if (m.has_reply_to_msg_id() && result && result->history()->peer->isMegagroup()) { + result->history()->peer->asChannel()->mgInfo->pinnedMsgId = m.vreply_to_msg_id.v; if (App::main()) emit App::main()->peerUpdated(result->history()->peer); } } break; @@ -1005,7 +1093,7 @@ bool History::addToOverview(MediaOverviewType type, MsgId msgId, AddToOverviewMe if (overviewCountData[type] > 0) { ++overviewCountData[type]; } - if (App::wnd()) App::wnd()->mediaOverviewUpdated(peer, type); + Notify::mediaOverviewUpdated(peer, type); } return true; } @@ -1026,7 +1114,7 @@ void History::eraseFromOverview(MediaOverviewType type, MsgId msgId) { break; } } - if (App::wnd()) App::wnd()->mediaOverviewUpdated(peer, type); + Notify::mediaOverviewUpdated(peer, type); } HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) { @@ -1107,20 +1195,20 @@ HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) { return adding; } -void History::unregTyping(UserData *from) { - uint64 updateAtMs = 0; - auto i = typing.find(from); - if (i != typing.cend()) { +void History::unregSendAction(UserData *from) { + auto updateAtMs = TimeMs(0); + auto i = _typing.find(from); + if (i != _typing.cend()) { updateAtMs = getms(); i.value() = updateAtMs; } - auto j = sendActions.find(from); - if (j != sendActions.cend()) { + auto j = _sendActions.find(from); + if (j != _sendActions.cend()) { if (!updateAtMs) updateAtMs = getms(); j.value().until = updateAtMs; } if (updateAtMs) { - updateTyping(updateAtMs, true); + updateSendActionNeedsAnimating(updateAtMs, true); } } @@ -1128,7 +1216,7 @@ void History::newItemAdded(HistoryItem *item) { App::checkImageCacheSize(); if (item->from() && item->from()->isUser()) { if (item->from() == item->author()) { - unregTyping(item->from()->asUser()); + unregSendAction(item->from()->asUser()); } MTPint itemServerTime; toServerTime(item->date.toTime_t(), itemServerTime); @@ -1291,8 +1379,11 @@ void History::addOlderSlice(const QVector &slice) { } } } - for (int32 t = 0; t < OverviewCount; ++t) { - if ((mask & (1 << t)) && App::wnd()) App::wnd()->mediaOverviewUpdated(peer, MediaOverviewType(t)); + if (mask) { + Notify::PeerUpdate update(peer); + update.flags |= Notify::PeerUpdate::Flag::SharedMediaChanged; + update.mediaTypesMask |= mask; + Notify::peerUpdatedDelayed(update); } } @@ -1369,8 +1460,11 @@ void History::checkAddAllToOverview() { mask |= item->addToOverview(AddToOverviewBack); } } - for (int32 t = 0; t < OverviewCount; ++t) { - if ((mask & (1 << t)) && App::wnd()) App::wnd()->mediaOverviewUpdated(peer, MediaOverviewType(t)); + if (mask) { + Notify::PeerUpdate update(peer); + update.flags |= Notify::PeerUpdate::Flag::SharedMediaChanged; + update.mediaTypesMask |= mask; + Notify::peerUpdatedDelayed(update); } } @@ -1637,18 +1731,22 @@ HistoryItem *History::addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, t_assert(blockIndex >= 0); t_assert(blockIndex < blocks.size()); t_assert(itemIndex >= 0); - t_assert(itemIndex <= blocks.at(blockIndex)->items.size()); + t_assert(itemIndex <= blocks[blockIndex]->items.size()); - HistoryBlock *block = blocks.at(blockIndex); + auto block = blocks.at(blockIndex); newItem->attachToBlock(block, itemIndex); block->items.insert(itemIndex, newItem); newItem->previousItemChanged(); - for (int i = itemIndex + 1, l = block->items.size(); i < l; ++i) { - block->items.at(i)->setIndexInBlock(i); - } if (itemIndex + 1 < block->items.size()) { - block->items.at(itemIndex + 1)->previousItemChanged(); + for (int i = itemIndex + 1, l = block->items.size(); i < l; ++i) { + block->items[i]->setIndexInBlock(i); + } + block->items[itemIndex + 1]->previousItemChanged(); + } else if (blockIndex + 1 < blocks.size() && !blocks[blockIndex + 1]->items.empty()) { + blocks[blockIndex + 1]->items.front()->previousItemChanged(); + } else { + newItem->nextItemChanged(); } return newItem; @@ -1666,14 +1764,18 @@ HistoryBlock *History::finishBuildingFrontBlock() { t_assert(isBuildingFrontBlock()); // Some checks if there was some message history already - HistoryBlock *block = _buildingFrontBlock->block; - if (block && blocks.size() > 1) { - HistoryItem *last = block->items.back(); // ... item, item, item, last ], [ first, item, item ... - HistoryItem *first = blocks.at(1)->items.front(); + auto block = _buildingFrontBlock->block; + if (block) { + if (blocks.size() > 1) { + auto last = block->items.back(); // ... item, item, item, last ], [ first, item, item ... + auto first = blocks.at(1)->items.front(); - // we've added a new front block, so previous item for - // the old first item of a first block was changed - first->previousItemChanged(); + // we've added a new front block, so previous item for + // the old first item of a first block was changed + first->previousItemChanged(); + } else { + block->items.back()->nextItemChanged(); + } } _buildingFrontBlock = nullptr; @@ -1760,6 +1862,10 @@ inline uint64 dialogPosFromDate(const QDateTime &date) { return (uint64(date.toTime_t()) << 32) | (++_dialogsPosToTopShift); } +inline uint64 pinnedDialogPos(int pinnedIndex) { + return 0xFFFFFFFF00000000ULL + pinnedIndex; +} + void History::setLastMessage(HistoryItem *msg) { if (msg) { if (!lastMsg) Local::removeSavedPeer(peer); @@ -1772,7 +1878,7 @@ void History::setLastMessage(HistoryItem *msg) { } bool History::needUpdateInChatList() const { - if (inChatList(Dialogs::Mode::All)) { + if (inChatList(Dialogs::Mode::All) || isPinnedDialog()) { return true; } else if (peer->migrateTo()) { return false; @@ -1781,9 +1887,8 @@ bool History::needUpdateInChatList() const { } void History::setChatsListDate(const QDateTime &date) { - bool updateDialog = needUpdateInChatList(); if (!lastMsgDate.isNull() && lastMsgDate >= date) { - if (!updateDialog || !inChatList(Dialogs::Mode::All)) { + if (!needUpdateInChatList() || !inChatList(Dialogs::Mode::All)) { return; } } @@ -1801,7 +1906,7 @@ void History::updateChatListSortPosition() { return lastMsgDate; }; - _sortKeyInChatList = dialogPosFromDate(chatListDate()); + _sortKeyInChatList = isPinnedDialog() ? pinnedDialogPos(_pinnedIndex) : dialogPosFromDate(chatListDate()); if (auto m = App::main()) { if (needUpdateInChatList()) { if (_sortKeyInChatList) { @@ -1914,7 +2019,7 @@ void History::clear(bool leaveItems) { } overview[i].clear(); overviewIds[i].clear(); - if (App::wnd() && !App::quitting()) App::wnd()->mediaOverviewUpdated(peer, MediaOverviewType(i)); + if (!App::quitting()) Notify::mediaOverviewUpdated(peer, MediaOverviewType(i)); } } clearBlocks(leaveItems); @@ -2011,16 +2116,30 @@ void History::addChatListEntryByLetter(Dialogs::Mode list, QChar letter, Dialogs } void History::updateChatListEntry() const { - if (MainWidget *m = App::main()) { + if (auto main = App::main()) { if (inChatList(Dialogs::Mode::All)) { - m->dlgUpdated(Dialogs::Mode::All, mainChatListLink(Dialogs::Mode::All)); + main->dlgUpdated(Dialogs::Mode::All, mainChatListLink(Dialogs::Mode::All)); if (inChatList(Dialogs::Mode::Important)) { - m->dlgUpdated(Dialogs::Mode::Important, mainChatListLink(Dialogs::Mode::Important)); + main->dlgUpdated(Dialogs::Mode::Important, mainChatListLink(Dialogs::Mode::Important)); } } } } +void History::setPinnedDialog(bool isPinned) { + auto pinnedIndex = isPinned ? (++GlobalPinnedIndex) : 0; + if (_pinnedIndex != pinnedIndex) { + auto wasPinned = isPinnedDialog(); + _pinnedIndex = pinnedIndex; + updateChatListSortPosition(); + updateChatListEntry(); + if (wasPinned != isPinnedDialog()) { + Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::PinnedChanged); + } + App::histories().setIsPinned(this, isPinnedDialog()); + } +} + void History::overviewSliceDone(int32 overviewIndex, const MTPmessages_Messages &result, bool onlyCounts) { const QVector *v = 0; switch (result.type()) { @@ -2106,11 +2225,13 @@ void History::removeBlock(HistoryBlock *block) { int index = block->indexInHistory(); blocks.removeAt(index); - for (int i = index, l = blocks.size(); i < l; ++i) { - blocks.at(i)->setIndexInHistory(i); - } if (index < blocks.size()) { + for (int i = index, l = blocks.size(); i < l; ++i) { + blocks.at(i)->setIndexInHistory(i); + } blocks.at(index)->items.front()->previousItemChanged(); + } else if (!blocks.empty() && !blocks.back()->items.empty()) { + blocks.back()->items.back()->nextItemChanged(); } } @@ -2176,6 +2297,8 @@ void HistoryBlock::removeItem(HistoryItem *item) { items.at(itemIndex)->previousItemChanged(); } else if (blockIndex + 1 < history->blocks.size()) { history->blocks.at(blockIndex + 1)->items.front()->previousItemChanged(); + } else if (!history->blocks.empty() && !history->blocks.back()->items.empty()) { + history->blocks.back()->items.back()->nextItemChanged(); } if (items.isEmpty()) { diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index 6dd45ecd8..4e2dc4d92 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -20,14 +20,14 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once +#include "structs.h" +#include "dialogs/dialogs_common.h" +#include "ui/effects/send_action_animations.h" + void historyInit(); class HistoryItem; - -typedef QMap SelectedItemSet; - -#include "structs.h" -#include "dialogs/dialogs_common.h" +using SelectedItemSet = QMap; enum NewMessageType { NewMessageUnread, @@ -38,14 +38,14 @@ enum NewMessageType { class History; class Histories { public: - typedef QHash Map; + using Map = QHash; Map map; - Histories() : _a_typings(animation(this, &Histories::step_typings)), _unreadFull(0), _unreadMuted(0) { + Histories() : _a_typings(animation(this, &Histories::step_typings)) { } void regSendAction(History *history, UserData *user, const MTPSendMessageAction &action, TimeId when); - void step_typings(uint64 ms, bool timer); + void step_typings(TimeMs ms, bool timer); History *find(const PeerId &peerId); History *findOrInsert(const PeerId &peerId); @@ -53,15 +53,12 @@ public: void clear(); void remove(const PeerId &peer); - ~Histories() { - _unreadFull = _unreadMuted = 0; - } HistoryItem *addNewMessage(const MTPMessage &msg, NewMessageType type); - typedef QMap TypingHistories; // when typing in this history started + typedef QMap TypingHistories; // when typing in this history started TypingHistories typing; - Animation _a_typings; + BasicAnimation _a_typings; int unreadBadge() const; int unreadMutedCount() const { @@ -82,8 +79,25 @@ public: } } + void setIsPinned(History *history, bool isPinned); + void clearPinned(); + int pinnedCount() const; + + struct SendActionAnimationUpdate { + History *history; + int width; + int height; + bool textUpdated; + }; + base::Observable &sendActionAnimationUpdated() { + return _sendActionAnimationUpdated; + } + private: - int _unreadFull, _unreadMuted; + int _unreadFull = 0; + int _unreadMuted = 0; + base::Observable _sendActionAnimationUpdated; + OrderedSet _pinnedDialogs; }; @@ -132,27 +146,30 @@ inline MTPMessagesFilter typeToMediaFilter(MediaOverviewType &type) { return MTPMessagesFilter(); } -enum SendActionType { - SendActionTyping, - SendActionRecordVideo, - SendActionUploadVideo, - SendActionRecordVoice, - SendActionUploadVoice, - SendActionUploadPhoto, - SendActionUploadFile, - SendActionChooseLocation, - SendActionChooseContact, - SendActionPlayGame, -}; -struct SendAction { - SendAction(SendActionType type, uint64 until, int32 progress = 0) : type(type), until(until), progress(progress) { - } - SendActionType type; - uint64 until; - int32 progress; +struct TextWithTags { + struct Tag { + int offset, length; + QString id; + }; + using Tags = QVector; + + QString text; + Tags tags; }; -using TextWithTags = FlatTextarea::TextWithTags; +inline bool operator==(const TextWithTags::Tag &a, const TextWithTags::Tag &b) { + return (a.offset == b.offset) && (a.length == b.length) && (a.id == b.id); +} +inline bool operator!=(const TextWithTags::Tag &a, const TextWithTags::Tag &b) { + return !(a == b); +} + +inline bool operator==(const TextWithTags &a, const TextWithTags &b) { + return (a.text == b.text) && (a.tags == b.tags); +} +inline bool operator!=(const TextWithTags &a, const TextWithTags &b) { + return !(a == b); +} namespace Data { struct Draft; @@ -214,7 +231,6 @@ public: void eraseFromOverview(MediaOverviewType type, MsgId msgId); void newItemAdded(HistoryItem *item); - void unregTyping(UserData *from); int countUnread(MsgId upTo); void updateShowFrom(); @@ -268,6 +284,14 @@ public: void addChatListEntryByLetter(Dialogs::Mode list, QChar letter, Dialogs::Row *row); void updateChatListEntry() const; + bool isPinnedDialog() const { + return (_pinnedIndex > 0); + } + void setPinnedDialog(bool isPinned); + int getPinnedIndex() const { + return _pinnedIndex; + } + MsgId minMsgId() const; MsgId maxMsgId() const; MsgId msgIdForRead() const; @@ -309,7 +333,12 @@ public: } void paintDialog(Painter &p, int32 w, bool sel) const; - bool updateTyping(uint64 ms, bool force = false); + bool updateSendActionNeedsAnimating(TimeMs ms, bool force = false); + void unregSendAction(UserData *from); + bool updateSendActionNeedsAnimating(UserData *user, const MTPSendMessageAction &action); + bool mySendActionUpdated(SendAction::Type type, bool doing); + bool paintSendAction(Painter &p, int x, int y, int availableWidth, int outerWidth, style::color color, TimeMs ms); + void clearLastKeyboard(); // optimization for userpics displayed on the left @@ -402,15 +431,6 @@ public: mutable const HistoryItem *textCachedFor = nullptr; // cache mutable Text lastItemTextCache; - using TypingUsers = QMap; - TypingUsers typing; - using SendActionUsers = QMap; - SendActionUsers sendActions; - QString typingStr; - Text typingText; - uint32 typingDots; - QMap mySendActions; - typedef QList MediaOverview; MediaOverview overview[OverviewCount]; @@ -546,6 +566,17 @@ private: std_::unique_ptr _localDraft, _cloudDraft; std_::unique_ptr _editDraft; + using TypingUsers = QMap; + TypingUsers _typing; + using SendActionUsers = QMap; + SendActionUsers _sendActions; + QString _sendActionString; + Text _sendActionText; + Ui::SendActionAnimation _sendActionAnimation; + QMap _mySendActions; + + int _pinnedIndex = 0; // > 0 for pinned dialogs + }; class HistoryJoined; diff --git a/Telegram/SourceFiles/history/field_autocomplete.cpp b/Telegram/SourceFiles/history/field_autocomplete.cpp index 5ef830b47..69e606dac 100644 --- a/Telegram/SourceFiles/history/field_autocomplete.cpp +++ b/Telegram/SourceFiles/history/field_autocomplete.cpp @@ -24,45 +24,47 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwindow.h" #include "apiwrap.h" #include "localstorage.h" +#include "ui/widgets/scroll_area.h" +#include "styles/style_history.h" +#include "styles/style_widgets.h" +#include "styles/style_stickers.h" FieldAutocomplete::FieldAutocomplete(QWidget *parent) : TWidget(parent) -, _scroll(this, st::mentionScroll) -, _inner(this, &_mrows, &_hrows, &_brows, &_srows) -, a_opacity(0) -, _a_appearance(animation(this, &FieldAutocomplete::step_appearance)) { - _hideTimer.setSingleShot(true); - connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideStart())); - - connect(_inner, SIGNAL(mentionChosen(UserData*,FieldAutocomplete::ChooseMethod)), this, SIGNAL(mentionChosen(UserData*,FieldAutocomplete::ChooseMethod))); - connect(_inner, SIGNAL(hashtagChosen(QString,FieldAutocomplete::ChooseMethod)), this, SIGNAL(hashtagChosen(QString,FieldAutocomplete::ChooseMethod))); - connect(_inner, SIGNAL(botCommandChosen(QString,FieldAutocomplete::ChooseMethod)), this, SIGNAL(botCommandChosen(QString,FieldAutocomplete::ChooseMethod))); - connect(_inner, SIGNAL(stickerChosen(DocumentData*,FieldAutocomplete::ChooseMethod)), this, SIGNAL(stickerChosen(DocumentData*,FieldAutocomplete::ChooseMethod))); - connect(_inner, SIGNAL(mustScrollTo(int, int)), _scroll, SLOT(scrollToY(int, int))); - - setFocusPolicy(Qt::NoFocus); - _scroll->setFocusPolicy(Qt::NoFocus); - _scroll->viewport()->setFocusPolicy(Qt::NoFocus); - - _inner->setGeometry(rect()); +, _scroll(this, st::mentionScroll) { _scroll->setGeometry(rect()); - _scroll->setOwnedWidget(_inner); + _inner = _scroll->setOwnedWidget(object_ptr(this, &_mrows, &_hrows, &_brows, &_srows)); + _inner->setGeometry(rect()); + + connect(_inner, SIGNAL(mentionChosen(UserData*, FieldAutocomplete::ChooseMethod)), this, SIGNAL(mentionChosen(UserData*, FieldAutocomplete::ChooseMethod))); + connect(_inner, SIGNAL(hashtagChosen(QString, FieldAutocomplete::ChooseMethod)), this, SIGNAL(hashtagChosen(QString, FieldAutocomplete::ChooseMethod))); + connect(_inner, SIGNAL(botCommandChosen(QString, FieldAutocomplete::ChooseMethod)), this, SIGNAL(botCommandChosen(QString, FieldAutocomplete::ChooseMethod))); + connect(_inner, SIGNAL(stickerChosen(DocumentData*, FieldAutocomplete::ChooseMethod)), this, SIGNAL(stickerChosen(DocumentData*, FieldAutocomplete::ChooseMethod))); + connect(_inner, SIGNAL(mustScrollTo(int, int)), _scroll, SLOT(scrollToY(int, int))); + _scroll->show(); _inner->show(); + hide(); + connect(_scroll, SIGNAL(geometryChanged()), _inner, SLOT(onParentGeometryChanged())); } void FieldAutocomplete::paintEvent(QPaintEvent *e) { Painter p(this); - if (_a_appearance.animating()) { - p.setOpacity(a_opacity.current()); - p.drawPixmap(0, 0, _cache); + auto opacity = _a_opacity.current(getms(), _hiding ? 0. : 1.); + if (opacity < 1.) { + if (opacity > 0.) { + p.setOpacity(opacity); + p.drawPixmap(0, 0, _cache); + } else if (_hiding) { + + } return; } - p.fillRect(rect(), st::white); + p.fillRect(rect(), st::mentionBg); } void FieldAutocomplete::showFiltered(PeerData *peer, QString query, bool addInlineBots) { @@ -333,7 +335,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) { void FieldAutocomplete::rowsUpdated(const internal::MentionRows &mrows, const internal::HashtagRows &hrows, const internal::BotCommandRows &brows, const StickerPack &srows, bool resetScroll) { if (mrows.isEmpty() && hrows.isEmpty() && brows.isEmpty() && srows.isEmpty()) { if (!isHidden()) { - hideStart(); + hideAnimated(); } _mrows.clear(); _hrows.clear(); @@ -354,7 +356,7 @@ void FieldAutocomplete::rowsUpdated(const internal::MentionRows &mrows, const in update(); if (hidden) { hide(); - showStart(); + showAnimated(); } } } @@ -394,27 +396,24 @@ void FieldAutocomplete::recount(bool resetScroll) { if (resetScroll) _inner->clearSel(); } -void FieldAutocomplete::fastHide() { - if (_a_appearance.animating()) { - _a_appearance.stop(); - } - a_opacity = anim::fvalue(0, 0); - _hideTimer.stop(); +void FieldAutocomplete::hideFast() { + _a_opacity.finish(); hideFinish(); } -void FieldAutocomplete::hideStart() { - if (!_hiding) { - if (_cache.isNull()) { - _scroll->show(); - _cache = myGrab(this); - } - _scroll->hide(); - _hiding = true; - a_opacity.start(0); - setAttribute(Qt::WA_OpaquePaintEvent, false); - _a_appearance.start(); +void FieldAutocomplete::hideAnimated() { + if (isHidden() || _hiding) { + return; } + + if (_cache.isNull()) { + _scroll->show(); + _cache = myGrab(this); + } + _scroll->hide(); + _hiding = true; + _a_opacity.start([this] { animationCallback(); }, 1., 0., st::emojiPanDuration); + setAttribute(Qt::WA_OpaquePaintEvent, false); } void FieldAutocomplete::hideFinish() { @@ -424,8 +423,8 @@ void FieldAutocomplete::hideFinish() { _inner->clearSel(true); } -void FieldAutocomplete::showStart() { - if (!isHidden() && a_opacity.current() == 1 && !_hiding) { +void FieldAutocomplete::showAnimated() { + if (!isHidden() && !_hiding) { return; } if (_cache.isNull()) { @@ -435,16 +434,13 @@ void FieldAutocomplete::showStart() { _scroll->hide(); _hiding = false; show(); - a_opacity.start(1); + _a_opacity.start([this] { animationCallback(); }, 0., 1., st::emojiPanDuration); setAttribute(Qt::WA_OpaquePaintEvent, false); - _a_appearance.start(); } -void FieldAutocomplete::step_appearance(float64 ms, bool timer) { - float64 dt = ms / st::dropdownDef.duration; - if (dt >= 1) { - _a_appearance.stop(); - a_opacity.finish(); +void FieldAutocomplete::animationCallback() { + update(); + if (!_a_opacity.animating()) { _cache = QPixmap(); setAttribute(Qt::WA_OpaquePaintEvent); if (_hiding) { @@ -453,10 +449,7 @@ void FieldAutocomplete::step_appearance(float64 ms, bool timer) { _scroll->show(); _inner->clearSel(); } - } else { - a_opacity.update(dt, anim::linear); } - if (timer) update(); } const QString &FieldAutocomplete::filter() const { @@ -544,7 +537,7 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) { int32 atwidth = st::mentionFont->width('@'), hashwidth = st::mentionFont->width('#'); int32 mentionleft = 2 * st::mentionPadding.left() + st::mentionPhotoSize; int32 mentionwidth = width() - mentionleft - 2 * st::mentionPadding.right(); - int32 htagleft = st::btnAttachPhoto.width + st::taMsgField.textMrg.left() - st::lineWidth, htagwidth = width() - st::mentionPadding.right() - htagleft - st::mentionScroll.width; + int32 htagleft = st::historyAttach.width + st::historyComposeField.textMrg.left() - st::lineWidth, htagwidth = width() - st::mentionPadding.right() - htagleft - st::mentionScroll.width; if (!_srows->isEmpty()) { int32 rows = rowscount(_srows->size(), _stickersPerRow); @@ -599,13 +592,12 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) { bool selected = (i == _sel); if (selected) { - p.fillRect(0, i * st::mentionHeight, width(), st::mentionHeight, st::mentionBgOver->b); - int skip = (st::mentionHeight - st::simpleClose.icon.pxHeight()) / 2; + p.fillRect(0, i * st::mentionHeight, width(), st::mentionHeight, st::mentionBgOver); + int skip = (st::mentionHeight - st::smallCloseIconOver.height()) / 2; if (!_hrows->isEmpty() || (!_mrows->isEmpty() && i < _recentInlineBotsInRows)) { - p.drawSprite(QPoint(width() - st::simpleClose.icon.pxWidth() - skip, i * st::mentionHeight + skip), st::simpleClose.icon); + st::smallCloseIconOver.paint(p, QPoint(width() - st::smallCloseIconOver.width() - skip, i * st::mentionHeight + skip), width()); } } - p.setPen(st::black->p); if (!_mrows->isEmpty()) { UserData *user = _mrows->at(i); QString first = (!filterIsEmpty && user->username.startsWith(filter, Qt::CaseInsensitive)) ? ('@' + user->username.mid(0, filterSize)) : QString(); @@ -626,11 +618,13 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) { } } user->loadUserpic(); - user->paintUserpicLeft(p, st::mentionPhotoSize, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width()); + user->paintUserpicLeft(p, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width(), st::mentionPhotoSize); + + p.setPen(selected ? st::mentionNameFgOver : st::mentionNameFg); user->nameText.drawElided(p, 2 * st::mentionPadding.left() + st::mentionPhotoSize, i * st::mentionHeight + st::mentionTop, namewidth); - p.setFont(st::mentionFont->f); - p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p); + p.setFont(st::mentionFont); + p.setPen(selected ? st::mentionFgOverActive : st::mentionFgActive); p.drawText(mentionleft + namewidth + st::mentionPadding.right(), i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first); if (!second.isEmpty()) { p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p); @@ -650,7 +644,7 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) { } } - p.setFont(st::mentionFont->f); + p.setFont(st::mentionFont); if (!first.isEmpty()) { p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p); p.drawText(htagleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first); @@ -669,11 +663,11 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) { toHighlight += '@' + user->username; } user->loadUserpic(); - user->paintUserpicLeft(p, st::mentionPhotoSize, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width()); + user->paintUserpicLeft(p, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width(), st::mentionPhotoSize); auto commandText = '/' + toHighlight; - p.setPen(st::windowTextFg); + p.setPen(selected ? st::mentionNameFgOver : st::mentionNameFg); p.setFont(st::semiboldFont); p.drawText(2 * st::mentionPadding.left() + st::mentionPhotoSize, i * st::mentionHeight + st::mentionTop + st::semiboldFont->ascent, commandText); @@ -687,9 +681,9 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) { } } } - p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerBottom() - st::lineWidth, width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowColor->b); + p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerBottom() - st::lineWidth, width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowFg); } - p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerTop(), width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowColor->b); + p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerTop(), width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowFg); } void FieldAutocompleteInner::resizeEvent(QResizeEvent *e) { diff --git a/Telegram/SourceFiles/history/field_autocomplete.h b/Telegram/SourceFiles/history/field_autocomplete.h index d0579b16b..d2a21137b 100644 --- a/Telegram/SourceFiles/history/field_autocomplete.h +++ b/Telegram/SourceFiles/history/field_autocomplete.h @@ -21,7 +21,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once #include "ui/twidget.h" -#include "ui/effects/rect_shadow.h" + +namespace Ui { +class ScrollArea; +} // namespace Ui namespace internal { @@ -39,15 +42,11 @@ class FieldAutocomplete final : public TWidget { public: FieldAutocomplete(QWidget *parent); - void fastHide(); - bool clearFilteredBotCommands(); void showFiltered(PeerData *peer, QString query, bool addInlineBots); void showStickers(EmojiPtr emoji); void setBoundings(QRect boundings); - void step_appearance(float64 ms, bool timer); - const QString &filter() const; ChatData *chat() const; ChannelData *channel() const; @@ -75,6 +74,8 @@ public: return rect().contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); } + void hideFast(); + ~FieldAutocomplete(); signals: @@ -86,15 +87,16 @@ signals: void moderateKeyActivate(int key, bool *outHandled) const; public slots: - void hideStart(); - void hideFinish(); - - void showStart(); + void showAnimated(); + void hideAnimated(); protected: void paintEvent(QPaintEvent *e) override; private: + void animationCallback(); + void hideFinish(); + void updateFiltered(bool resetScroll = false); void recount(bool resetScroll = false); @@ -106,8 +108,8 @@ private: void rowsUpdated(const internal::MentionRows &mrows, const internal::HashtagRows &hrows, const internal::BotCommandRows &brows, const StickerPack &srows, bool resetScroll); - ChildWidget _scroll; - ChildWidget _inner; + object_ptr _scroll; + QPointer _inner; ChatData *_chat = nullptr; UserData *_user = nullptr; @@ -127,10 +129,7 @@ private: int32 _width, _height; bool _hiding = false; - anim::fvalue a_opacity; - Animation _a_appearance; - - QTimer _hideTimer; + Animation _a_opacity; friend class internal::FieldAutocompleteInner; diff --git a/Telegram/SourceFiles/history/history.style b/Telegram/SourceFiles/history/history.style index ae7d0e116..379ee4ebd 100644 --- a/Telegram/SourceFiles/history/history.style +++ b/Telegram/SourceFiles/history/history.style @@ -20,121 +20,399 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ using "basic.style"; using "dialogs/dialogs.style"; +using "ui/widgets/widgets.style"; -historyPaddingBottom: 10px; +historyScroll: ScrollArea(defaultScrollArea) { + bg: historyScrollBg; + bgOver: historyScrollBgOver; + barBg: historyScrollBarBg; + barBgOver: historyScrollBarBgOver; + + round: 3px; + + width: 12px; + deltax: 3px; + deltat: 3px; + deltab: 3px; + + topsh: 0px; + bottomsh: -1px; +} + +historyPaddingBottom: 8px; -historyToDown: icon { - { "history_down_shadow", #00000040 }, - { "history_down_circle", #fff, point(2px, 1px) }, -}; historyToDownPosition: point(12px, 10px); -historyToDownArrow: icon { - { "history_down_arrow", #b9b9b9, point(14px, 19px) }, -}; +historyToDownAbove: icon {{ "history_down_arrow", menuIconFg, point(17px, 23px) }}; +historyToDownAboveOver: icon {{ "history_down_arrow", menuIconFgOver, point(17px, 23px) }}; historyToDownPaddingTop: 10px; +historyToDown: TwoIconButton { + width: 52px; + height: 62px; + + iconBelow: historyToDownBelow; + iconBelowOver: historyToDownBelowOver; + iconAbove: historyToDownAbove; + iconAboveOver: historyToDownAboveOver; + iconPosition: point(0px, historyToDownPaddingTop); + + rippleAreaPosition: point(5px, 15px); + rippleAreaSize: 42px; + ripple: RippleAnimation(defaultRippleAnimation) { + color: windowBgRipple; + } +} historyToDownBadgeFont: semiboldFont; historyToDownBadgeSize: 22px; -historyEmptyDog: icon {{ "history_empty_dog", #ffffff }}; -historyEmptySize: 128px; +historyToDownShownAfter: 480px; +historyToDownDuration: 150; -membersInnerScroll: flatScroll(solidScroll) { - deltat: 3px; - deltab: 3px; - round: 1px; - width: 8px; - deltax: 3px; -} membersInnerWidth: 310px; membersInnerHeightMax: 360px; membersInnerDropdown: InnerDropdown(defaultInnerDropdown) { + scroll: ScrollArea(defaultSolidScroll) { + deltat: 0px; + deltab: 0px; + round: 1px; + width: 8px; + deltax: 3px; + } scrollMargin: margins(0px, 5px, 0px, 5px); - scrollPadding: margins(0px, 3px, 8px, 3px); + scrollPadding: margins(0px, 3px, 0px, 3px); +} +membersInnerItem: ProfilePeerListItem { + left: 0px; + bottom: 0px; + button: OutlineButton { + outlineWidth: 0px; + + textBg: windowBg; + textBgOver: windowBgOver; + + textFg: windowSubTextFg; + textFgOver: windowSubTextFgOver; + + font: normalFont; + padding: margins(11px, 5px, 11px, 5px); + + ripple: defaultRippleAnimation; + } + statusFg: windowSubTextFg; + statusFgOver: windowSubTextFgOver; + statusFgActive: windowActiveTextFg; } -historyFileOutImage: icon {{ "history_file_image", msgOutBg }}; -historyFileOutImageSelected: icon {{ "history_file_image", msgOutBgSelected }}; -historyFileInImage: icon {{ "history_file_image", msgInBg }}; -historyFileInImageSelected: icon {{ "history_file_image", msgInBgSelected }}; -historyFileOutDocument: icon {{ "history_file_document", msgOutBg }}; -historyFileOutDocumentSelected: icon {{ "history_file_document", msgOutBgSelected }}; -historyFileInDocument: icon {{ "history_file_document", msgInBg }}; -historyFileInDocumentSelected: icon {{ "history_file_document", msgInBgSelected }}; -historyFileOutDownload: icon {{ "history_file_download", msgOutBg }}; -historyFileOutDownloadSelected: icon {{ "history_file_download", msgOutBgSelected }}; -historyFileInDownload: icon {{ "history_file_download", msgInBg }}; -historyFileInDownloadSelected: icon {{ "history_file_download", msgInBgSelected }}; -historyFileOutCancel: icon {{ "history_file_cancel", msgOutBg }}; -historyFileOutCancelSelected: icon {{ "history_file_cancel", msgOutBgSelected }}; -historyFileInCancel: icon {{ "history_file_cancel", msgInBg }}; -historyFileInCancelSelected: icon {{ "history_file_cancel", msgInBgSelected }}; -historyFileOutPause: icon {{ "history_file_pause", msgOutBg }}; -historyFileOutPauseSelected: icon {{ "history_file_pause", msgOutBgSelected }}; -historyFileInPause: icon {{ "history_file_pause", msgInBg }}; -historyFileInPauseSelected: icon {{ "history_file_pause", msgInBgSelected }}; -historyFileOutPlay: icon {{ "history_file_play", msgOutBg }}; -historyFileOutPlaySelected: icon {{ "history_file_play", msgOutBgSelected }}; -historyFileInPlay: icon {{ "history_file_play", msgInBg }}; -historyFileInPlaySelected: icon {{ "history_file_play", msgInBgSelected }}; +historyFileOutImage: icon {{ "history_file_image", historyFileOutIconFg }}; +historyFileOutImageSelected: icon {{ "history_file_image", historyFileOutIconFgSelected }}; +historyFileInImage: icon {{ "history_file_image", historyFileInIconFg }}; +historyFileInImageSelected: icon {{ "history_file_image", historyFileInIconFgSelected }}; +historyFileOutDocument: icon {{ "history_file_document", historyFileOutIconFg }}; +historyFileOutDocumentSelected: icon {{ "history_file_document", historyFileOutIconFgSelected }}; +historyFileInDocument: icon {{ "history_file_document", historyFileInIconFg }}; +historyFileInDocumentSelected: icon {{ "history_file_document", historyFileInIconFgSelected }}; +historyFileOutDownload: icon {{ "history_file_download", historyFileOutIconFg }}; +historyFileOutDownloadSelected: icon {{ "history_file_download", historyFileOutIconFgSelected }}; +historyFileInDownload: icon {{ "history_file_download", historyFileInIconFg }}; +historyFileInDownloadSelected: icon {{ "history_file_download", historyFileInIconFgSelected }}; +historyFileOutCancel: icon {{ "history_file_cancel", historyFileOutIconFg }}; +historyFileOutCancelSelected: icon {{ "history_file_cancel", historyFileOutIconFgSelected }}; +historyFileInCancel: icon {{ "history_file_cancel", historyFileInIconFg }}; +historyFileInCancelSelected: icon {{ "history_file_cancel", historyFileInIconFgSelected }}; +historyFileOutPause: icon {{ "history_file_pause", historyFileOutIconFg }}; +historyFileOutPauseSelected: icon {{ "history_file_pause", historyFileOutIconFgSelected }}; +historyFileInPause: icon {{ "history_file_pause", historyFileInIconFg }}; +historyFileInPauseSelected: icon {{ "history_file_pause", historyFileInIconFgSelected }}; +historyFileOutPlay: icon {{ "history_file_play", historyFileOutIconFg }}; +historyFileOutPlaySelected: icon {{ "history_file_play", historyFileOutIconFgSelected }}; +historyFileInPlay: icon {{ "history_file_play", historyFileInIconFg }}; +historyFileInPlaySelected: icon {{ "history_file_play", historyFileInIconFgSelected }}; + +historyFileThumbDownload: icon {{ "history_file_download", historyFileThumbIconFg }}; +historyFileThumbDownloadSelected: icon {{ "history_file_download", historyFileThumbIconFgSelected }}; +historyFileThumbCancel: icon {{ "history_file_cancel", historyFileThumbIconFg }}; +historyFileThumbCancelSelected: icon {{ "history_file_cancel", historyFileThumbIconFgSelected }}; +historyFileThumbPlay: icon {{ "history_file_play", historyFileThumbIconFg }}; +historyFileThumbPlaySelected: icon {{ "history_file_play", historyFileThumbIconFgSelected }}; -historyOutFg: dialogsSentStateFg; -historyOutSelectedFg: #4da79f; -historyInvertedFg: #ffffff; historySendStateSpace: 24px; historySendStatePosition: point(-17px, -19px); -historySentIcon: icon {{ "history_sent", historyOutFg, point(2px, 4px) }}; -historySentSelectedIcon: icon {{ "history_sent", historyOutSelectedFg, point(2px, 4px) }}; -historySentInvertedIcon: icon {{ "history_sent", historyInvertedFg, point(2px, 4px) }}; -historyReceivedIcon: icon {{ "history_received", historyOutFg, point(2px, 4px) }}; -historyReceivedSelectedIcon: icon {{ "history_received", historyOutSelectedFg, point(2px, 4px) }}; -historyReceivedInvertedIcon: icon {{ "history_received", historyInvertedFg, point(2px, 4px) }}; +historySentIcon: icon {{ "history_sent", historyOutIconFg, point(2px, 4px) }}; +historySentSelectedIcon: icon {{ "history_sent", historyOutIconFgSelected, point(2px, 4px) }}; +historySentInvertedIcon: icon {{ "history_sent", historyIconFgInverted, point(2px, 4px) }}; +historyReceivedIcon: icon {{ "history_received", historyOutIconFg, point(2px, 4px) }}; +historyReceivedSelectedIcon: icon {{ "history_received", historyOutIconFgSelected, point(2px, 4px) }}; +historyReceivedInvertedIcon: icon {{ "history_received", historyIconFgInverted, point(2px, 4px) }}; historyViewsSpace: 11px; historyViewsWidth: 20px; historyViewsTop: -15px; -historyViewsInIcon: icon {{ "history_views", #a0acb6 }}; -historyViewsInSelectedIcon: icon {{ "history_views", #6a9bc5 }}; -historyViewsOutIcon: icon {{ "history_views", historyOutFg }}; -historyViewsOutSelectedIcon: icon {{ "history_views", historyOutSelectedFg }}; -historyViewsInvertedIcon: icon {{ "history_views", #ffffffe6 }}; +historyViewsInIcon: icon {{ "history_views", msgInDateFg }}; +historyViewsInSelectedIcon: icon {{ "history_views", msgInDateFgSelected }}; +historyViewsOutIcon: icon {{ "history_views", historyOutIconFg }}; +historyViewsOutSelectedIcon: icon {{ "history_views", historyOutIconFgSelected }}; +historyViewsInvertedIcon: icon {{ "history_views", historySendingInvertedIconFg }}; -historyPeer1NameFg: #c03d33; // red -historyPeer1UserpicBg: #ed9482; -historyPeer1UserpicFg: #d3644b; -historyPeer1UserpicPerson: icon {{ size(120px, 120px), historyPeer1UserpicBg }, { "userpic_person", historyPeer1UserpicFg }}; -historyPeer1UserpicChat: icon {{ size(120px, 120px), historyPeer1UserpicBg }, { "userpic_chat", historyPeer1UserpicFg }}; -historyPeer1UserpicChannel: icon {{ size(120px, 120px), historyPeer1UserpicBg }, { "userpic_channel", historyPeer1UserpicFg }}; -historyPeer2NameFg: #4fad2d; // green -historyPeer2UserpicBg: #a8db92; -historyPeer2UserpicFg: #75c057; -historyPeer2UserpicPerson: icon {{ size(120px, 120px), historyPeer2UserpicBg }, { "userpic_person", historyPeer2UserpicFg }}; -historyPeer2UserpicChat: icon {{ size(120px, 120px), historyPeer2UserpicBg }, { "userpic_chat", historyPeer2UserpicFg }}; -historyPeer2UserpicChannel: icon {{ size(120px, 120px), historyPeer2UserpicBg }, { "userpic_channel", historyPeer2UserpicFg }}; -historyPeer3NameFg: #d09306; // yellow -historyPeer3UserpicBg: #efd289; -historyPeer3UserpicFg: #e4a861; -historyPeer3UserpicPerson: icon {{ size(120px, 120px), historyPeer3UserpicBg }, { "userpic_person", historyPeer3UserpicFg }}; -historyPeer3UserpicChat: icon {{ size(120px, 120px), historyPeer3UserpicBg }, { "userpic_chat", historyPeer3UserpicFg }}; -historyPeer3UserpicChannel: icon {{ size(120px, 120px), historyPeer3UserpicBg }, { "userpic_channel", historyPeer3UserpicFg }}; -historyPeer4NameFg: #348cd4; // blue -historyPeer4UserpicBg: #8fbfe9; -historyPeer4UserpicFg: #649fd3; -historyPeer4UserpicPerson: icon {{ size(120px, 120px), historyPeer4UserpicBg }, { "userpic_person", historyPeer4UserpicFg }}; -historyPeer4UserpicChat: icon {{ size(120px, 120px), historyPeer4UserpicBg }, { "userpic_chat", historyPeer4UserpicFg }}; -historyPeer4UserpicChannel: icon {{ size(120px, 120px), historyPeer4UserpicBg }, { "userpic_channel", historyPeer4UserpicFg }}; -historyPeer5NameFg: #8544d6; // purple -historyPeer5UserpicBg: #9992e4; -historyPeer5UserpicFg: #7b72cf; -historyPeer5UserpicPerson: icon {{ size(120px, 120px), historyPeer5UserpicBg }, { "userpic_person", historyPeer5UserpicFg }}; -historyPeer6NameFg: #cd4073; // pink -historyPeer6UserpicBg: #ffa9c3; -historyPeer6UserpicFg: #e87497; -historyPeer6UserpicPerson: icon {{ size(120px, 120px), historyPeer6UserpicBg }, { "userpic_person", historyPeer6UserpicFg }}; -historyPeer7NameFg: #2996ad; // sea -historyPeer7UserpicBg: #8eccdb; -historyPeer7UserpicFg: #5eb2c7; -historyPeer7UserpicPerson: icon {{ size(120px, 120px), historyPeer7UserpicBg }, { "userpic_person", historyPeer7UserpicFg }}; -historyPeer8NameFg: #ce671b; // orange -historyPeer8UserpicBg: #f7b37c; -historyPeer8UserpicFg: #de8d62; -historyPeer8UserpicPerson: icon {{ size(120px, 120px), historyPeer8UserpicBg }, { "userpic_person", historyPeer8UserpicFg }}; +historyComposeField: FlatTextarea { + textColor: historyComposeAreaFg; + bgColor: historyComposeAreaBg; + align: align(left); + textMrg: margins(5px, 5px, 5px, 5px); + font: msgFont; + + phColor: placeholderFg; + phFocusColor: placeholderFgActive; + phAlign: align(topleft); + phPos: point(2px, 0px); + phShift: 50px; + phDuration: 100; +} +historyComposeFieldMaxHeight: 224px; +// historyMinHeight: 56px; + +historySendPadding: 9px; +historySendRight: 2px; + +historyComposeButton: FlatButton { + color: windowActiveTextFg; + overColor: windowActiveTextFg; + + bgColor: historyComposeButtonBg; + overBgColor: historyComposeButtonBgOver; + + width: -32px; + height: 46px; + + textTop: 14px; + + font: semiboldFont; + overFont: semiboldFont; + + ripple: RippleAnimation(defaultRippleAnimation) { + color: historyComposeButtonBgRipple; + } +} +historyUnblock: FlatButton(historyComposeButton) { + color: attentionButtonFg; + overColor: attentionButtonFgOver; +} + +historySendIcon: icon {{ "send_control_send", historySendIconFg }}; +historySendIconOver: icon {{ "send_control_send", historySendIconFgOver }}; +historySendIconPosition: point(11px, 11px); +historySendSize: size(46px, 46px); +historyEditSaveIcon: icon {{ "send_control_save", historySendIconFg, point(3px, 7px) }}; +historyEditSaveIconOver: icon {{ "send_control_save", historySendIconFgOver, point(3px, 7px) }}; + +historyAttach: IconButton { + width: 46px; + height: 46px; + + icon: icon {{ "send_control_attach", historyComposeIconFg }}; + iconOver: icon {{ "send_control_attach", historyComposeIconFgOver }}; + iconPosition: point(11px, 11px); + + rippleAreaPosition: point(3px, 3px); + rippleAreaSize: 40px; + ripple: RippleAnimation(defaultRippleAnimation) { + color: windowBgOver; + } +} + +historyAttachEmoji: IconButton(historyAttach) { + icon: icon {{ "send_control_emoji", historyComposeIconFg }}; + iconOver: icon {{ "send_control_emoji", historyComposeIconFgOver }}; + iconPosition: point(15px, 15px); +} +historyEmojiCircle: size(20px, 20px); +historyEmojiCirclePeriod: 1500; +historyEmojiCircleDuration: 500; +historyEmojiCircleTop: 13px; +historyEmojiCircleLine: 2px; +historyEmojiCircleFg: historyComposeIconFg; +historyEmojiCircleFgOver: historyComposeIconFgOver; +historyEmojiCirclePart: 3.5; +historyBotKeyboardShow: IconButton(historyAttach) { + icon: icon {{ "send_control_bot_keyboard", historyComposeIconFg }}; + iconOver: icon {{ "send_control_bot_keyboard", historyComposeIconFgOver }}; +} +historyBotKeyboardHide: IconButton(historyAttach) { + icon: icon {{ "send_control_bot_keyboard_hide", historyComposeIconFg }}; + iconOver: icon {{ "send_control_bot_keyboard_hide", historyComposeIconFgOver }}; + iconPosition: point(11px, 16px); +} +historyBotCommandStart: IconButton(historyAttach) { + icon: icon {{ "send_control_bot_command", historyComposeIconFg }}; + iconOver: icon {{ "send_control_bot_command", historyComposeIconFgOver }}; +} +historyRecordVoiceFg: historyComposeIconFg; +historyRecordVoiceFgOver: historyComposeIconFgOver; +historyRecordVoiceFgActive: windowBgActive; +historyRecordVoiceDuration: 120; +historyRecordVoice: icon {{ "send_control_record", historyRecordVoiceFg }}; +historyRecordVoiceOver: icon {{ "send_control_record", historyRecordVoiceFgOver }}; +historyRecordVoiceActive: icon {{ "send_control_record", historyRecordVoiceFgActive }}; +historyRecordVoiceRippleBgActive: lightButtonBgOver; +historyRecordSignalColor: attentionButtonFg; +historyRecordSignalMin: 5px; +historyRecordSignalMax: 12px; +historyRecordCancel: windowSubTextFg; +historyRecordCancelActive: windowActiveTextFg; +historyRecordFont: font(13px); +historyRecordDurationFg: historyComposeAreaFg; +historyRecordTextTop: 14px; + +historySilentToggle: IconButton(historyBotKeyboardShow) { + icon: icon {{ "send_control_silent_off", historyComposeIconFg }}; + iconOver: icon {{ "send_control_silent_off", historyComposeIconFgOver }}; +} +historySilentToggleOn: icon {{ "send_control_silent_on", historyComposeIconFg }}; +historySilentToggleOnOver: icon {{ "send_control_silent_on", historyComposeIconFgOver }}; + +historyReplySkip: 51px; +historyReplyNameFg: windowActiveTextFg; +historyReplyHeight: 49px; +historyReplyTop: 8px; +historyReplyBottom: 6px; +historyReplyIconPosition: point(13px, 13px); +historyReplyIcon: icon {{ "history_action_reply", historyReplyIconFg }}; +historyForwardIcon: icon {{ "history_action_forward", historyReplyIconFg }}; +historyEditIcon: icon {{ "history_action_edit", historyReplyIconFg }}; +historyReplyCancel: IconButton { + width: 49px; + height: 49px; + + icon: historyReplyCancelIcon; + iconOver: historyReplyCancelIconOver; + iconPosition: point(-1px, -1px); + + rippleAreaPosition: point(4px, 4px); + rippleAreaSize: 40px; + ripple: RippleAnimation(defaultRippleAnimation) { + color: windowBgOver; + } +} + +reportSpamHide: FlatButton { + color: windowActiveTextFg; + overColor: windowActiveTextFg; + + bgColor: transparent; + overBgColor: transparent; + + width: -40px; + height: 46px; + + textTop: 15px; + + font: font(fsize); + overFont: font(fsize underline); +} +reportSpamSeparator: 30px; + +msgBotKbDuration: 200; +msgBotKbFont: semiboldFont; +msgBotKbIconPadding: 2px; +msgBotKbUrlIcon: icon {{ "inline_button_url", msgBotKbIconFg }}; +msgBotKbSwitchPmIcon: icon {{ "inline_button_switch", msgBotKbIconFg }}; +msgBotKbButton: BotKeyboardButton { + margin: 5px; + padding: 10px; + height: 36px; + textTop: 8px; + ripple: RippleAnimation(defaultRippleAnimation) { + color: msgBotKbRippleBg; + } +} + +botKbDuration: 200; +botKbBg: menuBgOver; +botKbOverBg: menuBgOver; +botKbDownBg: menuBgRipple; +botKbColor: windowBoldFgOver; +botKbStyle: TextStyle(defaultTextStyle) { + font: font(15px semibold); + linkFont: font(15px semibold); + linkFontOver: font(15px semibold); +} +botKbButton: BotKeyboardButton { + margin: 10px; + padding: 10px; + height: 38px; + textTop: 9px; + ripple: defaultRippleAnimation; +} +botKbTinyButton: BotKeyboardButton { + margin: 4px; + padding: 3px; + height: 25px; + textTop: 2px; + ripple: defaultRippleAnimation; +} +botKbScroll: defaultSolidScroll; + +mentionHeight: 40px; +mentionScroll: ScrollArea(defaultScrollArea) { + topsh: 0px; + bottomsh: 0px; +} +mentionPadding: margins(8px, 5px, 8px, 5px); +mentionTop: 11px; +mentionFont: linkFont; +mentionNameFg: windowFg; +mentionNameFgOver: windowFgOver; +mentionPhotoSize: msgPhotoSize; +mentionBg: windowBg; +mentionBgOver: windowBgOver; +mentionFg: windowSubTextFg; +mentionFgOver: windowSubTextFgOver; +mentionFgActive: windowActiveTextFg; +mentionFgOverActive: windowActiveTextFg; + +historyDateFadeDuration: 200; + +historyPhotoLeft: 14px; +historyMessageRadius: 6px; +historyBubbleTailInLeft: icon {{ "bubble_tail", msgInBg }}; +historyBubbleTailInLeftSelected: icon {{ "bubble_tail", msgInBgSelected }}; +historyBubbleTailOutLeft: icon {{ "bubble_tail", msgOutBg }}; +historyBubbleTailOutLeftSelected: icon {{ "bubble_tail", msgOutBgSelected }}; +historyBubbleTailInRight: icon {{ "bubble_tail-flip_horizontal", msgInBg }}; +historyBubbleTailInRightSelected: icon {{ "bubble_tail-flip_horizontal", msgInBgSelected }}; +historyBubbleTailOutRight: icon {{ "bubble_tail-flip_horizontal", msgOutBg }}; +historyBubbleTailOutRightSelected: icon {{ "bubble_tail-flip_horizontal", msgOutBgSelected }}; + +historyPeerUserpicFont: semiboldFont; + +historyStatusFg: windowSubTextFg; +historyStatusFgActive: windowActiveTextFg; +historyStatusFgTyping: historyStatusFgActive; + +historyUnreadBarHeight: 32px; +historyUnreadBarMargin: 8px; +historyUnreadBarFont: semiboldFont; + +historyForwardChooseMargins: margins(30px, 10px, 30px, 10px); +historyForwardChooseFont: font(16px); + +msgFileMenuSize: size(36px, 36px); +msgFileSize: 44px; +msgFilePadding: margins(14px, 12px, 11px, 12px); +msgFileThumbSize: 72px; +msgFileThumbPadding: margins(10px, 10px, 14px, 10px); +msgFileThumbNameTop: 12px; +msgFileThumbStatusTop: 32px; +msgFileThumbLinkTop: 60px; +msgFileNameTop: 16px; +msgFileStatusTop: 37px; +msgFileMinWidth: 294px; +msgFileTopMinus: 6px; + +msgFileOverDuration: 200; +msgFileRadialLine: 3px; + +msgVideoSize: size(320px, 240px); + +msgWaveformBar: 2px; +msgWaveformSkip: 1px; +msgWaveformMin: 2px; +msgWaveformMax: 20px; diff --git a/Telegram/SourceFiles/history/history_drag_area.cpp b/Telegram/SourceFiles/history/history_drag_area.cpp new file mode 100644 index 000000000..19b81c565 --- /dev/null +++ b/Telegram/SourceFiles/history/history_drag_area.cpp @@ -0,0 +1,179 @@ +/* +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 "history/history_drag_area.h" + +#include "styles/style_stickers.h" +#include "styles/style_boxes.h" +#include "boxes/confirmbox.h" +#include "boxes/stickersetbox.h" +#include "inline_bots/inline_bot_result.h" +#include "inline_bots/inline_bot_layout_item.h" +#include "dialogs/dialogs_layout.h" +#include "historywidget.h" +#include "localstorage.h" +#include "lang.h" +#include "mainwindow.h" +#include "apiwrap.h" +#include "mainwidget.h" + +DragArea::DragArea(QWidget *parent) : TWidget(parent) { + setMouseTracking(true); + setAcceptDrops(true); +} + +bool DragArea::overlaps(const QRect &globalRect) { + if (isHidden() || _a_opacity.animating()) { + return false; + } + + auto inner = innerRect(); + auto testRect = QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()); + return inner.marginsRemoved(QMargins(st::boxRadius, 0, st::boxRadius, 0)).contains(testRect) + || inner.marginsRemoved(QMargins(0, st::boxRadius, 0, st::boxRadius)).contains(testRect); +} + + +void DragArea::mouseMoveEvent(QMouseEvent *e) { + if (_hiding) return; + + auto in = QRect(st::dragPadding.left(), st::dragPadding.top(), width() - st::dragPadding.left() - st::dragPadding.right(), height() - st::dragPadding.top() - st::dragPadding.bottom()).contains(e->pos()); + setIn(in); +} + +void DragArea::dragMoveEvent(QDragMoveEvent *e) { + QRect r(st::dragPadding.left(), st::dragPadding.top(), width() - st::dragPadding.left() - st::dragPadding.right(), height() - st::dragPadding.top() - st::dragPadding.bottom()); + setIn(r.contains(e->pos())); + e->setDropAction(_in ? Qt::CopyAction : Qt::IgnoreAction); + e->accept(); +} + +void DragArea::setIn(bool in) { + if (_in != in) { + _in = in; + _a_in.start([this] { update(); }, _in ? 0. : 1., _in ? 1. : 0., st::boxDuration); + } +} + +void DragArea::setText(const QString &text, const QString &subtext) { + _text = text; + _subtext = subtext; + update(); +} + +void DragArea::paintEvent(QPaintEvent *e) { + Painter p(this); + + auto ms = getms(); + auto opacity = _a_opacity.current(ms, _hiding ? 0. : 1.); + if (!_a_opacity.animating() && _hiding) { + return; + } + p.setOpacity(opacity); + auto inner = innerRect(); + + if (!_cache.isNull()) { + p.drawPixmapLeft(inner.x() - st::boxRoundShadow.extend.left(), inner.y() - st::boxRoundShadow.extend.top(), width(), _cache); + return; + } + + Ui::Shadow::paint(p, inner, width(), st::boxRoundShadow); + App::roundRect(p, inner, st::boxBg, BoxCorners); + + p.setPen(anim::pen(st::dragColor, st::dragDropColor, _a_in.current(ms, _in ? 1. : 0.))); + + p.setFont(st::dragFont); + p.drawText(QRect(0, (height() - st::dragHeight) / 2, width(), st::dragFont->height), _text, QTextOption(style::al_top)); + + p.setFont(st::dragSubfont); + p.drawText(QRect(0, (height() + st::dragHeight) / 2 - st::dragSubfont->height, width(), st::dragSubfont->height * 2), _subtext, QTextOption(style::al_top)); +} + +void DragArea::dragEnterEvent(QDragEnterEvent *e) { + static_cast(parentWidget())->dragEnterEvent(e); + e->setDropAction(Qt::IgnoreAction); + e->accept(); +} + +void DragArea::dragLeaveEvent(QDragLeaveEvent *e) { + static_cast(parentWidget())->dragLeaveEvent(e); + setIn(false); +} + +void DragArea::dropEvent(QDropEvent *e) { + static_cast(parentWidget())->dropEvent(e); + if (e->isAccepted() && _droppedCallback) { + _droppedCallback(e->mimeData()); + } +} + +void DragArea::otherEnter() { + showStart(); +} + +void DragArea::otherLeave() { + hideStart(); +} + +void DragArea::hideFast() { + _a_opacity.finish(); + hide(); +} + +void DragArea::hideStart() { + if (_hiding || isHidden()) { + return; + } + if (_cache.isNull()) { + _cache = myGrab(this, innerRect().marginsAdded(st::boxRoundShadow.extend)); + } + _hiding = true; + setIn(false); + _a_opacity.start([this] { opacityAnimationCallback(); }, 1., 0., st::boxDuration); +} + +void DragArea::hideFinish() { + hide(); + _in = false; + _a_in.finish(); +} + +void DragArea::showStart() { + if (!_hiding && !isHidden()) { + return; + } + _hiding = false; + if (_cache.isNull()) { + _cache = myGrab(this, innerRect().marginsAdded(st::boxRoundShadow.extend)); + } + show(); + _a_opacity.start([this] { opacityAnimationCallback(); }, 0., 1., st::boxDuration); +} + +void DragArea::opacityAnimationCallback() { + update(); + if (!_a_opacity.animating()) { + _cache = QPixmap(); + if (_hiding) { + hideFinish(); + } + } +} diff --git a/Telegram/SourceFiles/history/history_drag_area.h b/Telegram/SourceFiles/history/history_drag_area.h new file mode 100644 index 000000000..b5b6e05ec --- /dev/null +++ b/Telegram/SourceFiles/history/history_drag_area.h @@ -0,0 +1,80 @@ +/* +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" + +class DragArea : public TWidget { + Q_OBJECT + +public: + DragArea(QWidget *parent); + + void setText(const QString &text, const QString &subtext); + + void otherEnter(); + void otherLeave(); + + bool overlaps(const QRect &globalRect); + + void hideFast(); + + void setDroppedCallback(base::lambda &&callback) { + _droppedCallback = std_::move(callback); + } + +protected: + void paintEvent(QPaintEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void dragEnterEvent(QDragEnterEvent *e) override; + void dragLeaveEvent(QDragLeaveEvent *e) override; + void dropEvent(QDropEvent *e) override; + void dragMoveEvent(QDragMoveEvent *e) override; + +public slots: + void hideStart(); + void hideFinish(); + + void showStart(); + +private: + void setIn(bool in); + void opacityAnimationCallback(); + QRect innerRect() const { + return QRect( + st::dragPadding.left(), + st::dragPadding.top(), + width() - st::dragPadding.left() - st::dragPadding.right(), + height() - st::dragPadding.top() - st::dragPadding.bottom() + ); + } + + bool _hiding = false; + bool _in = false; + QPixmap _cache; + base::lambda _droppedCallback; + + Animation _a_opacity; + Animation _a_in; + + QString _text, _subtext; + +}; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index e3adc56e4..ed487cd52 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -26,6 +26,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "history/history_service_layout.h" #include "media/media_clip_reader.h" #include "styles/style_dialogs.h" +#include "styles/style_history.h" +#include "ui/effects/ripple_animation.h" #include "fileuploader.h" namespace { @@ -97,7 +99,7 @@ QString ReplyMarkupClickHandler::buttonText() const { ReplyKeyboard::ReplyKeyboard(const HistoryItem *item, StylePtr &&s) : _item(item) , _a_selected(animation(this, &ReplyKeyboard::step_selected)) - , _st(std_::forward(s)) { + , _st(std_::move(s)) { if (auto markup = item->Get()) { _rows.reserve(markup->rows.size()); for (int i = 0, l = markup->rows.size(); i != l; ++i) { @@ -109,7 +111,7 @@ ReplyKeyboard::ReplyKeyboard(const HistoryItem *item, StylePtr &&s) auto str = row.at(j).text; button.type = row.at(j).type; button.link = MakeShared(item, i, j); - button.text.setText(_st->textFont(), textOneLine(str), _textPlainOptions); + button.text.setText(_st->textStyle(), textOneLine(str), _textPlainOptions); button.characters = str.isEmpty() ? 1 : str.size(); } _rows.push_back(newRow); @@ -176,7 +178,7 @@ void ReplyKeyboard::resize(int width, int height) { } } -bool ReplyKeyboard::isEnoughSpace(int width, const style::botKeyboardButton &st) const { +bool ReplyKeyboard::isEnoughSpace(int width, const style::BotKeyboardButton &st) const { for_const (auto &row, _rows) { int s = row.size(); int widthLeft = width - ((s - 1) * st.margin + s * 2 * st.padding); @@ -220,7 +222,7 @@ int ReplyKeyboard::naturalHeight() const { return (_rows.size() - 1) * _st->buttonSkip() + _rows.size() * _st->buttonHeight(); } -void ReplyKeyboard::paint(Painter &p, int outerWidth, const QRect &clip) const { +void ReplyKeyboard::paint(Painter &p, int outerWidth, const QRect &clip, TimeMs ms) const { t_assert(_st != nullptr); t_assert(_width > 0); @@ -234,7 +236,7 @@ void ReplyKeyboard::paint(Painter &p, int outerWidth, const QRect &clip) const { // just ignore the buttons that didn't layout well if (rect.x() + rect.width() > _width) break; - _st->paintButton(p, outerWidth, button); + _st->paintButton(p, outerWidth, button, ms); } } } @@ -250,6 +252,7 @@ ClickHandlerPtr ReplyKeyboard::getState(int x, int y) const { if (rect.x() + rect.width() > _width) break; if (rect.contains(x, y)) { + _savedCoords = QPoint(x, y); return button.link; } } @@ -260,37 +263,66 @@ ClickHandlerPtr ReplyKeyboard::getState(int x, int y) const { void ReplyKeyboard::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { if (!p) return; - bool startAnimation = false; + _savedActive = active ? p : ClickHandlerPtr(); + auto coords = findButtonCoordsByClickHandler(p); + if (coords.i >= 0 && _savedPressed != p) { + startAnimation(coords.i, coords.j, active ? 1 : -1); + } +} + + +ReplyKeyboard::ButtonCoords ReplyKeyboard::findButtonCoordsByClickHandler(const ClickHandlerPtr &p) { for (int i = 0, rows = _rows.size(); i != rows; ++i) { - auto &row = _rows.at(i); + auto &row = _rows[i]; for (int j = 0, cols = row.size(); j != cols; ++j) { - if (row.at(j).link == p) { - bool startAnimation = _animations.isEmpty(); + if (row[j].link == p) { + return { i, j }; + } + } + } + return { -1, -1 }; +} - int indexForAnimation = i * MatrixRowShift + j + 1; - if (!active) { - indexForAnimation = -indexForAnimation; - } +void ReplyKeyboard::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { + if (!p) return; - _animations.remove(-indexForAnimation); - if (!_animations.contains(indexForAnimation)) { - _animations.insert(indexForAnimation, getms()); - } - - if (startAnimation && !_a_selected.animating()) { - _a_selected.start(); - } - return; + _savedPressed = pressed ? p : ClickHandlerPtr(); + auto coords = findButtonCoordsByClickHandler(p); + if (coords.i >= 0) { + auto &button = _rows[coords.i][coords.j]; + if (pressed) { + if (!button.ripple) { + auto mask = Ui::RippleAnimation::roundRectMask(button.rect.size(), _st->buttonRadius()); + button.ripple = MakeShared(_st->_st->ripple, std_::move(mask), [this] { _st->repaint(_item); }); + } + button.ripple->add(_savedCoords - button.rect.topLeft()); + } else { + if (button.ripple) { + button.ripple->lastStop(); + } + if (_savedActive != p) { + startAnimation(coords.i, coords.j, -1); } } } } -void ReplyKeyboard::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { - _st->repaint(_item); +void ReplyKeyboard::startAnimation(int i, int j, int direction) { + auto notStarted = _animations.isEmpty(); + + int indexForAnimation = (i * MatrixRowShift + j + 1) * direction; + + _animations.remove(-indexForAnimation); + if (!_animations.contains(indexForAnimation)) { + _animations.insert(indexForAnimation, getms()); + } + + if (notStarted && !_a_selected.animating()) { + _a_selected.start(); + } } -void ReplyKeyboard::step_selected(uint64 ms, bool timer) { +void ReplyKeyboard::step_selected(TimeMs ms, bool timer) { for (Animations::iterator i = _animations.begin(); i != _animations.end();) { int index = qAbs(i.key()) - 1, row = (index / MatrixRowShift), col = index % MatrixRowShift; float64 dt = float64(ms - i.value()) / st::botKbDuration; @@ -317,11 +349,27 @@ void ReplyKeyboard::clearSelection() { _a_selected.stop(); } -void ReplyKeyboard::Style::paintButton(Painter &p, int outerWidth, const ReplyKeyboard::Button &button) const { - const QRect &rect = button.rect; - bool pressed = ClickHandler::showAsPressed(button.link); +int ReplyKeyboard::Style::buttonSkip() const { + return _st->margin; +} - paintButtonBg(p, rect, pressed, button.howMuchOver); +int ReplyKeyboard::Style::buttonPadding() const { + return _st->padding; +} + +int ReplyKeyboard::Style::buttonHeight() const { + return _st->height; +} + +void ReplyKeyboard::Style::paintButton(Painter &p, int outerWidth, const ReplyKeyboard::Button &button, TimeMs ms) const { + const QRect &rect = button.rect; + paintButtonBg(p, rect, button.howMuchOver); + if (button.ripple) { + button.ripple->paint(p, rect.x(), rect.y(), outerWidth, ms); + if (button.ripple->empty()) { + button.ripple.reset(); + } + } paintButtonIcon(p, rect, outerWidth, button.type); if (button.type == HistoryMessageReplyMarkup::Button::Type::Callback || button.type == HistoryMessageReplyMarkup::Button::Type::Game) { @@ -333,15 +381,14 @@ void ReplyKeyboard::Style::paintButton(Painter &p, int outerWidth, const ReplyKe } int tx = rect.x(), tw = rect.width(); - if (tw >= st::botKbFont->elidew + _st->padding * 2) { + if (tw >= st::botKbStyle.font->elidew + _st->padding * 2) { tx += _st->padding; tw -= _st->padding * 2; - } else if (tw > st::botKbFont->elidew) { - tx += (tw - st::botKbFont->elidew) / 2; - tw = st::botKbFont->elidew; + } else if (tw > st::botKbStyle.font->elidew) { + tx += (tw - st::botKbStyle.font->elidew) / 2; + tw = st::botKbStyle.font->elidew; } - int textTop = rect.y() + (pressed ? _st->downTextTop : _st->textTop); - button.text.drawElided(p, tx, textTop + ((rect.height() - _st->height) / 2), tw, 1, style::al_top); + button.text.drawElided(p, tx, rect.y() + _st->textTop + ((rect.height() - _st->height) / 2), tw, 1, style::al_top); } void HistoryMessageReplyMarkup::createFromButtonRows(const QVector &v) { @@ -455,18 +502,18 @@ void HistoryMessageUnreadBar::init(int count) { } int HistoryMessageUnreadBar::height() { - return st::unreadBarHeight + st::unreadBarMargin; + return st::historyUnreadBarHeight + st::historyUnreadBarMargin; } int HistoryMessageUnreadBar::marginTop() { - return st::lineWidth + st::unreadBarMargin; + return st::lineWidth + st::historyUnreadBarMargin; } void HistoryMessageUnreadBar::paint(Painter &p, int y, int w) const { - p.fillRect(0, y + marginTop(), w, height() - marginTop() - st::lineWidth, st::unreadBarBG); - p.fillRect(0, y + height() - st::lineWidth, w, st::lineWidth, st::unreadBarBorder); - p.setFont(st::unreadBarFont); - p.setPen(st::unreadBarColor); + p.fillRect(0, y + marginTop(), w, height() - marginTop() - st::lineWidth, st::historyUnreadBarBg); + p.fillRect(0, y + height() - st::lineWidth, w, st::lineWidth, st::historyUnreadBarBorder); + p.setFont(st::historyUnreadBarFont); + p.setPen(st::historyUnreadBarFg); int left = st::msgServiceMargin.left(); int maxwidth = w; @@ -475,7 +522,7 @@ void HistoryMessageUnreadBar::paint(Painter &p, int y, int w) const { } w = maxwidth; - p.drawText((w - _width) / 2, y + marginTop() + (st::unreadBarHeight - 2 * st::lineWidth - st::unreadBarFont->height) / 2 + st::unreadBarFont->ascent, _text); + p.drawText((w - _width) / 2, y + marginTop() + (st::historyUnreadBarHeight - 2 * st::lineWidth - st::historyUnreadBarFont->height) / 2 + st::historyUnreadBarFont->ascent, _text); } void HistoryMessageDate::init(const QDateTime &date) { @@ -537,7 +584,7 @@ void HistoryItem::finishCreate() { void HistoryItem::finishEdition(int oldKeyboardTop) { setPendingInitDimensions(); if (App::main()) { - App::main()->dlgUpdated(history(), id); + App::main()->dlgUpdated(history()->peer, id); } // invalidate cache for drawInDialog @@ -574,6 +621,9 @@ void HistoryItem::finishEditionToEmpty() { if (auto next = nextItem()) { next->previousItemChanged(); } + if (auto previous = previousItem()) { + previous->nextItemChanged(); + } } void HistoryItem::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { @@ -643,16 +693,22 @@ void HistoryItem::previousItemChanged() { recountAttachToPrevious(); } +// Called only if there is no more next item! Not always when it changes! +void HistoryItem::nextItemChanged() { + setAttachToNext(false); +} + void HistoryItem::recountAttachToPrevious() { bool attach = false; - if (!isPost() && !Has() && !Has()) { - if (auto previos = previousItem()) { - attach = !previos->isPost() - && !previos->serviceMsg() - && !previos->isEmpty() - && previos->from() == from() - && (qAbs(previos->date.secsTo(date)) < kAttachMessageToPreviousSecondsDelta); + if (auto previous = previousItem()) { + if (!Has() && !Has()) { + attach = !isPost() && !previous->isPost() + && !serviceMsg() && !previous->serviceMsg() + && !isEmpty() && !previous->isEmpty() + && previous->from() == from() + && (qAbs(previous->date.secsTo(date)) < kAttachMessageToPreviousSecondsDelta); } + previous->setAttachToNext(attach); } if (attach && !(_flags & MTPDmessage_ClientFlag::f_attach_to_previous)) { _flags |= MTPDmessage_ClientFlag::f_attach_to_previous; @@ -663,6 +719,16 @@ void HistoryItem::recountAttachToPrevious() { } } +void HistoryItem::setAttachToNext(bool attachToNext) { + if (attachToNext && !(_flags & MTPDmessage_ClientFlag::f_attach_to_next)) { + _flags |= MTPDmessage_ClientFlag::f_attach_to_next; + Global::RefPendingRepaintItems().insert(this); + } else if (!attachToNext && (_flags & MTPDmessage_ClientFlag::f_attach_to_next)) { + _flags &= ~MTPDmessage_ClientFlag::f_attach_to_next; + Global::RefPendingRepaintItems().insert(this); + } +} + void HistoryItem::setId(MsgId newId) { history()->changeMsgId(id, newId); id = newId; @@ -706,6 +772,18 @@ bool HistoryItem::canEdit(const QDateTime &cur) const { return false; } +bool HistoryItem::canDeleteForEveryone(const QDateTime &cur) const { + auto messageToMyself = (peerToUser(_history->peer->id) == MTP::authedId()); + auto messageTooOld = messageToMyself ? false : (date.secsTo(cur) >= Global::EditTimeLimit()); + if (id < 0 || messageToMyself || messageTooOld) return false; + if (history()->peer->isChannel()) return false; + + if (auto msg = toHistoryMessage()) { + return !isPost() && out(); + } + return false; +} + bool HistoryItem::unread() const { // Messages from myself are always read. if (history()->peer->isSelf()) return false; @@ -859,17 +937,17 @@ QString HistoryItem::inDialogsText() const { return plainText; } -void HistoryItem::drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const { +void HistoryItem::drawInDialog(Painter &p, const QRect &r, bool active, bool selected, const HistoryItem *&cacheFor, Text &cache) const { if (cacheFor != this) { cacheFor = this; - cache.setText(st::dialogsTextFont, inDialogsText(), _textDlgOptions); + cache.setText(st::dialogsTextStyle, inDialogsText(), _textDlgOptions); } if (r.width()) { - textstyleSet(&(act ? st::dialogsTextStyleActive : st::dialogsTextStyle)); + p.setTextPalette(active ? st::dialogsTextPaletteActive : (selected ? st::dialogsTextPaletteOver : st::dialogsTextPalette)); p.setFont(st::dialogsTextFont); - p.setPen(act ? st::dialogsTextFgActive : st::dialogsTextFg); + p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg)); cache.drawElided(p, r.left(), r.top(), r.width(), r.height() / st::dialogsTextFont->height); - textstyleRestore(); + p.restoreTextPalette(); } } @@ -880,12 +958,14 @@ HistoryItem::~HistoryItem() { } } -void GoToMessageClickHandler::onClickImpl() const { - if (App::main()) { - HistoryItem *current = App::mousedItem(); - if (current && current->history()->peer->id == peer()) { - App::main()->pushReplyReturn(current); +ClickHandlerPtr goToMessageClickHandler(PeerData *peer, MsgId msgId) { + return MakeShared([peer, msgId] { + if (App::main()) { + auto current = App::mousedItem(); + if (current && current->history()->peer == peer) { + App::main()->pushReplyReturn(current); + } + Ui::showPeerHistory(peer, msgId, Ui::ShowWay::Forward); } - Ui::showPeerHistory(peer(), msgid(), Ui::ShowWay::Forward); - } + }); } diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 0c61b2a87..6208e2e01 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -22,6 +22,15 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "core/runtime_composer.h" +namespace Ui { +class RippleAnimation; +} // namespace Ui + +namespace style { +struct BotKeyboardButton; +struct RippleAnimation; +} // namespace style + class HistoryElement { public: HistoryElement() = default; @@ -281,36 +290,31 @@ private: public: class Style { public: - Style(const style::botKeyboardButton &st) : _st(&st) { + Style(const style::BotKeyboardButton &st) : _st(&st) { } virtual void startPaint(Painter &p) const = 0; - virtual style::font textFont() const = 0; + virtual const style::TextStyle &textStyle() const = 0; - int buttonSkip() const { - return _st->margin; - } - int buttonPadding() const { - return _st->padding; - } - int buttonHeight() const { - return _st->height; - } + int buttonSkip() const; + int buttonPadding() const; + int buttonHeight() const; + virtual int buttonRadius() const = 0; virtual void repaint(const HistoryItem *item) const = 0; virtual ~Style() { } protected: - virtual void paintButtonBg(Painter &p, const QRect &rect, bool pressed, float64 howMuchOver) const = 0; + virtual void paintButtonBg(Painter &p, const QRect &rect, float64 howMuchOver) const = 0; virtual void paintButtonIcon(Painter &p, const QRect &rect, int outerWidth, HistoryMessageReplyMarkup::Button::Type type) const = 0; virtual void paintButtonLoading(Painter &p, const QRect &rect) const = 0; virtual int minButtonWidth(HistoryMessageReplyMarkup::Button::Type type) const = 0; private: - const style::botKeyboardButton *_st; + const style::BotKeyboardButton *_st; - void paintButton(Painter &p, int outerWidth, const ReplyKeyboard::Button &button) const; + void paintButton(Painter &p, int outerWidth, const ReplyKeyboard::Button &button, TimeMs ms) const; friend class ReplyKeyboard; }; @@ -320,7 +324,7 @@ public: ReplyKeyboard(const ReplyKeyboard &other) = delete; ReplyKeyboard &operator=(const ReplyKeyboard &other) = delete; - bool isEnoughSpace(int width, const style::botKeyboardButton &st) const; + bool isEnoughSpace(int width, const style::BotKeyboardButton &st) const; void setStyle(StylePtr &&s); void resize(int width, int height); @@ -328,7 +332,7 @@ public: int naturalWidth() const; int naturalHeight() const; - void paint(Painter &p, int outerWidth, const QRect &clip) const; + void paint(Painter &p, int outerWidth, const QRect &clip, TimeMs ms) const; ClickHandlerPtr getState(int x, int y) const; void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active); @@ -338,8 +342,7 @@ public: void updateMessageId(); private: - const HistoryItem *_item; - int _width = 0; + void startAnimation(int i, int j, int direction); friend class Style; using ReplyMarkupClickHandlerPtr = QSharedPointer; @@ -350,17 +353,33 @@ private: float64 howMuchOver = 0.; HistoryMessageReplyMarkup::Button::Type type; ReplyMarkupClickHandlerPtr link; + mutable QSharedPointer ripple; }; using ButtonRow = QVector