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