mirror-chatterino2/src/messages/messageref.cpp

402 lines
11 KiB
C++
Raw Normal View History

2017-09-24 18:43:24 +02:00
#include "messages/messageref.hpp"
2017-06-11 09:31:45 +02:00
#include "emotemanager.hpp"
#include "settingsmanager.hpp"
#include <QDebug>
#define MARGIN_LEFT 8
#define MARGIN_RIGHT 8
2017-09-12 19:06:16 +02:00
#define MARGIN_TOP 4
#define MARGIN_BOTTOM 4
2017-04-14 17:52:22 +02:00
using namespace chatterino::messages;
2017-04-12 17:46:44 +02:00
2017-04-14 17:52:22 +02:00
namespace chatterino {
namespace messages {
2017-09-21 12:15:01 +02:00
MessageRef::MessageRef(SharedMessage _message)
: message(_message)
, wordParts()
{
}
2017-04-12 17:46:44 +02:00
Message *MessageRef::getMessage()
{
2017-09-21 12:15:01 +02:00
return this->message.get();
2017-04-12 17:46:44 +02:00
}
int MessageRef::getHeight() const
{
2017-09-21 12:15:01 +02:00
return this->height;
2017-04-12 17:46:44 +02:00
}
// return true if redraw is required
2017-12-17 00:06:24 +01:00
bool MessageRef::layout(int width, float dpiMultiplyer)
{
2017-12-17 02:18:13 +01:00
auto &emoteManager = EmoteManager::getInstance();
bool layoutRequired = false;
2017-04-12 17:46:44 +02:00
// check if width changed
2017-12-17 00:06:24 +01:00
bool widthChanged = width != this->currentLayoutWidth;
layoutRequired |= widthChanged;
this->currentLayoutWidth = width;
2017-12-17 00:06:24 +01:00
// check if emotes changed
2017-12-17 02:18:13 +01:00
bool imagesChanged = this->emoteGeneration != emoteManager.getGeneration();
layoutRequired |= imagesChanged;
2017-12-17 02:18:13 +01:00
this->emoteGeneration = emoteManager.getGeneration();
2017-12-17 00:06:24 +01:00
// check if text changed
2017-12-17 00:06:24 +01:00
bool textChanged = this->fontGeneration != FontManager::getInstance().getGeneration();
layoutRequired |= textChanged;
this->fontGeneration = FontManager::getInstance().getGeneration();
2017-12-17 00:06:24 +01:00
// check if work mask changed
2017-12-17 00:06:24 +01:00
bool wordMaskChanged =
this->currentWordTypes != SettingsManager::getInstance().getWordTypeMask();
layoutRequired |= wordMaskChanged;
this->currentWordTypes = SettingsManager::getInstance().getWordTypeMask();
2017-12-17 00:06:24 +01:00
// check if dpi changed
bool dpiChanged = this->dpiMultiplyer != dpiMultiplyer;
layoutRequired |= dpiChanged;
this->dpiMultiplyer = dpiMultiplyer;
imagesChanged |= dpiChanged;
textChanged |= dpiChanged;
// update word sizes if needed
if (imagesChanged) {
this->updateImageSizes();
}
if (textChanged) {
this->updateTextSizes();
}
if (widthChanged || wordMaskChanged) {
this->buffer = nullptr;
}
2017-04-12 17:46:44 +02:00
// return if no layout is required
if (!layoutRequired) {
2017-04-12 17:46:44 +02:00
return false;
}
this->actuallyLayout(width);
2017-04-12 17:46:44 +02:00
return true;
}
2017-04-12 17:46:44 +02:00
void MessageRef::actuallyLayout(int width)
{
auto &settings = SettingsManager::getInstance();
2017-02-17 23:51:35 +01:00
const int spaceWidth = 4;
const int right = width - MARGIN_RIGHT;
// clear word parts
this->wordParts.clear();
2017-04-12 17:46:44 +02:00
// layout
int x = MARGIN_LEFT;
int y = MARGIN_TOP;
2017-02-17 23:51:35 +01:00
int lineNumber = 0;
int lineStart = 0;
int lineHeight = 0;
bool first = true;
uint32_t flags = settings.getWordTypeMask();
// loop throught all the words and add them when a line is full
2017-09-21 12:15:01 +02:00
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.getType() & 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() + MARGIN_LEFT > right) {
// align and end the current line
alignWordParts(lineStart, lineHeight, width);
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);
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,
2017-09-21 12:15:01 +02:00
word.getHeight(), lineNumber, mid, mid,
false, currentPartStart));
2017-09-21 02:20:02 +02:00
y += word.getFontMetrics().height();
currentPartStart = i - 1;
currentLineWidth = 0;
2017-02-17 23:51:35 +01:00
lineNumber++;
}
}
QString mid(word.getText().mid(currentPartStart));
currentLineWidth = word.getFontMetrics().width(mid);
this->wordParts.push_back(WordPart(word, MARGIN_LEFT, y - word.getHeight(),
currentLineWidth, word.getHeight(), lineNumber, mid,
mid, true, currentPartStart));
x = currentLineWidth + MARGIN_LEFT + spaceWidth;
lineHeight = word.getHeight();
2017-09-21 12:15:01 +02:00
lineStart = this->wordParts.size() - 1;
first = false;
}
// fits in the current line
else if (first || x + word.getWidth() + xOffset <= right) {
2017-09-21 12:15:01 +02:00
this->wordParts.push_back(
2017-04-12 17:46:44 +02:00
WordPart(word, x, y - word.getHeight(), lineNumber, word.getCopyText()));
x += word.getWidth() + xOffset;
x += spaceWidth;
lineHeight = std::max(word.getHeight(), lineHeight);
first = false;
}
// doesn't fit in the line
else {
// align and end the current line
alignWordParts(lineStart, lineHeight, width);
y += lineHeight;
2017-09-12 19:06:16 +02:00
lineNumber++;
2017-09-21 12:15:01 +02:00
this->wordParts.push_back(
2017-04-12 17:46:44 +02:00
WordPart(word, MARGIN_LEFT, y - word.getHeight(), lineNumber, word.getCopyText()));
2017-09-21 12:15:01 +02:00
lineStart = this->wordParts.size() - 1;
lineHeight = word.getHeight();
x = word.getWidth() + MARGIN_LEFT;
x += spaceWidth;
}
}
// align and end the current line
alignWordParts(lineStart, lineHeight, width);
// update height
int oldHeight = this->height;
this->height = y + lineHeight + MARGIN_BOTTOM;
// invalidate buffer if height changed
if (oldHeight != this->height) {
this->buffer = nullptr;
}
updateBuffer = true;
}
2017-09-12 19:06:16 +02:00
void MessageRef::updateTextSizes()
{
for (auto &word : this->message->getWords()) {
if (!word.isText())
continue;
QFontMetrics &metrics = word.getFontMetrics();
2017-12-17 00:06:24 +01:00
word.setSize((int)(metrics.width(word.getText()) * this->dpiMultiplyer),
(int)(metrics.height() * this->dpiMultiplyer));
}
}
void MessageRef::updateImageSizes()
{
const int mediumTextLineHeight =
FontManager::getInstance().getFontMetrics(FontManager::Medium).height();
2017-12-17 00:06:24 +01:00
const qreal emoteScale = SettingsManager::getInstance().emoteScale.get() * this->dpiMultiplyer;
const bool scaleEmotesByLineHeight = SettingsManager::getInstance().scaleEmotesByLineHeight;
for (auto &word : this->message->getWords()) {
if (!word.isImage())
continue;
auto &image = word.getImage();
qreal w = image.getWidth();
qreal h = image.getHeight();
if (scaleEmotesByLineHeight) {
word.setSize(w * mediumTextLineHeight / h * emoteScale,
mediumTextLineHeight * emoteScale);
} else {
word.setSize(w * image.getScale() * emoteScale, h * image.getScale() * emoteScale);
}
}
}
2017-04-12 17:46:44 +02:00
const std::vector<WordPart> &MessageRef::getWordParts() const
{
2017-09-21 12:15:01 +02:00
return this->wordParts;
2017-04-12 17:46:44 +02:00
}
void MessageRef::alignWordParts(int lineStart, int lineHeight, int width)
{
int xOffset = 0;
2017-09-21 12:15:01 +02:00
if (this->message->centered && this->wordParts.size() > 0) {
xOffset = (width - this->wordParts.at(this->wordParts.size() - 1).getRight()) / 2;
}
2017-09-21 12:15:01 +02:00
for (size_t i = lineStart; i < this->wordParts.size(); i++) {
WordPart &wordPart2 = this->wordParts.at(i);
wordPart2.setPosition(wordPart2.getX() + xOffset, wordPart2.getY() + lineHeight);
}
}
2017-02-17 23:51:35 +01:00
2017-09-12 19:06:16 +02:00
const Word *MessageRef::tryGetWordPart(QPoint point)
2017-02-17 23:51:35 +01:00
{
2017-04-12 17:46:44 +02:00
// go through all words and return the first one that contains the point.
2017-09-21 12:15:01 +02:00
for (WordPart &wordPart : this->wordParts) {
2017-02-17 23:51:35 +01:00
if (wordPart.getRect().contains(point)) {
2017-09-12 19:06:16 +02:00
return &wordPart.getWord();
2017-02-17 23:51:35 +01:00
}
}
2017-09-12 19:06:16 +02:00
return nullptr;
2017-02-17 23:51:35 +01:00
}
// 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;
}
2017-04-12 17:46:44 +02:00
int MessageRef::getSelectionIndex(QPoint position)
2017-02-17 23:51:35 +01:00
{
2017-09-21 12:15:01 +02:00
if (this->wordParts.size() == 0) {
2017-02-17 23:51:35 +01:00
return 0;
}
// find out in which line the cursor is
int lineNumber = 0, lineStart = 0, lineEnd = 0;
2017-09-21 12:15:01 +02:00
for (size_t i = 0; i < this->wordParts.size(); i++) {
WordPart &part = this->wordParts[i];
2017-02-17 23:51:35 +01:00
2017-04-24 23:00:26 +02:00
if (part.getLineNumber() != 0 && position.y() < part.getY()) {
2017-02-17 23:51:35 +01:00
break;
}
if (part.getLineNumber() != lineNumber) {
2017-09-12 19:06:16 +02:00
lineStart = i;
2017-02-17 23:51:35 +01:00
lineNumber = part.getLineNumber();
}
2017-09-12 19:06:16 +02:00
lineEnd = i + 1;
2017-02-17 23:51:35 +01:00
}
// count up to the cursor
int index = 0;
for (int i = 0; i < lineStart; i++) {
2017-09-21 12:15:01 +02:00
WordPart &part = this->wordParts[i];
2017-02-17 23:51:35 +01:00
index += part.getWord().isImage() ? 2 : part.getText().length() + 1;
}
for (int i = lineStart; i < lineEnd; i++) {
2017-09-21 12:15:01 +02:00
WordPart &part = this->wordParts[i];
2017-02-17 23:51:35 +01:00
// curser is left of the word part
if (position.x() < part.getX()) {
break;
}
// cursor is right of the word part
2017-04-24 23:00:26 +02:00
if (position.x() > part.getX() + part.getWidth()) {
2017-09-12 19:06:16 +02:00
index += part.getCharacterLength();
2017-02-17 23:51:35 +01:00
continue;
}
// cursor is over the word part
if (part.getWord().isImage()) {
2017-09-12 19:06:16 +02:00
if (position.x() - part.getX() > part.getWidth() / 2) {
index++;
}
2017-02-17 23:51:35 +01:00
} else {
2017-09-12 19:06:16 +02:00
// TODO: use word.getCharacterWidthCache();
auto text = part.getText();
2017-02-17 23:51:35 +01:00
int x = part.getX();
for (int j = 0; j < text.length(); j++) {
if (x > position.x()) {
break;
}
index++;
2017-04-12 17:46:44 +02:00
x = part.getX() + part.getWord().getFontMetrics().width(text, j + 1);
2017-02-17 23:51:35 +01:00
}
}
break;
}
return index;
}
2017-05-27 16:16:39 +02:00
} // namespace messages
} // namespace chatterino