]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/kitemviews/kstandarditemlistwidget.cpp
Add setting to select filename eliding behavior
[dolphin.git] / src / kitemviews / kstandarditemlistwidget.cpp
index a30a1bcd2ade909b7fef26d5dc56d734165ea2d2..49d2f26bfd945db1feedf96f15607abe8487541b 100644 (file)
@@ -6,8 +6,8 @@
 
 #include "kstandarditemlistwidget.h"
 
+#include "dolphin_contentdisplaysettings.h"
 #include "kfileitemlistview.h"
-#include "kfileitemmodel.h"
 #include "private/kfileitemclipboard.h"
 #include "private/kitemlistroleeditor.h"
 #include "private/kitemviewsutils.h"
@@ -18,6 +18,7 @@
 #include <KIconUtils>
 #include <KRatingPainter>
 #include <KStringHandler>
+#include <klocalizedstring.h>
 
 #include <QApplication>
 #include <QGraphicsScene>
@@ -276,7 +277,7 @@ KStandardItemListWidget::KStandardItemListWidget(KItemListWidgetInformant *infor
     , m_expansionArea()
     , m_customTextColor()
     , m_additionalInfoTextColor()
-    , m_overlay()
+    , m_overlays()
     , m_rating()
     , m_roleEditor(nullptr)
     , m_oldRoleEditor(nullptr)
@@ -345,24 +346,39 @@ void KStandardItemListWidget::paint(QPainter *painter, const QStyleOptionGraphic
         drawSiblingsInformation(painter);
     }
 
+    auto pixmap = isHovered() ? m_hoverPixmap : m_pixmap;
+    if (!m_overlays.isEmpty()) {
+        const qreal dpr = KItemViewsUtils::devicePixelRatio(this);
+
+        const bool iconOnTop = (m_layout == IconsLayout);
+        const KItemListStyleOption &option = styleOption();
+        const qreal padding = option.padding;
+
+        const int widgetIconSize = iconSize();
+        const int maxIconWidth = iconOnTop ? size().width() - 2 * padding : widgetIconSize;
+        const int maxIconHeight = widgetIconSize;
+
+        pixmap = addOverlays(pixmap, m_overlays, QSize(maxIconWidth, maxIconHeight), dpr);
+    }
+
     const KItemListStyleOption &itemListStyleOption = styleOption();
-    if (isHovered() && !m_pixmap.isNull()) {
+    if (isHovered() && !pixmap.isNull()) {
         if (hoverOpacity() < 1.0) {
             /*
-             * Linear interpolation between m_pixmap and m_hoverPixmap.
+             * Linear interpolation between pixmap and m_hoverPixmap.
              *
              * Note that this cannot be achieved by painting m_hoverPixmap over
-             * m_pixmap, even if the opacities are adjusted. For details see
+             * pixmap, even if the opacities are adjusted. For details see
              * https://git.reviewboard.kde.org/r/109614/
              */
-            // Paint pixmap1 so that pixmap1 = m_pixmap * (1.0 - hoverOpacity())
-            QPixmap pixmap1(m_pixmap.size());
-            pixmap1.setDevicePixelRatio(m_pixmap.devicePixelRatio());
+            // Paint pixmap1 so that pixmap1 = pixmap * (1.0 - hoverOpacity())
+            QPixmap pixmap1(pixmap.size());
+            pixmap1.setDevicePixelRatio(pixmap.devicePixelRatio());
             pixmap1.fill(Qt::transparent);
             {
                 QPainter p(&pixmap1);
                 p.setOpacity(1.0 - hoverOpacity());
-                p.drawPixmap(0, 0, m_pixmap);
+                p.drawPixmap(0, 0, pixmap);
             }
 
             // Paint pixmap2 so that pixmap2 = m_hoverPixmap * hoverOpacity()
@@ -376,8 +392,8 @@ void KStandardItemListWidget::paint(QPainter *painter, const QStyleOptionGraphic
             }
 
             // Paint pixmap2 on pixmap1 using CompositionMode_Plus
-            // Now pixmap1 = pixmap2 + m_pixmap * (1.0 - hoverOpacity())
-            //             = m_hoverPixmap * hoverOpacity() + m_pixmap * (1.0 - hoverOpacity())
+            // Now pixmap1 = pixmap2 + pixmap * (1.0 - hoverOpacity())
+            //             = m_hoverPixmap * hoverOpacity() + pixmap * (1.0 - hoverOpacity())
             {
                 QPainter p(&pixmap1);
                 p.setCompositionMode(QPainter::CompositionMode_Plus);
@@ -387,10 +403,10 @@ void KStandardItemListWidget::paint(QPainter *painter, const QStyleOptionGraphic
             // Finally paint pixmap1 on the widget
             drawPixmap(painter, pixmap1);
         } else {
-            drawPixmap(painter, m_hoverPixmap);
+            drawPixmap(painter, pixmap);
         }
-    } else if (!m_pixmap.isNull()) {
-        drawPixmap(painter, m_pixmap);
+    } else if (!pixmap.isNull()) {
+        drawPixmap(painter, pixmap);
     }
 
     painter->setFont(m_customizedFont);
@@ -632,7 +648,7 @@ void KStandardItemListWidget::startActivateSoonAnimation(int timeUntilActivation
     m_activateSoonAnimation->setEndValue(1.0);
     m_activateSoonAnimation->setDuration(timeUntilActivation);
 
-    const QVariant originalIconName{data()["iconName"]};
+    const QVariant originalIconName{value("iconName")};
     connect(m_activateSoonAnimation, &QVariantAnimation::valueChanged, this, [originalIconName, this](const QVariant &value) {
         auto progress = value.toFloat();
 
@@ -667,7 +683,7 @@ void KStandardItemListWidget::startActivateSoonAnimation(int timeUntilActivation
 
 bool KStandardItemListWidget::isIconControlledByActivateSoonAnimation() const
 {
-    return m_activateSoonAnimation && data()["iconName"] == "folder-open";
+    return m_activateSoonAnimation && value("iconName") == "folder-open";
 }
 
 KItemListWidgetInformant *KStandardItemListWidget::createInformant()
@@ -685,7 +701,6 @@ void KStandardItemListWidget::invalidateIconCache()
 {
     m_dirtyContent = true;
     m_dirtyContentRoles.insert("iconPixmap");
-    m_dirtyContentRoles.insert("iconOverlays");
 }
 
 void KStandardItemListWidget::refreshCache()
@@ -737,16 +752,21 @@ QColor KStandardItemListWidget::textColor(const QWidget &widget) const
     return styleOption().palette.color(group, role);
 }
 
-void KStandardItemListWidget::setOverlay(const QPixmap &overlay)
+void KStandardItemListWidget::setOverlays(QHash<Qt::Corner, QString> &overlays)
 {
-    m_overlay = overlay;
+    if (overlays == m_overlays) {
+        return;
+    }
+
+    m_overlays = overlays;
     m_dirtyContent = true;
+    m_dirtyContentRoles.insert("iconOverlays");
     update();
 }
 
-QPixmap KStandardItemListWidget::overlay() const
+QHash<Qt::Corner, QString> KStandardItemListWidget::overlays() const
 {
-    return m_overlay;
+    return m_overlays;
 }
 
 QString KStandardItemListWidget::roleText(const QByteArray &role, const QHash<QByteArray, QVariant> &values) const
@@ -1102,17 +1122,17 @@ void KStandardItemListWidget::updatePixmapCache()
                 // use a generic icon as fallback
                 iconName = QStringLiteral("unknown");
             }
-            const QStringList overlays = values["iconOverlays"].toStringList();
             const bool hasFocus = scene()->views()[0]->parentWidget()->hasFocus();
             m_pixmap = pixmapForIcon(iconName,
-                                     overlays,
-                                     maxIconHeight,
+                                     QSize(maxIconWidth, maxIconHeight),
                                      m_layout != IconsLayout && isActiveWindow() && isSelected() && hasFocus ? QIcon::Selected : QIcon::Normal);
 
-        } else if (m_pixmap.width() / m_pixmap.devicePixelRatio() != maxIconWidth || m_pixmap.height() / m_pixmap.devicePixelRatio() != maxIconHeight) {
-            // A custom pixmap has been applied. Assure that the pixmap
-            // is scaled to the maximum available size.
-            KPixmapModifier::scale(m_pixmap, QSize(maxIconWidth, maxIconHeight) * dpr);
+        } else {
+            if (m_pixmap.width() / m_pixmap.devicePixelRatio() != maxIconWidth || m_pixmap.height() / m_pixmap.devicePixelRatio() != maxIconHeight) {
+                // A custom pixmap has been applied. Assure that the pixmap
+                // is scaled to the maximum available size.
+                KPixmapModifier::scale(m_pixmap, QSize(maxIconWidth, maxIconHeight) * dpr);
+            }
         }
 
         if (m_pixmap.isNull()) {
@@ -1121,14 +1141,7 @@ void KStandardItemListWidget::updatePixmapCache()
         }
 
         if (m_isCut) {
-#if KICONTHEMES_VERSION >= QT_VERSION_CHECK(6, 5, 0)
             KIconEffect::toDisabled(m_pixmap);
-#else
-            QImage img = m_pixmap.toImage();
-            KIconEffect::toGray(img, 1);
-            KIconEffect::semiTransparent(img);
-            m_pixmap = QPixmap::fromImage(img);
-#endif
         }
 
         if (m_isHidden) {
@@ -1147,11 +1160,6 @@ void KStandardItemListWidget::updatePixmapCache()
         }
     }
 
-    if (!m_overlay.isNull()) {
-        QPainter painter(&m_pixmap);
-        painter.drawPixmap(0, (m_pixmap.height() - m_overlay.height()) / m_pixmap.devicePixelRatio(), m_overlay);
-    }
-
     int scaledIconSize = 0;
     if (iconOnTop) {
         const TextInfo *textInfo = m_textInfo.value("text");
@@ -1201,13 +1209,7 @@ void KStandardItemListWidget::updatePixmapCache()
     // Prepare the pixmap that is used when the item gets hovered
     if (isHovered()) {
         m_hoverPixmap = m_pixmap;
-#if KICONTHEMES_VERSION >= QT_VERSION_CHECK(6, 5, 0)
         KIconEffect::toActive(m_hoverPixmap);
-#else
-        QImage img = m_pixmap.toImage();
-        KIconEffect::toGamma(img, 0.7);
-        m_hoverPixmap = QPixmap::fromImage(img);
-#endif
     } else if (hoverOpacity() <= 0.0) {
         // No hover animation is ongoing. Clear m_hoverPixmap to save memory.
         m_hoverPixmap = QPixmap();
@@ -1285,6 +1287,31 @@ void KStandardItemListWidget::updateTextsCache()
     }
 }
 
+QString KStandardItemListWidget::elideText(QString text, qreal maxWidth) const
+{
+    if (ContentDisplaySettings::elidingMode() == ContentDisplaySettings::ElidingMode::Middle) {
+        return m_customizedFontMetrics.elidedText(text, Qt::ElideMiddle, maxWidth);
+    }
+
+    if (ContentDisplaySettings::elidingMode() == ContentDisplaySettings::ElidingMode::Right) {
+        qsizetype lastDotPosition = text.lastIndexOf(".");
+        QString extension = text.mid(lastDotPosition);
+
+        if (m_customizedFontMetrics.horizontalAdvance(QStringLiteral("…") + extension) > maxWidth) {
+            extension = "";
+            lastDotPosition = text.size();
+        }
+
+        maxWidth -= m_customizedFontMetrics.horizontalAdvance(extension);
+        QString leftPart = m_customizedFontMetrics.elidedText(text.left(lastDotPosition), Qt::ElideRight, maxWidth);
+
+        return leftPart + extension;
+    }
+
+    Q_UNREACHABLE();
+    return text;
+}
+
 QString KStandardItemListWidget::escapeString(const QString &text) const
 {
     QString escaped(text);
@@ -1345,7 +1372,7 @@ void KStandardItemListWidget::updateIconsLayoutTextCache()
                 qreal lastLineWidth;
                 do {
                     QString lastTextLine = nameText.mid(line.textStart());
-                    lastTextLine = m_customizedFontMetrics.elidedText(lastTextLine, Qt::ElideMiddle, elidingWidth);
+                    lastTextLine = elideText(lastTextLine, elidingWidth);
                     const QString elidedText = nameText.left(line.textStart()) + lastTextLine;
                     nameTextInfo->staticText.setText(elidedText);
 
@@ -1393,7 +1420,7 @@ void KStandardItemListWidget::updateIconsLayoutTextCache()
             textLine.setLineWidth(maxWidth);
             requiredWidth = textLine.naturalTextWidth();
             if (requiredWidth > maxWidth) {
-                const QString elidedText = m_customizedFontMetrics.elidedText(text, Qt::ElideMiddle, maxWidth);
+                const QString elidedText = m_customizedFontMetrics.elidedText(text, Qt::ElideRight, maxWidth);
                 textInfo->staticText.setText(elidedText);
                 requiredWidth = m_customizedFontMetrics.horizontalAdvance(elidedText);
             } else if (role == "rating") {
@@ -1445,8 +1472,13 @@ void KStandardItemListWidget::updateCompactLayoutTextCache()
         qreal requiredWidth = m_customizedFontMetrics.horizontalAdvance(text);
         if (requiredWidth > maxWidth) {
             requiredWidth = maxWidth;
-            const QString elidedText = m_customizedFontMetrics.elidedText(text, Qt::ElideMiddle, maxWidth);
-            textInfo->staticText.setText(elidedText);
+            if (role == "text") {
+                const QString elidedText = elideText(text, maxWidth);
+                textInfo->staticText.setText(elidedText);
+            } else {
+                const QString elidedText = m_customizedFontMetrics.elidedText(text, Qt::ElideRight, maxWidth);
+                textInfo->staticText.setText(elidedText);
+            }
         }
 
         textInfo->pos = QPointF(x, y);
@@ -1505,7 +1537,11 @@ void KStandardItemListWidget::updateDetailsLayoutTextCache()
         }
 
         if (requiredWidth > availableTextWidth) {
-            text = m_customizedFontMetrics.elidedText(text, Qt::ElideMiddle, availableTextWidth);
+            if (isTextRole) {
+                text = elideText(text, availableTextWidth);
+            } else {
+                text = m_customizedFontMetrics.elidedText(text, Qt::ElideRight, availableTextWidth);
+            }
             requiredWidth = m_customizedFontMetrics.horizontalAdvance(text);
         }
 
@@ -1551,6 +1587,76 @@ void KStandardItemListWidget::updateAdditionalInfoTextColor()
         QColor((c1.red() * p1 + c2.red() * p2) / 100, (c1.green() * p1 + c2.green() * p2) / 100, (c1.blue() * p1 + c2.blue() * p2) / 100);
 }
 
+QPixmap
+KStandardItemListWidget::addOverlays(const QPixmap &pixmap, const QHash<Qt::Corner, QString> &overlays, const QSize &size, qreal dpr, QIcon::Mode mode) const
+{
+    // similar to KIconUtils::addOverlays, keep in sync preferrably
+    if (overlays.isEmpty()) {
+        return pixmap;
+    }
+
+    int width = size.width();
+    int height = size.height();
+    const int iconSize = qMin(width, height);
+
+    // Determine the overlay icon
+    int overlaySize;
+    if (iconSize < 32) {
+        overlaySize = 8;
+    } else if (iconSize <= 48) {
+        overlaySize = 16;
+    } else if (iconSize <= 96) {
+        overlaySize = 22;
+    } else if (iconSize < 256) {
+        overlaySize = 32;
+    } else {
+        overlaySize = 64;
+    }
+
+    auto phyiscalSize = QSize(std::clamp(pixmap.width(), qFloor(2 * overlaySize * dpr), qFloor(size.width() * dpr)),
+                              std::clamp(pixmap.height(), qFloor(2 * overlaySize * dpr), qFloor(size.height() * dpr)));
+
+    QPixmap output(phyiscalSize);
+    output.setDevicePixelRatio(dpr);
+    output.fill(Qt::transparent);
+
+    QPainter painter(&output);
+    painter.drawPixmap(qFloor(phyiscalSize.width() / dpr / 2) - qFloor(pixmap.width() / pixmap.devicePixelRatio() / 2),
+                       // align the icon to the bottom to match the behavior elsewhere
+                       qFloor(phyiscalSize.height() / dpr) - qFloor(pixmap.height() / pixmap.devicePixelRatio()),
+                       pixmap);
+
+    width = qCeil(phyiscalSize.width() / dpr);
+    height = qCeil(phyiscalSize.height() / dpr);
+
+    // Iterate over stored overlays
+    for (const auto &[corner, overlay] : overlays.asKeyValueRange()) {
+        const QPixmap overlayPixmap = QIcon::fromTheme(overlay).pixmap(QSize{overlaySize, overlaySize}, dpr, mode);
+        if (overlayPixmap.isNull()) {
+            continue;
+        }
+
+        QPoint startPoint;
+        switch (corner) {
+        case Qt::BottomLeftCorner:
+            startPoint = QPoint{0, height - overlaySize};
+            break;
+        case Qt::BottomRightCorner:
+            startPoint = QPoint{width - overlaySize, height - overlaySize};
+            break;
+        case Qt::TopRightCorner:
+            startPoint = QPoint{width - overlaySize, 0};
+            break;
+        case Qt::TopLeftCorner:
+            startPoint = QPoint{};
+            break;
+        }
+        painter.drawPixmap(startPoint, overlayPixmap);
+    }
+
+    return output;
+}
+
 void KStandardItemListWidget::drawPixmap(QPainter *painter, const QPixmap &pixmap)
 {
     if (m_scaledPixmapSize != pixmap.size() / pixmap.devicePixelRatio()) {
@@ -1638,15 +1744,15 @@ void KStandardItemListWidget::closeRoleEditor()
     m_roleEditor = nullptr;
 }
 
-QPixmap KStandardItemListWidget::pixmapForIcon(const QString &name, const QStringList &overlays, int size, QIcon::Mode mode) const
+QPixmap KStandardItemListWidget::pixmapForIcon(const QString &name, const QSize &size, QIcon::Mode mode) const
 {
     static const QIcon fallbackIcon = QIcon::fromTheme(QStringLiteral("unknown"));
     const qreal dpr = KItemViewsUtils::devicePixelRatio(this);
 
-    size *= dpr;
+    int iconHeight = size.height();
+    QSize iconSize = QSize(iconHeight, iconHeight);
 
-    const QString key = "KStandardItemListWidget:" % name % ":" % overlays.join(QLatin1Char(':')) % ":" % QString::number(size) % "@" % QString::number(dpr)
-        % ":" % QString::number(mode);
+    const QString key = "KStandardItemListWidget:" % name % ":" % QString::number(iconHeight) % "@" % QString::number(dpr) % ":" % QString::number(mode);
     QPixmap pixmap;
 
     if (!QPixmapCache::find(key, &pixmap)) {
@@ -1654,25 +1760,15 @@ QPixmap KStandardItemListWidget::pixmapForIcon(const QString &name, const QStrin
         if (icon.isNull()) {
             icon = QIcon(name);
         }
-        if (icon.isNull() || icon.pixmap(size / dpr, size / dpr, mode).isNull()) {
-            icon = fallbackIcon;
+        if (!icon.isNull()) {
+            pixmap = icon.pixmap(iconSize, dpr, mode);
         }
-
-        pixmap = icon.pixmap(QSize(size / dpr, size / dpr), dpr, mode);
-        if (pixmap.width() != size || pixmap.height() != size) {
-            KPixmapModifier::scale(pixmap, QSize(size, size));
+        if (pixmap.isNull()) {
+            icon = fallbackIcon;
+            pixmap = icon.pixmap(iconSize, dpr, mode);
         }
-
-        // Strangely KFileItem::overlays() returns empty string-values, so
-        // we need to check first whether an overlay must be drawn at all.
-        for (const QString &overlay : overlays) {
-            if (!overlay.isEmpty()) {
-                // There is at least one overlay, draw all overlays above m_pixmap
-                // and cancel the check
-                const QSize size = pixmap.size();
-                pixmap = KIconUtils::addOverlays(pixmap, overlays).pixmap(size, mode);
-                break;
-            }
+        if (pixmap.width() != iconHeight * dpr || pixmap.height() != iconHeight * dpr) {
+            KPixmapModifier::scale(pixmap, iconSize * dpr);
         }
 
         QPixmapCache::insert(key, pixmap);