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
@@ -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
@@ -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;
+345
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
+83
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
@@ -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
}
}
@@ -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
}
}
@@ -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
@@ -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
+3 -2
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)
+55 -127
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;
+45 -51
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
+12 -20
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)
+18 -11
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
+2 -2
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;
+249
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
+224
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
-450
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
-70
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
-207
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
-163
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
-123
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
-56
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