diff --git a/.travis.yml b/.travis.yml index 9c9adbdfd..1267bf14c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,6 @@ before_install: - sudo apt-get install qt5-default qttools5-dev-tools script: - - qmake -project - - qmake Ultron.pro - - make \ No newline at end of file + - qmake -qt=qt5 -v + - qmake -qt=qt5 + - make diff --git a/messages/link.h b/messages/link.h index 022e0e3ae..1fe5f545b 100644 --- a/messages/link.h +++ b/messages/link.h @@ -24,19 +24,19 @@ public: Link(Type getType, const QString &getValue); bool - getIsValid() + getIsValid() const { return type == None; } Type - getType() + getType() const { return type; } const QString & - getValue() + getValue() const { return value; } diff --git a/messages/message.cpp b/messages/message.cpp index 80700f003..76f7d03ab 100644 --- a/messages/message.cpp +++ b/messages/message.cpp @@ -9,6 +9,7 @@ #include "resources.h" #include "settings.h" +#include #include #include #include @@ -144,7 +145,7 @@ Message::Message(const IrcPrivateMessage &ircMessage, Channel &channel, } // username - this->userName = ircMessage.account(); + this->userName = ircMessage.nick(); if (this->userName.isEmpty()) { auto iterator = tags.find("login"); @@ -230,7 +231,7 @@ Message::Message(const IrcPrivateMessage &ircMessage, Channel &channel, end > ircMessage.content().length()) continue; - QString name = ircMessage.content().mid(start, end - start); + QString name = ircMessage.content().mid(start, end - start + 1); twitchEmotes.push_back(std::pair( start, Emotes::getTwitchEmoteById(name, id))); diff --git a/messages/messageref.cpp b/messages/messageref.cpp index f67b0a9fb..7f4edce1e 100644 --- a/messages/messageref.cpp +++ b/messages/messageref.cpp @@ -36,11 +36,13 @@ MessageRef::layout(int width, bool enableEmoteMargins) bool recalculateImages = this->emoteGeneration != Emotes::getGeneration(); bool recalculateText = this->fontGeneration != Fonts::getGeneration(); + bool newWordTypes = + this->currentWordTypes != Settings::getInstance().getWordTypeMask(); qreal emoteScale = settings.emoteScale.get(); bool scaleEmotesByLineHeight = settings.scaleEmotesByLineHeight.get(); - if (recalculateImages || recalculateText) { + if (recalculateImages || recalculateText || newWordTypes) { this->emoteGeneration = Emotes::getGeneration(); this->fontGeneration = Fonts::getGeneration(); @@ -72,6 +74,10 @@ MessageRef::layout(int width, bool enableEmoteMargins) } } } + + if (newWordTypes) { + this->currentWordTypes = Settings::getInstance().getWordTypeMask(); + } } if (!redraw) { @@ -85,6 +91,7 @@ MessageRef::layout(int width, bool enableEmoteMargins) int right = width - MARGIN_RIGHT - MARGIN_LEFT; + int lineNumber = 0; int lineStart = 0; int lineHeight = 0; bool first = true; @@ -139,22 +146,23 @@ MessageRef::layout(int width, bool enableEmoteMargins) this->wordParts.push_back(WordPart(word, MARGIN_LEFT, y, width, word.getHeight(), - mid, mid)); + lineNumber, mid, mid)); y += metrics.height(); start = i - 1; width = 0; + lineNumber++; } } QString mid(text.mid(start)); width = metrics.width(mid); - this->wordParts.push_back(WordPart(word, MARGIN_LEFT, - y - word.getHeight(), width, - word.getHeight(), mid, mid)); + this->wordParts.push_back( + WordPart(word, MARGIN_LEFT, y - word.getHeight(), width, + word.getHeight(), lineNumber, mid, mid)); x = width + MARGIN_LEFT + spaceWidth; lineHeight = word.getHeight(); @@ -164,8 +172,8 @@ MessageRef::layout(int width, bool enableEmoteMargins) first = false; } else if (first || x + word.getWidth() + xOffset <= right) { // fits in the line - this->wordParts.push_back( - WordPart(word, x, y - word.getHeight(), word.getCopyText())); + this->wordParts.push_back(WordPart(word, x, y - word.getHeight(), + lineNumber, word.getCopyText())); x += word.getWidth() + xOffset; x += spaceWidth; @@ -179,8 +187,9 @@ MessageRef::layout(int width, bool enableEmoteMargins) y += lineHeight; - this->wordParts.push_back(WordPart( - word, MARGIN_LEFT, y - word.getHeight(), word.getCopyText())); + this->wordParts.push_back(WordPart(word, MARGIN_LEFT, + y - word.getHeight(), lineNumber, + word.getCopyText())); lineStart = this->wordParts.size() - 1; @@ -188,6 +197,8 @@ MessageRef::layout(int width, bool enableEmoteMargins) x = word.getWidth() + MARGIN_LEFT; x += spaceWidth; + + lineNumber++; } } @@ -216,5 +227,112 @@ MessageRef::alignWordParts(int lineStart, int lineHeight) wordPart2.setY(wordPart2.getY() + lineHeight); } } + +bool +MessageRef::tryGetWordPart(QPoint point, messages::Word &word) +{ + for (messages::WordPart &wordPart : this->wordParts) { + if (wordPart.getRect().contains(point)) { + word = wordPart.getWord(); + return true; + } + } + + return false; +} + +int +MessageRef::getSelectionIndex(QPoint position) +{ + if (this->wordParts.size() == 0) { + return 0; + } + + // find out in which line the cursor is + int lineNumber = 0, lineStart = 0, lineEnd = 0; + + for (int i = 0; i < this->wordParts.size(); i++) { + WordPart &part = this->wordParts[i]; + + // return if curser under the word + if (position.y() >= part.getBottom()) { + break; + } + + if (part.getLineNumber() != lineNumber) { + lineStart = i; + lineNumber = part.getLineNumber(); + } + + lineEnd = i; + } + + // count up to the cursor + int index = 0; + + for (int i = 0; i < lineStart; i++) { + WordPart &part = this->wordParts[i]; + + index += part.getWord().isImage() ? 2 : part.getText().length() + 1; + } + + for (int i = lineStart; i < lineEnd; i++) { + WordPart &part = this->wordParts[i]; + + // curser is left of the word part + if (position.x() < part.getX()) { + break; + } + + // cursor is right of the word part + if (position.x() > part.getX()) { + index += part.getWord().isImage() ? 2 : part.getText().length() + 1; + continue; + } + + // cursor is over the word part + if (part.getWord().isImage()) { + index++; + } else { + auto text = part.getWord().getText(); + + int x = part.getX(); + + for (int j = 0; j < text.length(); j++) { + if (x > position.x()) { + break; + } + + index++; + x = part.getX() + + part.getWord().getFontMetrics().width(text, j + 1); + } + } + + break; + } + + return index; + + // go through all the wordparts + // for (int i = 0; i < this->wordParts; i < this->wordParts.size()) { + + // WordPart &part = this->wordParts[i]; + + // // return if curser under the word + // if (position.y() >= part.getBottom()) { + // break; + // } + + // // increment index and continue if the curser x is bigger than the + // words + // // right edge + // if (position.x() > part.getRight()) { + // index += part.getWord().isImage() ? 2 + + // part.getText().length() + 1; + // continue; + // } + // } +} } } diff --git a/messages/messageref.h b/messages/messageref.h index fba83fea1..95339779d 100644 --- a/messages/messageref.h +++ b/messages/messageref.h @@ -37,6 +37,10 @@ public: std::shared_ptr buffer = nullptr; bool updateBuffer = false; + bool tryGetWordPart(QPoint point, messages::Word &word); + + int getSelectionIndex(QPoint position); + private: Message *message; std::shared_ptr messagePtr; @@ -48,6 +52,7 @@ private: int currentLayoutWidth = -1; int fontGeneration = -1; int emoteGeneration = -1; + Word::Type currentWordTypes = Word::None; void alignWordParts(int lineStart, int lineHeight); }; diff --git a/messages/word.h b/messages/word.h index 1e3d3808f..7e13d6015 100644 --- a/messages/word.h +++ b/messages/word.h @@ -65,6 +65,9 @@ public: ButtonTimeout }; + Word() + { + } explicit Word(LazyLoadedImage *image, Type getType, const QString ©text, const QString &getTooltip, const Link &getLink = Link()); explicit Word(const QString &text, Type getType, const QColor &getColor, @@ -182,7 +185,7 @@ public: } std::vector & - getCharacterWidthCache() + getCharacterWidthCache() const { return this->characterWidthCache; } @@ -206,7 +209,7 @@ private: Fonts::Type font = Fonts::Medium; Link link; - std::vector characterWidthCache; + mutable std::vector characterWidthCache; }; } } diff --git a/messages/wordpart.cpp b/messages/wordpart.cpp index da8f41745..0080b223c 100644 --- a/messages/wordpart.cpp +++ b/messages/wordpart.cpp @@ -4,8 +4,8 @@ namespace chatterino { namespace messages { -WordPart::WordPart(Word &word, int x, int y, const QString ©Text, - bool allowTrailingSpace) +WordPart::WordPart(Word &word, int x, int y, int lineNumber, + const QString ©Text, bool allowTrailingSpace) : m_word(word) , copyText(copyText) , text(word.isText() ? m_word.getText() : QString()) @@ -13,13 +13,14 @@ WordPart::WordPart(Word &word, int x, int y, const QString ©Text, , y(y) , width(word.getWidth()) , height(word.getHeight()) + , lineNumber(lineNumber) , _trailingSpace(word.hasTrailingSpace() & allowTrailingSpace) { } WordPart::WordPart(Word &word, int x, int y, int width, int height, - const QString ©Text, const QString &customText, - bool allowTrailingSpace) + int lineNumber, const QString ©Text, + const QString &customText, bool allowTrailingSpace) : m_word(word) , copyText(copyText) , text(customText) @@ -27,6 +28,7 @@ WordPart::WordPart(Word &word, int x, int y, int width, int height, , y(y) , width(width) , height(height) + , lineNumber(lineNumber) , _trailingSpace(word.hasTrailingSpace() & allowTrailingSpace) { } diff --git a/messages/wordpart.h b/messages/wordpart.h index a0bda61d5..0a19026fe 100644 --- a/messages/wordpart.h +++ b/messages/wordpart.h @@ -12,12 +12,12 @@ class Word; class WordPart { public: - WordPart(Word &getWord, int getX, int getY, const QString &getCopyText, - bool allowTrailingSpace = true); + WordPart(Word &getWord, int getX, int getY, int lineNumber, + const QString &getCopyText, bool allowTrailingSpace = true); WordPart(Word &getWord, int getX, int getY, int getWidth, int getHeight, - const QString &getCopyText, const QString &customText, - bool allowTrailingSpace = true); + int lineNumber, const QString &getCopyText, + const QString &customText, bool allowTrailingSpace = true); const Word & getWord() const @@ -98,6 +98,12 @@ public: return this->text; } + int + getLineNumber() + { + return this->lineNumber; + } + private: Word &m_word; @@ -109,6 +115,8 @@ private: int width; int height; + int lineNumber; + bool _trailingSpace; }; } diff --git a/widgets/chatwidget.cpp b/widgets/chatwidget.cpp index 1c05fc20a..fb2f5abf4 100644 --- a/widgets/chatwidget.cpp +++ b/widgets/chatwidget.cpp @@ -76,8 +76,6 @@ ChatWidget::setChannelName(const QString &name) auto messageRef = new messages::MessageRef(message); - qDebug() << "xD"; - this->messages.appendItem( std::shared_ptr(messageRef), deleted); }); diff --git a/widgets/chatwidgetview.cpp b/widgets/chatwidgetview.cpp index cd37f1ac9..8fd4fbefb 100644 --- a/widgets/chatwidgetview.cpp +++ b/widgets/chatwidgetview.cpp @@ -24,6 +24,7 @@ ChatWidgetView::ChatWidgetView(ChatWidget *parent) { this->setAttribute(Qt::WA_OpaquePaintEvent); this->scrollbar.setSmallChange(5); + this->setMouseTracking(true); QObject::connect(&Settings::getInstance(), &Settings::wordTypeMaskChanged, this, &ChatWidgetView::wordTypeMaskChanged); @@ -286,5 +287,70 @@ ChatWidgetView::wheelEvent(QWheelEvent *event) true); } } + +void +ChatWidgetView::mouseMoveEvent(QMouseEvent *event) +{ + std::shared_ptr message; + QPoint relativePos; + + if (!tryGetMessageAt(event->pos(), message, relativePos)) { + this->setCursor(Qt::ArrowCursor); + return; + } + + auto _message = message->getMessage(); + auto user = _message->getUserName(); + + messages::Word hoverWord; + + if (!message->tryGetWordPart(relativePos, hoverWord)) { + this->setCursor(Qt::ArrowCursor); + return; + } + + int index = message->getSelectionIndex(relativePos); + + qDebug() << index; + + if (hoverWord.getLink().getIsValid()) { + this->setCursor(Qt::PointingHandCursor); + qDebug() << hoverWord.getLink().getValue(); + } else { + this->setCursor(Qt::ArrowCursor); + } +} + +bool +ChatWidgetView::tryGetMessageAt(QPoint p, + std::shared_ptr &_message, + QPoint &relativePos) +{ + auto messages = chatWidget->getMessagesSnapshot(); + + int start = this->scrollbar.getCurrentValue(); + + if (start >= messages.getLength()) { + return false; + } + + int y = -(messages[start].get()->getHeight() * + (fmod(this->scrollbar.getCurrentValue(), 1))) + + 12; + + for (int i = start; i < messages.getLength(); ++i) { + auto message = messages[i]; + + y += message->getHeight(); + + if (p.y() < y) { + relativePos = QPoint(p.x(), y - p.y()); + _message = message; + return true; + } + } + + return false; +} } } diff --git a/widgets/chatwidgetview.h b/widgets/chatwidgetview.h index dc195d642..fdbf834b8 100644 --- a/widgets/chatwidgetview.h +++ b/widgets/chatwidgetview.h @@ -39,6 +39,12 @@ protected: void paintEvent(QPaintEvent *); void wheelEvent(QWheelEvent *event); + void mouseMoveEvent(QMouseEvent *event); + + bool tryGetMessageAt(QPoint p, + std::shared_ptr &message, + QPoint &relativePos); + private: struct GifEmoteData { messages::LazyLoadedImage *image; @@ -56,6 +62,7 @@ private slots: void wordTypeMaskChanged() { + layoutMessages(); update(); } }; diff --git a/widgets/notebook.cpp b/widgets/notebook.cpp index 9030e886d..5c110db88 100644 --- a/widgets/notebook.cpp +++ b/widgets/notebook.cpp @@ -152,6 +152,7 @@ Notebook::performLayout(bool animated) if (Settings::getInstance().hideUserButton.get()) { userButton.hide(); } else { + userButton.move(x, 0); userButton.show(); x += 24; }