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, 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, 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(size);
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 rating = qMin(rating, d->maxRating);
184 hoverRating = qMin(hoverRating, 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(qMin(rect.height(), maxHSizeOnePix), QIcon::On);
199 QSize ratingPixSize = ratingPix.size() / ratingPix.devicePixelRatio();
200
201 QPixmap disabledRatingPix = d->getPixmap(qMin(rect.height(), maxHSizeOnePix), QIcon::Off);
202 QImage disabledRatingImage = disabledRatingPix.toImage().convertToFormat(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(disabledRatingImage);
210 disabledRatingPix = QPixmap::fromImage(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(QImage::Format_ARGB32);
223 imageToGrayScale(disabledRatingImage, 0.5);
224
225 hoverPix = QPixmap::fromImage(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, ratingPix);
258 x += xInc;
259 }
260 if (half) {
261 painter->drawPixmap(x,
262 y,
263 ratingPixSize.width() / 2,
264 ratingPixSize.height(),
265 d->direction == Qt::RightToLeft ? (numHoverStars > 0 ? hoverPix : disabledRatingPix) : ratingPix,
266 0,
267 0,
268 ratingPix.width() / 2,
269 ratingPix.height()); // source sizes are deliberately not device independent
270 painter->drawPixmap(x + ratingPixSize.width() / 2,
271 y,
272 ratingPixSize.width() / 2,
273 ratingPixSize.height(),
274 d->direction == Qt::RightToLeft ? ratingPix : (numHoverStars > 0 ? hoverPix : disabledRatingPix),
275 ratingPix.width() / 2,
276 0,
277 ratingPix.width() / 2,
278 ratingPix.height());
279 x += xInc;
280 ++i;
281 }
282 for (; i < numHoverStars; ++i) {
283 painter->drawPixmap(x, y, hoverPix);
284 x += xInc;
285 }
286 if (halfHover) {
287 painter->drawPixmap(x,
288 y,
289 ratingPixSize.width() / 2,
290 ratingPixSize.height(),
291 d->direction == Qt::RightToLeft ? disabledRatingPix : hoverPix,
292 0,
293 0,
294 ratingPix.width() / 2,
295 ratingPix.height());
296 painter->drawPixmap(x + ratingPixSize.width() / 2,
297 y,
298 ratingPixSize.width() / 2,
299 ratingPixSize.height(),
300 d->direction == Qt::RightToLeft ? hoverPix : disabledRatingPix,
301 ratingPix.width() / 2,
302 0,
303 ratingPix.width() / 2,
304 ratingPix.height());
305 x += xInc;
306 ++i;
307 }
308 for (; i < numUsedStars; ++i) {
309 painter->drawPixmap(x, y, 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(qMin(rect.height(), 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(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{
366 rp.setAlignment(align);
367 rp.setLayoutDirection(painter->layoutDirection());
368 rp.paint(painter, rect, rating, hoverRating);
369}
370
372{
374 rp.setAlignment(align);
375 rp.setLayoutDirection(direction);
376 return rp.ratingFromPosition(rect, pos);
377}
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.
const QList< QKeySequence > & end()
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
void drawPixmap(const QPoint &point, const QPixmap &pixmap)
Qt::LayoutDirection layoutDirection() 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
typedef Alignment
LayoutDirection
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:43 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.