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 "playlistmodel.h"
23 #include "fontutils.h"
24 #include "downloaditem.h"
25 #include "iconutils.h"
26 #include "videodefinition.h"
28 #include "datautils.h"
30 const int PlaylistItemDelegate::THUMB_HEIGHT = 90;
31 const int PlaylistItemDelegate::THUMB_WIDTH = 160;
32 const int PlaylistItemDelegate::PADDING = 10;
36 void drawElidedText(QPainter *painter, const QRect &textBox, const int flags, const QString &text) {
37 QString elidedText = QFontMetrics(painter->font()).elidedText(text, Qt::ElideRight, textBox.width(), flags);
38 painter->drawText(textBox, 0, elidedText);
43 PlaylistItemDelegate::PlaylistItemDelegate(QObject* parent, bool downloadInfo)
44 : QStyledItemDelegate(parent),
45 downloadInfo(downloadInfo),
48 boldFont.setBold(true);
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);
59 } else createPlayIcon();
62 PlaylistItemDelegate::~PlaylistItemDelegate() {
63 if (progressBar) delete progressBar;
66 void PlaylistItemDelegate::createPlayIcon() {
67 qreal maxRatio = IconUtils::maxSupportedPixelRatio();
68 playIcon = QPixmap(THUMB_WIDTH * maxRatio, THUMB_HEIGHT * maxRatio);
69 playIcon.setDevicePixelRatio(maxRatio);
70 playIcon.fill(Qt::transparent);
72 QPixmap tempPixmap(THUMB_WIDTH * maxRatio, THUMB_HEIGHT * maxRatio);
73 tempPixmap.setDevicePixelRatio(maxRatio);
74 tempPixmap.fill(Qt::transparent);
75 QPainter painter(&tempPixmap);
76 painter.setRenderHints(QPainter::Antialiasing, true);
78 const int hPadding = PADDING*6;
79 const int vPadding = PADDING*2;
82 polygon << QPoint(hPadding, vPadding)
83 << QPoint(THUMB_WIDTH-hPadding, THUMB_HEIGHT/2)
84 << QPoint(hPadding, THUMB_HEIGHT-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*/, const QModelIndex& /*index*/ ) const {
101 return QSize(THUMB_WIDTH, THUMB_HEIGHT + 1);
104 void PlaylistItemDelegate::paint( QPainter* painter,
105 const QStyleOptionViewItem& option, const QModelIndex& index ) const {
107 int itemType = index.data(ItemTypeRole).toInt();
108 if (itemType == ItemTypeVideo) {
109 QStyleOptionViewItemV4 opt = QStyleOptionViewItemV4(option);
110 initStyleOption(&opt, index);
112 opt.widget->style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget);
113 paintBody(painter, opt, index);
115 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)
136 paintActiveOverlay(painter, option, line);
139 painter->setPen(option.palette.color(QPalette::Midlight));
140 painter->drawLine(THUMB_WIDTH, THUMB_HEIGHT, option.rect.width(), THUMB_HEIGHT);
141 if (!video->thumbnail().isNull())
142 painter->setPen(Qt::black);
143 painter->drawLine(0, THUMB_HEIGHT, THUMB_WIDTH-1, THUMB_HEIGHT);
146 painter->drawPixmap(0, 0, video->thumbnail());
148 // play icon overlayed on the thumb
150 painter->drawPixmap(0, 0, playIcon);
153 if (video->duration() > 0)
154 drawTime(painter, video->formattedDuration(), line);
156 if (line.width() > THUMB_WIDTH + 60) {
158 // if (isActive) painter->setFont(boldFont);
162 painter->setPen(QPen(option.palette.highlightedText(), 0));
164 painter->setPen(QPen(option.palette.text(), 0));
167 QString videoTitle = video->title();
168 QString v = videoTitle;
169 const int flags = Qt::AlignTop | Qt::TextWordWrap;
170 QRect textBox = line.adjusted(PADDING+THUMB_WIDTH, PADDING, 0, 0);
171 textBox = painter->boundingRect(textBox, flags, v);
172 while (textBox.height() > 55 && v.length() > 10) {
173 videoTitle.truncate(videoTitle.length() - 1);
175 v = v.trimmed().append("...");
176 textBox = painter->boundingRect(textBox, flags, v);
178 painter->drawText(textBox, flags, v);
180 painter->setFont(smallerFont);
183 QString publishedString = DataUtils::formatDateTime(video->published());
184 QSize stringSize(QFontMetrics(painter->font()).size( Qt::TextSingleLine, publishedString ) );
185 QPoint textLoc(PADDING+THUMB_WIDTH, PADDING*2 + textBox.height());
186 QRect publishedTextBox(textLoc , stringSize);
187 painter->drawText(publishedTextBox, Qt::AlignLeft | Qt::AlignTop, publishedString);
190 bool authorHovered = false;
191 bool authorPressed = false;
192 const bool isHovered = index.data(HoveredItemRole).toBool();
194 authorHovered = index.data(AuthorHoveredRole).toBool();
195 authorPressed = index.data(AuthorPressedRole).toBool();
199 painter->setFont(smallerBoldFont);
202 painter->setPen(QPen(option.palette.brush(QPalette::Highlight), 0));
204 painter->setOpacity(.5);
206 const QString &authorString = video->channelTitle();
207 textLoc.setX(textLoc.x() + stringSize.width() + PADDING);
208 stringSize = QSize(QFontMetrics(painter->font()).size( Qt::TextSingleLine, authorString ) );
209 QRect authorTextBox(textLoc , stringSize);
210 authorRects.insert(index.row(), authorTextBox);
211 if (authorTextBox.right() > line.width()) authorTextBox.setRight(line.width());
212 drawElidedText(painter, authorTextBox, Qt::AlignLeft | Qt::AlignTop, authorString);
216 if (video->viewCount() >= 0) {
218 QString viewCountString = tr("%1 views").arg(locale.toString(video->viewCount()));
219 textLoc.setX(textLoc.x() + stringSize.width() + PADDING);
220 stringSize = QSize(QFontMetrics(painter->font()).size( Qt::TextSingleLine, viewCountString ) );
221 QRect viewCountTextBox(textLoc , stringSize);
222 if (viewCountTextBox.right() > line.width()) viewCountTextBox.setRight(line.width());
223 drawElidedText(painter, viewCountTextBox, Qt::AlignLeft | Qt::AlignBottom, viewCountString);
227 const QString definitionString = VideoDefinition::getDefinitionFor(video->getDefinitionCode()).getName();
228 textLoc.setX(textLoc.x() + stringSize.width() + PADDING);
229 stringSize = QSize(QFontMetrics(painter->font()).size( Qt::TextSingleLine, definitionString ) );
230 QRect viewCountTextBox(textLoc , stringSize);
231 painter->drawText(viewCountTextBox, Qt::AlignLeft | Qt::AlignBottom, definitionString);
236 const bool isHovered = index.data(HoveredItemRole).toBool();
237 if (!isActive && isHovered) {
238 painter->setFont(smallerFont);
239 painter->setPen(Qt::white);
240 QString videoTitle = video->title();
241 QString v = videoTitle;
242 const int flags = Qt::AlignTop | Qt::TextWordWrap;
243 QRect textBox(PADDING, PADDING, THUMB_WIDTH - PADDING*2, THUMB_HEIGHT - PADDING*2);
244 textBox = painter->boundingRect(textBox, flags, v);
245 while (textBox.height() > THUMB_HEIGHT && v.length() > 10) {
246 videoTitle.truncate(videoTitle.length() - 1);
248 v = v.trimmed().append("...");
249 textBox = painter->boundingRect(textBox, flags, v);
251 painter->fillRect(QRect(0, 0, THUMB_WIDTH, textBox.height() + PADDING*2), QColor(0, 0, 0, 128));
252 painter->drawText(textBox, flags, v);
259 if (downloadInfo) paintDownloadInfo(painter, option, index);
263 void PlaylistItemDelegate::paintActiveOverlay(QPainter *painter, const QStyleOptionViewItem& option, const QRect &line) const {
265 painter->setOpacity(.1);
266 painter->fillRect(line, option.palette.highlight());
270 void PlaylistItemDelegate::drawTime(QPainter *painter, const QString &time, const QRect &line) const {
271 static const int timePadding = 4;
272 QRect textBox = painter->boundingRect(line, Qt::AlignLeft | Qt::AlignTop, time);
274 textBox.adjust(0, 0, timePadding, 0);
275 // move to bottom right corner of the thumb
276 textBox.translate(THUMB_WIDTH - textBox.width(), THUMB_HEIGHT - textBox.height());
279 painter->setPen(Qt::NoPen);
280 painter->setBrush(Qt::black);
281 painter->setOpacity(.5);
282 painter->drawRect(textBox);
286 painter->setPen(Qt::white);
287 painter->drawText(textBox, Qt::AlignCenter, time);
291 void PlaylistItemDelegate::paintDownloadInfo( QPainter* painter,
292 const QStyleOptionViewItem& option,
293 const QModelIndex& index ) const {
295 // get the video metadata
296 const DownloadItemPointer downloadItemPointer = index.data(DownloadItemRole).value<DownloadItemPointer>();
297 const DownloadItem *downloadItem = downloadItemPointer.data();
301 const QRect line(0, 0, option.rect.width() / 2, option.rect.height());
303 painter->translate(option.rect.topLeft());
304 painter->translate(line.width(), 0);
307 DownloadItemStatus status = downloadItem->status();
309 if (status == Downloading) {
310 QString downloaded = DownloadItem::formattedFilesize(downloadItem->bytesReceived());
311 QString total = DownloadItem::formattedFilesize(downloadItem->bytesTotal());
312 QString speed = DownloadItem::formattedSpeed(downloadItem->currentSpeed());
313 QString eta = DownloadItem::formattedTime(downloadItem->remainingTime());
315 message = tr("%1 of %2 (%3) — %4").arg(
321 } else if (status == Starting) {
322 message = tr("Preparing");
323 } else if (status == Failed) {
324 message = tr("Failed") + " — " + downloadItem->errorMessage();
325 } else if (status == Finished) {
326 message = tr("Completed");
327 } else if (status == Idle) {
328 message = tr("Stopped");
331 // progressBar->setPalette(option.palette);
332 if (status == Finished) {
333 progressBar->setValue(100);
334 progressBar->setEnabled(true);
335 } else if (status == Downloading) {
336 progressBar->setValue(downloadItem->currentPercent());
337 progressBar->setEnabled(true);
339 progressBar->setValue(0);
340 progressBar->setEnabled(false);
343 int progressBarWidth = line.width() - PADDING*4 - 16;
344 progressBar->setMaximumWidth(progressBarWidth);
345 progressBar->setMinimumWidth(progressBarWidth);
347 painter->translate(PADDING, PADDING);
348 progressBar->render(painter);
351 bool downloadButtonHovered = false;
352 bool downloadButtonPressed = false;
353 const bool isHovered = index.data(HoveredItemRole).toBool();
355 downloadButtonHovered = index.data(DownloadButtonHoveredRole).toBool();
356 downloadButtonPressed = index.data(DownloadButtonPressedRole).toBool();
358 QIcon::Mode iconMode;
359 if (downloadButtonPressed) iconMode = QIcon::Selected;
360 else if (downloadButtonHovered) iconMode = QIcon::Active;
361 else iconMode = QIcon::Normal;
363 if (status != Finished && status != Failed && status != Idle) {
364 if (downloadButtonHovered) message = tr("Stop downloading");
366 QIcon closeIcon = IconUtils::icon("window-close");
367 painter->drawPixmap(downloadButtonRect(line), closeIcon.pixmap(16, 16, iconMode));
371 else if (status == Finished) {
372 if (downloadButtonHovered)
374 message = tr("Show in %1").arg("Finder");
376 message = tr("Open parent folder");
379 QIcon searchIcon = IconUtils::icon("system-search");
380 painter->drawPixmap(downloadButtonRect(line), searchIcon.pixmap(16, 16, iconMode));
384 else if (status == Failed || status == Idle) {
385 if (downloadButtonHovered) message = tr("Restart downloading");
387 QIcon searchIcon = IconUtils::icon("view-refresh");
388 painter->drawPixmap(downloadButtonRect(line), searchIcon.pixmap(16, 16, iconMode));
392 QRect textBox = line.adjusted(PADDING, PADDING*2 + progressBar->sizeHint().height(), -2 * PADDING, -PADDING);
393 textBox = painter->boundingRect( textBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, message);
394 painter->drawText(textBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, message);
399 QRect PlaylistItemDelegate::downloadButtonRect(const QRect &line) const {
401 line.width() - PADDING*2 - 16,
402 PADDING + progressBar->sizeHint().height() / 2 - 8,
407 QRect PlaylistItemDelegate::authorRect(const QModelIndex& index) const {
408 return authorRects.value(index.row());