File indexing completed on 2021-12-21 13:27:59

0001 /**
0002  * Copyright (C) 2002-2004 Scott Wheeler <wheeler@kde.org>
0003  *
0004  * This program is free software; you can redistribute it and/or modify it under
0005  * the terms of the GNU General Public License as published by the Free Software
0006  * Foundation; either version 2 of the License, or (at your option) any later
0007  * version.
0008  *
0009  * This program is distributed in the hope that it will be useful, but WITHOUT ANY
0010  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
0011  * PARTICULAR PURPOSE. See the GNU General Public License for more details.
0012  *
0013  * You should have received a copy of the GNU General Public License along with
0014  * this program.  If not, see <http://www.gnu.org/licenses/>.
0015  */
0016 
0017 #include "playlistitem.h"
0018 
0019 #include <algorithm>
0020 
0021 #include <kiconloader.h>
0022 
0023 #include <QCollator>
0024 #include <QFileInfo>
0025 #include <QHeaderView>
0026 #include <QGlobalStatic>
0027 #include <QPixmap>
0028 
0029 #include "collectionlist.h"
0030 #include "juktag.h"
0031 #include "coverinfo.h"
0032 #include "covermanager.h"
0033 #include "tagtransactionmanager.h"
0034 
0035 #include "juk_debug.h"
0036 
0037 PlaylistItemList PlaylistItem::m_playingItems; // static
0038 
0039 static int naturalCompare(const QString &first, const QString &second)
0040 {
0041     static QCollator collator;
0042     collator.setNumericMode(true);
0043     collator.setCaseSensitivity(Qt::CaseInsensitive);
0044     return collator.compare(first, second);
0045 }
0046 
0047 ////////////////////////////////////////////////////////////////////////////////
0048 // PlaylistItem public methods
0049 ////////////////////////////////////////////////////////////////////////////////
0050 
0051 PlaylistItem::~PlaylistItem()
0052 {
0053     // Although this isn't the most efficient way to accomplish the task of
0054     // stopping playback when deleting the item being played, it has the
0055     // stark advantage of working reliably.  I'll tell anyone who tries to
0056     // optimize this, the timing issues can be *hard*. -- mpyne
0057 
0058     if(m_collectionItem) {
0059         m_collectionItem->removeChildItem(this);
0060     }
0061 
0062     if(m_playingItems.contains(this)) {
0063         m_playingItems.removeAll(this);
0064         if(m_playingItems.isEmpty())
0065             playlist()->setPlaying(0);
0066     }
0067 
0068     playlist()->updateDeletedItem(this);
0069     emit playlist()->signalAboutToRemove(this);
0070 
0071     if(m_watched)
0072         Pointer::clear(this);
0073 }
0074 
0075 void PlaylistItem::setFile(const FileHandle &file)
0076 {
0077     m_collectionItem->updateCollectionDict(d->fileHandle.absFilePath(), file.absFilePath());
0078     d->fileHandle = file;
0079     refresh();
0080 }
0081 
0082 void PlaylistItem::setFile(const QString &file)
0083 {
0084     QString oldPath = d->fileHandle.absFilePath();
0085     d->fileHandle.setFile(file);
0086     m_collectionItem->updateCollectionDict(oldPath, d->fileHandle.absFilePath());
0087     refresh();
0088 }
0089 
0090 FileHandle PlaylistItem::file() const
0091 {
0092     return d->fileHandle;
0093 }
0094 
0095 QString PlaylistItem::text(int column) const
0096 {
0097     if(!d->fileHandle.tag())
0098         return QString();
0099 
0100     int offset = playlist()->columnOffset();
0101 
0102     switch(column - offset) {
0103     case TrackColumn:
0104         return d->fileHandle.tag()->title();
0105     case ArtistColumn:
0106         return d->fileHandle.tag()->artist();
0107     case AlbumColumn:
0108         return d->fileHandle.tag()->album();
0109     case CoverColumn:
0110         return QString();
0111     case TrackNumberColumn:
0112         return d->fileHandle.tag()->track() > 0
0113             ? QString::number(d->fileHandle.tag()->track())
0114             : QString();
0115     case GenreColumn:
0116         return d->fileHandle.tag()->genre();
0117     case YearColumn:
0118         return d->fileHandle.tag()->year() > 0
0119             ? QString::number(d->fileHandle.tag()->year())
0120             : QString();
0121     case LengthColumn:
0122         return d->fileHandle.tag()->lengthString();
0123     case BitrateColumn:
0124         return QString::number(d->fileHandle.tag()->bitrate());
0125     case CommentColumn:
0126         return d->fileHandle.tag()->comment();
0127     case FileNameColumn:
0128         return d->fileHandle.fileInfo().fileName();
0129     case FullPathColumn:
0130         return d->fileHandle.fileInfo().absoluteFilePath();
0131     default:
0132         return QTreeWidgetItem::text(column);
0133     }
0134 }
0135 
0136 void PlaylistItem::setText(int column, const QString &text)
0137 {
0138     QTreeWidgetItem::setText(column, text);
0139     playlist()->slotWeightDirty(column);
0140 }
0141 
0142 bool PlaylistItem::isPlaying() const
0143 {
0144     return std::any_of(m_playingItems.begin(), m_playingItems.end(),
0145         [this](const PlaylistItem *playingItem) {
0146             return this == playingItem;
0147         });
0148 }
0149 
0150 void PlaylistItem::setPlaying(bool playing, bool master)
0151 {
0152     m_playingItems.removeAll(this);
0153 
0154     if(playing) {
0155         if(master)
0156             m_playingItems.prepend(this);
0157         else
0158             m_playingItems.append(this);
0159     }
0160     else {
0161 
0162         // This is a tricky little recursion, but it
0163         // in fact does clear the list.
0164 
0165         if(!m_playingItems.isEmpty())
0166             m_playingItems.front()->setPlaying(false);
0167     }
0168 
0169     treeWidget()->viewport()->update();
0170 }
0171 
0172 void PlaylistItem::guessTagInfo(TagGuesser::Type type)
0173 {
0174     switch(type) {
0175     case TagGuesser::FileName:
0176     {
0177         TagGuesser guesser(d->fileHandle.absFilePath());
0178         Tag *tag = TagTransactionManager::duplicateTag(d->fileHandle.tag());
0179 
0180         if(!guesser.title().isNull())
0181             tag->setTitle(guesser.title());
0182         if(!guesser.artist().isNull())
0183             tag->setArtist(guesser.artist());
0184         if(!guesser.album().isNull())
0185             tag->setAlbum(guesser.album());
0186         if(!guesser.track().isNull())
0187             tag->setTrack(guesser.track().toInt());
0188         if(!guesser.comment().isNull())
0189             tag->setComment(guesser.comment());
0190 
0191         TagTransactionManager::instance()->changeTagOnItem(this, tag);
0192         break;
0193     }
0194     case TagGuesser::MusicBrainz:
0195         qCDebug(JUK_LOG) << "Ignoring MusicBrainz query request until support is reimplemented.";
0196         break;
0197     }
0198 }
0199 
0200 Playlist *PlaylistItem::playlist() const
0201 {
0202     return static_cast<Playlist *>(treeWidget());
0203 }
0204 
0205 QVector<int> PlaylistItem::cachedWidths() const
0206 {
0207     return d->cachedWidths;
0208 }
0209 
0210 void PlaylistItem::refresh()
0211 {
0212     m_collectionItem->refresh();
0213 }
0214 
0215 void PlaylistItem::refreshFromDisk()
0216 {
0217     d->fileHandle.refresh();
0218     refresh();
0219 }
0220 
0221 void PlaylistItem::clear()
0222 {
0223     playlist()->clearItem(this);
0224 }
0225 
0226 ////////////////////////////////////////////////////////////////////////////////
0227 // PlaylistItem protected methods
0228 ////////////////////////////////////////////////////////////////////////////////
0229 
0230 PlaylistItem::PlaylistItem(CollectionListItem *item, Playlist *parent) :
0231     QTreeWidgetItem(parent),
0232     d(0),
0233     m_watched(0)
0234 {
0235     setup(item);
0236 }
0237 
0238 PlaylistItem::PlaylistItem(CollectionListItem *item, Playlist *parent, QTreeWidgetItem *after) :
0239     QTreeWidgetItem(parent, after),
0240     d(0),
0241     m_watched(0)
0242 {
0243     setup(item);
0244 }
0245 
0246 
0247 // This constructor should only be used by the CollectionList subclass.
0248 
0249 PlaylistItem::PlaylistItem(CollectionList *parent)
0250   : QTreeWidgetItem(parent)
0251     // We *will* be this but we aren't yet; subclass will fix
0252   , m_collectionItem(nullptr)
0253   , m_watched(false)
0254 {
0255     d = new Data;
0256     setFlags(flags() | Qt::ItemIsEditable | Qt::ItemIsDragEnabled);
0257 }
0258 
0259 int PlaylistItem::compare(const QTreeWidgetItem *item, int column, bool ascending) const
0260 {
0261     // reimplemented from QListViewItem
0262 
0263     int offset = playlist()->columnOffset();
0264 
0265     if(!item)
0266         return 0;
0267 
0268     const PlaylistItem *playlistItem = static_cast<const PlaylistItem *>(item);
0269 
0270     // The following statments first check to see if you can sort based on the
0271     // specified column.  If the values for the two PlaylistItems are the same
0272     // in that column it then tries to sort based on columns 1, 2, 3 and 0,
0273     // (artist, album, track number, track name) in that order.
0274 
0275     int c = compare(this, playlistItem, column, ascending);
0276 
0277     if(c != 0)
0278         return c;
0279     else {
0280         // Loop through the columns doing comparisons until something is differnt.
0281         // If all else is the same, compare the track name.
0282 
0283         int last = !playlist()->isColumnHidden(AlbumColumn + offset) ? TrackNumberColumn : ArtistColumn;
0284 
0285         for(int i = ArtistColumn; i <= last; i++) {
0286             if(!playlist()->isColumnHidden(i + offset)) {
0287                 c = compare(this, playlistItem, i, ascending);
0288                 if(c != 0)
0289                     return c;
0290             }
0291         }
0292         return compare(this, playlistItem, TrackColumn + offset, ascending);
0293     }
0294 }
0295 
0296 int PlaylistItem::compare(const PlaylistItem *firstItem, const PlaylistItem *secondItem, int column, bool) const
0297 {
0298     int offset = playlist()->columnOffset();
0299 
0300     if(column < 0 || column > lastColumn() + offset || !firstItem->d || !secondItem->d)
0301         return 0;
0302 
0303     if(column < offset) {
0304         QString first = firstItem->text(column);
0305         QString second = secondItem->text(column);
0306         return naturalCompare(first, second);
0307     }
0308 
0309     switch(column - offset) {
0310     case TrackNumberColumn:
0311         if(firstItem->d->fileHandle.tag()->track() > secondItem->d->fileHandle.tag()->track())
0312             return 1;
0313         else if(firstItem->d->fileHandle.tag()->track() < secondItem->d->fileHandle.tag()->track())
0314             return -1;
0315         else
0316             return 0;
0317         break;
0318     case LengthColumn:
0319         if(firstItem->d->fileHandle.tag()->seconds() > secondItem->d->fileHandle.tag()->seconds())
0320             return 1;
0321         else if(firstItem->d->fileHandle.tag()->seconds() < secondItem->d->fileHandle.tag()->seconds())
0322             return -1;
0323         else
0324             return 0;
0325         break;
0326     case BitrateColumn:
0327         if(firstItem->d->fileHandle.tag()->bitrate() > secondItem->d->fileHandle.tag()->bitrate())
0328             return 1;
0329         else if(firstItem->d->fileHandle.tag()->bitrate() < secondItem->d->fileHandle.tag()->bitrate())
0330             return -1;
0331         else
0332             return 0;
0333         break;
0334     case CoverColumn:
0335         if(firstItem->d->fileHandle.coverInfo()->coverId() == secondItem->d->fileHandle.coverInfo()->coverId())
0336             return 0;
0337         else if (firstItem->d->fileHandle.coverInfo()->coverId() != CoverManager::NoMatch)
0338             return -1;
0339         else
0340             return 1;
0341         break;
0342     default:
0343         return naturalCompare(firstItem->d->metadata[column - offset],
0344                               secondItem->d->metadata[column - offset]);
0345     }
0346 }
0347 
0348 bool PlaylistItem::operator<(const QTreeWidgetItem &other) const
0349 {
0350     bool ascending = playlist()->header()->sortIndicatorOrder() == Qt::AscendingOrder;
0351     return compare(&other, playlist()->sortColumn(), ascending) == -1;
0352 }
0353 
0354 bool PlaylistItem::isValid() const
0355 {
0356     return bool(d->fileHandle.tag());
0357 }
0358 
0359 void PlaylistItem::setTrackId(quint32 id)
0360 {
0361     m_trackId = id;
0362 }
0363 
0364 ////////////////////////////////////////////////////////////////////////////////
0365 // PlaylistItem private methods
0366 ////////////////////////////////////////////////////////////////////////////////
0367 
0368 void PlaylistItem::setup(CollectionListItem *item)
0369 {
0370     m_collectionItem = item;
0371 
0372     d = item->d;
0373     item->addChildItem(this);
0374     setFlags(flags() | Qt::ItemIsEditable | Qt::ItemIsDragEnabled);
0375 
0376     int offset = playlist()->columnOffset();
0377     int columns = lastColumn() + offset + 1;
0378 
0379     for(int i = offset; i < columns; i++) {
0380         setText(i, text(i));
0381     }
0382 }
0383 
0384 ////////////////////////////////////////////////////////////////////////////////
0385 // PlaylistItem::Pointer implementation
0386 ////////////////////////////////////////////////////////////////////////////////
0387 
0388 QHash<PlaylistItem *, QVector<PlaylistItem::Pointer *> > PlaylistItem::Pointer::m_itemPointers; // static
0389 
0390 PlaylistItem::Pointer::Pointer(PlaylistItem *item) :
0391     m_item(item)
0392 {
0393     if(!m_item)
0394         return;
0395 
0396     m_item->m_watched = true;
0397     m_itemPointers[m_item].append(this);
0398 }
0399 
0400 PlaylistItem::Pointer::Pointer(const Pointer &p) :
0401     m_item(p.m_item)
0402 {
0403     m_itemPointers[m_item].append(this);
0404 }
0405 
0406 PlaylistItem::Pointer::~Pointer()
0407 {
0408     if(!m_item)
0409         return;
0410 
0411     m_itemPointers[m_item].removeAll(this);
0412     if(m_itemPointers[m_item].isEmpty()) {
0413         m_itemPointers.remove(m_item);
0414         m_item->m_watched = false;
0415     }
0416 }
0417 
0418 PlaylistItem::Pointer &PlaylistItem::Pointer::operator=(PlaylistItem *item)
0419 {
0420     if(item == m_item)
0421         return *this;
0422 
0423     if(m_item) {
0424         m_itemPointers[m_item].removeAll(this);
0425         if(m_itemPointers[m_item].isEmpty()) {
0426             m_itemPointers.remove(m_item);
0427             m_item->m_watched = false;
0428         }
0429     }
0430 
0431     if(item) {
0432         m_itemPointers[item].append(this);
0433         item->m_watched = true;
0434     }
0435 
0436     m_item = item;
0437 
0438     return *this;
0439 }
0440 
0441 void PlaylistItem::Pointer::clear(PlaylistItem *item) // static
0442 {
0443     if(!item)
0444         return;
0445 
0446     const auto itemPointers = m_itemPointers[item];
0447     for(const auto &pointerGuard : itemPointers)
0448         pointerGuard->m_item = nullptr;
0449     m_itemPointers.remove(item);
0450     item->m_watched = false;
0451 }
0452 
0453 // vim: set et sw=4 tw=0 sta: