1 /***************************************************************************
2 * copyright : (C) 2007 Ian Monroe <ian@monroe.nu>
3 * : (C) 2007 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com>
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of
7 * the License or (at your option) version 3 or any later version
8 * accepted by the membership of KDE e.V. (or its successor approved
9 * by the membership of KDE e.V.), which shall act as a proxy
10 * defined in Section 14 of version 3 of the license.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 **************************************************************************/
22 #include "PlaylistModel.h"
25 #include "amarokconfig.h"
26 #include "AmarokMimeData.h"
28 #include "enginecontroller.h"
29 #include "PlaylistItem.h"
30 #include "PlaylistGraphicsView.h"
31 #include "RepeatTrackNavigator.h"
32 #include "StandardTrackNavigator.h"
33 #include "ContextStatusBar.h"
34 #include "TheInstances.h"
35 #include "UndoCommands.h"
37 #include "collection/BlockingQuery.h"
38 #include "collection/Collection.h"
39 #include "collection/CollectionManager.h"
40 #include "collection/QueryMaker.h"
41 #include "meta/lastfm/LastFmMeta.h"
44 #include <QStringList>
50 using namespace Playlist
;
55 // Sorting of a tracklist.
56 bool trackNumberLessThan( Meta::TrackPtr left
, Meta::TrackPtr right
)
58 if( left
->album() == right
->album() )
59 return left
->trackNumber() < right
->trackNumber();
61 else if( left
->artist() == right
->artist() )
62 return QString::localeAwareCompare( left
->album()->prettyName(), right
->album()->prettyName() ) < 0;
63 else // compare artists alphabetically
64 return QString::localeAwareCompare( left
->artist()->prettyName(), right
->artist()->prettyName() ) < 0;
68 Model
*Model::s_instance
= 0;
70 Model::Model( QObject
* parent
)
71 : QAbstractListModel( parent
)
73 , m_advancer( new StandardTrackNavigator( this ) )
74 , m_undoStack( new QUndoStack( this ) )
75 , m_playlistHandler ( new PlaylistHandler
)
77 connect( EngineController::instance(), SIGNAL( orderNext( bool ) ), this, SLOT( trackFinished() ), Qt::QueuedConnection
);
78 connect( EngineController::instance(), SIGNAL( orderCurrent() ), this, SLOT( playCurrentTrack() ), Qt::QueuedConnection
);
86 KActionCollection
* ac
= Amarok::actionCollection();
87 QAction
* undoButton
= m_undoStack
->createUndoAction( this, i18n("Undo") );
88 undoButton
->setIcon( KIcon( Amarok::icon( "undo" ) ) );
89 ac
->addAction("playlist_undo", undoButton
);
90 QAction
* redoButton
= m_undoStack
->createRedoAction( this, i18n("Redo") );
91 ac
->addAction("playlist_redo", redoButton
);
92 redoButton
->setIcon( KIcon( Amarok::icon( "redo" ) ) );
97 if( AmarokConfig::savePlaylist() )
100 foreach( Item
* item
, itemList() )
102 list
<< item
->track();
104 m_playlistHandler
->save( list
, defaultPlaylistPath() );
107 delete m_playlistHandler
;
111 Model::rowCount( const QModelIndex
& ) const
113 return m_items
.size();
117 Model::data( const QModelIndex
& index
, int role
) const
119 int row
= index
.row();
120 /*if( ( role == Qt::FontRole) && ( row == m_activeRow ) )
123 original.setBold( true );
127 if( role
== ItemRole
&& ( row
!= -1 ) )
128 return QVariant::fromValue( m_items
.at( row
) );
130 else if( role
== ActiveTrackRole
)
131 return ( row
== m_activeRow
);
133 else if( role
== TrackRole
&& ( row
!= -1 ) && m_items
.at( row
)->track() )
134 return QVariant::fromValue( m_items
.at( row
)->track() );
136 else if ( role
== GroupRole
)
138 TrackPtr track
= m_items
.at( row
)->track();
140 if ( !track
->album() )
141 return None
; // no albm set
142 else if ( !m_albumGroups
.contains( track
->album()->prettyName() ) )
143 return None
; // no group for this album, should never happen...
145 AlbumGroup
* albumGroup
= m_albumGroups
.value( track
->album()->prettyName() );
146 return albumGroup
->groupMode( row
);
150 else if ( role
== GroupedTracksRole
)
152 TrackPtr track
= m_items
.at( row
)->track();
153 AlbumGroup
* albumGroup
= m_albumGroups
.value( track
->album()->prettyName() );
154 return albumGroup
->elementsInGroup( row
);
157 else if ( role
== GroupedAlternateRole
)
159 TrackPtr track
= m_items
.at( row
)->track();
160 AlbumGroup
* albumGroup
= m_albumGroups
.value( track
->album()->prettyName() );
162 return albumGroup
->alternate( row
);
166 else if ( role
== GroupedCollapsibleRole
)
168 TrackPtr track
= m_items
.at( row
)->track();
169 AlbumGroup
* albumGroup
= m_albumGroups
.value( track
->album()->prettyName() );
170 //we cannot collapse the group that contains the currently selected track.
171 return ( albumGroup
->firstInGroup( m_activeRow
) == -1 );
175 else if( role
== Qt::DisplayRole
&& row
!= -1 )
177 switch( index
.column() )
180 return m_items
.at( row
)->track()->name();
182 if ( m_items
.at( row
)->track()->album() )
183 return m_items
.at( row
)->track()->album()->name();
187 if ( m_items
.at( row
)->track()->artist() )
188 return m_items
.at( row
)->track()->artist()->name();
197 case AlbumArtist: return track->album()->albumArtist()->name();
198 case Album: return track->album()->name();
199 case Artist: return track->artist()->name();
200 case Bitrate: return track->bitrate();
201 case Composer: return track->composer()->name();
202 case CoverImage: return track->album()->image( 50 );
203 case Comment: return track->comment();
204 case DiscNumber: return track->discNumber();
205 case Filesize: return track->filesize();
206 case Genre: return track->genre()->name();
207 case Length: return track->length();
208 case Rating: return track->rating();
209 case Score: return track->score();
210 case Title: return track->name();
211 case TrackNumber: return track->trackNumber();
212 case Year: return track->year()->name().toInt();
213 default: return QVariant();
218 // insertColumns( int column, Column type )
220 // beginInsertColumns( QModelIndex(), column, column + 1 );
221 // m_columns.insert( column, type );
222 // endInsertColumns();
227 Model::insertTrack( int row
, TrackPtr track
)
231 list
.append( track
);
232 insertTracks( row
, list
);
237 Model::insertTracks( int row
, TrackList tracks
)
239 m_undoStack
->push( new AddTracksCmd( 0, row
, tracks
) );
243 Model::removeRows( int position
, int rows
, const QModelIndex
& /*parent*/ )
245 m_undoStack
->push( new RemoveTracksCmd( 0, position
, rows
) );
251 Model::insertTracks( int row
, QueryMaker
*qm
)
253 qm
->startTrackQuery();
254 connect( qm
, SIGNAL( queryDone() ), SLOT( queryDone() ) );
255 connect( qm
, SIGNAL( newResultReady( QString
, Meta::TrackList
) ), SLOT( newResultReady( QString
, Meta::TrackList
) ) );
256 m_queryMap
.insert( qm
, row
);
261 Model::supportedDropActions() const
263 return Qt::CopyAction
| Qt::MoveAction
;
267 Model::trackFinished()
269 if( m_activeRow
< 0 || m_activeRow
>= m_items
.size() )
271 Meta::TrackPtr track
= m_items
.at( m_activeRow
)->track();
272 track
->finishedPlaying( 1.0 ); //TODO: get correct value for parameter
273 m_advancer
->advanceTrack();
277 Model::play( const QModelIndex
& index
)
283 Model::play( int row
)
287 EngineController::instance()->play( m_items
[ m_activeRow
]->track() );
291 Model::playlistRepeatMode( int item
)
294 playModeChanged( Playlist::Standard
);
295 else //for now just turn on repeat if anything but "off" is clicked
296 playModeChanged( Playlist::Repeat
);
302 if( m_activeRow
< 0 || m_activeRow
>= m_items
.size() )
304 Meta::TrackPtr track
= m_items
.at( m_activeRow
)->track();
305 track
->finishedPlaying( 0.5 ); //TODO: get correct value for parameter
306 m_advancer
->userAdvanceTrack();
312 if( m_activeRow
< 0 || m_activeRow
>= m_items
.size() )
314 Meta::TrackPtr track
= m_items
.at( m_activeRow
)->track();
315 track
->finishedPlaying( 0.5 ); //TODO: get correct value for parameter
316 m_advancer
->recedeTrack();
320 Model::playCurrentTrack()
322 int selected
= m_activeRow
;
323 if( selected
< 0 || selected
>= m_items
.size() )
325 //play first track if there are tracks in the playlist
336 Model::prettyColumnName( Column index
) //static
340 case Filename
: return i18n( "Filename" );
341 case Title
: return i18n( "Title" );
342 case Artist
: return i18n( "Artist" );
343 case AlbumArtist
:return i18n( "Album Artist");
344 case Composer
: return i18n( "Composer" );
345 case Year
: return i18n( "Year" );
346 case Album
: return i18n( "Album" );
347 case DiscNumber
: return i18n( "Disc Number" );
348 case TrackNumber
:return i18n( "Track" );
349 case Bpm
: return i18n( "BPM" );
350 case Genre
: return i18n( "Genre" );
351 case Comment
: return i18n( "Comment" );
352 case Directory
: return i18n( "Directory" );
353 case Type
: return i18n( "Type" );
354 case Length
: return i18n( "Length" );
355 case Bitrate
: return i18n( "Bitrate" );
356 case SampleRate
: return i18n( "Sample Rate" );
357 case Score
: return i18n( "Score" );
358 case Rating
: return i18n( "Rating" );
359 case PlayCount
: return i18n( "Play Count" );
360 case LastPlayed
: return i18nc( "Column name", "Last Played" );
361 case Mood
: return i18n( "Mood" );
362 case Filesize
: return i18n( "File Size" );
363 default: return "This is a bug.";
369 Model::playModeChanged( int row
)
374 case Playlist::Standard
:
375 m_advancer
= new StandardTrackNavigator(this);
377 case Playlist::Repeat
:
378 m_advancer
= new RepeatTrackNavigator(this);
384 Model::setActiveRow( int row
)
388 int max
= qMax( row
, m_activeRow
);
389 int min
= qMin( row
, m_activeRow
);
390 if( ( max
- min
) == 1 )
391 emit
dataChanged( createIndex( min
, 0 ), createIndex( max
, 0 ) );
394 emit
dataChanged( createIndex( min
, 0 ), createIndex( min
, 0 ) );
395 emit
dataChanged( createIndex( max
, 0 ), createIndex( max
, 0 ) );
397 debug() << "between " << min
<< " and " << max
;
401 //make sure that the group containg this track is expanded
403 //not all tracks have a valid album ( radio stations for instance... )
405 if ( !m_items
[ row
]->track()->album().isNull() ) {
406 albumName
= m_items
[ row
]->track()->album()->prettyName();
409 if( m_albumGroups
.contains( albumName
) && !albumName
.isEmpty() )
411 m_albumGroups
[ albumName
]->setCollapsed( row
, false );
413 emit( playlistGroupingChanged() );
419 Model::metadataChanged( Meta::Track
*track
)
422 const int size
= m_items
.size();
423 const Meta::TrackPtr needle
= Meta::TrackPtr( track
);
424 for( int i
= 0; i
< size
; i
++ )
426 if( m_items
.at( i
)->track() == needle
)
428 debug() << "Track in playlist";
429 emit
dataChanged( createIndex( i
, 0 ), createIndex( i
, 0 ) );
435 int index
= m_tracks
.indexOf( Meta::TrackPtr( track
), 0 );
437 emit
dataChanged( createIndex( index
, 0 ), createIndex( index
, 0 ) );
442 Model::metadataChanged(Meta::Album
* album
)
446 TrackList tracks
= album
->tracks();
447 foreach( TrackPtr track
, tracks
) {
448 metadataChanged( track
.data() );
456 if( m_items
.size() < 1 )
458 removeRows( 0, m_items
.size() );
459 m_albumGroups
.clear();
460 m_lastAddedTrackAlbum
= AlbumPtr();
461 The::playlistView()->scene()->setSceneRect( The::playlistView()->scene()->itemsBoundingRect() );
466 Model::insertOptioned( Meta::TrackList list
, int options
)
471 //TODO: we call insertOptioned on resume before the statusbar is fully created... We need a better way to handle this
472 if( list
.isEmpty() ) {
473 // Amarok::ContextStatusBar::instance()->shortMessage( i18n("Attempted to insert nothing into playlist.") );
474 return; // don't add empty items
478 if( options
& Unique
)
480 int alreadyOnPlaylist
= 0;
481 for( int i
= 0; i
< list
.size(); ++i
)
485 foreach( item
, m_items
)
487 if( item
->track() == list
.at( i
) )
496 if ( alreadyOnPlaylist
)
497 Amarok::ContextStatusBar::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
) );
500 int orgCount
= rowCount(); //needed because recursion messes up counting
501 bool playlistAdded
= false;
503 //HACK! Check if any of the incomming tracks is really a playlist. Warning, this can get highly recursive
504 for( int i
= 0; i
< list
.size(); ++i
)
506 if ( m_playlistHandler
->isPlaylist( list
.at( i
)->url() ) ) {
507 playlistAdded
= true;
508 m_playlistHandler
->load( list
.takeAt( i
)->url() );
512 //fix crash when list is empty
513 if ( list
.isEmpty() )
517 int firstItemAdded
= -1;
518 if( options
& Replace
)
522 insertTracks( 0, list
);
524 else if( options
& Append
)
527 firstItemAdded
= orgCount
;
529 firstItemAdded
= rowCount();
530 insertTracks( firstItemAdded
, list
);
531 if( orgCount
== 0 && (EngineController::engine()->state() != Engine::Playing
) )
532 play( firstItemAdded
);
534 else if( options
& Queue
)
536 //TODO implement queue
538 if( options
& DirectPlay
)
540 if ( rowCount() > firstItemAdded
)
541 play( firstItemAdded
);
543 else if( ( options
& StartPlay
) && ( EngineController::engine()->state() != Engine::Playing
) && ( rowCount() != 0 ) )
545 play( firstItemAdded
);
547 Amarok::actionCollection()->action( "playlist_clear" )->setEnabled( !m_items
.isEmpty() );
548 The::playlistView()->scene()->setSceneRect( The::playlistView()->scene()->itemsBoundingRect() );
552 Model::insertOptioned( Meta::TrackPtr track
, int options
)
554 Meta::TrackList list
;
555 list
.append( track
);
556 insertOptioned( list
, options
);
560 Model::insertOptioned( QueryMaker
*qm
, int options
)
562 qm
->startTrackQuery();
563 connect( qm
, SIGNAL( queryDone() ), SLOT( queryDone() ) );
564 connect( qm
, SIGNAL( newResultReady( QString
, Meta::TrackList
) ), SLOT( newResultReady( QString
, Meta::TrackList
) ) );
565 m_optionedQueryMap
.insert( qm
, options
);
570 Model::saveM3U( const QString
&path
) const
573 foreach( Item
* item
, itemList() )
575 if( m_playlistHandler
->save( tl
, path
) )
581 Model::flags(const QModelIndex
&index
) const
583 if( index
.isValid() )
585 return ( Qt::ItemIsEnabled
| Qt::ItemIsSelectable
| Qt::ItemIsDropEnabled
|
586 Qt::ItemIsDragEnabled
| Qt::ItemIsSelectable
);
588 return Qt::ItemIsDropEnabled
;
592 Model::mimeTypes() const //reimplemented
594 QStringList ret
= QAbstractListModel::mimeTypes();
595 ret
<< AmarokMimeData::TRACK_MIME
;
600 Model::mimeData( const QModelIndexList
&indexes
) const //reimplemented
602 AmarokMimeData
* mime
= new AmarokMimeData();
603 Meta::TrackList selectedTracks
;
605 foreach( const QModelIndex
&it
, indexes
)
606 selectedTracks
<< m_items
.at( it
.row() )->track();
608 mime
->setTracks( selectedTracks
);
613 Model::dropMimeData ( const QMimeData
* data
, Qt::DropAction action
, int row
, int column
, const QModelIndex
& parent
) //reimplemented
615 Q_UNUSED( column
); Q_UNUSED( parent
);
618 if( action
== Qt::IgnoreAction
)
621 if( data
->hasFormat( AmarokMimeData::TRACK_MIME
) )
623 debug() << "Found track mime type";
625 const AmarokMimeData
* trackListDrag
= dynamic_cast<const AmarokMimeData
*>( data
);
630 debug() << "Inserting at row: " << row
<< " so we're appending to the list.";
631 insertOptioned( trackListDrag
->tracks(), Playlist::Append
);
635 debug() << "Inserting at row: " << row
<<" so its inserted correctly.";
636 insertTracks( row
, trackListDrag
->tracks() );
641 else if( data
->hasUrls() )
643 //probably a drop from an external source
644 debug() << "Drop from external source";
645 QList
<QUrl
> urls
= data
->urls();
646 Meta::TrackList tracks
;
647 foreach( const QUrl
&url
, urls
)
649 Meta::TrackPtr track
= CollectionManager::instance()->trackForUrl( KUrl( url
) );
651 tracks
.append( track
);
653 if( !tracks
.isEmpty() )
654 insertOptioned( tracks
, Playlist::Append
);
666 Model::insertTracksCommand( int row
, TrackList list
)
669 debug() << "inserting... " << row
<< ' ' << list
.count();
673 beginInsertRows( QModelIndex(), row
, row
+ list
.size() - 1 );
675 foreach( TrackPtr track
, list
)
679 track
->subscribe( this );
681 track
->album()->subscribe( this );
683 m_items
.insert( row
+ i
, new Item( track
) );
688 //we need to regroup everything below this point as all the index changes
689 regroupAlbums( row
, m_items
.count() );
692 //push up the active row if needed
693 if( m_activeRow
> row
)
695 int oldActiveRow
= m_activeRow
;
696 m_activeRow
+= list
.size();
697 Q_UNUSED( oldActiveRow
);
698 //dataChanged( createIndex( oldActiveRow, 0 ), createIndex( oldActiveRow, columnCount() -1 ) );
699 //dataChanged( createIndex( m_activeRow, 0 ), createIndex( m_activeRow, columnCount() -1 ) );
701 dataChanged( createIndex( row
, 0 ), createIndex( rowCount() - 1, 0 ) );
702 Amarok::actionCollection()->action( "playlist_clear" )->setEnabled( !m_items
.isEmpty() );
705 emit
playlistCountChanged( rowCount() );
710 Model::removeRowsCommand( int position
, int rows
)
714 beginRemoveRows( QModelIndex(), position
, position
+ rows
- 1 );
715 // TrackList::iterator start = m_tracks.begin() + position;
716 // TrackList::iterator end = start + rows;
717 // m_tracks.erase( start, end );
719 for( int i
= position
; i
< position
+ rows
; i
++ )
721 Item
* item
= m_items
.takeAt( position
); //take at position, row times
722 item
->track()->unsubscribe( this );
723 if( item
->track()->album() )
724 item
->track()->album()->unsubscribe( this );
725 ret
.push_back( item
->track() );
730 Amarok::actionCollection()->action( "playlist_clear" )->setEnabled( !m_items
.isEmpty() );
733 bool activeRowChanged
= true;
734 bool oldActiveRow
= m_activeRow
;
735 if( m_activeRow
>= position
&& m_activeRow
< ( position
+ rows
) )
737 else if( m_activeRow
>= position
)
738 m_activeRow
= m_activeRow
- position
;
740 activeRowChanged
= false;
741 if( activeRowChanged
)
743 dataChanged( createIndex( oldActiveRow
, 0 ), createIndex( oldActiveRow
, columnCount() -1 ) );
744 dataChanged( createIndex( m_activeRow
, 0 ), createIndex( m_activeRow
, columnCount() -1 ) );
746 dataChanged( createIndex( position
, 0 ), createIndex( rowCount(), 0 ) );
748 //we need to regroup everything below this point as all the index changes
749 //also, use the count before the rows was removed to make sure all groups are deleted
750 regroupAlbums( position
, rows
, OffsetAfter
, 0 - rows
);
754 emit
playlistCountChanged( rowCount() );
759 Model::queryDone() //Slot
761 QueryMaker
*qm
= dynamic_cast<QueryMaker
*>( sender() );
764 m_queryMap
.remove( qm
);
770 Model::newResultReady( const QString
&collectionId
, const Meta::TrackList
&tracks
) //Slot
772 Meta::TrackList ourTracks
= tracks
;
773 qStableSort( ourTracks
.begin(), ourTracks
.end(), Amarok::trackNumberLessThan
);
774 Q_UNUSED( collectionId
)
775 QueryMaker
*qm
= dynamic_cast<QueryMaker
*>( sender() );
778 //requires better handling of queries which return multiple results
779 if( m_queryMap
.contains( qm
) )
780 insertTracks( m_queryMap
.value( qm
), ourTracks
);
781 else if( m_optionedQueryMap
.contains( qm
) )
782 insertOptioned( ourTracks
, m_optionedQueryMap
.value( qm
) );
786 QVariant
Model::headerData(int section
, Qt::Orientation orientation
, int role
) const
789 Q_UNUSED( orientation
);
791 if ( role
!= Qt::DisplayRole
)
809 void Model::moveRow(int row
, int to
)
812 m_items
.move( row
, to
);
818 regroupAlbums( QMIN( row
, to
) , QMAX( row
, to
), OffsetBetween
, offset
);
823 void Model::regroupAlbums( int firstRow
, int lastRow
, OffsetMode offsetMode
, int offset
)
827 //debug() << "first row: " << firstRow << ", last row: " << lastRow;
838 aboveFirst
= firstRow
- 1;
839 if ( aboveFirst
< 0 ) aboveFirst
= 0;
841 belowLast
= lastRow
+ 1;
842 if ( belowLast
> ( m_items
.count() - 1 ) ) belowLast
= m_items
.count() - 1;
844 debug() << "aboveFirst: " << aboveFirst
<< ", belowLast: " << belowLast
;
846 //delete affected groups
848 QMapIterator
< QString
, AlbumGroup
*> itt(m_albumGroups
);
849 while (itt
.hasNext()) {
852 AlbumGroup
* group
= itt
.value();
854 bool removeGroupAboveFirstRow
= false;
855 bool removeGroupBelowFirstRow
= false;
856 bool removeGroupAboveLastRow
= false;
857 bool removeGroupBelowLastRow
= false;
859 int temp
= group
->firstInGroup( aboveFirst
);
863 removeGroupAboveFirstRow
= true;
866 temp
= group
->lastInGroup( firstRow
+ 1 );
870 removeGroupBelowFirstRow
= true;
873 temp
= group
->firstInGroup( lastRow
- 1 );
877 removeGroupAboveLastRow
= true;
880 temp
= group
->lastInGroup( belowLast
);
884 removeGroupBelowLastRow
= true;
887 if ( removeGroupAboveFirstRow
)
888 { group
->removeGroup( aboveFirst
); debug() << "removing group at row: " << aboveFirst
; }
890 if ( removeGroupBelowFirstRow
)
891 { group
->removeGroup( firstRow
+ 1 ); debug() << "removing group at row: " << firstRow
+ 1; }
893 if ( removeGroupAboveLastRow
)
894 { group
->removeGroup( lastRow
-1 ); debug() << "removing group at row: " << lastRow
- 1; }
895 if ( removeGroupBelowLastRow
)
896 { group
->removeGroup( belowLast
); debug() << "removing group at row: " << belowLast
; }
898 group
->removeGroup( firstRow
);
899 group
->removeGroup( lastRow
);
901 //if there is nothing left in album group, discard it.
903 if ( group
->subgroupCount() == 0 ) {
904 //debug() << "empty...";
905 delete m_albumGroups
.take( itt
.key() );
916 if ( m_albumGroups
.count() == 0 ) { // start from scratch
919 area1End
= m_items
.count();
920 area2Start
= area1Start
; // just to skip second pass
925 if ( area1Start
== -1 ) {
927 area1Start
= aboveFirst
;
928 area1End
= belowLast
;
929 area2Start
= area1Start
;
932 if ( area1End
== -1 )
933 area1End
= belowLast
;
935 if ( area1Start
< 0 )
937 if ( area1End
> ( m_items
.count() - 1 ) )
938 area1End
= m_items
.count() - 1;
940 if ( area2Start
< 0 )
942 if ( area2End
> ( m_items
.count() - 1 ) )
943 area2End
= m_items
.count() - 1;
946 // regroup the two affected areas
948 if ( area1Start
== area2Start
) //merge areas
949 area1End
= QMAX( area1End
, area2End
);
950 else if ( area1End
== area2End
) {//merge areas
951 area1Start
= QMIN( area1Start
, area2Start
);
952 area2Start
= area1Start
;
956 debug() << "area1Start: " << area1Start
<< ", area1End: " << area1End
;
957 debug() << "area2Start: " << area2Start
<< ", area2End: " << area2End
;
960 debug() << "Groups before:";
961 foreach( AlbumGroup
* ag
, m_albumGroups
)
962 ag
->printGroupRows();
966 if ( offsetMode
!= OffsetNone
) {
971 if ( offsetMode
== OffsetBetween
) {
972 offsetFrom
= area1End
+ 1;
973 offsetTo
= area2Start
- 1;
974 // last but not least, change area1end and area2start to match new offsets
975 if ( area1End
!= area2End
) {
977 area2Start
+= offset
;
978 debug() << "area1Start: " << area1Start
<< ", area1End: " << area1End
;
979 debug() << "area2Start: " << area2Start
<< ", area2End: " << area2End
;
982 offsetFrom
= lastRow
;
983 offsetTo
= ( m_items
.count() - offset
) + 1;
986 QMapIterator
< QString
, AlbumGroup
*> itt(m_albumGroups
);
987 while (itt
.hasNext()) {
990 AlbumGroup
* group
= itt
.value();
991 group
->offsetBetween( offsetFrom
, offsetTo
, offset
);
999 debug() << "Groups after offsetting:";
1000 foreach( AlbumGroup
* ag
, m_albumGroups
)
1001 ag
->printGroupRows();
1005 for ( i
= area1Start
; ( i
<= area1End
) && ( i
< m_items
.count() ); i
++ )
1008 //debug() << "i: " << i;
1010 TrackPtr track
= m_items
.at( i
)->track();
1012 if ( !track
->album() )
1017 //not all tracks have a valid album ( radio stations for instance... )
1018 if ( !track
->album().isNull() ) {
1019 albumName
= track
->album()->prettyName();
1022 if ( m_albumGroups
.contains( albumName
) && !albumName
.isEmpty() ) {
1023 m_albumGroups
[ albumName
]->addRow( i
);
1025 //debug() << "Create new group for album " << track->album()->name() ;
1026 AlbumGroup
* newGroup
= new AlbumGroup();
1027 newGroup
->addRow( i
);
1028 m_albumGroups
.insert( albumName
, newGroup
);
1032 if ( ( area1Start
== area2Start
) || area2Start
== -1 ) {
1034 debug() << "Groups after:";
1035 foreach( AlbumGroup
* ag
, m_albumGroups
)
1036 ag
->printGroupRows();
1040 for ( i
= area2Start
; i
<= area2End
; i
++ )
1043 //debug() << "i: " << i;
1045 TrackPtr track
= m_items
.at( i
)->track();
1047 if ( !track
->album() )
1050 if ( m_albumGroups
.contains( track
->album()->prettyName() ) ) {
1051 m_albumGroups
[ track
->album()->prettyName() ]->addRow( i
);
1053 AlbumGroup
* newGroup
= new AlbumGroup();
1054 newGroup
->addRow( i
);
1055 m_albumGroups
.insert( track
->album()->prettyName(), newGroup
);
1059 debug() << "Groups after:";
1060 foreach( AlbumGroup
*ag
, m_albumGroups
)
1061 ag
->printGroupRows();
1065 //make sure that a group containg playing track is expanded
1066 if ( m_activeRow
!= -1 ){
1067 if ( m_albumGroups
.contains( m_items
[ m_activeRow
]->track()->album()->prettyName() ) ) {
1068 m_albumGroups
[ m_items
[ m_activeRow
]->track()->album()->prettyName() ]->setCollapsed( m_activeRow
, false );
1070 emit( playlistGroupingChanged() );
1078 void Playlist::Model::setCollapsed(int row
, bool collapsed
)
1081 m_albumGroups
[ m_items
[ row
]->track()->album()->prettyName() ]->setCollapsed( row
, collapsed
);
1082 emit( playlistGroupingChanged() );
1088 Playlist::Model
* playlistModel() { return Playlist::Model::s_instance
; }
1099 #include "PlaylistModel.moc"