]> git.sur5r.net Git - minitube/blob - src/playlistitemdelegate.cpp
c7795d5b572d77e600c5c4507371f1ebd20a1e59
[minitube] / src / playlistitemdelegate.cpp
1 /* $BEGIN_LICENSE
2
3 This file is part of Minitube.
4 Copyright 2009, Flavio Tordini <flavio.tordini@gmail.com>
5
6 Minitube is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 Minitube is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Minitube.  If not, see <http://www.gnu.org/licenses/>.
18
19 $END_LICENSE */
20
21 #include "playlistitemdelegate.h"
22 #include "datautils.h"
23 #include "downloaditem.h"
24 #include "fontutils.h"
25 #include "iconutils.h"
26 #include "playlistmodel.h"
27 #include "playlistview.h"
28 #include "video.h"
29 #include "videodefinition.h"
30
31 const int PlaylistItemDelegate::thumbHeight = 90;
32 const int PlaylistItemDelegate::thumbWidth = 160;
33 const int PlaylistItemDelegate::padding = 8;
34
35 namespace {
36
37 bool drawElidedText(QPainter *painter, const QRect &textBox, const int flags, const QString &text) {
38     QString elidedText =
39             painter->fontMetrics().elidedText(text, Qt::ElideRight, textBox.width(), flags);
40     painter->drawText(textBox, 0, elidedText);
41     return elidedText.length() < text.length();
42 }
43 }
44
45 PlaylistItemDelegate::PlaylistItemDelegate(QObject *parent, bool downloadInfo)
46     : QStyledItemDelegate(parent), downloadInfo(downloadInfo), progressBar(0) {
47     listView = qobject_cast<PlaylistView *>(parent);
48
49     smallerBoldFont = FontUtils::smallBold();
50     smallerFont = FontUtils::small();
51
52     if (downloadInfo) {
53         progressBar = new QProgressBar(qApp->activeWindow());
54         QPalette palette = progressBar->palette();
55         palette.setColor(QPalette::Window, Qt::transparent);
56         progressBar->setPalette(palette);
57         progressBar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
58         progressBar->hide();
59     } else
60         createPlayIcon();
61 }
62
63 PlaylistItemDelegate::~PlaylistItemDelegate() {
64     if (progressBar) delete progressBar;
65 }
66
67 void PlaylistItemDelegate::createPlayIcon() {
68     qreal maxRatio = IconUtils::maxSupportedPixelRatio();
69     playIcon = QPixmap(thumbWidth * maxRatio, thumbHeight * maxRatio);
70     playIcon.setDevicePixelRatio(maxRatio);
71     playIcon.fill(Qt::transparent);
72
73     QPixmap tempPixmap(thumbWidth * maxRatio, thumbHeight * maxRatio);
74     tempPixmap.setDevicePixelRatio(maxRatio);
75     tempPixmap.fill(Qt::transparent);
76     QPainter painter(&tempPixmap);
77     painter.setRenderHints(QPainter::Antialiasing, true);
78
79     const int hPadding = padding * 6;
80     const int vPadding = padding * 2;
81
82     QPolygon polygon;
83     polygon << QPoint(hPadding, vPadding) << QPoint(thumbWidth - hPadding, thumbHeight / 2)
84             << QPoint(hPadding, thumbHeight - vPadding);
85     painter.setBrush(Qt::white);
86     QPen pen;
87     pen.setColor(Qt::white);
88     pen.setWidth(padding);
89     pen.setJoinStyle(Qt::RoundJoin);
90     pen.setCapStyle(Qt::RoundCap);
91     painter.setPen(pen);
92     painter.drawPolygon(polygon);
93     painter.end();
94
95     QPainter painter2(&playIcon);
96     painter2.setOpacity(.75);
97     painter2.drawPixmap(0, 0, tempPixmap);
98 }
99
100 QSize PlaylistItemDelegate::sizeHint(const QStyleOptionViewItem & /*option*/,
101                                      const QModelIndex & /*index*/) const {
102     return QSize(thumbWidth, thumbHeight + 1);
103 }
104
105 void PlaylistItemDelegate::paint(QPainter *painter,
106                                  const QStyleOptionViewItem &option,
107                                  const QModelIndex &index) const {
108     int itemType = index.data(ItemTypeRole).toInt();
109     if (itemType == ItemTypeVideo) {
110         QStyleOptionViewItem opt = QStyleOptionViewItem(option);
111         initStyleOption(&opt, index);
112         opt.text.clear();
113         opt.widget->style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget);
114         paintBody(painter, opt, index);
115     } else
116         QStyledItemDelegate::paint(painter, option, index);
117 }
118
119 void PlaylistItemDelegate::paintBody(QPainter *painter,
120                                      const QStyleOptionViewItem &option,
121                                      const QModelIndex &index) const {
122     painter->save();
123     painter->translate(option.rect.topLeft());
124
125     QRect line(0, 0, option.rect.width(), option.rect.height());
126     if (downloadInfo) line.setWidth(line.width() / 2);
127
128     const bool isActive = index.data(ActiveTrackRole).toBool();
129     const bool isSelected = option.state & QStyle::State_Selected;
130
131     // get the video metadata
132     const Video *video = index.data(VideoRole).value<VideoPointer>().data();
133
134     // draw the "current track" highlight underneath the text
135     if (isActive && !isSelected) paintActiveOverlay(painter, option, line);
136
137     // separator
138     painter->setPen(option.palette.color(QPalette::Midlight));
139     painter->drawLine(thumbWidth, thumbHeight, option.rect.width(), thumbHeight);
140     if (!video->getThumbnail().isNull()) painter->setPen(Qt::black);
141     painter->drawLine(0, thumbHeight, thumbWidth - 1, thumbHeight);
142
143     // thumb
144     painter->drawPixmap(0, 0, video->getThumbnail());
145
146     const bool thumbsOnly = line.width() <= thumbWidth + 60;
147     const bool isHovered = index.data(HoveredItemRole).toBool();
148
149     // play icon overlayed on the thumb
150     if (isActive && (!isHovered && thumbsOnly)) painter->drawPixmap(0, 0, playIcon);
151
152     // time
153     if (video->getDuration() > 0) drawTime(painter, video->getFormattedDuration(), line);
154
155     if (!thumbsOnly) {
156         // text color
157         if (isSelected)
158             painter->setPen(QPen(option.palette.highlightedText(), 0));
159         else
160             painter->setPen(QPen(option.palette.text(), 0));
161
162         // title
163         QStringRef title(&video->getTitle());
164         QString elidedTitle = video->getTitle();
165         static const int titleFlags = Qt::AlignTop | Qt::TextWordWrap;
166         QRect textBox = line.adjusted(padding + thumbWidth, padding, 0, 0);
167         textBox = painter->boundingRect(textBox, titleFlags, elidedTitle);
168         while (textBox.height() > 55 && elidedTitle.length() > 10) {
169 #if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
170             title = title.left(title.length() - 1);
171 #elif QT_VERSION < QT_VERSION_CHECK(5, 8, 0)
172             title.truncate(title.length() - 1);
173 #else
174             title.chop(1);
175 #endif
176             elidedTitle = title.trimmed() + QStringLiteral("…");
177             textBox = painter->boundingRect(textBox, titleFlags, elidedTitle);
178         }
179         painter->drawText(textBox, titleFlags, elidedTitle);
180
181         painter->setFont(smallerFont);
182         painter->setOpacity(.5);
183         QFontMetrics fontMetrics = painter->fontMetrics();
184         static const int flags = Qt::AlignLeft | Qt::AlignTop;
185
186         // published date
187         const QString &published = video->getFormattedPublished();
188         QSize textSize(fontMetrics.size(Qt::TextSingleLine, published));
189         QPoint textPoint(padding + thumbWidth, padding * 2 + textBox.height());
190         textBox = QRect(textPoint, textSize);
191         painter->drawText(textBox, flags, published);
192
193         bool elided = false;
194
195         // author
196         if (!listView || listView->isClickableAuthors()) {
197             bool authorHovered = isHovered && index.data(AuthorHoveredRole).toBool();
198
199             painter->save();
200             painter->setFont(smallerBoldFont);
201             if (!isSelected) {
202                 if (authorHovered)
203                     painter->setPen(QPen(option.palette.brush(QPalette::Highlight), 0));
204             }
205             const QString &author = video->getChannelTitle();
206             textPoint.setX(textBox.right() + padding);
207             textSize = QSize(painter->fontMetrics().size(Qt::TextSingleLine, author));
208             textBox = QRect(textPoint, textSize);
209             authorRects.insert(index.row(), textBox);
210             if (textBox.right() > line.width()) {
211                 textBox.setRight(line.width());
212                 elided = drawElidedText(painter, textBox, flags, author);
213             } else {
214                 painter->drawText(textBox, flags, author);
215             }
216             painter->restore();
217         }
218
219         // view count
220         if (video->getViewCount() > 0) {
221             QLocale locale;
222             const QString viewCount = tr("%1 views").arg(locale.toString(video->getViewCount()));
223             textPoint.setX(textBox.right() + padding);
224             textSize = QSize(fontMetrics.size(Qt::TextSingleLine, viewCount));
225             if (elided || textPoint.x() + textSize.width() > line.width()) {
226                 textPoint.setX(thumbWidth + padding);
227                 textPoint.setY(textPoint.y() + textSize.height() + padding);
228             }
229             textBox = QRect(textPoint, textSize);
230             if (textBox.bottom() <= line.height()) {
231                 painter->drawText(textBox, flags, viewCount);
232             }
233         }
234
235         if (downloadInfo) {
236             const QString &def = VideoDefinition::forCode(video->getDefinitionCode()).getName();
237             textPoint.setX(textBox.right() + padding);
238             textSize = QSize(fontMetrics.size(Qt::TextSingleLine, def));
239             textBox = QRect(textPoint, textSize);
240             painter->drawText(textBox, flags, def);
241         }
242
243     } else {
244         // thumbs only
245         if (isHovered) {
246             painter->setFont(smallerFont);
247             painter->setPen(Qt::white);
248             QStringRef title(&video->getTitle());
249             QString elidedTitle = video->getTitle();
250             static const int titleFlags = Qt::AlignTop | Qt::TextWordWrap;
251             QRect textBox(padding, padding, thumbWidth - padding * 2, thumbHeight - padding * 2);
252             textBox = painter->boundingRect(textBox, titleFlags, elidedTitle);
253             while (textBox.height() > 55 && elidedTitle.length() > 10) {
254 #if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
255                 title = title.left(title.length() - 1);
256 #elif QT_VERSION < QT_VERSION_CHECK(5, 8, 0)
257                 title.truncate(title.length() - 1);
258 #else
259                 title.chop(1);
260 #endif
261                 elidedTitle = title.trimmed() + QStringLiteral("…");
262                 textBox = painter->boundingRect(textBox, titleFlags, elidedTitle);
263             }
264             painter->fillRect(QRect(0, 0, thumbWidth, textBox.height() + padding * 2),
265                               QColor(0, 0, 0, 128));
266             painter->drawText(textBox, titleFlags, elidedTitle);
267         }
268     }
269
270     painter->restore();
271
272     if (downloadInfo) paintDownloadInfo(painter, option, index);
273 }
274
275 void PlaylistItemDelegate::paintActiveOverlay(QPainter *painter,
276                                               const QStyleOptionViewItem &option,
277                                               const QRect &line) const {
278     painter->save();
279     painter->setOpacity(.2);
280     painter->fillRect(line, option.palette.highlight());
281     painter->restore();
282 }
283
284 void PlaylistItemDelegate::drawTime(QPainter *painter,
285                                     const QString &time,
286                                     const QRect &line) const {
287     static const int timePadding = 4;
288     QRect textBox = painter->boundingRect(line, Qt::AlignLeft | Qt::AlignTop, time);
289     // add padding
290     textBox.adjust(0, 0, timePadding, 0);
291     // move to bottom right corner of the thumb
292     textBox.translate(thumbWidth - textBox.width(), thumbHeight - textBox.height());
293
294     painter->save();
295     painter->setPen(Qt::NoPen);
296     painter->setBrush(Qt::black);
297     painter->setOpacity(.5);
298     painter->drawRect(textBox);
299     painter->restore();
300
301     painter->save();
302     painter->setPen(Qt::white);
303     painter->drawText(textBox, Qt::AlignCenter, time);
304     painter->restore();
305 }
306
307 void PlaylistItemDelegate::paintDownloadInfo(QPainter *painter,
308                                              const QStyleOptionViewItem &option,
309                                              const QModelIndex &index) const {
310     // get the video metadata
311     const DownloadItemPointer downloadItemPointer =
312             index.data(DownloadItemRole).value<DownloadItemPointer>();
313     const DownloadItem *downloadItem = downloadItemPointer.data();
314
315     painter->save();
316
317     const QRect line(0, 0, option.rect.width() / 2, option.rect.height());
318
319     painter->translate(option.rect.topLeft());
320     painter->translate(line.width(), 0);
321
322     QString message;
323     DownloadItemStatus status = downloadItem->status();
324
325     if (status == Downloading) {
326         QString downloaded = DownloadItem::formattedFilesize(downloadItem->bytesReceived());
327         QString total = DownloadItem::formattedFilesize(downloadItem->bytesTotal());
328         QString speed = DownloadItem::formattedSpeed(downloadItem->currentSpeed());
329         QString eta = DownloadItem::formattedTime(downloadItem->remainingTime());
330
331         message = tr("%1 of %2 (%3) — %4").arg(downloaded, total, speed, eta);
332     } else if (status == Starting) {
333         message = tr("Preparing");
334     } else if (status == Failed) {
335         message = tr("Failed") + " — " + downloadItem->errorMessage();
336     } else if (status == Finished) {
337         message = tr("Completed");
338     } else if (status == Idle) {
339         message = tr("Stopped");
340     }
341
342     // progressBar->setPalette(option.palette);
343     if (status == Finished) {
344         progressBar->setValue(100);
345         progressBar->setEnabled(true);
346     } else if (status == Downloading) {
347         progressBar->setValue(downloadItem->currentPercent());
348         progressBar->setEnabled(true);
349     } else {
350         progressBar->setValue(0);
351         progressBar->setEnabled(false);
352     }
353
354     int progressBarWidth = line.width() - padding * 4 - 16;
355     progressBar->setMaximumWidth(progressBarWidth);
356     progressBar->setMinimumWidth(progressBarWidth);
357     painter->save();
358     painter->translate(padding, padding);
359     progressBar->render(painter);
360     painter->restore();
361
362     bool downloadButtonHovered = false;
363     bool downloadButtonPressed = false;
364     const bool isHovered = index.data(HoveredItemRole).toBool();
365     if (isHovered) {
366         downloadButtonHovered = index.data(DownloadButtonHoveredRole).toBool();
367         downloadButtonPressed = index.data(DownloadButtonPressedRole).toBool();
368     }
369     QIcon::Mode iconMode;
370     if (downloadButtonPressed)
371         iconMode = QIcon::Selected;
372     else if (downloadButtonHovered)
373         iconMode = QIcon::Active;
374     else
375         iconMode = QIcon::Normal;
376
377     if (status != Finished && status != Failed && status != Idle) {
378         if (downloadButtonHovered) message = tr("Stop downloading");
379         painter->save();
380         QIcon closeIcon = IconUtils::icon("window-close");
381         painter->drawPixmap(downloadButtonRect(line), closeIcon.pixmap(16, 16, iconMode));
382         painter->restore();
383     }
384
385     else if (status == Finished) {
386         if (downloadButtonHovered)
387 #ifdef APP_MAC
388             message = tr("Show in %1").arg("Finder");
389 #else
390             message = tr("Open parent folder");
391 #endif
392         painter->save();
393         QIcon searchIcon = IconUtils::icon("system-search");
394         painter->drawPixmap(downloadButtonRect(line), searchIcon.pixmap(16, 16, iconMode));
395         painter->restore();
396     }
397
398     else if (status == Failed || status == Idle) {
399         if (downloadButtonHovered) message = tr("Restart downloading");
400         painter->save();
401         QIcon searchIcon = IconUtils::icon("view-refresh");
402         painter->drawPixmap(downloadButtonRect(line), searchIcon.pixmap(16, 16, iconMode));
403         painter->restore();
404     }
405
406     QRect textBox = line.adjusted(padding, padding * 2 + progressBar->sizeHint().height(),
407                                   -2 * padding, -padding);
408     textBox = painter->boundingRect(textBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap,
409                                     message);
410     painter->drawText(textBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, message);
411
412     painter->restore();
413 }
414
415 QRect PlaylistItemDelegate::downloadButtonRect(const QRect &line) const {
416     return QRect(line.width() - padding * 2 - 16,
417                  padding + progressBar->sizeHint().height() / 2 - 8, 16, 16);
418 }
419
420 QRect PlaylistItemDelegate::authorRect(const QModelIndex &index) const {
421     return authorRects.value(index.row());
422 }