From 654784ce9feef4dab412eb9357f06c728ec73ff5 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 4 Oct 2020 07:52:48 +0400 Subject: [PATCH 001/190] Use external_xcb and external_glib --- Telegram/CMakeLists.txt | 33 ++++----------------------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index f828f262e..7d896a91b 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -106,6 +106,9 @@ if (LINUX) desktop-app::external_materialdecoration desktop-app::external_nimf_qt5 desktop-app::external_qt5ct_support + desktop-app::external_xcb_screensaver + desktop-app::external_xcb + desktop-app::external_glib ) if (NOT DESKTOP_APP_DISABLE_DBUS_INTEGRATION) @@ -137,35 +140,6 @@ if (LINUX) ) endif() - if (DESKTOP_APP_USE_PACKAGED) - find_package(PkgConfig REQUIRED) - pkg_check_modules(XCB_SCREENSAVER REQUIRED IMPORTED_TARGET xcb-screensaver) - pkg_check_modules(XCB REQUIRED IMPORTED_TARGET xcb) - - target_link_libraries(Telegram - PRIVATE - PkgConfig::XCB_SCREENSAVER - PkgConfig::XCB - ) - else() - target_link_static_libraries(Telegram PRIVATE xcb-screensaver) - target_link_libraries(Telegram PRIVATE xcb) - endif() - - find_package(PkgConfig REQUIRED) - pkg_check_modules(GLIB2 REQUIRED IMPORTED_TARGET glib-2.0) - pkg_check_modules(GOBJECT REQUIRED IMPORTED_TARGET gobject-2.0) - pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) - - target_link_libraries(Telegram - PRIVATE - PkgConfig::GLIB2 - PkgConfig::GOBJECT - PkgConfig::GIO - ) - - target_compile_definitions(Telegram PRIVATE G_LOG_DOMAIN="Telegram") - if (NOT TDESKTOP_DISABLE_GTK_INTEGRATION) find_package(PkgConfig REQUIRED) @@ -1290,6 +1264,7 @@ target_compile_definitions(Telegram PRIVATE TDESKTOP_API_ID=${TDESKTOP_API_ID} TDESKTOP_API_HASH=${TDESKTOP_API_HASH} + G_LOG_DOMAIN="Telegram" ) if (APPLE OR NOT CMAKE_EXECUTABLE_SUFFIX STREQUAL "" OR NOT "${output_name}" STREQUAL "Telegram") From 2aa0b674cde819d4b84ccf1e7778de440ab573e9 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 4 Oct 2020 08:07:49 +0400 Subject: [PATCH 002/190] Use new XCB methods from lib_base --- .../platform/linux/specific_linux.cpp | 185 +++--------------- 1 file changed, 29 insertions(+), 156 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 7f28ad56a..179785e54 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/linux/linux_libs.h" #include "base/platform/base_platform_info.h" +#include "base/platform/linux/base_xcb_utilities_linux.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "mainwindow.h" @@ -25,7 +26,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include -#include #include #include @@ -301,128 +301,26 @@ bool GetImageFromClipboardSupported() { } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION -std::optional GetXCBAtom( - xcb_connection_t *connection, - const QString &name) { - const auto cookie = xcb_intern_atom( - connection, - 0, - name.size(), - name.toUtf8()); - - auto reply = xcb_intern_atom_reply( - connection, - cookie, - nullptr); - - if (!reply) { - return std::nullopt; - } - - const auto atom = reply->atom; - free(reply); - - return atom; -} - -bool IsXCBExtensionPresent( - xcb_connection_t *connection, - xcb_extension_t *ext) { - const auto reply = xcb_get_extension_data( - connection, - ext); - - if (!reply) { - return false; - } - - return reply->present; -} - -std::vector GetXCBWMSupported(xcb_connection_t *connection) { - auto netWmAtoms = std::vector{}; - - const auto native = QGuiApplication::platformNativeInterface(); - if (!native) { - return netWmAtoms; - } - - const auto root = static_cast(reinterpret_cast( - native->nativeResourceForIntegration(QByteArray("rootwindow")))); - - const auto supportedAtom = GetXCBAtom(connection, "_NET_SUPPORTED"); - if (!supportedAtom.has_value()) { - return netWmAtoms; - } - - auto offset = 0; - auto remaining = 0; - - do { - const auto cookie = xcb_get_property( - connection, - false, - root, - *supportedAtom, - XCB_ATOM_ATOM, - offset, - 1024); - - auto reply = xcb_get_property_reply( - connection, - cookie, - nullptr); - - if (!reply) { - break; - } - - remaining = 0; - - if (reply->type == XCB_ATOM_ATOM && reply->format == 32) { - const auto len = xcb_get_property_value_length(reply) - / sizeof(xcb_atom_t); - - const auto atoms = reinterpret_cast( - xcb_get_property_value(reply)); - - const auto s = netWmAtoms.size(); - netWmAtoms.resize(s + len); - memcpy(netWmAtoms.data() + s, atoms, len * sizeof(xcb_atom_t)); - - remaining = reply->bytes_after; - offset += len; - } - - free(reply); - } while (remaining > 0); - - return netWmAtoms; -} - std::optional XCBLastUserInputTime() { - const auto native = QGuiApplication::platformNativeInterface(); - if (!native) { - return std::nullopt; - } - - const auto connection = reinterpret_cast( - native->nativeResourceForIntegration(QByteArray("connection"))); - + const auto connection = base::Platform::XCB::GetConnectionFromQt(); if (!connection) { return std::nullopt; } - if (!IsXCBExtensionPresent(connection, &xcb_screensaver_id)) { + if (!base::Platform::XCB::IsExtensionPresent( + connection, + &xcb_screensaver_id)) { return std::nullopt; } - const auto root = static_cast(reinterpret_cast( - native->nativeResourceForIntegration(QByteArray("rootwindow")))); + const auto root = base::Platform::XCB::GetRootWindowFromQt(); + if (!root.has_value()) { + return std::nullopt; + } const auto cookie = xcb_screensaver_query_info( connection, - root); + *root); auto reply = xcb_screensaver_query_info_reply( connection, @@ -573,27 +471,21 @@ enum wl_shell_surface_resize WlResizeFromEdges(Qt::Edges edges) { #endif // Qt < 5.13 && !DESKTOP_APP_QT_PATCHED bool StartXCBMoveResize(QWindow *window, int edges) { - const auto native = QGuiApplication::platformNativeInterface(); - if (!native) { - return false; - } - - const auto connection = reinterpret_cast( - native->nativeResourceForIntegration(QByteArray("connection"))); - + const auto connection = base::Platform::XCB::GetConnectionFromQt(); if (!connection) { return false; } - const auto screen = xcb_setup_roots_iterator( - xcb_get_setup(connection)).data; - - if (!screen) { + const auto root = base::Platform::XCB::GetRootWindowFromQt(); + if (!root.has_value()) { return false; } - const auto moveResize = GetXCBAtom(connection, "_NET_WM_MOVERESIZE"); - if (!moveResize.has_value()) { + const auto moveResizeAtom = base::Platform::XCB::GetAtom( + connection, + "_NET_WM_MOVERESIZE"); + + if (!moveResizeAtom.has_value()) { return false; } @@ -601,7 +493,7 @@ bool StartXCBMoveResize(QWindow *window, int edges) { xcb_client_message_event_t xev; xev.response_type = XCB_CLIENT_MESSAGE; - xev.type = *moveResize; + xev.type = *moveResizeAtom; xev.sequence = 0; xev.window = window->winId(); xev.format = 32; @@ -617,7 +509,7 @@ bool StartXCBMoveResize(QWindow *window, int edges) { xcb_send_event( connection, false, - screen->root, + *root, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, reinterpret_cast(&xev)); @@ -679,19 +571,12 @@ bool ShowWaylandWindowMenu(QWindow *window) { } bool XCBFrameExtentsSupported() { - const auto native = QGuiApplication::platformNativeInterface(); - if (!native) { - return false; - } - - const auto connection = reinterpret_cast( - native->nativeResourceForIntegration(QByteArray("connection"))); - + const auto connection = base::Platform::XCB::GetConnectionFromQt(); if (!connection) { return false; } - const auto frameExtentsAtom = GetXCBAtom( + const auto frameExtentsAtom = base::Platform::XCB::GetAtom( connection, kXCBFrameExtentsAtomName.utf16()); @@ -699,23 +584,18 @@ bool XCBFrameExtentsSupported() { return false; } - return ranges::contains(GetXCBWMSupported(connection), *frameExtentsAtom); + return ranges::contains( + base::Platform::XCB::GetWMSupported(connection), + *frameExtentsAtom); } bool SetXCBFrameExtents(QWindow *window, const QMargins &extents) { - const auto native = QGuiApplication::platformNativeInterface(); - if (!native) { - return false; - } - - const auto connection = reinterpret_cast( - native->nativeResourceForIntegration(QByteArray("connection"))); - + const auto connection = base::Platform::XCB::GetConnectionFromQt(); if (!connection) { return false; } - const auto frameExtentsAtom = GetXCBAtom( + const auto frameExtentsAtom = base::Platform::XCB::GetAtom( connection, kXCBFrameExtentsAtomName.utf16()); @@ -744,19 +624,12 @@ bool SetXCBFrameExtents(QWindow *window, const QMargins &extents) { } bool UnsetXCBFrameExtents(QWindow *window) { - const auto native = QGuiApplication::platformNativeInterface(); - if (!native) { - return false; - } - - const auto connection = reinterpret_cast( - native->nativeResourceForIntegration(QByteArray("connection"))); - + const auto connection = base::Platform::XCB::GetConnectionFromQt(); if (!connection) { return false; } - const auto frameExtentsAtom = GetXCBAtom( + const auto frameExtentsAtom = base::Platform::XCB::GetAtom( connection, kXCBFrameExtentsAtomName.utf16()); From 245d644cd7dd3c00e4bf9e380c6d60342d5c7141 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 19 Oct 2020 01:55:52 +0400 Subject: [PATCH 003/190] Add always on top hint for media view window To avoid overlapping by panels in KDE --- .../media/view/media_view_overlay_widget.cpp | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 692386237..fd2088e9b 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -345,11 +345,13 @@ OverlayWidget::OverlayWidget() connect(QApplication::desktop(), SIGNAL(resized(int)), this, SLOT(onScreenResized(int))); -#if defined Q_OS_UNIX && !defined Q_OS_MAC - setWindowFlags(Qt::FramelessWindowHint | Qt::MaximizeUsingFullscreenGeometryHint); -#else // Q_OS_UNIX && !Q_OS_MAC - setWindowFlags(Qt::FramelessWindowHint); -#endif // Q_OS_UNIX && !Q_OS_MAC + if (Platform::IsLinux()) { + setWindowFlags(Qt::FramelessWindowHint + | Qt::WindowStaysOnTopHint + | Qt::MaximizeUsingFullscreenGeometryHint); + } else { + setWindowFlags(Qt::FramelessWindowHint); + } moveToScreen(); setAttribute(Qt::WA_NoSystemBackground, true); setAttribute(Qt::WA_TranslucentBackground, true); @@ -2280,11 +2282,11 @@ void OverlayWidget::displayFinished() { updateControls(); if (isHidden()) { Ui::Platform::UpdateOverlayed(this); -#if defined Q_OS_UNIX && !defined Q_OS_MAC - showFullScreen(); -#else // Q_OS_UNIX && !Q_OS_MAC - show(); -#endif // Q_OS_UNIX && !Q_OS_MAC + if (Platform::IsLinux()) { + showFullScreen(); + } else { + show(); + } Ui::Platform::ShowOverAll(this); activateWindow(); QApplication::setActiveWindow(this); From b6fc418d32374bfd456030becc915b04a2d1f76d Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 20 Oct 2020 09:26:17 +0400 Subject: [PATCH 004/190] 01org/libva -> intel/libva --- .github/workflows/linux.yml | 2 +- docs/building-cmake.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 91f47b087..aac77a1b7 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -182,7 +182,7 @@ jobs: run: | cd $LibrariesPath - git clone $GIT/01org/libva.git + git clone $GIT/intel/libva.git cd libva ./autogen.sh --enable-static make -j$(nproc) diff --git a/docs/building-cmake.md b/docs/building-cmake.md index 4921e1173..cc0c55cde 100644 --- a/docs/building-cmake.md +++ b/docs/building-cmake.md @@ -67,7 +67,7 @@ Go to ***BuildPath*** and run sudo make install cd .. - git clone https://github.com/01org/libva.git + git clone https://github.com/intel/libva.git cd libva CFLAGS=-fPIC CPPFLAGS=-fPIC LDFLAGS=-fPIC ./autogen.sh --enable-static make $MAKE_THREADS_CNT From 3f6399f13da0ac46fe8ed800e1c644f539be1649 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 20 Oct 2020 23:01:27 +0400 Subject: [PATCH 005/190] Log getting GTK settings --- Telegram/SourceFiles/platform/linux/linux_libs.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Telegram/SourceFiles/platform/linux/linux_libs.h b/Telegram/SourceFiles/platform/linux/linux_libs.h index 822841d57..a1f383534 100644 --- a/Telegram/SourceFiles/platform/linux/linux_libs.h +++ b/Telegram/SourceFiles/platform/linux/linux_libs.h @@ -277,6 +277,7 @@ inline QString GtkSetting(const gchar *propertyName) { gchararray value = GtkSetting(propertyName); QString str = QString::fromUtf8(value); g_free(value); + DEBUG_LOG(("Getting GTK setting, %1: '%2'").arg(propertyName).arg(str)); return str; } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION From b788ae0ae49bfcd08b96cbd01728cef66e1fb277 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 23 Oct 2020 12:04:44 +0400 Subject: [PATCH 006/190] Add stale bot configuration --- .github/stale.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 000000000..6e25c83d1 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,21 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 60 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Issues with these labels will never be considered stale +exemptLabels: [] +# Label to use when marking an issue as stale +staleLabel: stale +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: | + Hey there! + + This issue will be automatically closed in 7 days if there would be no activity. We therefore assume that the user has lost interest or resolved the problem on their own. + + Don't worry though; if this is an error, let us know with a comment and we'll be happy to reopen the issue. + + Thanks! +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false +# Process only issues +only: issues From 1cabfaa6a4769a93e71cc66118682f5068a2319c Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 22 Oct 2020 16:08:01 +0300 Subject: [PATCH 007/190] Fix cancel / crash in sending album to scheduled messages. Fixes #8788 --- Telegram/SourceFiles/history/history.cpp | 5 +++-- .../SourceFiles/history/view/history_view_context_menu.cpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 7b71c0662..f640822d9 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -416,11 +416,12 @@ void History::destroyMessage(not_null item) { types, item->id)); } - } else { - session().api().cancelLocalItem(item); } itemRemoved(item); } + if (item->isSending()) { + session().api().cancelLocalItem(item); + } owner().unregisterMessage(item); Core::App().notifications().clearFromItem(item); diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 1dcfc1f88..849d79ebd 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -69,7 +69,7 @@ constexpr auto kExportLocalTimeout = crl::time(1000); //} MsgId ItemIdAcrossData(not_null item) { - if (!item->isScheduled()) { + if (!item->isScheduled() || item->isSending() || item->hasFailed()) { return item->id; } const auto session = &item->history()->session(); From 00c962e55784776fcec2c07c16f116cecdd1ccd5 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 23 Oct 2020 11:24:02 +0300 Subject: [PATCH 008/190] Bump cmake_helpers submodule. --- cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake b/cmake index 5d6f8ebee..18bf821e5 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 5d6f8ebee32e334543b3f1fa43907a700bf8bde7 +Subproject commit 18bf821e506749afdd9826836adb38ffae0ea454 From 143b9682a43575a4a9ec2bc1bbe893cb23250496 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 12 Oct 2020 16:04:54 +0400 Subject: [PATCH 009/190] Get rid of lxqt-qtplugin It is stil impossible to build it statically and it seems that reading icon theme from gtk is pretty enough --- .gitmodules | 6 ------ Telegram/CMakeLists.txt | 8 -------- Telegram/SourceFiles/qt_static_plugins.cpp | 7 ------- Telegram/ThirdParty/libqtxdg | 1 - Telegram/ThirdParty/lxqt-qtplugin | 1 - 5 files changed, 23 deletions(-) delete mode 160000 Telegram/ThirdParty/libqtxdg delete mode 160000 Telegram/ThirdParty/lxqt-qtplugin diff --git a/.gitmodules b/.gitmodules index 1ce899afc..d4624361b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -82,12 +82,6 @@ [submodule "Telegram/ThirdParty/qt5ct"] path = Telegram/ThirdParty/qt5ct url = https://github.com/desktop-app/qt5ct.git -[submodule "Telegram/ThirdParty/lxqt-qtplugin"] - path = Telegram/ThirdParty/lxqt-qtplugin - url = https://github.com/lxqt/lxqt-qtplugin.git -[submodule "Telegram/ThirdParty/libqtxdg"] - path = Telegram/ThirdParty/libqtxdg - url = https://github.com/lxqt/libqtxdg.git [submodule "Telegram/ThirdParty/fcitx5-qt"] path = Telegram/ThirdParty/fcitx5-qt url = https://github.com/fcitx/fcitx5-qt.git diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 7d896a91b..19e767410 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -112,14 +112,6 @@ if (LINUX) ) if (NOT DESKTOP_APP_DISABLE_DBUS_INTEGRATION) - # conflicts with Qt static link - if (DESKTOP_APP_USE_PACKAGED_LAZY_PLATFORMTHEMES) - target_link_libraries(Telegram - PRIVATE - desktop-app::external_lxqt_qtplugin - ) - endif() - target_link_libraries(Telegram PRIVATE desktop-app::external_statusnotifieritem diff --git a/Telegram/SourceFiles/qt_static_plugins.cpp b/Telegram/SourceFiles/qt_static_plugins.cpp index c5f7473ce..5dffd6a7a 100644 --- a/Telegram/SourceFiles/qt_static_plugins.cpp +++ b/Telegram/SourceFiles/qt_static_plugins.cpp @@ -62,11 +62,4 @@ Q_IMPORT_PLUGIN(QHimePlatformInputContextPlugin) Q_IMPORT_PLUGIN(Qt5CTPlatformThemePlugin) Q_IMPORT_PLUGIN(Qt5CTStylePlugin) #endif // !DESKTOP_APP_USE_PACKAGED || DESKTOP_APP_USE_PACKAGED_LAZY_PLATFORMTHEMES - -// conflicts with Qt static link -#ifdef DESKTOP_APP_USE_PACKAGED_LAZY_PLATFORMTHEMES -#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION -Q_IMPORT_PLUGIN(LXQtPlatformThemePlugin) -#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION -#endif // DESKTOP_APP_USE_PACKAGED_LAZY_PLATFORMTHEMES #endif // Q_OS_UNIX && !Q_OS_MAC diff --git a/Telegram/ThirdParty/libqtxdg b/Telegram/ThirdParty/libqtxdg deleted file mode 160000 index ae412d30c..000000000 --- a/Telegram/ThirdParty/libqtxdg +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ae412d30c695f3d4ce9b79feabc937eefde5537b diff --git a/Telegram/ThirdParty/lxqt-qtplugin b/Telegram/ThirdParty/lxqt-qtplugin deleted file mode 160000 index 418162b36..000000000 --- a/Telegram/ThirdParty/lxqt-qtplugin +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 418162b36eff24fe70fd9195c60fae7276afa286 From f58874572da95eb6aadb25623c6b86648f8c94bf Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 12 Oct 2020 17:17:57 +0400 Subject: [PATCH 010/190] Check actual socket path length rather than InSnap/InFlatpak --- Telegram/SourceFiles/platform/linux/specific_linux.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 179785e54..753b61e2f 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -799,10 +799,15 @@ QString AppRuntimeDirectory() { } QString SingleInstanceLocalServerName(const QString &hash) { - if (InFlatpak() || InSnap()) { + const auto idealSocketPath = AppRuntimeDirectory() + + hash + + '-' + + cGUIDStr(); + + if (idealSocketPath.size() > 108) { return AppRuntimeDirectory() + hash; } else { - return AppRuntimeDirectory() + hash + '-' + cGUIDStr(); + return idealSocketPath; } } From 0179a2ca104c6d09fbaad1558f6f6d5399b63ebb Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 13 Oct 2020 12:47:46 +0400 Subject: [PATCH 011/190] Rename InstallMainDesktopFile to InstallLauncher --- Telegram/SourceFiles/platform/linux/specific_linux.cpp | 4 ++-- Telegram/SourceFiles/platform/linux/specific_linux.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 753b61e2f..799e0f3fa 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -1253,7 +1253,7 @@ void start() { void finish() { } -void InstallMainDesktopFile() { +void InstallLauncher() { static const auto DisabledByEnv = qEnvironmentVariableIsSet( "TDESKTOP_DISABLE_DESKTOP_FILE_GENERATION"); @@ -1421,7 +1421,7 @@ void finish() { } // namespace Platform void psNewVersion() { - Platform::InstallMainDesktopFile(); + Platform::InstallLauncher(); Platform::RegisterCustomScheme(); } diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.h b/Telegram/SourceFiles/platform/linux/specific_linux.h index 1b4cdc0d5..1772a3c87 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.h +++ b/Telegram/SourceFiles/platform/linux/specific_linux.h @@ -42,7 +42,7 @@ QString GetIconName(); inline void IgnoreApplicationActivationRightNow() { } -void InstallMainDesktopFile(); +void InstallLauncher(); } // namespace Platform From 4033a091b55d347ff21472b6b450363a131eb061 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 12 Oct 2020 12:05:43 +0400 Subject: [PATCH 012/190] Hide mark as read button in notifications when app is pass-code locked --- .../platform/linux/notifications_manager_linux.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index b6343df49..0695810d0 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -307,8 +307,10 @@ NotificationData::NotificationData( _actions.push_back(qsl("default")); _actions.push_back(QString()); - _actions.push_back(qsl("mail-mark-read")); - _actions.push_back(tr::lng_context_mark_read(tr::now)); + if (!hideReplyButton) { + _actions.push_back(qsl("mail-mark-read")); + _actions.push_back(tr::lng_context_mark_read(tr::now)); + } if (capabilities.contains(qsl("inline-reply")) && !hideReplyButton) { _actions.push_back(qsl("inline-reply")); From ca67ac913fbb51d431dacd5fc06c56109bb176b8 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 15 Oct 2020 20:31:40 +0400 Subject: [PATCH 013/190] Check for KDE portal backend when using portals on KDE --- .../platform/linux/specific_linux.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 799e0f3fa..d88e91c80 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -123,6 +123,14 @@ void PortalAutostart(bool autostart, bool silent = false) { } } +bool IsXDGDesktopPortalKDEPresent() { + static const auto Result = QDBusInterface( + qsl("org.freedesktop.impl.portal.desktop.kde"), + kXDGDesktopPortalObjectPath.utf16()).isValid(); + + return Result; +} + uint FileChooserPortalVersion() { static const auto Result = [&]() -> uint { auto message = QDBusMessage::createMethodCall( @@ -731,17 +739,20 @@ bool IsXDGDesktopPortalPresent() { } bool UseXDGDesktopPortal() { +#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION static const auto Result = [&] { const auto envVar = qEnvironmentVariableIsSet("TDESKTOP_USE_PORTAL"); const auto portalPresent = IsXDGDesktopPortalPresent(); + const auto neededForKde = DesktopEnvironment::IsKDE() + && IsXDGDesktopPortalKDEPresent(); - return ( - DesktopEnvironment::IsKDE() - || envVar - ) && portalPresent; + return (neededForKde || envVar) && portalPresent; }(); return Result; +#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION + + return false; } bool CanOpenDirectoryWithPortal() { From 4f2b0531f8a98a561569858b0a7155b74acaefec Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 8 Oct 2020 20:40:32 +0400 Subject: [PATCH 014/190] Replace GDBusProxy with GDBusConnection in NotificationData --- .../linux/notifications_manager_linux.cpp | 124 ++++++++++++++---- 1 file changed, 99 insertions(+), 25 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index 0695810d0..1a0b1bc18 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -244,7 +244,7 @@ public: void notificationReplied(uint id, const QString &text); private: - GDBusProxy *_dbusProxy; + GDBusConnection *_dbusConnection = nullptr; base::weak_ptr _manager; QString _title; @@ -255,14 +255,19 @@ private: QImage _image; uint _notificationId = 0; + guint _actionInvokedSignalId = 0; + guint _notificationRepliedSignalId = 0; + guint _notificationClosedSignalId = 0; NotificationId _id; static void signalEmitted( - GDBusProxy *proxy, - gchar *sender_name, - gchar *signal_name, + GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, GVariant *parameters, - NotificationData *notificationData); + gpointer user_data); }; @@ -275,20 +280,24 @@ NotificationData::NotificationData( const QString &msg, NotificationId id, bool hideReplyButton) -: _dbusProxy(g_dbus_proxy_new_for_bus_sync( - G_BUS_TYPE_SESSION, - G_DBUS_PROXY_FLAGS_NONE, - nullptr, - kService.utf8(), - kObjectPath.utf8(), - kInterface.utf8(), - nullptr, - nullptr)) -, _manager(manager) +: _manager(manager) , _title(title) , _imageKey(GetImageKey(ParseSpecificationVersion( GetServerInformation()))) , _id(id) { + GError *error = nullptr; + + _dbusConnection = g_bus_get_sync( + G_BUS_TYPE_SESSION, + nullptr, + &error); + + if (error) { + LOG(("Native notification error: %1").arg(error->message)); + g_error_free(error); + return; + } + const auto capabilities = GetCapabilities(); if (capabilities.contains(qsl("body-markup"))) { @@ -315,11 +324,35 @@ NotificationData::NotificationData( if (capabilities.contains(qsl("inline-reply")) && !hideReplyButton) { _actions.push_back(qsl("inline-reply")); _actions.push_back(tr::lng_notification_reply(tr::now)); + + _notificationRepliedSignalId = g_dbus_connection_signal_subscribe( + _dbusConnection, + kService.utf8(), + kInterface.utf8(), + "NotificationReplied", + kObjectPath.utf8(), + nullptr, + G_DBUS_SIGNAL_FLAGS_NONE, + signalEmitted, + this, + nullptr); } else { // icon name according to https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html _actions.push_back(qsl("mail-reply-sender")); _actions.push_back(tr::lng_notification_reply(tr::now)); } + + _actionInvokedSignalId = g_dbus_connection_signal_subscribe( + _dbusConnection, + kService.utf8(), + kInterface.utf8(), + "ActionInvoked", + kObjectPath.utf8(), + nullptr, + G_DBUS_SIGNAL_FLAGS_NONE, + signalEmitted, + this, + nullptr); } if (capabilities.contains(qsl("action-icons"))) { @@ -353,11 +386,35 @@ NotificationData::NotificationData( qsl("desktop-entry"), g_variant_new_string(GetLauncherBasename().toUtf8())); - g_signal_connect(_dbusProxy, "g-signal", G_CALLBACK(signalEmitted), this); + _notificationClosedSignalId = g_dbus_connection_signal_subscribe( + _dbusConnection, + kService.utf8(), + kInterface.utf8(), + "NotificationClosed", + kObjectPath.utf8(), + nullptr, + G_DBUS_SIGNAL_FLAGS_NONE, + signalEmitted, + this, + nullptr); } NotificationData::~NotificationData() { - g_object_unref(_dbusProxy); + if (_dbusConnection) { + if (_actionInvokedSignalId != 0) { + g_dbus_connection_signal_unsubscribe(_dbusConnection, _actionInvokedSignalId); + } + + if (_notificationRepliedSignalId != 0) { + g_dbus_connection_signal_unsubscribe(_dbusConnection, _notificationRepliedSignalId); + } + + if (_notificationClosedSignalId != 0) { + g_dbus_connection_signal_unsubscribe(_dbusConnection, _notificationClosedSignalId); + } + + g_object_unref(_dbusConnection); + } for (const auto &[key, value] : _hints) { if (value) { @@ -393,8 +450,11 @@ bool NotificationData::show() { ? GetIconName() : QString(); - auto reply = g_dbus_proxy_call_sync( - _dbusProxy, + auto reply = g_dbus_connection_call_sync( + _dbusConnection, + kService.utf8(), + kObjectPath.utf8(), + kInterface.utf8(), "Notify", g_variant_new( kNotifyArgsType.utf8(), @@ -406,6 +466,7 @@ bool NotificationData::show() { &actionsBuilder, &hintsBuilder, -1), + nullptr, G_DBUS_CALL_FLAGS_NONE, kDBusTimeout, nullptr, @@ -425,10 +486,14 @@ bool NotificationData::show() { } void NotificationData::close() { - g_dbus_proxy_call( - _dbusProxy, + g_dbus_connection_call( + _dbusConnection, + kService.utf8(), + kObjectPath.utf8(), + kInterface.utf8(), "CloseNotification", g_variant_new("(u)", _notificationId), + nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, @@ -465,11 +530,20 @@ void NotificationData::setImage(const QString &imagePath) { } void NotificationData::signalEmitted( - GDBusProxy *proxy, - gchar *sender_name, - gchar *signal_name, + GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, GVariant *parameters, - NotificationData *notificationData) { + gpointer user_data) { + const auto notificationData = reinterpret_cast( + user_data); + + if (!notificationData) { + return; + } + if(signal_name == qstr("ActionInvoked")) { guint32 id; gchar *actionName; From 1373bd0af1fcad188c5e7b1a057590aa42a60788 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 23 Oct 2020 12:47:41 +0300 Subject: [PATCH 015/190] Use OpenAL 1.20.1 with bugfix backport on Windows. --- Telegram/lib_base | 2 +- Telegram/lib_ui | 2 +- docs/building-msvc.md | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Telegram/lib_base b/Telegram/lib_base index 4f03dbd9a..98224799e 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 4f03dbd9a0c3dce0268fa208bf1e3ffff4c95c72 +Subproject commit 98224799e420dfcca33af816b11e261080cb1fed diff --git a/Telegram/lib_ui b/Telegram/lib_ui index ae340a0b7..914df12eb 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit ae340a0b76421fedf73421b0d42f688341658b45 +Subproject commit 914df12ebe3231dbdd5150d5b33cfab2e7dabe17 diff --git a/docs/building-msvc.md b/docs/building-msvc.md index c0a2b3123..1d6ee5de7 100644 --- a/docs/building-msvc.md +++ b/docs/building-msvc.md @@ -102,14 +102,13 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** git clone https://github.com/telegramdesktop/openal-soft.git cd openal-soft - git checkout fix_capture + git checkout fix_mono cd build cmake .. ^ -G "Visual Studio 16 2019" ^ -A Win32 ^ -D LIBTYPE:STRING=STATIC ^ - -D FORCE_STATIC_VCRT=ON ^ - -D ALSOFT_BACKEND_WASAPI=OFF + -D FORCE_STATIC_VCRT=ON msbuild OpenAL.vcxproj /property:Configuration=Debug msbuild OpenAL.vcxproj /property:Configuration=Release cd ..\.. From 1fdfa94497d3d820a4a9c7ef6adefd531e9f3af7 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 23 Oct 2020 13:49:31 +0400 Subject: [PATCH 016/190] Remove explicit Opus clone step from macos action Since no longer needed --- .github/workflows/mac.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 3555d3334..2d1ff6613 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -169,17 +169,14 @@ jobs: with: path: ${{ env.LibrariesPath }}/opus-cache key: ${{ runner.OS }}-opus-${{ env.CACHE_KEY }} - - name: Opus clone. - run: | - cd $LibrariesPath - - git clone -b v1.3 --depth=1 $GIT/xiph/opus - - name: Opus build. + - name: Opus. if: steps.cache-opus.outputs.cache-hit != 'true' run: | cd $LibrariesPath + git clone $GIT/xiph/opus cd opus + git checkout v1.3 ./autogen.sh CFLAGS="$MIN_MAC $UNGUARDED" CPPFLAGS="$MIN_MAC $UNGUARDED" LDFLAGS="$MIN_MAC" ./configure --prefix=$PREFIX make -j$(nproc) From 9697567b8d2c3edb7ea742baf901ab1c6f40828d Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 23 Oct 2020 13:10:30 +0300 Subject: [PATCH 017/190] Add some more open file warnings. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/core/core_settings.cpp | 9 ++- Telegram/SourceFiles/core/core_settings.h | 7 +++ Telegram/SourceFiles/data/data_document.cpp | 67 +++++++++++++-------- Telegram/SourceFiles/data/data_document.h | 7 ++- 5 files changed, 63 insertions(+), 28 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index d045626dd..78d54b851 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2271,6 +2271,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_language_not_ready_link" = "translations platform"; "lng_launch_exe_warning" = "This file has a {extension} extension.\nAre you sure you want to run it?"; +"lng_launch_svg_warning" = "Opening this file can potentially expose your IP address to its sender. Continue?"; "lng_launch_exe_sure" = "Run"; "lng_launch_exe_dont_ask" = "Don't ask me again"; diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index 43fdefe2d..a7b0b124d 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -109,7 +109,8 @@ QByteArray Settings::serialize() const { << qint32(_notifyFromAll ? 1 : 0) << qint32(_nativeWindowFrame.current() ? 1 : 0) << qint32(_systemDarkModeEnabled.current() ? 1 : 0) - << _callVideoInputDeviceId; + << _callVideoInputDeviceId + << qint32(_ipRevealWarning ? 1 : 0); } return result; } @@ -176,6 +177,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { qint32 notifyFromAll = _notifyFromAll ? 1 : 0; qint32 nativeWindowFrame = _nativeWindowFrame.current() ? 1 : 0; qint32 systemDarkModeEnabled = _systemDarkModeEnabled.current() ? 1 : 0; + qint32 ipRevealWarning = _ipRevealWarning ? 1 : 0; stream >> themesAccentColors; if (!stream.atEnd()) { @@ -259,6 +261,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) { if (!stream.atEnd()) { stream >> callVideoInputDeviceId; } + if (!stream.atEnd()) { + stream >> ipRevealWarning; + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for Core::Settings::constructFromSerialized()")); @@ -318,6 +323,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { _includeMutedCounter = (includeMutedCounter == 1); _countUnreadMessages = (countUnreadMessages == 1); _exeLaunchWarning = (exeLaunchWarning == 1); + _ipRevealWarning = (ipRevealWarning == 1); _notifyAboutPinned = (notifyAboutPinned == 1); _loopAnimatedStickers = (loopAnimatedStickers == 1); _largeEmoji = (largeEmoji == 1); @@ -468,6 +474,7 @@ void Settings::resetOnLastLogout() { _soundOverrides = {}; _exeLaunchWarning = true; + _ipRevealWarning = true; _loopAnimatedStickers = true; _largeEmoji = true; _replaceEmoji = true; diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index a774db095..d9c0bfaa8 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -255,6 +255,12 @@ public: void setExeLaunchWarning(bool warning) { _exeLaunchWarning = warning; } + [[nodiscard]] bool ipRevealWarning() const { + return _ipRevealWarning; + } + void setIpRevealWarning(bool warning) { + _ipRevealWarning = warning; + } [[nodiscard]] bool loopAnimatedStickers() const { return _loopAnimatedStickers; } @@ -513,6 +519,7 @@ private: Ui::InputSubmitSettings _sendSubmitWay; base::flat_map _soundOverrides; bool _exeLaunchWarning = true; + bool _ipRevealWarning = true; bool _loopAnimatedStickers = true; rpl::variable _largeEmoji = true; rpl::variable _replaceEmoji = true; diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index b30b7eb48..f9fcac1e9 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -74,15 +74,15 @@ void LaunchWithWarning( not_null session, const QString &name, HistoryItem *item) { + const auto isExecutable = Data::IsExecutableName(name); + const auto isIpReveal = Data::IsIpRevealingName(name); + auto &app = Core::App(); const auto warn = [&] { - if (!Data::IsExecutableName(name)) { - return false; - } else if (!Core::App().settings().exeLaunchWarning()) { - return false; - } else if (item && item->history()->peer->isVerified()) { + if (item && item->history()->peer->isVerified()) { return false; } - return true; + return (isExecutable && app.settings().exeLaunchWarning()) + || (isIpReveal && app.settings().ipRevealWarning()); }(); const auto extension = '.' + Data::FileExtension(name); if (Platform::IsWindows() && extension == u"."_q) { @@ -99,20 +99,27 @@ void LaunchWithWarning( File::Launch(name); return; } - const auto callback = [=](bool checked) { + const auto callback = [=, &app](bool checked) { if (checked) { - Core::App().settings().setExeLaunchWarning(false); - Core::App().saveSettingsDelayed(); + if (isExecutable) { + app.settings().setExeLaunchWarning(false); + } else if (isIpReveal) { + app.settings().setIpRevealWarning(false); + } + app.saveSettingsDelayed(); } File::Launch(name); }; - Ui::show(Box( - tr::lng_launch_exe_warning( + auto text = isExecutable + ? tr::lng_launch_exe_warning( lt_extension, rpl::single(Ui::Text::Bold(extension)), - Ui::Text::WithEntities), + Ui::Text::WithEntities) + : tr::lng_launch_svg_warning(Ui::Text::WithEntities); + Ui::show(Box( + std::move(text), tr::lng_launch_exe_dont_ask(tr::now), - tr::lng_launch_exe_sure(), + (isExecutable ? tr::lng_launch_exe_sure : tr::lng_continue)(), callback)); } @@ -1668,17 +1675,17 @@ mpkg pkg scpt scptd xhtm webarchive"); slp zsh"); #else // Q_OS_MAC || Q_OS_UNIX qsl("\ -ad ade adp app application appref-ms asp asx bas bat bin cdxml cer cfg chi \ -chm cmd cnt com cpl crt csh der diagcab dll drv eml exe fon fxp gadget grp \ -hlp hpj hta htt inf ini ins inx isp isu its jar jnlp job js jse ksh lnk \ -local lua mad maf mag mam manifest maq mar mas mat mau mav maw mcf mda mdb \ -mde mdt mdw mdz mht mhtml mjs mmc mof msc msg msh msh1 msh2 msh1xml msh2xml \ -mshxml msi msp mst ops osd paf pcd phar php php3 php4 php5 php7 phps php-s \ -pht phtml pif pl plg pm pod prf prg ps1 ps2 ps1xml ps2xml psc1 psc2 psd1 \ -psm1 pssc pst py py3 pyc pyd pyi pyo pyw pywz pyz rb reg rgs scf scr sct \ -search-ms settingcontent-ms sh shb shs slk sys t tmp u3p url vb vbe vbp vbs \ -vbscript vdx vsmacros vsd vsdm vsdx vss vssm vssx vst vstm vstx vsw vsx vtx \ -website ws wsc wsf wsh xbap xll xnk xs"); +ad ade adp app application appref-ms asp asx bas bat bin cab cdxml cer cfg \ +chi chm cmd cnt com cpl crt csh der diagcab dll drv eml exe fon fxp gadget \ +grp hlp hpj hta htt inf ini ins inx isp isu its jar jnlp job js jse key ksh \ +lnk local lua mad maf mag mam manifest maq mar mas mat mau mav maw mcf mda \ +mdb mde mdt mdw mdz mht mhtml mjs mmc mof msc msg msh msh1 msh2 msh1xml \ +msh2xml mshxml msi msp mst ops osd paf pcd phar php php3 php4 php5 php7 phps \ +php-s pht phtml pif pl plg pm pod prf prg ps1 ps2 ps1xml ps2xml psc1 psc2 \ +psd1 psm1 pssc pst py py3 pyc pyd pyi pyo pyw pywz pyz rb reg rgs scf scr \ +sct search-ms settingcontent-ms sh shb shs slk sys t tmp u3p url vb vbe vbp \ +vbs vbscript vdx vsmacros vsd vsdm vsdx vss vssm vssx vst vstm vstx vsw vsx \ +vtx website ws wsc wsf wsh xbap xll xnk xs"); #endif // !Q_OS_MAC && !Q_OS_UNIX const auto list = joined.split(' '); return base::flat_set(list.begin(), list.end()); @@ -1689,6 +1696,18 @@ website ws wsc wsf wsh xbap xll xnk xs"); FileExtension(filepath).toLower()); } +bool IsIpRevealingName(const QString &filepath) { + static const auto kExtensions = [] { + const auto joined = u"htm html svg"_q; + const auto list = joined.split(' '); + return base::flat_set(list.begin(), list.end()); + }(); + + return ranges::binary_search( + kExtensions, + FileExtension(filepath).toLower()); +} + base::binary_guard ReadImageAsync( not_null media, FnMut postprocess, diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index ea6a8528c..efd777f5e 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -443,9 +443,10 @@ QString DocumentFileNameForSave( namespace Data { -QString FileExtension(const QString &filepath); -bool IsValidMediaFile(const QString &filepath); -bool IsExecutableName(const QString &filepath); +[[nodiscard]] QString FileExtension(const QString &filepath); +[[nodiscard]] bool IsValidMediaFile(const QString &filepath); +[[nodiscard]] bool IsExecutableName(const QString &filepath); +[[nodiscard]] bool IsIpRevealingName(const QString &filepath); base::binary_guard ReadImageAsync( not_null media, FnMut postprocess, From 8b27aa533131a27158e89de323b2ac8f1ca3ca27 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 23 Oct 2020 13:11:42 +0300 Subject: [PATCH 018/190] Update cmake_helpers submodule. --- cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake b/cmake index 18bf821e5..b485d43a4 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 18bf821e506749afdd9826836adb38ffae0ea454 +Subproject commit b485d43a45eec75b8cbc0ab966ab3c06b5e8bac8 From 367b028094ee161aba362670bbfa21613fbac159 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 7 Oct 2020 20:08:50 +0300 Subject: [PATCH 019/190] Moved contacts box preparing to single place. --- .../SourceFiles/boxes/peer_list_controllers.cpp | 15 +++++++++++++++ .../SourceFiles/boxes/peer_list_controllers.h | 4 ++++ .../platform/linux/main_window_linux.cpp | 13 +------------ .../SourceFiles/platform/mac/main_window_mac.mm | 5 +---- Telegram/SourceFiles/window/window_main_menu.cpp | 5 +---- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp index fb9cd3ad9..4b8041fab 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_histories.h" #include "apiwrap.h" #include "mainwidget.h" +#include "mainwindow.h" #include "lang/lang_keys.h" #include "history/history.h" #include "dialogs/dialogs_main_list.h" @@ -104,6 +105,20 @@ void AddBotToGroup(not_null bot, not_null chat) { // return mapFromGlobal(QCursor::pos()) - _st.rippleAreaPosition; //} +object_ptr PrepareContactsBox( + not_null sessionController) { + const auto controller = sessionController; + auto delegate = [=](not_null box) { + box->addButton(tr::lng_close(), [=] { box->closeBox(); }); + box->addLeftButton( + tr::lng_profile_add_contact(), + [=] { controller->widget()->onShowAddContact(); }); + }; + return Box( + std::make_unique(controller), + std::move(delegate)); +} + void PeerListRowWithLink::setActionLink(const QString &action) { _action = action; refreshActionLink(); diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.h b/Telegram/SourceFiles/boxes/peer_list_controllers.h index 02eb810b4..17d495241 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.h +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.h @@ -31,9 +31,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class History; namespace Window { +class SessionController; class SessionNavigation; } // namespace Window +[[nodiscard]] object_ptr PrepareContactsBox( + not_null sessionController); + class PeerListRowWithLink : public PeerListRow { public: using PeerListRow::PeerListRow; diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index 1c3171c57..1c8a1cbb5 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -950,18 +950,7 @@ void MainWindow::createGlobalMenu() { return; } - Ui::show( - Box(std::make_unique( - sessionController()), - [](not_null box) { - box->addButton(tr::lng_close(), [box] { - box->closeBox(); - }); - - box->addLeftButton(tr::lng_profile_add_contact(), [] { - App::wnd()->onShowAddContact(); - }); - })); + Ui::show(PrepareContactsBox(sessionController())); })); psAddContact = tools->addAction( diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.mm b/Telegram/SourceFiles/platform/mac/main_window_mac.mm index e008f9ba1..6fbd9ec11 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.mm +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.mm @@ -721,10 +721,7 @@ void MainWindow::createGlobalMenu() { if (!sessionController()) { return; } - Ui::show(Box(std::make_unique(sessionController()), [](not_null box) { - box->addButton(tr::lng_close(), [box] { box->closeBox(); }); - box->addLeftButton(tr::lng_profile_add_contact(), [] { App::wnd()->onShowAddContact(); }); - })); + Ui::show(PrepareContactsBox(sessionController())); })); psAddContact = window->addAction(tr::lng_mac_menu_add_contact(tr::now), App::wnd(), SLOT(onShowAddContact())); window->addSeparator(); diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index d56d252e2..c4cfe2a0b 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -861,10 +861,7 @@ void MainMenu::refreshMenu() { App::wnd()->onShowNewChannel(); }, &st::mainMenuNewChannel, &st::mainMenuNewChannelOver); _menu->addAction(tr::lng_menu_contacts(tr::now), [=] { - Ui::show(Box(std::make_unique(controller), [](not_null box) { - box->addButton(tr::lng_close(), [box] { box->closeBox(); }); - box->addLeftButton(tr::lng_profile_add_contact(), [] { App::wnd()->onShowAddContact(); }); - })); + Ui::show(PrepareContactsBox(controller)); }, &st::mainMenuContacts, &st::mainMenuContactsOver); if (_controller->session().serverConfig().phoneCallsEnabled.current()) { _menu->addAction(tr::lng_menu_calls(tr::now), [=] { From 7de9bcad03959faa6eaa48aa0dc1df9907252f22 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 7 Oct 2020 21:06:22 +0300 Subject: [PATCH 020/190] Added ability to open contacts with shortcut. Fixed #8775. --- Telegram/SourceFiles/core/shortcuts.cpp | 3 +++ Telegram/SourceFiles/core/shortcuts.h | 1 + Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp | 5 +++++ 3 files changed, 9 insertions(+) diff --git a/Telegram/SourceFiles/core/shortcuts.cpp b/Telegram/SourceFiles/core/shortcuts.cpp index 3e39a37cb..ef50008ef 100644 --- a/Telegram/SourceFiles/core/shortcuts.cpp +++ b/Telegram/SourceFiles/core/shortcuts.cpp @@ -88,6 +88,7 @@ const auto CommandByName = base::flat_map{ { qsl("last_folder") , Command::ShowFolderLast }, { qsl("show_archive") , Command::ShowArchive }, + { qsl("show_contacts") , Command::ShowContacts }, { qsl("read_chat") , Command::ReadChat }, @@ -132,6 +133,7 @@ const auto CommandNames = base::flat_map{ { Command::ShowFolderLast , qsl("last_folder") }, { Command::ShowArchive , qsl("show_archive") }, + { Command::ShowContacts , qsl("show_contacts") }, { Command::ReadChat , qsl("read_chat") }, }; @@ -383,6 +385,7 @@ void Manager::fillDefaults() { set(qsl("ctrl+0"), Command::ChatSelf); set(qsl("ctrl+9"), Command::ShowArchive); + set(qsl("ctrl+shift+n"), Command::ShowContacts); set(qsl("ctrl+r"), Command::ReadChat); } diff --git a/Telegram/SourceFiles/core/shortcuts.h b/Telegram/SourceFiles/core/shortcuts.h index c64877dca..c6a8b596a 100644 --- a/Telegram/SourceFiles/core/shortcuts.h +++ b/Telegram/SourceFiles/core/shortcuts.h @@ -48,6 +48,7 @@ enum class Command { FolderPrevious, ShowArchive, + ShowContacts, JustSendMessage, SendSilentMessage, diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index f978ecbe5..5f4e32335 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -3178,6 +3178,11 @@ void InnerWidget::setupShortcuts() { return (history != nullptr); }); + request->check(Command::ShowContacts) && request->handle([=] { + Ui::show(PrepareContactsBox(_controller)); + return true; + }); + if (session().supportMode() && row.key.history()) { request->check( Command::SupportScrollToCurrent From 5540b0bb8bac677123ff3ff030258eeae2ab7b59 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 7 Oct 2020 21:38:45 +0300 Subject: [PATCH 021/190] Fixed glitch for scheduled messages with elapsed date in channels. --- Telegram/SourceFiles/api/api_updates.cpp | 32 ++++++++++++------- .../data/data_scheduled_messages.cpp | 10 +++--- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 4322cf935..28673ceac 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -63,6 +63,22 @@ enum class DataIsLoadedResult { Ok = 3, }; +void ProcessScheduledMessageWithElapsedTime( + not_null session, + bool needToAdd, + const MTPDmessage &data) { + if (needToAdd && !data.is_from_scheduled()) { + // If we still need to add a new message, + // we should first check if this message is in + // the list of scheduled messages. + // This is necessary to correctly update the file reference. + // Note that when a message is scheduled until online + // while the recipient is already online, the server sends + // an ordinary new message with skipped "from_scheduled" flag. + session->data().scheduledMessages().checkEntitiesAndUpdate(data); + } +} + bool IsForceLogoutNotification(const MTPDupdateServiceNotification &data) { return qs(data.vtype()).startsWith(qstr("AUTH_KEY_DROP_")); } @@ -958,17 +974,7 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) { LOG(("Skipping message, because it is already in blocks!")); needToAdd = false; } - if (needToAdd && !data.is_from_scheduled()) { - // If we still need to add a new message, - // we should first check if this message is in - // the list of scheduled messages. - // This is necessary to correctly update the file reference. - // Note that when a message is scheduled until online - // while the recipient is already online, the server sends - // an ordinary new message with skipped "from_scheduled" flag. - _session->data().scheduledMessages().checkEntitiesAndUpdate( - data); - } + ProcessScheduledMessageWithElapsedTime(_session, needToAdd, data); } if (needToAdd) { _session->data().addNewMessage( @@ -1057,10 +1063,12 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) { auto &d = update.c_updateNewChannelMessage(); auto needToAdd = true; if (d.vmessage().type() == mtpc_message) { // index forwarded messages to links _overview - if (_session->data().checkEntitiesAndViewsUpdate(d.vmessage().c_message())) { // already in blocks + const auto &data = d.vmessage().c_message(); + if (_session->data().checkEntitiesAndViewsUpdate(data)) { // already in blocks LOG(("Skipping message, because it is already in blocks!")); needToAdd = false; } + ProcessScheduledMessageWithElapsedTime(_session, needToAdd, data); } if (needToAdd) { _session->data().addNewMessage( diff --git a/Telegram/SourceFiles/data/data_scheduled_messages.cpp b/Telegram/SourceFiles/data/data_scheduled_messages.cpp index 9970c811a..c77f95ca3 100644 --- a/Telegram/SourceFiles/data/data_scheduled_messages.cpp +++ b/Telegram/SourceFiles/data/data_scheduled_messages.cpp @@ -228,14 +228,12 @@ void ScheduledMessages::apply(const MTPDupdateNewScheduledMessage &update) { void ScheduledMessages::checkEntitiesAndUpdate(const MTPDmessage &data) { // When the user sends a message with a media scheduled until online - // while the recipient is already online, the server sends - // updateNewMessage to the client and the client calls this method. + // while the recipient is already online, or scheduled message + // is already due and is sent immediately, the server sends + // updateNewMessage or updateNewChannelMessage to the client + // and the client calls this method. const auto peer = peerFromMTP(data.vpeer_id()); - if (!peerIsUser(peer)) { - return; - } - const auto history = _session->data().historyLoaded(peer); if (!history) { return; From 52cca981444bf5c8bc0ea0e98a14e0387acc1092 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 7 Oct 2020 21:59:33 +0300 Subject: [PATCH 022/190] Fixed replies button display in section of scheduled messages. --- Telegram/SourceFiles/history/history_message.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 9baaf1a61..e6cb8970f 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -487,7 +487,7 @@ HistoryMessage::HistoryMessage( } config.viaBotId = data.vvia_bot_id().value_or_empty(); config.viewsCount = data.vviews().value_or(-1); - config.mtpReplies = data.vreplies(); + config.mtpReplies = isScheduled() ? nullptr : data.vreplies(); config.mtpMarkup = data.vreply_markup(); config.editDate = data.vedit_date().value_or_empty(); config.author = qs(data.vpost_author().value_or_empty()); From bc7975ece776349520ee6ba5e1ea209478714e93 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 20 Oct 2020 23:21:51 +0300 Subject: [PATCH 023/190] Fixed crash when user reschedules already sent message. Fixed #8867. --- .../history/view/history_view_context_menu.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 849d79ebd..68f46aab8 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -420,23 +420,23 @@ bool AddSendNowMessageAction( bool AddRescheduleMessageAction( not_null menu, const ContextMenuRequest &request) { - if (!HasEditMessageAction(request) - || !request.item->isScheduled()) { + if (!HasEditMessageAction(request) || !request.item->isScheduled()) { return false; } - const auto item = request.item; - const auto owner = &item->history()->owner(); - const auto itemId = item->fullId(); + const auto owner = &request.item->history()->owner(); + const auto itemId = request.item->fullId(); menu->addAction(tr::lng_context_reschedule(tr::now), [=] { const auto item = owner->message(itemId); if (!item) { return; } const auto callback = [=](Api::SendOptions options) { - if (!item->media() || !item->media()->webpage()) { - options.removeWebPageId = true; + if (const auto item = owner->message(itemId)) { + if (!item->media() || !item->media()->webpage()) { + options.removeWebPageId = true; + } + Api::RescheduleMessage(item, options); } - Api::RescheduleMessage(item, options); }; const auto peer = item->history()->peer; From bc8f8bc68cc64d1141140980597fdc39ebe128e9 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 20 Oct 2020 23:25:42 +0300 Subject: [PATCH 024/190] Added auto-closing to some boxes which depend on certain message. --- Telegram/SourceFiles/boxes/edit_caption_box.cpp | 6 ++++++ Telegram/SourceFiles/data/data_session.cpp | 8 ++++++++ Telegram/SourceFiles/data/data_session.h | 2 ++ .../history/view/history_view_context_menu.cpp | 8 +++++++- 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index ef5c75254..1f24fc049 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -363,6 +363,12 @@ EditCaptionBox::EditCaptionBox( ) | rpl::start_with_next([&](bool checked) { _asFile = checked; }, _wayWrap->lifetime()); + + _controller->session().data().itemRemoved( + _msgId + ) | rpl::start_with_next([=] { + closeBox(); + }, lifetime()); } EditCaptionBox::~EditCaptionBox() = default; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index d789e2cf6..b748359af 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1428,6 +1428,14 @@ rpl::producer> Session::itemRemoved() const { return _itemRemoved.events(); } +rpl::producer> Session::itemRemoved( + FullMsgId itemId) const { + return itemRemoved( + ) | rpl::filter([=](not_null item) { + return (itemId == item->fullId()); + }); +} + void Session::notifyViewRemoved(not_null view) { _viewRemoved.fire_copy(view); } diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 4ac51efcd..d0337ca86 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -231,6 +231,8 @@ public: [[nodiscard]] rpl::producer> historyUnloaded() const; [[nodiscard]] rpl::producer> itemRemoved() const; + [[nodiscard]] rpl::producer> itemRemoved( + FullMsgId itemId) const; void notifyViewRemoved(not_null view); [[nodiscard]] rpl::producer> viewRemoved() const; void notifyHistoryCleared(not_null history); diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 68f46aab8..323476590 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -453,13 +453,19 @@ bool AddRescheduleMessageAction( ? HistoryView::DefaultScheduleTime() : item->date() + 600; - Ui::show( + const auto box = Ui::show( HistoryView::PrepareScheduleBox( &request.navigation->session(), sendMenuType, callback, date), Ui::LayerOption::KeepOther); + + owner->itemRemoved( + itemId + ) | rpl::start_with_next([=] { + box->closeBox(); + }, box->lifetime()); }); return true; } From 3a2b772a5d65ea5ca295a785721ef8f3b2641132 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 22 Oct 2020 19:30:23 +0300 Subject: [PATCH 025/190] Added spellchecker to Replies / Scheduled messages sections. Fixed #8793. --- .../SourceFiles/history/view/history_view_compose_controls.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp index 1529cb366..73974f3f9 100644 --- a/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp @@ -1005,6 +1005,7 @@ void ComposeControls::initField() { _field, &_window->session()); _raiseEmojiSuggestions = [=] { suggestions->raise(); }; + InitSpellchecker(_window, _field); } void ComposeControls::fieldChanged() { From 47bb8ec687d44bbff730538fc7361e7e64ac48b7 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 5 Sep 2020 02:49:59 +0300 Subject: [PATCH 026/190] Added Github Action that updates user-agent for DNS. --- .github/workflows/user_agent_updater.yml | 90 ++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 .github/workflows/user_agent_updater.yml diff --git a/.github/workflows/user_agent_updater.yml b/.github/workflows/user_agent_updater.yml new file mode 100644 index 000000000..44c2515af --- /dev/null +++ b/.github/workflows/user_agent_updater.yml @@ -0,0 +1,90 @@ +name: User-agent updater. + +on: + repository_dispatch: + types: ["Restart user_agent_updater workflow."] + schedule: + # At 00:00 on day-of-month 1. + - cron: '0 0 1 * *' + +jobs: + User-agent: + runs-on: ubuntu-latest + + env: + code_file: "Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp" + skip: "0" + + steps: + - name: Clone. + if: env.skip == '0' + uses: actions/checkout@v2 + + - name: Write a new version of Google Chrome to the user-agent for DNS. + if: env.skip == '0' + shell: python + run: | + import subprocess, os, re; + + regExpVersion = "[0-9]+.[0-9]+.[0-9]+.[0-9]+"; + chrome = "Chrome/"; + + def newVersion(): + output = subprocess.check_output(["google-chrome", "--version"]); + version = re.search(regExpVersion, output); + if not version: + print("Can't find a Chrome version."); + exit(); + return version.group(0); + + newChromeVersion = newVersion(); + print(newChromeVersion); + + def writeUserAgent(): + p = os.environ['code_file']; + w = open(p, "r"); + content = w.read(); + w.close(); + + regExpChrome = chrome + regExpVersion; + + version = re.search(regExpChrome, content); + if not version: + print("Can't find an user-agent in the code."); + exit(); + content = re.sub(regExpChrome, chrome + newChromeVersion, content); + + w = open(p, "w"); + w.write(content); + + print("::set-env name=ChromeVersion::" + newChromeVersion); + + writeUserAgent(); + + - name: Push to the current branch. + if: env.skip == '0' && env.ChromeVersion != '' + run: | + token=${{ secrets.TOKEN_FOR_MASTER_UPDATER }} + if [ -z "${token}" ]; then + echo "Token is unset. Nothing to do." + exit 0 + fi + + url=https://x-access-token:$token@github.com/$GITHUB_REPOSITORY + + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + + git diff > git_diff.txt + if [[ ! -s git_diff.txt ]]; then + echo "Nothing to commit." + exit 0 + fi + + git add $code_file + git commit -m "Update User-Agent for DNS to Chrome $ChromeVersion." + + git remote set-url origin $url + + git push origin HEAD:$GITHUB_REF + echo "Done!" From 3d54a263b8e31a4628d543a1b9d8da9a62bae63f Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 23 Oct 2020 15:22:38 +0300 Subject: [PATCH 027/190] Stop playing documents when items are deleted. --- Telegram/SourceFiles/data/data_session.cpp | 10 ++++++++++ Telegram/SourceFiles/data/data_session.h | 2 ++ Telegram/SourceFiles/history/history.cpp | 10 ++++++++++ Telegram/SourceFiles/mainwidget.cpp | 5 +++++ .../media/player/media_player_instance.cpp | 16 +++++++++++++--- .../media/player/media_player_instance.h | 1 + .../media/view/media_view_overlay_widget.cpp | 7 +++++++ Telegram/lib_spellcheck | 2 +- 8 files changed, 49 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index b748359af..3a186cc30 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_account.h" #include "storage/storage_encrypted_file.h" #include "media/player/media_player_instance.h" // instance()->play() +#include "media/audio/media_audio.h" #include "boxes/abstract_box.h" #include "passport/passport_form_controller.h" #include "window/themes/window_theme.h" @@ -3333,6 +3334,15 @@ void Session::unregisterContactItem( } } +void Session::documentMessageRemoved(not_null document) { + if (_documentItems.find(document) != _documentItems.end()) { + return; + } + if (document->loading()) { + document->cancel(); + } +} + void Session::checkPlayingAnimations() { auto check = base::flat_set>(); for (const auto view : _heavyViewParts) { diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index d0337ca86..52807a6e8 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -547,6 +547,8 @@ public: UserId contactId, not_null item); + void documentMessageRemoved(not_null document); + void checkPlayingAnimations(); HistoryItem *findWebPageItem(not_null page) const; diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index f640822d9..7c625f9e7 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_user.h" +#include "data/data_document.h" #include "data/data_histories.h" #include "lang/lang_keys.h" #include "apiwrap.h" @@ -423,6 +424,11 @@ void History::destroyMessage(not_null item) { session().api().cancelLocalItem(item); } + const auto document = [&] { + const auto media = item->media(); + return media ? media->document() : nullptr; + }(); + owner().unregisterMessage(item); Core::App().notifications().clearFromItem(item); @@ -432,6 +438,10 @@ void History::destroyMessage(not_null item) { Assert(i != end(_messages)); _messages.erase(i); + + if (document) { + session().data().documentMessageRemoved(document); + } } not_null History::addNewItem( diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index c0855a983..2c3b51af3 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -349,6 +349,11 @@ MainWidget::MainWidget( if (!songState.id || IsStoppedOrStopping(songState.state)) { closeBothPlayers(); } + } else if (type == AudioMsgId::Type::Song) { + const auto songState = Media::Player::instance()->getState(AudioMsgId::Type::Song); + if (!songState.id) { + closeBothPlayers(); + } } }); diff --git a/Telegram/SourceFiles/media/player/media_player_instance.cpp b/Telegram/SourceFiles/media/player/media_player_instance.cpp index c96d8940f..673f81490 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.cpp +++ b/Telegram/SourceFiles/media/player/media_player_instance.cpp @@ -203,10 +203,14 @@ void Instance::setSession(not_null data, Main::Session *session) { ) | rpl::start_with_next([=] { setSession(data, nullptr); }, data->sessionLifetime); + session->data().itemRemoved( + ) | rpl::filter([=](not_null item) { + return (data->current.contextId() == item->fullId()); + }) | rpl::start_with_next([=] { + stopAndClear(data); + }, data->sessionLifetime); } else { - stop(data->type); - _tracksFinishedNotifier.notify(data->type); - *data = Data(data->type, data->overview); + stopAndClear(data); } } @@ -527,6 +531,12 @@ void Instance::stop(AudioMsgId::Type type) { } } +void Instance::stopAndClear(not_null data) { + stop(data->type); + _tracksFinishedNotifier.notify(data->type); + *data = Data(data->type, data->overview); +} + void Instance::playPause(AudioMsgId::Type type) { if (const auto data = getData(type)) { if (!data->streamed) { diff --git a/Telegram/SourceFiles/media/player/media_player_instance.h b/Telegram/SourceFiles/media/player/media_player_instance.h index 2f2997e8e..07cba2a5b 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.h +++ b/Telegram/SourceFiles/media/player/media_player_instance.h @@ -217,6 +217,7 @@ private: void playlistUpdated(not_null data); bool moveInPlaylist(not_null data, int delta, bool autonext); HistoryItem *itemByIndex(not_null data, int index); + void stopAndClear(not_null data); void handleStreamingUpdate( not_null data, diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index fd2088e9b..a4ed8e449 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -3674,6 +3674,13 @@ void OverlayWidget::setSession(not_null session) { changingMsgId(change.item, change.oldId); }, _sessionLifetime); + session->data().itemRemoved( + ) | rpl::filter([=](not_null item) { + return (_document != nullptr) && (item->fullId() == _msgid); + }) | rpl::start_with_next([=] { + close(); + }, _sessionLifetime); + session->account().sessionChanges( ) | rpl::start_with_next([=] { clearSession(); diff --git a/Telegram/lib_spellcheck b/Telegram/lib_spellcheck index 58263c071..053e44a10 160000 --- a/Telegram/lib_spellcheck +++ b/Telegram/lib_spellcheck @@ -1 +1 @@ -Subproject commit 58263c0715ebfbf586c71e856a2ad962301bdc05 +Subproject commit 053e44a10107de4a0b7e9ad8826150c577db2a2b From f064692e5765007f3110a19a4ecf6fd24193167f Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 23 Oct 2020 15:28:20 +0300 Subject: [PATCH 028/190] Close media viewer when photo message is deleted. --- Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index a4ed8e449..10f12c9ee 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -3676,7 +3676,8 @@ void OverlayWidget::setSession(not_null session) { session->data().itemRemoved( ) | rpl::filter([=](not_null item) { - return (_document != nullptr) && (item->fullId() == _msgid); + return (_document != nullptr || _photo != nullptr) + && (item->fullId() == _msgid); }) | rpl::start_with_next([=] { close(); }, _sessionLifetime); From 53ac4c00ad347eb53bb58faf8e87549b0a5c51f6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 23 Oct 2020 16:35:43 +0300 Subject: [PATCH 029/190] Track deleted messages carefully. Fixes #8855. --- .../SourceFiles/data/data_replies_list.cpp | 87 ++++++++++++------- Telegram/SourceFiles/data/data_replies_list.h | 4 +- .../history/view/history_view_list_widget.cpp | 7 +- .../view/history_view_replies_section.cpp | 3 +- 4 files changed, 66 insertions(+), 35 deletions(-) diff --git a/Telegram/SourceFiles/data/data_replies_list.cpp b/Telegram/SourceFiles/data/data_replies_list.cpp index ce90f4ddd..b2004fa79 100644 --- a/Telegram/SourceFiles/data/data_replies_list.cpp +++ b/Telegram/SourceFiles/data/data_replies_list.cpp @@ -42,7 +42,10 @@ struct RepliesList::Viewer { MsgId around = 0; int limitBefore = 0; int limitAfter = 0; + int injectedForRoot = 0; base::has_weak_ptr guard; + bool stale = true; + bool scheduled = false; }; RepliesList::RepliesList(not_null history, MsgId rootId) @@ -67,7 +70,9 @@ rpl::producer RepliesList::source( _history->session().changes().historyFlagsValue( _history, Data::HistoryUpdate::Flag::LocalMessages) - ) | rpl::map([=](MessagesSlice &&server, const auto &) { + ) | rpl::filter([=](const MessagesSlice &data, const auto &) { + return (data.fullCount.value_or(0) >= 0); + }) | rpl::map([=](MessagesSlice &&server, const auto &) { appendLocalMessages(server); return std::move(server); }); @@ -82,10 +87,22 @@ rpl::producer RepliesList::sourceFromServer( auto lifetime = rpl::lifetime(); const auto viewer = lifetime.make_state(); const auto push = [=] { + viewer->scheduled = false; if (buildFromData(viewer)) { + viewer->stale = false; consumer.put_next_copy(viewer->slice); } }; + const auto pushDelayed = [=] { + if (!viewer->stale) { + viewer->stale = true; + consumer.put_next_copy(MessagesSlice{ .fullCount = -1 }); + } + if (!viewer->scheduled) { + viewer->scheduled = true; + crl::on_main(&viewer->guard, push); + } + }; viewer->around = around; viewer->limitBefore = limitBefore; viewer->limitAfter = limitAfter; @@ -96,14 +113,10 @@ rpl::producer RepliesList::sourceFromServer( | MessageUpdate::Flag::Destroyed ) | rpl::filter([=](const MessageUpdate &update) { return applyUpdate(viewer, update); - }) | rpl::start_with_next([=] { - crl::on_main(&viewer->guard, push); - }, lifetime); + }) | rpl::start_with_next(pushDelayed, lifetime); _partLoaded.events( - ) | rpl::start_with_next([=] { - crl::on_main(&viewer->guard, push); - }, lifetime); + ) | rpl::start_with_next(pushDelayed, lifetime); push(); return lifetime; @@ -173,32 +186,37 @@ rpl::producer RepliesList::fullCount() const { return _fullCount.value() | rpl::filter_optional(); } -void RepliesList::injectRootMessageAndReverse( - not_null slice) { - injectRootMessage(slice); - ranges::reverse(slice->ids); +void RepliesList::injectRootMessageAndReverse(not_null viewer) { + injectRootMessage(viewer); + ranges::reverse(viewer->slice.ids); } -void RepliesList::injectRootMessage(not_null slice) { +void RepliesList::injectRootMessage(not_null viewer) { + const auto slice = &viewer->slice; + viewer->injectedForRoot = 0; if (slice->skippedBefore != 0) { return; } - if (const auto root = lookupRoot()) { - injectRootDivider(root, slice); + const auto root = lookupRoot(); + if (!root) { + return; + } + injectRootDivider(root, slice); - if (const auto group = _history->owner().groups().find(root)) { - for (const auto item : ranges::view::reverse(group->items)) { - slice->ids.push_back(item->fullId()); - } - if (slice->fullCount) { - *slice->fullCount += group->items.size(); - } - } else { - slice->ids.push_back(root->fullId()); - if (slice->fullCount) { - ++*slice->fullCount; - } + if (const auto group = _history->owner().groups().find(root)) { + for (const auto item : ranges::view::reverse(group->items)) { + slice->ids.push_back(item->fullId()); } + viewer->injectedForRoot = group->items.size(); + if (slice->fullCount) { + *slice->fullCount += group->items.size(); + } + } else { + slice->ids.push_back(root->fullId()); + viewer->injectedForRoot = 1; + } + if (slice->fullCount) { + *slice->fullCount += viewer->injectedForRoot; } } @@ -232,7 +250,8 @@ bool RepliesList::buildFromData(not_null viewer) { = viewer->slice.skippedBefore = viewer->slice.skippedAfter = 0; - injectRootMessageAndReverse(&viewer->slice); + viewer->injectedForRoot = 0; + injectRootMessageAndReverse(viewer); return true; } const auto around = [&] { @@ -285,7 +304,7 @@ bool RepliesList::buildFromData(not_null viewer) { slice->ids.empty() ? 0 : slice->ids.back().msg)); slice->fullCount = _fullCount.current(); - injectRootMessageAndReverse(slice); + injectRootMessageAndReverse(viewer); if (_skippedBefore != 0 && useBefore < viewer->limitBefore + 1) { loadBefore(); @@ -301,10 +320,20 @@ bool RepliesList::applyUpdate( not_null viewer, const MessageUpdate &update) { if (update.item->history() != _history - || update.item->replyToTop() != _rootId || !IsServerMsgId(update.item->id)) { return false; } + if (update.flags & MessageUpdate::Flag::Destroyed) { + const auto id = update.item->fullId(); + for (auto i = 0; i != viewer->injectedForRoot; ++i) { + if (viewer->slice.ids[i] == id) { + return true; + } + } + } + if (update.item->replyToTop() != _rootId) { + return false; + } const auto id = update.item->id; const auto i = ranges::lower_bound(_list, id, std::greater<>()); if (update.flags & MessageUpdate::Flag::Destroyed) { diff --git a/Telegram/SourceFiles/data/data_replies_list.h b/Telegram/SourceFiles/data/data_replies_list.h index 2de7e2b7f..36932b82b 100644 --- a/Telegram/SourceFiles/data/data_replies_list.h +++ b/Telegram/SourceFiles/data/data_replies_list.h @@ -47,8 +47,8 @@ private: [[nodiscard]] bool applyUpdate( not_null viewer, const MessageUpdate &update); - void injectRootMessageAndReverse(not_null slice); - void injectRootMessage(not_null slice); + void injectRootMessageAndReverse(not_null viewer); + void injectRootMessage(not_null viewer); void injectRootDivider( not_null root, not_null slice); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index b841a248d..15aeb735e 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -292,9 +292,10 @@ ListWidget::ListWidget( }, lifetime()); session().data().itemRemoved( - ) | rpl::start_with_next( - [this](auto item) { itemRemoved(item); }, - lifetime()); + ) | rpl::start_with_next([=](not_null item) { + itemRemoved(item); + }, lifetime()); + subscribe(session().data().queryItemVisibility(), [this](const Data::Session::ItemVisibilityQuery &query) { if (const auto view = viewForItem(query.item)) { const auto top = itemTop(view); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index af0a9acae..26d9b0c4f 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -216,6 +216,7 @@ RepliesWidget::RepliesWidget( if (update.item == _root) { _root = nullptr; updatePinnedVisibility(); + controller->showBackFromStack(); } while (update.item == _replyReturn) { calculateNextReplyReturn(); @@ -1758,7 +1759,7 @@ MessagesBarData RepliesWidget::listMessagesBar( for (auto i = 0, count = int(elements.size()); i != count; ++i) { const auto item = elements[i]->data(); if (IsServerMsgId(item->id) && item->id > till) { - if (item->out()) { + if (item->out() || !item->replyToId()) { readTill(item); } else { return MessagesBarData{ From 0690d14f1b631ca89ff7d693714d41aeca4068e0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 23 Oct 2020 17:28:11 +0300 Subject: [PATCH 030/190] Don't send typings to bots and offline users. --- Telegram/SourceFiles/api/api_send_progress.cpp | 18 ++++++++++++++++++ Telegram/SourceFiles/api/api_send_progress.h | 2 ++ 2 files changed, 20 insertions(+) diff --git a/Telegram/SourceFiles/api/api_send_progress.cpp b/Telegram/SourceFiles/api/api_send_progress.cpp index e96b11da0..1335b6fb1 100644 --- a/Telegram/SourceFiles/api/api_send_progress.cpp +++ b/Telegram/SourceFiles/api/api_send_progress.cpp @@ -10,6 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "history/history.h" #include "data/data_peer.h" +#include "data/data_user.h" +#include "base/unixtime.h" +#include "data/data_peer_values.h" #include "apiwrap.h" namespace Api { @@ -98,6 +101,9 @@ bool SendProgressManager::updated(const Key &key, bool doing) { } void SendProgressManager::send(const Key &key, int progress) { + if (skipRequest(key)) { + return; + } using Type = SendProgressType; const auto action = [&]() -> MTPsendMessageAction { const auto p = MTP_int(progress); @@ -135,6 +141,18 @@ void SendProgressManager::send(const Key &key, int progress) { } } +bool SendProgressManager::skipRequest(const Key &key) const { + const auto user = key.history->peer->asUser(); + if (!user) { + return false; + } else if (user->isSelf()) { + return true; + } else if (user->isBot() && !user->isSupport()) { + return true; + } + return !Data::OnlineTextActive(user->onlineTill, base::unixtime::now()); +} + void SendProgressManager::done( const MTPBool &result, mtpRequestId requestId) { diff --git a/Telegram/SourceFiles/api/api_send_progress.h b/Telegram/SourceFiles/api/api_send_progress.h index dc5dc7802..22bed6ac1 100644 --- a/Telegram/SourceFiles/api/api_send_progress.h +++ b/Telegram/SourceFiles/api/api_send_progress.h @@ -90,6 +90,8 @@ private: void send(const Key &key, int progress); void done(const MTPBool &result, mtpRequestId requestId); + [[nodiscard]] bool skipRequest(const Key &key) const; + const not_null _session; base::flat_map _requests; base::flat_map _updated; From 1de144a48d9022b1261229a614db8a0d8f8d2972 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 23 Oct 2020 17:37:27 +0300 Subject: [PATCH 031/190] Show transfer ownership button for non-anonymous admins. --- Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp | 2 +- .../SourceFiles/boxes/peers/edit_peer_permissions_box.cpp | 6 ++++-- .../SourceFiles/boxes/peers/edit_peer_permissions_box.h | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp index d23abb04c..7f82a7f3b 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp @@ -308,7 +308,7 @@ void EditAdminBox::prepare() { }, lifetime()); if (canTransferOwnership()) { - const auto allFlags = FullAdminRights(isGroup); + const auto allFlags = AdminRightsForOwnershipTransfer(isGroup); setupTransferButton( isGroup )->toggleOn(rpl::duplicate( diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp index 29ac53e51..91e0a0dfe 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp @@ -298,10 +298,12 @@ ChatRestrictions FixDependentRestrictions(ChatRestrictions restrictions) { return restrictions; } -ChatAdminRights FullAdminRights(bool isGroup) { +ChatAdminRights AdminRightsForOwnershipTransfer(bool isGroup) { auto result = ChatAdminRights(); for (const auto &[flag, label] : AdminRightLabels(isGroup, true)) { - result |= flag; + if (!(flag & ChatAdminRight::f_anonymous)) { + result |= flag; + } } return result; } diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h index 53f16ea38..b7dfd4ed0 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h @@ -71,4 +71,4 @@ EditFlagsControl CreateEditAdminRights( ChatAdminRights DisabledByDefaultRestrictions(not_null peer); ChatRestrictions FixDependentRestrictions(ChatRestrictions restrictions); -ChatAdminRights FullAdminRights(bool isGroup); +ChatAdminRights AdminRightsForOwnershipTransfer(bool isGroup); From 9de4c425556ce8647f4610d77ff327bc96ea0a62 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 23 Oct 2020 18:25:55 +0300 Subject: [PATCH 032/190] Keep sending typings up to 30s after offline. --- Telegram/SourceFiles/api/api_send_progress.cpp | 4 +++- cmake | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/api/api_send_progress.cpp b/Telegram/SourceFiles/api/api_send_progress.cpp index 1335b6fb1..d4cfcba39 100644 --- a/Telegram/SourceFiles/api/api_send_progress.cpp +++ b/Telegram/SourceFiles/api/api_send_progress.cpp @@ -20,6 +20,7 @@ namespace { constexpr auto kCancelTypingActionTimeout = crl::time(5000); constexpr auto kSetMyActionForMs = 10 * crl::time(1000); +constexpr auto kSendTypingsToOfflineFor = TimeId(30); } // namespace @@ -150,7 +151,8 @@ bool SendProgressManager::skipRequest(const Key &key) const { } else if (user->isBot() && !user->isSupport()) { return true; } - return !Data::OnlineTextActive(user->onlineTill, base::unixtime::now()); + const auto recently = base::unixtime::now() - kSendTypingsToOfflineFor; + return !Data::OnlineTextActive(user->onlineTill, recently); } void SendProgressManager::done( diff --git a/cmake b/cmake index b485d43a4..cfc6051fb 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit b485d43a45eec75b8cbc0ab966ab3c06b5e8bac8 +Subproject commit cfc6051fb65da4c67ccbc2a1d9e753758f995fe3 From aff4f69b64f6887c8983930c4fa87d51e4091d4a Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 23 Oct 2020 19:37:58 +0300 Subject: [PATCH 033/190] Don't quit on call end with window hidden in tray. Fixes #8585. --- Telegram/SourceFiles/core/sandbox.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Telegram/SourceFiles/core/sandbox.cpp b/Telegram/SourceFiles/core/sandbox.cpp index f2989b99f..3f6f47cb5 100644 --- a/Telegram/SourceFiles/core/sandbox.cpp +++ b/Telegram/SourceFiles/core/sandbox.cpp @@ -90,6 +90,7 @@ Sandbox::Sandbox( } }) , _launcher(launcher) { + setQuitOnLastWindowClosed(false); } int Sandbox::start() { From 9717a8b5fa67e6e3afcc8a741a5a9e050d7b3966 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 23 Oct 2020 19:40:48 +0300 Subject: [PATCH 034/190] Version 2.4.4. - Fix application quit on call end with main window hidden in tray. - Update OpenAL library on Windows. - Several crash fixes. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 4 ++-- Telegram/build/version | 8 ++++---- changelog.txt | 6 ++++++ 6 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index e170c4e21..08ce1f0c2 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -9,7 +9,7 @@ + Version="2.4.4.0" /> Telegram Desktop Telegram FZ-LLC diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index b9b3048db..febd156ef 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 2,4,3,0 - PRODUCTVERSION 2,4,3,0 + FILEVERSION 2,4,4,0 + PRODUCTVERSION 2,4,4,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "2.4.3.0" + VALUE "FileVersion", "2.4.4.0" VALUE "LegalCopyright", "Copyright (C) 2014-2020" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "2.4.3.0" + VALUE "ProductVersion", "2.4.4.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 0636b1189..d7427a781 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 2,4,3,0 - PRODUCTVERSION 2,4,3,0 + FILEVERSION 2,4,4,0 + PRODUCTVERSION 2,4,4,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "2.4.3.0" + VALUE "FileVersion", "2.4.4.0" VALUE "LegalCopyright", "Copyright (C) 2014-2020" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "2.4.3.0" + VALUE "ProductVersion", "2.4.4.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 2d2c541e6..15003e6d0 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs; constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs; constexpr auto AppName = "Telegram Desktop"_cs; constexpr auto AppFile = "Telegram"_cs; -constexpr auto AppVersion = 2004003; -constexpr auto AppVersionStr = "2.4.3"; +constexpr auto AppVersion = 2004004; +constexpr auto AppVersionStr = "2.4.4"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 7c371f936..d76bc6152 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 2004003 +AppVersion 2004004 AppVersionStrMajor 2.4 -AppVersionStrSmall 2.4.3 -AppVersionStr 2.4.3 +AppVersionStrSmall 2.4.4 +AppVersionStr 2.4.4 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 2.4.3 +AppVersionOriginal 2.4.4 diff --git a/changelog.txt b/changelog.txt index 6e0fc2ebd..47c2c7748 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ +2.4.4 (23.10.20) + +- Fix application quit on call end with main window hidden in tray. +- Update OpenAL library on Windows. +- Several crash fixes. + 2.4.3 (07.10.20) - Fix sending voice messages in scheduled messages section. From 77e1b9f156511adc8d7fb873c5efdfb4003fd292 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sat, 24 Oct 2020 08:47:16 +0400 Subject: [PATCH 035/190] Change socket path length condition to >= Looks like 108 is the length including \0, therefore actual limit is 107 --- Telegram/SourceFiles/platform/linux/specific_linux.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index d88e91c80..92fc41d78 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -815,7 +815,7 @@ QString SingleInstanceLocalServerName(const QString &hash) { + '-' + cGUIDStr(); - if (idealSocketPath.size() > 108) { + if (idealSocketPath.size() >= 108) { return AppRuntimeDirectory() + hash; } else { return idealSocketPath; From f7496475674792360f1f81f72026b127b5d19616 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 26 Oct 2020 07:27:17 +0400 Subject: [PATCH 036/190] Check Qt version in runtime in CanOpenDirectoryWithPortal It is not guaranteed that app is running with the same Qt version that was compiled --- .../platform/linux/specific_linux.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 92fc41d78..6b1f01189 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include +#include #include #include @@ -756,11 +757,20 @@ bool UseXDGDesktopPortal() { } bool CanOpenDirectoryWithPortal() { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) || defined DESKTOP_APP_QT_PATCHED) && !defined DESKTOP_APP_DISABLE_DBUS_INTEGRATION - return FileChooserPortalVersion() >= 3; -#else // (Qt >= 5.15 || DESKTOP_APP_QT_PATCHED) && !DESKTOP_APP_DISABLE_DBUS_INTEGRATION +#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION + static const auto Result = [&] { +#ifdef DESKTOP_APP_QT_PATCHED + return FileChooserPortalVersion() >= 3; +#else // DESKTOP_APP_QT_PATCHED + return QLibraryInfo::version() >= QVersionNumber(5, 15, 0) + && FileChooserPortalVersion() >= 3; +#endif // !DESKTOP_APP_QT_PATCHED + }(); + + return Result; +#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION + return false; -#endif // (Qt < 5.15 && !DESKTOP_APP_QT_PATCHED) || DESKTOP_APP_DISABLE_DBUS_INTEGRATION } QString CurrentExecutablePath(int argc, char *argv[]) { From b0ce88395ffe2abc1d7fbef7d258a9b60bf738aa Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 26 Oct 2020 12:02:02 +0400 Subject: [PATCH 037/190] Don't stream videos when external player is used External player feature doesn't work otherwise --- Telegram/SourceFiles/data/data_document.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index f9fcac1e9..b80953ba5 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -1320,8 +1320,11 @@ bool DocumentData::useStreamingLoader() const { } bool DocumentData::canBeStreamed() const { - // For now video messages are not streamed. - return hasRemoteLocation() && supportsStreaming(); + // Streaming couldn't be used with external player + // Maybe someone brave will implement this once upon a time... + return hasRemoteLocation() + && supportsStreaming() + && (!cUseExternalVideoPlayer() || !isVideoFile()); } void DocumentData::setInappPlaybackFailed() { From 7ff99cdbf702936c701e13e0e52aeb6003c9f0da Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 25 Oct 2020 04:59:14 +0400 Subject: [PATCH 038/190] Use LONG_PTR with SetWindowLongPtr --- Telegram/SourceFiles/platform/win/main_window_win.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/platform/win/main_window_win.cpp b/Telegram/SourceFiles/platform/win/main_window_win.cpp index 8c05a1df2..10d625363 100644 --- a/Telegram/SourceFiles/platform/win/main_window_win.cpp +++ b/Telegram/SourceFiles/platform/win/main_window_win.cpp @@ -252,7 +252,7 @@ void MainWindow::workmodeUpdated(DBIWorkMode mode) { psSetupTrayIcon(); HWND psOwner = (HWND)GetWindowLongPtr(ps_hWnd, GWLP_HWNDPARENT); if (!psOwner) { - SetWindowLongPtr(ps_hWnd, GWLP_HWNDPARENT, (LONG)ps_tbHider_hWnd); + SetWindowLongPtr(ps_hWnd, GWLP_HWNDPARENT, (LONG_PTR)ps_tbHider_hWnd); } } break; From 022fc9a779b2eeba2fb55611333289bbff98cfca Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 25 Oct 2020 06:34:11 +0400 Subject: [PATCH 039/190] Fix gtk dialog condition + make it more readable --- .../platform/linux/file_utilities_linux.cpp | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp index a4ca9421b..a1b7db22b 100644 --- a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp @@ -125,19 +125,26 @@ constexpr auto kPreviewHeight = 512; using Type = ::FileDialog::internal::Type; #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION -bool NativeSupported(Type type = Type::ReadFile) { +bool UseNative(Type type = Type::ReadFile) { // use gtk file dialog on gtk-based desktop environments // or if QT_QPA_PLATFORMTHEME=(gtk2|gtk3) - // or if portals is used and operation is to open folder + // or if portals are used and operation is to open folder // and portal doesn't support folder choosing - return (Platform::DesktopEnvironment::IsGtkBased() - || Platform::IsGtkIntegrationForced() - || Platform::UseXDGDesktopPortal()) - && ((!Platform::UseXDGDesktopPortal() && - ((!Platform::InFlatpak() && !Platform::InSnap()) - || Platform::IsGtkIntegrationForced())) - || (type == Type::ReadFolder && !Platform::CanOpenDirectoryWithPortal())) - && Platform::internal::GdkHelperLoaded() + const auto neededForPortal = UseXDGDesktopPortal() + && type == Type::ReadFolder + && !CanOpenDirectoryWithPortal(); + + const auto neededNonForced = DesktopEnvironment::IsGtkBased() + || neededForPortal; + + const auto excludeNonForced = InFlatpak() || InSnap(); + + return IsGtkIntegrationForced() + || (neededNonForced && !excludeNonForced); +} + +bool NativeSupported() { + return Platform::internal::GdkHelperLoaded() && (Libs::gtk_widget_hide_on_delete != nullptr) && (Libs::gtk_clipboard_store != nullptr) && (Libs::gtk_clipboard_get != nullptr) @@ -241,7 +248,7 @@ bool Get( parent = parent->window(); } #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - if (NativeSupported(type)) { + if (UseNative(type) && NativeSupported()) { return GetNative( parent, files, From 9ab221d4c9d6d024fe292331befc23293fe70865 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 26 Oct 2020 07:43:45 +0400 Subject: [PATCH 040/190] Remove Platform:: where is not needed in linux platform files Fix lines length in notifications_manager_linux.cpp --- .../SourceFiles/platform/linux/linux_libs.cpp | 8 ++++---- .../linux/notifications_manager_linux.cpp | 17 +++++++++++------ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/linux_libs.cpp b/Telegram/SourceFiles/platform/linux/linux_libs.cpp index a57cf7a0f..50f53f37a 100644 --- a/Telegram/SourceFiles/platform/linux/linux_libs.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_libs.cpp @@ -115,7 +115,7 @@ bool setupGtkBase(QLibrary &lib_gtk) { // Otherwise we get segfault in Ubuntu 17.04 in gtk_init_check() call. // See https://github.com/telegramdesktop/tdesktop/issues/3176 // See https://github.com/telegramdesktop/tdesktop/issues/3162 - if(Platform::IsWayland()) { + if(IsWayland()) { DEBUG_LOG(("Limit allowed GDK backends to wayland,x11")); gdk_set_allowed_backends("wayland,x11"); } else { @@ -176,7 +176,7 @@ void SetIconTheme() { DEBUG_LOG(("New icon theme: %1").arg(QIcon::themeName())); DEBUG_LOG(("New fallback icon theme: %1").arg(QIcon::fallbackThemeName())); - Platform::SetApplicationIcon(Window::CreateIcon()); + SetApplicationIcon(Window::CreateIcon()); if (App::wnd()) { App::wnd()->setWindowIcon(Window::CreateIcon()); } @@ -188,13 +188,13 @@ void SetIconTheme() { void DarkModeChanged() { Core::Sandbox::Instance().customEnterFromEventLoop([] { - Core::App().settings().setSystemDarkMode(Platform::IsDarkMode()); + Core::App().settings().setSystemDarkMode(IsDarkMode()); }); } void DecorationLayoutChanged() { Core::Sandbox::Instance().customEnterFromEventLoop([] { - Core::App().settings().setWindowControlsLayout(Platform::WindowControlsLayout()); + Core::App().settings().setWindowControlsLayout(WindowControlsLayout()); }); } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index 1a0b1bc18..b4a0d8f83 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -87,8 +87,7 @@ void GetSupported() { } Checked = true; - if (Core::App().settings().nativeNotifications() - && !Platform::IsWayland()) { + if (Core::App().settings().nativeNotifications() && !IsWayland()) { ComputeSupported(true); } else { ComputeSupported(); @@ -402,15 +401,21 @@ NotificationData::NotificationData( NotificationData::~NotificationData() { if (_dbusConnection) { if (_actionInvokedSignalId != 0) { - g_dbus_connection_signal_unsubscribe(_dbusConnection, _actionInvokedSignalId); + g_dbus_connection_signal_unsubscribe( + _dbusConnection, + _actionInvokedSignalId); } if (_notificationRepliedSignalId != 0) { - g_dbus_connection_signal_unsubscribe(_dbusConnection, _notificationRepliedSignalId); + g_dbus_connection_signal_unsubscribe( + _dbusConnection, + _notificationRepliedSignalId); } if (_notificationClosedSignalId != 0) { - g_dbus_connection_signal_unsubscribe(_dbusConnection, _notificationClosedSignalId); + g_dbus_connection_signal_unsubscribe( + _dbusConnection, + _notificationClosedSignalId); } g_object_unref(_dbusConnection); @@ -647,7 +652,7 @@ std::unique_ptr Create( #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION if ((Core::App().settings().nativeNotifications() && Supported()) - || Platform::IsWayland()) { + || IsWayland()) { return std::make_unique(system); } From 41e0e4fba703eddfe599ab8935cf0b64549a933f Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 27 Oct 2020 09:03:50 +0400 Subject: [PATCH 041/190] Use glib to open files & urls --- .../platform/linux/file_utilities_linux.cpp | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp index a1b7db22b..dfecc5b79 100644 --- a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp @@ -18,6 +18,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +extern "C" { +#undef signals +#include +#define signals public +} // extern "C" + #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION #include @@ -63,38 +69,30 @@ QByteArray EscapeShell(const QByteArray &content) { void UnsafeOpenUrl(const QString &url) { if (InSnap()) { - const QStringList arguments{ - url - }; QProcess process; - process.startDetached(qsl("xdg-open"), arguments); - } else { + process.startDetached(qsl("xdg-open"), {url}); + } else if (!g_app_info_launch_default_for_uri( + url.toUtf8(), + nullptr, + nullptr)) { QDesktopServices::openUrl(url); } } void UnsafeOpenEmailLink(const QString &email) { - const auto url = qstr("mailto:") + email; - - if (InSnap()) { - const QStringList arguments{ - url - }; - QProcess process; - process.startDetached(qsl("xdg-open"), arguments); - } else { - QDesktopServices::openUrl(QUrl(url)); - } + UnsafeOpenUrl(qstr("mailto:") + email); } void UnsafeLaunch(const QString &filepath) { + const auto absolutePath = QFileInfo(filepath).absoluteFilePath(); + if (InSnap()) { - const QStringList arguments{ - QFileInfo(filepath).absoluteFilePath() - }; QProcess process; - process.startDetached(qsl("xdg-open"), arguments); - } else { + process.startDetached(qsl("xdg-open"), {absolutePath}); + } else if (!g_app_info_launch_default_for_uri( + ("file://" + absolutePath).toUtf8(), + nullptr, + nullptr)) { QDesktopServices::openUrl(QUrl::fromLocalFile(filepath)); } } From 08b513dd7e3af1f37780374e1509421f746d7474 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 27 Oct 2020 17:45:39 +0300 Subject: [PATCH 042/190] Update lib_base submodule. --- Telegram/lib_base | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_base b/Telegram/lib_base index 98224799e..55c28456e 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 98224799e420dfcca33af816b11e261080cb1fed +Subproject commit 55c28456e812b6f69a08220d9b3da01ad290489e From b814f320c610668f4b3199751d6593813ba80252 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 27 Oct 2020 18:31:00 +0300 Subject: [PATCH 043/190] Update lib_base submodule. --- Telegram/lib_base | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_base b/Telegram/lib_base index 55c28456e..df430bc4a 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 55c28456e812b6f69a08220d9b3da01ad290489e +Subproject commit df430bc4a27ec62b736d2f43c4b5feacc79605f4 From bcc11d78503d68a9af5597dbd490c413902afc73 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 24 Oct 2020 23:48:19 +0300 Subject: [PATCH 044/190] Changed shortcut to open contacts to avoid conflict with formatting. Fixed #8896. --- Telegram/SourceFiles/core/shortcuts.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/core/shortcuts.cpp b/Telegram/SourceFiles/core/shortcuts.cpp index ef50008ef..4e988bd13 100644 --- a/Telegram/SourceFiles/core/shortcuts.cpp +++ b/Telegram/SourceFiles/core/shortcuts.cpp @@ -385,7 +385,7 @@ void Manager::fillDefaults() { set(qsl("ctrl+0"), Command::ChatSelf); set(qsl("ctrl+9"), Command::ShowArchive); - set(qsl("ctrl+shift+n"), Command::ShowContacts); + set(qsl("ctrl+j"), Command::ShowContacts); set(qsl("ctrl+r"), Command::ReadChat); } From 1a2afda09ca38606fff897a89c03cfd296f72300 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 25 Oct 2020 02:58:59 +0300 Subject: [PATCH 045/190] Changed behavior of user-agent updater to open pull requests. --- .github/workflows/user_agent_updater.yml | 110 ++++++++++++++++++----- 1 file changed, 87 insertions(+), 23 deletions(-) diff --git a/.github/workflows/user_agent_updater.yml b/.github/workflows/user_agent_updater.yml index 44c2515af..eee5c8b2c 100644 --- a/.github/workflows/user_agent_updater.yml +++ b/.github/workflows/user_agent_updater.yml @@ -6,22 +6,53 @@ on: schedule: # At 00:00 on day-of-month 1. - cron: '0 0 1 * *' + pull_request_target: + types: [closed] jobs: User-agent: runs-on: ubuntu-latest env: - code_file: "Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp" - skip: "0" + codeFile: "Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp" + headBranchPrefix: "chrome_" + baseBranch: "dev" + isPull: "0" steps: + - name: Set env. + if: startsWith(github.event_name, 'pull_request') + run: | + echo "isPull=1" >> $GITHUB_ENV + - name: Clone. - if: env.skip == '0' uses: actions/checkout@v2 + - name: Set up git. + run: | + token=${{ secrets.TOKEN_FOR_MASTER_UPDATER }} + if [ -z "${token}" ]; then + echo "Token is unset. Nothing to do." + exit 1 + fi + + url=https://x-access-token:$token@github.com/$GITHUB_REPOSITORY + + git config --global user.email "action@github.com" + git config --global user.name "GitHub Action" + + git remote set-url origin $url + + - name: Delete branch. + if: | + env.isPull == '1' + && github.event.action == 'closed' + && startsWith(github.head_ref, env.headBranchPrefix) + run: | + git push origin --delete ${{ github.head_ref }} + - name: Write a new version of Google Chrome to the user-agent for DNS. - if: env.skip == '0' + if: env.isPull == '0' shell: python run: | import subprocess, os, re; @@ -40,8 +71,11 @@ jobs: newChromeVersion = newVersion(); print(newChromeVersion); + def setEnv(value): + open(os.environ['GITHUB_ENV'], "a").write(value); + def writeUserAgent(): - p = os.environ['code_file']; + p = os.environ['codeFile']; w = open(p, "r"); content = w.read(); w.close(); @@ -57,34 +91,64 @@ jobs: w = open(p, "w"); w.write(content); - print("::set-env name=ChromeVersion::" + newChromeVersion); + setEnv("ChromeVersion=" + newChromeVersion); writeUserAgent(); - - name: Push to the current branch. - if: env.skip == '0' && env.ChromeVersion != '' + - name: Push to a new branch. + if: env.isPull == '0' && env.ChromeVersion != '' run: | - token=${{ secrets.TOKEN_FOR_MASTER_UPDATER }} - if [ -z "${token}" ]; then - echo "Token is unset. Nothing to do." - exit 0 - fi - - url=https://x-access-token:$token@github.com/$GITHUB_REPOSITORY - - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git diff > git_diff.txt if [[ ! -s git_diff.txt ]]; then echo "Nothing to commit." exit 0 fi - git add $code_file + git checkout -b $headBranchPrefix$ChromeVersion + git add $codeFile git commit -m "Update User-Agent for DNS to Chrome $ChromeVersion." - git remote set-url origin $url - - git push origin HEAD:$GITHUB_REF + git push origin $headBranchPrefix$ChromeVersion echo "Done!" + + - name: Close previous pull requests. + if: env.isPull == '0' && env.ChromeVersion != '' + uses: actions/github-script@0.4.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const common = { + owner: context.repo.owner, + repo: context.repo.repo, + }; + + github.pulls.list(common).then(response => { + response.data.forEach((item, _) => { + if (item.head.ref.startsWith(process.env.headBranchPrefix)) { + console.log(`Close ${item.title} #${item.number}.`); + github.pulls.update({ + pull_number: item.number, + state: "closed", + ...common + }); + } + }); + }); + + - name: Create a new pull request. + if: env.isPull == '0' && env.ChromeVersion != '' + uses: actions/github-script@0.4.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const version = process.env.ChromeVersion; + const title = `Update User-Agent for DNS to Chrome ${version}.`; + + github.pulls.create({ + title: title, + body: "", + head: `${process.env.headBranchPrefix}${version}`, + base: process.env.baseBranch, + owner: context.repo.owner, + repo: context.repo.repo, + }); From 3883a268c742cf743e0259275bb02db5a325b56a Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 25 Oct 2020 04:46:27 +0300 Subject: [PATCH 046/190] Fixed warnings from Github CI. --- .github/workflows/issue_closer.yml | 4 +- .github/workflows/linux.yml | 10 ++-- .github/workflows/mac.yml | 12 ++--- .github/workflows/snap.yml | 2 +- .github/workflows/win.yml | 85 +++++++++++++----------------- 5 files changed, 50 insertions(+), 63 deletions(-) diff --git a/.github/workflows/issue_closer.yml b/.github/workflows/issue_closer.yml index db58d86bd..44782fa45 100644 --- a/.github/workflows/issue_closer.yml +++ b/.github/workflows/issue_closer.yml @@ -12,7 +12,7 @@ jobs: run: | tag=$(git ls-remote --tags git://github.com/$GITHUB_REPOSITORY | cut -f 2 | tail -n1) echo $tag - echo ::set-env name=LATEST_TAG::$tag + echo "LATEST_TAG=$tag" >> $GITHUB_ENV - name: Get the latest macOS version. shell: python @@ -28,7 +28,7 @@ jobs: ver = itemlist[0].attributes['sparkle:shortVersionString'].value; print(ver); - subprocess.check_call("echo ::set-env name=%s::%s" % ("LATEST_MACOS", ver), shell=True); + open(os.environ['GITHUB_ENV'], "a").write("LATEST_MACOS=" + ver); - name: Check a version from an issue. uses: actions/github-script@0.4.0 diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index aac77a1b7..7b4f32953 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -71,7 +71,7 @@ jobs: steps: - name: Get repository name. - run: echo ::set-env name=REPO_NAME::${GITHUB_REPOSITORY##*/} + run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV - name: Disable man for further package installs. run: | @@ -129,11 +129,11 @@ jobs: echo `md5sum $thisFile | cut -c -32` >> CACHE_KEY.txt fi md5cache=$(md5sum CACHE_KEY.txt | cut -c -32) - echo ::set-env name=CACHE_KEY::$md5cache + echo "CACHE_KEY=$md5cache" >> $GITHUB_ENV mkdir -p Libraries cd Libraries - echo ::set-env name=LibrariesPath::`pwd` + echo "LibrariesPath=`pwd`" >> $GITHUB_ENV - name: Patches. run: | @@ -556,9 +556,9 @@ jobs: if [ -n "${{ matrix.defines }}" ]; then DEFINE="-D ${{ matrix.defines }}=ON" echo Define from matrix: $DEFINE - echo ::set-env name=ARTIFACT_NAME::Telegram_${{ matrix.defines }} + echo "ARTIFACT_NAME=Telegram_${{ matrix.defines }}" >> $GITHUB_ENV else - echo ::set-env name=ARTIFACT_NAME::Telegram + echo "ARTIFACT_NAME=Telegram" >> $GITHUB_ENV fi ./configure.sh \ diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 2d1ff6613..11ace8da8 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -69,7 +69,7 @@ jobs: steps: - name: Get repository name. - run: echo ::set-env name=REPO_NAME::${GITHUB_REPOSITORY##*/} + run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV - name: Clone. uses: actions/checkout@v2 @@ -94,13 +94,13 @@ jobs: thisFile=$REPO_NAME/.github/workflows/mac.yml echo `md5 -q $thisFile` >> CACHE_KEY.txt fi - echo ::set-env name=CACHE_KEY::`md5 -q CACHE_KEY.txt` + echo "CACHE_KEY=`md5 -q CACHE_KEY.txt`" >> $GITHUB_ENV - echo ::add-path::$PWD/Libraries/depot_tools + echo "$PWD/Libraries/depot_tools" >> $GITHUB_PATH mkdir -p Libraries/macos cd Libraries/macos - echo ::set-env name=LibrariesPath::`pwd` + echo "LibrariesPath=`pwd`" >> $GITHUB_ENV - name: Patches. run: | @@ -490,9 +490,9 @@ jobs: if [ -n "${{ matrix.defines }}" ]; then DEFINE="-D ${{ matrix.defines }}=ON" echo Define from matrix: $DEFINE - echo ::set-env name=ARTIFACT_NAME::Telegram_${{ matrix.defines }} + echo "ARTIFACT_NAME=Telegram_${{ matrix.defines }}" >> $GITHUB_ENV else - echo ::set-env name=ARTIFACT_NAME::Telegram + echo "ARTIFACT_NAME=Telegram" >> $GITHUB_ENV fi ./configure.sh \ diff --git a/.github/workflows/snap.yml b/.github/workflows/snap.yml index b6a778d8e..21332ab2d 100644 --- a/.github/workflows/snap.yml +++ b/.github/workflows/snap.yml @@ -76,7 +76,7 @@ jobs: if: env.UPLOAD_ARTIFACT == 'true' run: | artifact_name=$(echo telegram-desktop_*.snap) - echo ::set-env name=ARTIFACT_NAME::$artifact_name + echo "ARTIFACT_NAME=$artifact_name" >> $GITHUB_ENV mkdir artifact mv $artifact_name artifact diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 032d66342..02140b397 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -68,10 +68,31 @@ jobs: DOC_PATH: "docs/building-msvc.md" AUTO_CACHING: "1" + defaults: + run: + shell: cmd + steps: - name: Get repository name. shell: bash - run: echo ::set-env name=REPO_NAME::${GITHUB_REPOSITORY##*/} + run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV + + - name: Set up environment paths. + shell: bash + run: | + echo "C:\\Strawberry\\perl\\bin\\" >> $GITHUB_PATH + echo "C:\\Program Files\\NASM\\" >> $GITHUB_PATH + echo "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Enterprise\\VC\\Auxiliary\\Build\\" >> $GITHUB_PATH + + mkdir Libraries && cd Libraries + echo "Convert unix path to win path." + p=`pwd | sed 's#^/[d]#d:#g' |sed 's#/#\\\\#g'` + echo "LibrariesPath=$p" >> $GITHUB_ENV + + - name: Save msbuild version. + run: | + call vcvars32.bat + msbuild -version > CACHE_KEY.txt - name: Clone. uses: actions/checkout@v2 @@ -79,38 +100,18 @@ jobs: submodules: recursive path: ${{ env.REPO_NAME }} - - name: Set up environment variables. - shell: cmd - run: | - echo ::add-path::C:\Strawberry\perl\bin\ - echo ::add-path::"%programfiles%\NASM" - - C: - cd "%programfiles(x86)%\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\" - echo ::add-path::%cd% - - call vcvars32.bat - D: - cd %GITHUB_WORKSPACE% - msbuild -version > CACHE_KEY.txt - echo %MANUAL_CACHING% >> CACHE_KEY.txt - - mkdir Libraries - cd Libraries - echo ::set-env name=LibrariesPath::%cd% - - name: Generate cache key. shell: bash run: | + echo $MANUAL_CACHING >> CACHE_KEY.txt if [ "$AUTO_CACHING" == "1" ]; then thisFile=$REPO_NAME/.github/workflows/win.yml echo `md5sum $thisFile | awk '{ print $1 }'` >> CACHE_KEY.txt fi - echo ::set-env name=CACHE_KEY::`md5sum CACHE_KEY.txt | awk '{ print $1 }'` + echo "CACHE_KEY=`md5sum CACHE_KEY.txt | awk '{ print $1 }'`" >> $GITHUB_ENV - name: Choco installs. - run: | - choco install --no-progress -y nasm yasm jom ninja + run: choco install --no-progress -y nasm yasm jom ninja - name: Patches. shell: bash @@ -123,21 +124,18 @@ jobs: eval $checkoutCommit - name: Find any version of Python 2. - shell: cmd + shell: bash run: | - echo Find any version of Python 2. - for /D %%a in (C:\hostedtoolcache\windows\Python\2.*) do ( - SET PY2=%%a\x64 - ) - if [%PY2%] == [] ( - echo Python 2 is not found. + echo "Find any version of Python 2." + p=`ls /c/hostedtoolcache/windows/python | grep 2 | tail -1` + if [ -z "$p" ]; then + echo "Python 2 is not found." exit 1 - ) - echo Found %PY2%. - echo ::set-env name=PY2::%PY2% + fi + echo "PY2=C:\\hostedtoolcache\\windows\\Python\\$p\\x64" >> $GITHUB_ENV + echo "Found $p." - name: LZMA. - shell: cmd run: | %VC% @@ -154,7 +152,6 @@ jobs: key: ${{ runner.OS }}-${{ env.CACHE_KEY }}-${{ env.OPENSSL_VER }} - name: OpenSSL. if: steps.cache-openssl.outputs.cache-hit != 'true' - shell: cmd run: | %VC% @@ -180,7 +177,6 @@ jobs: rmdir /S /Q .git - name: Zlib. - shell: cmd run: | %VC% @@ -197,7 +193,6 @@ jobs: path: ${{ env.LibrariesPath }}/openal-soft key: ${{ runner.OS }}-openal-soft-${{ env.CACHE_KEY }} - name: OpenAL Soft. - shell: cmd if: steps.cache-openal.outputs.cache-hit != 'true' run: | %VC% @@ -225,7 +220,6 @@ jobs: env: GYP_MSVS_OVERRIDE_PATH: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\' GYP_MSVS_VERSION: 2019 - shell: cmd if: steps.cache-breakpad.outputs.cache-hit != 'true' run: | cd %LibrariesPath% @@ -262,7 +256,6 @@ jobs: key: ${{ runner.OS }}-opus-${{ env.CACHE_KEY }} - name: Opus. if: steps.cache-opus.outputs.cache-hit != 'true' - shell: cmd run: | %VC% @@ -281,7 +274,6 @@ jobs: key: ${{ runner.OS }}-ffmpeg-${{ env.CACHE_KEY }}-2-${{ hashFiles('**/build_ffmpeg_win.sh') }} - name: FFmpeg. if: steps.cache-ffmpeg.outputs.cache-hit != 'true' - shell: cmd run: | %VC% choco install --no-progress -y msys2 @@ -303,7 +295,6 @@ jobs: key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qtbase_5_12_8/*') }} - name: Configure Qt 5.12.8. if: steps.cache-qt.outputs.cache-hit != 'true' - shell: cmd run: | %VC% @@ -338,7 +329,6 @@ jobs: -platform win32-msvc - name: Qt 5.12.8 build. if: steps.cache-qt.outputs.cache-hit != 'true' - shell: cmd run: | %VC% cd qt_%QT% @@ -357,7 +347,6 @@ jobs: key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }} - name: WebRTC. if: steps.cache-webrtc.outputs.cache-hit != 'true' - shell: cmd run: | %VC% @@ -407,15 +396,14 @@ jobs: if [ -n "${{ matrix.defines }}" ]; then DEFINE="-D ${{ matrix.defines }}=ON" echo Define from matrix: $DEFINE - echo ::set-env name=ARTIFACT_NAME::Telegram_${{ matrix.defines }} + echo "ARTIFACT_NAME=Telegram_${{ matrix.defines }}" >> $GITHUB_ENV else - echo ::set-env name=ARTIFACT_NAME::Telegram + echo "ARTIFACT_NAME=Telegram" >> $GITHUB_ENV fi - echo "::set-env name=TDESKTOP_BUILD_DEFINE::$DEFINE" + echo "TDESKTOP_BUILD_DEFINE=$DEFINE" >> $GITHUB_ENV - name: Telegram Desktop build. if: env.ONLY_CACHE == 'false' - shell: cmd run: | cd %REPO_NAME%\Telegram @@ -432,7 +420,6 @@ jobs: - name: Move artifact. if: env.UPLOAD_ARTIFACT == 'true' - shell: cmd run: | cd %REPO_NAME%\out\Debug mkdir artifact From fbacb6c0a4d73d845b655afaa349ff12848fc169 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 27 Oct 2020 17:24:51 +0000 Subject: [PATCH 047/190] Update User-Agent for DNS to Chrome 86.0.4240.75. --- .../SourceFiles/mtproto/details/mtproto_domain_resolver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp b/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp index 8cca31f3c..91cbf1e01 100644 --- a/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp +++ b/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp @@ -65,7 +65,7 @@ QByteArray DnsUserAgent() { static const auto kResult = QByteArray( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/85.0.4183.102 Safari/537.36"); + "Chrome/86.0.4240.75 Safari/537.36"); return kResult; } From 152f1ef17f904058fce8313b53538ea12a022ab8 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 27 Oct 2020 23:30:49 +0400 Subject: [PATCH 048/190] Use Core::UpdaterDisabled where a check is for installed or not --- .../SourceFiles/platform/linux/specific_linux.cpp | 14 ++++++-------- .../SourceFiles/platform/linux/specific_linux.h | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 6b1f01189..ae7122f5c 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -252,7 +252,7 @@ bool GenerateDesktopFile( QFile target(targetFile); if (target.open(QIODevice::WriteOnly)) { - if (IsStaticBinary() || InAppImage()) { + if (!Core::UpdaterDisabled()) { fileText = fileText.replace( QRegularExpression( qsl("^TryExec=.*$"), @@ -719,7 +719,7 @@ bool IsGtkIntegrationForced() { return false; } -bool IsQtPluginsBundled() { +bool AreQtPluginsBundled() { #ifdef DESKTOP_APP_USE_PACKAGED_LAZY return true; #else // DESKTOP_APP_USE_PACKAGED_LAZY @@ -834,7 +834,7 @@ QString SingleInstanceLocalServerName(const QString &hash) { QString GetLauncherBasename() { static const auto Result = [&] { - if ((IsStaticBinary() || InAppImage()) && !cExeName().isEmpty()) { + if (!Core::UpdaterDisabled() && !cExeName().isEmpty()) { const auto appimagePath = qsl("file://%1%2") .arg(cExeDir()) .arg(cExeName()) @@ -1244,13 +1244,11 @@ void start() { "this may lead to font issues."); #endif // DESKTOP_APP_USE_PACKAGED_FONTS - if (IsQtPluginsBundled()) { + if (AreQtPluginsBundled()) { qputenv("QT_WAYLAND_DECORATION", "material"); } - if ((IsStaticBinary() - || InAppImage() - || IsQtPluginsBundled()) + if (AreQtPluginsBundled() // it is handled by Qt for flatpak and snap && !InFlatpak() && !InSnap()) { @@ -1319,7 +1317,7 @@ void RegisterCustomScheme(bool force) { GError *error = nullptr; const auto neededCommandlineBuilder = qsl("%1 --") - .arg((IsStaticBinary() || InAppImage()) + .arg(!Core::UpdaterDisabled() ? cExeDir() + cExeName() : cExeName()); diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.h b/Telegram/SourceFiles/platform/linux/specific_linux.h index 1772a3c87..f5cae2472 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.h +++ b/Telegram/SourceFiles/platform/linux/specific_linux.h @@ -26,7 +26,7 @@ bool InAppImage(); bool IsStaticBinary(); bool UseGtkIntegration(); bool IsGtkIntegrationForced(); -bool IsQtPluginsBundled(); +bool AreQtPluginsBundled(); bool IsXDGDesktopPortalPresent(); bool UseXDGDesktopPortal(); From 3742db2b91dbb25f6f2e8730142be88d79ee84dc Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 27 Oct 2020 22:56:24 +0400 Subject: [PATCH 049/190] Use portals via glib to open urls & files in snap --- .../platform/linux/file_utilities_linux.cpp | 10 ++-------- snap/snapcraft.yaml | 1 + 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp index dfecc5b79..5f14f4f97 100644 --- a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp @@ -68,10 +68,7 @@ QByteArray EscapeShell(const QByteArray &content) { } // namespace internal void UnsafeOpenUrl(const QString &url) { - if (InSnap()) { - QProcess process; - process.startDetached(qsl("xdg-open"), {url}); - } else if (!g_app_info_launch_default_for_uri( + if (!g_app_info_launch_default_for_uri( url.toUtf8(), nullptr, nullptr)) { @@ -86,10 +83,7 @@ void UnsafeOpenEmailLink(const QString &email) { void UnsafeLaunch(const QString &filepath) { const auto absolutePath = QFileInfo(filepath).absoluteFilePath(); - if (InSnap()) { - QProcess process; - process.startDetached(qsl("xdg-open"), {absolutePath}); - } else if (!g_app_info_launch_default_for_uri( + if (!g_app_info_launch_default_for_uri( ("file://" + absolutePath).toUtf8(), nullptr, nullptr)) { diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 09ca41b13..09dc34469 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -20,6 +20,7 @@ apps: environment: # Use GTK3 cursor theme, icon theme and open/save file dialogs. QT_QPA_PLATFORMTHEME: gtk3 + GTK_USE_PORTAL: 1 plugs: - alsa - audio-playback From 92695f3ab01e7c439adbaff88b952953ed1c3623 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 28 Oct 2020 10:20:20 +0400 Subject: [PATCH 050/190] Make stale bot more patient & change its message --- .github/stale.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/stale.yml b/.github/stale.yml index 6e25c83d1..afc525a07 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,7 +1,7 @@ # Number of days of inactivity before an issue becomes stale -daysUntilStale: 60 +daysUntilStale: 180 # Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 +daysUntilClose: 30 # Issues with these labels will never be considered stale exemptLabels: [] # Label to use when marking an issue as stale @@ -10,7 +10,7 @@ staleLabel: stale markComment: | Hey there! - This issue will be automatically closed in 7 days if there would be no activity. We therefore assume that the user has lost interest or resolved the problem on their own. + This issue was inactive for a long time and will be automatically closed in 30 days if there isn't any further activity. We therefore assume that the user has lost interest or resolved the problem on their own. Don't worry though; if this is an error, let us know with a comment and we'll be happy to reopen the issue. From 35c639e4b0c27ac7de559b82c58394ecfe017415 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 28 Oct 2020 10:07:00 +0300 Subject: [PATCH 051/190] Revert 245d644cd7. See https://github.com/telegramdesktop/tdesktop/issues/8914. --- Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 10f12c9ee..38e7062e0 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -347,7 +347,6 @@ OverlayWidget::OverlayWidget() if (Platform::IsLinux()) { setWindowFlags(Qt::FramelessWindowHint - | Qt::WindowStaysOnTopHint | Qt::MaximizeUsingFullscreenGeometryHint); } else { setWindowFlags(Qt::FramelessWindowHint); From 9818724382e4ce9bcf4d65e834814e362ed16ad1 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 29 Oct 2020 13:31:58 +0400 Subject: [PATCH 052/190] CentOS-based Docker build --- Telegram/build/docker/centos_env/Dockerfile | 466 ++++++++++++++++++++ Telegram/build/docker/centos_env/build.sh | 10 + 2 files changed, 476 insertions(+) create mode 100644 Telegram/build/docker/centos_env/Dockerfile create mode 100644 Telegram/build/docker/centos_env/build.sh diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile new file mode 100644 index 000000000..c02e0d331 --- /dev/null +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -0,0 +1,466 @@ +FROM centos:7 AS builder + +ENV GIT https://github.com +ENV PKG_CONFIG_PATH /usr/local/lib/pkgconfig +ENV QT 5_12_8 +ENV QT_PREFIX /usr/local/desktop-app/Qt-5.12.8 +ENV OPENSSL_VER 1_1_1 +ENV OPENSSL_PREFIX /usr/local/desktop-app/openssl-1.1.1 + +RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm +RUN yum -y install centos-release-scl + +RUN yum -y install git cmake3 zlib-devel gtk2-devel libICE-devel \ + libSM-devel libdrm-devel autoconf automake libtool fontconfig-devel \ + freetype-devel libX11-devel at-spi2-core-devel alsa-lib-devel \ + pulseaudio-libs-devel mesa-libGL-devel mesa-libEGL-devel \ + pkgconfig bison yasm file which xorg-x11-util-macros \ + devtoolset-8-make devtoolset-8-gcc devtoolset-8-gcc-c++ \ + devtoolset-8-binutils + +RUN ln -s cmake3 /usr/bin/cmake + +ENV LibrariesPath /usr/src/Libraries +WORKDIR $LibrariesPath + +FROM builder AS patches +RUN git clone $GIT/desktop-app/patches.git +RUN cd patches && git checkout b00f25d + +FROM builder AS libffi +RUN git clone -b v3.3 --depth=1 $GIT/libffi/libffi.git + +WORKDIR libffi +RUN scl enable devtoolset-8 -- ./autogen.sh +RUN scl enable devtoolset-8 -- ./configure --enable-static --disable-docs +RUN scl enable devtoolset-8 -- make -j$(nproc) +RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/libffi-cache" install + +WORKDIR .. +RUN rm -rf libffi + +FROM builder AS xz +RUN git clone -b v5.2.5 https://git.tukaani.org/xz.git + +WORKDIR xz +RUN scl enable devtoolset-8 -- cmake3 -B build . -DCMAKE_BUILD_TYPE=Release +RUN scl enable devtoolset-8 -- cmake3 --build build -j$(nproc) +RUN DESTDIR="$LibrariesPath/xz-cache" scl enable devtoolset-8 -- cmake3 --install build + +FROM builder AS opus +RUN git clone -b v1.3 --depth=1 $GIT/xiph/opus.git + +WORKDIR opus +RUN scl enable devtoolset-8 -- ./autogen.sh +RUN scl enable devtoolset-8 -- ./configure +RUN scl enable devtoolset-8 -- make -j$(nproc) +RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/opus-cache" install + +WORKDIR .. +RUN rm -rf opus + +FROM builder AS xcb-proto +RUN git clone -b xcb-proto-1.14 --depth=1 https://gitlab.freedesktop.org/xorg/proto/xcbproto.git + +WORKDIR xcbproto +RUN scl enable devtoolset-8 -- ./autogen.sh --enable-static +RUN scl enable devtoolset-8 -- make -j$(nproc) +RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/xcb-proto-cache" install + +WORKDIR .. +RUN rm -rf xcbproto + +FROM builder AS xcb +COPY --from=xcb-proto ${LibrariesPath}/xcb-proto-cache / + +RUN git clone -b libxcb-1.14 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxcb.git + +WORKDIR libxcb +RUN scl enable devtoolset-8 -- ./autogen.sh --enable-static +RUN scl enable devtoolset-8 -- make -j$(nproc) +RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/xcb-cache" install + +WORKDIR .. +RUN rm -rf libxcb + +FROM builder AS libXext +RUN git clone -b libXext-1.3.4 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxext.git + +WORKDIR libxext +RUN scl enable devtoolset-8 -- ./autogen.sh --enable-static +RUN scl enable devtoolset-8 -- make -j$(nproc) +RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/libXext-cache" install + +WORKDIR .. +RUN rm -rf libxext + +FROM builder AS libXfixes +RUN git clone -b libXfixes-5.0.3 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxfixes.git + +WORKDIR libxfixes +RUN scl enable devtoolset-8 -- ./autogen.sh --enable-static +RUN scl enable devtoolset-8 -- make -j$(nproc) +RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/libXfixes-cache" install + +WORKDIR .. +RUN rm -rf libxfixes + +FROM builder AS libXi +RUN git clone -b libXi-1.7.10 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxi.git + +WORKDIR libxi +RUN scl enable devtoolset-8 -- ./autogen.sh --enable-static +RUN scl enable devtoolset-8 -- make -j$(nproc) +RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/libXi-cache" install + +WORKDIR .. +RUN rm -rf libxi + +FROM builder AS libXrender +RUN git clone -b libXrender-0.9.10 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxrender.git + +WORKDIR libxrender +RUN scl enable devtoolset-8 -- ./autogen.sh --enable-static +RUN scl enable devtoolset-8 -- make -j$(nproc) +RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/libXrender-cache" install + +WORKDIR .. +RUN rm -rf libxrender + +FROM builder AS wayland +COPY --from=libffi ${LibrariesPath}/libffi-cache / + +RUN git clone -b 1.18.0 --depth=1 https://gitlab.freedesktop.org/wayland/wayland.git + +WORKDIR wayland +RUN scl enable devtoolset-8 -- ./autogen.sh \ + --enable-static \ + --disable-documentation \ + --disable-dtd-validation + +RUN scl enable devtoolset-8 -- make -j$(nproc) +RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/wayland-cache" install + +WORKDIR .. +RUN rm -rf wayland + +FROM builder AS libva + +COPY --from=libffi ${LibrariesPath}/libffi-cache / +COPY --from=libXext ${LibrariesPath}/libXext-cache / +COPY --from=libXfixes ${LibrariesPath}/libXfixes-cache / +COPY --from=wayland ${LibrariesPath}/wayland-cache / + +RUN git clone -b 2.9.0 --depth=1 $GIT/intel/libva.git + +WORKDIR libva +RUN scl enable devtoolset-8 -- ./autogen.sh --enable-static +RUN scl enable devtoolset-8 -- make -j$(nproc) +RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/libva-cache" install + +WORKDIR .. +RUN rm -rf libva + +FROM builder AS libvdpau +RUN git clone -b libvdpau-1.2 --depth=1 https://gitlab.freedesktop.org/vdpau/libvdpau.git + +WORKDIR libvdpau +RUN scl enable devtoolset-8 -- ./autogen.sh --enable-static +RUN scl enable devtoolset-8 -- make -j$(nproc) +RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/libvdpau-cache" install + +WORKDIR .. +RUN rm -rf libvdpau + +FROM builder AS ffmpeg + +COPY --from=opus ${LibrariesPath}/opus-cache / +COPY --from=libva ${LibrariesPath}/libva-cache / +COPY --from=libvdpau ${LibrariesPath}/libvdpau-cache / + +RUN git clone -b release/3.4 --depth=1 $GIT/FFmpeg/FFmpeg.git ffmpeg + +WORKDIR ffmpeg +RUN scl enable devtoolset-8 -- ./configure \ + --disable-debug \ + --disable-programs \ + --disable-doc \ + --disable-network \ + --disable-autodetect \ + --disable-everything \ + --disable-alsa \ + --disable-iconv \ + --enable-libopus \ + --enable-vaapi \ + --enable-vdpau \ + --enable-protocol=file \ + --enable-hwaccel=h264_vaapi \ + --enable-hwaccel=h264_vdpau \ + --enable-hwaccel=mpeg4_vaapi \ + --enable-hwaccel=mpeg4_vdpau \ + --enable-decoder=aac \ + --enable-decoder=aac_fixed \ + --enable-decoder=aac_latm \ + --enable-decoder=aasc \ + --enable-decoder=alac \ + --enable-decoder=flac \ + --enable-decoder=gif \ + --enable-decoder=h264 \ + --enable-decoder=h264_vdpau \ + --enable-decoder=hevc \ + --enable-decoder=mp1 \ + --enable-decoder=mp1float \ + --enable-decoder=mp2 \ + --enable-decoder=mp2float \ + --enable-decoder=mp3 \ + --enable-decoder=mp3adu \ + --enable-decoder=mp3adufloat \ + --enable-decoder=mp3float \ + --enable-decoder=mp3on4 \ + --enable-decoder=mp3on4float \ + --enable-decoder=mpeg4 \ + --enable-decoder=mpeg4_vdpau \ + --enable-decoder=msmpeg4v2 \ + --enable-decoder=msmpeg4v3 \ + --enable-decoder=opus \ + --enable-decoder=pcm_alaw \ + --enable-decoder=pcm_f32be \ + --enable-decoder=pcm_f32le \ + --enable-decoder=pcm_f64be \ + --enable-decoder=pcm_f64le \ + --enable-decoder=pcm_lxf \ + --enable-decoder=pcm_mulaw \ + --enable-decoder=pcm_s16be \ + --enable-decoder=pcm_s16be_planar \ + --enable-decoder=pcm_s16le \ + --enable-decoder=pcm_s16le_planar \ + --enable-decoder=pcm_s24be \ + --enable-decoder=pcm_s24daud \ + --enable-decoder=pcm_s24le \ + --enable-decoder=pcm_s24le_planar \ + --enable-decoder=pcm_s32be \ + --enable-decoder=pcm_s32le \ + --enable-decoder=pcm_s32le_planar \ + --enable-decoder=pcm_s64be \ + --enable-decoder=pcm_s64le \ + --enable-decoder=pcm_s8 \ + --enable-decoder=pcm_s8_planar \ + --enable-decoder=pcm_u16be \ + --enable-decoder=pcm_u16le \ + --enable-decoder=pcm_u24be \ + --enable-decoder=pcm_u24le \ + --enable-decoder=pcm_u32be \ + --enable-decoder=pcm_u32le \ + --enable-decoder=pcm_u8 \ + --enable-decoder=pcm_zork \ + --enable-decoder=vorbis \ + --enable-decoder=wavpack \ + --enable-decoder=wmalossless \ + --enable-decoder=wmapro \ + --enable-decoder=wmav1 \ + --enable-decoder=wmav2 \ + --enable-decoder=wmavoice \ + --enable-encoder=libopus \ + --enable-parser=aac \ + --enable-parser=aac_latm \ + --enable-parser=flac \ + --enable-parser=h264 \ + --enable-parser=hevc \ + --enable-parser=mpeg4video \ + --enable-parser=mpegaudio \ + --enable-parser=opus \ + --enable-parser=vorbis \ + --enable-demuxer=aac \ + --enable-demuxer=flac \ + --enable-demuxer=gif \ + --enable-demuxer=h264 \ + --enable-demuxer=hevc \ + --enable-demuxer=m4v \ + --enable-demuxer=mov \ + --enable-demuxer=mp3 \ + --enable-demuxer=ogg \ + --enable-demuxer=wav \ + --enable-muxer=ogg \ + --enable-muxer=opus + +RUN scl enable devtoolset-8 -- make -j$(nproc) +RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/ffmpeg-cache" install + +FROM builder AS openal +RUN git clone -b openal-soft-1.20.1 --depth=1 $GIT/kcat/openal-soft.git + +WORKDIR openal-soft +RUN scl enable devtoolset-8 -- cmake3 -B build . \ + -DCMAKE_BUILD_TYPE=Release \ + -DLIBTYPE:STRING=STATIC \ + -DALSOFT_EXAMPLES=OFF \ + -DALSOFT_TESTS=OFF \ + -DALSOFT_UTILS=OFF \ + -DALSOFT_CONFIG=OFF + +RUN scl enable devtoolset-8 -- cmake3 --build build -j$(nproc) +RUN DESTDIR="$LibrariesPath/openal-cache" scl enable devtoolset-8 -- cmake3 --install build + +WORKDIR .. +RUN rm -rf openal + +FROM builder AS openssl +ENV opensslDir openssl_${OPENSSL_VER} +RUN git clone -b OpenSSL_${OPENSSL_VER}-stable --depth=1 \ + $GIT/openssl/openssl.git $opensslDir + +WORKDIR ${opensslDir} +RUN scl enable devtoolset-8 -- ./config --prefix="$OPENSSL_PREFIX" no-tests +RUN scl enable devtoolset-8 -- make -j$(nproc) +RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/openssl-cache" install_sw + +WORKDIR .. +RUN rm -rf $opensslDir + +FROM builder AS xkbcommon +RUN git clone -b xkbcommon-0.8.4 --depth=1 $GIT/xkbcommon/libxkbcommon.git + +WORKDIR libxkbcommon +RUN scl enable devtoolset-8 -- ./autogen.sh \ + --disable-docs \ + --disable-wayland \ + --with-xkb-config-root=/usr/share/X11/xkb \ + --with-x-locale-root=/usr/share/X11/locale + +RUN scl enable devtoolset-8 -- make -j$(nproc) +RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/xkbcommon-cache" install + +WORKDIR .. +RUN rm -rf libxkbcommon + +FROM patches AS qt + +COPY --from=libffi ${LibrariesPath}/libffi-cache / +COPY --from=xcb ${LibrariesPath}/xcb-cache / +COPY --from=libXext ${LibrariesPath}/libXext-cache / +COPY --from=libXfixes ${LibrariesPath}/libXfixes-cache / +COPY --from=libXi ${LibrariesPath}/libXi-cache / +COPY --from=libXrender ${LibrariesPath}/libXrender-cache / +COPY --from=wayland ${LibrariesPath}/wayland-cache / +COPY --from=openssl ${LibrariesPath}/openssl-cache / +COPY --from=xkbcommon ${LibrariesPath}/xkbcommon-cache / + +RUN git clone -b v5.12.8 --depth=1 git://code.qt.io/qt/qt5.git qt_${QT} +WORKDIR qt_${QT} +RUN perl init-repository --module-subset=qtbase,qtwayland,qtimageformats,qtsvg +RUN git submodule update qtbase qtwayland qtimageformats qtsvg + +WORKDIR qtbase +RUN find ../../patches/qtbase_${QT} -type f -print0 | sort -z | xargs -r0 git apply +WORKDIR ../qtwayland +RUN find ../../patches/qtwayland_${QT} -type f -print0 | sort -z | xargs -r0 git apply +WORKDIR .. + +RUN scl enable devtoolset-8 -- ./configure -prefix "$QT_PREFIX" \ + -release \ + -opensource \ + -confirm-license \ + -qt-libpng \ + -qt-libjpeg \ + -qt-harfbuzz \ + -qt-pcre \ + -qt-xcb \ + -no-icu \ + -no-gtk \ + -static \ + -dbus-runtime \ + -openssl-linked \ + -I "$OPENSSL_PREFIX/include" OPENSSL_LIBS="$OPENSSL_PREFIX/lib/libssl.a $OPENSSL_PREFIX/lib/libcrypto.a -lz -ldl -lpthread" \ + -nomake examples \ + -nomake tests + +RUN scl enable devtoolset-8 -- make -j$(nproc) +RUN scl enable devtoolset-8 -- make INSTALL_ROOT="$LibrariesPath/qt-cache" install + +FROM patches AS breakpad +RUN git clone https://chromium.googlesource.com/breakpad/breakpad.git + +WORKDIR breakpad +RUN git checkout bc8fb886 +RUN git clone https://chromium.googlesource.com/linux-syscall-support.git src/third_party/lss + +WORKDIR src/third_party/lss +RUN git checkout a91633d1 +WORKDIR ${LibrariesPath} + +ENV BreakpadCache ${LibrariesPath}/breakpad-cache +RUN git clone https://chromium.googlesource.com/external/gyp.git + +WORKDIR gyp +RUN git checkout 9f2a7bb1 +RUN git apply ../patches/gyp.diff + +WORKDIR ../breakpad +RUN scl enable devtoolset-8 -- ./configure +RUN scl enable devtoolset-8 -- make -j$(nproc) +RUN scl enable devtoolset-8 -- make DESTDIR="$BreakpadCache" install + +WORKDIR src +RUN rm -rf testing +RUN git clone $GIT/google/googletest.git testing + +WORKDIR tools +RUN sed -i 's/minidump_upload.m/minidump_upload.cc/' linux/tools_linux.gypi +RUN ../../../gyp/gyp --depth=. --generator-output=.. -Goutput_dir=../out tools.gyp --format=cmake + +WORKDIR ../../out/Default +RUN scl enable devtoolset-8 -- cmake3 . +RUN scl enable devtoolset-8 -- cmake3 --build . --target dump_syms -j$(nproc) +RUN mv dump_syms $BreakpadCache + +WORKDIR .. +RUN rm -rf gyp + +FROM builder AS webrtc + +COPY --from=opus ${LibrariesPath}/opus-cache / +COPY --from=ffmpeg ${LibrariesPath}/ffmpeg-cache / +COPY --from=openssl ${LibrariesPath}/openssl-cache / +COPY --from=qt ${LibrariesPath}/qt_${QT} qt_${QT} + +RUN git clone $GIT/desktop-app/tg_owt.git + +WORKDIR tg_owt +RUN git checkout c73a471 + +RUN scl enable devtoolset-8 -- cmake3 -B out/Release . \ + -DCMAKE_BUILD_TYPE=Release \ + -DTG_OWT_SPECIAL_TARGET=linux \ + -DTG_OWT_LIBJPEG_INCLUDE_PATH=$(pwd)/../qt_$QT/qtbase/src/3rdparty/libjpeg \ + -DTG_OWT_OPENSSL_INCLUDE_PATH=$OPENSSL_PREFIX/include \ + -DTG_OWT_OPUS_INCLUDE_PATH=/usr/local/include/opus \ + -DTG_OWT_FFMPEG_INCLUDE_PATH=/usr/local/include + +RUN scl enable devtoolset-8 -- cmake3 --build out/Release + +FROM builder + +COPY --from=libffi ${LibrariesPath}/libffi-cache / +COPY --from=xz ${LibrariesPath}/xz-cache / +COPY --from=opus ${LibrariesPath}/opus-cache / +COPY --from=xcb ${LibrariesPath}/xcb-cache / +COPY --from=libXext ${LibrariesPath}/libXext-cache / +COPY --from=libXfixes ${LibrariesPath}/libXfixes-cache / +COPY --from=libXi ${LibrariesPath}/libXi-cache / +COPY --from=libXrender ${LibrariesPath}/libXrender-cache / +COPY --from=wayland ${LibrariesPath}/wayland-cache / +COPY --from=libva ${LibrariesPath}/libva-cache / +COPY --from=libvdpau ${LibrariesPath}/libvdpau-cache / +COPY --from=ffmpeg ${LibrariesPath}/ffmpeg ffmpeg +COPY --from=ffmpeg ${LibrariesPath}/ffmpeg-cache / +COPY --from=openal ${LibrariesPath}/openal-cache / +COPY --from=openssl ${LibrariesPath}/openssl-cache / +COPY --from=xkbcommon ${LibrariesPath}/xkbcommon-cache / +COPY --from=qt ${LibrariesPath}/qt-cache / +COPY --from=breakpad ${LibrariesPath}/breakpad breakpad +COPY --from=breakpad ${LibrariesPath}/breakpad-cache / +COPY --from=webrtc ${LibrariesPath}/tg_owt tg_owt + +WORKDIR ../tdesktop +VOLUME [ "/usr/src/tdesktop" ] +CMD [ "/usr/src/tdesktop/Telegram/build/docker/centos_env/build.sh" ] diff --git a/Telegram/build/docker/centos_env/build.sh b/Telegram/build/docker/centos_env/build.sh new file mode 100644 index 000000000..9bac991f4 --- /dev/null +++ b/Telegram/build/docker/centos_env/build.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +cd Telegram +scl enable devtoolset-8 -- ./configure.sh "$@" + +if [ -n "$DEBUG" ]; then + scl enable devtoolset-8 -- cmake3 --build ../out/Debug -j$(nproc) +else + scl enable devtoolset-8 -- cmake3 --build ../out/Release -j$(nproc) +fi From 6e42d546328050d10a3efbdf53553579228be3ea Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 29 Oct 2020 18:34:31 +0400 Subject: [PATCH 053/190] Remove forgotten sws_scale check It should been removed in 7d29f9ce17c837a0ae2944f5b2c75dc16c9f61f6, but was forgotten --- Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp index 263ff877e..2053da32d 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp @@ -258,11 +258,7 @@ bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const Q // AV_NUM_DATA_POINTERS defined in AVFrame struct uint8_t *toData[AV_NUM_DATA_POINTERS] = { to.bits(), nullptr }; int toLinesize[AV_NUM_DATA_POINTERS] = { to.bytesPerLine(), 0 }; - int res; - if ((res = sws_scale(_swsContext, _frame->data, _frame->linesize, 0, _frame->height, toData, toLinesize)) != _swsSize.height()) { - LOG(("Gif Error: Unable to sws_scale to good size %1, height %2, should be %3").arg(logData()).arg(res).arg(_swsSize.height())); - return false; - } + sws_scale(_swsContext, _frame->data, _frame->linesize, 0, _frame->height, toData, toLinesize); } if (hasAlpha) { FFmpeg::PremultiplyInplace(to); From df9c7f07a19154edd5a3d4212047c6e7d48c352a Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 29 Oct 2020 23:43:28 +0300 Subject: [PATCH 054/190] Fixed crashpad build on macOS. --- docs/building-xcode.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/building-xcode.md b/docs/building-xcode.md index f11db2063..f286cf047 100644 --- a/docs/building-xcode.md +++ b/docs/building-xcode.md @@ -29,7 +29,7 @@ Go to ***BuildPath*** and run git clone https://github.com/desktop-app/patches.git cd patches - git checkout ddd4084 + git checkout a77e4d5 cd ../ git clone https://chromium.googlesource.com/external/gyp git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git @@ -46,7 +46,7 @@ Go to ***BuildPath*** and run git clone https://github.com/desktop-app/patches.git cd patches - git checkout ddd4084 + git checkout a77e4d5 cd .. git clone https://git.tukaani.org/xz.git From a38b4f039affa1f32c599df496d6bce934c1ca32 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 29 Oct 2020 23:56:13 +0400 Subject: [PATCH 055/190] Use some methods from lib_base --- Telegram/SourceFiles/core/application.cpp | 3 +- Telegram/SourceFiles/core/file_utilities.cpp | 3 +- Telegram/SourceFiles/core/launcher.cpp | 5 +- Telegram/SourceFiles/core/update_checker.cpp | 9 +- .../platform/linux/file_utilities_linux.cpp | 9 - .../platform/linux/specific_linux.cpp | 238 ------------------ .../platform/linux/specific_linux.h | 3 - .../platform/mac/file_utilities_mac.mm | 5 - .../SourceFiles/platform/mac/launcher_mac.mm | 4 +- .../SourceFiles/platform/mac/specific_mac.h | 6 - .../SourceFiles/platform/mac/specific_mac.mm | 70 ------ .../SourceFiles/platform/mac/specific_mac_p.h | 1 - .../platform/mac/specific_mac_p.mm | 8 - .../platform/platform_file_utilities.h | 1 - .../SourceFiles/platform/platform_specific.h | 17 +- .../platform/win/file_utilities_win.cpp | 5 - .../platform/win/file_utilities_win.h | 2 +- .../SourceFiles/platform/win/specific_win.cpp | 124 ++------- .../SourceFiles/platform/win/specific_win.h | 2 - .../settings/settings_privacy_security.cpp | 4 +- .../window/notifications_manager_default.cpp | 4 +- .../window/notifications_utilities.cpp | 4 +- 22 files changed, 42 insertions(+), 485 deletions(-) diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index 58fe355b1..001a3ad23 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/emoji_keywords.h" #include "chat_helpers/stickers_emoji_image_loader.h" #include "base/platform/base_platform_info.h" +#include "base/platform/base_platform_last_input.h" #include "platform/platform_specific.h" #include "mainwindow.h" #include "dialogs/dialogs_entry.h" @@ -807,7 +808,7 @@ void Application::updateNonIdle() { crl::time Application::lastNonIdleTime() const { return std::max( - Platform::LastUserInputTime().value_or(0), + base::Platform::LastUserInputTime().value_or(0), _lastNonIdleTime); } diff --git a/Telegram/SourceFiles/core/file_utilities.cpp b/Telegram/SourceFiles/core/file_utilities.cpp index 580be2082..5b3858ee6 100644 --- a/Telegram/SourceFiles/core/file_utilities.cpp +++ b/Telegram/SourceFiles/core/file_utilities.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/file_utilities.h" #include "storage/localstorage.h" +#include "base/platform/base_platform_file_utilities.h" #include "platform/platform_file_utilities.h" #include "core/application.h" #include "base/unixtime.h" @@ -154,7 +155,7 @@ void Launch(const QString &filepath) { void ShowInFolder(const QString &filepath) { crl::on_main([=] { Ui::PreventDelayedActivation(); - Platform::File::UnsafeShowInFolder(filepath); + base::Platform::ShowInFolder(filepath); }); } diff --git a/Telegram/SourceFiles/core/launcher.cpp b/Telegram/SourceFiles/core/launcher.cpp index ee01d01e0..28271acd9 100644 --- a/Telegram/SourceFiles/core/launcher.cpp +++ b/Telegram/SourceFiles/core/launcher.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/platform_launcher.h" #include "platform/platform_specific.h" #include "base/platform/base_platform_info.h" +#include "base/platform/base_platform_file_utilities.h" #include "ui/main_queue_processor.h" #include "ui/ui_utility.h" #include "core/crash_reports.h" @@ -342,7 +343,7 @@ int Launcher::exec() { if (!UpdaterDisabled() && cRestartingUpdate()) { DEBUG_LOG(("Sandbox Info: executing updater to install update.")); if (!launchUpdater(UpdaterLaunch::PerformUpdate)) { - psDeleteDir(cWorkingDir() + qsl("tupdates/temp")); + base::Platform::DeleteDirectory(cWorkingDir() + qsl("tupdates/temp")); } } else if (cRestarting()) { DEBUG_LOG(("Sandbox Info: executing Telegram because of restart.")); @@ -402,7 +403,7 @@ bool Launcher::customWorkingDir() const { } void Launcher::prepareSettings() { - auto path = Platform::CurrentExecutablePath(_argc, _argv); + auto path = base::Platform::CurrentExecutablePath(_argc, _argv); LOG(("Executable path before check: %1").arg(path)); if (!path.isEmpty()) { auto info = QFileInfo(path); diff --git a/Telegram/SourceFiles/core/update_checker.cpp b/Telegram/SourceFiles/core/update_checker.cpp index 2c4fb4794..69ba0f74b 100644 --- a/Telegram/SourceFiles/core/update_checker.cpp +++ b/Telegram/SourceFiles/core/update_checker.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/update_checker.h" #include "base/platform/base_platform_info.h" +#include "base/platform/base_platform_file_utilities.h" #include "base/timer.h" #include "base/bytes.h" #include "base/unixtime.h" @@ -211,7 +212,7 @@ QString UpdatesFolder() { } void ClearAll() { - psDeleteDir(UpdatesFolder()); + base::Platform::DeleteDirectory(UpdatesFolder()); } QString FindUpdateFile() { @@ -270,7 +271,7 @@ bool UnpackUpdate(const QString &filepath) { input.close(); QString tempDirPath = cWorkingDir() + qsl("tupdates/temp"), readyFilePath = cWorkingDir() + qsl("tupdates/temp/ready"); - psDeleteDir(tempDirPath); + base::Platform::DeleteDirectory(tempDirPath); QDir tempDir(tempDirPath); if (tempDir.exists() || QFile(readyFilePath).exists()) { @@ -1560,8 +1561,8 @@ bool checkReadyUpdate() { #endif // Q_OS_UNIX #ifdef Q_OS_MAC - Platform::RemoveQuarantine(QFileInfo(curUpdater).absolutePath()); - Platform::RemoveQuarantine(updater.absolutePath()); + base::Platform::RemoveQuarantine(QFileInfo(curUpdater).absolutePath()); + base::Platform::RemoveQuarantine(updater.absolutePath()); #endif // Q_OS_MAC return true; diff --git a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp index 5f14f4f97..0531a5b45 100644 --- a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp @@ -11,11 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/linux/linux_gdk_helper.h" #include "platform/linux/linux_desktop_environment.h" #include "platform/linux/specific_linux.h" -#include "boxes/abstract_box.h" #include "storage/localstorage.h" -#include "base/platform/base_platform_file_utilities.h" -#include #include extern "C" { @@ -91,12 +88,6 @@ void UnsafeLaunch(const QString &filepath) { } } -void UnsafeShowInFolder(const QString &filepath) { - // Hide mediaview to make other apps visible. - Ui::hideLayer(anim::type::instant); - base::Platform::ShowInFolder(filepath); -} - } // namespace File namespace FileDialog { diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index ae7122f5c..f07b61349 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -174,33 +174,6 @@ QString FlatpakID() { return Result; } -QString ProcessNameByPID(const QString &pid) { - constexpr auto kMaxPath = 1024; - char result[kMaxPath] = { 0 }; - auto count = readlink("/proc/" + pid.toLatin1() + "/exe", result, kMaxPath); - if (count > 0) { - auto filename = QFile::decodeName(result); - auto deletedPostfix = qstr(" (deleted)"); - if (filename.endsWith(deletedPostfix) && !QFileInfo(filename).exists()) { - filename.chop(deletedPostfix.size()); - } - return filename; - } - - return QString(); -} - -QString RealExecutablePath(int argc, char *argv[]) { - const auto processName = ProcessNameByPID(qsl("self")); - - // Fallback to the first command line argument. - return !processName.isEmpty() - ? processName - : argc - ? QFile::decodeName(argv[0]) - : QString(); -} - bool RunShellCommand(const QString &program, const QStringList &arguments) { const auto result = QProcess::execute(program, arguments); @@ -310,131 +283,6 @@ bool GetImageFromClipboardSupported() { } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION -std::optional XCBLastUserInputTime() { - const auto connection = base::Platform::XCB::GetConnectionFromQt(); - if (!connection) { - return std::nullopt; - } - - if (!base::Platform::XCB::IsExtensionPresent( - connection, - &xcb_screensaver_id)) { - return std::nullopt; - } - - const auto root = base::Platform::XCB::GetRootWindowFromQt(); - if (!root.has_value()) { - return std::nullopt; - } - - const auto cookie = xcb_screensaver_query_info( - connection, - *root); - - auto reply = xcb_screensaver_query_info_reply( - connection, - cookie, - nullptr); - - if (!reply) { - return std::nullopt; - } - - const auto idle = reply->ms_since_user_input; - free(reply); - - return (crl::now() - static_cast(idle)); -} - -#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION -std::optional FreedesktopDBusLastUserInputTime() { - static auto NotSupported = false; - - if (NotSupported) { - return std::nullopt; - } - - static const auto Message = QDBusMessage::createMethodCall( - qsl("org.freedesktop.ScreenSaver"), - qsl("/org/freedesktop/ScreenSaver"), - qsl("org.freedesktop.ScreenSaver"), - qsl("GetSessionIdleTime")); - - const QDBusReply reply = QDBusConnection::sessionBus().call( - Message); - - static const auto NotSupportedErrors = { - QDBusError::ServiceUnknown, - QDBusError::NotSupported, - }; - - static const auto NotSupportedErrorsToLog = { - QDBusError::Disconnected, - QDBusError::AccessDenied, - }; - - if (reply.isValid()) { - return (crl::now() - static_cast(reply.value())); - } else if (ranges::contains(NotSupportedErrors, reply.error().type())) { - NotSupported = true; - } else { - if (ranges::contains(NotSupportedErrorsToLog, reply.error().type())) { - NotSupported = true; - } - - LOG(("App Error: Unable to get last user input time " - "from org.freedesktop.ScreenSaver: %1: %2") - .arg(reply.error().name()) - .arg(reply.error().message())); - } - - return std::nullopt; -} - -std::optional MutterDBusLastUserInputTime() { - static auto NotSupported = false; - - if (NotSupported) { - return std::nullopt; - } - - static const auto Message = QDBusMessage::createMethodCall( - qsl("org.gnome.Mutter.IdleMonitor"), - qsl("/org/gnome/Mutter/IdleMonitor/Core"), - qsl("org.gnome.Mutter.IdleMonitor"), - qsl("GetIdletime")); - - const QDBusReply reply = QDBusConnection::sessionBus().call( - Message); - - static const auto NotSupportedErrors = { - QDBusError::ServiceUnknown, - }; - - static const auto NotSupportedErrorsToLog = { - QDBusError::Disconnected, - QDBusError::AccessDenied, - }; - - if (reply.isValid()) { - return (crl::now() - static_cast(reply.value())); - } else if (ranges::contains(NotSupportedErrors, reply.error().type())) { - NotSupported = true; - } else { - if (ranges::contains(NotSupportedErrorsToLog, reply.error().type())) { - NotSupported = true; - } - - LOG(("App Error: Unable to get last user input time " - "from org.gnome.Mutter.IdleMonitor: %1: %2") - .arg(reply.error().name()) - .arg(reply.error().message())); - } - - return std::nullopt; -} -#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION - uint XCBMoveResizeFromEdges(Qt::Edges edges) { if (edges == (Qt::TopEdge | Qt::LeftEdge)) return 0; @@ -682,11 +530,6 @@ bool InSnap() { return Result; } -bool InAppImage() { - static const auto Result = qEnvironmentVariableIsSet("APPIMAGE"); - return Result; -} - bool IsStaticBinary() { #ifdef DESKTOP_APP_USE_PACKAGED return false; @@ -773,25 +616,6 @@ bool CanOpenDirectoryWithPortal() { return false; } -QString CurrentExecutablePath(int argc, char *argv[]) { - if (InAppImage()) { - const auto appimagePath = QString::fromUtf8(qgetenv("APPIMAGE")); - const auto appimagePathList = appimagePath.split('/'); - - if (qEnvironmentVariableIsSet("ARGV0") - && appimagePathList.size() >= 5 - && appimagePathList[1] == qstr("run") - && appimagePathList[2] == qstr("user") - && appimagePathList[4] == qstr("appimagelauncherfs")) { - return QString::fromUtf8(qgetenv("ARGV0")); - } - - return appimagePath; - } - - return RealExecutablePath(argc, argv); -} - QString AppRuntimeDirectory() { static const auto Result = [&] { auto runtimeDir = QStandardPaths::writableLocation( @@ -908,29 +732,6 @@ QImage GetImageFromClipboard() { return data; } -std::optional LastUserInputTime() { - if (!IsWayland()) { - const auto xcbResult = XCBLastUserInputTime(); - if (xcbResult.has_value()) { - return xcbResult; - } - } - -#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION - const auto freedesktopResult = FreedesktopDBusLastUserInputTime(); - if (freedesktopResult.has_value()) { - return freedesktopResult; - } - - const auto mutterResult = MutterDBusLastUserInputTime(); - if (mutterResult.has_value()) { - return mutterResult; - } -#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION - - return std::nullopt; -} - std::optional IsDarkMode() { #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION if (Libs::GtkSettingSupported() && Libs::GtkLoaded()) { @@ -1089,41 +890,6 @@ QRect psDesktopRect() { void psWriteDump() { } -bool _removeDirectory(const QString &path) { // from http://stackoverflow.com/questions/2256945/removing-a-non-empty-directory-programmatically-in-c-or-c - QByteArray pathRaw = QFile::encodeName(path); - DIR *d = opendir(pathRaw.constData()); - if (!d) return false; - - while (struct dirent *p = readdir(d)) { - /* Skip the names "." and ".." as we don't want to recurse on them. */ - if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, "..")) continue; - - QString fname = path + '/' + p->d_name; - QByteArray fnameRaw = QFile::encodeName(fname); - struct stat statbuf; - if (!stat(fnameRaw.constData(), &statbuf)) { - if (S_ISDIR(statbuf.st_mode)) { - if (!_removeDirectory(fname)) { - closedir(d); - return false; - } - } else { - if (unlink(fnameRaw.constData())) { - closedir(d); - return false; - } - } - } - } - closedir(d); - - return !rmdir(pathRaw.constData()); -} - -void psDeleteDir(const QString &dir) { - _removeDirectory(dir); -} - void psActivateProcess(uint64 pid) { // objc_activateProgram(); } @@ -1444,10 +1210,6 @@ void psNewVersion() { Platform::RegisterCustomScheme(); } -bool psShowOpenWithMenu(int x, int y, const QString &file) { - return false; -} - void psAutoStart(bool start, bool silent) { if (InFlatpak()) { #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.h b/Telegram/SourceFiles/platform/linux/specific_linux.h index f5cae2472..bc71760f8 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.h +++ b/Telegram/SourceFiles/platform/linux/specific_linux.h @@ -22,7 +22,6 @@ inline void SetWatchingMediaKeys(bool watching) { bool InFlatpak(); bool InSnap(); -bool InAppImage(); bool IsStaticBinary(); bool UseGtkIntegration(); bool IsGtkIntegrationForced(); @@ -55,8 +54,6 @@ inline void psCheckLocalSocket(const QString &serverName) { void psWriteDump(); -void psDeleteDir(const QString &dir); - QStringList psInitLogs(); void psClearInitLogs(); diff --git a/Telegram/SourceFiles/platform/mac/file_utilities_mac.mm b/Telegram/SourceFiles/platform/mac/file_utilities_mac.mm index 81f924734..59f7f7329 100644 --- a/Telegram/SourceFiles/platform/mac/file_utilities_mac.mm +++ b/Telegram/SourceFiles/platform/mac/file_utilities_mac.mm @@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/mac/file_utilities_mac.h" #include "base/platform/mac/base_utilities_mac.h" -#include "base/platform/base_platform_file_utilities.h" #include "lang/lang_keys.h" #include "styles/style_window.h" @@ -573,9 +572,5 @@ void UnsafeLaunch(const QString &filepath) { } } -void UnsafeShowInFolder(const QString &filepath) { - base::Platform::ShowInFolder(filepath); -} - } // namespace File } // namespace Platform diff --git a/Telegram/SourceFiles/platform/mac/launcher_mac.mm b/Telegram/SourceFiles/platform/mac/launcher_mac.mm index a8d0cb26b..94fa4d5e5 100644 --- a/Telegram/SourceFiles/platform/mac/launcher_mac.mm +++ b/Telegram/SourceFiles/platform/mac/launcher_mac.mm @@ -10,8 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/crash_reports.h" #include "core/update_checker.h" #include "base/platform/base_platform_info.h" +#include "base/platform/base_platform_file_utilities.h" #include "base/platform/mac/base_utilities_mac.h" -#include "platform/platform_specific.h" #include #include @@ -54,7 +54,7 @@ bool Launcher::launchUpdater(UpdaterLaunch action) { return false; } path = [path stringByAppendingString:@"/Contents/Frameworks/Updater"]; - RemoveQuarantine(QFile::decodeName([path fileSystemRepresentation])); + base::Platform::RemoveQuarantine(QFile::decodeName([path fileSystemRepresentation])); NSMutableArray *args = [[NSMutableArray alloc] initWithObjects:@"-workpath", Q2NSString(cWorkingDir()), @"-procid", nil]; [args addObject:[NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]]]; diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.h b/Telegram/SourceFiles/platform/mac/specific_mac.h index d217b83f6..419ea4241 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac.h +++ b/Telegram/SourceFiles/platform/mac/specific_mac.h @@ -16,8 +16,6 @@ class LocationPoint; namespace Platform { -void RemoveQuarantine(const QString &path); - [[nodiscard]] bool IsDarkMenuBar(); inline QImage GetImageFromClipboard() { @@ -76,8 +74,6 @@ inline void psCheckLocalSocket(const QString &serverName) { void psWriteDump(); -void psDeleteDir(const QString &dir); - QStringList psInitLogs(); void psClearInitLogs(); @@ -92,8 +88,6 @@ QRect psDesktopRect(); int psCleanup(); int psFixPrevious(); -bool psShowOpenWithMenu(int x, int y, const QString &file); - void psNewVersion(); void psDownloadPathEnableAccess(); diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.mm b/Telegram/SourceFiles/platform/mac/specific_mac.mm index 1d7bfefc5..63d320087 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac.mm +++ b/Telegram/SourceFiles/platform/mac/specific_mac.mm @@ -65,10 +65,6 @@ void psWriteDump() { #endif // DESKTOP_APP_DISABLE_CRASH_REPORTS } -void psDeleteDir(const QString &dir) { - objc_deleteDir(dir); -} - QStringList psInitLogs() { return _initLogs; } @@ -118,10 +114,6 @@ void finish() { objc_finish(); } -QString CurrentExecutablePath(int argc, char *argv[]) { - return NS2QString([[NSBundle mainBundle] bundlePath]); -} - QString SingleInstanceLocalServerName(const QString &hash) { #ifndef OS_MAC_STORE return qsl("/tmp/") + hash + '-' + cGUIDStr(); @@ -130,14 +122,6 @@ QString SingleInstanceLocalServerName(const QString &hash) { #endif // OS_MAC_STORE } -void RemoveQuarantine(const QString &path) { - const auto kQuarantineAttribute = "com.apple.quarantine"; - - DEBUG_LOG(("Removing quarantine attribute: %1").arg(path)); - const auto local = QFile::encodeName(path); - removexattr(local.data(), kQuarantineAttribute, 0); -} - bool IsDarkMenuBar() { bool result = false; @autoreleasepool { @@ -234,60 +218,6 @@ bool OpenSystemSettings(SystemSettingsType type) { return true; } -// Taken from https://github.com/trueinteractions/tint/issues/53. -std::optional LastUserInputTime() { - CFMutableDictionaryRef properties = 0; - CFTypeRef obj; - mach_port_t masterPort; - io_iterator_t iter; - io_registry_entry_t curObj; - - IOMasterPort(MACH_PORT_NULL, &masterPort); - - /* Get IOHIDSystem */ - IOServiceGetMatchingServices(masterPort, IOServiceMatching("IOHIDSystem"), &iter); - if (iter == 0) { - return std::nullopt; - } else { - curObj = IOIteratorNext(iter); - } - if (IORegistryEntryCreateCFProperties(curObj, &properties, kCFAllocatorDefault, 0) == KERN_SUCCESS && properties != NULL) { - obj = CFDictionaryGetValue(properties, CFSTR("HIDIdleTime")); - CFRetain(obj); - } else { - return std::nullopt; - } - - uint64 err = ~0L, idleTime = err; - if (obj) { - CFTypeID type = CFGetTypeID(obj); - - if (type == CFDataGetTypeID()) { - CFDataGetBytes((CFDataRef) obj, CFRangeMake(0, sizeof(idleTime)), (UInt8*)&idleTime); - } else if (type == CFNumberGetTypeID()) { - CFNumberGetValue((CFNumberRef)obj, kCFNumberSInt64Type, &idleTime); - } else { - // error - } - - CFRelease(obj); - - if (idleTime != err) { - idleTime /= 1000000; // return as ms - } - } else { - // error - } - - CFRelease((CFTypeRef)properties); - IOObjectRelease(curObj); - IOObjectRelease(iter); - if (idleTime == err) { - return std::nullopt; - } - return (crl::now() - static_cast(idleTime)); -} - void IgnoreApplicationActivationRightNow() { objc_ignoreApplicationActivationRightNow(); } diff --git a/Telegram/SourceFiles/platform/mac/specific_mac_p.h b/Telegram/SourceFiles/platform/mac/specific_mac_p.h index 866e666f3..0a01209d3 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac_p.h +++ b/Telegram/SourceFiles/platform/mac/specific_mac_p.h @@ -19,7 +19,6 @@ void objc_finish(); void objc_activateProgram(WId winId); bool objc_moveFile(const QString &from, const QString &to); -void objc_deleteDir(const QString &dir); double objc_appkitVersion(); diff --git a/Telegram/SourceFiles/platform/mac/specific_mac_p.mm b/Telegram/SourceFiles/platform/mac/specific_mac_p.mm index 6d494395f..59b9539ee 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac_p.mm +++ b/Telegram/SourceFiles/platform/mac/specific_mac_p.mm @@ -340,14 +340,6 @@ bool objc_moveFile(const QString &from, const QString &to) { return false; } -void objc_deleteDir(const QString &dir) { - @autoreleasepool { - - [[NSFileManager defaultManager] removeItemAtPath:Q2NSString(dir) error:nil]; - - } -} - double objc_appkitVersion() { return NSAppKitVersionNumber; } diff --git a/Telegram/SourceFiles/platform/platform_file_utilities.h b/Telegram/SourceFiles/platform/platform_file_utilities.h index cd70b78a6..ad229d244 100644 --- a/Telegram/SourceFiles/platform/platform_file_utilities.h +++ b/Telegram/SourceFiles/platform/platform_file_utilities.h @@ -20,7 +20,6 @@ void UnsafeOpenEmailLink(const QString &email); bool UnsafeShowOpenWithDropdown(const QString &filepath, QPoint menuPosition); bool UnsafeShowOpenWith(const QString &filepath); void UnsafeLaunch(const QString &filepath); -void UnsafeShowInFolder(const QString &filepath); void PostprocessDownloaded(const QString &filepath); diff --git a/Telegram/SourceFiles/platform/platform_specific.h b/Telegram/SourceFiles/platform/platform_specific.h index cbdbb2ec3..0227a3174 100644 --- a/Telegram/SourceFiles/platform/platform_specific.h +++ b/Telegram/SourceFiles/platform/platform_specific.h @@ -31,24 +31,12 @@ enum class SystemSettingsType { void SetWatchingMediaKeys(bool watching); void SetApplicationIcon(const QIcon &icon); -QString CurrentExecutablePath(int argc, char *argv[]); QString SingleInstanceLocalServerName(const QString &hash); void RegisterCustomScheme(bool force = false); PermissionStatus GetPermissionStatus(PermissionType type); void RequestPermission(PermissionType type, Fn resultCallback); void OpenSystemSettingsForPermission(PermissionType type); bool OpenSystemSettings(SystemSettingsType type); - -[[nodiscard]] std::optional LastUserInputTime(); -[[nodiscard]] inline bool LastUserInputTimeSupported() { - return LastUserInputTime().has_value(); -} - -[[nodiscard]] std::optional IsDarkMode(); -[[nodiscard]] inline bool IsDarkModeSupported() { - return IsDarkMode().has_value(); -} - void IgnoreApplicationActivationRightNow(); bool AutostartSupported(); bool TrayIconSupported(); @@ -61,6 +49,11 @@ bool SetWindowExtents(QWindow *window, const QMargins &extents); bool UnsetWindowExtents(QWindow *window); Window::ControlsLayout WindowControlsLayout(); +[[nodiscard]] std::optional IsDarkMode(); +[[nodiscard]] inline bool IsDarkModeSupported() { + return IsDarkMode().has_value(); +} + namespace ThirdParty { void start(); diff --git a/Telegram/SourceFiles/platform/win/file_utilities_win.cpp b/Telegram/SourceFiles/platform/win/file_utilities_win.cpp index fe1dde530..b515f8e65 100644 --- a/Telegram/SourceFiles/platform/win/file_utilities_win.cpp +++ b/Telegram/SourceFiles/platform/win/file_utilities_win.cpp @@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwindow.h" #include "storage/localstorage.h" #include "platform/win/windows_dlls.h" -#include "base/platform/base_platform_file_utilities.h" #include "lang/lang_keys.h" #include "core/application.h" #include "core/crash_reports.h" @@ -266,10 +265,6 @@ void UnsafeLaunch(const QString &filepath) { ShellExecute(0, L"open", wstringPath.c_str(), 0, 0, SW_SHOWNORMAL); } -void UnsafeShowInFolder(const QString &filepath) { - base::Platform::ShowInFolder(filepath); -} - void PostprocessDownloaded(const QString &filepath) { auto wstringZoneFile = QDir::toNativeSeparators(filepath).toStdWString() + L":Zone.Identifier"; auto f = CreateFile(wstringZoneFile.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); diff --git a/Telegram/SourceFiles/platform/win/file_utilities_win.h b/Telegram/SourceFiles/platform/win/file_utilities_win.h index 3647b1ca5..4a5b07f66 100644 --- a/Telegram/SourceFiles/platform/win/file_utilities_win.h +++ b/Telegram/SourceFiles/platform/win/file_utilities_win.h @@ -13,7 +13,7 @@ namespace Platform { namespace File { inline QString UrlToLocal(const QUrl &url) { - return url.toLocalFile(); + return ::File::internal::UrlToLocalDefault(url); } inline void UnsafeOpenUrl(const QString &url) { diff --git a/Telegram/SourceFiles/platform/win/specific_win.cpp b/Telegram/SourceFiles/platform/win/specific_win.cpp index 38cc936db..ae492a312 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.cpp +++ b/Telegram/SourceFiles/platform/win/specific_win.cpp @@ -70,8 +70,6 @@ using namespace Platform; namespace { -constexpr auto kRefreshBadLastUserInputTimeout = 10 * crl::time(1000); - QStringList _initLogs; bool themeInited = false; @@ -87,49 +85,27 @@ public: }; _PsInitializer _psInitializer; -} // namespace +BOOL CALLBACK _ActivateProcess(HWND hWnd, LPARAM lParam) { + uint64 &processId(*(uint64*)lParam); -void psDeleteDir(const QString &dir) { - std::wstring wDir = QDir::toNativeSeparators(dir).toStdWString(); - WCHAR path[4096]; - memcpy(path, wDir.c_str(), (wDir.size() + 1) * sizeof(WCHAR)); - path[wDir.size() + 1] = 0; - SHFILEOPSTRUCT file_op = { - NULL, - FO_DELETE, - path, - L"", - FOF_NOCONFIRMATION | - FOF_NOERRORUI | - FOF_SILENT, - false, - 0, - L"" - }; - int res = SHFileOperation(&file_op); -} + DWORD dwProcessId; + ::GetWindowThreadProcessId(hWnd, &dwProcessId); -namespace { - BOOL CALLBACK _ActivateProcess(HWND hWnd, LPARAM lParam) { - uint64 &processId(*(uint64*)lParam); - - DWORD dwProcessId; - ::GetWindowThreadProcessId(hWnd, &dwProcessId); - - if ((uint64)dwProcessId == processId) { // found top-level window - static const int32 nameBufSize = 1024; - WCHAR nameBuf[nameBufSize]; - int32 len = GetWindowText(hWnd, nameBuf, nameBufSize); - if (len && len < nameBufSize) { - if (QRegularExpression(qsl("^Telegram(\\s*\\(\\d+\\))?$")).match(QString::fromStdWString(nameBuf)).hasMatch()) { - BOOL res = ::SetForegroundWindow(hWnd); - ::SetFocus(hWnd); - return FALSE; - } + if ((uint64)dwProcessId == processId) { // found top-level window + static const int32 nameBufSize = 1024; + WCHAR nameBuf[nameBufSize]; + int32 len = GetWindowText(hWnd, nameBuf, nameBufSize); + if (len && len < nameBufSize) { + if (QRegularExpression(qsl("^Telegram(\\s*\\(\\d+\\))?$")).match(QString::fromStdWString(nameBuf)).hasMatch()) { + BOOL res = ::SetForegroundWindow(hWnd); + ::SetFocus(hWnd); + return FALSE; } } - return TRUE; } + return TRUE; +} + } QStringList psInitLogs() { @@ -312,78 +288,10 @@ void SetApplicationIcon(const QIcon &icon) { QApplication::setWindowIcon(icon); } -QString CurrentExecutablePath(int argc, char *argv[]) { - WCHAR result[MAX_PATH + 1] = { 0 }; - auto count = GetModuleFileName(nullptr, result, MAX_PATH + 1); - if (count < MAX_PATH + 1) { - auto info = QFileInfo(QDir::fromNativeSeparators(QString::fromWCharArray(result))); - return info.absoluteFilePath(); - } - - // Fallback to the first command line argument. - auto argsCount = 0; - if (auto args = CommandLineToArgvW(GetCommandLine(), &argsCount)) { - auto info = QFileInfo(QDir::fromNativeSeparators(QString::fromWCharArray(args[0]))); - LocalFree(args); - return info.absoluteFilePath(); - } - return QString(); -} - QString SingleInstanceLocalServerName(const QString &hash) { return qsl("Global\\") + hash + '-' + cGUIDStr(); } -std::optional LastUserInputTime() { - auto lii = LASTINPUTINFO{ 0 }; - lii.cbSize = sizeof(LASTINPUTINFO); - if (!GetLastInputInfo(&lii)) { - return std::nullopt; - } - const auto now = crl::now(); - const auto input = crl::time(lii.dwTime); - static auto LastTrackedInput = input; - static auto LastTrackedWhen = now; - - const auto ticks32 = crl::time(GetTickCount()); - const auto ticks64 = crl::time(GetTickCount64()); - const auto elapsed = std::max(ticks32, ticks64) - input; - const auto good = (std::abs(ticks32 - ticks64) <= crl::time(1000)) - && (elapsed >= 0); - if (good) { - LastTrackedInput = input; - LastTrackedWhen = now; - return (now > elapsed) ? (now - elapsed) : crl::time(0); - } - - static auto WaitingDelayed = false; - if (!WaitingDelayed) { - WaitingDelayed = true; - base::call_delayed(kRefreshBadLastUserInputTimeout, [=] { - WaitingDelayed = false; - [[maybe_unused]] const auto cheked = LastUserInputTime(); - }); - } - constexpr auto OverrunLimit = std::numeric_limits::max(); - constexpr auto OverrunThreshold = OverrunLimit / 4; - if (LastTrackedInput == input) { - return LastTrackedWhen; - } - const auto guard = gsl::finally([&] { - LastTrackedInput = input; - LastTrackedWhen = now; - }); - if (input > LastTrackedInput) { - const auto add = input - LastTrackedInput; - return std::min(LastTrackedWhen + add, now); - } else if (crl::time(OverrunLimit) + input - LastTrackedInput - < crl::time(OverrunThreshold)) { - const auto add = crl::time(OverrunLimit) + input - LastTrackedInput; - return std::min(LastTrackedWhen + add, now); - } - return LastTrackedWhen; -} - std::optional IsDarkMode() { static const auto kSystemVersion = QOperatingSystemVersion::current(); static const auto kDarkModeAddedVersion = QOperatingSystemVersion( diff --git a/Telegram/SourceFiles/platform/win/specific_win.h b/Telegram/SourceFiles/platform/win/specific_win.h index 1ef5471c1..92114eda5 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.h +++ b/Telegram/SourceFiles/platform/win/specific_win.h @@ -65,8 +65,6 @@ inline void psCheckLocalSocket(const QString &) { void psWriteDump(); -void psDeleteDir(const QString &dir); - QStringList psInitLogs(); void psClearInitLogs(); diff --git a/Telegram/SourceFiles/settings/settings_privacy_security.cpp b/Telegram/SourceFiles/settings/settings_privacy_security.cpp index adac9798f..338009173 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_security.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_security.cpp @@ -32,7 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/calls_instance.h" #include "core/core_cloud_password.h" #include "core/update_checker.h" -#include "platform/platform_specific.h" +#include "base/platform/base_platform_last_input.h" #include "lang/lang_keys.h" #include "data/data_session.h" #include "data/data_chat.h" @@ -288,7 +288,7 @@ void SetupLocalPasscode( Ui::show(Box(&controller->session(), true)); }); - const auto label = Platform::LastUserInputTimeSupported() + const auto label = base::Platform::LastUserInputTimeSupported() ? tr::lng_passcode_autolock_away : tr::lng_passcode_autolock_inactive; auto value = PasscodeChanges( diff --git a/Telegram/SourceFiles/window/notifications_manager_default.cpp b/Telegram/SourceFiles/window/notifications_manager_default.cpp index ad4ddfa57..3162481b6 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.cpp +++ b/Telegram/SourceFiles/window/notifications_manager_default.cpp @@ -24,7 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_account.h" #include "history/history.h" #include "history/history_item.h" -#include "platform/platform_specific.h" +#include "base/platform/base_platform_last_input.h" #include "base/call_delayed.h" #include "facades.h" #include "app.h" @@ -663,7 +663,7 @@ void Notification::prepareActionsCache() { bool Notification::checkLastInput(bool hasReplyingNotifications) { if (!_waitingForInput) return true; - const auto waitForUserInput = Platform::LastUserInputTimeSupported() + const auto waitForUserInput = base::Platform::LastUserInputTimeSupported() ? (Core::App().lastNonIdleTime() <= _started) : false; if (!waitForUserInput) { diff --git a/Telegram/SourceFiles/window/notifications_utilities.cpp b/Telegram/SourceFiles/window/notifications_utilities.cpp index 86dc2a905..34b0c9cb0 100644 --- a/Telegram/SourceFiles/window/notifications_utilities.cpp +++ b/Telegram/SourceFiles/window/notifications_utilities.cpp @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "window/notifications_utilities.h" -#include "platform/platform_specific.h" +#include "base/platform/base_platform_file_utilities.h" #include "core/application.h" #include "data/data_peer.h" #include "ui/empty_userpic.h" @@ -36,7 +36,7 @@ CachedUserpics::~CachedUserpics() { } // This works about 1200ms on Windows for a folder with one image O_o - // psDeleteDir(cWorkingDir() + qsl("tdata/temp")); + // base::Platform::DeleteDirectory(cWorkingDir() + qsl("tdata/temp")); } } From 49e96d857a252c61ad3272d39b3ec842d598336f Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 13 Oct 2020 00:37:55 +0400 Subject: [PATCH 056/190] Add drag distance for move-by-titlebar to process double click right --- .../SourceFiles/window/window_title_qt.cpp | 38 +++++++++++++++---- Telegram/SourceFiles/window/window_title_qt.h | 5 ++- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/window/window_title_qt.cpp b/Telegram/SourceFiles/window/window_title_qt.cpp index 01aaca62b..71cb2118a 100644 --- a/Telegram/SourceFiles/window/window_title_qt.cpp +++ b/Telegram/SourceFiles/window/window_title_qt.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +#include namespace Window { namespace { @@ -231,17 +232,26 @@ void TitleWidgetQt::resizeEvent(QResizeEvent *e) { void TitleWidgetQt::mousePressEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) { - startMove(); + if ((crl::now() - _pressedForMoveTime) + < QApplication::doubleClickInterval()) { + if (_maximizedState) { + window()->setWindowState(Qt::WindowNoState); + } else { + window()->setWindowState(Qt::WindowMaximized); + } + } else { + _pressedForMove = true; + _pressedForMoveTime = crl::now(); + _pressedForMovePoint = e->windowPos().toPoint(); + } } else if (e->button() == Qt::RightButton) { Platform::ShowWindowMenu(window()->windowHandle()); } } -void TitleWidgetQt::mouseDoubleClickEvent(QMouseEvent *e) { - if (_maximizedState) { - window()->setWindowState(Qt::WindowNoState); - } else { - window()->setWindowState(Qt::WindowMaximized); +void TitleWidgetQt::mouseReleaseEvent(QMouseEvent *e) { + if (e->button() == Qt::LeftButton) { + _pressedForMove = false; } } @@ -250,14 +260,26 @@ bool TitleWidgetQt::eventFilter(QObject *obj, QEvent *e) { || e->type() == QEvent::MouseButtonPress) { if (window()->isAncestorOf(static_cast(obj))) { const auto mouseEvent = static_cast(e); - const auto edges = edgesFromPos( - mouseEvent->windowPos().toPoint()); + const auto currentPoint = mouseEvent->windowPos().toPoint(); + const auto edges = edgesFromPos(currentPoint); + const auto dragDistance = QApplication::startDragDistance(); if (e->type() == QEvent::MouseMove && mouseEvent->buttons() == Qt::NoButton) { + if (_pressedForMove) { + _pressedForMove = false; + } + updateCursor(edges); } + if (e->type() == QEvent::MouseMove + && _pressedForMove + && ((currentPoint - _pressedForMovePoint).manhattanLength() + >= dragDistance)) { + return startMove(); + } + if (e->type() == QEvent::MouseButtonPress && mouseEvent->button() == Qt::LeftButton && !_maximizedState) { diff --git a/Telegram/SourceFiles/window/window_title_qt.h b/Telegram/SourceFiles/window/window_title_qt.h index 40f823b0b..126711e95 100644 --- a/Telegram/SourceFiles/window/window_title_qt.h +++ b/Telegram/SourceFiles/window/window_title_qt.h @@ -39,7 +39,7 @@ protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void mousePressEvent(QMouseEvent *e) override; - void mouseDoubleClickEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; bool eventFilter(QObject *obj, QEvent *e) override; @@ -73,6 +73,9 @@ private: bool _windowWasFrameless = false; bool _cursorOverriden = false; bool _extentsSet = false; + bool _pressedForMove = false; + crl::time _pressedForMoveTime = 0; + QPoint _pressedForMovePoint; }; From 8f5b13600302ebfec616bbe13d6b995017456479 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 16 Oct 2020 07:02:05 +0400 Subject: [PATCH 057/190] Implement possibility to hide taskbar icon on Linux --- .../platform/linux/main_window_linux.cpp | 91 ++++++++++++++++++- .../platform/linux/specific_linux.cpp | 23 +++++ .../SourceFiles/platform/mac/specific_mac.h | 4 + .../SourceFiles/platform/platform_specific.h | 1 + .../SourceFiles/platform/win/specific_win.h | 4 + .../settings/settings_advanced.cpp | 2 +- 6 files changed, 119 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index 1c8a1cbb5..eaad0ce2e 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -23,6 +23,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/localstorage.h" #include "window/window_controller.h" #include "window/window_session_controller.h" +#include "base/platform/base_platform_info.h" +#include "base/platform/linux/base_xcb_utilities_linux.h" +#include "base/call_delayed.h" #include "ui/widgets/input_fields.h" #include "facades.h" #include "app.h" @@ -40,6 +43,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +#include + extern "C" { #undef signals #include @@ -74,6 +79,64 @@ base::flat_map TrayIconImageBack; QIcon TrayIcon; QString TrayIconThemeName, TrayIconName; +bool XCBSkipTaskbar(QWindow *window, bool set) { + const auto connection = base::Platform::XCB::GetConnectionFromQt(); + if (!connection) { + return false; + } + + const auto root = base::Platform::XCB::GetRootWindowFromQt(); + if (!root.has_value()) { + return false; + } + + const auto stateAtom = base::Platform::XCB::GetAtom( + connection, + "_NET_WM_STATE"); + + if (!stateAtom.has_value()) { + return false; + } + + const auto skipTaskbarAtom = base::Platform::XCB::GetAtom( + connection, + "_NET_WM_STATE_SKIP_TASKBAR"); + + if (!skipTaskbarAtom.has_value()) { + return false; + } + + xcb_client_message_event_t xev; + xev.response_type = XCB_CLIENT_MESSAGE; + xev.type = *stateAtom; + xev.sequence = 0; + xev.window = window->winId(); + xev.format = 32; + xev.data.data32[0] = set ? 1 : 0; + xev.data.data32[1] = *skipTaskbarAtom; + xev.data.data32[2] = 0; + xev.data.data32[3] = 0; + xev.data.data32[4] = 0; + + xcb_send_event( + connection, + false, + *root, + XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT + | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, + reinterpret_cast(&xev)); + + return true; +} + +bool SkipTaskbar(QWindow *window, bool set) { + if (!IsWayland()) { + return XCBSkipTaskbar(window, set); + } + + return false; +} + QString GetPanelIconName(int counter, bool muted) { return (counter > 0) ? (muted @@ -618,6 +681,10 @@ void MainWindow::handleSNIHostRegistered() { trayIcon = nullptr; psSetupTrayIcon(); + + SkipTaskbar( + windowHandle(), + Global::WorkMode().value() == dbiwmTrayOnly); } void MainWindow::handleSNIOwnerChanged( @@ -649,6 +716,10 @@ void MainWindow::handleSNIOwnerChanged( } else { LOG(("System tray is not available.")); } + + SkipTaskbar( + windowHandle(), + (Global::WorkMode().value() == dbiwmTrayOnly) && trayAvailable()); } void MainWindow::handleAppMenuOwnerChanged( @@ -724,6 +795,8 @@ void MainWindow::workmodeUpdated(DBIWorkMode mode) { } else { psSetupTrayIcon(); } + + SkipTaskbar(windowHandle(), mode == dbiwmTrayOnly); } void MainWindow::unreadCounterChangedHook() { @@ -820,9 +893,6 @@ void MainWindow::createGlobalMenu() { void MainWindow::updateGlobalMenuHook() { } -void MainWindow::handleVisibleChangedHook(bool visible) { -} - #else // DESKTOP_APP_DISABLE_DBUS_INTEGRATION void MainWindow::createGlobalMenu() { @@ -1113,7 +1183,19 @@ void MainWindow::updateGlobalMenuHook() { ForceDisabled(psClearFormat, !markdownEnabled); } +#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION + void MainWindow::handleVisibleChangedHook(bool visible) { + if (visible) { + base::call_delayed(1, this, [=] { + SkipTaskbar( + windowHandle(), + (Global::WorkMode().value() == dbiwmTrayOnly) + && trayAvailable()); + }); + } + +#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION if (_appMenuSupported && !_mainMenuPath.path().isEmpty()) { if (visible) { RegisterAppMenu(winId(), _mainMenuPath); @@ -1121,9 +1203,8 @@ void MainWindow::handleVisibleChangedHook(bool visible) { UnregisterAppMenu(winId()); } } -} - #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION +} MainWindow::~MainWindow() { #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index f07b61349..554f7fd1a 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -502,6 +502,25 @@ bool UnsetXCBFrameExtents(QWindow *window) { return true; } +bool XCBSkipTaskbarSupported() { + const auto connection = base::Platform::XCB::GetConnectionFromQt(); + if (!connection) { + return false; + } + + const auto skipTaskbarAtom = base::Platform::XCB::GetAtom( + connection, + "_NET_WM_STATE_SKIP_TASKBAR"); + + if (!skipTaskbarAtom.has_value()) { + return false; + } + + return ranges::contains( + base::Platform::XCB::GetWMSupported(connection), + *skipTaskbarAtom); +} + Window::Control GtkKeywordToWindowControl(const QString &keyword) { if (keyword == qstr("minimize")) { return Window::Control::Minimize; @@ -769,6 +788,10 @@ bool TrayIconSupported() { : false; } +bool SkipTaskbarSupported() { + return !IsWayland() && XCBSkipTaskbarSupported(); +} + bool StartSystemMove(QWindow *window) { if (IsWayland()) { return StartWaylandMove(window); diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.h b/Telegram/SourceFiles/platform/mac/specific_mac.h index 419ea4241..af04f1320 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac.h +++ b/Telegram/SourceFiles/platform/mac/specific_mac.h @@ -42,6 +42,10 @@ inline bool TrayIconSupported() { return true; } +inline bool SkipTaskbarSupported() { + return false; +} + inline bool SetWindowExtents(QWindow *window, const QMargins &extents) { return false; } diff --git a/Telegram/SourceFiles/platform/platform_specific.h b/Telegram/SourceFiles/platform/platform_specific.h index 0227a3174..45fafaefa 100644 --- a/Telegram/SourceFiles/platform/platform_specific.h +++ b/Telegram/SourceFiles/platform/platform_specific.h @@ -40,6 +40,7 @@ bool OpenSystemSettings(SystemSettingsType type); void IgnoreApplicationActivationRightNow(); bool AutostartSupported(); bool TrayIconSupported(); +bool SkipTaskbarSupported(); QImage GetImageFromClipboard(); bool StartSystemMove(QWindow *window); bool StartSystemResize(QWindow *window, Qt::Edges edges); diff --git a/Telegram/SourceFiles/platform/win/specific_win.h b/Telegram/SourceFiles/platform/win/specific_win.h index 92114eda5..6e4ae4b54 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.h +++ b/Telegram/SourceFiles/platform/win/specific_win.h @@ -38,6 +38,10 @@ inline bool TrayIconSupported() { return true; } +inline bool SkipTaskbarSupported() { + return true; +} + inline bool SetWindowExtents(QWindow *window, const QMargins &extents) { return false; } diff --git a/Telegram/SourceFiles/settings/settings_advanced.cpp b/Telegram/SourceFiles/settings/settings_advanced.cpp index 3471c0d34..b9c1f7105 100644 --- a/Telegram/SourceFiles/settings/settings_advanced.cpp +++ b/Telegram/SourceFiles/settings/settings_advanced.cpp @@ -352,7 +352,7 @@ void SetupSystemIntegrationContent(not_null container) { return (workMode == dbiwmWindowOnly) || (workMode == dbiwmWindowAndTray); }; - const auto taskbar = Platform::IsWindows() + const auto taskbar = Platform::SkipTaskbarSupported() ? addCheckbox( tr::lng_settings_workmode_window(), taskbarEnabled()) From d73d3cd43d66a61961835847e67d9c122c3633fd Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 16 Oct 2020 08:46:20 +0400 Subject: [PATCH 058/190] Implement ShowWindowMenu for XCB --- .../platform/linux/specific_linux.cpp | 49 ++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 554f7fd1a..6d37f1f15 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -374,6 +374,51 @@ bool StartXCBMoveResize(QWindow *window, int edges) { return true; } +bool ShowXCBWindowMenu(QWindow *window) { + const auto connection = base::Platform::XCB::GetConnectionFromQt(); + if (!connection) { + return false; + } + + const auto root = base::Platform::XCB::GetRootWindowFromQt(); + if (!root.has_value()) { + return false; + } + + const auto showWindowMenuAtom = base::Platform::XCB::GetAtom( + connection, + "_GTK_SHOW_WINDOW_MENU"); + + if (!showWindowMenuAtom.has_value()) { + return false; + } + + const auto globalPos = QCursor::pos(); + + xcb_client_message_event_t xev; + xev.response_type = XCB_CLIENT_MESSAGE; + xev.type = *showWindowMenuAtom; + xev.sequence = 0; + xev.window = window->winId(); + xev.format = 32; + xev.data.data32[0] = 0; + xev.data.data32[1] = globalPos.x(); + xev.data.data32[2] = globalPos.y(); + xev.data.data32[3] = 0; + xev.data.data32[4] = 0; + + xcb_ungrab_pointer(connection, XCB_CURRENT_TIME); + xcb_send_event( + connection, + false, + *root, + XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT + | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, + reinterpret_cast(&xev)); + + return true; +} + bool StartWaylandMove(QWindow *window) { // There are startSystemMove on Qt 5.15 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) && !defined DESKTOP_APP_QT_PATCHED @@ -811,9 +856,9 @@ bool StartSystemResize(QWindow *window, Qt::Edges edges) { bool ShowWindowMenu(QWindow *window) { if (IsWayland()) { return ShowWaylandWindowMenu(window); + } else { + return ShowXCBWindowMenu(window); } - - return false; } bool SetWindowExtents(QWindow *window, const QMargins &extents) { From af6b07b780120d690e95616c6c84f6ff45e60d5d Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 27 Oct 2020 13:29:50 +0400 Subject: [PATCH 059/190] Use crl::on_main in MainWindow::sniSignalEmitted --- .../SourceFiles/platform/linux/main_window_linux.cpp | 10 +++++++--- .../SourceFiles/platform/linux/main_window_linux.h | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index eaad0ce2e..ccedc4e52 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -509,7 +509,7 @@ void MainWindow::initHook() { _sniDBusProxy, "g-signal", G_CALLBACK(sniSignalEmitted), - this); + nullptr); auto sniWatcher = new QDBusServiceWatcher( kSNIWatcherService.utf16(), @@ -655,9 +655,13 @@ void MainWindow::sniSignalEmitted( gchar *sender_name, gchar *signal_name, GVariant *parameters, - MainWindow *window) { + gpointer user_data) { if(signal_name == qstr("StatusNotifierHostRegistered")) { - window->handleSNIHostRegistered(); + crl::on_main([] { + if (const auto window = App::wnd()) { + window->handleSNIHostRegistered(); + } + }); } } diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.h b/Telegram/SourceFiles/platform/linux/main_window_linux.h index a39089326..5dfb5eb8a 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.h +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.h @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +typedef void* gpointer; typedef char gchar; typedef struct _GVariant GVariant; typedef struct _GDBusProxy GDBusProxy; @@ -145,7 +146,7 @@ private: gchar *sender_name, gchar *signal_name, GVariant *parameters, - MainWindow *window); + gpointer user_data); #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION }; From 9b99bb172abffc435fc6815b4c80283de06737ac Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 27 Oct 2020 14:13:28 +0400 Subject: [PATCH 060/190] Make methods called from static methods private --- Telegram/SourceFiles/platform/linux/main_window_linux.h | 5 +---- .../platform/linux/notifications_manager_linux.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.h b/Telegram/SourceFiles/platform/linux/main_window_linux.h index 5dfb5eb8a..73934b4dc 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.h +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.h @@ -42,10 +42,6 @@ public: return _sniAvailable || QSystemTrayIcon::isSystemTrayAvailable(); } -#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION - void handleSNIHostRegistered(); -#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION - static void LibsLoaded(); ~MainWindow(); @@ -115,6 +111,7 @@ private: void setSNITrayIcon(int counter, bool muted); void attachToSNITrayIcon(); + void handleSNIHostRegistered(); void handleSNIOwnerChanged( const QString &service, diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index b4a0d8f83..bacb22379 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -238,10 +238,6 @@ public: void close(); void setImage(const QString &imagePath); - void notificationClosed(uint id, uint reason); - void actionInvoked(uint id, const QString &actionName); - void notificationReplied(uint id, const QString &text); - private: GDBusConnection *_dbusConnection = nullptr; base::weak_ptr _manager; @@ -259,6 +255,10 @@ private: guint _notificationClosedSignalId = 0; NotificationId _id; + void notificationClosed(uint id, uint reason); + void actionInvoked(uint id, const QString &actionName); + void notificationReplied(uint id, const QString &text); + static void signalEmitted( GDBusConnection *connection, const gchar *sender_name, From bbc59c1a999dd753a11c4c8fd4f42f853c77aadc Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 28 Oct 2020 18:15:43 +0400 Subject: [PATCH 061/190] Use Launcher::initHook on Linux --- Telegram/SourceFiles/core/launcher.cpp | 4 ---- Telegram/SourceFiles/platform/linux/launcher_linux.cpp | 7 +++++++ Telegram/SourceFiles/platform/linux/launcher_linux.h | 1 + 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/core/launcher.cpp b/Telegram/SourceFiles/core/launcher.cpp index 28271acd9..aa50e19a2 100644 --- a/Telegram/SourceFiles/core/launcher.cpp +++ b/Telegram/SourceFiles/core/launcher.cpp @@ -301,10 +301,6 @@ void Launcher::init() { QApplication::setApplicationName(qsl("TelegramDesktop")); -#if defined Q_OS_UNIX && !defined Q_OS_MAC && QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) - QApplication::setDesktopFileName(Platform::GetLauncherFilename()); -#endif - #ifndef OS_MAC_OLD QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true); #endif // OS_MAC_OLD diff --git a/Telegram/SourceFiles/platform/linux/launcher_linux.cpp b/Telegram/SourceFiles/platform/linux/launcher_linux.cpp index 1d948fd19..0a21a1841 100644 --- a/Telegram/SourceFiles/platform/linux/launcher_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/launcher_linux.cpp @@ -8,9 +8,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/linux/launcher_linux.h" #include "base/platform/base_platform_info.h" +#include "platform/linux/specific_linux.h" #include "core/crash_reports.h" #include "core/update_checker.h" +#include + #include #include #include @@ -46,6 +49,10 @@ Launcher::Launcher(int argc, char *argv[]) : Core::Launcher(argc, argv, DeviceModelPretty(), SystemVersionPretty()) { } +void Launcher::initHook() { + QApplication::setDesktopFileName(GetLauncherFilename()); +} + bool Launcher::launchUpdater(UpdaterLaunch action) { if (cExeName().isEmpty()) { return false; diff --git a/Telegram/SourceFiles/platform/linux/launcher_linux.h b/Telegram/SourceFiles/platform/linux/launcher_linux.h index 0b7dea131..188b1b6e6 100644 --- a/Telegram/SourceFiles/platform/linux/launcher_linux.h +++ b/Telegram/SourceFiles/platform/linux/launcher_linux.h @@ -16,6 +16,7 @@ public: Launcher(int argc, char *argv[]); private: + void initHook() override; bool launchUpdater(UpdaterLaunch action) override; }; From bb94507af12d475673da7ddbb9b29920765fad0f Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 29 Oct 2020 01:29:48 +0400 Subject: [PATCH 062/190] Use UrlClickHandler::Open instead of File::OpenUrl --- Telegram/SourceFiles/boxes/about_box.cpp | 3 +-- Telegram/SourceFiles/core/application.cpp | 1 - Telegram/SourceFiles/history/history_location_manager.cpp | 4 ++-- Telegram/SourceFiles/settings/settings_main.cpp | 4 ++-- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/boxes/about_box.cpp b/Telegram/SourceFiles/boxes/about_box.cpp index 90d2e82cc..cb4895c89 100644 --- a/Telegram/SourceFiles/boxes/about_box.cpp +++ b/Telegram/SourceFiles/boxes/about_box.cpp @@ -14,7 +14,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/text/text_utilities.h" -#include "core/file_utilities.h" #include "base/platform/base_platform_info.h" #include "core/click_handler_types.h" #include "core/update_checker.h" @@ -108,7 +107,7 @@ void AboutBox::showVersionHistory() { Ui::show(Box("The link to the current private alpha version of Telegram Desktop was copied to the clipboard.")); } else { - File::OpenUrl(qsl("https://desktop.telegram.org/changelog")); + UrlClickHandler::Open(qsl("https://desktop.telegram.org/changelog")); } } diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index 001a3ad23..d0f45f9b9 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -79,7 +79,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include -#include namespace Core { namespace { diff --git a/Telegram/SourceFiles/history/history_location_manager.cpp b/Telegram/SourceFiles/history/history_location_manager.cpp index 08f33f94f..13142c1da 100644 --- a/Telegram/SourceFiles/history/history_location_manager.cpp +++ b/Telegram/SourceFiles/history/history_location_manager.cpp @@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_location_manager.h" #include "mainwidget.h" -#include "core/file_utilities.h" +#include "core/click_handler_types.h" #include "lang/lang_keys.h" #include "ui/image/image.h" #include "data/data_file_origin.h" @@ -24,7 +24,7 @@ QString LocationClickHandler::copyToClipboardContextItemText() const { void LocationClickHandler::onClick(ClickContext context) const { if (!psLaunchMaps(_point)) { - File::OpenUrl(_text); + UrlClickHandler::Open(_text); } } diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp index c7d9579d5..36911968c 100644 --- a/Telegram/SourceFiles/settings/settings_main.cpp +++ b/Telegram/SourceFiles/settings/settings_main.cpp @@ -34,7 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_sensitive_content.h" #include "api/api_global_privacy.h" #include "window/window_session_controller.h" -#include "core/file_utilities.h" +#include "core/click_handler_types.h" #include "base/call_delayed.h" #include "facades.h" #include "app.h" @@ -274,7 +274,7 @@ void SetupInterfaceScale( } void OpenFaq() { - File::OpenUrl(telegramFaqLink()); + UrlClickHandler::Open(telegramFaqLink()); } void SetupFaq(not_null container, bool icon) { From e31ffb699aa0a4640b0809355f341450fae1a033 Mon Sep 17 00:00:00 2001 From: andry-dev Date: Thu, 29 Oct 2020 19:12:30 +0100 Subject: [PATCH 063/190] Ignore additional video streams inside an audio file. Fixes #5840, #5357 and #4327. --- .../SourceFiles/media/audio/media_audio.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/media/audio/media_audio.cpp b/Telegram/SourceFiles/media/audio/media_audio.cpp index 6e4ad2d17..5e79687ad 100644 --- a/Telegram/SourceFiles/media/audio/media_audio.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio.cpp @@ -1531,15 +1531,12 @@ public: int res = 0; char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - int videoStreamId = av_find_best_stream(fmtContext, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0); - if (videoStreamId >= 0) { - DEBUG_LOG(("Audio Read Error: Found video stream in file '%1', data size '%2', error %3, %4").arg(_file.name()).arg(_data.size()).arg(videoStreamId).arg(av_make_error_string(err, sizeof(err), streamId))); - return false; - } - for (int32 i = 0, l = fmtContext->nb_streams; i < l; ++i) { const auto stream = fmtContext->streams[i]; if (stream->disposition & AV_DISPOSITION_ATTACHED_PIC) { + if (!_cover.isNull()) { + continue; + } const auto &packet = stream->attached_pic; if (packet.size) { const auto coverBytes = QByteArray( @@ -1555,9 +1552,15 @@ public: if (!_cover.isNull()) { _coverBytes = coverBytes; _coverFormat = format; - break; } } + } else if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + DEBUG_LOG(("Audio Read Error: Found video stream in file '%1', data size '%2', error %3, %4") + .arg(_file.name()) + .arg(_data.size()) + .arg(i) + .arg(av_make_error_string(err, sizeof(err), streamId))); + return false; } } From 822d1718a97cbd5b6b68595615d4e3c1711c06d3 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 7 Oct 2020 06:21:20 +0400 Subject: [PATCH 064/190] Use MozJPEG --- .github/workflows/linux.yml | 19 ++++++++++++++-- .github/workflows/mac.yml | 21 +++++++++++++++-- .github/workflows/win.yml | 39 ++++++++++++++++---------------- docs/building-cmake.md | 16 ++++++++++--- docs/building-msvc.md | 16 ++++++++++--- docs/building-osx.md | 17 +++++++++++--- docs/building-xcode.md | 20 ++++++++++++++--- snap/snapcraft.yaml | 45 ++++++++++++++++++++++++++++++++++--- 8 files changed, 154 insertions(+), 39 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 7b4f32953..6d69c0b1a 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -157,6 +157,22 @@ jobs: cmake --version + - name: MozJPEG. + run: | + cd $LibrariesPath + + git clone -b v4.0.1-rc2 $GIT/mozilla/mozjpeg.git + cd mozjpeg + cmake -B build . \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr/local \ + -DWITH_JPEG8=ON \ + -DPNG_SUPPORTED=OFF + cmake --build build -j$(nproc) + sudo cmake --install build + cd .. + rm -rf mozjpeg + - name: Opus cache. id: cache-opus uses: actions/cache@v2 @@ -439,7 +455,6 @@ jobs: -confirm-license \ -qt-zlib \ -qt-libpng \ - -qt-libjpeg \ -qt-harfbuzz \ -qt-pcre \ -qt-xcb \ @@ -531,7 +546,7 @@ jobs: cmake -G Ninja \ -DCMAKE_BUILD_TYPE=Debug \ -DTG_OWT_SPECIAL_TARGET=linux \ - -DTG_OWT_LIBJPEG_INCLUDE_PATH=`pwd`/../../../qt_$QT/qtbase/src/3rdparty/libjpeg \ + -DTG_OWT_LIBJPEG_INCLUDE_PATH=/usr/local/include \ -DTG_OWT_OPENSSL_INCLUDE_PATH=$OPENSSL_PREFIX/include \ -DTG_OWT_OPUS_INCLUDE_PATH=/usr/local/include/opus \ -DTG_OWT_FFMPEG_INCLUDE_PATH=/usr/local/include \ diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 11ace8da8..48ad4d4da 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -132,6 +132,20 @@ jobs: make -j$(nproc) sudo make install + - name: MozJPEG. + run: | + cd $LibrariesPath + + git clone -b v4.0.1-rc2 $GIT/mozilla/mozjpeg.git + cd mozjpeg + cmake -B build . \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr/local/macos \ + -DWITH_JPEG8=ON \ + -DPNG_SUPPORTED=OFF + cmake --build build -j$(nproc) + sudo cmake --install build + - name: OpenSSL cache. id: cache-openssl uses: actions/cache@v2 @@ -443,7 +457,10 @@ jobs: -securetransport \ -nomake examples \ -nomake tests \ - -platform macx-clang + -platform macx-clang \ + -I "/usr/local/macos/include" \ + LIBJPEG_LIBS="/usr/local/macos/lib/libjpeg.a" \ + ZLIB_LIBS="/usr/local/macos/lib/libz.a" make -j$(nproc) sudo make install @@ -467,7 +484,7 @@ jobs: cd tg_owt/out/Debug cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug \ -DTG_OWT_SPECIAL_TARGET=mac \ - -DTG_OWT_LIBJPEG_INCLUDE_PATH=`pwd`/../../../qt_$QT/qtbase/src/3rdparty/libjpeg \ + -DTG_OWT_LIBJPEG_INCLUDE_PATH=/usr/local/macos/include \ -DTG_OWT_OPENSSL_INCLUDE_PATH=`pwd`/../../../openssl_$OPENSSL_VER/include \ -DTG_OWT_OPUS_INCLUDE_PATH=$PREFIX/include/opus \ -DTG_OWT_FFMPEG_INCLUDE_PATH=/usr/local/include \ diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 02140b397..83ffcc001 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -186,6 +186,20 @@ jobs: cd contrib\vstudio\vc14 msbuild -m zlibstat.vcxproj /property:Configuration=Debug + - name: MozJPEG. + shell: cmd + run: | + %VC% + + git clone -b v4.0.1-rc2 %GIT%/mozilla/mozjpeg.git + cd mozjpeg + cmake . ^ + -G "Visual Studio 16 2019" ^ + -A Win32 ^ + -DWITH_JPEG8=ON ^ + -DPNG_SUPPORTED=OFF + cmake --build . --config Debug + - name: OpenAL Soft cache. id: cache-openal uses: actions/cache@v2 @@ -326,7 +340,10 @@ jobs: -mp ^ -nomake examples ^ -nomake tests ^ - -platform win32-msvc + -platform win32-msvc ^ + -I "%LibrariesPath%\mozjpeg" ^ + LIBJPEG_LIBS_DEBUG="%LibrariesPath%\mozjpeg\Debug\jpeg-static.lib" ^ + LIBJPEG_LIBS_RELEASE="%LibrariesPath%\mozjpeg\Release\jpeg-static.lib" - name: Qt 5.12.8 build. if: steps.cache-qt.outputs.cache-hit != 'true' run: | @@ -350,28 +367,13 @@ jobs: run: | %VC% - :: Qt libjpeg. - mkdir qt_%QT% - cd qt_%QT% - git clone -b %QT_VER% https://github.com/qt/qtbase - - move qtbase\src\3rdparty\libjpeg .. - cd .. - dir - rmdir /S /Q qt_%QT% - mkdir qt_%QT%\qtbase\src\3rdparty\ - move libjpeg qt_%QT%\qtbase\src\3rdparty\ - - :: WebRTC. - cd %LibrariesPath% - git clone %GIT%/desktop-app/tg_owt.git mkdir tg_owt\out\Debug cd tg_owt\out\Debug cmake -G Ninja ^ -DCMAKE_BUILD_TYPE=Debug ^ -DTG_OWT_SPECIAL_TARGET=win ^ - -DTG_OWT_LIBJPEG_INCLUDE_PATH=%cd%/../../../qt_%QT%/qtbase/src/3rdparty/libjpeg ^ + -DTG_OWT_LIBJPEG_INCLUDE_PATH=%cd%/../../../mozjpeg ^ -DTG_OWT_OPENSSL_INCLUDE_PATH=%cd%/../../../openssl_%OPENSSL_VER%/include ^ -DTG_OWT_OPUS_INCLUDE_PATH=%cd%/../../../opus/include ^ -DTG_OWT_FFMPEG_INCLUDE_PATH=%cd%/../../../ffmpeg ^ @@ -386,9 +388,6 @@ jobs: mkdir out\Debug move tg_owt.lib out\Debug\tg_owt.lib - cd %LibrariesPath% - rmdir /S /Q qt_%QT% - - name: Read defines. shell: bash run: | diff --git a/docs/building-cmake.md b/docs/building-cmake.md index cc0c55cde..11e722095 100644 --- a/docs/building-cmake.md +++ b/docs/building-cmake.md @@ -58,6 +58,17 @@ Go to ***BuildPath*** and run git checkout ddd4084 cd ../ + git clone -b v4.0.1-rc2 https://github.com/mozilla/mozjpeg.git + cd mozjpeg + cmake -B build . \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr/local \ + -DWITH_JPEG8=ON \ + -DPNG_SUPPORTED=OFF + cmake --build build $MAKE_THREADS_CNT + sudo cmake --install build + cd .. + git clone https://github.com/xiph/opus cd opus git checkout v1.3 @@ -221,7 +232,7 @@ Go to ***BuildPath*** and run --enable-static \ --disable-documentation \ --disable-dtd-validation - make -j$(nproc) + make $MAKE_THREADS_CNT sudo make install cd .. @@ -257,7 +268,6 @@ Go to ***BuildPath*** and run -confirm-license \ -qt-zlib \ -qt-libpng \ - -qt-libjpeg \ -qt-harfbuzz \ -qt-pcre \ -qt-xcb \ @@ -283,7 +293,7 @@ Go to ***BuildPath*** and run cmake -G Ninja \ -DCMAKE_BUILD_TYPE=Debug \ -DTG_OWT_SPECIAL_TARGET=linux \ - -DTG_OWT_LIBJPEG_INCLUDE_PATH=`pwd`/../../../qt_5_12_8/qtbase/src/3rdparty/libjpeg \ + -DTG_OWT_LIBJPEG_INCLUDE_PATH=/usr/local/include \ -DTG_OWT_OPENSSL_INCLUDE_PATH=/usr/local/desktop-app/openssl-1.1.1/include \ -DTG_OWT_OPUS_INCLUDE_PATH=/usr/local/include/opus \ -DTG_OWT_FFMPEG_INCLUDE_PATH=/usr/local/include ../.. diff --git a/docs/building-msvc.md b/docs/building-msvc.md index 1d6ee5de7..1bc58046c 100644 --- a/docs/building-msvc.md +++ b/docs/building-msvc.md @@ -100,6 +100,16 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** msbuild zlibstat.vcxproj /property:Configuration=ReleaseWithoutAsm cd ..\..\..\.. + git clone -b v4.0.1-rc2 https://github.com/mozilla/mozjpeg.git + cd mozjpeg + cmake . ^ + -G "Visual Studio 16 2019" ^ + -A Win32 ^ + -DWITH_JPEG8=ON ^ + -DPNG_SUPPORTED=OFF + cmake --build . + cd .. + git clone https://github.com/telegramdesktop/openal-soft.git cd openal-soft git checkout fix_mono @@ -161,7 +171,7 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** for /r %i in (..\..\patches\qtbase_5_12_8\*) do git apply %i cd .. - configure -prefix "%LibrariesPath%\Qt-5.12.8" -debug-and-release -force-debug-info -opensource -confirm-license -static -static-runtime -I "%LibrariesPath%\openssl_1_1_1\include" -no-opengl -openssl-linked OPENSSL_LIBS_DEBUG="%LibrariesPath%\openssl_1_1_1\out32.dbg\libssl.lib %LibrariesPath%\openssl_1_1_1\out32.dbg\libcrypto.lib Ws2_32.lib Gdi32.lib Advapi32.lib Crypt32.lib User32.lib" OPENSSL_LIBS_RELEASE="%LibrariesPath%\openssl_1_1_1\out32\libssl.lib %LibrariesPath%\openssl_1_1_1\out32\libcrypto.lib Ws2_32.lib Gdi32.lib Advapi32.lib Crypt32.lib User32.lib" -mp -nomake examples -nomake tests -platform win32-msvc + configure -prefix "%LibrariesPath%\Qt-5.12.8" -debug-and-release -force-debug-info -opensource -confirm-license -static -static-runtime -I "%LibrariesPath%\openssl_1_1_1\include" -no-opengl -openssl-linked OPENSSL_LIBS_DEBUG="%LibrariesPath%\openssl_1_1_1\out32.dbg\libssl.lib %LibrariesPath%\openssl_1_1_1\out32.dbg\libcrypto.lib Ws2_32.lib Gdi32.lib Advapi32.lib Crypt32.lib User32.lib" OPENSSL_LIBS_RELEASE="%LibrariesPath%\openssl_1_1_1\out32\libssl.lib %LibrariesPath%\openssl_1_1_1\out32\libcrypto.lib Ws2_32.lib Gdi32.lib Advapi32.lib Crypt32.lib User32.lib" -mp -nomake examples -nomake tests -platform win32-msvc -I "%LibrariesPath%\mozjpeg" LIBJPEG_LIBS_DEBUG="%LibrariesPath%\mozjpeg\Debug\jpeg-static.lib" LIBJPEG_LIBS_RELEASE="%LibrariesPath%\mozjpeg\Release\jpeg-static.lib" jom -j4 jom -j4 install @@ -176,7 +186,7 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** cmake -G Ninja ^ -DCMAKE_BUILD_TYPE=Debug ^ -DTG_OWT_SPECIAL_TARGET=win ^ - -DTG_OWT_LIBJPEG_INCLUDE_PATH=%cd%/../../../qt_5_12_8/qtbase/src/3rdparty/libjpeg ^ + -DTG_OWT_LIBJPEG_INCLUDE_PATH=%cd%/../../../mozjpeg ^ -DTG_OWT_OPENSSL_INCLUDE_PATH=%cd%/../../../openssl_1_1_1/include ^ -DTG_OWT_OPUS_INCLUDE_PATH=%cd%/../../../opus/include ^ -DTG_OWT_FFMPEG_INCLUDE_PATH=%cd%/../../../ffmpeg ../.. @@ -187,7 +197,7 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** cmake -G Ninja ^ -DCMAKE_BUILD_TYPE=Release ^ -DTG_OWT_SPECIAL_TARGET=win ^ - -DTG_OWT_LIBJPEG_INCLUDE_PATH=%cd%/../../../qt_5_12_8/qtbase/src/3rdparty/libjpeg ^ + -DTG_OWT_LIBJPEG_INCLUDE_PATH=%cd%/../../../mozjpeg ^ -DTG_OWT_OPENSSL_INCLUDE_PATH=%cd%/../../../openssl_1_1_1/include ^ -DTG_OWT_OPUS_INCLUDE_PATH=%cd%/../../../opus/include ^ -DTG_OWT_FFMPEG_INCLUDE_PATH=%cd%/../../../ffmpeg ../.. diff --git a/docs/building-osx.md b/docs/building-osx.md index 25300b451..e2b263dfc 100644 --- a/docs/building-osx.md +++ b/docs/building-osx.md @@ -68,6 +68,17 @@ Go to ***BuildPath*** and run sudo make install cd .. + git clone -b v4.0.1-rc2 https://github.com/mozilla/mozjpeg.git + cd mozjpeg + cmake -B build . \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr/local \ + -DWITH_JPEG8=ON \ + -DPNG_SUPPORTED=OFF + cmake --build build $MAKE_THREADS_CNT + sudo cmake --install build + cd .. + git clone https://github.com/openssl/openssl openssl_1_1_1 cd openssl_1_1_1 git checkout OpenSSL_1_1_1-stable @@ -240,7 +251,7 @@ Go to ***BuildPath*** and run git apply ../../patches/qtbase_5_6_2.diff cd .. - ./configure -prefix "/usr/local/desktop-app/Qt-5.6.2" -debug-and-release -force-debug-info -opensource -confirm-license -static -opengl desktop -no-openssl -securetransport -nomake examples -nomake tests -platform macx-clang + ./configure -prefix "/usr/local/desktop-app/Qt-5.6.2" -debug-and-release -force-debug-info -opensource -confirm-license -static -opengl desktop -no-openssl -securetransport -nomake examples -nomake tests -platform macx-clang -I "/usr/local/include" LIBJPEG_LIBS="/usr/local/lib/libjpeg.a" ZLIB_LIBS="/usr/local/lib/libz.a" make $MAKE_THREADS_CNT sudo make install cd .. @@ -254,7 +265,7 @@ Go to ***BuildPath*** and run cmake -G Ninja \ -DCMAKE_BUILD_TYPE=Debug \ -DTG_OWT_SPECIAL_TARGET=osx \ - -DTG_OWT_LIBJPEG_INCLUDE_PATH=`pwd`/../../../qt5_6_2/qtbase/src/3rdparty/libjpeg \ + -DTG_OWT_LIBJPEG_INCLUDE_PATH=/usr/local/include \ -DTG_OWT_OPENSSL_INCLUDE_PATH=`pwd`/../../../openssl_1_1_1/include \ -DTG_OWT_OPUS_INCLUDE_PATH=/usr/local/include/opus \ -DTG_OWT_FFMPEG_INCLUDE_PATH=`pwd`/../../../ffmpeg ../.. @@ -265,7 +276,7 @@ Go to ***BuildPath*** and run cmake -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DTG_OWT_SPECIAL_TARGET=osx \ - -DTG_OWT_LIBJPEG_INCLUDE_PATH=`pwd`/../../../qt5_6_2/qtbase/src/3rdparty/libjpeg \ + -DTG_OWT_LIBJPEG_INCLUDE_PATH=/usr/local/include \ -DTG_OWT_OPENSSL_INCLUDE_PATH=`pwd`/../../../openssl_1_1_1/include \ -DTG_OWT_OPUS_INCLUDE_PATH=/usr/local/include/opus \ -DTG_OWT_FFMPEG_INCLUDE_PATH=`pwd`/../../../ffmpeg ../.. diff --git a/docs/building-xcode.md b/docs/building-xcode.md index f286cf047..1f47962af 100644 --- a/docs/building-xcode.md +++ b/docs/building-xcode.md @@ -75,6 +75,17 @@ Go to ***BuildPath*** and run sudo make install cd .. + git clone -b v4.0.1-rc2 https://github.com/mozilla/mozjpeg.git + cd mozjpeg + cmake -B build . \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr/local/macos \ + -DWITH_JPEG8=ON \ + -DPNG_SUPPORTED=OFF + cmake --build build $MAKE_THREADS_CNT + sudo cmake --install build + cd .. + git clone https://github.com/openssl/openssl openssl_1_1_1 cd openssl_1_1_1 git checkout OpenSSL_1_1_1-stable @@ -262,7 +273,10 @@ Go to ***BuildPath*** and run -securetransport \ -nomake examples \ -nomake tests \ - -platform macx-clang + -platform macx-clang \ + -I "/usr/local/macos/include" \ + LIBJPEG_LIBS="/usr/local/macos/lib/libjpeg.a" \ + ZLIB_LIBS="/usr/local/macos/lib/libz.a" make $MAKE_THREADS_CNT sudo make install @@ -277,7 +291,7 @@ Go to ***BuildPath*** and run cmake -G Ninja \ -DCMAKE_BUILD_TYPE=Debug \ -DTG_OWT_SPECIAL_TARGET=mac \ - -DTG_OWT_LIBJPEG_INCLUDE_PATH=`pwd`/../../../qt_5_12_8/qtbase/src/3rdparty/libjpeg \ + -DTG_OWT_LIBJPEG_INCLUDE_PATH=/usr/local/macos/include \ -DTG_OWT_OPENSSL_INCLUDE_PATH=`pwd`/../../../openssl_1_1_1/include \ -DTG_OWT_OPUS_INCLUDE_PATH=/usr/local/macos/include/opus \ -DTG_OWT_FFMPEG_INCLUDE_PATH=/usr/local/macos/include ../.. @@ -288,7 +302,7 @@ Go to ***BuildPath*** and run cmake -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DTG_OWT_SPECIAL_TARGET=mac \ - -DTG_OWT_LIBJPEG_INCLUDE_PATH=`pwd`/../../../qt_5_12_8/qtbase/src/3rdparty/libjpeg \ + -DTG_OWT_LIBJPEG_INCLUDE_PATH=/usr/local/macos/include \ -DTG_OWT_OPENSSL_INCLUDE_PATH=`pwd`/../../../openssl_1_1_1/include \ -DTG_OWT_OPUS_INCLUDE_PATH=/usr/local/macos/include/opus \ -DTG_OWT_FFMPEG_INCLUDE_PATH=/usr/local/macos/include ../.. diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 09dc34469..63ffae026 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -123,9 +123,12 @@ parts: snapcraftctl set-version "$version" sed -i 's|^Icon=.*|Icon=/usr/share/icons/hicolor/512x512/apps/telegram.png|g' lib/xdg/telegramdesktop.desktop + stage: + - -./usr/lib/$SNAPCRAFT_ARCH_TRIPLET/libjpeg.so.8.2.2 after: - desktop-qt5 - ffmpeg + - mozjpeg - openal - qtwayland - webrtc @@ -154,17 +157,29 @@ parts: - locales-all - xdg-user-dirs - fcitx-frontend-qt5 + stage: + - -./usr/lib/$SNAPCRAFT_ARCH_TRIPLET/libjpeg.so.8.2.2 + after: + - mozjpeg qt5-xdgdesktopportal-platform: plugin: nil stage-packages: - qt5-xdgdesktopportal-platformtheme + stage: + - -./usr/lib/$SNAPCRAFT_ARCH_TRIPLET/libjpeg.so.8.2.2 + after: + - mozjpeg # Qt checks that ibus-daemon binary is present, otherwise doesn't work ibus: plugin: nil stage-packages: - ibus + stage: + - -./usr/lib/$SNAPCRAFT_ARCH_TRIPLET/libjpeg.so.8.2.2 + after: + - mozjpeg ffmpeg: plugin: nil @@ -180,6 +195,27 @@ parts: - libavutil56 - libswresample3 - libswscale5 + stage: + - -./usr/lib/$SNAPCRAFT_ARCH_TRIPLET/libjpeg.so.8.2.2 + after: + - mozjpeg + + mozjpeg: + source: https://github.com/mozilla/mozjpeg.git + source-depth: 1 + source-tag: v4.0.1-rc2 + plugin: cmake + cmake-parameters: + - -DCMAKE_BUILD_TYPE=Release + - -DCMAKE_INSTALL_PREFIX=/usr + - -DENABLE_STATIC=OFF + - -DWITH_JPEG8=ON + - -DPNG_SUPPORTED=OFF + prime: + - -./usr/bin + - -./usr/include + - -./usr/lib/pkgconfig + - -./usr/share openal: source: https://github.com/kcat/openal-soft.git @@ -228,15 +264,17 @@ parts: plugin: cmake build-packages: - yasm - - libjpeg8-dev - libopus-dev - libssl-dev stage-packages: - - libjpeg8 - libopus0 - libssl1.1 override-build: | - cmake "$SNAPCRAFT_PART_SRC" -DCMAKE_BUILD_TYPE=Release + cmake "$SNAPCRAFT_PART_SRC" \ + -DCMAKE_BUILD_TYPE=Release \ + -DJPEG_LIBRARY_RELEASE="$SNAPCRAFT_STAGE/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/libjpeg.so" \ + -DJPEG_INCLUDE_DIR="$SNAPCRAFT_STAGE/usr/include" + cmake --build . -- -j$(nproc) cp -a . "$SNAPCRAFT_PART_INSTALL" organize: @@ -244,3 +282,4 @@ parts: prime: [-./*] after: - ffmpeg + - mozjpeg From 94d37509c136006f067e99bbd72204cb6d217f6e Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 9 Oct 2020 10:10:20 +0400 Subject: [PATCH 065/190] Add $SNAPCRAFT_ARCH_TRIPLET to paths --- snap/snapcraft.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 63ffae026..c1c4ff59f 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -68,7 +68,6 @@ parts: source-type: git parse-info: [usr/share/metainfo/telegram-desktop_telegram-desktop.appdata.xml] build-environment: - - LD_LIBRARY_PATH: $SNAPCRAFT_STAGE/usr/lib - tg_owt_DIR: $SNAPCRAFT_STAGE/tg_owt build-packages: - python @@ -214,7 +213,7 @@ parts: prime: - -./usr/bin - -./usr/include - - -./usr/lib/pkgconfig + - -./usr/lib/$SNAPCRAFT_ARCH_TRIPLET/pkgconfig - -./usr/share openal: @@ -241,8 +240,8 @@ parts: - -DALSOFT_CONFIG=OFF prime: - -./usr/include - - -./usr/lib/cmake - - -./usr/lib/pkgconfig + - -./usr/lib/$SNAPCRAFT_ARCH_TRIPLET/cmake + - -./usr/lib/$SNAPCRAFT_ARCH_TRIPLET/pkgconfig qtwayland: source: https://github.com/qt/qtwayland.git From 98afc99a8feba09d9d6c3fd4a15d89a8c0a8644e Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 30 Oct 2020 13:17:02 +0300 Subject: [PATCH 066/190] Update submodules and instructions. --- Telegram/ThirdParty/range-v3 | 2 +- Telegram/ThirdParty/rlottie | 2 +- Telegram/lib_ui | 2 +- cmake | 2 +- docs/building-cmake.md | 59 ++++++++++++++++++----------------- docs/building-msvc.md | 49 ++++++++++++++++++++--------- docs/building-osx.md | 2 +- docs/building-xcode.md | 60 ++++++++++++++++++------------------ 8 files changed, 99 insertions(+), 79 deletions(-) diff --git a/Telegram/ThirdParty/range-v3 b/Telegram/ThirdParty/range-v3 index 2d606af5c..413c8f9aa 160000 --- a/Telegram/ThirdParty/range-v3 +++ b/Telegram/ThirdParty/range-v3 @@ -1 +1 @@ -Subproject commit 2d606af5c71a47e91e095db20d8ba2d84a1ca113 +Subproject commit 413c8f9aacb269f6440fe180b587fd70c7ba16df diff --git a/Telegram/ThirdParty/rlottie b/Telegram/ThirdParty/rlottie index 839dcab7f..3426bf778 160000 --- a/Telegram/ThirdParty/rlottie +++ b/Telegram/ThirdParty/rlottie @@ -1 +1 @@ -Subproject commit 839dcab7f083a51b8130061ea5ec245195af6c58 +Subproject commit 3426bf778d306d41b1d1052fa06fa07ba24b2608 diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 914df12eb..109375453 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 914df12ebe3231dbdd5150d5b33cfab2e7dabe17 +Subproject commit 109375453e1465daff0970a920ba65e9fe8c78f0 diff --git a/cmake b/cmake index cfc6051fb..a7e73ebc0 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit cfc6051fb65da4c67ccbc2a1d9e753758f995fe3 +Subproject commit a7e73ebc036fdf32cdca56b62405bf9dcd8f8f09 diff --git a/docs/building-cmake.md b/docs/building-cmake.md index 11e722095..27022d9a9 100644 --- a/docs/building-cmake.md +++ b/docs/building-cmake.md @@ -262,23 +262,24 @@ Go to ***BuildPath*** and run OPENSSL_DIR=/usr/local/desktop-app/openssl-1.1.1 ./configure -prefix "/usr/local/desktop-app/Qt-5.12.8" \ - -release \ - -force-debug-info \ - -opensource \ - -confirm-license \ - -qt-zlib \ - -qt-libpng \ - -qt-harfbuzz \ - -qt-pcre \ - -qt-xcb \ - -no-gtk \ - -no-icu \ - -static \ - -dbus-runtime \ - -openssl-linked \ - -I "$OPENSSL_DIR/include" OPENSSL_LIBS="$OPENSSL_DIR/lib/libssl.a $OPENSSL_DIR/lib/libcrypto.a -ldl -lpthread" \ - -nomake examples \ - -nomake tests + -release \ + -force-debug-info \ + -opensource \ + -confirm-license \ + -qt-zlib \ + -qt-libpng \ + -qt-harfbuzz \ + -qt-pcre \ + -qt-xcb \ + -no-gtk \ + -no-icu \ + -static \ + -dbus-runtime \ + -openssl-linked \ + -I "$OPENSSL_DIR/include" \ + OPENSSL_LIBS="$OPENSSL_DIR/lib/libssl.a $OPENSSL_DIR/lib/libcrypto.a -ldl -lpthread" \ + -nomake examples \ + -nomake tests make $MAKE_THREADS_CNT sudo make install @@ -291,23 +292,23 @@ Go to ***BuildPath*** and run mkdir Debug cd Debug cmake -G Ninja \ - -DCMAKE_BUILD_TYPE=Debug \ - -DTG_OWT_SPECIAL_TARGET=linux \ - -DTG_OWT_LIBJPEG_INCLUDE_PATH=/usr/local/include \ - -DTG_OWT_OPENSSL_INCLUDE_PATH=/usr/local/desktop-app/openssl-1.1.1/include \ - -DTG_OWT_OPUS_INCLUDE_PATH=/usr/local/include/opus \ - -DTG_OWT_FFMPEG_INCLUDE_PATH=/usr/local/include ../.. + -DCMAKE_BUILD_TYPE=Debug \ + -DTG_OWT_SPECIAL_TARGET=linux \ + -DTG_OWT_LIBJPEG_INCLUDE_PATH=/usr/local/include \ + -DTG_OWT_OPENSSL_INCLUDE_PATH=/usr/local/desktop-app/openssl-1.1.1/include \ + -DTG_OWT_OPUS_INCLUDE_PATH=/usr/local/include/opus \ + -DTG_OWT_FFMPEG_INCLUDE_PATH=/usr/local/include ../.. ninja cd .. mkdir Release cd Release cmake -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DTG_OWT_SPECIAL_TARGET=linux \ - -DTG_OWT_LIBJPEG_INCLUDE_PATH=`pwd`/../../../qt_5_12_8/qtbase/src/3rdparty/libjpeg \ - -DTG_OWT_OPENSSL_INCLUDE_PATH=/usr/local/desktop-app/openssl-1.1.1/include \ - -DTG_OWT_OPUS_INCLUDE_PATH=/usr/local/include/opus \ - -DTG_OWT_FFMPEG_INCLUDE_PATH=/usr/local/include ../.. + -DCMAKE_BUILD_TYPE=Release \ + -DTG_OWT_SPECIAL_TARGET=linux \ + -DTG_OWT_LIBJPEG_INCLUDE_PATH=`pwd`/../../../qt_5_12_8/qtbase/src/3rdparty/libjpeg \ + -DTG_OWT_OPENSSL_INCLUDE_PATH=/usr/local/desktop-app/openssl-1.1.1/include \ + -DTG_OWT_OPUS_INCLUDE_PATH=/usr/local/include/opus \ + -DTG_OWT_FFMPEG_INCLUDE_PATH=/usr/local/include ../.. ninja cd ../../.. diff --git a/docs/building-msvc.md b/docs/building-msvc.md index 1bc58046c..798cade8f 100644 --- a/docs/building-msvc.md +++ b/docs/building-msvc.md @@ -61,8 +61,6 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** mkdir Libraries cd Libraries - SET LibrariesPath=%cd% - git clone https://github.com/desktop-app/patches.git cd patches git checkout ddd4084 @@ -162,6 +160,7 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** SET PATH=%PATH_BACKUP_% cd .. + SET LibrariesPath=%cd% git clone git://code.qt.io/qt/qt5.git qt_5_12_8 cd qt_5_12_8 perl init-repository --module-subset=qtbase,qtimageformats @@ -171,7 +170,27 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** for /r %i in (..\..\patches\qtbase_5_12_8\*) do git apply %i cd .. - configure -prefix "%LibrariesPath%\Qt-5.12.8" -debug-and-release -force-debug-info -opensource -confirm-license -static -static-runtime -I "%LibrariesPath%\openssl_1_1_1\include" -no-opengl -openssl-linked OPENSSL_LIBS_DEBUG="%LibrariesPath%\openssl_1_1_1\out32.dbg\libssl.lib %LibrariesPath%\openssl_1_1_1\out32.dbg\libcrypto.lib Ws2_32.lib Gdi32.lib Advapi32.lib Crypt32.lib User32.lib" OPENSSL_LIBS_RELEASE="%LibrariesPath%\openssl_1_1_1\out32\libssl.lib %LibrariesPath%\openssl_1_1_1\out32\libcrypto.lib Ws2_32.lib Gdi32.lib Advapi32.lib Crypt32.lib User32.lib" -mp -nomake examples -nomake tests -platform win32-msvc -I "%LibrariesPath%\mozjpeg" LIBJPEG_LIBS_DEBUG="%LibrariesPath%\mozjpeg\Debug\jpeg-static.lib" LIBJPEG_LIBS_RELEASE="%LibrariesPath%\mozjpeg\Release\jpeg-static.lib" + configure ^ + -prefix "%LibrariesPath%\Qt-5.12.8" ^ + -debug-and-release ^ + -force-debug-info ^ + -opensource ^ + -confirm-license ^ + -static ^ + -static-runtime ^ + -no-opengl ^ + -openssl-linked ^ + -recheck ^ + -I "%LibrariesPath%\openssl_1_1_1\include" ^ + OPENSSL_LIBS_DEBUG="%LibrariesPath%\openssl_1_1_1\out32.dbg\libssl.lib %LibrariesPath%\openssl_1_1_1\out32.dbg\libcrypto.lib Ws2_32.lib Gdi32.lib Advapi32.lib Crypt32.lib User32.lib" ^ + OPENSSL_LIBS_RELEASE="%LibrariesPath%\openssl_1_1_1\out32\libssl.lib %LibrariesPath%\openssl_1_1_1\out32\libcrypto.lib Ws2_32.lib Gdi32.lib Advapi32.lib Crypt32.lib User32.lib" ^ + -I "%LibrariesPath%\mozjpeg" ^ + LIBJPEG_LIBS_DEBUG="%LibrariesPath%\mozjpeg\Debug\jpeg-static.lib" ^ + LIBJPEG_LIBS_RELEASE="%LibrariesPath%\mozjpeg\Release\jpeg-static.lib" ^ + -mp ^ + -nomake examples ^ + -nomake tests ^ + -platform win32-msvc jom -j4 jom -j4 install @@ -184,23 +203,23 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** mkdir Debug cd Debug cmake -G Ninja ^ - -DCMAKE_BUILD_TYPE=Debug ^ - -DTG_OWT_SPECIAL_TARGET=win ^ - -DTG_OWT_LIBJPEG_INCLUDE_PATH=%cd%/../../../mozjpeg ^ - -DTG_OWT_OPENSSL_INCLUDE_PATH=%cd%/../../../openssl_1_1_1/include ^ - -DTG_OWT_OPUS_INCLUDE_PATH=%cd%/../../../opus/include ^ - -DTG_OWT_FFMPEG_INCLUDE_PATH=%cd%/../../../ffmpeg ../.. + -DCMAKE_BUILD_TYPE=Debug ^ + -DTG_OWT_SPECIAL_TARGET=win ^ + -DTG_OWT_LIBJPEG_INCLUDE_PATH=%cd%/../../../mozjpeg ^ + -DTG_OWT_OPENSSL_INCLUDE_PATH=%cd%/../../../openssl_1_1_1/include ^ + -DTG_OWT_OPUS_INCLUDE_PATH=%cd%/../../../opus/include ^ + -DTG_OWT_FFMPEG_INCLUDE_PATH=%cd%/../../../ffmpeg ../.. ninja cd .. mkdir Release cd Release cmake -G Ninja ^ - -DCMAKE_BUILD_TYPE=Release ^ - -DTG_OWT_SPECIAL_TARGET=win ^ - -DTG_OWT_LIBJPEG_INCLUDE_PATH=%cd%/../../../mozjpeg ^ - -DTG_OWT_OPENSSL_INCLUDE_PATH=%cd%/../../../openssl_1_1_1/include ^ - -DTG_OWT_OPUS_INCLUDE_PATH=%cd%/../../../opus/include ^ - -DTG_OWT_FFMPEG_INCLUDE_PATH=%cd%/../../../ffmpeg ../.. + -DCMAKE_BUILD_TYPE=Release ^ + -DTG_OWT_SPECIAL_TARGET=win ^ + -DTG_OWT_LIBJPEG_INCLUDE_PATH=%cd%/../../../mozjpeg ^ + -DTG_OWT_OPENSSL_INCLUDE_PATH=%cd%/../../../openssl_1_1_1/include ^ + -DTG_OWT_OPUS_INCLUDE_PATH=%cd%/../../../opus/include ^ + -DTG_OWT_FFMPEG_INCLUDE_PATH=%cd%/../../../ffmpeg ../.. ninja cd ..\..\.. diff --git a/docs/building-osx.md b/docs/building-osx.md index e2b263dfc..6537f6940 100644 --- a/docs/building-osx.md +++ b/docs/building-osx.md @@ -1,6 +1,6 @@ ## Build instructions for Xcode 10.1 -**NB** These are used for OS X 10.10/10.11 build, after the [Building using Xcode][xcode] instructions. +**NB** These are outdated, please refer to [Building using Xcode][xcode] instructions. ### Prepare folder diff --git a/docs/building-xcode.md b/docs/building-xcode.md index 1f47962af..dae91359e 100644 --- a/docs/building-xcode.md +++ b/docs/building-xcode.md @@ -78,10 +78,10 @@ Go to ***BuildPath*** and run git clone -b v4.0.1-rc2 https://github.com/mozilla/mozjpeg.git cd mozjpeg cmake -B build . \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX=/usr/local/macos \ - -DWITH_JPEG8=ON \ - -DPNG_SUPPORTED=OFF + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr/local/macos \ + -DWITH_JPEG8=ON \ + -DPNG_SUPPORTED=OFF cmake --build build $MAKE_THREADS_CNT sudo cmake --install build cd .. @@ -263,20 +263,20 @@ Go to ***BuildPath*** and run cd .. ./configure -prefix "/usr/local/desktop-app/Qt-5.12.8" \ - -debug-and-release \ - -force-debug-info \ - -opensource \ - -confirm-license \ - -static \ - -opengl desktop \ - -no-openssl \ - -securetransport \ - -nomake examples \ - -nomake tests \ - -platform macx-clang \ - -I "/usr/local/macos/include" \ - LIBJPEG_LIBS="/usr/local/macos/lib/libjpeg.a" \ - ZLIB_LIBS="/usr/local/macos/lib/libz.a" + -debug-and-release \ + -force-debug-info \ + -opensource \ + -confirm-license \ + -static \ + -opengl desktop \ + -no-openssl \ + -securetransport \ + -I "/usr/local/macos/include" \ + LIBJPEG_LIBS="/usr/local/macos/lib/libjpeg.a" \ + ZLIB_LIBS="/usr/local/macos/lib/libz.a" \ + -nomake examples \ + -nomake tests \ + -platform macx-clang make $MAKE_THREADS_CNT sudo make install @@ -289,23 +289,23 @@ Go to ***BuildPath*** and run mkdir Debug cd Debug cmake -G Ninja \ - -DCMAKE_BUILD_TYPE=Debug \ - -DTG_OWT_SPECIAL_TARGET=mac \ - -DTG_OWT_LIBJPEG_INCLUDE_PATH=/usr/local/macos/include \ - -DTG_OWT_OPENSSL_INCLUDE_PATH=`pwd`/../../../openssl_1_1_1/include \ - -DTG_OWT_OPUS_INCLUDE_PATH=/usr/local/macos/include/opus \ - -DTG_OWT_FFMPEG_INCLUDE_PATH=/usr/local/macos/include ../.. + -DCMAKE_BUILD_TYPE=Debug \ + -DTG_OWT_SPECIAL_TARGET=mac \ + -DTG_OWT_LIBJPEG_INCLUDE_PATH=/usr/local/macos/include \ + -DTG_OWT_OPENSSL_INCLUDE_PATH=`pwd`/../../../openssl_1_1_1/include \ + -DTG_OWT_OPUS_INCLUDE_PATH=/usr/local/macos/include/opus \ + -DTG_OWT_FFMPEG_INCLUDE_PATH=/usr/local/macos/include ../.. ninja cd .. mkdir Release cd Release cmake -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DTG_OWT_SPECIAL_TARGET=mac \ - -DTG_OWT_LIBJPEG_INCLUDE_PATH=/usr/local/macos/include \ - -DTG_OWT_OPENSSL_INCLUDE_PATH=`pwd`/../../../openssl_1_1_1/include \ - -DTG_OWT_OPUS_INCLUDE_PATH=/usr/local/macos/include/opus \ - -DTG_OWT_FFMPEG_INCLUDE_PATH=/usr/local/macos/include ../.. + -DCMAKE_BUILD_TYPE=Release \ + -DTG_OWT_SPECIAL_TARGET=mac \ + -DTG_OWT_LIBJPEG_INCLUDE_PATH=/usr/local/macos/include \ + -DTG_OWT_OPENSSL_INCLUDE_PATH=`pwd`/../../../openssl_1_1_1/include \ + -DTG_OWT_OPUS_INCLUDE_PATH=/usr/local/macos/include/opus \ + -DTG_OWT_FFMPEG_INCLUDE_PATH=/usr/local/macos/include ../.. ninja cd ../../.. From 1459e6f38e17947ef943c4c2df6418bb4016e520 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 30 Oct 2020 02:43:35 +0300 Subject: [PATCH 067/190] Moved request for attached stickers to separate file. --- Telegram/CMakeLists.txt | 2 + .../SourceFiles/api/api_attached_stickers.cpp | 64 +++++++++++++++++++ .../SourceFiles/api/api_attached_stickers.h | 35 ++++++++++ Telegram/SourceFiles/apiwrap.cpp | 6 ++ Telegram/SourceFiles/apiwrap.h | 3 + .../history/history_inner_widget.cpp | 5 +- .../media/view/media_view_overlay_widget.cpp | 8 ++- .../window/window_session_controller.cpp | 40 +----------- .../window/window_session_controller.h | 7 +- 9 files changed, 122 insertions(+), 48 deletions(-) create mode 100644 Telegram/SourceFiles/api/api_attached_stickers.cpp create mode 100644 Telegram/SourceFiles/api/api_attached_stickers.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 19e767410..51d5689ac 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -157,6 +157,8 @@ nice_target_sources(Telegram ${src_loc} PRIVATE ${style_files} + api/api_attached_stickers.cpp + api/api_attached_stickers.h api/api_authorizations.cpp api/api_authorizations.h api/api_bot.cpp diff --git a/Telegram/SourceFiles/api/api_attached_stickers.cpp b/Telegram/SourceFiles/api/api_attached_stickers.cpp new file mode 100644 index 000000000..fcdfe7721 --- /dev/null +++ b/Telegram/SourceFiles/api/api_attached_stickers.cpp @@ -0,0 +1,64 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "api/api_attached_stickers.h" + +#include "apiwrap.h" +#include "boxes/confirm_box.h" +#include "boxes/sticker_set_box.h" +#include "boxes/stickers_box.h" +#include "data/data_photo.h" +#include "lang/lang_keys.h" +#include "window/window_session_controller.h" + +namespace Api { + +AttachedStickers::AttachedStickers(not_null api) +: _api(&api->instance()) { +} + +void AttachedStickers::requestAttachedStickerSets( + not_null controller, + not_null photo) { + const auto weak = base::make_weak(controller.get()); + _api.request(_requestId).cancel(); + _requestId = _api.request( + MTPmessages_GetAttachedStickers( + MTP_inputStickeredMediaPhoto(photo->mtpInput()) + )).done([=](const MTPVector &result) { + _requestId = 0; + const auto strongController = weak.get(); + if (!strongController) { + return; + } + if (result.v.isEmpty()) { + Ui::show(Box(tr::lng_stickers_not_found(tr::now))); + return; + } else if (result.v.size() > 1) { + Ui::show(Box(strongController, result)); + return; + } + // Single attached sticker pack. + const auto setData = result.v.front().match([&](const auto &data) { + return data.vset().match([&](const MTPDstickerSet &data) { + return &data; + }); + }); + + const auto setId = (setData->vid().v && setData->vaccess_hash().v) + ? MTP_inputStickerSetID(setData->vid(), setData->vaccess_hash()) + : MTP_inputStickerSetShortName(setData->vshort_name()); + Ui::show( + Box(strongController, setId), + Ui::LayerOption::KeepOther); + }).fail([=](const RPCError &error) { + _requestId = 0; + Ui::show(Box(tr::lng_stickers_not_found(tr::now))); + }).send(); +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_attached_stickers.h b/Telegram/SourceFiles/api/api_attached_stickers.h new file mode 100644 index 000000000..bf186fef0 --- /dev/null +++ b/Telegram/SourceFiles/api/api_attached_stickers.h @@ -0,0 +1,35 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "mtproto/sender.h" + +class ApiWrap; +class PhotoData; + +namespace Window { +class SessionController; +} // namespace Window + +namespace Api { + +class AttachedStickers final { +public: + explicit AttachedStickers(not_null api); + + void requestAttachedStickerSets( + not_null controller, + not_null photo); + +private: + MTP::Sender _api; + mtpRequestId _requestId = 0; + +}; + +} // namespace Api diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 92805db3d..a082d9b88 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "api/api_authorizations.h" +#include "api/api_attached_stickers.h" #include "api/api_hash.h" #include "api/api_media.h" #include "api/api_sending.h" @@ -188,6 +189,7 @@ ApiWrap::ApiWrap(not_null session) , _topPromotionTimer([=] { refreshTopPromotion(); }) , _updateNotifySettingsTimer([=] { sendNotifySettingsUpdates(); }) , _authorizations(std::make_unique(this)) +, _attachedStickers(std::make_unique(this)) , _selfDestruct(std::make_unique(this)) , _sensitiveContent(std::make_unique(this)) , _globalPrivacy(std::make_unique(this)) { @@ -5227,6 +5229,10 @@ Api::Authorizations &ApiWrap::authorizations() { return *_authorizations; } +Api::AttachedStickers &ApiWrap::attachedStickers() { + return *_attachedStickers; +} + Api::SelfDestruct &ApiWrap::selfDestruct() { return *_selfDestruct; } diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 3284a10cf..bf8140496 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -53,6 +53,7 @@ namespace Api { class Updates; class Authorizations; +class AttachedStickers; class SelfDestruct; class SensitiveContent; class GlobalPrivacy; @@ -455,6 +456,7 @@ public: rpl::producer blockedPeersSlice(); [[nodiscard]] Api::Authorizations &authorizations(); + [[nodiscard]] Api::AttachedStickers &attachedStickers(); [[nodiscard]] Api::SelfDestruct &selfDestruct(); [[nodiscard]] Api::SensitiveContent &sensitiveContent(); [[nodiscard]] Api::GlobalPrivacy &globalPrivacy(); @@ -818,6 +820,7 @@ private: rpl::event_stream _blockedPeersChanges; const std::unique_ptr _authorizations; + const std::unique_ptr _attachedStickers; const std::unique_ptr _selfDestruct; const std::unique_ptr _sensitiveContent; const std::unique_ptr _globalPrivacy; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 8f0b91a55..aea01efe8 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -45,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session_settings.h" #include "core/application.h" #include "apiwrap.h" +#include "api/api_attached_stickers.h" #include "api/api_toggling_media.h" #include "lang/lang_keys.h" #include "data/data_session.h" @@ -1590,7 +1591,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { }); if (photo->hasSticker) { _menu->addAction(tr::lng_context_attached_stickers(tr::now), [=] { - controller->requestAttachedStickerSets(photo); + session->api().attachedStickers().requestAttachedStickerSets( + controller, + photo); }); } }; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 38e7062e0..7d9840f1b 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/view/media_view_overlay_widget.h" #include "apiwrap.h" +#include "api/api_attached_stickers.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "mainwindow.h" @@ -1545,15 +1546,16 @@ void OverlayWidget::onCopy() { } void OverlayWidget::onAttachedStickers() { - const auto session = _session; - if (!session || !_photo) { + if (!_session || !_photo) { return; } const auto &active = _session->windows(); if (active.empty()) { return; } - active.front()->requestAttachedStickerSets(_photo); + _session->api().attachedStickers().requestAttachedStickerSets( + active.front(), + _photo); close(); } diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 4f8ebf3a3..7828489c0 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -28,7 +28,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_changes.h" #include "data/data_chat_filters.h" -#include "data/data_photo.h" // requestAttachedStickerSets. #include "passport/passport_form_controller.h" #include "chat_helpers/tabbed_selector.h" #include "core/shortcuts.h" @@ -40,10 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/delayed_activation.h" #include "ui/toast/toast.h" #include "boxes/calendar_box.h" -#include "boxes/sticker_set_box.h" // requestAttachedStickerSets. -#include "boxes/confirm_box.h" // requestAttachedStickerSets. -#include "boxes/stickers_box.h" // requestAttachedStickerSets. -#include "lang/lang_keys.h" // requestAttachedStickerSets. +#include "boxes/confirm_box.h" #include "mainwidget.h" #include "mainwindow.h" #include "main/main_session.h" @@ -545,36 +541,6 @@ rpl::producer<> SessionController::filtersMenuChanged() const { return _filtersMenuChanged.events(); } -void SessionController::requestAttachedStickerSets( - not_null photo) { - session().api().request(_attachedStickerSetsRequestId).cancel(); - _attachedStickerSetsRequestId = session().api().request( - MTPmessages_GetAttachedStickers( - MTP_inputStickeredMediaPhoto(photo->mtpInput()) - )).done([=](const MTPVector &result) { - if (result.v.isEmpty()) { - Ui::show(Box(tr::lng_stickers_not_found(tr::now))); - return; - } else if (result.v.size() > 1) { - Ui::show(Box(this, result)); - return; - } - // Single attached sticker pack. - const auto setData = result.v.front().match([&](const auto &data) { - return data.vset().match([&](const MTPDstickerSet &data) { - return &data; - }); - }); - - const auto setId = (setData->vid().v && setData->vaccess_hash().v) - ? MTP_inputStickerSetID(setData->vid(), setData->vaccess_hash()) - : MTP_inputStickerSetShortName(setData->vshort_name()); - Ui::show(Box(this, setId), Ui::LayerOption::KeepOther); - }).fail([=](const RPCError &error) { - Ui::show(Box(tr::lng_stickers_not_found(tr::now))); - }).send(); -} - void SessionController::checkOpenedFilter() { if (const auto filterId = activeChatsFilterCurrent()) { const auto &list = session().data().chatsFilters().list(); @@ -1132,8 +1098,6 @@ void SessionController::setActiveChatsFilter(FilterId id) { } } -SessionController::~SessionController() { - session().api().request(_attachedStickerSetsRequestId).cancel(); -} +SessionController::~SessionController() = default; } // namespace Window diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 980b7d2e7..2feaa18ec 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -230,6 +230,7 @@ public: SessionController( not_null session, not_null window); + ~SessionController(); [[nodiscard]] Controller &window() const { return *_window; @@ -348,14 +349,10 @@ public: void toggleFiltersMenu(bool enabled); [[nodiscard]] rpl::producer<> filtersMenuChanged() const; - void requestAttachedStickerSets(not_null photo); - rpl::lifetime &lifetime() { return _lifetime; } - ~SessionController(); - private: void init(); void initSupportMode(); @@ -408,8 +405,6 @@ private: rpl::event_stream<> _filtersMenuChanged; - mtpRequestId _attachedStickerSetsRequestId = 0; - rpl::lifetime _lifetime; }; From 391ec8ac288036e73ae52f4b87077a821db5c4a1 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 30 Oct 2020 03:07:59 +0300 Subject: [PATCH 068/190] Moved public var indicating stickers presence to private in PhotoData. --- Telegram/SourceFiles/data/data_photo.cpp | 8 ++++++++ Telegram/SourceFiles/data/data_photo.h | 5 ++++- Telegram/SourceFiles/data/data_session.cpp | 8 ++++---- Telegram/SourceFiles/data/data_session.h | 4 ++-- Telegram/SourceFiles/history/history_inner_widget.cpp | 2 +- .../SourceFiles/media/view/media_view_overlay_widget.cpp | 2 +- 6 files changed, 20 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/data/data_photo.cpp b/Telegram/SourceFiles/data/data_photo.cpp index 254417ed2..d5ede173c 100644 --- a/Telegram/SourceFiles/data/data_photo.cpp +++ b/Telegram/SourceFiles/data/data_photo.cpp @@ -376,6 +376,14 @@ void PhotoData::updateImages( [&](Data::FileOrigin origin) { loadVideo(origin); }); } +[[nodiscard]] bool PhotoData::hasAttachedStickers() const { + return _hasStickers; +} + +void PhotoData::setHasAttachedStickers(bool value) { + _hasStickers = value; +} + int PhotoData::width() const { return _images[PhotoSizeIndex(PhotoSize::Large)].location.width(); } diff --git a/Telegram/SourceFiles/data/data_photo.h b/Telegram/SourceFiles/data/data_photo.h index 2c2a60066..09263dcf9 100644 --- a/Telegram/SourceFiles/data/data_photo.h +++ b/Telegram/SourceFiles/data/data_photo.h @@ -142,13 +142,15 @@ public: bool forceRemoteLoader) const -> std::unique_ptr; + [[nodiscard]] bool hasAttachedStickers() const; + void setHasAttachedStickers(bool value); + // For now they return size of the 'large' image. int width() const; int height() const; PhotoId id = 0; TimeId date = 0; - bool hasSticker = false; PeerData *peer = nullptr; // for chat and channel photos connection // geo, caption @@ -164,6 +166,7 @@ private: int32 _dc = 0; uint64 _access = 0; + bool _hasStickers = false; QByteArray _fileReference; std::unique_ptr _replyPreview; std::weak_ptr _media; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 3a186cc30..8ae6619a8 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -2291,7 +2291,7 @@ not_null Session::photo( const QByteArray &fileReference, TimeId date, int32 dc, - bool hasSticker, + bool hasStickers, const QByteArray &inlineThumbnailBytes, const ImageWithLocation &small, const ImageWithLocation &thumbnail, @@ -2305,7 +2305,7 @@ not_null Session::photo( fileReference, date, dc, - hasSticker, + hasStickers, inlineThumbnailBytes, small, thumbnail, @@ -2466,7 +2466,7 @@ void Session::photoApplyFields( const QByteArray &fileReference, TimeId date, int32 dc, - bool hasSticker, + bool hasStickers, const QByteArray &inlineThumbnailBytes, const ImageWithLocation &small, const ImageWithLocation &thumbnail, @@ -2478,7 +2478,7 @@ void Session::photoApplyFields( } photo->setRemoteLocation(dc, access, fileReference); photo->date = date; - photo->hasSticker = hasSticker; + photo->setHasAttachedStickers(hasStickers); photo->updateImages( inlineThumbnailBytes, small, diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 52807a6e8..4474e89b5 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -414,7 +414,7 @@ public: const QByteArray &fileReference, TimeId date, int32 dc, - bool hasSticker, + bool hasStickers, const QByteArray &inlineThumbnailBytes, const ImageWithLocation &small, const ImageWithLocation &thumbnail, @@ -685,7 +685,7 @@ private: const QByteArray &fileReference, TimeId date, int32 dc, - bool hasSticker, + bool hasStickers, const QByteArray &inlineThumbnailBytes, const ImageWithLocation &small, const ImageWithLocation &thumbnail, diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index aea01efe8..750a2273b 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -1589,7 +1589,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { _menu->addAction(tr::lng_context_copy_image(tr::now), [=] { copyContextImage(photo); }); - if (photo->hasSticker) { + if (photo->hasAttachedStickers()) { _menu->addAction(tr::lng_context_attached_stickers(tr::now), [=] { session->api().attachedStickers().requestAttachedStickerSets( controller, diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 7d9840f1b..43649dbe2 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -773,7 +773,7 @@ void OverlayWidget::updateActions() { if ((_document && documentContentShown()) || (_photo && _photoMedia->loaded())) { _actions.push_back({ tr::lng_mediaview_copy(tr::now), SLOT(onCopy()) }); } - if (_photo && _photo->hasSticker) { + if (_photo && _photo->hasAttachedStickers()) { _actions.push_back({ tr::lng_context_attached_stickers(tr::now), SLOT(onAttachedStickers()) }); } if (_canForwardItem) { From 665e322fcef0e704bc59cf84c1b004192e0264f9 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 30 Oct 2020 04:07:04 +0300 Subject: [PATCH 069/190] Added ability to see attached stickers on documents. Fixed #8927. --- .../SourceFiles/api/api_attached_stickers.cpp | 28 +++++++++++++++---- .../SourceFiles/api/api_attached_stickers.h | 9 ++++++ Telegram/SourceFiles/data/data_document.cpp | 9 +++++- Telegram/SourceFiles/data/data_document.h | 3 ++ .../history/history_inner_widget.cpp | 7 +++++ .../media/view/media_view_overlay_widget.cpp | 27 ++++++++++++++++-- .../media/view/media_view_overlay_widget.h | 3 +- 7 files changed, 76 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/api/api_attached_stickers.cpp b/Telegram/SourceFiles/api/api_attached_stickers.cpp index fcdfe7721..dc38822da 100644 --- a/Telegram/SourceFiles/api/api_attached_stickers.cpp +++ b/Telegram/SourceFiles/api/api_attached_stickers.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/confirm_box.h" #include "boxes/sticker_set_box.h" #include "boxes/stickers_box.h" +#include "data/data_document.h" #include "data/data_photo.h" #include "lang/lang_keys.h" #include "window/window_session_controller.h" @@ -21,15 +22,14 @@ AttachedStickers::AttachedStickers(not_null api) : _api(&api->instance()) { } -void AttachedStickers::requestAttachedStickerSets( +void AttachedStickers::request( not_null controller, - not_null photo) { + MTPmessages_GetAttachedStickers &&mtpRequest) { const auto weak = base::make_weak(controller.get()); _api.request(_requestId).cancel(); _requestId = _api.request( - MTPmessages_GetAttachedStickers( - MTP_inputStickeredMediaPhoto(photo->mtpInput()) - )).done([=](const MTPVector &result) { + std::move(mtpRequest) + ).done([=](const MTPVector &result) { _requestId = 0; const auto strongController = weak.get(); if (!strongController) { @@ -61,4 +61,22 @@ void AttachedStickers::requestAttachedStickerSets( }).send(); } +void AttachedStickers::requestAttachedStickerSets( + not_null controller, + not_null photo) { + request( + controller, + MTPmessages_GetAttachedStickers( + MTP_inputStickeredMediaPhoto(photo->mtpInput()))); +} + +void AttachedStickers::requestAttachedStickerSets( + not_null controller, + not_null document) { + request( + controller, + MTPmessages_GetAttachedStickers( + MTP_inputStickeredMediaDocument(document->mtpInput()))); +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_attached_stickers.h b/Telegram/SourceFiles/api/api_attached_stickers.h index bf186fef0..d0dd18bb4 100644 --- a/Telegram/SourceFiles/api/api_attached_stickers.h +++ b/Telegram/SourceFiles/api/api_attached_stickers.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/sender.h" class ApiWrap; +class DocumentData; class PhotoData; namespace Window { @@ -26,7 +27,15 @@ public: not_null controller, not_null photo); + void requestAttachedStickerSets( + not_null controller, + not_null document); + private: + void request( + not_null controller, + MTPmessages_GetAttachedStickers &&mtpRequest); + MTP::Sender _api; mtpRequestId _requestId = 0; diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index b80953ba5..56aee3cd0 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -499,7 +499,9 @@ Main::Session &DocumentData::session() const { void DocumentData::setattributes( const QVector &attributes) { - _flags &= ~(Flag::ImageType | kStreamingSupportedMask); + _flags &= ~(Flag::ImageType + | Flag::HasAttachedStickers + | kStreamingSupportedMask); _flags |= kStreamingSupportedUnknown; validateLottieSticker(); @@ -577,6 +579,7 @@ void DocumentData::setattributes( _filename = std::move(_filename).replace(ch, "_"); } }, [&](const MTPDdocumentAttributeHasStickers &data) { + _flags |= Flag::HasAttachedStickers; }); } if (type == StickerDocument @@ -1539,6 +1542,10 @@ bool DocumentData::isImage() const { return (_flags & Flag::ImageType); } +bool DocumentData::hasAttachedStickers() const { + return (_flags & Flag::HasAttachedStickers); +} + bool DocumentData::supportsStreaming() const { return (_flags & kStreamingSupportedMask) == kStreamingSupportedMaybeYes; } diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index efd777f5e..4c065e476 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -221,6 +221,8 @@ public: [[nodiscard]] bool hasMimeType(QLatin1String mime) const; void setMimeString(const QString &mime); + [[nodiscard]] bool hasAttachedStickers() const; + [[nodiscard]] MediaKey mediaKey() const; [[nodiscard]] Storage::Cache::Key cacheKey() const; [[nodiscard]] uint8 cacheTag() const; @@ -259,6 +261,7 @@ private: ImageType = 0x08, DownloadCancelled = 0x10, LoadedInMediaCache = 0x20, + HasAttachedStickers = 0x40, }; using Flags = base::flags; friend constexpr bool is_flag_type(Flag) { return true; }; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 750a2273b..9489ea8f7 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -1635,6 +1635,13 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { _menu->addAction(lnkIsVideo ? tr::lng_context_save_video(tr::now) : (lnkIsVoice ? tr::lng_context_save_audio(tr::now) : (lnkIsAudio ? tr::lng_context_save_audio_file(tr::now) : tr::lng_context_save_file(tr::now))), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [=] { saveDocumentToFile(itemId, document); })); + if (document->hasAttachedStickers()) { + _menu->addAction(tr::lng_context_attached_stickers(tr::now), [=] { + session->api().attachedStickers().requestAttachedStickerSets( + controller, + document); + }); + } }; const auto link = ClickHandler::getActive(); auto lnkPhoto = dynamic_cast(link.get()); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 43649dbe2..38bf76a48 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -773,8 +773,15 @@ void OverlayWidget::updateActions() { if ((_document && documentContentShown()) || (_photo && _photoMedia->loaded())) { _actions.push_back({ tr::lng_mediaview_copy(tr::now), SLOT(onCopy()) }); } - if (_photo && _photo->hasAttachedStickers()) { - _actions.push_back({ tr::lng_context_attached_stickers(tr::now), SLOT(onAttachedStickers()) }); + if ((_photo && _photo->hasAttachedStickers()) + || (_document && _document->hasAttachedStickers())) { + auto member = _photo + ? SLOT(onPhotoAttachedStickers()) + : SLOT(onDocumentAttachedStickers()); + _actions.push_back({ + tr::lng_context_attached_stickers(tr::now), + std::move(member) + }); } if (_canForwardItem) { _actions.push_back({ tr::lng_mediaview_forward(tr::now), SLOT(onForward()) }); @@ -1545,7 +1552,7 @@ void OverlayWidget::onCopy() { } } -void OverlayWidget::onAttachedStickers() { +void OverlayWidget::onPhotoAttachedStickers() { if (!_session || !_photo) { return; } @@ -1559,6 +1566,20 @@ void OverlayWidget::onAttachedStickers() { close(); } +void OverlayWidget::onDocumentAttachedStickers() { + if (!_session || !_document) { + return; + } + const auto &active = _session->windows(); + if (active.empty()) { + return; + } + _session->api().attachedStickers().requestAttachedStickerSets( + active.front(), + _document); + close(); +} + auto OverlayWidget::sharedMediaType() const -> std::optional { using Type = SharedMediaType; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index b1bbdc697..69902cfec 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -125,7 +125,8 @@ private slots: void onCopy(); void onMenuDestroy(QObject *obj); void receiveMouse(); - void onAttachedStickers(); + void onPhotoAttachedStickers(); + void onDocumentAttachedStickers(); void onDropdown(); From 8d424f6eaf86f1d5a2c7a42ffe1f6dbf3e1397c4 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 30 Oct 2020 12:38:50 +0300 Subject: [PATCH 070/190] Added ability to see attached stickers from admin log. --- .../admin_log/history_admin_log_inner.cpp | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index 2d0b7f92d..b3908f152 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwidget.h" #include "core/application.h" #include "apiwrap.h" +#include "api/api_attached_stickers.h" #include "layout.h" #include "window/window_session_controller.h" #include "main/main_session.h" @@ -1072,6 +1073,16 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { _menu->addAction(tr::lng_context_copy_image(tr::now), [=] { copyContextImage(photo); }); + if (photo->hasAttachedStickers()) { + const auto controller = _controller; + auto callback = [=] { + auto &attached = session().api().attachedStickers(); + attached.requestAttachedStickerSets(controller, photo); + }; + _menu->addAction( + tr::lng_context_attached_stickers(tr::now), + std::move(callback)); + } } else { auto document = lnkDocument->document(); if (document->loading()) { @@ -1104,6 +1115,16 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { _menu->addAction(lnkIsVideo ? tr::lng_context_save_video(tr::now) : (lnkIsVoice ? tr::lng_context_save_audio(tr::now) : (lnkIsAudio ? tr::lng_context_save_audio_file(tr::now) : tr::lng_context_save_file(tr::now))), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, document] { saveDocumentToFile(document); })); + if (document->hasAttachedStickers()) { + const auto controller = _controller; + auto callback = [=, doc = document] { + auto &attached = session().api().attachedStickers(); + attached.requestAttachedStickerSets(controller, doc); + }; + _menu->addAction( + tr::lng_context_attached_stickers(tr::now), + std::move(callback)); + } } } } else if (lnkPeer) { // suggest to block From 943593526f85ac42790430eed41a3a8e8c951c0f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 30 Oct 2020 13:16:03 +0300 Subject: [PATCH 071/190] Added ability to see attached stickers from sections. --- .../view/history_view_context_menu.cpp | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 323476590..c4b5d101d 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/history_view_context_menu.h" +#include "api/api_attached_stickers.h" #include "api/api_editing.h" #include "api/api_toggling_media.h" // Api::ToggleFavedSticker #include "base/unixtime.h" @@ -137,7 +138,8 @@ void ToggleFavedSticker( void AddPhotoActions( not_null menu, - not_null photo) { + not_null photo, + not_null list) { menu->addAction( tr::lng_context_save_image(tr::now), App::LambdaDelayed( @@ -147,6 +149,16 @@ void AddPhotoActions( menu->addAction(tr::lng_context_copy_image(tr::now), [=] { CopyImage(photo); }); + if (photo->hasAttachedStickers()) { + const auto controller = list->controller(); + auto callback = [=] { + auto &attached = photo->session().api().attachedStickers(); + attached.requestAttachedStickerSets(controller, photo); + }; + menu->addAction( + tr::lng_context_attached_stickers(tr::now), + std::move(callback)); + } } void OpenGif(not_null session, FullMsgId itemId) { @@ -239,6 +251,16 @@ void AddDocumentActions( : tr::lng_context_show_in_folder(tr::now)), [=] { ShowInFolder(document); }); } + if (document->hasAttachedStickers()) { + const auto controller = list->controller(); + auto callback = [=] { + auto &attached = session->api().attachedStickers(); + attached.requestAttachedStickerSets(controller, document); + }; + menu->addAction( + tr::lng_context_attached_stickers(tr::now), + std::move(callback)); + } AddSaveDocumentAction(menu, contextId, document); } @@ -768,7 +790,7 @@ base::unique_qptr FillContextMenu( AddTopMessageActions(result, request, list); if (linkPhoto) { - AddPhotoActions(result, photo); + AddPhotoActions(result, photo, list); } else if (linkDocument) { AddDocumentActions(result, document, itemId, list); //} else if (linkPeer) { // #feed From 02818a825162a30dbc065ebb1002fb346802840e Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 30 Oct 2020 13:23:28 +0400 Subject: [PATCH 072/190] Use new tg_owt installation support in snap --- snap/snapcraft.yaml | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index c1c4ff59f..bb734601b 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -67,8 +67,6 @@ parts: source: . source-type: git parse-info: [usr/share/metainfo/telegram-desktop_telegram-desktop.appdata.xml] - build-environment: - - tg_owt_DIR: $SNAPCRAFT_STAGE/tg_owt build-packages: - python - qtbase5-private-dev @@ -268,16 +266,11 @@ parts: stage-packages: - libopus0 - libssl1.1 - override-build: | - cmake "$SNAPCRAFT_PART_SRC" \ - -DCMAKE_BUILD_TYPE=Release \ - -DJPEG_LIBRARY_RELEASE="$SNAPCRAFT_STAGE/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/libjpeg.so" \ - -DJPEG_INCLUDE_DIR="$SNAPCRAFT_STAGE/usr/include" - - cmake --build . -- -j$(nproc) - cp -a . "$SNAPCRAFT_PART_INSTALL" - organize: - "*": tg_owt/ + cmake-parameters: + - -DCMAKE_BUILD_TYPE=Release + - -DCMAKE_INSTALL_PREFIX=/usr + - -DJPEG_LIBRARY_RELEASE=$SNAPCRAFT_STAGE/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/libjpeg.so + - -DJPEG_INCLUDE_DIR=$SNAPCRAFT_STAGE/usr/include prime: [-./*] after: - ffmpeg From 18fe87c0d4b3fc9deca3186f6ddcd0f94d16feff Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 30 Oct 2020 15:01:00 +0400 Subject: [PATCH 073/190] Add MozJPEG to docker --- Telegram/build/docker/centos_env/Dockerfile | 32 ++++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index c02e0d331..0e055dee2 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -47,6 +47,25 @@ RUN scl enable devtoolset-8 -- cmake3 -B build . -DCMAKE_BUILD_TYPE=Release RUN scl enable devtoolset-8 -- cmake3 --build build -j$(nproc) RUN DESTDIR="$LibrariesPath/xz-cache" scl enable devtoolset-8 -- cmake3 --install build +WORKDIR .. +RUN rm -rf xz + +FROM builder AS mozjpeg +RUN git clone -b v4.0.1-rc2 --depth=1 $GIT/mozilla/mozjpeg.git + +WORKDIR mozjpeg +RUN scl enable devtoolset-8 -- cmake3 -B build . \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr/local \ + -DWITH_JPEG8=ON \ + -DPNG_SUPPORTED=OFF + +RUN scl enable devtoolset-8 -- cmake3 --build build -j$(nproc) +RUN DESTDIR="$LibrariesPath/mozjpeg-cache" scl enable devtoolset-8 -- cmake3 --install build + +WORKDIR .. +RUN rm -rf mozjpeg + FROM builder AS opus RUN git clone -b v1.3 --depth=1 $GIT/xiph/opus.git @@ -336,6 +355,7 @@ RUN rm -rf libxkbcommon FROM patches AS qt COPY --from=libffi ${LibrariesPath}/libffi-cache / +COPY --from=mozjpeg ${LibrariesPath}/mozjpeg-cache / COPY --from=xcb ${LibrariesPath}/xcb-cache / COPY --from=libXext ${LibrariesPath}/libXext-cache / COPY --from=libXfixes ${LibrariesPath}/libXfixes-cache / @@ -361,7 +381,6 @@ RUN scl enable devtoolset-8 -- ./configure -prefix "$QT_PREFIX" \ -opensource \ -confirm-license \ -qt-libpng \ - -qt-libjpeg \ -qt-harfbuzz \ -qt-pcre \ -qt-xcb \ @@ -372,11 +391,15 @@ RUN scl enable devtoolset-8 -- ./configure -prefix "$QT_PREFIX" \ -openssl-linked \ -I "$OPENSSL_PREFIX/include" OPENSSL_LIBS="$OPENSSL_PREFIX/lib/libssl.a $OPENSSL_PREFIX/lib/libcrypto.a -lz -ldl -lpthread" \ -nomake examples \ - -nomake tests + -nomake tests \ + -L /usr/local/lib64 RUN scl enable devtoolset-8 -- make -j$(nproc) RUN scl enable devtoolset-8 -- make INSTALL_ROOT="$LibrariesPath/qt-cache" install +WORKDIR .. +RUN rm -rf qt_${QT} + FROM patches AS breakpad RUN git clone https://chromium.googlesource.com/breakpad/breakpad.git @@ -418,10 +441,10 @@ RUN rm -rf gyp FROM builder AS webrtc +COPY --from=mozjpeg ${LibrariesPath}/mozjpeg-cache / COPY --from=opus ${LibrariesPath}/opus-cache / COPY --from=ffmpeg ${LibrariesPath}/ffmpeg-cache / COPY --from=openssl ${LibrariesPath}/openssl-cache / -COPY --from=qt ${LibrariesPath}/qt_${QT} qt_${QT} RUN git clone $GIT/desktop-app/tg_owt.git @@ -431,7 +454,7 @@ RUN git checkout c73a471 RUN scl enable devtoolset-8 -- cmake3 -B out/Release . \ -DCMAKE_BUILD_TYPE=Release \ -DTG_OWT_SPECIAL_TARGET=linux \ - -DTG_OWT_LIBJPEG_INCLUDE_PATH=$(pwd)/../qt_$QT/qtbase/src/3rdparty/libjpeg \ + -DTG_OWT_LIBJPEG_INCLUDE_PATH=/usr/local/include \ -DTG_OWT_OPENSSL_INCLUDE_PATH=$OPENSSL_PREFIX/include \ -DTG_OWT_OPUS_INCLUDE_PATH=/usr/local/include/opus \ -DTG_OWT_FFMPEG_INCLUDE_PATH=/usr/local/include @@ -442,6 +465,7 @@ FROM builder COPY --from=libffi ${LibrariesPath}/libffi-cache / COPY --from=xz ${LibrariesPath}/xz-cache / +COPY --from=mozjpeg ${LibrariesPath}/mozjpeg-cache / COPY --from=opus ${LibrariesPath}/opus-cache / COPY --from=xcb ${LibrariesPath}/xcb-cache / COPY --from=libXext ${LibrariesPath}/libXext-cache / From 30f07280aaa857dc56f7a3d577a8525a96e2dbc7 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 30 Oct 2020 15:14:12 +0400 Subject: [PATCH 074/190] Add an action to test docker image build --- .github/workflows/docker.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/docker.yml diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 000000000..dc456da5c --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,25 @@ +name: Docker. + +on: + push: + paths: + - '.github/workflows/docker.yml' + - 'Telegram/build/docker/centos_env/**' + pull_request: + paths: + - '.github/workflows/docker.yml' + - 'Telegram/build/docker/centos_env/**' + +jobs: + docker: + name: Ubuntu + runs-on: ubuntu-latest + + steps: + - name: Clone. + uses: actions/checkout@v2 + with: + submodules: recursive + + - name: Docker image build. + run: docker build -t telegram_desktop Telegram/build/docker/centos_env From eb27763cae5c796ec8e956163060dfc54b6c48c2 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 30 Oct 2020 19:12:41 +0400 Subject: [PATCH 075/190] Restore Ui::hideLayer call in ShowInFolder on Linux --- Telegram/SourceFiles/core/file_utilities.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Telegram/SourceFiles/core/file_utilities.cpp b/Telegram/SourceFiles/core/file_utilities.cpp index 5b3858ee6..edd0681c9 100644 --- a/Telegram/SourceFiles/core/file_utilities.cpp +++ b/Telegram/SourceFiles/core/file_utilities.cpp @@ -7,7 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "core/file_utilities.h" +#include "boxes/abstract_box.h" #include "storage/localstorage.h" +#include "base/platform/base_platform_info.h" #include "base/platform/base_platform_file_utilities.h" #include "platform/platform_file_utilities.h" #include "core/application.h" @@ -155,6 +157,10 @@ void Launch(const QString &filepath) { void ShowInFolder(const QString &filepath) { crl::on_main([=] { Ui::PreventDelayedActivation(); + if (Platform::IsLinux()) { + // Hide mediaview to make other apps visible. + Ui::hideLayer(anim::type::instant); + } base::Platform::ShowInFolder(filepath); }); } From cc28ba4284f3d508028b117bea8979ac7cb418c1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 6 Oct 2020 15:58:16 +0300 Subject: [PATCH 076/190] Update API scheme to layer 120. --- Telegram/Resources/tl/api.tl | 38 +++++++++------- Telegram/SourceFiles/api/api_updates.cpp | 25 ----------- Telegram/SourceFiles/apiwrap.cpp | 3 ++ .../boxes/peers/add_participants_box.h | 3 ++ .../boxes/peers/edit_participants_box.cpp | 2 + .../calls/calls_box_controller.cpp | 2 +- Telegram/SourceFiles/data/data_location.cpp | 4 +- .../data/data_search_controller.cpp | 2 +- .../dialogs/dialogs_inner_widget.cpp | 18 ++++---- .../dialogs/dialogs_inner_widget.h | 6 +-- .../dialogs_search_from_controllers.cpp | 28 ++++++------ .../dialogs/dialogs_search_from_controllers.h | 9 ++-- .../SourceFiles/dialogs/dialogs_widget.cpp | 44 +++++++++---------- Telegram/SourceFiles/dialogs/dialogs_widget.h | 6 +-- .../export/data/export_data_types.cpp | 6 +++ .../export/data/export_data_types.h | 9 +++- .../SourceFiles/export/export_api_wrap.cpp | 2 +- .../export/output/export_output_html.cpp | 2 + .../export/output/export_output_json.cpp | 3 ++ .../SourceFiles/history/history_service.cpp | 2 + .../storage/download_manager_mtproto.cpp | 4 +- 21 files changed, 113 insertions(+), 105 deletions(-) diff --git a/Telegram/Resources/tl/api.tl b/Telegram/Resources/tl/api.tl index ce2e3b0b7..06f21cc28 100644 --- a/Telegram/Resources/tl/api.tl +++ b/Telegram/Resources/tl/api.tl @@ -69,7 +69,7 @@ inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = In inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia; inputMediaGame#d33f43f3 id:InputGame = InputMedia; inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia; -inputMediaGeoLive#ce4e82fd flags:# stopped:flags.0?true geo_point:InputGeoPoint period:flags.1?int = InputMedia; +inputMediaGeoLive#971fa843 flags:# stopped:flags.0?true geo_point:InputGeoPoint heading:flags.2?int period:flags.1?int proximity_notification_radius:flags.3?int = InputMedia; inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector solution:flags.1?string solution_entities:flags.1?Vector = InputMedia; inputMediaDice#e66fbf7b emoticon:string = InputMedia; @@ -78,7 +78,7 @@ inputChatUploadedPhoto#c642724e flags:# file:flags.0?InputFile video:flags.1?Inp inputChatPhoto#8953ad37 id:InputPhoto = InputChatPhoto; inputGeoPointEmpty#e4c123d6 = InputGeoPoint; -inputGeoPoint#f3b7acc9 lat:double long:double = InputGeoPoint; +inputGeoPoint#48222faf flags:# lat:double long:double accuracy_radius:flags.0?int = InputGeoPoint; inputPhotoEmpty#1cd7bf0d = InputPhoto; inputPhoto#3bb3b94a id:long access_hash:long file_reference:bytes = InputPhoto; @@ -141,7 +141,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#d20b9f3c flags:# has_video:flags.0?true photo_small:FileLocation photo_big:FileLocation dc_id:int = ChatPhoto; messageEmpty#83e5de54 id:int = Message; -message#58ae39c9 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector = Message; +message#58ae39c9 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector = Message; messageService#286fa604 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction = Message; messageMediaEmpty#3ded6320 = MessageMedia; @@ -154,7 +154,7 @@ messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia; messageMediaVenue#2ec0533f geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MessageMedia; messageMediaGame#fdb19008 game:Game = MessageMedia; messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia; -messageMediaGeoLive#7c3c2609 geo:GeoPoint period:int = MessageMedia; +messageMediaGeoLive#b940c666 flags:# geo:GeoPoint heading:flags.0?int period:int proximity_notification_radius:flags.1?int = MessageMedia; messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia; messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia; @@ -181,6 +181,7 @@ messageActionBotAllowed#abe9affe domain:string = MessageAction; messageActionSecureValuesSentMe#1b287353 values:Vector credentials:SecureCredentialsEncrypted = MessageAction; messageActionSecureValuesSent#d95c6154 types:Vector = MessageAction; messageActionContactSignUp#f3f25f76 = MessageAction; +messageActionGeoProximityReached#98e0d697 from_id:Peer to_id:Peer distance:int = MessageAction; dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -195,7 +196,7 @@ photoStrippedSize#e0b0bc2e type:string bytes:bytes = PhotoSize; photoSizeProgressive#5aa86a51 type:string location:FileLocation w:int h:int sizes:Vector = PhotoSize; geoPointEmpty#1117dd5f = GeoPoint; -geoPoint#296f104 long:double lat:double access_hash:long = GeoPoint; +geoPoint#b2a2f663 flags:# long:double lat:double access_hash:long accuracy_radius:flags.0?int = GeoPoint; auth.sentCode#5e002502 flags:# type:auth.SentCodeType phone_code_hash:string next_type:flags.1?auth.CodeType timeout:flags.2?int = auth.SentCode; @@ -247,8 +248,8 @@ messages.dialogsSlice#71e094f3 count:int dialogs:Vector messages:Vector< messages.dialogsNotModified#f0e3e596 count:int = messages.Dialogs; messages.messages#8c718e87 messages:Vector chats:Vector users:Vector = messages.Messages; -messages.messagesSlice#c8edce1e flags:# inexact:flags.1?true count:int next_rate:flags.0?int messages:Vector chats:Vector users:Vector = messages.Messages; -messages.channelMessages#99262e37 flags:# inexact:flags.1?true pts:int count:int messages:Vector chats:Vector users:Vector = messages.Messages; +messages.messagesSlice#3a54685e flags:# inexact:flags.1?true count:int next_rate:flags.0?int offset_id_offset:flags.2?int messages:Vector chats:Vector users:Vector = messages.Messages; +messages.channelMessages#64479808 flags:# inexact:flags.1?true pts:int count:int offset_id_offset:flags.2?int messages:Vector chats:Vector users:Vector = messages.Messages; messages.messagesNotModified#74535f21 count:int = messages.Messages; messages.chats#64ff9fd5 chats:Vector = messages.Chats; @@ -274,6 +275,7 @@ inputMessagesFilterRoundVideo#b549da53 = MessagesFilter; inputMessagesFilterMyMentions#c1f8e69a = MessagesFilter; inputMessagesFilterGeo#e7026d0d = MessagesFilter; inputMessagesFilterContacts#e062db83 = MessagesFilter; +inputMessagesFilterPinned#1bb00451 = MessagesFilter; updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = Update; updateMessageID#4e90bfd6 id:int random_id:long = Update; @@ -313,7 +315,6 @@ updateSavedGifs#9375341e = Update; updateBotInlineQuery#54826690 flags:# query_id:long user_id:int query:string geo:flags.0?GeoPoint offset:string = Update; updateBotInlineSend#e48f964 flags:# user_id:int query:string geo:flags.0?GeoPoint id:string msg_id:flags.1?InputBotInlineMessageID = Update; updateEditChannelMessage#1b3f4df7 message:Message pts:int pts_count:int = Update; -updateChannelPinnedMessage#98592475 channel_id:int id:int = Update; updateBotCallbackQuery#e73547e1 flags:# query_id:long user_id:int peer:Peer msg_id:int chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update; updateEditMessage#e40370a3 message:Message pts:int pts_count:int = Update; updateInlineBotCallbackQuery#f9d27a5a flags:# query_id:long user_id:int msg_id:InputBotInlineMessageID chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update; @@ -338,8 +339,6 @@ updateChannelReadMessagesContents#89893b45 channel_id:int messages:Vector = updateContactsReset#7084a7be = Update; updateChannelAvailableMessages#70db6837 channel_id:int available_min_id:int = Update; updateDialogUnreadMark#e16459c3 flags:# unread:flags.0?true peer:DialogPeer = Update; -updateUserPinnedMessage#4c43da18 user_id:int id:int = Update; -updateChatPinnedMessage#e10db349 chat_id:int id:int version:int = Update; updateMessagePoll#aca1657b flags:# poll_id:long poll:flags.0?Poll results:PollResults = Update; updateChatDefaultBannedRights#54c01850 peer:Peer default_banned_rights:ChatBannedRights version:int = Update; updateFolderPeers#19360dc0 folder_peers:Vector pts:int pts_count:int = Update; @@ -361,6 +360,8 @@ updateReadChannelDiscussionInbox#1cc7de54 flags:# channel_id:int top_msg_id:int updateReadChannelDiscussionOutbox#4638a26c channel_id:int top_msg_id:int read_max_id:int = Update; updatePeerBlocked#246a4b22 peer_id:Peer blocked:Bool = Update; updateChannelUserTyping#ff2abe9f flags:# channel_id:int top_msg_id:flags.0?int user_id:int action:SendMessageAction = Update; +updatePinnedMessages#ed85eab5 flags:# pinned:flags.0?true peer:Peer messages:Vector pts:int pts_count:int = Update; +updatePinnedChannelMessages#8588878b flags:# pinned:flags.0?true channel_id:int messages:Vector pts:int pts_count:int = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -607,6 +608,7 @@ channelParticipantSelf#a3289a6d user_id:int inviter_id:int date:int = ChannelPar channelParticipantCreator#447dca4b flags:# user_id:int admin_rights:ChatAdminRights rank:flags.0?string = ChannelParticipant; channelParticipantAdmin#ccbebbaf flags:# can_edit:flags.0?true self:flags.1?true user_id:int inviter_id:flags.1?int promoted_by:int date:int admin_rights:ChatAdminRights rank:flags.2?string = ChannelParticipant; channelParticipantBanned#1c0facaf flags:# left:flags.0?true user_id:int kicked_by:int date:int banned_rights:ChatBannedRights = ChannelParticipant; +channelParticipantLeft#c3c6796b user_id:int = ChannelParticipant; channelParticipantsRecent#de3f3c79 = ChannelParticipantsFilter; channelParticipantsAdmins#b4608969 = ChannelParticipantsFilter; @@ -615,6 +617,7 @@ channelParticipantsBots#b0d1865b = ChannelParticipantsFilter; channelParticipantsBanned#1427a5e1 q:string = ChannelParticipantsFilter; channelParticipantsSearch#656ac4b q:string = ChannelParticipantsFilter; channelParticipantsContacts#bb6ae88d q:string = ChannelParticipantsFilter; +channelParticipantsMentions#e04b5ceb flags:# q:flags.0?string top_msg_id:flags.1?int = ChannelParticipantsFilter; channels.channelParticipants#f56ee2a8 count:int participants:Vector users:Vector = channels.ChannelParticipants; channels.channelParticipantsNotModified#f0173fe9 = channels.ChannelParticipants; @@ -628,7 +631,7 @@ messages.savedGifs#2e0709a5 hash:int gifs:Vector = messages.SavedGifs; inputBotInlineMessageMediaAuto#3380c786 flags:# message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageText#3dcd7a87 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; -inputBotInlineMessageMediaGeo#c1b15d65 flags:# geo_point:InputGeoPoint period:int reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; +inputBotInlineMessageMediaGeo#96929a85 flags:# geo_point:InputGeoPoint heading:flags.0?int period:flags.1?int proximity_notification_radius:flags.3?int reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaVenue#417bbf11 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaContact#a6edbffd flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageGame#4b425864 flags:# reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; @@ -640,7 +643,7 @@ inputBotInlineResultGame#4fa417f2 id:string short_name:string send_message:Input botInlineMessageMediaAuto#764cf810 flags:# message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageText#8c7f65e2 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = BotInlineMessage; -botInlineMessageMediaGeo#b722de65 flags:# geo:GeoPoint period:int reply_markup:flags.2?ReplyMarkup = BotInlineMessage; +botInlineMessageMediaGeo#51846fd flags:# geo:GeoPoint heading:flags.0?int period:flags.1?int proximity_notification_radius:flags.3?int reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaVenue#8a86659c flags:# geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaContact#18d1cdc2 flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; @@ -1164,8 +1167,6 @@ messageViews#455b853d flags:# views:flags.0?int forwards:flags.1?int replies:fla messages.messageViews#b6c4f543 views:Vector chats:Vector users:Vector = messages.MessageViews; -stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats; - messages.discussionMessage#f5dd8f9d flags:# messages:Vector max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int chats:Vector users:Vector = messages.DiscussionMessage; messageReplyHeader#a6d57763 flags:# reply_to_msg_id:int reply_to_peer_id:flags.0?Peer reply_to_top_id:flags.1?int = MessageReplyHeader; @@ -1174,6 +1175,8 @@ messageReplies#4128faac flags:# comments:flags.0?true replies:int replies_pts:in peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked; +stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1299,7 +1302,7 @@ contacts.blockFromReplies#29a8962c flags:# delete_message:flags.0?true delete_hi messages.getMessages#63c66506 id:Vector = messages.Messages; messages.getDialogs#a0ee3b73 flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:int = messages.Dialogs; messages.getHistory#dcbb8260 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; -messages.search#4e17810b flags:# peer:InputPeer q:string from_id:flags.0?InputUser top_msg_id:flags.1?int filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; +messages.search#c352eec flags:# peer:InputPeer q:string from_id:flags.0?InputPeer top_msg_id:flags.1?int filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; messages.readHistory#e306d3a peer:InputPeer max_id:int = messages.AffectedMessages; messages.deleteHistory#1c015b09 flags:# just_clear:flags.0?true revoke:flags.1?true peer:InputPeer max_id:int = messages.AffectedHistory; messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = messages.AffectedMessages; @@ -1393,7 +1396,7 @@ messages.getSplitRanges#1cff7e08 = Vector; messages.markDialogUnread#c286d98f flags:# unread:flags.0?true peer:InputDialogPeer = Bool; messages.getDialogUnreadMarks#22e24e22 = Vector; messages.clearAllDrafts#7e58ee9c = Bool; -messages.updatePinnedMessage#d2aaf7ec flags:# silent:flags.0?true peer:InputPeer id:int = Updates; +messages.updatePinnedMessage#d2aaf7ec flags:# silent:flags.0?true unpin:flags.1?true pm_oneside:flags.2?true peer:InputPeer id:int = Updates; messages.sendVote#10ea6184 peer:InputPeer msg_id:int options:Vector = Updates; messages.getPollResults#73bb643b peer:InputPeer msg_id:int = Updates; messages.getOnlines#6e2be050 peer:InputPeer = ChatOnlines; @@ -1422,6 +1425,7 @@ messages.getOldFeaturedStickers#5fe7025b offset:int limit:int hash:int = message messages.getReplies#24b581ba peer:InputPeer msg_id:int offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; messages.getDiscussionMessage#446972fd peer:InputPeer msg_id:int = messages.DiscussionMessage; messages.readDiscussion#f731a9f4 peer:InputPeer msg_id:int read_max_id:int = Bool; +messages.unpinAllMessages#f025bc8b peer:InputPeer = messages.AffectedHistory; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1543,4 +1547,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 119 +// LAYER 120 diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 28673ceac..7865403d6 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -1998,31 +1998,6 @@ void Updates::feedUpdate(const MTPUpdate &update) { } } break; - // Pinned message. - case mtpc_updateUserPinnedMessage: { - const auto &d = update.c_updateUserPinnedMessage(); - if (const auto user = session().data().userLoaded(d.vuser_id().v)) { - user->setPinnedMessageId(d.vid().v); - } - } break; - - case mtpc_updateChatPinnedMessage: { - const auto &d = update.c_updateChatPinnedMessage(); - if (const auto chat = session().data().chatLoaded(d.vchat_id().v)) { - const auto status = chat->applyUpdateVersion(d.vversion().v); - if (status == ChatData::UpdateStatus::Good) { - chat->setPinnedMessageId(d.vid().v); - } - } - } break; - - case mtpc_updateChannelPinnedMessage: { - const auto &d = update.c_updateChannelPinnedMessage(); - if (const auto channel = session().data().channelLoaded(d.vchannel_id().v)) { - channel->setPinnedMessageId(d.vid().v); - } - } break; - ////// Cloud sticker sets case mtpc_updateNewStickerSet: { const auto &d = update.c_updateNewStickerSet(); diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index a082d9b88..5c4cd1225 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -1692,6 +1692,9 @@ void ApiWrap::requestSelfParticipant(not_null channel) { }, [&](const MTPDchannelParticipant &data) { LOG(("API Error: Got self regular participant.")); finalize(-1, 0); + }, [&](const MTPDchannelParticipantLeft &data) { + LOG(("API Error: Got self left participant.")); + finalize(-1, 0); }); }); }).fail([=](const RPCError &error) { diff --git a/Telegram/SourceFiles/boxes/peers/add_participants_box.h b/Telegram/SourceFiles/boxes/peers/add_participants_box.h index 2761d172d..122491c6d 100644 --- a/Telegram/SourceFiles/boxes/peers/add_participants_box.h +++ b/Telegram/SourceFiles/boxes/peers/add_participants_box.h @@ -86,6 +86,9 @@ public: AdminDoneCallback adminDoneCallback, BannedDoneCallback bannedDoneCallback); + [[nodiscard]] not_null peer() const { + return _peer; + } [[nodiscard]] Main::Session &session() const override; void prepare() override; void rowClicked(not_null row) override; diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp index 9fb27894a..3cc3aca22 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp @@ -557,6 +557,8 @@ UserData *ParticipantsAdditionalData::applyParticipant( return logBad(); } return applyBanned(data); + }, [&](const MTPDchannelParticipantLeft &data) { + return logBad(); }); } diff --git a/Telegram/SourceFiles/calls/calls_box_controller.cpp b/Telegram/SourceFiles/calls/calls_box_controller.cpp index 1e03fa153..f671469aa 100644 --- a/Telegram/SourceFiles/calls/calls_box_controller.cpp +++ b/Telegram/SourceFiles/calls/calls_box_controller.cpp @@ -296,7 +296,7 @@ void BoxController::loadMoreRows() { MTP_flags(0), MTP_inputPeerEmpty(), MTP_string(), - MTP_inputUserEmpty(), + MTP_inputPeerEmpty(), MTPint(), // top_msg_id MTP_inputMessagesFilterPhoneCalls(MTP_flags(0)), MTP_int(0), diff --git a/Telegram/SourceFiles/data/data_location.cpp b/Telegram/SourceFiles/data/data_location.cpp index c26e50e71..c08c6688f 100644 --- a/Telegram/SourceFiles/data/data_location.cpp +++ b/Telegram/SourceFiles/data/data_location.cpp @@ -36,9 +36,11 @@ QString LocationPoint::lonAsString() const { MTPGeoPoint LocationPoint::toMTP() const { return MTP_geoPoint( + MTP_flags(0), MTP_double(_lon), MTP_double(_lat), - MTP_long(_access)); + MTP_long(_access), + MTP_int(0)); // accuracy_radius } float64 LocationPoint::lat() const { diff --git a/Telegram/SourceFiles/data/data_search_controller.cpp b/Telegram/SourceFiles/data/data_search_controller.cpp index 7de8f2fc0..367ed2e97 100644 --- a/Telegram/SourceFiles/data/data_search_controller.cpp +++ b/Telegram/SourceFiles/data/data_search_controller.cpp @@ -87,7 +87,7 @@ std::optional PrepareSearchRequest( MTP_flags(0), peer->input, MTP_string(query), - MTP_inputUserEmpty(), + MTP_inputPeerEmpty(), MTPint(), // top_msg_id filter, MTP_int(0), diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 5f4e32335..d7b03c4b8 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -380,7 +380,7 @@ int InnerWidget::searchedOffset() const { int InnerWidget::searchInChatSkip() const { auto result = st::searchedBarHeight + st::dialogsSearchInHeight; - if (_searchFromUser) { + if (_searchFromPeer) { result += st::lineWidth + st::dialogsSearchInHeight; } return result; @@ -811,7 +811,7 @@ void InnerWidget::paintSearchInChat(Painter &p) const { auto fullRect = QRect(0, top, width(), height - top); p.fillRect(fullRect, st::dialogsBg); - if (_searchFromUser) { + if (_searchFromPeer) { p.fillRect(QRect(0, top + st::dialogsSearchInHeight, width(), st::lineWidth), st::shadowFg); } @@ -829,7 +829,7 @@ void InnerWidget::paintSearchInChat(Painter &p) const { } else { Unexpected("Empty Key in paintSearchInChat."); } - if (const auto from = _searchFromUser) { + if (const auto from = _searchFromPeer) { top += st::dialogsSearchInHeight + st::lineWidth; p.setPen(st::dialogsTextFg); p.setTextPalette(st::dialogsSearchFromPalette); @@ -1868,7 +1868,7 @@ void InnerWidget::applyFilterUpdate(QString newFilter, bool force) { newFilter = words.isEmpty() ? QString() : words.join(' '); if (newFilter != _filter || force) { _filter = newFilter; - if (_filter.isEmpty() && !_searchFromUser) { + if (_filter.isEmpty() && !_searchFromPeer) { clearFilter(); } else { _state = WidgetState::Filtered; @@ -2328,7 +2328,7 @@ bool InnerWidget::hasFilteredResults() const { return !_filterResults.empty() && _hashtagResults.empty(); } -void InnerWidget::searchInChat(Key key, UserData *from) { +void InnerWidget::searchInChat(Key key, PeerData *from) { _searchInMigrated = nullptr; if (const auto peer = key.peer()) { if (const auto migrateTo = peer->migrateTo()) { @@ -2338,7 +2338,7 @@ void InnerWidget::searchInChat(Key key, UserData *from) { } } _searchInChat = key; - _searchFromUser = from; + _searchFromPeer = from; if (_searchInChat) { _controller->closeFolder(); onHashtagFilterUpdate(QStringRef()); @@ -2347,9 +2347,9 @@ void InnerWidget::searchInChat(Key key, UserData *from) { } else { _cancelSearchInChat->hide(); } - if (_searchFromUser) { + if (_searchFromPeer) { _cancelSearchFromUser->show(); - _searchFromUserUserpic = _searchFromUser->createUserpicView(); + _searchFromUserUserpic = _searchFromPeer->createUserpicView(); } else { _cancelSearchFromUser->hide(); _searchFromUserUserpic = nullptr; @@ -2386,7 +2386,7 @@ void InnerWidget::refreshSearchInChatLabel() { dialog, Ui::DialogTextOptions()); } - const auto from = _searchFromUser ? _searchFromUser->name : QString(); + const auto from = _searchFromPeer ? _searchFromPeer->name : QString(); if (!from.isEmpty()) { const auto fromUserText = tr::lng_dlg_search_from( tr::now, diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index e6a245d1f..ea3a59fa2 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -109,7 +109,7 @@ public: } bool hasFilteredResults() const; - void searchInChat(Key key, UserData *from); + void searchInChat(Key key, PeerData *from); void applyFilterUpdate(QString newFilter, bool force = false); void onHashtagFilterUpdate(QStringRef newFilter); @@ -119,7 +119,7 @@ public: void setLoadMoreCallback(Fn callback); [[nodiscard]] rpl::producer<> listBottomReached() const; - base::Observable searchFromUserChanged; + base::Observable searchFromUserChanged; [[nodiscard]] rpl::producer chosenRow() const; [[nodiscard]] rpl::producer<> updated() const; @@ -395,7 +395,7 @@ private: Key _searchInChat; History *_searchInMigrated = nullptr; - UserData *_searchFromUser = nullptr; + PeerData *_searchFromPeer = nullptr; mutable std::shared_ptr _searchInChatUserpic; mutable std::shared_ptr _searchFromUserUserpic; Ui::Text::String _searchInChatText; diff --git a/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.cpp b/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.cpp index 770b6d759..6ff20477c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.cpp @@ -19,7 +19,7 @@ namespace Dialogs { void ShowSearchFromBox( not_null peer, - Fn)> callback, + Fn)> callback, Fn closedCallback) { auto createController = [ peer, @@ -47,33 +47,33 @@ void ShowSearchFromBox( SearchFromController::SearchFromController( not_null peer, - Fn)> callback) + Fn)> callback) : AddSpecialBoxController( peer, ParticipantsBoxController::Role::Members, AdminDoneCallback(), BannedDoneCallback()) -, _callback(std::move(callback)) -{ +, _callback(std::move(callback)) { _excludeSelf = false; } void SearchFromController::prepare() { AddSpecialBoxController::prepare(); delegate()->peerListSetTitle(tr::lng_search_messages_from()); -} - -void SearchFromController::rowClicked(not_null row) { - Expects(row->peer()->isUser()); - - if (const auto onstack = base::duplicate(_callback)) { - onstack(row->peer()->asUser()); + if (const auto megagroup = peer()->asMegagroup()) { + if (!delegate()->peerListFindRow(megagroup->id)) { + delegate()->peerListAppendRow( + std::make_unique(megagroup)); + setDescriptionText({}); + delegate()->peerListRefreshRows(); + } } } -std::unique_ptr SearchFromController::createRow( - not_null user) const { - return std::make_unique(user); +void SearchFromController::rowClicked(not_null row) { + if (const auto onstack = base::duplicate(_callback)) { + onstack(row->peer()); + } } } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.h b/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.h index 709353bb5..f095ae566 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.h +++ b/Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.h @@ -14,23 +14,20 @@ namespace Dialogs { void ShowSearchFromBox( not_null peer, - Fn)> callback, + Fn)> callback, Fn closedCallback); class SearchFromController : public AddSpecialBoxController { public: SearchFromController( not_null peer, - Fn)> callback); + Fn)> callback); void prepare() override; void rowClicked(not_null row) override; -protected: - std::unique_ptr createRow(not_null user) const; - private: - Fn)> _callback; + Fn)> _callback; }; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 3e00d4b87..731a4f957 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -208,8 +208,8 @@ Widget::Widget( connect(_inner, SIGNAL(completeHashtag(QString)), this, SLOT(onCompleteHashtag(QString))); connect(_inner, SIGNAL(refreshHashtags()), this, SLOT(onFilterCursorMoved())); connect(_inner, SIGNAL(cancelSearchInChat()), this, SLOT(onCancelSearchInChat())); - subscribe(_inner->searchFromUserChanged, [this](UserData *user) { - setSearchInChat(_searchInChat, user); + subscribe(_inner->searchFromUserChanged, [this](PeerData *from) { + setSearchInChat(_searchInChat, from); applyFilterUpdate(true); }); _inner->chosenRow( @@ -789,7 +789,7 @@ void Widget::onDraggingScrollTimer() { bool Widget::onSearchMessages(bool searchCache) { auto result = false; auto q = _filter->getLastText().trimmed(); - if (q.isEmpty() && !_searchFromUser) { + if (q.isEmpty() && !_searchFromAuthor) { cancelSearchRequest(); _api.request(base::take(_peerSearchRequest)).cancel(); return true; @@ -804,7 +804,7 @@ bool Widget::onSearchMessages(bool searchCache) { const auto i = _searchCache.find(q); if (i != _searchCache.end()) { _searchQuery = q; - _searchQueryFrom = _searchFromUser; + _searchQueryFrom = _searchFromAuthor; _searchNextRate = 0; _searchFull = _searchFullMigrated = false; cancelSearchRequest(); @@ -816,9 +816,9 @@ bool Widget::onSearchMessages(bool searchCache) { 0); result = true; } - } else if (_searchQuery != q || _searchQueryFrom != _searchFromUser) { + } else if (_searchQuery != q || _searchQueryFrom != _searchFromAuthor) { _searchQuery = q; - _searchQueryFrom = _searchFromUser; + _searchQueryFrom = _searchFromAuthor; _searchNextRate = 0; _searchFull = _searchFullMigrated = false; cancelSearchRequest(); @@ -836,8 +836,8 @@ bool Widget::onSearchMessages(bool searchCache) { peer->input, MTP_string(_searchQuery), (_searchQueryFrom - ? _searchQueryFrom->inputUser - : MTP_inputUserEmpty()), + ? _searchQueryFrom->input + : MTP_inputPeerEmpty()), MTPint(), // top_msg_id MTP_inputMessagesFilterEmpty(), MTP_int(0), @@ -1009,8 +1009,8 @@ void Widget::onSearchMore() { peer->input, MTP_string(_searchQuery), (_searchQueryFrom - ? _searchQueryFrom->inputUser - : MTP_inputUserEmpty()), + ? _searchQueryFrom->input + : MTP_inputPeerEmpty()), MTPint(), // top_msg_id MTP_inputMessagesFilterEmpty(), MTP_int(0), @@ -1103,8 +1103,8 @@ void Widget::onSearchMore() { _searchInMigrated->peer->input, MTP_string(_searchQuery), (_searchQueryFrom - ? _searchQueryFrom->inputUser - : MTP_inputUserEmpty()), + ? _searchQueryFrom->input + : MTP_inputPeerEmpty()), MTPint(), // top_msg_id MTP_inputMessagesFilterEmpty(), MTP_int(0), @@ -1380,7 +1380,7 @@ void Widget::applyFilterUpdate(bool force) { auto filterText = _filter->getLastText(); _inner->applyFilterUpdate(filterText, force); - if (filterText.isEmpty() && !_searchFromUser) { + if (filterText.isEmpty() && !_searchFromAuthor) { clearSearchCache(); } _cancelSearch->toggle(!filterText.isEmpty(), anim::type::normal); @@ -1395,7 +1395,7 @@ void Widget::applyFilterUpdate(bool force) { _peerSearchQuery = QString(); } - if (_chooseFromUser->toggled() || _searchFromUser) { + if (_chooseFromUser->toggled() || _searchFromAuthor) { auto switchToChooseFrom = SwitchToChooseFromQuery(); if (_lastFilterText != switchToChooseFrom && switchToChooseFrom.startsWith(_lastFilterText) @@ -1412,7 +1412,7 @@ void Widget::searchInChat(Key chat) { applyFilterUpdate(true); } -void Widget::setSearchInChat(Key chat, UserData *from) { +void Widget::setSearchInChat(Key chat, PeerData *from) { if (chat.folder()) { chat = Key(); } @@ -1433,13 +1433,13 @@ void Widget::setSearchInChat(Key chat, UserData *from) { } else if (!_searchInChat) { from = nullptr; } - if (_searchFromUser != from || searchInPeerUpdated) { - _searchFromUser = from; + if (_searchFromAuthor != from || searchInPeerUpdated) { + _searchFromAuthor = from; updateSearchFromVisibility(); clearSearchCache(); } - _inner->searchInChat(_searchInChat, _searchFromUser); - if (_searchFromUser && _lastFilterText == SwitchToChooseFromQuery()) { + _inner->searchInChat(_searchInChat, _searchFromAuthor); + if (_searchFromAuthor && _lastFilterText == SwitchToChooseFromQuery()) { onCancelSearch(); } _filter->setFocus(); @@ -1467,9 +1467,9 @@ void Widget::showSearchFrom() { const auto chat = _searchInChat; ShowSearchFromBox( peer, - crl::guard(this, [=](not_null user) { + crl::guard(this, [=](not_null from) { Ui::hideLayer(); - setSearchInChat(chat, user); + setSearchInChat(chat, from); applyFilterUpdate(true); }), crl::guard(this, [=] { _filter->setFocus(); })); @@ -1558,7 +1558,7 @@ void Widget::updateSearchFromVisibility(bool fast) { auto visible = [&] { if (const auto peer = _searchInChat.peer()) { if (peer->isChat() || peer->isMegagroup()) { - return !_searchFromUser; + return !_searchFromAuthor; } } return false; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index f50b5b9e2..271754f09 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -141,7 +141,7 @@ private: void setupConnectingWidget(); void setupMainMenuToggle(); bool searchForPeersRequired(const QString &query) const; - void setSearchInChat(Key chat, UserData *from = nullptr); + void setSearchInChat(Key chat, PeerData *from = nullptr); void showJumpToDate(); void showSearchFrom(); void showMainMenu(); @@ -212,7 +212,7 @@ private: Data::Folder *_openedFolder = nullptr; Dialogs::Key _searchInChat; History *_searchInMigrated = nullptr; - UserData *_searchFromUser = nullptr; + PeerData *_searchFromAuthor = nullptr; QString _lastFilterText; QTimer _searchTimer; @@ -222,7 +222,7 @@ private: mtpRequestId _peerSearchRequest = 0; QString _searchQuery; - UserData *_searchQueryFrom = nullptr; + PeerData *_searchQueryFrom = nullptr; int32 _searchNextRate = 0; bool _searchFull = false; bool _searchFullMigrated = false; diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 260865f3c..3819cbec1 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -1094,6 +1094,12 @@ ServiceAction ParseServiceAction( result.content = content; }, [&](const MTPDmessageActionContactSignUp &data) { result.content = ActionContactSignUp(); + }, [&](const MTPDmessageActionGeoProximityReached &data) { + auto content = ActionGeoProximityReached(); + content.fromId = ParsePeerId(data.vfrom_id()); + content.toId = ParsePeerId(data.vto_id()); + content.distance = data.vdistance().v; + result.content = content; }, [](const MTPDmessageActionEmpty &data) {}); return result; } diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index 06fc860ba..70fbf56af 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -450,6 +450,12 @@ struct ActionContactSignUp { struct ActionPhoneNumberRequest { }; +struct ActionGeoProximityReached { + PeerId fromId = 0; + PeerId toId = 0; + int distance = 0; +}; + struct ServiceAction { std::variant< v::null_t, @@ -473,7 +479,8 @@ struct ServiceAction { ActionBotAllowed, ActionSecureValuesSent, ActionContactSignUp, - ActionPhoneNumberRequest> content; + ActionPhoneNumberRequest, + ActionGeoProximityReached> content; }; ServiceAction ParseServiceAction( diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp index 88caefa00..b317617e0 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.cpp +++ b/Telegram/SourceFiles/export/export_api_wrap.cpp @@ -1429,7 +1429,7 @@ void ApiWrap::requestChatMessages( MTP_flags(MTPmessages_Search::Flag::f_from_id), realPeerInput, MTP_string(), // query - _user, + MTP_inputPeerSelf(), MTPint(), // top_msg_id MTP_inputMessagesFilterEmpty(), MTP_int(0), // min_date diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index 61b692d35..c527decca 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -1092,6 +1092,8 @@ auto HtmlWriter::Wrap::pushMessage( + SerializeList(list); }, [&](const ActionContactSignUp &data) { return serviceFrom + " joined Telegram"; + }, [&](const ActionGeoProximityReached &data) { + return serviceFrom + " reached"; // #TODO files distance from to }, [&](const ActionPhoneNumberRequest &data) { return serviceFrom + " requested your phone number"; }, [](v::null_t) { return QByteArray(); }); diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp index 4989e1608..20d6169b5 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -473,6 +473,9 @@ QByteArray SerializeMessage( }, [&](const ActionContactSignUp &data) { pushActor(); pushAction("joined_telegram"); + }, [&](const ActionGeoProximityReached &data) { + pushActor(); + pushAction("proximity_reached"); // #TODO files distance from to }, [&](const ActionPhoneNumberRequest &data) { pushActor(); pushAction("requested_phone_number"); diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index 1ea514dfe..b3e61a7cf 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -260,6 +260,8 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { return prepareSecureValuesSent(data); }, [&](const MTPDmessageActionContactSignUp &data) { return prepareContactSignUp(); + }, [&](const MTPDmessageActionGeoProximityReached &data) { + return PreparedText{ tr::lng_message_empty(tr::now) }; // #TODO files }, [](const MTPDmessageActionPaymentSentMe &) { LOG(("API Error: messageActionPaymentSentMe received.")); return PreparedText{ tr::lng_message_empty(tr::now) }; diff --git a/Telegram/SourceFiles/storage/download_manager_mtproto.cpp b/Telegram/SourceFiles/storage/download_manager_mtproto.cpp index 093d4c01e..4ba4a9ce9 100644 --- a/Telegram/SourceFiles/storage/download_manager_mtproto.cpp +++ b/Telegram/SourceFiles/storage/download_manager_mtproto.cpp @@ -541,8 +541,10 @@ mtpRequestId DownloadMtprotoTask::sendRequest( return api().request(MTPupload_GetWebFile( MTP_inputWebFileGeoPointLocation( MTP_inputGeoPoint( + MTP_flags(0), MTP_double(location.lat), - MTP_double(location.lon)), + MTP_double(location.lon), + MTP_int(0)), // accuracy_radius MTP_long(location.access), MTP_int(location.width), MTP_int(location.height), From 399b03beb20cb14841a5d610211f940dbc2cbd3a Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 6 Oct 2020 21:27:26 +0300 Subject: [PATCH 077/190] Implement multi-song albums display. --- .../SourceFiles/data/data_media_types.cpp | 2 +- .../history/view/history_view_message.cpp | 141 ++++++++++++++++-- .../view/media/history_view_document.cpp | 125 ++++++++++++---- .../view/media/history_view_document.h | 32 ++++ .../history/view/media/history_view_media.h | 11 ++ .../view/media/history_view_media_grouped.cpp | 91 +++++++++-- .../view/media/history_view_media_grouped.h | 16 +- 7 files changed, 357 insertions(+), 61 deletions(-) diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index d332394a7..6c2301ea8 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -465,7 +465,7 @@ Storage::SharedMediaTypesMask MediaFile::sharedMediaTypes() const { } bool MediaFile::canBeGrouped() const { - return _document->isVideoFile(); + return _document->isVideoFile() || _document->isSong(); } bool MediaFile::hasReplyPreview() const { diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 9121cf3f4..ab556e3a9 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -138,27 +138,101 @@ QString FastReplyText() { return tr::lng_fast_reply(tr::now); } -void PaintBubble(Painter &p, QRect rect, int outerWidth, bool selected, bool outbg, RectPart tailSide) { +void PaintBubble(Painter &p, QRect rect, int outerWidth, bool selected, bool outbg, RectPart tailSide, RectParts skip) { auto &bg = selected ? (outbg ? st::msgOutBgSelected : st::msgInBgSelected) : (outbg ? st::msgOutBg : st::msgInBg); - auto &sh = selected ? (outbg ? st::msgOutShadowSelected : st::msgInShadowSelected) : (outbg ? st::msgOutShadow : st::msgInShadow); + auto sh = &(selected ? (outbg ? st::msgOutShadowSelected : st::msgInShadowSelected) : (outbg ? st::msgOutShadow : st::msgInShadow)); auto cors = selected ? (outbg ? MessageOutSelectedCorners : MessageInSelectedCorners) : (outbg ? MessageOutCorners : MessageInCorners); - auto parts = RectPart::FullTop | RectPart::NoTopBottom | RectPart::Bottom; + auto parts = RectPart::None | RectPart::NoTopBottom; + if (skip & RectPart::Top) { + if (skip & RectPart::Bottom) { + p.fillRect(rect, bg); + return; + } + rect.setTop(rect.y() - st::historyMessageRadius); + } else { + parts |= RectPart::FullTop; + } + if (skip & RectPart::Bottom) { + rect.setHeight(rect.height() + st::historyMessageRadius); + sh = nullptr; + tailSide = RectPart::None; + } else { + parts |= RectPart::Bottom; + } if (tailSide == RectPart::Right) { parts |= RectPart::BottomLeft; p.fillRect(rect.x() + rect.width() - st::historyMessageRadius, rect.y() + rect.height() - st::historyMessageRadius, st::historyMessageRadius, st::historyMessageRadius, bg); auto &tail = selected ? st::historyBubbleTailOutRightSelected : st::historyBubbleTailOutRight; tail.paint(p, rect.x() + rect.width(), rect.y() + rect.height() - tail.height(), outerWidth); - p.fillRect(rect.x() + rect.width() - st::historyMessageRadius, rect.y() + rect.height(), st::historyMessageRadius + tail.width(), st::msgShadow, sh); + p.fillRect(rect.x() + rect.width() - st::historyMessageRadius, rect.y() + rect.height(), st::historyMessageRadius + tail.width(), st::msgShadow, *sh); } else if (tailSide == RectPart::Left) { parts |= RectPart::BottomRight; p.fillRect(rect.x(), rect.y() + rect.height() - st::historyMessageRadius, st::historyMessageRadius, st::historyMessageRadius, bg); auto &tail = selected ? (outbg ? st::historyBubbleTailOutLeftSelected : st::historyBubbleTailInLeftSelected) : (outbg ? st::historyBubbleTailOutLeft : st::historyBubbleTailInLeft); tail.paint(p, rect.x() - tail.width(), rect.y() + rect.height() - tail.height(), outerWidth); - p.fillRect(rect.x() - tail.width(), rect.y() + rect.height(), st::historyMessageRadius + tail.width(), st::msgShadow, sh); - } else { + p.fillRect(rect.x() - tail.width(), rect.y() + rect.height(), st::historyMessageRadius + tail.width(), st::msgShadow, *sh); + } else if (!(skip & RectPart::Bottom)) { parts |= RectPart::FullBottom; } - App::roundRect(p, rect, bg, cors, &sh, parts); + App::roundRect(p, rect, bg, cors, sh, parts); +} + +void PaintBubble(Painter &p, QRect rect, int outerWidth, bool selected, const std::vector &selection, bool outbg, RectPart tailSide) { + if (selection.empty()) { + PaintBubble( + p, + rect, + outerWidth, + selected, + outbg, + tailSide, + RectPart::None); + return; + } + const auto left = rect.x(); + const auto width = rect.width(); + const auto top = rect.y(); + const auto bottom = top + rect.height(); + auto from = top; + for (const auto &selected : selection) { + if (selected.top > from) { + const auto skip = RectPart::Bottom + | (from > top ? RectPart::Top : RectPart::None); + PaintBubble( + p, + QRect(left, from, width, selected.top - from), + outerWidth, + false, + outbg, + tailSide, + skip); + } + const auto skip = ((selected.top > top) + ? RectPart::Top + : RectPart::None) + | ((selected.top + selected.height < bottom) + ? RectPart::Bottom + : RectPart::None); + PaintBubble( + p, + QRect(left, selected.top, width, selected.height), + outerWidth, + true, + outbg, + tailSide, + skip); + from = selected.top + selected.height; + } + if (from < bottom) { + PaintBubble( + p, + QRect(left, from, width, bottom - from), + outerWidth, + false, + outbg, + tailSide, + RectPart::Top); + } } style::color FromNameFg(PeerId peerId, bool selected) { @@ -535,20 +609,52 @@ void Message::draw( auto entry = logEntryOriginal(); auto mediaDisplayed = media && media->isDisplayed(); + // Entry page is always a bubble bottom. + auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); + auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); + + + auto mediaSelectionIntervals = (!selected && mediaDisplayed) + ? media->getBubbleSelectionIntervals(selection) + : std::vector(); + if (!mediaSelectionIntervals.empty()) { + auto localMediaBottom = g.top() + g.height(); + if (data()->repliesAreComments() || data()->externalReply()) { + localMediaBottom -= st::historyCommentsButtonHeight; + } + if (!mediaOnBottom) { + localMediaBottom -= st::msgPadding.bottom(); + } + if (entry) { + localMediaBottom -= entry->height(); + } + const auto localMediaTop = localMediaBottom - media->height(); + for (auto &[top, height] : mediaSelectionIntervals) { + top += localMediaTop; + } + } + auto skipTail = isAttachedToNext() || (media && media->skipBubbleTail()) || (keyboard != nullptr) || (context() == Context::Replies && data()->isDiscussionPost()); - auto displayTail = skipTail ? RectPart::None : (outbg && !Core::App().settings().chatWide()) ? RectPart::Right : RectPart::Left; - PaintBubble(p, g, width(), selected, outbg, displayTail); + auto displayTail = skipTail + ? RectPart::None + : (outbg && !Core::App().settings().chatWide()) + ? RectPart::Right + : RectPart::Left; + PaintBubble( + p, + g, + width(), + selected, + mediaSelectionIntervals, + outbg, + displayTail); auto inner = g; paintCommentsButton(p, inner, selected); - // Entry page is always a bubble bottom. - auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); - auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); - auto trect = inner.marginsRemoved(st::msgPadding); if (mediaOnBottom) { trect.setHeight(trect.height() + st::msgPadding.bottom()); @@ -591,11 +697,16 @@ void Message::draw( ? !media->customInfoLayout() : true); if (needDrawInfo) { - drawInfo(p, inner.left() + inner.width(), inner.top() + inner.height(), 2 * inner.left() + inner.width(), selected, InfoDisplayType::Default); + const auto bottomSelected = selected + || (!mediaSelectionIntervals.empty() + && (mediaSelectionIntervals.back().top + + mediaSelectionIntervals.back().height + >= inner.y() + inner.height())); + drawInfo(p, inner.left() + inner.width(), inner.top() + inner.height(), 2 * inner.left() + inner.width(), bottomSelected, InfoDisplayType::Default); if (g != inner) { const auto o = p.opacity(); p.setOpacity(0.3); - const auto color = selected + const auto color = bottomSelected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg); p.fillRect(inner.left(), inner.top() + inner.height() - st::lineWidth, inner.width(), st::lineWidth, color); diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index 785998a76..158f2c71e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -247,8 +247,21 @@ QSize Document::countCurrentSize(int newWidth) { return { newWidth, newHeight }; } -void Document::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const { - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; +void Document::draw( + Painter &p, + const QRect &r, + TextSelection selection, + crl::time ms) const { + draw(p, width(), selection, ms, LayoutMode::Full); +} + +void Document::draw( + Painter &p, + int width, + TextSelection selection, + crl::time ms, + LayoutMode mode) const { + if (width < st::msgPadding.left() + st::msgPadding.right() + 1) return; ensureDataMediaCreated(); @@ -260,7 +273,7 @@ void Document::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti bool loaded = dataLoaded(), displayLoading = _data->displayLoading(); bool selected = (selection == FullSelection); - int captionw = width() - st::msgPadding.left() - st::msgPadding.right(); + int captionw = width - st::msgPadding.left() - st::msgPadding.right(); auto outbg = _parent->hasOutLayout(); if (displayLoading) { @@ -284,7 +297,7 @@ void Document::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti auto inWebPage = (_parent->media() != this); auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large; - QRect rthumb(style::rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, width())); + QRect rthumb(style::rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, width)); QPixmap thumb; if (const auto normal = _dataMedia->thumbnail()) { thumb = normal->pixSingle(thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize, roundRadius); @@ -339,7 +352,7 @@ void Document::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti bool over = ClickHandler::showAsActive(lnk); p.setFont(over ? st::semiboldFont->underline() : st::semiboldFont); p.setPen(outbg ? (selected ? st::msgFileThumbLinkOutFgSelected : st::msgFileThumbLinkOutFg) : (selected ? st::msgFileThumbLinkInFgSelected : st::msgFileThumbLinkInFg)); - p.drawTextLeft(nameleft, linktop, width(), thumbed->_link, thumbed->_linkw); + p.drawTextLeft(nameleft, linktop, width, thumbed->_link, thumbed->_linkw); } } else { nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); @@ -348,7 +361,7 @@ void Document::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti statustop = st::msgFileStatusTop - topMinus; bottom = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() - topMinus; - QRect inner(style::rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, st::msgFileSize, st::msgFileSize, width())); + QRect inner(style::rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, st::msgFileSize, st::msgFileSize, width)); p.setPen(Qt::NoPen); if (selected) { p.setBrush(outbg ? st::msgFileOutBgSelected : st::msgFileInBgSelected); @@ -386,7 +399,7 @@ void Document::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti drawCornerDownload(p, selected); } - auto namewidth = width() - nameleft - nameright; + auto namewidth = width - nameleft - nameright; auto statuswidth = namewidth; auto voiceStatusOverride = QString(); @@ -470,9 +483,9 @@ void Document::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti p.setFont(st::semiboldFont); p.setPen(outbg ? (selected ? st::historyFileNameOutFgSelected : st::historyFileNameOutFg) : (selected ? st::historyFileNameInFgSelected : st::historyFileNameInFg)); if (namewidth < named->_namew) { - p.drawTextLeft(nameleft, nametop, width(), st::semiboldFont->elided(named->_name, namewidth, Qt::ElideMiddle)); + p.drawTextLeft(nameleft, nametop, width, st::semiboldFont->elided(named->_name, namewidth, Qt::ElideMiddle)); } else { - p.drawTextLeft(nameleft, nametop, width(), named->_name, named->_namew); + p.drawTextLeft(nameleft, nametop, width, named->_name, named->_namew); } } @@ -480,7 +493,7 @@ void Document::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti auto status = outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg); p.setFont(st::normalFont); p.setPen(status); - p.drawTextLeft(nameleft, statustop, width(), statusText); + p.drawTextLeft(nameleft, statustop, width, statusText); if (_parent->data()->hasUnreadMediaFlag()) { auto w = st::normalFont->width(statusText); @@ -490,14 +503,16 @@ void Document::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti { PainterHighQualityEnabler hq(p); - p.drawEllipse(style::rtlrect(nameleft + w + st::mediaUnreadSkip, statustop + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, width())); + p.drawEllipse(style::rtlrect(nameleft + w + st::mediaUnreadSkip, statustop + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, width)); } } } - if (auto captioned = Get()) { - p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg)); - captioned->_caption.draw(p, st::msgPadding.left(), bottom, captionw, style::al_left, 0, -1, selection); + if (mode == LayoutMode::Full) { + if (auto captioned = Get()) { + p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg)); + captioned->_caption.draw(p, st::msgPadding.left(), bottom, captionw, style::al_left, 0, -1, selection); + } } } @@ -587,9 +602,20 @@ TextState Document::cornerDownloadTextState( } TextState Document::textState(QPoint point, StateRequest request) const { + return textState(point, { width(), height() }, request, LayoutMode::Full); +} + +TextState Document::textState( + QPoint point, + QSize layout, + StateRequest request, + LayoutMode mode) const { + const auto width = layout.width(); + const auto height = layout.height(); + auto result = TextState(_parent); - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { + if (width < st::msgPadding.left() + st::msgPadding.right() + 1) { return result; } @@ -607,14 +633,14 @@ TextState Document::textState(QPoint point, StateRequest request) const { linktop = st::msgFileThumbLinkTop - topMinus; bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom() - topMinus; - QRect rthumb(style::rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, width())); + QRect rthumb(style::rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, width)); if ((_data->loading() || _data->uploading()) && rthumb.contains(point)) { result.link = _cancell; return result; } if (_data->status != FileUploadFailed) { - if (style::rtlrect(nameleft, linktop, thumbed->_linkw, st::semiboldFont->height, width()).contains(point)) { + if (style::rtlrect(nameleft, linktop, thumbed->_linkw, st::semiboldFont->height, width).contains(point)) { result.link = (_data->loading() || _data->uploading()) ? thumbed->_linkcancell : dataLoaded() @@ -632,7 +658,7 @@ TextState Document::textState(QPoint point, StateRequest request) const { if (const auto state = cornerDownloadTextState(point, request); state.link) { return state; } - QRect inner(style::rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, st::msgFileSize, st::msgFileSize, width())); + QRect inner(style::rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, st::msgFileSize, st::msgFileSize, width)); if ((_data->loading() || _data->uploading()) && inner.contains(point) && !downloadInCorner()) { result.link = _cancell; return result; @@ -640,7 +666,7 @@ TextState Document::textState(QPoint point, StateRequest request) const { } if (const auto voice = Get()) { - auto namewidth = width() - nameleft - nameright; + auto namewidth = width - nameleft - nameright; auto waveformbottom = st::msgFilePadding.top() - topMinus + st::msgWaveformMax + st::msgWaveformMin; if (QRect(nameleft, nametop, namewidth, waveformbottom - nametop).contains(point)) { const auto state = ::Media::Player::instance()->getState(AudioMsgId::Type::Voice); @@ -655,22 +681,24 @@ TextState Document::textState(QPoint point, StateRequest request) const { } } - auto painth = height(); - if (const auto captioned = Get()) { - if (point.y() >= bottom) { - result = TextState(_parent, captioned->_caption.getState( - point - QPoint(st::msgPadding.left(), bottom), - width() - st::msgPadding.left() - st::msgPadding.right(), - request.forText())); - return result; - } - auto captionw = width() - st::msgPadding.left() - st::msgPadding.right(); - painth -= captioned->_caption.countHeight(captionw); - if (isBubbleBottom()) { - painth -= st::msgPadding.bottom(); + auto painth = layout.height(); + if (mode == LayoutMode::Full) { + if (const auto captioned = Get()) { + if (point.y() >= bottom) { + result = TextState(_parent, captioned->_caption.getState( + point - QPoint(st::msgPadding.left(), bottom), + width - st::msgPadding.left() - st::msgPadding.right(), + request.forText())); + return result; + } + auto captionw = width - st::msgPadding.left() - st::msgPadding.right(); + painth -= captioned->_caption.countHeight(captionw); + if (isBubbleBottom()) { + painth -= st::msgPadding.bottom(); + } } } - if (QRect(0, 0, width(), painth).contains(point) + if (QRect(0, 0, width, painth).contains(point) && (!_data->loading() || downloadInCorner()) && !_data->uploading() && !_data->isNull()) { @@ -831,6 +859,37 @@ bool Document::hideForwardedFrom() const { return _data->isSong(); } +QSize Document::sizeForGrouping() const { + const auto height = st::msgFilePadding.top() + + st::msgFileSize + + st::msgFilePadding.bottom(); + return { maxWidth(), height }; +} + +void Document::drawGrouped( + Painter &p, + const QRect &clip, + TextSelection selection, + crl::time ms, + const QRect &geometry, + RectParts sides, + RectParts corners, + not_null cacheKey, + not_null cache) const { + p.translate(geometry.topLeft()); + draw(p, geometry.width(), selection, ms, LayoutMode::Grouped); + p.translate(-geometry.topLeft()); +} + +TextState Document::getStateGrouped( + const QRect &geometry, + RectParts sides, + QPoint point, + StateRequest request) const { + point -= geometry.topLeft(); + return textState(point, geometry.size(), request, LayoutMode::Grouped); +} + bool Document::voiceProgressAnimationCallback(crl::time now) { if (anim::Disabled()) { now += (2 * kAudioVoiceMsgUpdateView); diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.h b/Telegram/SourceFiles/history/view/media/history_view_document.h index c024d04c8..5f7dc99ec 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.h +++ b/Telegram/SourceFiles/history/view/media/history_view_document.h @@ -61,6 +61,23 @@ public: QMargins bubbleMargins() const override; bool hideForwardedFrom() const override; + QSize sizeForGrouping() const override; + void drawGrouped( + Painter &p, + const QRect &clip, + TextSelection selection, + crl::time ms, + const QRect &geometry, + RectParts sides, + RectParts corners, + not_null cacheKey, + not_null cache) const override; + TextState getStateGrouped( + const QRect &geometry, + RectParts sides, + QPoint point, + StateRequest request) const override; + bool voiceProgressAnimationCallback(crl::time now); void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; @@ -82,7 +99,22 @@ private: bool showPause = false; int realDuration = 0; }; + enum class LayoutMode { + Full, + Grouped, + }; + void draw( + Painter &p, + int width, + TextSelection selection, + crl::time ms, + LayoutMode mode) const; + [[nodiscard]] TextState textState( + QPoint point, + QSize layout, + StateRequest request, + LayoutMode mode) const; void ensureDataMediaCreated() const; [[nodiscard]] Ui::Text::String createCaption(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index 98be50118..e0a227430 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -45,6 +45,11 @@ enum class MediaInBubbleState { Bottom, }; +struct BubbleSelectionInterval { + int top = 0; + int height = 0; +}; + [[nodiscard]] QString DocumentTimestampLinkBase( not_null document, FullMsgId context); @@ -116,6 +121,12 @@ public: [[nodiscard]] TextSelection unskipSelection( TextSelection selection) const; + [[nodiscard]] virtual auto getBubbleSelectionIntervals( + TextSelection selection) const + -> std::vector { + return {}; + } + // if we press and drag this link should we drag the item [[nodiscard]] virtual bool dragItemByHandler( const ClickHandlerPtr &p) const = 0; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index f724353b1..3e567220b 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -22,6 +22,32 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_history.h" namespace HistoryView { +namespace { + +std::vector LayoutPlaylist( + const std::vector &sizes) { + Expects(!sizes.empty()); + + auto result = std::vector(); + result.reserve(sizes.size()); + const auto width = ranges::max_element( + sizes, + std::less<>(), + &QSize::width)->width(); + auto top = 0; + for (const auto &size : sizes) { + result.push_back({ + .geometry = QRect(0, top, width, size.height()), + .sides = RectPart::Left | RectPart::Right + }); + top += size.height(); + } + result.front().sides |= RectPart::Top; + result.back().sides |= RectPart::Bottom; + return result; +} + +} // namespace GroupedMedia::Part::Part( not_null parent, @@ -39,7 +65,7 @@ GroupedMedia::GroupedMedia( const auto truncated = ranges::view::all( medias ) | ranges::view::transform([](const std::unique_ptr &v) { - return not_null(v.get()); + return v.get(); }) | ranges::view::take(kMaxSize); const auto result = applyGroup(truncated); @@ -66,6 +92,13 @@ GroupedMedia::~GroupedMedia() { base::take(_parts); } +GroupedMedia::Mode GroupedMedia::DetectMode(not_null media) { + const auto document = media->document(); + return (document && document->isSong()) + ? Mode::Playlist + : Mode::Grid; +} + QSize GroupedMedia::countOptimalSize() { if (_caption.hasSkipBlock()) { _caption.updateSkipBlock( @@ -81,11 +114,13 @@ QSize GroupedMedia::countOptimalSize() { sizes.push_back(media->sizeForGrouping()); } - const auto layout = Ui::LayoutMediaGroup( - sizes, - st::historyGroupWidthMax, - st::historyGroupWidthMin, - st::historyGroupSkip); + const auto layout = (_mode == Mode::Grid) + ? Ui::LayoutMediaGroup( + sizes, + st::historyGroupWidthMax, + st::historyGroupWidthMin, + st::historyGroupSkip) + : LayoutPlaylist(sizes); Assert(layout.size() == _parts.size()); auto maxWidth = 0; @@ -313,6 +348,33 @@ TextForMimeData GroupedMedia::selectedText( return _caption.toTextForMimeData(selection); } +auto GroupedMedia::getBubbleSelectionIntervals( + TextSelection selection) const +-> std::vector { + auto result = std::vector(); + for (auto i = 0, count = int(_parts.size()); i != count; ++i) { + const auto &part = _parts[i]; + if (!IsGroupItemSelection(selection, i)) { + continue; + } + const auto &geometry = part.geometry; + if (result.empty() + || (result.back().top + result.back().height + < geometry.top()) + || (result.back().top > geometry.top() + geometry.height())) { + result.push_back({ geometry.top(), geometry.height() }); + } else { + auto &last = result.back(); + const auto newTop = std::min(last.top, geometry.top()); + const auto newHeight = std::max( + last.top + last.height - newTop, + geometry.top() + geometry.height() - newTop); + last = BubbleSelectionInterval{ newTop, newHeight }; + } + } + return result; +} + void GroupedMedia::clickHandlerActiveChanged( const ClickHandlerPtr &p, bool active) { @@ -339,7 +401,15 @@ bool GroupedMedia::applyGroup(const DataMediaRange &medias) { return true; } + auto modeChosen = false; for (const auto media : medias) { + const auto mediaMode = DetectMode(media); + if (!modeChosen) { + _mode = mediaMode; + modeChosen = true; + } else if (mediaMode != _mode) { + continue; + } _parts.push_back(Part(_parent, media)); } if (_parts.empty()) { @@ -449,7 +519,7 @@ bool GroupedMedia::needsBubble() const { } bool GroupedMedia::computeNeedBubble() const { - if (!_caption.isEmpty()) { + if (!_caption.isEmpty() || _mode == Mode::Playlist) { return true; } if (const auto item = _parent->data()) { @@ -467,9 +537,10 @@ bool GroupedMedia::computeNeedBubble() const { } bool GroupedMedia::needInfoDisplay() const { - return (_parent->data()->id < 0 - || _parent->isUnderCursor() - || _parent->isLastAndSelfMessage()); + return (_mode != Mode::Playlist) + && (_parent->data()->id < 0 + || _parent->isUnderCursor() + || _parent->isLastAndSelfMessage()); } } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h index 92e7f33aa..3e1185c42 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h @@ -60,6 +60,9 @@ public: TextForMimeData selectedText(TextSelection selection) const override; + std::vector getBubbleSelectionIntervals( + TextSelection selection) const override; + void clickHandlerActiveChanged( const ClickHandlerPtr &p, bool active) override; @@ -76,12 +79,14 @@ public: HistoryMessageEdited *displayedEditBadge() const override; bool skipBubbleTail() const override { - return isRoundedInBubbleBottom() && _caption.isEmpty(); + return (_mode == Mode::Grid) + && isRoundedInBubbleBottom() + && _caption.isEmpty(); } void updateNeedBubbleState() override; bool needsBubble() const override; bool customInfoLayout() const override { - return _caption.isEmpty(); + return _caption.isEmpty() && (_mode != Mode::Playlist); } bool allowsFastShare() const override { return true; @@ -95,6 +100,10 @@ public: void parentTextUpdated() override; private: + enum class Mode : char { + Grid, + Playlist, + }; struct Part { Part( not_null parent, @@ -111,6 +120,8 @@ private: }; + [[nodiscard]] static Mode DetectMode(not_null media); + template bool applyGroup(const DataMediaRange &medias); @@ -131,6 +142,7 @@ private: Ui::Text::String _caption; std::vector _parts; + Mode _mode = Mode::Grid; bool _needBubble = false; }; From ec35e3f081091514eb26dbbe8a71e0b4f455da05 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 9 Oct 2020 15:56:33 +0300 Subject: [PATCH 078/190] Track multiple pinned messages in MessagesList. --- Telegram/CMakeLists.txt | 2 + Telegram/SourceFiles/api/api_updates.cpp | 43 +++++++++ Telegram/SourceFiles/boxes/confirm_box.cpp | 10 ++- Telegram/SourceFiles/boxes/confirm_box.h | 3 +- Telegram/SourceFiles/data/data_channel.cpp | 8 +- Telegram/SourceFiles/data/data_chat.cpp | 4 +- Telegram/SourceFiles/data/data_messages.cpp | 84 +++++++++++++++--- Telegram/SourceFiles/data/data_messages.h | 73 +++++++-------- Telegram/SourceFiles/data/data_peer.cpp | 78 ++++++++++++++-- Telegram/SourceFiles/data/data_peer.h | 20 +++-- .../SourceFiles/data/data_pinned_messages.cpp | 88 +++++++++++++++++++ .../SourceFiles/data/data_pinned_messages.h | 41 +++++++++ Telegram/SourceFiles/data/data_user.cpp | 4 +- Telegram/SourceFiles/history/history.cpp | 22 +++-- Telegram/SourceFiles/history/history_item.cpp | 19 ++-- Telegram/SourceFiles/history/history_item.h | 5 +- .../SourceFiles/history/history_widget.cpp | 23 ++--- Telegram/SourceFiles/history/history_widget.h | 2 +- .../view/history_view_replies_section.cpp | 13 +-- 19 files changed, 435 insertions(+), 107 deletions(-) create mode 100644 Telegram/SourceFiles/data/data_pinned_messages.cpp create mode 100644 Telegram/SourceFiles/data/data_pinned_messages.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 51d5689ac..8d6715541 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -435,6 +435,8 @@ PRIVATE data/data_photo.h data/data_photo_media.cpp data/data_photo_media.h + data/data_pinned_messages.cpp + data/data_pinned_messages.h data/data_poll.cpp data/data_poll.h data/data_pts_waiter.cpp diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 7865403d6..4b853a1e2 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -1083,6 +1083,17 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) { _session->data().updateEditedMessage(d.vmessage()); } break; + case mtpc_updatePinnedChannelMessages: { + const auto &d = update.c_updatePinnedChannelMessages(); + const auto channelId = d.vchannel_id().v; + for (const auto &msgId : d.vmessages().v) { + const auto item = session().data().message(channelId, msgId.v); + if (item) { + item->setIsPinned(d.is_pinned()); + } + } + } break; + case mtpc_updateEditMessage: { auto &d = update.c_updateEditMessage(); _session->data().updateEditedMessage(d.vmessage()); @@ -1098,6 +1109,17 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) { _session->data().processMessagesDeleted(d.vchannel_id().v, d.vmessages().v); } break; + case mtpc_updatePinnedMessages: { + const auto &d = update.c_updatePinnedMessages(); + const auto peerId = peerFromMTP(d.vpeer()); + for (const auto &msgId : d.vmessages().v) { + const auto item = session().data().message(0, msgId.v); + if (item) { + item->setIsPinned(d.is_pinned()); + } + } + } break; + default: Unexpected("Type in applyUpdateNoPtsCheck()"); } } @@ -1393,6 +1415,21 @@ void Updates::feedUpdate(const MTPUpdate &update) { } } break; + case mtpc_updatePinnedChannelMessages: { + auto &d = update.c_updatePinnedChannelMessages(); + auto channel = session().data().channelLoaded(d.vchannel_id().v); + + if (channel && !_handlingChannelDifference) { + if (channel->ptsRequesting()) { // skip global updates while getting channel difference + return; + } else { + channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update); + } + } else { + applyUpdateNoPtsCheck(update); + } + } break; + // Messages being read. case mtpc_updateReadHistoryInbox: { auto &d = update.c_updateReadHistoryInbox(); @@ -1998,6 +2035,12 @@ void Updates::feedUpdate(const MTPUpdate &update) { } } break; + // Pinned message. + case mtpc_updatePinnedMessages: { + const auto &d = update.c_updatePinnedMessages(); + updateAndApply(d.vpts().v, d.vpts_count().v, update); + } break; + ////// Cloud sticker sets case mtpc_updateNewStickerSet: { const auto &d = update.c_updateNewStickerSet(); diff --git a/Telegram/SourceFiles/boxes/confirm_box.cpp b/Telegram/SourceFiles/boxes/confirm_box.cpp index fb13a8d5d..a534f0400 100644 --- a/Telegram/SourceFiles/boxes/confirm_box.cpp +++ b/Telegram/SourceFiles/boxes/confirm_box.cpp @@ -444,14 +444,20 @@ PinMessageBox::PinMessageBox( : _peer(peer) , _api(&peer->session().mtp()) , _msgId(msgId) -, _text(this, tr::lng_pinned_pin_sure(tr::now), st::boxLabel) { +, _pinningOld(msgId < peer->topPinnedMessageId()) +, _text( + this, + (_pinningOld + ? "Do you want to pin an older message while leaving a more recent one pinned?" // #TODO pinned + : tr::lng_pinned_pin_sure(tr::now)), + st::boxLabel) { } void PinMessageBox::prepare() { addButton(tr::lng_pinned_pin(), [this] { pinMessage(); }); addButton(tr::lng_cancel(), [this] { closeBox(); }); - if (_peer->isChat() || _peer->isMegagroup()) { + if (!_pinningOld && (_peer->isChat() || _peer->isMegagroup())) { _notify.create(this, tr::lng_pinned_notify(tr::now), true, st::defaultBoxCheckbox); } diff --git a/Telegram/SourceFiles/boxes/confirm_box.h b/Telegram/SourceFiles/boxes/confirm_box.h index 3b8f80e05..585d8ec52 100644 --- a/Telegram/SourceFiles/boxes/confirm_box.h +++ b/Telegram/SourceFiles/boxes/confirm_box.h @@ -183,7 +183,8 @@ private: const not_null _peer; MTP::Sender _api; - MsgId _msgId; + MsgId _msgId = 0; + bool _pinningOld = false; object_ptr _text; object_ptr _notify = { nullptr }; diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 6164c94d6..1af259a67 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -383,9 +383,7 @@ void ChannelData::setUnavailableReasons( void ChannelData::setAvailableMinId(MsgId availableMinId) { if (_availableMinId != availableMinId) { _availableMinId = availableMinId; - if (pinnedMessageId() <= _availableMinId) { - clearPinnedMessage(); - } + clearPinnedMessages(_availableMinId + 1); } } @@ -772,9 +770,9 @@ void ApplyChannelUpdate( } } if (const auto pinned = update.vpinned_msg_id()) { - channel->setPinnedMessageId(pinned->v); + channel->setTopPinnedMessageId(pinned->v); } else { - channel->clearPinnedMessage(); + channel->clearPinnedMessages(); } if (channel->isMegagroup()) { const auto stickerSet = update.vstickerset(); diff --git a/Telegram/SourceFiles/data/data_chat.cpp b/Telegram/SourceFiles/data/data_chat.cpp index 5cabfff25..37930f103 100644 --- a/Telegram/SourceFiles/data/data_chat.cpp +++ b/Telegram/SourceFiles/data/data_chat.cpp @@ -333,9 +333,9 @@ void ApplyChatUpdate(not_null chat, const MTPDchatFull &update) { return QString(); })); if (const auto pinned = update.vpinned_msg_id()) { - chat->setPinnedMessageId(pinned->v); + chat->setTopPinnedMessageId(pinned->v); } else { - chat->clearPinnedMessage(); + chat->clearPinnedMessages(); } chat->checkFolder(update.vfolder_id().value_or_empty()); chat->fullUpdated(); diff --git a/Telegram/SourceFiles/data/data_messages.cpp b/Telegram/SourceFiles/data/data_messages.cpp index 857442270..65ef717c9 100644 --- a/Telegram/SourceFiles/data/data_messages.cpp +++ b/Telegram/SourceFiles/data/data_messages.cpp @@ -117,6 +117,11 @@ void MessagesList::addRange( _sliceUpdated.fire(std::move(update)); } +void MessagesList::addOne(MessagePosition messageId) { + auto range = { messageId }; + addRange(range, { messageId, messageId }, std::nullopt, true); +} + void MessagesList::addNew(MessagePosition messageId) { auto range = { messageId }; addRange(range, { messageId, MaxMessagePosition }, std::nullopt, true); @@ -165,6 +170,31 @@ void MessagesList::removeAll(ChannelId channelId) { } } +void MessagesList::removeLessThan(MessagePosition messageId) { + auto removed = 0; + for (auto i = begin(_slices); i != end(_slices);) { + if (i->range.till <= messageId) { + removed += i->messages.size(); + i = _slices.erase(i); + continue; + } else if (i->range.from <= messageId) { + _slices.modify(i, [&](Slice &slice) { + slice.range.from = MinMessagePosition; + auto from = begin(slice.messages); + auto till = ranges::lower_bound(slice.messages, messageId); + if (from != till) { + removed += till - from; + slice.messages.erase(from, till); + } + }); + break; + } + } + if (removed && _count) { + *_count -= removed; + } +} + void MessagesList::invalidate() { _slices.clear(); _count = std::nullopt; @@ -184,19 +214,26 @@ void MessagesList::invalidateBottom() { _count = std::nullopt; } +MessagesResult MessagesList::queryCurrent(const MessagesQuery &query) const { + if (!query.aroundId) { + return MessagesResult(); + } + const auto slice = ranges::lower_bound( + _slices, + query.aroundId, + std::less<>(), + [](const Slice &slice) { return slice.range.till; }); + return (slice != _slices.end() && slice->range.from <= query.aroundId) + ? queryFromSlice(query, *slice) + : MessagesResult(); +} + rpl::producer MessagesList::query( MessagesQuery &&query) const { return [this, query = std::move(query)](auto consumer) { - auto slice = query.aroundId - ? ranges::lower_bound( - _slices, - query.aroundId, - std::less<>(), - [](const Slice &slice) { return slice.range.till; }) - : _slices.end(); - if (slice != _slices.end() - && slice->range.from <= query.aroundId) { - consumer.put_next(queryFromSlice(query, *slice)); + auto current = queryCurrent(query); + if (current.count.has_value() || !current.messageIds.empty()) { + consumer.put_next(std::move(current)); } consumer.put_done(); return rpl::lifetime(); @@ -207,6 +244,31 @@ rpl::producer MessagesList::sliceUpdated() const { return _sliceUpdated.events(); } +MessagesResult MessagesList::snapshot(MessagesQuery &&query) const { + return queryCurrent(query); +} + +bool MessagesList::empty() const { + for (const auto &slice : _slices) { + if (!slice.messages.empty()) { + return false; + } + } + return true; +} + +rpl::producer MessagesList::viewer( + MessagesQuery &&query) const { + auto copy = query; + return rpl::single( + queryCurrent(query) + ) | rpl::then(sliceUpdated() | rpl::map([=] { + return queryCurrent(query); + })) | rpl::filter([=](const MessagesResult &value) { + return value.count.has_value() || !value.messageIds.empty(); + }); +} + MessagesResult MessagesList::queryFromSlice( const MessagesQuery &query, const Slice &slice) const { @@ -372,7 +434,7 @@ void MessagesSliceBuilder::mergeSliceData( if (count) { _fullCount = count; } - const auto impossible = MessagePosition(-1, FullMsgId()); + const auto impossible = MessagePosition{ .fullId = {}, .date = -1 }; auto wasMinId = _ids.empty() ? impossible : _ids.front(); auto wasMaxId = _ids.empty() ? impossible : _ids.back(); _ids.merge(messageIds.begin(), messageIds.end()); diff --git a/Telegram/SourceFiles/data/data_messages.h b/Telegram/SourceFiles/data/data_messages.h index 083baf641..baf73af75 100644 --- a/Telegram/SourceFiles/data/data_messages.h +++ b/Telegram/SourceFiles/data/data_messages.h @@ -16,11 +16,8 @@ enum class LoadDirection : char { }; struct MessagePosition { - constexpr MessagePosition() = default; - constexpr MessagePosition(TimeId date, FullMsgId fullId) - : fullId(fullId) - , date(date) { - } + FullMsgId fullId; + TimeId date = 0; explicit operator bool() const { return (fullId.msg != 0); @@ -50,18 +47,11 @@ struct MessagePosition { inline constexpr bool operator!=(const MessagePosition &other) const { return !(*this == other); } - - FullMsgId fullId; - TimeId date = 0; - }; struct MessagesRange { - constexpr MessagesRange() = default; - constexpr MessagesRange(MessagePosition from, MessagePosition till) - : from(from) - , till(till) { - } + MessagePosition from; + MessagePosition till; inline constexpr bool operator==(const MessagesRange &other) const { return (from == other.from) @@ -70,26 +60,26 @@ struct MessagesRange { inline constexpr bool operator!=(const MessagesRange &other) const { return !(*this == other); } - - MessagePosition from; - MessagePosition till; - }; constexpr auto MinDate = TimeId(0); constexpr auto MaxDate = std::numeric_limits::max(); -constexpr auto MinMessagePosition = MessagePosition( - MinDate, - FullMsgId(NoChannel, 1)); -constexpr auto MaxMessagePosition = MessagePosition( - MaxDate, - FullMsgId(NoChannel, ServerMaxMsgId - 1)); -constexpr auto FullMessagesRange = MessagesRange( - MinMessagePosition, - MaxMessagePosition); -constexpr auto UnreadMessagePosition = MessagePosition( - MinDate, - FullMsgId(NoChannel, ShowAtUnreadMsgId)); +constexpr auto MinMessagePosition = MessagePosition{ + .fullId = FullMsgId(NoChannel, 1), + .date = MinDate, +}; +constexpr auto MaxMessagePosition = MessagePosition{ + .fullId = FullMsgId(NoChannel, ServerMaxMsgId - 1), + .date = MaxDate, +}; +constexpr auto FullMessagesRange = MessagesRange{ + .from = MinMessagePosition, + .till = MaxMessagePosition, +}; +constexpr auto UnreadMessagePosition = MessagePosition{ + .fullId = FullMsgId(NoChannel, ShowAtUnreadMsgId), + .date = MinDate, +}; struct MessagesSlice { std::vector ids; @@ -100,15 +90,6 @@ struct MessagesSlice { }; struct MessagesQuery { - MessagesQuery( - MessagePosition aroundId, - int limitBefore, - int limitAfter) - : aroundId(aroundId) - , limitBefore(limitBefore) - , limitAfter(limitAfter) { - } - MessagePosition aroundId; int limitBefore = 0; int limitAfter = 0; @@ -129,6 +110,7 @@ struct MessagesSliceUpdate { class MessagesList { public: + void addOne(MessagePosition messageId); void addNew(MessagePosition messageId); void addSlice( std::vector &&messageIds, @@ -136,10 +118,18 @@ public: std::optional count); void removeOne(MessagePosition messageId); void removeAll(ChannelId channelId); + void removeLessThan(MessagePosition messageId); void invalidate(); void invalidateBottom(); - rpl::producer query(MessagesQuery &&query) const; - rpl::producer sliceUpdated() const; + [[nodiscard]] rpl::producer query( + MessagesQuery &&query) const; + [[nodiscard]] rpl::producer sliceUpdated() const; + + [[nodiscard]] MessagesResult snapshot(MessagesQuery &&query) const; + [[nodiscard]] rpl::producer viewer( + MessagesQuery &&query) const; + + [[nodiscard]] bool empty() const; private: struct Slice { @@ -183,6 +173,7 @@ private: MessagesResult queryFromSlice( const MessagesQuery &query, const Slice &slice) const; + MessagesResult queryCurrent(const MessagesQuery &query) const; std::optional _count; base::flat_set _slices; diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 59e41781c..3cd7b45d6 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_file_origin.h" #include "data/data_histories.h" +#include "data/data_pinned_messages.h" #include "base/unixtime.h" #include "base/crc32hash.h" #include "lang/lang_keys.h" @@ -460,18 +461,83 @@ bool PeerData::canEditMessagesIndefinitely() const { Unexpected("Peer type in PeerData::canEditMessagesIndefinitely."); } -void PeerData::setPinnedMessageId(MsgId messageId) { +MsgId PeerData::topPinnedMessageId() const { + return _pinnedMessages ? _pinnedMessages->topId() : 0; +} + +void PeerData::ensurePinnedMessagesCreated() { + if (!_pinnedMessages) { + _pinnedMessages = std::make_unique( + peerToChannel(id)); + } +} + +void PeerData::removeEmptyPinnedMessages() { + if (_pinnedMessages && _pinnedMessages->empty()) { + _pinnedMessages = nullptr; + } +} + +bool PeerData::messageIdTooSmall(MsgId messageId) const { + if (const auto channel = asChannel()) { + return (messageId <= channel->availableMinId()); + } + return false; +} + +void PeerData::setTopPinnedMessageId(MsgId messageId) { + if (messageIdTooSmall(messageId)) { + return; + } + ensurePinnedMessagesCreated(); + _pinnedMessages->setTopId(messageId); +} + +void PeerData::clearPinnedMessages(MsgId lessThanId) { + if (!_pinnedMessages) { + return; + } + _pinnedMessages->clearLessThanId(lessThanId); + removeEmptyPinnedMessages(); +} + +void PeerData::addPinnedMessage(MsgId messageId) { + if (messageIdTooSmall(messageId)) { + return; + } + ensurePinnedMessagesCreated(); + _pinnedMessages->add(messageId); +} + +void PeerData::addPinnedSlice( + std::vector &&ids, + MsgId from, + MsgId till) { const auto min = [&] { if (const auto channel = asChannel()) { return channel->availableMinId(); } - return MsgId(0); + return 0; }(); - messageId = (messageId > min) ? messageId : MsgId(0); - if (_pinnedMessageId != messageId) { - _pinnedMessageId = messageId; - session().changes().peerUpdated(this, UpdateFlag::PinnedMessage); + ids.erase( + ranges::remove_if(ids, [&](MsgId id) { return id <= min; }), + end(ids)); + if (from <= min) { + from = 0; } + if (ids.empty() && !_pinnedMessages) { + return; + } + ensurePinnedMessagesCreated(); + _pinnedMessages->add(std::move(ids), from, till, std::nullopt); +} + +void PeerData::removePinnedMessage(MsgId messageId) { + if (!_pinnedMessages) { + return; + } + _pinnedMessages->remove(messageId); + removeEmptyPinnedMessages(); } bool PeerData::canExportChatHistory() const { diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 3712adca9..7aff79a44 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -29,6 +29,7 @@ class Session; namespace Data { class Session; +class PinnedMessages; int PeerColorIndex(PeerId peerId); int PeerColorIndex(int32 bareId); @@ -326,13 +327,12 @@ public: [[nodiscard]] bool canPinMessages() const; [[nodiscard]] bool canEditMessagesIndefinitely() const; - [[nodiscard]] MsgId pinnedMessageId() const { - return _pinnedMessageId; - } - void setPinnedMessageId(MsgId messageId); - void clearPinnedMessage() { - setPinnedMessageId(0); - } + [[nodiscard]] MsgId topPinnedMessageId() const; + void setTopPinnedMessageId(MsgId messageId); + void clearPinnedMessages(MsgId lessThanId = ServerMaxMsgId); + void addPinnedMessage(MsgId messageId); + void addPinnedSlice(std::vector &&ids, MsgId from, MsgId till); + void removePinnedMessage(MsgId messageId); [[nodiscard]] bool canExportChatHistory() const; @@ -411,6 +411,10 @@ private: -> const std::vector &; void setUserpicChecked(PhotoId photoId, const ImageLocation &location); + void ensurePinnedMessagesCreated(); + void removeEmptyPinnedMessages(); + + [[nodiscard]] bool messageIdTooSmall(MsgId messageId) const; static constexpr auto kUnknownPhotoId = PhotoId(0xFFFFFFFFFFFFFFFFULL); @@ -428,7 +432,7 @@ private: base::flat_set _nameFirstLetters; crl::time _lastFullUpdate = 0; - MsgId _pinnedMessageId = 0; + std::unique_ptr _pinnedMessages; Settings _settings = { kSettingsUnknown }; BlockStatus _blockStatus = BlockStatus::Unknown; diff --git a/Telegram/SourceFiles/data/data_pinned_messages.cpp b/Telegram/SourceFiles/data/data_pinned_messages.cpp new file mode 100644 index 000000000..ad7037e85 --- /dev/null +++ b/Telegram/SourceFiles/data/data_pinned_messages.cpp @@ -0,0 +1,88 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "data/data_pinned_messages.h" + +namespace Data { + +PinnedMessages::PinnedMessages(ChannelId channelId) : _channelId(channelId) { +} + +bool PinnedMessages::empty() const { + return _list.empty(); +} + +MsgId PinnedMessages::topId() const { + const auto slice = _list.snapshot(MessagesQuery{ + .aroundId = MaxMessagePosition, + .limitBefore = 1, + .limitAfter = 1 + }); + return slice.messageIds.empty() ? 0 : slice.messageIds.back().fullId.msg; +} + +MessagePosition PinnedMessages::position(MsgId id) const { + return MessagePosition{ + .fullId = FullMsgId(_channelId, id), + }; +} + +void PinnedMessages::add(MsgId messageId) { + _list.addOne(position(messageId)); +} + +void PinnedMessages::add( + std::vector &&ids, + MsgId from, + MsgId till, + std::optional count) { + auto positions = ids | ranges::view::transform([&](MsgId id) { + return position(id); + }) | ranges::to_vector; + + _list.addSlice( + std::move(positions), + MessagesRange{ + .from = from ? position(from) : MinMessagePosition, + .till = position(till) + }, + count); +} + +void PinnedMessages::remove(MsgId messageId) { + _list.removeOne(MessagePosition{ + .fullId = FullMsgId(0, messageId), + }); +} + +void PinnedMessages::setTopId(MsgId messageId) { + while (true) { + auto top = topId(); + if (top > messageId) { + remove(top); + } else if (top == messageId) { + return; + } else { + break; + } + } + const auto position = MessagePosition{ + .fullId = FullMsgId(0, messageId), + }; + _list.addSlice( + { position }, + { .from = position, .till = MaxMessagePosition }, + std::nullopt); +} + +void PinnedMessages::clearLessThanId(MsgId messageId) { + _list.removeLessThan(MessagePosition{ + .fullId = FullMsgId(0, messageId), + }); +} + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_pinned_messages.h b/Telegram/SourceFiles/data/data_pinned_messages.h new file mode 100644 index 000000000..7f825a343 --- /dev/null +++ b/Telegram/SourceFiles/data/data_pinned_messages.h @@ -0,0 +1,41 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "data/data_messages.h" + +namespace Data { + +class PinnedMessages final { +public: + explicit PinnedMessages(ChannelId channelId); + + [[nodiscard]] bool empty() const; + [[nodiscard]] MsgId topId() const; + + void add(MsgId messageId); + void add( + std::vector &&ids, + MsgId from, + MsgId till, + std::optional count); + void remove(MsgId messageId); + + void setTopId(MsgId messageId); + + void clearLessThanId(MsgId messageId); + +private: + [[nodiscard]] MessagePosition position(MsgId id) const; + + MessagesList _list; + ChannelId _channelId = 0; + +}; + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index 6c36ea279..84b3b510a 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -262,9 +262,9 @@ void ApplyUserUpdate(not_null user, const MTPDuserFull &update) { user->setBotInfoVersion(-1); } if (const auto pinned = update.vpinned_msg_id()) { - user->setPinnedMessageId(pinned->v); + user->setTopPinnedMessageId(pinned->v); } else { - user->clearPinnedMessage(); + user->clearPinnedMessages(); } user->setFullFlags(update.vflags().v); user->setIsBlocked(update.is_blocked()); diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 7c625f9e7..814252ba2 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -177,9 +177,7 @@ void History::itemVanished(not_null item) { && unreadCount() > 0) { setUnreadCount(unreadCount() - 1); } - if (peer->pinnedMessageId() == item->id) { - peer->clearPinnedMessage(); - } + peer->removePinnedMessage(item->id); } void History::setLocalDraft(std::unique_ptr &&draft) { @@ -709,6 +707,9 @@ not_null History::addNewToBack( item->id, { from, till })); } + if (item->isPinned()) { + item->history()->peer->addPinnedMessage(item->id); + } } if (item->from()->id) { if (auto user = item->from()->asUser()) { @@ -1005,8 +1006,8 @@ void History::applyServiceChanges( if (const auto replyTo = data.vreply_to()) { replyTo->match([&](const MTPDmessageReplyHeader &data) { if (item) { - item->history()->peer->setPinnedMessageId( - data.vreply_to_msg_id().v); + //item->history()->peer->setTopPinnedMessageId( // #TODO pinned + // data.vreply_to_msg_id().v); } }); } @@ -1160,6 +1161,7 @@ void History::addEdgesToSharedMedia() { {}, { from, till })); } + peer->addPinnedSlice({}, from, till); } void History::addOlderSlice(const QVector &slice) { @@ -1323,6 +1325,7 @@ void History::checkAddAllToUnreadMentions() { void History::addToSharedMedia( const std::vector> &items) { + auto pinned = std::vector(); std::vector medias[Storage::kSharedMediaTypeCount]; for (const auto item : items) { if (const auto types = item->sharedMediaTypes()) { @@ -1336,6 +1339,12 @@ void History::addToSharedMedia( } } } + if (item->isPinned()) { + if (pinned.empty()) { + pinned.reserve(items.size()); + } + pinned.push_back(item->id); + } } const auto from = loadedAtTop() ? 0 : minMsgId(); const auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId(); @@ -1349,6 +1358,7 @@ void History::addToSharedMedia( { from, till })); } } + peer->addPinnedSlice(std::move(pinned), from, till); } void History::calculateFirstUnreadMessage() { @@ -3017,7 +3027,7 @@ void History::clear(ClearType type) { clearSharedMedia(); clearLastKeyboard(); if (const auto channel = peer->asChannel()) { - channel->clearPinnedMessage(); + channel->clearPinnedMessages(); //if (const auto feed = channel->feed()) { // #feed // // Should be after resetting the _lastMessage. // feed->historyCleared(this); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index ad038352a..e75ed612c 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -347,6 +347,16 @@ void HistoryItem::markMediaRead() { } } +void HistoryItem::setIsPinned(bool pinned) { + if (pinned) { + _flags |= MTPDmessage::Flag::f_pinned; + history()->peer->addPinnedMessage(id); + } else { + _flags &= ~MTPDmessage::Flag::f_pinned; + history()->peer->removePinnedMessage(id); + } +} + bool HistoryItem::definesReplyKeyboard() const { if (const auto markup = Get()) { if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) { @@ -476,6 +486,9 @@ void HistoryItem::indexAsNewItem() { types, id)); } + if (isPinned()) { + _history->peer->setTopPinnedMessageId(id); + } //if (const auto channel = history()->peer->asChannel()) { // #feed // if (const auto feed = channel->feed()) { // _history->session().storage().add(Storage::FeedMessagesAddNew( @@ -509,10 +522,6 @@ void HistoryItem::setRealId(MsgId newId) { _history->owner().requestItemRepaint(this); } -bool HistoryItem::isPinned() const { - return (_history->peer->pinnedMessageId() == id); -} - bool HistoryItem::canPin() const { if (id < 0 || !toHistoryMessage()) { return false; @@ -656,7 +665,7 @@ ChannelId HistoryItem::channelId() const { } Data::MessagePosition HistoryItem::position() const { - return Data::MessagePosition(date(), fullId()); + return { .fullId = fullId(), .date = date() }; } MsgId HistoryItem::replyToId() const { diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 82b99f50c..280afbed2 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -113,6 +113,9 @@ public: [[nodiscard]] bool out() const { return _flags & MTPDmessage::Flag::f_out; } + [[nodiscard]] bool isPinned() const { + return _flags & MTPDmessage::Flag::f_pinned; + } [[nodiscard]] bool unread() const; [[nodiscard]] bool showNotification() const; void markClientSideAsRead(); @@ -121,6 +124,7 @@ public: [[nodiscard]] bool isUnreadMedia() const; [[nodiscard]] bool hasUnreadMediaFlag() const; void markMediaRead(); + void setIsPinned(bool isPinned); // For edit media in history_message. virtual void returnSavedMedia() {}; @@ -312,7 +316,6 @@ public: return _text.isEmpty(); } - [[nodiscard]] bool isPinned() const; [[nodiscard]] bool canPin() const; [[nodiscard]] bool canStopPoll() const; [[nodiscard]] virtual bool allowsSendNow() const; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 960238c68..4700db368 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -5200,6 +5200,7 @@ void HistoryWidget::updatePinnedBar(bool force) { if (!_pinnedBar) { return; } + const auto messageId = _pinnedBar->msgId; if (!force) { if (_pinnedBar->msg) { return; @@ -5208,7 +5209,9 @@ void HistoryWidget::updatePinnedBar(bool force) { Assert(_history != nullptr); if (!_pinnedBar->msg) { - _pinnedBar->msg = session().data().message(_history->channelId(), _pinnedBar->msgId); + _pinnedBar->msg = session().data().message( + _history->channelId(), + messageId); } if (_pinnedBar->msg) { _pinnedBar->text.setText( @@ -5217,17 +5220,15 @@ void HistoryWidget::updatePinnedBar(bool force) { Ui::DialogTextOptions()); update(); } else if (force) { - if (auto channel = _peer ? _peer->asChannel() : nullptr) { - channel->clearPinnedMessage(); - } destroyPinnedBar(); + _history->peer->removePinnedMessage(messageId); updateControlsGeometry(); } } bool HistoryWidget::pinnedMsgVisibilityUpdated() { auto result = false; - auto pinnedId = _peer->pinnedMessageId(); + auto pinnedId = _peer->topPinnedMessageId(); if (pinnedId && !_peer->canPinMessages()) { const auto hiddenId = session().settings().hiddenPinnedMessageId( _peer->id); @@ -5540,23 +5541,23 @@ void HistoryWidget::unpinMessage(FullMsgId itemId) { if (!_peer) { return; } - UnpinMessage(_peer); + UnpinMessage(_peer, itemId.msg); } -void HistoryWidget::UnpinMessage(not_null peer) { +void HistoryWidget::UnpinMessage(not_null peer, MsgId msgId) { if (!peer) { return; } const auto session = &peer->session(); Ui::show(Box(tr::lng_pinned_unpin_sure(tr::now), tr::lng_pinned_unpin(tr::now), crl::guard(session, [=] { - peer->clearPinnedMessage(); + peer->removePinnedMessage(msgId); Ui::hideLayer(); session->api().request(MTPmessages_UpdatePinnedMessage( - MTP_flags(0), + MTP_flags(MTPmessages_UpdatePinnedMessage::Flag::f_unpin), peer->input, - MTP_int(0) + MTP_int(msgId) )).done([=](const MTPUpdates &result) { session->api().applyUpdates(result); }).send(); @@ -5564,7 +5565,7 @@ void HistoryWidget::UnpinMessage(not_null peer) { } void HistoryWidget::hidePinnedMessage() { - const auto pinnedId = _peer ? _peer->pinnedMessageId() : MsgId(0); + const auto pinnedId = _peer ? _peer->topPinnedMessageId() : MsgId(0); if (!pinnedId) { if (pinnedMsgVisibilityUpdated()) { updateControlsGeometry(); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 759b5a0fc..12f943212 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -530,7 +530,7 @@ private: void addMessagesToFront(PeerData *peer, const QVector &messages); void addMessagesToBack(PeerData *peer, const QVector &messages); - static void UnpinMessage(not_null peer); + static void UnpinMessage(not_null peer, MsgId msgId); void updateHistoryGeometry(bool initial = false, bool loadedDown = false, const ScrollChange &change = { ScrollChangeNone, 0 }); void updateListSize(); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 26d9b0c4f..f5db39172 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -85,8 +85,9 @@ RepliesMemento::RepliesMemento( : RepliesMemento(commentsItem->history(), commentsItem->id, commentId) { if (commentId) { _list.setAroundPosition({ - TimeId(0), - FullMsgId(commentsItem->history()->channelId(), commentId) + .fullId = FullMsgId(commentsItem->history()->channelId(), commentId), + .date = TimeId(0), + }); } else if (commentsItem->computeRepliesInboxReadTillFull() == MsgId(1)) { _list.setAroundPosition(Data::MinMessagePosition); @@ -1430,7 +1431,9 @@ bool RepliesWidget::showMessage( } return nullptr; }(); - showAtPosition(Data::MessagePosition(message->date(), id), originItem); + showAtPosition( + Data::MessagePosition{ .fullId = id, .date = message->date() }, + originItem); return true; } @@ -1479,8 +1482,8 @@ void RepliesWidget::restoreState(not_null memento) { _inner->restoreState(memento->list()); if (const auto highlight = memento->getHighlightId()) { const auto position = Data::MessagePosition{ - TimeId(0), - FullMsgId(_history->channelId(), highlight) + .fullId = FullMsgId(_history->channelId(), highlight), + .date = TimeId(0), }; _inner->showAroundPosition(position, [=] { return showAtPositionNow(position, nullptr); From b9f40e35cd174809c61bcb575eb0fca577ee0b60 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 9 Oct 2020 19:23:53 +0300 Subject: [PATCH 079/190] Switch between pinned messages in chat. --- Telegram/CMakeLists.txt | 2 + Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/data/data_peer.cpp | 12 +- Telegram/SourceFiles/data/data_peer.h | 9 +- .../SourceFiles/data/data_pinned_messages.cpp | 42 +-- .../SourceFiles/data/data_pinned_messages.h | 16 +- Telegram/SourceFiles/history/history.cpp | 61 +++-- Telegram/SourceFiles/history/history.h | 4 +- .../SourceFiles/history/history_widget.cpp | 83 +++++- Telegram/SourceFiles/history/history_widget.h | 6 + .../view/history_view_pinned_tracker.cpp | 247 ++++++++++++++++++ .../view/history_view_pinned_tracker.h | 52 ++++ 12 files changed, 466 insertions(+), 69 deletions(-) create mode 100644 Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp create mode 100644 Telegram/SourceFiles/history/view/history_view_pinned_tracker.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 8d6715541..fed5b1ce2 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -564,6 +564,8 @@ PRIVATE history/view/history_view_message.cpp history/view/history_view_message.h history/view/history_view_object.h + history/view/history_view_pinned_tracker.cpp + history/view/history_view_pinned_tracker.h history/view/history_view_replies_section.cpp history/view/history_view_replies_section.h history/view/history_view_schedule_box.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 78d54b851..8cfd5b8cb 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -161,6 +161,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_deleted" = "Deleted Account"; "lng_deleted_message" = "Deleted message"; "lng_pinned_message" = "Pinned message"; +"lng_pinned_previous" = "Previous message"; "lng_pinned_poll" = "Pinned poll"; "lng_pinned_quiz" = "Pinned quiz"; "lng_pinned_unpin_sure" = "Would you like to unpin this message?"; diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 3cd7b45d6..27347867c 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -469,12 +469,14 @@ void PeerData::ensurePinnedMessagesCreated() { if (!_pinnedMessages) { _pinnedMessages = std::make_unique( peerToChannel(id)); + session().changes().peerUpdated(this, UpdateFlag::PinnedMessage); } } void PeerData::removeEmptyPinnedMessages() { if (_pinnedMessages && _pinnedMessages->empty()) { _pinnedMessages = nullptr; + session().changes().peerUpdated(this, UpdateFlag::PinnedMessage); } } @@ -511,8 +513,8 @@ void PeerData::addPinnedMessage(MsgId messageId) { void PeerData::addPinnedSlice( std::vector &&ids, - MsgId from, - MsgId till) { + MsgRange noSkipRange, + std::optional count) { const auto min = [&] { if (const auto channel = asChannel()) { return channel->availableMinId(); @@ -522,14 +524,14 @@ void PeerData::addPinnedSlice( ids.erase( ranges::remove_if(ids, [&](MsgId id) { return id <= min; }), end(ids)); - if (from <= min) { - from = 0; + if (noSkipRange.from <= min) { + noSkipRange.from = 0; } if (ids.empty() && !_pinnedMessages) { return; } ensurePinnedMessagesCreated(); - _pinnedMessages->add(std::move(ids), from, till, std::nullopt); + _pinnedMessages->add(std::move(ids), noSkipRange, std::nullopt); } void PeerData::removePinnedMessage(MsgId messageId) { diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 7aff79a44..c8bf6eac1 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -30,6 +30,7 @@ namespace Data { class Session; class PinnedMessages; +struct PinnedAroundId; int PeerColorIndex(PeerId peerId); int PeerColorIndex(int32 bareId); @@ -331,8 +332,14 @@ public: void setTopPinnedMessageId(MsgId messageId); void clearPinnedMessages(MsgId lessThanId = ServerMaxMsgId); void addPinnedMessage(MsgId messageId); - void addPinnedSlice(std::vector &&ids, MsgId from, MsgId till); + void addPinnedSlice( + std::vector &&ids, + MsgRange noSkipRange, + std::optional count); void removePinnedMessage(MsgId messageId); + Data::PinnedMessages *currentPinnedMessages() const { + return _pinnedMessages.get(); + } [[nodiscard]] bool canExportChatHistory() const; diff --git a/Telegram/SourceFiles/data/data_pinned_messages.cpp b/Telegram/SourceFiles/data/data_pinned_messages.cpp index ad7037e85..e9fc9a110 100644 --- a/Telegram/SourceFiles/data/data_pinned_messages.cpp +++ b/Telegram/SourceFiles/data/data_pinned_messages.cpp @@ -25,6 +25,25 @@ MsgId PinnedMessages::topId() const { return slice.messageIds.empty() ? 0 : slice.messageIds.back().fullId.msg; } +rpl::producer PinnedMessages::viewer( + MsgId aroundId, + int limit) const { + return _list.viewer(MessagesQuery{ + .aroundId = position(aroundId), + .limitBefore = limit, + .limitAfter = limit + }) | rpl::map([](const MessagesResult &result) { + auto data = PinnedAroundId(); + data.fullCount = result.count; + data.skippedBefore = result.skippedBefore; + data.skippedAfter = result.skippedAfter; + data.ids = result.messageIds | ranges::view::transform( + [](MessagePosition position) { return position.fullId.msg; } + ) | ranges::to_vector; + return data; + }); +} + MessagePosition PinnedMessages::position(MsgId id) const { return MessagePosition{ .fullId = FullMsgId(_channelId, id), @@ -37,8 +56,7 @@ void PinnedMessages::add(MsgId messageId) { void PinnedMessages::add( std::vector &&ids, - MsgId from, - MsgId till, + MsgRange range, std::optional count) { auto positions = ids | ranges::view::transform([&](MsgId id) { return position(id); @@ -47,16 +65,14 @@ void PinnedMessages::add( _list.addSlice( std::move(positions), MessagesRange{ - .from = from ? position(from) : MinMessagePosition, - .till = position(till) + .from = range.from ? position(range.from) : MinMessagePosition, + .till = position(range.till) }, count); } void PinnedMessages::remove(MsgId messageId) { - _list.removeOne(MessagePosition{ - .fullId = FullMsgId(0, messageId), - }); + _list.removeOne(position(messageId)); } void PinnedMessages::setTopId(MsgId messageId) { @@ -70,19 +86,15 @@ void PinnedMessages::setTopId(MsgId messageId) { break; } } - const auto position = MessagePosition{ - .fullId = FullMsgId(0, messageId), - }; + const auto wrapped = position(messageId); _list.addSlice( - { position }, - { .from = position, .till = MaxMessagePosition }, + { wrapped }, + { .from = wrapped, .till = MaxMessagePosition }, std::nullopt); } void PinnedMessages::clearLessThanId(MsgId messageId) { - _list.removeLessThan(MessagePosition{ - .fullId = FullMsgId(0, messageId), - }); + _list.removeLessThan(position(messageId)); } } // namespace Data diff --git a/Telegram/SourceFiles/data/data_pinned_messages.h b/Telegram/SourceFiles/data/data_pinned_messages.h index 7f825a343..33cf120e5 100644 --- a/Telegram/SourceFiles/data/data_pinned_messages.h +++ b/Telegram/SourceFiles/data/data_pinned_messages.h @@ -8,21 +8,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "data/data_messages.h" +#include "base/weak_ptr.h" namespace Data { -class PinnedMessages final { +struct PinnedAroundId { + std::vector ids; + std::optional skippedBefore; + std::optional skippedAfter; + std::optional fullCount; +}; + +class PinnedMessages final : public base::has_weak_ptr { public: explicit PinnedMessages(ChannelId channelId); [[nodiscard]] bool empty() const; [[nodiscard]] MsgId topId() const; + [[nodiscard]] rpl::producer viewer( + MsgId aroundId, + int limit) const; void add(MsgId messageId); void add( std::vector &&ids, - MsgId from, - MsgId till, + MsgRange range, std::optional count); void remove(MsgId messageId); diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 814252ba2..84d8d5a76 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -1161,7 +1161,7 @@ void History::addEdgesToSharedMedia() { {}, { from, till })); } - peer->addPinnedSlice({}, from, till); + peer->addPinnedSlice({}, { from, till }, std::nullopt); } void History::addOlderSlice(const QVector &slice) { @@ -1358,7 +1358,7 @@ void History::addToSharedMedia( { from, till })); } } - peer->addPinnedSlice(std::move(pinned), from, till); + peer->addPinnedSlice(std::move(pinned), { from, till }, std::nullopt); } void History::calculateFirstUnreadMessage() { @@ -1785,16 +1785,19 @@ TimeId History::adjustedChatListTimeId() const { } void History::countScrollState(int top) { - countScrollTopItem(top); - if (scrollTopItem) { - scrollTopOffset = (top - scrollTopItem->block()->y() - scrollTopItem->y()); - } + std::tie(scrollTopItem, scrollTopOffset) = findItemAndOffset(top); } -void History::countScrollTopItem(int top) { +auto History::findItemAndOffset(int top) const -> std::pair { + if (const auto element = findScrollTopItem(top)) { + return { element, (top - element->block()->y() - element->y()) }; + } + return {}; +} + +auto History::findScrollTopItem(int top) const -> Element* { if (isEmpty()) { - forgetScrollState(); - return; + return nullptr; } auto itemIndex = 0; @@ -1813,8 +1816,7 @@ void History::countScrollTopItem(int top) { const auto view = block->messages[itemIndex].get(); itemTop = block->y() + view->y(); if (itemTop <= top) { - scrollTopItem = view; - return; + return view; } } if (--blockIndex >= 0) { @@ -1824,27 +1826,24 @@ void History::countScrollTopItem(int top) { } } while (true); - scrollTopItem = blocks.front()->messages.front().get(); - } else { - // go forward through history while we don't find the last item that starts above - for (auto blocksCount = int(blocks.size()); blockIndex < blocksCount; ++blockIndex) { - const auto &block = blocks[blockIndex]; - for (auto itemsCount = int(block->messages.size()); itemIndex < itemsCount; ++itemIndex) { - itemTop = block->y() + block->messages[itemIndex]->y(); - if (itemTop > top) { - Assert(itemIndex > 0 || blockIndex > 0); - if (itemIndex > 0) { - scrollTopItem = block->messages[itemIndex - 1].get(); - } else { - scrollTopItem = blocks[blockIndex - 1]->messages.back().get(); - } - return; - } - } - itemIndex = 0; - } - scrollTopItem = blocks.back()->messages.back().get(); + return blocks.front()->messages.front().get(); } + // go forward through history while we don't find the last item that starts above + for (auto blocksCount = int(blocks.size()); blockIndex < blocksCount; ++blockIndex) { + const auto &block = blocks[blockIndex]; + for (auto itemsCount = int(block->messages.size()); itemIndex < itemsCount; ++itemIndex) { + itemTop = block->y() + block->messages[itemIndex]->y(); + if (itemTop > top) { + Assert(itemIndex > 0 || blockIndex > 0); + if (itemIndex > 0) { + return block->messages[itemIndex - 1].get(); + } + return blocks[blockIndex - 1]->messages.back().get(); + } + } + itemIndex = 0; + } + return blocks.back()->messages.back().get(); } void History::getNextScrollTopItem(HistoryBlock *block, int32 i) { diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 44718865d..30b09844b 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -366,6 +366,8 @@ public: // of the displayed window relative to the history start coordinate void countScrollState(int top); + [[nodiscard]] std::pair findItemAndOffset(int top) const; + MsgId nextNonHistoryEntryId(); bool folderKnown() const override; @@ -422,7 +424,7 @@ private: void getNextScrollTopItem(HistoryBlock *block, int32 i); // helper method for countScrollState(int top) - void countScrollTopItem(int top); + [[nodiscard]] Element *findScrollTopItem(int top) const; // this method just removes a block from the blocks list // when the last item from this block was detached and diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 4700db368..288a23f89 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -88,6 +88,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "history/view/history_view_top_bar_widget.h" #include "history/view/history_view_contact_status.h" +#include "history/view/history_view_pinned_tracker.h" #include "base/qthelp_regex.h" #include "ui/widgets/popup_menu.h" #include "ui/text_options.h" @@ -1697,7 +1698,8 @@ void HistoryWidget::showHistory( _history->showAtMsgId = _showAtMsgId; destroyUnreadBarOnClose(); - destroyPinnedBar(); + showPinnedMessage(FullMsgId()); + _pinnedTracker = nullptr; _membersDropdown.destroy(); _scrollToAnimation.stop(); @@ -1811,6 +1813,7 @@ void HistoryWidget::showHistory( _updateHistoryItems.stop(); + setupPinnedTracker(); pinnedMsgVisibilityUpdated(); if (_history->scrollTopItem || (_migrated && _migrated->scrollTopItem) @@ -2732,6 +2735,7 @@ void HistoryWidget::delayedShowAt(MsgId showAtMsgId) { void HistoryWidget::onScroll() { preloadHistoryIfNeeded(); visibleAreaUpdated(); + updatePinnedViewer(); if (!_synteticScrollEvent) { _lastUserScrolled = crl::now(); } @@ -3211,6 +3215,7 @@ void HistoryWidget::doneShow() { handlePendingHistoryUpdate(); } preloadHistoryIfNeeded(); + updatePinnedViewer(); checkHistoryActivation(); App::wnd()->setInnerFocus(); } @@ -5226,14 +5231,66 @@ void HistoryWidget::updatePinnedBar(bool force) { } } +void HistoryWidget::updatePinnedViewer() { + if (_firstLoadRequest + || _delayedShowAtRequest + || _scroll->isHidden() + || !_history + || !_historyInited) { + return; + } + const auto [item, offset] = [&] { + auto visibleTop = _scroll->scrollTop(); + if (_migrated + && _history->loadedAtBottom() + && _migrated->loadedAtTop()) { + visibleTop -= _migrated->height(); + } + auto [item, offset] = _history->findItemAndOffset(visibleTop); + while (item && !IsServerMsgId(item->data()->id)) { + offset -= item->height(); + item = item->nextInBlocks(); + } + return std::pair(item, offset); + }(); + const auto last = _history->peer->topPinnedMessageId(); + const auto lessThanId = item + ? (item->data()->id + (offset > 0 ? 1 : 0)) + : (last + 1); + _pinnedTracker->trackAround(lessThanId); +} + +void HistoryWidget::setupPinnedTracker() { + Expects(_history != nullptr); + + _pinnedTracker = std::make_unique(_history); + _pinnedTracker->shownMessageId( + ) | rpl::start_with_next([=](MsgId messageId) { + showPinnedMessage({ peerToChannel(_peer->id), messageId }); + }, _list->lifetime()); +} + +void HistoryWidget::showPinnedMessage(FullMsgId id) { + if (_pinnedId == id) { + return; + } + _pinnedId = id; + if (pinnedMsgVisibilityUpdated()) { + updateHistoryGeometry(); + updateControlsVisibility(); + updateControlsGeometry(); + this->update(); + } +} + bool HistoryWidget::pinnedMsgVisibilityUpdated() { auto result = false; - auto pinnedId = _peer->topPinnedMessageId(); + auto pinnedId = _pinnedId; if (pinnedId && !_peer->canPinMessages()) { const auto hiddenId = session().settings().hiddenPinnedMessageId( _peer->id); - if (hiddenId == pinnedId) { - pinnedId = 0; + if (hiddenId == pinnedId.msg) { + pinnedId = FullMsgId(); } else if (hiddenId) { session().settings().setHiddenPinnedMessageId(_peer->id, 0); session().saveSettings(); @@ -5241,7 +5298,7 @@ bool HistoryWidget::pinnedMsgVisibilityUpdated() { } if (pinnedId) { if (!_pinnedBar) { - _pinnedBar = std::make_unique(pinnedId, this); + _pinnedBar = std::make_unique(pinnedId.msg, this); if (_a_show.animating()) { _pinnedBar->cancel->hide(); _pinnedBar->shadow->hide(); @@ -5261,8 +5318,8 @@ bool HistoryWidget::pinnedMsgVisibilityUpdated() { if (!barTop || _scroll->scrollTop() != *barTop) { synteticScrollToY(_scroll->scrollTop() + st::historyReplyHeight); } - } else if (_pinnedBar->msgId != pinnedId) { - _pinnedBar->msgId = pinnedId; + } else if (_pinnedBar->msgId != pinnedId.msg) { + _pinnedBar->msgId = pinnedId.msg; _pinnedBar->msg = nullptr; _pinnedBar->text.clear(); updatePinnedBar(); @@ -5565,7 +5622,7 @@ void HistoryWidget::UnpinMessage(not_null peer, MsgId msgId) { } void HistoryWidget::hidePinnedMessage() { - const auto pinnedId = _peer ? _peer->topPinnedMessageId() : MsgId(0); + const auto pinnedId = _pinnedId; if (!pinnedId) { if (pinnedMsgVisibilityUpdated()) { updateControlsGeometry(); @@ -5575,11 +5632,9 @@ void HistoryWidget::hidePinnedMessage() { } if (_peer->canPinMessages()) { - unpinMessage(FullMsgId( - _peer->isChannel() ? peerToChannel(_peer->id) : NoChannel, - pinnedId)); + unpinMessage(pinnedId); } else { - session().settings().setHiddenPinnedMessageId(_peer->id, pinnedId); + session().settings().setHiddenPinnedMessageId(_peer->id, pinnedId.msg); session().saveSettings(); if (pinnedMsgVisibilityUpdated()) { updateControlsGeometry(); @@ -6389,7 +6444,9 @@ void HistoryWidget::drawPinnedBar(Painter &p) { p.setPen(st::historyReplyNameFg); p.setFont(st::msgServiceNameFont); const auto poll = media ? media->poll() : nullptr; - const auto pinnedHeader = !poll + const auto pinnedHeader = (_pinnedBar->msgId < _peer->topPinnedMessageId()) + ? tr::lng_pinned_previous(tr::now) + : !poll ? tr::lng_pinned_message(tr::now) : poll->quiz() ? tr::lng_pinned_quiz(tr::now) diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 12f943212..e23a4de00 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -93,6 +93,7 @@ namespace HistoryView { class TopBarWidget; class ContactStatus; class Element; +class PinnedTracker; } // namespace HistoryView class DragArea; @@ -491,9 +492,12 @@ private: void updateReplyEditTexts(bool force = false); void updateReplyEditText(not_null item); + void showPinnedMessage(FullMsgId id); void updatePinnedBar(bool force = false); bool pinnedMsgVisibilityUpdated(); void destroyPinnedBar(); + void updatePinnedViewer(); + void setupPinnedTracker(); void sendInlineResult( not_null result, @@ -610,7 +614,9 @@ private: object_ptr _fieldBarCancel; + FullMsgId _pinnedId; std::unique_ptr _pinnedBar; + std::unique_ptr _pinnedTracker; mtpRequestId _saveEditMsgRequestId = 0; diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp new file mode 100644 index 000000000..ea6f076d7 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp @@ -0,0 +1,247 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/view/history_view_pinned_tracker.h" + +#include "data/data_changes.h" +#include "data/data_pinned_messages.h" +#include "data/data_peer.h" +#include "data/data_channel.h" +#include "data/data_session.h" +#include "data/data_histories.h" +#include "main/main_session.h" +#include "history/history.h" +#include "history/history_item.h" +#include "apiwrap.h" + +namespace HistoryView { +namespace { + +constexpr auto kLoadedLimit = 4; +constexpr auto kPerPage = 40; + +} // namespace + +PinnedTracker::PinnedTracker(not_null history) : _history(history) { + _history->session().changes().peerFlagsValue( + _history->peer, + Data::PeerUpdate::Flag::PinnedMessage + ) | rpl::start_with_next([=] { + refreshData(); + }, _lifetime); +} + +PinnedTracker::~PinnedTracker() { + _history->owner().histories().cancelRequest(_beforeRequestId); + _history->owner().histories().cancelRequest(_afterRequestId); +} + +rpl::producer PinnedTracker::shownMessageId() const { + return _current.value(); +} + +void PinnedTracker::refreshData() { + const auto now = _history->peer->currentPinnedMessages(); + if (!now) { + _dataLifetime.destroy(); + _current = MsgId(0); + } else if (_data.get() != now) { + _dataLifetime.destroy(); + _data = now; + if (_aroundId) { + setupViewer(now); + } + } +} + +void PinnedTracker::trackAround(MsgId messageId) { + if (_aroundId == messageId) { + return; + } + _dataLifetime.destroy(); + _aroundId = messageId; + if (!_aroundId) { + _current = MsgId(0); + } else if (const auto now = _data.get()) { + setupViewer(now); + } +} + +void PinnedTracker::setupViewer(not_null data) { + data->viewer( + _aroundId, + kLoadedLimit + 2 + ) | rpl::start_with_next([=](const Data::PinnedAroundId &snapshot) { + const auto i = ranges::lower_bound(snapshot.ids, _aroundId); + const auto empty = snapshot.ids.empty(); + const auto before = (i - begin(snapshot.ids)); + const auto after = (end(snapshot.ids) - i); + if (before < kLoadedLimit && !snapshot.skippedBefore) { + load( + Data::LoadDirection::Before, + empty ? _aroundId : snapshot.ids.front()); + } + if (after < kLoadedLimit && !snapshot.skippedAfter) { + load( + Data::LoadDirection::After, + empty ? _aroundId : snapshot.ids.back()); + } + if (i != begin(snapshot.ids)) { + _current = *(i - 1); + } else if (snapshot.skippedBefore == 0) { + _current = 0; + } + }, _dataLifetime); +} + +void PinnedTracker::load(Data::LoadDirection direction, MsgId id) { + const auto requestId = (direction == Data::LoadDirection::Before) + ? &_beforeRequestId + : &_afterRequestId; + const auto aroundId = (direction == Data::LoadDirection::Before) + ? &_beforeId + : &_afterId; + if (*requestId) { + if (*aroundId == id) { + return; + } + _history->owner().histories().cancelRequest(*requestId); + } + *aroundId = id; + const auto send = [=](Fn finish) { + const auto offsetId = [&] { + switch (direction) { + case Data::LoadDirection::Before: return id; + case Data::LoadDirection::After: return id + 1; + } + Unexpected("Direction in PinnedTracker::load"); + }(); + const auto addOffset = [&] { + switch (direction) { + case Data::LoadDirection::Before: return 0; + case Data::LoadDirection::After: return -kPerPage; + } + Unexpected("Direction in PinnedTracker::load"); + }(); + return _history->session().api().request(MTPmessages_Search( + MTP_flags(0), + _history->peer->input, + MTP_string(QString()), + MTP_inputPeerEmpty(), + MTPint(), // top_msg_id + MTP_inputMessagesFilterPinned(), + MTP_int(0), + MTP_int(0), + MTP_int(offsetId), + MTP_int(addOffset), + MTP_int(kPerPage), + MTP_int(0), // max_id + MTP_int(0), // min_id + MTP_int(0) // hash + )).done([=](const MTPmessages_Messages &result) { + *aroundId = 0; + *requestId = 0; + finish(); + + apply(direction, id, result); + }).fail([=](const RPCError &error) { + *aroundId = 0; + *requestId = 0; + finish(); + }).send(); + }; + _beforeRequestId = _history->owner().histories().sendRequest( + _history, + Data::Histories::RequestType::History, + send); +} + +void PinnedTracker::apply( + Data::LoadDirection direction, + MsgId aroundId, + const MTPmessages_Messages &result) { + auto noSkipRange = MsgRange{ aroundId, aroundId }; + auto fullCount = std::optional(); + auto messages = [&] { + switch (result.type()) { + case mtpc_messages_messages: { + auto &d = result.c_messages_messages(); + _history->owner().processUsers(d.vusers()); + _history->owner().processChats(d.vchats()); + fullCount = d.vmessages().v.size(); + return &d.vmessages().v; + } break; + + case mtpc_messages_messagesSlice: { + auto &d = result.c_messages_messagesSlice(); + _history->owner().processUsers(d.vusers()); + _history->owner().processChats(d.vchats()); + fullCount = d.vcount().v; + return &d.vmessages().v; + } break; + + case mtpc_messages_channelMessages: { + auto &d = result.c_messages_channelMessages(); + if (auto channel = _history->peer->asChannel()) { + channel->ptsReceived(d.vpts().v); + } else { + LOG(("API Error: received messages.channelMessages when " + "no channel was passed! (PinnedTracker::apply)")); + } + _history->owner().processUsers(d.vusers()); + _history->owner().processChats(d.vchats()); + fullCount = d.vcount().v; + return &d.vmessages().v; + } break; + + case mtpc_messages_messagesNotModified: { + LOG(("API Error: received messages.messagesNotModified! " + "(PinnedTracker::apply)")); + return (const QVector*)nullptr; + } break; + } + Unexpected("messages.Messages type in PinnedTracker::apply."); + }(); + + if (!messages) { + return; + } + + const auto addType = NewMessageType::Existing; + auto list = std::vector(); + list.reserve(messages->size()); + for (const auto &message : *messages) { + const auto item = _history->owner().addNewMessage( + message, + MTPDmessage_ClientFlags(), + addType); + if (item) { + const auto itemId = item->id; + if (item->isPinned()) { + list.push_back(itemId); + } + accumulate_min(noSkipRange.from, itemId); + accumulate_max(noSkipRange.till, itemId); + } + } + if (aroundId && list.empty()) { + noSkipRange = [&]() -> MsgRange { + switch (direction) { + case Data::LoadDirection::Before: // All old loaded. + return { 0, noSkipRange.till }; + case Data::LoadDirection::Around: // All loaded. + return { 0, ServerMaxMsgId }; + case Data::LoadDirection::After: // All new loaded. + return { noSkipRange.from, ServerMaxMsgId }; + } + Unexpected("Direction in PinnedTracker::apply."); + }(); + } + _history->peer->addPinnedSlice(std::move(list), noSkipRange, fullCount); +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_tracker.h b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.h new file mode 100644 index 000000000..e34df6af4 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.h @@ -0,0 +1,52 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +class History; + +namespace Data { +class PinnedMessages; +enum class LoadDirection : char; +} // namespace Data + +namespace HistoryView { + +class PinnedTracker final { +public: + explicit PinnedTracker(not_null history); + ~PinnedTracker(); + + [[nodiscard]] rpl::producer shownMessageId() const; + void trackAround(MsgId messageId); + +private: + void refreshData(); + void setupViewer(not_null data); + void load(Data::LoadDirection direction, MsgId id); + void apply( + Data::LoadDirection direction, + MsgId aroundId, + const MTPmessages_Messages &result); + + const not_null _history; + + base::weak_ptr _data; + rpl::variable _current = MsgId(); + rpl::lifetime _dataLifetime; + + MsgId _aroundId = 0; + MsgId _beforeId = 0; + MsgId _afterId = 0; + MsgId _beforeRequestId = 0; + MsgId _afterRequestId = 0; + + rpl::lifetime _lifetime; + +}; + +} // namespace HistoryView From ae298818a8ae3200a4d4e749ca6af7bfb6903d09 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 10 Oct 2020 12:15:37 +0300 Subject: [PATCH 080/190] Implement message bar with animations. --- Telegram/CMakeLists.txt | 7 +- Telegram/SourceFiles/api/api_sending.cpp | 2 +- Telegram/SourceFiles/apiwrap.cpp | 2 +- Telegram/SourceFiles/app.cpp | 2 +- .../SourceFiles/boxes/add_contact_box.cpp | 2 +- .../boxes/background_preview_box.cpp | 2 +- Telegram/SourceFiles/boxes/connection_box.cpp | 2 +- .../SourceFiles/boxes/edit_caption_box.cpp | 4 +- Telegram/SourceFiles/boxes/language_box.cpp | 2 +- Telegram/SourceFiles/boxes/peer_list_box.cpp | 2 +- .../boxes/peers/edit_participant_box.cpp | 2 +- Telegram/SourceFiles/boxes/send_files_box.cpp | 4 +- Telegram/SourceFiles/boxes/share_box.cpp | 4 +- Telegram/SourceFiles/calls/calls_panel.cpp | 2 +- .../SourceFiles/chat_helpers/bot_keyboard.cpp | 2 +- .../chat_helpers/field_autocomplete.cpp | 2 +- .../chat_helpers/message_field.cpp | 2 +- .../stickers_emoji_image_loader.cpp | 2 +- .../chat_helpers/stickers_emoji_pack.cpp | 2 +- Telegram/SourceFiles/core/application.cpp | 2 +- .../SourceFiles/data/data_media_types.cpp | 2 +- Telegram/SourceFiles/data/data_peer.cpp | 2 +- Telegram/SourceFiles/data/data_poll.cpp | 2 +- Telegram/SourceFiles/data/data_user.cpp | 2 +- .../dialogs/dialogs_inner_widget.cpp | 2 +- .../SourceFiles/dialogs/dialogs_layout.cpp | 2 +- Telegram/SourceFiles/dialogs/dialogs_row.cpp | 2 +- .../SourceFiles/dialogs/dialogs_widget.cpp | 2 +- Telegram/SourceFiles/facades.cpp | 2 +- .../admin_log/history_admin_log_inner.cpp | 2 +- .../admin_log/history_admin_log_section.cpp | 2 +- Telegram/SourceFiles/history/history.cpp | 2 +- .../history/history_inner_widget.cpp | 4 +- Telegram/SourceFiles/history/history_item.cpp | 4 +- .../history/history_item_components.cpp | 4 +- .../SourceFiles/history/history_item_text.cpp | 2 +- .../SourceFiles/history/history_message.cpp | 4 +- .../SourceFiles/history/history_service.cpp | 2 +- .../SourceFiles/history/history_widget.cpp | 4 +- .../view/history_view_compose_controls.cpp | 4 +- .../view/history_view_contact_status.cpp | 2 +- .../history/view/history_view_element.cpp | 2 +- .../history/view/history_view_list_widget.cpp | 2 +- .../history/view/history_view_message.cpp | 2 +- .../view/history_view_replies_section.cpp | 4 +- .../view/history_view_schedule_box.cpp | 2 +- .../view/history_view_scheduled_section.cpp | 4 +- .../history/view/history_view_send_action.cpp | 2 +- .../view/history_view_service_message.cpp | 4 +- .../view/history_view_top_bar_widget.cpp | 2 +- .../history/view/media/history_view_call.cpp | 2 +- .../view/media/history_view_contact.cpp | 4 +- .../history/view/media/history_view_dice.cpp | 2 +- .../view/media/history_view_document.cpp | 2 +- .../history/view/media/history_view_file.cpp | 2 +- .../history/view/media/history_view_game.cpp | 4 +- .../history/view/media/history_view_gif.cpp | 2 +- .../view/media/history_view_invoice.cpp | 4 +- .../view/media/history_view_large_emoji.cpp | 2 +- .../view/media/history_view_location.cpp | 4 +- .../history/view/media/history_view_media.cpp | 4 +- .../view/media/history_view_media_common.cpp | 2 +- .../view/media/history_view_media_grouped.cpp | 4 +- .../media/history_view_media_unwrapped.cpp | 2 +- .../history/view/media/history_view_photo.cpp | 2 +- .../history/view/media/history_view_poll.cpp | 4 +- .../view/media/history_view_sticker.cpp | 2 +- .../media/history_view_theme_document.cpp | 2 +- .../view/media/history_view_web_page.cpp | 4 +- .../inline_bot_layout_internal.cpp | 2 +- Telegram/SourceFiles/mainwidget.cpp | 4 +- .../media/player/media_player_float.cpp | 2 +- .../media/view/media_view_overlay_widget.cpp | 4 +- Telegram/SourceFiles/overview/overview.style | 2 +- .../SourceFiles/overview/overview_layout.cpp | 4 +- .../passport/passport_panel_edit_scans.cpp | 2 +- .../passport/passport_panel_form.cpp | 2 +- .../profile/profile_block_peer_list.cpp | 2 +- .../settings/settings_privacy_controllers.cpp | 2 +- .../SourceFiles/support/support_helper.cpp | 2 +- .../history.style => ui/chat/chat.style} | 18 ++ Telegram/SourceFiles/ui/chat/message_bar.cpp | 295 ++++++++++++++++++ Telegram/SourceFiles/ui/chat/message_bar.h | 88 ++++++ Telegram/SourceFiles/ui/empty_userpic.cpp | 2 +- Telegram/SourceFiles/ui/item_text_options.cpp | 70 +++++ Telegram/SourceFiles/ui/item_text_options.h | 27 ++ Telegram/SourceFiles/ui/special_buttons.cpp | 2 +- .../ui/{ => text}/text_options.cpp | 60 +--- .../SourceFiles/ui/{ => text}/text_options.h | 12 - Telegram/SourceFiles/ui/ui_pch.h | 3 + .../SourceFiles/ui/widgets/multi_select.cpp | 2 +- .../window/notifications_manager_default.cpp | 2 +- .../window/themes/window_theme.cpp | 2 +- .../window/themes/window_theme_preview.cpp | 4 +- .../themes/window_themes_cloud_list.cpp | 2 +- Telegram/SourceFiles/window/window.style | 2 +- .../window/window_history_hider.cpp | 2 +- .../window/window_media_preview.cpp | 2 +- Telegram/cmake/td_ui.cmake | 7 + 99 files changed, 626 insertions(+), 187 deletions(-) rename Telegram/SourceFiles/{history/history.style => ui/chat/chat.style} (98%) create mode 100644 Telegram/SourceFiles/ui/chat/message_bar.cpp create mode 100644 Telegram/SourceFiles/ui/chat/message_bar.h create mode 100644 Telegram/SourceFiles/ui/item_text_options.cpp create mode 100644 Telegram/SourceFiles/ui/item_text_options.h rename Telegram/SourceFiles/ui/{ => text}/text_options.cpp (70%) rename Telegram/SourceFiles/ui/{ => text}/text_options.h (66%) diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index fed5b1ce2..abc87d05b 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -41,9 +41,7 @@ set(style_files boxes/boxes.style calls/calls.style chat_helpers/chat_helpers.style - dialogs/dialogs.style export/view/export.style - history/history.style info/info.style intro/intro.style media/view/media_view.style @@ -53,7 +51,6 @@ set(style_files profile/profile.style settings/settings.style ui/filter_icons.style - window/window.style ) set(dependent_style_files @@ -1026,6 +1023,8 @@ PRIVATE ui/filter_icon_panel.h ui/grouped_layout.cpp ui/grouped_layout.h + ui/item_text_options.cpp + ui/item_text_options.h ui/resize_area.h ui/search_field_controller.cpp ui/search_field_controller.h @@ -1033,8 +1032,6 @@ PRIVATE ui/special_buttons.h ui/special_fields.cpp ui/special_fields.h - ui/text_options.cpp - ui/text_options.h ui/unread_badge.cpp ui/unread_badge.h window/main_window.cpp diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 6d569ad4f..f33429433 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -22,7 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_message.h" // NewMessageFlags. #include "chat_helpers/message_field.h" // ConvertTextTagsToEntities. #include "ui/text/text_entity.h" // TextWithEntities. -#include "ui/text_options.h" // Ui::ItemTextOptions. +#include "ui/item_text_options.h" // Ui::ItemTextOptions. #include "main/main_session.h" #include "main/main_account.h" #include "main/main_app_config.h" diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 5c4cd1225..f78d3dcd4 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -64,7 +64,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/themes/window_theme.h" #include "inline_bots/inline_bot_result.h" #include "chat_helpers/message_field.h" -#include "ui/text_options.h" +#include "ui/item_text_options.h" #include "ui/emoji_config.h" #include "support/support_helper.h" #include "storage/localimageloader.h" diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 61a0b95cb..4be5be873 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -44,7 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_overview.h" #include "styles/style_media_view.h" #include "styles/style_chat_helpers.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include "styles/style_layers.h" #include diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp index aab5438ba..c0419a5ef 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp @@ -27,7 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/toast/toast.h" #include "ui/special_buttons.h" #include "ui/special_fields.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "ui/unread_badge.h" #include "ui/ui_utility.h" #include "data/data_channel.h" diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp index 6597fe75a..487cf333f 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp @@ -29,7 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/background_preview_box.h" #include "window/window_session_controller.h" #include "app.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" diff --git a/Telegram/SourceFiles/boxes/connection_box.cpp b/Telegram/SourceFiles/boxes/connection_box.cpp index 638251871..0ede048a0 100644 --- a/Telegram/SourceFiles/boxes/connection_box.cpp +++ b/Telegram/SourceFiles/boxes/connection_box.cpp @@ -25,7 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/toast/toast.h" #include "ui/effects/animations.h" #include "ui/effects/radial_animation.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "facades.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index 1f24fc049..b2e36864a 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -48,7 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/checkbox.h" #include "ui/text/format_values.h" #include "ui/special_buttons.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "window/window_session_controller.h" #include "confirm_box.h" #include "apiwrap.h" @@ -57,7 +57,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_layers.h" #include "styles/style_boxes.h" #include "styles/style_chat_helpers.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include diff --git a/Telegram/SourceFiles/boxes/language_box.cpp b/Telegram/SourceFiles/boxes/language_box.cpp index d852281a8..53ba3f53e 100644 --- a/Telegram/SourceFiles/boxes/language_box.cpp +++ b/Telegram/SourceFiles/boxes/language_box.cpp @@ -20,7 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/slide_wrap.h" #include "ui/effects/ripple_animation.h" #include "ui/toast/toast.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "storage/localstorage.h" #include "boxes/confirm_box.h" #include "mainwidget.h" diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index ec4c6cf46..9b8a5d51e 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -17,7 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/ripple_animation.h" #include "ui/empty_userpic.h" #include "ui/wrap/slide_wrap.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "lang/lang_keys.h" #include "storage/file_download.h" #include "data/data_peer_values.h" diff --git a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp index 7f82a7f3b..ab18b9f87 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp @@ -19,7 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/layers/generic_box.h" #include "ui/toast/toast.h" #include "ui/text/text_utilities.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "ui/special_buttons.h" #include "chat_helpers/emoji_suggestions_widget.h" #include "settings/settings_privacy_security.h" diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 5a0ca8ce9..fcfc37789 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -33,7 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/fade_wrap.h" #include "ui/text/format_values.h" #include "ui/grouped_layout.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "ui/special_buttons.h" #include "lottie/lottie_single_player.h" #include "data/data_document.h" @@ -44,7 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/core_settings.h" #include "facades.h" // App::LambdaDelayed. #include "app.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" #include "styles/style_chat_helpers.h" diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index eb377c0b3..da1dd35fb 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -21,7 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/scroll_area.h" #include "ui/widgets/input_fields.h" #include "ui/wrap/slide_wrap.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "chat_helpers/message_field.h" #include "chat_helpers/send_context_menu.h" #include "history/history.h" @@ -40,7 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" class ShareBox::Inner final : public Ui::RpWidget, private base::Subscriber { public: diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 4ad6195fe..10b1d6bd3 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -42,7 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "app.h" #include "webrtc/webrtc_video_track.h" #include "styles/style_calls.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #ifdef Q_OS_WIN #include "ui/platform/win/ui_window_title_win.h" diff --git a/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp b/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp index 39ce30e89..375039449 100644 --- a/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp +++ b/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp @@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "facades.h" #include "app.h" #include "styles/style_widgets.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace { diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp index a290af8cc..de207c1b5 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp @@ -33,7 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "facades.h" #include "app.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include "styles/style_widgets.h" #include "styles/style_chat_helpers.h" diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 33f7f12f2..2513d507e 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -29,7 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include #include diff --git a/Telegram/SourceFiles/chat_helpers/stickers_emoji_image_loader.cpp b/Telegram/SourceFiles/chat_helpers/stickers_emoji_image_loader.cpp index fa47ce5f7..9829009fa 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_emoji_image_loader.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_emoji_image_loader.cpp @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "chat_helpers/stickers_emoji_image_loader.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace Stickers { diff --git a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp index a4cf34643..1cfec6d02 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp @@ -22,7 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/call_delayed.h" #include "apiwrap.h" #include "app.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index d0f45f9b9..74152204e 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -57,7 +57,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_location_manager.h" #include "ui/widgets/tooltip.h" #include "ui/image/image.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "ui/emoji_config.h" #include "ui/effects/animations.h" #include "storage/serialize_common.h" diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 6c2301ea8..40309e4c5 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -26,7 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_dice.h" #include "ui/image/image.h" #include "ui/text/format_values.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "ui/emoji_config.h" #include "storage/storage_shared_media.h" #include "storage/localstorage.h" diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 27347867c..a74f95714 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -32,7 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "ui/image/image.h" #include "ui/empty_userpic.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "history/history.h" #include "history/view/history_view_element.h" #include "history/history_item.h" diff --git a/Telegram/SourceFiles/data/data_poll.cpp b/Telegram/SourceFiles/data/data_poll.cpp index 202edef2e..63ceb2a1a 100644 --- a/Telegram/SourceFiles/data/data_poll.cpp +++ b/Telegram/SourceFiles/data/data_poll.cpp @@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/call_delayed.h" #include "main/main_session.h" #include "api/api_text_entities.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" namespace { diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index 84b3b510a..099da8df1 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "data/data_session.h" #include "data/data_changes.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "apiwrap.h" #include "lang/lang_keys.h" diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index d7b03c4b8..5aa666cc9 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -19,7 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/buttons.h" #include "ui/widgets/popup_menu.h" #include "ui/text/text_utilities.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "ui/ui_utility.h" #include "data/data_drafts.h" #include "data/data_folder.h" diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp index 4d44581b4..c8641fa1c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp @@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_window.h" #include "storage/localstorage.h" #include "ui/empty_userpic.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "ui/unread_badge.h" #include "lang/lang_keys.h" #include "support/support_helper.h" diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.cpp b/Telegram/SourceFiles/dialogs/dialogs_row.cpp index 1e26442ad..97e045275 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_row.cpp @@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_row.h" #include "ui/effects/ripple_animation.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "dialogs/dialogs_entry.h" #include "data/data_folder.h" #include "data/data_peer_values.h" diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 731a4f957..b2840faeb 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -45,7 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "facades.h" #include "app.h" #include "styles/style_dialogs.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include "styles/style_info.h" #include "styles/style_window.h" diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index e5b23fccb..bf3c6989c 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -31,8 +31,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" #include "history/view/media/history_view_media.h" -#include "styles/style_history.h" #include "data/data_session.h" +#include "styles/style_chat.h" namespace { diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index b3908f152..f2a3a7753 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/admin_log/history_admin_log_inner.h" -#include "styles/style_history.h" #include "history/history.h" #include "history/view/media/history_view_media.h" #include "history/view/media/history_view_web_page.h" @@ -50,6 +49,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "facades.h" #include "app.h" +#include "styles/style_chat.h" #include #include diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp index 351c2e59a..4200080ce 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp @@ -28,7 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "lang/lang_keys.h" #include "facades.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include "styles/style_window.h" #include "styles/style_info.h" diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 84d8d5a76..973e26aff 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -42,7 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL //#include "storage/storage_feed_messages.h" // #feed #include "support/support_helper.h" #include "ui/image/image.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "core/crash_reports.h" #include "core/application.h" #include "base/unixtime.h" diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 9489ea8f7..e44c610b8 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -24,7 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/popup_menu.h" #include "ui/image/image.h" #include "ui/toast/toast.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "ui/ui_utility.h" #include "ui/inactive_press.h" #include "window/window_session_controller.h" @@ -62,7 +62,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/stickers/data_stickers.h" #include "facades.h" #include "app.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include "styles/style_window.h" // st::windowMinWidth #include diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index e75ed612c..0bae6ffd7 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -21,7 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/clip/media_clip_reader.h" #include "ui/effects/ripple_animation.h" #include "ui/text/text_isolated_emoji.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "storage/file_upload.h" #include "storage/storage_facade.h" #include "storage/storage_shared_media.h" @@ -44,7 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_chat.h" #include "data/data_user.h" #include "styles/style_dialogs.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace { diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 817ef5aec..8a7f5e784 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/ripple_animation.h" #include "ui/image/image.h" #include "ui/toast/toast.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "history/history.h" #include "history/history_message.h" #include "history/view/history_view_service_message.h" @@ -28,7 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "facades.h" #include "styles/style_widgets.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include diff --git a/Telegram/SourceFiles/history/history_item_text.cpp b/Telegram/SourceFiles/history/history_item_text.cpp index a21fb3460..3af55f05c 100644 --- a/Telegram/SourceFiles/history/history_item_text.cpp +++ b/Telegram/SourceFiles/history/history_item_text.cpp @@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_groups.h" #include "data/data_peer.h" #include "lang/lang_keys.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" TextForMimeData WrapAsReply( TextForMimeData &&text, diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index e6cb8970f..dba278fe5 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -27,7 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "ui/text/text_isolated_emoji.h" #include "ui/text/format_values.h" -#include "ui/text_options.h" +#include "ui/item_text_options.h" #include "core/application.h" #include "core/ui_integration.h" #include "window/notifications_manager.h" @@ -44,7 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "app.h" #include "styles/style_dialogs.h" #include "styles/style_widgets.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include "styles/style_window.h" #include diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index b3e61a7cf..70b50a101 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -29,7 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "storage/storage_shared_media.h" #include "ui/text/format_values.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" namespace { diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 288a23f89..7474dc637 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -91,7 +91,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_pinned_tracker.h" #include "base/qthelp_regex.h" #include "ui/widgets/popup_menu.h" -#include "ui/text_options.h" +#include "ui/item_text_options.h" #include "ui/unread_badge.h" #include "main/main_session.h" #include "main/main_session_settings.h" @@ -109,7 +109,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_key.h" #include "facades.h" #include "app.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include "styles/style_dialogs.h" #include "styles/style_window.h" #include "styles/style_boxes.h" diff --git a/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp index 73974f3f9..154495ed4 100644 --- a/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp @@ -31,9 +31,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "media/audio/media_audio_capture.h" #include "media/audio/media_audio.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include "ui/special_buttons.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "ui/ui_utility.h" #include "ui/widgets/input_fields.h" #include "ui/text/format_values.h" diff --git a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp index 84555f5ff..d3457cee1 100644 --- a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp +++ b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp @@ -27,7 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/confirm_box.h" #include "boxes/peers/edit_contact_box.h" #include "app.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include "styles/style_layers.h" namespace HistoryView { diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index f8768d2c3..c6a6d5b11 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -27,7 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "layout.h" #include "app.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace HistoryView { namespace { diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 15aeb735e..a39adecef 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -37,7 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document.h" #include "data/data_peer.h" #include "facades.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include #include diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index ab556e3a9..56d59d5af 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -32,7 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "facades.h" #include "app.h" #include "styles/style_widgets.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include "styles/style_dialogs.h" namespace HistoryView { diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index f5db39172..d7751ce84 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -20,7 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/shadow.h" #include "ui/wrap/slide_wrap.h" #include "ui/layers/generic_box.h" -#include "ui/text_options.h" +#include "ui/item_text_options.h" #include "ui/toast/toast.h" #include "ui/text/format_values.h" #include "ui/special_buttons.h" @@ -52,7 +52,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "facades.h" #include "app.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include "styles/style_window.h" #include "styles/style_info.h" #include "styles/style_boxes.h" diff --git a/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp b/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp index 73960991b..f09de4f01 100644 --- a/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp +++ b/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp @@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/send_context_menu.h" #include "styles/style_info.h" #include "styles/style_layers.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 227b2a1fa..ce7b5e9f7 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -18,7 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/scroll_area.h" #include "ui/widgets/shadow.h" #include "ui/layers/generic_box.h" -#include "ui/text_options.h" +#include "ui/item_text_options.h" #include "ui/toast/toast.h" #include "ui/special_buttons.h" #include "ui/ui_utility.h" @@ -47,7 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "facades.h" #include "app.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include "styles/style_window.h" #include "styles/style_info.h" #include "styles/style_boxes.h" diff --git a/Telegram/SourceFiles/history/view/history_view_send_action.cpp b/Telegram/SourceFiles/history/view/history_view_send_action.cpp index cd4ad548a..705ad5942 100644 --- a/Telegram/SourceFiles/history/view/history_view_send_action.cpp +++ b/Telegram/SourceFiles/history/view/history_view_send_action.cpp @@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "lang/lang_keys.h" #include "ui/effects/animations.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "styles/style_dialogs.h" namespace HistoryView { diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.cpp b/Telegram/SourceFiles/history/view/history_view_service_message.cpp index c72050603..083b85ff4 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_service_message.cpp @@ -15,14 +15,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_abstract_structure.h" #include "data/data_chat.h" #include "data/data_channel.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "core/core_settings.h" #include "core/application.h" #include "mainwidget.h" #include "layout.h" #include "lang/lang_keys.h" #include "app.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace HistoryView { namespace { diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index 7583e433f..289eca0e3 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -46,7 +46,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "facades.h" #include "styles/style_window.h" #include "styles/style_dialogs.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include "styles/style_info.h" namespace HistoryView { diff --git a/Telegram/SourceFiles/history/view/media/history_view_call.cpp b/Telegram/SourceFiles/history/view/media/history_view_call.cpp index dfa64941b..982e7c459 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_call.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_call.cpp @@ -19,7 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_media_types.h" #include "data/data_user.h" #include "main/main_session.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace HistoryView { namespace { diff --git a/Telegram/SourceFiles/history/view/media/history_view_contact.cpp b/Telegram/SourceFiles/history/view/media/history_view_contact.cpp index e563516e8..eb8a5ca37 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_contact.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_contact.cpp @@ -18,14 +18,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_cursor_state.h" #include "window/window_session_controller.h" #include "ui/empty_userpic.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "data/data_session.h" #include "data/data_user.h" #include "data/data_media_types.h" #include "data/data_cloud_file.h" #include "main/main_session.h" #include "app.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace HistoryView { namespace { diff --git a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp index 5dcc98492..3a79c6924 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp @@ -19,7 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "lang/lang_keys.h" #include "main/main_session.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace HistoryView { namespace { diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index 158f2c71e..6eb21fba3 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -25,7 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_media_types.h" #include "data/data_file_origin.h" #include "app.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace HistoryView { namespace { diff --git a/Telegram/SourceFiles/history/view/media/history_view_file.cpp b/Telegram/SourceFiles/history/view/media/history_view_file.cpp index b089cb36f..a32d63dc2 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_file.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_file.cpp @@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "data/data_document.h" #include "data/data_session.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace HistoryView { diff --git a/Telegram/SourceFiles/history/view/media/history_view_game.cpp b/Telegram/SourceFiles/history/view/media/history_view_game.cpp index dcb8dbf65..133c008f3 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_game.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_game.cpp @@ -14,13 +14,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" #include "history/view/media/history_view_media_common.h" -#include "ui/text_options.h" +#include "ui/item_text_options.h" #include "core/ui_integration.h" #include "data/data_session.h" #include "data/data_game.h" #include "data/data_media_types.h" #include "app.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace HistoryView { diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 6ea53e0ca..3fab69882 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -36,7 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document_media.h" #include "app.h" #include "layout.h" // FullSelection -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace HistoryView { namespace { diff --git a/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp b/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp index 230227c43..5e3549655 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp @@ -13,11 +13,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_cursor_state.h" #include "history/view/media/history_view_photo.h" #include "history/view/media/history_view_media_common.h" -#include "ui/text_options.h" +#include "ui/item_text_options.h" #include "ui/text/format_values.h" #include "data/data_media_types.h" #include "app.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace HistoryView { diff --git a/Telegram/SourceFiles/history/view/media/history_view_large_emoji.cpp b/Telegram/SourceFiles/history/view/media/history_view_large_emoji.cpp index cde50e11b..56ac0ceb0 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_large_emoji.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_large_emoji.cpp @@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/image/image.h" #include "data/data_file_origin.h" #include "layout.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace HistoryView { namespace { diff --git a/Telegram/SourceFiles/history/view/media/history_view_location.cpp b/Telegram/SourceFiles/history/view/media/history_view_location.cpp index e07b932e2..70a1bfbd7 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_location.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_location.cpp @@ -15,12 +15,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" #include "ui/image/image.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "data/data_session.h" #include "data/data_file_origin.h" #include "data/data_cloud_file.h" #include "app.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace HistoryView { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.cpp b/Telegram/SourceFiles/history/view/media/history_view_media.cpp index 7f4f47b2a..64eb1ff5d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media.cpp @@ -14,9 +14,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lottie/lottie_single_player.h" #include "storage/storage_shared_media.h" #include "data/data_document.h" -#include "ui/text_options.h" +#include "ui/item_text_options.h" #include "core/ui_integration.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace HistoryView { namespace { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp index 71d6a3b2c..200325885 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp @@ -16,7 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_document.h" #include "history/view/media/history_view_sticker.h" #include "history/view/media/history_view_theme_document.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace HistoryView { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index 3e567220b..a756931aa 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -17,9 +17,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_shared_media.h" #include "lang/lang_keys.h" #include "ui/grouped_layout.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "layout.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace HistoryView { namespace { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp index bf45d56fe..9345ad268 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -17,7 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/core_settings.h" #include "layout.h" #include "app.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace HistoryView { namespace { diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 6421bab8c..193455a9e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -29,7 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_auto_download.h" #include "core/application.h" #include "app.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace HistoryView { namespace { diff --git a/Telegram/SourceFiles/history/view/media/history_view_poll.cpp b/Telegram/SourceFiles/history/view/media/history_view_poll.cpp index 138816721..2738debf8 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_poll.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_poll.cpp @@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" #include "calls/calls_instance.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "ui/text/text_utilities.h" #include "ui/text/format_values.h" #include "ui/effects/animations.h" @@ -30,7 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "layout.h" // FullSelection #include "apiwrap.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include "styles/style_widgets.h" #include "styles/style_window.h" diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp index 92884b2e6..15ee3cb28 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp @@ -30,7 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_file_origin.h" #include "lottie/lottie_single_player.h" #include "chat_helpers/stickers_lottie.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace HistoryView { namespace { diff --git a/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp index 33e8432aa..437ab56c0 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp @@ -20,7 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/themes/window_theme.h" #include "layout.h" // FullSelection #include "app.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace HistoryView { diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp index 5e84f604a..4e4e395f3 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -17,7 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_cursor_state.h" #include "history/view/media/history_view_media_common.h" #include "ui/image/image.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "ui/text/format_values.h" #include "layout.h" // FullSelection #include "data/data_session.h" @@ -27,7 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_photo_media.h" #include "data/data_file_origin.h" #include "app.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace HistoryView { namespace { diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index f63fd149e..dde873ad1 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -29,7 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "app.h" #include "styles/style_overview.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include "styles/style_chat_helpers.h" #include "styles/style_widgets.h" diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 2c3b51af3..847b82de3 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -39,7 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/image/image.h" #include "ui/focus_persister.h" #include "ui/resize_area.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "ui/emoji_config.h" #include "window/section_memento.h" #include "window/section_widget.h" @@ -109,7 +109,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "app.h" #include "facades.h" #include "styles/style_dialogs.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include "styles/style_boxes.h" #include diff --git a/Telegram/SourceFiles/media/player/media_player_float.cpp b/Telegram/SourceFiles/media/player/media_player_float.cpp index ef201aee2..637f3a70b 100644 --- a/Telegram/SourceFiles/media/player/media_player_float.cpp +++ b/Telegram/SourceFiles/media/player/media_player_float.cpp @@ -28,7 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "facades.h" #include "app.h" #include "styles/style_media_player.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 38bf76a48..4771cea59 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/platform/ui_platform_utility.h" #include "ui/toast/toast.h" #include "ui/text/format_values.h" -#include "ui/text_options.h" +#include "ui/item_text_options.h" #include "ui/ui_utility.h" #include "boxes/confirm_box.h" #include "media/audio/media_audio.h" @@ -62,7 +62,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "facades.h" #include "app.h" #include "styles/style_media_view.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #ifdef Q_OS_MAC #include "platform/mac/touchbar/mac_touchbar_media_view.h" diff --git a/Telegram/SourceFiles/overview/overview.style b/Telegram/SourceFiles/overview/overview.style index c5e0915e0..818d39ce9 100644 --- a/Telegram/SourceFiles/overview/overview.style +++ b/Telegram/SourceFiles/overview/overview.style @@ -6,7 +6,7 @@ For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ using "ui/basic.style"; -using "history/history.style"; +using "ui/chat/chat.style"; using "ui/widgets/widgets.style"; using "media/view/media_view.style"; using "boxes/boxes.style"; diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 2821f54d0..5a4096ec1 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -17,7 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_photo_media.h" #include "data/data_document_media.h" #include "styles/style_overview.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include "core/file_utilities.h" #include "boxes/add_contact_box.h" #include "boxes/confirm_box.h" @@ -37,7 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/round_checkbox.h" #include "ui/image/image.h" #include "ui/text/format_values.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "app.h" namespace Overview { diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp index a9f019e85..5148a10f9 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp @@ -16,7 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "ui/text/text_utilities.h" // Ui::Text::ToUpper -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "core/file_utilities.h" #include "lang/lang_keys.h" #include "boxes/abstract_box.h" diff --git a/Telegram/SourceFiles/passport/passport_panel_form.cpp b/Telegram/SourceFiles/passport/passport_panel_form.cpp index 826851c74..75eeba4c3 100644 --- a/Telegram/SourceFiles/passport/passport_panel_form.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_form.cpp @@ -22,7 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/fade_wrap.h" #include "ui/wrap/padding_wrap.h" #include "ui/text/text_utilities.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "ui/special_buttons.h" #include "styles/style_passport.h" #include "styles/style_layers.h" diff --git a/Telegram/SourceFiles/profile/profile_block_peer_list.cpp b/Telegram/SourceFiles/profile/profile_block_peer_list.cpp index 23e9e8596..60c198f62 100644 --- a/Telegram/SourceFiles/profile/profile_block_peer_list.cpp +++ b/Telegram/SourceFiles/profile/profile_block_peer_list.cpp @@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "profile/profile_block_peer_list.h" #include "ui/effects/ripple_animation.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "data/data_peer.h" #include "data/data_cloud_file.h" #include "main/main_session.h" diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index bc66f2c60..786e1f5f4 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -37,7 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_privacy_security.h" #include "facades.h" #include "app.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include "styles/style_boxes.h" #include "styles/style_settings.h" diff --git a/Telegram/SourceFiles/support/support_helper.cpp b/Telegram/SourceFiles/support/support_helper.cpp index 5b73d2c00..82c2fd328 100644 --- a/Telegram/SourceFiles/support/support_helper.cpp +++ b/Telegram/SourceFiles/support/support_helper.cpp @@ -18,7 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/toast/toast.h" #include "ui/widgets/input_fields.h" #include "ui/text/text_entity.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "chat_helpers/message_field.h" #include "chat_helpers/emoji_suggestions_widget.h" #include "base/unixtime.h" diff --git a/Telegram/SourceFiles/history/history.style b/Telegram/SourceFiles/ui/chat/chat.style similarity index 98% rename from Telegram/SourceFiles/history/history.style rename to Telegram/SourceFiles/ui/chat/chat.style index 60cffe609..4364fca61 100644 --- a/Telegram/SourceFiles/history/history.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -9,6 +9,24 @@ using "ui/basic.style"; using "dialogs/dialogs.style"; using "ui/widgets/widgets.style"; +MessageBar { + title: TextStyle; + titleFg: color; + text: TextStyle; + textFg: color; + textPalette: TextPalette; + duration: int; +} + +defaultMessageBar: MessageBar { + title: semiboldTextStyle; + titleFg: windowActiveTextFg; + text: messageTextStyle; + textFg: historyComposeAreaFg; + textPalette: historyComposeAreaPalette; + duration: 160; +} + minPhotoSize: 100px; minVideoSize: 160px; maxMediaSize: 430px; diff --git a/Telegram/SourceFiles/ui/chat/message_bar.cpp b/Telegram/SourceFiles/ui/chat/message_bar.cpp new file mode 100644 index 000000000..cf4a27c60 --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/message_bar.cpp @@ -0,0 +1,295 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/chat/message_bar.h" + +#include "ui/text/text_options.h" +#include "styles/style_chat.h" + +namespace Ui { + +MessageBar::MessageBar(not_null parent, const style::MessageBar &st) +: _st(st) +, _widget(parent) { + setup(); +} + +void MessageBar::setup() { + _widget.resize(0, st::historyReplyHeight); + _widget.paintRequest( + ) | rpl::start_with_next([=](QRect rect) { + auto p = Painter(&_widget); + paint(p); + }, _widget.lifetime()); +} + +void MessageBar::set(MessageBarContent &&content) { + _contentLifetime.destroy(); + tweenTo(std::move(content)); +} + +void MessageBar::set(rpl::producer content) { + _contentLifetime.destroy(); + std::move( + content + ) | rpl::start_with_next([=](MessageBarContent &&content) { + tweenTo(std::move(content)); + }, _contentLifetime); +} + +MessageBar::BodyAnimation MessageBar::DetectBodyAnimationType( + Animation *currentAnimation, + const MessageBarContent ¤tContent, + const MessageBarContent &nextContent) { + const auto now = currentAnimation + ? currentAnimation->bodyAnimation + : BodyAnimation::None; + return (now == BodyAnimation::Full + || currentContent.title != nextContent.title) + ? BodyAnimation::Full + : (now == BodyAnimation::Text + || currentContent.text != nextContent.text + || currentContent.id != nextContent.id) + ? BodyAnimation::Text + : BodyAnimation::None; +} + +void MessageBar::tweenTo(MessageBarContent &&content) { + _widget.update(); + if (!_st.duration || anim::Disabled() || _widget.size().isEmpty()) { + updateFromContent(std::move(content)); + return; + } + const auto hasImageChanged = (_content.preview.isNull() + != content.preview.isNull()); + const auto bodyChanged = (_content.id != content.id + || _content.title != content.title + || _content.text != content.text + || _content.preview.constBits() != content.preview.constBits()); + auto animation = Animation(); + animation.bodyAnimation = DetectBodyAnimationType( + _animation.get(), + _content, + content); + animation.movingTo = (content.id > _content.id) + ? RectPart::Top + : (content.id < _content.id) + ? RectPart::Bottom + : RectPart::None; + animation.imageFrom = grabImagePart(); + animation.bodyOrTextFrom = grabBodyOrTextPart(animation.bodyAnimation); + auto was = std::move(_animation); + updateFromContent(std::move(content)); + animation.imageTo = grabImagePart(); + animation.bodyOrTextTo = grabBodyOrTextPart(animation.bodyAnimation); + if (was) { + _animation = std::move(was); + std::swap(*_animation, animation); + _animation->imageShown = std::move(animation.imageShown); + } else { + _animation = std::make_unique(std::move(animation)); + } + + if (hasImageChanged) { + _animation->imageShown.start( + [=] { _widget.update(); }, + _image.isNull() ? 1. : 0., + _image.isNull() ? 0. : 1., + _st.duration); + } + if (bodyChanged) { + _animation->bodyMoved.start( + [=] { _widget.update(); }, + 0., + 1., + _st.duration); + } +} + +void MessageBar::updateFromContent(MessageBarContent &&content) { + _content = std::move(content); + _title.setText(_st.title, _content.title); + _text.setMarkedText(_st.text, _content.text, Ui::DialogTextOptions()); + _image = prepareImage(_content.preview); +} + +QRect MessageBar::imageRect() const { + const auto left = st::msgReplyBarSkip + st::msgReplyBarSkip; + const auto top = st::msgReplyPadding.top(); + const auto size = st::msgReplyBarSize.height(); + return QRect(left, top, size, size); +} + +QRect MessageBar::bodyRect(bool withImage) const { + const auto innerLeft = st::msgReplyBarSkip + st::msgReplyBarSkip; + const auto imageSkip = st::msgReplyBarSize.height() + + st::msgReplyBarSkip + - st::msgReplyBarSize.width() + - st::msgReplyBarPos.x(); + const auto left = innerLeft + (withImage ? imageSkip : 0); + const auto top = st::msgReplyPadding.top(); + const auto width = _widget.width() - left - st::msgReplyPadding.right(); + const auto height = st::msgReplyBarSize.height(); + return QRect(left, top, width, height); +} + +QRect MessageBar::bodyRect() const { + return bodyRect(!_image.isNull()); +} + +QRect MessageBar::textRect() const { + auto result = bodyRect(); + result.setTop(result.top() + st::msgServiceNameFont->height); + return result; +} + +auto MessageBar::makeGrabGuard() { + auto imageShown = _animation + ? std::move(_animation->imageShown) + : Ui::Animations::Simple(); + return gsl::finally([&, shown = std::move(imageShown)]() mutable { + if (_animation) { + _animation->imageShown = std::move(shown); + } + }); +} + +QPixmap MessageBar::grabBodyOrTextPart(BodyAnimation type) { + return (type == BodyAnimation::Full) + ? grabBodyPart() + : (type == BodyAnimation::Text) + ? grabTextPart() + : QPixmap(); +} + +QPixmap MessageBar::grabBodyPart() { + const auto guard = makeGrabGuard(); + return GrabWidget(widget(), bodyRect()); +} + +QPixmap MessageBar::grabTextPart() { + const auto guard = makeGrabGuard(); + return GrabWidget(widget(), textRect()); +} + +QPixmap MessageBar::grabImagePart() { + if (!_animation) { + return _image; + } + const auto guard = makeGrabGuard(); + return (_animation->bodyMoved.animating() + && !_animation->imageFrom.isNull() + && !_animation->imageTo.isNull()) + ? GrabWidget(widget(), imageRect()) + : _animation->imageFrom; +} + +void MessageBar::finishAnimating() { + if (_animation) { + _animation = nullptr; + _widget.update(); + } +} + +QPixmap MessageBar::prepareImage(const QImage &preview) { + return QPixmap::fromImage(preview, Qt::ColorOnly); +} + +void MessageBar::paint(Painter &p) { + const auto progress = _animation ? _animation->bodyMoved.value(1.) : 1.; + const auto imageFinal = _image.isNull() ? 0. : 1.; + const auto imageShown = _animation + ? _animation->imageShown.value(imageFinal) + : imageFinal; + if (progress == 1. && imageShown == 1. && _animation) { + _animation = nullptr; + } + const auto body = [&] { + if (!_animation || !_animation->imageShown.animating()) { + return bodyRect(); + } + const auto noImage = bodyRect(false); + const auto withImage = bodyRect(true); + return QRect( + anim::interpolate(noImage.x(), withImage.x(), imageShown), + noImage.y(), + anim::interpolate(noImage.width(), withImage.width(), imageShown), + noImage.height()); + }(); + const auto text = textRect(); + const auto image = imageRect(); + const auto width = _widget.width(); + const auto noShift = !_animation + || (_animation->movingTo == RectPart::None); + const auto shiftFull = st::msgReplyBarSkip; + const auto shiftTo = noShift + ? 0 + : (_animation->movingTo == RectPart::Top) + ? anim::interpolate(shiftFull, 0, progress) + : anim::interpolate(-shiftFull, 0, progress); + const auto shiftFrom = noShift + ? 0 + : (_animation->movingTo == RectPart::Top) + ? (shiftTo - shiftFull) + : (shiftTo + shiftFull); + + if (!_animation) { + if (!_image.isNull()) { + p.drawPixmap(image, _image); + } + } else if (!_animation->imageFrom.isNull() + || !_animation->imageTo.isNull()) { + const auto rect = [&] { + if (!_animation->imageShown.animating()) { + return image; + } + const auto size = anim::interpolate(0, image.width(), imageShown); + return QRect( + image.x(), + image.y() + (image.height() - size) / 2, + size, + size); + }(); + if (_animation->bodyMoved.animating()) { + p.setOpacity(1. - progress); + p.drawPixmap( + rect.translated(0, shiftFrom), + _animation->imageFrom); + p.setOpacity(progress); + p.drawPixmap(rect.translated(0, shiftTo), _animation->imageTo); + } else { + p.drawPixmap(rect, _image); + } + } + if (!_animation || _animation->bodyAnimation == BodyAnimation::None) { + p.setPen(_st.textFg); + p.setTextPalette(_st.textPalette); + _text.drawLeftElided(p, body.x(), text.y(), body.width(), width); + } else if (_animation->bodyAnimation == BodyAnimation::Text) { + p.setOpacity(1. - progress); + p.drawPixmap( + body.x(), + text.y() + shiftFrom, + _animation->bodyOrTextFrom); + p.setOpacity(progress); + p.drawPixmap(body.x(), text.y() + shiftTo, _animation->bodyOrTextTo); + } + if (!_animation || _animation->bodyAnimation != BodyAnimation::Full) { + p.setPen(_st.titleFg); + _title.drawLeftElided(p, body.x(), body.y(), body.width(), width); + } else { + p.setOpacity(1. - progress); + p.drawPixmap( + body.x(), + body.y() + shiftFrom, + _animation->bodyOrTextFrom); + p.setOpacity(progress); + p.drawPixmap(body.x(), body.y() + shiftTo, _animation->bodyOrTextTo); + } +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/message_bar.h b/Telegram/SourceFiles/ui/chat/message_bar.h new file mode 100644 index 000000000..73bf97330 --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/message_bar.h @@ -0,0 +1,88 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/rp_widget.h" + +class Painter; + +namespace style { +struct MessageBar; +} // namespace style + +namespace Ui { + +struct MessageBarContent { + int id = 0; + QString title; + TextWithEntities text; + QImage preview; +}; + +class MessageBar final { +public: + MessageBar(not_null parent, const style::MessageBar &st); + + void set(MessageBarContent &&content); + void set(rpl::producer content); + + [[nodiscard]] not_null widget() { + return &_widget; + } + + void finishAnimating(); + +private: + enum class BodyAnimation : char { + Full, + Text, + None, + }; + struct Animation { + Ui::Animations::Simple bodyMoved; + Ui::Animations::Simple imageShown; + QPixmap bodyOrTextFrom; + QPixmap bodyOrTextTo; + QPixmap imageFrom; + QPixmap imageTo; + BodyAnimation bodyAnimation = BodyAnimation::None; + RectPart movingTo = RectPart::None; + }; + void setup(); + void paint(Painter &p); + void tweenTo(MessageBarContent &&content); + void updateFromContent(MessageBarContent &&content); + [[nodiscard]] QPixmap prepareImage(const QImage &preview); + + [[nodiscard]] QRect imageRect() const; + [[nodiscard]] QRect bodyRect(bool withImage) const; + [[nodiscard]] QRect bodyRect() const; + [[nodiscard]] QRect textRect() const; + + auto makeGrabGuard(); + [[nodiscard]] QPixmap grabBodyOrTextPart(BodyAnimation type); + [[nodiscard]] QPixmap grabImagePart(); + [[nodiscard]] QPixmap grabBodyPart(); + [[nodiscard]] QPixmap grabTextPart(); + + [[nodiscard]] static BodyAnimation DetectBodyAnimationType( + Animation *currentAnimation, + const MessageBarContent ¤tContent, + const MessageBarContent &nextContent); + + const style::MessageBar &_st; + Ui::RpWidget _widget; + MessageBarContent _content; + rpl::lifetime _contentLifetime; + Ui::Text::String _title, _text; + QPixmap _image; + std::unique_ptr _animation; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/empty_userpic.cpp b/Telegram/SourceFiles/ui/empty_userpic.cpp index ba07e0cc2..7fdd39374 100644 --- a/Telegram/SourceFiles/ui/empty_userpic.cpp +++ b/Telegram/SourceFiles/ui/empty_userpic.cpp @@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/emoji_config.h" #include "ui/effects/animation_value.h" #include "app.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include "styles/style_dialogs.h" namespace Ui { diff --git a/Telegram/SourceFiles/ui/item_text_options.cpp b/Telegram/SourceFiles/ui/item_text_options.cpp new file mode 100644 index 000000000..57ccc44c5 --- /dev/null +++ b/Telegram/SourceFiles/ui/item_text_options.cpp @@ -0,0 +1,70 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/item_text_options.h" + +#include "history/history.h" +#include "history/history_item.h" +#include "data/data_channel.h" +#include "data/data_chat.h" +#include "data/data_user.h" + +namespace Ui { +namespace { + +bool UseBotTextOptions( + not_null history, + not_null author) { + if (const auto user = history->peer->asUser()) { + if (user->isBot()) { + return true; + } + } else if (const auto chat = history->peer->asChat()) { + if (chat->botStatus >= 0) { + return true; + } + } else if (const auto group = history->peer->asMegagroup()) { + if (group->mgInfo->botStatus >= 0) { + return true; + } + } + if (const auto user = author->asUser()) { + if (user->isBot()) { + return true; + } + } + return false; +} + +} // namespace + +const TextParseOptions &ItemTextOptions( + not_null history, + not_null author) { + return UseBotTextOptions(history, author) + ? ItemTextBotDefaultOptions() + : ItemTextDefaultOptions(); +} + +const TextParseOptions &ItemTextOptions(not_null item) { + return ItemTextOptions(item->history(), item->author()); +} + +const TextParseOptions &ItemTextNoMonoOptions( + not_null history, + not_null author) { + return UseBotTextOptions(history, author) + ? ItemTextBotNoMonoOptions() + : ItemTextNoMonoOptions(); +} + +const TextParseOptions &ItemTextNoMonoOptions( + not_null item) { + return ItemTextNoMonoOptions(item->history(), item->author()); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/item_text_options.h b/Telegram/SourceFiles/ui/item_text_options.h new file mode 100644 index 000000000..4ec410243 --- /dev/null +++ b/Telegram/SourceFiles/ui/item_text_options.h @@ -0,0 +1,27 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/text/text_options.h" + +class History; +class PeerData; +class HistoryItem; + +namespace Ui { + +const TextParseOptions &ItemTextOptions( + not_null history, + not_null author); +const TextParseOptions &ItemTextNoMonoOptions( + not_null history, + not_null author); +const TextParseOptions &ItemTextOptions(not_null item); +const TextParseOptions &ItemTextNoMonoOptions(not_null item); + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/special_buttons.cpp b/Telegram/SourceFiles/ui/special_buttons.cpp index d26fa88bc..29fce70ba 100644 --- a/Telegram/SourceFiles/ui/special_buttons.cpp +++ b/Telegram/SourceFiles/ui/special_buttons.cpp @@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/special_buttons.h" #include "styles/style_boxes.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include "dialogs/dialogs_layout.h" #include "ui/effects/ripple_animation.h" #include "ui/effects/radial_animation.h" diff --git a/Telegram/SourceFiles/ui/text_options.cpp b/Telegram/SourceFiles/ui/text/text_options.cpp similarity index 70% rename from Telegram/SourceFiles/ui/text_options.cpp rename to Telegram/SourceFiles/ui/text/text_options.cpp index 99247f568..74e2f997f 100644 --- a/Telegram/SourceFiles/ui/text_options.cpp +++ b/Telegram/SourceFiles/ui/text/text_options.cpp @@ -5,15 +5,10 @@ the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ -#include "ui/text_options.h" +#include "ui/text/text_options.h" -#include "history/history.h" -#include "history/history_item.h" -#include "data/data_channel.h" -#include "data/data_chat.h" -#include "data/data_user.h" -#include "styles/style_history.h" #include "styles/style_window.h" +#include "styles/style_chat.h" namespace Ui { namespace { @@ -110,37 +105,13 @@ TextParseOptions WebpageDescriptionOptions = { Qt::LayoutDirectionAuto, // dir }; -bool UseBotTextOptions( - not_null history, - not_null author) { - if (const auto user = history->peer->asUser()) { - if (user->isBot()) { - return true; - } - } else if (const auto chat = history->peer->asChat()) { - if (chat->botStatus >= 0) { - return true; - } - } else if (const auto group = history->peer->asMegagroup()) { - if (group->mgInfo->botStatus >= 0) { - return true; - } - } - if (const auto user = author->asUser()) { - if (user->isBot()) { - return true; - } - } - return false; -} - } // namespace void InitTextOptions() { HistoryServiceOptions.dir = TextNameOptions.dir = TextDialogOptions.dir - = cLangDir(); + = Qt::LeftToRight; TextDialogOptions.maxw = st::columnMaximalWidthLeft * 2; WebpageTitleOptions.maxh = st::webPageTitleFont->height * 2; WebpageTitleOptions.maxw @@ -188,29 +159,4 @@ const TextParseOptions &DialogTextOptions() { return TextDialogOptions; } -const TextParseOptions &ItemTextOptions( - not_null history, - not_null author) { - return UseBotTextOptions(history, author) - ? HistoryBotOptions - : HistoryTextOptions; -} - -const TextParseOptions &ItemTextOptions(not_null item) { - return ItemTextOptions(item->history(), item->author()); -} - -const TextParseOptions &ItemTextNoMonoOptions( - not_null history, - not_null author) { - return UseBotTextOptions(history, author) - ? HistoryBotNoMonoOptions - : HistoryTextNoMonoOptions; -} - -const TextParseOptions &ItemTextNoMonoOptions( - not_null item) { - return ItemTextNoMonoOptions(item->history(), item->author()); -} - } // namespace Ui diff --git a/Telegram/SourceFiles/ui/text_options.h b/Telegram/SourceFiles/ui/text/text_options.h similarity index 66% rename from Telegram/SourceFiles/ui/text_options.h rename to Telegram/SourceFiles/ui/text/text_options.h index 85b0a80c3..59bf8b182 100644 --- a/Telegram/SourceFiles/ui/text_options.h +++ b/Telegram/SourceFiles/ui/text/text_options.h @@ -7,9 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -class History; -class PeerData; - struct TextParseOptions; namespace Ui { @@ -28,13 +25,4 @@ const TextParseOptions &WebpageTextDescriptionOptions(); const TextParseOptions &NameTextOptions(); const TextParseOptions &DialogTextOptions(); -const TextParseOptions &ItemTextOptions( - not_null history, - not_null author); -const TextParseOptions &ItemTextNoMonoOptions( - not_null history, - not_null author); -const TextParseOptions &ItemTextOptions(not_null item); -const TextParseOptions &ItemTextNoMonoOptions(not_null item); - } // namespace Ui diff --git a/Telegram/SourceFiles/ui/ui_pch.h b/Telegram/SourceFiles/ui/ui_pch.h index 1894747b8..07af4a4fb 100644 --- a/Telegram/SourceFiles/ui/ui_pch.h +++ b/Telegram/SourceFiles/ui/ui_pch.h @@ -31,3 +31,6 @@ #include "base/basic_types.h" #include "base/flat_map.h" #include "base/flat_set.h" + +#include "ui/text/text.h" +#include "ui/effects/animations.h" diff --git a/Telegram/SourceFiles/ui/widgets/multi_select.cpp b/Telegram/SourceFiles/ui/widgets/multi_select.cpp index 17ceb2f7c..cef0403f4 100644 --- a/Telegram/SourceFiles/ui/widgets/multi_select.cpp +++ b/Telegram/SourceFiles/ui/widgets/multi_select.cpp @@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/input_fields.h" #include "ui/widgets/scroll_area.h" #include "ui/effects/cross_animation.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "lang/lang_keys.h" #include "app.h" diff --git a/Telegram/SourceFiles/window/notifications_manager_default.cpp b/Telegram/SourceFiles/window/notifications_manager_default.cpp index 3162481b6..27d534b4f 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.cpp +++ b/Telegram/SourceFiles/window/notifications_manager_default.cpp @@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "ui/platform/ui_platform_utility.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "ui/emoji_config.h" #include "ui/empty_userpic.h" #include "ui/ui_utility.h" diff --git a/Telegram/SourceFiles/window/themes/window_theme.cpp b/Telegram/SourceFiles/window/themes/window_theme.cpp index 67391766f..c90ba42f9 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme.cpp @@ -30,7 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "app.h" #include "styles/style_widgets.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include diff --git a/Telegram/SourceFiles/window/themes/window_theme_preview.cpp b/Telegram/SourceFiles/window/themes/window_theme_preview.cpp index 11c7236bb..c8737cd2f 100644 --- a/Telegram/SourceFiles/window/themes/window_theme_preview.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme_preview.cpp @@ -10,13 +10,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/themes/window_theme.h" #include "lang/lang_keys.h" #include "platform/platform_window_title.h" -#include "ui/text_options.h" +#include "ui/text/text_options.h" #include "ui/image/image_prepare.h" #include "ui/emoji_config.h" #include "styles/style_widgets.h" #include "styles/style_window.h" #include "styles/style_media_view.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include "styles/style_dialogs.h" #include "styles/style_info.h" diff --git a/Telegram/SourceFiles/window/themes/window_themes_cloud_list.cpp b/Telegram/SourceFiles/window/themes/window_themes_cloud_list.cpp index ab355c0e0..4b18cca3b 100644 --- a/Telegram/SourceFiles/window/themes/window_themes_cloud_list.cpp +++ b/Telegram/SourceFiles/window/themes/window_themes_cloud_list.cpp @@ -26,7 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "styles/style_settings.h" #include "styles/style_boxes.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" #include #include diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index 36ee49616..3b3577e08 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ using "ui/basic.style"; using "ui/widgets/widgets.style"; -using "history/history.style"; +using "ui/chat/chat.style"; using "boxes/boxes.style"; // UserpicButton windowMinWidth: 380px; diff --git a/Telegram/SourceFiles/window/window_history_hider.cpp b/Telegram/SourceFiles/window/window_history_hider.cpp index a989ef1a2..df519d3f7 100644 --- a/Telegram/SourceFiles/window/window_history_hider.cpp +++ b/Telegram/SourceFiles/window/window_history_hider.cpp @@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "facades.h" #include "app.h" #include "styles/style_layers.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace Window { diff --git a/Telegram/SourceFiles/window/window_media_preview.cpp b/Telegram/SourceFiles/window/window_media_preview.cpp index c7f7d4aca..ea24e9b5f 100644 --- a/Telegram/SourceFiles/window/window_media_preview.cpp +++ b/Telegram/SourceFiles/window/window_media_preview.cpp @@ -20,7 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "styles/style_layers.h" #include "styles/style_chat_helpers.h" -#include "styles/style_history.h" +#include "styles/style_chat.h" namespace Window { namespace { diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 609d05a96..086a78e73 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -12,6 +12,9 @@ include(lib_ui/cmake/generate_styles.cmake) set(style_files ui/td_common.style + ui/chat/chat.style + dialogs/dialogs.style + window/window.style ) set(dependent_style_files @@ -29,8 +32,12 @@ PRIVATE ${style_files} ui/ui_pch.h + ui/chat/message_bar.cpp + ui/chat/message_bar.h ui/text/format_values.cpp ui/text/format_values.h + ui/text/text_options.cpp + ui/text/text_options.h ui/toasts/common_toasts.cpp ui/toasts/common_toasts.h ) From 67290eed586a0429195f86c9ff4c0de662ec6ecc Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 10 Oct 2020 15:04:28 +0300 Subject: [PATCH 081/190] Use new message bar for pinned message. --- Telegram/CMakeLists.txt | 2 + .../SourceFiles/history/history_widget.cpp | 386 ++++++++---------- Telegram/SourceFiles/history/history_widget.h | 22 +- .../history/view/history_view_pinned_bar.cpp | 229 +++++++++++ .../history/view/history_view_pinned_bar.h | 59 +++ Telegram/SourceFiles/ui/chat/message_bar.cpp | 13 +- 6 files changed, 473 insertions(+), 238 deletions(-) create mode 100644 Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp create mode 100644 Telegram/SourceFiles/history/view/history_view_pinned_bar.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index abc87d05b..5a0a439a9 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -561,6 +561,8 @@ PRIVATE history/view/history_view_message.cpp history/view/history_view_message.h history/view/history_view_object.h + history/view/history_view_pinned_bar.cpp + history/view/history_view_pinned_bar.h history/view/history_view_pinned_tracker.cpp history/view/history_view_pinned_tracker.h history/view/history_view_replies_section.cpp diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 7474dc637..ff32dd3e1 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/ripple_animation.h" #include "ui/text/text_utilities.h" // Ui::Text::ToUpper #include "ui/text/format_values.h" +#include "ui/chat/message_bar.h" #include "ui/image/image.h" #include "ui/special_buttons.h" #include "inline_bots/inline_bot_result.h" @@ -62,6 +63,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_scheduled_section.h" #include "history/view/history_view_schedule_box.h" #include "history/view/history_view_webpage_preview.h" +#include "history/view/history_view_top_bar_widget.h" +#include "history/view/history_view_contact_status.h" +#include "history/view/history_view_pinned_tracker.h" +#include "history/view/history_view_pinned_bar.h" #include "history/view/media/history_view_media.h" #include "profile/profile_block_group_members.h" #include "info/info_memento.h" @@ -86,9 +91,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/player/media_player_instance.h" #include "core/application.h" #include "apiwrap.h" -#include "history/view/history_view_top_bar_widget.h" -#include "history/view/history_view_contact_status.h" -#include "history/view/history_view_pinned_tracker.h" #include "base/qthelp_regex.h" #include "ui/widgets/popup_menu.h" #include "ui/item_text_options.h" @@ -552,7 +554,6 @@ HistoryWidget::HistoryWidget( UpdateFlag::Rights | UpdateFlag::Migration | UpdateFlag::UnavailableReason - | UpdateFlag::PinnedMessage | UpdateFlag::IsBlocked | UpdateFlag::Admins | UpdateFlag::Members @@ -590,14 +591,6 @@ HistoryWidget::HistoryWidget( updateControlsVisibility(); updateControlsGeometry(); } - if (flags & UpdateFlag::PinnedMessage) { - if (pinnedMsgVisibilityUpdated()) { - updateHistoryGeometry(); - updateControlsVisibility(); - updateControlsGeometry(); - this->update(); - } - } if (flags & UpdateFlag::Slowmode) { updateSendButtonType(); } @@ -1142,7 +1135,7 @@ void HistoryWidget::orderWidgets() { _contactStatus->raise(); } if (_pinnedBar) { - _pinnedBar->shadow->raise(); + _pinnedBar->raise(); } _topShadow->raise(); if (_membersDropdown) { @@ -1698,7 +1691,7 @@ void HistoryWidget::showHistory( _history->showAtMsgId = _showAtMsgId; destroyUnreadBarOnClose(); - showPinnedMessage(FullMsgId()); + _pinnedBar = nullptr; _pinnedTracker = nullptr; _membersDropdown.destroy(); _scrollToAnimation.stop(); @@ -1814,7 +1807,6 @@ void HistoryWidget::showHistory( _updateHistoryItems.stop(); setupPinnedTracker(); - pinnedMsgVisibilityUpdated(); if (_history->scrollTopItem || (_migrated && _migrated->scrollTopItem) || _history->isReadyFor(_showAtMsgId)) { @@ -2026,13 +2018,15 @@ void HistoryWidget::updateControlsVisibility() { if (_tabbedPanel) { _tabbedPanel->hideFast(); } + if (_pinnedBar) { + _pinnedBar->hide(); + } hideChildren(); return; } if (_pinnedBar) { - _pinnedBar->cancel->show(); - _pinnedBar->shadow->show(); + _pinnedBar->show(); } if (_firstLoadRequest && !_scroll->isHidden()) { _scroll->hide(); @@ -2242,7 +2236,7 @@ void HistoryWidget::showAboutTopPromotion() { } void HistoryWidget::updateMouseTracking() { - bool trackMouse = !_fieldBarCancel->isHidden() || _pinnedBar; + const auto trackMouse = !_fieldBarCancel->isHidden(); setMouseTracking(trackMouse); } @@ -3180,6 +3174,9 @@ void HistoryWidget::showAnimated( if (_tabbedPanel) { _tabbedPanel->hideFast(); } + if (_pinnedBar) { + _pinnedBar->hide(); + } hideChildren(); if (params.withTopBarShadow) _topShadow->show(); @@ -3336,14 +3333,12 @@ void HistoryWidget::mouseMoveEvent(QMouseEvent *e) { void HistoryWidget::updateOverStates(QPoint pos) { auto inField = pos.y() >= (_scroll->y() + _scroll->height()) && pos.y() < height() && pos.x() >= 0 && pos.x() < width(); auto inReplyEditForward = QRect(st::historyReplySkip, _field->y() - st::historySendPadding - st::historyReplyHeight, width() - st::historyReplySkip - _fieldBarCancel->width(), st::historyReplyHeight).contains(pos) && (_editMsgId || replyToId() || readyToForward()); - auto inPinnedMsg = QRect(0, _topBar->bottomNoMargins(), width(), st::historyReplyHeight).contains(pos) && _pinnedBar; - auto inClickable = inReplyEditForward || inPinnedMsg; + auto inClickable = inReplyEditForward; if (inField != _inField && _recording) { _inField = inField; _send->setRecordActive(_inField); } _inReplyEditForward = inReplyEditForward; - _inPinnedMsg = inPinnedMsg; if (inClickable != _inClickable) { _inClickable = inClickable; setCursor(_inClickable ? style::cur_pointer : style::cur_default); @@ -4442,10 +4437,10 @@ void HistoryWidget::updateControlsGeometry() { const auto pinnedBarTop = _topBar->bottomNoMargins(); if (_pinnedBar) { - _pinnedBar->cancel->moveToLeft(width() - _pinnedBar->cancel->width(), pinnedBarTop); - _pinnedBar->shadow->setGeometryToLeft(0, pinnedBarTop + st::historyReplyHeight, width(), st::lineWidth); + _pinnedBar->move(0, pinnedBarTop); + _pinnedBar->resizeToWidth(width()); } - const auto contactStatusTop = pinnedBarTop + (_pinnedBar ? st::historyReplyHeight : 0); + const auto contactStatusTop = pinnedBarTop + (_pinnedBar ? _pinnedBar->height() : 0); if (_contactStatus) { _contactStatus->move(0, contactStatusTop); } @@ -4487,9 +4482,6 @@ void HistoryWidget::itemRemoved(not_null item) { while (item == _replyReturn) { calcNextReplyReturn(); } - if (_pinnedBar && item->id == _pinnedBar->msgId) { - pinnedMsgVisibilityUpdated(); - } if (_kbReplyTo && item == _kbReplyTo) { toggleKeyboard(); _kbReplyTo = nullptr; @@ -4509,9 +4501,6 @@ void HistoryWidget::itemEdited(not_null item) { if (item.get() == _replyEditMsg) { updateReplyEditTexts(true); } - if (_pinnedBar && item->id == _pinnedBar->msgId) { - updatePinnedBar(true); - } } void HistoryWidget::updateScrollColors() { @@ -4612,7 +4601,7 @@ void HistoryWidget::updateHistoryGeometry( auto newScrollHeight = height() - _topBar->height(); if (_pinnedBar) { - newScrollHeight -= st::historyReplyHeight; + newScrollHeight -= _pinnedBar->height(); } if (_contactStatus) { newScrollHeight -= _contactStatus->height(); @@ -4848,7 +4837,7 @@ int HistoryWidget::computeMaxFieldHeight() const { const auto available = height() - _topBar->height() - (_contactStatus ? _contactStatus->height() : 0) - - (_pinnedBar ? st::historyReplyHeight : 0) + - (_pinnedBar ? _pinnedBar->height() : 0) - ((_editMsgId || replyToId() || readyToForward() @@ -4980,9 +4969,9 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { } else { Ui::showPeerHistory(_peer, _editMsgId ? _editMsgId : replyToId()); } - } else if (_inPinnedMsg) { - Assert(_pinnedBar != nullptr); - Ui::showPeerHistory(_peer, _pinnedBar->msgId); + //} else if (_inPinnedMsg) { // #TODO pinned + // Assert(_pinnedBar != nullptr); + // Ui::showPeerHistory(_peer, _pinnedBar->msgId); } } @@ -5189,48 +5178,6 @@ void HistoryWidget::sendInlineResult( _field->setFocus(); } -HistoryWidget::PinnedBar::PinnedBar(MsgId msgId, HistoryWidget *parent) -: msgId(msgId) -, cancel(parent, st::historyReplyCancel) -, shadow(parent) { -} - -HistoryWidget::PinnedBar::~PinnedBar() { - cancel.destroyDelayed(); - shadow.destroyDelayed(); -} - -void HistoryWidget::updatePinnedBar(bool force) { - update(); - if (!_pinnedBar) { - return; - } - const auto messageId = _pinnedBar->msgId; - if (!force) { - if (_pinnedBar->msg) { - return; - } - } - - Assert(_history != nullptr); - if (!_pinnedBar->msg) { - _pinnedBar->msg = session().data().message( - _history->channelId(), - messageId); - } - if (_pinnedBar->msg) { - _pinnedBar->text.setText( - st::messageTextStyle, - _pinnedBar->msg->inReplyText(), - Ui::DialogTextOptions()); - update(); - } else if (force) { - destroyPinnedBar(); - _history->peer->removePinnedMessage(messageId); - updateControlsGeometry(); - } -} - void HistoryWidget::updatePinnedViewer() { if (_firstLoadRequest || _delayedShowAtRequest @@ -5264,80 +5211,90 @@ void HistoryWidget::setupPinnedTracker() { Expects(_history != nullptr); _pinnedTracker = std::make_unique(_history); - _pinnedTracker->shownMessageId( - ) | rpl::start_with_next([=](MsgId messageId) { - showPinnedMessage({ peerToChannel(_peer->id), messageId }); - }, _list->lifetime()); -} + _pinnedBar = std::make_unique( + this, + &session(), + _pinnedTracker->shownMessageId() | rpl::map([=](MsgId messageId) { + return FullMsgId{ peerToChannel(_peer->id), messageId }; + }), + true); -void HistoryWidget::showPinnedMessage(FullMsgId id) { - if (_pinnedId == id) { - return; - } - _pinnedId = id; - if (pinnedMsgVisibilityUpdated()) { + _pinnedBar->closeClicks( + ) | rpl::start_with_next([=] { + hidePinnedMessage(); + }, _pinnedBar->lifetime()); + + _pinnedBarHeight = 0; + _pinnedBar->heightValue( + ) | rpl::start_with_next([=](int height) { + _topDelta = (height - _pinnedBarHeight); + _pinnedBarHeight = height; updateHistoryGeometry(); - updateControlsVisibility(); updateControlsGeometry(); - this->update(); + _topDelta = 0; + }, _pinnedBar->lifetime()); + orderWidgets(); + if (_a_show.animating()) { + _pinnedBar->hide(); } } - -bool HistoryWidget::pinnedMsgVisibilityUpdated() { - auto result = false; - auto pinnedId = _pinnedId; - if (pinnedId && !_peer->canPinMessages()) { - const auto hiddenId = session().settings().hiddenPinnedMessageId( - _peer->id); - if (hiddenId == pinnedId.msg) { - pinnedId = FullMsgId(); - } else if (hiddenId) { - session().settings().setHiddenPinnedMessageId(_peer->id, 0); - session().saveSettings(); - } - } - if (pinnedId) { - if (!_pinnedBar) { - _pinnedBar = std::make_unique(pinnedId.msg, this); - if (_a_show.animating()) { - _pinnedBar->cancel->hide(); - _pinnedBar->shadow->hide(); - } else { - _pinnedBar->cancel->show(); - _pinnedBar->shadow->show(); - } - _pinnedBar->cancel->addClickHandler([=] { - hidePinnedMessage(); - }); - orderWidgets(); - - updatePinnedBar(); - result = true; - - const auto barTop = unreadBarTop(); - if (!barTop || _scroll->scrollTop() != *barTop) { - synteticScrollToY(_scroll->scrollTop() + st::historyReplyHeight); - } - } else if (_pinnedBar->msgId != pinnedId.msg) { - _pinnedBar->msgId = pinnedId.msg; - _pinnedBar->msg = nullptr; - _pinnedBar->text.clear(); - updatePinnedBar(); - } - if (!_pinnedBar->msg) { - requestMessageData(_pinnedBar->msgId); - } - } else if (_pinnedBar) { - destroyPinnedBar(); - result = true; - const auto barTop = unreadBarTop(); - if (!barTop || _scroll->scrollTop() != *barTop) { - synteticScrollToY(_scroll->scrollTop() - st::historyReplyHeight); - } - updateControlsGeometry(); - } - return result; -} +// +//bool HistoryWidget::pinnedMsgVisibilityUpdated() { +// auto result = false; +// auto pinnedId = _pinnedId; +// if (pinnedId && !_peer->canPinMessages()) { +// const auto hiddenId = session().settings().hiddenPinnedMessageId( +// _peer->id); +// if (hiddenId == pinnedId.msg) { +// pinnedId = FullMsgId(); +// } else if (hiddenId) { +// session().settings().setHiddenPinnedMessageId(_peer->id, 0); +// session().saveSettings(); +// } +// } +// if (pinnedId) { +// if (!_pinnedBar) { +// _pinnedBar = std::make_unique(pinnedId.msg, this); +// if (_a_show.animating()) { +// _pinnedBar->cancel->hide(); +// _pinnedBar->bar->widget()->hide(); +// _pinnedBar->shadow->hide(); +// } else { +// _pinnedBar->cancel->show(); +// _pinnedBar->bar->widget()->show(); +// _pinnedBar->shadow->show(); +// } +// _pinnedBar->cancel->addClickHandler([=] { +// hidePinnedMessage(); +// }); +// orderWidgets(); +// +// updatePinnedBar(); +// result = true; +// +// const auto barTop = unreadBarTop(); +// if (!barTop || _scroll->scrollTop() != *barTop) { +// synteticScrollToY(_scroll->scrollTop() + st::historyReplyHeight); +// } +// } else if (_pinnedBar->msgId != pinnedId.msg) { +// _pinnedBar->msgId = pinnedId.msg; +// _pinnedBar->msg = nullptr; +// updatePinnedBar(); +// } +// if (!_pinnedBar->msg) { +// requestMessageData(_pinnedBar->msgId); +// } +// } else if (_pinnedBar) { +// destroyPinnedBar(); +// result = true; +// const auto barTop = unreadBarTop(); +// if (!barTop || _scroll->scrollTop() != *barTop) { +// synteticScrollToY(_scroll->scrollTop() - st::historyReplyHeight); +// } +// updateControlsGeometry(); +// } +// return result; +//} void HistoryWidget::requestMessageData(MsgId msgId) { const auto callback = [=](ChannelData *channel, MsgId msgId) { @@ -5349,11 +5306,6 @@ void HistoryWidget::requestMessageData(MsgId msgId) { crl::guard(this, callback)); } -void HistoryWidget::destroyPinnedBar() { - _pinnedBar.reset(); - _inPinnedMsg = false; -} - bool HistoryWidget::sendExistingDocument( not_null document, Api::SendOptions options) { @@ -5622,25 +5574,25 @@ void HistoryWidget::UnpinMessage(not_null peer, MsgId msgId) { } void HistoryWidget::hidePinnedMessage() { - const auto pinnedId = _pinnedId; - if (!pinnedId) { - if (pinnedMsgVisibilityUpdated()) { - updateControlsGeometry(); - update(); - } - return; - } + //const auto pinnedId = _pinnedId; // #TODO pinned + //if (!pinnedId) { + // if (pinnedMsgVisibilityUpdated()) { + // updateControlsGeometry(); + // update(); + // } + // return; + //} - if (_peer->canPinMessages()) { - unpinMessage(pinnedId); - } else { - session().settings().setHiddenPinnedMessageId(_peer->id, pinnedId.msg); - session().saveSettings(); - if (pinnedMsgVisibilityUpdated()) { - updateControlsGeometry(); - update(); - } - } + //if (_peer->canPinMessages()) { + // unpinMessage(pinnedId); + //} else { + // session().settings().setHiddenPinnedMessageId(_peer->id, pinnedId.msg); + // session().saveSettings(); + // if (pinnedMsgVisibilityUpdated()) { + // updateControlsGeometry(); + // update(); + // } + //} } bool HistoryWidget::lastForceReplyReplied(const FullMsgId &replyTo) const { @@ -6080,13 +6032,12 @@ void HistoryWidget::updateTopBarSelection() { } void HistoryWidget::messageDataReceived(ChannelData *channel, MsgId msgId) { - if (!_peer || _peer->asChannel() != channel || !msgId) return; + if (!_peer || _peer->asChannel() != channel || !msgId) { + return; + } if (_editMsgId == msgId || _replyToId == msgId) { updateReplyEditTexts(true); } - if (_pinnedBar && _pinnedBar->msgId == msgId) { - updatePinnedBar(true); - } } void HistoryWidget::updateReplyEditText(not_null item) { @@ -6420,49 +6371,49 @@ void HistoryWidget::drawRecording(Painter &p, float64 recordActive) { p.setPen(anim::pen(st::historyRecordCancel, st::historyRecordCancelActive, 1. - recordActive)); p.drawText(left + (right - left - _recordCancelWidth) / 2, _attachToggle->y() + st::historyRecordTextTop + st::historyRecordFont->ascent, tr::lng_record_cancel(tr::now)); } - -void HistoryWidget::drawPinnedBar(Painter &p) { - Expects(_pinnedBar != nullptr); - - auto top = _topBar->bottomNoMargins(); - p.fillRect(myrtlrect(0, top, width(), st::historyReplyHeight), st::historyPinnedBg); - - top += st::msgReplyPadding.top(); - QRect rbar(myrtlrect(st::msgReplyBarSkip + st::msgReplyBarPos.x(), top + st::msgReplyBarPos.y(), st::msgReplyBarSize.width(), st::msgReplyBarSize.height())); - p.fillRect(rbar, st::msgInReplyBarColor); - - int32 left = st::msgReplyBarSkip + st::msgReplyBarSkip; - if (_pinnedBar->msg) { - const auto media = _pinnedBar->msg->media(); - if (media && media->hasReplyPreview()) { - if (const auto image = media->replyPreview()) { - QRect to(left, top, st::msgReplyBarSize.height(), st::msgReplyBarSize.height()); - p.drawPixmap(to.x(), to.y(), image->pixSingle(image->width() / cIntRetinaFactor(), image->height() / cIntRetinaFactor(), to.width(), to.height(), ImageRoundRadius::Small)); - } - left += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x(); - } - p.setPen(st::historyReplyNameFg); - p.setFont(st::msgServiceNameFont); - const auto poll = media ? media->poll() : nullptr; - const auto pinnedHeader = (_pinnedBar->msgId < _peer->topPinnedMessageId()) - ? tr::lng_pinned_previous(tr::now) - : !poll - ? tr::lng_pinned_message(tr::now) - : poll->quiz() - ? tr::lng_pinned_quiz(tr::now) - : tr::lng_pinned_poll(tr::now); - p.drawText(left, top + st::msgServiceNameFont->ascent, pinnedHeader); - - p.setPen(st::historyComposeAreaFg); - p.setTextPalette(st::historyComposeAreaPalette); - _pinnedBar->text.drawElided(p, left, top + st::msgServiceNameFont->height, width() - left - _pinnedBar->cancel->width() - st::msgReplyPadding.right()); - p.restoreTextPalette(); - } else { - p.setFont(st::msgDateFont); - p.setPen(st::historyComposeAreaFgService); - p.drawText(left, top + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2 + st::msgDateFont->ascent, st::msgDateFont->elided(tr::lng_profile_loading(tr::now), width() - left - _pinnedBar->cancel->width() - st::msgReplyPadding.right())); - } -} +// +//void HistoryWidget::drawPinnedBar(Painter &p) { +// Expects(_pinnedBar != nullptr); +// +// auto top = _topBar->bottomNoMargins(); +// p.fillRect(myrtlrect(0, top, width(), st::historyReplyHeight), st::historyPinnedBg); +// +// top += st::msgReplyPadding.top(); +// QRect rbar(myrtlrect(st::msgReplyBarSkip + st::msgReplyBarPos.x(), top + st::msgReplyBarPos.y(), st::msgReplyBarSize.width(), st::msgReplyBarSize.height())); +// p.fillRect(rbar, st::msgInReplyBarColor); +// +// //int32 left = st::msgReplyBarSkip + st::msgReplyBarSkip; +// //if (_pinnedBar->msg) { +// // const auto media = _pinnedBar->msg->media(); +// // if (media && media->hasReplyPreview()) { +// // if (const auto image = media->replyPreview()) { +// // QRect to(left, top, st::msgReplyBarSize.height(), st::msgReplyBarSize.height()); +// // p.drawPixmap(to.x(), to.y(), image->pixSingle(image->width() / cIntRetinaFactor(), image->height() / cIntRetinaFactor(), to.width(), to.height(), ImageRoundRadius::Small)); +// // } +// // left += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x(); +// // } +// // p.setPen(st::historyReplyNameFg); +// // p.setFont(st::msgServiceNameFont); +// // const auto poll = media ? media->poll() : nullptr; +// // const auto pinnedHeader = (_pinnedBar->msgId < _peer->topPinnedMessageId()) +// // ? tr::lng_pinned_previous(tr::now) +// // : !poll +// // ? tr::lng_pinned_message(tr::now) +// // : poll->quiz() +// // ? tr::lng_pinned_quiz(tr::now) +// // : tr::lng_pinned_poll(tr::now); +// // p.drawText(left, top + st::msgServiceNameFont->ascent, pinnedHeader); +// +// // p.setPen(st::historyComposeAreaFg); +// // p.setTextPalette(st::historyComposeAreaPalette); +// // _pinnedBar->text.drawElided(p, left, top + st::msgServiceNameFont->height, width() - left - _pinnedBar->cancel->width() - st::msgReplyPadding.right()); +// // p.restoreTextPalette(); +// //} else { +// // p.setFont(st::msgDateFont); +// // p.setPen(st::historyComposeAreaFgService); +// // p.drawText(left, top + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2 + st::msgDateFont->ascent, st::msgDateFont->elided(tr::lng_profile_loading(tr::now), width() - left - _pinnedBar->cancel->width() - st::msgReplyPadding.right())); +// //} +//} bool HistoryWidget::paintShowAnimationFrame() { auto progress = _a_show.value(1.); @@ -6513,9 +6464,6 @@ void HistoryWidget::paintEvent(QPaintEvent *e) { } else if (const auto error = writeRestriction()) { drawRestrictedWrite(p, *error); } - if (_pinnedBar && !_pinnedBar->cancel->isHidden()) { - drawPinnedBar(p); - } } else { const auto w = st::msgServiceFont->width(tr::lng_willbe_history(tr::now)) + st::msgPadding.left() diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index e23a4de00..db6ed145b 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -66,6 +66,7 @@ class SilentToggle; class FlatButton; class LinkButton; class RoundButton; +class MessageBar; namespace Toast { class Instance; } // namespace Toast @@ -94,6 +95,7 @@ class TopBarWidget; class ContactStatus; class Element; class PinnedTracker; +class PinnedBar; } // namespace HistoryView class DragArea; @@ -326,16 +328,6 @@ private slots: private: using TabbedPanel = ChatHelpers::TabbedPanel; using TabbedSelector = ChatHelpers::TabbedSelector; - struct PinnedBar { - PinnedBar(MsgId msgId, HistoryWidget *parent); - ~PinnedBar(); - - MsgId msgId = 0; - HistoryItem *msg = nullptr; - Ui::Text::String text; - object_ptr cancel; - object_ptr shadow; - }; enum ScrollChangeType { ScrollChangeNone, @@ -492,10 +484,6 @@ private: void updateReplyEditTexts(bool force = false); void updateReplyEditText(not_null item); - void showPinnedMessage(FullMsgId id); - void updatePinnedBar(bool force = false); - bool pinnedMsgVisibilityUpdated(); - void destroyPinnedBar(); void updatePinnedViewer(); void setupPinnedTracker(); @@ -511,7 +499,6 @@ private: int left, int top) const; void drawRecording(Painter &p, float64 recordActive); - void drawPinnedBar(Painter &p); void drawRestrictedWrite(Painter &p, const QString &error); bool paintShowAnimationFrame(); @@ -614,9 +601,9 @@ private: object_ptr _fieldBarCancel; - FullMsgId _pinnedId; - std::unique_ptr _pinnedBar; std::unique_ptr _pinnedTracker; + std::unique_ptr _pinnedBar; + int _pinnedBarHeight = 0; mtpRequestId _saveEditMsgRequestId = 0; @@ -705,7 +692,6 @@ private: bool _recording = false; bool _inField = false; bool _inReplyEditForward = false; - bool _inPinnedMsg = false; bool _inClickable = false; int _recordingSamples = 0; int _recordCancelWidth; diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp new file mode 100644 index 000000000..82aaafc42 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp @@ -0,0 +1,229 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/view/history_view_pinned_bar.h" + +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "data/data_session.h" +#include "data/data_changes.h" +#include "data/data_poll.h" +#include "ui/widgets/shadow.h" +#include "ui/widgets/buttons.h" +#include "history/history_item.h" +#include "history/history.h" +#include "apiwrap.h" +#include "styles/style_chat.h" + +namespace HistoryView { +namespace { + +[[nodiscard]] rpl::producer ContentByItem( + not_null item) { + return item->history()->session().changes().messageFlagsValue( + item, + Data::MessageUpdate::Flag::Edited + ) | rpl::map([=] { + const auto media = item->media(); + const auto poll = media ? media->poll() : nullptr; + return Ui::MessageBarContent{ + .id = item->id, + .title = ((item->id < item->history()->peer->topPinnedMessageId()) + ? tr::lng_pinned_previous(tr::now) + : !poll + ? tr::lng_pinned_message(tr::now) + : poll->quiz() + ? tr::lng_pinned_quiz(tr::now) + : tr::lng_pinned_poll(tr::now)), + .text = item->inReplyText(), + }; + }); +} + +[[nodiscard]] rpl::producer ContentByItemId( + not_null session, + FullMsgId id, + bool alreadyLoaded = false) { + if (!id) { + return rpl::single(Ui::MessageBarContent()); + } else if (const auto item = session->data().message(id)) { + return ContentByItem(item); + } else if (alreadyLoaded) { + return rpl::single(Ui::MessageBarContent()); // Deleted message?.. + } + auto load = rpl::make_producer([=](auto consumer) { + consumer.put_next(Ui::MessageBarContent{ + .text = tr::lng_contacts_loading(tr::now), + }); + const auto channel = id.channel + ? session->data().channel(id.channel).get() + : nullptr; + const auto callback = [=](ChannelData *channel, MsgId id) { + consumer.put_done(); + }; + session->api().requestMessageData(channel, id.msg, callback); + return rpl::lifetime(); + }); + return std::move( + load + ) | rpl::then(rpl::deferred([=] { + return ContentByItemId(session, id, true); + })); +} + +} // namespace + +PinnedBar::PinnedBar( + not_null parent, + not_null session, + rpl::producer itemId, + bool withClose) +: _wrap(parent, object_ptr(parent)) +, _close(withClose + ? std::make_unique( + _wrap.entity(), + st::historyReplyCancel) + : nullptr) +, _shadow(std::make_unique(_wrap.parentWidget())) { + _wrap.hide(anim::type::instant); + _shadow->hide(); + + _wrap.entity()->paintRequest( + ) | rpl::start_with_next([=](QRect clip) { + QPainter(_wrap.entity()).fillRect(clip, st::historyPinnedBg); + }, lifetime()); + _wrap.setAttribute(Qt::WA_OpaquePaintEvent); + + rpl::duplicate( + itemId + ) | rpl::distinct_until_changed( + ) | rpl::map([=](FullMsgId id) { + return ContentByItemId(session, id); + }) | rpl::flatten_latest( + ) | rpl::filter([=](const Ui::MessageBarContent &content) { + return !content.title.isEmpty() || !content.text.text.isEmpty(); + }) | rpl::start_with_next([=](Ui::MessageBarContent &&content) { + const auto creating = !_bar; + if (creating) { + createControls(); + } + _bar->set(std::move(content)); + if (creating) { + _bar->finishAnimating(); + } + }, lifetime()); + + std::move( + itemId + ) | rpl::map([=](FullMsgId id) { + return !id; + }) | rpl::start_with_next([=](bool hidden) { + _shouldBeShown = !hidden; + if (!_forceHidden) { + _wrap.toggle(_shouldBeShown, anim::type::normal); + } else if (!_shouldBeShown) { + _bar = nullptr; + } + }, lifetime()); +} + +void PinnedBar::createControls() { + Expects(!_bar); + + _bar = std::make_unique( + _wrap.entity(), + st::defaultMessageBar); + if (_close) { + _close->raise(); + } + + _bar->widget()->move(0, 0); + _bar->widget()->show(); + _wrap.entity()->resize(_wrap.entity()->width(), _bar->widget()->height()); + + _wrap.geometryValue( + ) | rpl::start_with_next([=](QRect rect) { + _shadow->setGeometry( + rect.x(), + rect.y() + rect.height(), + rect.width(), + st::lineWidth); + _bar->widget()->resizeToWidth( + rect.width() - (_close ? _close->width() : 0)); + const auto hidden = _wrap.isHidden() || !rect.height(); + if (_shadow->isHidden() != hidden) { + _shadow->setVisible(!hidden); + } + if (_close) { + _close->moveToRight(0, 0); + } + }, _bar->widget()->lifetime()); + + _wrap.shownValue( + ) | rpl::skip( + 1 + ) | rpl::filter([=](bool shown) { + return !shown && !_forceHidden; + }) | rpl::start_with_next([=] { + _bar = nullptr; + }, _bar->widget()->lifetime()); + + Ensures(_bar != nullptr); +} + +void PinnedBar::show() { + if (!_forceHidden) { + return; + } + _forceHidden = false; + if (_shouldBeShown) { + _wrap.show(anim::type::instant); + _shadow->show(); + } +} + +void PinnedBar::hide() { + if (_forceHidden) { + return; + } + _forceHidden = true; + _wrap.hide(anim::type::instant); + _shadow->hide(); +} + +void PinnedBar::raise() { + _wrap.raise(); + _shadow->raise(); +} + +void PinnedBar::move(int x, int y) { + _wrap.move(x, y); +} + +void PinnedBar::resizeToWidth(int width) { + _wrap.entity()->resizeToWidth(width); +} + +int PinnedBar::height() const { + return !_forceHidden + ? _wrap.height() + : _shouldBeShown + ? st::historyReplyHeight + : 0; +} + +rpl::producer PinnedBar::heightValue() const { + return _wrap.heightValue(); +} + +rpl::producer<> PinnedBar::closeClicks() const { + return !_close + ? (rpl::never<>() | rpl::type_erased()) + : (_close->clicks() | rpl::map([] { return rpl::empty_value(); })); +} + +} // namespace HistoryView \ No newline at end of file diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_bar.h b/Telegram/SourceFiles/history/view/history_view_pinned_bar.h new file mode 100644 index 000000000..e6e61e931 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_pinned_bar.h @@ -0,0 +1,59 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/wrap/slide_wrap.h" +#include "ui/chat/message_bar.h" + +namespace Main { +class Session; +} // namespace Main + +namespace Ui { +class IconButton; +class PlainShadow; +} // namespace Ui + +namespace HistoryView { + +class PinnedBar final { +public: + PinnedBar( + not_null parent, + not_null session, + rpl::producer itemId, + bool withClose = false); + + void show(); + void hide(); + void raise(); + + void move(int x, int y); + void resizeToWidth(int width); + [[nodiscard]] int height() const; + [[nodiscard]] rpl::producer heightValue() const; + [[nodiscard]] rpl::producer<> closeClicks() const; + + [[nodiscard]] rpl::lifetime &lifetime() { + return _wrap.lifetime(); + } + +private: + void createControls(); + + Ui::SlideWrap<> _wrap; + std::unique_ptr _bar; + std::unique_ptr _close; + std::unique_ptr _shadow; + rpl::event_stream _content; + bool _shouldBeShown = false; + bool _forceHidden = false; + +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/ui/chat/message_bar.cpp b/Telegram/SourceFiles/ui/chat/message_bar.cpp index cf4a27c60..05a02f16c 100644 --- a/Telegram/SourceFiles/ui/chat/message_bar.cpp +++ b/Telegram/SourceFiles/ui/chat/message_bar.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_options.h" #include "styles/style_chat.h" +#include "styles/palette.h" namespace Ui { @@ -205,7 +206,7 @@ void MessageBar::paint(Painter &p) { const auto imageShown = _animation ? _animation->imageShown.value(imageFinal) : imageFinal; - if (progress == 1. && imageShown == 1. && _animation) { + if (progress == 1. && imageShown == imageFinal && _animation) { _animation = nullptr; } const auto body = [&] { @@ -237,6 +238,13 @@ void MessageBar::paint(Painter &p) { ? (shiftTo - shiftFull) : (shiftTo + shiftFull); + const auto bar = QRect( + st::msgReplyBarSkip + st::msgReplyBarPos.x(), + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), + st::msgReplyBarSize.width(), + st::msgReplyBarSize.height()); + p.fillRect(bar, st::msgInReplyBarColor); + if (!_animation) { if (!_image.isNull()) { p.drawPixmap(image, _image); @@ -261,6 +269,7 @@ void MessageBar::paint(Painter &p) { _animation->imageFrom); p.setOpacity(progress); p.drawPixmap(rect.translated(0, shiftTo), _animation->imageTo); + p.setOpacity(1.); } else { p.drawPixmap(rect, _image); } @@ -277,6 +286,7 @@ void MessageBar::paint(Painter &p) { _animation->bodyOrTextFrom); p.setOpacity(progress); p.drawPixmap(body.x(), text.y() + shiftTo, _animation->bodyOrTextTo); + p.setOpacity(1.); } if (!_animation || _animation->bodyAnimation != BodyAnimation::Full) { p.setPen(_st.titleFg); @@ -289,6 +299,7 @@ void MessageBar::paint(Painter &p) { _animation->bodyOrTextFrom); p.setOpacity(progress); p.drawPixmap(body.x(), body.y() + shiftTo, _animation->bodyOrTextTo); + p.setOpacity(1.); } } From 91a041603746a4e115a328a29d75c3bf49aba505 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 12 Oct 2020 12:41:05 +0300 Subject: [PATCH 082/190] Implement local pinned bar hiding. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/boxes/confirm_box.cpp | 2 +- Telegram/SourceFiles/data/data_peer.cpp | 18 +- Telegram/SourceFiles/history/history.cpp | 7 +- .../SourceFiles/history/history_widget.cpp | 169 +++++++----------- Telegram/SourceFiles/history/history_widget.h | 1 + .../history/view/history_view_pinned_bar.cpp | 35 ++-- .../history/view/history_view_pinned_bar.h | 15 +- .../view/history_view_pinned_tracker.cpp | 27 ++- .../view/history_view_pinned_tracker.h | 23 ++- 10 files changed, 167 insertions(+), 131 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 8cfd5b8cb..556f2fd35 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -166,6 +166,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_pinned_quiz" = "Pinned quiz"; "lng_pinned_unpin_sure" = "Would you like to unpin this message?"; "lng_pinned_pin_sure" = "Would you like to pin this message?"; +"lng_pinned_pin_old_sure" = "Do you want to pin an older message while leaving a more recent one pinned?"; "lng_pinned_pin" = "Pin"; "lng_pinned_unpin" = "Unpin"; "lng_pinned_notify" = "Notify all members"; diff --git a/Telegram/SourceFiles/boxes/confirm_box.cpp b/Telegram/SourceFiles/boxes/confirm_box.cpp index a534f0400..b098ef100 100644 --- a/Telegram/SourceFiles/boxes/confirm_box.cpp +++ b/Telegram/SourceFiles/boxes/confirm_box.cpp @@ -448,7 +448,7 @@ PinMessageBox::PinMessageBox( , _text( this, (_pinningOld - ? "Do you want to pin an older message while leaving a more recent one pinned?" // #TODO pinned + ? tr::lng_pinned_pin_old_sure(tr::now) : tr::lng_pinned_pin_sure(tr::now)), st::boxLabel) { } diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index a74f95714..3e1490909 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "boxes/confirm_box.h" #include "main/main_session.h" +#include "main/main_session_settings.h" #include "main/main_account.h" #include "main/main_domain.h" #include "main/main_app_config.h" @@ -469,14 +470,12 @@ void PeerData::ensurePinnedMessagesCreated() { if (!_pinnedMessages) { _pinnedMessages = std::make_unique( peerToChannel(id)); - session().changes().peerUpdated(this, UpdateFlag::PinnedMessage); } } void PeerData::removeEmptyPinnedMessages() { if (_pinnedMessages && _pinnedMessages->empty()) { _pinnedMessages = nullptr; - session().changes().peerUpdated(this, UpdateFlag::PinnedMessage); } } @@ -489,18 +488,30 @@ bool PeerData::messageIdTooSmall(MsgId messageId) const { void PeerData::setTopPinnedMessageId(MsgId messageId) { if (messageIdTooSmall(messageId)) { + clearPinnedMessages(); return; } + if (session().settings().hiddenPinnedMessageId(id) != messageId) { + session().settings().setHiddenPinnedMessageId(id, 0); + session().saveSettingsDelayed(); + } ensurePinnedMessagesCreated(); _pinnedMessages->setTopId(messageId); + session().changes().peerUpdated(this, UpdateFlag::PinnedMessage); } void PeerData::clearPinnedMessages(MsgId lessThanId) { + if (lessThanId == ServerMaxMsgId + && session().settings().hiddenPinnedMessageId(id) != 0) { + session().settings().setHiddenPinnedMessageId(id, 0); + session().saveSettingsDelayed(); + } if (!_pinnedMessages) { return; } _pinnedMessages->clearLessThanId(lessThanId); removeEmptyPinnedMessages(); + session().changes().peerUpdated(this, UpdateFlag::PinnedMessage); } void PeerData::addPinnedMessage(MsgId messageId) { @@ -509,6 +520,7 @@ void PeerData::addPinnedMessage(MsgId messageId) { } ensurePinnedMessagesCreated(); _pinnedMessages->add(messageId); + session().changes().peerUpdated(this, UpdateFlag::PinnedMessage); } void PeerData::addPinnedSlice( @@ -532,6 +544,7 @@ void PeerData::addPinnedSlice( } ensurePinnedMessagesCreated(); _pinnedMessages->add(std::move(ids), noSkipRange, std::nullopt); + session().changes().peerUpdated(this, UpdateFlag::PinnedMessage); } void PeerData::removePinnedMessage(MsgId messageId) { @@ -540,6 +553,7 @@ void PeerData::removePinnedMessage(MsgId messageId) { } _pinnedMessages->remove(messageId); removeEmptyPinnedMessages(); + session().changes().peerUpdated(this, UpdateFlag::PinnedMessage); } bool PeerData::canExportChatHistory() const { diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 973e26aff..847d512a9 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -1005,9 +1005,12 @@ void History::applyServiceChanges( case mtpc_messageActionPinMessage: { if (const auto replyTo = data.vreply_to()) { replyTo->match([&](const MTPDmessageReplyHeader &data) { + const auto id = data.vreply_to_msg_id().v; if (item) { - //item->history()->peer->setTopPinnedMessageId( // #TODO pinned - // data.vreply_to_msg_id().v); + item->history()->peer->addPinnedSlice( + { id }, + { id, ServerMaxMsgId }, + std::nullopt); } }); } diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index ff32dd3e1..705ff00fd 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -563,6 +563,7 @@ HistoryWidget::HistoryWidget( | UpdateFlag::ChannelLinkedChat | UpdateFlag::Slowmode | UpdateFlag::BotStartToken + | UpdateFlag::PinnedMessage ) | rpl::filter([=](const Data::PeerUpdate &update) { return (update.peer.get() == _peer); }) | rpl::map([](const Data::PeerUpdate &update) { @@ -603,6 +604,9 @@ HistoryWidget::HistoryWidget( | UpdateFlag::ChannelLinkedChat)) { handlePeerUpdate(); } + if (_pinnedTracker && (flags & UpdateFlag::PinnedMessage)) { + checkPinnedBarState(); + } }, lifetime()); rpl::merge( @@ -5211,12 +5215,50 @@ void HistoryWidget::setupPinnedTracker() { Expects(_history != nullptr); _pinnedTracker = std::make_unique(_history); + _pinnedBar = nullptr; + checkPinnedBarState(); +} + +void HistoryWidget::checkPinnedBarState() { + Expects(_pinnedTracker != nullptr); + + const auto hiddenId = _peer->canPinMessages() + ? MsgId(0) + : session().settings().hiddenPinnedMessageId(_peer->id); + const auto currentPinnedId = _peer->topPinnedMessageId(); + if (currentPinnedId == hiddenId) { + if (_pinnedBar) { + _pinnedTracker->reset(); + auto qobject = base::unique_qptr{ + Ui::WrapAsQObject(this, std::move(_pinnedBar)).get() + }; + auto destroyer = [this, object = std::move(qobject)]() mutable { + object = nullptr; + updateHistoryGeometry(); + updateControlsGeometry(); + }; + base::call_delayed( + st::defaultMessageBar.duration, + this, + std::move(destroyer)); + } + return; + } + if (_pinnedBar || !currentPinnedId) { + return; + } + + auto shown = _pinnedTracker->shownMessageId( + ) | rpl::map([=](HistoryView::PinnedId messageId) { + return HistoryView::PinnedBarId{ + FullMsgId{ peerToChannel(_peer->id), messageId.message }, + messageId.type + }; + }); _pinnedBar = std::make_unique( this, &session(), - _pinnedTracker->shownMessageId() | rpl::map([=](MsgId messageId) { - return FullMsgId{ peerToChannel(_peer->id), messageId }; - }), + std::move(shown), true); _pinnedBar->closeClicks( @@ -5233,68 +5275,13 @@ void HistoryWidget::setupPinnedTracker() { updateControlsGeometry(); _topDelta = 0; }, _pinnedBar->lifetime()); + orderWidgets(); + if (_a_show.animating()) { _pinnedBar->hide(); } } -// -//bool HistoryWidget::pinnedMsgVisibilityUpdated() { -// auto result = false; -// auto pinnedId = _pinnedId; -// if (pinnedId && !_peer->canPinMessages()) { -// const auto hiddenId = session().settings().hiddenPinnedMessageId( -// _peer->id); -// if (hiddenId == pinnedId.msg) { -// pinnedId = FullMsgId(); -// } else if (hiddenId) { -// session().settings().setHiddenPinnedMessageId(_peer->id, 0); -// session().saveSettings(); -// } -// } -// if (pinnedId) { -// if (!_pinnedBar) { -// _pinnedBar = std::make_unique(pinnedId.msg, this); -// if (_a_show.animating()) { -// _pinnedBar->cancel->hide(); -// _pinnedBar->bar->widget()->hide(); -// _pinnedBar->shadow->hide(); -// } else { -// _pinnedBar->cancel->show(); -// _pinnedBar->bar->widget()->show(); -// _pinnedBar->shadow->show(); -// } -// _pinnedBar->cancel->addClickHandler([=] { -// hidePinnedMessage(); -// }); -// orderWidgets(); -// -// updatePinnedBar(); -// result = true; -// -// const auto barTop = unreadBarTop(); -// if (!barTop || _scroll->scrollTop() != *barTop) { -// synteticScrollToY(_scroll->scrollTop() + st::historyReplyHeight); -// } -// } else if (_pinnedBar->msgId != pinnedId.msg) { -// _pinnedBar->msgId = pinnedId.msg; -// _pinnedBar->msg = nullptr; -// updatePinnedBar(); -// } -// if (!_pinnedBar->msg) { -// requestMessageData(_pinnedBar->msgId); -// } -// } else if (_pinnedBar) { -// destroyPinnedBar(); -// result = true; -// const auto barTop = unreadBarTop(); -// if (!barTop || _scroll->scrollTop() != *barTop) { -// synteticScrollToY(_scroll->scrollTop() - st::historyReplyHeight); -// } -// updateControlsGeometry(); -// } -// return result; -//} void HistoryWidget::requestMessageData(MsgId msgId) { const auto callback = [=](ChannelData *channel, MsgId msgId) { @@ -5574,25 +5561,25 @@ void HistoryWidget::UnpinMessage(not_null peer, MsgId msgId) { } void HistoryWidget::hidePinnedMessage() { - //const auto pinnedId = _pinnedId; // #TODO pinned - //if (!pinnedId) { - // if (pinnedMsgVisibilityUpdated()) { - // updateControlsGeometry(); - // update(); - // } - // return; - //} + Expects(_pinnedBar != nullptr); - //if (_peer->canPinMessages()) { - // unpinMessage(pinnedId); - //} else { - // session().settings().setHiddenPinnedMessageId(_peer->id, pinnedId.msg); - // session().saveSettings(); - // if (pinnedMsgVisibilityUpdated()) { - // updateControlsGeometry(); - // update(); - // } - //} + const auto id = _pinnedTracker->currentMessageId(); + if (!id.message) { + return; + } + if (_peer->canPinMessages()) { + unpinMessage({ peerToChannel(_peer->id), id.message }); + } else { + const auto top = _peer->topPinnedMessageId(); + if (top) { + session().settings().setHiddenPinnedMessageId(_peer->id, top); + session().saveSettingsDelayed(); + + checkPinnedBarState(); + } else { + session().api().requestFullPeer(_peer); + } + } } bool HistoryWidget::lastForceReplyReplied(const FullMsgId &replyTo) const { @@ -6373,16 +6360,6 @@ void HistoryWidget::drawRecording(Painter &p, float64 recordActive) { } // //void HistoryWidget::drawPinnedBar(Painter &p) { -// Expects(_pinnedBar != nullptr); -// -// auto top = _topBar->bottomNoMargins(); -// p.fillRect(myrtlrect(0, top, width(), st::historyReplyHeight), st::historyPinnedBg); -// -// top += st::msgReplyPadding.top(); -// QRect rbar(myrtlrect(st::msgReplyBarSkip + st::msgReplyBarPos.x(), top + st::msgReplyBarPos.y(), st::msgReplyBarSize.width(), st::msgReplyBarSize.height())); -// p.fillRect(rbar, st::msgInReplyBarColor); -// -// //int32 left = st::msgReplyBarSkip + st::msgReplyBarSkip; // //if (_pinnedBar->msg) { // // const auto media = _pinnedBar->msg->media(); // // if (media && media->hasReplyPreview()) { @@ -6392,22 +6369,6 @@ void HistoryWidget::drawRecording(Painter &p, float64 recordActive) { // // } // // left += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x(); // // } -// // p.setPen(st::historyReplyNameFg); -// // p.setFont(st::msgServiceNameFont); -// // const auto poll = media ? media->poll() : nullptr; -// // const auto pinnedHeader = (_pinnedBar->msgId < _peer->topPinnedMessageId()) -// // ? tr::lng_pinned_previous(tr::now) -// // : !poll -// // ? tr::lng_pinned_message(tr::now) -// // : poll->quiz() -// // ? tr::lng_pinned_quiz(tr::now) -// // : tr::lng_pinned_poll(tr::now); -// // p.drawText(left, top + st::msgServiceNameFont->ascent, pinnedHeader); -// -// // p.setPen(st::historyComposeAreaFg); -// // p.setTextPalette(st::historyComposeAreaPalette); -// // _pinnedBar->text.drawElided(p, left, top + st::msgServiceNameFont->height, width() - left - _pinnedBar->cancel->width() - st::msgReplyPadding.right()); -// // p.restoreTextPalette(); // //} else { // // p.setFont(st::msgDateFont); // // p.setPen(st::historyComposeAreaFgService); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index db6ed145b..1be6ab3bc 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -486,6 +486,7 @@ private: void updatePinnedViewer(); void setupPinnedTracker(); + void checkPinnedBarState(); void sendInlineResult( not_null result, diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp index 82aaafc42..c8af04b24 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_poll.h" #include "ui/widgets/shadow.h" #include "ui/widgets/buttons.h" +#include "history/view/history_view_pinned_tracker.h" #include "history/history_item.h" #include "history/history.h" #include "apiwrap.h" @@ -23,7 +24,8 @@ namespace HistoryView { namespace { [[nodiscard]] rpl::producer ContentByItem( - not_null item) { + not_null item, + PinnedIdType type) { return item->history()->session().changes().messageFlagsValue( item, Data::MessageUpdate::Flag::Edited @@ -32,7 +34,9 @@ namespace { const auto poll = media ? media->poll() : nullptr; return Ui::MessageBarContent{ .id = item->id, - .title = ((item->id < item->history()->peer->topPinnedMessageId()) + .title = ((type == PinnedIdType::First) + ? "First message" + : (type == PinnedIdType::Middle) ? tr::lng_pinned_previous(tr::now) : !poll ? tr::lng_pinned_message(tr::now) @@ -46,12 +50,12 @@ namespace { [[nodiscard]] rpl::producer ContentByItemId( not_null session, - FullMsgId id, + PinnedBarId id, bool alreadyLoaded = false) { - if (!id) { + if (!id.message) { return rpl::single(Ui::MessageBarContent()); - } else if (const auto item = session->data().message(id)) { - return ContentByItem(item); + } else if (const auto item = session->data().message(id.message)) { + return ContentByItem(item, id.type); } else if (alreadyLoaded) { return rpl::single(Ui::MessageBarContent()); // Deleted message?.. } @@ -59,13 +63,13 @@ namespace { consumer.put_next(Ui::MessageBarContent{ .text = tr::lng_contacts_loading(tr::now), }); - const auto channel = id.channel - ? session->data().channel(id.channel).get() + const auto channel = id.message.channel + ? session->data().channel(id.message.channel).get() : nullptr; const auto callback = [=](ChannelData *channel, MsgId id) { consumer.put_done(); }; - session->api().requestMessageData(channel, id.msg, callback); + session->api().requestMessageData(channel, id.message.msg, callback); return rpl::lifetime(); }); return std::move( @@ -80,7 +84,7 @@ namespace { PinnedBar::PinnedBar( not_null parent, not_null session, - rpl::producer itemId, + rpl::producer itemId, bool withClose) : _wrap(parent, object_ptr(parent)) , _close(withClose @@ -101,7 +105,7 @@ PinnedBar::PinnedBar( rpl::duplicate( itemId ) | rpl::distinct_until_changed( - ) | rpl::map([=](FullMsgId id) { + ) | rpl::map([=](PinnedBarId id) { return ContentByItemId(session, id); }) | rpl::flatten_latest( ) | rpl::filter([=](const Ui::MessageBarContent &content) { @@ -119,15 +123,18 @@ PinnedBar::PinnedBar( std::move( itemId - ) | rpl::map([=](FullMsgId id) { - return !id; - }) | rpl::start_with_next([=](bool hidden) { + ) | rpl::map([=](PinnedBarId id) { + return !id.message; + }) | rpl::start_with_next_done([=](bool hidden) { _shouldBeShown = !hidden; if (!_forceHidden) { _wrap.toggle(_shouldBeShown, anim::type::normal); } else if (!_shouldBeShown) { _bar = nullptr; } + }, [=] { + _forceHidden = true; + _wrap.toggle(false, anim::type::normal); }, lifetime()); } diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_bar.h b/Telegram/SourceFiles/history/view/history_view_pinned_bar.h index e6e61e931..397cbc92f 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_bar.h +++ b/Telegram/SourceFiles/history/view/history_view_pinned_bar.h @@ -21,12 +21,25 @@ class PlainShadow; namespace HistoryView { +enum class PinnedIdType; +struct PinnedBarId { + FullMsgId message; + PinnedIdType type = PinnedIdType(); + + bool operator<(const PinnedBarId &other) const { + return std::tie(message, type) < std::tie(other.message, other.type); + } + bool operator==(const PinnedBarId &other) const { + return std::tie(message, type) == std::tie(other.message, other.type); + } +}; + class PinnedBar final { public: PinnedBar( not_null parent, not_null session, - rpl::producer itemId, + rpl::producer itemId, bool withClose = false); void show(); diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp index ea6f076d7..bb18c8356 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp @@ -40,15 +40,23 @@ PinnedTracker::~PinnedTracker() { _history->owner().histories().cancelRequest(_afterRequestId); } -rpl::producer PinnedTracker::shownMessageId() const { +rpl::producer PinnedTracker::shownMessageId() const { return _current.value(); } +void PinnedTracker::reset() { + _current.reset(currentMessageId()); +} + +PinnedId PinnedTracker::currentMessageId() const { + return _current.current(); +} + void PinnedTracker::refreshData() { const auto now = _history->peer->currentPinnedMessages(); if (!now) { _dataLifetime.destroy(); - _current = MsgId(0); + _current = PinnedId(); } else if (_data.get() != now) { _dataLifetime.destroy(); _data = now; @@ -65,7 +73,7 @@ void PinnedTracker::trackAround(MsgId messageId) { _dataLifetime.destroy(); _aroundId = messageId; if (!_aroundId) { - _current = MsgId(0); + _current = PinnedId(); } else if (const auto now = _data.get()) { setupViewer(now); } @@ -90,10 +98,19 @@ void PinnedTracker::setupViewer(not_null data) { Data::LoadDirection::After, empty ? _aroundId : snapshot.ids.back()); } + if (snapshot.ids.empty()) { + _current = PinnedId(); + return; + } + const auto type = (!after && (snapshot.skippedAfter == 0)) + ? PinnedIdType::Last + : (before < 2 && (snapshot.skippedBefore == 0)) + ? PinnedIdType::First + : PinnedIdType::Middle; if (i != begin(snapshot.ids)) { - _current = *(i - 1); + _current = PinnedId{ *(i - 1), type }; } else if (snapshot.skippedBefore == 0) { - _current = 0; + _current = PinnedId{ snapshot.ids.front(), type }; } }, _dataLifetime); } diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_tracker.h b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.h index e34df6af4..1fbf061b4 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_tracker.h +++ b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.h @@ -16,13 +16,32 @@ enum class LoadDirection : char; namespace HistoryView { +enum class PinnedIdType { + First, + Middle, + Last, +}; +struct PinnedId { + MsgId message = 0; + PinnedIdType type = PinnedIdType::Middle; + + bool operator<(const PinnedId &other) const { + return std::tie(message, type) < std::tie(other.message, other.type); + } + bool operator==(const PinnedId &other) const { + return std::tie(message, type) == std::tie(other.message, other.type); + } +}; + class PinnedTracker final { public: explicit PinnedTracker(not_null history); ~PinnedTracker(); - [[nodiscard]] rpl::producer shownMessageId() const; + [[nodiscard]] rpl::producer shownMessageId() const; + [[nodiscard]] PinnedId currentMessageId() const; void trackAround(MsgId messageId); + void reset(); private: void refreshData(); @@ -36,7 +55,7 @@ private: const not_null _history; base::weak_ptr _data; - rpl::variable _current = MsgId(); + rpl::variable _current; rpl::lifetime _dataLifetime; MsgId _aroundId = 0; From 9b4b15ee6d25037cecb71893b921640f60a447bf Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 12 Oct 2020 15:28:54 +0300 Subject: [PATCH 083/190] Handle pinned bar clicks. --- Telegram/SourceFiles/data/data_messages.cpp | 9 +++++++ .../SourceFiles/history/history_widget.cpp | 15 ++++++----- .../history/view/history_view_pinned_bar.cpp | 26 +++++++++++++++++-- .../history/view/history_view_pinned_bar.h | 2 ++ Telegram/SourceFiles/ui/chat/message_bar.cpp | 26 ++++++++++++++----- 5 files changed, 62 insertions(+), 16 deletions(-) diff --git a/Telegram/SourceFiles/data/data_messages.cpp b/Telegram/SourceFiles/data/data_messages.cpp index 65ef717c9..6e312cbd9 100644 --- a/Telegram/SourceFiles/data/data_messages.cpp +++ b/Telegram/SourceFiles/data/data_messages.cpp @@ -135,6 +135,7 @@ void MessagesList::addSlice( } void MessagesList::removeOne(MessagePosition messageId) { + auto update = MessagesSliceUpdate(); auto slice = ranges::lower_bound( _slices, messageId, @@ -144,10 +145,16 @@ void MessagesList::removeOne(MessagePosition messageId) { _slices.modify(slice, [&](Slice &slice) { return slice.messages.remove(messageId); }); + update.messages = &slice->messages; + update.range = slice->range; } if (_count) { --*_count; } + update.count = _count; + if (update.messages) { + _sliceUpdated.fire(std::move(update)); + } } void MessagesList::removeAll(ChannelId channelId) { @@ -188,6 +195,8 @@ void MessagesList::removeLessThan(MessagePosition messageId) { } }); break; + } else { + break; } } if (removed && _count) { diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 705ff00fd..a92dd5ab7 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -4973,9 +4973,6 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { } else { Ui::showPeerHistory(_peer, _editMsgId ? _editMsgId : replyToId()); } - //} else if (_inPinnedMsg) { // #TODO pinned - // Assert(_pinnedBar != nullptr); - // Ui::showPeerHistory(_peer, _pinnedBar->msgId); } } @@ -5266,6 +5263,14 @@ void HistoryWidget::checkPinnedBarState() { hidePinnedMessage(); }, _pinnedBar->lifetime()); + _pinnedBar->barClicks( + ) | rpl::start_with_next([=] { + const auto id = _pinnedTracker->currentMessageId(); + if (id.message) { + Ui::showPeerHistory(_peer, id.message); + } + }, _pinnedBar->lifetime()); + _pinnedBarHeight = 0; _pinnedBar->heightValue( ) | rpl::start_with_next([=](int height) { @@ -6369,10 +6374,6 @@ void HistoryWidget::drawRecording(Painter &p, float64 recordActive) { // // } // // left += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x(); // // } -// //} else { -// // p.setFont(st::msgDateFont); -// // p.setPen(st::historyComposeAreaFgService); -// // p.drawText(left, top + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2 + st::msgDateFont->ascent, st::msgDateFont->elided(tr::lng_profile_loading(tr::now), width() - left - _pinnedBar->cancel->width() - st::msgReplyPadding.right())); // //} //} diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp index c8af04b24..824705a77 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp @@ -35,7 +35,7 @@ namespace { return Ui::MessageBarContent{ .id = item->id, .title = ((type == PinnedIdType::First) - ? "First message" + ? tr::lng_pinned_previous(tr::now) // #TODO pinned first? : (type == PinnedIdType::Middle) ? tr::lng_pinned_previous(tr::now) : !poll @@ -148,6 +148,24 @@ void PinnedBar::createControls() { _close->raise(); } + // Clicks. + _bar->widget()->setCursor(style::cur_pointer); + _bar->widget()->events( + ) | rpl::filter([=](not_null event) { + return (event->type() == QEvent::MouseButtonPress); + }) | rpl::map([=] { + return _bar->widget()->events( + ) | rpl::filter([=](not_null event) { + return (event->type() == QEvent::MouseButtonRelease); + }) | rpl::take(1) | rpl::filter([=](not_null event) { + return _bar->widget()->rect().contains( + static_cast(event.get())->pos()); + }); + }) | rpl::flatten_latest( + ) | rpl::map([] { + return rpl::empty_value(); + }) | rpl::start_to_stream(_barClicks, _bar->widget()->lifetime()); + _bar->widget()->move(0, 0); _bar->widget()->show(); _wrap.entity()->resize(_wrap.entity()->width(), _bar->widget()->height()); @@ -233,4 +251,8 @@ rpl::producer<> PinnedBar::closeClicks() const { : (_close->clicks() | rpl::map([] { return rpl::empty_value(); })); } -} // namespace HistoryView \ No newline at end of file +rpl::producer<> PinnedBar::barClicks() const { + return _barClicks.events(); +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_bar.h b/Telegram/SourceFiles/history/view/history_view_pinned_bar.h index 397cbc92f..469e6f2a1 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_bar.h +++ b/Telegram/SourceFiles/history/view/history_view_pinned_bar.h @@ -51,6 +51,7 @@ public: [[nodiscard]] int height() const; [[nodiscard]] rpl::producer heightValue() const; [[nodiscard]] rpl::producer<> closeClicks() const; + [[nodiscard]] rpl::producer<> barClicks() const; [[nodiscard]] rpl::lifetime &lifetime() { return _wrap.lifetime(); @@ -64,6 +65,7 @@ private: std::unique_ptr _close; std::unique_ptr _shadow; rpl::event_stream _content; + rpl::event_stream<> _barClicks; bool _shouldBeShown = false; bool _forceHidden = false; diff --git a/Telegram/SourceFiles/ui/chat/message_bar.cpp b/Telegram/SourceFiles/ui/chat/message_bar.cpp index 05a02f16c..1cb11a7ae 100644 --- a/Telegram/SourceFiles/ui/chat/message_bar.cpp +++ b/Telegram/SourceFiles/ui/chat/message_bar.cpp @@ -49,12 +49,13 @@ MessageBar::BodyAnimation MessageBar::DetectBodyAnimationType( const auto now = currentAnimation ? currentAnimation->bodyAnimation : BodyAnimation::None; + const auto somethingChanged = (currentContent.text != nextContent.text) + || (currentContent.id != nextContent.id); return (now == BodyAnimation::Full - || currentContent.title != nextContent.title) + || currentContent.title != nextContent.title + || (currentContent.title.isEmpty() && somethingChanged)) ? BodyAnimation::Full - : (now == BodyAnimation::Text - || currentContent.text != nextContent.text - || currentContent.id != nextContent.id) + : (now == BodyAnimation::Text || somethingChanged) ? BodyAnimation::Text : BodyAnimation::None; } @@ -275,9 +276,20 @@ void MessageBar::paint(Painter &p) { } } if (!_animation || _animation->bodyAnimation == BodyAnimation::None) { - p.setPen(_st.textFg); - p.setTextPalette(_st.textPalette); - _text.drawLeftElided(p, body.x(), text.y(), body.width(), width); + if (_title.isEmpty()) { + // "Loading..." state. + p.setPen(st::historyComposeAreaFgService); + _text.drawLeftElided( + p, + body.x(), + body.y() + (body.height() - st::normalFont->height) / 2, + body.width(), + width); + } else { + p.setPen(_st.textFg); + p.setTextPalette(_st.textPalette); + _text.drawLeftElided(p, body.x(), text.y(), body.width(), width); + } } else if (_animation->bodyAnimation == BodyAnimation::Text) { p.setOpacity(1. - progress); p.drawPixmap( From 37fb94cbfb28e31ab2465846d64e67f8733f1f7f Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 12 Oct 2020 16:24:45 +0300 Subject: [PATCH 084/190] Load and show image previews in pinned bar. --- Telegram/SourceFiles/data/data_document.cpp | 9 ++ Telegram/SourceFiles/data/data_document.h | 1 + .../SourceFiles/data/data_media_types.cpp | 37 ++++++++ Telegram/SourceFiles/data/data_media_types.h | 6 ++ Telegram/SourceFiles/data/data_photo.cpp | 7 ++ Telegram/SourceFiles/data/data_photo.h | 1 + .../SourceFiles/data/data_reply_preview.cpp | 4 + .../SourceFiles/data/data_reply_preview.h | 1 + .../history/view/history_view_pinned_bar.cpp | 85 +++++++++++++++---- 9 files changed, 136 insertions(+), 15 deletions(-) diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index 56aee3cd0..38a511a8e 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -1266,6 +1266,15 @@ Image *DocumentData::getReplyPreview(Data::FileOrigin origin) { return _replyPreview->image(origin); } +bool DocumentData::replyPreviewLoaded() const { + if (!hasThumbnail()) { + return true; + } else if (!_replyPreview) { + return false; + } + return _replyPreview->loaded(); +} + StickerData *DocumentData::sticker() const { return (type == StickerDocument) ? static_cast(_additional.get()) diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index 4c065e476..e8b5b905f 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -126,6 +126,7 @@ public: [[nodiscard]] bool saveToCache() const; [[nodiscard]] Image *getReplyPreview(Data::FileOrigin origin); + [[nodiscard]] bool replyPreviewLoaded() const; [[nodiscard]] StickerData *sticker() const; [[nodiscard]] Data::FileOrigin stickerSetOrigin() const; diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 40309e4c5..c87b7b733 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -209,6 +209,10 @@ Image *Media::replyPreview() const { return nullptr; } +bool Media::replyPreviewLoaded() const { + return true; +} + bool Media::allowsForward() const { return true; } @@ -312,6 +316,10 @@ Image *MediaPhoto::replyPreview() const { return _photo->getReplyPreview(parent()->fullId()); } +bool MediaPhoto::replyPreviewLoaded() const { + return _photo->replyPreviewLoaded(); +} + QString MediaPhoto::notificationText() const { return WithCaptionNotificationText( tr::lng_in_dlg_photo(tr::now), @@ -476,6 +484,10 @@ Image *MediaFile::replyPreview() const { return _document->getReplyPreview(parent()->fullId()); } +bool MediaFile::replyPreviewLoaded() const { + return _document->replyPreviewLoaded(); +} + QString MediaFile::chatListText() const { if (const auto sticker = _document->sticker()) { return Media::chatListText(); @@ -987,6 +999,15 @@ Image *MediaWebPage::replyPreview() const { return nullptr; } +bool MediaWebPage::replyPreviewLoaded() const { + if (const auto document = MediaWebPage::document()) { + return document->replyPreviewLoaded(); + } else if (const auto photo = MediaWebPage::photo()) { + return photo->replyPreviewLoaded(); + } + return true; +} + QString MediaWebPage::chatListText() const { return notificationText(); } @@ -1051,6 +1072,15 @@ Image *MediaGame::replyPreview() const { return nullptr; } +bool MediaGame::replyPreviewLoaded() const { + if (const auto document = _game->document) { + return document->replyPreviewLoaded(); + } else if (const auto photo = _game->photo) { + return photo->replyPreviewLoaded(); + } + return true; +} + QString MediaGame::notificationText() const { // Add a game controller emoji before game title. auto result = QString(); @@ -1153,6 +1183,13 @@ Image *MediaInvoice::replyPreview() const { return nullptr; } +bool MediaInvoice::replyPreviewLoaded() const { + if (const auto photo = _invoice.photo) { + return photo->replyPreviewLoaded(); + } + return true; +} + QString MediaInvoice::notificationText() const { return _invoice.title; } diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index caba0e25c..5037a2592 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -88,6 +88,7 @@ public: virtual bool canBeGrouped() const; virtual bool hasReplyPreview() const; virtual Image *replyPreview() const; + virtual bool replyPreviewLoaded() const; // Returns text with link-start and link-end commands for service-color highlighting. // Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text" virtual QString chatListText() const; @@ -143,6 +144,7 @@ public: bool canBeGrouped() const override; bool hasReplyPreview() const override; Image *replyPreview() const override; + bool replyPreviewLoaded() const override; QString chatListText() const override; QString notificationText() const override; QString pinnedTextSubstring() const override; @@ -180,6 +182,7 @@ public: bool canBeGrouped() const override; bool hasReplyPreview() const override; Image *replyPreview() const override; + bool replyPreviewLoaded() const override; QString chatListText() const override; QString notificationText() const override; QString pinnedTextSubstring() const override; @@ -311,6 +314,7 @@ public: bool hasReplyPreview() const override; Image *replyPreview() const override; + bool replyPreviewLoaded() const override; QString chatListText() const override; QString notificationText() const override; QString pinnedTextSubstring() const override; @@ -341,6 +345,7 @@ public: bool hasReplyPreview() const override; Image *replyPreview() const override; + bool replyPreviewLoaded() const override; QString notificationText() const override; QString pinnedTextSubstring() const override; TextForMimeData clipboardText() const override; @@ -377,6 +382,7 @@ public: bool hasReplyPreview() const override; Image *replyPreview() const override; + bool replyPreviewLoaded() const override; QString notificationText() const override; QString pinnedTextSubstring() const override; TextForMimeData clipboardText() const override; diff --git a/Telegram/SourceFiles/data/data_photo.cpp b/Telegram/SourceFiles/data/data_photo.cpp index d5ede173c..b1d67d1cd 100644 --- a/Telegram/SourceFiles/data/data_photo.cpp +++ b/Telegram/SourceFiles/data/data_photo.cpp @@ -205,6 +205,13 @@ Image *PhotoData::getReplyPreview(Data::FileOrigin origin) { return _replyPreview->image(origin); } +bool PhotoData::replyPreviewLoaded() const { + if (!_replyPreview) { + return false; + } + return _replyPreview->loaded(); +} + void PhotoData::setRemoteLocation( int32 dc, uint64 access, diff --git a/Telegram/SourceFiles/data/data_photo.h b/Telegram/SourceFiles/data/data_photo.h index 09263dcf9..bcd3f9ccf 100644 --- a/Telegram/SourceFiles/data/data_photo.h +++ b/Telegram/SourceFiles/data/data_photo.h @@ -65,6 +65,7 @@ public: [[nodiscard]] bool waitingForAlbum() const; [[nodiscard]] Image *getReplyPreview(Data::FileOrigin origin); + [[nodiscard]] bool replyPreviewLoaded() const; void setRemoteLocation( int32 dc, diff --git a/Telegram/SourceFiles/data/data_reply_preview.cpp b/Telegram/SourceFiles/data/data_reply_preview.cpp index 960328506..1067e2346 100644 --- a/Telegram/SourceFiles/data/data_reply_preview.cpp +++ b/Telegram/SourceFiles/data/data_reply_preview.cpp @@ -107,4 +107,8 @@ Image *ReplyPreview::image(Data::FileOrigin origin) { return _image.get(); } +bool ReplyPreview::loaded() const { + return _checked; +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_reply_preview.h b/Telegram/SourceFiles/data/data_reply_preview.h index 3c31cbe84..01314e7ef 100644 --- a/Telegram/SourceFiles/data/data_reply_preview.h +++ b/Telegram/SourceFiles/data/data_reply_preview.h @@ -24,6 +24,7 @@ public: ~ReplyPreview(); [[nodiscard]] Image *image(Data::FileOrigin origin); + [[nodiscard]] bool loaded() const; private: void prepare(not_null image, Images::Options options); diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp index 824705a77..4401bbb09 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp @@ -23,29 +23,84 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView { namespace { +[[nodiscard]] Ui::MessageBarContent ContentWithoutPreview( + not_null item, + PinnedIdType type) { + const auto media = item->media(); + const auto poll = media ? media->poll() : nullptr; + return Ui::MessageBarContent{ + .id = item->id, + .title = ((type == PinnedIdType::First) + ? tr::lng_pinned_previous(tr::now) // #TODO pinned first? + : (type == PinnedIdType::Middle) + ? tr::lng_pinned_previous(tr::now) + : !poll + ? tr::lng_pinned_message(tr::now) + : poll->quiz() + ? tr::lng_pinned_quiz(tr::now) + : tr::lng_pinned_poll(tr::now)), + .text = item->inReplyText(), + }; +} + +[[nodiscard]] Ui::MessageBarContent ContentWithPreview( + not_null item, + PinnedIdType type, + Image *preview) { + auto result = ContentWithoutPreview(item, type); + if (!preview) { + static const auto kEmpty = [&] { + const auto size = st::historyReplyHeight * cIntRetinaFactor(); + auto result = QImage( + QSize(size, size), + QImage::Format_ARGB32_Premultiplied); + result.fill(Qt::transparent); + result.setDevicePixelRatio(cRetinaFactor()); + return result; + }(); + result.preview = kEmpty; + } else { + result.preview = preview->original(); + } + return result; +} + [[nodiscard]] rpl::producer ContentByItem( not_null item, PinnedIdType type) { return item->history()->session().changes().messageFlagsValue( item, Data::MessageUpdate::Flag::Edited - ) | rpl::map([=] { + ) | rpl::map([=]() -> rpl::producer { const auto media = item->media(); - const auto poll = media ? media->poll() : nullptr; - return Ui::MessageBarContent{ - .id = item->id, - .title = ((type == PinnedIdType::First) - ? tr::lng_pinned_previous(tr::now) // #TODO pinned first? - : (type == PinnedIdType::Middle) - ? tr::lng_pinned_previous(tr::now) - : !poll - ? tr::lng_pinned_message(tr::now) - : poll->quiz() - ? tr::lng_pinned_quiz(tr::now) - : tr::lng_pinned_poll(tr::now)), - .text = item->inReplyText(), + if (!media || !media->hasReplyPreview()) { + return rpl::single(ContentWithoutPreview(item, type)); + } + constexpr auto kFullLoaded = 2; + constexpr auto kSomeLoaded = 1; + constexpr auto kNotLoaded = 0; + const auto loadedLevel = [=] { + const auto preview = media->replyPreview(); + return media->replyPreviewLoaded() + ? kFullLoaded + : preview + ? kSomeLoaded + : kNotLoaded; }; - }); + return rpl::single( + loadedLevel() + ) | rpl::then( + item->history()->session().downloaderTaskFinished( + ) | rpl::map(loadedLevel) + ) | rpl::distinct_until_changed( + ) | rpl::take_while([=](int loadLevel) { + return loadLevel < kFullLoaded; + }) | rpl::then( + rpl::single(kFullLoaded) + ) | rpl::map([=] { + return ContentWithPreview(item, type, media->replyPreview()); + }); + }) | rpl::flatten_latest(); } [[nodiscard]] rpl::producer ContentByItemId( From 0873db58d0ffef9c4bcd7a5c9e574293a9315430 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 12 Oct 2020 18:05:54 +0300 Subject: [PATCH 085/190] Moved PinnedBar to Ui:: in td_ui. --- .../SourceFiles/history/history_widget.cpp | 6 +- Telegram/SourceFiles/history/history_widget.h | 5 +- .../history/view/history_view_pinned_bar.cpp | 245 ++++-------------- .../history/view/history_view_pinned_bar.h | 48 +--- Telegram/SourceFiles/ui/chat/message_bar.h | 1 + Telegram/SourceFiles/ui/chat/pinned_bar.cpp | 195 ++++++++++++++ Telegram/SourceFiles/ui/chat/pinned_bar.h | 54 ++++ Telegram/cmake/td_ui.cmake | 2 + 8 files changed, 316 insertions(+), 240 deletions(-) create mode 100644 Telegram/SourceFiles/ui/chat/pinned_bar.cpp create mode 100644 Telegram/SourceFiles/ui/chat/pinned_bar.h diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index a92dd5ab7..3514e9eca 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -92,6 +92,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "apiwrap.h" #include "base/qthelp_regex.h" +#include "ui/chat/pinned_bar.h" #include "ui/widgets/popup_menu.h" #include "ui/item_text_options.h" #include "ui/unread_badge.h" @@ -5252,10 +5253,9 @@ void HistoryWidget::checkPinnedBarState() { messageId.type }; }); - _pinnedBar = std::make_unique( + _pinnedBar = std::make_unique( this, - &session(), - std::move(shown), + HistoryView::PinnedBarContent(&session(), std::move(shown)), true); _pinnedBar->closeClicks( diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 1be6ab3bc..230570317 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -66,7 +66,7 @@ class SilentToggle; class FlatButton; class LinkButton; class RoundButton; -class MessageBar; +class PinnedBar; namespace Toast { class Instance; } // namespace Toast @@ -95,7 +95,6 @@ class TopBarWidget; class ContactStatus; class Element; class PinnedTracker; -class PinnedBar; } // namespace HistoryView class DragArea; @@ -603,7 +602,7 @@ private: object_ptr _fieldBarCancel; std::unique_ptr _pinnedTracker; - std::unique_ptr _pinnedBar; + std::unique_ptr _pinnedBar; int _pinnedBarHeight = 0; mtpRequestId _saveEditMsgRequestId = 0; diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp index 4401bbb09..a31713bbc 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp @@ -12,42 +12,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_changes.h" #include "data/data_poll.h" -#include "ui/widgets/shadow.h" -#include "ui/widgets/buttons.h" #include "history/view/history_view_pinned_tracker.h" #include "history/history_item.h" #include "history/history.h" #include "apiwrap.h" -#include "styles/style_chat.h" +#include "styles/style_history.h" namespace HistoryView { namespace { [[nodiscard]] Ui::MessageBarContent ContentWithoutPreview( - not_null item, - PinnedIdType type) { + not_null item) { const auto media = item->media(); const auto poll = media ? media->poll() : nullptr; return Ui::MessageBarContent{ .id = item->id, - .title = ((type == PinnedIdType::First) - ? tr::lng_pinned_previous(tr::now) // #TODO pinned first? - : (type == PinnedIdType::Middle) - ? tr::lng_pinned_previous(tr::now) - : !poll - ? tr::lng_pinned_message(tr::now) - : poll->quiz() - ? tr::lng_pinned_quiz(tr::now) - : tr::lng_pinned_poll(tr::now)), .text = item->inReplyText(), }; } [[nodiscard]] Ui::MessageBarContent ContentWithPreview( not_null item, - PinnedIdType type, Image *preview) { - auto result = ContentWithoutPreview(item, type); + auto result = ContentWithoutPreview(item); if (!preview) { static const auto kEmpty = [&] { const auto size = st::historyReplyHeight * cIntRetinaFactor(); @@ -66,15 +53,14 @@ namespace { } [[nodiscard]] rpl::producer ContentByItem( - not_null item, - PinnedIdType type) { + not_null item) { return item->history()->session().changes().messageFlagsValue( item, Data::MessageUpdate::Flag::Edited ) | rpl::map([=]() -> rpl::producer { const auto media = item->media(); if (!media || !media->hasReplyPreview()) { - return rpl::single(ContentWithoutPreview(item, type)); + return rpl::single(ContentWithoutPreview(item)); } constexpr auto kFullLoaded = 2; constexpr auto kSomeLoaded = 1; @@ -98,19 +84,19 @@ namespace { }) | rpl::then( rpl::single(kFullLoaded) ) | rpl::map([=] { - return ContentWithPreview(item, type, media->replyPreview()); + return ContentWithPreview(item, media->replyPreview()); }); }) | rpl::flatten_latest(); } [[nodiscard]] rpl::producer ContentByItemId( not_null session, - PinnedBarId id, + FullMsgId id, bool alreadyLoaded = false) { - if (!id.message) { + if (!id) { return rpl::single(Ui::MessageBarContent()); - } else if (const auto item = session->data().message(id.message)) { - return ContentByItem(item, id.type); + } else if (const auto item = session->data().message(id)) { + return ContentByItem(item); } else if (alreadyLoaded) { return rpl::single(Ui::MessageBarContent()); // Deleted message?.. } @@ -118,13 +104,13 @@ namespace { consumer.put_next(Ui::MessageBarContent{ .text = tr::lng_contacts_loading(tr::now), }); - const auto channel = id.message.channel - ? session->data().channel(id.message.channel).get() + const auto channel = id.channel + ? session->data().channel(id.channel).get() : nullptr; const auto callback = [=](ChannelData *channel, MsgId id) { consumer.put_done(); }; - session->api().requestMessageData(channel, id.message.msg, callback); + session->api().requestMessageData(channel, id.msg, callback); return rpl::lifetime(); }); return std::move( @@ -134,180 +120,47 @@ namespace { })); } +auto WithPinnedTitle(not_null session, PinnedBarId id) { + return [=](Ui::MessageBarContent &&content) { + const auto item = session->data().message(id.message); + if (!item) { + return std::move(content); + } + const auto media = item->media(); + const auto poll = media ? media->poll() : nullptr; + content.title = (id.type == PinnedIdType::First) + ? tr::lng_pinned_previous(tr::now) // #TODO pinned first? + : (id.type == PinnedIdType::Middle) + ? tr::lng_pinned_previous(tr::now) + : !poll + ? tr::lng_pinned_message(tr::now) + : poll->quiz() + ? tr::lng_pinned_quiz(tr::now) + : tr::lng_pinned_poll(tr::now); + return std::move(content); + }; +} + } // namespace -PinnedBar::PinnedBar( - not_null parent, - not_null session, - rpl::producer itemId, - bool withClose) -: _wrap(parent, object_ptr(parent)) -, _close(withClose - ? std::make_unique( - _wrap.entity(), - st::historyReplyCancel) - : nullptr) -, _shadow(std::make_unique(_wrap.parentWidget())) { - _wrap.hide(anim::type::instant); - _shadow->hide(); +rpl::producer MessageBarContentByItemId( + not_null session, + FullMsgId id) { + return ContentByItemId(session, id); +} - _wrap.entity()->paintRequest( - ) | rpl::start_with_next([=](QRect clip) { - QPainter(_wrap.entity()).fillRect(clip, st::historyPinnedBg); - }, lifetime()); - _wrap.setAttribute(Qt::WA_OpaquePaintEvent); - - rpl::duplicate( - itemId +rpl::producer PinnedBarContent( + not_null session, + rpl::producer id) { + return std::move( + id ) | rpl::distinct_until_changed( ) | rpl::map([=](PinnedBarId id) { - return ContentByItemId(session, id); - }) | rpl::flatten_latest( - ) | rpl::filter([=](const Ui::MessageBarContent &content) { - return !content.title.isEmpty() || !content.text.text.isEmpty(); - }) | rpl::start_with_next([=](Ui::MessageBarContent &&content) { - const auto creating = !_bar; - if (creating) { - createControls(); - } - _bar->set(std::move(content)); - if (creating) { - _bar->finishAnimating(); - } - }, lifetime()); - - std::move( - itemId - ) | rpl::map([=](PinnedBarId id) { - return !id.message; - }) | rpl::start_with_next_done([=](bool hidden) { - _shouldBeShown = !hidden; - if (!_forceHidden) { - _wrap.toggle(_shouldBeShown, anim::type::normal); - } else if (!_shouldBeShown) { - _bar = nullptr; - } - }, [=] { - _forceHidden = true; - _wrap.toggle(false, anim::type::normal); - }, lifetime()); -} - -void PinnedBar::createControls() { - Expects(!_bar); - - _bar = std::make_unique( - _wrap.entity(), - st::defaultMessageBar); - if (_close) { - _close->raise(); - } - - // Clicks. - _bar->widget()->setCursor(style::cur_pointer); - _bar->widget()->events( - ) | rpl::filter([=](not_null event) { - return (event->type() == QEvent::MouseButtonPress); - }) | rpl::map([=] { - return _bar->widget()->events( - ) | rpl::filter([=](not_null event) { - return (event->type() == QEvent::MouseButtonRelease); - }) | rpl::take(1) | rpl::filter([=](not_null event) { - return _bar->widget()->rect().contains( - static_cast(event.get())->pos()); - }); - }) | rpl::flatten_latest( - ) | rpl::map([] { - return rpl::empty_value(); - }) | rpl::start_to_stream(_barClicks, _bar->widget()->lifetime()); - - _bar->widget()->move(0, 0); - _bar->widget()->show(); - _wrap.entity()->resize(_wrap.entity()->width(), _bar->widget()->height()); - - _wrap.geometryValue( - ) | rpl::start_with_next([=](QRect rect) { - _shadow->setGeometry( - rect.x(), - rect.y() + rect.height(), - rect.width(), - st::lineWidth); - _bar->widget()->resizeToWidth( - rect.width() - (_close ? _close->width() : 0)); - const auto hidden = _wrap.isHidden() || !rect.height(); - if (_shadow->isHidden() != hidden) { - _shadow->setVisible(!hidden); - } - if (_close) { - _close->moveToRight(0, 0); - } - }, _bar->widget()->lifetime()); - - _wrap.shownValue( - ) | rpl::skip( - 1 - ) | rpl::filter([=](bool shown) { - return !shown && !_forceHidden; - }) | rpl::start_with_next([=] { - _bar = nullptr; - }, _bar->widget()->lifetime()); - - Ensures(_bar != nullptr); -} - -void PinnedBar::show() { - if (!_forceHidden) { - return; - } - _forceHidden = false; - if (_shouldBeShown) { - _wrap.show(anim::type::instant); - _shadow->show(); - } -} - -void PinnedBar::hide() { - if (_forceHidden) { - return; - } - _forceHidden = true; - _wrap.hide(anim::type::instant); - _shadow->hide(); -} - -void PinnedBar::raise() { - _wrap.raise(); - _shadow->raise(); -} - -void PinnedBar::move(int x, int y) { - _wrap.move(x, y); -} - -void PinnedBar::resizeToWidth(int width) { - _wrap.entity()->resizeToWidth(width); -} - -int PinnedBar::height() const { - return !_forceHidden - ? _wrap.height() - : _shouldBeShown - ? st::historyReplyHeight - : 0; -} - -rpl::producer PinnedBar::heightValue() const { - return _wrap.heightValue(); -} - -rpl::producer<> PinnedBar::closeClicks() const { - return !_close - ? (rpl::never<>() | rpl::type_erased()) - : (_close->clicks() | rpl::map([] { return rpl::empty_value(); })); -} - -rpl::producer<> PinnedBar::barClicks() const { - return _barClicks.events(); + return ContentByItemId( + session, + id.message + ) | rpl::map(WithPinnedTitle(session, id)); + }) | rpl::flatten_latest(); } } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_bar.h b/Telegram/SourceFiles/history/view/history_view_pinned_bar.h index 469e6f2a1..0995d1cb5 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_bar.h +++ b/Telegram/SourceFiles/history/view/history_view_pinned_bar.h @@ -7,9 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "ui/wrap/slide_wrap.h" #include "ui/chat/message_bar.h" +#include + namespace Main { class Session; } // namespace Main @@ -17,10 +18,15 @@ class Session; namespace Ui { class IconButton; class PlainShadow; +struct MessageBarContent; } // namespace Ui namespace HistoryView { +[[nodiscard]] rpl::producer MessageBarContentByItemId( + not_null session, + FullMsgId id); + enum class PinnedIdType; struct PinnedBarId { FullMsgId message; @@ -33,42 +39,8 @@ struct PinnedBarId { return std::tie(message, type) == std::tie(other.message, other.type); } }; - -class PinnedBar final { -public: - PinnedBar( - not_null parent, - not_null session, - rpl::producer itemId, - bool withClose = false); - - void show(); - void hide(); - void raise(); - - void move(int x, int y); - void resizeToWidth(int width); - [[nodiscard]] int height() const; - [[nodiscard]] rpl::producer heightValue() const; - [[nodiscard]] rpl::producer<> closeClicks() const; - [[nodiscard]] rpl::producer<> barClicks() const; - - [[nodiscard]] rpl::lifetime &lifetime() { - return _wrap.lifetime(); - } - -private: - void createControls(); - - Ui::SlideWrap<> _wrap; - std::unique_ptr _bar; - std::unique_ptr _close; - std::unique_ptr _shadow; - rpl::event_stream _content; - rpl::event_stream<> _barClicks; - bool _shouldBeShown = false; - bool _forceHidden = false; - -}; +[[nodiscard]] rpl::producer PinnedBarContent( + not_null session, + rpl::producer id); } // namespace HistoryView diff --git a/Telegram/SourceFiles/ui/chat/message_bar.h b/Telegram/SourceFiles/ui/chat/message_bar.h index 73bf97330..1c82d5672 100644 --- a/Telegram/SourceFiles/ui/chat/message_bar.h +++ b/Telegram/SourceFiles/ui/chat/message_bar.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "ui/rp_widget.h" +#include "ui/effects/animations.h" class Painter; diff --git a/Telegram/SourceFiles/ui/chat/pinned_bar.cpp b/Telegram/SourceFiles/ui/chat/pinned_bar.cpp new file mode 100644 index 000000000..b925b91d7 --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/pinned_bar.cpp @@ -0,0 +1,195 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/chat/pinned_bar.h" + +#include "ui/chat/message_bar.h" +#include "ui/widgets/shadow.h" +#include "ui/widgets/buttons.h" +#include "styles/style_chat.h" +#include "styles/palette.h" + +#include + +namespace Ui { + +PinnedBar::PinnedBar( + not_null parent, + rpl::producer content, + bool withClose) +: _wrap(parent, object_ptr(parent)) +, _close(withClose + ? std::make_unique( + _wrap.entity(), + st::historyReplyCancel) + : nullptr) +, _shadow(std::make_unique(_wrap.parentWidget())) { + _wrap.hide(anim::type::instant); + _shadow->hide(); + + _wrap.entity()->paintRequest( + ) | rpl::start_with_next([=](QRect clip) { + QPainter(_wrap.entity()).fillRect(clip, st::historyPinnedBg); + }, lifetime()); + _wrap.setAttribute(Qt::WA_OpaquePaintEvent); + + auto copy = std::move( + content + ) | rpl::start_spawning(_wrap.lifetime()); + + rpl::duplicate( + copy + ) | rpl::filter([=](const MessageBarContent &content) { + return !content.title.isEmpty() || !content.text.text.isEmpty(); + }) | rpl::start_with_next([=](MessageBarContent &&content) { + const auto creating = !_bar; + if (creating) { + createControls(); + } + _bar->set(std::move(content)); + if (creating) { + _bar->finishAnimating(); + } + }, lifetime()); + + std::move( + copy + ) | rpl::map([=](const MessageBarContent &content) { + return content.title.isEmpty() || content.text.text.isEmpty(); + }) | rpl::start_with_next_done([=](bool hidden) { + _shouldBeShown = !hidden; + if (!_forceHidden) { + _wrap.toggle(_shouldBeShown, anim::type::normal); + } else if (!_shouldBeShown) { + _bar = nullptr; + } + }, [=] { + _forceHidden = true; + _wrap.toggle(false, anim::type::normal); + }, lifetime()); +} + +PinnedBar::~PinnedBar() = default; + +void PinnedBar::createControls() { + Expects(!_bar); + + _bar = std::make_unique( + _wrap.entity(), + st::defaultMessageBar); + if (_close) { + _close->raise(); + } + + // Clicks. + _bar->widget()->setCursor(style::cur_pointer); + _bar->widget()->events( + ) | rpl::filter([=](not_null event) { + return (event->type() == QEvent::MouseButtonPress); + }) | rpl::map([=] { + return _bar->widget()->events( + ) | rpl::filter([=](not_null event) { + return (event->type() == QEvent::MouseButtonRelease); + }) | rpl::take(1) | rpl::filter([=](not_null event) { + return _bar->widget()->rect().contains( + static_cast(event.get())->pos()); + }); + }) | rpl::flatten_latest( + ) | rpl::map([] { + return rpl::empty_value(); + }) | rpl::start_to_stream(_barClicks, _bar->widget()->lifetime()); + + _bar->widget()->move(0, 0); + _bar->widget()->show(); + _wrap.entity()->resize(_wrap.entity()->width(), _bar->widget()->height()); + + _wrap.geometryValue( + ) | rpl::start_with_next([=](QRect rect) { + _shadow->setGeometry( + rect.x(), + rect.y() + rect.height(), + rect.width(), + st::lineWidth); + _bar->widget()->resizeToWidth( + rect.width() - (_close ? _close->width() : 0)); + const auto hidden = _wrap.isHidden() || !rect.height(); + if (_shadow->isHidden() != hidden) { + _shadow->setVisible(!hidden); + } + if (_close) { + _close->moveToRight(0, 0); + } + }, _bar->widget()->lifetime()); + + _wrap.shownValue( + ) | rpl::skip( + 1 + ) | rpl::filter([=](bool shown) { + return !shown && !_forceHidden; + }) | rpl::start_with_next([=] { + _bar = nullptr; + }, _bar->widget()->lifetime()); + + Ensures(_bar != nullptr); +} + +void PinnedBar::show() { + if (!_forceHidden) { + return; + } + _forceHidden = false; + if (_shouldBeShown) { + _wrap.show(anim::type::instant); + _shadow->show(); + } +} + +void PinnedBar::hide() { + if (_forceHidden) { + return; + } + _forceHidden = true; + _wrap.hide(anim::type::instant); + _shadow->hide(); +} + +void PinnedBar::raise() { + _wrap.raise(); + _shadow->raise(); +} + +void PinnedBar::move(int x, int y) { + _wrap.move(x, y); +} + +void PinnedBar::resizeToWidth(int width) { + _wrap.entity()->resizeToWidth(width); +} + +int PinnedBar::height() const { + return !_forceHidden + ? _wrap.height() + : _shouldBeShown + ? st::historyReplyHeight + : 0; +} + +rpl::producer PinnedBar::heightValue() const { + return _wrap.heightValue(); +} + +rpl::producer<> PinnedBar::closeClicks() const { + return !_close + ? (rpl::never<>() | rpl::type_erased()) + : (_close->clicks() | rpl::map([] { return rpl::empty_value(); })); +} + +rpl::producer<> PinnedBar::barClicks() const { + return _barClicks.events(); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/pinned_bar.h b/Telegram/SourceFiles/ui/chat/pinned_bar.h new file mode 100644 index 000000000..dca2ee50e --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/pinned_bar.h @@ -0,0 +1,54 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once +#include "ui/wrap/slide_wrap.h" + +namespace Ui { + +struct MessageBarContent; +class MessageBar; +class IconButton; +class PlainShadow; + +class PinnedBar final { +public: + PinnedBar( + not_null parent, + rpl::producer content, + bool withClose = false); + ~PinnedBar(); + + void show(); + void hide(); + void raise(); + + void move(int x, int y); + void resizeToWidth(int width); + [[nodiscard]] int height() const; + [[nodiscard]] rpl::producer heightValue() const; + [[nodiscard]] rpl::producer<> closeClicks() const; + [[nodiscard]] rpl::producer<> barClicks() const; + + [[nodiscard]] rpl::lifetime &lifetime() { + return _wrap.lifetime(); + } + +private: + void createControls(); + + Ui::SlideWrap<> _wrap; + std::unique_ptr _bar; + std::unique_ptr _close; + std::unique_ptr _shadow; + rpl::event_stream<> _barClicks; + bool _shouldBeShown = false; + bool _forceHidden = false; + +}; + +} // namespace Ui diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 086a78e73..9c41becf5 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -34,6 +34,8 @@ PRIVATE ui/ui_pch.h ui/chat/message_bar.cpp ui/chat/message_bar.h + ui/chat/pinned_bar.cpp + ui/chat/pinned_bar.h ui/text/format_values.cpp ui/text/format_values.h ui/text/text_options.cpp From af1854e87735fc67851ca2383dd2125878363f33 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 13 Oct 2020 10:16:16 +0300 Subject: [PATCH 086/190] Use Ui::PinnedBar in Replies section. --- .../SourceFiles/history/history_widget.cpp | 15 ++ .../view/history_view_replies_section.cpp | 167 +++++++----------- .../view/history_view_replies_section.h | 10 +- Telegram/SourceFiles/ui/chat/pinned_bar.cpp | 26 ++- Telegram/SourceFiles/ui/chat/pinned_bar.h | 5 + 5 files changed, 103 insertions(+), 120 deletions(-) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 3514e9eca..6d48a15f2 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -5258,6 +5258,21 @@ void HistoryWidget::checkPinnedBarState() { HistoryView::PinnedBarContent(&session(), std::move(shown)), true); + rpl::single( + rpl::empty_value() + ) | rpl::then( + base::ObservableViewer(Adaptive::Changed()) + ) | rpl::map([] { + return Adaptive::OneColumn(); + }) | rpl::start_with_next([=](bool one) { + _pinnedBar->setShadowGeometryPostprocess([=](QRect geometry) { + if (!one) { + geometry.setLeft(geometry.left() + st::lineWidth); + } + return geometry; + }); + }, _pinnedBar->lifetime()); + _pinnedBar->closeClicks( ) | rpl::start_with_next([=] { hidePinnedMessage(); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index d7751ce84..d433276e1 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -11,11 +11,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_top_bar_widget.h" #include "history/view/history_view_list_widget.h" #include "history/view/history_view_schedule_box.h" +#include "history/view/history_view_pinned_bar.h" #include "history/history.h" #include "history/history_drag_area.h" #include "history/history_item_components.h" #include "history/history_item.h" #include "chat_helpers/send_context_menu.h" // SendMenu::Type. +#include "ui/chat/pinned_bar.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/shadow.h" #include "ui/wrap/slide_wrap.h" @@ -23,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/item_text_options.h" #include "ui/toast/toast.h" #include "ui/text/format_values.h" +#include "ui/text/text_utilities.h" #include "ui/special_buttons.h" #include "ui/ui_utility.h" #include "ui/toasts/common_toasts.h" @@ -77,6 +80,27 @@ bool CanSendFiles(not_null data) { return false; } +rpl::producer RootViewContent( + not_null history, + MsgId rootId) { + return MessageBarContentByItemId( + &history->session(), + FullMsgId{ history->channelId(), rootId } + ) | rpl::map([=](Ui::MessageBarContent &&content) { + const auto item = history->owner().message( + history->channelId(), + rootId); + if (!item) { + content.text = Ui::Text::Link(tr::lng_deleted_message(tr::now)); + } + const auto sender = (item && item->discussionPostOriginalSender()) + ? item->discussionPostOriginalSender() + : history->peer.get(); + content.title = sender->name.isEmpty() ? "Message" : sender->name; + return std::move(content); + }); +} + } // namespace RepliesMemento::RepliesMemento( @@ -131,8 +155,6 @@ RepliesWidget::RepliesWidget( this, controller, ComposeControls::Mode::Normal)) -, _rootView(this, object_ptr(this)) -, _rootShadow(this) , _scroll(std::make_unique(this, st::historyScroll, false)) , _scrollDown(_scroll.get(), st::historyToDown) , _readRequestTimer([=] { sendReadTillRequest(); }) { @@ -170,7 +192,6 @@ RepliesWidget::RepliesWidget( }, _topBar->lifetime()); _rootView->raise(); - _rootShadow->raise(); _topBarShadow->raise(); updateAdaptiveLayout(); subscribe(Adaptive::Changed(), [=] { updateAdaptiveLayout(); }); @@ -263,9 +284,7 @@ void RepliesWidget::sendReadTillRequest() { } void RepliesWidget::setupRoot() { - if (_root) { - refreshRootView(); - } else { + if (!_root) { const auto channel = _history->peer->asChannel(); const auto done = crl::guard(this, [=](ChannelData*, MsgId) { _root = lookupRoot(); @@ -277,86 +296,41 @@ void RepliesWidget::setupRoot() { _inner->update(); } updatePinnedVisibility(); - refreshRootView(); }); _history->session().api().requestMessageData(channel, _rootId, done); } } void RepliesWidget::setupRootView() { - const auto raw = _rootView->entity(); - raw->resize(raw->width(), st::historyReplyHeight); - raw->paintRequest( - ) | rpl::start_with_next([=](QRect clip) { - auto p = Painter(raw); - p.fillRect(clip, st::historyPinnedBg); + auto content = rpl::combine( + RootViewContent(_history, _rootId), + _rootVisible.value() + ) | rpl::map([=](Ui::MessageBarContent &&content, bool shown) { + return shown ? std::move(content) : Ui::MessageBarContent(); + }); + _rootView = std::make_unique(this, std::move(content)); - auto top = st::msgReplyPadding.top(); - QRect rbar(myrtlrect(st::msgReplyBarSkip + st::msgReplyBarPos.x(), top + st::msgReplyBarPos.y(), st::msgReplyBarSize.width(), st::msgReplyBarSize.height())); - p.fillRect(rbar, st::msgInReplyBarColor); - - int32 left = st::msgReplyBarSkip + st::msgReplyBarSkip; - if (!_rootTitle.isEmpty()) { - const auto media = _root ? _root->media() : nullptr; - if (media && media->hasReplyPreview()) { - if (const auto image = media->replyPreview()) { - QRect to(left, top, st::msgReplyBarSize.height(), st::msgReplyBarSize.height()); - p.drawPixmap(to.x(), to.y(), image->pixSingle(image->width() / cIntRetinaFactor(), image->height() / cIntRetinaFactor(), to.width(), to.height(), ImageRoundRadius::Small)); - } - left += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x(); + rpl::single( + rpl::empty_value() + ) | rpl::then( + base::ObservableViewer(Adaptive::Changed()) + ) | rpl::map([] { + return Adaptive::OneColumn(); + }) | rpl::start_with_next([=](bool one) { + _rootView->setShadowGeometryPostprocess([=](QRect geometry) { + if (!one) { + geometry.setLeft(geometry.left() + st::lineWidth); } - p.setPen(st::historyReplyNameFg); - p.setFont(st::msgServiceNameFont); - const auto poll = media ? media->poll() : nullptr; - const auto pinnedHeader = !poll - ? tr::lng_pinned_message(tr::now) - : poll->quiz() - ? tr::lng_pinned_quiz(tr::now) - : tr::lng_pinned_poll(tr::now); - _rootTitle.drawElided(p, left, top, width() - left - st::msgReplyPadding.right()); - - p.setPen(st::historyComposeAreaFg); - p.setTextPalette(st::historyComposeAreaPalette); - _rootMessage.drawElided(p, left, top + st::msgServiceNameFont->height, width() - left - st::msgReplyPadding.right()); - p.restoreTextPalette(); - } else { - p.setFont(st::msgDateFont); - p.setPen(st::historyComposeAreaFgService); - p.drawText(left, top + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2 + st::msgDateFont->ascent, st::msgDateFont->elided(tr::lng_profile_loading(tr::now), width() - left - st::msgReplyPadding.right())); - } - }, raw->lifetime()); - - raw->setCursor(style::cur_pointer); - const auto pressed = raw->lifetime().make_state(); - raw->events( - ) | rpl::start_with_next([=](not_null e) { - const auto mouse = static_cast(e.get()); - if (e->type() == QEvent::MouseButtonPress) { - if (mouse->button() == Qt::LeftButton) { - *pressed = true; - } - } else if (e->type() == QEvent::MouseButtonRelease) { - if (mouse->button() == Qt::LeftButton) { - if (base::take(*pressed) - && raw->rect().contains(mouse->pos())) { - showAtStart(); - } - } - } - }, raw->lifetime()); - - _rootView->geometryValue( - ) | rpl::start_with_next([=](QRect rect) { - _rootShadow->moveToLeft( - _rootShadow->x(), - rect.y() + rect.height()); + return geometry; + }); }, _rootView->lifetime()); - _rootShadow->showOn(_rootView->shownValue()); + _rootView->barClicks( + ) | rpl::start_with_next([=] { + showAtStart(); + }, lifetime()); - _rootView->hide(anim::type::instant); _rootViewHeight = 0; - _rootView->heightValue( ) | rpl::start_with_next([=](int height) { if (const auto delta = height - _rootViewHeight) { @@ -366,34 +340,6 @@ void RepliesWidget::setupRootView() { }, _rootView->lifetime()); } -void RepliesWidget::refreshRootView() { - const auto sender = (_root && _root->discussionPostOriginalSender()) - ? _root->discussionPostOriginalSender() - : _history->peer.get(); - _rootTitle.setText( - st::fwdTextStyle, - sender->name, - Ui::NameTextOptions()); - if (_rootTitle.isEmpty()) { - _rootTitle.setText( - st::fwdTextStyle, - "Message", - Ui::NameTextOptions()); - } - if (_root) { - _rootMessage.setText( - st::messageTextStyle, - _root->inReplyText(), - Ui::DialogTextOptions()); - } else { - _rootMessage.setText( - st::messageTextStyle, - textcmdLink(1, tr::lng_deleted_message(tr::now)), - Ui::DialogTextOptions()); - } - update(); -} - HistoryItem *RepliesWidget::lookupRoot() const { return _history->owner().message(_history->channelId(), _rootId); } @@ -1332,9 +1278,6 @@ void RepliesWidget::updateAdaptiveLayout() { _topBarShadow->moveToLeft( Adaptive::OneColumn() ? 0 : st::lineWidth, _topBar->height()); - _rootShadow->moveToLeft( - Adaptive::OneColumn() ? 0 : st::lineWidth, - _rootShadow->y()); } not_null RepliesWidget::history() const { @@ -1518,7 +1461,9 @@ void RepliesWidget::updateControlsGeometry() { : base::make_optional(_scroll->scrollTop() + topDelta()); _topBar->resizeToWidth(contentWidth); _topBarShadow->resize(contentWidth, st::lineWidth); - _rootShadow->resize(contentWidth, st::lineWidth); + if (_rootView) { + _rootView->resizeToWidth(contentWidth); + } _rootView->resizeToWidth(contentWidth); const auto bottom = height(); @@ -1606,10 +1551,16 @@ void RepliesWidget::setPinnedVisibility(bool shown) { updateControlsGeometry(); } } - _rootView->toggle(shown, anim::type::instant); + if (shown) { + _rootView->show(); + } else { + _rootView->hide(); + } + _rootVisible = shown; + _rootView->finishAnimating(); _rootViewInited = true; } else { - _rootView->toggle(shown, anim::type::normal); + _rootVisible = shown; } } } diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 90c9a64b8..5adf27731 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -35,8 +35,7 @@ class ScrollArea; class PlainShadow; class FlatButton; class HistoryDownButton; -template -class SlideWrap; +class PinnedBar; } // namespace Ui namespace Profile { @@ -158,7 +157,6 @@ private: void setupRoot(); void setupRootView(); - void refreshRootView(); void setupDragArea(); void sendReadTillRequest(); void readTill(not_null item); @@ -250,12 +248,10 @@ private: std::unique_ptr _composeControls; bool _skipScrollEvent = false; - Ui::Text::String _rootTitle; - Ui::Text::String _rootMessage; - object_ptr> _rootView; + std::unique_ptr _rootView; int _rootViewHeight = 0; - object_ptr _rootShadow; bool _rootViewInited = false; + rpl::variable _rootVisible = false; std::unique_ptr _scroll; diff --git a/Telegram/SourceFiles/ui/chat/pinned_bar.cpp b/Telegram/SourceFiles/ui/chat/pinned_bar.cpp index b925b91d7..c4878ed28 100644 --- a/Telegram/SourceFiles/ui/chat/pinned_bar.cpp +++ b/Telegram/SourceFiles/ui/chat/pinned_bar.cpp @@ -75,6 +75,22 @@ PinnedBar::PinnedBar( PinnedBar::~PinnedBar() = default; +void PinnedBar::setShadowGeometryPostprocess(Fn postprocess) { + _shadowGeometryPostprocess = std::move(postprocess); + updateShadowGeometry(_wrap.geometry()); +} + +void PinnedBar::updateShadowGeometry(QRect wrapGeometry) { + const auto regular = QRect( + wrapGeometry.x(), + wrapGeometry.y() + wrapGeometry.height(), + wrapGeometry.width(), + st::lineWidth); + _shadow->setGeometry(_shadowGeometryPostprocess + ? _shadowGeometryPostprocess(regular) + : regular); +} + void PinnedBar::createControls() { Expects(!_bar); @@ -109,11 +125,7 @@ void PinnedBar::createControls() { _wrap.geometryValue( ) | rpl::start_with_next([=](QRect rect) { - _shadow->setGeometry( - rect.x(), - rect.y() + rect.height(), - rect.width(), - st::lineWidth); + updateShadowGeometry(rect); _bar->widget()->resizeToWidth( rect.width() - (_close ? _close->width() : 0)); const auto hidden = _wrap.isHidden() || !rect.height(); @@ -162,6 +174,10 @@ void PinnedBar::raise() { _shadow->raise(); } +void PinnedBar::finishAnimating() { + _wrap.finishAnimating(); +} + void PinnedBar::move(int x, int y) { _wrap.move(x, y); } diff --git a/Telegram/SourceFiles/ui/chat/pinned_bar.h b/Telegram/SourceFiles/ui/chat/pinned_bar.h index dca2ee50e..55ca6a283 100644 --- a/Telegram/SourceFiles/ui/chat/pinned_bar.h +++ b/Telegram/SourceFiles/ui/chat/pinned_bar.h @@ -26,6 +26,9 @@ public: void show(); void hide(); void raise(); + void finishAnimating(); + + void setShadowGeometryPostprocess(Fn postprocess); void move(int x, int y); void resizeToWidth(int width); @@ -40,12 +43,14 @@ public: private: void createControls(); + void updateShadowGeometry(QRect wrapGeometry); Ui::SlideWrap<> _wrap; std::unique_ptr _bar; std::unique_ptr _close; std::unique_ptr _shadow; rpl::event_stream<> _barClicks; + Fn _shadowGeometryPostprocess; bool _shouldBeShown = false; bool _forceHidden = false; From 39cf51c0664135bfdfde918798f88c8676307620 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 13 Oct 2020 13:00:35 +0300 Subject: [PATCH 087/190] Move SendButton/EmojiButton to td_ui. --- .../SourceFiles/boxes/edit_caption_box.cpp | 2 +- Telegram/SourceFiles/boxes/send_files_box.cpp | 2 +- Telegram/SourceFiles/config.h | 22 -- Telegram/SourceFiles/core/file_utilities.cpp | 15 +- Telegram/SourceFiles/core/file_utilities.h | 7 +- .../SourceFiles/history/history_widget.cpp | 7 +- .../view/history_view_compose_controls.cpp | 3 +- .../view/history_view_replies_section.cpp | 6 +- .../view/history_view_scheduled_section.cpp | 6 +- .../passport/passport_panel_edit_scans.cpp | 5 +- .../SourceFiles/settings/settings_chat.cpp | 4 +- .../settings/settings_information.cpp | 6 +- .../storage/storage_media_prepare.cpp | 5 +- .../ui/chat/attach/attach_extensions.cpp | 33 ++ .../ui/chat/attach/attach_extensions.h | 15 + .../SourceFiles/ui/controls/emoji_button.cpp | 127 +++++++ .../SourceFiles/ui/controls/emoji_button.h | 46 +++ .../SourceFiles/ui/controls/send_button.cpp | 265 ++++++++++++++ .../SourceFiles/ui/controls/send_button.h | 92 +++++ Telegram/SourceFiles/ui/special_buttons.cpp | 344 +----------------- Telegram/SourceFiles/ui/special_buttons.h | 107 ------ Telegram/SourceFiles/ui/ui_pch.h | 2 + Telegram/cmake/td_ui.cmake | 9 +- 23 files changed, 624 insertions(+), 506 deletions(-) create mode 100644 Telegram/SourceFiles/ui/chat/attach/attach_extensions.cpp create mode 100644 Telegram/SourceFiles/ui/chat/attach/attach_extensions.h create mode 100644 Telegram/SourceFiles/ui/controls/emoji_button.cpp create mode 100644 Telegram/SourceFiles/ui/controls/emoji_button.h create mode 100644 Telegram/SourceFiles/ui/controls/send_button.cpp create mode 100644 Telegram/SourceFiles/ui/controls/send_button.h diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index b2e36864a..93ca21033 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -47,8 +47,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h" #include "ui/text/format_values.h" -#include "ui/special_buttons.h" #include "ui/text/text_options.h" +#include "ui/controls/emoji_button.h" #include "window/window_session_controller.h" #include "confirm_box.h" #include "apiwrap.h" diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index fcfc37789..a247b56b0 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -34,7 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/format_values.h" #include "ui/grouped_layout.h" #include "ui/text/text_options.h" -#include "ui/special_buttons.h" +#include "ui/controls/emoji_button.h" #include "lottie/lottie_single_player.h" #include "data/data_document.h" #include "media/clip/media_clip_reader.h" diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index 40d01e9fc..5bc8f9c47 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -133,25 +133,3 @@ inline const QRegularExpression &cRussianLetters() { static QRegularExpression regexp(QString::fromUtf8("[а-яА-ЯёЁ]")); return regexp; } - -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 result; -} - -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 result; -} diff --git a/Telegram/SourceFiles/core/file_utilities.cpp b/Telegram/SourceFiles/core/file_utilities.cpp index edd0681c9..90560e7a6 100644 --- a/Telegram/SourceFiles/core/file_utilities.cpp +++ b/Telegram/SourceFiles/core/file_utilities.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "base/unixtime.h" #include "ui/delayed_activation.h" +#include "ui/chat/attach/attach_extensions.h" #include "main/main_session.h" #include "mainwindow.h" @@ -316,8 +317,20 @@ QString AllFilesFilter() { #endif // Q_OS_WIN } +QString ImagesFilter() { + return u"Image files (*"_q + Ui::ImageExtensions().join(u" *"_q) + u")"_q; +} + +QString AllOrImagesFilter() { + return AllFilesFilter() + u";;"_q + ImagesFilter(); +} + +QString ImagesOrAllFilter() { + return ImagesFilter() + u";;"_q + AllFilesFilter(); +} + QString AlbumFilesFilter() { - return qsl("Image and Video Files (*.png *.jpg *.jpeg *.mp4 *.mov)"); + return u"Image and Video Files (*.png *.jpg *.jpeg *.mp4 *.mov)"_q; } namespace internal { diff --git a/Telegram/SourceFiles/core/file_utilities.h b/Telegram/SourceFiles/core/file_utilities.h index ecefe35bb..d44072bb1 100644 --- a/Telegram/SourceFiles/core/file_utilities.h +++ b/Telegram/SourceFiles/core/file_utilities.h @@ -89,8 +89,11 @@ void GetFolder( Fn callback, Fn failed = Fn()); -QString AllFilesFilter(); -QString AlbumFilesFilter(); +[[nodiscard]] QString AllFilesFilter(); +[[nodiscard]] QString ImagesFilter(); +[[nodiscard]] QString AllOrImagesFilter(); +[[nodiscard]] QString ImagesOrAllFilter(); +[[nodiscard]] QString AlbumFilesFilter(); namespace internal { diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 6d48a15f2..751f3023b 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -32,6 +32,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/message_bar.h" #include "ui/image/image.h" #include "ui/special_buttons.h" +#include "ui/controls/emoji_button.h" +#include "ui/controls/send_button.h" #include "inline_bots/inline_bot_result.h" #include "base/event_filter.h" #include "base/unixtime.h" @@ -3276,10 +3278,7 @@ void HistoryWidget::chooseAttach() { return; } - const auto filter = FileDialog::AllFilesFilter() - + qsl(";;Image files (*") - + cImgExtensions().join(qsl(" *")) - + qsl(")"); + const auto filter = FileDialog::AllOrImagesFilter(); FileDialog::GetOpenPaths(this, tr::lng_choose_files(tr::now), filter, crl::guard(this, [=]( FileDialog::OpenResult &&result) { diff --git a/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp index 154495ed4..6e3ec83d2 100644 --- a/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp @@ -32,11 +32,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/audio/media_audio_capture.h" #include "media/audio/media_audio.h" #include "styles/style_chat.h" -#include "ui/special_buttons.h" #include "ui/text/text_options.h" #include "ui/ui_utility.h" #include "ui/widgets/input_fields.h" #include "ui/text/format_values.h" +#include "ui/controls/emoji_button.h" +#include "ui/controls/send_button.h" #include "window/window_session_controller.h" #include "mainwindow.h" diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index d433276e1..41d644c40 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -526,11 +526,7 @@ void RepliesWidget::chooseAttach() { return; } - const auto filter = FileDialog::AllFilesFilter() - + qsl(";;Image files (*") - + cImgExtensions().join(qsl(" *")) - + qsl(")"); - + const auto filter = FileDialog::AllOrImagesFilter(); FileDialog::GetOpenPaths(this, tr::lng_choose_files(tr::now), filter, crl::guard(this, [=]( FileDialog::OpenResult &&result) { if (result.paths.isEmpty() && result.remoteContent.isEmpty()) { diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index ce7b5e9f7..dca932b12 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -266,11 +266,7 @@ void ScheduledWidget::chooseAttach() { return; } - const auto filter = FileDialog::AllFilesFilter() - + qsl(";;Image files (*") - + cImgExtensions().join(qsl(" *")) - + qsl(")"); - + const auto filter = FileDialog::AllOrImagesFilter(); FileDialog::GetOpenPaths(this, tr::lng_choose_files(tr::now), filter, crl::guard(this, [=]( FileDialog::OpenResult &&result) { if (result.paths.isEmpty() && result.remoteContent.isEmpty()) { diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp index 5148a10f9..6bdad1c90 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp @@ -853,10 +853,7 @@ void EditScans::ChooseScan( Fn errorCallback) { Expects(parent != nullptr); - const auto filter = FileDialog::AllFilesFilter() - + qsl(";;Image files (*") - + cImgExtensions().join(qsl(" *")) - + qsl(")"); + const auto filter = FileDialog::AllOrImagesFilter(); const auto guardedCallback = crl::guard(parent, doneCallback); const auto guardedError = crl::guard(parent, errorCallback); const auto onMainError = [=](ReadScanError error) { diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp index 8633a560e..a27a33f60 100644 --- a/Telegram/SourceFiles/settings/settings_chat.cpp +++ b/Telegram/SourceFiles/settings/settings_chat.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" +#include "ui/chat/attach/attach_extensions.h" #include "ui/layers/generic_box.h" #include "ui/effects/radial_animation.h" #include "ui/toast/toast.h" @@ -600,10 +601,9 @@ void BackgroundRow::updateImage() { void ChooseFromFile( not_null controller, not_null parent) { - const auto &imgExtensions = cImgExtensions(); auto filters = QStringList( qsl("Theme files (*.tdesktop-theme *.tdesktop-palette *") - + imgExtensions.join(qsl(" *")) + + Ui::ImageExtensions().join(qsl(" *")) + qsl(")")); filters.push_back(FileDialog::AllFilesFilter()); const auto callback = crl::guard(controller, [=]( diff --git a/Telegram/SourceFiles/settings/settings_information.cpp b/Telegram/SourceFiles/settings/settings_information.cpp index 476c6ba32..da2fd6461 100644 --- a/Telegram/SourceFiles/settings/settings_information.cpp +++ b/Telegram/SourceFiles/settings/settings_information.cpp @@ -63,11 +63,7 @@ void SetupPhoto( st::settingsInfoPhotoSet); upload->setFullRadius(true); upload->addClickHandler([=] { - const auto imageExtensions = cImgExtensions(); - const auto filter = qsl("Image files (*") - + imageExtensions.join(qsl(" *")) - + qsl(");;") - + FileDialog::AllFilesFilter(); + const auto filter = FileDialog::ImagesOrAllFilter(); const auto callback = [=](const FileDialog::OpenResult &result) { if (result.paths.isEmpty() && result.remoteContent.isEmpty()) { return; diff --git a/Telegram/SourceFiles/storage/storage_media_prepare.cpp b/Telegram/SourceFiles/storage/storage_media_prepare.cpp index c67f36b34..6099d31df 100644 --- a/Telegram/SourceFiles/storage/storage_media_prepare.cpp +++ b/Telegram/SourceFiles/storage/storage_media_prepare.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/localimageloader.h" #include "core/mime_type.h" #include "ui/image/image_prepare.h" +#include "ui/chat/attach/attach_extensions.h" #include "app.h" #include @@ -194,7 +195,7 @@ MimeDataState ComputeMimeDataState(const QMimeData *data) { return MimeDataState::None; } - const auto imageExtensions = cImgExtensions(); + const auto imageExtensions = Ui::ImageExtensions(); auto files = QStringList(); auto allAreSmallImages = true; for (const auto &url : urls) { @@ -242,7 +243,7 @@ PreparedList PrepareMediaList(const QList &files, int previewWidth) { PreparedList PrepareMediaList(const QStringList &files, int previewWidth) { auto result = PreparedList(); result.files.reserve(files.size()); - const auto extensionsToCompress = cExtensionsForCompress(); + const auto extensionsToCompress = Ui::ExtensionsForCompression(); for (const auto &file : files) { const auto fileinfo = QFileInfo(file); const auto filesize = fileinfo.size(); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_extensions.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_extensions.cpp new file mode 100644 index 000000000..73fbaf25b --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/attach/attach_extensions.cpp @@ -0,0 +1,33 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/chat/attach/attach_extensions.h" + +namespace Ui { + +const QStringList &ImageExtensions() { + static const auto result = QStringList{ + u".bmp"_q, + u".jpg"_q, + u".jpeg"_q, + u".png"_q, + u".gif"_q, + }; + return result; +} + +const QStringList &ExtensionsForCompression() { + static const auto result = QStringList{ + u".bmp"_q, + u".jpg"_q, + u".jpeg"_q, + u".png"_q, + }; + return result; +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_extensions.h b/Telegram/SourceFiles/ui/chat/attach/attach_extensions.h new file mode 100644 index 000000000..f49f685ae --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/attach/attach_extensions.h @@ -0,0 +1,15 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Ui { + +[[nodiscard]] const QStringList &ImageExtensions(); +[[nodiscard]] const QStringList &ExtensionsForCompression(); + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/controls/emoji_button.cpp b/Telegram/SourceFiles/ui/controls/emoji_button.cpp new file mode 100644 index 000000000..83b9ba31b --- /dev/null +++ b/Telegram/SourceFiles/ui/controls/emoji_button.cpp @@ -0,0 +1,127 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/controls/emoji_button.h" + +#include "ui/effects/radial_animation.h" +#include "ui/effects/ripple_animation.h" +#include "styles/style_chat.h" + +namespace Ui { +namespace { + +} // namespace + +EmojiButton::EmojiButton(QWidget *parent, const style::IconButton &st) +: RippleButton(parent, st.ripple) +, _st(st) { + resize(_st.width, _st.height); + setCursor(style::cur_pointer); +} + +void EmojiButton::paintEvent(QPaintEvent *e) { + Painter p(this); + + p.fillRect(e->rect(), st::historyComposeAreaBg); + paintRipple(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y(), _rippleOverride ? &(*_rippleOverride)->c : nullptr); + + const auto over = isOver(); + const auto loadingState = _loading + ? _loading->computeState() + : RadialState{ 0., 0, RadialState::kFull }; + if (loadingState.shown < 1.) { + p.setOpacity(1. - loadingState.shown); + + const auto icon = _iconOverride ? _iconOverride : &(over ? _st.iconOver : _st.icon); + auto position = _st.iconPosition; + if (position.x() < 0) { + position.setX((width() - icon->width()) / 2); + } + if (position.y() < 0) { + position.setY((height() - icon->height()) / 2); + } + icon->paint(p, position, width()); + + p.setOpacity(1.); + } + + QRect inner(QPoint((width() - st::historyEmojiCircle.width()) / 2, (height() - st::historyEmojiCircle.height()) / 2), st::historyEmojiCircle); + const auto color = (_colorOverride + ? *_colorOverride + : (over + ? st::historyEmojiCircleFgOver + : st::historyEmojiCircleFg)); + if (anim::Disabled() && _loading && _loading->animating()) { + anim::DrawStaticLoading( + p, + inner, + st::historyEmojiCircleLine, + color); + } else { + auto pen = color->p; + pen.setWidth(st::historyEmojiCircleLine); + pen.setCapStyle(Qt::RoundCap); + p.setPen(pen); + p.setBrush(Qt::NoBrush); + + PainterHighQualityEnabler hq(p); + if (loadingState.arcLength < RadialState::kFull) { + p.drawArc(inner, loadingState.arcFrom, loadingState.arcLength); + } else { + p.drawEllipse(inner); + } + } +} + +void EmojiButton::loadingAnimationCallback() { + if (!anim::Disabled()) { + update(); + } +} + +void EmojiButton::setLoading(bool loading) { + if (loading && !_loading) { + _loading = std::make_unique( + [=] { loadingAnimationCallback(); }, + st::defaultInfiniteRadialAnimation); + } + if (loading) { + _loading->start(); + update(); + } else if (_loading) { + _loading->stop(); + update(); + } +} + +void EmojiButton::setColorOverrides(const style::icon *iconOverride, const style::color *colorOverride, const style::color *rippleOverride) { + _iconOverride = iconOverride; + _colorOverride = colorOverride; + _rippleOverride = rippleOverride; + update(); +} + +void EmojiButton::onStateChanged(State was, StateChangeSource source) { + RippleButton::onStateChanged(was, source); + auto wasOver = static_cast(was & StateFlag::Over); + if (isOver() != wasOver) { + update(); + } +} + +QPoint EmojiButton::prepareRippleStartPosition() const { + if (!_st.rippleAreaSize) { + return DisabledRippleStartPosition(); + } + return mapFromGlobal(QCursor::pos()) - _st.rippleAreaPosition; +} + +QImage EmojiButton::prepareRippleMask() const { + return RippleAnimation::ellipseMask(QSize(_st.rippleAreaSize, _st.rippleAreaSize)); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/controls/emoji_button.h b/Telegram/SourceFiles/ui/controls/emoji_button.h new file mode 100644 index 000000000..a39d40c9c --- /dev/null +++ b/Telegram/SourceFiles/ui/controls/emoji_button.h @@ -0,0 +1,46 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/widgets/buttons.h" + +namespace Ui { + +class InfiniteRadialAnimation; + +class EmojiButton final : public RippleButton { +public: + EmojiButton(QWidget *parent, const style::IconButton &st); + + void setLoading(bool loading); + void setColorOverrides( + const style::icon *iconOverride, + const style::color *colorOverride, + const style::color *rippleOverride); + +protected: + void paintEvent(QPaintEvent *e) override; + void onStateChanged(State was, StateChangeSource source) override; + + QImage prepareRippleMask() const override; + QPoint prepareRippleStartPosition() const override; + +private: + void loadingAnimationCallback(); + + const style::IconButton &_st; + + std::unique_ptr _loading; + + const style::icon *_iconOverride = nullptr; + const style::color *_colorOverride = nullptr; + const style::color *_rippleOverride = nullptr; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/controls/send_button.cpp b/Telegram/SourceFiles/ui/controls/send_button.cpp new file mode 100644 index 000000000..3c7df9e84 --- /dev/null +++ b/Telegram/SourceFiles/ui/controls/send_button.cpp @@ -0,0 +1,265 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/controls/send_button.h" + +#include "ui/effects/ripple_animation.h" +#include "styles/style_chat.h" + +namespace Ui { +namespace { + +constexpr int kWideScale = 5; + +} // namespace + +SendButton::SendButton(QWidget *parent) +: RippleButton(parent, st::historyReplyCancel.ripple) { + resize(st::historySendSize); +} + +void SendButton::setType(Type type) { + Expects(isSlowmode() || type != Type::Slowmode); + + if (isSlowmode() && type != Type::Slowmode) { + _afterSlowmodeType = type; + return; + } + if (_type != type) { + _contentFrom = grabContent(); + _type = type; + _a_typeChanged.stop(); + _contentTo = grabContent(); + _a_typeChanged.start( + [=] { update(); }, + 0., + 1., + st::historyRecordVoiceDuration); + setPointerCursor(_type != Type::Slowmode); + update(); + } + if (_type != Type::Record) { + _recordActive = false; + _a_recordActive.stop(); + } +} + +void SendButton::setRecordActive(bool recordActive) { + if (_recordActive != recordActive) { + _recordActive = recordActive; + _a_recordActive.start( + [=] { recordAnimationCallback(); }, + _recordActive ? 0. : 1., + _recordActive ? 1. : 0, + st::historyRecordVoiceDuration); + update(); + } +} + +void SendButton::setSlowmodeDelay(int seconds) { + Expects(seconds >= 0 && seconds < kSlowmodeDelayLimit); + + if (_slowmodeDelay == seconds) { + return; + } + _slowmodeDelay = seconds; + _slowmodeDelayText = isSlowmode() + ? u"%1:%2"_q.arg(seconds / 60).arg(seconds % 60, 2, 10, QChar('0')) + : QString(); + setType(isSlowmode() ? Type::Slowmode : _afterSlowmodeType); + update(); +} + +void SendButton::finishAnimating() { + _a_typeChanged.stop(); + _a_recordActive.stop(); + update(); +} + +void SendButton::mouseMoveEvent(QMouseEvent *e) { + AbstractButton::mouseMoveEvent(e); + if (_recording) { + if (_recordUpdateCallback) { + _recordUpdateCallback(e->globalPos()); + } + } +} + +void SendButton::paintEvent(QPaintEvent *e) { + Painter p(this); + + auto over = (isDown() || isOver()); + auto changed = _a_typeChanged.value(1.); + if (changed < 1.) { + PainterHighQualityEnabler hq(p); + p.setOpacity(1. - changed); + auto targetRect = QRect((1 - kWideScale) / 2 * width(), (1 - kWideScale) / 2 * height(), kWideScale * width(), kWideScale * height()); + auto hiddenWidth = anim::interpolate(0, (1 - kWideScale) / 2 * width(), changed); + auto hiddenHeight = anim::interpolate(0, (1 - kWideScale) / 2 * height(), changed); + p.drawPixmap(targetRect.marginsAdded(QMargins(hiddenWidth, hiddenHeight, hiddenWidth, hiddenHeight)), _contentFrom); + p.setOpacity(changed); + auto shownWidth = anim::interpolate((1 - kWideScale) / 2 * width(), 0, changed); + auto shownHeight = anim::interpolate((1 - kWideScale) / 2 * height(), 0, changed); + p.drawPixmap(targetRect.marginsAdded(QMargins(shownWidth, shownHeight, shownWidth, shownHeight)), _contentTo); + return; + } + switch (_type) { + case Type::Record: paintRecord(p, over); break; + case Type::Save: paintSave(p, over); break; + case Type::Cancel: paintCancel(p, over); break; + case Type::Send: paintSend(p, over); break; + case Type::Schedule: paintSchedule(p, over); break; + case Type::Slowmode: paintSlowmode(p); break; + } +} + +void SendButton::paintRecord(Painter &p, bool over) { + auto recordActive = recordActiveRatio(); + if (!isDisabled()) { + auto rippleColor = anim::color(st::historyAttachEmoji.ripple.color, st::historyRecordVoiceRippleBgActive, recordActive); + paintRipple(p, (width() - st::historyAttachEmoji.rippleAreaSize) / 2, st::historyAttachEmoji.rippleAreaPosition.y(), &rippleColor); + } + + auto fastIcon = [&] { + if (isDisabled()) { + return &st::historyRecordVoice; + } else if (recordActive == 1.) { + return &st::historyRecordVoiceActive; + } else if (over) { + return &st::historyRecordVoiceOver; + } + return &st::historyRecordVoice; + }; + fastIcon()->paintInCenter(p, rect()); + if (!isDisabled() && recordActive > 0. && recordActive < 1.) { + p.setOpacity(recordActive); + st::historyRecordVoiceActive.paintInCenter(p, rect()); + p.setOpacity(1.); + } +} + +void SendButton::paintSave(Painter &p, bool over) { + const auto &saveIcon = over + ? st::historyEditSaveIconOver + : st::historyEditSaveIcon; + saveIcon.paint(p, st::historySendIconPosition, width()); +} + +void SendButton::paintCancel(Painter &p, bool over) { + paintRipple(p, (width() - st::historyAttachEmoji.rippleAreaSize) / 2, st::historyAttachEmoji.rippleAreaPosition.y()); + + const auto &cancelIcon = over + ? st::historyReplyCancelIconOver + : st::historyReplyCancelIcon; + cancelIcon.paintInCenter(p, rect()); +} + +void SendButton::paintSend(Painter &p, bool over) { + const auto &sendIcon = over + ? st::historySendIconOver + : st::historySendIcon; + if (isDisabled()) { + const auto color = st::historyRecordVoiceFg->c; + sendIcon.paint(p, st::historySendIconPosition, width(), color); + } else { + sendIcon.paint(p, st::historySendIconPosition, width()); + } +} + +void SendButton::paintSchedule(Painter &p, bool over) { + { + PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); + p.setBrush(over ? st::historySendIconFgOver : st::historySendIconFg); + p.drawEllipse( + st::historyScheduleIconPosition.x(), + st::historyScheduleIconPosition.y(), + st::historyScheduleIcon.width(), + st::historyScheduleIcon.height()); + } + st::historyScheduleIcon.paint( + p, + st::historyScheduleIconPosition, + width()); +} + +void SendButton::paintSlowmode(Painter &p) { + p.setFont(st::normalFont); + p.setPen(st::windowSubTextFg); + p.drawText( + rect().marginsRemoved(st::historySlowmodeCounterMargins), + _slowmodeDelayText, + style::al_center); +} + +void SendButton::onStateChanged(State was, StateChangeSource source) { + RippleButton::onStateChanged(was, source); + + auto down = (state() & StateFlag::Down); + if ((was & StateFlag::Down) != down) { + if (down) { + if (_type == Type::Record) { + _recording = true; + if (_recordStartCallback) { + _recordStartCallback(); + } + } + } else if (_recording) { + _recording = false; + if (_recordStopCallback) { + _recordStopCallback(_recordActive); + } + } + } +} + +bool SendButton::isSlowmode() const { + return (_slowmodeDelay > 0); +} + +QPixmap SendButton::grabContent() { + auto result = QImage( + kWideScale * size() * style::DevicePixelRatio(), + QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(style::DevicePixelRatio()); + result.fill(Qt::transparent); + { + Painter p(&result); + p.drawPixmap( + (kWideScale - 1) / 2 * width(), + (kWideScale - 1) / 2 * height(), + GrabWidget(this)); + } + return Ui::PixmapFromImage(std::move(result)); +} + +QImage SendButton::prepareRippleMask() const { + auto size = (_type == Type::Record) + ? st::historyAttachEmoji.rippleAreaSize + : st::historyReplyCancel.rippleAreaSize; + return RippleAnimation::ellipseMask(QSize(size, size)); +} + +QPoint SendButton::prepareRippleStartPosition() const { + auto real = mapFromGlobal(QCursor::pos()); + auto size = (_type == Type::Record) + ? st::historyAttachEmoji.rippleAreaSize + : st::historyReplyCancel.rippleAreaSize; + auto y = (_type == Type::Record) + ? st::historyAttachEmoji.rippleAreaPosition.y() + : (height() - st::historyReplyCancel.rippleAreaSize) / 2; + return real - QPoint((width() - size) / 2, y); +} + +void SendButton::recordAnimationCallback() { + update(); + if (_recordAnimationCallback) { + _recordAnimationCallback(); + } +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/controls/send_button.h b/Telegram/SourceFiles/ui/controls/send_button.h new file mode 100644 index 000000000..ad6500699 --- /dev/null +++ b/Telegram/SourceFiles/ui/controls/send_button.h @@ -0,0 +1,92 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/widgets/buttons.h" + +namespace Ui { + +class SendButton final : public RippleButton { +public: + SendButton(QWidget *parent); + + static constexpr auto kSlowmodeDelayLimit = 100 * 60; + + enum class Type { + Send, + Schedule, + Save, + Record, + Cancel, + Slowmode, + }; + [[nodiscard]] Type type() const { + return _type; + } + void setType(Type state); + void setRecordActive(bool recordActive); + void setSlowmodeDelay(int seconds); + void finishAnimating(); + + void setRecordStartCallback(Fn callback) { + _recordStartCallback = std::move(callback); + } + void setRecordUpdateCallback(Fn callback) { + _recordUpdateCallback = std::move(callback); + } + void setRecordStopCallback(Fn callback) { + _recordStopCallback = std::move(callback); + } + void setRecordAnimationCallback(Fn callback) { + _recordAnimationCallback = std::move(callback); + } + + [[nodiscard]] float64 recordActiveRatio() { + return _a_recordActive.value(_recordActive ? 1. : 0.); + } + +protected: + void mouseMoveEvent(QMouseEvent *e) override; + void paintEvent(QPaintEvent *e) override; + void onStateChanged(State was, StateChangeSource source) override; + + QImage prepareRippleMask() const override; + QPoint prepareRippleStartPosition() const override; + +private: + void recordAnimationCallback(); + [[nodiscard]] QPixmap grabContent(); + [[nodiscard]] bool isSlowmode() const; + + void paintRecord(Painter &p, bool over); + void paintSave(Painter &p, bool over); + void paintCancel(Painter &p, bool over); + void paintSend(Painter &p, bool over); + void paintSchedule(Painter &p, bool over); + void paintSlowmode(Painter &p); + + Type _type = Type::Send; + Type _afterSlowmodeType = Type::Send; + bool _recordActive = false; + QPixmap _contentFrom, _contentTo; + + Ui::Animations::Simple _a_typeChanged; + Ui::Animations::Simple _a_recordActive; + + bool _recording = false; + Fn _recordStartCallback; + Fn _recordStopCallback; + Fn _recordUpdateCallback; + Fn _recordAnimationCallback; + + int _slowmodeDelay = 0; + QString _slowmodeDelayText; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/special_buttons.cpp b/Telegram/SourceFiles/ui/special_buttons.cpp index 29fce70ba..a353e5465 100644 --- a/Telegram/SourceFiles/ui/special_buttons.cpp +++ b/Telegram/SourceFiles/ui/special_buttons.cpp @@ -43,8 +43,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Ui { namespace { -constexpr int kWideScale = 5; - QString CropTitle(not_null peer) { if (peer->isChat() || peer->isMegagroup()) { return tr::lng_create_group_crop(tr::now); @@ -122,11 +120,7 @@ void ShowChoosePhotoBox( QPointer parent, const QString &title, Callback &&callback) { - auto imgExtensions = cImgExtensions(); - auto filter = qsl("Image files (*") - + imgExtensions.join(qsl(" *")) - + qsl(");;") - + FileDialog::AllFilesFilter(); + auto filter = FileDialog::ImagesOrAllFilter(); auto handleChosenPhoto = [ title, callback = std::forward(callback) @@ -185,342 +179,6 @@ void HistoryDownButton::setUnreadCount(int unreadCount) { } } -EmojiButton::EmojiButton(QWidget *parent, const style::IconButton &st) -: RippleButton(parent, st.ripple) -, _st(st) { - resize(_st.width, _st.height); - setCursor(style::cur_pointer); -} - -void EmojiButton::paintEvent(QPaintEvent *e) { - Painter p(this); - - p.fillRect(e->rect(), st::historyComposeAreaBg); - paintRipple(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y(), _rippleOverride ? &(*_rippleOverride)->c : nullptr); - - const auto over = isOver(); - const auto loadingState = _loading - ? _loading->computeState() - : Ui::RadialState{ 0., 0, FullArcLength }; - if (loadingState.shown < 1.) { - p.setOpacity(1. - loadingState.shown); - - const auto icon = _iconOverride ? _iconOverride : &(over ? _st.iconOver : _st.icon); - auto position = _st.iconPosition; - if (position.x() < 0) { - position.setX((width() - icon->width()) / 2); - } - if (position.y() < 0) { - position.setY((height() - icon->height()) / 2); - } - icon->paint(p, position, width()); - - p.setOpacity(1.); - } - - QRect inner(QPoint((width() - st::historyEmojiCircle.width()) / 2, (height() - st::historyEmojiCircle.height()) / 2), st::historyEmojiCircle); - const auto color = (_colorOverride - ? *_colorOverride - : (over - ? st::historyEmojiCircleFgOver - : st::historyEmojiCircleFg)); - if (anim::Disabled() && _loading && _loading->animating()) { - anim::DrawStaticLoading( - p, - inner, - st::historyEmojiCircleLine, - color); - } else { - auto pen = color->p; - pen.setWidth(st::historyEmojiCircleLine); - pen.setCapStyle(Qt::RoundCap); - p.setPen(pen); - p.setBrush(Qt::NoBrush); - - PainterHighQualityEnabler hq(p); - if (loadingState.arcLength < FullArcLength) { - p.drawArc(inner, loadingState.arcFrom, loadingState.arcLength); - } else { - p.drawEllipse(inner); - } - } -} - -void EmojiButton::loadingAnimationCallback() { - if (!anim::Disabled()) { - update(); - } -} - -void EmojiButton::setLoading(bool loading) { - if (loading && !_loading) { - _loading = std::make_unique( - [=] { loadingAnimationCallback(); }, - st::defaultInfiniteRadialAnimation); - } - if (loading) { - _loading->start(); - update(); - } else if (_loading) { - _loading->stop(); - update(); - } -} - -void EmojiButton::setColorOverrides(const style::icon *iconOverride, const style::color *colorOverride, const style::color *rippleOverride) { - _iconOverride = iconOverride; - _colorOverride = colorOverride; - _rippleOverride = rippleOverride; - update(); -} - -void EmojiButton::onStateChanged(State was, StateChangeSource source) { - RippleButton::onStateChanged(was, source); - auto wasOver = static_cast(was & StateFlag::Over); - if (isOver() != wasOver) { - update(); - } -} - -QPoint EmojiButton::prepareRippleStartPosition() const { - if (!_st.rippleAreaSize) { - return DisabledRippleStartPosition(); - } - return mapFromGlobal(QCursor::pos()) - _st.rippleAreaPosition; -} - -QImage EmojiButton::prepareRippleMask() const { - return RippleAnimation::ellipseMask(QSize(_st.rippleAreaSize, _st.rippleAreaSize)); -} - -SendButton::SendButton(QWidget *parent) : RippleButton(parent, st::historyReplyCancel.ripple) { - resize(st::historySendSize); -} - -void SendButton::setType(Type type) { - Expects(isSlowmode() || type != Type::Slowmode); - - if (isSlowmode() && type != Type::Slowmode) { - _afterSlowmodeType = type; - return; - } - if (_type != type) { - _contentFrom = grabContent(); - _type = type; - _a_typeChanged.stop(); - _contentTo = grabContent(); - _a_typeChanged.start([=] { update(); }, 0., 1., st::historyRecordVoiceDuration); - setPointerCursor(_type != Type::Slowmode); - update(); - } - if (_type != Type::Record) { - _recordActive = false; - _a_recordActive.stop(); - } -} - -void SendButton::setRecordActive(bool recordActive) { - if (_recordActive != recordActive) { - _recordActive = recordActive; - _a_recordActive.start([this] { recordAnimationCallback(); }, _recordActive ? 0. : 1., _recordActive ? 1. : 0, st::historyRecordVoiceDuration); - update(); - } -} - -void SendButton::setSlowmodeDelay(int seconds) { - Expects(seconds >= 0 && seconds < kSlowmodeDelayLimit); - - if (_slowmodeDelay == seconds) { - return; - } - _slowmodeDelay = seconds; - _slowmodeDelayText = isSlowmode() - ? qsl("%1:%2").arg(seconds / 60).arg(seconds % 60, 2, 10, QChar('0')) - : QString(); - setType(isSlowmode() ? Type::Slowmode : _afterSlowmodeType); - update(); -} - -void SendButton::finishAnimating() { - _a_typeChanged.stop(); - _a_recordActive.stop(); - update(); -} - -void SendButton::mouseMoveEvent(QMouseEvent *e) { - AbstractButton::mouseMoveEvent(e); - if (_recording) { - if (_recordUpdateCallback) { - _recordUpdateCallback(e->globalPos()); - } - } -} - -void SendButton::paintEvent(QPaintEvent *e) { - Painter p(this); - - auto over = (isDown() || isOver()); - auto changed = _a_typeChanged.value(1.); - if (changed < 1.) { - PainterHighQualityEnabler hq(p); - p.setOpacity(1. - changed); - auto targetRect = QRect((1 - kWideScale) / 2 * width(), (1 - kWideScale) / 2 * height(), kWideScale * width(), kWideScale * height()); - auto hiddenWidth = anim::interpolate(0, (1 - kWideScale) / 2 * width(), changed); - auto hiddenHeight = anim::interpolate(0, (1 - kWideScale) / 2 * height(), changed); - p.drawPixmap(targetRect.marginsAdded(QMargins(hiddenWidth, hiddenHeight, hiddenWidth, hiddenHeight)), _contentFrom); - p.setOpacity(changed); - auto shownWidth = anim::interpolate((1 - kWideScale) / 2 * width(), 0, changed); - auto shownHeight = anim::interpolate((1 - kWideScale) / 2 * height(), 0, changed); - p.drawPixmap(targetRect.marginsAdded(QMargins(shownWidth, shownHeight, shownWidth, shownHeight)), _contentTo); - return; - } - switch (_type) { - case Type::Record: paintRecord(p, over); break; - case Type::Save: paintSave(p, over); break; - case Type::Cancel: paintCancel(p, over); break; - case Type::Send: paintSend(p, over); break; - case Type::Schedule: paintSchedule(p, over); break; - case Type::Slowmode: paintSlowmode(p); break; - } -} - -void SendButton::paintRecord(Painter &p, bool over) { - auto recordActive = recordActiveRatio(); - if (!isDisabled()) { - auto rippleColor = anim::color(st::historyAttachEmoji.ripple.color, st::historyRecordVoiceRippleBgActive, recordActive); - paintRipple(p, (width() - st::historyAttachEmoji.rippleAreaSize) / 2, st::historyAttachEmoji.rippleAreaPosition.y(), &rippleColor); - } - - auto fastIcon = [&] { - if (isDisabled()) { - return &st::historyRecordVoice; - } else if (recordActive == 1.) { - return &st::historyRecordVoiceActive; - } else if (over) { - return &st::historyRecordVoiceOver; - } - return &st::historyRecordVoice; - }; - fastIcon()->paintInCenter(p, rect()); - if (!isDisabled() && recordActive > 0. && recordActive < 1.) { - p.setOpacity(recordActive); - st::historyRecordVoiceActive.paintInCenter(p, rect()); - p.setOpacity(1.); - } -} - -void SendButton::paintSave(Painter &p, bool over) { - const auto &saveIcon = over - ? st::historyEditSaveIconOver - : st::historyEditSaveIcon; - saveIcon.paint(p, st::historySendIconPosition, width()); -} - -void SendButton::paintCancel(Painter &p, bool over) { - paintRipple(p, (width() - st::historyAttachEmoji.rippleAreaSize) / 2, st::historyAttachEmoji.rippleAreaPosition.y()); - - const auto &cancelIcon = over - ? st::historyReplyCancelIconOver - : st::historyReplyCancelIcon; - cancelIcon.paintInCenter(p, rect()); -} - -void SendButton::paintSend(Painter &p, bool over) { - const auto &sendIcon = over - ? st::historySendIconOver - : st::historySendIcon; - if (isDisabled()) { - const auto color = st::historyRecordVoiceFg->c; - sendIcon.paint(p, st::historySendIconPosition, width(), color); - } else { - sendIcon.paint(p, st::historySendIconPosition, width()); - } -} - -void SendButton::paintSchedule(Painter &p, bool over) { - { - PainterHighQualityEnabler hq(p); - p.setPen(Qt::NoPen); - p.setBrush(over ? st::historySendIconFgOver : st::historySendIconFg); - p.drawEllipse( - st::historyScheduleIconPosition.x(), - st::historyScheduleIconPosition.y(), - st::historyScheduleIcon.width(), - st::historyScheduleIcon.height()); - } - st::historyScheduleIcon.paint( - p, - st::historyScheduleIconPosition, - width()); -} - -void SendButton::paintSlowmode(Painter &p) { - p.setFont(st::normalFont); - p.setPen(st::windowSubTextFg); - p.drawText( - rect().marginsRemoved(st::historySlowmodeCounterMargins), - _slowmodeDelayText, - style::al_center); -} - -void SendButton::onStateChanged(State was, StateChangeSource source) { - RippleButton::onStateChanged(was, source); - - auto down = (state() & StateFlag::Down); - if ((was & StateFlag::Down) != down) { - if (down) { - if (_type == Type::Record) { - _recording = true; - if (_recordStartCallback) { - _recordStartCallback(); - } - } - } else if (_recording) { - _recording = false; - if (_recordStopCallback) { - _recordStopCallback(_recordActive); - } - } - } -} - -bool SendButton::isSlowmode() const { - return (_slowmodeDelay > 0); -} - -QPixmap SendButton::grabContent() { - auto result = QImage(kWideScale * size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); - result.setDevicePixelRatio(cRetinaFactor()); - result.fill(Qt::transparent); - { - Painter p(&result); - p.drawPixmap( - (kWideScale - 1) / 2 * width(), - (kWideScale - 1) / 2 * height(), - GrabWidget(this)); - } - return App::pixmapFromImageInPlace(std::move(result)); -} - -QImage SendButton::prepareRippleMask() const { - auto size = (_type == Type::Record) ? st::historyAttachEmoji.rippleAreaSize : st::historyReplyCancel.rippleAreaSize; - return Ui::RippleAnimation::ellipseMask(QSize(size, size)); -} - -QPoint SendButton::prepareRippleStartPosition() const { - auto real = mapFromGlobal(QCursor::pos()); - auto size = (_type == Type::Record) ? st::historyAttachEmoji.rippleAreaSize : st::historyReplyCancel.rippleAreaSize; - auto y = (_type == Type::Record) ? st::historyAttachEmoji.rippleAreaPosition.y() : (height() - st::historyReplyCancel.rippleAreaSize) / 2; - return real - QPoint((width() - size) / 2, y); -} - -void SendButton::recordAnimationCallback() { - update(); - if (_recordAnimationCallback) { - _recordAnimationCallback(); - } -} - UserpicButton::UserpicButton( QWidget *parent, const QString &cropTitle, diff --git a/Telegram/SourceFiles/ui/special_buttons.h b/Telegram/SourceFiles/ui/special_buttons.h index 86d908e01..4cc13286f 100644 --- a/Telegram/SourceFiles/ui/special_buttons.h +++ b/Telegram/SourceFiles/ui/special_buttons.h @@ -34,8 +34,6 @@ struct Information; namespace Ui { -class InfiniteRadialAnimation; - class HistoryDownButton : public RippleButton { public: HistoryDownButton(QWidget *parent, const style::TwoIconButton &st); @@ -58,111 +56,6 @@ private: }; -class EmojiButton : public RippleButton { -public: - EmojiButton(QWidget *parent, const style::IconButton &st); - - void setLoading(bool loading); - void setColorOverrides(const style::icon *iconOverride, const style::color *colorOverride, const style::color *rippleOverride); - -protected: - void paintEvent(QPaintEvent *e) override; - void onStateChanged(State was, StateChangeSource source) override; - - QImage prepareRippleMask() const override; - QPoint prepareRippleStartPosition() const override; - -private: - void loadingAnimationCallback(); - - const style::IconButton &_st; - - std::unique_ptr _loading; - - const style::icon *_iconOverride = nullptr; - const style::color *_colorOverride = nullptr; - const style::color *_rippleOverride = nullptr; - -}; - -class SendButton : public RippleButton { -public: - SendButton(QWidget *parent); - - static constexpr auto kSlowmodeDelayLimit = 100 * 60; - - enum class Type { - Send, - Schedule, - Save, - Record, - Cancel, - Slowmode, - }; - Type type() const { - return _type; - } - void setType(Type state); - void setRecordActive(bool recordActive); - void setSlowmodeDelay(int seconds); - void finishAnimating(); - - void setRecordStartCallback(Fn callback) { - _recordStartCallback = std::move(callback); - } - void setRecordUpdateCallback(Fn callback) { - _recordUpdateCallback = std::move(callback); - } - void setRecordStopCallback(Fn callback) { - _recordStopCallback = std::move(callback); - } - void setRecordAnimationCallback(Fn callback) { - _recordAnimationCallback = std::move(callback); - } - - float64 recordActiveRatio() { - return _a_recordActive.value(_recordActive ? 1. : 0.); - } - -protected: - void mouseMoveEvent(QMouseEvent *e) override; - void paintEvent(QPaintEvent *e) override; - void onStateChanged(State was, StateChangeSource source) override; - - QImage prepareRippleMask() const override; - QPoint prepareRippleStartPosition() const override; - -private: - void recordAnimationCallback(); - QPixmap grabContent(); - bool isSlowmode() const; - - void paintRecord(Painter &p, bool over); - void paintSave(Painter &p, bool over); - void paintCancel(Painter &p, bool over); - void paintSend(Painter &p, bool over); - void paintSchedule(Painter &p, bool over); - void paintSlowmode(Painter &p); - - Type _type = Type::Send; - Type _afterSlowmodeType = Type::Send; - bool _recordActive = false; - QPixmap _contentFrom, _contentTo; - - Ui::Animations::Simple _a_typeChanged; - Ui::Animations::Simple _a_recordActive; - - bool _recording = false; - Fn _recordStartCallback; - Fn _recordStopCallback; - Fn _recordUpdateCallback; - Fn _recordAnimationCallback; - - int _slowmodeDelay = 0; - QString _slowmodeDelayText; - -}; - class UserpicButton : public RippleButton { public: enum class Role { diff --git a/Telegram/SourceFiles/ui/ui_pch.h b/Telegram/SourceFiles/ui/ui_pch.h index 07af4a4fb..52bc1e52a 100644 --- a/Telegram/SourceFiles/ui/ui_pch.h +++ b/Telegram/SourceFiles/ui/ui_pch.h @@ -19,6 +19,7 @@ #include #include #include +#include #include @@ -34,3 +35,4 @@ #include "ui/text/text.h" #include "ui/effects/animations.h" +#include "styles/palette.h" diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 9c41becf5..b1feecb12 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -31,17 +31,24 @@ nice_target_sources(td_ui ${src_loc} PRIVATE ${style_files} - ui/ui_pch.h + ui/chat/attach/attach_extensions.cpp + ui/chat/attach/attach_extensions.h ui/chat/message_bar.cpp ui/chat/message_bar.h ui/chat/pinned_bar.cpp ui/chat/pinned_bar.h + ui/controls/emoji_button.cpp + ui/controls/emoji_button.h + ui/controls/send_button.cpp + ui/controls/send_button.h ui/text/format_values.cpp ui/text/format_values.h ui/text/text_options.cpp ui/text/text_options.h ui/toasts/common_toasts.cpp ui/toasts/common_toasts.h + + ui/ui_pch.h ) target_include_directories(td_ui From b3b11bd9e7de205b42b1b60e81029c7346ce4caa Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 13 Oct 2020 15:07:53 +0300 Subject: [PATCH 088/190] Move PreparedFile/PreparedList to td_ui. --- Telegram/SourceFiles/apiwrap.cpp | 9 +- Telegram/SourceFiles/apiwrap.h | 9 +- .../SourceFiles/boxes/edit_caption_box.cpp | 15 +-- Telegram/SourceFiles/boxes/edit_caption_box.h | 3 +- Telegram/SourceFiles/boxes/send_files_box.cpp | 71 +++++++------- Telegram/SourceFiles/boxes/send_files_box.h | 11 ++- Telegram/SourceFiles/core/mime_type.cpp | 28 +++--- Telegram/SourceFiles/core/mime_type.h | 1 + .../SourceFiles/history/history_widget.cpp | 15 +-- Telegram/SourceFiles/history/history_widget.h | 9 +- .../view/history_view_replies_section.cpp | 15 +-- .../view/history_view_replies_section.h | 8 +- .../view/history_view_scheduled_section.cpp | 15 +-- .../view/history_view_scheduled_section.h | 11 +-- .../SourceFiles/media/audio/media_audio.cpp | 4 +- .../SourceFiles/media/audio/media_audio.h | 6 +- .../media/clip/media_clip_reader.cpp | 4 +- .../media/clip/media_clip_reader.h | 6 +- .../SourceFiles/storage/localimageloader.cpp | 33 ++++--- .../SourceFiles/storage/localimageloader.h | 42 ++------- .../storage/storage_media_prepare.cpp | 93 +++---------------- .../storage/storage_media_prepare.h | 75 +++------------ .../SourceFiles/support/support_helper.cpp | 1 + .../ui/chat/attach/attach_prepare.cpp | 91 ++++++++++++++++++ .../ui/chat/attach/attach_prepare.h | 85 +++++++++++++++++ 25 files changed, 358 insertions(+), 302 deletions(-) create mode 100644 Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp create mode 100644 Telegram/SourceFiles/ui/chat/attach/attach_prepare.h diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index f78d3dcd4..686d8924a 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -66,6 +66,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/message_field.h" #include "ui/item_text_options.h" #include "ui/emoji_config.h" +#include "ui/chat/attach/attach_prepare.h" #include "support/support_helper.h" #include "storage/localimageloader.h" #include "storage/download_manager_mtproto.h" @@ -4183,7 +4184,7 @@ void ApiWrap::sendVoiceMessage( } void ApiWrap::editMedia( - Storage::PreparedList &&list, + Ui::PreparedList &&list, SendMediaType type, TextWithTags &&caption, const SendAction &action, @@ -4205,7 +4206,7 @@ void ApiWrap::editMedia( } void ApiWrap::sendFiles( - Storage::PreparedList &&list, + Ui::PreparedList &&list, SendMediaType type, TextWithTags &&caption, std::shared_ptr album, @@ -4231,10 +4232,10 @@ void ApiWrap::sendFiles( for (auto &file : list.files) { if (album) { switch (file.type) { - case Storage::PreparedFile::AlbumType::Photo: + case Ui::PreparedFile::AlbumType::Photo: type = SendMediaType::Photo; break; - case Storage::PreparedFile::AlbumType::Video: + case Ui::PreparedFile::AlbumType::Video: type = SendMediaType::File; break; default: Unexpected("AlbumType in uploadFilesAfterConfirmation"); diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index bf8140496..24c0d0960 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -36,7 +36,6 @@ class Result; namespace Storage { enum class SharedMediaType : signed char; -struct PreparedList; class DownloadMtprotoTask; class Account; } // namespace Storage @@ -49,6 +48,10 @@ namespace Core { struct CloudPasswordState; } // namespace Core +namespace Ui { +struct PreparedList; +} // namespace Ui + namespace Api { class Updates; @@ -393,7 +396,7 @@ public: int duration, const SendAction &action); void sendFiles( - Storage::PreparedList &&list, + Ui::PreparedList &&list, SendMediaType type, TextWithTags &&caption, std::shared_ptr album, @@ -404,7 +407,7 @@ public: const SendAction &action); void editMedia( - Storage::PreparedList &&list, + Ui::PreparedList &&list, SendMediaType type, TextWithTags &&caption, const SendAction &action, diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index 93ca21033..371d966f5 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -48,6 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/checkbox.h" #include "ui/text/format_values.h" #include "ui/text/text_options.h" +#include "ui/chat/attach/attach_prepare.h" #include "ui/controls/emoji_button.h" #include "window/window_session_controller.h" #include "confirm_box.h" @@ -67,13 +68,13 @@ using namespace ::Media::Streaming; using Data::PhotoSize; auto ListFromMimeData(not_null data) { - using Error = Storage::PreparedList::Error; + using Error = Ui::PreparedList::Error; auto result = data->hasUrls() ? Storage::PrepareMediaList( // When we edit media, we need only 1 file. data->urls().mid(0, 1), st::sendMediaPreviewSize) - : Storage::PreparedList(Error::EmptyFile, QString()); + : Ui::PreparedList(Error::EmptyFile, QString()); if (result.error == Error::None) { return result; } else if (data->hasImage()) { @@ -475,7 +476,7 @@ void EditCaptionBox::streamingReady(Information &&info) { } void EditCaptionBox::updateEditPreview() { - using Info = FileMediaInformation; + using Info = Ui::PreparedFileInformation; const auto file = &_preparedList.files.front(); const auto fileMedia = &file->information->media; @@ -590,7 +591,7 @@ void EditCaptionBox::createEditMediaButton() { Ui::show(Box(t(tr::now)), Ui::LayerOption::KeepOther); }; - auto list = Storage::PreparedList::PreparedFileFromFilesDialog( + auto list = Storage::PreparedFileFromFilesDialog( std::move(result), _isAlbum, std::move(showBoxErrorCallback), @@ -677,8 +678,8 @@ bool EditCaptionBox::fileFromClipboard(not_null data) { if (!_isAllowedEditMedia) { return false; } - using Error = Storage::PreparedList::Error; - using AlbumType = Storage::PreparedFile::AlbumType; + using Error = Ui::PreparedList::Error; + using AlbumType = Ui::PreparedFile::AlbumType; auto list = ListFromMimeData(data); if (list.error != Error::None || list.files.empty()) { @@ -688,7 +689,7 @@ bool EditCaptionBox::fileFromClipboard(not_null data) { const auto file = &list.files.front(); if (_isAlbum && (file->type == AlbumType::None)) { const auto imageAsDoc = [&] { - using Info = FileMediaInformation; + using Info = Ui::PreparedFileInformation; const auto fileMedia = &file->information->media; if (const auto image = std::get_if(fileMedia)) { return !Storage::ValidateThumbDimensions( diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.h b/Telegram/SourceFiles/boxes/edit_caption_box.h index 4ffc10ffc..a91e35e9e 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.h +++ b/Telegram/SourceFiles/boxes/edit_caption_box.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/abstract_box.h" #include "storage/storage_media_prepare.h" #include "ui/wrap/slide_wrap.h" +#include "ui/chat/attach/attach_prepare.h" class Image; @@ -130,7 +131,7 @@ private: int _gifh = 0; int _gifx = 0; - Storage::PreparedList _preparedList; + Ui::PreparedList _preparedList; mtpRequestId _saveRequestId = 0; diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index a247b56b0..b927cc1f4 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/input_fields.h" #include "ui/widgets/scroll_area.h" #include "ui/wrap/fade_wrap.h" +#include "ui/chat/attach/attach_prepare.h" #include "ui/text/format_values.h" #include "ui/grouped_layout.h" #include "ui/text/text_options.h" @@ -67,13 +68,13 @@ inline bool CanAddUrls(const QList &urls) { return !urls.isEmpty() && ranges::all_of(urls, &QUrl::isLocalFile); } -inline bool IsFirstAlbumItem(const Storage::PreparedList &list) { - using AlbumType = Storage::PreparedFile::AlbumType; +inline bool IsFirstAlbumItem(const Ui::PreparedList &list) { + using AlbumType = Ui::PreparedFile::AlbumType; return (list.files.size() > 0) && (list.files.front().type != AlbumType::None); } -inline bool IsSingleItem(const Storage::PreparedList &list) { +inline bool IsSingleItem(const Ui::PreparedList &list) { return list.files.size() == 1; } @@ -132,12 +133,12 @@ QRect PaintAlbumThumbButtons( void FileDialogCallback( FileDialog::OpenResult &&result, bool isAlbum, - Fn callback) { + Fn callback) { auto showBoxErrorCallback = [](tr::phrase<> text) { Ui::show(Box(text(tr::now)), Ui::LayerOption::KeepOther); }; - auto list = Storage::PreparedList::PreparedFileFromFilesDialog( + auto list = Storage::PreparedFileFromFilesDialog( std::move(result), isAlbum, std::move(showBoxErrorCallback), @@ -155,7 +156,7 @@ public: static SingleMediaPreview *Create( QWidget *parent, not_null controller, - const Storage::PreparedFile &file); + const Ui::PreparedFile &file); SingleMediaPreview( QWidget *parent, @@ -198,7 +199,7 @@ class SingleFilePreview : public Ui::RpWidget { public: SingleFilePreview( QWidget *parent, - const Storage::PreparedFile &file); + const Ui::PreparedFile &file); rpl::producer desiredHeightValue() const override; @@ -206,7 +207,7 @@ protected: void paintEvent(QPaintEvent *e) override; private: - void preparePreview(const Storage::PreparedFile &file); + void preparePreview(const Ui::PreparedFile &file); void prepareThumb(const QImage &preview); QPixmap _fileThumb; @@ -221,7 +222,7 @@ private: class AlbumThumb { public: AlbumThumb( - const Storage::PreparedFile &file, + const Ui::PreparedFile &file, const Ui::GroupMediaLayout &layout, QWidget *parent, Fn editCallback, @@ -287,7 +288,7 @@ private: }; AlbumThumb::AlbumThumb( - const Storage::PreparedFile &file, + const Ui::PreparedFile &file, const Ui::GroupMediaLayout &layout, QWidget *parent, Fn editCallback, @@ -295,7 +296,7 @@ AlbumThumb::AlbumThumb( : _layout(layout) , _fullPreview(file.preview) , _shrinkSize(int(std::ceil(st::historyMessageRadius / 1.4))) -, _isVideo(file.type == Storage::PreparedFile::AlbumType::Video) { +, _isVideo(file.type == Ui::PreparedFile::AlbumType::Video) { Expects(!_fullPreview.isNull()); moveToLayout(layout); @@ -762,15 +763,15 @@ void AlbumThumb::finishAnimations() { SingleMediaPreview *SingleMediaPreview::Create( QWidget *parent, not_null controller, - const Storage::PreparedFile &file) { + const Ui::PreparedFile &file) { auto preview = QImage(); bool animated = false; bool animationPreview = false; - if (const auto image = std::get_if( + if (const auto image = std::get_if( &file.information->media)) { preview = image->data; animated = animationPreview = image->animated; - } else if (const auto video = std::get_if( + } else if (const auto video = std::get_if( &file.information->media)) { preview = video->thumbnail; animated = true; @@ -970,7 +971,7 @@ rpl::producer SingleMediaPreview::desiredHeightValue() const { SingleFilePreview::SingleFilePreview( QWidget *parent, - const Storage::PreparedFile &file) + const Ui::PreparedFile &file) : RpWidget(parent) { preparePreview(file); } @@ -1002,12 +1003,12 @@ void SingleFilePreview::prepareThumb(const QImage &preview) { st::msgFileThumbSize)); } -void SingleFilePreview::preparePreview(const Storage::PreparedFile &file) { +void SingleFilePreview::preparePreview(const Ui::PreparedFile &file) { auto preview = QImage(); - if (const auto image = std::get_if( + if (const auto image = std::get_if( &file.information->media)) { preview = image->data; - } else if (const auto video = std::get_if( + } else if (const auto video = std::get_if( &file.information->media)) { preview = video->thumbnail; } @@ -1034,7 +1035,7 @@ void SingleFilePreview::preparePreview(const Storage::PreparedFile &file) { auto songTitle = QString(); auto songPerformer = QString(); if (file.information) { - if (const auto song = std::get_if( + if (const auto song = std::get_if( &file.information->media)) { songTitle = song->title; songPerformer = song->performer; @@ -1118,7 +1119,7 @@ rpl::producer SingleFilePreview::desiredHeightValue() const { } rpl::producer FieldPlaceholder( - const Storage::PreparedList &list, + const Ui::PreparedList &list, SendFilesWay way) { const auto isAlbum = (way == SendFilesWay::Album); const auto compressImages = (way != SendFilesWay::Files); @@ -1133,7 +1134,7 @@ class SendFilesBox::AlbumPreview : public Ui::RpWidget { public: AlbumPreview( QWidget *parent, - const Storage::PreparedList &list, + const Ui::PreparedList &list, SendFilesWay way); void setSendWay(SendFilesWay way); @@ -1185,7 +1186,7 @@ private: void cancelDrag(); void finishDrag(); - const Storage::PreparedList &_list; + const Ui::PreparedList &_list; SendFilesWay _sendWay = SendFilesWay::Files; style::cursor _cursor = style::cur_default; std::vector _order; @@ -1210,7 +1211,7 @@ private: SendFilesBox::AlbumPreview::AlbumPreview( QWidget *parent, - const Storage::PreparedList &list, + const Ui::PreparedList &list, SendFilesWay way) : RpWidget(parent) , _list(list) @@ -1662,7 +1663,7 @@ void SendFilesBox::AlbumPreview::mouseReleaseEvent(QMouseEvent *e) { SendFilesBox::SendFilesBox( QWidget*, not_null controller, - Storage::PreparedList &&list, + Ui::PreparedList &&list, const TextWithTags &caption, CompressConfirm compressed, SendLimit limit, @@ -1899,7 +1900,7 @@ void SendFilesBox::setupDragArea() { void SendFilesBox::updateLeftButtonVisibility() { const auto isAlbum = _list.albumIsPossible - && (_list.files.size() < Storage::MaxAlbumItems()); + && (_list.files.size() < Ui::MaxAlbumItems()); if (isAlbum || (IsSingleItem(_list) && IsFirstAlbumItem(_list))) { _addFileToAlbum->show(); } else { @@ -1999,8 +2000,8 @@ void SendFilesBox::refreshAlbumMediaCount() { _albumVideosCount = _list.albumIsPossible ? ranges::count( _list.files, - Storage::PreparedFile::AlbumType::Video, - [](const Storage::PreparedFile &file) { return file.type; }) + Ui::PreparedFile::AlbumType::Video, + [](const Ui::PreparedFile &file) { return file.type; }) : 0; _albumPhotosCount = _list.albumIsPossible ? (_list.files.size() - _albumVideosCount) @@ -2076,7 +2077,7 @@ void SendFilesBox::applyAlbumOrder() { return; } - _list = Storage::PreparedList::Reordered(std::move(_list), order); + _list = Ui::PreparedList::Reordered(std::move(_list), order); } void SendFilesBox::setupCaption() { @@ -2189,7 +2190,7 @@ bool SendFilesBox::canAddFiles(not_null data) const { ++filesCount; } - if (_list.files.size() + filesCount > Storage::MaxAlbumItems()) { + if (_list.files.size() + filesCount > Ui::MaxAlbumItems()) { return false; } else if (_list.files.size() > 1 && !_albumPreview) { return false; @@ -2204,10 +2205,10 @@ bool SendFilesBox::addFiles(not_null data) { const auto urls = data->hasUrls() ? data->urls() : QList(); auto result = CanAddUrls(urls) ? Storage::PrepareMediaList(urls, st::sendMediaPreviewSize) - : Storage::PreparedList( - Storage::PreparedList::Error::EmptyFile, + : Ui::PreparedList( + Ui::PreparedList::Error::EmptyFile, QString()); - if (result.error == Storage::PreparedList::Error::None) { + if (result.error == Ui::PreparedList::Error::None) { return result; } else if (data->hasImage()) { auto image = Platform::GetImageFromClipboard(); @@ -2226,10 +2227,10 @@ bool SendFilesBox::addFiles(not_null data) { return addFiles(std::move(list)); } -bool SendFilesBox::addFiles(Storage::PreparedList list) { +bool SendFilesBox::addFiles(Ui::PreparedList list) { const auto sumFiles = _list.files.size() + list.files.size(); - const auto cutToAlbumSize = (sumFiles > Storage::MaxAlbumItems()); - if (list.error != Storage::PreparedList::Error::None) { + const auto cutToAlbumSize = (sumFiles > Ui::MaxAlbumItems()); + if (list.error != Ui::PreparedList::Error::None) { return false; } else if (!IsSingleItem(list) && !list.albumIsPossible) { return false; diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h index e7b013071..d8bfc5f99 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.h +++ b/Telegram/SourceFiles/boxes/send_files_box.h @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include "boxes/abstract_box.h" +#include "ui/chat/attach/attach_prepare.h" #include "storage/localimageloader.h" #include "storage/storage_media_prepare.h" @@ -59,7 +60,7 @@ public: SendFilesBox( QWidget*, not_null controller, - Storage::PreparedList &&list, + Ui::PreparedList &&list, const TextWithTags &caption, CompressConfirm compressed, SendLimit limit, @@ -68,7 +69,7 @@ public: void setConfirmedCallback( Fn data) const; bool addFiles(not_null data); - bool addFiles(Storage::PreparedList list); + bool addFiles(Ui::PreparedList list); void openDialogToAddFileToAlbum(); void updateLeftButtonVisibility(); @@ -139,7 +140,7 @@ private: QString _titleText; int _titleHeight = 0; - Storage::PreparedList _list; + Ui::PreparedList _list; CompressConfirm _compressConfirmInitial = CompressConfirm::None; CompressConfirm _compressConfirm = CompressConfirm::None; @@ -147,7 +148,7 @@ private: SendMenu::Type _sendMenuType = SendMenu::Type(); Fn #include #include #include diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 751f3023b..9abb0d823 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/toasts/common_toasts.h" #include "ui/special_buttons.h" #include "ui/emoji_config.h" +#include "ui/chat/attach/attach_prepare.h" #include "ui/widgets/buttons.h" #include "ui/widgets/inner_dropdown.h" #include "ui/widgets/dropdown_menu.h" @@ -4091,7 +4092,7 @@ void HistoryWidget::updateFieldPlaceholder() { } bool HistoryWidget::showSendingFilesError( - const Storage::PreparedList &list) const { + const Ui::PreparedList &list) const { const auto text = [&] { const auto error = _peer ? Data::RestrictionError( @@ -4113,7 +4114,7 @@ bool HistoryWidget::showSendingFilesError( lt_left, Ui::FormatDurationWords(left)); } - using Error = Storage::PreparedList::Error; + using Error = Ui::PreparedList::Error; switch (list.error) { case Error::None: return QString(); case Error::EmptyFile: @@ -4158,7 +4159,7 @@ bool HistoryWidget::confirmSendingFiles( } bool HistoryWidget::confirmSendingFiles( - Storage::PreparedList &&list, + Ui::PreparedList &&list, CompressConfirm compressed, const QString &insertTextOnCancel) { if (showSendingFilesError(list)) { @@ -4191,7 +4192,7 @@ bool HistoryWidget::confirmSendingFiles( sendMenuType()); _field->setTextWithTags({}); box->setConfirmedCallback(crl::guard(this, [=]( - Storage::PreparedList &&list, + Ui::PreparedList &&list, SendFilesWay way, TextWithTags &&caption, Api::SendOptions options, @@ -4279,8 +4280,8 @@ bool HistoryWidget::confirmSendingFiles( auto list = Storage::PrepareMediaList( urls, st::sendMediaPreviewSize); - if (list.error != Storage::PreparedList::Error::NonLocalUrl) { - if (list.error == Storage::PreparedList::Error::None + if (list.error != Ui::PreparedList::Error::NonLocalUrl) { + if (list.error == Ui::PreparedList::Error::None || !hasImage) { const auto emptyTextOnCancel = QString(); confirmSendingFiles( @@ -4310,7 +4311,7 @@ bool HistoryWidget::confirmSendingFiles( } void HistoryWidget::uploadFilesAfterConfirmation( - Storage::PreparedList &&list, + Ui::PreparedList &&list, SendMediaType type, TextWithTags &&caption, MsgId replyTo, diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 230570317..2e5f034c8 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -21,7 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class RPCError; struct FileLoadResult; -struct FileMediaInformation; struct SendingAlbum; enum class SendMediaType; enum class CompressConfirm; @@ -67,6 +66,7 @@ class FlatButton; class LinkButton; class RoundButton; class PinnedBar; +struct PreparedList; namespace Toast { class Instance; } // namespace Toast @@ -84,7 +84,6 @@ class TabbedSelector; namespace Storage { enum class MimeDataState; -struct PreparedList; struct UploadedPhoto; struct UploadedDocument; struct UploadedThumbDocument; @@ -424,15 +423,15 @@ private: CompressConfirm compressed, const QString &insertTextOnCancel = QString()); bool confirmSendingFiles( - Storage::PreparedList &&list, + Ui::PreparedList &&list, CompressConfirm compressed, const QString &insertTextOnCancel = QString()); - bool showSendingFilesError(const Storage::PreparedList &list) const; + bool showSendingFilesError(const Ui::PreparedList &list) const; void uploadFile(const QByteArray &fileContent, SendMediaType type); void uploadFilesAfterConfirmation( - Storage::PreparedList &&list, + Ui::PreparedList &&list, SendMediaType type, TextWithTags &&caption, MsgId replyTo, diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 41d644c40..613acdc9d 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/toast/toast.h" #include "ui/text/format_values.h" #include "ui/text/text_utilities.h" +#include "ui/chat/attach/attach_prepare.h" #include "ui/special_buttons.h" #include "ui/ui_utility.h" #include "ui/toasts/common_toasts.h" @@ -571,8 +572,8 @@ bool RepliesWidget::confirmSendingFiles( auto list = Storage::PrepareMediaList( urls, st::sendMediaPreviewSize); - if (list.error != Storage::PreparedList::Error::NonLocalUrl) { - if (list.error == Storage::PreparedList::Error::None + if (list.error != Ui::PreparedList::Error::NonLocalUrl) { + if (list.error == Ui::PreparedList::Error::None || !hasImage) { const auto emptyTextOnCancel = QString(); confirmSendingFiles( @@ -602,7 +603,7 @@ bool RepliesWidget::confirmSendingFiles( } bool RepliesWidget::confirmSendingFiles( - Storage::PreparedList &&list, + Ui::PreparedList &&list, CompressConfirm compressed, const QString &insertTextOnCancel) { if (showSendingFilesError(list)) { @@ -633,7 +634,7 @@ bool RepliesWidget::confirmSendingFiles( const auto replyTo = replyToId(); box->setConfirmedCallback(crl::guard(this, [=]( - Storage::PreparedList &&list, + Ui::PreparedList &&list, SendFilesWay way, TextWithTags &&caption, Api::SendOptions options, @@ -728,7 +729,7 @@ std::optional RepliesWidget::writeRestriction() const { } void RepliesWidget::uploadFilesAfterConfirmation( - Storage::PreparedList &&list, + Ui::PreparedList &&list, SendMediaType type, TextWithTags &&caption, MsgId replyTo, @@ -818,7 +819,7 @@ void RepliesWidget::uploadFile( } bool RepliesWidget::showSendingFilesError( - const Storage::PreparedList &list) const { + const Ui::PreparedList &list) const { const auto text = [&] { const auto error = Data::RestrictionError( _history->peer, @@ -836,7 +837,7 @@ bool RepliesWidget::showSendingFilesError( lt_left, Ui::FormatDurationWords(left)); } - using Error = Storage::PreparedList::Error; + using Error = Ui::PreparedList::Error; switch (list.error) { case Error::None: return QString(); case Error::EmptyFile: diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 5adf27731..97e061a55 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -27,7 +27,6 @@ struct SendOptions; } // namespace Api namespace Storage { -struct PreparedList; } // namespace Storage namespace Ui { @@ -36,6 +35,7 @@ class PlainShadow; class FlatButton; class HistoryDownButton; class PinnedBar; +struct PreparedList; } // namespace Ui namespace Profile { @@ -201,16 +201,16 @@ private: CompressConfirm compressed, const QString &insertTextOnCancel = QString()); bool confirmSendingFiles( - Storage::PreparedList &&list, + Ui::PreparedList &&list, CompressConfirm compressed, const QString &insertTextOnCancel = QString()); bool confirmSendingFiles( not_null data, CompressConfirm compressed, const QString &insertTextOnCancel = QString()); - bool showSendingFilesError(const Storage::PreparedList &list) const; + bool showSendingFilesError(const Ui::PreparedList &list) const; void uploadFilesAfterConfirmation( - Storage::PreparedList &&list, + Ui::PreparedList &&list, SendMediaType type, TextWithTags &&caption, MsgId replyTo, diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index dca932b12..ad47b2da3 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/layers/generic_box.h" #include "ui/item_text_options.h" #include "ui/toast/toast.h" +#include "ui/chat/attach/attach_prepare.h" #include "ui/special_buttons.h" #include "ui/ui_utility.h" #include "ui/toasts/common_toasts.h" @@ -311,8 +312,8 @@ bool ScheduledWidget::confirmSendingFiles( auto list = Storage::PrepareMediaList( urls, st::sendMediaPreviewSize); - if (list.error != Storage::PreparedList::Error::NonLocalUrl) { - if (list.error == Storage::PreparedList::Error::None + if (list.error != Ui::PreparedList::Error::NonLocalUrl) { + if (list.error == Ui::PreparedList::Error::None || !hasImage) { const auto emptyTextOnCancel = QString(); confirmSendingFiles( @@ -342,7 +343,7 @@ bool ScheduledWidget::confirmSendingFiles( } bool ScheduledWidget::confirmSendingFiles( - Storage::PreparedList &&list, + Ui::PreparedList &&list, CompressConfirm compressed, const QString &insertTextOnCancel) { if (showSendingFilesError(list)) { @@ -374,7 +375,7 @@ bool ScheduledWidget::confirmSendingFiles( //_field->setTextWithTags({}); box->setConfirmedCallback(crl::guard(this, [=]( - Storage::PreparedList &&list, + Ui::PreparedList &&list, SendFilesWay way, TextWithTags &&caption, Api::SendOptions options, @@ -436,7 +437,7 @@ bool ScheduledWidget::confirmSendingFiles( } void ScheduledWidget::uploadFilesAfterConfirmation( - Storage::PreparedList &&list, + Ui::PreparedList &&list, SendMediaType type, TextWithTags &&caption, MsgId replyTo, @@ -480,7 +481,7 @@ void ScheduledWidget::uploadFile( } bool ScheduledWidget::showSendingFilesError( - const Storage::PreparedList &list) const { + const Ui::PreparedList &list) const { const auto text = [&] { const auto error = Data::RestrictionError( _history->peer, @@ -488,7 +489,7 @@ bool ScheduledWidget::showSendingFilesError( if (error) { return *error; } - using Error = Storage::PreparedList::Error; + using Error = Ui::PreparedList::Error; switch (list.error) { case Error::None: return QString(); case Error::EmptyFile: diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h index 1cc916625..57af2c5c3 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h @@ -25,15 +25,12 @@ namespace Api { struct SendOptions; } // namespace Api -namespace Storage { -struct PreparedList; -} // namespace Storage - namespace Ui { class ScrollArea; class PlainShadow; class FlatButton; class HistoryDownButton; +struct PreparedList; } // namespace Ui namespace Profile { @@ -170,16 +167,16 @@ private: CompressConfirm compressed, const QString &insertTextOnCancel = QString()); bool confirmSendingFiles( - Storage::PreparedList &&list, + Ui::PreparedList &&list, CompressConfirm compressed, const QString &insertTextOnCancel = QString()); bool confirmSendingFiles( not_null data, CompressConfirm compressed, const QString &insertTextOnCancel = QString()); - bool showSendingFilesError(const Storage::PreparedList &list) const; + bool showSendingFilesError(const Ui::PreparedList &list) const; void uploadFilesAfterConfirmation( - Storage::PreparedList &&list, + Ui::PreparedList &&list, SendMediaType type, TextWithTags &&caption, MsgId replyTo, diff --git a/Telegram/SourceFiles/media/audio/media_audio.cpp b/Telegram/SourceFiles/media/audio/media_audio.cpp index 5e79687ad..e429ee774 100644 --- a/Telegram/SourceFiles/media/audio/media_audio.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio.cpp @@ -1630,8 +1630,8 @@ private: namespace Player { -FileMediaInformation::Song PrepareForSending(const QString &fname, const QByteArray &data) { - auto result = FileMediaInformation::Song(); +Ui::PreparedFileInformation::Song PrepareForSending(const QString &fname, const QByteArray &data) { + auto result = Ui::PreparedFileInformation::Song(); FFMpegAttributesReader reader(FileLocation(fname), data); const auto positionMs = crl::time(0); if (reader.open(positionMs) && reader.samplesCount() > 0) { diff --git a/Telegram/SourceFiles/media/audio/media_audio.h b/Telegram/SourceFiles/media/audio/media_audio.h index 369ee4507..962017073 100644 --- a/Telegram/SourceFiles/media/audio/media_audio.h +++ b/Telegram/SourceFiles/media/audio/media_audio.h @@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "ui/effects/animation_value.h" -#include "storage/localimageloader.h" +#include "ui/chat/attach/attach_prepare.h" #include "base/bytes.h" #include @@ -345,7 +345,9 @@ private: }; -FileMediaInformation::Song PrepareForSending(const QString &fname, const QByteArray &data); +[[nodiscard]] Ui::PreparedFileInformation::Song PrepareForSending( + const QString &fname, + const QByteArray &data); namespace internal { diff --git a/Telegram/SourceFiles/media/clip/media_clip_reader.cpp b/Telegram/SourceFiles/media/clip/media_clip_reader.cpp index 84b57b9a6..7cb071529 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_reader.cpp +++ b/Telegram/SourceFiles/media/clip/media_clip_reader.cpp @@ -883,8 +883,8 @@ Manager::~Manager() { clear(); } -FileMediaInformation::Video PrepareForSending(const QString &fname, const QByteArray &data) { - auto result = FileMediaInformation::Video(); +Ui::PreparedFileInformation::Video PrepareForSending(const QString &fname, const QByteArray &data) { + auto result = Ui::PreparedFileInformation::Video(); auto localLocation = FileLocation(fname); auto localData = QByteArray(data); diff --git a/Telegram/SourceFiles/media/clip/media_clip_reader.h b/Telegram/SourceFiles/media/clip/media_clip_reader.h index fc85b1e10..cb84dffd9 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_reader.h +++ b/Telegram/SourceFiles/media/clip/media_clip_reader.h @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "storage/localimageloader.h" +#include "ui/chat/attach/attach_prepare.h" #include "ui/image/image_prepare.h" #include @@ -294,7 +294,9 @@ private: }; -FileMediaInformation::Video PrepareForSending(const QString &fname, const QByteArray &data); +[[nodiscard]] Ui::PreparedFileInformation::Video PrepareForSending( + const QString &fname, + const QByteArray &data); void Finish(); diff --git a/Telegram/SourceFiles/storage/localimageloader.cpp b/Telegram/SourceFiles/storage/localimageloader.cpp index 09f7146ac..782e73fb3 100644 --- a/Telegram/SourceFiles/storage/localimageloader.cpp +++ b/Telegram/SourceFiles/storage/localimageloader.cpp @@ -489,7 +489,7 @@ FileLoadTask::FileLoadTask( not_null session, const QString &filepath, const QByteArray &content, - std::unique_ptr information, + std::unique_ptr information, SendMediaType type, const FileLoadTo &to, const TextWithTags &caption, @@ -528,11 +528,13 @@ FileLoadTask::FileLoadTask( , _caption(caption) { } -std::unique_ptr FileLoadTask::ReadMediaInformation( +FileLoadTask::~FileLoadTask() = default; + +std::unique_ptr FileLoadTask::ReadMediaInformation( const QString &filepath, const QByteArray &content, const QString &filemime) { - auto result = std::make_unique(); + auto result = std::make_unique(); result->filemime = filemime; if (CheckForSong(filepath, content, result)) { @@ -565,7 +567,7 @@ bool FileLoadTask::CheckMimeOrExtensions( bool FileLoadTask::CheckForSong( const QString &filepath, const QByteArray &content, - std::unique_ptr &result) { + std::unique_ptr &result) { static const auto mimes = { qstr("audio/mp3"), qstr("audio/m4a"), @@ -606,7 +608,7 @@ bool FileLoadTask::CheckForSong( bool FileLoadTask::CheckForVideo( const QString &filepath, const QByteArray &content, - std::unique_ptr &result) { + std::unique_ptr &result) { static const auto mimes = { qstr("video/mp4"), qstr("video/quicktime"), @@ -640,7 +642,7 @@ bool FileLoadTask::CheckForVideo( bool FileLoadTask::CheckForImage( const QString &filepath, const QByteArray &content, - std::unique_ptr &result) { + std::unique_ptr &result) { auto animated = false; auto image = [&] { if (filepath.endsWith(qstr(".tgs"), Qt::CaseInsensitive)) { @@ -665,13 +667,13 @@ bool FileLoadTask::CheckForImage( bool FileLoadTask::FillImageInformation( QImage &&image, bool animated, - std::unique_ptr &result) { + std::unique_ptr &result) { Expects(result != nullptr); if (image.isNull()) { return false; } - auto media = FileMediaInformation::Image(); + auto media = Ui::PreparedFileInformation::Image(); media.data = std::move(image); media.animated = animated; result->media = media; @@ -717,7 +719,7 @@ void FileLoadTask::process() { _information = readMediaInformation(Core::MimeTypeForFile(info).name()); } filemime = _information->filemime; - if (auto image = std::get_if( + if (auto image = std::get_if( &_information->media)) { fullimage = base::take(image->data); if (!Core::IsMimeSticker(filemime)) { @@ -732,7 +734,7 @@ void FileLoadTask::process() { filemime = "audio/ogg"; } else { if (_information) { - if (auto image = std::get_if( + if (auto image = std::get_if( &_information->media)) { fullimage = base::take(image->data); } @@ -757,7 +759,7 @@ void FileLoadTask::process() { } } else { if (_information) { - if (auto image = std::get_if( + if (auto image = std::get_if( &_information->media)) { fullimage = base::take(image->data); } @@ -807,13 +809,13 @@ void FileLoadTask::process() { _information = readMediaInformation(filemime); filemime = _information->filemime; } - if (auto song = std::get_if( + if (auto song = std::get_if( &_information->media)) { isSong = true; auto flags = MTPDdocumentAttributeAudio::Flag::f_title | MTPDdocumentAttributeAudio::Flag::f_performer; attributes.push_back(MTP_documentAttributeAudio(MTP_flags(flags), MTP_int(song->duration), MTP_string(song->title), MTP_string(song->performer), MTPstring())); thumbnail = PrepareFileThumbnail(std::move(song->cover)); - } else if (auto video = std::get_if( + } else if (auto video = std::get_if( &_information->media)) { isVideo = true; auto coverWidth = video->thumbnail.width(); @@ -996,6 +998,11 @@ FileLoadResult *FileLoadTask::peekResult() const { return _result.get(); } +std::unique_ptr FileLoadTask::readMediaInformation( + const QString &filemime) const { + return ReadMediaInformation(_filepath, _content, filemime); +} + void FileLoadTask::removeFromAlbum() { if (!_album) { return; diff --git a/Telegram/SourceFiles/storage/localimageloader.h b/Telegram/SourceFiles/storage/localimageloader.h index fed9e2a9d..e80058430 100644 --- a/Telegram/SourceFiles/storage/localimageloader.h +++ b/Telegram/SourceFiles/storage/localimageloader.h @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/variant.h" #include "api/api_common.h" +#include "ui/chat/attach/attach_prepare.h" constexpr auto kFileSizeLimit = 2000 * 1024 * 1024; // Load files up to 1500mb @@ -239,44 +240,22 @@ struct FileLoadResult { }; -struct FileMediaInformation { - struct Image { - QImage data; - bool animated = false; - }; - struct Song { - int duration = -1; - QString title; - QString performer; - QImage cover; - }; - struct Video { - bool isGifv = false; - bool supportsStreaming = false; - int duration = -1; - QImage thumbnail; - }; - - QString filemime; - std::variant media; -}; - class FileLoadTask final : public Task { public: - static std::unique_ptr ReadMediaInformation( + static std::unique_ptr ReadMediaInformation( const QString &filepath, const QByteArray &content, const QString &filemime); static bool FillImageInformation( QImage &&image, bool animated, - std::unique_ptr &result); + std::unique_ptr &result); FileLoadTask( not_null session, const QString &filepath, const QByteArray &content, - std::unique_ptr information, + std::unique_ptr information, SendMediaType type, const FileLoadTo &to, const TextWithTags &caption, @@ -289,6 +268,7 @@ public: const VoiceWaveform &waveform, const FileLoadTo &to, const TextWithTags &caption); + ~FileLoadTask(); uint64 fileid() const { return _id; @@ -303,22 +283,20 @@ private: static bool CheckForSong( const QString &filepath, const QByteArray &content, - std::unique_ptr &result); + std::unique_ptr &result); static bool CheckForVideo( const QString &filepath, const QByteArray &content, - std::unique_ptr &result); + std::unique_ptr &result); static bool CheckForImage( const QString &filepath, const QByteArray &content, - std::unique_ptr &result); + std::unique_ptr &result); template static bool CheckMimeOrExtensions(const QString &filepath, const QString &filemime, Mimes &mimes, Extensions &extensions); - std::unique_ptr readMediaInformation(const QString &filemime) const { - return ReadMediaInformation(_filepath, _content, filemime); - } + std::unique_ptr readMediaInformation(const QString &filemime) const; void removeFromAlbum(); uint64 _id = 0; @@ -328,7 +306,7 @@ private: const std::shared_ptr _album; QString _filepath; QByteArray _content; - std::unique_ptr _information; + std::unique_ptr _information; int32 _duration = 0; VoiceWaveform _waveform; SendMediaType _type; diff --git a/Telegram/SourceFiles/storage/storage_media_prepare.cpp b/Telegram/SourceFiles/storage/storage_media_prepare.cpp index 6099d31df..629ea4766 100644 --- a/Telegram/SourceFiles/storage/storage_media_prepare.cpp +++ b/Telegram/SourceFiles/storage/storage_media_prepare.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/mime_type.h" #include "ui/image/image_prepare.h" #include "ui/chat/attach/attach_extensions.h" +#include "ui/chat/attach/attach_prepare.h" #include "app.h" #include @@ -20,7 +21,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Storage { namespace { -constexpr auto kMaxAlbumCount = 10; +using Ui::PreparedFileInformation; +using Ui::PreparedFile; +using Ui::PreparedList; bool HasExtensionFrom(const QString &file, const QStringList &extensions) { for (const auto &extension : extensions) { @@ -33,7 +36,7 @@ bool HasExtensionFrom(const QString &file, const QStringList &extensions) { } bool ValidPhotoForAlbum( - const FileMediaInformation::Image &image, + const PreparedFileInformation::Image &image, const QString &mime) { if (image.animated || Core::IsMimeSticker(mime)) { return false; @@ -43,7 +46,7 @@ bool ValidPhotoForAlbum( return ValidateThumbDimensions(width, height); } -bool ValidVideoForAlbum(const FileMediaInformation::Video &video) { +bool ValidVideoForAlbum(const PreparedFileInformation::Video &video) { const auto width = video.thumbnail.width(); const auto height = video.thumbnail.height(); return ValidateThumbDimensions(width, height); @@ -82,8 +85,8 @@ bool PrepareAlbumMediaIsWaiting( Assert(file.information != nullptr); } - using Image = FileMediaInformation::Image; - using Video = FileMediaInformation::Video; + using Image = PreparedFileInformation::Image; + using Video = PreparedFileInformation::Video; if (const auto image = std::get_if( &file.information->media)) { if (ValidPhotoForAlbum(*image, file.mime)) { @@ -115,7 +118,7 @@ bool PrepareAlbumMediaIsWaiting( void PrepareAlbum(PreparedList &result, int previewWidth) { const auto count = int(result.files.size()); - if (count > kMaxAlbumCount) { + if (count > Ui::MaxAlbumItems()) { return; } @@ -167,15 +170,6 @@ bool ValidateThumbDimensions(int width, int height) { && (height < 20 * width); } -PreparedFile::PreparedFile(const QString &path) : path(path) { -} - -PreparedFile::PreparedFile(PreparedFile &&other) = default; - -PreparedFile &PreparedFile::operator=(PreparedFile &&other) = default; - -PreparedFile::~PreparedFile() = default; - MimeDataState ComputeMimeDataState(const QMimeData *data) { if (!data || data->hasFormat(qsl("application/x-td-forward"))) { return MimeDataState::None; @@ -284,7 +278,7 @@ PreparedList PrepareMediaFromImage( auto file = PreparedFile(QString()); file.content = content; if (file.content.isEmpty()) { - file.information = std::make_unique(); + file.information = std::make_unique(); const auto animated = false; FileLoadTask::FillImageInformation( std::move(image), @@ -296,7 +290,7 @@ PreparedList PrepareMediaFromImage( return result; } -std::optional PreparedList::PreparedFileFromFilesDialog( +std::optional PreparedFileFromFilesDialog( FileDialog::OpenResult &&result, bool isAlbum, Fn)> errorCallback, @@ -347,7 +341,7 @@ std::optional PreparedList::PreparedFileFromFilesDialog( if (!isAlbum) { return true; } - using Info = FileMediaInformation; + using Info = PreparedFileInformation; const auto media = &file.information->media; const auto valid = v::match(*media, [](const Info::Image &data) { @@ -386,68 +380,5 @@ std::optional PreparedList::PreparedFileFromFilesDialog( return std::nullopt; } -PreparedList PreparedList::Reordered( - PreparedList &&list, - std::vector order) { - Expects(list.error == PreparedList::Error::None); - Expects(list.files.size() == order.size()); - - auto result = PreparedList(list.error, list.errorData); - result.albumIsPossible = list.albumIsPossible; - result.allFilesForCompress = list.allFilesForCompress; - result.files.reserve(list.files.size()); - for (auto index : order) { - result.files.push_back(std::move(list.files[index])); - } - return result; -} - -void PreparedList::mergeToEnd(PreparedList &&other, bool cutToAlbumSize) { - if (error != Error::None) { - return; - } - if (other.error != Error::None) { - error = other.error; - errorData = other.errorData; - return; - } - allFilesForCompress = allFilesForCompress && other.allFilesForCompress; - files.reserve(std::min( - size_t(cutToAlbumSize ? kMaxAlbumCount : INT_MAX), - files.size() + other.files.size())); - for (auto &file : other.files) { - if (cutToAlbumSize && files.size() == kMaxAlbumCount) { - break; - } - files.push_back(std::move(file)); - } - if (files.size() > 1 && files.size() <= kMaxAlbumCount) { - const auto badIt = ranges::find( - files, - PreparedFile::AlbumType::None, - [](const PreparedFile &file) { return file.type; }); - albumIsPossible = (badIt == files.end()); - } else { - albumIsPossible = false; - } -} - -bool PreparedList::canAddCaption(bool isAlbum, bool compressImages) const { - const auto isSticker = [&] { - if (files.empty()) { - return false; - } - return Core::IsMimeSticker(files.front().mime) - || files.front().path.endsWith( - qstr(".tgs"), - Qt::CaseInsensitive); - }; - return isAlbum || (files.size() == 1 && !isSticker()); -} - -int MaxAlbumItems() { - return kMaxAlbumCount; -} - } // namespace Storage diff --git a/Telegram/SourceFiles/storage/storage_media_prepare.h b/Telegram/SourceFiles/storage/storage_media_prepare.h index 1cdfa3082..d07d90abe 100644 --- a/Telegram/SourceFiles/storage/storage_media_prepare.h +++ b/Telegram/SourceFiles/storage/storage_media_prepare.h @@ -10,7 +10,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/file_utilities.h" #include "lang/lang_keys.h" -struct FileMediaInformation; +namespace Ui { +struct PreparedFileInformation; +struct PreparedFile; +struct PreparedList; +} // namespace Ui namespace Storage { @@ -21,72 +25,19 @@ enum class MimeDataState { Image, }; +std::optional PreparedFileFromFilesDialog( + FileDialog::OpenResult &&result, + bool isAlbum, + Fn)> errorCallback, + int previewWidth); MimeDataState ComputeMimeDataState(const QMimeData *data); - -struct PreparedFile { - enum class AlbumType { - None, - Photo, - Video, - }; - - PreparedFile(const QString &path); - PreparedFile(PreparedFile &&other); - PreparedFile &operator=(PreparedFile &&other); - ~PreparedFile(); - - QString path; - QByteArray content; - QString mime; - std::unique_ptr information; - QImage preview; - QSize shownDimensions; - AlbumType type = AlbumType::None; - -}; - -struct PreparedList { - enum class Error { - None, - NonLocalUrl, - Directory, - EmptyFile, - TooLargeFile, - }; - - PreparedList() = default; - PreparedList(Error error, QString errorData) - : error(error) - , errorData(errorData) { - } - static PreparedList Reordered( - PreparedList &&list, - std::vector order); - static std::optional PreparedFileFromFilesDialog( - FileDialog::OpenResult &&result, - bool isAlbum, - Fn)> errorCallback, - int previewWidth); - void mergeToEnd(PreparedList &&other, bool cutToAlbumSize = false); - - bool canAddCaption(bool isAlbum, bool compressImages) const; - - Error error = Error::None; - QString errorData; - std::vector files; - bool allFilesForCompress = true; - bool albumIsPossible = false; - -}; - bool ValidateDragData(not_null data, bool isAlbum); bool ValidateThumbDimensions(int width, int height); -PreparedList PrepareMediaList(const QList &files, int previewWidth); -PreparedList PrepareMediaList(const QStringList &files, int previewWidth); -PreparedList PrepareMediaFromImage( +Ui::PreparedList PrepareMediaList(const QList &files, int previewWidth); +Ui::PreparedList PrepareMediaList(const QStringList &files, int previewWidth); +Ui::PreparedList PrepareMediaFromImage( QImage &&image, QByteArray &&content, int previewWidth); -int MaxAlbumItems(); } // namespace Storage diff --git a/Telegram/SourceFiles/support/support_helper.cpp b/Telegram/SourceFiles/support/support_helper.cpp index 82c2fd328..c660a5de7 100644 --- a/Telegram/SourceFiles/support/support_helper.cpp +++ b/Telegram/SourceFiles/support/support_helper.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/abstract_box.h" #include "ui/toast/toast.h" #include "ui/widgets/input_fields.h" +#include "ui/chat/attach/attach_prepare.h" #include "ui/text/text_entity.h" #include "ui/text/text_options.h" #include "chat_helpers/message_field.h" diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp new file mode 100644 index 000000000..94a03753d --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp @@ -0,0 +1,91 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/chat/attach/attach_prepare.h" + +#include "core/mime_type.h" + +namespace Ui { +namespace { + +constexpr auto kMaxAlbumCount = 10; + +} // namespace + +PreparedFile::PreparedFile(const QString &path) : path(path) { +} + +PreparedFile::PreparedFile(PreparedFile &&other) = default; + +PreparedFile &PreparedFile::operator=(PreparedFile && other) = default; + +PreparedFile::~PreparedFile() = default; + +PreparedList PreparedList::Reordered( + PreparedList &&list, + std::vector order) { + Expects(list.error == PreparedList::Error::None); + Expects(list.files.size() == order.size()); + + auto result = PreparedList(list.error, list.errorData); + result.albumIsPossible = list.albumIsPossible; + result.allFilesForCompress = list.allFilesForCompress; + result.files.reserve(list.files.size()); + for (auto index : order) { + result.files.push_back(std::move(list.files[index])); + } + return result; +} + +void PreparedList::mergeToEnd(PreparedList &&other, bool cutToAlbumSize) { + if (error != Error::None) { + return; + } + if (other.error != Error::None) { + error = other.error; + errorData = other.errorData; + return; + } + allFilesForCompress = allFilesForCompress && other.allFilesForCompress; + files.reserve(std::min( + size_t(cutToAlbumSize ? kMaxAlbumCount : INT_MAX), + files.size() + other.files.size())); + for (auto &file : other.files) { + if (cutToAlbumSize && files.size() == kMaxAlbumCount) { + break; + } + files.push_back(std::move(file)); + } + if (files.size() > 1 && files.size() <= kMaxAlbumCount) { + const auto badIt = ranges::find( + files, + PreparedFile::AlbumType::None, + [](const PreparedFile &file) { return file.type; }); + albumIsPossible = (badIt == files.end()); + } else { + albumIsPossible = false; + } +} + +bool PreparedList::canAddCaption(bool isAlbum, bool compressImages) const { + const auto isSticker = [&] { + if (files.empty()) { + return false; + } + return Core::IsMimeSticker(files.front().mime) + || files.front().path.endsWith( + qstr(".tgs"), + Qt::CaseInsensitive); + }; + return isAlbum || (files.size() == 1 && !isSticker()); +} + +int MaxAlbumItems() { + return kMaxAlbumCount; +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h new file mode 100644 index 000000000..2853996cd --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h @@ -0,0 +1,85 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Ui { + +struct PreparedFileInformation { + struct Image { + QImage data; + bool animated = false; + }; + struct Song { + int duration = -1; + QString title; + QString performer; + QImage cover; + }; + struct Video { + bool isGifv = false; + bool supportsStreaming = false; + int duration = -1; + QImage thumbnail; + }; + + QString filemime; + std::variant media; +}; + +struct PreparedFile { + enum class AlbumType { + None, + Photo, + Video, + }; + + PreparedFile(const QString &path); + PreparedFile(PreparedFile &&other); + PreparedFile &operator=(PreparedFile &&other); + ~PreparedFile(); + + QString path; + QByteArray content; + QString mime; + std::unique_ptr information; + QImage preview; + QSize shownDimensions; + AlbumType type = AlbumType::None; +}; + +struct PreparedList { + enum class Error { + None, + NonLocalUrl, + Directory, + EmptyFile, + TooLargeFile, + }; + + PreparedList() = default; + PreparedList(Error error, QString errorData) + : error(error) + , errorData(errorData) { + } + [[nodiscard]] static PreparedList Reordered( + PreparedList &&list, + std::vector order); + void mergeToEnd(PreparedList &&other, bool cutToAlbumSize = false); + + [[nodiscard]] bool canAddCaption(bool isAlbum, bool compressImages) const; + + Error error = Error::None; + QString errorData; + std::vector files; + bool allFilesForCompress = true; + bool albumIsPossible = false; +}; + +[[nodiscard]] int MaxAlbumItems(); + +} // namespace Ui From 05eb549a3db6e7222688c3a6f39b69ac7802d9d1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 13 Oct 2020 18:11:53 +0300 Subject: [PATCH 089/190] Move App::roundRect to Ui::FillRoundRect. --- Telegram/CMakeLists.txt | 34 - Telegram/SourceFiles/app.cpp | 244 +-- Telegram/SourceFiles/app.h | 59 - .../SourceFiles/boxes/edit_caption_box.cpp | 16 +- Telegram/SourceFiles/boxes/send_files_box.cpp | 1312 +---------------- Telegram/SourceFiles/boxes/send_files_box.h | 26 +- .../SourceFiles/boxes/sticker_set_box.cpp | 4 +- Telegram/SourceFiles/boxes/stickers_box.cpp | 6 +- .../SourceFiles/chat_helpers/bot_keyboard.cpp | 4 +- .../chat_helpers/emoji_list_widget.cpp | 8 +- .../chat_helpers/emoji_suggestions_widget.cpp | 6 +- .../chat_helpers/field_autocomplete.cpp | 4 +- .../chat_helpers/stickers_list_widget.cpp | 8 +- .../chat_helpers/tabbed_selector.cpp | 11 +- Telegram/SourceFiles/core/core_settings.cpp | 13 +- Telegram/SourceFiles/core/core_settings.h | 8 +- Telegram/SourceFiles/core/mime_type.cpp | 19 + Telegram/SourceFiles/core/mime_type.h | 14 +- Telegram/SourceFiles/data/data_document.cpp | 42 +- Telegram/SourceFiles/data/data_document.h | 6 - .../SourceFiles/history/history_drag_area.cpp | 4 +- .../history/history_inner_widget.cpp | 3 +- .../SourceFiles/history/history_widget.cpp | 7 +- .../history/view/history_view_message.cpp | 14 +- .../view/history_view_replies_section.cpp | 7 +- .../view/history_view_scheduled_section.cpp | 7 +- .../view/media/history_view_document.cpp | 6 +- .../history/view/media/history_view_game.cpp | 4 +- .../history/view/media/history_view_gif.cpp | 16 +- .../view/media/history_view_invoice.cpp | 4 +- .../view/media/history_view_location.cpp | 8 +- .../media/history_view_media_unwrapped.cpp | 4 +- .../history/view/media/history_view_photo.cpp | 8 +- .../media/history_view_theme_document.cpp | 7 +- .../view/media/history_view_web_page.cpp | 6 +- .../SourceFiles/info/info_layer_widget.cpp | 7 +- .../inline_bot_layout_internal.cpp | 6 +- .../inline_bots/inline_results_widget.cpp | 4 +- Telegram/SourceFiles/layout.cpp | 6 +- Telegram/SourceFiles/layout.h | 6 +- .../main/main_session_settings.cpp | 9 +- .../media/player/media_player_panel.cpp | 4 +- .../player/media_player_volume_controller.cpp | 4 +- .../media/view/media_view_overlay_widget.cpp | 3 +- .../view/media_view_playback_controls.cpp | 4 +- .../SourceFiles/overview/overview_layout.cpp | 15 +- .../SourceFiles/settings/settings_intro.cpp | 6 +- .../settings/settings_privacy_controllers.cpp | 3 +- .../details/storage_settings_scheme.cpp | 5 +- .../SourceFiles/ui/cached_round_corners.cpp | 255 ++++ .../SourceFiles/ui/cached_round_corners.h | 75 + .../ui/chat/attach/attach_album_preview.cpp | 484 ++++++ .../ui/chat/attach/attach_album_preview.h | 99 ++ .../ui/chat/attach/attach_album_thumbnail.cpp | 543 +++++++ .../ui/chat/attach/attach_album_thumbnail.h | 97 ++ .../ui/chat/attach/attach_common.h | 24 + .../attach/attach_single_file_preview.cpp | 173 +++ .../chat/attach/attach_single_file_preview.h | 40 + Telegram/SourceFiles/ui/filter_icon_panel.cpp | 8 +- Telegram/SourceFiles/ui/grouped_layout.cpp | 4 +- .../SourceFiles/ui/text/format_values.cpp | 16 + Telegram/SourceFiles/ui/text/format_values.h | 5 + .../SourceFiles/window/themes/window_theme.h | 3 +- .../window/themes/window_theme_warning.cpp | 4 +- .../window/window_history_hider.cpp | 4 +- Telegram/cmake/td_ui.cmake | 33 +- 66 files changed, 2051 insertions(+), 1847 deletions(-) create mode 100644 Telegram/SourceFiles/ui/cached_round_corners.cpp create mode 100644 Telegram/SourceFiles/ui/cached_round_corners.h create mode 100644 Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp create mode 100644 Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h create mode 100644 Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp create mode 100644 Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.h create mode 100644 Telegram/SourceFiles/ui/chat/attach/attach_common.h create mode 100644 Telegram/SourceFiles/ui/chat/attach/attach_single_file_preview.cpp create mode 100644 Telegram/SourceFiles/ui/chat/attach/attach_single_file_preview.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 5a0a439a9..d6fdb2a4d 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -21,9 +21,6 @@ add_subdirectory(lib_qr) add_subdirectory(lib_webrtc) add_subdirectory(codegen) -include(lib_ui/cmake/generate_styles.cmake) -include(cmake/generate_numbers.cmake) - get_filename_component(src_loc SourceFiles REALPATH) get_filename_component(res_loc Resources REALPATH) @@ -37,33 +34,6 @@ include(cmake/td_lang.cmake) include(cmake/td_scheme.cmake) include(cmake/td_ui.cmake) -set(style_files - boxes/boxes.style - calls/calls.style - chat_helpers/chat_helpers.style - export/view/export.style - info/info.style - intro/intro.style - media/view/media_view.style - media/player/media_player.style - overview/overview.style - passport/passport.style - profile/profile.style - settings/settings.style - ui/filter_icons.style -) - -set(dependent_style_files - ${submodules_loc}/lib_ui/ui/colors.palette - ${submodules_loc}/lib_ui/ui/basic.style - ${submodules_loc}/lib_ui/ui/layers/layers.style - ${submodules_loc}/lib_ui/ui/widgets/widgets.style - ${src_loc}/ui/td_common.style -) - -generate_styles(Telegram ${src_loc} "${style_files}" "${dependent_style_files}") -generate_numbers(Telegram ${res_loc}/numbers.txt) - set_target_properties(Telegram PROPERTIES AUTOMOC ON AUTORCC ON) target_link_libraries(Telegram @@ -359,8 +329,6 @@ PRIVATE core/launcher.h core/local_url_handlers.cpp core/local_url_handlers.h - core/mime_type.cpp - core/mime_type.h core/sandbox.cpp core/sandbox.h core/shortcuts.cpp @@ -1023,8 +991,6 @@ PRIVATE ui/filter_icons.h ui/filter_icon_panel.cpp ui/filter_icon_panel.h - ui/grouped_layout.cpp - ui/grouped_layout.h ui/item_text_options.cpp ui/item_text_options.h ui/resize_area.h diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 4be5be873..8c244d6fa 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_service_message.h" #include "media/audio/media_audio.h" #include "ui/image/image.h" +#include "ui/cached_round_corners.h" #include "inline_bots/inline_bot_layout_item.h" #include "core/crash_reports.h" #include "core/update_checker.h" @@ -66,14 +67,6 @@ HistoryView::Element *hoveredItem = nullptr, *pressedLinkItem = nullptr, *mousedItem = nullptr; -struct CornersPixmaps { - QPixmap p[4]; -}; -QVector corners; -using CornersMap = QMap; -CornersMap cornersMap; -QImage cornersMaskLarge[4], cornersMaskSmall[4]; - } // namespace namespace App { @@ -104,114 +97,12 @@ namespace App { return result; } - void prepareCorners(RoundCorners index, int32 radius, const QBrush &brush, const style::color *shadow = nullptr, QImage *cors = nullptr) { - Expects(::corners.size() > index); - - int32 r = radius * cIntRetinaFactor(), s = st::msgShadow * cIntRetinaFactor(); - QImage rect(r * 3, r * 3 + (shadow ? s : 0), QImage::Format_ARGB32_Premultiplied), localCors[4]; - { - Painter p(&rect); - PainterHighQualityEnabler hq(p); - - p.setCompositionMode(QPainter::CompositionMode_Source); - p.fillRect(QRect(0, 0, rect.width(), rect.height()), Qt::transparent); - p.setCompositionMode(QPainter::CompositionMode_SourceOver); - p.setPen(Qt::NoPen); - if (shadow) { - p.setBrush((*shadow)->b); - p.drawRoundedRect(0, s, r * 3, r * 3, r, r); - } - p.setBrush(brush); - p.drawRoundedRect(0, 0, r * 3, r * 3, r, r); - } - if (!cors) cors = localCors; - cors[0] = rect.copy(0, 0, r, r); - cors[1] = rect.copy(r * 2, 0, r, r); - cors[2] = rect.copy(0, r * 2, r, r + (shadow ? s : 0)); - cors[3] = rect.copy(r * 2, r * 2, r, r + (shadow ? s : 0)); - if (index != SmallMaskCorners && index != LargeMaskCorners) { - for (int i = 0; i < 4; ++i) { - ::corners[index].p[i] = pixmapFromImageInPlace(std::move(cors[i])); - ::corners[index].p[i].setDevicePixelRatio(cRetinaFactor()); - } - } - } - - void createMaskCorners() { - QImage mask[4]; - prepareCorners(SmallMaskCorners, st::buttonRadius, QColor(255, 255, 255), nullptr, mask); - for (int i = 0; i < 4; ++i) { - ::cornersMaskSmall[i] = mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied); - ::cornersMaskSmall[i].setDevicePixelRatio(cRetinaFactor()); - } - prepareCorners(LargeMaskCorners, st::historyMessageRadius, QColor(255, 255, 255), nullptr, mask); - for (int i = 0; i < 4; ++i) { - ::cornersMaskLarge[i] = mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied); - ::cornersMaskLarge[i].setDevicePixelRatio(cRetinaFactor()); - } - } - - void createPaletteCorners() { - 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, st::historyMessageRadius, st::msgSelectOverlay); - prepareCorners(DateCorners, st::dateRadius, st::msgDateImgBg); - prepareCorners(DateSelectedCorners, st::dateRadius, st::msgDateImgBgSelected); - prepareCorners(OverviewVideoCorners, st::overviewVideoStatusRadius, st::msgDateImgBg); - prepareCorners(OverviewVideoSelectedCorners, st::overviewVideoStatusRadius, st::msgDateImgBgSelected); - prepareCorners(InShadowCorners, st::historyMessageRadius, st::msgInShadow); - prepareCorners(InSelectedShadowCorners, st::historyMessageRadius, st::msgInShadowSelected); - prepareCorners(ForwardCorners, st::historyMessageRadius, 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(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, st::historyMessageRadius, st::msgInBg, &st::msgInShadow); - prepareCorners(MessageInSelectedCorners, st::historyMessageRadius, st::msgInBgSelected, &st::msgInShadowSelected); - prepareCorners(MessageOutCorners, st::historyMessageRadius, st::msgOutBg, &st::msgOutShadow); - prepareCorners(MessageOutSelectedCorners, st::historyMessageRadius, st::msgOutBgSelected, &st::msgOutShadowSelected); - - prepareCorners(SendFilesBoxAlbumGroupCorners, st::sendBoxAlbumGroupRadius, st::callFingerprintBg); - } - - void createCorners() { - ::corners.resize(RoundCornersCount); - createMaskCorners(); - createPaletteCorners(); - } - - void clearCorners() { - ::corners.clear(); - ::cornersMap.clear(); - } - void initMedia() { - createCorners(); + Ui::StartCachedCorners(); using Update = Window::Theme::BackgroundUpdate; static auto subscription = Window::Theme::Background()->add_subscription([](const Update &update) { if (update.paletteChanged()) { - createPaletteCorners(); - - if (const auto m = App::main()) { // multi good - m->updateScrollColors(); - } - HistoryView::serviceColorsUpdated(); - } else if (update.type == Update::Type::New) { - prepareCorners(StickerCorners, st::dateRadius, st::msgServiceBg); - prepareCorners(StickerSelectedCorners, st::dateRadius, st::msgServiceBgSelected); - if (const auto m = App::main()) { // multi good m->updateScrollColors(); } @@ -221,8 +112,7 @@ namespace App { } void deinitMedia() { - clearCorners(); - + Ui::FinishCachedCorners(); Data::clearGlobalStructures(); } @@ -396,132 +286,4 @@ namespace App { return QPixmap::fromImage(std::move(image), Qt::ColorOnly); } - void rectWithCorners(Painter &p, QRect rect, const style::color &bg, RoundCorners index, RectParts corners) { - auto parts = RectPart::Top - | RectPart::NoTopBottom - | RectPart::Bottom - | corners; - roundRect(p, rect, bg, index, nullptr, parts); - if ((corners & RectPart::AllCorners) != RectPart::AllCorners) { - const auto size = ::corners[index].p[0].width() / cIntRetinaFactor(); - if (!(corners & RectPart::TopLeft)) { - p.fillRect(rect.x(), rect.y(), size, size, bg); - } - if (!(corners & RectPart::TopRight)) { - p.fillRect(rect.x() + rect.width() - size, rect.y(), size, size, bg); - } - if (!(corners & RectPart::BottomLeft)) { - p.fillRect(rect.x(), rect.y() + rect.height() - size, size, size, bg); - } - if (!(corners & RectPart::BottomRight)) { - p.fillRect(rect.x() + rect.width() - size, rect.y() + rect.height() - size, size, size, bg); - } - } - } - - void complexOverlayRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners) { - if (radius == ImageRoundRadius::Ellipse) { - PainterHighQualityEnabler hq(p); - p.setPen(Qt::NoPen); - p.setBrush(p.textPalette().selectOverlay); - p.drawEllipse(rect); - } else { - auto overlayCorners = (radius == ImageRoundRadius::Small) - ? SelectedOverlaySmallCorners - : SelectedOverlayLargeCorners; - const auto bg = p.textPalette().selectOverlay; - rectWithCorners(p, rect, bg, overlayCorners, corners); - } - } - - void complexLocationRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners) { - rectWithCorners(p, rect, st::msgInBg, MessageInCorners, corners); - } - - 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 * cornerHeight) { - if ((parts & RectPart::NoTopBottom) == 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]); - } - } - - 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, 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, 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) { - case ImageRoundRadius::Small: prepareCorners(SmallMaskCorners, st::buttonRadius, bg, nullptr, images); break; - case ImageRoundRadius::Large: prepareCorners(LargeMaskCorners, st::historyMessageRadius, bg, nullptr, images); break; - default: p.fillRect(x, y, w, h, bg); return; - } - - CornersPixmaps pixmaps; - for (int j = 0; j < 4; ++j) { - pixmaps.p[j] = pixmapFromImageInPlace(std::move(images[j])); - pixmaps.p[j].setDevicePixelRatio(cRetinaFactor()); - } - i = cornersMap.insert(colorKey, pixmaps); - } - roundRect(p, x, y, w, h, bg, i.value(), nullptr, parts); - } - } diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index fc7c3c6a8..5d12e4665 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -8,54 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "data/data_types.h" -#include "ui/rect_part.h" - -enum class ImageRoundRadius; namespace HistoryView { class Element; } // namespace HistoryView -enum RoundCorners : int { - SmallMaskCorners = 0x00, // for images - LargeMaskCorners, - - BoxCorners, - MenuCorners, - BotKbOverCorners, - StickerCorners, - StickerSelectedCorners, - SelectedOverlaySmallCorners, - SelectedOverlayLargeCorners, - DateCorners, - DateSelectedCorners, - OverviewVideoCorners, - OverviewVideoSelectedCorners, - ForwardCorners, - MediaviewSaveCorners, - EmojiHoverCorners, - StickerHoverCorners, - BotKeyboardCorners, - PhotoSelectOverlayCorners, - - Doc1Corners, - Doc2Corners, - Doc3Corners, - Doc4Corners, - - InShadowCorners, // for photos without bg - InSelectedShadowCorners, - - MessageInCorners, // with shadow - MessageInSelectedCorners, - MessageOutCorners, - MessageOutSelectedCorners, - - SendFilesBoxAlbumGroupCorners, - - RoundCornersCount -}; - namespace App { QString formatPhone(QString phone); @@ -90,20 +47,4 @@ namespace App { QImage readImage(const QString &file, QByteArray *format = nullptr, bool opaque = true, bool *animated = nullptr, QByteArray *content = 0); QPixmap pixmapFromImageInPlace(QImage &&image); - void complexOverlayRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners); - void complexLocationRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners); - - 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, 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, 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); - } - }; diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index 371d966f5..e3f6e530f 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -50,11 +50,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_options.h" #include "ui/chat/attach/attach_prepare.h" #include "ui/controls/emoji_button.h" +#include "ui/cached_round_corners.h" #include "window/window_session_controller.h" #include "confirm_box.h" #include "apiwrap.h" +#include "app.h" // App::pixmapFromImageInPlace. #include "facades.h" // App::LambdaDelayed. -#include "app.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" #include "styles/style_chat_helpers.h" @@ -484,7 +485,7 @@ void EditCaptionBox::updateEditPreview() { const auto fileinfo = QFileInfo(file->path); const auto filename = fileinfo.fileName(); - _isImage = fileIsImage(filename, file->mime); + _isImage = Core::FileIsImage(filename, file->mime); _isAudio = false; _animated = false; _photo = false; @@ -516,7 +517,7 @@ void EditCaptionBox::updateEditPreview() { if (shouldAsDoc) { auto nameString = filename; if (const auto song = std::get_if(fileMedia)) { - nameString = DocumentData::ComposeNameString( + nameString = Ui::ComposeNameString( filename, song->title, song->performer); @@ -623,9 +624,10 @@ void EditCaptionBox::createEditMediaButton() { _editMedia.create(this, st::editMediaButton); updateEditMediaButton(); _editMedia->setClickedCallback( - App::LambdaDelayed(st::historyAttach.ripple.hideDuration, this, [=] { - buttonCallback(); - })); + App::LambdaDelayed( + st::historyAttach.ripple.hideDuration, + this, + buttonCallback)); } void EditCaptionBox::prepare() { @@ -894,7 +896,7 @@ void EditCaptionBox::paintEvent(QPaintEvent *e) { const auto namewidth = w - nameleft - editButton; const auto x = (width() - w) / 2, y = st::boxPhotoPadding.top(); -// App::roundRect(p, x, y, w, h, st::msgInBg, MessageInCorners, &st::msgInShadow); +// Ui::FillRoundCorner(p, x, y, w, h, st::msgInBg, Ui::MessageInCorners, &st::msgInShadow); if (_thumbw) { QRect rthumb(style::rtlrect(x + 0, y + 0, st::msgFileThumbSize, st::msgFileThumbSize, width())); diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index b927cc1f4..f6ac6012b 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -32,6 +32,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/scroll_area.h" #include "ui/wrap/fade_wrap.h" #include "ui/chat/attach/attach_prepare.h" +#include "ui/chat/attach/attach_album_preview.h" +#include "ui/chat/attach/attach_single_file_preview.h" #include "ui/text/format_values.h" #include "ui/grouped_layout.h" #include "ui/text/text_options.h" @@ -55,14 +57,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { constexpr auto kMinPreviewWidth = 20; -constexpr auto kShrinkDuration = crl::time(150); -constexpr auto kDragDuration = crl::time(200); -enum class ButtonType { - Edit, - Delete, - None, -}; +using Ui::SendFilesWay; inline bool CanAddUrls(const QList &urls) { return !urls.isEmpty() && ranges::all_of(urls, &QUrl::isLocalFile); @@ -78,58 +74,6 @@ inline bool IsSingleItem(const Ui::PreparedList &list) { return list.files.size() == 1; } -QRect PaintAlbumThumbButtons( - Painter &p, - QPoint point, - int outerWidth, - float64 shrinkProgress) { - - const auto skipInternal = st::sendBoxAlbumGroupEditInternalSkip; - const auto size = st::sendBoxAlbumGroupHeight; - const auto skipRight = st::sendBoxAlbumGroupSkipRight; - const auto skipTop = st::sendBoxAlbumGroupSkipTop; - const auto groupWidth = size * 2 + skipInternal; - - // If the width is tiny, it would be better to not display the buttons. - if (groupWidth > outerWidth) { - return QRect(); - } - - // If the width is too small, - // it would be better to display the buttons in the center. - const auto groupX = point.x() + ((groupWidth + skipRight * 2 > outerWidth) - ? (outerWidth - groupWidth) / 2 - : outerWidth - skipRight - groupWidth); - const auto groupY = point.y() + skipTop; - const auto deleteLeft = skipInternal + size; - - p.setOpacity(1.0 - shrinkProgress); - - QRect groupRect(groupX, groupY, groupWidth, size); - App::roundRect( - p, - groupRect, - st::callFingerprintBg, - SendFilesBoxAlbumGroupCorners); - - const auto editP = st::sendBoxAlbumGroupEditButtonIconPosition; - const auto deleteP = st::sendBoxAlbumGroupDeleteButtonIconPosition; - - st::sendBoxAlbumGroupEditButtonIcon.paintInCenter( - p, - QRect(groupX + editP.x(), groupY + editP.y(), size, size)); - st::sendBoxAlbumGroupDeleteButtonIcon.paintInCenter( - p, - QRect( - groupX + deleteLeft + deleteP.x(), - groupY + deleteP.y(), - size, - size)); - p.setOpacity(1); - - return groupRect; -} - void FileDialogCallback( FileDialog::OpenResult &&result, bool isAlbum, @@ -195,571 +139,6 @@ private: }; -class SingleFilePreview : public Ui::RpWidget { -public: - SingleFilePreview( - QWidget *parent, - const Ui::PreparedFile &file); - - rpl::producer desiredHeightValue() const override; - -protected: - void paintEvent(QPaintEvent *e) override; - -private: - void preparePreview(const Ui::PreparedFile &file); - void prepareThumb(const QImage &preview); - - QPixmap _fileThumb; - Ui::Text::String _nameText; - bool _fileIsAudio = false; - bool _fileIsImage = false; - QString _statusText; - int _statusWidth = 0; - -}; - -class AlbumThumb { -public: - AlbumThumb( - const Ui::PreparedFile &file, - const Ui::GroupMediaLayout &layout, - QWidget *parent, - Fn editCallback, - Fn deleteCallback); - - void moveToLayout(const Ui::GroupMediaLayout &layout); - void animateLayoutToInitial(); - void resetLayoutAnimation(); - - int photoHeight() const; - - void paintInAlbum( - Painter &p, - int left, - int top, - float64 shrinkProgress, - float64 moveProgress); - void paintPhoto(Painter &p, int left, int top, int outerWidth); - void paintFile(Painter &p, int left, int top, int outerWidth); - - bool containsPoint(QPoint position) const; - bool buttonsContainPoint(QPoint position) const; - ButtonType buttonTypeFromPoint(QPoint position) const; - int distanceTo(QPoint position) const; - bool isPointAfter(QPoint position) const; - void moveInAlbum(QPoint to); - QPoint center() const; - void suggestMove(float64 delta, Fn callback); - void finishAnimations(); - - void updateFileRow(int row); - -private: - QRect countRealGeometry() const; - QRect countCurrentGeometry(float64 progress) const; - void prepareCache(QSize size, int shrink); - void drawSimpleFrame(Painter &p, QRect to, QSize size) const; - - Ui::GroupMediaLayout _layout; - std::optional _animateFromGeometry; - const QImage _fullPreview; - const int _shrinkSize = 0; - QPixmap _albumImage; - QImage _albumCache; - QPoint _albumPosition; - RectParts _albumCorners = RectPart::None; - QPixmap _photo; - QPixmap _fileThumb; - QString _name; - QString _status; - int _nameWidth = 0; - int _statusWidth = 0; - bool _isVideo = false; - float64 _suggestedMove = 0.; - Ui::Animations::Simple _suggestedMoveAnimation; - int _lastShrinkValue = 0; - - QRect _lastRectOfButtons; - - object_ptr _editMedia = nullptr; - object_ptr _deleteMedia = nullptr; - -}; - -AlbumThumb::AlbumThumb( - const Ui::PreparedFile &file, - const Ui::GroupMediaLayout &layout, - QWidget *parent, - Fn editCallback, - Fn deleteCallback) -: _layout(layout) -, _fullPreview(file.preview) -, _shrinkSize(int(std::ceil(st::historyMessageRadius / 1.4))) -, _isVideo(file.type == Ui::PreparedFile::AlbumType::Video) { - Expects(!_fullPreview.isNull()); - - moveToLayout(layout); - - using Option = Images::Option; - const auto previewWidth = _fullPreview.width(); - const auto previewHeight = _fullPreview.height(); - const auto imageWidth = std::max( - previewWidth / cIntRetinaFactor(), - st::minPhotoSize); - const auto imageHeight = std::max( - previewHeight / cIntRetinaFactor(), - st::minPhotoSize); - _photo = App::pixmapFromImageInPlace(Images::prepare( - _fullPreview, - previewWidth, - previewHeight, - Option::RoundedLarge | Option::RoundedAll, - imageWidth, - imageHeight)); - - const auto idealSize = st::sendMediaFileThumbSize * cIntRetinaFactor(); - const auto fileThumbSize = (previewWidth > previewHeight) - ? QSize(previewWidth * idealSize / previewHeight, idealSize) - : QSize(idealSize, previewHeight * idealSize / previewWidth); - _fileThumb = App::pixmapFromImageInPlace(Images::prepare( - _fullPreview, - fileThumbSize.width(), - fileThumbSize.height(), - Option::RoundedSmall | Option::RoundedAll, - st::sendMediaFileThumbSize, - st::sendMediaFileThumbSize - )); - - const auto availableFileWidth = st::sendMediaPreviewSize - - st::sendMediaFileThumbSkip - - st::sendMediaFileThumbSize - // Right buttons. - - st::sendBoxAlbumGroupButtonFile.width * 2 - - st::sendBoxAlbumGroupEditInternalSkip * 2 - - st::sendBoxAlbumGroupSkipRight; - const auto filepath = file.path; - if (filepath.isEmpty()) { - _name = filedialogDefaultName( - qsl("image"), - qsl(".png"), - QString(), - true); - _status = qsl("%1x%2").arg( - _fullPreview.width() - ).arg( - _fullPreview.height() - ); - } else { - auto fileinfo = QFileInfo(filepath); - _name = fileinfo.fileName(); - _status = Ui::FormatSizeText(fileinfo.size()); - } - _nameWidth = st::semiboldFont->width(_name); - if (_nameWidth > availableFileWidth) { - _name = st::semiboldFont->elided( - _name, - availableFileWidth, - Qt::ElideMiddle); - _nameWidth = st::semiboldFont->width(_name); - } - _statusWidth = st::normalFont->width(_status); - - _editMedia.create(parent, st::sendBoxAlbumGroupButtonFile); - _deleteMedia.create(parent, st::sendBoxAlbumGroupButtonFile); - - const auto duration = st::historyAttach.ripple.hideDuration; - _editMedia->setClickedCallback(App::LambdaDelayed( - duration, - parent, - std::move(editCallback))); - _deleteMedia->setClickedCallback(App::LambdaDelayed( - duration, - parent, - std::move(deleteCallback))); - - _editMedia->setIconOverride(&st::editMediaButtonIconFile); - _deleteMedia->setIconOverride(&st::sendBoxAlbumGroupDeleteButtonIconFile); - - updateFileRow(-1); -} - -void AlbumThumb::updateFileRow(int row) { - if (row < 0) { - _editMedia->hide(); - _deleteMedia->hide(); - return; - } - _editMedia->show(); - _deleteMedia->show(); - - const auto fileHeight = st::sendMediaFileThumbSize - + st::sendMediaFileThumbSkip; - - const auto top = row * fileHeight + st::sendBoxAlbumGroupSkipTop; - const auto size = st::editMediaButtonSize; - - auto right = st::sendBoxAlbumGroupSkipRight + size; - _deleteMedia->moveToRight(right, top); - right += st::sendBoxAlbumGroupEditInternalSkip + size; - _editMedia->moveToRight(right, top); -} - -void AlbumThumb::resetLayoutAnimation() { - _animateFromGeometry = std::nullopt; -} - -void AlbumThumb::animateLayoutToInitial() { - _animateFromGeometry = countRealGeometry(); - _suggestedMove = 0.; - _albumPosition = QPoint(0, 0); -} - -void AlbumThumb::moveToLayout(const Ui::GroupMediaLayout &layout) { - animateLayoutToInitial(); - _layout = layout; - - const auto width = _layout.geometry.width(); - const auto height = _layout.geometry.height(); - _albumCorners = Ui::GetCornersFromSides(_layout.sides); - using Option = Images::Option; - const auto options = Option::Smooth - | Option::RoundedLarge - | ((_albumCorners & RectPart::TopLeft) - ? Option::RoundedTopLeft - : Option::None) - | ((_albumCorners & RectPart::TopRight) - ? Option::RoundedTopRight - : Option::None) - | ((_albumCorners & RectPart::BottomLeft) - ? Option::RoundedBottomLeft - : Option::None) - | ((_albumCorners & RectPart::BottomRight) - ? Option::RoundedBottomRight - : Option::None); - const auto pixSize = Ui::GetImageScaleSizeForGeometry( - { _fullPreview.width(), _fullPreview.height() }, - { width, height }); - const auto pixWidth = pixSize.width() * cIntRetinaFactor(); - const auto pixHeight = pixSize.height() * cIntRetinaFactor(); - - _albumImage = App::pixmapFromImageInPlace(Images::prepare( - _fullPreview, - pixWidth, - pixHeight, - options, - width, - height)); -} - -int AlbumThumb::photoHeight() const { - return _photo.height() / cIntRetinaFactor(); -} - -void AlbumThumb::paintInAlbum( - Painter &p, - int left, - int top, - float64 shrinkProgress, - float64 moveProgress) { - const auto shrink = anim::interpolate(0, _shrinkSize, shrinkProgress); - _lastShrinkValue = shrink; - const auto geometry = countCurrentGeometry(moveProgress); - const auto x = left + geometry.x(); - const auto y = top + geometry.y(); - if (shrink > 0 || moveProgress < 1.) { - const auto size = geometry.size(); - if (shrinkProgress < 1 && _albumCorners != RectPart::None) { - prepareCache(size, shrink); - p.drawImage(x, y, _albumCache); - } else { - const auto to = QRect({ x, y }, size).marginsRemoved( - { shrink, shrink, shrink, shrink } - ); - drawSimpleFrame(p, to, size); - } - } else { - p.drawPixmap(x, y, _albumImage); - } - if (_isVideo) { - const auto inner = QRect( - x + (geometry.width() - st::msgFileSize) / 2, - y + (geometry.height() - st::msgFileSize) / 2, - st::msgFileSize, - st::msgFileSize); - { - PainterHighQualityEnabler hq(p); - p.setPen(Qt::NoPen); - p.setBrush(st::msgDateImgBg); - p.drawEllipse(inner); - } - st::historyFileThumbPlay.paintInCenter(p, inner); - } - - _lastRectOfButtons = PaintAlbumThumbButtons( - p, - { x, y }, - geometry.width(), - shrinkProgress); -} - -void AlbumThumb::prepareCache(QSize size, int shrink) { - const auto width = std::max( - _layout.geometry.width(), - _animateFromGeometry ? _animateFromGeometry->width() : 0); - const auto height = std::max( - _layout.geometry.height(), - _animateFromGeometry ? _animateFromGeometry->height() : 0); - const auto cacheSize = QSize(width, height) * cIntRetinaFactor(); - - if (_albumCache.width() < cacheSize.width() - || _albumCache.height() < cacheSize.height()) { - _albumCache = QImage(cacheSize, QImage::Format_ARGB32_Premultiplied); - } - _albumCache.fill(Qt::transparent); - { - Painter p(&_albumCache); - const auto to = QRect(QPoint(), size).marginsRemoved( - { shrink, shrink, shrink, shrink } - ); - drawSimpleFrame(p, to, size); - } - Images::prepareRound( - _albumCache, - ImageRoundRadius::Large, - _albumCorners, - QRect(QPoint(), size * cIntRetinaFactor())); - _albumCache.setDevicePixelRatio(cRetinaFactor()); -} - -void AlbumThumb::drawSimpleFrame(Painter &p, QRect to, QSize size) const { - const auto fullWidth = _fullPreview.width(); - const auto fullHeight = _fullPreview.height(); - const auto previewSize = Ui::GetImageScaleSizeForGeometry( - { fullWidth, fullHeight }, - { size.width(), size.height() }); - const auto previewWidth = previewSize.width() * cIntRetinaFactor(); - const auto previewHeight = previewSize.height() * cIntRetinaFactor(); - const auto width = size.width() * cIntRetinaFactor(); - const auto height = size.height() * cIntRetinaFactor(); - const auto scaleWidth = to.width() / float64(width); - const auto scaleHeight = to.height() / float64(height); - const auto Round = [](float64 value) { - return int(std::round(value)); - }; - const auto [from, fillBlack] = [&] { - if (previewWidth < width && previewHeight < height) { - const auto toWidth = Round(previewWidth * scaleWidth); - const auto toHeight = Round(previewHeight * scaleHeight); - return std::make_pair( - QRect(0, 0, fullWidth, fullHeight), - QMargins( - (to.width() - toWidth) / 2, - (to.height() - toHeight) / 2, - to.width() - toWidth - (to.width() - toWidth) / 2, - to.height() - toHeight - (to.height() - toHeight) / 2)); - } else if (previewWidth * height > previewHeight * width) { - if (previewHeight >= height) { - const auto takeWidth = previewWidth * height / previewHeight; - const auto useWidth = fullWidth * width / takeWidth; - return std::make_pair( - QRect( - (fullWidth - useWidth) / 2, - 0, - useWidth, - fullHeight), - QMargins(0, 0, 0, 0)); - } else { - const auto takeWidth = previewWidth; - const auto useWidth = fullWidth * width / takeWidth; - const auto toHeight = Round(previewHeight * scaleHeight); - const auto toSkip = (to.height() - toHeight) / 2; - return std::make_pair( - QRect( - (fullWidth - useWidth) / 2, - 0, - useWidth, - fullHeight), - QMargins( - 0, - toSkip, - 0, - to.height() - toHeight - toSkip)); - } - } else { - if (previewWidth >= width) { - const auto takeHeight = previewHeight * width / previewWidth; - const auto useHeight = fullHeight * height / takeHeight; - return std::make_pair( - QRect( - 0, - (fullHeight - useHeight) / 2, - fullWidth, - useHeight), - QMargins(0, 0, 0, 0)); - } else { - const auto takeHeight = previewHeight; - const auto useHeight = fullHeight * height / takeHeight; - const auto toWidth = Round(previewWidth * scaleWidth); - const auto toSkip = (to.width() - toWidth) / 2; - return std::make_pair( - QRect( - 0, - (fullHeight - useHeight) / 2, - fullWidth, - useHeight), - QMargins( - toSkip, - 0, - to.width() - toWidth - toSkip, - 0)); - } - } - }(); - - p.drawImage(to.marginsRemoved(fillBlack), _fullPreview, from); - if (fillBlack.top() > 0) { - p.fillRect(to.x(), to.y(), to.width(), fillBlack.top(), st::imageBg); - } - if (fillBlack.bottom() > 0) { - p.fillRect( - to.x(), - to.y() + to.height() - fillBlack.bottom(), - to.width(), - fillBlack.bottom(), - st::imageBg); - } - if (fillBlack.left() > 0) { - p.fillRect( - to.x(), - to.y() + fillBlack.top(), - fillBlack.left(), - to.height() - fillBlack.top() - fillBlack.bottom(), - st::imageBg); - } - if (fillBlack.right() > 0) { - p.fillRect( - to.x() + to.width() - fillBlack.right(), - to.y() + fillBlack.top(), - fillBlack.right(), - to.height() - fillBlack.top() - fillBlack.bottom(), - st::imageBg); - } -} - -void AlbumThumb::paintPhoto(Painter &p, int left, int top, int outerWidth) { - const auto width = _photo.width() / cIntRetinaFactor(); - p.drawPixmapLeft( - left + (st::sendMediaPreviewSize - width) / 2, - top, - outerWidth, - _photo); - - _lastRectOfButtons = PaintAlbumThumbButtons( - p, - { left, top }, - st::sendMediaPreviewSize, - 0); -} - -void AlbumThumb::paintFile(Painter &p, int left, int top, int outerWidth) { - const auto textLeft = left - + st::sendMediaFileThumbSize - + st::sendMediaFileThumbSkip; - - p.drawPixmap(left, top, _fileThumb); - p.setFont(st::semiboldFont); - p.setPen(st::historyFileNameInFg); - p.drawTextLeft( - textLeft, - top + st::sendMediaFileNameTop, - outerWidth, - _name, - _nameWidth); - p.setFont(st::normalFont); - p.setPen(st::mediaInFg); - p.drawTextLeft( - textLeft, - top + st::sendMediaFileStatusTop, - outerWidth, - _status, - _statusWidth); -} - -bool AlbumThumb::containsPoint(QPoint position) const { - return _layout.geometry.contains(position); -} - -bool AlbumThumb::buttonsContainPoint(QPoint position) const { - return _lastRectOfButtons.contains(position); -} - -ButtonType AlbumThumb::buttonTypeFromPoint(QPoint position) const { - if (!buttonsContainPoint(position)) { - return ButtonType::None; - } - return (position.x() < _lastRectOfButtons.center().x()) - ? ButtonType::Edit - : ButtonType::Delete; -} - -int AlbumThumb::distanceTo(QPoint position) const { - const auto delta = (_layout.geometry.center() - position); - return QPoint::dotProduct(delta, delta); -} - -bool AlbumThumb::isPointAfter(QPoint position) const { - return position.x() > _layout.geometry.center().x(); -} - -void AlbumThumb::moveInAlbum(QPoint to) { - _albumPosition = to; -} - -QPoint AlbumThumb::center() const { - auto realGeometry = _layout.geometry; - realGeometry.moveTopLeft(realGeometry.topLeft() + _albumPosition); - return realGeometry.center(); -} - -void AlbumThumb::suggestMove(float64 delta, Fn callback) { - if (_suggestedMove != delta) { - _suggestedMoveAnimation.start( - std::move(callback), - _suggestedMove, - delta, - kShrinkDuration); - _suggestedMove = delta; - } -} - -QRect AlbumThumb::countRealGeometry() const { - const auto addLeft = int(std::round( - _suggestedMoveAnimation.value(_suggestedMove) * _lastShrinkValue)); - const auto current = _layout.geometry; - const auto realTopLeft = current.topLeft() - + _albumPosition - + QPoint(addLeft, 0); - return { realTopLeft, current.size() }; -} - -QRect AlbumThumb::countCurrentGeometry(float64 progress) const { - const auto now = countRealGeometry(); - if (_animateFromGeometry && progress < 1.) { - return { - anim::interpolate(_animateFromGeometry->x(), now.x(), progress), - anim::interpolate(_animateFromGeometry->y(), now.y(), progress), - anim::interpolate(_animateFromGeometry->width(), now.width(), progress), - anim::interpolate(_animateFromGeometry->height(), now.height(), progress) - }; - } - return now; -} - -void AlbumThumb::finishAnimations() { - _suggestedMoveAnimation.stop(); -} - SingleMediaPreview *SingleMediaPreview::Create( QWidget *parent, not_null controller, @@ -969,155 +348,6 @@ rpl::producer SingleMediaPreview::desiredHeightValue() const { return rpl::single(st::boxPhotoPadding.top() + _previewHeight); } -SingleFilePreview::SingleFilePreview( - QWidget *parent, - const Ui::PreparedFile &file) -: RpWidget(parent) { - preparePreview(file); -} - -void SingleFilePreview::prepareThumb(const QImage &preview) { - if (preview.isNull()) { - return; - } - - auto originalWidth = preview.width(); - auto originalHeight = preview.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 = App::pixmapFromImageInPlace(Images::prepare( - preview, - thumbWidth * cIntRetinaFactor(), - 0, - options, - st::msgFileThumbSize, - st::msgFileThumbSize)); -} - -void SingleFilePreview::preparePreview(const Ui::PreparedFile &file) { - auto preview = QImage(); - if (const auto image = std::get_if( - &file.information->media)) { - preview = image->data; - } else if (const auto video = std::get_if( - &file.information->media)) { - preview = video->thumbnail; - } - prepareThumb(preview); - const auto filepath = file.path; - if (filepath.isEmpty()) { - auto filename = filedialogDefaultName( - qsl("image"), - qsl(".png"), - QString(), - true); - _nameText.setText( - st::semiboldTextStyle, - filename, - Ui::NameTextOptions()); - _statusText = qsl("%1x%2").arg(preview.width()).arg(preview.height()); - _statusWidth = qMax(_nameText.maxWidth(), st::normalFont->width(_statusText)); - _fileIsImage = true; - } else { - auto fileinfo = QFileInfo(filepath); - auto filename = fileinfo.fileName(); - _fileIsImage = fileIsImage(filename, Core::MimeTypeForFile(fileinfo).name()); - - auto songTitle = QString(); - auto songPerformer = QString(); - if (file.information) { - if (const auto song = std::get_if( - &file.information->media)) { - songTitle = song->title; - songPerformer = song->performer; - _fileIsAudio = true; - } - } - - const auto nameString = DocumentData::ComposeNameString( - filename, - songTitle, - songPerformer); - _nameText.setText( - st::semiboldTextStyle, - nameString, - Ui::NameTextOptions()); - _statusText = Ui::FormatSizeText(fileinfo.size()); - _statusWidth = qMax( - _nameText.maxWidth(), - st::normalFont->width(_statusText)); - } -} - -void SingleFilePreview::paintEvent(QPaintEvent *e) { - Painter p(this); - - 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()) { - QRect inner(style::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 = _fileIsAudio - ? st::historyFileOutPlay - : _fileIsImage - ? st::historyFileOutImage - : st::historyFileOutDocument; - icon.paintInCenter(p, inner); - } else { - QRect rthumb(style::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); -} - -rpl::producer SingleFilePreview::desiredHeightValue() const { - auto h = _fileThumb.isNull() - ? (st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom()) - : (st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom()); - return rpl::single(st::boxPhotoPadding.top() + h + st::msgShadow); -} - rpl::producer FieldPlaceholder( const Ui::PreparedList &list, SendFilesWay way) { @@ -1130,536 +360,6 @@ rpl::producer FieldPlaceholder( } // namespace -class SendFilesBox::AlbumPreview : public Ui::RpWidget { -public: - AlbumPreview( - QWidget *parent, - const Ui::PreparedList &list, - SendFilesWay way); - - void setSendWay(SendFilesWay way); - std::vector takeOrder(); - - auto thumbDeleted() { - return _thumbDeleted.events(); - } - - auto thumbChanged() { - return _thumbChanged.events(); - } - -protected: - void paintEvent(QPaintEvent *e) override; - void mousePressEvent(QMouseEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; - -private: - int countLayoutHeight( - const std::vector &layout) const; - std::vector generateOrderedLayout() const; - std::vector defaultOrder() const; - void prepareThumbs(); - void updateSizeAnimated(const std::vector &layout); - void updateSize(); - void updateFileRows(); - - int thumbIndex(AlbumThumb *thumb); - AlbumThumb *thumbUnderCursor(); - void deleteThumbByIndex(int index); - void changeThumbByIndex(int index); - void thumbButtonsCallback( - not_null thumb, - ButtonType type); - - void paintAlbum(Painter &p) const; - void paintPhotos(Painter &p, QRect clip) const; - void paintFiles(Painter &p, QRect clip) const; - - void applyCursor(style::cursor cursor); - int contentLeft() const; - int contentTop() const; - AlbumThumb *findThumb(QPoint position) const; - not_null findClosestThumb(QPoint position) const; - void updateSuggestedDrag(QPoint position); - int orderIndex(not_null thumb) const; - void cancelDrag(); - void finishDrag(); - - const Ui::PreparedList &_list; - SendFilesWay _sendWay = SendFilesWay::Files; - style::cursor _cursor = style::cur_default; - std::vector _order; - std::vector> _thumbs; - int _thumbsHeight = 0; - int _photosHeight = 0; - int _filesHeight = 0; - - AlbumThumb *_draggedThumb = nullptr; - AlbumThumb *_suggestedThumb = nullptr; - AlbumThumb *_paintedAbove = nullptr; - QPoint _draggedStartPosition; - - rpl::event_stream _thumbDeleted; - rpl::event_stream _thumbChanged; - - mutable Ui::Animations::Simple _thumbsHeightAnimation; - mutable Ui::Animations::Simple _shrinkAnimation; - mutable Ui::Animations::Simple _finishDragAnimation; - -}; - -SendFilesBox::AlbumPreview::AlbumPreview( - QWidget *parent, - const Ui::PreparedList &list, - SendFilesWay way) -: RpWidget(parent) -, _list(list) -, _sendWay(way) { - setMouseTracking(true); - prepareThumbs(); - updateSize(); - updateFileRows(); -} - -void SendFilesBox::AlbumPreview::setSendWay(SendFilesWay way) { - if (_sendWay != way) { - cancelDrag(); - _sendWay = way; - } - updateSize(); - updateFileRows(); - update(); -} - -void SendFilesBox::AlbumPreview::updateFileRows() { - Expects(_order.size() == _thumbs.size()); - const auto isFile = (_sendWay == SendFilesWay::Files); - for (auto i = 0; i < _order.size(); i++) { - _thumbs[i]->updateFileRow(isFile ? _order[i] : -1); - } -} - -std::vector SendFilesBox::AlbumPreview::takeOrder() { - auto reordered = std::vector>(); - reordered.reserve(_thumbs.size()); - for (auto index : _order) { - reordered.push_back(std::move(_thumbs[index])); - } - _thumbs = std::move(reordered); - return std::exchange(_order, defaultOrder()); -} - -auto SendFilesBox::AlbumPreview::generateOrderedLayout() const --> std::vector { - auto sizes = ranges::view::all( - _order - ) | ranges::view::transform([&](int index) { - return _list.files[index].shownDimensions; - }) | ranges::to_vector; - - auto layout = Ui::LayoutMediaGroup( - sizes, - st::sendMediaPreviewSize, - st::historyGroupWidthMin / 2, - st::historyGroupSkip / 2); - Assert(layout.size() == _order.size()); - return layout; -} - -std::vector SendFilesBox::AlbumPreview::defaultOrder() const { - const auto count = int(_list.files.size()); - return ranges::view::ints(0, count) | ranges::to_vector; -} - -void SendFilesBox::AlbumPreview::prepareThumbs() { - _order = defaultOrder(); - - const auto count = int(_list.files.size()); - const auto layout = generateOrderedLayout(); - _thumbs.reserve(count); - for (auto i = 0; i != count; ++i) { - _thumbs.push_back(std::make_unique( - _list.files[i], - layout[i], - this, - [=] { changeThumbByIndex(thumbIndex(thumbUnderCursor())); }, - [=] { deleteThumbByIndex(thumbIndex(thumbUnderCursor())); })); - } - _thumbsHeight = countLayoutHeight(layout); - _photosHeight = ranges::accumulate(ranges::view::all( - _thumbs - ) | ranges::view::transform([](const auto &thumb) { - return thumb->photoHeight(); - }), 0) + (count - 1) * st::sendMediaPreviewPhotoSkip; - - _filesHeight = count * st::sendMediaFileThumbSize - + (count - 1) * st::sendMediaFileThumbSkip; -} - -int SendFilesBox::AlbumPreview::contentLeft() const { - return (st::boxWideWidth - st::sendMediaPreviewSize) / 2; -} - -int SendFilesBox::AlbumPreview::contentTop() const { - return 0; -} - -AlbumThumb *SendFilesBox::AlbumPreview::findThumb(QPoint position) const { - position -= QPoint(contentLeft(), contentTop()); - - auto top = 0; - const auto isPhotosWay = (_sendWay == SendFilesWay::Photos); - const auto skip = isPhotosWay - ? st::sendMediaPreviewPhotoSkip - : st::sendMediaFileThumbSkip; - auto find = [&](const auto &thumb) { - if (_sendWay == SendFilesWay::Album) { - return thumb->containsPoint(position); - } else if (isPhotosWay || _sendWay == SendFilesWay::Files) { - const auto bottom = top + (isPhotosWay - ? thumb->photoHeight() - : st::sendMediaFileThumbSize); - const auto isUnderTop = (position.y() > top); - top = bottom + skip; - return isUnderTop && (position.y() < bottom); - } - return false; - }; - - const auto i = ranges::find_if(_thumbs, std::move(find)); - return (i == _thumbs.end()) ? nullptr : i->get(); -} - -not_null SendFilesBox::AlbumPreview::findClosestThumb( - QPoint position) const { - Expects(_draggedThumb != nullptr); - - if (const auto exact = findThumb(position)) { - return exact; - } - auto result = _draggedThumb; - auto distance = _draggedThumb->distanceTo(position); - for (const auto &thumb : _thumbs) { - const auto check = thumb->distanceTo(position); - if (check < distance) { - distance = check; - result = thumb.get(); - } - } - return result; -} - -int SendFilesBox::AlbumPreview::orderIndex( - not_null thumb) const { - const auto i = ranges::find_if(_order, [&](int index) { - return (_thumbs[index].get() == thumb); - }); - Assert(i != _order.end()); - return int(i - _order.begin()); -} - -void SendFilesBox::AlbumPreview::cancelDrag() { - _thumbsHeightAnimation.stop(); - _finishDragAnimation.stop(); - _shrinkAnimation.stop(); - if (_draggedThumb) { - _draggedThumb->moveInAlbum({ 0, 0 }); - _draggedThumb = nullptr; - } - if (_suggestedThumb) { - const auto suggestedIndex = orderIndex(_suggestedThumb); - if (suggestedIndex > 0) { - _thumbs[_order[suggestedIndex - 1]]->suggestMove(0., [] {}); - } - if (suggestedIndex < int(_order.size() - 1)) { - _thumbs[_order[suggestedIndex + 1]]->suggestMove(0., [] {}); - } - _suggestedThumb->suggestMove(0., [] {}); - _suggestedThumb->finishAnimations(); - _suggestedThumb = nullptr; - } - _paintedAbove = nullptr; - update(); -} - -void SendFilesBox::AlbumPreview::finishDrag() { - Expects(_draggedThumb != nullptr); - Expects(_suggestedThumb != nullptr); - - if (_suggestedThumb != _draggedThumb) { - const auto currentIndex = orderIndex(_draggedThumb); - const auto newIndex = orderIndex(_suggestedThumb); - const auto delta = (currentIndex < newIndex) ? 1 : -1; - const auto realIndex = _order[currentIndex]; - for (auto i = currentIndex; i != newIndex; i += delta) { - _order[i] = _order[i + delta]; - } - _order[newIndex] = realIndex; - const auto layout = generateOrderedLayout(); - for (auto i = 0, count = int(_order.size()); i != count; ++i) { - _thumbs[_order[i]]->moveToLayout(layout[i]); - } - _finishDragAnimation.start([=] { update(); }, 0., 1., kDragDuration); - - updateSizeAnimated(layout); - } else { - for (const auto &thumb : _thumbs) { - thumb->resetLayoutAnimation(); - } - _draggedThumb->animateLayoutToInitial(); - _finishDragAnimation.start([=] { update(); }, 0., 1., kDragDuration); - } -} - -int SendFilesBox::AlbumPreview::countLayoutHeight( - const std::vector &layout) const { - const auto accumulator = [](int current, const auto &item) { - return std::max(current, item.geometry.y() + item.geometry.height()); - }; - return ranges::accumulate(layout, 0, accumulator); -} - -void SendFilesBox::AlbumPreview::updateSizeAnimated( - const std::vector &layout) { - const auto newHeight = countLayoutHeight(layout); - if (newHeight != _thumbsHeight) { - _thumbsHeightAnimation.start( - [=] { updateSize(); }, - _thumbsHeight, - newHeight, - kDragDuration); - _thumbsHeight = newHeight; - } -} - -void SendFilesBox::AlbumPreview::updateSize() { - const auto newHeight = [&] { - switch (_sendWay) { - case SendFilesWay::Album: - return int(std::round(_thumbsHeightAnimation.value( - _thumbsHeight))); - case SendFilesWay::Photos: return _photosHeight; - case SendFilesWay::Files: return _filesHeight; - } - Unexpected("Send way in SendFilesBox::AlbumPreview::updateSize"); - }(); - if (height() != newHeight) { - resize(st::boxWideWidth, newHeight); - } -} - -void SendFilesBox::AlbumPreview::paintEvent(QPaintEvent *e) { - Painter p(this); - - switch (_sendWay) { - case SendFilesWay::Album: paintAlbum(p); break; - case SendFilesWay::Photos: paintPhotos(p, e->rect()); break; - case SendFilesWay::Files: paintFiles(p, e->rect()); break; - } -} - -void SendFilesBox::AlbumPreview::paintAlbum(Painter &p) const { - const auto shrink = _shrinkAnimation.value(_draggedThumb ? 1. : 0.); - const auto moveProgress = _finishDragAnimation.value(1.); - const auto left = contentLeft(); - const auto top = contentTop(); - for (const auto &thumb : _thumbs) { - if (thumb.get() != _paintedAbove) { - thumb->paintInAlbum(p, left, top, shrink, moveProgress); - } - } - if (_paintedAbove) { - _paintedAbove->paintInAlbum(p, left, top, shrink, moveProgress); - } -} - -void SendFilesBox::AlbumPreview::paintPhotos(Painter &p, QRect clip) const { - const auto left = (st::boxWideWidth - st::sendMediaPreviewSize) / 2; - auto top = 0; - const auto outerWidth = width(); - for (const auto &thumb : _thumbs) { - const auto bottom = top + thumb->photoHeight(); - const auto guard = gsl::finally([&] { - top = bottom + st::sendMediaPreviewPhotoSkip; - }); - if (top >= clip.y() + clip.height()) { - break; - } else if (bottom <= clip.y()) { - continue; - } - thumb->paintPhoto(p, left, top, outerWidth); - } -} - -void SendFilesBox::AlbumPreview::paintFiles(Painter &p, QRect clip) const { - const auto fileHeight = st::sendMediaFileThumbSize - + st::sendMediaFileThumbSkip; - const auto bottom = clip.y() + clip.height(); - const auto from = floorclamp(clip.y(), fileHeight, 0, _thumbs.size()); - const auto till = ceilclamp(bottom, fileHeight, 0, _thumbs.size()); - const auto left = (st::boxWideWidth - st::sendMediaPreviewSize) / 2; - const auto outerWidth = width(); - - auto top = from * fileHeight; - for (auto i = from; i != till; ++i) { - _thumbs[i]->paintFile(p, left, top, outerWidth); - top += fileHeight; - } -} - -int SendFilesBox::AlbumPreview::thumbIndex(AlbumThumb *thumb) { - if (!thumb) { - return -1; - } - const auto thumbIt = ranges::find_if(_thumbs, [&](auto &t) { - return t.get() == thumb; - }); - Expects(thumbIt != _thumbs.end()); - return std::distance(_thumbs.begin(), thumbIt); -} - -AlbumThumb *SendFilesBox::AlbumPreview::thumbUnderCursor() { - return findThumb(mapFromGlobal(QCursor::pos())); -} - -void SendFilesBox::AlbumPreview::deleteThumbByIndex(int index) { - if (index < 0) { - return; - } - const auto orderIt = ranges::find(_order, index); - Expects(orderIt != _order.end()); - - _order.erase(orderIt); - ranges::for_each(_order, [=](auto &i) { - if (i > index) { - i--; - } - }); - _thumbDeleted.fire(std::move(index)); -} - -void SendFilesBox::AlbumPreview::changeThumbByIndex(int index) { - if (index < 0) { - return; - } - _thumbChanged.fire(std::move(index)); -} - -void SendFilesBox::AlbumPreview::thumbButtonsCallback( - not_null thumb, - ButtonType type) { - const auto index = thumbIndex(thumb); - - switch (type) { - case ButtonType::None: return; - case ButtonType::Edit: changeThumbByIndex(index); break; - case ButtonType::Delete: deleteThumbByIndex(index); break; - } -} - -void SendFilesBox::AlbumPreview::mousePressEvent(QMouseEvent *e) { - if (_finishDragAnimation.animating()) { - return; - } - const auto position = e->pos(); - cancelDrag(); - if (const auto thumb = findThumb(position)) { - if (thumb->buttonsContainPoint(e->pos())) { - thumbButtonsCallback(thumb, thumb->buttonTypeFromPoint(e->pos())); - return; - } - _paintedAbove = _suggestedThumb = _draggedThumb = thumb; - _draggedStartPosition = position; - _shrinkAnimation.start([=] { update(); }, 0., 1., kShrinkDuration); - } -} - -void SendFilesBox::AlbumPreview::mouseMoveEvent(QMouseEvent *e) { - if (_sendWay == SendFilesWay::Files) { - applyCursor(style::cur_default); - return; - } - const auto isAlbum = (_sendWay == SendFilesWay::Album); - if (isAlbum && _draggedThumb) { - const auto position = e->pos(); - _draggedThumb->moveInAlbum(position - _draggedStartPosition); - updateSuggestedDrag(_draggedThumb->center()); - update(); - } else { - const auto thumb = findThumb(e->pos()); - const auto regularCursor = isAlbum - ? style::cur_sizeall - : style::cur_default; - const auto cursor = thumb - ? (thumb->buttonsContainPoint(e->pos()) - ? style::cur_pointer - : regularCursor) - : style::cur_default; - applyCursor(cursor); - } -} - -void SendFilesBox::AlbumPreview::applyCursor(style::cursor cursor) { - if (_cursor != cursor) { - _cursor = cursor; - setCursor(_cursor); - } -} - -void SendFilesBox::AlbumPreview::updateSuggestedDrag(QPoint position) { - auto closest = findClosestThumb(position); - auto closestIndex = orderIndex(closest); - - const auto draggedIndex = orderIndex(_draggedThumb); - const auto closestIsBeforePoint = closest->isPointAfter(position); - if (closestIndex < draggedIndex && closestIsBeforePoint) { - closest = _thumbs[_order[++closestIndex]].get(); - } else if (closestIndex > draggedIndex && !closestIsBeforePoint) { - closest = _thumbs[_order[--closestIndex]].get(); - } - - if (_suggestedThumb == closest) { - return; - } - - const auto last = int(_order.size()) - 1; - if (_suggestedThumb) { - const auto suggestedIndex = orderIndex(_suggestedThumb); - if (suggestedIndex < draggedIndex && suggestedIndex > 0) { - const auto previous = _thumbs[_order[suggestedIndex - 1]].get(); - previous->suggestMove(0., [=] { update(); }); - } else if (suggestedIndex > draggedIndex && suggestedIndex < last) { - const auto next = _thumbs[_order[suggestedIndex + 1]].get(); - next->suggestMove(0., [=] { update(); }); - } - _suggestedThumb->suggestMove(0., [=] { update(); }); - } - _suggestedThumb = closest; - const auto suggestedIndex = closestIndex; - if (_suggestedThumb != _draggedThumb) { - const auto delta = (suggestedIndex < draggedIndex) ? 1. : -1.; - if (delta > 0. && suggestedIndex > 0) { - const auto previous = _thumbs[_order[suggestedIndex - 1]].get(); - previous->suggestMove(-delta, [=] { update(); }); - } else if (delta < 0. && suggestedIndex < last) { - const auto next = _thumbs[_order[suggestedIndex + 1]].get(); - next->suggestMove(-delta, [=] { update(); }); - } - _suggestedThumb->suggestMove(delta, [=] { update(); }); - } -} - -void SendFilesBox::AlbumPreview::mouseReleaseEvent(QMouseEvent *e) { - if (_draggedThumb) { - finishDrag(); - _shrinkAnimation.start([=] { update(); }, 1., 0., kShrinkDuration); - _draggedThumb = nullptr; - _suggestedThumb = nullptr; - update(); - } -} - SendFilesBox::SendFilesBox( QWidget*, not_null controller, @@ -1718,7 +418,7 @@ void SendFilesBox::prepareSingleFilePreview() { _preview = media; initPreview(media->desiredHeightValue()); } else { - const auto preview = Ui::CreateChild(this, file); + const auto preview = Ui::CreateChild(this, file); _compressConfirm = CompressConfirm::None; _preview = preview; initPreview(preview->desiredHeightValue()); @@ -1731,7 +431,7 @@ void SendFilesBox::prepareAlbumPreview() { const auto wrap = Ui::CreateChild( this, st::boxScroll); - _albumPreview = wrap->setOwnedWidget(object_ptr( + _albumPreview = wrap->setOwnedWidget(object_ptr( this, _list, _sendWay->value())); @@ -1807,7 +507,7 @@ void SendFilesBox::addThumbButtonHandlers(not_null wrap) { void SendFilesBox::setupShadows( not_null wrap, - not_null content) { + not_null content) { using namespace rpl::mappers; const auto topShadow = Ui::CreateChild(this); diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h index d8bfc5f99..4b99028d7 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.h +++ b/Telegram/SourceFiles/boxes/send_files_box.h @@ -35,6 +35,8 @@ class RoundButton; class InputField; struct GroupMediaLayout; class EmojiButton; +class AlbumPreview; +enum class SendFilesWay; } // namespace Ui namespace Window { @@ -45,12 +47,6 @@ namespace SendMenu { enum class Type; } // namespace SendMenu -enum class SendFilesWay { - Album, - Photos, - Files, -}; - class SendFilesBox : public Ui::BoxContent { public: enum class SendLimit { @@ -70,7 +66,7 @@ public: void setConfirmedCallback( Fn callback) { @@ -91,8 +87,6 @@ protected: void resizeEvent(QResizeEvent *e) override; private: - class AlbumPreview; - void initSendWay(); void initPreview(rpl::producer desiredPreviewHeight); @@ -101,7 +95,7 @@ private: void setupCaption(); void setupShadows( not_null wrap, - not_null content); + not_null content); void setupEmojiPanel(); void updateEmojiPanelGeometry(); @@ -149,7 +143,7 @@ private: Fn _confirmedCallback; @@ -161,16 +155,16 @@ private: base::unique_qptr _emojiPanel; base::unique_qptr _emojiFilter; - object_ptr> _sendAlbum = { nullptr }; - object_ptr> _sendPhotos = { nullptr }; - object_ptr> _sendFiles = { nullptr }; - std::shared_ptr> _sendWay; + object_ptr> _sendAlbum = { nullptr }; + object_ptr> _sendPhotos = { nullptr }; + object_ptr> _sendFiles = { nullptr }; + std::shared_ptr> _sendWay; rpl::variable _footerHeight = 0; rpl::event_stream<> _albumChanged; QWidget *_preview = nullptr; - AlbumPreview *_albumPreview = nullptr; + Ui::AlbumPreview *_albumPreview = nullptr; int _albumVideosCount = 0; int _albumPhotosCount = 0; diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index d7488d2c5..62f75f797 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/emoji_config.h" #include "ui/toast/toast.h" #include "ui/widgets/popup_menu.h" +#include "ui/cached_round_corners.h" #include "lottie/lottie_multi_player.h" #include "lottie/lottie_animation.h" #include "chat_helpers/stickers_lottie.h" @@ -35,7 +36,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "mainwidget.h" #include "mainwindow.h" -#include "app.h" #include "styles/style_layers.h" #include "styles/style_chat_helpers.h" #include "styles/style_info.h" @@ -661,7 +661,7 @@ void StickerSetBox::Inner::paintSticker( p.setOpacity(over); auto tl = position; if (rtl()) tl.setX(width() - tl.x() - st::stickersSize.width()); - App::roundRect(p, QRect(tl, st::stickersSize), st::emojiPanHover, StickerHoverCorners); + Ui::FillRoundRect(p, QRect(tl, st::stickersSize), st::emojiPanHover, Ui::StickerHoverCorners); p.setOpacity(1); } diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp index c70fa623c..5ed95a24b 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.cpp +++ b/Telegram/SourceFiles/boxes/stickers_box.cpp @@ -33,9 +33,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/discrete_sliders.h" #include "ui/widgets/input_fields.h" #include "ui/image/image.h" +#include "ui/cached_round_corners.h" #include "window/window_session_controller.h" #include "main/main_session.h" -#include "app.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" #include "styles/style_chat_helpers.h" @@ -1129,7 +1129,7 @@ void StickersBox::Inner::paintRow(Painter &p, not_null row, int index) { Ui::Shadow::paint(p, rect, width(), st::boxRoundShadow); p.setOpacity(1); - App::roundRect(p, rect, st::boxBg, BoxCorners); + Ui::FillRoundRect(p, rect, st::boxBg, Ui::BoxCorners); p.setOpacity(1. - current); paintFakeButton(p, row, index); @@ -1318,7 +1318,7 @@ void StickersBox::Inner::paintFakeButton(Painter &p, not_null row, int ind 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); + Ui::FillRoundRect(p, myrtlrect(rect), textBg, ImageRoundRadius::Small); if (row->ripple) { row->ripple->paint(p, rect.x(), rect.y(), width()); if (row->ripple->empty()) { diff --git a/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp b/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp index 375039449..17ee74a6b 100644 --- a/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp +++ b/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp @@ -12,8 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_session.h" #include "main/main_session.h" +#include "ui/cached_round_corners.h" #include "facades.h" -#include "app.h" #include "styles/style_widgets.h" #include "styles/style_chat.h" @@ -76,7 +76,7 @@ void Style::paintButtonBg( Painter &p, const QRect &rect, float64 howMuchOver) const { - App::roundRect(p, rect, st::botKbBg, BotKeyboardCorners); + Ui::FillRoundRect(p, rect, st::botKbBg, Ui::BotKeyboardCorners); } void Style::paintButtonIcon( diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index ec66b0cfc..ea5213b66 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/shadow.h" #include "ui/emoji_config.h" #include "ui/ui_utility.h" +#include "ui/cached_round_corners.h" #include "lang/lang_keys.h" #include "emoji_suggestions_data.h" #include "emoji_suggestions_helper.h" @@ -20,7 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "window/window_session_controller.h" #include "facades.h" -#include "app.h" #include "styles/style_chat_helpers.h" namespace ChatHelpers { @@ -211,7 +211,7 @@ void EmojiColorPicker::paintEvent(QPaintEvent *e) { return; } Ui::Shadow::paint(p, inner, width(), st::defaultRoundShadow); - App::roundRect(p, inner, st::boxBg, BoxCorners); + Ui::FillRoundRect(p, inner, st::boxBg, Ui::BoxCorners); auto x = st::emojiPanMargins.left() + 2 * st::emojiColorsPadding + _singleSize.width(); if (rtl()) x = width() - x - st::emojiColorsSep; @@ -372,7 +372,7 @@ void EmojiColorPicker::drawVariant(Painter &p, int variant) { if (variant == _selected) { QPoint tl(w); if (rtl()) tl.setX(width() - tl.x() - _singleSize.width()); - App::roundRect(p, QRect(tl, _singleSize), st::emojiPanHover, StickerHoverCorners); + Ui::FillRoundRect(p, QRect(tl, _singleSize), st::emojiPanHover, Ui::StickerHoverCorners); } const auto esize = Ui::Emoji::GetSizeLarge(); Ui::Emoji::Draw( @@ -558,7 +558,7 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) { if (selected) { auto tl = w; if (rtl()) tl.setX(width() - tl.x() - _singleSize.width()); - App::roundRect(p, QRect(tl, _singleSize), st::emojiPanHover, StickerHoverCorners); + Ui::FillRoundRect(p, QRect(tl, _singleSize), st::emojiPanHover, Ui::StickerHoverCorners); } Ui::Emoji::Draw( p, diff --git a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp index d77ebf94e..c3693d3b3 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp @@ -17,11 +17,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/input_fields.h" #include "ui/emoji_config.h" #include "ui/ui_utility.h" +#include "ui/cached_round_corners.h" #include "platform/platform_specific.h" #include "core/application.h" #include "base/event_filter.h" #include "main/main_session.h" -#include "app.h" #include "styles/style_chat_helpers.h" #include @@ -218,11 +218,11 @@ void SuggestionsWidget::paintEvent(QPaintEvent *e) { ? _pressed : _selectedAnimation.value(_selected); if (selected > -1.) { - App::roundRect( + Ui::FillRoundRect( p, QRect(selected * _oneWidth, 0, _oneWidth, _oneWidth), st::emojiPanHover, - StickerHoverCorners); + Ui::StickerHoverCorners); } for (auto i = from; i != till; ++i) { diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp index de207c1b5..41bce617a 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp @@ -29,10 +29,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/scroll_area.h" #include "ui/image/image.h" #include "ui/ui_utility.h" +#include "ui/cached_round_corners.h" #include "base/unixtime.h" #include "window/window_session_controller.h" #include "facades.h" -#include "app.h" #include "styles/style_chat.h" #include "styles/style_widgets.h" #include "styles/style_chat_helpers.h" @@ -686,7 +686,7 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) { if (_sel == index) { QPoint tl(pos); if (rtl()) tl.setX(width() - tl.x() - st::stickerPanSize.width()); - App::roundRect(p, QRect(tl, st::stickerPanSize), st::emojiPanHover, StickerHoverCorners); + Ui::FillRoundRect(p, QRect(tl, st::stickerPanSize), st::emojiPanHover, Ui::StickerHoverCorners); } media->checkStickerSmall(); diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index 28b35ff14..bece2cd01 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/animations.h" #include "ui/effects/ripple_animation.h" #include "ui/image/image.h" +#include "ui/cached_round_corners.h" #include "lottie/lottie_multi_player.h" #include "lottie/lottie_single_player.h" #include "lottie/lottie_animation.h" @@ -38,7 +39,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session_settings.h" #include "apiwrap.h" #include "api/api_toggling_media.h" // Api::ToggleFavedSticker -#include "app.h" #include "styles/style_chat_helpers.h" #include "styles/style_window.h" @@ -1537,7 +1537,7 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) { auto selected = selectedButton ? (selectedButton->section == info.section) : false; auto &textBg = selected ? st::stickersTrendingAdd.textBgOver : st::stickersTrendingAdd.textBg; - App::roundRect(p, myrtlrect(add), textBg, ImageRoundRadius::Small); + Ui::FillRoundRect(p, myrtlrect(add), textBg, ImageRoundRadius::Small); if (set.ripple) { set.ripple->paint(p, add.x(), add.y(), width()); if (set.ripple->empty()) { @@ -1767,7 +1767,7 @@ void StickersListWidget::paintMegagroupEmptySet(Painter &p, int y, bool buttonSe : st::stickerGroupCategoryAdd.textBg; auto button = _megagroupSetButtonRect.translated(0, y); - App::roundRect(p, myrtlrect(button), textBg, ImageRoundRadius::Small); + Ui::FillRoundRect(p, myrtlrect(button), textBg, ImageRoundRadius::Small); if (_megagroupSetButtonRipple) { _megagroupSetButtonRipple->paint(p, button.x(), button.y(), width()); if (_megagroupSetButtonRipple->empty()) { @@ -1847,7 +1847,7 @@ void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section, if (selected) { auto tl = pos; if (rtl()) tl.setX(width() - tl.x() - _singleSize.width()); - App::roundRect(p, QRect(tl, _singleSize), st::emojiPanHover, StickerHoverCorners); + Ui::FillRoundRect(p, QRect(tl, _singleSize), st::emojiPanHover, Ui::StickerHoverCorners); } media->checkStickerSmall(); diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index b60304b46..1121c7cf4 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/popup_menu.h" #include "ui/widgets/scroll_area.h" #include "ui/image/image_prepare.h" +#include "ui/cached_round_corners.h" #include "window/window_session_controller.h" #include "main/main_session.h" #include "main/main_session_settings.h" @@ -521,10 +522,10 @@ void TabbedSelector::paintSlideFrame(Painter &p) { if (_roundRadius > 0) { if (full()) { auto topPart = QRect(0, 0, width(), _tabsSlider->height() + _roundRadius); - App::roundRect(p, topPart, st::emojiPanBg, ImageRoundRadius::Small, RectPart::FullTop | RectPart::NoTopBottom); + Ui::FillRoundRect(p, topPart, st::emojiPanBg, ImageRoundRadius::Small, RectPart::FullTop | RectPart::NoTopBottom); } else { auto topPart = QRect(0, 0, width(), 3 * _roundRadius); - App::roundRect(p, topPart, st::emojiPanBg, ImageRoundRadius::Small, RectPart::FullTop); + Ui::FillRoundRect(p, topPart, st::emojiPanBg, ImageRoundRadius::Small, RectPart::FullTop); } } else if (full()) { p.fillRect(0, 0, width(), _tabsSlider->height(), st::emojiPanBg); @@ -538,15 +539,15 @@ void TabbedSelector::paintContent(Painter &p) { if (_roundRadius > 0) { if (full()) { auto topPart = QRect(0, 0, width(), _tabsSlider->height() + _roundRadius); - App::roundRect(p, topPart, st::emojiPanBg, ImageRoundRadius::Small, RectPart::FullTop | RectPart::NoTopBottom); + Ui::FillRoundRect(p, topPart, st::emojiPanBg, ImageRoundRadius::Small, RectPart::FullTop | RectPart::NoTopBottom); } else { auto topPart = QRect(0, 0, width(), 3 * _roundRadius); - App::roundRect(p, topPart, st::emojiPanBg, ImageRoundRadius::Small, RectPart::FullTop); + Ui::FillRoundRect(p, topPart, st::emojiPanBg, ImageRoundRadius::Small, RectPart::FullTop); } auto bottomPart = QRect(0, _footerTop - _roundRadius, width(), st::emojiFooterHeight + _roundRadius); auto bottomParts = RectPart::NoTopBottom | RectPart::FullBottom; - App::roundRect(p, bottomPart, bottomBg, ImageRoundRadius::Small, bottomParts); + Ui::FillRoundRect(p, bottomPart, bottomBg, ImageRoundRadius::Small, bottomParts); } else { if (full()) { p.fillRect(0, 0, width(), _tabsSlider->height(), st::emojiPanBg); diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index a7b0b124d..41d138ebf 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/send_files_box.h" #include "ui/widgets/input_fields.h" +#include "ui/chat/attach/attach_common.h" #include "storage/serialize_common.h" #include "window/themes/window_theme.h" #include "window/section_widget.h" @@ -18,7 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Core { Settings::Settings() -: _sendFilesWay(SendFilesWay::Album) +: _sendFilesWay(Ui::SendFilesWay::Album) , _sendSubmitWay(Ui::InputSubmitSettings::Enter) , _floatPlayerColumn(Window::Column::Second) , _floatPlayerCorner(RectPart::TopRight) @@ -309,11 +310,11 @@ void Settings::addFromSerialized(const QByteArray &serialized) { _callAudioDuckingEnabled = (callAudioDuckingEnabled == 1); _lastSeenWarningSeen = (lastSeenWarningSeen == 1); _soundOverrides = std::move(soundOverrides); - auto uncheckedSendFilesWay = static_cast(sendFilesWay); + auto uncheckedSendFilesWay = static_cast(sendFilesWay); switch (uncheckedSendFilesWay) { - case SendFilesWay::Album: - case SendFilesWay::Photos: - case SendFilesWay::Files: _sendFilesWay = uncheckedSendFilesWay; break; + case Ui::SendFilesWay::Album: + case Ui::SendFilesWay::Photos: + case Ui::SendFilesWay::Files: _sendFilesWay = uncheckedSendFilesWay; break; } auto uncheckedSendSubmitWay = static_cast(sendSubmitWay); switch (uncheckedSendSubmitWay) { @@ -469,7 +470,7 @@ void Settings::resetOnLastLogout() { //_themesAccentColors = Window::Theme::AccentColors(); _lastSeenWarningSeen = false; - _sendFilesWay = SendFilesWay::Album; + _sendFilesWay = Ui::SendFilesWay::Album; //_sendSubmitWay = Ui::InputSubmitSettings::Enter; _soundOverrides = {}; diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index d9c0bfaa8..bab3bd920 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -10,11 +10,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/themes/window_themes_embedded.h" #include "window/window_controls_layout.h" -enum class SendFilesWay; enum class RectPart; namespace Ui { enum class InputSubmitSettings; +enum class SendFilesWay; } // namespace Ui namespace Window { @@ -229,10 +229,10 @@ public: [[nodiscard]] bool lastSeenWarningSeen() const { return _lastSeenWarningSeen; } - void setSendFilesWay(SendFilesWay way) { + void setSendFilesWay(Ui::SendFilesWay way) { _sendFilesWay = way; } - [[nodiscard]] SendFilesWay sendFilesWay() const { + [[nodiscard]] Ui::SendFilesWay sendFilesWay() const { return _sendFilesWay; } void setSendSubmitWay(Ui::InputSubmitSettings value) { @@ -515,7 +515,7 @@ private: bool _callAudioDuckingEnabled = true; Window::Theme::AccentColors _themesAccentColors; bool _lastSeenWarningSeen = false; - SendFilesWay _sendFilesWay; + Ui::SendFilesWay _sendFilesWay; Ui::InputSubmitSettings _sendSubmitWay; base::flat_map _soundOverrides; bool _exeLaunchWarning = true; diff --git a/Telegram/SourceFiles/core/mime_type.cpp b/Telegram/SourceFiles/core/mime_type.cpp index c1596e550..71e7ae065 100644 --- a/Telegram/SourceFiles/core/mime_type.cpp +++ b/Telegram/SourceFiles/core/mime_type.cpp @@ -118,4 +118,23 @@ bool IsMimeAcceptedForAlbum(const QString &mime) { || (mime == u"video/quicktime"_q); } +bool FileIsImage(const QString &name, const QString &mime) { + QString lowermime = mime.toLower(), namelower = name.toLower(); + if (lowermime.startsWith(qstr("image/"))) { + return true; + } else if (namelower.endsWith(qstr(".bmp")) + || namelower.endsWith(qstr(".jpg")) + || namelower.endsWith(qstr(".jpeg")) + || namelower.endsWith(qstr(".gif")) + || namelower.endsWith(qstr(".webp")) + || namelower.endsWith(qstr(".tga")) + || namelower.endsWith(qstr(".tiff")) + || namelower.endsWith(qstr(".tif")) + || namelower.endsWith(qstr(".psd")) + || namelower.endsWith(qstr(".png"))) { + return true; + } + return false; +} + } // namespace Core diff --git a/Telegram/SourceFiles/core/mime_type.h b/Telegram/SourceFiles/core/mime_type.h index b817305f1..73b2dcc9b 100644 --- a/Telegram/SourceFiles/core/mime_type.h +++ b/Telegram/SourceFiles/core/mime_type.h @@ -36,12 +36,14 @@ private: }; -MimeType MimeTypeForName(const QString &mime); -MimeType MimeTypeForFile(const QFileInfo &file); -MimeType MimeTypeForData(const QByteArray &data); +[[nodiscard]] MimeType MimeTypeForName(const QString &mime); +[[nodiscard]] MimeType MimeTypeForFile(const QFileInfo &file); +[[nodiscard]] MimeType MimeTypeForData(const QByteArray &data); -bool IsMimeStickerAnimated(const QString &mime); -bool IsMimeSticker(const QString &mime); -bool IsMimeAcceptedForAlbum(const QString &mime); +[[nodiscard]] bool IsMimeStickerAnimated(const QString &mime); +[[nodiscard]] bool IsMimeSticker(const QString &mime); +[[nodiscard]] bool IsMimeAcceptedForAlbum(const QString &mime); + +[[nodiscard]] bool FileIsImage(const QString &name, const QString &mime); } // namespace Core diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index 38a511a8e..9d9dbf3e1 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/confirm_box.h" #include "ui/image/image.h" #include "ui/text/text_utilities.h" +#include "ui/text/format_values.h" #include "base/base_file_utilities.h" #include "mainwindow.h" #include "core/application.h" @@ -125,25 +126,6 @@ void LaunchWithWarning( } // namespace -bool fileIsImage(const QString &name, const QString &mime) { - QString lowermime = mime.toLower(), namelower = name.toLower(); - if (lowermime.startsWith(qstr("image/"))) { - return true; - } else if (namelower.endsWith(qstr(".bmp")) - || namelower.endsWith(qstr(".jpg")) - || namelower.endsWith(qstr(".jpeg")) - || namelower.endsWith(qstr(".gif")) - || namelower.endsWith(qstr(".webp")) - || namelower.endsWith(qstr(".tga")) - || namelower.endsWith(qstr(".tiff")) - || namelower.endsWith(qstr(".tif")) - || namelower.endsWith(qstr(".psd")) - || namelower.endsWith(qstr(".png"))) { - return true; - } - return false; -} - QString FileNameUnsafe( not_null session, const QString &title, @@ -1456,12 +1438,12 @@ uint8 DocumentData::cacheTag() const { QString DocumentData::composeNameString() const { if (auto songData = song()) { - return ComposeNameString( + return Ui::ComposeNameString( _filename, songData->title, songData->performer); } - return ComposeNameString(_filename, QString(), QString()); + return Ui::ComposeNameString(_filename, QString(), QString()); } LocationType DocumentData::locationType() const { @@ -1577,7 +1559,7 @@ void DocumentData::setMaybeSupportsStreaming(bool supports) { void DocumentData::recountIsImage() { const auto isImage = !isAnimation() && !isVideoFile() - && fileIsImage(filename(), mimeString()); + && Core::FileIsImage(filename(), mimeString()); if (isImage) { _flags |= Flag::ImageType; } else { @@ -1635,22 +1617,6 @@ void DocumentData::collectLocalData(not_null local) { } } -QString DocumentData::ComposeNameString( - const QString &filename, - const QString &songTitle, - const QString &songPerformer) { - if (songTitle.isEmpty() && songPerformer.isEmpty()) { - return filename.isEmpty() ? qsl("Unknown File") : filename; - } - - if (songPerformer.isEmpty()) { - return songTitle; - } - - auto trackTitle = (songTitle.isEmpty() ? qsl("Unknown Track") : songTitle); - return songPerformer + QString::fromUtf8(" \xe2\x80\x93 ") + trackTitle; -} - namespace Data { QString FileExtension(const QString &filepath) { diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index e8b5b905f..a31dd6370 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -78,8 +78,6 @@ struct VoiceData : public DocumentAdditionalData { char wavemax = 0; }; -bool fileIsImage(const QString &name, const QString &mime); - namespace Serialize { class Document; } // namespace Serialize; @@ -228,10 +226,6 @@ public: [[nodiscard]] Storage::Cache::Key cacheKey() const; [[nodiscard]] uint8 cacheTag() const; - [[nodiscard]] static QString ComposeNameString( - const QString &filename, - const QString &songTitle, - const QString &songPerformer); [[nodiscard]] QString composeNameString() const; [[nodiscard]] bool canBeStreamed() const; diff --git a/Telegram/SourceFiles/history/history_drag_area.cpp b/Telegram/SourceFiles/history/history_drag_area.cpp index 5614ad254..92a3041a9 100644 --- a/Telegram/SourceFiles/history/history_drag_area.cpp +++ b/Telegram/SourceFiles/history/history_drag_area.cpp @@ -18,10 +18,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "ui/widgets/shadow.h" #include "ui/ui_utility.h" +#include "ui/cached_round_corners.h" #include "mainwindow.h" #include "apiwrap.h" #include "mainwidget.h" -#include "app.h" #include "storage/storage_media_prepare.h" #include "styles/style_chat_helpers.h" #include "styles/style_layers.h" @@ -315,7 +315,7 @@ void DragArea::paintEvent(QPaintEvent *e) { } Ui::Shadow::paint(p, inner, width(), st::boxRoundShadow); - App::roundRect(p, inner, st::boxBg, BoxCorners); + Ui::FillRoundRect(p, inner, st::boxBg, Ui::BoxCorners); p.setPen(anim::pen( st::dragColor, diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index e44c610b8..6f9db66fd 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/toast/toast.h" #include "ui/text/text_options.h" #include "ui/ui_utility.h" +#include "ui/cached_round_corners.h" #include "ui/inactive_press.h" #include "window/window_session_controller.h" #include "window/window_peer_menu.h" @@ -591,7 +592,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { if (!_firstLoading && _botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) { if (clip.y() < _botAbout->rect.y() + _botAbout->rect.height() && clip.y() + clip.height() > _botAbout->rect.y()) { p.setTextPalette(st::inTextPalette); - App::roundRect(p, _botAbout->rect, st::msgInBg, MessageInCorners, &st::msgInShadow); + Ui::FillRoundRect(p, _botAbout->rect, st::msgInBg, Ui::MessageInCorners, &st::msgInShadow); auto top = _botAbout->rect.top() + st::msgPadding.top(); if (!_history->peer->isRepliesChat()) { diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 9abb0d823..8bd28b1dc 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" // Ui::Text::ToUpper #include "ui/text/format_values.h" #include "ui/chat/message_bar.h" +#include "ui/chat/attach/attach_common.h" #include "ui/image/image.h" #include "ui/special_buttons.h" #include "ui/controls/emoji_button.h" @@ -4193,17 +4194,17 @@ bool HistoryWidget::confirmSendingFiles( _field->setTextWithTags({}); box->setConfirmedCallback(crl::guard(this, [=]( Ui::PreparedList &&list, - SendFilesWay way, + Ui::SendFilesWay way, TextWithTags &&caption, Api::SendOptions options, bool ctrlShiftEnter) { if (showSendingFilesError(list)) { return; } - const auto type = (way == SendFilesWay::Files) + const auto type = (way == Ui::SendFilesWay::Files) ? SendMediaType::File : SendMediaType::Photo; - const auto album = (way == SendFilesWay::Album) + const auto album = (way == Ui::SendFilesWay::Album) ? std::make_shared() : nullptr; uploadFilesAfterConfirmation( diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 56d59d5af..1eaf6786a 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/toast/toast.h" #include "ui/text/text_utilities.h" #include "ui/text/text_entity.h" +#include "ui/cached_round_corners.h" #include "data/data_session.h" #include "data/data_user.h" #include "data/data_channel.h" @@ -30,7 +31,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "layout.h" #include "facades.h" -#include "app.h" #include "styles/style_widgets.h" #include "styles/style_chat.h" #include "styles/style_dialogs.h" @@ -81,11 +81,11 @@ void KeyboardStyle::paintButtonBg( Painter &p, const QRect &rect, float64 howMuchOver) const { - App::roundRect(p, rect, st::msgServiceBg, StickerCorners); + Ui::FillRoundRect(p, rect, st::msgServiceBg, Ui::StickerCorners); if (howMuchOver > 0) { auto o = p.opacity(); p.setOpacity(o * howMuchOver); - App::roundRect(p, rect, st::msgBotKbOverBgAdd, BotKbOverCorners); + Ui::FillRoundRect(p, rect, st::msgBotKbOverBgAdd, Ui::BotKbOverCorners); p.setOpacity(o); } } @@ -141,7 +141,7 @@ QString FastReplyText() { void PaintBubble(Painter &p, QRect rect, int outerWidth, bool selected, bool outbg, RectPart tailSide, RectParts skip) { auto &bg = selected ? (outbg ? st::msgOutBgSelected : st::msgInBgSelected) : (outbg ? st::msgOutBg : st::msgInBg); auto sh = &(selected ? (outbg ? st::msgOutShadowSelected : st::msgInShadowSelected) : (outbg ? st::msgOutShadow : st::msgInShadow)); - auto cors = selected ? (outbg ? MessageOutSelectedCorners : MessageInSelectedCorners) : (outbg ? MessageOutCorners : MessageInCorners); + auto cors = selected ? (outbg ? Ui::MessageOutSelectedCorners : Ui::MessageInSelectedCorners) : (outbg ? Ui::MessageOutCorners : Ui::MessageInCorners); auto parts = RectPart::None | RectPart::NoTopBottom; if (skip & RectPart::Top) { if (skip & RectPart::Bottom) { @@ -174,7 +174,7 @@ void PaintBubble(Painter &p, QRect rect, int outerWidth, bool selected, bool out } else if (!(skip & RectPart::Bottom)) { parts |= RectPart::FullBottom; } - App::roundRect(p, rect, bg, cors, sh, parts); + Ui::FillRoundRect(p, rect, bg, cors, sh, parts); } void PaintBubble(Painter &p, QRect rect, int outerWidth, bool selected, const std::vector &selection, bool outbg, RectPart tailSide) { @@ -1799,10 +1799,10 @@ void Message::drawInfo( auto dateY = infoBottom - st::msgDateFont->height; if (type == InfoDisplayType::Image) { auto dateW = infoW + 2 * st::msgDateImgPadding.x(), dateH = st::msgDateFont->height + 2 * st::msgDateImgPadding.y(); - App::roundRect(p, dateX - st::msgDateImgPadding.x(), dateY - st::msgDateImgPadding.y(), dateW, dateH, selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); + Ui::FillRoundRect(p, dateX - st::msgDateImgPadding.x(), dateY - st::msgDateImgPadding.y(), dateW, dateH, selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? Ui::DateSelectedCorners : Ui::DateCorners); } else if (type == InfoDisplayType::Background) { auto dateW = infoW + 2 * st::msgDateImgPadding.x(), dateH = st::msgDateFont->height + 2 * st::msgDateImgPadding.y(); - App::roundRect(p, dateX - st::msgDateImgPadding.x(), dateY - st::msgDateImgPadding.y(), dateW, dateH, selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? StickerSelectedCorners : StickerCorners); + Ui::FillRoundRect(p, dateX - st::msgDateImgPadding.x(), dateY - st::msgDateImgPadding.y(), dateW, dateH, selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? Ui::StickerSelectedCorners : Ui::StickerCorners); } dateX += timeLeft(); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 613acdc9d..37d256a6b 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/format_values.h" #include "ui/text/text_utilities.h" #include "ui/chat/attach/attach_prepare.h" +#include "ui/chat/attach/attach_common.h" #include "ui/special_buttons.h" #include "ui/ui_utility.h" #include "ui/toasts/common_toasts.h" @@ -635,17 +636,17 @@ bool RepliesWidget::confirmSendingFiles( const auto replyTo = replyToId(); box->setConfirmedCallback(crl::guard(this, [=]( Ui::PreparedList &&list, - SendFilesWay way, + Ui::SendFilesWay way, TextWithTags &&caption, Api::SendOptions options, bool ctrlShiftEnter) { if (showSendingFilesError(list)) { return; } - const auto type = (way == SendFilesWay::Files) + const auto type = (way == Ui::SendFilesWay::Files) ? SendMediaType::File : SendMediaType::Photo; - const auto album = (way == SendFilesWay::Album) + const auto album = (way == Ui::SendFilesWay::Album) ? std::make_shared() : nullptr; uploadFilesAfterConfirmation( diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index ad47b2da3..94015efa2 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/item_text_options.h" #include "ui/toast/toast.h" #include "ui/chat/attach/attach_prepare.h" +#include "ui/chat/attach/attach_common.h" #include "ui/special_buttons.h" #include "ui/ui_utility.h" #include "ui/toasts/common_toasts.h" @@ -376,17 +377,17 @@ bool ScheduledWidget::confirmSendingFiles( box->setConfirmedCallback(crl::guard(this, [=]( Ui::PreparedList &&list, - SendFilesWay way, + Ui::SendFilesWay way, TextWithTags &&caption, Api::SendOptions options, bool ctrlShiftEnter) { if (showSendingFilesError(list)) { return; } - const auto type = (way == SendFilesWay::Files) + const auto type = (way == Ui::SendFilesWay::Files) ? SendMediaType::File : SendMediaType::Photo; - const auto album = (way == SendFilesWay::Album) + const auto album = (way == Ui::SendFilesWay::Album) ? std::make_shared() : nullptr; uploadFilesAfterConfirmation( diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index 6eb21fba3..0aa633d6e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -18,13 +18,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_media_common.h" #include "ui/image/image.h" #include "ui/text/format_values.h" +#include "ui/cached_round_corners.h" #include "layout.h" // FullSelection #include "data/data_session.h" #include "data/data_document.h" #include "data/data_document_media.h" #include "data/data_media_types.h" #include "data/data_file_origin.h" -#include "app.h" #include "styles/style_chat.h" namespace HistoryView { @@ -306,8 +306,8 @@ void Document::draw( } p.drawPixmap(rthumb.topLeft(), thumb); if (selected) { - auto overlayCorners = inWebPage ? SelectedOverlaySmallCorners : SelectedOverlayLargeCorners; - App::roundRect(p, rthumb, p.textPalette().selectOverlay, overlayCorners); + auto overlayCorners = inWebPage ? Ui::SelectedOverlaySmallCorners : Ui::SelectedOverlayLargeCorners; + Ui::FillRoundRect(p, rthumb, p.textPalette().selectOverlay, overlayCorners); } if (radial || (!loaded && !_data->loading())) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_game.cpp b/Telegram/SourceFiles/history/view/media/history_view_game.cpp index 133c008f3..af6f8a282 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_game.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_game.cpp @@ -15,11 +15,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_cursor_state.h" #include "history/view/media/history_view_media_common.h" #include "ui/item_text_options.h" +#include "ui/cached_round_corners.h" #include "core/ui_integration.h" #include "data/data_session.h" #include "data/data_game.h" #include "data/data_media_types.h" -#include "app.h" #include "styles/style_chat.h" namespace HistoryView { @@ -261,7 +261,7 @@ void Game::draw(Painter &p, const QRect &r, TextSelection selection, crl::time m auto gameX = pixwidth - st::msgDateImgDelta - gameW; auto gameY = pixheight - st::msgDateImgDelta - gameH; - App::roundRect(p, style::rtlrect(gameX, gameY, gameW, gameH, pixwidth), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); + Ui::FillRoundRect(p, style::rtlrect(gameX, gameY, gameW, gameH, pixwidth), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? Ui::DateSelectedCorners : Ui::DateCorners); p.setFont(st::msgDateFont); p.setPen(st::msgDateImgFg); diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 3fab69882..43916d11d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -29,12 +29,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/image/image.h" #include "ui/text/format_values.h" #include "ui/grouped_layout.h" +#include "ui/cached_round_corners.h" #include "data/data_session.h" #include "data/data_streaming.h" #include "data/data_document.h" #include "data/data_file_origin.h" #include "data/data_document_media.h" -#include "app.h" #include "layout.h" // FullSelection #include "styles/style_chat.h" @@ -343,7 +343,7 @@ void Gif::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms } } } else if (!isRound) { - App::roundShadow(p, 0, 0, paintw, height(), selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); + Ui::FillRoundShadow(p, 0, 0, paintw, height(), selected ? st::msgInShadowSelected : st::msgInShadow, selected ? Ui::InSelectedShadowCorners : Ui::InShadowCorners); } auto usex = 0, usew = paintw; @@ -451,14 +451,14 @@ void Gif::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms | RectPart::NoTopBottom | (roundTop ? RectPart::Top : RectPart::None) | (roundBottom ? RectPart::Bottom : RectPart::None); - App::roundRect(p, rthumb.marginsAdded({ 0, roundTop ? 0 : margin, 0, roundBottom ? 0 : margin }), st::imageBg, roundRadius, parts); + Ui::FillRoundRect(p, rthumb.marginsAdded({ 0, roundTop ? 0 : margin, 0, roundBottom ? 0 : margin }), st::imageBg, roundRadius, parts); } } } } if (selected) { - App::complexOverlayRect(p, rthumb, roundRadius, roundCorners); + Ui::FillComplexOverlayRect(p, rthumb, roundRadius, roundCorners); } if (radial @@ -553,7 +553,7 @@ void Gif::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms if (mediaUnread) { statusW += st::mediaUnreadSkip + st::mediaUnreadSize; } - App::roundRect(p, style::rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? StickerSelectedCorners : StickerCorners); + Ui::FillRoundRect(p, style::rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? Ui::StickerSelectedCorners : Ui::StickerCorners); p.setFont(st::normalFont); p.setPen(st::msgServiceFg); p.drawTextLeft(statusX, statusY, width(), _statusText, statusW - 2 * st::msgDateImgPadding.x()); @@ -584,7 +584,7 @@ void Gif::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms int recty = painty; if (rtl()) rectx = width() - rectx - rectw; - App::roundRect(p, rectx, recty, rectw, recth, selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? StickerSelectedCorners : StickerCorners); + Ui::FillRoundRect(p, rectx, recty, rectw, recth, selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? Ui::StickerSelectedCorners : Ui::StickerCorners); p.setPen(st::msgServiceFg); rectx += st::msgReplyPadding.left(); rectw = innerw; @@ -677,7 +677,7 @@ void Gif::drawCornerStatus(Painter &p, bool selected, QPoint position) const { const auto statusY = position.y() + st::msgDateImgDelta + padding.y(); const auto around = style::rtlrect(statusX - padding.x(), statusY - padding.y(), statusW, statusH, width()); const auto statusTextTop = statusY + (cornerDownload ? (((statusH - 2 * st::normalFont->height) / 3) - padding.y()) : 0); - App::roundRect(p, around, selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); + Ui::FillRoundRect(p, around, selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? Ui::DateSelectedCorners : Ui::DateCorners); p.setFont(st::normalFont); p.setPen(st::msgDateImgFg); p.drawTextLeft(statusX + addLeft, statusTextTop, width(), text, statusW - 2 * padding.x()); @@ -985,7 +985,7 @@ void Gif::drawGrouped( } if (selected) { - App::complexOverlayRect(p, geometry, roundRadius, corners); + Ui::FillComplexOverlayRect(p, geometry, roundRadius, corners); } if (radial diff --git a/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp b/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp index 5e3549655..dbd40997b 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp @@ -15,8 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_media_common.h" #include "ui/item_text_options.h" #include "ui/text/format_values.h" +#include "ui/cached_round_corners.h" #include "data/data_media_types.h" -#include "app.h" #include "styles/style_chat.h" namespace HistoryView { @@ -260,7 +260,7 @@ void Invoice::draw(Painter &p, const QRect &r, TextSelection selection, crl::tim auto statusX = st::msgDateImgDelta; auto statusY = st::msgDateImgDelta; - App::roundRect(p, style::rtlrect(statusX, statusY, statusW, statusH, pixwidth), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); + Ui::FillRoundRect(p, style::rtlrect(statusX, statusY, statusW, statusH, pixwidth), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? Ui::DateSelectedCorners : Ui::DateCorners); p.setFont(st::msgDateFont); p.setPen(st::msgDateImgFg); diff --git a/Telegram/SourceFiles/history/view/media/history_view_location.cpp b/Telegram/SourceFiles/history/view/media/history_view_location.cpp index 70a1bfbd7..f539dddcb 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_location.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_location.cpp @@ -16,10 +16,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_cursor_state.h" #include "ui/image/image.h" #include "ui/text/text_options.h" +#include "ui/cached_round_corners.h" #include "data/data_session.h" #include "data/data_file_origin.h" #include "data/data_cloud_file.h" -#include "app.h" #include "styles/style_chat.h" namespace HistoryView { @@ -179,7 +179,7 @@ void Location::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti } painth -= painty; } else { - App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); + Ui::FillRoundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? Ui::InSelectedShadowCorners : Ui::InShadowCorners); } auto roundRadius = ImageRoundRadius::Large; @@ -191,7 +191,7 @@ void Location::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti const auto &pix = thumbnail->pixSingle(paintw, painth, paintw, painth, roundRadius, roundCorners); p.drawPixmap(rthumb.topLeft(), pix); } else { - App::complexLocationRect(p, rthumb, roundRadius, roundCorners); + Ui::FillComplexLocationRect(p, rthumb, roundRadius, roundCorners); } const auto paintMarker = [&](const style::icon &icon) { icon.paint( @@ -203,7 +203,7 @@ void Location::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti paintMarker(st::historyMapPoint); paintMarker(st::historyMapPointInner); if (selected) { - App::complexOverlayRect(p, rthumb, roundRadius, roundCorners); + Ui::FillComplexOverlayRect(p, rthumb, roundRadius, roundCorners); } if (_parent->media() == this) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp index 9345ad268..8b2b63b97 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -15,8 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lottie/lottie_single_player.h" #include "core/application.h" #include "core/core_settings.h" +#include "ui/cached_round_corners.h" #include "layout.h" -#include "app.h" #include "styles/style_chat.h" namespace HistoryView { @@ -206,7 +206,7 @@ void UnwrappedMedia::drawSurrounding( int recty = 0; if (rtl()) rectx = width() - rectx - rectw; - App::roundRect(p, rectx, recty, rectw, recth, selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? StickerSelectedCorners : StickerCorners); + Ui::FillRoundRect(p, rectx, recty, rectw, recth, selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? Ui::StickerSelectedCorners : Ui::StickerCorners); p.setPen(st::msgServiceFg); rectx += st::msgReplyPadding.left(); rectw -= st::msgReplyPadding.left() + st::msgReplyPadding.right(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 193455a9e..87f7e7bf9 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session_settings.h" #include "ui/image/image.h" #include "ui/grouped_layout.h" +#include "ui/cached_round_corners.h" #include "data/data_session.h" #include "data/data_streaming.h" #include "data/data_photo.h" @@ -28,7 +29,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_file_origin.h" #include "data/data_auto_download.h" #include "core/application.h" -#include "app.h" #include "styles/style_chat.h" namespace HistoryView { @@ -249,7 +249,7 @@ void Photo::draw(Painter &p, const QRect &r, TextSelection selection, crl::time rthumb = style::rtlrect(paintx, painty, paintw, painth, width()); } } else { - App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); + Ui::FillRoundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? Ui::InSelectedShadowCorners : Ui::InShadowCorners); } auto inWebPage = (_parent->media() != this); auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large; @@ -272,7 +272,7 @@ void Photo::draw(Painter &p, const QRect &r, TextSelection selection, crl::time }(); p.drawPixmap(rthumb.topLeft(), pix); if (selected) { - App::complexOverlayRect(p, rthumb, roundRadius, roundCorners); + Ui::FillComplexOverlayRect(p, rthumb, roundRadius, roundCorners); } } if (radial || (!loaded && !_data->loading())) { @@ -505,7 +505,7 @@ void Photo::drawGrouped( p.drawPixmap(geometry.topLeft(), *cache); if (selected) { const auto roundRadius = ImageRoundRadius::Large; - App::complexOverlayRect(p, geometry, roundRadius, corners); + Ui::FillComplexOverlayRect(p, geometry, roundRadius, corners); } const auto displayState = radial diff --git a/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp index 437ab56c0..f6055a7fb 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp @@ -17,9 +17,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_file_origin.h" #include "base/qthelp_url.h" #include "ui/text/format_values.h" +#include "ui/cached_round_corners.h" #include "window/themes/window_theme.h" #include "layout.h" // FullSelection -#include "app.h" +#include "app.h" // App::pixmapFromImageInPlace. #include "styles/style_chat.h" namespace HistoryView { @@ -139,14 +140,14 @@ void ThemeDocument::draw(Painter &p, const QRect &r, TextSelection selection, cr validateThumbnail(); p.drawPixmap(rthumb.topLeft(), _thumbnail); if (selected) { - App::complexOverlayRect(p, rthumb, roundRadius, roundCorners); + Ui::FillComplexOverlayRect(p, rthumb, roundRadius, roundCorners); } auto statusX = paintx + st::msgDateImgDelta + st::msgDateImgPadding.x(); auto statusY = painty + st::msgDateImgDelta + st::msgDateImgPadding.y(); auto statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x(); auto statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y(); - App::roundRect(p, style::rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); + Ui::FillRoundRect(p, style::rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? Ui::DateSelectedCorners : Ui::DateCorners); p.setFont(st::normalFont); p.setPen(st::msgDateImgFg); p.drawTextLeft(statusX, statusY, width(), _statusText, statusW - 2 * st::msgDateImgPadding.x()); diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp index 4e4e395f3..52a6a76ed 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/image/image.h" #include "ui/text/text_options.h" #include "ui/text/format_values.h" +#include "ui/cached_round_corners.h" #include "layout.h" // FullSelection #include "data/data_session.h" #include "data/data_media_types.h" @@ -26,7 +27,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_photo.h" #include "data/data_photo_media.h" #include "data/data_file_origin.h" -#include "app.h" #include "styles/style_chat.h" namespace HistoryView { @@ -501,7 +501,7 @@ void WebPage::draw(Painter &p, const QRect &r, TextSelection selection, crl::tim } p.drawPixmapLeft(padding.left() + paintw - pw, tshift, width(), pix); if (selected) { - App::roundRect(p, style::rtlrect(padding.left() + paintw - pw, tshift, pw, _pixh, width()), p.textPalette().selectOverlay, SelectedOverlaySmallCorners); + Ui::FillRoundRect(p, style::rtlrect(padding.left() + paintw - pw, tshift, pw, _pixh, width()), p.textPalette().selectOverlay, Ui::SelectedOverlaySmallCorners); } paintw -= pw + st::webPagePhotoDelta; } @@ -569,7 +569,7 @@ void WebPage::draw(Painter &p, const QRect &r, TextSelection selection, crl::tim auto dateW = pixwidth - dateX - st::msgDateImgDelta; auto dateH = pixheight - dateY - st::msgDateImgDelta; - App::roundRect(p, dateX, dateY, dateW, dateH, selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); + Ui::FillRoundRect(p, dateX, dateY, dateW, dateH, selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? Ui::DateSelectedCorners : Ui::DateCorners); p.setFont(st::msgDateFont); p.setPen(st::msgDateImgFg); diff --git a/Telegram/SourceFiles/info/info_layer_widget.cpp b/Telegram/SourceFiles/info/info_layer_widget.cpp index cd062dbc7..1627b1383 100644 --- a/Telegram/SourceFiles/info/info_layer_widget.cpp +++ b/Telegram/SourceFiles/info/info_layer_widget.cpp @@ -13,13 +13,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/rp_widget.h" #include "ui/focus_persister.h" #include "ui/widgets/buttons.h" +#include "ui/cached_round_corners.h" #include "window/section_widget.h" #include "window/window_session_controller.h" #include "window/main_window.h" #include "main/main_session.h" #include "boxes/abstract_box.h" #include "core/application.h" -#include "app.h" +#include "app.h" // App::quitting. #include "styles/style_info.h" #include "styles/style_window.h" #include "styles/style_layers.h" @@ -269,11 +270,11 @@ void LayerWidget::paintEvent(QPaintEvent *e) { } } if (parts) { - App::roundRect( + Ui::FillRoundRect( p, rect(), st::boxBg, - BoxCorners, + Ui::BoxCorners, nullptr, parts); } diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index dde873ad1..4810cbc4a 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -25,9 +25,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_cursor_state.h" #include "ui/image/image.h" #include "ui/text/format_values.h" +#include "ui/cached_round_corners.h" #include "main/main_session.h" #include "lang/lang_keys.h" -#include "app.h" #include "styles/style_overview.h" #include "styles/style_chat.h" #include "styles/style_chat_helpers.h" @@ -460,7 +460,7 @@ void Sticker::paint(Painter &p, const QRect &clip, const PaintContext *context) auto over = _a_over.value(_active ? 1. : 0.); if (over > 0) { p.setOpacity(over); - App::roundRect(p, QRect(QPoint(0, 0), st::stickerPanSize), st::emojiPanHover, StickerHoverCorners); + Ui::FillRoundRect(p, QRect(QPoint(0, 0), st::stickerPanSize), st::emojiPanHover, Ui::StickerHoverCorners); p.setOpacity(1); } @@ -734,7 +734,7 @@ void Video::paint(Painter &p, const QRect &clip, const PaintContext *context) co int durationTop = st::inlineRowMargin + st::inlineThumbSize - st::normalFont->height - st::inlineDurationMargin; int durationW = _durationWidth + 2 * st::msgDateImgPadding.x(), durationH = st::normalFont->height + 2 * st::msgDateImgPadding.y(); int durationX = (st::inlineThumbSize - durationW) / 2, durationY = st::inlineRowMargin + st::inlineThumbSize - durationH; - App::roundRect(p, durationX, durationY - st::msgDateImgPadding.y(), durationW, durationH, st::msgDateImgBg, DateCorners); + Ui::FillRoundRect(p, durationX, durationY - st::msgDateImgPadding.y(), durationW, durationH, st::msgDateImgBg, Ui::DateCorners); p.setPen(st::msgDateImgFg); p.setFont(st::normalFont); p.drawText(durationX + st::msgDateImgPadding.x(), durationTop + st::normalFont->ascent, _duration); diff --git a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp index 335ca6cd5..c3184fbb9 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp @@ -33,9 +33,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/labels.h" +#include "ui/cached_round_corners.h" #include "history/view/history_view_cursor_state.h" #include "facades.h" -#include "app.h" #include "styles/style_chat_helpers.h" #include @@ -905,7 +905,7 @@ void Widget::paintEvent(QPaintEvent *e) { void Widget::paintContent(Painter &p) { auto inner = innerRect(); - App::roundRect(p, inner, st::emojiPanBg, ImageRoundRadius::Small, RectPart::FullTop | RectPart::FullBottom); + Ui::FillRoundRect(p, inner, st::emojiPanBg, ImageRoundRadius::Small, RectPart::FullTop | RectPart::FullBottom); auto horizontal = horizontalRect(); auto sidesTop = horizontal.y(); diff --git a/Telegram/SourceFiles/layout.cpp b/Telegram/SourceFiles/layout.cpp index cea341830..6dcba44ab 100644 --- a/Telegram/SourceFiles/layout.cpp +++ b/Telegram/SourceFiles/layout.cpp @@ -18,7 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/audio/media_audio.h" #include "storage/localstorage.h" #include "history/view/history_view_cursor_state.h" -#include "app.h" +#include "ui/cached_round_corners.h" int32 documentColorIndex(DocumentData *document, QString &ext) { auto colorIndex = 0; @@ -118,8 +118,8 @@ style::color documentSelectedColor(int32 colorIndex) { return colors[colorIndex & 3]; } -RoundCorners documentCorners(int32 colorIndex) { - return RoundCorners(Doc1Corners + (colorIndex & 3)); +Ui::CachedRoundCorners documentCorners(int32 colorIndex) { + return Ui::CachedRoundCorners(Ui::Doc1Corners + (colorIndex & 3)); } [[nodiscard]] HistoryView::TextState LayoutItemBase::getState( diff --git a/Telegram/SourceFiles/layout.h b/Telegram/SourceFiles/layout.h index 7f5033db3..8fd524927 100644 --- a/Telegram/SourceFiles/layout.h +++ b/Telegram/SourceFiles/layout.h @@ -14,7 +14,9 @@ struct TextState; struct StateRequest; } // namespace HistoryView -enum RoundCorners : int; +namespace Ui { +enum CachedRoundCorners : int; +} // namespace Ui constexpr auto FullSelection = TextSelection { 0xFFFF, 0xFFFF }; @@ -57,7 +59,7 @@ style::color documentColor(int colorIndex); style::color documentDarkColor(int colorIndex); style::color documentOverColor(int colorIndex); style::color documentSelectedColor(int colorIndex); -RoundCorners documentCorners(int colorIndex); +Ui::CachedRoundCorners documentCorners(int colorIndex); class PaintContextBase { public: diff --git a/Telegram/SourceFiles/main/main_session_settings.cpp b/Telegram/SourceFiles/main/main_session_settings.cpp index 4a80fe280..6b178cd01 100644 --- a/Telegram/SourceFiles/main/main_session_settings.cpp +++ b/Telegram/SourceFiles/main/main_session_settings.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/tabbed_selector.h" #include "ui/widgets/input_fields.h" +#include "ui/chat/attach/attach_common.h" #include "window/section_widget.h" #include "support/support_common.h" #include "storage/serialize_common.h" @@ -367,11 +368,11 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { for (const auto &[key, value] : appSoundOverrides) { app.setSoundOverride(key, value); } - auto uncheckedSendFilesWay = static_cast(appSendFilesWay); + auto uncheckedSendFilesWay = static_cast(appSendFilesWay); switch (uncheckedSendFilesWay) { - case SendFilesWay::Album: - case SendFilesWay::Photos: - case SendFilesWay::Files: app.setSendFilesWay(uncheckedSendFilesWay); break; + case Ui::SendFilesWay::Album: + case Ui::SendFilesWay::Photos: + case Ui::SendFilesWay::Files: app.setSendFilesWay(uncheckedSendFilesWay); break; } auto uncheckedSendSubmitWay = static_cast( appSendSubmitWay); diff --git a/Telegram/SourceFiles/media/player/media_player_panel.cpp b/Telegram/SourceFiles/media/player/media_player_panel.cpp index 2be6eb4da..fcc7fe218 100644 --- a/Telegram/SourceFiles/media/player/media_player_panel.cpp +++ b/Telegram/SourceFiles/media/player/media_player_panel.cpp @@ -19,9 +19,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/shadow.h" #include "ui/widgets/scroll_area.h" #include "ui/ui_utility.h" +#include "ui/cached_round_corners.h" #include "mainwindow.h" #include "main/main_session.h" -#include "app.h" #include "styles/style_overview.h" #include "styles/style_widgets.h" #include "styles/style_media_player.h" @@ -159,7 +159,7 @@ void Panel::paintEvent(QPaintEvent *e) { | RectPart::Top; Ui::Shadow::paint(p, shadowedRect, width(), st::defaultRoundShadow, shadowedSides); auto parts = RectPart::Full; - App::roundRect(p, shadowedRect, st::menuBg, MenuCorners, nullptr, parts); + Ui::FillRoundRect(p, shadowedRect, st::menuBg, Ui::MenuCorners, nullptr, parts); } void Panel::enterEventHook(QEvent *e) { diff --git a/Telegram/SourceFiles/media/player/media_player_volume_controller.cpp b/Telegram/SourceFiles/media/player/media_player_volume_controller.cpp index d21a1fbdc..2e2c452af 100644 --- a/Telegram/SourceFiles/media/player/media_player_volume_controller.cpp +++ b/Telegram/SourceFiles/media/player/media_player_volume_controller.cpp @@ -12,13 +12,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/shadow.h" #include "ui/widgets/continuous_sliders.h" #include "ui/ui_utility.h" +#include "ui/cached_round_corners.h" #include "base/object_ptr.h" #include "mainwindow.h" #include "main/main_session.h" #include "window/window_session_controller.h" #include "core/application.h" #include "core/core_settings.h" -#include "app.h" #include "styles/style_media_player.h" #include "styles/style_widgets.h" @@ -146,7 +146,7 @@ void VolumeWidget::paintEvent(QPaintEvent *e) { auto shadowedSides = RectPart::Left | RectPart::Right | RectPart::Bottom; Ui::Shadow::paint(p, shadowedRect, width(), st::defaultRoundShadow, shadowedSides); auto parts = RectPart::NoTopBottom | RectPart::FullBottom; - App::roundRect(p, QRect(shadowedRect.x(), -st::buttonRadius, shadowedRect.width(), shadowedRect.y() + shadowedRect.height() + st::buttonRadius), st::menuBg, MenuCorners, nullptr, parts); + Ui::FillRoundRect(p, QRect(shadowedRect.x(), -st::buttonRadius, shadowedRect.width(), shadowedRect.y() + shadowedRect.height() + st::buttonRadius), st::menuBg, Ui::MenuCorners, nullptr, parts); } void VolumeWidget::enterEventHook(QEvent *e) { diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 4771cea59..eff607fcb 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/format_values.h" #include "ui/item_text_options.h" #include "ui/ui_utility.h" +#include "ui/cached_round_corners.h" #include "boxes/confirm_box.h" #include "media/audio/media_audio.h" #include "media/view/media_view_playback_controls.h" @@ -3014,7 +3015,7 @@ void OverlayWidget::paintEvent(QPaintEvent *e) { _saveMsgOpacity.update(qMin(progress, 1.), anim::linear); if (_saveMsgOpacity.current() > 0) { p.setOpacity(_saveMsgOpacity.current()); - App::roundRect(p, _saveMsg, st::mediaviewSaveMsgBg, MediaviewSaveCorners); + Ui::FillRoundRect(p, _saveMsg, st::mediaviewSaveMsgBg, Ui::MediaviewSaveCorners); st::mediaviewSaveMsgCheck.paint(p, _saveMsg.topLeft() + st::mediaviewSaveMsgCheckPos, width()); p.setPen(st::mediaviewSaveMsgFg); diff --git a/Telegram/SourceFiles/media/view/media_view_playback_controls.cpp b/Telegram/SourceFiles/media/view/media_view_playback_controls.cpp index f2ed6ccbd..3b8bb05be 100644 --- a/Telegram/SourceFiles/media/view/media_view_playback_controls.cpp +++ b/Telegram/SourceFiles/media/view/media_view_playback_controls.cpp @@ -15,8 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/buttons.h" #include "ui/widgets/popup_menu.h" #include "ui/text/format_values.h" +#include "ui/cached_round_corners.h" #include "lang/lang_keys.h" -#include "app.h" #include "styles/style_media_view.h" namespace Media { @@ -429,7 +429,7 @@ void PlaybackControls::paintEvent(QPaintEvent *e) { _volumeController->setFadeOpacity(1.); _childrenHidden = false; } - App::roundRect(p, rect(), st::mediaviewSaveMsgBg, MediaviewSaveCorners); + Ui::FillRoundRect(p, rect(), st::mediaviewSaveMsgBg, Ui::MediaviewSaveCorners); } void PlaybackControls::mousePressEvent(QMouseEvent *e) { diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 5a4096ec1..57d25bdf4 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/image/image.h" #include "ui/text/format_values.h" #include "ui/text/text_options.h" +#include "ui/cached_round_corners.h" #include "app.h" namespace Overview { @@ -501,7 +502,7 @@ void Video::paint(Painter &p, const QRect &clip, TextSelection selection, const const auto statusW = icon.width() + padding.x() + st::normalFont->width(text) + 2 * padding.x(); const auto statusH = st::normalFont->height + 2 * padding.y(); p.setOpacity(1. - radialOpacity); - App::roundRect(p, statusX - padding.x(), statusY - padding.y(), statusW, statusH, selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? OverviewVideoSelectedCorners : OverviewVideoCorners); + Ui::FillRoundRect(p, statusX - padding.x(), statusY - padding.y(), statusW, statusH, selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? Ui::OverviewVideoSelectedCorners : Ui::OverviewVideoCorners); p.setFont(st::normalFont); p.setPen(st::msgDateImgFg); icon.paint(p, statusX, statusY + (st::normalFont->height - icon.height()) / 2, _width); @@ -1658,19 +1659,19 @@ void Link::validateThumbnail() { const auto index = _letter.isEmpty() ? 0 : (_letter[0].unicode() % 4); - const auto fill = [&](style::color color, RoundCorners corners) { + const auto fill = [&](style::color color, Ui::CachedRoundCorners corners) { auto pixRect = QRect( 0, 0, st::linksPhotoSize, st::linksPhotoSize); - App::roundRect(p, pixRect, color, corners); + Ui::FillRoundRect(p, pixRect, color, corners); }; switch (index) { - case 0: fill(st::msgFile1Bg, Doc1Corners); break; - case 1: fill(st::msgFile2Bg, Doc2Corners); break; - case 2: fill(st::msgFile3Bg, Doc3Corners); break; - case 3: fill(st::msgFile4Bg, Doc4Corners); break; + case 0: fill(st::msgFile1Bg, Ui::Doc1Corners); break; + case 1: fill(st::msgFile2Bg, Ui::Doc2Corners); break; + case 2: fill(st::msgFile3Bg, Ui::Doc3Corners); break; + case 3: fill(st::msgFile4Bg, Ui::Doc4Corners); break; } if (!_letter.isEmpty()) { diff --git a/Telegram/SourceFiles/settings/settings_intro.cpp b/Telegram/SourceFiles/settings/settings_intro.cpp index 85a323fbf..7f7424433 100644 --- a/Telegram/SourceFiles/settings/settings_intro.cpp +++ b/Telegram/SourceFiles/settings/settings_intro.cpp @@ -18,10 +18,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" #include "ui/widgets/scroll_area.h" +#include "ui/cached_round_corners.h" #include "lang/lang_keys.h" #include "boxes/abstract_box.h" #include "window/window_controller.h" -#include "app.h" #include "styles/style_settings.h" #include "styles/style_layers.h" #include "styles/style_info.h" @@ -524,11 +524,11 @@ void LayerWidget::paintEvent(QPaintEvent *e) { parts |= RectPart::FullBottom; } if (parts) { - App::roundRect( + Ui::FillRoundRect( p, rect(), st::boxBg, - BoxCorners, + Ui::BoxCorners, nullptr, parts); } diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index 786e1f5f4..466f1bc9a 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/vertical_layout.h" #include "ui/wrap/slide_wrap.h" #include "ui/image/image_prepare.h" +#include "ui/cached_round_corners.h" #include "window/section_widget.h" #include "window/window_session_controller.h" #include "boxes/peer_list_controllers.h" @@ -859,7 +860,7 @@ void ForwardsPrivacyController::PaintForwardedTooltip( const auto arrowLeft = arrowLeft2; const auto geometry = rect.translated(left, top); - App::roundRect(p, geometry, st::toastBg, ImageRoundRadius::Small); + Ui::FillRoundRect(p, geometry, st::toastBg, ImageRoundRadius::Small); p.setFont(font); p.setPen(st::toastFg); diff --git a/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp b/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp index 729181abf..2bed620ff 100644 --- a/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp +++ b/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/mtproto_config.h" #include "ui/effects/animation_value.h" #include "ui/widgets/input_fields.h" +#include "ui/chat/attach/attach_common.h" #include "window/themes/window_theme.h" #include "core/update_checker.h" #include "platform/platform_specific.h" @@ -918,8 +919,8 @@ bool ReadSetting( if (!CheckStreamStatus(stream)) return false; Core::App().settings().setSendFilesWay((v == 1) - ? SendFilesWay::Album - : SendFilesWay::Files); + ? Ui::SendFilesWay::Album + : Ui::SendFilesWay::Files); context.legacyRead = true; } break; diff --git a/Telegram/SourceFiles/ui/cached_round_corners.cpp b/Telegram/SourceFiles/ui/cached_round_corners.cpp new file mode 100644 index 000000000..3402363b9 --- /dev/null +++ b/Telegram/SourceFiles/ui/cached_round_corners.cpp @@ -0,0 +1,255 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/cached_round_corners.h" + +#include "ui/ui_utility.h" +#include "ui/image/image_prepare.h" +#include "styles/style_chat.h" +#include "styles/style_layers.h" +#include "styles/style_overview.h" +#include "styles/style_media_view.h" +#include "styles/style_chat_helpers.h" + +namespace Ui { +namespace { + +struct CornersPixmaps { + QPixmap p[4]; +}; +std::vector Corners; +base::flat_map CornersMap; +QImage CornersMaskLarge[4], CornersMaskSmall[4]; +rpl::lifetime PaletteChangedLifetime; + +void PrepareCorners(CachedRoundCorners index, int32 radius, const QBrush &brush, const style::color *shadow = nullptr, QImage *cors = nullptr) { + Expects(Corners.size() > index); + + int32 r = radius * style::DevicePixelRatio(), s = st::msgShadow * style::DevicePixelRatio(); + QImage rect(r * 3, r * 3 + (shadow ? s : 0), QImage::Format_ARGB32_Premultiplied), localCors[4]; + { + Painter p(&rect); + PainterHighQualityEnabler hq(p); + + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(QRect(0, 0, rect.width(), rect.height()), Qt::transparent); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + p.setPen(Qt::NoPen); + if (shadow) { + p.setBrush((*shadow)->b); + p.drawRoundedRect(0, s, r * 3, r * 3, r, r); + } + p.setBrush(brush); + p.drawRoundedRect(0, 0, r * 3, r * 3, r, r); + } + if (!cors) cors = localCors; + cors[0] = rect.copy(0, 0, r, r); + cors[1] = rect.copy(r * 2, 0, r, r); + cors[2] = rect.copy(0, r * 2, r, r + (shadow ? s : 0)); + cors[3] = rect.copy(r * 2, r * 2, r, r + (shadow ? s : 0)); + if (index != SmallMaskCorners && index != LargeMaskCorners) { + for (int i = 0; i < 4; ++i) { + Corners[index].p[i] = PixmapFromImage(std::move(cors[i])); + Corners[index].p[i].setDevicePixelRatio(style::DevicePixelRatio()); + } + } +} + +void CreateMaskCorners() { + QImage mask[4]; + PrepareCorners(SmallMaskCorners, st::buttonRadius, QColor(255, 255, 255), nullptr, mask); + for (int i = 0; i < 4; ++i) { + CornersMaskSmall[i] = mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied); + CornersMaskSmall[i].setDevicePixelRatio(style::DevicePixelRatio()); + } + PrepareCorners(LargeMaskCorners, st::historyMessageRadius, QColor(255, 255, 255), nullptr, mask); + for (int i = 0; i < 4; ++i) { + CornersMaskLarge[i] = mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied); + CornersMaskLarge[i].setDevicePixelRatio(style::DevicePixelRatio()); + } +} + +void CreatePaletteCorners() { + 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, st::historyMessageRadius, st::msgSelectOverlay); + PrepareCorners(DateCorners, st::dateRadius, st::msgDateImgBg); + PrepareCorners(DateSelectedCorners, st::dateRadius, st::msgDateImgBgSelected); + PrepareCorners(OverviewVideoCorners, st::overviewVideoStatusRadius, st::msgDateImgBg); + PrepareCorners(OverviewVideoSelectedCorners, st::overviewVideoStatusRadius, st::msgDateImgBgSelected); + PrepareCorners(InShadowCorners, st::historyMessageRadius, st::msgInShadow); + PrepareCorners(InSelectedShadowCorners, st::historyMessageRadius, st::msgInShadowSelected); + PrepareCorners(ForwardCorners, st::historyMessageRadius, 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(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, st::historyMessageRadius, st::msgInBg, &st::msgInShadow); + PrepareCorners(MessageInSelectedCorners, st::historyMessageRadius, st::msgInBgSelected, &st::msgInShadowSelected); + PrepareCorners(MessageOutCorners, st::historyMessageRadius, st::msgOutBg, &st::msgOutShadow); + PrepareCorners(MessageOutSelectedCorners, st::historyMessageRadius, st::msgOutBgSelected, &st::msgOutShadowSelected); +} + +} // namespace + +void StartCachedCorners() { + Corners.resize(RoundCornersCount); + CreateMaskCorners(); + CreatePaletteCorners(); + + style::PaletteChanged( + ) | rpl::start_with_next([=] { + CreatePaletteCorners(); + }, PaletteChangedLifetime); +} + +void FinishCachedCorners() { + Corners.clear(); + CornersMap.clear(); + PaletteChangedLifetime.destroy(); +} + +void RectWithCorners(Painter &p, QRect rect, const style::color &bg, CachedRoundCorners index, RectParts corners) { + auto parts = RectPart::Top + | RectPart::NoTopBottom + | RectPart::Bottom + | corners; + FillRoundRect(p, rect, bg, index, nullptr, parts); + if ((corners & RectPart::AllCorners) != RectPart::AllCorners) { + const auto size = Corners[index].p[0].width() / style::DevicePixelRatio(); + if (!(corners & RectPart::TopLeft)) { + p.fillRect(rect.x(), rect.y(), size, size, bg); + } + if (!(corners & RectPart::TopRight)) { + p.fillRect(rect.x() + rect.width() - size, rect.y(), size, size, bg); + } + if (!(corners & RectPart::BottomLeft)) { + p.fillRect(rect.x(), rect.y() + rect.height() - size, size, size, bg); + } + if (!(corners & RectPart::BottomRight)) { + p.fillRect(rect.x() + rect.width() - size, rect.y() + rect.height() - size, size, size, bg); + } + } +} + +void FillComplexOverlayRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners) { + if (radius == ImageRoundRadius::Ellipse) { + PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); + p.setBrush(p.textPalette().selectOverlay); + p.drawEllipse(rect); + } else { + auto overlayCorners = (radius == ImageRoundRadius::Small) + ? SelectedOverlaySmallCorners + : SelectedOverlayLargeCorners; + const auto bg = p.textPalette().selectOverlay; + RectWithCorners(p, rect, bg, overlayCorners, corners); + } +} + +void FillComplexLocationRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners) { + RectWithCorners(p, rect, st::msgInBg, MessageInCorners, corners); +} + +void FillRoundRect(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() / style::DevicePixelRatio(); + auto cornerHeight = corner.p[0].height() / style::DevicePixelRatio(); + 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 * cornerHeight) { + if ((parts & RectPart::NoTopBottom) == 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]); + } +} + +void FillRoundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, CachedRoundCorners index, const style::color *shadow, RectParts parts) { + FillRoundRect(p, x, y, w, h, bg, Corners[index], shadow, parts); +} + +void FillRoundShadow(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color shadow, CachedRoundCorners index, RectParts parts) { + auto &corner = Corners[index]; + auto cornerWidth = corner.p[0].width() / style::DevicePixelRatio(); + auto cornerHeight = corner.p[0].height() / style::DevicePixelRatio(); + 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 FillRoundRect(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) { + case ImageRoundRadius::Small: PrepareCorners(SmallMaskCorners, st::buttonRadius, bg, nullptr, images); break; + case ImageRoundRadius::Large: PrepareCorners(LargeMaskCorners, st::historyMessageRadius, bg, nullptr, images); break; + default: p.fillRect(x, y, w, h, bg); return; + } + + CornersPixmaps pixmaps; + for (int j = 0; j < 4; ++j) { + pixmaps.p[j] = PixmapFromImage(std::move(images[j])); + pixmaps.p[j].setDevicePixelRatio(style::DevicePixelRatio()); + } + i = CornersMap.emplace(colorKey, pixmaps).first; + } + FillRoundRect(p, x, y, w, h, bg, i->second, nullptr, parts); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/cached_round_corners.h b/Telegram/SourceFiles/ui/cached_round_corners.h new file mode 100644 index 000000000..92e61df08 --- /dev/null +++ b/Telegram/SourceFiles/ui/cached_round_corners.h @@ -0,0 +1,75 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/rect_part.h" + +class Painter; + +enum class ImageRoundRadius; + +namespace Ui { + +enum CachedRoundCorners : int { + SmallMaskCorners = 0x00, // for images + LargeMaskCorners, + + BoxCorners, + MenuCorners, + BotKbOverCorners, + StickerCorners, + StickerSelectedCorners, + SelectedOverlaySmallCorners, + SelectedOverlayLargeCorners, + DateCorners, + DateSelectedCorners, + OverviewVideoCorners, + OverviewVideoSelectedCorners, + ForwardCorners, + MediaviewSaveCorners, + EmojiHoverCorners, + StickerHoverCorners, + BotKeyboardCorners, + PhotoSelectOverlayCorners, + + Doc1Corners, + Doc2Corners, + Doc3Corners, + Doc4Corners, + + InShadowCorners, // for photos without bg + InSelectedShadowCorners, + + MessageInCorners, // with shadow + MessageInSelectedCorners, + MessageOutCorners, + MessageOutSelectedCorners, + + RoundCornersCount +}; + +void FillComplexOverlayRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners); +void FillComplexLocationRect(Painter &p, QRect rect, ImageRoundRadius radius, RectParts corners); + +void FillRoundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, CachedRoundCorners index, const style::color *shadow = nullptr, RectParts parts = RectPart::Full); +inline void FillRoundRect(Painter &p, const QRect &rect, style::color bg, CachedRoundCorners index, const style::color *shadow = nullptr, RectParts parts = RectPart::Full) { + return FillRoundRect(p, rect.x(), rect.y(), rect.width(), rect.height(), bg, index, shadow, parts); +} +void FillRoundShadow(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color shadow, CachedRoundCorners index, RectParts parts = RectPart::Full); +inline void FillRoundShadow(Painter &p, const QRect &rect, style::color shadow, CachedRoundCorners index, RectParts parts = RectPart::Full) { + return FillRoundShadow(p, rect.x(), rect.y(), rect.width(), rect.height(), shadow, index, parts); +} +void FillRoundRect(Painter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, ImageRoundRadius radius, RectParts parts = RectPart::Full); +inline void FillRoundRect(Painter &p, const QRect &rect, style::color bg, ImageRoundRadius radius, RectParts parts = RectPart::Full) { + return FillRoundRect(p, rect.x(), rect.y(), rect.width(), rect.height(), bg, radius, parts); +} + +void StartCachedCorners(); +void FinishCachedCorners(); + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp new file mode 100644 index 000000000..f7086ccd6 --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp @@ -0,0 +1,484 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/chat/attach/attach_album_preview.h" + +#include "ui/chat/attach/attach_album_thumbnail.h" +#include "ui/chat/attach/attach_prepare.h" +#include "styles/style_chat.h" +#include "styles/style_boxes.h" +#include "styles/style_layers.h" + +namespace Ui { +namespace { + +constexpr auto kDragDuration = crl::time(200); + +} // namespace + +AlbumPreview::AlbumPreview( + QWidget *parent, + const PreparedList &list, + SendFilesWay way) +: RpWidget(parent) +, _list(list) +, _sendWay(way) { + setMouseTracking(true); + prepareThumbs(); + updateSize(); + updateFileRows(); +} + +AlbumPreview::~AlbumPreview() = default; + +void AlbumPreview::setSendWay(SendFilesWay way) { + if (_sendWay != way) { + cancelDrag(); + _sendWay = way; + } + updateSize(); + updateFileRows(); + update(); +} + +void AlbumPreview::updateFileRows() { + Expects(_order.size() == _thumbs.size()); + const auto isFile = (_sendWay == SendFilesWay::Files); + for (auto i = 0; i < _order.size(); i++) { + _thumbs[i]->updateFileRow(isFile ? _order[i] : -1); + } +} + +std::vector AlbumPreview::takeOrder() { + auto reordered = std::vector>(); + reordered.reserve(_thumbs.size()); + for (auto index : _order) { + reordered.push_back(std::move(_thumbs[index])); + } + _thumbs = std::move(reordered); + return std::exchange(_order, defaultOrder()); +} + +auto AlbumPreview::generateOrderedLayout() const +-> std::vector { + auto sizes = ranges::view::all( + _order + ) | ranges::view::transform([&](int index) { + return _list.files[index].shownDimensions; + }) | ranges::to_vector; + + auto layout = LayoutMediaGroup( + sizes, + st::sendMediaPreviewSize, + st::historyGroupWidthMin / 2, + st::historyGroupSkip / 2); + Assert(layout.size() == _order.size()); + return layout; +} + +std::vector AlbumPreview::defaultOrder() const { + const auto count = int(_list.files.size()); + return ranges::view::ints(0, count) | ranges::to_vector; +} + +void AlbumPreview::prepareThumbs() { + _order = defaultOrder(); + + const auto count = int(_list.files.size()); + const auto layout = generateOrderedLayout(); + _thumbs.reserve(count); + for (auto i = 0; i != count; ++i) { + _thumbs.push_back(std::make_unique( + _list.files[i], + layout[i], + this, + [=] { changeThumbByIndex(thumbIndex(thumbUnderCursor())); }, + [=] { deleteThumbByIndex(thumbIndex(thumbUnderCursor())); })); + } + _thumbsHeight = countLayoutHeight(layout); + _photosHeight = ranges::accumulate(ranges::view::all( + _thumbs + ) | ranges::view::transform([](const auto &thumb) { + return thumb->photoHeight(); + }), 0) + (count - 1) * st::sendMediaPreviewPhotoSkip; + + _filesHeight = count * st::sendMediaFileThumbSize + + (count - 1) * st::sendMediaFileThumbSkip; +} + +int AlbumPreview::contentLeft() const { + return (st::boxWideWidth - st::sendMediaPreviewSize) / 2; +} + +int AlbumPreview::contentTop() const { + return 0; +} + +AlbumThumbnail *AlbumPreview::findThumb(QPoint position) const { + position -= QPoint(contentLeft(), contentTop()); + + auto top = 0; + const auto isPhotosWay = (_sendWay == SendFilesWay::Photos); + const auto skip = isPhotosWay + ? st::sendMediaPreviewPhotoSkip + : st::sendMediaFileThumbSkip; + auto find = [&](const auto &thumb) { + if (_sendWay == SendFilesWay::Album) { + return thumb->containsPoint(position); + } else if (isPhotosWay || _sendWay == SendFilesWay::Files) { + const auto bottom = top + (isPhotosWay + ? thumb->photoHeight() + : st::sendMediaFileThumbSize); + const auto isUnderTop = (position.y() > top); + top = bottom + skip; + return isUnderTop && (position.y() < bottom); + } + return false; + }; + + const auto i = ranges::find_if(_thumbs, std::move(find)); + return (i == _thumbs.end()) ? nullptr : i->get(); +} + +not_null AlbumPreview::findClosestThumb( + QPoint position) const { + Expects(_draggedThumb != nullptr); + + if (const auto exact = findThumb(position)) { + return exact; + } + auto result = _draggedThumb; + auto distance = _draggedThumb->distanceTo(position); + for (const auto &thumb : _thumbs) { + const auto check = thumb->distanceTo(position); + if (check < distance) { + distance = check; + result = thumb.get(); + } + } + return result; +} + +int AlbumPreview::orderIndex( + not_null thumb) const { + const auto i = ranges::find_if(_order, [&](int index) { + return (_thumbs[index].get() == thumb); + }); + Assert(i != _order.end()); + return int(i - _order.begin()); +} + +void AlbumPreview::cancelDrag() { + _thumbsHeightAnimation.stop(); + _finishDragAnimation.stop(); + _shrinkAnimation.stop(); + if (_draggedThumb) { + _draggedThumb->moveInAlbum({ 0, 0 }); + _draggedThumb = nullptr; + } + if (_suggestedThumb) { + const auto suggestedIndex = orderIndex(_suggestedThumb); + if (suggestedIndex > 0) { + _thumbs[_order[suggestedIndex - 1]]->suggestMove(0., [] {}); + } + if (suggestedIndex < int(_order.size() - 1)) { + _thumbs[_order[suggestedIndex + 1]]->suggestMove(0., [] {}); + } + _suggestedThumb->suggestMove(0., [] {}); + _suggestedThumb->finishAnimations(); + _suggestedThumb = nullptr; + } + _paintedAbove = nullptr; + update(); +} + +void AlbumPreview::finishDrag() { + Expects(_draggedThumb != nullptr); + Expects(_suggestedThumb != nullptr); + + if (_suggestedThumb != _draggedThumb) { + const auto currentIndex = orderIndex(_draggedThumb); + const auto newIndex = orderIndex(_suggestedThumb); + const auto delta = (currentIndex < newIndex) ? 1 : -1; + const auto realIndex = _order[currentIndex]; + for (auto i = currentIndex; i != newIndex; i += delta) { + _order[i] = _order[i + delta]; + } + _order[newIndex] = realIndex; + const auto layout = generateOrderedLayout(); + for (auto i = 0, count = int(_order.size()); i != count; ++i) { + _thumbs[_order[i]]->moveToLayout(layout[i]); + } + _finishDragAnimation.start([=] { update(); }, 0., 1., kDragDuration); + + updateSizeAnimated(layout); + } else { + for (const auto &thumb : _thumbs) { + thumb->resetLayoutAnimation(); + } + _draggedThumb->animateLayoutToInitial(); + _finishDragAnimation.start([=] { update(); }, 0., 1., kDragDuration); + } +} + +int AlbumPreview::countLayoutHeight( + const std::vector &layout) const { + const auto accumulator = [](int current, const auto &item) { + return std::max(current, item.geometry.y() + item.geometry.height()); + }; + return ranges::accumulate(layout, 0, accumulator); +} + +void AlbumPreview::updateSizeAnimated( + const std::vector &layout) { + const auto newHeight = countLayoutHeight(layout); + if (newHeight != _thumbsHeight) { + _thumbsHeightAnimation.start( + [=] { updateSize(); }, + _thumbsHeight, + newHeight, + kDragDuration); + _thumbsHeight = newHeight; + } +} + +void AlbumPreview::updateSize() { + const auto newHeight = [&] { + switch (_sendWay) { + case SendFilesWay::Album: + return int(std::round(_thumbsHeightAnimation.value( + _thumbsHeight))); + case SendFilesWay::Photos: return _photosHeight; + case SendFilesWay::Files: return _filesHeight; + } + Unexpected("Send way in AlbumPreview::updateSize"); + }(); + if (height() != newHeight) { + resize(st::boxWideWidth, newHeight); + } +} + +void AlbumPreview::paintEvent(QPaintEvent *e) { + Painter p(this); + + switch (_sendWay) { + case SendFilesWay::Album: paintAlbum(p); break; + case SendFilesWay::Photos: paintPhotos(p, e->rect()); break; + case SendFilesWay::Files: paintFiles(p, e->rect()); break; + } +} + +void AlbumPreview::paintAlbum(Painter &p) const { + const auto shrink = _shrinkAnimation.value(_draggedThumb ? 1. : 0.); + const auto moveProgress = _finishDragAnimation.value(1.); + const auto left = contentLeft(); + const auto top = contentTop(); + for (const auto &thumb : _thumbs) { + if (thumb.get() != _paintedAbove) { + thumb->paintInAlbum(p, left, top, shrink, moveProgress); + } + } + if (_paintedAbove) { + _paintedAbove->paintInAlbum(p, left, top, shrink, moveProgress); + } +} + +void AlbumPreview::paintPhotos(Painter &p, QRect clip) const { + const auto left = (st::boxWideWidth - st::sendMediaPreviewSize) / 2; + auto top = 0; + const auto outerWidth = width(); + for (const auto &thumb : _thumbs) { + const auto bottom = top + thumb->photoHeight(); + const auto guard = gsl::finally([&] { + top = bottom + st::sendMediaPreviewPhotoSkip; + }); + if (top >= clip.y() + clip.height()) { + break; + } else if (bottom <= clip.y()) { + continue; + } + thumb->paintPhoto(p, left, top, outerWidth); + } +} + +void AlbumPreview::paintFiles(Painter &p, QRect clip) const { + const auto fileHeight = st::sendMediaFileThumbSize + + st::sendMediaFileThumbSkip; + const auto bottom = clip.y() + clip.height(); + const auto from = std::clamp(clip.y() / fileHeight, 0, int(_thumbs.size())); + const auto till = std::clamp((bottom + fileHeight - 1) / fileHeight, 0, int(_thumbs.size())); + const auto left = (st::boxWideWidth - st::sendMediaPreviewSize) / 2; + const auto outerWidth = width(); + + auto top = from * fileHeight; + for (auto i = from; i != till; ++i) { + _thumbs[i]->paintFile(p, left, top, outerWidth); + top += fileHeight; + } +} + +int AlbumPreview::thumbIndex(AlbumThumbnail *thumb) { + if (!thumb) { + return -1; + } + const auto thumbIt = ranges::find_if(_thumbs, [&](auto &t) { + return t.get() == thumb; + }); + Expects(thumbIt != _thumbs.end()); + return std::distance(_thumbs.begin(), thumbIt); +} + +AlbumThumbnail *AlbumPreview::thumbUnderCursor() { + return findThumb(mapFromGlobal(QCursor::pos())); +} + +void AlbumPreview::deleteThumbByIndex(int index) { + if (index < 0) { + return; + } + const auto orderIt = ranges::find(_order, index); + Expects(orderIt != _order.end()); + + _order.erase(orderIt); + ranges::for_each(_order, [=](auto &i) { + if (i > index) { + i--; + } + }); + _thumbDeleted.fire(std::move(index)); +} + +void AlbumPreview::changeThumbByIndex(int index) { + if (index < 0) { + return; + } + _thumbChanged.fire(std::move(index)); +} + +void AlbumPreview::thumbButtonsCallback( + not_null thumb, + AttachButtonType type) { + const auto index = thumbIndex(thumb); + + switch (type) { + case AttachButtonType::None: return; + case AttachButtonType::Edit: changeThumbByIndex(index); break; + case AttachButtonType::Delete: deleteThumbByIndex(index); break; + } +} + +void AlbumPreview::mousePressEvent(QMouseEvent *e) { + if (_finishDragAnimation.animating()) { + return; + } + const auto position = e->pos(); + cancelDrag(); + if (const auto thumb = findThumb(position)) { + if (thumb->buttonsContainPoint(e->pos())) { + thumbButtonsCallback(thumb, thumb->buttonTypeFromPoint(e->pos())); + return; + } + _paintedAbove = _suggestedThumb = _draggedThumb = thumb; + _draggedStartPosition = position; + _shrinkAnimation.start( + [=] { update(); }, + 0., + 1., + AlbumThumbnail::kShrinkDuration); + } +} + +void AlbumPreview::mouseMoveEvent(QMouseEvent *e) { + if (_sendWay == SendFilesWay::Files) { + applyCursor(style::cur_default); + return; + } + const auto isAlbum = (_sendWay == SendFilesWay::Album); + if (isAlbum && _draggedThumb) { + const auto position = e->pos(); + _draggedThumb->moveInAlbum(position - _draggedStartPosition); + updateSuggestedDrag(_draggedThumb->center()); + update(); + } else { + const auto thumb = findThumb(e->pos()); + const auto regularCursor = isAlbum + ? style::cur_sizeall + : style::cur_default; + const auto cursor = thumb + ? (thumb->buttonsContainPoint(e->pos()) + ? style::cur_pointer + : regularCursor) + : style::cur_default; + applyCursor(cursor); + } +} + +void AlbumPreview::applyCursor(style::cursor cursor) { + if (_cursor != cursor) { + _cursor = cursor; + setCursor(_cursor); + } +} + +void AlbumPreview::updateSuggestedDrag(QPoint position) { + auto closest = findClosestThumb(position); + auto closestIndex = orderIndex(closest); + + const auto draggedIndex = orderIndex(_draggedThumb); + const auto closestIsBeforePoint = closest->isPointAfter(position); + if (closestIndex < draggedIndex && closestIsBeforePoint) { + closest = _thumbs[_order[++closestIndex]].get(); + } else if (closestIndex > draggedIndex && !closestIsBeforePoint) { + closest = _thumbs[_order[--closestIndex]].get(); + } + + if (_suggestedThumb == closest) { + return; + } + + const auto last = int(_order.size()) - 1; + if (_suggestedThumb) { + const auto suggestedIndex = orderIndex(_suggestedThumb); + if (suggestedIndex < draggedIndex && suggestedIndex > 0) { + const auto previous = _thumbs[_order[suggestedIndex - 1]].get(); + previous->suggestMove(0., [=] { update(); }); + } else if (suggestedIndex > draggedIndex && suggestedIndex < last) { + const auto next = _thumbs[_order[suggestedIndex + 1]].get(); + next->suggestMove(0., [=] { update(); }); + } + _suggestedThumb->suggestMove(0., [=] { update(); }); + } + _suggestedThumb = closest; + const auto suggestedIndex = closestIndex; + if (_suggestedThumb != _draggedThumb) { + const auto delta = (suggestedIndex < draggedIndex) ? 1. : -1.; + if (delta > 0. && suggestedIndex > 0) { + const auto previous = _thumbs[_order[suggestedIndex - 1]].get(); + previous->suggestMove(-delta, [=] { update(); }); + } else if (delta < 0. && suggestedIndex < last) { + const auto next = _thumbs[_order[suggestedIndex + 1]].get(); + next->suggestMove(-delta, [=] { update(); }); + } + _suggestedThumb->suggestMove(delta, [=] { update(); }); + } +} + +void AlbumPreview::mouseReleaseEvent(QMouseEvent *e) { + if (_draggedThumb) { + finishDrag(); + _shrinkAnimation.start( + [=] { update(); }, + 1., + 0., + AlbumThumbnail::kShrinkDuration); + _draggedThumb = nullptr; + _suggestedThumb = nullptr; + update(); + } +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h new file mode 100644 index 000000000..752cc2af7 --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h @@ -0,0 +1,99 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/rp_widget.h" +#include "ui/chat/attach/attach_common.h" + +namespace Ui { + +struct PreparedList; +struct GroupMediaLayout; +class AlbumThumbnail; + +class AlbumPreview final : public RpWidget { +public: + AlbumPreview( + QWidget *parent, + const PreparedList &list, + SendFilesWay way); + ~AlbumPreview(); + + void setSendWay(SendFilesWay way); + std::vector takeOrder(); + + auto thumbDeleted() { + return _thumbDeleted.events(); + } + + auto thumbChanged() { + return _thumbChanged.events(); + } + +protected: + void paintEvent(QPaintEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + +private: + int countLayoutHeight( + const std::vector &layout) const; + std::vector generateOrderedLayout() const; + std::vector defaultOrder() const; + void prepareThumbs(); + void updateSizeAnimated(const std::vector &layout); + void updateSize(); + void updateFileRows(); + + int thumbIndex(AlbumThumbnail *thumb); + AlbumThumbnail *thumbUnderCursor(); + void deleteThumbByIndex(int index); + void changeThumbByIndex(int index); + void thumbButtonsCallback( + not_null thumb, + AttachButtonType type); + + void paintAlbum(Painter &p) const; + void paintPhotos(Painter &p, QRect clip) const; + void paintFiles(Painter &p, QRect clip) const; + + void applyCursor(style::cursor cursor); + int contentLeft() const; + int contentTop() const; + AlbumThumbnail *findThumb(QPoint position) const; + not_null findClosestThumb(QPoint position) const; + void updateSuggestedDrag(QPoint position); + int orderIndex(not_null thumb) const; + void cancelDrag(); + void finishDrag(); + + const PreparedList &_list; + SendFilesWay _sendWay = SendFilesWay::Files; + style::cursor _cursor = style::cur_default; + std::vector _order; + std::vector> _thumbs; + int _thumbsHeight = 0; + int _photosHeight = 0; + int _filesHeight = 0; + + AlbumThumbnail *_draggedThumb = nullptr; + AlbumThumbnail *_suggestedThumb = nullptr; + AlbumThumbnail *_paintedAbove = nullptr; + QPoint _draggedStartPosition; + + rpl::event_stream _thumbDeleted; + rpl::event_stream _thumbChanged; + + mutable Animations::Simple _thumbsHeightAnimation; + mutable Animations::Simple _shrinkAnimation; + mutable Animations::Simple _finishDragAnimation; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp new file mode 100644 index 000000000..7664a2168 --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp @@ -0,0 +1,543 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/chat/attach/attach_album_thumbnail.h" + +#include "ui/chat/attach/attach_prepare.h" +#include "ui/image/image_prepare.h" +#include "ui/text/format_values.h" +#include "ui/widgets/buttons.h" +#include "ui/ui_utility.h" +#include "base/call_delayed.h" +#include "styles/style_chat.h" +#include "styles/style_boxes.h" + +#include + +namespace Ui { + +AlbumThumbnail::AlbumThumbnail( + const PreparedFile &file, + const GroupMediaLayout &layout, + QWidget *parent, + Fn editCallback, + Fn deleteCallback) +: _layout(layout) +, _fullPreview(file.preview) +, _shrinkSize(int(std::ceil(st::historyMessageRadius / 1.4))) +, _isVideo(file.type == PreparedFile::AlbumType::Video) +, _buttonsRect(st::sendBoxAlbumGroupRadius, st::callFingerprintBg) { + Expects(!_fullPreview.isNull()); + + moveToLayout(layout); + + using Option = Images::Option; + const auto previewWidth = _fullPreview.width(); + const auto previewHeight = _fullPreview.height(); + const auto imageWidth = std::max( + previewWidth / style::DevicePixelRatio(), + st::minPhotoSize); + const auto imageHeight = std::max( + previewHeight / style::DevicePixelRatio(), + st::minPhotoSize); + _photo = PixmapFromImage(Images::prepare( + _fullPreview, + previewWidth, + previewHeight, + Option::RoundedLarge | Option::RoundedAll, + imageWidth, + imageHeight)); + + const auto idealSize = st::sendMediaFileThumbSize * style::DevicePixelRatio(); + const auto fileThumbSize = (previewWidth > previewHeight) + ? QSize(previewWidth * idealSize / previewHeight, idealSize) + : QSize(idealSize, previewHeight * idealSize / previewWidth); + _fileThumb = PixmapFromImage(Images::prepare( + _fullPreview, + fileThumbSize.width(), + fileThumbSize.height(), + Option::RoundedSmall | Option::RoundedAll, + st::sendMediaFileThumbSize, + st::sendMediaFileThumbSize + )); + + const auto availableFileWidth = st::sendMediaPreviewSize + - st::sendMediaFileThumbSkip + - st::sendMediaFileThumbSize + // Right buttons. + - st::sendBoxAlbumGroupButtonFile.width * 2 + - st::sendBoxAlbumGroupEditInternalSkip * 2 + - st::sendBoxAlbumGroupSkipRight; + const auto filepath = file.path; + if (filepath.isEmpty()) { + //_name = filedialogDefaultName( // #TODO files + // u"image"_q, + // u".png"_q, + // QString(), + // true); + _name = "image.png"; + _status = u"%1x%2"_q.arg( + _fullPreview.width() + ).arg( + _fullPreview.height() + ); + } else { + auto fileinfo = QFileInfo(filepath); + _name = fileinfo.fileName(); + _status = FormatSizeText(fileinfo.size()); + } + _nameWidth = st::semiboldFont->width(_name); + if (_nameWidth > availableFileWidth) { + _name = st::semiboldFont->elided( + _name, + availableFileWidth, + Qt::ElideMiddle); + _nameWidth = st::semiboldFont->width(_name); + } + _statusWidth = st::normalFont->width(_status); + + _editMedia.create(parent, st::sendBoxAlbumGroupButtonFile); + _deleteMedia.create(parent, st::sendBoxAlbumGroupButtonFile); + + const auto duration = st::historyAttach.ripple.hideDuration; + _editMedia->setClickedCallback([=] { + base::call_delayed(duration, parent, editCallback); + }); + _deleteMedia->setClickedCallback([=] { + base::call_delayed(duration, parent, deleteCallback); + }); + + _editMedia->setIconOverride(&st::editMediaButtonIconFile); + _deleteMedia->setIconOverride(&st::sendBoxAlbumGroupDeleteButtonIconFile); + + updateFileRow(-1); +} + +void AlbumThumbnail::updateFileRow(int row) { + if (row < 0) { + _editMedia->hide(); + _deleteMedia->hide(); + return; + } + _editMedia->show(); + _deleteMedia->show(); + + const auto fileHeight = st::sendMediaFileThumbSize + + st::sendMediaFileThumbSkip; + + const auto top = row * fileHeight + st::sendBoxAlbumGroupSkipTop; + const auto size = st::editMediaButtonSize; + + auto right = st::sendBoxAlbumGroupSkipRight + size; + _deleteMedia->moveToRight(right, top); + right += st::sendBoxAlbumGroupEditInternalSkip + size; + _editMedia->moveToRight(right, top); +} + +void AlbumThumbnail::resetLayoutAnimation() { + _animateFromGeometry = std::nullopt; +} + +void AlbumThumbnail::animateLayoutToInitial() { + _animateFromGeometry = countRealGeometry(); + _suggestedMove = 0.; + _albumPosition = QPoint(0, 0); +} + +void AlbumThumbnail::moveToLayout(const GroupMediaLayout &layout) { + animateLayoutToInitial(); + _layout = layout; + + const auto width = _layout.geometry.width(); + const auto height = _layout.geometry.height(); + _albumCorners = GetCornersFromSides(_layout.sides); + using Option = Images::Option; + const auto options = Option::Smooth + | Option::RoundedLarge + | ((_albumCorners & RectPart::TopLeft) + ? Option::RoundedTopLeft + : Option::None) + | ((_albumCorners & RectPart::TopRight) + ? Option::RoundedTopRight + : Option::None) + | ((_albumCorners & RectPart::BottomLeft) + ? Option::RoundedBottomLeft + : Option::None) + | ((_albumCorners & RectPart::BottomRight) + ? Option::RoundedBottomRight + : Option::None); + const auto pixSize = GetImageScaleSizeForGeometry( + { _fullPreview.width(), _fullPreview.height() }, + { width, height }); + const auto pixWidth = pixSize.width() * style::DevicePixelRatio(); + const auto pixHeight = pixSize.height() * style::DevicePixelRatio(); + + _albumImage = PixmapFromImage(Images::prepare( + _fullPreview, + pixWidth, + pixHeight, + options, + width, + height)); +} + +int AlbumThumbnail::photoHeight() const { + return _photo.height() / style::DevicePixelRatio(); +} + +void AlbumThumbnail::paintInAlbum( + Painter &p, + int left, + int top, + float64 shrinkProgress, + float64 moveProgress) { + const auto shrink = anim::interpolate(0, _shrinkSize, shrinkProgress); + _lastShrinkValue = shrink; + const auto geometry = countCurrentGeometry(moveProgress); + const auto x = left + geometry.x(); + const auto y = top + geometry.y(); + if (shrink > 0 || moveProgress < 1.) { + const auto size = geometry.size(); + if (shrinkProgress < 1 && _albumCorners != RectPart::None) { + prepareCache(size, shrink); + p.drawImage(x, y, _albumCache); + } else { + const auto to = QRect({ x, y }, size).marginsRemoved( + { shrink, shrink, shrink, shrink } + ); + drawSimpleFrame(p, to, size); + } + } else { + p.drawPixmap(x, y, _albumImage); + } + if (_isVideo) { + const auto inner = QRect( + x + (geometry.width() - st::msgFileSize) / 2, + y + (geometry.height() - st::msgFileSize) / 2, + st::msgFileSize, + st::msgFileSize); + { + PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); + p.setBrush(st::msgDateImgBg); + p.drawEllipse(inner); + } + st::historyFileThumbPlay.paintInCenter(p, inner); + } + + _lastRectOfButtons = paintButtons( + p, + { x, y }, + geometry.width(), + shrinkProgress); +} + +void AlbumThumbnail::prepareCache(QSize size, int shrink) { + const auto width = std::max( + _layout.geometry.width(), + _animateFromGeometry ? _animateFromGeometry->width() : 0); + const auto height = std::max( + _layout.geometry.height(), + _animateFromGeometry ? _animateFromGeometry->height() : 0); + const auto cacheSize = QSize(width, height) * style::DevicePixelRatio(); + + if (_albumCache.width() < cacheSize.width() + || _albumCache.height() < cacheSize.height()) { + _albumCache = QImage(cacheSize, QImage::Format_ARGB32_Premultiplied); + } + _albumCache.fill(Qt::transparent); + { + Painter p(&_albumCache); + const auto to = QRect(QPoint(), size).marginsRemoved( + { shrink, shrink, shrink, shrink } + ); + drawSimpleFrame(p, to, size); + } + Images::prepareRound( + _albumCache, + ImageRoundRadius::Large, + _albumCorners, + QRect(QPoint(), size * style::DevicePixelRatio())); + _albumCache.setDevicePixelRatio(style::DevicePixelRatio()); +} + +void AlbumThumbnail::drawSimpleFrame(Painter &p, QRect to, QSize size) const { + const auto fullWidth = _fullPreview.width(); + const auto fullHeight = _fullPreview.height(); + const auto previewSize = GetImageScaleSizeForGeometry( + { fullWidth, fullHeight }, + { size.width(), size.height() }); + const auto previewWidth = previewSize.width() * style::DevicePixelRatio(); + const auto previewHeight = previewSize.height() * style::DevicePixelRatio(); + const auto width = size.width() * style::DevicePixelRatio(); + const auto height = size.height() * style::DevicePixelRatio(); + const auto scaleWidth = to.width() / float64(width); + const auto scaleHeight = to.height() / float64(height); + const auto Round = [](float64 value) { + return int(std::round(value)); + }; + const auto [from, fillBlack] = [&] { + if (previewWidth < width && previewHeight < height) { + const auto toWidth = Round(previewWidth * scaleWidth); + const auto toHeight = Round(previewHeight * scaleHeight); + return std::make_pair( + QRect(0, 0, fullWidth, fullHeight), + QMargins( + (to.width() - toWidth) / 2, + (to.height() - toHeight) / 2, + to.width() - toWidth - (to.width() - toWidth) / 2, + to.height() - toHeight - (to.height() - toHeight) / 2)); + } else if (previewWidth * height > previewHeight * width) { + if (previewHeight >= height) { + const auto takeWidth = previewWidth * height / previewHeight; + const auto useWidth = fullWidth * width / takeWidth; + return std::make_pair( + QRect( + (fullWidth - useWidth) / 2, + 0, + useWidth, + fullHeight), + QMargins(0, 0, 0, 0)); + } else { + const auto takeWidth = previewWidth; + const auto useWidth = fullWidth * width / takeWidth; + const auto toHeight = Round(previewHeight * scaleHeight); + const auto toSkip = (to.height() - toHeight) / 2; + return std::make_pair( + QRect( + (fullWidth - useWidth) / 2, + 0, + useWidth, + fullHeight), + QMargins( + 0, + toSkip, + 0, + to.height() - toHeight - toSkip)); + } + } else { + if (previewWidth >= width) { + const auto takeHeight = previewHeight * width / previewWidth; + const auto useHeight = fullHeight * height / takeHeight; + return std::make_pair( + QRect( + 0, + (fullHeight - useHeight) / 2, + fullWidth, + useHeight), + QMargins(0, 0, 0, 0)); + } else { + const auto takeHeight = previewHeight; + const auto useHeight = fullHeight * height / takeHeight; + const auto toWidth = Round(previewWidth * scaleWidth); + const auto toSkip = (to.width() - toWidth) / 2; + return std::make_pair( + QRect( + 0, + (fullHeight - useHeight) / 2, + fullWidth, + useHeight), + QMargins( + toSkip, + 0, + to.width() - toWidth - toSkip, + 0)); + } + } + }(); + + p.drawImage(to.marginsRemoved(fillBlack), _fullPreview, from); + if (fillBlack.top() > 0) { + p.fillRect(to.x(), to.y(), to.width(), fillBlack.top(), st::imageBg); + } + if (fillBlack.bottom() > 0) { + p.fillRect( + to.x(), + to.y() + to.height() - fillBlack.bottom(), + to.width(), + fillBlack.bottom(), + st::imageBg); + } + if (fillBlack.left() > 0) { + p.fillRect( + to.x(), + to.y() + fillBlack.top(), + fillBlack.left(), + to.height() - fillBlack.top() - fillBlack.bottom(), + st::imageBg); + } + if (fillBlack.right() > 0) { + p.fillRect( + to.x() + to.width() - fillBlack.right(), + to.y() + fillBlack.top(), + fillBlack.right(), + to.height() - fillBlack.top() - fillBlack.bottom(), + st::imageBg); + } +} + +void AlbumThumbnail::paintPhoto(Painter &p, int left, int top, int outerWidth) { + const auto width = _photo.width() / style::DevicePixelRatio(); + p.drawPixmapLeft( + left + (st::sendMediaPreviewSize - width) / 2, + top, + outerWidth, + _photo); + + _lastRectOfButtons = paintButtons( + p, + { left, top }, + st::sendMediaPreviewSize, + 0); +} + +void AlbumThumbnail::paintFile(Painter &p, int left, int top, int outerWidth) { + const auto textLeft = left + + st::sendMediaFileThumbSize + + st::sendMediaFileThumbSkip; + + p.drawPixmap(left, top, _fileThumb); + p.setFont(st::semiboldFont); + p.setPen(st::historyFileNameInFg); + p.drawTextLeft( + textLeft, + top + st::sendMediaFileNameTop, + outerWidth, + _name, + _nameWidth); + p.setFont(st::normalFont); + p.setPen(st::mediaInFg); + p.drawTextLeft( + textLeft, + top + st::sendMediaFileStatusTop, + outerWidth, + _status, + _statusWidth); +} + +bool AlbumThumbnail::containsPoint(QPoint position) const { + return _layout.geometry.contains(position); +} + +bool AlbumThumbnail::buttonsContainPoint(QPoint position) const { + return _lastRectOfButtons.contains(position); +} + +AttachButtonType AlbumThumbnail::buttonTypeFromPoint(QPoint position) const { + if (!buttonsContainPoint(position)) { + return AttachButtonType::None; + } + return (position.x() < _lastRectOfButtons.center().x()) + ? AttachButtonType::Edit + : AttachButtonType::Delete; +} + +int AlbumThumbnail::distanceTo(QPoint position) const { + const auto delta = (_layout.geometry.center() - position); + return QPoint::dotProduct(delta, delta); +} + +bool AlbumThumbnail::isPointAfter(QPoint position) const { + return position.x() > _layout.geometry.center().x(); +} + +void AlbumThumbnail::moveInAlbum(QPoint to) { + _albumPosition = to; +} + +QPoint AlbumThumbnail::center() const { + auto realGeometry = _layout.geometry; + realGeometry.moveTopLeft(realGeometry.topLeft() + _albumPosition); + return realGeometry.center(); +} + +void AlbumThumbnail::suggestMove(float64 delta, Fn callback) { + if (_suggestedMove != delta) { + _suggestedMoveAnimation.start( + std::move(callback), + _suggestedMove, + delta, + kShrinkDuration); + _suggestedMove = delta; + } +} + +QRect AlbumThumbnail::countRealGeometry() const { + const auto addLeft = int(std::round( + _suggestedMoveAnimation.value(_suggestedMove) * _lastShrinkValue)); + const auto current = _layout.geometry; + const auto realTopLeft = current.topLeft() + + _albumPosition + + QPoint(addLeft, 0); + return { realTopLeft, current.size() }; +} + +QRect AlbumThumbnail::countCurrentGeometry(float64 progress) const { + const auto now = countRealGeometry(); + if (_animateFromGeometry && progress < 1.) { + return { + anim::interpolate(_animateFromGeometry->x(), now.x(), progress), + anim::interpolate(_animateFromGeometry->y(), now.y(), progress), + anim::interpolate(_animateFromGeometry->width(), now.width(), progress), + anim::interpolate(_animateFromGeometry->height(), now.height(), progress) + }; + } + return now; +} + +void AlbumThumbnail::finishAnimations() { + _suggestedMoveAnimation.stop(); +} + +QRect AlbumThumbnail::paintButtons( + Painter &p, + QPoint point, + int outerWidth, + float64 shrinkProgress) { + const auto skipInternal = st::sendBoxAlbumGroupEditInternalSkip; + const auto size = st::sendBoxAlbumGroupHeight; + const auto skipRight = st::sendBoxAlbumGroupSkipRight; + const auto skipTop = st::sendBoxAlbumGroupSkipTop; + const auto groupWidth = size * 2 + skipInternal; + + // If the width is tiny, it would be better to not display the buttons. + if (groupWidth > outerWidth) { + return QRect(); + } + + // If the width is too small, + // it would be better to display the buttons in the center. + const auto groupX = point.x() + ((groupWidth + skipRight * 2 > outerWidth) + ? (outerWidth - groupWidth) / 2 + : outerWidth - skipRight - groupWidth); + const auto groupY = point.y() + skipTop; + const auto deleteLeft = skipInternal + size; + + p.setOpacity(1.0 - shrinkProgress); + + QRect groupRect(groupX, groupY, groupWidth, size); + _buttonsRect.paint(p, groupRect); + + const auto editP = st::sendBoxAlbumGroupEditButtonIconPosition; + const auto deleteP = st::sendBoxAlbumGroupDeleteButtonIconPosition; + + st::sendBoxAlbumGroupEditButtonIcon.paintInCenter( + p, + QRect(groupX + editP.x(), groupY + editP.y(), size, size)); + st::sendBoxAlbumGroupDeleteButtonIcon.paintInCenter( + p, + QRect( + groupX + deleteLeft + deleteP.x(), + groupY + deleteP.y(), + size, + size)); + p.setOpacity(1); + + return groupRect; +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.h b/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.h new file mode 100644 index 000000000..13683d356 --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.h @@ -0,0 +1,97 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/chat/attach/attach_common.h" +#include "ui/effects/animations.h" +#include "ui/grouped_layout.h" +#include "ui/round_rect.h" +#include "base/object_ptr.h" + +namespace Ui { + +struct PreparedFile; +class IconButton; + +class AlbumThumbnail final { +public: + AlbumThumbnail( + const PreparedFile &file, + const GroupMediaLayout &layout, + QWidget *parent, + Fn editCallback, + Fn deleteCallback); + + void moveToLayout(const GroupMediaLayout &layout); + void animateLayoutToInitial(); + void resetLayoutAnimation(); + + int photoHeight() const; + + void paintInAlbum( + Painter &p, + int left, + int top, + float64 shrinkProgress, + float64 moveProgress); + void paintPhoto(Painter &p, int left, int top, int outerWidth); + void paintFile(Painter &p, int left, int top, int outerWidth); + + bool containsPoint(QPoint position) const; + bool buttonsContainPoint(QPoint position) const; + AttachButtonType buttonTypeFromPoint(QPoint position) const; + int distanceTo(QPoint position) const; + bool isPointAfter(QPoint position) const; + void moveInAlbum(QPoint to); + QPoint center() const; + void suggestMove(float64 delta, Fn callback); + void finishAnimations(); + + void updateFileRow(int row); + + static constexpr auto kShrinkDuration = crl::time(150); + +private: + QRect countRealGeometry() const; + QRect countCurrentGeometry(float64 progress) const; + void prepareCache(QSize size, int shrink); + void drawSimpleFrame(Painter &p, QRect to, QSize size) const; + QRect paintButtons( + Painter &p, + QPoint point, + int outerWidth, + float64 shrinkProgress); + + GroupMediaLayout _layout; + std::optional _animateFromGeometry; + const QImage _fullPreview; + const int _shrinkSize = 0; + QPixmap _albumImage; + QImage _albumCache; + QPoint _albumPosition; + RectParts _albumCorners = RectPart::None; + QPixmap _photo; + QPixmap _fileThumb; + QString _name; + QString _status; + int _nameWidth = 0; + int _statusWidth = 0; + bool _isVideo = false; + float64 _suggestedMove = 0.; + Animations::Simple _suggestedMoveAnimation; + int _lastShrinkValue = 0; + RoundRect _buttonsRect; + + QRect _lastRectOfButtons; + + object_ptr _editMedia = nullptr; + object_ptr _deleteMedia = nullptr; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_common.h b/Telegram/SourceFiles/ui/chat/attach/attach_common.h new file mode 100644 index 000000000..89226df88 --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/attach/attach_common.h @@ -0,0 +1,24 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Ui { + +enum class AttachButtonType { + Edit, + Delete, + None, +}; + +enum class SendFilesWay { + Album, + Photos, + Files, +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_single_file_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_single_file_preview.cpp new file mode 100644 index 000000000..43f0e0911 --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/attach/attach_single_file_preview.cpp @@ -0,0 +1,173 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/chat/attach/attach_single_file_preview.h" + +#include "ui/chat/attach/attach_prepare.h" +#include "ui/text/format_values.h" +#include "ui/text/text_options.h" +#include "ui/image/image_prepare.h" +#include "ui/cached_round_corners.h" +#include "core/mime_type.h" +#include "styles/style_chat.h" +#include "styles/style_boxes.h" + +#include + +namespace Ui { + +SingleFilePreview::SingleFilePreview( + QWidget *parent, + const PreparedFile &file) +: RpWidget(parent) { + preparePreview(file); +} + +void SingleFilePreview::prepareThumb(const QImage &preview) { + if (preview.isNull()) { + return; + } + + auto originalWidth = preview.width(); + auto originalHeight = preview.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 = PixmapFromImage(Images::prepare( + preview, + thumbWidth * style::DevicePixelRatio(), + 0, + options, + st::msgFileThumbSize, + st::msgFileThumbSize)); +} + +void SingleFilePreview::preparePreview(const PreparedFile &file) { + auto preview = QImage(); + if (const auto image = std::get_if( + &file.information->media)) { + preview = image->data; + } else if (const auto video = std::get_if( + &file.information->media)) { + preview = video->thumbnail; + } + prepareThumb(preview); + const auto filepath = file.path; + if (filepath.isEmpty()) { + //auto filename = filedialogDefaultName( + // qsl("image"), + // qsl(".png"), + // QString(), + // true); // #TODO files + auto filename = "image.png"; + _nameText.setText( + st::semiboldTextStyle, + filename, + NameTextOptions()); + _statusText = u"%1x%2"_q.arg(preview.width()).arg(preview.height()); + _statusWidth = qMax(_nameText.maxWidth(), st::normalFont->width(_statusText)); + _fileIsImage = true; + } else { + auto fileinfo = QFileInfo(filepath); + auto filename = fileinfo.fileName(); + _fileIsImage = Core::FileIsImage(filename, Core::MimeTypeForFile(fileinfo).name()); + + auto songTitle = QString(); + auto songPerformer = QString(); + if (file.information) { + if (const auto song = std::get_if( + &file.information->media)) { + songTitle = song->title; + songPerformer = song->performer; + _fileIsAudio = true; + } + } + + const auto nameString = ComposeNameString( + filename, + songTitle, + songPerformer); + _nameText.setText( + st::semiboldTextStyle, + nameString, + NameTextOptions()); + _statusText = FormatSizeText(fileinfo.size()); + _statusWidth = qMax( + _nameText.maxWidth(), + st::normalFont->width(_statusText)); + } +} + +void SingleFilePreview::paintEvent(QPaintEvent *e) { + Painter p(this); + + 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(); + + FillRoundRect(p, x, y, w, h, st::msgOutBg, MessageOutCorners, &st::msgOutShadow); + + if (_fileThumb.isNull()) { + QRect inner(style::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 = _fileIsAudio + ? st::historyFileOutPlay + : _fileIsImage + ? st::historyFileOutImage + : st::historyFileOutDocument; + icon.paintInCenter(p, inner); + } else { + QRect rthumb(style::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); +} + +rpl::producer SingleFilePreview::desiredHeightValue() const { + auto h = _fileThumb.isNull() + ? (st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom()) + : (st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom()); + return rpl::single(st::boxPhotoPadding.top() + h + st::msgShadow); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_single_file_preview.h b/Telegram/SourceFiles/ui/chat/attach/attach_single_file_preview.h new file mode 100644 index 000000000..442106148 --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/attach/attach_single_file_preview.h @@ -0,0 +1,40 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/rp_widget.h" + +namespace Ui { + +struct PreparedFile; + +class SingleFilePreview final : public RpWidget { +public: + SingleFilePreview( + QWidget *parent, + const PreparedFile &file); + + rpl::producer desiredHeightValue() const override; + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + void preparePreview(const PreparedFile &file); + void prepareThumb(const QImage &preview); + + QPixmap _fileThumb; + Text::String _nameText; + bool _fileIsAudio = false; + bool _fileIsImage = false; + QString _statusText; + int _statusWidth = 0; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/filter_icon_panel.cpp b/Telegram/SourceFiles/ui/filter_icon_panel.cpp index c4df60cf6..e9d3d8f47 100644 --- a/Telegram/SourceFiles/ui/filter_icon_panel.cpp +++ b/Telegram/SourceFiles/ui/filter_icon_panel.cpp @@ -12,9 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/panel_animation.h" #include "ui/ui_utility.h" #include "ui/filter_icons.h" +#include "ui/cached_round_corners.h" #include "lang/lang_keys.h" #include "core/application.h" -#include "app.h" #include "styles/style_chat_helpers.h" #include "styles/style_window.h" @@ -102,7 +102,7 @@ void FilterIconPanel::setupInner() { _inner->paintRequest( ) | rpl::start_with_next([=](QRect clip) { auto p = Painter(_inner); - App::roundRect( + Ui::FillRoundRect( p, _inner->rect(), st::emojiPanBg, @@ -122,11 +122,11 @@ void FilterIconPanel::setupInner() { continue; } if (i == selected) { - App::roundRect( + Ui::FillRoundRect( p, rect, st::emojiPanHover, - StickerHoverCorners); + Ui::StickerHoverCorners); } const auto icon = LookupFilterIcon(kIcons[i]).normal; icon->paintInCenter(p, rect, st::emojiIconFg->c); diff --git a/Telegram/SourceFiles/ui/grouped_layout.cpp b/Telegram/SourceFiles/ui/grouped_layout.cpp index 7b98af8b3..cefc127b5 100644 --- a/Telegram/SourceFiles/ui/grouped_layout.cpp +++ b/Telegram/SourceFiles/ui/grouped_layout.cpp @@ -444,8 +444,8 @@ std::vector ComplexLayouter::CropRatios( constexpr auto kMaxRatio = 2.75; constexpr auto kMinRatio = 0.6667; return (averageRatio > 1.1) - ? snap(ratio, 1., kMaxRatio) - : snap(ratio, kMinRatio, 1.); + ? std::clamp(ratio, 1., kMaxRatio) + : std::clamp(ratio, kMinRatio, 1.); }) | ranges::to_vector; } diff --git a/Telegram/SourceFiles/ui/text/format_values.cpp b/Telegram/SourceFiles/ui/text/format_values.cpp index 0bc069c82..60534c3ad 100644 --- a/Telegram/SourceFiles/ui/text/format_values.cpp +++ b/Telegram/SourceFiles/ui/text/format_values.cpp @@ -128,4 +128,20 @@ QString FillAmountAndCurrency(uint64 amount, const QString ¤cy) { //return currencyText + amountText; } +QString ComposeNameString( + const QString &filename, + const QString &songTitle, + const QString &songPerformer) { + if (songTitle.isEmpty() && songPerformer.isEmpty()) { + return filename.isEmpty() ? u"Unknown File"_q : filename; + } + + if (songPerformer.isEmpty()) { + return songTitle; + } + + auto trackTitle = (songTitle.isEmpty() ? u"Unknown Track"_q : songTitle); + return songPerformer + QString::fromUtf8(" \xe2\x80\x93 ") + trackTitle; +} + } // namespace Ui diff --git a/Telegram/SourceFiles/ui/text/format_values.h b/Telegram/SourceFiles/ui/text/format_values.h index 9fb6c00c8..0e0ce8680 100644 --- a/Telegram/SourceFiles/ui/text/format_values.h +++ b/Telegram/SourceFiles/ui/text/format_values.h @@ -25,4 +25,9 @@ inline constexpr auto FileStatusSizeFailed = 0x7FFFFFF2; uint64 amount, const QString ¤cy); +[[nodiscard]] QString ComposeNameString( + const QString &filename, + const QString &songTitle, + const QString &songPerformer); + } // namespace Ui diff --git a/Telegram/SourceFiles/window/themes/window_theme.h b/Telegram/SourceFiles/window/themes/window_theme.h index ec06f9eb9..0d38ecf14 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.h +++ b/Telegram/SourceFiles/window/themes/window_theme.h @@ -118,7 +118,8 @@ struct BackgroundUpdate { [[nodiscard]] bool paletteChanged() const { return (type == Type::TestingTheme) || (type == Type::RevertingTheme) - || (type == Type::ApplyingEdit); + || (type == Type::ApplyingEdit) + || (type == Type::New); } Type type; bool tiled; diff --git a/Telegram/SourceFiles/window/themes/window_theme_warning.cpp b/Telegram/SourceFiles/window/themes/window_theme_warning.cpp index 09929d22a..13d86a951 100644 --- a/Telegram/SourceFiles/window/themes/window_theme_warning.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme_warning.cpp @@ -10,9 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" #include "ui/ui_utility.h" +#include "ui/cached_round_corners.h" #include "window/themes/window_theme.h" #include "lang/lang_keys.h" -#include "app.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" @@ -62,7 +62,7 @@ void WarningWidget::paintEvent(QPaintEvent *e) { } Ui::Shadow::paint(p, _inner, width(), st::boxRoundShadow); - App::roundRect(p, _inner, st::boxBg, BoxCorners); + Ui::FillRoundRect(p, _inner, st::boxBg, Ui::BoxCorners); p.setFont(st::boxTitleFont); p.setPen(st::boxTitleFg); diff --git a/Telegram/SourceFiles/window/window_history_hider.cpp b/Telegram/SourceFiles/window/window_history_hider.cpp index df519d3f7..442e6a156 100644 --- a/Telegram/SourceFiles/window/window_history_hider.cpp +++ b/Telegram/SourceFiles/window/window_history_hider.cpp @@ -10,9 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" +#include "ui/cached_round_corners.h" #include "mainwidget.h" #include "facades.h" -#include "app.h" #include "styles/style_layers.h" #include "styles/style_chat.h" @@ -57,7 +57,7 @@ void HistoryHider::paintEvent(QPaintEvent *e) { p.setFont(st::historyForwardChooseFont); auto w = st::historyForwardChooseMargins.left() + _chooseWidth + st::historyForwardChooseMargins.right(); auto h = st::historyForwardChooseMargins.top() + st::historyForwardChooseFont->height + st::historyForwardChooseMargins.bottom(); - App::roundRect(p, (width() - w) / 2, (height() - h) / 2, w, h, st::historyForwardChooseBg, ForwardCorners); + Ui::FillRoundRect(p, (width() - w) / 2, (height() - h) / 2, w, h, st::historyForwardChooseBg, Ui::ForwardCorners); p.setPen(st::historyForwardChooseFg); p.drawText(_box, _text, QTextOption(style::al_center)); diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index b1feecb12..12d26d2da 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -9,11 +9,25 @@ init_target(td_ui) add_library(tdesktop::td_ui ALIAS td_ui) include(lib_ui/cmake/generate_styles.cmake) +include(cmake/generate_numbers.cmake) set(style_files ui/td_common.style + ui/filter_icons.style ui/chat/chat.style + boxes/boxes.style dialogs/dialogs.style + chat_helpers/chat_helpers.style + calls/calls.style + export/view/export.style + info/info.style + intro/intro.style + media/player/media_player.style + passport/passport.style + profile/profile.style + settings/settings.style + media/view/media_view.style + overview/overview.style window/window.style ) @@ -25,14 +39,27 @@ set(dependent_style_files ) generate_styles(td_ui ${src_loc} "${style_files}" "${dependent_style_files}") +generate_numbers(td_ui ${res_loc}/numbers.txt) target_precompile_headers(td_ui PRIVATE ${src_loc}/ui/ui_pch.h) nice_target_sources(td_ui ${src_loc} PRIVATE ${style_files} + core/mime_type.cpp + core/mime_type.h + + ui/chat/attach/attach_album_thumbnail.cpp + ui/chat/attach/attach_album_thumbnail.h + ui/chat/attach/attach_album_preview.cpp + ui/chat/attach/attach_album_preview.h + ui/chat/attach/attach_common.h ui/chat/attach/attach_extensions.cpp ui/chat/attach/attach_extensions.h + ui/chat/attach/attach_prepare.cpp + ui/chat/attach/attach_prepare.h + ui/chat/attach/attach_single_file_preview.cpp + ui/chat/attach/attach_single_file_preview.h ui/chat/message_bar.cpp ui/chat/message_bar.h ui/chat/pinned_bar.cpp @@ -47,7 +74,11 @@ PRIVATE ui/text/text_options.h ui/toasts/common_toasts.cpp ui/toasts/common_toasts.h - + ui/cached_round_corners.cpp + ui/cached_round_corners.h + ui/grouped_layout.cpp + ui/grouped_layout.h + ui/ui_pch.h ) From 8b96f4c214d119b1665d9c66083512385d243ac8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 13 Oct 2020 19:43:18 +0300 Subject: [PATCH 090/190] Move Media::Clip::Reader and FileLocation to td_ui. --- Telegram/CMakeLists.txt | 8 - .../chat_helpers/stickers_dice_pack.cpp | 2 +- Telegram/SourceFiles/config.h | 5 - Telegram/SourceFiles/core/file_location.cpp | 121 +++++++++++ Telegram/SourceFiles/core/file_location.h | 72 +++++++ Telegram/SourceFiles/data/data_document.cpp | 18 +- Telegram/SourceFiles/data/data_document.h | 7 +- .../SourceFiles/data/data_document_media.cpp | 6 +- Telegram/SourceFiles/ffmpeg/ffmpeg_utility.h | 1 + .../inline_bot_layout_internal.cpp | 7 +- .../SourceFiles/media/audio/media_audio.cpp | 10 +- .../SourceFiles/media/audio/media_audio.h | 5 +- .../media/audio/media_audio_capture.cpp | 5 +- .../media/audio/media_audio_ffmpeg_loader.cpp | 16 +- .../media/audio/media_audio_ffmpeg_loader.h | 10 +- .../media/audio/media_audio_loader.cpp | 4 +- .../media/audio/media_audio_loader.h | 7 +- .../media/audio/media_audio_track.cpp | 5 +- .../media/audio/media_audio_track.h | 6 +- .../media/audio/media_child_ffmpeg_loader.cpp | 3 +- .../media/audio/media_child_ffmpeg_loader.h | 2 +- .../media/clip/media_clip_check_streaming.cpp | 8 +- .../media/clip/media_clip_check_streaming.h | 6 +- .../media/clip/media_clip_ffmpeg.cpp | 146 +++----------- .../media/clip/media_clip_ffmpeg.h | 28 ++- .../media/clip/media_clip_implementation.cpp | 2 + .../media/clip/media_clip_implementation.h | 14 +- .../media/clip/media_clip_reader.cpp | 189 +++++++----------- .../media/clip/media_clip_reader.h | 56 ++---- .../platform/linux/specific_linux.h | 24 --- .../platform/mac/file_bookmark_mac.h | 33 +++ .../platform/mac/file_bookmark_mac.mm | 125 ++++++++++++ .../SourceFiles/platform/mac/specific_mac.h | 25 --- .../SourceFiles/platform/mac/specific_mac.mm | 4 - .../SourceFiles/platform/mac/specific_mac_p.h | 21 -- .../platform/mac/specific_mac_p.mm | 111 ---------- .../platform/platform_file_bookmark.h | 43 ++++ .../SourceFiles/platform/win/specific_win.h | 24 --- .../SourceFiles/storage/file_download.cpp | 3 +- Telegram/SourceFiles/storage/file_upload.cpp | 5 +- Telegram/SourceFiles/storage/localstorage.cpp | 5 +- .../SourceFiles/storage/storage_account.cpp | 9 +- .../SourceFiles/storage/storage_account.h | 11 +- .../SourceFiles/ui/image/image_location.cpp | 100 --------- .../SourceFiles/ui/image/image_location.h | 52 ----- .../window/window_media_preview.cpp | 4 +- Telegram/cmake/td_ui.cmake | 16 ++ 47 files changed, 637 insertions(+), 747 deletions(-) create mode 100644 Telegram/SourceFiles/core/file_location.cpp create mode 100644 Telegram/SourceFiles/core/file_location.h create mode 100644 Telegram/SourceFiles/platform/mac/file_bookmark_mac.h create mode 100644 Telegram/SourceFiles/platform/mac/file_bookmark_mac.mm create mode 100644 Telegram/SourceFiles/platform/platform_file_bookmark.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index d6fdb2a4d..94741dda3 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -692,14 +692,6 @@ PRIVATE media/audio/media_child_ffmpeg_loader.h media/audio/media_openal_functions.cpp media/audio/media_openal_functions.h - media/clip/media_clip_check_streaming.cpp - media/clip/media_clip_check_streaming.h - media/clip/media_clip_ffmpeg.cpp - media/clip/media_clip_ffmpeg.h - media/clip/media_clip_implementation.cpp - media/clip/media_clip_implementation.h - media/clip/media_clip_reader.cpp - media/clip/media_clip_reader.h media/player/media_player_button.cpp media/player/media_player_button.h media/player/media_player_float.cpp diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp index baca58e7b..083778c9e 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp @@ -110,7 +110,7 @@ void DicePack::tryGenerateLocalZero() { const auto document = _session->data().processDocument( result->document, Images::FromImageInMemory(result->thumb, "PNG")); - document->setLocation(FileLocation(path)); + document->setLocation(Core::FileLocation(path)); _map.emplace(0, document); diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index 5bc8f9c47..f9b9f496d 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -21,13 +21,8 @@ enum { LocalEncryptSaltSize = 32, // 256 bit AnimationTimerDelta = 7, - ClipThreadsCount = 8, - AverageGifSize = 320 * 240, - WaitBeforeGifPause = 200, // wait 200ms for gif draw before pausing it RecentInlineBotsLimit = 10, - AVBlockSize = 4096, // 4Kb for ffmpeg blocksize - AutoSearchTimeout = 900, // 0.9 secs SearchPerPage = 50, SearchManyPerPage = 100, diff --git a/Telegram/SourceFiles/core/file_location.cpp b/Telegram/SourceFiles/core/file_location.cpp new file mode 100644 index 000000000..641bf0ea0 --- /dev/null +++ b/Telegram/SourceFiles/core/file_location.cpp @@ -0,0 +1,121 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "core/file_location.h" + +#include "platform/platform_file_bookmark.h" +#include "logs.h" + +#include + +namespace Core { +namespace { + +const auto kInMediaCacheLocation = u"*media_cache*"_q; + +} // namespace + +ReadAccessEnabler::ReadAccessEnabler(const Platform::FileBookmark *bookmark) +: _bookmark(bookmark) +, _failed(_bookmark ? !_bookmark->enable() : false) { +} + +ReadAccessEnabler::ReadAccessEnabler( + const std::shared_ptr &bookmark) +: _bookmark(bookmark.get()) +, _failed(_bookmark ? !_bookmark->enable() : false) { +} + +ReadAccessEnabler::~ReadAccessEnabler() { + if (_bookmark && !_failed) _bookmark->disable(); +} + +FileLocation::FileLocation(const QString &name) : fname(name) { + if (fname.isEmpty() || fname == kInMediaCacheLocation) { + size = 0; + } else { + setBookmark(Platform::PathBookmark(name)); + + QFileInfo f(name); + if (f.exists()) { + qint64 s = f.size(); + if (s > INT_MAX) { + fname = QString(); + _bookmark = nullptr; + size = 0; + } else { + modified = f.lastModified(); + size = qint32(s); + } + } else { + fname = QString(); + _bookmark = nullptr; + size = 0; + } + } +} + +FileLocation FileLocation::InMediaCacheLocation() { + return FileLocation(kInMediaCacheLocation); +} + +bool FileLocation::check() const { + if (fname.isEmpty() || fname == kInMediaCacheLocation) { + return false; + } + + ReadAccessEnabler enabler(_bookmark); + if (enabler.failed()) { + const_cast(this)->_bookmark = nullptr; + } + + QFileInfo f(name()); + if (!f.isReadable()) return false; + + quint64 s = f.size(); + if (s > INT_MAX) { + DEBUG_LOG(("File location check: Wrong size %1").arg(s)); + return false; + } + + if (qint32(s) != size) { + DEBUG_LOG(("File location check: Wrong size %1 when should be %2").arg(s).arg(size)); + return false; + } + auto realModified = f.lastModified(); + if (realModified != modified) { + DEBUG_LOG(("File location check: Wrong last modified time %1 when should be %2").arg(realModified.toMSecsSinceEpoch()).arg(modified.toMSecsSinceEpoch())); + return false; + } + return true; +} + +const QString &FileLocation::name() const { + return _bookmark ? _bookmark->name(fname) : fname; +} + +QByteArray FileLocation::bookmark() const { + return _bookmark ? _bookmark->bookmark() : QByteArray(); +} + +bool FileLocation::inMediaCache() const { + return (fname == kInMediaCacheLocation); +} + +void FileLocation::setBookmark(const QByteArray &bm) { + _bookmark.reset(bm.isEmpty() ? nullptr : new Platform::FileBookmark(bm)); +} + +bool FileLocation::accessEnable() const { + return isEmpty() ? false : (_bookmark ? _bookmark->enable() : true); +} + +void FileLocation::accessDisable() const { + return _bookmark ? _bookmark->disable() : (void)0; +} + +} // namespace Core diff --git a/Telegram/SourceFiles/core/file_location.h b/Telegram/SourceFiles/core/file_location.h new file mode 100644 index 000000000..16db6fce3 --- /dev/null +++ b/Telegram/SourceFiles/core/file_location.h @@ -0,0 +1,72 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include + +namespace Platform { +class FileBookmark; +} // namespace Platform + +namespace Core { + +class ReadAccessEnabler { +public: + ReadAccessEnabler(const Platform::FileBookmark *bookmark); + ReadAccessEnabler( + const std::shared_ptr &bookmark); + bool failed() const { + return _failed; + } + ~ReadAccessEnabler(); + +private: + const Platform::FileBookmark *_bookmark = nullptr; + bool _failed; + +}; + +class FileLocation { +public: + FileLocation() = default; + explicit FileLocation(const QString &name); + + static FileLocation InMediaCacheLocation(); + + [[nodiscard]] bool check() const; + [[nodiscard]] const QString &name() const; + void setBookmark(const QByteArray &bookmark); + QByteArray bookmark() const; + [[nodiscard]] bool isEmpty() const { + return name().isEmpty(); + } + [[nodiscard]] bool inMediaCache() const; + + bool accessEnable() const; + void accessDisable() const; + + QString fname; + QDateTime modified; + qint32 size; + +private: + std::shared_ptr _bookmark; + +}; + +inline bool operator==(const FileLocation &a, const FileLocation &b) { + return (a.name() == b.name()) + && (a.modified == b.modified) + && (a.size == b.size); +} + +inline bool operator!=(const FileLocation &a, const FileLocation &b) { + return !(a == b); +} + +} // namespace Core diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index 9d9dbf3e1..a4784561b 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -849,7 +849,7 @@ void DocumentData::finishLoad() { _flags |= Flag::DownloadCancelled; return; } - setLocation(FileLocation(_loader->fileName())); + setLocation(Core::FileLocation(_loader->fileName())); setGoodThumbnailDataReady(); if (const auto media = activeMediaView()) { media->setBytes(_loader->bytes()); @@ -917,7 +917,7 @@ void DocumentData::setLoadedInMediaCache(bool loaded) { if (loadedInMediaCache()) { session().local().writeFileLocation( mediaKey(), - FileLocation::InMediaCacheLocation()); + Core::FileLocation::InMediaCacheLocation()); } else { session().local().removeFileLocation(mediaKey()); } @@ -926,7 +926,7 @@ void DocumentData::setLoadedInMediaCache(bool loaded) { } void DocumentData::setLoadedInMediaCacheLocation() { - _location = FileLocation(); + _location = Core::FileLocation(); _flags |= Flag::LoadedInMediaCache; } @@ -954,10 +954,10 @@ void DocumentData::save( f.write(media->bytes()); f.close(); - setLocation(FileLocation(toFile)); + setLocation(Core::FileLocation(toFile)); session().local().writeFileLocation( mediaKey(), - FileLocation(toFile)); + Core::FileLocation(toFile)); } else if (l.accessEnable()) { const auto &alreadyName = l.name(); if (alreadyName != toFile) { @@ -1151,7 +1151,7 @@ QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform) { return result; } -const FileLocation &DocumentData::location(bool check) const { +const Core::FileLocation &DocumentData::location(bool check) const { if (check && !_location.check()) { const auto location = session().local().readFileLocation(mediaKey()); const auto that = const_cast(this); @@ -1164,7 +1164,7 @@ const FileLocation &DocumentData::location(bool check) const { return _location; } -void DocumentData::setLocation(const FileLocation &loc) { +void DocumentData::setLocation(const Core::FileLocation &loc) { if (loc.inMediaCache()) { setLoadedInMediaCacheLocation(); } else if (loc.check()) { @@ -1207,7 +1207,7 @@ bool DocumentData::saveFromDataChecked() { return false; } file.close(); - _location = FileLocation(path); + _location = Core::FileLocation(path); session().local().writeFileLocation(mediaKey(), _location); return true; } @@ -1585,7 +1585,7 @@ void DocumentData::setRemoteLocation( } else if (_location.isEmpty() && loadedInMediaCache()) { session().local().writeFileLocation( mediaKey(), - FileLocation::InMediaCacheLocation()); + Core::FileLocation::InMediaCacheLocation()); } } } diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index a31dd6370..35b998d12 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/binary_guard.h" #include "data/data_types.h" #include "data/data_cloud_file.h" +#include "core/file_location.h" #include "ui/image/image.h" class mtpFileLoader; @@ -114,8 +115,8 @@ public: void setWaitingForAlbum(); [[nodiscard]] bool waitingForAlbum() const; - [[nodiscard]] const FileLocation &location(bool check = false) const; - void setLocation(const FileLocation &loc); + [[nodiscard]] const Core::FileLocation &location(bool check = false) const; + void setLocation(const Core::FileLocation &loc); bool saveFromData(); bool saveFromDataSilent(); @@ -315,7 +316,7 @@ private: std::weak_ptr _media; PhotoData *_goodThumbnailPhoto = nullptr; - FileLocation _location; + Core::FileLocation _location; std::unique_ptr _additional; int32 _duration = -1; mutable Flags _flags = kStreamingSupportedUnknown; diff --git a/Telegram/SourceFiles/data/data_document_media.cpp b/Telegram/SourceFiles/data/data_document_media.cpp index 28916e511..4a1267e6e 100644 --- a/Telegram/SourceFiles/data/data_document_media.cpp +++ b/Telegram/SourceFiles/data/data_document_media.cpp @@ -111,8 +111,8 @@ void VideoPreviewState::automaticLoad(Data::FileOrigin origin) const { _media->videoThumbnailContent(), std::move(callback)) : ::Media::Clip::MakeReader( - _media, - FullMsgId(), + _media->owner()->location(), + _media->bytes(), std::move(callback)); } @@ -386,7 +386,7 @@ void DocumentMedia::GenerateGoodThumbnail( : FileType::Video; auto location = document->location().isEmpty() ? nullptr - : std::make_unique(document->location()); + : std::make_unique(document->location()); if (data.isEmpty() && !location) { document->setGoodThumbnailChecked(false); return; diff --git a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.h b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.h index de7db7a21..4be81913c 100644 --- a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.h +++ b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.h @@ -25,6 +25,7 @@ class QImage; namespace FFmpeg { inline constexpr auto kPixelBytesSize = 4; +inline constexpr auto kAVBlockSize = 4096; // 4Kb for ffmpeg blocksize constexpr auto kUniversalTimeBase = AVRational{ 1, AV_TIME_BASE }; constexpr auto kNormalAspect = AVRational{ 1, 1 }; diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index 4810cbc4a..c36ac5937 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -1373,9 +1373,10 @@ void Game::paint(Painter &p, const QRect &clip, const PaintContext *context) con bool loaded = _documentMedia->loaded(), loading = document->loading(), displayLoading = document->displayLoading(); if (loaded && !_gif && !_gif.isBad()) { auto that = const_cast(this); - that->_gif = Media::Clip::MakeReader(_documentMedia.get(), FullMsgId(), [that](Media::Clip::Notification notification) { - that->clipCallback(notification); - }); + that->_gif = Media::Clip::MakeReader( + _documentMedia->owner()->location(), + _documentMedia->bytes(), + [=](Media::Clip::Notification notification) { that->clipCallback(notification); }); } bool animating = (_gif && _gif->started()); diff --git a/Telegram/SourceFiles/media/audio/media_audio.cpp b/Telegram/SourceFiles/media/audio/media_audio.cpp index e429ee774..02dcffd79 100644 --- a/Telegram/SourceFiles/media/audio/media_audio.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio.cpp @@ -430,7 +430,7 @@ void Mixer::Track::clear() { detach(); state = TrackState(); - file = FileLocation(); + file = Core::FileLocation(); data = QByteArray(); bufferedPosition = 0; bufferedLength = 0; @@ -1519,7 +1519,7 @@ void DetachFromDevice(not_null instance) { class FFMpegAttributesReader : public AbstractFFMpegLoader { public: - FFMpegAttributesReader(const FileLocation &file, const QByteArray &data) + FFMpegAttributesReader(const Core::FileLocation &file, const QByteArray &data) : AbstractFFMpegLoader(file, data, bytes::vector()) { } @@ -1632,7 +1632,7 @@ namespace Player { Ui::PreparedFileInformation::Song PrepareForSending(const QString &fname, const QByteArray &data) { auto result = Ui::PreparedFileInformation::Song(); - FFMpegAttributesReader reader(FileLocation(fname), data); + FFMpegAttributesReader reader(Core::FileLocation(fname), data); const auto positionMs = crl::time(0); if (reader.open(positionMs) && reader.samplesCount() > 0) { result.duration = reader.samplesCount() / reader.samplesFrequency(); @@ -1647,7 +1647,7 @@ Ui::PreparedFileInformation::Song PrepareForSending(const QString &fname, const class FFMpegWaveformCounter : public FFMpegLoader { public: - FFMpegWaveformCounter(const FileLocation &file, const QByteArray &data) : FFMpegLoader(file, data, bytes::vector()) { + FFMpegWaveformCounter(const Core::FileLocation &file, const QByteArray &data) : FFMpegLoader(file, data, bytes::vector()) { } bool open(crl::time positionMs) override { @@ -1732,7 +1732,7 @@ private: } // namespace Media VoiceWaveform audioCountWaveform( - const FileLocation &file, + const Core::FileLocation &file, const QByteArray &data) { Media::FFMpegWaveformCounter counter(file, data); const auto positionMs = crl::time(0); diff --git a/Telegram/SourceFiles/media/audio/media_audio.h b/Telegram/SourceFiles/media/audio/media_audio.h index 962017073..890570cdb 100644 --- a/Telegram/SourceFiles/media/audio/media_audio.h +++ b/Telegram/SourceFiles/media/audio/media_audio.h @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/animation_value.h" #include "ui/chat/attach/attach_prepare.h" +#include "core/file_location.h" #include "base/bytes.h" #include @@ -221,7 +222,7 @@ private: TrackState state; - FileLocation file; + Core::FileLocation file; QByteArray data; int64 bufferedPosition = 0; int64 bufferedLength = 0; @@ -368,7 +369,7 @@ bool audioCheckError(); } // namespace Player } // namespace Media -VoiceWaveform audioCountWaveform(const FileLocation &file, const QByteArray &data); +VoiceWaveform audioCountWaveform(const Core::FileLocation &file, const QByteArray &data); namespace Media { namespace Audio { diff --git a/Telegram/SourceFiles/media/audio/media_audio_capture.cpp b/Telegram/SourceFiles/media/audio/media_audio_capture.cpp index 224bd7ffb..580a8f8bc 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_capture.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio_capture.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/audio/media_audio_capture.h" #include "media/audio/media_audio_ffmpeg_loader.h" +#include "ffmpeg/ffmpeg_utility.h" #include "base/timer.h" #include @@ -247,9 +248,9 @@ void Instance::Inner::start(Fn updated, Fn error) { // Create encoding context - d->ioBuffer = (uchar*)av_malloc(AVBlockSize); + d->ioBuffer = (uchar*)av_malloc(FFmpeg::kAVBlockSize); - d->ioContext = avio_alloc_context(d->ioBuffer, AVBlockSize, 1, static_cast(d.get()), &Private::_read_data, &Private::_write_data, &Private::_seek_data); + d->ioContext = avio_alloc_context(d->ioBuffer, FFmpeg::kAVBlockSize, 1, static_cast(d.get()), &Private::_read_data, &Private::_write_data, &Private::_seek_data); int res = 0; char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; AVOutputFormat *fmt = 0; diff --git a/Telegram/SourceFiles/media/audio/media_audio_ffmpeg_loader.cpp b/Telegram/SourceFiles/media/audio/media_audio_ffmpeg_loader.cpp index 258e46a41..24c8bac2a 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_ffmpeg_loader.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio_ffmpeg_loader.cpp @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/audio/media_audio_ffmpeg_loader.h" +#include "core/file_location.h" +#include "ffmpeg/ffmpeg_utility.h" #include "base/bytes.h" namespace Media { @@ -34,13 +36,13 @@ bool AbstractFFMpegLoader::open(crl::time positionMs) { int res = 0; char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - ioBuffer = (uchar *)av_malloc(AVBlockSize); + ioBuffer = (uchar *)av_malloc(FFmpeg::kAVBlockSize); if (!_data.isEmpty()) { - ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, reinterpret_cast(this), &AbstractFFMpegLoader::_read_data, 0, &AbstractFFMpegLoader::_seek_data); + ioContext = avio_alloc_context(ioBuffer, FFmpeg::kAVBlockSize, 0, reinterpret_cast(this), &AbstractFFMpegLoader::_read_data, 0, &AbstractFFMpegLoader::_seek_data); } else if (!_bytes.empty()) { - ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, reinterpret_cast(this), &AbstractFFMpegLoader::_read_bytes, 0, &AbstractFFMpegLoader::_seek_bytes); + ioContext = avio_alloc_context(ioBuffer, FFmpeg::kAVBlockSize, 0, reinterpret_cast(this), &AbstractFFMpegLoader::_read_bytes, 0, &AbstractFFMpegLoader::_seek_bytes); } else { - ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, reinterpret_cast(this), &AbstractFFMpegLoader::_read_file, 0, &AbstractFFMpegLoader::_seek_file); + ioContext = avio_alloc_context(ioBuffer, FFmpeg::kAVBlockSize, 0, reinterpret_cast(this), &AbstractFFMpegLoader::_read_file, 0, &AbstractFFMpegLoader::_seek_file); } fmtContext = avformat_alloc_context(); if (!fmtContext) { @@ -187,7 +189,7 @@ int64_t AbstractFFMpegLoader::_seek_file(void *opaque, int64_t offset, int whenc } AbstractAudioFFMpegLoader::AbstractAudioFFMpegLoader( - const FileLocation &file, + const Core::FileLocation &file, const QByteArray &data, bytes::vector &&buffer) : AbstractFFMpegLoader(file, data, std::move(buffer)) @@ -389,7 +391,7 @@ bool AbstractAudioFFMpegLoader::ensureResampleSpaceAvailable(int samples) { return true; } const auto allocate = std::max(samples, int(av_rescale_rnd( - AVBlockSize / _outputSampleSize, + FFmpeg::kAVBlockSize / _outputSampleSize, _swrDstRate, _swrSrcRate, AV_ROUND_UP))); @@ -501,7 +503,7 @@ AbstractAudioFFMpegLoader::~AbstractAudioFFMpegLoader() { } FFMpegLoader::FFMpegLoader( - const FileLocation & file, + const Core::FileLocation & file, const QByteArray & data, bytes::vector && buffer) : AbstractAudioFFMpegLoader(file, data, std::move(buffer)) { diff --git a/Telegram/SourceFiles/media/audio/media_audio_ffmpeg_loader.h b/Telegram/SourceFiles/media/audio/media_audio_ffmpeg_loader.h index c0c5f32ca..83de13bad 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_ffmpeg_loader.h +++ b/Telegram/SourceFiles/media/audio/media_audio_ffmpeg_loader.h @@ -20,12 +20,16 @@ extern "C" { #include +namespace Core { +class FileLocation; +} // namespace Core + namespace Media { class AbstractFFMpegLoader : public AudioPlayerLoader { public: AbstractFFMpegLoader( - const FileLocation &file, + const Core::FileLocation &file, const QByteArray &data, bytes::vector &&buffer) : AudioPlayerLoader(file, data, std::move(buffer)) { @@ -74,7 +78,7 @@ private: class AbstractAudioFFMpegLoader : public AbstractFFMpegLoader { public: AbstractAudioFFMpegLoader( - const FileLocation &file, + const Core::FileLocation &file, const QByteArray &data, bytes::vector &&buffer); @@ -149,7 +153,7 @@ private: class FFMpegLoader : public AbstractAudioFFMpegLoader { public: FFMpegLoader( - const FileLocation &file, + const Core::FileLocation &file, const QByteArray &data, bytes::vector &&buffer); diff --git a/Telegram/SourceFiles/media/audio/media_audio_loader.cpp b/Telegram/SourceFiles/media/audio/media_audio_loader.cpp index a71f66835..7871f7195 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_loader.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio_loader.cpp @@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Media { AudioPlayerLoader::AudioPlayerLoader( - const FileLocation &file, + const Core::FileLocation &file, const QByteArray &data, bytes::vector &&buffer) : _file(file) @@ -26,7 +26,7 @@ AudioPlayerLoader::~AudioPlayerLoader() { } bool AudioPlayerLoader::check( - const FileLocation &file, + const Core::FileLocation &file, const QByteArray &data) { return (this->_file == file) && (this->_data.size() == data.size()); } diff --git a/Telegram/SourceFiles/media/audio/media_audio_loader.h b/Telegram/SourceFiles/media/audio/media_audio_loader.h index cdf51ca8d..399d85f71 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_loader.h +++ b/Telegram/SourceFiles/media/audio/media_audio_loader.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "base/bytes.h" +#include "core/file_location.h" #include "media/streaming/media_streaming_utility.h" namespace Media { @@ -15,12 +16,12 @@ namespace Media { class AudioPlayerLoader { public: AudioPlayerLoader( - const FileLocation &file, + const Core::FileLocation &file, const QByteArray &data, bytes::vector &&buffer); virtual ~AudioPlayerLoader(); - virtual bool check(const FileLocation &file, const QByteArray &data); + virtual bool check(const Core::FileLocation &file, const QByteArray &data); virtual bool open(crl::time positionMs) = 0; virtual int64 samplesCount() = 0; @@ -56,7 +57,7 @@ public: bool holdsSavedDecodedSamples() const; protected: - FileLocation _file; + Core::FileLocation _file; bool _access = false; QByteArray _data; bytes::vector _bytes; diff --git a/Telegram/SourceFiles/media/audio/media_audio_track.cpp b/Telegram/SourceFiles/media/audio/media_audio_track.cpp index 6e5e56c45..034aff421 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_track.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio_track.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/audio/media_audio_ffmpeg_loader.h" #include "media/audio/media_audio.h" #include "core/application.h" +#include "core/file_location.h" #include #include @@ -49,7 +50,7 @@ void Track::samplePeakEach(crl::time peakDuration) { } void Track::fillFromData(bytes::vector &&data) { - FFMpegLoader loader(FileLocation(), QByteArray(), std::move(data)); + FFMpegLoader loader(Core::FileLocation(), QByteArray(), std::move(data)); auto position = qint64(0); if (!loader.open(position)) { @@ -110,7 +111,7 @@ void Track::fillFromData(bytes::vector &&data) { _lengthMs = (loader.samplesCount() * crl::time(1000)) / _sampleRate; } -void Track::fillFromFile(const FileLocation &location) { +void Track::fillFromFile(const Core::FileLocation &location) { if (location.accessEnable()) { fillFromFile(location.name()); location.accessDisable(); diff --git a/Telegram/SourceFiles/media/audio/media_audio_track.h b/Telegram/SourceFiles/media/audio/media_audio_track.h index 81656d190..b24b50b28 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_track.h +++ b/Telegram/SourceFiles/media/audio/media_audio_track.h @@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/timer.h" #include "base/bytes.h" +namespace Core { +class FileLocation; +} // namespace Core + namespace Media { namespace Audio { @@ -22,7 +26,7 @@ public: void samplePeakEach(crl::time peakDuration); void fillFromData(bytes::vector &&data); - void fillFromFile(const FileLocation &location); + void fillFromFile(const Core::FileLocation &location); void fillFromFile(const QString &filePath); void playOnce() { diff --git a/Telegram/SourceFiles/media/audio/media_child_ffmpeg_loader.cpp b/Telegram/SourceFiles/media/audio/media_child_ffmpeg_loader.cpp index e50ae0ad2..179a53589 100644 --- a/Telegram/SourceFiles/media/audio/media_child_ffmpeg_loader.cpp +++ b/Telegram/SourceFiles/media/audio/media_child_ffmpeg_loader.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/audio/media_child_ffmpeg_loader.h" #include "core/crash_reports.h" +#include "core/file_location.h" namespace Media { namespace { @@ -30,7 +31,7 @@ bool IsPlanarFormat(int format) { ChildFFMpegLoader::ChildFFMpegLoader( std::unique_ptr &&data) : AbstractAudioFFMpegLoader( - FileLocation(), + Core::FileLocation(), QByteArray(), bytes::vector()) , _parentData(std::move(data)) { diff --git a/Telegram/SourceFiles/media/audio/media_child_ffmpeg_loader.h b/Telegram/SourceFiles/media/audio/media_child_ffmpeg_loader.h index 95fb9a320..a68dd5529 100644 --- a/Telegram/SourceFiles/media/audio/media_child_ffmpeg_loader.h +++ b/Telegram/SourceFiles/media/audio/media_child_ffmpeg_loader.h @@ -31,7 +31,7 @@ public: bool open(crl::time positionMs) override; - bool check(const FileLocation &file, const QByteArray &data) override { + bool check(const Core::FileLocation &file, const QByteArray &data) override { return true; } diff --git a/Telegram/SourceFiles/media/clip/media_clip_check_streaming.cpp b/Telegram/SourceFiles/media/clip/media_clip_check_streaming.cpp index 38e659de5..3e37dc7cf 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_check_streaming.cpp +++ b/Telegram/SourceFiles/media/clip/media_clip_check_streaming.cpp @@ -7,8 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/clip/media_clip_check_streaming.h" -#include +#include "core/file_location.h" +#include "base/bytes.h" +#include "logs.h" + #include +#include namespace Media { namespace Clip { @@ -33,7 +37,7 @@ bool IsAtom(bytes::const_span header, const char (&atom)[5]) { } // namespace bool CheckStreamingSupport( - const FileLocation &location, + const Core::FileLocation &location, QByteArray data) { QBuffer buffer; QFile file; diff --git a/Telegram/SourceFiles/media/clip/media_clip_check_streaming.h b/Telegram/SourceFiles/media/clip/media_clip_check_streaming.h index c2071e89f..093a456cb 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_check_streaming.h +++ b/Telegram/SourceFiles/media/clip/media_clip_check_streaming.h @@ -7,11 +7,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +namespace Core { +class FileLocation; +} // namespace Core + namespace Media { namespace Clip { bool CheckStreamingSupport( - const FileLocation &location, + const Core::FileLocation &location, QByteArray data); } // namespace Clip diff --git a/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp index 2053da32d..15afec3ec 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp @@ -7,9 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/clip/media_clip_ffmpeg.h" -#include "media/audio/media_audio.h" -#include "media/audio/media_child_ffmpeg_loader.h" -#include "storage/file_download.h" +#include "core/file_location.h" +#include "logs.h" namespace Media { namespace Clip { @@ -49,12 +48,10 @@ bool isAlignedImage(const QImage &image) { } // namespace FFMpegReaderImplementation::FFMpegReaderImplementation( - FileLocation *location, - QByteArray *data, - const AudioMsgId &audio) + Core::FileLocation *location, + QByteArray *data) : ReaderImplementation(location, data) -, _frame(FFmpeg::MakeFramePointer()) -, _audioMsgId(audio) { +, _frame(FFmpeg::MakeFramePointer()) { } ReaderImplementation::ReadResult FFMpegReaderImplementation::readNextFrame() { @@ -73,9 +70,6 @@ ReaderImplementation::ReadResult FFMpegReaderImplementation::readNextFrame() { if (res == AVERROR_EOF) { _packetQueue.clear(); - if (_mode == Mode::Normal) { - return ReadResult::EndOfFile; - } if (!_hadFrame) { LOG(("Gif Error: Got EOF before a single frame was read!")); return ReadResult::Error; @@ -171,44 +165,18 @@ void FFMpegReaderImplementation::processReadFrame() { } ReaderImplementation::ReadResult FFMpegReaderImplementation::readFramesTill(crl::time frameMs, crl::time systemMs) { - if (_audioStreamId < 0) { // just keep up - if (_frameRead && _frameTime > frameMs) { - return ReadResult::Success; - } - auto readResult = readNextFrame(); - if (readResult != ReadResult::Success || _frameTime > frameMs) { - return readResult; - } - readResult = readNextFrame(); - if (_frameTime <= frameMs) { - _frameTime = frameMs + 5; // keep up - } + if (_frameRead && _frameTime > frameMs) { + return ReadResult::Success; + } + auto readResult = readNextFrame(); + if (readResult != ReadResult::Success || _frameTime > frameMs) { return readResult; } - - // sync by audio stream - auto correctMs = (frameMs >= 0) - ? Player::mixer()->getExternalCorrectedTime( - _audioMsgId, - frameMs, - systemMs) - : frameMs; - if (!_frameRead) { - auto readResult = readNextFrame(); - if (readResult != ReadResult::Success) { - return readResult; - } + readResult = readNextFrame(); + if (_frameTime <= frameMs) { + _frameTime = frameMs + 5; // keep up } - while (_frameTime <= correctMs) { - auto readResult = readNextFrame(); - if (readResult != ReadResult::Success) { - return readResult; - } - } - if (frameMs >= 0) { - _frameTimeCorrection = frameMs - correctMs; - } - return ReadResult::Success; + return readResult; } crl::time FFMpegReaderImplementation::frameRealTime() const { @@ -273,17 +241,6 @@ bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const Q to = to.transformed(rotationTransform); } - // Read some future packets for audio stream. - if (_audioStreamId >= 0) { - while (_frameMs + 5000 > _lastReadAudioMs - && _frameMs + 15000 > _lastReadVideoMs) { - auto packetResult = readAndProcessPacket(); - if (packetResult != PacketResult::Ok) { - break; - } - } - } - FFmpeg::ClearFrameMemory(_frame.get()); return true; @@ -306,8 +263,8 @@ bool FFMpegReaderImplementation::start(Mode mode, crl::time &positionMs) { LOG(("Gif Error: Unable to open device %1").arg(logData())); return false; } - _ioBuffer = (uchar*)av_malloc(AVBlockSize); - _ioContext = avio_alloc_context(_ioBuffer, AVBlockSize, 0, static_cast(this), &FFMpegReaderImplementation::_read, nullptr, &FFMpegReaderImplementation::_seek); + _ioBuffer = (uchar*)av_malloc(FFmpeg::kAVBlockSize); + _ioContext = avio_alloc_context(_ioBuffer, FFmpeg::kAVBlockSize, 0, static_cast(this), &FFMpegReaderImplementation::_read, nullptr, &FFMpegReaderImplementation::_seek); _fmtContext = avformat_alloc_context(); if (!_fmtContext) { LOG(("Gif Error: Unable to avformat_alloc_context %1").arg(logData())); @@ -360,12 +317,9 @@ bool FFMpegReaderImplementation::start(Mode mode, crl::time &positionMs) { const auto codec = avcodec_find_decoder(_codecContext->codec_id); - _audioStreamId = av_find_best_stream(_fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0); if (_mode == Mode::Inspecting) { - _hasAudioStream = (_audioStreamId >= 0); - _audioStreamId = -1; - } else if (_mode == Mode::Silent || !_audioMsgId.externalPlayId()) { - _audioStreamId = -1; + const auto audioStreamId = av_find_best_stream(_fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0); + _hasAudioStream = (audioStreamId >= 0); } if ((res = avcodec_open2(_codecContext, codec, nullptr)) < 0) { @@ -373,36 +327,6 @@ bool FFMpegReaderImplementation::start(Mode mode, crl::time &positionMs) { return false; } - std::unique_ptr soundData; - if (_audioStreamId >= 0) { - auto audioContext = avcodec_alloc_context3(nullptr); - if (!audioContext) { - LOG(("Audio Error: Unable to avcodec_alloc_context3 %1").arg(logData())); - return false; - } - if ((res = avcodec_parameters_to_context(audioContext, _fmtContext->streams[_audioStreamId]->codecpar)) < 0) { - LOG(("Audio Error: Unable to avcodec_parameters_to_context %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; - } - av_codec_set_pkt_timebase(audioContext, _fmtContext->streams[_audioStreamId]->time_base); - av_opt_set_int(audioContext, "refcounted_frames", 1, 0); - - const auto audioCodec = avcodec_find_decoder(audioContext->codec_id); - if ((res = avcodec_open2(audioContext, audioCodec, 0)) < 0) { - avcodec_free_context(&audioContext); - LOG(("Gif Error: Unable to avcodec_open2 %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - _audioStreamId = -1; - } else { - soundData = std::make_unique(); - soundData->codec = FFmpeg::CodecPointer(audioContext); - soundData->frequency = _fmtContext->streams[_audioStreamId]->codecpar->sample_rate; - if (_fmtContext->streams[_audioStreamId]->duration == AV_NOPTS_VALUE) { - soundData->length = (_fmtContext->duration * soundData->frequency) / AV_TIME_BASE; - } else { - soundData->length = (_fmtContext->streams[_audioStreamId]->duration * soundData->frequency * _fmtContext->streams[_audioStreamId]->time_base.num) / _fmtContext->streams[_audioStreamId]->time_base.den; - } - } - } if (positionMs > 0) { const auto timeBase = _fmtContext->streams[_streamId]->time_base; const auto timeStamp = (positionMs * timeBase.den) @@ -420,10 +344,6 @@ bool FFMpegReaderImplementation::start(Mode mode, crl::time &positionMs) { positionMs = countPacketMs(packet); } - if (hasAudio()) { - Player::mixer()->play(_audioMsgId, std::move(soundData), positionMs); - } - if (readResult == PacketResult::Ok) { processPacket(std::move(packet)); } @@ -462,7 +382,7 @@ bool FFMpegReaderImplementation::isGifv() const { if (_hasAudioStream) { return false; } - if (dataSize() > Storage::kMaxAnimationInMemory) { + if (dataSize() > kMaxInMemory) { return false; } if (_codecContext->codec_id != AV_CODEC_ID_H264) { @@ -472,7 +392,7 @@ bool FFMpegReaderImplementation::isGifv() const { } QString FFMpegReaderImplementation::logData() const { - return qsl("for file '%1', data size '%2'").arg(_location ? _location->name() : QString()).arg(_data->size()); + return u"for file '%1', data size '%2'"_q.arg(_location ? _location->name() : QString()).arg(_data->size()); } FFMpegReaderImplementation::~FFMpegReaderImplementation() { @@ -494,14 +414,6 @@ FFMpegReaderImplementation::PacketResult FFMpegReaderImplementation::readPacket( int res = 0; if ((res = av_read_frame(_fmtContext, &packet.fields())) < 0) { if (res == AVERROR_EOF) { - if (_audioStreamId >= 0) { - // queue terminating packet to audio player - auto empty = FFmpeg::Packet(); - Player::mixer()->feedFromExternal({ - _audioMsgId, - gsl::make_span(&empty, 1) - }); - } return PacketResult::EndOfFile; } char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; @@ -514,21 +426,9 @@ FFMpegReaderImplementation::PacketResult FFMpegReaderImplementation::readPacket( void FFMpegReaderImplementation::processPacket(FFmpeg::Packet &&packet) { const auto &native = packet.fields(); auto videoPacket = (native.stream_index == _streamId); - auto audioPacket = (_audioStreamId >= 0 && native.stream_index == _audioStreamId); - if (audioPacket || videoPacket) { - if (videoPacket) { - _lastReadVideoMs = countPacketMs(packet); - - _packetQueue.push_back(std::move(packet)); - } else if (audioPacket) { - _lastReadAudioMs = countPacketMs(packet); - - // queue packet to audio player - Player::mixer()->feedFromExternal({ - _audioMsgId, - gsl::make_span(&packet, 1) - }); - } + if (videoPacket) { + _lastReadVideoMs = countPacketMs(packet); + _packetQueue.push_back(std::move(packet)); } } diff --git a/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.h b/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.h index 6e1c7b11b..03d96ff0e 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.h +++ b/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.h @@ -7,23 +7,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -extern "C" { - -#include - -} // extern "C" - #include "media/clip/media_clip_implementation.h" -#include "media/audio/media_child_ffmpeg_loader.h" -#include "media/streaming/media_streaming_utility.h" +#include "ffmpeg/ffmpeg_utility.h" + +extern "C" { +#include +#include +} // extern "C" +#include + +//#include "media/streaming/media_streaming_utility.h" namespace Media { namespace Clip { namespace internal { +constexpr auto kMaxInMemory = 10 * 1024 * 1024; + class FFMpegReaderImplementation : public ReaderImplementation { public: - FFMpegReaderImplementation(FileLocation *location, QByteArray *data, const AudioMsgId &audio); + FFMpegReaderImplementation(Core::FileLocation *location, QByteArray *data); ReadResult readFramesTill(crl::time frameMs, crl::time systemMs) override; @@ -33,9 +36,6 @@ public: bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) override; crl::time durationMs() const override; - bool hasAudio() const override { - return (_audioStreamId >= 0); - } bool start(Mode mode, crl::time &positionMs) override; bool inspectAt(crl::time &positionMs); @@ -74,7 +74,7 @@ private: static int _read(void *opaque, uint8_t *buf, int buf_size); static int64_t _seek(void *opaque, int64_t offset, int whence); - Mode _mode = Mode::Normal; + Mode _mode = Mode::Silent; Rotation _rotation = Rotation::None; @@ -90,8 +90,6 @@ private: int _skippedInvalidDataPackets = 0; bool _hasAudioStream = false; - int _audioStreamId = -1; - AudioMsgId _audioMsgId; crl::time _lastReadVideoMs = 0; crl::time _lastReadAudioMs = 0; diff --git a/Telegram/SourceFiles/media/clip/media_clip_implementation.cpp b/Telegram/SourceFiles/media/clip/media_clip_implementation.cpp index d8999bfed..e9b1f25b2 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_implementation.cpp +++ b/Telegram/SourceFiles/media/clip/media_clip_implementation.cpp @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/clip/media_clip_implementation.h" +#include "core/file_location.h" + namespace Media { namespace Clip { namespace internal { diff --git a/Telegram/SourceFiles/media/clip/media_clip_implementation.h b/Telegram/SourceFiles/media/clip/media_clip_implementation.h index 72d746cf7..7e709fe60 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_implementation.h +++ b/Telegram/SourceFiles/media/clip/media_clip_implementation.h @@ -9,7 +9,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include +namespace Core { class FileLocation; +} // namespace Core namespace Media { namespace Clip { @@ -17,13 +19,12 @@ namespace internal { class ReaderImplementation { public: - ReaderImplementation(FileLocation *location, QByteArray *data) - : _location(location) - , _data(data) { + ReaderImplementation(Core::FileLocation *location, QByteArray *data) + : _location(location) + , _data(data) { } enum class Mode { Silent, - Normal, Inspecting, // Not playing video, but reading data. }; @@ -43,7 +44,6 @@ public: virtual bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) = 0; virtual crl::time durationMs() const = 0; - virtual bool hasAudio() const = 0; virtual bool start(Mode mode, crl::time &positionMs) = 0; @@ -54,8 +54,8 @@ public: } protected: - FileLocation *_location; - QByteArray *_data; + Core::FileLocation *_location = nullptr; + QByteArray *_data = nullptr; QFile _file; QBuffer _buffer; QIODevice *_device = nullptr; diff --git a/Telegram/SourceFiles/media/clip/media_clip_reader.cpp b/Telegram/SourceFiles/media/clip/media_clip_reader.cpp index 7cb071529..8c8194e7e 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_reader.cpp +++ b/Telegram/SourceFiles/media/clip/media_clip_reader.cpp @@ -7,29 +7,34 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/clip/media_clip_reader.h" -#include "data/data_document.h" -#include "data/data_document_media.h" -#include "storage/file_download.h" #include "media/clip/media_clip_ffmpeg.h" #include "media/clip/media_clip_check_streaming.h" -#include "mainwidget.h" -#include "mainwindow.h" +#include "core/file_location.h" +#include "base/openssl_help.h" +#include "base/invoke_queued.h" +#include "logs.h" #include #include #include +#include +#include extern "C" { #include #include #include #include -} +} // extern "C" namespace Media { namespace Clip { namespace { +constexpr auto kClipThreadsCount = 8; +constexpr auto kAverageGifSize = 320 * 240; +constexpr auto kWaitBeforeGifPause = crl::time(200); + QVector threads; QVector managers; @@ -85,44 +90,32 @@ QPixmap PrepareFrame(const FrameRequest &request, const QImage &original, bool h } // namespace -Reader::Reader(const QString &filepath, Callback &&callback, Mode mode, crl::time seekMs) -: _callback(std::move(callback)) -, _mode(mode) -, _seekPositionMs(seekMs) { - init(FileLocation(filepath), QByteArray()); -} - Reader::Reader( - not_null media, - FullMsgId msgId, - Callback &&callback, - Mode mode, - crl::time seekMs) -: _callback(std::move(callback)) -, _mode(mode) -, _audioMsgId( - media->owner(), - msgId, - (mode == Mode::Video) ? AudioMsgId::CreateExternalPlayId() : 0) -, _seekPositionMs(seekMs) { - init(media->owner()->location(), media->bytes()); + const Core::FileLocation &location, + const QByteArray &data, + Callback &&callback) +: _callback(std::move(callback)) { + init(location, data); } -Reader::Reader(const QByteArray &data, Callback &&callback, Mode mode, crl::time seekMs) -: _callback(std::move(callback)) -, _mode(mode) -, _seekPositionMs(seekMs) { - init(FileLocation(QString()), data); +Reader::Reader(const QString &filepath, Callback &&callback) +: _callback(std::move(callback)) { + init(Core::FileLocation(filepath), QByteArray()); } -void Reader::init(const FileLocation &location, const QByteArray &data) { - if (threads.size() < ClipThreadsCount) { +Reader::Reader(const QByteArray &data, Callback &&callback) +: _callback(std::move(callback)) { + init(Core::FileLocation(QString()), data); +} + +void Reader::init(const Core::FileLocation &location, const QByteArray &data) { + if (threads.size() < kClipThreadsCount) { _threadIndex = threads.size(); threads.push_back(new QThread()); managers.push_back(new Manager(threads.back())); threads.back()->start(); } else { - _threadIndex = int32(rand_value() % threads.size()); + _threadIndex = int32(openssl::RandomValue() % threads.size()); int32 loadLevel = 0x7FFFFFFF; for (int32 i = 0, l = threads.size(); i < l; ++i) { int32 level = managers.at(i)->loadLevel(); @@ -216,7 +209,7 @@ void Reader::start(int32 framew, int32 frameh, int32 outerw, int32 outerh, Image if (_state == State::Error) return; if (_step.loadAcquire() == WaitingForRequestStep) { - int factor = cIntRetinaFactor(); + int factor = style::DevicePixelRatio(); FrameRequest request; request.factor = factor; request.framew = framew * factor; @@ -252,7 +245,7 @@ QPixmap Reader::current(int32 framew, int32 frameh, int32 outerw, int32 outerh, frame->displayed.storeRelease(-1); } - auto factor = cIntRetinaFactor(); + auto factor = style::DevicePixelRatio(); if (frame->pix.width() == outerw * factor && frame->pix.height() == outerh * factor && frame->request.radius == radius @@ -284,17 +277,6 @@ QPixmap Reader::current(int32 framew, int32 frameh, int32 outerw, int32 outerh, return frame->pix; } -QPixmap Reader::current() { - Expects(_mode == Mode::Video); - - auto frame = frameToShow(); - Assert(frame != nullptr); - - frame->displayed.storeRelease(1); - moveToNextShow(); - return frame->pix; -} - bool Reader::ready() const { if (_width && _height) return true; @@ -307,15 +289,11 @@ bool Reader::ready() const { return false; } -bool Reader::hasAudio() const { - return ready() ? _hasAudio : false; -} - crl::time Reader::getPositionMs() const { if (auto frame = frameToShow()) { return frame->positionMs; } - return _seekPositionMs; + return 0; } crl::time Reader::getDurationMs() const { @@ -370,13 +348,11 @@ Reader::~Reader() { class ReaderPrivate { public: - ReaderPrivate(Reader *reader, const FileLocation &location, const QByteArray &data) : _interface(reader) - , _mode(reader->mode()) - , _audioMsgId(reader->audioMsgId()) - , _seekPositionMs(reader->seekPositionMs()) + ReaderPrivate(Reader *reader, const Core::FileLocation &location, const QByteArray &data) + : _interface(reader) , _data(data) { if (_data.isEmpty()) { - _location = std::make_unique(location); + _location = std::make_unique(location); if (!_location->accessEnable()) { error(); return; @@ -396,8 +372,8 @@ public: // get the frame size and return a black frame with that size. auto firstFramePositionMs = crl::time(0); - auto reader = std::make_unique(_location.get(), &_data, AudioMsgId()); - if (reader->start(internal::ReaderImplementation::Mode::Normal, firstFramePositionMs)) { + auto reader = std::make_unique(_location.get(), &_data); + if (reader->start(internal::ReaderImplementation::Mode::Silent, firstFramePositionMs)) { auto firstFrameReadResult = reader->readFramesTill(-1, ms); if (firstFrameReadResult == internal::ReaderImplementation::ReadResult::Success) { if (reader->renderFrame(frame()->original, frame()->alpha, QSize())) { @@ -408,7 +384,6 @@ public: _width = frame()->original.width(); _height = frame()->original.height(); _durationMs = _implementation->durationMs(); - _hasAudio = _implementation->hasAudio(); return ProcessResult::Started; } } @@ -426,7 +401,6 @@ public: _width = frame()->original.width(); _height = frame()->original.height(); _durationMs = _implementation->durationMs(); - _hasAudio = _implementation->hasAudio(); return ProcessResult::Started; } return ProcessResult::Wait; @@ -444,9 +418,6 @@ public: } if (!_started) { _started = true; - if (!_videoPausedAtMs && _hasAudio) { - Player::mixer()->resume(_audioMsgId, true); - } } if (!_autoPausedGif && !_videoPausedAtMs && ms >= _nextFrameWhen) { @@ -459,7 +430,7 @@ public: auto frameMs = _seekPositionMs + ms - _animationStarted; auto readResult = _implementation->readFramesTill(frameMs, ms); if (readResult == internal::ReaderImplementation::ReadResult::EndOfFile) { - stop(Player::State::StoppedAtEnd); + stop(); _state = State::Finished; return ProcessResult::Finished; } else if (readResult == internal::ReaderImplementation::ReadResult::Error) { @@ -494,7 +465,7 @@ public: } bool init() { - if (_data.isEmpty() && QFileInfo(_location->name()).size() <= Storage::kMaxAnimationInMemory) { + if (_data.isEmpty() && QFileInfo(_location->name()).size() <= internal::kMaxInMemory) { QFile f(_location->name()); if (f.open(QIODevice::ReadOnly)) { _data = f.readAll(); @@ -504,16 +475,9 @@ public: } } - _implementation = std::make_unique(_location.get(), &_data, _audioMsgId); + _implementation = std::make_unique(_location.get(), &_data); - auto implementationMode = [this]() { - using ImplementationMode = internal::ReaderImplementation::Mode; - if (_mode == Reader::Mode::Gif) { - return ImplementationMode::Silent; - } - return ImplementationMode::Normal; - }; - return _implementation->start(implementationMode(), _seekPositionMs); + return _implementation->start(internal::ReaderImplementation::Mode::Silent, _seekPositionMs); } void startedAt(crl::time ms) { @@ -524,9 +488,6 @@ public: if (_videoPausedAtMs) return; // Paused already. _videoPausedAtMs = ms; - if (_hasAudio) { - Player::mixer()->pause(_audioMsgId, true); - } } void resumeVideo(crl::time ms) { @@ -537,23 +498,16 @@ public: _nextFrameWhen += delta; _videoPausedAtMs = 0; - if (_hasAudio) { - Player::mixer()->resume(_audioMsgId, true); - } } ProcessResult error() { - stop(Player::State::StoppedAtError); + stop(); _state = State::Error; return ProcessResult::Error; } - void stop(Player::State audioState) { + void stop() { _implementation = nullptr; - if (_hasAudio) { - Player::mixer()->stop(_audioMsgId, audioState); - } - if (_location) { if (_accessed) { _location->accessDisable(); @@ -564,19 +518,17 @@ public: } ~ReaderPrivate() { - stop(Player::State::Stopped); + stop(); _data.clear(); } private: Reader *_interface; State _state = State::Reading; - Reader::Mode _mode; - AudioMsgId _audioMsgId; crl::time _seekPositionMs = 0; QByteArray _data; - std::unique_ptr _location; + std::unique_ptr _location; bool _accessed = false; QBuffer _buffer; @@ -601,7 +553,6 @@ private: int _width = 0; int _height = 0; - bool _hasAudio = false; crl::time _durationMs = 0; crl::time _animationStarted = 0; crl::time _nextFrameWhen = 0; @@ -617,24 +568,17 @@ private: Manager::Manager(QThread *thread) { moveToThread(thread); - connect(thread, SIGNAL(started()), this, SLOT(process())); - connect(thread, SIGNAL(finished()), this, SLOT(finish())); - connect(this, SIGNAL(processDelayed()), this, SLOT(process()), Qt::QueuedConnection); + connect(thread, &QThread::started, this, [=] { process(); }); + connect(thread, &QThread::finished, this, [=] { finish(); }); _timer.setSingleShot(true); _timer.moveToThread(thread); - connect(&_timer, SIGNAL(timeout()), this, SLOT(process())); - - connect( - this, - &Manager::callback, - QCoreApplication::instance(), - &Reader::callback); + connect(&_timer, &QTimer::timeout, this, [=] { process(); }); } -void Manager::append(Reader *reader, const FileLocation &location, const QByteArray &data) { +void Manager::append(Reader *reader, const Core::FileLocation &location, const QByteArray &data) { reader->_private = new ReaderPrivate(reader, location, data); - _loadLevel.fetchAndAddRelaxed(AverageGifSize); + _loadLevel.fetchAndAddRelaxed(kAverageGifSize); update(reader); } @@ -650,7 +594,7 @@ void Manager::update(Reader *reader) { } else { i->storeRelease(1); } - emit processDelayed(); + InvokeQueued(this, [=] { process(); }); } void Manager::stop(Reader *reader) { @@ -658,7 +602,7 @@ void Manager::stop(Reader *reader) { QMutexLocker lock(&_readerPointersMutex); _readerPointers.remove(reader); - emit processDelayed(); + InvokeQueued(this, [=] { process(); }); } bool Manager::carries(Reader *reader) const { @@ -680,20 +624,26 @@ Manager::ReaderPointers::const_iterator Manager::constUnsafeFindReaderPointer(Re return (it == _readerPointers.cend() || it.key()->_private == reader) ? it : _readerPointers.cend(); } +void Manager::callback(Reader *reader, Notification notification) { + crl::on_main([=, threadIndex = reader->threadIndex()] { + Reader::callback(reader, threadIndex, notification); + }); +} + bool Manager::handleProcessResult(ReaderPrivate *reader, ProcessResult result, crl::time ms) { QMutexLocker lock(&_readerPointersMutex); auto it = unsafeFindReaderPointer(reader); if (result == ProcessResult::Error) { if (it != _readerPointers.cend()) { it.key()->error(); - emit callback(it.key(), it.key()->threadIndex(), NotificationReinit); + callback(it.key(), NotificationReinit); _readerPointers.erase(it); } return false; } else if (result == ProcessResult::Finished) { if (it != _readerPointers.cend()) { it.key()->finished(); - emit callback(it.key(), it.key()->threadIndex(), NotificationReinit); + callback(it.key(), NotificationReinit); } return false; } @@ -702,17 +652,16 @@ bool Manager::handleProcessResult(ReaderPrivate *reader, ProcessResult result, c } if (result == ProcessResult::Started) { - _loadLevel.fetchAndAddRelaxed(reader->_width * reader->_height - AverageGifSize); + _loadLevel.fetchAndAddRelaxed(reader->_width * reader->_height - kAverageGifSize); it.key()->_durationMs = reader->_durationMs; - it.key()->_hasAudio = reader->_hasAudio; } // See if we need to pause GIF because it is not displayed right now. - if (!reader->_autoPausedGif && reader->_mode == Reader::Mode::Gif && result == ProcessResult::Repaint) { + if (!reader->_autoPausedGif && result == ProcessResult::Repaint) { int32 ishowing, iprevious; auto showing = it.key()->frameToShow(&ishowing), previous = it.key()->frameToWriteNext(false, &iprevious); Assert(previous != nullptr && showing != nullptr && ishowing >= 0 && iprevious >= 0); if (reader->_frames[ishowing].when > 0 && showing->displayed.loadAcquire() <= 0) { // current frame was not shown - if (reader->_frames[ishowing].when + WaitBeforeGifPause < ms || (reader->_frames[iprevious].when && previous->displayed.loadAcquire() <= 0)) { + if (reader->_frames[ishowing].when + kWaitBeforeGifPause < ms || (reader->_frames[iprevious].when && previous->displayed.loadAcquire() <= 0)) { reader->_autoPausedGif = true; it.key()->_autoPausedGif.storeRelease(1); result = ProcessResult::Paused; @@ -730,21 +679,21 @@ bool Manager::handleProcessResult(ReaderPrivate *reader, ProcessResult result, c if (result == ProcessResult::Started) { reader->startedAt(ms); it.key()->moveToNextWrite(); - emit callback(it.key(), it.key()->threadIndex(), NotificationReinit); + callback(it.key(), NotificationReinit); } } else if (result == ProcessResult::Paused) { it.key()->moveToNextWrite(); - emit callback(it.key(), it.key()->threadIndex(), NotificationReinit); + callback(it.key(), NotificationReinit); } else if (result == ProcessResult::Repaint) { it.key()->moveToNextWrite(); - emit callback(it.key(), it.key()->threadIndex(), NotificationRepaint); + callback(it.key(), NotificationRepaint); } return true; } Manager::ResultHandleState Manager::handleResult(ReaderPrivate *reader, ProcessResult result, crl::time ms) { if (!handleProcessResult(reader, result, ms)) { - _loadLevel.fetchAndAddRelaxed(-1 * (reader->_width > 0 ? reader->_width * reader->_height : AverageGifSize)); + _loadLevel.fetchAndAddRelaxed(-1 * (reader->_width > 0 ? reader->_width * reader->_height : kAverageGifSize)); delete reader; return ResultHandleRemove; } @@ -836,7 +785,7 @@ void Manager::process() { QMutexLocker lock(&_readerPointersMutex); auto it = constUnsafeFindReaderPointer(reader); if (it == _readerPointers.cend()) { - _loadLevel.fetchAndAddRelaxed(-1 * (reader->_width > 0 ? reader->_width * reader->_height : AverageGifSize)); + _loadLevel.fetchAndAddRelaxed(-1 * (reader->_width > 0 ? reader->_width * reader->_height : kAverageGifSize)); delete reader; i = _readers.erase(i); continue; @@ -885,11 +834,11 @@ Manager::~Manager() { Ui::PreparedFileInformation::Video PrepareForSending(const QString &fname, const QByteArray &data) { auto result = Ui::PreparedFileInformation::Video(); - auto localLocation = FileLocation(fname); + auto localLocation = Core::FileLocation(fname); auto localData = QByteArray(data); auto seekPositionMs = crl::time(0); - auto reader = std::make_unique(&localLocation, &localData, AudioMsgId()); + auto reader = std::make_unique(&localLocation, &localData); if (reader->start(internal::ReaderImplementation::Mode::Inspecting, seekPositionMs)) { auto durationMs = reader->durationMs(); if (durationMs > 0) { @@ -939,7 +888,7 @@ void Finish() { } } -Reader *const ReaderPointer::BadPointer = SharedMemoryLocation(); +Reader *const ReaderPointer::BadPointer = reinterpret_cast(1); ReaderPointer::~ReaderPointer() { if (valid()) { diff --git a/Telegram/SourceFiles/media/clip/media_clip_reader.h b/Telegram/SourceFiles/media/clip/media_clip_reader.h index cb84dffd9..9a86e68eb 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_reader.h +++ b/Telegram/SourceFiles/media/clip/media_clip_reader.h @@ -11,12 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/image/image_prepare.h" #include +#include +namespace Core { class FileLocation; - -namespace Data { -class DocumentMedia; -} // namespace Data +} // namespace Core namespace Media { namespace Clip { @@ -60,23 +59,15 @@ public: Video, }; - Reader(not_null media, FullMsgId msgId, Callback &&callback, Mode mode = Mode::Gif, crl::time seekMs = 0); - Reader(const QString &filepath, Callback &&callback, Mode mode = Mode::Gif, crl::time seekMs = 0); - Reader(const QByteArray &data, Callback &&callback, Mode mode = Mode::Gif, crl::time seekMs = 0); + Reader(const Core::FileLocation &location, const QByteArray &data, Callback &&callback); + Reader(const QString &filepath, Callback &&callback); + Reader(const QByteArray &data, Callback &&callback); // Reader can be already deleted. static void callback(Reader *reader, qint32 threadIndex, qint32 notification); - AudioMsgId audioMsgId() const { - return _audioMsgId; - } - crl::time seekPositionMs() const { - return _seekPositionMs; - } - void start(int framew, int frameh, int outerw, int outerh, ImageRoundRadius radius, RectParts corners); QPixmap current(int framew, int frameh, int outerw, int outerh, ImageRoundRadius radius, RectParts corners, crl::time ms); - QPixmap current(); QPixmap frameOriginal() const { if (auto frame = frameToShow()) { auto result = QPixmap::fromImage(frame->original); @@ -107,7 +98,6 @@ public: } bool ready() const; - bool hasAudio() const; crl::time getPositionMs() const; crl::time getDurationMs() const; void pauseResumeVideo(); @@ -116,24 +106,15 @@ public: void error(); void finished(); - Mode mode() const { - return _mode; - } - ~Reader(); private: - void init(const FileLocation &location, const QByteArray &data); + void init(const Core::FileLocation &location, const QByteArray &data); Callback _callback; - Mode _mode; - State _state = State::Reading; - AudioMsgId _audioMsgId; - bool _hasAudio = false; crl::time _durationMs = 0; - crl::time _seekPositionMs = 0; mutable int _width = 0; mutable int _height = 0; @@ -240,32 +221,23 @@ enum class ProcessResult { }; class Manager : public QObject { - Q_OBJECT - public: + explicit Manager(QThread *thread); + ~Manager(); - Manager(QThread *thread); - int32 loadLevel() const { + int loadLevel() const { return _loadLevel.load(); } - void append(Reader *reader, const FileLocation &location, const QByteArray &data); + void append(Reader *reader, const Core::FileLocation &location, const QByteArray &data); void start(Reader *reader); void update(Reader *reader); void stop(Reader *reader); bool carries(Reader *reader) const; - ~Manager(); - -signals: - void processDelayed(); - - void callback(Media::Clip::Reader *reader, qint32 threadIndex, qint32 notification); - -public slots: - void process(); - void finish(); private: - + void process(); + void finish(); + void callback(Reader *reader, Notification notification); void clear(); QAtomicInt _loadLevel; diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.h b/Telegram/SourceFiles/platform/linux/specific_linux.h index bc71760f8..ff39fe802 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.h +++ b/Telegram/SourceFiles/platform/linux/specific_linux.h @@ -73,33 +73,9 @@ void psNewVersion(); inline QByteArray psDownloadPathBookmark(const QString &path) { return QByteArray(); } -inline QByteArray psPathBookmark(const QString &path) { - return QByteArray(); -} inline void psDownloadPathEnableAccess() { } -class PsFileBookmark { -public: - PsFileBookmark(const QByteArray &bookmark) { - } - bool check() const { - return true; - } - bool enable() const { - return true; - } - void disable() const { - } - const QString &name(const QString &original) const { - return original; - } - QByteArray bookmark() const { - return QByteArray(); - } - -}; - bool linuxMoveFile(const char *from, const char *to); bool psLaunchMaps(const Data::LocationPoint &point); diff --git a/Telegram/SourceFiles/platform/mac/file_bookmark_mac.h b/Telegram/SourceFiles/platform/mac/file_bookmark_mac.h new file mode 100644 index 000000000..a60971184 --- /dev/null +++ b/Telegram/SourceFiles/platform/mac/file_bookmark_mac.h @@ -0,0 +1,33 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Platform { + +class FileBookmark final { +public: + FileBookmark(const QByteArray &bookmark); + ~FileBookmark(); + + [[nodiscard]] bool check() const; + bool enable() const; + void disable(); + [[nodiscard]] const QString &name(const QString &original) const; + [[nodiscard]] QByteArray bookmark() const; + +private: +#ifdef OS_MAC_STORE + struct Data; + Data *data = nullptr; +#endif // OS_MAC_STORE + +}; + +[[nodiscard]] QByteArray PathBookmark(const QString &path); + +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/mac/file_bookmark_mac.mm b/Telegram/SourceFiles/platform/mac/file_bookmark_mac.mm new file mode 100644 index 000000000..27fd68f93 --- /dev/null +++ b/Telegram/SourceFiles/platform/mac/file_bookmark_mac.mm @@ -0,0 +1,125 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "platform/mac/file_bookmark_mac.h" + +namespace Platform { +namespace { + +#ifdef OS_MAC_STORE +QMutex BookmarksMutex; +#endif // OS_MAC_STORE + +} // namespace + +#ifdef OS_MAC_STORE +struct FileBookmark::Data { + ~Data() { + if (url) [url release]; + } + NSURL *url = nil; + QString name; + QByteArray bookmark; + int counter = 0; +}; +#endif // OS_MAC_STORE + +FileBookmark::FileBookmark(const QByteArray &bookmark) { +#ifdef OS_MAC_STORE + if (bookmark.isEmpty()) return; + + BOOL isStale = NO; + NSError *error = nil; + NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark.toNSData() options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error]; + if (!url) return; + + if ([url startAccessingSecurityScopedResource]) { + data = new Data(); + data->url = [url retain]; + data->name = NS2QString([url path]); + data->bookmark = bookmark; + [url stopAccessingSecurityScopedResource]; + } +#endif // OS_MAC_STORE +} + +bool FileBookmark::check() const { + if (enable()) { + disable(); + return true; + } + return false; +} + +bool FileBookmark::enable() const { +#ifndef OS_MAC_STORE + return true; +#else // OS_MAC_STORE + if (!data) return false; + + QMutexLocker lock(&_bookmarksMutex); + if (data->counter > 0 || [data->url startAccessingSecurityScopedResource] == YES) { + ++data->counter; + return true; + } + return false; +#endif // OS_MAC_STORE +} + +void FileBookmark::disable() const { +#ifdef OS_MAC_STORE + if (!data) return; + + QMutexLocker lock(&_bookmarksMutex); + if (data->counter > 0) { + --data->counter; + if (!data->counter) { + [data->url stopAccessingSecurityScopedResource]; + } + } +#endif // OS_MAC_STORE +} + +const QString &FileBookmark::name(const QString &original) const { +#ifndef OS_MAC_STORE + return original; +#else // OS_MAC_STORE + return (data && !data->name.isEmpty()) ? data->name : original; +#endif // OS_MAC_STORE +} + +QByteArray FileBookmark::bookmark() const { +#ifndef OS_MAC_STORE + return QByteArray(); +#else // OS_MAC_STORE + return data ? data->bookmark : QByteArray(); +#endif // OS_MAC_STORE +} + +FileBookmark::~FileBookmark() { +#ifdef OS_MAC_STORE + if (data && data->counter > 0) { + LOG(("Did not disable() bookmark, counter: %1").arg(data->counter)); + [data->url stopAccessingSecurityScopedResource]; + } +#endif // OS_MAC_STORE +} + +QByteArray PathBookmark(const QString &path) { +#ifndef OS_MAC_STORE + return QByteArray(); +#else // OS_MAC_STORE + NSURL *url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:path.toUtf8().constData()]]; + if (!url) return QByteArray(); + + NSError *error = nil; + NSData *data = [url bookmarkDataWithOptions:(NSURLBookmarkCreationWithSecurityScope | NSURLBookmarkCreationSecurityScopeAllowOnlyReadAccess) includingResourceValuesForKeys:nil relativeToURL:nil error:&error]; + return data ? QByteArray::fromNSData(data) : QByteArray(); +#endif // OS_MAC_STORE +} + +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.h b/Telegram/SourceFiles/platform/mac/specific_mac.h index af04f1320..25c3bc933 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac.h +++ b/Telegram/SourceFiles/platform/mac/specific_mac.h @@ -98,31 +98,6 @@ void psDownloadPathEnableAccess(); QByteArray psDownloadPathBookmark(const QString &path); QByteArray psPathBookmark(const QString &path); -class PsFileBookmark { -public: - PsFileBookmark(const QByteArray &bookmark) : _inner(bookmark) { - } - bool check() const { - return _inner.valid(); - } - bool enable() const { - return _inner.enable(); - } - void disable() const { - return _inner.disable(); - } - const QString &name(const QString &original) const { - return _inner.name(original); - } - QByteArray bookmark() const { - return _inner.bookmark(); - } - -private: - objc_FileBookmark _inner; - -}; - QString strNotificationAboutThemeChange(); QString strNotificationAboutScreenLocked(); QString strNotificationAboutScreenUnlocked(); diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.mm b/Telegram/SourceFiles/platform/mac/specific_mac.mm index 63d320087..d2becc60d 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac.mm +++ b/Telegram/SourceFiles/platform/mac/specific_mac.mm @@ -253,10 +253,6 @@ QByteArray psDownloadPathBookmark(const QString &path) { return objc_downloadPathBookmark(path); } -QByteArray psPathBookmark(const QString &path) { - return objc_pathBookmark(path); -} - bool psLaunchMaps(const Data::LocationPoint &point) { return QDesktopServices::openUrl(qsl("https://maps.apple.com/?q=Point&z=16&ll=%1,%2").arg(point.latAsString()).arg(point.lonAsString())); } diff --git a/Telegram/SourceFiles/platform/mac/specific_mac_p.h b/Telegram/SourceFiles/platform/mac/specific_mac_p.h index 0a01209d3..35eb05d53 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac_p.h +++ b/Telegram/SourceFiles/platform/mac/specific_mac_p.h @@ -25,25 +25,4 @@ double objc_appkitVersion(); QString objc_documentsPath(); QString objc_appDataPath(); QByteArray objc_downloadPathBookmark(const QString &path); -QByteArray objc_pathBookmark(const QString &path); void objc_downloadPathEnableAccess(const QByteArray &bookmark); - -class objc_FileBookmark { -public: - objc_FileBookmark(const QByteArray &bookmark); - bool valid() const; - bool enable() const; - void disable() const; - - const QString &name(const QString &original) const; - QByteArray bookmark() const; - - ~objc_FileBookmark(); - -private: -#ifdef OS_MAC_STORE - class objc_FileBookmarkData; - objc_FileBookmarkData *data = nullptr; -#endif // OS_MAC_STORE - -}; diff --git a/Telegram/SourceFiles/platform/mac/specific_mac_p.mm b/Telegram/SourceFiles/platform/mac/specific_mac_p.mm index 59b9539ee..6cba7ee3c 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac_p.mm +++ b/Telegram/SourceFiles/platform/mac/specific_mac_p.mm @@ -373,19 +373,6 @@ QByteArray objc_downloadPathBookmark(const QString &path) { #endif // OS_MAC_STORE } -QByteArray objc_pathBookmark(const QString &path) { -#ifndef OS_MAC_STORE - return QByteArray(); -#else // OS_MAC_STORE - NSURL *url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:path.toUtf8().constData()]]; - if (!url) return QByteArray(); - - NSError *error = nil; - NSData *data = [url bookmarkDataWithOptions:(NSURLBookmarkCreationWithSecurityScope | NSURLBookmarkCreationSecurityScopeAllowOnlyReadAccess) includingResourceValuesForKeys:nil relativeToURL:nil error:&error]; - return data ? QByteArray::fromNSData(data) : QByteArray(); -#endif // OS_MAC_STORE -} - void objc_downloadPathEnableAccess(const QByteArray &bookmark) { #ifdef OS_MAC_STORE if (bookmark.isEmpty()) return; @@ -412,101 +399,3 @@ void objc_downloadPathEnableAccess(const QByteArray &bookmark) { } #endif // OS_MAC_STORE } - -#ifdef OS_MAC_STORE -namespace { - QMutex _bookmarksMutex; -} - -class objc_FileBookmark::objc_FileBookmarkData { -public: - ~objc_FileBookmarkData() { - if (url) [url release]; - } - NSURL *url = nil; - QString name; - QByteArray bookmark; - int counter = 0; -}; -#endif // OS_MAC_STORE - -objc_FileBookmark::objc_FileBookmark(const QByteArray &bookmark) { -#ifdef OS_MAC_STORE - if (bookmark.isEmpty()) return; - - BOOL isStale = NO; - NSError *error = nil; - NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark.toNSData() options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error]; - if (!url) return; - - if ([url startAccessingSecurityScopedResource]) { - data = new objc_FileBookmarkData(); - data->url = [url retain]; - data->name = NS2QString([url path]); - data->bookmark = bookmark; - [url stopAccessingSecurityScopedResource]; - } -#endif // OS_MAC_STORE -} - -bool objc_FileBookmark::valid() const { - if (enable()) { - disable(); - return true; - } - return false; -} - -bool objc_FileBookmark::enable() const { -#ifndef OS_MAC_STORE - return true; -#else // OS_MAC_STORE - if (!data) return false; - - QMutexLocker lock(&_bookmarksMutex); - if (data->counter > 0 || [data->url startAccessingSecurityScopedResource] == YES) { - ++data->counter; - return true; - } - return false; -#endif // OS_MAC_STORE -} - -void objc_FileBookmark::disable() const { -#ifdef OS_MAC_STORE - if (!data) return; - - QMutexLocker lock(&_bookmarksMutex); - if (data->counter > 0) { - --data->counter; - if (!data->counter) { - [data->url stopAccessingSecurityScopedResource]; - } - } -#endif // OS_MAC_STORE -} - -const QString &objc_FileBookmark::name(const QString &original) const { -#ifndef OS_MAC_STORE - return original; -#else // OS_MAC_STORE - return (data && !data->name.isEmpty()) ? data->name : original; -#endif // OS_MAC_STORE -} - -QByteArray objc_FileBookmark::bookmark() const { -#ifndef OS_MAC_STORE - return QByteArray(); -#else // OS_MAC_STORE - return data ? data->bookmark : QByteArray(); -#endif // OS_MAC_STORE -} - -objc_FileBookmark::~objc_FileBookmark() { -#ifdef OS_MAC_STORE - if (data && data->counter > 0) { - LOG(("Did not disable() bookmark, counter: %1").arg(data->counter)); - [data->url stopAccessingSecurityScopedResource]; - } -#endif // OS_MAC_STORE -} diff --git a/Telegram/SourceFiles/platform/platform_file_bookmark.h b/Telegram/SourceFiles/platform/platform_file_bookmark.h new file mode 100644 index 000000000..dffc792b8 --- /dev/null +++ b/Telegram/SourceFiles/platform/platform_file_bookmark.h @@ -0,0 +1,43 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#ifdef Q_OS_MAC +#include "platform/mac/file_bookmark_mac.h" +#else // Q_OS_MAC + +namespace Platform { + +class FileBookmark { +public: + FileBookmark(const QByteArray &bookmark) { + } + bool check() const { + return true; + } + bool enable() const { + return true; + } + void disable() const { + } + const QString &name(const QString &original) const { + return original; + } + QByteArray bookmark() const { + return QByteArray(); + } + +}; + +[[nodiscard]] inline QByteArray PathBookmark(const QString &path) { + return QByteArray(); +} + +} // namespace Platform + +#endif // Q_OS_MAC diff --git a/Telegram/SourceFiles/platform/win/specific_win.h b/Telegram/SourceFiles/platform/win/specific_win.h index 6e4ae4b54..e86df71bb 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.h +++ b/Telegram/SourceFiles/platform/win/specific_win.h @@ -89,31 +89,7 @@ void psNewVersion(); inline QByteArray psDownloadPathBookmark(const QString &path) { return QByteArray(); } -inline QByteArray psPathBookmark(const QString &path) { - return QByteArray(); -} inline void psDownloadPathEnableAccess() { } -class PsFileBookmark { -public: - PsFileBookmark(const QByteArray &bookmark) { - } - bool check() const { - return true; - } - bool enable() const { - return true; - } - void disable() const { - } - const QString &name(const QString &original) const { - return original; - } - QByteArray bookmark() const { - return QByteArray(); - } - -}; - bool psLaunchMaps(const Data::LocationPoint &point); diff --git a/Telegram/SourceFiles/storage/file_download.cpp b/Telegram/SourceFiles/storage/file_download.cpp index ba0fc2685..629f2922b 100644 --- a/Telegram/SourceFiles/storage/file_download.cpp +++ b/Telegram/SourceFiles/storage/file_download.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwidget.h" #include "mainwindow.h" #include "core/application.h" +#include "core/file_location.h" #include "storage/storage_account.h" #include "storage/file_download_mtproto.h" #include "storage/file_download_web.h" @@ -454,7 +455,7 @@ bool FileLoader::finalizeResult() { if (!_filename.isEmpty()) { _session->local().writeFileLocation( *key, - FileLocation(_filename)); + Core::FileLocation(_filename)); } } const auto key = cacheKey(); diff --git a/Telegram/SourceFiles/storage/file_upload.cpp b/Telegram/SourceFiles/storage/file_upload.cpp index 837f73a79..2fd2ce0b2 100644 --- a/Telegram/SourceFiles/storage/file_upload.cpp +++ b/Telegram/SourceFiles/storage/file_upload.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/image/image_location_factory.h" #include "history/history_item.h" #include "history/history.h" +#include "core/file_location.h" #include "core/mime_type.h" #include "main/main_session.h" #include "apiwrap.h" @@ -320,7 +321,7 @@ void Uploader::uploadMedia( } } if (!media.file.isEmpty()) { - document->setLocation(FileLocation(media.file)); + document->setLocation(Core::FileLocation(media.file)); } } queue.emplace(msgId, File(media)); @@ -368,7 +369,7 @@ void Uploader::upload( document->setDataAndCache(file->content); } if (!file->filepath.isEmpty()) { - document->setLocation(FileLocation(file->filepath)); + document->setLocation(Core::FileLocation(file->filepath)); } if (file->type == SendMediaType::ThemeFile) { document->checkWallPaperProperties(); diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp index 4a869fc0c..1d80e44c1 100644 --- a/Telegram/SourceFiles/storage/localstorage.cpp +++ b/Telegram/SourceFiles/storage/localstorage.cpp @@ -17,10 +17,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/platform/base_platform_info.h" #include "ui/effects/animation_value.h" #include "core/update_checker.h" +#include "core/file_location.h" +#include "core/application.h" #include "media/audio/media_audio.h" #include "mtproto/mtproto_config.h" #include "mtproto/mtproto_dc_options.h" -#include "core/application.h" #include "main/main_domain.h" #include "main/main_account.h" #include "main/main_session.h" @@ -870,7 +871,7 @@ public: protected: DocumentData *_doc = nullptr; - FileLocation _loc; + Core::FileLocation _loc; QByteArray _data; VoiceWaveform _waveform; char _wavemax; diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp index 7a0ba37b1..0a541a023 100644 --- a/Telegram/SourceFiles/storage/storage_account.cpp +++ b/Telegram/SourceFiles/storage/storage_account.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/mtp_instance.h" #include "history/history.h" #include "core/application.h" +#include "core/file_location.h" #include "data/stickers/data_stickers.h" #include "data/data_session.h" #include "data/data_document.h" @@ -669,7 +670,7 @@ void Account::readLocations() { while (!locations.stream.atEnd()) { quint64 first, second; QByteArray bookmark; - FileLocation loc; + Core::FileLocation loc; quint32 legacyTypeField = 0; locations.stream >> first >> second >> legacyTypeField >> loc.fname; if (locations.version > 9013) { @@ -1164,7 +1165,7 @@ bool Account::hasDraft(const PeerId &peer) { return _draftsMap.contains(peer); } -void Account::writeFileLocation(MediaKey location, const FileLocation &local) { +void Account::writeFileLocation(MediaKey location, const Core::FileLocation &local) { if (local.fname.isEmpty()) { return; } @@ -1217,7 +1218,7 @@ void Account::removeFileLocation(MediaKey location) { writeLocationsQueued(); } -FileLocation Account::readFileLocation(MediaKey location) { +Core::FileLocation Account::readFileLocation(MediaKey location) { const auto aliasIt = _fileLocationAliases.constFind(location); if (aliasIt != _fileLocationAliases.cend()) { location = aliasIt.value(); @@ -1232,7 +1233,7 @@ FileLocation Account::readFileLocation(MediaKey location) { } return i.value(); } - return FileLocation(); + return Core::FileLocation(); } EncryptionKey Account::cacheKey() const { diff --git a/Telegram/SourceFiles/storage/storage_account.h b/Telegram/SourceFiles/storage/storage_account.h index c0a7a4a9b..d40e4638b 100644 --- a/Telegram/SourceFiles/storage/storage_account.h +++ b/Telegram/SourceFiles/storage/storage_account.h @@ -12,7 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/stickers/data_stickers_set.h" class History; + +namespace Core { class FileLocation; +} // namespace Core namespace Export { struct Settings; @@ -86,8 +89,8 @@ public: [[nodiscard]] bool hasDraftCursors(const PeerId &peer); [[nodiscard]] bool hasDraft(const PeerId &peer); - void writeFileLocation(MediaKey location, const FileLocation &local); - [[nodiscard]] FileLocation readFileLocation(MediaKey location); + void writeFileLocation(MediaKey location, const Core::FileLocation &local); + [[nodiscard]] Core::FileLocation readFileLocation(MediaKey location); void removeFileLocation(MediaKey location); [[nodiscard]] EncryptionKey cacheKey() const; @@ -219,8 +222,8 @@ private: base::flat_map _draftCursorsMap; base::flat_map _draftsNotReadMap; - QMultiMap _fileLocations; - QMap> _fileLocationPairs; + QMultiMap _fileLocations; + QMap> _fileLocationPairs; QMap _fileLocationAliases; FileKey _locationsKey = 0; diff --git a/Telegram/SourceFiles/ui/image/image_location.cpp b/Telegram/SourceFiles/ui/image/image_location.cpp index dea8c72b5..7b217e72b 100644 --- a/Telegram/SourceFiles/ui/image/image_location.cpp +++ b/Telegram/SourceFiles/ui/image/image_location.cpp @@ -25,7 +25,6 @@ constexpr auto kPhotoBaseCacheTag = 0x0000000000020000ULL; constexpr auto kPhotoBaseCacheMask = 0x000000000000FF00ULL; constexpr auto kSerializeTypeShift = quint8(0x08); constexpr auto kNonStorageLocationToken = quint8(0x10); -const auto kInMediaCacheLocation = QString("*media_cache*"); enum class NonStorageLocationType : quint8 { Web, @@ -939,102 +938,3 @@ std::optional ImageLocation::FromSerialized( } return std::nullopt; } - -ReadAccessEnabler::ReadAccessEnabler(const PsFileBookmark *bookmark) -: _bookmark(bookmark) -, _failed(_bookmark ? !_bookmark->enable() : false) { -} - -ReadAccessEnabler::ReadAccessEnabler( - const std::shared_ptr &bookmark) -: _bookmark(bookmark.get()) -, _failed(_bookmark ? !_bookmark->enable() : false) { -} - -ReadAccessEnabler::~ReadAccessEnabler() { - if (_bookmark && !_failed) _bookmark->disable(); -} - -FileLocation::FileLocation(const QString &name) : fname(name) { - if (fname.isEmpty() || fname == kInMediaCacheLocation) { - size = 0; - } else { - setBookmark(psPathBookmark(name)); - - QFileInfo f(name); - if (f.exists()) { - qint64 s = f.size(); - if (s > INT_MAX) { - fname = QString(); - _bookmark = nullptr; - size = 0; - } else { - modified = f.lastModified(); - size = qint32(s); - } - } else { - fname = QString(); - _bookmark = nullptr; - size = 0; - } - } -} - -FileLocation FileLocation::InMediaCacheLocation() { - return FileLocation(kInMediaCacheLocation); -} - -bool FileLocation::check() const { - if (fname.isEmpty() || fname == kInMediaCacheLocation) { - return false; - } - - ReadAccessEnabler enabler(_bookmark); - if (enabler.failed()) { - const_cast(this)->_bookmark = nullptr; - } - - QFileInfo f(name()); - if (!f.isReadable()) return false; - - quint64 s = f.size(); - if (s > INT_MAX) { - DEBUG_LOG(("File location check: Wrong size %1").arg(s)); - return false; - } - - if (qint32(s) != size) { - DEBUG_LOG(("File location check: Wrong size %1 when should be %2").arg(s).arg(size)); - return false; - } - auto realModified = f.lastModified(); - if (realModified != modified) { - DEBUG_LOG(("File location check: Wrong last modified time %1 when should be %2").arg(realModified.toMSecsSinceEpoch()).arg(modified.toMSecsSinceEpoch())); - return false; - } - return true; -} - -const QString &FileLocation::name() const { - return _bookmark ? _bookmark->name(fname) : fname; -} - -QByteArray FileLocation::bookmark() const { - return _bookmark ? _bookmark->bookmark() : QByteArray(); -} - -bool FileLocation::inMediaCache() const { - return (fname == kInMediaCacheLocation); -} - -void FileLocation::setBookmark(const QByteArray &bm) { - _bookmark.reset(bm.isEmpty() ? nullptr : new PsFileBookmark(bm)); -} - -bool FileLocation::accessEnable() const { - return isEmpty() ? false : (_bookmark ? _bookmark->enable() : true); -} - -void FileLocation::accessDisable() const { - return _bookmark ? _bookmark->disable() : (void)0; -} diff --git a/Telegram/SourceFiles/ui/image/image_location.h b/Telegram/SourceFiles/ui/image/image_location.h index 299812205..fb3fb7299 100644 --- a/Telegram/SourceFiles/ui/image/image_location.h +++ b/Telegram/SourceFiles/ui/image/image_location.h @@ -644,55 +644,3 @@ inline QSize shrinkToKeepAspect(int32 width, int32 height, int32 towidth, int32 } return QSize(qMax(w, 1), qMax(h, 1)); } - -class PsFileBookmark; -class ReadAccessEnabler { -public: - ReadAccessEnabler(const PsFileBookmark *bookmark); - ReadAccessEnabler(const std::shared_ptr &bookmark); - bool failed() const { - return _failed; - } - ~ReadAccessEnabler(); - -private: - const PsFileBookmark *_bookmark; - bool _failed; - -}; - -class FileLocation { -public: - FileLocation() = default; - explicit FileLocation(const QString &name); - - static FileLocation InMediaCacheLocation(); - - [[nodiscard]] bool check() const; - [[nodiscard]] const QString &name() const; - void setBookmark(const QByteArray &bookmark); - QByteArray bookmark() const; - [[nodiscard]] bool isEmpty() const { - return name().isEmpty(); - } - [[nodiscard]] bool inMediaCache() const; - - bool accessEnable() const; - void accessDisable() const; - - QString fname; - QDateTime modified; - qint32 size; - -private: - std::shared_ptr _bookmark; - -}; -inline bool operator==(const FileLocation &a, const FileLocation &b) { - return (a.name() == b.name()) - && (a.modified == b.modified) - && (a.size == b.size); -} -inline bool operator!=(const FileLocation &a, const FileLocation &b) { - return !(a == b); -} diff --git a/Telegram/SourceFiles/window/window_media_preview.cpp b/Telegram/SourceFiles/window/window_media_preview.cpp index ea24e9b5f..bb2c97e1f 100644 --- a/Telegram/SourceFiles/window/window_media_preview.cpp +++ b/Telegram/SourceFiles/window/window_media_preview.cpp @@ -375,8 +375,8 @@ void MediaPreviewWidget::validateGifAnimation() { }; if (contentLoaded) { _gif = Media::Clip::MakeReader( - _documentMedia.get(), - FullMsgId(), + _documentMedia->owner()->location(), + _documentMedia->bytes(), std::move(callback)); } else { _gifThumbnail = Media::Clip::MakeReader( diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 12d26d2da..8a19ce621 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -46,9 +46,24 @@ nice_target_sources(td_ui ${src_loc} PRIVATE ${style_files} + core/file_location.cpp + core/file_location.h core/mime_type.cpp core/mime_type.h + media/clip/media_clip_check_streaming.cpp + media/clip/media_clip_check_streaming.h + media/clip/media_clip_ffmpeg.cpp + media/clip/media_clip_ffmpeg.h + media/clip/media_clip_implementation.cpp + media/clip/media_clip_implementation.h + media/clip/media_clip_reader.cpp + media/clip/media_clip_reader.h + + platform/mac/file_bookmark_mac.h + platform/mac/file_bookmark_mac.mm + platform/platform_file_bookmark.h + ui/chat/attach/attach_album_thumbnail.cpp ui/chat/attach/attach_album_thumbnail.h ui/chat/attach/attach_album_preview.cpp @@ -91,4 +106,5 @@ target_link_libraries(td_ui PUBLIC tdesktop::td_lang desktop-app::lib_ui + desktop-app::lib_ffmpeg ) From 64ac6b18bf344014b5ac299a54d9220a2879ac89 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 13 Oct 2020 20:15:52 +0300 Subject: [PATCH 091/190] Move SingleMediaPreview to td_ui. --- .../SourceFiles/boxes/edit_caption_box.cpp | 4 +- Telegram/SourceFiles/boxes/send_files_box.cpp | 262 +----------------- .../passport/passport_panel_edit_scans.cpp | 3 +- .../SourceFiles/storage/localimageloader.cpp | 2 +- .../storage/storage_media_prepare.cpp | 15 +- .../storage/storage_media_prepare.h | 1 - .../ui/chat/attach/attach_prepare.cpp | 7 + .../ui/chat/attach/attach_prepare.h | 1 + .../attach/attach_single_media_preview.cpp | 235 ++++++++++++++++ .../chat/attach/attach_single_media_preview.h | 66 +++++ Telegram/cmake/td_ui.cmake | 3 + 11 files changed, 327 insertions(+), 272 deletions(-) create mode 100644 Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.cpp create mode 100644 Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.h diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index e3f6e530f..1a216e3bd 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -498,7 +498,7 @@ void EditCaptionBox::updateEditPreview() { auto shouldAsDoc = true; auto docPhotoSize = QSize(); if (const auto image = std::get_if(fileMedia)) { - shouldAsDoc = !Storage::ValidateThumbDimensions( + shouldAsDoc = !Ui::ValidateThumbDimensions( image->data.width(), image->data.height()); if (shouldAsDoc) { @@ -694,7 +694,7 @@ bool EditCaptionBox::fileFromClipboard(not_null data) { using Info = Ui::PreparedFileInformation; const auto fileMedia = &file->information->media; if (const auto image = std::get_if(fileMedia)) { - return !Storage::ValidateThumbDimensions( + return !Ui::ValidateThumbDimensions( image->data.width(), image->data.height()); } diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index f6ac6012b..16a389809 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/attach/attach_prepare.h" #include "ui/chat/attach/attach_album_preview.h" #include "ui/chat/attach/attach_single_file_preview.h" +#include "ui/chat/attach/attach_single_media_preview.h" #include "ui/text/format_values.h" #include "ui/grouped_layout.h" #include "ui/text/text_options.h" @@ -56,8 +57,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { -constexpr auto kMinPreviewWidth = 20; - using Ui::SendFilesWay; inline bool CanAddUrls(const QList &urls) { @@ -95,259 +94,6 @@ void FileDialogCallback( callback(std::move(*list)); } -class SingleMediaPreview : public Ui::RpWidget { -public: - static SingleMediaPreview *Create( - QWidget *parent, - not_null controller, - const Ui::PreparedFile &file); - - SingleMediaPreview( - QWidget *parent, - not_null controller, - QImage preview, - bool animated, - bool sticker, - const QString &animatedPreviewPath); - - bool canSendAsPhoto() const { - return _canSendAsPhoto; - } - - rpl::producer desiredHeightValue() const override; - -protected: - void paintEvent(QPaintEvent *e) override; - -private: - void preparePreview( - QImage preview, - const QString &animatedPreviewPath); - void prepareAnimatedPreview(const QString &animatedPreviewPath); - void clipCallback(Media::Clip::Notification notification); - - not_null _controller; - bool _animated = false; - bool _sticker = false; - bool _canSendAsPhoto = false; - QPixmap _preview; - int _previewLeft = 0; - int _previewWidth = 0; - int _previewHeight = 0; - Media::Clip::ReaderPointer _gifPreview; - std::unique_ptr _lottiePreview; - -}; - -SingleMediaPreview *SingleMediaPreview::Create( - QWidget *parent, - not_null controller, - const Ui::PreparedFile &file) { - auto preview = QImage(); - bool animated = false; - bool animationPreview = false; - if (const auto image = std::get_if( - &file.information->media)) { - preview = image->data; - animated = animationPreview = image->animated; - } else if (const auto video = std::get_if( - &file.information->media)) { - preview = video->thumbnail; - animated = true; - animationPreview = video->isGifv; - } - if (preview.isNull()) { - return nullptr; - } else if (!animated && !Storage::ValidateThumbDimensions( - preview.width(), - preview.height())) { - return nullptr; - } - return Ui::CreateChild( - parent, - controller, - preview, - animated, - Core::IsMimeSticker(file.information->filemime), - animationPreview ? file.path : QString()); -} - -SingleMediaPreview::SingleMediaPreview( - QWidget *parent, - not_null controller, - QImage preview, - bool animated, - bool sticker, - const QString &animatedPreviewPath) -: RpWidget(parent) -, _controller(controller) -, _animated(animated) -, _sticker(sticker) { - Expects(!preview.isNull()); - - _canSendAsPhoto = !_animated - && !_sticker - && Storage::ValidateThumbDimensions( - preview.width(), - preview.height()); - - preparePreview(preview, animatedPreviewPath); -} - -void SingleMediaPreview::preparePreview( - QImage preview, - const QString &animatedPreviewPath) { - auto maxW = 0; - auto maxH = 0; - if (_animated && !_sticker) { - auto limitW = st::sendMediaPreviewSize; - auto limitH = st::confirmMaxHeight; - maxW = qMax(preview.width(), 1); - maxH = qMax(preview.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; - } - } - preview = Images::prepare( - preview, - maxW * cIntRetinaFactor(), - maxH * cIntRetinaFactor(), - Images::Option::Smooth | Images::Option::Blurred, - maxW, - maxH); - } - auto originalWidth = preview.width(); - auto originalHeight = preview.height(); - if (!originalWidth || !originalHeight) { - originalWidth = originalHeight = 1; - } - _previewWidth = st::sendMediaPreviewSize; - if (preview.width() < _previewWidth) { - _previewWidth = qMax(preview.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; - - preview = std::move(preview).scaled( - _previewWidth * cIntRetinaFactor(), - _previewHeight * cIntRetinaFactor(), - Qt::IgnoreAspectRatio, - Qt::SmoothTransformation); - preview = Images::prepareOpaque(std::move(preview)); - _preview = App::pixmapFromImageInPlace(std::move(preview)); - _preview.setDevicePixelRatio(cRetinaFactor()); - - prepareAnimatedPreview(animatedPreviewPath); -} - -void SingleMediaPreview::prepareAnimatedPreview( - const QString &animatedPreviewPath) { - if (_sticker && _animated) { - const auto box = QSize(_previewWidth, _previewHeight) - * cIntRetinaFactor(); - _lottiePreview = std::make_unique( - Lottie::ReadContent(QByteArray(), animatedPreviewPath), - Lottie::FrameRequest{ box }); - _lottiePreview->updates( - ) | rpl::start_with_next([=] { - update(); - }, lifetime()); - } else if (!animatedPreviewPath.isEmpty()) { - auto callback = [=](Media::Clip::Notification notification) { - clipCallback(notification); - }; - _gifPreview = Media::Clip::MakeReader( - animatedPreviewPath, - std::move(callback)); - } -} - -void SingleMediaPreview::clipCallback(Media::Clip::Notification notification) { - using namespace Media::Clip; - switch (notification) { - case NotificationReinit: { - if (_gifPreview && _gifPreview->state() == State::Error) { - _gifPreview.setBad(); - } - - if (_gifPreview && _gifPreview->ready() && !_gifPreview->started()) { - auto s = QSize(_previewWidth, _previewHeight); - _gifPreview->start(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None); - } - - update(); - } break; - - case NotificationRepaint: { - if (_gifPreview && !_gifPreview->currentDisplayed()) { - update(); - } - } break; - } -} - -void SingleMediaPreview::paintEvent(QPaintEvent *e) { - Painter p(this); - - if (!_sticker) { - 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); - } - } - if (_gifPreview && _gifPreview->started()) { - auto s = QSize(_previewWidth, _previewHeight); - auto paused = _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Layer); - auto frame = _gifPreview->current(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None, paused ? 0 : crl::now()); - p.drawPixmap(_previewLeft, st::boxPhotoPadding.top(), frame); - } else if (_lottiePreview && _lottiePreview->ready()) { - const auto frame = _lottiePreview->frame(); - const auto size = frame.size() / cIntRetinaFactor(); - p.drawImage( - QRect( - _previewLeft + (_previewWidth - size.width()) / 2, - st::boxPhotoPadding.top() + (_previewHeight - size.height()) / 2, - size.width(), - size.height()), - frame); - _lottiePreview->markFrameShown(); - } else { - p.drawPixmap(_previewLeft, st::boxPhotoPadding.top(), _preview); - } - if (_animated && !_gifPreview && !_lottiePreview) { - 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); - } -} - -rpl::producer SingleMediaPreview::desiredHeightValue() const { - return rpl::single(st::boxPhotoPadding.top() + _previewHeight); -} - rpl::producer FieldPlaceholder( const Ui::PreparedList &list, SendFilesWay way) { @@ -410,7 +156,11 @@ void SendFilesBox::prepareSingleFilePreview() { Expects(IsSingleItem(_list)); const auto &file = _list.files[0]; - const auto media = SingleMediaPreview::Create(this, _controller, file); + const auto controller = _controller; + const auto media = Ui::SingleMediaPreview::Create(this, [=] { + return controller->isGifPausedAtLeastFor( + Window::GifPauseReason::Layer); + }, file); if (media) { if (!media->canSendAsPhoto()) { _compressConfirm = CompressConfirm::None; diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp index 6bdad1c90..85b77c712 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/fade_wrap.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" +#include "ui/chat/attach/attach_prepare.h" #include "ui/text/text_utilities.h" // Ui::Text::ToUpper #include "ui/text/text_options.h" #include "core/file_utilities.h" @@ -41,7 +42,7 @@ std::variant ProcessImage(QByteArray &&bytes) { auto image = App::readImage(base::take(bytes)); if (image.isNull()) { return ReadScanError::CantReadImage; - } else if (!Storage::ValidateThumbDimensions(image.width(), image.height())) { + } else if (!Ui::ValidateThumbDimensions(image.width(), image.height())) { return ReadScanError::BadImageSize; } if (std::max(image.width(), image.height()) > kMaxDimensions) { diff --git a/Telegram/SourceFiles/storage/localimageloader.cpp b/Telegram/SourceFiles/storage/localimageloader.cpp index 782e73fb3..d9c8a52a7 100644 --- a/Telegram/SourceFiles/storage/localimageloader.cpp +++ b/Telegram/SourceFiles/storage/localimageloader.cpp @@ -40,7 +40,7 @@ constexpr auto kThumbnailQuality = 87; constexpr auto kThumbnailSize = 320; constexpr auto kPhotoUploadPartSize = 32 * 1024; -using Storage::ValidateThumbDimensions; +using Ui::ValidateThumbDimensions; struct PreparedFileThumbnail { uint64 id = 0; diff --git a/Telegram/SourceFiles/storage/storage_media_prepare.cpp b/Telegram/SourceFiles/storage/storage_media_prepare.cpp index 629ea4766..ebd65091c 100644 --- a/Telegram/SourceFiles/storage/storage_media_prepare.cpp +++ b/Telegram/SourceFiles/storage/storage_media_prepare.cpp @@ -43,13 +43,13 @@ bool ValidPhotoForAlbum( } const auto width = image.data.width(); const auto height = image.data.height(); - return ValidateThumbDimensions(width, height); + return Ui::ValidateThumbDimensions(width, height); } bool ValidVideoForAlbum(const PreparedFileInformation::Video &video) { const auto width = video.thumbnail.width(); const auto height = video.thumbnail.height(); - return ValidateThumbDimensions(width, height); + return Ui::ValidateThumbDimensions(width, height); } QSize PrepareShownDimensions(const QImage &preview) { @@ -163,13 +163,6 @@ bool ValidateDragData(not_null data, bool isAlbum) { return true; } -bool ValidateThumbDimensions(int width, int height) { - return (width > 0) - && (height > 0) - && (width < 20 * height) - && (height < 20 * width); -} - MimeDataState ComputeMimeDataState(const QMimeData *data) { if (!data || data->hasFormat(qsl("application/x-td-forward"))) { return MimeDataState::None; @@ -272,7 +265,7 @@ PreparedList PrepareMediaFromImage( QByteArray &&content, int previewWidth) { auto result = Storage::PreparedList(); - result.allFilesForCompress = ValidateThumbDimensions( + result.allFilesForCompress = Ui::ValidateThumbDimensions( image.width(), image.height()); auto file = PreparedFile(QString()); @@ -345,7 +338,7 @@ std::optional PreparedFileFromFilesDialog( const auto media = &file.information->media; const auto valid = v::match(*media, [](const Info::Image &data) { - return Storage::ValidateThumbDimensions( + return Ui::ValidateThumbDimensions( data.data.width(), data.data.height()) && !data.animated; diff --git a/Telegram/SourceFiles/storage/storage_media_prepare.h b/Telegram/SourceFiles/storage/storage_media_prepare.h index d07d90abe..98b298a3b 100644 --- a/Telegram/SourceFiles/storage/storage_media_prepare.h +++ b/Telegram/SourceFiles/storage/storage_media_prepare.h @@ -32,7 +32,6 @@ std::optional PreparedFileFromFilesDialog( int previewWidth); MimeDataState ComputeMimeDataState(const QMimeData *data); bool ValidateDragData(not_null data, bool isAlbum); -bool ValidateThumbDimensions(int width, int height); Ui::PreparedList PrepareMediaList(const QList &files, int previewWidth); Ui::PreparedList PrepareMediaList(const QStringList &files, int previewWidth); Ui::PreparedList PrepareMediaFromImage( diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp index 94a03753d..06712deb9 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp @@ -88,4 +88,11 @@ int MaxAlbumItems() { return kMaxAlbumCount; } +bool ValidateThumbDimensions(int width, int height) { + return (width > 0) + && (height > 0) + && (width < 20 * height) + && (height < 20 * width); +} + } // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h index 2853996cd..05f9e3db0 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h @@ -81,5 +81,6 @@ struct PreparedList { }; [[nodiscard]] int MaxAlbumItems(); +[[nodiscard]] bool ValidateThumbDimensions(int width, int height); } // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.cpp new file mode 100644 index 000000000..18b41cbb9 --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.cpp @@ -0,0 +1,235 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/chat/attach/attach_single_media_preview.h" + +#include "ui/chat/attach/attach_prepare.h" +#include "core/mime_type.h" +#include "lottie/lottie_single_player.h" +#include "styles/style_boxes.h" +#include "styles/style_chat.h" +#include "styles/style_layers.h" + +namespace Ui { +namespace { + +constexpr auto kMinPreviewWidth = 20; + +} // namespace + +SingleMediaPreview *SingleMediaPreview::Create( + QWidget *parent, + Fn gifPaused, + const PreparedFile &file) { + auto preview = QImage(); + bool animated = false; + bool animationPreview = false; + if (const auto image = std::get_if( + &file.information->media)) { + preview = image->data; + animated = animationPreview = image->animated; + } else if (const auto video = std::get_if( + &file.information->media)) { + preview = video->thumbnail; + animated = true; + animationPreview = video->isGifv; + } + if (preview.isNull()) { + return nullptr; + } else if (!animated && !ValidateThumbDimensions( + preview.width(), + preview.height())) { + return nullptr; + } + return CreateChild( + parent, + std::move(gifPaused), + preview, + animated, + Core::IsMimeSticker(file.information->filemime), + animationPreview ? file.path : QString()); +} + +SingleMediaPreview::SingleMediaPreview( + QWidget *parent, + Fn gifPaused, + QImage preview, + bool animated, + bool sticker, + const QString &animatedPreviewPath) +: RpWidget(parent) +, _gifPaused(std::move(gifPaused)) +, _animated(animated) +, _sticker(sticker) { + Expects(!preview.isNull()); + + _canSendAsPhoto = !_animated + && !_sticker + && ValidateThumbDimensions( + preview.width(), + preview.height()); + + preparePreview(preview, animatedPreviewPath); +} + +SingleMediaPreview::~SingleMediaPreview() = default; + +void SingleMediaPreview::preparePreview( + QImage preview, + const QString &animatedPreviewPath) { + auto maxW = 0; + auto maxH = 0; + if (_animated && !_sticker) { + auto limitW = st::sendMediaPreviewSize; + auto limitH = st::confirmMaxHeight; + maxW = qMax(preview.width(), 1); + maxH = qMax(preview.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; + } + } + preview = Images::prepare( + preview, + maxW * style::DevicePixelRatio(), + maxH * style::DevicePixelRatio(), + Images::Option::Smooth | Images::Option::Blurred, + maxW, + maxH); + } + auto originalWidth = preview.width(); + auto originalHeight = preview.height(); + if (!originalWidth || !originalHeight) { + originalWidth = originalHeight = 1; + } + _previewWidth = st::sendMediaPreviewSize; + if (preview.width() < _previewWidth) { + _previewWidth = qMax(preview.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; + + preview = std::move(preview).scaled( + _previewWidth * style::DevicePixelRatio(), + _previewHeight * style::DevicePixelRatio(), + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + preview = Images::prepareOpaque(std::move(preview)); + _preview = PixmapFromImage(std::move(preview)); + _preview.setDevicePixelRatio(style::DevicePixelRatio()); + + prepareAnimatedPreview(animatedPreviewPath); +} + +void SingleMediaPreview::prepareAnimatedPreview( + const QString &animatedPreviewPath) { + if (_sticker && _animated) { + const auto box = QSize(_previewWidth, _previewHeight) + * style::DevicePixelRatio(); + _lottiePreview = std::make_unique( + Lottie::ReadContent(QByteArray(), animatedPreviewPath), + Lottie::FrameRequest{ box }); + _lottiePreview->updates( + ) | rpl::start_with_next([=] { + update(); + }, lifetime()); + } else if (!animatedPreviewPath.isEmpty()) { + auto callback = [=](Media::Clip::Notification notification) { + clipCallback(notification); + }; + _gifPreview = Media::Clip::MakeReader( + animatedPreviewPath, + std::move(callback)); + } +} + +void SingleMediaPreview::clipCallback(Media::Clip::Notification notification) { + using namespace Media::Clip; + switch (notification) { + case NotificationReinit: { + if (_gifPreview && _gifPreview->state() == State::Error) { + _gifPreview.setBad(); + } + + if (_gifPreview && _gifPreview->ready() && !_gifPreview->started()) { + auto s = QSize(_previewWidth, _previewHeight); + _gifPreview->start(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None); + } + + update(); + } break; + + case NotificationRepaint: { + if (_gifPreview && !_gifPreview->currentDisplayed()) { + update(); + } + } break; + } +} + +void SingleMediaPreview::paintEvent(QPaintEvent *e) { + Painter p(this); + + if (!_sticker) { + 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); + } + } + if (_gifPreview && _gifPreview->started()) { + auto s = QSize(_previewWidth, _previewHeight); + auto paused = _gifPaused(); + auto frame = _gifPreview->current(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None, paused ? 0 : crl::now()); + p.drawPixmap(_previewLeft, st::boxPhotoPadding.top(), frame); + } else if (_lottiePreview && _lottiePreview->ready()) { + const auto frame = _lottiePreview->frame(); + const auto size = frame.size() / style::DevicePixelRatio(); + p.drawImage( + QRect( + _previewLeft + (_previewWidth - size.width()) / 2, + st::boxPhotoPadding.top() + (_previewHeight - size.height()) / 2, + size.width(), + size.height()), + frame); + _lottiePreview->markFrameShown(); + } else { + p.drawPixmap(_previewLeft, st::boxPhotoPadding.top(), _preview); + } + if (_animated && !_gifPreview && !_lottiePreview) { + 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); + } +} + +rpl::producer SingleMediaPreview::desiredHeightValue() const { + return rpl::single(st::boxPhotoPadding.top() + _previewHeight); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.h b/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.h new file mode 100644 index 000000000..17e035e8e --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.h @@ -0,0 +1,66 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/rp_widget.h" +#include "media/clip/media_clip_reader.h" + +namespace Lottie { +class SinglePlayer; +} // namespace Lottie + +namespace Ui { + +struct PreparedFile; + +class SingleMediaPreview : public RpWidget { +public: + static SingleMediaPreview *Create( + QWidget *parent, + Fn gifPaused, + const PreparedFile &file); + + SingleMediaPreview( + QWidget *parent, + Fn gifPaused, + QImage preview, + bool animated, + bool sticker, + const QString &animatedPreviewPath); + ~SingleMediaPreview(); + + bool canSendAsPhoto() const { + return _canSendAsPhoto; + } + + rpl::producer desiredHeightValue() const override; + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + void preparePreview( + QImage preview, + const QString &animatedPreviewPath); + void prepareAnimatedPreview(const QString &animatedPreviewPath); + void clipCallback(Media::Clip::Notification notification); + + Fn _gifPaused; + bool _animated = false; + bool _sticker = false; + bool _canSendAsPhoto = false; + QPixmap _preview; + int _previewLeft = 0; + int _previewWidth = 0; + int _previewHeight = 0; + Media::Clip::ReaderPointer _gifPreview; + std::unique_ptr _lottiePreview; + +}; + +} // namespace Ui diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 8a19ce621..ac073df9c 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -75,6 +75,8 @@ PRIVATE ui/chat/attach/attach_prepare.h ui/chat/attach/attach_single_file_preview.cpp ui/chat/attach/attach_single_file_preview.h + ui/chat/attach/attach_single_media_preview.cpp + ui/chat/attach/attach_single_media_preview.h ui/chat/message_bar.cpp ui/chat/message_bar.h ui/chat/pinned_bar.cpp @@ -107,4 +109,5 @@ PUBLIC tdesktop::td_lang desktop-app::lib_ui desktop-app::lib_ffmpeg + desktop-app::lib_lottie ) From 3feea400afc6c770fefbdcb077ec0b5515e0038a Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 14 Oct 2020 11:28:17 +0300 Subject: [PATCH 092/190] Fix build on macOS. --- Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp | 4 ++-- Telegram/SourceFiles/history/view/history_view_pinned_bar.h | 3 +++ Telegram/SourceFiles/platform/mac/file_bookmark_mac.h | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp index a31713bbc..267a9513f 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp @@ -27,7 +27,7 @@ namespace { const auto poll = media ? media->poll() : nullptr; return Ui::MessageBarContent{ .id = item->id, - .text = item->inReplyText(), + .text = { item->inReplyText() }, }; } @@ -102,7 +102,7 @@ namespace { } auto load = rpl::make_producer([=](auto consumer) { consumer.put_next(Ui::MessageBarContent{ - .text = tr::lng_contacts_loading(tr::now), + .text = { tr::lng_contacts_loading(tr::now) }, }); const auto channel = id.channel ? session->data().channel(id.channel).get() diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_bar.h b/Telegram/SourceFiles/history/view/history_view_pinned_bar.h index 0995d1cb5..d09394e7e 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_bar.h +++ b/Telegram/SourceFiles/history/view/history_view_pinned_bar.h @@ -38,6 +38,9 @@ struct PinnedBarId { bool operator==(const PinnedBarId &other) const { return std::tie(message, type) == std::tie(other.message, other.type); } + bool operator!=(const PinnedBarId &other) const { + return !(*this == other); + } }; [[nodiscard]] rpl::producer PinnedBarContent( not_null session, diff --git a/Telegram/SourceFiles/platform/mac/file_bookmark_mac.h b/Telegram/SourceFiles/platform/mac/file_bookmark_mac.h index a60971184..e72a8b2c5 100644 --- a/Telegram/SourceFiles/platform/mac/file_bookmark_mac.h +++ b/Telegram/SourceFiles/platform/mac/file_bookmark_mac.h @@ -16,7 +16,7 @@ public: [[nodiscard]] bool check() const; bool enable() const; - void disable(); + void disable() const; [[nodiscard]] const QString &name(const QString &original) const; [[nodiscard]] QByteArray bookmark() const; From 7f956d32a6ffc271826493b114c170fd499551d8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 14 Oct 2020 16:01:07 +0300 Subject: [PATCH 093/190] Support slot machine game. --- Telegram/CMakeLists.txt | 2 + Telegram/SourceFiles/api/api_sending.cpp | 6 +- .../chat_helpers/stickers_dice_pack.cpp | 20 +- .../chat_helpers/stickers_dice_pack.h | 12 +- .../SourceFiles/data/data_media_types.cpp | 63 +++++- Telegram/SourceFiles/data/data_media_types.h | 6 + .../history/view/media/history_view_dice.cpp | 50 +---- .../view/media/history_view_slot_machine.cpp | 200 ++++++++++++++++++ .../view/media/history_view_slot_machine.h | 79 +++++++ .../view/media/history_view_sticker.cpp | 6 +- 10 files changed, 381 insertions(+), 63 deletions(-) create mode 100644 Telegram/SourceFiles/history/view/media/history_view_slot_machine.cpp create mode 100644 Telegram/SourceFiles/history/view/media/history_view_slot_machine.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 94741dda3..f27aeb137 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -508,6 +508,8 @@ PRIVATE history/view/media/history_view_photo.cpp history/view/media/history_view_poll.h history/view/media/history_view_poll.cpp + history/view/media/history_view_slot_machine.h + history/view/media/history_view_slot_machine.cpp history/view/media/history_view_sticker.h history/view/media/history_view_sticker.cpp history/view/media/history_view_theme_document.h diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index f33429433..becdfceb8 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_message.h" // NewMessageFlags. #include "chat_helpers/message_field.h" // ConvertTextTagsToEntities. +#include "chat_helpers/stickers_dice_pack.h" // DicePacks::kDiceString. #include "ui/text/text_entity.h" // TextWithEntities. #include "ui/item_text_options.h" // Ui::ItemTextOptions. #include "main/main_session.h" @@ -221,8 +222,9 @@ bool SendDice(Api::MessageToSend &message) { auto &account = message.action.history->session().account(); auto &config = account.appConfig(); static const auto hardcoded = std::vector{ - QString::fromUtf8("\xF0\x9F\x8E\xB2"), - QString::fromUtf8("\xF0\x9F\x8E\xAF") + Stickers::DicePacks::kDiceString, + Stickers::DicePacks::kDartString, + Stickers::DicePacks::kSlotString, }; const auto list = config.get>( "emojies_send_dice", diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp index 083778c9e..59a5c8ba4 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp @@ -20,6 +20,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Stickers { +const QString DicePacks::kDiceString = QString::fromUtf8("\xF0\x9F\x8E\xB2"); +const QString DicePacks::kDartString = QString::fromUtf8("\xF0\x9F\x8E\xAF"); +const QString DicePacks::kSlotString = QString::fromUtf8("\xF0\x9F\x8E\xB0"); + DicePack::DicePack(not_null session, const QString &emoji) : _session(session) , _emoji(emoji) { @@ -54,13 +58,21 @@ void DicePack::load() { void DicePack::applySet(const MTPDmessages_stickerSet &data) { _map.clear(); auto documents = base::flat_map>(); + const auto isSlotMachine = DicePacks::IsSlot(_emoji); for (const auto &sticker : data.vdocuments().v) { const auto document = _session->data().processDocument( sticker); if (document->sticker()) { - documents.emplace(document->id, document); + if (isSlotMachine) { + _map.emplace(_map.size(), document); + } else { + documents.emplace(document->id, document); + } } } + if (isSlotMachine) { + return; + } for (const auto pack : data.vpacks().v) { pack.match([&](const MTPDstickerPack &data) { const auto emoji = qs(data.vemoticon()); @@ -86,11 +98,9 @@ void DicePack::tryGenerateLocalZero() { return; } - static const auto kDiceString = QString::fromUtf8("\xF0\x9F\x8E\xB2"); - static const auto kDartString = QString::fromUtf8("\xF0\x9F\x8E\xAF"); - const auto path = (_emoji == kDiceString) + const auto path = (_emoji == DicePacks::kDiceString) ? qsl(":/gui/art/dice_idle.tgs") - : (_emoji == kDartString) + : (_emoji == DicePacks::kDartString) ? qsl(":/gui/art/dart_idle.tgs") : QString(); if (path.isEmpty()) { diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h index 744a6d65e..a1556ee25 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h @@ -21,7 +21,7 @@ public: DicePack(not_null session, const QString &emoji); ~DicePack(); - DocumentData *lookup(int value); + [[nodiscard]] DocumentData *lookup(int value); private: void load(); @@ -39,7 +39,15 @@ class DicePacks final { public: explicit DicePacks(not_null session); - DocumentData *lookup(const QString &emoji, int value); + static const QString kDiceString; + static const QString kDartString; + static const QString kSlotString; + + [[nodiscard]] static bool IsSlot(const QString &emoji) { + return (emoji == kSlotString); + } + + [[nodiscard]] DocumentData *lookup(const QString &emoji, int value); private: const not_null _session; diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index c87b7b733..7a89c6252 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -23,13 +23,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_web_page.h" #include "history/view/media/history_view_poll.h" #include "history/view/media/history_view_theme_document.h" +#include "history/view/media/history_view_slot_machine.h" #include "history/view/media/history_view_dice.h" #include "ui/image/image.h" #include "ui/text/format_values.h" #include "ui/text/text_options.h" +#include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" #include "ui/emoji_config.h" +#include "api/api_sending.h" #include "storage/storage_shared_media.h" #include "storage/localstorage.h" +#include "chat_helpers/stickers_dice_pack.h" // Stickers::DicePacks::IsSlot. #include "data/data_session.h" #include "data/data_photo.h" #include "data/data_document.h" @@ -42,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "storage/file_upload.h" #include "app.h" +#include "styles/style_chat.h" namespace Data { namespace { @@ -1339,9 +1345,60 @@ std::unique_ptr MediaDice::createView( not_null message, not_null realParent, HistoryView::Element *replacing) { - return std::make_unique( - message, - std::make_unique(message, this)); + return ::Stickers::DicePacks::IsSlot(_emoji) + ? std::make_unique( + message, + std::make_unique(message, this)) + : std::make_unique( + message, + std::make_unique(message, this)); +} + +ClickHandlerPtr MediaDice::makeHandler() const { + return MakeHandler(parent()->history(), _emoji); +} + +ClickHandlerPtr MediaDice::MakeHandler( + not_null history, + const QString &emoji) { + static auto ShownToast = base::weak_ptr(); + static const auto HideExisting = [] { + if (const auto toast = ShownToast.get()) { + toast->hideAnimated(); + ShownToast = nullptr; + } + }; + return std::make_shared([=] { + auto config = Ui::Toast::Config{ + .text = { tr::lng_about_random(tr::now, lt_emoji, emoji) }, + .st = &st::historyDiceToast, + .durationMs = Ui::Toast::kDefaultDuration * 2, + .multiline = true, + }; + if (history->peer->canWrite()) { + auto link = Ui::Text::Link( + tr::lng_about_random_send(tr::now).toUpper()); + link.entities.push_back( + EntityInText(EntityType::Semibold, 0, link.text.size())); + config.text.append(' ').append(std::move(link)); + config.filter = crl::guard(&history->session(), [=]( + const ClickHandlerPtr &handler, + Qt::MouseButton button) { + if (button == Qt::LeftButton && !ShownToast.empty()) { + auto message = Api::MessageToSend(history); + message.action.clearDraft = false; + message.textWithTags.text = emoji; + + Api::SendDice(message); + HideExisting(); + } + return false; + }); + } + + HideExisting(); + ShownToast = Ui::Toast::Show(config); + }); } } // namespace Data diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 5037a2592..ed139bbcf 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_location.h" class Image; +class History; class HistoryItem; namespace base { @@ -447,6 +448,11 @@ public: not_null realParent, HistoryView::Element *replacing = nullptr) override; + [[nodiscard]] ClickHandlerPtr makeHandler() const; + [[nodiscard]] static ClickHandlerPtr MakeHandler( + not_null history, + const QString &emoji); + private: QString _emoji; int _value = 0; diff --git a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp index 3a79c6924..9c823b0b9 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp @@ -9,15 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "chat_helpers/stickers_dice_pack.h" -#include "api/api_sending.h" -#include "api/api_common.h" #include "history/history.h" #include "history/history_item.h" #include "history/history_item_components.h" #include "history/view/history_view_element.h" -#include "ui/toast/toast.h" -#include "ui/text/text_utilities.h" -#include "lang/lang_keys.h" #include "main/main_session.h" #include "styles/style_chat.h" @@ -32,49 +27,6 @@ namespace { return session.diceStickersPacks().lookup(emoji, value); } -[[nodiscard]] ClickHandlerPtr MakeDiceHandler( - not_null history, - const QString &emoji) { - static auto ShownToast = base::weak_ptr(); - static const auto HideExisting = [] { - if (const auto toast = ShownToast.get()) { - toast->hideAnimated(); - ShownToast = nullptr; - } - }; - return std::make_shared([=] { - auto config = Ui::Toast::Config{ - .text = { tr::lng_about_random(tr::now, lt_emoji, emoji) }, - .st = &st::historyDiceToast, - .durationMs = Ui::Toast::kDefaultDuration * 2, - .multiline = true, - }; - if (history->peer->canWrite()) { - auto link = Ui::Text::Link( - tr::lng_about_random_send(tr::now).toUpper()); - link.entities.push_back( - EntityInText(EntityType::Semibold, 0, link.text.size())); - config.text.append(' ').append(std::move(link)); - config.filter = crl::guard(&history->session(), [=]( - const ClickHandlerPtr &handler, - Qt::MouseButton button) { - if (button == Qt::LeftButton && !ShownToast.empty()) { - auto message = Api::MessageToSend(history); - message.action.clearDraft = false; - message.textWithTags.text = emoji; - - Api::SendDice(message); - HideExisting(); - } - return false; - }); - } - - HideExisting(); - ShownToast = Ui::Toast::Show(config); - }); -} - } // namespace Dice::Dice(not_null parent, not_null dice) @@ -82,7 +34,7 @@ Dice::Dice(not_null parent, not_null dice) , _dice(dice) , _link(_parent->data()->Has() ? nullptr - : MakeDiceHandler(_parent->history(), dice->emoji())) { + : dice->makeHandler()) { if (const auto document = Lookup(parent, dice->emoji(), 0)) { _start.emplace(parent, document); _start->setDiceIndex(_dice->emoji(), 0); diff --git a/Telegram/SourceFiles/history/view/media/history_view_slot_machine.cpp b/Telegram/SourceFiles/history/view/media/history_view_slot_machine.cpp new file mode 100644 index 000000000..67a2f2743 --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_slot_machine.cpp @@ -0,0 +1,200 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/view/media/history_view_slot_machine.h" + +#include "data/data_session.h" +#include "chat_helpers/stickers_dice_pack.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/history_item_components.h" +#include "history/view/history_view_element.h" +#include "main/main_session.h" +#include "styles/style_chat.h" + +namespace HistoryView { +namespace { + +constexpr auto kStartBackIndex = 0; +constexpr auto kWinBackIndex = 1; +constexpr auto kPullIndex = 2; +constexpr auto kShifts = std::array{ 3, 9, 15 }; +constexpr auto kSevenWinIndex = 0; +constexpr auto kSevenIndex = 1; +constexpr auto kBarIndex = 2; +constexpr auto kBerriesIndex = 3; +constexpr auto kLemonIndex = 4; +constexpr auto kStartIndex = 5; + +constexpr auto kWinValue = 64; + +const auto &kEmoji = ::Stickers::DicePacks::kSlotString; + +[[nodiscard]] DocumentData *Lookup( + not_null view, + int value) { + const auto &session = view->data()->history()->session(); + return session.diceStickersPacks().lookup(kEmoji, value); +} + +[[nodiscard]] int ComplexIndex(int partIndex, int inPartIndex) { + Expects(partIndex >= 0 && partIndex < 3); + + return kShifts[partIndex] + inPartIndex; +} + +[[nodiscard]] int ComputeComplexIndex(int value, int partIndex) { + Expects(value > 0 && value <= 64); + + if (value == kWinValue) { + return ComplexIndex(partIndex, kSevenWinIndex); + } + const auto bits = ((value - 1) >> (partIndex * 2)) & 0x03; // 0..3 + return ComplexIndex(partIndex, [&] { + switch (bits) { + case 0: return kBarIndex; + case 1: return kBerriesIndex; + case 2: return kLemonIndex; + case 3: return kSevenIndex; + } + Unexpected("Bits value in ComputeComplexIndex."); + }()); +} + +} // namespace + +SlotMachine::SlotMachine( + not_null parent, + not_null dice) +: _parent(parent) +, _dice(dice) +, _link(_parent->data()->Has() + ? nullptr + : dice->makeHandler()) { + resolveStarts(); + _showLastFrame = _parent->data()->Has(); + if (_showLastFrame) { + for (auto &drawingEnd : _drawingEnd) { + drawingEnd = true; + } + } +} + +SlotMachine::~SlotMachine() = default; + +void SlotMachine::resolve( + std::optional &sticker, + int singleTimeIndex, + int index, + bool initSize) const { + if (sticker) { + return; + } + const auto document = Lookup(_parent, index); + if (!document) { + return; + } + sticker.emplace(_parent, document); + sticker->setDiceIndex(kEmoji, singleTimeIndex); + if (initSize) { + sticker->initSize(); + } +} + +void SlotMachine::resolveStarts(bool initSize) { + resolve(_pull, kPullIndex, kPullIndex, initSize); + resolve(_start[0], 0, kStartBackIndex, initSize); + for (auto i = 0; i != 3; ++i) { + resolve(_start[i + 1], 0, ComplexIndex(i, kStartIndex), initSize); + } +} + +void SlotMachine::resolveEnds(int value) { + if (value <= 0 || value > 64) { + return; + } + if (value == kWinValue) { + resolve(_end[0], kWinBackIndex, kWinBackIndex, true); + } + for (auto i = 0; i != 3; ++i) { + const auto index = ComputeComplexIndex(value, i); + resolve(_end[i + 1], index, index, true); + } +} + +bool SlotMachine::isEndResolved() const { + for (auto i = 0; i != 3; ++i) { + if (!_end[i + 1]) { + return false; + } + } + return _end[0].has_value() || (_dice->value() != kWinValue); +} + +QSize SlotMachine::size() { + return _pull + ? _pull->size() + : Sticker::GetAnimatedEmojiSize(&_parent->history()->session()); +} + +ClickHandlerPtr SlotMachine::link() { + return _link; +} + +void SlotMachine::draw(Painter &p, const QRect &r, bool selected) { + resolveStarts(true); + resolveEnds(_dice->value()); + + //const auto endResolved = isEndResolved(); + //if (!endResolved) { + // for (auto &drawingEnd : _drawingEnd) { + // drawingEnd = false; + // } + //} + auto switchedToEnd = _drawingEnd; + const auto pullReady = _pull && _pull->readyToDrawLottie(); + const auto paintReady = [&] { + auto result = pullReady; + for (auto i = 1; i != 4; ++i) { + if (!_end[i] || !_end[i]->readyToDrawLottie()) { + switchedToEnd[i] = false; + } + if (!switchedToEnd[i] + && (!_start[i] || !_start[i]->readyToDrawLottie())) { + result = false; + } + } + if (!_end[0] || !_end[0]->readyToDrawLottie()) { + switchedToEnd[0] = false; + } + if (ranges::contains(switchedToEnd, false) + && (!_start[0] || !_start[0]->readyToDrawLottie())) { + result = false; + } + return result; + }(); + + if (!paintReady) { + return; + } + + for (auto i = 0; i != 4; ++i) { + if (switchedToEnd[i]) { + _end[i]->draw(p, r, selected); + } else { + _start[i]->draw(p, r, selected); + if (_end[i] + && _end[i]->readyToDrawLottie() + && _start[i]->atTheEnd()) { + _drawingEnd[i] = true; + } + } + } + _pull->draw(p, r, selected); +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_slot_machine.h b/Telegram/SourceFiles/history/view/media/history_view_slot_machine.h new file mode 100644 index 000000000..fad54f1c3 --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_slot_machine.h @@ -0,0 +1,79 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "history/view/media/history_view_media_unwrapped.h" +#include "history/view/media/history_view_sticker.h" + +namespace Data { +class MediaDice; +} // namespace Data + +namespace HistoryView { + +class SlotMachine final : public UnwrappedMedia::Content { +public: + SlotMachine(not_null parent, not_null dice); + ~SlotMachine(); + + QSize size() override; + void draw(Painter &p, const QRect &r, bool selected) override; + + ClickHandlerPtr link() override; + + bool hasHeavyPart() const override { + if (_pull && _pull->hasHeavyPart()) { + return true; + } + for (auto i = 0; i != 4; ++i) { + if ((_start[i] && _start[i]->hasHeavyPart()) + || (_end[i] && _end[i]->hasHeavyPart())) { + return true; + } + } + return false; + } + void unloadHeavyPart() override { + if (_pull) { + _pull->unloadHeavyPart(); + } + for (auto i = 0; i != 4; ++i) { + if (_start[i]) { + _start[i]->unloadHeavyPart(); + } + if (_end[i]) { + _end[i]->unloadHeavyPart(); + } + } + } + bool hidesForwardedInfo() override { + return false; + } + +private: + void resolveStarts(bool initSize = false); + void resolveEnds(int value); + [[nodiscard]] bool isEndResolved() const; + void resolve( + std::optional &sticker, + int singleTimeIndex, + int index, + bool initSize) const; + + const not_null _parent; + const not_null _dice; + ClickHandlerPtr _link; + std::optional _pull; + std::array, 4> _start; + std::array, 4> _end; + mutable bool _showLastFrame = false; + mutable std::array _drawingEnd = { { false } }; + +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp index 15ee3cb28..6cc2e8cc2 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp @@ -109,7 +109,9 @@ void Sticker::initSize() { } QSize Sticker::size() { - initSize(); + if (_size.isEmpty()) { + initSize(); + } return _size; } @@ -301,7 +303,7 @@ void Sticker::setupLottie() { _dataMedia.get(), _replacements, ChatHelpers::StickerLottieSize::MessageHistory, - _size * cIntRetinaFactor(), + size() * cIntRetinaFactor(), Lottie::Quality::High); lottieCreated(); } From c2f0bcf933ed9d4913b10e0e501151eea6619e4d Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 14 Oct 2020 18:12:24 +0300 Subject: [PATCH 094/190] Keep first game sticker frames inside the binary. --- Telegram/Resources/art/bball_idle.tgs | Bin 0 -> 23121 bytes Telegram/Resources/art/fball_idle.tgs | Bin 0 -> 42220 bytes Telegram/Resources/art/slot_0_idle.tgs | Bin 0 -> 9745 bytes Telegram/Resources/art/slot_1_idle.tgs | Bin 0 -> 6191 bytes Telegram/Resources/art/slot_2_idle.tgs | Bin 0 -> 9730 bytes Telegram/Resources/art/slot_back.tgs | Bin 0 -> 1945 bytes Telegram/Resources/art/slot_pull.tgs | Bin 0 -> 5484 bytes Telegram/Resources/qrc/telegram/telegram.qrc | 7 +++ Telegram/SourceFiles/api/api_sending.cpp | 3 ++ .../chat_helpers/stickers_dice_pack.cpp | 48 ++++++++++++------ .../chat_helpers/stickers_dice_pack.h | 3 ++ .../history/view/media/history_view_dice.cpp | 4 +- .../view/media/history_view_slot_machine.cpp | 4 +- .../SourceFiles/storage/localimageloader.cpp | 15 +++--- .../SourceFiles/storage/localimageloader.h | 11 +++- 15 files changed, 64 insertions(+), 31 deletions(-) create mode 100644 Telegram/Resources/art/bball_idle.tgs create mode 100644 Telegram/Resources/art/fball_idle.tgs create mode 100644 Telegram/Resources/art/slot_0_idle.tgs create mode 100644 Telegram/Resources/art/slot_1_idle.tgs create mode 100644 Telegram/Resources/art/slot_2_idle.tgs create mode 100644 Telegram/Resources/art/slot_back.tgs create mode 100644 Telegram/Resources/art/slot_pull.tgs diff --git a/Telegram/Resources/art/bball_idle.tgs b/Telegram/Resources/art/bball_idle.tgs new file mode 100644 index 0000000000000000000000000000000000000000..e82b626397930fa0e5b375eaf8143f9f2fb6dea4 GIT binary patch literal 23121 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 glufKo|0QkAdMcu^1l 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 KMRL?v!UzD|5zg=c literal 0 HcmV?d00001 diff --git a/Telegram/Resources/art/slot_0_idle.tgs b/Telegram/Resources/art/slot_0_idle.tgs new file mode 100644 index 0000000000000000000000000000000000000000..1d8a0955f1751a40a3a983f77b5f9757a2a76939 GIT binary patch literal 9745 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/art/slot_1_idle.tgs b/Telegram/Resources/art/slot_1_idle.tgs new file mode 100644 index 0000000000000000000000000000000000000000..b6549225f778c0bc7f253fc6d49d38fc782d1665 GIT binary patch literal 6191 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/art/slot_2_idle.tgs b/Telegram/Resources/art/slot_2_idle.tgs new file mode 100644 index 0000000000000000000000000000000000000000..c13193e81aef1e989a3931f7ba45b22c265562b9 GIT binary patch literal 9730 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/art/slot_back.tgs b/Telegram/Resources/art/slot_back.tgs new file mode 100644 index 0000000000000000000000000000000000000000..5eaa214a3ad0d8c58a4646aa1173ffeaf53745f0 GIT binary patch literal 1945 zcmV;K2WI#miwFP!000021MOSgZ`(!;|5pmV?sPcvzVo<;^>x7ZRs@5$e6CJX*lvfS z=>I;FC&{uUyJ=%5ZtMmhH_i3_j{028KHklLef!Jw_WEY=*X_mhk_NcAxR9Qc`TTN0l=Z>DagT7c%$ab9LsL@n#RxDa)y9J`?79>hq=gz|BN@y{9qIqIPH%2B%GQ z%A9{y%lw{=yAXmb%9KTsVAZam8<<2$@SUL{ zm9+G54+F|4rqqy=_AV>1MqBj6?npJ5tgT5b{7-duHJQ#Yd5Q8CexEFE-T=*gbJ4O? zv)1{dk`DbxHT0lJgBs^e3d-mJIhdG&Pr!HC@O=&`M+N+<9#;B;F<<5L_GpPHQ<{dp z^YmZ2{Cy`pS>GRv*~9zG4J%ym+g$r#X7m#^)YbR`D8$n zh5tF5+|REbW_Q)s^M>?x_VDq8JWBI~!zbDoKEQ*v+4tblQcmbND0JZd(6OzpW7aO% zM@W`7Ivof`L%=cyn|xGtjygmn83)>#&?8{fCif+3nlmNer=+MYZ7Mwq zM9{&76%t9g>=enm(3b(AMfAs65}K(xLbI6ezFBQ|-#*WhV6%?KHaa_`Pl@%50}RgYY$I8$hZ$ugJo4VBVLHGz&H&6$Kr?N=Sf9w&mp#&w30l7LH-R!Gka_K z!IPHkL`!g!tInJw^VPw;$Y~Boq!Ib-2IxaO32RYD+93exvl`sZX%3ije&a)8aBRDM zq>Jnu7a59+OqaVUOTPL;KR6^~$~g}g`llkx(?Fm-E{29=F!qlOn{KVi-KRaZqxLif zF`sg_n33(Cm4n67is6jBOE4p|kSnoW8nt8;{3#r*6NtX;{EK_jMRor^Jwsr zfc-~+cvJ>$^jz$GP;Ex`;;dC#e&Us)F;Hw%?wTw9i>?#sJ_*w8?%sZBqXzSnk!k6`>xWLePMOJ0y#goE--gB#MRO zQfMcDqF~q9jMj$K#J(GEPk?$_h1!1oMz%@TGG0J?E9rTMWZN7W)ns*WJMkO8k$Yc7 z0>ZI+gxetTj8BCAxvRc@9OIgtGJcZ@BU6{3STaDNzqqdCR=@0GF zi4P{waK2AAaE$C6D{+mDDWpuD*ajQW#wH;J@=X(yEZ5oWg zSb`~=IB89gLS6U*McMnwljw@mpK{t0F#awI(Of4AQReJ>^Rgg#RooFIA%TAeL9NbePGv=-Yg#u@l^@2C<1d^W|2~b+Si25LTcLtOI<*nOrr}} z9A6|~nF$o5Lu+(x&c*0LNr|Cp9A=ZWH+dHYFv@JRvwKDTH|Bo**tQ4aFugzx@?N6? ztD{7WT}6x1^LXcPZ&?aJhAc;AO;r!fZZ^zijYD~^uhR3vE}ns1&?OwS|lzJw=}my zo*EmbJ7!+PPCv^B1Ox9j;Bp{E*S7W f7b{$n9kdv(Fmw(`_`uywvN=rrSRz&~+w(iuo literal 0 HcmV?d00001 diff --git a/Telegram/Resources/art/slot_pull.tgs b/Telegram/Resources/art/slot_pull.tgs new file mode 100644 index 0000000000000000000000000000000000000000..478b690679a1a0e6d6a339a9cef85749bacb18d2 GIT binary patch literal 5484 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/qrc/telegram/telegram.qrc b/Telegram/Resources/qrc/telegram/telegram.qrc index 86befe36e..291058f37 100644 --- a/Telegram/Resources/qrc/telegram/telegram.qrc +++ b/Telegram/Resources/qrc/telegram/telegram.qrc @@ -49,6 +49,13 @@ ../../art/sunrise.jpg ../../art/dice_idle.tgs ../../art/dart_idle.tgs + ../../art/bball_idle.tgs + ../../art/fball_idle.tgs + ../../art/slot_0_idle.tgs + ../../art/slot_1_idle.tgs + ../../art/slot_2_idle.tgs + ../../art/slot_back.tgs + ../../art/slot_pull.tgs ../../day-blue.tdesktop-theme ../../night.tdesktop-theme ../../night-green.tdesktop-theme diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index becdfceb8..3ee6026f6 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -225,6 +225,9 @@ bool SendDice(Api::MessageToSend &message) { Stickers::DicePacks::kDiceString, Stickers::DicePacks::kDartString, Stickers::DicePacks::kSlotString, + Stickers::DicePacks::kFballString, + Stickers::DicePacks::kFballString + QChar(0xFE0F), + Stickers::DicePacks::kBballString, }; const auto list = config.get>( "emojies_send_dice", diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp index 59a5c8ba4..631cc8df0 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp @@ -23,6 +23,8 @@ namespace Stickers { const QString DicePacks::kDiceString = QString::fromUtf8("\xF0\x9F\x8E\xB2"); const QString DicePacks::kDartString = QString::fromUtf8("\xF0\x9F\x8E\xAF"); const QString DicePacks::kSlotString = QString::fromUtf8("\xF0\x9F\x8E\xB0"); +const QString DicePacks::kFballString = QString::fromUtf8("\xE2\x9A\xBD"); +const QString DicePacks::kBballString = QString::fromUtf8("\xF0\x9F\x8F\x80"); DicePack::DicePack(not_null session, const QString &emoji) : _session(session) @@ -56,15 +58,15 @@ void DicePack::load() { } void DicePack::applySet(const MTPDmessages_stickerSet &data) { - _map.clear(); - auto documents = base::flat_map>(); const auto isSlotMachine = DicePacks::IsSlot(_emoji); + auto index = 0; + auto documents = base::flat_map>(); for (const auto &sticker : data.vdocuments().v) { const auto document = _session->data().processDocument( sticker); if (document->sticker()) { if (isSlotMachine) { - _map.emplace(_map.size(), document); + _map.emplace(index++, document); } else { documents.emplace(document->id, document); } @@ -98,14 +100,25 @@ void DicePack::tryGenerateLocalZero() { return; } - const auto path = (_emoji == DicePacks::kDiceString) - ? qsl(":/gui/art/dice_idle.tgs") - : (_emoji == DicePacks::kDartString) - ? qsl(":/gui/art/dart_idle.tgs") - : QString(); - if (path.isEmpty()) { - return; + if (_emoji == DicePacks::kDiceString) { + generateLocal(0, u"dice_idle"_q); + } else if (_emoji == DicePacks::kDartString) { + generateLocal(0, u"dart_idle"_q); + } else if (_emoji == DicePacks::kBballString) { + generateLocal(0, u"bball_idle"_q); + } else if (_emoji == DicePacks::kFballString) { + generateLocal(0, u"fball_idle"_q); + } else if (_emoji == DicePacks::kSlotString) { + generateLocal(0, u"slot_back"_q); + generateLocal(2, u"slot_pull"_q); + generateLocal(8, u"slot_0_idle"_q); + generateLocal(14, u"slot_1_idle"_q); + generateLocal(20, u"slot_2_idle"_q); } +} + +void DicePack::generateLocal(int index, const QString &name) { + const auto path = u":/gui/art/"_q + name + u".tgs"_q; auto task = FileLoadTask( _session, path, @@ -114,15 +127,15 @@ void DicePack::tryGenerateLocalZero() { SendMediaType::File, FileLoadTo(0, {}, 0), {}); - task.process(); + task.process({ .generateGoodThumbnail = false }); const auto result = task.peekResult(); Assert(result != nullptr); const auto document = _session->data().processDocument( result->document, - Images::FromImageInMemory(result->thumb, "PNG")); + Images::FromImageInMemory(result->thumb, "WEBP", result->thumbbytes)); document->setLocation(Core::FileLocation(path)); - _map.emplace(0, document); + _map.emplace(index, document); Ensures(document->sticker()); Ensures(document->sticker()->animated); @@ -132,13 +145,16 @@ DicePacks::DicePacks(not_null session) : _session(session) { } DocumentData *DicePacks::lookup(const QString &emoji, int value) { - const auto i = _packs.find(emoji); + const auto key = emoji.endsWith(QChar(0xFE0F)) + ? emoji.mid(0, emoji.size() - 1) + : emoji; + const auto i = _packs.find(key); if (i != end(_packs)) { return i->second->lookup(value); } return _packs.emplace( - emoji, - std::make_unique(_session, emoji) + key, + std::make_unique(_session, key) ).first->second->lookup(value); } diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h index a1556ee25..e8752b84f 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h @@ -27,6 +27,7 @@ private: void load(); void applySet(const MTPDmessages_stickerSet &data); void tryGenerateLocalZero(); + void generateLocal(int index, const QString &name); const not_null _session; QString _emoji; @@ -42,6 +43,8 @@ public: static const QString kDiceString; static const QString kDartString; static const QString kSlotString; + static const QString kFballString; + static const QString kBballString; [[nodiscard]] static bool IsSlot(const QString &emoji) { return (emoji == kSlotString); diff --git a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp index 9c823b0b9..4afa66425 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp @@ -32,9 +32,7 @@ namespace { Dice::Dice(not_null parent, not_null dice) : _parent(parent) , _dice(dice) -, _link(_parent->data()->Has() - ? nullptr - : dice->makeHandler()) { +, _link(dice->makeHandler()) { if (const auto document = Lookup(parent, dice->emoji(), 0)) { _start.emplace(parent, document); _start->setDiceIndex(_dice->emoji(), 0); diff --git a/Telegram/SourceFiles/history/view/media/history_view_slot_machine.cpp b/Telegram/SourceFiles/history/view/media/history_view_slot_machine.cpp index 67a2f2743..ddd1ef733 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_slot_machine.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_slot_machine.cpp @@ -72,9 +72,7 @@ SlotMachine::SlotMachine( not_null dice) : _parent(parent) , _dice(dice) -, _link(_parent->data()->Has() - ? nullptr - : dice->makeHandler()) { +, _link(dice->makeHandler()) { resolveStarts(); _showLastFrame = _parent->data()->Has(); if (_showLastFrame) { diff --git a/Telegram/SourceFiles/storage/localimageloader.cpp b/Telegram/SourceFiles/storage/localimageloader.cpp index d9c8a52a7..e1e67bf84 100644 --- a/Telegram/SourceFiles/storage/localimageloader.cpp +++ b/Telegram/SourceFiles/storage/localimageloader.cpp @@ -680,7 +680,7 @@ bool FileLoadTask::FillImageInformation( return true; } -void FileLoadTask::process() { +void FileLoadTask::process(Args &&args) { _result = std::make_shared( id(), _id, @@ -829,12 +829,13 @@ void FileLoadTask::process() { } attributes.push_back(MTP_documentAttributeVideo(MTP_flags(flags), MTP_int(video->duration), MTP_int(coverWidth), MTP_int(coverHeight))); - goodThumbnail = video->thumbnail; - { - QBuffer buffer(&goodThumbnailBytes); - goodThumbnail.save(&buffer, "JPG", kThumbnailQuality); + if (args.generateGoodThumbnail) { + goodThumbnail = video->thumbnail; + { + QBuffer buffer(&goodThumbnailBytes); + goodThumbnail.save(&buffer, "JPG", kThumbnailQuality); + } } - thumbnail = PrepareFileThumbnail(std::move(video->thumbnail)); } else if (filemime == qstr("application/x-tdesktop-theme") || filemime == qstr("application/x-tgtheme-tdesktop")) { @@ -863,7 +864,7 @@ void FileLoadTask::process() { MTP_string(), MTP_inputStickerSetEmpty(), MTPMaskCoords())); - if (isAnimation) { + if (isAnimation && args.generateGoodThumbnail) { goodThumbnail = fullimage; { QBuffer buffer(&goodThumbnailBytes); diff --git a/Telegram/SourceFiles/storage/localimageloader.h b/Telegram/SourceFiles/storage/localimageloader.h index e80058430..d744b5b4c 100644 --- a/Telegram/SourceFiles/storage/localimageloader.h +++ b/Telegram/SourceFiles/storage/localimageloader.h @@ -274,8 +274,15 @@ public: return _id; } - void process(); - void finish(); + struct Args { + bool generateGoodThumbnail = true; + }; + void process(Args &&args); + + void process() override { + process({}); + } + void finish() override; FileLoadResult *peekResult() const; From 8d2fa313b7276c3e69ef1bb07ed0e6fea7378534 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 15 Oct 2020 17:27:16 +0300 Subject: [PATCH 095/190] Add setting for grouping files. --- Telegram/SourceFiles/apiwrap.cpp | 3 +- Telegram/SourceFiles/boxes/send_files_box.cpp | 278 ++++++++---------- Telegram/SourceFiles/boxes/send_files_box.h | 26 +- Telegram/SourceFiles/core/core_settings.cpp | 17 +- Telegram/SourceFiles/core/core_settings.h | 2 +- .../SourceFiles/history/history_widget.cpp | 66 ++--- Telegram/SourceFiles/history/history_widget.h | 9 +- .../view/history_view_replies_section.cpp | 64 ++-- .../view/history_view_replies_section.h | 6 +- .../view/history_view_scheduled_section.cpp | 62 ++-- .../view/history_view_scheduled_section.h | 6 +- .../main/main_session_settings.cpp | 11 +- .../details/storage_settings_scheme.cpp | 9 +- .../SourceFiles/storage/localimageloader.h | 7 - .../storage/storage_media_prepare.cpp | 24 +- .../ui/chat/attach/attach_album_preview.cpp | 33 ++- .../ui/chat/attach/attach_album_preview.h | 4 +- .../ui/chat/attach/attach_album_thumbnail.cpp | 4 +- .../ui/chat/attach/attach_album_thumbnail.h | 2 +- .../ui/chat/attach/attach_common.h | 24 -- .../ui/chat/attach/attach_prepare.cpp | 10 +- .../ui/chat/attach/attach_prepare.h | 11 +- .../ui/chat/attach/attach_send_files_way.cpp | 76 +++++ .../ui/chat/attach/attach_send_files_way.h | 72 +++++ Telegram/cmake/td_ui.cmake | 3 +- 25 files changed, 409 insertions(+), 420 deletions(-) delete mode 100644 Telegram/SourceFiles/ui/chat/attach/attach_common.h create mode 100644 Telegram/SourceFiles/ui/chat/attach/attach_send_files_way.cpp create mode 100644 Telegram/SourceFiles/ui/chat/attach/attach_send_files_way.h diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 686d8924a..1830eee54 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -4213,8 +4213,7 @@ void ApiWrap::sendFiles( const SendAction &action) { const auto haveCaption = !caption.text.isEmpty(); const auto isAlbum = (album != nullptr); - const auto compressImages = (type == SendMediaType::Photo); - if (haveCaption && !list.canAddCaption(isAlbum, compressImages)) { + if (haveCaption && !list.canAddCaption(isAlbum)) { auto message = MessageToSend(action.history); message.textWithTags = std::move(caption); message.action = action; diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 16a389809..01d323c81 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/scroll_area.h" #include "ui/wrap/fade_wrap.h" #include "ui/chat/attach/attach_prepare.h" +#include "ui/chat/attach/attach_send_files_way.h" #include "ui/chat/attach/attach_album_preview.h" #include "ui/chat/attach/attach_single_file_preview.h" #include "ui/chat/attach/attach_single_media_preview.h" @@ -97,9 +98,9 @@ void FileDialogCallback( rpl::producer FieldPlaceholder( const Ui::PreparedList &list, SendFilesWay way) { - const auto isAlbum = (way == SendFilesWay::Album); - const auto compressImages = (way != SendFilesWay::Files); - return list.canAddCaption(isAlbum, compressImages) + const auto isAlbum = list.singleAlbumIsPossible + && way.groupMediaInAlbums(); + return list.canAddCaption(isAlbum) ? tr::lng_photo_caption() : tr::lng_photos_comment(); } @@ -111,15 +112,12 @@ SendFilesBox::SendFilesBox( not_null controller, Ui::PreparedList &&list, const TextWithTags &caption, - CompressConfirm compressed, SendLimit limit, Api::SendType sendType, SendMenu::Type sendMenuType) : _controller(controller) , _sendType(sendType) , _list(std::move(list)) -, _compressConfirmInitial(compressed) -, _compressConfirm(compressed) , _sendLimit(limit) , _sendMenuType(sendMenuType) , _caption( @@ -131,11 +129,13 @@ SendFilesBox::SendFilesBox( } void SendFilesBox::initPreview(rpl::producer desiredPreviewHeight) { + using namespace rpl::mappers; + setupControls(); updateBoxSize(); - using namespace rpl::mappers; + _dimensionsLifetime.destroy(); rpl::combine( std::move(desiredPreviewHeight), _footerHeight.value(), @@ -145,7 +145,7 @@ void SendFilesBox::initPreview(rpl::producer desiredPreviewHeight) { st::boxWideWidth, std::min(st::sendMediaPreviewHeightMax, height), true); - }, lifetime()); + }, _dimensionsLifetime); if (_preview) { _preview->show(); @@ -162,29 +162,23 @@ void SendFilesBox::prepareSingleFilePreview() { Window::GifPauseReason::Layer); }, file); if (media) { - if (!media->canSendAsPhoto()) { - _compressConfirm = CompressConfirm::None; - } _preview = media; initPreview(media->desiredHeightValue()); } else { const auto preview = Ui::CreateChild(this, file); - _compressConfirm = CompressConfirm::None; _preview = preview; initPreview(preview->desiredHeightValue()); } } void SendFilesBox::prepareAlbumPreview() { - Expects(_sendWay != nullptr); - const auto wrap = Ui::CreateChild( this, st::boxScroll); _albumPreview = wrap->setOwnedWidget(object_ptr( this, _list, - _sendWay->value())); + _sendWay.current())); addThumbButtonHandlers(wrap); @@ -214,15 +208,10 @@ void SendFilesBox::addThumbButtonHandlers(not_null wrap) { _albumPreview = nullptr; if (IsSingleItem(_list)) { - _list.albumIsPossible = false; - if (_sendWay->value() == SendFilesWay::Album) { - _sendWay->setValue(SendFilesWay::Photos); - } + _list.singleAlbumIsPossible = false; } - _compressConfirm = _compressConfirmInitial; refreshAllAfterAlbumChanges(); - }, _albumPreview->lifetime()); _albumPreview->thumbChanged( @@ -297,20 +286,20 @@ void SendFilesBox::prepare() { addButton(tr::lng_cancel(), [=] { closeBox(); }); initSendWay(); setupCaption(); + setupSendWayControls(); preparePreview(); + boxClosing() | rpl::start_with_next([=] { if (!_confirmed && _cancelledCallback) { _cancelledCallback(); } }, lifetime()); - _addFileToAlbum = addLeftButton( + _addFile = addLeftButton( tr::lng_stickers_featured_add(), App::LambdaDelayed(st::historyAttach.ripple.hideDuration, this, [=] { openDialogToAddFileToAlbum(); })); - - updateLeftButtonVisibility(); setupDragArea(); } @@ -348,21 +337,9 @@ void SendFilesBox::setupDragArea() { }, lifetime()); } -void SendFilesBox::updateLeftButtonVisibility() { - const auto isAlbum = _list.albumIsPossible - && (_list.files.size() < Ui::MaxAlbumItems()); - if (isAlbum || (IsSingleItem(_list) && IsFirstAlbumItem(_list))) { - _addFileToAlbum->show(); - } else { - _addFileToAlbum->hide(); - } -} - void SendFilesBox::refreshAllAfterAlbumChanges() { - refreshAlbumMediaCount(); preparePreview(); captionResized(); - updateLeftButtonVisibility(); _albumChanged.fire({}); } @@ -384,36 +361,23 @@ void SendFilesBox::openDialogToAddFileToAlbum() { } void SendFilesBox::initSendWay() { - refreshAlbumMediaCount(); - const auto value = [&] { - if (_sendLimit == SendLimit::One - && _list.albumIsPossible - && _list.files.size() > 1) { - return SendFilesWay::Album; + _sendWay = [&] { + auto result = Core::App().settings().sendFilesWay(); + if (_sendLimit == SendLimit::One) { + result.setGroupMediaInAlbums(true); + result.setGroupFiles(true); + return result; + } else if (_list.overrideSendImagesAsPhotos == false) { + result.setSendImagesAsPhotos(false); + return result; + } else if (_list.overrideSendImagesAsPhotos == true) { + result.setSendImagesAsPhotos(true); + return result; } - if (_compressConfirm == CompressConfirm::None) { - return SendFilesWay::Files; - } else if (_compressConfirm == CompressConfirm::No) { - return SendFilesWay::Files; - } else if (_compressConfirm == CompressConfirm::Yes) { - return _list.albumIsPossible - ? SendFilesWay::Album - : SendFilesWay::Photos; - } - const auto way = Core::App().settings().sendFilesWay(); - if (way == SendFilesWay::Files) { - return way; - } else if (way == SendFilesWay::Album) { - return _list.albumIsPossible - ? SendFilesWay::Album - : SendFilesWay::Photos; - } - return (_list.albumIsPossible && !_albumPhotosCount) - ? SendFilesWay::Album - : SendFilesWay::Photos; + return result; }(); - _sendWay = std::make_shared>(value); - _sendWay->setChangedCallback([=](SendFilesWay value) { + _sendWay.changes( + ) | rpl::start_with_next([=](SendFilesWay value) { updateCaptionPlaceholder(); applyAlbumOrder(); if (_albumPreview) { @@ -421,18 +385,17 @@ void SendFilesBox::initSendWay() { } updateEmojiPanelGeometry(); setInnerFocus(); - }); + }, lifetime()); } void SendFilesBox::updateCaptionPlaceholder() { if (!_caption) { return; } - const auto sendWay = _sendWay->value(); - const auto isAlbum = (sendWay == SendFilesWay::Album); - const auto compressImages = (sendWay != SendFilesWay::Files); - if (!_list.canAddCaption(isAlbum, compressImages) - && _sendLimit == SendLimit::One) { + const auto sendWay = _sendWay.current(); + const auto isAlbum = _list.singleAlbumIsPossible + && sendWay.groupMediaInAlbums(); + if (!_list.canAddCaption(isAlbum) && _sendLimit == SendLimit::One) { _caption->hide(); if (_emojiToggle) { _emojiToggle->hide(); @@ -446,67 +409,74 @@ void SendFilesBox::updateCaptionPlaceholder() { } } -void SendFilesBox::refreshAlbumMediaCount() { - _albumVideosCount = _list.albumIsPossible - ? ranges::count( - _list.files, - Ui::PreparedFile::AlbumType::Video, - [](const Ui::PreparedFile &file) { return file.type; }) - : 0; - _albumPhotosCount = _list.albumIsPossible - ? (_list.files.size() - _albumVideosCount) - : 0; -} - void SendFilesBox::preparePreview() { if (IsSingleItem(_list)) { prepareSingleFilePreview(); } else { - if (_list.albumIsPossible) { - prepareAlbumPreview(); - } else { - auto desiredPreviewHeight = rpl::single(0); - initPreview(std::move(desiredPreviewHeight)); - } + prepareAlbumPreview(); // #TODO files many albums } } void SendFilesBox::setupControls() { setupTitleText(); - setupSendWayControls(); + updateSendWayControlsVisibility(); } void SendFilesBox::setupSendWayControls() { - _sendAlbum.destroy(); - _sendPhotos.destroy(); - _sendFiles.destroy(); - if (_compressConfirm == CompressConfirm::None - || _sendLimit == SendLimit::One) { + // #TODO files + _groupMediaInAlbums.create( + this, + "Group media in albums", + _sendWay.current().groupMediaInAlbums(), + st::defaultBoxCheckbox); + _sendImagesAsPhotos.create( + this, + "Send images as photos", + _sendWay.current().sendImagesAsPhotos(), + st::defaultBoxCheckbox); + _groupFiles.create( + this, + "Group files", + _sendWay.current().groupFiles(), + st::defaultBoxCheckbox); + + _sendWay.changes( + ) | rpl::start_with_next([=](SendFilesWay value) { + _groupMediaInAlbums->setChecked(value.groupMediaInAlbums()); + _sendImagesAsPhotos->setChecked(value.sendImagesAsPhotos()); + _groupFiles->setChecked(value.groupFiles()); + }, lifetime()); + + _groupMediaInAlbums->checkedChanges( + ) | rpl::start_with_next([=] { + auto sendWay = _sendWay.current(); + sendWay.setGroupMediaInAlbums(_groupMediaInAlbums->checked()); + _sendWay = sendWay; + }, lifetime()); + + _sendImagesAsPhotos->checkedChanges( + ) | rpl::start_with_next([=] { + auto sendWay = _sendWay.current(); + sendWay.setSendImagesAsPhotos(_sendImagesAsPhotos->checked()); + _sendWay = sendWay; + }, lifetime()); + + _groupFiles->checkedChanges( + ) | rpl::start_with_next([=] { + auto sendWay = _sendWay.current(); + sendWay.setGroupFiles(_groupFiles->checked()); + _sendWay = sendWay; + }, lifetime()); +} + +void SendFilesBox::updateSendWayControlsVisibility() { + if (_sendLimit == SendLimit::One) { return; } - const auto addRadio = [&]( - object_ptr> &button, - SendFilesWay value, - const QString &text) { - const auto &style = st::defaultBoxCheckbox; - button.create(this, _sendWay, value, text, style); - button->show(); - }; - if (_list.albumIsPossible) { - addRadio(_sendAlbum, SendFilesWay::Album, tr::lng_send_album(tr::now)); - } - if (!_list.albumIsPossible || _albumPhotosCount > 0) { - addRadio(_sendPhotos, SendFilesWay::Photos, IsSingleItem(_list) - ? tr::lng_send_photo(tr::now) - : (_albumVideosCount > 0) - ? tr::lng_send_separate_photos_videos(tr::now) - : (_list.albumIsPossible - ? tr::lng_send_separate_photos(tr::now) - : tr::lng_send_photos(tr::now, lt_count, _list.files.size()))); - } - addRadio(_sendFiles, SendFilesWay::Files, (IsSingleItem(_list)) - ? tr::lng_send_file(tr::now) - : tr::lng_send_files(tr::now, lt_count, _list.files.size())); + const auto onlyOne = (_sendLimit == SendLimit::One); + _groupMediaInAlbums->setVisible(!onlyOne); + _sendImagesAsPhotos->setVisible(/*_list.hasImagesForCompression()*/true); // #TODO files + _groupFiles->setVisible(!onlyOne); } void SendFilesBox::applyAlbumOrder() { @@ -682,34 +652,29 @@ bool SendFilesBox::addFiles(Ui::PreparedList list) { const auto cutToAlbumSize = (sumFiles > Ui::MaxAlbumItems()); if (list.error != Ui::PreparedList::Error::None) { return false; - } else if (!IsSingleItem(list) && !list.albumIsPossible) { - return false; - } else if (!IsFirstAlbumItem(list)) { - return false; - } else if (_list.files.size() > 1 && !_albumPreview) { - return false; - } else if (!IsFirstAlbumItem(_list)) { - return false; + //} else if (!IsSingleItem(list) && !list.albumIsPossible) { // #TODO files + // return false; + //} else if (!IsFirstAlbumItem(list)) { + // return false; + //} else if (_list.files.size() > 1 && !_albumPreview) { + // return false; + //} else if (!IsFirstAlbumItem(_list)) { + // return false; } applyAlbumOrder(); delete base::take(_preview); _albumPreview = nullptr; - if (IsSingleItem(_list) - && _sendWay->value() == SendFilesWay::Photos) { - _sendWay->setValue(SendFilesWay::Album); - } _list.mergeToEnd(std::move(list), cutToAlbumSize); - _compressConfirm = _compressConfirmInitial; refreshAllAfterAlbumChanges(); return true; } void SendFilesBox::setupTitleText() { if (_list.files.size() > 1) { - const auto onlyImages = (_compressConfirm != CompressConfirm::None) - && (_albumVideosCount == 0); + const auto onlyImages = false;/* #TODO files (_compressConfirm != CompressConfirm::None) + && (_albumVideosCount == 0);*/ _titleText = onlyImages ? tr::lng_send_images_selected(tr::now, lt_count, _list.files.size()) : tr::lng_send_files_selected(tr::now, lt_count, _list.files.size()); @@ -726,12 +691,12 @@ void SendFilesBox::updateBoxSize() { footerHeight += st::boxPhotoCaptionSkip + _caption->height(); } const auto pointers = { - _sendAlbum.data(), - _sendPhotos.data(), - _sendFiles.data() + _groupMediaInAlbums.data(), + _sendImagesAsPhotos.data(), + _groupFiles.data() }; for (auto pointer : pointers) { - if (pointer) { + if (pointer && !pointer->isHidden()) { footerHeight += st::boxPhotoCompressedSkip + pointer->heightNoMargins(); } @@ -740,7 +705,7 @@ void SendFilesBox::updateBoxSize() { } void SendFilesBox::keyPressEvent(QKeyEvent *e) { - if (e->matches(QKeySequence::Open) && !_addFileToAlbum->isHidden()) { + if (e->matches(QKeySequence::Open)) { openDialogToAddFileToAlbum(); } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { const auto modifiers = e->modifiers(); @@ -792,12 +757,12 @@ void SendFilesBox::updateControlsGeometry() { } } const auto pointers = { - _sendAlbum.data(), - _sendPhotos.data(), - _sendFiles.data() + _groupMediaInAlbums.data(), + _sendImagesAsPhotos.data(), + _groupFiles.data() }; for (const auto pointer : ranges::view::reverse(pointers)) { - if (pointer) { + if (pointer && !pointer->isHidden()) { pointer->moveToLeft( st::boxPhotoPadding.left(), bottom - pointer->heightNoMargins()); @@ -827,22 +792,21 @@ void SendFilesBox::send( return sendScheduled(); } - using Way = SendFilesWay; - const auto way = _sendWay ? _sendWay->value() : Way::Files; - - if (_compressConfirm == CompressConfirm::Auto) { - const auto oldWay = Core::App().settings().sendFilesWay(); - if (way != oldWay) { - // Check if the user _could_ use the old value, but didn't. - if ((oldWay == Way::Album && _sendAlbum) - || (oldWay == Way::Photos && _sendPhotos) - || (oldWay == Way::Files && _sendFiles) - || (way == Way::Files && (_sendAlbum || _sendPhotos))) { - // And in that case save it to settings. - Core::App().settings().setSendFilesWay(way); - Core::App().saveSettingsDelayed(); - } - } + auto way = _sendWay.current(); + auto oldWay = Core::App().settings().sendFilesWay(); + if (_list.overrideSendImagesAsPhotos == way.sendImagesAsPhotos() + || _sendImagesAsPhotos->isHidden()) { + way.setSendImagesAsPhotos(oldWay.sendImagesAsPhotos()); + } + if (_groupMediaInAlbums->isHidden()) { + way.setGroupMediaInAlbums(oldWay.groupMediaInAlbums()); + } + if (_groupFiles->isHidden()) { + way.setGroupFiles(oldWay.groupFiles()); + } + if (way != oldWay) { + Core::App().settings().setSendFilesWay(way); + Core::App().saveSettingsDelayed(); } applyAlbumOrder(); diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h index 4b99028d7..46ffe34b5 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.h +++ b/Telegram/SourceFiles/boxes/send_files_box.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include "boxes/abstract_box.h" #include "ui/chat/attach/attach_prepare.h" +#include "ui/chat/attach/attach_send_files_way.h" #include "storage/localimageloader.h" #include "storage/storage_media_prepare.h" @@ -27,16 +28,12 @@ class TabbedPanel; } // namespace ChatHelpers namespace Ui { -template -class Radioenum; -template -class RadioenumGroup; +class Checkbox; class RoundButton; class InputField; struct GroupMediaLayout; class EmojiButton; class AlbumPreview; -enum class SendFilesWay; } // namespace Ui namespace Window { @@ -58,7 +55,6 @@ public: not_null controller, Ui::PreparedList &&list, const TextWithTags &caption, - CompressConfirm compressed, SendLimit limit, Api::SendType sendType, SendMenu::Type sendMenuType); @@ -98,10 +94,10 @@ private: not_null content); void setupEmojiPanel(); + void updateSendWayControlsVisibility(); void updateEmojiPanelGeometry(); void emojiFilterForGeometry(not_null event); - void refreshAlbumMediaCount(); void preparePreview(); void prepareSingleFilePreview(); void prepareAlbumPreview(); @@ -125,7 +121,6 @@ private: bool addFiles(Ui::PreparedList list); void openDialogToAddFileToAlbum(); - void updateLeftButtonVisibility(); void refreshAllAfterAlbumChanges(); const not_null _controller; @@ -136,8 +131,6 @@ private: Ui::PreparedList _list; - CompressConfirm _compressConfirmInitial = CompressConfirm::None; - CompressConfirm _compressConfirm = CompressConfirm::None; SendLimit _sendLimit = SendLimit::Many; SendMenu::Type _sendMenuType = SendMenu::Type(); @@ -155,22 +148,21 @@ private: base::unique_qptr _emojiPanel; base::unique_qptr _emojiFilter; - object_ptr> _sendAlbum = { nullptr }; - object_ptr> _sendPhotos = { nullptr }; - object_ptr> _sendFiles = { nullptr }; - std::shared_ptr> _sendWay; + object_ptr _groupMediaInAlbums = { nullptr }; + object_ptr _sendImagesAsPhotos = { nullptr }; + object_ptr _groupFiles = { nullptr }; + rpl::variable _sendWay = Ui::SendFilesWay(); rpl::variable _footerHeight = 0; rpl::event_stream<> _albumChanged; + rpl::lifetime _dimensionsLifetime; QWidget *_preview = nullptr; Ui::AlbumPreview *_albumPreview = nullptr; - int _albumVideosCount = 0; - int _albumPhotosCount = 0; int _lastScrollTop = 0; QPointer _send; - QPointer _addFileToAlbum; + QPointer _addFile; }; diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index 41d138ebf..c6ea4b8dc 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -9,7 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/send_files_box.h" #include "ui/widgets/input_fields.h" -#include "ui/chat/attach/attach_common.h" #include "storage/serialize_common.h" #include "window/themes/window_theme.h" #include "window/section_widget.h" @@ -19,8 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Core { Settings::Settings() -: _sendFilesWay(Ui::SendFilesWay::Album) -, _sendSubmitWay(Ui::InputSubmitSettings::Enter) +: _sendSubmitWay(Ui::InputSubmitSettings::Enter) , _floatPlayerColumn(Window::Column::Second) , _floatPlayerCorner(RectPart::TopRight) , _dialogsWidthRatio(DefaultDialogsWidthRatio()) { @@ -76,7 +74,7 @@ QByteArray Settings::serialize() const { stream << key << value; } stream - << qint32(_sendFilesWay) + << qint32(_sendFilesWay.serialize()) << qint32(_sendSubmitWay) << qint32(_includeMutedCounter ? 1 : 0) << qint32(_countUnreadMessages ? 1 : 0) @@ -150,7 +148,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { qint32 lastSeenWarningSeen = _lastSeenWarningSeen ? 1 : 0; qint32 soundOverridesCount = 0; base::flat_map soundOverrides; - qint32 sendFilesWay = static_cast(_sendFilesWay); + qint32 sendFilesWay = _sendFilesWay.serialize(); qint32 sendSubmitWay = static_cast(_sendSubmitWay); qint32 includeMutedCounter = _includeMutedCounter ? 1 : 0; qint32 countUnreadMessages = _countUnreadMessages ? 1 : 0; @@ -310,12 +308,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { _callAudioDuckingEnabled = (callAudioDuckingEnabled == 1); _lastSeenWarningSeen = (lastSeenWarningSeen == 1); _soundOverrides = std::move(soundOverrides); - auto uncheckedSendFilesWay = static_cast(sendFilesWay); - switch (uncheckedSendFilesWay) { - case Ui::SendFilesWay::Album: - case Ui::SendFilesWay::Photos: - case Ui::SendFilesWay::Files: _sendFilesWay = uncheckedSendFilesWay; break; - } + _sendFilesWay = Ui::SendFilesWay::FromSerialized(sendFilesWay).value_or(_sendFilesWay); auto uncheckedSendSubmitWay = static_cast(sendSubmitWay); switch (uncheckedSendSubmitWay) { case Ui::InputSubmitSettings::Enter: @@ -470,7 +463,7 @@ void Settings::resetOnLastLogout() { //_themesAccentColors = Window::Theme::AccentColors(); _lastSeenWarningSeen = false; - _sendFilesWay = Ui::SendFilesWay::Album; + _sendFilesWay = Ui::SendFilesWay(); //_sendSubmitWay = Ui::InputSubmitSettings::Enter; _soundOverrides = {}; diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index bab3bd920..dbb3a8933 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -9,12 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/themes/window_themes_embedded.h" #include "window/window_controls_layout.h" +#include "ui/chat/attach/attach_send_files_way.h" enum class RectPart; namespace Ui { enum class InputSubmitSettings; -enum class SendFilesWay; } // namespace Ui namespace Window { diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 8bd28b1dc..4a50804bd 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -31,7 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" // Ui::Text::ToUpper #include "ui/text/format_values.h" #include "ui/chat/message_bar.h" -#include "ui/chat/attach/attach_common.h" +#include "ui/chat/attach/attach_send_files_way.h" #include "ui/image/image.h" #include "ui/special_buttons.h" #include "ui/controls/emoji_button.h" @@ -327,10 +327,7 @@ HistoryWidget::HistoryWidget( if (action == Ui::InputField::MimeAction::Check) { return canSendFiles(data); } else if (action == Ui::InputField::MimeAction::Insert) { - return confirmSendingFiles( - data, - CompressConfirm::Auto, - data->text()); + return confirmSendingFiles(data, std::nullopt, data->text()); } Unexpected("action in MimeData hook."); }); @@ -375,11 +372,11 @@ HistoryWidget::HistoryWidget( crl::guard(this, [=](bool f) { _field->setAcceptDrops(f); }), crl::guard(this, [=] { updateControlsGeometry(); })); _attachDragAreas.document->setDroppedCallback([=](const QMimeData *data) { - confirmSendingFiles(data, CompressConfirm::No); + confirmSendingFiles(data, false); Window::ActivateWindow(controller); }); _attachDragAreas.photo->setDroppedCallback([=](const QMimeData *data) { - confirmSendingFiles(data, CompressConfirm::Yes); + confirmSendingFiles(data, true); Window::ActivateWindow(controller); }); @@ -3298,8 +3295,7 @@ void HistoryWidget::chooseAttach() { if (!image.isNull() && !animated) { confirmSendingFiles( std::move(image), - std::move(result.remoteContent), - CompressConfirm::Auto); + std::move(result.remoteContent)); } else { uploadFile(result.remoteContent, SendMediaType::File); } @@ -3307,11 +3303,7 @@ void HistoryWidget::chooseAttach() { auto list = Storage::PrepareMediaList( result.paths, st::sendMediaPreviewSize); - if (list.allFilesForCompress || list.albumIsPossible) { - confirmSendingFiles(std::move(list), CompressConfirm::Auto); - } else if (!showSendingFilesError(list)) { - confirmSendingFiles(std::move(list), CompressConfirm::No); - } + confirmSendingFiles(std::move(list)); } }), nullptr); } @@ -4107,7 +4099,7 @@ bool HistoryWidget::showSendingFilesError( } if (list.files.size() > 1 && _peer->slowmodeApplied() - && !list.albumIsPossible) { + && !list.singleAlbumIsPossible) { return tr::lng_slowmode_no_many(tr::now); } else if (const auto left = _peer->slowmodeSecondsLeft()) { return tr::lng_slowmode_enabled( @@ -4142,26 +4134,23 @@ bool HistoryWidget::showSendingFilesError( } bool HistoryWidget::confirmSendingFiles(const QStringList &files) { - return confirmSendingFiles(files, CompressConfirm::Auto); + return confirmSendingFiles(files, QString()); } bool HistoryWidget::confirmSendingFiles(not_null data) { - return confirmSendingFiles(data, CompressConfirm::Auto); + return confirmSendingFiles(data, std::nullopt); } bool HistoryWidget::confirmSendingFiles( const QStringList &files, - CompressConfirm compressed, const QString &insertTextOnCancel) { return confirmSendingFiles( Storage::PrepareMediaList(files, st::sendMediaPreviewSize), - compressed, insertTextOnCancel); } bool HistoryWidget::confirmSendingFiles( Ui::PreparedList &&list, - CompressConfirm compressed, const QString &insertTextOnCancel) { if (showSendingFilesError(list)) { return false; @@ -4171,13 +4160,6 @@ bool HistoryWidget::confirmSendingFiles( return false; } - const auto noCompressOption = (list.files.size() > 1) - && !list.allFilesForCompress - && !list.albumIsPossible; - const auto boxCompressConfirm = noCompressOption - ? CompressConfirm::None - : compressed; - const auto cursor = _field->textCursor(); const auto position = cursor.position(); const auto anchor = cursor.anchor(); @@ -4187,7 +4169,6 @@ bool HistoryWidget::confirmSendingFiles( controller(), std::move(list), text, - boxCompressConfirm, _peer->slowmodeApplied() ? SendLimit::One : SendLimit::Many, Api::SendType::Normal, sendMenuType()); @@ -4201,10 +4182,10 @@ bool HistoryWidget::confirmSendingFiles( if (showSendingFilesError(list)) { return; } - const auto type = (way == Ui::SendFilesWay::Files) - ? SendMediaType::File - : SendMediaType::Photo; - const auto album = (way == Ui::SendFilesWay::Album) + const auto type = way.sendImagesAsPhotos() + ? SendMediaType::Photo + : SendMediaType::File; + const auto album = way.groupMediaInAlbums() // #TODO files ? std::make_shared() : nullptr; uploadFilesAfterConfirmation( @@ -4238,7 +4219,7 @@ bool HistoryWidget::confirmSendingFiles( bool HistoryWidget::confirmSendingFiles( QImage &&image, QByteArray &&content, - CompressConfirm compressed, + std::optional overrideSendImagesAsPhotos, const QString &insertTextOnCancel) { if (image.isNull()) { return false; @@ -4248,10 +4229,8 @@ bool HistoryWidget::confirmSendingFiles( std::move(image), std::move(content), st::sendMediaPreviewSize); - return confirmSendingFiles( - std::move(list), - compressed, - insertTextOnCancel); + list.overrideSendImagesAsPhotos = overrideSendImagesAsPhotos; + return confirmSendingFiles(std::move(list), insertTextOnCancel); } bool HistoryWidget::canSendFiles(not_null data) const { @@ -4269,7 +4248,7 @@ bool HistoryWidget::canSendFiles(not_null data) const { bool HistoryWidget::confirmSendingFiles( not_null data, - CompressConfirm compressed, + std::optional overrideSendImagesAsPhotos, const QString &insertTextOnCancel) { if (!canWriteMessage()) { return false; @@ -4285,10 +4264,8 @@ bool HistoryWidget::confirmSendingFiles( if (list.error == Ui::PreparedList::Error::None || !hasImage) { const auto emptyTextOnCancel = QString(); - confirmSendingFiles( - std::move(list), - compressed, - emptyTextOnCancel); + list.overrideSendImagesAsPhotos = overrideSendImagesAsPhotos; + confirmSendingFiles(std::move(list), emptyTextOnCancel); return true; } } @@ -4303,7 +4280,7 @@ bool HistoryWidget::confirmSendingFiles( confirmSendingFiles( std::move(image), QByteArray(), - compressed, + overrideSendImagesAsPhotos, insertTextOnCancel); return true; } @@ -4321,12 +4298,11 @@ void HistoryWidget::uploadFilesAfterConfirmation( Assert(canWriteMessage()); const auto isAlbum = (album != nullptr); - const auto compressImages = (type == SendMediaType::Photo); if (_peer->slowmodeApplied() && ((list.files.size() > 1 && !album) || (!list.files.empty() && !caption.text.isEmpty() - && !list.canAddCaption(isAlbum, compressImages)))) { + && !list.canAddCaption(isAlbum)))) { Ui::ShowMultilineToast({ .text = { tr::lng_slowmode_no_many(tr::now) }, }); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 2e5f034c8..377bb4ede 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -23,7 +23,6 @@ class RPCError; struct FileLoadResult; struct SendingAlbum; enum class SendMediaType; -enum class CompressConfirm; class MessageLinksParser; namespace SendMenu { @@ -411,20 +410,18 @@ private: bool canSendFiles(not_null data) const; bool confirmSendingFiles( const QStringList &files, - CompressConfirm compressed, - const QString &insertTextOnCancel = QString()); + const QString &insertTextOnCancel); bool confirmSendingFiles( QImage &&image, QByteArray &&content, - CompressConfirm compressed, + std::optional overrideSendImagesAsPhotos = std::nullopt, const QString &insertTextOnCancel = QString()); bool confirmSendingFiles( not_null data, - CompressConfirm compressed, + std::optional overrideSendImagesAsPhotos, const QString &insertTextOnCancel = QString()); bool confirmSendingFiles( Ui::PreparedList &&list, - CompressConfirm compressed, const QString &insertTextOnCancel = QString()); bool showSendingFilesError(const Ui::PreparedList &list) const; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 37d256a6b..6415cd87a 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -27,7 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/format_values.h" #include "ui/text/text_utilities.h" #include "ui/chat/attach/attach_prepare.h" -#include "ui/chat/attach/attach_common.h" +#include "ui/chat/attach/attach_send_files_way.h" #include "ui/special_buttons.h" #include "ui/ui_utility.h" #include "ui/toasts/common_toasts.h" @@ -505,10 +505,7 @@ void RepliesWidget::setupComposeControls() { if (action == Ui::InputField::MimeAction::Check) { return CanSendFiles(data); } else if (action == Ui::InputField::MimeAction::Insert) { - return confirmSendingFiles( - data, - CompressConfirm::Auto, - data->text()); + return confirmSendingFiles(data, std::nullopt, data->text()); } Unexpected("action in MimeData hook."); }); @@ -545,8 +542,7 @@ void RepliesWidget::chooseAttach() { if (!image.isNull() && !animated) { confirmSendingFiles( std::move(image), - std::move(result.remoteContent), - CompressConfirm::Auto); + std::move(result.remoteContent)); } else { uploadFile(result.remoteContent, SendMediaType::File); } @@ -554,18 +550,14 @@ void RepliesWidget::chooseAttach() { auto list = Storage::PrepareMediaList( result.paths, st::sendMediaPreviewSize); - if (list.allFilesForCompress || list.albumIsPossible) { - confirmSendingFiles(std::move(list), CompressConfirm::Auto); - } else if (!showSendingFilesError(list)) { - confirmSendingFiles(std::move(list), CompressConfirm::No); - } + confirmSendingFiles(std::move(list)); } }), nullptr); } bool RepliesWidget::confirmSendingFiles( not_null data, - CompressConfirm compressed, + std::optional overrideSendImagesAsPhotos, const QString &insertTextOnCancel) { const auto hasImage = data->hasImage(); @@ -577,10 +569,8 @@ bool RepliesWidget::confirmSendingFiles( if (list.error == Ui::PreparedList::Error::None || !hasImage) { const auto emptyTextOnCancel = QString(); - confirmSendingFiles( - std::move(list), - compressed, - emptyTextOnCancel); + list.overrideSendImagesAsPhotos = overrideSendImagesAsPhotos; + confirmSendingFiles(std::move(list), emptyTextOnCancel); return true; } } @@ -595,7 +585,7 @@ bool RepliesWidget::confirmSendingFiles( confirmSendingFiles( std::move(image), QByteArray(), - compressed, + overrideSendImagesAsPhotos, insertTextOnCancel); return true; } @@ -605,19 +595,11 @@ bool RepliesWidget::confirmSendingFiles( bool RepliesWidget::confirmSendingFiles( Ui::PreparedList &&list, - CompressConfirm compressed, const QString &insertTextOnCancel) { if (showSendingFilesError(list)) { return false; } - const auto noCompressOption = (list.files.size() > 1) - && !list.allFilesForCompress - && !list.albumIsPossible; - const auto boxCompressConfirm = noCompressOption - ? CompressConfirm::None - : compressed; - //const auto cursor = _field->textCursor(); //const auto position = cursor.position(); //const auto anchor = cursor.anchor(); @@ -627,7 +609,6 @@ bool RepliesWidget::confirmSendingFiles( controller(), std::move(list), text, - boxCompressConfirm, _history->peer->slowmodeApplied() ? SendLimit::One : SendLimit::Many, Api::SendType::Normal, SendMenu::Type::Disabled); // #TODO replies schedule @@ -643,10 +624,10 @@ bool RepliesWidget::confirmSendingFiles( if (showSendingFilesError(list)) { return; } - const auto type = (way == Ui::SendFilesWay::Files) - ? SendMediaType::File - : SendMediaType::Photo; - const auto album = (way == Ui::SendFilesWay::Album) + const auto type = way.sendImagesAsPhotos() + ? SendMediaType::Photo + : SendMediaType::File; + const auto album = way.groupMediaInAlbums() // #TODO files ? std::make_shared() : nullptr; uploadFilesAfterConfirmation( @@ -683,7 +664,7 @@ bool RepliesWidget::confirmSendingFiles( bool RepliesWidget::confirmSendingFiles( QImage &&image, QByteArray &&content, - CompressConfirm compressed, + std::optional overrideSendImagesAsPhotos, const QString &insertTextOnCancel) { if (image.isNull()) { return false; @@ -693,10 +674,8 @@ bool RepliesWidget::confirmSendingFiles( std::move(image), std::move(content), st::sendMediaPreviewSize); - return confirmSendingFiles( - std::move(list), - compressed, - insertTextOnCancel); + list.overrideSendImagesAsPhotos = overrideSendImagesAsPhotos; + return confirmSendingFiles(std::move(list), insertTextOnCancel); } bool RepliesWidget::showSlowmodeError() { @@ -737,12 +716,11 @@ void RepliesWidget::uploadFilesAfterConfirmation( Api::SendOptions options, std::shared_ptr album) { const auto isAlbum = (album != nullptr); - const auto compressImages = (type == SendMediaType::Photo); if (_history->peer->slowmodeApplied() && ((list.files.size() > 1 && !album) || (!list.files.empty() && !caption.text.isEmpty() - && !list.canAddCaption(isAlbum, compressImages)))) { + && !list.canAddCaption(isAlbum)))) { Ui::ShowMultilineToast({ .text = { tr::lng_slowmode_no_many(tr::now) } }); @@ -830,7 +808,7 @@ bool RepliesWidget::showSendingFilesError( } if (list.files.size() > 1 && _history->peer->slowmodeApplied() - && !list.albumIsPossible) { + && !list.singleAlbumIsPossible) { return tr::lng_slowmode_no_many(tr::now); } else if (const auto left = _history->peer->slowmodeSecondsLeft()) { return tr::lng_slowmode_enabled( @@ -1808,14 +1786,14 @@ void RepliesWidget::setupDragArea() { nullptr, [=] { updateControlsGeometry(); }); - const auto droppedCallback = [=](CompressConfirm compressed) { + const auto droppedCallback = [=](bool overrideSendImagesAsPhotos) { return [=](const QMimeData *data) { - confirmSendingFiles(data, compressed); + confirmSendingFiles(data, overrideSendImagesAsPhotos); Window::ActivateWindow(controller()); }; }; - areas.document->setDroppedCallback(droppedCallback(CompressConfirm::No)); - areas.photo->setDroppedCallback(droppedCallback(CompressConfirm::Yes)); + areas.document->setDroppedCallback(droppedCallback(false)); + areas.photo->setDroppedCallback(droppedCallback(true)); } } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 97e061a55..f7a664740 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -14,7 +14,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/timer.h" class History; -enum class CompressConfirm; enum class SendMediaType; struct SendingAlbum; @@ -198,15 +197,14 @@ private: bool confirmSendingFiles( QImage &&image, QByteArray &&content, - CompressConfirm compressed, + std::optional overrideSendImagesAsPhotos = std::nullopt, const QString &insertTextOnCancel = QString()); bool confirmSendingFiles( Ui::PreparedList &&list, - CompressConfirm compressed, const QString &insertTextOnCancel = QString()); bool confirmSendingFiles( not_null data, - CompressConfirm compressed, + std::optional overrideSendImagesAsPhotos = std::nullopt, const QString &insertTextOnCancel = QString()); bool showSendingFilesError(const Ui::PreparedList &list) const; void uploadFilesAfterConfirmation( diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 94015efa2..d16daf465 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -21,7 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/item_text_options.h" #include "ui/toast/toast.h" #include "ui/chat/attach/attach_prepare.h" -#include "ui/chat/attach/attach_common.h" +#include "ui/chat/attach/attach_send_files_way.h" #include "ui/special_buttons.h" #include "ui/ui_utility.h" #include "ui/toasts/common_toasts.h" @@ -249,10 +249,7 @@ void ScheduledWidget::setupComposeControls() { if (action == Ui::InputField::MimeAction::Check) { return CanSendFiles(data); } else if (action == Ui::InputField::MimeAction::Insert) { - return confirmSendingFiles( - data, - CompressConfirm::Auto, - data->text()); + return confirmSendingFiles(data, std::nullopt, data->text()); } Unexpected("action in MimeData hook."); }); @@ -285,8 +282,7 @@ void ScheduledWidget::chooseAttach() { if (!image.isNull() && !animated) { confirmSendingFiles( std::move(image), - std::move(result.remoteContent), - CompressConfirm::Auto); + std::move(result.remoteContent)); } else { uploadFile(result.remoteContent, SendMediaType::File); } @@ -294,18 +290,14 @@ void ScheduledWidget::chooseAttach() { auto list = Storage::PrepareMediaList( result.paths, st::sendMediaPreviewSize); - if (list.allFilesForCompress || list.albumIsPossible) { - confirmSendingFiles(std::move(list), CompressConfirm::Auto); - } else if (!showSendingFilesError(list)) { - confirmSendingFiles(std::move(list), CompressConfirm::No); - } + confirmSendingFiles(std::move(list)); } }), nullptr); } bool ScheduledWidget::confirmSendingFiles( not_null data, - CompressConfirm compressed, + std::optional overrideSendImagesAsPhotos, const QString &insertTextOnCancel) { const auto hasImage = data->hasImage(); @@ -317,10 +309,8 @@ bool ScheduledWidget::confirmSendingFiles( if (list.error == Ui::PreparedList::Error::None || !hasImage) { const auto emptyTextOnCancel = QString(); - confirmSendingFiles( - std::move(list), - compressed, - emptyTextOnCancel); + list.overrideSendImagesAsPhotos = overrideSendImagesAsPhotos; + confirmSendingFiles(std::move(list), emptyTextOnCancel); return true; } } @@ -335,7 +325,7 @@ bool ScheduledWidget::confirmSendingFiles( confirmSendingFiles( std::move(image), QByteArray(), - compressed, + overrideSendImagesAsPhotos, insertTextOnCancel); return true; } @@ -345,19 +335,11 @@ bool ScheduledWidget::confirmSendingFiles( bool ScheduledWidget::confirmSendingFiles( Ui::PreparedList &&list, - CompressConfirm compressed, const QString &insertTextOnCancel) { if (showSendingFilesError(list)) { return false; } - const auto noCompressOption = (list.files.size() > 1) - && !list.allFilesForCompress - && !list.albumIsPossible; - const auto boxCompressConfirm = noCompressOption - ? CompressConfirm::None - : compressed; - //const auto cursor = _field->textCursor(); //const auto position = cursor.position(); //const auto anchor = cursor.anchor(); @@ -367,7 +349,6 @@ bool ScheduledWidget::confirmSendingFiles( controller(), std::move(list), text, - boxCompressConfirm, _history->peer->slowmodeApplied() ? SendLimit::One : SendLimit::Many, CanScheduleUntilOnline(_history->peer) ? Api::SendType::ScheduledToUser @@ -384,10 +365,10 @@ bool ScheduledWidget::confirmSendingFiles( if (showSendingFilesError(list)) { return; } - const auto type = (way == Ui::SendFilesWay::Files) - ? SendMediaType::File - : SendMediaType::Photo; - const auto album = (way == Ui::SendFilesWay::Album) + const auto type = way.sendImagesAsPhotos() + ? SendMediaType::Photo + : SendMediaType::File; + const auto album = way.groupMediaInAlbums() // #TODO files ? std::make_shared() : nullptr; uploadFilesAfterConfirmation( @@ -421,7 +402,7 @@ bool ScheduledWidget::confirmSendingFiles( bool ScheduledWidget::confirmSendingFiles( QImage &&image, QByteArray &&content, - CompressConfirm compressed, + std::optional overrideSendImagesAsPhotos, const QString &insertTextOnCancel) { if (image.isNull()) { return false; @@ -431,10 +412,8 @@ bool ScheduledWidget::confirmSendingFiles( std::move(image), std::move(content), st::sendMediaPreviewSize); - return confirmSendingFiles( - std::move(list), - compressed, - insertTextOnCancel); + list.overrideSendImagesAsPhotos = overrideSendImagesAsPhotos; + return confirmSendingFiles(std::move(list), insertTextOnCancel); } void ScheduledWidget::uploadFilesAfterConfirmation( @@ -445,12 +424,11 @@ void ScheduledWidget::uploadFilesAfterConfirmation( Api::SendOptions options, std::shared_ptr album) { const auto isAlbum = (album != nullptr); - const auto compressImages = (type == SendMediaType::Photo); if (_history->peer->slowmodeApplied() && ((list.files.size() > 1 && !album) || (!list.files.empty() && !caption.text.isEmpty() - && !list.canAddCaption(isAlbum, compressImages)))) { + && !list.canAddCaption(isAlbum)))) { Ui::ShowMultilineToast({ .text = { tr::lng_slowmode_no_many(tr::now) }, }); @@ -1228,14 +1206,14 @@ void ScheduledWidget::setupDragArea() { nullptr, [=] { updateControlsGeometry(); }); - const auto droppedCallback = [=](CompressConfirm compressed) { + const auto droppedCallback = [=](bool overrideSendImagesAsPhotos) { return [=](const QMimeData *data) { - confirmSendingFiles(data, compressed); + confirmSendingFiles(data, overrideSendImagesAsPhotos); Window::ActivateWindow(controller()); }; }; - areas.document->setDroppedCallback(droppedCallback(CompressConfirm::No)); - areas.photo->setDroppedCallback(droppedCallback(CompressConfirm::Yes)); + areas.document->setDroppedCallback(droppedCallback(false)); + areas.photo->setDroppedCallback(droppedCallback(true)); } } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h index 57af2c5c3..56c2377f4 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h @@ -13,7 +13,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_messages.h" class History; -enum class CompressConfirm; enum class SendMediaType; struct SendingAlbum; @@ -164,15 +163,14 @@ private: bool confirmSendingFiles( QImage &&image, QByteArray &&content, - CompressConfirm compressed, + std::optional overrideSendImagesAsPhotos = std::nullopt, const QString &insertTextOnCancel = QString()); bool confirmSendingFiles( Ui::PreparedList &&list, - CompressConfirm compressed, const QString &insertTextOnCancel = QString()); bool confirmSendingFiles( not_null data, - CompressConfirm compressed, + std::optional overrideSendImagesAsPhotos = std::nullopt, const QString &insertTextOnCancel = QString()); bool showSendingFilesError(const Ui::PreparedList &list) const; void uploadFilesAfterConfirmation( diff --git a/Telegram/SourceFiles/main/main_session_settings.cpp b/Telegram/SourceFiles/main/main_session_settings.cpp index 6b178cd01..e97770cbf 100644 --- a/Telegram/SourceFiles/main/main_session_settings.cpp +++ b/Telegram/SourceFiles/main/main_session_settings.cpp @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/tabbed_selector.h" #include "ui/widgets/input_fields.h" -#include "ui/chat/attach/attach_common.h" +#include "ui/chat/attach/attach_send_files_way.h" #include "window/section_widget.h" #include "support/support_common.h" #include "storage/serialize_common.h" @@ -97,7 +97,7 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { float64 appDialogsWidthRatio = app.dialogsWidthRatio(); int appThirdColumnWidth = app.thirdColumnWidth(); int appThirdSectionExtendedBy = app.thirdSectionExtendedBy(); - qint32 appSendFilesWay = static_cast(app.sendFilesWay()); + qint32 appSendFilesWay = app.sendFilesWay().serialize(); qint32 legacyCallsPeerToPeer = qint32(0); qint32 appSendSubmitWay = static_cast(app.sendSubmitWay()); qint32 supportSwitch = static_cast(_supportSwitch); @@ -368,11 +368,8 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { for (const auto &[key, value] : appSoundOverrides) { app.setSoundOverride(key, value); } - auto uncheckedSendFilesWay = static_cast(appSendFilesWay); - switch (uncheckedSendFilesWay) { - case Ui::SendFilesWay::Album: - case Ui::SendFilesWay::Photos: - case Ui::SendFilesWay::Files: app.setSendFilesWay(uncheckedSendFilesWay); break; + if (const auto sendFilesWay = Ui::SendFilesWay::FromSerialized(appSendFilesWay)) { + app.setSendFilesWay(*sendFilesWay); } auto uncheckedSendSubmitWay = static_cast( appSendSubmitWay); diff --git a/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp b/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp index 2bed620ff..6550150a8 100644 --- a/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp +++ b/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp @@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/mtproto_config.h" #include "ui/effects/animation_value.h" #include "ui/widgets/input_fields.h" -#include "ui/chat/attach/attach_common.h" +#include "ui/chat/attach/attach_send_files_way.h" #include "window/themes/window_theme.h" #include "core/update_checker.h" #include "platform/platform_specific.h" @@ -918,9 +918,10 @@ bool ReadSetting( stream >> v; if (!CheckStreamStatus(stream)) return false; - Core::App().settings().setSendFilesWay((v == 1) - ? Ui::SendFilesWay::Album - : Ui::SendFilesWay::Files); + auto way = Ui::SendFilesWay(); + way.setGroupMediaInAlbums(v == 1); + way.setSendImagesAsPhotos(v == 1); + Core::App().settings().setSendFilesWay(way); context.legacyRead = true; } break; diff --git a/Telegram/SourceFiles/storage/localimageloader.h b/Telegram/SourceFiles/storage/localimageloader.h index d744b5b4c..b550bb082 100644 --- a/Telegram/SourceFiles/storage/localimageloader.h +++ b/Telegram/SourceFiles/storage/localimageloader.h @@ -13,13 +13,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL constexpr auto kFileSizeLimit = 2000 * 1024 * 1024; // Load files up to 1500mb -enum class CompressConfirm { - Auto, - Yes, - No, - None, -}; - enum class SendMediaType { Photo, Audio, diff --git a/Telegram/SourceFiles/storage/storage_media_prepare.cpp b/Telegram/SourceFiles/storage/storage_media_prepare.cpp index ebd65091c..a7c79875e 100644 --- a/Telegram/SourceFiles/storage/storage_media_prepare.cpp +++ b/Telegram/SourceFiles/storage/storage_media_prepare.cpp @@ -122,7 +122,7 @@ void PrepareAlbum(PreparedList &result, int previewWidth) { return; } - result.albumIsPossible = (count > 1); + //result.albumIsPossible = (count > 1); auto waiting = 0; QSemaphore semaphore; for (auto &file : result.files) { @@ -132,13 +132,13 @@ void PrepareAlbum(PreparedList &result, int previewWidth) { } if (waiting > 0) { semaphore.acquire(waiting); - if (result.albumIsPossible) { - const auto badIt = ranges::find( - result.files, - PreparedFile::AlbumType::None, - [](const PreparedFile &file) { return file.type; }); - result.albumIsPossible = (badIt == result.files.end()); - } + //if (result.albumIsPossible) { + // const auto badIt = ranges::find( + // result.files, + // PreparedFile::AlbumType::None, + // [](const PreparedFile &file) { return file.type; }); + // result.albumIsPossible = (badIt == result.files.end()); + //} } } @@ -252,7 +252,7 @@ PreparedList PrepareMediaList(const QStringList &files, int previewWidth) { } const auto toCompress = HasExtensionFrom(file, extensionsToCompress); if (filesize > App::kImageSizeLimit || !toCompress) { - result.allFilesForCompress = false; +// result.allFilesForCompress = false; } result.files.emplace_back(file); } @@ -265,9 +265,6 @@ PreparedList PrepareMediaFromImage( QByteArray &&content, int previewWidth) { auto result = Storage::PreparedList(); - result.allFilesForCompress = Ui::ValidateThumbDimensions( - image.width(), - image.height()); auto file = PreparedFile(QString()); file.content = content; if (file.content.isEmpty()) { @@ -364,8 +361,7 @@ std::optional PreparedFileFromFilesDialog( } auto list = PreparedList(temp.error, temp.errorData); - list.albumIsPossible = isAlbum; - list.allFilesForCompress = temp.allFilesForCompress; + //list.albumIsPossible = isAlbum; list.files = std::move(filteredFiles); return list; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp index f7086ccd6..e57e5ab48 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp @@ -47,7 +47,8 @@ void AlbumPreview::setSendWay(SendFilesWay way) { void AlbumPreview::updateFileRows() { Expects(_order.size() == _thumbs.size()); - const auto isFile = (_sendWay == SendFilesWay::Files); + + const auto isFile = !_sendWay.sendImagesAsPhotos(); for (auto i = 0; i < _order.size(); i++) { _thumbs[i]->updateFileRow(isFile ? _order[i] : -1); } @@ -122,14 +123,14 @@ AlbumThumbnail *AlbumPreview::findThumb(QPoint position) const { position -= QPoint(contentLeft(), contentTop()); auto top = 0; - const auto isPhotosWay = (_sendWay == SendFilesWay::Photos); + const auto isPhotosWay = _sendWay.sendImagesAsPhotos(); const auto skip = isPhotosWay ? st::sendMediaPreviewPhotoSkip : st::sendMediaFileThumbSkip; auto find = [&](const auto &thumb) { - if (_sendWay == SendFilesWay::Album) { + if (_sendWay.groupMediaInAlbums()) { return thumb->containsPoint(position); - } else if (isPhotosWay || _sendWay == SendFilesWay::Files) { + } else { const auto bottom = top + (isPhotosWay ? thumb->photoHeight() : st::sendMediaFileThumbSize); @@ -248,14 +249,14 @@ void AlbumPreview::updateSizeAnimated( void AlbumPreview::updateSize() { const auto newHeight = [&] { - switch (_sendWay) { - case SendFilesWay::Album: + if (_sendWay.groupMediaInAlbums()) { return int(std::round(_thumbsHeightAnimation.value( _thumbsHeight))); - case SendFilesWay::Photos: return _photosHeight; - case SendFilesWay::Files: return _filesHeight; + } else if (_sendWay.sendImagesAsPhotos()) { + return _photosHeight; + } else { + return _filesHeight; } - Unexpected("Send way in AlbumPreview::updateSize"); }(); if (height() != newHeight) { resize(st::boxWideWidth, newHeight); @@ -265,10 +266,12 @@ void AlbumPreview::updateSize() { void AlbumPreview::paintEvent(QPaintEvent *e) { Painter p(this); - switch (_sendWay) { - case SendFilesWay::Album: paintAlbum(p); break; - case SendFilesWay::Photos: paintPhotos(p, e->rect()); break; - case SendFilesWay::Files: paintFiles(p, e->rect()); break; + if (_sendWay.groupMediaInAlbums()) { + paintAlbum(p); + } else if (_sendWay.sendImagesAsPhotos()) { + paintPhotos(p, e->rect()); + } else { + paintFiles(p, e->rect()); } } @@ -393,11 +396,11 @@ void AlbumPreview::mousePressEvent(QMouseEvent *e) { } void AlbumPreview::mouseMoveEvent(QMouseEvent *e) { - if (_sendWay == SendFilesWay::Files) { + if (!_sendWay.sendImagesAsPhotos()) { applyCursor(style::cur_default); return; } - const auto isAlbum = (_sendWay == SendFilesWay::Album); + const auto isAlbum = _sendWay.groupMediaInAlbums(); if (isAlbum && _draggedThumb) { const auto position = e->pos(); _draggedThumb->moveInAlbum(position - _draggedStartPosition); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h index 752cc2af7..fe9a2d160 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h @@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "ui/rp_widget.h" -#include "ui/chat/attach/attach_common.h" +#include "ui/chat/attach/attach_send_files_way.h" namespace Ui { @@ -74,7 +74,7 @@ private: void finishDrag(); const PreparedList &_list; - SendFilesWay _sendWay = SendFilesWay::Files; + SendFilesWay _sendWay; style::cursor _cursor = style::cur_default; std::vector _order; std::vector> _thumbs; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp index 7664a2168..6b2618140 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp @@ -107,9 +107,7 @@ AlbumThumbnail::AlbumThumbnail( _editMedia->setClickedCallback([=] { base::call_delayed(duration, parent, editCallback); }); - _deleteMedia->setClickedCallback([=] { - base::call_delayed(duration, parent, deleteCallback); - }); + _deleteMedia->setClickedCallback(deleteCallback); _editMedia->setIconOverride(&st::editMediaButtonIconFile); _deleteMedia->setIconOverride(&st::sendBoxAlbumGroupDeleteButtonIconFile); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.h b/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.h index 13683d356..c492d4eeb 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.h @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "ui/chat/attach/attach_common.h" +#include "ui/chat/attach/attach_send_files_way.h" #include "ui/effects/animations.h" #include "ui/grouped_layout.h" #include "ui/round_rect.h" diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_common.h b/Telegram/SourceFiles/ui/chat/attach/attach_common.h deleted file mode 100644 index 89226df88..000000000 --- a/Telegram/SourceFiles/ui/chat/attach/attach_common.h +++ /dev/null @@ -1,24 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop application for the Telegram messaging service. - -For license and copyright information please follow this link: -https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL -*/ -#pragma once - -namespace Ui { - -enum class AttachButtonType { - Edit, - Delete, - None, -}; - -enum class SendFilesWay { - Album, - Photos, - Files, -}; - -} // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp index 06712deb9..1698eb5bb 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp @@ -32,8 +32,7 @@ PreparedList PreparedList::Reordered( Expects(list.files.size() == order.size()); auto result = PreparedList(list.error, list.errorData); - result.albumIsPossible = list.albumIsPossible; - result.allFilesForCompress = list.allFilesForCompress; + result.singleAlbumIsPossible = list.singleAlbumIsPossible; result.files.reserve(list.files.size()); for (auto index : order) { result.files.push_back(std::move(list.files[index])); @@ -50,7 +49,6 @@ void PreparedList::mergeToEnd(PreparedList &&other, bool cutToAlbumSize) { errorData = other.errorData; return; } - allFilesForCompress = allFilesForCompress && other.allFilesForCompress; files.reserve(std::min( size_t(cutToAlbumSize ? kMaxAlbumCount : INT_MAX), files.size() + other.files.size())); @@ -65,13 +63,13 @@ void PreparedList::mergeToEnd(PreparedList &&other, bool cutToAlbumSize) { files, PreparedFile::AlbumType::None, [](const PreparedFile &file) { return file.type; }); - albumIsPossible = (badIt == files.end()); + singleAlbumIsPossible = (badIt == files.end()); } else { - albumIsPossible = false; + singleAlbumIsPossible = false; } } -bool PreparedList::canAddCaption(bool isAlbum, bool compressImages) const { +bool PreparedList::canAddCaption(bool isAlbum) const { const auto isSticker = [&] { if (files.empty()) { return false; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h index 05f9e3db0..56e9781ec 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include + namespace Ui { struct PreparedFileInformation { @@ -71,13 +73,16 @@ struct PreparedList { std::vector order); void mergeToEnd(PreparedList &&other, bool cutToAlbumSize = false); - [[nodiscard]] bool canAddCaption(bool isAlbum, bool compressImages) const; + [[nodiscard]] bool canAddCaption(bool isAlbum) const; Error error = Error::None; QString errorData; std::vector files; - bool allFilesForCompress = true; - bool albumIsPossible = false; + std::deque filesToProcess; + std::optional overrideSendImagesAsPhotos; + //bool someFilesForCompress = false; + //bool someAlbumIsPossible = false; + bool singleAlbumIsPossible = false; }; [[nodiscard]] int MaxAlbumItems(); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_send_files_way.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_send_files_way.cpp new file mode 100644 index 000000000..00fea6cd3 --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/attach/attach_send_files_way.cpp @@ -0,0 +1,76 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/chat/attach/attach_send_files_way.h" + +namespace Ui { + +void SendFilesWay::setGroupMediaInAlbums(bool value) { + if (value) { + _flags |= (Flag::GroupMediaInAlbums | Flag::SendImagesAsPhotos); + } else { + _flags &= ~Flag::GroupMediaInAlbums; + } +} + +void SendFilesWay::setSendImagesAsPhotos(bool value) { + if (value) { + _flags |= Flag::SendImagesAsPhotos; + } else { + _flags &= ~(Flag::SendImagesAsPhotos | Flag::GroupMediaInAlbums); + } +} + +void SendFilesWay::setGroupFiles(bool value) { + if (value) { + _flags |= Flag::GroupFiles; + } else { + _flags &= ~Flag::GroupFiles; + } +} + +//enum class SendFilesWay { // Old way. Serialize should be compatible. +// Album, +// Photos, +// Files, +//}; + +int32 SendFilesWay::serialize() const { + auto result = groupMediaInAlbums() + ? int32(0) + : sendImagesAsPhotos() + ? int32(1) + : int32(2); + if (!groupFiles()) { + result |= 0x04; + } + return result; +} + +std::optional SendFilesWay::FromSerialized(int32 value) { + auto result = SendFilesWay(); + result.setGroupFiles(!(value & 0x04)); + value &= ~0x04; + switch (value) { + case 0: + result.setGroupMediaInAlbums(true); + result.setSendImagesAsPhotos(true); + break; + case 1: + result.setGroupMediaInAlbums(false); + result.setSendImagesAsPhotos(true); + break; + case 2: + result.setGroupMediaInAlbums(false); + result.setSendImagesAsPhotos(false); + break; + default: return std::nullopt; + } + return result; +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_send_files_way.h b/Telegram/SourceFiles/ui/chat/attach/attach_send_files_way.h new file mode 100644 index 000000000..53cb646c8 --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/attach/attach_send_files_way.h @@ -0,0 +1,72 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "base/flags.h" + +namespace Ui { + +enum class AttachButtonType { + Edit, + Delete, + None, +}; + +class SendFilesWay final { +public: + [[nodiscard]] bool groupMediaInAlbums() const { + return (_flags & Flag::GroupMediaInAlbums) != 0; + } + [[nodiscard]] bool sendImagesAsPhotos() const { + return (_flags & Flag::SendImagesAsPhotos) != 0; + } + [[nodiscard]] bool groupFiles() const { + return (_flags & Flag::GroupFiles); + } + void setGroupMediaInAlbums(bool value); + void setSendImagesAsPhotos(bool value); + void setGroupFiles(bool value); + + [[nodiscard]] inline bool operator<(const SendFilesWay &other) const { + return _flags < other._flags; + } + [[nodiscard]] inline bool operator>(const SendFilesWay &other) const { + return other < *this; + } + [[nodiscard]] inline bool operator<=(const SendFilesWay &other) const { + return !(other < *this); + } + [[nodiscard]] inline bool operator>=(const SendFilesWay &other) const { + return !(*this < other); + } + [[nodiscard]] inline bool operator==(const SendFilesWay &other) const { + return _flags == other._flags; + } + [[nodiscard]] inline bool operator!=(const SendFilesWay &other) const { + return !(*this == other); + } + + [[nodiscard]] int32 serialize() const; + [[nodiscard]] static std::optional FromSerialized( + int32 value); + +private: + enum class Flag : uchar { + GroupMediaInAlbums = (1 << 0), + SendImagesAsPhotos = (1 << 1), + GroupFiles = (1 << 2), + + Default = GroupMediaInAlbums | SendImagesAsPhotos | GroupFiles, + }; + friend inline constexpr bool is_flag_type(Flag) { return true; }; + + base::flags _flags = Flag::Default; + +}; + +} // namespace Ui diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index ac073df9c..1c7d0888c 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -68,11 +68,12 @@ PRIVATE ui/chat/attach/attach_album_thumbnail.h ui/chat/attach/attach_album_preview.cpp ui/chat/attach/attach_album_preview.h - ui/chat/attach/attach_common.h ui/chat/attach/attach_extensions.cpp ui/chat/attach/attach_extensions.h ui/chat/attach/attach_prepare.cpp ui/chat/attach/attach_prepare.h + ui/chat/attach/attach_send_files_way.cpp + ui/chat/attach/attach_send_files_way.h ui/chat/attach/attach_single_file_preview.cpp ui/chat/attach/attach_single_file_preview.h ui/chat/attach/attach_single_media_preview.cpp From 202534575b6855c6d5a033d8d577cb3ecb9c8851 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 16 Oct 2020 10:52:55 +0300 Subject: [PATCH 096/190] Allow many previews in SendFilesBox. --- .../SourceFiles/boxes/edit_caption_box.cpp | 2 +- Telegram/SourceFiles/boxes/send_files_box.cpp | 313 ++++++++++-------- Telegram/SourceFiles/boxes/send_files_box.h | 17 +- .../SourceFiles/history/history_widget.cpp | 4 +- .../view/history_view_replies_section.cpp | 7 +- .../SourceFiles/storage/localimageloader.cpp | 9 +- .../SourceFiles/storage/localimageloader.h | 2 +- .../storage/storage_media_prepare.cpp | 40 +-- .../ui/chat/attach/attach_album_preview.cpp | 10 +- .../ui/chat/attach/attach_album_preview.h | 10 +- .../ui/chat/attach/attach_prepare.cpp | 57 +++- .../ui/chat/attach/attach_prepare.h | 19 +- .../attach/attach_single_file_preview.cpp | 12 +- .../chat/attach/attach_single_file_preview.h | 2 - .../attach/attach_single_media_preview.cpp | 6 +- .../chat/attach/attach_single_media_preview.h | 2 - 16 files changed, 286 insertions(+), 226 deletions(-) diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index 1a216e3bd..659151e98 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -689,7 +689,7 @@ bool EditCaptionBox::fileFromClipboard(not_null data) { } const auto file = &list.files.front(); - if (_isAlbum && (file->type == AlbumType::None)) { + if (_isAlbum && (file->type == AlbumType::File)) { const auto imageAsDoc = [&] { using Info = Ui::PreparedFileInformation; const auto fileMedia = &file->information->media; diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 01d323c81..f0accfe2e 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/input_fields.h" #include "ui/widgets/scroll_area.h" #include "ui/wrap/fade_wrap.h" +#include "ui/wrap/vertical_layout.h" #include "ui/chat/attach/attach_prepare.h" #include "ui/chat/attach/attach_send_files_way.h" #include "ui/chat/attach/attach_album_preview.h" @@ -64,12 +65,6 @@ inline bool CanAddUrls(const QList &urls) { return !urls.isEmpty() && ranges::all_of(urls, &QUrl::isLocalFile); } -inline bool IsFirstAlbumItem(const Ui::PreparedList &list) { - using AlbumType = Ui::PreparedFile::AlbumType; - return (list.files.size() > 0) - && (list.files.front().type != AlbumType::None); -} - inline bool IsSingleItem(const Ui::PreparedList &list) { return list.files.size() == 1; } @@ -98,9 +93,7 @@ void FileDialogCallback( rpl::producer FieldPlaceholder( const Ui::PreparedList &list, SendFilesWay way) { - const auto isAlbum = list.singleAlbumIsPossible - && way.groupMediaInAlbums(); - return list.canAddCaption(isAlbum) + return list.canAddCaption(way.groupMediaInAlbums()) ? tr::lng_photo_caption() : tr::lng_photos_comment(); } @@ -125,10 +118,14 @@ SendFilesBox::SendFilesBox( st::confirmCaptionArea, Ui::InputField::Mode::MultiLine, nullptr, - caption) { + caption) +, _scroll(this, st::boxScroll) +, _inner( + _scroll->setOwnedWidget( + object_ptr(_scroll.data()))) { } -void SendFilesBox::initPreview(rpl::producer desiredPreviewHeight) { +void SendFilesBox::initPreview() { using namespace rpl::mappers; setupControls(); @@ -136,8 +133,10 @@ void SendFilesBox::initPreview(rpl::producer desiredPreviewHeight) { updateBoxSize(); _dimensionsLifetime.destroy(); + _inner->resizeToWidth(st::boxWideWidth); + rpl::combine( - std::move(desiredPreviewHeight), + _inner->heightValue(), _footerHeight.value(), _titleHeight + _1 + _2 ) | rpl::start_with_next([=](int height) { @@ -146,102 +145,83 @@ void SendFilesBox::initPreview(rpl::producer desiredPreviewHeight) { std::min(st::sendMediaPreviewHeightMax, height), true); }, _dimensionsLifetime); - - if (_preview) { - _preview->show(); - } } -void SendFilesBox::prepareSingleFilePreview() { - Expects(IsSingleItem(_list)); +//void SendFilesBox::prepareSingleFilePreview() { +// const auto &file = _list.files[0]; +// const auto controller = _controller; +// const auto media = Ui::SingleMediaPreview::Create(this, [=] { +// return controller->isGifPausedAtLeastFor( +// Window::GifPauseReason::Layer); +// }, file); +// if (media) { +// _preview = media; +// initPreview(media->desiredHeightValue()); +// } else { +// const auto preview = Ui::CreateChild(this, file); +// _preview = preview; +// initPreview(preview->desiredHeightValue()); +// } +//} - const auto &file = _list.files[0]; - const auto controller = _controller; - const auto media = Ui::SingleMediaPreview::Create(this, [=] { - return controller->isGifPausedAtLeastFor( - Window::GifPauseReason::Layer); - }, file); - if (media) { - _preview = media; - initPreview(media->desiredHeightValue()); - } else { - const auto preview = Ui::CreateChild(this, file); - _preview = preview; - initPreview(preview->desiredHeightValue()); - } -} - -void SendFilesBox::prepareAlbumPreview() { - const auto wrap = Ui::CreateChild( - this, - st::boxScroll); - _albumPreview = wrap->setOwnedWidget(object_ptr( - this, - _list, - _sendWay.current())); - - addThumbButtonHandlers(wrap); - - _preview = wrap; - _albumPreview->show(); - setupShadows(wrap, _albumPreview); - - initPreview(_albumPreview->desiredHeightValue()); - - crl::on_main([=] { - wrap->scrollToY(_lastScrollTop); - _lastScrollTop = 0; - }); -} +//void SendFilesBox::prepareAlbumPreview() { +// addThumbButtonHandlers(wrap); // #TODO files +// +// setupShadows(wrap, _albumPreview); +// +// initPreview(_albumPreview->desiredHeightValue()); +// +// crl::on_main([=] { +// wrap->scrollToY(_lastScrollTop); +// _lastScrollTop = 0; +// }); +//} void SendFilesBox::addThumbButtonHandlers(not_null wrap) { - _albumPreview->thumbDeleted( - ) | rpl::start_with_next([=](auto index) { - _lastScrollTop = wrap->scrollTop(); + // #TODO files + //_albumPreview->thumbDeleted( + //) | rpl::start_with_next([=](auto index) { + // _lastScrollTop = wrap->scrollTop(); - _list.files.erase(_list.files.begin() + index); - applyAlbumOrder(); + // _list.files.erase(_list.files.begin() + index); + // applyAlbumOrder(); - if (_preview) { - _preview->deleteLater(); - } - _albumPreview = nullptr; + // if (_preview) { + // _preview->deleteLater(); + // } + // _albumPreview = nullptr; - if (IsSingleItem(_list)) { - _list.singleAlbumIsPossible = false; - } + // refreshAllAfterAlbumChanges(); + //}, _albumPreview->lifetime()); - refreshAllAfterAlbumChanges(); - }, _albumPreview->lifetime()); + //_albumPreview->thumbChanged( + //) | rpl::start_with_next([=](auto index) { + // _lastScrollTop = wrap->scrollTop(); - _albumPreview->thumbChanged( - ) | rpl::start_with_next([=](auto index) { - _lastScrollTop = wrap->scrollTop(); + // const auto callback = [=](FileDialog::OpenResult &&result) { + // FileDialogCallback( + // std::move(result), + // true, + // [=] (auto list) { + // _list.files[index] = std::move(list.files.front()); + // applyAlbumOrder(); - const auto callback = [=](FileDialog::OpenResult &&result) { - FileDialogCallback( - std::move(result), - true, - [=] (auto list) { - _list.files[index] = std::move(list.files.front()); - applyAlbumOrder(); + // if (_preview) { + // _preview->deleteLater(); + // } + // _albumPreview = nullptr; - if (_preview) { - _preview->deleteLater(); - } - _albumPreview = nullptr; + // refreshAllAfterAlbumChanges(); + // }); + // }; - refreshAllAfterAlbumChanges(); - }); - }; + // FileDialog::GetOpenPath( + // this, + // tr::lng_choose_file(tr::now), + // FileDialog::AlbumFilesFilter(), + // crl::guard(this, callback)); - FileDialog::GetOpenPath( - this, - tr::lng_choose_file(tr::now), - FileDialog::AlbumFilesFilter(), - crl::guard(this, callback)); - - }, _albumPreview->lifetime()); + //}, _albumPreview->lifetime()); } void SendFilesBox::setupShadows( @@ -288,6 +268,10 @@ void SendFilesBox::prepare() { setupCaption(); setupSendWayControls(); preparePreview(); + initPreview(); + + _scroll->show(); + _inner->show(); boxClosing() | rpl::start_with_next([=] { if (!_confirmed && _cancelledCallback) { @@ -379,10 +363,6 @@ void SendFilesBox::initSendWay() { _sendWay.changes( ) | rpl::start_with_next([=](SendFilesWay value) { updateCaptionPlaceholder(); - applyAlbumOrder(); - if (_albumPreview) { - _albumPreview->setSendWay(value); - } updateEmojiPanelGeometry(); setInnerFocus(); }, lifetime()); @@ -393,9 +373,8 @@ void SendFilesBox::updateCaptionPlaceholder() { return; } const auto sendWay = _sendWay.current(); - const auto isAlbum = _list.singleAlbumIsPossible - && sendWay.groupMediaInAlbums(); - if (!_list.canAddCaption(isAlbum) && _sendLimit == SendLimit::One) { + if (!_list.canAddCaption(sendWay.groupMediaInAlbums()) + && _sendLimit == SendLimit::One) { _caption->hide(); if (_emojiToggle) { _emojiToggle->hide(); @@ -410,11 +389,73 @@ void SendFilesBox::updateCaptionPlaceholder() { } void SendFilesBox::preparePreview() { - if (IsSingleItem(_list)) { - prepareSingleFilePreview(); - } else { - prepareAlbumPreview(); // #TODO files many albums + using Type = Ui::PreparedFile::AlbumType; + + _blocks.clear(); + auto albumStart = -1; + const auto finishAlbum = [&](int till) { + if (albumStart < 0) { + return; + } + const auto count = (till - albumStart); + const auto preview = _inner->add(object_ptr( + this, + gsl::make_span(_list.files).subspan(albumStart, count), + _sendWay.current())); + + auto &block = _blocks.emplace_back(); + block.fromIndex = albumStart; + block.tillIndex = albumStart + count; + block.preview.reset(preview); + block.preview->show(); + + _sendWay.changes( + ) | rpl::start_with_next([=](SendFilesWay value) { + applyAlbumOrder(preview, albumStart); + preview->setSendWay(value); + }, preview->lifetime()); + + albumStart = -1; + }; + const auto finishSingle = [&](int index) { + const auto &file = _list.files[index]; + const auto controller = _controller; + auto &block = _blocks.emplace_back(); + block.fromIndex = index; + block.tillIndex = index + 1; + const auto media = Ui::SingleMediaPreview::Create(this, [=] { + return controller->isGifPausedAtLeastFor( + Window::GifPauseReason::Layer); + }, file); + if (media) { + block.preview.reset( + _inner->add(object_ptr::fromRaw( + media))); + } else { + block.preview.reset( + _inner->add(object_ptr(this, file))); + } + block.preview->show(); + }; + for (auto i = 0, count = int(_list.files.size()); i != count; ++i) { + const auto type = _list.files[i].type; + if (albumStart >= 0) { + const auto albumCount = (i - albumStart); + if (type == Type::File || (albumCount == Ui::MaxAlbumItems())) { + finishAlbum(i); + } else { + continue; + } + } + if (type != Type::File) { + if (albumStart < 0) { + albumStart = i; + } + continue; + } + finishSingle(i); } + finishAlbum(_list.files.size()); } void SendFilesBox::setupControls() { @@ -479,12 +520,10 @@ void SendFilesBox::updateSendWayControlsVisibility() { _groupFiles->setVisible(!onlyOne); } -void SendFilesBox::applyAlbumOrder() { - if (!_albumPreview) { - return; - } - - const auto order = _albumPreview->takeOrder(); +void SendFilesBox::applyAlbumOrder( + not_null preview, + int from) { + const auto order = preview->takeOrder(); const auto isDefault = [&] { for (auto i = 0, count = int(order.size()); i != count; ++i) { if (order[i] != i) { @@ -497,7 +536,14 @@ void SendFilesBox::applyAlbumOrder() { return; } - _list = Ui::PreparedList::Reordered(std::move(_list), order); + auto elements = std::vector(); + elements.reserve(order.size()); + for (const auto index : order) { + elements.push_back(std::move(_list.files[from + index])); + } + for (auto i = 0, count = int(order.size()); i != count; ++i) { + _list.files[from + i] = std::move(elements[i]); + } } void SendFilesBox::setupCaption() { @@ -604,20 +650,21 @@ void SendFilesBox::captionResized() { } bool SendFilesBox::canAddFiles(not_null data) const { - const auto urls = data->hasUrls() ? data->urls() : QList(); - auto filesCount = CanAddUrls(urls) ? urls.size() : 0; - if (!filesCount && data->hasImage()) { - ++filesCount; - } + return (data->hasUrls() && CanAddUrls(data->urls())) || data->hasImage(); + //const auto urls = data->hasUrls() ? data->urls() : QList(); + //auto filesCount = CanAddUrls(urls) ? urls.size() : 0; + //if (!filesCount && data->hasImage()) { + // ++filesCount; + //} - if (_list.files.size() + filesCount > Ui::MaxAlbumItems()) { - return false; - } else if (_list.files.size() > 1 && !_albumPreview) { - return false; - } else if (!IsFirstAlbumItem(_list)) { - return false; - } - return true; + //if (_list.files.size() + filesCount > Ui::MaxAlbumItems()) { // #TODO files + // return false; + //} else if (_list.files.size() > 1 && !_albumPreview) { + // return false; + //} else if (!IsFirstAlbumItem(_list)) { + // return false; + //} + //return true; } bool SendFilesBox::addFiles(not_null data) { @@ -661,10 +708,10 @@ bool SendFilesBox::addFiles(Ui::PreparedList list) { //} else if (!IsFirstAlbumItem(_list)) { // return false; } - applyAlbumOrder(); - delete base::take(_preview); - _albumPreview = nullptr; - + //applyAlbumOrder(); + //delete base::take(_preview); + //_albumPreview = nullptr; + return false; _list.mergeToEnd(std::move(list), cutToAlbumSize); refreshAllAfterAlbumChanges(); @@ -769,10 +816,8 @@ void SendFilesBox::updateControlsGeometry() { bottom -= st::boxPhotoCompressedSkip + pointer->heightNoMargins(); } } - if (_preview) { - _preview->resize(width(), bottom - _titleHeight); - _preview->move(0, _titleHeight); - } + _scroll->resize(width(), bottom - _titleHeight); + _scroll->move(0, _titleHeight); } void SendFilesBox::setInnerFocus() { @@ -809,7 +854,7 @@ void SendFilesBox::send( Core::App().saveSettingsDelayed(); } - applyAlbumOrder(); + //applyAlbumOrder(); // #TODO files _confirmed = true; if (_confirmedCallback) { auto caption = (_caption && !_caption->isHidden()) diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h index 46ffe34b5..00f0626e8 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.h +++ b/Telegram/SourceFiles/boxes/send_files_box.h @@ -34,6 +34,7 @@ class InputField; struct GroupMediaLayout; class EmojiButton; class AlbumPreview; +class VerticalLayout; } // namespace Ui namespace Window { @@ -83,8 +84,13 @@ protected: void resizeEvent(QResizeEvent *e) override; private: + struct Block { + base::unique_qptr preview; + int fromIndex = 0; + int tillIndex = 0; + }; void initSendWay(); - void initPreview(rpl::producer desiredPreviewHeight); + void initPreview(); void setupControls(); void setupSendWayControls(); @@ -99,9 +105,7 @@ private: void emojiFilterForGeometry(not_null event); void preparePreview(); - void prepareSingleFilePreview(); - void prepareAlbumPreview(); - void applyAlbumOrder(); + void applyAlbumOrder(not_null preview, int from); void send(Api::SendOptions options, bool ctrlShiftEnter = false); void sendSilent(); @@ -157,8 +161,9 @@ private: rpl::event_stream<> _albumChanged; rpl::lifetime _dimensionsLifetime; - QWidget *_preview = nullptr; - Ui::AlbumPreview *_albumPreview = nullptr; + object_ptr _scroll; + QPointer _inner; + std::vector _blocks; int _lastScrollTop = 0; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 4a50804bd..f73155795 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -4097,9 +4097,7 @@ bool HistoryWidget::showSendingFilesError( } else if (!canWriteMessage()) { return tr::lng_forward_send_files_cant(tr::now); } - if (list.files.size() > 1 - && _peer->slowmodeApplied() - && !list.singleAlbumIsPossible) { + if (_peer->slowmodeApplied() && !list.canBeSentInSlowmode()) { return tr::lng_slowmode_no_many(tr::now); } else if (const auto left = _peer->slowmodeSecondsLeft()) { return tr::lng_slowmode_enabled( diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 6415cd87a..46a0ed4f6 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -800,15 +800,14 @@ void RepliesWidget::uploadFile( bool RepliesWidget::showSendingFilesError( const Ui::PreparedList &list) const { const auto text = [&] { + const auto peer = _history->peer; const auto error = Data::RestrictionError( - _history->peer, + peer, ChatRestriction::f_send_media); if (error) { return *error; } - if (list.files.size() > 1 - && _history->peer->slowmodeApplied() - && !list.singleAlbumIsPossible) { + if (peer->slowmodeApplied() && !list.canBeSentInSlowmode()) { return tr::lng_slowmode_no_many(tr::now); } else if (const auto left = _history->peer->slowmodeSecondsLeft()) { return tr::lng_slowmode_enabled( diff --git a/Telegram/SourceFiles/storage/localimageloader.cpp b/Telegram/SourceFiles/storage/localimageloader.cpp index e1e67bf84..6865d5200 100644 --- a/Telegram/SourceFiles/storage/localimageloader.cpp +++ b/Telegram/SourceFiles/storage/localimageloader.cpp @@ -530,10 +530,11 @@ FileLoadTask::FileLoadTask( FileLoadTask::~FileLoadTask() = default; -std::unique_ptr FileLoadTask::ReadMediaInformation( - const QString &filepath, - const QByteArray &content, - const QString &filemime) { +auto FileLoadTask::ReadMediaInformation( + const QString &filepath, + const QByteArray &content, + const QString &filemime) +-> std::unique_ptr { auto result = std::make_unique(); result->filemime = filemime; diff --git a/Telegram/SourceFiles/storage/localimageloader.h b/Telegram/SourceFiles/storage/localimageloader.h index b550bb082..02a403d9b 100644 --- a/Telegram/SourceFiles/storage/localimageloader.h +++ b/Telegram/SourceFiles/storage/localimageloader.h @@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_common.h" #include "ui/chat/attach/attach_prepare.h" -constexpr auto kFileSizeLimit = 2000 * 1024 * 1024; // Load files up to 1500mb +constexpr auto kFileSizeLimit = 2000 * 1024 * 1024; // Load files up to 2000MB enum class SendMediaType { Photo, diff --git a/Telegram/SourceFiles/storage/storage_media_prepare.cpp b/Telegram/SourceFiles/storage/storage_media_prepare.cpp index a7c79875e..aa9535964 100644 --- a/Telegram/SourceFiles/storage/storage_media_prepare.cpp +++ b/Telegram/SourceFiles/storage/storage_media_prepare.cpp @@ -62,11 +62,10 @@ QSize PrepareShownDimensions(const QImage &preview) { : result; } -bool PrepareAlbumMediaIsWaiting( +bool PrepareDetailsIsWaiting( QSemaphore &semaphore, PreparedFile &file, int previewWidth) { - // TODO: Use some special thread queue, like a separate QThreadPool. crl::async([=, &semaphore, &file] { const auto guard = gsl::finally([&] { semaphore.release(); }); if (!file.path.isEmpty()) { @@ -116,29 +115,21 @@ bool PrepareAlbumMediaIsWaiting( return true; } -void PrepareAlbum(PreparedList &result, int previewWidth) { - const auto count = int(result.files.size()); - if (count > Ui::MaxAlbumItems()) { - return; - } +void PrepareDetailsInParallel(PreparedList &result, int previewWidth) { + Expects(result.files.size() <= Ui::MaxAlbumItems()); - //result.albumIsPossible = (count > 1); auto waiting = 0; QSemaphore semaphore; for (auto &file : result.files) { - if (PrepareAlbumMediaIsWaiting(semaphore, file, previewWidth)) { + if (PrepareDetailsIsWaiting( + semaphore, + file, + previewWidth)) { ++waiting; } } if (waiting > 0) { semaphore.acquire(waiting); - //if (result.albumIsPossible) { - // const auto badIt = ranges::find( - // result.files, - // PreparedFile::AlbumType::None, - // [](const PreparedFile &file) { return file.type; }); - // result.albumIsPossible = (badIt == result.files.end()); - //} } } @@ -250,13 +241,15 @@ PreparedList PrepareMediaList(const QStringList &files, int previewWidth) { file }; } - const auto toCompress = HasExtensionFrom(file, extensionsToCompress); - if (filesize > App::kImageSizeLimit || !toCompress) { -// result.allFilesForCompress = false; + if (result.files.size() < Ui::MaxAlbumItems()) { + result.files.emplace_back(file); + result.files.back().size = filesize; + } else { + result.filesToProcess.emplace_back(file); + result.files.back().size = filesize; } - result.files.emplace_back(file); } - PrepareAlbum(result, previewWidth); + PrepareDetailsInParallel(result, previewWidth); return result; } @@ -276,7 +269,7 @@ PreparedList PrepareMediaFromImage( file.information); } result.files.push_back(std::move(file)); - PrepareAlbum(result, previewWidth); + PrepareDetailsInParallel(result, previewWidth); return result; } @@ -304,7 +297,7 @@ std::optional PreparedFileFromFilesDialog( if (isAlbum) { const auto file = &list.files.front(); if (!Core::IsMimeAcceptedForAlbum(mimeFile) - || file->type == Storage::PreparedFile::AlbumType::None) { + || file->type == Storage::PreparedFile::AlbumType::File) { errorCallback(tr::lng_edit_media_album_error); return std::nullopt; } @@ -361,7 +354,6 @@ std::optional PreparedFileFromFilesDialog( } auto list = PreparedList(temp.error, temp.errorData); - //list.albumIsPossible = isAlbum; list.files = std::move(filteredFiles); return list; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp index e57e5ab48..af6b66526 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp @@ -22,7 +22,7 @@ constexpr auto kDragDuration = crl::time(200); AlbumPreview::AlbumPreview( QWidget *parent, - const PreparedList &list, + gsl::span list, SendFilesWay way) : RpWidget(parent) , _list(list) @@ -69,7 +69,7 @@ auto AlbumPreview::generateOrderedLayout() const auto sizes = ranges::view::all( _order ) | ranges::view::transform([&](int index) { - return _list.files[index].shownDimensions; + return _list[index].shownDimensions; }) | ranges::to_vector; auto layout = LayoutMediaGroup( @@ -82,19 +82,19 @@ auto AlbumPreview::generateOrderedLayout() const } std::vector AlbumPreview::defaultOrder() const { - const auto count = int(_list.files.size()); + const auto count = int(_list.size()); return ranges::view::ints(0, count) | ranges::to_vector; } void AlbumPreview::prepareThumbs() { _order = defaultOrder(); - const auto count = int(_list.files.size()); + const auto count = int(_list.size()); const auto layout = generateOrderedLayout(); _thumbs.reserve(count); for (auto i = 0; i != count; ++i) { _thumbs.push_back(std::make_unique( - _list.files[i], + _list[i], layout[i], this, [=] { changeThumbByIndex(thumbIndex(thumbUnderCursor())); }, diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h index fe9a2d160..fad5a80db 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h @@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Ui { -struct PreparedList; +struct PreparedFile; struct GroupMediaLayout; class AlbumThumbnail; @@ -20,18 +20,18 @@ class AlbumPreview final : public RpWidget { public: AlbumPreview( QWidget *parent, - const PreparedList &list, + gsl::span list, SendFilesWay way); ~AlbumPreview(); void setSendWay(SendFilesWay way); std::vector takeOrder(); - auto thumbDeleted() { + [[nodiscard]] rpl::producer thumbDeleted() const { return _thumbDeleted.events(); } - auto thumbChanged() { + [[nodiscard]] rpl::producer thumbChanged() const { return _thumbChanged.events(); } @@ -73,7 +73,7 @@ private: void cancelDrag(); void finishDrag(); - const PreparedList &_list; + gsl::span _list; SendFilesWay _sendWay; style::cursor _cursor = style::cur_default; std::vector _order; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp index 1698eb5bb..07d23def1 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp @@ -32,7 +32,6 @@ PreparedList PreparedList::Reordered( Expects(list.files.size() == order.size()); auto result = PreparedList(list.error, list.errorData); - result.singleAlbumIsPossible = list.singleAlbumIsPossible; result.files.reserve(list.files.size()); for (auto index : order) { result.files.push_back(std::move(list.files[index])); @@ -58,28 +57,50 @@ void PreparedList::mergeToEnd(PreparedList &&other, bool cutToAlbumSize) { } files.push_back(std::move(file)); } - if (files.size() > 1 && files.size() <= kMaxAlbumCount) { - const auto badIt = ranges::find( - files, - PreparedFile::AlbumType::None, - [](const PreparedFile &file) { return file.type; }); - singleAlbumIsPossible = (badIt == files.end()); - } else { - singleAlbumIsPossible = false; - } } -bool PreparedList::canAddCaption(bool isAlbum) const { - const auto isSticker = [&] { - if (files.empty()) { - return false; - } - return Core::IsMimeSticker(files.front().mime) +bool PreparedList::canBeSentInSlowmode() const { + if (!filesToProcess.empty()) { + return false; + } else if (files.size() < 2) { + return true; + } else if (files.size() > kMaxAlbumCount) { + return false; + } + + const auto hasFiles = ranges::contains( + files, + PreparedFile::AlbumType::File, + &PreparedFile::type); + const auto hasVideos = ranges::contains( + files, + PreparedFile::AlbumType::Video, + &PreparedFile::type); + + // File-s and Video-s never can be grouped. + return !hasFiles || !hasVideos; +} + +bool PreparedList::canAddCaption(bool groupMediaInAlbums) const { + if (!filesToProcess.empty() + || files.empty() + || files.size() > kMaxAlbumCount) { + return false; + } + if (files.size() == 1) { + const auto isSticker = Core::IsMimeSticker(files.front().mime) || files.front().path.endsWith( qstr(".tgs"), Qt::CaseInsensitive); - }; - return isAlbum || (files.size() == 1 && !isSticker()); + return !isSticker; + } else if (!groupMediaInAlbums) { + return false; + } + const auto hasFiles = ranges::contains( + files, + PreparedFile::AlbumType::File, + &PreparedFile::type); + return !hasFiles; } int MaxAlbumItems() { diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h index 56e9781ec..a7a64dd51 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include #include namespace Ui { @@ -34,8 +35,15 @@ struct PreparedFileInformation { }; struct PreparedFile { + // File-s can be grouped if 'groupFiles'. + // File-s + Photo-s can be grouped if 'groupFiles && !sendImagesAsPhotos'. + // Photo-s can be grouped if '(groupFiles && !sendImagesAsPhotos) + // || (groupMediaInAlbums && sendImagesAsPhotos)'. + // Photo-s + Video-s can be grouped if 'groupMediaInAlbums + // && sendImagesAsPhotos'. + // Video-s can be grouped if 'groupMediaInAlbums'. enum class AlbumType { - None, + File, Photo, Video, }; @@ -48,10 +56,11 @@ struct PreparedFile { QString path; QByteArray content; QString mime; + int size = 0; std::unique_ptr information; QImage preview; QSize shownDimensions; - AlbumType type = AlbumType::None; + AlbumType type = AlbumType::File; }; struct PreparedList { @@ -73,16 +82,14 @@ struct PreparedList { std::vector order); void mergeToEnd(PreparedList &&other, bool cutToAlbumSize = false); - [[nodiscard]] bool canAddCaption(bool isAlbum) const; + [[nodiscard]] bool canAddCaption(bool groupMediaInAlbums) const; + [[nodiscard]] bool canBeSentInSlowmode() const; Error error = Error::None; QString errorData; std::vector files; std::deque filesToProcess; std::optional overrideSendImagesAsPhotos; - //bool someFilesForCompress = false; - //bool someAlbumIsPossible = false; - bool singleAlbumIsPossible = false; }; [[nodiscard]] int MaxAlbumItems(); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_single_file_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_single_file_preview.cpp index 43f0e0911..1e837150e 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_single_file_preview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_single_file_preview.cpp @@ -25,6 +25,11 @@ SingleFilePreview::SingleFilePreview( const PreparedFile &file) : RpWidget(parent) { preparePreview(file); + + auto h = _fileThumb.isNull() + ? (st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom()) + : (st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom()); + resize(width(), st::boxPhotoPadding.top() + h + st::msgShadow); } void SingleFilePreview::prepareThumb(const QImage &preview) { @@ -163,11 +168,4 @@ void SingleFilePreview::paintEvent(QPaintEvent *e) { p.drawTextLeft(x + nameleft, y + statustop, width(), _statusText); } -rpl::producer SingleFilePreview::desiredHeightValue() const { - auto h = _fileThumb.isNull() - ? (st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom()) - : (st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom()); - return rpl::single(st::boxPhotoPadding.top() + h + st::msgShadow); -} - } // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_single_file_preview.h b/Telegram/SourceFiles/ui/chat/attach/attach_single_file_preview.h index 442106148..bb06249dd 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_single_file_preview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_single_file_preview.h @@ -19,8 +19,6 @@ public: QWidget *parent, const PreparedFile &file); - rpl::producer desiredHeightValue() const override; - protected: void paintEvent(QPaintEvent *e) override; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.cpp index 18b41cbb9..3d88cbb35 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.cpp @@ -135,6 +135,8 @@ void SingleMediaPreview::preparePreview( _preview.setDevicePixelRatio(style::DevicePixelRatio()); prepareAnimatedPreview(animatedPreviewPath); + + resize(width(), st::boxPhotoPadding.top() + _previewHeight); } void SingleMediaPreview::prepareAnimatedPreview( @@ -228,8 +230,4 @@ void SingleMediaPreview::paintEvent(QPaintEvent *e) { } } -rpl::producer SingleMediaPreview::desiredHeightValue() const { - return rpl::single(st::boxPhotoPadding.top() + _previewHeight); -} - } // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.h b/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.h index 17e035e8e..8f61787d6 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.h @@ -38,8 +38,6 @@ public: return _canSendAsPhoto; } - rpl::producer desiredHeightValue() const override; - protected: void paintEvent(QPaintEvent *e) override; From 0539cc9448c18bfd4cd7a829c0ef9d48eac13738 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 16 Oct 2020 13:38:12 +0300 Subject: [PATCH 097/190] Add a way to recreate SendFilesBox content. --- Telegram/SourceFiles/apiwrap.cpp | 1 + Telegram/SourceFiles/boxes/boxes.style | 6 +- .../SourceFiles/boxes/edit_caption_box.cpp | 2 +- Telegram/SourceFiles/boxes/send_files_box.cpp | 254 ++++++++++-------- Telegram/SourceFiles/boxes/send_files_box.h | 36 ++- .../storage/storage_media_prepare.cpp | 11 +- .../ui/chat/attach/attach_album_preview.cpp | 41 +-- .../ui/chat/attach/attach_album_preview.h | 8 +- .../ui/chat/attach/attach_album_thumbnail.cpp | 6 +- .../ui/chat/attach/attach_prepare.cpp | 6 +- .../ui/chat/attach/attach_prepare.h | 1 + 11 files changed, 217 insertions(+), 155 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 1830eee54..7de96c1ad 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -4235,6 +4235,7 @@ void ApiWrap::sendFiles( type = SendMediaType::Photo; break; case Ui::PreparedFile::AlbumType::Video: + case Ui::PreparedFile::AlbumType::File: type = SendMediaType::File; break; default: Unexpected("AlbumType in uploadFilesAfterConfirmation"); diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index ac791872f..528cd1f70 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -489,7 +489,7 @@ backgroundScroll: ScrollArea(boxScroll) { deltab: 10px; } -editMediaButtonSize: 29px; +editMediaButtonSize: 28px; editMediaButtonSkip: 8px; editMediaButtonFileSkipRight: 1px; editMediaButtonFileSkipTop: 7px; @@ -514,6 +514,10 @@ sendBoxAlbumGroupSkipTop: 6px; sendBoxAlbumGroupRadius: 12px; sendBoxAlbumGroupHeight: 25px; +sendBoxFileGroupSkipTop: 2px; +sendBoxFileGroupSkipRight: 0px; +sendBoxFileGroupEditInternalSkip: 4px; + sendBoxAlbumGroupEditButtonIcon: editMediaButtonIconPhoto; sendBoxAlbumGroupEditButtonIconPosition: point(4px, -1px); diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index 659151e98..03e2f9e47 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -689,7 +689,7 @@ bool EditCaptionBox::fileFromClipboard(not_null data) { } const auto file = &list.files.front(); - if (_isAlbum && (file->type == AlbumType::File)) { + if (_isAlbum && (file->type == AlbumType::File || file->type == AlbumType::None)) { const auto imageAsDoc = [&] { using Info = Ui::PreparedFileInformation; const auto fileMedia = &file->information->media; diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index f0accfe2e..f405c48a1 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -100,6 +100,95 @@ rpl::producer FieldPlaceholder( } // namespace +SendFilesBox::Block::Block( + not_null parent, + not_null*> items, + int from, + int till, + Fn gifPaused, + SendFilesWay way) +: _items(items) +, _from(from) +, _till(till) { + Expects(from >= 0); + Expects(till > from); + Expects(till <= items->size()); + + const auto count = till - from; + const auto my = gsl::make_span(*items).subspan(from, count); + const auto &first = my.front(); + _isAlbum = (my.size() > 1) + || (first.type == Ui::PreparedFile::AlbumType::Photo); + if (_isAlbum) { + const auto preview = Ui::CreateChild( + parent.get(), + my, + way); + _preview.reset(preview); + } else { + const auto media = Ui::SingleMediaPreview::Create( + parent, + gifPaused, + first); + if (media) { + _preview.reset(media); + } else { + _preview.reset( + Ui::CreateChild(parent.get(), first)); + } + } + _preview->show(); +} + +int SendFilesBox::Block::fromIndex() const { + return _from; +} + +int SendFilesBox::Block::tillIndex() const { + return _till; +} + +object_ptr SendFilesBox::Block::takeWidget() { + return object_ptr::fromRaw(_preview.get()); +} + +void SendFilesBox::Block::setSendWay(Ui::SendFilesWay way) { + if (!_isAlbum) { + return; + } + applyAlbumOrder(); + const auto album = static_cast(_preview.get()); + album->setSendWay(way); +} + +void SendFilesBox::Block::applyAlbumOrder() { + if (!_isAlbum) { + return; + } + const auto album = static_cast(_preview.get()); + const auto order = album->takeOrder(); + const auto isIdentity = [&] { + for (auto i = 0, count = int(order.size()); i != count; ++i) { + if (order[i] != i) { + return false; + } + } + return true; + }(); + if (isIdentity) { + return; + } + + auto elements = std::vector(); + elements.reserve(order.size()); + for (const auto index : order) { + elements.push_back(std::move((*_items)[_from + index])); + } + for (auto i = 0, count = int(order.size()); i != count; ++i) { + (*_items)[_from + i] = std::move(elements[i]); + } +} + SendFilesBox::SendFilesBox( QWidget*, not_null controller, @@ -147,36 +236,6 @@ void SendFilesBox::initPreview() { }, _dimensionsLifetime); } -//void SendFilesBox::prepareSingleFilePreview() { -// const auto &file = _list.files[0]; -// const auto controller = _controller; -// const auto media = Ui::SingleMediaPreview::Create(this, [=] { -// return controller->isGifPausedAtLeastFor( -// Window::GifPauseReason::Layer); -// }, file); -// if (media) { -// _preview = media; -// initPreview(media->desiredHeightValue()); -// } else { -// const auto preview = Ui::CreateChild(this, file); -// _preview = preview; -// initPreview(preview->desiredHeightValue()); -// } -//} - -//void SendFilesBox::prepareAlbumPreview() { -// addThumbButtonHandlers(wrap); // #TODO files -// -// setupShadows(wrap, _albumPreview); -// -// initPreview(_albumPreview->desiredHeightValue()); -// -// crl::on_main([=] { -// wrap->scrollToY(_lastScrollTop); -// _lastScrollTop = 0; -// }); -//} - void SendFilesBox::addThumbButtonHandlers(not_null wrap) { // #TODO files //_albumPreview->thumbDeleted( @@ -224,14 +283,12 @@ void SendFilesBox::addThumbButtonHandlers(not_null wrap) { //}, _albumPreview->lifetime()); } -void SendFilesBox::setupShadows( - not_null wrap, - not_null content) { +void SendFilesBox::setupShadows() { using namespace rpl::mappers; const auto topShadow = Ui::CreateChild(this); const auto bottomShadow = Ui::CreateChild(this); - wrap->geometryValue( + _scroll->geometryValue( ) | rpl::start_with_next_done([=](const QRect &geometry) { topShadow->resizeToWidth(geometry.width()); topShadow->move( @@ -246,11 +303,11 @@ void SendFilesBox::setupShadows( Ui::DestroyChild(b.data()); }, topShadow->lifetime()); - topShadow->toggleOn(wrap->scrollTopValue() | rpl::map(_1 > 0)); + topShadow->toggleOn(_scroll->scrollTopValue() | rpl::map(_1 > 0)); bottomShadow->toggleOn(rpl::combine( - wrap->scrollTopValue(), - wrap->heightValue(), - content->heightValue(), + _scroll->scrollTopValue(), + _scroll->heightValue(), + _inner->heightValue(), _1 + _2 < _3)); } @@ -269,9 +326,7 @@ void SendFilesBox::prepare() { setupSendWayControls(); preparePreview(); initPreview(); - - _scroll->show(); - _inner->show(); + setupShadows(); boxClosing() | rpl::start_with_next([=] { if (!_confirmed && _cancelledCallback) { @@ -364,6 +419,9 @@ void SendFilesBox::initSendWay() { ) | rpl::start_with_next([=](SendFilesWay value) { updateCaptionPlaceholder(); updateEmojiPanelGeometry(); + for (auto &block : _blocks) { + block.setSendWay(value); + } setInnerFocus(); }, lifetime()); } @@ -389,73 +447,59 @@ void SendFilesBox::updateCaptionPlaceholder() { } void SendFilesBox::preparePreview() { + generatePreviewFrom(0); +} + +void SendFilesBox::generatePreviewFrom(int fromBlock) { + Expects(fromBlock <= _blocks.size()); + using Type = Ui::PreparedFile::AlbumType; - _blocks.clear(); + _blocks.erase(_blocks.begin() + fromBlock, _blocks.end()); + + const auto fromItem = _blocks.empty() ? 0 : _blocks.back().tillIndex(); + Assert(fromItem <= _list.files.size()); + auto albumStart = -1; - const auto finishAlbum = [&](int till) { - if (albumStart < 0) { - return; - } - const auto count = (till - albumStart); - const auto preview = _inner->add(object_ptr( - this, - gsl::make_span(_list.files).subspan(albumStart, count), - _sendWay.current())); - - auto &block = _blocks.emplace_back(); - block.fromIndex = albumStart; - block.tillIndex = albumStart + count; - block.preview.reset(preview); - block.preview->show(); - - _sendWay.changes( - ) | rpl::start_with_next([=](SendFilesWay value) { - applyAlbumOrder(preview, albumStart); - preview->setSendWay(value); - }, preview->lifetime()); - - albumStart = -1; + const auto gifPaused = [controller = _controller] { + return controller->isGifPausedAtLeastFor( + Window::GifPauseReason::Layer); }; - const auto finishSingle = [&](int index) { - const auto &file = _list.files[index]; - const auto controller = _controller; - auto &block = _blocks.emplace_back(); - block.fromIndex = index; - block.tillIndex = index + 1; - const auto media = Ui::SingleMediaPreview::Create(this, [=] { - return controller->isGifPausedAtLeastFor( - Window::GifPauseReason::Layer); - }, file); - if (media) { - block.preview.reset( - _inner->add(object_ptr::fromRaw( - media))); - } else { - block.preview.reset( - _inner->add(object_ptr(this, file))); - } - block.preview->show(); + const auto pushBlock = [&](int from, int till) { + _blocks.emplace_back( + _inner.data(), + &_list.files, + from, + till, + gifPaused, + _sendWay.current()); + _inner->add(_blocks.back().takeWidget()); }; - for (auto i = 0, count = int(_list.files.size()); i != count; ++i) { + for (auto i = fromItem, till = int(_list.files.size()); i != till; ++i) { const auto type = _list.files[i].type; if (albumStart >= 0) { const auto albumCount = (i - albumStart); - if (type == Type::File || (albumCount == Ui::MaxAlbumItems())) { - finishAlbum(i); + if ((type == Type::File) + || (type == Type::None) + || (albumCount == Ui::MaxAlbumItems())) { + pushBlock(std::exchange(albumStart, -1), i); } else { continue; } } - if (type != Type::File) { + if (type != Type::File && type != Type::None) { if (albumStart < 0) { albumStart = i; } continue; } - finishSingle(i); + pushBlock(i, i + 1); } - finishAlbum(_list.files.size()); + if (albumStart >= 0) { + pushBlock(albumStart, _list.files.size()); + } + + _scroll->scrollToY(0); } void SendFilesBox::setupControls() { @@ -520,32 +564,6 @@ void SendFilesBox::updateSendWayControlsVisibility() { _groupFiles->setVisible(!onlyOne); } -void SendFilesBox::applyAlbumOrder( - not_null preview, - int from) { - const auto order = preview->takeOrder(); - const auto isDefault = [&] { - for (auto i = 0, count = int(order.size()); i != count; ++i) { - if (order[i] != i) { - return false; - } - } - return true; - }(); - if (isDefault) { - return; - } - - auto elements = std::vector(); - elements.reserve(order.size()); - for (const auto index : order) { - elements.push_back(std::move(_list.files[from + index])); - } - for (auto i = 0, count = int(order.size()); i != count; ++i) { - _list.files[from + i] = std::move(elements[i]); - } -} - void SendFilesBox::setupCaption() { _caption->setMaxLength( _controller->session().serverConfig().captionLengthMax); @@ -854,7 +872,9 @@ void SendFilesBox::send( Core::App().saveSettingsDelayed(); } - //applyAlbumOrder(); // #TODO files + for (auto &block : _blocks) { + block.applyAlbumOrder(); + } _confirmed = true; if (_confirmedCallback) { auto caption = (_caption && !_caption->isHidden()) diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h index 00f0626e8..b2df78752 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.h +++ b/Telegram/SourceFiles/boxes/send_files_box.h @@ -84,10 +84,32 @@ protected: void resizeEvent(QResizeEvent *e) override; private: - struct Block { - base::unique_qptr preview; - int fromIndex = 0; - int tillIndex = 0; + class Block final { + public: + Block( + not_null parent, + not_null*> items, + int from, + int till, + Fn gifPaused, + Ui::SendFilesWay way); + Block(Block &&other) = default; + Block &operator=(Block &&other) = default; + + [[nodiscard]] int fromIndex() const; + [[nodiscard]] int tillIndex() const; + [[nodiscard]] object_ptr takeWidget(); + + void setSendWay(Ui::SendFilesWay way); + void applyAlbumOrder(); + + private: + base::unique_qptr _preview; + not_null*> _items; + int _from = 0; + int _till = 0; + bool _isAlbum = false; + }; void initSendWay(); void initPreview(); @@ -95,9 +117,7 @@ private: void setupControls(); void setupSendWayControls(); void setupCaption(); - void setupShadows( - not_null wrap, - not_null content); + void setupShadows(); void setupEmojiPanel(); void updateSendWayControlsVisibility(); @@ -105,7 +125,7 @@ private: void emojiFilterForGeometry(not_null event); void preparePreview(); - void applyAlbumOrder(not_null preview, int from); + void generatePreviewFrom(int fromBlock); void send(Api::SendOptions options, bool ctrlShiftEnter = false); void sendSilent(); diff --git a/Telegram/SourceFiles/storage/storage_media_prepare.cpp b/Telegram/SourceFiles/storage/storage_media_prepare.cpp index aa9535964..b0cc66abf 100644 --- a/Telegram/SourceFiles/storage/storage_media_prepare.cpp +++ b/Telegram/SourceFiles/storage/storage_media_prepare.cpp @@ -97,6 +97,8 @@ bool PrepareDetailsIsWaiting( Assert(!file.preview.isNull()); file.preview.setDevicePixelRatio(cRetinaFactor()); file.type = PreparedFile::AlbumType::Photo; + } else if (Core::IsMimeSticker(file.mime)) { + file.type = PreparedFile::AlbumType::None; } } else if (const auto video = std::get_if