From 6860c7007e76471a5f965ebec2434e1434bb72b7 Mon Sep 17 00:00:00 2001 From: nerix Date: Sat, 23 Sep 2023 17:09:56 +0200 Subject: [PATCH] Fix selection rendering (#4830) The rendering of selections was not aligned to the actual selection that took place for newlines at the end of messages, if they were the only part that was selected of that message. In addition to that fix, we've already refactored the MessageLayoutContainer to try to make it a little bit more sane to work with in the future. Co-authored-by: Rasmus Karlsson --- CHANGELOG.md | 2 +- src/messages/Selection.hpp | 9 +- src/messages/layouts/MessageLayout.cpp | 10 +- src/messages/layouts/MessageLayout.hpp | 20 +- .../layouts/MessageLayoutContainer.cpp | 1145 +++++++++-------- .../layouts/MessageLayoutContainer.hpp | 298 +++-- src/messages/layouts/MessageLayoutElement.cpp | 24 +- src/messages/layouts/MessageLayoutElement.hpp | 33 +- src/widgets/helper/ChannelView.cpp | 2 +- 9 files changed, 886 insertions(+), 657 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60a293e8d..c6a57398c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ - Bugfix: Fixed selection of tabs after closing a tab when using "Live Tabs Only". (#4770) - Bugfix: Fixed input in reply thread popup losing focus when dragging. (#4815) - Bugfix: Fixed the Quick Switcher (CTRL+K) from sometimes showing up on the wrong window. (#4819) -- Bugfix: Fixed too much text being copied when copying chat messages. (#4812) +- Bugfix: Fixed too much text being copied when copying chat messages. (#4812, #4830) - 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) diff --git a/src/messages/Selection.hpp b/src/messages/Selection.hpp index 8c879c0ae..6563ff9e2 100644 --- a/src/messages/Selection.hpp +++ b/src/messages/Selection.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -7,12 +8,12 @@ namespace chatterino { struct SelectionItem { - uint32_t messageIndex{0}; - uint32_t charIndex{0}; + size_t messageIndex{0}; + size_t charIndex{0}; SelectionItem() = default; - SelectionItem(uint32_t _messageIndex, uint32_t _charIndex) + SelectionItem(size_t _messageIndex, size_t _charIndex) : messageIndex(_messageIndex) , charIndex(_charIndex) { @@ -73,7 +74,7 @@ struct Selection { } // Shift all message selection indices `offset` back - void shiftMessageIndex(uint32_t offset) + void shiftMessageIndex(size_t offset) { if (offset > this->selectionMin.messageIndex) { diff --git a/src/messages/layouts/MessageLayout.cpp b/src/messages/layouts/MessageLayout.cpp index e5b180091..a2d961bdd 100644 --- a/src/messages/layouts/MessageLayout.cpp +++ b/src/messages/layouts/MessageLayout.cpp @@ -143,7 +143,7 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags flags) bool hideSimilar = getSettings()->hideSimilar; bool hideReplies = !flags.has(MessageElementFlag::RepliedMessage); - this->container_.begin(width, this->scale_, messageFlags); + this->container_.beginLayout(width, this->scale_, messageFlags); for (const auto &element : this->message_->elements) { @@ -184,7 +184,7 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags flags) this->deleteBuffer(); } - this->container_.end(); + this->container_.endLayout(); this->height_ = this->container_.getHeight(); // collapsed state @@ -424,17 +424,17 @@ const MessageLayoutElement *MessageLayout::getElementAt(QPoint point) return this->container_.getElementAt(point); } -int MessageLayout::getLastCharacterIndex() const +size_t MessageLayout::getLastCharacterIndex() const { return this->container_.getLastCharacterIndex(); } -int MessageLayout::getFirstMessageCharacterIndex() const +size_t MessageLayout::getFirstMessageCharacterIndex() const { return this->container_.getFirstMessageCharacterIndex(); } -int MessageLayout::getSelectionIndex(QPoint position) +size_t MessageLayout::getSelectionIndex(QPoint position) const { return this->container_.getSelectionIndex(position); } diff --git a/src/messages/layouts/MessageLayout.hpp b/src/messages/layouts/MessageLayout.hpp index b5d47ba15..c4f0796a2 100644 --- a/src/messages/layouts/MessageLayout.hpp +++ b/src/messages/layouts/MessageLayout.hpp @@ -62,9 +62,23 @@ public: // Elements const MessageLayoutElement *getElementAt(QPoint point); - int getLastCharacterIndex() const; - int getFirstMessageCharacterIndex() const; - int getSelectionIndex(QPoint position); + + /** + * Get the index of the last character in this message's container + * This is the sum of all the characters in `elements_` + */ + size_t getLastCharacterIndex() const; + + /** + * Get the index of the first visible character in this message's container + * This is not always 0 in case there elements that are skipped + */ + size_t getFirstMessageCharacterIndex() const; + + /** + * Get the character index at the given position, in the context of selections + */ + size_t getSelectionIndex(QPoint position) const; void addSelectionText(QString &str, uint32_t from = 0, uint32_t to = UINT32_MAX, CopyMode copymode = CopyMode::Everything); diff --git a/src/messages/layouts/MessageLayoutContainer.cpp b/src/messages/layouts/MessageLayoutContainer.cpp index efef94767..efb59cc36 100644 --- a/src/messages/layouts/MessageLayoutContainer.cpp +++ b/src/messages/layouts/MessageLayoutContainer.cpp @@ -12,34 +12,38 @@ #include "util/Helpers.hpp" #include +#include #include +#include + #define COMPACT_EMOTES_OFFSET 4 #define MAX_UNCOLLAPSED_LINES \ (getSettings()->collpseMessagesMinLines.getValue()) +namespace { + +constexpr const QMargins MARGIN{4, 8, 4, 8}; + +} // namespace + namespace chatterino { -int MessageLayoutContainer::getHeight() const +void MessageLayoutContainer::beginLayout(int width, float scale, + MessageFlags flags) { - return this->height_; -} + this->elements_.clear(); + this->lines_.clear(); -int MessageLayoutContainer::getWidth() const -{ - return this->width_; -} + this->line_ = 0; + this->currentX_ = 0; + this->currentY_ = 0; + this->lineStart_ = 0; + this->lineHeight_ = 0; + this->charIndex_ = 0; -float MessageLayoutContainer::getScale() const -{ - return this->scale_; -} - -// methods -void MessageLayoutContainer::begin(int width, float scale, MessageFlags flags) -{ - this->clear(); this->width_ = width; + this->height_ = 0; this->scale_ = scale; this->flags_ = flags; auto mediumFontMetrics = @@ -52,18 +56,53 @@ void MessageLayoutContainer::begin(int width, float scale, MessageFlags flags) this->wasPrevReversed_ = false; } -void MessageLayoutContainer::clear() +void MessageLayoutContainer::endLayout() { - this->elements_.clear(); - this->lines_.clear(); + if (!this->canAddElements()) + { + static TextElement dotdotdot("...", MessageElementFlag::Collapsed, + MessageColor::Link); + static QString dotdotdotText("..."); - this->height_ = 0; - this->line_ = 0; - this->currentX_ = 0; - this->currentY_ = 0; - this->lineStart_ = 0; - this->lineHeight_ = 0; - this->charIndex_ = 0; + auto *element = new TextLayoutElement( + dotdotdot, dotdotdotText, + QSize(this->dotdotdotWidth_, this->textLineHeight_), + QColor("#00D80A"), FontStyle::ChatMediumBold, this->scale_); + + if (this->first == FirstWord::RTL) + { + // Shift all elements in the next line to the left + for (auto i = this->lines_.back().startIndex; + i < this->elements_.size(); i++) + { + QPoint prevPos = this->elements_[i]->getRect().topLeft(); + this->elements_[i]->setPosition( + QPoint(prevPos.x() + this->dotdotdotWidth_, prevPos.y())); + } + } + this->addElement(element, true, -2); + this->isCollapsed_ = true; + } + + if (!this->atStartOfLine()) + { + this->breakLine(); + } + + this->height_ += this->lineHeight_; + + if (!this->lines_.empty()) + { + this->lines_[0].rect.setTop(-100000); + this->lines_.back().rect.setBottom(100000); + this->lines_.back().endIndex = this->elements_.size(); + this->lines_.back().endCharIndex = this->charIndex_; + } + + if (!this->elements_.empty()) + { + this->elements_.back()->setTrailingSpace(false); + } } void MessageLayoutContainer::addElement(MessageLayoutElement *element) @@ -73,22 +112,398 @@ void MessageLayoutContainer::addElement(MessageLayoutElement *element) this->breakLine(); } - this->_addElement(element); + this->addElement(element, false, -2); } void MessageLayoutContainer::addElementNoLineBreak( MessageLayoutElement *element) { - this->_addElement(element); + this->addElement(element, false, -2); } -bool MessageLayoutContainer::canAddElements() const +void MessageLayoutContainer::breakLine() { - return this->canAddMessages_; + if (this->containsRTL) + { + for (int i = 0; i < this->elements_.size(); i++) + { + if (this->elements_[i]->getFlags().has( + MessageElementFlag::Username)) + { + this->reorderRTL(i + 1); + break; + } + } + } + + int xOffset = 0; + + if (this->flags_.has(MessageFlag::Centered) && this->elements_.size() > 0) + { + const int marginOffset = int(MARGIN.left() * this->scale_) + + int(MARGIN.right() * this->scale_); + xOffset = (width_ - marginOffset - + this->elements_.at(this->elements_.size() - 1) + ->getRect() + .right()) / + 2; + } + + for (size_t i = lineStart_; i < this->elements_.size(); i++) + { + MessageLayoutElement *element = this->elements_.at(i).get(); + + bool isCompactEmote = + !this->flags_.has(MessageFlag::DisableCompactEmotes) && + element->getCreator().getFlags().has( + MessageElementFlag::EmoteImages); + + int yExtra = 0; + if (isCompactEmote) + { + yExtra = (COMPACT_EMOTES_OFFSET / 2) * this->scale_; + } + + element->setPosition( + QPoint(element->getRect().x() + xOffset + + int(MARGIN.left() * this->scale_), + element->getRect().y() + this->lineHeight_ + yExtra)); + } + + if (!this->lines_.empty()) + { + this->lines_.back().endIndex = this->lineStart_; + this->lines_.back().endCharIndex = this->charIndex_; + } + this->lines_.push_back({ + .startIndex = lineStart_, + .endIndex = 0, + .startCharIndex = this->charIndex_, + .endCharIndex = 0, + .rect = QRect(-100000, this->currentY_, 200000, lineHeight_), + }); + + for (auto i = this->lineStart_; i < this->elements_.size(); i++) + { + this->charIndex_ += this->elements_[i]->getSelectionIndexCount(); + } + + this->lineStart_ = this->elements_.size(); + // this->currentX = (int)(this->scale * 8); + + if (this->canCollapse() && this->line_ + 1 >= MAX_UNCOLLAPSED_LINES) + { + this->canAddMessages_ = false; + return; + } + + this->currentX_ = 0; + this->currentY_ += this->lineHeight_; + this->height_ = this->currentY_ + int(MARGIN.bottom() * this->scale_); + this->lineHeight_ = 0; + this->line_++; } -void MessageLayoutContainer::_addElement(MessageLayoutElement *element, - bool forceAdd, int prevIndex) +void MessageLayoutContainer::paintElements(QPainter &painter, + const MessagePaintContext &ctx) const +{ +#ifdef FOURTF + static constexpr std::array lineColors{ + QColor{255, 0, 0, 60}, // RED + QColor{0, 255, 0, 60}, // GREEN + QColor{0, 0, 255, 60}, // BLUE + QColor{255, 0, 255, 60}, // PINk + QColor{0, 255, 255, 60}, // CYAN + }; + + int lineNum = 0; + for (const auto &line : this->lines_) + { + const auto &color = lineColors[lineNum++ % 5]; + painter.fillRect(line.rect, color); + } +#endif + + for (const auto &element : this->elements_) + { +#ifdef FOURTF + painter.setPen(QColor(0, 255, 0)); + painter.drawRect(element->getRect()); +#endif + + element->paint(painter, ctx.messageColors); + } +} + +void MessageLayoutContainer::paintAnimatedElements(QPainter &painter, + int yOffset) const +{ + for (const auto &element : this->elements_) + { + element->paintAnimated(painter, yOffset); + } +} + +void MessageLayoutContainer::paintSelection(QPainter &painter, + const size_t messageIndex, + const Selection &selection, + const int yOffset) const +{ + if (selection.selectionMin.messageIndex > messageIndex || + selection.selectionMax.messageIndex < messageIndex) + { + // This message is not part of the selection, don't draw anything + return; + } + + const auto selectionColor = getTheme()->messages.selection; + + if (selection.selectionMin.messageIndex < messageIndex && + selection.selectionMax.messageIndex > messageIndex) + { + // The selection fully covers this message + // Paint all lines completely + + for (const Line &line : this->lines_) + { + // Fully paint a selection rectangle over all lines + auto left = this->elements_[line.startIndex]->getRect().left(); + auto right = this->elements_[line.endIndex - 1]->getRect().right(); + this->paintSelectionRect(painter, line, left, right, yOffset, + selectionColor); + } + + return; + } + + size_t lineIndex = 0; + + if (selection.selectionMin.messageIndex == messageIndex) + { + auto oLineIndex = this->paintSelectionStart(painter, messageIndex, + selection, yOffset); + + if (!oLineIndex) + { + // There's no more selection to be drawn in this message + return; + } + + // There's further selection to be painted in this message + lineIndex = *oLineIndex; + } + + // Paint the selection starting at lineIndex + this->paintSelectionEnd(painter, lineIndex, selection, yOffset); +} + +void MessageLayoutContainer::addSelectionText(QString &str, uint32_t from, + uint32_t to, + CopyMode copymode) const +{ + uint32_t index = 0; + bool first = true; + + for (const auto &element : this->elements_) + { + if (copymode != CopyMode::Everything && + element->getCreator().getFlags().has( + MessageElementFlag::RepliedMessage)) + { + // Don't include the message being replied to + continue; + } + + if (copymode == CopyMode::OnlyTextAndEmotes) + { + if (element->getCreator().getFlags().hasAny( + {MessageElementFlag::Timestamp, + MessageElementFlag::Username, MessageElementFlag::Badges})) + { + continue; + } + } + + auto indexCount = element->getSelectionIndexCount(); + + if (first) + { + if (index + indexCount > from) + { + element->addCopyTextToString(str, from - index, to - index); + first = false; + + if (index + indexCount >= to) + { + break; + } + } + } + else + { + if (index + indexCount >= to) + { + element->addCopyTextToString(str, 0, to - index); + break; + } + + element->addCopyTextToString(str); + } + + index += indexCount; + } +} + +MessageLayoutElement *MessageLayoutContainer::getElementAt(QPoint point) const +{ + for (const auto &element : this->elements_) + { + if (element->getRect().contains(point)) + { + return element.get(); + } + } + + return nullptr; +} + +size_t MessageLayoutContainer::getSelectionIndex(QPoint point) const +{ + if (this->elements_.empty()) + { + return 0; + } + + auto line = this->lines_.begin(); + + for (; line != this->lines_.end(); line++) + { + if (line->rect.contains(point)) + { + break; + } + } + + auto lineStart = line == this->lines_.end() ? this->lines_.back().startIndex + : line->startIndex; + if (line != this->lines_.end()) + { + line++; + } + auto lineEnd = + line == this->lines_.end() ? this->elements_.size() : line->startIndex; + + size_t index = 0; + + for (auto i = 0; i < lineEnd; i++) + { + auto &&element = this->elements_[i]; + + // end of line + if (i == lineEnd) + { + break; + } + + // before line + if (i < lineStart) + { + index += element->getSelectionIndexCount(); + continue; + } + + // this is the word + auto rightMargin = element->hasTrailingSpace() ? this->spaceWidth_ : 0; + + if (point.x() <= element->getRect().right() + rightMargin) + { + index += element->getMouseOverIndex(point); + break; + } + + index += element->getSelectionIndexCount(); + } + + return index; +} + +size_t MessageLayoutContainer::getFirstMessageCharacterIndex() const +{ + static const FlagsEnum skippedFlags{ + MessageElementFlag::RepliedMessage, + MessageElementFlag::Timestamp, + MessageElementFlag::Badges, + MessageElementFlag::Username, + }; + + // Get the index of the first character of the real message + size_t index = 0; + for (const auto &element : this->elements_) + { + if (element->getFlags().hasAny(skippedFlags)) + { + index += element->getSelectionIndexCount(); + } + else + { + break; + } + } + return index; +} + +size_t MessageLayoutContainer::getLastCharacterIndex() const +{ + if (this->lines_.empty()) + { + return 0; + } + + return this->lines_.back().endCharIndex; +} + +int MessageLayoutContainer::getWidth() const +{ + return this->width_; +} + +int MessageLayoutContainer::getHeight() const +{ + return this->height_; +} + +float MessageLayoutContainer::getScale() const +{ + return this->scale_; +} + +bool MessageLayoutContainer::isCollapsed() const +{ + return this->isCollapsed_; +} + +bool MessageLayoutContainer::atStartOfLine() const +{ + return this->lineStart_ == this->elements_.size(); +} + +bool MessageLayoutContainer::fitsInLine(int width) const +{ + return width <= this->remainingWidth(); +} + +int MessageLayoutContainer::remainingWidth() const +{ + return (this->width_ - int(MARGIN.left() * this->scale_) - + int(MARGIN.right() * this->scale_) - + (this->line_ + 1 == MAX_UNCOLLAPSED_LINES ? this->dotdotdotWidth_ + : 0)) - + this->currentX_; +} + +void MessageLayoutContainer::addElement(MessageLayoutElement *element, + const bool forceAdd, + const int prevIndex) { if (!this->canAddElements() && !forceAdd) { @@ -154,7 +569,7 @@ void MessageLayoutContainer::_addElement(MessageLayoutElement *element, // top margin if (this->elements_.empty()) { - this->currentY_ = int(this->margin.top * this->scale_); + this->currentY_ = int(MARGIN.top() * this->scale_); } int elementLineHeight = element->getRect().height(); @@ -180,7 +595,7 @@ void MessageLayoutContainer::_addElement(MessageLayoutElement *element, element->getCreator().getFlags().hasNone( {MessageElementFlag::TwitchEmoteImage})) { - yOffset -= (this->margin.top * this->scale_); + yOffset -= (MARGIN.top() * this->scale_); } if (getSettings()->removeSpacesBetweenEmotes && @@ -328,379 +743,165 @@ void MessageLayoutContainer::reorderRTL(int firstTextIndex) // manually do the first call with -1 as previous index if (this->canAddElements()) { - this->_addElement(this->elements_[correctSequence[0]].get(), false, -1); + this->addElement(this->elements_[correctSequence[0]].get(), false, -1); } for (int i = 1; i < correctSequence.size() && this->canAddElements(); i++) { - this->_addElement(this->elements_[correctSequence[i]].get(), false, - correctSequence[i - 1]); + this->addElement(this->elements_[correctSequence[i]].get(), false, + correctSequence[i - 1]); } } -void MessageLayoutContainer::breakLine() +void MessageLayoutContainer::paintSelectionRect(QPainter &painter, + const Line &line, + const int left, const int right, + const int yOffset, + const QColor &color) const { - if (this->containsRTL) + QRect rect = line.rect; + + rect.setTop(std::max(0, rect.top()) + yOffset); + rect.setBottom(std::min(this->height_, rect.bottom()) + yOffset); + rect.setLeft(left); + rect.setRight(right); + + painter.fillRect(rect, color); +} + +std::optional MessageLayoutContainer::paintSelectionStart( + QPainter &painter, const size_t messageIndex, const Selection &selection, + const int yOffset) const +{ + const auto selectionColor = getTheme()->messages.selection; + + // The selection starts in this message + for (size_t lineIndex = 0; lineIndex < this->lines_.size(); lineIndex++) { - for (int i = 0; i < this->elements_.size(); i++) + const Line &line = this->lines_[lineIndex]; + + // Selection doesn't start in this line + if (selection.selectionMin.charIndex >= line.endCharIndex) { - if (this->elements_[i]->getFlags().has( - MessageElementFlag::Username)) - { - this->reorderRTL(i + 1); - break; - } - } - } - - int xOffset = 0; - - if (this->flags_.has(MessageFlag::Centered) && this->elements_.size() > 0) - { - const int marginOffset = int(this->margin.left * this->scale_) + - int(this->margin.right * this->scale_); - xOffset = (width_ - marginOffset - - this->elements_.at(this->elements_.size() - 1) - ->getRect() - .right()) / - 2; - } - - for (size_t i = lineStart_; i < this->elements_.size(); i++) - { - MessageLayoutElement *element = this->elements_.at(i).get(); - - bool isCompactEmote = - !this->flags_.has(MessageFlag::DisableCompactEmotes) && - element->getCreator().getFlags().has( - MessageElementFlag::EmoteImages); - - int yExtra = 0; - if (isCompactEmote) - { - yExtra = (COMPACT_EMOTES_OFFSET / 2) * this->scale_; - } - - element->setPosition( - QPoint(element->getRect().x() + xOffset + - int(this->margin.left * this->scale_), - element->getRect().y() + this->lineHeight_ + yExtra)); - } - - if (!this->lines_.empty()) - { - this->lines_.back().endIndex = this->lineStart_; - this->lines_.back().endCharIndex = this->charIndex_; - } - this->lines_.push_back( - {(int)lineStart_, 0, this->charIndex_, 0, - QRect(-100000, this->currentY_, 200000, lineHeight_)}); - - for (auto i = this->lineStart_; i < this->elements_.size(); i++) - { - this->charIndex_ += this->elements_[i]->getSelectionIndexCount(); - } - - this->lineStart_ = this->elements_.size(); - // this->currentX = (int)(this->scale * 8); - - if (this->canCollapse() && line_ + 1 >= MAX_UNCOLLAPSED_LINES) - { - this->canAddMessages_ = false; - return; - } - - this->currentX_ = 0; - this->currentY_ += this->lineHeight_; - this->height_ = this->currentY_ + int(this->margin.bottom * this->scale_); - this->lineHeight_ = 0; - this->line_++; -} - -bool MessageLayoutContainer::atStartOfLine() -{ - return this->lineStart_ == this->elements_.size(); -} - -bool MessageLayoutContainer::fitsInLine(int _width) -{ - return _width <= this->remainingWidth(); -} - -int MessageLayoutContainer::remainingWidth() const -{ - return (this->width_ - int(this->margin.left * this->scale_) - - int(this->margin.right * this->scale_) - - (this->line_ + 1 == MAX_UNCOLLAPSED_LINES ? this->dotdotdotWidth_ - : 0)) - - this->currentX_; -} - -void MessageLayoutContainer::end() -{ - if (!this->canAddElements()) - { - static TextElement dotdotdot("...", MessageElementFlag::Collapsed, - MessageColor::Link); - static QString dotdotdotText("..."); - - auto *element = new TextLayoutElement( - dotdotdot, dotdotdotText, - QSize(this->dotdotdotWidth_, this->textLineHeight_), - QColor("#00D80A"), FontStyle::ChatMediumBold, this->scale_); - - if (this->first == FirstWord::RTL) - { - // Shift all elements in the next line to the left - for (int i = this->lines_.back().startIndex; - i < this->elements_.size(); i++) - { - QPoint prevPos = this->elements_[i]->getRect().topLeft(); - this->elements_[i]->setPosition( - QPoint(prevPos.x() + this->dotdotdotWidth_, prevPos.y())); - } - } - this->_addElement(element, true); - this->isCollapsed_ = true; - } - - if (!this->atStartOfLine()) - { - this->breakLine(); - } - - this->height_ += this->lineHeight_; - - if (!this->lines_.empty()) - { - this->lines_[0].rect.setTop(-100000); - this->lines_.back().rect.setBottom(100000); - this->lines_.back().endIndex = this->elements_.size(); - this->lines_.back().endCharIndex = this->charIndex_; - } - - if (!this->elements_.empty()) - { - this->elements_.back()->setTrailingSpace(false); - } -} - -bool MessageLayoutContainer::canCollapse() -{ - return getSettings()->collpseMessagesMinLines.getValue() > 0 && - this->flags_.has(MessageFlag::Collapsed); -} - -bool MessageLayoutContainer::isCollapsed() const -{ - return this->isCollapsed_; -} - -MessageLayoutElement *MessageLayoutContainer::getElementAt(QPoint point) -{ - for (std::unique_ptr &element : this->elements_) - { - if (element->getRect().contains(point)) - { - return element.get(); - } - } - - return nullptr; -} - -// painting -void MessageLayoutContainer::paintElements(QPainter &painter, - const MessagePaintContext &ctx) -{ - for (const std::unique_ptr &element : this->elements_) - { -#ifdef FOURTF - painter.setPen(QColor(0, 255, 0)); - painter.drawRect(element->getRect()); -#endif - - element->paint(painter, ctx.messageColors); - } -} - -void MessageLayoutContainer::paintAnimatedElements(QPainter &painter, - int yOffset) -{ - for (const std::unique_ptr &element : this->elements_) - { - element->paintAnimated(painter, yOffset); - } -} - -void MessageLayoutContainer::paintSelection(QPainter &painter, - size_t messageIndex, - const Selection &selection, - int yOffset) -{ - auto *app = getApp(); - QColor selectionColor = app->themes->messages.selection; - - // don't draw anything - if (selection.selectionMin.messageIndex > messageIndex || - selection.selectionMax.messageIndex < messageIndex) - { - return; - } - - // fully selected - if (selection.selectionMin.messageIndex < messageIndex && - selection.selectionMax.messageIndex > messageIndex) - { - for (Line &line : this->lines_) - { - QRect rect = line.rect; - - rect.setTop(std::max(0, rect.top()) + yOffset); - rect.setBottom(std::min(this->height_, rect.bottom()) + yOffset); - rect.setLeft(this->elements_[line.startIndex]->getRect().left()); - rect.setRight( - this->elements_[line.endIndex - 1]->getRect().right()); - - painter.fillRect(rect, selectionColor); - } - return; - } - - int lineIndex = 0; - int index = 0; - - // start in this message - if (selection.selectionMin.messageIndex == messageIndex) - { - for (; lineIndex < this->lines_.size(); lineIndex++) - { - Line &line = this->lines_[lineIndex]; - index = line.startCharIndex; - - bool returnAfter = false; - bool breakAfter = false; - int x = this->elements_[line.startIndex]->getRect().left(); - int r = this->elements_[line.endIndex - 1]->getRect().right(); - - if (line.endCharIndex <= selection.selectionMin.charIndex) - { - continue; - } - - for (int i = line.startIndex; i < line.endIndex; i++) - { - int c = this->elements_[i]->getSelectionIndexCount(); - - if (index + c > selection.selectionMin.charIndex) - { - x = this->elements_[i]->getXFromIndex( - selection.selectionMin.charIndex - index); - - // ends in same line - if (selection.selectionMax.messageIndex == messageIndex && - line.endCharIndex > - /*=*/selection.selectionMax.charIndex) - { - returnAfter = true; - index = line.startCharIndex; - for (int i = line.startIndex; i < line.endIndex; i++) - { - int c = - this->elements_[i]->getSelectionIndexCount(); - - if (index + c > selection.selectionMax.charIndex) - { - r = this->elements_[i]->getXFromIndex( - selection.selectionMax.charIndex - index); - break; - } - index += c; - } - } - // ends in same line end - - if (selection.selectionMax.messageIndex != messageIndex) - { - int lineIndex2 = lineIndex + 1; - for (; lineIndex2 < this->lines_.size(); lineIndex2++) - { - Line &line2 = this->lines_[lineIndex2]; - QRect rect = line2.rect; - - rect.setTop(std::max(0, rect.top()) + yOffset); - rect.setBottom( - std::min(this->height_, rect.bottom()) + - yOffset); - rect.setLeft(this->elements_[line2.startIndex] - ->getRect() - .left()); - rect.setRight(this->elements_[line2.endIndex - 1] - ->getRect() - .right()); - - painter.fillRect(rect, selectionColor); - } - returnAfter = true; - } - else - { - lineIndex++; - breakAfter = true; - } - - break; - } - index += c; - } - - QRect rect = line.rect; - - rect.setTop(std::max(0, rect.top()) + yOffset); - rect.setBottom(std::min(this->height_, rect.bottom()) + yOffset); - rect.setLeft(x); - rect.setRight(r); - - painter.fillRect(rect, selectionColor); - - if (returnAfter) - { - return; - } - - if (breakAfter) - { - break; - } - } - } - - // start in this message - for (; lineIndex < this->lines_.size(); lineIndex++) - { - Line &line = this->lines_[lineIndex]; - index = line.startCharIndex; - - // just draw the garbage - if (line.endCharIndex < /*=*/selection.selectionMax.charIndex) - { - QRect rect = line.rect; - - rect.setTop(std::max(0, rect.top()) + yOffset); - rect.setBottom(std::min(this->height_, rect.bottom()) + yOffset); - rect.setLeft(this->elements_[line.startIndex]->getRect().left()); - rect.setRight( - this->elements_[line.endIndex - 1]->getRect().right()); - - painter.fillRect(rect, selectionColor); continue; } + if (selection.selectionMin.charIndex == line.endCharIndex - 1) + { + // Selection starts at the trailing newline + // NOTE: Should this be included in the selection? Right now this is + // painted since it's included in the copy action, but if it's trimmed we + // should stop painting this + auto right = this->elements_[line.endIndex - 1]->getRect().right(); + this->paintSelectionRect(painter, line, right, right, yOffset, + selectionColor); + return std::nullopt; + } + + int x = this->elements_[line.startIndex]->getRect().left(); int r = this->elements_[line.endIndex - 1]->getRect().right(); - for (int i = line.startIndex; i < line.endIndex; i++) + auto index = line.startCharIndex; + for (auto i = line.startIndex; i < line.endIndex; i++) { - int c = this->elements_[i]->getSelectionIndexCount(); + auto indexCount = this->elements_[i]->getSelectionIndexCount(); + if (index + indexCount <= selection.selectionMin.charIndex) + { + index += indexCount; + continue; + } + + x = this->elements_[i]->getXFromIndex( + selection.selectionMin.charIndex - index); + + if (selection.selectionMax.messageIndex == messageIndex && + selection.selectionMax.charIndex < line.endCharIndex) + { + // The selection ends in the same line it started + index = line.startCharIndex; + for (auto elementIdx = line.startIndex; + elementIdx < line.endIndex; elementIdx++) + { + auto c = + this->elements_[elementIdx]->getSelectionIndexCount(); + + if (index + c > selection.selectionMax.charIndex) + { + r = this->elements_[elementIdx]->getXFromIndex( + selection.selectionMax.charIndex - index); + break; + } + index += c; + } + + this->paintSelectionRect(painter, line, x, r, yOffset, + selectionColor); + + return std::nullopt; + } + + // doesn't end in this message -> paint the following lines of this message + if (selection.selectionMax.messageIndex != messageIndex) + { + // The selection does not end in this message + for (size_t lineIndex2 = lineIndex + 1; + lineIndex2 < this->lines_.size(); lineIndex2++) + { + const auto &line2 = this->lines_[lineIndex2]; + auto left = + this->elements_[line2.startIndex]->getRect().left(); + auto right = + this->elements_[line2.endIndex - 1]->getRect().right(); + + this->paintSelectionRect(painter, line2, left, right, + yOffset, selectionColor); + } + + this->paintSelectionRect(painter, line, x, r, yOffset, + selectionColor); + + return std::nullopt; + } + + // The selection starts in this line, but ends in some next line or message + this->paintSelectionRect(painter, line, x, r, yOffset, + selectionColor); + + return {++lineIndex}; + } + } + + return std::nullopt; +} + +void MessageLayoutContainer::paintSelectionEnd(QPainter &painter, + size_t lineIndex, + const Selection &selection, + const int yOffset) const +{ + const auto selectionColor = getTheme()->messages.selection; + // [2] selection contains or ends in this message (starts before our message or line) + for (; lineIndex < this->lines_.size(); lineIndex++) + { + const Line &line = this->lines_[lineIndex]; + size_t index = line.startCharIndex; + + // the whole line is included + if (line.endCharIndex < selection.selectionMax.charIndex) + { + auto left = this->elements_[line.startIndex]->getRect().left(); + auto right = this->elements_[line.endIndex - 1]->getRect().right(); + this->paintSelectionRect(painter, line, left, right, yOffset, + selectionColor); + continue; + } + + // find the right end of the selection + int r = this->elements_[line.endIndex - 1]->getRect().right(); + + for (auto i = line.startIndex; i < line.endIndex; i++) + { + size_t c = this->elements_[i]->getSelectionIndexCount(); if (index + c > selection.selectionMax.charIndex) { @@ -712,167 +913,23 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, index += c; } - QRect rect = line.rect; + auto left = this->elements_[line.startIndex]->getRect().left(); + this->paintSelectionRect(painter, line, left, r, yOffset, + selectionColor); - rect.setTop(std::max(0, rect.top()) + yOffset); - rect.setBottom(std::min(this->height_, rect.bottom()) + yOffset); - rect.setLeft(this->elements_[line.startIndex]->getRect().left()); - rect.setRight(r); - - painter.fillRect(rect, selectionColor); - break; + return; } } -// selection -int MessageLayoutContainer::getSelectionIndex(QPoint point) +bool MessageLayoutContainer::canAddElements() const { - if (this->elements_.empty()) - { - return 0; - } - - auto line = this->lines_.begin(); - - for (; line != this->lines_.end(); line++) - { - if (line->rect.contains(point)) - { - break; - } - } - - int lineStart = line == this->lines_.end() ? this->lines_.back().startIndex - : line->startIndex; - if (line != this->lines_.end()) - { - line++; - } - int lineEnd = - line == this->lines_.end() ? this->elements_.size() : line->startIndex; - - int index = 0; - - for (int i = 0; i < lineEnd; i++) - { - auto &&element = this->elements_[i]; - - // end of line - if (i == lineEnd) - { - break; - } - - // before line - if (i < lineStart) - { - index += element->getSelectionIndexCount(); - continue; - } - - // this is the word - auto rightMargin = element->hasTrailingSpace() ? this->spaceWidth_ : 0; - - if (point.x() <= element->getRect().right() + rightMargin) - { - index += element->getMouseOverIndex(point); - break; - } - - index += element->getSelectionIndexCount(); - } - - return index; + return this->canAddMessages_; } -// fourtf: no idea if this is acurate LOL -int MessageLayoutContainer::getLastCharacterIndex() const +bool MessageLayoutContainer::canCollapse() const { - if (this->lines_.empty()) - { - return 0; - } - return this->lines_.back().endCharIndex; -} - -int MessageLayoutContainer::getFirstMessageCharacterIndex() const -{ - static FlagsEnum skippedFlags; - skippedFlags.set(MessageElementFlag::RepliedMessage); - skippedFlags.set(MessageElementFlag::Timestamp); - skippedFlags.set(MessageElementFlag::Badges); - skippedFlags.set(MessageElementFlag::Username); - - // Get the index of the first character of the real message - int index = 0; - for (const auto &element : this->elements_) - { - if (element->getFlags().hasAny(skippedFlags)) - { - index += element->getSelectionIndexCount(); - } - else - { - break; - } - } - return index; -} - -void MessageLayoutContainer::addSelectionText(QString &str, uint32_t from, - uint32_t to, CopyMode copymode) -{ - uint32_t index = 0; - bool first = true; - - for (auto &element : this->elements_) - { - if (copymode != CopyMode::Everything && - element->getCreator().getFlags().has( - MessageElementFlag::RepliedMessage)) - { - // Don't include the message being replied to - continue; - } - - if (copymode == CopyMode::OnlyTextAndEmotes) - { - if (element->getCreator().getFlags().hasAny( - {MessageElementFlag::Timestamp, - MessageElementFlag::Username, MessageElementFlag::Badges})) - { - continue; - } - } - - auto indexCount = element->getSelectionIndexCount(); - - if (first) - { - if (index + indexCount > from) - { - element->addCopyTextToString(str, from - index, to - index); - first = false; - - if (index + indexCount >= to) - { - break; - } - } - } - else - { - if (index + indexCount >= to) - { - element->addCopyTextToString(str, 0, to - index); - break; - } - - element->addCopyTextToString(str); - } - - index += indexCount; - } + return getSettings()->collpseMessagesMinLines.getValue() > 0 && + this->flags_.has(MessageFlag::Collapsed); } } // namespace chatterino diff --git a/src/messages/layouts/MessageLayoutContainer.hpp b/src/messages/layouts/MessageLayoutContainer.hpp index 5f4bc2228..5887295fb 100644 --- a/src/messages/layouts/MessageLayoutContainer.hpp +++ b/src/messages/layouts/MessageLayoutContainer.hpp @@ -7,6 +7,7 @@ #include #include +#include #include class QPainter; @@ -20,87 +21,173 @@ class MessageLayoutElement; struct Selection; struct MessagePaintContext; -struct Margin { - int top; - int right; - int bottom; - int left; - - Margin() - : Margin(0) - { - } - - Margin(int value) - : Margin(value, value, value, value) - { - } - - Margin(int _top, int _right, int _bottom, int _left) - : top(_top) - , right(_right) - , bottom(_bottom) - , left(_left) - { - } -}; - struct MessageLayoutContainer { MessageLayoutContainer() = default; FirstWord first = FirstWord::Neutral; - bool containsRTL = false; - int getHeight() const; + /** + * Begin the layout process of this message + * + * This will reset all line calculations, and will be considered incomplete + * until the accompanying end function has been called + */ + void beginLayout(int width_, float scale_, MessageFlags flags_); + + /** + * Finish the layout process of this message + */ + void endLayout(); + + /** + * Add the given `element` to this message. + * + * This will also prepend a line break if the element + * does not fit in the current line + */ + void addElement(MessageLayoutElement *element); + + /** + * Add the given `element` to this message + */ + void addElementNoLineBreak(MessageLayoutElement *element); + + /** + * Break the current line + */ + void breakLine(); + + /** + * Paint the elements in this message + */ + void paintElements(QPainter &painter, const MessagePaintContext &ctx) const; + + /** + * Paint the animated elements in this message + */ + void paintAnimatedElements(QPainter &painter, int yOffset) const; + + /** + * Paint the selection for this container + * This container contains one or more message elements + * + * @param painter The painter we draw everything to + * @param messageIndex This container's message index in the context of + * the layout we're being painted in + * @param selection The selection we need to paint + * @param yOffset The extra offset added to Y for everything that's painted + */ + void paintSelection(QPainter &painter, size_t messageIndex, + const Selection &selection, int yOffset) const; + + /** + * Add text from this message into the `str` parameter + * + * @param[out] str The string where we append our selected text to + * @param from The character index from which we collecting our selected text + * @param to The character index where we stop collecting our selected text + * @param copymode Decides what from the message gets added to the selected text + */ + void addSelectionText(QString &str, uint32_t from, uint32_t to, + CopyMode copymode) const; + + /** + * Returns a raw pointer to the element at the given point + * + * If no element is found at the given point, this returns a null pointer + */ + MessageLayoutElement *getElementAt(QPoint point) const; + + /** + * Get the character index at the given point, in the context of selections + */ + size_t getSelectionIndex(QPoint point) const; + + /** + * Get the index of the first visible character in this message + * + * This can be non-zero if there are elements in this message that are skipped + */ + size_t getFirstMessageCharacterIndex() const; + + /** + * Get the index of the last character in this message + * This is the sum of all the characters in `elements_` + */ + size_t getLastCharacterIndex() const; + + /** + * Returns the width of this message + */ int getWidth() const; + + /** + * Returns the height of this message + */ + int getHeight() const; + + /** + * Returns the scale of this message + */ float getScale() const; - // methods - void begin(int width_, float scale_, MessageFlags flags_); - void end(); - - void clear(); - bool canAddElements() const; - void addElement(MessageLayoutElement *element); - void addElementNoLineBreak(MessageLayoutElement *element); - void breakLine(); - bool atStartOfLine(); - bool fitsInLine(int width_); - int remainingWidth() const; - // this method is called when a message has an RTL word - // we need to reorder the words to be shown properly - // however we don't we to reorder non-text elements like badges, timestamps, username - // firstTextIndex is the index of the first text element that we need to start the reordering from - void reorderRTL(int firstTextIndex); - MessageLayoutElement *getElementAt(QPoint point); - - // painting - void paintElements(QPainter &painter, const MessagePaintContext &ctx); - void paintAnimatedElements(QPainter &painter, int yOffset); - void paintSelection(QPainter &painter, size_t messageIndex, - const Selection &selection, int yOffset); - - // selection - int getSelectionIndex(QPoint point); - int getLastCharacterIndex() const; - int getFirstMessageCharacterIndex() const; - void addSelectionText(QString &str, uint32_t from, uint32_t to, - CopyMode copymode); - + /** + * Returns true if this message is collapsed + */ bool isCollapsed() const; + /** + * Return true if we are at the start of a new line + */ + bool atStartOfLine() const; + + /** + * Check whether an additional `width` would fit in the current line + * + * Returns true if it does fit, false if not + */ + bool fitsInLine(int width) const; + + /** + * Returns the remaining width of this line until we will need to start a new line + */ + int remainingWidth() const; + private: struct Line { - int startIndex{}; - int endIndex{}; - int startCharIndex{}; - int endCharIndex{}; + /** + * The index of the first message element on this line + * Points into `elements_` + */ + size_t startIndex{}; + + /** + * The index of the last message element on this line + * Points into `elements_` + */ + size_t endIndex{}; + + /** + * In the context of selections, the index of the first character on this line + * The first line's startCharIndex will always be 0 + */ + size_t startCharIndex{}; + + /** + * In the context of selections, the index of the last character on this line + * The last line's startCharIndex will always be the sum of all characters in this message + */ + size_t endCharIndex{}; + + /** + * The rectangle that covers all elements on this line + * This rectangle will always take up 100% of the view's width + */ QRect rect; }; - // helpers /* - _addElement is called at two stages. first stage is the normal one where we want to add message layout elements to the container. + addElement is called at two stages. first stage is the normal one where we want to add message layout elements to the container. If we detect an RTL word in the message, reorderRTL will be called, which is the second stage, where we call _addElement again for each layout element, but in the correct order this time, without adding the elemnt to the this->element_ vector. Due to compact emote logic, we need the previous element to check if we should change the spacing or not. @@ -109,21 +196,76 @@ private: In stage one we don't need that and we pass -2 to indicate stage one (i.e. adding mode) In stage two, we pass -1 for the first element, and the index of the oredered privous element for the rest. */ - void _addElement(MessageLayoutElement *element, bool forceAdd = false, - int prevIndex = -2); - bool canCollapse(); + void addElement(MessageLayoutElement *element, bool forceAdd, + int prevIndex); - const Margin margin = {4, 8, 4, 8}; + // this method is called when a message has an RTL word + // we need to reorder the words to be shown properly + // however we don't we to reorder non-text elements like badges, timestamps, username + // firstTextIndex is the index of the first text element that we need to start the reordering from + void reorderRTL(int firstTextIndex); + + /** + * Paint a selection rectangle over the given line + * + * @param painter The painter we draw everything to + * @param line The line whose rect we use as the base top & bottom of the rect to paint + * @param left The left coordinates of the rect to paint + * @param right The right coordinates of the rect to paint + * @param yOffset Extra offset for line's top & bottom + * @param color Color of the selection + **/ + void paintSelectionRect(QPainter &painter, const Line &line, int left, + int right, int yOffset, const QColor &color) const; + + /** + * Paint the selection start + * + * Returns a line index if this message should also paint the selection end + */ + std::optional paintSelectionStart(QPainter &painter, + size_t messageIndex, + const Selection &selection, + int yOffset) const; + + /** + * Paint the selection end + * + * @param lineIndex The index of the line to start painting at + */ + void paintSelectionEnd(QPainter &painter, size_t lineIndex, + const Selection &selection, int yOffset) const; + + /** + * canAddElements returns true if it's possible to add more elements to this message + */ + bool canAddElements() const; + + /** + * Return true if this message can collapse + * + * TODO: comment this better :-) + */ + bool canCollapse() const; // variables - float scale_ = 1.f; + float scale_ = 1.F; int width_ = 0; MessageFlags flags_{}; - int line_ = 0; + /** + * line_ is the current line index we are adding + * This is not the number of lines this message contains, since this will stop + * incrementing if the message is collapsed + */ + size_t line_{}; int height_ = 0; int currentX_ = 0; int currentY_ = 0; - int charIndex_ = 0; + /** + * charIndex_ is the selection-contexted index of where we currently are in our message + * At the end, this will always be equal to the sum of `elements_` getSelectionIndexCount() + */ + size_t charIndex_ = 0; size_t lineStart_ = 0; int lineHeight_ = 0; int spaceWidth_ = 4; @@ -133,7 +275,19 @@ private: bool isCollapsed_ = false; bool wasPrevReversed_ = false; + /** + * containsRTL indicates whether or not any of the text in this message + * contains any right-to-left characters (e.g. arabic) + */ + bool containsRTL = false; + std::vector> elements_; + + /** + * A list of lines covering this message + * A message that spans 3 lines in a view will have 3 elements in lines_ + * These lines hold no relation to the elements that are in this + */ std::vector lines_; }; diff --git a/src/messages/layouts/MessageLayoutElement.cpp b/src/messages/layouts/MessageLayoutElement.cpp index 122de28c2..523a6b145 100644 --- a/src/messages/layouts/MessageLayoutElement.cpp +++ b/src/messages/layouts/MessageLayoutElement.cpp @@ -60,12 +60,12 @@ bool MessageLayoutElement::hasTrailingSpace() const return this->trailingSpace; } -int MessageLayoutElement::getLine() const +size_t MessageLayoutElement::getLine() const { return this->line_; } -void MessageLayoutElement::setLine(int line) +void MessageLayoutElement::setLine(size_t line) { this->line_ = line; } @@ -132,7 +132,7 @@ void ImageLayoutElement::addCopyTextToString(QString &str, uint32_t from, } } -int ImageLayoutElement::getSelectionIndexCount() const +size_t ImageLayoutElement::getSelectionIndexCount() const { return this->trailingSpace ? 2 : 1; } @@ -176,7 +176,7 @@ int ImageLayoutElement::getMouseOverIndex(const QPoint &abs) const return 0; } -int ImageLayoutElement::getXFromIndex(int index) +int ImageLayoutElement::getXFromIndex(size_t index) { if (index <= 0) { @@ -224,7 +224,7 @@ void LayeredImageLayoutElement::addCopyTextToString(QString &str, uint32_t from, } } -int LayeredImageLayoutElement::getSelectionIndexCount() const +size_t LayeredImageLayoutElement::getSelectionIndexCount() const { return this->trailingSpace ? 2 : 1; } @@ -304,7 +304,7 @@ int LayeredImageLayoutElement::getMouseOverIndex(const QPoint &abs) const return 0; } -int LayeredImageLayoutElement::getXFromIndex(int index) +int LayeredImageLayoutElement::getXFromIndex(size_t index) { if (index <= 0) { @@ -422,7 +422,7 @@ void TextLayoutElement::addCopyTextToString(QString &str, uint32_t from, } } -int TextLayoutElement::getSelectionIndexCount() const +size_t TextLayoutElement::getSelectionIndexCount() const { return this->getText().length() + (this->trailingSpace ? 1 : 0); } @@ -489,7 +489,7 @@ int TextLayoutElement::getMouseOverIndex(const QPoint &abs) const return this->getSelectionIndexCount() - (this->hasTrailingSpace() ? 1 : 0); } -int TextLayoutElement::getXFromIndex(int index) +int TextLayoutElement::getXFromIndex(size_t index) { auto app = getApp(); @@ -532,7 +532,7 @@ void TextIconLayoutElement::addCopyTextToString(QString &str, uint32_t from, { } -int TextIconLayoutElement::getSelectionIndexCount() const +size_t TextIconLayoutElement::getSelectionIndexCount() const { return this->trailingSpace ? 2 : 1; } @@ -576,7 +576,7 @@ int TextIconLayoutElement::getMouseOverIndex(const QPoint &abs) const return 0; } -int TextIconLayoutElement::getXFromIndex(int index) +int TextIconLayoutElement::getXFromIndex(size_t index) { if (index <= 0) { @@ -649,7 +649,7 @@ int ReplyCurveLayoutElement::getMouseOverIndex(const QPoint &abs) const return 0; } -int ReplyCurveLayoutElement::getXFromIndex(int index) +int ReplyCurveLayoutElement::getXFromIndex(size_t index) { if (index <= 0) { @@ -664,7 +664,7 @@ void ReplyCurveLayoutElement::addCopyTextToString(QString &str, uint32_t from, { } -int ReplyCurveLayoutElement::getSelectionIndexCount() const +size_t ReplyCurveLayoutElement::getSelectionIndexCount() const { return 1; } diff --git a/src/messages/layouts/MessageLayoutElement.hpp b/src/messages/layouts/MessageLayoutElement.hpp index 1cdc16edc..df42d1c9c 100644 --- a/src/messages/layouts/MessageLayoutElement.hpp +++ b/src/messages/layouts/MessageLayoutElement.hpp @@ -40,8 +40,8 @@ public: MessageElement &getCreator() const; void setPosition(QPoint point); bool hasTrailingSpace() const; - int getLine() const; - void setLine(int line); + size_t getLine() const; + void setLine(size_t line); MessageLayoutElement *setTrailingSpace(bool value); MessageLayoutElement *setLink(const Link &link_); @@ -49,12 +49,12 @@ public: virtual void addCopyTextToString(QString &str, uint32_t from = 0, uint32_t to = UINT32_MAX) const = 0; - virtual int getSelectionIndexCount() const = 0; + virtual size_t getSelectionIndexCount() const = 0; virtual void paint(QPainter &painter, const MessageColors &messageColors) = 0; virtual void paintAnimated(QPainter &painter, int yOffset) = 0; virtual int getMouseOverIndex(const QPoint &abs) const = 0; - virtual int getXFromIndex(int index) = 0; + virtual int getXFromIndex(size_t index) = 0; const Link &getLink() const; const QString &getText() const; @@ -68,7 +68,10 @@ private: QRect rect_; Link link_; MessageElement &creator_; - int line_{}; + /** + * The line of the container this element is laid out at + */ + size_t line_{}; }; // IMAGE @@ -81,11 +84,11 @@ public: protected: void addCopyTextToString(QString &str, uint32_t from = 0, uint32_t to = UINT32_MAX) const override; - int getSelectionIndexCount() const override; + size_t getSelectionIndexCount() const override; void paint(QPainter &painter, const MessageColors &messageColors) override; void paintAnimated(QPainter &painter, int yOffset) override; int getMouseOverIndex(const QPoint &abs) const override; - int getXFromIndex(int index) override; + int getXFromIndex(size_t index) override; ImagePtr image_; }; @@ -100,11 +103,11 @@ public: protected: void addCopyTextToString(QString &str, uint32_t from = 0, uint32_t to = UINT32_MAX) const override; - int getSelectionIndexCount() const override; + size_t getSelectionIndexCount() const override; void paint(QPainter &painter, const MessageColors &messageColors) override; void paintAnimated(QPainter &painter, int yOffset) override; int getMouseOverIndex(const QPoint &abs) const override; - int getXFromIndex(int index) override; + int getXFromIndex(size_t index) override; std::vector images_; std::vector sizes_; @@ -153,11 +156,11 @@ public: protected: void addCopyTextToString(QString &str, uint32_t from = 0, uint32_t to = UINT32_MAX) const override; - int getSelectionIndexCount() const override; + size_t getSelectionIndexCount() const override; void paint(QPainter &painter, const MessageColors &messageColors) override; void paintAnimated(QPainter &painter, int yOffset) override; int getMouseOverIndex(const QPoint &abs) const override; - int getXFromIndex(int index) override; + int getXFromIndex(size_t index) override; QColor color_; FontStyle style_; @@ -177,11 +180,11 @@ public: protected: void addCopyTextToString(QString &str, uint32_t from = 0, uint32_t to = UINT32_MAX) const override; - int getSelectionIndexCount() const override; + size_t getSelectionIndexCount() const override; void paint(QPainter &painter, const MessageColors &messageColors) override; void paintAnimated(QPainter &painter, int yOffset) override; int getMouseOverIndex(const QPoint &abs) const override; - int getXFromIndex(int index) override; + int getXFromIndex(size_t index) override; private: float scale; @@ -199,10 +202,10 @@ protected: void paint(QPainter &painter, const MessageColors &messageColors) override; void paintAnimated(QPainter &painter, int yOffset) override; int getMouseOverIndex(const QPoint &abs) const override; - int getXFromIndex(int index) override; + int getXFromIndex(size_t index) override; void addCopyTextToString(QString &str, uint32_t from = 0, uint32_t to = UINT32_MAX) const override; - int getSelectionIndexCount() const override; + size_t getSelectionIndexCount() const override; private: const QPen pen_; diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index 1e4cf4b56..a2beffb02 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -1519,7 +1519,7 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event) if (this->isLeftMouseDown_) { // this->pause(PauseReason::Selecting, 300); - int index = layout->getSelectionIndex(relativePos); + auto index = layout->getSelectionIndex(relativePos); this->setSelection(this->selection_.start, SelectionItem(messageIndex, index));