From 7e83088a8444416e2582735c3bef086d7b22ccef Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 9 Jul 2021 14:59:48 +0300 Subject: [PATCH] Completely refactored EditCaptionBox. Moved preview content to separate widget. Increased caption area height. --- .../SourceFiles/boxes/edit_caption_box.cpp | 1338 +++++------------ Telegram/SourceFiles/boxes/edit_caption_box.h | 110 +- 2 files changed, 422 insertions(+), 1026 deletions(-) diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index 24949d74a..9d8f90517 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -7,75 +7,55 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/edit_caption_box.h" -#include "apiwrap.h" #include "api/api_editing.h" #include "api/api_text_entities.h" -#include "main/main_session.h" -#include "main/main_session_settings.h" +#include "apiwrap.h" +#include "base/event_filter.h" #include "chat_helpers/emoji_suggestions_widget.h" #include "chat_helpers/message_field.h" #include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_selector.h" -#include "base/event_filter.h" #include "core/application.h" #include "core/core_settings.h" #include "core/file_utilities.h" #include "core/mime_type.h" #include "data/data_document.h" -#include "data/data_media_types.h" -#include "data/data_photo.h" -#include "data/data_user.h" -#include "data/data_session.h" -#include "data/data_streaming.h" -#include "data/data_file_origin.h" #include "data/data_photo_media.h" -#include "data/data_document_media.h" -#include "history/history.h" +#include "data/data_session.h" +#include "editor/photo_editor_layer_widget.h" #include "history/history_drag_area.h" #include "history/history_item.h" -#include "history/view/media/history_view_document.h" // DrawThumbnailAsSongCover -#include "platform/platform_specific.h" #include "lang/lang_keys.h" -#include "media/streaming/media_streaming_instance.h" -#include "media/streaming/media_streaming_player.h" -#include "media/streaming/media_streaming_document.h" -#include "media/streaming/media_streaming_loader_local.h" -#include "platform/platform_file_utilities.h" -#include "storage/localimageloader.h" -#include "storage/storage_media_prepare.h" +#include "main/main_session.h" +#include "main/main_session_settings.h" #include "mtproto/mtproto_config.h" -#include "ui/image/image.h" -#include "ui/widgets/input_fields.h" -#include "ui/widgets/checkbox.h" -#include "ui/widgets/checkbox.h" -#include "ui/text/format_song_document_name.h" -#include "ui/text/format_values.h" -#include "ui/text/text_options.h" -#include "ui/chat/attach/attach_controls.h" -#include "ui/chat/attach/attach_prepare.h" +#include "platform/platform_specific.h" +#include "storage/localimageloader.h" // SendMediaType +#include "storage/storage_media_prepare.h" +#include "ui/chat/attach/attach_item_single_file_preview.h" +#include "ui/chat/attach/attach_item_single_media_preview.h" +#include "ui/chat/attach/attach_single_file_preview.h" +#include "ui/chat/attach/attach_single_media_preview.h" #include "ui/controls/emoji_button.h" +#include "ui/image/image.h" #include "ui/toast/toast.h" -#include "ui/cached_round_corners.h" -#include "ui/abstract_button.h" #include "ui/ui_utility.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/input_fields.h" +#include "ui/widgets/scroll_area.h" +#include "ui/wrap/fade_wrap.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/wrap/vertical_layout.h" #include "window/window_session_controller.h" -#include "confirm_box.h" -#include "apiwrap.h" -#include "facades.h" // App::LambdaDelayed. -#include "styles/style_layers.h" #include "styles/style_boxes.h" -#include "styles/style_chat_helpers.h" #include "styles/style_chat.h" - -#include "editor/photo_editor_layer_widget.h" +#include "styles/style_chat_helpers.h" +#include "styles/style_layers.h" #include namespace { -using namespace ::Media::Streaming; -using Data::PhotoSize; - auto ListFromMimeData(not_null data) { using Error = Ui::PreparedList::Error; auto result = data->hasUrls() @@ -101,6 +81,26 @@ auto ListFromMimeData(not_null data) { return result; } +Ui::AlbumType ComputeAlbumType(not_null item) { + if (item->groupId().empty()) { + return Ui::AlbumType(); + } + const auto media = item->media(); + + if (media->photo()) { + return Ui::AlbumType::PhotoVideo; + } else if (const auto document = media->document()) { + if (document->isVideoFile()) { + return Ui::AlbumType::PhotoVideo; + } else if (document->isSong()) { + return Ui::AlbumType::Music; + } else { + return Ui::AlbumType::File; + } + } + return Ui::AlbumType(); +} + } // namespace EditCaptionBox::EditCaptionBox( @@ -108,271 +108,132 @@ EditCaptionBox::EditCaptionBox( not_null controller, not_null item) : _controller(controller) -, _msgId(item->fullId()) { +, _historyItem(item) +, _isAllowedEditMedia(item->media() + ? item->media()->allowsEditMedia() + : false) +, _albumType(ComputeAlbumType(item)) +, _controls(base::make_unique_q(this)) +, _scroll(base::make_unique_q(this, st::boxScroll)) +, _field(base::make_unique_q( + this, + st::confirmCaptionArea, + Ui::InputField::Mode::MultiLine, + tr::lng_photo_caption(), + PrepareEditText(item))) +, _emojiToggle(base::make_unique_q( + this, + st::boxAttachEmoji)) +, _topShadow(base::make_unique_q(this)) +, _bottomShadow(base::make_unique_q(this)) { Expects(item->media() != nullptr); Expects(item->media()->allowsEditCaption()); - _isAllowedEditMedia = item->media()->allowsEditMedia(); - auto dimensions = QSize(); - const auto media = item->media(); + _controller->session().data().itemRemoved( + _historyItem->fullId() + ) | rpl::start_with_next([=] { + closeBox(); + }, lifetime()); +} - if (!item->groupId().empty()) { - if (media->photo()) { - _albumType = Ui::AlbumType::PhotoVideo; - } else if (const auto document = media->document()) { - if (document->isVideoFile()) { - _albumType = Ui::AlbumType::PhotoVideo; - } else if (document->isSong()) { - _albumType = Ui::AlbumType::Music; - } else { - _albumType = Ui::AlbumType::File; - } - } - } +EditCaptionBox::~EditCaptionBox() = default; - if (const auto photo = media->photo()) { - _photoMedia = photo->createMediaView(); - _photoMedia->wanted(PhotoSize::Large, _msgId); - dimensions = _photoMedia->size(PhotoSize::Large); - if (dimensions.isEmpty()) { - dimensions = QSize(1, 1); - } - _photo = true; - } else if (const auto document = media->document()) { - _documentMedia = document->createMediaView(); - _documentMedia->thumbnailWanted(_msgId); - dimensions = _documentMedia->thumbnail() - ? _documentMedia->thumbnail()->size() - : document->dimensions; - if (document->isAnimation()) { - _gifw = style::ConvertScale(document->dimensions.width()); - _gifh = style::ConvertScale(document->dimensions.height()); - _animated = true; - } else if (document->isVideoFile()) { - _animated = true; - } else { - _doc = true; - } - } else { - Unexpected("Photo or document should be set."); - } - const auto editData = PrepareEditText(item); +void EditCaptionBox::prepare() { + addButton(tr::lng_settings_save(), [=] { save(); }); + addButton(tr::lng_cancel(), [=] { closeBox(); }); - const auto computeImage = [=] { - if (_documentMedia) { - return _documentMedia->thumbnail(); - } else if (const auto large = _photoMedia->image(PhotoSize::Large)) { - return large; - } else if (const auto thumbnail = _photoMedia->image( - PhotoSize::Thumbnail)) { - return thumbnail; - } else if (const auto small = _photoMedia->image(PhotoSize::Small)) { - return small; - } else { - return _photoMedia->thumbnailInline(); - } + updateBoxSize(); + + setupField(); + setupEmojiPanel(); + + rebuildPreview(); + setupEditEventHandler(); + setupShadows(); + + setupControls(); + setupPhotoEditorEventHandler(); + + setupDragArea(); + + captionResized(); +} + +void EditCaptionBox::rebuildPreview() { + const auto gifPaused = [controller = _controller] { + return controller->isGifPausedAtLeastFor( + Window::GifPauseReason::Layer); }; - if (!_animated && _documentMedia) { - if (dimensions.isEmpty()) { - _thumbw = 0; - _thumbnailImageLoaded = true; + if (_preparedList.files.empty()) { + const auto media = _historyItem->media(); + const auto photo = media->photo(); + const auto document = media->document(); + if (photo || document->isVideoFile() || document->isAnimation()) { + _isPhoto = true; + const auto media = Ui::CreateChild( + this, + gifPaused, + _historyItem, + Ui::AttachControls::Type::EditOnly); + _photoMedia = media->sharedPhotoMedia(); + _content.reset(media); } else { - const auto thumbSize = (!media->document()->isSongWithCover() - ? st::msgFileThumbLayout - : st::msgFileLayout).thumbSize; - const auto tw = dimensions.width(), th = dimensions.height(); - if (tw > th) { - _thumbw = (tw * thumbSize) / th; - } else { - _thumbw = thumbSize; - } - _refreshThumbnail = [=] { - const auto image = computeImage(); - if (!image) { - return; - } - if (media->document()->isSongWithCover()) { - const auto size = QSize(thumbSize, thumbSize); - _thumb = QPixmap(size); - _thumb.fill(Qt::transparent); - Painter p(&_thumb); - - HistoryView::DrawThumbnailAsSongCover( - p, - _documentMedia, - QRect(QPoint(), size)); - } else { - const auto options = Images::Option::Smooth - | Images::Option::RoundedSmall - | Images::Option::RoundedTopLeft - | Images::Option::RoundedTopRight - | Images::Option::RoundedBottomLeft - | Images::Option::RoundedBottomRight; - _thumb = Ui::PixmapFromImage(Images::prepare( - image->original(), - _thumbw * cIntRetinaFactor(), - 0, - options, - thumbSize, - thumbSize)); - } - _thumbnailImageLoaded = true; - }; - _refreshThumbnail(); - } - - if (_documentMedia) { - const auto document = _documentMedia->owner(); - const auto nameString = document->isVoiceMessage() - ? tr::lng_media_audio(tr::now) - : Ui::Text::FormatSongNameFor(document).string(); - setName(nameString, document->size); - _isImage = document->isImage(); - _isAudio = document->isVoiceMessage() - || document->isAudioFile(); + _isPhoto = false; + _content.reset(Ui::CreateChild( + this, + _historyItem, + Ui::AttachControls::Type::EditOnly)); } } else { - auto maxW = 0, maxH = 0; - const auto limitW = st::sendMediaPreviewSize; - auto limitH = std::min(st::confirmMaxHeight, _gifh ? _gifh : INT_MAX); - if (_animated) { - maxW = std::max(dimensions.width(), 1); - maxH = std::max(dimensions.height(), 1); - if (maxW * limitH > maxH * limitW) { - if (maxW < limitW) { - maxH = maxH * limitW / maxW; - maxW = limitW; - } - } else { - if (maxH < limitH) { - maxW = maxW * limitH / maxH; - maxH = limitH; - } - } - _refreshThumbnail = [=] { - const auto image = computeImage(); - const auto use = image ? image : Image::BlankMedia().get(); - const auto options = Images::Option::Smooth - | Images::Option::Blurred; - _thumb = use->pixNoCache( - maxW * cIntRetinaFactor(), - maxH * cIntRetinaFactor(), - options, - maxW, - maxH); - _thumbnailImageLoaded = true; - }; + const auto &file = _preparedList.files.front(); + + const auto media = Ui::SingleMediaPreview::Create( + this, + gifPaused, + file, + Ui::AttachControls::Type::EditOnly); + if (media) { + _isPhoto = media->isPhoto(); + _content.reset(media); } else { - Assert(_photoMedia != nullptr); - - maxW = dimensions.width(); - maxH = dimensions.height(); - _refreshThumbnail = [=] { - const auto image = computeImage(); - const auto photo = _photoMedia->image(Data::PhotoSize::Large); - const auto use = photo - ? photo - : image - ? image - : Image::BlankMedia().get(); - const auto options = Images::Option::Smooth - | (photo - ? Images::Option(0) - : Images::Option::Blurred); - _thumbnailImageLoaded = (photo != nullptr); - _thumb = use->pixNoCache( - maxW * cIntRetinaFactor(), - maxH * cIntRetinaFactor(), - options, - maxW, - maxH); - }; + _isPhoto = false; + _content.reset(Ui::CreateChild( + this, + file, + Ui::AttachControls::Type::EditOnly)); } - _refreshThumbnail(); - - const auto resizeDimensions = [&](int &thumbWidth, int &thumbHeight, int &thumbX) { - auto tw = thumbWidth, th = thumbHeight; - if (!tw || !th) { - tw = th = 1; - } - - // Edit media button takes place on thumb preview - // And its height can be greater than height of thumb. - const auto minThumbHeight = st::editMediaButtonSize - + st::editMediaButtonSkip * 2; - const auto minThumbWidth = minThumbHeight * tw / th; - - if (thumbWidth < st::sendMediaPreviewSize) { - thumbWidth = (thumbWidth > minThumbWidth) - ? thumbWidth - : minThumbWidth; - } else { - thumbWidth = st::sendMediaPreviewSize; - } - const auto maxThumbHeight = std::min(int(std::round(1.5 * thumbWidth)), limitH); - thumbHeight = int(std::round(th * float64(thumbWidth) / tw)); - if (thumbHeight > maxThumbHeight) { - thumbWidth = int(std::round(thumbWidth * float64(maxThumbHeight) / thumbHeight)); - thumbHeight = maxThumbHeight; - if (thumbWidth < 10) { - thumbWidth = 10; - } - } - thumbX = (st::boxWideWidth - thumbWidth) / 2; - }; - - if (_documentMedia && _documentMedia->owner()->isAnimation()) { - resizeDimensions(_gifw, _gifh, _gifx); - } - limitH = std::min(st::confirmMaxHeight, _gifh ? _gifh : INT_MAX); - - _thumbw = _thumb.width(); - _thumbh = _thumb.height(); - // If thumb's and resized gif's sizes are equal, - // Then just take made values. - if (_thumbw == _gifw && _thumbh == _gifh) { - _thumbx = (st::boxWideWidth - _thumbw) / 2; - } else { - resizeDimensions(_thumbw, _thumbh, _thumbx); - } - - const auto prepareBasicThumb = _refreshThumbnail; - const auto scaleThumbDown = [=] { - _thumb = Ui::PixmapFromImage(_thumb.toImage().scaled( - _thumbw * cIntRetinaFactor(), - _thumbh * cIntRetinaFactor(), - Qt::KeepAspectRatio, - Qt::SmoothTransformation)); - _thumb.setDevicePixelRatio(cRetinaFactor()); - }; - _refreshThumbnail = [=] { - prepareBasicThumb(); - scaleThumbDown(); - }; - scaleThumbDown(); } - Assert(_animated || _photo || _doc); - Assert(_thumbnailImageLoaded || _refreshThumbnail); + Assert(_content != nullptr); - if (!_thumbnailImageLoaded) { - _controller->session().downloaderTaskFinished( - ) | rpl::start_with_next([=] { - if (_thumbnailImageLoaded - || (_photoMedia && !_photoMedia->image(PhotoSize::Large)) - || (_documentMedia && !_documentMedia->thumbnail())) { - return; - } - _refreshThumbnail(); - update(); - }, lifetime()); - } - _field.create( - this, - st::confirmCaptionArea, - Ui::InputField::Mode::MultiLine, - tr::lng_photo_caption(), - editData); + rpl::combine( + _content->heightValue(), + _footerHeight.value(), + rpl::single(st::boxPhotoPadding.top()), + rpl::mappers::_1 + rpl::mappers::_2 + rpl::mappers::_3 + ) | rpl::start_with_next([=](int height) { + setDimensions( + st::boxWideWidth, + std::min(st::sendMediaPreviewHeightMax, height), + true); + }, _content->lifetime()); + + _content->editRequests( + ) | rpl::start_to_stream(_editMediaClicks, _content->lifetime()); + + _content->modifyRequests( + ) | rpl::start_to_stream(_photoEditorOpens, _content->lifetime()); + + _content->heightValue( + ) | rpl::start_to_stream(_contentHeight, _content->lifetime()); + + _scroll->setOwnedWidget( + object_ptr::fromRaw(_content.get())); + + captionResized(); +} + +void EditCaptionBox::setupField() { _field->setMaxLength( _controller->session().serverConfig().captionLengthMax); _field->setSubmitSettings( @@ -383,310 +244,103 @@ EditCaptionBox::EditCaptionBox( _field->setMarkdownReplacesEnabled(rpl::single(true)); _field->setEditLinkCallback( DefaultEditLinkCallback(_controller, _field)); + _field->setMaxHeight(st::confirmCaptionArea.heightMax); InitSpellchecker(_controller, _field); - auto label = object_ptr>( + connect(_field, &Ui::InputField::submitted, [=] { save(); }); + connect(_field, &Ui::InputField::cancelled, [=] { closeBox(); }); + connect(_field, &Ui::InputField::resized, [=] { captionResized(); }); + _field->setMimeDataHook([=]( + not_null data, + Ui::InputField::MimeAction action) { + if (action == Ui::InputField::MimeAction::Check) { + if (!data->hasText() && !_isAllowedEditMedia) { + return false; + } else if (Storage::ValidateEditMediaDragData(data, _albumType)) { + return true; + } + return data->hasText(); + } else if (action == Ui::InputField::MimeAction::Insert) { + return fileFromClipboard(data); + } + Unexpected("Action in MimeData hook."); + }); + Ui::Emoji::SuggestionsController::Init( + getDelegate()->outerContainer(), + _field, + &_controller->session()); + + auto cursor = _field->textCursor(); + cursor.movePosition(QTextCursor::End); + _field->setTextCursor(cursor); +} + +void EditCaptionBox::setupShadows() { + using namespace rpl::mappers; + + const auto _topShadow = Ui::CreateChild(this); + const auto _bottomShadow = Ui::CreateChild(this); + _scroll->geometryValue( + ) | rpl::start_with_next([=](const QRect &geometry) { + _topShadow->resizeToWidth(geometry.width()); + _topShadow->move( + geometry.x(), + geometry.y()); + _bottomShadow->resizeToWidth(geometry.width()); + _bottomShadow->move( + geometry.x(), + geometry.y() + geometry.height() - st::lineWidth); + }, _topShadow->lifetime()); + + _topShadow->toggleOn(_scroll->scrollTopValue() | rpl::map(_1 > 0)); + _bottomShadow->toggleOn(rpl::combine( + _scroll->scrollTopValue(), + _scroll->heightValue(), + _contentHeight.events(), + _1 + _2 < _3)); +} + +void EditCaptionBox::setupControls() { + auto hintLabelToggleOn = _isPhoto.value( + ) | rpl::map([=](bool value) { + return _controller->session().settings().photoEditorHintShown() + ? value + : false; + }); + + _controls->add(object_ptr>( this, object_ptr( this, tr::lng_edit_photo_editor_hint(tr::now), st::editMediaHintLabel), - st::editMediaLabelMargins); - _hintLabel = label.data(); - _hintLabel->toggle( - _controller->session().settings().photoEditorHintShown() - ? _photo - : false, - anim::type::instant); + st::editMediaLabelMargins) + )->toggleOn(std::move(hintLabelToggleOn), anim::type::instant); - auto r = object_ptr>( + _controls->add(object_ptr>( this, object_ptr( this, tr::lng_send_compressed(tr::now), true, st::defaultBoxCheckbox), - st::editMediaCheckboxMargins); - _wayWrap = r.data(); - _wayWrap->toggle(false, anim::type::instant); - - r->entity()->checkedChanges( + st::editMediaCheckboxMargins) + )->toggleOn( + _isPhoto.value( + ) | rpl::map([=](bool value) { + return value && (_albumType == Ui::AlbumType::None); + }), + anim::type::instant + )->entity()->checkedChanges( ) | rpl::start_with_next([&](bool checked) { _asFile = !checked; - }, _wayWrap->lifetime()); + }, _controls->lifetime()); - _controller->session().data().itemRemoved( - _msgId - ) | rpl::start_with_next([=] { - closeBox(); - }, lifetime()); - - _photoEditorOpens.events( - ) | rpl::start_with_next([=, controller = _controller] { - const auto previewWidth = st::sendMediaPreviewSize; - if (!_preparedList.files.empty()) { - Editor::OpenWithPreparedFile( - this, - controller, - &_preparedList.files.front(), - previewWidth, - [=] { updateEditPreview(); }); - } else { - auto callback = [=](const Editor::PhotoModifications &mods) { - if (!mods) { - return; - } - auto copy = computeImage()->original(); - _preparedList = Storage::PrepareMediaFromImage( - std::move(copy), - QByteArray(), - previewWidth); - - using ImageInfo = Ui::PreparedFileInformation::Image; - auto &file = _preparedList.files.front(); - const auto image = std::get_if( - &file.information->media); - - image->modifications = mods; - Storage::UpdateImageDetails(file, previewWidth); - updateEditPreview(); - }; - const auto fileImage = std::make_shared(*computeImage()); - controller->showLayer( - std::make_unique( - this, - &controller->window(), - fileImage, - Editor::PhotoModifications(), - std::move(callback)), - Ui::LayerOption::KeepOther); - } - }, lifetime()); + _controls->resizeToWidth(st::sendMediaPreviewSize); } -EditCaptionBox::~EditCaptionBox() = default; - -void EditCaptionBox::emojiFilterForGeometry(not_null event) { - const auto type = event->type(); - if (type == QEvent::Move || type == QEvent::Resize) { - // updateEmojiPanelGeometry uses not only container geometry, but - // also container children geometries that will be updated later. - crl::on_main(this, [=] { updateEmojiPanelGeometry(); }); - } -} - -void EditCaptionBox::updateEmojiPanelGeometry() { - const auto parent = _emojiPanel->parentWidget(); - const auto global = _emojiToggle->mapToGlobal({ 0, 0 }); - const auto local = parent->mapFromGlobal(global); - _emojiPanel->moveBottomRight( - local.y(), - local.x() + _emojiToggle->width() * 3); -} - -void EditCaptionBox::prepareStreamedPreview() { - const auto isListEmpty = _preparedList.files.empty(); - if (_streamed) { - return; - } else if (!_documentMedia && isListEmpty) { - return; - } - const auto document = _documentMedia - ? _documentMedia->owner().get() - : nullptr; - if (document && document->isAnimation()) { - setupStreamedPreview( - document->owner().streaming().sharedDocument( - document, - _msgId)); - } else if (!isListEmpty) { - const auto file = &_preparedList.files.front(); - auto loader = file->path.isEmpty() - ? MakeBytesLoader(file->content) - : MakeFileLoader(file->path); - setupStreamedPreview(std::make_shared(std::move(loader))); - } -} - -void EditCaptionBox::setupStreamedPreview(std::shared_ptr shared) { - if (!shared) { - return; - } - _streamed = std::make_unique( - std::move(shared), - [=] { update(); }); - _streamed->lockPlayer(); - _streamed->player().updates( - ) | rpl::start_with_next_error([=](Update &&update) { - handleStreamingUpdate(std::move(update)); - }, [=](Error &&error) { - handleStreamingError(std::move(error)); - }, _streamed->lifetime()); - - if (_streamed->ready()) { - streamingReady(base::duplicate(_streamed->info())); - } - checkStreamedIsStarted(); -} - -void EditCaptionBox::handleStreamingUpdate(Update &&update) { - v::match(update.data, [&](Information &update) { - streamingReady(std::move(update)); - }, [&](const PreloadedVideo &update) { - }, [&](const UpdateVideo &update) { - this->update(); - }, [&](const PreloadedAudio &update) { - }, [&](const UpdateAudio &update) { - }, [&](const WaitingForData &update) { - }, [&](MutedByOther) { - }, [&](Finished) { - }); -} - -void EditCaptionBox::handleStreamingError(Error &&error) { -} - -void EditCaptionBox::streamingReady(Information &&info) { - const auto calculateGifDimensions = [&] { - const auto scaled = QSize( - info.video.size.width(), - info.video.size.height() - ).scaled( - st::sendMediaPreviewSize, - st::confirmMaxHeight, - Qt::KeepAspectRatio); - _thumbw = _gifw = scaled.width(); - _thumbh = _gifh = scaled.height(); - _thumbx = _gifx = (st::boxWideWidth - _gifw) / 2; - updateBoxSize(); - }; - // If gif file is not mp4, - // Its dimension values will be known only after reading. - if (_gifw <= 0 || _gifh <= 0) { - calculateGifDimensions(); - } -} - -void EditCaptionBox::updateEditPreview() { - using Info = Ui::PreparedFileInformation; - - const auto file = &_preparedList.files.front(); - const auto fileMedia = &file->information->media; - - const auto fileinfo = QFileInfo(file->path); - const auto filename = fileinfo.fileName(); - - const auto mime = file->information->filemime; - _isImage = Core::FileIsImage(filename, mime); - _isAudio = false; - _animated = false; - _photo = false; - _doc = false; - _streamed = nullptr; - _thumbw = _thumbh = _thumbx = 0; - _gifw = _gifh = _gifx = 0; - - auto isGif = false; - auto shouldAsDoc = true; - auto docPhotoSize = QSize(); - if (const auto image = std::get_if(fileMedia)) { - shouldAsDoc = !Ui::ValidateThumbDimensions( - image->data.width(), - image->data.height() - ) || (_albumType == Ui::AlbumType::File); - if (shouldAsDoc) { - docPhotoSize.setWidth(image->data.width()); - docPhotoSize.setHeight(image->data.height()); - } - isGif = image->animated; - _animated = isGif; - _photo = !isGif && !shouldAsDoc; - _isImage = true; - } else if (const auto video = std::get_if(fileMedia)) { - isGif = video->isGifv; - _animated = true; - shouldAsDoc = false; - } - if (shouldAsDoc) { - auto nameString = filename; - if (const auto song = std::get_if(fileMedia)) { - nameString = Ui::Text::FormatSongName( - filename, - song->title, - song->performer).string(); - _isAudio = true; - - if (auto cover = song->cover; !cover.isNull()) { - _thumb = Ui::PrepareSongCoverForThumbnail( - cover, - st::msgFileLayout.thumbSize); - _thumbw = _thumb.width() / cIntRetinaFactor(); - _thumbh = _thumb.height() / cIntRetinaFactor(); - } - } - - const auto getExt = [&] { - auto patterns = Core::MimeTypeForName(mime).globPatterns(); - if (!patterns.isEmpty()) { - return patterns.front().replace('*', QString()); - } - return QString(); - }; - setName( - nameString.isEmpty() - ? filedialogDefaultName( - _isImage ? qsl("image") : qsl("file"), - getExt(), - QString(), - true) - : nameString, - fileinfo.size() - ? fileinfo.size() - : _preparedList.files.front().content.size()); - // Show image dimensions if it should be sent as doc. - if (_isImage && docPhotoSize.isValid()) { - _status = Ui::FormatImageSizeText(docPhotoSize); - } - _doc = true; - } - - const auto showCheckbox = _photo && (_albumType == Ui::AlbumType::None); - _wayWrap->toggle(showCheckbox, anim::type::instant); - if (_controller->session().settings().photoEditorHintShown()) { - _hintLabel->toggle(_photo, anim::type::instant); - } - _photoEditorButton->setVisible(_photo); - - if (!_doc) { - _thumb = Ui::PixmapFromImage( - file->preview.scaled( - st::sendMediaPreviewSize * cIntRetinaFactor(), - (st::confirmMaxHeight - (showCheckbox - ? st::confirmMaxHeightSkip - : 0)) * cIntRetinaFactor(), - Qt::KeepAspectRatio)); - _thumbw = _thumb.width() / cIntRetinaFactor(); - _thumbh = _thumb.height() / cIntRetinaFactor(); - _thumbx = (st::boxWideWidth - _thumbw) / 2; - if (isGif) { - _gifw = _thumbw; - _gifh = _thumbh; - _gifx = _thumbx; - prepareStreamedPreview(); - } - } - updateEditMediaButton(); - updateCaptionMaxHeight(); - captionResized(); -} - -void EditCaptionBox::updateEditMediaButton() { - _editMedia->setVisible(!_doc); - _editFile->setVisible(_doc); -} - -void EditCaptionBox::createEditMediaButton() { +void EditCaptionBox::setupEditEventHandler() { const auto callback = [=](FileDialog::OpenResult &&result) { auto showError = [](tr::phrase<> t) { Ui::Toast::Show(t(tr::now)); @@ -734,171 +388,60 @@ void EditCaptionBox::createEditMediaButton() { ) | rpl::start_with_next( buttonCallback, lifetime()); - - // Create edit media button. - _editMedia.create(this, Ui::AttachControls::Type::EditOnly); - _editFile.create(this, st::editMediaButton); - updateEditMediaButton(); - _editFile->setClickedCallback( - App::LambdaDelayed( - st::historyAttach.ripple.hideDuration, - this, - buttonCallback)); - - _editMedia->editRequests( - ) | rpl::start_with_next(buttonCallback, _editMedia->lifetime()); - - _photoEditorButton = base::make_unique_q(this); - _photoEditorButton->clicks( - ) | rpl::to_empty | rpl::start_to_stream( - _photoEditorOpens, - _photoEditorButton->lifetime()); - - _photoEditorButton->raise(); - _editMedia->raise(); } -void EditCaptionBox::prepare() { - if (_animated) { - prepareStreamedPreview(); - } - - addButton(tr::lng_settings_save(), [this] { save(); }); - if (_isAllowedEditMedia) { - createEditMediaButton(); - } else { - _preparedList.files.clear(); - } - addButton(tr::lng_cancel(), [this] { closeBox(); }); - - updateBoxSize(); - connect(_field, &Ui::InputField::submitted, [=] { save(); }); - connect(_field, &Ui::InputField::cancelled, [=] { closeBox(); }); - connect(_field, &Ui::InputField::resized, [=] { captionResized(); }); - _field->setMimeDataHook([=]( - not_null data, - Ui::InputField::MimeAction action) { - if (action == Ui::InputField::MimeAction::Check) { - if (!data->hasText() && !_isAllowedEditMedia) { - return false; - } else if (Storage::ValidateEditMediaDragData(data, _albumType)) { - return true; +void EditCaptionBox::setupPhotoEditorEventHandler() { + _photoEditorOpens.events( + ) | rpl::start_with_next([=, controller = _controller] { + const auto previewWidth = st::sendMediaPreviewSize; + if (!_preparedList.files.empty()) { + Editor::OpenWithPreparedFile( + this, + controller, + &_preparedList.files.front(), + previewWidth, + [=] { rebuildPreview(); }); + } else if (_photoMedia) { + const auto large = _photoMedia->image(Data::PhotoSize::Large); + if (!large) { + return; } - return data->hasText(); - } else if (action == Ui::InputField::MimeAction::Insert) { - return fileFromClipboard(data); + auto callback = [=](const Editor::PhotoModifications &mods) { + if (!mods || !_photoMedia) { + return; + } + const auto large = _photoMedia->image(Data::PhotoSize::Large); + if (!large) { + return; + } + auto copy = large->original(); + _preparedList = Storage::PrepareMediaFromImage( + std::move(copy), + QByteArray(), + previewWidth); + + using ImageInfo = Ui::PreparedFileInformation::Image; + auto &file = _preparedList.files.front(); + const auto image = std::get_if( + &file.information->media); + + image->modifications = mods; + Storage::UpdateImageDetails(file, previewWidth); + rebuildPreview(); + }; + const auto fileImage = std::make_shared(*large); + controller->showLayer( + std::make_unique( + this, + &controller->window(), + fileImage, + Editor::PhotoModifications(), + std::move(callback)), + Ui::LayerOption::KeepOther); } - Unexpected("action in MimeData hook."); - }); - Ui::Emoji::SuggestionsController::Init( - getDelegate()->outerContainer(), - _field, - &_controller->session()); - - setupEmojiPanel(); - - auto cursor = _field->textCursor(); - cursor.movePosition(QTextCursor::End); - _field->setTextCursor(cursor); - updateCaptionMaxHeight(); - - setupDragArea(); -} - -bool EditCaptionBox::fileFromClipboard(not_null data) { - return setPreparedList(ListFromMimeData(data)); -} - -bool EditCaptionBox::setPreparedList(Ui::PreparedList &&list) { - if (!_isAllowedEditMedia) { - return false; - } - using Error = Ui::PreparedList::Error; - using Type = Ui::PreparedFile::Type; - if (list.error != Error::None || list.files.empty()) { - return false; - } - auto file = &list.files.front(); - const auto invalidForAlbum = (_albumType != Ui::AlbumType::None) - && !file->canBeInAlbumType(_albumType); - if (_albumType == Ui::AlbumType::PhotoVideo) { - using Video = Ui::PreparedFileInformation::Video; - if (const auto video = std::get_if