From 36a6f83c9946ff7e709c0b8bf911ca4d2df80197 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 27 Jun 2026 06:51:40 +0400 Subject: [PATCH] Edit a photo in the media editor from the rich editor --- .../SourceFiles/iv/editor/iv_editor_box.cpp | 4 + .../SourceFiles/iv/editor/iv_editor_box.h | 4 + .../iv/editor/iv_editor_session.cpp | 93 +++++++++++++++++-- .../iv/editor/iv_editor_widget.cpp | 77 +++++++++++++++ .../SourceFiles/iv/editor/iv_editor_widget.h | 8 ++ 5 files changed, 179 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/iv/editor/iv_editor_box.cpp b/Telegram/SourceFiles/iv/editor/iv_editor_box.cpp index b6485d3770..975d3f72e6 100644 --- a/Telegram/SourceFiles/iv/editor/iv_editor_box.cpp +++ b/Telegram/SourceFiles/iv/editor/iv_editor_box.cpp @@ -1198,6 +1198,10 @@ void WindowHost::Impl::setupWindow(ShowWindowDescriptor &&descriptor) { }, .requestMedia = std::move(descriptor.requestMedia), .applyPreparedMedia = std::move(descriptor.applyPreparedMedia), + .requestPhotoEditSource + = std::move(descriptor.requestPhotoEditSource), + .replacePhotoWithList + = std::move(descriptor.replacePhotoWithList), .imeCompositionStarts = window->imeCompositionStarts(), }, descriptor.peer, diff --git a/Telegram/SourceFiles/iv/editor/iv_editor_box.h b/Telegram/SourceFiles/iv/editor/iv_editor_box.h index b65dad28e1..19a75fa2e9 100644 --- a/Telegram/SourceFiles/iv/editor/iv_editor_box.h +++ b/Telegram/SourceFiles/iv/editor/iv_editor_box.h @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +#include #include #include @@ -82,6 +83,9 @@ struct ShowWindowDescriptor { RequestMediaType)> requestMedia; Fn, Ui::PreparedList, PreparedMediaPasteTarget)> applyPreparedMedia; + Fn requestPhotoEditSource; + Fn, Ui::PreparedList, State::ReplaceTarget)> + replacePhotoWithList; Fn, QPointer, rpl::producer<>)> requestMap; Fn closed; Fn showLimitToast; diff --git a/Telegram/SourceFiles/iv/editor/iv_editor_session.cpp b/Telegram/SourceFiles/iv/editor/iv_editor_session.cpp index fdcc8f1200..cd59caadbf 100644 --- a/Telegram/SourceFiles/iv/editor/iv_editor_session.cpp +++ b/Telegram/SourceFiles/iv/editor/iv_editor_session.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_editing.h" #include "apiwrap.h" #include "base/algorithm.h" +#include "base/flat_map.h" #include "base/timer.h" #include "base/weak_qptr.h" #include "base/weak_ptr.h" @@ -30,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document.h" #include "data/data_location.h" #include "data/data_photo.h" +#include "data/data_photo_media.h" #include "data/data_session.h" #include "data/data_user.h" #include "history/history.h" @@ -54,6 +56,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/confirm_box.h" #include "ui/chat/attach/attach_prepare.h" #include "ui/controls/location_picker.h" +#include "ui/image/image.h" #include "ui/rp_widget.h" #include "ui/text/text_utilities.h" #include "ui/widgets/separate_panel.h" @@ -1426,6 +1429,19 @@ private: std::move(list), std::move(target)); }, + .requestPhotoEditSource = [session = shared_from_this()]( + uint64 photoId) { + return session->photoEditSource(photoId); + }, + .replacePhotoWithList = [session = shared_from_this()]( + not_null editor, + PreparedList list, + State::ReplaceTarget replaceTarget) { + session->replaceMediaWithPreparedList( + QPointer(editor.get()), + std::move(list), + std::move(replaceTarget)); + }, .requestMap = [session = shared_from_this()]( not_null editor, QPointer parent, @@ -1588,6 +1604,47 @@ private: std::move(target)); } + void replaceMediaWithPreparedList( + QPointer editor, + PreparedList list, + State::ReplaceTarget replaceTarget) { + if (!editor) { + return; + } + if (list.error != PreparedList::Error::None) { + showToast(tr::lng_send_media_invalid_files(tr::now)); + return; + } + if (!list.files.empty()) { + Storage::UpdateImageDetails( + list.files.front(), + st::sendMediaPreviewSize, + PhotoSideLimit(true)); + } + applyPreparedList( + editor, + std::move(list), + ++_prepareBatchId, + AttachmentInsertMode::ReplaceBlock, + std::nullopt, + std::move(replaceTarget)); + } + + [[nodiscard]] QImage photoEditSource(uint64 photoId) { + if (const auto i = _originalMediaImages.find(photoId); + i != end(_originalMediaImages)) { + return i->second; + } + const auto photo = _session->data().photo(PhotoId(photoId)); + const auto media = photo->createMediaView(); + const auto origin = composeDraftOrigin(); + media->wanted(::Data::PhotoSize::Large, origin); + if (const auto large = media->image(::Data::PhotoSize::Large)) { + return large->original(); + } + return QImage(); + } + void applyPreparedList( QPointer editor, PreparedList list, @@ -1753,12 +1810,21 @@ private: AttachmentInsertMode insertMode, std::optional replaceTarget) { const auto meta = BuildAttachmentMeta(file); + auto originalImage = QImage(); + if (file.information) { + using ImageInfo = Ui::PreparedFileInformation::Image; + if (const auto image = std::get_if( + &file.information->media)) { + originalImage = image->data; + } + } const auto weak = base::make_weak(this); ++_pendingAttachmentPrepareCount; _attachmentPrepareQueue.addTask( std::make_unique( BuildPrepareTaskArgs(_session, _peer->id, std::move(file)), - [weak, editor, meta, batchId, order, insertMode, replaceTarget]( + [weak, editor, meta, batchId, order, insertMode, replaceTarget, + originalImage = std::move(originalImage)]( std::shared_ptr prepared) mutable { if (const auto session = weak.get()) { session->attachmentPrepared( @@ -1768,7 +1834,8 @@ private: batchId, order, insertMode, - std::move(replaceTarget)); + std::move(replaceTarget), + std::move(originalImage)); } })); } @@ -1780,7 +1847,8 @@ private: uint64 batchId, int order, AttachmentInsertMode insertMode, - std::optional replaceTarget) { + std::optional replaceTarget, + QImage originalImage) { _pendingAttachmentPrepareCount = std::max( _pendingAttachmentPrepareCount - 1, 0); @@ -1827,7 +1895,8 @@ private: batchId, order, insertMode, - std::move(replaceTarget)); + std::move(replaceTarget), + std::move(originalImage)); maybeContinueDeferredSubmit(); } @@ -1838,7 +1907,8 @@ private: uint64 batchId, int order, AttachmentInsertMode insertMode, - std::optional replaceTarget) { + std::optional replaceTarget, + QImage originalImage) { if (!editor) { return; } @@ -1847,7 +1917,8 @@ private: const auto blockKind = meta.blockKind; const auto uploadId = createAttachmentUpload( std::move(meta), - std::move(prepared)); + std::move(prepared), + std::move(originalImage)); if (!uploadId) { return; } @@ -1880,7 +1951,8 @@ private: [[nodiscard]] std::optional createAttachmentUpload( AttachmentMeta meta, - std::shared_ptr prepared) { + std::shared_ptr prepared, + QImage originalImage) { if (!prepared) { return std::nullopt; } @@ -1940,6 +2012,12 @@ private: } } + if (record.blockKind == RichPage::BlockKind::Photo + && !originalImage.isNull() + && record.localMediaId) { + _originalMediaImages[record.localMediaId] = std::move(originalImage); + } + _attachments.push_back(std::move(record)); _session->uploader().upload(uploadId, prepared); return uploadId; @@ -3269,6 +3347,7 @@ private: Fn _onWindowClosedContinuation; std::shared_ptr _submittedPage; std::vector _attachments; + base::flat_map _originalMediaImages; std::deque _prepareQueue; std::vector _mediaBatches; TaskQueue _attachmentPrepareQueue; diff --git a/Telegram/SourceFiles/iv/editor/iv_editor_widget.cpp b/Telegram/SourceFiles/iv/editor/iv_editor_widget.cpp index 8a9ac9f091..9763492167 100644 --- a/Telegram/SourceFiles/iv/editor/iv_editor_widget.cpp +++ b/Telegram/SourceFiles/iv/editor/iv_editor_widget.cpp @@ -10,12 +10,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/qthelp_url.h" #include "base/qt/qt_common_adapters.h" #include "base/random.h" +#include "base/weak_qptr.h" #include "chat_helpers/emoji_suggestions_widget.h" #include "chat_helpers/message_field.h" #include "core/mime_type.h" #include "data/data_msg_id.h" #include "data/data_types.h" #include "data/stickers/data_custom_emoji.h" +#include "editor/editor_layer_widget.h" +#include "editor/photo_editor.h" +#include "editor/photo_editor_common.h" #include "iv/editor/iv_editor_text_entities.h" #include "iv/markdown/iv_markdown_article_paint.h" #include "iv/markdown/iv_markdown_microtex.h" @@ -32,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/chat_style.h" #include "ui/chat/chat_theme.h" #include "ui/click_handler.h" +#include "ui/image/image.h" #include "ui/image/image_location.h" #include "ui/layers/generic_box.h" #include "ui/painter.h" @@ -2589,6 +2594,8 @@ Widget::Widget( , _customEmojiPaused(std::move(services.customEmojiPaused)) , _requestMedia(std::move(services.requestMedia)) , _applyPreparedMedia(std::move(services.applyPreparedMedia)) +, _requestPhotoEditSource(std::move(services.requestPhotoEditSource)) +, _replacePhotoWithList(std::move(services.replacePhotoWithList)) , _peer(peer) , _state(std::move(state)) , _showLimitToast(std::move(showLimitToast)) @@ -5464,6 +5471,14 @@ void Widget::showSimpleMediaMenu( requestReplaceMedia(path); }, &st::menuIconReplace); + if (block->kind == RichPage::BlockKind::Photo) { + menu->addAction( + tr::lng_context_draw(tr::now), + [=] { + editPhotoBlock(path); + }, + &st::menuIconPalette); + } if (IsPhotoVideoBlockKind(block->kind)) { const auto currentSpoiler = block->spoiler; Menu::AddCheckedAction( @@ -5761,6 +5776,68 @@ void Widget::requestReplaceMedia(State::BlockPath path) { requestMedia(std::move(target)); } +void Widget::editPhotoBlock(State::BlockPath path) { + const auto block = BlockFromPath(_state->richPage(), path); + if (!block || block->kind != RichPage::BlockKind::Photo) { + return; + } + auto target = _state->replaceTargetForBlock(path); + if (!target) { + return; + } + if (!_requestPhotoEditSource) { + return; + } + auto source = _requestPhotoEditSource(block->photoId); + if (source.isNull()) { + return; + } + const auto spoiler = block->spoiler; + const auto previewWidth = st::sendMediaPreviewSize; + const auto sourceShared = std::make_shared(std::move(source)); + const auto replaceTarget = std::make_shared( + std::move(*target)); + auto fileImage = std::make_shared(QImage(*sourceShared)); + auto editor = base::make_unique_q<::Editor::PhotoEditor>( + _outer, + _show, + nullptr, + std::move(fileImage), + ::Editor::PhotoModifications()); + const auto raw = editor.get(); + auto layer = std::make_unique<::Editor::LayerWidget>( + _outer, + std::move(editor)); + const auto weak = base::make_weak(this); + ::Editor::InitEditorLayer(layer.get(), raw, [=]( + ::Editor::PhotoModifications mods) { + const auto strong = weak.get(); + if (!strong || !mods || !strong->_replacePhotoWithList) { + return; + } + auto copy = QImage(*sourceShared); + auto list = Storage::PrepareMediaFromImage( + std::move(copy), + QByteArray(), + previewWidth); + if (list.files.empty()) { + return; + } + using ImageInfo = Ui::PreparedFileInformation::Image; + auto &file = list.files.front(); + file.spoiler = spoiler; + if (const auto image = std::get_if( + &file.information->media)) { + image->modifications = std::move(mods); + } + strong->_replacePhotoWithList( + not_null(strong), + std::move(list), + *replaceTarget); + }); + _show->showLayer(std::move(layer), Ui::LayerOption::KeepOther); +} + void Widget::touchEvent(QTouchEvent *e) { if (e->type() == QEvent::TouchCancel) { _pendingTouchHorizontalScrollPoint = std::nullopt; diff --git a/Telegram/SourceFiles/iv/editor/iv_editor_widget.h b/Telegram/SourceFiles/iv/editor/iv_editor_widget.h index 84afe988d5..4af8f809a6 100644 --- a/Telegram/SourceFiles/iv/editor/iv_editor_widget.h +++ b/Telegram/SourceFiles/iv/editor/iv_editor_widget.h @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +#include #include #include @@ -80,6 +81,9 @@ struct WidgetServices { RequestMediaType)> requestMedia; Fn, Ui::PreparedList, PreparedMediaPasteTarget)> applyPreparedMedia; + Fn requestPhotoEditSource; + Fn, Ui::PreparedList, State::ReplaceTarget)> + replacePhotoWithList; rpl::producer<> imeCompositionStarts; }; @@ -738,6 +742,7 @@ private: Qt::MouseButton button); [[nodiscard]] bool applyMediaBlockChange(Fn change); void requestReplaceMedia(State::BlockPath path); + void editPhotoBlock(State::BlockPath path); [[nodiscard]] Markdown::PreparedEditSelection structuralSelectionFromHits( const Markdown::PreparedEditHit &anchor, const Markdown::PreparedEditHit &focus) const; @@ -763,6 +768,9 @@ private: RequestMediaType)> _requestMedia; const Fn, Ui::PreparedList, PreparedMediaPasteTarget)> _applyPreparedMedia; + const Fn _requestPhotoEditSource; + const Fn, Ui::PreparedList, State::ReplaceTarget)> + _replacePhotoWithList; const not_null _peer; const std::shared_ptr _state; const Fn _showLimitToast;