diff --git a/src/core/lrucache.h b/src/core/lrucache.h --- a/src/core/lrucache.h +++ b/src/core/lrucache.h @@ -94,6 +94,12 @@ mEntries.front() = {std::move(key), std::move(value)}; } + void clear() + { + mNumEntries = 0; + mEntries.fill({}); + } + private: Entries mEntries; std::size_t mNumEntries = 0; diff --git a/src/widgets/room/autotests/messagelistdelegatetest.cpp b/src/widgets/room/autotests/messagelistdelegatetest.cpp --- a/src/widgets/room/autotests/messagelistdelegatetest.cpp +++ b/src/widgets/room/autotests/messagelistdelegatetest.cpp @@ -142,9 +142,10 @@ const int bottom = layout.usableRect.y() + layout.usableRect.height(); // Avatar - QCOMPARE(layout.avatarPixmap.height(), layout.senderRect.height()); + QCOMPARE(layout.avatarPixmap.height() / layout.avatarPixmap.devicePixelRatioF(), layout.senderRect.height()); + QCOMPARE(layout.avatarPixmap.devicePixelRatioF(), fakeWidget.devicePixelRatioF()); //qDebug() << layout.avatarPos.y() << "+" << layout.avatarPixmap.height() << "must be <=" << bottom; - QVERIFY(layout.avatarPos.y() + layout.avatarPixmap.height() <= bottom); + QVERIFY(layout.avatarPos.y() + layout.avatarPixmap.height() / layout.avatarPixmap.devicePixelRatioF() <= bottom); // Reactions if (message.reactions().isEmpty()) { diff --git a/src/widgets/room/delegate/messagelistdelegate.h b/src/widgets/room/delegate/messagelistdelegate.h --- a/src/widgets/room/delegate/messagelistdelegate.h +++ b/src/widgets/room/delegate/messagelistdelegate.h @@ -27,6 +27,8 @@ #include #include +#include "pixmapcache.h" + class RocketChatAccount; class Message; class MessageDelegateHelperBase; @@ -64,7 +66,7 @@ void selectAll(const QStyleOptionViewItem &option, const QModelIndex &index); private: - QPixmap makeAvatarPixmap(const QModelIndex &index, int maxHeight) const; + QPixmap makeAvatarPixmap(const QWidget *widget, const QModelIndex &index, int maxHeight) const; struct Layout { // Sender @@ -129,6 +131,12 @@ QScopedPointer mHelperReactions; QScopedPointer mHelperVideo; QScopedPointer mHelperSound; + // DPR-dependent cache of avatars + struct AvatarCache { + qreal dpr = 0.; + PixmapCache cache; + }; + mutable AvatarCache mAvatarCache; }; #endif // MESSAGELISTDELEGATE_H diff --git a/src/widgets/room/delegate/messagelistdelegate.cpp b/src/widgets/room/delegate/messagelistdelegate.cpp --- a/src/widgets/room/delegate/messagelistdelegate.cpp +++ b/src/widgets/room/delegate/messagelistdelegate.cpp @@ -38,13 +38,19 @@ #include #include #include -#include #include #include #include #include +static QSizeF dprAwareSize(const QPixmap &pixmap) +{ + if (pixmap.isNull()) + return {0, 0}; // prevent division-by-zero + return pixmap.size() / pixmap.devicePixelRatioF(); +} + MessageListDelegate::MessageListDelegate(QObject *parent) : QItemDelegate(parent) , mEditedIcon(QIcon::fromTheme(QStringLiteral("document-edit"))) @@ -81,22 +87,36 @@ return QSize(option.fontMetrics.horizontalAdvance(timeStampText), option.fontMetrics.height()); } -QPixmap MessageListDelegate::makeAvatarPixmap(const QModelIndex &index, int maxHeight) const +QPixmap MessageListDelegate::makeAvatarPixmap(const QWidget *widget, const QModelIndex &index, int maxHeight) const { const QString userId = index.data(MessageModel::UserId).toString(); const QString iconUrlStr = mRocketChatAccount->avatarUrl(userId); - QPixmap pix; - if (!iconUrlStr.isEmpty() && !QPixmapCache::find(iconUrlStr, &pix)) { + if (iconUrlStr.isEmpty()) { + return {}; + } + + const auto dpr = widget->devicePixelRatioF(); + if (dpr != mAvatarCache.dpr) { + mAvatarCache.dpr = dpr; + mAvatarCache.cache.clear(); + } + + auto &cache = mAvatarCache.cache; + + auto downScaled = cache.findCachedPixmap(iconUrlStr); + if (downScaled.isNull()) { const QUrl iconUrl(iconUrlStr); Q_ASSERT(iconUrl.isLocalFile()); - if (pix.load(iconUrl.toLocalFile())) { - pix = pix.scaledToHeight(maxHeight); - QPixmapCache::insert(iconUrlStr, pix); - } else { + QPixmap fullScale; + if (!fullScale.load(iconUrl.toLocalFile())) { qCWarning(RUQOLAWIDGETS_LOG) << "Could not load" << iconUrl.toLocalFile(); + return {}; } + downScaled = fullScale.scaledToHeight(maxHeight * dpr); + downScaled.setDevicePixelRatio(dpr); + cache.insertCachedPixmap(iconUrlStr, downScaled); } - return pix; + return downScaled; } // [Optional date header] @@ -118,7 +138,7 @@ const qreal senderAscent = senderFontMetrics.ascent(); const QSizeF senderTextSize = senderFontMetrics.size(Qt::TextSingleLine, layout.senderText); - layout.avatarPixmap = makeAvatarPixmap(index, senderTextSize.height()); + layout.avatarPixmap = makeAvatarPixmap(option.widget, index, senderTextSize.height()); QRect usableRect = option.rect; const bool displayLastSeenMessage = index.data(MessageModel::DisplayLastSeenMessage).toBool(); @@ -131,7 +151,7 @@ layout.usableRect = usableRect; // Just for the top, for now. The left will move later on. const qreal margin = basicMargin(); - const int senderX = option.rect.x() + layout.avatarPixmap.width() + 2 * margin; + const int senderX = option.rect.x() + dprAwareSize(layout.avatarPixmap).width() + 2 * margin; int textLeft = senderX + senderTextSize.width() + margin; // Roles icon @@ -372,7 +392,7 @@ // contents is date + text + attachments + reactions + replies + discussions (where all of those are optional) const int contentsHeight = layout.repliesY + layout.repliesHeight + layout.discussionsHeight - option.rect.y(); const int senderAndAvatarHeight = qMax(layout.senderRect.y() + layout.senderRect.height() - option.rect.y(), - layout.avatarPos.y() + layout.avatarPixmap.height() - option.rect.y()); + layout.avatarPos.y() + dprAwareSize(layout.avatarPixmap).height() - option.rect.y()); //qDebug() << "senderAndAvatarHeight" << senderAndAvatarHeight << "text" << layout.textRect.height() // << "attachments" << layout.attachmentsRect.height() << "reactions" << layout.reactionsHeight << "total contents" << contentsHeight; diff --git a/src/widgets/room/delegate/pixmapcache.h b/src/widgets/room/delegate/pixmapcache.h --- a/src/widgets/room/delegate/pixmapcache.h +++ b/src/widgets/room/delegate/pixmapcache.h @@ -32,11 +32,13 @@ public: QPixmap pixmapForLocalFile(const QString &path); + QPixmap findCachedPixmap(const QString &path); + void insertCachedPixmap(const QString &path, const QPixmap &pixmap); + void clear(); + private: friend class PixmapCacheTest; LRUCache mCachedImages; - QPixmap findCachedPixmap(const QString &link); - void insertCachedPixmap(const QString &link, const QPixmap &pixmap); }; #endif // PIXMAPCACHE_H diff --git a/src/widgets/room/delegate/pixmapcache.cpp b/src/widgets/room/delegate/pixmapcache.cpp --- a/src/widgets/room/delegate/pixmapcache.cpp +++ b/src/widgets/room/delegate/pixmapcache.cpp @@ -23,15 +23,32 @@ QPixmap PixmapCache::pixmapForLocalFile(const QString &path) { - auto it = mCachedImages.find(path); - if (it != mCachedImages.end()) { - return it->value; - } - auto pixmap = QPixmap(path); + auto pixmap = findCachedPixmap(path); + if (pixmap.isNull()) { - qCWarning(RUQOLAWIDGETS_LOG) << "Could not load" << path; - return pixmap; + pixmap = QPixmap(path); + if (pixmap.isNull()) { + qCWarning(RUQOLAWIDGETS_LOG) << "Could not load" << path; + return pixmap; + } + insertCachedPixmap(path, pixmap); } - mCachedImages.insert(path, pixmap); + return pixmap; } + +QPixmap PixmapCache::findCachedPixmap(const QString &path) +{ + auto it = mCachedImages.find(path); + return it == mCachedImages.end() ? QPixmap() : it->value; +} + +void PixmapCache::insertCachedPixmap(const QString& path, const QPixmap& pixmap) +{ + mCachedImages.insert(path, pixmap); +} + +void PixmapCache::clear() +{ + mCachedImages.clear(); +}