diff --git a/Telegram/SourceFiles/history/feed/history_feed_section.cpp b/Telegram/SourceFiles/history/feed/history_feed_section.cpp index 957a58ac2..91b24e3c4 100644 --- a/Telegram/SourceFiles/history/feed/history_feed_section.cpp +++ b/Telegram/SourceFiles/history/feed/history_feed_section.cpp @@ -175,10 +175,14 @@ void Widget::listScrollTo(int top) { } } -void Widget::listCloseRequest() { +void Widget::listCancelRequest() { controller()->showBackFromStack(); } +void Widget::listDeleteRequest() { + confirmDeleteSelected(); +} + rpl::producer Widget::listSource( Data::MessagePosition aroundId, int limitBefore, diff --git a/Telegram/SourceFiles/history/feed/history_feed_section.h b/Telegram/SourceFiles/history/feed/history_feed_section.h index 043835e72..0e2736b37 100644 --- a/Telegram/SourceFiles/history/feed/history_feed_section.h +++ b/Telegram/SourceFiles/history/feed/history_feed_section.h @@ -63,7 +63,8 @@ public: // HistoryView::ListDelegate interface. HistoryView::Context listContext() override; void listScrollTo(int top) override; - void listCloseRequest() override; + void listCancelRequest() override; + void listDeleteRequest() override; rpl::producer listSource( Data::MessagePosition aroundId, int limitBefore, diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 40c3d6d6b..c3f35e098 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -578,11 +578,15 @@ bool ListWidget::isSelectedAsGroup( return applyTo.contains(item->fullId()); } +bool ListWidget::isGoodForSelection(not_null item) const { + return IsServerMsgId(item->id) && !item->serviceMsg(); +} + bool ListWidget::isGoodForSelection( SelectedMap &applyTo, not_null item, int &totalCount) const { - if (!IsServerMsgId(item->id) || item->serviceMsg()) { + if (!isGoodForSelection(item)) { return false; } else if (!applyTo.contains(item->fullId())) { ++totalCount; @@ -980,7 +984,7 @@ TextSelection ListWidget::computeRenderSelection( TextSelection ListWidget::itemRenderSelection( not_null view) const { - if (_dragSelectAction != DragSelectAction::None) { + if (!_dragSelected.empty()) { const auto i = _dragSelected.find(view->data()->fullId()); if (i != _dragSelected.end()) { return (_dragSelectAction == DragSelectAction::Selecting) @@ -1242,7 +1246,11 @@ auto ListWidget::countScrollState() const -> ScrollTopState { void ListWidget::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape || e->key() == Qt::Key_Back) { - _delegate->listCloseRequest(); + if (hasSelectedText() || hasSelectedItems()) { + cancelSelection(); + } else { + _delegate->listCancelRequest(); + } } else if (e == QKeySequence::Copy && (hasSelectedText() || hasSelectedItems())) { SetClipboardWithEntities(getSelectedText()); @@ -1251,6 +1259,8 @@ void ListWidget::keyPressEvent(QKeyEvent *e) { && e->modifiers().testFlag(Qt::ControlModifier)) { SetClipboardWithEntities(getSelectedText(), QClipboard::FindBuffer); #endif // Q_OS_MAC + } else if (e == QKeySequence::Delete) { + _delegate->listDeleteRequest(); } else { e->ignore(); } @@ -1425,26 +1435,94 @@ void ListWidget::updateDragSelection() { const auto selectingUp = _delegate->listIsLessInOrder( overView->data(), pressItem); + if (selectingUp != _dragSelectDirectionUp) { + _dragSelectDirectionUp = selectingUp; + _dragSelectAction = DragSelectAction::None; + } const auto fromView = selectingUp ? overView : pressView; const auto tillView = selectingUp ? pressView : overView; - // #TODO skip-from / skip-till + updateDragSelection( + selectingUp ? overView : pressView, + selectingUp ? _overState : _pressState, + selectingUp ? pressView : overView, + selectingUp ? _pressState : _overState); +} + +void ListWidget::updateDragSelection( + const Element *fromView, + const MouseState &fromState, + const Element *tillView, + const MouseState &tillState) { + Expects(fromView != nullptr || tillView != nullptr); + + const auto delta = QApplication::startDragDistance(); + + const auto includeFrom = [&] ( + not_null view, + const MouseState &state) { + const auto bottom = view->height() - view->marginBottom(); + return (state.point.y() < bottom - delta); + }; + const auto includeTill = [&] ( + not_null view, + const MouseState &state) { + const auto top = view->marginTop(); + return (state.point.y() >= top + delta); + }; + const auto includeSingleItem = [&] ( + not_null view, + const MouseState &state1, + const MouseState &state2) { + const auto top = view->marginTop(); + const auto bottom = view->height() - view->marginBottom(); + const auto y1 = std::min(state1.point.y(), state2.point.y()); + const auto y2 = std::max(state1.point.y(), state2.point.y()); + return (y1 < bottom - delta && y2 >= top + delta) + ? (y2 - y1 >= delta) + : false; + }; + const auto from = [&] { - if (fromView) { - const auto result = ranges::find( - _items, - fromView, - [](auto view) { return view.get(); }); - return (result == end(_items)) ? begin(_items) : result; - } - return begin(_items); + const auto result = fromView ? ranges::find( + _items, + fromView, + [](auto view) { return view.get(); }) : end(_items); + return (result == end(_items)) + ? begin(_items) + : (fromView == tillView || includeFrom(fromView, fromState)) + ? result + : (result + 1); }(); - const auto till = tillView - ? ranges::find( + const auto till = [&] { + if (fromView == tillView) { + return (from == end(_items)) + ? from + : includeSingleItem(fromView, fromState, tillState) + ? (from + 1) + : from; + } + const auto result = tillView ? ranges::find( _items, tillView, - [](auto view) { return view.get(); }) - : end(_items); - Assert(from <= till); + [](auto view) { return view.get(); }) : end(_items); + return (result == end(_items)) + ? end(_items) + : includeTill(tillView, tillState) + ? (result + 1) + : result; + }(); + if (from < till) { + updateDragSelection(from, till); + } else { + clearDragSelection(); + } +} + +void ListWidget::updateDragSelection( + std::vector>::const_iterator from, + std::vector>::const_iterator till) { + Expects(from < till); + const auto &groups = Auth().data().groups(); const auto changeItem = [&](not_null item, bool add) { const auto itemId = item->fullId(); @@ -1456,10 +1534,15 @@ void ListWidget::updateDragSelection() { }; const auto changeGroup = [&](not_null item, bool add) { if (const auto group = groups.find(item)) { + for (const auto item : group->items) { + if (!isGoodForSelection(item)) { + return; + } + } for (const auto item : group->items) { changeItem(item, add); } - } else { + } else if (isGoodForSelection(item)) { changeItem(item, add); } }; @@ -1477,25 +1560,28 @@ void ListWidget::updateDragSelection() { for (auto i = till; i != end(_items); ++i) { changeView(*i, false); } - _dragSelectAction = [&] { - if (_dragSelected.empty()) { - return DragSelectAction::None; - } else if (!pressView) { - return _dragSelectAction; - } - if (_selected.find(pressItem->fullId()) != end(_selected)) { - return DragSelectAction::Deselecting; - } else { - return DragSelectAction::Selecting; - } - }(); + + ensureDragSelectAction(from, till); + update(); +} + +void ListWidget::ensureDragSelectAction( + std::vector>::const_iterator from, + std::vector>::const_iterator till) { + if (_dragSelectAction != DragSelectAction::None) { + return; + } + const auto start = _dragSelectDirectionUp ? (till - 1) : from; + const auto startId = (*start)->data()->fullId(); + _dragSelectAction = _selected.contains(startId) + ? DragSelectAction::Deselecting + : DragSelectAction::Selecting; if (!_wasSelectedText && !_dragSelected.empty() && _dragSelectAction == DragSelectAction::Selecting) { _wasSelectedText = true; setFocus(); } - update(); } void ListWidget::clearDragSelection() { @@ -1571,7 +1657,8 @@ void ListWidget::mouseActionStart( _mouseAction = MouseAction::PrepareDrag; } else { if (dragState.afterSymbol) ++_mouseTextSymbol; - if (!hasSelectedItems()) { + if (!hasSelectedItems() + && _overState.pointState != PointState::Outside) { setTextSelection(pressedView, TextSelection( _mouseTextSymbol, _mouseTextSymbol)); @@ -1627,7 +1714,6 @@ void ListWidget::mouseActionFinish( auto activated = ClickHandler::unpressed(); auto simpleSelectionChange = pressState.itemId - && (pressState.pointState != PointState::Outside) && !_pressWasInactive && (button != Qt::RightButton) && (_mouseAction == MouseAction::PrepareSelect @@ -1700,12 +1786,11 @@ void ListWidget::mouseActionUpdate() { const auto view = strictFindItemByY(point.y()); const auto item = view ? view->data().get() : nullptr; const auto itemPoint = mapPointToItem(point, view); - _overState = MouseState{ + _overState = MouseState( item ? item->fullId() : FullMsgId(), view ? view->height() : 0, itemPoint, - view ? view->pointState(itemPoint) : PointState::Outside - }; + view ? view->pointState(itemPoint) : PointState::Outside); if (_overElement != view) { repaintItem(_overElement); _overElement = view; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index bffbd7d1d..525f29070 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -46,7 +46,8 @@ class ListDelegate { public: virtual Context listContext() = 0; virtual void listScrollTo(int top) = 0; - virtual void listCloseRequest() = 0; + virtual void listCancelRequest() = 0; + virtual void listDeleteRequest() = 0; virtual rpl::producer listSource( Data::MessagePosition aroundId, int limitBefore, @@ -290,6 +291,7 @@ private: //bool applyItemSelection(SelectedMap &applyTo, FullMsgId itemId) const; //void toggleItemSelection(FullMsgId itemId); + bool isGoodForSelection(not_null item) const; bool isGoodForSelection( SelectedMap &applyTo, not_null item, @@ -318,6 +320,17 @@ private: bool requiredToStartDragging(not_null view) const; bool isPressInSelectedText(TextState state) const; void updateDragSelection(); + void updateDragSelection( + const Element *fromView, + const MouseState &fromState, + const Element *tillView, + const MouseState &tillState); + void updateDragSelection( + std::vector>::const_iterator from, + std::vector>::const_iterator till); + void ensureDragSelectAction( + std::vector>::const_iterator from, + std::vector>::const_iterator till); void clearDragSelection(); void applyDragSelection(); void applyDragSelection(SelectedMap &applyTo) const; @@ -401,6 +414,7 @@ private: SelectedMap _selected; base::flat_set _dragSelected; DragSelectAction _dragSelectAction = DragSelectAction::None; + bool _dragSelectDirectionUp = false; // Was some text selected in current drag action. bool _wasSelectedText = false; Qt::CursorShape _cursor = style::cur_default;