make PlaylistModel observe albums for changes to the cover image (needed for services...
[amarok.git] / src / playlist / PlaylistModel.cpp
blob0b9d05e6fead09b7c9b1b9fd3f4698e4b9362515
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 "StandardTrackAdvancer.h"
17 #include "statusbar.h"
18 #include "TheInstances.h"
19 #include "UndoCommands.h"
21 #include "collection/BlockingQuery.h"
22 #include "collection/Collection.h"
23 #include "collection/CollectionManager.h"
24 #include "collection/QueryMaker.h"
25 #include "meta/lastfm/LastFmMeta.h"
27 #include <QAction>
28 #include <QStringList>
29 #include <QUndoStack>
31 #include <KIcon>
32 #include <KUrl>
34 using namespace PlaylistNS;
35 using namespace Meta;
37 Model *Model::s_instance = 0;
39 Model::Model( QObject* parent )
40 : QAbstractListModel( parent )
41 , m_activeRow( -1 )
42 , m_advancer( new StandardTrackAdvancer( this ) )
43 , m_undoStack( new QUndoStack( this ) )
45 connect( EngineController::instance(), SIGNAL( trackFinished() ), this, SLOT( trackFinished() ) );
46 s_instance = this;
49 void
50 Model::init()
52 KActionCollection* ac = Amarok::actionCollection();
53 QAction* undoButton = m_undoStack->createUndoAction( this, i18n("Undo") );
54 undoButton->setIcon( KIcon( Amarok::icon( "undo" ) ) );
55 ac->addAction("playlist_undo", undoButton);
56 QAction* redoButton = m_undoStack->createRedoAction( this, i18n("Redo") );
57 ac->addAction("playlist_redo", redoButton);
58 redoButton->setIcon( KIcon( Amarok::icon( "redo" ) ) );
61 Model::~Model()
63 delete m_advancer;
66 int
67 Model::rowCount( const QModelIndex& ) const
69 return m_items.size();
72 QVariant
73 Model::data( const QModelIndex& index, int role ) const
75 int row = index.row();
76 /*if( ( role == Qt::FontRole) && ( row == m_activeRow ) )
78 QFont original;
79 original.setBold( true );
80 return original;
82 else*/
83 if( role == ItemRole && ( row != -1 ) )
85 return QVariant::fromValue( m_items.at( row ) );
87 else if( role == ActiveTrackRole )
88 return ( row == m_activeRow );
89 else if( role == TrackRole && ( row != -1 ) && m_items.at( row )->track() )
91 return QVariant::fromValue( m_items.at( row )->track() );
93 else if( role == Qt::DisplayRole && row != -1 )
95 return m_items.at( row )->track()->name();
97 else
99 return QVariant();
101 /* switch( role )
103 case AlbumArtist: return track->album()->albumArtist()->name();
104 case Album: return track->album()->name();
105 case Artist: return track->artist()->name();
106 case Bitrate: return track->bitrate();
107 case Composer: return track->composer()->name();
108 case CoverImage: return track->album()->image( 50 );
109 case Comment: return track->comment();
110 case DiscNumber: return track->discNumber();
111 case Filesize: return track->filesize();
112 case Genre: return track->genre()->name();
113 case Length: return track->length();
114 case Rating: return track->rating();
115 case Score: return track->score();
116 case Title: return track->name();
117 case TrackNumber: return track->trackNumber();
118 case Year: return track->year()->name().toInt();
119 default: return QVariant();
120 } */
123 // void
124 // insertColumns( int column, Column type )
125 // {
126 // beginInsertColumns( QModelIndex(), column, column + 1 );
127 // m_columns.insert( column, type );
128 // endInsertColumns();
129 // return true;
130 // }
132 void
133 Model::insertTrack( int row, TrackPtr track )
135 DEBUG_BLOCK
136 TrackList list;
137 list.append( track );
138 insertTracks( row, list );
142 void
143 Model::insertTracks( int row, TrackList tracks )
145 m_undoStack->push( new AddTracksCmd( 0, row, tracks ) );
148 bool
149 Model::removeRows( int position, int rows, const QModelIndex& /*parent*/ )
151 m_undoStack->push( new RemoveTracksCmd( 0, position, rows ) );
152 return true;
156 void
157 Model::insertTracks( int row, QueryMaker *qm )
159 qm->startTrackQuery();
160 connect( qm, SIGNAL( queryDone() ), SLOT( queryDone() ) );
161 connect( qm, SIGNAL( newResultReady( QString, Meta::TrackList ) ), SLOT( newResultReady( QString, Meta::TrackList ) ) );
162 m_queryMap.insert( qm, row );
163 qm->run();
166 void
167 Model::testData()
169 DEBUG_BLOCK
170 Collection *local = 0;
171 foreach( Collection *coll, CollectionManager::instance()->collections() )
173 if( coll->collectionId() == "localCollection" )
174 local = coll;
176 if( !local )
177 return;
178 QueryMaker *qm = local->queryMaker();
179 qm->startTrackQuery();
180 qm->limitMaxResultSize( 10 );
181 BlockingQuery bq( qm );
182 bq.startQuery();
183 insertTracks( 0, bq.tracks( "localCollection" ) );
184 reset();
187 Qt::DropActions
188 Model::supportedDropActions() const
190 return Qt::CopyAction | Qt::MoveAction;
193 void
194 Model::trackFinished()
196 Meta::TrackPtr track = m_items.at( m_activeRow )->track();
197 track->finishedPlaying( 1.0 ); //TODO: get correct value for parameter
198 m_advancer->advanceTrack();
201 void
202 Model::play( const QModelIndex& index )
204 play( index.row() );
207 void
208 Model::play( int row )
210 setActiveRow( row );
211 EngineController::instance()->play( m_items[ m_activeRow ]->track() );
214 QString
215 Model::prettyColumnName( Column index ) //static
217 switch( index )
219 case Filename: return i18n( "Filename" );
220 case Title: return i18n( "Title" );
221 case Artist: return i18n( "Artist" );
222 case AlbumArtist:return i18n( "Album Artist");
223 case Composer: return i18n( "Composer" );
224 case Year: return i18n( "Year" );
225 case Album: return i18n( "Album" );
226 case DiscNumber: return i18n( "Disc Number" );
227 case TrackNumber:return i18n( "Track" );
228 case Bpm: return i18n( "BPM" );
229 case Genre: return i18n( "Genre" );
230 case Comment: return i18n( "Comment" );
231 case Directory: return i18n( "Directory" );
232 case Type: return i18n( "Type" );
233 case Length: return i18n( "Length" );
234 case Bitrate: return i18n( "Bitrate" );
235 case SampleRate: return i18n( "Sample Rate" );
236 case Score: return i18n( "Score" );
237 case Rating: return i18n( "Rating" );
238 case PlayCount: return i18n( "Play Count" );
239 case LastPlayed: return i18nc( "Column name", "Last Played" );
240 case Mood: return i18n( "Mood" );
241 case Filesize: return i18n( "File Size" );
242 default: return "This is a bug.";
248 void
249 Model::setActiveRow( int row )
251 DEBUG_BLOCK
253 int max = qMax( row, m_activeRow );
254 int min = qMin( row, m_activeRow );
255 if( ( max - min ) == 1 )
256 emit dataChanged( createIndex( min, 0 ), createIndex( max, 0 ) );
257 else
259 emit dataChanged( createIndex( min, 0 ), createIndex( min, 0 ) );
260 emit dataChanged( createIndex( max, 0 ), createIndex( max, 0 ) );
262 debug() << "between " << min << " and " << max;
263 m_activeRow = row;
266 void
267 Model::metadataChanged( Meta::Track *track )
269 DEBUG_BLOCK
270 const int size = m_items.size();
271 const Meta::TrackPtr needle = Meta::TrackPtr( track );
272 for( int i = 0; i < size; i++ )
274 if( m_items.at( i )->track() == needle )
276 debug() << "Track in playlist";
277 emit dataChanged( createIndex( i, 0 ), createIndex( i, 0 ) );
278 break;
282 #if 0
283 int index = m_tracks.indexOf( Meta::TrackPtr( track ), 0 );
284 if( index != -1 )
285 emit dataChanged( createIndex( index, 0 ), createIndex( index, 0 ) );
286 #endif
289 void
290 Model::metadataChanged(Meta::Album * album)
292 DEBUG_BLOCK
293 //process each track
294 TrackList tracks = album->tracks();
295 foreach( TrackPtr track, tracks ) {
296 metadataChanged( track.data() );
301 void
302 Model::clear()
304 removeRows( 0, m_items.size() );
305 // m_activeRow = -1;
308 void
309 Model::insertOptioned( Meta::TrackList list, int options )
312 if( list.isEmpty() ) {
313 Amarok::StatusBar::instance()->shortMessage( i18n("Attempted to insert nothing into playlist.") );
314 return; // don't add empty items
317 if( Unique )
319 int alreadyOnPlaylist = 0;
320 for( int i = 0; i < list.size(); ++i )
322 Item* item;
323 foreach( item, m_items )
325 if( item->track() == list.at( i ) )
327 list.removeAt( i );
328 alreadyOnPlaylist++;
329 break;
333 if ( alreadyOnPlaylist )
334 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 ) );
336 int firstItemAdded = -1;
337 if( options & Replace )
339 clear();
340 firstItemAdded = 0;
341 insertTracks( 0, list );
343 else if( options & Append )
345 firstItemAdded = rowCount();
346 insertTracks( firstItemAdded, list );
348 else if( options & Queue )
350 //TODO implement queue
352 if( options & DirectPlay )
354 play( firstItemAdded );
356 else if( ( options & StartPlay ) && ( EngineController::engine()->state() != Engine::Playing ) )
358 play( firstItemAdded );
360 Amarok::actionCollection()->action( "playlist_clear" )->setEnabled( !m_items.isEmpty() );
363 void
364 Model::insertOptioned( Meta::TrackPtr track, int options )
366 Meta::TrackList list;
367 list.append( track );
368 insertOptioned( list, options );
371 void
372 Model::insertOptioned( QueryMaker *qm, int options )
374 qm->startTrackQuery();
375 connect( qm, SIGNAL( queryDone() ), SLOT( queryDone() ) );
376 connect( qm, SIGNAL( newResultReady( QString, Meta::TrackList ) ), SLOT( newResultReady( QString, Meta::TrackList ) ) );
377 m_optionedQueryMap.insert( qm, options );
378 qm->run();
381 void
382 Model::insertMedia( KUrl::List list, int options )
384 KUrl url;
385 Meta::TrackList trackList;
386 foreach( url, list )
388 Meta::TrackPtr track = CollectionManager::instance()->trackForUrl( url );
389 if( track )
390 trackList.push_back( track );
392 insertOptioned( trackList, options );
395 bool
396 Model::saveM3U( const QString &path, bool relative ) const
398 Q_UNUSED( path ); Q_UNUSED( relative );
399 // Q3ValueList<KUrl> urls;
400 // Q3ValueList<QString> titles;
401 // Q3ValueList<int> lengths;
402 // for( MyIt it( firstChild(), MyIt::Visible ); *it; ++it )
403 // {
404 // urls << (*it)->url();
405 // titles << (*it)->title();
406 // lengths << (*it)->length();
407 // }
408 // return PlaylistBrowser::savePlaylist( path, urls, titles, lengths, relative );
409 return false;
412 Qt::ItemFlags
413 Model::flags(const QModelIndex &index) const
415 Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
416 return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled | defaultFlags;
419 QStringList
420 Model::mimeTypes() const //reimplemented
422 QStringList ret = QAbstractListModel::mimeTypes();
423 ret << AmarokMimeData::TRACK_MIME;
424 debug() << ret;
425 return ret;
428 QMimeData*
429 Model::mimeData( const QModelIndexList &indexes ) const //reimplemented
431 AmarokMimeData* mime = new AmarokMimeData();
432 Meta::TrackList selectedTracks;
434 foreach( QModelIndex it, indexes )
435 selectedTracks << m_items.at( it.row() )->track();
437 mime->setTracks( selectedTracks );
438 return mime;
441 bool
442 Model::dropMimeData ( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent ) //reimplemented
444 Q_UNUSED( column ); Q_UNUSED( parent );
445 DEBUG_BLOCK
447 if( action == Qt::IgnoreAction )
448 return true;
450 if( data->hasFormat( AmarokMimeData::TRACK_MIME ) )
452 debug() << "Found track mime type";
454 const AmarokMimeData* trackListDrag = dynamic_cast<const AmarokMimeData*>( data );
455 if( trackListDrag )
457 debug() << "It's a list drag!";
458 if( row < 0 )
460 debug() << "Inserting at row: " << row << " so we're appending to the list.";
461 insertOptioned( trackListDrag->tracks(), PlaylistNS::Append );
463 else
465 debug() << "Inserting at row: " << row <<" so its inserted correctly.";
466 insertTracks( row, trackListDrag->tracks() );
468 return true;
471 else if( data->hasUrls() )
473 //probably a drop from an external source
474 debug() << "Drop from external source";
475 QList<QUrl> urls = data->urls();
476 Meta::TrackList tracks;
477 foreach( QUrl url, urls )
479 Meta::TrackPtr track = CollectionManager::instance()->trackForUrl( KUrl( url ) );
480 if( track )
481 tracks.append( track );
483 if( !tracks.isEmpty() )
484 insertOptioned( tracks, PlaylistNS::Append );
485 return true;
487 return false;
490 ////////////
491 //Private Methods
492 ///////////
495 void
496 Model::insertTracksCommand( int row, TrackList list )
498 DEBUG_BLOCK
499 debug() << "inserting... " << row << ' ' << list.count();
500 if( !list.size() )
501 return;
503 beginInsertRows( QModelIndex(), row, row + list.size() - 1 );
504 int i = 0;
505 foreach( TrackPtr track , list )
507 if( track )
509 track->subscribe( this );
510 track->album()->subscribe( this );
511 m_items.insert( row + i, new Item( track ) );
512 i++;
515 endInsertRows();
516 //push up the active row if needed
517 if( m_activeRow > row )
519 int oldActiveRow = m_activeRow;
520 m_activeRow += list.size();
521 Q_UNUSED( oldActiveRow );
522 //dataChanged( createIndex( oldActiveRow, 0 ), createIndex( oldActiveRow, columnCount() -1 ) );
523 //dataChanged( createIndex( m_activeRow, 0 ), createIndex( m_activeRow, columnCount() -1 ) );
525 dataChanged( createIndex( row, 0 ), createIndex( rowCount() - 1, 0 ) );
526 Amarok::actionCollection()->action( "playlist_clear" )->setEnabled( !m_items.isEmpty() );
530 TrackList
531 Model::removeRowsCommand( int position, int rows )
533 DEBUG_BLOCK
535 beginRemoveRows( QModelIndex(), position, position + rows - 1 );
536 // TrackList::iterator start = m_tracks.begin() + position;
537 // TrackList::iterator end = start + rows;
538 // m_tracks.erase( start, end );
539 TrackList ret;
540 for( int i = position; i < position + rows; i++ )
542 Item* item = m_items.takeAt( position ); //take at position, row times
543 item->track()->unsubscribe( this );
544 ret.push_back( item->track() );
545 delete item;
547 endRemoveRows();
549 Amarok::actionCollection()->action( "playlist_clear" )->setEnabled( !m_items.isEmpty() );
551 //update m_activeRow
552 bool activeRowChanged = true;
553 bool oldActiveRow = m_activeRow;
554 if( m_activeRow >= position && m_activeRow < ( position + rows ) )
555 m_activeRow = -1;
556 else if( m_activeRow >= position )
557 m_activeRow = m_activeRow - position;
558 else
559 activeRowChanged = false;
560 if( activeRowChanged )
562 dataChanged( createIndex( oldActiveRow, 0 ), createIndex( oldActiveRow, columnCount() -1 ) );
563 dataChanged( createIndex( m_activeRow, 0 ), createIndex( m_activeRow, columnCount() -1 ) );
565 dataChanged( createIndex( position, 0 ), createIndex( rowCount(), 0 ) );
566 return ret;
569 void
570 Model::queryDone() //Slot
572 QueryMaker *qm = dynamic_cast<QueryMaker*>( sender() );
573 if( qm )
575 m_queryMap.remove( qm );
576 qm->deleteLater();
580 void
581 Model::newResultReady( const QString &collectionId, const Meta::TrackList &tracks ) //Slot
583 Q_UNUSED( collectionId )
584 QueryMaker *qm = dynamic_cast<QueryMaker*>( sender() );
585 if( qm )
587 //requires better handling of queries which return multiple results
588 if( m_queryMap.contains( qm ) )
589 insertTracks( m_queryMap.value( qm ), tracks );
590 else if( m_optionedQueryMap.contains( qm ) )
591 insertOptioned( tracks, m_optionedQueryMap.value( qm ) );
595 namespace The {
596 PlaylistNS::Model* playlistModel() { return PlaylistNS::Model::s_instance; }
601 #include "PlaylistModel.moc"