1/*
2 SPDX-FileCopyrightText: 2013 Martin Klapetek <mklapetek@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5*/
6
7#include <array>
8
9#include "kiconutils.h"
10
11#include <QHash>
12#include <QIconEngine>
13#include <QPainter>
14
15class KOverlayIconEngine : public QIconEngine
16{
17public:
18 KOverlayIconEngine(const QIcon &icon, const QIcon &overlay, Qt::Corner position);
19 KOverlayIconEngine(const QIcon &icon, const QHash<Qt::Corner, QIcon> &overlays);
20 KOverlayIconEngine(const QIcon &icon, const QStringList &overlays);
21 void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override;
22 QIconEngine *clone() const override;
23
24 QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
25 QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
26
27 void addPixmap(const QPixmap &pixmap, QIcon::Mode mode, QIcon::State state) override;
28 void addFile(const QString &fileName, const QSize &size, QIcon::Mode mode, QIcon::State state) override;
29
30 void virtual_hook(int id, void *data) override;
31
32private:
33 QIcon m_base;
34 QHash<Qt::Corner, QIcon> m_overlays;
35};
36
37KOverlayIconEngine::KOverlayIconEngine(const QIcon &icon, const QIcon &overlay, Qt::Corner position)
38 : QIconEngine()
39 , m_base(icon)
40{
41 m_overlays.insert(key: position, value: overlay);
42}
43
44KOverlayIconEngine::KOverlayIconEngine(const QIcon &icon, const QHash<Qt::Corner, QIcon> &overlays)
45 : QIconEngine()
46 , m_base(icon)
47 , m_overlays(overlays)
48{
49}
50
51KOverlayIconEngine::KOverlayIconEngine(const QIcon &icon, const QStringList &overlays)
52 : QIconEngine()
53 , m_base(icon)
54{
55 const std::array<Qt::Corner, 4> indexToCorner{
56 Qt::BottomRightCorner,
57 Qt::BottomLeftCorner,
58 Qt::TopLeftCorner,
59 Qt::TopRightCorner,
60 };
61
62 // static_cast becaue size() returns a qsizetype in Qt6
63 const int count = std::min(a: 4, b: static_cast<int>(overlays.size()));
64
65 m_overlays.reserve(size: count);
66
67 for (int i = 0; i < count; i++) {
68 m_overlays.insert(key: indexToCorner[i], value: QIcon::fromTheme(name: overlays.at(i)));
69 }
70}
71
72QIconEngine *KOverlayIconEngine::clone() const
73{
74 return new KOverlayIconEngine(*this);
75}
76
77QSize KOverlayIconEngine::actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state)
78{
79 return m_base.actualSize(size, mode, state);
80}
81
82QPixmap KOverlayIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
83{
84 QPixmap pixmap(size);
85 pixmap.fill(fillColor: Qt::transparent);
86 QPainter p(&pixmap);
87
88 paint(painter: &p, rect: pixmap.rect(), mode, state);
89
90 return pixmap;
91}
92
93void KOverlayIconEngine::addPixmap(const QPixmap &pixmap, QIcon::Mode mode, QIcon::State state)
94{
95 m_base.addPixmap(pixmap, mode, state);
96}
97
98void KOverlayIconEngine::addFile(const QString &fileName, const QSize &size, QIcon::Mode mode, QIcon::State state)
99{
100 m_base.addFile(fileName, size, mode, state);
101}
102
103void KOverlayIconEngine::virtual_hook(int id, void *data)
104{
105 if (id == QIconEngine::ScaledPixmapHook) {
106 auto *info = reinterpret_cast<ScaledPixmapArgument *>(data);
107
108 QPixmap pixmap(info->size);
109 pixmap.setDevicePixelRatio(info->scale);
110 pixmap.fill(fillColor: Qt::transparent);
111
112 QRect rect = pixmap.rect();
113
114 const QRect logicalRect(rect.x() / info->scale, rect.y() / info->scale, rect.width() / info->scale, rect.height() / info->scale);
115 QPainter p(&pixmap);
116 paint(painter: &p, rect: logicalRect, mode: info->mode, state: info->state);
117
118 info->pixmap = pixmap;
119
120 return;
121 }
122 QIconEngine::virtual_hook(id, data);
123}
124
125void KOverlayIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state)
126{
127 // Paint the base icon as the first layer
128 m_base.paint(painter, rect, alignment: Qt::AlignCenter, mode, state);
129
130 if (m_overlays.isEmpty()) {
131 return;
132 }
133
134 const int width = rect.width();
135 const int height = rect.height();
136 const int iconSize = qMin(a: width, b: height);
137 // Determine the overlay icon size
138 int overlaySize;
139 if (iconSize < 32) {
140 overlaySize = 8;
141 } else if (iconSize <= 48) {
142 overlaySize = 16;
143 } else if (iconSize <= 64) {
144 overlaySize = 22;
145 } else if (iconSize <= 96) {
146 overlaySize = 32;
147 } else if (iconSize <= 128) {
148 overlaySize = 48;
149 } else {
150 overlaySize = (int)(iconSize / 4);
151 }
152
153 // Iterate over stored overlays
154 QHash<Qt::Corner, QIcon>::const_iterator i = m_overlays.constBegin();
155 while (i != m_overlays.constEnd()) {
156 const QPixmap overlayPixmap = i.value().pixmap(w: overlaySize, h: overlaySize, mode, state);
157 if (overlayPixmap.isNull()) {
158 ++i;
159 continue;
160 }
161
162 QPoint startPoint;
163 switch (i.key()) {
164 case Qt::BottomLeftCorner:
165 startPoint = QPoint(2, height - overlaySize - 2);
166 break;
167 case Qt::BottomRightCorner:
168 startPoint = QPoint(width - overlaySize - 2, height - overlaySize - 2);
169 break;
170 case Qt::TopRightCorner:
171 startPoint = QPoint(width - overlaySize - 2, 2);
172 break;
173 case Qt::TopLeftCorner:
174 startPoint = QPoint(2, 2);
175 break;
176 }
177
178 // Draw the overlay pixmap
179 painter->drawPixmap(p: startPoint, pm: overlayPixmap);
180
181 ++i;
182 }
183}
184
185// ============================================================================
186
187namespace KIconUtils
188{
189QIcon addOverlay(const QIcon &icon, const QIcon &overlay, Qt::Corner position)
190{
191 return QIcon(new KOverlayIconEngine(icon, overlay, position));
192}
193
194QIcon addOverlays(const QIcon &icon, const QHash<Qt::Corner, QIcon> &overlays)
195{
196 return QIcon(new KOverlayIconEngine(icon, overlays));
197}
198
199QIcon addOverlays(const QIcon &icon, const QStringList &overlays)
200{
201 if (overlays.count() == 0) {
202 return icon;
203 }
204
205 return QIcon(new KOverlayIconEngine(icon, overlays));
206}
207
208QIcon addOverlays(const QString &iconName, const QStringList &overlays)
209{
210 const QIcon icon = QIcon::fromTheme(name: iconName);
211
212 if (overlays.count() == 0) {
213 return icon;
214 }
215
216 return QIcon(new KOverlayIconEngine(icon, overlays));
217}
218}
219

source code of kguiaddons/src/util/kiconutils.cpp