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"