2018-06-26 14:09:39 +02:00
|
|
|
#include "messages/layouts/MessageLayout.hpp"
|
2018-04-27 22:11:19 +02:00
|
|
|
|
2018-06-26 14:09:39 +02:00
|
|
|
#include "Application.hpp"
|
2018-06-26 17:20:03 +02:00
|
|
|
#include "debug/Benchmark.hpp"
|
2018-06-28 19:46:45 +02:00
|
|
|
#include "singletons/Emotes.hpp"
|
|
|
|
#include "singletons/Settings.hpp"
|
2018-06-26 14:09:39 +02:00
|
|
|
#include "singletons/WindowManager.hpp"
|
2018-01-11 20:16:25 +01:00
|
|
|
|
|
|
|
#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 {
|
|
|
|
|
2018-05-25 13:02:14 +02:00
|
|
|
MessageLayout::MessageLayout(MessagePtr message)
|
2018-05-31 14:15:04 +02:00
|
|
|
: message_(message)
|
|
|
|
, buffer_(nullptr)
|
2018-01-11 20:16:25 +01:00
|
|
|
{
|
2018-06-26 17:06:17 +02:00
|
|
|
DebugCount::increase("message layout");
|
2018-04-06 16:37:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
MessageLayout::~MessageLayout()
|
|
|
|
{
|
2018-06-26 17:06:17 +02:00
|
|
|
DebugCount::decrease("message layout");
|
2018-01-11 20:16:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Message *MessageLayout::getMessage()
|
|
|
|
{
|
2018-05-31 14:15:04 +02:00
|
|
|
return this->message_.get();
|
2018-01-11 20:16:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Height
|
|
|
|
int MessageLayout::getHeight() const
|
|
|
|
{
|
2018-05-31 14:15:04 +02:00
|
|
|
return container_.getHeight();
|
2018-01-11 20:16:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Layout
|
|
|
|
// return true if redraw is required
|
2018-01-17 16:52:51 +01:00
|
|
|
bool MessageLayout::layout(int width, float scale, MessageElement::Flags flags)
|
2018-01-11 20:16:25 +01:00
|
|
|
{
|
2018-05-26 16:31:43 +02:00
|
|
|
// BenchmarkGuard benchmark("MessageLayout::layout()");
|
2018-05-24 22:58:07 +02:00
|
|
|
|
2018-04-27 22:11:19 +02:00
|
|
|
auto app = getApp();
|
2018-01-11 20:16:25 +01:00
|
|
|
|
|
|
|
bool layoutRequired = false;
|
|
|
|
|
|
|
|
// check if width changed
|
2018-05-31 14:15:04 +02:00
|
|
|
bool widthChanged = width != this->currentLayoutWidth_;
|
2018-01-11 20:16:25 +01:00
|
|
|
layoutRequired |= widthChanged;
|
2018-05-31 14:15:04 +02:00
|
|
|
this->currentLayoutWidth_ = width;
|
2018-01-11 20:16:25 +01:00
|
|
|
|
2018-06-04 16:10:54 +02:00
|
|
|
// check if layout state changed
|
|
|
|
if (this->layoutState_ != app->windows->getGeneration()) {
|
|
|
|
layoutRequired = true;
|
|
|
|
this->flags |= RequiresBufferUpdate;
|
|
|
|
this->layoutState_ = app->windows->getGeneration();
|
|
|
|
}
|
2018-01-11 20:16:25 +01:00
|
|
|
|
|
|
|
// check if work mask changed
|
2018-06-04 16:10:54 +02:00
|
|
|
layoutRequired |= this->currentWordFlags_ != flags;
|
2018-05-31 14:15:04 +02:00
|
|
|
this->currentWordFlags_ = flags; // app->settings->getWordTypeMask();
|
2018-01-11 20:16:25 +01:00
|
|
|
|
2018-05-25 12:45:18 +02:00
|
|
|
// check if layout was requested manually
|
|
|
|
layoutRequired |= bool(this->flags & RequiresLayout);
|
2018-06-04 16:10:54 +02:00
|
|
|
this->flags &= decltype(RequiresLayout)(~RequiresLayout);
|
2018-05-25 12:45:18 +02:00
|
|
|
|
2018-01-11 20:16:25 +01:00
|
|
|
// check if dpi changed
|
2018-06-04 16:10:54 +02:00
|
|
|
layoutRequired |= this->scale_ != scale;
|
2018-05-31 14:15:04 +02:00
|
|
|
this->scale_ = scale;
|
2018-05-24 23:12:50 +02:00
|
|
|
|
2018-01-11 20:16:25 +01:00
|
|
|
if (!layoutRequired) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-05-31 14:15:04 +02:00
|
|
|
int oldHeight = this->container_.getHeight();
|
2018-01-17 16:52:51 +01:00
|
|
|
this->actuallyLayout(width, flags);
|
2018-06-04 16:10:54 +02:00
|
|
|
if (widthChanged || this->container_.getHeight() != oldHeight) {
|
2018-05-31 14:15:04 +02:00
|
|
|
this->deleteBuffer();
|
|
|
|
}
|
2018-01-11 20:16:25 +01:00
|
|
|
this->invalidateBuffer();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-05-24 11:35:50 +02:00
|
|
|
void MessageLayout::actuallyLayout(int width, MessageElement::Flags _flags)
|
2018-01-11 20:16:25 +01:00
|
|
|
{
|
2018-05-31 14:15:04 +02:00
|
|
|
auto messageFlags = this->message_->flags.value;
|
2018-05-24 11:35:50 +02:00
|
|
|
|
|
|
|
if (this->flags & MessageLayout::Expanded ||
|
|
|
|
(_flags & MessageElement::ModeratorTools &&
|
2018-05-31 14:15:04 +02:00
|
|
|
!(this->message_->flags & Message::MessageFlags::Disabled))) {
|
2018-05-25 12:45:18 +02:00
|
|
|
messageFlags = Message::MessageFlags(messageFlags & ~Message::MessageFlags::Collapsed);
|
2018-05-24 11:35:50 +02:00
|
|
|
}
|
|
|
|
|
2018-05-31 14:15:04 +02:00
|
|
|
this->container_.begin(width, this->scale_, messageFlags);
|
2018-01-11 20:16:25 +01:00
|
|
|
|
2018-05-31 14:15:04 +02:00
|
|
|
for (const std::unique_ptr<MessageElement> &element : this->message_->getElements()) {
|
|
|
|
element->addToContainer(this->container_, _flags);
|
2018-01-11 20:16:25 +01:00
|
|
|
}
|
|
|
|
|
2018-05-31 14:15:04 +02:00
|
|
|
if (this->height_ != this->container_.getHeight()) {
|
2018-01-11 20:16:25 +01:00
|
|
|
this->deleteBuffer();
|
|
|
|
}
|
|
|
|
|
2018-05-31 14:15:04 +02:00
|
|
|
this->container_.end();
|
|
|
|
this->height_ = this->container_.getHeight();
|
2018-05-24 11:35:50 +02:00
|
|
|
|
|
|
|
// collapsed state
|
|
|
|
this->flags &= ~Flags::Collapsed;
|
2018-05-31 14:15:04 +02:00
|
|
|
if (this->container_.isCollapsed()) {
|
2018-05-24 11:35:50 +02:00
|
|
|
this->flags |= Flags::Collapsed;
|
|
|
|
}
|
2018-01-11 20:16:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Painting
|
2018-05-31 12:59:43 +02:00
|
|
|
void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
|
|
|
|
Selection &selection, bool isLastReadMessage, bool isWindowFocused)
|
2018-01-11 20:16:25 +01:00
|
|
|
{
|
2018-04-27 22:11:19 +02:00
|
|
|
auto app = getApp();
|
2018-05-31 14:15:04 +02:00
|
|
|
QPixmap *pixmap = this->buffer_.get();
|
2018-01-11 20:16:25 +01:00
|
|
|
|
|
|
|
// create new buffer if required
|
|
|
|
if (!pixmap) {
|
|
|
|
#ifdef Q_OS_MACOS
|
2018-05-31 12:59:43 +02:00
|
|
|
pixmap = new QPixmap(int(width * painter.device()->devicePixelRatioF()),
|
2018-05-31 14:15:04 +02:00
|
|
|
int(container_.getHeight() * painter.device()->devicePixelRatioF()));
|
2018-01-11 20:16:25 +01:00
|
|
|
pixmap->setDevicePixelRatio(painter.device()->devicePixelRatioF());
|
|
|
|
#else
|
2018-05-31 14:15:04 +02:00
|
|
|
pixmap = new QPixmap(width, std::max(16, this->container_.getHeight()));
|
2018-01-11 20:16:25 +01:00
|
|
|
#endif
|
|
|
|
|
2018-05-31 14:15:04 +02:00
|
|
|
this->buffer_ = std::shared_ptr<QPixmap>(pixmap);
|
|
|
|
this->bufferValid_ = false;
|
2018-06-26 17:06:17 +02:00
|
|
|
DebugCount::increase("message drawing buffers");
|
2018-01-11 20:16:25 +01:00
|
|
|
}
|
|
|
|
|
2018-05-31 14:15:04 +02:00
|
|
|
if (!this->bufferValid_ || !selection.isEmpty()) {
|
2018-01-11 20:16:25 +01:00
|
|
|
this->updateBuffer(pixmap, messageIndex, selection);
|
|
|
|
}
|
|
|
|
|
|
|
|
// draw on buffer
|
2018-01-23 22:51:15 +01:00
|
|
|
painter.drawPixmap(0, y, *pixmap);
|
|
|
|
// painter.drawPixmap(0, y, this->container.width, this->container.getHeight(), *pixmap);
|
2018-01-11 20:16:25 +01:00
|
|
|
|
2018-04-01 11:43:26 +02:00
|
|
|
// draw gif emotes
|
2018-05-31 14:15:04 +02:00
|
|
|
this->container_.paintAnimatedElements(painter, y);
|
2018-04-01 11:43:26 +02:00
|
|
|
|
2018-01-13 02:03:53 +01:00
|
|
|
// draw disabled
|
2018-05-31 14:15:04 +02:00
|
|
|
if (this->message_->flags.HasFlag(Message::Disabled)) {
|
2018-04-28 15:20:18 +02:00
|
|
|
painter.fillRect(0, y, pixmap->width(), pixmap->height(), app->themes->messages.disabled);
|
2018-01-13 02:03:53 +01:00
|
|
|
}
|
|
|
|
|
2018-04-10 03:29:00 +02:00
|
|
|
// draw selection
|
|
|
|
if (!selection.isEmpty()) {
|
2018-05-31 14:15:04 +02:00
|
|
|
this->container_.paintSelection(painter, messageIndex, selection, y);
|
2018-04-10 03:29:00 +02:00
|
|
|
}
|
|
|
|
|
2018-05-06 14:38:23 +02:00
|
|
|
// draw message seperation line
|
2018-06-24 18:32:00 +02:00
|
|
|
if (app->settings->separateMessages.getValue()) {
|
2018-05-31 14:15:04 +02:00
|
|
|
painter.fillRect(0, y, this->container_.getWidth(), 1,
|
2018-05-06 14:38:23 +02:00
|
|
|
app->themes->splits.messageSeperator);
|
|
|
|
}
|
|
|
|
|
2018-01-23 22:48:33 +01:00
|
|
|
// draw last read message line
|
|
|
|
if (isLastReadMessage) {
|
2018-04-28 15:20:18 +02:00
|
|
|
QColor color = isWindowFocused ? app->themes->tabs.selected.backgrounds.regular.color()
|
|
|
|
: app->themes->tabs.selected.backgrounds.unfocused.color();
|
2018-01-23 22:48:33 +01:00
|
|
|
|
2018-07-03 13:44:59 +02:00
|
|
|
QBrush brush(color,
|
|
|
|
static_cast<Qt::BrushStyle>(app->settings->lastMessagePattern.getValue()));
|
2018-01-23 22:48:33 +01:00
|
|
|
|
2018-06-05 00:14:20 +02:00
|
|
|
painter.fillRect(0, y + this->container_.getHeight() - 1, pixmap->width(), 1, brush);
|
2018-01-23 22:48:33 +01:00
|
|
|
}
|
|
|
|
|
2018-05-31 14:15:04 +02:00
|
|
|
this->bufferValid_ = true;
|
2018-01-11 20:16:25 +01:00
|
|
|
}
|
|
|
|
|
2018-05-25 12:45:18 +02:00
|
|
|
void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/, Selection & /*selection*/)
|
2018-01-11 20:16:25 +01:00
|
|
|
{
|
2018-04-27 22:11:19 +02:00
|
|
|
auto app = getApp();
|
2018-01-11 20:16:25 +01:00
|
|
|
|
|
|
|
QPainter painter(buffer);
|
|
|
|
|
|
|
|
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
|
|
|
|
|
|
|
// draw background
|
2018-05-06 14:38:23 +02:00
|
|
|
QColor backgroundColor;
|
2018-05-31 14:15:04 +02:00
|
|
|
if (this->message_->flags & Message::Highlighted) {
|
2018-05-06 14:38:23 +02:00
|
|
|
backgroundColor = app->themes->messages.backgrounds.highlighted;
|
2018-06-04 12:23:23 +02:00
|
|
|
} else if (this->message_->flags & Message::Subscription) {
|
|
|
|
backgroundColor = app->themes->messages.backgrounds.subscription;
|
2018-05-06 14:38:23 +02:00
|
|
|
} else if (app->settings->alternateMessageBackground.getValue() &&
|
|
|
|
this->flags & MessageLayout::AlternateBackground) {
|
|
|
|
backgroundColor = app->themes->messages.backgrounds.alternate;
|
|
|
|
} else {
|
|
|
|
backgroundColor = app->themes->messages.backgrounds.regular;
|
|
|
|
}
|
|
|
|
painter.fillRect(buffer->rect(), backgroundColor);
|
2018-01-11 20:16:25 +01:00
|
|
|
|
|
|
|
// draw message
|
2018-05-31 14:15:04 +02:00
|
|
|
this->container_.paintElements(painter);
|
2018-01-11 20:16:25 +01:00
|
|
|
|
2018-04-25 14:49:30 +02:00
|
|
|
#ifdef FOURTF
|
2018-01-11 20:16:25 +01:00
|
|
|
// 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);
|
|
|
|
|
2018-04-10 02:42:41 +02:00
|
|
|
painter.drawText(QRectF(1, 1, this->container.getWidth() - 3, 1000),
|
2018-01-11 20:16:25 +01:00
|
|
|
QString::number(++this->bufferUpdatedCount), option);
|
2018-01-11 20:26:32 +01:00
|
|
|
#endif
|
2018-01-11 20:16:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void MessageLayout::invalidateBuffer()
|
|
|
|
{
|
2018-05-31 14:15:04 +02:00
|
|
|
this->bufferValid_ = false;
|
2018-01-11 20:16:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void MessageLayout::deleteBuffer()
|
|
|
|
{
|
2018-05-31 14:15:04 +02:00
|
|
|
if (this->buffer_ != nullptr) {
|
2018-06-26 17:06:17 +02:00
|
|
|
DebugCount::decrease("message drawing buffers");
|
2018-04-06 17:46:12 +02:00
|
|
|
|
2018-05-31 14:15:04 +02:00
|
|
|
this->buffer_ = nullptr;
|
2018-04-06 17:46:12 +02:00
|
|
|
}
|
2018-01-11 20:16:25 +01:00
|
|
|
}
|
|
|
|
|
2018-04-18 09:12:29 +02:00
|
|
|
void MessageLayout::deleteCache()
|
|
|
|
{
|
|
|
|
this->deleteBuffer();
|
|
|
|
|
|
|
|
#ifdef XD
|
2018-05-31 14:15:04 +02:00
|
|
|
this->container_.clear();
|
2018-04-18 09:12:29 +02:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2018-01-11 20:16:25 +01:00
|
|
|
// 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.
|
2018-05-31 14:15:04 +02:00
|
|
|
return this->container_.getElementAt(point);
|
2018-01-11 20:16:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
int MessageLayout::getLastCharacterIndex() const
|
|
|
|
{
|
2018-05-31 14:15:04 +02:00
|
|
|
return this->container_.getLastCharacterIndex();
|
2018-01-11 20:16:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
int MessageLayout::getSelectionIndex(QPoint position)
|
|
|
|
{
|
2018-05-31 14:15:04 +02:00
|
|
|
return this->container_.getSelectionIndex(position);
|
2018-01-11 20:16:25 +01:00
|
|
|
}
|
2018-01-16 02:39:31 +01:00
|
|
|
|
|
|
|
void MessageLayout::addSelectionText(QString &str, int from, int to)
|
|
|
|
{
|
2018-05-31 14:15:04 +02:00
|
|
|
this->container_.addSelectionText(str, from, to);
|
2018-01-16 02:39:31 +01:00
|
|
|
}
|
2018-04-01 16:43:30 +02:00
|
|
|
|
2018-01-11 20:16:25 +01:00
|
|
|
} // namespace chatterino
|