File indexing completed on 2024-12-08 04:34:35

0001 /*
0002    SPDX-FileCopyrightText: 2023-2024 Laurent Montel <montel.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "servererrorinfohistorydelegate.h"
0008 #include "common/delegatepaintutil.h"
0009 #include "config-ruqola.h"
0010 #include "delegateutils/messagedelegateutils.h"
0011 #include "delegateutils/textselectionimpl.h"
0012 #include "model/servererrorinfohistorymodel.h"
0013 #include "rocketchataccount.h"
0014 #if USE_SIZEHINT_CACHE_SUPPORT
0015 #include "ruqola_sizehint_cache_debug.h"
0016 #endif
0017 #include <QAbstractItemView>
0018 #include <QPainter>
0019 #include <QToolTip>
0020 
0021 ServerErrorInfoHistoryDelegate::ServerErrorInfoHistoryDelegate(QListView *view, QObject *parent)
0022     : MessageListDelegateBase{view, parent}
0023 {
0024 }
0025 
0026 ServerErrorInfoHistoryDelegate::~ServerErrorInfoHistoryDelegate() = default;
0027 
0028 void ServerErrorInfoHistoryDelegate::drawAccountInfo(QPainter *painter, const QModelIndex &index, const QStyleOptionViewItem &option) const
0029 {
0030     const QPen origPen = painter->pen();
0031     const qreal margin = MessageDelegateUtils::basicMargin();
0032     const QString accountName = index.data(ServerErrorInfoHistoryModel::AccountName).toString();
0033     const QString accountInfoStr = accountName;
0034     const QSize infoSize = option.fontMetrics.size(Qt::TextSingleLine, accountInfoStr);
0035     const QRect infoAreaRect(option.rect.x(), option.rect.y(), option.rect.width(), infoSize.height()); // the whole row
0036     const QRect infoTextRect = QStyle::alignedRect(Qt::LayoutDirectionAuto, Qt::AlignCenter, infoSize, infoAreaRect);
0037     painter->drawText(infoTextRect, accountInfoStr);
0038     const int lineY = (infoAreaRect.top() + infoAreaRect.bottom()) / 2;
0039     QColor lightColor(painter->pen().color());
0040     lightColor.setAlpha(60);
0041     painter->setPen(lightColor);
0042     painter->drawLine(infoAreaRect.left(), lineY, infoTextRect.left() - margin, lineY);
0043     painter->drawLine(infoTextRect.right() + margin, lineY, infoAreaRect.right(), lineY);
0044     painter->setPen(origPen);
0045 }
0046 
0047 void ServerErrorInfoHistoryDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
0048 {
0049     painter->save();
0050     drawBackground(painter, option, index);
0051 
0052     const Layout layout = doLayout(option, index);
0053 
0054     if (!layout.sameAccountAsPreviousMessage) {
0055         drawAccountInfo(painter, index, option);
0056     }
0057 
0058     // Draw Text
0059     if (layout.textRect.isValid()) {
0060         auto *doc = documentForModelIndex(index, layout.textRect.width());
0061         if (doc) {
0062             MessageDelegateUtils::drawSelection(doc,
0063                                                 layout.textRect,
0064                                                 layout.textRect.top(),
0065                                                 painter,
0066                                                 index,
0067                                                 option,
0068                                                 mTextSelectionImpl->textSelection(),
0069                                                 {},
0070                                                 {},
0071                                                 false);
0072         }
0073     }
0074 
0075     // Timestamp
0076     DelegatePaintUtil::drawLighterText(painter, layout.timeStampText, layout.timeStampPos);
0077 
0078     // debug (TODO remove it for release)
0079     // painter->drawRect(option.rect.adjusted(0, 0, -1, -1));
0080 
0081     painter->restore();
0082 }
0083 
0084 QSize ServerErrorInfoHistoryDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
0085 {
0086 #if USE_SIZEHINT_CACHE_SUPPORT
0087     const QString identifier = cacheIdentifier(index);
0088     auto it = mSizeHintCache.find(identifier);
0089     if (it != mSizeHintCache.end()) {
0090         const QSize result = it->value;
0091         qCDebug(RUQOLA_SIZEHINT_CACHE_LOG) << "ServerErrorInfoHistoryDelegate: SizeHint found in cache: " << result;
0092         return result;
0093     }
0094 #endif
0095     // Note: option.rect in this method is huge (as big as the viewport)
0096     const Layout layout = doLayout(option, index);
0097     int additionalHeight = 0;
0098     // A little bit of margin below the very last item, it just looks better
0099     if (index.row() == index.model()->rowCount() - 1) {
0100         additionalHeight += 4;
0101     }
0102     // contents is date + text
0103     const int contentsHeight = layout.textRect.y() + layout.textRect.height() - option.rect.y();
0104     const QSize size = {option.rect.width(), contentsHeight + additionalHeight};
0105 #if USE_SIZEHINT_CACHE_SUPPORT
0106     if (!size.isEmpty()) {
0107         mSizeHintCache.insert(identifier, size);
0108     }
0109 #endif
0110     return size;
0111 }
0112 
0113 ServerErrorInfoHistoryDelegate::Layout ServerErrorInfoHistoryDelegate::doLayout(const QStyleOptionViewItem &option, const QModelIndex &index) const
0114 {
0115     ServerErrorInfoHistoryDelegate::Layout layout;
0116     const QString accountName = index.data(ServerErrorInfoHistoryModel::AccountName).toString();
0117     const auto sameAccountAsPreviousMessage = [&] {
0118         if (index.row() < 1) {
0119             return false;
0120         }
0121 
0122         const auto previousIndex = index.siblingAtRow(index.row() - 1);
0123         const auto previousAccountName = previousIndex.data(ServerErrorInfoHistoryModel::AccountName).toString();
0124         return previousAccountName == accountName;
0125     }();
0126 
0127     // Timestamp
0128     layout.timeStampText = index.data(ServerErrorInfoHistoryModel::DateTimeStr).toString();
0129     layout.sameAccountAsPreviousMessage = sameAccountAsPreviousMessage;
0130 
0131     const int margin = MessageDelegateUtils::basicMargin();
0132 
0133     const int textLeft = margin;
0134     const QSize timeSize = MessageDelegateUtils::timeStampSize(layout.timeStampText, option);
0135     const int widthAfterMessage = margin + timeSize.width() + margin / 2;
0136     const int maxWidth = qMax(30, option.rect.width() - textLeft - widthAfterMessage);
0137 
0138     layout.baseLine = 0;
0139     const QSize textSize = textSizeHint(index, maxWidth, option, &layout.baseLine);
0140 
0141     const int textVMargin = 3; // adjust this for "compactness"
0142     QRect usableRect = option.rect;
0143     // Add area for account/room info
0144 
0145     if (!layout.sameAccountAsPreviousMessage) {
0146         usableRect.setTop(usableRect.top() + option.fontMetrics.height());
0147     }
0148 
0149     layout.textRect = QRect(textLeft, usableRect.top() + textVMargin, maxWidth, textSize.height() + textVMargin);
0150     layout.baseLine += layout.textRect.top(); // make it absolute
0151 
0152     layout.timeStampPos = QPoint(option.rect.width() - timeSize.width() - margin / 2, layout.baseLine);
0153 
0154     return layout;
0155 }
0156 
0157 QString ServerErrorInfoHistoryDelegate::cacheIdentifier(const QModelIndex &index) const
0158 {
0159     const QString identifier = index.data(ServerErrorInfoHistoryModel::Identifier).toString();
0160     Q_ASSERT(!identifier.isEmpty());
0161     return identifier;
0162 }
0163 
0164 QTextDocument *ServerErrorInfoHistoryDelegate::documentForModelIndex(const QModelIndex &index, int width) const
0165 {
0166     Q_ASSERT(index.isValid());
0167     const QString identifier = cacheIdentifier(index);
0168     const QString messageStr = index.data(ServerErrorInfoHistoryModel::MessageStr).toString();
0169 
0170     return documentForDelegate(nullptr, identifier, messageStr, width);
0171 }
0172 
0173 bool ServerErrorInfoHistoryDelegate::helpEvent(QHelpEvent *helpEvent, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index)
0174 {
0175     if (!helpEvent || !view || !index.isValid()) {
0176         return QItemDelegate::helpEvent(helpEvent, view, option, index);
0177     }
0178 
0179     if (helpEvent->type() != QEvent::ToolTip) {
0180         return false;
0181     }
0182 
0183     const Layout layout = doLayout(option, index);
0184     const auto *doc = documentForModelIndex(index, layout.textRect.width());
0185     if (!doc) {
0186         return false;
0187     }
0188 
0189     const QPoint relativePos = adaptMousePosition(helpEvent->pos(), layout.textRect, option);
0190     QString formattedTooltip;
0191     if (MessageDelegateUtils::generateToolTip(doc, relativePos, formattedTooltip)) {
0192         QToolTip::showText(helpEvent->globalPos(), formattedTooltip, view);
0193         return true;
0194     }
0195     return true;
0196 }
0197 
0198 QPoint ServerErrorInfoHistoryDelegate::adaptMousePosition(const QPoint &pos, QRect textRect, const QStyleOptionViewItem &option)
0199 {
0200     Q_UNUSED(option);
0201     const QPoint relativePos = pos - textRect.topLeft();
0202     return relativePos;
0203 }
0204 
0205 bool ServerErrorInfoHistoryDelegate::mouseEvent(QEvent *event, const QStyleOptionViewItem &option, const QModelIndex &index)
0206 {
0207     const QEvent::Type eventType = event->type();
0208     if (eventType == QEvent::MouseButtonRelease) {
0209         auto mev = static_cast<QMouseEvent *>(event);
0210         const Layout layout = doLayout(option, index);
0211         if (handleMouseEvent(mev, layout.textRect, option, index)) {
0212             return true;
0213         }
0214     } else if (eventType == QEvent::MouseButtonPress || eventType == QEvent::MouseMove || eventType == QEvent::MouseButtonDblClick) {
0215         auto mev = static_cast<QMouseEvent *>(event);
0216         if (mev->buttons() & Qt::LeftButton) {
0217             const Layout layout = doLayout(option, index);
0218             if (handleMouseEvent(mev, layout.textRect, option, index)) {
0219                 return true;
0220             }
0221         }
0222     }
0223     return false;
0224 }
0225 
0226 bool ServerErrorInfoHistoryDelegate::maybeStartDrag(QMouseEvent *event, const QStyleOptionViewItem &option, const QModelIndex &index)
0227 {
0228     const Layout layout = doLayout(option, index);
0229     if (MessageListDelegateBase::maybeStartDrag(event, layout.textRect, option, index)) {
0230         return true;
0231     }
0232     return false;
0233 }
0234 
0235 RocketChatAccount *ServerErrorInfoHistoryDelegate::rocketChatAccount(const QModelIndex &index) const
0236 {
0237     Q_UNUSED(index);
0238     return nullptr;
0239 }
0240 
0241 #include "moc_servererrorinfohistorydelegate.cpp"