]> git.sur5r.net Git - minitube/blob - src/playlist/PrettyItemDelegate.cpp
6cdac51014805cdb6efc823740a9419996ee9f28
[minitube] / src / playlist / PrettyItemDelegate.cpp
1 #include "PrettyItemDelegate.h"
2 #include "../ListModel.h"
3 #include "../fontutils.h"
4 #include "../downloaditem.h"
5 #include "../iconloader/qticonloader.h"
6
7 #include <QFontMetricsF>
8 #include <QPainter>
9 #include <QHash>
10
11 const qreal PrettyItemDelegate::THUMB_HEIGHT = 90.0;
12 const qreal PrettyItemDelegate::THUMB_WIDTH = 120.0;
13 const qreal PrettyItemDelegate::PADDING = 10.0;
14
15 QRect lastAuthorRect;
16 QHash<int, QRect> authorRects;
17
18 PrettyItemDelegate::PrettyItemDelegate(QObject* parent, bool downloadInfo)
19     : QStyledItemDelegate(parent),
20     downloadInfo(downloadInfo) {
21     boldFont.setBold(true);
22     smallerBoldFont = FontUtils::smallBold();
23     smallerFont = FontUtils::small();
24
25     if (downloadInfo) {
26         progressBar = new QProgressBar(qApp->activeWindow());
27         QPalette palette = progressBar->palette();
28         palette.setColor(QPalette::Window, Qt::transparent);
29         progressBar->setPalette(palette);
30         progressBar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
31         progressBar->hide();
32     } else createPlayIcon();
33 }
34
35 void PrettyItemDelegate::createPlayIcon() {
36     playIcon = QPixmap(THUMB_WIDTH, THUMB_HEIGHT);
37     playIcon.fill(Qt::transparent);
38     QPainter painter(&playIcon);
39     QPolygon polygon;
40     polygon << QPoint(PADDING*4, PADDING*2)
41             << QPoint(THUMB_WIDTH-PADDING*4, THUMB_HEIGHT/2)
42             << QPoint(PADDING*4, THUMB_HEIGHT-PADDING*2);
43     painter.setRenderHints(QPainter::Antialiasing, true);
44     painter.setBrush(Qt::white);
45     QPen pen;
46     pen.setColor(Qt::white);
47     pen.setWidth(PADDING);
48     pen.setJoinStyle(Qt::RoundJoin);
49     pen.setCapStyle(Qt::RoundCap);
50     painter.setPen(pen);
51     painter.drawPolygon(polygon);
52 }
53
54 PrettyItemDelegate::~PrettyItemDelegate() { }
55
56 QSize PrettyItemDelegate::sizeHint( const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/ ) const {
57     return QSize( 256, THUMB_HEIGHT+1.0);
58 }
59
60 void PrettyItemDelegate::paint( QPainter* painter,
61                                 const QStyleOptionViewItem& option, const QModelIndex& index ) const {
62
63     int itemType = index.data(ItemTypeRole).toInt();
64     if (itemType == ItemTypeVideo) {
65         QStyleOptionViewItemV4 opt = QStyleOptionViewItemV4(option);
66         initStyleOption(&opt, index);
67         opt.text = "";
68         opt.widget->style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget);
69         paintBody(painter, opt, index);
70     } else
71         QStyledItemDelegate::paint( painter, option, index );
72
73 }
74
75 void PrettyItemDelegate::paintBody( QPainter* painter,
76                                     const QStyleOptionViewItem& option,
77                                     const QModelIndex& index ) const {
78
79     painter->save();
80     painter->translate( option.rect.topLeft() );
81
82
83     QRectF line(0, 0, option.rect.width(), option.rect.height());
84     if (downloadInfo) line.setWidth(line.width() / 2);
85     painter->setClipRect(line);
86
87     const bool isActive = index.data( ActiveTrackRole ).toBool();
88     const bool isSelected = option.state & QStyle::State_Selected;
89
90     // draw the "current track" highlight underneath the text
91     if (isActive && !isSelected) {
92         paintActiveOverlay(painter, line.x(), line.y(), line.width(), line.height());
93     }
94
95     // get the video metadata
96     const VideoPointer videoPointer = index.data( VideoRole ).value<VideoPointer>();
97     const Video *video = videoPointer.data();
98
99     // thumb
100     if (!video->thumbnail().isNull()) {
101         painter->drawImage(QRect(0, 0, THUMB_WIDTH, THUMB_HEIGHT), video->thumbnail());
102
103         // play icon overlayed on the thumb
104         if (isActive)
105             paintPlayIcon(painter);
106
107         // time
108         QString timeString;
109         int duration = video->duration();
110         if ( duration > 3600 )
111             timeString = QTime().addSecs(duration).toString("h:mm:ss");
112         else
113             timeString = QTime().addSecs(duration).toString("m:ss");
114         drawTime(painter, timeString, line);
115
116     }
117
118     if (isActive) painter->setFont(boldFont);
119
120     // text color
121     if (isSelected)
122         painter->setPen(QPen(option.palette.brush(QPalette::HighlightedText), 0));
123     else
124         painter->setPen(QPen(option.palette.brush(QPalette::Text), 0));
125
126     // title
127     QString videoTitle = video->title();
128     QRectF textBox = line.adjusted(PADDING+THUMB_WIDTH, PADDING, -2 * PADDING, -PADDING);
129     textBox = painter->boundingRect( textBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, videoTitle);
130     painter->drawText(textBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, videoTitle);
131
132     painter->setFont(smallerFont);
133
134     // published date
135     QString publishedString = video->published().date().toString(Qt::DefaultLocaleShortDate);
136     QSizeF publishedStringSize(QFontMetrics(painter->font()).size( Qt::TextSingleLine, publishedString ) );
137     QPointF textLoc(PADDING+THUMB_WIDTH, PADDING*2 + textBox.height());
138     QRectF publishedTextBox( textLoc , publishedStringSize);
139     painter->drawText(publishedTextBox, Qt::AlignLeft | Qt::AlignTop, publishedString);
140
141     // author
142     bool authorHovered = false;
143     bool authorPressed = false;
144     const bool isHovered = index.data(HoveredItemRole).toBool();
145     if (isHovered) {
146         authorHovered = index.data(AuthorHoveredRole).toBool();
147         authorPressed = index.data(AuthorPressedRole).toBool();
148     }
149
150     painter->save();
151     painter->setFont(smallerBoldFont);
152     if (!isSelected) {
153         if (authorHovered)
154             painter->setPen(QPen(option.palette.brush(QPalette::Highlight), 0));
155         else
156             painter->setPen(QPen(option.palette.brush(QPalette::Mid), 0));
157     }
158     QString authorString = video->author();
159     QSizeF authorStringSize(QFontMetrics(painter->font()).size( Qt::TextSingleLine, authorString ) );
160     textLoc.setX(textLoc.x() + publishedStringSize.width() + PADDING);
161     QRectF authorTextBox( textLoc , authorStringSize);
162     authorRects.insert(index.row(), authorTextBox.toRect());
163     painter->drawText(authorTextBox, Qt::AlignLeft | Qt::AlignTop, authorString);
164     painter->restore();
165
166     // view count
167     if (video->viewCount() >= 0) {
168         painter->save();
169         QLocale locale;
170         QString viewCountString = tr("%1 views").arg(locale.toString(video->viewCount()));
171         QSizeF viewCountStringSize(QFontMetrics(painter->font()).size( Qt::TextSingleLine, viewCountString ) );
172         textLoc.setX(textLoc.x() + authorStringSize.width() + PADDING);
173         QRectF viewCountTextBox( textLoc , viewCountStringSize);
174         painter->drawText(viewCountTextBox, Qt::AlignLeft | Qt::AlignBottom, viewCountString);
175         painter->restore();
176     }
177
178     /*
179     QLinearGradient myGradient;
180     QPen myPen;
181     QFont myFont;
182     QPointF baseline(authorTextBox.x(), authorTextBox.y() + authorTextBox.height());
183     QPainterPath myPath;
184     myPath.addText(baseline, boldFont, authorString);
185     painter->setBrush(palette.color(QPalette::WindowText));
186     painter->setPen(palette.color(QPalette::Dark));
187     painter->setRenderHints (QPainter::Antialiasing, true);
188     painter->drawPath(myPath);
189     */
190
191     // separator
192     painter->setClipping(false);
193     painter->setPen(option.palette.color(QPalette::Midlight));
194     painter->drawLine(THUMB_WIDTH, THUMB_HEIGHT, option.rect.width(), THUMB_HEIGHT);
195     if (!video->thumbnail().isNull())
196         painter->setPen(Qt::black);
197     painter->drawLine(0, THUMB_HEIGHT, THUMB_WIDTH-1, THUMB_HEIGHT);
198
199     painter->restore();
200
201     if (downloadInfo) paintDownloadInfo(painter, option, index);
202
203 }
204
205 void PrettyItemDelegate::paintActiveOverlay( QPainter *painter, qreal x, qreal y, qreal w, qreal h ) const {
206
207     QPalette palette;
208     QColor highlightColor = palette.color(QPalette::Highlight);
209     QColor backgroundColor = palette.color(QPalette::Base);
210     const float animation = 0.25;
211     const int gradientRange = 16;
212
213     QColor color2 = QColor::fromHsv(
214             highlightColor.hue(),
215             (int) (backgroundColor.saturation() * (1.0f - animation) + highlightColor.saturation() * animation),
216             (int) (backgroundColor.value() * (1.0f - animation) + highlightColor.value() * animation)
217             );
218     QColor color1 = QColor::fromHsv(
219             color2.hue(),
220             qMax(color2.saturation() - gradientRange, 0),
221             qMin(color2.value() + gradientRange, 255)
222             );
223     QRect rect((int) x, (int) y, (int) w, (int) h);
224     painter->save();
225     painter->setPen(Qt::NoPen);
226     QLinearGradient linearGradient(0, 0, 0, rect.height());
227     linearGradient.setColorAt(0.0, color1);
228     linearGradient.setColorAt(1.0, color2);
229     painter->setBrush(linearGradient);
230     painter->drawRect(rect);
231     painter->restore();
232 }
233
234 void PrettyItemDelegate::paintPlayIcon(QPainter *painter) const {
235     painter->save();
236     painter->setOpacity(.5);
237     painter->drawPixmap(playIcon.rect(), playIcon);
238     painter->restore();
239 }
240
241 void PrettyItemDelegate::drawTime(QPainter *painter, QString time, QRectF line) const {
242     static const int timePadding = 4;
243     QRectF textBox = painter->boundingRect(line, Qt::AlignLeft | Qt::AlignTop, time);
244     // add padding
245     textBox.adjust(0, 0, timePadding, 0);
246     // move to bottom right corner of the thumb
247     textBox.translate(THUMB_WIDTH - textBox.width(), THUMB_HEIGHT - textBox.height());
248
249     painter->save();
250     painter->setPen(Qt::NoPen);
251     painter->setBrush(Qt::black);
252     painter->setOpacity(.5);
253     painter->drawRect(textBox);
254     painter->restore();
255
256     painter->save();
257     painter->setPen(Qt::white);
258     painter->drawText(textBox, Qt::AlignCenter, time);
259     painter->restore();
260 }
261
262 void PrettyItemDelegate::paintDownloadInfo( QPainter* painter,
263                                             const QStyleOptionViewItem& option,
264                                             const QModelIndex& index ) const {
265
266     // get the video metadata
267     const DownloadItemPointer downloadItemPointer = index.data(DownloadItemRole).value<DownloadItemPointer>();
268     const DownloadItem *downloadItem = downloadItemPointer.data();
269
270     painter->save();
271
272     const QRect line(0, 0, option.rect.width() / 2, option.rect.height());
273
274     painter->translate(option.rect.topLeft());
275     painter->translate(line.width(), 0);
276
277     QString message;
278     DownloadItemStatus status = downloadItem->status();
279
280     if (status == Downloading) {
281         QString downloaded = DownloadItem::formattedFilesize(downloadItem->bytesReceived());
282         QString total = DownloadItem::formattedFilesize(downloadItem->bytesTotal());
283         QString speed = DownloadItem::formattedSpeed(downloadItem->currentSpeed());
284         QString eta = DownloadItem::formattedTime(downloadItem->remainingTime());
285
286         message = tr("%1 of %2 (%3) — %4").arg(
287                 downloaded,
288                 total,
289                 speed,
290                 eta
291                 );
292     } else if (status == Starting) {
293         message = tr("Preparing");
294     } else if (status == Failed) {
295         message = tr("Failed") + " — " + downloadItem->errorMessage();
296     } else if (status == Finished) {
297         message = tr("Completed");
298     } else if (status == Idle) {
299         message = tr("Stopped");
300     }
301
302     // progressBar->setPalette(option.palette);
303     if (status == Finished) {
304         progressBar->setValue(100);
305         progressBar->setEnabled(true);
306     } else if (status == Downloading) {
307         progressBar->setValue(downloadItem->currentPercent());
308         progressBar->setEnabled(true);
309     } else {
310         progressBar->setValue(0);
311         progressBar->setEnabled(false);
312     }
313
314     int progressBarWidth = line.width() - PADDING*4 - 16;
315     progressBar->setMaximumWidth(progressBarWidth);
316     progressBar->setMinimumWidth(progressBarWidth);
317     painter->save();
318     painter->translate(PADDING, PADDING);
319     progressBar->render(painter);
320     painter->restore();
321
322     bool downloadButtonHovered = false;
323     bool downloadButtonPressed = false;
324     const bool isHovered = index.data(HoveredItemRole).toBool();
325     if (isHovered) {
326         downloadButtonHovered = index.data(DownloadButtonHoveredRole).toBool();
327         downloadButtonPressed = index.data(DownloadButtonPressedRole).toBool();
328     }
329     QIcon::Mode iconMode;
330     if (downloadButtonPressed) iconMode = QIcon::Selected;
331     else if (downloadButtonHovered) iconMode = QIcon::Active;
332     else iconMode = QIcon::Normal;
333
334     if (status != Finished && status != Failed && status != Idle) {
335         if (downloadButtonHovered) message = tr("Stop downloading");
336         painter->save();
337         QIcon closeIcon = QtIconLoader::icon("window-close");
338         painter->drawPixmap(downloadButtonRect(line), closeIcon.pixmap(16, 16, iconMode));
339         painter->restore();
340     }
341
342     else if (status == Finished) {
343         if (downloadButtonHovered)
344 #ifdef APP_MAC
345         message = tr("Show in %1").arg("Finder");
346 #else
347         message = tr("Open parent folder");
348 #endif
349         painter->save();
350         QIcon searchIcon = QtIconLoader::icon("system-search");
351         painter->drawPixmap(downloadButtonRect(line), searchIcon.pixmap(16, 16, iconMode));
352         painter->restore();
353     }
354
355     else if (status == Failed || status == Idle) {
356         if (downloadButtonHovered) message = tr("Restart downloading");
357         painter->save();
358         QIcon searchIcon = QtIconLoader::icon("view-refresh");
359         painter->drawPixmap(downloadButtonRect(line), searchIcon.pixmap(16, 16, iconMode));
360         painter->restore();
361     }
362
363     QRectF textBox = line.adjusted(PADDING, PADDING*2 + progressBar->sizeHint().height(), -2 * PADDING, -PADDING);
364     textBox = painter->boundingRect( textBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, message);
365     painter->drawText(textBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, message);
366
367     painter->restore();
368
369 }
370
371 QRect PrettyItemDelegate::downloadButtonRect(QRect line) const {
372     return QRect(
373             line.width() - PADDING*2 - 16,
374             PADDING + progressBar->sizeHint().height() / 2 - 8,
375             16,
376             16);
377 }
378
379 QRect PrettyItemDelegate::authorRect(const QModelIndex& index) const {
380     return authorRects.value(index.row());
381 }