1 /* $BEGIN_LICENSE
2
3 This file is part of Musique.
4 Copyright 2013, Flavio Tordini <flavio.tordini@gmail.com>
5
6 Musique 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 Musique 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 Musique. If not, see <http://www.gnu.org/licenses/>.
18
19 $END_LICENSE */
20
21 #include "playlistitemdelegate.h"
22 #include "datautils.h"
23 #include "iconutils.h"
24 #include "model/album.h"
25 #include "model/artist.h"
26 #include "model/track.h"
27 #include "playlistmodel.h"
28
29 const int PlaylistItemDelegate::PADDING = 10;
30 int PlaylistItemDelegate::ITEM_HEIGHT = 0;
31
32 namespace {
33
drawElidedText(QPainter * painter,const QRect & textBox,const int flags,const QString & text)34 void drawElidedText(QPainter *painter, const QRect &textBox, const int flags, const QString &text) {
35 QString elidedText =
36 QFontMetrics(painter->font()).elidedText(text, Qt::ElideRight, textBox.width(), flags);
37 painter->drawText(textBox, 0, elidedText);
38 }
39
40 } // namespace
41
PlaylistItemDelegate(QObject * parent)42 PlaylistItemDelegate::PlaylistItemDelegate(QObject *parent) : QStyledItemDelegate(parent) {}
43
sizeHint(const QStyleOptionViewItem & option,const QModelIndex & index) const44 QSize PlaylistItemDelegate::sizeHint(const QStyleOptionViewItem &option,
45 const QModelIndex &index) const {
46 // determine item height based on font metrics
47 if (ITEM_HEIGHT == 0) {
48 ITEM_HEIGHT = option.fontMetrics.height() * 1.8;
49 }
50
51 static const QSize headerSize = QSize(ITEM_HEIGHT * 2, ITEM_HEIGHT * 2);
52
53 QModelIndex previousIndex = index.sibling(index.row() - 1, index.column());
54 if (previousIndex.isValid()) {
55 const TrackPointer previousTrackPointer =
56 previousIndex.data(Playlist::DataObjectRole).value<TrackPointer>();
57 Track *previousTrack = previousTrackPointer.data();
58 if (previousTrack) {
59 const TrackPointer trackPointer =
60 index.data(Playlist::DataObjectRole).value<TrackPointer>();
61 Track *track = trackPointer.data();
62 Album *previousAlbum = previousTrack->getAlbum();
63 if (!previousAlbum && previousTrack->getArtist() != track->getArtist()) {
64 return headerSize;
65 }
66 if (previousAlbum != track->getAlbum()) {
67 return headerSize;
68 }
69 }
70 } else {
71 return headerSize;
72 }
73
74 return QSize(ITEM_HEIGHT, ITEM_HEIGHT);
75 }
76
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const77 void PlaylistItemDelegate::paint(QPainter *painter,
78 const QStyleOptionViewItem &option,
79 const QModelIndex &index) const {
80 paintTrack(painter, option, index);
81 }
82
getPlayIcon(const QColor & color,const QStyleOptionViewItem & option) const83 QPixmap PlaylistItemDelegate::getPlayIcon(const QColor &color,
84 const QStyleOptionViewItem &option) const {
85 static QHash<QString, QPixmap> cache;
86 const QString key = color.name();
87 if (cache.contains(key)) return cache.value(key);
88 const int iconSize = ITEM_HEIGHT / 2;
89 QIcon icon = IconUtils::tintedIcon("media-playback-start", color, QSize(32, 32));
90 QPixmap pixmap = icon.pixmap(iconSize, iconSize);
91 cache.insert(key, pixmap);
92 return pixmap;
93 }
94
paintTrack(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const95 void PlaylistItemDelegate::paintTrack(QPainter *painter,
96 const QStyleOptionViewItem &option,
97 const QModelIndex &index) const {
98 Track *track = index.data(Playlist::DataObjectRole).value<TrackPointer>().data();
99
100 const bool isActive = index.data(Playlist::ActiveItemRole).toBool();
101 // const bool isHovered = index.data(Playlist::HoveredItemRole).toBool();
102 const bool isSelected = option.state & QStyle::State_Selected;
103
104 if (isSelected)
105 QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter);
106
107 painter->save();
108
109 painter->translate(option.rect.topLeft());
110 QRect line(0, 0, option.rect.width(), option.rect.height());
111
112 // text color
113 if (isSelected)
114 painter->setPen(QPen(option.palette.brush(QPalette::HighlightedText), 0));
115 else
116 painter->setPen(QPen(option.palette.brush(QPalette::Text), 0));
117
118 if (line.height() > ITEM_HEIGHT) {
119 // qDebug() << "header at index" << index.row();
120 line.setHeight(ITEM_HEIGHT);
121 paintAlbumHeader(painter, option, line, track);
122
123 // now modify our rect and painter
124 // to make them similar to "headerless" items
125 line.moveBottom(ITEM_HEIGHT);
126 painter->translate(0, ITEM_HEIGHT);
127 }
128
129 if (isActive) {
130 // if (!isSelected) paintActiveOverlay(painter, option, line);
131 static const QPixmap p = [] {
132 QIcon icon = IconUtils::icon("media-playback-start");
133 QPixmap p = icon.pixmap(16, 16);
134 IconUtils::tint(p, qApp->palette().highlight().color());
135 return p;
136 }();
137 painter->save();
138 if (isSelected) painter->setCompositionMode(QPainter::CompositionMode_Plus);
139 painter->drawPixmap(PADDING, (ITEM_HEIGHT - (p.height() / p.devicePixelRatio())) / 2, p);
140 painter->restore();
141 } else {
142 paintTrackNumber(painter, option, line, track);
143 }
144
145 // qDebug() << "painting" << track;
146 paintTrackTitle(painter, option, line, track, isActive);
147 paintTrackLength(painter, option, line, track);
148
149 painter->restore();
150 }
151
paintAlbumHeader(QPainter * painter,const QStyleOptionViewItem & option,const QRect & line,Track * track) const152 void PlaylistItemDelegate::paintAlbumHeader(QPainter *painter,
153 const QStyleOptionViewItem &option,
154 const QRect &line,
155 Track *track) const {
156 QString headerTitle;
157 Album *album = track->getAlbum();
158 if (album) headerTitle = album->getTitle();
159 Artist *artist = track->getArtist();
160 if (artist) {
161 if (!headerTitle.isEmpty()) headerTitle += QLatin1String(" - ");
162 headerTitle += artist->getName();
163 }
164
165 painter->save();
166
167 const int h = line.height();
168
169 const QColor &highlightColor = option.palette.highlight().color();
170 const int hue = highlightColor.hue();
171 const int saturation = highlightColor.saturation() * .2;
172 int value = option.palette.window().color().value();
173 value = value > 128 ? value * .75 : value * 2;
174 const int value2 = value - 16;
175 const QColor topColor = QColor::fromHsv(hue, saturation, value);
176 const QColor bottomColor = QColor::fromHsv(hue, saturation, value2);
177 QLinearGradient linearGradient(0, 0, 0, h);
178 linearGradient.setColorAt(0.0, topColor);
179 linearGradient.setColorAt(1.0, bottomColor);
180 painter->fillRect(line, linearGradient);
181
182 const qreal pixelRatio = painter->device()->devicePixelRatioF();
183
184 if (album) {
185 QPixmap p = album->getPhoto();
186 if (!p.isNull()) {
187 const int ph = h * pixelRatio;
188 p = p.scaled(ph, ph, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
189 p.setDevicePixelRatio(pixelRatio);
190 painter->drawPixmap(0, 0, p);
191 }
192 } else if (artist) {
193 QPixmap p = artist->getPhoto();
194 if (!p.isNull()) {
195 const int ph = h * pixelRatio;
196 p = p.scaled(ph, ph, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
197 p.setDevicePixelRatio(pixelRatio);
198 painter->drawPixmap(0, 0, p);
199 }
200 }
201
202 // album length
203 /*
204 if (album) {
205 // TODO this is the album duration, but not necessarily what we have in the playlist
206 QString albumLength = album->formattedDuration();
207 QFont normalFont = painter->font();
208 normalFont.setBold(false);
209 // normalFont.setPointSize(boldFont.pointSize()*.9);
210 painter->setFont(normalFont);
211
212 painter->setPen(Qt::white);
213 painter->drawText(line.translated(-PADDING, 0), Qt::AlignRight | Qt::AlignVCenter,
214 albumLength);
215 }
216 */
217
218 const QFontMetrics fontMetrics = QFontMetrics(painter->font());
219 const int textLeft = h + fontMetrics.height() / 2;
220
221 // text size
222 QSize trackStringSize(fontMetrics.size(Qt::TextSingleLine, headerTitle));
223
224 int width = trackStringSize.width();
225 const int maxWidth = line.width() - textLeft;
226 if (width > maxWidth) width = maxWidth;
227
228 QPoint textLoc(textLeft, 0);
229 QRect trackTextBox(textLoc.x(), textLoc.y(), width, line.height());
230 headerTitle = fontMetrics.elidedText(headerTitle, Qt::ElideRight, trackTextBox.width());
231
232 // text
233 painter->setPen(bottomColor.value() < 200 ? Qt::white : Qt::black);
234 painter->drawText(trackTextBox, Qt::AlignLeft | Qt::AlignVCenter, headerTitle);
235
236 painter->restore();
237 }
238
paintTrackNumber(QPainter * painter,const QStyleOptionViewItem & option,const QRect & line,Track * track) const239 void PlaylistItemDelegate::paintTrackNumber(QPainter *painter,
240 const QStyleOptionViewItem &option,
241 const QRect &line,
242 Track *track) const {
243 const int trackNumber = track->getNumber();
244 if (trackNumber < 1) return;
245
246 painter->save();
247
248 const qreal pixelRatio = painter->device()->devicePixelRatioF();
249
250 // track number
251 QFont font = painter->font();
252 font.setPointSize(font.pointSize() - pixelRatio);
253 painter->setFont(font);
254 QString trackString = QString("%1").arg(trackNumber, 2, 10, QChar('0'));
255
256 if (track->getDiskCount() > 1) {
257 trackString = QString::number(track->getDiskNumber()) + '.' + trackString;
258 }
259
260 QRect trackTextBox(0, 0, line.height(), line.height());
261
262 painter->setOpacity(.5);
263 painter->drawText(trackTextBox, Qt::AlignCenter, trackString);
264 painter->restore();
265 }
266
paintTrackTitle(QPainter * painter,const QStyleOptionViewItem & option,const QRect & line,Track * track,bool isActive) const267 void PlaylistItemDelegate::paintTrackTitle(QPainter *painter,
268 const QStyleOptionViewItem &option,
269 const QRect &line,
270 Track *track,
271 bool isActive) const {
272 Q_UNUSED(isActive);
273 Q_UNUSED(option);
274
275 painter->save();
276
277 QFontMetrics fontMetrics = QFontMetrics(painter->font());
278
279 QString trackTitle = track->getTitle();
280
281 QSize trackStringSize(fontMetrics.size(Qt::TextSingleLine, trackTitle));
282 const int textLeft = line.height() + fontMetrics.height() / 2;
283 QPoint textLoc(textLeft, 0);
284
285 int width = trackStringSize.width();
286 const int maxX = line.width() - textLeft;
287
288 QRect trackTextBox(textLoc.x(), textLoc.y(), width, line.height());
289 if (trackTextBox.right() > maxX) trackTextBox.setRight(maxX);
290
291 trackTitle = fontMetrics.elidedText(trackTitle, Qt::ElideRight, trackTextBox.width());
292
293 painter->drawText(trackTextBox, Qt::AlignLeft | Qt::AlignVCenter, trackTitle);
294
295 // track artist
296 if (trackTextBox.right() < maxX) {
297 Album *album = track->getAlbum();
298 if (album && album->getArtist()) {
299 Artist *albumArtist = album->getArtist();
300 Artist *trackArtist = track->getArtist();
301 if (albumArtist && trackArtist && albumArtist->getId() != trackArtist->getId()) {
302 static const QString by = "—";
303 const int x = trackTextBox.right();
304 QRect textBox(x, line.height(), 0, 0);
305 const int flags = Qt::AlignVCenter | Qt::AlignLeft;
306 QString artistName = track->getArtist()->getName();
307
308 painter->save();
309
310 textBox = painter->boundingRect(
311 line.adjusted(textBox.x() + textBox.width() + PADDING, 0, 0, 0), flags, by);
312 if (textBox.right() > maxX) textBox.setRight(maxX);
313 drawElidedText(painter, textBox, flags, by);
314
315 textBox = painter->boundingRect(
316 line.adjusted(textBox.x() + textBox.width() + PADDING, 0, 0, 0), flags,
317 artistName);
318 if (textBox.right() > maxX) textBox.setRight(maxX);
319 drawElidedText(painter, textBox, flags, artistName);
320 painter->restore();
321 }
322 }
323 }
324
325 painter->restore();
326 }
327
paintTrackLength(QPainter * painter,const QStyleOptionViewItem & option,const QRect & line,Track * track) const328 void PlaylistItemDelegate::paintTrackLength(QPainter *painter,
329 const QStyleOptionViewItem &option,
330 const QRect &line,
331 Track *track) const {
332 const QString trackLength = DataUtils::formatDuration(track->getLength());
333
334 // QSize trackStringSize(QFontMetrics(painter->font()).size(Qt::TextSingleLine, trackLength));
335 QPoint textLoc(PADDING * 10, 0);
336 QRect trackTextBox(textLoc.x(), textLoc.y(), line.width() - textLoc.x() - PADDING,
337 line.height());
338
339 const bool isSelected = option.state & QStyle::State_Selected;
340 painter->save();
341 const qreal pixelRatio = painter->device()->devicePixelRatioF();
342 QFont font = painter->font();
343 font.setPointSize(font.pointSize() - pixelRatio);
344 painter->setFont(font);
345 if (isSelected)
346 painter->setPen(option.palette.highlightedText().color());
347 else
348 painter->setOpacity(.5);
349 painter->drawText(trackTextBox, Qt::AlignRight | Qt::AlignVCenter, trackLength);
350 painter->restore();
351 }
352
paintActiveOverlay(QPainter * painter,const QStyleOptionViewItem & option,const QRect & line) const353 void PlaylistItemDelegate::paintActiveOverlay(QPainter *painter,
354 const QStyleOptionViewItem &option,
355 const QRect &line) const {
356 QColor highlightColor = option.palette.color(QPalette::Highlight);
357 QColor backgroundColor = option.palette.color(QPalette::Base);
358 const float animation = 0.25;
359 const int gradientRange = 16;
360
361 QColor color2 = QColor::fromHsv(highlightColor.hue(),
362 (int)(backgroundColor.saturation() * (1.0f - animation) +
363 highlightColor.saturation() * animation),
364 (int)(backgroundColor.value() * (1.0f - animation) +
365 highlightColor.value() * animation));
366 QColor color1 = QColor::fromHsv(color2.hue(), qMax(color2.saturation() - gradientRange, 0),
367 qMin(color2.value() + gradientRange, 255));
368
369 painter->save();
370 painter->setPen(Qt::NoPen);
371 QLinearGradient linearGradient(0, 0, 0, line.height());
372 linearGradient.setColorAt(0.0, color1);
373 linearGradient.setColorAt(1.0, color2);
374 painter->fillRect(line, linearGradient);
375 painter->restore();
376 }
377