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