diff --git a/chatterino.pro b/chatterino.pro index 9b9a89bb5..ecb8cf4b2 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -58,11 +58,8 @@ SOURCES += \ src/channel.cpp \ src/channeldata.cpp \ src/singletons/ircmanager.cpp \ - src/messages/lazyloadedimage.cpp \ src/messages/link.cpp \ src/messages/message.cpp \ - src/messages/word.cpp \ - src/messages/wordpart.cpp \ src/widgets/notebook.cpp \ src/widgets/helper/notebookbutton.cpp \ src/widgets/helper/notebooktab.cpp \ @@ -71,7 +68,6 @@ SOURCES += \ src/widgets/settingsdialog.cpp \ src/widgets/helper/settingsdialogtab.cpp \ src/widgets/textinputdialog.cpp \ - src/messages/messageref.cpp \ src/logging/loggingmanager.cpp \ src/logging/loggingchannel.cpp \ src/singletons/windowmanager.cpp \ @@ -115,7 +111,12 @@ SOURCES += \ src/singletons/resourcemanager.cpp \ src/singletons/helper/ircmessagehandler.cpp \ src/singletons/pathmanager.cpp \ - src/widgets/helper/searchpopup.cpp + src/widgets/helper/searchpopup.cpp \ + src/messages/messageelement.cpp \ + src/messages/image.cpp \ + src/messages/layouts/messagelayout.cpp \ + src/messages/layouts/messagelayoutelement.cpp \ + src/messages/layouts/messagelayoutcontainer.cpp HEADERS += \ src/precompiled_headers.hpp \ @@ -124,11 +125,8 @@ HEADERS += \ src/util/concurrentmap.hpp \ src/emojis.hpp \ src/singletons/ircmanager.hpp \ - src/messages/lazyloadedimage.hpp \ src/messages/link.hpp \ src/messages/message.hpp \ - src/messages/word.hpp \ - src/messages/wordpart.hpp \ src/twitch/emotevalue.hpp \ src/widgets/notebook.hpp \ src/widgets/helper/notebookbutton.hpp \ @@ -142,7 +140,6 @@ HEADERS += \ src/widgets/helper/resizingtextedit.hpp \ src/messages/limitedqueue.hpp \ src/messages/limitedqueuesnapshot.hpp \ - src/messages/messageref.hpp \ src/logging/loggingmanager.hpp \ src/logging/loggingchannel.hpp \ src/singletons/channelmanager.hpp \ @@ -200,7 +197,13 @@ HEADERS += \ src/messages/selection.hpp \ src/singletons/pathmanager.hpp \ src/widgets/helper/searchpopup.hpp \ - src/widgets/helper/shortcut.hpp + src/widgets/helper/shortcut.hpp \ + src/messages/messageelement.hpp \ + src/messages/image.hpp \ + src/messages/layouts/messagelayout.hpp \ + src/messages/layouts/messagelayoutelement.hpp \ + src/messages/layouts/messagelayoutcontainer.hpp \ + src/util/property.hpp PRECOMPILED_HEADER = diff --git a/src/channel.cpp b/src/channel.cpp index d1e94fe5b..7acf19d79 100644 --- a/src/channel.cpp +++ b/src/channel.cpp @@ -29,14 +29,14 @@ bool Channel::isEmpty() const return false; } -messages::LimitedQueueSnapshot Channel::getMessageSnapshot() +messages::LimitedQueueSnapshot Channel::getMessageSnapshot() { return this->messages.getSnapshot(); } -void Channel::addMessage(std::shared_ptr message) +void Channel::addMessage(MessagePtr message) { - std::shared_ptr deleted; + MessagePtr deleted; const QString &username = message->loginName; @@ -56,16 +56,16 @@ void Channel::addMessage(std::shared_ptr message) this->messageAppended(message); } -void Channel::addMessagesAtStart(std::vector &_messages) +void Channel::addMessagesAtStart(std::vector &_messages) { - std::vector addedMessages = this->messages.pushFront(_messages); + std::vector addedMessages = this->messages.pushFront(_messages); if (addedMessages.size() != 0) { this->messagesAddedAtStart(addedMessages); } } -void Channel::replaceMessage(messages::SharedMessage message, messages::SharedMessage replacement) +void Channel::replaceMessage(messages::MessagePtr message, messages::MessagePtr replacement) { int index = this->messages.replaceItem(message, replacement); diff --git a/src/channel.hpp b/src/channel.hpp index 920987c08..51aa307c2 100644 --- a/src/channel.hpp +++ b/src/channel.hpp @@ -1,7 +1,7 @@ #pragma once #include "logging/loggingchannel.hpp" -#include "messages/lazyloadedimage.hpp" +#include "messages/image.hpp" #include "messages/limitedqueue.hpp" #include "util/concurrentmap.hpp" @@ -24,17 +24,17 @@ class Channel : public std::enable_shared_from_this public: explicit Channel(const QString &_name); - boost::signals2::signal messageRemovedFromStart; - boost::signals2::signal messageAppended; - boost::signals2::signal &)> messagesAddedAtStart; - boost::signals2::signal messageReplaced; + boost::signals2::signal messageRemovedFromStart; + boost::signals2::signal messageAppended; + boost::signals2::signal &)> messagesAddedAtStart; + boost::signals2::signal messageReplaced; virtual bool isEmpty() const; - messages::LimitedQueueSnapshot getMessageSnapshot(); + messages::LimitedQueueSnapshot getMessageSnapshot(); - void addMessage(messages::SharedMessage message); - void addMessagesAtStart(std::vector &messages); - void replaceMessage(messages::SharedMessage message, messages::SharedMessage replacement); + void addMessage(messages::MessagePtr message); + void addMessagesAtStart(std::vector &messages); + void replaceMessage(messages::MessagePtr message, messages::MessagePtr replacement); void addRecentChatter(const std::shared_ptr &message); struct NameOptions { @@ -55,9 +55,11 @@ public: virtual void sendMessage(const QString &message); private: - messages::LimitedQueue messages; + messages::LimitedQueue messages; // std::shared_ptr loggingChannel; }; +typedef std::shared_ptr SharedChannel; + } // namespace chatterino diff --git a/src/channeldata.hpp b/src/channeldata.hpp index d3d122506..744f8455b 100644 --- a/src/channeldata.hpp +++ b/src/channeldata.hpp @@ -1,7 +1,7 @@ #pragma once #include "lockedobject.hpp" -#include "messages/lazyloadedimage.hpp" +#include "messages/image.hpp" namespace chatterino { diff --git a/src/emojis.hpp b/src/emojis.hpp index b37eae68f..e74fc4438 100644 --- a/src/emojis.hpp +++ b/src/emojis.hpp @@ -1,6 +1,6 @@ #pragma once -#include "messages/lazyloadedimage.hpp" +#include "messages/image.hpp" #include "util/concurrentmap.hpp" #include diff --git a/src/logging/loggingchannel.cpp b/src/logging/loggingchannel.cpp index 4f2e7420c..95df696c1 100644 --- a/src/logging/loggingchannel.cpp +++ b/src/logging/loggingchannel.cpp @@ -39,7 +39,7 @@ void Channel::append(std::shared_ptr message) str.append("] "); str.append(message->loginName); str.append(": "); - str.append(message->getContent()); + str.append(message->getSearchText()); str.append('\n'); this->appendLine(str); } diff --git a/src/main.cpp b/src/main.cpp index 350a59281..7aa016c2d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,9 @@ int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::AA_Use96Dpi, true); +#ifdef Q_OS_WIN32 QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true); +#endif QApplication::setAttribute(Qt::AA_UseSoftwareOpenGL, true); QApplication a(argc, argv); diff --git a/src/messages/lazyloadedimage.cpp b/src/messages/image.cpp similarity index 73% rename from src/messages/lazyloadedimage.cpp rename to src/messages/image.cpp index bca802a7f..6e637ec6f 100644 --- a/src/messages/lazyloadedimage.cpp +++ b/src/messages/image.cpp @@ -1,4 +1,4 @@ -#include "messages/lazyloadedimage.hpp" +#include "messages/image.hpp" #include "asyncexec.hpp" #include "singletons/emotemanager.hpp" #include "singletons/ircmanager.hpp" @@ -19,8 +19,8 @@ namespace chatterino { namespace messages { -LazyLoadedImage::LazyLoadedImage(const QString &url, qreal scale, const QString &name, - const QString &tooltip, const QMargins &margin, bool isHat) +Image::Image(const QString &url, qreal scale, const QString &name, const QString &tooltip, + const QMargins &margin, bool isHat) : currentPixmap(nullptr) , url(url) , name(name) @@ -32,8 +32,8 @@ LazyLoadedImage::LazyLoadedImage(const QString &url, qreal scale, const QString { } -LazyLoadedImage::LazyLoadedImage(QPixmap *image, qreal scale, const QString &name, - const QString &tooltip, const QMargins &margin, bool isHat) +Image::Image(QPixmap *image, qreal scale, const QString &name, const QString &tooltip, + const QMargins &margin, bool isHat) : currentPixmap(image) , name(name) , tooltip(tooltip) @@ -44,7 +44,7 @@ LazyLoadedImage::LazyLoadedImage(QPixmap *image, qreal scale, const QString &nam { } -void LazyLoadedImage::loadImage() +void Image::loadImage() { util::NetworkRequest req(this->getUrl()); req.setCaller(this); @@ -67,7 +67,7 @@ void LazyLoadedImage::loadImage() lli->currentPixmap = pixmap; } - chatterino::messages::LazyLoadedImage::FrameData data; + chatterino::messages::Image::FrameData data; data.duration = std::max(20, reader.nextImageDelay()); data.image = pixmap; @@ -90,7 +90,7 @@ void LazyLoadedImage::loadImage() // doesn't work, so this is the fix. } -void LazyLoadedImage::gifUpdateTimout() +void Image::gifUpdateTimout() { if (animated) { this->currentFrameOffset += GIF_FRAME_LENGTH; @@ -108,7 +108,7 @@ void LazyLoadedImage::gifUpdateTimout() } } -const QPixmap *LazyLoadedImage::getPixmap() +const QPixmap *Image::getPixmap() { if (!this->isLoading) { this->isLoading = true; @@ -118,42 +118,42 @@ const QPixmap *LazyLoadedImage::getPixmap() return this->currentPixmap; } -qreal LazyLoadedImage::getScale() const +qreal Image::getScale() const { return this->scale; } -const QString &LazyLoadedImage::getUrl() const +const QString &Image::getUrl() const { return this->url; } -const QString &LazyLoadedImage::getName() const +const QString &Image::getName() const { return this->name; } -const QString &LazyLoadedImage::getTooltip() const +const QString &Image::getTooltip() const { return this->tooltip; } -const QMargins &LazyLoadedImage::getMargin() const +const QMargins &Image::getMargin() const { return this->margin; } -bool LazyLoadedImage::getAnimated() const +bool Image::isAnimated() const { return this->animated; } -bool LazyLoadedImage::isHat() const +bool Image::isHat() const { return this->ishat; } -int LazyLoadedImage::getWidth() const +int Image::getWidth() const { if (this->currentPixmap == nullptr) { return 16; @@ -161,12 +161,12 @@ int LazyLoadedImage::getWidth() const return this->currentPixmap->width(); } -int LazyLoadedImage::getScaledWidth() const +int Image::getScaledWidth() const { - return static_cast(getWidth() * this->scale); + return static_cast(this->getWidth() * this->scale); } -int LazyLoadedImage::getHeight() const +int Image::getHeight() const { if (this->currentPixmap == nullptr) { return 16; @@ -174,9 +174,9 @@ int LazyLoadedImage::getHeight() const return this->currentPixmap->height(); } -int LazyLoadedImage::getScaledHeight() const +int Image::getScaledHeight() const { - return static_cast(getHeight() * this->scale); + return static_cast(this->getHeight() * this->scale); } } // namespace messages diff --git a/src/messages/lazyloadedimage.hpp b/src/messages/image.hpp similarity index 61% rename from src/messages/lazyloadedimage.hpp rename to src/messages/image.hpp index 96a9f61da..48c649c19 100644 --- a/src/messages/lazyloadedimage.hpp +++ b/src/messages/image.hpp @@ -3,21 +3,23 @@ #include #include +#include + namespace chatterino { namespace messages { -class LazyLoadedImage : public QObject +class Image : public QObject, boost::noncopyable { public: - LazyLoadedImage() = delete; + Image() = delete; - explicit LazyLoadedImage(const QString &_url, qreal _scale = 1, const QString &_name = "", - const QString &_tooltip = "", const QMargins &_margin = QMargins(), - bool isHat = false); + explicit Image(const QString &_url, qreal _scale = 1, const QString &_name = "", + const QString &_tooltip = "", const QMargins &_margin = QMargins(), + bool isHat = false); - explicit LazyLoadedImage(QPixmap *_currentPixmap, qreal _scale = 1, const QString &_name = "", - const QString &_tooltip = "", const QMargins &_margin = QMargins(), - bool isHat = false); + explicit Image(QPixmap *_currentPixmap, qreal _scale = 1, const QString &_name = "", + const QString &_tooltip = "", const QMargins &_margin = QMargins(), + bool isHat = false); const QPixmap *getPixmap(); qreal getScale() const; @@ -25,7 +27,7 @@ public: const QString &getName() const; const QString &getTooltip() const; const QMargins &getMargin() const; - bool getAnimated() const; + bool isAnimated() const; bool isHat() const; int getWidth() const; int getScaledWidth() const; diff --git a/src/messages/layouts/messagelayout.cpp b/src/messages/layouts/messagelayout.cpp new file mode 100644 index 000000000..3ad347a77 --- /dev/null +++ b/src/messages/layouts/messagelayout.cpp @@ -0,0 +1,345 @@ +#include "messages/layouts/messagelayout.hpp" +#include "singletons/emotemanager.hpp" +#include "singletons/settingsmanager.hpp" + +#include +#include +#include +#include +#include + +#define MARGIN_LEFT (int)(8 * this->scale) +#define MARGIN_RIGHT (int)(8 * this->scale) +#define MARGIN_TOP (int)(4 * this->scale) +#define MARGIN_BOTTOM (int)(4 * this->scale) +#define COMPACT_EMOTES_OFFSET 6 + +namespace chatterino { +namespace messages { +namespace layouts { + +MessageLayout::MessageLayout(MessagePtr _message) + : message(_message) + , buffer(nullptr) +{ + if (_message->hasFlags(Message::Collapsed)) { + this->addFlags(MessageLayout::Collapsed); + } +} + +Message *MessageLayout::getMessage() +{ + return this->message.get(); +} + +// Height +int MessageLayout::getHeight() const +{ + return container.getHeight(); +} + +// Flags +MessageLayout::Flags MessageLayout::getFlags() const +{ + return this->flags; +} + +bool MessageLayout::hasFlags(Flags _flags) const +{ + return this->flags & _flags; +} + +void MessageLayout::addFlags(Flags _flags) +{ + this->flags = (Flags)((MessageLayoutFlagsType)this->flags | (MessageLayoutFlagsType)_flags); +} + +void MessageLayout::removeFlags(Flags _flags) +{ + this->flags = (Flags)((MessageLayoutFlagsType)this->flags & ~((MessageLayoutFlagsType)_flags)); +} + +// Layout +// return true if redraw is required +bool MessageLayout::layout(int width, float scale) +{ + auto &emoteManager = singletons::EmoteManager::getInstance(); + + bool layoutRequired = false; + + // check if width changed + bool widthChanged = width != this->currentLayoutWidth; + layoutRequired |= widthChanged; + this->currentLayoutWidth = width; + + // check if emotes changed + bool imagesChanged = this->emoteGeneration != emoteManager.getGeneration(); + layoutRequired |= imagesChanged; + this->emoteGeneration = emoteManager.getGeneration(); + + // check if text changed + bool textChanged = + this->fontGeneration != singletons::FontManager::getInstance().getGeneration(); + layoutRequired |= textChanged; + this->fontGeneration = singletons::FontManager::getInstance().getGeneration(); + + // check if work mask changed + bool wordMaskChanged = + this->currentWordTypes != singletons::SettingManager::getInstance().getWordTypeMask(); + layoutRequired |= wordMaskChanged; + this->currentWordTypes = singletons::SettingManager::getInstance().getWordTypeMask(); + + // check if dpi changed + bool scaleChanged = this->scale != scale; + layoutRequired |= scaleChanged; + this->scale = scale; + imagesChanged |= scaleChanged; + textChanged |= scaleChanged; + + // update word sizes if needed + if (imagesChanged) { + // fourtf: update images + this->addFlags(MessageLayout::RequiresBufferUpdate); + } + if (textChanged) { + // fourtf: update text + this->addFlags(MessageLayout::RequiresBufferUpdate); + } + if (widthChanged || wordMaskChanged) { + this->deleteBuffer(); + } + + // return if no layout is required + if (!layoutRequired) { + return false; + } + + this->actuallyLayout(width); + this->invalidateBuffer(); + + return true; +} + +void MessageLayout::actuallyLayout(int width) +{ + this->container.clear(); + this->container.width = width; + this->container.scale = this->scale; + + for (const std::unique_ptr &element : this->message->getElements()) { + element->addToContainer(this->container, MessageElement::Default); + } + + if (this->height != this->container.getHeight()) { + this->deleteBuffer(); + } + + this->container.finish(); + this->height = this->container.getHeight(); +} + +// Painting +void MessageLayout::paint(QPainter &painter, int y, int messageIndex, Selection &selection) +{ + QPixmap *pixmap = this->buffer.get(); + + // create new buffer if required + if (!pixmap) { +#ifdef Q_OS_MACOS + + pixmap = + new QPixmap((int)(this->container.getWidth() * painter.device()->devicePixelRatioF()), + (int)(this->container.getHeight() * painter.device()->devicePixelRatioF())); + pixmap->setDevicePixelRatio(painter.device()->devicePixelRatioF()); +#else + pixmap = new QPixmap(this->container.width, std::max(16, this->container.getHeight())); +#endif + + this->buffer = std::shared_ptr(pixmap); + this->bufferValid = false; + } + + if (!this->bufferValid) { + this->updateBuffer(pixmap, messageIndex, selection); + } + + // draw on buffer + painter.drawPixmap(0, y, pixmap->width(), pixmap->height(), *pixmap); + + this->bufferValid = true; +} + +void MessageLayout::updateBuffer(QPixmap *buffer, int messageIndex, Selection &selection) +{ + singletons::ThemeManager &themeManager = singletons::ThemeManager::getInstance(); + + QPainter painter(buffer); + + painter.setRenderHint(QPainter::SmoothPixmapTransform); + + // draw background + painter.fillRect(buffer->rect(), + this->message->hasFlags(Message::Highlighted) + ? themeManager.messages.backgrounds.highlighted + : themeManager.messages.backgrounds.regular); + + // draw selection + if (!selection.isEmpty()) { + this->container.paintSelection(painter, messageIndex, selection); + } + + // draw message + this->container.paintElements(painter); + + // debug + painter.setPen(QColor(255, 0, 0)); + painter.drawRect(buffer->rect().x(), buffer->rect().y(), buffer->rect().width() - 1, + buffer->rect().height() - 1); + + QTextOption option; + option.setAlignment(Qt::AlignRight | Qt::AlignTop); + + painter.drawText(QRectF(1, 1, this->container.width - 3, 1000), + QString::number(++this->bufferUpdatedCount), option); +} + +void MessageLayout::invalidateBuffer() +{ + this->bufferValid = false; +} + +void MessageLayout::deleteBuffer() +{ + this->buffer = nullptr; +} + +// Elements +// assert(QThread::currentThread() == QApplication::instance()->thread()); + +// returns nullptr if none was found + +// fourtf: this should return a MessageLayoutItem +const MessageLayoutElement *MessageLayout::getElementAt(QPoint point) +{ + // go through all words and return the first one that contains the point. + return this->container.getElementAt(point); +} + +// XXX(pajlada): This is probably not the optimal way to calculate this +int MessageLayout::getLastCharacterIndex() const +{ + // fourtf: xD + // // find out in which line the cursor is + // int lineNumber = 0, lineStart = 0, lineEnd = 0; + + // for (size_t i = 0; i < this->wordParts.size(); i++) { + // const LayoutItem &part = this->wordParts[i]; + + // if (part.getLineNumber() != lineNumber) { + // lineStart = i; + // lineNumber = part.getLineNumber(); + // } + + // lineEnd = i + 1; + // } + + // // count up to the cursor + // int index = 0; + + // for (int i = 0; i < lineStart; i++) { + // const LayoutItem &part = this->wordParts[i]; + + // index += part.getWord().isImage() ? 2 : part.getText().length() + 1; + // } + + // for (int i = lineStart; i < lineEnd; i++) { + // const LayoutItem &part = this->wordParts[i]; + + // index += part.getCharacterLength(); + // } + + // return index; + + return 0; +} + +int MessageLayout::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 (size_t i = 0; i < this->wordParts.size(); i++) { + // LayoutItem &part = this->wordParts[i]; + + // if (part.getLineNumber() != 0 && position.y() < part.getY()) { + // break; + // } + + // if (part.getLineNumber() != lineNumber) { + // lineStart = i; + // lineNumber = part.getLineNumber(); + // } + + // lineEnd = i + 1; + // } + + // // count up to the cursor + // int index = 0; + + // for (int i = 0; i < lineStart; i++) { + // LayoutItem &part = this->wordParts[i]; + + // index += part.getWord().isImage() ? 2 : part.getText().length() + 1; + // } + + // for (int i = lineStart; i < lineEnd; i++) { + // LayoutItem &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() + part.getWidth()) { + // index += part.getCharacterLength(); + // continue; + // } + + // // cursor is over the word part + // if (part.getWord().isImage()) { + // if (position.x() - part.getX() > part.getWidth() / 2) { + // index++; + // } + // } else { + // // TODO: use word.getCharacterWidthCache(); + + // auto text = part.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(this->scale).width(text, j + + // 1); + // } + // } + + // break; + // } + + // return index; + + return 0; +} +} // namespace layouts +} // namespace messages +} // namespace chatterino diff --git a/src/messages/layouts/messagelayout.hpp b/src/messages/layouts/messagelayout.hpp new file mode 100644 index 000000000..5fe40fb26 --- /dev/null +++ b/src/messages/layouts/messagelayout.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include "messages/layouts/messagelayoutcontainer.hpp" +#include "messages/layouts/messagelayoutelement.hpp" +#include "messages/message.hpp" +#include "messages/selection.hpp" + +#include + +#include +#include +#include + +namespace chatterino { +namespace messages { +namespace layouts { + +class MessageLayout; +typedef std::shared_ptr MessageLayoutPtr; +typedef uint8_t MessageLayoutFlagsType; + +class MessageLayout : boost::noncopyable +{ +public: + enum Flags : MessageLayoutFlagsType { Collapsed, RequiresBufferUpdate, RequiresLayout }; + + MessageLayout(MessagePtr message); + + Message *getMessage(); + + // Height + int getHeight() const; + + // Flags + Flags getFlags() const; + bool hasFlags(Flags flags) const; + void addFlags(Flags flags); + void removeFlags(Flags flags); + + // Layout + bool layout(int width, float scale); + + // Painting + void paint(QPainter &painter, int y, int messageIndex, Selection &selection); + void invalidateBuffer(); + void deleteBuffer(); + + // Elements + const MessageLayoutElement *getElementAt(QPoint point); + int getLastCharacterIndex() const; + int getSelectionIndex(QPoint position); + + // Misc + bool isDisabled() const; + +private: + // variables + MessagePtr message; + MessageLayoutContainer container; + std::shared_ptr buffer = nullptr; + bool bufferValid = false; + Flags flags; + + int height = 0; + + int currentLayoutWidth = -1; + int fontGeneration = -1; + int emoteGeneration = -1; + float scale = -1; + unsigned int bufferUpdatedCount = 0; + + MessageElement::Flags currentWordTypes = MessageElement::None; + + int collapsedHeight = 32; + + // methods + void actuallyLayout(int width); + void updateBuffer(QPixmap *pixmap, int messageIndex, Selection &selection); +}; + +} // namespace layouts +} // namespace messages +} // namespace chatterino diff --git a/src/messages/layouts/messagelayoutcontainer.cpp b/src/messages/layouts/messagelayoutcontainer.cpp new file mode 100644 index 000000000..823471d24 --- /dev/null +++ b/src/messages/layouts/messagelayoutcontainer.cpp @@ -0,0 +1,157 @@ +#include "messagelayoutcontainer.hpp" + +#include "messagelayoutelement.hpp" +#include "messages/selection.hpp" +#include "singletons/settingsmanager.hpp" + +#include + +#define COMPACT_EMOTES_OFFSET 6 + +namespace chatterino { +namespace messages { +namespace layouts { +MessageLayoutContainer::MessageLayoutContainer() + : scale(1) + , margin(4, 8, 4, 8) + , centered(false) +{ + this->clear(); +} + +int MessageLayoutContainer::getHeight() const +{ + return this->height; +} + +// methods +void MessageLayoutContainer::clear() +{ + this->elements.clear(); + + this->height = 0; + this->line = 0; + this->currentX = 0; + this->currentY = 0; + this->lineStart = 0; + this->lineHeight = 0; +} + +void MessageLayoutContainer::addElement(MessageLayoutElement *element) +{ + if (!this->fitsInLine(element->getRect().width())) { + this->breakLine(); + } + + this->_addElement(element); +} + +void MessageLayoutContainer::addElementNoLineBreak(MessageLayoutElement *element) +{ + this->_addElement(element); +} + +void MessageLayoutContainer::_addElement(MessageLayoutElement *element) +{ + if (this->elements.size() == 0) { + this->currentY = this->margin.top * this->scale; + } + + int newLineHeight = element->getRect().height(); + + // fourtf: xD + // bool compactEmotes = true; + // if (compactEmotes && element->word.isImage() && word.getFlags() & + // MessageElement::EmoteImages) { + // newLineHeight -= COMPACT_EMOTES_OFFSET * this->scale; + // } + + this->lineHeight = std::max(this->lineHeight, newLineHeight); + + element->setPosition(QPoint(this->currentX, this->currentY - element->getRect().height())); + this->elements.push_back(std::unique_ptr(element)); + + this->currentX += element->getRect().width(); + + if (element->hasTrailingSpace()) { + this->currentX += this->spaceWidth; + } +} + +void MessageLayoutContainer::breakLine() +{ + int xOffset = 0; + + if (this->centered && this->elements.size() > 0) { + xOffset = (width - 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 = false; + + // fourtf: xD + // this->enableCompactEmotes && element->getWord().isImage() && + // element->getWord().getFlags() & + // MessageElement::EmoteImages; + + int yExtra = 0; + if (isCompactEmote) { + yExtra = (COMPACT_EMOTES_OFFSET / 2) * this->scale; + } + + element->setPosition(QPoint(element->getRect().x() + xOffset + this->margin.left, + element->getRect().y() + this->lineHeight + yExtra)); + } + + this->lineStart = this->elements.size(); + this->currentX = 0; + this->currentY += this->lineHeight; + this->height = this->currentY + (this->margin.bottom * this->scale); + this->lineHeight = 0; +} + +bool MessageLayoutContainer::atStartOfLine() +{ + return this->lineStart == this->elements.size(); +} + +bool MessageLayoutContainer::fitsInLine(int _width) +{ + return this->currentX + _width <= this->width - this->margin.left - this->margin.right; +} + +void MessageLayoutContainer::finish() +{ + if (!this->atStartOfLine()) { + this->breakLine(); + } +} + +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) +{ + for (const std::unique_ptr &element : this->elements) { + element->paint(painter); + } +} + +void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex, + Selection &selection) +{ +} +} // namespace layouts +} +} diff --git a/src/messages/layouts/messagelayoutcontainer.hpp b/src/messages/layouts/messagelayoutcontainer.hpp new file mode 100644 index 000000000..b642ed093 --- /dev/null +++ b/src/messages/layouts/messagelayoutcontainer.hpp @@ -0,0 +1,84 @@ +#pragma once + +#include +#include + +#include + +class QPainter; + +namespace chatterino { +namespace messages { +class Selection; + +namespace layouts { +class MessageLayoutElement; + +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) + { + } +}; + +class MessageLayoutContainer +{ +public: + MessageLayoutContainer(); + + float scale; + Margin margin; + bool centered; + bool enableCompactEmotes; + int width; + + int getHeight() const; + + // methods + void clear(); + void addElement(MessageLayoutElement *element); + void addElementNoLineBreak(MessageLayoutElement *element); + void breakLine(); + bool atStartOfLine(); + bool fitsInLine(int width); + void finish(); + MessageLayoutElement *getElementAt(QPoint point); + + // painting + void paintElements(QPainter &painter); + void paintSelection(QPainter &painter, int messageIndex, Selection &selection); + +private: + // helpers + void _addElement(MessageLayoutElement *element); + + // variables + int line; + int height; + int currentX, currentY; + size_t lineStart = 0; + int lineHeight = 0; + int spaceWidth = 4; + std::vector> elements; +}; +} // namespace layouts +} +} diff --git a/src/messages/layouts/messagelayoutelement.cpp b/src/messages/layouts/messagelayoutelement.cpp new file mode 100644 index 000000000..bd01a7428 --- /dev/null +++ b/src/messages/layouts/messagelayoutelement.cpp @@ -0,0 +1,132 @@ +#include "messages/layouts/messagelayoutelement.hpp" +#include "messages/messageelement.hpp" + +#include + +namespace chatterino { +namespace messages { +namespace layouts { + +const QRect &MessageLayoutElement::getRect() const +{ + return this->rect; +} + +MessageLayoutElement::MessageLayoutElement(MessageElement &_creator, const QSize &size) + : creator(_creator) +{ + this->rect.setSize(size); +} + +MessageElement &MessageLayoutElement::getCreator() const +{ + return this->creator; +} + +void MessageLayoutElement::setPosition(QPoint point) +{ + this->rect.moveTopLeft(point); +} + +bool MessageLayoutElement::hasTrailingSpace() const +{ + return this->trailingSpace; +} + +MessageLayoutElement *MessageLayoutElement::setTrailingSpace(bool value) +{ + this->trailingSpace = value; + + return this; +} + +// +// IMAGE +// + +ImageLayoutElement::ImageLayoutElement(MessageElement &_creator, Image &_image, QSize _size) + : MessageLayoutElement(_creator, _size) + , image(_image) +{ + this->trailingSpace = _creator.hasTrailingSpace(); +} + +void ImageLayoutElement::addCopyTextToString(QString &str, int from, int to) const +{ + str += ""; +} + +int ImageLayoutElement::getSelectionIndexCount() +{ + return this->trailingSpace ? 2 : 1; +} + +void ImageLayoutElement::paint(QPainter &painter) +{ + const QPixmap *pixmap = this->image.getPixmap(); + + if (pixmap != nullptr && !this->image.isAnimated()) { + // fourtf: make it use qreal values + painter.drawPixmap(QRectF(this->getRect()), *pixmap, QRectF()); + } +} + +void ImageLayoutElement::paintAnimated(QPainter &painter) +{ + if (this->image.isAnimated()) { + if (this->image.getPixmap() != nullptr) { + // fourtf: make it use qreal values + painter.drawPixmap(QRectF(this->getRect()), *this->image.getPixmap(), QRectF()); + } + } +} + +int ImageLayoutElement::getMouseOverIndex(const QPoint &abs) +{ + return 0; +} + +// +// TEXT +// + +TextLayoutElement::TextLayoutElement(MessageElement &_creator, QString &_text, QSize _size, + QColor _color, FontStyle _style, float _scale) + : MessageLayoutElement(_creator, _size) + , text(_text) + , color(_color) + , style(_style) + , scale(_scale) +{ +} + +void TextLayoutElement::addCopyTextToString(QString &str, int from, int to) const +{ +} + +int TextLayoutElement::getSelectionIndexCount() +{ + return this->text.length() + (this->trailingSpace ? 1 : 0); +} + +void TextLayoutElement::paint(QPainter &painter) +{ + painter.setPen(this->color); + + painter.setFont(singletons::FontManager::getInstance().getFont(this->style, this->scale)); + + painter.drawText(QRectF(this->getRect().x(), this->getRect().y(), 10000, 10000), this->text, + QTextOption(Qt::AlignLeft | Qt::AlignTop)); +} + +void TextLayoutElement::paintAnimated(QPainter &painter) +{ +} + +int TextLayoutElement::getMouseOverIndex(const QPoint &abs) +{ + return 0; +} +} // namespace layouts +} // namespace messages +} // namespace chatterino diff --git a/src/messages/layouts/messagelayoutelement.hpp b/src/messages/layouts/messagelayoutelement.hpp new file mode 100644 index 000000000..3418f7d94 --- /dev/null +++ b/src/messages/layouts/messagelayoutelement.hpp @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include "messages/messagecolor.hpp" +#include "singletons/fontmanager.hpp" + +class QPainter; + +namespace chatterino { +namespace messages { +class MessageElement; +class Image; + +namespace layouts { + +class MessageLayoutElement : boost::noncopyable +{ +public: + MessageLayoutElement(MessageElement &creator, const QSize &size); + + const QRect &getRect() const; + MessageElement &getCreator() const; + void setPosition(QPoint point); + bool hasTrailingSpace() const; + + MessageLayoutElement *setTrailingSpace(bool value); + + virtual void addCopyTextToString(QString &str, int from = 0, int to = INT_MAX) const = 0; + virtual int getSelectionIndexCount() = 0; + virtual void paint(QPainter &painter) = 0; + virtual void paintAnimated(QPainter &painter) = 0; + virtual int getMouseOverIndex(const QPoint &abs) = 0; + +protected: + bool trailingSpace = true; + +private: + QRect rect; + // bool isInNewLine; + MessageElement &creator; +}; + +// IMAGE +class ImageLayoutElement : public MessageLayoutElement +{ +public: + ImageLayoutElement(MessageElement &creator, Image &image, QSize size); + +protected: + virtual void addCopyTextToString(QString &str, int from = 0, int to = INT_MAX) const override; + virtual int getSelectionIndexCount() override; + virtual void paint(QPainter &painter) override; + virtual void paintAnimated(QPainter &painter) override; + virtual int getMouseOverIndex(const QPoint &abs) override; + +private: + Image ℑ +}; + +// TEXT +class TextLayoutElement : public MessageLayoutElement +{ +public: + TextLayoutElement(MessageElement &creator, QString &text, QSize size, QColor color, + FontStyle style, float scale); + +protected: + virtual void addCopyTextToString(QString &str, int from = 0, int to = INT_MAX) const override; + virtual int getSelectionIndexCount() override; + virtual void paint(QPainter &painter) override; + virtual void paintAnimated(QPainter &painter) override; + virtual int getMouseOverIndex(const QPoint &abs) override; + +private: + QString text; + QColor color; + FontStyle style; + float scale; +}; +} // namespace layouts +} // namespace messages +} // namespace chatterino diff --git a/src/messages/limitedqueue.hpp b/src/messages/limitedqueue.hpp index 70f60ac21..07f6b3b35 100644 --- a/src/messages/limitedqueue.hpp +++ b/src/messages/limitedqueue.hpp @@ -171,7 +171,7 @@ public: { std::lock_guard lock(this->mutex); - int x = 0; + size_t x = 0; for (size_t i = 0; i < this->chunks->size(); i++) { Chunk &chunk = this->chunks->at(i); @@ -191,11 +191,12 @@ public: newChunk->at(j) = replacement; this->chunks->at(i) = newChunk; - return x; + return true; } x++; } } + return false; } // void insertAfter(const std::vector &items, const T &index) diff --git a/src/messages/message.cpp b/src/messages/message.cpp index 44e76202a..640c0f850 100644 --- a/src/messages/message.cpp +++ b/src/messages/message.cpp @@ -1,67 +1,34 @@ #include "messages/message.hpp" -#include "channel.hpp" -#include "emojis.hpp" -#include "messages/link.hpp" -#include "singletons/emotemanager.hpp" -#include "singletons/fontmanager.hpp" -#include "singletons/ircmanager.hpp" -#include "singletons/resourcemanager.hpp" -#include "singletons/thememanager.hpp" +#include "messageelement.hpp" #include "util/irchelpers.hpp" -#include -#include -#include - typedef chatterino::widgets::ScrollbarHighlight SBHighlight; namespace chatterino { namespace messages { -bool Message::containsHighlightedPhrase() const +// elements +void Message::addElement(MessageElement *element) { - return this->highlightTab; + this->elements.push_back(std::unique_ptr(element)); } -void Message::setHighlight(bool value) +const std::vector> &Message::getElements() const { - this->highlightTab = value; -} - -const QString &Message::getTimeoutUser() const -{ - return this->timeoutUser; -} - -int Message::getTimeoutCount() const -{ - return this->timeoutCount; -} - -const QString &Message::getContent() const -{ - if (this->content.isNull()) { - this->updateContent(); - } - - return this->content; -} - -const std::chrono::time_point &Message::getParseTime() const -{ - return this->parseTime; -} - -std::vector &Message::getWords() -{ - return this->words; + return this->elements; } +// Flags Message::MessageFlags Message::getFlags() const { return this->flags; } +bool Message::hasFlags(MessageFlags _flags) const +{ + return this->flags & _flags; +} + void Message::setFlags(MessageFlags _flags) { this->flags = flags; @@ -77,113 +44,74 @@ void Message::removeFlags(MessageFlags _flags) this->flags = (MessageFlags)((MessageFlagsType)this->flags & ~((MessageFlagsType)_flags)); } -bool Message::isDisabled() const +// Parse Time +const QTime &Message::getParseTime() const { - return this->disabled; -} - -void Message::setDisabled(bool value) -{ - this->disabled = value; + return this->parseTime; } +// Id const QString &Message::getId() const { return this->id; } -bool Message::getCollapsedDefault() const +void Message::setId(const QString &_id) { - return this->collapsedDefault; + this->id = _id; } -void Message::setCollapsedDefault(bool value) +// Search +const QString &Message::getSearchText() const { - this->collapsedDefault = value; -} - -bool Message::getDisableCompactEmotes() const -{ - return this->disableCompactEmotes; -} - -void Message::setDisableCompactEmotes(bool value) -{ - this->disableCompactEmotes = value; -} - -void Message::updateContent() const -{ - QString _content(""); - - bool first; - - for (const Word &word : this->words) { - if (!first) { - _content += ""; - } - - _content += word.getCopyText(); - first = false; - } - - this->content = _content; + // fourtf: asdf + // if (this->searchText.isNull()) { + // QString _content(""); + + // bool first; + + // for (const MessageElement &word : this->words) { + // if (!first) { + // _content += ""; + // } + + // _content += word.getCopyText(); + // first = false; + // } + + // this->searchText = _content; + // } + + // return this->searchText; + + static QString xd; + + return xd; } +// Highlight SBHighlight Message::getScrollBarHighlight() const { - if (this->getFlags() & Message::Highlighted) { + if (this->hasFlags(Message::Highlighted)) { return SBHighlight(SBHighlight::Highlight); } return SBHighlight(); } -namespace { - -void AddCurrentTimestamp(Message *message) +// Static +MessagePtr Message::createSystemMessage(const QString &text) { - std::time_t t; - time(&t); - char timeStampBuffer[69]; + MessagePtr message(new Message); - // Add word for timestamp with no seconds - strftime(timeStampBuffer, 69, "%H:%M", localtime(&t)); - QString timestampNoSeconds(timeStampBuffer); - message->getWords().push_back(Word(timestampNoSeconds, Word::TimestampNoSeconds, - MessageColor(MessageColor::System), - singletons::FontManager::Medium, QString(), QString())); - - // Add word for timestamp with seconds - strftime(timeStampBuffer, 69, "%H:%M:%S", localtime(&t)); - QString timestampWithSeconds(timeStampBuffer); - message->getWords().push_back(Word(timestampWithSeconds, Word::TimestampWithSeconds, - MessageColor(MessageColor::System), - singletons::FontManager::Medium, QString(), QString())); -} - -} // namespace - -/// Static -Message *Message::createSystemMessage(const QString &text) -{ - Message *message = new Message; - - AddCurrentTimestamp(message); - - QStringList words = text.split(' '); - - for (QString word : words) { - message->getWords().push_back(Word(word, Word::Flags::Default, - MessageColor(MessageColor::Type::System), - singletons::FontManager::Medium, word, QString())); - } + message->addElement(new TimestampElement(QTime::currentTime())); + message->addElement(new TextElement(text, MessageElement::Text, MessageColor::System)); message->addFlags(Message::System); - return message; + return MessagePtr(message); } -Message *Message::createTimeoutMessage(const QString &username, const QString &durationInSeconds, - const QString &reason, bool multipleTimes) +MessagePtr Message::createTimeoutMessage(const QString &username, const QString &durationInSeconds, + const QString &reason, bool multipleTimes) { QString text; @@ -207,7 +135,7 @@ Message *Message::createTimeoutMessage(const QString &username, const QString &d if (reason.length() > 0) { text.append(": \""); - text.append(ParseTagString(reason)); + text.append(util::ParseTagString(reason)); text.append("\""); } text.append("."); @@ -216,7 +144,7 @@ Message *Message::createTimeoutMessage(const QString &username, const QString &d text.append(" (multiple times)"); } - Message *message = Message::createSystemMessage(text); + MessagePtr message = Message::createSystemMessage(text); message->addFlags(Message::Timeout); message->timeoutUser = username; return message; diff --git a/src/messages/message.hpp b/src/messages/message.hpp index bc26a8150..51a290ad0 100644 --- a/src/messages/message.hpp +++ b/src/messages/message.hpp @@ -1,94 +1,88 @@ #pragma once -#include "messages/word.hpp" +#include "messages/messageelement.hpp" #include "widgets/helper/scrollbarhighlight.hpp" -#include +#include #include #include +#include + namespace chatterino { - -class Channel; - namespace messages { class Message; -typedef std::shared_ptr SharedMessage; -typedef char MessageFlagsType; +typedef std::shared_ptr MessagePtr; +typedef uint8_t MessageFlagsType; class Message { public: enum MessageFlags : MessageFlagsType { None = 0, - System = (1 << 1), - Timeout = (1 << 2), - Highlighted = (1 << 3), + System = (1 << 0), + Timeout = (1 << 1), + Highlighted = (1 << 2), + DoNotTriggerNotification = (1 << 3), // disable notification sound + Centered = (1 << 4), + Disabled = (1 << 5), + DisableCompactEmotes = (1 << 6), + Collapsed = (1 << 7), }; - bool containsHighlightedPhrase() const; - void setHighlight(bool value); - const QString &getTimeoutUser() const; - int getTimeoutCount() const; - const QString &getContent() const; - const std::chrono::time_point &getParseTime() const; - std::vector &getWords(); + // Elements + // Messages should not be added after the message is done initializing. + void addElement(MessageElement *element); + const std::vector> &getElements() const; + + // Message flags MessageFlags getFlags() const; + bool hasFlags(MessageFlags flags) const; void setFlags(MessageFlags flags); void addFlags(MessageFlags flags); void removeFlags(MessageFlags flags); - bool isDisabled() const; - void setDisabled(bool value); + + // Parse Time + const QTime &getParseTime() const; + + // Id const QString &getId() const; - bool getCollapsedDefault() const; - void setCollapsedDefault(bool value); - bool getDisableCompactEmotes() const; - void setDisableCompactEmotes(bool value); - void updateContent() const; + void setId(const QString &id); + + // Searching + const QString &getSearchText() const; + + // Scrollbar widgets::ScrollbarHighlight getScrollBarHighlight() const; + // Usernames QString loginName; QString displayName; QString localizedName; - QString timeoutUser; - const QString text; - bool centered = false; + // Timeouts + const QString &getTimeoutUser(const QString &value) const; + void setTimeoutUser(); - static Message *createSystemMessage(const QString &text); + // Static + static MessagePtr createSystemMessage(const QString &text); - static Message *createTimeoutMessage(const QString &username, const QString &durationInSeconds, - const QString &reason, bool multipleTimes); + static MessagePtr createTimeoutMessage(const QString &username, + const QString &durationInSeconds, const QString &reason, + bool multipleTimes); private: - static LazyLoadedImage *badgeStaff; - static LazyLoadedImage *badgeAdmin; - static LazyLoadedImage *badgeGlobalmod; - static LazyLoadedImage *badgeModerator; - static LazyLoadedImage *badgeTurbo; - static LazyLoadedImage *badgeBroadcaster; - static LazyLoadedImage *badgePremium; - static QRegularExpression *cheerRegex; MessageFlags flags = MessageFlags::None; - - // what is highlightTab? - bool highlightTab = false; - - int timeoutCount = 0; - bool disabled = false; - + QString timeoutUser; bool collapsedDefault = false; - bool disableCompactEmotes = false; - - std::chrono::time_point parseTime; - - mutable QString content; + QTime parseTime; + mutable QString searchText; QString id = ""; - std::vector words; + std::vector> elements; }; } // namespace messages diff --git a/src/messages/messagebuilder.cpp b/src/messages/messagebuilder.cpp index 64739db0b..4c34300c6 100644 --- a/src/messages/messagebuilder.cpp +++ b/src/messages/messagebuilder.cpp @@ -1,7 +1,7 @@ #include "messagebuilder.hpp" -#include "singletons/thememanager.hpp" #include "singletons/emotemanager.hpp" #include "singletons/resourcemanager.hpp" +#include "singletons/thememanager.hpp" #include @@ -11,43 +11,35 @@ namespace messages { MessageBuilder::MessageBuilder() : message(new Message) { - _parseTime = std::chrono::system_clock::now(); } -SharedMessage MessageBuilder::getMessage() +MessagePtr MessageBuilder::getMessage() { return this->message; } -void MessageBuilder::appendWord(const Word &&word) +void MessageBuilder::appendElement(MessageElement *element) { - this->message->getWords().push_back(word); + this->message->addElement(element); } void MessageBuilder::appendTimestamp() { - QDateTime t = QDateTime::currentDateTime(); - this->appendTimestamp(t); + this->appendTimestamp(QTime::currentTime()); } void MessageBuilder::setHighlight(bool value) { - this->message->setHighlight(value); + if (value) { + this->message->addFlags(Message::Highlighted); + } else { + this->message->removeFlags(Message::Highlighted); + } } -void MessageBuilder::appendTimestamp(QDateTime &time) +void MessageBuilder::appendTimestamp(const QTime &time) { - // Add word for timestamp with no seconds - QString timestampNoSeconds(time.toString("hh:mm")); - this->appendWord(Word(timestampNoSeconds, Word::TimestampNoSeconds, - MessageColor(MessageColor::System), singletons::FontManager::Medium, QString(), - QString())); - - // Add word for timestamp with seconds - QString timestampWithSeconds(time.toString("hh:mm:ss")); - this->appendWord(Word(timestampWithSeconds, Word::TimestampWithSeconds, - MessageColor(MessageColor::System), singletons::FontManager::Medium, QString(), - QString())); + this->appendElement(new TimestampElement(time)); } QString MessageBuilder::matchLink(const QString &string) diff --git a/src/messages/messagebuilder.hpp b/src/messages/messagebuilder.hpp index 128bc07da..3cfac7636 100644 --- a/src/messages/messagebuilder.hpp +++ b/src/messages/messagebuilder.hpp @@ -14,25 +14,32 @@ class MessageBuilder public: MessageBuilder(); - SharedMessage getMessage(); + MessagePtr getMessage(); - void appendWord(const Word &&word); - void appendTimestamp(); - void appendTimestamp(QDateTime &time); void setHighlight(bool value); + void appendElement(MessageElement *element); + + // typename std::enable_if::value, T>::type + + template + T *append(Args &&... args) + { + static_assert(std::is_base_of::value, "T must extend MessageElement"); + + T *element = new T(std::forward(args)...); + this->appendElement(element); + return element; + } + + void appendTimestamp(); + void appendTimestamp(const QTime &time); QString matchLink(const QString &string); - QRegularExpression linkRegex; QString originalMessage; protected: - std::shared_ptr message; - -private: - std::vector _words; - bool highlight = false; - std::chrono::time_point _parseTime; + MessagePtr message; }; } // namespace messages diff --git a/src/messages/messagecolor.hpp b/src/messages/messagecolor.hpp index c1b25168b..ba07f2f9b 100644 --- a/src/messages/messagecolor.hpp +++ b/src/messages/messagecolor.hpp @@ -12,8 +12,8 @@ class MessageColor public: enum Type { Custom, Text, Link, System }; - explicit MessageColor(const QColor &color); - explicit MessageColor(Type type = Text); + MessageColor(const QColor &color); + MessageColor(Type type = Text); Type getType() const; const QColor &getColor(singletons::ThemeManager &themeManager) const; diff --git a/src/messages/messageelement.cpp b/src/messages/messageelement.cpp new file mode 100644 index 000000000..6d501e177 --- /dev/null +++ b/src/messages/messageelement.cpp @@ -0,0 +1,249 @@ +#include "messages/messageelement.hpp" +#include "messages/layouts/messagelayoutcontainer.hpp" +#include "messages/layouts/messagelayoutelement.hpp" +#include "singletons/settingsmanager.hpp" +#include "util/benchmark.hpp" +#include "util/emotemap.hpp" + +namespace chatterino { +namespace messages { + +MessageElement::MessageElement(Flags _flags) + : flags(_flags) +{ +} + +MessageElement *MessageElement::setLink(const Link &_link) +{ + this->link = _link; + return this; +} + +MessageElement *MessageElement::setTooltip(const QString &_tooltip) +{ + this->tooltip = _tooltip; + return this; +} + +MessageElement *MessageElement::setTrailingSpace(bool value) +{ + this->trailingSpace = value; +} + +const QString &MessageElement::getTooltip() const +{ + return this->tooltip; +} + +const Link &MessageElement::getLink() const +{ + return this->link; +} + +bool MessageElement::hasTrailingSpace() const +{ + return this->trailingSpace; +} + +MessageElement::Flags MessageElement::getFlags() const +{ + return this->flags; +} + +// IMAGE +ImageElement::ImageElement(Image &_image, MessageElement::Flags flags) + : MessageElement(flags) + , image(_image) +{ + this->setTooltip(_image.getTooltip()); +} + +void ImageElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags _flags) +{ + QSize size(this->image.getWidth() * this->image.getScale() * container.scale, + this->image.getHeight() * this->image.getScale() * container.scale); + + container.addElement(new ImageLayoutElement(*this, this->image, size)); +} + +void ImageElement::update(UpdateFlags _flags) +{ +} + +// EMOTE +EmoteElement::EmoteElement(const util::EmoteData &_data, MessageElement::Flags flags) + : MessageElement(flags) + , data(_data) +{ + if (_data.isValid()) { + this->setTooltip(data.image1x->getTooltip()); + } +} + +void EmoteElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags _flags) +{ + if (!this->data.isValid()) { + qDebug() << "EmoteElement::data is invalid xD"; + return; + } + + int quality = singletons::SettingManager::getInstance().preferredEmoteQuality; + + Image *_image; + if (quality == 3 && this->data.image3x != nullptr) { + _image = this->data.image3x; + } else if (quality >= 2 && this->data.image2x != nullptr) { + _image = this->data.image2x; + } else { + _image = this->data.image1x; + } + + QSize size((int)(container.scale * _image->getScaledWidth()), + (int)(container.scale * _image->getScaledHeight())); + + container.addElement(new ImageLayoutElement(*this, *_image, size)); +} + +void EmoteElement::update(UpdateFlags _flags) +{ +} + +// TEXT +TextElement::TextElement(const QString &text, MessageElement::Flags flags, + const MessageColor &_color, FontStyle _style) + : MessageElement(flags) + , color(_color) + , style(_style) +{ + for (QString word : text.split(' ')) { + this->words.push_back({word, -1}); + // fourtf: add logic to store mutliple spaces after message + } +} + +void TextElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags _flags) +{ + QFontMetrics &metrics = + singletons::FontManager::getInstance().getFontMetrics(this->style, container.scale); + singletons::ThemeManager &themeManager = singletons::ThemeManager::ThemeManager::getInstance(); + + for (Word &word : this->words) { + auto getTextLayoutElement = [&](QString text, int width) { + return new TextLayoutElement(*this, text, QSize(width, metrics.height()), + this->color.getColor(themeManager), this->style, + container.scale); + }; + + if (word.width == -1) { + word.width = metrics.width(word.text); + } + + // see if the text fits in the current line + if (container.fitsInLine(word.width)) { + container.addElementNoLineBreak(getTextLayoutElement(word.text, word.width)); + continue; + } + + // see if the text fits in the next line + if (!container.atStartOfLine()) { + container.breakLine(); + + if (container.fitsInLine(word.width)) { + container.addElementNoLineBreak(getTextLayoutElement(word.text, word.width)); + continue; + } + } + + // we done goofed, we need to wrap the text + QString text = word.text; + int textLength = text.length(); + int wordStart = 0; + int width = metrics.width(text[0]); + int lastWidth = 0; + + for (int i = 1; i < textLength; i++) { + int chatWidth = metrics.width(text[i]); + + if (!container.fitsInLine(width + chatWidth)) { + container.addElementNoLineBreak( + getTextLayoutElement(text.mid(wordStart, i - wordStart), width - lastWidth)); + container.breakLine(); + + i += 2; + wordStart = i; + lastWidth = width; + width += chatWidth; + continue; + } + } + + container.addElement(getTextLayoutElement(text.mid(wordStart), word.width - lastWidth)); + } +} + +void TextElement::update(UpdateFlags _flags) +{ + if (_flags & UpdateFlags::Update_Text) { + for (Word &word : this->words) { + word.width = -1; + } + } +} + +// TIMESTAMP +TimestampElement::TimestampElement() + : TimestampElement(QTime::currentTime()) +{ +} + +TimestampElement::TimestampElement(QTime _time) + : MessageElement(MessageElement::Timestamp) + , time(_time) + , element(formatTime(_time)) +{ + assert(this->element != nullptr); +} + +TimestampElement::~TimestampElement() +{ + delete this->element; +} + +void TimestampElement::addToContainer(MessageLayoutContainer &container, + MessageElement::Flags _flags) +{ + this->element->addToContainer(container, _flags); +} + +void TimestampElement::update(UpdateFlags _flags) +{ + if (_flags == UpdateFlags::Update_All) { + this->element = TimestampElement::formatTime(this->time); + } else { + this->element->update(_flags); + } +} + +TextElement *TimestampElement::formatTime(const QTime &time) +{ + QString text = time.toString(singletons::SettingManager::getInstance().timestampFormat); + + return new TextElement(text, Flags::Timestamp, MessageColor::System, FontStyle::Medium); +} + +// TWITCH MODERATION +TwitchModerationElement::TwitchModerationElement() + : MessageElement(MessageElement::ModeratorTools) +{ +} + +void TwitchModerationElement::addToContainer(MessageLayoutContainer &container, + MessageElement::Flags _flags) +{ +} + +void TwitchModerationElement::update(UpdateFlags _flags) +{ +} +} // namespace messages +} // namespace chatterino diff --git a/src/messages/messageelement.hpp b/src/messages/messageelement.hpp new file mode 100644 index 000000000..97aecdad9 --- /dev/null +++ b/src/messages/messageelement.hpp @@ -0,0 +1,224 @@ +#pragma once + +#include "messages/image.hpp" +#include "messages/link.hpp" +#include "messages/messagecolor.hpp" +#include "singletons/fontmanager.hpp" + +#include +#include +#include +#include + +#include +#include + +namespace chatterino { +class Channel; +namespace util { +struct EmoteData; +} +namespace messages { +namespace layouts { +class MessageLayoutContainer; +} + +using namespace chatterino::messages::layouts; + +class MessageElement : boost::noncopyable +{ +public: + enum Flags : uint32_t { + None = 0, + Misc = (1 << 0), + Text = (1 << 1), + + Username = (1 << 2), + Timestamp = (1 << 3), + + TwitchEmoteImage = (1 << 4), + TwitchEmoteText = (1 << 5), + TwitchEmote = TwitchEmoteImage | TwitchEmoteText, + BttvEmoteImage = (1 << 6), + BttvEmoteText = (1 << 7), + BttvEmote = BttvEmoteImage | BttvEmoteText, + FfzEmoteImage = (1 << 10), + FfzEmoteText = (1 << 11), + FfzEmote = FfzEmoteImage | FfzEmoteText, + EmoteImages = TwitchEmoteImage | BttvEmoteImage | FfzEmoteImage, + + BitsStatic = (1 << 12), + BitsAnimated = (1 << 13), + + // Slot 1: Twitch + // - Staff badge + // - Admin badge + // - Global Moderator badge + BadgeGlobalAuthority = (1 << 14), + + // Slot 2: Twitch + // - Moderator badge + // - Broadcaster badge + BadgeChannelAuthority = (1 << 15), + + // Slot 3: Twitch + // - Subscription badges + BadgeSubscription = (1 << 16), + + // Slot 4: Twitch + // - Turbo badge + // - Prime badge + // - Bit badges + // - Game badges + BadgeVanity = (1 << 17), + + // Slot 5: Chatterino + // - Chatterino developer badge + // - Chatterino donator badge + // - Chatterino top donator badge + BadgeChatterino = (1 << 18), + + // Rest of slots: ffz custom badge? bttv custom badge? mywaifu (puke) custom badge? + + Badges = BadgeGlobalAuthority | BadgeChannelAuthority | BadgeSubscription | BadgeVanity | + BadgeChatterino, + + ChannelName = (1 << 19), + + BitsAmount = (1 << 20), + + ModeratorTools = (1 << 21), + + EmojiImage = (1 << 23), + EmojiText = (1 << 24), + EmojiAll = EmojiImage | EmojiText, + + AlwaysShow = (1 << 25), + + // used in the ChannelView class to make the collapse buttons visible if needed + Collapsed = (1 << 26), + + Default = Timestamp | Badges | Username | BitsStatic | FfzEmoteImage | BttvEmoteImage | + TwitchEmoteImage | BitsAmount | Text | AlwaysShow, + }; + + enum UpdateFlags : char { + Update_Text, + Update_Emotes, + Update_Images, + Update_All = Update_Text | Update_Emotes | Update_Images + }; + + virtual ~MessageElement() + { + } + + MessageElement *setLink(const Link &link); + MessageElement *setTooltip(const QString &tooltip); + MessageElement *setTrailingSpace(bool value); + const QString &getTooltip() const; + const Link &getLink() const; + bool hasTrailingSpace() const; + Flags getFlags() const; + + virtual void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) = 0; + virtual void update(UpdateFlags flags) = 0; + +protected: + MessageElement(Flags flags); + bool trailingSpace = true; + +private: + Link link; + QString tooltip; + Flags flags; +}; + +// contains a simple image +class ImageElement : public MessageElement +{ + Image ℑ + +public: + ImageElement(Image &image, MessageElement::Flags flags); + + virtual void addToContainer(MessageLayoutContainer &container, + MessageElement::Flags flags) override; + virtual void update(UpdateFlags flags) override; +}; + +// contains emote data and will pick the emote based on : +// a) are images for the emote type enabled +// b) which size it wants +class EmoteElement : public MessageElement +{ + const util::EmoteData data; + +public: + EmoteElement(const util::EmoteData &data, MessageElement::Flags flags); + + virtual void addToContainer(MessageLayoutContainer &container, + MessageElement::Flags flags) override; + virtual void update(UpdateFlags flags) override; +}; + +// contains a text, it will split it into words +class TextElement : public MessageElement +{ + MessageColor color; + FontStyle style; + + struct Word { + QString text; + int width = -1; + }; + std::vector words; + +public: + TextElement(const QString &text, MessageElement::Flags flags, + const MessageColor &color = MessageColor::Text, + FontStyle style = FontStyle::Medium); + + virtual void addToContainer(MessageLayoutContainer &container, + MessageElement::Flags flags) override; + virtual void update(UpdateFlags flags); +}; + +// contains a text, formated depending on the preferences +class TimestampElement : public MessageElement +{ + QTime time; + TextElement *element; + +public: + TimestampElement(); + TimestampElement(QTime time); + virtual ~TimestampElement(); + + virtual void addToContainer(MessageLayoutContainer &container, + MessageElement::Flags flags) override; + virtual void update(UpdateFlags flags); + + TextElement *formatTime(const QTime &time); +}; + +// adds all the custom moderation buttons, adds a variable amount of items depending on settings +// fourtf: implement +class TwitchModerationElement : public MessageElement +{ +public: + TwitchModerationElement(); + + virtual void addToContainer(MessageLayoutContainer &container, + MessageElement::Flags flags) override; + virtual void update(UpdateFlags flags); +}; + +// adds bits as text, static image or animated image +// class BitsElement : public MessageElement +//{ +// public: +// virtual void addToContainer(LayoutContainer &container) override; +//}; +} // namespace messages +} // namespace chatterino diff --git a/src/messages/messageref.cpp b/src/messages/messageref.cpp deleted file mode 100644 index bb59c2e20..000000000 --- a/src/messages/messageref.cpp +++ /dev/null @@ -1,450 +0,0 @@ -#include "messages/messageref.hpp" -#include "singletons/emotemanager.hpp" -#include "singletons/settingsmanager.hpp" - -#include - -#define MARGIN_LEFT (int)(8 * this->scale) -#define MARGIN_RIGHT (int)(8 * this->scale) -#define MARGIN_TOP (int)(4 * this->scale) -#define MARGIN_BOTTOM (int)(4 * this->scale) -#define COMPACT_EMOTES_OFFSET 6 - -using namespace chatterino::messages; - -namespace chatterino { -namespace messages { - -MessageRef::MessageRef(SharedMessage _message) - : message(_message) - , wordParts() - , collapsed(_message->getCollapsedDefault()) -{ -} - -Message *MessageRef::getMessage() -{ - return this->message.get(); -} - -int MessageRef::getHeight() const -{ - return this->height; -} - -// return true if redraw is required -bool MessageRef::layout(int width, float scale) -{ - auto &emoteManager = singletons::EmoteManager::getInstance(); - - bool rebuildRequired = false, layoutRequired = false; - - // check if width changed - bool widthChanged = width != this->currentLayoutWidth; - layoutRequired |= widthChanged; - this->currentLayoutWidth = width; - - // check if emotes changed - bool imagesChanged = this->emoteGeneration != emoteManager.getGeneration(); - layoutRequired |= imagesChanged; - this->emoteGeneration = emoteManager.getGeneration(); - - // check if text changed - bool textChanged = - this->fontGeneration != singletons::FontManager::getInstance().getGeneration(); - layoutRequired |= textChanged; - this->fontGeneration = singletons::FontManager::getInstance().getGeneration(); - - // check if work mask changed - bool wordMaskChanged = - this->currentWordTypes != singletons::SettingManager::getInstance().getWordTypeMask(); - layoutRequired |= wordMaskChanged; - this->currentWordTypes = singletons::SettingManager::getInstance().getWordTypeMask(); - - // check if dpi changed - bool scaleChanged = this->scale != scale; - layoutRequired |= scaleChanged; - this->scale = scale; - imagesChanged |= scaleChanged; - textChanged |= scaleChanged; - - // update word sizes if needed - if (imagesChanged) { - this->updateImageSizes(); - this->buffer = nullptr; - } - if (textChanged) { - this->updateTextSizes(); - this->buffer = nullptr; - } - if (widthChanged || wordMaskChanged) { - this->buffer = nullptr; - } - - // return if no layout is required - if (!layoutRequired) { - return false; - } - - this->actuallyLayout(width); - - return true; -} - -void MessageRef::actuallyLayout(int width) -{ - auto &settings = singletons::SettingManager::getInstance(); - - const int spaceWidth = 4; - const int right = width - MARGIN_RIGHT; - - bool compactEmotes = !this->getMessage()->getDisableCompactEmotes(); - - // clear word parts - this->wordParts.clear(); - - // layout - int x = MARGIN_LEFT; - int y = MARGIN_TOP; - - int lineNumber = 0; - int lineStart = 0; - int lineHeight = 0; - int firstLineHeight = -1; - bool first = true; - - uint32_t flags = settings.getWordTypeMask(); - if (this->collapsed) { - flags |= Word::Collapsed; - } - - // loop throught all the words and add them when a line is full - for (auto it = this->message->getWords().begin(); it != this->message->getWords().end(); ++it) { - Word &word = *it; - - // Check if given word is supposed to be rendered by comparing it to the current setting - if ((word.getFlags() & flags) == Word::None) { - continue; - } - - int xOffset = 0, yOffset = 0; - - /// if (enableEmoteMargins) { - /// if (word.isImage() && word.getImage().isHat()) { - /// xOffset = -word.getWidth() + 2; - /// } else { - xOffset = word.getXOffset(); - yOffset = word.getYOffset(); - /// } - /// } - - // word wrapping - if (word.isText() && word.getWidth(this->scale) + MARGIN_LEFT > right) { - // align and end the current line - this->_alignWordParts(lineStart, lineHeight, width, firstLineHeight); - y += lineHeight; - - int currentPartStart = 0; - int currentLineWidth = 0; - - // go through the text, break text when it doesn't fit in the line anymore - for (int i = 1; i <= word.getText().length(); i++) { - currentLineWidth += word.getCharWidth(i - 1, this->scale); - - if (currentLineWidth + MARGIN_LEFT > right) { - // add the current line - QString mid = word.getText().mid(currentPartStart, i - currentPartStart - 1); - - this->wordParts.push_back(WordPart(word, MARGIN_LEFT, y, currentLineWidth, - word.getHeight(this->scale), lineNumber, mid, - mid, false, currentPartStart)); - - y += word.getFontMetrics(this->scale).height(); - - currentPartStart = i - 1; - - currentLineWidth = 0; - lineNumber++; - } - } - - QString mid(word.getText().mid(currentPartStart)); - currentLineWidth = word.getFontMetrics(this->scale).width(mid); - - this->wordParts.push_back(WordPart(word, MARGIN_LEFT, y - word.getHeight(this->scale), - currentLineWidth, word.getHeight(this->scale), - lineNumber, mid, mid, true, currentPartStart)); - - x = currentLineWidth + MARGIN_LEFT + spaceWidth; - lineHeight = this->_updateLineHeight(0, word, compactEmotes); - lineStart = this->wordParts.size() - 1; - } - // fits in the current line - else if (first || x + word.getWidth(this->scale) + xOffset <= right) { - this->wordParts.push_back(WordPart(word, x, y - word.getHeight(this->scale), scale, - lineNumber, word.getCopyText())); - - x += word.getWidth(this->scale) + xOffset; - x += spaceWidth; - - lineHeight = this->_updateLineHeight(lineHeight, word, compactEmotes); - } - // doesn't fit in the line - else { - // align and end the current line - this->_alignWordParts(lineStart, lineHeight, width, firstLineHeight); - - y += lineHeight; - - lineNumber++; - - this->wordParts.push_back(WordPart(word, MARGIN_LEFT, y - word.getHeight(this->scale), - this->scale, lineNumber, word.getCopyText())); - - lineStart = this->wordParts.size() - 1; - - lineHeight = this->_updateLineHeight(0, word, compactEmotes); - - x = word.getWidth(this->scale) + MARGIN_LEFT; - x += spaceWidth; - } - - first = false; - } - - // align and end the current line - this->_alignWordParts(lineStart, lineHeight, width, firstLineHeight); - - this->collapsedHeight = firstLineHeight == -1 ? (int)(24 * this->scale) - : firstLineHeight + MARGIN_TOP + MARGIN_BOTTOM; - - // update height - int oldHeight = this->height; - - if (this->isCollapsed()) { - this->height = this->collapsedHeight; - } else { - this->height = y + lineHeight + MARGIN_BOTTOM; - } - - // invalidate buffer if height changed - if (oldHeight != this->height) { - this->buffer = nullptr; - } - - updateBuffer = true; -} - -void MessageRef::updateTextSizes() -{ - for (auto &word : this->message->getWords()) { - if (!word.isText()) { - continue; - } - - word.updateSize(); - } -} - -void MessageRef::updateImageSizes() -{ - for (auto &word : this->message->getWords()) { - if (!word.isImage()) - continue; - - word.updateSize(); - } -} - -const std::vector &MessageRef::getWordParts() const -{ - return this->wordParts; -} - -void MessageRef::_alignWordParts(int lineStart, int lineHeight, int width, int &firstLineHeight) -{ - bool compactEmotes = true; - - if (firstLineHeight == -1) { - firstLineHeight = lineHeight; - } - - int xOffset = 0; - - if (this->message->centered && this->wordParts.size() > 0) { - xOffset = (width - this->wordParts.at(this->wordParts.size() - 1).getRight()) / 2; - } - - for (size_t i = lineStart; i < this->wordParts.size(); i++) { - WordPart &wordPart = this->wordParts.at(i); - - const bool isCompactEmote = compactEmotes && wordPart.getWord().isImage() && - wordPart.getWord().getFlags() & Word::EmoteImages; - - int yExtra = 0; - if (isCompactEmote) { - yExtra = (COMPACT_EMOTES_OFFSET / 2) * this->scale; - } - - wordPart.setPosition(wordPart.getX() + xOffset, wordPart.getY() + lineHeight + yExtra); - } -} - -int MessageRef::_updateLineHeight(int currentLineHeight, Word &word, bool compactEmotes) -{ - int newLineHeight = word.getHeight(this->scale); - - // fourtf: doesn't care about the height of a normal line - if (compactEmotes && word.isImage() && word.getFlags() & Word::EmoteImages) { - newLineHeight -= COMPACT_EMOTES_OFFSET * this->scale; - } - - return std::max(currentLineHeight, newLineHeight); -} - -const Word *MessageRef::tryGetWordPart(QPoint point) -{ - // go through all words and return the first one that contains the point. - for (WordPart &wordPart : this->wordParts) { - if (wordPart.getRect().contains(point)) { - return &wordPart.getWord(); - } - } - - return nullptr; -} - -// XXX(pajlada): This is probably not the optimal way to calculate this -int MessageRef::getLastCharacterIndex() const -{ - // find out in which line the cursor is - int lineNumber = 0, lineStart = 0, lineEnd = 0; - - for (size_t i = 0; i < this->wordParts.size(); i++) { - const WordPart &part = this->wordParts[i]; - - if (part.getLineNumber() != lineNumber) { - lineStart = i; - lineNumber = part.getLineNumber(); - } - - lineEnd = i + 1; - } - - // count up to the cursor - int index = 0; - - for (int i = 0; i < lineStart; i++) { - const WordPart &part = this->wordParts[i]; - - index += part.getWord().isImage() ? 2 : part.getText().length() + 1; - } - - for (int i = lineStart; i < lineEnd; i++) { - const WordPart &part = this->wordParts[i]; - - index += part.getCharacterLength(); - } - - return index; -} - -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 (size_t i = 0; i < this->wordParts.size(); i++) { - WordPart &part = this->wordParts[i]; - - if (part.getLineNumber() != 0 && position.y() < part.getY()) { - break; - } - - if (part.getLineNumber() != lineNumber) { - lineStart = i; - lineNumber = part.getLineNumber(); - } - - lineEnd = i + 1; - } - - // 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() + part.getWidth()) { - index += part.getCharacterLength(); - continue; - } - - // cursor is over the word part - if (part.getWord().isImage()) { - if (position.x() - part.getX() > part.getWidth() / 2) { - index++; - } - } else { - // TODO: use word.getCharacterWidthCache(); - - auto text = part.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(this->scale).width(text, j + 1); - } - } - - break; - } - - return index; -} - -bool MessageRef::isCollapsed() const -{ - return this->collapsed; -} - -void MessageRef::setCollapsed(bool value) -{ - if (this->collapsed != value) { - this->currentLayoutWidth = 0; - this->collapsed = value; - } -} - -int MessageRef::getCollapsedHeight() const -{ - return this->collapsedHeight; -} - -bool MessageRef::isDisabled() const -{ - return this->message->isDisabled(); -} -} // namespace messages -} // namespace chatterino diff --git a/src/messages/messageref.hpp b/src/messages/messageref.hpp deleted file mode 100644 index 7d165f88e..000000000 --- a/src/messages/messageref.hpp +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once - -#include "messages/message.hpp" -#include "messages/wordpart.hpp" - -#include - -#include - -namespace chatterino { -namespace messages { - -class MessageRef; - -typedef std::shared_ptr SharedMessageRef; - -class MessageRef -{ -public: - MessageRef(SharedMessage message); - - Message *getMessage(); - int getHeight() const; - - bool layout(int width, float scale); - - const std::vector &getWordParts() const; - - std::shared_ptr buffer = nullptr; - bool updateBuffer = false; - - const Word *tryGetWordPart(QPoint point); - int getLastCharacterIndex() const; - int getSelectionIndex(QPoint position); - - bool isCollapsed() const; - void setCollapsed(bool value); - int getCollapsedHeight() const; - int getCollapsedLineCount() const; - - bool isDisabled() const; - -private: - // variables - SharedMessage message; - std::vector wordParts; - - int height = 0; - - int currentLayoutWidth = -1; - int fontGeneration = -1; - int emoteGeneration = -1; - float scale = -1; - - Word::Flags currentWordTypes = Word::None; - - bool collapsed; - int collapsedHeight = 32; - - // methods - void rebuild(); - void actuallyLayout(int width); - void _alignWordParts(int lineStart, int lineHeight, int width, int &firstLineHeight); - int _updateLineHeight(int currentLineHeight, Word &word, bool overlapEmotes); - void updateTextSizes(); - void updateImageSizes(); -}; - -} // namespace messages -} // namespace chatterino diff --git a/src/messages/word.cpp b/src/messages/word.cpp deleted file mode 100644 index e5f0f5ee9..000000000 --- a/src/messages/word.cpp +++ /dev/null @@ -1,207 +0,0 @@ -#include "messages/word.hpp" -#include "singletons/settingsmanager.hpp" -#include "util/benchmark.hpp" - -namespace chatterino { -namespace messages { - -// Image word -Word::Word(LazyLoadedImage *image, Flags type, const QString ©text, const QString &tooltip, - const Link &link) - : image(image) - , _isImage(true) - , type(type) - , copyText(copytext) - , tooltip(tooltip) - , link(link) -{ -} - -// Text word -Word::Word(const QString &text, Flags type, const MessageColor &color, - singletons::FontManager::Type font, const QString ©text, const QString &tooltip, - const Link &link) - : image(nullptr) - , text(text) - , color(color) - , _isImage(false) - , type(type) - , copyText(copytext) - , tooltip(tooltip) - , font(font) - , link(link) -{ -} - -LazyLoadedImage &Word::getImage() const -{ - return *this->image; -} - -const QString &Word::getText() const -{ - return this->text; -} - -int Word::getWidth(float scale) const -{ - return this->getSize(scale).width(); -} - -int Word::getHeight(float scale) const -{ - return this->getSize(scale).height(); -} - -QSize Word::getSize(float scale) const -{ - auto &data = this->getDataByScale(scale); - - if (data.size.isEmpty()) { - // no size found - if (this->isText()) { - QFontMetrics &metrics = this->getFontMetrics(scale); - data.size.setWidth((int)(metrics.width(this->getText()))); - data.size.setHeight((int)(metrics.height())); - } else { - const int mediumTextLineHeight = - singletons::FontManager::getInstance().getFontMetrics(this->font, scale).height(); - const qreal emoteScale = - singletons::SettingManager::getInstance().emoteScale.getValue() * scale; - const bool scaleEmotesByLineHeight = - singletons::SettingManager::getInstance().scaleEmotesByLineHeight; - - auto &image = this->getImage(); - - qreal w = image.getWidth(); - qreal h = image.getHeight(); - - if (scaleEmotesByLineHeight) { - data.size.setWidth(w * mediumTextLineHeight / h * emoteScale); - data.size.setHeight(mediumTextLineHeight * emoteScale); - } else { - data.size.setWidth(w * image.getScale() * emoteScale); - data.size.setHeight(h * image.getScale() * emoteScale); - } - } - } - - return data.size; -} - -void Word::updateSize() -{ - this->dataByScale.clear(); -} - -bool Word::isImage() const -{ - return this->_isImage; -} - -bool Word::isText() const -{ - return !this->_isImage; -} - -const QString &Word::getCopyText() const -{ - return this->copyText; -} - -bool Word::hasTrailingSpace() const -{ - return this->_hasTrailingSpace; -} - -QFont &Word::getFont(float scale) const -{ - return singletons::FontManager::getInstance().getFont(this->font, scale); -} - -QFontMetrics &Word::getFontMetrics(float scale) const -{ - return singletons::FontManager::getInstance().getFontMetrics(this->font, scale); -} - -Word::Flags Word::getFlags() const -{ - return this->type; -} - -const QString &Word::getTooltip() const -{ - return this->tooltip; -} - -const MessageColor &Word::getTextColor() const -{ - return this->color; -} - -const Link &Word::getLink() const -{ - return this->link; -} - -int Word::getXOffset() const -{ - return this->xOffset; -} - -int Word::getYOffset() const -{ - return this->yOffset; -} - -void Word::setOffset(int xOffset, int yOffset) -{ - this->xOffset = std::max(0, xOffset); - this->yOffset = std::max(0, yOffset); -} - -int Word::getCharacterLength() const -{ - return this->isImage() ? 2 : this->getText().length() + 1; -} - -short Word::getCharWidth(int index, float scale) const -{ - return this->getCharacterWidthCache(scale).at(index); -} - -std::vector &Word::getCharacterWidthCache(float scale) const -{ - auto &data = this->getDataByScale(scale); - - // lock not required because there is only one gui thread - // std::lock_guard lock(this->charWidthCacheMutex); - - if (data.charWidthCache.size() == 0 && this->isText()) { - for (int i = 0; i < this->getText().length(); i++) { - data.charWidthCache.push_back( - this->getFontMetrics(scale).charWidth(this->getText(), i)); - } - } - - // TODO: on font change - return data.charWidthCache; -} - -Word::ScaleDependantData &Word::getDataByScale(float scale) const -{ - // try to find and return data for scale - for (auto it = this->dataByScale.begin(); it != this->dataByScale.end(); it++) { - if (it->scale == scale) { - return *it; - } - } - - // create new data element and return that - this->dataByScale.emplace_back(scale); - - return this->dataByScale.back(); -} - -} // namespace messages -} // namespace chatterino diff --git a/src/messages/word.hpp b/src/messages/word.hpp deleted file mode 100644 index 592e0896a..000000000 --- a/src/messages/word.hpp +++ /dev/null @@ -1,163 +0,0 @@ -#pragma once - -#include "messages/lazyloadedimage.hpp" -#include "messages/link.hpp" -#include "messages/messagecolor.hpp" -#include "singletons/fontmanager.hpp" -//#include "wordflags.hpp" - -#include -#include -#include - -namespace chatterino { -namespace messages { - -class Word -{ -public: - enum Flags : uint32_t { - None = 0, - Misc = (1 << 0), - Text = (1 << 1), - - TimestampNoSeconds = (1 << 2), - TimestampWithSeconds = (1 << 3), - - TwitchEmoteImage = (1 << 4), - TwitchEmoteText = (1 << 5), - BttvEmoteImage = (1 << 6), - BttvEmoteText = (1 << 7), - BttvGifEmoteImage = (1 << 8), - BttvGifEmoteText = (1 << 9), - FfzEmoteImage = (1 << 10), - FfzEmoteText = (1 << 11), - EmoteImages = TwitchEmoteImage | BttvEmoteImage | BttvGifEmoteImage | FfzEmoteImage, - - BitsStatic = (1 << 12), - BitsAnimated = (1 << 13), - - // Slot 1: Twitch - // - Staff badge - // - Admin badge - // - Global Moderator badge - BadgeGlobalAuthority = (1 << 14), - - // Slot 2: Twitch - // - Moderator badge - // - Broadcaster badge - BadgeChannelAuthority = (1 << 15), - - // Slot 3: Twitch - // - Subscription badges - BadgeSubscription = (1 << 16), - - // Slot 4: Twitch - // - Turbo badge - // - Prime badge - // - Bit badges - // - Game badges - BadgeVanity = (1 << 17), - - // Slot 5: Chatterino - // - Chatterino developer badge - // - Chatterino donator badge - // - Chatterino top donator badge - BadgeChatterino = (1 << 18), - - // Rest of slots: ffz custom badge? bttv custom badge? mywaifu (puke) custom badge? - - Badges = BadgeGlobalAuthority | BadgeChannelAuthority | BadgeSubscription | BadgeVanity | - BadgeChatterino, - - Username = (1 << 19), - BitsAmount = (1 << 20), - - ButtonBan = (1 << 21), - ButtonTimeout = (1 << 22), - - EmojiImage = (1 << 23), - EmojiText = (1 << 24), - - AlwaysShow = (1 << 25), - - // used in the ChannelView class to make the collapse buttons visible if needed - Collapsed = (1 << 26), - - Default = TimestampNoSeconds | Badges | Username | BitsStatic | FfzEmoteImage | - BttvEmoteImage | BttvGifEmoteImage | TwitchEmoteImage | BitsAmount | Text | - ButtonBan | ButtonTimeout | AlwaysShow - }; - - Word() - { - } - - explicit Word(LazyLoadedImage *_image, Flags getFlags, const QString ©text, - const QString &tooltip, const Link &getLink = Link()); - explicit Word(const QString &_text, Flags getFlags, const MessageColor &textColor, - singletons::FontManager::Type font, const QString ©text, - const QString &tooltip, const Link &getLink = Link()); - - bool isImage() const; - bool isText() const; - - LazyLoadedImage &getImage() const; - const QString &getText() const; - const QString &getCopyText() const; - bool hasTrailingSpace() const; - Flags getFlags() const; - const QString &getTooltip() const; - const MessageColor &getTextColor() const; - const Link &getLink() const; - int getXOffset() const; - int getYOffset() const; - void setOffset(int _xOffset, int _yOffset); - int getCharacterLength() const; - - void updateSize(); - - QFont &getFont(float scale) const; - QFontMetrics &getFontMetrics(float scale) const; - int getWidth(float scale) const; - int getHeight(float scale) const; - QSize getSize(float scale) const; - short getCharWidth(int index, float scale) const; - -private: - LazyLoadedImage *image; - QString text; - MessageColor color; - bool _isImage; - - Flags type; - QString copyText; - QString tooltip; - - int xOffset = 0; - int yOffset = 0; - - bool _hasTrailingSpace = true; - singletons::FontManager::Type font = singletons::FontManager::Medium; - Link link; - - struct ScaleDependantData { - float scale; - QSize size; - mutable std::vector charWidthCache; - - ScaleDependantData(float _scale) - : scale(_scale) - , size() - { - } - }; - - mutable std::list dataByScale; - - inline ScaleDependantData &getDataByScale(float scale) const; - std::vector &getCharacterWidthCache(float scale) const; -}; - -} // namespace messages -} // namespace chatterino diff --git a/src/messages/wordpart.cpp b/src/messages/wordpart.cpp deleted file mode 100644 index 64f3d61f3..000000000 --- a/src/messages/wordpart.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#include "messages/wordpart.hpp" -#include "messages/word.hpp" - -namespace chatterino { -namespace messages { - -WordPart::WordPart(Word &_word, int _x, int _y, float scale, int _lineNumber, - const QString &_copyText, bool _allowTrailingSpace) - : word(_word) - , copyText(_copyText) - , text(_word.isText() ? _word.getText() : QString()) - , x(_x) - , y(_y) - , width(_word.getWidth(scale)) - , height(_word.getHeight(scale)) - , lineNumber(_lineNumber) - , _trailingSpace(!_word.getCopyText().isEmpty() && - _word.hasTrailingSpace() & _allowTrailingSpace) - , wordCharOffset(0) -{ -} - -WordPart::WordPart(Word &_word, int _x, int _y, int _width, int _height, int _lineNumber, - const QString &_copyText, const QString &_customText, bool _allowTrailingSpace, - int _wordCharOffset) - : word(_word) - , copyText(_copyText) - , text(_customText) - , x(_x) - , y(_y) - , width(_width) - , height(_height) - , lineNumber(_lineNumber) - , _trailingSpace(!_word.getCopyText().isEmpty() && - _word.hasTrailingSpace() & _allowTrailingSpace) - , wordCharOffset(_wordCharOffset) -{ -} - -const Word &WordPart::getWord() const -{ - return this->word; -} - -int WordPart::getWidth() const -{ - return this->width; -} - -int WordPart::getHeight() const -{ - return this->height; -} - -int WordPart::getX() const -{ - return this->x; -} - -int WordPart::getY() const -{ - return this->y; -} - -void WordPart::setPosition(int x, int y) -{ - this->x = x; - this->y = y; -} - -void WordPart::setY(int y) -{ - this->y = y; -} - -int WordPart::getRight() const -{ - return this->x + this->width; -} - -int WordPart::getBottom() const -{ - return this->y + this->height; -} - -QRect WordPart::getRect() const -{ - return QRect(this->x, this->y, this->width, this->height - 1); -} - -const QString WordPart::getCopyText() const -{ - return this->copyText; -} - -int WordPart::hasTrailingSpace() const -{ - return this->_trailingSpace; -} - -const QString &WordPart::getText() const -{ - return this->text; -} - -int WordPart::getLineNumber() const -{ - return this->lineNumber; -} - -int WordPart::getCharacterLength() const -{ - // return (this->getWord().isImage() ? 1 : this->getText().length()) + (_trailingSpace ? 1 : - // 0); - return this->getWord().isImage() ? 2 : this->getText().length() + 1; -} - -short WordPart::getCharWidth(int index, float scale) const -{ - return this->getWord().getCharWidth(index + this->wordCharOffset, scale); -} -} // namespace messages -} // namespace chatterino diff --git a/src/messages/wordpart.hpp b/src/messages/wordpart.hpp deleted file mode 100644 index 6a54fd0c2..000000000 --- a/src/messages/wordpart.hpp +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include -#include - -namespace chatterino { -namespace messages { - -class Word; - -class WordPart -{ -public: - WordPart(Word &getWord, int getX, int getY, float scale, int _lineNumber, - const QString &getCopyText, bool allowTrailingSpace = true); - - WordPart(Word &getWord, int getX, int getY, int getWidth, int getHeight, int _lineNumber, - const QString &getCopyText, const QString &customText, bool allowTrailingSpace = true, - int wordCharOffset = 0); - - const Word &getWord() const; - int getWidth() const; - int getHeight() const; - int getX() const; - int getY() const; - void setPosition(int _x, int _y); - void setY(int _y); - int getRight() const; - int getBottom() const; - QRect getRect() const; - const QString getCopyText() const; - int hasTrailingSpace() const; - const QString &getText() const; - int getLineNumber() const; - int getCharacterLength() const; - short getCharWidth(int index, float scale) const; - -private: - Word &word; - - QString copyText; - QString text; - - int x; - int y; - int width; - int height; - - int lineNumber; - - bool _trailingSpace; - int wordCharOffset; -}; - -} // namespace messages -} // namespace chatterino diff --git a/src/singletons/channelmanager.cpp b/src/singletons/channelmanager.cpp index c2e007d46..893d788cc 100644 --- a/src/singletons/channelmanager.cpp +++ b/src/singletons/channelmanager.cpp @@ -19,11 +19,11 @@ ChannelManager::ChannelManager() { } -const std::vector> ChannelManager::getItems() +const std::vector ChannelManager::getItems() { QMutexLocker locker(&this->channelsMutex); - std::vector> items; + std::vector items; for (auto &item : this->twitchChannels.values()) { items.push_back(std::get<0>(item)); @@ -32,7 +32,7 @@ const std::vector> ChannelManager::getItems() return items; } -std::shared_ptr ChannelManager::addTwitchChannel(const QString &rawChannelName) +SharedChannel ChannelManager::addTwitchChannel(const QString &rawChannelName) { QString channelName = rawChannelName.toLower(); @@ -63,7 +63,7 @@ std::shared_ptr ChannelManager::addTwitchChannel(const QString &rawChan return std::get<0>(it.value()); } -std::shared_ptr ChannelManager::getTwitchChannel(const QString &channel) +SharedChannel ChannelManager::getTwitchChannel(const QString &channel) { QMutexLocker locker(&this->channelsMutex); @@ -128,7 +128,7 @@ const std::string &ChannelManager::getUserID(const std::string &username) return temporary; } -void ChannelManager::doOnAll(std::function)> func) +void ChannelManager::doOnAll(std::function func) { for (const auto &channel : this->twitchChannels) { func(std::get<0>(channel)); diff --git a/src/singletons/channelmanager.hpp b/src/singletons/channelmanager.hpp index e2009926b..3fcf6b41c 100644 --- a/src/singletons/channelmanager.hpp +++ b/src/singletons/channelmanager.hpp @@ -17,20 +17,20 @@ class ChannelManager public: static ChannelManager &getInstance(); - const std::vector> getItems(); + const std::vector getItems(); - std::shared_ptr addTwitchChannel(const QString &channel); - std::shared_ptr getTwitchChannel(const QString &channel); + SharedChannel addTwitchChannel(const QString &channel); + SharedChannel getTwitchChannel(const QString &channel); void removeTwitchChannel(const QString &channel); const std::string &getUserID(const std::string &username); - void doOnAll(std::function)> func); + void doOnAll(std::function func); // Special channels - const std::shared_ptr whispersChannel; - const std::shared_ptr mentionsChannel; - const std::shared_ptr emptyChannel; + const SharedChannel whispersChannel; + const SharedChannel mentionsChannel; + const SharedChannel emptyChannel; private: std::map usernameToID; diff --git a/src/singletons/commandmanager.cpp b/src/singletons/commandmanager.cpp index 621459fb7..40b070fab 100644 --- a/src/singletons/commandmanager.cpp +++ b/src/singletons/commandmanager.cpp @@ -88,7 +88,7 @@ QStringList CommandManager::getCommands() return this->commandsStringList; } -QString CommandManager::execCommand(const QString &text, std::shared_ptr channel, +QString CommandManager::execCommand(const QString &text, SharedChannel channel, bool dryRun) { QStringList words = text.split(' ', QString::SkipEmptyParts); @@ -110,9 +110,8 @@ QString CommandManager::execCommand(const QString &text, std::shared_ptrisLive ? twitchChannel->streamUptime : "Channel is not live."; - messages::SharedMessage message( - messages::Message::createSystemMessage(messageText)); - channel->addMessage(message); + + channel->addMessage(messages::Message::createSystemMessage(messageText)); return ""; } else if (commandName == "/ignore" && words.size() >= 2) { @@ -122,9 +121,7 @@ QString CommandManager::execCommand(const QString &text, std::shared_ptraddMessage(message); + channel->addMessage(messages::Message::createSystemMessage(messageText)); return ""; } else if (commandName == "/unignore") { QString messageText; @@ -133,9 +130,7 @@ QString CommandManager::execCommand(const QString &text, std::shared_ptraddMessage(message); + channel->addMessage(messages::Message::createSystemMessage(messageText)); return ""; } } diff --git a/src/singletons/emotemanager.cpp b/src/singletons/emotemanager.cpp index 84ae75add..3f6371d6a 100644 --- a/src/singletons/emotemanager.cpp +++ b/src/singletons/emotemanager.cpp @@ -62,14 +62,14 @@ static void FillInFFZEmoteData(const QJsonObject &urls, const QString &code, assert(!url1x.isEmpty()); - emoteData.image1x = new LazyLoadedImage(url1x, 1, code, code + "
Global FFZ Emote"); + emoteData.image1x = new Image(url1x, 1, code, code + "
Global FFZ Emote"); if (!url2x.isEmpty()) { - emoteData.image2x = new LazyLoadedImage(url2x, 0.5, code, code + "
Global FFZ Emote"); + emoteData.image2x = new Image(url2x, 0.5, code, code + "
Global FFZ Emote"); } if (!url3x.isEmpty()) { - emoteData.image3x = new LazyLoadedImage(url3x, 0.25, code, code + "
Global FFZ Emote"); + emoteData.image3x = new Image(url3x, 0.25, code, code + "
Global FFZ Emote"); } } @@ -143,7 +143,7 @@ void EmoteManager::reloadBTTVChannelEmotes(const QString &channelName, auto emote = this->getBTTVChannelEmoteFromCaches().getOrAdd(id, [this, &code, &link] { return util::EmoteData( - new LazyLoadedImage(link, 1, code, code + "
Channel BTTV Emote")); + new Image(link, 1, code, code + "
Channel BTTV Emote")); }); this->bttvChannelEmotes.insert(code, emote); @@ -294,7 +294,7 @@ void EmoteManager::loadEmojis() code + ".png"; this->emojis.insert(code, - util::EmoteData(new LazyLoadedImage(url, 0.35, ":" + shortCode + ":", + util::EmoteData(new Image(url, 0.35, ":" + shortCode + ":", ":" + shortCode + ":
Emoji"))); // TODO(pajlada): The vectors in emojiFirstByte need to be sorted by @@ -375,7 +375,7 @@ void EmoteManager::parseEmojis(std::vector> // Create or fetch cached emoji image auto emojiImage = this->emojis.getOrAdd(matchedEmoji.code, [this, &url] { return util::EmoteData( - new LazyLoadedImage(url, 0.35, "?????????", "???????????????")); // + new Image(url, 0.35, "?????????", "???????????????")); // }); // Push the emoji as a word to parsedWords @@ -489,11 +489,11 @@ void EmoteManager::loadBTTVEmotes() QString code = emote.toObject().value("code").toString(); util::EmoteData emoteData; - emoteData.image1x = new LazyLoadedImage(GetBTTVEmoteLink(urlTemplate, id, "1x"), 1, + emoteData.image1x = new Image(GetBTTVEmoteLink(urlTemplate, id, "1x"), 1, code, code + "
Global BTTV Emote"); - emoteData.image2x = new LazyLoadedImage(GetBTTVEmoteLink(urlTemplate, id, "2x"), 0.5, + emoteData.image2x = new Image(GetBTTVEmoteLink(urlTemplate, id, "2x"), 0.5, code, code + "
Global BTTV Emote"); - emoteData.image3x = new LazyLoadedImage(GetBTTVEmoteLink(urlTemplate, id, "3x"), 0.25, + emoteData.image3x = new Image(GetBTTVEmoteLink(urlTemplate, id, "3x"), 0.25, code, code + "
Global BTTV Emote"); this->bttvGlobalEmotes.insert(code, emoteData); @@ -547,11 +547,11 @@ util::EmoteData EmoteManager::getTwitchEmoteById(long id, const QString &emoteNa return _twitchEmoteFromCache.getOrAdd(id, [this, &emoteName, &_emoteName, &id] { util::EmoteData newEmoteData; - newEmoteData.image1x = new LazyLoadedImage(GetTwitchEmoteLink(id, "1.0"), 1, emoteName, + newEmoteData.image1x = new Image(GetTwitchEmoteLink(id, "1.0"), 1, emoteName, _emoteName + "
Twitch Emote 1x"); - newEmoteData.image2x = new LazyLoadedImage(GetTwitchEmoteLink(id, "2.0"), .5, emoteName, + newEmoteData.image2x = new Image(GetTwitchEmoteLink(id, "2.0"), .5, emoteName, _emoteName + "
Twitch Emote 2x"); - newEmoteData.image3x = new LazyLoadedImage(GetTwitchEmoteLink(id, "3.0"), .25, emoteName, + newEmoteData.image3x = new Image(GetTwitchEmoteLink(id, "3.0"), .25, emoteName, _emoteName + "
Twitch Emote 3x"); return newEmoteData; diff --git a/src/singletons/emotemanager.hpp b/src/singletons/emotemanager.hpp index eacf9fc6d..5fe9e8b9a 100644 --- a/src/singletons/emotemanager.hpp +++ b/src/singletons/emotemanager.hpp @@ -3,7 +3,7 @@ #define GIF_FRAME_LENGTH 33 #include "emojis.hpp" -#include "messages/lazyloadedimage.hpp" +#include "messages/image.hpp" #include "signalvector.hpp" #include "twitch/emotevalue.hpp" #include "twitch/twitchuser.hpp" @@ -63,7 +63,7 @@ public: boost::signals2::signal &getGifUpdateSignal(); // Bit badge/emotes? - util::ConcurrentMap miscImageCache; + util::ConcurrentMap miscImageCache; private: SettingManager &settingsManager; diff --git a/src/singletons/fontmanager.hpp b/src/singletons/fontmanager.hpp index bc94836c9..c7ca84685 100644 --- a/src/singletons/fontmanager.hpp +++ b/src/singletons/fontmanager.hpp @@ -132,6 +132,7 @@ private: int generation = 0; }; - -} // namespace chatterino } + +typedef singletons::FontManager::Type FontStyle; +} // namespace chatterino diff --git a/src/singletons/helper/ircmessagehandler.cpp b/src/singletons/helper/ircmessagehandler.cpp index 60b7ebdda..5ac578cfc 100644 --- a/src/singletons/helper/ircmessagehandler.cpp +++ b/src/singletons/helper/ircmessagehandler.cpp @@ -71,9 +71,7 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message) // check if the chat has been cleared by a moderator if (message->parameters().length() == 1) { - SharedMessage msg(Message::createSystemMessage("Chat has been cleared by a moderator.")); - - c->addMessage(msg); + c->addMessage(Message::createSystemMessage("Chat has been cleared by a moderator.")); return; } @@ -94,12 +92,13 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message) } // add the notice that the user has been timed out - LimitedQueueSnapshot snapshot = c->getMessageSnapshot(); + LimitedQueueSnapshot snapshot = c->getMessageSnapshot(); bool addMessage = true; + int snapshotLength = snapshot.getLength(); - for (int i = std::max(0, (int)snapshot.getLength() - 20); i < snapshot.getLength(); i++) { - if (snapshot[i]->getFlags() & Message::Timeout && snapshot[i]->timeoutUser == username) { - SharedMessage replacement( + for (int i = std::max(0, snapshotLength - 20); i < snapshotLength; i++) { + if (snapshot[i]->hasFlags(Message::Timeout) && snapshot[i]->loginName == username) { + MessagePtr replacement( Message::createTimeoutMessage(username, durationInSeconds, reason, true)); c->replaceMessage(snapshot[i], replacement); addMessage = false; @@ -108,16 +107,13 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message) } if (addMessage) { - SharedMessage msg( - Message::createTimeoutMessage(username, durationInSeconds, reason, false)); - c->addMessage(msg); + c->addMessage(Message::createTimeoutMessage(username, durationInSeconds, reason, false)); } // disable the messages from the user - for (int i = 0; i < snapshot.getLength(); i++) { - if (!(snapshot[i]->getFlags() & Message::Timeout) && - snapshot[i]->getTimeoutUser() == username) { - snapshot[i]->setDisabled(true); + for (int i = 0; i < snapshotLength; i++) { + if (!snapshot[i]->hasFlags(Message::Timeout) && snapshot[i]->loginName == username) { + snapshot[i]->setFlags(Message::Disabled); } } @@ -155,7 +151,7 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message) auto rawChannelName = message->target(); bool broadcast = rawChannelName.length() < 2; - std::shared_ptr msg(Message::createSystemMessage(message->content())); + MessagePtr msg = Message::createSystemMessage(message->content()); if (broadcast) { this->channelManager.doOnAll([msg](const auto &c) { diff --git a/src/singletons/ircmanager.cpp b/src/singletons/ircmanager.cpp index ad2d03960..627b72864 100644 --- a/src/singletons/ircmanager.cpp +++ b/src/singletons/ircmanager.cpp @@ -381,9 +381,9 @@ void IrcManager::removeIgnoredUser(QString const &username) void IrcManager::onConnected() { - std::shared_ptr msg(Message::createSystemMessage("connected to chat")); + MessagePtr msg = Message::createSystemMessage("connected to chat"); - this->channelManager.doOnAll([msg](std::shared_ptr channel) { + this->channelManager.doOnAll([msg](SharedChannel channel) { assert(channel); channel->addMessage(msg); }); @@ -391,9 +391,9 @@ void IrcManager::onConnected() void IrcManager::onDisconnected() { - std::shared_ptr msg(Message::createSystemMessage("disconnected from chat")); + MessagePtr msg = Message::createSystemMessage("disconnected from chat"); - this->channelManager.doOnAll([msg](std::shared_ptr channel) { + this->channelManager.doOnAll([msg](SharedChannel channel) { assert(channel); channel->addMessage(msg); }); diff --git a/src/singletons/resourcemanager.cpp b/src/singletons/resourcemanager.cpp index 1056b4093..7fc745d81 100644 --- a/src/singletons/resourcemanager.cpp +++ b/src/singletons/resourcemanager.cpp @@ -10,9 +10,9 @@ namespace singletons { namespace { -inline messages::LazyLoadedImage *lli(const char *pixmapPath, qreal scale = 1) +inline messages::Image *lli(const char *pixmapPath, qreal scale = 1) { - return new messages::LazyLoadedImage(new QPixmap(pixmapPath), scale); + return new messages::Image(new QPixmap(pixmapPath), scale); } } // namespace @@ -49,9 +49,9 @@ ResourceManager &ResourceManager::getInstance() } ResourceManager::BadgeVersion::BadgeVersion(QJsonObject &&root) - : badgeImage1x(new messages::LazyLoadedImage(root.value("image_url_1x").toString())) - , badgeImage2x(new messages::LazyLoadedImage(root.value("image_url_2x").toString())) - , badgeImage4x(new messages::LazyLoadedImage(root.value("image_url_4x").toString())) + : badgeImage1x(new messages::Image(root.value("image_url_1x").toString())) + , badgeImage2x(new messages::Image(root.value("image_url_2x").toString())) + , badgeImage4x(new messages::Image(root.value("image_url_4x").toString())) , description(root.value("description").toString().toStdString()) , title(root.value("title").toString().toStdString()) , clickAction(root.value("clickAction").toString().toStdString()) @@ -139,7 +139,7 @@ void ResourceManager::loadChatterinoBadges() const QString &badgeVariantImageURL = badgeVariant.value("image").toString(); auto badgeVariantPtr = std::make_shared( - badgeVariantTooltip, new messages::LazyLoadedImage(badgeVariantImageURL)); + badgeVariantTooltip, new messages::Image(badgeVariantImageURL)); QJsonArray badgeVariantUsers = badgeVariant.value("users").toArray(); diff --git a/src/singletons/resourcemanager.hpp b/src/singletons/resourcemanager.hpp index 0f0239eb5..57bcc6792 100644 --- a/src/singletons/resourcemanager.hpp +++ b/src/singletons/resourcemanager.hpp @@ -1,6 +1,6 @@ #pragma once -#include "messages/lazyloadedimage.hpp" +#include "messages/image.hpp" #include #include @@ -16,34 +16,34 @@ class ResourceManager public: static ResourceManager &getInstance(); - messages::LazyLoadedImage *badgeStaff; - messages::LazyLoadedImage *badgeAdmin; - messages::LazyLoadedImage *badgeGlobalModerator; - messages::LazyLoadedImage *badgeModerator; - messages::LazyLoadedImage *badgeTurbo; - messages::LazyLoadedImage *badgeBroadcaster; - messages::LazyLoadedImage *badgePremium; - messages::LazyLoadedImage *badgeVerified; - messages::LazyLoadedImage *badgeSubscriber; - messages::LazyLoadedImage *badgeCollapsed; + messages::Image *badgeStaff; + messages::Image *badgeAdmin; + messages::Image *badgeGlobalModerator; + messages::Image *badgeModerator; + messages::Image *badgeTurbo; + messages::Image *badgeBroadcaster; + messages::Image *badgePremium; + messages::Image *badgeVerified; + messages::Image *badgeSubscriber; + messages::Image *badgeCollapsed; - messages::LazyLoadedImage *cheerBadge100000; - messages::LazyLoadedImage *cheerBadge10000; - messages::LazyLoadedImage *cheerBadge5000; - messages::LazyLoadedImage *cheerBadge1000; - messages::LazyLoadedImage *cheerBadge100; - messages::LazyLoadedImage *cheerBadge1; + messages::Image *cheerBadge100000; + messages::Image *cheerBadge10000; + messages::Image *cheerBadge5000; + messages::Image *cheerBadge1000; + messages::Image *cheerBadge100; + messages::Image *cheerBadge1; - std::map cheerBadges; + std::map cheerBadges; struct BadgeVersion { BadgeVersion() = delete; explicit BadgeVersion(QJsonObject &&root); - messages::LazyLoadedImage *badgeImage1x; - messages::LazyLoadedImage *badgeImage2x; - messages::LazyLoadedImage *badgeImage4x; + messages::Image *badgeImage1x; + messages::Image *badgeImage2x; + messages::Image *badgeImage4x; std::string description; std::string title; std::string clickAction; @@ -58,8 +58,8 @@ public: bool dynamicBadgesLoaded = false; - messages::LazyLoadedImage *buttonBan; - messages::LazyLoadedImage *buttonTimeout; + messages::Image *buttonBan; + messages::Image *buttonTimeout; struct Channel { std::map badgeSets; @@ -72,14 +72,14 @@ public: // Chatterino badges struct ChatterinoBadge { - ChatterinoBadge(const std::string &_tooltip, messages::LazyLoadedImage *_image) + ChatterinoBadge(const std::string &_tooltip, messages::Image *_image) : tooltip(_tooltip) , image(_image) { } std::string tooltip; - messages::LazyLoadedImage *image; + messages::Image *image; }; // username diff --git a/src/singletons/settingsmanager.cpp b/src/singletons/settingsmanager.cpp index 82a1ac17b..2c227d4a2 100644 --- a/src/singletons/settingsmanager.cpp +++ b/src/singletons/settingsmanager.cpp @@ -18,7 +18,6 @@ SettingManager::SettingManager() : snapshot(nullptr) { this->wordMaskListener.addSetting(this->showTimestamps); - this->wordMaskListener.addSetting(this->showTimestampSeconds); this->wordMaskListener.addSetting(this->showBadges); this->wordMaskListener.addSetting(this->enableBttvEmotes); this->wordMaskListener.addSetting(this->enableEmojis); @@ -29,7 +28,7 @@ SettingManager::SettingManager() }; } -Word::Flags SettingManager::getWordTypeMask() +MessageElement::Flags SettingManager::getWordTypeMask() { return this->wordTypeMask; } @@ -48,35 +47,31 @@ void SettingManager::init() void SettingManager::updateWordTypeMask() { - uint32_t newMaskUint = Word::Text; + uint32_t newMaskUint = MessageElement::Text; if (this->showTimestamps) { - if (this->showTimestampSeconds) { - newMaskUint |= Word::TimestampWithSeconds; - } else { - newMaskUint |= Word::TimestampNoSeconds; - } + newMaskUint |= MessageElement::Timestamp; } - newMaskUint |= enableTwitchEmotes ? Word::TwitchEmoteImage : Word::TwitchEmoteText; - newMaskUint |= enableFfzEmotes ? Word::FfzEmoteImage : Word::FfzEmoteText; - newMaskUint |= enableBttvEmotes ? Word::BttvEmoteImage : Word::BttvEmoteText; newMaskUint |= - (enableBttvEmotes && enableGifAnimations) ? Word::BttvEmoteImage : Word::BttvEmoteText; - newMaskUint |= enableEmojis ? Word::EmojiImage : Word::EmojiText; + enableTwitchEmotes ? MessageElement::TwitchEmoteImage : MessageElement::TwitchEmoteText; + newMaskUint |= enableFfzEmotes ? MessageElement::FfzEmoteImage : MessageElement::FfzEmoteText; + newMaskUint |= + enableBttvEmotes ? MessageElement::BttvEmoteImage : MessageElement::BttvEmoteText; + newMaskUint |= enableEmojis ? MessageElement::EmojiImage : MessageElement::EmojiText; - newMaskUint |= Word::BitsAmount; - newMaskUint |= enableGifAnimations ? Word::BitsAnimated : Word::BitsStatic; + newMaskUint |= MessageElement::BitsAmount; + newMaskUint |= enableGifAnimations ? MessageElement::BitsAnimated : MessageElement::BitsStatic; if (this->showBadges) { - newMaskUint |= Word::Badges; + newMaskUint |= MessageElement::Badges; } - newMaskUint |= Word::Username; + newMaskUint |= MessageElement::Username; - newMaskUint |= Word::AlwaysShow; + newMaskUint |= MessageElement::AlwaysShow; - Word::Flags newMask = static_cast(newMaskUint); + MessageElement::Flags newMask = static_cast(newMaskUint); if (newMask != this->wordTypeMask) { this->wordTypeMask = newMask; diff --git a/src/singletons/settingsmanager.hpp b/src/singletons/settingsmanager.hpp index 801d8cbaa..78d5bf88e 100644 --- a/src/singletons/settingsmanager.hpp +++ b/src/singletons/settingsmanager.hpp @@ -1,7 +1,7 @@ #pragma once #include "messages/highlightphrase.hpp" -#include "messages/word.hpp" +#include "messages/messageelement.hpp" #include "singletons/helper/chatterinosetting.hpp" #include @@ -23,14 +23,14 @@ class SettingManager : public QObject using QStringSetting = ChatterinoSetting; public: - messages::Word::Flags getWordTypeMask(); + messages::MessageElement::Flags getWordTypeMask(); bool isIgnoredEmote(const QString &emote); void init(); /// Appearance BoolSetting showTimestamps = {"/appearance/messages/showTimestamps", true}; - BoolSetting showTimestampSeconds = {"/appearance/messages/showTimestampSeconds", true}; + QStringSetting timestampFormat = {"/appearance/messages/timestampFormat", "h:mm"}; BoolSetting showBadges = {"/appearance/messages/showBadges", true}; BoolSetting showLastMessageIndicator = {"/appearance/messages/showLastMessageIndicator", false}; BoolSetting hideEmptyInput = {"/appearance/hideEmptyInputBox", false}; @@ -110,7 +110,7 @@ private: SettingManager(); - messages::Word::Flags wordTypeMask = messages::Word::Default; + messages::MessageElement::Flags wordTypeMask = messages::MessageElement::Default; pajlada::Settings::SettingListener wordMaskListener; }; diff --git a/src/twitch/twitchchannel.cpp b/src/twitch/twitchchannel.cpp index 97fe10ade..02ed977e1 100644 --- a/src/twitch/twitchchannel.cpp +++ b/src/twitch/twitchchannel.cpp @@ -107,7 +107,7 @@ void TwitchChannel::refreshLiveStatus() std::weak_ptr weak = this->shared_from_this(); util::twitch::get2(url, QThread::currentThread(), [weak](rapidjson::Document &d) { - std::shared_ptr shared = weak.lock(); + SharedChannel shared = weak.lock(); if (!shared) { return; @@ -168,7 +168,7 @@ void TwitchChannel::fetchRecentMessages() std::weak_ptr weak = this->shared_from_this(); util::twitch::get(genericURL.arg(roomID), QThread::currentThread(), [weak](QJsonObject obj) { - std::shared_ptr shared = weak.lock(); + SharedChannel shared = weak.lock(); if (!shared) { return; @@ -179,7 +179,7 @@ void TwitchChannel::fetchRecentMessages() auto msgArray = obj.value("messages").toArray(); if (msgArray.size() > 0) { - std::vector messages; + std::vector messages; messages.resize(msgArray.size()); for (int i = 0; i < msgArray.size(); i++) { diff --git a/src/twitch/twitchmessagebuilder.cpp b/src/twitch/twitchmessagebuilder.cpp index 8e220c3c0..a729f0cf1 100644 --- a/src/twitch/twitchmessagebuilder.cpp +++ b/src/twitch/twitchmessagebuilder.cpp @@ -29,15 +29,14 @@ TwitchMessageBuilder::TwitchMessageBuilder(Channel *_channel, { } -SharedMessage TwitchMessageBuilder::parse() +MessagePtr TwitchMessageBuilder::parse() { singletons::SettingManager &settings = singletons::SettingManager::getInstance(); singletons::EmoteManager &emoteManager = singletons::EmoteManager::getInstance(); - auto preferredEmoteQuality = settings.preferredEmoteQuality.getValue(); - this->originalMessage = this->ircMessage->content(); + // PARSING this->parseUsername(); // this->message->setCollapsedDefault(true); @@ -48,25 +47,27 @@ SharedMessage TwitchMessageBuilder::parse() // Whether or not will be rendered is decided/checked later // Appends the correct timestamp if the message is a past message + bool isPastMsg = this->tags.contains("historical"); if (isPastMsg) { // This may be architecture dependent(datatype) qint64 ts = this->tags.value("tmi-sent-ts").toLongLong(); - QDateTime time = QDateTime::fromMSecsSinceEpoch(ts); - this->appendTimestamp(time); + QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(ts); + this->append(dateTime.time()); } else { - this->appendTimestamp(); + this->append(); } this->parseMessageID(); this->parseRoomID(); - this->appendModerationButtons(); + // TIMESTAMP + this->append(); this->parseTwitchBadges(); - this->parseChatterinoBadges(); + this->addChatterinoBadges(); if (this->args.includeChannelName) { this->parseChannelName(); @@ -123,9 +124,8 @@ SharedMessage TwitchMessageBuilder::parse() // twitch emote if (currentTwitchEmote != twitchEmotes.end() && currentTwitchEmote->first == i) { - auto emoteImage = currentTwitchEmote->second.getImageForSize(preferredEmoteQuality); - this->appendWord(Word(emoteImage, Word::TwitchEmoteImage, emoteImage->getName(), - emoteImage->getTooltip(), Link(Link::Url, emoteImage->getUrl()))); + auto emoteImage = currentTwitchEmote->second; + this->append(emoteImage, MessageElement::TwitchEmote); i += split.length() + 1; currentTwitchEmote = std::next(currentTwitchEmote); @@ -177,31 +177,13 @@ SharedMessage TwitchMessageBuilder::parse() QString bitsLink = QString("http://static-cdn.jtvnw.net/bits/dark/static/" + color + "/1"); - LazyLoadedImage *imageAnimated = emoteManager.miscImageCache.getOrAdd( - bitsLinkAnimated, [this, &bitsLinkAnimated] { - return new LazyLoadedImage(bitsLinkAnimated); - }); - LazyLoadedImage *image = emoteManager.miscImageCache.getOrAdd( - bitsLink, [this, &bitsLink] { return new LazyLoadedImage(bitsLink); }); + Image *imageAnimated = emoteManager.miscImageCache.getOrAdd( + bitsLinkAnimated, + [this, &bitsLinkAnimated] { return new Image(bitsLinkAnimated); }); + Image *image = emoteManager.miscImageCache.getOrAdd( + bitsLink, [this, &bitsLink] { return new Image(bitsLink); }); - this->appendWord(Word(imageAnimated, Word::BitsAnimated, QString("cheer"), - QString("Twitch Cheer"), - Link(Link::Url, QString("https://blog.twitch.tv/" - "introducing-cheering-celebrate-" - "together-da62af41fac6")))); - this->appendWord(Word( - image, Word::BitsStatic, QString("cheer"), QString("Twitch Cheer"), - Link(Link::Url, - QString("https://blog.twitch.tv/" - "introducing-cheering-celebrate-together-da62af41fac6")))); - - this->appendWord(Word( - QString("x" + string.mid(5)), Word::BitsAmount, MessageColor(bitsColor), - singletons::FontManager::Medium, QString(string.mid(5)), - QString("Twitch Cheer"), - Link(Link::Url, - QString("https://blog.twitch.tv/" - "introducing-cheering-celebrate-together-da62af41fac6")))); + // append bits continue; } @@ -228,15 +210,10 @@ SharedMessage TwitchMessageBuilder::parse() textColor = MessageColor(MessageColor::Link); } - this->appendWord(Word(string, Word::Text, textColor, - singletons::FontManager::Medium, string, QString(), link)); + this->append(string, EmoteElement::Text) // + ->setLink(link); } else { // is emoji - auto emoteImage = emoteData.getImageForSize(preferredEmoteQuality); - this->appendWord(Word(emoteImage, Word::EmojiImage, emoteImage->getName(), - emoteImage->getTooltip())); - Word(emoteImage->getName(), Word::EmojiText, textColor, - singletons::FontManager::Medium, emoteImage->getName(), - emoteImage->getTooltip()); + this->append(emoteData, EmoteElement::EmojiAll); } } @@ -283,9 +260,10 @@ void TwitchMessageBuilder::parseRoomID() void TwitchMessageBuilder::parseChannelName() { QString channelName("#" + this->channel->name); - this->appendWord(Word(channelName, Word::Misc, MessageColor(MessageColor::System), - singletons::FontManager::Medium, QString(channelName), QString(), - Link(Link::Url, this->channel->name + "\n" + this->messageID))); + Link link(Link::Url, this->channel->name + "\n" + this->messageID); + + this->append(channelName, MessageElement::ChannelName, MessageColor::System) // + ->setLink(link); } void TwitchMessageBuilder::parseUsername() @@ -303,7 +281,6 @@ void TwitchMessageBuilder::parseUsername() } this->message->loginName = this->userName; - this->message->timeoutUser = this->userName; } void TwitchMessageBuilder::appendUsername() @@ -330,30 +307,30 @@ void TwitchMessageBuilder::appendUsername() bool hasLocalizedName = !localizedName.isEmpty(); // The full string that will be rendered in the chat widget - QString usernameString; + QString usernameText; pajlada::Settings::Setting usernameDisplayMode( "/appearance/messages/usernameDisplayMode", UsernameDisplayMode::UsernameAndLocalizedName); switch (usernameDisplayMode.getValue()) { case UsernameDisplayMode::Username: { - usernameString = username; + usernameText = username; } break; case UsernameDisplayMode::LocalizedName: { if (hasLocalizedName) { - usernameString = localizedName; + usernameText = localizedName; } else { - usernameString = username; + usernameText = username; } } break; default: case UsernameDisplayMode::UsernameAndLocalizedName: { if (hasLocalizedName) { - usernameString = username + "(" + localizedName + ")"; + usernameText = username + "(" + localizedName + ")"; } else { - usernameString = username; + usernameText = username; } } break; } @@ -369,12 +346,12 @@ void TwitchMessageBuilder::appendUsername() } if (!ircMessage->isAction()) { - usernameString += ":"; + usernameText += ":"; } - this->appendWord(Word(usernameString, Word::Username, MessageColor(this->usernameColor), - singletons::FontManager::MediumBold, usernameString, QString(), - Link(Link::UserInfo, this->userName))); + this->append(usernameText, MessageElement::Text, this->usernameColor, + FontStyle::MediumBold) + ->setLink({Link::UserInfo, this->userName}); } void TwitchMessageBuilder::parseHighlights() @@ -465,19 +442,6 @@ void TwitchMessageBuilder::parseHighlights() } } -void TwitchMessageBuilder::appendModerationButtons() -{ - // mod buttons - static QString buttonBanTooltip("Ban user"); - static QString buttonTimeoutTooltip("Timeout user"); - - this->appendWord(Word(singletons::ResourceManager::getInstance().buttonBan, Word::ButtonBan, - QString(), buttonBanTooltip, Link(Link::UserBan, ircMessage->account()))); - this->appendWord(Word(singletons::ResourceManager::getInstance().buttonTimeout, - Word::ButtonTimeout, QString(), buttonTimeoutTooltip, - Link(Link::UserTimeout, ircMessage->account()))); -} - void TwitchMessageBuilder::appendTwitchEmote(const Communi::IrcPrivateMessage *ircMessage, const QString &emote, std::vector> &vec) @@ -547,20 +511,18 @@ bool TwitchMessageBuilder::tryAppendEmote(QString &emoteString) bool TwitchMessageBuilder::appendEmote(const util::EmoteData &emoteData) { - auto emoteImage = emoteData.getImageForSize( - singletons::SettingManager::getInstance().preferredEmoteQuality.getValue()); - - this->appendWord(Word(emoteImage, Word::BttvEmoteImage, emoteImage->getName(), - emoteImage->getTooltip(), Link(Link::Url, emoteImage->getUrl()))); + this->append(emoteData, MessageElement::BttvEmote); // Perhaps check for ignored emotes here? return true; } +// fourtf: this is ugly +// maybe put the individual badges into a map instead of this mess void TwitchMessageBuilder::parseTwitchBadges() { - const auto &channelResources = - singletons::ResourceManager::getInstance().channels[this->roomID]; + singletons::ResourceManager &resourceManager = singletons::ResourceManager::getInstance(); + const auto &channelResources = resourceManager.channels[this->roomID]; auto iterator = this->tags.find("badges"); @@ -586,14 +548,14 @@ void TwitchMessageBuilder::parseTwitchBadges() std::string versionKey = cheerAmountQS.toStdString(); try { - auto &badgeSet = singletons::ResourceManager::getInstance().badgeSets.at("bits"); + auto &badgeSet = resourceManager.badgeSets.at("bits"); try { auto &badgeVersion = badgeSet.versions.at(versionKey); - appendWord( - Word(badgeVersion.badgeImage1x, Word::BadgeVanity, QString(), - QString("Twitch " + QString::fromStdString(badgeVersion.title)))); + this->append(*badgeVersion.badgeImage1x, + MessageElement::BadgeVanity) + ->setTooltip("Twitch " + QString::fromStdString(badgeVersion.title)); } catch (const std::exception &e) { debug::Log("Exception caught: {} when trying to fetch badge version {} ", e.what(), versionKey); @@ -602,36 +564,40 @@ void TwitchMessageBuilder::parseTwitchBadges() debug::Log("No badge set with key bits. Exception: {}", e.what()); } } else if (badge == "staff/1") { - appendWord(Word(singletons::ResourceManager::getInstance().badgeStaff, - Word::BadgeGlobalAuthority, QString(), QString("Twitch Staff"))); + this->append(*resourceManager.badgeStaff, + MessageElement::BadgeGlobalAuthority) + ->setTooltip("Twitch Staff"); } else if (badge == "admin/1") { - appendWord(Word(singletons::ResourceManager::getInstance().badgeAdmin, - Word::BadgeGlobalAuthority, QString(), QString("Twitch Admin"))); + this->append(*resourceManager.badgeAdmin, + MessageElement::BadgeGlobalAuthority) + ->setTooltip("Twitch Admin"); } else if (badge == "global_mod/1") { - appendWord(Word(singletons::ResourceManager::getInstance().badgeGlobalModerator, - Word::BadgeGlobalAuthority, QString(), QString("Global Moderator"))); + this->append(*resourceManager.badgeGlobalModerator, + MessageElement::BadgeGlobalAuthority) + ->setTooltip("Twitch Global Moderator"); } else if (badge == "moderator/1") { // TODO: Implement custom FFZ moderator badge - appendWord(Word(singletons::ResourceManager::getInstance().badgeModerator, - Word::BadgeChannelAuthority, QString(), - QString("Channel Moderator"))); // custom badge + this->append(*resourceManager.badgeModerator, + MessageElement::BadgeChannelAuthority) + ->setTooltip("Twitch Channel Moderator"); } else if (badge == "turbo/1") { - appendWord(Word(singletons::ResourceManager::getInstance().badgeTurbo, - Word::BadgeVanity, QString(), QString("Turbo Subscriber"))); + this->append(*resourceManager.badgeTurbo, + MessageElement::BadgeGlobalAuthority) + ->setTooltip("Twitch Turbo Subscriber"); } else if (badge == "broadcaster/1") { - appendWord(Word(singletons::ResourceManager::getInstance().badgeBroadcaster, - Word::BadgeChannelAuthority, QString(), - QString("Channel Broadcaster"))); + this->append(*resourceManager.badgeBroadcaster, + MessageElement::BadgeChannelAuthority) + ->setTooltip("Twitch Broadcaster"); } else if (badge == "premium/1") { - appendWord(Word(singletons::ResourceManager::getInstance().badgePremium, - Word::BadgeVanity, QString(), QString("Twitch Prime"))); - + this->append(*resourceManager.badgePremium, MessageElement::BadgeVanity) + ->setTooltip("Twitch Prime Subscriber"); } else if (badge.startsWith("partner/")) { int index = badge.midRef(8).toInt(); switch (index) { case 1: { - appendWord(Word(singletons::ResourceManager::getInstance().badgeVerified, - Word::BadgeVanity, QString(), "Twitch Verified")); + this->append(*resourceManager.badgeVerified, + MessageElement::BadgeVanity) + ->setTooltip("Twitch Verified"); } break; default: { printf("[TwitchMessageBuilder] Unhandled partner badge index: %d\n", index); @@ -646,9 +612,9 @@ void TwitchMessageBuilder::parseTwitchBadges() auto badgeSetIt = channelResources.badgeSets.find("subscriber"); if (badgeSetIt == channelResources.badgeSets.end()) { // Fall back to default badge - appendWord(Word(singletons::ResourceManager::getInstance().badgeSubscriber, - Word::Flags::BadgeSubscription, QString(), - QString("Twitch Subscriber"))); + this->append(*resourceManager.badgeSubscriber, + MessageElement::BadgeSubscription) + ->setTooltip("Twitch Subscriber"); continue; } @@ -660,18 +626,19 @@ void TwitchMessageBuilder::parseTwitchBadges() if (badgeVersionIt == badgeSet.versions.end()) { // Fall back to default badge - appendWord(Word(singletons::ResourceManager::getInstance().badgeSubscriber, - Word::Flags::BadgeSubscription, QString(), - QString("Twitch Subscriber"))); + this->append(*resourceManager.badgeSubscriber, + MessageElement::BadgeSubscription) + ->setTooltip("Twitch Subscriber"); continue; } auto &badgeVersion = badgeVersionIt->second; - appendWord(Word(badgeVersion.badgeImage1x, Word::Flags::BadgeSubscription, QString(), - QString("Twitch " + QString::fromStdString(badgeVersion.title)))); + this->append(*badgeVersion.badgeImage1x, + MessageElement::BadgeSubscription) + ->setTooltip("Twitch " + QString::fromStdString(badgeVersion.title)); } else { - if (!singletons::ResourceManager::getInstance().dynamicBadgesLoaded) { + if (!resourceManager.dynamicBadgesLoaded) { // Do nothing continue; } @@ -683,21 +650,19 @@ void TwitchMessageBuilder::parseTwitchBadges() continue; } - Word::Flags badgeType = Word::Flags::BadgeVanity; + MessageElement::Flags badgeType = MessageElement::Flags::BadgeVanity; std::string badgeSetKey = parts[0].toStdString(); std::string versionKey = parts[1].toStdString(); try { - auto &badgeSet = - singletons::ResourceManager::getInstance().badgeSets.at(badgeSetKey); + auto &badgeSet = resourceManager.badgeSets.at(badgeSetKey); try { auto &badgeVersion = badgeSet.versions.at(versionKey); - appendWord( - Word(badgeVersion.badgeImage1x, badgeType, QString(), - QString("Twitch " + QString::fromStdString(badgeVersion.title)))); + this->append(*badgeVersion.badgeImage1x, badgeType) + ->setTooltip("Twitch " + QString::fromStdString(badgeVersion.title)); } catch (const std::exception &e) { qDebug() << "Exception caught:" << e.what() << "when trying to fetch badge version " << versionKey.c_str(); @@ -710,7 +675,7 @@ void TwitchMessageBuilder::parseTwitchBadges() } } -void TwitchMessageBuilder::parseChatterinoBadges() +void TwitchMessageBuilder::addChatterinoBadges() { auto &badges = singletons::ResourceManager::getInstance().chatterinoBadges; auto it = badges.find(this->userName.toStdString()); @@ -721,12 +686,13 @@ void TwitchMessageBuilder::parseChatterinoBadges() const auto badge = it->second; - this->appendWord(Word(badge->image, Word::BadgeChatterino, QString(), badge->tooltip.c_str())); + this->append(*badge->image, MessageElement::BadgeChatterino) + ->setTooltip(QString::fromStdString(badge->tooltip)); } // bool -// sortTwitchEmotes(const std::pair &a, -// const std::pair &b) +// sortTwitchEmotes(const std::pair &a, +// const std::pair &b) //{ // return a.first < b.first; //} diff --git a/src/twitch/twitchmessagebuilder.hpp b/src/twitch/twitchmessagebuilder.hpp index cd947874f..3cd42b754 100644 --- a/src/twitch/twitchmessagebuilder.hpp +++ b/src/twitch/twitchmessagebuilder.hpp @@ -38,11 +38,11 @@ public: QString messageID; QString userName; - messages::SharedMessage parse(); + messages::MessagePtr parse(); // static bool sortTwitchEmotes( - // const std::pair &a, - // const std::pair &b); + // const std::pair &a, + // const std::pair &b); private: QString roomID; @@ -56,14 +56,13 @@ private: void appendUsername(); void parseHighlights(); - void appendModerationButtons(); void appendTwitchEmote(const Communi::IrcPrivateMessage *ircMessage, const QString &emote, std::vector> &vec); bool tryAppendEmote(QString &emoteString); bool appendEmote(const util::EmoteData &emoteData); void parseTwitchBadges(); - void parseChatterinoBadges(); + void addChatterinoBadges(); }; } // namespace twitch diff --git a/src/util/emotemap.hpp b/src/util/emotemap.hpp index fba5b4b6e..36afd1ed0 100644 --- a/src/util/emotemap.hpp +++ b/src/util/emotemap.hpp @@ -1,6 +1,6 @@ #pragma once -#include "messages/lazyloadedimage.hpp" +#include "messages/image.hpp" #include "util/concurrentmap.hpp" #include @@ -13,14 +13,14 @@ struct EmoteData { { } - EmoteData(messages::LazyLoadedImage *_image) + EmoteData(messages::Image *_image) : image1x(_image) { } - messages::LazyLoadedImage *getImageForSize(unsigned emoteSize) const + messages::Image *getImageForSize(unsigned emoteSize) const { - messages::LazyLoadedImage *ret = nullptr; + messages::Image *ret = nullptr; switch (emoteSize) { case 0: @@ -53,9 +53,9 @@ struct EmoteData { return this->image1x != nullptr; } - messages::LazyLoadedImage *image1x = nullptr; - messages::LazyLoadedImage *image2x = nullptr; - messages::LazyLoadedImage *image3x = nullptr; + messages::Image *image1x = nullptr; + messages::Image *image2x = nullptr; + messages::Image *image3x = nullptr; }; typedef ConcurrentMap EmoteMap; diff --git a/src/util/irchelpers.hpp b/src/util/irchelpers.hpp index 09845137d..9462680b8 100644 --- a/src/util/irchelpers.hpp +++ b/src/util/irchelpers.hpp @@ -3,7 +3,7 @@ #include namespace chatterino { - +namespace util { QString ParseTagString(const QString &input) { QString output = input; @@ -53,5 +53,5 @@ QString ParseTagString(const QString &input) return output; } } - +} } // namespace chatterino diff --git a/src/util/property.hpp b/src/util/property.hpp new file mode 100644 index 000000000..aee1a0372 --- /dev/null +++ b/src/util/property.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "boost/noncopyable.hpp" + +namespace chatterino { +namespace util { +template +class Property final : boost::noncopyable +{ +public: + Property() + { + } + + Property(const T &_value) + : value(_value) + { + } + + T &operator=(const T &f) + { + return value = f; + } + + operator T const &() const + { + return value; + } + +protected: + T value; +}; +} +} diff --git a/src/widgets/accountpopup.cpp b/src/widgets/accountpopup.cpp index 9c87f06ca..65e172ada 100644 --- a/src/widgets/accountpopup.cpp +++ b/src/widgets/accountpopup.cpp @@ -18,7 +18,7 @@ namespace chatterino { namespace widgets { -AccountPopupWidget::AccountPopupWidget(std::shared_ptr _channel) +AccountPopupWidget::AccountPopupWidget(SharedChannel _channel) : BaseWidget() , ui(new Ui::AccountPopup) , channel(_channel) @@ -132,7 +132,7 @@ void AccountPopupWidget::setName(const QString &name) this->getUserId(); } -void AccountPopupWidget::setChannel(std::shared_ptr _channel) +void AccountPopupWidget::setChannel(SharedChannel _channel) { this->channel = _channel; } diff --git a/src/widgets/accountpopup.hpp b/src/widgets/accountpopup.hpp index ee34da7f5..1b5056198 100644 --- a/src/widgets/accountpopup.hpp +++ b/src/widgets/accountpopup.hpp @@ -23,10 +23,10 @@ class AccountPopupWidget : public BaseWidget { Q_OBJECT public: - AccountPopupWidget(std::shared_ptr _channel); + AccountPopupWidget(SharedChannel _channel); void setName(const QString &name); - void setChannel(std::shared_ptr _channel); + void setChannel(SharedChannel _channel); void updatePermissions(); @@ -47,7 +47,7 @@ private: enum class permissions { User, Mod, Owner }; permissions permission; - std::shared_ptr channel; + SharedChannel channel; QString userID; QPixmap avatar; diff --git a/src/widgets/emotepopup.cpp b/src/widgets/emotepopup.cpp index 95d202abe..64cca6319 100644 --- a/src/widgets/emotepopup.cpp +++ b/src/widgets/emotepopup.cpp @@ -34,7 +34,7 @@ EmotePopup::EmotePopup(singletons::ThemeManager &themeManager) this->loadEmojis(); } -void EmotePopup::loadChannel(std::shared_ptr _channel) +void EmotePopup::loadChannel(SharedChannel _channel) { TwitchChannel *channel = dynamic_cast(_channel.get()); @@ -42,29 +42,25 @@ void EmotePopup::loadChannel(std::shared_ptr _channel) return; } - std::shared_ptr emoteChannel(new Channel("")); + SharedChannel emoteChannel(new Channel("")); auto addEmotes = [&](util::EmoteMap &map, const QString &title, const QString &emoteDesc) { // TITLE messages::MessageBuilder builder1; - builder1.appendWord(Word(title, Word::Flags::Text, MessageColor(MessageColor::Text), - singletons::FontManager::Medium, QString(), QString())); + builder1.appendElement(new TextElement(title, MessageElement::Text)); - builder1.getMessage()->centered = true; + builder1.getMessage()->addFlags(Message::Centered); emoteChannel->addMessage(builder1.getMessage()); // EMOTES messages::MessageBuilder builder2; - builder2.getMessage()->centered = true; - builder2.getMessage()->setDisableCompactEmotes(true); - - int preferredEmoteSize = singletons::SettingManager::getInstance().preferredEmoteQuality; + builder2.getMessage()->addFlags(Message::Centered); + builder2.getMessage()->addFlags(Message::DisableCompactEmotes); map.each([&](const QString &key, const util::EmoteData &value) { - builder2.appendWord(Word(value.getImageForSize(preferredEmoteSize), - Word::Flags::AlwaysShow, key, emoteDesc, - Link(Link::Type::InsertText, key))); + builder2.appendElement((new EmoteElement(value, MessageElement::Flags::AlwaysShow)) // + ->setLink(Link(Link::InsertText, key))); }); emoteChannel->addMessage(builder2.getMessage()); @@ -85,31 +81,26 @@ void EmotePopup::loadChannel(std::shared_ptr _channel) void EmotePopup::loadEmojis() { - int preferredEmoteSize = singletons::SettingManager::getInstance().preferredEmoteQuality; util::EmoteMap &emojis = singletons::EmoteManager::getInstance().getEmojis(); - std::shared_ptr emojiChannel(new Channel("")); + SharedChannel emojiChannel(new Channel("")); // title messages::MessageBuilder builder1; - builder1.appendWord(Word("emojis", Word::Flags::Text, MessageColor(MessageColor::Text), - singletons::FontManager::Medium, QString(), QString())); - - builder1.getMessage()->centered = true; + builder1.appendElement(new TextElement("emojis", MessageElement::Text)); + builder1.getMessage()->addFlags(Message::Centered); emojiChannel->addMessage(builder1.getMessage()); // emojis messages::MessageBuilder builder; - builder.getMessage()->centered = true; - builder.getMessage()->setDisableCompactEmotes(true); + builder.getMessage()->addFlags(Message::Centered); + builder.getMessage()->setFlags(Message::DisableCompactEmotes); - emojis.each( - [this, &builder, preferredEmoteSize](const QString &key, const util::EmoteData &value) { - auto emoteImage = value.getImageForSize(preferredEmoteSize); - builder.appendWord(Word(emoteImage, Word::Flags::AlwaysShow, key, "emoji", - Link(Link::Type::InsertText, key))); - }); + emojis.each([this, &builder](const QString &key, const util::EmoteData &value) { + builder.appendElement((new EmoteElement(value, MessageElement::Flags::AlwaysShow)) // + ->setLink(Link(Link::Type::InsertText, key))); + }); emojiChannel->addMessage(builder.getMessage()); this->viewEmojis->setChannel(emojiChannel); diff --git a/src/widgets/emotepopup.hpp b/src/widgets/emotepopup.hpp index 313e3ced7..8ea988288 100644 --- a/src/widgets/emotepopup.hpp +++ b/src/widgets/emotepopup.hpp @@ -12,7 +12,7 @@ class EmotePopup : public BaseWidget public: explicit EmotePopup(singletons::ThemeManager &); - void loadChannel(std::shared_ptr channel); + void loadChannel(SharedChannel channel); void loadEmojis(); private: diff --git a/src/widgets/helper/channelview.cpp b/src/widgets/helper/channelview.cpp index 8b34289a7..a218788f7 100644 --- a/src/widgets/helper/channelview.cpp +++ b/src/widgets/helper/channelview.cpp @@ -1,8 +1,8 @@ #include "channelview.hpp" #include "debug/log.hpp" +#include "messages/layouts/messagelayout.hpp" #include "messages/limitedqueuesnapshot.hpp" #include "messages/message.hpp" -#include "messages/messageref.hpp" #include "singletons/channelmanager.hpp" #include "singletons/settingsmanager.hpp" #include "singletons/thememanager.hpp" @@ -57,8 +57,7 @@ ChannelView::ChannelView(BaseWidget *parent) singletons::WindowManager &windowManager = singletons::WindowManager::getInstance(); - this->repaintGifsConnection = - windowManager.repaintGifs.connect([&] { this->updateGifEmotes(); }); + this->repaintGifsConnection = windowManager.repaintGifs.connect([&] { this->queueUpdate(); }); this->layoutConnection = windowManager.layout.connect([&](Channel *channel) { if (channel == nullptr || this->channel.get() == channel) { this->layoutMessages(); @@ -225,14 +224,6 @@ void ChannelView::clearMessages() this->queueUpdate(); } -void ChannelView::updateGifEmotes() -{ - if (!this->gifEmotes.empty()) { - this->onlyUpdateEmotes = true; - this->queueUpdate(); - } -} - Scrollbar &ChannelView::getScrollBar() { return this->scrollBar; @@ -240,103 +231,106 @@ Scrollbar &ChannelView::getScrollBar() QString ChannelView::getSelectedText() { - auto messagesSnapshot = this->getMessagesSnapshot(); + // fourtf: xD + // auto messagesSnapshot = this->getMessagesSnapshot(); - QString text; - bool isSingleMessage = this->selection.isSingleMessage(); + // QString text; + // bool isSingleMessage = this->selection.isSingleMessage(); - size_t i = std::max(0, this->selection.min.messageIndex); + // size_t i = std::max(0, this->selection.min.messageIndex); - int charIndex = 0; + // int charIndex = 0; - bool first = true; + // bool first = true; - auto addPart = [&](const WordPart &part, int from = 0, int to = -1) { - if (part.getCopyText().isEmpty()) { - return; - } + // auto addPart = [&](const MessageLayoutElement &part, int from = 0, int to = -1) { + // if (part.getCopyText().isEmpty()) { + // return; + // } - if (part.getWord().isText()) { - text += part.getText().mid(from, to); - } else { - text += part.getCopyText(); - } - }; + // if (part.getWord().isText()) { + // text += part.getText().mid(from, to); + // } else { + // text += part.getCopyText(); + // } + // }; - // first line - for (const messages::WordPart &part : messagesSnapshot[i]->getWordParts()) { - int charLength = part.getCharacterLength(); + // // first line + // for (const messages::MessageLayoutElement &part : messagesSnapshot[i]->getWordParts()) { + // int charLength = part.getCharacterLength(); - if (charIndex + charLength < this->selection.min.charIndex) { - charIndex += charLength; - continue; - } + // if (charIndex + charLength < this->selection.min.charIndex) { + // charIndex += charLength; + // continue; + // } - if (first) { - first = false; - bool isSingleWord = - isSingleMessage && - this->selection.max.charIndex - charIndex < part.getCharacterLength(); + // if (first) { + // first = false; + // bool isSingleWord = + // isSingleMessage && + // this->selection.max.charIndex - charIndex < part.getCharacterLength(); - if (isSingleWord) { - // return single word - addPart(part, this->selection.min.charIndex - charIndex, - this->selection.max.charIndex - this->selection.min.charIndex); - return text; - } else { - // add first word of the selection - addPart(part, this->selection.min.charIndex - charIndex); - } - } else if (isSingleMessage && charIndex + charLength >= selection.max.charIndex) { - addPart(part, 0, this->selection.max.charIndex - charIndex); + // if (isSingleWord) { + // // return single word + // addPart(part, this->selection.min.charIndex - charIndex, + // this->selection.max.charIndex - this->selection.min.charIndex); + // return text; + // } else { + // // add first word of the selection + // addPart(part, this->selection.min.charIndex - charIndex); + // } + // } else if (isSingleMessage && charIndex + charLength >= selection.max.charIndex) { + // addPart(part, 0, this->selection.max.charIndex - charIndex); - return text; - } else { - text += part.getCopyText() + (part.hasTrailingSpace() ? " " : ""); - } + // return text; + // } else { + // text += part.getCopyText() + (part.hasTrailingSpace() ? " " : ""); + // } - charIndex += charLength; - } + // charIndex += charLength; + // } - text += "\n"; + // text += "\n"; - // middle lines - for (i++; (int)i < this->selection.max.messageIndex; i++) { - for (const messages::WordPart &part : messagesSnapshot[i]->getWordParts()) { - if (!part.getCopyText().isEmpty()) { - text += part.getCopyText(); + // // middle lines + // for (i++; (int)i < this->selection.max.messageIndex; i++) { + // for (const messages::MessageLayoutElement &part : messagesSnapshot[i]->getWordParts()) + // { + // if (!part.getCopyText().isEmpty()) { + // text += part.getCopyText(); - if (part.hasTrailingSpace()) { - text += " "; - } - } - } - text += "\n"; - } + // if (part.hasTrailingSpace()) { + // text += " "; + // } + // } + // } + // text += "\n"; + // } - // last line - charIndex = 0; + // // last line + // charIndex = 0; - for (const messages::WordPart &part : - messagesSnapshot[this->selection.max.messageIndex]->getWordParts()) { - int charLength = part.getCharacterLength(); + // for (const messages::MessageLayoutElement &part : + // messagesSnapshot[this->selection.max.messageIndex]->getWordParts()) { + // int charLength = part.getCharacterLength(); - if (charIndex + charLength >= this->selection.max.charIndex) { - addPart(part, 0, this->selection.max.charIndex - charIndex); + // if (charIndex + charLength >= this->selection.max.charIndex) { + // addPart(part, 0, this->selection.max.charIndex - charIndex); - return text; - } + // return text; + // } - text += part.getCopyText(); + // text += part.getCopyText(); - if (part.hasTrailingSpace()) { - text += " "; - } + // if (part.hasTrailingSpace()) { + // text += " "; + // } - charIndex += charLength; - } + // charIndex += charLength; + // } - return text; + // return text; + return ""; } bool ChannelView::hasSelection() @@ -360,7 +354,7 @@ bool ChannelView::getEnableScrollingToBottom() const return this->enableScrollingToBottom; } -messages::LimitedQueueSnapshot ChannelView::getMessagesSnapshot() +messages::LimitedQueueSnapshot ChannelView::getMessagesSnapshot() { if (!this->paused) { this->snapshot = this->messages.getSnapshot(); @@ -369,7 +363,7 @@ messages::LimitedQueueSnapshot ChannelView::getMessagesSnapsho return this->snapshot; } -void ChannelView::setChannel(std::shared_ptr newChannel) +void ChannelView::setChannel(SharedChannel newChannel) { if (this->channel) { this->detachChannel(); @@ -378,12 +372,12 @@ void ChannelView::setChannel(std::shared_ptr newChannel) // on new message this->messageAppendedConnection = - newChannel->messageAppended.connect([this](SharedMessage &message) { - SharedMessageRef deleted; + newChannel->messageAppended.connect([this](MessagePtr &message) { + MessageLayoutPtr deleted; - auto messageRef = new MessageRef(message); + auto messageRef = new MessageLayout(message); - if (this->messages.pushBack(SharedMessageRef(messageRef), deleted)) { + if (this->messages.pushBack(MessageLayoutPtr(messageRef), deleted)) { if (this->scrollBar.isAtBottom()) { this->scrollBar.scrollToBottom(); } else { @@ -391,7 +385,7 @@ void ChannelView::setChannel(std::shared_ptr newChannel) } } - if (message->containsHighlightedPhrase()) { + if (!message->hasFlags(Message::DoNotTriggerNotification)) { this->highlightedMessageReceived.invoke(); } @@ -402,12 +396,12 @@ void ChannelView::setChannel(std::shared_ptr newChannel) }); this->messageAddedAtStartConnection = - newChannel->messagesAddedAtStart.connect([this](std::vector &messages) { - std::vector messageRefs; + newChannel->messagesAddedAtStart.connect([this](std::vector &messages) { + std::vector messageRefs; messageRefs.resize(messages.size()); qDebug() << messages.size(); - for (int i = 0; i < messages.size(); i++) { - messageRefs.at(i) = SharedMessageRef(new MessageRef(messages.at(i))); + for (size_t i = 0; i < messages.size(); i++) { + messageRefs.at(i) = MessageLayoutPtr(new MessageLayout(messages.at(i))); } if (this->messages.pushFront(messageRefs).size() > 0) { @@ -420,7 +414,7 @@ void ChannelView::setChannel(std::shared_ptr newChannel) std::vector highlights; highlights.reserve(messages.size()); - for (int i = 0; i < messages.size(); i++) { + for (size_t i = 0; i < messages.size(); i++) { highlights.push_back(messages.at(i)->getScrollBarHighlight()); } @@ -432,7 +426,7 @@ void ChannelView::setChannel(std::shared_ptr newChannel) // on message removed this->messageRemovedConnection = - newChannel->messageRemovedFromStart.connect([this](SharedMessage &) { + newChannel->messageRemovedFromStart.connect([this](MessagePtr &) { this->selection.min.messageIndex--; this->selection.max.messageIndex--; this->selection.start.messageIndex--; @@ -443,8 +437,8 @@ void ChannelView::setChannel(std::shared_ptr newChannel) // on message replaced this->messageReplacedConnection = - newChannel->messageReplaced.connect([this](size_t index, SharedMessage replacement) { - SharedMessageRef newItem(new MessageRef(replacement)); + newChannel->messageReplaced.connect([this](size_t index, MessagePtr replacement) { + MessageLayoutPtr newItem(new MessageLayout(replacement)); this->scrollBar.replaceHighlight(index, replacement->getScrollBarHighlight()); @@ -455,11 +449,11 @@ void ChannelView::setChannel(std::shared_ptr newChannel) auto snapshot = newChannel->getMessageSnapshot(); for (size_t i = 0; i < snapshot.getLength(); i++) { - SharedMessageRef deleted; + MessageLayoutPtr deleted; - auto messageRef = new MessageRef(snapshot[i]); + auto messageRef = new MessageLayout(snapshot[i]); - this->messages.pushBack(SharedMessageRef(messageRef), deleted); + this->messages.pushBack(MessageLayoutPtr(messageRef), deleted); } this->channel = newChannel; @@ -513,47 +507,20 @@ void ChannelView::setSelection(const SelectionItem &start, const SelectionItem & void ChannelView::paintEvent(QPaintEvent * /*event*/) { // BENCH(timer); + QPainter painter(this); -// only update gif emotes -#ifndef Q_OS_MACOS -// if (this->onlyUpdateEmotes) { -// this->onlyUpdateEmotes = false; - -// for (const GifEmoteData &item : this->gifEmotes) { -// painter.fillRect(item.rect, this->themeManager.ChatBackground); - -// painter.drawPixmap(item.rect, *item.image->getPixmap()); -// } - -// return; -// } -#endif - - // update all messages - this->gifEmotes.clear(); - painter.fillRect(rect(), this->themeManager.splits.background); // draw messages - this->drawMessages(painter, false); - - painter.setRenderHint(QPainter::SmoothPixmapTransform); - - // draw gif emotes - for (GifEmoteData &item : this->gifEmotes) { - painter.drawPixmap(item.rect, *item.image->getPixmap()); - } - - // draw the overlays of the messages (such as disabled message blur-out) - this->drawMessages(painter, true); + this->drawMessages(painter); // MARK(timer); } // if overlays is false then it draws the message, if true then it draws things such as the grey // overlay when a message is disabled -void ChannelView::drawMessages(QPainter &painter, bool overlays) +void ChannelView::drawMessages(QPainter &painter) { auto messagesSnapshot = this->getMessagesSnapshot(); @@ -566,83 +533,16 @@ void ChannelView::drawMessages(QPainter &painter, bool overlays) int y = -(messagesSnapshot[start].get()->getHeight() * (fmod(this->scrollBar.getCurrentValue(), 1))); - messages::MessageRef *end = nullptr; + messages::MessageLayout *end = nullptr; for (size_t i = start; i < messagesSnapshot.getLength(); ++i) { - messages::MessageRef *messageRef = messagesSnapshot[i].get(); + messages::MessageLayout *layout = messagesSnapshot[i].get(); - if (overlays) { - if (messageRef->isDisabled()) { - painter.fillRect(0, y, this->width(), messageRef->getHeight(), - this->themeManager.messages.disabled); - } - } else { - std::shared_ptr buffer = messageRef->buffer; + layout->paint(painter, y, i, this->selection); - // bool updateBuffer = messageRef->updateBuffer; - bool updateBuffer = false; + y += layout->getHeight(); - if (!buffer) { - QPixmap *pixmap; - -#ifdef Q_OS_MACOS - - pixmap = new QPixmap( - (int)(this->width() * painter.device()->devicePixelRatioF()), - (int)(messageRef->getHeight() * painter.device()->devicePixelRatioF())); - pixmap->setDevicePixelRatio(painter.device()->devicePixelRatioF()); -#else - pixmap = new QPixmap(this->width(), messageRef->getHeight()); -#endif - buffer = std::shared_ptr(pixmap); - updateBuffer = true; - } - - updateBuffer |= this->selecting; - - // update messages that have been changed - if (updateBuffer) { - this->updateMessageBuffer(messageRef, buffer.get(), i); - // qDebug() << "updating buffer xD"; - } - - // get gif emotes - for (messages::WordPart const &wordPart : messageRef->getWordParts()) { - if (wordPart.getWord().isImage()) { - messages::LazyLoadedImage &lli = wordPart.getWord().getImage(); - - if (lli.getAnimated()) { - GifEmoteData gifEmoteData; - gifEmoteData.image = &lli; - QRect rect(wordPart.getX(), wordPart.getY() + y, wordPart.getWidth(), - wordPart.getHeight()); - - gifEmoteData.rect = rect; - - this->gifEmotes.push_back(gifEmoteData); - } - } - } - - messageRef->buffer = buffer; - - if (buffer) { -//#ifdef Q_OS_MACOS -// painter.setRenderHint(QPainter::SmoothPixmapTransform, false); -// painter.drawPixmap(0, y, - -// (int)(buffer->width() / painter.device()->devicePixelRatioF()), -// (int)(buffer->height() / painter.device()->devicePixelRatioF()), -// *buffer.get()); -//#else - painter.drawPixmap(0, y, *buffer.get()); -//#endif - } - } - - y += messageRef->getHeight(); - - end = messageRef; + end = layout; if (y > height()) { break; } @@ -662,222 +562,168 @@ void ChannelView::drawMessages(QPainter &painter, bool overlays) } // delete the message buffers that aren't on screen - for (std::shared_ptr item : this->messagesOnScreen) { - item->buffer.reset(); + for (const std::shared_ptr &item : this->messagesOnScreen) { + item->deleteBuffer(); } this->messagesOnScreen.clear(); // add all messages on screen to the map for (size_t i = start; i < messagesSnapshot.getLength(); ++i) { - std::shared_ptr messageRef = messagesSnapshot[i]; + std::shared_ptr layout = messagesSnapshot[i]; - this->messagesOnScreen.insert(messageRef); + this->messagesOnScreen.insert(layout); - if (messageRef.get() == end) { + if (layout.get() == end) { break; } } } -void ChannelView::updateMessageBuffer(messages::MessageRef *messageRef, QPixmap *buffer, - int messageIndex) -{ - QPainter painter(buffer); - - painter.setRenderHint(QPainter::SmoothPixmapTransform); - - // draw background - // if (this->selectionMin.messageIndex <= messageIndex && - // this->selectionMax.messageIndex >= messageIndex) { - // painter.fillRect(buffer->rect(), QColor(24, 55, 25)); - //} else { - painter.fillRect(buffer->rect(), - (messageRef->getMessage()->containsHighlightedPhrase()) - ? this->themeManager.messages.backgrounds.highlighted - : this->themeManager.messages.backgrounds.regular); - //} - - // draw selection - if (!selection.isEmpty()) { - drawMessageSelection(painter, messageRef, messageIndex, buffer->height()); - } - - // draw message - for (messages::WordPart const &wordPart : messageRef->getWordParts()) { - // image - if (wordPart.getWord().isImage()) { - messages::LazyLoadedImage &lli = wordPart.getWord().getImage(); - - const QPixmap *image = lli.getPixmap(); - - if (image != nullptr && !lli.getAnimated()) { - painter.drawPixmap(QRect(wordPart.getX(), wordPart.getY(), wordPart.getWidth(), - wordPart.getHeight()), - *image); - } - } - // text - else { - QColor color = wordPart.getWord().getTextColor().getColor(this->themeManager); - - this->themeManager.normalizeColor(color); - - painter.setPen(color); - painter.setFont(wordPart.getWord().getFont(this->getDpiMultiplier())); - - painter.drawText(QRectF(wordPart.getX(), wordPart.getY(), 10000, 10000), - wordPart.getText(), QTextOption(Qt::AlignLeft | Qt::AlignTop)); - } - } - - messageRef->updateBuffer = false; -} - -void ChannelView::drawMessageSelection(QPainter &painter, messages::MessageRef *messageRef, - int messageIndex, int bufferHeight) -{ - if (this->selection.min.messageIndex > messageIndex || - this->selection.max.messageIndex < messageIndex) { - return; - } - - QColor selectionColor = this->themeManager.messages.selection; - - int charIndex = 0; - size_t i = 0; - auto &parts = messageRef->getWordParts(); - - int currentLineNumber = 0; - QRect rect; - - if (parts.size() > 0) { - if (selection.min.messageIndex == messageIndex) { - rect.setTop(parts.at(0).getY()); - } - rect.setLeft(parts.at(0).getX()); - } - - // skip until selection start - if (this->selection.min.messageIndex == messageIndex && this->selection.min.charIndex != 0) { - for (; i < parts.size(); i++) { - const messages::WordPart &part = parts.at(i); - auto characterLength = part.getCharacterLength(); - - if (characterLength + charIndex > selection.min.charIndex) { - break; - } - - charIndex += characterLength; - currentLineNumber = part.getLineNumber(); - } - - if (i >= parts.size()) { - return; - } - - // handle word that has a cut of selection - const messages::WordPart &part = parts.at(i); - - // check if selection if single word - int characterLength = part.getCharacterLength(); - bool isSingleWord = charIndex + characterLength > this->selection.max.charIndex && - this->selection.max.messageIndex == messageIndex; - - rect = part.getRect(); - currentLineNumber = part.getLineNumber(); - - if (part.getWord().isText()) { - int offset = this->selection.min.charIndex - charIndex; - - for (int j = 0; j < offset; j++) { - rect.setLeft(rect.left() + part.getCharWidth(j, this->getDpiMultiplier())); - } - - if (isSingleWord) { - int length = (this->selection.max.charIndex - charIndex) - offset; - - rect.setRight(part.getX()); - - for (int j = 0; j < offset + length; j++) { - rect.setRight(rect.right() + part.getCharWidth(j, this->getDpiMultiplier())); - } - - painter.fillRect(rect, selectionColor); - - return; - } - } else { - if (isSingleWord) { - if (charIndex + 1 != this->selection.max.charIndex) { - rect.setRight(part.getX() + part.getWord().getImage().getScaledWidth()); - } - painter.fillRect(rect, selectionColor); - - return; - } - - if (charIndex != this->selection.min.charIndex) { - rect.setLeft(part.getX() + part.getWord().getImage().getScaledWidth()); - } - } - - i++; - charIndex += characterLength; - } - - // go through lines and draw selection - for (; i < parts.size(); i++) { - const messages::WordPart &part = parts.at(i); - - int charLength = part.getCharacterLength(); - - bool isLastSelectedWord = this->selection.max.messageIndex == messageIndex && - charIndex + charLength > this->selection.max.charIndex; - - if (part.getLineNumber() == currentLineNumber) { - rect.setLeft(std::min(rect.left(), part.getX())); - rect.setTop(std::min(rect.top(), part.getY())); - rect.setRight(std::max(rect.right(), part.getRight())); - rect.setBottom(std::max(rect.bottom(), part.getBottom() - 1)); - } else { - painter.fillRect(rect, selectionColor); - - currentLineNumber = part.getLineNumber(); - - rect = part.getRect(); - } - - if (isLastSelectedWord) { - if (part.getWord().isText()) { - int offset = this->selection.min.charIndex - charIndex; - - int length = (this->selection.max.charIndex - charIndex) - offset; - - rect.setRight(part.getX()); - - for (int j = 0; j < offset + length; j++) { - rect.setRight(rect.right() + part.getCharWidth(j, this->getDpiMultiplier())); - } - } else { - if (this->selection.max.charIndex == charIndex) { - rect.setRight(part.getX()); - } - } - painter.fillRect(rect, selectionColor); - - return; - } - - charIndex += charLength; - } - - if (this->selection.max.messageIndex != messageIndex) { - rect.setBottom(bufferHeight); - } - - painter.fillRect(rect, selectionColor); -} +// void ChannelView::drawMessageSelection(QPainter &painter, messages::MessageLayout *messageRef, +// int messageIndex, int bufferHeight) +//{ +// if (this->selection.min.messageIndex > messageIndex || +// this->selection.max.messageIndex < messageIndex) { +// return; +// } +// +// QColor selectionColor = this->themeManager.messages.selection; +// +// int charIndex = 0; +// size_t i = 0; +// auto &parts = messageRef->getWordParts(); +// +// int currentLineNumber = 0; +// QRect rect; +// +// if (parts.size() > 0) { +// if (selection.min.messageIndex == messageIndex) { +// rect.setTop(parts.at(0).getY()); +// } +// rect.setLeft(parts.at(0).getX()); +// } +// +// // skip until selection start +// if (this->selection.min.messageIndex == messageIndex && this->selection.min.charIndex != 0) { +// for (; i < parts.size(); i++) { +// const messages::MessageLayoutElement &part = parts.at(i); +// auto characterLength = part.getCharacterLength(); +// +// if (characterLength + charIndex > selection.min.charIndex) { +// break; +// } +// +// charIndex += characterLength; +// currentLineNumber = part.getLineNumber(); +// } +// +// if (i >= parts.size()) { +// return; +// } +// +// // handle word that has a cut of selection +// const messages::MessageLayoutElement &part = parts.at(i); +// +// // check if selection if single word +// int characterLength = part.getCharacterLength(); +// bool isSingleWord = charIndex + characterLength > this->selection.max.charIndex && +// this->selection.max.messageIndex == messageIndex; +// +// rect = part.getRect(); +// currentLineNumber = part.getLineNumber(); +// +// if (part.getWord().isText()) { +// int offset = this->selection.min.charIndex - charIndex; +// +// for (int j = 0; j < offset; j++) { +// rect.setLeft(rect.left() + part.getCharWidth(j, this->getDpiMultiplier())); +// } +// +// if (isSingleWord) { +// int length = (this->selection.max.charIndex - charIndex) - offset; +// +// rect.setRight(part.getX()); +// +// for (int j = 0; j < offset + length; j++) { +// rect.setRight(rect.right() + part.getCharWidth(j, this->getDpiMultiplier())); +// } +// +// painter.fillRect(rect, selectionColor); +// +// return; +// } +// } else { +// if (isSingleWord) { +// if (charIndex + 1 != this->selection.max.charIndex) { +// rect.setRight(part.getX() + part.getWord().getImage().getScaledWidth()); +// } +// painter.fillRect(rect, selectionColor); +// +// return; +// } +// +// if (charIndex != this->selection.min.charIndex) { +// rect.setLeft(part.getX() + part.getWord().getImage().getScaledWidth()); +// } +// } +// +// i++; +// charIndex += characterLength; +// } +// +// // go through lines and draw selection +// for (; i < parts.size(); i++) { +// const messages::MessageLayoutElement &part = parts.at(i); +// +// int charLength = part.getCharacterLength(); +// +// bool isLastSelectedWord = this->selection.max.messageIndex == messageIndex && +// charIndex + charLength > this->selection.max.charIndex; +// +// if (part.getLineNumber() == currentLineNumber) { +// rect.setLeft(std::min(rect.left(), part.getX())); +// rect.setTop(std::min(rect.top(), part.getY())); +// rect.setRight(std::max(rect.right(), part.getRight())); +// rect.setBottom(std::max(rect.bottom(), part.getBottom() - 1)); +// } else { +// painter.fillRect(rect, selectionColor); +// +// currentLineNumber = part.getLineNumber(); +// +// rect = part.getRect(); +// } +// +// if (isLastSelectedWord) { +// if (part.getWord().isText()) { +// int offset = this->selection.min.charIndex - charIndex; +// +// int length = (this->selection.max.charIndex - charIndex) - offset; +// +// rect.setRight(part.getX()); +// +// for (int j = 0; j < offset + length; j++) { +// rect.setRight(rect.right() + part.getCharWidth(j, this->getDpiMultiplier())); +// } +// } else { +// if (this->selection.max.charIndex == charIndex) { +// rect.setRight(part.getX()); +// } +// } +// painter.fillRect(rect, selectionColor); +// +// return; +// } +// +// charIndex += charLength; +// } +// +// if (this->selection.max.messageIndex != messageIndex) { +// rect.setBottom(bufferHeight); +// } +// +// painter.fillRect(rect, selectionColor); +//} void ChannelView::wheelEvent(QWheelEvent *event) { @@ -888,7 +734,8 @@ void ChannelView::wheelEvent(QWheelEvent *event) float delta = event->delta() * 1.5 * mouseMultiplier; auto snapshot = this->getMessagesSnapshot(); - int i = std::min((int)desired, (int)snapshot.getLength()); + int snapshotLength = (int)snapshot.getLength(); + int i = std::min((int)desired, snapshotLength); if (delta > 0) { float scrollFactor = fmod(desired, 1); @@ -916,7 +763,7 @@ void ChannelView::wheelEvent(QWheelEvent *event) float scrollFactor = 1 - fmod(desired, 1); float currentScrollLeft = (int)(scrollFactor * snapshot[i]->getHeight()); - for (; i < snapshot.getLength(); i++) { + for (; i < snapshotLength; i++) { if (delta < currentScrollLeft) { desired += scrollFactor * ((double)delta / currentScrollLeft); break; @@ -925,7 +772,7 @@ void ChannelView::wheelEvent(QWheelEvent *event) desired += scrollFactor; } - if (i == snapshot.getLength() - 1) { + if (i == snapshotLength - 1) { desired = snapshot.getLength(); } else { snapshot[i + 1]->layout(LAYOUT_WIDTH, this->getDpiMultiplier()); @@ -957,12 +804,12 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event) } auto tooltipWidget = TooltipWidget::getInstance(); - std::shared_ptr message; + std::shared_ptr layout; QPoint relativePos; int messageIndex; // no message under cursor - if (!tryGetMessageAt(event->pos(), message, relativePos, messageIndex)) { + if (!tryGetMessageAt(event->pos(), layout, relativePos, messageIndex)) { this->setCursor(Qt::ArrowCursor); tooltipWidget->hide(); return; @@ -971,7 +818,7 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event) // is selecting if (this->selecting) { this->pause(500); - int index = message->getSelectionIndex(relativePos); + int index = layout->getSelectionIndex(relativePos); this->setSelection(this->selection.start, SelectionItem(messageIndex, index)); @@ -979,29 +826,28 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event) } // message under cursor is collapsed - if (message->isCollapsed()) { + if (layout->getFlags() & MessageLayout::Collapsed) { this->setCursor(Qt::PointingHandCursor); tooltipWidget->hide(); return; } // check if word underneath cursor - const messages::Word *hoverWord; - if ((hoverWord = message->tryGetWordPart(relativePos)) == nullptr) { + const messages::MessageLayoutElement *hoverLayoutElement = layout->getElementAt(relativePos); + + if (hoverLayoutElement == nullptr) { this->setCursor(Qt::ArrowCursor); tooltipWidget->hide(); return; } - const auto &tooltip = hoverWord->getTooltip(); + const auto &tooltip = hoverLayoutElement->getCreator().getTooltip(); - if (hoverWord->isImage()) { - tooltipWidget->moveTo(event->globalPos()); - tooltipWidget->setText(tooltip); - tooltipWidget->show(); - } + tooltipWidget->moveTo(event->globalPos()); + tooltipWidget->setText(tooltip); + tooltipWidget->show(); // check if word has a link - if (hoverWord->getLink().isValid()) { + if (hoverLayoutElement->getCreator().getLink().isValid()) { this->setCursor(Qt::PointingHandCursor); } else { this->setCursor(Qt::ArrowCursor); @@ -1018,13 +864,13 @@ void ChannelView::mousePressEvent(QMouseEvent *event) this->lastPressPosition = event->screenPos(); - std::shared_ptr message; + std::shared_ptr layout; QPoint relativePos; int messageIndex; this->mouseDown(event); - if (!tryGetMessageAt(event->pos(), message, relativePos, messageIndex)) { + if (!tryGetMessageAt(event->pos(), layout, relativePos, messageIndex)) { setCursor(Qt::ArrowCursor); auto messagesSnapshot = this->getMessagesSnapshot(); @@ -1045,11 +891,11 @@ void ChannelView::mousePressEvent(QMouseEvent *event) } // check if message is collapsed - if (message->isCollapsed()) { + if (layout->getFlags() & MessageLayout::Collapsed) { return; } - int index = message->getSelectionIndex(relativePos); + int index = layout->getSelectionIndex(relativePos); auto selectionItem = SelectionItem(messageIndex, index); this->setSelection(selectionItem, selectionItem); @@ -1087,30 +933,30 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event) // show user thing pajaW - std::shared_ptr message; + std::shared_ptr layout; QPoint relativePos; int messageIndex; - if (!tryGetMessageAt(event->pos(), message, relativePos, messageIndex)) { + if (!tryGetMessageAt(event->pos(), layout, relativePos, messageIndex)) { // No message at clicked position this->userPopupWidget.hide(); return; } // message under cursor is collapsed - if (message->isCollapsed()) { - message->setCollapsed(false); + if (layout->getFlags() & MessageLayout::Collapsed) { + layout->addFlags(MessageLayout::Collapsed); this->layoutMessages(); return; } - const messages::Word *hoverWord; + const messages::MessageLayoutElement *hoverLayoutElement = layout->getElementAt(relativePos); - if ((hoverWord = message->tryGetWordPart(relativePos)) == nullptr) { + if (hoverLayoutElement == nullptr) { return; } - auto &link = hoverWord->getLink(); + auto &link = hoverLayoutElement->getCreator().getLink(); switch (link.getType()) { case messages::Link::UserInfo: { @@ -1131,7 +977,7 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event) } } -bool ChannelView::tryGetMessageAt(QPoint p, std::shared_ptr &_message, +bool ChannelView::tryGetMessageAt(QPoint p, std::shared_ptr &_message, QPoint &relativePos, int &index) { auto messagesSnapshot = this->getMessagesSnapshot(); diff --git a/src/widgets/helper/channelview.hpp b/src/widgets/helper/channelview.hpp index f0c296632..1ed0aa8d1 100644 --- a/src/widgets/helper/channelview.hpp +++ b/src/widgets/helper/channelview.hpp @@ -1,11 +1,11 @@ #pragma once #include "channel.hpp" -#include "messages/lazyloadedimage.hpp" +#include "messages/image.hpp" +#include "messages/layouts/messagelayout.hpp" #include "messages/limitedqueuesnapshot.hpp" -#include "messages/messageref.hpp" +#include "messages/messageelement.hpp" #include "messages/selection.hpp" -#include "messages/word.hpp" #include "widgets/accountpopup.hpp" #include "widgets/basewidget.hpp" #include "widgets/helper/rippleeffectlabel.hpp" @@ -31,7 +31,6 @@ public: explicit ChannelView(BaseWidget *parent = 0); ~ChannelView(); - void updateGifEmotes(); void queueUpdate(); Scrollbar &getScrollBar(); QString getSelectedText(); @@ -41,8 +40,8 @@ public: bool getEnableScrollingToBottom() const; void pause(int msecTimeout); - void setChannel(std::shared_ptr channel); - messages::LimitedQueueSnapshot getMessagesSnapshot(); + void setChannel(SharedChannel channel); + messages::LimitedQueueSnapshot getMessagesSnapshot(); void layoutMessages(); void clearMessages(); @@ -64,35 +63,25 @@ protected: virtual void mousePressEvent(QMouseEvent *event) override; virtual void mouseReleaseEvent(QMouseEvent *event) override; - bool tryGetMessageAt(QPoint p, std::shared_ptr &message, + bool tryGetMessageAt(QPoint p, std::shared_ptr &message, QPoint &relativePos, int &index); private: - struct GifEmoteData { - messages::LazyLoadedImage *image; - QRect rect; - }; - QTimer updateTimer; bool updateQueued = false; bool messageWasAdded = false; bool paused = false; QTimer pauseTimeout; - messages::LimitedQueueSnapshot snapshot; + messages::LimitedQueueSnapshot snapshot; void detachChannel(); void actuallyLayoutMessages(); - void drawMessages(QPainter &painter, bool overlays); - void updateMessageBuffer(messages::MessageRef *messageRef, QPixmap *buffer, int messageIndex); - void drawMessageSelection(QPainter &painter, messages::MessageRef *messageRef, int messageIndex, - int bufferHeight); + void drawMessages(QPainter &painter); void setSelection(const messages::SelectionItem &start, const messages::SelectionItem &end); - std::shared_ptr channel; - - std::vector gifEmotes; + SharedChannel channel; Scrollbar scrollBar; RippleEffectLabel *goToBottom; @@ -112,7 +101,7 @@ private: messages::Selection selection; bool selecting = false; - messages::LimitedQueue messages; + messages::LimitedQueue messages; boost::signals2::connection messageAppendedConnection; boost::signals2::connection messageAddedAtStartConnection; @@ -123,7 +112,7 @@ private: std::vector managedConnections; - std::unordered_set> messagesOnScreen; + std::unordered_set> messagesOnScreen; private slots: void wordTypeMaskChanged() diff --git a/src/widgets/helper/searchpopup.cpp b/src/widgets/helper/searchpopup.cpp index b8aa72bc8..e9b1e51ad 100644 --- a/src/widgets/helper/searchpopup.cpp +++ b/src/widgets/helper/searchpopup.cpp @@ -59,7 +59,7 @@ void SearchPopup::initLayout() } } -void SearchPopup::setChannel(std::shared_ptr channel) +void SearchPopup::setChannel(SharedChannel channel) { this->snapshot = channel->getMessageSnapshot(); this->performSearch(); @@ -71,13 +71,13 @@ void SearchPopup::performSearch() { QString text = searchInput->text(); - std::shared_ptr channel(new Channel("search")); + SharedChannel channel(new Channel("search")); for (size_t i = 0; i < this->snapshot.getLength(); i++) { - messages::SharedMessage message = this->snapshot[i]; + messages::MessagePtr message = this->snapshot[i]; if (text.isEmpty() || - message->getContent().indexOf(this->searchInput->text(), 0, Qt::CaseInsensitive) != + message->getSearchText().indexOf(this->searchInput->text(), 0, Qt::CaseInsensitive) != -1) { channel->addMessage(message); } diff --git a/src/widgets/helper/searchpopup.hpp b/src/widgets/helper/searchpopup.hpp index 71d7db24c..613700bff 100644 --- a/src/widgets/helper/searchpopup.hpp +++ b/src/widgets/helper/searchpopup.hpp @@ -20,7 +20,7 @@ public: void setChannel(std::shared_ptr channel); private: - messages::LimitedQueueSnapshot snapshot; + messages::LimitedQueueSnapshot snapshot; QLineEdit *searchInput; ChannelView *channelView; diff --git a/src/widgets/helper/splitinput.cpp b/src/widgets/helper/splitinput.cpp index 41e9615b0..125920c3d 100644 --- a/src/widgets/helper/splitinput.cpp +++ b/src/widgets/helper/splitinput.cpp @@ -48,9 +48,8 @@ SplitInput::SplitInput(Split *_chatWidget) this->textLengthLabel.setAlignment(Qt::AlignRight); this->emotesLabel.getLabel().setTextFormat(Qt::RichText); - this->emotesLabel.getLabel().setText( - ""); + this->emotesLabel.getLabel().setText(""); connect(&this->emotesLabel, &RippleEffectLabel::clicked, [this] { if (this->emotePopup == nullptr) { @@ -86,16 +85,17 @@ SplitInput::SplitInput(Split *_chatWidget) sendMessage = sendMessage.replace('\n', ' '); c->sendMessage(sendMessage); - prevMsg.append(message); + this->prevMsg.append(message); event->accept(); if (!(event->modifiers() == Qt::ControlModifier)) { - textInput.setText(QString()); - prevIndex = 0; - } else if (textInput.toPlainText() == prevMsg.at(prevMsg.size() - 1)) { - prevMsg.removeLast(); + this->textInput.setText(QString()); + this->prevIndex = 0; + } else if (this->textInput.toPlainText() == + this->prevMsg.at(this->prevMsg.size() - 1)) { + this->prevMsg.removeLast(); } - prevIndex = prevMsg.size(); + this->prevIndex = this->prevMsg.size(); } else if (event->key() == Qt::Key_Up) { if (event->modifiers() == Qt::AltModifier) { SplitContainer *page = @@ -108,9 +108,9 @@ SplitInput::SplitInput(Split *_chatWidget) page->requestFocus(reqX, reqY); } else { - if (prevMsg.size() && prevIndex) { - prevIndex--; - textInput.setText(prevMsg.at(prevIndex)); + if (this->prevMsg.size() && this->prevIndex) { + this->prevIndex--; + this->textInput.setText(this->prevMsg.at(this->prevIndex)); } } } else if (event->key() == Qt::Key_Down) { @@ -125,12 +125,13 @@ SplitInput::SplitInput(Split *_chatWidget) page->requestFocus(reqX, reqY); } else { - if (prevIndex != (prevMsg.size() - 1) && prevIndex != prevMsg.size()) { - prevIndex++; - textInput.setText(prevMsg.at(prevIndex)); + if (this->prevIndex != (this->prevMsg.size() - 1) && + this->prevIndex != this->prevMsg.size()) { + this->prevIndex++; + this->textInput.setText(this->prevMsg.at(this->prevIndex)); } else { - prevIndex = prevMsg.size(); - textInput.setText(QString()); + this->prevIndex = this->prevMsg.size(); + this->textInput.setText(QString()); } } } else if (event->key() == Qt::Key_Left) { diff --git a/src/widgets/helper/splitinput.hpp b/src/widgets/helper/splitinput.hpp index 14e354078..63321af74 100644 --- a/src/widgets/helper/splitinput.hpp +++ b/src/widgets/helper/splitinput.hpp @@ -48,7 +48,7 @@ private: QLabel textLengthLabel; RippleEffectLabel emotesLabel; QStringList prevMsg; - unsigned int prevIndex = 0; + int prevIndex = 0; virtual void refreshTheme() override; private slots: diff --git a/src/widgets/notebook.cpp b/src/widgets/notebook.cpp index 531eb96f5..2321e0887 100644 --- a/src/widgets/notebook.cpp +++ b/src/widgets/notebook.cpp @@ -126,9 +126,9 @@ void Notebook::select(SplitContainer *page) this->performLayout(); } -void Notebook::selectIndex(unsigned index) +void Notebook::selectIndex(int index) { - if (index >= this->pages.size()) { + if (index < 0 || index >= this->pages.size()) { return; } diff --git a/src/widgets/notebook.hpp b/src/widgets/notebook.hpp index cbf7ec009..b0b9a1551 100644 --- a/src/widgets/notebook.hpp +++ b/src/widgets/notebook.hpp @@ -30,7 +30,7 @@ public: void removePage(SplitContainer *page); void removeCurrentPage(); void select(SplitContainer *page); - void selectIndex(unsigned index); + void selectIndex(int index); SplitContainer *getSelectedPage() const { diff --git a/src/widgets/settingsdialog.cpp b/src/widgets/settingsdialog.cpp index 45103bda0..18bf5e078 100644 --- a/src/widgets/settingsdialog.cpp +++ b/src/widgets/settingsdialog.cpp @@ -297,7 +297,7 @@ QVBoxLayout *SettingsDialog::createAppearanceTab() auto v = new QVBoxLayout(); v->addWidget(createCheckbox("Show timestamp", settings.showTimestamps)); - v->addWidget(createCheckbox("Show seconds in timestamp", settings.showTimestampSeconds)); + // fourtf: add timestamp format v->addWidget(createCheckbox("Show badges", settings.showBadges)); v->addWidget(createCheckbox("Allow sending duplicate messages (add a space at the end)", settings.allowDuplicateMessages)); diff --git a/src/widgets/split.cpp b/src/widgets/split.cpp index 1667d9cc6..73eb7196f 100644 --- a/src/widgets/split.cpp +++ b/src/widgets/split.cpp @@ -121,17 +121,17 @@ const std::string &Split::getUUID() const return this->uuid; } -std::shared_ptr Split::getChannel() const +SharedChannel Split::getChannel() const { return this->channel; } -std::shared_ptr &Split::getChannelRef() +SharedChannel &Split::getChannelRef() { return this->channel; } -void Split::setChannel(std::shared_ptr _newChannel) +void Split::setChannel(SharedChannel _newChannel) { this->view.setChannel(_newChannel); @@ -212,7 +212,8 @@ void Split::layoutMessages() void Split::updateGifEmotes() { - this->view.updateGifEmotes(); + qDebug() << "this shouldn't even exist"; + this->view.queueUpdate(); } void Split::giveFocus(Qt::FocusReason reason) diff --git a/src/widgets/split.hpp b/src/widgets/split.hpp index 09f3fd983..718b45272 100644 --- a/src/widgets/split.hpp +++ b/src/widgets/split.hpp @@ -1,10 +1,10 @@ #pragma once #include "channel.hpp" +#include "messages/layouts/messagelayout.hpp" +#include "messages/layouts/messagelayoutelement.hpp" #include "messages/limitedqueuesnapshot.hpp" -#include "messages/messageref.hpp" -#include "messages/word.hpp" -#include "messages/wordpart.hpp" +#include "messages/messageelement.hpp" #include "widgets/basewidget.hpp" #include "widgets/helper/channelview.hpp" #include "widgets/helper/rippleeffectlabel.hpp" @@ -55,8 +55,8 @@ public: } const std::string &getUUID() const; - std::shared_ptr getChannel() const; - std::shared_ptr &getChannelRef(); + SharedChannel getChannel() const; + SharedChannel &getChannelRef(); void setFlexSizeX(double x); double getFlexSizeX(); void setFlexSizeY(double y); @@ -73,7 +73,7 @@ protected: private: SplitContainer &parentPage; - std::shared_ptr channel; + SharedChannel channel; QVBoxLayout vbox; SplitHeader header; @@ -84,7 +84,7 @@ private: boost::signals2::connection channelIDChangedConnection; - void setChannel(std::shared_ptr newChannel); + void setChannel(SharedChannel newChannel); void doOpenAccountPopupWidget(AccountPopupWidget *widget, QString user); void channelNameUpdated(const std::string &newChannelName);