diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 56c899f4d0..9254dad2bd 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -956,9 +956,9 @@ void PeerListContent::mousePressReleased(Qt::MouseButton button) { } void PeerListContent::contextMenuEvent(QContextMenuEvent *e) { - if (_menu) { - _menu->deleteLater(); - _menu = nullptr; + if (_contextMenu) { + _contextMenu->deleteLater(); + _contextMenu = nullptr; } setContexted(Selected()); if (e->reason() == QContextMenuEvent::Mouse) { @@ -971,15 +971,15 @@ void PeerListContent::contextMenuEvent(QContextMenuEvent *e) { } if (auto row = getRow(_contexted.index)) { - _menu = _controller->rowContextMenu(row); - if (_menu) { - _menu->setDestroyedCallback(base::lambda_guarded( + _contextMenu = _controller->rowContextMenu(row); + if (_contextMenu) { + _contextMenu->setDestroyedCallback(base::lambda_guarded( this, [this] { setContexted(Selected()); handleMouseMove(QCursor::pos()); })); - _menu->popup(e->globalPos()); + _contextMenu->popup(e->globalPos()); e->accept(); } } diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index 22192e01bf..a60e9ba7ae 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -604,7 +604,7 @@ private: std::vector> _searchRows; base::Timer _repaintByStatus; - QPointer _menu; + QPointer _contextMenu; }; diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp index 8b1728c6b5..894198e4bd 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp +++ b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp @@ -26,11 +26,17 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "window/themes/window_theme.h" #include "window/window_controller.h" #include "storage/file_download.h" +#include "ui/widgets/popup_menu.h" #include "lang/lang_keys.h" #include "auth_session.h" +#include "mainwidget.h" #include "window/main_window.h" #include "styles/style_overview.h" #include "styles/style_info.h" +#include "boxes/peer_list_box.h" +#include "boxes/confirm_box.h" +#include "info/info_top_bar_override.h" +#include "core/file_utilities.h" namespace Layout = Overview::Layout; @@ -634,6 +640,19 @@ auto ListWidget::collectSelectedItems() const -> SelectedItems { return items; } +SelectedItemSet ListWidget::collectSelectedSet() const { + auto items = SelectedItemSet(); + if (hasSelectedItems()) { + for (auto &data : _selected) { + auto fullId = computeFullId(data.first); + if (auto item = App::histItemById(fullId)) { + items.insert(items.size(), item); + } + } + } + return items; +} + void ListWidget::pushSelectedItems() { _selectedListStream.fire(collectSelectedItems()); } @@ -1061,6 +1080,260 @@ void ListWidget::mouseDoubleClickEvent(QMouseEvent *e) { trySwitchToWordSelection(); } +void ListWidget::showContextMenu( + QContextMenuEvent *e, + ContextMenuSource source) { + if (_contextMenu) { + _contextMenu->deleteLater(); + _contextMenu = nullptr; + repaintItem(_contextUniversalId); + } + if (e->reason() == QContextMenuEvent::Mouse) { + mouseActionUpdate(e->globalPos()); + } + + auto item = App::histItemById(computeFullId(_overState.itemId)); + if (!item || !_overState.inside) { + return; + } + auto universalId = _contextUniversalId = _overState.itemId; + + enum class SelectionState { + NoSelectedItems, + NotOverSelectedItems, + OverSelectedItems, + NotOverSelectedText, + OverSelectedText, + }; + auto overSelected = SelectionState::NoSelectedItems; + if (source == ContextMenuSource::Touch) { + if (hasSelectedItems()) { + overSelected = SelectionState::OverSelectedItems; + } else if (hasSelectedText()) { + overSelected = SelectionState::OverSelectedItems; + } + } else if (hasSelectedText()) { + // #TODO text selection + } else if (hasSelectedItems()) { + auto it = _selected.find(_overState.itemId); + if (isSelectedItem(it) && _overState.inside) { + overSelected = SelectionState::OverSelectedItems; + } else { + overSelected = SelectionState::NotOverSelectedItems; + } + } + + auto canDeleteAll = [&] { + return base::find_if(_selected, [](auto &item) { + return !item.second.canDelete; + }) == _selected.end(); + }; + auto canForwardAll = [&] { + return base::find_if(_selected, [](auto &item) { + return !item.second.canForward; + }) == _selected.end(); + }; + + auto link = ClickHandler::getActive(); + + _contextMenu = new Ui::PopupMenu(nullptr); + _contextMenu->addAction( + lang(lng_context_to_msg), + [itemFullId = item->fullId()] { + if (auto item = App::histItemById(itemFullId)) { + Ui::showPeerHistoryAtItem(item); + } + }); + + auto photoLink = dynamic_cast(link.data()); + auto fileLink = dynamic_cast(link.data()); + if (photoLink || fileLink) { + auto [isVideo, isVoice, isSong] = [&] { + if (fileLink) { + auto document = fileLink->document(); + return std::make_tuple( + document->isVideo(), + (document->voice() != nullptr), + (document->song() != nullptr) + ); + } + return std::make_tuple(false, false, false); + }(); + + if (photoLink) { + } else { + if (auto document = fileLink->document()) { + if (document->loading()) { + _contextMenu->addAction( + lang(lng_context_cancel_download), + [document] { + document->cancel(); + }); + } else { + auto filepath = document->filepath(DocumentData::FilePathResolveChecked); + if (!filepath.isEmpty()) { + auto handler = App::LambdaDelayed( + st::defaultDropdownMenu.menu.ripple.hideDuration, + this, + [filepath] { + File::ShowInFolder(filepath); + }); + _contextMenu->addAction( + lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) + ? lng_context_show_in_finder + : lng_context_show_in_folder), + std::move(handler)); + } + auto handler = App::LambdaDelayed( + st::defaultDropdownMenu.menu.ripple.hideDuration, + this, + [document] { + DocumentSaveClickHandler::doSave(document, true); + }); + _contextMenu->addAction( + lang(isVideo + ? lng_context_save_video + : isVoice + ? lng_context_save_audio + : isSong + ? lng_context_save_audio_file + : lng_context_save_file), + std::move(handler)); + } + } + } + } else if (link) { + auto linkCopyToClipboardText + = link->copyToClipboardContextItemText(); + if (!linkCopyToClipboardText.isEmpty()) { + _contextMenu->addAction( + linkCopyToClipboardText, + [link] { + link->copyToClipboard(); + }); + } + } + if (overSelected == SelectionState::OverSelectedItems) { + if (canForwardAll()) { + _contextMenu->addAction( + lang(lng_context_forward_selected), + base::lambda_guarded(this, [this] { + forwardSelected(); + })); + } + if (canDeleteAll()) { + _contextMenu->addAction( + lang(lng_context_delete_selected), + base::lambda_guarded(this, [this] { + deleteSelected(); + })); + } + _contextMenu->addAction( + lang(lng_context_clear_selection), + base::lambda_guarded(this, [this] { + clearSelected(); + })); + } else { + if (overSelected != SelectionState::NotOverSelectedItems) { + if (item->canForward()) { + _contextMenu->addAction( + lang(lng_context_forward_msg), + base::lambda_guarded(this, [this, universalId] { + forwardItem(universalId); + })); + } + if (item->canDelete()) { + _contextMenu->addAction( + lang(lng_context_delete_msg), + base::lambda_guarded(this, [this, universalId] { + deleteItem(universalId); + })); + } + } + _contextMenu->addAction( + lang(lng_context_select_msg), + base::lambda_guarded(this, [this, universalId] { + if (hasSelectedText()) { + clearSelected(); + } else if (_selected.size() == MaxSelectedItems) { + return; + } else if (_selected.empty()) { + update(); + } + applyItemSelection(universalId, FullSelection); + })); + } + + _contextMenu->setDestroyedCallback(base::lambda_guarded( + this, + [this, universalId] { + _contextMenu = nullptr; + mouseActionUpdate(QCursor::pos()); + repaintItem(universalId); + })); + _contextMenu->popup(e->globalPos()); + e->accept(); +} + +void ListWidget::contextMenuEvent(QContextMenuEvent *e) { + showContextMenu( + e, + (e->reason() == QContextMenuEvent::Mouse) + ? ContextMenuSource::Mouse + : ContextMenuSource::Other); +} + +void ListWidget::forwardSelected() { + forwardItems(collectSelectedSet()); +} + +void ListWidget::forwardItem(UniversalMsgId universalId) { + if (auto item = App::histItemById(computeFullId(universalId))) { + auto items = SelectedItemSet(); + items.insert(0, item); + forwardItems(std::move(items)); + } +} + +void ListWidget::forwardItems(SelectedItemSet items) { + if (items.empty()) { + return; + } + auto that = weak(this); + auto controller = std::make_unique( + [that, items = std::move(items)](not_null peer) { + App::main()->setForwardDraft(peer->id, items); + if (that) { + that->clearSelected(); + } + }); + Ui::show(Box( + std::move(controller), + [](not_null box) { + box->addButton(langFactory(lng_cancel), [box] { + box->closeBox(); + }); + })); +} + +void ListWidget::deleteSelected() { + deleteItems(collectSelectedSet()); +} + +void ListWidget::deleteItem(UniversalMsgId universalId) { + if (auto item = App::histItemById(computeFullId(universalId))) { + auto items = SelectedItemSet(); + items.insert(0, item); + deleteItems(std::move(items)); + } +} + +void ListWidget::deleteItems(SelectedItemSet items) { + if (!items.empty()) { + Ui::show(Box(items)); + } +} + void ListWidget::trySwitchToWordSelection() { auto selectingSome = (_mouseAction == MouseAction::Selecting) && hasSelectedText(); diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.h b/Telegram/SourceFiles/info/media/info_media_list_widget.h index cfa0ccd744..92fa24ecd1 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_widget.h +++ b/Telegram/SourceFiles/info/media/info_media_list_widget.h @@ -87,6 +87,7 @@ protected: void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseDoubleClickEvent(QMouseEvent *e) override; + void contextMenuEvent(QContextMenuEvent *e) override; void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; @@ -144,6 +145,11 @@ private: } }; + enum class ContextMenuSource { + Mouse, + Touch, + Other, + }; void start(); int recountHeight(); @@ -173,6 +179,7 @@ private: Type type); SelectedItems collectSelectedItems() const; + SelectedItemSet collectSelectedSet() const; void pushSelectedItems(); FullMsgId computeFullId(UniversalMsgId universalId) const; bool hasSelected() const; @@ -183,6 +190,12 @@ private: bool hasSelectedText() const; bool hasSelectedItems() const; void clearSelected(); + void forwardSelected(); + void forwardItem(UniversalMsgId universalId); + void forwardItems(SelectedItemSet items); + void deleteSelected(); + void deleteItem(UniversalMsgId universalId); + void deleteItems(SelectedItemSet items); void applyItemSelection( UniversalMsgId universalId, TextSelection selection); @@ -238,6 +251,9 @@ private: void mouseActionCancel(); void performDrag(); style::cursor computeMouseCursor() const; + void showContextMenu( + QContextMenuEvent *e, + ContextMenuSource source); void updateDragSelection(); void clearDragSelection(); @@ -271,6 +287,7 @@ private: CursorState _overState; CursorState _pressState; BaseLayout *_overLayout = nullptr; + UniversalMsgId _contextUniversalId = 0; HistoryCursorState _mouseCursorState = HistoryDefaultCursorState; uint16 _mouseTextSymbol = 0; bool _pressWasInactive = false; @@ -281,7 +298,6 @@ private: DragSelectAction _dragSelectAction = DragSelectAction::None; bool _wasSelectedText = false; // was some text selected in current drag action Ui::PopupMenu *_contextMenu = nullptr; - ClickHandlerPtr _contextMenuLink; QPoint _trippleClickPoint; TimeMs _trippleClickStartTime = 0; diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp index d8de2cedeb..1108b4c9b4 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp @@ -334,6 +334,9 @@ void Cover::refreshStatusText() { refreshStatusGeometry(width()); } +Cover::~Cover() { +} + void Cover::refreshNameGeometry(int newWidth) { auto nameWidth = newWidth - st::infoProfileNameLeft diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.h b/Telegram/SourceFiles/info/profile/info_profile_cover.h index 2b0bd7206f..e1c54cee27 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.h +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.h @@ -70,6 +70,8 @@ public: SectionWithToggle::setToggleShown(std::move(shown))); } + ~Cover(); + private: void setupChildGeometry(); void initViewers();