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