1 /***************************************************************************
2 * copyright : (C) 2007 Ian Monroe <ian@monroe.nu> *
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"
12 #include "AmarokMimeData.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"
29 #include <QStringList>
35 using namespace Playlist
;
38 Model
*Model::s_instance
= 0;
40 Model::Model( QObject
* parent
)
41 : QAbstractListModel( parent
)
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() ) );
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" ) ) );
67 delete m_playlistLoader
;
71 Model::rowCount( const QModelIndex
& ) const
73 return m_items
.size();
77 Model::data( const QModelIndex
& index
, int role
) const
79 int row
= index
.row();
80 /*if( ( role == Qt::FontRole) && ( row == m_activeRow ) )
83 original.setBold( true );
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 return m_items
.at( row
)->track()->name();
107 case AlbumArtist: return track->album()->albumArtist()->name();
108 case Album: return track->album()->name();
109 case Artist: return track->artist()->name();
110 case Bitrate: return track->bitrate();
111 case Composer: return track->composer()->name();
112 case CoverImage: return track->album()->image( 50 );
113 case Comment: return track->comment();
114 case DiscNumber: return track->discNumber();
115 case Filesize: return track->filesize();
116 case Genre: return track->genre()->name();
117 case Length: return track->length();
118 case Rating: return track->rating();
119 case Score: return track->score();
120 case Title: return track->name();
121 case TrackNumber: return track->trackNumber();
122 case Year: return track->year()->name().toInt();
123 default: return QVariant();
128 // insertColumns( int column, Column type )
130 // beginInsertColumns( QModelIndex(), column, column + 1 );
131 // m_columns.insert( column, type );
132 // endInsertColumns();
137 Model::insertTrack( int row
, TrackPtr track
)
141 list
.append( track
);
142 insertTracks( row
, list
);
147 Model::insertTracks( int row
, TrackList tracks
)
149 m_undoStack
->push( new AddTracksCmd( 0, row
, tracks
) );
153 Model::removeRows( int position
, int rows
, const QModelIndex
& /*parent*/ )
155 m_undoStack
->push( new RemoveTracksCmd( 0, position
, rows
) );
161 Model::insertTracks( int row
, QueryMaker
*qm
)
163 qm
->startTrackQuery();
164 connect( qm
, SIGNAL( queryDone() ), SLOT( queryDone() ) );
165 connect( qm
, SIGNAL( newResultReady( QString
, Meta::TrackList
) ), SLOT( newResultReady( QString
, Meta::TrackList
) ) );
166 m_queryMap
.insert( qm
, row
);
174 Collection
*local
= 0;
175 foreach( Collection
*coll
, CollectionManager::instance()->collections() )
177 if( coll
->collectionId() == "localCollection" )
182 QueryMaker
*qm
= local
->queryMaker();
183 qm
->startTrackQuery();
184 qm
->limitMaxResultSize( 10 );
185 BlockingQuery
bq( qm
);
187 insertTracks( 0, bq
.tracks( "localCollection" ) );
192 Model::supportedDropActions() const
194 return Qt::CopyAction
| Qt::MoveAction
;
198 Model::trackFinished()
200 if( m_activeRow
< 0 || m_activeRow
>= m_items
.size() )
202 Meta::TrackPtr track
= m_items
.at( m_activeRow
)->track();
203 track
->finishedPlaying( 1.0 ); //TODO: get correct value for parameter
204 m_advancer
->advanceTrack();
208 Model::play( const QModelIndex
& index
)
214 Model::play( int row
)
217 EngineController::instance()->play( m_items
[ m_activeRow
]->track() );
221 Model::playlistRepeatMode( int item
)
224 playModeChanged( Playlist::Standard
);
225 else //for now just turn on repeat if anything but "off" is clicked
226 playModeChanged( Playlist::Repeat
);
232 if( m_activeRow
< 0 || m_activeRow
>= m_items
.size() )
234 Meta::TrackPtr track
= m_items
.at( m_activeRow
)->track();
235 track
->finishedPlaying( 0.5 ); //TODO: get correct value for parameter
236 m_advancer
->userAdvanceTrack();
242 if( m_activeRow
< 0 || m_activeRow
>= m_items
.size() )
244 Meta::TrackPtr track
= m_items
.at( m_activeRow
)->track();
245 track
->finishedPlaying( 0.5 ); //TODO: get correct value for parameter
246 m_advancer
->recedeTrack();
250 Model::playCurrentTrack()
252 int selected
= m_activeRow
;
253 if( selected
< 0 || selected
>= m_items
.size() )
255 //play first track if there are tracks in the playlist
266 Model::prettyColumnName( Column index
) //static
270 case Filename
: return i18n( "Filename" );
271 case Title
: return i18n( "Title" );
272 case Artist
: return i18n( "Artist" );
273 case AlbumArtist
:return i18n( "Album Artist");
274 case Composer
: return i18n( "Composer" );
275 case Year
: return i18n( "Year" );
276 case Album
: return i18n( "Album" );
277 case DiscNumber
: return i18n( "Disc Number" );
278 case TrackNumber
:return i18n( "Track" );
279 case Bpm
: return i18n( "BPM" );
280 case Genre
: return i18n( "Genre" );
281 case Comment
: return i18n( "Comment" );
282 case Directory
: return i18n( "Directory" );
283 case Type
: return i18n( "Type" );
284 case Length
: return i18n( "Length" );
285 case Bitrate
: return i18n( "Bitrate" );
286 case SampleRate
: return i18n( "Sample Rate" );
287 case Score
: return i18n( "Score" );
288 case Rating
: return i18n( "Rating" );
289 case PlayCount
: return i18n( "Play Count" );
290 case LastPlayed
: return i18nc( "Column name", "Last Played" );
291 case Mood
: return i18n( "Mood" );
292 case Filesize
: return i18n( "File Size" );
293 default: return "This is a bug.";
299 Model::playModeChanged( int row
)
304 case Playlist::Standard
:
305 m_advancer
= new StandardTrackNavigator(this);
307 case Playlist::Repeat
:
308 m_advancer
= new RepeatTrackNavigator(this);
314 Model::setActiveRow( int row
)
318 int max
= qMax( row
, m_activeRow
);
319 int min
= qMin( row
, m_activeRow
);
320 if( ( max
- min
) == 1 )
321 emit
dataChanged( createIndex( min
, 0 ), createIndex( max
, 0 ) );
324 emit
dataChanged( createIndex( min
, 0 ), createIndex( min
, 0 ) );
325 emit
dataChanged( createIndex( max
, 0 ), createIndex( max
, 0 ) );
327 debug() << "between " << min
<< " and " << max
;
332 Model::metadataChanged( Meta::Track
*track
)
335 const int size
= m_items
.size();
336 const Meta::TrackPtr needle
= Meta::TrackPtr( track
);
337 for( int i
= 0; i
< size
; i
++ )
339 if( m_items
.at( i
)->track() == needle
)
341 debug() << "Track in playlist";
342 emit
dataChanged( createIndex( i
, 0 ), createIndex( i
, 0 ) );
348 int index
= m_tracks
.indexOf( Meta::TrackPtr( track
), 0 );
350 emit
dataChanged( createIndex( index
, 0 ), createIndex( index
, 0 ) );
355 Model::metadataChanged(Meta::Album
* album
)
359 TrackList tracks
= album
->tracks();
360 foreach( TrackPtr track
, tracks
) {
361 metadataChanged( track
.data() );
369 removeRows( 0, m_items
.size() );
374 Model::insertOptioned( Meta::TrackList list
, int options
)
377 if( list
.isEmpty() ) {
378 Amarok::StatusBar::instance()->shortMessage( i18n("Attempted to insert nothing into playlist.") );
379 return; // don't add empty items
383 if( options
& Unique
)
385 int alreadyOnPlaylist
= 0;
386 for( int i
= 0; i
< list
.size(); ++i
)
390 foreach( item
, m_items
)
392 if( item
->track() == list
.at( i
) )
401 if ( alreadyOnPlaylist
)
402 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
) );
405 int orgCount
= rowCount(); //needed because recursion messes up counting
406 bool playlistAdded
= false;
408 //HACK! Check if any of the incomming tracks is really a playlist. Warning, this can get highly recursive
409 for( int i
= 0; i
< list
.size(); ++i
)
411 if ( m_playlistLoader
->isPlaylist( list
.at( i
)->url() ) ) {
412 playlistAdded
= true;
413 m_playlistLoader
->load( list
.takeAt( i
)->url() );
418 int firstItemAdded
= -1;
419 if( options
& Replace
)
423 insertTracks( 0, list
);
425 else if( options
& Append
)
428 firstItemAdded
= orgCount
;
430 firstItemAdded
= rowCount();
432 if ( rowCount() > firstItemAdded
)
433 insertTracks( firstItemAdded
, list
);
435 else if( options
& Queue
)
437 //TODO implement queue
439 if( options
& DirectPlay
)
441 if ( rowCount() > firstItemAdded
)
442 play( firstItemAdded
);
444 else if( ( options
& StartPlay
) && ( EngineController::engine()->state() != Engine::Playing
) && ( rowCount() != 0 ) )
446 play( firstItemAdded
);
448 Amarok::actionCollection()->action( "playlist_clear" )->setEnabled( !m_items
.isEmpty() );
452 Model::insertOptioned( Meta::TrackPtr track
, int options
)
454 Meta::TrackList list
;
455 list
.append( track
);
456 insertOptioned( list
, options
);
460 Model::insertOptioned( QueryMaker
*qm
, int options
)
462 qm
->startTrackQuery();
463 connect( qm
, SIGNAL( queryDone() ), SLOT( queryDone() ) );
464 connect( qm
, SIGNAL( newResultReady( QString
, Meta::TrackList
) ), SLOT( newResultReady( QString
, Meta::TrackList
) ) );
465 m_optionedQueryMap
.insert( qm
, options
);
470 Model::insertMedia( KUrl::List list
, int options
)
473 Meta::TrackList trackList
;
476 Meta::TrackPtr track
= CollectionManager::instance()->trackForUrl( url
);
478 trackList
.push_back( track
);
480 if( trackList
.isEmpty() )
481 debug() << "Attempted to insert nothing into the playlist!";
483 insertOptioned( trackList
, options
);
487 Model::saveM3U( const QString
&path
, bool relative
) const
489 Q_UNUSED( path
); Q_UNUSED( relative
);
490 // Q3ValueList<KUrl> urls;
491 // Q3ValueList<QString> titles;
492 // Q3ValueList<int> lengths;
493 // for( MyIt it( firstChild(), MyIt::Visible ); *it; ++it )
495 // urls << (*it)->url();
496 // titles << (*it)->title();
497 // lengths << (*it)->length();
499 // return PlaylistBrowser::savePlaylist( path, urls, titles, lengths, relative );
504 Model::flags(const QModelIndex
&index
) const
506 if( index
.isValid() )
508 return ( Qt::ItemIsEnabled
| Qt::ItemIsSelectable
| Qt::ItemIsDropEnabled
|
509 Qt::ItemIsDragEnabled
| Qt::ItemIsSelectable
);
511 return Qt::ItemIsDropEnabled
;
515 Model::mimeTypes() const //reimplemented
517 QStringList ret
= QAbstractListModel::mimeTypes();
518 ret
<< AmarokMimeData::TRACK_MIME
;
523 Model::mimeData( const QModelIndexList
&indexes
) const //reimplemented
525 AmarokMimeData
* mime
= new AmarokMimeData();
526 Meta::TrackList selectedTracks
;
528 foreach( QModelIndex it
, indexes
)
529 selectedTracks
<< m_items
.at( it
.row() )->track();
531 mime
->setTracks( selectedTracks
);
536 Model::dropMimeData ( const QMimeData
* data
, Qt::DropAction action
, int row
, int column
, const QModelIndex
& parent
) //reimplemented
538 Q_UNUSED( column
); Q_UNUSED( parent
);
541 if( action
== Qt::IgnoreAction
)
544 if( data
->hasFormat( AmarokMimeData::TRACK_MIME
) )
546 debug() << "Found track mime type";
548 const AmarokMimeData
* trackListDrag
= dynamic_cast<const AmarokMimeData
*>( data
);
553 debug() << "Inserting at row: " << row
<< " so we're appending to the list.";
554 insertOptioned( trackListDrag
->tracks(), Playlist::Append
);
558 debug() << "Inserting at row: " << row
<<" so its inserted correctly.";
559 insertTracks( row
, trackListDrag
->tracks() );
564 else if( data
->hasUrls() )
566 //probably a drop from an external source
567 debug() << "Drop from external source";
568 QList
<QUrl
> urls
= data
->urls();
569 Meta::TrackList tracks
;
570 foreach( QUrl url
, urls
)
572 Meta::TrackPtr track
= CollectionManager::instance()->trackForUrl( KUrl( url
) );
574 tracks
.append( track
);
576 if( !tracks
.isEmpty() )
577 insertOptioned( tracks
, Playlist::Append
);
589 Model::insertTracksCommand( int row
, TrackList list
)
592 debug() << "inserting... " << row
<< ' ' << list
.count();
596 beginInsertRows( QModelIndex(), row
, row
+ list
.size() - 1 );
598 foreach( TrackPtr track
, list
)
602 track
->subscribe( this );
604 track
->album()->subscribe( this );
605 m_items
.insert( row
+ i
, new Item( track
) );
610 //push up the active row if needed
611 if( m_activeRow
> row
)
613 int oldActiveRow
= m_activeRow
;
614 m_activeRow
+= list
.size();
615 Q_UNUSED( oldActiveRow
);
616 //dataChanged( createIndex( oldActiveRow, 0 ), createIndex( oldActiveRow, columnCount() -1 ) );
617 //dataChanged( createIndex( m_activeRow, 0 ), createIndex( m_activeRow, columnCount() -1 ) );
619 dataChanged( createIndex( row
, 0 ), createIndex( rowCount() - 1, 0 ) );
620 Amarok::actionCollection()->action( "playlist_clear" )->setEnabled( !m_items
.isEmpty() );
622 emit
playlistCountChanged( rowCount() );
627 Model::removeRowsCommand( int position
, int rows
)
631 beginRemoveRows( QModelIndex(), position
, position
+ rows
- 1 );
632 // TrackList::iterator start = m_tracks.begin() + position;
633 // TrackList::iterator end = start + rows;
634 // m_tracks.erase( start, end );
636 for( int i
= position
; i
< position
+ rows
; i
++ )
638 Item
* item
= m_items
.takeAt( position
); //take at position, row times
639 item
->track()->unsubscribe( this );
640 if( item
->track()->album() )
641 item
->track()->album()->unsubscribe( this );
642 ret
.push_back( item
->track() );
647 Amarok::actionCollection()->action( "playlist_clear" )->setEnabled( !m_items
.isEmpty() );
650 bool activeRowChanged
= true;
651 bool oldActiveRow
= m_activeRow
;
652 if( m_activeRow
>= position
&& m_activeRow
< ( position
+ rows
) )
654 else if( m_activeRow
>= position
)
655 m_activeRow
= m_activeRow
- position
;
657 activeRowChanged
= false;
658 if( activeRowChanged
)
660 dataChanged( createIndex( oldActiveRow
, 0 ), createIndex( oldActiveRow
, columnCount() -1 ) );
661 dataChanged( createIndex( m_activeRow
, 0 ), createIndex( m_activeRow
, columnCount() -1 ) );
663 dataChanged( createIndex( position
, 0 ), createIndex( rowCount(), 0 ) );
664 emit
playlistCountChanged( rowCount() );
669 Model::queryDone() //Slot
671 QueryMaker
*qm
= dynamic_cast<QueryMaker
*>( sender() );
674 m_queryMap
.remove( qm
);
680 Model::newResultReady( const QString
&collectionId
, const Meta::TrackList
&tracks
) //Slot
682 Q_UNUSED( collectionId
)
683 QueryMaker
*qm
= dynamic_cast<QueryMaker
*>( sender() );
686 //requires better handling of queries which return multiple results
687 if( m_queryMap
.contains( qm
) )
688 insertTracks( m_queryMap
.value( qm
), tracks
);
689 else if( m_optionedQueryMap
.contains( qm
) )
690 insertOptioned( tracks
, m_optionedQueryMap
.value( qm
) );
695 Playlist::Model
* playlistModel() { return Playlist::Model::s_instance
; }
701 #include "PlaylistModel.moc"