File indexing completed on 2024-05-19 04:48:40

0001 /****************************************************************************************
0002  * Copyright (c) 2009-2010 Bart Cerneels <bart.cerneels@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 #define DEBUG_PREFIX "PlaylistBrowserModel"
0018 
0019 #include "PlaylistBrowserModel.h"
0020 
0021 #include "AmarokMimeData.h"
0022 #include "playlistmanager/PlaylistManager.h"
0023 #include "playlist/PlaylistController.h"
0024 #include "playlist/PlaylistModel.h"
0025 #include "core/support/Debug.h"
0026 #include "widgets/PrettyTreeRoles.h"
0027 
0028 #include <QIcon>
0029 
0030 #include <QAction>
0031 
0032 #include <algorithm>
0033 
0034 using namespace PlaylistBrowserNS;
0035 
0036 // to be used with qSort.
0037 static bool
0038 lessThanPlaylistTitles( const Playlists::PlaylistPtr &lhs, const Playlists::PlaylistPtr &rhs )
0039 {
0040     return lhs->prettyName().toLower() < rhs->prettyName().toLower();
0041 }
0042 
0043 PlaylistBrowserModel::PlaylistBrowserModel( int playlistCategory )
0044     : m_playlistCategory( playlistCategory )
0045 {
0046     connect( The::playlistManager(), &PlaylistManager::updated, this, &PlaylistBrowserModel::slotUpdate );
0047 
0048     connect( The::playlistManager(), &PlaylistManager::playlistAdded,
0049              this, &PlaylistBrowserModel::slotPlaylistAdded );
0050     connect( The::playlistManager(), &PlaylistManager::playlistRemoved,
0051              this, &PlaylistBrowserModel::slotPlaylistRemoved );
0052     connect( The::playlistManager(), &PlaylistManager::playlistUpdated,
0053              this, &PlaylistBrowserModel::slotPlaylistUpdated );
0054 
0055     connect( The::playlistManager(), &PlaylistManager::renamePlaylist,
0056              this, &PlaylistBrowserModel::slotRenamePlaylist );
0057 
0058     m_playlists = loadPlaylists();
0059 }
0060 
0061 QVariant
0062 PlaylistBrowserModel::data( const QModelIndex &index, int role ) const
0063 {
0064     int row = REMOVE_TRACK_MASK(index.internalId());
0065     Playlists::PlaylistPtr playlist = m_playlists.value( row );
0066 
0067     QString name;
0068     QIcon icon;
0069     int playlistCount = 0;
0070     QList<QAction *> providerActions;
0071     QList<Playlists::PlaylistProvider *> providers =
0072         The::playlistManager()->getProvidersForPlaylist( playlist );
0073     Playlists::PlaylistProvider *provider = providers.count() == 1 ? providers.first() : nullptr;
0074     Meta::TrackPtr track;
0075 
0076     switch( index.column() )
0077     {
0078         case PlaylistBrowserModel::PlaylistItemColumn: //playlist or track data
0079         {
0080             if( IS_TRACK(index) )
0081             {
0082                 track = playlist->tracks()[index.row()];
0083                 name = track->prettyName();
0084                 icon = QIcon::fromTheme( QStringLiteral("amarok_track") );
0085             }
0086             else
0087             {
0088                 name = playlist->prettyName();
0089                 icon = QIcon::fromTheme( QStringLiteral("amarok_playlist") );
0090             }
0091             break;
0092         }
0093         case PlaylistBrowserModel::LabelColumn: //group
0094         {
0095             if( !playlist->groups().isEmpty() )
0096             {
0097                 name = playlist->groups().first();
0098                 icon = QIcon::fromTheme( QStringLiteral("folder") );
0099             }
0100             break;
0101         }
0102 
0103         case PlaylistBrowserModel::ProviderColumn: //source
0104         {
0105             if( providers.count() > 1 )
0106             {
0107                 QVariantList nameData;
0108                 QVariantList iconData;
0109                 QVariantList playlistCountData;
0110                 QVariantList providerActionsData;
0111                 foreach( Playlists::PlaylistProvider *provider, providers )
0112                 {
0113                     name = provider->prettyName();
0114                     nameData << name;
0115                     icon = provider->icon();
0116                     iconData << QVariant( icon );
0117                     playlistCount = provider->playlists().count();
0118                     if( playlistCount >= 0 )
0119                         playlistCountData << i18ncp(
0120                                 "number of playlists from one source",
0121                                 "One Playlist", "%1 playlists",
0122                                 playlistCount );
0123                     else
0124                         playlistCountData << i18nc(
0125                                 "normally number of playlists, but they are still loading",
0126                                 "Loading..." );
0127                     providerActions << provider->providerActions();
0128                     providerActionsData << QVariant::fromValue( providerActions );
0129                 }
0130                 switch( role )
0131                 {
0132                 case Qt::DisplayRole:
0133                 case Qt::EditRole:
0134                 case Qt::ToolTipRole:
0135                     return nameData;
0136                 case Qt::DecorationRole:
0137                     return iconData;
0138                 case PrettyTreeRoles::ByLineRole:
0139                     return playlistCountData;
0140                 case PrettyTreeRoles::DecoratorRoleCount:
0141                     return providerActions.count();
0142                 case PrettyTreeRoles::DecoratorRole:
0143                     return providerActionsData;
0144                 }
0145             }
0146             else if( provider )
0147             {
0148                 name = provider->prettyName();
0149                 icon = provider->icon();
0150                 playlistCount = provider->playlistCount();
0151                 providerActions << provider->providerActions();
0152             }
0153 
0154             break;
0155         }
0156 
0157         default: break;
0158     }
0159 
0160 
0161     switch( role )
0162     {
0163         case Qt::DisplayRole:
0164         case Qt::EditRole:
0165         case Qt::ToolTipRole:
0166             return name;
0167         case Qt::DecorationRole:
0168             return QVariant( icon );
0169         case PrettyTreeRoles::ByLineRole:
0170             if( IS_TRACK(index) )
0171                 return QVariant();
0172             else
0173                 return i18ncp( "number of playlists from one source", "One playlist",
0174                                "%1 playlists", playlistCount );
0175         case PlaylistBrowserModel::ProviderRole:
0176             return provider ? QVariant::fromValue( provider ) : QVariant();
0177         case PlaylistBrowserModel::PlaylistRole:
0178             return playlist ? QVariant::fromValue( playlist ) : QVariant();
0179         case PlaylistBrowserModel::TrackRole:
0180             return track ? QVariant::fromValue( track ) : QVariant();
0181 
0182         default:
0183             return QVariant();
0184     }
0185 }
0186 
0187 bool
0188 PlaylistBrowserModel::setData( const QModelIndex &idx, const QVariant &value, int role )
0189 {
0190 
0191     if( !idx.isValid() )
0192         return false;
0193 
0194     switch( idx.column() )
0195     {
0196         case ProviderColumn:
0197         {
0198             if( role == Qt::DisplayRole || role == Qt::EditRole )
0199             {
0200                 Playlists::PlaylistProvider *provider = getProviderByName( value.toString() );
0201                 if( !provider )
0202                     return false;
0203 
0204                 if( IS_TRACK( idx ) )
0205                 {
0206                     Meta::TrackPtr track = trackFromIndex( idx );
0207                     if( !track )
0208                         return false;
0209                     debug() << QStringLiteral( "Copy track \"%1\" to \"%2\"." )
0210                             .arg( track->prettyName(),  provider->prettyName() );
0211     //                return !provider->addTrack( track ).isNull();
0212                     provider->addTrack( track ); //ignore result since UmsPodcastProvider returns NULL
0213                     return true;
0214                 }
0215                 else
0216                 {
0217                     Playlists::PlaylistPtr playlist = playlistFromIndex( idx );
0218                     if( !playlist || ( playlist->provider() == provider ) )
0219                         return false;
0220 
0221                     foreach( Playlists::PlaylistPtr tempPlaylist , provider->playlists() )
0222                     {
0223                         if ( tempPlaylist->name() == playlist->name() )
0224                             return false;
0225                     }
0226 
0227                     debug() << QStringLiteral( "Copy playlist \"%1\" to \"%2\"." )
0228                             .arg( playlist->prettyName(), provider->prettyName() );
0229 
0230                     return !provider->addPlaylist( playlist ).isNull();
0231                 }
0232             }
0233 
0234             //return true even for the data we didn't handle to get QAbstractItemModel::setItemData to work
0235             //TODO: implement setItemData()
0236             return true;
0237         }
0238         case LabelColumn:
0239         {
0240             debug() << "changing group of item " << idx.internalId() << " to " << value.toString();
0241             Playlists::PlaylistPtr item = m_playlists.value( idx.internalId() );
0242             item->setGroups( value.toStringList() );
0243 
0244             return true;
0245         }
0246     }
0247 
0248     return false;
0249 }
0250 
0251 QModelIndex
0252 PlaylistBrowserModel::index( int row, int column, const QModelIndex &parent) const
0253 {
0254     if( !parent.isValid() )
0255     {
0256         if( row >= 0 && row < m_playlists.count() )
0257             return createIndex( row, column, row );
0258     }
0259     else //if it has a parent it is a track
0260     {
0261         //but check if the playlist indeed has that track
0262         Playlists::PlaylistPtr playlist = m_playlists.value( parent.row() );
0263         if( row < playlist->tracks().count() )
0264             return createIndex( row, column, SET_TRACK_MASK(parent.row()) );
0265     }
0266 
0267     return QModelIndex();
0268 }
0269 
0270 QModelIndex
0271 PlaylistBrowserModel::parent( const QModelIndex &index ) const
0272 {
0273     if( IS_TRACK(index) )
0274     {
0275         int row = REMOVE_TRACK_MASK(index.internalId());
0276         return this->index( row, index.column(), QModelIndex() );
0277     }
0278 
0279     return QModelIndex();
0280 }
0281 
0282 bool
0283 PlaylistBrowserModel::hasChildren( const QModelIndex &parent ) const
0284 {
0285     if( parent.column() > 0 )
0286         return false;
0287     if( !parent.isValid() )
0288     {
0289         return !m_playlists.isEmpty();
0290     }
0291     else if( !IS_TRACK(parent) )
0292     {
0293         Playlists::PlaylistPtr playlist = m_playlists.value( parent.internalId() );
0294         return playlist->trackCount() != 0; //-1 might mean there are tracks, but not yet loaded.
0295     }
0296 
0297     return false;
0298 }
0299 
0300 int
0301 PlaylistBrowserModel::rowCount( const QModelIndex &parent ) const
0302 {
0303     if( parent.column() > 0 )
0304         return 0;
0305 
0306     if( !parent.isValid() )
0307     {
0308         return m_playlists.count();
0309     }
0310     else if( !IS_TRACK(parent) )
0311     {
0312         Playlists::PlaylistPtr playlist = m_playlists.value( parent.internalId() );
0313         return playlist->trackCount();
0314     }
0315 
0316     return 0;
0317 }
0318 
0319 int
0320 PlaylistBrowserModel::columnCount( const QModelIndex &parent ) const
0321 {
0322     if( !parent.isValid() ) //for playlists (children of root)
0323         return 3; //name, group and provider
0324 
0325     //for tracks
0326     return 1; //only name
0327 }
0328 
0329 bool
0330 PlaylistBrowserModel::canFetchMore( const QModelIndex &parent ) const
0331 {
0332     if( parent.column() > 0 )
0333         return false;
0334 
0335     if( !parent.isValid() )
0336     {
0337         return false;
0338     }
0339     else if( !IS_TRACK(parent) )
0340     {
0341         Playlists::PlaylistPtr playlist = m_playlists.value( parent.internalId() );
0342         //TODO: implement incremental loading of tracks by checking for ==
0343         if( playlist->trackCount() != playlist->tracks().count() )
0344             return true; //tracks still need to be loaded.
0345     }
0346 
0347     return false;
0348 }
0349 
0350 void
0351 PlaylistBrowserModel::fetchMore ( const QModelIndex &parent )
0352 {
0353     if( parent.column() > 0 )
0354         return;
0355 
0356     //TODO: load playlists dynamically from provider
0357     if( !parent.isValid() )
0358         return;
0359 
0360     if( !IS_TRACK(parent) )
0361     {
0362         Playlists::PlaylistPtr playlist = m_playlists.value( parent.internalId() );
0363          // TODO: following doesn't seem to be needed, PlaylistBrowserModel seems to be able to cope with async track loading fine
0364         playlist->makeLoadingSync();
0365         playlist->triggerTrackLoad();
0366     }
0367 }
0368 
0369 Qt::ItemFlags
0370 PlaylistBrowserModel::flags( const QModelIndex &idx ) const
0371 {
0372     //Both providers and groups can be empty. QtGroupingProxy makes empty groups from the data in
0373     //the rootnode (here an invalid QModelIndex).
0374     //TODO: editable only if provider is writable.
0375     if( idx.column() == PlaylistBrowserModel::ProviderColumn )
0376         return Qt::ItemIsEnabled | Qt::ItemIsEditable;
0377 
0378     if( idx.column() == PlaylistBrowserModel::LabelColumn )
0379         return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDropEnabled;
0380 
0381     if( !idx.isValid() )
0382         return Qt::ItemIsDropEnabled;
0383 
0384     if( IS_TRACK(idx) )
0385             return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled;
0386 
0387     //item is a playlist
0388     return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled |
0389            Qt::ItemIsDropEnabled;
0390 }
0391 
0392 QVariant
0393 PlaylistBrowserModel::headerData( int section, Qt::Orientation orientation, int role ) const
0394 {
0395     if( orientation == Qt::Horizontal && role == Qt::DisplayRole )
0396     {
0397         switch( section )
0398         {
0399             case PlaylistBrowserModel::PlaylistItemColumn: return i18n("Name");
0400             case PlaylistBrowserModel::LabelColumn: return i18n("Group");
0401             case PlaylistBrowserModel::ProviderColumn: return i18n("Source");
0402             default: return QVariant();
0403         }
0404     }
0405 
0406     return QVariant();
0407 }
0408 
0409 QStringList
0410 PlaylistBrowserModel::mimeTypes() const
0411 {
0412     QStringList ret;
0413     ret << AmarokMimeData::PLAYLIST_MIME;
0414     ret << AmarokMimeData::TRACK_MIME;
0415     return ret;
0416 }
0417 
0418 QMimeData*
0419 PlaylistBrowserModel::mimeData( const QModelIndexList &indices ) const
0420 {
0421     AmarokMimeData* mime = new AmarokMimeData();
0422 
0423     Playlists::PlaylistList playlists;
0424     Meta::TrackList tracks;
0425 
0426     foreach( const QModelIndex &index, indices )
0427     {
0428         if( IS_TRACK(index) )
0429             tracks << trackFromIndex( index );
0430         else
0431             playlists << m_playlists.value( index.internalId() );
0432     }
0433 
0434     mime->setPlaylists( playlists );
0435     mime->setTracks( tracks );
0436 
0437     return mime;
0438 }
0439 
0440 bool
0441 PlaylistBrowserModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column,
0442                                  const QModelIndex &parent )
0443 {
0444     DEBUG_BLOCK
0445     debug() << "Dropped on" << parent << "row" << row << "column" << column << "action" << action;
0446     if( action == Qt::IgnoreAction )
0447         return true;
0448 
0449     //drop on track is not possible
0450     if( IS_TRACK(parent) )
0451         return false;
0452 
0453     const AmarokMimeData* amarokMime = dynamic_cast<const AmarokMimeData*>( data );
0454     if( !amarokMime )
0455         return false;
0456 
0457     if( data->hasFormat( AmarokMimeData::PLAYLIST_MIME ) )
0458     {
0459         // TODO: is this ever called????
0460         Playlists::PlaylistList playlists = amarokMime->playlists();
0461 
0462         foreach( Playlists::PlaylistPtr playlist, playlists )
0463         {
0464             if( !m_playlists.contains( playlist ) )
0465                 debug() << "Unknown playlist dragged in: " << playlist->prettyName();
0466         }
0467 
0468         return true;
0469     }
0470     else if( data->hasFormat( AmarokMimeData::TRACK_MIME ) )
0471     {
0472         Meta::TrackList tracks = amarokMime->tracks();
0473         if( !parent.isValid() && row == -1 && column == -1 )
0474         {
0475             debug() << "Dropped tracks on empty area: create new playlist in a default provider";
0476             The::playlistManager()->save( tracks, Amarok::generatePlaylistName( tracks ) );
0477             return true;
0478         }
0479         else if( !parent.isValid() )
0480         {
0481             warning() << "Dropped tracks between root items, this is not supported!";
0482             return false;
0483         }
0484         else
0485         {
0486             debug() << "Dropped tracks on " << parent << " at row: " << row;
0487 
0488             Playlists::PlaylistPtr playlist = playlistFromIndex( parent );
0489             if( !playlist )
0490                 return false;
0491 
0492             foreach( Meta::TrackPtr track, tracks )
0493                 playlist->addTrack( track, ( row >= 0 ) ? row++ : -1 ); // increment only if positive
0494 
0495             return true;
0496         }
0497     }
0498 
0499     return false;
0500 }
0501 
0502 void
0503 PlaylistBrowserModel::metadataChanged( const Playlists::PlaylistPtr &playlist )
0504 {
0505     int indexNumber = m_playlists.indexOf( playlist );
0506     if( indexNumber == -1 )
0507     {
0508         error() << "This playlist is not in the list of this model.";
0509         return;
0510     }
0511     QModelIndex playlistIdx = index( indexNumber, 0 );
0512     Q_EMIT dataChanged( playlistIdx, playlistIdx );
0513 }
0514 
0515 void
0516 PlaylistBrowserModel::trackAdded(const Playlists::PlaylistPtr &playlist, const Meta::TrackPtr &track,
0517                                           int position )
0518 {
0519     Q_UNUSED( track );
0520     int indexNumber = m_playlists.indexOf( playlist );
0521     if( indexNumber == -1 )
0522     {
0523         error() << "This playlist is not in the list of this model.";
0524         return;
0525     }
0526     QModelIndex playlistIdx = index( indexNumber, 0, QModelIndex() );
0527     beginInsertRows( playlistIdx, position, position );
0528     endInsertRows();
0529 }
0530 
0531 void
0532 PlaylistBrowserModel::trackRemoved(const Playlists::PlaylistPtr &playlist, int position )
0533 {
0534     int indexNumber = m_playlists.indexOf( playlist );
0535     if( indexNumber == -1 )
0536     {
0537         error() << "This playlist is not in the list of this model.";
0538         return;
0539     }
0540     QModelIndex playlistIdx = index( indexNumber, 0, QModelIndex() );
0541     beginRemoveRows( playlistIdx, position, position );
0542     endRemoveRows();
0543 }
0544 
0545 void
0546 PlaylistBrowserModel::slotRenamePlaylist( Playlists::PlaylistPtr playlist )
0547 {
0548     if( !playlist->provider() || playlist->provider()->category() != m_playlistCategory )
0549         return;
0550 
0551     int row = 0;
0552     foreach( Playlists::PlaylistPtr p, m_playlists )
0553     {
0554         if( p == playlist )
0555         {
0556             Q_EMIT renameIndex( index( row, 0 ) );
0557             break;
0558         }
0559         row++;
0560     }
0561 }
0562 
0563 void
0564 PlaylistBrowserModel::slotUpdate( int category )
0565 {
0566     if( category != m_playlistCategory )
0567         return;
0568 
0569     beginResetModel();
0570 
0571     foreach( Playlists::PlaylistPtr playlist, m_playlists )
0572         unsubscribeFrom( playlist );
0573 
0574     m_playlists.clear();
0575     m_playlists = loadPlaylists();
0576 
0577     endResetModel();
0578 }
0579 
0580 Playlists::PlaylistList
0581 PlaylistBrowserModel::loadPlaylists()
0582 {
0583     Playlists::PlaylistList playlists =
0584             The::playlistManager()->playlistsOfCategory( m_playlistCategory );
0585     QListIterator<Playlists::PlaylistPtr> i( playlists );
0586 
0587     debug() << playlists.count() << " playlists for category " << m_playlistCategory;
0588 
0589     while( i.hasNext() )
0590     {
0591         Playlists::PlaylistPtr playlist = i.next();
0592         subscribeTo( playlist );
0593     }
0594 
0595     std::sort( playlists.begin(), playlists.end(), lessThanPlaylistTitles );
0596 
0597     return playlists;
0598 }
0599 
0600 void
0601 PlaylistBrowserModel::slotPlaylistAdded( Playlists::PlaylistPtr playlist, int category )
0602 {
0603     //ignore playlists of a different category
0604     if( category != m_playlistCategory )
0605         return;
0606 
0607     subscribeTo( playlist );
0608     int i;
0609     for( i = 0; i < m_playlists.count(); i++ )
0610     {
0611         if( lessThanPlaylistTitles( playlist, m_playlists[i] ) )
0612             break;
0613     }
0614 
0615     beginInsertRows( QModelIndex(), i, i );
0616     m_playlists.insert( i, playlist );
0617     endInsertRows();
0618 }
0619 
0620 void
0621 PlaylistBrowserModel::slotPlaylistRemoved( Playlists::PlaylistPtr playlist, int category )
0622 {
0623     if( category != m_playlistCategory )
0624         return;
0625 
0626     int position = m_playlists.indexOf( playlist );
0627     if( position == -1 )
0628     {
0629         error() << "signal received for removed playlist not in m_playlists";
0630         return;
0631     }
0632 
0633     beginRemoveRows( QModelIndex(), position, position );
0634     m_playlists.removeAt( position );
0635     endRemoveRows();
0636 }
0637 
0638 void
0639 PlaylistBrowserModel::slotPlaylistUpdated( Playlists::PlaylistPtr playlist, int category )
0640 {
0641     if( category != m_playlistCategory )
0642         return;
0643 
0644     int position = m_playlists.indexOf( playlist );
0645     if( position == -1 )
0646     {
0647         error() << "signal received for updated playlist not in m_playlists";
0648         return;
0649     }
0650 
0651     //TODO: this should work by signaling a change in the model data, but QtGroupingProxy doesn't
0652     //work like that ATM
0653 //    const QModelIndex &idx = index( position, 0 );
0654 //    Q_EMIT dataChanged( idx, idx );
0655 
0656     //HACK: remove and readd so QtGroupingProxy can put it in the correct groups.
0657     beginRemoveRows( QModelIndex(), position, position );
0658     endRemoveRows();
0659 
0660     beginInsertRows( QModelIndex(), position, position );
0661     endInsertRows();
0662 }
0663 
0664 Meta::TrackList
0665 PlaylistBrowserModel::tracksFromIndexes( const QModelIndexList &list ) const
0666 {
0667     Meta::TrackList tracks;
0668     foreach( const QModelIndex &index, list )
0669     {
0670         if( IS_TRACK(index) )
0671             tracks << trackFromIndex( index );
0672         else if( Playlists::PlaylistPtr playlist = playlistFromIndex( index ) )
0673         {
0674             playlist->makeLoadingSync();
0675             //first trigger a load of the tracks or we'll end up with an empty list
0676             playlist->triggerTrackLoad();
0677             tracks << playlist->tracks();
0678         }
0679     }
0680     return tracks;
0681 }
0682 
0683 Meta::TrackPtr
0684 PlaylistBrowserModel::trackFromIndex( const QModelIndex &idx ) const
0685 {
0686     if( !idx.isValid() || !IS_TRACK(idx) )
0687         return Meta::TrackPtr();
0688 
0689     int playlistRow = REMOVE_TRACK_MASK(idx.internalId());
0690     if( playlistRow >= m_playlists.count() )
0691         return Meta::TrackPtr();
0692 
0693     Playlists::PlaylistPtr playlist = m_playlists.value( playlistRow );
0694     if( playlist.isNull() || playlist->tracks().count() <= idx.row() )
0695         return Meta::TrackPtr();
0696 
0697     return playlist->tracks()[idx.row()];
0698 }
0699 
0700 Playlists::PlaylistPtr
0701 PlaylistBrowserModel::playlistFromIndex( const QModelIndex &index ) const
0702 {
0703     if( !index.isValid() )
0704         return Playlists::PlaylistPtr();
0705 
0706     return m_playlists.value( index.internalId() );
0707 }
0708 
0709 Playlists::PlaylistProvider *
0710 PlaylistBrowserModel::providerForIndex( const QModelIndex &idx ) const
0711 {
0712     if( !idx.isValid() )
0713         return nullptr;
0714 
0715     int playlistRow;
0716     if( IS_TRACK( idx ) )
0717         playlistRow = REMOVE_TRACK_MASK( idx.internalId() );
0718     else
0719         playlistRow = idx.row();
0720 
0721     if( playlistRow >= m_playlists.count() )
0722         return nullptr;
0723 
0724     return m_playlists.at( playlistRow )->provider();
0725 }
0726 
0727 Playlists::PlaylistProvider *
0728 PlaylistBrowserModel::getProviderByName( const QString &name )
0729 {
0730     QList<Playlists::PlaylistProvider *> providers =
0731             The::playlistManager()->providersForCategory( m_playlistCategory );
0732     foreach( Playlists::PlaylistProvider *provider, providers )
0733     {
0734         if( provider->prettyName() == name )
0735             return provider;
0736     }
0737     return nullptr;
0738 }