Changeset View
Changeset View
Standalone View
Standalone View
src/widgets/room/delegate/messagelistdelegate.cpp
Show All 39 Lines | |||||
40 | #include <QPainter> | 40 | #include <QPainter> | ||
41 | #include <QPixmapCache> | 41 | #include <QPixmapCache> | ||
42 | #include <QScreen> | 42 | #include <QScreen> | ||
43 | #include <QToolTip> | 43 | #include <QToolTip> | ||
44 | 44 | | |||
45 | #include <KLocalizedString> | 45 | #include <KLocalizedString> | ||
46 | #include <KColorScheme> | 46 | #include <KColorScheme> | ||
47 | 47 | | |||
48 | static QSizeF dprAwareSize(const QPixmap &pixmap) | ||||
49 | { | ||||
50 | if (pixmap.isNull()) | ||||
51 | return {0, 0}; // prevent division-by-zero | ||||
52 | return pixmap.size() / pixmap.devicePixelRatioF(); | ||||
53 | } | ||||
54 | | ||||
48 | MessageListDelegate::MessageListDelegate(QObject *parent) | 55 | MessageListDelegate::MessageListDelegate(QObject *parent) | ||
49 | : QItemDelegate(parent) | 56 | : QItemDelegate(parent) | ||
50 | , mEditedIcon(QIcon::fromTheme(QStringLiteral("document-edit"))) | 57 | , mEditedIcon(QIcon::fromTheme(QStringLiteral("document-edit"))) | ||
51 | , mRolesIcon(QIcon::fromTheme(QStringLiteral("documentinfo"))) | 58 | , mRolesIcon(QIcon::fromTheme(QStringLiteral("documentinfo"))) | ||
52 | // https://bugs.kde.org/show_bug.cgi?id=417298 added smiley-add to KF 5.68 | 59 | // https://bugs.kde.org/show_bug.cgi?id=417298 added smiley-add to KF 5.68 | ||
53 | , mAddReactionIcon(QIcon::fromTheme(QStringLiteral("smiley-add"), QIcon::fromTheme(QStringLiteral("face-smile")))) | 60 | , mAddReactionIcon(QIcon::fromTheme(QStringLiteral("smiley-add"), QIcon::fromTheme(QStringLiteral("face-smile")))) | ||
54 | , mHelperText(new MessageDelegateHelperText) | 61 | , mHelperText(new MessageDelegateHelperText) | ||
55 | , mHelperImage(new MessageDelegateHelperImage) | 62 | , mHelperImage(new MessageDelegateHelperImage) | ||
Show All 20 Lines | |||||
76 | 83 | | |||
77 | static QSize timeStampSize(const QString &timeStampText, const QStyleOptionViewItem &option) | 84 | static QSize timeStampSize(const QString &timeStampText, const QStyleOptionViewItem &option) | ||
78 | { | 85 | { | ||
79 | // This gives incorrect results (too small bounding rect), no idea why! | 86 | // This gives incorrect results (too small bounding rect), no idea why! | ||
80 | //const QSize timeSize = painter->fontMetrics().boundingRect(timeStampText).size(); | 87 | //const QSize timeSize = painter->fontMetrics().boundingRect(timeStampText).size(); | ||
81 | return QSize(option.fontMetrics.horizontalAdvance(timeStampText), option.fontMetrics.height()); | 88 | return QSize(option.fontMetrics.horizontalAdvance(timeStampText), option.fontMetrics.height()); | ||
82 | } | 89 | } | ||
83 | 90 | | |||
84 | QPixmap MessageListDelegate::makeAvatarPixmap(const QModelIndex &index, int maxHeight) const | 91 | QPixmap MessageListDelegate::makeAvatarPixmap(const QWidget *widget, const QModelIndex &index, int maxHeight) const | ||
85 | { | 92 | { | ||
86 | const QString userId = index.data(MessageModel::UserId).toString(); | 93 | const QString userId = index.data(MessageModel::UserId).toString(); | ||
87 | const QString iconUrlStr = mRocketChatAccount->avatarUrl(userId); | 94 | const QString iconUrlStr = mRocketChatAccount->avatarUrl(userId); | ||
88 | QPixmap pix; | 95 | if (iconUrlStr.isEmpty()) { | ||
89 | if (!iconUrlStr.isEmpty() && !QPixmapCache::find(iconUrlStr, &pix)) { | 96 | return {}; | ||
97 | } | ||||
98 | | ||||
99 | const auto dpr = widget->devicePixelRatioF(); | ||||
100 | auto &cache = mScaledAvatars[dpr]; | ||||
101 | | ||||
102 | auto downScaled = cache.findCachedPixmap(iconUrlStr); | ||||
103 | if (downScaled.isNull()) { | ||||
104 | QPixmap fullScale; | ||||
105 | if (!QPixmapCache::find(iconUrlStr, &fullScale)) { | ||||
90 | const QUrl iconUrl(iconUrlStr); | 106 | const QUrl iconUrl(iconUrlStr); | ||
91 | Q_ASSERT(iconUrl.isLocalFile()); | 107 | Q_ASSERT(iconUrl.isLocalFile()); | ||
92 | if (pix.load(iconUrl.toLocalFile())) { | 108 | if (!fullScale.load(iconUrl.toLocalFile())) { | ||
93 | pix = pix.scaledToHeight(maxHeight); | | |||
94 | QPixmapCache::insert(iconUrlStr, pix); | | |||
95 | } else { | | |||
96 | qCWarning(RUQOLAWIDGETS_LOG) << "Could not load" << iconUrl.toLocalFile(); | 109 | qCWarning(RUQOLAWIDGETS_LOG) << "Could not load" << iconUrl.toLocalFile(); | ||
110 | return {}; | ||||
111 | } | ||||
112 | QPixmapCache::insert(iconUrlStr, fullScale); | ||||
dfaure: Why does this still use QPixmapCache, if we're caching the pixmap in mScaledAvatars anyway? | |||||
yes you are right, it's probably overkill - I'll remove it. we'd be able to share across screens but that's very uncommon so let's remove that. and while at it - I'll also not use a QHash for the per-dpr but only keep one cache and check if the DPR matches the current one mwolff: yes you are right, it's probably overkill - I'll remove it.
we'd be able to share across… | |||||
97 | } | 113 | } | ||
114 | downScaled = fullScale.scaledToHeight(maxHeight * dpr); | ||||
115 | downScaled.setDevicePixelRatio(dpr); | ||||
116 | cache.insertCachedPixmap(iconUrlStr, downScaled); | ||||
98 | } | 117 | } | ||
99 | return pix; | 118 | return downScaled; | ||
100 | } | 119 | } | ||
101 | 120 | | |||
102 | // [Optional date header] | 121 | // [Optional date header] | ||
103 | // [margin] <pixmap> [margin] <sender> [margin] <editicon> [margin] <text message> [margin] <add reaction> [margin] <timestamp> [margin/2] | 122 | // [margin] <pixmap> [margin] <sender> [margin] <editicon> [margin] <text message> [margin] <add reaction> [margin] <timestamp> [margin/2] | ||
104 | // <attachments> | 123 | // <attachments> | ||
105 | // <reactions> | 124 | // <reactions> | ||
106 | // <N replies> | 125 | // <N replies> | ||
107 | MessageListDelegate::Layout MessageListDelegate::doLayout(const QStyleOptionViewItem &option, const QModelIndex &index) const | 126 | MessageListDelegate::Layout MessageListDelegate::doLayout(const QStyleOptionViewItem &option, const QModelIndex &index) const | ||
108 | { | 127 | { | ||
109 | const Message *message = index.data(MessageModel::MessagePointer).value<Message *>(); | 128 | const Message *message = index.data(MessageModel::MessagePointer).value<Message *>(); | ||
110 | Q_ASSERT(message); | 129 | Q_ASSERT(message); | ||
111 | const int iconSize = option.widget->style()->pixelMetric(QStyle::PM_ButtonIconSize); | 130 | const int iconSize = option.widget->style()->pixelMetric(QStyle::PM_ButtonIconSize); | ||
112 | 131 | | |||
113 | Layout layout; | 132 | Layout layout; | ||
114 | layout.senderText = QLatin1Char('@') + message->username(); | 133 | layout.senderText = QLatin1Char('@') + message->username(); | ||
115 | layout.senderFont = option.font; | 134 | layout.senderFont = option.font; | ||
116 | layout.senderFont.setBold(true); | 135 | layout.senderFont.setBold(true); | ||
117 | const QFontMetricsF senderFontMetrics(layout.senderFont); | 136 | const QFontMetricsF senderFontMetrics(layout.senderFont); | ||
118 | const qreal senderAscent = senderFontMetrics.ascent(); | 137 | const qreal senderAscent = senderFontMetrics.ascent(); | ||
119 | const QSizeF senderTextSize = senderFontMetrics.size(Qt::TextSingleLine, layout.senderText); | 138 | const QSizeF senderTextSize = senderFontMetrics.size(Qt::TextSingleLine, layout.senderText); | ||
120 | 139 | | |||
121 | layout.avatarPixmap = makeAvatarPixmap(index, senderTextSize.height()); | 140 | layout.avatarPixmap = makeAvatarPixmap(option.widget, index, senderTextSize.height()); | ||
122 | 141 | | |||
123 | QRect usableRect = option.rect; | 142 | QRect usableRect = option.rect; | ||
124 | const bool displayLastSeenMessage = index.data(MessageModel::DisplayLastSeenMessage).toBool(); | 143 | const bool displayLastSeenMessage = index.data(MessageModel::DisplayLastSeenMessage).toBool(); | ||
125 | if (index.data(MessageModel::DateDiffersFromPrevious).toBool()) { | 144 | if (index.data(MessageModel::DateDiffersFromPrevious).toBool()) { | ||
126 | usableRect.setTop(usableRect.top() + option.fontMetrics.height()); | 145 | usableRect.setTop(usableRect.top() + option.fontMetrics.height()); | ||
127 | } else if (displayLastSeenMessage) { | 146 | } else if (displayLastSeenMessage) { | ||
128 | layout.displayLastSeenMessageY = usableRect.top(); | 147 | layout.displayLastSeenMessageY = usableRect.top(); | ||
129 | } | 148 | } | ||
130 | 149 | | |||
131 | layout.usableRect = usableRect; // Just for the top, for now. The left will move later on. | 150 | layout.usableRect = usableRect; // Just for the top, for now. The left will move later on. | ||
132 | 151 | | |||
133 | const qreal margin = basicMargin(); | 152 | const qreal margin = basicMargin(); | ||
134 | const int senderX = option.rect.x() + layout.avatarPixmap.width() + 2 * margin; | 153 | const int senderX = option.rect.x() + dprAwareSize(layout.avatarPixmap).width() + 2 * margin; | ||
135 | int textLeft = senderX + senderTextSize.width() + margin; | 154 | int textLeft = senderX + senderTextSize.width() + margin; | ||
136 | 155 | | |||
137 | // Roles icon | 156 | // Roles icon | ||
138 | const bool hasRoles = !index.data(MessageModel::Roles).toString().isEmpty(); | 157 | const bool hasRoles = !index.data(MessageModel::Roles).toString().isEmpty(); | ||
139 | if (hasRoles) { | 158 | if (hasRoles) { | ||
140 | textLeft += iconSize + margin; | 159 | textLeft += iconSize + margin; | ||
141 | } | 160 | } | ||
142 | 161 | | |||
▲ Show 20 Lines • Show All 224 Lines • ▼ Show 20 Line(s) | 381 | { | |||
367 | // A little bit of margin below the very last item, it just looks better | 386 | // A little bit of margin below the very last item, it just looks better | ||
368 | if (index.row() == index.model()->rowCount() - 1) { | 387 | if (index.row() == index.model()->rowCount() - 1) { | ||
369 | additionalHeight += 4; | 388 | additionalHeight += 4; | ||
370 | } | 389 | } | ||
371 | 390 | | |||
372 | // contents is date + text + attachments + reactions + replies + discussions (where all of those are optional) | 391 | // contents is date + text + attachments + reactions + replies + discussions (where all of those are optional) | ||
373 | const int contentsHeight = layout.repliesY + layout.repliesHeight + layout.discussionsHeight - option.rect.y(); | 392 | const int contentsHeight = layout.repliesY + layout.repliesHeight + layout.discussionsHeight - option.rect.y(); | ||
374 | const int senderAndAvatarHeight = qMax<int>(layout.senderRect.y() + layout.senderRect.height() - option.rect.y(), | 393 | const int senderAndAvatarHeight = qMax<int>(layout.senderRect.y() + layout.senderRect.height() - option.rect.y(), | ||
375 | layout.avatarPos.y() + layout.avatarPixmap.height() - option.rect.y()); | 394 | layout.avatarPos.y() + dprAwareSize(layout.avatarPixmap).height() - option.rect.y()); | ||
376 | 395 | | |||
377 | //qDebug() << "senderAndAvatarHeight" << senderAndAvatarHeight << "text" << layout.textRect.height() | 396 | //qDebug() << "senderAndAvatarHeight" << senderAndAvatarHeight << "text" << layout.textRect.height() | ||
378 | // << "attachments" << layout.attachmentsRect.height() << "reactions" << layout.reactionsHeight << "total contents" << contentsHeight; | 397 | // << "attachments" << layout.attachmentsRect.height() << "reactions" << layout.reactionsHeight << "total contents" << contentsHeight; | ||
379 | //qDebug() << "=> returning" << qMax(senderAndAvatarHeight, contentsHeight) + additionalHeight; | 398 | //qDebug() << "=> returning" << qMax(senderAndAvatarHeight, contentsHeight) + additionalHeight; | ||
380 | 399 | | |||
381 | return QSize(option.rect.width(), | 400 | return QSize(option.rect.width(), | ||
382 | qMax(senderAndAvatarHeight, contentsHeight) + additionalHeight); | 401 | qMax(senderAndAvatarHeight, contentsHeight) + additionalHeight); | ||
383 | } | 402 | } | ||
▲ Show 20 Lines • Show All 123 Lines • Show Last 20 Lines |
Why does this still use QPixmapCache, if we're caching the pixmap in mScaledAvatars anyway?
AFAICT we're not going to go into the first if() more than once for a given avatar.