]> git.sur5r.net Git - minitube/blob - src/playlistitemdelegate.cpp
Upload 3.9.3-2 to unstable
[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 } // namespace
44
45 PlaylistItemDelegate::PlaylistItemDelegate(QObject *parent, bool downloadInfo)
46     : QStyledItemDelegate(parent), downloadInfo(downloadInfo), progressBar(nullptr) {
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 = 2.0;
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);
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         paintBody(painter, option, index);
111     else
112         QStyledItemDelegate::paint(painter, option, index);
113 }
114
115 void PlaylistItemDelegate::paintBody(QPainter *painter,
116                                      const QStyleOptionViewItem &option,
117                                      const QModelIndex &index) const {
118     const bool isSelected = option.state & QStyle::State_Selected;
119     if (isSelected)
120         QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter);
121
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
130     // get the video metadata
131     const Video *video = index.data(VideoRole).value<VideoPointer>().data();
132
133     // draw the "current track" highlight underneath the text
134     if (isActive && !isSelected) paintActiveOverlay(painter, option, line);
135
136     // thumb
137     const QPixmap &thumb = video->getThumbnail();
138     if (!thumb.isNull()) {
139         painter->drawPixmap(0, 0, thumb);
140         if (video->getDuration() > 0) drawTime(painter, video->getFormattedDuration(), line);
141     }
142
143     const bool thumbsOnly = line.width() <= thumbWidth + 60;
144     const bool isHovered = index.data(HoveredItemRole).toBool();
145
146     // play icon overlayed on the thumb
147     bool needPlayIcon = isActive;
148     if (thumbsOnly) needPlayIcon = needPlayIcon && !isHovered;
149     if (needPlayIcon) painter->drawPixmap(0, 0, playIcon);
150
151     if (!thumbsOnly) {
152         // text color
153         if (isSelected)
154             painter->setPen(QPen(option.palette.highlightedText(), 0));
155         else
156             painter->setPen(QPen(option.palette.text(), 0));
157
158         // title
159         QStringRef title(&video->getTitle());
160         QString elidedTitle = video->getTitle();
161         static const int titleFlags = Qt::AlignTop | Qt::TextWordWrap;
162         QRect textBox = line.adjusted(padding + thumbWidth, padding, -padding, 0);
163         textBox = painter->boundingRect(textBox, titleFlags, elidedTitle);
164         while (textBox.height() > 55 && elidedTitle.length() > 10) {
165 #if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
166             title = title.left(title.length() - 1);
167 #elif QT_VERSION < QT_VERSION_CHECK(5, 8, 0)
168             title.truncate(title.length() - 1);
169 #else
170             title.chop(1);
171 #endif
172             elidedTitle = title.trimmed() + QStringLiteral("…");
173             textBox = painter->boundingRect(textBox, titleFlags, elidedTitle);
174         }
175         painter->drawText(textBox, titleFlags, elidedTitle);
176
177         painter->setFont(smallerFont);
178         painter->setOpacity(.5);
179         QFontMetrics fontMetrics = painter->fontMetrics();
180         static const int flags = Qt::AlignLeft | Qt::AlignTop;
181
182         // published date
183         const QString &published = video->getFormattedPublished();
184         QSize textSize(fontMetrics.size(Qt::TextSingleLine, published));
185         QPoint textPoint(padding + thumbWidth, padding * 2 + textBox.height());
186         textBox = QRect(textPoint, textSize);
187         painter->drawText(textBox, flags, published);
188
189         bool elided = false;
190
191         // author
192         if (!listView || listView->isClickableAuthors()) {
193             bool authorHovered = isHovered && index.data(AuthorHoveredRole).toBool();
194
195             painter->save();
196             painter->setFont(smallerBoldFont);
197             if (!isSelected) {
198                 if (authorHovered)
199                     painter->setPen(QPen(option.palette.brush(QPalette::Highlight), 0));
200             }
201             const QString &author = video->getChannelTitle();
202             textPoint.setX(textBox.right() + padding);
203             textSize = QSize(painter->fontMetrics().size(Qt::TextSingleLine, author));
204             textBox = QRect(textPoint, textSize);
205             authorRects.insert(index.row(), textBox);
206             if (textBox.right() > line.width() - padding) {
207                 textBox.setRight(line.width());
208                 elided = drawElidedText(painter, textBox, flags, author);
209             } else {
210                 painter->drawText(textBox, flags, author);
211             }
212             painter->restore();
213         }
214
215         // view count
216         if (video->getViewCount() > 0) {
217             const QString &viewCount = video->getFormattedViewCount();
218             textPoint.setX(textBox.right() + padding);
219             textSize = QSize(fontMetrics.size(Qt::TextSingleLine, viewCount));
220             if (elided || textPoint.x() + textSize.width() > line.width() - padding) {
221                 textPoint.setX(thumbWidth + padding);
222                 textPoint.setY(textPoint.y() + textSize.height() + padding);
223             }
224             textBox = QRect(textPoint, textSize);
225             if (textBox.bottom() <= line.height()) {
226                 painter->drawText(textBox, flags, viewCount);
227             }
228         }
229
230         if (downloadInfo) {
231             const QString &def = VideoDefinition::forCode(video->getDefinitionCode()).getName();
232             textPoint.setX(textBox.right() + padding);
233             textSize = QSize(fontMetrics.size(Qt::TextSingleLine, def));
234             textBox = QRect(textPoint, textSize);
235             painter->drawText(textBox, flags, def);
236         }
237
238     } else {
239         // thumbs only
240         if (isHovered) {
241             painter->setFont(smallerFont);
242             painter->setPen(Qt::white);
243             QStringRef title(&video->getTitle());
244             QString elidedTitle = video->getTitle();
245             static const int titleFlags = Qt::AlignTop | Qt::TextWordWrap;
246             QRect textBox(padding, padding, thumbWidth - padding * 2, thumbHeight - padding * 2);
247             textBox = painter->boundingRect(textBox, titleFlags, elidedTitle);
248             while (textBox.height() > 55 && elidedTitle.length() > 10) {
249 #if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
250                 title = title.left(title.length() - 1);
251 #elif QT_VERSION < QT_VERSION_CHECK(5, 8, 0)
252                 title.truncate(title.length() - 1);
253 #else
254                 title.chop(1);
255 #endif
256                 elidedTitle = title.trimmed() + QStringLiteral("…");
257                 textBox = painter->boundingRect(textBox, titleFlags, elidedTitle);
258             }
259             painter->fillRect(QRect(0, 0, thumbWidth, textBox.height() + padding * 2),
260                               QColor(0, 0, 0, 128));
261             painter->drawText(textBox, titleFlags, elidedTitle);
262         }
263     }
264
265     painter->restore();
266
267     if (downloadInfo) paintDownloadInfo(painter, option, index);
268 }
269
270 void PlaylistItemDelegate::paintActiveOverlay(QPainter *painter,
271                                               const QStyleOptionViewItem &option,
272                                               const QRect &line) const {
273     painter->save();
274     painter->setOpacity(.2);
275     painter->fillRect(line, option.palette.highlight());
276     painter->restore();
277 }
278
279 void PlaylistItemDelegate::drawTime(QPainter *painter,
280                                     const QString &time,
281                                     const QRect &line) const {
282     static const int timePadding = 4;
283     QRect textBox = painter->boundingRect(line, Qt::AlignLeft | Qt::AlignTop, time);
284     // add padding
285     textBox.adjust(0, 0, timePadding, 0);
286     // move to bottom right corner of the thumb
287     textBox.translate(thumbWidth - textBox.width(), thumbHeight - textBox.height());
288
289     painter->save();
290     painter->setPen(Qt::NoPen);
291     painter->setBrush(Qt::black);
292     painter->setOpacity(.5);
293     painter->drawRect(textBox);
294     painter->restore();
295
296     painter->save();
297     painter->setPen(Qt::white);
298     painter->drawText(textBox, Qt::AlignCenter, time);
299     painter->restore();
300 }
301
302 void PlaylistItemDelegate::paintDownloadInfo(QPainter *painter,
303                                              const QStyleOptionViewItem &option,
304                                              const QModelIndex &index) const {
305     // get the video metadata
306     const DownloadItemPointer downloadItemPointer =
307             index.data(DownloadItemRole).value<DownloadItemPointer>();
308     const DownloadItem *downloadItem = downloadItemPointer.data();
309
310     painter->save();
311
312     const QRect line(0, 0, option.rect.width() / 2, option.rect.height());
313
314     painter->translate(option.rect.topLeft());
315     painter->translate(line.width(), 0);
316
317     QString message;
318     DownloadItemStatus status = downloadItem->status();
319
320     if (status == Downloading) {
321         QString downloaded = DownloadItem::formattedFilesize(downloadItem->bytesReceived());
322         QString total = DownloadItem::formattedFilesize(downloadItem->bytesTotal());
323         QString speed = DownloadItem::formattedSpeed(downloadItem->currentSpeed());
324         QString eta = DownloadItem::formattedTime(downloadItem->remainingTime());
325
326         message = tr("%1 of %2 (%3) — %4").arg(downloaded, total, speed, eta);
327     } else if (status == Starting) {
328         message = tr("Preparing");
329     } else if (status == Failed) {
330         message = tr("Failed") + " — " + downloadItem->errorMessage();
331     } else if (status == Finished) {
332         message = tr("Completed");
333     } else if (status == Idle) {
334         message = tr("Stopped");
335     }
336
337     // progressBar->setPalette(option.palette);
338     if (status == Finished) {
339         progressBar->setValue(100);
340         progressBar->setEnabled(true);
341     } else if (status == Downloading) {
342         progressBar->setValue(downloadItem->currentPercent());
343         progressBar->setEnabled(true);
344     } else {
345         progressBar->setValue(0);
346         progressBar->setEnabled(false);
347     }
348
349     int progressBarWidth = line.width() - padding * 4 - 16;
350     progressBar->setMaximumWidth(progressBarWidth);
351     progressBar->setMinimumWidth(progressBarWidth);
352     painter->save();
353     painter->translate(padding, padding);
354     progressBar->render(painter);
355     painter->restore();
356
357     bool downloadButtonHovered = false;
358     bool downloadButtonPressed = false;
359     const bool isHovered = index.data(HoveredItemRole).toBool();
360     if (isHovered) {
361         downloadButtonHovered = index.data(DownloadButtonHoveredRole).toBool();
362         downloadButtonPressed = index.data(DownloadButtonPressedRole).toBool();
363     }
364     QIcon::Mode iconMode;
365     if (downloadButtonPressed)
366         iconMode = QIcon::Selected;
367     else if (downloadButtonHovered)
368         iconMode = QIcon::Active;
369     else
370         iconMode = QIcon::Normal;
371
372     if (status != Finished && status != Failed && status != Idle) {
373         if (downloadButtonHovered) message = tr("Stop downloading");
374         painter->save();
375         QIcon closeIcon = IconUtils::icon("window-close");
376         painter->drawPixmap(downloadButtonRect(line), closeIcon.pixmap(16, 16, iconMode));
377         painter->restore();
378     }
379
380     else if (status == Finished) {
381         if (downloadButtonHovered)
382 #ifdef APP_MAC
383             message = tr("Show in %1").arg("Finder");
384 #else
385             message = tr("Open parent folder");
386 #endif
387         painter->save();
388         QIcon searchIcon = IconUtils::icon("system-search");
389         painter->drawPixmap(downloadButtonRect(line), searchIcon.pixmap(16, 16, iconMode));
390         painter->restore();
391     }
392
393     else if (status == Failed || status == Idle) {
394         if (downloadButtonHovered) message = tr("Restart downloading");
395         painter->save();
396         QIcon searchIcon = IconUtils::icon("view-refresh");
397         painter->drawPixmap(downloadButtonRect(line), searchIcon.pixmap(16, 16, iconMode));
398         painter->restore();
399     }
400
401     QRect textBox = line.adjusted(padding, padding * 2 + progressBar->sizeHint().height(),
402                                   -2 * padding, -padding);
403     textBox = painter->boundingRect(textBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap,
404                                     message);
405     painter->drawText(textBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, message);
406
407     painter->restore();
408 }
409
410 QRect PlaylistItemDelegate::downloadButtonRect(const QRect &line) const {
411     return QRect(line.width() - padding * 2 - 16,
412                  padding + progressBar->sizeHint().height() / 2 - 8, 16, 16);
413 }
414
415 QRect PlaylistItemDelegate::authorRect(const QModelIndex &index) const {
416     return authorRects.value(index.row());
417 }