1
0
forked from blue/squawk

link clicking and hovering in message body now works!

This commit is contained in:
Blue 2022-04-29 00:29:44 +03:00
parent eac87e713f
commit 7ba94e9deb
Signed by untrusted user: blue
GPG Key ID: 9B203B252A63EE38
4 changed files with 105 additions and 120 deletions

View File

@ -51,7 +51,8 @@ FeedView::FeedView(QWidget* parent):
progress(), progress(),
dividerFont(), dividerFont(),
dividerMetrics(dividerFont), dividerMetrics(dividerFont),
mousePressed(false) mousePressed(false),
anchorHovered(false)
{ {
horizontalScrollBar()->setRange(0, 0); horizontalScrollBar()->setRange(0, 0);
verticalScrollBar()->setSingleStep(approximateSingleMessageHeight); verticalScrollBar()->setSingleStep(approximateSingleMessageHeight);
@ -408,6 +409,18 @@ void FeedView::verticalScrollbarValueChanged(int value)
QAbstractItemView::verticalScrollbarValueChanged(vo); QAbstractItemView::verticalScrollbarValueChanged(vo);
} }
void FeedView::setAnchorHovered(bool hovered)
{
if (anchorHovered != hovered) {
anchorHovered = hovered;
if (anchorHovered) {
setCursor(Qt::PointingHandCursor);
} else {
setCursor(Qt::ArrowCursor);
}
}
}
void FeedView::mouseMoveEvent(QMouseEvent* event) void FeedView::mouseMoveEvent(QMouseEvent* event)
{ {
if (!isVisible()) { if (!isVisible()) {
@ -418,6 +431,22 @@ void FeedView::mouseMoveEvent(QMouseEvent* event)
//qDebug() << event; //qDebug() << event;
QAbstractItemView::mouseMoveEvent(event); QAbstractItemView::mouseMoveEvent(event);
if (specialDelegate) {
QPoint point = event->localPos().toPoint();
QModelIndex index = indexAt(point);
if (index.isValid()) {
QRect rect = visualRect(index);
MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
if (rect.contains(point)) {
setAnchorHovered(del->isAnchorHovered(point, index, rect));
} else {
setAnchorHovered(false);
}
} else {
setAnchorHovered(false);
}
}
} }
void FeedView::mousePressEvent(QMouseEvent* event) void FeedView::mousePressEvent(QMouseEvent* event)
@ -487,6 +516,7 @@ void FeedView::setItemDelegate(QAbstractItemDelegate* delegate)
elementMargin = MessageDelegate::margin; elementMargin = MessageDelegate::margin;
connect(del, &MessageDelegate::buttonPushed, this, &FeedView::onMessageButtonPushed); connect(del, &MessageDelegate::buttonPushed, this, &FeedView::onMessageButtonPushed);
connect(del, &MessageDelegate::invalidPath, this, &FeedView::onMessageInvalidPath); connect(del, &MessageDelegate::invalidPath, this, &FeedView::onMessageInvalidPath);
connect(del, &MessageDelegate::openLink, &QDesktopServices::openUrl);
} else { } else {
specialDelegate = false; specialDelegate = false;
elementMargin = 0; elementMargin = 0;

View File

@ -20,6 +20,8 @@
#define FEEDVIEW_H #define FEEDVIEW_H
#include <QAbstractItemView> #include <QAbstractItemView>
#include <QDesktopServices>
#include <QUrl>
#include <deque> #include <deque>
#include <set> #include <set>
@ -76,6 +78,7 @@ private:
bool tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* model, uint32_t totalHeight); bool tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* model, uint32_t totalHeight);
void positionProgress(); void positionProgress();
void drawDateDevider(int top, const QDateTime& date, QPainter& painter); void drawDateDevider(int top, const QDateTime& date, QPainter& painter);
void setAnchorHovered(bool hovered);
private: private:
struct Hint { struct Hint {
@ -96,6 +99,7 @@ private:
QFont dividerFont; QFont dividerFont;
QFontMetrics dividerMetrics; QFontMetrics dividerMetrics;
bool mousePressed; bool mousePressed;
bool anchorHovered;
static const std::set<int> geometryChangingRoles; static const std::set<int> geometryChangingRoles;

View File

@ -44,7 +44,6 @@ MessageDelegate::MessageDelegate(QObject* parent):
bodyFont(), bodyFont(),
nickFont(), nickFont(),
dateFont(), dateFont(),
bodyMetrics(bodyFont),
bodyRenderer(new QTextDocument()), bodyRenderer(new QTextDocument()),
nickMetrics(nickFont), nickMetrics(nickFont),
dateMetrics(dateFont), dateMetrics(dateFont),
@ -55,7 +54,6 @@ MessageDelegate::MessageDelegate(QObject* parent):
bars(new std::map<QString, QProgressBar*>()), bars(new std::map<QString, QProgressBar*>()),
statusIcons(new std::map<QString, QLabel*>()), statusIcons(new std::map<QString, QLabel*>()),
pencilIcons(new std::map<QString, QLabel*>()), pencilIcons(new std::map<QString, QLabel*>()),
bodies(new std::map<QString, QTextBrowser*>()),
previews(new std::map<QString, Preview*>()), previews(new std::map<QString, Preview*>()),
idsToKeep(new std::set<QString>()), idsToKeep(new std::set<QString>()),
clearingWidgets(false) clearingWidgets(false)
@ -88,10 +86,6 @@ MessageDelegate::~MessageDelegate()
delete pair.second; delete pair.second;
} }
for (const std::pair<const QString, QTextBrowser*>& pair: *bodies){
delete pair.second;
}
for (const std::pair<const QString, Preview*>& pair: *previews){ for (const std::pair<const QString, Preview*>& pair: *previews){
delete pair.second; delete pair.second;
} }
@ -101,7 +95,6 @@ MessageDelegate::~MessageDelegate()
delete idsToKeep; delete idsToKeep;
delete buttons; delete buttons;
delete bars; delete bars;
delete bodies;
delete previews; delete previews;
delete bodyRenderer; delete bodyRenderer;
} }
@ -366,66 +359,83 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
return messageSize; return messageSize;
} }
void MessageDelegate::leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const QRect MessageDelegate::getHoveredMessageBodyRect(const QModelIndex& index, const Models::FeedItem& data, const QRect& sizeHint) const
{
QRect localHint = sizeHint.adjusted(bubbleMargin, bubbleMargin + margin, -bubbleMargin, -bubbleMargin / 2);
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;
}
QString MessageDelegate::getAnchor(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const
{ {
QVariant vi = index.data(Models::MessageFeed::Bulk); QVariant vi = index.data(Models::MessageFeed::Bulk);
Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi); Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
if (data.text.size() > 0) { if (data.text.size() > 0) {
QRect localHint = sizeHint.adjusted(bubbleMargin, bubbleMargin + margin, -bubbleMargin, -bubbleMargin / 2); QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint);
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));
if (localHint.contains(point)) { if (localHint.contains(point)) {
qDebug() << "MESSAGE CLICKED";
QPoint translated = point - localHint.topLeft(); QPoint translated = point - localHint.topLeft();
bodyRenderer->setPlainText(data.text); bodyRenderer->setHtml(Shared::processMessageBody(data.text));
bodyRenderer->setTextWidth(localHint.size().width()); bodyRenderer->setTextWidth(localHint.size().width());
int pos = bodyRenderer->documentLayout()->hitTest(translated, Qt::FuzzyHit); return bodyRenderer->documentLayout()->anchorAt(translated);
QTextBlock block = bodyRenderer->findBlock(pos);
QString text = block.text();
if (text.size() > 0) {
qDebug() << text;
}
} }
} }
return QString();
}
void MessageDelegate::leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const
{
QString anchor = getAnchor(point, index, sizeHint);
if (anchor.size() > 0) {
emit openLink(anchor);
}
}
bool MessageDelegate::isAnchorHovered(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const
{
QString anchor = getAnchor(point, index, sizeHint);
return anchor.size() > 0;
} }
void MessageDelegate::initializeFonts(const QFont& font) void MessageDelegate::initializeFonts(const QFont& font)
@ -452,7 +462,6 @@ void MessageDelegate::initializeFonts(const QFont& font)
} }
bodyFont.setKerning(false); bodyFont.setKerning(false);
bodyMetrics = QFontMetrics(bodyFont);
nickMetrics = QFontMetrics(nickFont); nickMetrics = QFontMetrics(nickFont);
dateMetrics = QFontMetrics(dateFont); dateMetrics = QFontMetrics(dateFont);
@ -636,36 +645,6 @@ QLabel * MessageDelegate::getPencilIcon(const Models::FeedItem& data) const
return result; return result;
} }
QTextBrowser * MessageDelegate::getBody(const Models::FeedItem& data) const
{
std::map<QString, QTextBrowser*>::const_iterator itr = bodies->find(data.id);
QTextBrowser* result = 0;
if (itr != bodies->end()) {
result = itr->second;
} else {
result = new QTextBrowser();
result->setFont(bodyFont);
result->setContextMenuPolicy(Qt::NoContextMenu);
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);
bodies->insert(std::make_pair(data.id, result));
}
result->setHtml(Shared::processMessageBody(data.text));
return result;
}
template <typename T> template <typename T>
void removeElements(std::map<QString, T*>* elements, std::set<QString>* idsToKeep) { void removeElements(std::map<QString, T*>* elements, std::set<QString>* idsToKeep) {
std::set<QString> toRemove; std::set<QString> toRemove;
@ -691,26 +670,6 @@ int MessageDelegate::paintBody(const Models::FeedItem& data, QPainter* painter,
painter->restore(); painter->restore();
QSize bodySize(std::ceil(bodyRenderer->idealWidth()), std::ceil(bodyRenderer->size().height())); QSize bodySize(std::ceil(bodyRenderer->idealWidth()), std::ceil(bodyRenderer->size().height()));
/*
QTextBrowser* editor = nullptr;
if (option.state.testFlag(QStyle::State_MouseOver)) {
std::set<QString> ids({data.id});
removeElements(bodies, &ids);
editor = getBody(data);
editor->setParent(static_cast<QWidget*>(painter->device()));
} else {
std::map<QString, QTextBrowser*>::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); option.rect.adjust(0, bodySize.height() + textMargin, 0, 0);
return bodySize.width(); return bodySize.width();
} }
@ -723,8 +682,6 @@ void MessageDelegate::beginClearWidgets()
clearingWidgets = true; clearingWidgets = true;
} }
void MessageDelegate::endClearWidgets() void MessageDelegate::endClearWidgets()
{ {
if (clearingWidgets) { if (clearingWidgets) {
@ -732,7 +689,6 @@ void MessageDelegate::endClearWidgets()
removeElements(bars, idsToKeep); removeElements(bars, idsToKeep);
removeElements(statusIcons, idsToKeep); removeElements(statusIcons, idsToKeep);
removeElements(pencilIcons, idsToKeep); removeElements(pencilIcons, idsToKeep);
removeElements(bodies, idsToKeep);
removeElements(previews, idsToKeep); removeElements(previews, idsToKeep);
idsToKeep->clear(); idsToKeep->clear();
@ -760,8 +716,3 @@ void MessageDelegate::clearHelperWidget(const Models::FeedItem& data) const
} }
} }
} }
// void MessageDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
// {
//
// }

View File

@ -29,8 +29,6 @@
#include <QPushButton> #include <QPushButton>
#include <QProgressBar> #include <QProgressBar>
#include <QLabel> #include <QLabel>
#include <QTextEdit>
#include <QTextBrowser>
#include "shared/icons.h" #include "shared/icons.h"
#include "shared/global.h" #include "shared/global.h"
@ -59,6 +57,7 @@ public:
void endClearWidgets(); void endClearWidgets();
void beginClearWidgets(); void beginClearWidgets();
void leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const; void leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const;
bool isAnchorHovered(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const;
static int avatarHeight; static int avatarHeight;
static int margin; static int margin;
@ -66,6 +65,7 @@ public:
signals: signals:
void buttonPushed(const QString& messageId) const; void buttonPushed(const QString& messageId) const;
void invalidPath(const QString& messageId) const; void invalidPath(const QString& messageId) const;
void openLink(const QString& href) const;
protected: protected:
int paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const; int paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
@ -80,12 +80,14 @@ protected:
QProgressBar* getBar(const Models::FeedItem& data) const; QProgressBar* getBar(const Models::FeedItem& data) const;
QLabel* getStatusIcon(const Models::FeedItem& data) const; QLabel* getStatusIcon(const Models::FeedItem& data) const;
QLabel* getPencilIcon(const Models::FeedItem& data) const; QLabel* getPencilIcon(const Models::FeedItem& data) const;
QTextBrowser* getBody(const Models::FeedItem& data) const;
void clearHelperWidget(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; bool needToDrawAvatar(const QModelIndex& index, const Models::FeedItem& data, const QStyleOptionViewItem& option) const;
bool needToDrawSender(const QModelIndex& index, const Models::FeedItem& data) const; bool needToDrawSender(const QModelIndex& index, const Models::FeedItem& data) const;
QRect getHoveredMessageBodyRect(const QModelIndex& index, const Models::FeedItem& data, const QRect& sizeHint) const;
QString getAnchor(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const;
protected slots: protected slots:
void onButtonPushed() const; void onButtonPushed() const;
@ -98,7 +100,6 @@ private:
QFont bodyFont; QFont bodyFont;
QFont nickFont; QFont nickFont;
QFont dateFont; QFont dateFont;
QFontMetrics bodyMetrics;
QTextDocument* bodyRenderer; QTextDocument* bodyRenderer;
QFontMetrics nickMetrics; QFontMetrics nickMetrics;
QFontMetrics dateMetrics; QFontMetrics dateMetrics;
@ -111,7 +112,6 @@ private:
std::map<QString, QProgressBar*>* bars; std::map<QString, QProgressBar*>* bars;
std::map<QString, QLabel*>* statusIcons; std::map<QString, QLabel*>* statusIcons;
std::map<QString, QLabel*>* pencilIcons; std::map<QString, QLabel*>* pencilIcons;
std::map<QString, QTextBrowser*>* bodies;
std::map<QString, Preview*>* previews; std::map<QString, Preview*>* previews;
std::set<QString>* idsToKeep; std::set<QString>* idsToKeep;
bool clearingWidgets; bool clearingWidgets;