File indexing completed on 2024-04-14 14:21:01

0001 /*
0002     SPDX-FileCopyrightText: 2013 Martin Klapetek <mklapetek@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include <array>
0008 
0009 #include "kiconutils.h"
0010 
0011 #include <QHash>
0012 #include <QIconEngine>
0013 #include <QPainter>
0014 
0015 class KOverlayIconEngine : public QIconEngine
0016 {
0017 public:
0018     KOverlayIconEngine(const QIcon &icon, const QIcon &overlay, Qt::Corner position);
0019     KOverlayIconEngine(const QIcon &icon, const QHash<Qt::Corner, QIcon> &overlays);
0020     KOverlayIconEngine(const QIcon &icon, const QStringList &overlays);
0021     void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override;
0022     QIconEngine *clone() const override;
0023 
0024     QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
0025     QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
0026 
0027     void addPixmap(const QPixmap &pixmap, QIcon::Mode mode, QIcon::State state) override;
0028     void addFile(const QString &fileName, const QSize &size, QIcon::Mode mode, QIcon::State state) override;
0029 
0030     void virtual_hook(int id, void *data) override;
0031 
0032 private:
0033     QIcon m_base;
0034     QHash<Qt::Corner, QIcon> m_overlays;
0035 };
0036 
0037 KOverlayIconEngine::KOverlayIconEngine(const QIcon &icon, const QIcon &overlay, Qt::Corner position)
0038     : QIconEngine()
0039     , m_base(icon)
0040 {
0041     m_overlays.insert(position, overlay);
0042 }
0043 
0044 KOverlayIconEngine::KOverlayIconEngine(const QIcon &icon, const QHash<Qt::Corner, QIcon> &overlays)
0045     : QIconEngine()
0046     , m_base(icon)
0047     , m_overlays(overlays)
0048 {
0049 }
0050 
0051 KOverlayIconEngine::KOverlayIconEngine(const QIcon &icon, const QStringList &overlays)
0052     : QIconEngine()
0053     , m_base(icon)
0054 {
0055     const std::array<Qt::Corner, 4> indexToCorner{
0056         Qt::BottomRightCorner,
0057         Qt::BottomLeftCorner,
0058         Qt::TopLeftCorner,
0059         Qt::TopRightCorner,
0060     };
0061 
0062     // static_cast becaue size() returns a qsizetype in Qt6
0063     const int count = std::min(4, static_cast<int>(overlays.size()));
0064 
0065     m_overlays.reserve(count);
0066 
0067     for (int i = 0; i < count; i++) {
0068         m_overlays.insert(indexToCorner[i], QIcon::fromTheme(overlays.at(i)));
0069     }
0070 }
0071 
0072 QIconEngine *KOverlayIconEngine::clone() const
0073 {
0074     return new KOverlayIconEngine(*this);
0075 }
0076 
0077 QSize KOverlayIconEngine::actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state)
0078 {
0079     return m_base.actualSize(size, mode, state);
0080 }
0081 
0082 QPixmap KOverlayIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
0083 {
0084     QPixmap pixmap(size);
0085     pixmap.fill(Qt::transparent);
0086     QPainter p(&pixmap);
0087 
0088     paint(&p, pixmap.rect(), mode, state);
0089 
0090     return pixmap;
0091 }
0092 
0093 void KOverlayIconEngine::addPixmap(const QPixmap &pixmap, QIcon::Mode mode, QIcon::State state)
0094 {
0095     m_base.addPixmap(pixmap, mode, state);
0096 }
0097 
0098 void KOverlayIconEngine::addFile(const QString &fileName, const QSize &size, QIcon::Mode mode, QIcon::State state)
0099 {
0100     m_base.addFile(fileName, size, mode, state);
0101 }
0102 
0103 void KOverlayIconEngine::virtual_hook(int id, void *data)
0104 {
0105     if (id == QIconEngine::ScaledPixmapHook) {
0106         auto *info = reinterpret_cast<ScaledPixmapArgument *>(data);
0107 
0108         QPixmap pixmap(info->size);
0109         pixmap.setDevicePixelRatio(info->scale);
0110         pixmap.fill(Qt::transparent);
0111 
0112         QRect rect = pixmap.rect();
0113 
0114         const QRect logicalRect(rect.x() / info->scale, rect.y() / info->scale, rect.width() / info->scale, rect.height() / info->scale);
0115         QPainter p(&pixmap);
0116         paint(&p, logicalRect, info->mode, info->state);
0117 
0118         info->pixmap = pixmap;
0119 
0120         return;
0121     }
0122     QIconEngine::virtual_hook(id, data);
0123 }
0124 
0125 void KOverlayIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state)
0126 {
0127     // Paint the base icon as the first layer
0128     m_base.paint(painter, rect, Qt::AlignCenter, mode, state);
0129 
0130     if (m_overlays.isEmpty()) {
0131         return;
0132     }
0133 
0134     const int width = rect.width();
0135     const int height = rect.height();
0136     const int iconSize = qMin(width, height);
0137     // Determine the overlay icon size
0138     int overlaySize;
0139     if (iconSize < 32) {
0140         overlaySize = 8;
0141     } else if (iconSize <= 48) {
0142         overlaySize = 16;
0143     } else if (iconSize <= 64) {
0144         overlaySize = 22;
0145     } else if (iconSize <= 96) {
0146         overlaySize = 32;
0147     } else if (iconSize <= 128) {
0148         overlaySize = 48;
0149     } else {
0150         overlaySize = (int)(iconSize / 4);
0151     }
0152 
0153     // Iterate over stored overlays
0154     QHash<Qt::Corner, QIcon>::const_iterator i = m_overlays.constBegin();
0155     while (i != m_overlays.constEnd()) {
0156         const QPixmap overlayPixmap = i.value().pixmap(overlaySize, overlaySize, mode, state);
0157         if (overlayPixmap.isNull()) {
0158             ++i;
0159             continue;
0160         }
0161 
0162         QPoint startPoint;
0163         switch (i.key()) {
0164         case Qt::BottomLeftCorner:
0165             startPoint = QPoint(2, height - overlaySize - 2);
0166             break;
0167         case Qt::BottomRightCorner:
0168             startPoint = QPoint(width - overlaySize - 2, height - overlaySize - 2);
0169             break;
0170         case Qt::TopRightCorner:
0171             startPoint = QPoint(width - overlaySize - 2, 2);
0172             break;
0173         case Qt::TopLeftCorner:
0174             startPoint = QPoint(2, 2);
0175             break;
0176         }
0177 
0178         // Draw the overlay pixmap
0179         painter->drawPixmap(startPoint, overlayPixmap);
0180 
0181         ++i;
0182     }
0183 }
0184 
0185 // ============================================================================
0186 
0187 namespace KIconUtils
0188 {
0189 QIcon addOverlay(const QIcon &icon, const QIcon &overlay, Qt::Corner position)
0190 {
0191     return QIcon(new KOverlayIconEngine(icon, overlay, position));
0192 }
0193 
0194 QIcon addOverlays(const QIcon &icon, const QHash<Qt::Corner, QIcon> &overlays)
0195 {
0196     return QIcon(new KOverlayIconEngine(icon, overlays));
0197 }
0198 
0199 QIcon addOverlays(const QIcon &icon, const QStringList &overlays)
0200 {
0201     if (overlays.count() == 0) {
0202         return icon;
0203     }
0204 
0205     return QIcon(new KOverlayIconEngine(icon, overlays));
0206 }
0207 
0208 QIcon addOverlays(const QString &iconName, const QStringList &overlays)
0209 {
0210     const QIcon icon = QIcon::fromTheme(iconName);
0211 
0212     if (overlays.count() == 0) {
0213         return icon;
0214     }
0215 
0216     return QIcon(new KOverlayIconEngine(icon, overlays));
0217 }
0218 }