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();
}
void Widget::listDeleteRequest() {
confirmDeleteSelected();
}
rpl::producer<Data::MessagesSlice> Widget::listSource(
Data::MessagePosition aroundId,
int limitBefore,

View file

@ -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<Data::MessagesSlice> listSource(
Data::MessagePosition aroundId,
int limitBefore,

View file

@ -578,11 +578,15 @@ bool ListWidget::isSelectedAsGroup(
return applyTo.contains(item->fullId());
}
bool ListWidget::isGoodForSelection(not_null<HistoryItem*> item) const {
return IsServerMsgId(item->id) && !item->serviceMsg();
}
bool ListWidget::isGoodForSelection(
SelectedMap &applyTo,
not_null<HistoryItem*> 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<const Element*> 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<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 = [&] {
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<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 changeItem = [&](not_null<HistoryItem*> item, bool add) {
const auto itemId = item->fullId();
@ -1456,10 +1534,15 @@ void ListWidget::updateDragSelection() {
};
const auto changeGroup = [&](not_null<HistoryItem*> 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<not_null<Element*>>::const_iterator from,
std::vector<not_null<Element*>>::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;

View file

@ -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<Data::MessagesSlice> 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<HistoryItem*> item) const;
bool isGoodForSelection(
SelectedMap &applyTo,
not_null<HistoryItem*> item,
@ -318,6 +320,17 @@ private:
bool requiredToStartDragging(not_null<Element*> 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<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 applyDragSelection();
void applyDragSelection(SelectedMap &applyTo) const;
@ -401,6 +414,7 @@ private:
SelectedMap _selected;
base::flat_set<FullMsgId> _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;