From 8c0351be4ea099b77c5552adea7783e2c3727763 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 29 May 2024 23:09:51 +0400 Subject: [PATCH] Allow editing caption above/below media. --- .../SourceFiles/boxes/edit_caption_box.cpp | 23 +-- Telegram/SourceFiles/boxes/edit_caption_box.h | 4 - Telegram/SourceFiles/boxes/send_files_box.cpp | 7 +- .../SourceFiles/history/history_widget.cpp | 41 ++++-- Telegram/SourceFiles/history/history_widget.h | 1 + .../history_view_compose_controls.cpp | 31 ++-- ...istory_view_compose_media_edit_manager.cpp | 132 +++++++++++++----- .../history_view_compose_media_edit_manager.h | 35 ++++- .../view/media/history_view_media_grouped.cpp | 2 +- Telegram/SourceFiles/menu/menu_send.cpp | 15 +- 10 files changed, 200 insertions(+), 91 deletions(-) diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index be4d4e7fa..bb9914f09 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "main/main_session_settings.h" #include "mainwidget.h" // controller->content() -> QWidget* +#include "menu/menu_send.h" #include "mtproto/mtproto_config.h" #include "platform/platform_specific.h" #include "storage/localimageloader.h" // SendMediaType @@ -225,13 +226,6 @@ void EditPhotoImage( } // namespace -EditCaptionBox::EditCaptionBox( - QWidget*, - not_null controller, - not_null item) -: EditCaptionBox({}, controller, item, PrepareEditText(item), {}, {}) { -} - EditCaptionBox::EditCaptionBox( QWidget*, not_null controller, @@ -365,9 +359,21 @@ void EditCaptionBox::StartPhotoEdit( } void EditCaptionBox::prepare() { - addButton(tr::lng_settings_save(), [=] { save(); }); + const auto button = addButton(tr::lng_settings_save(), [=] { save(); }); addButton(tr::lng_cancel(), [=] { closeBox(); }); + const auto details = crl::guard(this, [=] { + return SendMenu::Details(); + }); + const auto callback = [=](SendMenu::Action action, const auto &) { + + }; + SendMenu::SetupMenuAndShortcuts( + button, + nullptr, + details, + crl::guard(this, callback)); + updateBoxSize(); setupField(); @@ -899,6 +905,7 @@ void EditCaptionBox::save() { auto options = Api::SendOptions(); options.scheduled = item->isScheduled() ? item->date() : 0; options.shortcutId = item->shortcutId(); + //options.invertCaption = _invertCaption; if (!_preparedList.files.empty()) { if ((_albumType != Ui::AlbumType::None) diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.h b/Telegram/SourceFiles/boxes/edit_caption_box.h index 110c0e588..b3a10f43f 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.h +++ b/Telegram/SourceFiles/boxes/edit_caption_box.h @@ -32,10 +32,6 @@ enum class AlbumType; class EditCaptionBox final : public Ui::BoxContent { public: - EditCaptionBox( - QWidget*, - not_null controller, - not_null item); EditCaptionBox( QWidget*, not_null controller, diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index c38004f7b..0656488ee 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -379,7 +379,7 @@ Fn SendFilesBox::prepareSendMenuDetails( const auto canMoveCaption = _list.canMoveCaption( way.groupFiles() && way.sendImagesAsPhotos(), way.sendImagesAsPhotos() - ) && _caption && !_caption->getLastText().isEmpty(); + ) && _caption && HasSendText(_caption); result.caption = !canMoveCaption ? SendMenu::CaptionState::None : _invertCaption @@ -638,14 +638,15 @@ void SendFilesBox::addMenuButton() { const auto &tabbed = _st.tabbed; const auto &icons = tabbed.icons; _menu = base::make_unique_q(top, tabbed.menu); + const auto position = QCursor::pos(); SendMenu::FillSendMenu( _menu.get(), _show, _sendMenuDetails(), _sendMenuCallback, &_st.tabbed.icons, - QCursor::pos()); - _menu->popup(QCursor::pos()); + position); + _menu->popup(position); return true; }); } diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index e68bade1a..bcbada572 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -324,7 +324,12 @@ HistoryWidget::HistoryWidget( { using namespace SendMenu; const auto sendAction = [=](Action action, Details) { - if (action.type == ActionType::Send) { + if (action.type == ActionType::CaptionUp + || action.type == ActionType::CaptionDown + || action.type == ActionType::SpoilerOn + || action.type == ActionType::SpoilerOff) { + _mediaEditSpoiler.apply(action); + } else if (action.type == ActionType::Send) { send(action.options); } else { sendScheduled(action.options); @@ -2672,7 +2677,7 @@ void HistoryWidget::setEditMsgId(MsgId msgId) { unregisterDraftSources(); _editMsgId = msgId; if (!msgId) { - _mediaEditSpoiler.setSpoilerOverride(std::nullopt); + _mediaEditSpoiler.cancel(); _canReplaceMedia = false; if (_preview) { _preview->setDisabled(false); @@ -4056,15 +4061,14 @@ void HistoryWidget::saveEditMsg() { })(); }; - auto options = Api::SendOptions(); _saveEditMsgRequestId = Api::EditTextMessage( item, sending, webPageDraft, - options, + { .invertCaption = _mediaEditSpoiler.invertCaption() }, done, fail, - _mediaEditSpoiler.spoilerOverride()); + _mediaEditSpoiler.spoilered()); } void HistoryWidget::hideChildWidgets() { @@ -4222,6 +4226,12 @@ SendMenu::Details HistoryWidget::sendMenuDetails() const { return { .type = type, .effectAllowed = effectAllowed }; } +SendMenu::Details HistoryWidget::saveMenuDetails() const { + return (_editMsgId && _replyEditMsg) + ? _mediaEditSpoiler.sendMenuDetails(HasSendText(_field)) + : SendMenu::Details(); +} + auto HistoryWidget::computeSendButtonType() const { using Type = Ui::SendButton::Type; @@ -4236,7 +4246,11 @@ auto HistoryWidget::computeSendButtonType() const { } SendMenu::Details HistoryWidget::sendButtonMenuDetails() const { - if (computeSendButtonType() != Ui::SendButton::Type::Send) { + using Type = Ui::SendButton::Type; + const auto type = computeSendButtonType(); + if (type == Type::Save) { + return saveMenuDetails(); + } else if (type != Type::Send) { return {}; } return sendMenuDetails(); @@ -6587,8 +6601,8 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { && (e->button() == Qt::RightButton)) { _mediaEditSpoiler.showMenu( _list, - session().data().message(_history->peer, _editMsgId), - [=](bool) { mouseMoveEvent(nullptr); }); + [=] { mouseMoveEvent(nullptr); }, + HasSendText(_field)); } else if (_inPhotoEdit && _photoEditMedia) { EditCaptionBox::StartPhotoEdit( controller(), @@ -8171,6 +8185,9 @@ void HistoryWidget::updateReplyEditTexts(bool force) { const auto editMedia = _editMsgId ? _replyEditMsg->media() : nullptr; + if (_editMsgId && _replyEditMsg) { + _mediaEditSpoiler.start(_replyEditMsg); + } _canReplaceMedia = editMedia && editMedia->allowsEditMedia(); _photoEditMedia = (_canReplaceMedia && editMedia->photo() @@ -8264,14 +8281,12 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { ? drawMsgText->media() : nullptr; const auto hasPreview = media && media->hasReplyPreview(); - const auto preview = _mediaEditSpoiler.spoilerOverride() - ? _mediaEditSpoiler.mediaPreview(drawMsgText) + const auto preview = _mediaEditSpoiler + ? _mediaEditSpoiler.mediaPreview() : hasPreview ? media->replyPreview() : nullptr; - const auto spoilered = _mediaEditSpoiler.spoilerOverride() - ? (*_mediaEditSpoiler.spoilerOverride()) - : (preview && media->hasSpoiler()); + const auto spoilered = _mediaEditSpoiler.spoilered(); if (!spoilered) { _replySpoiler = nullptr; } else if (!_replySpoiler) { diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index bc3641e67..f829cf797 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -269,6 +269,7 @@ public: void clearSelected(); [[nodiscard]] SendMenu::Details sendMenuDetails() const; + [[nodiscard]] SendMenu::Details saveMenuDetails() const; bool sendExistingDocument( not_null document, Api::SendOptions options, diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index c81c59b8c..6c985bbf0 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -124,7 +124,8 @@ class FieldHeader final : public Ui::RpWidget { public: FieldHeader( QWidget *parent, - std::shared_ptr show); + std::shared_ptr show, + Fn hasSendText); void setHistory(const SetHistoryArgs &args); void updateTopicRootId(MsgId topicRootId); @@ -187,6 +188,8 @@ private: }; const std::shared_ptr _show; + const Fn _hasSendText; + History *_history = nullptr; MsgId _topicRootId = 0; @@ -230,9 +233,11 @@ private: FieldHeader::FieldHeader( QWidget *parent, - std::shared_ptr show) + std::shared_ptr show, + Fn hasSendText) : RpWidget(parent) , _show(std::move(show)) +, _hasSendText(std::move(hasSendText)) , _forwardPanel( std::make_unique([=] { customEmojiRepaint(); })) , _data(&_show->session().data()) @@ -406,8 +411,8 @@ void FieldHeader::init() { if (inPreviewRect && isEditingMessage()) { _mediaEditSpoiler.showMenu( this, - _data->message(_editMsgId.current()), - [=](bool) { update(); }); + [=] { update(); }, + _hasSendText()); } else if (const auto reply = replyingToMessage()) { _jumpToItemRequests.fire_copy(reply); } @@ -582,14 +587,12 @@ void FieldHeader::paintEditOrReplyToMessage(Painter &p) { const auto media = _shownMessage->media(); _shownMessageHasPreview = media && media->hasReplyPreview(); - const auto preview = _mediaEditSpoiler.spoilerOverride() - ? _mediaEditSpoiler.mediaPreview(_shownMessage) + const auto preview = _mediaEditSpoiler + ? _mediaEditSpoiler.mediaPreview() : _shownMessageHasPreview ? media->replyPreview() : nullptr; - const auto spoilered = _mediaEditSpoiler.spoilerOverride() - ? (*_mediaEditSpoiler.spoilerOverride()) - : (preview && media->hasSpoiler()); + const auto spoilered = _mediaEditSpoiler.spoilered(); if (!spoilered) { _shownPreviewSpoiler = nullptr; } else if (!_shownPreviewSpoiler) { @@ -734,7 +737,7 @@ void FieldHeader::editMessage(FullMsgId id, bool photoEditAllowed) { _photoEditAllowed = photoEditAllowed; _editMsgId = id; if (!photoEditAllowed) { - _mediaEditSpoiler.setSpoilerOverride(std::nullopt); + _mediaEditSpoiler.cancel(); _inPhotoEdit = false; _inPhotoEditOver.stop(); } @@ -781,8 +784,9 @@ MessageToEdit FieldHeader::queryToEdit() { .options = { .scheduled = item->isScheduled() ? item->date() : 0, .shortcutId = item->shortcutId(), + .invertCaption = _mediaEditSpoiler.invertCaption(), }, - .spoilerMediaOverride = _mediaEditSpoiler.spoilerOverride(), + .spoilerMediaOverride = _mediaEditSpoiler.spoilered(), }; } @@ -842,7 +846,10 @@ ComposeControls::ComposeControls( parent, _show, &_st.tabbed)) -, _header(std::make_unique(_wrap.get(), _show)) +, _header(std::make_unique( + _wrap.get(), + _show, + [=] { return HasSendText(_field); })) , _voiceRecordBar(std::make_unique( _wrap.get(), Controls::VoiceRecordBarDescriptor{ diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.cpp index d6c58bb6a..f20681a0f 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.cpp @@ -10,74 +10,134 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document.h" #include "data/data_file_origin.h" #include "data/data_photo.h" +#include "data/data_session.h" #include "history/history.h" #include "history/history_item.h" #include "lang/lang_keys.h" +#include "menu/menu_send.h" #include "ui/widgets/popup_menu.h" +#include "styles/style_chat_helpers.h" #include "styles/style_menu_icons.h" namespace HistoryView { MediaEditSpoilerManager::MediaEditSpoilerManager() = default; +void MediaEditSpoilerManager::start(not_null item) { + const auto media = item->media(); + if (!media) { + return; + } + _item = item; + _spoilered = media->hasSpoiler(); + _invertCaption = item->invertMedia(); + _lifetime = item->history()->owner().itemRemoved( + ) | rpl::start_with_next([=](not_null removed) { + if (removed == _item) { + cancel(); + } + }); +} + +void MediaEditSpoilerManager::apply(SendMenu::Action action) { + using Type = SendMenu::Action::Type; + if (action.type == Type::CaptionUp) { + _invertCaption = true; + } else if (action.type == Type::CaptionDown) { + _invertCaption = false; + } else if (action.type == Type::SpoilerOn) { + _spoilered = true; + } else if (action.type == Type::SpoilerOff) { + _spoilered = false; + } +} + +void MediaEditSpoilerManager::cancel() { + _menu = nullptr; + _item = nullptr; + _lifetime.destroy(); +} + void MediaEditSpoilerManager::showMenu( not_null parent, - not_null item, - Fn callback) { - const auto media = item->media(); + Fn finished, + bool hasCaptionText) { + if (!_item) { + return; + } + const auto media = _item->media(); const auto hasPreview = media && media->hasReplyPreview(); const auto preview = hasPreview ? media->replyPreview() : nullptr; if (!preview || (media && media->webpage())) { return; } - const auto spoilered = _spoilerOverride - ? (*_spoilerOverride) - : (preview && media->hasSpoiler()); - const auto menu = Ui::CreateChild( + _menu = base::make_unique_q( parent, st::popupMenuWithIcons); - menu->addAction( - spoilered - ? tr::lng_context_disable_spoiler(tr::now) - : tr::lng_context_spoiler_effect(tr::now), - [=] { - _spoilerOverride = (!spoilered); - if (callback) { - callback(!spoilered); - } - }, - spoilered ? &st::menuIconSpoilerOff : &st::menuIconSpoiler); - menu->popup(QCursor::pos()); + const auto callback = [=](SendMenu::Action action, const auto &) { + apply(action); + }; + const auto position = QCursor::pos(); + SendMenu::FillSendMenu( + _menu.get(), + nullptr, + sendMenuDetails(hasCaptionText), + callback, + &st::defaultComposeIcons, + position); + _menu->popup(position); } -[[nodiscard]] Image *MediaEditSpoilerManager::mediaPreview( - not_null item) { - if (!_spoilerOverride) { - return nullptr; - } - if (const auto media = item->media()) { +[[nodiscard]] Image *MediaEditSpoilerManager::mediaPreview() { + if (const auto media = _item ? _item->media() : nullptr) { if (const auto photo = media->photo()) { return photo->getReplyPreview( - item->fullId(), - item->history()->peer, - *_spoilerOverride); + _item->fullId(), + _item->history()->peer, + _spoilered); } else if (const auto document = media->document()) { return document->getReplyPreview( - item->fullId(), - item->history()->peer, - *_spoilerOverride); + _item->fullId(), + _item->history()->peer, + _spoilered); } } return nullptr; } -void MediaEditSpoilerManager::setSpoilerOverride( - std::optional spoilerOverride) { - _spoilerOverride = spoilerOverride; +bool MediaEditSpoilerManager::spoilered() const { + return _spoilered; } -std::optional MediaEditSpoilerManager::spoilerOverride() const { - return _spoilerOverride; +bool MediaEditSpoilerManager::invertCaption() const { + return _invertCaption; +} + +SendMenu::Details MediaEditSpoilerManager::sendMenuDetails( + bool hasCaptionText) const { + const auto media = _item ? _item->media() : nullptr; + if (!media) { + return {}; + } + const auto editingMedia = media->allowsEditMedia(); + const auto editPhoto = editingMedia ? media->photo() : nullptr; + const auto editDocument = editingMedia ? media->document() : nullptr; + const auto canSaveSpoiler = (editPhoto && !editPhoto->isNull()) + || (editDocument + && (editDocument->isVideoFile() || editDocument->isGifv())); + const auto canMoveCaption = media->allowsEditCaption() && hasCaptionText; + return { + .spoiler = (!canSaveSpoiler + ? SendMenu::SpoilerState::None + : _spoilered + ? SendMenu::SpoilerState::Enabled + : SendMenu::SpoilerState::Possible), + .caption = (!canMoveCaption + ? SendMenu::CaptionState::None + : _invertCaption + ? SendMenu::CaptionState::Above + : SendMenu::CaptionState::Below), + }; } } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.h index 8bc34b7c4..0f9f49e2b 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.h @@ -7,8 +7,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/unique_qptr.h" + +namespace SendMenu { +struct Details; +struct Action; +} // namespace SendMenu + namespace Ui { class RpWidget; +class PopupMenu; } // namespace Ui class Image; @@ -20,19 +28,34 @@ class MediaEditSpoilerManager final { public: MediaEditSpoilerManager(); + void start(not_null item); + void apply(SendMenu::Action action); + void cancel(); + void showMenu( not_null parent, - not_null item, - Fn callback); + Fn finished, + bool hasCaptionText); - [[nodiscard]] Image *mediaPreview(not_null item); + [[nodiscard]] Image *mediaPreview(); - void setSpoilerOverride(std::optional spoilerOverride); + [[nodiscard]] bool spoilered() const; + [[nodiscard]] bool invertCaption() const; - std::optional spoilerOverride() const; + [[nodiscard]] SendMenu::Details sendMenuDetails( + bool hasCaptionText) const; + + [[nodiscard]] explicit operator bool() const { + return _item != nullptr; + } private: - std::optional _spoilerOverride; + base::unique_qptr _menu; + HistoryItem *_item = nullptr; + bool _spoilered = false; + bool _invertCaption = false; + + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index 7d11579e9..c4e51f174 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -396,7 +396,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const { } // date - if (_parent->media() == this) { + if (_parent->media() == this && isBubbleBottom()) { auto fullRight = width(); auto fullBottom = height(); if (needInfoDisplay()) { diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index 4bdd5709b..a1c8909fb 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -615,7 +615,8 @@ FillMenuResult FillSendMenu( const style::ComposeIcons *iconsOverride, std::optional desiredPositionOverride) { const auto type = details.type; - const auto empty = (type == Type::Disabled) + const auto sending = (type != Type::Disabled); + const auto empty = !sending && (details.spoiler == SpoilerState::None) && (details.caption == CaptionState::None); if (empty || !action) { @@ -653,18 +654,16 @@ FillMenuResult FillSendMenu( toggles = true; } if (toggles && type != Type::Disabled) { - menu->addSeparator(); + menu->addSeparator(&st::expandedMenuSeparator); } - if (type != Type::Reminder) { + if (sending && type != Type::Reminder) { menu->addAction( tr::lng_send_silent_message(tr::now), - [=] { action( - { Api::SendOptions{ .silent = true } }, - details); }, + [=] { action({ Api::SendOptions{ .silent = true } }, details); }, &icons.menuMute); } - if (type != Type::SilentOnly) { + if (sending && type != Type::SilentOnly) { menu->addAction( (type == Type::Reminder ? tr::lng_reminder_message(tr::now) @@ -672,7 +671,7 @@ FillMenuResult FillSendMenu( [=] { action({ .type = ActionType::Schedule }, details); }, &icons.menuSchedule); } - if (type == Type::ScheduledToUser) { + if (sending && type == Type::ScheduledToUser) { menu->addAction( tr::lng_scheduled_send_until_online(tr::now), [=] { action(