diff --git a/CHANGELOG.md b/CHANGELOG.md index 46c36be5d89..4a4874d95a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) - Bugfix: Fixed an issue where notifications on Windows would contain no or an old avatar. (#4899) - Dev: Fixed UTF16 encoding of `modes` file for the installer. (#4791) - Dev: Temporarily disable High DPI scaling on Qt6 builds on Windows. (#4767) diff --git a/src/messages/Selection.hpp b/src/messages/Selection.hpp index 5af3612bc59..b04ad167368 100644 --- a/src/messages/Selection.hpp +++ b/src/messages/Selection.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -72,6 +73,13 @@ struct Selection { return !this->operator==(b); } + //union of both selections + Selection operator|(const Selection &b) const + { + return {std::min(this->selectionMin, b.selectionMin), + std::max(this->selectionMax, b.selectionMax)}; + } + bool isEmpty() const { return this->start == this->end; @@ -127,15 +135,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 diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index de185eb6292..c80b1ae50a8 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -385,6 +385,7 @@ void ChannelView::unpaused() { /// Move selection this->selection_.shiftMessageIndex(this->pauseSelectionOffset_); + this->doubleClickSelection_.shiftMessageIndex(this->pauseSelectionOffset_); this->pauseSelectionOffset_ = 0; } @@ -927,6 +928,7 @@ void ChannelView::messageAppended(MessagePtr &message, this->scrollBar_->scrollToBottom(false); } this->selection_.shiftMessageIndex(1); + this->doubleClickSelection_.shiftMessageIndex(1); } } @@ -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; @@ -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(); @@ -1512,15 +1518,31 @@ 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_) { 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 hoveredWord = Selection{SelectionItem(messageIndex, wordStart), + SelectionItem(messageIndex, wordEnd)}; + // combined selection spanning from initially selected word to hoveredWord + auto selectUnion = this->doubleClickSelection_ | hoveredWord; + + this->setSelection(selectUnion); + } + // message under cursor is collapsed if (layout->flags.has(MessageLayoutFlag::Collapsed)) { @@ -1529,10 +1551,6 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event) return; } - // check if word underneath cursor - const MessageLayoutElement *hoverLayoutElement = - layout->getElementAt(relativePos); - if (hoverLayoutElement == nullptr) { this->setCursor(Qt::ArrowCursor); @@ -1540,142 +1558,6 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event) 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(element); @@ -1950,13 +1832,11 @@ 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; // Was actually not a wanted triple-click - if (fabsf(distanceBetweenPoints(this->lastDClickPosition_, + if (fabsf(distanceBetweenPoints(this->lastDoubleClickPosition_, event->screenPos())) > 10.f) { this->clickTimer_->stop(); @@ -1975,7 +1855,7 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event) // Triple-clicking a message selects the whole message if (foundElement && this->clickTimer_->isActive() && - (fabsf(distanceBetweenPoints(this->lastDClickPosition_, + (fabsf(distanceBetweenPoints(this->lastDoubleClickPosition_, event->screenPos())) < 10.f)) { this->selectWholeMessage(layout.get(), messageIndex); @@ -2597,6 +2477,10 @@ void ChannelView::mouseDoubleClickEvent(QMouseEvent *event) return; } + this->isDoubleClick_ = true; + this->lastDoubleClickPosition_ = event->screenPos(); + this->clickTimer_->start(); + // message under cursor is collapsed if (layout->flags.has(MessageLayoutFlag::Collapsed)) { @@ -2605,38 +2489,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) { @@ -2892,17 +2755,18 @@ 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) +/// @returns [wordStart, wordEnd] position indexes for word hovered by mouse +std::pair ChannelView::getWordBounds( + MessageLayout *layout, const MessageLayoutElement *element, + const QPoint &relativePos) { - const int mouseInWordIndex = element->getMouseOverIndex(relativePos); - wordStart = layout->getSelectionIndex(relativePos) - mouseInWordIndex; - const int selectionLength = element->getSelectionIndexCount(); - const int length = + const auto wordStart = layout->getSelectionIndex(relativePos) - + element->getMouseOverIndex(relativePos); + const auto selectionLength = element->getSelectionIndexCount(); + const auto length = element->hasTrailingSpace() ? selectionLength - 1 : selectionLength; - wordEnd = wordStart + length; + + return {wordStart, wordStart + length}; } void ChannelView::enableScrolling(const QPointF &scrollStart) diff --git a/src/widgets/helper/ChannelView.hpp b/src/widgets/helper/ChannelView.hpp index 27ae83cfe14..88b66ad2186 100644 --- a/src/widgets/helper/ChannelView.hpp +++ b/src/widgets/helper/ChannelView.hpp @@ -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 getWordBounds(MessageLayout *layout, + const MessageLayoutElement *element, + const QPoint &relativePos); void handleMouseClick(QMouseEvent *event, const MessageLayoutElement *hoveredElement, @@ -307,10 +308,9 @@ class ChannelView final : public BaseWidget bool isLeftMouseDown_ = false; bool isRightMouseDown_ = false; bool isDoubleClick_ = false; - DoubleClickSelection doubleClickSelection_; QPointF lastLeftPressPosition_; QPointF lastRightPressPosition_; - QPointF lastDClickPosition_; + QPointF lastDoubleClickPosition_; QTimer *clickTimer_; bool isScrolling_ = false; @@ -330,6 +330,7 @@ class ChannelView final : public BaseWidget } cursors_; Selection selection_; + Selection doubleClickSelection_; const Context context_;