mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-04-01 23:00:58 +00:00
Improve drag selection in HistoryView::ListWidget.
This commit is contained in:
parent
2fdc3169ce
commit
e5f3bed801
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user