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 }