File indexing completed on 2024-05-19 04:49:51

0001 /****************************************************************************************
0002  * Copyright (c) 2009 Téo Mrnjavac <teo@kde.org>                                        *
0003  * Copyright (c) 2010 Nanno Langstraat <langstr@gmail.com>                              *
0004  * Copyright (c) 2013 Konrad Zemek <konrad.zemek@gmail.com>                             *
0005  *                                                                                      *
0006  * This program is free software; you can redistribute it and/or modify it under        *
0007  * the terms of the GNU General Public License as published by the Free Software        *
0008  * Foundation; either version 2 of the License, or (at your option) any later           *
0009  * version.                                                                             *
0010  *                                                                                      *
0011  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0012  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0013  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0014  *                                                                                      *
0015  * You should have received a copy of the GNU General Public License along with         *
0016  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0017  ****************************************************************************************/
0018 
0019 #include "SortAlgorithms.h"
0020 
0021 #include "core/meta/Meta.h"
0022 #include "core/meta/Statistics.h"
0023 #include "core/support/Debug.h"
0024 #include "playlist/proxymodels/AbstractModel.h"
0025 
0026 #include <QDateTime>
0027 #include <QRandomGenerator>
0028 
0029 namespace Playlist
0030 {
0031 
0032 void
0033 multilevelLessThan::setSortScheme( const SortScheme & scheme )
0034 {
0035     m_scheme = scheme;
0036     m_randomSalt = QRandomGenerator::global()->generate();    //! Do a different random sort order every time.
0037 }
0038 
0039 bool
0040 multilevelLessThan::operator()( const QAbstractItemModel* sourceModel,
0041                                 int sourceModelRowA, int sourceModelRowB ) const
0042 {
0043     // Handle "Last Played" as a special case because the time since last played is not
0044     // reported as an int in the data columns. Handle Title, Album, Artist as special
0045     // cases with Meta::Base::sortableName(). This is necessary in order to have the same
0046     // sort order policy regarding "The" in both the playlist and the collection browser.
0047     QSet< Playlist::Column > specialCases;
0048     specialCases << Playlist::LastPlayed << Playlist::Title << Playlist::Album
0049                  << Playlist::Artist << Playlist::AlbumArtist;
0050 
0051     foreach( const SortLevel &level, m_scheme )
0052     {
0053         const bool inverted = ( level.order() == Qt::DescendingOrder );
0054         const Playlist::Column currentCategory = level.category();
0055 
0056         const QModelIndex indexA = sourceModel->index( sourceModelRowA, currentCategory );
0057         const QModelIndex indexB = sourceModel->index( sourceModelRowB, currentCategory );
0058 
0059         const Meta::TrackPtr trackA = indexA.data( TrackRole ).value<Meta::TrackPtr>();
0060         const Meta::TrackPtr trackB = indexB.data( TrackRole ).value<Meta::TrackPtr>();
0061 
0062         if( trackA && trackB && specialCases.contains( currentCategory ) )
0063         {
0064             switch( currentCategory )
0065             {
0066                 case Playlist::LastPlayed:
0067                 {
0068                     const QDateTime lastPlayedA = trackA->statistics()->lastPlayed();
0069                     const QDateTime lastPlayedB = trackB->statistics()->lastPlayed();
0070 
0071                     // The track with higher lastPlayed value was played more recently
0072                     //
0073                     // '!=' is the XOR operation; it simply negates the result if 'inverted'
0074                     // is true. It isn't necessary to do it this way, although later on it will
0075                     // ease figuring out what's actually being returned.
0076                     if( lastPlayedA != lastPlayedB )
0077                         return ( lastPlayedA > lastPlayedB ) != inverted;
0078 
0079                     break;
0080                 }
0081                 case Playlist::Title:
0082                 {
0083                     const int compareResult = compareBySortableName( trackA, trackB );
0084 
0085                     if( compareResult != 0 )
0086                         return ( compareResult < 0 ) != inverted;
0087 
0088                     break;
0089                 }
0090                 case Playlist::Album:
0091                 {
0092                     const int compareResult
0093                             = compareBySortableName( trackA->album(), trackB->album() );
0094 
0095                     if( compareResult != 0 )
0096                         return ( compareResult < 0 ) != inverted;
0097 
0098                     // Fall through to sorting by album artist if albums have same name
0099                     Q_FALLTHROUGH();
0100                 }
0101                 case Playlist::AlbumArtist:
0102                 {
0103                     const Meta::ArtistPtr artistA =
0104                             (trackA->album() ? trackA->album()->albumArtist() : Meta::ArtistPtr());
0105 
0106                     const Meta::ArtistPtr artistB =
0107                             (trackB->album() ? trackB->album()->albumArtist() : Meta::ArtistPtr());
0108 
0109                     const int compareResult = compareBySortableName( artistA, artistB );
0110 
0111                     if( compareResult != 0 )
0112                         return ( compareResult < 0 ) != inverted;
0113 
0114                     break;
0115                 }
0116                 case Playlist::Artist:
0117                 {
0118                     const int compareResult
0119                             = compareBySortableName( trackA->artist(), trackB->artist() );
0120 
0121                     if( compareResult != 0 )
0122                         return ( compareResult < 0 ) != inverted;
0123 
0124                     break;
0125                 }
0126                 default:
0127                     warning() << "One of the cases in specialCases set has not received special treatment!";
0128                     break;
0129             }
0130         }
0131         else // either it's not a special case, or we don't have means (TrackPtrs) to handle it
0132         {
0133             const QVariant dataA = indexA.data( Qt::DisplayRole );
0134             const QVariant dataB = indexB.data( Qt::DisplayRole );
0135 
0136             if( level.isString() )
0137             {
0138                 const int compareResult =
0139                         dataA.toString().compare(dataB.toString(),
0140                                                  Qt::CaseInsensitive);
0141                 if( compareResult != 0 )
0142                     return ( compareResult < 0 ) != inverted;
0143             }
0144             else if( level.isFloat() )
0145             {
0146                 if( dataA.toDouble() != dataB.toDouble() )
0147                     return ( dataA.toDouble() < dataB.toDouble() ) != inverted;
0148             }
0149             else // if it's neither a string nor a float ==> it's an integer
0150             {
0151                 if( dataA.toInt() != dataB.toInt() )
0152                     return ( dataA.toInt() < dataB.toInt() ) != inverted;
0153             }
0154         }
0155     }
0156 
0157     // Tie breaker: order by row number
0158     return ( sourceModelRowA < sourceModelRowB );
0159 }
0160 
0161 template<typename T>
0162 int
0163 multilevelLessThan::compareBySortableName( const AmarokSharedPointer<T> &left,
0164                                            const AmarokSharedPointer<T> &right ) const
0165 {
0166     if( !left && right )
0167         return -1;
0168     else if( left && !right )
0169         return 1;
0170     else if( left && right )
0171         return left->sortableName().compare( right->sortableName(),
0172                                              Qt::CaseInsensitive );
0173     return 0;
0174 }
0175 
0176 }   //namespace Playlist