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 }