Improve drag selection in HistoryView::ListWidget.

This commit is contained in:
John Preston 2018-01-27 19:41:06 +03:00
parent 2fdc3169ce
commit e5f3bed801
4 changed files with 143 additions and 39 deletions

View File

@ -175,10 +175,14 @@ void Widget::listScrollTo(int top) {
} }
} }
void Widget::listCloseRequest() { void Widget::listCancelRequest() {
controller()->showBackFromStack(); controller()->showBackFromStack();
} }
void Widget::listDeleteRequest() {
confirmDeleteSelected();
}
rpl::producer<Data::MessagesSlice> Widget::listSource( rpl::producer<Data::MessagesSlice> Widget::listSource(
Data::MessagePosition aroundId, Data::MessagePosition aroundId,
int limitBefore, int limitBefore,

View File

@ -63,7 +63,8 @@ public:
// HistoryView::ListDelegate interface. // HistoryView::ListDelegate interface.
HistoryView::Context listContext() override; HistoryView::Context listContext() override;
void listScrollTo(int top) override; void listScrollTo(int top) override;
void listCloseRequest() override; void listCancelRequest() override;
void listDeleteRequest() override;
rpl::producer<Data::MessagesSlice> listSource( rpl::producer<Data::MessagesSlice> listSource(
Data::MessagePosition aroundId, Data::MessagePosition aroundId,
int limitBefore, int limitBefore,

View File

@ -578,11 +578,15 @@ bool ListWidget::isSelectedAsGroup(
return applyTo.contains(item->fullId()); return applyTo.contains(item->fullId());
} }
bool ListWidget::isGoodForSelection(not_null<HistoryItem*> item) const {
return IsServerMsgId(item->id) && !item->serviceMsg();
}
bool ListWidget::isGoodForSelection( bool ListWidget::isGoodForSelection(
SelectedMap &applyTo, SelectedMap &applyTo,
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
int &totalCount) const { int &totalCount) const {
if (!IsServerMsgId(item->id) || item->serviceMsg()) { if (!isGoodForSelection(item)) {
return false; return false;
} else if (!applyTo.contains(item->fullId())) { } else if (!applyTo.contains(item->fullId())) {
++totalCount; ++totalCount;
@ -980,7 +984,7 @@ TextSelection ListWidget::computeRenderSelection(
TextSelection ListWidget::itemRenderSelection( TextSelection ListWidget::itemRenderSelection(
not_null<const Element*> view) const { not_null<const Element*> view) const {
if (_dragSelectAction != DragSelectAction::None) { if (!_dragSelected.empty()) {
const auto i = _dragSelected.find(view->data()->fullId()); const auto i = _dragSelected.find(view->data()->fullId());
if (i != _dragSelected.end()) { if (i != _dragSelected.end()) {
return (_dragSelectAction == DragSelectAction::Selecting) return (_dragSelectAction == DragSelectAction::Selecting)
@ -1242,7 +1246,11 @@ auto ListWidget::countScrollState() const -> ScrollTopState {
void ListWidget::keyPressEvent(QKeyEvent *e) { void ListWidget::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Escape || e->key() == Qt::Key_Back) { 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 } else if (e == QKeySequence::Copy
&& (hasSelectedText() || hasSelectedItems())) { && (hasSelectedText() || hasSelectedItems())) {
SetClipboardWithEntities(getSelectedText()); SetClipboardWithEntities(getSelectedText());
@ -1251,6 +1259,8 @@ void ListWidget::keyPressEvent(QKeyEvent *e) {
&& e->modifiers().testFlag(Qt::ControlModifier)) { && e->modifiers().testFlag(Qt::ControlModifier)) {
SetClipboardWithEntities(getSelectedText(), QClipboard::FindBuffer); SetClipboardWithEntities(getSelectedText(), QClipboard::FindBuffer);
#endif // Q_OS_MAC #endif // Q_OS_MAC
} else if (e == QKeySequence::Delete) {
_delegate->listDeleteRequest();
} else { } else {
e->ignore(); e->ignore();
} }
@ -1425,26 +1435,94 @@ void ListWidget::updateDragSelection() {
const auto selectingUp = _delegate->listIsLessInOrder( const auto selectingUp = _delegate->listIsLessInOrder(
overView->data(), overView->data(),
pressItem); pressItem);
if (selectingUp != _dragSelectDirectionUp) {
_dragSelectDirectionUp = selectingUp;
_dragSelectAction = DragSelectAction::None;
}
const auto fromView = selectingUp ? overView : pressView; const auto fromView = selectingUp ? overView : pressView;
const auto tillView = selectingUp ? pressView : overView; 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<const Element*> view,
const MouseState &state) {
const auto bottom = view->height() - view->marginBottom();
return (state.point.y() < bottom - delta);
};
const auto includeTill = [&] (
not_null<const Element*> view,
const MouseState &state) {
const auto top = view->marginTop();
return (state.point.y() >= top + delta);
};
const auto includeSingleItem = [&] (
not_null<const Element*> 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 = [&] { const auto from = [&] {
if (fromView) { const auto result = fromView ? ranges::find(
const auto result = ranges::find( _items,
_items, fromView,
fromView, [](auto view) { return view.get(); }) : end(_items);
[](auto view) { return view.get(); }); return (result == end(_items))
return (result == end(_items)) ? begin(_items) : result; ? begin(_items)
} : (fromView == tillView || includeFrom(fromView, fromState))
return begin(_items); ? result
: (result + 1);
}(); }();
const auto till = tillView const auto till = [&] {
? ranges::find( if (fromView == tillView) {
return (from == end(_items))
? from
: includeSingleItem(fromView, fromState, tillState)
? (from + 1)
: from;
}
const auto result = tillView ? ranges::find(
_items, _items,
tillView, tillView,
[](auto view) { return view.get(); }) [](auto view) { return view.get(); }) : end(_items);
: end(_items); return (result == end(_items))
Assert(from <= till); ? end(_items)
: includeTill(tillView, tillState)
? (result + 1)
: result;
}();
if (from < till) {
updateDragSelection(from, till);
} else {
clearDragSelection();
}
}
void ListWidget::updateDragSelection(
std::vector<not_null<Element*>>::const_iterator from,
std::vector<not_null<Element*>>::const_iterator till) {
Expects(from < till);
const auto &groups = Auth().data().groups(); const auto &groups = Auth().data().groups();
const auto changeItem = [&](not_null<HistoryItem*> item, bool add) { const auto changeItem = [&](not_null<HistoryItem*> item, bool add) {
const auto itemId = item->fullId(); const auto itemId = item->fullId();
@ -1456,10 +1534,15 @@ void ListWidget::updateDragSelection() {
}; };
const auto changeGroup = [&](not_null<HistoryItem*> item, bool add) { const auto changeGroup = [&](not_null<HistoryItem*> item, bool add) {
if (const auto group = groups.find(item)) { if (const auto group = groups.find(item)) {
for (const auto item : group->items) {
if (!isGoodForSelection(item)) {
return;
}
}
for (const auto item : group->items) { for (const auto item : group->items) {
changeItem(item, add); changeItem(item, add);
} }
} else { } else if (isGoodForSelection(item)) {
changeItem(item, add); changeItem(item, add);
} }
}; };
@ -1477,25 +1560,28 @@ void ListWidget::updateDragSelection() {
for (auto i = till; i != end(_items); ++i) { for (auto i = till; i != end(_items); ++i) {
changeView(*i, false); changeView(*i, false);
} }
_dragSelectAction = [&] {
if (_dragSelected.empty()) { ensureDragSelectAction(from, till);
return DragSelectAction::None; update();
} else if (!pressView) { }
return _dragSelectAction;
} void ListWidget::ensureDragSelectAction(
if (_selected.find(pressItem->fullId()) != end(_selected)) { std::vector<not_null<Element*>>::const_iterator from,
return DragSelectAction::Deselecting; std::vector<not_null<Element*>>::const_iterator till) {
} else { if (_dragSelectAction != DragSelectAction::None) {
return DragSelectAction::Selecting; return;
} }
}(); const auto start = _dragSelectDirectionUp ? (till - 1) : from;
const auto startId = (*start)->data()->fullId();
_dragSelectAction = _selected.contains(startId)
? DragSelectAction::Deselecting
: DragSelectAction::Selecting;
if (!_wasSelectedText if (!_wasSelectedText
&& !_dragSelected.empty() && !_dragSelected.empty()
&& _dragSelectAction == DragSelectAction::Selecting) { && _dragSelectAction == DragSelectAction::Selecting) {
_wasSelectedText = true; _wasSelectedText = true;
setFocus(); setFocus();
} }
update();
} }
void ListWidget::clearDragSelection() { void ListWidget::clearDragSelection() {
@ -1571,7 +1657,8 @@ void ListWidget::mouseActionStart(
_mouseAction = MouseAction::PrepareDrag; _mouseAction = MouseAction::PrepareDrag;
} else { } else {
if (dragState.afterSymbol) ++_mouseTextSymbol; if (dragState.afterSymbol) ++_mouseTextSymbol;
if (!hasSelectedItems()) { if (!hasSelectedItems()
&& _overState.pointState != PointState::Outside) {
setTextSelection(pressedView, TextSelection( setTextSelection(pressedView, TextSelection(
_mouseTextSymbol, _mouseTextSymbol,
_mouseTextSymbol)); _mouseTextSymbol));
@ -1627,7 +1714,6 @@ void ListWidget::mouseActionFinish(
auto activated = ClickHandler::unpressed(); auto activated = ClickHandler::unpressed();
auto simpleSelectionChange = pressState.itemId auto simpleSelectionChange = pressState.itemId
&& (pressState.pointState != PointState::Outside)
&& !_pressWasInactive && !_pressWasInactive
&& (button != Qt::RightButton) && (button != Qt::RightButton)
&& (_mouseAction == MouseAction::PrepareSelect && (_mouseAction == MouseAction::PrepareSelect
@ -1700,12 +1786,11 @@ void ListWidget::mouseActionUpdate() {
const auto view = strictFindItemByY(point.y()); const auto view = strictFindItemByY(point.y());
const auto item = view ? view->data().get() : nullptr; const auto item = view ? view->data().get() : nullptr;
const auto itemPoint = mapPointToItem(point, view); const auto itemPoint = mapPointToItem(point, view);
_overState = MouseState{ _overState = MouseState(
item ? item->fullId() : FullMsgId(), item ? item->fullId() : FullMsgId(),
view ? view->height() : 0, view ? view->height() : 0,
itemPoint, itemPoint,
view ? view->pointState(itemPoint) : PointState::Outside view ? view->pointState(itemPoint) : PointState::Outside);
};
if (_overElement != view) { if (_overElement != view) {
repaintItem(_overElement); repaintItem(_overElement);
_overElement = view; _overElement = view;

View File

@ -46,7 +46,8 @@ class ListDelegate {
public: public:
virtual Context listContext() = 0; virtual Context listContext() = 0;
virtual void listScrollTo(int top) = 0; virtual void listScrollTo(int top) = 0;
virtual void listCloseRequest() = 0; virtual void listCancelRequest() = 0;
virtual void listDeleteRequest() = 0;
virtual rpl::producer<Data::MessagesSlice> listSource( virtual rpl::producer<Data::MessagesSlice> listSource(
Data::MessagePosition aroundId, Data::MessagePosition aroundId,
int limitBefore, int limitBefore,
@ -290,6 +291,7 @@ private:
//bool applyItemSelection(SelectedMap &applyTo, FullMsgId itemId) const; //bool applyItemSelection(SelectedMap &applyTo, FullMsgId itemId) const;
//void toggleItemSelection(FullMsgId itemId); //void toggleItemSelection(FullMsgId itemId);
bool isGoodForSelection(not_null<HistoryItem*> item) const;
bool isGoodForSelection( bool isGoodForSelection(
SelectedMap &applyTo, SelectedMap &applyTo,
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
@ -318,6 +320,17 @@ private:
bool requiredToStartDragging(not_null<Element*> view) const; bool requiredToStartDragging(not_null<Element*> view) const;
bool isPressInSelectedText(TextState state) const; bool isPressInSelectedText(TextState state) const;
void updateDragSelection(); void updateDragSelection();
void updateDragSelection(
const Element *fromView,
const MouseState &fromState,
const Element *tillView,
const MouseState &tillState);
void updateDragSelection(
std::vector<not_null<Element*>>::const_iterator from,
std::vector<not_null<Element*>>::const_iterator till);
void ensureDragSelectAction(
std::vector<not_null<Element*>>::const_iterator from,
std::vector<not_null<Element*>>::const_iterator till);
void clearDragSelection(); void clearDragSelection();
void applyDragSelection(); void applyDragSelection();
void applyDragSelection(SelectedMap &applyTo) const; void applyDragSelection(SelectedMap &applyTo) const;
@ -401,6 +414,7 @@ private:
SelectedMap _selected; SelectedMap _selected;
base::flat_set<FullMsgId> _dragSelected; base::flat_set<FullMsgId> _dragSelected;
DragSelectAction _dragSelectAction = DragSelectAction::None; DragSelectAction _dragSelectAction = DragSelectAction::None;
bool _dragSelectDirectionUp = false;
// Was some text selected in current drag action. // Was some text selected in current drag action.
bool _wasSelectedText = false; bool _wasSelectedText = false;
Qt::CursorShape _cursor = style::cur_default; Qt::CursorShape _cursor = style::cur_default;