File indexing completed on 2024-12-22 04:46:04

0001 /*
0002    SPDX-FileCopyrightText: 2020 David Faure <faure@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "messageattachmentdelegatehelperbase.h"
0008 #include "delegateutils/messagedelegateutils.h"
0009 #include "rocketchataccount.h"
0010 #include "ruqola.h"
0011 #include "ruqolawidgets_selection_debug.h"
0012 
0013 #include <QAbstractTextDocumentLayout>
0014 #include <QDrag>
0015 #include <QListView>
0016 #include <QMimeData>
0017 #include <QPainter>
0018 #include <QRect>
0019 #include <QStyleOptionViewItem>
0020 #include <QToolTip>
0021 
0022 MessageAttachmentDelegateHelperBase::~MessageAttachmentDelegateHelperBase() = default;
0023 
0024 MessageAttachmentDelegateHelperBase::MessageAttachmentDelegateHelperBase(RocketChatAccount *account, QListView *view, TextSelectionImpl *textSelectionImpl)
0025     : MessageDelegateHelperBase(account, view, textSelectionImpl)
0026 {
0027 }
0028 
0029 bool MessageAttachmentDelegateHelperBase::handleMouseEvent(const MessageAttachment &msgAttach,
0030                                                            QMouseEvent *mouseEvent,
0031                                                            QRect attachmentsRect,
0032                                                            const QStyleOptionViewItem &option,
0033                                                            const QModelIndex &index)
0034 {
0035     switch (mouseEvent->type()) {
0036     case QEvent::MouseMove: {
0037         if (!mTextSelectionImpl->mightStartDrag()) {
0038             if (const auto *doc = documentDescriptionForIndex(convertAttachmentToDocumentDescriptionInfo(msgAttach, attachmentsRect.width()))) {
0039                 const QPoint pos = mouseEvent->pos();
0040                 const int charPos = charPosition(doc, msgAttach, attachmentsRect, pos, option);
0041                 if (charPos != -1) {
0042                     // QWidgetTextControl also has code to support isPreediting()/commitPreedit(), selectBlockOnTripleClick
0043                     mTextSelectionImpl->textSelection()->setEnd(index, charPos, msgAttach);
0044                     return true;
0045                 }
0046             }
0047         }
0048         break;
0049     }
0050     case QEvent::MouseButtonRelease: {
0051         qCDebug(RUQOLAWIDGETS_SELECTION_LOG) << "released";
0052         MessageDelegateUtils::setClipboardSelection(mTextSelectionImpl->textSelection());
0053         // Clicks on links
0054         if (!mTextSelectionImpl->textSelection()->hasSelection()) {
0055             if (const auto *doc = documentDescriptionForIndex(convertAttachmentToDocumentDescriptionInfo(msgAttach, attachmentsRect.width()))) {
0056                 const QPoint pos = mouseEvent->pos();
0057                 const QPoint mouseClickPos = adaptMousePosition(pos, msgAttach, attachmentsRect, option);
0058                 const QString link = doc->documentLayout()->anchorAt(mouseClickPos);
0059                 if (!link.isEmpty()) {
0060                     Q_EMIT mRocketChatAccount->openLinkRequested(link);
0061                     return true;
0062                 }
0063             }
0064         } else if (mTextSelectionImpl->mightStartDrag()) {
0065             // clicked into selection, didn't start drag, clear it (like kwrite and QTextEdit)
0066             mTextSelectionImpl->textSelection()->clear();
0067         }
0068         // don't return true here, we need to send mouse release events to other helpers (ex: click on image)
0069         break;
0070     }
0071     case QEvent::MouseButtonDblClick: {
0072         if (!mTextSelectionImpl->textSelection()->hasSelection()) {
0073             if (const auto *doc = documentDescriptionForIndex(convertAttachmentToDocumentDescriptionInfo(msgAttach, attachmentsRect.width()))) {
0074                 const QPoint pos = mouseEvent->pos();
0075                 const int charPos = charPosition(doc, msgAttach, attachmentsRect, pos, option);
0076                 qCDebug(RUQOLAWIDGETS_SELECTION_LOG) << "double-clicked at pos" << charPos;
0077                 if (charPos == -1) {
0078                     return false;
0079                 }
0080                 mTextSelectionImpl->textSelection()->selectWordUnderCursor(index, charPos, this, msgAttach);
0081                 return true;
0082             }
0083         }
0084         break;
0085     }
0086     case QEvent::MouseButtonPress: {
0087         mTextSelectionImpl->setMightStartDrag(false);
0088         mCurrentIndex = QModelIndex();
0089         if (const auto *doc = documentDescriptionForIndex(convertAttachmentToDocumentDescriptionInfo(msgAttach, attachmentsRect.width()))) {
0090             const QPoint pos = mouseEvent->pos();
0091             const int charPos = charPosition(doc, msgAttach, attachmentsRect, pos, option);
0092             qCDebug(RUQOLAWIDGETS_SELECTION_LOG) << "pressed at pos" << charPos;
0093             if (charPos == -1) {
0094                 return false;
0095             }
0096             // TODO fix mTextSelectionImpl->contains with attachment
0097             if (mTextSelectionImpl->textSelection()->contains(index, charPos) && doc->documentLayout()->hitTest(pos, Qt::ExactHit) != -1) {
0098                 mTextSelectionImpl->setMightStartDrag(true);
0099                 mCurrentIndex = index;
0100                 return true;
0101             }
0102 
0103             // QWidgetTextControl also has code to support selectBlockOnTripleClick, shift to extend selection
0104             // (look there if you want to add these things)
0105 
0106             mTextSelectionImpl->textSelection()->setStart(index, charPos, msgAttach);
0107             return true;
0108         } else {
0109             mTextSelectionImpl->textSelection()->clear();
0110         }
0111         break;
0112     }
0113     default:
0114         break;
0115     }
0116     return false;
0117 }
0118 
0119 bool MessageAttachmentDelegateHelperBase::maybeStartDrag(const MessageAttachment &msgAttach,
0120                                                          QMouseEvent *mouseEvent,
0121                                                          QRect attachmentsRect,
0122                                                          const QStyleOptionViewItem &option,
0123                                                          const QModelIndex &index)
0124 {
0125     if (!mTextSelectionImpl->mightStartDrag() || index != mCurrentIndex || !attachmentsRect.contains(mouseEvent->pos())) {
0126         return false;
0127     }
0128 
0129     auto mimeData = new QMimeData;
0130     mimeData->setUrls({mRocketChatAccount->attachmentUrlFromLocalCache(msgAttach.link())});
0131 
0132     auto drag = new QDrag(const_cast<QWidget *>(option.widget));
0133     drag->setMimeData(mimeData);
0134     drag->exec(Qt::CopyAction);
0135 
0136     return true;
0137 }
0138 
0139 int MessageAttachmentDelegateHelperBase::charPosition(const QTextDocument *doc,
0140                                                       const MessageAttachment &msgAttach,
0141                                                       QRect attachmentsRect,
0142                                                       const QPoint &pos,
0143                                                       const QStyleOptionViewItem &option)
0144 {
0145     const QPoint relativePos = adaptMousePosition(pos, msgAttach, attachmentsRect, option);
0146     const int charPos = doc->documentLayout()->hitTest(relativePos, Qt::FuzzyHit);
0147     return charPos;
0148 }
0149 
0150 void MessageAttachmentDelegateHelperBase::drawTitle(const MessageAttachment &msgAttach, QPainter *painter)
0151 {
0152 }
0153 
0154 void MessageAttachmentDelegateHelperBase::drawDescription(const MessageAttachment &msgAttach,
0155                                                           QRect descriptionRect,
0156                                                           QPainter *painter,
0157                                                           int topPos,
0158                                                           const QModelIndex &index,
0159                                                           const QStyleOptionViewItem &option) const
0160 {
0161     auto *doc = documentDescriptionForIndex(convertAttachmentToDocumentDescriptionInfo(msgAttach, descriptionRect.width()));
0162     if (!doc) {
0163         return;
0164     }
0165 
0166     MessageDelegateUtils::drawSelection(doc, descriptionRect, topPos, painter, index, option, mTextSelectionImpl->textSelection(), msgAttach, {});
0167 }
0168 
0169 QTextDocument *MessageAttachmentDelegateHelperBase::documentForAttachement(const MessageAttachment &msgAttach) const
0170 {
0171     return documentDescriptionForIndex(convertAttachmentToDocumentDescriptionInfo(msgAttach, -1));
0172 }
0173 
0174 MessageDelegateHelperBase::DocumentDescriptionInfo MessageAttachmentDelegateHelperBase::convertAttachmentToDocumentTitleInfo(const MessageAttachment &msgAttach,
0175                                                                                                                              int width) const
0176 {
0177     return convertAttachmentToDocumentTypeInfo(MessageAttachmentDelegateHelperBase::DocumentIdType::Title, msgAttach, width);
0178 }
0179 
0180 MessageDelegateHelperBase::DocumentDescriptionInfo
0181 MessageAttachmentDelegateHelperBase::convertAttachmentToDocumentDescriptionInfo(const MessageAttachment &msgAttach, int width) const
0182 {
0183     return convertAttachmentToDocumentTypeInfo(MessageAttachmentDelegateHelperBase::DocumentIdType::Description, msgAttach, width);
0184 }
0185 
0186 MessageDelegateHelperBase::DocumentDescriptionInfo
0187 MessageAttachmentDelegateHelperBase::convertAttachmentToDocumentTypeInfo(DocumentIdType type, const MessageAttachment &msgAttach, int width) const
0188 {
0189     MessageDelegateHelperBase::DocumentDescriptionInfo info;
0190     info.documentId = documendIdPrefix(type) + msgAttach.attachmentId();
0191     info.description = msgAttach.description();
0192     info.width = width;
0193     return info;
0194 }
0195 
0196 QString MessageAttachmentDelegateHelperBase::documendIdPrefix(DocumentIdType type) const
0197 {
0198     switch (type) {
0199     case MessageAttachmentDelegateHelperBase::DocumentIdType::Unknown:
0200         return {};
0201     case MessageAttachmentDelegateHelperBase::DocumentIdType::Title:
0202         return QLatin1String("title_");
0203     case MessageAttachmentDelegateHelperBase::DocumentIdType::Description:
0204         return QLatin1String("description_");
0205     }
0206     return {};
0207 }
0208 
0209 bool MessageAttachmentDelegateHelperBase::handleHelpEvent(QHelpEvent *helpEvent,
0210                                                           QRect messageRect,
0211                                                           const MessageAttachment &msgAttach,
0212                                                           const QStyleOptionViewItem &option)
0213 {
0214     if (helpEvent->type() != QEvent::ToolTip) {
0215         return false;
0216     }
0217 
0218     const auto *doc = documentDescriptionForIndex(convertAttachmentToDocumentDescriptionInfo(msgAttach, messageRect.width()));
0219     if (!doc) {
0220         return false;
0221     }
0222 
0223     const QPoint relativePos = adaptMousePosition(helpEvent->pos(), msgAttach, messageRect, option);
0224     QString formattedTooltip;
0225     if (MessageDelegateUtils::generateToolTip(doc, relativePos, formattedTooltip)) {
0226         QToolTip::showText(helpEvent->globalPos(), formattedTooltip, mListView);
0227         return true;
0228     }
0229     return true;
0230 }
0231 
0232 QString MessageAttachmentDelegateHelperBase::urlAt(const QStyleOptionViewItem &option, const MessageAttachment &msgAttach, QRect attachmentsRect, QPoint pos)
0233 {
0234     auto document = documentDescriptionForIndex(convertAttachmentToDocumentDescriptionInfo(msgAttach, attachmentsRect.width()));
0235     if (!document) {
0236         return {};
0237     }
0238     const QPoint relativePos = adaptMousePosition(pos, msgAttach, attachmentsRect, option);
0239     return document->documentLayout()->anchorAt(relativePos);
0240 }
0241 
0242 bool MessageAttachmentDelegateHelperBase::contextMenu(const QPoint &pos,
0243                                                       const QPoint &globalPos,
0244                                                       const MessageAttachment &msgAttach,
0245                                                       QRect attachmentsRect,
0246                                                       const QStyleOptionViewItem &option,
0247                                                       QMenu *menu)
0248 {
0249     Q_UNUSED(msgAttach);
0250     Q_UNUSED(attachmentsRect);
0251     Q_UNUSED(pos);
0252     Q_UNUSED(option);
0253     Q_UNUSED(globalPos);
0254     Q_UNUSED(menu);
0255     return false;
0256 }