squawk/ui/widgets/messageline/messagedelegate.cpp

788 lines
29 KiB
C++
Raw Normal View History

2020-08-20 21:32:30 +00:00
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <QDebug>
2020-08-20 21:32:30 +00:00
#include <QPainter>
#include <QPainterPath>
2020-08-20 21:32:30 +00:00
#include <QApplication>
#include <QMouseEvent>
#include <QAbstractItemView>
#include <QAbstractTextDocumentLayout>
#include <QTextBlock>
#include <cmath>
2020-08-20 21:32:30 +00:00
#include "messagedelegate.h"
#include "messagefeed.h"
2020-08-20 21:32:30 +00:00
2023-08-15 15:28:25 +00:00
#include "shared/defines.h"
constexpr int textMargin = 2;
constexpr int statusIconSize = 16;
constexpr int bubbleMargin = 6;
constexpr int bubbleBorderRadius = 3;
2020-08-20 21:32:30 +00:00
int MessageDelegate::avatarHeight(50);
int MessageDelegate::margin(6);
2020-08-20 21:32:30 +00:00
MessageDelegate::MessageDelegate(QObject* parent):
QStyledItemDelegate(parent),
bodyFont(Shared::Global::getInstance()->defaultFont),
nickFont(Shared::Global::getInstance()->headerFont),
dateFont(Shared::Global::getInstance()->smallFont),
nickMetrics(Shared::Global::getInstance()->headerFontMetrics),
dateMetrics(Shared::Global::getInstance()->smallFontMetrics),
bodyRenderer(),
buttonHeight(0),
2022-01-09 14:32:23 +00:00
buttonWidth(0),
barHeight(0),
buttons(),
bars(),
statusIcons(),
pencilIcons(),
encryptionIcons(),
previews(),
idsToKeep(),
clearingWidgets(false),
currentId(""),
selection(0, 0)
2020-08-20 21:32:30 +00:00
{
bodyRenderer.setDocumentMargin(0);
bodyRenderer.setDefaultFont(bodyFont);
2022-01-09 14:32:23 +00:00
QPushButton btn(QCoreApplication::translate("MessageLine", "Download"));
buttonHeight = btn.sizeHint().height();
2022-01-09 14:32:23 +00:00
buttonWidth = btn.sizeHint().width();
QProgressBar bar;
barHeight = bar.sizeHint().height();
2020-08-20 21:32:30 +00:00
}
2023-08-15 15:28:25 +00:00
MessageDelegate::~MessageDelegate() {
for (const std::pair<const QString, FeedButton*>& pair: buttons)
delete pair.second;
for (const std::pair<const QString, QProgressBar*>& pair: bars)
delete pair.second;
for (const std::pair<const QString, QLabel*>& pair: statusIcons)
delete pair.second;
for (const std::pair<const QString, QLabel*>& pair: pencilIcons)
delete pair.second;
for (const std::pair<const QString, QLabel*>& pair: encryptionIcons)
delete pair.second;
for (const std::pair<const QString, Preview*>& pair: previews)
delete pair.second;
2020-08-20 21:32:30 +00:00
}
2023-08-15 15:28:25 +00:00
void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const {
QVariant vi = index.data(Models::MessageFeed::Bulk);
2023-08-15 15:28:25 +00:00
if (!vi.isValid())
return;
2023-08-15 15:28:25 +00:00
Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
2020-08-20 21:32:30 +00:00
painter->save();
painter->setRenderHint(QPainter::Antialiasing, true);
2022-01-09 14:32:23 +00:00
paintBubble(data, painter, option);
bool ntds = needToDrawSender(index, data);
2023-08-15 15:28:25 +00:00
if (ntds || option.rect.y() < 1)
paintAvatar(data, index, option, painter);
2020-08-20 21:32:30 +00:00
QStyleOptionViewItem opt = option;
2022-01-09 14:32:23 +00:00
opt.rect = option.rect.adjusted(bubbleMargin, bubbleMargin, -bubbleMargin, -bubbleMargin / 2);
2023-08-15 15:28:25 +00:00
if (!data.sentByMe)
2020-08-20 21:32:30 +00:00
opt.displayAlignment = Qt::AlignLeft | Qt::AlignTop;
2023-08-15 15:28:25 +00:00
else
2020-08-20 21:32:30 +00:00
opt.displayAlignment = Qt::AlignRight | Qt::AlignTop;
2022-01-09 14:32:23 +00:00
QRect rect;
if (ntds) {
2022-01-09 14:32:23 +00:00
painter->setFont(nickFont);
painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect);
opt.rect.adjust(0, nickMetrics.lineSpacing() + textMargin, 0, 0);
}
2022-01-09 14:32:23 +00:00
painter->save();
switch (data.attach.state) {
case Models::none:
clearHelperWidget(data); //i can't imagine the situation where it's gonna be needed
break; //but it's a possible performance problem
case Models::uploading:
2022-01-09 14:32:23 +00:00
paintPreview(data, painter, opt);
[[fallthrough]];
case Models::downloading:
2022-01-09 14:32:23 +00:00
paintBar(getBar(data), painter, data.sentByMe, opt);
break;
case Models::remote:
2022-01-09 14:32:23 +00:00
paintButton(getButton(data), painter, data.sentByMe, opt);
break;
case Models::ready:
case Models::local:
clearHelperWidget(data);
2022-01-09 14:32:23 +00:00
paintPreview(data, painter, opt);
break;
case Models::errorDownload: {
2022-01-09 14:32:23 +00:00
paintButton(getButton(data), painter, data.sentByMe, opt);
paintComment(data, painter, opt);
}
break;
case Models::errorUpload:{
clearHelperWidget(data);
2022-01-09 14:32:23 +00:00
paintPreview(data, painter, opt);
paintComment(data, painter, opt);
}
break;
}
painter->restore();
QWidget* vp = static_cast<QWidget*>(painter->device());
paintBody(data, painter, opt);
2020-08-20 21:32:30 +00:00
painter->setFont(dateFont);
QColor q = painter->pen().color();
2022-01-09 14:32:23 +00:00
QString dateString = data.date.toLocalTime().toString("hh:mm");
2020-08-20 21:32:30 +00:00
q.setAlpha(180);
painter->setPen(q);
painter->drawText(opt.rect, opt.displayAlignment, dateString, &rect);
int currentY = opt.rect.y();
int statusOffset = statusIconSize;
if (data.sentByMe) {
QLabel* statusIcon = getStatusIcon(data);
statusIcon->setParent(vp);
2022-01-09 14:32:23 +00:00
statusIcon->move(opt.rect.left(), currentY);
statusIcon->show();
opt.rect.adjust(0, statusIconSize + textMargin, 0, 0);
statusOffset = statusIconSize + margin;
}
2020-08-20 21:32:30 +00:00
if (data.correction.corrected) {
QLabel* pencilIcon = getPencilIcon(data);
pencilIcon->setParent(vp);
2023-08-15 15:28:25 +00:00
if (data.sentByMe)
pencilIcon->move(opt.rect.left() + statusOffset, currentY);
2023-08-15 15:28:25 +00:00
else
pencilIcon->move(opt.rect.right() - statusOffset, currentY);
2023-08-15 15:28:25 +00:00
pencilIcon->show();
statusOffset += statusIconSize + margin;
} else {
std::map<QString, QLabel*>::const_iterator itr = pencilIcons.find(data.id);
if (itr != pencilIcons.end()) {
delete itr->second;
pencilIcons.erase(itr);
}
}
if (data.encryption != Shared::EncryptionProtocol::none) {
QLabel* shieldIcon = getEncryptionIcon(data);
shieldIcon->setParent(vp);
if (data.sentByMe)
shieldIcon->move(opt.rect.left() + statusOffset, currentY);
else
shieldIcon->move(opt.rect.right() - statusOffset, currentY);
shieldIcon->show();
statusOffset += statusIconSize + margin;
}
2020-08-20 21:32:30 +00:00
painter->restore();
2023-08-15 15:28:25 +00:00
if (clearingWidgets)
idsToKeep.insert(data.id);
2020-08-20 21:32:30 +00:00
}
2023-08-15 15:28:25 +00:00
void MessageDelegate::paintBubble(const Models::FeedItem& data, QPainter* painter, const QStyleOptionViewItem& option) const {
painter->save();
2023-08-15 15:28:25 +00:00
if (data.sentByMe)
painter->setBrush(option.palette.brush(QPalette::Inactive, QPalette::Highlight));
2023-08-15 15:28:25 +00:00
else
painter->setBrush(option.palette.brush(QPalette::Window));
2023-08-15 15:28:25 +00:00
painter->setPen(Qt::NoPen);
2022-01-09 14:32:23 +00:00
painter->drawRoundedRect(option.rect, bubbleBorderRadius, bubbleBorderRadius);
painter->restore();
}
2023-08-15 15:28:25 +00:00
void MessageDelegate::paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const {
int currentRow = index.row();
int y = option.rect.y();
bool firstAttempt = true;
QIcon icon(data.avatar);
while (y < 0 && currentRow > 0) {
QRect rect;
if (firstAttempt) {
firstAttempt = false;
rect = option.rect;
} else {
QModelIndex ci = index.siblingAtRow(currentRow);
if (
(ci.data(Models::MessageFeed::Sender).toString() != data.sender) ||
(ci.data(Models::MessageFeed::Date).toDateTime().daysTo(data.date) != 0)
) {
break;
}
//TODO this is really bad, but for now I have no idea how else can I access the view;
const QAbstractItemView* view = static_cast<const QAbstractItemView*>(option.styleObject);
rect = view->visualRect(ci);
}
y = std::min(0, rect.bottom() - margin - avatarHeight);
--currentRow;
}
QPixmap pixmap = icon.pixmap(avatarHeight, avatarHeight);
QPainterPath path;
int ax;
2023-08-15 15:28:25 +00:00
if (data.sentByMe)
2022-01-09 14:32:23 +00:00
ax = option.rect.x() + option.rect.width() + margin;
2023-08-15 15:28:25 +00:00
else
ax = margin;
path.addEllipse(ax, y + margin / 2, avatarHeight, avatarHeight);
painter->save();
painter->setClipPath(path);
painter->drawPixmap(ax, y + margin / 2, pixmap);
painter->restore();
}
2023-08-15 15:28:25 +00:00
bool MessageDelegate::needToDrawAvatar(const QModelIndex& index, const Models::FeedItem& data, const QStyleOptionViewItem& option) const {
return (option.rect.y() < 1) || needToDrawSender(index, data);
}
bool MessageDelegate::needToDrawSender(const QModelIndex& index, const Models::FeedItem& data) const
{
if (index.row() == index.model()->rowCount() - 1) {
return true;
} else {
QModelIndex prevIndex = index.siblingAtRow(index.row() + 1);
return (prevIndex.data(Models::MessageFeed::Sender).toString() != data.sender) ||
(prevIndex.data(Models::MessageFeed::Date).toDateTime().daysTo(data.date) != 0);
}
}
2023-08-15 15:28:25 +00:00
QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const {
QRect messageRect = option.rect.adjusted(bubbleMargin, margin / 2 + bubbleMargin, -(avatarHeight + 3 * margin + bubbleMargin), -(margin + bubbleMargin) / 2);
2020-08-20 21:32:30 +00:00
QStyleOptionViewItem opt = option;
opt.rect = messageRect;
QVariant vi = index.data(Models::MessageFeed::Bulk);
Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
QSize messageSize(0, 0);
if (data.text.size() > 0) {
bodyRenderer.setPlainText(data.text);
bodyRenderer.setTextWidth(messageRect.size().width());
QSizeF size = bodyRenderer.size();
size.setWidth(bodyRenderer.idealWidth());
messageSize = QSize(std::ceil(size.width()), std::ceil(size.height()));
messageSize.rheight() += textMargin;
}
2020-08-20 21:32:30 +00:00
switch (data.attach.state) {
case Models::none:
break;
case Models::uploading:
messageSize.rheight() += Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), messageRect).height() + textMargin;
[[fallthrough]];
case Models::downloading:
messageSize.rheight() += barHeight + textMargin;
2022-01-09 14:32:23 +00:00
messageSize.setWidth(messageRect.width());
break;
case Models::remote:
messageSize.rheight() += buttonHeight + textMargin;
2022-01-09 14:32:23 +00:00
messageSize.setWidth(std::max(messageSize.width(), buttonWidth));
break;
case Models::ready:
2022-01-09 14:32:23 +00:00
case Models::local: {
QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), messageRect);
2022-01-09 14:32:23 +00:00
messageSize.rheight() += aSize.height() + textMargin;
messageSize.setWidth(std::max(messageSize.width(), aSize.width()));
}
break;
2022-01-09 14:32:23 +00:00
case Models::errorDownload: {
QSize commentSize = dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.attach.error).size();
messageSize.rheight() += commentSize.height() + buttonHeight + textMargin * 2;
messageSize.setWidth(std::max(messageSize.width(), std::max(commentSize.width(), buttonWidth)));
}
break;
2022-01-09 14:32:23 +00:00
case Models::errorUpload: {
QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), messageRect);
2022-01-09 14:32:23 +00:00
QSize commentSize = dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.attach.error).size();
messageSize.rheight() += aSize.height() + commentSize.height() + textMargin * 2;
messageSize.setWidth(std::max(messageSize.width(), std::max(commentSize.width(), aSize.width())));
}
break;
}
if (needToDrawSender(index, data)) {
2022-01-09 14:32:23 +00:00
QSize senderSize = nickMetrics.boundingRect(messageRect, 0, data.sender).size();
messageSize.rheight() += senderSize.height() + textMargin;
messageSize.setWidth(std::max(senderSize.width(), messageSize.width()));
}
2022-01-09 14:32:23 +00:00
QString dateString = data.date.toLocalTime().toString("hh:mm");
QSize dateSize = dateMetrics.boundingRect(messageRect, 0, dateString).size();
messageSize.rheight() += bubbleMargin * 1.5;
messageSize.rheight() += dateSize.height() > statusIconSize ? dateSize.height() : statusIconSize;
int statusWidth = dateSize.width() + statusIconSize + margin;
if (data.correction.corrected)
2022-01-09 14:32:23 +00:00
statusWidth += statusIconSize + margin;
if (data.encryption != Shared::EncryptionProtocol::none)
statusWidth += statusIconSize + margin;
2022-01-09 14:32:23 +00:00
messageSize.setWidth(std::max(statusWidth, messageSize.width()));
messageSize.rwidth() += 2 * bubbleMargin;
2020-08-20 21:32:30 +00:00
return messageSize;
}
2023-08-15 15:28:25 +00:00
QRect MessageDelegate::getHoveredMessageBodyRect(const QModelIndex& index, const Models::FeedItem& data, const QRect& sizeHint) const {
QRect localHint = sizeHint.adjusted(bubbleMargin, bubbleMargin + margin, -bubbleMargin, -bubbleMargin / 2);
2023-08-15 15:28:25 +00:00
if (needToDrawSender(index, data))
localHint.adjust(0, nickMetrics.lineSpacing() + textMargin, 0, 0);
int attachHeight = 0;
switch (data.attach.state) {
case Models::none:
break;
case Models::uploading:
attachHeight += Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint).height() + textMargin;
[[fallthrough]];
case Models::downloading:
attachHeight += barHeight + textMargin;
break;
case Models::remote:
attachHeight += buttonHeight + textMargin;
break;
case Models::ready:
case Models::local: {
QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint);
attachHeight += aSize.height() + textMargin;
}
break;
case Models::errorDownload: {
QSize commentSize = dateMetrics.boundingRect(localHint, Qt::TextWordWrap, data.attach.error).size();
attachHeight += commentSize.height() + buttonHeight + textMargin * 2;
}
break;
case Models::errorUpload: {
QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint);
QSize commentSize = dateMetrics.boundingRect(localHint, Qt::TextWordWrap, data.attach.error).size();
attachHeight += aSize.height() + commentSize.height() + textMargin * 2;
}
break;
}
int bottomSize = std::max(dateMetrics.lineSpacing(), statusIconSize);
localHint.adjust(0, attachHeight, 0, -(bottomSize + textMargin));
return localHint;
}
2023-08-15 15:28:25 +00:00
QString MessageDelegate::getAnchor(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const {
QVariant vi = index.data(Models::MessageFeed::Bulk);
Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
if (data.text.size() > 0) {
QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint);
if (localHint.contains(point)) {
QPoint translated = point - localHint.topLeft();
bodyRenderer.setHtml(Shared::processMessageBody(data.text));
bodyRenderer.setTextWidth(localHint.size().width());
return bodyRenderer.documentLayout()->anchorAt(translated);
}
}
return QString();
}
2023-08-15 15:28:25 +00:00
void MessageDelegate::leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const {
QString anchor = getAnchor(point, index, sizeHint);
2023-08-15 15:28:25 +00:00
if (anchor.size() > 0)
emit openLink(anchor);
}
2023-08-15 15:28:25 +00:00
QString MessageDelegate::leftDoubleClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) {
QVariant vi = index.data(Models::MessageFeed::Bulk);
Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
if (data.text.size() > 0) {
QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint);
if (localHint.contains(point)) {
QPoint translated = point - localHint.topLeft();
bodyRenderer.setHtml(Shared::processMessageBody(data.text));
bodyRenderer.setTextWidth(localHint.size().width());
QAbstractTextDocumentLayout* lay = bodyRenderer.documentLayout();
int position = lay->hitTest(translated, Qt::HitTestAccuracy::FuzzyHit);
QTextCursor cursor(&bodyRenderer);
cursor.setPosition(position, QTextCursor::MoveAnchor);
cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
selection.first = cursor.anchor();
selection.second = cursor.position();
currentId = data.id;
if (selection.first != selection.second) {
return cursor.selectedText();
}
}
}
return "";
}
2023-08-15 15:28:25 +00:00
Shared::Hover MessageDelegate::hoverType(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const {
QVariant vi = index.data(Models::MessageFeed::Bulk);
Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
if (data.text.size() > 0) {
QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint);
if (localHint.contains(point)) {
QPoint translated = point - localHint.topLeft();
bodyRenderer.setHtml(Shared::processMessageBody(data.text));
bodyRenderer.setTextWidth(localHint.size().width());
QAbstractTextDocumentLayout* lay = bodyRenderer.documentLayout();
QString anchor = lay->anchorAt(translated);
if (anchor.size() > 0) {
return Shared::Hover::anchor;
} else {
int position = lay->hitTest(translated, Qt::HitTestAccuracy::ExactHit);
2023-08-15 15:28:25 +00:00
if (position != -1)
return Shared::Hover::text;
}
}
}
return Shared::Hover::nothing;
}
2023-08-15 15:28:25 +00:00
QString MessageDelegate::mouseDrag(const QPoint& start, const QPoint& end, const QModelIndex& index, const QRect& sizeHint) {
QVariant vi = index.data(Models::MessageFeed::Bulk);
Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
if (data.text.size() > 0) {
QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint);
if (localHint.contains(start)) {
QPoint tl = localHint.topLeft();
QPoint first = start - tl;
QPoint last = end - tl;
last.setX(std::max(last.x(), 0));
last.setX(std::min(last.x(), localHint.width() - 1));
last.setY(std::max(last.y(), 0));
last.setY(std::min(last.y(), localHint.height()));
bodyRenderer.setHtml(Shared::processMessageBody(data.text));
bodyRenderer.setTextWidth(localHint.size().width());
selection.first = bodyRenderer.documentLayout()->hitTest(first, Qt::HitTestAccuracy::FuzzyHit);
selection.second = bodyRenderer.documentLayout()->hitTest(last, Qt::HitTestAccuracy::FuzzyHit);
currentId = data.id;
if (selection.first != selection.second) {
QTextCursor cursor(&bodyRenderer);
cursor.setPosition(selection.first, QTextCursor::MoveAnchor);
cursor.setPosition(selection.second, QTextCursor::KeepAnchor);
return cursor.selectedText();
}
}
}
return "";
}
2023-08-15 15:28:25 +00:00
QString MessageDelegate::clearSelection() {
QString lastSelectedId = currentId;
currentId = "";
selection = std::pair(0, 0);
return lastSelectedId;
}
2023-08-15 15:28:25 +00:00
bool MessageDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) {
//qDebug() << event->type();
return QStyledItemDelegate::editorEvent(event, model, option, index);
}
2023-08-15 15:28:25 +00:00
int MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const {
QPoint start;
2023-08-15 15:28:25 +00:00
if (sentByMe)
start = {option.rect.x() + option.rect.width() - btn->width(), option.rect.top()};
2023-08-15 15:28:25 +00:00
else
start = option.rect.topLeft();
QWidget* vp = static_cast<QWidget*>(painter->device());
btn->setParent(vp);
btn->move(start);
btn->show();
option.rect.adjust(0, buttonHeight + textMargin, 0, 0);
return btn->width();
}
2023-08-15 15:28:25 +00:00
int MessageDelegate::paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const {
painter->setFont(dateFont);
QColor q = painter->pen().color();
q.setAlpha(180);
painter->setPen(q);
QRect rect;
painter->drawText(option.rect, option.displayAlignment, data.attach.error, &rect);
option.rect.adjust(0, rect.height() + textMargin, 0, 0);
return rect.width();
}
2023-08-15 15:28:25 +00:00
int MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const {
SHARED_UNUSED(sentByMe);
QPoint start = option.rect.topLeft();
bar->resize(option.rect.width(), barHeight);
painter->translate(start);
bar->render(painter, QPoint(), QRegion(), QWidget::DrawChildren);
option.rect.adjust(0, barHeight + textMargin, 0, 0);
return option.rect.width();
}
2023-08-15 15:28:25 +00:00
int MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const {
Preview* preview = 0;
std::map<QString, Preview*>::iterator itr = previews.find(data.id);
QSize size = option.rect.size();
QString path = Shared::resolvePath(data.attach.localPath);
if (itr != previews.end()) {
preview = itr->second;
preview->actualize(path, size, option.rect.topLeft());
} else {
QWidget* vp = static_cast<QWidget*>(painter->device());
preview = new Preview(path, size, option.rect.topLeft(), vp);
previews.insert(std::make_pair(data.id, preview));
}
2023-08-15 15:28:25 +00:00
if (!preview->isFileReachable()) //this is the situation when the file preview couldn't be painted because the file was moved
emit invalidPath(data.id); //or deleted. This signal notifies the model, and the model notifies the core, preview can
2023-08-15 15:28:25 +00:00
//handle being invalid for as long as I need and can be even become valid again with a new path
QSize pSize(preview->size());
option.rect.adjust(0, pSize.height() + textMargin, 0, 0);
return pSize.width();
}
2023-08-15 15:28:25 +00:00
QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const {
std::map<QString, FeedButton*>::const_iterator itr = buttons.find(data.id);
FeedButton* result = 0;
if (itr != buttons.end()) {
result = itr->second;
} else {
std::map<QString, QProgressBar*>::const_iterator barItr = bars.find(data.id);
if (barItr != bars.end()) {
delete barItr->second;
bars.erase(barItr);
}
}
if (result == 0) {
result = new FeedButton();
result->messageId = data.id;
result->setText(QCoreApplication::translate("MessageLine", "Download"));
buttons.insert(std::make_pair(data.id, result));
connect(result, &QPushButton::clicked, this, &MessageDelegate::onButtonPushed);
}
return result;
}
2023-08-15 15:28:25 +00:00
QProgressBar * MessageDelegate::getBar(const Models::FeedItem& data) const {
std::map<QString, QProgressBar*>::const_iterator barItr = bars.find(data.id);
QProgressBar* result = 0;
if (barItr != bars.end()) {
result = barItr->second;
} else {
std::map<QString, FeedButton*>::const_iterator itr = buttons.find(data.id);
if (itr != buttons.end()) {
delete itr->second;
buttons.erase(itr);
}
}
if (result == 0) {
result = new QProgressBar();
result->setRange(0, 100);
bars.insert(std::make_pair(data.id, result));
}
result->setValue(data.attach.progress * 100);
return result;
}
2023-08-15 15:28:25 +00:00
QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const {
std::map<QString, QLabel*>::const_iterator itr = statusIcons.find(data.id);
QLabel* result = 0;
if (itr != statusIcons.end()) {
result = itr->second;
} else {
result = new QLabel();
statusIcons.insert(std::make_pair(data.id, result));
}
QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast<uint8_t>(data.state)]));
QString tt = Shared::Global::getName(data.state);
if (data.state == Shared::Message::State::error) {
2024-12-13 23:53:04 +00:00
if (data.error.size() > 0)
tt += ": " + data.error;
}
if (result->toolTip() != tt) { //If i just assign pixmap every time unconditionally
result->setPixmap(q.pixmap(statusIconSize)); //it invokes an infinite cycle of repaint
result->setToolTip(tt); //may be it's better to subclass and store last condition in int?
}
return result;
}
QLabel* MessageDelegate::getPencilIcon(const Models::FeedItem& data) const {
std::map<QString, QLabel*>::const_iterator itr = pencilIcons.find(data.id);
QLabel* result = 0;
if (itr != pencilIcons.end()) {
result = itr->second;
} else {
result = new QLabel();
QIcon icon = Shared::icon("edit-rename");
result->setPixmap(icon.pixmap(statusIconSize));
pencilIcons.insert(std::make_pair(data.id, result));
}
result->setToolTip("Last time edited: " + data.correction.lastCorrection.toLocalTime().toString()
+ "\nOriginal message: " + data.correction.original);
return result;
}
QLabel* MessageDelegate::getEncryptionIcon(const Models::FeedItem& data) const {
std::map<QString, QLabel*>::const_iterator itr = encryptionIcons.find(data.id);
QLabel* result = 0;
if (itr != encryptionIcons.end()) {
result = itr->second;
} else {
result = new QLabel();
QIcon icon = Shared::icon("secure");
result->setPixmap(icon.pixmap(statusIconSize));
encryptionIcons.insert(std::make_pair(data.id, result));
result->setToolTip("Encrypted: " + Shared::Global::getName(data.encryption));
}
return result;
}
template <typename T>
void removeElements(std::map<QString, T*>& elements, std::set<QString>& idsToKeep) {
std::set<QString> toRemove;
for (const std::pair<const QString, T*>& pair: elements) {
if (idsToKeep.find(pair.first) == idsToKeep.end()) {
delete pair.second;
toRemove.insert(pair.first);
}
}
for (const QString& key : toRemove)
elements.erase(key);
}
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());
if (data.id == currentId) {
QTextCursor cursor(&bodyRenderer);
cursor.setPosition(selection.first, QTextCursor::MoveAnchor);
cursor.setPosition(selection.second, QTextCursor::KeepAnchor);
QTextCharFormat format = cursor.charFormat();
format.setBackground(option.palette.color(QPalette::Active, QPalette::Highlight));
format.setForeground(option.palette.color(QPalette::Active, QPalette::HighlightedText));
cursor.setCharFormat(format);
}
bodyRenderer.drawContents(painter);
painter->restore();
QSize bodySize(std::ceil(bodyRenderer.idealWidth()), std::ceil(bodyRenderer.size().height()));
option.rect.adjust(0, bodySize.height() + textMargin, 0, 0);
return bodySize.width();
}
return 0;
}
2023-08-15 15:28:25 +00:00
void MessageDelegate::beginClearWidgets() {
idsToKeep.clear();
clearingWidgets = true;
}
2023-08-15 15:28:25 +00:00
void MessageDelegate::endClearWidgets() {
if (clearingWidgets) {
removeElements(buttons, idsToKeep);
removeElements(bars, idsToKeep);
removeElements(statusIcons, idsToKeep);
removeElements(pencilIcons, idsToKeep);
removeElements(encryptionIcons, idsToKeep);
removeElements(previews, idsToKeep);
idsToKeep.clear();
clearingWidgets = false;
}
}
2023-08-15 15:28:25 +00:00
void MessageDelegate::onButtonPushed() const {
FeedButton* btn = static_cast<FeedButton*>(sender());
emit buttonPushed(btn->messageId);
}
2020-08-20 21:32:30 +00:00
2023-08-15 15:28:25 +00:00
void MessageDelegate::clearHelperWidget(const Models::FeedItem& data) const {
std::map<QString, FeedButton*>::const_iterator itr = buttons.find(data.id);
if (itr != buttons.end()) {
delete itr->second;
buttons.erase(itr);
} else {
std::map<QString, QProgressBar*>::const_iterator barItr = bars.find(data.id);
if (barItr != bars.end()) {
delete barItr->second;
bars.erase(barItr);
}
}
}