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 &currentTrack = 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"