File indexing completed on 2024-12-08 04:34:09
0001 /* 0002 SPDX-FileCopyrightText: 2022-2024 Laurent Montel <montel@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "bannerinfolistviewdelegate.h" 0008 #include "config-ruqola.h" 0009 #include "model/bannerinfosmodel.h" 0010 #if USE_SIZEHINT_CACHE_SUPPORT 0011 #include "ruqola_sizehint_cache_debug.h" 0012 #endif 0013 0014 #include <KColorScheme> 0015 #include <KLocalizedString> 0016 #include <QAbstractItemView> 0017 #include <QPainter> 0018 #include <QToolTip> 0019 0020 #include "delegateutils/messagedelegateutils.h" 0021 #include "delegateutils/textselectionimpl.h" 0022 #include "rocketchataccount.h" 0023 0024 BannerInfoListViewDelegate::BannerInfoListViewDelegate(QListView *view, RocketChatAccount *account, QObject *parent) 0025 : MessageListDelegateBase(view, parent) 0026 , mRocketChatAccount(account) 0027 { 0028 } 0029 0030 BannerInfoListViewDelegate::~BannerInfoListViewDelegate() = default; 0031 0032 void BannerInfoListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const 0033 { 0034 painter->save(); 0035 0036 const Layout layout = doLayout(option, index); 0037 0038 // Draw Text 0039 if (layout.textRect.isValid()) { 0040 auto *doc = documentForModelIndex(index, layout.textRect.width()); 0041 if (doc) { 0042 MessageDelegateUtils::drawSelection(doc, 0043 layout.textRect, 0044 layout.textRect.top(), 0045 painter, 0046 index, 0047 option, 0048 mTextSelectionImpl->textSelection(), 0049 {}, 0050 {}, 0051 false); 0052 } 0053 } 0054 0055 // debug 0056 // painter->drawRect(option.rect.adjusted(0, 0, -1, -1)); 0057 painter->restore(); 0058 } 0059 0060 QSize BannerInfoListViewDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const 0061 { 0062 #if USE_SIZEHINT_CACHE_SUPPORT 0063 const QString identifier = cacheIdentifier(index); 0064 auto it = mSizeHintCache.find(identifier); 0065 if (it != mSizeHintCache.end()) { 0066 const QSize result = it->value; 0067 qCDebug(RUQOLA_SIZEHINT_CACHE_LOG) << "BannerInfoListViewDelegate: SizeHint found in cache: " << result; 0068 return result; 0069 } 0070 #endif 0071 // Note: option.rect in this method is huge (as big as the viewport) 0072 const Layout layout = doLayout(option, index); 0073 0074 int additionalHeight = 0; 0075 // A little bit of margin below the very last item, it just looks better 0076 if (index.row() == index.model()->rowCount() - 1) { 0077 additionalHeight += 4; 0078 } 0079 0080 // contents is date + text 0081 const int contentsHeight = layout.textRect.height() - option.rect.y(); 0082 // qDebug() << "senderAndAvatarHeight" << senderAndAvatarHeight << "text" << layout.textRect.height() << "total contents" << contentsHeight; 0083 // qDebug() << "=> returning" << qMax(senderAndAvatarHeight, contentsHeight) + additionalHeight; 0084 0085 const QSize size = {option.rect.width(), contentsHeight + additionalHeight}; 0086 #if USE_SIZEHINT_CACHE_SUPPORT 0087 if (!size.isEmpty()) { 0088 mSizeHintCache.insert(identifier, size); 0089 } 0090 #endif 0091 return size; 0092 } 0093 0094 bool BannerInfoListViewDelegate::helpEvent(QHelpEvent *helpEvent, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) 0095 { 0096 if (!helpEvent || !view || !index.isValid()) { 0097 return QItemDelegate::helpEvent(helpEvent, view, option, index); 0098 } 0099 0100 if (helpEvent->type() != QEvent::ToolTip) { 0101 return false; 0102 } 0103 0104 const Layout layout = doLayout(option, index); 0105 const auto *doc = documentForModelIndex(index, layout.textRect.width()); 0106 if (!doc) { 0107 return false; 0108 } 0109 0110 const QPoint relativePos = adaptMousePosition(helpEvent->pos(), layout.textRect, option); 0111 QString formattedTooltip; 0112 if (MessageDelegateUtils::generateToolTip(doc, relativePos, formattedTooltip)) { 0113 QToolTip::showText(helpEvent->globalPos(), formattedTooltip, view); 0114 return true; 0115 } 0116 return true; 0117 } 0118 0119 bool BannerInfoListViewDelegate::mouseEvent(QEvent *event, const QStyleOptionViewItem &option, const QModelIndex &index) 0120 { 0121 const QEvent::Type eventType = event->type(); 0122 if (eventType == QEvent::MouseButtonRelease) { 0123 auto mev = static_cast<QMouseEvent *>(event); 0124 const Layout layout = doLayout(option, index); 0125 0126 if (handleMouseEvent(mev, layout.textRect, option, index)) { 0127 return true; 0128 } 0129 } else if (eventType == QEvent::MouseButtonPress || eventType == QEvent::MouseMove || eventType == QEvent::MouseButtonDblClick) { 0130 auto mev = static_cast<QMouseEvent *>(event); 0131 if (mev->buttons() & Qt::LeftButton) { 0132 const Layout layout = doLayout(option, index); 0133 if (handleMouseEvent(mev, layout.textRect, option, index)) { 0134 return true; 0135 } 0136 } 0137 } 0138 return false; 0139 } 0140 0141 QPoint BannerInfoListViewDelegate::adaptMousePosition(const QPoint &pos, QRect textRect, const QStyleOptionViewItem &option) 0142 { 0143 Q_UNUSED(option); 0144 const QPoint relativePos = pos - textRect.topLeft(); 0145 return relativePos; 0146 } 0147 0148 BannerInfoListViewDelegate::Layout BannerInfoListViewDelegate::doLayout(const QStyleOptionViewItem &option, const QModelIndex &index) const 0149 { 0150 Layout layout; 0151 // Message (using the rest of the available width) 0152 const int iconSize = option.widget->style()->pixelMetric(QStyle::PM_ButtonIconSize); 0153 0154 const int senderX = option.rect.x(); 0155 const int margin = MessageDelegateUtils::basicMargin(); 0156 const int textLeft = senderX + margin; 0157 const int widthAfterMessage = iconSize + margin + margin / 2; 0158 const int maxWidth = qMax(30, option.rect.width() - textLeft - widthAfterMessage); 0159 0160 layout.baseLine = 0; 0161 const QSize textSize = textSizeHint(index, maxWidth, option, &layout.baseLine); 0162 0163 const int textVMargin = 3; // adjust this for "compactness" 0164 QRect usableRect = option.rect; 0165 layout.textRect = QRect(textLeft, usableRect.top() + textVMargin, maxWidth, textSize.height() + textVMargin); 0166 layout.baseLine += layout.textRect.top(); // make it absolute 0167 return layout; 0168 } 0169 0170 QString BannerInfoListViewDelegate::cacheIdentifier(const QModelIndex &index) const 0171 { 0172 const QString identifier = index.data(BannerInfosModel::Identifier).toString(); 0173 Q_ASSERT(!identifier.isEmpty()); 0174 return identifier; 0175 } 0176 0177 QTextDocument *BannerInfoListViewDelegate::documentForModelIndex(const QModelIndex &index, int width) const 0178 { 0179 Q_ASSERT(index.isValid()); 0180 const QString messageId = cacheIdentifier(index); 0181 const QString messageBannerStr = index.data(BannerInfosModel::Text).toString(); 0182 return documentForDelegate(mRocketChatAccount, messageId, messageBannerStr, width); 0183 } 0184 0185 bool BannerInfoListViewDelegate::maybeStartDrag(QMouseEvent *event, const QStyleOptionViewItem &option, const QModelIndex &index) 0186 { 0187 const Layout layout = doLayout(option, index); 0188 if (MessageListDelegateBase::maybeStartDrag(event, layout.textRect, option, index)) { 0189 return true; 0190 } 0191 return false; 0192 } 0193 0194 RocketChatAccount *BannerInfoListViewDelegate::rocketChatAccount(const QModelIndex &index) const 0195 { 0196 Q_UNUSED(index); 0197 return mRocketChatAccount; 0198 } 0199 0200 #include "moc_bannerinfolistviewdelegate.cpp"