File indexing completed on 2025-02-02 14:20:09
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2007-2008 Sebastian Trueg <trueg@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "kratingpainter.h" 0009 0010 #include <QIcon> 0011 #include <QPainter> 0012 #include <QPixmap> 0013 #include <QPoint> 0014 #include <QRect> 0015 0016 class KRatingPainterPrivate 0017 { 0018 public: 0019 QPixmap getPixmap(int size, QIcon::State state = QIcon::On); 0020 0021 int maxRating = 10; 0022 int spacing = 0; 0023 QIcon icon; 0024 bool isEnabled = true; 0025 bool bHalfSteps = true; 0026 Qt::Alignment alignment = Qt::AlignCenter; 0027 Qt::LayoutDirection direction = Qt::LeftToRight; 0028 QPixmap customPixmap; 0029 }; 0030 0031 static void imageToGrayScale(QImage &img, float value); 0032 static void imageToSemiTransparent(QImage &img); 0033 0034 QPixmap KRatingPainterPrivate::getPixmap(int size, QIcon::State state) 0035 { 0036 bool transformToOffState = (state == QIcon::Off); 0037 QPixmap p; 0038 0039 if (!customPixmap.isNull()) { 0040 p = customPixmap.scaled(QSize(size, size)); 0041 } else { 0042 QIcon _icon(icon); 0043 if (_icon.isNull()) { 0044 if (state == QIcon::On) { 0045 _icon = QIcon::fromTheme(QStringLiteral("rating")); 0046 } else if (QIcon::hasThemeIcon(QStringLiteral("rating-unrated"))) { 0047 _icon = QIcon::fromTheme(QStringLiteral("rating-unrated")); 0048 transformToOffState = false; // no need because we already have the perfect icon 0049 } else { 0050 _icon = QIcon::fromTheme(QStringLiteral("rating")); // will be transformed to the "off" state 0051 } 0052 } 0053 p = _icon.pixmap(size); 0054 } 0055 0056 if (transformToOffState) { 0057 QImage img = p.toImage().convertToFormat(QImage::Format_ARGB32); 0058 imageToGrayScale(img, 1.0); 0059 // The icon might have already been monochrome, so we also need to make it semi-transparent to see a difference. 0060 imageToSemiTransparent(img); 0061 return QPixmap::fromImage(img); 0062 } 0063 return p; 0064 } 0065 0066 KRatingPainter::KRatingPainter() 0067 : d(new KRatingPainterPrivate()) 0068 { 0069 } 0070 0071 KRatingPainter::~KRatingPainter() = default; 0072 0073 int KRatingPainter::maxRating() const 0074 { 0075 return d->maxRating; 0076 } 0077 0078 bool KRatingPainter::halfStepsEnabled() const 0079 { 0080 return d->bHalfSteps; 0081 } 0082 0083 Qt::Alignment KRatingPainter::alignment() const 0084 { 0085 return d->alignment; 0086 } 0087 0088 Qt::LayoutDirection KRatingPainter::layoutDirection() const 0089 { 0090 return d->direction; 0091 } 0092 0093 QIcon KRatingPainter::icon() const 0094 { 0095 return d->icon; 0096 } 0097 0098 bool KRatingPainter::isEnabled() const 0099 { 0100 return d->isEnabled; 0101 } 0102 0103 QPixmap KRatingPainter::customPixmap() const 0104 { 0105 return d->customPixmap; 0106 } 0107 0108 int KRatingPainter::spacing() const 0109 { 0110 return d->spacing; 0111 } 0112 0113 void KRatingPainter::setMaxRating(int max) 0114 { 0115 d->maxRating = max; 0116 } 0117 0118 void KRatingPainter::setHalfStepsEnabled(bool enabled) 0119 { 0120 d->bHalfSteps = enabled; 0121 } 0122 0123 void KRatingPainter::setAlignment(Qt::Alignment align) 0124 { 0125 d->alignment = align; 0126 } 0127 0128 void KRatingPainter::setLayoutDirection(Qt::LayoutDirection direction) 0129 { 0130 d->direction = direction; 0131 } 0132 0133 void KRatingPainter::setIcon(const QIcon &icon) 0134 { 0135 d->icon = icon; 0136 } 0137 0138 void KRatingPainter::setEnabled(bool enabled) 0139 { 0140 d->isEnabled = enabled; 0141 } 0142 0143 void KRatingPainter::setCustomPixmap(const QPixmap &pixmap) 0144 { 0145 d->customPixmap = pixmap; 0146 } 0147 0148 void KRatingPainter::setSpacing(int s) 0149 { 0150 d->spacing = qMax(0, s); 0151 } 0152 0153 static void imageToGrayScale(QImage &img, float value) 0154 { 0155 QRgb *data = (QRgb *)img.bits(); 0156 QRgb *end = data + img.width() * img.height(); 0157 0158 unsigned char gray; 0159 unsigned char val = (unsigned char)(255.0 * value); 0160 while (data != end) { 0161 gray = qGray(*data); 0162 *data = qRgba((val * gray + (255 - val) * qRed(*data)) >> 8, 0163 (val * gray + (255 - val) * qGreen(*data)) >> 8, 0164 (val * gray + (255 - val) * qBlue(*data)) >> 8, 0165 qAlpha(*data)); 0166 ++data; 0167 } 0168 } 0169 0170 static void imageToSemiTransparent(QImage &img) 0171 { 0172 QRgb *data = (QRgb *)img.bits(); 0173 QRgb *end = data + img.width() * img.height(); 0174 0175 while (data != end) { 0176 *data = qRgba(qRed(*data), qGreen(*data), qBlue(*data), qAlpha(*data) >> 1); 0177 ++data; 0178 } 0179 } 0180 0181 void KRatingPainter::paint(QPainter *painter, const QRect &rect, int rating, int hoverRating) const 0182 { 0183 rating = qMin(rating, d->maxRating); 0184 hoverRating = qMin(hoverRating, d->maxRating); 0185 0186 int numUsedStars = d->bHalfSteps ? d->maxRating / 2 : d->maxRating; 0187 0188 if (hoverRating >= 0 && hoverRating < rating) { 0189 int tmp = hoverRating; 0190 hoverRating = rating; 0191 rating = tmp; 0192 } 0193 0194 int usedSpacing = d->spacing; 0195 0196 // get the rating pixmaps 0197 int maxHSizeOnePix = (rect.width() - (numUsedStars - 1) * usedSpacing) / numUsedStars; 0198 QPixmap ratingPix = d->getPixmap(qMin(rect.height(), maxHSizeOnePix), QIcon::On); 0199 QSize ratingPixSize = ratingPix.size() / ratingPix.devicePixelRatio(); 0200 0201 QPixmap disabledRatingPix = d->getPixmap(qMin(rect.height(), maxHSizeOnePix), QIcon::Off); 0202 QImage disabledRatingImage = disabledRatingPix.toImage().convertToFormat(QImage::Format_ARGB32); 0203 QPixmap hoverPix; 0204 0205 // if we are disabled we become gray and more transparent 0206 if (!d->isEnabled) { 0207 ratingPix = disabledRatingPix; 0208 0209 imageToSemiTransparent(disabledRatingImage); 0210 disabledRatingPix = QPixmap::fromImage(disabledRatingImage); 0211 } 0212 0213 bool half = d->bHalfSteps && rating % 2; 0214 int numRatingStars = d->bHalfSteps ? rating / 2 : rating; 0215 0216 int numHoverStars = 0; 0217 bool halfHover = false; 0218 if (hoverRating >= 0 && rating != hoverRating && d->isEnabled) { 0219 numHoverStars = d->bHalfSteps ? hoverRating / 2 : hoverRating; 0220 halfHover = d->bHalfSteps && hoverRating % 2; 0221 0222 disabledRatingImage = ratingPix.toImage().convertToFormat(QImage::Format_ARGB32); 0223 imageToGrayScale(disabledRatingImage, 0.5); 0224 0225 hoverPix = QPixmap::fromImage(disabledRatingImage); 0226 } 0227 0228 if (d->alignment & Qt::AlignJustify && numUsedStars > 1) { 0229 int w = rect.width(); 0230 w -= numUsedStars * ratingPixSize.width(); 0231 usedSpacing = w / (numUsedStars - 1); 0232 } 0233 0234 int ratingAreaWidth = ratingPixSize.width() * numUsedStars + usedSpacing * (numUsedStars - 1); 0235 0236 int i = 0; 0237 int x = rect.x(); 0238 if (d->alignment & Qt::AlignRight) { 0239 x += (rect.width() - ratingAreaWidth); 0240 } else if (d->alignment & Qt::AlignHCenter) { 0241 x += (rect.width() - ratingAreaWidth) / 2; 0242 } 0243 0244 int xInc = ratingPixSize.width() + usedSpacing; 0245 if (d->direction == Qt::RightToLeft) { 0246 x = rect.width() - ratingPixSize.width() - x; 0247 xInc = -xInc; 0248 } 0249 0250 int y = rect.y(); 0251 if (d->alignment & Qt::AlignVCenter) { 0252 y += (rect.height() / 2 - ratingPixSize.height() / 2); 0253 } else if (d->alignment & Qt::AlignBottom) { 0254 y += (rect.height() - ratingPixSize.height()); 0255 } 0256 for (; i < numRatingStars; ++i) { 0257 painter->drawPixmap(x, y, ratingPix); 0258 x += xInc; 0259 } 0260 if (half) { 0261 painter->drawPixmap(x, 0262 y, 0263 ratingPixSize.width() / 2, 0264 ratingPixSize.height(), 0265 d->direction == Qt::RightToLeft ? (numHoverStars > 0 ? hoverPix : disabledRatingPix) : ratingPix, 0266 0, 0267 0, 0268 ratingPix.width() / 2, 0269 ratingPix.height()); // source sizes are deliberately not device independent 0270 painter->drawPixmap(x + ratingPixSize.width() / 2, 0271 y, 0272 ratingPixSize.width() / 2, 0273 ratingPixSize.height(), 0274 d->direction == Qt::RightToLeft ? ratingPix : (numHoverStars > 0 ? hoverPix : disabledRatingPix), 0275 ratingPix.width() / 2, 0276 0, 0277 ratingPix.width() / 2, 0278 ratingPix.height()); 0279 x += xInc; 0280 ++i; 0281 } 0282 for (; i < numHoverStars; ++i) { 0283 painter->drawPixmap(x, y, hoverPix); 0284 x += xInc; 0285 } 0286 if (halfHover) { 0287 painter->drawPixmap(x, 0288 y, 0289 ratingPixSize.width() / 2, 0290 ratingPixSize.height(), 0291 d->direction == Qt::RightToLeft ? disabledRatingPix : hoverPix, 0292 0, 0293 0, 0294 ratingPix.width() / 2, 0295 ratingPix.height()); 0296 painter->drawPixmap(x + ratingPixSize.width() / 2, 0297 y, 0298 ratingPixSize.width() / 2, 0299 ratingPixSize.height(), 0300 d->direction == Qt::RightToLeft ? hoverPix : disabledRatingPix, 0301 ratingPix.width() / 2, 0302 0, 0303 ratingPix.width() / 2, 0304 ratingPix.height()); 0305 x += xInc; 0306 ++i; 0307 } 0308 for (; i < numUsedStars; ++i) { 0309 painter->drawPixmap(x, y, disabledRatingPix); 0310 x += xInc; 0311 } 0312 } 0313 0314 int KRatingPainter::ratingFromPosition(const QRect &rect, const QPoint &pos) const 0315 { 0316 int usedSpacing = d->spacing; 0317 int numUsedStars = d->bHalfSteps ? d->maxRating / 2 : d->maxRating; 0318 int maxHSizeOnePix = (rect.width() - (numUsedStars - 1) * usedSpacing) / numUsedStars; 0319 QPixmap ratingPix = d->getPixmap(qMin(rect.height(), maxHSizeOnePix)); 0320 QSize ratingPixSize = ratingPix.size() / ratingPix.devicePixelRatio(); 0321 0322 int ratingAreaWidth = ratingPixSize.width() * numUsedStars + usedSpacing * (numUsedStars - 1); 0323 0324 QRect usedRect(rect); 0325 if (d->alignment & Qt::AlignRight) { 0326 usedRect.setLeft(rect.right() - ratingAreaWidth); 0327 } else if (d->alignment & Qt::AlignHCenter) { 0328 int x = (rect.width() - ratingAreaWidth) / 2; 0329 usedRect.setLeft(rect.left() + x); 0330 usedRect.setRight(rect.right() - x); 0331 } else { // d->alignment & Qt::AlignLeft 0332 usedRect.setRight(rect.left() + ratingAreaWidth - 1); 0333 } 0334 0335 if (d->alignment & Qt::AlignBottom) { 0336 usedRect.setTop(rect.bottom() - ratingPixSize.height() + 1); 0337 } else if (d->alignment & Qt::AlignVCenter) { 0338 int x = (rect.height() - ratingPixSize.height()) / 2; 0339 usedRect.setTop(rect.top() + x); 0340 usedRect.setBottom(rect.bottom() - x); 0341 } else { // d->alignment & Qt::AlignTop 0342 usedRect.setBottom(rect.top() + ratingPixSize.height() - 1); 0343 } 0344 0345 if (usedRect.contains(pos)) { 0346 int x = 0; 0347 if (d->direction == Qt::RightToLeft) { 0348 x = usedRect.right() - pos.x(); 0349 } else { 0350 x = pos.x() - usedRect.left(); 0351 } 0352 0353 double one = (double)usedRect.width() / (double)d->maxRating; 0354 0355 // qCDebug(KWidgetsAddonsLog) << "rating:" << ( int )( ( double )x/one + 0.5 ); 0356 0357 return (int)((double)x / one + 0.5); 0358 } else { 0359 return -1; 0360 } 0361 } 0362 0363 void KRatingPainter::paintRating(QPainter *painter, const QRect &rect, Qt::Alignment align, int rating, int hoverRating) 0364 { 0365 KRatingPainter rp; 0366 rp.setAlignment(align); 0367 rp.setLayoutDirection(painter->layoutDirection()); 0368 rp.paint(painter, rect, rating, hoverRating); 0369 } 0370 0371 int KRatingPainter::getRatingFromPosition(const QRect &rect, Qt::Alignment align, Qt::LayoutDirection direction, const QPoint &pos) 0372 { 0373 KRatingPainter rp; 0374 rp.setAlignment(align); 0375 rp.setLayoutDirection(direction); 0376 return rp.ratingFromPosition(rect, pos); 0377 }