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 }