1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2007-2008 Sebastian Trueg <trueg@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "kratingpainter.h"
9
10#include <QIcon>
11#include <QPainter>
12#include <QPixmap>
13#include <QPoint>
14#include <QRect>
15
16class KRatingPainterPrivate
17{
18public:
19 QPixmap getPixmap(int size, qreal dpr, QIcon::State state = QIcon::On);
20
21 int maxRating = 10;
22 int spacing = 0;
23 QIcon icon;
24 bool isEnabled = true;
25 bool bHalfSteps = true;
26 Qt::Alignment alignment = Qt::AlignCenter;
27 Qt::LayoutDirection direction = Qt::LeftToRight;
28 QPixmap customPixmap;
29};
30
31static void imageToGrayScale(QImage &img, float value);
32static void imageToSemiTransparent(QImage &img);
33
34QPixmap KRatingPainterPrivate::getPixmap(int size, qreal dpr, QIcon::State state)
35{
36 bool transformToOffState = (state == QIcon::Off);
37 QPixmap p;
38
39 if (!customPixmap.isNull()) {
40 p = customPixmap.scaled(s: QSize(size, size));
41 } else {
42 QIcon _icon(icon);
43 if (_icon.isNull()) {
44 if (state == QIcon::On) {
45 _icon = QIcon::fromTheme(QStringLiteral("rating"));
46 } else if (QIcon::hasThemeIcon(QStringLiteral("rating-unrated"))) {
47 _icon = QIcon::fromTheme(QStringLiteral("rating-unrated"));
48 transformToOffState = false; // no need because we already have the perfect icon
49 } else {
50 _icon = QIcon::fromTheme(QStringLiteral("rating")); // will be transformed to the "off" state
51 }
52 }
53 p = _icon.pixmap(size: QSize(size, size), devicePixelRatio: dpr);
54 }
55
56 if (transformToOffState) {
57 QImage img = p.toImage().convertToFormat(f: QImage::Format_ARGB32);
58 imageToGrayScale(img, value: 1.0);
59 // The icon might have already been monochrome, so we also need to make it semi-transparent to see a difference.
60 imageToSemiTransparent(img);
61 return QPixmap::fromImage(image: img);
62 }
63 return p;
64}
65
66KRatingPainter::KRatingPainter()
67 : d(new KRatingPainterPrivate())
68{
69}
70
71KRatingPainter::~KRatingPainter() = default;
72
73int KRatingPainter::maxRating() const
74{
75 return d->maxRating;
76}
77
78bool KRatingPainter::halfStepsEnabled() const
79{
80 return d->bHalfSteps;
81}
82
83Qt::Alignment KRatingPainter::alignment() const
84{
85 return d->alignment;
86}
87
88Qt::LayoutDirection KRatingPainter::layoutDirection() const
89{
90 return d->direction;
91}
92
93QIcon KRatingPainter::icon() const
94{
95 return d->icon;
96}
97
98bool KRatingPainter::isEnabled() const
99{
100 return d->isEnabled;
101}
102
103QPixmap KRatingPainter::customPixmap() const
104{
105 return d->customPixmap;
106}
107
108int KRatingPainter::spacing() const
109{
110 return d->spacing;
111}
112
113void KRatingPainter::setMaxRating(int max)
114{
115 d->maxRating = max;
116}
117
118void KRatingPainter::setHalfStepsEnabled(bool enabled)
119{
120 d->bHalfSteps = enabled;
121}
122
123void KRatingPainter::setAlignment(Qt::Alignment align)
124{
125 d->alignment = align;
126}
127
128void KRatingPainter::setLayoutDirection(Qt::LayoutDirection direction)
129{
130 d->direction = direction;
131}
132
133void KRatingPainter::setIcon(const QIcon &icon)
134{
135 d->icon = icon;
136}
137
138void KRatingPainter::setEnabled(bool enabled)
139{
140 d->isEnabled = enabled;
141}
142
143void KRatingPainter::setCustomPixmap(const QPixmap &pixmap)
144{
145 d->customPixmap = pixmap;
146}
147
148void KRatingPainter::setSpacing(int s)
149{
150 d->spacing = qMax(a: 0, b: s);
151}
152
153static void imageToGrayScale(QImage &img, float value)
154{
155 QRgb *data = (QRgb *)img.bits();
156 QRgb *end = data + img.width() * img.height();
157
158 unsigned char gray;
159 unsigned char val = (unsigned char)(255.0 * value);
160 while (data != end) {
161 gray = qGray(rgb: *data);
162 *data = qRgba(r: (val * gray + (255 - val) * qRed(rgb: *data)) >> 8,
163 g: (val * gray + (255 - val) * qGreen(rgb: *data)) >> 8,
164 b: (val * gray + (255 - val) * qBlue(rgb: *data)) >> 8,
165 a: qAlpha(rgb: *data));
166 ++data;
167 }
168}
169
170static void imageToSemiTransparent(QImage &img)
171{
172 QRgb *data = (QRgb *)img.bits();
173 QRgb *end = data + img.width() * img.height();
174
175 while (data != end) {
176 *data = qRgba(r: qRed(rgb: *data), g: qGreen(rgb: *data), b: qBlue(rgb: *data), a: qAlpha(rgb: *data) >> 1);
177 ++data;
178 }
179}
180
181void KRatingPainter::paint(QPainter *painter, const QRect &rect, int rating, int hoverRating) const
182{
183 const qreal dpr = painter->device()->devicePixelRatio();
184 rating = qMin(a: rating, b: d->maxRating);
185 hoverRating = qMin(a: hoverRating, b: d->maxRating);
186
187 int numUsedStars = d->bHalfSteps ? d->maxRating / 2 : d->maxRating;
188
189 if (hoverRating >= 0 && hoverRating < rating) {
190 int tmp = hoverRating;
191 hoverRating = rating;
192 rating = tmp;
193 }
194
195 int usedSpacing = d->spacing;
196
197 // get the rating pixmaps
198 int maxHSizeOnePix = (rect.width() - (numUsedStars - 1) * usedSpacing) / numUsedStars;
199 QPixmap ratingPix = d->getPixmap(size: qMin(a: rect.height(), b: maxHSizeOnePix), dpr, state: QIcon::On);
200
201 QSize ratingPixSize = ratingPix.size() / ratingPix.devicePixelRatio();
202
203 QPixmap disabledRatingPix = d->getPixmap(size: qMin(a: rect.height(), b: maxHSizeOnePix), dpr, state: QIcon::Off);
204 QImage disabledRatingImage = disabledRatingPix.toImage().convertToFormat(f: QImage::Format_ARGB32);
205 QPixmap hoverPix;
206
207 // if we are disabled we become gray and more transparent
208 if (!d->isEnabled) {
209 ratingPix = disabledRatingPix;
210
211 imageToSemiTransparent(img&: disabledRatingImage);
212 disabledRatingPix = QPixmap::fromImage(image: disabledRatingImage);
213 }
214
215 bool half = d->bHalfSteps && rating % 2;
216 int numRatingStars = d->bHalfSteps ? rating / 2 : rating;
217
218 int numHoverStars = 0;
219 bool halfHover = false;
220 if (hoverRating >= 0 && rating != hoverRating && d->isEnabled) {
221 numHoverStars = d->bHalfSteps ? hoverRating / 2 : hoverRating;
222 halfHover = d->bHalfSteps && hoverRating % 2;
223
224 disabledRatingImage = ratingPix.toImage().convertToFormat(f: QImage::Format_ARGB32);
225 imageToGrayScale(img&: disabledRatingImage, value: 0.5);
226
227 hoverPix = QPixmap::fromImage(image: disabledRatingImage);
228 }
229
230 if (d->alignment & Qt::AlignJustify && numUsedStars > 1) {
231 int w = rect.width();
232 w -= numUsedStars * ratingPixSize.width();
233 usedSpacing = w / (numUsedStars - 1);
234 }
235
236 int ratingAreaWidth = ratingPixSize.width() * numUsedStars + usedSpacing * (numUsedStars - 1);
237
238 int i = 0;
239 int x = rect.x();
240 if (d->alignment & Qt::AlignRight) {
241 x += (rect.width() - ratingAreaWidth);
242 } else if (d->alignment & Qt::AlignHCenter) {
243 x += (rect.width() - ratingAreaWidth) / 2;
244 }
245
246 int xInc = ratingPixSize.width() + usedSpacing;
247 if (d->direction == Qt::RightToLeft) {
248 x = rect.width() - ratingPixSize.width() - x;
249 xInc = -xInc;
250 }
251
252 int y = rect.y();
253 if (d->alignment & Qt::AlignVCenter) {
254 y += (rect.height() / 2 - ratingPixSize.height() / 2);
255 } else if (d->alignment & Qt::AlignBottom) {
256 y += (rect.height() - ratingPixSize.height());
257 }
258 for (; i < numRatingStars; ++i) {
259 painter->drawPixmap(x, y, pm: ratingPix);
260 x += xInc;
261 }
262 if (half) {
263 painter->drawPixmap(x,
264 y,
265 w: ratingPixSize.width() / 2,
266 h: ratingPixSize.height(),
267 pm: d->direction == Qt::RightToLeft ? (numHoverStars > 0 ? hoverPix : disabledRatingPix) : ratingPix,
268 sx: 0,
269 sy: 0,
270 sw: ratingPix.width() / 2,
271 sh: ratingPix.height()); // source sizes are deliberately not device independent
272 painter->drawPixmap(x: x + ratingPixSize.width() / 2,
273 y,
274 w: ratingPixSize.width() / 2,
275 h: ratingPixSize.height(),
276 pm: d->direction == Qt::RightToLeft ? ratingPix : (numHoverStars > 0 ? hoverPix : disabledRatingPix),
277 sx: ratingPix.width() / 2,
278 sy: 0,
279 sw: ratingPix.width() / 2,
280 sh: ratingPix.height());
281 x += xInc;
282 ++i;
283 }
284 for (; i < numHoverStars; ++i) {
285 painter->drawPixmap(x, y, pm: hoverPix);
286 x += xInc;
287 }
288 if (halfHover) {
289 painter->drawPixmap(x,
290 y,
291 w: ratingPixSize.width() / 2,
292 h: ratingPixSize.height(),
293 pm: d->direction == Qt::RightToLeft ? disabledRatingPix : hoverPix,
294 sx: 0,
295 sy: 0,
296 sw: ratingPix.width() / 2,
297 sh: ratingPix.height());
298 painter->drawPixmap(x: x + ratingPixSize.width() / 2,
299 y,
300 w: ratingPixSize.width() / 2,
301 h: ratingPixSize.height(),
302 pm: d->direction == Qt::RightToLeft ? hoverPix : disabledRatingPix,
303 sx: ratingPix.width() / 2,
304 sy: 0,
305 sw: ratingPix.width() / 2,
306 sh: ratingPix.height());
307 x += xInc;
308 ++i;
309 }
310 for (; i < numUsedStars; ++i) {
311 painter->drawPixmap(x, y, pm: disabledRatingPix);
312 x += xInc;
313 }
314}
315
316int KRatingPainter::ratingFromPosition(const QRect &rect, const QPoint &pos) const
317{
318 int usedSpacing = d->spacing;
319 int numUsedStars = d->bHalfSteps ? d->maxRating / 2 : d->maxRating;
320 int maxHSizeOnePix = (rect.width() - (numUsedStars - 1) * usedSpacing) / numUsedStars;
321 QPixmap ratingPix = d->getPixmap(size: qMin(a: rect.height(), b: maxHSizeOnePix), dpr: 1.0);
322 QSize ratingPixSize = ratingPix.deviceIndependentSize().toSize();
323
324 int ratingAreaWidth = ratingPixSize.width() * numUsedStars + usedSpacing * (numUsedStars - 1);
325
326 QRect usedRect(rect);
327 if (d->alignment & Qt::AlignRight) {
328 usedRect.setLeft(rect.right() - ratingAreaWidth);
329 } else if (d->alignment & Qt::AlignHCenter) {
330 int x = (rect.width() - ratingAreaWidth) / 2;
331 usedRect.setLeft(rect.left() + x);
332 usedRect.setRight(rect.right() - x);
333 } else { // d->alignment & Qt::AlignLeft
334 usedRect.setRight(rect.left() + ratingAreaWidth - 1);
335 }
336
337 if (d->alignment & Qt::AlignBottom) {
338 usedRect.setTop(rect.bottom() - ratingPixSize.height() + 1);
339 } else if (d->alignment & Qt::AlignVCenter) {
340 int x = (rect.height() - ratingPixSize.height()) / 2;
341 usedRect.setTop(rect.top() + x);
342 usedRect.setBottom(rect.bottom() - x);
343 } else { // d->alignment & Qt::AlignTop
344 usedRect.setBottom(rect.top() + ratingPixSize.height() - 1);
345 }
346
347 if (usedRect.contains(p: pos)) {
348 int x = 0;
349 if (d->direction == Qt::RightToLeft) {
350 x = usedRect.right() - pos.x();
351 } else {
352 x = pos.x() - usedRect.left();
353 }
354
355 double one = (double)usedRect.width() / (double)d->maxRating;
356
357 // qCDebug(KWidgetsAddonsLog) << "rating:" << ( int )( ( double )x/one + 0.5 );
358
359 return (int)((double)x / one + 0.5);
360 } else {
361 return -1;
362 }
363}
364
365void KRatingPainter::paintRating(QPainter *painter, const QRect &rect, Qt::Alignment align, int rating, int hoverRating)
366{
367 KRatingPainter rp;
368 rp.setAlignment(align);
369 rp.setLayoutDirection(painter->layoutDirection());
370 rp.paint(painter, rect, rating, hoverRating);
371}
372
373int KRatingPainter::getRatingFromPosition(const QRect &rect, Qt::Alignment align, Qt::LayoutDirection direction, const QPoint &pos)
374{
375 KRatingPainter rp;
376 rp.setAlignment(align);
377 rp.setLayoutDirection(direction);
378 return rp.ratingFromPosition(rect, pos);
379}
380

source code of kwidgetsaddons/src/kratingpainter.cpp