From 6d08394adccf3ea0d4e1ca4c24027385624863f2 Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Tue, 18 Dec 2018 14:45:06 +0400 Subject: [PATCH] Divide history_media_types to several modules. --- Telegram/SourceFiles/apiwrap.cpp | 1 - Telegram/SourceFiles/app.cpp | 20 +- Telegram/SourceFiles/boxes/send_files_box.cpp | 2 +- Telegram/SourceFiles/boxes/share_box.cpp | 2 +- Telegram/SourceFiles/data/data_document.cpp | 1 - .../SourceFiles/data/data_media_types.cpp | 16 +- Telegram/SourceFiles/data/data_photo.cpp | 1 - Telegram/SourceFiles/data/data_session.cpp | 2 +- .../SourceFiles/data/data_shared_media.cpp | 2 +- Telegram/SourceFiles/facades.cpp | 2 +- .../admin_log/history_admin_log_inner.cpp | 5 +- Telegram/SourceFiles/history/history.cpp | 2 +- .../history/history_inner_widget.cpp | 6 +- Telegram/SourceFiles/history/history_item.cpp | 3 +- .../history/history_item_components.cpp | 3 +- .../history/history_media_types.cpp | 5134 ----------------- .../SourceFiles/history/history_media_types.h | 990 ---- .../SourceFiles/history/history_message.cpp | 2 +- .../SourceFiles/history/history_service.cpp | 3 +- .../SourceFiles/history/history_widget.cpp | 5 +- .../history/{ => media}/history_media.cpp | 2 +- .../history/{ => media}/history_media.h | 0 .../history/media/history_media_call.cpp | 111 + .../history/media/history_media_call.h | 59 + .../history/media/history_media_common.cpp | 92 + .../history/media/history_media_common.h | 36 + .../history/media/history_media_contact.cpp | 216 + .../history/media/history_media_contact.h | 71 + .../history/media/history_media_document.cpp | 770 +++ .../history/media/history_media_document.h | 77 + .../history/media/history_media_file.cpp | 126 + .../history/media/history_media_file.h | 101 + .../history/media/history_media_game.cpp | 437 ++ .../history/media/history_media_game.h | 104 + .../history/media/history_media_gif.cpp | 924 +++ .../history/media/history_media_gif.h | 120 + .../{ => media}/history_media_grouped.cpp | 3 +- .../{ => media}/history_media_grouped.h | 2 +- .../history/media/history_media_invoice.cpp | 447 ++ .../history/media/history_media_invoice.h | 98 + .../history/media/history_media_location.cpp | 311 + .../history/media/history_media_location.h | 72 + .../history/media/history_media_photo.cpp | 530 ++ .../history/media/history_media_photo.h | 102 + .../history/media/history_media_sticker.cpp | 273 + .../history/media/history_media_sticker.h | 67 + .../history/media/history_media_video.cpp | 525 ++ .../history/media/history_media_video.h | 96 + .../history/media/history_media_web_page.cpp | 696 +++ .../history/media/history_media_web_page.h | 118 + .../view/history_view_context_menu.cpp | 3 +- .../history/view/history_view_element.cpp | 4 +- .../history/view/history_view_list_widget.cpp | 3 +- .../history/view/history_view_message.cpp | 4 +- .../view/history_view_service_message.cpp | 2 +- Telegram/SourceFiles/mainwidget.cpp | 2 +- .../media/player/media_player_float.cpp | 2 +- .../media/view/media_view_group_thumbs.cpp | 2 +- Telegram/SourceFiles/mediaview.cpp | 1 - Telegram/gyp/telegram_sources.txt | 36 +- 60 files changed, 6673 insertions(+), 6174 deletions(-) delete mode 100644 Telegram/SourceFiles/history/history_media_types.cpp delete mode 100644 Telegram/SourceFiles/history/history_media_types.h rename Telegram/SourceFiles/history/{ => media}/history_media.cpp (97%) rename Telegram/SourceFiles/history/{ => media}/history_media.h (100%) create mode 100644 Telegram/SourceFiles/history/media/history_media_call.cpp create mode 100644 Telegram/SourceFiles/history/media/history_media_call.h create mode 100644 Telegram/SourceFiles/history/media/history_media_common.cpp create mode 100644 Telegram/SourceFiles/history/media/history_media_common.h create mode 100644 Telegram/SourceFiles/history/media/history_media_contact.cpp create mode 100644 Telegram/SourceFiles/history/media/history_media_contact.h create mode 100644 Telegram/SourceFiles/history/media/history_media_document.cpp create mode 100644 Telegram/SourceFiles/history/media/history_media_document.h create mode 100644 Telegram/SourceFiles/history/media/history_media_file.cpp create mode 100644 Telegram/SourceFiles/history/media/history_media_file.h create mode 100644 Telegram/SourceFiles/history/media/history_media_game.cpp create mode 100644 Telegram/SourceFiles/history/media/history_media_game.h create mode 100644 Telegram/SourceFiles/history/media/history_media_gif.cpp create mode 100644 Telegram/SourceFiles/history/media/history_media_gif.h rename Telegram/SourceFiles/history/{ => media}/history_media_grouped.cpp (99%) rename Telegram/SourceFiles/history/{ => media}/history_media_grouped.h (98%) create mode 100644 Telegram/SourceFiles/history/media/history_media_invoice.cpp create mode 100644 Telegram/SourceFiles/history/media/history_media_invoice.h create mode 100644 Telegram/SourceFiles/history/media/history_media_location.cpp create mode 100644 Telegram/SourceFiles/history/media/history_media_location.h create mode 100644 Telegram/SourceFiles/history/media/history_media_photo.cpp create mode 100644 Telegram/SourceFiles/history/media/history_media_photo.h create mode 100644 Telegram/SourceFiles/history/media/history_media_sticker.cpp create mode 100644 Telegram/SourceFiles/history/media/history_media_sticker.h create mode 100644 Telegram/SourceFiles/history/media/history_media_video.cpp create mode 100644 Telegram/SourceFiles/history/media/history_media_video.h create mode 100644 Telegram/SourceFiles/history/media/history_media_web_page.cpp create mode 100644 Telegram/SourceFiles/history/media/history_media_web_page.h diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index e81de07fc..1b7c7c851 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -30,7 +30,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/add_contact_box.h" #include "history/history.h" #include "history/history_message.h" -#include "history/history_media_types.h" #include "history/history_item_components.h" #include "history/feed/history_feed_section.h" #include "storage/localstorage.h" diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 5dfea1f6e..bd63c469a 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -17,31 +17,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_abstract_structure.h" #include "data/data_media_types.h" #include "data/data_session.h" +#include "data/data_document.h" #include "history/history.h" #include "history/history_location_manager.h" -#include "history/history_media_types.h" #include "history/history_item_components.h" #include "history/view/history_view_service_message.h" #include "media/media_audio.h" #include "ui/image/image.h" #include "inline_bots/inline_bot_layout_item.h" +#include "core/crash_reports.h" +#include "core/update_checker.h" +#include "window/themes/window_theme.h" +#include "window/notifications_manager.h" +#include "platform/platform_notifications_manager.h" +#include "storage/file_upload.h" +#include "storage/localstorage.h" +#include "storage/storage_facade.h" +#include "storage/storage_shared_media.h" #include "messenger.h" #include "application.h" -#include "storage/file_upload.h" #include "mainwindow.h" #include "mainwidget.h" -#include "storage/localstorage.h" #include "apiwrap.h" #include "numbers.h" #include "observer_peer.h" #include "auth_session.h" -#include "core/crash_reports.h" -#include "core/update_checker.h" -#include "storage/storage_facade.h" -#include "storage/storage_shared_media.h" -#include "window/themes/window_theme.h" -#include "window/notifications_manager.h" -#include "platform/platform_notifications_manager.h" #ifdef OS_MAC_OLD #include <libexif/exif-data.h> diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 734f2ca3e..f6cbcf807 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -11,7 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/localstorage.h" #include "storage/storage_media_prepare.h" #include "mainwidget.h" -#include "history/history_media_types.h" #include "chat_helpers/message_field.h" #include "chat_helpers/emoji_suggestions_widget.h" #include "chat_helpers/tabbed_panel.h" @@ -27,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/grouped_layout.h" #include "ui/text_options.h" #include "ui/special_buttons.h" +#include "data/data_document.h" #include "media/media_clip_reader.h" #include "window/window_controller.h" #include "layout.h" diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index bf7060c92..12fd6dff1 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -25,7 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text_options.h" #include "chat_helpers/message_field.h" #include "history/history.h" -#include "history/history_media_types.h" +//#include "history/media/history_media_types.h" #include "history/history_message.h" #include "window/themes/window_theme.h" #include "boxes/peer_list_box.h" diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index 8c964ec60..1c607bd58 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -20,7 +20,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/platform_specific.h" #include "history/history.h" #include "history/history_item.h" -#include "history/history_media_types.h" #include "window/window_controller.h" #include "storage/cache/storage_cache_database.h" #include "boxes/confirm_box.h" diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index a407b34ba..5220ade16 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -7,10 +7,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_media_types.h" -#include "history/history_media_types.h" #include "history/history_item.h" #include "history/history_location_manager.h" #include "history/view/history_view_element.h" +#include "history/media/history_media_photo.h" +#include "history/media/history_media_sticker.h" +#include "history/media/history_media_gif.h" +#include "history/media/history_media_video.h" +#include "history/media/history_media_document.h" +#include "history/media/history_media_contact.h" +#include "history/media/history_media_location.h" +#include "history/media/history_media_game.h" +#include "history/media/history_media_invoice.h" +#include "history/media/history_media_call.h" +#include "history/media/history_media_web_page.h" #include "ui/image/image.h" #include "ui/image/image_source.h" #include "ui/text_options.h" @@ -18,6 +28,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_shared_media.h" #include "storage/localstorage.h" #include "data/data_session.h" +#include "data/data_photo.h" +#include "data/data_document.h" +#include "data/data_game.h" +#include "data/data_web_page.h" #include "lang/lang_keys.h" #include "auth_session.h" #include "layout.h" diff --git a/Telegram/SourceFiles/data/data_photo.cpp b/Telegram/SourceFiles/data/data_photo.cpp index 77b575a1d..ecf8965da 100644 --- a/Telegram/SourceFiles/data/data_photo.cpp +++ b/Telegram/SourceFiles/data/data_photo.cpp @@ -11,7 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/image/image.h" #include "ui/image/image_source.h" #include "mainwidget.h" -#include "history/history_media_types.h" #include "auth_session.h" #include "messenger.h" diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 20a2baaf9..058d5ac73 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -17,7 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/notifications_manager.h" #include "history/history.h" #include "history/history_item_components.h" -#include "history/history_media.h" +#include "history/media/history_media.h" #include "history/view/history_view_element.h" #include "inline_bots/inline_bot_layout_item.h" #include "storage/localstorage.h" diff --git a/Telegram/SourceFiles/data/data_shared_media.cpp b/Telegram/SourceFiles/data/data_shared_media.cpp index a824a86e2..cf1749f1e 100644 --- a/Telegram/SourceFiles/data/data_shared_media.cpp +++ b/Telegram/SourceFiles/data/data_shared_media.cpp @@ -14,8 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_shared_media.h" #include "history/history.h" #include "history/history_item.h" -#include "history/history_media_types.h" #include "data/data_media_types.h" +#include "data/data_photo.h" #include "data/data_sparse_ids.h" #include "data/data_session.h" #include "info/info_memento.h" diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 499e2afff..2de246475 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -24,7 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/observer.h" #include "history/history.h" #include "history/history_item.h" -#include "history/history_media.h" +#include "history/media/history_media.h" #include "styles/style_history.h" #include "data/data_session.h" 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 5fab1052f..47309847b 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -9,7 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_history.h" #include "history/history.h" -#include "history/history_media_types.h" +#include "history/media/history_media.h" +#include "history/media/history_media_web_page.h" #include "history/history_message.h" #include "history/history_item_components.h" #include "history/history_item_text.h" @@ -35,6 +36,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "boxes/edit_participant_box.h" #include "data/data_session.h" +#include "data/data_photo.h" +#include "data/data_document.h" #include "data/data_media_types.h" namespace AdminLog { diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 5cc9c41ef..79630b9a9 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_element.h" #include "history/admin_log/history_admin_log_section.h" #include "history/history_message.h" -#include "history/history_media_types.h" #include "history/history_service.h" #include "history/history_item_components.h" #include "history/history_inner_widget.h" @@ -33,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_feed_messages.h" #include "data/data_channel_admins.h" #include "data/data_feed.h" +#include "data/data_photo.h" #include "ui/image/image.h" #include "ui/text_options.h" #include "core/crash_reports.h" diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 2880e34e2..c40245f26 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -13,7 +13,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/crash_reports.h" #include "history/history.h" #include "history/history_message.h" -#include "history/history_media_types.h" +#include "history/media/history_media.h" +#include "history/media/history_media_sticker.h" +#include "history/media/history_media_web_page.h" #include "history/history_item_components.h" #include "history/history_item_text.h" #include "history/view/history_view_message.h" @@ -39,6 +41,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "data/data_session.h" #include "data/data_media_types.h" +#include "data/data_document.h" +#include "data/data_photo.h" namespace { diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index b90954c17..029dd61e4 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -13,8 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_element.h" #include "history/view/history_view_service_message.h" #include "history/history_item_components.h" -#include "history/history_media_types.h" -#include "history/history_media_grouped.h" +#include "history/media/history_media_grouped.h" #include "history/history_service.h" #include "history/history_message.h" #include "history/history.h" diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index bd38a55d4..a496ac051 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -12,9 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/image/image.h" #include "ui/text_options.h" #include "history/history_message.h" -#include "history/history_media.h" -#include "history/history_media_types.h" #include "history/view/history_view_service_message.h" +#include "history/media/history_media_document.h" #include "media/media_audio.h" #include "media/player/media_player_instance.h" #include "auth_session.h" diff --git a/Telegram/SourceFiles/history/history_media_types.cpp b/Telegram/SourceFiles/history/history_media_types.cpp deleted file mode 100644 index 1867613eb..000000000 --- a/Telegram/SourceFiles/history/history_media_types.cpp +++ /dev/null @@ -1,5134 +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 -*/ -#include "history/history_media_types.h" - -#include "lang/lang_keys.h" -#include "mainwidget.h" -#include "layout.h" -#include "mainwindow.h" -#include "storage/localstorage.h" -#include "storage/storage_shared_media.h" -#include "media/media_audio.h" -#include "media/media_clip_reader.h" -#include "media/player/media_player_instance.h" -#include "media/player/media_player_round_controller.h" -#include "media/view/media_clip_playback.h" -#include "boxes/confirm_box.h" -#include "boxes/add_contact_box.h" -#include "boxes/sticker_set_box.h" -#include "core/click_handler_types.h" -#include "history/history.h" -#include "history/history_item_components.h" -#include "history/history_location_manager.h" -#include "history/history_message.h" -#include "history/history_media_grouped.h" -#include "history/view/history_view_element.h" -#include "history/view/history_view_cursor_state.h" -#include "window/main_window.h" -#include "window/window_controller.h" -#include "styles/style_history.h" -#include "calls/calls_instance.h" -#include "ui/image/image.h" -#include "ui/empty_userpic.h" -#include "ui/grouped_layout.h" -#include "ui/text_options.h" -#include "ui/emoji_config.h" -#include "data/data_session.h" -#include "data/data_media_types.h" - -namespace { - -constexpr auto kMaxGifForwardedBarLines = 4; -constexpr auto kMaxOriginalEntryLines = 8192; - -using TextState = HistoryView::TextState; - -int documentMaxStatusWidth(DocumentData *document) { - auto result = st::normalFont->width(formatDownloadText(document->size, document->size)); - if (const auto song = document->song()) { - accumulate_max(result, st::normalFont->width(formatPlayedText(song->duration, song->duration))); - accumulate_max(result, st::normalFont->width(formatDurationAndSizeText(song->duration, document->size))); - } else if (const auto voice = document->voice()) { - accumulate_max(result, st::normalFont->width(formatPlayedText(voice->duration, voice->duration))); - accumulate_max(result, st::normalFont->width(formatDurationAndSizeText(voice->duration, document->size))); - } else if (document->isVideoFile()) { - accumulate_max(result, st::normalFont->width(formatDurationAndSizeText(document->duration(), document->size))); - } else { - accumulate_max(result, st::normalFont->width(formatSizeText(document->size))); - } - return result; -} - -int gifMaxStatusWidth(DocumentData *document) { - auto result = st::normalFont->width(formatDownloadText(document->size, document->size)); - accumulate_max(result, st::normalFont->width(formatGifAndSizeText(document->size))); - return result; -} - -std::unique_ptr<HistoryMedia> CreateAttach( - not_null<HistoryView::Element*> parent, - DocumentData *document, - PhotoData *photo, - const std::vector<std::unique_ptr<Data::Media>> &collage = {}) { - if (!collage.empty()) { - return std::make_unique<HistoryGroupedMedia>(parent, collage); - } else if (document) { - if (document->sticker()) { - return std::make_unique<HistorySticker>(parent, document); - } else if (document->isAnimation()) { - return std::make_unique<HistoryGif>( - parent, - document); - } else if (document->isVideoFile()) { - return std::make_unique<HistoryVideo>( - parent, - parent->data(), - document); - } - return std::make_unique<HistoryDocument>( - parent, - document); - } else if (photo) { - return std::make_unique<HistoryPhoto>( - parent, - parent->data(), - photo); - } - return nullptr; -} - -std::vector<std::unique_ptr<Data::Media>> PrepareCollageMedia( - not_null<HistoryItem*> parent, - const WebPageCollage &data) { - auto result = std::vector<std::unique_ptr<Data::Media>>(); - result.reserve(data.items.size()); - for (const auto item : data.items) { - if (const auto document = base::get_if<DocumentData*>(&item)) { - result.push_back(std::make_unique<Data::MediaFile>( - parent, - *document)); - } else if (const auto photo = base::get_if<PhotoData*>(&item)) { - result.push_back(std::make_unique<Data::MediaPhoto>( - parent, - *photo)); - } else { - return {}; - } - if (!result.back()->canBeGrouped()) { - return {}; - } - } - return result; -} - -void PaintInterpolatedIcon( - Painter &p, - const style::icon &a, - const style::icon &b, - float64 b_ratio, - QRect rect) { - PainterHighQualityEnabler hq(p); - p.save(); - p.translate(rect.center()); - p.setOpacity(b_ratio); - p.scale(b_ratio, b_ratio); - b.paintInCenter(p, rect.translated(-rect.center())); - p.restore(); - - p.save(); - p.translate(rect.center()); - p.setOpacity(1. - b_ratio); - p.scale(1. - b_ratio, 1. - b_ratio); - a.paintInCenter(p, rect.translated(-rect.center())); - p.restore(); -} - -} // namespace - -QString FillAmountAndCurrency(uint64 amount, const QString ¤cy) { - static const auto ShortCurrencyNames = QMap<QString, QString> { - { qsl("USD"), QString::fromUtf8("\x24") }, - { qsl("GBP"), QString::fromUtf8("\xC2\xA3") }, - { qsl("EUR"), QString::fromUtf8("\xE2\x82\xAC") }, - { qsl("JPY"), QString::fromUtf8("\xC2\xA5") }, - }; - static const auto Denominators = QMap<QString, int> { - { qsl("CLF"), 10000 }, - { qsl("BHD"), 1000 }, - { qsl("IQD"), 1000 }, - { qsl("JOD"), 1000 }, - { qsl("KWD"), 1000 }, - { qsl("LYD"), 1000 }, - { qsl("OMR"), 1000 }, - { qsl("TND"), 1000 }, - { qsl("BIF"), 1 }, - { qsl("BYR"), 1 }, - { qsl("CLP"), 1 }, - { qsl("CVE"), 1 }, - { qsl("DJF"), 1 }, - { qsl("GNF"), 1 }, - { qsl("ISK"), 1 }, - { qsl("JPY"), 1 }, - { qsl("KMF"), 1 }, - { qsl("KRW"), 1 }, - { qsl("MGA"), 1 }, - { qsl("PYG"), 1 }, - { qsl("RWF"), 1 }, - { qsl("UGX"), 1 }, - { qsl("UYI"), 1 }, - { qsl("VND"), 1 }, - { qsl("VUV"), 1 }, - { qsl("XAF"), 1 }, - { qsl("XOF"), 1 }, - { qsl("XPF"), 1 }, - { qsl("MRO"), 10 }, - }; - const auto currencyText = ShortCurrencyNames.value(currency, currency); - const auto denominator = Denominators.value(currency, 100); - const auto currencyValue = amount / float64(denominator); - const auto digits = [&] { - auto result = 0; - for (auto test = 1; test < denominator; test *= 10) { - ++result; - } - return result; - }(); - return QLocale::system().toCurrencyString(currencyValue, currencyText); - //auto amountBucks = amount / 100; - //auto amountCents = amount % 100; - //auto amountText = qsl("%1,%2").arg(amountBucks).arg(amountCents, 2, 10, QChar('0')); - //return currencyText + amountText; -} - -void HistoryFileMedia::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { - if (p == _savel || p == _cancell) { - if (active && !dataLoaded()) { - ensureAnimation(); - _animation->a_thumbOver.start([this] { thumbAnimationCallback(); }, 0., 1., st::msgFileOverDuration); - } else if (!active && _animation && !dataLoaded()) { - _animation->a_thumbOver.start([this] { thumbAnimationCallback(); }, 1., 0., st::msgFileOverDuration); - } - } -} - -void HistoryFileMedia::thumbAnimationCallback() { - Auth().data().requestViewRepaint(_parent); -} - -void HistoryFileMedia::clickHandlerPressedChanged( - const ClickHandlerPtr &handler, - bool pressed) { - Auth().data().requestViewRepaint(_parent); -} - -void HistoryFileMedia::setLinks( - FileClickHandlerPtr &&openl, - FileClickHandlerPtr &&savel, - FileClickHandlerPtr &&cancell) { - _openl = std::move(openl); - _savel = std::move(savel); - _cancell = std::move(cancell); -} - -void HistoryFileMedia::refreshParentId(not_null<HistoryItem*> realParent) { - const auto contextId = realParent->fullId(); - _openl->setMessageId(contextId); - _savel->setMessageId(contextId); - _cancell->setMessageId(contextId); -} - -void HistoryFileMedia::setStatusSize(int newSize, int fullSize, int duration, qint64 realDuration) const { - _statusSize = newSize; - if (_statusSize == FileStatusSizeReady) { - _statusText = (duration >= 0) ? formatDurationAndSizeText(duration, fullSize) : (duration < -1 ? formatGifAndSizeText(fullSize) : formatSizeText(fullSize)); - } else if (_statusSize == FileStatusSizeLoaded) { - _statusText = (duration >= 0) ? formatDurationText(duration) : (duration < -1 ? qsl("GIF") : formatSizeText(fullSize)); - } else if (_statusSize == FileStatusSizeFailed) { - _statusText = lang(lng_attach_failed); - } else if (_statusSize >= 0) { - _statusText = formatDownloadText(_statusSize, fullSize); - } else { - _statusText = formatPlayedText(-_statusSize - 1, realDuration); - } -} - -void HistoryFileMedia::step_radial(TimeMs ms, bool timer) { - const auto updateRadial = [&] { - return _animation->radial.update( - dataProgress(), - dataFinished(), - ms); - }; - if (timer) { - if (!anim::Disabled() || updateRadial()) { - Auth().data().requestViewRepaint(_parent); - } - } else { - updateRadial(); - if (!_animation->radial.animating()) { - checkAnimationFinished(); - } - } -} - -void HistoryFileMedia::ensureAnimation() const { - if (!_animation) { - _animation = std::make_unique<AnimationData>(animation(const_cast<HistoryFileMedia*>(this), &HistoryFileMedia::step_radial)); - } -} - -void HistoryFileMedia::checkAnimationFinished() const { - if (_animation && !_animation->a_thumbOver.animating() && !_animation->radial.animating()) { - if (dataLoaded()) { - _animation.reset(); - } - } -} -void HistoryFileMedia::setDocumentLinks( - not_null<DocumentData*> document, - not_null<HistoryItem*> realParent, - bool inlinegif) { - FileClickHandlerPtr open, save; - const auto context = realParent->fullId(); - if (inlinegif) { - open = std::make_shared<GifOpenClickHandler>(document, context); - } else { - open = std::make_shared<DocumentOpenClickHandler>(document, context); - } - if (inlinegif) { - save = std::make_shared<GifOpenClickHandler>(document, context); - } else if (document->isVoiceMessage()) { - save = std::make_shared<DocumentOpenClickHandler>(document, context); - } else { - save = std::make_shared<DocumentSaveClickHandler>(document, context); - } - setLinks( - std::move(open), - std::move(save), - std::make_shared<DocumentCancelClickHandler>(document, context)); -} - -HistoryFileMedia::~HistoryFileMedia() = default; - -HistoryPhoto::HistoryPhoto( - not_null<Element*> parent, - not_null<HistoryItem*> realParent, - not_null<PhotoData*> photo) -: HistoryFileMedia(parent, realParent) -, _data(photo) -, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { - const auto fullId = realParent->fullId(); - setLinks( - std::make_shared<PhotoOpenClickHandler>(_data, fullId), - std::make_shared<PhotoSaveClickHandler>(_data, fullId), - std::make_shared<PhotoCancelClickHandler>(_data, fullId)); - _caption = createCaption(realParent); - create(realParent->fullId()); -} - -HistoryPhoto::HistoryPhoto( - not_null<Element*> parent, - not_null<PeerData*> chat, - not_null<PhotoData*> photo, - int width) -: HistoryFileMedia(parent, parent->data()) -, _data(photo) -, _serviceWidth(width) { - create(parent->data()->fullId(), chat); -} - -void HistoryPhoto::create(FullMsgId contextId, PeerData *chat) { - setLinks( - std::make_shared<PhotoOpenClickHandler>(_data, contextId, chat), - std::make_shared<PhotoSaveClickHandler>(_data, contextId, chat), - std::make_shared<PhotoCancelClickHandler>(_data, contextId, chat)); - _data->thumb->load(contextId); -} - -QSize HistoryPhoto::countOptimalSize() { - if (_parent->media() != this) { - _caption = Text(); - } else if (_caption.hasSkipBlock()) { - _caption.updateSkipBlock( - _parent->skipBlockWidth(), - _parent->skipBlockHeight()); - } - - auto maxWidth = 0; - auto minHeight = 0; - - auto tw = ConvertScale(_data->full->width()); - auto th = ConvertScale(_data->full->height()); - if (!tw || !th) { - tw = th = 1; - } - if (tw > st::maxMediaSize) { - th = (st::maxMediaSize * th) / tw; - tw = st::maxMediaSize; - } - if (th > st::maxMediaSize) { - tw = (st::maxMediaSize * tw) / th; - th = st::maxMediaSize; - } - - if (_serviceWidth > 0) { - return { _serviceWidth, _serviceWidth }; - } - const auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); - const auto maxActualWidth = qMax(tw, minWidth); - maxWidth = qMax(maxActualWidth, th); - minHeight = qMax(th, st::minPhotoSize); - if (_parent->hasBubble() && !_caption.isEmpty()) { - auto captionw = maxActualWidth - st::msgPadding.left() - st::msgPadding.right(); - minHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); - if (isBubbleBottom()) { - minHeight += st::msgPadding.bottom(); - } - } - return { maxWidth, minHeight }; -} - -QSize HistoryPhoto::countCurrentSize(int newWidth) { - int tw = ConvertScale(_data->full->width()), th = ConvertScale(_data->full->height()); - if (tw > st::maxMediaSize) { - th = (st::maxMediaSize * th) / tw; - tw = st::maxMediaSize; - } - if (th > st::maxMediaSize) { - tw = (st::maxMediaSize * tw) / th; - th = st::maxMediaSize; - } - - _pixw = qMin(newWidth, maxWidth()); - _pixh = th; - if (tw > _pixw) { - _pixh = (_pixw * _pixh / tw); - } else { - _pixw = tw; - } - if (_pixh > newWidth) { - _pixw = (_pixw * newWidth) / _pixh; - _pixh = newWidth; - } - if (_pixw < 1) _pixw = 1; - if (_pixh < 1) _pixh = 1; - - auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); - newWidth = qMax(_pixw, minWidth); - auto newHeight = qMax(_pixh, st::minPhotoSize); - if (_parent->hasBubble() && !_caption.isEmpty()) { - const auto captionw = newWidth - - st::msgPadding.left() - - st::msgPadding.right(); - newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); - if (isBubbleBottom()) { - newHeight += st::msgPadding.bottom(); - } - } - return { newWidth, newHeight }; -} - -void HistoryPhoto::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; - - _data->automaticLoad(_realParent->fullId(), _parent->data()); - auto selected = (selection == FullSelection); - auto loaded = _data->loaded(); - auto displayLoading = _data->displayLoading(); - - auto inWebPage = (_parent->media() != this); - auto paintx = 0, painty = 0, paintw = width(), painth = height(); - auto bubble = _parent->hasBubble(); - - auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right(); - - if (displayLoading) { - ensureAnimation(); - if (!_animation->radial.animating()) { - _animation->radial.start(_data->progress()); - } - } - bool radial = isRadialAnimation(ms); - - auto rthumb = rtlrect(paintx, painty, paintw, painth, width()); - if (_serviceWidth > 0) { - const auto pix = loaded - ? _data->full->pixCircled(_realParent->fullId(), _pixw, _pixh) - : _data->thumb->pixBlurredCircled(_realParent->fullId(), _pixw, _pixh); - p.drawPixmap(rthumb.topLeft(), pix); - } else { - if (bubble) { - if (!_caption.isEmpty()) { - painth -= st::mediaCaptionSkip + _caption.countHeight(captionw); - if (isBubbleBottom()) { - painth -= st::msgPadding.bottom(); - } - rthumb = rtlrect(paintx, painty, paintw, painth, width()); - } - } else { - App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); - } - auto inWebPage = (_parent->media() != this); - auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large; - auto roundCorners = inWebPage ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None) - | ((isBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None)); - const auto pix = loaded - ? _data->full->pixSingle(_realParent->fullId(), _pixw, _pixh, paintw, painth, roundRadius, roundCorners) - : _data->thumb->pixBlurredSingle(_realParent->fullId(), _pixw, _pixh, paintw, painth, roundRadius, roundCorners); - p.drawPixmap(rthumb.topLeft(), pix); - if (selected) { - App::complexOverlayRect(p, rthumb, roundRadius, roundCorners); - } - } - if (radial || (!loaded && !_data->loading())) { - const auto radialOpacity = (radial && loaded && !_data->uploading()) - ? _animation->radial.opacity() : - 1.; - QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); - p.setPen(Qt::NoPen); - if (selected) { - p.setBrush(st::msgDateImgBgSelected); - } else if (isThumbAnimation(ms)) { - auto over = _animation->a_thumbOver.current(); - p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over)); - } else { - auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); - p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); - } - - p.setOpacity(radialOpacity * p.opacity()); - - { - PainterHighQualityEnabler hq(p); - p.drawEllipse(inner); - } - - p.setOpacity(radialOpacity); - auto icon = ([radial, this, selected]() -> const style::icon* { - if (radial || _data->loading()) { - if (_data->uploading() - || !_data->full->location().isNull()) { - return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); - } - return nullptr; - } - return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); - })(); - if (icon) { - icon->paintInCenter(p, inner); - } - p.setOpacity(1); - if (radial) { - QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); - _animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg); - } - } - - // date - if (!_caption.isEmpty()) { - auto outbg = _parent->hasOutLayout(); - p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg)); - _caption.draw(p, st::msgPadding.left(), painty + painth + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection); - } else if (!inWebPage) { - auto fullRight = paintx + paintw; - auto fullBottom = painty + painth; - if (needInfoDisplay()) { - _parent->drawInfo(p, fullRight, fullBottom, 2 * paintx + paintw, selected, InfoDisplayType::Image); - } - if (!bubble && _parent->displayRightAction()) { - auto fastShareLeft = (fullRight + st::historyFastShareLeft); - auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); - _parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * paintx + paintw); - } - } -} - -TextState HistoryPhoto::textState(QPoint point, StateRequest request) const { - auto result = TextState(_parent); - - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { - return result; - } - auto paintx = 0, painty = 0, paintw = width(), painth = height(); - auto bubble = _parent->hasBubble(); - - if (bubble && !_caption.isEmpty()) { - const auto captionw = paintw - - st::msgPadding.left() - - st::msgPadding.right(); - painth -= _caption.countHeight(captionw); - if (isBubbleBottom()) { - painth -= st::msgPadding.bottom(); - } - if (QRect(st::msgPadding.left(), painth, captionw, height() - painth).contains(point)) { - result = TextState(_parent, _caption.getState( - point - QPoint(st::msgPadding.left(), painth), - captionw, - request.forText())); - return result; - } - painth -= st::mediaCaptionSkip; - } - if (QRect(paintx, painty, paintw, painth).contains(point)) { - if (_data->uploading()) { - result.link = _cancell; - } else if (_data->loaded()) { - result.link = _openl; - } else if (_data->loading()) { - if (!_data->full->location().isNull()) { - result.link = _cancell; - } - } else { - result.link = _savel; - } - } - if (_caption.isEmpty() && _parent->media() == this) { - auto fullRight = paintx + paintw; - auto fullBottom = painty + painth; - if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) { - result.cursor = CursorState::Date; - } - if (!bubble && _parent->displayRightAction()) { - auto fastShareLeft = (fullRight + st::historyFastShareLeft); - auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); - if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) { - result.link = _parent->rightActionLink(); - } - } - } - return result; -} - -QSize HistoryPhoto::sizeForGrouping() const { - const auto width = _data->full->width(); - const auto height = _data->full->height(); - return { std::max(width, 1), std::max(height, 1) }; -} - -void HistoryPhoto::drawGrouped( - Painter &p, - const QRect &clip, - TextSelection selection, - TimeMs ms, - const QRect &geometry, - RectParts corners, - not_null<uint64*> cacheKey, - not_null<QPixmap*> cache) const { - _data->automaticLoad(_realParent->fullId(), _parent->data()); - - validateGroupedCache(geometry, corners, cacheKey, cache); - - const auto selected = (selection == FullSelection); - const auto loaded = _data->loaded(); - const auto displayLoading = _data->displayLoading(); - const auto bubble = _parent->hasBubble(); - - if (displayLoading) { - ensureAnimation(); - if (!_animation->radial.animating()) { - _animation->radial.start(_data->progress()); - } - } - const auto radial = isRadialAnimation(ms); - - if (!bubble) { -// App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); - } - p.drawPixmap(geometry.topLeft(), *cache); - if (selected) { - const auto roundRadius = ImageRoundRadius::Large; - App::complexOverlayRect(p, geometry, roundRadius, corners); - } - - const auto displayState = radial - || (!loaded && !_data->loading()) - || _data->waitingForAlbum(); - if (displayState) { - const auto radialOpacity = radial - ? _animation->radial.opacity() - : 1.; - const auto backOpacity = (loaded && !_data->uploading()) - ? radialOpacity - : 1.; - const auto radialSize = st::historyGroupRadialSize; - const auto inner = QRect( - geometry.x() + (geometry.width() - radialSize) / 2, - geometry.y() + (geometry.height() - radialSize) / 2, - radialSize, - radialSize); - p.setPen(Qt::NoPen); - if (selected) { - p.setBrush(st::msgDateImgBgSelected); - } else if (isThumbAnimation(ms)) { - auto over = _animation->a_thumbOver.current(); - p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over)); - } else { - auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); - p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); - } - - p.setOpacity(backOpacity * p.opacity()); - - { - PainterHighQualityEnabler hq(p); - p.drawEllipse(inner); - } - - const auto icon = [&]() -> const style::icon* { - if (_data->waitingForAlbum()) { - return &(selected ? st::historyFileThumbWaitingSelected : st::historyFileThumbWaiting); - } else if (radial || _data->loading()) { - if (_data->uploading() || !_data->full->location().isNull()) { - return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); - } - return nullptr; - } - return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); - }(); - const auto previous = [&]() -> const style::icon* { - if (_data->waitingForAlbum()) { - return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); - } - return nullptr; - }(); - p.setOpacity(backOpacity); - if (icon) { - if (previous && radialOpacity > 0. && radialOpacity < 1.) { - PaintInterpolatedIcon(p, *icon, *previous, radialOpacity, inner); - } else { - icon->paintInCenter(p, inner); - } - } - p.setOpacity(1); - if (radial) { - const auto line = st::historyGroupRadialLine; - const auto rinner = inner.marginsRemoved({ line, line, line, line }); - const auto color = selected - ? st::historyFileThumbRadialFgSelected - : st::historyFileThumbRadialFg; - _animation->radial.draw(p, rinner, line, color); - } - } -} - -TextState HistoryPhoto::getStateGrouped( - const QRect &geometry, - QPoint point, - StateRequest request) const { - if (!geometry.contains(point)) { - return {}; - } - return TextState(_parent, _data->uploading() - ? _cancell - : _data->loaded() - ? _openl - : _data->loading() - ? (_data->full->location().isNull() - ? ClickHandlerPtr() - : _cancell) - : _savel); -} - -float64 HistoryPhoto::dataProgress() const { - return _data->progress(); -} - -bool HistoryPhoto::dataFinished() const { - return !_data->loading() - && (!_data->uploading() || _data->waitingForAlbum()); -} - -bool HistoryPhoto::dataLoaded() const { - return _data->loaded(); -} - -bool HistoryPhoto::needInfoDisplay() const { - return (_parent->data()->id < 0 || _parent->isUnderCursor()); -} - -void HistoryPhoto::validateGroupedCache( - const QRect &geometry, - RectParts corners, - not_null<uint64*> cacheKey, - not_null<QPixmap*> cache) const { - using Option = Images::Option; - const auto loaded = _data->loaded(); - const auto loadLevel = loaded ? 2 : _data->thumb->loaded() ? 1 : 0; - const auto width = geometry.width(); - const auto height = geometry.height(); - const auto options = Option::Smooth - | Option::RoundedLarge - | (loaded ? Option::None : Option::Blurred) - | ((corners & RectPart::TopLeft) ? Option::RoundedTopLeft : Option::None) - | ((corners & RectPart::TopRight) ? Option::RoundedTopRight : Option::None) - | ((corners & RectPart::BottomLeft) ? Option::RoundedBottomLeft : Option::None) - | ((corners & RectPart::BottomRight) ? Option::RoundedBottomRight : Option::None); - const auto key = (uint64(width) << 48) - | (uint64(height) << 32) - | (uint64(options) << 16) - | (uint64(loadLevel)); - if (*cacheKey == key) { - return; - } - - const auto originalWidth = ConvertScale(_data->full->width()); - const auto originalHeight = ConvertScale(_data->full->height()); - const auto pixSize = Ui::GetImageScaleSizeForGeometry( - { originalWidth, originalHeight }, - { width, height }); - const auto pixWidth = pixSize.width() * cIntRetinaFactor(); - const auto pixHeight = pixSize.height() * cIntRetinaFactor(); - const auto &image = loaded ? _data->full : _data->thumb; - - *cacheKey = key; - *cache = image->pixNoCache(_realParent->fullId(), pixWidth, pixHeight, options, width, height); -} - -TextWithEntities HistoryPhoto::selectedText(TextSelection selection) const { - return _caption.originalTextWithEntities(selection, ExpandLinksAll); -} - -bool HistoryPhoto::needsBubble() const { - if (!_caption.isEmpty()) { - return true; - } - const auto item = _parent->data(); - if (item->toHistoryMessage()) { - return item->viaBot() - || item->Has<HistoryMessageReply>() - || _parent->displayForwardedFrom() - || _parent->displayFromName(); - } - return false; -} - -void HistoryPhoto::parentTextUpdated() { - _caption = (_parent->media() == this) - ? createCaption(_parent->data()) - : Text(); - Auth().data().requestViewResize(_parent); -} - -HistoryVideo::HistoryVideo( - not_null<Element*> parent, - not_null<HistoryItem*> realParent, - not_null<DocumentData*> document) -: HistoryFileMedia(parent, realParent) -, _data(document) -, _thumbw(1) -, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { - _caption = createCaption(realParent); - - setDocumentLinks(_data, realParent); - - setStatusSize(FileStatusSizeReady); - - _data->thumb->load(realParent->fullId()); -} - -QSize HistoryVideo::countOptimalSize() { - if (_parent->media() != this) { - _caption = Text(); - } else if (_caption.hasSkipBlock()) { - _caption.updateSkipBlock( - _parent->skipBlockWidth(), - _parent->skipBlockHeight()); - } - - auto tw = ConvertScale(_data->thumb->width()); - auto th = ConvertScale(_data->thumb->height()); - if (!tw || !th) { - tw = th = 1; - } - if (tw * st::msgVideoSize.height() > th * st::msgVideoSize.width()) { - th = qRound((st::msgVideoSize.width() / float64(tw)) * th); - tw = st::msgVideoSize.width(); - } else { - tw = qRound((st::msgVideoSize.height() / float64(th)) * tw); - th = st::msgVideoSize.height(); - } - - _thumbw = qMax(tw, 1); - _thumbh = qMax(th, 1); - - auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); - minWidth = qMax(minWidth, documentMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); - auto maxWidth = qMax(_thumbw, minWidth); - auto minHeight = qMax(th, st::minPhotoSize); - if (_parent->hasBubble() && !_caption.isEmpty()) { - const auto captionw = maxWidth - - st::msgPadding.left() - - st::msgPadding.right(); - minHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); - if (isBubbleBottom()) { - minHeight += st::msgPadding.bottom(); - } - } - return { maxWidth, minHeight }; -} - -QSize HistoryVideo::countCurrentSize(int newWidth) { - int tw = ConvertScale(_data->thumb->width()), th = ConvertScale(_data->thumb->height()); - if (!tw || !th) { - tw = th = 1; - } - if (tw * st::msgVideoSize.height() > th * st::msgVideoSize.width()) { - th = qRound((st::msgVideoSize.width() / float64(tw)) * th); - tw = st::msgVideoSize.width(); - } else { - tw = qRound((st::msgVideoSize.height() / float64(th)) * tw); - th = st::msgVideoSize.height(); - } - - if (newWidth < tw) { - th = qRound((newWidth / float64(tw)) * th); - tw = newWidth; - } - - _thumbw = qMax(tw, 1); - _thumbh = qMax(th, 1); - auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); - minWidth = qMax(minWidth, documentMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); - newWidth = qMax(_thumbw, minWidth); - auto newHeight = qMax(th, st::minPhotoSize); - if (_parent->hasBubble() && !_caption.isEmpty()) { - const auto captionw = newWidth - - st::msgPadding.left() - - st::msgPadding.right(); - newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); - if (isBubbleBottom()) { - newHeight += st::msgPadding.bottom(); - } - } - return { newWidth, newHeight }; -} - -void HistoryVideo::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; - - _data->automaticLoad(_realParent->fullId(), _parent->data()); - bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); - bool selected = (selection == FullSelection); - - auto paintx = 0, painty = 0, paintw = width(), painth = height(); - bool bubble = _parent->hasBubble(); - - int captionw = paintw - st::msgPadding.left() - st::msgPadding.right(); - - if (displayLoading) { - ensureAnimation(); - if (!_animation->radial.animating()) { - _animation->radial.start(_data->progress()); - } - } - updateStatusText(); - bool radial = isRadialAnimation(ms); - - if (bubble) { - if (!_caption.isEmpty()) { - painth -= st::mediaCaptionSkip + _caption.countHeight(captionw); - if (isBubbleBottom()) { - painth -= st::msgPadding.bottom(); - } - } - } else { - App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); - } - - auto inWebPage = (_parent->media() != this); - auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large; - auto roundCorners = inWebPage ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None) - | ((isBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None)); - QRect rthumb(rtlrect(paintx, painty, paintw, painth, width())); - - const auto good = _data->goodThumbnail(); - if (good && good->loaded()) { - p.drawPixmap(rthumb.topLeft(), good->pixSingle({}, _thumbw, _thumbh, paintw, painth, roundRadius, roundCorners)); - } else { - if (good) { - good->load({}); - } - p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(_realParent->fullId(), _thumbw, _thumbh, paintw, painth, roundRadius, roundCorners)); - } - if (selected) { - App::complexOverlayRect(p, rthumb, roundRadius, roundCorners); - } - - QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); - p.setPen(Qt::NoPen); - if (selected) { - p.setBrush(st::msgDateImgBgSelected); - } else if (isThumbAnimation(ms)) { - auto over = _animation->a_thumbOver.current(); - p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over)); - } else { - bool over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); - p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); - } - - { - PainterHighQualityEnabler hq(p); - p.drawEllipse(inner); - } - - if (!selected && _animation) { - p.setOpacity(1); - } - - auto icon = ([this, radial, selected, loaded]() -> const style::icon * { - if (loaded && !radial) { - return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay); - } else if (radial || _data->loading()) { - if (_parent->data()->id > 0 || _data->uploading()) { - return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); - } - return nullptr; - } - return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); - })(); - if (icon) { - icon->paintInCenter(p, inner); - } - if (radial) { - QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); - _animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg); - } - - auto statusX = paintx + st::msgDateImgDelta + st::msgDateImgPadding.x(), 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, rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); - p.setFont(st::normalFont); - p.setPen(st::msgDateImgFg); - p.drawTextLeft(statusX, statusY, width(), _statusText, statusW - 2 * st::msgDateImgPadding.x()); - - // date - if (!_caption.isEmpty()) { - auto outbg = _parent->hasOutLayout(); - p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg)); - _caption.draw(p, st::msgPadding.left(), painty + painth + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection); - } else if (_parent->media() == this) { - auto fullRight = paintx + paintw, fullBottom = painty + painth; - _parent->drawInfo(p, fullRight, fullBottom, 2 * paintx + paintw, selected, InfoDisplayType::Image); - if (!bubble && _parent->displayRightAction()) { - auto fastShareLeft = (fullRight + st::historyFastShareLeft); - auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); - _parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * paintx + paintw); - } - } -} - -TextState HistoryVideo::textState(QPoint point, StateRequest request) const { - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { - return {}; - } - - auto result = TextState(_parent); - bool loaded = _data->loaded(); - - auto paintx = 0, painty = 0, paintw = width(), painth = height(); - bool bubble = _parent->hasBubble(); - - if (bubble && !_caption.isEmpty()) { - const auto captionw = paintw - - st::msgPadding.left() - - st::msgPadding.right(); - painth -= _caption.countHeight(captionw); - if (isBubbleBottom()) { - painth -= st::msgPadding.bottom(); - } - if (QRect(st::msgPadding.left(), painth, captionw, height() - painth).contains(point)) { - result = TextState(_parent, _caption.getState( - point - QPoint(st::msgPadding.left(), painth), - captionw, - request.forText())); - } - painth -= st::mediaCaptionSkip; - } - if (QRect(paintx, painty, paintw, painth).contains(point)) { - if (_data->uploading()) { - result.link = _cancell; - } else { - result.link = loaded ? _openl : (_data->loading() ? _cancell : _savel); - } - } - if (_caption.isEmpty() && _parent->media() == this) { - auto fullRight = paintx + paintw; - auto fullBottom = painty + painth; - if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) { - result.cursor = CursorState::Date; - } - if (!bubble && _parent->displayRightAction()) { - auto fastShareLeft = (fullRight + st::historyFastShareLeft); - auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); - if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) { - result.link = _parent->rightActionLink(); - } - } - } - return result; -} - -QSize HistoryVideo::sizeForGrouping() const { - const auto width = _data->dimensions.isEmpty() - ? _data->thumb->width() - : _data->dimensions.width(); - const auto height = _data->dimensions.isEmpty() - ? _data->thumb->height() - : _data->dimensions.height(); - return { std::max(width, 1), std::max(height, 1) }; -} - -void HistoryVideo::drawGrouped( - Painter &p, - const QRect &clip, - TextSelection selection, - TimeMs ms, - const QRect &geometry, - RectParts corners, - not_null<uint64*> cacheKey, - not_null<QPixmap*> cache) const { - _data->automaticLoad(_realParent->fullId(), _parent->data()); - - validateGroupedCache(geometry, corners, cacheKey, cache); - - const auto selected = (selection == FullSelection); - const auto loaded = _data->loaded(); - const auto displayLoading = _data->displayLoading(); - const auto bubble = _parent->hasBubble(); - - if (displayLoading) { - ensureAnimation(); - if (!_animation->radial.animating()) { - _animation->radial.start(_data->progress()); - } - } - const auto radial = isRadialAnimation(ms); - - if (!bubble) { -// App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); - } - p.drawPixmap(geometry.topLeft(), *cache); - if (selected) { - const auto roundRadius = ImageRoundRadius::Large; - App::complexOverlayRect(p, geometry, roundRadius, corners); - } - - const auto radialOpacity = radial - ? _animation->radial.opacity() - : 1.; - const auto backOpacity = (loaded && !_data->uploading()) - ? radialOpacity - : 1.; - const auto radialSize = st::historyGroupRadialSize; - const auto inner = QRect( - geometry.x() + (geometry.width() - radialSize) / 2, - geometry.y() + (geometry.height() - radialSize) / 2, - radialSize, - radialSize); - p.setPen(Qt::NoPen); - if (selected) { - p.setBrush(st::msgDateImgBgSelected); - } else if (isThumbAnimation(ms)) { - auto over = _animation->a_thumbOver.current(); - p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over)); - } else { - auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); - p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); - } - - p.setOpacity(backOpacity * p.opacity()); - - { - PainterHighQualityEnabler hq(p); - p.drawEllipse(inner); - } - - auto icon = [&]() -> const style::icon * { - if (_data->waitingForAlbum()) { - return &(selected ? st::historyFileThumbWaitingSelected : st::historyFileThumbWaiting); - } else if (loaded && !radial) { - return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay); - } else if (radial || _data->loading()) { - if (_parent->data()->id > 0 || _data->uploading()) { - return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); - } - return nullptr; - } - return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); - }(); - const auto previous = [&]() -> const style::icon* { - if (_data->waitingForAlbum()) { - return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); - } - return nullptr; - }(); - p.setOpacity(backOpacity); - if (icon) { - if (previous && radialOpacity > 0. && radialOpacity < 1.) { - LOG(("INTERPOLATING: %1").arg(radialOpacity)); - PaintInterpolatedIcon(p, *icon, *previous, radialOpacity, inner); - } else { - icon->paintInCenter(p, inner); - } - } - p.setOpacity(1); - if (radial) { - const auto line = st::historyGroupRadialLine; - const auto rinner = inner.marginsRemoved({ line, line, line, line }); - const auto color = selected - ? st::historyFileThumbRadialFgSelected - : st::historyFileThumbRadialFg; - _animation->radial.draw(p, rinner, line, color); - } -} - -TextState HistoryVideo::getStateGrouped( - const QRect &geometry, - QPoint point, - StateRequest request) const { - if (!geometry.contains(point)) { - return {}; - } - return TextState(_parent, _data->uploading() - ? _cancell - : _data->loaded() - ? _openl - : _data->loading() - ? _cancell - : _savel); -} - -float64 HistoryVideo::dataProgress() const { - return _data->progress(); -} - -bool HistoryVideo::dataFinished() const { - return !_data->loading() - && (!_data->uploading() || _data->waitingForAlbum()); -} - -bool HistoryVideo::dataLoaded() const { - return _data->loaded(); -} - -void HistoryVideo::validateGroupedCache( - const QRect &geometry, - RectParts corners, - not_null<uint64*> cacheKey, - not_null<QPixmap*> cache) const { - using Option = Images::Option; - const auto good = _data->goodThumbnail(); - const auto useGood = (good && good->loaded()); - const auto image = useGood ? good : _data->thumb.get(); - if (good && !useGood) { - good->load({}); - } - - const auto loaded = useGood ? true : _data->thumb->loaded(); - const auto loadLevel = loaded ? 1 : 0; - const auto width = geometry.width(); - const auto height = geometry.height(); - const auto options = Option::Smooth - | Option::RoundedLarge - | (useGood ? Option(0) : Option::Blurred) - | ((corners & RectPart::TopLeft) ? Option::RoundedTopLeft : Option::None) - | ((corners & RectPart::TopRight) ? Option::RoundedTopRight : Option::None) - | ((corners & RectPart::BottomLeft) ? Option::RoundedBottomLeft : Option::None) - | ((corners & RectPart::BottomRight) ? Option::RoundedBottomRight : Option::None); - const auto key = (uint64(width) << 48) - | (uint64(height) << 32) - | (uint64(options) << 16) - | (uint64(loadLevel)); - if (*cacheKey == key) { - return; - } - - const auto originalWidth = ConvertScale(_data->thumb->width()); - const auto originalHeight = ConvertScale(_data->thumb->height()); - const auto pixSize = Ui::GetImageScaleSizeForGeometry( - { originalWidth, originalHeight }, - { width, height }); - const auto pixWidth = pixSize.width() * cIntRetinaFactor(); - const auto pixHeight = pixSize.height() * cIntRetinaFactor(); - - *cacheKey = key; - *cache = image->pixNoCache(_realParent->fullId(), pixWidth, pixHeight, options, width, height); -} - -void HistoryVideo::setStatusSize(int newSize) const { - HistoryFileMedia::setStatusSize(newSize, _data->size, _data->duration(), 0); -} - -TextWithEntities HistoryVideo::selectedText(TextSelection selection) const { - return _caption.originalTextWithEntities(selection, ExpandLinksAll); -} - -bool HistoryVideo::needsBubble() const { - if (!_caption.isEmpty()) { - return true; - } - const auto item = _parent->data(); - return item->viaBot() - || item->Has<HistoryMessageReply>() - || _parent->displayForwardedFrom() - || _parent->displayFromName(); - return false; -} - -void HistoryVideo::parentTextUpdated() { - _caption = (_parent->media() == this) - ? createCaption(_parent->data()) - : Text(); - Auth().data().requestViewResize(_parent); -} - -void HistoryVideo::updateStatusText() const { - auto showPause = false; - auto statusSize = 0; - auto realDuration = 0; - if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { - statusSize = FileStatusSizeFailed; - } else if (_data->uploading()) { - statusSize = _data->uploadingData->offset; - } else if (_data->loading()) { - statusSize = _data->loadOffset(); - } else if (_data->loaded()) { - statusSize = FileStatusSizeLoaded; - } else { - statusSize = FileStatusSizeReady; - } - if (statusSize != _statusSize) { - setStatusSize(statusSize); - } -} - -HistoryDocument::HistoryDocument( - not_null<Element*> parent, - not_null<DocumentData*> document) -: HistoryFileMedia(parent, parent->data()) -, _data(document) { - const auto item = parent->data(); - auto caption = createCaption(item); - - createComponents(!caption.isEmpty()); - if (auto named = Get<HistoryDocumentNamed>()) { - fillNamedFromData(named); - } - - setDocumentLinks(_data, item); - - setStatusSize(FileStatusSizeReady); - - if (auto captioned = Get<HistoryDocumentCaptioned>()) { - captioned->_caption = std::move(caption); - } -} - -float64 HistoryDocument::dataProgress() const { - return _data->progress(); -} - -bool HistoryDocument::dataFinished() const { - return !_data->loading() && !_data->uploading(); -} - -bool HistoryDocument::dataLoaded() const { - return _data->loaded(); -} - -void HistoryDocument::createComponents(bool caption) { - uint64 mask = 0; - if (_data->isVoiceMessage()) { - mask |= HistoryDocumentVoice::Bit(); - } else { - mask |= HistoryDocumentNamed::Bit(); - if (!_data->isSong() - && !_data->thumb->isNull() - && _data->thumb->width() - && _data->thumb->height() - && !Data::IsExecutableName(_data->filename())) { - mask |= HistoryDocumentThumbed::Bit(); - } - } - if (caption) { - mask |= HistoryDocumentCaptioned::Bit(); - } - UpdateComponents(mask); - if (auto thumbed = Get<HistoryDocumentThumbed>()) { - thumbed->_linksavel = std::make_shared<DocumentSaveClickHandler>( - _data, - _parent->data()->fullId()); - thumbed->_linkcancell = std::make_shared<DocumentCancelClickHandler>( - _data, - _parent->data()->fullId()); - } - if (auto voice = Get<HistoryDocumentVoice>()) { - voice->_seekl = std::make_shared<VoiceSeekClickHandler>( - _data, - _parent->data()->fullId()); - } -} - -void HistoryDocument::fillNamedFromData(HistoryDocumentNamed *named) { - auto nameString = named->_name = _data->composeNameString(); - named->_namew = st::semiboldFont->width(nameString); -} - -QSize HistoryDocument::countOptimalSize() { - const auto item = _parent->data(); - - auto captioned = Get<HistoryDocumentCaptioned>(); - if (_parent->media() != this) { - if (captioned) { - RemoveComponents(HistoryDocumentCaptioned::Bit()); - captioned = nullptr; - } - } else if (captioned && captioned->_caption.hasSkipBlock()) { - captioned->_caption.updateSkipBlock( - _parent->skipBlockWidth(), - _parent->skipBlockHeight()); - } - auto thumbed = Get<HistoryDocumentThumbed>(); - if (thumbed) { - _data->thumb->load(_realParent->fullId()); - auto tw = ConvertScale(_data->thumb->width()); - auto th = ConvertScale(_data->thumb->height()); - if (tw > th) { - thumbed->_thumbw = (tw * st::msgFileThumbSize) / th; - } else { - thumbed->_thumbw = st::msgFileThumbSize; - } - } - - auto maxWidth = st::msgFileMinWidth; - - auto tleft = 0; - auto tright = 0; - if (thumbed) { - tleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); - tright = st::msgFileThumbPadding.left(); - accumulate_max(maxWidth, tleft + documentMaxStatusWidth(_data) + tright); - } else { - tleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); - tright = st::msgFileThumbPadding.left(); - auto unread = _data->isVoiceMessage() ? (st::mediaUnreadSkip + st::mediaUnreadSize) : 0; - accumulate_max(maxWidth, tleft + documentMaxStatusWidth(_data) + unread + _parent->skipBlockWidth() + st::msgPadding.right()); - } - - if (auto named = Get<HistoryDocumentNamed>()) { - accumulate_max(maxWidth, tleft + named->_namew + tright); - accumulate_min(maxWidth, st::msgMaxWidth); - } - - auto minHeight = 0; - if (thumbed) { - minHeight = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom(); - } else { - minHeight = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom(); - } - if (!captioned && (item->Has<HistoryMessageSigned>() - || item->Has<HistoryMessageViews>() - || _parent->displayEditedBadge())) { - minHeight += st::msgDateFont->height - st::msgDateDelta.y(); - } - if (!isBubbleTop()) { - minHeight -= st::msgFileTopMinus; - } - - if (captioned) { - auto captionw = maxWidth - - st::msgPadding.left() - - st::msgPadding.right(); - minHeight += captioned->_caption.countHeight(captionw); - if (isBubbleBottom()) { - minHeight += st::msgPadding.bottom(); - } - } - return { maxWidth, minHeight }; -} - -QSize HistoryDocument::countCurrentSize(int newWidth) { - auto captioned = Get<HistoryDocumentCaptioned>(); - if (!captioned) { - return HistoryFileMedia::countCurrentSize(newWidth); - } - - accumulate_min(newWidth, maxWidth()); - auto newHeight = 0; - if (Get<HistoryDocumentThumbed>()) { - newHeight = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom(); - } else { - newHeight = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom(); - } - if (!isBubbleTop()) { - newHeight -= st::msgFileTopMinus; - } - auto captionw = newWidth - st::msgPadding.left() - st::msgPadding.right(); - newHeight += captioned->_caption.countHeight(captionw); - if (isBubbleBottom()) { - newHeight += st::msgPadding.bottom(); - } - - return { newWidth, newHeight }; -} - -void HistoryDocument::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; - - _data->automaticLoad(_realParent->fullId(), _parent->data()); - bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); - bool selected = (selection == FullSelection); - - int captionw = width() - st::msgPadding.left() - st::msgPadding.right(); - auto outbg = _parent->hasOutLayout(); - - if (displayLoading) { - ensureAnimation(); - if (!_animation->radial.animating()) { - _animation->radial.start(_data->progress()); - } - } - bool showPause = updateStatusText(); - bool radial = isRadialAnimation(ms); - - auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus; - int nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0, bottom = 0; - if (auto thumbed = Get<HistoryDocumentThumbed>()) { - nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); - nametop = st::msgFileThumbNameTop - topMinus; - nameright = st::msgFileThumbPadding.left(); - statustop = st::msgFileThumbStatusTop - topMinus; - linktop = st::msgFileThumbLinkTop - topMinus; - bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom() - topMinus; - - auto inWebPage = (_parent->media() != this); - auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large; - QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, width())); - QPixmap thumb; - if (loaded) { - thumb = _data->thumb->pixSingle(_realParent->fullId(), thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize, roundRadius); - } else { - thumb = _data->thumb->pixBlurredSingle(_realParent->fullId(), thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize, roundRadius); - } - p.drawPixmap(rthumb.topLeft(), thumb); - if (selected) { - auto overlayCorners = inWebPage ? SelectedOverlaySmallCorners : SelectedOverlayLargeCorners; - App::roundRect(p, rthumb, p.textPalette().selectOverlay, overlayCorners); - } - - if (radial || (!loaded && !_data->loading())) { - float64 radialOpacity = (radial && loaded && !_data->uploading()) ? _animation->radial.opacity() : 1; - QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); - p.setPen(Qt::NoPen); - if (selected) { - p.setBrush(st::msgDateImgBgSelected); - } else if (isThumbAnimation(ms)) { - auto over = _animation->a_thumbOver.current(); - p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over)); - } else { - auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); - p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); - } - p.setOpacity(radialOpacity * p.opacity()); - - { - PainterHighQualityEnabler hq(p); - p.drawEllipse(inner); - } - - p.setOpacity(radialOpacity); - auto icon = ([radial, this, selected] { - if (radial || _data->loading()) { - return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); - } - return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); - })(); - p.setOpacity((radial && loaded) ? _animation->radial.opacity() : 1); - icon->paintInCenter(p, inner); - if (radial) { - p.setOpacity(1); - - QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); - _animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg); - } - } - - if (_data->status != FileUploadFailed) { - const auto &lnk = (_data->loading() || _data->uploading()) - ? thumbed->_linkcancell - : thumbed->_linksavel; - 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); - } - } else { - nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); - nametop = st::msgFileNameTop - topMinus; - nameright = st::msgFilePadding.left(); - statustop = st::msgFileStatusTop - topMinus; - bottom = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() - topMinus; - - QRect inner(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); - } else if (isThumbAnimation(ms)) { - auto over = _animation->a_thumbOver.current(); - p.setBrush(anim::brush(outbg ? st::msgFileOutBg : st::msgFileInBg, outbg ? st::msgFileOutBgOver : st::msgFileInBgOver, over)); - } else { - auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); - p.setBrush(outbg ? (over ? st::msgFileOutBgOver : st::msgFileOutBg) : (over ? st::msgFileInBgOver : st::msgFileInBg)); - } - - { - PainterHighQualityEnabler hq(p); - p.drawEllipse(inner); - } - - if (radial) { - QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); - auto fg = outbg ? (selected ? st::historyFileOutRadialFgSelected : st::historyFileOutRadialFg) : (selected ? st::historyFileInRadialFgSelected : st::historyFileInRadialFg); - _animation->radial.draw(p, rinner, st::msgFileRadialLine, fg); - } - - auto icon = ([showPause, radial, this, loaded, outbg, selected] { - if (showPause) { - return &(outbg ? (selected ? st::historyFileOutPauseSelected : st::historyFileOutPause) : (selected ? st::historyFileInPauseSelected : st::historyFileInPause)); - } else if (radial || _data->loading()) { - return &(outbg ? (selected ? st::historyFileOutCancelSelected : st::historyFileOutCancel) : (selected ? st::historyFileInCancelSelected : st::historyFileInCancel)); - } else if (loaded) { - if (_data->isAudioFile() || _data->isVoiceMessage()) { - return &(outbg ? (selected ? st::historyFileOutPlaySelected : st::historyFileOutPlay) : (selected ? st::historyFileInPlaySelected : st::historyFileInPlay)); - } else if (_data->isImage()) { - return &(outbg ? (selected ? st::historyFileOutImageSelected : st::historyFileOutImage) : (selected ? st::historyFileInImageSelected : st::historyFileInImage)); - } - return &(outbg ? (selected ? st::historyFileOutDocumentSelected : st::historyFileOutDocument) : (selected ? st::historyFileInDocumentSelected : st::historyFileInDocument)); - } - return &(outbg ? (selected ? st::historyFileOutDownloadSelected : st::historyFileOutDownload) : (selected ? st::historyFileInDownloadSelected : st::historyFileInDownload)); - })(); - icon->paintInCenter(p, inner); - } - auto namewidth = width() - nameleft - nameright; - auto statuswidth = namewidth; - - auto voiceStatusOverride = QString(); - if (auto voice = Get<HistoryDocumentVoice>()) { - const VoiceWaveform *wf = nullptr; - uchar norm_value = 0; - if (const auto voiceData = _data->voice()) { - wf = &voiceData->waveform; - if (wf->isEmpty()) { - wf = nullptr; - if (loaded) { - Local::countVoiceWaveform(_data); - } - } else if (wf->at(0) < 0) { - wf = nullptr; - } else { - norm_value = voiceData->wavemax; - } - } - auto progress = ([voice] { - if (voice->seeking()) { - return voice->seekingCurrent(); - } else if (voice->_playback) { - return voice->_playback->a_progress.current(); - } - return 0.; - })(); - if (voice->seeking()) { - voiceStatusOverride = formatPlayedText(qRound(progress * voice->_lastDurationMs) / 1000, voice->_lastDurationMs / 1000); - } - - // rescale waveform by going in waveform.size * bar_count 1D grid - auto active = outbg ? (selected ? st::msgWaveformOutActiveSelected : st::msgWaveformOutActive) : (selected ? st::msgWaveformInActiveSelected : st::msgWaveformInActive); - auto inactive = outbg ? (selected ? st::msgWaveformOutInactiveSelected : st::msgWaveformOutInactive) : (selected ? st::msgWaveformInInactiveSelected : st::msgWaveformInInactive); - auto wf_size = wf ? wf->size() : Media::Player::kWaveformSamplesCount; - auto availw = namewidth + st::msgWaveformSkip; - auto activew = qRound(availw * progress); - if (!outbg && !voice->_playback && _parent->data()->isMediaUnread()) { - activew = availw; - } - auto bar_count = qMin(availw / (st::msgWaveformBar + st::msgWaveformSkip), wf_size); - auto max_value = 0; - auto max_delta = st::msgWaveformMax - st::msgWaveformMin; - auto bottom = st::msgFilePadding.top() - topMinus + st::msgWaveformMax; - p.setPen(Qt::NoPen); - for (auto i = 0, bar_x = 0, sum_i = 0; i < wf_size; ++i) { - auto value = wf ? wf->at(i) : 0; - if (sum_i + bar_count >= wf_size) { // draw bar - sum_i = sum_i + bar_count - wf_size; - if (sum_i < (bar_count + 1) / 2) { - if (max_value < value) max_value = value; - } - auto bar_value = ((max_value * max_delta) + ((norm_value + 1) / 2)) / (norm_value + 1); - - if (bar_x >= activew) { - p.fillRect(nameleft + bar_x, bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, inactive); - } else if (bar_x + st::msgWaveformBar <= activew) { - p.fillRect(nameleft + bar_x, bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, active); - } else { - p.fillRect(nameleft + bar_x, bottom - bar_value, activew - bar_x, st::msgWaveformMin + bar_value, active); - p.fillRect(nameleft + activew, bottom - bar_value, st::msgWaveformBar - (activew - bar_x), st::msgWaveformMin + bar_value, inactive); - } - bar_x += st::msgWaveformBar + st::msgWaveformSkip; - - if (sum_i < (bar_count + 1) / 2) { - max_value = 0; - } else { - max_value = value; - } - } else { - if (max_value < value) max_value = value; - - sum_i += bar_count; - } - } - } else if (auto named = Get<HistoryDocumentNamed>()) { - 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)); - } else { - p.drawTextLeft(nameleft, nametop, width(), named->_name, named->_namew); - } - } - - auto statusText = voiceStatusOverride.isEmpty() ? _statusText : voiceStatusOverride; - 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); - - if (_parent->data()->isMediaUnread()) { - auto w = st::normalFont->width(statusText); - if (w + st::mediaUnreadSkip + st::mediaUnreadSize <= statuswidth) { - p.setPen(Qt::NoPen); - p.setBrush(outbg ? (selected ? st::msgFileOutBgSelected : st::msgFileOutBg) : (selected ? st::msgFileInBgSelected : st::msgFileInBg)); - - { - PainterHighQualityEnabler hq(p); - p.drawEllipse(rtlrect(nameleft + w + st::mediaUnreadSkip, statustop + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, width())); - } - } - } - - if (auto captioned = Get<HistoryDocumentCaptioned>()) { - 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); - } -} - -TextState HistoryDocument::textState(QPoint point, StateRequest request) const { - auto result = TextState(_parent); - - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { - return result; - } - - bool loaded = _data->loaded(); - - bool showPause = updateStatusText(); - - auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0, bottom = 0; - auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus; - if (auto thumbed = Get<HistoryDocumentThumbed>()) { - nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); - nameright = st::msgFileThumbPadding.left(); - nametop = st::msgFileThumbNameTop - topMinus; - linktop = st::msgFileThumbLinkTop - topMinus; - bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom() - topMinus; - - QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, width())); - - if ((_data->loading() || _data->uploading() || !loaded) && rthumb.contains(point)) { - result.link = (_data->loading() || _data->uploading()) ? _cancell : _savel; - return result; - } - - if (_data->status != FileUploadFailed) { - if (rtlrect(nameleft, linktop, thumbed->_linkw, st::semiboldFont->height, width()).contains(point)) { - result.link = (_data->loading() || _data->uploading()) - ? thumbed->_linkcancell - : thumbed->_linksavel; - return result; - } - } - } else { - nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); - nameright = st::msgFilePadding.left(); - nametop = st::msgFileNameTop - topMinus; - bottom = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() - topMinus; - - QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, st::msgFileSize, st::msgFileSize, width())); - if ((_data->loading() || _data->uploading() || !loaded) && inner.contains(point)) { - result.link = (_data->loading() || _data->uploading()) ? _cancell : _savel; - return result; - } - } - - if (auto voice = Get<HistoryDocumentVoice>()) { - auto namewidth = width() - nameleft - nameright; - auto waveformbottom = st::msgFilePadding.top() - topMinus + st::msgWaveformMax + st::msgWaveformMin; - if (QRect(nameleft, nametop, namewidth, waveformbottom - nametop).contains(point)) { - auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); - if (state.id == AudioMsgId(_data, _parent->data()->fullId()) - && !Media::Player::IsStoppedOrStopping(state.state)) { - if (!voice->seeking()) { - voice->setSeekingStart((point.x() - nameleft) / float64(namewidth)); - } - result.link = voice->_seekl; - return result; - } - } - } - - auto painth = height(); - if (auto captioned = Get<HistoryDocumentCaptioned>()) { - 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) && !_data->loading() && !_data->uploading() && _data->isValid()) { - result.link = _openl; - return result; - } - return result; -} - -void HistoryDocument::updatePressed(QPoint point) { - if (auto voice = Get<HistoryDocumentVoice>()) { - if (voice->seeking()) { - auto nameleft = 0, nameright = 0; - if (auto thumbed = Get<HistoryDocumentThumbed>()) { - nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); - nameright = st::msgFileThumbPadding.left(); - } else { - nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); - nameright = st::msgFilePadding.left(); - } - voice->setSeekingCurrent(snap((point.x() - nameleft) / float64(width() - nameleft - nameright), 0., 1.)); - Auth().data().requestViewRepaint(_parent); - } - } -} - -TextSelection HistoryDocument::adjustSelection( - TextSelection selection, - TextSelectType type) const { - if (const auto captioned = Get<HistoryDocumentCaptioned>()) { - return captioned->_caption.adjustSelection(selection, type); - } - return selection; -} - -uint16 HistoryDocument::fullSelectionLength() const { - if (const auto captioned = Get<HistoryDocumentCaptioned>()) { - return captioned->_caption.length(); - } - return 0; -} - -bool HistoryDocument::hasTextForCopy() const { - return Has<HistoryDocumentCaptioned>(); -} - -TextWithEntities HistoryDocument::selectedText(TextSelection selection) const { - if (const auto captioned = Get<HistoryDocumentCaptioned>()) { - const auto &caption = captioned->_caption; - return caption.originalTextWithEntities(selection, ExpandLinksAll); - } - return TextWithEntities(); -} - -void HistoryDocument::setStatusSize(int newSize, qint64 realDuration) const { - auto duration = _data->isSong() - ? _data->song()->duration - : (_data->isVoiceMessage() - ? _data->voice()->duration - : -1); - HistoryFileMedia::setStatusSize(newSize, _data->size, duration, realDuration); - if (auto thumbed = Get<HistoryDocumentThumbed>()) { - if (_statusSize == FileStatusSizeReady) { - thumbed->_link = lang(lng_media_download).toUpper(); - } else if (_statusSize == FileStatusSizeLoaded) { - thumbed->_link = lang(lng_media_open_with).toUpper(); - } else if (_statusSize == FileStatusSizeFailed) { - thumbed->_link = lang(lng_media_download).toUpper(); - } else if (_statusSize >= 0) { - thumbed->_link = lang(lng_media_cancel).toUpper(); - } else { - thumbed->_link = lang(lng_media_open_with).toUpper(); - } - thumbed->_linkw = st::semiboldFont->width(thumbed->_link); - } -} - -bool HistoryDocument::updateStatusText() const { - auto showPause = false; - auto statusSize = 0; - auto realDuration = 0; - if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { - statusSize = FileStatusSizeFailed; - } else if (_data->uploading()) { - statusSize = _data->uploadingData->offset; - } else if (_data->loading()) { - statusSize = _data->loadOffset(); - } else if (_data->loaded()) { - using State = Media::Player::State; - statusSize = FileStatusSizeLoaded; - if (_data->isVoiceMessage()) { - auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); - if (state.id == AudioMsgId(_data, _parent->data()->fullId()) - && !Media::Player::IsStoppedOrStopping(state.state)) { - if (auto voice = Get<HistoryDocumentVoice>()) { - bool was = (voice->_playback != nullptr); - voice->ensurePlayback(this); - if (!was || state.position != voice->_playback->_position) { - auto prg = state.length ? snap(float64(state.position) / state.length, 0., 1.) : 0.; - if (voice->_playback->_position < state.position) { - voice->_playback->a_progress.start(prg); - } else { - voice->_playback->a_progress = anim::value(0., prg); - } - voice->_playback->_position = state.position; - voice->_playback->_a_progress.start(); - } - voice->_lastDurationMs = static_cast<int>((state.length * 1000LL) / state.frequency); // Bad :( - } - - statusSize = -1 - (state.position / state.frequency); - realDuration = (state.length / state.frequency); - showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); - } else { - if (auto voice = Get<HistoryDocumentVoice>()) { - voice->checkPlaybackFinished(); - } - } - if (!showPause && (state.id == AudioMsgId(_data, _parent->data()->fullId()))) { - showPause = Media::Player::instance()->isSeeking(AudioMsgId::Type::Voice); - } - } else if (_data->isAudioFile()) { - auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song); - if (state.id == AudioMsgId(_data, _parent->data()->fullId()) - && !Media::Player::IsStoppedOrStopping(state.state)) { - statusSize = -1 - (state.position / state.frequency); - realDuration = (state.length / state.frequency); - showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); - } else { - } - if (!showPause && (state.id == AudioMsgId(_data, _parent->data()->fullId()))) { - showPause = Media::Player::instance()->isSeeking(AudioMsgId::Type::Song); - } - } - } else { - statusSize = FileStatusSizeReady; - } - if (statusSize != _statusSize) { - setStatusSize(statusSize, realDuration); - } - return showPause; -} - -QMargins HistoryDocument::bubbleMargins() const { - return Get<HistoryDocumentThumbed>() ? QMargins(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbPadding.left(), st::msgFileThumbPadding.bottom()) : st::msgPadding; -} - -void HistoryDocument::step_voiceProgress(float64 ms, bool timer) { - if (anim::Disabled()) { - ms += (2 * AudioVoiceMsgUpdateView); - } - if (auto voice = Get<HistoryDocumentVoice>()) { - if (voice->_playback) { - float64 dt = ms / (2 * AudioVoiceMsgUpdateView); - if (dt >= 1) { - voice->_playback->_a_progress.stop(); - voice->_playback->a_progress.finish(); - } else { - voice->_playback->a_progress.update(qMin(dt, 1.), anim::linear); - } - if (timer) { - Auth().data().requestViewRepaint(_parent); - } - } - } -} - -void HistoryDocument::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { - if (auto voice = Get<HistoryDocumentVoice>()) { - if (pressed && p == voice->_seekl && !voice->seeking()) { - voice->startSeeking(); - } else if (!pressed && voice->seeking()) { - auto type = AudioMsgId::Type::Voice; - auto state = Media::Player::mixer()->currentState(type); - if (state.id == AudioMsgId(_data, _parent->data()->fullId()) && state.length) { - auto currentProgress = voice->seekingCurrent(); - auto currentPosition = state.frequency - ? qRound(currentProgress * state.length * 1000. / state.frequency) - : 0; - Media::Player::mixer()->seek(type, currentPosition); - - voice->ensurePlayback(this); - voice->_playback->_position = 0; - voice->_playback->a_progress = anim::value(currentProgress, currentProgress); - } - voice->stopSeeking(); - } - } - HistoryFileMedia::clickHandlerPressedChanged(p, pressed); -} - -void HistoryDocument::refreshParentId(not_null<HistoryItem*> realParent) { - HistoryFileMedia::refreshParentId(realParent); - - const auto fullId = realParent->fullId(); - if (auto thumbed = Get<HistoryDocumentThumbed>()) { - if (thumbed->_linksavel) { - thumbed->_linksavel->setMessageId(fullId); - thumbed->_linkcancell->setMessageId(fullId); - } - } - if (auto voice = Get<HistoryDocumentVoice>()) { - if (voice->_seekl) { - voice->_seekl->setMessageId(fullId); - } - } -} - -void HistoryDocument::parentTextUpdated() { - auto caption = (_parent->media() == this) - ? createCaption(_parent->data()) - : Text(); - if (!caption.isEmpty()) { - AddComponents(HistoryDocumentCaptioned::Bit()); - auto captioned = Get<HistoryDocumentCaptioned>(); - captioned->_caption = std::move(caption); - } else { - RemoveComponents(HistoryDocumentCaptioned::Bit()); - } - Auth().data().requestViewResize(_parent); -} - -TextWithEntities HistoryDocument::getCaption() const { - if (const auto captioned = Get<HistoryDocumentCaptioned>()) { - return captioned->_caption.originalTextWithEntities(); - } - return TextWithEntities(); -} - -HistoryGif::HistoryGif( - not_null<Element*> parent, - not_null<DocumentData*> document) -: HistoryFileMedia(parent, parent->data()) -, _data(document) -, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { - const auto item = parent->data(); - setDocumentLinks(_data, item, true); - - setStatusSize(FileStatusSizeReady); - - _caption = createCaption(item); - _data->thumb->load(item->fullId()); -} - -QSize HistoryGif::countOptimalSize() { - if (_parent->media() != this) { - _caption = Text(); - } else if (_caption.hasSkipBlock()) { - _caption.updateSkipBlock( - _parent->skipBlockWidth(), - _parent->skipBlockHeight()); - } - if (!_openInMediaviewLink) { - _openInMediaviewLink = std::make_shared<DocumentOpenClickHandler>( - _data, - _parent->data()->fullId()); - } - - auto tw = 0; - auto th = 0; - if (_gif && _gif->state() == Media::Clip::State::Error) { - if (!_gif->autoplay()) { - Ui::show(Box<InformBox>(lang(lng_gif_error))); - } - setClipReader(Media::Clip::ReaderPointer::Bad()); - } - - const auto reader = currentReader(); - if (reader) { - tw = ConvertScale(reader->width()); - th = ConvertScale(reader->height()); - } else { - tw = ConvertScale(_data->dimensions.width()), th = ConvertScale(_data->dimensions.height()); - if (!tw || !th) { - tw = ConvertScale(_data->thumb->width()); - th = ConvertScale(_data->thumb->height()); - } - } - const auto maxSize = _data->isVideoMessage() - ? st::maxVideoMessageSize - : st::maxGifSize; - if (tw > maxSize) { - th = (maxSize * th) / tw; - tw = maxSize; - } - if (th > maxSize) { - tw = (maxSize * tw) / th; - th = maxSize; - } - if (!tw || !th) { - tw = th = 1; - } - _thumbw = tw; - _thumbh = th; - auto maxWidth = qMax(tw, st::minPhotoSize); - auto minHeight = qMax(th, st::minPhotoSize); - accumulate_max(maxWidth, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); - if (!reader) { - accumulate_max(maxWidth, gifMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); - } - if (_parent->hasBubble()) { - if (!_caption.isEmpty()) { - auto captionw = maxWidth - st::msgPadding.left() - st::msgPadding.right(); - minHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); - if (isBubbleBottom()) { - minHeight += st::msgPadding.bottom(); - } - } - } else if (isSeparateRoundVideo()) { - const auto item = _parent->data(); - auto via = item->Get<HistoryMessageVia>(); - auto reply = item->Get<HistoryMessageReply>(); - auto forwarded = item->Get<HistoryMessageForwarded>(); - if (forwarded) { - forwarded->create(via); - } - maxWidth += additionalWidth(via, reply, forwarded); - } - return { maxWidth, minHeight }; -} - -QSize HistoryGif::countCurrentSize(int newWidth) { - auto availableWidth = newWidth; - - int tw = 0, th = 0; - const auto reader = currentReader(); - if (reader) { - tw = ConvertScale(reader->width()); - th = ConvertScale(reader->height()); - } else { - tw = ConvertScale(_data->dimensions.width()), th = ConvertScale(_data->dimensions.height()); - if (!tw || !th) { - tw = ConvertScale(_data->thumb->width()); - th = ConvertScale(_data->thumb->height()); - } - } - const auto maxSize = _data->isVideoMessage() - ? st::maxVideoMessageSize - : st::maxGifSize; - if (tw > maxSize) { - th = (maxSize * th) / tw; - tw = maxSize; - } - if (th > maxSize) { - tw = (maxSize * tw) / th; - th = maxSize; - } - if (!tw || !th) { - tw = th = 1; - } - - if (newWidth < tw) { - th = qRound((newWidth / float64(tw)) * th); - tw = newWidth; - } - _thumbw = tw; - _thumbh = th; - - newWidth = qMax(tw, st::minPhotoSize); - auto newHeight = qMax(th, st::minPhotoSize); - accumulate_max(newWidth, _parent->infoWidth() + 2 * st::msgDateImgDelta + st::msgDateImgPadding.x()); - if (reader) { - const auto own = (reader->mode() == Media::Clip::Reader::Mode::Gif); - if (own && !reader->started()) { - auto isRound = _data->isVideoMessage(); - auto inWebPage = (_parent->media() != this); - auto roundRadius = isRound - ? ImageRoundRadius::Ellipse - : inWebPage - ? ImageRoundRadius::Small - : ImageRoundRadius::Large; - auto roundCorners = (isRound || inWebPage) - ? RectPart::AllCorners - : ((isBubbleTop() - ? (RectPart::TopLeft | RectPart::TopRight) - : RectPart::None) - | ((isBubbleBottom() && _caption.isEmpty()) - ? (RectPart::BottomLeft | RectPart::BottomRight) - : RectPart::None)); - reader->start( - _thumbw, - _thumbh, - newWidth, - newHeight, - roundRadius, - roundCorners); - } - } else { - accumulate_max(newWidth, gifMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); - } - if (_parent->hasBubble()) { - if (!_caption.isEmpty()) { - auto captionw = newWidth - st::msgPadding.left() - st::msgPadding.right(); - newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); - if (isBubbleBottom()) { - newHeight += st::msgPadding.bottom(); - } - } - } else if (isSeparateRoundVideo()) { - const auto item = _parent->data(); - auto via = item->Get<HistoryMessageVia>(); - auto reply = item->Get<HistoryMessageReply>(); - auto forwarded = item->Get<HistoryMessageForwarded>(); - if (via || reply || forwarded) { - auto additional = additionalWidth(via, reply, forwarded); - newWidth += additional; - accumulate_min(newWidth, availableWidth); - auto usew = maxWidth() - additional; - auto availw = newWidth - usew - st::msgReplyPadding.left() - st::msgReplyPadding.left() - st::msgReplyPadding.left(); - if (!forwarded && via) { - via->resize(availw); - } - if (reply) { - reply->resize(availw); - } - } - } - - return { newWidth, newHeight }; -} - -void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; - - const auto item = _parent->data(); - _data->automaticLoad(_realParent->fullId(), item); - auto loaded = _data->loaded(); - auto displayLoading = (item->id < 0) || _data->displayLoading(); - auto selected = (selection == FullSelection); - - if (loaded - && cAutoPlayGif() - && !_gif - && !_gif.isBad() - && !activeRoundVideo()) { - _parent->delegate()->elementAnimationAutoplayAsync(_parent); - } - - auto paintx = 0, painty = 0, paintw = width(), painth = height(); - bool bubble = _parent->hasBubble(); - auto outbg = _parent->hasOutLayout(); - auto inWebPage = (_parent->media() != this); - - auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right(); - - auto isRound = _data->isVideoMessage(); - auto displayMute = false; - const auto reader = currentReader(); - const auto playingVideo = reader - ? (reader->mode() == Media::Clip::Reader::Mode::Video) - : false; - const auto animating = reader && reader->started(); - - if ((!animating || item->id < 0) && displayLoading) { - ensureAnimation(); - if (!_animation->radial.animating()) { - _animation->radial.start(dataProgress()); - } - } - updateStatusText(); - auto radial = isRadialAnimation(ms); - - if (bubble) { - if (!_caption.isEmpty()) { - painth -= st::mediaCaptionSkip + _caption.countHeight(captionw); - if (isBubbleBottom()) { - painth -= st::msgPadding.bottom(); - } - } - } else if (!isRound) { - App::roundShadow(p, 0, 0, paintw, height(), selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); - } - - auto usex = 0, usew = paintw; - auto separateRoundVideo = isSeparateRoundVideo(); - auto via = separateRoundVideo ? item->Get<HistoryMessageVia>() : nullptr; - auto reply = separateRoundVideo ? item->Get<HistoryMessageReply>() : nullptr; - auto forwarded = separateRoundVideo ? item->Get<HistoryMessageForwarded>() : nullptr; - if (via || reply || forwarded) { - usew = maxWidth() - additionalWidth(via, reply, forwarded); - if (outbg) { - usex = width() - usew; - } - } - if (rtl()) usex = width() - usex - usew; - - QRect rthumb(rtlrect(usex + paintx, painty, usew, painth, width())); - - auto roundRadius = isRound ? ImageRoundRadius::Ellipse : inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large; - auto roundCorners = (isRound || inWebPage) ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None) - | ((isBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None)); - if (animating) { - auto paused = App::wnd()->controller()->isGifPausedAtLeastFor(Window::GifPauseReason::Any); - if (isRound) { - if (playingVideo) { - paused = false; - } else { - displayMute = true; - } - } - p.drawPixmap(rthumb.topLeft(), reader->current(_thumbw, _thumbh, usew, painth, roundRadius, roundCorners, paused ? 0 : ms)); - - if (const auto playback = videoPlayback()) { - const auto value = playback->value(ms); - if (value > 0.) { - auto pen = st::historyVideoMessageProgressFg->p; - auto was = p.pen(); - pen.setWidth(st::radialLine); - pen.setCapStyle(Qt::RoundCap); - p.setPen(pen); - p.setOpacity(st::historyVideoMessageProgressOpacity); - - auto from = QuarterArcLength; - auto len = -qRound(FullArcLength * value); - auto stepInside = st::radialLine / 2; - { - PainterHighQualityEnabler hq(p); - p.drawArc(rthumb.marginsRemoved(QMargins(stepInside, stepInside, stepInside, stepInside)), from, len); - } - - p.setPen(was); - p.setOpacity(1.); - } - } - } else { - const auto good = _data->goodThumbnail(); - if (good && good->loaded()) { - p.drawPixmap(rthumb.topLeft(), good->pixSingle({}, _thumbw, _thumbh, usew, painth, roundRadius, roundCorners)); - } else { - if (good) { - good->load({}); - } - p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(_realParent->fullId(), _thumbw, _thumbh, usew, painth, roundRadius, roundCorners)); - } - } - - if (selected) { - App::complexOverlayRect(p, rthumb, roundRadius, roundCorners); - } - - if (radial || (!reader && (_gif.isBad() || (!loaded && !_data->loading()) || !cAutoPlayGif()))) { - auto radialOpacity = (radial && loaded && item->id > 0) ? _animation->radial.opacity() : 1.; - auto inner = QRect(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); - p.setPen(Qt::NoPen); - if (selected) { - p.setBrush(st::msgDateImgBgSelected); - } else if (isThumbAnimation(ms)) { - auto over = _animation->a_thumbOver.current(); - p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over)); - } else { - auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); - p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); - } - p.setOpacity(radialOpacity * p.opacity()); - - { - PainterHighQualityEnabler hq(p); - p.drawEllipse(inner); - } - - p.setOpacity(radialOpacity); - auto icon = [&]() -> const style::icon * { - if (_data->loaded() && !radial) { - return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay); - } else if (radial || _data->loading()) { - if (item->id > 0 || _data->uploading()) { - return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); - } - return nullptr; - } - return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); - }(); - if (icon) { - icon->paintInCenter(p, inner); - } - if (radial) { - p.setOpacity(1); - QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); - _animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg); - } - - if (!isRound && (!animating || item->id < 0)) { - 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, rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); - p.setFont(st::normalFont); - p.setPen(st::msgDateImgFg); - p.drawTextLeft(statusX, statusY, width(), _statusText, statusW - 2 * st::msgDateImgPadding.x()); - } - } - if (displayMute) { - auto muteRect = rtlrect(rthumb.x() + (rthumb.width() - st::historyVideoMessageMuteSize) / 2, rthumb.y() + st::msgDateImgDelta, st::historyVideoMessageMuteSize, st::historyVideoMessageMuteSize, width()); - p.setPen(Qt::NoPen); - p.setBrush(selected ? st::msgDateImgBgSelected : st::msgDateImgBg); - PainterHighQualityEnabler hq(p); - p.drawEllipse(muteRect); - (selected ? st::historyVideoMessageMuteSelected : st::historyVideoMessageMute).paintInCenter(p, muteRect); - } - - if (!inWebPage && isRound) { - auto mediaUnread = item->isMediaUnread(); - auto statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x(); - auto statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y(); - auto statusX = usex + paintx + st::msgDateImgDelta + st::msgDateImgPadding.x(); - auto statusY = painty + painth - st::msgDateImgDelta - statusH + st::msgDateImgPadding.y(); - if (item->isMediaUnread()) { - statusW += st::mediaUnreadSkip + st::mediaUnreadSize; - } - App::roundRect(p, rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? StickerSelectedCorners : StickerCorners); - p.setFont(st::normalFont); - p.setPen(st::msgServiceFg); - p.drawTextLeft(statusX, statusY, width(), _statusText, statusW - 2 * st::msgDateImgPadding.x()); - if (mediaUnread) { - p.setPen(Qt::NoPen); - p.setBrush(st::msgServiceFg); - - { - PainterHighQualityEnabler hq(p); - p.drawEllipse(rtlrect(statusX - st::msgDateImgPadding.x() + statusW - st::msgDateImgPadding.x() - st::mediaUnreadSize, statusY + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, width())); - } - } - if (via || reply || forwarded) { - auto rectw = width() - usew - st::msgReplyPadding.left(); - auto innerw = rectw - (st::msgReplyPadding.left() + st::msgReplyPadding.right()); - auto recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom(); - auto forwardedHeightReal = forwarded ? forwarded->text.countHeight(innerw) : 0; - auto forwardedHeight = qMin(forwardedHeightReal, kMaxGifForwardedBarLines * st::msgServiceNameFont->height); - if (forwarded) { - recth += forwardedHeight; - } else if (via) { - recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); - } - if (reply) { - recth += st::msgReplyBarSize.height(); - } - int rectx = outbg ? 0 : (usew + st::msgReplyPadding.left()); - int recty = painty; - if (rtl()) rectx = width() - rectx - rectw; - - App::roundRect(p, rectx, recty, rectw, recth, selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? StickerSelectedCorners : StickerCorners); - p.setPen(st::msgServiceFg); - rectx += st::msgReplyPadding.left(); - rectw = innerw; - if (forwarded) { - p.setTextPalette(st::serviceTextPalette); - auto breakEverywhere = (forwardedHeightReal > forwardedHeight); - forwarded->text.drawElided(p, rectx, recty + st::msgReplyPadding.top(), rectw, kMaxGifForwardedBarLines, style::al_left, 0, -1, 0, breakEverywhere); - p.restoreTextPalette(); - } else if (via) { - p.setFont(st::msgDateFont); - p.drawTextLeft(rectx, recty + st::msgReplyPadding.top(), 2 * rectx + rectw, via->text); - int skip = st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); - recty += skip; - } - if (reply) { - HistoryMessageReply::PaintFlags flags = 0; - if (selected) { - flags |= HistoryMessageReply::PaintFlag::Selected; - } - reply->paint(p, _parent, rectx, recty, rectw, flags); - } - } - } - if (!isRound && !_caption.isEmpty()) { - p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg)); - _caption.draw(p, st::msgPadding.left(), painty + painth + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection); - } else if (!inWebPage) { - auto fullRight = paintx + usex + usew; - auto fullBottom = painty + painth; - auto maxRight = _parent->width() - st::msgMargin.left(); - if (_parent->hasFromPhoto()) { - maxRight -= st::msgMargin.right(); - } else { - maxRight -= st::msgMargin.left(); - } - if (isRound && !outbg) { - auto infoWidth = _parent->infoWidth(); - - // This is just some arbitrary point, - // the main idea is to make info left aligned here. - fullRight += infoWidth - st::normalFont->height; - if (fullRight > maxRight) { - fullRight = maxRight; - } - } - if (isRound || needInfoDisplay()) { - _parent->drawInfo(p, fullRight, fullBottom, 2 * paintx + paintw, selected, isRound ? InfoDisplayType::Background : InfoDisplayType::Image); - } - if (!bubble && _parent->displayRightAction()) { - auto fastShareLeft = (fullRight + st::historyFastShareLeft); - auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); - if (fastShareLeft + st::historyFastShareSize > maxRight) { - fastShareLeft = (fullRight - st::historyFastShareSize - st::msgDateImgDelta); - fastShareTop -= (st::msgDateImgDelta + st::msgDateImgPadding.y() + st::msgDateFont->height + st::msgDateImgPadding.y()); - } - _parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * paintx + paintw); - } - } -} - -TextState HistoryGif::textState(QPoint point, StateRequest request) const { - auto result = TextState(_parent); - - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { - return result; - } - auto paintx = 0, painty = 0, paintw = width(), painth = height(); - auto bubble = _parent->hasBubble(); - - if (bubble && !_caption.isEmpty()) { - auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right(); - painth -= _caption.countHeight(captionw); - if (isBubbleBottom()) { - painth -= st::msgPadding.bottom(); - } - if (QRect(st::msgPadding.left(), painth, captionw, height() - painth).contains(point)) { - result = TextState(_parent, _caption.getState( - point - QPoint(st::msgPadding.left(), painth), - captionw, - request.forText())); - return result; - } - painth -= st::mediaCaptionSkip; - } - auto outbg = _parent->hasOutLayout(); - auto inWebPage = (_parent->media() != this); - auto isRound = _data->isVideoMessage(); - auto usew = paintw, usex = 0; - auto separateRoundVideo = isSeparateRoundVideo(); - const auto item = _parent->data(); - auto via = separateRoundVideo ? item->Get<HistoryMessageVia>() : nullptr; - auto reply = separateRoundVideo ? item->Get<HistoryMessageReply>() : nullptr; - auto forwarded = separateRoundVideo ? item->Get<HistoryMessageForwarded>() : nullptr; - if (via || reply || forwarded) { - usew = maxWidth() - additionalWidth(via, reply, forwarded); - if (outbg) { - usex = width() - usew; - } - } - if (rtl()) usex = width() - usex - usew; - - if (via || reply || forwarded) { - auto rectw = paintw - usew - st::msgReplyPadding.left(); - auto innerw = rectw - (st::msgReplyPadding.left() + st::msgReplyPadding.right()); - auto recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom(); - auto forwardedHeightReal = forwarded ? forwarded->text.countHeight(innerw) : 0; - auto forwardedHeight = qMin(forwardedHeightReal, kMaxGifForwardedBarLines * st::msgServiceNameFont->height); - if (forwarded) { - recth += forwardedHeight; - } else if (via) { - recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); - } - if (reply) { - recth += st::msgReplyBarSize.height(); - } - auto rectx = outbg ? 0 : (usew + st::msgReplyPadding.left()); - auto recty = painty; - if (rtl()) rectx = width() - rectx - rectw; - - if (forwarded) { - if (QRect(rectx, recty, rectw, st::msgReplyPadding.top() + forwardedHeight).contains(point)) { - auto breakEverywhere = (forwardedHeightReal > forwardedHeight); - auto textRequest = request.forText(); - if (breakEverywhere) { - textRequest.flags |= Text::StateRequest::Flag::BreakEverywhere; - } - result = TextState(_parent, forwarded->text.getState( - point - QPoint(rectx + st::msgReplyPadding.left(), recty + st::msgReplyPadding.top()), - innerw, - textRequest)); - result.symbol = 0; - result.afterSymbol = false; - if (breakEverywhere) { - result.cursor = CursorState::Forwarded; - } else { - result.cursor = CursorState::None; - } - return result; - } - recty += forwardedHeight; - recth -= forwardedHeight; - } else if (via) { - auto viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom()); - if (QRect(rectx, recty, rectw, viah).contains(point)) { - result.link = via->link; - return result; - } - auto skip = st::msgServiceNameFont->height + (reply ? 2 * st::msgReplyPadding.top() : 0); - recty += skip; - recth -= skip; - } - if (reply) { - if (QRect(rectx, recty, rectw, recth).contains(point)) { - result.link = reply->replyToLink(); - return result; - } - } - } - if (QRect(usex + paintx, painty, usew, painth).contains(point)) { - if (_data->uploading()) { - result.link = _cancell; - } else if (!_gif || !cAutoPlayGif() || _data->isVideoMessage()) { - result.link = _data->loaded() ? _openl : (_data->loading() ? _cancell : _savel); - } else { - result.link = _openInMediaviewLink; - } - } - if (isRound || _caption.isEmpty()) { - auto fullRight = usex + paintx + usew; - auto fullBottom = painty + painth; - auto maxRight = _parent->width() - st::msgMargin.left(); - if (_parent->hasFromPhoto()) { - maxRight -= st::msgMargin.right(); - } else { - maxRight -= st::msgMargin.left(); - } - if (isRound && !outbg) { - auto infoWidth = _parent->infoWidth(); - - // This is just some arbitrary point, - // the main idea is to make info left aligned here. - fullRight += infoWidth - st::normalFont->height; - if (fullRight > maxRight) { - fullRight = maxRight; - } - } - if (!inWebPage) { - if (_parent->pointInTime(fullRight, fullBottom, point, isRound ? InfoDisplayType::Background : InfoDisplayType::Image)) { - result.cursor = CursorState::Date; - } - } - if (!bubble && _parent->displayRightAction()) { - auto fastShareLeft = (fullRight + st::historyFastShareLeft); - auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); - if (fastShareLeft + st::historyFastShareSize > maxRight) { - fastShareLeft = (fullRight - st::historyFastShareSize - st::msgDateImgDelta); - fastShareTop -= st::msgDateImgDelta + st::msgDateImgPadding.y() + st::msgDateFont->height + st::msgDateImgPadding.y(); - } - if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) { - result.link = _parent->rightActionLink(); - } - } - } - return result; -} - -TextWithEntities HistoryGif::selectedText(TextSelection selection) const { - return _caption.originalTextWithEntities(selection, ExpandLinksAll); -} - -bool HistoryGif::needsBubble() const { - if (_data->isVideoMessage()) { - return false; - } - if (!_caption.isEmpty()) { - return true; - } - const auto item = _parent->data(); - return item->viaBot() - || item->Has<HistoryMessageReply>() - || _parent->displayForwardedFrom() - || _parent->displayFromName(); - return false; -} - -int HistoryGif::additionalWidth() const { - const auto item = _parent->data(); - return additionalWidth( - item->Get<HistoryMessageVia>(), - item->Get<HistoryMessageReply>(), - item->Get<HistoryMessageForwarded>()); -} - -QString HistoryGif::mediaTypeString() const { - return _data->isVideoMessage() - ? lang(lng_in_dlg_video_message) - : qsl("GIF"); -} - -bool HistoryGif::isSeparateRoundVideo() const { - return _data->isVideoMessage() - && (_parent->media() == this) - && !_parent->hasBubble(); -} - -void HistoryGif::setStatusSize(int newSize) const { - if (_data->isVideoMessage()) { - _statusSize = newSize; - if (newSize < 0) { - _statusText = formatDurationText(-newSize - 1); - } else { - _statusText = formatDurationText(_data->duration()); - } - } else { - HistoryFileMedia::setStatusSize(newSize, _data->size, -2, 0); - } -} - -void HistoryGif::updateStatusText() const { - auto showPause = false; - auto statusSize = 0; - auto realDuration = 0; - if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { - statusSize = FileStatusSizeFailed; - } else if (_data->uploading()) { - statusSize = _data->uploadingData->offset; - } else if (_data->loading()) { - statusSize = _data->loadOffset(); - } else if (_data->loaded()) { - statusSize = FileStatusSizeLoaded; - if (const auto video = activeRoundPlayer()) { - statusSize = -1 - _data->duration(); - - const auto state = Media::Player::mixer()->currentState( - AudioMsgId::Type::Voice); - if (state.id == video->audioMsgId() && state.length) { - auto position = int64(0); - if (Media::Player::IsStoppedAtEnd(state.state)) { - position = state.length; - } else if (!Media::Player::IsStoppedOrStopping(state.state)) { - position = state.position; - } - accumulate_max(statusSize, -1 - int((state.length - position) / state.frequency + 1)); - } - } - } else { - statusSize = FileStatusSizeReady; - } - if (statusSize != _statusSize) { - setStatusSize(statusSize); - } -} - -void HistoryGif::refreshParentId(not_null<HistoryItem*> realParent) { - HistoryFileMedia::refreshParentId(realParent); - - const auto fullId = realParent->fullId(); - if (_openInMediaviewLink) { - _openInMediaviewLink->setMessageId(fullId); - } -} - -QString HistoryGif::additionalInfoString() const { - if (_data->isVideoMessage()) { - updateStatusText(); - return _statusText; - } - return QString(); -} - -void HistoryGif::parentTextUpdated() { - _caption = (_parent->media() == this) - ? createCaption(_parent->data()) - : Text(); - Auth().data().requestViewResize(_parent); -} - -int HistoryGif::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const { - int result = 0; - if (forwarded) { - accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + forwarded->text.maxWidth() + st::msgReplyPadding.right()); - } else if (via) { - accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + via->maxWidth + st::msgReplyPadding.left()); - } - if (reply) { - accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth()); - } - return result; -} - -Media::Player::RoundController *HistoryGif::activeRoundVideo() const { - return App::wnd()->controller()->roundVideo(_parent->data()); -} - -Media::Clip::Reader *HistoryGif::activeRoundPlayer() const { - if (const auto video = activeRoundVideo()) { - if (const auto result = video->reader()) { - if (result->ready()) { - return result; - } - } - } - return nullptr; -} - -Media::Clip::Reader *HistoryGif::currentReader() const { - if (const auto result = activeRoundPlayer()) { - return result; - } - return (_gif && _gif->ready()) ? _gif.get() : nullptr; -} - -Media::Clip::Playback *HistoryGif::videoPlayback() const { - if (const auto video = activeRoundVideo()) { - return video->playback(); - } - return nullptr; -} - -void HistoryGif::clipCallback(Media::Clip::Notification notification) { - using namespace Media::Clip; - - const auto reader = _gif.get(); - if (!reader) { - return; - } - switch (notification) { - case NotificationReinit: { - auto stopped = false; - if (reader->autoPausedGif()) { - auto amVisible = false; - Auth().data().queryItemVisibility().notify( - { _parent->data(), &amVisible }, - true); - if (!amVisible) { // Stop animation if it is not visible. - stopAnimation(); - stopped = true; - } - } - if (!stopped) { - Auth().data().requestViewResize(_parent); - } - } break; - - case NotificationRepaint: { - if (!reader->currentDisplayed()) { - Auth().data().requestViewRepaint(_parent); - } - } break; - } -} - -void HistoryGif::playAnimation(bool autoplay) { - if (_data->isVideoMessage() && !autoplay) { - return; - } else if (_gif && autoplay) { - return; - } - using Mode = Media::Clip::Reader::Mode; - if (_gif) { - stopAnimation(); - } else if (_data->loaded(DocumentData::FilePathResolveChecked)) { - if (!cAutoPlayGif()) { - Auth().data().stopAutoplayAnimations(); - } - setClipReader(Media::Clip::MakeReader( - _data, - _parent->data()->fullId(), - [=](auto notification) { clipCallback(notification); }, - Mode::Gif)); - if (_gif && autoplay) { - _gif->setAutoplay(); - } - } -} - -void HistoryGif::stopAnimation() { - if (_gif) { - clearClipReader(); - Auth().data().requestViewResize(_parent); - _data->unload(); - } -} - -void HistoryGif::setClipReader(Media::Clip::ReaderPointer gif) { - if (_gif) { - Auth().data().unregisterAutoplayAnimation(_gif.get()); - } - _gif = std::move(gif); - if (_gif) { - Auth().data().registerAutoplayAnimation(_gif.get(), _parent); - } -} - -HistoryGif::~HistoryGif() { - clearClipReader(); -} - -float64 HistoryGif::dataProgress() const { - return (_data->uploading() || _parent->data()->id > 0) - ? _data->progress() - : 0; -} - -bool HistoryGif::dataFinished() const { - return (_parent->data()->id > 0) - ? (!_data->loading() && !_data->uploading()) - : false; -} - -bool HistoryGif::dataLoaded() const { - return (_parent->data()->id > 0) ? _data->loaded() : false; -} - -bool HistoryGif::needInfoDisplay() const { - return (_parent->data()->id < 0 || _parent->isUnderCursor()); -} - -HistorySticker::HistorySticker( - not_null<Element*> parent, - not_null<DocumentData*> document) -: HistoryMedia(parent) -, _data(document) -, _emoji(_data->sticker()->alt) { - _data->thumb->load(parent->data()->fullId()); - if (auto emoji = Ui::Emoji::Find(_emoji)) { - _emoji = emoji->text(); - } -} - -QSize HistorySticker::countOptimalSize() { - auto sticker = _data->sticker(); - - if (!_packLink && sticker && sticker->set.type() != mtpc_inputStickerSetEmpty) { - _packLink = std::make_shared<LambdaClickHandler>([document = _data] { - StickerSetBox::Show(document); - }); - } - _pixw = _data->dimensions.width(); - _pixh = _data->dimensions.height(); - if (_pixw > st::maxStickerSize) { - _pixh = (st::maxStickerSize * _pixh) / _pixw; - _pixw = st::maxStickerSize; - } - if (_pixh > st::maxStickerSize) { - _pixw = (st::maxStickerSize * _pixw) / _pixh; - _pixh = st::maxStickerSize; - } - if (_pixw < 1) _pixw = 1; - if (_pixh < 1) _pixh = 1; - auto maxWidth = qMax(_pixw, st::minPhotoSize); - auto minHeight = qMax(_pixh, st::minPhotoSize); - accumulate_max( - maxWidth, - _parent->infoWidth() + 2 * st::msgDateImgPadding.x()); - if (_parent->media() == this) { - maxWidth += additionalWidth(); - } - return { maxWidth, minHeight }; -} - -QSize HistorySticker::countCurrentSize(int newWidth) { - const auto item = _parent->data(); - accumulate_min(newWidth, maxWidth()); - if (_parent->media() == this) { - auto via = item->Get<HistoryMessageVia>(); - auto reply = item->Get<HistoryMessageReply>(); - if (via || reply) { - int usew = maxWidth() - additionalWidth(via, reply); - int availw = newWidth - usew - st::msgReplyPadding.left() - st::msgReplyPadding.left() - st::msgReplyPadding.left(); - if (via) { - via->resize(availw); - } - if (reply) { - reply->resize(availw); - } - } - } - return { newWidth, minHeight() }; -} - -void HistorySticker::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { - auto sticker = _data->sticker(); - if (!sticker) return; - - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; - - _data->checkSticker(); - bool loaded = _data->loaded(); - bool selected = (selection == FullSelection); - - auto outbg = _parent->hasOutLayout(); - auto inWebPage = (_parent->media() != this); - - const auto item = _parent->data(); - int usew = maxWidth(), usex = 0; - auto via = inWebPage ? nullptr : item->Get<HistoryMessageVia>(); - auto reply = inWebPage ? nullptr : item->Get<HistoryMessageReply>(); - if (via || reply) { - usew -= additionalWidth(via, reply); - if (outbg) { - usex = width() - usew; - } - } - if (rtl()) usex = width() - usex - usew; - - const auto &pixmap = [&]() -> const QPixmap & { - const auto o = item->fullId(); - const auto w = _pixw; - const auto h = _pixh; - const auto &c = st::msgStickerOverlay; - if (const auto image = _data->getStickerImage()) { - return selected - ? image->pixColored(o, c, w, h) - : image->pix(o, w, h); - } - return selected - ? _data->thumb->pixBlurredColored(o, c, w, h) - : _data->thumb->pixBlurred(o, w, h); - }(); - p.drawPixmap( - QPoint{ usex + (usew - _pixw) / 2, (minHeight() - _pixh) / 2 }, - pixmap); - - if (!inWebPage) { - auto fullRight = usex + usew; - auto fullBottom = height(); - if (needInfoDisplay()) { - _parent->drawInfo(p, fullRight, fullBottom, usex * 2 + usew, selected, InfoDisplayType::Background); - } - if (via || reply) { - int rectw = width() - usew - st::msgReplyPadding.left(); - int recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom(); - if (via) { - recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); - } - if (reply) { - recth += st::msgReplyBarSize.height(); - } - int rectx = outbg ? 0 : (usew + st::msgReplyPadding.left()); - int recty = st::msgDateImgDelta; - if (rtl()) rectx = width() - rectx - rectw; - - App::roundRect(p, rectx, recty, rectw, recth, selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? StickerSelectedCorners : StickerCorners); - p.setPen(st::msgServiceFg); - rectx += st::msgReplyPadding.left(); - rectw -= st::msgReplyPadding.left() + st::msgReplyPadding.right(); - if (via) { - p.setFont(st::msgDateFont); - p.drawTextLeft(rectx, recty + st::msgReplyPadding.top(), 2 * rectx + rectw, via->text); - int skip = st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); - recty += skip; - } - if (reply) { - HistoryMessageReply::PaintFlags flags = 0; - if (selected) { - flags |= HistoryMessageReply::PaintFlag::Selected; - } - reply->paint(p, _parent, rectx, recty, rectw, flags); - } - } - if (_parent->displayRightAction()) { - auto fastShareLeft = (fullRight + st::historyFastShareLeft); - auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); - _parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * usex + usew); - } - } -} - -TextState HistorySticker::textState(QPoint point, StateRequest request) const { - auto result = TextState(_parent); - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { - return result; - } - - auto outbg = _parent->hasOutLayout(); - auto inWebPage = (_parent->media() != this); - - const auto item = _parent->data(); - int usew = maxWidth(), usex = 0; - auto via = inWebPage ? nullptr : item->Get<HistoryMessageVia>(); - auto reply = inWebPage ? nullptr : item->Get<HistoryMessageReply>(); - if (via || reply) { - usew -= additionalWidth(via, reply); - if (outbg) { - usex = width() - usew; - } - } - if (rtl()) usex = width() - usex - usew; - - if (via || reply) { - int rectw = width() - usew - st::msgReplyPadding.left(); - int recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom(); - if (via) { - recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); - } - if (reply) { - recth += st::msgReplyBarSize.height(); - } - int rectx = outbg ? 0 : (usew + st::msgReplyPadding.left()); - int recty = st::msgDateImgDelta; - if (rtl()) rectx = width() - rectx - rectw; - - if (via) { - int viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom()); - if (QRect(rectx, recty, rectw, viah).contains(point)) { - result.link = via->link; - return result; - } - int skip = st::msgServiceNameFont->height + (reply ? 2 * st::msgReplyPadding.top() : 0); - recty += skip; - recth -= skip; - } - if (reply) { - if (QRect(rectx, recty, rectw, recth).contains(point)) { - result.link = reply->replyToLink(); - return result; - } - } - } - if (_parent->media() == this) { - auto fullRight = usex + usew; - auto fullBottom = height(); - if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) { - result.cursor = CursorState::Date; - } - if (_parent->displayRightAction()) { - auto fastShareLeft = (fullRight + st::historyFastShareLeft); - auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); - if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) { - result.link = _parent->rightActionLink(); - } - } - } - - auto pixLeft = usex + (usew - _pixw) / 2; - auto pixTop = (minHeight() - _pixh) / 2; - if (QRect(pixLeft, pixTop, _pixw, _pixh).contains(point)) { - result.link = _packLink; - return result; - } - return result; -} - -bool HistorySticker::needInfoDisplay() const { - return (_parent->data()->id < 0 || _parent->isUnderCursor()); -} - -int HistorySticker::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const { - int result = 0; - if (via) { - accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + via->maxWidth + st::msgReplyPadding.left()); - } - if (reply) { - accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth()); - } - return result; -} - -int HistorySticker::additionalWidth() const { - const auto item = _parent->data(); - return additionalWidth( - item->Get<HistoryMessageVia>(), - item->Get<HistoryMessageReply>()); -} - -namespace { - -ClickHandlerPtr sendMessageClickHandler(PeerData *peer) { - return std::make_shared<LambdaClickHandler>([peer] { - App::wnd()->controller()->showPeerHistory( - peer->id, - Window::SectionShow::Way::Forward); - }); -} - -ClickHandlerPtr addContactClickHandler(not_null<HistoryItem*> item) { - return std::make_shared<LambdaClickHandler>([fullId = item->fullId()] { - if (const auto item = App::histItemById(fullId)) { - if (const auto media = item->media()) { - if (const auto contact = media->sharedContact()) { - Ui::show(Box<AddContactBox>( - contact->firstName, - contact->lastName, - contact->phoneNumber)); - } - } - } - }); -} - -} // namespace - -HistoryContact::HistoryContact( - not_null<Element*> parent, - UserId userId, - const QString &first, - const QString &last, - const QString &phone) -: HistoryMedia(parent) -, _userId(userId) -, _fname(first) -, _lname(last) -, _phone(App::formatPhone(phone)) { - Auth().data().registerContactView(userId, parent); - - _name.setText( - st::semiboldTextStyle, - lng_full_name(lt_first_name, first, lt_last_name, last).trimmed(), - Ui::NameTextOptions()); - _phonew = st::normalFont->width(_phone); -} - -HistoryContact::~HistoryContact() { - Auth().data().unregisterContactView(_userId, _parent); -} - -void HistoryContact::updateSharedContactUserId(UserId userId) { - if (_userId != userId) { - Auth().data().unregisterContactView(_userId, _parent); - _userId = userId; - Auth().data().registerContactView(_userId, _parent); - } -} - -QSize HistoryContact::countOptimalSize() { - const auto item = _parent->data(); - auto maxWidth = st::msgFileMinWidth; - - _contact = _userId ? App::userLoaded(_userId) : nullptr; - if (_contact) { - _contact->loadUserpic(); - } else { - _photoEmpty = std::make_unique<Ui::EmptyUserpic>( - Data::PeerUserpicColor(_userId ? _userId : _parent->data()->id), - _name.originalText()); - } - if (_contact - && _contact->contactStatus() == UserData::ContactStatus::Contact) { - _linkl = sendMessageClickHandler(_contact); - _link = lang(lng_profile_send_message).toUpper(); - } else if (_userId) { - _linkl = addContactClickHandler(_parent->data()); - _link = lang(lng_profile_add_contact).toUpper(); - } - _linkw = _link.isEmpty() ? 0 : st::semiboldFont->width(_link); - - auto tleft = 0; - auto tright = 0; - if (_userId) { - tleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); - tright = st::msgFileThumbPadding.left(); - accumulate_max(maxWidth, tleft + _phonew + tright); - } else { - tleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); - tright = st::msgFileThumbPadding.left(); - accumulate_max(maxWidth, tleft + _phonew + _parent->skipBlockWidth() + st::msgPadding.right()); - } - - accumulate_max(maxWidth, tleft + _name.maxWidth() + tright); - accumulate_min(maxWidth, st::msgMaxWidth); - auto minHeight = 0; - if (_userId) { - minHeight = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom(); - if (item->Has<HistoryMessageSigned>() - || item->Has<HistoryMessageViews>()) { - minHeight += st::msgDateFont->height - st::msgDateDelta.y(); - } - } else { - minHeight = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom(); - } - if (!isBubbleTop()) { - minHeight -= st::msgFileTopMinus; - } - return { maxWidth, minHeight }; -} - -void HistoryContact::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; - auto paintx = 0, painty = 0, paintw = width(), painth = height(); - - auto outbg = _parent->hasOutLayout(); - bool selected = (selection == FullSelection); - - accumulate_min(paintw, maxWidth()); - - auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0; - auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus; - if (_userId) { - nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); - nametop = st::msgFileThumbNameTop - topMinus; - nameright = st::msgFileThumbPadding.left(); - statustop = st::msgFileThumbStatusTop - topMinus; - linktop = st::msgFileThumbLinkTop - topMinus; - - QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, paintw)); - if (_contact) { - _contact->paintUserpic(p, rthumb.x(), rthumb.y(), st::msgFileThumbSize); - } else { - _photoEmpty->paint(p, st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, paintw, st::msgFileThumbSize); - } - if (selected) { - PainterHighQualityEnabler hq(p); - p.setBrush(p.textPalette().selectOverlay); - p.setPen(Qt::NoPen); - p.drawEllipse(rthumb); - } - - bool over = ClickHandler::showAsActive(_linkl); - 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, paintw, _link, _linkw); - } else { - nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); - nametop = st::msgFileNameTop - topMinus; - nameright = st::msgFilePadding.left(); - statustop = st::msgFileStatusTop - topMinus; - - _photoEmpty->paint(p, st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, paintw, st::msgFileSize); - } - auto namewidth = paintw - nameleft - nameright; - - p.setFont(st::semiboldFont); - p.setPen(outbg ? (selected ? st::historyFileNameOutFgSelected : st::historyFileNameOutFg) : (selected ? st::historyFileNameInFgSelected : st::historyFileNameInFg)); - _name.drawLeftElided(p, nameleft, nametop, namewidth, paintw); - - auto &status = outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg); - p.setFont(st::normalFont); - p.setPen(status); - p.drawTextLeft(nameleft, statustop, paintw, _phone); -} - -TextState HistoryContact::textState(QPoint point, StateRequest request) const { - auto result = TextState(_parent); - - auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0; - auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus; - if (_userId) { - nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); - linktop = st::msgFileThumbLinkTop - topMinus; - if (rtlrect(nameleft, linktop, _linkw, st::semiboldFont->height, width()).contains(point)) { - result.link = _linkl; - return result; - } - } - if (QRect(0, 0, width(), height()).contains(point) && _contact) { - result.link = _contact->openLink(); - return result; - } - return result; -} - -HistoryCall::HistoryCall( - not_null<Element*> parent, - not_null<Data::Call*> call) -: HistoryMedia(parent) { - _duration = call->duration; - _reason = call->finishReason; - - const auto item = parent->data(); - _text = Data::MediaCall::Text(item, _reason); - _status = parent->dateTime().time().toString(cTimeFormat()); - if (_duration) { - if (_reason != FinishReason::Missed - && _reason != FinishReason::Busy) { - _status = lng_call_duration_info( - lt_time, - _status, - lt_duration, - formatDurationWords(_duration)); - } else { - _duration = 0; - } - } -} - -QSize HistoryCall::countOptimalSize() { - const auto user = _parent->data()->history()->peer->asUser(); - _link = std::make_shared<LambdaClickHandler>([=] { - if (user) { - Calls::Current().startOutgoingCall(user); - } - }); - - auto maxWidth = st::historyCallWidth; - auto minHeight = st::historyCallHeight; - if (!isBubbleTop()) { - minHeight -= st::msgFileTopMinus; - } - return { maxWidth, minHeight }; -} - -void HistoryCall::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; - auto paintx = 0, painty = 0, paintw = width(), painth = height(); - - auto outbg = _parent->hasOutLayout(); - auto selected = (selection == FullSelection); - - accumulate_min(paintw, maxWidth()); - - auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0; - auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus; - - nameleft = st::historyCallLeft; - nametop = st::historyCallTop - topMinus; - nameright = st::msgFilePadding.left(); - statustop = st::historyCallStatusTop - topMinus; - - auto namewidth = paintw - nameleft - nameright; - - p.setFont(st::semiboldFont); - p.setPen(outbg ? (selected ? st::historyFileNameOutFgSelected : st::historyFileNameOutFg) : (selected ? st::historyFileNameInFgSelected : st::historyFileNameInFg)); - p.drawTextLeft(nameleft, nametop, paintw, _text); - - auto statusleft = nameleft; - auto missed = (_reason == FinishReason::Missed || _reason == FinishReason::Busy); - auto &arrow = outbg ? (selected ? st::historyCallArrowOutSelected : st::historyCallArrowOut) : missed ? (selected ? st::historyCallArrowMissedInSelected : st::historyCallArrowMissedIn) : (selected ? st::historyCallArrowInSelected : st::historyCallArrowIn); - arrow.paint(p, statusleft + st::historyCallArrowPosition.x(), statustop + st::historyCallArrowPosition.y(), paintw); - statusleft += arrow.width() + st::historyCallStatusSkip; - - auto &statusFg = outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg); - p.setFont(st::normalFont); - p.setPen(statusFg); - p.drawTextLeft(statusleft, statustop, paintw, _status); - - auto &icon = outbg ? (selected ? st::historyCallOutIconSelected : st::historyCallOutIcon) : (selected ? st::historyCallInIconSelected : st::historyCallInIcon); - icon.paint(p, paintw - st::historyCallIconPosition.x() - icon.width(), st::historyCallIconPosition.y() - topMinus, paintw); -} - -TextState HistoryCall::textState(QPoint point, StateRequest request) const { - auto result = TextState(_parent); - if (QRect(0, 0, width(), height()).contains(point)) { - result.link = _link; - return result; - } - return result; -} - -namespace { - -int articleThumbWidth(PhotoData *thumb, int height) { - auto w = thumb->medium->width(); - auto h = thumb->medium->height(); - return qMax(qMin(height * w / h, height), 1); -} - -int articleThumbHeight(PhotoData *thumb, int width) { - return qMax(thumb->medium->height() * width / thumb->medium->width(), 1); -} - -int unitedLineHeight() { - return qMax(st::webPageTitleFont->height, st::webPageDescriptionFont->height); -} - -} // namespace - -HistoryWebPage::HistoryWebPage( - not_null<Element*> parent, - not_null<WebPageData*> data) -: HistoryMedia(parent) -, _data(data) -, _title(st::msgMinWidth - st::webPageLeft) -, _description(st::msgMinWidth - st::webPageLeft) { - Auth().data().registerWebPageView(_data, _parent); -} - -QSize HistoryWebPage::countOptimalSize() { - if (_data->pendingTill) { - return { 0, 0 }; - } - const auto versionChanged = (_dataVersion != _data->version); - if (versionChanged) { - _dataVersion = _data->version; - _openl = nullptr; - _attach = nullptr; - _collage = PrepareCollageMedia(_parent->data(), _data->collage); - _title = Text(st::msgMinWidth - st::webPageLeft); - _description = Text(st::msgMinWidth - st::webPageLeft); - _siteNameWidth = 0; - } - auto lineHeight = unitedLineHeight(); - - if (!_openl && !_data->url.isEmpty()) { - const auto previewOfHiddenUrl = [&] { - const auto simplify = [](const QString &url) { - auto result = url.toLower(); - if (result.endsWith('/')) { - result.chop(1); - } - const auto prefixes = { qstr("http://"), qstr("https://") }; - for (const auto &prefix : prefixes) { - if (result.startsWith(prefix)) { - result = result.mid(prefix.size()); - break; - } - } - return result; - }; - const auto simplified = simplify(_data->url); - const auto full = _parent->data()->originalText(); - for (const auto &entity : full.entities) { - if (entity.type() != EntityInTextUrl) { - continue; - } - const auto link = full.text.mid( - entity.offset(), - entity.length()); - if (simplify(link) == simplified) { - return false; - } - } - return true; - }(); - _openl = previewOfHiddenUrl - ? std::make_shared<HiddenUrlClickHandler>(_data->url) - : std::make_shared<UrlClickHandler>(_data->url, true); - } - - // init layout - auto title = TextUtilities::SingleLine(_data->title.isEmpty() - ? _data->author - : _data->title); - if (!_collage.empty()) { - _asArticle = false; - } else if (!_data->document - && _data->photo - && _data->type != WebPageType::Photo - && _data->type != WebPageType::Video) { - if (_data->type == WebPageType::Profile) { - _asArticle = true; - } else if (_data->siteName == qstr("Twitter") - || _data->siteName == qstr("Facebook") - || _data->type == WebPageType::ArticleWithIV) { - _asArticle = false; - } else { - _asArticle = true; - } - if (_asArticle - && _data->description.text.isEmpty() - && title.isEmpty() - && _data->siteName.isEmpty()) { - _asArticle = false; - } - } else { - _asArticle = false; - } - - // init attach - if (!_attach && !_asArticle) { - _attach = CreateAttach( - _parent, - _data->document, - _data->photo, - _collage); - } - - auto textFloatsAroundInfo = !_asArticle && !_attach && isBubbleBottom(); - - // init strings - if (_description.isEmpty() && !_data->description.text.isEmpty()) { - auto text = _data->description; - - if (textFloatsAroundInfo) { - text.text += _parent->skipBlock(); - } - if (isLogEntryOriginal()) { - // Fix layout for small bubbles (narrow media caption edit log entries). - _description = Text(st::minPhotoSize - - st::msgPadding.left() - - st::msgPadding.right() - - st::webPageLeft); - } - _description.setMarkedText( - st::webPageDescriptionStyle, - text, - Ui::WebpageTextDescriptionOptions(_data->siteName)); - } - if (_title.isEmpty() && !title.isEmpty()) { - if (textFloatsAroundInfo && _description.isEmpty()) { - title += _parent->skipBlock(); - } - _title.setText( - st::webPageTitleStyle, - title, - Ui::WebpageTextTitleOptions()); - } - if (!_siteNameWidth && !_data->siteName.isEmpty()) { - _siteNameWidth = st::webPageTitleFont->width(_data->siteName); - } - - // init dimensions - auto l = st::msgPadding.left() + st::webPageLeft, r = st::msgPadding.right(); - auto skipBlockWidth = _parent->skipBlockWidth(); - auto maxWidth = skipBlockWidth; - auto minHeight = 0; - - auto siteNameHeight = _data->siteName.isEmpty() ? 0 : lineHeight; - auto titleMinHeight = _title.isEmpty() ? 0 : lineHeight; - auto descMaxLines = isLogEntryOriginal() ? kMaxOriginalEntryLines : (3 + (siteNameHeight ? 0 : 1) + (titleMinHeight ? 0 : 1)); - auto descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * lineHeight); - auto articleMinHeight = siteNameHeight + titleMinHeight + descriptionMinHeight; - auto articlePhotoMaxWidth = 0; - if (_asArticle) { - articlePhotoMaxWidth = st::webPagePhotoDelta + qMax(articleThumbWidth(_data->photo, articleMinHeight), lineHeight); - } - - if (_siteNameWidth) { - if (_title.isEmpty() && _description.isEmpty()) { - accumulate_max(maxWidth, _siteNameWidth + _parent->skipBlockWidth()); - } else { - accumulate_max(maxWidth, _siteNameWidth + articlePhotoMaxWidth); - } - minHeight += lineHeight; - } - if (!_title.isEmpty()) { - accumulate_max(maxWidth, _title.maxWidth() + articlePhotoMaxWidth); - minHeight += titleMinHeight; - } - if (!_description.isEmpty()) { - accumulate_max(maxWidth, _description.maxWidth() + articlePhotoMaxWidth); - minHeight += descriptionMinHeight; - } - if (_attach) { - auto attachAtTop = !_siteNameWidth && _title.isEmpty() && _description.isEmpty(); - if (!attachAtTop) minHeight += st::mediaInBubbleSkip; - - _attach->initDimensions(); - auto bubble = _attach->bubbleMargins(); - auto maxMediaWidth = _attach->maxWidth() - bubble.left() - bubble.right(); - if (isBubbleBottom() && _attach->customInfoLayout()) { - maxMediaWidth += skipBlockWidth; - } - accumulate_max(maxWidth, maxMediaWidth); - minHeight += _attach->minHeight() - bubble.top() - bubble.bottom(); - if (!_attach->additionalInfoString().isEmpty()) { - minHeight += bottomInfoPadding(); - } - } - if (_data->type == WebPageType::Video && _data->duration) { - _duration = formatDurationText(_data->duration); - _durationWidth = st::msgDateFont->width(_duration); - } - maxWidth += st::msgPadding.left() + st::webPageLeft + st::msgPadding.right(); - auto padding = inBubblePadding(); - minHeight += padding.top() + padding.bottom(); - - if (_asArticle) { - minHeight = resizeGetHeight(maxWidth); - } - return { maxWidth, minHeight }; -} - -QSize HistoryWebPage::countCurrentSize(int newWidth) { - if (_data->pendingTill) { - return { newWidth, minHeight() }; - } - - auto innerWidth = newWidth - st::msgPadding.left() - st::webPageLeft - st::msgPadding.right(); - auto newHeight = 0; - - auto lineHeight = unitedLineHeight(); - auto linesMax = isLogEntryOriginal() ? kMaxOriginalEntryLines : 5; - auto siteNameLines = _siteNameWidth ? 1 : 0; - auto siteNameHeight = _siteNameWidth ? lineHeight : 0; - if (_asArticle) { - _pixh = linesMax * lineHeight; - do { - _pixw = articleThumbWidth(_data->photo, _pixh); - auto wleft = innerWidth - st::webPagePhotoDelta - qMax(_pixw, lineHeight); - - newHeight = siteNameHeight; - - if (_title.isEmpty()) { - _titleLines = 0; - } else { - if (_title.countHeight(wleft) < 2 * st::webPageTitleFont->height) { - _titleLines = 1; - } else { - _titleLines = 2; - } - newHeight += _titleLines * lineHeight; - } - - auto descriptionHeight = _description.countHeight(wleft); - if (descriptionHeight < (linesMax - siteNameLines - _titleLines) * st::webPageDescriptionFont->height) { - // We have height for all the lines. - _descriptionLines = -1; - newHeight += descriptionHeight; - } else { - _descriptionLines = (linesMax - siteNameLines - _titleLines); - newHeight += _descriptionLines * lineHeight; - } - - if (newHeight >= _pixh) { - break; - } - - _pixh -= lineHeight; - } while (_pixh > lineHeight); - newHeight += bottomInfoPadding(); - } else { - newHeight = siteNameHeight; - - if (_title.isEmpty()) { - _titleLines = 0; - } else { - if (_title.countHeight(innerWidth) < 2 * st::webPageTitleFont->height) { - _titleLines = 1; - } else { - _titleLines = 2; - } - newHeight += _titleLines * lineHeight; - } - - if (_description.isEmpty()) { - _descriptionLines = 0; - } else { - auto descriptionHeight = _description.countHeight(innerWidth); - if (descriptionHeight < (linesMax - siteNameLines - _titleLines) * st::webPageDescriptionFont->height) { - // We have height for all the lines. - _descriptionLines = -1; - newHeight += descriptionHeight; - } else { - _descriptionLines = (linesMax - siteNameLines - _titleLines); - newHeight += _descriptionLines * lineHeight; - } - } - - if (_attach) { - auto attachAtTop = !_siteNameWidth && !_titleLines && !_descriptionLines; - if (!attachAtTop) newHeight += st::mediaInBubbleSkip; - - auto bubble = _attach->bubbleMargins(); - - _attach->resizeGetHeight(innerWidth + bubble.left() + bubble.right()); - newHeight += _attach->height() - bubble.top() - bubble.bottom(); - if (!_attach->additionalInfoString().isEmpty()) { - newHeight += bottomInfoPadding(); - } else if (isBubbleBottom() && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > innerWidth + bubble.left() + bubble.right()) { - newHeight += bottomInfoPadding(); - } - } - } - auto padding = inBubblePadding(); - newHeight += padding.top() + padding.bottom(); - - return { newWidth, newHeight }; -} - -TextSelection HistoryWebPage::toDescriptionSelection( - TextSelection selection) const { - return HistoryView::UnshiftItemSelection(selection, _title); -} - -TextSelection HistoryWebPage::fromDescriptionSelection( - TextSelection selection) const { - return HistoryView::ShiftItemSelection(selection, _title); -} - -void HistoryWebPage::refreshParentId(not_null<HistoryItem*> realParent) { - if (_attach) { - _attach->refreshParentId(realParent); - } -} - -void HistoryWebPage::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; - auto paintx = 0, painty = 0, paintw = width(), painth = height(); - - auto outbg = _parent->hasOutLayout(); - bool selected = (selection == FullSelection); - - auto &barfg = selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor); - auto &semibold = selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg); - auto ®ular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg); - - QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); - auto padding = inBubblePadding(); - auto tshift = padding.top(); - auto bshift = padding.bottom(); - paintw -= padding.left() + padding.right(); - auto attachAdditionalInfoText = _attach ? _attach->additionalInfoString() : QString(); - if (_asArticle) { - bshift += bottomInfoPadding(); - } else if (!attachAdditionalInfoText.isEmpty()) { - bshift += bottomInfoPadding(); - } else if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) { - bshift += bottomInfoPadding(); - } - - QRect bar(rtlrect(st::msgPadding.left(), tshift, st::webPageBar, height() - tshift - bshift, width())); - p.fillRect(bar, barfg); - - auto lineHeight = unitedLineHeight(); - if (_asArticle) { - const auto contextId = _parent->data()->fullId(); - _data->photo->medium->load(contextId, false, false); - bool full = _data->photo->medium->loaded(); - QPixmap pix; - auto pw = qMax(_pixw, lineHeight); - auto ph = _pixh; - auto pixw = _pixw, pixh = articleThumbHeight(_data->photo, _pixw); - auto maxw = ConvertScale(_data->photo->medium->width()), maxh = ConvertScale(_data->photo->medium->height()); - if (pixw * ph != pixh * pw) { - float64 coef = (pixw * ph > pixh * pw) ? qMin(ph / float64(pixh), maxh / float64(pixh)) : qMin(pw / float64(pixw), maxw / float64(pixw)); - pixh = qRound(pixh * coef); - pixw = qRound(pixw * coef); - } - if (full) { - pix = _data->photo->medium->pixSingle(contextId, pixw, pixh, pw, ph, ImageRoundRadius::Small); - } else { - pix = _data->photo->thumb->pixBlurredSingle(contextId, pixw, pixh, pw, ph, ImageRoundRadius::Small); - } - p.drawPixmapLeft(padding.left() + paintw - pw, tshift, width(), pix); - if (selected) { - App::roundRect(p, rtlrect(padding.left() + paintw - pw, tshift, pw, _pixh, width()), p.textPalette().selectOverlay, SelectedOverlaySmallCorners); - } - paintw -= pw + st::webPagePhotoDelta; - } - if (_siteNameWidth) { - p.setFont(st::webPageTitleFont); - p.setPen(semibold); - p.drawTextLeft(padding.left(), tshift, width(), (paintw >= _siteNameWidth) ? _data->siteName : st::webPageTitleFont->elided(_data->siteName, paintw)); - tshift += lineHeight; - } - if (_titleLines) { - p.setPen(outbg ? st::webPageTitleOutFg : st::webPageTitleInFg); - auto endskip = 0; - if (_title.hasSkipBlock()) { - endskip = _parent->skipBlockWidth(); - } - _title.drawLeftElided(p, padding.left(), tshift, paintw, width(), _titleLines, style::al_left, 0, -1, endskip, false, selection); - tshift += _titleLines * lineHeight; - } - if (_descriptionLines) { - p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg); - auto endskip = 0; - if (_description.hasSkipBlock()) { - endskip = _parent->skipBlockWidth(); - } - if (_descriptionLines > 0) { - _description.drawLeftElided(p, padding.left(), tshift, paintw, width(), _descriptionLines, style::al_left, 0, -1, endskip, false, toDescriptionSelection(selection)); - tshift += _descriptionLines * lineHeight; - } else { - _description.drawLeft(p, padding.left(), tshift, paintw, width(), style::al_left, 0, -1, toDescriptionSelection(selection)); - tshift += _description.countHeight(paintw); - } - } - if (_attach) { - auto attachAtTop = !_siteNameWidth && !_titleLines && !_descriptionLines; - if (!attachAtTop) tshift += st::mediaInBubbleSkip; - - auto attachLeft = padding.left() - bubble.left(); - auto attachTop = tshift - bubble.top(); - if (rtl()) attachLeft = width() - attachLeft - _attach->width(); - - p.translate(attachLeft, attachTop); - - auto attachSelection = selected ? FullSelection : TextSelection { 0, 0 }; - _attach->draw(p, r.translated(-attachLeft, -attachTop), attachSelection, ms); - auto pixwidth = _attach->width(); - auto pixheight = _attach->height(); - - if (_data->type == WebPageType::Video - && _attach->type() == MediaTypePhoto) { - if (_attach->isReadyForOpen()) { - if (_data->siteName == qstr("YouTube")) { - st::youtubeIcon.paint(p, (pixwidth - st::youtubeIcon.width()) / 2, (pixheight - st::youtubeIcon.height()) / 2, width()); - } else { - st::videoIcon.paint(p, (pixwidth - st::videoIcon.width()) / 2, (pixheight - st::videoIcon.height()) / 2, width()); - } - } - if (_durationWidth) { - auto dateX = pixwidth - _durationWidth - st::msgDateImgDelta - 2 * st::msgDateImgPadding.x(); - auto dateY = pixheight - st::msgDateFont->height - 2 * st::msgDateImgPadding.y() - st::msgDateImgDelta; - 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); - - p.setFont(st::msgDateFont); - p.setPen(st::msgDateImgFg); - p.drawTextLeft(dateX + st::msgDateImgPadding.x(), dateY + st::msgDateImgPadding.y(), pixwidth, _duration); - } - } - - p.translate(-attachLeft, -attachTop); - - if (!attachAdditionalInfoText.isEmpty()) { - p.setFont(st::msgDateFont); - p.setPen(selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg)); - p.drawTextLeft(st::msgPadding.left(), bar.y() + bar.height() + st::mediaInBubbleSkip, width(), attachAdditionalInfoText); - } - } -} - -TextState HistoryWebPage::textState(QPoint point, StateRequest request) const { - auto result = TextState(_parent); - - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { - return result; - } - auto paintx = 0, painty = 0, paintw = width(), painth = height(); - - QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); - auto padding = inBubblePadding(); - auto tshift = padding.top(); - auto bshift = padding.bottom(); - if (_asArticle || (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right())) { - bshift += bottomInfoPadding(); - } - paintw -= padding.left() + padding.right(); - - auto lineHeight = unitedLineHeight(); - auto inThumb = false; - if (_asArticle) { - auto pw = qMax(_pixw, lineHeight); - if (rtlrect(padding.left() + paintw - pw, 0, pw, _pixh, width()).contains(point)) { - inThumb = true; - } - paintw -= pw + st::webPagePhotoDelta; - } - int symbolAdd = 0; - if (_siteNameWidth) { - tshift += lineHeight; - } - if (_titleLines) { - if (point.y() >= tshift && point.y() < tshift + _titleLines * lineHeight) { - Text::StateRequestElided titleRequest = request.forText(); - titleRequest.lines = _titleLines; - result = TextState(_parent, _title.getStateElidedLeft( - point - QPoint(padding.left(), tshift), - paintw, - width(), - titleRequest)); - } else if (point.y() >= tshift + _titleLines * lineHeight) { - symbolAdd += _title.length(); - } - tshift += _titleLines * lineHeight; - } - if (_descriptionLines) { - auto descriptionHeight = (_descriptionLines > 0) ? _descriptionLines * lineHeight : _description.countHeight(paintw); - if (point.y() >= tshift && point.y() < tshift + descriptionHeight) { - if (_descriptionLines > 0) { - Text::StateRequestElided descriptionRequest = request.forText(); - descriptionRequest.lines = _descriptionLines; - result = TextState(_parent, _description.getStateElidedLeft( - point - QPoint(padding.left(), tshift), - paintw, - width(), - descriptionRequest)); - } else { - result = TextState(_parent, _description.getStateLeft( - point - QPoint(padding.left(), tshift), - paintw, - width(), - request.forText())); - } - } else if (point.y() >= tshift + descriptionHeight) { - symbolAdd += _description.length(); - } - tshift += descriptionHeight; - } - if (inThumb) { - result.link = _openl; - } else if (_attach) { - auto attachAtTop = !_siteNameWidth && !_titleLines && !_descriptionLines; - if (!attachAtTop) tshift += st::mediaInBubbleSkip; - - if (QRect(padding.left(), tshift, paintw, height() - tshift - bshift).contains(point)) { - auto attachLeft = padding.left() - bubble.left(); - auto attachTop = tshift - bubble.top(); - if (rtl()) attachLeft = width() - attachLeft - _attach->width(); - result = _attach->textState(point - QPoint(attachLeft, attachTop), request); - - if (result.link && !_data->document && _data->photo && _collage.empty() && _attach->isReadyForOpen()) { - if (_data->type == WebPageType::Profile - || _data->type == WebPageType::Video) { - result.link = _openl; - } else if (_data->type == WebPageType::Photo - || _data->siteName == qstr("Twitter") - || _data->siteName == qstr("Facebook")) { - // leave photo link - } else { - result.link = _openl; - } - } - } - } - - result.symbol += symbolAdd; - return result; -} - -TextSelection HistoryWebPage::adjustSelection(TextSelection selection, TextSelectType type) const { - if (!_descriptionLines || selection.to <= _title.length()) { - return _title.adjustSelection(selection, type); - } - auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type); - if (selection.from >= _title.length()) { - return fromDescriptionSelection(descriptionSelection); - } - auto titleSelection = _title.adjustSelection(selection, type); - return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to }; -} - -void HistoryWebPage::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { - if (_attach) { - _attach->clickHandlerActiveChanged(p, active); - } -} - -void HistoryWebPage::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { - if (_attach) { - _attach->clickHandlerPressedChanged(p, pressed); - } -} - -void HistoryWebPage::playAnimation(bool autoplay) { - if (_attach) { - if (autoplay) { - _attach->autoplayAnimation(); - } else { - _attach->playAnimation(); - } - } -} - -bool HistoryWebPage::isDisplayed() const { - const auto item = _parent->data(); - return !_data->pendingTill - && !item->Has<HistoryMessageLogEntryOriginal>(); -} - -TextWithEntities HistoryWebPage::selectedText(TextSelection selection) const { - auto titleResult = _title.originalTextWithEntities( - selection, - ExpandLinksAll); - auto descriptionResult = _description.originalTextWithEntities( - toDescriptionSelection(selection), - ExpandLinksAll); - if (titleResult.text.isEmpty()) { - return descriptionResult; - } else if (descriptionResult.text.isEmpty()) { - return titleResult; - } - - titleResult.text += '\n'; - TextUtilities::Append(titleResult, std::move(descriptionResult)); - return titleResult; -} - -QMargins HistoryWebPage::inBubblePadding() const { - auto lshift = st::msgPadding.left() + st::webPageLeft; - auto rshift = st::msgPadding.right(); - auto bshift = isBubbleBottom() ? st::msgPadding.left() : st::mediaInBubbleSkip; - auto tshift = isBubbleTop() ? st::msgPadding.left() : st::mediaInBubbleSkip; - return QMargins(lshift, tshift, rshift, bshift); -} - -bool HistoryWebPage::isLogEntryOriginal() const { - return _parent->data()->isLogEntry() && _parent->media() != this; -} - -int HistoryWebPage::bottomInfoPadding() const { - if (!isBubbleBottom()) return 0; - - auto result = st::msgDateFont->height; - - // We use padding greater than st::msgPadding.bottom() in the - // bottom of the bubble so that the left line looks pretty. - // but if we have bottom skip because of the info display - // we don't need that additional padding so we replace it - // back with st::msgPadding.bottom() instead of left(). - result += st::msgPadding.bottom() - st::msgPadding.left(); - return result; -} - -HistoryWebPage::~HistoryWebPage() { - Auth().data().unregisterWebPageView(_data, _parent); -} - -HistoryGame::HistoryGame( - not_null<Element*> parent, - not_null<GameData*> data, - const TextWithEntities &consumed) -: HistoryMedia(parent) -, _data(data) -, _title(st::msgMinWidth - st::webPageLeft) -, _description(st::msgMinWidth - st::webPageLeft) { - if (!consumed.text.isEmpty()) { - _description.setMarkedText( - st::webPageDescriptionStyle, - consumed, - Ui::ItemTextOptions(parent->data())); - } - Auth().data().registerGameView(_data, _parent); -} - -QSize HistoryGame::countOptimalSize() { - auto lineHeight = unitedLineHeight(); - - const auto item = _parent->data(); - if (!_openl && IsServerMsgId(item->id)) { - const auto row = 0; - const auto column = 0; - _openl = std::make_shared<ReplyMarkupClickHandler>( - row, - column, - item->fullId()); - } - - auto title = TextUtilities::SingleLine(_data->title); - - // init attach - if (!_attach) { - _attach = CreateAttach(_parent, _data->document, _data->photo); - } - - // init strings - if (_description.isEmpty() && !_data->description.isEmpty()) { - auto text = _data->description; - if (!text.isEmpty()) { - if (!_attach) { - text += _parent->skipBlock(); - } - auto marked = TextWithEntities { text }; - auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText; - TextUtilities::ParseEntities(marked, parseFlags); - _description.setMarkedText( - st::webPageDescriptionStyle, - marked, - Ui::WebpageTextDescriptionOptions()); - } - } - if (_title.isEmpty() && !title.isEmpty()) { - _title.setText( - st::webPageTitleStyle, - title, - Ui::WebpageTextTitleOptions()); - } - - // init dimensions - auto l = st::msgPadding.left() + st::webPageLeft, r = st::msgPadding.right(); - auto skipBlockWidth = _parent->skipBlockWidth(); - auto maxWidth = skipBlockWidth; - auto minHeight = 0; - - auto titleMinHeight = _title.isEmpty() ? 0 : lineHeight; - // enable any count of lines in game description / message - auto descMaxLines = 4096; - auto descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * lineHeight); - - if (!_title.isEmpty()) { - accumulate_max(maxWidth, _title.maxWidth()); - minHeight += titleMinHeight; - } - if (!_description.isEmpty()) { - accumulate_max(maxWidth, _description.maxWidth()); - minHeight += descriptionMinHeight; - } - if (_attach) { - auto attachAtTop = !_titleLines && !_descriptionLines; - if (!attachAtTop) minHeight += st::mediaInBubbleSkip; - - _attach->initDimensions(); - QMargins bubble(_attach->bubbleMargins()); - auto maxMediaWidth = _attach->maxWidth() - bubble.left() - bubble.right(); - if (isBubbleBottom() && _attach->customInfoLayout()) { - maxMediaWidth += skipBlockWidth; - } - accumulate_max(maxWidth, maxMediaWidth); - minHeight += _attach->minHeight() - bubble.top() - bubble.bottom(); - } - maxWidth += st::msgPadding.left() + st::webPageLeft + st::msgPadding.right(); - auto padding = inBubblePadding(); - minHeight += padding.top() + padding.bottom(); - - if (!_gameTagWidth) { - _gameTagWidth = st::msgDateFont->width(lang(lng_game_tag).toUpper()); - } - return { maxWidth, minHeight }; -} - -void HistoryGame::refreshParentId(not_null<HistoryItem*> realParent) { - if (_openl) { - _openl->setMessageId(realParent->fullId()); - } - if (_attach) { - _attach->refreshParentId(realParent); - } -} - -QSize HistoryGame::countCurrentSize(int newWidth) { - accumulate_min(newWidth, maxWidth()); - auto innerWidth = newWidth - st::msgPadding.left() - st::webPageLeft - st::msgPadding.right(); - - // enable any count of lines in game description / message - auto linesMax = 4096; - auto lineHeight = unitedLineHeight(); - auto newHeight = 0; - if (_title.isEmpty()) { - _titleLines = 0; - } else { - if (_title.countHeight(innerWidth) < 2 * st::webPageTitleFont->height) { - _titleLines = 1; - } else { - _titleLines = 2; - } - newHeight += _titleLines * lineHeight; - } - - if (_description.isEmpty()) { - _descriptionLines = 0; - } else { - auto descriptionHeight = _description.countHeight(innerWidth); - if (descriptionHeight < (linesMax - _titleLines) * st::webPageDescriptionFont->height) { - _descriptionLines = (descriptionHeight / st::webPageDescriptionFont->height); - } else { - _descriptionLines = (linesMax - _titleLines); - } - newHeight += _descriptionLines * lineHeight; - } - - if (_attach) { - auto attachAtTop = !_titleLines && !_descriptionLines; - if (!attachAtTop) newHeight += st::mediaInBubbleSkip; - - QMargins bubble(_attach->bubbleMargins()); - - _attach->resizeGetHeight(innerWidth + bubble.left() + bubble.right()); - newHeight += _attach->height() - bubble.top() - bubble.bottom(); - if (isBubbleBottom() && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > innerWidth + bubble.left() + bubble.right()) { - newHeight += bottomInfoPadding(); - } - } - auto padding = inBubblePadding(); - newHeight += padding.top() + padding.bottom(); - - return { newWidth, newHeight }; -} - -TextSelection HistoryGame::toDescriptionSelection( - TextSelection selection) const { - return HistoryView::UnshiftItemSelection(selection, _title); -} - -TextSelection HistoryGame::fromDescriptionSelection( - TextSelection selection) const { - return HistoryView::ShiftItemSelection(selection, _title); -} - -void HistoryGame::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; - auto paintw = width(), painth = height(); - - auto outbg = _parent->hasOutLayout(); - bool selected = (selection == FullSelection); - - auto &barfg = selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor); - auto &semibold = selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg); - auto ®ular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg); - - QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); - auto padding = inBubblePadding(); - auto tshift = padding.top(); - auto bshift = padding.bottom(); - paintw -= padding.left() + padding.right(); - if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) { - bshift += bottomInfoPadding(); - } - - QRect bar(rtlrect(st::msgPadding.left(), tshift, st::webPageBar, height() - tshift - bshift, width())); - p.fillRect(bar, barfg); - - auto lineHeight = unitedLineHeight(); - if (_titleLines) { - p.setPen(semibold); - auto endskip = 0; - if (_title.hasSkipBlock()) { - endskip = _parent->skipBlockWidth(); - } - _title.drawLeftElided(p, padding.left(), tshift, paintw, width(), _titleLines, style::al_left, 0, -1, endskip, false, selection); - tshift += _titleLines * lineHeight; - } - if (_descriptionLines) { - p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg); - auto endskip = 0; - if (_description.hasSkipBlock()) { - endskip = _parent->skipBlockWidth(); - } - _description.drawLeftElided(p, padding.left(), tshift, paintw, width(), _descriptionLines, style::al_left, 0, -1, endskip, false, toDescriptionSelection(selection)); - tshift += _descriptionLines * lineHeight; - } - if (_attach) { - auto attachAtTop = !_titleLines && !_descriptionLines; - if (!attachAtTop) tshift += st::mediaInBubbleSkip; - - auto attachLeft = padding.left() - bubble.left(); - auto attachTop = tshift - bubble.top(); - if (rtl()) attachLeft = width() - attachLeft - _attach->width(); - - auto attachSelection = selected ? FullSelection : TextSelection { 0, 0 }; - - p.translate(attachLeft, attachTop); - _attach->draw(p, r.translated(-attachLeft, -attachTop), attachSelection, ms); - auto pixwidth = _attach->width(); - auto pixheight = _attach->height(); - - auto gameW = _gameTagWidth + 2 * st::msgDateImgPadding.x(); - auto gameH = st::msgDateFont->height + 2 * st::msgDateImgPadding.y(); - auto gameX = pixwidth - st::msgDateImgDelta - gameW; - auto gameY = pixheight - st::msgDateImgDelta - gameH; - - App::roundRect(p, rtlrect(gameX, gameY, gameW, gameH, pixwidth), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); - - p.setFont(st::msgDateFont); - p.setPen(st::msgDateImgFg); - p.drawTextLeft(gameX + st::msgDateImgPadding.x(), gameY + st::msgDateImgPadding.y(), pixwidth, lang(lng_game_tag).toUpper()); - - p.translate(-attachLeft, -attachTop); - } -} - -TextState HistoryGame::textState(QPoint point, StateRequest request) const { - auto result = TextState(_parent); - - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { - return result; - } - auto paintw = width(), painth = height(); - - QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); - auto padding = inBubblePadding(); - auto tshift = padding.top(); - auto bshift = padding.bottom(); - if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) { - bshift += bottomInfoPadding(); - } - paintw -= padding.left() + padding.right(); - - auto inThumb = false; - auto symbolAdd = 0; - auto lineHeight = unitedLineHeight(); - if (_titleLines) { - if (point.y() >= tshift && point.y() < tshift + _titleLines * lineHeight) { - Text::StateRequestElided titleRequest = request.forText(); - titleRequest.lines = _titleLines; - result = TextState(_parent, _title.getStateElidedLeft( - point - QPoint(padding.left(), tshift), - paintw, - width(), - titleRequest)); - } else if (point.y() >= tshift + _titleLines * lineHeight) { - symbolAdd += _title.length(); - } - tshift += _titleLines * lineHeight; - } - if (_descriptionLines) { - if (point.y() >= tshift && point.y() < tshift + _descriptionLines * lineHeight) { - Text::StateRequestElided descriptionRequest = request.forText(); - descriptionRequest.lines = _descriptionLines; - result = TextState(_parent, _description.getStateElidedLeft( - point - QPoint(padding.left(), tshift), - paintw, - width(), - descriptionRequest)); - } else if (point.y() >= tshift + _descriptionLines * lineHeight) { - symbolAdd += _description.length(); - } - tshift += _descriptionLines * lineHeight; - } - if (inThumb) { - if (!_parent->data()->isLogEntry()) { - result.link = _openl; - } - } else if (_attach) { - auto attachAtTop = !_titleLines && !_descriptionLines; - if (!attachAtTop) tshift += st::mediaInBubbleSkip; - - auto attachLeft = padding.left() - bubble.left(); - auto attachTop = tshift - bubble.top(); - if (rtl()) attachLeft = width() - attachLeft - _attach->width(); - - if (QRect(attachLeft, tshift, _attach->width(), height() - tshift - bshift).contains(point)) { - if (_attach->isReadyForOpen()) { - if (!_parent->data()->isLogEntry()) { - result.link = _openl; - } - } else { - result = _attach->textState(point - QPoint(attachLeft, attachTop), request); - } - } - } - - result.symbol += symbolAdd; - return result; -} - -TextSelection HistoryGame::adjustSelection(TextSelection selection, TextSelectType type) const { - if (!_descriptionLines || selection.to <= _title.length()) { - return _title.adjustSelection(selection, type); - } - auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type); - if (selection.from >= _title.length()) { - return fromDescriptionSelection(descriptionSelection); - } - auto titleSelection = _title.adjustSelection(selection, type); - return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to }; -} - -void HistoryGame::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { - if (_attach) { - _attach->clickHandlerActiveChanged(p, active); - } -} - -void HistoryGame::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { - if (_attach) { - _attach->clickHandlerPressedChanged(p, pressed); - } -} - -TextWithEntities HistoryGame::selectedText(TextSelection selection) const { - auto titleResult = _title.originalTextWithEntities( - selection, - ExpandLinksAll); - auto descriptionResult = _description.originalTextWithEntities( - toDescriptionSelection(selection), - ExpandLinksAll); - if (titleResult.text.isEmpty()) { - return descriptionResult; - } else if (descriptionResult.text.isEmpty()) { - return titleResult; - } - - titleResult.text += '\n'; - TextUtilities::Append(titleResult, std::move(descriptionResult)); - return titleResult; -} - -void HistoryGame::playAnimation(bool autoplay) { - if (_attach) { - if (autoplay) { - _attach->autoplayAnimation(); - } else { - _attach->playAnimation(); - } - } -} - -QMargins HistoryGame::inBubblePadding() const { - auto lshift = st::msgPadding.left() + st::webPageLeft; - auto rshift = st::msgPadding.right(); - auto bshift = isBubbleBottom() ? st::msgPadding.left() : st::mediaInBubbleSkip; - auto tshift = isBubbleTop() ? st::msgPadding.left() : st::mediaInBubbleSkip; - return QMargins(lshift, tshift, rshift, bshift); -} - -int HistoryGame::bottomInfoPadding() const { - if (!isBubbleBottom()) return 0; - - auto result = st::msgDateFont->height; - - // we use padding greater than st::msgPadding.bottom() in the - // bottom of the bubble so that the left line looks pretty. - // but if we have bottom skip because of the info display - // we don't need that additional padding so we replace it - // back with st::msgPadding.bottom() instead of left(). - result += st::msgPadding.bottom() - st::msgPadding.left(); - return result; -} - -void HistoryGame::parentTextUpdated() { - if (const auto media = _parent->data()->media()) { - const auto consumed = media->consumedMessageText(); - if (!consumed.text.isEmpty()) { - _description.setMarkedText( - st::webPageDescriptionStyle, - consumed, - Ui::ItemTextOptions(_parent->data())); - } else { - _description = Text(st::msgMinWidth - st::webPageLeft); - } - Auth().data().requestViewResize(_parent); - } -} - -HistoryGame::~HistoryGame() { - Auth().data().unregisterGameView(_data, _parent); -} - -HistoryInvoice::HistoryInvoice( - not_null<Element*> parent, - not_null<Data::Invoice*> invoice) -: HistoryMedia(parent) -, _title(st::msgMinWidth) -, _description(st::msgMinWidth) -, _status(st::msgMinWidth) { - fillFromData(invoice); -} - -void HistoryInvoice::fillFromData(not_null<Data::Invoice*> invoice) { - if (invoice->photo) { - _attach = std::make_unique<HistoryPhoto>( - _parent, - _parent->data(), - invoice->photo); - } else { - _attach = nullptr; - } - auto labelText = [&] { - if (invoice->receiptMsgId) { - if (invoice->isTest) { - return lang(lng_payments_receipt_label_test); - } - return lang(lng_payments_receipt_label); - } else if (invoice->isTest) { - return lang(lng_payments_invoice_label_test); - } - return lang(lng_payments_invoice_label); - }; - auto statusText = TextWithEntities { - FillAmountAndCurrency(invoice->amount, invoice->currency), - EntitiesInText() - }; - statusText.entities.push_back(EntityInText(EntityInTextBold, 0, statusText.text.size())); - statusText.text += ' ' + labelText().toUpper(); - _status.setMarkedText( - st::defaultTextStyle, - statusText, - Ui::ItemTextOptions(_parent->data())); - - _receiptMsgId = invoice->receiptMsgId; - - // init strings - if (!invoice->description.isEmpty()) { - auto marked = TextWithEntities { invoice->description }; - auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText; - TextUtilities::ParseEntities(marked, parseFlags); - _description.setMarkedText( - st::webPageDescriptionStyle, - marked, - Ui::WebpageTextDescriptionOptions()); - } - if (!invoice->title.isEmpty()) { - _title.setText( - st::webPageTitleStyle, - invoice->title, - Ui::WebpageTextTitleOptions()); - } -} - -QSize HistoryInvoice::countOptimalSize() { - auto lineHeight = unitedLineHeight(); - - if (_attach) { - if (_status.hasSkipBlock()) { - _status.removeSkipBlock(); - } - } else { - _status.updateSkipBlock( - _parent->skipBlockWidth(), - _parent->skipBlockHeight()); - } - - // init dimensions - auto l = st::msgPadding.left(), r = st::msgPadding.right(); - auto skipBlockWidth = _parent->skipBlockWidth(); - auto maxWidth = skipBlockWidth; - auto minHeight = 0; - - auto titleMinHeight = _title.isEmpty() ? 0 : lineHeight; - // enable any count of lines in game description / message - auto descMaxLines = 4096; - auto descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * lineHeight); - - if (!_title.isEmpty()) { - accumulate_max(maxWidth, _title.maxWidth()); - minHeight += titleMinHeight; - } - if (!_description.isEmpty()) { - accumulate_max(maxWidth, _description.maxWidth()); - minHeight += descriptionMinHeight; - } - if (_attach) { - auto attachAtTop = _title.isEmpty() && _description.isEmpty(); - if (!attachAtTop) minHeight += st::mediaInBubbleSkip; - - _attach->initDimensions(); - auto bubble = _attach->bubbleMargins(); - auto maxMediaWidth = _attach->maxWidth() - bubble.left() - bubble.right(); - if (isBubbleBottom() && _attach->customInfoLayout()) { - maxMediaWidth += skipBlockWidth; - } - accumulate_max(maxWidth, maxMediaWidth); - minHeight += _attach->minHeight() - bubble.top() - bubble.bottom(); - } else { - accumulate_max(maxWidth, _status.maxWidth()); - minHeight += st::mediaInBubbleSkip + _status.minHeight(); - } - auto padding = inBubblePadding(); - maxWidth += padding.left() + padding.right(); - minHeight += padding.top() + padding.bottom(); - return { maxWidth, minHeight }; -} - -QSize HistoryInvoice::countCurrentSize(int newWidth) { - accumulate_min(newWidth, maxWidth()); - auto innerWidth = newWidth - st::msgPadding.left() - st::msgPadding.right(); - - auto lineHeight = unitedLineHeight(); - - auto newHeight = 0; - if (_title.isEmpty()) { - _titleHeight = 0; - } else { - if (_title.countHeight(innerWidth) < 2 * st::webPageTitleFont->height) { - _titleHeight = lineHeight; - } else { - _titleHeight = 2 * lineHeight; - } - newHeight += _titleHeight; - } - - if (_description.isEmpty()) { - _descriptionHeight = 0; - } else { - _descriptionHeight = _description.countHeight(innerWidth); - newHeight += _descriptionHeight; - } - - if (_attach) { - auto attachAtTop = !_title.isEmpty() && _description.isEmpty(); - if (!attachAtTop) newHeight += st::mediaInBubbleSkip; - - QMargins bubble(_attach->bubbleMargins()); - - _attach->resizeGetHeight(innerWidth + bubble.left() + bubble.right()); - newHeight += _attach->height() - bubble.top() - bubble.bottom(); - if (isBubbleBottom() && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > innerWidth + bubble.left() + bubble.right()) { - newHeight += bottomInfoPadding(); - } - } else { - newHeight += st::mediaInBubbleSkip + _status.countHeight(innerWidth); - } - auto padding = inBubblePadding(); - newHeight += padding.top() + padding.bottom(); - - return { newWidth, newHeight }; -} - -TextSelection HistoryInvoice::toDescriptionSelection( - TextSelection selection) const { - return HistoryView::UnshiftItemSelection(selection, _title); -} - -TextSelection HistoryInvoice::fromDescriptionSelection( - TextSelection selection) const { - return HistoryView::ShiftItemSelection(selection, _title); -} - -void HistoryInvoice::refreshParentId(not_null<HistoryItem*> realParent) { - if (_attach) { - _attach->refreshParentId(realParent); - } -} - -void HistoryInvoice::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; - auto paintw = width(), painth = height(); - - auto outbg = _parent->hasOutLayout(); - bool selected = (selection == FullSelection); - - auto &barfg = selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor); - auto &semibold = selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg); - auto ®ular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg); - - QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); - auto padding = inBubblePadding(); - auto tshift = padding.top(); - auto bshift = padding.bottom(); - paintw -= padding.left() + padding.right(); - if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) { - bshift += bottomInfoPadding(); - } - - auto lineHeight = unitedLineHeight(); - if (_titleHeight) { - p.setPen(semibold); - p.setTextPalette(selected ? (outbg ? st::outTextPaletteSelected : st::inTextPaletteSelected) : (outbg ? st::outSemiboldPalette : st::inSemiboldPalette)); - - auto endskip = 0; - if (_title.hasSkipBlock()) { - endskip = _parent->skipBlockWidth(); - } - _title.drawLeftElided(p, padding.left(), tshift, paintw, width(), _titleHeight / lineHeight, style::al_left, 0, -1, endskip, false, selection); - tshift += _titleHeight; - - p.setTextPalette(selected ? (outbg ? st::outTextPaletteSelected : st::inTextPaletteSelected) : (outbg ? st::outTextPalette : st::inTextPalette)); - } - if (_descriptionHeight) { - p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg); - _description.drawLeft(p, padding.left(), tshift, paintw, width(), style::al_left, 0, -1, toDescriptionSelection(selection)); - tshift += _descriptionHeight; - } - if (_attach) { - auto attachAtTop = !_titleHeight && !_descriptionHeight; - if (!attachAtTop) tshift += st::mediaInBubbleSkip; - - auto attachLeft = padding.left() - bubble.left(); - auto attachTop = tshift - bubble.top(); - if (rtl()) attachLeft = width() - attachLeft - _attach->width(); - - auto attachSelection = selected ? FullSelection : TextSelection { 0, 0 }; - - p.translate(attachLeft, attachTop); - _attach->draw(p, r.translated(-attachLeft, -attachTop), attachSelection, ms); - auto pixwidth = _attach->width(); - auto pixheight = _attach->height(); - - auto available = _status.maxWidth(); - auto statusW = available + 2 * st::msgDateImgPadding.x(); - auto statusH = st::msgDateFont->height + 2 * st::msgDateImgPadding.y(); - auto statusX = st::msgDateImgDelta; - auto statusY = st::msgDateImgDelta; - - App::roundRect(p, rtlrect(statusX, statusY, statusW, statusH, pixwidth), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); - - p.setFont(st::msgDateFont); - p.setPen(st::msgDateImgFg); - _status.drawLeftElided(p, statusX + st::msgDateImgPadding.x(), statusY + st::msgDateImgPadding.y(), available, pixwidth); - - p.translate(-attachLeft, -attachTop); - } else { - p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg); - _status.drawLeft(p, padding.left(), tshift + st::mediaInBubbleSkip, paintw, width()); - } -} - -TextState HistoryInvoice::textState(QPoint point, StateRequest request) const { - auto result = TextState(_parent); - - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { - return result; - } - auto paintw = width(), painth = height(); - - QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); - auto padding = inBubblePadding(); - auto tshift = padding.top(); - auto bshift = padding.bottom(); - if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) { - bshift += bottomInfoPadding(); - } - paintw -= padding.left() + padding.right(); - - auto lineHeight = unitedLineHeight(); - auto symbolAdd = 0; - if (_titleHeight) { - if (point.y() >= tshift && point.y() < tshift + _titleHeight) { - Text::StateRequestElided titleRequest = request.forText(); - titleRequest.lines = _titleHeight / lineHeight; - result = TextState(_parent, _title.getStateElidedLeft( - point - QPoint(padding.left(), tshift), - paintw, - width(), - titleRequest)); - } else if (point.y() >= tshift + _titleHeight) { - symbolAdd += _title.length(); - } - tshift += _titleHeight; - } - if (_descriptionHeight) { - if (point.y() >= tshift && point.y() < tshift + _descriptionHeight) { - result = TextState(_parent, _description.getStateLeft( - point - QPoint(padding.left(), tshift), - paintw, - width(), - request.forText())); - } else if (point.y() >= tshift + _descriptionHeight) { - symbolAdd += _description.length(); - } - tshift += _descriptionHeight; - } - if (_attach) { - auto attachAtTop = !_titleHeight && !_descriptionHeight; - if (!attachAtTop) tshift += st::mediaInBubbleSkip; - - auto attachLeft = padding.left() - bubble.left(); - auto attachTop = tshift - bubble.top(); - if (rtl()) attachLeft = width() - attachLeft - _attach->width(); - - if (QRect(attachLeft, tshift, _attach->width(), height() - tshift - bshift).contains(point)) { - result = _attach->textState(point - QPoint(attachLeft, attachTop), request); - } - } - - result.symbol += symbolAdd; - return result; -} - -TextSelection HistoryInvoice::adjustSelection(TextSelection selection, TextSelectType type) const { - if (!_descriptionHeight || selection.to <= _title.length()) { - return _title.adjustSelection(selection, type); - } - auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type); - if (selection.from >= _title.length()) { - return fromDescriptionSelection(descriptionSelection); - } - auto titleSelection = _title.adjustSelection(selection, type); - return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to }; -} - -void HistoryInvoice::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { - if (_attach) { - _attach->clickHandlerActiveChanged(p, active); - } -} - -void HistoryInvoice::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { - if (_attach) { - _attach->clickHandlerPressedChanged(p, pressed); - } -} - -TextWithEntities HistoryInvoice::selectedText(TextSelection selection) const { - auto titleResult = _title.originalTextWithEntities( - selection, - ExpandLinksAll); - auto descriptionResult = _description.originalTextWithEntities( - toDescriptionSelection(selection), - ExpandLinksAll); - if (titleResult.text.isEmpty()) { - return descriptionResult; - } else if (descriptionResult.text.isEmpty()) { - return titleResult; - } - - titleResult.text += '\n'; - TextUtilities::Append(titleResult, std::move(descriptionResult)); - return titleResult; -} - -QMargins HistoryInvoice::inBubblePadding() const { - auto lshift = st::msgPadding.left(); - auto rshift = st::msgPadding.right(); - auto bshift = isBubbleBottom() ? st::msgPadding.top() : st::mediaInBubbleSkip; - auto tshift = isBubbleTop() ? st::msgPadding.bottom() : st::mediaInBubbleSkip; - return QMargins(lshift, tshift, rshift, bshift); -} - -int HistoryInvoice::bottomInfoPadding() const { - if (!isBubbleBottom()) return 0; - - auto result = st::msgDateFont->height; - return result; -} - -HistoryLocation::HistoryLocation( - not_null<Element*> parent, - not_null<LocationData*> location, - const QString &title, - const QString &description) -: HistoryMedia(parent) -, _data(location) -, _title(st::msgMinWidth) -, _description(st::msgMinWidth) -, _link(std::make_shared<LocationClickHandler>(_data->coords)) { - if (!title.isEmpty()) { - _title.setText( - st::webPageTitleStyle, - TextUtilities::Clean(title), - Ui::WebpageTextTitleOptions()); - } - if (!description.isEmpty()) { - _description.setMarkedText( - st::webPageDescriptionStyle, - TextUtilities::ParseEntities( - TextUtilities::Clean(description), - TextParseLinks | TextParseMultiline | TextParseRichText), - Ui::WebpageTextDescriptionOptions()); - } -} - -QSize HistoryLocation::countOptimalSize() { - auto tw = fullWidth(); - auto th = fullHeight(); - if (tw > st::maxMediaSize) { - th = (st::maxMediaSize * th) / tw; - tw = st::maxMediaSize; - } - auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); - auto maxWidth = qMax(tw, minWidth); - auto minHeight = qMax(th, st::minPhotoSize); - - if (_parent->hasBubble()) { - if (!_title.isEmpty()) { - minHeight += qMin(_title.countHeight(maxWidth - st::msgPadding.left() - st::msgPadding.right()), 2 * st::webPageTitleFont->height); - } - if (!_description.isEmpty()) { - minHeight += qMin(_description.countHeight(maxWidth - st::msgPadding.left() - st::msgPadding.right()), 3 * st::webPageDescriptionFont->height); - } - if (!_title.isEmpty() || !_description.isEmpty()) { - minHeight += st::mediaInBubbleSkip; - if (isBubbleTop()) { - minHeight += st::msgPadding.top(); - } - } - } - return { maxWidth, minHeight }; -} - -QSize HistoryLocation::countCurrentSize(int newWidth) { - accumulate_min(newWidth, maxWidth()); - - auto tw = fullWidth(); - auto th = fullHeight(); - if (tw > st::maxMediaSize) { - th = (st::maxMediaSize * th) / tw; - tw = st::maxMediaSize; - } - auto newHeight = th; - if (tw > newWidth) { - newHeight = (newWidth * newHeight / tw); - } else { - newWidth = tw; - } - auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); - accumulate_max(newWidth, minWidth); - accumulate_max(newHeight, st::minPhotoSize); - if (_parent->hasBubble()) { - if (!_title.isEmpty()) { - newHeight += qMin(_title.countHeight(newWidth - st::msgPadding.left() - st::msgPadding.right()), st::webPageTitleFont->height * 2); - } - if (!_description.isEmpty()) { - newHeight += qMin(_description.countHeight(newWidth - st::msgPadding.left() - st::msgPadding.right()), st::webPageDescriptionFont->height * 3); - } - if (!_title.isEmpty() || !_description.isEmpty()) { - newHeight += st::mediaInBubbleSkip; - if (isBubbleTop()) { - newHeight += st::msgPadding.top(); - } - } - } - return { newWidth, newHeight }; -} - -TextSelection HistoryLocation::toDescriptionSelection( - TextSelection selection) const { - return HistoryView::UnshiftItemSelection(selection, _title); -} - -TextSelection HistoryLocation::fromDescriptionSelection( - TextSelection selection) const { - return HistoryView::ShiftItemSelection(selection, _title); -} - -void HistoryLocation::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; - auto paintx = 0, painty = 0, paintw = width(), painth = height(); - bool bubble = _parent->hasBubble(); - auto outbg = _parent->hasOutLayout(); - bool selected = (selection == FullSelection); - - if (bubble) { - if (!_title.isEmpty() || !_description.isEmpty()) { - if (isBubbleTop()) { - painty += st::msgPadding.top(); - } - } - - auto textw = width() - st::msgPadding.left() - st::msgPadding.right(); - - if (!_title.isEmpty()) { - p.setPen(outbg ? st::webPageTitleOutFg : st::webPageTitleInFg); - _title.drawLeftElided(p, paintx + st::msgPadding.left(), painty, textw, width(), 2, style::al_left, 0, -1, 0, false, selection); - painty += qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height); - } - if (!_description.isEmpty()) { - p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg); - _description.drawLeftElided(p, paintx + st::msgPadding.left(), painty, textw, width(), 3, style::al_left, 0, -1, 0, false, toDescriptionSelection(selection)); - painty += qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height); - } - if (!_title.isEmpty() || !_description.isEmpty()) { - painty += st::mediaInBubbleSkip; - } - painth -= painty; - } else { - App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); - } - - const auto contextId = _parent->data()->fullId(); - _data->load(contextId); - auto roundRadius = ImageRoundRadius::Large; - auto roundCorners = ((isBubbleTop() && _title.isEmpty() && _description.isEmpty()) ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None) - | (isBubbleBottom() ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None); - auto rthumb = QRect(paintx, painty, paintw, painth); - if (_data && !_data->thumb->isNull()) { - const auto &pix = _data->thumb->pixSingle(contextId, paintw, painth, paintw, painth, roundRadius, roundCorners); - p.drawPixmap(rthumb.topLeft(), pix); - } else { - App::complexLocationRect(p, rthumb, roundRadius, roundCorners); - } - const auto paintMarker = [&](const style::icon &icon) { - icon.paint( - p, - rthumb.x() + ((rthumb.width() - icon.width()) / 2), - rthumb.y() + (rthumb.height() / 2) - icon.height(), - width()); - }; - paintMarker(st::historyMapPoint); - paintMarker(st::historyMapPointInner); - if (selected) { - App::complexOverlayRect(p, rthumb, roundRadius, roundCorners); - } - - if (_parent->media() == this) { - auto fullRight = paintx + paintw; - auto fullBottom = height(); - _parent->drawInfo(p, fullRight, fullBottom, paintx * 2 + paintw, selected, InfoDisplayType::Image); - if (!bubble && _parent->displayRightAction()) { - auto fastShareLeft = (fullRight + st::historyFastShareLeft); - auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); - _parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * paintx + paintw); - } - } -} - -TextState HistoryLocation::textState(QPoint point, StateRequest request) const { - auto result = TextState(_parent); - auto symbolAdd = 0; - - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { - return result; - } - auto paintx = 0, painty = 0, paintw = width(), painth = height(); - bool bubble = _parent->hasBubble(); - - if (bubble) { - if (!_title.isEmpty() || !_description.isEmpty()) { - if (isBubbleTop()) { - painty += st::msgPadding.top(); - } - } - - auto textw = width() - st::msgPadding.left() - st::msgPadding.right(); - - if (!_title.isEmpty()) { - auto titleh = qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height); - if (point.y() >= painty && point.y() < painty + titleh) { - result = TextState(_parent, _title.getStateLeft( - point - QPoint(paintx + st::msgPadding.left(), painty), - textw, - width(), - request.forText())); - return result; - } else if (point.y() >= painty + titleh) { - symbolAdd += _title.length(); - } - painty += titleh; - } - if (!_description.isEmpty()) { - auto descriptionh = qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height); - if (point.y() >= painty && point.y() < painty + descriptionh) { - result = TextState(_parent, _description.getStateLeft( - point - QPoint(paintx + st::msgPadding.left(), painty), - textw, - width(), - request.forText())); - } else if (point.y() >= painty + descriptionh) { - symbolAdd += _description.length(); - } - painty += descriptionh; - } - if (!_title.isEmpty() || !_description.isEmpty()) { - painty += st::mediaInBubbleSkip; - } - painth -= painty; - } - if (QRect(paintx, painty, paintw, painth).contains(point) && _data) { - result.link = _link; - } - if (_parent->media() == this) { - auto fullRight = paintx + paintw; - auto fullBottom = height(); - if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) { - result.cursor = CursorState::Date; - } - if (!bubble && _parent->displayRightAction()) { - auto fastShareLeft = (fullRight + st::historyFastShareLeft); - auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); - if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) { - result.link = _parent->rightActionLink(); - } - } - } - result.symbol += symbolAdd; - return result; -} - -TextSelection HistoryLocation::adjustSelection(TextSelection selection, TextSelectType type) const { - if (_description.isEmpty() || selection.to <= _title.length()) { - return _title.adjustSelection(selection, type); - } - auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type); - if (selection.from >= _title.length()) { - return fromDescriptionSelection(descriptionSelection); - } - auto titleSelection = _title.adjustSelection(selection, type); - return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to }; -} - -TextWithEntities HistoryLocation::selectedText(TextSelection selection) const { - auto titleResult = _title.originalTextWithEntities(selection); - auto descriptionResult = _description.originalTextWithEntities(toDescriptionSelection(selection)); - if (titleResult.text.isEmpty()) { - return descriptionResult; - } else if (descriptionResult.text.isEmpty()) { - return titleResult; - } - titleResult.text += '\n'; - TextUtilities::Append(titleResult, std::move(descriptionResult)); - return titleResult; -} - -bool HistoryLocation::needsBubble() const { - if (!_title.isEmpty() || !_description.isEmpty()) { - return true; - } - const auto item = _parent->data(); - return item->viaBot() - || item->Has<HistoryMessageReply>() - || _parent->displayForwardedFrom() - || _parent->displayFromName(); - return false; -} - -int HistoryLocation::fullWidth() const { - return st::locationSize.width(); -} - -int HistoryLocation::fullHeight() const { - return st::locationSize.height(); -} diff --git a/Telegram/SourceFiles/history/history_media_types.h b/Telegram/SourceFiles/history/history_media_types.h deleted file mode 100644 index 01d20e2e2..000000000 --- a/Telegram/SourceFiles/history/history_media_types.h +++ /dev/null @@ -1,990 +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 - -#include "base/runtime_composer.h" -#include "history/history_media.h" -#include "ui/effects/radial_animation.h" -#include "data/data_document.h" -#include "data/data_photo.h" -#include "data/data_web_page.h" -#include "data/data_game.h" - -class ReplyMarkupClickHandler; -struct HistoryDocumentNamed; -struct HistoryMessageVia; -struct HistoryMessageReply; -struct HistoryMessageForwarded; -struct WebPageCollage; - -namespace Data { -enum class CallFinishReason : char; -struct Invoice; -struct Call; -class Media; -} // namespace Data - -namespace Media { -namespace Clip { -class Playback; -} // namespace Clip - -namespace Player { -class RoundController; -} // namespace Player -} // namespace Media - -namespace Ui { -class EmptyUserpic; -} // namespace Ui - -QString FillAmountAndCurrency(uint64 amount, const QString ¤cy); - -class HistoryFileMedia : public HistoryMedia { -public: - HistoryFileMedia( - not_null<Element*> parent, - not_null<HistoryItem*> realParent) - : HistoryMedia(parent) - , _realParent(realParent) { - } - - bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { - return p == _openl || p == _savel || p == _cancell; - } - bool dragItemByHandler(const ClickHandlerPtr &p) const override { - return p == _openl || p == _savel || p == _cancell; - } - - void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; - void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; - - void refreshParentId(not_null<HistoryItem*> realParent) override; - - bool allowsFastShare() const override { - return true; - } - - ~HistoryFileMedia(); - -protected: - using FileClickHandlerPtr = std::shared_ptr<FileClickHandler>; - - not_null<HistoryItem*> _realParent; - FileClickHandlerPtr _openl, _savel, _cancell; - - void setLinks( - FileClickHandlerPtr &&openl, - FileClickHandlerPtr &&savel, - FileClickHandlerPtr &&cancell); - void setDocumentLinks( - not_null<DocumentData*> document, - not_null<HistoryItem*> realParent, - bool inlinegif = false); - - // >= 0 will contain download / upload string, _statusSize = loaded bytes - // < 0 will contain played string, _statusSize = -(seconds + 1) played - // 0x7FFFFFF0 will contain status for not yet downloaded file - // 0x7FFFFFF1 will contain status for already downloaded file - // 0x7FFFFFF2 will contain status for failed to download / upload file - mutable int _statusSize; - mutable QString _statusText; - - // duration = -1 - no duration, duration = -2 - "GIF" duration - void setStatusSize(int newSize, int fullSize, int duration, qint64 realDuration) const; - - void step_radial(TimeMs ms, bool timer); - void thumbAnimationCallback(); - - void ensureAnimation() const; - void checkAnimationFinished() const; - - bool isRadialAnimation(TimeMs ms) const { - if (!_animation || !_animation->radial.animating()) return false; - - _animation->radial.step(ms); - return _animation && _animation->radial.animating(); - } - bool isThumbAnimation(TimeMs ms) const { - if (_animation) { - if (_animation->a_thumbOver.animating(ms)) { - return true; - } - checkAnimationFinished(); - } - return false; - } - - virtual float64 dataProgress() const = 0; - virtual bool dataFinished() const = 0; - virtual bool dataLoaded() const = 0; - - struct AnimationData { - AnimationData(AnimationCallbacks &&radialCallbacks) - : radial(std::move(radialCallbacks)) { - } - Animation a_thumbOver; - Ui::RadialAnimation radial; - }; - mutable std::unique_ptr<AnimationData> _animation; - -}; - -class HistoryPhoto : public HistoryFileMedia { -public: - HistoryPhoto( - not_null<Element*> parent, - not_null<HistoryItem*> realParent, - not_null<PhotoData*> photo); - HistoryPhoto( - not_null<Element*> parent, - not_null<PeerData*> chat, - not_null<PhotoData*> photo, - int width); - - HistoryMediaType type() const override { - return MediaTypePhoto; - } - - void draw(Painter &p, const QRect &clip, TextSelection selection, TimeMs ms) const override; - TextState textState(QPoint point, StateRequest request) const override; - - [[nodiscard]] TextSelection adjustSelection( - TextSelection selection, - TextSelectType type) const override { - return _caption.adjustSelection(selection, type); - } - uint16 fullSelectionLength() const override { - return _caption.length(); - } - bool hasTextForCopy() const override { - return !_caption.isEmpty(); - } - - TextWithEntities selectedText(TextSelection selection) const override; - - PhotoData *getPhoto() const override { - return _data; - } - - QSize sizeForGrouping() const override; - void drawGrouped( - Painter &p, - const QRect &clip, - TextSelection selection, - TimeMs ms, - const QRect &geometry, - RectParts corners, - not_null<uint64*> cacheKey, - not_null<QPixmap*> cache) const override; - TextState getStateGrouped( - const QRect &geometry, - QPoint point, - StateRequest request) const override; - - TextWithEntities getCaption() const override { - return _caption.originalTextWithEntities(); - } - bool needsBubble() const override; - bool customInfoLayout() const override { - return _caption.isEmpty(); - } - bool skipBubbleTail() const override { - return isBubbleBottom() && _caption.isEmpty(); - } - bool isReadyForOpen() const override { - return _data->loaded(); - } - - void parentTextUpdated() override; - -protected: - float64 dataProgress() const override; - bool dataFinished() const override; - bool dataLoaded() const override; - -private: - void create(FullMsgId contextId, PeerData *chat = nullptr); - - QSize countOptimalSize() override; - QSize countCurrentSize(int newWidth) override; - - bool needInfoDisplay() const; - void validateGroupedCache( - const QRect &geometry, - RectParts corners, - not_null<uint64*> cacheKey, - not_null<QPixmap*> cache) const; - - not_null<PhotoData*> _data; - int _serviceWidth = 0; - int _pixw = 1; - int _pixh = 1; - Text _caption; - -}; - -class HistoryVideo : public HistoryFileMedia { -public: - HistoryVideo( - not_null<Element*> parent, - not_null<HistoryItem*> realParent, - not_null<DocumentData*> document); - - HistoryMediaType type() const override { - return MediaTypeVideo; - } - - void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; - TextState textState(QPoint point, StateRequest request) const override; - - [[nodiscard]] TextSelection adjustSelection( - TextSelection selection, - TextSelectType type) const override { - return _caption.adjustSelection(selection, type); - } - uint16 fullSelectionLength() const override { - return _caption.length(); - } - bool hasTextForCopy() const override { - return !_caption.isEmpty(); - } - - TextWithEntities selectedText(TextSelection selection) const override; - - DocumentData *getDocument() const override { - return _data; - } - - QSize sizeForGrouping() const override; - void drawGrouped( - Painter &p, - const QRect &clip, - TextSelection selection, - TimeMs ms, - const QRect &geometry, - RectParts corners, - not_null<uint64*> cacheKey, - not_null<QPixmap*> cache) const override; - TextState getStateGrouped( - const QRect &geometry, - QPoint point, - StateRequest request) const override; - - bool uploading() const override { - return _data->uploading(); - } - - TextWithEntities getCaption() const override { - return _caption.originalTextWithEntities(); - } - bool needsBubble() const override; - bool customInfoLayout() const override { - return _caption.isEmpty(); - } - bool skipBubbleTail() const override { - return isBubbleBottom() && _caption.isEmpty(); - } - - void parentTextUpdated() override; - -protected: - float64 dataProgress() const override; - bool dataFinished() const override; - bool dataLoaded() const override; - -private: - QSize countOptimalSize() override; - QSize countCurrentSize(int newWidth) override; - - void validateGroupedCache( - const QRect &geometry, - RectParts corners, - not_null<uint64*> cacheKey, - not_null<QPixmap*> cache) const; - void setStatusSize(int newSize) const; - void updateStatusText() const; - - not_null<DocumentData*> _data; - int _thumbw = 1; - int _thumbh = 1; - Text _caption; - -}; - -class HistoryDocument - : public HistoryFileMedia - , public RuntimeComposer<HistoryDocument> { -public: - HistoryDocument( - not_null<Element*> parent, - not_null<DocumentData*> document); - - HistoryMediaType type() const override { - return _data->isVoiceMessage() - ? MediaTypeVoiceFile - : (_data->isSong() - ? MediaTypeMusicFile - : MediaTypeFile); - } - - void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; - TextState textState(QPoint point, StateRequest request) const override; - void updatePressed(QPoint point) override; - - [[nodiscard]] TextSelection adjustSelection( - TextSelection selection, - TextSelectType type) const override; - uint16 fullSelectionLength() const override; - bool hasTextForCopy() const override; - - TextWithEntities selectedText(TextSelection selection) const override; - - bool uploading() const override { - return _data->uploading(); - } - - DocumentData *getDocument() const override { - return _data; - } - - TextWithEntities getCaption() const override; - bool needsBubble() const override { - return true; - } - bool customInfoLayout() const override { - return false; - } - QMargins bubbleMargins() const override; - bool hideForwardedFrom() const override { - return _data->isSong(); - } - - void step_voiceProgress(float64 ms, bool timer); - - void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; - - void refreshParentId(not_null<HistoryItem*> realParent) override; - void parentTextUpdated() override; - -protected: - float64 dataProgress() const override; - bool dataFinished() const override; - bool dataLoaded() const override; - -private: - QSize countOptimalSize() override; - QSize countCurrentSize(int newWidth) override; - - void createComponents(bool caption); - void fillNamedFromData(HistoryDocumentNamed *named); - - void setStatusSize(int newSize, qint64 realDuration = 0) const; - bool updateStatusText() const; // returns showPause - - not_null<DocumentData*> _data; - -}; - -class HistoryGif : public HistoryFileMedia { -public: - HistoryGif( - not_null<Element*> parent, - not_null<DocumentData*> document); - - HistoryMediaType type() const override { - return MediaTypeGif; - } - - void refreshParentId(not_null<HistoryItem*> realParent) override; - - void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; - TextState textState(QPoint point, StateRequest request) const override; - - [[nodiscard]] TextSelection adjustSelection( - TextSelection selection, - TextSelectType type) const override { - return _caption.adjustSelection(selection, type); - } - uint16 fullSelectionLength() const override { - return _caption.length(); - } - bool hasTextForCopy() const override { - return !_caption.isEmpty(); - } - - TextWithEntities selectedText(TextSelection selection) const override; - - bool uploading() const override { - return _data->uploading(); - } - - DocumentData *getDocument() const override { - return _data; - } - - void stopAnimation() override; - - TextWithEntities getCaption() const override { - return _caption.originalTextWithEntities(); - } - bool needsBubble() const override; - bool customInfoLayout() const override { - return _caption.isEmpty(); - } - QString additionalInfoString() const override; - - bool skipBubbleTail() const override { - return isBubbleBottom() && _caption.isEmpty(); - } - bool isReadyForOpen() const override { - return _data->loaded(); - } - - void parentTextUpdated() override; - - ~HistoryGif(); - -protected: - float64 dataProgress() const override; - bool dataFinished() const override; - bool dataLoaded() const override; - - void setClipReader(Media::Clip::ReaderPointer gif); - void clearClipReader() { - setClipReader(Media::Clip::ReaderPointer()); - } - -private: - void playAnimation(bool autoplay) override; - QSize countOptimalSize() override; - QSize countCurrentSize(int newWidth) override; - Media::Player::RoundController *activeRoundVideo() const; - Media::Clip::Reader *activeRoundPlayer() const; - Media::Clip::Reader *currentReader() const; - Media::Clip::Playback *videoPlayback() const; - void clipCallback(Media::Clip::Notification notification); - - bool needInfoDisplay() const; - int additionalWidth( - const HistoryMessageVia *via, - const HistoryMessageReply *reply, - const HistoryMessageForwarded *forwarded) const; - int additionalWidth() const; - QString mediaTypeString() const; - bool isSeparateRoundVideo() const; - - not_null<DocumentData*> _data; - FileClickHandlerPtr _openInMediaviewLink; - int _thumbw = 1; - int _thumbh = 1; - Text _caption; - Media::Clip::ReaderPointer _gif; - - void setStatusSize(int newSize) const; - void updateStatusText() const; - -}; - -class HistorySticker : public HistoryMedia { -public: - HistorySticker( - not_null<Element*> parent, - not_null<DocumentData*> document); - - HistoryMediaType type() const override { - return MediaTypeSticker; - } - - void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; - TextState textState(QPoint point, StateRequest request) const override; - - bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { - return true; - } - bool dragItem() const override { - return true; - } - bool dragItemByHandler(const ClickHandlerPtr &p) const override { - return true; - } - - DocumentData *getDocument() const override { - return _data; - } - - bool needsBubble() const override { - return false; - } - bool customInfoLayout() const override { - return true; - } - QString emoji() const { - return _emoji; - } - -private: - QSize countOptimalSize() override; - QSize countCurrentSize(int newWidth) override; - - bool needInfoDisplay() const; - int additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const; - int additionalWidth() const; - - int _pixw = 1; - int _pixh = 1; - ClickHandlerPtr _packLink; - not_null<DocumentData*> _data; - QString _emoji; - -}; - -class HistoryContact : public HistoryMedia { -public: - HistoryContact( - not_null<Element*> parent, - UserId userId, - const QString &first, - const QString &last, - const QString &phone); - ~HistoryContact(); - - HistoryMediaType type() const override { - return MediaTypeContact; - } - - void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; - TextState textState(QPoint point, StateRequest request) const override; - - bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { - return true; - } - bool dragItemByHandler(const ClickHandlerPtr &p) const override { - return true; - } - - bool needsBubble() const override { - return true; - } - bool customInfoLayout() const override { - return false; - } - - const QString &fname() const { - return _fname; - } - const QString &lname() const { - return _lname; - } - const QString &phone() const { - return _phone; - } - - // Should be called only by Data::Session. - void updateSharedContactUserId(UserId userId) override; - -private: - QSize countOptimalSize() override; - - UserId _userId = 0; - UserData *_contact = nullptr; - - int _phonew = 0; - QString _fname, _lname, _phone; - Text _name; - std::unique_ptr<Ui::EmptyUserpic> _photoEmpty; - - ClickHandlerPtr _linkl; - int _linkw = 0; - QString _link; - -}; - -class HistoryCall : public HistoryMedia { -public: - HistoryCall( - not_null<Element*> parent, - not_null<Data::Call*> call); - - HistoryMediaType type() const override { - return MediaTypeCall; - } - - void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; - TextState textState(QPoint point, StateRequest request) const override; - - bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { - return true; - } - bool dragItemByHandler(const ClickHandlerPtr &p) const override { - return false; - } - - bool needsBubble() const override { - return true; - } - bool customInfoLayout() const override { - return true; - } - - Data::CallFinishReason reason() const; - -private: - using FinishReason = Data::CallFinishReason; - - QSize countOptimalSize() override; - - FinishReason _reason; - int _duration = 0; - - QString _text; - QString _status; - - ClickHandlerPtr _link; - -}; - -class HistoryWebPage : public HistoryMedia { -public: - HistoryWebPage( - not_null<Element*> parent, - not_null<WebPageData*> data); - - HistoryMediaType type() const override { - return MediaTypeWebPage; - } - - void refreshParentId(not_null<HistoryItem*> realParent) override; - - void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; - TextState textState(QPoint point, StateRequest request) const override; - - bool hideMessageText() const override { - return false; - } - - [[nodiscard]] TextSelection adjustSelection( - TextSelection selection, - TextSelectType type) const override; - uint16 fullSelectionLength() const override { - return _title.length() + _description.length(); - } - bool hasTextForCopy() const override { - return false; // we do not add _title and _description in FullSelection text copy. - } - - bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { - return _attach && _attach->toggleSelectionByHandlerClick(p); - } - bool dragItemByHandler(const ClickHandlerPtr &p) const override { - return _attach && _attach->dragItemByHandler(p); - } - - TextWithEntities selectedText(TextSelection selection) const override; - - void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; - void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; - - bool isDisplayed() const override; - PhotoData *getPhoto() const override { - return _attach ? _attach->getPhoto() : nullptr; - } - DocumentData *getDocument() const override { - return _attach ? _attach->getDocument() : nullptr; - } - void stopAnimation() override { - if (_attach) _attach->stopAnimation(); - } - - not_null<WebPageData*> webpage() { - return _data; - } - - bool needsBubble() const override { - return true; - } - bool customInfoLayout() const override { - return false; - } - bool allowsFastShare() const override { - return true; - } - - HistoryMedia *attach() const { - return _attach.get(); - } - - ~HistoryWebPage(); - -private: - void playAnimation(bool autoplay) override; - QSize countOptimalSize() override; - QSize countCurrentSize(int newWidth) override; - - TextSelection toDescriptionSelection(TextSelection selection) const; - TextSelection fromDescriptionSelection(TextSelection selection) const; - QMargins inBubblePadding() const; - int bottomInfoPadding() const; - bool isLogEntryOriginal() const; - - not_null<WebPageData*> _data; - std::vector<std::unique_ptr<Data::Media>> _collage; - ClickHandlerPtr _openl; - std::unique_ptr<HistoryMedia> _attach; - - bool _asArticle = false; - int _dataVersion = -1; - int _titleLines = 0; - int _descriptionLines = 0; - - Text _title, _description; - int _siteNameWidth = 0; - - QString _duration; - int _durationWidth = 0; - - int _pixw = 0; - int _pixh = 0; - -}; - -class HistoryGame : public HistoryMedia { -public: - HistoryGame( - not_null<Element*> parent, - not_null<GameData*> data, - const TextWithEntities &consumed); - - HistoryMediaType type() const override { - return MediaTypeGame; - } - - void refreshParentId(not_null<HistoryItem*> realParent) override; - - void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; - TextState textState(QPoint point, StateRequest request) const override; - - [[nodiscard]] TextSelection adjustSelection( - TextSelection selection, - TextSelectType type) const override; - uint16 fullSelectionLength() const override { - return _title.length() + _description.length(); - } - bool hasTextForCopy() const override { - return false; // we do not add _title and _description in FullSelection text copy. - } - - bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { - return _attach && _attach->toggleSelectionByHandlerClick(p); - } - bool dragItemByHandler(const ClickHandlerPtr &p) const override { - return _attach && _attach->dragItemByHandler(p); - } - - TextWithEntities selectedText(TextSelection selection) const override; - - void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; - void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; - - PhotoData *getPhoto() const override { - return _attach ? _attach->getPhoto() : nullptr; - } - DocumentData *getDocument() const override { - return _attach ? _attach->getDocument() : nullptr; - } - void stopAnimation() override { - if (_attach) _attach->stopAnimation(); - } - - not_null<GameData*> game() { - return _data; - } - - bool needsBubble() const override { - return true; - } - bool customInfoLayout() const override { - return false; - } - bool allowsFastShare() const override { - return true; - } - - HistoryMedia *attach() const { - return _attach.get(); - } - - void parentTextUpdated() override; - - ~HistoryGame(); - -private: - void playAnimation(bool autoplay) override; - QSize countOptimalSize() override; - QSize countCurrentSize(int newWidth) override; - - TextSelection toDescriptionSelection(TextSelection selection) const; - TextSelection fromDescriptionSelection(TextSelection selection) const; - QMargins inBubblePadding() const; - int bottomInfoPadding() const; - - not_null<GameData*> _data; - std::shared_ptr<ReplyMarkupClickHandler> _openl; - std::unique_ptr<HistoryMedia> _attach; - - int _titleLines, _descriptionLines; - - Text _title, _description; - - int _gameTagWidth = 0; - -}; - -class HistoryInvoice : public HistoryMedia { -public: - HistoryInvoice( - not_null<Element*> parent, - not_null<Data::Invoice*> invoice); - - HistoryMediaType type() const override { - return MediaTypeInvoice; - } - - void refreshParentId(not_null<HistoryItem*> realParent) override; - - MsgId getReceiptMsgId() const { - return _receiptMsgId; - } - QString getTitle() const { - return _title.originalText(); - } - - bool hideMessageText() const override { - return false; - } - - void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; - TextState textState(QPoint point, StateRequest request) const override; - - [[nodiscard]] TextSelection adjustSelection( - TextSelection selection, - TextSelectType type) const override; - uint16 fullSelectionLength() const override { - return _title.length() + _description.length(); - } - bool hasTextForCopy() const override { - return false; // we do not add _title and _description in FullSelection text copy. - } - - bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { - return _attach && _attach->toggleSelectionByHandlerClick(p); - } - bool dragItemByHandler(const ClickHandlerPtr &p) const override { - return _attach && _attach->dragItemByHandler(p); - } - - TextWithEntities selectedText(TextSelection selection) const override; - - void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; - void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; - - bool needsBubble() const override { - return true; - } - bool customInfoLayout() const override { - return false; - } - - HistoryMedia *attach() const { - return _attach.get(); - } - -private: - QSize countOptimalSize() override; - QSize countCurrentSize(int newWidth) override; - - void fillFromData(not_null<Data::Invoice*> invoice); - - TextSelection toDescriptionSelection(TextSelection selection) const; - TextSelection fromDescriptionSelection(TextSelection selection) const; - QMargins inBubblePadding() const; - int bottomInfoPadding() const; - - std::unique_ptr<HistoryMedia> _attach; - - int _titleHeight = 0; - int _descriptionHeight = 0; - Text _title; - Text _description; - Text _status; - - MsgId _receiptMsgId = 0; - -}; - -class LocationCoords; -struct LocationData; - -class HistoryLocation : public HistoryMedia { -public: - HistoryLocation( - not_null<Element*> parent, - not_null<LocationData*> location, - const QString &title = QString(), - const QString &description = QString()); - - HistoryMediaType type() const override { - return MediaTypeLocation; - } - - void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; - TextState textState(QPoint point, StateRequest request) const override; - - [[nodiscard]] TextSelection adjustSelection( - TextSelection selection, - TextSelectType type) const override; - uint16 fullSelectionLength() const override { - return _title.length() + _description.length(); - } - bool hasTextForCopy() const override { - return !_title.isEmpty() || !_description.isEmpty(); - } - - bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { - return p == _link; - } - bool dragItemByHandler(const ClickHandlerPtr &p) const override { - return p == _link; - } - - TextWithEntities selectedText(TextSelection selection) const override; - - bool needsBubble() const override; - bool customInfoLayout() const override { - return true; - } - - bool skipBubbleTail() const override { - return isBubbleBottom(); - } - -private: - QSize countOptimalSize() override; - QSize countCurrentSize(int newWidth) override; - - TextSelection toDescriptionSelection(TextSelection selection) const; - TextSelection fromDescriptionSelection(TextSelection selection) const; - - LocationData *_data; - Text _title, _description; - ClickHandlerPtr _link; - - int fullWidth() const; - int fullHeight() const; - -}; diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 484eedfce..f2e6b57be 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -14,7 +14,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item_components.h" #include "history/history_location_manager.h" -#include "history/history_media_types.h" #include "history/history_service.h" #include "history/view/history_view_service_message.h" #include "auth_session.h" @@ -29,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "observer_peer.h" #include "storage/storage_shared_media.h" #include "data/data_session.h" +#include "data/data_game.h" #include "data/data_media_types.h" #include "styles/style_dialogs.h" #include "styles/style_widgets.h" diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index 9770d52f8..083cfaec2 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -13,13 +13,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "layout.h" #include "history/history.h" -#include "history/history_media_types.h" +#include "history/media/history_media_invoice.h" #include "history/history_message.h" #include "history/history_item_components.h" #include "history/view/history_view_service_message.h" #include "data/data_feed.h" #include "data/data_session.h" #include "data/data_media_types.h" +#include "data/data_game.h" #include "window/notifications_manager.h" #include "window/window_controller.h" #include "storage/storage_shared_media.h" diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 6e295c62b..9f60aceff 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -26,11 +26,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "inline_bots/inline_bot_result.h" #include "data/data_drafts.h" #include "data/data_session.h" +#include "data/data_web_page.h" +#include "data/data_document.h" +#include "data/data_photo.h" #include "data/data_media_types.h" #include "history/history.h" #include "history/history_item.h" #include "history/history_message.h" -#include "history/history_media_types.h" +#include "history/media/history_media.h" #include "history/history_drag_area.h" #include "history/history_inner_widget.h" #include "history/history_item_components.h" diff --git a/Telegram/SourceFiles/history/history_media.cpp b/Telegram/SourceFiles/history/media/history_media.cpp similarity index 97% rename from Telegram/SourceFiles/history/history_media.cpp rename to Telegram/SourceFiles/history/media/history_media.cpp index b540556d8..31a31d066 100644 --- a/Telegram/SourceFiles/history/history_media.cpp +++ b/Telegram/SourceFiles/history/media/history_media.cpp @@ -5,7 +5,7 @@ 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/history_media.h" +#include "history/media/history_media.h" #include "history/history_item.h" #include "history/view/history_view_element.h" diff --git a/Telegram/SourceFiles/history/history_media.h b/Telegram/SourceFiles/history/media/history_media.h similarity index 100% rename from Telegram/SourceFiles/history/history_media.h rename to Telegram/SourceFiles/history/media/history_media.h diff --git a/Telegram/SourceFiles/history/media/history_media_call.cpp b/Telegram/SourceFiles/history/media/history_media_call.cpp new file mode 100644 index 000000000..c4d63305c --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_call.cpp @@ -0,0 +1,111 @@ +/* +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/media/history_media_call.h" + +#include "lang/lang_keys.h" +#include "layout.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_cursor_state.h" +#include "calls/calls_instance.h" +#include "data/data_media_types.h" +#include "styles/style_history.h" + +namespace { + +using TextState = HistoryView::TextState; + +} // namespace + +HistoryCall::HistoryCall( + not_null<Element*> parent, + not_null<Data::Call*> call) +: HistoryMedia(parent) { + _duration = call->duration; + _reason = call->finishReason; + + const auto item = parent->data(); + _text = Data::MediaCall::Text(item, _reason); + _status = parent->dateTime().time().toString(cTimeFormat()); + if (_duration) { + if (_reason != FinishReason::Missed + && _reason != FinishReason::Busy) { + _status = lng_call_duration_info( + lt_time, + _status, + lt_duration, + formatDurationWords(_duration)); + } else { + _duration = 0; + } + } +} + +QSize HistoryCall::countOptimalSize() { + const auto user = _parent->data()->history()->peer->asUser(); + _link = std::make_shared<LambdaClickHandler>([=] { + if (user) { + Calls::Current().startOutgoingCall(user); + } + }); + + auto maxWidth = st::historyCallWidth; + auto minHeight = st::historyCallHeight; + if (!isBubbleTop()) { + minHeight -= st::msgFileTopMinus; + } + return { maxWidth, minHeight }; +} + +void HistoryCall::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; + auto paintx = 0, painty = 0, paintw = width(), painth = height(); + + auto outbg = _parent->hasOutLayout(); + auto selected = (selection == FullSelection); + + accumulate_min(paintw, maxWidth()); + + auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0; + auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus; + + nameleft = st::historyCallLeft; + nametop = st::historyCallTop - topMinus; + nameright = st::msgFilePadding.left(); + statustop = st::historyCallStatusTop - topMinus; + + auto namewidth = paintw - nameleft - nameright; + + p.setFont(st::semiboldFont); + p.setPen(outbg ? (selected ? st::historyFileNameOutFgSelected : st::historyFileNameOutFg) : (selected ? st::historyFileNameInFgSelected : st::historyFileNameInFg)); + p.drawTextLeft(nameleft, nametop, paintw, _text); + + auto statusleft = nameleft; + auto missed = (_reason == FinishReason::Missed || _reason == FinishReason::Busy); + auto &arrow = outbg ? (selected ? st::historyCallArrowOutSelected : st::historyCallArrowOut) : missed ? (selected ? st::historyCallArrowMissedInSelected : st::historyCallArrowMissedIn) : (selected ? st::historyCallArrowInSelected : st::historyCallArrowIn); + arrow.paint(p, statusleft + st::historyCallArrowPosition.x(), statustop + st::historyCallArrowPosition.y(), paintw); + statusleft += arrow.width() + st::historyCallStatusSkip; + + auto &statusFg = outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg); + p.setFont(st::normalFont); + p.setPen(statusFg); + p.drawTextLeft(statusleft, statustop, paintw, _status); + + auto &icon = outbg ? (selected ? st::historyCallOutIconSelected : st::historyCallOutIcon) : (selected ? st::historyCallInIconSelected : st::historyCallInIcon); + icon.paint(p, paintw - st::historyCallIconPosition.x() - icon.width(), st::historyCallIconPosition.y() - topMinus, paintw); +} + +TextState HistoryCall::textState(QPoint point, StateRequest request) const { + auto result = TextState(_parent); + if (QRect(0, 0, width(), height()).contains(point)) { + result.link = _link; + return result; + } + return result; +} diff --git a/Telegram/SourceFiles/history/media/history_media_call.h b/Telegram/SourceFiles/history/media/history_media_call.h new file mode 100644 index 000000000..bb6bcc358 --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_call.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 "history/media/history_media.h" + +namespace Data { +enum class CallFinishReason : char; +struct Call; +} // namespace Data + +class HistoryCall : public HistoryMedia { +public: + HistoryCall( + not_null<Element*> parent, + not_null<Data::Call*> call); + + HistoryMediaType type() const override { + return MediaTypeCall; + } + + void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; + TextState textState(QPoint point, StateRequest request) const override; + + bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { + return true; + } + bool dragItemByHandler(const ClickHandlerPtr &p) const override { + return false; + } + + bool needsBubble() const override { + return true; + } + bool customInfoLayout() const override { + return true; + } + + Data::CallFinishReason reason() const; + +private: + using FinishReason = Data::CallFinishReason; + + QSize countOptimalSize() override; + + FinishReason _reason; + int _duration = 0; + + QString _text; + QString _status; + + ClickHandlerPtr _link; + +}; diff --git a/Telegram/SourceFiles/history/media/history_media_common.cpp b/Telegram/SourceFiles/history/media/history_media_common.cpp new file mode 100644 index 000000000..a1a2d3ef1 --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_common.cpp @@ -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 +*/ +#include "history/media/history_media_common.h" + +#include "layout.h" +#include "data/data_document.h" +#include "history/view/history_view_element.h" +#include "history/media/history_media_grouped.h" +#include "history/media/history_media_photo.h" +#include "history/media/history_media_gif.h" +#include "history/media/history_media_document.h" +#include "history/media/history_media_sticker.h" +#include "history/media/history_media_video.h" + +int documentMaxStatusWidth(DocumentData *document) { + auto result = st::normalFont->width(formatDownloadText(document->size, document->size)); + if (const auto song = document->song()) { + accumulate_max(result, st::normalFont->width(formatPlayedText(song->duration, song->duration))); + accumulate_max(result, st::normalFont->width(formatDurationAndSizeText(song->duration, document->size))); + } else if (const auto voice = document->voice()) { + accumulate_max(result, st::normalFont->width(formatPlayedText(voice->duration, voice->duration))); + accumulate_max(result, st::normalFont->width(formatDurationAndSizeText(voice->duration, document->size))); + } else if (document->isVideoFile()) { + accumulate_max(result, st::normalFont->width(formatDurationAndSizeText(document->duration(), document->size))); + } else { + accumulate_max(result, st::normalFont->width(formatSizeText(document->size))); + } + return result; +} + +void PaintInterpolatedIcon( + Painter &p, + const style::icon &a, + const style::icon &b, + float64 b_ratio, + QRect rect) { + PainterHighQualityEnabler hq(p); + p.save(); + p.translate(rect.center()); + p.setOpacity(b_ratio); + p.scale(b_ratio, b_ratio); + b.paintInCenter(p, rect.translated(-rect.center())); + p.restore(); + + p.save(); + p.translate(rect.center()); + p.setOpacity(1. - b_ratio); + p.scale(1. - b_ratio, 1. - b_ratio); + a.paintInCenter(p, rect.translated(-rect.center())); + p.restore(); +} + +std::unique_ptr<HistoryMedia> CreateAttach( + not_null<HistoryView::Element*> parent, + DocumentData *document, + PhotoData *photo, + const std::vector<std::unique_ptr<Data::Media>> &collage) { + if (!collage.empty()) { + return std::make_unique<HistoryGroupedMedia>(parent, collage); + } else if (document) { + if (document->sticker()) { + return std::make_unique<HistorySticker>(parent, document); + } else if (document->isAnimation()) { + return std::make_unique<HistoryGif>( + parent, + document); + } else if (document->isVideoFile()) { + return std::make_unique<HistoryVideo>( + parent, + parent->data(), + document); + } + return std::make_unique<HistoryDocument>( + parent, + document); + } else if (photo) { + return std::make_unique<HistoryPhoto>( + parent, + parent->data(), + photo); + } + return nullptr; +} + +int unitedLineHeight() { + return qMax(st::webPageTitleFont->height, st::webPageDescriptionFont->height); +} diff --git a/Telegram/SourceFiles/history/media/history_media_common.h b/Telegram/SourceFiles/history/media/history_media_common.h new file mode 100644 index 000000000..57d952733 --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_common.h @@ -0,0 +1,36 @@ +/* +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 HistoryView { +class Element; +} // namespace HistoryView + +namespace Data { +class Media; +} // namespace Data + +class DocumentData; +class PhotoData; +class HistoryMedia; + +int documentMaxStatusWidth(DocumentData *document); + +void PaintInterpolatedIcon( + Painter &p, + const style::icon &a, + const style::icon &b, + float64 b_ratio, + QRect rect); + +std::unique_ptr<HistoryMedia> CreateAttach( + not_null<HistoryView::Element*> parent, + DocumentData *document, + PhotoData *photo, + const std::vector<std::unique_ptr<Data::Media>> &collage = {}); +int unitedLineHeight(); diff --git a/Telegram/SourceFiles/history/media/history_media_contact.cpp b/Telegram/SourceFiles/history/media/history_media_contact.cpp new file mode 100644 index 000000000..fba2194a7 --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_contact.cpp @@ -0,0 +1,216 @@ +/* +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/media/history_media_contact.h" + +#include "lang/lang_keys.h" +#include "layout.h" +#include "mainwindow.h" +#include "auth_session.h" +#include "boxes/add_contact_box.h" +#include "history/history_item_components.h" +#include "history/history_item.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_cursor_state.h" +#include "window/window_controller.h" +#include "ui/empty_userpic.h" +#include "ui/text_options.h" +#include "data/data_session.h" +#include "data/data_media_types.h" +#include "styles/style_history.h" + +namespace { + +using TextState = HistoryView::TextState; + +} // namespace + +namespace { + +ClickHandlerPtr sendMessageClickHandler(PeerData *peer) { + return std::make_shared<LambdaClickHandler>([peer] { + App::wnd()->controller()->showPeerHistory( + peer->id, + Window::SectionShow::Way::Forward); + }); +} + +ClickHandlerPtr addContactClickHandler(not_null<HistoryItem*> item) { + return std::make_shared<LambdaClickHandler>([fullId = item->fullId()] { + if (const auto item = App::histItemById(fullId)) { + if (const auto media = item->media()) { + if (const auto contact = media->sharedContact()) { + Ui::show(Box<AddContactBox>( + contact->firstName, + contact->lastName, + contact->phoneNumber)); + } + } + } + }); +} + +} // namespace + +HistoryContact::HistoryContact( + not_null<Element*> parent, + UserId userId, + const QString &first, + const QString &last, + const QString &phone) +: HistoryMedia(parent) +, _userId(userId) +, _fname(first) +, _lname(last) +, _phone(App::formatPhone(phone)) { + Auth().data().registerContactView(userId, parent); + + _name.setText( + st::semiboldTextStyle, + lng_full_name(lt_first_name, first, lt_last_name, last).trimmed(), + Ui::NameTextOptions()); + _phonew = st::normalFont->width(_phone); +} + +HistoryContact::~HistoryContact() { + Auth().data().unregisterContactView(_userId, _parent); +} + +void HistoryContact::updateSharedContactUserId(UserId userId) { + if (_userId != userId) { + Auth().data().unregisterContactView(_userId, _parent); + _userId = userId; + Auth().data().registerContactView(_userId, _parent); + } +} + +QSize HistoryContact::countOptimalSize() { + const auto item = _parent->data(); + auto maxWidth = st::msgFileMinWidth; + + _contact = _userId ? App::userLoaded(_userId) : nullptr; + if (_contact) { + _contact->loadUserpic(); + } else { + _photoEmpty = std::make_unique<Ui::EmptyUserpic>( + Data::PeerUserpicColor(_userId ? _userId : _parent->data()->id), + _name.originalText()); + } + if (_contact + && _contact->contactStatus() == UserData::ContactStatus::Contact) { + _linkl = sendMessageClickHandler(_contact); + _link = lang(lng_profile_send_message).toUpper(); + } else if (_userId) { + _linkl = addContactClickHandler(_parent->data()); + _link = lang(lng_profile_add_contact).toUpper(); + } + _linkw = _link.isEmpty() ? 0 : st::semiboldFont->width(_link); + + auto tleft = 0; + auto tright = 0; + if (_userId) { + tleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); + tright = st::msgFileThumbPadding.left(); + accumulate_max(maxWidth, tleft + _phonew + tright); + } else { + tleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); + tright = st::msgFileThumbPadding.left(); + accumulate_max(maxWidth, tleft + _phonew + _parent->skipBlockWidth() + st::msgPadding.right()); + } + + accumulate_max(maxWidth, tleft + _name.maxWidth() + tright); + accumulate_min(maxWidth, st::msgMaxWidth); + auto minHeight = 0; + if (_userId) { + minHeight = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom(); + if (item->Has<HistoryMessageSigned>() + || item->Has<HistoryMessageViews>()) { + minHeight += st::msgDateFont->height - st::msgDateDelta.y(); + } + } else { + minHeight = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom(); + } + if (!isBubbleTop()) { + minHeight -= st::msgFileTopMinus; + } + return { maxWidth, minHeight }; +} + +void HistoryContact::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; + auto paintx = 0, painty = 0, paintw = width(), painth = height(); + + auto outbg = _parent->hasOutLayout(); + bool selected = (selection == FullSelection); + + accumulate_min(paintw, maxWidth()); + + auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0; + auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus; + if (_userId) { + nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); + nametop = st::msgFileThumbNameTop - topMinus; + nameright = st::msgFileThumbPadding.left(); + statustop = st::msgFileThumbStatusTop - topMinus; + linktop = st::msgFileThumbLinkTop - topMinus; + + QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, paintw)); + if (_contact) { + _contact->paintUserpic(p, rthumb.x(), rthumb.y(), st::msgFileThumbSize); + } else { + _photoEmpty->paint(p, st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, paintw, st::msgFileThumbSize); + } + if (selected) { + PainterHighQualityEnabler hq(p); + p.setBrush(p.textPalette().selectOverlay); + p.setPen(Qt::NoPen); + p.drawEllipse(rthumb); + } + + bool over = ClickHandler::showAsActive(_linkl); + 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, paintw, _link, _linkw); + } else { + nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); + nametop = st::msgFileNameTop - topMinus; + nameright = st::msgFilePadding.left(); + statustop = st::msgFileStatusTop - topMinus; + + _photoEmpty->paint(p, st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, paintw, st::msgFileSize); + } + auto namewidth = paintw - nameleft - nameright; + + p.setFont(st::semiboldFont); + p.setPen(outbg ? (selected ? st::historyFileNameOutFgSelected : st::historyFileNameOutFg) : (selected ? st::historyFileNameInFgSelected : st::historyFileNameInFg)); + _name.drawLeftElided(p, nameleft, nametop, namewidth, paintw); + + auto &status = outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg); + p.setFont(st::normalFont); + p.setPen(status); + p.drawTextLeft(nameleft, statustop, paintw, _phone); +} + +TextState HistoryContact::textState(QPoint point, StateRequest request) const { + auto result = TextState(_parent); + + auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0; + auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus; + if (_userId) { + nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); + linktop = st::msgFileThumbLinkTop - topMinus; + if (rtlrect(nameleft, linktop, _linkw, st::semiboldFont->height, width()).contains(point)) { + result.link = _linkl; + return result; + } + } + if (QRect(0, 0, width(), height()).contains(point) && _contact) { + result.link = _contact->openLink(); + return result; + } + return result; +} diff --git a/Telegram/SourceFiles/history/media/history_media_contact.h b/Telegram/SourceFiles/history/media/history_media_contact.h new file mode 100644 index 000000000..906241849 --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_contact.h @@ -0,0 +1,71 @@ +/* +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/media/history_media.h" + +class HistoryContact : public HistoryMedia { +public: + HistoryContact( + not_null<Element*> parent, + UserId userId, + const QString &first, + const QString &last, + const QString &phone); + ~HistoryContact(); + + HistoryMediaType type() const override { + return MediaTypeContact; + } + + void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; + TextState textState(QPoint point, StateRequest request) const override; + + bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { + return true; + } + bool dragItemByHandler(const ClickHandlerPtr &p) const override { + return true; + } + + bool needsBubble() const override { + return true; + } + bool customInfoLayout() const override { + return false; + } + + const QString &fname() const { + return _fname; + } + const QString &lname() const { + return _lname; + } + const QString &phone() const { + return _phone; + } + + // Should be called only by Data::Session. + void updateSharedContactUserId(UserId userId) override; + +private: + QSize countOptimalSize() override; + + UserId _userId = 0; + UserData *_contact = nullptr; + + int _phonew = 0; + QString _fname, _lname, _phone; + Text _name; + std::unique_ptr<Ui::EmptyUserpic> _photoEmpty; + + ClickHandlerPtr _linkl; + int _linkw = 0; + QString _link; + +}; diff --git a/Telegram/SourceFiles/history/media/history_media_document.cpp b/Telegram/SourceFiles/history/media/history_media_document.cpp new file mode 100644 index 000000000..581c0eb42 --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_document.cpp @@ -0,0 +1,770 @@ +/* +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/media/history_media_document.h" + +#include "lang/lang_keys.h" +#include "layout.h" +#include "auth_session.h" +#include "storage/localstorage.h" +#include "media/media_audio.h" +#include "media/player/media_player_instance.h" +#include "history/history_item_components.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_cursor_state.h" +#include "history/media/history_media_common.h" +#include "ui/image/image.h" +#include "data/data_session.h" +#include "data/data_document.h" +#include "data/data_media_types.h" +#include "styles/style_history.h" + +namespace { + +using TextState = HistoryView::TextState; + +} // namespace + +HistoryDocument::HistoryDocument( + not_null<Element*> parent, + not_null<DocumentData*> document) +: HistoryFileMedia(parent, parent->data()) +, _data(document) { + const auto item = parent->data(); + auto caption = createCaption(item); + + createComponents(!caption.isEmpty()); + if (auto named = Get<HistoryDocumentNamed>()) { + fillNamedFromData(named); + } + + setDocumentLinks(_data, item); + + setStatusSize(FileStatusSizeReady); + + if (auto captioned = Get<HistoryDocumentCaptioned>()) { + captioned->_caption = std::move(caption); + } +} + +HistoryMediaType HistoryDocument::type() const { + return _data->isVoiceMessage() + ? MediaTypeVoiceFile + : (_data->isSong() + ? MediaTypeMusicFile + : MediaTypeFile); +} + +float64 HistoryDocument::dataProgress() const { + return _data->progress(); +} + +bool HistoryDocument::dataFinished() const { + return !_data->loading() && !_data->uploading(); +} + +bool HistoryDocument::dataLoaded() const { + return _data->loaded(); +} + +void HistoryDocument::createComponents(bool caption) { + uint64 mask = 0; + if (_data->isVoiceMessage()) { + mask |= HistoryDocumentVoice::Bit(); + } else { + mask |= HistoryDocumentNamed::Bit(); + if (!_data->isSong() + && !_data->thumb->isNull() + && _data->thumb->width() + && _data->thumb->height() + && !Data::IsExecutableName(_data->filename())) { + mask |= HistoryDocumentThumbed::Bit(); + } + } + if (caption) { + mask |= HistoryDocumentCaptioned::Bit(); + } + UpdateComponents(mask); + if (auto thumbed = Get<HistoryDocumentThumbed>()) { + thumbed->_linksavel = std::make_shared<DocumentSaveClickHandler>( + _data, + _parent->data()->fullId()); + thumbed->_linkcancell = std::make_shared<DocumentCancelClickHandler>( + _data, + _parent->data()->fullId()); + } + if (auto voice = Get<HistoryDocumentVoice>()) { + voice->_seekl = std::make_shared<VoiceSeekClickHandler>( + _data, + _parent->data()->fullId()); + } +} + +void HistoryDocument::fillNamedFromData(HistoryDocumentNamed *named) { + auto nameString = named->_name = _data->composeNameString(); + named->_namew = st::semiboldFont->width(nameString); +} + +QSize HistoryDocument::countOptimalSize() { + const auto item = _parent->data(); + + auto captioned = Get<HistoryDocumentCaptioned>(); + if (_parent->media() != this) { + if (captioned) { + RemoveComponents(HistoryDocumentCaptioned::Bit()); + captioned = nullptr; + } + } else if (captioned && captioned->_caption.hasSkipBlock()) { + captioned->_caption.updateSkipBlock( + _parent->skipBlockWidth(), + _parent->skipBlockHeight()); + } + auto thumbed = Get<HistoryDocumentThumbed>(); + if (thumbed) { + _data->thumb->load(_realParent->fullId()); + auto tw = ConvertScale(_data->thumb->width()); + auto th = ConvertScale(_data->thumb->height()); + if (tw > th) { + thumbed->_thumbw = (tw * st::msgFileThumbSize) / th; + } else { + thumbed->_thumbw = st::msgFileThumbSize; + } + } + + auto maxWidth = st::msgFileMinWidth; + + auto tleft = 0; + auto tright = 0; + if (thumbed) { + tleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); + tright = st::msgFileThumbPadding.left(); + accumulate_max(maxWidth, tleft + documentMaxStatusWidth(_data) + tright); + } else { + tleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); + tright = st::msgFileThumbPadding.left(); + auto unread = _data->isVoiceMessage() ? (st::mediaUnreadSkip + st::mediaUnreadSize) : 0; + accumulate_max(maxWidth, tleft + documentMaxStatusWidth(_data) + unread + _parent->skipBlockWidth() + st::msgPadding.right()); + } + + if (auto named = Get<HistoryDocumentNamed>()) { + accumulate_max(maxWidth, tleft + named->_namew + tright); + accumulate_min(maxWidth, st::msgMaxWidth); + } + + auto minHeight = 0; + if (thumbed) { + minHeight = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom(); + } else { + minHeight = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom(); + } + if (!captioned && (item->Has<HistoryMessageSigned>() + || item->Has<HistoryMessageViews>() + || _parent->displayEditedBadge())) { + minHeight += st::msgDateFont->height - st::msgDateDelta.y(); + } + if (!isBubbleTop()) { + minHeight -= st::msgFileTopMinus; + } + + if (captioned) { + auto captionw = maxWidth + - st::msgPadding.left() + - st::msgPadding.right(); + minHeight += captioned->_caption.countHeight(captionw); + if (isBubbleBottom()) { + minHeight += st::msgPadding.bottom(); + } + } + return { maxWidth, minHeight }; +} + +QSize HistoryDocument::countCurrentSize(int newWidth) { + auto captioned = Get<HistoryDocumentCaptioned>(); + if (!captioned) { + return HistoryFileMedia::countCurrentSize(newWidth); + } + + accumulate_min(newWidth, maxWidth()); + auto newHeight = 0; + if (Get<HistoryDocumentThumbed>()) { + newHeight = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom(); + } else { + newHeight = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom(); + } + if (!isBubbleTop()) { + newHeight -= st::msgFileTopMinus; + } + auto captionw = newWidth - st::msgPadding.left() - st::msgPadding.right(); + newHeight += captioned->_caption.countHeight(captionw); + if (isBubbleBottom()) { + newHeight += st::msgPadding.bottom(); + } + + return { newWidth, newHeight }; +} + +void HistoryDocument::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; + + _data->automaticLoad(_realParent->fullId(), _parent->data()); + bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); + bool selected = (selection == FullSelection); + + int captionw = width() - st::msgPadding.left() - st::msgPadding.right(); + auto outbg = _parent->hasOutLayout(); + + if (displayLoading) { + ensureAnimation(); + if (!_animation->radial.animating()) { + _animation->radial.start(_data->progress()); + } + } + bool showPause = updateStatusText(); + bool radial = isRadialAnimation(ms); + + auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus; + int nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0, bottom = 0; + if (auto thumbed = Get<HistoryDocumentThumbed>()) { + nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); + nametop = st::msgFileThumbNameTop - topMinus; + nameright = st::msgFileThumbPadding.left(); + statustop = st::msgFileThumbStatusTop - topMinus; + linktop = st::msgFileThumbLinkTop - topMinus; + bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom() - topMinus; + + auto inWebPage = (_parent->media() != this); + auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large; + QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, width())); + QPixmap thumb; + if (loaded) { + thumb = _data->thumb->pixSingle(_realParent->fullId(), thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize, roundRadius); + } else { + thumb = _data->thumb->pixBlurredSingle(_realParent->fullId(), thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize, roundRadius); + } + p.drawPixmap(rthumb.topLeft(), thumb); + if (selected) { + auto overlayCorners = inWebPage ? SelectedOverlaySmallCorners : SelectedOverlayLargeCorners; + App::roundRect(p, rthumb, p.textPalette().selectOverlay, overlayCorners); + } + + if (radial || (!loaded && !_data->loading())) { + float64 radialOpacity = (radial && loaded && !_data->uploading()) ? _animation->radial.opacity() : 1; + QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); + p.setPen(Qt::NoPen); + if (selected) { + p.setBrush(st::msgDateImgBgSelected); + } else if (isThumbAnimation(ms)) { + auto over = _animation->a_thumbOver.current(); + p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over)); + } else { + auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); + p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); + } + p.setOpacity(radialOpacity * p.opacity()); + + { + PainterHighQualityEnabler hq(p); + p.drawEllipse(inner); + } + + p.setOpacity(radialOpacity); + auto icon = ([radial, this, selected] { + if (radial || _data->loading()) { + return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); + } + return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); + })(); + p.setOpacity((radial && loaded) ? _animation->radial.opacity() : 1); + icon->paintInCenter(p, inner); + if (radial) { + p.setOpacity(1); + + QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); + _animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg); + } + } + + if (_data->status != FileUploadFailed) { + const auto &lnk = (_data->loading() || _data->uploading()) + ? thumbed->_linkcancell + : thumbed->_linksavel; + 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); + } + } else { + nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); + nametop = st::msgFileNameTop - topMinus; + nameright = st::msgFilePadding.left(); + statustop = st::msgFileStatusTop - topMinus; + bottom = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() - topMinus; + + QRect inner(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); + } else if (isThumbAnimation(ms)) { + auto over = _animation->a_thumbOver.current(); + p.setBrush(anim::brush(outbg ? st::msgFileOutBg : st::msgFileInBg, outbg ? st::msgFileOutBgOver : st::msgFileInBgOver, over)); + } else { + auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); + p.setBrush(outbg ? (over ? st::msgFileOutBgOver : st::msgFileOutBg) : (over ? st::msgFileInBgOver : st::msgFileInBg)); + } + + { + PainterHighQualityEnabler hq(p); + p.drawEllipse(inner); + } + + if (radial) { + QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); + auto fg = outbg ? (selected ? st::historyFileOutRadialFgSelected : st::historyFileOutRadialFg) : (selected ? st::historyFileInRadialFgSelected : st::historyFileInRadialFg); + _animation->radial.draw(p, rinner, st::msgFileRadialLine, fg); + } + + auto icon = ([showPause, radial, this, loaded, outbg, selected] { + if (showPause) { + return &(outbg ? (selected ? st::historyFileOutPauseSelected : st::historyFileOutPause) : (selected ? st::historyFileInPauseSelected : st::historyFileInPause)); + } else if (radial || _data->loading()) { + return &(outbg ? (selected ? st::historyFileOutCancelSelected : st::historyFileOutCancel) : (selected ? st::historyFileInCancelSelected : st::historyFileInCancel)); + } else if (loaded) { + if (_data->isAudioFile() || _data->isVoiceMessage()) { + return &(outbg ? (selected ? st::historyFileOutPlaySelected : st::historyFileOutPlay) : (selected ? st::historyFileInPlaySelected : st::historyFileInPlay)); + } else if (_data->isImage()) { + return &(outbg ? (selected ? st::historyFileOutImageSelected : st::historyFileOutImage) : (selected ? st::historyFileInImageSelected : st::historyFileInImage)); + } + return &(outbg ? (selected ? st::historyFileOutDocumentSelected : st::historyFileOutDocument) : (selected ? st::historyFileInDocumentSelected : st::historyFileInDocument)); + } + return &(outbg ? (selected ? st::historyFileOutDownloadSelected : st::historyFileOutDownload) : (selected ? st::historyFileInDownloadSelected : st::historyFileInDownload)); + })(); + icon->paintInCenter(p, inner); + } + auto namewidth = width() - nameleft - nameright; + auto statuswidth = namewidth; + + auto voiceStatusOverride = QString(); + if (auto voice = Get<HistoryDocumentVoice>()) { + const VoiceWaveform *wf = nullptr; + uchar norm_value = 0; + if (const auto voiceData = _data->voice()) { + wf = &voiceData->waveform; + if (wf->isEmpty()) { + wf = nullptr; + if (loaded) { + Local::countVoiceWaveform(_data); + } + } else if (wf->at(0) < 0) { + wf = nullptr; + } else { + norm_value = voiceData->wavemax; + } + } + auto progress = ([voice] { + if (voice->seeking()) { + return voice->seekingCurrent(); + } else if (voice->_playback) { + return voice->_playback->a_progress.current(); + } + return 0.; + })(); + if (voice->seeking()) { + voiceStatusOverride = formatPlayedText(qRound(progress * voice->_lastDurationMs) / 1000, voice->_lastDurationMs / 1000); + } + + // rescale waveform by going in waveform.size * bar_count 1D grid + auto active = outbg ? (selected ? st::msgWaveformOutActiveSelected : st::msgWaveformOutActive) : (selected ? st::msgWaveformInActiveSelected : st::msgWaveformInActive); + auto inactive = outbg ? (selected ? st::msgWaveformOutInactiveSelected : st::msgWaveformOutInactive) : (selected ? st::msgWaveformInInactiveSelected : st::msgWaveformInInactive); + auto wf_size = wf ? wf->size() : Media::Player::kWaveformSamplesCount; + auto availw = namewidth + st::msgWaveformSkip; + auto activew = qRound(availw * progress); + if (!outbg && !voice->_playback && _parent->data()->isMediaUnread()) { + activew = availw; + } + auto bar_count = qMin(availw / (st::msgWaveformBar + st::msgWaveformSkip), wf_size); + auto max_value = 0; + auto max_delta = st::msgWaveformMax - st::msgWaveformMin; + auto bottom = st::msgFilePadding.top() - topMinus + st::msgWaveformMax; + p.setPen(Qt::NoPen); + for (auto i = 0, bar_x = 0, sum_i = 0; i < wf_size; ++i) { + auto value = wf ? wf->at(i) : 0; + if (sum_i + bar_count >= wf_size) { // draw bar + sum_i = sum_i + bar_count - wf_size; + if (sum_i < (bar_count + 1) / 2) { + if (max_value < value) max_value = value; + } + auto bar_value = ((max_value * max_delta) + ((norm_value + 1) / 2)) / (norm_value + 1); + + if (bar_x >= activew) { + p.fillRect(nameleft + bar_x, bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, inactive); + } else if (bar_x + st::msgWaveformBar <= activew) { + p.fillRect(nameleft + bar_x, bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, active); + } else { + p.fillRect(nameleft + bar_x, bottom - bar_value, activew - bar_x, st::msgWaveformMin + bar_value, active); + p.fillRect(nameleft + activew, bottom - bar_value, st::msgWaveformBar - (activew - bar_x), st::msgWaveformMin + bar_value, inactive); + } + bar_x += st::msgWaveformBar + st::msgWaveformSkip; + + if (sum_i < (bar_count + 1) / 2) { + max_value = 0; + } else { + max_value = value; + } + } else { + if (max_value < value) max_value = value; + + sum_i += bar_count; + } + } + } else if (auto named = Get<HistoryDocumentNamed>()) { + 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)); + } else { + p.drawTextLeft(nameleft, nametop, width(), named->_name, named->_namew); + } + } + + auto statusText = voiceStatusOverride.isEmpty() ? _statusText : voiceStatusOverride; + 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); + + if (_parent->data()->isMediaUnread()) { + auto w = st::normalFont->width(statusText); + if (w + st::mediaUnreadSkip + st::mediaUnreadSize <= statuswidth) { + p.setPen(Qt::NoPen); + p.setBrush(outbg ? (selected ? st::msgFileOutBgSelected : st::msgFileOutBg) : (selected ? st::msgFileInBgSelected : st::msgFileInBg)); + + { + PainterHighQualityEnabler hq(p); + p.drawEllipse(rtlrect(nameleft + w + st::mediaUnreadSkip, statustop + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, width())); + } + } + } + + if (auto captioned = Get<HistoryDocumentCaptioned>()) { + 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); + } +} + +TextState HistoryDocument::textState(QPoint point, StateRequest request) const { + auto result = TextState(_parent); + + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { + return result; + } + + bool loaded = _data->loaded(); + + bool showPause = updateStatusText(); + + auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0, bottom = 0; + auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus; + if (auto thumbed = Get<HistoryDocumentThumbed>()) { + nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); + nameright = st::msgFileThumbPadding.left(); + nametop = st::msgFileThumbNameTop - topMinus; + linktop = st::msgFileThumbLinkTop - topMinus; + bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom() - topMinus; + + QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, width())); + + if ((_data->loading() || _data->uploading() || !loaded) && rthumb.contains(point)) { + result.link = (_data->loading() || _data->uploading()) ? _cancell : _savel; + return result; + } + + if (_data->status != FileUploadFailed) { + if (rtlrect(nameleft, linktop, thumbed->_linkw, st::semiboldFont->height, width()).contains(point)) { + result.link = (_data->loading() || _data->uploading()) + ? thumbed->_linkcancell + : thumbed->_linksavel; + return result; + } + } + } else { + nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); + nameright = st::msgFilePadding.left(); + nametop = st::msgFileNameTop - topMinus; + bottom = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() - topMinus; + + QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, st::msgFileSize, st::msgFileSize, width())); + if ((_data->loading() || _data->uploading() || !loaded) && inner.contains(point)) { + result.link = (_data->loading() || _data->uploading()) ? _cancell : _savel; + return result; + } + } + + if (auto voice = Get<HistoryDocumentVoice>()) { + auto namewidth = width() - nameleft - nameright; + auto waveformbottom = st::msgFilePadding.top() - topMinus + st::msgWaveformMax + st::msgWaveformMin; + if (QRect(nameleft, nametop, namewidth, waveformbottom - nametop).contains(point)) { + auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); + if (state.id == AudioMsgId(_data, _parent->data()->fullId()) + && !Media::Player::IsStoppedOrStopping(state.state)) { + if (!voice->seeking()) { + voice->setSeekingStart((point.x() - nameleft) / float64(namewidth)); + } + result.link = voice->_seekl; + return result; + } + } + } + + auto painth = height(); + if (auto captioned = Get<HistoryDocumentCaptioned>()) { + 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) && !_data->loading() && !_data->uploading() && _data->isValid()) { + result.link = _openl; + return result; + } + return result; +} + +void HistoryDocument::updatePressed(QPoint point) { + if (auto voice = Get<HistoryDocumentVoice>()) { + if (voice->seeking()) { + auto nameleft = 0, nameright = 0; + if (auto thumbed = Get<HistoryDocumentThumbed>()) { + nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); + nameright = st::msgFileThumbPadding.left(); + } else { + nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right(); + nameright = st::msgFilePadding.left(); + } + voice->setSeekingCurrent(snap((point.x() - nameleft) / float64(width() - nameleft - nameright), 0., 1.)); + Auth().data().requestViewRepaint(_parent); + } + } +} + +TextSelection HistoryDocument::adjustSelection( + TextSelection selection, + TextSelectType type) const { + if (const auto captioned = Get<HistoryDocumentCaptioned>()) { + return captioned->_caption.adjustSelection(selection, type); + } + return selection; +} + +uint16 HistoryDocument::fullSelectionLength() const { + if (const auto captioned = Get<HistoryDocumentCaptioned>()) { + return captioned->_caption.length(); + } + return 0; +} + +bool HistoryDocument::hasTextForCopy() const { + return Has<HistoryDocumentCaptioned>(); +} + +TextWithEntities HistoryDocument::selectedText(TextSelection selection) const { + if (const auto captioned = Get<HistoryDocumentCaptioned>()) { + const auto &caption = captioned->_caption; + return caption.originalTextWithEntities(selection, ExpandLinksAll); + } + return TextWithEntities(); +} + +bool HistoryDocument::uploading() const { + return _data->uploading(); +} + +void HistoryDocument::setStatusSize(int newSize, qint64 realDuration) const { + auto duration = _data->isSong() + ? _data->song()->duration + : (_data->isVoiceMessage() + ? _data->voice()->duration + : -1); + HistoryFileMedia::setStatusSize(newSize, _data->size, duration, realDuration); + if (auto thumbed = Get<HistoryDocumentThumbed>()) { + if (_statusSize == FileStatusSizeReady) { + thumbed->_link = lang(lng_media_download).toUpper(); + } else if (_statusSize == FileStatusSizeLoaded) { + thumbed->_link = lang(lng_media_open_with).toUpper(); + } else if (_statusSize == FileStatusSizeFailed) { + thumbed->_link = lang(lng_media_download).toUpper(); + } else if (_statusSize >= 0) { + thumbed->_link = lang(lng_media_cancel).toUpper(); + } else { + thumbed->_link = lang(lng_media_open_with).toUpper(); + } + thumbed->_linkw = st::semiboldFont->width(thumbed->_link); + } +} + +bool HistoryDocument::updateStatusText() const { + auto showPause = false; + auto statusSize = 0; + auto realDuration = 0; + if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { + statusSize = FileStatusSizeFailed; + } else if (_data->uploading()) { + statusSize = _data->uploadingData->offset; + } else if (_data->loading()) { + statusSize = _data->loadOffset(); + } else if (_data->loaded()) { + using State = Media::Player::State; + statusSize = FileStatusSizeLoaded; + if (_data->isVoiceMessage()) { + auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); + if (state.id == AudioMsgId(_data, _parent->data()->fullId()) + && !Media::Player::IsStoppedOrStopping(state.state)) { + if (auto voice = Get<HistoryDocumentVoice>()) { + bool was = (voice->_playback != nullptr); + voice->ensurePlayback(this); + if (!was || state.position != voice->_playback->_position) { + auto prg = state.length ? snap(float64(state.position) / state.length, 0., 1.) : 0.; + if (voice->_playback->_position < state.position) { + voice->_playback->a_progress.start(prg); + } else { + voice->_playback->a_progress = anim::value(0., prg); + } + voice->_playback->_position = state.position; + voice->_playback->_a_progress.start(); + } + voice->_lastDurationMs = static_cast<int>((state.length * 1000LL) / state.frequency); // Bad :( + } + + statusSize = -1 - (state.position / state.frequency); + realDuration = (state.length / state.frequency); + showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); + } else { + if (auto voice = Get<HistoryDocumentVoice>()) { + voice->checkPlaybackFinished(); + } + } + if (!showPause && (state.id == AudioMsgId(_data, _parent->data()->fullId()))) { + showPause = Media::Player::instance()->isSeeking(AudioMsgId::Type::Voice); + } + } else if (_data->isAudioFile()) { + auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song); + if (state.id == AudioMsgId(_data, _parent->data()->fullId()) + && !Media::Player::IsStoppedOrStopping(state.state)) { + statusSize = -1 - (state.position / state.frequency); + realDuration = (state.length / state.frequency); + showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); + } else { + } + if (!showPause && (state.id == AudioMsgId(_data, _parent->data()->fullId()))) { + showPause = Media::Player::instance()->isSeeking(AudioMsgId::Type::Song); + } + } + } else { + statusSize = FileStatusSizeReady; + } + if (statusSize != _statusSize) { + setStatusSize(statusSize, realDuration); + } + return showPause; +} + +QMargins HistoryDocument::bubbleMargins() const { + return Get<HistoryDocumentThumbed>() ? QMargins(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbPadding.left(), st::msgFileThumbPadding.bottom()) : st::msgPadding; +} + +bool HistoryDocument::hideForwardedFrom() const { + return _data->isSong(); +} + +void HistoryDocument::step_voiceProgress(float64 ms, bool timer) { + if (anim::Disabled()) { + ms += (2 * AudioVoiceMsgUpdateView); + } + if (auto voice = Get<HistoryDocumentVoice>()) { + if (voice->_playback) { + float64 dt = ms / (2 * AudioVoiceMsgUpdateView); + if (dt >= 1) { + voice->_playback->_a_progress.stop(); + voice->_playback->a_progress.finish(); + } else { + voice->_playback->a_progress.update(qMin(dt, 1.), anim::linear); + } + if (timer) { + Auth().data().requestViewRepaint(_parent); + } + } + } +} + +void HistoryDocument::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { + if (auto voice = Get<HistoryDocumentVoice>()) { + if (pressed && p == voice->_seekl && !voice->seeking()) { + voice->startSeeking(); + } else if (!pressed && voice->seeking()) { + auto type = AudioMsgId::Type::Voice; + auto state = Media::Player::mixer()->currentState(type); + if (state.id == AudioMsgId(_data, _parent->data()->fullId()) && state.length) { + auto currentProgress = voice->seekingCurrent(); + auto currentPosition = state.frequency + ? qRound(currentProgress * state.length * 1000. / state.frequency) + : 0; + Media::Player::mixer()->seek(type, currentPosition); + + voice->ensurePlayback(this); + voice->_playback->_position = 0; + voice->_playback->a_progress = anim::value(currentProgress, currentProgress); + } + voice->stopSeeking(); + } + } + HistoryFileMedia::clickHandlerPressedChanged(p, pressed); +} + +void HistoryDocument::refreshParentId(not_null<HistoryItem*> realParent) { + HistoryFileMedia::refreshParentId(realParent); + + const auto fullId = realParent->fullId(); + if (auto thumbed = Get<HistoryDocumentThumbed>()) { + if (thumbed->_linksavel) { + thumbed->_linksavel->setMessageId(fullId); + thumbed->_linkcancell->setMessageId(fullId); + } + } + if (auto voice = Get<HistoryDocumentVoice>()) { + if (voice->_seekl) { + voice->_seekl->setMessageId(fullId); + } + } +} + +void HistoryDocument::parentTextUpdated() { + auto caption = (_parent->media() == this) + ? createCaption(_parent->data()) + : Text(); + if (!caption.isEmpty()) { + AddComponents(HistoryDocumentCaptioned::Bit()); + auto captioned = Get<HistoryDocumentCaptioned>(); + captioned->_caption = std::move(caption); + } else { + RemoveComponents(HistoryDocumentCaptioned::Bit()); + } + Auth().data().requestViewResize(_parent); +} + +TextWithEntities HistoryDocument::getCaption() const { + if (const auto captioned = Get<HistoryDocumentCaptioned>()) { + return captioned->_caption.originalTextWithEntities(); + } + return TextWithEntities(); +} diff --git a/Telegram/SourceFiles/history/media/history_media_document.h b/Telegram/SourceFiles/history/media/history_media_document.h new file mode 100644 index 000000000..07ce6b89d --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_document.h @@ -0,0 +1,77 @@ +/* +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/media/history_media_file.h" +#include "base/runtime_composer.h" + +struct HistoryDocumentNamed; + +class HistoryDocument + : public HistoryFileMedia + , public RuntimeComposer<HistoryDocument> { +public: + HistoryDocument( + not_null<Element*> parent, + not_null<DocumentData*> document); + + HistoryMediaType type() const override; + + void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; + TextState textState(QPoint point, StateRequest request) const override; + void updatePressed(QPoint point) override; + + [[nodiscard]] TextSelection adjustSelection( + TextSelection selection, + TextSelectType type) const override; + uint16 fullSelectionLength() const override; + bool hasTextForCopy() const override; + + TextWithEntities selectedText(TextSelection selection) const override; + + bool uploading() const override; + + DocumentData *getDocument() const override { + return _data; + } + + TextWithEntities getCaption() const override; + bool needsBubble() const override { + return true; + } + bool customInfoLayout() const override { + return false; + } + QMargins bubbleMargins() const override; + bool hideForwardedFrom() const override; + + void step_voiceProgress(float64 ms, bool timer); + + void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; + + void refreshParentId(not_null<HistoryItem*> realParent) override; + void parentTextUpdated() override; + +protected: + float64 dataProgress() const override; + bool dataFinished() const override; + bool dataLoaded() const override; + +private: + QSize countOptimalSize() override; + QSize countCurrentSize(int newWidth) override; + + void createComponents(bool caption); + void fillNamedFromData(HistoryDocumentNamed *named); + + void setStatusSize(int newSize, qint64 realDuration = 0) const; + bool updateStatusText() const; // returns showPause + + not_null<DocumentData*> _data; + +}; diff --git a/Telegram/SourceFiles/history/media/history_media_file.cpp b/Telegram/SourceFiles/history/media/history_media_file.cpp new file mode 100644 index 000000000..8899cca56 --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_file.cpp @@ -0,0 +1,126 @@ +/* +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/media/history_media_file.h" + +#include "lang/lang_keys.h" +#include "layout.h" +#include "auth_session.h" +#include "history/history_item.h" +#include "data/data_document.h" +#include "data/data_session.h" +#include "styles/style_history.h" + +void HistoryFileMedia::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { + if (p == _savel || p == _cancell) { + if (active && !dataLoaded()) { + ensureAnimation(); + _animation->a_thumbOver.start([this] { thumbAnimationCallback(); }, 0., 1., st::msgFileOverDuration); + } else if (!active && _animation && !dataLoaded()) { + _animation->a_thumbOver.start([this] { thumbAnimationCallback(); }, 1., 0., st::msgFileOverDuration); + } + } +} + +void HistoryFileMedia::thumbAnimationCallback() { + Auth().data().requestViewRepaint(_parent); +} + +void HistoryFileMedia::clickHandlerPressedChanged( + const ClickHandlerPtr &handler, + bool pressed) { + Auth().data().requestViewRepaint(_parent); +} + +void HistoryFileMedia::setLinks( + FileClickHandlerPtr &&openl, + FileClickHandlerPtr &&savel, + FileClickHandlerPtr &&cancell) { + _openl = std::move(openl); + _savel = std::move(savel); + _cancell = std::move(cancell); +} + +void HistoryFileMedia::refreshParentId(not_null<HistoryItem*> realParent) { + const auto contextId = realParent->fullId(); + _openl->setMessageId(contextId); + _savel->setMessageId(contextId); + _cancell->setMessageId(contextId); +} + +void HistoryFileMedia::setStatusSize(int newSize, int fullSize, int duration, qint64 realDuration) const { + _statusSize = newSize; + if (_statusSize == FileStatusSizeReady) { + _statusText = (duration >= 0) ? formatDurationAndSizeText(duration, fullSize) : (duration < -1 ? formatGifAndSizeText(fullSize) : formatSizeText(fullSize)); + } else if (_statusSize == FileStatusSizeLoaded) { + _statusText = (duration >= 0) ? formatDurationText(duration) : (duration < -1 ? qsl("GIF") : formatSizeText(fullSize)); + } else if (_statusSize == FileStatusSizeFailed) { + _statusText = lang(lng_attach_failed); + } else if (_statusSize >= 0) { + _statusText = formatDownloadText(_statusSize, fullSize); + } else { + _statusText = formatPlayedText(-_statusSize - 1, realDuration); + } +} + +void HistoryFileMedia::step_radial(TimeMs ms, bool timer) { + const auto updateRadial = [&] { + return _animation->radial.update( + dataProgress(), + dataFinished(), + ms); + }; + if (timer) { + if (!anim::Disabled() || updateRadial()) { + Auth().data().requestViewRepaint(_parent); + } + } else { + updateRadial(); + if (!_animation->radial.animating()) { + checkAnimationFinished(); + } + } +} + +void HistoryFileMedia::ensureAnimation() const { + if (!_animation) { + _animation = std::make_unique<AnimationData>(animation(const_cast<HistoryFileMedia*>(this), &HistoryFileMedia::step_radial)); + } +} + +void HistoryFileMedia::checkAnimationFinished() const { + if (_animation && !_animation->a_thumbOver.animating() && !_animation->radial.animating()) { + if (dataLoaded()) { + _animation.reset(); + } + } +} +void HistoryFileMedia::setDocumentLinks( + not_null<DocumentData*> document, + not_null<HistoryItem*> realParent, + bool inlinegif) { + FileClickHandlerPtr open, save; + const auto context = realParent->fullId(); + if (inlinegif) { + open = std::make_shared<GifOpenClickHandler>(document, context); + } else { + open = std::make_shared<DocumentOpenClickHandler>(document, context); + } + if (inlinegif) { + save = std::make_shared<GifOpenClickHandler>(document, context); + } else if (document->isVoiceMessage()) { + save = std::make_shared<DocumentOpenClickHandler>(document, context); + } else { + save = std::make_shared<DocumentSaveClickHandler>(document, context); + } + setLinks( + std::move(open), + std::move(save), + std::make_shared<DocumentCancelClickHandler>(document, context)); +} + +HistoryFileMedia::~HistoryFileMedia() = default; diff --git a/Telegram/SourceFiles/history/media/history_media_file.h b/Telegram/SourceFiles/history/media/history_media_file.h new file mode 100644 index 000000000..3b24f2029 --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_file.h @@ -0,0 +1,101 @@ +/* +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/media/history_media.h" +#include "ui/effects/radial_animation.h" + +class HistoryFileMedia : public HistoryMedia { +public: + HistoryFileMedia( + not_null<Element*> parent, + not_null<HistoryItem*> realParent) + : HistoryMedia(parent) + , _realParent(realParent) { + } + + bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { + return p == _openl || p == _savel || p == _cancell; + } + bool dragItemByHandler(const ClickHandlerPtr &p) const override { + return p == _openl || p == _savel || p == _cancell; + } + + void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; + void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; + + void refreshParentId(not_null<HistoryItem*> realParent) override; + + bool allowsFastShare() const override { + return true; + } + + ~HistoryFileMedia(); + +protected: + using FileClickHandlerPtr = std::shared_ptr<FileClickHandler>; + + not_null<HistoryItem*> _realParent; + FileClickHandlerPtr _openl, _savel, _cancell; + + void setLinks( + FileClickHandlerPtr &&openl, + FileClickHandlerPtr &&savel, + FileClickHandlerPtr &&cancell); + void setDocumentLinks( + not_null<DocumentData*> document, + not_null<HistoryItem*> realParent, + bool inlinegif = false); + + // >= 0 will contain download / upload string, _statusSize = loaded bytes + // < 0 will contain played string, _statusSize = -(seconds + 1) played + // 0x7FFFFFF0 will contain status for not yet downloaded file + // 0x7FFFFFF1 will contain status for already downloaded file + // 0x7FFFFFF2 will contain status for failed to download / upload file + mutable int _statusSize; + mutable QString _statusText; + + // duration = -1 - no duration, duration = -2 - "GIF" duration + void setStatusSize(int newSize, int fullSize, int duration, qint64 realDuration) const; + + void step_radial(TimeMs ms, bool timer); + void thumbAnimationCallback(); + + void ensureAnimation() const; + void checkAnimationFinished() const; + + bool isRadialAnimation(TimeMs ms) const { + if (!_animation || !_animation->radial.animating()) return false; + + _animation->radial.step(ms); + return _animation && _animation->radial.animating(); + } + bool isThumbAnimation(TimeMs ms) const { + if (_animation) { + if (_animation->a_thumbOver.animating(ms)) { + return true; + } + checkAnimationFinished(); + } + return false; + } + + virtual float64 dataProgress() const = 0; + virtual bool dataFinished() const = 0; + virtual bool dataLoaded() const = 0; + + struct AnimationData { + AnimationData(AnimationCallbacks &&radialCallbacks) + : radial(std::move(radialCallbacks)) { + } + Animation a_thumbOver; + Ui::RadialAnimation radial; + }; + mutable std::unique_ptr<AnimationData> _animation; + +}; diff --git a/Telegram/SourceFiles/history/media/history_media_game.cpp b/Telegram/SourceFiles/history/media/history_media_game.cpp new file mode 100644 index 000000000..09072319d --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_game.cpp @@ -0,0 +1,437 @@ +/* +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/media/history_media_game.h" + +#include "lang/lang_keys.h" +#include "layout.h" +#include "auth_session.h" +#include "history/history_item_components.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_cursor_state.h" +#include "history/media/history_media_common.h" +#include "ui/text_options.h" +#include "data/data_session.h" +#include "data/data_game.h" +#include "data/data_media_types.h" +#include "styles/style_history.h" + +namespace { + +using TextState = HistoryView::TextState; + +} // namespace + +HistoryGame::HistoryGame( + not_null<Element*> parent, + not_null<GameData*> data, + const TextWithEntities &consumed) +: HistoryMedia(parent) +, _data(data) +, _title(st::msgMinWidth - st::webPageLeft) +, _description(st::msgMinWidth - st::webPageLeft) { + if (!consumed.text.isEmpty()) { + _description.setMarkedText( + st::webPageDescriptionStyle, + consumed, + Ui::ItemTextOptions(parent->data())); + } + Auth().data().registerGameView(_data, _parent); +} + +QSize HistoryGame::countOptimalSize() { + auto lineHeight = unitedLineHeight(); + + const auto item = _parent->data(); + if (!_openl && IsServerMsgId(item->id)) { + const auto row = 0; + const auto column = 0; + _openl = std::make_shared<ReplyMarkupClickHandler>( + row, + column, + item->fullId()); + } + + auto title = TextUtilities::SingleLine(_data->title); + + // init attach + if (!_attach) { + _attach = CreateAttach(_parent, _data->document, _data->photo); + } + + // init strings + if (_description.isEmpty() && !_data->description.isEmpty()) { + auto text = _data->description; + if (!text.isEmpty()) { + if (!_attach) { + text += _parent->skipBlock(); + } + auto marked = TextWithEntities { text }; + auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText; + TextUtilities::ParseEntities(marked, parseFlags); + _description.setMarkedText( + st::webPageDescriptionStyle, + marked, + Ui::WebpageTextDescriptionOptions()); + } + } + if (_title.isEmpty() && !title.isEmpty()) { + _title.setText( + st::webPageTitleStyle, + title, + Ui::WebpageTextTitleOptions()); + } + + // init dimensions + auto l = st::msgPadding.left() + st::webPageLeft, r = st::msgPadding.right(); + auto skipBlockWidth = _parent->skipBlockWidth(); + auto maxWidth = skipBlockWidth; + auto minHeight = 0; + + auto titleMinHeight = _title.isEmpty() ? 0 : lineHeight; + // enable any count of lines in game description / message + auto descMaxLines = 4096; + auto descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * lineHeight); + + if (!_title.isEmpty()) { + accumulate_max(maxWidth, _title.maxWidth()); + minHeight += titleMinHeight; + } + if (!_description.isEmpty()) { + accumulate_max(maxWidth, _description.maxWidth()); + minHeight += descriptionMinHeight; + } + if (_attach) { + auto attachAtTop = !_titleLines && !_descriptionLines; + if (!attachAtTop) minHeight += st::mediaInBubbleSkip; + + _attach->initDimensions(); + QMargins bubble(_attach->bubbleMargins()); + auto maxMediaWidth = _attach->maxWidth() - bubble.left() - bubble.right(); + if (isBubbleBottom() && _attach->customInfoLayout()) { + maxMediaWidth += skipBlockWidth; + } + accumulate_max(maxWidth, maxMediaWidth); + minHeight += _attach->minHeight() - bubble.top() - bubble.bottom(); + } + maxWidth += st::msgPadding.left() + st::webPageLeft + st::msgPadding.right(); + auto padding = inBubblePadding(); + minHeight += padding.top() + padding.bottom(); + + if (!_gameTagWidth) { + _gameTagWidth = st::msgDateFont->width(lang(lng_game_tag).toUpper()); + } + return { maxWidth, minHeight }; +} + +void HistoryGame::refreshParentId(not_null<HistoryItem*> realParent) { + if (_openl) { + _openl->setMessageId(realParent->fullId()); + } + if (_attach) { + _attach->refreshParentId(realParent); + } +} + +QSize HistoryGame::countCurrentSize(int newWidth) { + accumulate_min(newWidth, maxWidth()); + auto innerWidth = newWidth - st::msgPadding.left() - st::webPageLeft - st::msgPadding.right(); + + // enable any count of lines in game description / message + auto linesMax = 4096; + auto lineHeight = unitedLineHeight(); + auto newHeight = 0; + if (_title.isEmpty()) { + _titleLines = 0; + } else { + if (_title.countHeight(innerWidth) < 2 * st::webPageTitleFont->height) { + _titleLines = 1; + } else { + _titleLines = 2; + } + newHeight += _titleLines * lineHeight; + } + + if (_description.isEmpty()) { + _descriptionLines = 0; + } else { + auto descriptionHeight = _description.countHeight(innerWidth); + if (descriptionHeight < (linesMax - _titleLines) * st::webPageDescriptionFont->height) { + _descriptionLines = (descriptionHeight / st::webPageDescriptionFont->height); + } else { + _descriptionLines = (linesMax - _titleLines); + } + newHeight += _descriptionLines * lineHeight; + } + + if (_attach) { + auto attachAtTop = !_titleLines && !_descriptionLines; + if (!attachAtTop) newHeight += st::mediaInBubbleSkip; + + QMargins bubble(_attach->bubbleMargins()); + + _attach->resizeGetHeight(innerWidth + bubble.left() + bubble.right()); + newHeight += _attach->height() - bubble.top() - bubble.bottom(); + if (isBubbleBottom() && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > innerWidth + bubble.left() + bubble.right()) { + newHeight += bottomInfoPadding(); + } + } + auto padding = inBubblePadding(); + newHeight += padding.top() + padding.bottom(); + + return { newWidth, newHeight }; +} + +TextSelection HistoryGame::toDescriptionSelection( + TextSelection selection) const { + return HistoryView::UnshiftItemSelection(selection, _title); +} + +TextSelection HistoryGame::fromDescriptionSelection( + TextSelection selection) const { + return HistoryView::ShiftItemSelection(selection, _title); +} + +void HistoryGame::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; + auto paintw = width(), painth = height(); + + auto outbg = _parent->hasOutLayout(); + bool selected = (selection == FullSelection); + + auto &barfg = selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor); + auto &semibold = selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg); + auto ®ular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg); + + QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); + auto padding = inBubblePadding(); + auto tshift = padding.top(); + auto bshift = padding.bottom(); + paintw -= padding.left() + padding.right(); + if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) { + bshift += bottomInfoPadding(); + } + + QRect bar(rtlrect(st::msgPadding.left(), tshift, st::webPageBar, height() - tshift - bshift, width())); + p.fillRect(bar, barfg); + + auto lineHeight = unitedLineHeight(); + if (_titleLines) { + p.setPen(semibold); + auto endskip = 0; + if (_title.hasSkipBlock()) { + endskip = _parent->skipBlockWidth(); + } + _title.drawLeftElided(p, padding.left(), tshift, paintw, width(), _titleLines, style::al_left, 0, -1, endskip, false, selection); + tshift += _titleLines * lineHeight; + } + if (_descriptionLines) { + p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg); + auto endskip = 0; + if (_description.hasSkipBlock()) { + endskip = _parent->skipBlockWidth(); + } + _description.drawLeftElided(p, padding.left(), tshift, paintw, width(), _descriptionLines, style::al_left, 0, -1, endskip, false, toDescriptionSelection(selection)); + tshift += _descriptionLines * lineHeight; + } + if (_attach) { + auto attachAtTop = !_titleLines && !_descriptionLines; + if (!attachAtTop) tshift += st::mediaInBubbleSkip; + + auto attachLeft = padding.left() - bubble.left(); + auto attachTop = tshift - bubble.top(); + if (rtl()) attachLeft = width() - attachLeft - _attach->width(); + + auto attachSelection = selected ? FullSelection : TextSelection { 0, 0 }; + + p.translate(attachLeft, attachTop); + _attach->draw(p, r.translated(-attachLeft, -attachTop), attachSelection, ms); + auto pixwidth = _attach->width(); + auto pixheight = _attach->height(); + + auto gameW = _gameTagWidth + 2 * st::msgDateImgPadding.x(); + auto gameH = st::msgDateFont->height + 2 * st::msgDateImgPadding.y(); + auto gameX = pixwidth - st::msgDateImgDelta - gameW; + auto gameY = pixheight - st::msgDateImgDelta - gameH; + + App::roundRect(p, rtlrect(gameX, gameY, gameW, gameH, pixwidth), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); + + p.setFont(st::msgDateFont); + p.setPen(st::msgDateImgFg); + p.drawTextLeft(gameX + st::msgDateImgPadding.x(), gameY + st::msgDateImgPadding.y(), pixwidth, lang(lng_game_tag).toUpper()); + + p.translate(-attachLeft, -attachTop); + } +} + +TextState HistoryGame::textState(QPoint point, StateRequest request) const { + auto result = TextState(_parent); + + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { + return result; + } + auto paintw = width(), painth = height(); + + QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); + auto padding = inBubblePadding(); + auto tshift = padding.top(); + auto bshift = padding.bottom(); + if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) { + bshift += bottomInfoPadding(); + } + paintw -= padding.left() + padding.right(); + + auto inThumb = false; + auto symbolAdd = 0; + auto lineHeight = unitedLineHeight(); + if (_titleLines) { + if (point.y() >= tshift && point.y() < tshift + _titleLines * lineHeight) { + Text::StateRequestElided titleRequest = request.forText(); + titleRequest.lines = _titleLines; + result = TextState(_parent, _title.getStateElidedLeft( + point - QPoint(padding.left(), tshift), + paintw, + width(), + titleRequest)); + } else if (point.y() >= tshift + _titleLines * lineHeight) { + symbolAdd += _title.length(); + } + tshift += _titleLines * lineHeight; + } + if (_descriptionLines) { + if (point.y() >= tshift && point.y() < tshift + _descriptionLines * lineHeight) { + Text::StateRequestElided descriptionRequest = request.forText(); + descriptionRequest.lines = _descriptionLines; + result = TextState(_parent, _description.getStateElidedLeft( + point - QPoint(padding.left(), tshift), + paintw, + width(), + descriptionRequest)); + } else if (point.y() >= tshift + _descriptionLines * lineHeight) { + symbolAdd += _description.length(); + } + tshift += _descriptionLines * lineHeight; + } + if (inThumb) { + if (!_parent->data()->isLogEntry()) { + result.link = _openl; + } + } else if (_attach) { + auto attachAtTop = !_titleLines && !_descriptionLines; + if (!attachAtTop) tshift += st::mediaInBubbleSkip; + + auto attachLeft = padding.left() - bubble.left(); + auto attachTop = tshift - bubble.top(); + if (rtl()) attachLeft = width() - attachLeft - _attach->width(); + + if (QRect(attachLeft, tshift, _attach->width(), height() - tshift - bshift).contains(point)) { + if (_attach->isReadyForOpen()) { + if (!_parent->data()->isLogEntry()) { + result.link = _openl; + } + } else { + result = _attach->textState(point - QPoint(attachLeft, attachTop), request); + } + } + } + + result.symbol += symbolAdd; + return result; +} + +TextSelection HistoryGame::adjustSelection(TextSelection selection, TextSelectType type) const { + if (!_descriptionLines || selection.to <= _title.length()) { + return _title.adjustSelection(selection, type); + } + auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type); + if (selection.from >= _title.length()) { + return fromDescriptionSelection(descriptionSelection); + } + auto titleSelection = _title.adjustSelection(selection, type); + return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to }; +} + +void HistoryGame::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { + if (_attach) { + _attach->clickHandlerActiveChanged(p, active); + } +} + +void HistoryGame::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { + if (_attach) { + _attach->clickHandlerPressedChanged(p, pressed); + } +} + +TextWithEntities HistoryGame::selectedText(TextSelection selection) const { + auto titleResult = _title.originalTextWithEntities( + selection, + ExpandLinksAll); + auto descriptionResult = _description.originalTextWithEntities( + toDescriptionSelection(selection), + ExpandLinksAll); + if (titleResult.text.isEmpty()) { + return descriptionResult; + } else if (descriptionResult.text.isEmpty()) { + return titleResult; + } + + titleResult.text += '\n'; + TextUtilities::Append(titleResult, std::move(descriptionResult)); + return titleResult; +} + +void HistoryGame::playAnimation(bool autoplay) { + if (_attach) { + if (autoplay) { + _attach->autoplayAnimation(); + } else { + _attach->playAnimation(); + } + } +} + +QMargins HistoryGame::inBubblePadding() const { + auto lshift = st::msgPadding.left() + st::webPageLeft; + auto rshift = st::msgPadding.right(); + auto bshift = isBubbleBottom() ? st::msgPadding.left() : st::mediaInBubbleSkip; + auto tshift = isBubbleTop() ? st::msgPadding.left() : st::mediaInBubbleSkip; + return QMargins(lshift, tshift, rshift, bshift); +} + +int HistoryGame::bottomInfoPadding() const { + if (!isBubbleBottom()) return 0; + + auto result = st::msgDateFont->height; + + // we use padding greater than st::msgPadding.bottom() in the + // bottom of the bubble so that the left line looks pretty. + // but if we have bottom skip because of the info display + // we don't need that additional padding so we replace it + // back with st::msgPadding.bottom() instead of left(). + result += st::msgPadding.bottom() - st::msgPadding.left(); + return result; +} + +void HistoryGame::parentTextUpdated() { + if (const auto media = _parent->data()->media()) { + const auto consumed = media->consumedMessageText(); + if (!consumed.text.isEmpty()) { + _description.setMarkedText( + st::webPageDescriptionStyle, + consumed, + Ui::ItemTextOptions(_parent->data())); + } else { + _description = Text(st::msgMinWidth - st::webPageLeft); + } + Auth().data().requestViewResize(_parent); + } +} + +HistoryGame::~HistoryGame() { + Auth().data().unregisterGameView(_data, _parent); +} diff --git a/Telegram/SourceFiles/history/media/history_media_game.h b/Telegram/SourceFiles/history/media/history_media_game.h new file mode 100644 index 000000000..a9cc3a766 --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_game.h @@ -0,0 +1,104 @@ +/* +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/media/history_media.h" + +class ReplyMarkupClickHandler; + +class HistoryGame : public HistoryMedia { +public: + HistoryGame( + not_null<Element*> parent, + not_null<GameData*> data, + const TextWithEntities &consumed); + + HistoryMediaType type() const override { + return MediaTypeGame; + } + + void refreshParentId(not_null<HistoryItem*> realParent) override; + + void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; + TextState textState(QPoint point, StateRequest request) const override; + + [[nodiscard]] TextSelection adjustSelection( + TextSelection selection, + TextSelectType type) const override; + uint16 fullSelectionLength() const override { + return _title.length() + _description.length(); + } + bool hasTextForCopy() const override { + return false; // we do not add _title and _description in FullSelection text copy. + } + + bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { + return _attach && _attach->toggleSelectionByHandlerClick(p); + } + bool dragItemByHandler(const ClickHandlerPtr &p) const override { + return _attach && _attach->dragItemByHandler(p); + } + + TextWithEntities selectedText(TextSelection selection) const override; + + void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; + void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; + + PhotoData *getPhoto() const override { + return _attach ? _attach->getPhoto() : nullptr; + } + DocumentData *getDocument() const override { + return _attach ? _attach->getDocument() : nullptr; + } + void stopAnimation() override { + if (_attach) _attach->stopAnimation(); + } + + not_null<GameData*> game() { + return _data; + } + + bool needsBubble() const override { + return true; + } + bool customInfoLayout() const override { + return false; + } + bool allowsFastShare() const override { + return true; + } + + HistoryMedia *attach() const { + return _attach.get(); + } + + void parentTextUpdated() override; + + ~HistoryGame(); + +private: + void playAnimation(bool autoplay) override; + QSize countOptimalSize() override; + QSize countCurrentSize(int newWidth) override; + + TextSelection toDescriptionSelection(TextSelection selection) const; + TextSelection fromDescriptionSelection(TextSelection selection) const; + QMargins inBubblePadding() const; + int bottomInfoPadding() const; + + not_null<GameData*> _data; + std::shared_ptr<ReplyMarkupClickHandler> _openl; + std::unique_ptr<HistoryMedia> _attach; + + int _titleLines, _descriptionLines; + + Text _title, _description; + + int _gameTagWidth = 0; + +}; diff --git a/Telegram/SourceFiles/history/media/history_media_gif.cpp b/Telegram/SourceFiles/history/media/history_media_gif.cpp new file mode 100644 index 000000000..0b5a01126 --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_gif.cpp @@ -0,0 +1,924 @@ +/* +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/media/history_media_gif.h" + +#include "lang/lang_keys.h" +#include "layout.h" +#include "mainwindow.h" +#include "auth_session.h" +#include "media/media_audio.h" +#include "media/media_clip_reader.h" +#include "media/player/media_player_round_controller.h" +#include "media/view/media_clip_playback.h" +#include "boxes/confirm_box.h" +#include "history/history_item_components.h" +#include "history/history_item.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_cursor_state.h" +#include "window/window_controller.h" +#include "ui/image/image.h" +#include "data/data_session.h" +#include "data/data_document.h" +#include "styles/style_history.h" + +namespace { + +using TextState = HistoryView::TextState; + +} // namespace + +namespace { + +constexpr auto kMaxGifForwardedBarLines = 4; + +int gifMaxStatusWidth(DocumentData *document) { + auto result = st::normalFont->width(formatDownloadText(document->size, document->size)); + accumulate_max(result, st::normalFont->width(formatGifAndSizeText(document->size))); + return result; +} + +} // namespace + +HistoryGif::HistoryGif( + not_null<Element*> parent, + not_null<DocumentData*> document) +: HistoryFileMedia(parent, parent->data()) +, _data(document) +, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { + const auto item = parent->data(); + setDocumentLinks(_data, item, true); + + setStatusSize(FileStatusSizeReady); + + _caption = createCaption(item); + _data->thumb->load(item->fullId()); +} + +QSize HistoryGif::countOptimalSize() { + if (_parent->media() != this) { + _caption = Text(); + } else if (_caption.hasSkipBlock()) { + _caption.updateSkipBlock( + _parent->skipBlockWidth(), + _parent->skipBlockHeight()); + } + if (!_openInMediaviewLink) { + _openInMediaviewLink = std::make_shared<DocumentOpenClickHandler>( + _data, + _parent->data()->fullId()); + } + + auto tw = 0; + auto th = 0; + if (_gif && _gif->state() == Media::Clip::State::Error) { + if (!_gif->autoplay()) { + Ui::show(Box<InformBox>(lang(lng_gif_error))); + } + setClipReader(Media::Clip::ReaderPointer::Bad()); + } + + const auto reader = currentReader(); + if (reader) { + tw = ConvertScale(reader->width()); + th = ConvertScale(reader->height()); + } else { + tw = ConvertScale(_data->dimensions.width()), th = ConvertScale(_data->dimensions.height()); + if (!tw || !th) { + tw = ConvertScale(_data->thumb->width()); + th = ConvertScale(_data->thumb->height()); + } + } + const auto maxSize = _data->isVideoMessage() + ? st::maxVideoMessageSize + : st::maxGifSize; + if (tw > maxSize) { + th = (maxSize * th) / tw; + tw = maxSize; + } + if (th > maxSize) { + tw = (maxSize * tw) / th; + th = maxSize; + } + if (!tw || !th) { + tw = th = 1; + } + _thumbw = tw; + _thumbh = th; + auto maxWidth = qMax(tw, st::minPhotoSize); + auto minHeight = qMax(th, st::minPhotoSize); + accumulate_max(maxWidth, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + if (!reader) { + accumulate_max(maxWidth, gifMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + } + if (_parent->hasBubble()) { + if (!_caption.isEmpty()) { + auto captionw = maxWidth - st::msgPadding.left() - st::msgPadding.right(); + minHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); + if (isBubbleBottom()) { + minHeight += st::msgPadding.bottom(); + } + } + } else if (isSeparateRoundVideo()) { + const auto item = _parent->data(); + auto via = item->Get<HistoryMessageVia>(); + auto reply = item->Get<HistoryMessageReply>(); + auto forwarded = item->Get<HistoryMessageForwarded>(); + if (forwarded) { + forwarded->create(via); + } + maxWidth += additionalWidth(via, reply, forwarded); + } + return { maxWidth, minHeight }; +} + +QSize HistoryGif::countCurrentSize(int newWidth) { + auto availableWidth = newWidth; + + int tw = 0, th = 0; + const auto reader = currentReader(); + if (reader) { + tw = ConvertScale(reader->width()); + th = ConvertScale(reader->height()); + } else { + tw = ConvertScale(_data->dimensions.width()), th = ConvertScale(_data->dimensions.height()); + if (!tw || !th) { + tw = ConvertScale(_data->thumb->width()); + th = ConvertScale(_data->thumb->height()); + } + } + const auto maxSize = _data->isVideoMessage() + ? st::maxVideoMessageSize + : st::maxGifSize; + if (tw > maxSize) { + th = (maxSize * th) / tw; + tw = maxSize; + } + if (th > maxSize) { + tw = (maxSize * tw) / th; + th = maxSize; + } + if (!tw || !th) { + tw = th = 1; + } + + if (newWidth < tw) { + th = qRound((newWidth / float64(tw)) * th); + tw = newWidth; + } + _thumbw = tw; + _thumbh = th; + + newWidth = qMax(tw, st::minPhotoSize); + auto newHeight = qMax(th, st::minPhotoSize); + accumulate_max(newWidth, _parent->infoWidth() + 2 * st::msgDateImgDelta + st::msgDateImgPadding.x()); + if (reader) { + const auto own = (reader->mode() == Media::Clip::Reader::Mode::Gif); + if (own && !reader->started()) { + auto isRound = _data->isVideoMessage(); + auto inWebPage = (_parent->media() != this); + auto roundRadius = isRound + ? ImageRoundRadius::Ellipse + : inWebPage + ? ImageRoundRadius::Small + : ImageRoundRadius::Large; + auto roundCorners = (isRound || inWebPage) + ? RectPart::AllCorners + : ((isBubbleTop() + ? (RectPart::TopLeft | RectPart::TopRight) + : RectPart::None) + | ((isBubbleBottom() && _caption.isEmpty()) + ? (RectPart::BottomLeft | RectPart::BottomRight) + : RectPart::None)); + reader->start( + _thumbw, + _thumbh, + newWidth, + newHeight, + roundRadius, + roundCorners); + } + } else { + accumulate_max(newWidth, gifMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + } + if (_parent->hasBubble()) { + if (!_caption.isEmpty()) { + auto captionw = newWidth - st::msgPadding.left() - st::msgPadding.right(); + newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); + if (isBubbleBottom()) { + newHeight += st::msgPadding.bottom(); + } + } + } else if (isSeparateRoundVideo()) { + const auto item = _parent->data(); + auto via = item->Get<HistoryMessageVia>(); + auto reply = item->Get<HistoryMessageReply>(); + auto forwarded = item->Get<HistoryMessageForwarded>(); + if (via || reply || forwarded) { + auto additional = additionalWidth(via, reply, forwarded); + newWidth += additional; + accumulate_min(newWidth, availableWidth); + auto usew = maxWidth() - additional; + auto availw = newWidth - usew - st::msgReplyPadding.left() - st::msgReplyPadding.left() - st::msgReplyPadding.left(); + if (!forwarded && via) { + via->resize(availw); + } + if (reply) { + reply->resize(availw); + } + } + } + + return { newWidth, newHeight }; +} + +void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; + + const auto item = _parent->data(); + _data->automaticLoad(_realParent->fullId(), item); + auto loaded = _data->loaded(); + auto displayLoading = (item->id < 0) || _data->displayLoading(); + auto selected = (selection == FullSelection); + + if (loaded + && cAutoPlayGif() + && !_gif + && !_gif.isBad() + && !activeRoundVideo()) { + _parent->delegate()->elementAnimationAutoplayAsync(_parent); + } + + auto paintx = 0, painty = 0, paintw = width(), painth = height(); + bool bubble = _parent->hasBubble(); + auto outbg = _parent->hasOutLayout(); + auto inWebPage = (_parent->media() != this); + + auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right(); + + auto isRound = _data->isVideoMessage(); + auto displayMute = false; + const auto reader = currentReader(); + const auto playingVideo = reader + ? (reader->mode() == Media::Clip::Reader::Mode::Video) + : false; + const auto animating = reader && reader->started(); + + if ((!animating || item->id < 0) && displayLoading) { + ensureAnimation(); + if (!_animation->radial.animating()) { + _animation->radial.start(dataProgress()); + } + } + updateStatusText(); + auto radial = isRadialAnimation(ms); + + if (bubble) { + if (!_caption.isEmpty()) { + painth -= st::mediaCaptionSkip + _caption.countHeight(captionw); + if (isBubbleBottom()) { + painth -= st::msgPadding.bottom(); + } + } + } else if (!isRound) { + App::roundShadow(p, 0, 0, paintw, height(), selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); + } + + auto usex = 0, usew = paintw; + auto separateRoundVideo = isSeparateRoundVideo(); + auto via = separateRoundVideo ? item->Get<HistoryMessageVia>() : nullptr; + auto reply = separateRoundVideo ? item->Get<HistoryMessageReply>() : nullptr; + auto forwarded = separateRoundVideo ? item->Get<HistoryMessageForwarded>() : nullptr; + if (via || reply || forwarded) { + usew = maxWidth() - additionalWidth(via, reply, forwarded); + if (outbg) { + usex = width() - usew; + } + } + if (rtl()) usex = width() - usex - usew; + + QRect rthumb(rtlrect(usex + paintx, painty, usew, painth, width())); + + auto roundRadius = isRound ? ImageRoundRadius::Ellipse : inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large; + auto roundCorners = (isRound || inWebPage) ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None) + | ((isBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None)); + if (animating) { + auto paused = App::wnd()->controller()->isGifPausedAtLeastFor(Window::GifPauseReason::Any); + if (isRound) { + if (playingVideo) { + paused = false; + } else { + displayMute = true; + } + } + p.drawPixmap(rthumb.topLeft(), reader->current(_thumbw, _thumbh, usew, painth, roundRadius, roundCorners, paused ? 0 : ms)); + + if (const auto playback = videoPlayback()) { + const auto value = playback->value(ms); + if (value > 0.) { + auto pen = st::historyVideoMessageProgressFg->p; + auto was = p.pen(); + pen.setWidth(st::radialLine); + pen.setCapStyle(Qt::RoundCap); + p.setPen(pen); + p.setOpacity(st::historyVideoMessageProgressOpacity); + + auto from = QuarterArcLength; + auto len = -qRound(FullArcLength * value); + auto stepInside = st::radialLine / 2; + { + PainterHighQualityEnabler hq(p); + p.drawArc(rthumb.marginsRemoved(QMargins(stepInside, stepInside, stepInside, stepInside)), from, len); + } + + p.setPen(was); + p.setOpacity(1.); + } + } + } else { + const auto good = _data->goodThumbnail(); + if (good && good->loaded()) { + p.drawPixmap(rthumb.topLeft(), good->pixSingle({}, _thumbw, _thumbh, usew, painth, roundRadius, roundCorners)); + } else { + if (good) { + good->load({}); + } + p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(_realParent->fullId(), _thumbw, _thumbh, usew, painth, roundRadius, roundCorners)); + } + } + + if (selected) { + App::complexOverlayRect(p, rthumb, roundRadius, roundCorners); + } + + if (radial || (!reader && (_gif.isBad() || (!loaded && !_data->loading()) || !cAutoPlayGif()))) { + auto radialOpacity = (radial && loaded && item->id > 0) ? _animation->radial.opacity() : 1.; + auto inner = QRect(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); + p.setPen(Qt::NoPen); + if (selected) { + p.setBrush(st::msgDateImgBgSelected); + } else if (isThumbAnimation(ms)) { + auto over = _animation->a_thumbOver.current(); + p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over)); + } else { + auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); + p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); + } + p.setOpacity(radialOpacity * p.opacity()); + + { + PainterHighQualityEnabler hq(p); + p.drawEllipse(inner); + } + + p.setOpacity(radialOpacity); + auto icon = [&]() -> const style::icon * { + if (_data->loaded() && !radial) { + return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay); + } else if (radial || _data->loading()) { + if (item->id > 0 || _data->uploading()) { + return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); + } + return nullptr; + } + return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); + }(); + if (icon) { + icon->paintInCenter(p, inner); + } + if (radial) { + p.setOpacity(1); + QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); + _animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg); + } + + if (!isRound && (!animating || item->id < 0)) { + 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, rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); + p.setFont(st::normalFont); + p.setPen(st::msgDateImgFg); + p.drawTextLeft(statusX, statusY, width(), _statusText, statusW - 2 * st::msgDateImgPadding.x()); + } + } + if (displayMute) { + auto muteRect = rtlrect(rthumb.x() + (rthumb.width() - st::historyVideoMessageMuteSize) / 2, rthumb.y() + st::msgDateImgDelta, st::historyVideoMessageMuteSize, st::historyVideoMessageMuteSize, width()); + p.setPen(Qt::NoPen); + p.setBrush(selected ? st::msgDateImgBgSelected : st::msgDateImgBg); + PainterHighQualityEnabler hq(p); + p.drawEllipse(muteRect); + (selected ? st::historyVideoMessageMuteSelected : st::historyVideoMessageMute).paintInCenter(p, muteRect); + } + + if (!inWebPage && isRound) { + auto mediaUnread = item->isMediaUnread(); + auto statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x(); + auto statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y(); + auto statusX = usex + paintx + st::msgDateImgDelta + st::msgDateImgPadding.x(); + auto statusY = painty + painth - st::msgDateImgDelta - statusH + st::msgDateImgPadding.y(); + if (item->isMediaUnread()) { + statusW += st::mediaUnreadSkip + st::mediaUnreadSize; + } + App::roundRect(p, rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? StickerSelectedCorners : StickerCorners); + p.setFont(st::normalFont); + p.setPen(st::msgServiceFg); + p.drawTextLeft(statusX, statusY, width(), _statusText, statusW - 2 * st::msgDateImgPadding.x()); + if (mediaUnread) { + p.setPen(Qt::NoPen); + p.setBrush(st::msgServiceFg); + + { + PainterHighQualityEnabler hq(p); + p.drawEllipse(rtlrect(statusX - st::msgDateImgPadding.x() + statusW - st::msgDateImgPadding.x() - st::mediaUnreadSize, statusY + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, width())); + } + } + if (via || reply || forwarded) { + auto rectw = width() - usew - st::msgReplyPadding.left(); + auto innerw = rectw - (st::msgReplyPadding.left() + st::msgReplyPadding.right()); + auto recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom(); + auto forwardedHeightReal = forwarded ? forwarded->text.countHeight(innerw) : 0; + auto forwardedHeight = qMin(forwardedHeightReal, kMaxGifForwardedBarLines * st::msgServiceNameFont->height); + if (forwarded) { + recth += forwardedHeight; + } else if (via) { + recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); + } + if (reply) { + recth += st::msgReplyBarSize.height(); + } + int rectx = outbg ? 0 : (usew + st::msgReplyPadding.left()); + int recty = painty; + if (rtl()) rectx = width() - rectx - rectw; + + App::roundRect(p, rectx, recty, rectw, recth, selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? StickerSelectedCorners : StickerCorners); + p.setPen(st::msgServiceFg); + rectx += st::msgReplyPadding.left(); + rectw = innerw; + if (forwarded) { + p.setTextPalette(st::serviceTextPalette); + auto breakEverywhere = (forwardedHeightReal > forwardedHeight); + forwarded->text.drawElided(p, rectx, recty + st::msgReplyPadding.top(), rectw, kMaxGifForwardedBarLines, style::al_left, 0, -1, 0, breakEverywhere); + p.restoreTextPalette(); + } else if (via) { + p.setFont(st::msgDateFont); + p.drawTextLeft(rectx, recty + st::msgReplyPadding.top(), 2 * rectx + rectw, via->text); + int skip = st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); + recty += skip; + } + if (reply) { + HistoryMessageReply::PaintFlags flags = 0; + if (selected) { + flags |= HistoryMessageReply::PaintFlag::Selected; + } + reply->paint(p, _parent, rectx, recty, rectw, flags); + } + } + } + if (!isRound && !_caption.isEmpty()) { + p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg)); + _caption.draw(p, st::msgPadding.left(), painty + painth + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection); + } else if (!inWebPage) { + auto fullRight = paintx + usex + usew; + auto fullBottom = painty + painth; + auto maxRight = _parent->width() - st::msgMargin.left(); + if (_parent->hasFromPhoto()) { + maxRight -= st::msgMargin.right(); + } else { + maxRight -= st::msgMargin.left(); + } + if (isRound && !outbg) { + auto infoWidth = _parent->infoWidth(); + + // This is just some arbitrary point, + // the main idea is to make info left aligned here. + fullRight += infoWidth - st::normalFont->height; + if (fullRight > maxRight) { + fullRight = maxRight; + } + } + if (isRound || needInfoDisplay()) { + _parent->drawInfo(p, fullRight, fullBottom, 2 * paintx + paintw, selected, isRound ? InfoDisplayType::Background : InfoDisplayType::Image); + } + if (!bubble && _parent->displayRightAction()) { + auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); + if (fastShareLeft + st::historyFastShareSize > maxRight) { + fastShareLeft = (fullRight - st::historyFastShareSize - st::msgDateImgDelta); + fastShareTop -= (st::msgDateImgDelta + st::msgDateImgPadding.y() + st::msgDateFont->height + st::msgDateImgPadding.y()); + } + _parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * paintx + paintw); + } + } +} + +TextState HistoryGif::textState(QPoint point, StateRequest request) const { + auto result = TextState(_parent); + + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { + return result; + } + auto paintx = 0, painty = 0, paintw = width(), painth = height(); + auto bubble = _parent->hasBubble(); + + if (bubble && !_caption.isEmpty()) { + auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right(); + painth -= _caption.countHeight(captionw); + if (isBubbleBottom()) { + painth -= st::msgPadding.bottom(); + } + if (QRect(st::msgPadding.left(), painth, captionw, height() - painth).contains(point)) { + result = TextState(_parent, _caption.getState( + point - QPoint(st::msgPadding.left(), painth), + captionw, + request.forText())); + return result; + } + painth -= st::mediaCaptionSkip; + } + auto outbg = _parent->hasOutLayout(); + auto inWebPage = (_parent->media() != this); + auto isRound = _data->isVideoMessage(); + auto usew = paintw, usex = 0; + auto separateRoundVideo = isSeparateRoundVideo(); + const auto item = _parent->data(); + auto via = separateRoundVideo ? item->Get<HistoryMessageVia>() : nullptr; + auto reply = separateRoundVideo ? item->Get<HistoryMessageReply>() : nullptr; + auto forwarded = separateRoundVideo ? item->Get<HistoryMessageForwarded>() : nullptr; + if (via || reply || forwarded) { + usew = maxWidth() - additionalWidth(via, reply, forwarded); + if (outbg) { + usex = width() - usew; + } + } + if (rtl()) usex = width() - usex - usew; + + if (via || reply || forwarded) { + auto rectw = paintw - usew - st::msgReplyPadding.left(); + auto innerw = rectw - (st::msgReplyPadding.left() + st::msgReplyPadding.right()); + auto recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom(); + auto forwardedHeightReal = forwarded ? forwarded->text.countHeight(innerw) : 0; + auto forwardedHeight = qMin(forwardedHeightReal, kMaxGifForwardedBarLines * st::msgServiceNameFont->height); + if (forwarded) { + recth += forwardedHeight; + } else if (via) { + recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); + } + if (reply) { + recth += st::msgReplyBarSize.height(); + } + auto rectx = outbg ? 0 : (usew + st::msgReplyPadding.left()); + auto recty = painty; + if (rtl()) rectx = width() - rectx - rectw; + + if (forwarded) { + if (QRect(rectx, recty, rectw, st::msgReplyPadding.top() + forwardedHeight).contains(point)) { + auto breakEverywhere = (forwardedHeightReal > forwardedHeight); + auto textRequest = request.forText(); + if (breakEverywhere) { + textRequest.flags |= Text::StateRequest::Flag::BreakEverywhere; + } + result = TextState(_parent, forwarded->text.getState( + point - QPoint(rectx + st::msgReplyPadding.left(), recty + st::msgReplyPadding.top()), + innerw, + textRequest)); + result.symbol = 0; + result.afterSymbol = false; + if (breakEverywhere) { + result.cursor = CursorState::Forwarded; + } else { + result.cursor = CursorState::None; + } + return result; + } + recty += forwardedHeight; + recth -= forwardedHeight; + } else if (via) { + auto viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom()); + if (QRect(rectx, recty, rectw, viah).contains(point)) { + result.link = via->link; + return result; + } + auto skip = st::msgServiceNameFont->height + (reply ? 2 * st::msgReplyPadding.top() : 0); + recty += skip; + recth -= skip; + } + if (reply) { + if (QRect(rectx, recty, rectw, recth).contains(point)) { + result.link = reply->replyToLink(); + return result; + } + } + } + if (QRect(usex + paintx, painty, usew, painth).contains(point)) { + if (_data->uploading()) { + result.link = _cancell; + } else if (!_gif || !cAutoPlayGif() || _data->isVideoMessage()) { + result.link = _data->loaded() ? _openl : (_data->loading() ? _cancell : _savel); + } else { + result.link = _openInMediaviewLink; + } + } + if (isRound || _caption.isEmpty()) { + auto fullRight = usex + paintx + usew; + auto fullBottom = painty + painth; + auto maxRight = _parent->width() - st::msgMargin.left(); + if (_parent->hasFromPhoto()) { + maxRight -= st::msgMargin.right(); + } else { + maxRight -= st::msgMargin.left(); + } + if (isRound && !outbg) { + auto infoWidth = _parent->infoWidth(); + + // This is just some arbitrary point, + // the main idea is to make info left aligned here. + fullRight += infoWidth - st::normalFont->height; + if (fullRight > maxRight) { + fullRight = maxRight; + } + } + if (!inWebPage) { + if (_parent->pointInTime(fullRight, fullBottom, point, isRound ? InfoDisplayType::Background : InfoDisplayType::Image)) { + result.cursor = CursorState::Date; + } + } + if (!bubble && _parent->displayRightAction()) { + auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); + if (fastShareLeft + st::historyFastShareSize > maxRight) { + fastShareLeft = (fullRight - st::historyFastShareSize - st::msgDateImgDelta); + fastShareTop -= st::msgDateImgDelta + st::msgDateImgPadding.y() + st::msgDateFont->height + st::msgDateImgPadding.y(); + } + if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) { + result.link = _parent->rightActionLink(); + } + } + } + return result; +} + +TextWithEntities HistoryGif::selectedText(TextSelection selection) const { + return _caption.originalTextWithEntities(selection, ExpandLinksAll); +} + +bool HistoryGif::uploading() const { + return _data->uploading(); +} + +bool HistoryGif::needsBubble() const { + if (_data->isVideoMessage()) { + return false; + } + if (!_caption.isEmpty()) { + return true; + } + const auto item = _parent->data(); + return item->viaBot() + || item->Has<HistoryMessageReply>() + || _parent->displayForwardedFrom() + || _parent->displayFromName(); + return false; +} + +int HistoryGif::additionalWidth() const { + const auto item = _parent->data(); + return additionalWidth( + item->Get<HistoryMessageVia>(), + item->Get<HistoryMessageReply>(), + item->Get<HistoryMessageForwarded>()); +} + +QString HistoryGif::mediaTypeString() const { + return _data->isVideoMessage() + ? lang(lng_in_dlg_video_message) + : qsl("GIF"); +} + +bool HistoryGif::isSeparateRoundVideo() const { + return _data->isVideoMessage() + && (_parent->media() == this) + && !_parent->hasBubble(); +} + +void HistoryGif::setStatusSize(int newSize) const { + if (_data->isVideoMessage()) { + _statusSize = newSize; + if (newSize < 0) { + _statusText = formatDurationText(-newSize - 1); + } else { + _statusText = formatDurationText(_data->duration()); + } + } else { + HistoryFileMedia::setStatusSize(newSize, _data->size, -2, 0); + } +} + +void HistoryGif::updateStatusText() const { + auto showPause = false; + auto statusSize = 0; + auto realDuration = 0; + if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { + statusSize = FileStatusSizeFailed; + } else if (_data->uploading()) { + statusSize = _data->uploadingData->offset; + } else if (_data->loading()) { + statusSize = _data->loadOffset(); + } else if (_data->loaded()) { + statusSize = FileStatusSizeLoaded; + if (const auto video = activeRoundPlayer()) { + statusSize = -1 - _data->duration(); + + const auto state = Media::Player::mixer()->currentState( + AudioMsgId::Type::Voice); + if (state.id == video->audioMsgId() && state.length) { + auto position = int64(0); + if (Media::Player::IsStoppedAtEnd(state.state)) { + position = state.length; + } else if (!Media::Player::IsStoppedOrStopping(state.state)) { + position = state.position; + } + accumulate_max(statusSize, -1 - int((state.length - position) / state.frequency + 1)); + } + } + } else { + statusSize = FileStatusSizeReady; + } + if (statusSize != _statusSize) { + setStatusSize(statusSize); + } +} + +void HistoryGif::refreshParentId(not_null<HistoryItem*> realParent) { + HistoryFileMedia::refreshParentId(realParent); + + const auto fullId = realParent->fullId(); + if (_openInMediaviewLink) { + _openInMediaviewLink->setMessageId(fullId); + } +} + +QString HistoryGif::additionalInfoString() const { + if (_data->isVideoMessage()) { + updateStatusText(); + return _statusText; + } + return QString(); +} + +bool HistoryGif::isReadyForOpen() const { + return _data->loaded(); +} + +void HistoryGif::parentTextUpdated() { + _caption = (_parent->media() == this) + ? createCaption(_parent->data()) + : Text(); + Auth().data().requestViewResize(_parent); +} + +int HistoryGif::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const { + int result = 0; + if (forwarded) { + accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + forwarded->text.maxWidth() + st::msgReplyPadding.right()); + } else if (via) { + accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + via->maxWidth + st::msgReplyPadding.left()); + } + if (reply) { + accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth()); + } + return result; +} + +Media::Player::RoundController *HistoryGif::activeRoundVideo() const { + return App::wnd()->controller()->roundVideo(_parent->data()); +} + +Media::Clip::Reader *HistoryGif::activeRoundPlayer() const { + if (const auto video = activeRoundVideo()) { + if (const auto result = video->reader()) { + if (result->ready()) { + return result; + } + } + } + return nullptr; +} + +Media::Clip::Reader *HistoryGif::currentReader() const { + if (const auto result = activeRoundPlayer()) { + return result; + } + return (_gif && _gif->ready()) ? _gif.get() : nullptr; +} + +Media::Clip::Playback *HistoryGif::videoPlayback() const { + if (const auto video = activeRoundVideo()) { + return video->playback(); + } + return nullptr; +} + +void HistoryGif::clipCallback(Media::Clip::Notification notification) { + using namespace Media::Clip; + + const auto reader = _gif.get(); + if (!reader) { + return; + } + switch (notification) { + case NotificationReinit: { + auto stopped = false; + if (reader->autoPausedGif()) { + auto amVisible = false; + Auth().data().queryItemVisibility().notify( + { _parent->data(), &amVisible }, + true); + if (!amVisible) { // Stop animation if it is not visible. + stopAnimation(); + stopped = true; + } + } + if (!stopped) { + Auth().data().requestViewResize(_parent); + } + } break; + + case NotificationRepaint: { + if (!reader->currentDisplayed()) { + Auth().data().requestViewRepaint(_parent); + } + } break; + } +} + +void HistoryGif::playAnimation(bool autoplay) { + if (_data->isVideoMessage() && !autoplay) { + return; + } else if (_gif && autoplay) { + return; + } + using Mode = Media::Clip::Reader::Mode; + if (_gif) { + stopAnimation(); + } else if (_data->loaded(DocumentData::FilePathResolveChecked)) { + if (!cAutoPlayGif()) { + Auth().data().stopAutoplayAnimations(); + } + setClipReader(Media::Clip::MakeReader( + _data, + _parent->data()->fullId(), + [=](auto notification) { clipCallback(notification); }, + Mode::Gif)); + if (_gif && autoplay) { + _gif->setAutoplay(); + } + } +} + +void HistoryGif::stopAnimation() { + if (_gif) { + clearClipReader(); + Auth().data().requestViewResize(_parent); + _data->unload(); + } +} + +void HistoryGif::setClipReader(Media::Clip::ReaderPointer gif) { + if (_gif) { + Auth().data().unregisterAutoplayAnimation(_gif.get()); + } + _gif = std::move(gif); + if (_gif) { + Auth().data().registerAutoplayAnimation(_gif.get(), _parent); + } +} + +HistoryGif::~HistoryGif() { + clearClipReader(); +} + +float64 HistoryGif::dataProgress() const { + return (_data->uploading() || _parent->data()->id > 0) + ? _data->progress() + : 0; +} + +bool HistoryGif::dataFinished() const { + return (_parent->data()->id > 0) + ? (!_data->loading() && !_data->uploading()) + : false; +} + +bool HistoryGif::dataLoaded() const { + return (_parent->data()->id > 0) ? _data->loaded() : false; +} + +bool HistoryGif::needInfoDisplay() const { + return (_parent->data()->id < 0 || _parent->isUnderCursor()); +} diff --git a/Telegram/SourceFiles/history/media/history_media_gif.h b/Telegram/SourceFiles/history/media/history_media_gif.h new file mode 100644 index 000000000..ad0f78f93 --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_gif.h @@ -0,0 +1,120 @@ +/* +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/media/history_media_file.h" + +struct HistoryMessageVia; +struct HistoryMessageReply; +struct HistoryMessageForwarded; + +namespace Media { +namespace Clip { +class Playback; +} // namespace Clip + +namespace Player { +class RoundController; +} // namespace Player +} // namespace Media + +class HistoryGif : public HistoryFileMedia { +public: + HistoryGif( + not_null<Element*> parent, + not_null<DocumentData*> document); + + HistoryMediaType type() const override { + return MediaTypeGif; + } + + void refreshParentId(not_null<HistoryItem*> realParent) override; + + void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; + TextState textState(QPoint point, StateRequest request) const override; + + [[nodiscard]] TextSelection adjustSelection( + TextSelection selection, + TextSelectType type) const override { + return _caption.adjustSelection(selection, type); + } + uint16 fullSelectionLength() const override { + return _caption.length(); + } + bool hasTextForCopy() const override { + return !_caption.isEmpty(); + } + + TextWithEntities selectedText(TextSelection selection) const override; + + bool uploading() const override; + + DocumentData *getDocument() const override { + return _data; + } + + void stopAnimation() override; + + TextWithEntities getCaption() const override { + return _caption.originalTextWithEntities(); + } + bool needsBubble() const override; + bool customInfoLayout() const override { + return _caption.isEmpty(); + } + QString additionalInfoString() const override; + + bool skipBubbleTail() const override { + return isBubbleBottom() && _caption.isEmpty(); + } + bool isReadyForOpen() const override; + + void parentTextUpdated() override; + + ~HistoryGif(); + +protected: + float64 dataProgress() const override; + bool dataFinished() const override; + bool dataLoaded() const override; + + void setClipReader(Media::Clip::ReaderPointer gif); + void clearClipReader() { + setClipReader(Media::Clip::ReaderPointer()); + } + +private: + void playAnimation(bool autoplay) override; + QSize countOptimalSize() override; + QSize countCurrentSize(int newWidth) override; + Media::Player::RoundController *activeRoundVideo() const; + Media::Clip::Reader *activeRoundPlayer() const; + Media::Clip::Reader *currentReader() const; + Media::Clip::Playback *videoPlayback() const; + void clipCallback(Media::Clip::Notification notification); + + bool needInfoDisplay() const; + int additionalWidth( + const HistoryMessageVia *via, + const HistoryMessageReply *reply, + const HistoryMessageForwarded *forwarded) const; + int additionalWidth() const; + QString mediaTypeString() const; + bool isSeparateRoundVideo() const; + + not_null<DocumentData*> _data; + FileClickHandlerPtr _openInMediaviewLink; + int _thumbw = 1; + int _thumbh = 1; + Text _caption; + Media::Clip::ReaderPointer _gif; + + void setStatusSize(int newSize) const; + void updateStatusText() const; + +}; diff --git a/Telegram/SourceFiles/history/history_media_grouped.cpp b/Telegram/SourceFiles/history/media/history_media_grouped.cpp similarity index 99% rename from Telegram/SourceFiles/history/history_media_grouped.cpp rename to Telegram/SourceFiles/history/media/history_media_grouped.cpp index 0bdc58281..7104b32e0 100644 --- a/Telegram/SourceFiles/history/history_media_grouped.cpp +++ b/Telegram/SourceFiles/history/media/history_media_grouped.cpp @@ -5,10 +5,9 @@ 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/history_media_grouped.h" +#include "history/media/history_media_grouped.h" #include "history/history_item_components.h" -#include "history/history_media_types.h" #include "history/history_message.h" #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" diff --git a/Telegram/SourceFiles/history/history_media_grouped.h b/Telegram/SourceFiles/history/media/history_media_grouped.h similarity index 98% rename from Telegram/SourceFiles/history/history_media_grouped.h rename to Telegram/SourceFiles/history/media/history_media_grouped.h index 8320312f0..211c81507 100644 --- a/Telegram/SourceFiles/history/history_media_grouped.h +++ b/Telegram/SourceFiles/history/media/history_media_grouped.h @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "history/history_media.h" +#include "history/media/history_media.h" #include "data/data_document.h" #include "data/data_photo.h" diff --git a/Telegram/SourceFiles/history/media/history_media_invoice.cpp b/Telegram/SourceFiles/history/media/history_media_invoice.cpp new file mode 100644 index 000000000..501c5dfa1 --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_invoice.cpp @@ -0,0 +1,447 @@ +/* +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/media/history_media_invoice.h" + +#include "lang/lang_keys.h" +#include "layout.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_cursor_state.h" +#include "history/media/history_media_photo.h" +#include "history/media/history_media_common.h" +#include "ui/text_options.h" +#include "data/data_media_types.h" +#include "styles/style_history.h" + +namespace { + +using TextState = HistoryView::TextState; + +} // namespace + +HistoryInvoice::HistoryInvoice( + not_null<Element*> parent, + not_null<Data::Invoice*> invoice) +: HistoryMedia(parent) +, _title(st::msgMinWidth) +, _description(st::msgMinWidth) +, _status(st::msgMinWidth) { + fillFromData(invoice); +} + +void HistoryInvoice::fillFromData(not_null<Data::Invoice*> invoice) { + if (invoice->photo) { + _attach = std::make_unique<HistoryPhoto>( + _parent, + _parent->data(), + invoice->photo); + } else { + _attach = nullptr; + } + auto labelText = [&] { + if (invoice->receiptMsgId) { + if (invoice->isTest) { + return lang(lng_payments_receipt_label_test); + } + return lang(lng_payments_receipt_label); + } else if (invoice->isTest) { + return lang(lng_payments_invoice_label_test); + } + return lang(lng_payments_invoice_label); + }; + auto statusText = TextWithEntities { + FillAmountAndCurrency(invoice->amount, invoice->currency), + EntitiesInText() + }; + statusText.entities.push_back(EntityInText(EntityInTextBold, 0, statusText.text.size())); + statusText.text += ' ' + labelText().toUpper(); + _status.setMarkedText( + st::defaultTextStyle, + statusText, + Ui::ItemTextOptions(_parent->data())); + + _receiptMsgId = invoice->receiptMsgId; + + // init strings + if (!invoice->description.isEmpty()) { + auto marked = TextWithEntities { invoice->description }; + auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText; + TextUtilities::ParseEntities(marked, parseFlags); + _description.setMarkedText( + st::webPageDescriptionStyle, + marked, + Ui::WebpageTextDescriptionOptions()); + } + if (!invoice->title.isEmpty()) { + _title.setText( + st::webPageTitleStyle, + invoice->title, + Ui::WebpageTextTitleOptions()); + } +} + +QSize HistoryInvoice::countOptimalSize() { + auto lineHeight = unitedLineHeight(); + + if (_attach) { + if (_status.hasSkipBlock()) { + _status.removeSkipBlock(); + } + } else { + _status.updateSkipBlock( + _parent->skipBlockWidth(), + _parent->skipBlockHeight()); + } + + // init dimensions + auto l = st::msgPadding.left(), r = st::msgPadding.right(); + auto skipBlockWidth = _parent->skipBlockWidth(); + auto maxWidth = skipBlockWidth; + auto minHeight = 0; + + auto titleMinHeight = _title.isEmpty() ? 0 : lineHeight; + // enable any count of lines in game description / message + auto descMaxLines = 4096; + auto descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * lineHeight); + + if (!_title.isEmpty()) { + accumulate_max(maxWidth, _title.maxWidth()); + minHeight += titleMinHeight; + } + if (!_description.isEmpty()) { + accumulate_max(maxWidth, _description.maxWidth()); + minHeight += descriptionMinHeight; + } + if (_attach) { + auto attachAtTop = _title.isEmpty() && _description.isEmpty(); + if (!attachAtTop) minHeight += st::mediaInBubbleSkip; + + _attach->initDimensions(); + auto bubble = _attach->bubbleMargins(); + auto maxMediaWidth = _attach->maxWidth() - bubble.left() - bubble.right(); + if (isBubbleBottom() && _attach->customInfoLayout()) { + maxMediaWidth += skipBlockWidth; + } + accumulate_max(maxWidth, maxMediaWidth); + minHeight += _attach->minHeight() - bubble.top() - bubble.bottom(); + } else { + accumulate_max(maxWidth, _status.maxWidth()); + minHeight += st::mediaInBubbleSkip + _status.minHeight(); + } + auto padding = inBubblePadding(); + maxWidth += padding.left() + padding.right(); + minHeight += padding.top() + padding.bottom(); + return { maxWidth, minHeight }; +} + +QSize HistoryInvoice::countCurrentSize(int newWidth) { + accumulate_min(newWidth, maxWidth()); + auto innerWidth = newWidth - st::msgPadding.left() - st::msgPadding.right(); + + auto lineHeight = unitedLineHeight(); + + auto newHeight = 0; + if (_title.isEmpty()) { + _titleHeight = 0; + } else { + if (_title.countHeight(innerWidth) < 2 * st::webPageTitleFont->height) { + _titleHeight = lineHeight; + } else { + _titleHeight = 2 * lineHeight; + } + newHeight += _titleHeight; + } + + if (_description.isEmpty()) { + _descriptionHeight = 0; + } else { + _descriptionHeight = _description.countHeight(innerWidth); + newHeight += _descriptionHeight; + } + + if (_attach) { + auto attachAtTop = !_title.isEmpty() && _description.isEmpty(); + if (!attachAtTop) newHeight += st::mediaInBubbleSkip; + + QMargins bubble(_attach->bubbleMargins()); + + _attach->resizeGetHeight(innerWidth + bubble.left() + bubble.right()); + newHeight += _attach->height() - bubble.top() - bubble.bottom(); + if (isBubbleBottom() && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > innerWidth + bubble.left() + bubble.right()) { + newHeight += bottomInfoPadding(); + } + } else { + newHeight += st::mediaInBubbleSkip + _status.countHeight(innerWidth); + } + auto padding = inBubblePadding(); + newHeight += padding.top() + padding.bottom(); + + return { newWidth, newHeight }; +} + +TextSelection HistoryInvoice::toDescriptionSelection( + TextSelection selection) const { + return HistoryView::UnshiftItemSelection(selection, _title); +} + +TextSelection HistoryInvoice::fromDescriptionSelection( + TextSelection selection) const { + return HistoryView::ShiftItemSelection(selection, _title); +} + +void HistoryInvoice::refreshParentId(not_null<HistoryItem*> realParent) { + if (_attach) { + _attach->refreshParentId(realParent); + } +} + +void HistoryInvoice::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; + auto paintw = width(), painth = height(); + + auto outbg = _parent->hasOutLayout(); + bool selected = (selection == FullSelection); + + auto &barfg = selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor); + auto &semibold = selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg); + auto ®ular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg); + + QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); + auto padding = inBubblePadding(); + auto tshift = padding.top(); + auto bshift = padding.bottom(); + paintw -= padding.left() + padding.right(); + if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) { + bshift += bottomInfoPadding(); + } + + auto lineHeight = unitedLineHeight(); + if (_titleHeight) { + p.setPen(semibold); + p.setTextPalette(selected ? (outbg ? st::outTextPaletteSelected : st::inTextPaletteSelected) : (outbg ? st::outSemiboldPalette : st::inSemiboldPalette)); + + auto endskip = 0; + if (_title.hasSkipBlock()) { + endskip = _parent->skipBlockWidth(); + } + _title.drawLeftElided(p, padding.left(), tshift, paintw, width(), _titleHeight / lineHeight, style::al_left, 0, -1, endskip, false, selection); + tshift += _titleHeight; + + p.setTextPalette(selected ? (outbg ? st::outTextPaletteSelected : st::inTextPaletteSelected) : (outbg ? st::outTextPalette : st::inTextPalette)); + } + if (_descriptionHeight) { + p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg); + _description.drawLeft(p, padding.left(), tshift, paintw, width(), style::al_left, 0, -1, toDescriptionSelection(selection)); + tshift += _descriptionHeight; + } + if (_attach) { + auto attachAtTop = !_titleHeight && !_descriptionHeight; + if (!attachAtTop) tshift += st::mediaInBubbleSkip; + + auto attachLeft = padding.left() - bubble.left(); + auto attachTop = tshift - bubble.top(); + if (rtl()) attachLeft = width() - attachLeft - _attach->width(); + + auto attachSelection = selected ? FullSelection : TextSelection { 0, 0 }; + + p.translate(attachLeft, attachTop); + _attach->draw(p, r.translated(-attachLeft, -attachTop), attachSelection, ms); + auto pixwidth = _attach->width(); + auto pixheight = _attach->height(); + + auto available = _status.maxWidth(); + auto statusW = available + 2 * st::msgDateImgPadding.x(); + auto statusH = st::msgDateFont->height + 2 * st::msgDateImgPadding.y(); + auto statusX = st::msgDateImgDelta; + auto statusY = st::msgDateImgDelta; + + App::roundRect(p, rtlrect(statusX, statusY, statusW, statusH, pixwidth), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); + + p.setFont(st::msgDateFont); + p.setPen(st::msgDateImgFg); + _status.drawLeftElided(p, statusX + st::msgDateImgPadding.x(), statusY + st::msgDateImgPadding.y(), available, pixwidth); + + p.translate(-attachLeft, -attachTop); + } else { + p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg); + _status.drawLeft(p, padding.left(), tshift + st::mediaInBubbleSkip, paintw, width()); + } +} + +TextState HistoryInvoice::textState(QPoint point, StateRequest request) const { + auto result = TextState(_parent); + + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { + return result; + } + auto paintw = width(), painth = height(); + + QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); + auto padding = inBubblePadding(); + auto tshift = padding.top(); + auto bshift = padding.bottom(); + if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) { + bshift += bottomInfoPadding(); + } + paintw -= padding.left() + padding.right(); + + auto lineHeight = unitedLineHeight(); + auto symbolAdd = 0; + if (_titleHeight) { + if (point.y() >= tshift && point.y() < tshift + _titleHeight) { + Text::StateRequestElided titleRequest = request.forText(); + titleRequest.lines = _titleHeight / lineHeight; + result = TextState(_parent, _title.getStateElidedLeft( + point - QPoint(padding.left(), tshift), + paintw, + width(), + titleRequest)); + } else if (point.y() >= tshift + _titleHeight) { + symbolAdd += _title.length(); + } + tshift += _titleHeight; + } + if (_descriptionHeight) { + if (point.y() >= tshift && point.y() < tshift + _descriptionHeight) { + result = TextState(_parent, _description.getStateLeft( + point - QPoint(padding.left(), tshift), + paintw, + width(), + request.forText())); + } else if (point.y() >= tshift + _descriptionHeight) { + symbolAdd += _description.length(); + } + tshift += _descriptionHeight; + } + if (_attach) { + auto attachAtTop = !_titleHeight && !_descriptionHeight; + if (!attachAtTop) tshift += st::mediaInBubbleSkip; + + auto attachLeft = padding.left() - bubble.left(); + auto attachTop = tshift - bubble.top(); + if (rtl()) attachLeft = width() - attachLeft - _attach->width(); + + if (QRect(attachLeft, tshift, _attach->width(), height() - tshift - bshift).contains(point)) { + result = _attach->textState(point - QPoint(attachLeft, attachTop), request); + } + } + + result.symbol += symbolAdd; + return result; +} + +TextSelection HistoryInvoice::adjustSelection(TextSelection selection, TextSelectType type) const { + if (!_descriptionHeight || selection.to <= _title.length()) { + return _title.adjustSelection(selection, type); + } + auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type); + if (selection.from >= _title.length()) { + return fromDescriptionSelection(descriptionSelection); + } + auto titleSelection = _title.adjustSelection(selection, type); + return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to }; +} + +void HistoryInvoice::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { + if (_attach) { + _attach->clickHandlerActiveChanged(p, active); + } +} + +void HistoryInvoice::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { + if (_attach) { + _attach->clickHandlerPressedChanged(p, pressed); + } +} + +TextWithEntities HistoryInvoice::selectedText(TextSelection selection) const { + auto titleResult = _title.originalTextWithEntities( + selection, + ExpandLinksAll); + auto descriptionResult = _description.originalTextWithEntities( + toDescriptionSelection(selection), + ExpandLinksAll); + if (titleResult.text.isEmpty()) { + return descriptionResult; + } else if (descriptionResult.text.isEmpty()) { + return titleResult; + } + + titleResult.text += '\n'; + TextUtilities::Append(titleResult, std::move(descriptionResult)); + return titleResult; +} + +QMargins HistoryInvoice::inBubblePadding() const { + auto lshift = st::msgPadding.left(); + auto rshift = st::msgPadding.right(); + auto bshift = isBubbleBottom() ? st::msgPadding.top() : st::mediaInBubbleSkip; + auto tshift = isBubbleTop() ? st::msgPadding.bottom() : st::mediaInBubbleSkip; + return QMargins(lshift, tshift, rshift, bshift); +} + +int HistoryInvoice::bottomInfoPadding() const { + if (!isBubbleBottom()) return 0; + + auto result = st::msgDateFont->height; + return result; +} + +QString FillAmountAndCurrency(uint64 amount, const QString ¤cy) { + static const auto ShortCurrencyNames = QMap<QString, QString> { + { qsl("USD"), QString::fromUtf8("\x24") }, + { qsl("GBP"), QString::fromUtf8("\xC2\xA3") }, + { qsl("EUR"), QString::fromUtf8("\xE2\x82\xAC") }, + { qsl("JPY"), QString::fromUtf8("\xC2\xA5") }, + }; + static const auto Denominators = QMap<QString, int> { + { qsl("CLF"), 10000 }, + { qsl("BHD"), 1000 }, + { qsl("IQD"), 1000 }, + { qsl("JOD"), 1000 }, + { qsl("KWD"), 1000 }, + { qsl("LYD"), 1000 }, + { qsl("OMR"), 1000 }, + { qsl("TND"), 1000 }, + { qsl("BIF"), 1 }, + { qsl("BYR"), 1 }, + { qsl("CLP"), 1 }, + { qsl("CVE"), 1 }, + { qsl("DJF"), 1 }, + { qsl("GNF"), 1 }, + { qsl("ISK"), 1 }, + { qsl("JPY"), 1 }, + { qsl("KMF"), 1 }, + { qsl("KRW"), 1 }, + { qsl("MGA"), 1 }, + { qsl("PYG"), 1 }, + { qsl("RWF"), 1 }, + { qsl("UGX"), 1 }, + { qsl("UYI"), 1 }, + { qsl("VND"), 1 }, + { qsl("VUV"), 1 }, + { qsl("XAF"), 1 }, + { qsl("XOF"), 1 }, + { qsl("XPF"), 1 }, + { qsl("MRO"), 10 }, + }; + const auto currencyText = ShortCurrencyNames.value(currency, currency); + const auto denominator = Denominators.value(currency, 100); + const auto currencyValue = amount / float64(denominator); + const auto digits = [&] { + auto result = 0; + for (auto test = 1; test < denominator; test *= 10) { + ++result; + } + return result; + }(); + return QLocale::system().toCurrencyString(currencyValue, currencyText); + //auto amountBucks = amount / 100; + //auto amountCents = amount % 100; + //auto amountText = qsl("%1,%2").arg(amountBucks).arg(amountCents, 2, 10, QChar('0')); + //return currencyText + amountText; +} diff --git a/Telegram/SourceFiles/history/media/history_media_invoice.h b/Telegram/SourceFiles/history/media/history_media_invoice.h new file mode 100644 index 000000000..36c9d73f7 --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_invoice.h @@ -0,0 +1,98 @@ +/* +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/media/history_media.h" + +namespace Data { +struct Invoice; +} // namespace Data + +class HistoryInvoice : public HistoryMedia { +public: + HistoryInvoice( + not_null<Element*> parent, + not_null<Data::Invoice*> invoice); + + HistoryMediaType type() const override { + return MediaTypeInvoice; + } + + void refreshParentId(not_null<HistoryItem*> realParent) override; + + MsgId getReceiptMsgId() const { + return _receiptMsgId; + } + QString getTitle() const { + return _title.originalText(); + } + + bool hideMessageText() const override { + return false; + } + + void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; + TextState textState(QPoint point, StateRequest request) const override; + + [[nodiscard]] TextSelection adjustSelection( + TextSelection selection, + TextSelectType type) const override; + uint16 fullSelectionLength() const override { + return _title.length() + _description.length(); + } + bool hasTextForCopy() const override { + return false; // we do not add _title and _description in FullSelection text copy. + } + + bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { + return _attach && _attach->toggleSelectionByHandlerClick(p); + } + bool dragItemByHandler(const ClickHandlerPtr &p) const override { + return _attach && _attach->dragItemByHandler(p); + } + + TextWithEntities selectedText(TextSelection selection) const override; + + void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; + void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; + + bool needsBubble() const override { + return true; + } + bool customInfoLayout() const override { + return false; + } + + HistoryMedia *attach() const { + return _attach.get(); + } + +private: + QSize countOptimalSize() override; + QSize countCurrentSize(int newWidth) override; + + void fillFromData(not_null<Data::Invoice*> invoice); + + TextSelection toDescriptionSelection(TextSelection selection) const; + TextSelection fromDescriptionSelection(TextSelection selection) const; + QMargins inBubblePadding() const; + int bottomInfoPadding() const; + + std::unique_ptr<HistoryMedia> _attach; + + int _titleHeight = 0; + int _descriptionHeight = 0; + Text _title; + Text _description; + Text _status; + + MsgId _receiptMsgId = 0; + +}; + +QString FillAmountAndCurrency(uint64 amount, const QString ¤cy); diff --git a/Telegram/SourceFiles/history/media/history_media_location.cpp b/Telegram/SourceFiles/history/media/history_media_location.cpp new file mode 100644 index 000000000..2055310d1 --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_location.cpp @@ -0,0 +1,311 @@ +/* +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/media/history_media_location.h" + +#include "layout.h" +#include "history/history_item_components.h" +#include "history/history_item.h" +#include "history/history_location_manager.h" +#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 "styles/style_history.h" + +namespace { + +using TextState = HistoryView::TextState; + +} // namespace + +HistoryLocation::HistoryLocation( + not_null<Element*> parent, + not_null<LocationData*> location, + const QString &title, + const QString &description) +: HistoryMedia(parent) +, _data(location) +, _title(st::msgMinWidth) +, _description(st::msgMinWidth) +, _link(std::make_shared<LocationClickHandler>(_data->coords)) { + if (!title.isEmpty()) { + _title.setText( + st::webPageTitleStyle, + TextUtilities::Clean(title), + Ui::WebpageTextTitleOptions()); + } + if (!description.isEmpty()) { + _description.setMarkedText( + st::webPageDescriptionStyle, + TextUtilities::ParseEntities( + TextUtilities::Clean(description), + TextParseLinks | TextParseMultiline | TextParseRichText), + Ui::WebpageTextDescriptionOptions()); + } +} + +QSize HistoryLocation::countOptimalSize() { + auto tw = fullWidth(); + auto th = fullHeight(); + if (tw > st::maxMediaSize) { + th = (st::maxMediaSize * th) / tw; + tw = st::maxMediaSize; + } + auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + auto maxWidth = qMax(tw, minWidth); + auto minHeight = qMax(th, st::minPhotoSize); + + if (_parent->hasBubble()) { + if (!_title.isEmpty()) { + minHeight += qMin(_title.countHeight(maxWidth - st::msgPadding.left() - st::msgPadding.right()), 2 * st::webPageTitleFont->height); + } + if (!_description.isEmpty()) { + minHeight += qMin(_description.countHeight(maxWidth - st::msgPadding.left() - st::msgPadding.right()), 3 * st::webPageDescriptionFont->height); + } + if (!_title.isEmpty() || !_description.isEmpty()) { + minHeight += st::mediaInBubbleSkip; + if (isBubbleTop()) { + minHeight += st::msgPadding.top(); + } + } + } + return { maxWidth, minHeight }; +} + +QSize HistoryLocation::countCurrentSize(int newWidth) { + accumulate_min(newWidth, maxWidth()); + + auto tw = fullWidth(); + auto th = fullHeight(); + if (tw > st::maxMediaSize) { + th = (st::maxMediaSize * th) / tw; + tw = st::maxMediaSize; + } + auto newHeight = th; + if (tw > newWidth) { + newHeight = (newWidth * newHeight / tw); + } else { + newWidth = tw; + } + auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + accumulate_max(newWidth, minWidth); + accumulate_max(newHeight, st::minPhotoSize); + if (_parent->hasBubble()) { + if (!_title.isEmpty()) { + newHeight += qMin(_title.countHeight(newWidth - st::msgPadding.left() - st::msgPadding.right()), st::webPageTitleFont->height * 2); + } + if (!_description.isEmpty()) { + newHeight += qMin(_description.countHeight(newWidth - st::msgPadding.left() - st::msgPadding.right()), st::webPageDescriptionFont->height * 3); + } + if (!_title.isEmpty() || !_description.isEmpty()) { + newHeight += st::mediaInBubbleSkip; + if (isBubbleTop()) { + newHeight += st::msgPadding.top(); + } + } + } + return { newWidth, newHeight }; +} + +TextSelection HistoryLocation::toDescriptionSelection( + TextSelection selection) const { + return HistoryView::UnshiftItemSelection(selection, _title); +} + +TextSelection HistoryLocation::fromDescriptionSelection( + TextSelection selection) const { + return HistoryView::ShiftItemSelection(selection, _title); +} + +void HistoryLocation::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; + auto paintx = 0, painty = 0, paintw = width(), painth = height(); + bool bubble = _parent->hasBubble(); + auto outbg = _parent->hasOutLayout(); + bool selected = (selection == FullSelection); + + if (bubble) { + if (!_title.isEmpty() || !_description.isEmpty()) { + if (isBubbleTop()) { + painty += st::msgPadding.top(); + } + } + + auto textw = width() - st::msgPadding.left() - st::msgPadding.right(); + + if (!_title.isEmpty()) { + p.setPen(outbg ? st::webPageTitleOutFg : st::webPageTitleInFg); + _title.drawLeftElided(p, paintx + st::msgPadding.left(), painty, textw, width(), 2, style::al_left, 0, -1, 0, false, selection); + painty += qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height); + } + if (!_description.isEmpty()) { + p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg); + _description.drawLeftElided(p, paintx + st::msgPadding.left(), painty, textw, width(), 3, style::al_left, 0, -1, 0, false, toDescriptionSelection(selection)); + painty += qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height); + } + if (!_title.isEmpty() || !_description.isEmpty()) { + painty += st::mediaInBubbleSkip; + } + painth -= painty; + } else { + App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); + } + + const auto contextId = _parent->data()->fullId(); + _data->load(contextId); + auto roundRadius = ImageRoundRadius::Large; + auto roundCorners = ((isBubbleTop() && _title.isEmpty() && _description.isEmpty()) ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None) + | (isBubbleBottom() ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None); + auto rthumb = QRect(paintx, painty, paintw, painth); + if (_data && !_data->thumb->isNull()) { + const auto &pix = _data->thumb->pixSingle(contextId, paintw, painth, paintw, painth, roundRadius, roundCorners); + p.drawPixmap(rthumb.topLeft(), pix); + } else { + App::complexLocationRect(p, rthumb, roundRadius, roundCorners); + } + const auto paintMarker = [&](const style::icon &icon) { + icon.paint( + p, + rthumb.x() + ((rthumb.width() - icon.width()) / 2), + rthumb.y() + (rthumb.height() / 2) - icon.height(), + width()); + }; + paintMarker(st::historyMapPoint); + paintMarker(st::historyMapPointInner); + if (selected) { + App::complexOverlayRect(p, rthumb, roundRadius, roundCorners); + } + + if (_parent->media() == this) { + auto fullRight = paintx + paintw; + auto fullBottom = height(); + _parent->drawInfo(p, fullRight, fullBottom, paintx * 2 + paintw, selected, InfoDisplayType::Image); + if (!bubble && _parent->displayRightAction()) { + auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); + _parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * paintx + paintw); + } + } +} + +TextState HistoryLocation::textState(QPoint point, StateRequest request) const { + auto result = TextState(_parent); + auto symbolAdd = 0; + + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { + return result; + } + auto paintx = 0, painty = 0, paintw = width(), painth = height(); + bool bubble = _parent->hasBubble(); + + if (bubble) { + if (!_title.isEmpty() || !_description.isEmpty()) { + if (isBubbleTop()) { + painty += st::msgPadding.top(); + } + } + + auto textw = width() - st::msgPadding.left() - st::msgPadding.right(); + + if (!_title.isEmpty()) { + auto titleh = qMin(_title.countHeight(textw), 2 * st::webPageTitleFont->height); + if (point.y() >= painty && point.y() < painty + titleh) { + result = TextState(_parent, _title.getStateLeft( + point - QPoint(paintx + st::msgPadding.left(), painty), + textw, + width(), + request.forText())); + return result; + } else if (point.y() >= painty + titleh) { + symbolAdd += _title.length(); + } + painty += titleh; + } + if (!_description.isEmpty()) { + auto descriptionh = qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height); + if (point.y() >= painty && point.y() < painty + descriptionh) { + result = TextState(_parent, _description.getStateLeft( + point - QPoint(paintx + st::msgPadding.left(), painty), + textw, + width(), + request.forText())); + } else if (point.y() >= painty + descriptionh) { + symbolAdd += _description.length(); + } + painty += descriptionh; + } + if (!_title.isEmpty() || !_description.isEmpty()) { + painty += st::mediaInBubbleSkip; + } + painth -= painty; + } + if (QRect(paintx, painty, paintw, painth).contains(point) && _data) { + result.link = _link; + } + if (_parent->media() == this) { + auto fullRight = paintx + paintw; + auto fullBottom = height(); + if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) { + result.cursor = CursorState::Date; + } + if (!bubble && _parent->displayRightAction()) { + auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); + if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) { + result.link = _parent->rightActionLink(); + } + } + } + result.symbol += symbolAdd; + return result; +} + +TextSelection HistoryLocation::adjustSelection(TextSelection selection, TextSelectType type) const { + if (_description.isEmpty() || selection.to <= _title.length()) { + return _title.adjustSelection(selection, type); + } + auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type); + if (selection.from >= _title.length()) { + return fromDescriptionSelection(descriptionSelection); + } + auto titleSelection = _title.adjustSelection(selection, type); + return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to }; +} + +TextWithEntities HistoryLocation::selectedText(TextSelection selection) const { + auto titleResult = _title.originalTextWithEntities(selection); + auto descriptionResult = _description.originalTextWithEntities(toDescriptionSelection(selection)); + if (titleResult.text.isEmpty()) { + return descriptionResult; + } else if (descriptionResult.text.isEmpty()) { + return titleResult; + } + titleResult.text += '\n'; + TextUtilities::Append(titleResult, std::move(descriptionResult)); + return titleResult; +} + +bool HistoryLocation::needsBubble() const { + if (!_title.isEmpty() || !_description.isEmpty()) { + return true; + } + const auto item = _parent->data(); + return item->viaBot() + || item->Has<HistoryMessageReply>() + || _parent->displayForwardedFrom() + || _parent->displayFromName(); + return false; +} + +int HistoryLocation::fullWidth() const { + return st::locationSize.width(); +} + +int HistoryLocation::fullHeight() const { + return st::locationSize.height(); +} diff --git a/Telegram/SourceFiles/history/media/history_media_location.h b/Telegram/SourceFiles/history/media/history_media_location.h new file mode 100644 index 000000000..1fcbd0b2c --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_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 "history/media/history_media.h" + +class LocationCoords; +struct LocationData; + +class HistoryLocation : public HistoryMedia { +public: + HistoryLocation( + not_null<Element*> parent, + not_null<LocationData*> location, + const QString &title = QString(), + const QString &description = QString()); + + HistoryMediaType type() const override { + return MediaTypeLocation; + } + + void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; + TextState textState(QPoint point, StateRequest request) const override; + + [[nodiscard]] TextSelection adjustSelection( + TextSelection selection, + TextSelectType type) const override; + uint16 fullSelectionLength() const override { + return _title.length() + _description.length(); + } + bool hasTextForCopy() const override { + return !_title.isEmpty() || !_description.isEmpty(); + } + + bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { + return p == _link; + } + bool dragItemByHandler(const ClickHandlerPtr &p) const override { + return p == _link; + } + + TextWithEntities selectedText(TextSelection selection) const override; + + bool needsBubble() const override; + bool customInfoLayout() const override { + return true; + } + + bool skipBubbleTail() const override { + return isBubbleBottom(); + } + +private: + QSize countOptimalSize() override; + QSize countCurrentSize(int newWidth) override; + + TextSelection toDescriptionSelection(TextSelection selection) const; + TextSelection fromDescriptionSelection(TextSelection selection) const; + + LocationData *_data; + Text _title, _description; + ClickHandlerPtr _link; + + int fullWidth() const; + int fullHeight() const; + +}; diff --git a/Telegram/SourceFiles/history/media/history_media_photo.cpp b/Telegram/SourceFiles/history/media/history_media_photo.cpp new file mode 100644 index 000000000..244b3b9e5 --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_photo.cpp @@ -0,0 +1,530 @@ +/* +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/media/history_media_photo.h" + +#include "layout.h" +#include "auth_session.h" +#include "history/history_item_components.h" +#include "history/history_item.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_cursor_state.h" +#include "history/media/history_media_common.h" +#include "ui/image/image.h" +#include "ui/grouped_layout.h" +#include "data/data_session.h" +#include "data/data_photo.h" +#include "styles/style_history.h" + +namespace { + +using TextState = HistoryView::TextState; + +} // namespace + +HistoryPhoto::HistoryPhoto( + not_null<Element*> parent, + not_null<HistoryItem*> realParent, + not_null<PhotoData*> photo) +: HistoryFileMedia(parent, realParent) +, _data(photo) +, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { + const auto fullId = realParent->fullId(); + setLinks( + std::make_shared<PhotoOpenClickHandler>(_data, fullId), + std::make_shared<PhotoSaveClickHandler>(_data, fullId), + std::make_shared<PhotoCancelClickHandler>(_data, fullId)); + _caption = createCaption(realParent); + create(realParent->fullId()); +} + +HistoryPhoto::HistoryPhoto( + not_null<Element*> parent, + not_null<PeerData*> chat, + not_null<PhotoData*> photo, + int width) +: HistoryFileMedia(parent, parent->data()) +, _data(photo) +, _serviceWidth(width) { + create(parent->data()->fullId(), chat); +} + +void HistoryPhoto::create(FullMsgId contextId, PeerData *chat) { + setLinks( + std::make_shared<PhotoOpenClickHandler>(_data, contextId, chat), + std::make_shared<PhotoSaveClickHandler>(_data, contextId, chat), + std::make_shared<PhotoCancelClickHandler>(_data, contextId, chat)); + _data->thumb->load(contextId); +} + +QSize HistoryPhoto::countOptimalSize() { + if (_parent->media() != this) { + _caption = Text(); + } else if (_caption.hasSkipBlock()) { + _caption.updateSkipBlock( + _parent->skipBlockWidth(), + _parent->skipBlockHeight()); + } + + auto maxWidth = 0; + auto minHeight = 0; + + auto tw = ConvertScale(_data->full->width()); + auto th = ConvertScale(_data->full->height()); + if (!tw || !th) { + tw = th = 1; + } + if (tw > st::maxMediaSize) { + th = (st::maxMediaSize * th) / tw; + tw = st::maxMediaSize; + } + if (th > st::maxMediaSize) { + tw = (st::maxMediaSize * tw) / th; + th = st::maxMediaSize; + } + + if (_serviceWidth > 0) { + return { _serviceWidth, _serviceWidth }; + } + const auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + const auto maxActualWidth = qMax(tw, minWidth); + maxWidth = qMax(maxActualWidth, th); + minHeight = qMax(th, st::minPhotoSize); + if (_parent->hasBubble() && !_caption.isEmpty()) { + auto captionw = maxActualWidth - st::msgPadding.left() - st::msgPadding.right(); + minHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); + if (isBubbleBottom()) { + minHeight += st::msgPadding.bottom(); + } + } + return { maxWidth, minHeight }; +} + +QSize HistoryPhoto::countCurrentSize(int newWidth) { + int tw = ConvertScale(_data->full->width()), th = ConvertScale(_data->full->height()); + if (tw > st::maxMediaSize) { + th = (st::maxMediaSize * th) / tw; + tw = st::maxMediaSize; + } + if (th > st::maxMediaSize) { + tw = (st::maxMediaSize * tw) / th; + th = st::maxMediaSize; + } + + _pixw = qMin(newWidth, maxWidth()); + _pixh = th; + if (tw > _pixw) { + _pixh = (_pixw * _pixh / tw); + } else { + _pixw = tw; + } + if (_pixh > newWidth) { + _pixw = (_pixw * newWidth) / _pixh; + _pixh = newWidth; + } + if (_pixw < 1) _pixw = 1; + if (_pixh < 1) _pixh = 1; + + auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + newWidth = qMax(_pixw, minWidth); + auto newHeight = qMax(_pixh, st::minPhotoSize); + if (_parent->hasBubble() && !_caption.isEmpty()) { + const auto captionw = newWidth + - st::msgPadding.left() + - st::msgPadding.right(); + newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); + if (isBubbleBottom()) { + newHeight += st::msgPadding.bottom(); + } + } + return { newWidth, newHeight }; +} + +void HistoryPhoto::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; + + _data->automaticLoad(_realParent->fullId(), _parent->data()); + auto selected = (selection == FullSelection); + auto loaded = _data->loaded(); + auto displayLoading = _data->displayLoading(); + + auto inWebPage = (_parent->media() != this); + auto paintx = 0, painty = 0, paintw = width(), painth = height(); + auto bubble = _parent->hasBubble(); + + auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right(); + + if (displayLoading) { + ensureAnimation(); + if (!_animation->radial.animating()) { + _animation->radial.start(_data->progress()); + } + } + bool radial = isRadialAnimation(ms); + + auto rthumb = rtlrect(paintx, painty, paintw, painth, width()); + if (_serviceWidth > 0) { + const auto pix = loaded + ? _data->full->pixCircled(_realParent->fullId(), _pixw, _pixh) + : _data->thumb->pixBlurredCircled(_realParent->fullId(), _pixw, _pixh); + p.drawPixmap(rthumb.topLeft(), pix); + } else { + if (bubble) { + if (!_caption.isEmpty()) { + painth -= st::mediaCaptionSkip + _caption.countHeight(captionw); + if (isBubbleBottom()) { + painth -= st::msgPadding.bottom(); + } + rthumb = rtlrect(paintx, painty, paintw, painth, width()); + } + } else { + App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); + } + auto inWebPage = (_parent->media() != this); + auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large; + auto roundCorners = inWebPage ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None) + | ((isBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None)); + const auto pix = loaded + ? _data->full->pixSingle(_realParent->fullId(), _pixw, _pixh, paintw, painth, roundRadius, roundCorners) + : _data->thumb->pixBlurredSingle(_realParent->fullId(), _pixw, _pixh, paintw, painth, roundRadius, roundCorners); + p.drawPixmap(rthumb.topLeft(), pix); + if (selected) { + App::complexOverlayRect(p, rthumb, roundRadius, roundCorners); + } + } + if (radial || (!loaded && !_data->loading())) { + const auto radialOpacity = (radial && loaded && !_data->uploading()) + ? _animation->radial.opacity() : + 1.; + QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); + p.setPen(Qt::NoPen); + if (selected) { + p.setBrush(st::msgDateImgBgSelected); + } else if (isThumbAnimation(ms)) { + auto over = _animation->a_thumbOver.current(); + p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over)); + } else { + auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); + p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); + } + + p.setOpacity(radialOpacity * p.opacity()); + + { + PainterHighQualityEnabler hq(p); + p.drawEllipse(inner); + } + + p.setOpacity(radialOpacity); + auto icon = ([radial, this, selected]() -> const style::icon* { + if (radial || _data->loading()) { + if (_data->uploading() + || !_data->full->location().isNull()) { + return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); + } + return nullptr; + } + return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); + })(); + if (icon) { + icon->paintInCenter(p, inner); + } + p.setOpacity(1); + if (radial) { + QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); + _animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg); + } + } + + // date + if (!_caption.isEmpty()) { + auto outbg = _parent->hasOutLayout(); + p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg)); + _caption.draw(p, st::msgPadding.left(), painty + painth + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection); + } else if (!inWebPage) { + auto fullRight = paintx + paintw; + auto fullBottom = painty + painth; + if (needInfoDisplay()) { + _parent->drawInfo(p, fullRight, fullBottom, 2 * paintx + paintw, selected, InfoDisplayType::Image); + } + if (!bubble && _parent->displayRightAction()) { + auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); + _parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * paintx + paintw); + } + } +} + +TextState HistoryPhoto::textState(QPoint point, StateRequest request) const { + auto result = TextState(_parent); + + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { + return result; + } + auto paintx = 0, painty = 0, paintw = width(), painth = height(); + auto bubble = _parent->hasBubble(); + + if (bubble && !_caption.isEmpty()) { + const auto captionw = paintw + - st::msgPadding.left() + - st::msgPadding.right(); + painth -= _caption.countHeight(captionw); + if (isBubbleBottom()) { + painth -= st::msgPadding.bottom(); + } + if (QRect(st::msgPadding.left(), painth, captionw, height() - painth).contains(point)) { + result = TextState(_parent, _caption.getState( + point - QPoint(st::msgPadding.left(), painth), + captionw, + request.forText())); + return result; + } + painth -= st::mediaCaptionSkip; + } + if (QRect(paintx, painty, paintw, painth).contains(point)) { + if (_data->uploading()) { + result.link = _cancell; + } else if (_data->loaded()) { + result.link = _openl; + } else if (_data->loading()) { + if (!_data->full->location().isNull()) { + result.link = _cancell; + } + } else { + result.link = _savel; + } + } + if (_caption.isEmpty() && _parent->media() == this) { + auto fullRight = paintx + paintw; + auto fullBottom = painty + painth; + if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) { + result.cursor = CursorState::Date; + } + if (!bubble && _parent->displayRightAction()) { + auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); + if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) { + result.link = _parent->rightActionLink(); + } + } + } + return result; +} + +QSize HistoryPhoto::sizeForGrouping() const { + const auto width = _data->full->width(); + const auto height = _data->full->height(); + return { std::max(width, 1), std::max(height, 1) }; +} + +void HistoryPhoto::drawGrouped( + Painter &p, + const QRect &clip, + TextSelection selection, + TimeMs ms, + const QRect &geometry, + RectParts corners, + not_null<uint64*> cacheKey, + not_null<QPixmap*> cache) const { + _data->automaticLoad(_realParent->fullId(), _parent->data()); + + validateGroupedCache(geometry, corners, cacheKey, cache); + + const auto selected = (selection == FullSelection); + const auto loaded = _data->loaded(); + const auto displayLoading = _data->displayLoading(); + const auto bubble = _parent->hasBubble(); + + if (displayLoading) { + ensureAnimation(); + if (!_animation->radial.animating()) { + _animation->radial.start(_data->progress()); + } + } + const auto radial = isRadialAnimation(ms); + + if (!bubble) { +// App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); + } + p.drawPixmap(geometry.topLeft(), *cache); + if (selected) { + const auto roundRadius = ImageRoundRadius::Large; + App::complexOverlayRect(p, geometry, roundRadius, corners); + } + + const auto displayState = radial + || (!loaded && !_data->loading()) + || _data->waitingForAlbum(); + if (displayState) { + const auto radialOpacity = radial + ? _animation->radial.opacity() + : 1.; + const auto backOpacity = (loaded && !_data->uploading()) + ? radialOpacity + : 1.; + const auto radialSize = st::historyGroupRadialSize; + const auto inner = QRect( + geometry.x() + (geometry.width() - radialSize) / 2, + geometry.y() + (geometry.height() - radialSize) / 2, + radialSize, + radialSize); + p.setPen(Qt::NoPen); + if (selected) { + p.setBrush(st::msgDateImgBgSelected); + } else if (isThumbAnimation(ms)) { + auto over = _animation->a_thumbOver.current(); + p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over)); + } else { + auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); + p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); + } + + p.setOpacity(backOpacity * p.opacity()); + + { + PainterHighQualityEnabler hq(p); + p.drawEllipse(inner); + } + + const auto icon = [&]() -> const style::icon* { + if (_data->waitingForAlbum()) { + return &(selected ? st::historyFileThumbWaitingSelected : st::historyFileThumbWaiting); + } else if (radial || _data->loading()) { + if (_data->uploading() || !_data->full->location().isNull()) { + return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); + } + return nullptr; + } + return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); + }(); + const auto previous = [&]() -> const style::icon* { + if (_data->waitingForAlbum()) { + return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); + } + return nullptr; + }(); + p.setOpacity(backOpacity); + if (icon) { + if (previous && radialOpacity > 0. && radialOpacity < 1.) { + PaintInterpolatedIcon(p, *icon, *previous, radialOpacity, inner); + } else { + icon->paintInCenter(p, inner); + } + } + p.setOpacity(1); + if (radial) { + const auto line = st::historyGroupRadialLine; + const auto rinner = inner.marginsRemoved({ line, line, line, line }); + const auto color = selected + ? st::historyFileThumbRadialFgSelected + : st::historyFileThumbRadialFg; + _animation->radial.draw(p, rinner, line, color); + } + } +} + +TextState HistoryPhoto::getStateGrouped( + const QRect &geometry, + QPoint point, + StateRequest request) const { + if (!geometry.contains(point)) { + return {}; + } + return TextState(_parent, _data->uploading() + ? _cancell + : _data->loaded() + ? _openl + : _data->loading() + ? (_data->full->location().isNull() + ? ClickHandlerPtr() + : _cancell) + : _savel); +} + +float64 HistoryPhoto::dataProgress() const { + return _data->progress(); +} + +bool HistoryPhoto::dataFinished() const { + return !_data->loading() + && (!_data->uploading() || _data->waitingForAlbum()); +} + +bool HistoryPhoto::dataLoaded() const { + return _data->loaded(); +} + +bool HistoryPhoto::needInfoDisplay() const { + return (_parent->data()->id < 0 || _parent->isUnderCursor()); +} + +void HistoryPhoto::validateGroupedCache( + const QRect &geometry, + RectParts corners, + not_null<uint64*> cacheKey, + not_null<QPixmap*> cache) const { + using Option = Images::Option; + const auto loaded = _data->loaded(); + const auto loadLevel = loaded ? 2 : _data->thumb->loaded() ? 1 : 0; + const auto width = geometry.width(); + const auto height = geometry.height(); + const auto options = Option::Smooth + | Option::RoundedLarge + | (loaded ? Option::None : Option::Blurred) + | ((corners & RectPart::TopLeft) ? Option::RoundedTopLeft : Option::None) + | ((corners & RectPart::TopRight) ? Option::RoundedTopRight : Option::None) + | ((corners & RectPart::BottomLeft) ? Option::RoundedBottomLeft : Option::None) + | ((corners & RectPart::BottomRight) ? Option::RoundedBottomRight : Option::None); + const auto key = (uint64(width) << 48) + | (uint64(height) << 32) + | (uint64(options) << 16) + | (uint64(loadLevel)); + if (*cacheKey == key) { + return; + } + + const auto originalWidth = ConvertScale(_data->full->width()); + const auto originalHeight = ConvertScale(_data->full->height()); + const auto pixSize = Ui::GetImageScaleSizeForGeometry( + { originalWidth, originalHeight }, + { width, height }); + const auto pixWidth = pixSize.width() * cIntRetinaFactor(); + const auto pixHeight = pixSize.height() * cIntRetinaFactor(); + const auto &image = loaded ? _data->full : _data->thumb; + + *cacheKey = key; + *cache = image->pixNoCache(_realParent->fullId(), pixWidth, pixHeight, options, width, height); +} + +TextWithEntities HistoryPhoto::selectedText(TextSelection selection) const { + return _caption.originalTextWithEntities(selection, ExpandLinksAll); +} + +bool HistoryPhoto::needsBubble() const { + if (!_caption.isEmpty()) { + return true; + } + const auto item = _parent->data(); + if (item->toHistoryMessage()) { + return item->viaBot() + || item->Has<HistoryMessageReply>() + || _parent->displayForwardedFrom() + || _parent->displayFromName(); + } + return false; +} + +bool HistoryPhoto::isReadyForOpen() const { + return _data->loaded(); +} + +void HistoryPhoto::parentTextUpdated() { + _caption = (_parent->media() == this) + ? createCaption(_parent->data()) + : Text(); + Auth().data().requestViewResize(_parent); +} diff --git a/Telegram/SourceFiles/history/media/history_media_photo.h b/Telegram/SourceFiles/history/media/history_media_photo.h new file mode 100644 index 000000000..019ce379e --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_photo.h @@ -0,0 +1,102 @@ +/* +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/media/history_media_file.h" + +class HistoryPhoto : public HistoryFileMedia { +public: + HistoryPhoto( + not_null<Element*> parent, + not_null<HistoryItem*> realParent, + not_null<PhotoData*> photo); + HistoryPhoto( + not_null<Element*> parent, + not_null<PeerData*> chat, + not_null<PhotoData*> photo, + int width); + + HistoryMediaType type() const override { + return MediaTypePhoto; + } + + void draw(Painter &p, const QRect &clip, TextSelection selection, TimeMs ms) const override; + TextState textState(QPoint point, StateRequest request) const override; + + [[nodiscard]] TextSelection adjustSelection( + TextSelection selection, + TextSelectType type) const override { + return _caption.adjustSelection(selection, type); + } + uint16 fullSelectionLength() const override { + return _caption.length(); + } + bool hasTextForCopy() const override { + return !_caption.isEmpty(); + } + + TextWithEntities selectedText(TextSelection selection) const override; + + PhotoData *getPhoto() const override { + return _data; + } + + QSize sizeForGrouping() const override; + void drawGrouped( + Painter &p, + const QRect &clip, + TextSelection selection, + TimeMs ms, + const QRect &geometry, + RectParts corners, + not_null<uint64*> cacheKey, + not_null<QPixmap*> cache) const override; + TextState getStateGrouped( + const QRect &geometry, + QPoint point, + StateRequest request) const override; + + TextWithEntities getCaption() const override { + return _caption.originalTextWithEntities(); + } + bool needsBubble() const override; + bool customInfoLayout() const override { + return _caption.isEmpty(); + } + bool skipBubbleTail() const override { + return isBubbleBottom() && _caption.isEmpty(); + } + bool isReadyForOpen() const override; + + void parentTextUpdated() override; + +protected: + float64 dataProgress() const override; + bool dataFinished() const override; + bool dataLoaded() const override; + +private: + void create(FullMsgId contextId, PeerData *chat = nullptr); + + QSize countOptimalSize() override; + QSize countCurrentSize(int newWidth) override; + + bool needInfoDisplay() const; + void validateGroupedCache( + const QRect &geometry, + RectParts corners, + not_null<uint64*> cacheKey, + not_null<QPixmap*> cache) const; + + not_null<PhotoData*> _data; + int _serviceWidth = 0; + int _pixw = 1; + int _pixh = 1; + Text _caption; + +}; diff --git a/Telegram/SourceFiles/history/media/history_media_sticker.cpp b/Telegram/SourceFiles/history/media/history_media_sticker.cpp new file mode 100644 index 000000000..f671db755 --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_sticker.cpp @@ -0,0 +1,273 @@ +/* +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/media/history_media_sticker.h" + +#include "layout.h" +#include "boxes/sticker_set_box.h" +#include "history/history_item_components.h" +#include "history/history_item.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_cursor_state.h" +#include "ui/image/image.h" +#include "ui/emoji_config.h" +#include "data/data_document.h" +#include "styles/style_history.h" + +namespace { + +using TextState = HistoryView::TextState; + +} // namespace + +HistorySticker::HistorySticker( + not_null<Element*> parent, + not_null<DocumentData*> document) +: HistoryMedia(parent) +, _data(document) +, _emoji(_data->sticker()->alt) { + _data->thumb->load(parent->data()->fullId()); + if (auto emoji = Ui::Emoji::Find(_emoji)) { + _emoji = emoji->text(); + } +} + +QSize HistorySticker::countOptimalSize() { + auto sticker = _data->sticker(); + + if (!_packLink && sticker && sticker->set.type() != mtpc_inputStickerSetEmpty) { + _packLink = std::make_shared<LambdaClickHandler>([document = _data] { + StickerSetBox::Show(document); + }); + } + _pixw = _data->dimensions.width(); + _pixh = _data->dimensions.height(); + if (_pixw > st::maxStickerSize) { + _pixh = (st::maxStickerSize * _pixh) / _pixw; + _pixw = st::maxStickerSize; + } + if (_pixh > st::maxStickerSize) { + _pixw = (st::maxStickerSize * _pixw) / _pixh; + _pixh = st::maxStickerSize; + } + if (_pixw < 1) _pixw = 1; + if (_pixh < 1) _pixh = 1; + auto maxWidth = qMax(_pixw, st::minPhotoSize); + auto minHeight = qMax(_pixh, st::minPhotoSize); + accumulate_max( + maxWidth, + _parent->infoWidth() + 2 * st::msgDateImgPadding.x()); + if (_parent->media() == this) { + maxWidth += additionalWidth(); + } + return { maxWidth, minHeight }; +} + +QSize HistorySticker::countCurrentSize(int newWidth) { + const auto item = _parent->data(); + accumulate_min(newWidth, maxWidth()); + if (_parent->media() == this) { + auto via = item->Get<HistoryMessageVia>(); + auto reply = item->Get<HistoryMessageReply>(); + if (via || reply) { + int usew = maxWidth() - additionalWidth(via, reply); + int availw = newWidth - usew - st::msgReplyPadding.left() - st::msgReplyPadding.left() - st::msgReplyPadding.left(); + if (via) { + via->resize(availw); + } + if (reply) { + reply->resize(availw); + } + } + } + return { newWidth, minHeight() }; +} + +void HistorySticker::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { + auto sticker = _data->sticker(); + if (!sticker) return; + + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; + + _data->checkSticker(); + bool loaded = _data->loaded(); + bool selected = (selection == FullSelection); + + auto outbg = _parent->hasOutLayout(); + auto inWebPage = (_parent->media() != this); + + const auto item = _parent->data(); + int usew = maxWidth(), usex = 0; + auto via = inWebPage ? nullptr : item->Get<HistoryMessageVia>(); + auto reply = inWebPage ? nullptr : item->Get<HistoryMessageReply>(); + if (via || reply) { + usew -= additionalWidth(via, reply); + if (outbg) { + usex = width() - usew; + } + } + if (rtl()) usex = width() - usex - usew; + + const auto &pixmap = [&]() -> const QPixmap & { + const auto o = item->fullId(); + const auto w = _pixw; + const auto h = _pixh; + const auto &c = st::msgStickerOverlay; + if (const auto image = _data->getStickerImage()) { + return selected + ? image->pixColored(o, c, w, h) + : image->pix(o, w, h); + } + return selected + ? _data->thumb->pixBlurredColored(o, c, w, h) + : _data->thumb->pixBlurred(o, w, h); + }(); + p.drawPixmap( + QPoint{ usex + (usew - _pixw) / 2, (minHeight() - _pixh) / 2 }, + pixmap); + + if (!inWebPage) { + auto fullRight = usex + usew; + auto fullBottom = height(); + if (needInfoDisplay()) { + _parent->drawInfo(p, fullRight, fullBottom, usex * 2 + usew, selected, InfoDisplayType::Background); + } + if (via || reply) { + int rectw = width() - usew - st::msgReplyPadding.left(); + int recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom(); + if (via) { + recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); + } + if (reply) { + recth += st::msgReplyBarSize.height(); + } + int rectx = outbg ? 0 : (usew + st::msgReplyPadding.left()); + int recty = st::msgDateImgDelta; + if (rtl()) rectx = width() - rectx - rectw; + + App::roundRect(p, rectx, recty, rectw, recth, selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? StickerSelectedCorners : StickerCorners); + p.setPen(st::msgServiceFg); + rectx += st::msgReplyPadding.left(); + rectw -= st::msgReplyPadding.left() + st::msgReplyPadding.right(); + if (via) { + p.setFont(st::msgDateFont); + p.drawTextLeft(rectx, recty + st::msgReplyPadding.top(), 2 * rectx + rectw, via->text); + int skip = st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); + recty += skip; + } + if (reply) { + HistoryMessageReply::PaintFlags flags = 0; + if (selected) { + flags |= HistoryMessageReply::PaintFlag::Selected; + } + reply->paint(p, _parent, rectx, recty, rectw, flags); + } + } + if (_parent->displayRightAction()) { + auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); + _parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * usex + usew); + } + } +} + +TextState HistorySticker::textState(QPoint point, StateRequest request) const { + auto result = TextState(_parent); + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { + return result; + } + + auto outbg = _parent->hasOutLayout(); + auto inWebPage = (_parent->media() != this); + + const auto item = _parent->data(); + int usew = maxWidth(), usex = 0; + auto via = inWebPage ? nullptr : item->Get<HistoryMessageVia>(); + auto reply = inWebPage ? nullptr : item->Get<HistoryMessageReply>(); + if (via || reply) { + usew -= additionalWidth(via, reply); + if (outbg) { + usex = width() - usew; + } + } + if (rtl()) usex = width() - usex - usew; + + if (via || reply) { + int rectw = width() - usew - st::msgReplyPadding.left(); + int recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom(); + if (via) { + recth += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); + } + if (reply) { + recth += st::msgReplyBarSize.height(); + } + int rectx = outbg ? 0 : (usew + st::msgReplyPadding.left()); + int recty = st::msgDateImgDelta; + if (rtl()) rectx = width() - rectx - rectw; + + if (via) { + int viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom()); + if (QRect(rectx, recty, rectw, viah).contains(point)) { + result.link = via->link; + return result; + } + int skip = st::msgServiceNameFont->height + (reply ? 2 * st::msgReplyPadding.top() : 0); + recty += skip; + recth -= skip; + } + if (reply) { + if (QRect(rectx, recty, rectw, recth).contains(point)) { + result.link = reply->replyToLink(); + return result; + } + } + } + if (_parent->media() == this) { + auto fullRight = usex + usew; + auto fullBottom = height(); + if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) { + result.cursor = CursorState::Date; + } + if (_parent->displayRightAction()) { + auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); + if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) { + result.link = _parent->rightActionLink(); + } + } + } + + auto pixLeft = usex + (usew - _pixw) / 2; + auto pixTop = (minHeight() - _pixh) / 2; + if (QRect(pixLeft, pixTop, _pixw, _pixh).contains(point)) { + result.link = _packLink; + return result; + } + return result; +} + +bool HistorySticker::needInfoDisplay() const { + return (_parent->data()->id < 0 || _parent->isUnderCursor()); +} + +int HistorySticker::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const { + int result = 0; + if (via) { + accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + via->maxWidth + st::msgReplyPadding.left()); + } + if (reply) { + accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth()); + } + return result; +} + +int HistorySticker::additionalWidth() const { + const auto item = _parent->data(); + return additionalWidth( + item->Get<HistoryMessageVia>(), + item->Get<HistoryMessageReply>()); +} diff --git a/Telegram/SourceFiles/history/media/history_media_sticker.h b/Telegram/SourceFiles/history/media/history_media_sticker.h new file mode 100644 index 000000000..8d84df6a1 --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_sticker.h @@ -0,0 +1,67 @@ +/* +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/media/history_media.h" + +struct HistoryMessageVia; +struct HistoryMessageReply; +struct HistoryMessageForwarded; + +class HistorySticker : public HistoryMedia { +public: + HistorySticker( + not_null<Element*> parent, + not_null<DocumentData*> document); + + HistoryMediaType type() const override { + return MediaTypeSticker; + } + + void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; + TextState textState(QPoint point, StateRequest request) const override; + + bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { + return true; + } + bool dragItem() const override { + return true; + } + bool dragItemByHandler(const ClickHandlerPtr &p) const override { + return true; + } + + DocumentData *getDocument() const override { + return _data; + } + + bool needsBubble() const override { + return false; + } + bool customInfoLayout() const override { + return true; + } + QString emoji() const { + return _emoji; + } + +private: + QSize countOptimalSize() override; + QSize countCurrentSize(int newWidth) override; + + bool needInfoDisplay() const; + int additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const; + int additionalWidth() const; + + int _pixw = 1; + int _pixh = 1; + ClickHandlerPtr _packLink; + not_null<DocumentData*> _data; + QString _emoji; + +}; diff --git a/Telegram/SourceFiles/history/media/history_media_video.cpp b/Telegram/SourceFiles/history/media/history_media_video.cpp new file mode 100644 index 000000000..d648f743f --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_video.cpp @@ -0,0 +1,525 @@ +/* +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/media/history_media_video.h" + +#include "history/media/history_media_common.h" +#include "layout.h" +#include "auth_session.h" +#include "history/history_item_components.h" +#include "history/history_item.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_cursor_state.h" +#include "ui/image/image.h" +#include "ui/grouped_layout.h" +#include "data/data_session.h" +#include "data/data_document.h" +#include "styles/style_history.h" + +namespace { + +using TextState = HistoryView::TextState; + +} // namespace + +HistoryVideo::HistoryVideo( + not_null<Element*> parent, + not_null<HistoryItem*> realParent, + not_null<DocumentData*> document) +: HistoryFileMedia(parent, realParent) +, _data(document) +, _thumbw(1) +, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { + _caption = createCaption(realParent); + + setDocumentLinks(_data, realParent); + + setStatusSize(FileStatusSizeReady); + + _data->thumb->load(realParent->fullId()); +} + +QSize HistoryVideo::countOptimalSize() { + if (_parent->media() != this) { + _caption = Text(); + } else if (_caption.hasSkipBlock()) { + _caption.updateSkipBlock( + _parent->skipBlockWidth(), + _parent->skipBlockHeight()); + } + + auto tw = ConvertScale(_data->thumb->width()); + auto th = ConvertScale(_data->thumb->height()); + if (!tw || !th) { + tw = th = 1; + } + if (tw * st::msgVideoSize.height() > th * st::msgVideoSize.width()) { + th = qRound((st::msgVideoSize.width() / float64(tw)) * th); + tw = st::msgVideoSize.width(); + } else { + tw = qRound((st::msgVideoSize.height() / float64(th)) * tw); + th = st::msgVideoSize.height(); + } + + _thumbw = qMax(tw, 1); + _thumbh = qMax(th, 1); + + auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + minWidth = qMax(minWidth, documentMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + auto maxWidth = qMax(_thumbw, minWidth); + auto minHeight = qMax(th, st::minPhotoSize); + if (_parent->hasBubble() && !_caption.isEmpty()) { + const auto captionw = maxWidth + - st::msgPadding.left() + - st::msgPadding.right(); + minHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); + if (isBubbleBottom()) { + minHeight += st::msgPadding.bottom(); + } + } + return { maxWidth, minHeight }; +} + +QSize HistoryVideo::countCurrentSize(int newWidth) { + int tw = ConvertScale(_data->thumb->width()), th = ConvertScale(_data->thumb->height()); + if (!tw || !th) { + tw = th = 1; + } + if (tw * st::msgVideoSize.height() > th * st::msgVideoSize.width()) { + th = qRound((st::msgVideoSize.width() / float64(tw)) * th); + tw = st::msgVideoSize.width(); + } else { + tw = qRound((st::msgVideoSize.height() / float64(th)) * tw); + th = st::msgVideoSize.height(); + } + + if (newWidth < tw) { + th = qRound((newWidth / float64(tw)) * th); + tw = newWidth; + } + + _thumbw = qMax(tw, 1); + _thumbh = qMax(th, 1); + auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + minWidth = qMax(minWidth, documentMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); + newWidth = qMax(_thumbw, minWidth); + auto newHeight = qMax(th, st::minPhotoSize); + if (_parent->hasBubble() && !_caption.isEmpty()) { + const auto captionw = newWidth + - st::msgPadding.left() + - st::msgPadding.right(); + newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); + if (isBubbleBottom()) { + newHeight += st::msgPadding.bottom(); + } + } + return { newWidth, newHeight }; +} + +void HistoryVideo::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; + + _data->automaticLoad(_realParent->fullId(), _parent->data()); + bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); + bool selected = (selection == FullSelection); + + auto paintx = 0, painty = 0, paintw = width(), painth = height(); + bool bubble = _parent->hasBubble(); + + int captionw = paintw - st::msgPadding.left() - st::msgPadding.right(); + + if (displayLoading) { + ensureAnimation(); + if (!_animation->radial.animating()) { + _animation->radial.start(_data->progress()); + } + } + updateStatusText(); + bool radial = isRadialAnimation(ms); + + if (bubble) { + if (!_caption.isEmpty()) { + painth -= st::mediaCaptionSkip + _caption.countHeight(captionw); + if (isBubbleBottom()) { + painth -= st::msgPadding.bottom(); + } + } + } else { + App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); + } + + auto inWebPage = (_parent->media() != this); + auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large; + auto roundCorners = inWebPage ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None) + | ((isBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None)); + QRect rthumb(rtlrect(paintx, painty, paintw, painth, width())); + + const auto good = _data->goodThumbnail(); + if (good && good->loaded()) { + p.drawPixmap(rthumb.topLeft(), good->pixSingle({}, _thumbw, _thumbh, paintw, painth, roundRadius, roundCorners)); + } else { + if (good) { + good->load({}); + } + p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(_realParent->fullId(), _thumbw, _thumbh, paintw, painth, roundRadius, roundCorners)); + } + if (selected) { + App::complexOverlayRect(p, rthumb, roundRadius, roundCorners); + } + + QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); + p.setPen(Qt::NoPen); + if (selected) { + p.setBrush(st::msgDateImgBgSelected); + } else if (isThumbAnimation(ms)) { + auto over = _animation->a_thumbOver.current(); + p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over)); + } else { + bool over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); + p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); + } + + { + PainterHighQualityEnabler hq(p); + p.drawEllipse(inner); + } + + if (!selected && _animation) { + p.setOpacity(1); + } + + auto icon = ([this, radial, selected, loaded]() -> const style::icon * { + if (loaded && !radial) { + return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay); + } else if (radial || _data->loading()) { + if (_parent->data()->id > 0 || _data->uploading()) { + return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); + } + return nullptr; + } + return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); + })(); + if (icon) { + icon->paintInCenter(p, inner); + } + if (radial) { + QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); + _animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg); + } + + auto statusX = paintx + st::msgDateImgDelta + st::msgDateImgPadding.x(), 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, rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); + p.setFont(st::normalFont); + p.setPen(st::msgDateImgFg); + p.drawTextLeft(statusX, statusY, width(), _statusText, statusW - 2 * st::msgDateImgPadding.x()); + + // date + if (!_caption.isEmpty()) { + auto outbg = _parent->hasOutLayout(); + p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg)); + _caption.draw(p, st::msgPadding.left(), painty + painth + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection); + } else if (_parent->media() == this) { + auto fullRight = paintx + paintw, fullBottom = painty + painth; + _parent->drawInfo(p, fullRight, fullBottom, 2 * paintx + paintw, selected, InfoDisplayType::Image); + if (!bubble && _parent->displayRightAction()) { + auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); + _parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * paintx + paintw); + } + } +} + +TextState HistoryVideo::textState(QPoint point, StateRequest request) const { + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { + return {}; + } + + auto result = TextState(_parent); + bool loaded = _data->loaded(); + + auto paintx = 0, painty = 0, paintw = width(), painth = height(); + bool bubble = _parent->hasBubble(); + + if (bubble && !_caption.isEmpty()) { + const auto captionw = paintw + - st::msgPadding.left() + - st::msgPadding.right(); + painth -= _caption.countHeight(captionw); + if (isBubbleBottom()) { + painth -= st::msgPadding.bottom(); + } + if (QRect(st::msgPadding.left(), painth, captionw, height() - painth).contains(point)) { + result = TextState(_parent, _caption.getState( + point - QPoint(st::msgPadding.left(), painth), + captionw, + request.forText())); + } + painth -= st::mediaCaptionSkip; + } + if (QRect(paintx, painty, paintw, painth).contains(point)) { + if (_data->uploading()) { + result.link = _cancell; + } else { + result.link = loaded ? _openl : (_data->loading() ? _cancell : _savel); + } + } + if (_caption.isEmpty() && _parent->media() == this) { + auto fullRight = paintx + paintw; + auto fullBottom = painty + painth; + if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) { + result.cursor = CursorState::Date; + } + if (!bubble && _parent->displayRightAction()) { + auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize); + if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) { + result.link = _parent->rightActionLink(); + } + } + } + return result; +} + +QSize HistoryVideo::sizeForGrouping() const { + const auto width = _data->dimensions.isEmpty() + ? _data->thumb->width() + : _data->dimensions.width(); + const auto height = _data->dimensions.isEmpty() + ? _data->thumb->height() + : _data->dimensions.height(); + return { std::max(width, 1), std::max(height, 1) }; +} + +void HistoryVideo::drawGrouped( + Painter &p, + const QRect &clip, + TextSelection selection, + TimeMs ms, + const QRect &geometry, + RectParts corners, + not_null<uint64*> cacheKey, + not_null<QPixmap*> cache) const { + _data->automaticLoad(_realParent->fullId(), _parent->data()); + + validateGroupedCache(geometry, corners, cacheKey, cache); + + const auto selected = (selection == FullSelection); + const auto loaded = _data->loaded(); + const auto displayLoading = _data->displayLoading(); + const auto bubble = _parent->hasBubble(); + + if (displayLoading) { + ensureAnimation(); + if (!_animation->radial.animating()) { + _animation->radial.start(_data->progress()); + } + } + const auto radial = isRadialAnimation(ms); + + if (!bubble) { +// App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); + } + p.drawPixmap(geometry.topLeft(), *cache); + if (selected) { + const auto roundRadius = ImageRoundRadius::Large; + App::complexOverlayRect(p, geometry, roundRadius, corners); + } + + const auto radialOpacity = radial + ? _animation->radial.opacity() + : 1.; + const auto backOpacity = (loaded && !_data->uploading()) + ? radialOpacity + : 1.; + const auto radialSize = st::historyGroupRadialSize; + const auto inner = QRect( + geometry.x() + (geometry.width() - radialSize) / 2, + geometry.y() + (geometry.height() - radialSize) / 2, + radialSize, + radialSize); + p.setPen(Qt::NoPen); + if (selected) { + p.setBrush(st::msgDateImgBgSelected); + } else if (isThumbAnimation(ms)) { + auto over = _animation->a_thumbOver.current(); + p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over)); + } else { + auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel); + p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); + } + + p.setOpacity(backOpacity * p.opacity()); + + { + PainterHighQualityEnabler hq(p); + p.drawEllipse(inner); + } + + auto icon = [&]() -> const style::icon * { + if (_data->waitingForAlbum()) { + return &(selected ? st::historyFileThumbWaitingSelected : st::historyFileThumbWaiting); + } else if (loaded && !radial) { + return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay); + } else if (radial || _data->loading()) { + if (_parent->data()->id > 0 || _data->uploading()) { + return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); + } + return nullptr; + } + return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); + }(); + const auto previous = [&]() -> const style::icon* { + if (_data->waitingForAlbum()) { + return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); + } + return nullptr; + }(); + p.setOpacity(backOpacity); + if (icon) { + if (previous && radialOpacity > 0. && radialOpacity < 1.) { + LOG(("INTERPOLATING: %1").arg(radialOpacity)); + PaintInterpolatedIcon(p, *icon, *previous, radialOpacity, inner); + } else { + icon->paintInCenter(p, inner); + } + } + p.setOpacity(1); + if (radial) { + const auto line = st::historyGroupRadialLine; + const auto rinner = inner.marginsRemoved({ line, line, line, line }); + const auto color = selected + ? st::historyFileThumbRadialFgSelected + : st::historyFileThumbRadialFg; + _animation->radial.draw(p, rinner, line, color); + } +} + +TextState HistoryVideo::getStateGrouped( + const QRect &geometry, + QPoint point, + StateRequest request) const { + if (!geometry.contains(point)) { + return {}; + } + return TextState(_parent, _data->uploading() + ? _cancell + : _data->loaded() + ? _openl + : _data->loading() + ? _cancell + : _savel); +} + +bool HistoryVideo::uploading() const { + return _data->uploading(); +} + +float64 HistoryVideo::dataProgress() const { + return _data->progress(); +} + +bool HistoryVideo::dataFinished() const { + return !_data->loading() + && (!_data->uploading() || _data->waitingForAlbum()); +} + +bool HistoryVideo::dataLoaded() const { + return _data->loaded(); +} + +void HistoryVideo::validateGroupedCache( + const QRect &geometry, + RectParts corners, + not_null<uint64*> cacheKey, + not_null<QPixmap*> cache) const { + using Option = Images::Option; + const auto good = _data->goodThumbnail(); + const auto useGood = (good && good->loaded()); + const auto image = useGood ? good : _data->thumb.get(); + if (good && !useGood) { + good->load({}); + } + + const auto loaded = useGood ? true : _data->thumb->loaded(); + const auto loadLevel = loaded ? 1 : 0; + const auto width = geometry.width(); + const auto height = geometry.height(); + const auto options = Option::Smooth + | Option::RoundedLarge + | (useGood ? Option(0) : Option::Blurred) + | ((corners & RectPart::TopLeft) ? Option::RoundedTopLeft : Option::None) + | ((corners & RectPart::TopRight) ? Option::RoundedTopRight : Option::None) + | ((corners & RectPart::BottomLeft) ? Option::RoundedBottomLeft : Option::None) + | ((corners & RectPart::BottomRight) ? Option::RoundedBottomRight : Option::None); + const auto key = (uint64(width) << 48) + | (uint64(height) << 32) + | (uint64(options) << 16) + | (uint64(loadLevel)); + if (*cacheKey == key) { + return; + } + + const auto originalWidth = ConvertScale(_data->thumb->width()); + const auto originalHeight = ConvertScale(_data->thumb->height()); + const auto pixSize = Ui::GetImageScaleSizeForGeometry( + { originalWidth, originalHeight }, + { width, height }); + const auto pixWidth = pixSize.width() * cIntRetinaFactor(); + const auto pixHeight = pixSize.height() * cIntRetinaFactor(); + + *cacheKey = key; + *cache = image->pixNoCache(_realParent->fullId(), pixWidth, pixHeight, options, width, height); +} + +void HistoryVideo::setStatusSize(int newSize) const { + HistoryFileMedia::setStatusSize(newSize, _data->size, _data->duration(), 0); +} + +TextWithEntities HistoryVideo::selectedText(TextSelection selection) const { + return _caption.originalTextWithEntities(selection, ExpandLinksAll); +} + +bool HistoryVideo::needsBubble() const { + if (!_caption.isEmpty()) { + return true; + } + const auto item = _parent->data(); + return item->viaBot() + || item->Has<HistoryMessageReply>() + || _parent->displayForwardedFrom() + || _parent->displayFromName(); + return false; +} + +void HistoryVideo::parentTextUpdated() { + _caption = (_parent->media() == this) + ? createCaption(_parent->data()) + : Text(); + Auth().data().requestViewResize(_parent); +} + +void HistoryVideo::updateStatusText() const { + auto showPause = false; + auto statusSize = 0; + auto realDuration = 0; + if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) { + statusSize = FileStatusSizeFailed; + } else if (_data->uploading()) { + statusSize = _data->uploadingData->offset; + } else if (_data->loading()) { + statusSize = _data->loadOffset(); + } else if (_data->loaded()) { + statusSize = FileStatusSizeLoaded; + } else { + statusSize = FileStatusSizeReady; + } + if (statusSize != _statusSize) { + setStatusSize(statusSize); + } +} diff --git a/Telegram/SourceFiles/history/media/history_media_video.h b/Telegram/SourceFiles/history/media/history_media_video.h new file mode 100644 index 000000000..34e463aa3 --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_video.h @@ -0,0 +1,96 @@ +/* +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/media/history_media_file.h" + +class HistoryVideo : public HistoryFileMedia { +public: + HistoryVideo( + not_null<Element*> parent, + not_null<HistoryItem*> realParent, + not_null<DocumentData*> document); + + HistoryMediaType type() const override { + return MediaTypeVideo; + } + + void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; + TextState textState(QPoint point, StateRequest request) const override; + + [[nodiscard]] TextSelection adjustSelection( + TextSelection selection, + TextSelectType type) const override { + return _caption.adjustSelection(selection, type); + } + uint16 fullSelectionLength() const override { + return _caption.length(); + } + bool hasTextForCopy() const override { + return !_caption.isEmpty(); + } + + TextWithEntities selectedText(TextSelection selection) const override; + + DocumentData *getDocument() const override { + return _data; + } + + QSize sizeForGrouping() const override; + void drawGrouped( + Painter &p, + const QRect &clip, + TextSelection selection, + TimeMs ms, + const QRect &geometry, + RectParts corners, + not_null<uint64*> cacheKey, + not_null<QPixmap*> cache) const override; + TextState getStateGrouped( + const QRect &geometry, + QPoint point, + StateRequest request) const override; + + bool uploading() const override; + + TextWithEntities getCaption() const override { + return _caption.originalTextWithEntities(); + } + bool needsBubble() const override; + bool customInfoLayout() const override { + return _caption.isEmpty(); + } + bool skipBubbleTail() const override { + return isBubbleBottom() && _caption.isEmpty(); + } + + void parentTextUpdated() override; + +protected: + float64 dataProgress() const override; + bool dataFinished() const override; + bool dataLoaded() const override; + +private: + QSize countOptimalSize() override; + QSize countCurrentSize(int newWidth) override; + + void validateGroupedCache( + const QRect &geometry, + RectParts corners, + not_null<uint64*> cacheKey, + not_null<QPixmap*> cache) const; + void setStatusSize(int newSize) const; + void updateStatusText() const; + + not_null<DocumentData*> _data; + int _thumbw = 1; + int _thumbh = 1; + Text _caption; + +}; diff --git a/Telegram/SourceFiles/history/media/history_media_web_page.cpp b/Telegram/SourceFiles/history/media/history_media_web_page.cpp new file mode 100644 index 000000000..6c0f44ba9 --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_web_page.cpp @@ -0,0 +1,696 @@ +/* +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/media/history_media_web_page.h" + +#include "layout.h" +#include "auth_session.h" +#include "core/click_handler_types.h" +#include "history/history_item_components.h" +#include "history/history_item.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_cursor_state.h" +#include "history/media/history_media_common.h" +#include "ui/image/image.h" +#include "ui/text_options.h" +#include "data/data_session.h" +#include "data/data_media_types.h" +#include "data/data_web_page.h" +#include "data/data_photo.h" +#include "styles/style_history.h" + +namespace { + +using TextState = HistoryView::TextState; + +} // namespace + +namespace { + +constexpr auto kMaxOriginalEntryLines = 8192; + +int articleThumbWidth(PhotoData *thumb, int height) { + auto w = thumb->medium->width(); + auto h = thumb->medium->height(); + return qMax(qMin(height * w / h, height), 1); +} + +int articleThumbHeight(PhotoData *thumb, int width) { + return qMax(thumb->medium->height() * width / thumb->medium->width(), 1); +} + +std::vector<std::unique_ptr<Data::Media>> PrepareCollageMedia( + not_null<HistoryItem*> parent, + const WebPageCollage &data) { + auto result = std::vector<std::unique_ptr<Data::Media>>(); + result.reserve(data.items.size()); + for (const auto item : data.items) { + if (const auto document = base::get_if<DocumentData*>(&item)) { + result.push_back(std::make_unique<Data::MediaFile>( + parent, + *document)); + } else if (const auto photo = base::get_if<PhotoData*>(&item)) { + result.push_back(std::make_unique<Data::MediaPhoto>( + parent, + *photo)); + } else { + return {}; + } + if (!result.back()->canBeGrouped()) { + return {}; + } + } + return result; +} + +} // namespace + +HistoryWebPage::HistoryWebPage( + not_null<Element*> parent, + not_null<WebPageData*> data) +: HistoryMedia(parent) +, _data(data) +, _title(st::msgMinWidth - st::webPageLeft) +, _description(st::msgMinWidth - st::webPageLeft) { + Auth().data().registerWebPageView(_data, _parent); +} + +QSize HistoryWebPage::countOptimalSize() { + if (_data->pendingTill) { + return { 0, 0 }; + } + const auto versionChanged = (_dataVersion != _data->version); + if (versionChanged) { + _dataVersion = _data->version; + _openl = nullptr; + _attach = nullptr; + _collage = PrepareCollageMedia(_parent->data(), _data->collage); + _title = Text(st::msgMinWidth - st::webPageLeft); + _description = Text(st::msgMinWidth - st::webPageLeft); + _siteNameWidth = 0; + } + auto lineHeight = unitedLineHeight(); + + if (!_openl && !_data->url.isEmpty()) { + const auto previewOfHiddenUrl = [&] { + const auto simplify = [](const QString &url) { + auto result = url.toLower(); + if (result.endsWith('/')) { + result.chop(1); + } + const auto prefixes = { qstr("http://"), qstr("https://") }; + for (const auto &prefix : prefixes) { + if (result.startsWith(prefix)) { + result = result.mid(prefix.size()); + break; + } + } + return result; + }; + const auto simplified = simplify(_data->url); + const auto full = _parent->data()->originalText(); + for (const auto &entity : full.entities) { + if (entity.type() != EntityInTextUrl) { + continue; + } + const auto link = full.text.mid( + entity.offset(), + entity.length()); + if (simplify(link) == simplified) { + return false; + } + } + return true; + }(); + _openl = previewOfHiddenUrl + ? std::make_shared<HiddenUrlClickHandler>(_data->url) + : std::make_shared<UrlClickHandler>(_data->url, true); + } + + // init layout + auto title = TextUtilities::SingleLine(_data->title.isEmpty() + ? _data->author + : _data->title); + if (!_collage.empty()) { + _asArticle = false; + } else if (!_data->document + && _data->photo + && _data->type != WebPageType::Photo + && _data->type != WebPageType::Video) { + if (_data->type == WebPageType::Profile) { + _asArticle = true; + } else if (_data->siteName == qstr("Twitter") + || _data->siteName == qstr("Facebook") + || _data->type == WebPageType::ArticleWithIV) { + _asArticle = false; + } else { + _asArticle = true; + } + if (_asArticle + && _data->description.text.isEmpty() + && title.isEmpty() + && _data->siteName.isEmpty()) { + _asArticle = false; + } + } else { + _asArticle = false; + } + + // init attach + if (!_attach && !_asArticle) { + _attach = CreateAttach( + _parent, + _data->document, + _data->photo, + _collage); + } + + auto textFloatsAroundInfo = !_asArticle && !_attach && isBubbleBottom(); + + // init strings + if (_description.isEmpty() && !_data->description.text.isEmpty()) { + auto text = _data->description; + + if (textFloatsAroundInfo) { + text.text += _parent->skipBlock(); + } + if (isLogEntryOriginal()) { + // Fix layout for small bubbles (narrow media caption edit log entries). + _description = Text(st::minPhotoSize + - st::msgPadding.left() + - st::msgPadding.right() + - st::webPageLeft); + } + _description.setMarkedText( + st::webPageDescriptionStyle, + text, + Ui::WebpageTextDescriptionOptions(_data->siteName)); + } + if (_title.isEmpty() && !title.isEmpty()) { + if (textFloatsAroundInfo && _description.isEmpty()) { + title += _parent->skipBlock(); + } + _title.setText( + st::webPageTitleStyle, + title, + Ui::WebpageTextTitleOptions()); + } + if (!_siteNameWidth && !_data->siteName.isEmpty()) { + _siteNameWidth = st::webPageTitleFont->width(_data->siteName); + } + + // init dimensions + auto l = st::msgPadding.left() + st::webPageLeft, r = st::msgPadding.right(); + auto skipBlockWidth = _parent->skipBlockWidth(); + auto maxWidth = skipBlockWidth; + auto minHeight = 0; + + auto siteNameHeight = _data->siteName.isEmpty() ? 0 : lineHeight; + auto titleMinHeight = _title.isEmpty() ? 0 : lineHeight; + auto descMaxLines = isLogEntryOriginal() ? kMaxOriginalEntryLines : (3 + (siteNameHeight ? 0 : 1) + (titleMinHeight ? 0 : 1)); + auto descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * lineHeight); + auto articleMinHeight = siteNameHeight + titleMinHeight + descriptionMinHeight; + auto articlePhotoMaxWidth = 0; + if (_asArticle) { + articlePhotoMaxWidth = st::webPagePhotoDelta + qMax(articleThumbWidth(_data->photo, articleMinHeight), lineHeight); + } + + if (_siteNameWidth) { + if (_title.isEmpty() && _description.isEmpty()) { + accumulate_max(maxWidth, _siteNameWidth + _parent->skipBlockWidth()); + } else { + accumulate_max(maxWidth, _siteNameWidth + articlePhotoMaxWidth); + } + minHeight += lineHeight; + } + if (!_title.isEmpty()) { + accumulate_max(maxWidth, _title.maxWidth() + articlePhotoMaxWidth); + minHeight += titleMinHeight; + } + if (!_description.isEmpty()) { + accumulate_max(maxWidth, _description.maxWidth() + articlePhotoMaxWidth); + minHeight += descriptionMinHeight; + } + if (_attach) { + auto attachAtTop = !_siteNameWidth && _title.isEmpty() && _description.isEmpty(); + if (!attachAtTop) minHeight += st::mediaInBubbleSkip; + + _attach->initDimensions(); + auto bubble = _attach->bubbleMargins(); + auto maxMediaWidth = _attach->maxWidth() - bubble.left() - bubble.right(); + if (isBubbleBottom() && _attach->customInfoLayout()) { + maxMediaWidth += skipBlockWidth; + } + accumulate_max(maxWidth, maxMediaWidth); + minHeight += _attach->minHeight() - bubble.top() - bubble.bottom(); + if (!_attach->additionalInfoString().isEmpty()) { + minHeight += bottomInfoPadding(); + } + } + if (_data->type == WebPageType::Video && _data->duration) { + _duration = formatDurationText(_data->duration); + _durationWidth = st::msgDateFont->width(_duration); + } + maxWidth += st::msgPadding.left() + st::webPageLeft + st::msgPadding.right(); + auto padding = inBubblePadding(); + minHeight += padding.top() + padding.bottom(); + + if (_asArticle) { + minHeight = resizeGetHeight(maxWidth); + } + return { maxWidth, minHeight }; +} + +QSize HistoryWebPage::countCurrentSize(int newWidth) { + if (_data->pendingTill) { + return { newWidth, minHeight() }; + } + + auto innerWidth = newWidth - st::msgPadding.left() - st::webPageLeft - st::msgPadding.right(); + auto newHeight = 0; + + auto lineHeight = unitedLineHeight(); + auto linesMax = isLogEntryOriginal() ? kMaxOriginalEntryLines : 5; + auto siteNameLines = _siteNameWidth ? 1 : 0; + auto siteNameHeight = _siteNameWidth ? lineHeight : 0; + if (_asArticle) { + _pixh = linesMax * lineHeight; + do { + _pixw = articleThumbWidth(_data->photo, _pixh); + auto wleft = innerWidth - st::webPagePhotoDelta - qMax(_pixw, lineHeight); + + newHeight = siteNameHeight; + + if (_title.isEmpty()) { + _titleLines = 0; + } else { + if (_title.countHeight(wleft) < 2 * st::webPageTitleFont->height) { + _titleLines = 1; + } else { + _titleLines = 2; + } + newHeight += _titleLines * lineHeight; + } + + auto descriptionHeight = _description.countHeight(wleft); + if (descriptionHeight < (linesMax - siteNameLines - _titleLines) * st::webPageDescriptionFont->height) { + // We have height for all the lines. + _descriptionLines = -1; + newHeight += descriptionHeight; + } else { + _descriptionLines = (linesMax - siteNameLines - _titleLines); + newHeight += _descriptionLines * lineHeight; + } + + if (newHeight >= _pixh) { + break; + } + + _pixh -= lineHeight; + } while (_pixh > lineHeight); + newHeight += bottomInfoPadding(); + } else { + newHeight = siteNameHeight; + + if (_title.isEmpty()) { + _titleLines = 0; + } else { + if (_title.countHeight(innerWidth) < 2 * st::webPageTitleFont->height) { + _titleLines = 1; + } else { + _titleLines = 2; + } + newHeight += _titleLines * lineHeight; + } + + if (_description.isEmpty()) { + _descriptionLines = 0; + } else { + auto descriptionHeight = _description.countHeight(innerWidth); + if (descriptionHeight < (linesMax - siteNameLines - _titleLines) * st::webPageDescriptionFont->height) { + // We have height for all the lines. + _descriptionLines = -1; + newHeight += descriptionHeight; + } else { + _descriptionLines = (linesMax - siteNameLines - _titleLines); + newHeight += _descriptionLines * lineHeight; + } + } + + if (_attach) { + auto attachAtTop = !_siteNameWidth && !_titleLines && !_descriptionLines; + if (!attachAtTop) newHeight += st::mediaInBubbleSkip; + + auto bubble = _attach->bubbleMargins(); + + _attach->resizeGetHeight(innerWidth + bubble.left() + bubble.right()); + newHeight += _attach->height() - bubble.top() - bubble.bottom(); + if (!_attach->additionalInfoString().isEmpty()) { + newHeight += bottomInfoPadding(); + } else if (isBubbleBottom() && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > innerWidth + bubble.left() + bubble.right()) { + newHeight += bottomInfoPadding(); + } + } + } + auto padding = inBubblePadding(); + newHeight += padding.top() + padding.bottom(); + + return { newWidth, newHeight }; +} + +TextSelection HistoryWebPage::toDescriptionSelection( + TextSelection selection) const { + return HistoryView::UnshiftItemSelection(selection, _title); +} + +TextSelection HistoryWebPage::fromDescriptionSelection( + TextSelection selection) const { + return HistoryView::ShiftItemSelection(selection, _title); +} + +void HistoryWebPage::refreshParentId(not_null<HistoryItem*> realParent) { + if (_attach) { + _attach->refreshParentId(realParent); + } +} + +void HistoryWebPage::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; + auto paintx = 0, painty = 0, paintw = width(), painth = height(); + + auto outbg = _parent->hasOutLayout(); + bool selected = (selection == FullSelection); + + auto &barfg = selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor); + auto &semibold = selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg); + auto ®ular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg); + + QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); + auto padding = inBubblePadding(); + auto tshift = padding.top(); + auto bshift = padding.bottom(); + paintw -= padding.left() + padding.right(); + auto attachAdditionalInfoText = _attach ? _attach->additionalInfoString() : QString(); + if (_asArticle) { + bshift += bottomInfoPadding(); + } else if (!attachAdditionalInfoText.isEmpty()) { + bshift += bottomInfoPadding(); + } else if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right()) { + bshift += bottomInfoPadding(); + } + + QRect bar(rtlrect(st::msgPadding.left(), tshift, st::webPageBar, height() - tshift - bshift, width())); + p.fillRect(bar, barfg); + + auto lineHeight = unitedLineHeight(); + if (_asArticle) { + const auto contextId = _parent->data()->fullId(); + _data->photo->medium->load(contextId, false, false); + bool full = _data->photo->medium->loaded(); + QPixmap pix; + auto pw = qMax(_pixw, lineHeight); + auto ph = _pixh; + auto pixw = _pixw, pixh = articleThumbHeight(_data->photo, _pixw); + auto maxw = ConvertScale(_data->photo->medium->width()), maxh = ConvertScale(_data->photo->medium->height()); + if (pixw * ph != pixh * pw) { + float64 coef = (pixw * ph > pixh * pw) ? qMin(ph / float64(pixh), maxh / float64(pixh)) : qMin(pw / float64(pixw), maxw / float64(pixw)); + pixh = qRound(pixh * coef); + pixw = qRound(pixw * coef); + } + if (full) { + pix = _data->photo->medium->pixSingle(contextId, pixw, pixh, pw, ph, ImageRoundRadius::Small); + } else { + pix = _data->photo->thumb->pixBlurredSingle(contextId, pixw, pixh, pw, ph, ImageRoundRadius::Small); + } + p.drawPixmapLeft(padding.left() + paintw - pw, tshift, width(), pix); + if (selected) { + App::roundRect(p, rtlrect(padding.left() + paintw - pw, tshift, pw, _pixh, width()), p.textPalette().selectOverlay, SelectedOverlaySmallCorners); + } + paintw -= pw + st::webPagePhotoDelta; + } + if (_siteNameWidth) { + p.setFont(st::webPageTitleFont); + p.setPen(semibold); + p.drawTextLeft(padding.left(), tshift, width(), (paintw >= _siteNameWidth) ? _data->siteName : st::webPageTitleFont->elided(_data->siteName, paintw)); + tshift += lineHeight; + } + if (_titleLines) { + p.setPen(outbg ? st::webPageTitleOutFg : st::webPageTitleInFg); + auto endskip = 0; + if (_title.hasSkipBlock()) { + endskip = _parent->skipBlockWidth(); + } + _title.drawLeftElided(p, padding.left(), tshift, paintw, width(), _titleLines, style::al_left, 0, -1, endskip, false, selection); + tshift += _titleLines * lineHeight; + } + if (_descriptionLines) { + p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg); + auto endskip = 0; + if (_description.hasSkipBlock()) { + endskip = _parent->skipBlockWidth(); + } + if (_descriptionLines > 0) { + _description.drawLeftElided(p, padding.left(), tshift, paintw, width(), _descriptionLines, style::al_left, 0, -1, endskip, false, toDescriptionSelection(selection)); + tshift += _descriptionLines * lineHeight; + } else { + _description.drawLeft(p, padding.left(), tshift, paintw, width(), style::al_left, 0, -1, toDescriptionSelection(selection)); + tshift += _description.countHeight(paintw); + } + } + if (_attach) { + auto attachAtTop = !_siteNameWidth && !_titleLines && !_descriptionLines; + if (!attachAtTop) tshift += st::mediaInBubbleSkip; + + auto attachLeft = padding.left() - bubble.left(); + auto attachTop = tshift - bubble.top(); + if (rtl()) attachLeft = width() - attachLeft - _attach->width(); + + p.translate(attachLeft, attachTop); + + auto attachSelection = selected ? FullSelection : TextSelection { 0, 0 }; + _attach->draw(p, r.translated(-attachLeft, -attachTop), attachSelection, ms); + auto pixwidth = _attach->width(); + auto pixheight = _attach->height(); + + if (_data->type == WebPageType::Video + && _attach->type() == MediaTypePhoto) { + if (_attach->isReadyForOpen()) { + if (_data->siteName == qstr("YouTube")) { + st::youtubeIcon.paint(p, (pixwidth - st::youtubeIcon.width()) / 2, (pixheight - st::youtubeIcon.height()) / 2, width()); + } else { + st::videoIcon.paint(p, (pixwidth - st::videoIcon.width()) / 2, (pixheight - st::videoIcon.height()) / 2, width()); + } + } + if (_durationWidth) { + auto dateX = pixwidth - _durationWidth - st::msgDateImgDelta - 2 * st::msgDateImgPadding.x(); + auto dateY = pixheight - st::msgDateFont->height - 2 * st::msgDateImgPadding.y() - st::msgDateImgDelta; + 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); + + p.setFont(st::msgDateFont); + p.setPen(st::msgDateImgFg); + p.drawTextLeft(dateX + st::msgDateImgPadding.x(), dateY + st::msgDateImgPadding.y(), pixwidth, _duration); + } + } + + p.translate(-attachLeft, -attachTop); + + if (!attachAdditionalInfoText.isEmpty()) { + p.setFont(st::msgDateFont); + p.setPen(selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg)); + p.drawTextLeft(st::msgPadding.left(), bar.y() + bar.height() + st::mediaInBubbleSkip, width(), attachAdditionalInfoText); + } + } +} + +TextState HistoryWebPage::textState(QPoint point, StateRequest request) const { + auto result = TextState(_parent); + + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { + return result; + } + auto paintx = 0, painty = 0, paintw = width(), painth = height(); + + QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); + auto padding = inBubblePadding(); + auto tshift = padding.top(); + auto bshift = padding.bottom(); + if (_asArticle || (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->width() + _parent->skipBlockWidth() > paintw + bubble.left() + bubble.right())) { + bshift += bottomInfoPadding(); + } + paintw -= padding.left() + padding.right(); + + auto lineHeight = unitedLineHeight(); + auto inThumb = false; + if (_asArticle) { + auto pw = qMax(_pixw, lineHeight); + if (rtlrect(padding.left() + paintw - pw, 0, pw, _pixh, width()).contains(point)) { + inThumb = true; + } + paintw -= pw + st::webPagePhotoDelta; + } + int symbolAdd = 0; + if (_siteNameWidth) { + tshift += lineHeight; + } + if (_titleLines) { + if (point.y() >= tshift && point.y() < tshift + _titleLines * lineHeight) { + Text::StateRequestElided titleRequest = request.forText(); + titleRequest.lines = _titleLines; + result = TextState(_parent, _title.getStateElidedLeft( + point - QPoint(padding.left(), tshift), + paintw, + width(), + titleRequest)); + } else if (point.y() >= tshift + _titleLines * lineHeight) { + symbolAdd += _title.length(); + } + tshift += _titleLines * lineHeight; + } + if (_descriptionLines) { + auto descriptionHeight = (_descriptionLines > 0) ? _descriptionLines * lineHeight : _description.countHeight(paintw); + if (point.y() >= tshift && point.y() < tshift + descriptionHeight) { + if (_descriptionLines > 0) { + Text::StateRequestElided descriptionRequest = request.forText(); + descriptionRequest.lines = _descriptionLines; + result = TextState(_parent, _description.getStateElidedLeft( + point - QPoint(padding.left(), tshift), + paintw, + width(), + descriptionRequest)); + } else { + result = TextState(_parent, _description.getStateLeft( + point - QPoint(padding.left(), tshift), + paintw, + width(), + request.forText())); + } + } else if (point.y() >= tshift + descriptionHeight) { + symbolAdd += _description.length(); + } + tshift += descriptionHeight; + } + if (inThumb) { + result.link = _openl; + } else if (_attach) { + auto attachAtTop = !_siteNameWidth && !_titleLines && !_descriptionLines; + if (!attachAtTop) tshift += st::mediaInBubbleSkip; + + if (QRect(padding.left(), tshift, paintw, height() - tshift - bshift).contains(point)) { + auto attachLeft = padding.left() - bubble.left(); + auto attachTop = tshift - bubble.top(); + if (rtl()) attachLeft = width() - attachLeft - _attach->width(); + result = _attach->textState(point - QPoint(attachLeft, attachTop), request); + + if (result.link && !_data->document && _data->photo && _collage.empty() && _attach->isReadyForOpen()) { + if (_data->type == WebPageType::Profile + || _data->type == WebPageType::Video) { + result.link = _openl; + } else if (_data->type == WebPageType::Photo + || _data->siteName == qstr("Twitter") + || _data->siteName == qstr("Facebook")) { + // leave photo link + } else { + result.link = _openl; + } + } + } + } + + result.symbol += symbolAdd; + return result; +} + +TextSelection HistoryWebPage::adjustSelection(TextSelection selection, TextSelectType type) const { + if (!_descriptionLines || selection.to <= _title.length()) { + return _title.adjustSelection(selection, type); + } + auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type); + if (selection.from >= _title.length()) { + return fromDescriptionSelection(descriptionSelection); + } + auto titleSelection = _title.adjustSelection(selection, type); + return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to }; +} + +void HistoryWebPage::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { + if (_attach) { + _attach->clickHandlerActiveChanged(p, active); + } +} + +void HistoryWebPage::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { + if (_attach) { + _attach->clickHandlerPressedChanged(p, pressed); + } +} + +void HistoryWebPage::playAnimation(bool autoplay) { + if (_attach) { + if (autoplay) { + _attach->autoplayAnimation(); + } else { + _attach->playAnimation(); + } + } +} + +bool HistoryWebPage::isDisplayed() const { + const auto item = _parent->data(); + return !_data->pendingTill + && !item->Has<HistoryMessageLogEntryOriginal>(); +} + +TextWithEntities HistoryWebPage::selectedText(TextSelection selection) const { + auto titleResult = _title.originalTextWithEntities( + selection, + ExpandLinksAll); + auto descriptionResult = _description.originalTextWithEntities( + toDescriptionSelection(selection), + ExpandLinksAll); + if (titleResult.text.isEmpty()) { + return descriptionResult; + } else if (descriptionResult.text.isEmpty()) { + return titleResult; + } + + titleResult.text += '\n'; + TextUtilities::Append(titleResult, std::move(descriptionResult)); + return titleResult; +} + +QMargins HistoryWebPage::inBubblePadding() const { + auto lshift = st::msgPadding.left() + st::webPageLeft; + auto rshift = st::msgPadding.right(); + auto bshift = isBubbleBottom() ? st::msgPadding.left() : st::mediaInBubbleSkip; + auto tshift = isBubbleTop() ? st::msgPadding.left() : st::mediaInBubbleSkip; + return QMargins(lshift, tshift, rshift, bshift); +} + +bool HistoryWebPage::isLogEntryOriginal() const { + return _parent->data()->isLogEntry() && _parent->media() != this; +} + +int HistoryWebPage::bottomInfoPadding() const { + if (!isBubbleBottom()) return 0; + + auto result = st::msgDateFont->height; + + // We use padding greater than st::msgPadding.bottom() in the + // bottom of the bubble so that the left line looks pretty. + // but if we have bottom skip because of the info display + // we don't need that additional padding so we replace it + // back with st::msgPadding.bottom() instead of left(). + result += st::msgPadding.bottom() - st::msgPadding.left(); + return result; +} + +HistoryWebPage::~HistoryWebPage() { + Auth().data().unregisterWebPageView(_data, _parent); +} diff --git a/Telegram/SourceFiles/history/media/history_media_web_page.h b/Telegram/SourceFiles/history/media/history_media_web_page.h new file mode 100644 index 000000000..8c91d034d --- /dev/null +++ b/Telegram/SourceFiles/history/media/history_media_web_page.h @@ -0,0 +1,118 @@ +/* +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/media/history_media.h" + +namespace Data { +class Media; +} // namespace Data + +class HistoryWebPage : public HistoryMedia { +public: + HistoryWebPage( + not_null<Element*> parent, + not_null<WebPageData*> data); + + HistoryMediaType type() const override { + return MediaTypeWebPage; + } + + void refreshParentId(not_null<HistoryItem*> realParent) override; + + void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; + TextState textState(QPoint point, StateRequest request) const override; + + bool hideMessageText() const override { + return false; + } + + [[nodiscard]] TextSelection adjustSelection( + TextSelection selection, + TextSelectType type) const override; + uint16 fullSelectionLength() const override { + return _title.length() + _description.length(); + } + bool hasTextForCopy() const override { + return false; // we do not add _title and _description in FullSelection text copy. + } + + bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { + return _attach && _attach->toggleSelectionByHandlerClick(p); + } + bool dragItemByHandler(const ClickHandlerPtr &p) const override { + return _attach && _attach->dragItemByHandler(p); + } + + TextWithEntities selectedText(TextSelection selection) const override; + + void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; + void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; + + bool isDisplayed() const override; + PhotoData *getPhoto() const override { + return _attach ? _attach->getPhoto() : nullptr; + } + DocumentData *getDocument() const override { + return _attach ? _attach->getDocument() : nullptr; + } + void stopAnimation() override { + if (_attach) _attach->stopAnimation(); + } + + not_null<WebPageData*> webpage() { + return _data; + } + + bool needsBubble() const override { + return true; + } + bool customInfoLayout() const override { + return false; + } + bool allowsFastShare() const override { + return true; + } + + HistoryMedia *attach() const { + return _attach.get(); + } + + ~HistoryWebPage(); + +private: + void playAnimation(bool autoplay) override; + QSize countOptimalSize() override; + QSize countCurrentSize(int newWidth) override; + + TextSelection toDescriptionSelection(TextSelection selection) const; + TextSelection fromDescriptionSelection(TextSelection selection) const; + QMargins inBubblePadding() const; + int bottomInfoPadding() const; + bool isLogEntryOriginal() const; + + not_null<WebPageData*> _data; + std::vector<std::unique_ptr<Data::Media>> _collage; + ClickHandlerPtr _openl; + std::unique_ptr<HistoryMedia> _attach; + + bool _asArticle = false; + int _dataVersion = -1; + int _titleLines = 0; + int _descriptionLines = 0; + + Text _title, _description; + int _siteNameWidth = 0; + + QString _duration; + int _durationWidth = 0; + + int _pixw = 0; + int _pixh = 0; + +}; diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index e8e0e6817..351d80fd1 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -13,7 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item.h" #include "history/history_message.h" #include "history/history_item_text.h" -#include "history/history_media_types.h" +#include "history/media/history_media.h" +#include "history/media/history_media_web_page.h" #include "ui/widgets/popup_menu.h" #include "ui/image/image.h" #include "chat_helpers/message_field.h" diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index e764dc8af..00798a575 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -10,8 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_service_message.h" #include "history/history_item_components.h" #include "history/history_item.h" -#include "history/history_media.h" -#include "history/history_media_grouped.h" +#include "history/media/history_media.h" +#include "history/media/history_media_grouped.h" #include "history/history.h" #include "data/data_session.h" #include "data/data_groups.h" diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 73a9d3c5c..ef8460c78 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/history_view_list_widget.h" -#include "history/history_media_types.h" +#include "history/media/history_media.h" #include "history/history_message.h" #include "history/history_item_components.h" #include "history/history_item_text.h" @@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_feed.h" #include "data/data_media_types.h" +#include "data/data_document.h" #include "styles/style_history.h" namespace HistoryView { diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 0f12ce8b5..aa2108005 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -10,8 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_cursor_state.h" #include "history/history_item_components.h" #include "history/history_message.h" -#include "history/history_media_types.h" -#include "history/history_media.h" +#include "history/media/history_media.h" +#include "history/media/history_media_web_page.h" #include "history/history.h" #include "data/data_session.h" #include "lang/lang_keys.h" diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.cpp b/Telegram/SourceFiles/history/view/history_view_service_message.cpp index 397953068..4605cf3eb 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_service_message.cpp @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_service.h" -#include "history/history_media.h" +#include "history/media/history_media.h" #include "history/history_item_components.h" #include "history/view/history_view_cursor_state.h" #include "data/data_abstract_structure.h" diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 6f8a2c29b..dc43080ff 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -43,7 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_widget.h" #include "history/history_message.h" -#include "history/history_media.h" +#include "history/media/history_media.h" #include "history/view/history_view_service_message.h" #include "history/view/history_view_element.h" #include "lang/lang_keys.h" diff --git a/Telegram/SourceFiles/media/player/media_player_float.cpp b/Telegram/SourceFiles/media/player/media_player_float.cpp index e8eb4375b..d232909c2 100644 --- a/Telegram/SourceFiles/media/player/media_player_float.cpp +++ b/Telegram/SourceFiles/media/player/media_player_float.cpp @@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document.h" #include "data/data_session.h" #include "data/data_media_types.h" -#include "history/history_media.h" +#include "history/media/history_media.h" #include "history/history_item.h" #include "history/view/history_view_element.h" #include "media/media_clip_reader.h" diff --git a/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp b/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp index bccb729f4..93626d00e 100644 --- a/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp +++ b/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp @@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_web_page.h" #include "history/history.h" -#include "history/history_media.h" +#include "history/media/history_media.h" #include "ui/image/image.h" #include "auth_session.h" #include "styles/style_mediaview.h" diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index bd20258d4..0748b0d8b 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -23,7 +23,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/media_audio.h" #include "history/history.h" #include "history/history_message.h" -#include "history/history_media_types.h" #include "data/data_media_types.h" #include "data/data_session.h" #include "window/themes/window_theme_preview.h" diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index e09acab86..1f100825c 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -220,6 +220,36 @@ <(src_loc)/history/admin_log/history_admin_log_section.h <(src_loc)/history/feed/history_feed_section.cpp <(src_loc)/history/feed/history_feed_section.h +<(src_loc)/history/media/history_media.h +<(src_loc)/history/media/history_media.cpp +<(src_loc)/history/media/history_media_call.h +<(src_loc)/history/media/history_media_call.cpp +<(src_loc)/history/media/history_media_common.h +<(src_loc)/history/media/history_media_common.cpp +<(src_loc)/history/media/history_media_contact.h +<(src_loc)/history/media/history_media_contact.cpp +<(src_loc)/history/media/history_media_document.h +<(src_loc)/history/media/history_media_document.cpp +<(src_loc)/history/media/history_media_file.h +<(src_loc)/history/media/history_media_file.cpp +<(src_loc)/history/media/history_media_game.h +<(src_loc)/history/media/history_media_game.cpp +<(src_loc)/history/media/history_media_gif.h +<(src_loc)/history/media/history_media_gif.cpp +<(src_loc)/history/media/history_media_grouped.h +<(src_loc)/history/media/history_media_grouped.cpp +<(src_loc)/history/media/history_media_invoice.h +<(src_loc)/history/media/history_media_invoice.cpp +<(src_loc)/history/media/history_media_location.h +<(src_loc)/history/media/history_media_location.cpp +<(src_loc)/history/media/history_media_photo.h +<(src_loc)/history/media/history_media_photo.cpp +<(src_loc)/history/media/history_media_sticker.h +<(src_loc)/history/media/history_media_sticker.cpp +<(src_loc)/history/media/history_media_video.h +<(src_loc)/history/media/history_media_video.cpp +<(src_loc)/history/media/history_media_web_page.h +<(src_loc)/history/media/history_media_web_page.cpp <(src_loc)/history/view/history_view_context_menu.cpp <(src_loc)/history/view/history_view_context_menu.h <(src_loc)/history/view/history_view_cursor_state.cpp @@ -249,12 +279,6 @@ <(src_loc)/history/history_inner_widget.h <(src_loc)/history/history_location_manager.cpp <(src_loc)/history/history_location_manager.h -<(src_loc)/history/history_media.h -<(src_loc)/history/history_media.cpp -<(src_loc)/history/history_media_grouped.h -<(src_loc)/history/history_media_grouped.cpp -<(src_loc)/history/history_media_types.cpp -<(src_loc)/history/history_media_types.h <(src_loc)/history/history_message.cpp <(src_loc)/history/history_message.h <(src_loc)/history/history_service.cpp