diff --git a/shared/utils.cpp b/shared/utils.cpp
index a7a4ecb..518d288 100644
--- a/shared/utils.cpp
+++ b/shared/utils.cpp
@@ -40,5 +40,5 @@ QString Shared::processMessageBody(const QString& msg)
{
QString processed = msg.toHtmlEscaped();
processed.replace(urlReg, "\\1");
- return "
" + processed + "
";
+ return "" + processed + "
";
}
diff --git a/ui/utils/CMakeLists.txt b/ui/utils/CMakeLists.txt
index 823287d..b46d30d 100644
--- a/ui/utils/CMakeLists.txt
+++ b/ui/utils/CMakeLists.txt
@@ -15,6 +15,4 @@ target_sources(squawk PRIVATE
resizer.h
shadowoverlay.cpp
shadowoverlay.h
- textmeter.cpp
- textmeter.h
)
diff --git a/ui/utils/textmeter.cpp b/ui/utils/textmeter.cpp
deleted file mode 100644
index 51c6d54..0000000
--- a/ui/utils/textmeter.cpp
+++ /dev/null
@@ -1,233 +0,0 @@
-// Squawk messenger.
-// Copyright (C) 2019 Yury Gubich
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see .
-
-#include "textmeter.h"
-#include
-#include
-
-TextMeter::TextMeter():
- base(),
- fonts()
-{
-}
-
-TextMeter::~TextMeter()
-{
-}
-
-void TextMeter::initializeFonts(const QFont& font)
-{
- fonts.clear();
- QList supported = base.writingSystems(font.family());
- std::set sup;
- std::set added({font.family()});
- for (const QFontDatabase::WritingSystem& system : supported) {
- sup.insert(system);
- }
- fonts.push_back(QFontMetrics(font));
- QString style = base.styleString(font);
-
- QList systems = base.writingSystems();
- for (const QFontDatabase::WritingSystem& system : systems) {
- if (sup.count(system) == 0) {
- QStringList families = base.families(system);
- if (!families.empty() && added.count(families.first()) == 0) {
- QString family(families.first());
- QFont nfont = base.font(family, style, font.pointSize());
- if (added.count(nfont.family()) == 0) {
- added.insert(family);
- fonts.push_back(QFontMetrics(nfont));
- qDebug() << "Added font" << nfont.family() << "for" << system;
- }
- }
- }
- }
-}
-
-QSize TextMeter::boundingSize(const QString& text, const QSize& limits) const
-{
-// QString str("ridiculus mus. Suspendisse potenti. Cras pretium venenatis enim, faucibus accumsan ex");
-// bool first = true;
-// int width = 0;
-// QStringList list = str.split(" ");
-// QFontMetrics m = fonts.front();
-// for (const QString& word : list) {
-// if (first) {
-// first = false;
-// } else {
-// width += m.horizontalAdvance(QChar::Space);
-// }
-// width += m.horizontalAdvance(word);
-// for (const QChar& ch : word) {
-// width += m.horizontalAdvance(ch);
-// }
-// }
-// qDebug() << "together:" << m.horizontalAdvance(str);
-// qDebug() << "apart:" << width;
-// I cant measure or wrap text this way, this simple example shows that even this gives differen result
-// The Qt implementation under it is thousands and thousands lines of code in QTextEngine
-// I simply can't get though it
-
- if (text.size() == 0) {
- return QSize (0, 0);
- }
- Helper current(limits.width());
- for (const QChar& ch : text) {
- if (newLine(ch)) {
- current.computeNewWord();
- if (current.height == 0) {
- current.height = fonts.front().lineSpacing();
- }
- current.beginNewLine();
- } else if (visible(ch)) {
- bool found = false;
- for (const QFontMetrics& metrics : fonts) {
- if (metrics.inFont(ch)) {
- current.computeChar(ch, metrics);
- found = true;
- break;
- }
- }
-
- if (!found) {
- current.computeChar(ch, fonts.front());
- }
- }
- }
- current.computeNewWord();
- current.beginNewLine();
-
- int& height = current.size.rheight();
- if (height > 0) {
- height -= fonts.front().leading();
- }
-
- return current.size;
-}
-
-void TextMeter::Helper::computeChar(const QChar& ch, const QFontMetrics& metrics)
-{
- int ha = metrics.horizontalAdvance(ch);
- if (newWord(ch)) {
- if (printOnLineBreak(ch)) {
- if (!lineOverflow(metrics, ha, ch)){
- computeNewWord();
- }
- } else {
- computeNewWord();
- delayedSpaceWidth = ha;
- lastSpace = ch;
- }
- } else {
- lineOverflow(metrics, ha, ch);
- }
-}
-
-void TextMeter::Helper::computeNewLine(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch)
-{
- if (wordBeganWithTheLine) {
- text = word.chopped(1);
- width = wordWidth - horizontalAdvance;
- height = wordHeight;
- }
- if (width != metrics.horizontalAdvance(text)) {
- qDebug() << "Kerning Error" << width - metrics.horizontalAdvance(text);
- }
- beginNewLine();
- if (wordBeganWithTheLine) {
- word = ch;
- wordWidth = horizontalAdvance;
- wordHeight = metrics.lineSpacing();
- }
-
- wordBeganWithTheLine = true;
- delayedSpaceWidth = 0;
- lastSpace = QChar::Null;
-}
-
-void TextMeter::Helper::beginNewLine()
-{
- size.rheight() += height;
- size.rwidth() = qMax(size.width(), width);
- qDebug() << text;
- text = "";
- width = 0;
- height = 0;
-}
-
-void TextMeter::Helper::computeNewWord()
-{
- width += wordWidth + delayedSpaceWidth;
- height = qMax(height, wordHeight);
- if (lastSpace != QChar::Null) {
- text += lastSpace;
- }
- text += word;
- word = "";
- wordWidth = 0;
- wordHeight = 0;
- delayedSpaceWidth = 0;
- lastSpace = QChar::Null;
- wordBeganWithTheLine = false;
-}
-
-bool TextMeter::Helper::lineOverflow(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch)
-{
- wordHeight = qMax(wordHeight, metrics.lineSpacing());
- wordWidth += horizontalAdvance;
- word += ch;
- if (width + delayedSpaceWidth + wordWidth > maxWidth) {
- computeNewLine(metrics, horizontalAdvance, ch);
- return true;
- }
- return false;
-}
-
-
-bool TextMeter::newLine(const QChar& ch)
-{
- return ch == QChar::LineFeed;
-}
-
-bool TextMeter::newWord(const QChar& ch)
-{
- return ch.isSpace() || ch.isPunct();
-}
-
-bool TextMeter::printOnLineBreak(const QChar& ch)
-{
- return ch != QChar::Space;
-}
-
-bool TextMeter::visible(const QChar& ch)
-{
- return true;
-}
-
-TextMeter::Helper::Helper(int p_maxWidth):
- width(0),
- height(0),
- wordWidth(0),
- wordHeight(0),
- delayedSpaceWidth(0),
- maxWidth(p_maxWidth),
- wordBeganWithTheLine(true),
- text(""),
- word(""),
- lastSpace(QChar::Null),
- size(0, 0)
-{
-}
diff --git a/ui/utils/textmeter.h b/ui/utils/textmeter.h
deleted file mode 100644
index 243d989..0000000
--- a/ui/utils/textmeter.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// Squawk messenger.
-// Copyright (C) 2019 Yury Gubich
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see .
-
-#ifndef TEXTMETER_H
-#define TEXTMETER_H
-
-#include
-#include
-
-#include
-#include
-#include
-#include
-
-class TextMeter
-{
-public:
- TextMeter();
- ~TextMeter();
- void initializeFonts(const QFont& font);
- QSize boundingSize(const QString& text, const QSize& limits) const;
-
-private:
- QFontDatabase base;
- std::list fonts;
-
- struct Helper {
- Helper(int maxWidth);
- int width;
- int height;
- int wordWidth;
- int wordHeight;
- int delayedSpaceWidth;
- int maxWidth;
- bool wordBeganWithTheLine;
- QString text;
- QString word;
- QChar lastSpace;
- QSize size;
-
- void computeNewLine(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch);
- void computeChar(const QChar& ch, const QFontMetrics& metrics);
- void computeNewWord();
- void beginNewLine();
- bool lineOverflow(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch);
- };
-
- static bool newLine(const QChar& ch);
- static bool newWord(const QChar& ch);
- static bool visible(const QChar& ch);
- static bool printOnLineBreak(const QChar& ch);
-
-};
-
-#endif // TEXTMETER_H
diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp
index de7f56f..e0c1477 100644
--- a/ui/widgets/messageline/feedview.cpp
+++ b/ui/widgets/messageline/feedview.cpp
@@ -343,6 +343,7 @@ void FeedView::paintEvent(QPaintEvent* event)
QDateTime lastDate;
bool first = true;
+ QRect viewportRect = vp->rect();
for (const QModelIndex& index : toRener) {
QDateTime currentDate = index.data(Models::MessageFeed::Date).toDateTime();
option.rect = visualRect(index);
@@ -356,7 +357,10 @@ void FeedView::paintEvent(QPaintEvent* event)
}
first = false;
}
- bool mouseOver = option.rect.contains(cursor) && vp->rect().contains(cursor);
+ QRect stripe = option.rect;
+ stripe.setLeft(0);
+ stripe.setWidth(viewportRect.width());
+ bool mouseOver = stripe.contains(cursor) && viewportRect.contains(cursor);
option.state.setFlag(QStyle::State_MouseOver, mouseOver);
itemDelegate(index)->paint(&painter, option, index);
diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
index 4ddecee..1d094fa 100644
--- a/ui/widgets/messageline/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -22,6 +22,7 @@
#include
#include
#include
+#include
#include "messagedelegate.h"
#include "messagefeed.h"
@@ -42,7 +43,7 @@ MessageDelegate::MessageDelegate(QObject* parent):
nickFont(),
dateFont(),
bodyMetrics(bodyFont),
- bodyMeter(),
+ bodyRenderer(new QTextDocument()),
nickMetrics(nickFont),
dateMetrics(dateFont),
buttonHeight(0),
@@ -52,11 +53,13 @@ MessageDelegate::MessageDelegate(QObject* parent):
bars(new std::map()),
statusIcons(new std::map()),
pencilIcons(new std::map()),
- bodies(new std::map()),
+ bodies(new std::map()),
previews(new std::map()),
idsToKeep(new std::set()),
clearingWidgets(false)
{
+ bodyRenderer->setDocumentMargin(0);
+
QPushButton btn(QCoreApplication::translate("MessageLine", "Download"));
buttonHeight = btn.sizeHint().height();
buttonWidth = btn.sizeHint().width();
@@ -83,7 +86,7 @@ MessageDelegate::~MessageDelegate()
delete pair.second;
}
- for (const std::pair& pair: *bodies){
+ for (const std::pair& pair: *bodies){
delete pair.second;
}
@@ -98,6 +101,7 @@ MessageDelegate::~MessageDelegate()
delete bars;
delete bodies;
delete previews;
+ delete bodyRenderer;
}
void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
@@ -124,8 +128,6 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
opt.displayAlignment = Qt::AlignRight | Qt::AlignTop;
}
- QSize bodySize = bodyMeter.boundingSize(data.text, opt.rect.size());
-
QRect rect;
if (ntds) {
painter->setFont(nickFont);
@@ -168,15 +170,7 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
painter->restore();
QWidget* vp = static_cast(painter->device());
- if (data.text.size() > 0) {
- QLabel* body = getBody(data);
- body->setParent(vp);
- body->setMinimumSize(bodySize);
- body->setMaximumSize(bodySize);
- body->move(opt.rect.left(), opt.rect.y());
- body->show();
- opt.rect.adjust(0, bodySize.height() + textMargin, 0, 0);
- }
+ paintBody(data, painter, opt);
painter->setFont(dateFont);
QColor q = painter->pen().color();
QString dateString = data.date.toLocalTime().toString("hh:mm");
@@ -304,7 +298,12 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
Models::FeedItem data = qvariant_cast(vi);
QSize messageSize(0, 0);
if (data.text.size() > 0) {
- messageSize = bodyMeter.boundingSize(data.text, messageRect.size());
+ bodyRenderer->setPlainText(data.text);
+ bodyRenderer->setTextWidth(messageRect.size().width());
+
+ QSizeF size = bodyRenderer->size();
+ size.setWidth(bodyRenderer->idealWidth());
+ messageSize = QSize(qCeil(size.width()), qCeil(size.height()));
messageSize.rheight() += textMargin;
}
@@ -393,7 +392,7 @@ void MessageDelegate::initializeFonts(const QFont& font)
nickMetrics = QFontMetrics(nickFont);
dateMetrics = QFontMetrics(dateFont);
- bodyMeter.initializeFonts(bodyFont);
+ bodyRenderer->setDefaultFont(bodyFont);
Preview::initializeFont(bodyFont);
}
@@ -573,34 +572,36 @@ QLabel * MessageDelegate::getPencilIcon(const Models::FeedItem& data) const
return result;
}
-QLabel * MessageDelegate::getBody(const Models::FeedItem& data) const
+QTextBrowser * MessageDelegate::getBody(const Models::FeedItem& data) const
{
- std::map::const_iterator itr = bodies->find(data.id);
- QLabel* result = 0;
+ std::map::const_iterator itr = bodies->find(data.id);
+ QTextBrowser* result = 0;
if (itr != bodies->end()) {
result = itr->second;
} else {
- result = new QLabel();
+ result = new QTextBrowser();
result->setFont(bodyFont);
result->setContextMenuPolicy(Qt::NoContextMenu);
- result->setWordWrap(true);
+ result->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ result->setContentsMargins(0, 0, 0, 0);
+ //result->viewport()->setAutoFillBackground(false);
+ result->document()->setDocumentMargin(0);
+ result->setFrameStyle(0);
+ result->setLineWidth(0);
+ //result->setAutoFillBackground(false);
+ //->setWordWrap(true);
+ result->setOpenExternalLinks(true);
+ //result->setTextInteractionFlags(result->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
result->setOpenExternalLinks(true);
- result->setTextInteractionFlags(result->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
bodies->insert(std::make_pair(data.id, result));
}
- result->setText(Shared::processMessageBody(data.text));
+ result->setHtml(Shared::processMessageBody(data.text));
return result;
}
-void MessageDelegate::beginClearWidgets()
-{
- idsToKeep->clear();
- clearingWidgets = true;
-}
-
template
void removeElements(std::map* elements, std::set* idsToKeep) {
std::set toRemove;
@@ -615,6 +616,51 @@ void removeElements(std::map* elements, std::set* idsToKee
}
}
+int MessageDelegate::paintBody(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const
+{
+ if (data.text.size() > 0) {
+ bodyRenderer->setHtml(Shared::processMessageBody(data.text));
+ bodyRenderer->setTextWidth(option.rect.size().width());
+ painter->save();
+ painter->translate(option.rect.topLeft());
+ bodyRenderer->drawContents(painter);
+ painter->restore();
+ QSize bodySize(qCeil(bodyRenderer->idealWidth()), qCeil(bodyRenderer->size().height()));
+
+
+ QTextBrowser* editor = nullptr;
+ if (option.state.testFlag(QStyle::State_MouseOver)) {
+ std::set ids({data.id});
+ removeElements(bodies, &ids);
+ editor = getBody(data);
+ editor->setParent(static_cast(painter->device()));
+ } else {
+ std::map::const_iterator itr = bodies->find(data.id);
+ if (itr != bodies->end()) {
+ editor = itr->second;
+ }
+ }
+ if (editor != nullptr) {
+ editor->setMinimumSize(bodySize);
+ editor->setMaximumSize(bodySize);
+ editor->move(option.rect.left(), option.rect.y());
+ editor->show();
+ }
+
+ option.rect.adjust(0, bodySize.height() + textMargin, 0, 0);
+ return bodySize.width();
+ }
+ return 0;
+}
+
+void MessageDelegate::beginClearWidgets()
+{
+ idsToKeep->clear();
+ clearingWidgets = true;
+}
+
+
+
void MessageDelegate::endClearWidgets()
{
if (clearingWidgets) {
diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h
index 38ec0ee..b49410f 100644
--- a/ui/widgets/messageline/messagedelegate.h
+++ b/ui/widgets/messageline/messagedelegate.h
@@ -29,12 +29,13 @@
#include
#include
#include
+#include
+#include
#include "shared/icons.h"
#include "shared/global.h"
#include "shared/utils.h"
#include "shared/pathcheck.h"
-#include "ui/utils/textmeter.h"
#include "preview.h"
@@ -70,13 +71,15 @@ protected:
int paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
int paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
int paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
+ int paintBody(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
void paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const;
void paintBubble(const Models::FeedItem& data, QPainter* painter, const QStyleOptionViewItem& option) const;
+
QPushButton* getButton(const Models::FeedItem& data) const;
QProgressBar* getBar(const Models::FeedItem& data) const;
QLabel* getStatusIcon(const Models::FeedItem& data) const;
QLabel* getPencilIcon(const Models::FeedItem& data) const;
- QLabel* getBody(const Models::FeedItem& data) const;
+ QTextBrowser* getBody(const Models::FeedItem& data) const;
void clearHelperWidget(const Models::FeedItem& data) const;
bool needToDrawAvatar(const QModelIndex& index, const Models::FeedItem& data, const QStyleOptionViewItem& option) const;
@@ -95,7 +98,7 @@ private:
QFont nickFont;
QFont dateFont;
QFontMetrics bodyMetrics;
- TextMeter bodyMeter;
+ QTextDocument* bodyRenderer;
QFontMetrics nickMetrics;
QFontMetrics dateMetrics;
@@ -107,7 +110,7 @@ private:
std::map* bars;
std::map* statusIcons;
std::map* pencilIcons;
- std::map* bodies;
+ std::map* bodies;
std::map* previews;
std::set* idsToKeep;
bool clearingWidgets;