2
0
mirror of https://github.com/telegramdesktop/tdesktop synced 2025-09-05 00:46:08 +00:00

Edit media captions in message field.

This commit is contained in:
John Preston
2023-04-07 18:32:53 +04:00
parent e3f2dcec22
commit 42c96b4c7f
11 changed files with 355 additions and 130 deletions

View File

@@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "mainwidget.h" // controller->content() -> QWidget*
#include "mtproto/mtproto_config.h"
#include "platform/platform_specific.h"
#include "storage/localimageloader.h" // SendMediaType
@@ -69,7 +70,9 @@ namespace {
constexpr auto kChangesDebounceTimeout = crl::time(1000);
auto ListFromMimeData(not_null<const QMimeData*> data, bool premium) {
[[nodiscard]] Ui::PreparedList ListFromMimeData(
not_null<const QMimeData*> data,
bool premium) {
using Error = Ui::PreparedList::Error;
const auto list = Core::ReadMimeUrls(data);
auto result = !list.isEmpty()
@@ -89,7 +92,7 @@ auto ListFromMimeData(not_null<const QMimeData*> data, bool premium) {
return result;
}
Ui::AlbumType ComputeAlbumType(not_null<HistoryItem*> item) {
[[nodiscard]] Ui::AlbumType ComputeAlbumType(not_null<HistoryItem*> item) {
if (item->groupId().empty()) {
return Ui::AlbumType();
}
@@ -109,17 +112,130 @@ Ui::AlbumType ComputeAlbumType(not_null<HistoryItem*> item) {
return Ui::AlbumType();
}
bool CanBeCompressed(Ui::AlbumType type) {
[[nodiscard]] bool CanBeCompressed(Ui::AlbumType type) {
return (type == Ui::AlbumType::None)
|| (type == Ui::AlbumType::PhotoVideo);
}
void ChooseReplacement(
not_null<Window::SessionController*> controller,
Ui::AlbumType type,
Fn<void(Ui::PreparedList&&)> chosen) {
const auto weak = base::make_weak(controller);
const auto callback = [=](FileDialog::OpenResult &&result) {
const auto strong = weak.get();
if (!strong) {
return;
}
const auto showError = [=](tr::phrase<> t) {
if (const auto strong = weak.get()) {
strong->showToast({ t(tr::now) });
}
};
const auto checkResult = [=](const Ui::PreparedList &list) {
if (list.files.size() != 1) {
return false;
}
const auto &file = list.files.front();
const auto mime = file.information->filemime;
if (Core::IsMimeSticker(mime)) {
showError(tr::lng_edit_media_invalid_file);
return false;
} else if (type != Ui::AlbumType::None
&& !file.canBeInAlbumType(type)) {
showError(tr::lng_edit_media_album_error);
return false;
}
return true;
};
const auto premium = strong->session().premium();
auto list = Storage::PreparedFileFromFilesDialog(
std::move(result),
checkResult,
showError,
st::sendMediaPreviewSize,
premium);
if (list) {
chosen(std::move(*list));
}
};
const auto filters = (type == Ui::AlbumType::PhotoVideo)
? FileDialog::PhotoVideoFilesFilter()
: FileDialog::AllFilesFilter();
FileDialog::GetOpenPath(
controller->content().get(),
tr::lng_choose_file(tr::now),
filters,
crl::guard(controller, callback));
}
void EditPhotoImage(
not_null<Window::SessionController*> controller,
std::shared_ptr<Data::PhotoMedia> media,
bool wasSpoiler,
Fn<void(Ui::PreparedList)> done) {
const auto large = media
? media->image(Data::PhotoSize::Large)
: nullptr;
const auto parent = controller->content();
const auto previewWidth = st::sendMediaPreviewSize;
auto callback = [=](const Editor::PhotoModifications &mods) {
if (!mods) {
return;
}
const auto large = media->image(Data::PhotoSize::Large);
if (!large) {
return;
}
auto copy = large->original();
auto list = Storage::PrepareMediaFromImage(
std::move(copy),
QByteArray(),
previewWidth);
using ImageInfo = Ui::PreparedFileInformation::Image;
auto &file = list.files.front();
file.spoiler = wasSpoiler;
const auto image = std::get_if<ImageInfo>(&file.information->media);
image->modifications = mods;
const auto sideLimit = PhotoSideLimit();
Storage::UpdateImageDetails(file, previewWidth, sideLimit);
done(std::move(list));
};
const auto fileImage = std::make_shared<Image>(*large);
auto editor = base::make_unique_q<Editor::PhotoEditor>(
parent,
&controller->window(),
fileImage,
Editor::PhotoModifications());
const auto raw = editor.get();
auto layer = std::make_unique<Editor::LayerWidget>(
parent,
std::move(editor));
Editor::InitEditorLayer(layer.get(), raw, std::move(callback));
controller->showLayer(std::move(layer), Ui::LayerOption::KeepOther);
}
} // namespace
EditCaptionBox::EditCaptionBox(
QWidget*,
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item)
: EditCaptionBox({}, controller, item, PrepareEditText(item), {}, {}) {
}
EditCaptionBox::EditCaptionBox(
QWidget*,
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item,
TextWithTags &&text,
Ui::PreparedList &&list,
Fn<void()> saved)
: _controller(controller)
, _historyItem(item)
, _isAllowedEditMedia(item->media()
@@ -135,7 +251,10 @@ EditCaptionBox::EditCaptionBox(
tr::lng_photo_caption()))
, _emojiToggle(base::make_unique_q<Ui::EmojiButton>(
this,
st::boxAttachEmoji)) {
st::boxAttachEmoji))
, _initialText(std::move(text))
, _initialList(std::move(list))
, _saved(std::move(saved)) {
Expects(item->media() != nullptr);
Expects(item->media()->allowsEditCaption());
@@ -148,6 +267,57 @@ EditCaptionBox::EditCaptionBox(
EditCaptionBox::~EditCaptionBox() = default;
void EditCaptionBox::StartMediaReplace(
not_null<Window::SessionController*> controller,
FullMsgId itemId,
TextWithTags text,
Fn<void()> saved) {
const auto session = &controller->session();
const auto item = session->data().message(itemId);
if (!item) {
return;
}
const auto show = [=](Ui::PreparedList &&list) mutable {
controller->show(Box<EditCaptionBox>(
controller,
item,
std::move(text),
std::move(list),
std::move(saved)));
};
ChooseReplacement(
controller,
ComputeAlbumType(item),
crl::guard(controller, show));
}
void EditCaptionBox::StartPhotoEdit(
not_null<Window::SessionController*> controller,
std::shared_ptr<Data::PhotoMedia> media,
FullMsgId itemId,
TextWithTags text,
Fn<void()> saved) {
const auto session = &controller->session();
const auto item = session->data().message(itemId);
if (!item) {
return;
}
const auto hasSpoiler = item->media() && item->media()->hasSpoiler();
EditPhotoImage(controller, media, hasSpoiler, [=](
Ui::PreparedList &&list) mutable {
const auto item = session->data().message(itemId);
if (!item) {
return;
}
controller->show(Box<EditCaptionBox>(
controller,
item,
std::move(text),
std::move(list),
std::move(saved)));
});
}
void EditCaptionBox::prepare() {
addButton(tr::lng_settings_save(), [=] { save(); });
addButton(tr::lng_cancel(), [=] { closeBox(); });
@@ -158,7 +328,9 @@ void EditCaptionBox::prepare() {
setupEmojiPanel();
setInitialText();
rebuildPreview();
if (!setPreparedList(std::move(_initialList))) {
rebuildPreview();
}
setupEditEventHandler();
SetupShadowsToScrollContent(this, _scroll, _contentHeight.events());
@@ -290,16 +462,15 @@ void EditCaptionBox::setupField() {
}
void EditCaptionBox::setInitialText() {
const auto initial = PrepareEditText(_historyItem);
_field->setTextWithTags(
initial,
_initialText,
Ui::InputField::HistoryAction::Clear);
auto cursor = _field->textCursor();
cursor.movePosition(QTextCursor::End);
_field->setTextCursor(cursor);
_checkChangedTimer.setCallback([=] {
if (_field->getTextWithAppliedMarkdown() == initial) {
if (_field->getTextWithAppliedMarkdown() == _initialText) {
setCloseByOutsideClick(true);
}
});
@@ -353,132 +524,44 @@ void EditCaptionBox::setupControls() {
}
void EditCaptionBox::setupEditEventHandler() {
const auto toastParent = Ui::BoxShow(this).toastParent();
const auto callback = [=](FileDialog::OpenResult &&result) {
auto showError = [toastParent](tr::phrase<> t) {
Ui::Toast::Show(toastParent, t(tr::now));
};
const auto checkResult = [=](const Ui::PreparedList &list) {
if (list.files.size() != 1) {
return false;
}
const auto &file = list.files.front();
const auto mime = file.information->filemime;
if (Core::IsMimeSticker(mime)) {
showError(tr::lng_edit_media_invalid_file);
return false;
} else if (_albumType != Ui::AlbumType::None
&& !file.canBeInAlbumType(_albumType)) {
showError(tr::lng_edit_media_album_error);
return false;
}
return true;
};
const auto premium = _controller->session().premium();
auto list = Storage::PreparedFileFromFilesDialog(
std::move(result),
checkResult,
showError,
st::sendMediaPreviewSize,
premium);
if (list) {
setPreparedList(std::move(*list));
}
};
const auto buttonCallback = [=] {
const auto filters = (_albumType == Ui::AlbumType::PhotoVideo)
? FileDialog::PhotoVideoFilesFilter()
: FileDialog::AllFilesFilter();
FileDialog::GetOpenPath(
this,
tr::lng_choose_file(tr::now),
filters,
crl::guard(this, callback));
};
_editMediaClicks.events(
) | rpl::start_with_next(
buttonCallback,
lifetime());
) | rpl::start_with_next([=] {
ChooseReplacement(_controller, _albumType, crl::guard(this, [=](
Ui::PreparedList &&list) {
setPreparedList(std::move(list));
}));
}, lifetime());
}
void EditCaptionBox::setupPhotoEditorEventHandler() {
const auto openedOnce = lifetime().make_state<bool>(false);
_photoEditorOpens.events(
) | rpl::start_with_next([=, controller = _controller] {
const auto increment = [=] {
if (*openedOnce) {
return;
}
if (_preparedList.files.empty()
&& (!_photoMedia
|| !_photoMedia->image(Data::PhotoSize::Large))) {
return;
} else if (!*openedOnce) {
*openedOnce = true;
controller->session().settings().incrementPhotoEditorHintShown();
controller->session().saveSettings();
};
const auto clearError = [=] {
}
if (!_error.isEmpty()) {
_error = QString();
update();
};
const auto previewWidth = st::sendMediaPreviewSize;
}
if (!_preparedList.files.empty()) {
increment();
clearError();
Editor::OpenWithPreparedFile(
this,
controller,
&_preparedList.files.front(),
previewWidth,
st::sendMediaPreviewSize,
[=] { rebuildPreview(); });
} else if (_photoMedia) {
const auto large = _photoMedia->image(Data::PhotoSize::Large);
if (!large) {
return;
}
increment();
clearError();
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();
const auto wasSpoiler = hasSpoiler();
_preparedList = Storage::PrepareMediaFromImage(
std::move(copy),
QByteArray(),
previewWidth);
using ImageInfo = Ui::PreparedFileInformation::Image;
auto &file = _preparedList.files.front();
file.spoiler = wasSpoiler;
const auto image = std::get_if<ImageInfo>(
&file.information->media);
image->modifications = mods;
const auto sideLimit = PhotoSideLimit();
Storage::UpdateImageDetails(file, previewWidth, sideLimit);
rebuildPreview();
};
const auto fileImage = std::make_shared<Image>(*large);
auto editor = base::make_unique_q<Editor::PhotoEditor>(
this,
&controller->window(),
fileImage,
Editor::PhotoModifications());
const auto raw = editor.get();
auto layer = std::make_unique<Editor::LayerWidget>(
this,
std::move(editor));
Editor::InitEditorLayer(layer.get(), raw, std::move(callback));
controller->showLayer(
std::move(layer),
Ui::LayerOption::KeepOther);
} else {
EditPhotoImage(_controller, _photoMedia, hasSpoiler(), [=](
Ui::PreparedList &&list) {
setPreparedList(std::move(list));
});
}
}, lifetime());
}
@@ -780,13 +863,13 @@ void EditCaptionBox::save() {
: SendMediaType::File,
_field->getTextWithAppliedMarkdown(),
action);
closeBox();
closeAfterSave();
return;
}
const auto done = crl::guard(this, [=] {
_saveRequestId = 0;
closeBox();
closeAfterSave();
});
const auto fail = crl::guard(this, [=](const QString &error) {
@@ -795,7 +878,7 @@ void EditCaptionBox::save() {
_error = tr::lng_edit_error(tr::now);
update();
} else if (error == u"MESSAGE_NOT_MODIFIED"_q) {
closeBox();
closeAfterSave();
} else if (error == u"MESSAGE_EMPTY"_q) {
_field->setFocus();
_field->showError();
@@ -816,6 +899,16 @@ void EditCaptionBox::save() {
_saveRequestId = Api::EditCaption(item, sending, options, done, fail);
}
void EditCaptionBox::closeAfterSave() {
const auto weak = MakeWeak(this);
if (_saved) {
_saved();
}
if (weak) {
closeBox();
}
}
void EditCaptionBox::keyPressEvent(QKeyEvent *e) {
const auto ctrl = e->modifiers().testFlag(Qt::ControlModifier);
if ((e->key() == Qt::Key_E) && ctrl) {