File indexing completed on 2025-01-05 04:29:45
0001 /* 0002 * playlistmodel.cpp 0003 * 0004 * Copyright (C) 2009-2011 Christoph Pfister <christophpfister@gmail.com> 0005 * 0006 * This program is free software; you can redistribute it and/or modify 0007 * it under the terms of the GNU General Public License as published by 0008 * the Free Software Foundation; either version 2 of the License, or 0009 * (at your option) any later version. 0010 * 0011 * This program is distributed in the hope that it will be useful, 0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0014 * GNU General Public License for more details. 0015 * 0016 * You should have received a copy of the GNU General Public License along 0017 * with this program; if not, write to the Free Software Foundation, Inc., 0018 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 0019 */ 0020 0021 #include "../log.h" 0022 0023 #include <QDir> 0024 #include <QDomDocument> 0025 #include <QLocale> 0026 #include <QMimeData> 0027 #include <QSet> 0028 #include <QTextStream> 0029 #include <QXmlStreamWriter> 0030 0031 #include "playlistmodel.h" 0032 0033 bool Playlist::load(const QUrl &url_, Format format) 0034 { 0035 url = url_; 0036 title = url.fileName(); 0037 QString localFile = url.toLocalFile(); 0038 0039 if (localFile.isEmpty()) { 0040 // FIXME 0041 qCInfo(logPlaylist, "Opening remote playlists not supported yet"); 0042 return false; 0043 } 0044 0045 QFile file(localFile); 0046 0047 if (!file.open(QIODevice::ReadOnly)) { 0048 qCWarning(logPlaylist, "Cannot open file %s", qPrintable(file.fileName())); 0049 return false; 0050 } 0051 0052 switch (format) { 0053 case Invalid: 0054 return false; 0055 case Kaffeine: 0056 return loadKaffeinePlaylist(&file); 0057 case M3U: 0058 return loadM3UPlaylist(&file); 0059 case PLS: 0060 return loadPLSPlaylist(&file); 0061 case XSPF: 0062 return loadXSPFPlaylist(&file); 0063 } 0064 0065 return false; 0066 } 0067 0068 bool Playlist::save(Format format) const 0069 { 0070 QString localFile = url.toLocalFile(); 0071 0072 if (localFile.isEmpty()) { 0073 // FIXME 0074 qCInfo(logPlaylist, "Opening remote playlists not supported yet"); 0075 return false; 0076 } 0077 0078 QFile file(localFile); 0079 0080 if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { 0081 qCWarning(logPlaylist, "Cannot open file %s", qPrintable(file.fileName())); 0082 return false; 0083 } 0084 0085 switch (format) { 0086 case Invalid: 0087 return false; 0088 case Kaffeine: 0089 return false; // read-only 0090 case M3U: 0091 saveM3UPlaylist(&file); 0092 return true; 0093 case PLS: 0094 savePLSPlaylist(&file); 0095 return true; 0096 case XSPF: 0097 saveXSPFPlaylist(&file); 0098 return true; 0099 } 0100 0101 return false; 0102 } 0103 0104 void Playlist::appendTrack(PlaylistTrack &track) 0105 { 0106 if (track.url.isValid()) { 0107 if (track.title.isEmpty()) { 0108 track.title = track.url.fileName(); 0109 } 0110 0111 tracks.append(track); 0112 } 0113 } 0114 0115 QUrl Playlist::fromFileOrUrl(const QString &fileOrUrl) const 0116 { 0117 if (!QFileInfo(fileOrUrl).isRelative()) { 0118 return QUrl::fromLocalFile(fileOrUrl); 0119 } 0120 0121 QUrl trackUrl(fileOrUrl); 0122 0123 if (trackUrl.isRelative()) { 0124 trackUrl = url.resolved(QUrl::fromLocalFile(fileOrUrl)); 0125 0126 if (trackUrl.toEncoded() == url.toEncoded()) { 0127 return QUrl(); 0128 } 0129 } 0130 0131 return trackUrl; 0132 } 0133 0134 QUrl Playlist::fromRelativeUrl(const QString &trackUrlString) const 0135 { 0136 QUrl trackUrl(trackUrlString); 0137 0138 if (trackUrl.isRelative()) { 0139 trackUrl = url.resolved(trackUrl); 0140 } 0141 0142 return trackUrl; 0143 } 0144 0145 QString Playlist::toFileOrUrl(const QUrl &trackUrl) const 0146 { 0147 QString localFile = trackUrl.toLocalFile(); 0148 0149 if (!localFile.isEmpty()) { 0150 QString playlistPath = url.path(); 0151 int index = playlistPath.lastIndexOf(QLatin1Char('/')); 0152 playlistPath.truncate(index + 1); 0153 0154 if (localFile.startsWith(playlistPath)) { 0155 localFile.remove(0, index + 1); 0156 } 0157 0158 return QDir::toNativeSeparators(localFile); 0159 } else { 0160 return trackUrl.url(); 0161 } 0162 } 0163 0164 QString Playlist::toRelativeUrl(const QUrl &trackUrl) const 0165 { 0166 if ((trackUrl.scheme() == url.scheme()) && (trackUrl.authority() == url.authority())) { 0167 QByteArray playlistPath = url.toEncoded(); 0168 int index = playlistPath.lastIndexOf('/'); 0169 playlistPath.truncate(index + 1); 0170 QByteArray trackPath = trackUrl.toEncoded(); 0171 0172 if (trackPath.startsWith(playlistPath)) { 0173 trackPath.remove(0, index + 1); 0174 QUrl absolute (playlistPath); 0175 QStringList sBase = trackUrl.toString().split('/'); 0176 QStringList sAbsolute = absolute.toString().split('/'); 0177 QStringList res = QStringList(sAbsolute); 0178 0179 if (trackUrl.isParentOf(absolute)) { 0180 foreach(QString s, sBase) { 0181 res.removeFirst(); 0182 } 0183 } else { 0184 //Chop of the domain part 0185 res.removeFirst(); 0186 res.removeFirst(); 0187 res.removeFirst(); 0188 } 0189 0190 return res.join("/"); 0191 } 0192 } 0193 0194 return trackUrl.url(); 0195 } 0196 0197 bool Playlist::loadKaffeinePlaylist(QIODevice *device) 0198 { 0199 QDomDocument document; 0200 0201 if (!document.setContent(device)) { 0202 return false; 0203 } 0204 0205 QDomElement root = document.documentElement(); 0206 0207 if (root.nodeName() != QLatin1String("playlist")) { 0208 return false; 0209 } 0210 0211 for (QDomNode node = root.firstChild(); !node.isNull(); node = node.nextSibling()) { 0212 if (!node.isElement() || (node.nodeName() != QLatin1String("entry"))) { 0213 continue; 0214 } 0215 0216 PlaylistTrack track; 0217 track.url = fromFileOrUrl(node.attributes().namedItem(QLatin1String("url")).nodeValue()); 0218 track.title = node.attributes().namedItem(QLatin1String("title")).nodeValue(); 0219 track.artist = node.attributes().namedItem(QLatin1String("artist")).nodeValue(); 0220 track.album = node.attributes().namedItem(QLatin1String("album")).nodeValue(); 0221 0222 bool ok; 0223 int trackNumber = node.attributes().namedItem(QLatin1String("track")).nodeValue().toInt(&ok); 0224 0225 if (ok && (trackNumber >= 0)) { 0226 track.trackNumber = trackNumber; 0227 } 0228 0229 QString length = node.attributes().namedItem(QLatin1String("length")).nodeValue(); 0230 0231 if (length.size() == 7) { 0232 length.prepend(QLatin1Char('0')); 0233 } 0234 0235 track.length = QTime::fromString(length, Qt::ISODate); 0236 0237 if (QTime(0, 0, 0).msecsTo(track.length) == 0) { 0238 track.length = QTime(0, 0, 0); 0239 } 0240 0241 appendTrack(track); 0242 } 0243 0244 return true; 0245 } 0246 0247 bool Playlist::loadM3UPlaylist(QIODevice *device) 0248 { 0249 QTextStream stream(device); 0250 stream.setCodec("UTF-8"); 0251 PlaylistTrack track; 0252 0253 while (!stream.atEnd()) { 0254 QString line = stream.readLine(); 0255 0256 if (line.startsWith(QLatin1Char('#'))) { 0257 if (line.startsWith(QLatin1String("#EXTINF:"))) { 0258 int index = line.indexOf(QLatin1Char(','), 8); 0259 bool ok; 0260 int length = line.mid(8, index - 8).toInt(&ok); 0261 0262 if (ok && (length >= 0)) { 0263 track.length = QTime(0, 0, 0).addSecs(length); 0264 } 0265 0266 track.title = line.mid(index + 1); 0267 } 0268 } else { 0269 track.url = fromFileOrUrl(line); 0270 appendTrack(track); 0271 track = PlaylistTrack(); 0272 } 0273 } 0274 0275 return true; 0276 } 0277 0278 void Playlist::saveM3UPlaylist(QIODevice *device) const 0279 { 0280 QTextStream stream(device); 0281 stream.setCodec("UTF-8"); 0282 stream << "#EXTM3U\n"; 0283 0284 foreach (const PlaylistTrack &track, tracks) { 0285 int length = -1; 0286 0287 if (track.length.isValid()) { 0288 length = QTime(0, 0, 0).secsTo(track.length); 0289 } 0290 0291 stream << "#EXTINF:" << length << QLatin1Char(',') << track.title << QLatin1Char('\n'); 0292 stream << toFileOrUrl(track.url) << QLatin1Char('\n'); 0293 } 0294 } 0295 0296 bool Playlist::loadPLSPlaylist(QIODevice *device) 0297 { 0298 QTextStream stream(device); 0299 stream.setCodec("UTF-8"); 0300 0301 if (stream.readLine().compare(QLatin1String("[playlist]"), Qt::CaseInsensitive) != 0) { 0302 return false; 0303 } 0304 0305 PlaylistTrack track; 0306 int lastNumber = -1; 0307 0308 while (!stream.atEnd()) { 0309 QString line = stream.readLine(); 0310 int start; 0311 0312 if (line.startsWith(QLatin1String("File"), Qt::CaseInsensitive)) { 0313 start = 4; 0314 } else if (line.startsWith(QLatin1String("Title"), Qt::CaseInsensitive)) { 0315 start = 5; 0316 } else if (line.startsWith(QLatin1String("Length"), Qt::CaseInsensitive)) { 0317 start = 6; 0318 } else { 0319 continue; 0320 } 0321 0322 int index = line.indexOf(QLatin1Char('='), start); 0323 QString content = line.mid(index + 1); 0324 bool ok; 0325 int number = line.mid(start, index - start).toInt(&ok); 0326 0327 if (!ok) { 0328 continue; 0329 } 0330 0331 if (lastNumber != number) { 0332 if (lastNumber >= 0) { 0333 appendTrack(track); 0334 track = PlaylistTrack(); 0335 } 0336 0337 lastNumber = number; 0338 } 0339 0340 switch (start) { 0341 case 4: 0342 track.url = fromFileOrUrl(content); 0343 break; 0344 case 5: 0345 track.title = content; 0346 break; 0347 case 6: { 0348 int length = content.toInt(&ok); 0349 0350 if (ok && (length >= 0)) { 0351 track.length = QTime(0, 0, 0).addSecs(content.toInt()); 0352 } 0353 0354 break; 0355 } 0356 } 0357 } 0358 0359 if (lastNumber >= 0) { 0360 appendTrack(track); 0361 } 0362 0363 return true; 0364 } 0365 0366 void Playlist::savePLSPlaylist(QIODevice *device) const 0367 { 0368 QTextStream stream(device); 0369 stream.setCodec("UTF-8"); 0370 stream << "[Playlist]\n" 0371 "NumberOfEntries=" << tracks.size() << '\n'; 0372 int index = 1; 0373 0374 foreach (const PlaylistTrack &track, tracks) { 0375 int length = -1; 0376 0377 if (track.length.isValid()) { 0378 length = QTime(0, 0, 0).secsTo(track.length); 0379 } 0380 0381 stream << "File" << index << '=' << toFileOrUrl(track.url) << '\n'; 0382 stream << "Title" << index << '=' << track.title << '\n'; 0383 stream << "Length" << index << '=' << length << '\n'; 0384 ++index; 0385 } 0386 0387 stream << "Version=2\n"; 0388 } 0389 0390 bool Playlist::loadXSPFPlaylist(QIODevice *device) 0391 { 0392 QDomDocument document; 0393 0394 if (!document.setContent(device)) { 0395 return false; 0396 } 0397 0398 QDomElement root = document.documentElement(); 0399 0400 if (root.nodeName() != QLatin1String("playlist")) { 0401 return false; 0402 } 0403 0404 for (QDomNode node = root.firstChild(); !node.isNull(); node = node.nextSibling()) { 0405 if (!node.isElement()) { 0406 continue; 0407 } 0408 0409 QString nodeName = node.nodeName(); 0410 0411 if (nodeName == QLatin1String("title")) { 0412 title = node.toElement().text(); 0413 } else if (nodeName == QLatin1String("trackList")) { 0414 for (QDomNode childNode = node.firstChild(); !childNode.isNull(); 0415 childNode = childNode.nextSibling()) { 0416 if (!childNode.isElement()) { 0417 continue; 0418 } 0419 0420 PlaylistTrack track; 0421 0422 for (QDomNode trackNode = childNode.firstChild(); 0423 !trackNode.isNull(); trackNode = trackNode.nextSibling()) { 0424 if (!trackNode.isElement()) { 0425 continue; 0426 } 0427 0428 if (trackNode.nodeName() == QLatin1String("location")) { 0429 if (!track.url.isValid()) { 0430 track.url = fromRelativeUrl( 0431 trackNode.toElement().text()); 0432 } 0433 } else if (trackNode.nodeName() == QLatin1String("title")) { 0434 track.title = trackNode.toElement().text(); 0435 } else if (trackNode.nodeName() == QLatin1String("creator")) { 0436 track.artist = trackNode.toElement().text(); 0437 } else if (trackNode.nodeName() == QLatin1String("album")) { 0438 track.album = trackNode.toElement().text(); 0439 } else if (trackNode.nodeName() == QLatin1String("trackNum")) { 0440 bool ok; 0441 int trackNumber = 0442 trackNode.toElement().text().toInt(&ok); 0443 0444 if (ok && (trackNumber >= 0)) { 0445 track.trackNumber = trackNumber; 0446 } 0447 } else if (trackNode.nodeName() == QLatin1String("duration")) { 0448 bool ok; 0449 int length = 0450 trackNode.toElement().text().toInt(&ok); 0451 0452 if (ok && (length >= 0)) { 0453 track.length = QTime(0, 0, 0).addMSecs(length); 0454 } 0455 } 0456 } 0457 0458 appendTrack(track); 0459 } 0460 } 0461 } 0462 0463 return true; 0464 } 0465 0466 void Playlist::saveXSPFPlaylist(QIODevice *device) const 0467 { 0468 QXmlStreamWriter stream(device); 0469 stream.setAutoFormatting(true); 0470 stream.setAutoFormattingIndent(2); 0471 stream.writeStartDocument(); 0472 stream.writeStartElement(QLatin1String("playlist")); 0473 stream.writeAttribute(QLatin1String("version"), QLatin1String("1")); 0474 stream.writeAttribute(QLatin1String("xmlns"), QLatin1String("http://xspf.org/ns/0/")); 0475 stream.writeTextElement(QLatin1String("title"), title); 0476 stream.writeStartElement(QLatin1String("trackList")); 0477 0478 foreach (const PlaylistTrack &track, tracks) { 0479 stream.writeStartElement(QLatin1String("track")); 0480 stream.writeTextElement(QLatin1String("location"), toRelativeUrl(track.url)); 0481 0482 if (!track.title.isEmpty()) { 0483 stream.writeTextElement(QLatin1String("title"), track.title); 0484 } 0485 0486 if (!track.artist.isEmpty()) { 0487 stream.writeTextElement(QLatin1String("creator"), track.artist); 0488 } 0489 0490 if (!track.album.isEmpty()) { 0491 stream.writeTextElement(QLatin1String("album"), track.album); 0492 } 0493 0494 if (track.trackNumber >= 0) { 0495 stream.writeTextElement(QLatin1String("trackNum"), QString::number(track.trackNumber)); 0496 } 0497 0498 if (track.length.isValid()) { 0499 stream.writeTextElement(QLatin1String("duration"), 0500 QString::number(QTime(0, 0, 0).msecsTo(track.length))); 0501 } 0502 0503 stream.writeEndElement(); 0504 } 0505 0506 stream.writeEndElement(); 0507 stream.writeEndElement(); 0508 stream.writeEndDocument(); 0509 } 0510 0511 PlaylistModel::PlaylistModel(Playlist *visiblePlaylist_, QObject *parent) : 0512 QAbstractTableModel(parent), visiblePlaylist(visiblePlaylist_) 0513 { 0514 } 0515 0516 PlaylistModel::~PlaylistModel() 0517 { 0518 } 0519 0520 void PlaylistModel::setVisiblePlaylist(Playlist *visiblePlaylist_) 0521 { 0522 if (visiblePlaylist != visiblePlaylist_) { 0523 QAbstractItemModel::beginResetModel(); 0524 visiblePlaylist = visiblePlaylist_; 0525 QAbstractItemModel::endResetModel(); 0526 } 0527 } 0528 0529 Playlist *PlaylistModel::getVisiblePlaylist() const 0530 { 0531 return visiblePlaylist; 0532 } 0533 0534 void PlaylistModel::appendUrls(Playlist *playlist, const QList<QUrl> &urls, bool playImmediately) 0535 { 0536 insertUrls(playlist, playlist->tracks.size(), urls, playImmediately); 0537 } 0538 0539 void PlaylistModel::removeRows(Playlist *playlist, int row, int count) 0540 { 0541 if (playlist == visiblePlaylist) { 0542 beginRemoveRows(QModelIndex(), row, row + count - 1); 0543 } 0544 0545 QList<PlaylistTrack>::Iterator begin = playlist->tracks.begin() + row; 0546 playlist->tracks.erase(begin, begin + count); 0547 0548 if (playlist->currentTrack >= row) { 0549 if (playlist->currentTrack >= (row + count)) { 0550 playlist->currentTrack -= count; 0551 } else { 0552 playlist->currentTrack = -1; 0553 emit playTrack(playlist, -1); 0554 } 0555 } 0556 0557 if (playlist == visiblePlaylist) { 0558 endRemoveRows(); 0559 } 0560 } 0561 0562 void PlaylistModel::setCurrentTrack(Playlist *playlist, int track) 0563 { 0564 int oldTrack = playlist->currentTrack; 0565 playlist->currentTrack = track; 0566 0567 if (playlist == visiblePlaylist) { 0568 if (oldTrack >= 0) { 0569 QModelIndex modelIndex = index(oldTrack, 0); 0570 emit dataChanged(modelIndex, modelIndex); 0571 } 0572 0573 if (track >= 0) { 0574 QModelIndex modelIndex = index(track, 0); 0575 emit dataChanged(modelIndex, modelIndex); 0576 } 0577 } 0578 } 0579 0580 void PlaylistModel::updateTrackLength(Playlist *playlist, int length) 0581 { 0582 if (playlist->currentTrack >= 0) { 0583 if (QTime(0, 0, 0).msecsTo(playlist->tracks.at(playlist->currentTrack).length) < length) { 0584 playlist->tracks[playlist->currentTrack].length = QTime(0, 0, 0).addMSecs(length); 0585 0586 if (playlist == visiblePlaylist) { 0587 QModelIndex modelIndex = index(playlist->currentTrack, 4); 0588 emit dataChanged(modelIndex, modelIndex); 0589 } 0590 } 0591 } 0592 } 0593 0594 void PlaylistModel::updateTrackMetadata(Playlist *playlist, 0595 const QMap<MediaWidget::MetadataType, QString> &metadata) 0596 { 0597 if (playlist->currentTrack >= 0) { 0598 PlaylistTrack ¤tTrack = playlist->tracks[playlist->currentTrack]; 0599 0600 if ((currentTrack.title != metadata.value(MediaWidget::Title)) && 0601 !metadata.value(MediaWidget::Title).isEmpty()) { 0602 currentTrack.title = metadata.value(MediaWidget::Title); 0603 0604 if (playlist == visiblePlaylist) { 0605 QModelIndex modelIndex = index(playlist->currentTrack, 0); 0606 emit dataChanged(modelIndex, modelIndex); 0607 } 0608 } 0609 0610 if ((currentTrack.artist != metadata.value(MediaWidget::Artist)) && 0611 !metadata.value(MediaWidget::Artist).isEmpty()) { 0612 currentTrack.artist = metadata.value(MediaWidget::Artist); 0613 0614 if (playlist == visiblePlaylist) { 0615 QModelIndex modelIndex = index(playlist->currentTrack, 1); 0616 emit dataChanged(modelIndex, modelIndex); 0617 } 0618 } 0619 0620 if ((currentTrack.album != metadata.value(MediaWidget::Album)) && 0621 !metadata.value(MediaWidget::Album).isEmpty()) { 0622 currentTrack.album = metadata.value(MediaWidget::Album); 0623 0624 if (playlist == visiblePlaylist) { 0625 QModelIndex modelIndex = index(playlist->currentTrack, 2); 0626 emit dataChanged(modelIndex, modelIndex); 0627 } 0628 } 0629 0630 bool ok; 0631 int trackNumber = metadata.value(MediaWidget::TrackNumber).toInt(&ok); 0632 0633 if (!ok) { 0634 trackNumber = -1; 0635 } 0636 0637 if ((currentTrack.trackNumber != trackNumber) && (trackNumber >= 0)) { 0638 currentTrack.trackNumber = trackNumber; 0639 0640 if (playlist == visiblePlaylist) { 0641 QModelIndex modelIndex = index(playlist->currentTrack, 3); 0642 emit dataChanged(modelIndex, modelIndex); 0643 } 0644 } 0645 } 0646 } 0647 0648 void PlaylistModel::clearVisiblePlaylist() 0649 { 0650 QAbstractItemModel::beginResetModel(); 0651 0652 visiblePlaylist->tracks.clear(); 0653 0654 if (visiblePlaylist->currentTrack >= 0) { 0655 visiblePlaylist->currentTrack = -1; 0656 emit playTrack(visiblePlaylist, -1); 0657 } 0658 0659 QAbstractItemModel::endResetModel(); 0660 } 0661 0662 void PlaylistModel::insertUrls(Playlist *playlist, int row, const QList<QUrl> &urls, 0663 bool playImmediately) 0664 { 0665 QList<QUrl> processedUrls; 0666 0667 foreach (const QUrl &url, urls) { 0668 QString fileName = url.fileName(); 0669 Playlist::Format format = Playlist::Invalid; 0670 0671 if (fileName.endsWith(QLatin1String(".kaffeine"), Qt::CaseInsensitive)) { 0672 format = Playlist::Kaffeine; 0673 } else if (fileName.endsWith(QLatin1String(".m3u"), Qt::CaseInsensitive) || 0674 fileName.endsWith(QLatin1String(".m3u8"), Qt::CaseInsensitive)) { 0675 format = Playlist::M3U; 0676 } else if (fileName.endsWith(QLatin1String(".pls"), Qt::CaseInsensitive)) { 0677 format = Playlist::PLS; 0678 } else if (fileName.endsWith(QLatin1String(".xspf"), Qt::CaseInsensitive)) { 0679 format = Playlist::XSPF; 0680 } 0681 0682 if (format != Playlist::Invalid) { 0683 Playlist *nestdPlaylist = new Playlist(); 0684 0685 if (nestdPlaylist->load(url, format)) { 0686 emit appendPlaylist(nestdPlaylist, playImmediately); 0687 playImmediately = false; 0688 } else { 0689 delete nestdPlaylist; 0690 } 0691 0692 continue; 0693 } 0694 0695 QString localFile = url.toLocalFile(); 0696 0697 if (!localFile.isEmpty() && QFileInfo(localFile).isDir()) { 0698 QDir dir(localFile); 0699 QString extensionFilter = MediaWidget::extensionFilter(); 0700 extensionFilter.truncate(extensionFilter.indexOf(QLatin1Char('|'))); 0701 QStringList entries = dir.entryList(extensionFilter.split(QLatin1Char(' ')), 0702 QDir::Files, QDir::Name | QDir::LocaleAware); 0703 0704 for (int i = 0; i < entries.size(); ++i) { 0705 const QString &entry = entries.at(i); 0706 processedUrls.append(QUrl::fromLocalFile(dir.filePath(entry))); 0707 } 0708 } else { 0709 processedUrls.append(url); 0710 } 0711 } 0712 0713 if (!processedUrls.isEmpty()) { 0714 if (playlist == visiblePlaylist) { 0715 beginInsertRows(QModelIndex(), row, row + processedUrls.size() - 1); 0716 } 0717 0718 for (int i = 0; i < processedUrls.size(); ++i) { 0719 PlaylistTrack track; 0720 track.url = processedUrls.at(i); 0721 track.title = track.url.fileName(); 0722 playlist->tracks.insert(row + i, track); 0723 } 0724 0725 if (playlist->currentTrack >= row) { 0726 playlist->currentTrack += processedUrls.size(); 0727 } 0728 0729 if (playlist == visiblePlaylist) { 0730 endInsertRows(); 0731 } 0732 0733 if (playImmediately) { 0734 emit playTrack(playlist, row); 0735 } 0736 } 0737 } 0738 0739 int PlaylistModel::columnCount(const QModelIndex &parent) const 0740 { 0741 if (parent.isValid()) { 0742 return 0; 0743 } 0744 0745 return 5; 0746 } 0747 0748 int PlaylistModel::rowCount(const QModelIndex &parent) const 0749 { 0750 if (parent.isValid()) { 0751 return 0; 0752 } 0753 0754 return visiblePlaylist->tracks.size(); 0755 } 0756 0757 QVariant PlaylistModel::data(const QModelIndex &index, int role) const 0758 { 0759 if (role == Qt::DecorationRole) { 0760 if ((index.row() == visiblePlaylist->currentTrack) && (index.column() == 0)) { 0761 return QIcon::fromTheme(QLatin1String("arrow-right"), QIcon(":arrow-right")); 0762 } 0763 } else if (role == Qt::DisplayRole) { 0764 switch (index.column()) { 0765 case 0: 0766 return visiblePlaylist->at(index.row()).title; 0767 case 1: 0768 return visiblePlaylist->at(index.row()).artist; 0769 case 2: 0770 return visiblePlaylist->at(index.row()).album; 0771 case 3: { 0772 int trackNumber = visiblePlaylist->at(index.row()).trackNumber; 0773 0774 if (trackNumber >= 0) { 0775 return trackNumber; 0776 } else { 0777 return QVariant(); 0778 } 0779 } 0780 case 4: { 0781 QTime length = visiblePlaylist->at(index.row()).length; 0782 0783 if (length.isValid()) { 0784 return length.toString("HH:mm:ss.zzz"); 0785 } else { 0786 return QVariant(); 0787 } 0788 } 0789 } 0790 } 0791 0792 return QVariant(); 0793 } 0794 0795 QVariant PlaylistModel::headerData(int section, Qt::Orientation orientation, int role) const 0796 { 0797 if ((orientation == Qt::Horizontal) && (role == Qt::DisplayRole)) { 0798 switch (section) { 0799 case 0: 0800 return i18nc("playlist track", "Title"); 0801 case 1: 0802 return i18nc("playlist track", "Artist"); 0803 case 2: 0804 return i18nc("playlist track", "Album"); 0805 case 3: 0806 return i18nc("playlist track", "Track Number"); 0807 case 4: 0808 return i18nc("playlist track", "Length"); 0809 } 0810 } 0811 0812 return QVariant(); 0813 } 0814 0815 bool PlaylistModel::removeRows(int row, int count, const QModelIndex &parent) 0816 { 0817 if (parent.isValid()) { 0818 return false; 0819 } 0820 0821 removeRows(visiblePlaylist, row, count); 0822 return true; 0823 } 0824 0825 class PlaylistTrackLessThan 0826 { 0827 public: 0828 PlaylistTrackLessThan(const Playlist *playlist_, int column_, Qt::SortOrder order_) : 0829 playlist(playlist_), column(column_), order(order_) { } 0830 ~PlaylistTrackLessThan() { } 0831 0832 bool operator()(int x, int y) 0833 { 0834 int compare = 0; 0835 0836 switch (column) { 0837 case 0: 0838 compare = playlist->at(x).title.localeAwareCompare(playlist->at(y).title); 0839 break; 0840 case 1: 0841 compare = 0842 playlist->at(x).artist.localeAwareCompare(playlist->at(y).artist); 0843 break; 0844 case 2: 0845 compare = playlist->at(x).album.localeAwareCompare(playlist->at(y).album); 0846 break; 0847 case 3: 0848 compare = (playlist->at(x).trackNumber - playlist->at(y).trackNumber); 0849 break; 0850 case 4: 0851 compare = playlist->at(y).length.msecsTo(playlist->at(x).length); 0852 break; 0853 } 0854 0855 if (compare != 0) { 0856 if (order == Qt::AscendingOrder) { 0857 return (compare < 0); 0858 } else { 0859 return (compare > 0); 0860 } 0861 } 0862 0863 return (x < y); 0864 } 0865 0866 private: 0867 const Playlist *playlist; 0868 int column; 0869 Qt::SortOrder order; 0870 }; 0871 0872 void PlaylistModel::sort(int column, Qt::SortOrder order) 0873 { 0874 if (visiblePlaylist->tracks.size() <= 1) { 0875 return; 0876 } 0877 0878 QVector<int> mapping(visiblePlaylist->tracks.size()); 0879 0880 for (int i = 0; i < mapping.size(); ++i) { 0881 mapping[i] = i; 0882 } 0883 0884 std::sort(mapping.begin(), mapping.end(), 0885 PlaylistTrackLessThan(visiblePlaylist, column, order)); 0886 0887 QVector<int> reverseMapping(mapping.size()); 0888 0889 for (int i = 0; i < mapping.size(); ++i) { 0890 reverseMapping[mapping.at(i)] = i; 0891 } 0892 0893 emit layoutAboutToBeChanged(); 0894 0895 for (int i = 0; i < mapping.size();) { 0896 int target = mapping.at(i); 0897 0898 if (i != target) { 0899 qSwap(mapping[i], mapping[target]); 0900 #if QT_VERSION < 0x050c00 0901 visiblePlaylist->tracks.swap(i, target); 0902 #else 0903 visiblePlaylist->tracks.swapItemsAt(i, target); 0904 #endif 0905 } else { 0906 ++i; 0907 } 0908 } 0909 0910 if (visiblePlaylist->currentTrack >= 0) { 0911 visiblePlaylist->currentTrack = 0912 reverseMapping.value(visiblePlaylist->currentTrack); 0913 } 0914 0915 QModelIndexList oldIndexes = persistentIndexList(); 0916 QModelIndexList newIndexes; 0917 0918 foreach (const QModelIndex &oldIndex, oldIndexes) { 0919 newIndexes.append(index(reverseMapping.value(oldIndex.row()), oldIndex.column())); 0920 } 0921 0922 changePersistentIndexList(oldIndexes, newIndexes); 0923 emit layoutChanged(); 0924 } 0925 0926 Qt::ItemFlags PlaylistModel::flags(const QModelIndex &index) const 0927 { 0928 if (index.isValid()) { 0929 return (QAbstractTableModel::flags(index) | Qt::ItemIsDragEnabled); 0930 } else { 0931 return (QAbstractTableModel::flags(index) | Qt::ItemIsDropEnabled); 0932 } 0933 } 0934 0935 QStringList PlaylistModel::mimeTypes() const 0936 { 0937 return (QStringList() << QLatin1String("text/uri-list") << QLatin1String("x-org.kde.kaffeine-playlist")); 0938 } 0939 0940 Qt::DropActions PlaylistModel::supportedDropActions() const 0941 { 0942 return (Qt::CopyAction | Qt::MoveAction); 0943 } 0944 0945 Q_DECLARE_METATYPE(QList<PlaylistTrack>) 0946 0947 QMimeData *PlaylistModel::mimeData(const QModelIndexList &indexes) const 0948 { 0949 QList<PlaylistTrack> tracks; 0950 QSet<int> rows; 0951 0952 foreach (const QModelIndex &index, indexes) { 0953 int row = index.row(); 0954 0955 if (!rows.contains(row)) { 0956 tracks.append(visiblePlaylist->at(row)); 0957 rows.insert(row); 0958 } 0959 } 0960 0961 QMimeData *mimeData = new QMimeData(); 0962 mimeData->setData(QLatin1String("x-org.kde.kaffeine-playlist"), QByteArray()); 0963 mimeData->setProperty("tracks", QVariant::fromValue(tracks)); 0964 return mimeData; 0965 } 0966 0967 bool PlaylistModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, 0968 const QModelIndex &parent) 0969 { 0970 Q_UNUSED(action) 0971 Q_UNUSED(column) 0972 Q_UNUSED(parent) 0973 0974 if (row < 0) { 0975 row = visiblePlaylist->tracks.size(); 0976 } 0977 0978 QList<PlaylistTrack> tracks = data->property("tracks").value<QList<PlaylistTrack> >(); 0979 0980 if (!tracks.isEmpty()) { 0981 beginInsertRows(QModelIndex(), row, row + tracks.size() - 1); 0982 0983 for (int i = 0; i < tracks.size(); ++i) { 0984 visiblePlaylist->tracks.insert(row + i, tracks.at(i)); 0985 } 0986 0987 if (visiblePlaylist->currentTrack >= row) { 0988 visiblePlaylist->currentTrack += tracks.size(); 0989 } 0990 0991 endInsertRows(); 0992 return true; 0993 } 0994 0995 if (data->hasUrls()) { 0996 insertUrls(visiblePlaylist, row, data->urls(), false); 0997 return true; 0998 } 0999 1000 return false; 1001 } 1002 1003 #include "moc_playlistmodel.cpp"