1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2005-08-15
7 * Description : a widget to draw stars rating
8 *
9 * Copyright (C) 2005 by Owen Hirst <n8rider@sbcglobal.net>
10 * Copyright (C) 2006-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
11 *
12 * This program is free software; you can redistribute it
13 * and/or modify it under the terms of the GNU General
14 * Public License as published by the Free Software Foundation;
15 * either version 2, or (at your option)
16 * any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * ============================================================ */
24
25 #include "ratingwidget.h"
26
27 // C++ includes
28
29 #include <cmath>
30
31 // Qt includes
32
33 #include <QApplication>
34 #include <QPainter>
35 #include <QPalette>
36 #include <QPixmap>
37 #include <QTimeLine>
38 #include <QFont>
39 #include <QAction>
40 #include <QWidgetAction>
41
42 // KDE includes
43
44 #include <klocalizedstring.h>
45 #include <kactioncollection.h>
46
47 // Local includes
48
49 #include "digikam_debug.h"
50 #include "digikam_globals.h"
51 #include "thememanager.h"
52 #include "dxmlguiwindow.h"
53 #include "dexpanderbox.h"
54
55 namespace Digikam
56 {
57
58 class Q_DECL_HIDDEN RatingWidget::Private
59 {
60 public:
61
Private()62 explicit Private()
63 : tracking (true),
64 isHovered (false),
65 fading (false),
66 rating (0),
67 fadingValue (0),
68 duration (600),
69 offset (0),
70 fadingTimeLine (nullptr)
71 {
72 }
73
74 bool tracking;
75 bool isHovered;
76 bool fading;
77
78 int rating;
79 int fadingValue;
80 int duration; ///< in milliseconds
81 int offset;
82
83 QTimeLine* fadingTimeLine;
84
85 QPixmap selPixmap; ///< Selected star.
86 QPixmap regPixmap; ///< Regular star.
87 QPixmap disPixmap; ///< Disable star.
88 };
89
RatingWidget(QWidget * const parent)90 RatingWidget::RatingWidget(QWidget* const parent)
91 : QWidget(parent),
92 d (new Private)
93 {
94 slotThemeChanged();
95
96 connect(ThemeManager::instance(), SIGNAL(signalThemeChanged()),
97 this, SLOT(slotThemeChanged()));
98 }
99
~RatingWidget()100 RatingWidget::~RatingWidget()
101 {
102 delete d;
103 }
104
setupTimeLine()105 void RatingWidget::setupTimeLine()
106 {
107 delete d->fadingTimeLine;
108 d->fadingTimeLine = new QTimeLine(d->duration, this);
109 d->fadingTimeLine->setFrameRange(0, 255);
110
111 connect(d->fadingTimeLine, SIGNAL(frameChanged(int)),
112 this, SLOT(setFadingValue(int)));
113
114 d->fadingTimeLine->start();
115 }
116
regPixmapWidth() const117 int RatingWidget::regPixmapWidth() const
118 {
119 return d->regPixmap.width();
120 }
121
setRating(int val)122 void RatingWidget::setRating(int val)
123 {
124 if (((val < RatingMin) || (val > RatingMax)) && (val != NoRating))
125 {
126 return;
127 }
128
129 d->rating = val;
130
131 if (d->tracking)
132 {
133 emit signalRatingChanged(d->rating);
134 }
135
136 emit signalRatingModified(d->rating);
137 update();
138 }
139
rating() const140 int RatingWidget::rating() const
141 {
142 return d->rating;
143 }
144
setTracking(bool tracking)145 void RatingWidget::setTracking(bool tracking)
146 {
147 d->tracking = tracking;
148 }
149
hasTracking() const150 bool RatingWidget::hasTracking() const
151 {
152 return d->tracking;
153 }
154
setFading(bool fading)155 void RatingWidget::setFading(bool fading)
156 {
157 d->fading = fading;
158 }
159
hasFading() const160 bool RatingWidget::hasFading() const
161 {
162 return d->fading;
163 }
164
setFadingValue(int value)165 void RatingWidget::setFadingValue(int value)
166 {
167 d->fadingValue = value;
168
169 if ((d->fadingValue >= 255) && d->fadingTimeLine)
170 {
171 d->fadingTimeLine->stop();
172 }
173
174 update();
175 }
176
setVisible(bool visible)177 void RatingWidget::setVisible(bool visible)
178 {
179 QWidget::setVisible(visible);
180
181 if (visible)
182 {
183 startFading();
184 }
185 else
186 {
187 stopFading();
188 }
189 }
190
maximumVisibleWidth() const191 int RatingWidget::maximumVisibleWidth() const
192 {
193 return RatingMax * (d->disPixmap.width()+1);
194 }
195
startFading()196 void RatingWidget::startFading()
197 {
198 if (!hasFading())
199 {
200 return;
201 }
202
203 if (!d->isHovered)
204 {
205 d->isHovered = true;
206 d->fadingValue = 0;
207 setupTimeLine();
208 }
209 }
210
stopFading()211 void RatingWidget::stopFading()
212 {
213 if (!hasFading())
214 {
215 return;
216 }
217
218 if (d->fadingTimeLine)
219 {
220 d->fadingTimeLine->stop();
221 }
222
223 d->isHovered = false;
224 d->fadingValue = 0;
225 update();
226 }
227
setVisibleImmediately()228 void RatingWidget::setVisibleImmediately()
229 {
230 setFadingValue(255);
231 }
232
starPixmapDisabled() const233 QPixmap RatingWidget::starPixmapDisabled() const
234 {
235 return d->disPixmap;
236 }
237
starPixmapFilled() const238 QPixmap RatingWidget::starPixmapFilled() const
239 {
240 return d->selPixmap;
241 }
242
starPixmap() const243 QPixmap RatingWidget::starPixmap() const
244 {
245 return d->regPixmap;
246 }
247
regeneratePixmaps()248 void RatingWidget::regeneratePixmaps()
249 {
250 slotThemeChanged();
251 }
252
mousePressEvent(QMouseEvent * e)253 void RatingWidget::mousePressEvent(QMouseEvent* e)
254 {
255 if (e->button() != Qt::LeftButton)
256 {
257 return;
258 }
259
260 if (hasFading() && (d->fadingValue < 255))
261 {
262 return;
263 }
264
265 int pos = (e->x() - d->offset) / d->regPixmap.width() +1;
266
267 if (d->rating == pos)
268 {
269 d->rating--;
270 }
271 else
272 {
273 d->rating = pos;
274 }
275
276 if (d->rating > RatingMax)
277 {
278 d->rating = RatingMax;
279 }
280
281 if (d->rating < RatingMin)
282 {
283 d->rating = RatingMin;
284 }
285
286 if (d->tracking)
287 {
288 emit signalRatingChanged(d->rating);
289 }
290
291 emit signalRatingModified(d->rating);
292 update();
293 }
294
mouseMoveEvent(QMouseEvent * e)295 void RatingWidget::mouseMoveEvent(QMouseEvent* e)
296 {
297 if (!(e->buttons() & Qt::LeftButton))
298 {
299 return;
300 }
301
302 if (hasFading() && (d->fadingValue < 255))
303 {
304 return;
305 }
306
307 int pos = (e->x() - d->offset) / d->regPixmap.width() +1;
308
309 if (d->rating != pos)
310 {
311 if (pos > RatingMax) // NOTE: bug. #151357
312 {
313 pos = RatingMax;
314 }
315
316 if (pos < RatingMin)
317 {
318 pos = RatingMin;
319 }
320
321 d->rating = pos;
322
323 if (d->tracking)
324 {
325 emit signalRatingChanged(d->rating);
326 }
327
328 emit signalRatingModified(d->rating);
329 update();
330 }
331 }
332
mouseReleaseEvent(QMouseEvent * e)333 void RatingWidget::mouseReleaseEvent(QMouseEvent* e)
334 {
335 if (e->button() != Qt::LeftButton)
336 {
337 return;
338 }
339
340 if (hasFading() && (d->fadingValue < 255))
341 {
342 return;
343 }
344
345 emit signalRatingChanged(d->rating);
346 }
347
slotThemeChanged()348 void RatingWidget::slotThemeChanged()
349 {
350 d->regPixmap = QPixmap(15, 15);
351 d->regPixmap.fill(Qt::transparent);
352 d->selPixmap = QPixmap(15, 15);
353 d->selPixmap.fill(Qt::transparent);
354 d->disPixmap = QPixmap(15, 15);
355 d->disPixmap.fill(Qt::transparent);
356
357 QPainter p1(&d->regPixmap);
358 p1.setRenderHint(QPainter::Antialiasing, true);
359 p1.setBrush(palette().color(QPalette::Active, backgroundRole()));
360 p1.setPen(palette().color(QPalette::Active, foregroundRole()));
361 p1.drawPolygon(starPolygon(), Qt::WindingFill);
362 p1.end();
363
364 QPainter p2(&d->selPixmap);
365 p2.setRenderHint(QPainter::Antialiasing, true);
366 p2.setBrush(qApp->palette().color(QPalette::Link));
367 p2.setPen(palette().color(QPalette::Active, foregroundRole()));
368 p2.drawPolygon(starPolygon(), Qt::WindingFill);
369 p2.end();
370
371 QPainter p3(&d->disPixmap);
372 p3.setRenderHint(QPainter::Antialiasing, true);
373 p3.setBrush(palette().color(QPalette::Disabled, backgroundRole()));
374 p3.setPen(palette().color(QPalette::Disabled, foregroundRole()));
375 p3.drawPolygon(starPolygon(), Qt::WindingFill);
376 p3.end();
377
378 setMinimumSize(QSize((d->regPixmap.width()+1)*RatingMax, d->regPixmap.height()));
379 update();
380 }
381
starPolygon()382 QPolygon RatingWidget::starPolygon()
383 {
384 QPolygon star;
385 star << QPoint(0, 6);
386 star << QPoint(5, 5);
387 star << QPoint(7, 0);
388 star << QPoint(9, 5);
389 star << QPoint(14, 6);
390 star << QPoint(10, 9);
391 star << QPoint(11, 14);
392 star << QPoint(7, 11);
393 star << QPoint(3, 14);
394 star << QPoint(4, 9);
395
396 return star;
397 }
398
buildIcon(int rate,int size)399 QIcon RatingWidget::buildIcon(int rate, int size)
400 {
401 QPixmap pix(size, size);
402 pix.fill(Qt::transparent);
403 QPainter p(&pix);
404 QTransform transform;
405 transform.scale(size/15.0, size/15.0);
406 p.setTransform(transform);
407 p.setRenderHint(QPainter::Antialiasing, true);
408 p.setPen(qApp->palette().color(QPalette::Active, QPalette::ButtonText));
409
410 if (rate > 0)
411 {
412 p.setBrush(qApp->palette().color(QPalette::Link));
413 }
414
415 p.drawPolygon(starPolygon(), Qt::WindingFill);
416 p.end();
417
418 return QIcon(pix);
419 }
420
paintEvent(QPaintEvent *)421 void RatingWidget::paintEvent(QPaintEvent*)
422 {
423 QPainter p(this);
424
425 d->offset = (width() - RatingMax * (d->disPixmap.width()+1)) / 2;
426
427 // Widget is disable : drawing grayed frame.
428
429 if (!isEnabled())
430 {
431 int x = d->offset;
432
433 for (int i = 0 ; i < RatingMax ; ++i)
434 {
435 p.drawPixmap(x, 0, d->disPixmap);
436 x += d->disPixmap.width()+1;
437 }
438 }
439 else
440 {
441 int x = d->offset;
442 int rate = d->rating != NoRating ? d->rating : 0;
443 QPixmap sel = d->selPixmap;
444 applyFading(sel);
445
446 for (int i = 0 ; i < rate ; ++i)
447 {
448 p.drawPixmap(x, 0, sel);
449 x += sel.width()+1;
450 }
451
452 QPixmap reg = d->regPixmap;
453 applyFading(reg);
454
455 for (int i = rate ; i < RatingMax ; ++i)
456 {
457 p.drawPixmap(x, 0, reg);
458 x += reg.width()+1;
459 }
460 }
461
462 p.end();
463 }
464
applyFading(QPixmap & pix)465 void RatingWidget::applyFading(QPixmap& pix)
466 {
467 if (hasFading() && d->fadingValue < 255)
468 {
469 QPainter p(&pix);
470 p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
471 p.fillRect(pix.rect(), QColor(0, 0, 0, d->fadingValue));
472 p.end();
473 }
474 }
475
476 // -------------------------------------------------------------------------------
477
478 class Q_DECL_HIDDEN RatingBox::Private
479 {
480
481 public:
482
Private()483 explicit Private()
484 : shortcut (nullptr),
485 ratingWidget(nullptr)
486 {
487 }
488
489 DAdjustableLabel* shortcut;
490
491 RatingWidget* ratingWidget;
492 };
493
RatingBox(QWidget * const parent)494 RatingBox::RatingBox(QWidget* const parent)
495 : DVBox(parent),
496 d (new Private)
497 {
498 setAttribute(Qt::WA_DeleteOnClose);
499 setFocusPolicy(Qt::NoFocus);
500
501 d->ratingWidget = new RatingWidget(this);
502 d->ratingWidget->setTracking(false);
503
504 d->shortcut = new DAdjustableLabel(this);
505 QFont fnt = d->shortcut->font();
506 fnt.setItalic(true);
507 d->shortcut->setFont(fnt);
508 d->shortcut->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
509 d->shortcut->setWordWrap(false);
510
511 setContentsMargins(QMargins());
512 setSpacing(0);
513
514 // -------------------------------------------------------------
515
516 connect(d->ratingWidget, SIGNAL(signalRatingModified(int)),
517 this, SLOT(slotUpdateDescription(int)));
518
519 connect(d->ratingWidget, SIGNAL(signalRatingChanged(int)),
520 this, SIGNAL(signalRatingChanged(int)));
521 }
522
~RatingBox()523 RatingBox::~RatingBox()
524 {
525 delete d;
526 }
527
slotUpdateDescription(int rating)528 void RatingBox::slotUpdateDescription(int rating)
529 {
530 DXmlGuiWindow* const app = dynamic_cast<DXmlGuiWindow*>(qApp->activeWindow());
531
532 if (app)
533 {
534 QAction* const ac = app->actionCollection()->action(QString::fromLatin1("rateshortcut-%1").arg(rating));
535
536 if (ac)
537 {
538 d->shortcut->setAdjustedText(ac->shortcut().toString());
539 }
540 }
541 }
542
543 // -------------------------------------------------------------------------------
544
RatingMenuAction(QMenu * const parent)545 RatingMenuAction::RatingMenuAction(QMenu* const parent)
546 : QMenu(parent)
547 {
548 setTitle(i18n("Rating"));
549 QWidgetAction* const wa = new QWidgetAction(this);
550 RatingBox* const rb = new RatingBox(parent);
551 wa->setDefaultWidget(rb);
552 addAction(wa);
553
554 connect(rb, SIGNAL(signalRatingChanged(int)),
555 this, SIGNAL(signalRatingChanged(int)));
556
557 connect(rb, SIGNAL(signalRatingChanged(int)),
558 parent, SLOT(close()));
559 }
560
~RatingMenuAction()561 RatingMenuAction::~RatingMenuAction()
562 {
563 }
564
565 } // namespace Digikam
566