File indexing completed on 2024-04-28 03:59:12

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 }