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