1 /****************************************************************************************
2  * Copyright (c) 2012 Matěj Laitl <matej@laitl.cz>                                      *
3  *                                                                                      *
4  * This program is free software; you can redistribute it and/or modify it under        *
5  * the terms of the GNU General Public License as published by the Free Software        *
6  * Foundation; either version 2 of the License, or (at your option) any later           *
7  * version.                                                                             *
8  *                                                                                      *
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
11  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
12  *                                                                                      *
13  * You should have received a copy of the GNU General Public License along with         *
14  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
15  ****************************************************************************************/
16 
17 #include "MatchedTracksModel.h"
18 
19 #include "MetaValues.h"
20 #include "core/meta/support/MetaConstants.h"
21 #include "core/support/Debug.h"
22 #include "statsyncing/TrackTuple.h"
23 
24 #include <KColorScheme>
25 #include <KLocalizedString>
26 
27 using namespace StatSyncing;
28 
29 static const quintptr tupleIndexIndernalId = 0;
30 
MatchedTracksModel(const QList<TrackTuple> & matchedTuples,const QList<qint64> & columns,const Options & options,QObject * parent)31 MatchedTracksModel::MatchedTracksModel( const QList<TrackTuple> &matchedTuples,
32     const QList<qint64> &columns, const Options &options, QObject *parent )
33     : QAbstractItemModel( parent )
34     , CommonModel( columns, options )
35     , m_matchedTuples( matchedTuples )
36 {
37     m_titleColumn = m_columns.indexOf( Meta::valTitle );
38 }
39 
40 QModelIndex
index(int row,int column,const QModelIndex & parent) const41 MatchedTracksModel::index( int row, int column, const QModelIndex &parent ) const
42 {
43     if( !parent.isValid() && column >= 0 && column < m_columns.count() )
44         return createIndex( row, column, tupleIndexIndernalId );
45     if( parent.internalId() == tupleIndexIndernalId &&
46         parent.row() >= 0 && parent.row() < m_matchedTuples.count() &&
47         parent.column() == m_titleColumn &&
48         row >= 0 && row < m_matchedTuples.at( parent.row() ).count() &&
49         column >=0 && column < m_columns.count() )
50     {
51         return createIndex( row, column, parent.row() );
52     }
53     return QModelIndex();
54 }
55 
56 QModelIndex
parent(const QModelIndex & child) const57 MatchedTracksModel::parent( const QModelIndex &child ) const
58 {
59     if( !child.isValid() || child.internalId() == tupleIndexIndernalId )
60         return QModelIndex();
61     return createIndex( child.internalId(), m_titleColumn, tupleIndexIndernalId );
62 }
63 
64 bool
hasChildren(const QModelIndex & parent) const65 MatchedTracksModel::hasChildren( const QModelIndex &parent ) const
66 {
67     if( !parent.isValid() )
68         return !m_matchedTuples.isEmpty();
69     if( parent.internalId() == tupleIndexIndernalId &&
70         parent.row() >= 0 && parent.row() < m_matchedTuples.count() &&
71         parent.column() == m_titleColumn )
72     {
73         return true; // we expect only nonempty tuples
74     }
75     return false; // leaf node
76 }
77 
78 int
rowCount(const QModelIndex & parent) const79 MatchedTracksModel::rowCount( const QModelIndex &parent ) const
80 {
81     if( !parent.isValid() )
82         return m_matchedTuples.count();
83     if( parent.internalId() == tupleIndexIndernalId &&
84         parent.column() == m_titleColumn )
85         return m_matchedTuples.value( parent.row() ).count(); // handles invalid row numbers gracefully
86     return 0; // parent is leaf node
87 }
88 
89 int
columnCount(const QModelIndex & parent) const90 MatchedTracksModel::columnCount( const QModelIndex &parent ) const
91 {
92     if( !parent.isValid() ||
93         ( parent.internalId() == tupleIndexIndernalId && parent.column() == m_titleColumn ) )
94     {
95         return m_columns.count();
96     }
97     return 0; // parent is leaf node
98 }
99 
100 QVariant
headerData(int section,Qt::Orientation orientation,int role) const101 MatchedTracksModel::headerData( int section, Qt::Orientation orientation, int role ) const
102 {
103     return CommonModel::headerData( section, orientation, role );
104 }
105 
106 QVariant
data(const QModelIndex & index,int role) const107 MatchedTracksModel::data( const QModelIndex &index, int role ) const
108 {
109     if( !index.isValid() || index.column() < 0 || index.column() >= m_columns.count() )
110         return QVariant();
111 
112     qint64 field = m_columns.at( index.column() );
113     if( index.internalId() == tupleIndexIndernalId )
114     {
115         TrackTuple tuple = m_matchedTuples.value( index.row() );
116         if( tuple.isEmpty() )
117             return QVariant();
118         return tupleData( tuple, field, role );
119     }
120     else if( index.internalId() < (quintptr)m_matchedTuples.count() )
121     {
122         TrackTuple tuple = m_matchedTuples.value( index.internalId() );
123         ProviderPtr provider = tuple.provider( index.row() );
124         if( !provider )
125             return QVariant();
126         return trackData( provider, tuple, field, role );
127     }
128     return QVariant();
129 }
130 
131 bool
setData(const QModelIndex & idx,const QVariant & value,int role)132 MatchedTracksModel::setData( const QModelIndex &idx, const QVariant &value, int role )
133 {
134     if( !idx.isValid() ||
135         idx.internalId() >= (quintptr)m_matchedTuples.count() ||
136         role != Qt::CheckStateRole )
137     {
138         return false;
139     }
140     qint64 field = m_columns.value( idx.column() );
141     TrackTuple &tuple = m_matchedTuples[ idx.internalId() ]; // we need reference
142     ProviderPtr provider = tuple.provider( idx.row() );
143     if( !provider )
144         return false;
145 
146     switch( field )
147     {
148         case Meta::valRating:
149             switch( Qt::CheckState( value.toInt() ) )
150             {
151                 case Qt::Checked:
152                     tuple.setRatingProvider( provider );
153                     break;
154                 case Qt::Unchecked:
155                     tuple.setRatingProvider( ProviderPtr() );
156                     break;
157                 default:
158                     return false;
159             }
160             break;
161         case Meta::valLabel:
162         {
163             ProviderPtrSet labelProviders = tuple.labelProviders();
164             switch( Qt::CheckState( value.toInt() ) )
165             {
166                 case Qt::Checked:
167                     labelProviders.insert( provider );
168                     tuple.setLabelProviders( labelProviders );
169                     break;
170                 case Qt::Unchecked:
171                     labelProviders.remove( provider );
172                     tuple.setLabelProviders( labelProviders );
173                     break;
174                 default:
175                     return false;
176             }
177             break;
178         }
179         default:
180             return false;
181     }
182 
183     // parent changes:
184     QModelIndex parent = idx.parent();
185     QModelIndex parentRating = index( parent.row(), idx.column(), parent.parent() );
186     Q_EMIT dataChanged( parentRating, parentRating );
187 
188     // children change:
189     QModelIndex topLeft = index( 0, idx.column(), parent );
190     QModelIndex bottomRight = index( tuple.count() - 1, idx.column(), parent );
191     Q_EMIT dataChanged( topLeft, bottomRight );
192     return true;
193 }
194 
195 Qt::ItemFlags
flags(const QModelIndex & index) const196 MatchedTracksModel::flags( const QModelIndex &index ) const
197 {
198     // many false positives here, but no-one is hurt
199     return QAbstractItemModel::flags( index ) | Qt::ItemIsUserCheckable;
200 }
201 
202 const QList<TrackTuple> &
matchedTuples()203 MatchedTracksModel::matchedTuples()
204 {
205     return m_matchedTuples;
206 }
207 
208 bool
hasUpdate() const209 MatchedTracksModel::hasUpdate() const
210 {
211     foreach( const TrackTuple &tuple, m_matchedTuples )
212     {
213         if( tuple.hasUpdate( m_options ) )
214             return true;
215     }
216     return false;
217 }
218 
219 bool
hasConflict(int i) const220 MatchedTracksModel::hasConflict( int i ) const
221 {
222     if( i >= 0 )
223         return m_matchedTuples.value( i ).hasConflict( m_options );
224     foreach( const TrackTuple &tuple, m_matchedTuples )
225     {
226         if( tuple.hasConflict( m_options ) )
227             return true;
228     }
229     return false;
230 }
231 
232 void
takeRatingsFrom(const ProviderPtr & provider)233 MatchedTracksModel::takeRatingsFrom( const ProviderPtr &provider )
234 {
235     for( int i = 0; i < m_matchedTuples.count(); i++ )
236     {
237         TrackTuple &tuple = m_matchedTuples[ i ]; // we need reference
238         if( !tuple.fieldHasConflict( Meta::valRating, m_options ) )
239             continue;
240 
241         if( tuple.ratingProvider() == provider )
242             continue; // short-cut
243         tuple.setRatingProvider( provider ); // does nothing if non-null provider isn't in tuple
244 
245         // parent changes:
246         int ratingColumn = m_columns.indexOf( Meta::valRating );
247         QModelIndex parentRating = index( i, ratingColumn );
248         Q_EMIT dataChanged( parentRating, parentRating );
249 
250         // children change:
251         QModelIndex parent = index( i, 0 );
252         QModelIndex topLeft = index( 0, ratingColumn, parent );
253         QModelIndex bottomRight = index( tuple.count() - 1, ratingColumn, parent );
254         Q_EMIT dataChanged( topLeft, bottomRight );
255     }
256 }
257 
258 void
includeLabelsFrom(const ProviderPtr & provider)259 MatchedTracksModel::includeLabelsFrom( const ProviderPtr &provider )
260 {
261     if( !provider )
262         return; // has no sense
263     for( int i = 0; i < m_matchedTuples.count(); i++ )
264     {
265         TrackTuple &tuple = m_matchedTuples[ i ]; // we need reference
266         if( !tuple.fieldHasConflict( Meta::valLabel, m_options ) )
267             continue;
268         ProviderPtrSet providers = tuple.labelProviders();
269         providers.insert( provider );
270 
271         if( providers == tuple.labelProviders() )
272             continue; // short-cut
273         tuple.setLabelProviders( providers ); // does nothing if provider isn't in tuple
274 
275         // parent changes:
276         int ratingColumn = m_columns.indexOf( Meta::valRating );
277         QModelIndex parentRating = index( i, ratingColumn );
278         Q_EMIT dataChanged( parentRating, parentRating );
279 
280         // children change:
281         QModelIndex parent = index( i, 0 );
282         QModelIndex topLeft = index( 0, ratingColumn, parent );
283         QModelIndex bottomRight = index( tuple.count() - 1, ratingColumn, parent );
284         Q_EMIT dataChanged( topLeft, bottomRight );
285     }
286 }
287 
288 void
excludeLabelsFrom(const ProviderPtr & provider)289 MatchedTracksModel::excludeLabelsFrom( const ProviderPtr &provider )
290 {
291     for( int i = 0; i < m_matchedTuples.count(); i++ )
292     {
293         TrackTuple &tuple = m_matchedTuples[ i ]; // we need reference
294         if( !tuple.fieldHasConflict( Meta::valLabel, m_options ) )
295             continue;
296         ProviderPtrSet providers = tuple.labelProviders();
297         if( provider )
298             // normal more, remove one provider
299             providers.remove( provider );
300         else
301             // reset mode, clear providers
302             providers.clear();
303 
304         if( providers == tuple.labelProviders() )
305             continue; // short-cut
306         tuple.setLabelProviders( providers ); // does nothing if provider isn't in tuple
307 
308         // parent changes:
309         int ratingColumn = m_columns.indexOf( Meta::valRating );
310         QModelIndex parentRating = index( i, ratingColumn );
311         Q_EMIT dataChanged( parentRating, parentRating );
312 
313         // children change:
314         QModelIndex parent = index( i, 0 );
315         QModelIndex topLeft = index( 0, ratingColumn, parent );
316         QModelIndex bottomRight = index( tuple.count() - 1, ratingColumn, parent );
317         Q_EMIT dataChanged( topLeft, bottomRight );
318     }
319 }
320 
321 QVariant
tupleData(const TrackTuple & tuple,qint64 field,int role) const322 MatchedTracksModel::tupleData( const TrackTuple &tuple, qint64 field, int role ) const
323 {
324     ProviderPtr firstProvider = tuple.provider( 0 );
325     TrackPtr first = tuple.track( firstProvider );
326     switch( role )
327     {
328         case Qt::DisplayRole:
329             switch( field )
330             {
331                 case Meta::valTitle:
332                     return trackTitleData( first );
333                 case Meta::valRating:
334                     return tuple.syncedRating( m_options );
335                 case Meta::valFirstPlayed:
336                     return tuple.syncedFirstPlayed( m_options );
337                 case Meta::valLastPlayed:
338                     return tuple.syncedLastPlayed( m_options );
339                 case Meta::valPlaycount:
340                     return tuple.syncedPlaycount( m_options );
341                 case Meta::valLabel:
342                     if( tuple.fieldHasConflict( field, m_options, /* includeResolved */ false ) )
343                         return -1; // display same icon as for rating conflict
344                     return QStringList( tuple.syncedLabels( m_options ).toList() ).join(
345                         i18nc( "comma between list words", ", " ) );
346                 default:
347                     return QStringLiteral( "Unknown field!" );
348             }
349             break;
350         case Qt::ToolTipRole:
351             switch( field )
352             {
353                 case Meta::valTitle:
354                     return trackToolTipData( first ); // TODO way to specify which additional meta-data to display
355                 case Meta::valLabel:
356                     return QStringList( tuple.syncedLabels( m_options ).toList() ).join(
357                         i18nc( "comma between list words", ", " ) );
358             }
359             break;
360         case Qt::BackgroundRole:
361             if( tuple.fieldUpdated( field, m_options ) )
362                 return KColorScheme( QPalette::Active ).background( KColorScheme::PositiveBackground );
363             break;
364         case Qt::TextAlignmentRole:
365             return textAlignmentData( field );
366         case Qt::SizeHintRole:
367             return sizeHintData( field );
368         case CommonModel::FieldRole:
369             return field;
370         case TupleFlagsRole:
371             int flags = tuple.hasUpdate( m_options ) ? HasUpdate : 0;
372             flags |= tuple.hasConflict( m_options ) ? HasConflict : 0;
373             return flags;
374     }
375     return QVariant();
376 }
377 
378 QVariant
trackData(ProviderPtr provider,const TrackTuple & tuple,qint64 field,int role) const379 MatchedTracksModel::trackData( ProviderPtr provider, const TrackTuple &tuple,
380                                qint64 field, int role ) const
381 {
382     TrackPtr track = tuple.track( provider );
383 
384     if( role == Qt::DisplayRole && field == Meta::valTitle )
385         return provider->prettyName();
386     else if( role == Qt::DecorationRole && field == Meta::valTitle )
387         return provider->icon();
388     // no special background if the field in whole tuple is not updated
389     else if( role == Qt::BackgroundRole && tuple.fieldUpdated( field, m_options ) )
390     {
391         KColorScheme::BackgroundRole backgroundRole =
392                 tuple.fieldUpdated( field, m_options, provider ) ? KColorScheme::NegativeBackground
393                                                                  : KColorScheme::PositiveBackground;
394         return KColorScheme( QPalette::Active ).background( backgroundRole );
395     }
396     else if( role == Qt::CheckStateRole && tuple.fieldHasConflict( field, m_options ) )
397     {
398         switch( field )
399         {
400             case Meta::valRating:
401                 return ( tuple.ratingProvider() == provider ) ? Qt::Checked : Qt::Unchecked;
402             case Meta::valLabel:
403                 return ( tuple.labelProviders().contains( provider ) ) ? Qt::Checked : Qt::Unchecked;
404             default:
405                 warning() << __PRETTY_FUNCTION__ << "this should be never reached";
406         }
407     }
408     return trackData( track, field, role );
409 }
410