[network/ruqola] src/widgets: Fix layout

Laurent Montel <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 1865bdc6daadf94833f34030f408068fe250dfca by Laurent Montel.
Committed on 13/08/2026 at 16:50.
Pushed by mlaurent into branch 'master'.

Fix layout

M  +6    -6    src/widgets/channellist/channellistdelegate.cpp
M  +12   -6    src/widgets/common/emojicompletiondelegate.cpp
M  +5    -5    src/widgets/misc/avatarcachemanager.cpp
M  +7    -5    src/widgets/misc/joinedchannelcompletiondelegate.cpp
M  +10   -5    src/widgets/room/commandpreviewimagedelegate.cpp
M  +22   -7    src/widgets/room/delegate/messageattachmentdelegatehelpersound.cpp
M  +2    -0    src/widgets/room/delegate/messageattachmentdelegatehelpersound.h
M  +22   -7    src/widgets/room/delegate/messageattachmentdelegatehelpervideo.cpp
M  +2    -0    src/widgets/room/delegate/messageattachmentdelegatehelpervideo.h
M  +7    -6    src/widgets/room/messagetextedit.cpp
M  +18   -7    src/widgets/switchchannelhistory/switchchanneldelegate.cpp

https://invent.kde.org/network/ruqola/-/commit/1865bdc6daadf94833f34030f408068fe250dfca

diff --git a/src/widgets/channellist/channellistdelegate.cpp b/src/widgets/channellist/channellistdelegate.cpp
index 30b4c6cb47..0d64b14eec 100644
--- a/src/widgets/channellist/channellistdelegate.cpp
+++ b/src/widgets/channellist/channellistdelegate.cpp
@@ -63,7 +63,7 @@ ChannelListDelegate::Layout ChannelListDelegate::doLayout(const QStyleOptionView
     layout.unreadText = layout.isHeader ? QString() : makeUnreadText(index);
     const int margin = DelegatePaintUtil::margin();
     layout.unreadSize = !layout.unreadText.isEmpty() ? option.fontMetrics.size(Qt::TextSingleLine, layout.unreadText) : QSize(0, 0);
-    layout.unreadRect = QRect(option.rect.width() - layout.unreadSize.width() - 2 * margin,
+    layout.unreadRect = QRect(option.rect.x() + option.rect.width() - layout.unreadSize.width() - 2 * margin,
                               option.rect.y() + padding,
                               layout.unreadSize.width() + margin,
                               layout.unreadSize.height());
@@ -114,10 +114,10 @@ void ChannelListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &o
 
     const ChannelListDelegate::Layout layout = doLayout(option, index);
 
-    const int xText = offsetAvatarRoom + option.rect.x() + iconSize + (isHeader ? 1 : 2) * margin;
-    const QRect displayRect(xText,
+    const int xTextOffset = offsetAvatarRoom + iconSize + (isHeader ? 1 : 2) * margin;
+    const QRect displayRect(option.rect.x() + xTextOffset,
                             option.rect.y() + padding,
-                            option.rect.width() - xText - layout.unreadSize.width() - 2 * margin,
+                            option.rect.width() - xTextOffset - layout.unreadSize.width() - 2 * margin,
                             option.rect.height() - extraMargins);
 
     QStyleOptionViewItem optionCopy = option;
@@ -132,10 +132,10 @@ void ChannelListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &o
             if (avatarInfo.isValid()) {
                 const QPixmap pix = mAvatarCacheManager->makeAvatarPixmap(option.widget, avatarInfo, option.rect.height() - extraMargins);
 #if USE_ROUNDED_RECT_PIXMAP
-                const QPointF pos(margin, option.rect.top() + padding);
+                const QPointF pos(option.rect.x() + margin, option.rect.top() + padding);
                 DelegatePaintUtil::createClipRoundedRectangle(painter, QRectF(pos, pix.deviceIndependentSize()), pix);
 #else
-                painter->drawPixmap(margin, option.rect.top() + padding, pix);
+                painter->drawPixmap(option.rect.x() + margin, option.rect.top() + padding, pix);
 #endif
             }
         }
diff --git a/src/widgets/common/emojicompletiondelegate.cpp b/src/widgets/common/emojicompletiondelegate.cpp
index 2df1217958..4206870b07 100644
--- a/src/widgets/common/emojicompletiondelegate.cpp
+++ b/src/widgets/common/emojicompletiondelegate.cpp
@@ -30,18 +30,24 @@ void EmojiCompletionDelegate::paint(QPainter *painter, const QStyleOptionViewIte
 
     const QIcon icon = index.data(EmoticonModel::IconRole).value<QIcon>();
     const QString emojiText = index.data(EmoticonModel::UnicodeEmojiRole).toString();
+    const int xEmoji = option.rect.x() + margin;
     if (!icon.isNull()) {
         const int emojiWidth = emojiFontMetrics.horizontalAdvance(u"MM"_s);
-        const QRect displayRect(margin, option.rect.y(), emojiWidth, option.rect.height());
-        drawDecoration(painter, option, displayRect, icon.pixmap(emojiWidth, option.rect.height()));
-        painter->drawText(margin + emojiWidth, option.rect.y() + emojiFontMetrics.ascent(), emojiText);
+        const QRect displayRect(xEmoji, option.rect.y(), emojiWidth, option.rect.height());
+        // Not drawDecoration(): it centers the pixmap using its device size, so on a HiDPI screen
+        // (where QIcon::pixmap() returns a devicePixelRatio > 1 pixmap) it lands outside displayRect.
+        const QPixmap pix = icon.pixmap(emojiWidth, option.rect.height());
+        const QSizeF pixSize = pix.deviceIndependentSize();
+        painter->drawPixmap(
+            QPointF(displayRect.x() + (displayRect.width() - pixSize.width()) / 2, displayRect.y() + (displayRect.height() - pixSize.height()) / 2),
+            pix);
+        painter->drawText(xEmoji + emojiWidth, option.rect.y() + emojiFontMetrics.ascent(), emojiText);
     } else {
         const int emojiWidth = emojiFontMetrics.horizontalAdvance(emojiText);
         painter->setFont(mEmojiFont);
-        painter->drawText(margin, option.rect.y() + emojiFontMetrics.ascent(), emojiText);
+        painter->drawText(xEmoji, option.rect.y() + emojiFontMetrics.ascent(), emojiText);
         const QString text = index.data(EmoticonModel::IdentifierRole).toString();
-        const int xText = option.rect.x() + margin + emojiWidth;
-        const QRect displayRect(xText, option.rect.y(), option.rect.width() - xText, option.rect.height());
+        const QRect displayRect(xEmoji + emojiWidth, option.rect.y(), option.rect.width() - margin - emojiWidth, option.rect.height());
         drawDisplay(painter, option, displayRect, text);
     }
 }
diff --git a/src/widgets/misc/avatarcachemanager.cpp b/src/widgets/misc/avatarcachemanager.cpp
index 9f3313533a..e9e6e49c32 100644
--- a/src/widgets/misc/avatarcachemanager.cpp
+++ b/src/widgets/misc/avatarcachemanager.cpp
@@ -175,16 +175,16 @@ QPixmap AvatarCacheManager::makeRoundedAvatarPixmap(const QWidget *widget, const
         pix = pix.scaledToHeight(maxHeight * dpr, Qt::SmoothTransformation);
         pix.setDevicePixelRatio(dpr);
 
-        QPixmap fullScale(pix.size());
-        fullScale.fill(Qt::transparent);
-
-        downScaled = fullScale.scaledToHeight(maxHeight * dpr, Qt::SmoothTransformation);
+        downScaled = QPixmap(pix.size());
+        downScaled.fill(Qt::transparent);
         downScaled.setDevicePixelRatio(dpr);
 
         QPainterPath path;
         QPainter p(&downScaled);
         p.setRenderHint(QPainter::Antialiasing);
-        path.addRoundedRect(downScaled.rect(), 5, 5);
+        // The painter paints in device-independent pixels, so the clip path must be expressed in
+        // them too: QPixmap::rect() is in device pixels and would be dpr times too large.
+        path.addRoundedRect(QRectF(QPointF(0, 0), downScaled.deviceIndependentSize()), 5, 5);
 
         p.setClipPath(path);
         p.drawPixmap(QPoint(0, 0), pix);
diff --git a/src/widgets/misc/joinedchannelcompletiondelegate.cpp b/src/widgets/misc/joinedchannelcompletiondelegate.cpp
index b63bb46c25..766940b926 100644
--- a/src/widgets/misc/joinedchannelcompletiondelegate.cpp
+++ b/src/widgets/misc/joinedchannelcompletiondelegate.cpp
@@ -45,12 +45,14 @@ void JoinedChannelCompletionDelegate::paint(QPainter *painter, const QStyleOptio
     int xPos = -1;
     const Utils::AvatarInfo info = index.data(JoinedChannelModel::AvatarInfo).value<Utils::AvatarInfo>();
     if (info.isValid()) {
-        const QRect displayRect(margin, option.rect.y(), option.rect.height(), option.rect.height());
-        constexpr ushort marginTop = padding / 2;
-        const QPixmap pix = mAvatarCacheManager->makeRoundedAvatarPixmap(option.widget, info, option.rect.height() - marginTop);
+        constexpr int marginTop = padding / 2;
+        const int iconSize = option.rect.height() - padding;
+        const QPixmap pix = mAvatarCacheManager->makeRoundedAvatarPixmap(option.widget, info, iconSize);
         if (!pix.isNull()) {
-            drawDecoration(painter, option, displayRect, pix);
-            xPos = margin + option.rect.height();
+            // Not drawDecoration(): it aligns the pixmap using its device size, which offsets it
+            // when the devicePixelRatio is > 1.
+            painter->drawPixmap(option.rect.x() + margin, option.rect.y() + marginTop, iconSize, iconSize, pix);
+            xPos = margin + iconSize;
         }
     }
 
diff --git a/src/widgets/room/commandpreviewimagedelegate.cpp b/src/widgets/room/commandpreviewimagedelegate.cpp
index bfcff11f9e..2cf689fd3a 100644
--- a/src/widgets/room/commandpreviewimagedelegate.cpp
+++ b/src/widgets/room/commandpreviewimagedelegate.cpp
@@ -8,9 +8,14 @@
 #include "model/previewcommandmodel.h"
 #include <QListView>
 #include <QPainter>
-CommandPreviewImageDelegate::CommandPreviewImageDelegate(QListView *view, QObject *parent)
+
+namespace
+{
+constexpr int margin = 5;
+}
+
+CommandPreviewImageDelegate::CommandPreviewImageDelegate(QObject *parent)
     : QStyledItemDelegate{parent}
-    , mView(view)
 {
 }
 
@@ -22,13 +27,13 @@ void CommandPreviewImageDelegate::paint(QPainter *painter, const QStyleOptionVie
 
     QPixmap pixmap = index.data(static_cast<int>(PreviewCommandModel::PreviewCommandRoles::Image)).value<QPixmap>();
     if (!pixmap.isNull()) {
-        const QRect rect = option.rect.adjusted(5, 5, -5, -5);
+        const QRect rect = option.rect.adjusted(margin, margin, -margin, -margin);
         painter->drawPixmap(rect, pixmap.scaled(rect.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
         if (hasFocus) {
             painter->drawRect(option.rect.adjusted(0, 0, -1, -1));
         }
     } else {
-        painter->drawText(option.rect, index.data(Qt::UserRole).toString());
+        painter->drawText(option.rect, index.data(Qt::DisplayRole).toString());
     }
 }
 
@@ -36,7 +41,7 @@ QSize CommandPreviewImageDelegate::sizeHint([[maybe_unused]] const QStyleOptionV
 {
     const QPixmap pixmap = index.data(static_cast<int>(PreviewCommandModel::PreviewCommandRoles::Image)).value<QPixmap>();
     if (!pixmap.isNull()) {
-        return pixmap.size();
+        return pixmap.size() + QSize(2 * margin, 2 * margin);
     }
     return QSize(60, 60);
 }
diff --git a/src/widgets/room/delegate/messageattachmentdelegatehelpersound.cpp b/src/widgets/room/delegate/messageattachmentdelegatehelpersound.cpp
index f4d65345b8..42f7e2fbfd 100644
--- a/src/widgets/room/delegate/messageattachmentdelegatehelpersound.cpp
+++ b/src/widgets/room/delegate/messageattachmentdelegatehelpersound.cpp
@@ -40,9 +40,11 @@ void MessageAttachmentDelegateHelperSound::draw(const MessageAttachment &msgAtta
     // Draw title and buttons
     painter->drawText(messageRect.x(), messageRect.y() + option.fontMetrics.ascent(), layout.title);
     mPlayerVolumeIcon.paint(painter, layout.playerVolumeButtonRect.translated(messageRect.topLeft()));
-    mDownloadIcon.paint(painter, layout.downloadButtonRect.translated(messageRect.topLeft()));
+    if (layout.downloadButtonRect.isValid()) {
+        mDownloadIcon.paint(painter, layout.downloadButtonRect.translated(messageRect.topLeft()));
+    }
 
-    const int nextY = messageRect.y() + layout.titleSize.height() + DelegatePaintUtil::margin();
+    const int nextY = messageRect.y() + layout.headerSize.height() + DelegatePaintUtil::margin();
     drawDescription(msgAttach, messageRect, painter, nextY, index, option);
 }
 
@@ -52,13 +54,13 @@ QSize MessageAttachmentDelegateHelperSound::sizeHint(const MessageAttachment &ms
                                                      const QStyleOptionViewItem &option) const
 {
     const SoundLayout layout = layoutSound(msgAttach, option, maxWidth);
-    int height = layout.titleSize.height() + DelegatePaintUtil::margin();
+    int height = layout.headerSize.height() + DelegatePaintUtil::margin();
     int descriptionWidth = 0;
     if (layout.hasDescription) {
         descriptionWidth = layout.descriptionSize.width();
         height += layout.descriptionSize.height() + DelegatePaintUtil::margin();
     }
-    return {qMax(qMax(0, layout.titleSize.width()), descriptionWidth), height};
+    return {qMax(layout.headerSize.width(), descriptionWidth), height};
 }
 
 QPoint MessageAttachmentDelegateHelperSound::adaptMousePosition(const QPoint &pos,
@@ -67,7 +69,7 @@ QPoint MessageAttachmentDelegateHelperSound::adaptMousePosition(const QPoint &po
                                                                 const QStyleOptionViewItem &option)
 {
     const SoundLayout layout = layoutSound(msgAttach, option, attachmentsRect.width());
-    const QPoint relativePos = pos - attachmentsRect.topLeft() - QPoint(0, layout.titleSize.height() + DelegatePaintUtil::margin());
+    const QPoint relativePos = pos - attachmentsRect.topLeft() - QPoint(0, layout.headerSize.height() + DelegatePaintUtil::margin());
     return relativePos;
 }
 
@@ -80,8 +82,14 @@ bool MessageAttachmentDelegateHelperSound::handleMouseEvent(const MessageAttachm
     const QEvent::Type eventType = mouseEvent->type();
     switch (eventType) {
     case QEvent::MouseButtonRelease: {
+        // Only a plain left click acts on the buttons: a release ending a text selection must go to the
+        // base class, otherwise selecting the description pops up the sound dialog.
+        if (mouseEvent->button() != Qt::LeftButton || mTextSelectionImpl->textSelection()->hasSelection()) {
+            break;
+        }
         const QPoint pos = mouseEvent->pos();
 
+        // downloadButtonRect is invalid when the attachment can't be downloaded => it contains() nothing
         const SoundLayout layout = layoutSound(msgAttach, option, attachmentsRect.width());
         if (layout.downloadButtonRect.translated(attachmentsRect.topLeft()).contains(pos)) {
             MessageAttachmentDownloadAndSaveJob::MessageAttachmentDownloadJobInfo info;
@@ -122,8 +130,15 @@ MessageAttachmentDelegateHelperSound::layoutSound(const MessageAttachment &msgAt
     layout.titleSize = option.fontMetrics.size(Qt::TextSingleLine, layout.title);
     layout.descriptionSize = documentTypeForIndexSize(convertAttachmentToDocumentDescriptionInfo(msgAttach, attachmentsWidth));
     const int iconSize = option.widget->style()->pixelMetric(QStyle::PM_ButtonIconSize);
-    layout.playerVolumeButtonRect = QRect(layout.titleSize.width() + DelegatePaintUtil::margin(), 0, iconSize, iconSize);
-    layout.downloadButtonRect = layout.playerVolumeButtonRect.translated(iconSize + DelegatePaintUtil::margin(), 0);
+    const int headerHeight = qMax(layout.titleSize.height(), iconSize);
+    const int iconY = (headerHeight - iconSize) / 2;
+    layout.playerVolumeButtonRect = QRect(layout.titleSize.width() + DelegatePaintUtil::margin(), iconY, iconSize, iconSize);
+    int headerWidth = layout.playerVolumeButtonRect.right() + 1;
+    if (msgAttach.canDownloadAttachment()) {
+        layout.downloadButtonRect = layout.playerVolumeButtonRect.translated(iconSize + DelegatePaintUtil::margin(), 0);
+        headerWidth = layout.downloadButtonRect.right() + 1;
+    }
+    layout.headerSize = QSize(headerWidth, headerHeight);
     layout.audioPath = msgAttach.link();
     return layout;
 }
diff --git a/src/widgets/room/delegate/messageattachmentdelegatehelpersound.h b/src/widgets/room/delegate/messageattachmentdelegatehelpersound.h
index 9b865f6bd0..2ca825ed6a 100644
--- a/src/widgets/room/delegate/messageattachmentdelegatehelpersound.h
+++ b/src/widgets/room/delegate/messageattachmentdelegatehelpersound.h
@@ -30,6 +30,8 @@ private:
         QString audioPath;
         QString title;
         QSize titleSize;
+        // Title + buttons: what the first row really needs, the title alone is not enough
+        QSize headerSize;
         QSize descriptionSize;
         QRect playerVolumeButtonRect;
         QRect downloadButtonRect;
diff --git a/src/widgets/room/delegate/messageattachmentdelegatehelpervideo.cpp b/src/widgets/room/delegate/messageattachmentdelegatehelpervideo.cpp
index e6e24e1c55..7eb4791c5a 100644
--- a/src/widgets/room/delegate/messageattachmentdelegatehelpervideo.cpp
+++ b/src/widgets/room/delegate/messageattachmentdelegatehelpervideo.cpp
@@ -41,9 +41,11 @@ void MessageAttachmentDelegateHelperVideo::draw(const MessageAttachment &msgAtta
     painter->drawText(messageRect.x(), messageRect.y() + option.fontMetrics.ascent(), layout.title);
 
     mVisibilityIcon.paint(painter, layout.showButtonRect.translated(messageRect.topLeft()));
-    mDownloadIcon.paint(painter, layout.downloadButtonRect.translated(messageRect.topLeft()));
+    if (layout.downloadButtonRect.isValid()) {
+        mDownloadIcon.paint(painter, layout.downloadButtonRect.translated(messageRect.topLeft()));
+    }
 
-    const int nextY = messageRect.y() + layout.titleSize.height() + DelegatePaintUtil::margin();
+    const int nextY = messageRect.y() + layout.headerSize.height() + DelegatePaintUtil::margin();
 
     drawDescription(msgAttach, messageRect, painter, nextY, index, option);
 }
@@ -54,13 +56,13 @@ QSize MessageAttachmentDelegateHelperVideo::sizeHint(const MessageAttachment &ms
                                                      const QStyleOptionViewItem &option) const
 {
     const VideoLayout layout = layoutVideo(msgAttach, option, maxWidth);
-    int height = layout.titleSize.height() + DelegatePaintUtil::margin();
+    int height = layout.headerSize.height() + DelegatePaintUtil::margin();
     int descriptionWidth = 0;
     if (layout.hasDescription) {
         descriptionWidth = layout.descriptionSize.width();
         height += layout.descriptionSize.height() + DelegatePaintUtil::margin();
     }
-    return {qMax(qMax(0, layout.titleSize.width()), descriptionWidth), height};
+    return {qMax(layout.headerSize.width(), descriptionWidth), height};
 }
 
 bool MessageAttachmentDelegateHelperVideo::handleMouseEvent(const MessageAttachment &msgAttach,
@@ -72,8 +74,14 @@ bool MessageAttachmentDelegateHelperVideo::handleMouseEvent(const MessageAttachm
     const QEvent::Type eventType = mouseEvent->type();
     switch (eventType) {
     case QEvent::MouseButtonRelease: {
+        // Only a plain left click acts on the buttons: a release ending a text selection must go to the
+        // base class, otherwise selecting the description pops up the video dialog.
+        if (mouseEvent->button() != Qt::LeftButton || mTextSelectionImpl->textSelection()->hasSelection()) {
+            break;
+        }
         const QPoint pos = mouseEvent->pos();
 
+        // downloadButtonRect is invalid when the attachment can't be downloaded => it contains() nothing
         const VideoLayout layout = layoutVideo(msgAttach, option, attachmentsRect.width());
         if (layout.downloadButtonRect.translated(attachmentsRect.topLeft()).contains(pos)) {
             MessageAttachmentDownloadAndSaveJob::MessageAttachmentDownloadJobInfo info;
@@ -114,8 +122,15 @@ MessageAttachmentDelegateHelperVideo::layoutVideo(const MessageAttachment &msgAt
     layout.titleSize = option.fontMetrics.size(Qt::TextSingleLine, layout.title);
     layout.descriptionSize = documentTypeForIndexSize(convertAttachmentToDocumentDescriptionInfo(msgAttach, attachmentsWidth));
     const int iconSize = option.widget->style()->pixelMetric(QStyle::PM_ButtonIconSize);
-    layout.showButtonRect = QRect(layout.titleSize.width() + DelegatePaintUtil::margin(), 0, iconSize, iconSize);
-    layout.downloadButtonRect = layout.showButtonRect.translated(iconSize + DelegatePaintUtil::margin(), 0);
+    const int headerHeight = qMax(layout.titleSize.height(), iconSize);
+    const int iconY = (headerHeight - iconSize) / 2;
+    layout.showButtonRect = QRect(layout.titleSize.width() + DelegatePaintUtil::margin(), iconY, iconSize, iconSize);
+    int headerWidth = layout.showButtonRect.right() + 1;
+    if (msgAttach.canDownloadAttachment()) {
+        layout.downloadButtonRect = layout.showButtonRect.translated(iconSize + DelegatePaintUtil::margin(), 0);
+        headerWidth = layout.downloadButtonRect.right() + 1;
+    }
+    layout.headerSize = QSize(headerWidth, headerHeight);
     layout.videoPath = msgAttach.link();
     return layout;
 }
@@ -126,6 +141,6 @@ QPoint MessageAttachmentDelegateHelperVideo::adaptMousePosition(const QPoint &po
                                                                 const QStyleOptionViewItem &option)
 {
     const VideoLayout layout = layoutVideo(msgAttach, option, attachmentsRect.width());
-    const QPoint relativePos = pos - attachmentsRect.topLeft() - QPoint(0, layout.titleSize.height() + DelegatePaintUtil::margin());
+    const QPoint relativePos = pos - attachmentsRect.topLeft() - QPoint(0, layout.headerSize.height() + DelegatePaintUtil::margin());
     return relativePos;
 }
diff --git a/src/widgets/room/delegate/messageattachmentdelegatehelpervideo.h b/src/widgets/room/delegate/messageattachmentdelegatehelpervideo.h
index f2c44d89a3..59d783f379 100644
--- a/src/widgets/room/delegate/messageattachmentdelegatehelpervideo.h
+++ b/src/widgets/room/delegate/messageattachmentdelegatehelpervideo.h
@@ -31,6 +31,8 @@ private:
         QString videoPath;
         QString title;
         QSize titleSize;
+        // Title + buttons: what the first row really needs, the title alone is not enough
+        QSize headerSize;
         QSize descriptionSize;
         QRect downloadButtonRect;
         QRect showButtonRect;
diff --git a/src/widgets/room/messagetextedit.cpp b/src/widgets/room/messagetextedit.cpp
index 8717cbdf9f..d94e5fb2e6 100644
--- a/src/widgets/room/messagetextedit.cpp
+++ b/src/widgets/room/messagetextedit.cpp
@@ -134,8 +134,8 @@ void MessageTextEdit::switchAutoCorrectionLanguage(const QString &lang)
         auto settings = Ruqola::self()->autoCorrection()->autoCorrectionSettings();
         settings->setLanguage(lang);
         Ruqola::self()->autoCorrection()->setAutoCorrectionSettings(settings);
+        qDebug() << " MessageTextEdit::switchAutoCorrectionLanguage " << lang;
     }
-    qDebug() << " MessageTextEdit::switchAutoCorrectionLanguage " << lang;
 #endif
 }
 
@@ -228,12 +228,13 @@ QMenu *MessageTextEdit::mousePopupMenu()
 {
     QMenu *menu = KTextEdit::mousePopupMenu();
 
-    QClipboard *const clip = QApplication::clipboard();
-    const QMimeData *mimeData = clip->mimeData();
-    if (mimeData->hasImage()) {
+    // Don't store the QMimeData pointer: it's owned by the clipboard and is invalidated as soon as the clipboard contents change.
+    if (const QMimeData *mimeData = QApplication::clipboard()->mimeData(); mimeData && mimeData->hasImage()) {
         menu->addSeparator();
-        menu->addAction(i18n("Paste Image"), this, [this, mimeData]() {
-            Q_EMIT handleMimeData(mimeData);
+        menu->addAction(i18n("Paste Image"), this, [this]() {
+            if (const QMimeData *currentMimeData = QApplication::clipboard()->mimeData(); currentMimeData && currentMimeData->hasImage()) {
+                Q_EMIT handleMimeData(currentMimeData);
+            }
         });
     }
     menu->addSeparator();
diff --git a/src/widgets/switchchannelhistory/switchchanneldelegate.cpp b/src/widgets/switchchannelhistory/switchchanneldelegate.cpp
index 70bf15d192..b70b4e657c 100644
--- a/src/widgets/switchchannelhistory/switchchanneldelegate.cpp
+++ b/src/widgets/switchchannelhistory/switchchanneldelegate.cpp
@@ -15,7 +15,7 @@
 
 namespace
 {
-constexpr uint padding = 4;
+constexpr int padding = 4;
 }
 
 using namespace Qt::Literals::StringLiterals;
@@ -40,25 +40,36 @@ void SwitchChannelDelegate::paint(QPainter *painter, const QStyleOptionViewItem
     drawBackground(painter, option, index);
     const QString text = index.data(SwitchChannelHistoryModel::Name).toString();
     const int margin = DelegatePaintUtil::margin();
+    // The row is padding pixels taller than the text, so inset the avatar by it instead of
+    // letting it fill the row edge to edge.
+    const int iconSize = option.rect.height() - padding;
     int xPos = 0;
     const Utils::AvatarInfo info = index.data(SwitchChannelHistoryModel::AvatarInfo).value<Utils::AvatarInfo>();
     if (info.isValid()) {
-        const QRect displayRect(option.rect.x() + margin, option.rect.y(), option.rect.height(), option.rect.height());
-        const QPixmap pix = mAvatarCacheManager->makeRoundedAvatarPixmap(option.widget, info, option.rect.height());
+        const QPixmap pix = mAvatarCacheManager->makeRoundedAvatarPixmap(option.widget, info, iconSize);
         if (!pix.isNull()) {
-            drawDecoration(painter, option, displayRect, pix);
-            xPos = margin + option.rect.height();
+            // Don't use drawDecoration() here: it aligns the pixmap using its device size, so an
+            // avatar with a devicePixelRatio > 1 ends up offset by half of it. Drawing into an
+            // explicit target rect is devicePixelRatio-correct.
+            painter->drawPixmap(option.rect.x() + margin, option.rect.y() + padding / 2, iconSize, iconSize, pix);
+            xPos = margin + iconSize;
         }
     }
     const int xText = option.rect.x() + margin + xPos;
 
-    const QRect displayRect(xText, option.rect.y(), option.rect.width() - xText, option.rect.height());
+    const QRect displayRect(xText, option.rect.y(), option.rect.width() - margin - xPos, option.rect.height());
     drawDisplay(painter, option, displayRect, text);
 }
 
 QSize SwitchChannelDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
 {
-    return QItemDelegate::sizeHint(option, index) + QSize(0, padding);
+    QSize size = QItemDelegate::sizeHint(option, index) + QSize(0, padding);
+    const Utils::AvatarInfo info = index.data(SwitchChannelHistoryModel::AvatarInfo).value<Utils::AvatarInfo>();
+    if (info.isValid()) {
+        // Same layout as paint(): [M] <square avatar of (row height - padding)> [M] <name>
+        size.rwidth() += 2 * DelegatePaintUtil::margin() + size.height() - padding;
+    }
+    return size;
 }
 
 #include "moc_switchchanneldelegate.cpp"
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.