diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp index bf1da61..0758dd9 100644 --- a/ui/widgets/messageline/feedview.cpp +++ b/ui/widgets/messageline/feedview.cpp @@ -51,7 +51,8 @@ FeedView::FeedView(QWidget* parent): progress(), dividerFont(), dividerMetrics(dividerFont), - mousePressed(false) + mousePressed(false), + anchorHovered(false) { horizontalScrollBar()->setRange(0, 0); verticalScrollBar()->setSingleStep(approximateSingleMessageHeight); @@ -408,6 +409,18 @@ void FeedView::verticalScrollbarValueChanged(int value) 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) { if (!isVisible()) { @@ -418,6 +431,22 @@ void FeedView::mouseMoveEvent(QMouseEvent* event) //qDebug() << 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(itemDelegate()); + if (rect.contains(point)) { + setAnchorHovered(del->isAnchorHovered(point, index, rect)); + } else { + setAnchorHovered(false); + } + } else { + setAnchorHovered(false); + } + } } void FeedView::mousePressEvent(QMouseEvent* event) @@ -487,6 +516,7 @@ void FeedView::setItemDelegate(QAbstractItemDelegate* delegate) elementMargin = MessageDelegate::margin; connect(del, &MessageDelegate::buttonPushed, this, &FeedView::onMessageButtonPushed); connect(del, &MessageDelegate::invalidPath, this, &FeedView::onMessageInvalidPath); + connect(del, &MessageDelegate::openLink, &QDesktopServices::openUrl); } else { specialDelegate = false; elementMargin = 0; diff --git a/ui/widgets/messageline/feedview.h b/ui/widgets/messageline/feedview.h index c757986..7a00dd7 100644 --- a/ui/widgets/messageline/feedview.h +++ b/ui/widgets/messageline/feedview.h @@ -20,6 +20,8 @@ #define FEEDVIEW_H #include +#include +#include #include #include @@ -76,6 +78,7 @@ private: bool tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* model, uint32_t totalHeight); void positionProgress(); void drawDateDevider(int top, const QDateTime& date, QPainter& painter); + void setAnchorHovered(bool hovered); private: struct Hint { @@ -96,6 +99,7 @@ private: QFont dividerFont; QFontMetrics dividerMetrics; bool mousePressed; + bool anchorHovered; static const std::set geometryChangingRoles; diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp index c787cfa..22a575b 100644 --- a/ui/widgets/messageline/messagedelegate.cpp +++ b/ui/widgets/messageline/messagedelegate.cpp @@ -44,7 +44,6 @@ MessageDelegate::MessageDelegate(QObject* parent): bodyFont(), nickFont(), dateFont(), - bodyMetrics(bodyFont), bodyRenderer(new QTextDocument()), nickMetrics(nickFont), dateMetrics(dateFont), @@ -55,7 +54,6 @@ MessageDelegate::MessageDelegate(QObject* parent): bars(new std::map()), statusIcons(new std::map()), pencilIcons(new std::map()), - bodies(new std::map()), previews(new std::map()), idsToKeep(new std::set()), clearingWidgets(false) @@ -88,10 +86,6 @@ MessageDelegate::~MessageDelegate() delete pair.second; } - for (const std::pair& pair: *bodies){ - delete pair.second; - } - for (const std::pair& pair: *previews){ delete pair.second; } @@ -101,7 +95,6 @@ MessageDelegate::~MessageDelegate() delete idsToKeep; delete buttons; delete bars; - delete bodies; delete previews; delete bodyRenderer; } @@ -366,66 +359,83 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel 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); Models::FeedItem data = qvariant_cast(vi); if (data.text.size() > 0) { - 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)); + QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint); if (localHint.contains(point)) { - qDebug() << "MESSAGE CLICKED"; QPoint translated = point - localHint.topLeft(); - bodyRenderer->setPlainText(data.text); + bodyRenderer->setHtml(Shared::processMessageBody(data.text)); bodyRenderer->setTextWidth(localHint.size().width()); - int pos = bodyRenderer->documentLayout()->hitTest(translated, Qt::FuzzyHit); - QTextBlock block = bodyRenderer->findBlock(pos); - QString text = block.text(); - if (text.size() > 0) { - qDebug() << text; - } + return bodyRenderer->documentLayout()->anchorAt(translated); } } + + 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) @@ -452,7 +462,6 @@ void MessageDelegate::initializeFonts(const QFont& font) } bodyFont.setKerning(false); - bodyMetrics = QFontMetrics(bodyFont); nickMetrics = QFontMetrics(nickFont); dateMetrics = QFontMetrics(dateFont); @@ -636,36 +645,6 @@ QLabel * MessageDelegate::getPencilIcon(const Models::FeedItem& data) const return result; } -QTextBrowser * MessageDelegate::getBody(const Models::FeedItem& data) const -{ - std::map::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 void removeElements(std::map* elements, std::set* idsToKeep) { std::set toRemove; @@ -691,26 +670,6 @@ int MessageDelegate::paintBody(const Models::FeedItem& data, QPainter* painter, painter->restore(); QSize bodySize(std::ceil(bodyRenderer->idealWidth()), std::ceil(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(); } @@ -723,8 +682,6 @@ void MessageDelegate::beginClearWidgets() clearingWidgets = true; } - - void MessageDelegate::endClearWidgets() { if (clearingWidgets) { @@ -732,7 +689,6 @@ void MessageDelegate::endClearWidgets() removeElements(bars, idsToKeep); removeElements(statusIcons, idsToKeep); removeElements(pencilIcons, idsToKeep); - removeElements(bodies, idsToKeep); removeElements(previews, idsToKeep); 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 -// { -// -// } diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h index 9333d45..d871a52 100644 --- a/ui/widgets/messageline/messagedelegate.h +++ b/ui/widgets/messageline/messagedelegate.h @@ -29,8 +29,6 @@ #include #include #include -#include -#include #include "shared/icons.h" #include "shared/global.h" @@ -59,6 +57,7 @@ public: void endClearWidgets(); void beginClearWidgets(); 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 margin; @@ -66,6 +65,7 @@ public: signals: void buttonPushed(const QString& messageId) const; void invalidPath(const QString& messageId) const; + void openLink(const QString& href) const; protected: int paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const; @@ -80,11 +80,13 @@ protected: QProgressBar* getBar(const Models::FeedItem& data) const; QLabel* getStatusIcon(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; bool needToDrawAvatar(const QModelIndex& index, const Models::FeedItem& data, const QStyleOptionViewItem& option) 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: void onButtonPushed() const; @@ -98,7 +100,6 @@ private: QFont bodyFont; QFont nickFont; QFont dateFont; - QFontMetrics bodyMetrics; QTextDocument* bodyRenderer; QFontMetrics nickMetrics; QFontMetrics dateMetrics; @@ -111,7 +112,6 @@ private: std::map* bars; std::map* statusIcons; std::map* pencilIcons; - std::map* bodies; std::map* previews; std::set* idsToKeep; bool clearingWidgets;