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, 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, 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(extent: size);
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 rating = qMin(a: rating, b: d->maxRating);
184 hoverRating = qMin(a: hoverRating, b: d->maxRating);
185
186 int numUsedStars = d->bHalfSteps ? d->maxRating / 2 : d->maxRating;
187
188 if (hoverRating >= 0 && hoverRating < rating) {
189 int tmp = hoverRating;
190 hoverRating = rating;
191 rating = tmp;
192 }
193
194 int usedSpacing = d->spacing;
195
196 // get the rating pixmaps
197 int maxHSizeOnePix = (rect.width() - (numUsedStars - 1) * usedSpacing) / numUsedStars;
198 QPixmap ratingPix = d->getPixmap(size: qMin(a: rect.height(), b: maxHSizeOnePix), state: QIcon::On);
199 QSize ratingPixSize = ratingPix.size() / ratingPix.devicePixelRatio();
200
201 QPixmap disabledRatingPix = d->getPixmap(size: qMin(a: rect.height(), b: maxHSizeOnePix), state: QIcon::Off);
202 QImage disabledRatingImage = disabledRatingPix.toImage().convertToFormat(f: QImage::Format_ARGB32);
203 QPixmap hoverPix;
204
205 // if we are disabled we become gray and more transparent
206 if (!d->isEnabled) {
207 ratingPix = disabledRatingPix;
208
209 imageToSemiTransparent(img&: disabledRatingImage);
210 disabledRatingPix = QPixmap::fromImage(image: disabledRatingImage);
211 }
212
213 bool half = d->bHalfSteps && rating % 2;
214 int numRatingStars = d->bHalfSteps ? rating / 2 : rating;
215
216 int numHoverStars = 0;
217 bool halfHover = false;
218 if (hoverRating >= 0 && rating != hoverRating && d->isEnabled) {
219 numHoverStars = d->bHalfSteps ? hoverRating / 2 : hoverRating;
220 halfHover = d->bHalfSteps && hoverRating % 2;
221
222 disabledRatingImage = ratingPix.toImage().convertToFormat(f: QImage::Format_ARGB32);
223 imageToGrayScale(img&: disabledRatingImage, value: 0.5);
224
225 hoverPix = QPixmap::fromImage(image: disabledRatingImage);
226 }
227
228 if (d->alignment & Qt::AlignJustify && numUsedStars > 1) {
229 int w = rect.width();
230 w -= numUsedStars * ratingPixSize.width();
231 usedSpacing = w / (numUsedStars - 1);
232 }
233
234 int ratingAreaWidth = ratingPixSize.width() * numUsedStars + usedSpacing * (numUsedStars - 1);
235
236 int i = 0;
237 int x = rect.x();
238 if (d->alignment & Qt::AlignRight) {
239 x += (rect.width() - ratingAreaWidth);
240 } else if (d->alignment & Qt::AlignHCenter) {
241 x += (rect.width() - ratingAreaWidth) / 2;
242 }
243
244 int xInc = ratingPixSize.width() + usedSpacing;
245 if (d->direction == Qt::RightToLeft) {
246 x = rect.width() - ratingPixSize.width() - x;
247 xInc = -xInc;
248 }
249
250 int y = rect.y();
251 if (d->alignment & Qt::AlignVCenter) {
252 y += (rect.height() / 2 - ratingPixSize.height() / 2);
253 } else if (d->alignment & Qt::AlignBottom) {
254 y += (rect.height() - ratingPixSize.height());
255 }
256 for (; i < numRatingStars; ++i) {
257 painter->drawPixmap(x, y, pm: ratingPix);
258 x += xInc;
259 }
260 if (half) {
261 painter->drawPixmap(x,
262 y,
263 w: ratingPixSize.width() / 2,
264 h: ratingPixSize.height(),
265 pm: d->direction == Qt::RightToLeft ? (numHoverStars > 0 ? hoverPix : disabledRatingPix) : ratingPix,
266 sx: 0,
267 sy: 0,
268 sw: ratingPix.width() / 2,
269 sh: ratingPix.height()); // source sizes are deliberately not device independent
270 painter->drawPixmap(x: x + ratingPixSize.width() / 2,
271 y,
272 w: ratingPixSize.width() / 2,
273 h: ratingPixSize.height(),
274 pm: d->direction == Qt::RightToLeft ? ratingPix : (numHoverStars > 0 ? hoverPix : disabledRatingPix),
275 sx: ratingPix.width() / 2,
276 sy: 0,
277 sw: ratingPix.width() / 2,
278 sh: ratingPix.height());
279 x += xInc;
280 ++i;
281 }
282 for (; i < numHoverStars; ++i) {
283 painter->drawPixmap(x, y, pm: hoverPix);
284 x += xInc;
285 }
286 if (halfHover) {
287 painter->drawPixmap(x,
288 y,
289 w: ratingPixSize.width() / 2,
290 h: ratingPixSize.height(),
291 pm: d->direction == Qt::RightToLeft ? disabledRatingPix : hoverPix,
292 sx: 0,
293 sy: 0,
294 sw: ratingPix.width() / 2,
295 sh: ratingPix.height());
296 painter->drawPixmap(x: x + ratingPixSize.width() / 2,
297 y,
298 w: ratingPixSize.width() / 2,
299 h: ratingPixSize.height(),
300 pm: d->direction == Qt::RightToLeft ? hoverPix : disabledRatingPix,
301 sx: ratingPix.width() / 2,
302 sy: 0,
303 sw: ratingPix.width() / 2,
304 sh: ratingPix.height());
305 x += xInc;
306 ++i;
307 }
308 for (; i < numUsedStars; ++i) {
309 painter->drawPixmap(x, y, pm: disabledRatingPix);
310 x += xInc;
311 }
312}
313
314int KRatingPainter::ratingFromPosition(const QRect &rect, const QPoint &pos) const
315{
316 int usedSpacing = d->spacing;
317 int numUsedStars = d->bHalfSteps ? d->maxRating / 2 : d->maxRating;
318 int maxHSizeOnePix = (rect.width() - (numUsedStars - 1) * usedSpacing) / numUsedStars;
319 QPixmap ratingPix = d->getPixmap(size: qMin(a: rect.height(), b: maxHSizeOnePix));
320 QSize ratingPixSize = ratingPix.size() / ratingPix.devicePixelRatio();
321
322 int ratingAreaWidth = ratingPixSize.width() * numUsedStars + usedSpacing * (numUsedStars - 1);
323
324 QRect usedRect(rect);
325 if (d->alignment & Qt::AlignRight) {
326 usedRect.setLeft(rect.right() - ratingAreaWidth);
327 } else if (d->alignment & Qt::AlignHCenter) {
328 int x = (rect.width() - ratingAreaWidth) / 2;
329 usedRect.setLeft(rect.left() + x);
330 usedRect.setRight(rect.right() - x);
331 } else { // d->alignment & Qt::AlignLeft
332 usedRect.setRight(rect.left() + ratingAreaWidth - 1);
333 }
334
335 if (d->alignment & Qt::AlignBottom) {
336 usedRect.setTop(rect.bottom() - ratingPixSize.height() + 1);
337 } else if (d->alignment & Qt::AlignVCenter) {
338 int x = (rect.height() - ratingPixSize.height()) / 2;
339 usedRect.setTop(rect.top() + x);
340 usedRect.setBottom(rect.bottom() - x);
341 } else { // d->alignment & Qt::AlignTop
342 usedRect.setBottom(rect.top() + ratingPixSize.height() - 1);
343 }
344
345 if (usedRect.contains(p: pos)) {
346 int x = 0;
347 if (d->direction == Qt::RightToLeft) {
348 x = usedRect.right() - pos.x();
349 } else {
350 x = pos.x() - usedRect.left();
351 }
352
353 double one = (double)usedRect.width() / (double)d->maxRating;
354
355 // qCDebug(KWidgetsAddonsLog) << "rating:" << ( int )( ( double )x/one + 0.5 );
356
357 return (int)((double)x / one + 0.5);
358 } else {
359 return -1;
360 }
361}
362
363void KRatingPainter::paintRating(QPainter *painter, const QRect &rect, Qt::Alignment align, int rating, int hoverRating)
364{
365 KRatingPainter rp;
366 rp.setAlignment(align);
367 rp.setLayoutDirection(painter->layoutDirection());
368 rp.paint(painter, rect, rating, hoverRating);
369}
370
371int KRatingPainter::getRatingFromPosition(const QRect &rect, Qt::Alignment align, Qt::LayoutDirection direction, const QPoint &pos)
372{
373 KRatingPainter rp;
374 rp.setAlignment(align);
375 rp.setLayoutDirection(direction);
376 return rp.ratingFromPosition(rect, pos);
377}
378

source code of kwidgetsaddons/src/kratingpainter.cpp