3 This file is part of Minitube.
4 Copyright 2009, Flavio Tordini <flavio.tordini@gmail.com>
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.
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.
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/>.
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"
29 #include "videodefinition.h"
31 const int PlaylistItemDelegate::thumbHeight = 90;
32 const int PlaylistItemDelegate::thumbWidth = 160;
33 const int PlaylistItemDelegate::padding = 8;
37 bool drawElidedText(QPainter *painter, const QRect &textBox, const int flags, const QString &text) {
39 painter->fontMetrics().elidedText(text, Qt::ElideRight, textBox.width(), flags);
40 painter->drawText(textBox, 0, elidedText);
41 return elidedText.length() < text.length();
45 PlaylistItemDelegate::PlaylistItemDelegate(QObject *parent, bool downloadInfo)
46 : QStyledItemDelegate(parent), downloadInfo(downloadInfo), progressBar(0) {
47 listView = qobject_cast<PlaylistView *>(parent);
49 smallerBoldFont = FontUtils::smallBold();
50 smallerFont = FontUtils::small();
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);
63 PlaylistItemDelegate::~PlaylistItemDelegate() {
64 if (progressBar) delete progressBar;
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);
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);
79 const int hPadding = padding * 6;
80 const int vPadding = padding * 2;
83 polygon << QPoint(hPadding, vPadding) << QPoint(thumbWidth - hPadding, thumbHeight / 2)
84 << QPoint(hPadding, thumbHeight - vPadding);
85 painter.setBrush(Qt::white);
87 pen.setColor(Qt::white);
88 pen.setWidth(padding);
89 pen.setJoinStyle(Qt::RoundJoin);
90 pen.setCapStyle(Qt::RoundCap);
92 painter.drawPolygon(polygon);
95 QPainter painter2(&playIcon);
96 painter2.setOpacity(.75);
97 painter2.drawPixmap(0, 0, tempPixmap);
100 QSize PlaylistItemDelegate::sizeHint(const QStyleOptionViewItem & /*option*/,
101 const QModelIndex & /*index*/) const {
102 return QSize(thumbWidth, thumbHeight + 1);
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);
113 opt.widget->style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget);
114 paintBody(painter, opt, index);
116 QStyledItemDelegate::paint(painter, option, index);
119 void PlaylistItemDelegate::paintBody(QPainter *painter,
120 const QStyleOptionViewItem &option,
121 const QModelIndex &index) const {
123 painter->translate(option.rect.topLeft());
125 QRect line(0, 0, option.rect.width(), option.rect.height());
126 if (downloadInfo) line.setWidth(line.width() / 2);
128 const bool isActive = index.data(ActiveTrackRole).toBool();
129 const bool isSelected = option.state & QStyle::State_Selected;
131 // get the video metadata
132 const Video *video = index.data(VideoRole).value<VideoPointer>().data();
134 // draw the "current track" highlight underneath the text
135 if (isActive && !isSelected) paintActiveOverlay(painter, option, line);
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);
144 painter->drawPixmap(0, 0, video->getThumbnail());
146 const bool thumbsOnly = line.width() <= thumbWidth + 60;
147 const bool isHovered = index.data(HoveredItemRole).toBool();
149 // play icon overlayed on the thumb
150 if (isActive && (!isHovered && thumbsOnly)) painter->drawPixmap(0, 0, playIcon);
153 if (video->getDuration() > 0) drawTime(painter, video->getFormattedDuration(), line);
158 painter->setPen(QPen(option.palette.highlightedText(), 0));
160 painter->setPen(QPen(option.palette.text(), 0));
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);
176 elidedTitle = title.trimmed() + QStringLiteral("…");
177 textBox = painter->boundingRect(textBox, titleFlags, elidedTitle);
179 painter->drawText(textBox, titleFlags, elidedTitle);
181 painter->setFont(smallerFont);
182 painter->setOpacity(.5);
183 QFontMetrics fontMetrics = painter->fontMetrics();
184 static const int flags = Qt::AlignLeft | Qt::AlignTop;
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);
196 if (!listView || listView->isClickableAuthors()) {
197 bool authorHovered = isHovered && index.data(AuthorHoveredRole).toBool();
200 painter->setFont(smallerBoldFont);
203 painter->setPen(QPen(option.palette.brush(QPalette::Highlight), 0));
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);
214 painter->drawText(textBox, flags, author);
220 if (video->getViewCount() > 0) {
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);
229 textBox = QRect(textPoint, textSize);
230 if (textBox.bottom() <= line.height()) {
231 painter->drawText(textBox, flags, viewCount);
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);
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);
261 elidedTitle = title.trimmed() + QStringLiteral("…");
262 textBox = painter->boundingRect(textBox, titleFlags, elidedTitle);
264 painter->fillRect(QRect(0, 0, thumbWidth, textBox.height() + padding * 2),
265 QColor(0, 0, 0, 128));
266 painter->drawText(textBox, titleFlags, elidedTitle);
272 if (downloadInfo) paintDownloadInfo(painter, option, index);
275 void PlaylistItemDelegate::paintActiveOverlay(QPainter *painter,
276 const QStyleOptionViewItem &option,
277 const QRect &line) const {
279 painter->setOpacity(.2);
280 painter->fillRect(line, option.palette.highlight());
284 void PlaylistItemDelegate::drawTime(QPainter *painter,
286 const QRect &line) const {
287 static const int timePadding = 4;
288 QRect textBox = painter->boundingRect(line, Qt::AlignLeft | Qt::AlignTop, time);
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());
295 painter->setPen(Qt::NoPen);
296 painter->setBrush(Qt::black);
297 painter->setOpacity(.5);
298 painter->drawRect(textBox);
302 painter->setPen(Qt::white);
303 painter->drawText(textBox, Qt::AlignCenter, time);
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();
317 const QRect line(0, 0, option.rect.width() / 2, option.rect.height());
319 painter->translate(option.rect.topLeft());
320 painter->translate(line.width(), 0);
323 DownloadItemStatus status = downloadItem->status();
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());
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");
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);
350 progressBar->setValue(0);
351 progressBar->setEnabled(false);
354 int progressBarWidth = line.width() - padding * 4 - 16;
355 progressBar->setMaximumWidth(progressBarWidth);
356 progressBar->setMinimumWidth(progressBarWidth);
358 painter->translate(padding, padding);
359 progressBar->render(painter);
362 bool downloadButtonHovered = false;
363 bool downloadButtonPressed = false;
364 const bool isHovered = index.data(HoveredItemRole).toBool();
366 downloadButtonHovered = index.data(DownloadButtonHoveredRole).toBool();
367 downloadButtonPressed = index.data(DownloadButtonPressedRole).toBool();
369 QIcon::Mode iconMode;
370 if (downloadButtonPressed)
371 iconMode = QIcon::Selected;
372 else if (downloadButtonHovered)
373 iconMode = QIcon::Active;
375 iconMode = QIcon::Normal;
377 if (status != Finished && status != Failed && status != Idle) {
378 if (downloadButtonHovered) message = tr("Stop downloading");
380 QIcon closeIcon = IconUtils::icon("window-close");
381 painter->drawPixmap(downloadButtonRect(line), closeIcon.pixmap(16, 16, iconMode));
385 else if (status == Finished) {
386 if (downloadButtonHovered)
388 message = tr("Show in %1").arg("Finder");
390 message = tr("Open parent folder");
393 QIcon searchIcon = IconUtils::icon("system-search");
394 painter->drawPixmap(downloadButtonRect(line), searchIcon.pixmap(16, 16, iconMode));
398 else if (status == Failed || status == Idle) {
399 if (downloadButtonHovered) message = tr("Restart downloading");
401 QIcon searchIcon = IconUtils::icon("view-refresh");
402 painter->drawPixmap(downloadButtonRect(line), searchIcon.pixmap(16, 16, iconMode));
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,
410 painter->drawText(textBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, message);
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);
420 QRect PlaylistItemDelegate::authorRect(const QModelIndex &index) const {
421 return authorRects.value(index.row());