File indexing completed on 2024-12-22 04:46:04
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 "messagelistcompactlayout.h" 0008 #include "delegateutils/messagedelegateutils.h" 0009 #include "model/messagesmodel.h" 0010 #include "rocketchataccount.h" 0011 #include "room/delegate/messageattachmentdelegatehelperbase.h" 0012 #include "room/delegate/messageblockdelegatehelperbase.h" 0013 #include "room/delegate/messagedelegatehelperreactions.h" 0014 #include "room/delegate/messagedelegatehelpertext.h" 0015 #include "room/delegate/messagelistdelegate.h" 0016 0017 MessageListCompactLayout::MessageListCompactLayout(MessageListDelegate *delegate) 0018 : MessageListLayoutBase(delegate) 0019 { 0020 } 0021 0022 MessageListCompactLayout::~MessageListCompactLayout() = default; 0023 0024 // [Optional date header] 0025 // [margin] <pixmap> [margin] <sender> [margin] <editicon> [margin] <text message> [margin] <add reaction> [margin] <timestamp> [margin/2] 0026 // <attachments> 0027 // <blocks> 0028 // <reactions> 0029 // <N replies> 0030 MessageListLayoutBase::Layout MessageListCompactLayout::doLayout(const QStyleOptionViewItem &option, const QModelIndex &index) const 0031 { 0032 const Message *message = index.data(MessagesModel::MessagePointer).value<Message *>(); 0033 Q_ASSERT(message); 0034 const int iconSize = option.widget->style()->pixelMetric(QStyle::PM_ButtonIconSize); 0035 0036 Layout layout; 0037 generateSenderInfo(layout, message, option, index); 0038 0039 const QFontMetricsF senderFontMetrics(layout.senderFont); 0040 const qreal senderAscent = senderFontMetrics.ascent(); 0041 const QSizeF senderTextSize = senderFontMetrics.size(Qt::TextSingleLine, layout.senderText); 0042 0043 if (mRocketChatAccount && mRocketChatAccount->displayAvatars()) { 0044 layout.avatarPixmap = mDelegate->makeAvatarPixmap(option.widget, index, senderTextSize.height()); 0045 } 0046 0047 QRect usableRect = option.rect; 0048 const bool displayLastSeenMessage = index.data(MessagesModel::DisplayLastSeenMessage).toBool(); 0049 if (index.data(MessagesModel::DateDiffersFromPrevious).toBool()) { 0050 usableRect.setTop(usableRect.top() + option.fontMetrics.height()); 0051 } else if (displayLastSeenMessage) { 0052 layout.displayLastSeenMessageY = usableRect.top(); 0053 } 0054 0055 layout.usableRect = usableRect; // Just for the top, for now. The left will move later on. 0056 0057 const qreal margin = MessageDelegateUtils::basicMargin(); 0058 const int senderX = option.rect.x() + MessageDelegateUtils::dprAwareSize(layout.avatarPixmap).width() + 2 * margin; 0059 int textLeft = senderX + senderTextSize.width() + margin; 0060 0061 const qreal iconSizeMargin = iconSize + margin; 0062 // Roles icon 0063 const bool hasRoles = !index.data(MessagesModel::Roles).toString().isEmpty() && mRocketChatAccount && !mRocketChatAccount->hideRoles(); 0064 if (hasRoles) { 0065 textLeft += iconSizeMargin; 0066 } 0067 0068 // Edit icon 0069 const int editIconX = textLeft; 0070 if (message->wasEdited()) { 0071 textLeft += iconSizeMargin; 0072 } 0073 0074 const int favoriteIconX = textLeft; 0075 // Favorite icon 0076 if (message->isStarred()) { 0077 textLeft += iconSizeMargin; 0078 } 0079 0080 const int pinIconX = textLeft; 0081 // Pin icon 0082 if (message->isPinned()) { 0083 textLeft += iconSizeMargin; 0084 } 0085 0086 const int followingIconX = textLeft; 0087 layout.messageIsFollowing = mRocketChatAccount && message->replies().contains(mRocketChatAccount->userId()); 0088 // Following icon 0089 if (layout.messageIsFollowing) { 0090 textLeft += iconSizeMargin; 0091 } 0092 0093 const int translatedIconX = textLeft; 0094 // translated icon 0095 if (message->isAutoTranslated()) { 0096 textLeft += iconSizeMargin; 0097 } 0098 0099 const int showIgnoreMessageIconX = textLeft; 0100 // showIgnoreMessage icon 0101 const bool ignoreMessage = MessageDelegateUtils::showIgnoreMessages(index); 0102 if (ignoreMessage) { 0103 textLeft += iconSizeMargin; 0104 } 0105 0106 // Timestamp 0107 layout.timeStampText = index.data(MessagesModel::Timestamp).toString(); 0108 const QSize timeSize = MessageDelegateUtils::timeStampSize(layout.timeStampText, option); 0109 0110 // Message (using the rest of the available width) 0111 const int widthAfterMessage = iconSizeMargin + timeSize.width() + margin / 2; 0112 const int maxWidth = qMax(30, option.rect.width() - textLeft - widthAfterMessage); 0113 layout.baseLine = 0; 0114 const QSize textSize = mDelegate->helperText()->sizeHint(index, maxWidth, option, &layout.baseLine); 0115 int attachmentsY = 0; 0116 const int textVMargin = 3; // adjust this for "compactness" 0117 if (textSize.isValid()) { 0118 layout.textRect = QRect(textLeft, usableRect.top() + textVMargin, maxWidth, textSize.height() + textVMargin); 0119 attachmentsY = layout.textRect.y() + layout.textRect.height(); 0120 layout.baseLine += layout.textRect.top(); // make it absolute 0121 } else { 0122 attachmentsY = usableRect.top() + textVMargin; 0123 layout.baseLine = attachmentsY + option.fontMetrics.ascent(); 0124 } 0125 layout.usableRect.setLeft(textLeft); 0126 0127 // Align top of sender rect so it matches the baseline of the richtext 0128 layout.senderRect = QRectF(senderX, layout.baseLine - senderAscent, senderTextSize.width(), senderTextSize.height()); 0129 // Align top of avatar with top of sender rect 0130 const double senderRectY{layout.senderRect.y()}; 0131 layout.avatarPos = QPointF(option.rect.x() + margin, senderRectY); 0132 // Same for the roles and edit icon 0133 if (hasRoles) { 0134 layout.rolesIconRect = QRect(editIconX - iconSize - margin, senderRectY, iconSize, iconSize); 0135 } 0136 if (message->wasEdited()) { 0137 layout.editedIconRect = QRect(editIconX, senderRectY, iconSize, iconSize); 0138 } 0139 0140 if (message->isStarred()) { 0141 layout.favoriteIconRect = QRect(favoriteIconX, senderRectY, iconSize, iconSize); 0142 } 0143 0144 if (message->isPinned()) { 0145 layout.pinIconRect = QRect(pinIconX, senderRectY, iconSize, iconSize); 0146 } 0147 if (layout.messageIsFollowing) { 0148 layout.followingIconRect = QRect(followingIconX, senderRectY, iconSize, iconSize); 0149 } 0150 if (message->isAutoTranslated()) { 0151 layout.translatedIconRect = QRect(translatedIconX, senderRectY, iconSize, iconSize); 0152 } 0153 0154 if (ignoreMessage) { 0155 layout.showIgnoredMessageIconRect = QRect(showIgnoreMessageIconX, senderRectY, iconSize, iconSize); 0156 layout.showIgnoreMessage = index.data(MessagesModel::ShowIgnoredMessage).toBool(); 0157 } 0158 layout.addReactionRect = QRect(textLeft + textSize.width() + margin, senderRectY, iconSize, iconSize); 0159 layout.timeStampPos = QPoint(option.rect.width() - timeSize.width() - margin / 2, layout.baseLine); 0160 layout.timeStampRect = QRect(QPoint(layout.timeStampPos.x(), usableRect.top()), timeSize); 0161 0162 generateAttachmentBlockAndUrlPreviewLayout(mDelegate, layout, message, attachmentsY, textLeft, maxWidth, option, index); 0163 layout.reactionsHeight = mDelegate->helperReactions()->sizeHint(index, maxWidth, option).height(); 0164 0165 // Replies 0166 layout.repliesY = layout.reactionsY + layout.reactionsHeight; 0167 if (message->threadCount() > 0) { 0168 layout.repliesHeight = option.fontMetrics.height(); 0169 } 0170 // Discussions 0171 if (!message->discussionRoomId().isEmpty()) { 0172 layout.discussionsHeight = option.fontMetrics.height(); 0173 } 0174 0175 return layout; 0176 } 0177 0178 QSize MessageListCompactLayout::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const 0179 { 0180 // Note: option.rect in this method is huge (as big as the viewport) 0181 const MessageListLayoutBase::Layout layout = doLayout(option, index); 0182 0183 int additionalHeight = 0; 0184 // A little bit of margin below the very last item, it just looks better 0185 if (index.row() == index.model()->rowCount() - 1) { 0186 additionalHeight += 4; 0187 } 0188 0189 // contents is date + text + attachments + reactions + replies + discussions (where all of those are optional) 0190 const int contentsHeight = layout.repliesY + layout.repliesHeight + layout.discussionsHeight - option.rect.y(); 0191 const int senderAndAvatarHeight = qMax<int>(layout.senderRect.y() + layout.senderRect.height() - option.rect.y(), 0192 layout.avatarPos.y() + MessageDelegateUtils::dprAwareSize(layout.avatarPixmap).height() - option.rect.y()); 0193 0194 // qDebug() << "senderAndAvatarHeight" << senderAndAvatarHeight << "text" << layout.textRect.height() 0195 // << "attachments" << layout.attachmentsRect.height() << "reactions" << layout.reactionsHeight << "total contents" << contentsHeight; 0196 // qDebug() << "=> returning" << qMax(senderAndAvatarHeight, contentsHeight) + additionalHeight; 0197 0198 const QSize size = {option.rect.width(), qMax(senderAndAvatarHeight, contentsHeight) + additionalHeight}; 0199 return size; 0200 }