File indexing completed on 2025-12-07 04:08:35
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2005-08-15 0007 * Description : a widget to draw stars rating 0008 * 0009 * SPDX-FileCopyrightText: 2005 by Owen Hirst <n8rider@sbcglobal.net> 0010 * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "ratingwidget.h" 0017 0018 // C++ includes 0019 0020 #include <cmath> 0021 0022 // Qt includes 0023 0024 #include <QApplication> 0025 #include <QPainter> 0026 #include <QPalette> 0027 #include <QPixmap> 0028 #include <QTimeLine> 0029 #include <QFont> 0030 #include <QAction> 0031 #include <QWidgetAction> 0032 0033 // KDE includes 0034 0035 #if !defined(Q_OS_DARWIN) && defined(Q_CC_GNU) 0036 # pragma GCC diagnostic push 0037 # pragma GCC diagnostic ignored "-Wdeprecated-declarations" 0038 #endif 0039 0040 #if defined(Q_CC_CLANG) 0041 # pragma clang diagnostic push 0042 # pragma clang diagnostic ignored "-Wdeprecated-declarations" 0043 #endif 0044 0045 #include <klocalizedstring.h> 0046 #include <kactioncollection.h> 0047 0048 // Restore warnings 0049 #if !defined(Q_OS_DARWIN) && defined(Q_CC_GNU) 0050 # pragma GCC diagnostic pop 0051 #endif 0052 0053 #if defined(Q_CC_CLANG) 0054 # pragma clang diagnostic pop 0055 #endif 0056 0057 // Local includes 0058 0059 #include "digikam_debug.h" 0060 #include "digikam_globals.h" 0061 #include "thememanager.h" 0062 #include "dxmlguiwindow.h" 0063 #include "dexpanderbox.h" 0064 0065 namespace Digikam 0066 { 0067 0068 class Q_DECL_HIDDEN RatingWidget::Private 0069 { 0070 public: 0071 0072 explicit Private() 0073 : tracking (true), 0074 isHovered (false), 0075 fading (false), 0076 rating (0), 0077 fadingValue (0), 0078 duration (600), 0079 offset (0), 0080 width (15), 0081 fadingTimeLine (nullptr) 0082 { 0083 } 0084 0085 bool tracking; 0086 bool isHovered; 0087 bool fading; 0088 0089 int rating; 0090 int fadingValue; 0091 int duration; ///< in milliseconds 0092 int offset; 0093 int width; 0094 0095 QTimeLine* fadingTimeLine; 0096 0097 QPixmap selPixmap; ///< Selected star. 0098 QPixmap regPixmap; ///< Regular star. 0099 QPixmap disPixmap; ///< Disable star. 0100 }; 0101 0102 RatingWidget::RatingWidget(QWidget* const parent) 0103 : QWidget(parent), 0104 d (new Private) 0105 { 0106 slotThemeChanged(); 0107 0108 connect(ThemeManager::instance(), SIGNAL(signalThemeChanged()), 0109 this, SLOT(slotThemeChanged())); 0110 } 0111 0112 RatingWidget::~RatingWidget() 0113 { 0114 delete d; 0115 } 0116 0117 void RatingWidget::setupTimeLine() 0118 { 0119 delete d->fadingTimeLine; 0120 d->fadingTimeLine = new QTimeLine(d->duration, this); 0121 d->fadingTimeLine->setFrameRange(0, 255); 0122 0123 connect(d->fadingTimeLine, SIGNAL(frameChanged(int)), 0124 this, SLOT(setFadingValue(int))); 0125 0126 d->fadingTimeLine->start(); 0127 } 0128 0129 int RatingWidget::regPixmapWidth() const 0130 { 0131 return d->width; 0132 } 0133 0134 void RatingWidget::setRating(int val) 0135 { 0136 if (((val < RatingMin) || (val > RatingMax)) && (val != NoRating)) 0137 { 0138 return; 0139 } 0140 0141 d->rating = val; 0142 0143 if (d->tracking) 0144 { 0145 Q_EMIT signalRatingChanged(d->rating); 0146 } 0147 0148 Q_EMIT signalRatingModified(d->rating); 0149 update(); 0150 } 0151 0152 int RatingWidget::rating() const 0153 { 0154 return d->rating; 0155 } 0156 0157 void RatingWidget::setTracking(bool tracking) 0158 { 0159 d->tracking = tracking; 0160 } 0161 0162 bool RatingWidget::hasTracking() const 0163 { 0164 return d->tracking; 0165 } 0166 0167 void RatingWidget::setFading(bool fading) 0168 { 0169 d->fading = fading; 0170 } 0171 0172 bool RatingWidget::hasFading() const 0173 { 0174 return d->fading; 0175 } 0176 0177 void RatingWidget::setFadingValue(int value) 0178 { 0179 d->fadingValue = value; 0180 0181 if ((d->fadingValue >= 255) && d->fadingTimeLine) 0182 { 0183 d->fadingTimeLine->stop(); 0184 } 0185 0186 update(); 0187 } 0188 0189 void RatingWidget::setVisible(bool visible) 0190 { 0191 QWidget::setVisible(visible); 0192 0193 if (visible) 0194 { 0195 startFading(); 0196 } 0197 else 0198 { 0199 stopFading(); 0200 } 0201 } 0202 0203 int RatingWidget::maximumVisibleWidth() const 0204 { 0205 return RatingMax * (d->width + 1); 0206 } 0207 0208 void RatingWidget::startFading() 0209 { 0210 if (!hasFading()) 0211 { 0212 return; 0213 } 0214 0215 if (!d->isHovered) 0216 { 0217 d->isHovered = true; 0218 d->fadingValue = 0; 0219 setupTimeLine(); 0220 } 0221 } 0222 0223 void RatingWidget::stopFading() 0224 { 0225 if (!hasFading()) 0226 { 0227 return; 0228 } 0229 0230 if (d->fadingTimeLine) 0231 { 0232 d->fadingTimeLine->stop(); 0233 } 0234 0235 d->isHovered = false; 0236 d->fadingValue = 0; 0237 update(); 0238 } 0239 0240 void RatingWidget::setVisibleImmediately() 0241 { 0242 setFadingValue(255); 0243 } 0244 0245 QPixmap RatingWidget::starPixmapDisabled() const 0246 { 0247 return d->disPixmap; 0248 } 0249 0250 QPixmap RatingWidget::starPixmapFilled() const 0251 { 0252 return d->selPixmap; 0253 } 0254 0255 QPixmap RatingWidget::starPixmap() const 0256 { 0257 return d->regPixmap; 0258 } 0259 0260 void RatingWidget::regeneratePixmaps() 0261 { 0262 slotThemeChanged(); 0263 } 0264 0265 void RatingWidget::mousePressEvent(QMouseEvent* e) 0266 { 0267 if (e->button() != Qt::LeftButton) 0268 { 0269 return; 0270 } 0271 0272 if (hasFading() && (d->fadingValue < 255)) 0273 { 0274 return; 0275 } 0276 0277 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0278 0279 int pos = (e->position().toPoint().x() - d->offset) / d->width + 1; 0280 0281 #else 0282 0283 int pos = (e->x() - d->offset) / d->width + 1; 0284 0285 #endif 0286 0287 if (d->rating == pos) 0288 { 0289 d->rating--; 0290 } 0291 else 0292 { 0293 d->rating = pos; 0294 } 0295 0296 if (d->rating > RatingMax) 0297 { 0298 d->rating = RatingMax; 0299 } 0300 0301 if (d->rating < RatingMin) 0302 { 0303 d->rating = RatingMin; 0304 } 0305 0306 if (d->tracking) 0307 { 0308 Q_EMIT signalRatingChanged(d->rating); 0309 } 0310 0311 Q_EMIT signalRatingModified(d->rating); 0312 update(); 0313 } 0314 0315 void RatingWidget::mouseMoveEvent(QMouseEvent* e) 0316 { 0317 if (!(e->buttons() & Qt::LeftButton)) 0318 { 0319 return; 0320 } 0321 0322 if (hasFading() && (d->fadingValue < 255)) 0323 { 0324 return; 0325 } 0326 0327 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0328 0329 int pos = (e->position().toPoint().x() - d->offset) / d->width + 1; 0330 0331 #else 0332 0333 int pos = (e->x() - d->offset) / d->width + 1; 0334 0335 #endif 0336 0337 if (d->rating != pos) 0338 { 0339 if (pos > RatingMax) // NOTE: bug. #151357 0340 { 0341 pos = RatingMax; 0342 } 0343 0344 if (pos < RatingMin) 0345 { 0346 pos = RatingMin; 0347 } 0348 0349 d->rating = pos; 0350 0351 if (d->tracking) 0352 { 0353 Q_EMIT signalRatingChanged(d->rating); 0354 } 0355 0356 Q_EMIT signalRatingModified(d->rating); 0357 update(); 0358 } 0359 } 0360 0361 void RatingWidget::mouseReleaseEvent(QMouseEvent* e) 0362 { 0363 if (e->button() != Qt::LeftButton) 0364 { 0365 return; 0366 } 0367 0368 if (hasFading() && (d->fadingValue < 255)) 0369 { 0370 return; 0371 } 0372 0373 Q_EMIT signalRatingChanged(d->rating); 0374 } 0375 0376 void RatingWidget::slotThemeChanged() 0377 { 0378 qreal dpr = devicePixelRatio(); 0379 0380 d->regPixmap = QPixmap(d->width * dpr, d->width * dpr); 0381 d->regPixmap.fill(Qt::transparent); 0382 d->selPixmap = QPixmap(d->width * dpr, d->width * dpr); 0383 d->selPixmap.fill(Qt::transparent); 0384 d->disPixmap = QPixmap(d->width * dpr, d->width * dpr); 0385 d->disPixmap.fill(Qt::transparent); 0386 0387 d->regPixmap.setDevicePixelRatio(dpr); 0388 d->selPixmap.setDevicePixelRatio(dpr); 0389 d->disPixmap.setDevicePixelRatio(dpr); 0390 0391 QPainter p1(&d->regPixmap); 0392 p1.setRenderHint(QPainter::Antialiasing, true); 0393 p1.setBrush(palette().color(QPalette::Active, backgroundRole())); 0394 p1.setPen(palette().color(QPalette::Active, foregroundRole())); 0395 p1.drawPolygon(starPolygon(), Qt::WindingFill); 0396 p1.end(); 0397 0398 QPainter p2(&d->selPixmap); 0399 p2.setRenderHint(QPainter::Antialiasing, true); 0400 p2.setBrush(qApp->palette().color(QPalette::Link)); 0401 p2.setPen(palette().color(QPalette::Active, foregroundRole())); 0402 p2.drawPolygon(starPolygon(), Qt::WindingFill); 0403 p2.end(); 0404 0405 QPainter p3(&d->disPixmap); 0406 p3.setRenderHint(QPainter::Antialiasing, true); 0407 p3.setBrush(palette().color(QPalette::Disabled, backgroundRole())); 0408 p3.setPen(palette().color(QPalette::Disabled, foregroundRole())); 0409 p3.drawPolygon(starPolygon(), Qt::WindingFill); 0410 p3.end(); 0411 0412 setMinimumSize(QSize((d->width + 1) * RatingMax, d->width)); 0413 update(); 0414 } 0415 0416 QPolygon RatingWidget::starPolygon() 0417 { 0418 QPolygon star; 0419 star << QPoint(0, 6); 0420 star << QPoint(5, 5); 0421 star << QPoint(7, 0); 0422 star << QPoint(9, 5); 0423 star << QPoint(14, 6); 0424 star << QPoint(10, 9); 0425 star << QPoint(11, 14); 0426 star << QPoint(7, 11); 0427 star << QPoint(3, 14); 0428 star << QPoint(4, 9); 0429 0430 return star; 0431 } 0432 0433 QIcon RatingWidget::buildIcon(int rate, int size) 0434 { 0435 QPixmap pix(size, size); 0436 pix.fill(Qt::transparent); 0437 QPainter p(&pix); 0438 QTransform transform; 0439 transform.scale(size/15.0, size/15.0); 0440 p.setTransform(transform); 0441 p.setRenderHint(QPainter::Antialiasing, true); 0442 p.setPen(qApp->palette().color(QPalette::Active, QPalette::ButtonText)); 0443 0444 if (rate > 0) 0445 { 0446 p.setBrush(qApp->palette().color(QPalette::Link)); 0447 } 0448 0449 p.drawPolygon(starPolygon(), Qt::WindingFill); 0450 p.end(); 0451 0452 return QIcon(pix); 0453 } 0454 0455 void RatingWidget::paintEvent(QPaintEvent*) 0456 { 0457 QPainter p(this); 0458 0459 d->offset = (width() - RatingMax * (d->width + 1)) / 2; 0460 0461 // Widget is disable : drawing grayed frame. 0462 0463 if (!isEnabled()) 0464 { 0465 int x = d->offset; 0466 0467 for (int i = 0 ; i < RatingMax ; ++i) 0468 { 0469 p.drawPixmap(x, 0, d->disPixmap); 0470 x += d->width + 1; 0471 } 0472 } 0473 else 0474 { 0475 int x = d->offset; 0476 int rate = d->rating != NoRating ? d->rating : 0; 0477 QPixmap sel = d->selPixmap; 0478 applyFading(sel); 0479 0480 for (int i = 0 ; i < rate ; ++i) 0481 { 0482 p.drawPixmap(x, 0, sel); 0483 x += d->width + 1; 0484 } 0485 0486 QPixmap reg = d->regPixmap; 0487 applyFading(reg); 0488 0489 for (int i = rate ; i < RatingMax ; ++i) 0490 { 0491 p.drawPixmap(x, 0, reg); 0492 x += d->width + 1; 0493 } 0494 } 0495 0496 p.end(); 0497 } 0498 0499 void RatingWidget::applyFading(QPixmap& pix) 0500 { 0501 if (hasFading() && d->fadingValue < 255) 0502 { 0503 QPainter p(&pix); 0504 p.setCompositionMode(QPainter::CompositionMode_DestinationIn); 0505 p.fillRect(pix.rect(), QColor(0, 0, 0, d->fadingValue)); 0506 p.end(); 0507 } 0508 } 0509 0510 // ------------------------------------------------------------------------------- 0511 0512 class Q_DECL_HIDDEN RatingBox::Private 0513 { 0514 0515 public: 0516 0517 explicit Private() 0518 : shortcut (nullptr), 0519 ratingWidget(nullptr) 0520 { 0521 } 0522 0523 DAdjustableLabel* shortcut; 0524 0525 RatingWidget* ratingWidget; 0526 }; 0527 0528 RatingBox::RatingBox(QWidget* const parent) 0529 : DVBox(parent), 0530 d (new Private) 0531 { 0532 setAttribute(Qt::WA_DeleteOnClose); 0533 setFocusPolicy(Qt::NoFocus); 0534 0535 d->ratingWidget = new RatingWidget(this); 0536 d->ratingWidget->setTracking(false); 0537 0538 d->shortcut = new DAdjustableLabel(this); 0539 QFont fnt = d->shortcut->font(); 0540 fnt.setItalic(true); 0541 d->shortcut->setFont(fnt); 0542 d->shortcut->setAlignment(Qt::AlignRight | Qt::AlignVCenter); 0543 d->shortcut->setWordWrap(false); 0544 0545 setContentsMargins(QMargins()); 0546 setSpacing(0); 0547 0548 // ------------------------------------------------------------- 0549 0550 connect(d->ratingWidget, SIGNAL(signalRatingModified(int)), 0551 this, SLOT(slotUpdateDescription(int))); 0552 0553 connect(d->ratingWidget, SIGNAL(signalRatingChanged(int)), 0554 this, SIGNAL(signalRatingChanged(int))); 0555 } 0556 0557 RatingBox::~RatingBox() 0558 { 0559 delete d; 0560 } 0561 0562 void RatingBox::slotUpdateDescription(int rating) 0563 { 0564 DXmlGuiWindow* const app = dynamic_cast<DXmlGuiWindow*>(qApp->activeWindow()); 0565 0566 if (app) 0567 { 0568 QAction* const ac = app->actionCollection()->action(QString::fromLatin1("rateshortcut-%1").arg(rating)); 0569 0570 if (ac) 0571 { 0572 d->shortcut->setAdjustedText(ac->shortcut().toString()); 0573 } 0574 } 0575 } 0576 0577 // ------------------------------------------------------------------------------- 0578 0579 RatingMenuAction::RatingMenuAction(QMenu* const parent) 0580 : QMenu(parent) 0581 { 0582 setTitle(i18n("Rating")); 0583 QWidgetAction* const wa = new QWidgetAction(this); 0584 RatingBox* const rb = new RatingBox(parent); 0585 wa->setDefaultWidget(rb); 0586 addAction(wa); 0587 0588 connect(rb, SIGNAL(signalRatingChanged(int)), 0589 this, SIGNAL(signalRatingChanged(int))); 0590 0591 connect(rb, SIGNAL(signalRatingChanged(int)), 0592 parent, SLOT(close())); 0593 } 0594 0595 RatingMenuAction::~RatingMenuAction() 0596 { 0597 } 0598 0599 } // namespace Digikam 0600 0601 #include "moc_ratingwidget.cpp"