2018-01-11 20:16:25 +01:00
|
|
|
#include "messagelayoutcontainer.hpp"
|
|
|
|
|
|
|
|
#include "messagelayoutelement.hpp"
|
|
|
|
#include "messages/selection.hpp"
|
|
|
|
#include "singletons/settingsmanager.hpp"
|
|
|
|
|
2018-01-15 04:08:48 +01:00
|
|
|
#include <QDebug>
|
2018-01-11 20:16:25 +01:00
|
|
|
#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();
|
2018-01-16 00:26:04 +01:00
|
|
|
this->lines.clear();
|
2018-01-11 20:16:25 +01:00
|
|
|
|
|
|
|
this->height = 0;
|
|
|
|
this->line = 0;
|
|
|
|
this->currentX = 0;
|
|
|
|
this->currentY = 0;
|
|
|
|
this->lineStart = 0;
|
|
|
|
this->lineHeight = 0;
|
2018-01-16 00:26:04 +01:00
|
|
|
this->charIndex = 0;
|
2018-01-11 20:16:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2018-01-16 00:26:04 +01:00
|
|
|
if (this->lines.size() != 0) {
|
|
|
|
this->lines.back().endIndex = this->lineStart;
|
|
|
|
this->lines.back().endCharIndex = this->charIndex;
|
|
|
|
}
|
|
|
|
this->lines.push_back({(int)lineStart, 0, this->charIndex, 0,
|
|
|
|
QRect(-100000, this->currentY, 200000, lineHeight)});
|
|
|
|
|
|
|
|
for (int i = this->lineStart; i < this->elements.size(); i++) {
|
|
|
|
this->charIndex += this->elements[i]->getSelectionIndexCount();
|
|
|
|
}
|
2018-01-15 04:08:48 +01:00
|
|
|
|
2018-01-11 20:16:25 +01:00
|
|
|
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();
|
|
|
|
}
|
2018-01-15 04:08:48 +01:00
|
|
|
|
|
|
|
if (this->lines.size() != 0) {
|
2018-01-16 00:26:04 +01:00
|
|
|
this->lines[0].rect.setTop(-100000);
|
|
|
|
this->lines.back().rect.setBottom(100000);
|
|
|
|
this->lines.back().endIndex = this->elements.size();
|
|
|
|
this->lines.back().endCharIndex = this->charIndex;
|
2018-01-15 04:08:48 +01:00
|
|
|
}
|
2018-01-11 20:16:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-13 02:13:59 +01:00
|
|
|
void MessageLayoutContainer::paintAnimatedElements(QPainter &painter, int yOffset)
|
|
|
|
{
|
|
|
|
for (const std::unique_ptr<MessageLayoutElement> &element : this->elements) {
|
|
|
|
element->paintAnimated(painter, yOffset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-11 20:16:25 +01:00
|
|
|
void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
|
|
|
|
Selection &selection)
|
|
|
|
{
|
2018-01-16 00:34:32 +01:00
|
|
|
singletons::ThemeManager &themeManager = singletons::ThemeManager::getInstance();
|
|
|
|
QColor selectionColor = themeManager.messages.selection;
|
|
|
|
|
2018-01-16 00:26:04 +01:00
|
|
|
// don't draw anything
|
|
|
|
if (selection.min.messageIndex > messageIndex || selection.max.messageIndex < messageIndex) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// fully selected
|
|
|
|
if (selection.min.messageIndex < messageIndex && selection.max.messageIndex > messageIndex) {
|
|
|
|
for (Line &line : this->lines) {
|
|
|
|
QRect rect = line.rect;
|
|
|
|
|
|
|
|
rect.setTop(std::max(0, rect.top()));
|
|
|
|
rect.setBottom(std::min(this->height, rect.bottom()));
|
|
|
|
rect.setLeft(this->elements[line.startIndex]->getRect().left());
|
|
|
|
rect.setRight(this->elements[line.endIndex - 1]->getRect().right());
|
|
|
|
|
2018-01-16 00:34:32 +01:00
|
|
|
painter.fillRect(rect, selectionColor);
|
2018-01-16 00:26:04 +01:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int lineIndex = 0;
|
|
|
|
int index = 0;
|
|
|
|
|
|
|
|
// start in this message
|
|
|
|
if (selection.min.messageIndex == messageIndex) {
|
|
|
|
for (; lineIndex < this->lines.size(); lineIndex++) {
|
|
|
|
Line &line = this->lines[lineIndex];
|
|
|
|
index = line.startCharIndex;
|
|
|
|
|
|
|
|
bool returnAfter = false;
|
|
|
|
bool breakAfter = false;
|
|
|
|
int x = this->elements[line.startIndex]->getRect().left();
|
|
|
|
int r = this->elements[line.endIndex - 1]->getRect().right();
|
|
|
|
|
|
|
|
if (line.endCharIndex < selection.min.charIndex) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = line.startIndex; i < line.endIndex; i++) {
|
|
|
|
int c = this->elements[i]->getSelectionIndexCount();
|
|
|
|
|
|
|
|
if (index + c > selection.min.charIndex) {
|
|
|
|
x = this->elements[i]->getXFromIndex(selection.min.charIndex - index);
|
|
|
|
|
|
|
|
// ends in same line
|
|
|
|
if (selection.max.messageIndex == messageIndex &&
|
|
|
|
line.endCharIndex > /*=*/selection.max.charIndex) //
|
|
|
|
{
|
|
|
|
returnAfter = true;
|
|
|
|
index = line.startCharIndex;
|
|
|
|
for (int i = line.startIndex; i < line.endIndex; i++) {
|
|
|
|
int c = this->elements[i]->getSelectionIndexCount();
|
|
|
|
|
|
|
|
if (index + c > selection.max.charIndex) {
|
|
|
|
r = this->elements[i]->getXFromIndex(selection.max.charIndex -
|
|
|
|
index);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
index += c;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// ends in same line end
|
|
|
|
|
|
|
|
if (selection.max.messageIndex != messageIndex) {
|
|
|
|
int lineIndex2 = lineIndex + 1;
|
|
|
|
for (; lineIndex2 < this->lines.size(); lineIndex2++) {
|
|
|
|
Line &line = this->lines[lineIndex2];
|
|
|
|
QRect rect = line.rect;
|
|
|
|
|
|
|
|
rect.setTop(std::max(0, rect.top()));
|
|
|
|
rect.setBottom(std::min(this->height, rect.bottom()));
|
|
|
|
rect.setLeft(this->elements[line.startIndex]->getRect().left());
|
|
|
|
rect.setRight(this->elements[line.endIndex - 1]->getRect().right());
|
|
|
|
|
2018-01-16 00:34:32 +01:00
|
|
|
painter.fillRect(rect, selectionColor);
|
2018-01-16 00:26:04 +01:00
|
|
|
}
|
|
|
|
returnAfter = true;
|
|
|
|
} else {
|
|
|
|
lineIndex++;
|
|
|
|
breakAfter = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
index += c;
|
|
|
|
}
|
|
|
|
|
|
|
|
QRect rect = line.rect;
|
|
|
|
|
|
|
|
rect.setTop(std::max(0, rect.top()));
|
|
|
|
rect.setBottom(std::min(this->height, rect.bottom()));
|
|
|
|
rect.setLeft(x);
|
|
|
|
rect.setRight(r);
|
|
|
|
|
2018-01-16 00:34:32 +01:00
|
|
|
painter.fillRect(rect, selectionColor);
|
2018-01-16 00:26:04 +01:00
|
|
|
|
|
|
|
if (returnAfter) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (breakAfter) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// start in this message
|
|
|
|
for (; lineIndex < this->lines.size(); lineIndex++) {
|
|
|
|
Line &line = this->lines[lineIndex];
|
|
|
|
index = line.startCharIndex;
|
|
|
|
|
|
|
|
// just draw the garbage
|
|
|
|
if (line.endCharIndex < /*=*/selection.max.charIndex) {
|
|
|
|
QRect rect = line.rect;
|
|
|
|
|
|
|
|
rect.setTop(std::max(0, rect.top()));
|
|
|
|
rect.setBottom(std::min(this->height, rect.bottom()));
|
|
|
|
rect.setLeft(this->elements[line.startIndex]->getRect().left());
|
|
|
|
rect.setRight(this->elements[line.endIndex - 1]->getRect().right());
|
|
|
|
|
2018-01-16 00:34:32 +01:00
|
|
|
painter.fillRect(rect, selectionColor);
|
2018-01-16 00:26:04 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
int r = this->elements[line.endIndex - 1]->getRect().right();
|
|
|
|
|
|
|
|
for (int i = line.startIndex; i < line.endIndex; i++) {
|
|
|
|
int c = this->elements[i]->getSelectionIndexCount();
|
|
|
|
|
|
|
|
if (index + c > selection.max.charIndex) {
|
|
|
|
r = this->elements[i]->getXFromIndex(selection.max.charIndex - index);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
index += c;
|
|
|
|
}
|
|
|
|
|
|
|
|
QRect rect = line.rect;
|
|
|
|
|
|
|
|
rect.setTop(std::max(0, rect.top()));
|
|
|
|
rect.setBottom(std::min(this->height, rect.bottom()));
|
|
|
|
rect.setLeft(this->elements[line.startIndex]->getRect().left());
|
|
|
|
rect.setRight(r);
|
|
|
|
|
2018-01-16 00:34:32 +01:00
|
|
|
painter.fillRect(rect, selectionColor);
|
2018-01-16 00:26:04 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-15 04:08:48 +01:00
|
|
|
// selection
|
|
|
|
int MessageLayoutContainer::getSelectionIndex(QPoint point)
|
|
|
|
{
|
|
|
|
if (this->elements.size() == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto line = this->lines.begin();
|
|
|
|
|
|
|
|
for (; line != this->lines.end(); line++) {
|
|
|
|
if (line->rect.contains(point)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-16 00:26:04 +01:00
|
|
|
int lineStart = line == this->lines.end() ? this->lines.back().startIndex : line->startIndex;
|
|
|
|
if (line != this->lines.end()) {
|
|
|
|
line++;
|
|
|
|
}
|
2018-01-15 04:08:48 +01:00
|
|
|
int lineEnd = line == this->lines.end() ? this->elements.size() : line->startIndex;
|
|
|
|
|
|
|
|
int index = 0;
|
|
|
|
|
|
|
|
for (int i = 0; i < lineEnd; i++) {
|
|
|
|
// end of line
|
|
|
|
if (i == lineEnd) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// before line
|
|
|
|
if (i < lineStart) {
|
|
|
|
index += this->elements[i]->getSelectionIndexCount();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// this is the word
|
2018-01-16 00:26:04 +01:00
|
|
|
if (point.x() < this->elements[i]->getRect().right()) {
|
2018-01-15 04:08:48 +01:00
|
|
|
index += this->elements[i]->getMouseOverIndex(point);
|
|
|
|
break;
|
|
|
|
}
|
2018-01-16 00:26:04 +01:00
|
|
|
|
|
|
|
index += this->elements[i]->getSelectionIndexCount();
|
2018-01-15 04:08:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
qDebug() << index;
|
|
|
|
|
|
|
|
return index;
|
|
|
|
}
|
2018-01-11 20:16:25 +01:00
|
|
|
} // namespace layouts
|
2018-01-13 02:13:59 +01:00
|
|
|
} // namespace messages
|
|
|
|
} // namespace chatterino
|