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"