I BROKE EVERYTHING

refactored the rendering process
This commit is contained in:
fourtf 2018-01-11 20:16:25 +01:00
parent c240d6f7c2
commit 10850c0ec7
62 changed files with 2155 additions and 2117 deletions

View file

@ -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 =

View file

@ -29,14 +29,14 @@ bool Channel::isEmpty() const
return false;
}
messages::LimitedQueueSnapshot<messages::SharedMessage> Channel::getMessageSnapshot()
messages::LimitedQueueSnapshot<messages::MessagePtr> Channel::getMessageSnapshot()
{
return this->messages.getSnapshot();
}
void Channel::addMessage(std::shared_ptr<Message> message)
void Channel::addMessage(MessagePtr message)
{
std::shared_ptr<Message> deleted;
MessagePtr deleted;
const QString &username = message->loginName;
@ -56,16 +56,16 @@ void Channel::addMessage(std::shared_ptr<Message> message)
this->messageAppended(message);
}
void Channel::addMessagesAtStart(std::vector<messages::SharedMessage> &_messages)
void Channel::addMessagesAtStart(std::vector<messages::MessagePtr> &_messages)
{
std::vector<messages::SharedMessage> addedMessages = this->messages.pushFront(_messages);
std::vector<messages::MessagePtr> 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);

View file

@ -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<Channel>
public:
explicit Channel(const QString &_name);
boost::signals2::signal<void(messages::SharedMessage &)> messageRemovedFromStart;
boost::signals2::signal<void(messages::SharedMessage &)> messageAppended;
boost::signals2::signal<void(std::vector<messages::SharedMessage> &)> messagesAddedAtStart;
boost::signals2::signal<void(size_t index, messages::SharedMessage &)> messageReplaced;
boost::signals2::signal<void(messages::MessagePtr &)> messageRemovedFromStart;
boost::signals2::signal<void(messages::MessagePtr &)> messageAppended;
boost::signals2::signal<void(std::vector<messages::MessagePtr> &)> messagesAddedAtStart;
boost::signals2::signal<void(size_t index, messages::MessagePtr &)> messageReplaced;
virtual bool isEmpty() const;
messages::LimitedQueueSnapshot<messages::SharedMessage> getMessageSnapshot();
messages::LimitedQueueSnapshot<messages::MessagePtr> getMessageSnapshot();
void addMessage(messages::SharedMessage message);
void addMessagesAtStart(std::vector<messages::SharedMessage> &messages);
void replaceMessage(messages::SharedMessage message, messages::SharedMessage replacement);
void addMessage(messages::MessagePtr message);
void addMessagesAtStart(std::vector<messages::MessagePtr> &messages);
void replaceMessage(messages::MessagePtr message, messages::MessagePtr replacement);
void addRecentChatter(const std::shared_ptr<messages::Message> &message);
struct NameOptions {
@ -55,9 +55,11 @@ public:
virtual void sendMessage(const QString &message);
private:
messages::LimitedQueue<messages::SharedMessage> messages;
messages::LimitedQueue<messages::MessagePtr> messages;
// std::shared_ptr<logging::Channel> loggingChannel;
};
typedef std::shared_ptr<Channel> SharedChannel;
} // namespace chatterino

View file

@ -1,7 +1,7 @@
#pragma once
#include "lockedobject.hpp"
#include "messages/lazyloadedimage.hpp"
#include "messages/image.hpp"
namespace chatterino {

View file

@ -1,6 +1,6 @@
#pragma once
#include "messages/lazyloadedimage.hpp"
#include "messages/image.hpp"
#include "util/concurrentmap.hpp"
#include <QObject>

View file

@ -39,7 +39,7 @@ void Channel::append(std::shared_ptr<messages::Message> message)
str.append("] ");
str.append(message->loginName);
str.append(": ");
str.append(message->getContent());
str.append(message->getSearchText());
str.append('\n');
this->appendLine(str);
}

View file

@ -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);

View file

@ -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<int>(getWidth() * this->scale);
return static_cast<int>(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<int>(getHeight() * this->scale);
return static_cast<int>(this->getHeight() * this->scale);
}
} // namespace messages

View file

@ -3,21 +3,23 @@
#include <QPixmap>
#include <QString>
#include <boost/noncopyable.hpp>
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;

View file

@ -0,0 +1,345 @@
#include "messages/layouts/messagelayout.hpp"
#include "singletons/emotemanager.hpp"
#include "singletons/settingsmanager.hpp"
#include <QApplication>
#include <QDebug>
#include <QPainter>
#include <QThread>
#include <QtGlobal>
#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<MessageElement> &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<QPixmap>(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

View file

@ -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 <QPixmap>
#include <boost/noncopyable.hpp>
#include <cinttypes>
#include <memory>
namespace chatterino {
namespace messages {
namespace layouts {
class MessageLayout;
typedef std::shared_ptr<MessageLayout> 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<QPixmap> 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

View file

@ -0,0 +1,157 @@
#include "messagelayoutcontainer.hpp"
#include "messagelayoutelement.hpp"
#include "messages/selection.hpp"
#include "singletons/settingsmanager.hpp"
#include <QPainter>
#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<MessageLayoutElement>(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<MessageLayoutElement> &element : this->elements) {
if (element->getRect().contains(point)) {
return element.get();
}
}
return nullptr;
}
// painting
void MessageLayoutContainer::paintElements(QPainter &painter)
{
for (const std::unique_ptr<MessageLayoutElement> &element : this->elements) {
element->paint(painter);
}
}
void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
Selection &selection)
{
}
} // namespace layouts
}
}

View file

@ -0,0 +1,84 @@
#pragma once
#include <memory>
#include <vector>
#include <QPoint>
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<std::unique_ptr<MessageLayoutElement>> elements;
};
} // namespace layouts
}
}

View file

@ -0,0 +1,132 @@
#include "messages/layouts/messagelayoutelement.hpp"
#include "messages/messageelement.hpp"
#include <QPainter>
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 += "<image>";
}
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

View file

@ -0,0 +1,88 @@
#pragma once
#include <QPoint>
#include <QRect>
#include <QString>
#include <boost/noncopyable.hpp>
#include <climits>
#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 &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

View file

@ -171,7 +171,7 @@ public:
{
std::lock_guard<std::mutex> 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<T> &items, const T &index)

View file

@ -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 <ctime>
#include <list>
#include <tuple>
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<MessageElement>(element));
}
void Message::setHighlight(bool value)
const std::vector<std::unique_ptr<MessageElement>> &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<std::chrono::system_clock> &Message::getParseTime() const
{
return this->parseTime;
}
std::vector<Word> &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;

View file

@ -1,94 +1,88 @@
#pragma once
#include "messages/word.hpp"
#include "messages/messageelement.hpp"
#include "widgets/helper/scrollbarhighlight.hpp"
#include <chrono>
#include <cinttypes>
#include <memory>
#include <vector>
#include <QTime>
namespace chatterino {
class Channel;
namespace messages {
class Message;
typedef std::shared_ptr<Message> SharedMessage;
typedef char MessageFlagsType;
typedef std::shared_ptr<Message> 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<std::chrono::system_clock> &getParseTime() const;
std::vector<Word> &getWords();
// Elements
// Messages should not be added after the message is done initializing.
void addElement(MessageElement *element);
const std::vector<std::unique_ptr<MessageElement>> &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<std::chrono::system_clock> parseTime;
mutable QString content;
QTime parseTime;
mutable QString searchText;
QString id = "";
std::vector<Word> words;
std::vector<std::unique_ptr<MessageElement>> elements;
};
} // namespace messages

View file

@ -1,7 +1,7 @@
#include "messagebuilder.hpp"
#include "singletons/thememanager.hpp"
#include "singletons/emotemanager.hpp"
#include "singletons/resourcemanager.hpp"
#include "singletons/thememanager.hpp"
#include <QDateTime>
@ -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)

View file

@ -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<std::is_base_of<MessageElement, T>::value, T>::type
template <class T, class... Args>
T *append(Args &&... args)
{
static_assert(std::is_base_of<MessageElement, T>::value, "T must extend MessageElement");
T *element = new T(std::forward<Args>(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<messages::Message> message;
private:
std::vector<Word> _words;
bool highlight = false;
std::chrono::time_point<std::chrono::system_clock> _parseTime;
MessagePtr message;
};
} // namespace messages

View file

@ -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;

View file

@ -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

View file

@ -0,0 +1,224 @@
#pragma once
#include "messages/image.hpp"
#include "messages/link.hpp"
#include "messages/messagecolor.hpp"
#include "singletons/fontmanager.hpp"
#include <stdint.h>
#include <QRect>
#include <QString>
#include <QTime>
#include <boost/noncopyable.hpp>
#include <util/emotemap.hpp>
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 &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<Word> 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

View file

@ -1,450 +0,0 @@
#include "messages/messageref.hpp"
#include "singletons/emotemanager.hpp"
#include "singletons/settingsmanager.hpp"
#include <QDebug>
#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<WordPart> &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

View file

@ -1,70 +0,0 @@
#pragma once
#include "messages/message.hpp"
#include "messages/wordpart.hpp"
#include <QPixmap>
#include <memory>
namespace chatterino {
namespace messages {
class MessageRef;
typedef std::shared_ptr<MessageRef> SharedMessageRef;
class MessageRef
{
public:
MessageRef(SharedMessage message);
Message *getMessage();
int getHeight() const;
bool layout(int width, float scale);
const std::vector<WordPart> &getWordParts() const;
std::shared_ptr<QPixmap> 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<WordPart> 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

View file

@ -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 &copytext, 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 &copytext, 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<short> &Word::getCharacterWidthCache(float scale) const
{
auto &data = this->getDataByScale(scale);
// lock not required because there is only one gui thread
// std::lock_guard<std::mutex> 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

View file

@ -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 <stdint.h>
#include <QRect>
#include <QString>
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 &copytext,
const QString &tooltip, const Link &getLink = Link());
explicit Word(const QString &_text, Flags getFlags, const MessageColor &textColor,
singletons::FontManager::Type font, const QString &copytext,
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<short> charWidthCache;
ScaleDependantData(float _scale)
: scale(_scale)
, size()
{
}
};
mutable std::list<ScaleDependantData> dataByScale;
inline ScaleDependantData &getDataByScale(float scale) const;
std::vector<short> &getCharacterWidthCache(float scale) const;
};
} // namespace messages
} // namespace chatterino

View file

@ -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

View file

@ -1,56 +0,0 @@
#pragma once
#include <QRect>
#include <QString>
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

View file

@ -19,11 +19,11 @@ ChannelManager::ChannelManager()
{
}
const std::vector<std::shared_ptr<Channel>> ChannelManager::getItems()
const std::vector<SharedChannel> ChannelManager::getItems()
{
QMutexLocker locker(&this->channelsMutex);
std::vector<std::shared_ptr<Channel>> items;
std::vector<SharedChannel> items;
for (auto &item : this->twitchChannels.values()) {
items.push_back(std::get<0>(item));
@ -32,7 +32,7 @@ const std::vector<std::shared_ptr<Channel>> ChannelManager::getItems()
return items;
}
std::shared_ptr<Channel> ChannelManager::addTwitchChannel(const QString &rawChannelName)
SharedChannel ChannelManager::addTwitchChannel(const QString &rawChannelName)
{
QString channelName = rawChannelName.toLower();
@ -63,7 +63,7 @@ std::shared_ptr<Channel> ChannelManager::addTwitchChannel(const QString &rawChan
return std::get<0>(it.value());
}
std::shared_ptr<Channel> 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<void(std::shared_ptr<Channel>)> func)
void ChannelManager::doOnAll(std::function<void(SharedChannel)> func)
{
for (const auto &channel : this->twitchChannels) {
func(std::get<0>(channel));

View file

@ -17,20 +17,20 @@ class ChannelManager
public:
static ChannelManager &getInstance();
const std::vector<std::shared_ptr<Channel>> getItems();
const std::vector<SharedChannel> getItems();
std::shared_ptr<Channel> addTwitchChannel(const QString &channel);
std::shared_ptr<Channel> 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<void(std::shared_ptr<Channel>)> func);
void doOnAll(std::function<void(SharedChannel)> func);
// Special channels
const std::shared_ptr<Channel> whispersChannel;
const std::shared_ptr<Channel> mentionsChannel;
const std::shared_ptr<Channel> emptyChannel;
const SharedChannel whispersChannel;
const SharedChannel mentionsChannel;
const SharedChannel emptyChannel;
private:
std::map<std::string, std::string> usernameToID;

View file

@ -88,7 +88,7 @@ QStringList CommandManager::getCommands()
return this->commandsStringList;
}
QString CommandManager::execCommand(const QString &text, std::shared_ptr<Channel> 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_ptr<Channel
if (commandName == "/uptime") {
QString messageText =
twitchChannel->isLive ? 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_ptr<Channel
messageText = "Ignored user \"" + words.at(1) + "\".";
}
messages::SharedMessage message(
messages::Message::createSystemMessage(messageText));
channel->addMessage(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_ptr<Channel
messageText = "Ignored user \"" + words.at(1) + "\".";
}
messages::SharedMessage message(
messages::Message::createSystemMessage(messageText));
channel->addMessage(message);
channel->addMessage(messages::Message::createSystemMessage(messageText));
return "";
}
}

View file

@ -62,14 +62,14 @@ static void FillInFFZEmoteData(const QJsonObject &urls, const QString &code,
assert(!url1x.isEmpty());
emoteData.image1x = new LazyLoadedImage(url1x, 1, code, code + "<br />Global FFZ Emote");
emoteData.image1x = new Image(url1x, 1, code, code + "<br />Global FFZ Emote");
if (!url2x.isEmpty()) {
emoteData.image2x = new LazyLoadedImage(url2x, 0.5, code, code + "<br />Global FFZ Emote");
emoteData.image2x = new Image(url2x, 0.5, code, code + "<br />Global FFZ Emote");
}
if (!url3x.isEmpty()) {
emoteData.image3x = new LazyLoadedImage(url3x, 0.25, code, code + "<br />Global FFZ Emote");
emoteData.image3x = new Image(url3x, 0.25, code, code + "<br />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 + "<br/>Channel BTTV Emote"));
new Image(link, 1, code, code + "<br/>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 + ":<br/>Emoji")));
// TODO(pajlada): The vectors in emojiFirstByte need to be sorted by
@ -375,7 +375,7 @@ void EmoteManager::parseEmojis(std::vector<std::tuple<util::EmoteData, QString>>
// 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 + "<br />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 + "<br />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 + "<br />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 + "<br/>Twitch Emote 1x");
newEmoteData.image2x = new LazyLoadedImage(GetTwitchEmoteLink(id, "2.0"), .5, emoteName,
newEmoteData.image2x = new Image(GetTwitchEmoteLink(id, "2.0"), .5, emoteName,
_emoteName + "<br/>Twitch Emote 2x");
newEmoteData.image3x = new LazyLoadedImage(GetTwitchEmoteLink(id, "3.0"), .25, emoteName,
newEmoteData.image3x = new Image(GetTwitchEmoteLink(id, "3.0"), .25, emoteName,
_emoteName + "<br/>Twitch Emote 3x");
return newEmoteData;

View file

@ -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<void()> &getGifUpdateSignal();
// Bit badge/emotes?
util::ConcurrentMap<QString, messages::LazyLoadedImage *> miscImageCache;
util::ConcurrentMap<QString, messages::Image *> miscImageCache;
private:
SettingManager &settingsManager;

View file

@ -132,6 +132,7 @@ private:
int generation = 0;
};
} // namespace chatterino
}
typedef singletons::FontManager::Type FontStyle;
} // namespace chatterino

View file

@ -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<SharedMessage> snapshot = c->getMessageSnapshot();
LimitedQueueSnapshot<MessagePtr> 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<Message> msg(Message::createSystemMessage(message->content()));
MessagePtr msg = Message::createSystemMessage(message->content());
if (broadcast) {
this->channelManager.doOnAll([msg](const auto &c) {

View file

@ -381,9 +381,9 @@ void IrcManager::removeIgnoredUser(QString const &username)
void IrcManager::onConnected()
{
std::shared_ptr<Message> msg(Message::createSystemMessage("connected to chat"));
MessagePtr msg = Message::createSystemMessage("connected to chat");
this->channelManager.doOnAll([msg](std::shared_ptr<Channel> 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<Message> msg(Message::createSystemMessage("disconnected from chat"));
MessagePtr msg = Message::createSystemMessage("disconnected from chat");
this->channelManager.doOnAll([msg](std::shared_ptr<Channel> channel) {
this->channelManager.doOnAll([msg](SharedChannel channel) {
assert(channel);
channel->addMessage(msg);
});

View file

@ -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<ChatterinoBadge>(
badgeVariantTooltip, new messages::LazyLoadedImage(badgeVariantImageURL));
badgeVariantTooltip, new messages::Image(badgeVariantImageURL));
QJsonArray badgeVariantUsers = badgeVariant.value("users").toArray();

View file

@ -1,6 +1,6 @@
#pragma once
#include "messages/lazyloadedimage.hpp"
#include "messages/image.hpp"
#include <map>
#include <memory>
@ -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<std::string, messages::LazyLoadedImage *> cheerBadges;
std::map<std::string, messages::Image *> 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<std::string, BadgeSet> 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

View file

@ -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<Word::Flags>(newMaskUint);
MessageElement::Flags newMask = static_cast<MessageElement::Flags>(newMaskUint);
if (newMask != this->wordTypeMask) {
this->wordTypeMask = newMask;

View file

@ -1,7 +1,7 @@
#pragma once
#include "messages/highlightphrase.hpp"
#include "messages/word.hpp"
#include "messages/messageelement.hpp"
#include "singletons/helper/chatterinosetting.hpp"
#include <pajlada/settings/setting.hpp>
@ -23,14 +23,14 @@ class SettingManager : public QObject
using QStringSetting = ChatterinoSetting<QString>;
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;
};

View file

@ -107,7 +107,7 @@ void TwitchChannel::refreshLiveStatus()
std::weak_ptr<Channel> weak = this->shared_from_this();
util::twitch::get2(url, QThread::currentThread(), [weak](rapidjson::Document &d) {
std::shared_ptr<Channel> shared = weak.lock();
SharedChannel shared = weak.lock();
if (!shared) {
return;
@ -168,7 +168,7 @@ void TwitchChannel::fetchRecentMessages()
std::weak_ptr<Channel> weak = this->shared_from_this();
util::twitch::get(genericURL.arg(roomID), QThread::currentThread(), [weak](QJsonObject obj) {
std::shared_ptr<Channel> 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::SharedMessage> messages;
std::vector<messages::MessagePtr> messages;
messages.resize(msgArray.size());
for (int i = 0; i < msgArray.size(); i++) {

View file

@ -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<TimestampElement>(dateTime.time());
} else {
this->appendTimestamp();
this->append<TimestampElement>();
}
this->parseMessageID();
this->parseRoomID();
this->appendModerationButtons();
// TIMESTAMP
this->append<TwitchModerationElement>();
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<EmoteElement>(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<TextElement>(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<EmoteElement>(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<TextElement>(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<int> 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<TextElement>(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<std::pair<long int, util::EmoteData>> &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<EmoteElement>(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<ImageElement>(*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<ImageElement>(*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<ImageElement>(*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<ImageElement>(*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<ImageElement>(*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<ImageElement>(*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<ImageElement>(*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<ImageElement>(*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<ImageElement>(*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<ImageElement>(*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<ImageElement>(*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<ImageElement>(*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<ImageElement>(*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<ImageElement>(*badge->image, MessageElement::BadgeChatterino)
->setTooltip(QString::fromStdString(badge->tooltip));
}
// bool
// sortTwitchEmotes(const std::pair<long int, LazyLoadedImage *> &a,
// const std::pair<long int, LazyLoadedImage *> &b)
// sortTwitchEmotes(const std::pair<long int, Image *> &a,
// const std::pair<long int, Image *> &b)
//{
// return a.first < b.first;
//}

View file

@ -38,11 +38,11 @@ public:
QString messageID;
QString userName;
messages::SharedMessage parse();
messages::MessagePtr parse();
// static bool sortTwitchEmotes(
// const std::pair<long int, messages::LazyLoadedImage *> &a,
// const std::pair<long int, messages::LazyLoadedImage *> &b);
// const std::pair<long int, messages::Image *> &a,
// const std::pair<long int, messages::Image *> &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<std::pair<long, util::EmoteData>> &vec);
bool tryAppendEmote(QString &emoteString);
bool appendEmote(const util::EmoteData &emoteData);
void parseTwitchBadges();
void parseChatterinoBadges();
void addChatterinoBadges();
};
} // namespace twitch

View file

@ -1,6 +1,6 @@
#pragma once
#include "messages/lazyloadedimage.hpp"
#include "messages/image.hpp"
#include "util/concurrentmap.hpp"
#include <cassert>
@ -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<QString, EmoteData> EmoteMap;

View file

@ -3,7 +3,7 @@
#include <QString>
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

34
src/util/property.hpp Normal file
View file

@ -0,0 +1,34 @@
#pragma once
#include "boost/noncopyable.hpp"
namespace chatterino {
namespace util {
template <typename T>
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;
};
}
}

View file

@ -18,7 +18,7 @@
namespace chatterino {
namespace widgets {
AccountPopupWidget::AccountPopupWidget(std::shared_ptr<Channel> _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> _channel)
void AccountPopupWidget::setChannel(SharedChannel _channel)
{
this->channel = _channel;
}

View file

@ -23,10 +23,10 @@ class AccountPopupWidget : public BaseWidget
{
Q_OBJECT
public:
AccountPopupWidget(std::shared_ptr<Channel> _channel);
AccountPopupWidget(SharedChannel _channel);
void setName(const QString &name);
void setChannel(std::shared_ptr<Channel> _channel);
void setChannel(SharedChannel _channel);
void updatePermissions();
@ -47,7 +47,7 @@ private:
enum class permissions { User, Mod, Owner };
permissions permission;
std::shared_ptr<Channel> channel;
SharedChannel channel;
QString userID;
QPixmap avatar;

View file

@ -34,7 +34,7 @@ EmotePopup::EmotePopup(singletons::ThemeManager &themeManager)
this->loadEmojis();
}
void EmotePopup::loadChannel(std::shared_ptr<Channel> _channel)
void EmotePopup::loadChannel(SharedChannel _channel)
{
TwitchChannel *channel = dynamic_cast<TwitchChannel *>(_channel.get());
@ -42,29 +42,25 @@ void EmotePopup::loadChannel(std::shared_ptr<Channel> _channel)
return;
}
std::shared_ptr<Channel> 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> _channel)
void EmotePopup::loadEmojis()
{
int preferredEmoteSize = singletons::SettingManager::getInstance().preferredEmoteQuality;
util::EmoteMap &emojis = singletons::EmoteManager::getInstance().getEmojis();
std::shared_ptr<Channel> 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);

View file

@ -12,7 +12,7 @@ class EmotePopup : public BaseWidget
public:
explicit EmotePopup(singletons::ThemeManager &);
void loadChannel(std::shared_ptr<Channel> channel);
void loadChannel(SharedChannel channel);
void loadEmojis();
private:

View file

@ -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<SharedMessageRef> ChannelView::getMessagesSnapshot()
messages::LimitedQueueSnapshot<MessageLayoutPtr> ChannelView::getMessagesSnapshot()
{
if (!this->paused) {
this->snapshot = this->messages.getSnapshot();
@ -369,7 +363,7 @@ messages::LimitedQueueSnapshot<SharedMessageRef> ChannelView::getMessagesSnapsho
return this->snapshot;
}
void ChannelView::setChannel(std::shared_ptr<Channel> newChannel)
void ChannelView::setChannel(SharedChannel newChannel)
{
if (this->channel) {
this->detachChannel();
@ -378,12 +372,12 @@ void ChannelView::setChannel(std::shared_ptr<Channel> 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<Channel> newChannel)
}
}
if (message->containsHighlightedPhrase()) {
if (!message->hasFlags(Message::DoNotTriggerNotification)) {
this->highlightedMessageReceived.invoke();
}
@ -402,12 +396,12 @@ void ChannelView::setChannel(std::shared_ptr<Channel> newChannel)
});
this->messageAddedAtStartConnection =
newChannel->messagesAddedAtStart.connect([this](std::vector<SharedMessage> &messages) {
std::vector<SharedMessageRef> messageRefs;
newChannel->messagesAddedAtStart.connect([this](std::vector<MessagePtr> &messages) {
std::vector<MessageLayoutPtr> 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<Channel> newChannel)
std::vector<ScrollbarHighlight> 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<Channel> 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<Channel> 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<Channel> 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<QPixmap> 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<QPixmap>(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<messages::MessageRef> item : this->messagesOnScreen) {
item->buffer.reset();
for (const std::shared_ptr<messages::MessageLayout> &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<messages::MessageRef> messageRef = messagesSnapshot[i];
std::shared_ptr<messages::MessageLayout> 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<messages::MessageRef> message;
std::shared_ptr<messages::MessageLayout> 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<messages::MessageRef> message;
std::shared_ptr<messages::MessageLayout> 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<messages::MessageRef> message;
std::shared_ptr<messages::MessageLayout> 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<messages::MessageRef> &_message,
bool ChannelView::tryGetMessageAt(QPoint p, std::shared_ptr<messages::MessageLayout> &_message,
QPoint &relativePos, int &index)
{
auto messagesSnapshot = this->getMessagesSnapshot();

View file

@ -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> channel);
messages::LimitedQueueSnapshot<messages::SharedMessageRef> getMessagesSnapshot();
void setChannel(SharedChannel channel);
messages::LimitedQueueSnapshot<messages::MessageLayoutPtr> 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<messages::MessageRef> &message,
bool tryGetMessageAt(QPoint p, std::shared_ptr<messages::MessageLayout> &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<messages::SharedMessageRef> snapshot;
messages::LimitedQueueSnapshot<messages::MessageLayoutPtr> 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> channel;
std::vector<GifEmoteData> gifEmotes;
SharedChannel channel;
Scrollbar scrollBar;
RippleEffectLabel *goToBottom;
@ -112,7 +101,7 @@ private:
messages::Selection selection;
bool selecting = false;
messages::LimitedQueue<messages::SharedMessageRef> messages;
messages::LimitedQueue<messages::MessageLayoutPtr> messages;
boost::signals2::connection messageAppendedConnection;
boost::signals2::connection messageAddedAtStartConnection;
@ -123,7 +112,7 @@ private:
std::vector<pajlada::Signals::ScopedConnection> managedConnections;
std::unordered_set<std::shared_ptr<messages::MessageRef>> messagesOnScreen;
std::unordered_set<std::shared_ptr<messages::MessageLayout>> messagesOnScreen;
private slots:
void wordTypeMaskChanged()

View file

@ -59,7 +59,7 @@ void SearchPopup::initLayout()
}
}
void SearchPopup::setChannel(std::shared_ptr<Channel> 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> 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);
}

View file

@ -20,7 +20,7 @@ public:
void setChannel(std::shared_ptr<Channel> channel);
private:
messages::LimitedQueueSnapshot<messages::SharedMessage> snapshot;
messages::LimitedQueueSnapshot<messages::MessagePtr> snapshot;
QLineEdit *searchInput;
ChannelView *channelView;

View file

@ -48,9 +48,8 @@ SplitInput::SplitInput(Split *_chatWidget)
this->textLengthLabel.setAlignment(Qt::AlignRight);
this->emotesLabel.getLabel().setTextFormat(Qt::RichText);
this->emotesLabel.getLabel().setText(
"<img src=':/images/emote.svg' width='12' height='12' "
"/>");
this->emotesLabel.getLabel().setText("<img src=':/images/emote.svg' width='12' height='12' "
"/>");
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) {

View file

@ -48,7 +48,7 @@ private:
QLabel textLengthLabel;
RippleEffectLabel emotesLabel;
QStringList prevMsg;
unsigned int prevIndex = 0;
int prevIndex = 0;
virtual void refreshTheme() override;
private slots:

View file

@ -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;
}

View file

@ -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
{

View file

@ -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));

View file

@ -121,17 +121,17 @@ const std::string &Split::getUUID() const
return this->uuid;
}
std::shared_ptr<Channel> Split::getChannel() const
SharedChannel Split::getChannel() const
{
return this->channel;
}
std::shared_ptr<Channel> &Split::getChannelRef()
SharedChannel &Split::getChannelRef()
{
return this->channel;
}
void Split::setChannel(std::shared_ptr<Channel> _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)

View file

@ -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<Channel> getChannel() const;
std::shared_ptr<Channel> &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> channel;
SharedChannel channel;
QVBoxLayout vbox;
SplitHeader header;
@ -84,7 +84,7 @@ private:
boost::signals2::connection channelIDChangedConnection;
void setChannel(std::shared_ptr<Channel> newChannel);
void setChannel(SharedChannel newChannel);
void doOpenAccountPopupWidget(AccountPopupWidget *widget, QString user);
void channelNameUpdated(const std::string &newChannelName);