File indexing completed on 2024-05-19 04:50:29

0001 /****************************************************************************************
0002  * Copyright (c) 2012 Matěj Laitl <matej@laitl.cz>                                      *
0003  *                                                                                      *
0004  * This program is free software; you can redistribute it and/or modify it under        *
0005  * the terms of the GNU General Public License as published by the Free Software        *
0006  * Foundation; either version 2 of the License, or (at your option) any later           *
0007  * version.                                                                             *
0008  *                                                                                      *
0009  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0010  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0011  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0012  *                                                                                      *
0013  * You should have received a copy of the GNU General Public License along with         *
0014  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0015  ****************************************************************************************/
0016 
0017 #include "MatchedTracksModel.h"
0018 
0019 #include "MetaValues.h"
0020 #include "core/meta/support/MetaConstants.h"
0021 #include "core/support/Debug.h"
0022 #include "statsyncing/TrackTuple.h"
0023 
0024 #include <KColorScheme>
0025 #include <KLocalizedString>
0026 
0027 using namespace StatSyncing;
0028 
0029 static const quintptr tupleIndexIndernalId = 0;
0030 
0031 MatchedTracksModel::MatchedTracksModel( const QList<TrackTuple> &matchedTuples,
0032     const QList<qint64> &columns, const Options &options, QObject *parent )
0033     : QAbstractItemModel( parent )
0034     , CommonModel( columns, options )
0035     , m_matchedTuples( matchedTuples )
0036 {
0037     m_titleColumn = m_columns.indexOf( Meta::valTitle );
0038 }
0039 
0040 QModelIndex
0041 MatchedTracksModel::index( int row, int column, const QModelIndex &parent ) const
0042 {
0043     if( !parent.isValid() && column >= 0 && column < m_columns.count() )
0044         return createIndex( row, column, tupleIndexIndernalId );
0045     if( parent.internalId() == tupleIndexIndernalId &&
0046         parent.row() >= 0 && parent.row() < m_matchedTuples.count() &&
0047         parent.column() == m_titleColumn &&
0048         row >= 0 && row < m_matchedTuples.at( parent.row() ).count() &&
0049         column >=0 && column < m_columns.count() )
0050     {
0051         return createIndex( row, column, parent.row() );
0052     }
0053     return QModelIndex();
0054 }
0055 
0056 QModelIndex
0057 MatchedTracksModel::parent( const QModelIndex &child ) const
0058 {
0059     if( !child.isValid() || child.internalId() == tupleIndexIndernalId )
0060         return QModelIndex();
0061     return createIndex( child.internalId(), m_titleColumn, tupleIndexIndernalId );
0062 }
0063 
0064 bool
0065 MatchedTracksModel::hasChildren( const QModelIndex &parent ) const
0066 {
0067     if( !parent.isValid() )
0068         return !m_matchedTuples.isEmpty();
0069     if( parent.internalId() == tupleIndexIndernalId &&
0070         parent.row() >= 0 && parent.row() < m_matchedTuples.count() &&
0071         parent.column() == m_titleColumn )
0072     {
0073         return true; // we expect only nonempty tuples
0074     }
0075     return false; // leaf node
0076 }
0077 
0078 int
0079 MatchedTracksModel::rowCount( const QModelIndex &parent ) const
0080 {
0081     if( !parent.isValid() )
0082         return m_matchedTuples.count();
0083     if( parent.internalId() == tupleIndexIndernalId &&
0084         parent.column() == m_titleColumn )
0085         return m_matchedTuples.value( parent.row() ).count(); // handles invalid row numbers gracefully
0086     return 0; // parent is leaf node
0087 }
0088 
0089 int
0090 MatchedTracksModel::columnCount( const QModelIndex &parent ) const
0091 {
0092     if( !parent.isValid() ||
0093         ( parent.internalId() == tupleIndexIndernalId && parent.column() == m_titleColumn ) )
0094     {
0095         return m_columns.count();
0096     }
0097     return 0; // parent is leaf node
0098 }
0099 
0100 QVariant
0101 MatchedTracksModel::headerData( int section, Qt::Orientation orientation, int role ) const
0102 {
0103     return CommonModel::headerData( section, orientation, role );
0104 }
0105 
0106 QVariant
0107 MatchedTracksModel::data( const QModelIndex &index, int role ) const
0108 {
0109     if( !index.isValid() || index.column() < 0 || index.column() >= m_columns.count() )
0110         return QVariant();
0111 
0112     qint64 field = m_columns.at( index.column() );
0113     if( index.internalId() == tupleIndexIndernalId )
0114     {
0115         TrackTuple tuple = m_matchedTuples.value( index.row() );
0116         if( tuple.isEmpty() )
0117             return QVariant();
0118         return tupleData( tuple, field, role );
0119     }
0120     else if( index.internalId() < (quintptr)m_matchedTuples.count() )
0121     {
0122         TrackTuple tuple = m_matchedTuples.value( index.internalId() );
0123         ProviderPtr provider = tuple.provider( index.row() );
0124         if( !provider )
0125             return QVariant();
0126         return trackData( provider, tuple, field, role );
0127     }
0128     return QVariant();
0129 }
0130 
0131 bool
0132 MatchedTracksModel::setData( const QModelIndex &idx, const QVariant &value, int role )
0133 {
0134     if( !idx.isValid() ||
0135         idx.internalId() >= (quintptr)m_matchedTuples.count() ||
0136         role != Qt::CheckStateRole )
0137     {
0138         return false;
0139     }
0140     qint64 field = m_columns.value( idx.column() );
0141     TrackTuple &tuple = m_matchedTuples[ idx.internalId() ]; // we need reference
0142     ProviderPtr provider = tuple.provider( idx.row() );
0143     if( !provider )
0144         return false;
0145 
0146     switch( field )
0147     {
0148         case Meta::valRating:
0149             switch( Qt::CheckState( value.toInt() ) )
0150             {
0151                 case Qt::Checked:
0152                     tuple.setRatingProvider( provider );
0153                     break;
0154                 case Qt::Unchecked:
0155                     tuple.setRatingProvider( ProviderPtr() );
0156                     break;
0157                 default:
0158                     return false;
0159             }
0160             break;
0161         case Meta::valLabel:
0162         {
0163             ProviderPtrSet labelProviders = tuple.labelProviders();
0164             switch( Qt::CheckState( value.toInt() ) )
0165             {
0166                 case Qt::Checked:
0167                     labelProviders.insert( provider );
0168                     tuple.setLabelProviders( labelProviders );
0169                     break;
0170                 case Qt::Unchecked:
0171                     labelProviders.remove( provider );
0172                     tuple.setLabelProviders( labelProviders );
0173                     break;
0174                 default:
0175                     return false;
0176             }
0177             break;
0178         }
0179         default:
0180             return false;
0181     }
0182 
0183     // parent changes:
0184     QModelIndex parent = idx.parent();
0185     QModelIndex parentRating = index( parent.row(), idx.column(), parent.parent() );
0186     Q_EMIT dataChanged( parentRating, parentRating );
0187 
0188     // children change:
0189     QModelIndex topLeft = index( 0, idx.column(), parent );
0190     QModelIndex bottomRight = index( tuple.count() - 1, idx.column(), parent );
0191     Q_EMIT dataChanged( topLeft, bottomRight );
0192     return true;
0193 }
0194 
0195 Qt::ItemFlags
0196 MatchedTracksModel::flags( const QModelIndex &index ) const
0197 {
0198     // many false positives here, but no-one is hurt
0199     return QAbstractItemModel::flags( index ) | Qt::ItemIsUserCheckable;
0200 }
0201 
0202 const QList<TrackTuple> &
0203 MatchedTracksModel::matchedTuples()
0204 {
0205     return m_matchedTuples;
0206 }
0207 
0208 bool
0209 MatchedTracksModel::hasUpdate() const
0210 {
0211     foreach( const TrackTuple &tuple, m_matchedTuples )
0212     {
0213         if( tuple.hasUpdate( m_options ) )
0214             return true;
0215     }
0216     return false;
0217 }
0218 
0219 bool
0220 MatchedTracksModel::hasConflict( int i ) const
0221 {
0222     if( i >= 0 )
0223         return m_matchedTuples.value( i ).hasConflict( m_options );
0224     foreach( const TrackTuple &tuple, m_matchedTuples )
0225     {
0226         if( tuple.hasConflict( m_options ) )
0227             return true;
0228     }
0229     return false;
0230 }
0231 
0232 void
0233 MatchedTracksModel::takeRatingsFrom( const ProviderPtr &provider )
0234 {
0235     for( int i = 0; i < m_matchedTuples.count(); i++ )
0236     {
0237         TrackTuple &tuple = m_matchedTuples[ i ]; // we need reference
0238         if( !tuple.fieldHasConflict( Meta::valRating, m_options ) )
0239             continue;
0240 
0241         if( tuple.ratingProvider() == provider )
0242             continue; // short-cut
0243         tuple.setRatingProvider( provider ); // does nothing if non-null provider isn't in tuple
0244 
0245         // parent changes:
0246         int ratingColumn = m_columns.indexOf( Meta::valRating );
0247         QModelIndex parentRating = index( i, ratingColumn );
0248         Q_EMIT dataChanged( parentRating, parentRating );
0249 
0250         // children change:
0251         QModelIndex parent = index( i, 0 );
0252         QModelIndex topLeft = index( 0, ratingColumn, parent );
0253         QModelIndex bottomRight = index( tuple.count() - 1, ratingColumn, parent );
0254         Q_EMIT dataChanged( topLeft, bottomRight );
0255     }
0256 }
0257 
0258 void
0259 MatchedTracksModel::includeLabelsFrom( const ProviderPtr &provider )
0260 {
0261     if( !provider )
0262         return; // has no sense
0263     for( int i = 0; i < m_matchedTuples.count(); i++ )
0264     {
0265         TrackTuple &tuple = m_matchedTuples[ i ]; // we need reference
0266         if( !tuple.fieldHasConflict( Meta::valLabel, m_options ) )
0267             continue;
0268         ProviderPtrSet providers = tuple.labelProviders();
0269         providers.insert( provider );
0270 
0271         if( providers == tuple.labelProviders() )
0272             continue; // short-cut
0273         tuple.setLabelProviders( providers ); // does nothing if provider isn't in tuple
0274 
0275         // parent changes:
0276         int ratingColumn = m_columns.indexOf( Meta::valRating );
0277         QModelIndex parentRating = index( i, ratingColumn );
0278         Q_EMIT dataChanged( parentRating, parentRating );
0279 
0280         // children change:
0281         QModelIndex parent = index( i, 0 );
0282         QModelIndex topLeft = index( 0, ratingColumn, parent );
0283         QModelIndex bottomRight = index( tuple.count() - 1, ratingColumn, parent );
0284         Q_EMIT dataChanged( topLeft, bottomRight );
0285     }
0286 }
0287 
0288 void
0289 MatchedTracksModel::excludeLabelsFrom( const ProviderPtr &provider )
0290 {
0291     for( int i = 0; i < m_matchedTuples.count(); i++ )
0292     {
0293         TrackTuple &tuple = m_matchedTuples[ i ]; // we need reference
0294         if( !tuple.fieldHasConflict( Meta::valLabel, m_options ) )
0295             continue;
0296         ProviderPtrSet providers = tuple.labelProviders();
0297         if( provider )
0298             // normal more, remove one provider
0299             providers.remove( provider );
0300         else
0301             // reset mode, clear providers
0302             providers.clear();
0303 
0304         if( providers == tuple.labelProviders() )
0305             continue; // short-cut
0306         tuple.setLabelProviders( providers ); // does nothing if provider isn't in tuple
0307 
0308         // parent changes:
0309         int ratingColumn = m_columns.indexOf( Meta::valRating );
0310         QModelIndex parentRating = index( i, ratingColumn );
0311         Q_EMIT dataChanged( parentRating, parentRating );
0312 
0313         // children change:
0314         QModelIndex parent = index( i, 0 );
0315         QModelIndex topLeft = index( 0, ratingColumn, parent );
0316         QModelIndex bottomRight = index( tuple.count() - 1, ratingColumn, parent );
0317         Q_EMIT dataChanged( topLeft, bottomRight );
0318     }
0319 }
0320 
0321 QVariant
0322 MatchedTracksModel::tupleData( const TrackTuple &tuple, qint64 field, int role ) const
0323 {
0324     ProviderPtr firstProvider = tuple.provider( 0 );
0325     TrackPtr first = tuple.track( firstProvider );
0326     switch( role )
0327     {
0328         case Qt::DisplayRole:
0329             switch( field )
0330             {
0331                 case Meta::valTitle:
0332                     return trackTitleData( first );
0333                 case Meta::valRating:
0334                     return tuple.syncedRating( m_options );
0335                 case Meta::valFirstPlayed:
0336                     return tuple.syncedFirstPlayed( m_options );
0337                 case Meta::valLastPlayed:
0338                     return tuple.syncedLastPlayed( m_options );
0339                 case Meta::valPlaycount:
0340                     return tuple.syncedPlaycount( m_options );
0341                 case Meta::valLabel:
0342                     if( tuple.fieldHasConflict( field, m_options, /* includeResolved */ false ) )
0343                         return -1; // display same icon as for rating conflict
0344                     return QStringList( tuple.syncedLabels( m_options ).values() ).join(
0345                         i18nc( "comma between list words", ", " ) );
0346                 default:
0347                     return QStringLiteral( "Unknown field!" );
0348             }
0349             break;
0350         case Qt::ToolTipRole:
0351             switch( field )
0352             {
0353                 case Meta::valTitle:
0354                     return trackToolTipData( first ); // TODO way to specify which additional meta-data to display
0355                 case Meta::valLabel:
0356                     return QStringList( tuple.syncedLabels( m_options ).values() ).join(
0357                         i18nc( "comma between list words", ", " ) );
0358             }
0359             break;
0360         case Qt::BackgroundRole:
0361             if( tuple.fieldUpdated( field, m_options ) )
0362                 return KColorScheme( QPalette::Active ).background( KColorScheme::PositiveBackground );
0363             break;
0364         case Qt::TextAlignmentRole:
0365             return textAlignmentData( field );
0366         case Qt::SizeHintRole:
0367             return sizeHintData( field );
0368         case CommonModel::FieldRole:
0369             return field;
0370         case TupleFlagsRole:
0371             int flags = tuple.hasUpdate( m_options ) ? HasUpdate : 0;
0372             flags |= tuple.hasConflict( m_options ) ? HasConflict : 0;
0373             return flags;
0374     }
0375     return QVariant();
0376 }
0377 
0378 QVariant
0379 MatchedTracksModel::trackData( ProviderPtr provider, const TrackTuple &tuple,
0380                                qint64 field, int role ) const
0381 {
0382     TrackPtr track = tuple.track( provider );
0383 
0384     if( role == Qt::DisplayRole && field == Meta::valTitle )
0385         return provider->prettyName();
0386     else if( role == Qt::DecorationRole && field == Meta::valTitle )
0387         return provider->icon();
0388     // no special background if the field in whole tuple is not updated
0389     else if( role == Qt::BackgroundRole && tuple.fieldUpdated( field, m_options ) )
0390     {
0391         KColorScheme::BackgroundRole backgroundRole =
0392                 tuple.fieldUpdated( field, m_options, provider ) ? KColorScheme::NegativeBackground
0393                                                                  : KColorScheme::PositiveBackground;
0394         return KColorScheme( QPalette::Active ).background( backgroundRole );
0395     }
0396     else if( role == Qt::CheckStateRole && tuple.fieldHasConflict( field, m_options ) )
0397     {
0398         switch( field )
0399         {
0400             case Meta::valRating:
0401                 return ( tuple.ratingProvider() == provider ) ? Qt::Checked : Qt::Unchecked;
0402             case Meta::valLabel:
0403                 return ( tuple.labelProviders().contains( provider ) ) ? Qt::Checked : Qt::Unchecked;
0404             default:
0405                 warning() << __PRETTY_FUNCTION__ << "this should be never reached";
0406         }
0407     }
0408     return trackData( track, field, role );
0409 }