1 /* PlaylistItemDelegate.cpp */
2
3 /* Copyright (C) 2011-2020 Michael Lugmair (Lucio Carreras)
4 *
5 * This file is part of sayonara player
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "PlaylistModel.h"
22 #include "PlaylistDelegate.h"
23
24 #include "Utils/Settings/Settings.h"
25
26 #include "Gui/Utils/Widgets/RatingLabel.h"
27 #include "Gui/Utils/Style.h"
28 #include "Gui/Utils/GuiUtils.h"
29 #include "Gui/Utils/Icons.h"
30
31 #include <QPainter>
32 #include <QIcon>
33
34 using Gui::RatingEditor;
35 using Gui::RatingLabel;
36 using Playlist::Delegate;
37 using Playlist::Model;
38
39 namespace
40 {
isCurrentTrack(const QModelIndex & index)41 inline bool isCurrentTrack(const QModelIndex& index)
42 {
43 return index.data(Model::CurrentPlayingRole).toBool();
44 }
45
46 struct PlaylistStyleItem
47 {
48 QString text;
49 bool isBold {false};
50 bool isItalic {false};
51 };
52
parseEntryLookString(const QModelIndex & index)53 QList<PlaylistStyleItem> parseEntryLookString(const QModelIndex& index)
54 {
55 const auto entryLook = index.data(Model::EntryLookRole).toString();
56
57 QList<PlaylistStyleItem> ret;
58 PlaylistStyleItem currentItem;
59
60 for(const auto c : entryLook)
61 {
62 if((c != QChar(Model::StyleElement::Bold)) &&
63 (c != QChar(Model::StyleElement::Italic)))
64 {
65 currentItem.text += c;
66 continue;
67 }
68
69 ret << currentItem;
70 currentItem.text.clear();
71
72 if(c == QChar(Model::StyleElement::Bold))
73 {
74 currentItem.isBold = !currentItem.isBold;
75 }
76
77 else if(c == QChar(Model::StyleElement::Italic))
78 {
79 currentItem.isItalic = !currentItem.isItalic;
80 }
81 }
82
83 if(!currentItem.text.isEmpty())
84 {
85 ret << currentItem;
86 }
87
88 return ret;
89 }
90
isTrackNumberColumn(const QModelIndex & index)91 inline bool isTrackNumberColumn(const QModelIndex& index)
92 {
93 return (index.column() == Model::ColumnName::TrackNumber);
94 }
95
isDragIndex(const QModelIndex & index)96 inline bool isDragIndex(const QModelIndex& index)
97 {
98 return index.data(Model::DragIndexRole).toBool();
99 }
100
parseRating(const QModelIndex & index)101 inline Rating parseRating(const QModelIndex& index)
102 {
103 return index.data(Model::RatingRole).value<Rating>();
104 }
105
drawDragDropLine(QPainter * painter,const QRect & rect)106 void drawDragDropLine(QPainter* painter, const QRect& rect)
107 {
108 const auto y = rect.topLeft().y() + rect.height() - 1;
109 painter->drawLine(rect.x(), y, rect.x() + rect.width(), y);
110 }
111
getCurrentTrackColor(bool hasCustomColor,const QString & customColor,const QColor & standardColor)112 QColor getCurrentTrackColor(bool hasCustomColor, const QString& customColor, const QColor& standardColor)
113 {
114 if(hasCustomColor)
115 {
116 if(const auto color = QColor(customColor); color.isValid())
117 {
118 return color;
119 }
120 }
121
122 return standardColor;
123 }
124
getTextColor(const QStyleOptionViewItem & option,bool isCurrentTrack)125 QColor getTextColor(const QStyleOptionViewItem& option, bool isCurrentTrack)
126 {
127 const auto standardColor = option.palette.color(QPalette::Active, QPalette::WindowText);
128 if(isCurrentTrack)
129 {
130 if(Style::isDark())
131 {
132 return getCurrentTrackColor(GetSetting(Set::PL_CurrentTrackCustomColorDark),
133 GetSetting(Set::PL_CurrentTrackColorStringDark),
134 standardColor);
135 }
136
137 else
138 {
139 return getCurrentTrackColor(GetSetting(Set::PL_CurrentTrackCustomColorStandard),
140 GetSetting(Set::PL_CurrentTrackColorStringStandard),
141 standardColor);
142 }
143 }
144
145 return standardColor;
146 }
147
setTextColor(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index)148 void setTextColor(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index)
149 {
150 const auto isSelected = (option.state & QStyle::State_Selected);
151 const auto isEnabled = (option.state & QStyle::State_Enabled);
152 const auto isCurrentTrack = ::isCurrentTrack(index);
153
154 auto textColor = (isSelected)
155 ? option.palette.color(QPalette::Active, QPalette::HighlightedText)
156 : getTextColor(option, isCurrentTrack);
157
158 if(!isEnabled)
159 {
160 textColor.setAlpha(196);
161 }
162
163 auto pen = painter->pen();
164 pen.setColor(textColor);
165 painter->setPen(pen);
166 }
167
setFontStyle(QPainter * painter,bool isBold,bool isItalic)168 void setFontStyle(QPainter* painter, bool isBold, bool isItalic)
169 {
170 auto font = painter->font();
171 font.setBold(isBold);
172 font.setItalic(isItalic);
173 painter->setFont(font);
174 }
175
drawStyleItem(QPainter * painter,const PlaylistStyleItem & styleItem,bool alignTop,QRect & rect)176 void drawStyleItem(QPainter* painter, const PlaylistStyleItem& styleItem, bool alignTop, QRect& rect)
177 {
178 setFontStyle(painter, styleItem.isBold, styleItem.isItalic);
179
180 const auto alignment = (alignTop)
181 ? (Qt::AlignLeft | Qt::AlignTop)
182 : (Qt::AlignLeft | Qt::AlignVCenter);
183
184 const auto fontMetric = painter->fontMetrics();
185 painter->drawText(rect, alignment, fontMetric.elidedText(styleItem.text, Qt::ElideRight, rect.width()));
186
187 const auto horizontalOffset = Gui::Util::textWidth(fontMetric, styleItem.text);
188 rect.setWidth(rect.width() - horizontalOffset);
189 rect.translate(horizontalOffset, 0);
190 }
191
192 void
drawTrackMetadata(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index,bool alignTop)193 drawTrackMetadata(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, bool alignTop)
194 {
195 setTextColor(painter, option, index);
196
197 auto rect = (alignTop)
198 ? QRect(option.rect.left(), option.rect.y() + 1, option.rect.width(), option.rect.height() - 2)
199 : option.rect;
200
201 const auto styleItems = parseEntryLookString(index);
202 for(const auto& styleItem : styleItems)
203 {
204 drawStyleItem(painter, styleItem, alignTop, rect);
205 }
206 }
207
paintRatingLabel(QPainter * painter,const Rating rating,const QRect & rect)208 void paintRatingLabel(QPainter* painter, const Rating rating, const QRect& rect)
209 {
210 if(rating != Rating::Last)
211 {
212 painter->translate(0, 2);
213
214 auto ratingLabel = RatingLabel(nullptr, true);
215 ratingLabel.setRating(rating);
216 ratingLabel.setVerticalOffset(rect.height() / 2);
217 ratingLabel.paint(painter, rect);
218 }
219 }
220
paintPlayPixmap(QPainter * painter,const QRect & rect)221 void paintPlayPixmap(QPainter* painter, const QRect& rect)
222 {
223 constexpr const auto ScaleFactor = 10;
224
225 const auto icon = Gui::Icons::icon(Gui::Icons::Play);
226
227 const auto yTop = rect.y() + (rect.height() / ScaleFactor);
228 const auto height = (rect.height() * (ScaleFactor - 2)) / ScaleFactor;
229 const auto xLeft = rect.x() + (rect.width() - height) / 2;
230
231 painter->drawPixmap(xLeft, yTop, icon.pixmap(height, height).scaledToHeight(height));
232 }
233 }
234
Delegate(QObject * parent)235 Delegate::Delegate(QObject* parent) :
236 StyledItemDelegate(parent) {}
237
238 Delegate::~Delegate() = default;
239
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const240 void Delegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
241 {
242 if(!index.isValid())
243 {
244 return;
245 }
246
247 StyledItemDelegate::paint(painter, option, index);
248
249 if(isCurrentTrack(index) && isTrackNumberColumn(index))
250 {
251 paintPlayPixmap(painter, option.rect);
252 }
253
254 if(isDragIndex(index))
255 {
256 drawDragDropLine(painter, option.rect);
257 }
258
259 if(index.column() == Model::ColumnName::Description)
260 {
261 painter->save();
262 painter->translate(8, 0);
263
264 const auto showRating = GetSetting(Set::PL_ShowRating);
265 drawTrackMetadata(painter, option, index, showRating);
266
267 if(showRating)
268 {
269 const auto rating = parseRating(index);
270 paintRatingLabel(painter, rating, option.rect);
271 }
272
273 painter->restore();
274 }
275 }
276
createEditor(QWidget * parent,const QStyleOptionViewItem & option,const QModelIndex & index) const277 QWidget* Delegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
278 {
279 if(const auto rating = parseRating(index); (rating != Rating::Last))
280 {
281 auto* ratingEditor = new RatingEditor(rating, parent);
282 ratingEditor->setVerticalOffset(option.rect.height() / 2);
283
284 connect(ratingEditor, &RatingEditor::sigFinished, this, &Delegate::deleteEditor);
285
286 return ratingEditor;
287 }
288
289 return nullptr;
290 }
291
deleteEditor(bool save)292 void Delegate::deleteEditor([[maybe_unused]] bool save)
293 {
294 if(auto* ratingEditor = dynamic_cast<RatingEditor*>(sender()); ratingEditor)
295 {
296 disconnect(ratingEditor, &RatingEditor::sigFinished, this, &Delegate::deleteEditor);
297
298 emit commitData(ratingEditor);
299 emit closeEditor(ratingEditor);
300 }
301 }
302
setEditorData(QWidget * editor,const QModelIndex & index) const303 void Delegate::setEditorData(QWidget* editor, const QModelIndex& index) const
304 {
305 if(auto* ratingEditor = dynamic_cast<RatingEditor*>(editor); ratingEditor)
306 {
307 const auto rating = parseRating(index);
308 ratingEditor->setRating(rating);
309 }
310 }
311
setModelData(QWidget * editor,QAbstractItemModel * model,const QModelIndex & index) const312 void Delegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
313 {
314 if(auto* ratingEditor = dynamic_cast<RatingEditor*>(editor); ratingEditor)
315 {
316 const auto rating = ratingEditor->rating();
317 model->setData(index, QVariant::fromValue(rating));
318 }
319 }
320