diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 9c1bc9997f..9ba2feb822 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -1552,33 +1552,6 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { saveDocumentToFile(document); })); } - } else if (media->type() == MediaTypeGif && !link) { - if (auto document = media->getDocument()) { - if (document->loading()) { - _menu->addAction(lang(lng_context_cancel_download), [=] { - cancelContextDownload(document); - }); - } else { - if (document->isGifv()) { - if (!cAutoPlayGif()) { - _menu->addAction(lang(lng_context_open_gif), [=] { - openContextGif(itemId); - }); - } - _menu->addAction(lang(lng_context_save_gif), [=] { - saveContextGif(itemId); - }); - } - if (!document->filepath(DocumentData::FilePathResolveChecked).isEmpty()) { - _menu->addAction(lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_context_show_in_finder : lng_context_show_in_folder), [=] { - showContextInFolder(document); - }); - } - _menu->addAction(lang(lng_context_save_file), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, document] { - saveDocumentToFile(document); - })); - } - } } } if (msg && view && !link && (view->hasVisibleText() || mediaHasTextForCopy)) { diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 8f1e9bf3a0..02a731e532 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -377,7 +377,7 @@ bool HistoryItem::allowsEdit(const QDateTime &now) const { } bool HistoryItem::canDelete() const { - if (isLogEntry()) { + if (isLogEntry() || (!IsServerMsgId(id) && serviceMsg())) { return false; } auto channel = _history->peer->asChannel(); @@ -450,7 +450,7 @@ bool HistoryItem::suggestDeleteAllReport() const { } bool HistoryItem::hasDirectLink() const { - if (id <= 0) { + if (!IsServerMsgId(id)) { return false; } if (auto channel = _history->peer->asChannel()) { diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 86c01df3a6..5acd2e42a8 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -8,14 +8,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_context_menu.h" #include "history/view/history_view_list_widget.h" +#include "history/view/history_view_cursor_state.h" +#include "history/history.h" #include "history/history_item.h" +#include "history/history_message.h" #include "history/history_item_text.h" #include "history/history_media_types.h" #include "ui/widgets/popup_menu.h" #include "chat_helpers/message_field.h" +#include "boxes/confirm_box.h" #include "data/data_photo.h" #include "data/data_document.h" #include "data/data_media_types.h" +#include "data/data_session.h" +#include "data/data_groups.h" #include "core/file_utilities.h" #include "window/window_peer_menu.h" #include "lang/lang_keys.h" @@ -61,6 +67,25 @@ void CopyImage(not_null photo) { QApplication::clipboard()->setPixmap(photo->full->pix()); } +void ShowStickerPackInfo(not_null document) { + if (const auto sticker = document->sticker()) { + if (sticker->set.type() != mtpc_inputStickerSetEmpty) { + App::main()->stickersBox(sticker->set); + } + } +} + +void ToggleFavedSticker(not_null document) { + const auto unfave = Stickers::IsFaved(document); + MTP::send( + MTPmessages_FaveSticker( + document->mtpInput(), + MTP_bool(unfave)), + rpcDone([=](const MTPBool &result) { + Stickers::SetFaved(document, !unfave); + })); +} + void AddPhotoActions( not_null menu, not_null photo) { @@ -129,6 +154,19 @@ void AddDocumentActions( }); } } + if (document->sticker() + && document->sticker()->set.type() != mtpc_inputStickerSetEmpty) { + menu->addAction( + lang(document->isStickerSetInstalled() + ? lng_context_pack_info + : lng_context_pack_add), + [=] { ShowStickerPackInfo(document); }); + menu->addAction( + lang(Stickers::IsFaved(document) + ? lng_faved_stickers_remove + : lng_faved_stickers_add), + [=] { ToggleFavedSticker(document); }); + } if (!document->filepath( DocumentData::FilePathResolveChecked).isEmpty()) { menu->addAction( @@ -140,14 +178,256 @@ void AddDocumentActions( AddSaveDocumentAction(menu, document); } -void ShowStickerPackInfo(not_null document) { - if (const auto sticker = document->sticker()) { - if (sticker->set.type() != mtpc_inputStickerSetEmpty) { - App::main()->stickersBox(sticker->set); +void CopyPostLink(FullMsgId itemId) { + if (const auto item = App::histItemById(itemId)) { + if (item->hasDirectLink()) { + QApplication::clipboard()->setText(item->directLink()); } } } +void AddPostLinkAction( + not_null 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; + } + const auto itemId = item->fullId(); + menu->addAction( + lang(item->history()->peer->isMegagroup() + ? lng_context_copy_link + : lng_context_copy_post_link), + [=] { CopyPostLink(itemId); }); +} + +MessageIdsList ExtractIdsList(const SelectedItems &items) { + return ranges::view::all( + items + ) | ranges::view::transform([](const auto &item) { + return item.msgId; + }) | ranges::to_vector; +} + +bool AddForwardSelectedAction( + not_null menu, + const ContextMenuRequest &request, + not_null list) { + if (!request.overSelection || request.selectedItems.empty()) { + return false; + } + if (ranges::find_if(request.selectedItems, [](const auto &item) { + return !item.canForward; + }) != end(request.selectedItems)) { + return false; + } + + menu->addAction(lang(lng_context_forward_selected), [=] { + const auto weak = make_weak(list); + auto items = ExtractIdsList(request.selectedItems); + Window::ShowForwardMessagesBox(std::move(items), [=] { + if (const auto strong = weak.data()) { + strong->cancelSelection(); + } + }); + }); + return true; +} + +bool AddForwardMessageAction( + not_null menu, + const ContextMenuRequest &request, + not_null list) { + const auto item = request.item; + if (!request.selectedItems.empty()) { + return false; + } else if (!item || !item->allowsForward()) { + return false; + } + const auto asGroup = (request.pointState != PointState::GroupPart); + if (asGroup) { + if (const auto group = Auth().data().groups().find(item)) { + if (ranges::find_if(group->items, [](auto item) { + return !item->allowsForward(); + }) != end(group->items)) { + return false; + } + } + } + const auto itemId = item->fullId(); + menu->addAction(lang(lng_context_forward_msg), [=] { + if (const auto item = App::histItemById(itemId)) { + Window::ShowForwardMessagesBox(asGroup + ? Auth().data().itemOrItsGroup(item) + : MessageIdsList{ 1, itemId }); + } + }); + return true; +} + +void AddForwardAction( + not_null menu, + const ContextMenuRequest &request, + not_null list) { + AddForwardSelectedAction(menu, request, list); + AddForwardMessageAction(menu, request, list); +} + +bool AddDeleteSelectedAction( + not_null menu, + const ContextMenuRequest &request, + not_null list) { + if (!request.overSelection || request.selectedItems.empty()) { + return false; + } + if (ranges::find_if(request.selectedItems, [](const auto &item) { + return !item.canDelete; + }) != end(request.selectedItems)) { + return false; + } + + menu->addAction(lang(lng_context_delete_selected), [=] { + const auto weak = make_weak(list); + auto items = ExtractIdsList(request.selectedItems); + const auto box = Ui::show(Box(std::move(items))); + box->setDeleteConfirmedCallback([=] { + if (const auto strong = weak.data()) { + strong->cancelSelection(); + } + }); + }); + return true; +} + +bool AddDeleteMessageAction( + not_null menu, + const ContextMenuRequest &request, + not_null list) { + const auto item = request.item; + if (!request.selectedItems.empty()) { + return false; + } else if (!item || !item->canDelete()) { + return false; + } + const auto asGroup = (request.pointState != PointState::GroupPart); + if (asGroup) { + if (const auto group = Auth().data().groups().find(item)) { + if (ranges::find_if(group->items, [](auto item) { + return !IsServerMsgId(item->id) || !item->canDelete(); + }) != end(group->items)) { + return false; + } + } + } + const auto itemId = item->fullId(); + menu->addAction(lang(lng_context_delete_msg), [=] { + if (const auto item = App::histItemById(itemId)) { + if (asGroup) { + if (const auto group = Auth().data().groups().find(item)) { + Ui::show(Box( + Auth().data().itemsToIds(group->items))); + return; + } + } + if (const auto message = item->toHistoryMessage()) { + if (message->uploading()) { + App::main()->cancelUploadLayer(item); + return; + } + } + const auto suggestModerateActions = true; + Ui::show(Box(item, suggestModerateActions)); + } + }); + return true; +} + +void AddDeleteAction( + not_null menu, + const ContextMenuRequest &request, + not_null list) { + if (!AddDeleteSelectedAction(menu, request, list)) { + AddDeleteMessageAction(menu, request, list); + } +} + +bool AddClearSelectionAction( + not_null menu, + const ContextMenuRequest &request, + not_null list) { + if (!request.overSelection || request.selectedItems.empty()) { + return false; + } + menu->addAction(lang(lng_context_clear_selection), [=] { + list->cancelSelection(); + }); + return true; +} + +bool AddSelectMessageAction( + not_null menu, + const ContextMenuRequest &request, + not_null list) { + const auto item = request.item; + if (request.overSelection && !request.selectedItems.empty()) { + return false; + } else if (!item || !IsServerMsgId(item->id) || item->serviceMsg()) { + return false; + } + const auto itemId = item->fullId(); + const auto asGroup = (request.pointState != PointState::GroupPart); + menu->addAction(lang(lng_context_select_msg), [=] { + if (const auto item = App::histItemById(itemId)) { + if (asGroup) { + list->selectItemAsGroup(item); + } else { + list->selectItem(item); + } + } + }); + return true; +} + +void AddSelectionAction( + not_null menu, + const ContextMenuRequest &request, + not_null list) { + if (!AddClearSelectionAction(menu, request, list)) { + AddSelectMessageAction(menu, request, list); + } +} + +void AddMessageActions( + not_null menu, + const ContextMenuRequest &request, + not_null list) { + AddPostLinkAction(menu, request); + AddForwardAction(menu, request, list); + AddDeleteAction(menu, request, list); + AddSelectionAction(menu, request, list); +} + +void AddCopyLinkAction( + not_null menu, + const ClickHandlerPtr &link) { + if (!link) { + return; + } + const auto action = link->copyToClipboardContextItemText(); + if (action.isEmpty()) { + return; + } + const auto text = link->copyToClipboardText(); + menu->addAction( + action, + [=] { QApplication::clipboard()->setText(text); }); +} + } // namespace base::unique_qptr FillContextMenu( @@ -157,7 +437,7 @@ base::unique_qptr FillContextMenu( const auto link = request.link; const auto view = request.view; - const auto item = view ? view->data().get() : nullptr; + const auto item = request.item; const auto itemId = item ? item->fullId() : FullMsgId(); const auto rawLink = link.get(); const auto linkPhoto = dynamic_cast(rawLink); @@ -174,7 +454,10 @@ base::unique_qptr FillContextMenu( || !request.selectedText.text.isEmpty(); if (request.overSelection) { - result->addAction(lang(lng_context_copy_selected), [=] { + const auto text = lang(request.selectedItems.empty() + ? lng_context_copy_selected + : lng_context_copy_selected_items); + result->addAction(text, [=] { SetClipboardWithEntities(list->getSelectedText()); }); } @@ -187,57 +470,38 @@ base::unique_qptr FillContextMenu( if (list->delegate()->listContext() == Context::Feed) { AddToggleGroupingAction(result, linkPeer->peer()); } - } else { // maybe cursor on some text history item? - bool canDelete = item && item->canDelete() && (item->id > 0 || !item->serviceMsg()); - bool canForward = item && item->allowsForward(); - - const auto msg = item->toHistoryMessage(); - if (!request.overSelection) { - if (item && !hasSelection) { - auto mediaHasTextForCopy = false; - if (auto media = view->media()) { - mediaHasTextForCopy = media->hasTextForCopy(); - if (media->type() == MediaTypeWebPage - && static_cast(media)->attach()) { - media = static_cast(media)->attach(); - } - if (media->type() == MediaTypeSticker) { - if (auto document = media->getDocument()) { - if (document->sticker() && document->sticker()->set.type() != mtpc_inputStickerSetEmpty) { - result->addAction(lang(document->isStickerSetInstalled() ? lng_context_pack_info : lng_context_pack_add), [=] { - ShowStickerPackInfo(document); - }); - } - result->addAction( - lang(lng_context_save_image), - App::LambdaDelayed( - st::defaultDropdownMenu.menu.ripple.hideDuration, - list, - [=] { DocumentSaveClickHandler::doSave(document, true); })); - } - } - } - if (!link && (view->hasVisibleText() || mediaHasTextForCopy)) { - result->addAction(lang(lng_context_copy_text), [=] { - if (const auto item = App::histItemById(itemId)) { - SetClipboardWithEntities(HistoryItemText(item)); - } - }); + } else if (!request.overSelection && view && !hasSelection) { + auto media = view->media(); + const auto mediaHasTextForCopy = media && media->hasTextForCopy(); + if (media) { + if (media->type() == MediaTypeWebPage + && static_cast(media)->attach()) { + media = static_cast(media)->attach(); + } + if (media->type() == MediaTypeSticker) { + if (const auto document = media->getDocument()) { + AddDocumentActions(result, document, view->data()->fullId()); } } } - - const auto actionText = link - ? link->copyToClipboardContextItemText() - : QString(); - if (!actionText.isEmpty()) { - result->addAction( - actionText, - [text = link->copyToClipboardText()] { - QApplication::clipboard()->setText(text); - }); + if (!link && (view->hasVisibleText() || mediaHasTextForCopy)) { + const auto asGroup = (request.pointState != PointState::GroupPart); + result->addAction(lang(lng_context_copy_text), [=] { + if (const auto item = App::histItemById(itemId)) { + if (asGroup) { + if (const auto group = Auth().data().groups().find(item)) { + SetClipboardWithEntities(HistoryGroupText(group)); + return; + } + } + SetClipboardWithEntities(HistoryItemText(item)); + } + }); } } + + AddCopyLinkAction(result, link); + AddMessageActions(result, request, list); return result; } diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.h b/Telegram/SourceFiles/history/view/history_view_context_menu.h index cc919265be..c9b71b7779 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.h +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.h @@ -15,16 +15,20 @@ class PopupMenu; namespace HistoryView { +enum class PointState : char; class ListWidget; class Element; +struct SelectedItem; +using SelectedItems = std::vector; struct ContextMenuRequest { ClickHandlerPtr link; Element *view = nullptr; - MessageIdsList selectedItems; + HistoryItem *item = nullptr; + SelectedItems selectedItems; TextWithEntities selectedText; - bool overView = false; bool overSelection = false; + PointState pointState = PointState(); }; base::unique_qptr FillContextMenu( diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 62e5d4be65..d94b69ea6d 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -715,6 +715,28 @@ void ListWidget::cancelSelection() { clearTextSelection(); } +void ListWidget::selectItem(not_null item) { + if (const auto view = viewForItem(item)) { + clearTextSelection(); + changeSelection( + _selected, + item, + SelectAction::Select); + pushSelectedItems(); + } +} + +void ListWidget::selectItemAsGroup(not_null item) { + if (const auto view = viewForItem(item)) { + clearTextSelection(); + changeSelectionAsGroup( + _selected, + item, + SelectAction::Select); + pushSelectedItems(); + } +} + void ListWidget::clearSelected() { if (_selected.empty()) { return; @@ -1325,13 +1347,14 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { ContextMenuRequest request; request.link = ClickHandler::getActive(); request.view = _overElement; - // #TODO group part context menu using _overItemExact - request.overView = _overElement - && (_overState.pointState != PointState::Outside); + request.item = _overItemExact + ? _overItemExact + : _overElement + ? _overElement->data().get() + : nullptr; + request.pointState = _overState.pointState; request.selectedText = _selectedText; - if (!_selected.empty()) { - request.selectedItems = collectSelectedIds(); - } + request.selectedItems = collectSelectedItems(); request.overSelection = showFromTouch || (_overElement && isInsideSelection( _overElement, diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index 21ae7f8caf..f7f0940db0 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -138,6 +138,8 @@ public: TextWithEntities getSelectedText() const; MessageIdsList getSelectedItems() const; void cancelSelection(); + void selectItem(not_null item); + void selectItemAsGroup(not_null item); // AbstractTooltipShower interface QString tooltipText() const override; diff --git a/Telegram/SourceFiles/ui/twidget.h b/Telegram/SourceFiles/ui/twidget.h index 43810f1c80..63d9657c96 100644 --- a/Telegram/SourceFiles/ui/twidget.h +++ b/Telegram/SourceFiles/ui/twidget.h @@ -442,6 +442,16 @@ QPointer make_weak(const Widget *object) { return QPointer(object); } +template +QPointer make_weak(not_null object) { + return QPointer(object.get()); +} + +template +QPointer make_weak(not_null object) { + return QPointer(object.get()); +} + class SingleQueuedInvokation : public QObject { public: SingleQueuedInvokation(base::lambda callback) : _callback(callback) {