File indexing completed on 2024-05-26 05:05:28

0001 /*
0002    SPDX-FileCopyrightText: 2023-2024 Laurent Montel <montel.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "moderationreportinfodelegate.h"
0008 #include "common/delegatepaintutil.h"
0009 #include "delegateutils/messagedelegateutils.h"
0010 #include "delegateutils/textselectionimpl.h"
0011 #include "model/moderationreportinfomodel.h"
0012 #include "rocketchataccount.h"
0013 #include "ruqola.h"
0014 #include <QAbstractItemView>
0015 #include <QPainter>
0016 #include <QToolTip>
0017 
0018 ModerationReportInfoDelegate::ModerationReportInfoDelegate(RocketChatAccount *account, QListView *view, QObject *parent)
0019     : MessageListDelegateBase{view, parent}
0020     , mRocketChatAccount(account)
0021 {
0022 }
0023 
0024 ModerationReportInfoDelegate::~ModerationReportInfoDelegate() = default;
0025 
0026 void ModerationReportInfoDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
0027 {
0028     painter->save();
0029     drawBackground(painter, option, index);
0030 
0031     const Layout layout = doLayout(option, index);
0032 
0033 #if 0
0034     if (!layout.sameAccountRoomAsPreviousMessage) {
0035         drawAccountRoomInfo(painter, index, option);
0036     }
0037 #endif
0038 
0039     // Draw the pixmap
0040     if (!layout.avatarPixmap.isNull()) {
0041         painter->drawPixmap(layout.avatarPos, layout.avatarPixmap);
0042     }
0043 
0044     // Draw the sender
0045     const QFont oldFont = painter->font();
0046     painter->setFont(layout.senderFont);
0047     painter->drawText(layout.senderRect.x(), layout.baseLine, layout.senderText);
0048     painter->setFont(oldFont);
0049 
0050     // Draw Text
0051     if (layout.textRect.isValid()) {
0052         auto *doc = documentForModelIndex(index, layout.textRect.width());
0053         if (doc) {
0054             MessageDelegateUtils::drawSelection(doc,
0055                                                 layout.textRect,
0056                                                 layout.textRect.top(),
0057                                                 painter,
0058                                                 index,
0059                                                 option,
0060                                                 mTextSelectionImpl->textSelection(),
0061                                                 {},
0062                                                 {},
0063                                                 false);
0064         }
0065     }
0066 
0067     // Timestamp
0068     DelegatePaintUtil::drawLighterText(painter, layout.timeStampText, layout.timeStampPos);
0069 
0070     // debug (TODO remove it for release)
0071     // painter->drawRect(option.rect.adjusted(0, 0, -1, -1));
0072 
0073     painter->restore();
0074 }
0075 
0076 QSize ModerationReportInfoDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
0077 {
0078     const QString identifier = cacheIdentifier(index);
0079     auto it = mSizeHintCache.find(identifier);
0080     if (it != mSizeHintCache.end()) {
0081         return it->value;
0082     }
0083 
0084     // Note: option.rect in this method is huge (as big as the viewport)
0085     const Layout layout = doLayout(option, index);
0086     int additionalHeight = 0;
0087     // A little bit of margin below the very last item, it just looks better
0088     if (index.row() == index.model()->rowCount() - 1) {
0089         additionalHeight += 4;
0090     }
0091 
0092     // contents is date + text
0093     const int contentsHeight = layout.textRect.y() + layout.textRect.height() - option.rect.y();
0094     const int senderAndAvatarHeight = qMax<int>(layout.senderRect.y() + layout.senderRect.height() - option.rect.y(),
0095                                                 layout.avatarPos.y() + MessageDelegateUtils::dprAwareSize(layout.avatarPixmap).height() - option.rect.y());
0096 
0097     //    qDebug() << "senderAndAvatarHeight" << senderAndAvatarHeight << "text" << layout.textRect.height() << "total contents" << contentsHeight;
0098     //    qDebug() << "=> returning" << qMax(senderAndAvatarHeight, contentsHeight) + additionalHeight;
0099 
0100     const QSize size = {option.rect.width(), qMax(senderAndAvatarHeight, contentsHeight) + additionalHeight};
0101     mSizeHintCache.insert(identifier, size);
0102     return size;
0103 }
0104 
0105 // text AccountName/room
0106 // [margin] <pixmap> [margin] <sender> [margin] <text message> [margin] <date/time> [margin]
0107 ModerationReportInfoDelegate::Layout ModerationReportInfoDelegate::doLayout(const QStyleOptionViewItem &option, const QModelIndex &index) const
0108 {
0109     ModerationReportInfoDelegate::Layout layout;
0110     const QString userName = index.data(ModerationReportInfoModel::ReportUserName).toString();
0111     const int margin = MessageDelegateUtils::basicMargin();
0112     layout.senderText = QLatin1Char('@') + userName;
0113     layout.senderFont = option.font;
0114     layout.senderFont.setBold(true);
0115 
0116     // Timestamp
0117     layout.timeStampText = index.data(ModerationReportInfoModel::DateTime).toString();
0118 
0119     // Message (using the rest of the available width)
0120     const int iconSize = option.widget->style()->pixelMetric(QStyle::PM_ButtonIconSize);
0121     const QFontMetricsF senderFontMetrics(layout.senderFont);
0122     const qreal senderAscent = senderFontMetrics.ascent();
0123     const QSizeF senderTextSize = senderFontMetrics.size(Qt::TextSingleLine, layout.senderText);
0124     // TODO add pixmap
0125 #if 0
0126     // Resize pixmap TODO cache ?
0127     const auto pix = index.data(NotificationHistoryModel::Pixmap).value<QPixmap>();
0128     if (!pix.isNull()) {
0129         const QPixmap scaledPixmap = pix.scaled(senderTextSize.height(), senderTextSize.height(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
0130         layout.avatarPixmap = scaledPixmap;
0131     }
0132 #endif
0133 
0134     const int senderX = option.rect.x() + MessageDelegateUtils::dprAwareSize(layout.avatarPixmap).width() + 2 * margin;
0135 
0136     const int textLeft = senderX + senderTextSize.width() + margin;
0137     const QSize timeSize = MessageDelegateUtils::timeStampSize(layout.timeStampText, option);
0138     const int widthAfterMessage = iconSize + margin + timeSize.width() + margin / 2;
0139     const int maxWidth = qMax(30, option.rect.width() - textLeft - widthAfterMessage);
0140 
0141     layout.baseLine = 0;
0142     const QSize textSize = textSizeHint(index, maxWidth, option, &layout.baseLine);
0143 
0144     const int textVMargin = 3; // adjust this for "compactness"
0145     QRect usableRect = option.rect;
0146 
0147     layout.textRect = QRect(textLeft, usableRect.top() + textVMargin, maxWidth, textSize.height() + textVMargin);
0148     layout.baseLine += layout.textRect.top(); // make it absolute
0149 
0150     layout.timeStampPos = QPoint(option.rect.width() - timeSize.width() - margin / 2, layout.baseLine);
0151 
0152     layout.senderRect = QRectF(senderX, layout.baseLine - senderAscent, senderTextSize.width(), senderTextSize.height());
0153 
0154     // Align top of avatar with top of sender rect
0155     layout.avatarPos = QPointF(option.rect.x() + margin, layout.senderRect.y());
0156     return layout;
0157 }
0158 
0159 QString ModerationReportInfoDelegate::cacheIdentifier(const QModelIndex &index) const
0160 {
0161     const QString identifier = index.data(ModerationReportInfoModel::ReportIdentifier).toString();
0162     Q_ASSERT(!identifier.isEmpty());
0163     return identifier;
0164 }
0165 
0166 QTextDocument *ModerationReportInfoDelegate::documentForModelIndex(const QModelIndex &index, int width) const
0167 {
0168     Q_ASSERT(index.isValid());
0169     const QString messageId = cacheIdentifier(index);
0170     const QString messageStr = index.data(ModerationReportInfoModel::Message).toString();
0171     return documentForDelegate(mRocketChatAccount, messageId, messageStr, width);
0172 }
0173 
0174 bool ModerationReportInfoDelegate::helpEvent(QHelpEvent *helpEvent, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index)
0175 {
0176     if (!helpEvent || !view || !index.isValid()) {
0177         return QItemDelegate::helpEvent(helpEvent, view, option, index);
0178     }
0179 
0180     if (helpEvent->type() != QEvent::ToolTip) {
0181         return false;
0182     }
0183 
0184     const Layout layout = doLayout(option, index);
0185     const auto *doc = documentForModelIndex(index, layout.textRect.width());
0186     if (!doc) {
0187         return false;
0188     }
0189 #if 0
0190     const QPoint helpEventPos{helpEvent->pos()};
0191     if (layout.senderRect.contains(helpEventPos)) {
0192         auto account = rocketChatAccount(index);
0193         if (account) {
0194             const QString senderName = index.data(ModerationReportInfoModel::SenderName).toString();
0195             QString tooltip = senderName;
0196             if (account->useRealName() && !tooltip.isEmpty()) {
0197                 const QString senderUserName = index.data(ModerationReportInfoModel::SenderUserName).toString();
0198                 tooltip = QLatin1Char('@') + senderUserName;
0199             }
0200             if (!tooltip.isEmpty()) {
0201                 QToolTip::showText(helpEvent->globalPos(), tooltip, view);
0202                 return true;
0203             }
0204         }
0205     }
0206 #endif
0207     const QPoint relativePos = adaptMousePosition(helpEvent->pos(), layout.textRect, option);
0208     QString formattedTooltip;
0209     if (MessageDelegateUtils::generateToolTip(doc, relativePos, formattedTooltip)) {
0210         QToolTip::showText(helpEvent->globalPos(), formattedTooltip, view);
0211         return true;
0212     }
0213     return true;
0214 }
0215 
0216 QPoint ModerationReportInfoDelegate::adaptMousePosition(const QPoint &pos, QRect textRect, const QStyleOptionViewItem &option)
0217 {
0218     Q_UNUSED(option);
0219     const QPoint relativePos = pos - textRect.topLeft();
0220     return relativePos;
0221 }
0222 
0223 bool ModerationReportInfoDelegate::mouseEvent(QEvent *event, const QStyleOptionViewItem &option, const QModelIndex &index)
0224 {
0225     const QEvent::Type eventType = event->type();
0226     if (eventType == QEvent::MouseButtonRelease) {
0227         auto mev = static_cast<QMouseEvent *>(event);
0228         const Layout layout = doLayout(option, index);
0229         if (handleMouseEvent(mev, layout.textRect, option, index)) {
0230             return true;
0231         }
0232     } else if (eventType == QEvent::MouseButtonPress || eventType == QEvent::MouseMove || eventType == QEvent::MouseButtonDblClick) {
0233         auto mev = static_cast<QMouseEvent *>(event);
0234         if (mev->buttons() & Qt::LeftButton) {
0235             const Layout layout = doLayout(option, index);
0236             if (handleMouseEvent(mev, layout.textRect, option, index)) {
0237                 return true;
0238             }
0239         }
0240     }
0241     return false;
0242 }
0243 
0244 bool ModerationReportInfoDelegate::maybeStartDrag(QMouseEvent *event, const QStyleOptionViewItem &option, const QModelIndex &index)
0245 {
0246     const Layout layout = doLayout(option, index);
0247     if (MessageListDelegateBase::maybeStartDrag(event, layout.textRect, option, index)) {
0248         return true;
0249     }
0250     return false;
0251 }
0252 
0253 RocketChatAccount *ModerationReportInfoDelegate::rocketChatAccount(const QModelIndex &index) const
0254 {
0255     Q_UNUSED(index);
0256     return mRocketChatAccount;
0257 }
0258 
0259 #include "moc_moderationreportinfodelegate.cpp"