Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: simplify double click selection #4898

Merged
merged 10 commits into from
Oct 17, 2023
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
- Bugfix: Fixed issue on Windows preventing the title bar from being dragged in the top left corner. (#4873)
- Bugfix: Fixed an issue where reply context didn't render correctly if an emoji was touching text. (#4875)
- Bugfix: Fixed the input completion popup from disappearing when clicking on it on Windows and macOS. (#4876)
- Bugfix: Fixed double-click text selection moving its position with each new message. (#4898)
- Dev: Fixed UTF16 encoding of `modes` file for the installer. (#4791)
- Dev: Temporarily disable High DPI scaling on Qt6 builds on Windows. (#4767)
- Dev: Tests now run on Ubuntu 22.04 instead of 20.04 to loosen C++ restrictions in tests. (#4774)
Expand Down
18 changes: 7 additions & 11 deletions src/messages/Selection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ struct Selection {
return !this->operator==(b);
}

//union of both selections
Selection operator|=(const Selection &b) const
kornes marked this conversation as resolved.
Show resolved Hide resolved
{
return {std::min(this->selectionMin, b.selectionMin),
kornes marked this conversation as resolved.
Show resolved Hide resolved
std::max(this->selectionMax, b.selectionMax)};
kornes marked this conversation as resolved.
Show resolved Hide resolved
}

bool isEmpty() const
{
return this->start == this->end;
Expand Down Expand Up @@ -127,15 +134,4 @@ struct Selection {
}
}
};

struct DoubleClickSelection {
uint32_t originalStart{0};
uint32_t originalEnd{0};
uint32_t origMessageIndex{0};
bool selectingLeft{false};
bool selectingRight{false};
SelectionItem origStartItem;
SelectionItem origEndItem;
};

} // namespace chatterino
222 changes: 42 additions & 180 deletions src/widgets/helper/ChannelView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ void ChannelView::unpaused()
{
/// Move selection
this->selection_.shiftMessageIndex(this->pauseSelectionOffset_);
this->doubleClickSelection_.shiftMessageIndex(this->pauseSelectionOffset_);

this->pauseSelectionOffset_ = 0;
}
Expand Down Expand Up @@ -927,6 +928,7 @@ void ChannelView::messageAppended(MessagePtr &message,
this->scrollBar_->scrollToBottom(false);
}
this->selection_.shiftMessageIndex(1);
this->doubleClickSelection_.shiftMessageIndex(1);
}
}

Expand Down Expand Up @@ -1088,10 +1090,8 @@ void ChannelView::resizeEvent(QResizeEvent *)
this->update();
}

void ChannelView::setSelection(const SelectionItem &start,
const SelectionItem &end)
void ChannelView::setSelection(const Selection &newSelection)
{
auto newSelection = Selection(start, end);
if (this->selection_ != newSelection)
{
this->selection_ = newSelection;
Expand All @@ -1100,6 +1100,12 @@ void ChannelView::setSelection(const SelectionItem &start,
}
}

void ChannelView::setSelection(const SelectionItem &start,
const SelectionItem &end)
{
this->setSelection({start, end});
}

MessageElementFlags ChannelView::getFlags() const
{
auto app = getApp();
Expand Down Expand Up @@ -1512,15 +1518,30 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event)
this->currentMousePosition_ = event->screenPos();
}

// is selecting
// check for word underneath cursor
const MessageLayoutElement *hoverLayoutElement =
layout->getElementAt(relativePos);

//selecting single characters
if (this->isLeftMouseDown_)
kornes marked this conversation as resolved.
Show resolved Hide resolved
{
auto index = layout->getSelectionIndex(relativePos);

this->setSelection(this->selection_.start,
SelectionItem(messageIndex, index));
}

//selecting whole words
if (this->isDoubleClick_ && hoverLayoutElement)
{
auto [wordStart, wordEnd] =
this->getWordBounds(layout.get(), hoverLayoutElement, relativePos);
auto wordSelection = Selection{SelectionItem(messageIndex, wordStart),
SelectionItem(messageIndex, wordEnd)};
auto selectUnion = this->doubleClickSelection_ |= wordSelection;
kornes marked this conversation as resolved.
Show resolved Hide resolved

this->setSelection(selectUnion);
}

// message under cursor is collapsed
if (layout->flags.has(MessageLayoutFlag::Collapsed))
{
Expand All @@ -1529,153 +1550,13 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event)
return;
}

// check if word underneath cursor
const MessageLayoutElement *hoverLayoutElement =
layout->getElementAt(relativePos);

if (hoverLayoutElement == nullptr)
{
this->setCursor(Qt::ArrowCursor);
tooltipWidget->hide();
return;
}

if (this->isDoubleClick_)
{
int wordStart;
int wordEnd;
this->getWordBounds(layout.get(), hoverLayoutElement, relativePos,
wordStart, wordEnd);
SelectionItem newStart(messageIndex, wordStart);
SelectionItem newEnd(messageIndex, wordEnd);

// Selection changed in same message
if (messageIndex == this->doubleClickSelection_.origMessageIndex)
{
// Selecting to the left
if (wordStart < this->selection_.start.charIndex &&
!this->doubleClickSelection_.selectingRight)
{
this->doubleClickSelection_.selectingLeft = true;
// Ensure that the original word stays selected(Edge case)
if (wordStart > this->doubleClickSelection_.originalEnd)
{
this->setSelection(
this->doubleClickSelection_.origStartItem, newEnd);
}
else
{
this->setSelection(newStart, this->selection_.end);
}
// Selecting to the right
}
else if (wordEnd > this->selection_.end.charIndex &&
!this->doubleClickSelection_.selectingLeft)
{
this->doubleClickSelection_.selectingRight = true;
// Ensure that the original word stays selected(Edge case)
if (wordEnd < this->doubleClickSelection_.originalStart)
{
this->setSelection(newStart,
this->doubleClickSelection_.origEndItem);
}
else
{
this->setSelection(this->selection_.start, newEnd);
}
}
// Swapping from selecting left to selecting right
if (wordStart > this->selection_.start.charIndex &&
!this->doubleClickSelection_.selectingRight)
{
if (wordStart > this->doubleClickSelection_.originalEnd)
{
this->doubleClickSelection_.selectingLeft = false;
this->doubleClickSelection_.selectingRight = true;
this->setSelection(
this->doubleClickSelection_.origStartItem, newEnd);
}
else
{
this->setSelection(newStart, this->selection_.end);
}
// Swapping from selecting right to selecting left
}
else if (wordEnd < this->selection_.end.charIndex &&
!this->doubleClickSelection_.selectingLeft)
{
if (wordEnd < this->doubleClickSelection_.originalStart)
{
this->doubleClickSelection_.selectingLeft = true;
this->doubleClickSelection_.selectingRight = false;
this->setSelection(newStart,
this->doubleClickSelection_.origEndItem);
}
else
{
this->setSelection(this->selection_.start, newEnd);
}
}
// Selection changed in a different message
}
else
{
// Message over the original
if (messageIndex < this->selection_.start.messageIndex)
{
// Swapping from left to right selecting
if (!this->doubleClickSelection_.selectingLeft)
{
this->doubleClickSelection_.selectingLeft = true;
this->doubleClickSelection_.selectingRight = false;
}
if (wordStart < this->selection_.start.charIndex &&
!this->doubleClickSelection_.selectingRight)
{
this->doubleClickSelection_.selectingLeft = true;
}
this->setSelection(newStart,
this->doubleClickSelection_.origEndItem);
// Message under the original
}
else if (messageIndex > this->selection_.end.messageIndex)
{
// Swapping from right to left selecting
if (!this->doubleClickSelection_.selectingRight)
{
this->doubleClickSelection_.selectingLeft = false;
this->doubleClickSelection_.selectingRight = true;
}
if (wordEnd > this->selection_.end.charIndex &&
!this->doubleClickSelection_.selectingLeft)
{
this->doubleClickSelection_.selectingRight = true;
}
this->setSelection(this->doubleClickSelection_.origStartItem,
newEnd);
// Selection changed in non original message
}
else
{
if (this->doubleClickSelection_.selectingLeft)
{
this->setSelection(newStart, this->selection_.end);
}
else
{
this->setSelection(this->selection_.start, newEnd);
}
}
}
// Reset direction of selection
if (wordStart == this->doubleClickSelection_.originalStart &&
wordEnd == this->doubleClickSelection_.originalEnd)
{
this->doubleClickSelection_.selectingLeft =
this->doubleClickSelection_.selectingRight = false;
}
}

auto element = &hoverLayoutElement->getCreator();
bool isLinkValid = hoverLayoutElement->getLink().isValid();
auto emoteElement = dynamic_cast<const EmoteElement *>(element);
Expand Down Expand Up @@ -1950,8 +1831,6 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event)
// check if mouse was pressed
if (event->button() == Qt::LeftButton)
{
this->doubleClickSelection_.selectingLeft =
this->doubleClickSelection_.selectingRight = false;
if (this->isDoubleClick_)
{
this->isDoubleClick_ = false;
Expand Down Expand Up @@ -2597,6 +2476,10 @@ void ChannelView::mouseDoubleClickEvent(QMouseEvent *event)
return;
}

this->isDoubleClick_ = true;
this->lastDClickPosition_ = event->screenPos();
this->clickTimer_->start();

// message under cursor is collapsed
if (layout->flags.has(MessageLayoutFlag::Collapsed))
{
Expand All @@ -2605,38 +2488,17 @@ void ChannelView::mouseDoubleClickEvent(QMouseEvent *event)

const MessageLayoutElement *hoverLayoutElement =
layout->getElementAt(relativePos);
this->lastDClickPosition_ = event->screenPos();

if (hoverLayoutElement == nullptr)
{
// Possibility for triple click which doesn't have to be over an
// existing layout element
this->clickTimer_->start();
return;
}

if (!this->isLeftMouseDown_)
{
this->isDoubleClick_ = true;

int wordStart;
int wordEnd;
this->getWordBounds(layout.get(), hoverLayoutElement, relativePos,
wordStart, wordEnd);

this->clickTimer_->start();

SelectionItem wordMin(messageIndex, wordStart);
SelectionItem wordMax(messageIndex, wordEnd);

this->doubleClickSelection_.originalStart = wordStart;
this->doubleClickSelection_.originalEnd = wordEnd;
this->doubleClickSelection_.origMessageIndex = messageIndex;
this->doubleClickSelection_.origStartItem = wordMin;
this->doubleClickSelection_.origEndItem = wordMax;

this->setSelection(wordMin, wordMax);
}
auto [wordStart, wordEnd] =
this->getWordBounds(layout.get(), hoverLayoutElement, relativePos);
this->doubleClickSelection_ = {SelectionItem(messageIndex, wordStart),
SelectionItem(messageIndex, wordEnd)};
this->setSelection(this->doubleClickSelection_);

if (getSettings()->linksDoubleClickOnly)
{
Expand Down Expand Up @@ -2892,17 +2754,17 @@ void ChannelView::selectWholeMessage(MessageLayout *layout, int &messageIndex)
this->setSelection(msgStart, msgEnd);
}

void ChannelView::getWordBounds(MessageLayout *layout,
const MessageLayoutElement *element,
const QPoint &relativePos, int &wordStart,
int &wordEnd)
std::pair<size_t, size_t> ChannelView::getWordBounds(
kornes marked this conversation as resolved.
Show resolved Hide resolved
MessageLayout *layout, const MessageLayoutElement *element,
const QPoint &relativePos)
{
const int mouseInWordIndex = element->getMouseOverIndex(relativePos);
wordStart = layout->getSelectionIndex(relativePos) - mouseInWordIndex;
const int wordStart = layout->getSelectionIndex(relativePos) -
kornes marked this conversation as resolved.
Show resolved Hide resolved
element->getMouseOverIndex(relativePos);
const int selectionLength = element->getSelectionIndexCount();
const int length =
element->hasTrailingSpace() ? selectionLength - 1 : selectionLength;
wordEnd = wordStart + length;

return {wordStart, wordStart + length};
}

void ChannelView::enableScrolling(const QPointF &scrollStart)
Expand Down
9 changes: 5 additions & 4 deletions src/widgets/helper/ChannelView.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,11 @@ class ChannelView final : public BaseWidget

void drawMessages(QPainter &painter);
void setSelection(const SelectionItem &start, const SelectionItem &end);
void setSelection(const Selection &newSelection);
void selectWholeMessage(MessageLayout *layout, int &messageIndex);
void getWordBounds(MessageLayout *layout,
const MessageLayoutElement *element,
const QPoint &relativePos, int &wordStart, int &wordEnd);
std::pair<size_t, size_t> getWordBounds(MessageLayout *layout,
kornes marked this conversation as resolved.
Show resolved Hide resolved
const MessageLayoutElement *element,
const QPoint &relativePos);

void handleMouseClick(QMouseEvent *event,
const MessageLayoutElement *hoveredElement,
Expand Down Expand Up @@ -307,7 +308,7 @@ class ChannelView final : public BaseWidget
bool isLeftMouseDown_ = false;
bool isRightMouseDown_ = false;
bool isDoubleClick_ = false;
DoubleClickSelection doubleClickSelection_;
Selection doubleClickSelection_{};
kornes marked this conversation as resolved.
Show resolved Hide resolved
QPointF lastLeftPressPosition_;
QPointF lastRightPressPosition_;
QPointF lastDClickPosition_;
Expand Down
Loading