diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..1267bf14 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +before_install: + - sudo add-apt-repository --yes ppa:ubuntu-sdk-team/ppa + - sudo apt-get update -qq + - sudo apt-get install qtbase5-dev qtdeclarative5-dev libqt5webkit5-dev libsqlite3-dev + - sudo apt-get install qt5-default qttools5-dev-tools + +script: + - qmake -qt=qt5 -v + - qmake -qt=qt5 + - make diff --git a/README.md b/README.md index 8c997b21..acd7b4e6 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,6 @@ Chatterino 2 is the second installment of my twitch chat client series "Chatteri 1. download the [boost library](https://sourceforge.net/projects/boost/files/boost/1.63.0/boost_1_63_0.zip/download) and extract it to `C:\local\boost` 2. download binaries for OpenSSL >= 1.0.2 or compile it from source. [example download](https://indy.fulgan.com/SSL/) 3. Place libeay32.dll and ssleay32.dll from OpenSSL in a directory in PATH. + +## code style +The code is normally formated using clang format in qt creator. [clangformat.txt](https://github.com/fourtf/chatterino2/blob/master/clangformat.txt) contains the style file for clang format. diff --git a/clangformat.txt b/clangformat.txt new file mode 100644 index 00000000..b40edcf5 --- /dev/null +++ b/clangformat.txt @@ -0,0 +1,28 @@ +IndentCaseLabels: true +BasedOnStyle: Google +IndentWidth: 4 +Standard: Auto +PointerBindsToType: false +Language: Cpp +SpacesBeforeTrailingComments: 2 +AccessModifierOffset: -1 +AlignEscapedNewlinesLeft: true +AlwaysBreakAfterDefinitionReturnType: true +AlwaysBreakBeforeMultilineStrings: false +BreakConstructorInitializersBeforeComma: true +# BreakBeforeBraces: Linux +BreakBeforeBraces: Custom +AccessModifierOffset: -4 +ConstructorInitializerAllOnOneLineOrOnePerLine: false +AllowShortFunctionsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +DerivePointerBinding: false +BraceWrapping: { + AfterNamespace: 'false' + AfterClass: 'true' + BeforeElse: 'false' + AfterControlStatement: 'false' + AfterFunction: 'true' + BeforeCatch: 'false' +} \ No newline at end of file diff --git a/emotes.cpp b/emotes.cpp index 95d8e528..7fa16e9e 100644 --- a/emotes.cpp +++ b/emotes.cpp @@ -26,6 +26,10 @@ ConcurrentMap Emotes::ffzChannelEmoteFromCaches; ConcurrentMap Emotes::twitchEmoteFromCache; ConcurrentMap Emotes::miscImageFromCache; +boost::signals2::signal Emotes::gifUpdateTimerSignal; + +QTimer Emotes::gifUpdateTimer; +bool Emotes::gifUpdateTimerInitiated(false); int Emotes::generation = 0; @@ -85,7 +89,7 @@ Emotes::loadBttvEmotes() void Emotes::loadFfzEmotes() { - // bttv + // ffz QNetworkAccessManager *manager = new QNetworkAccessManager(); QUrl url("https://api.frankerfacez.com/v1/set/global"); diff --git a/emotes.h b/emotes.h index 50df0aa7..1ae669b4 100644 --- a/emotes.h +++ b/emotes.h @@ -1,12 +1,17 @@ #ifndef EMOTES_H #define EMOTES_H +#define GIF_FRAME_LENGTH 33 + #include "concurrentmap.h" #include "messages/lazyloadedimage.h" #include "twitchemotevalue.h" +#include "windows.h" #include #include +#include +#include namespace chatterino { @@ -82,6 +87,24 @@ public: generation++; } + static boost::signals2::signal & + getGifUpdateSignal() + { + if (!gifUpdateTimerInitiated) { + gifUpdateTimerInitiated = true; + + gifUpdateTimer.setInterval(30); + gifUpdateTimer.start(); + + QObject::connect(&gifUpdateTimer, &QTimer::timeout, [] { + gifUpdateTimerSignal(); + Windows::repaintGifEmotes(); + }); + } + + return gifUpdateTimerSignal; + } + private: Emotes(); @@ -102,10 +125,15 @@ private: static QString getTwitchEmoteLink(long id, qreal &scale); + static QTimer gifUpdateTimer; + static bool gifUpdateTimerInitiated; + static void loadFfzEmotes(); static void loadBttvEmotes(); static int generation; + + static boost::signals2::signal gifUpdateTimerSignal; }; } diff --git a/messages/lazyloadedimage.cpp b/messages/lazyloadedimage.cpp index 09a9ad37..4886d17e 100644 --- a/messages/lazyloadedimage.cpp +++ b/messages/lazyloadedimage.cpp @@ -5,9 +5,12 @@ #include "ircmanager.h" #include "windows.h" +#include +#include #include #include #include +#include #include namespace chatterino { @@ -16,7 +19,10 @@ namespace messages { LazyLoadedImage::LazyLoadedImage(const QString &url, qreal scale, const QString &name, const QString &tooltip, const QMargins &margin, bool isHat) - : pixmap(NULL) + : currentPixmap(NULL) + , allFrames() + , currentFrame(0) + , currentFrameOffset(0) , url(url) , name(name) , tooltip(tooltip) @@ -31,7 +37,10 @@ LazyLoadedImage::LazyLoadedImage(const QString &url, qreal scale, LazyLoadedImage::LazyLoadedImage(QPixmap *image, qreal scale, const QString &name, const QString &tooltip, const QMargins &margin, bool isHat) - : pixmap(image) + : currentPixmap(image) + , allFrames() + , currentFrame(0) + , currentFrameOffset(0) , url() , name(name) , tooltip(tooltip) @@ -46,7 +55,6 @@ LazyLoadedImage::LazyLoadedImage(QPixmap *image, qreal scale, void LazyLoadedImage::loadImage() { - // QThreadPool::globalInstance()->start(new LambdaQRunnable([=] { QNetworkAccessManager *manager = new QNetworkAccessManager(); QUrl url(this->url); @@ -55,21 +63,64 @@ LazyLoadedImage::loadImage() QNetworkReply *reply = manager->get(request); QObject::connect(reply, &QNetworkReply::finished, [=] { - QPixmap *pixmap = new QPixmap(); - pixmap->loadFromData(reply->readAll()); + QByteArray array = reply->readAll(); + QBuffer buffer(&array); + buffer.open(QIODevice::ReadOnly); - if (pixmap->isNull()) { - return; + QImage image; + QImageReader reader(&buffer); + + bool first = true; + + for (int index = 0; index < reader.imageCount(); ++index) { + if (reader.read(&image)) { + auto pixmap = new QPixmap(QPixmap::fromImage(image)); + + if (first) { + first = false; + this->currentPixmap = pixmap; + } + + FrameData data; + data.duration = std::max(20, reader.nextImageDelay()); + data.image = pixmap; + + allFrames.push_back(data); + } + } + + if (allFrames.size() > 1) { + this->animated = true; + + Emotes::getGifUpdateSignal().connect([this] { gifUpdateTimout(); }); } - this->pixmap = pixmap; Emotes::incGeneration(); Windows::layoutVisibleChatWidgets(); reply->deleteLater(); manager->deleteLater(); }); - // })); +} + +void +LazyLoadedImage::gifUpdateTimout() +{ + this->currentFrameOffset += GIF_FRAME_LENGTH; + + while (true) { + if (this->currentFrameOffset > + this->allFrames.at(this->currentFrame).duration) { + this->currentFrameOffset -= + this->allFrames.at(this->currentFrame).duration; + this->currentFrame = + (this->currentFrame + 1) % this->allFrames.size(); + } else { + break; + } + } + + this->currentPixmap = this->allFrames[this->currentFrame].image; } } } diff --git a/messages/lazyloadedimage.h b/messages/lazyloadedimage.h index 482af09c..564d3c21 100644 --- a/messages/lazyloadedimage.h +++ b/messages/lazyloadedimage.h @@ -7,7 +7,7 @@ namespace chatterino { namespace messages { -class LazyLoadedImage +class LazyLoadedImage : QObject { public: explicit LazyLoadedImage(const QString &url, qreal scale = 1, @@ -15,7 +15,7 @@ public: const QString &tooltip = "", const QMargins &margin = QMargins(), bool isHat = false); - explicit LazyLoadedImage(QPixmap *pixmap, qreal scale = 1, + explicit LazyLoadedImage(QPixmap *currentPixmap, qreal scale = 1, const QString &name = "", const QString &tooltip = "", const QMargins &margin = QMargins(), @@ -29,7 +29,7 @@ public: loadImage(); } - return pixmap; + return currentPixmap; } qreal @@ -77,23 +77,31 @@ public: int getWidth() const { - if (pixmap == NULL) { + if (currentPixmap == NULL) { return 16; } - return pixmap->width(); + return currentPixmap->width(); } int getHeight() const { - if (pixmap == NULL) { + if (currentPixmap == NULL) { return 16; } - return pixmap->height(); + return currentPixmap->height(); } private: - QPixmap *pixmap; + struct FrameData { + QPixmap *image; + int duration; + }; + + QPixmap *currentPixmap; + std::vector allFrames; + int currentFrame; + int currentFrameOffset; QString url; QString name; @@ -106,6 +114,8 @@ private: bool isLoading; void loadImage(); + + void gifUpdateTimout(); }; } } diff --git a/messages/limitedqueue.h b/messages/limitedqueue.h index 9bb3d53f..64e3e6c2 100644 --- a/messages/limitedqueue.h +++ b/messages/limitedqueue.h @@ -3,8 +3,6 @@ #include "messages/limitedqueuesnapshot.h" -#include - #include #include #include @@ -17,14 +15,13 @@ class LimitedQueue { public: LimitedQueue(int limit = 100, int buffer = 25) - : vector(new std::vector(limit + buffer)) - , vectorPtr(this->vector) - , mutex() + : mutex() , offset(0) - , length(0) , limit(limit) , buffer(buffer) { + vector = std::make_shared>(); + vector->reserve(this->limit + this->buffer); } void @@ -32,11 +29,10 @@ public: { std::lock_guard lock(this->mutex); - this->vector = new std::vector(this->limit + this->buffer); - this->vectorPtr = std::shared_ptr>(this->vector); + this->vector = std::make_shared>(); + this->vector->reserve(this->limit + this->buffer); this->offset = 0; - this->length = 0; } // return true if an item was deleted @@ -46,39 +42,36 @@ public: { std::lock_guard lock(this->mutex); - if (this->length == this->limit) { + if (this->vector->size() >= this->limit) { // vector is full if (this->offset == this->buffer) { deleted = this->vector->at(this->offset); // create new vector - auto *vector = new std::vector(this->limit + this->buffer); + auto newVector = std::make_shared>(); + newVector->reserve(this->limit + this->buffer); - for (int i = 0; i < this->limit; i++) { - vector->at(i) = this->vector->at(i + this->offset); + for (unsigned int i = 0; i < this->limit - 1; i++) { + newVector->push_back(this->vector->at(i + this->offset)); } - - vector->at(limit - 1) = item; + newVector->push_back(item); this->offset = 0; - - this->vector = vector; - this->vectorPtr = std::shared_ptr>(vector); + this->vector = newVector; return true; } else { - // append item and remove first deleted = this->vector->at(this->offset); - this->vector->at(this->length + this->offset) = item; + //append item and increment offset("deleting" first element) + this->vector->push_back(item); this->offset++; return true; } } else { // append item - this->vector->at(this->length) = item; - this->length++; + this->vector->push_back(item); return false; } @@ -87,21 +80,23 @@ public: messages::LimitedQueueSnapshot getSnapshot() { - std::lock_guard lock(mutex); + std::lock_guard lock(this->mutex); - return LimitedQueueSnapshot(vectorPtr, offset, length); + if(vector->size() < limit) { + return LimitedQueueSnapshot(this->vector, this->offset, this->vector->size()); + } else { + return LimitedQueueSnapshot(this->vector, this->offset, this->limit); + } } private: - std::vector *vector; - std::shared_ptr> vectorPtr; + std::shared_ptr> vector; std::mutex mutex; - int offset; - int length; - int limit; - int buffer; + unsigned int offset; + unsigned int limit; + unsigned int buffer; }; } // namespace messages diff --git a/messages/limitedqueuesnapshot.h b/messages/limitedqueuesnapshot.h index c1816814..fd76b448 100644 --- a/messages/limitedqueuesnapshot.h +++ b/messages/limitedqueuesnapshot.h @@ -1,7 +1,6 @@ #ifndef LIMITEDQUEUESNAPSHOT_H #define LIMITEDQUEUESNAPSHOT_H -#include #include #include @@ -14,8 +13,7 @@ class LimitedQueueSnapshot public: LimitedQueueSnapshot(std::shared_ptr> ptr, int offset, int length) - : vectorPtr(ptr) - , vector(ptr.get()) + : vector(ptr) , offset(offset) , length(length) { @@ -24,20 +22,16 @@ public: int getLength() { - return length; + return this->length; } T const &operator[](int index) const { - // assert(index >= 0); - // assert(index < length); - - return vector->at(index + offset); + return this->vector->at(index + this->offset); } private: - std::shared_ptr> vectorPtr; - std::vector *vector; + std::shared_ptr> vector; int offset; int length; diff --git a/messages/link.h b/messages/link.h index 022e0e3a..1fe5f545 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 846092a5..acf02019 100644 --- a/messages/message.cpp +++ b/messages/message.cpp @@ -10,6 +10,7 @@ #include "resources.h" #include "settings.h" +#include #include #include #include @@ -228,7 +229,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 f67b0a9f..7f4edce1 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 fba83fea..95339779 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 73467e8e..018743a5 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; }; } // namespace messages diff --git a/messages/wordpart.cpp b/messages/wordpart.cpp index da8f4174..0080b223 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 a0bda61d..0a19026f 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/chatwidgetview.cpp b/widgets/chatwidgetview.cpp index b5b9045f..8fd4fbef 100644 --- a/widgets/chatwidgetview.cpp +++ b/widgets/chatwidgetview.cpp @@ -8,7 +8,9 @@ #include #include +#include #include +#include #include namespace chatterino { @@ -18,8 +20,11 @@ ChatWidgetView::ChatWidgetView(ChatWidget *parent) : QWidget() , chatWidget(parent) , scrollbar(this) + , onlyUpdateEmotes(false) { + this->setAttribute(Qt::WA_OpaquePaintEvent); this->scrollbar.setSmallChange(5); + this->setMouseTracking(true); QObject::connect(&Settings::getInstance(), &Settings::wordTypeMaskChanged, this, &ChatWidgetView::wordTypeMaskChanged); @@ -49,33 +54,26 @@ ChatWidgetView::layoutMessages() int start = this->scrollbar.getCurrentValue(); - if (messages.getLength() <= start) { - // The scrollbar wants to show more values than we can offer + // layout the visible messages in the view + if (messages.getLength() > start) { + int y = -(messages[start].get()->getHeight() * + (fmod(this->scrollbar.getCurrentValue(), 1))); - // just return for now - return false; + for (int i = start; i < messages.getLength(); ++i) { + auto messagePtr = messages[i]; + auto message = messagePtr.get(); + redraw |= message->layout(this->width(), true); - // Lower start value to the last message - // start = messages.getLength() - 1; - } + y += message->getHeight(); - int y = -(messages[start].get()->getHeight() * - (fmod(this->scrollbar.getCurrentValue(), 1))); - - for (int i = start; i < messages.getLength(); ++i) { - auto messagePtr = messages[i]; - auto message = messagePtr.get(); - - redraw |= message->layout(this->width(), true); - - y += message->getHeight(); - - if (y >= height()) { - break; + if (y >= height()) { + break; + } } } + // layout the messages at the bottom to determine the scrollbar thumb size int h = this->height() - 8; for (int i = messages.getLength() - 1; i >= 0; i--) { @@ -97,6 +95,10 @@ ChatWidgetView::layoutMessages() this->scrollbar.setVisible(showScrollbar); + if (!showScrollbar) { + this->scrollbar.setDesiredValue(0); + } + this->scrollbar.setMaximum(messages.getLength()); return redraw; @@ -109,10 +111,12 @@ ChatWidgetView::resizeEvent(QResizeEvent *) this->scrollbar.move(width() - this->scrollbar.width(), 0); layoutMessages(); + + update(); } void -ChatWidgetView::paintEvent(QPaintEvent *) +ChatWidgetView::paintEvent(QPaintEvent *event) { QPainter _painter(this); @@ -120,6 +124,24 @@ ChatWidgetView::paintEvent(QPaintEvent *) ColorScheme &scheme = ColorScheme::getInstance(); + // only update gif emotes + if (onlyUpdateEmotes) { + onlyUpdateEmotes = false; + + for (GifEmoteData &item : this->gifEmotes) { + _painter.fillRect(item.rect, scheme.ChatBackground); + + _painter.drawPixmap(item.rect, *item.image->getPixmap()); + } + + return; + } + + // update all messages + gifEmotes.clear(); + + _painter.fillRect(rect(), scheme.ChatBackground); + // code for tesing colors /* QColor color; @@ -178,16 +200,13 @@ ChatWidgetView::paintEvent(QPaintEvent *) updateBuffer = true; } + // update messages that have been changed if (updateBuffer) { QPainter painter(buffer); painter.fillRect(buffer->rect(), scheme.ChatBackground); for (messages::WordPart const &wordPart : messageRef->getWordParts()) { - painter.setPen(QColor(255, 0, 0)); - painter.drawRect(wordPart.getX(), wordPart.getY(), - wordPart.getWidth(), wordPart.getHeight()); - // image if (wordPart.getWord().isImage()) { messages::LazyLoadedImage &lli = @@ -221,6 +240,24 @@ ChatWidgetView::paintEvent(QPaintEvent *) messageRef->updateBuffer = false; } + // get gif emotes + for (messages::WordPart const &wordPart : messageRef->getWordParts()) { + if (wordPart.getWord().isImage()) { + messages::LazyLoadedImage &lli = wordPart.getWord().getImage(); + + if (lli.getAnimated()) { + GifEmoteData data; + data.image = &lli; + QRect rect(wordPart.getX(), wordPart.getY() + y, + wordPart.getWidth(), wordPart.getHeight()); + + data.rect = rect; + + gifEmotes.push_back(data); + } + } + } + messageRef->buffer = bufferPtr; _painter.drawPixmap(0, y, *buffer); @@ -231,16 +268,89 @@ ChatWidgetView::paintEvent(QPaintEvent *) break; } } + + for (GifEmoteData &item : this->gifEmotes) { + _painter.fillRect(item.rect, scheme.ChatBackground); + + _painter.drawPixmap(item.rect, *item.image->getPixmap()); + } } void ChatWidgetView::wheelEvent(QWheelEvent *event) { - this->scrollbar.setDesiredValue( - this->scrollbar.getDesiredValue() - - event->delta() / 10.0 * - Settings::getInstance().mouseScrollMultiplier.get(), - true); + if (this->scrollbar.isVisible()) { + this->scrollbar.setDesiredValue( + this->scrollbar.getDesiredValue() - + event->delta() / 10.0 * + Settings::getInstance().mouseScrollMultiplier.get(), + 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 086a12f8..fdbf834b 100644 --- a/widgets/chatwidgetview.h +++ b/widgets/chatwidgetview.h @@ -2,6 +2,7 @@ #define CHATVIEW_H #include "channel.h" +#include "messages/lazyloadedimage.h" #include "messages/messageref.h" #include "messages/word.h" #include "widgets/scrollbar.h" @@ -25,24 +26,44 @@ public: bool layoutMessages(); + void + updateGifEmotes() + { + this->onlyUpdateEmotes = true; + this->update(); + } + protected: void resizeEvent(QResizeEvent *); 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; + QRect rect; + }; + + std::vector gifEmotes; + ChatWidget *chatWidget; ScrollBar scrollbar; + bool onlyUpdateEmotes; private slots: void wordTypeMaskChanged() { - if (layoutMessages()) { - update(); - } + layoutMessages(); + update(); } }; } diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp index a910938a..f684ffa1 100644 --- a/widgets/mainwindow.cpp +++ b/widgets/mainwindow.cpp @@ -44,7 +44,7 @@ MainWindow::layoutVisibleChatWidgets(Channel *channel) if (channel == NULL || channel == widget->getChannel().get()) { if (widget->getView().layoutMessages()) { - widget->update(); + widget->getView().update(); } } } @@ -66,11 +66,29 @@ MainWindow::repaintVisibleChatWidgets(Channel *channel) if (channel == NULL || channel == widget->getChannel().get()) { widget->getView().layoutMessages(); - widget->update(); + widget->getView().update(); } } } +void +MainWindow::repaintGifEmotes() +{ + auto *page = notebook.getSelectedPage(); + + if (page == NULL) { + return; + } + + const std::vector &widgets = page->getChatWidgets(); + + for (auto it = widgets.begin(); it != widgets.end(); ++it) { + ChatWidget *widget = *it; + + widget->getView().updateGifEmotes(); + } +} + void MainWindow::load(const boost::property_tree::ptree &tree) { diff --git a/widgets/mainwindow.h b/widgets/mainwindow.h index 560dd4a7..e77e587d 100644 --- a/widgets/mainwindow.h +++ b/widgets/mainwindow.h @@ -20,6 +20,7 @@ public: void layoutVisibleChatWidgets(Channel *channel = NULL); void repaintVisibleChatWidgets(Channel *channel = NULL); + void repaintGifEmotes(); void load(const boost::property_tree::ptree &tree); boost::property_tree::ptree save(); diff --git a/widgets/notebook.cpp b/widgets/notebook.cpp index 9030e886..5c110db8 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; } diff --git a/windows.cpp b/windows.cpp index 317deedb..0d298dbe 100644 --- a/windows.cpp +++ b/windows.cpp @@ -38,6 +38,14 @@ Windows::repaintVisibleChatWidgets(Channel *channel) } } +void +Windows::repaintGifEmotes() +{ + if (Windows::mainWindow != nullptr) { + Windows::mainWindow->repaintGifEmotes(); + } +} + void Windows::updateAll() { diff --git a/windows.h b/windows.h index 0265815d..49aa73c0 100644 --- a/windows.h +++ b/windows.h @@ -12,6 +12,7 @@ class Windows public: static void layoutVisibleChatWidgets(Channel *channel = NULL); static void repaintVisibleChatWidgets(Channel *channel = NULL); + static void repaintGifEmotes(); static void updateAll(); static widgets::MainWindow &