1 /**
2  * projectM-qt -- Qt4 based projectM GUI
3  * Copyright (C)2003-2004 projectM Team
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  * See 'LICENSE.txt' included within this release
19  *
20  */
21 
22 #include <QIcon>
23 #include <QXmlStreamReader>
24 #include <QtDebug>
25 #include <QFileInfo>
26 #include <QDir>
27 #include <QMessageBox>
28 
29 #include "qxmlplaylisthandler.hpp"
30 #include "qplaylistmodel.hpp"
31 #include <QMimeData>
32 
33 QString QPlaylistModel::PRESET_MIME_TYPE("text/x-projectM-preset");
34 
35 class XmlReadFunctor {
36 	public:
XmlReadFunctor(QPlaylistModel & model)37 		XmlReadFunctor(QPlaylistModel & model) : m_model(model) {}
38 
setPlaylistDesc(const QString & desc)39 		inline void setPlaylistDesc(const QString & desc) {
40 			m_model.setPlaylistDesc(desc);
41 		}
42 
appendItem(const QString & url,const QString & name,int rating,int breedability)43 		inline void appendItem(const QString & url, const QString & name, int rating, int breedability) {
44 			m_model.appendRow(url, name, rating, breedability);
45 		}
46 
47 
48 	private:
49 		QPlaylistModel & m_model;
50 
51 };
52 
53 
54 class XmlWriteFunctor {
55 	public:
XmlWriteFunctor(QPlaylistModel & model)56 		XmlWriteFunctor(QPlaylistModel & model) : m_model(model), m_index(0) {}
57 
58 
playlistDesc() const59 		inline const QString & playlistDesc() const {
60 			return m_model.playlistDesc();
61 		}
62 
nextItem(QString & name,QString & url,int & rating,int & breedability)63 		inline bool nextItem(QString & name, QString & url, int & rating, int & breedability) {
64 			Q_UNUSED(name);
65 
66 			if (m_index >= m_model.rowCount())
67 				return false;
68 
69 			QModelIndex modelIndex = m_model.index(m_index, 0);
70 			url = m_model.data(modelIndex, QPlaylistModel::URLInfoRole).toString();
71 			rating = m_model.data(modelIndex, QPlaylistModel::RatingRole).toInt();
72 
73 			breedability = m_model.data(modelIndex, QPlaylistModel::BreedabilityRole).toInt();
74 
75 			m_index++;
76 			return true;
77 		}
78 	private:
79 		QPlaylistModel & m_model;
80 		int m_index;
81 
82 };
83 
softCutRatingsEnabled() const84 bool QPlaylistModel::softCutRatingsEnabled() const {
85 	return m_projectM.settings().softCutRatingsEnabled;
86 }
87 
QPlaylistModel(projectM & _projectM,QObject * parent)88 QPlaylistModel::QPlaylistModel ( projectM & _projectM, QObject * parent ) :
89 		QAbstractTableModel ( parent ), m_projectM ( _projectM )
90 {
91 
92 }
93 
94 
updateItemHighlights()95 void QPlaylistModel::updateItemHighlights()
96 {
97 	if ( rowCount() == 0 )
98 		return;
99 
100 	emit ( dataChanged ( this->index ( 0,0 ), this->index ( rowCount()-1,columnCount()-1 ) ) );
101 }
102 
setData(const QModelIndex & index,const QVariant & value,int role)103 bool QPlaylistModel::setData ( const QModelIndex & index, const QVariant & value, int role )
104 {
105 	if ( role == QPlaylistModel::RatingRole )
106 	{
107 		m_projectM.changePresetRating(index.row(), value.toInt(), HARD_CUT_RATING_TYPE);
108 		return true;
109 	} else if (role == QPlaylistModel::BreedabilityRole) {
110 		m_projectM.changePresetRating(index.row(), value.toInt(), SOFT_CUT_RATING_TYPE);
111 		return true;
112 	} else if (role == QPlaylistModel::NameRole) {
113 		m_projectM.changePresetName(index.row(), value.toString().toStdString());
114 		return true;
115 	}
116 	else
117 		return QAbstractTableModel::setData ( index, value, role );
118 
119 }
120 
flags(const QModelIndex & index) const121 Qt::ItemFlags QPlaylistModel::flags(const QModelIndex &index) const
122 {
123 	Qt::ItemFlags defaultFlags = QAbstractTableModel::flags(index);
124 
125 	if (index.isValid())
126 		return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
127 	else
128 		return Qt::ItemIsDropEnabled | defaultFlags;
129 }
130 
131 
dropMimeData(const QMimeData * data,Qt::DropAction action,int row,int column,const QModelIndex & parent)132  bool QPlaylistModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
133 				int row, int column, const QModelIndex &parent)
134 {
135 	Q_UNUSED(row);
136 	Q_UNUSED(parent);
137 	if (!data->hasFormat(PRESET_MIME_TYPE))
138 		return false;
139 
140 	if (action == Qt::IgnoreAction)
141 		return true;
142 
143 	if (column > 0)
144 		return false;
145 
146 	return true;
147 }
148 
ratingToIcon(int rating) const149 QVariant QPlaylistModel::ratingToIcon ( int rating )  const
150 {
151 	switch ( rating )
152 	{
153 		case 1:
154 			return QVariant ();
155 		case 2:
156 			return QVariant ( QIcon ( ":/images/icons/rating-1.png" ) );
157 		case 3:
158 			return QVariant ( QIcon ( ":/images/icons/rating-2.png" ) );
159 		case 4:
160 			return QVariant ( QIcon ( ":/images/icons/rating-3.png" ) );
161 		case 5:
162 			return QVariant ( QIcon ( ":/images/icons/rating-4.png" ) );
163 		case 6:
164 			return QVariant ( QIcon ( ":/images/icons/rating-5.png" ) );
165 		default:
166 			if (rating <= 0)
167 				return QVariant ();
168 			else
169 				return QVariant ( QIcon ( ":/images/icons/rating-5.png" ) );
170 	}
171 }
172 
173 
breedabilityToIcon(int rating) const174 QVariant QPlaylistModel::breedabilityToIcon( int rating )  const
175 {
176 	switch ( rating )
177 	{
178 		case 1:
179 			return QVariant ();
180 		case 2:
181 			return QVariant ( QIcon ( ":/images/icons/rating-1.png" ) );
182 		case 3:
183 			return QVariant ( QIcon ( ":/images/icons/rating-2.png" ) );
184 		case 4:
185 			return QVariant ( QIcon ( ":/images/icons/rating-3.png" ) );
186 		case 5:
187 			return QVariant ( QIcon ( ":/images/icons/rating-4.png" ) );
188 		case 6:
189 			return QVariant ( QIcon ( ":/images/icons/rating-5.png" ) );
190 		default:
191 			if (rating <= 0)
192 				return QVariant ();
193 			else
194 				return QVariant ( QIcon ( ":/images/icons/rating-5.png" ) );
195 	}
196 }
197 
getSillyRatingToolTip(int rating)198 QString QPlaylistModel::getSillyRatingToolTip(int rating) {
199 
200 switch (rating) {
201 	case 1:
202 		return QString("Rather watch grass grow than watch this");
203 	case 2:
204 		return QString("A very poor preset");
205 	case 3:
206 		return QString("Tolerable");
207 	case 4:
208 		return QString("Pretty good");
209 	case 5:
210 		return QString("Trippy eye candy");
211 	case 6:
212 		return QString("Crafted by a psychotic deity");
213 	default:
214 		if (rating <= 0 )
215 			return QString("So bad it literally makes other presets bad!");
216 		else
217 			return QString("Better than projectM itself!");
218 }
219 }
220 
221 
getBreedabilityToolTip(int rating) const222 QString QPlaylistModel::getBreedabilityToolTip(int rating) const {
223 
224 switch (rating) {
225 	case 1:
226 		return QString("Hidious.");
227 	case 2:
228 		return QString("Ugly.");
229 	case 3:
230 		return QString("Doable.");
231 	case 4:
232 		return QString("Hot.");
233 	case 5:
234 		return QString("Preset Magnet.");
235 	case 6:
236 		return QString("Preset Whore.");
237 	default:
238 		if (rating <= 0 )
239 			return QString("Infertile.");
240 		else
241 			return QString("Diseased slut.");
242 }
243 }
244 
data(const QModelIndex & index,int role=Qt::DisplayRole) const245 QVariant QPlaylistModel::data ( const QModelIndex & index, int role = Qt::DisplayRole ) const
246 {
247 
248 	if (!index.isValid())
249 		return QVariant();
250 
251 	unsigned int pos;
252 	const int rowidx = index.row();
253 
254 	switch ( role )
255 	{
256 		case Qt::DisplayRole:
257 			if ( index.column() == 0 )
258 				return QVariant ( QString ( m_projectM.getPresetName ( rowidx ).c_str() ) );
259 			else if (index.column() == 1)
260 				return ratingToIcon ( m_projectM.getPresetRating(rowidx, HARD_CUT_RATING_TYPE) );
261 			else
262 				return ratingToIcon ( m_projectM.getPresetRating(rowidx, SOFT_CUT_RATING_TYPE) );
263 		case Qt::ToolTipRole:
264 			if ( index.column() == 0 )
265 				return QVariant ( QString ( m_projectM.getPresetName ( rowidx ).c_str() ) );
266 			else if (index.column() == 1)
267 				return QString ( getSillyRatingToolTip(m_projectM.getPresetRating(rowidx, HARD_CUT_RATING_TYPE)));
268 			else return 		getBreedabilityToolTip(m_projectM.getPresetRating(rowidx, SOFT_CUT_RATING_TYPE));
269 		case Qt::DecorationRole:
270 			if ( index.column() == 1 )
271 				return ratingToIcon ( m_projectM.getPresetRating(rowidx, HARD_CUT_RATING_TYPE) );
272 			else if (index.column() == 2)
273 				return breedabilityToIcon ( m_projectM.getPresetRating(rowidx, SOFT_CUT_RATING_TYPE) );
274 			else
275 				return QVariant();
276 		case QPlaylistModel::RatingRole:
277 			return QVariant (  m_projectM.getPresetRating(rowidx, HARD_CUT_RATING_TYPE)  );
278 		case QPlaylistModel::BreedabilityRole:
279 			return QVariant (  m_projectM.getPresetRating(rowidx, SOFT_CUT_RATING_TYPE)  );
280 		case Qt::BackgroundRole:
281 
282 			if (!m_projectM.selectedPresetIndex(pos))
283 				return QVariant();
284 			if (m_projectM.isPresetLocked() && rowidx >= 0 && static_cast<unsigned int>(rowidx) == pos )
285 				return QColor(Qt::red);
286 			if (!m_projectM.isPresetLocked() && rowidx >= 0 && static_cast<unsigned int>(rowidx) == pos )
287 				return QColor(Qt::green);
288 			return QVariant();
289 		case QPlaylistModel::URLInfoRole:
290 			return QVariant ( QString ( m_projectM.getPresetURL ( rowidx ).c_str() ) );
291 		default:
292 
293 			return QVariant();
294 	}
295 }
296 
headerData(int section,Qt::Orientation orientation,int role) const297 QVariant QPlaylistModel::headerData ( int section, Qt::Orientation orientation, int role ) const
298 {
299 
300 	if ( orientation == Qt::Vertical )
301 		return QAbstractTableModel::headerData ( section, orientation, role );
302 	if ( ( section == 0 ) && ( role == Qt::DisplayRole ) )
303 		return QString ( tr ( "Preset" ) );
304 
305 	/// @bug hack. this should be formalized like it is in libprojectM.
306 	if ( ( section == 1 ) && ( role == Qt::DisplayRole )) {
307 		if (columnCount() == 2)
308 			return QString ( tr ( "Rating" ) );
309 		else
310 			return QString ( tr ( "Hard Rating" ) );
311     }
312 	if ( ( section == 2 ) && ( role == Qt::DisplayRole ) )
313 		return QString ( tr ( "Soft Rating" ) );
314 
315 	return QAbstractTableModel::headerData ( section, orientation, role );
316 }
317 
rowCount(const QModelIndex & parent) const318 int QPlaylistModel::rowCount ( const QModelIndex & parent ) const
319 {
320 	Q_UNUSED(parent);
321 
322 	return m_projectM.getPlaylistSize();
323 }
324 
325 
columnCount(const QModelIndex & parent) const326 int QPlaylistModel::columnCount ( const QModelIndex & parent ) const
327 {
328 	Q_UNUSED(parent);
329 
330 	return softCutRatingsEnabled() ? 3 : 2;
331 }
332 
appendRow(const QString & presetURL,const QString & presetName,int rating,int breedability)333 void QPlaylistModel::appendRow ( const QString & presetURL, const QString & presetName, int rating, int breedability )
334 {
335 	RatingList ratings;
336 	ratings.push_back(rating);
337 	ratings.push_back(breedability);
338 
339 	beginInsertRows ( QModelIndex(), rowCount(), rowCount() );
340 	m_projectM.addPresetURL ( presetURL.toStdString(), presetName.toStdString(), ratings);
341 	endInsertRows();
342 }
343 
insertRow(int index,const QString & presetURL,const QString & presetName,int rating,int breedability)344 void QPlaylistModel::insertRow (int index, const QString & presetURL, const QString & presetName, int rating, int breedability)  {
345 	RatingList ratings;
346 	ratings.push_back(rating);
347 	ratings.push_back(breedability);
348 
349 	beginInsertRows ( QModelIndex(), index, index);
350 	m_projectM.insertPresetURL (index, presetURL.toStdString(), presetName.toStdString(), ratings);
351 	endInsertRows();
352 }
353 
removeRows(int row,int count,const QModelIndex & parent)354 bool QPlaylistModel::removeRows ( int row, int count, const QModelIndex & parent)   {
355 	Q_UNUSED(parent);
356 
357 	beginRemoveRows ( QModelIndex(), row, count);
358 
359 	for (int i = 0; i < count; i++) {
360 		m_projectM.removePreset (row );
361 	}
362 	endRemoveRows();
363 	return true;
364 }
365 
removeRow(int index,const QModelIndex & parent)366 bool QPlaylistModel::removeRow ( int index, const QModelIndex & parent)
367 {
368 	Q_UNUSED(parent);
369 
370 	beginRemoveRows ( QModelIndex(), index, index );
371 	m_projectM.removePreset ( index );
372 	endRemoveRows();
373 	return true;
374 }
375 
clear()376 void QPlaylistModel::clear()
377 {
378 	clearItems();
379 	m_playlistName = "";
380 	m_playlistDesc = "";
381 }
382 
clearItems()383 void QPlaylistModel::clearItems()
384 {
385 	beginRemoveRows ( QModelIndex(), 0, rowCount()-1 );
386 	m_projectM.clearPlaylist();
387 	endRemoveRows();
388 }
389 
390 
writePlaylist(const QString & file)391 bool QPlaylistModel::writePlaylist ( const QString & file ) {
392 
393 	QFile qfile(file);
394 
395  	if (!qfile.open(QIODevice::WriteOnly)) {
396 		QMessageBox::warning (0, "Playlist Save Error", QString("There was a problem trying to save the playlist \"%1\".  You may not have permission to modify this file.").arg(file));
397 		return false;
398 	}
399 
400 	XmlWriteFunctor writeFunctor(*this);
401 
402 	QXmlPlaylistHandler::writePlaylist(&qfile, writeFunctor);
403 	return true;
404 }
405 
readPlaylist(const QString & file)406 bool QPlaylistModel::readPlaylist ( const QString & file )
407 {
408 
409 	if (QFileInfo(file).isDir()) {
410 		if (!QDir(file).isReadable()) {
411 			QMessageBox::warning (0, "Playlist Directory Error", QString(tr("There was a problem trying to open the playlist directory \"%1\".  The directory doesn't exist or you may not have permission to open it. ")).arg(file));
412 			return false;
413 		}
414 
415 		foreach (QFileInfo info, QDir(file).entryInfoList()) {
416 			if (info.fileName().toLower().endsWith(".prjm") || info.fileName().toLower().endsWith(".milk") || info.fileName().toLower().endsWith(".so"))
417 				appendRow(info.absoluteFilePath(), info.fileName(), 3,3);
418 		}
419 		return true;
420 	}
421 
422         QFile qfile(file);
423  	if (!qfile.open(QIODevice::ReadOnly)) {
424 		QMessageBox::warning (0, "Playlist File Error", QString(tr("There was a problem trying to open the playlist \"%1\".  The file may no longer exist or you may not have permission to read the file.")).arg(file));
425 		return false;
426 	}
427 
428 	XmlReadFunctor readFunctor(*this);
429 
430 	if (QXmlPlaylistHandler::readPlaylist(&qfile, readFunctor) != QXmlStreamReader::NoError) {
431 		QMessageBox::warning ( 0, "Playlist Parse Error", QString(tr("There was a problem trying to parse the playlist \"%1\". Some of your playlist items may have loaded correctly, but don't expect miracles.")).arg(file));
432 	}
433 
434 	return true;
435 }
436 
notifyDataChanged(unsigned int ind)437 void QPlaylistModel::notifyDataChanged(unsigned int ind)
438 {
439 
440 
441 	QModelIndex modelIndex = index (ind, 1);
442 	emit ( dataChanged ( modelIndex, modelIndex));
443 }
444 
445