File indexing completed on 2024-12-08 04:34:13
0001 /* 0002 SPDX-FileCopyrightText: 2020-2024 Laurent Montel <montel@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "messagedelegateutils.h" 0008 #include "delegateutils/textselection.h" 0009 #include "model/messagesmodel.h" 0010 0011 #include <QApplication> 0012 #include <QClipboard> 0013 #include <QPainter> 0014 #include <QTextFrame> 0015 #include <QTextFrameFormat> 0016 #include <QTextStream> 0017 0018 std::unique_ptr<QTextDocument> MessageDelegateUtils::createTextDocument(bool useItalic, const QString &text, int width) 0019 { 0020 std::unique_ptr<QTextDocument> doc(new QTextDocument); 0021 doc->setHtml(text); 0022 doc->setTextWidth(width); 0023 QFont font = qApp->font(); 0024 font.setItalic(useItalic); 0025 doc->setDefaultFont(font); 0026 QTextFrame *frame = doc->frameAt(0); 0027 QTextFrameFormat frameFormat = frame->frameFormat(); 0028 frameFormat.setMargin(0); 0029 frame->setFrameFormat(frameFormat); 0030 return doc; 0031 } 0032 0033 bool MessageDelegateUtils::generateToolTip(const QTextDocument *doc, const QPoint &pos, QString &formattedTooltip) 0034 { 0035 const auto format = doc->documentLayout()->formatAt(pos); 0036 const auto tooltip = format.property(QTextFormat::TextToolTip).toString(); 0037 const auto href = format.property(QTextFormat::AnchorHref).toString(); 0038 if (tooltip.isEmpty() && (href.isEmpty() || href.startsWith(QLatin1String("ruqola:/")))) { 0039 return false; 0040 } 0041 0042 generateToolTip(tooltip, href, formattedTooltip); 0043 return true; 0044 } 0045 0046 void MessageDelegateUtils::generateToolTip(const QString &toolTip, const QString &href, QString &formattedTooltip) 0047 { 0048 QTextStream stream(&formattedTooltip); 0049 auto addLine = [&](const QString &line) { 0050 if (!line.isEmpty()) { 0051 stream << QLatin1String("<p>") << line << QLatin1String("</p>"); 0052 } 0053 }; 0054 0055 stream << QLatin1String("<qt>"); 0056 addLine(toolTip); 0057 addLine(href); 0058 stream << QLatin1String("</qt>"); 0059 } 0060 0061 bool MessageDelegateUtils::useItalicsForMessage(const QModelIndex &index) 0062 { 0063 const auto messageType = index.data(MessagesModel::MessageType).value<Message::MessageType>(); 0064 const bool isSystemMessage = 0065 messageType == Message::System && index.data(MessagesModel::SystemMessageType).toString() != QStringLiteral("jitsi_call_started"); 0066 return isSystemMessage; 0067 } 0068 0069 bool MessageDelegateUtils::pendingMessage(const QModelIndex &index) 0070 { 0071 return index.data(MessagesModel::PendingMessage).toBool(); 0072 } 0073 0074 QVector<QAbstractTextDocumentLayout::Selection> MessageDelegateUtils::selection(TextSelection *selection, 0075 QTextDocument *doc, 0076 const QModelIndex &index, 0077 const QStyleOptionViewItem &option, 0078 const MessageAttachment &msgAttach, 0079 const MessageUrl &msgUrl, 0080 bool isAMessage) 0081 { 0082 QVector<QAbstractTextDocumentLayout::Selection> selections; 0083 const QTextCursor selectionTextCursor = selection->selectionForIndex(index, doc, msgAttach, msgUrl); 0084 if (!selectionTextCursor.isNull()) { 0085 QTextCharFormat selectionFormat; 0086 selectionFormat.setBackground(option.palette.brush(QPalette::Highlight)); 0087 selectionFormat.setForeground(option.palette.brush(QPalette::HighlightedText)); 0088 selections.append({selectionTextCursor, selectionFormat}); 0089 } 0090 if (isAMessage && (MessageDelegateUtils::useItalicsForMessage(index) || MessageDelegateUtils::pendingMessage(index))) { 0091 QTextCursor cursor(doc); 0092 cursor.select(QTextCursor::Document); 0093 QTextCharFormat format; 0094 format.setForeground(Qt::gray); // TODO use color from theme. 0095 cursor.mergeCharFormat(format); 0096 } 0097 return selections; 0098 } 0099 0100 void MessageDelegateUtils::drawSelection(QTextDocument *doc, 0101 QRect rect, 0102 int top, 0103 QPainter *painter, 0104 const QModelIndex &index, 0105 const QStyleOptionViewItem &option, 0106 TextSelection *selection, 0107 const MessageAttachment &msgAttach, 0108 const MessageUrl &msgUrl, 0109 bool isAMessage) 0110 { 0111 painter->save(); 0112 painter->translate(rect.left(), top); 0113 const QRect clip(0, 0, rect.width(), rect.height()); 0114 0115 QAbstractTextDocumentLayout::PaintContext ctx; 0116 if (selection) { 0117 const QVector<QAbstractTextDocumentLayout::Selection> selections = 0118 MessageDelegateUtils::selection(selection, doc, index, option, msgAttach, msgUrl, isAMessage); 0119 // Same as pDoc->drawContents(painter, clip) but we also set selections 0120 ctx.selections = selections; 0121 if (clip.isValid()) { 0122 painter->setClipRect(clip); 0123 ctx.clip = clip; 0124 } 0125 } 0126 doc->documentLayout()->draw(painter, ctx); 0127 painter->restore(); 0128 } 0129 0130 void MessageDelegateUtils::setClipboardSelection(TextSelection *selection) 0131 { 0132 QClipboard *clipboard = QGuiApplication::clipboard(); 0133 if (selection->hasSelection() && clipboard->supportsSelection()) { 0134 const QString text = selection->selectedText(TextSelection::Text); 0135 clipboard->setText(text, QClipboard::Selection); 0136 } 0137 } 0138 0139 QSizeF MessageDelegateUtils::dprAwareSize(const QPixmap &pixmap) 0140 { 0141 if (pixmap.isNull()) { 0142 return {0, 0}; // prevent division-by-zero 0143 } 0144 return pixmap.size() / pixmap.devicePixelRatioF(); 0145 } 0146 0147 qreal MessageDelegateUtils::basicMargin() 0148 { 0149 return 8; 0150 } 0151 0152 QSize MessageDelegateUtils::timeStampSize(const QString &timeStampText, const QStyleOptionViewItem &option) 0153 { 0154 // This gives incorrect results (too small bounding rect), no idea why! 0155 // const QSize timeSize = painter->fontMetrics().boundingRect(timeStampText).size(); 0156 return {option.fontMetrics.horizontalAdvance(timeStampText), option.fontMetrics.height()}; 0157 } 0158 0159 QSize MessageDelegateUtils::textSizeHint(QTextDocument *doc, qreal *pBaseLine) 0160 { 0161 if (!doc) { 0162 return {}; 0163 } 0164 const QSize size(doc->idealWidth(), doc->size().height()); // do the layouting, required by lineAt(0) below 0165 0166 const QTextLine &line = doc->firstBlock().layout()->lineAt(0); 0167 *pBaseLine = line.y() + line.ascent(); // relative 0168 0169 return size; 0170 } 0171 0172 bool MessageDelegateUtils::showIgnoreMessages(const QModelIndex &index) 0173 { 0174 const bool isIgnoredMessage = index.data(MessagesModel::Ignored).toBool(); 0175 const bool isDirectMessage = index.data(MessagesModel::DirectChannels).toBool(); 0176 return isIgnoredMessage && !isDirectMessage; 0177 }