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