2018-01-25 10:10:52 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
|
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
|
|
|
|
For license and copyright information please follow this link:
|
|
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
|
|
*/
|
|
|
|
#include "history/view/history_view_context_menu.h"
|
|
|
|
|
2020-10-30 10:16:03 +00:00
|
|
|
#include "api/api_attached_stickers.h"
|
2020-05-28 11:43:11 +00:00
|
|
|
#include "api/api_editing.h"
|
2020-08-17 12:29:27 +00:00
|
|
|
#include "api/api_toggling_media.h" // Api::ToggleFavedSticker
|
2020-05-30 08:47:32 +00:00
|
|
|
#include "base/unixtime.h"
|
2018-01-25 10:10:52 +00:00
|
|
|
#include "history/view/history_view_list_widget.h"
|
2018-01-28 15:08:34 +00:00
|
|
|
#include "history/view/history_view_cursor_state.h"
|
|
|
|
#include "history/history.h"
|
2018-01-25 10:10:52 +00:00
|
|
|
#include "history/history_item.h"
|
2018-01-28 15:08:34 +00:00
|
|
|
#include "history/history_message.h"
|
2018-01-26 15:40:11 +00:00
|
|
|
#include "history/history_item_text.h"
|
2020-05-11 00:11:32 +00:00
|
|
|
#include "history/view/history_view_schedule_box.h"
|
2019-08-02 13:21:09 +00:00
|
|
|
#include "history/view/media/history_view_media.h"
|
|
|
|
#include "history/view/media/history_view_web_page.h"
|
2018-01-25 10:10:52 +00:00
|
|
|
#include "ui/widgets/popup_menu.h"
|
2018-10-23 09:44:42 +00:00
|
|
|
#include "ui/image/image.h"
|
2019-04-04 10:02:22 +00:00
|
|
|
#include "ui/toast/toast.h"
|
2019-09-13 12:22:54 +00:00
|
|
|
#include "ui/ui_utility.h"
|
2020-08-09 11:28:39 +00:00
|
|
|
#include "chat_helpers/send_context_menu.h"
|
2018-01-28 15:08:34 +00:00
|
|
|
#include "boxes/confirm_box.h"
|
2018-11-26 11:55:02 +00:00
|
|
|
#include "boxes/sticker_set_box.h"
|
2020-10-01 14:06:04 +00:00
|
|
|
#include "boxes/report_box.h"
|
2018-01-25 10:10:52 +00:00
|
|
|
#include "data/data_photo.h"
|
2020-05-25 14:16:04 +00:00
|
|
|
#include "data/data_photo_media.h"
|
2018-01-25 10:10:52 +00:00
|
|
|
#include "data/data_document.h"
|
|
|
|
#include "data/data_media_types.h"
|
2018-01-28 15:08:34 +00:00
|
|
|
#include "data/data_session.h"
|
|
|
|
#include "data/data_groups.h"
|
2019-01-04 11:09:48 +00:00
|
|
|
#include "data/data_channel.h"
|
2019-09-16 11:14:06 +00:00
|
|
|
#include "data/data_file_origin.h"
|
2020-05-18 18:35:06 +00:00
|
|
|
#include "data/data_scheduled_messages.h"
|
2018-01-25 10:10:52 +00:00
|
|
|
#include "core/file_utilities.h"
|
2019-09-17 16:13:12 +00:00
|
|
|
#include "base/platform/base_platform_info.h"
|
2018-01-25 10:10:52 +00:00
|
|
|
#include "window/window_peer_menu.h"
|
2019-07-25 18:55:11 +00:00
|
|
|
#include "window/window_session_controller.h"
|
2018-01-25 10:10:52 +00:00
|
|
|
#include "lang/lang_keys.h"
|
2019-01-21 13:42:21 +00:00
|
|
|
#include "core/application.h"
|
2018-01-25 10:10:52 +00:00
|
|
|
#include "mainwidget.h"
|
2019-07-24 11:45:24 +00:00
|
|
|
#include "main/main_session.h"
|
2020-06-18 12:47:09 +00:00
|
|
|
#include "main/main_session_settings.h"
|
2018-07-16 19:41:43 +00:00
|
|
|
#include "apiwrap.h"
|
2019-09-13 06:06:02 +00:00
|
|
|
#include "facades.h"
|
2018-01-25 10:10:52 +00:00
|
|
|
|
2019-09-04 07:19:15 +00:00
|
|
|
#include <QtGui/QGuiApplication>
|
|
|
|
#include <QtGui/QClipboard>
|
|
|
|
|
2018-01-25 10:10:52 +00:00
|
|
|
namespace HistoryView {
|
|
|
|
namespace {
|
|
|
|
|
2019-04-05 11:50:16 +00:00
|
|
|
// If we can't cloud-export link for such time we export it locally.
|
|
|
|
constexpr auto kExportLocalTimeout = crl::time(1000);
|
2021-02-04 17:41:03 +00:00
|
|
|
constexpr auto kRescheduleLimit = 20;
|
2019-04-05 11:50:16 +00:00
|
|
|
|
2019-04-15 11:54:03 +00:00
|
|
|
//void AddToggleGroupingAction( // #feed
|
|
|
|
// not_null<Ui::PopupMenu*> menu,
|
|
|
|
// not_null<PeerData*> peer) {
|
|
|
|
// if (const auto channel = peer->asChannel()) {
|
|
|
|
// const auto grouped = (channel->feed() != nullptr);
|
|
|
|
// menu->addAction( // #feed
|
2019-06-19 15:09:03 +00:00
|
|
|
// grouped ? tr::lng_feed_ungroup(tr::now) : tr::lng_feed_group(tr::now),
|
2019-04-15 11:54:03 +00:00
|
|
|
// [=] { Window::ToggleChannelGrouping(channel, !grouped); });
|
|
|
|
// }
|
|
|
|
//}
|
2018-01-25 10:10:52 +00:00
|
|
|
|
2020-05-18 18:35:06 +00:00
|
|
|
MsgId ItemIdAcrossData(not_null<HistoryItem*> item) {
|
2020-10-22 13:08:01 +00:00
|
|
|
if (!item->isScheduled() || item->isSending() || item->hasFailed()) {
|
2020-05-18 18:35:06 +00:00
|
|
|
return item->id;
|
|
|
|
}
|
|
|
|
const auto session = &item->history()->session();
|
|
|
|
return session->data().scheduledMessages().lookupId(item);
|
|
|
|
}
|
|
|
|
|
2020-10-22 07:53:56 +00:00
|
|
|
bool HasEditMessageAction(
|
|
|
|
const ContextMenuRequest &request,
|
|
|
|
not_null<ListWidget*> list) {
|
2020-05-30 08:47:32 +00:00
|
|
|
const auto item = request.item;
|
2020-10-22 07:53:56 +00:00
|
|
|
const auto context = list->elementContext();
|
2020-05-30 08:47:32 +00:00
|
|
|
if (!item
|
|
|
|
|| item->isSending()
|
2020-09-25 12:31:53 +00:00
|
|
|
|| item->hasFailed()
|
2020-05-30 08:47:32 +00:00
|
|
|
|| item->isEditingMedia()
|
2020-10-22 07:53:56 +00:00
|
|
|
|| !request.selectedItems.empty()
|
|
|
|
|| (context != Context::History && context != Context::Replies)) {
|
2020-05-30 08:47:32 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const auto peer = item->history()->peer;
|
|
|
|
if (const auto channel = peer->asChannel()) {
|
2020-06-04 07:36:58 +00:00
|
|
|
if (!channel->isMegagroup() && !channel->canEditMessages()) {
|
2020-05-30 08:47:32 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-01-25 10:10:52 +00:00
|
|
|
void SavePhotoToFile(not_null<PhotoData*> photo) {
|
2020-05-25 14:16:04 +00:00
|
|
|
const auto media = photo->activeMediaView();
|
|
|
|
if (photo->isNull() || !media || !media->loaded()) {
|
2018-01-25 10:10:52 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-05-25 14:16:04 +00:00
|
|
|
const auto image = media->image(Data::PhotoSize::Large)->original();
|
2018-01-25 10:10:52 +00:00
|
|
|
FileDialog::GetWritePath(
|
2019-01-21 13:42:21 +00:00
|
|
|
Core::App().getFileDialogParent(),
|
2019-06-19 15:09:03 +00:00
|
|
|
tr::lng_save_photo(tr::now),
|
2018-01-25 10:10:52 +00:00
|
|
|
qsl("JPEG Image (*.jpg);;") + FileDialog::AllFilesFilter(),
|
|
|
|
filedialogDefaultName(qsl("photo"), qsl(".jpg")),
|
2019-07-24 11:13:51 +00:00
|
|
|
crl::guard(&photo->session(), [=](const QString &result) {
|
2018-01-25 10:10:52 +00:00
|
|
|
if (!result.isEmpty()) {
|
2020-05-25 14:16:04 +00:00
|
|
|
image.save(result, "JPG");
|
2018-01-25 10:10:52 +00:00
|
|
|
}
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
void CopyImage(not_null<PhotoData*> photo) {
|
2020-05-25 14:16:04 +00:00
|
|
|
const auto media = photo->activeMediaView();
|
|
|
|
if (photo->isNull() || !media || !media->loaded()) {
|
2018-01-25 10:10:52 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-05-25 14:16:04 +00:00
|
|
|
const auto image = media->image(Data::PhotoSize::Large)->original();
|
2020-12-11 08:00:52 +00:00
|
|
|
QGuiApplication::clipboard()->setImage(image);
|
2018-01-25 10:10:52 +00:00
|
|
|
}
|
|
|
|
|
2020-08-31 12:39:20 +00:00
|
|
|
void ShowStickerPackInfo(
|
|
|
|
not_null<DocumentData*> document,
|
|
|
|
not_null<ListWidget*> list) {
|
|
|
|
StickerSetBox::Show(list->controller(), document);
|
2018-01-28 15:08:34 +00:00
|
|
|
}
|
|
|
|
|
2018-07-16 19:41:43 +00:00
|
|
|
void ToggleFavedSticker(
|
|
|
|
not_null<DocumentData*> document,
|
|
|
|
FullMsgId contextId) {
|
2020-08-17 12:29:27 +00:00
|
|
|
Api::ToggleFavedSticker(document, contextId);
|
2018-01-28 15:08:34 +00:00
|
|
|
}
|
|
|
|
|
2018-01-25 10:10:52 +00:00
|
|
|
void AddPhotoActions(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
2020-10-30 10:16:03 +00:00
|
|
|
not_null<PhotoData*> photo,
|
|
|
|
not_null<ListWidget*> list) {
|
2018-01-25 10:10:52 +00:00
|
|
|
menu->addAction(
|
2019-06-19 15:09:03 +00:00
|
|
|
tr::lng_context_save_image(tr::now),
|
2018-01-25 10:10:52 +00:00
|
|
|
App::LambdaDelayed(
|
|
|
|
st::defaultDropdownMenu.menu.ripple.hideDuration,
|
2019-07-24 11:13:51 +00:00
|
|
|
&photo->session(),
|
2018-01-25 10:10:52 +00:00
|
|
|
[=] { SavePhotoToFile(photo); }));
|
2019-06-19 15:09:03 +00:00
|
|
|
menu->addAction(tr::lng_context_copy_image(tr::now), [=] {
|
2018-01-25 10:10:52 +00:00
|
|
|
CopyImage(photo);
|
|
|
|
});
|
2020-10-30 10:16:03 +00:00
|
|
|
if (photo->hasAttachedStickers()) {
|
|
|
|
const auto controller = list->controller();
|
|
|
|
auto callback = [=] {
|
|
|
|
auto &attached = photo->session().api().attachedStickers();
|
|
|
|
attached.requestAttachedStickerSets(controller, photo);
|
|
|
|
};
|
|
|
|
menu->addAction(
|
|
|
|
tr::lng_context_attached_stickers(tr::now),
|
|
|
|
std::move(callback));
|
|
|
|
}
|
2018-01-25 10:10:52 +00:00
|
|
|
}
|
|
|
|
|
2020-06-09 09:36:40 +00:00
|
|
|
void OpenGif(not_null<Main::Session*> session, FullMsgId itemId) {
|
|
|
|
if (const auto item = session->data().message(itemId)) {
|
2018-01-25 10:10:52 +00:00
|
|
|
if (const auto media = item->media()) {
|
|
|
|
if (const auto document = media->document()) {
|
2019-01-21 13:42:21 +00:00
|
|
|
Core::App().showDocument(document, item);
|
2018-01-25 10:10:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ShowInFolder(not_null<DocumentData*> document) {
|
2020-04-09 14:02:09 +00:00
|
|
|
const auto filepath = document->filepath(true);
|
2018-01-25 10:10:52 +00:00
|
|
|
if (!filepath.isEmpty()) {
|
|
|
|
File::ShowInFolder(filepath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddSaveDocumentAction(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
2018-07-13 21:25:47 +00:00
|
|
|
Data::FileOrigin origin,
|
2018-01-25 10:10:52 +00:00
|
|
|
not_null<DocumentData*> document) {
|
2019-02-27 11:36:19 +00:00
|
|
|
const auto save = [=] {
|
2019-03-11 14:35:11 +00:00
|
|
|
DocumentSaveClickHandler::Save(
|
|
|
|
origin,
|
|
|
|
document,
|
|
|
|
DocumentSaveClickHandler::Mode::ToNewFile);
|
2019-02-27 11:36:19 +00:00
|
|
|
};
|
2019-06-19 15:09:03 +00:00
|
|
|
|
2018-01-25 10:10:52 +00:00
|
|
|
menu->addAction(
|
2019-06-19 15:09:03 +00:00
|
|
|
(document->isVideoFile()
|
|
|
|
? tr::lng_context_save_video(tr::now)
|
2018-01-25 10:10:52 +00:00
|
|
|
: (document->isVoiceMessage()
|
2019-06-19 15:09:03 +00:00
|
|
|
? tr::lng_context_save_audio(tr::now)
|
2018-01-25 10:10:52 +00:00
|
|
|
: (document->isAudioFile()
|
2019-06-19 15:09:03 +00:00
|
|
|
? tr::lng_context_save_audio_file(tr::now)
|
2018-01-25 10:10:52 +00:00
|
|
|
: (document->sticker()
|
2019-06-19 15:09:03 +00:00
|
|
|
? tr::lng_context_save_image(tr::now)
|
|
|
|
: tr::lng_context_save_file(tr::now))))),
|
2018-01-25 10:10:52 +00:00
|
|
|
App::LambdaDelayed(
|
|
|
|
st::defaultDropdownMenu.menu.ripple.hideDuration,
|
2019-07-24 11:13:51 +00:00
|
|
|
&document->session(),
|
2019-02-27 11:36:19 +00:00
|
|
|
save));
|
2018-01-25 10:10:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void AddDocumentActions(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
not_null<DocumentData*> document,
|
2020-08-31 12:39:20 +00:00
|
|
|
FullMsgId contextId,
|
|
|
|
not_null<ListWidget*> list) {
|
2018-01-25 10:10:52 +00:00
|
|
|
if (document->loading()) {
|
2019-06-19 15:09:03 +00:00
|
|
|
menu->addAction(tr::lng_context_cancel_download(tr::now), [=] {
|
2018-01-25 10:10:52 +00:00
|
|
|
document->cancel();
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
2020-06-09 09:36:40 +00:00
|
|
|
const auto session = &document->session();
|
|
|
|
if (const auto item = session->data().message(contextId)) {
|
2019-12-24 10:09:04 +00:00
|
|
|
const auto notAutoplayedGif = [&] {
|
|
|
|
return document->isGifv()
|
2019-12-30 10:38:02 +00:00
|
|
|
&& !Data::AutoDownload::ShouldAutoPlay(
|
2019-12-24 10:09:04 +00:00
|
|
|
document->session().settings().autoDownload(),
|
|
|
|
item->history()->peer,
|
|
|
|
document);
|
|
|
|
}();
|
|
|
|
if (notAutoplayedGif) {
|
|
|
|
menu->addAction(tr::lng_context_open_gif(tr::now), [=] {
|
2020-06-09 09:36:40 +00:00
|
|
|
OpenGif(session, contextId);
|
2019-12-24 10:09:04 +00:00
|
|
|
});
|
|
|
|
}
|
2018-01-25 10:10:52 +00:00
|
|
|
}
|
2018-01-28 15:08:34 +00:00
|
|
|
if (document->sticker()
|
|
|
|
&& document->sticker()->set.type() != mtpc_inputStickerSetEmpty) {
|
|
|
|
menu->addAction(
|
2019-06-19 15:09:03 +00:00
|
|
|
(document->isStickerSetInstalled()
|
|
|
|
? tr::lng_context_pack_info(tr::now)
|
|
|
|
: tr::lng_context_pack_add(tr::now)),
|
2020-08-31 12:39:20 +00:00
|
|
|
[=] { ShowStickerPackInfo(document, list); });
|
2018-01-28 15:08:34 +00:00
|
|
|
menu->addAction(
|
2020-06-08 17:24:36 +00:00
|
|
|
(document->owner().stickers().isFaved(document)
|
2019-06-19 15:09:03 +00:00
|
|
|
? tr::lng_faved_stickers_remove(tr::now)
|
|
|
|
: tr::lng_faved_stickers_add(tr::now)),
|
2018-07-16 19:41:43 +00:00
|
|
|
[=] { ToggleFavedSticker(document, contextId); });
|
2018-01-28 15:08:34 +00:00
|
|
|
}
|
2020-04-09 14:02:09 +00:00
|
|
|
if (!document->filepath(true).isEmpty()) {
|
2018-01-25 10:10:52 +00:00
|
|
|
menu->addAction(
|
2019-06-19 15:09:03 +00:00
|
|
|
(Platform::IsMac()
|
|
|
|
? tr::lng_context_show_in_finder(tr::now)
|
|
|
|
: tr::lng_context_show_in_folder(tr::now)),
|
2018-01-25 10:10:52 +00:00
|
|
|
[=] { ShowInFolder(document); });
|
|
|
|
}
|
2020-10-30 10:16:03 +00:00
|
|
|
if (document->hasAttachedStickers()) {
|
|
|
|
const auto controller = list->controller();
|
|
|
|
auto callback = [=] {
|
|
|
|
auto &attached = session->api().attachedStickers();
|
|
|
|
attached.requestAttachedStickerSets(controller, document);
|
|
|
|
};
|
|
|
|
menu->addAction(
|
|
|
|
tr::lng_context_attached_stickers(tr::now),
|
|
|
|
std::move(callback));
|
|
|
|
}
|
2018-07-13 21:25:47 +00:00
|
|
|
AddSaveDocumentAction(menu, contextId, document);
|
2018-01-25 10:10:52 +00:00
|
|
|
}
|
|
|
|
|
2018-01-28 15:08:34 +00:00
|
|
|
void AddPostLinkAction(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
const ContextMenuRequest &request) {
|
|
|
|
const auto item = request.item;
|
|
|
|
if (!item
|
|
|
|
|| !item->hasDirectLink()
|
|
|
|
|| request.pointState == PointState::Outside) {
|
|
|
|
return;
|
|
|
|
} else if (request.link
|
|
|
|
&& !request.link->copyToClipboardContextItemText().isEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
2020-06-09 09:36:40 +00:00
|
|
|
const auto session = &item->history()->session();
|
2018-01-28 15:08:34 +00:00
|
|
|
const auto itemId = item->fullId();
|
2020-09-22 15:28:49 +00:00
|
|
|
const auto context = request.view
|
|
|
|
? request.view->context()
|
|
|
|
: Context::History;
|
2018-01-28 15:08:34 +00:00
|
|
|
menu->addAction(
|
2019-06-19 15:09:03 +00:00
|
|
|
(item->history()->peer->isMegagroup()
|
2020-12-22 15:11:52 +00:00
|
|
|
? tr::lng_context_copy_message_link
|
2019-06-19 15:09:03 +00:00
|
|
|
: tr::lng_context_copy_post_link)(tr::now),
|
2020-09-22 15:28:49 +00:00
|
|
|
[=] { CopyPostLink(session, itemId, context); });
|
2018-01-28 15:08:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
MessageIdsList ExtractIdsList(const SelectedItems &items) {
|
|
|
|
return ranges::view::all(
|
|
|
|
items
|
2020-05-18 19:33:14 +00:00
|
|
|
) | ranges::view::transform(
|
|
|
|
&SelectedItem::msgId
|
|
|
|
) | ranges::to_vector;
|
2018-01-28 15:08:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool AddForwardSelectedAction(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
const ContextMenuRequest &request,
|
|
|
|
not_null<ListWidget*> list) {
|
|
|
|
if (!request.overSelection || request.selectedItems.empty()) {
|
|
|
|
return false;
|
|
|
|
}
|
2020-05-18 19:33:14 +00:00
|
|
|
if (!ranges::all_of(request.selectedItems, &SelectedItem::canForward)) {
|
2018-01-28 15:08:34 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-06-19 15:09:03 +00:00
|
|
|
menu->addAction(tr::lng_context_forward_selected(tr::now), [=] {
|
2019-09-13 12:22:54 +00:00
|
|
|
const auto weak = Ui::MakeWeak(list);
|
2019-07-25 18:55:11 +00:00
|
|
|
const auto callback = [=] {
|
2018-01-28 15:08:34 +00:00
|
|
|
if (const auto strong = weak.data()) {
|
|
|
|
strong->cancelSelection();
|
|
|
|
}
|
2019-07-25 18:55:11 +00:00
|
|
|
};
|
|
|
|
Window::ShowForwardMessagesBox(
|
|
|
|
request.navigation,
|
|
|
|
ExtractIdsList(request.selectedItems),
|
|
|
|
callback);
|
2018-01-28 15:08:34 +00:00
|
|
|
});
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AddForwardMessageAction(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
const ContextMenuRequest &request,
|
|
|
|
not_null<ListWidget*> list) {
|
|
|
|
const auto item = request.item;
|
|
|
|
if (!request.selectedItems.empty()) {
|
|
|
|
return false;
|
|
|
|
} else if (!item || !item->allowsForward()) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-07-24 11:13:51 +00:00
|
|
|
const auto owner = &item->history()->owner();
|
2018-01-28 15:08:34 +00:00
|
|
|
const auto asGroup = (request.pointState != PointState::GroupPart);
|
|
|
|
if (asGroup) {
|
2019-07-24 11:13:51 +00:00
|
|
|
if (const auto group = owner->groups().find(item)) {
|
2020-05-18 19:33:14 +00:00
|
|
|
if (!ranges::all_of(group->items, &HistoryItem::allowsForward)) {
|
2018-01-28 15:08:34 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const auto itemId = item->fullId();
|
2019-06-19 15:09:03 +00:00
|
|
|
menu->addAction(tr::lng_context_forward_msg(tr::now), [=] {
|
2019-07-24 11:13:51 +00:00
|
|
|
if (const auto item = owner->message(itemId)) {
|
2019-07-25 18:55:11 +00:00
|
|
|
Window::ShowForwardMessagesBox(
|
|
|
|
request.navigation,
|
|
|
|
(asGroup
|
|
|
|
? owner->itemOrItsGroup(item)
|
|
|
|
: MessageIdsList{ 1, itemId }));
|
2018-01-28 15:08:34 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddForwardAction(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
const ContextMenuRequest &request,
|
|
|
|
not_null<ListWidget*> list) {
|
|
|
|
AddForwardSelectedAction(menu, request, list);
|
|
|
|
AddForwardMessageAction(menu, request, list);
|
|
|
|
}
|
|
|
|
|
2019-08-09 17:58:58 +00:00
|
|
|
bool AddSendNowSelectedAction(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
const ContextMenuRequest &request,
|
|
|
|
not_null<ListWidget*> list) {
|
|
|
|
if (!request.overSelection || request.selectedItems.empty()) {
|
|
|
|
return false;
|
|
|
|
}
|
2020-05-18 19:33:14 +00:00
|
|
|
if (!ranges::all_of(request.selectedItems, &SelectedItem::canSendNow)) {
|
2019-08-09 17:58:58 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto session = &request.navigation->session();
|
|
|
|
auto histories = ranges::view::all(
|
|
|
|
request.selectedItems
|
|
|
|
) | ranges::view::transform([&](const SelectedItem &item) {
|
|
|
|
return session->data().message(item.msgId);
|
|
|
|
}) | ranges::view::filter([](HistoryItem *item) {
|
|
|
|
return item != nullptr;
|
2020-05-18 19:33:14 +00:00
|
|
|
}) | ranges::view::transform(
|
|
|
|
&HistoryItem::history
|
|
|
|
);
|
2019-08-09 17:58:58 +00:00
|
|
|
if (histories.begin() == histories.end()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const auto history = *histories.begin();
|
|
|
|
|
|
|
|
menu->addAction(tr::lng_context_send_now_selected(tr::now), [=] {
|
2019-09-13 12:22:54 +00:00
|
|
|
const auto weak = Ui::MakeWeak(list);
|
2019-08-09 17:58:58 +00:00
|
|
|
const auto callback = [=] {
|
|
|
|
request.navigation->showBackFromStack();
|
|
|
|
};
|
|
|
|
Window::ShowSendNowMessagesBox(
|
|
|
|
request.navigation,
|
|
|
|
history,
|
|
|
|
ExtractIdsList(request.selectedItems),
|
|
|
|
callback);
|
|
|
|
});
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AddSendNowMessageAction(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
const ContextMenuRequest &request,
|
|
|
|
not_null<ListWidget*> list) {
|
|
|
|
const auto item = request.item;
|
|
|
|
if (!request.selectedItems.empty()) {
|
|
|
|
return false;
|
|
|
|
} else if (!item || !item->allowsSendNow()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const auto owner = &item->history()->owner();
|
|
|
|
const auto asGroup = (request.pointState != PointState::GroupPart);
|
|
|
|
if (asGroup) {
|
|
|
|
if (const auto group = owner->groups().find(item)) {
|
2020-05-18 19:33:14 +00:00
|
|
|
if (!ranges::all_of(group->items, &HistoryItem::allowsSendNow)) {
|
2019-08-09 17:58:58 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const auto itemId = item->fullId();
|
|
|
|
menu->addAction(tr::lng_context_send_now_msg(tr::now), [=] {
|
|
|
|
if (const auto item = owner->message(itemId)) {
|
|
|
|
const auto callback = [=] {
|
|
|
|
request.navigation->showBackFromStack();
|
|
|
|
};
|
|
|
|
Window::ShowSendNowMessagesBox(
|
|
|
|
request.navigation,
|
|
|
|
item->history(),
|
|
|
|
(asGroup
|
|
|
|
? owner->itemOrItsGroup(item)
|
|
|
|
: MessageIdsList{ 1, itemId }),
|
|
|
|
callback);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-02-01 01:00:58 +00:00
|
|
|
bool AddRescheduleAction(
|
2020-05-11 00:11:32 +00:00
|
|
|
not_null<Ui::PopupMenu*> menu,
|
2020-10-22 07:53:56 +00:00
|
|
|
const ContextMenuRequest &request,
|
|
|
|
not_null<ListWidget*> list) {
|
2021-02-01 01:00:58 +00:00
|
|
|
const auto owner = &request.navigation->session().data();
|
|
|
|
|
|
|
|
const auto goodSingle = !(!HasEditMessageAction(request, list)
|
|
|
|
|| !request.item->isScheduled());
|
|
|
|
const auto goodMany = [&] {
|
|
|
|
if (goodSingle) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!request.overSelection || request.selectedItems.empty()) {
|
|
|
|
return false;
|
|
|
|
}
|
2021-02-04 17:41:03 +00:00
|
|
|
if (request.selectedItems.size() > kRescheduleLimit) {
|
|
|
|
return false;
|
|
|
|
}
|
2021-02-01 01:00:58 +00:00
|
|
|
return true;
|
|
|
|
}();
|
|
|
|
if (!goodSingle && !goodMany) {
|
2020-05-11 00:11:32 +00:00
|
|
|
return false;
|
|
|
|
}
|
2021-02-01 01:00:58 +00:00
|
|
|
auto ids = goodSingle
|
|
|
|
? MessageIdsList{ request.item->fullId() }
|
|
|
|
: ExtractIdsList(request.selectedItems);
|
|
|
|
ranges::sort(ids, [&](const FullMsgId &a, const FullMsgId &b) {
|
|
|
|
const auto itemA = owner->message(a);
|
|
|
|
const auto itemB = owner->message(b);
|
|
|
|
return (itemA && itemB) && (itemA->position() < itemB->position());
|
|
|
|
});
|
|
|
|
|
|
|
|
auto text = ((ids.size() == 1)
|
|
|
|
? tr::lng_context_reschedule
|
|
|
|
: tr::lng_context_reschedule_selected)(tr::now);
|
|
|
|
|
|
|
|
menu->addAction(std::move(text), [=] {
|
|
|
|
const auto firstItem = owner->message(ids.front());
|
|
|
|
if (!firstItem) {
|
2020-05-11 00:11:32 +00:00
|
|
|
return;
|
|
|
|
}
|
2021-02-01 01:00:58 +00:00
|
|
|
list->cancelSelection();
|
2020-05-11 00:11:32 +00:00
|
|
|
const auto callback = [=](Api::SendOptions options) {
|
2021-02-01 01:00:58 +00:00
|
|
|
for (const auto &id : ids) {
|
|
|
|
const auto item = owner->message(id);
|
|
|
|
if (!item && !item->isScheduled()) {
|
|
|
|
continue;
|
|
|
|
}
|
2020-10-20 20:21:51 +00:00
|
|
|
if (!item->media() || !item->media()->webpage()) {
|
|
|
|
options.removeWebPageId = true;
|
|
|
|
}
|
|
|
|
Api::RescheduleMessage(item, options);
|
2021-02-04 17:41:03 +00:00
|
|
|
// Increase the scheduled date by 1s to keep the order.
|
2021-02-01 01:00:58 +00:00
|
|
|
options.scheduled += 1;
|
2020-05-29 23:23:27 +00:00
|
|
|
}
|
2020-05-11 00:11:32 +00:00
|
|
|
};
|
|
|
|
|
2021-02-01 01:00:58 +00:00
|
|
|
const auto peer = firstItem->history()->peer;
|
2020-05-11 00:11:32 +00:00
|
|
|
const auto sendMenuType = !peer
|
2020-08-10 12:22:54 +00:00
|
|
|
? SendMenu::Type::Disabled
|
2020-05-11 00:11:32 +00:00
|
|
|
: peer->isSelf()
|
2020-08-10 12:22:54 +00:00
|
|
|
? SendMenu::Type::Reminder
|
2020-05-11 00:11:32 +00:00
|
|
|
: HistoryView::CanScheduleUntilOnline(peer)
|
2020-08-10 12:22:54 +00:00
|
|
|
? SendMenu::Type::ScheduledToUser
|
|
|
|
: SendMenu::Type::Scheduled;
|
2020-05-11 00:11:32 +00:00
|
|
|
|
2020-06-08 06:56:09 +00:00
|
|
|
using S = Data::ScheduledMessages;
|
2021-02-01 01:00:58 +00:00
|
|
|
const auto itemDate = firstItem->date();
|
|
|
|
const auto date = (itemDate == S::kScheduledUntilOnlineTimestamp)
|
2020-06-08 06:56:09 +00:00
|
|
|
? HistoryView::DefaultScheduleTime()
|
2021-02-01 01:00:58 +00:00
|
|
|
: itemDate + 600;
|
2020-06-08 06:56:09 +00:00
|
|
|
|
2020-10-20 20:25:42 +00:00
|
|
|
const auto box = Ui::show(
|
2020-05-11 00:11:32 +00:00
|
|
|
HistoryView::PrepareScheduleBox(
|
|
|
|
&request.navigation->session(),
|
|
|
|
sendMenuType,
|
|
|
|
callback,
|
2020-06-08 06:56:09 +00:00
|
|
|
date),
|
2020-05-11 00:11:32 +00:00
|
|
|
Ui::LayerOption::KeepOther);
|
2020-10-20 20:25:42 +00:00
|
|
|
|
|
|
|
owner->itemRemoved(
|
2021-02-01 01:00:58 +00:00
|
|
|
) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
|
|
|
|
if (ranges::contains(ids, item->fullId())) {
|
|
|
|
box->closeBox();
|
|
|
|
}
|
2020-10-20 20:25:42 +00:00
|
|
|
}, box->lifetime());
|
2020-05-11 00:11:32 +00:00
|
|
|
});
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-09-01 06:07:37 +00:00
|
|
|
bool AddReplyToMessageAction(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
const ContextMenuRequest &request,
|
|
|
|
not_null<ListWidget*> list) {
|
2020-10-21 14:41:13 +00:00
|
|
|
const auto context = list->elementContext();
|
2020-09-01 06:07:37 +00:00
|
|
|
const auto item = request.item;
|
|
|
|
if (!item
|
|
|
|
|| !IsServerMsgId(item->id)
|
2020-10-21 14:41:13 +00:00
|
|
|
|| !item->history()->peer->canWrite()
|
|
|
|
|| (context != Context::History && context != Context::Replies)) {
|
2020-09-01 06:07:37 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const auto owner = &item->history()->owner();
|
|
|
|
const auto itemId = item->fullId();
|
|
|
|
menu->addAction(tr::lng_context_reply_msg(tr::now), [=] {
|
|
|
|
const auto item = owner->message(itemId);
|
|
|
|
if (!item) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
list->replyToMessageRequestNotify(item->fullId());
|
|
|
|
});
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-10-21 14:41:13 +00:00
|
|
|
bool AddViewRepliesAction(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
const ContextMenuRequest &request,
|
|
|
|
not_null<ListWidget*> list) {
|
|
|
|
const auto context = list->elementContext();
|
|
|
|
const auto item = request.item;
|
|
|
|
if (!item
|
|
|
|
|| !IsServerMsgId(item->id)
|
|
|
|
|| (context != Context::History && context != Context::Pinned)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const auto repliesCount = item->repliesCount();
|
|
|
|
const auto withReplies = (repliesCount > 0);
|
|
|
|
if (!withReplies || !item->history()->peer->isMegagroup()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const auto rootId = repliesCount ? item->id : item->replyToTop();
|
|
|
|
const auto phrase = (repliesCount > 0)
|
|
|
|
? tr::lng_replies_view(
|
|
|
|
tr::now,
|
|
|
|
lt_count,
|
|
|
|
repliesCount)
|
|
|
|
: tr::lng_replies_view_thread(tr::now);
|
|
|
|
const auto controller = list->controller();
|
|
|
|
const auto history = item->history();
|
|
|
|
menu->addAction(phrase, crl::guard(controller, [=] {
|
|
|
|
controller->showRepliesForMessage(history, rootId);
|
|
|
|
}));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-05-30 08:47:32 +00:00
|
|
|
bool AddEditMessageAction(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
const ContextMenuRequest &request,
|
|
|
|
not_null<ListWidget*> list) {
|
2020-10-22 07:53:56 +00:00
|
|
|
if (!HasEditMessageAction(request, list)) {
|
2020-05-30 08:47:32 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const auto item = request.item;
|
|
|
|
if (!item->allowsEdit(base::unixtime::now())) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const auto owner = &item->history()->owner();
|
|
|
|
const auto itemId = item->fullId();
|
|
|
|
menu->addAction(tr::lng_context_edit_msg(tr::now), [=] {
|
|
|
|
const auto item = owner->message(itemId);
|
|
|
|
if (!item) {
|
|
|
|
return;
|
|
|
|
}
|
2020-06-04 10:29:16 +00:00
|
|
|
list->editMessageRequestNotify(item->fullId());
|
2020-05-30 08:47:32 +00:00
|
|
|
});
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-10-21 14:41:13 +00:00
|
|
|
bool AddPinMessageAction(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
const ContextMenuRequest &request,
|
|
|
|
not_null<ListWidget*> list) {
|
|
|
|
const auto context = list->elementContext();
|
|
|
|
const auto item = request.item;
|
|
|
|
if (!item
|
|
|
|
|| !IsServerMsgId(item->id)
|
|
|
|
|| (context != Context::History && context != Context::Pinned)) {
|
|
|
|
return false;
|
|
|
|
}
|
2020-10-26 09:33:19 +00:00
|
|
|
const auto group = item->history()->owner().groups().find(item);
|
|
|
|
const auto pinItem = ((item->canPin() && item->isPinned()) || !group)
|
|
|
|
? item
|
|
|
|
: group->items.front().get();
|
|
|
|
if (!pinItem->canPin()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const auto pinItemId = pinItem->fullId();
|
|
|
|
const auto isPinned = pinItem->isPinned();
|
2020-10-21 14:41:13 +00:00
|
|
|
const auto controller = list->controller();
|
|
|
|
menu->addAction(isPinned ? tr::lng_context_unpin_msg(tr::now) : tr::lng_context_pin_msg(tr::now), crl::guard(controller, [=] {
|
2020-10-26 09:33:19 +00:00
|
|
|
Window::ToggleMessagePinned(controller, pinItemId, !isPinned);
|
2020-10-21 14:41:13 +00:00
|
|
|
}));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-10-22 07:53:56 +00:00
|
|
|
bool AddGoToMessageAction(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
const ContextMenuRequest &request,
|
|
|
|
not_null<ListWidget*> list) {
|
|
|
|
const auto context = list->elementContext();
|
|
|
|
const auto view = request.view;
|
|
|
|
if (!view
|
|
|
|
|| !IsServerMsgId(view->data()->id)
|
|
|
|
|| context != Context::Pinned
|
|
|
|
|| !view->hasOutLayout()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const auto itemId = view->data()->fullId();
|
|
|
|
const auto controller = list->controller();
|
|
|
|
menu->addAction(tr::lng_context_to_msg(tr::now), crl::guard(controller, [=] {
|
|
|
|
const auto item = controller->session().data().message(itemId);
|
|
|
|
if (item) {
|
|
|
|
goToMessageClickHandler(item)->onClick(ClickContext{});
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-08-09 17:58:58 +00:00
|
|
|
void AddSendNowAction(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
const ContextMenuRequest &request,
|
|
|
|
not_null<ListWidget*> list) {
|
|
|
|
AddSendNowSelectedAction(menu, request, list);
|
|
|
|
AddSendNowMessageAction(menu, request, list);
|
|
|
|
}
|
|
|
|
|
2018-01-28 15:08:34 +00:00
|
|
|
bool AddDeleteSelectedAction(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
const ContextMenuRequest &request,
|
|
|
|
not_null<ListWidget*> list) {
|
|
|
|
if (!request.overSelection || request.selectedItems.empty()) {
|
|
|
|
return false;
|
|
|
|
}
|
2020-05-18 19:33:14 +00:00
|
|
|
if (!ranges::all_of(request.selectedItems, &SelectedItem::canDelete)) {
|
2018-01-28 15:08:34 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-06-19 15:09:03 +00:00
|
|
|
menu->addAction(tr::lng_context_delete_selected(tr::now), [=] {
|
2019-09-13 12:22:54 +00:00
|
|
|
const auto weak = Ui::MakeWeak(list);
|
2018-01-28 15:08:34 +00:00
|
|
|
auto items = ExtractIdsList(request.selectedItems);
|
2019-07-24 14:00:30 +00:00
|
|
|
const auto box = Ui::show(Box<DeleteMessagesBox>(
|
2019-07-25 18:55:11 +00:00
|
|
|
&request.navigation->session(),
|
2019-07-24 14:00:30 +00:00
|
|
|
std::move(items)));
|
2018-01-28 15:08:34 +00:00
|
|
|
box->setDeleteConfirmedCallback([=] {
|
|
|
|
if (const auto strong = weak.data()) {
|
|
|
|
strong->cancelSelection();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AddDeleteMessageAction(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
const ContextMenuRequest &request,
|
|
|
|
not_null<ListWidget*> list) {
|
|
|
|
const auto item = request.item;
|
|
|
|
if (!request.selectedItems.empty()) {
|
|
|
|
return false;
|
|
|
|
} else if (!item || !item->canDelete()) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-07-24 11:13:51 +00:00
|
|
|
const auto owner = &item->history()->owner();
|
2018-01-28 15:08:34 +00:00
|
|
|
const auto asGroup = (request.pointState != PointState::GroupPart);
|
|
|
|
if (asGroup) {
|
2019-07-24 11:13:51 +00:00
|
|
|
if (const auto group = owner->groups().find(item)) {
|
2020-05-18 19:33:14 +00:00
|
|
|
if (ranges::any_of(group->items, [](auto item) {
|
2020-05-18 18:35:06 +00:00
|
|
|
const auto id = ItemIdAcrossData(item);
|
|
|
|
return !IsServerMsgId(id) || !item->canDelete();
|
2020-05-18 19:33:14 +00:00
|
|
|
})) {
|
2018-01-28 15:08:34 +00:00
|
|
|
return false;
|
|
|
|
}
|
2018-01-25 10:10:52 +00:00
|
|
|
}
|
|
|
|
}
|
2020-06-11 16:33:15 +00:00
|
|
|
const auto controller = list->controller();
|
2018-01-28 15:08:34 +00:00
|
|
|
const auto itemId = item->fullId();
|
2020-06-11 16:33:15 +00:00
|
|
|
const auto callback = crl::guard(controller, [=] {
|
2019-07-24 11:13:51 +00:00
|
|
|
if (const auto item = owner->message(itemId)) {
|
2018-01-28 15:08:34 +00:00
|
|
|
if (asGroup) {
|
2019-07-24 11:13:51 +00:00
|
|
|
if (const auto group = owner->groups().find(item)) {
|
2018-01-28 15:08:34 +00:00
|
|
|
Ui::show(Box<DeleteMessagesBox>(
|
2019-07-24 14:00:30 +00:00
|
|
|
&owner->session(),
|
2019-07-24 11:13:51 +00:00
|
|
|
owner->itemsToIds(group->items)));
|
2018-01-28 15:08:34 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (const auto message = item->toHistoryMessage()) {
|
|
|
|
if (message->uploading()) {
|
2020-06-11 16:33:15 +00:00
|
|
|
controller->content()->cancelUploadLayer(item);
|
2018-01-28 15:08:34 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const auto suggestModerateActions = true;
|
|
|
|
Ui::show(Box<DeleteMessagesBox>(item, suggestModerateActions));
|
|
|
|
}
|
|
|
|
});
|
2020-06-04 17:09:56 +00:00
|
|
|
const auto text = [&] {
|
|
|
|
if (const auto message = item->toHistoryMessage()) {
|
|
|
|
if (message->uploading()) {
|
|
|
|
return tr::lng_context_cancel_upload;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return tr::lng_context_delete_msg;
|
|
|
|
}()(tr::now);
|
|
|
|
menu->addAction(text, callback);
|
2018-01-28 15:08:34 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddDeleteAction(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
const ContextMenuRequest &request,
|
|
|
|
not_null<ListWidget*> list) {
|
|
|
|
if (!AddDeleteSelectedAction(menu, request, list)) {
|
|
|
|
AddDeleteMessageAction(menu, request, list);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-01 14:06:04 +00:00
|
|
|
void AddReportAction(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
const ContextMenuRequest &request,
|
|
|
|
not_null<ListWidget*> list) {
|
|
|
|
const auto item = request.item;
|
|
|
|
if (!request.selectedItems.empty()) {
|
|
|
|
return;
|
|
|
|
} else if (!item || !item->suggestReport()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto owner = &item->history()->owner();
|
|
|
|
const auto asGroup = (request.pointState != PointState::GroupPart);
|
|
|
|
const auto controller = list->controller();
|
|
|
|
const auto itemId = item->fullId();
|
|
|
|
const auto callback = crl::guard(controller, [=] {
|
|
|
|
if (const auto item = owner->message(itemId)) {
|
|
|
|
const auto peer = item->history()->peer;
|
|
|
|
const auto group = owner->groups().find(item);
|
|
|
|
Ui::show(Box<ReportBox>(
|
|
|
|
peer,
|
|
|
|
(group
|
|
|
|
? owner->itemsToIds(group->items)
|
|
|
|
: MessageIdsList(1, itemId))));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
menu->addAction(tr::lng_context_report_msg(tr::now), callback);
|
|
|
|
}
|
|
|
|
|
2018-01-28 15:08:34 +00:00
|
|
|
bool AddClearSelectionAction(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
const ContextMenuRequest &request,
|
|
|
|
not_null<ListWidget*> list) {
|
|
|
|
if (!request.overSelection || request.selectedItems.empty()) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-06-19 15:09:03 +00:00
|
|
|
menu->addAction(tr::lng_context_clear_selection(tr::now), [=] {
|
2018-01-28 15:08:34 +00:00
|
|
|
list->cancelSelection();
|
|
|
|
});
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AddSelectMessageAction(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
const ContextMenuRequest &request,
|
|
|
|
not_null<ListWidget*> list) {
|
|
|
|
const auto item = request.item;
|
|
|
|
if (request.overSelection && !request.selectedItems.empty()) {
|
|
|
|
return false;
|
2020-05-18 18:35:06 +00:00
|
|
|
} else if (!item
|
2020-09-25 12:31:53 +00:00
|
|
|
|| item->isSending()
|
|
|
|
|| item->hasFailed()
|
|
|
|
|| !IsServerMsgId(ItemIdAcrossData(item))
|
|
|
|
|| item->serviceMsg()) {
|
2018-01-28 15:08:34 +00:00
|
|
|
return false;
|
|
|
|
}
|
2019-07-24 11:13:51 +00:00
|
|
|
const auto owner = &item->history()->owner();
|
2018-01-28 15:08:34 +00:00
|
|
|
const auto itemId = item->fullId();
|
|
|
|
const auto asGroup = (request.pointState != PointState::GroupPart);
|
2019-06-19 15:09:03 +00:00
|
|
|
menu->addAction(tr::lng_context_select_msg(tr::now), [=] {
|
2019-07-24 11:13:51 +00:00
|
|
|
if (const auto item = owner->message(itemId)) {
|
2018-01-28 15:08:34 +00:00
|
|
|
if (asGroup) {
|
|
|
|
list->selectItemAsGroup(item);
|
|
|
|
} else {
|
|
|
|
list->selectItem(item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddSelectionAction(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
const ContextMenuRequest &request,
|
|
|
|
not_null<ListWidget*> list) {
|
|
|
|
if (!AddClearSelectionAction(menu, request, list)) {
|
|
|
|
AddSelectMessageAction(menu, request, list);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-01 06:07:37 +00:00
|
|
|
void AddTopMessageActions(
|
2018-01-28 15:08:34 +00:00
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
const ContextMenuRequest &request,
|
|
|
|
not_null<ListWidget*> list) {
|
2020-09-01 06:07:37 +00:00
|
|
|
AddReplyToMessageAction(menu, request, list);
|
2020-10-22 07:53:56 +00:00
|
|
|
AddGoToMessageAction(menu, request, list);
|
2020-10-21 14:41:13 +00:00
|
|
|
AddViewRepliesAction(menu, request, list);
|
2020-05-30 08:47:32 +00:00
|
|
|
AddEditMessageAction(menu, request, list);
|
2020-10-21 14:41:13 +00:00
|
|
|
AddPinMessageAction(menu, request, list);
|
2020-09-01 06:07:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void AddMessageActions(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
const ContextMenuRequest &request,
|
|
|
|
not_null<ListWidget*> list) {
|
2018-01-28 15:08:34 +00:00
|
|
|
AddPostLinkAction(menu, request);
|
|
|
|
AddForwardAction(menu, request, list);
|
2019-08-09 17:58:58 +00:00
|
|
|
AddSendNowAction(menu, request, list);
|
2018-01-28 15:08:34 +00:00
|
|
|
AddDeleteAction(menu, request, list);
|
2020-10-01 14:06:04 +00:00
|
|
|
AddReportAction(menu, request, list);
|
2018-01-28 15:08:34 +00:00
|
|
|
AddSelectionAction(menu, request, list);
|
2021-02-01 01:00:58 +00:00
|
|
|
AddRescheduleAction(menu, request, list);
|
2018-01-28 15:08:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void AddCopyLinkAction(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
const ClickHandlerPtr &link) {
|
|
|
|
if (!link) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto action = link->copyToClipboardContextItemText();
|
|
|
|
if (action.isEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto text = link->copyToClipboardText();
|
|
|
|
menu->addAction(
|
|
|
|
action,
|
2019-09-04 07:19:15 +00:00
|
|
|
[=] { QGuiApplication::clipboard()->setText(text); });
|
2018-01-25 10:10:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
2019-07-25 18:55:11 +00:00
|
|
|
ContextMenuRequest::ContextMenuRequest(
|
|
|
|
not_null<Window::SessionNavigation*> navigation)
|
|
|
|
: navigation(navigation) {
|
2019-07-24 14:00:30 +00:00
|
|
|
}
|
|
|
|
|
2018-01-25 10:10:52 +00:00
|
|
|
base::unique_qptr<Ui::PopupMenu> FillContextMenu(
|
|
|
|
not_null<ListWidget*> list,
|
|
|
|
const ContextMenuRequest &request) {
|
2018-07-19 14:27:09 +00:00
|
|
|
auto result = base::make_unique_q<Ui::PopupMenu>(list);
|
2018-01-25 10:10:52 +00:00
|
|
|
|
|
|
|
const auto link = request.link;
|
|
|
|
const auto view = request.view;
|
2018-01-28 15:08:34 +00:00
|
|
|
const auto item = request.item;
|
2018-01-25 10:10:52 +00:00
|
|
|
const auto itemId = item ? item->fullId() : FullMsgId();
|
|
|
|
const auto rawLink = link.get();
|
|
|
|
const auto linkPhoto = dynamic_cast<PhotoClickHandler*>(rawLink);
|
|
|
|
const auto linkDocument = dynamic_cast<DocumentClickHandler*>(rawLink);
|
|
|
|
const auto linkPeer = dynamic_cast<PeerClickHandler*>(rawLink);
|
|
|
|
const auto photo = linkPhoto ? linkPhoto->photo().get() : nullptr;
|
|
|
|
const auto document = linkDocument
|
|
|
|
? linkDocument->document().get()
|
|
|
|
: nullptr;
|
2021-01-02 14:36:14 +00:00
|
|
|
const auto poll = item
|
|
|
|
? (item->media() ? item->media()->poll() : nullptr)
|
|
|
|
: nullptr;
|
2018-01-25 10:10:52 +00:00
|
|
|
const auto hasSelection = !request.selectedItems.empty()
|
2019-04-08 15:10:06 +00:00
|
|
|
|| !request.selectedText.empty();
|
2018-01-25 10:10:52 +00:00
|
|
|
|
|
|
|
if (request.overSelection) {
|
2019-06-19 15:09:03 +00:00
|
|
|
const auto text = request.selectedItems.empty()
|
|
|
|
? tr::lng_context_copy_selected(tr::now)
|
|
|
|
: tr::lng_context_copy_selected_items(tr::now);
|
2018-01-28 15:08:34 +00:00
|
|
|
result->addAction(text, [=] {
|
2019-09-16 11:14:06 +00:00
|
|
|
TextUtilities::SetClipboardText(list->getSelectedText());
|
2018-01-25 10:10:52 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-09-01 06:07:37 +00:00
|
|
|
AddTopMessageActions(result, request, list);
|
2018-01-25 10:10:52 +00:00
|
|
|
if (linkPhoto) {
|
2020-10-30 10:16:03 +00:00
|
|
|
AddPhotoActions(result, photo, list);
|
2018-01-25 10:10:52 +00:00
|
|
|
} else if (linkDocument) {
|
2020-08-31 12:39:20 +00:00
|
|
|
AddDocumentActions(result, document, itemId, list);
|
2019-04-15 11:54:03 +00:00
|
|
|
//} else if (linkPeer) { // #feed
|
|
|
|
// const auto peer = linkPeer->peer();
|
|
|
|
// if (peer->isChannel()
|
|
|
|
// && peer->asChannel()->feed() != nullptr
|
|
|
|
// && (list->delegate()->listContext() == Context::Feed)) {
|
|
|
|
// Window::PeerMenuAddMuteAction(peer, [&](
|
|
|
|
// const QString &text,
|
|
|
|
// Fn<void()> handler) {
|
|
|
|
// return result->addAction(text, handler);
|
|
|
|
// });
|
|
|
|
// AddToggleGroupingAction(result, linkPeer->peer());
|
|
|
|
// }
|
2021-01-02 14:36:14 +00:00
|
|
|
} else if (poll) {
|
|
|
|
AddPollActions(result, poll, item, list->elementContext());
|
2018-01-28 15:08:34 +00:00
|
|
|
} else if (!request.overSelection && view && !hasSelection) {
|
2019-07-24 11:13:51 +00:00
|
|
|
const auto owner = &view->data()->history()->owner();
|
2018-12-18 11:06:57 +00:00
|
|
|
const auto media = view->media();
|
2018-01-28 15:08:34 +00:00
|
|
|
const auto mediaHasTextForCopy = media && media->hasTextForCopy();
|
2018-12-18 11:06:57 +00:00
|
|
|
if (const auto document = media ? media->getDocument() : nullptr) {
|
2020-08-31 12:39:20 +00:00
|
|
|
AddDocumentActions(
|
|
|
|
result,
|
|
|
|
document,
|
|
|
|
view->data()->fullId(),
|
|
|
|
list);
|
2018-01-25 10:10:52 +00:00
|
|
|
}
|
2018-01-28 15:08:34 +00:00
|
|
|
if (!link && (view->hasVisibleText() || mediaHasTextForCopy)) {
|
|
|
|
const auto asGroup = (request.pointState != PointState::GroupPart);
|
2019-06-19 15:09:03 +00:00
|
|
|
result->addAction(tr::lng_context_copy_text(tr::now), [=] {
|
2019-07-24 11:13:51 +00:00
|
|
|
if (const auto item = owner->message(itemId)) {
|
2018-01-28 15:08:34 +00:00
|
|
|
if (asGroup) {
|
2019-07-24 11:13:51 +00:00
|
|
|
if (const auto group = owner->groups().find(item)) {
|
2019-09-16 11:14:06 +00:00
|
|
|
TextUtilities::SetClipboardText(HistoryGroupText(group));
|
2018-01-28 15:08:34 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2019-09-16 11:14:06 +00:00
|
|
|
TextUtilities::SetClipboardText(HistoryItemText(item));
|
2018-01-28 15:08:34 +00:00
|
|
|
}
|
|
|
|
});
|
2018-01-25 10:10:52 +00:00
|
|
|
}
|
|
|
|
}
|
2018-01-28 15:08:34 +00:00
|
|
|
|
|
|
|
AddCopyLinkAction(result, link);
|
|
|
|
AddMessageActions(result, request, list);
|
2018-01-25 10:10:52 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2020-09-22 15:28:49 +00:00
|
|
|
void CopyPostLink(
|
|
|
|
not_null<Main::Session*> session,
|
|
|
|
FullMsgId itemId,
|
|
|
|
Context context) {
|
2020-06-09 09:36:40 +00:00
|
|
|
const auto item = session->data().message(itemId);
|
2019-04-05 11:50:16 +00:00
|
|
|
if (!item || !item->hasDirectLink()) {
|
|
|
|
return;
|
|
|
|
}
|
2020-09-22 15:28:49 +00:00
|
|
|
const auto inRepliesContext = (context == Context::Replies);
|
2019-09-04 07:19:15 +00:00
|
|
|
QGuiApplication::clipboard()->setText(
|
2020-09-22 15:28:49 +00:00
|
|
|
item->history()->session().api().exportDirectMessageLink(
|
|
|
|
item,
|
|
|
|
inRepliesContext));
|
|
|
|
|
|
|
|
const auto isPublicLink = [&] {
|
|
|
|
const auto channel = item->history()->peer->asChannel();
|
|
|
|
Assert(channel != nullptr);
|
|
|
|
if (const auto rootId = item->replyToTop()) {
|
|
|
|
const auto root = item->history()->owner().message(
|
|
|
|
channel->bareId(),
|
|
|
|
rootId);
|
|
|
|
const auto sender = root
|
|
|
|
? root->discussionPostOriginalSender()
|
|
|
|
: nullptr;
|
|
|
|
if (sender && sender->hasUsername()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return channel->hasUsername();
|
|
|
|
}();
|
2019-04-04 10:02:22 +00:00
|
|
|
|
2020-09-22 15:28:49 +00:00
|
|
|
Ui::Toast::Show(isPublicLink
|
2019-06-19 15:09:03 +00:00
|
|
|
? tr::lng_channel_public_link_copied(tr::now)
|
|
|
|
: tr::lng_context_about_private_link(tr::now));
|
2019-04-04 10:02:22 +00:00
|
|
|
}
|
|
|
|
|
2020-06-09 09:36:40 +00:00
|
|
|
void StopPoll(not_null<Main::Session*> session, FullMsgId itemId) {
|
2019-04-05 11:50:16 +00:00
|
|
|
const auto stop = [=] {
|
|
|
|
Ui::hideLayer();
|
2020-06-09 09:36:40 +00:00
|
|
|
if (const auto item = session->data().message(itemId)) {
|
|
|
|
session->api().closePoll(item);
|
2019-04-05 11:50:16 +00:00
|
|
|
}
|
|
|
|
};
|
2018-12-19 11:20:04 +00:00
|
|
|
Ui::show(Box<ConfirmBox>(
|
2019-06-19 15:09:03 +00:00
|
|
|
tr::lng_polls_stop_warning(tr::now),
|
|
|
|
tr::lng_polls_stop_sure(tr::now),
|
|
|
|
tr::lng_cancel(tr::now),
|
2019-04-05 11:50:16 +00:00
|
|
|
stop));
|
2018-12-19 11:20:04 +00:00
|
|
|
}
|
|
|
|
|
2021-01-02 14:36:14 +00:00
|
|
|
void AddPollActions(
|
|
|
|
not_null<Ui::PopupMenu*> menu,
|
|
|
|
not_null<PollData*> poll,
|
|
|
|
not_null<HistoryItem*> item,
|
|
|
|
Context context) {
|
|
|
|
if ((context != Context::History)
|
|
|
|
&& (context != Context::Replies)
|
|
|
|
&& (context != Context::Pinned)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (poll->closed()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto itemId = item->fullId();
|
|
|
|
if (poll->voted() && !poll->quiz()) {
|
|
|
|
menu->addAction(tr::lng_polls_retract(tr::now), [=] {
|
|
|
|
poll->session().api().sendPollVotes(itemId, {});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (item->canStopPoll()) {
|
|
|
|
menu->addAction(tr::lng_polls_stop(tr::now), [=] {
|
|
|
|
StopPoll(&poll->session(), itemId);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-25 10:10:52 +00:00
|
|
|
} // namespace HistoryView
|