Edit a photo in the media editor from the rich editor

This commit is contained in:
John Preston
2026-06-27 06:51:40 +04:00
parent 54eb43f00e
commit 36a6f83c99
5 changed files with 179 additions and 7 deletions
@@ -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,
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtCore/QPointer>
#include <QtCore/QString>
#include <QtGui/QImage>
#include <memory>
#include <optional>
@@ -82,6 +83,9 @@ struct ShowWindowDescriptor {
RequestMediaType)> requestMedia;
Fn<void(not_null<Widget*>, Ui::PreparedList, PreparedMediaPasteTarget)>
applyPreparedMedia;
Fn<QImage(uint64 /*photoId*/)> requestPhotoEditSource;
Fn<void(not_null<Widget*>, Ui::PreparedList, State::ReplaceTarget)>
replacePhotoWithList;
Fn<void(not_null<Widget*>, QPointer<QWidget>, rpl::producer<>)> requestMap;
Fn<void()> closed;
Fn<void(RichMessageLimitError)> showLimitToast;
@@ -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<Widget*> editor,
PreparedList list,
State::ReplaceTarget replaceTarget) {
session->replaceMediaWithPreparedList(
QPointer<Widget>(editor.get()),
std::move(list),
std::move(replaceTarget));
},
.requestMap = [session = shared_from_this()](
not_null<Widget*> editor,
QPointer<QWidget> parent,
@@ -1588,6 +1604,47 @@ private:
std::move(target));
}
void replaceMediaWithPreparedList(
QPointer<Widget> 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<Widget> editor,
PreparedList list,
@@ -1753,12 +1810,21 @@ private:
AttachmentInsertMode insertMode,
std::optional<State::ReplaceTarget> replaceTarget) {
const auto meta = BuildAttachmentMeta(file);
auto originalImage = QImage();
if (file.information) {
using ImageInfo = Ui::PreparedFileInformation::Image;
if (const auto image = std::get_if<ImageInfo>(
&file.information->media)) {
originalImage = image->data;
}
}
const auto weak = base::make_weak(this);
++_pendingAttachmentPrepareCount;
_attachmentPrepareQueue.addTask(
std::make_unique<PrepareAttachmentTask>(
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<FilePrepareResult> 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<State::ReplaceTarget> replaceTarget) {
std::optional<State::ReplaceTarget> 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<State::ReplaceTarget> replaceTarget) {
std::optional<State::ReplaceTarget> 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<FullMsgId> createAttachmentUpload(
AttachmentMeta meta,
std::shared_ptr<FilePrepareResult> prepared) {
std::shared_ptr<FilePrepareResult> 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<void()> _onWindowClosedContinuation;
std::shared_ptr<const RichPage> _submittedPage;
std::vector<AttachmentRecord> _attachments;
base::flat_map<uint64, QImage> _originalMediaImages;
std::deque<QueuedPrepare> _prepareQueue;
std::vector<MediaBatch> _mediaBatches;
TaskQueue _attachmentPrepareQueue;
@@ -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<QImage>(std::move(source));
const auto replaceTarget = std::make_shared<State::ReplaceTarget>(
std::move(*target));
auto fileImage = std::make_shared<Image>(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<ImageInfo>(
&file.information->media)) {
image->modifications = std::move(mods);
}
strong->_replacePhotoWithList(
not_null<Widget*>(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;
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <rpl/event_stream.h>
#include <QtCore/QPointer>
#include <QtGui/QImage>
#include <array>
#include <memory>
@@ -80,6 +81,9 @@ struct WidgetServices {
RequestMediaType)> requestMedia;
Fn<void(not_null<Widget*>, Ui::PreparedList, PreparedMediaPasteTarget)>
applyPreparedMedia;
Fn<QImage(uint64 /*photoId*/)> requestPhotoEditSource;
Fn<void(not_null<Widget*>, Ui::PreparedList, State::ReplaceTarget)>
replacePhotoWithList;
rpl::producer<> imeCompositionStarts;
};
@@ -738,6 +742,7 @@ private:
Qt::MouseButton button);
[[nodiscard]] bool applyMediaBlockChange(Fn<bool()> 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<void(not_null<Widget*>, Ui::PreparedList, PreparedMediaPasteTarget)>
_applyPreparedMedia;
const Fn<QImage(uint64)> _requestPhotoEditSource;
const Fn<void(not_null<Widget*>, Ui::PreparedList, State::ReplaceTarget)>
_replacePhotoWithList;
const not_null<PeerData*> _peer;
const std::shared_ptr<State> _state;
const Fn<void(RichMessageLimitError)> _showLimitToast;