Warning, file /network/ruqola/src/widgets/discussions/discussion/listdiscussiondelegate.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 SPDX-FileCopyrightText: 2020-2024 Laurent Montel <montel@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 #include "listdiscussiondelegate.h" 0007 #include "config-ruqola.h" 0008 #if USE_SIZEHINT_CACHE_SUPPORT 0009 #include "ruqola_sizehint_cache_debug.h" 0010 #endif 0011 #include <KColorScheme> 0012 #include <KLocalizedString> 0013 #include <QAbstractItemView> 0014 #include <QPainter> 0015 #include <QToolTip> 0016 0017 #include "colorsandmessageviewstyle.h" 0018 #include "common/delegatepaintutil.h" 0019 #include "delegateutils/messagedelegateutils.h" 0020 #include "delegateutils/textselectionimpl.h" 0021 #include "misc/avatarcachemanager.h" 0022 #include "model/discussionsmodel.h" 0023 #include "rocketchataccount.h" 0024 0025 ListDiscussionDelegate::ListDiscussionDelegate(QListView *view, RocketChatAccount *account, QObject *parent) 0026 : MessageListDelegateBase(view, parent) 0027 , mRocketChatAccount(account) 0028 , mAvatarCacheManager(new AvatarCacheManager(Utils::AvatarType::User, this)) 0029 { 0030 mAvatarCacheManager->setCurrentRocketChatAccount(mRocketChatAccount); 0031 } 0032 0033 ListDiscussionDelegate::~ListDiscussionDelegate() = default; 0034 0035 // [date] 0036 // text 0037 // number of discussion + last date 0038 // add text for opening dicussion. 0039 void ListDiscussionDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const 0040 { 0041 painter->save(); 0042 0043 const Layout layout = doLayout(option, index); 0044 0045 // Draw the pixmap 0046 if (!layout.avatarPixmap.isNull()) { 0047 #if USE_ROUNDED_RECT_PIXMAP 0048 DelegatePaintUtil::createClipRoundedRectangle(painter, QRectF(layout.avatarPos, layout.avatarPixmap.size()), layout.avatarPos, layout.avatarPixmap); 0049 #else 0050 painter->drawPixmap(layout.avatarPos, layout.avatarPixmap); 0051 #endif 0052 } 0053 0054 // Draw the sender 0055 const QFont oldFont = painter->font(); 0056 painter->setFont(layout.senderFont); 0057 painter->drawText(layout.senderRect.x(), layout.baseLine, layout.senderText); 0058 painter->setFont(oldFont); 0059 0060 // Draw Text 0061 if (layout.textRect.isValid()) { 0062 auto *doc = documentForModelIndex(index, layout.textRect.width()); 0063 if (doc) { 0064 MessageDelegateUtils::drawSelection(doc, 0065 layout.textRect, 0066 layout.textRect.top(), 0067 painter, 0068 index, 0069 option, 0070 mTextSelectionImpl->textSelection(), 0071 {}, 0072 {}, 0073 false); 0074 } 0075 } 0076 0077 // Draw the number of message + timestamp (below the sender) 0078 const QString messageStr = i18np("%1 message", "%1 messages", layout.numberOfMessages) + QLatin1Char(' ') + layout.lastMessageTimeText; 0079 DelegatePaintUtil::drawLighterText(painter, messageStr, QPoint(layout.textRect.left(), layout.lastMessageTimeY + painter->fontMetrics().ascent())); 0080 0081 const QString discussionsText = i18n("Open Discussion"); 0082 painter->setPen(ColorsAndMessageViewStyle::self().schemeView().foreground(KColorScheme::LinkText).color()); 0083 painter->drawText(layout.textRect.x(), layout.openDiscussionTextY + painter->fontMetrics().ascent(), discussionsText); 0084 0085 // debug 0086 // painter->drawRect(option.rect.adjusted(0, 0, -1, -1)); 0087 painter->restore(); 0088 } 0089 0090 QSize ListDiscussionDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const 0091 { 0092 #if USE_SIZEHINT_CACHE_SUPPORT 0093 const QString identifier = cacheIdentifier(index); 0094 auto it = mSizeHintCache.find(identifier); 0095 if (it != mSizeHintCache.end()) { 0096 const QSize result = it->value; 0097 qCDebug(RUQOLA_SIZEHINT_CACHE_LOG) << "ListDiscussionDelegate: SizeHint found in cache: " << result; 0098 return result; 0099 } 0100 #endif 0101 // Note: option.rect in this method is huge (as big as the viewport) 0102 const Layout layout = doLayout(option, index); 0103 0104 int additionalHeight = 0; 0105 // A little bit of margin below the very last item, it just looks better 0106 if (index.row() == index.model()->rowCount() - 1) { 0107 additionalHeight += 4; 0108 } 0109 0110 // contents is date + text 0111 const int contentsHeight = layout.openDiscussionTextY + layout.textRect.height() - option.rect.y(); 0112 const int senderAndAvatarHeight = qMax<int>(layout.senderRect.y() + layout.senderRect.height() - option.rect.y(), 0113 layout.avatarPos.y() + MessageDelegateUtils::dprAwareSize(layout.avatarPixmap).height() - option.rect.y()); 0114 0115 // qDebug() << "senderAndAvatarHeight" << senderAndAvatarHeight << "text" << layout.textRect.height() << "total contents" << contentsHeight; 0116 // qDebug() << "=> returning" << qMax(senderAndAvatarHeight, contentsHeight) + additionalHeight; 0117 0118 const QSize size = {option.rect.width(), qMax(senderAndAvatarHeight, contentsHeight) + additionalHeight}; 0119 #if USE_SIZEHINT_CACHE_SUPPORT 0120 if (!size.isEmpty()) { 0121 mSizeHintCache.insert(identifier, size); 0122 } 0123 #endif 0124 return size; 0125 } 0126 0127 bool ListDiscussionDelegate::helpEvent(QHelpEvent *helpEvent, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) 0128 { 0129 if (!helpEvent || !view || !index.isValid()) { 0130 return QItemDelegate::helpEvent(helpEvent, view, option, index); 0131 } 0132 0133 if (helpEvent->type() != QEvent::ToolTip) { 0134 return false; 0135 } 0136 0137 const Layout layout = doLayout(option, index); 0138 const auto *doc = documentForModelIndex(index, layout.textRect.width()); 0139 if (!doc) { 0140 return false; 0141 } 0142 0143 const QPoint relativePos = adaptMousePosition(helpEvent->pos(), layout.textRect, option); 0144 QString formattedTooltip; 0145 if (MessageDelegateUtils::generateToolTip(doc, relativePos, formattedTooltip)) { 0146 QToolTip::showText(helpEvent->globalPos(), formattedTooltip, view); 0147 return true; 0148 } 0149 return true; 0150 } 0151 0152 bool ListDiscussionDelegate::mouseEvent(QEvent *event, const QStyleOptionViewItem &option, const QModelIndex &index) 0153 { 0154 const QEvent::Type eventType = event->type(); 0155 if (eventType == QEvent::MouseButtonRelease) { 0156 auto mev = static_cast<QMouseEvent *>(event); 0157 const Layout layout = doLayout(option, index); 0158 0159 const QRect discussionRect(layout.textRect.x(), layout.openDiscussionTextY, layout.textRect.width(), layout.openDiscussionTextHeight); 0160 if (discussionRect.contains(mev->pos())) { 0161 const QString discussionRoomId = index.data(DiscussionsModel::DiscussionRoomId).toString(); 0162 Q_EMIT openDiscussion(discussionRoomId); 0163 return true; 0164 } 0165 if (handleMouseEvent(mev, layout.textRect, option, index)) { 0166 return true; 0167 } 0168 } else if (eventType == QEvent::MouseButtonPress || eventType == QEvent::MouseMove || eventType == QEvent::MouseButtonDblClick) { 0169 auto mev = static_cast<QMouseEvent *>(event); 0170 if (mev->buttons() & Qt::LeftButton) { 0171 const Layout layout = doLayout(option, index); 0172 if (handleMouseEvent(mev, layout.textRect, option, index)) { 0173 return true; 0174 } 0175 } 0176 } 0177 return false; 0178 } 0179 0180 QPoint ListDiscussionDelegate::adaptMousePosition(const QPoint &pos, QRect textRect, const QStyleOptionViewItem &option) 0181 { 0182 Q_UNUSED(option); 0183 const QPoint relativePos = pos - textRect.topLeft(); 0184 return relativePos; 0185 } 0186 0187 QPixmap ListDiscussionDelegate::makeAvatarPixmap(const QWidget *widget, const QModelIndex &index, int maxHeight) const 0188 { 0189 Utils::AvatarInfo info; 0190 info.avatarType = Utils::AvatarType::User; 0191 info.identifier = index.data(DiscussionsModel::UserName).toString(); 0192 0193 return mAvatarCacheManager->makeAvatarPixmap(widget, info, maxHeight); 0194 } 0195 0196 ListDiscussionDelegate::Layout ListDiscussionDelegate::doLayout(const QStyleOptionViewItem &option, const QModelIndex &index) const 0197 { 0198 Layout layout; 0199 const QString userName = index.data(DiscussionsModel::UserName).toString(); 0200 const int margin = MessageDelegateUtils::basicMargin(); 0201 layout.senderText = QLatin1Char('@') + userName; 0202 layout.senderFont = option.font; 0203 layout.senderFont.setBold(true); 0204 0205 // Message (using the rest of the available width) 0206 const int iconSize = option.widget->style()->pixelMetric(QStyle::PM_ButtonIconSize); 0207 const QFontMetricsF senderFontMetrics(layout.senderFont); 0208 const qreal senderAscent = senderFontMetrics.ascent(); 0209 const QSizeF senderTextSize = senderFontMetrics.size(Qt::TextSingleLine, layout.senderText); 0210 0211 const QPixmap pix = makeAvatarPixmap(option.widget, index, senderTextSize.height()); 0212 if (!pix.isNull()) { 0213 const QPixmap scaledPixmap = pix.scaled(senderTextSize.height(), senderTextSize.height(), Qt::KeepAspectRatio, Qt::SmoothTransformation); 0214 layout.avatarPixmap = scaledPixmap; 0215 } 0216 0217 const int senderX = option.rect.x() + MessageDelegateUtils::dprAwareSize(layout.avatarPixmap).width() + 2 * margin; 0218 0219 const int textLeft = senderX + senderTextSize.width() + margin; 0220 const int widthAfterMessage = iconSize + margin + margin / 2; 0221 const int maxWidth = qMax(30, option.rect.width() - textLeft - widthAfterMessage); 0222 0223 layout.baseLine = 0; 0224 const QSize textSize = textSizeHint(index, maxWidth, option, &layout.baseLine); 0225 0226 const int textVMargin = 3; // adjust this for "compactness" 0227 QRect usableRect = option.rect; 0228 layout.textRect = QRect(textLeft, usableRect.top() + textVMargin, maxWidth, textSize.height() + textVMargin); 0229 layout.baseLine += layout.textRect.top(); // make it absolute 0230 0231 layout.senderRect = QRectF(senderX, layout.baseLine - senderAscent, senderTextSize.width(), senderTextSize.height()); 0232 // Align top of avatar with top of sender rect 0233 layout.avatarPos = QPointF(option.rect.x() + margin, layout.senderRect.y()); 0234 0235 layout.lastMessageTimeText = index.data(DiscussionsModel::LastMessage).toString(); 0236 layout.lastMessageTimeY = layout.textRect.bottom(); 0237 0238 layout.numberOfMessages = index.data(DiscussionsModel::NumberOfMessages).toInt(); 0239 0240 layout.openDiscussionTextY = layout.lastMessageTimeY + option.fontMetrics.height(); 0241 layout.openDiscussionTextHeight = option.fontMetrics.height(); 0242 0243 return layout; 0244 } 0245 0246 QString ListDiscussionDelegate::cacheIdentifier(const QModelIndex &index) const 0247 { 0248 const QString discussionRoomId = index.data(DiscussionsModel::DiscussionRoomId).toString(); 0249 Q_ASSERT(!discussionRoomId.isEmpty()); 0250 return discussionRoomId; 0251 } 0252 0253 QTextDocument *ListDiscussionDelegate::documentForModelIndex(const QModelIndex &index, int width) const 0254 { 0255 Q_ASSERT(index.isValid()); 0256 const QString messageId = cacheIdentifier(index); 0257 const QString messageStr = index.data(DiscussionsModel::Description).toString(); 0258 return documentForDelegate(mRocketChatAccount, messageId, messageStr, width); 0259 } 0260 0261 bool ListDiscussionDelegate::maybeStartDrag(QMouseEvent *event, const QStyleOptionViewItem &option, const QModelIndex &index) 0262 { 0263 const Layout layout = doLayout(option, index); 0264 if (MessageListDelegateBase::maybeStartDrag(event, layout.textRect, option, index)) { 0265 return true; 0266 } 0267 return false; 0268 } 0269 0270 RocketChatAccount *ListDiscussionDelegate::rocketChatAccount(const QModelIndex &index) const 0271 { 0272 Q_UNUSED(index); 0273 return mRocketChatAccount; 0274 } 0275 0276 #include "moc_listdiscussiondelegate.cpp"