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"