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 "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"
28 #include <QStringList>
34 using namespace PlaylistNS
;
37 Model
*Model::s_instance
= 0;
39 Model::Model( QObject
* parent
)
40 : QAbstractListModel( parent
)
42 , m_advancer( new StandardTrackAdvancer( this ) )
43 , m_undoStack( new QUndoStack( this ) )
45 connect( EngineController::instance(), SIGNAL( trackFinished() ), this, SLOT( trackFinished() ) );
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" ) ) );
67 Model::rowCount( const QModelIndex
& ) const
69 return m_items
.size();
73 Model::data( const QModelIndex
& index
, int role
) const
75 int row
= index
.row();
76 /*if( ( role == Qt::FontRole) && ( row == m_activeRow ) )
79 original.setBold( true );
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();
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();
124 // insertColumns( int column, Column type )
126 // beginInsertColumns( QModelIndex(), column, column + 1 );
127 // m_columns.insert( column, type );
128 // endInsertColumns();
133 Model::insertTrack( int row
, TrackPtr track
)
137 list
.append( track
);
138 insertTracks( row
, list
);
143 Model::insertTracks( int row
, TrackList tracks
)
145 m_undoStack
->push( new AddTracksCmd( 0, row
, tracks
) );
149 Model::removeRows( int position
, int rows
, const QModelIndex
& /*parent*/ )
151 m_undoStack
->push( new RemoveTracksCmd( 0, position
, rows
) );
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
);
170 Collection
*local
= 0;
171 foreach( Collection
*coll
, CollectionManager::instance()->collections() )
173 if( coll
->collectionId() == "localCollection" )
178 QueryMaker
*qm
= local
->queryMaker();
179 qm
->startTrackQuery();
180 qm
->limitMaxResultSize( 10 );
181 BlockingQuery
bq( qm
);
183 insertTracks( 0, bq
.tracks( "localCollection" ) );
188 Model::supportedDropActions() const
190 return Qt::CopyAction
| Qt::MoveAction
;
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();
202 Model::play( const QModelIndex
& index
)
208 Model::play( int row
)
211 EngineController::instance()->play( m_items
[ m_activeRow
]->track() );
215 Model::prettyColumnName( Column index
) //static
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.";
249 Model::setActiveRow( int row
)
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 ) );
259 emit
dataChanged( createIndex( min
, 0 ), createIndex( min
, 0 ) );
260 emit
dataChanged( createIndex( max
, 0 ), createIndex( max
, 0 ) );
262 debug() << "between " << min
<< " and " << max
;
267 Model::metadataChanged( Meta::Track
*track
)
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 ) );
283 int index
= m_tracks
.indexOf( Meta::TrackPtr( track
), 0 );
285 emit
dataChanged( createIndex( index
, 0 ), createIndex( index
, 0 ) );
290 Model::metadataChanged(Meta::Album
* album
)
294 TrackList tracks
= album
->tracks();
295 foreach( TrackPtr track
, tracks
) {
296 metadataChanged( track
.data() );
304 removeRows( 0, m_items
.size() );
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
319 int alreadyOnPlaylist
= 0;
320 for( int i
= 0; i
< list
.size(); ++i
)
323 foreach( item
, m_items
)
325 if( item
->track() == list
.at( i
) )
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
)
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() );
364 Model::insertOptioned( Meta::TrackPtr track
, int options
)
366 Meta::TrackList list
;
367 list
.append( track
);
368 insertOptioned( list
, options
);
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
);
382 Model::insertMedia( KUrl::List list
, int options
)
385 Meta::TrackList trackList
;
388 Meta::TrackPtr track
= CollectionManager::instance()->trackForUrl( url
);
390 trackList
.push_back( track
);
392 insertOptioned( trackList
, options
);
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 )
404 // urls << (*it)->url();
405 // titles << (*it)->title();
406 // lengths << (*it)->length();
408 // return PlaylistBrowser::savePlaylist( path, urls, titles, lengths, relative );
413 Model::flags(const QModelIndex
&index
) const
415 Qt::ItemFlags defaultFlags
= QAbstractListModel::flags(index
);
416 return Qt::ItemIsDragEnabled
| Qt::ItemIsDropEnabled
| Qt::ItemIsEnabled
| defaultFlags
;
420 Model::mimeTypes() const //reimplemented
422 QStringList ret
= QAbstractListModel::mimeTypes();
423 ret
<< AmarokMimeData::TRACK_MIME
;
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
);
442 Model::dropMimeData ( const QMimeData
* data
, Qt::DropAction action
, int row
, int column
, const QModelIndex
& parent
) //reimplemented
444 Q_UNUSED( column
); Q_UNUSED( parent
);
447 if( action
== Qt::IgnoreAction
)
450 if( data
->hasFormat( AmarokMimeData::TRACK_MIME
) )
452 debug() << "Found track mime type";
454 const AmarokMimeData
* trackListDrag
= dynamic_cast<const AmarokMimeData
*>( data
);
457 debug() << "It's a list drag!";
460 debug() << "Inserting at row: " << row
<< " so we're appending to the list.";
461 insertOptioned( trackListDrag
->tracks(), PlaylistNS::Append
);
465 debug() << "Inserting at row: " << row
<<" so its inserted correctly.";
466 insertTracks( row
, trackListDrag
->tracks() );
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
) );
481 tracks
.append( track
);
483 if( !tracks
.isEmpty() )
484 insertOptioned( tracks
, PlaylistNS::Append
);
496 Model::insertTracksCommand( int row
, TrackList list
)
499 debug() << "inserting... " << row
<< ' ' << list
.count();
503 beginInsertRows( QModelIndex(), row
, row
+ list
.size() - 1 );
505 foreach( TrackPtr track
, list
)
509 track
->subscribe( this );
510 track
->album()->subscribe( this );
511 m_items
.insert( row
+ i
, new Item( track
) );
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() );
531 Model::removeRowsCommand( int position
, int rows
)
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 );
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() );
549 Amarok::actionCollection()->action( "playlist_clear" )->setEnabled( !m_items
.isEmpty() );
552 bool activeRowChanged
= true;
553 bool oldActiveRow
= m_activeRow
;
554 if( m_activeRow
>= position
&& m_activeRow
< ( position
+ rows
) )
556 else if( m_activeRow
>= position
)
557 m_activeRow
= m_activeRow
- position
;
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 ) );
570 Model::queryDone() //Slot
572 QueryMaker
*qm
= dynamic_cast<QueryMaker
*>( sender() );
575 m_queryMap
.remove( qm
);
581 Model::newResultReady( const QString
&collectionId
, const Meta::TrackList
&tracks
) //Slot
583 Q_UNUSED( collectionId
)
584 QueryMaker
*qm
= dynamic_cast<QueryMaker
*>( sender() );
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
) );
596 PlaylistNS::Model
* playlistModel() { return PlaylistNS::Model::s_instance
; }
601 #include "PlaylistModel.moc"