1 /****************************************************************************************
2  * Copyright (c) 2009 Téo Mrnjavac <teo@kde.org>                                        *
3  * Copyright (c) 2010 Nanno Langstraat <langstr@gmail.com>                              *
4  * Copyright (c) 2013 Konrad Zemek <konrad.zemek@gmail.com>                             *
5  *                                                                                      *
6  * This program is free software; you can redistribute it and/or modify it under        *
7  * the terms of the GNU General Public License as published by the Free Software        *
8  * Foundation; either version 2 of the License, or (at your option) any later           *
9  * version.                                                                             *
10  *                                                                                      *
11  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
12  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
13  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
14  *                                                                                      *
15  * You should have received a copy of the GNU General Public License along with         *
16  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
17  ****************************************************************************************/
18 
19 #include "SortAlgorithms.h"
20 
21 #include "core/meta/Meta.h"
22 #include "core/meta/Statistics.h"
23 #include "core/support/Debug.h"
24 #include "playlist/proxymodels/AbstractModel.h"
25 
26 #include <QDateTime>
27 
28 namespace Playlist
29 {
30 
31 void
setSortScheme(const SortScheme & scheme)32 multilevelLessThan::setSortScheme( const SortScheme & scheme )
33 {
34     m_scheme = scheme;
35     m_randomSalt = qrand();    //! Do a different random sort order every time.
36 }
37 
38 bool
operator ()(const QAbstractItemModel * sourceModel,int sourceModelRowA,int sourceModelRowB) const39 multilevelLessThan::operator()( const QAbstractItemModel* sourceModel,
40                                 int sourceModelRowA, int sourceModelRowB ) const
41 {
42     // Handle "Last Played" as a special case because the time since last played is not
43     // reported as an int in the data columns. Handle Title, Album, Artist as special
44     // cases with Meta::Base::sortableName(). This is necessary in order to have the same
45     // sort order policy regarding "The" in both the playlist and the collection browser.
46     QSet< Playlist::Column > specialCases;
47     specialCases << Playlist::LastPlayed << Playlist::Title << Playlist::Album
48                  << Playlist::Artist << Playlist::AlbumArtist;
49 
50     foreach( const SortLevel &level, m_scheme )
51     {
52         const bool inverted = ( level.order() == Qt::DescendingOrder );
53         const Playlist::Column currentCategory = level.category();
54 
55         const QModelIndex indexA = sourceModel->index( sourceModelRowA, currentCategory );
56         const QModelIndex indexB = sourceModel->index( sourceModelRowB, currentCategory );
57 
58         const Meta::TrackPtr trackA = indexA.data( TrackRole ).value<Meta::TrackPtr>();
59         const Meta::TrackPtr trackB = indexB.data( TrackRole ).value<Meta::TrackPtr>();
60 
61         if( trackA && trackB && specialCases.contains( currentCategory ) )
62         {
63             switch( currentCategory )
64             {
65                 case Playlist::LastPlayed:
66                 {
67                     const QDateTime lastPlayedA = trackA->statistics()->lastPlayed();
68                     const QDateTime lastPlayedB = trackB->statistics()->lastPlayed();
69 
70                     // The track with higher lastPlayed value was played more recently
71                     //
72                     // '!=' is the XOR operation; it simply negates the result if 'inverted'
73                     // is true. It isn't necessary to do it this way, although later on it will
74                     // ease figuring out what's actually being returned.
75                     if( lastPlayedA != lastPlayedB )
76                         return ( lastPlayedA > lastPlayedB ) != inverted;
77 
78                     break;
79                 }
80                 case Playlist::Title:
81                 {
82                     const int compareResult = compareBySortableName( trackA, trackB );
83 
84                     if( compareResult != 0 )
85                         return ( compareResult < 0 ) != inverted;
86 
87                     break;
88                 }
89                 case Playlist::Album:
90                 {
91                     const int compareResult
92                             = compareBySortableName( trackA->album(), trackB->album() );
93 
94                     if( compareResult != 0 )
95                         return ( compareResult < 0 ) != inverted;
96 
97                     // Fall through to sorting by album artist if albums have same name
98                     Q_FALLTHROUGH();
99                 }
100                 case Playlist::AlbumArtist:
101                 {
102                     const Meta::ArtistPtr artistA =
103                             (trackA->album() ? trackA->album()->albumArtist() : Meta::ArtistPtr());
104 
105                     const Meta::ArtistPtr artistB =
106                             (trackB->album() ? trackB->album()->albumArtist() : Meta::ArtistPtr());
107 
108                     const int compareResult = compareBySortableName( artistA, artistB );
109 
110                     if( compareResult != 0 )
111                         return ( compareResult < 0 ) != inverted;
112 
113                     break;
114                 }
115                 case Playlist::Artist:
116                 {
117                     const int compareResult
118                             = compareBySortableName( trackA->artist(), trackB->artist() );
119 
120                     if( compareResult != 0 )
121                         return ( compareResult < 0 ) != inverted;
122 
123                     break;
124                 }
125                 default:
126                     warning() << "One of the cases in specialCases set has not received special treatment!";
127                     break;
128             }
129         }
130         else // either it's not a special case, or we don't have means (TrackPtrs) to handle it
131         {
132             const QVariant dataA = indexA.data( Qt::DisplayRole );
133             const QVariant dataB = indexB.data( Qt::DisplayRole );
134 
135             if( level.isString() )
136             {
137                 const int compareResult =
138                         dataA.toString().compare(dataB.toString(),
139                                                  Qt::CaseInsensitive);
140                 if( compareResult != 0 )
141                     return ( compareResult < 0 ) != inverted;
142             }
143             else if( level.isFloat() )
144             {
145                 if( dataA.toDouble() != dataB.toDouble() )
146                     return ( dataA.toDouble() < dataB.toDouble() ) != inverted;
147             }
148             else // if it's neither a string nor a float ==> it's an integer
149             {
150                 if( dataA.toInt() != dataB.toInt() )
151                     return ( dataA.toInt() < dataB.toInt() ) != inverted;
152             }
153         }
154     }
155 
156     // Tie breaker: order by row number
157     return ( sourceModelRowA < sourceModelRowB );
158 }
159 
160 template<typename T>
161 int
compareBySortableName(const AmarokSharedPointer<T> & left,const AmarokSharedPointer<T> & right) const162 multilevelLessThan::compareBySortableName( const AmarokSharedPointer<T> &left,
163                                            const AmarokSharedPointer<T> &right ) const
164 {
165     if( !left && right )
166         return -1;
167     else if( left && !right )
168         return 1;
169     else if( left && right )
170         return left->sortableName().compare( right->sortableName(),
171                                              Qt::CaseInsensitive );
172     return 0;
173 }
174 
175 }   //namespace Playlist
176