Merge commit 'refs/merge-requests/2031' of git://gitorious.org/amarok/amarok into...
[amarok:thecrashers-sandbox.git] / src / playlist / proxymodels / GroupingProxy.cpp
1 /****************************************************************************************
2  * Copyright (c) 2007-2008 Ian Monroe <ian@monroe.nu>                                   *
3  * Copyright (c) 2007 Nikolaj Hald Nielsen <nhnFreespirit@gmail.com>                    *
4  * Copyright (c) 2008 Seb Ruiz <ruiz@kde.org>                                           *
5  * Copyright (c) 2008 Soren Harward <stharward@gmail.com>                               *
6  * Copyright (c) 2009 Téo Mrnjavac <teo.mrnjavac@gmail.com>                             *
7  *                                                                                      *
8  * This program is free software; you can redistribute it and/or modify it under        *
9  * the terms of the GNU General Public License as published by the Free Software        *
10  * Foundation; either version 2 of the License, or (at your option) version 3 or        *
11  * any later version accepted by the membership of KDE e.V. (or its successor approved  *
12  * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of  *
13  * version 3 of the license.                                                            *
14  *                                                                                      *
15  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
16  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
17  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
18  *                                                                                      *
19  * You should have received a copy of the GNU General Public License along with         *
20  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
21  ****************************************************************************************/
22
23 #define DEBUG_PREFIX "Playlist::GroupingProxy"
24
25 #include "GroupingProxy.h"
26
27 #include "Debug.h"
28 #include "Collection.h"
29 #include "meta/MetaUtility.h"
30 #include "meta/capabilities/SourceInfoCapability.h"
31 #include "playlist/PlaylistDefines.h"
32
33 #include <QVariant>
34
35 Playlist::GroupingProxy::GroupingProxy( Playlist::AbstractModel *belowModel, QObject *parent )
36     : ProxyBase( parent )
37     , m_groupingCategory( QString( "Album" ) )
38 {
39     m_belowModel = belowModel;
40     setSourceModel( dynamic_cast< QAbstractItemModel * >( m_belowModel ) );
41     // signal proxies
42     connect( sourceModel(), SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ), this, SLOT( modelDataChanged( const QModelIndex&, const QModelIndex& ) ) );
43     connect( sourceModel(), SIGNAL( rowsInserted( const QModelIndex&, int, int ) ), this, SLOT( modelRowsInserted( const QModelIndex &, int, int ) ) );
44     connect( sourceModel(), SIGNAL( rowsRemoved( const QModelIndex&, int, int ) ), this, SLOT( modelRowsRemoved( const QModelIndex&, int, int ) ) );
45     connect( sourceModel(), SIGNAL( activeTrackChanged( const quint64 ) ), this, SIGNAL( activeTrackChanged( quint64 ) ) );
46     connect( sourceModel(), SIGNAL( metadataUpdated() ), this, SIGNAL( metadataUpdated() ) );
47     connect( this, SIGNAL( metadataUpdated() ), this, SLOT( regroupAll() ) );
48
49     connect( sourceModel(), SIGNAL( layoutChanged() ), this, SLOT( regroupAll() ) );
50     connect( sourceModel(), SIGNAL( layoutChanged() ), this, SIGNAL( layoutChanged() ) );
51     connect( sourceModel(), SIGNAL( queueChanged() ), this, SIGNAL( queueChanged() ) );
52     connect( sourceModel(), SIGNAL( modelReset() ), this, SLOT( regroupAll() ) );
53
54     connect( sourceModel(), SIGNAL( insertedIds( const QList<quint64>& ) ), this, SIGNAL( insertedIds( const QList< quint64>& ) ) );
55     connect( sourceModel(), SIGNAL( beginRemoveIds() ), this, SIGNAL( beginRemoveIds() ) );
56     connect( sourceModel(), SIGNAL( removedIds( const QList<quint64>& ) ), this, SIGNAL( removedIds( const QList< quint64 >& ) ) );
57     connect( sourceModel(), SIGNAL( activeTrackChanged( const quint64 ) ), this, SIGNAL( activeTrackChanged( quint64 ) ) );
58
59     int max = m_belowModel->rowCount();
60     for ( int i = 0; i < max; i++ )
61         m_rowGroupMode.append( None );
62
63     regroupRows( 0, max - 1 );
64
65      setObjectName( "GroupingProxy" );
66
67 }
68
69 Playlist::GroupingProxy::~GroupingProxy()
70 {
71 }
72
73
74 QVariant
75 Playlist::GroupingProxy::data( const QModelIndex& index, int role ) const
76 {
77     if( !index.isValid() )
78         return QVariant();
79
80     int row = index.row();
81
82     if( role == Playlist::GroupRole )
83         return m_rowGroupMode.at( row );
84
85     else if( role == Playlist::GroupedTracksRole )
86         return groupRowCount( row );
87
88     else if( role == Playlist::GroupedAlternateRole )
89         return ( row % 2 == 1 );
90     else if( role == Qt::DisplayRole || role == Qt::ToolTipRole )
91     {
92         switch( index.column() )
93         {
94             case GroupLength:
95             {
96                 return Meta::msToPrettyTime( lengthOfGroup( row ) );
97             }
98             case GroupTracks:
99             {
100                 return i18np ( "1 track", "%1 tracks", tracksInGroup( row ) );
101             }
102             default:
103                 return m_belowModel->data( index, role );
104         }
105     }
106     else
107         return m_belowModel->data( index, role );
108 }
109
110 void
111 Playlist::GroupingProxy::setCollapsed( int, bool ) const
112 {
113     AMAROK_DEPRECATED
114 }
115
116 int
117 Playlist::GroupingProxy::firstInGroup( int row ) const
118 {
119     if ( m_rowGroupMode.at( row ) == None )
120         return row;
121
122     while ( row >= 0 )
123     {
124         if ( m_rowGroupMode.at( row ) == Head )
125             return row;
126         row--;
127     }
128     warning() << "No group head found for row" << row;
129     return row;
130 }
131
132 int
133 Playlist::GroupingProxy::lastInGroup( int row ) const
134 {
135     if ( m_rowGroupMode.at( row ) == None )
136         return row;
137
138     while ( row < rowCount() )
139     {
140         if ( m_rowGroupMode.at( row ) == Tail )
141             return row;
142         row++;
143     }
144     warning() << "No group tail found for row" << row;
145     return row;
146 }
147
148 void
149 Playlist::GroupingProxy::modelDataChanged( const QModelIndex& start, const QModelIndex& end )
150 {
151     regroupRows( start.row(), end.row() );
152 }
153
154 void
155 Playlist::GroupingProxy::modelRowsInserted( const QModelIndex& idx, int start, int end )
156 {
157     beginInsertRows( idx, start, end );
158     for ( int i = start; i <= end; i++ )
159     {
160         m_rowGroupMode.insert( i, None );
161     }
162     endInsertRows();
163 }
164
165 void
166 Playlist::GroupingProxy::modelRowsRemoved( const QModelIndex& idx, int start, int end )
167 {
168     beginRemoveRows( idx, start, end );
169     for ( int i = start; i <= end; i++ )
170     {
171         m_rowGroupMode.removeAt( start );
172     }
173     endRemoveRows();
174 }
175
176 void
177 Playlist::GroupingProxy::regroupAll()
178 {
179     regroupRows( 0, rowCount() - 1 );
180 }
181
182 void
183 Playlist::GroupingProxy::regroupRows( int first, int last )
184 {
185     /* This function maps row numbers to one of the GroupMode enums, according
186      * to the following truth matrix:
187      *
188      *                  Matches Preceding Row
189      *
190      *                     true      false
191      *   Matches      true Body      Head
192      * Following
193      *       Row     false Tail      None
194      *
195      * Non-existent albums are non-matches
196      */
197
198     first = ( first > 0 ) ? ( first - 1 ) : first;
199     last = ( last < ( m_belowModel->rowCount() - 1 ) ) ? ( last + 1 ) : last;
200
201     for ( int row = first; row <= last; row++ )
202     {
203         Meta::TrackPtr thisTrack = m_belowModel->trackAt( row );
204
205         if (( thisTrack == Meta::TrackPtr() ) || ( thisTrack->album() == Meta::AlbumPtr() ) )
206         {
207             m_rowGroupMode[row] = None;
208             continue;
209         }
210
211         int beforeRow = row - 1;
212         bool matchBefore = false;
213         Meta::TrackPtr beforeTrack = m_belowModel->trackAt( beforeRow );
214         if ( beforeTrack != Meta::TrackPtr() )
215             matchBefore = shouldBeGrouped( beforeTrack, thisTrack );
216
217         int afterRow = row + 1;
218         bool matchAfter = false;
219         Meta::TrackPtr afterTrack = m_belowModel->trackAt( afterRow );
220         if ( afterTrack != Meta::TrackPtr() )
221             matchAfter = shouldBeGrouped( afterTrack, thisTrack );
222
223         if ( matchBefore && matchAfter )
224             m_rowGroupMode[row] = Body;
225         else if ( !matchBefore && matchAfter )
226             m_rowGroupMode[row] = Head;
227         else if ( matchBefore && !matchAfter )
228             m_rowGroupMode[row] = Tail;
229         else
230             m_rowGroupMode[row] = None;
231     }
232
233     emit layoutChanged();
234 }
235
236 int
237 Playlist::GroupingProxy::groupRowCount( int row ) const
238 {
239     AMAROK_DEPRECATED
240     return lastInGroup( row ) - firstInGroup( row ) + 1;
241 }
242
243 bool
244 Playlist::GroupingProxy::shouldBeGrouped( Meta::TrackPtr track1, Meta::TrackPtr track2 )
245 {
246     //An empty grouping category means "no grouping"
247     if ( m_groupingCategory.isEmpty() )
248         return false;
249
250     if( groupableCategories.contains( m_groupingCategory ) )   //sanity
251     {
252         switch( groupableCategories.indexOf( m_groupingCategory ) )
253         {
254             case 0: //Album
255                 if( track1 && track1->album() && track2 && track2->album() )
256                     return ( *track1->album().data() ) == ( *track2->album().data() ) && ( track1->discNumber() == track2->discNumber() );
257             case 1: //Artist
258                 if( track1 && track1->artist() && track2 && track2->artist() )
259                     return ( *track1->artist().data() ) == ( *track2->artist().data() );
260             case 2: //Composer
261                 if( track1 && track1->composer() && track2 && track2->composer() )
262                     return ( *track1->composer().data() ) == ( *track2->composer().data() );
263             case 3: //Genre
264                 if( track1 && track1->genre() && track2 && track2->genre() )
265                     return ( *track1->genre().data() ) == ( *track2->genre().data() );
266             case 4: //Rating
267                 if( track1 && track1->rating() && track2 && track2->rating() )
268                     return ( track1->rating() ) == ( track2->rating() );
269             case 5: //Source
270                 if( track1 && track2 )
271                 {
272                     Meta::SourceInfoCapability *sic1 = track1->create< Meta::SourceInfoCapability >();
273                     Meta::SourceInfoCapability *sic2 = track2->create< Meta::SourceInfoCapability >();
274                     QString source1, source2;
275                     if( sic1 && sic2)
276                     {
277                         source1 = sic1->sourceName();
278                         source2 = sic2->sourceName();
279                         delete sic1;
280                         delete sic2;
281                     }
282                     else
283                     {
284                         // First I make sure I delete sic1 and sic2 if only one of them was
285                         // instantiated:
286                         if( sic1 )
287                             delete sic1;
288                         if( sic2 )
289                             delete sic2;
290
291                         if( track1->collection() && track2->collection() )
292                         {
293                             source1 = track1->collection()->collectionId();
294                             source2 = track2->collection()->collectionId();
295                         }
296                         else
297                             return false;
298                     }
299                     return source1 == source2;
300                 }
301             case 6: //Year
302                 if( track1 && track1->year() && track2 && track2->year() )
303                     return ( *track1->year().data() ) == ( *track2->year().data() );
304             default:
305                 return false;
306         }
307     }
308     return false;
309 }
310
311 int Playlist::GroupingProxy::tracksInGroup( int row ) const
312 {
313     return ( lastInGroup( row ) - firstInGroup( row ) ) + 1;
314 }
315
316 int Playlist::GroupingProxy::lengthOfGroup( int row ) const
317 {
318     int totalLength = 0;
319     for ( int i = firstInGroup( row ); i <= lastInGroup( row ); i++ )
320     {
321         Meta::TrackPtr track = m_belowModel->trackAt( i );
322         if ( track )
323             totalLength += track->length();
324         else
325             warning() << "Playlist::GroupingProxy::lengthOfGroup(): TrackPtr is 0!  i = " << i << ", rowCount = " << m_belowModel->rowCount();
326     }
327
328     return totalLength;
329 }
330
331 QString
332 Playlist::GroupingProxy::groupingCategory() const
333 {
334     return m_groupingCategory;
335 }
336
337 void
338 Playlist::GroupingProxy::setGroupingCategory( const QString &groupingCategory )
339 {
340     if( groupableCategories.contains( groupingCategory ) || groupingCategory == "None" || groupingCategory.isEmpty() )
341     {
342         m_groupingCategory = groupingCategory;
343         regroupAll();
344     }
345 }