Just as a relaxing sunday evening project, try to create a simple, alternate playlist...
[amarok.git] / src / playlist / PlaylistModel.cpp
blob0ac447f74125b1cbf1d90c351f3c2d514ff8d646
1 /***************************************************************************
2 * copyright : (C) 2007 Ian Monroe <ian@monroe.nu> *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License version 2 *
6 * as published by the Free Software Foundation. *
7 ***************************************************************************/
9 #include "PlaylistModel.h"
11 #include "amarok.h"
12 #include "AmarokMimeData.h"
13 #include "debug.h"
14 #include "enginecontroller.h"
15 #include "PlaylistItem.h"
16 #include "RepeatTrackNavigator.h"
17 #include "StandardTrackNavigator.h"
18 #include "statusbar.h"
19 #include "TheInstances.h"
20 #include "UndoCommands.h"
22 #include "collection/BlockingQuery.h"
23 #include "collection/Collection.h"
24 #include "collection/CollectionManager.h"
25 #include "collection/QueryMaker.h"
26 #include "meta/lastfm/LastFmMeta.h"
28 #include <QAction>
29 #include <QStringList>
30 #include <QUndoStack>
32 #include <KIcon>
33 #include <KUrl>
35 using namespace Playlist;
36 using namespace Meta;
38 Model *Model::s_instance = 0;
40 Model::Model( QObject* parent )
41 : QAbstractListModel( parent )
42 , m_activeRow( -1 )
43 , m_advancer( new StandardTrackNavigator( this ) )
44 , m_undoStack( new QUndoStack( this ) )
45 , m_playlistLoader ( new PlaylistLoader )
47 connect( EngineController::instance(), SIGNAL( trackFinished() ), this, SLOT( trackFinished() ) );
48 connect( EngineController::instance(), SIGNAL( orderCurrent() ), this, SLOT( playCurrentTrack() ) );
49 s_instance = this;
52 void
53 Model::init()
55 KActionCollection* ac = Amarok::actionCollection();
56 QAction* undoButton = m_undoStack->createUndoAction( this, i18n("Undo") );
57 undoButton->setIcon( KIcon( Amarok::icon( "undo" ) ) );
58 ac->addAction("playlist_undo", undoButton);
59 QAction* redoButton = m_undoStack->createRedoAction( this, i18n("Redo") );
60 ac->addAction("playlist_redo", redoButton);
61 redoButton->setIcon( KIcon( Amarok::icon( "redo" ) ) );
64 Model::~Model()
66 delete m_advancer;
67 delete m_playlistLoader;
70 int
71 Model::rowCount( const QModelIndex& ) const
73 return m_items.size();
76 QVariant
77 Model::data( const QModelIndex& index, int role ) const
79 int row = index.row();
80 /*if( ( role == Qt::FontRole) && ( row == m_activeRow ) )
82 QFont original;
83 original.setBold( true );
84 return original;
86 else*/
87 if( role == ItemRole && ( row != -1 ) )
89 return QVariant::fromValue( m_items.at( row ) );
91 else if( role == ActiveTrackRole )
92 return ( row == m_activeRow );
93 else if( role == TrackRole && ( row != -1 ) && m_items.at( row )->track() )
95 return QVariant::fromValue( m_items.at( row )->track() );
97 else if( role == Qt::DisplayRole && row != -1 )
99 switch ( index.column() ) {
100 case 0:
101 return m_items.at( row )->track()->name();
102 case 1:
103 return m_items.at( row )->track()->album()->name();
104 case 2:
105 return m_items.at( row )->track()->artist()->name();
108 else
110 return QVariant();
112 /* switch( role )
114 case AlbumArtist: return track->album()->albumArtist()->name();
115 case Album: return track->album()->name();
116 case Artist: return track->artist()->name();
117 case Bitrate: return track->bitrate();
118 case Composer: return track->composer()->name();
119 case CoverImage: return track->album()->image( 50 );
120 case Comment: return track->comment();
121 case DiscNumber: return track->discNumber();
122 case Filesize: return track->filesize();
123 case Genre: return track->genre()->name();
124 case Length: return track->length();
125 case Rating: return track->rating();
126 case Score: return track->score();
127 case Title: return track->name();
128 case TrackNumber: return track->trackNumber();
129 case Year: return track->year()->name().toInt();
130 default: return QVariant();
131 } */
134 // void
135 // insertColumns( int column, Column type )
136 // {
137 // beginInsertColumns( QModelIndex(), column, column + 1 );
138 // m_columns.insert( column, type );
139 // endInsertColumns();
140 // return true;
141 // }
143 void
144 Model::insertTrack( int row, TrackPtr track )
146 DEBUG_BLOCK
147 TrackList list;
148 list.append( track );
149 insertTracks( row, list );
153 void
154 Model::insertTracks( int row, TrackList tracks )
156 m_undoStack->push( new AddTracksCmd( 0, row, tracks ) );
159 bool
160 Model::removeRows( int position, int rows, const QModelIndex& /*parent*/ )
162 m_undoStack->push( new RemoveTracksCmd( 0, position, rows ) );
163 return true;
167 void
168 Model::insertTracks( int row, QueryMaker *qm )
170 qm->startTrackQuery();
171 connect( qm, SIGNAL( queryDone() ), SLOT( queryDone() ) );
172 connect( qm, SIGNAL( newResultReady( QString, Meta::TrackList ) ), SLOT( newResultReady( QString, Meta::TrackList ) ) );
173 m_queryMap.insert( qm, row );
174 qm->run();
177 void
178 Model::testData()
180 DEBUG_BLOCK
181 Collection *local = 0;
182 foreach( Collection *coll, CollectionManager::instance()->collections() )
184 if( coll->collectionId() == "localCollection" )
185 local = coll;
187 if( !local )
188 return;
189 QueryMaker *qm = local->queryMaker();
190 qm->startTrackQuery();
191 qm->limitMaxResultSize( 10 );
192 BlockingQuery bq( qm );
193 bq.startQuery();
194 insertTracks( 0, bq.tracks( "localCollection" ) );
195 reset();
198 Qt::DropActions
199 Model::supportedDropActions() const
201 return Qt::CopyAction | Qt::MoveAction;
204 void
205 Model::trackFinished()
207 if( m_activeRow < 0 || m_activeRow >= m_items.size() )
208 return;
209 Meta::TrackPtr track = m_items.at( m_activeRow )->track();
210 track->finishedPlaying( 1.0 ); //TODO: get correct value for parameter
211 m_advancer->advanceTrack();
214 void
215 Model::play( const QModelIndex& index )
217 play( index.row() );
220 void
221 Model::play( int row )
223 setActiveRow( row );
224 EngineController::instance()->play( m_items[ m_activeRow ]->track() );
227 void
228 Model::playlistRepeatMode( int item )
230 if( item == 0 )
231 playModeChanged( Playlist::Standard );
232 else //for now just turn on repeat if anything but "off" is clicked
233 playModeChanged( Playlist::Repeat );
236 void
237 Model::next()
239 if( m_activeRow < 0 || m_activeRow >= m_items.size() )
240 return;
241 Meta::TrackPtr track = m_items.at( m_activeRow )->track();
242 track->finishedPlaying( 0.5 ); //TODO: get correct value for parameter
243 m_advancer->userAdvanceTrack();
246 void
247 Model::back()
249 if( m_activeRow < 0 || m_activeRow >= m_items.size() )
250 return;
251 Meta::TrackPtr track = m_items.at( m_activeRow )->track();
252 track->finishedPlaying( 0.5 ); //TODO: get correct value for parameter
253 m_advancer->recedeTrack();
256 void
257 Model::playCurrentTrack()
259 int selected = m_activeRow;
260 if( selected < 0 || selected >= m_items.size() )
262 //play first track if there are tracks in the playlist
263 if( m_items.size() )
264 selected = 0;
265 else
266 return;
268 play( selected );
272 QString
273 Model::prettyColumnName( Column index ) //static
275 switch( index )
277 case Filename: return i18n( "Filename" );
278 case Title: return i18n( "Title" );
279 case Artist: return i18n( "Artist" );
280 case AlbumArtist:return i18n( "Album Artist");
281 case Composer: return i18n( "Composer" );
282 case Year: return i18n( "Year" );
283 case Album: return i18n( "Album" );
284 case DiscNumber: return i18n( "Disc Number" );
285 case TrackNumber:return i18n( "Track" );
286 case Bpm: return i18n( "BPM" );
287 case Genre: return i18n( "Genre" );
288 case Comment: return i18n( "Comment" );
289 case Directory: return i18n( "Directory" );
290 case Type: return i18n( "Type" );
291 case Length: return i18n( "Length" );
292 case Bitrate: return i18n( "Bitrate" );
293 case SampleRate: return i18n( "Sample Rate" );
294 case Score: return i18n( "Score" );
295 case Rating: return i18n( "Rating" );
296 case PlayCount: return i18n( "Play Count" );
297 case LastPlayed: return i18nc( "Column name", "Last Played" );
298 case Mood: return i18n( "Mood" );
299 case Filesize: return i18n( "File Size" );
300 default: return "This is a bug.";
305 void
306 Model::playModeChanged( int row )
308 delete m_advancer;
309 switch (row)
311 case Playlist::Standard:
312 m_advancer = new StandardTrackNavigator(this);
313 break;
314 case Playlist::Repeat:
315 m_advancer = new RepeatTrackNavigator(this);
316 break;
320 void
321 Model::setActiveRow( int row )
323 DEBUG_BLOCK
325 int max = qMax( row, m_activeRow );
326 int min = qMin( row, m_activeRow );
327 if( ( max - min ) == 1 )
328 emit dataChanged( createIndex( min, 0 ), createIndex( max, 0 ) );
329 else
331 emit dataChanged( createIndex( min, 0 ), createIndex( min, 0 ) );
332 emit dataChanged( createIndex( max, 0 ), createIndex( max, 0 ) );
334 debug() << "between " << min << " and " << max;
335 m_activeRow = row;
338 void
339 Model::metadataChanged( Meta::Track *track )
341 DEBUG_BLOCK
342 const int size = m_items.size();
343 const Meta::TrackPtr needle = Meta::TrackPtr( track );
344 for( int i = 0; i < size; i++ )
346 if( m_items.at( i )->track() == needle )
348 debug() << "Track in playlist";
349 emit dataChanged( createIndex( i, 0 ), createIndex( i, 0 ) );
350 break;
354 #if 0
355 int index = m_tracks.indexOf( Meta::TrackPtr( track ), 0 );
356 if( index != -1 )
357 emit dataChanged( createIndex( index, 0 ), createIndex( index, 0 ) );
358 #endif
361 void
362 Model::metadataChanged(Meta::Album * album)
364 DEBUG_BLOCK
365 //process each track
366 TrackList tracks = album->tracks();
367 foreach( TrackPtr track, tracks ) {
368 metadataChanged( track.data() );
373 void
374 Model::clear()
376 removeRows( 0, m_items.size() );
377 // m_activeRow = -1;
380 void
381 Model::insertOptioned( Meta::TrackList list, int options )
384 DEBUG_BLOCK
385 if( list.isEmpty() ) {
386 Amarok::StatusBar::instance()->shortMessage( i18n("Attempted to insert nothing into playlist.") );
387 return; // don't add empty items
391 if( options & Unique )
393 int alreadyOnPlaylist = 0;
394 for( int i = 0; i < list.size(); ++i )
397 Item* item;
398 foreach( item, m_items )
400 if( item->track() == list.at( i ) )
402 list.removeAt( i );
403 alreadyOnPlaylist++;
404 break;
409 if ( alreadyOnPlaylist )
410 Amarok::StatusBar::instance()->shortMessage( i18np("One track was already in the playlist, so it was not added.", "%1 tracks were already in the playlist, so they were not added.", alreadyOnPlaylist ) );
413 int orgCount = rowCount(); //needed because recursion messes up counting
414 bool playlistAdded = false;
416 //HACK! Check if any of the incomming tracks is really a playlist. Warning, this can get highly recursive
417 for( int i = 0; i < list.size(); ++i )
419 if ( m_playlistLoader->isPlaylist( list.at( i )->url() ) ) {
420 playlistAdded = true;
421 m_playlistLoader->load( list.takeAt( i )->url() );
426 int firstItemAdded = -1;
427 if( options & Replace )
429 clear();
430 firstItemAdded = 0;
431 insertTracks( 0, list );
433 else if( options & Append )
435 if ( playlistAdded )
436 firstItemAdded = orgCount;
437 else
438 firstItemAdded = rowCount();
439 insertTracks( firstItemAdded, list );
441 else if( options & Queue )
443 //TODO implement queue
445 if( options & DirectPlay )
447 if ( rowCount() > firstItemAdded )
448 play( firstItemAdded );
450 else if( ( options & StartPlay ) && ( EngineController::engine()->state() != Engine::Playing ) && ( rowCount() != 0 ) )
452 play( firstItemAdded );
454 Amarok::actionCollection()->action( "playlist_clear" )->setEnabled( !m_items.isEmpty() );
457 void
458 Model::insertOptioned( Meta::TrackPtr track, int options )
460 Meta::TrackList list;
461 list.append( track );
462 insertOptioned( list, options );
465 void
466 Model::insertOptioned( QueryMaker *qm, int options )
468 qm->startTrackQuery();
469 connect( qm, SIGNAL( queryDone() ), SLOT( queryDone() ) );
470 connect( qm, SIGNAL( newResultReady( QString, Meta::TrackList ) ), SLOT( newResultReady( QString, Meta::TrackList ) ) );
471 m_optionedQueryMap.insert( qm, options );
472 qm->run();
475 void
476 Model::insertMedia( KUrl::List list, int options )
478 KUrl url;
479 Meta::TrackList trackList;
480 foreach( url, list )
482 Meta::TrackPtr track = CollectionManager::instance()->trackForUrl( url );
483 if( track )
484 trackList.push_back( track );
486 if( trackList.isEmpty() )
487 debug() << "Attempted to insert nothing into the playlist!";
489 insertOptioned( trackList, options );
492 bool
493 Model::saveM3U( const QString &path, bool relative ) const
495 Q_UNUSED( path ); Q_UNUSED( relative );
496 // Q3ValueList<KUrl> urls;
497 // Q3ValueList<QString> titles;
498 // Q3ValueList<int> lengths;
499 // for( MyIt it( firstChild(), MyIt::Visible ); *it; ++it )
500 // {
501 // urls << (*it)->url();
502 // titles << (*it)->title();
503 // lengths << (*it)->length();
504 // }
505 // return PlaylistBrowser::savePlaylist( path, urls, titles, lengths, relative );
506 return false;
509 Qt::ItemFlags
510 Model::flags(const QModelIndex &index) const
512 if( index.isValid() )
514 return ( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled |
515 Qt::ItemIsDragEnabled | Qt::ItemIsSelectable );
517 return Qt::ItemIsDropEnabled;
520 QStringList
521 Model::mimeTypes() const //reimplemented
523 QStringList ret = QAbstractListModel::mimeTypes();
524 ret << AmarokMimeData::TRACK_MIME;
525 return ret;
528 QMimeData*
529 Model::mimeData( const QModelIndexList &indexes ) const //reimplemented
531 AmarokMimeData* mime = new AmarokMimeData();
532 Meta::TrackList selectedTracks;
534 foreach( QModelIndex it, indexes )
535 selectedTracks << m_items.at( it.row() )->track();
537 mime->setTracks( selectedTracks );
538 return mime;
541 bool
542 Model::dropMimeData ( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent ) //reimplemented
544 Q_UNUSED( column ); Q_UNUSED( parent );
545 DEBUG_BLOCK
547 if( action == Qt::IgnoreAction )
548 return true;
550 if( data->hasFormat( AmarokMimeData::TRACK_MIME ) )
552 debug() << "Found track mime type";
554 const AmarokMimeData* trackListDrag = dynamic_cast<const AmarokMimeData*>( data );
555 if( trackListDrag )
557 if( row < 0 )
559 debug() << "Inserting at row: " << row << " so we're appending to the list.";
560 insertOptioned( trackListDrag->tracks(), Playlist::Append );
562 else
564 debug() << "Inserting at row: " << row <<" so its inserted correctly.";
565 insertTracks( row, trackListDrag->tracks() );
567 return true;
570 else if( data->hasUrls() )
572 //probably a drop from an external source
573 debug() << "Drop from external source";
574 QList<QUrl> urls = data->urls();
575 Meta::TrackList tracks;
576 foreach( QUrl url, urls )
578 Meta::TrackPtr track = CollectionManager::instance()->trackForUrl( KUrl( url ) );
579 if( track )
580 tracks.append( track );
582 if( !tracks.isEmpty() )
583 insertOptioned( tracks, Playlist::Append );
584 return true;
586 return false;
589 ////////////
590 //Private Methods
591 ///////////
594 void
595 Model::insertTracksCommand( int row, TrackList list )
597 DEBUG_BLOCK
598 debug() << "inserting... " << row << ' ' << list.count();
599 if( !list.size() )
600 return;
602 beginInsertRows( QModelIndex(), row, row + list.size() - 1 );
603 int i = 0;
604 foreach( TrackPtr track , list )
606 if( track )
608 track->subscribe( this );
609 if( track->album() )
610 track->album()->subscribe( this );
611 m_items.insert( row + i, new Item( track ) );
612 i++;
615 endInsertRows();
616 //push up the active row if needed
617 if( m_activeRow > row )
619 int oldActiveRow = m_activeRow;
620 m_activeRow += list.size();
621 Q_UNUSED( oldActiveRow );
622 //dataChanged( createIndex( oldActiveRow, 0 ), createIndex( oldActiveRow, columnCount() -1 ) );
623 //dataChanged( createIndex( m_activeRow, 0 ), createIndex( m_activeRow, columnCount() -1 ) );
625 dataChanged( createIndex( row, 0 ), createIndex( rowCount() - 1, 0 ) );
626 Amarok::actionCollection()->action( "playlist_clear" )->setEnabled( !m_items.isEmpty() );
628 emit playlistCountChanged( rowCount() );
632 TrackList
633 Model::removeRowsCommand( int position, int rows )
635 DEBUG_BLOCK
637 beginRemoveRows( QModelIndex(), position, position + rows - 1 );
638 // TrackList::iterator start = m_tracks.begin() + position;
639 // TrackList::iterator end = start + rows;
640 // m_tracks.erase( start, end );
641 TrackList ret;
642 for( int i = position; i < position + rows; i++ )
644 Item* item = m_items.takeAt( position ); //take at position, row times
645 item->track()->unsubscribe( this );
646 if( item->track()->album() )
647 item->track()->album()->unsubscribe( this );
648 ret.push_back( item->track() );
649 delete item;
651 endRemoveRows();
653 Amarok::actionCollection()->action( "playlist_clear" )->setEnabled( !m_items.isEmpty() );
655 //update m_activeRow
656 bool activeRowChanged = true;
657 bool oldActiveRow = m_activeRow;
658 if( m_activeRow >= position && m_activeRow < ( position + rows ) )
659 m_activeRow = -1;
660 else if( m_activeRow >= position )
661 m_activeRow = m_activeRow - position;
662 else
663 activeRowChanged = false;
664 if( activeRowChanged )
666 dataChanged( createIndex( oldActiveRow, 0 ), createIndex( oldActiveRow, columnCount() -1 ) );
667 dataChanged( createIndex( m_activeRow, 0 ), createIndex( m_activeRow, columnCount() -1 ) );
669 dataChanged( createIndex( position, 0 ), createIndex( rowCount(), 0 ) );
670 emit playlistCountChanged( rowCount() );
671 return ret;
674 void
675 Model::queryDone() //Slot
677 QueryMaker *qm = dynamic_cast<QueryMaker*>( sender() );
678 if( qm )
680 m_queryMap.remove( qm );
681 qm->deleteLater();
685 void
686 Model::newResultReady( const QString &collectionId, const Meta::TrackList &tracks ) //Slot
688 Q_UNUSED( collectionId )
689 QueryMaker *qm = dynamic_cast<QueryMaker*>( sender() );
690 if( qm )
692 //requires better handling of queries which return multiple results
693 if( m_queryMap.contains( qm ) )
694 insertTracks( m_queryMap.value( qm ), tracks );
695 else if( m_optionedQueryMap.contains( qm ) )
696 insertOptioned( tracks, m_optionedQueryMap.value( qm ) );
700 QVariant Model::headerData(int section, Qt::Orientation orientation, int role) const
703 Q_UNUSED( orientation );
705 if ( role != Qt::DisplayRole )
706 return QVariant();
708 switch ( section )
710 case 0:
711 return "title";
712 case 1:
713 return "album";
714 case 2:
715 return "artist";
716 default:
717 return QVariant();
723 namespace The {
724 Playlist::Model* playlistModel() { return Playlist::Model::s_instance; }
732 #include "PlaylistModel.moc"