Allow editing caption above/below media.

This commit is contained in:
John Preston 2024-05-29 23:09:51 +04:00
parent 67f7816088
commit 8c0351be4e
10 changed files with 200 additions and 91 deletions

View file

@ -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<Window::SessionController*> controller,
not_null<HistoryItem*> item)
: EditCaptionBox({}, controller, item, PrepareEditText(item), {}, {}) {
}
EditCaptionBox::EditCaptionBox(
QWidget*,
not_null<Window::SessionController*> 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)

View file

@ -32,10 +32,6 @@ enum class AlbumType;
class EditCaptionBox final : public Ui::BoxContent {
public:
EditCaptionBox(
QWidget*,
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item);
EditCaptionBox(
QWidget*,
not_null<Window::SessionController*> controller,

View file

@ -379,7 +379,7 @@ Fn<SendMenu::Details()> 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<Ui::PopupMenu>(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;
});
}

View file

@ -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) {

View file

@ -269,6 +269,7 @@ public:
void clearSelected();
[[nodiscard]] SendMenu::Details sendMenuDetails() const;
[[nodiscard]] SendMenu::Details saveMenuDetails() const;
bool sendExistingDocument(
not_null<DocumentData*> document,
Api::SendOptions options,

View file

@ -124,7 +124,8 @@ class FieldHeader final : public Ui::RpWidget {
public:
FieldHeader(
QWidget *parent,
std::shared_ptr<ChatHelpers::Show> show);
std::shared_ptr<ChatHelpers::Show> show,
Fn<bool()> hasSendText);
void setHistory(const SetHistoryArgs &args);
void updateTopicRootId(MsgId topicRootId);
@ -187,6 +188,8 @@ private:
};
const std::shared_ptr<ChatHelpers::Show> _show;
const Fn<bool()> _hasSendText;
History *_history = nullptr;
MsgId _topicRootId = 0;
@ -230,9 +233,11 @@ private:
FieldHeader::FieldHeader(
QWidget *parent,
std::shared_ptr<ChatHelpers::Show> show)
std::shared_ptr<ChatHelpers::Show> show,
Fn<bool()> hasSendText)
: RpWidget(parent)
, _show(std::move(show))
, _hasSendText(std::move(hasSendText))
, _forwardPanel(
std::make_unique<ForwardPanel>([=] { 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<FieldHeader>(_wrap.get(), _show))
, _header(std::make_unique<FieldHeader>(
_wrap.get(),
_show,
[=] { return HasSendText(_field); }))
, _voiceRecordBar(std::make_unique<VoiceRecordBar>(
_wrap.get(),
Controls::VoiceRecordBarDescriptor{

View file

@ -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<HistoryItem*> 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<const HistoryItem*> 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<Ui::RpWidget*> parent,
not_null<HistoryItem*> item,
Fn<void(bool)> callback) {
const auto media = item->media();
Fn<void()> 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<Ui::PopupMenu>(
_menu = base::make_unique_q<Ui::PopupMenu>(
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<HistoryItem*> 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<bool> spoilerOverride) {
_spoilerOverride = spoilerOverride;
bool MediaEditSpoilerManager::spoilered() const {
return _spoilered;
}
std::optional<bool> 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

View file

@ -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<HistoryItem*> item);
void apply(SendMenu::Action action);
void cancel();
void showMenu(
not_null<Ui::RpWidget*> parent,
not_null<HistoryItem*> item,
Fn<void(bool)> callback);
Fn<void()> finished,
bool hasCaptionText);
[[nodiscard]] Image *mediaPreview(not_null<HistoryItem*> item);
[[nodiscard]] Image *mediaPreview();
void setSpoilerOverride(std::optional<bool> spoilerOverride);
[[nodiscard]] bool spoilered() const;
[[nodiscard]] bool invertCaption() const;
std::optional<bool> spoilerOverride() const;
[[nodiscard]] SendMenu::Details sendMenuDetails(
bool hasCaptionText) const;
[[nodiscard]] explicit operator bool() const {
return _item != nullptr;
}
private:
std::optional<bool> _spoilerOverride;
base::unique_qptr<Ui::PopupMenu> _menu;
HistoryItem *_item = nullptr;
bool _spoilered = false;
bool _invertCaption = false;
rpl::lifetime _lifetime;
};

View file

@ -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()) {

View file

@ -615,7 +615,8 @@ FillMenuResult FillSendMenu(
const style::ComposeIcons *iconsOverride,
std::optional<QPoint> 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(