KWidgetsAddons

kratingpainter.cpp
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;
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(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(QSize(size, size), dpr);
54 }
55
56 if (transformToOffState) {
58 imageToGrayScale(img, 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(img);
62 }
63 return p;
64}
65
67 : d(new KRatingPainterPrivate())
68{
69}
70
72
74{
75 return d->maxRating;
76}
77
79{
80 return d->bHalfSteps;
81}
82
84{
85 return d->alignment;
86}
87
89{
90 return d->direction;
91}
92
94{
95 return d->icon;
96}
97
99{
100 return d->isEnabled;
101}
102
104{
105 return d->customPixmap;
106}
107
109{
110 return d->spacing;
111}
112
114{
115 d->maxRating = max;
116}
117
119{
120 d->bHalfSteps = enabled;
121}
122
124{
125 d->alignment = align;
126}
127
129{
130 d->direction = direction;
131}
132
134{
135 d->icon = icon;
136}
137
139{
140 d->isEnabled = enabled;
141}
142
144{
145 d->customPixmap = pixmap;
146}
147
149{
150 d->spacing = qMax(0, 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(*data);
162 *data = qRgba((val * gray + (255 - val) * qRed(*data)) >> 8,
163 (val * gray + (255 - val) * qGreen(*data)) >> 8,
164 (val * gray + (255 - val) * qBlue(*data)) >> 8,
165 qAlpha(*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(qRed(*data), qGreen(*data), qBlue(*data), qAlpha(*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(rating, d->maxRating);
185 hoverRating = qMin(hoverRating, 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(qMin(rect.height(), maxHSizeOnePix), dpr, QIcon::On);
200
201 QSize ratingPixSize = ratingPix.size() / ratingPix.devicePixelRatio();
202
203 QPixmap disabledRatingPix = d->getPixmap(qMin(rect.height(), maxHSizeOnePix), dpr, QIcon::Off);
204 QImage disabledRatingImage = disabledRatingPix.toImage().convertToFormat(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(disabledRatingImage);
212 disabledRatingPix = QPixmap::fromImage(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(QImage::Format_ARGB32);
225 imageToGrayScale(disabledRatingImage, 0.5);
226
227 hoverPix = QPixmap::fromImage(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, ratingPix);
260 x += xInc;
261 }
262 if (half) {
263 painter->drawPixmap(x,
264 y,
265 ratingPixSize.width() / 2,
266 ratingPixSize.height(),
267 d->direction == Qt::RightToLeft ? (numHoverStars > 0 ? hoverPix : disabledRatingPix) : ratingPix,
268 0,
269 0,
270 ratingPix.width() / 2,
271 ratingPix.height()); // source sizes are deliberately not device independent
272 painter->drawPixmap(x + ratingPixSize.width() / 2,
273 y,
274 ratingPixSize.width() / 2,
275 ratingPixSize.height(),
276 d->direction == Qt::RightToLeft ? ratingPix : (numHoverStars > 0 ? hoverPix : disabledRatingPix),
277 ratingPix.width() / 2,
278 0,
279 ratingPix.width() / 2,
280 ratingPix.height());
281 x += xInc;
282 ++i;
283 }
284 for (; i < numHoverStars; ++i) {
285 painter->drawPixmap(x, y, hoverPix);
286 x += xInc;
287 }
288 if (halfHover) {
289 painter->drawPixmap(x,
290 y,
291 ratingPixSize.width() / 2,
292 ratingPixSize.height(),
293 d->direction == Qt::RightToLeft ? disabledRatingPix : hoverPix,
294 0,
295 0,
296 ratingPix.width() / 2,
297 ratingPix.height());
298 painter->drawPixmap(x + ratingPixSize.width() / 2,
299 y,
300 ratingPixSize.width() / 2,
301 ratingPixSize.height(),
302 d->direction == Qt::RightToLeft ? hoverPix : disabledRatingPix,
303 ratingPix.width() / 2,
304 0,
305 ratingPix.width() / 2,
306 ratingPix.height());
307 x += xInc;
308 ++i;
309 }
310 for (; i < numUsedStars; ++i) {
311 painter->drawPixmap(x, y, 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(qMin(rect.height(), maxHSizeOnePix), 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(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{
368 rp.setAlignment(align);
369 rp.setLayoutDirection(painter->layoutDirection());
370 rp.paint(painter, rect, rating, hoverRating);
371}
372
374{
376 rp.setAlignment(align);
377 rp.setLayoutDirection(direction);
378 return rp.ratingFromPosition(rect, pos);
379}
Utility class that draws a row of stars for a rating value.
int maxRating() const
The maximum rating, i.e.
Qt::Alignment alignment() const
The alignment of the stars.
void setMaxRating(int max)
The maximum rating.
void setEnabled(bool enabled)
Enable or disable the rating.
static void paintRating(QPainter *p, const QRect &rect, Qt::Alignment align, int rating, int hoverRating=-1)
Convenience method that paints a rating into the given rect.
void setCustomPixmap(const QPixmap &pixmap)
Set a custom pixmap.
int ratingFromPosition(const QRect &rect, const QPoint &pos) const
Calculate the rating value from mouse position pos.
QIcon icon() const
The icon used to draw a star.
void setAlignment(Qt::Alignment align)
The alignment of the stars in the drawing rect.
int spacing() const
The spacing between rating pixmaps.
Qt::LayoutDirection layoutDirection() const
The layout direction.
void setLayoutDirection(Qt::LayoutDirection direction)
LTR or RTL.
KRatingPainter()
Create a new KRatingPainter.
bool halfStepsEnabled() const
If half steps are enabled one star equals to 2 rating points and uneven rating values result in half-...
bool isEnabled() const
The rating can be painted in a disabled state where no color is used and hover ratings are ignored.
void setSpacing(int spacing)
Set the spacing between rating pixmaps.
static int getRatingFromPosition(const QRect &rect, Qt::Alignment align, Qt::LayoutDirection direction, const QPoint &pos)
Get the rating that would be selected if the user clicked position pos within rect if the rating has ...
void setHalfStepsEnabled(bool enabled)
If half steps are enabled (the default) then one rating step corresponds to half a star.
QPixmap customPixmap() const
The custom pixmap set to draw a star.
void paint(QPainter *painter, const QRect &rect, int rating, int hoverRating=-1) const
Draw the rating.
void setIcon(const QIcon &icon)
Set a custom icon.
~KRatingPainter()
Destructor.
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
QIcon fromTheme(const QString &name)
bool hasThemeIcon(const QString &name)
uchar * bits()
QImage convertToFormat(Format format, Qt::ImageConversionFlags flags) &&
int height() const const
int width() const const
qreal devicePixelRatio() const const
QPaintDevice * device() const const
void drawPixmap(const QPoint &point, const QPixmap &pixmap)
Qt::LayoutDirection layoutDirection() const const
QSizeF deviceIndependentSize() const const
qreal devicePixelRatio() const const
QPixmap fromImage(QImage &&image, Qt::ImageConversionFlags flags)
int height() const const
bool isNull() const const
QPixmap scaled(const QSize &size, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformMode) const const
QSize size() const const
QImage toImage() const const
int width() const const
int x() const const
int bottom() const const
int height() const const
int left() const const
int right() const const
int top() const const
int width() const const
int x() const const
int y() const const
int height() const const
int width() const const
QSize toSize() const const
typedef Alignment
LayoutDirection
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 29 2024 11:58:18 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.