1 /****************************************************************************************
2  * Copyright (c) 2009 Téo Mrnjavac <teo@kde.org>                                        *
3  * Copyright (c) 2010 Nanno Langstraat <langstr@gmail.com>                              *
4  *                                                                                      *
5  * This program is free software; you can redistribute it and/or modify it under        *
6  * the terms of the GNU General Public License as published by the Free Software        *
7  * Foundation; either version 2 of the License, or (at your option) any later           *
8  * version.                                                                             *
9  *                                                                                      *
10  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
11  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
12  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
13  *                                                                                      *
14  * You should have received a copy of the GNU General Public License along with         *
15  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
16  ****************************************************************************************/
17 
18 #include "ProxyBase.h"
19 
20 #include "core/meta/Meta.h"
21 #include "core/meta/Statistics.h"
22 #include "core-impl/playlists/types/file/PlaylistFileSupport.h"
23 #include "playlist/PlaylistModel.h"
24 
25 namespace Playlist
26 {
27 
ProxyBase(AbstractModel * belowModel,QObject * parent)28 ProxyBase::ProxyBase( AbstractModel *belowModel, QObject *parent )
29     : QSortFilterProxyModel( parent )
30     , m_belowModel( belowModel )
31 {
32     setSourceModel( m_belowModel->qaim() );
33 
34     // Proxy the Playlist::AbstractModel signals.
35     //   If you need to do something special in a subclass, disconnect() this signal and
36     //   do your own connect() call.
37     connect( qobject_cast<Playlist::Model*>( sourceModel() ), &Playlist::Model::activeTrackChanged,
38              this, &ProxyBase::activeTrackChanged );
39     connect( qobject_cast<Playlist::Model*>( sourceModel() ), &Playlist::Model::queueChanged,
40              this, &ProxyBase::queueChanged );
41 }
42 
~ProxyBase()43 ProxyBase::~ProxyBase()
44 {}
45 
46 // Pass-through virtual public methods, that pretty much just forward stuff through the stack
47 // of proxies, start here.
48 // Please keep them sorted alphabetically.  -- Téo
49 
50 quint64
activeId() const51 ProxyBase::activeId() const
52 {
53     return m_belowModel->activeId();
54 }
55 
56 int
activeRow() const57 ProxyBase::activeRow() const
58 {
59     // We map the active row form the source to this ProxyModel.
60     return rowFromSource( m_belowModel->activeRow() );
61 }
62 
63 Meta::TrackPtr
activeTrack() const64 ProxyBase::activeTrack() const
65 {
66     return m_belowModel->activeTrack();
67 }
68 
69 QSet<int>
allRowsForTrack(const Meta::TrackPtr & track) const70 ProxyBase::allRowsForTrack( const Meta::TrackPtr& track ) const
71 {
72     QSet<int> proxyModelRows;
73 
74     foreach( int sourceModelRow, m_belowModel->allRowsForTrack( track ) )
75     {
76         int proxyModelRow = rowFromSource( sourceModelRow );
77         if ( proxyModelRow != -1 )
78             proxyModelRows.insert( proxyModelRow );
79     }
80 
81     return proxyModelRows;
82 }
83 
84 void
clearSearchTerm()85 ProxyBase::clearSearchTerm()
86 {
87     m_belowModel->clearSearchTerm();
88 }
89 
90 bool
containsTrack(const Meta::TrackPtr & track) const91 ProxyBase::containsTrack( const Meta::TrackPtr& track ) const
92 {
93     return ( firstRowForTrack( track ) != -1 );    // Let him do the clever work.
94 }
95 
96 int
currentSearchFields()97 ProxyBase::currentSearchFields()
98 {
99     return m_belowModel->currentSearchFields();
100 }
101 
102 QString
currentSearchTerm()103 ProxyBase::currentSearchTerm()
104 {
105     return m_belowModel->currentSearchTerm();
106 }
107 
108 bool
exportPlaylist(const QString & path,bool relative)109 ProxyBase::exportPlaylist( const QString &path, bool relative )
110 {
111     return Playlists::exportPlaylistFile( tracks(), QUrl::fromLocalFile(path), relative );
112 }
113 
114 void
filterUpdated()115 ProxyBase::filterUpdated()
116 {
117     m_belowModel->filterUpdated();
118 }
119 
120 int
find(const QString & searchTerm,int searchFields)121 ProxyBase::find( const QString &searchTerm, int searchFields )
122 {
123     ProxyBase *proxyBase = dynamic_cast< ProxyBase * >( m_belowModel );
124     if ( !proxyBase )
125         return -1;
126 
127     return rowFromSource( proxyBase->find( searchTerm, searchFields ) );
128 }
129 
130 int
findNext(const QString & searchTerm,int selectedRow,int searchFields)131 ProxyBase::findNext( const QString &searchTerm, int selectedRow, int searchFields )
132 {
133     ProxyBase *proxyBase = dynamic_cast< ProxyBase * >( m_belowModel );
134     if ( !proxyBase )
135         return -1;
136 
137     return rowFromSource( proxyBase->findNext( searchTerm, rowToSource( selectedRow ), searchFields ) );
138 }
139 
140 int
findPrevious(const QString & searchTerm,int selectedRow,int searchFields)141 ProxyBase::findPrevious( const QString &searchTerm, int selectedRow, int searchFields )
142 {
143     ProxyBase *proxyBase = dynamic_cast< ProxyBase * >( m_belowModel );
144     if ( !proxyBase )
145         return -1;
146 
147     return rowFromSource( proxyBase->findPrevious( searchTerm, rowToSource( selectedRow ), searchFields ) );
148 }
149 
150 int
firstRowForTrack(const Meta::TrackPtr & track) const151 ProxyBase::firstRowForTrack( const Meta::TrackPtr& track ) const
152 {
153     // First optimistically try 'firstRowForTrack()'. It'll usually work.
154     int proxyModelRow = rowFromSource( m_belowModel->firstRowForTrack( track ) );
155     if ( proxyModelRow != -1 )
156         return proxyModelRow;
157     else
158     {
159         // It might be that there are multiple hits in the source model, and we just got
160         // unlucky with a source row that's filtered out in this model. So, we need to
161         // check all hits.
162         foreach( int sourceModelRow, m_belowModel->allRowsForTrack( track ) )
163         {
164             proxyModelRow = rowFromSource( sourceModelRow );
165             if ( proxyModelRow != -1 )
166                 return proxyModelRow;
167         }
168 
169         return -1;
170     }
171 }
172 
173 quint64
idAt(const int row) const174 ProxyBase::idAt( const int row ) const
175 {
176     if( rowExists( row ) )
177         return m_belowModel->idAt( rowToSource( row ) );
178     return 0;
179 }
180 
181 bool
rowExists(int row) const182 ProxyBase::rowExists( int row ) const
183 {
184     QModelIndex index = this->index( row, 0 );
185     return index.isValid();
186 }
187 
188 int
rowForId(const quint64 id) const189 ProxyBase::rowForId( const quint64 id ) const
190 {
191     return rowFromSource( m_belowModel->rowForId( id ) );
192 }
193 
194 int
rowFromBottomModel(const int row)195 ProxyBase::rowFromBottomModel( const int row )
196 {
197     return rowFromSource( m_belowModel->rowFromBottomModel( row ) );
198 }
199 
200 int
rowToBottomModel(const int row)201 ProxyBase::rowToBottomModel( const int row )
202 {
203     return m_belowModel->rowToBottomModel( rowToSource( row )  );
204 }
205 
206 void
setActiveId(const quint64 id)207 ProxyBase::setActiveId( const quint64 id )
208 {
209     m_belowModel->setActiveId( id );
210 }
211 
212 void
setActiveRow(int row)213 ProxyBase::setActiveRow( int row )
214 {
215     m_belowModel->setActiveRow( rowToSource( row ) );
216 }
217 
218 void
setAllUnplayed()219 ProxyBase::setAllUnplayed()
220 {
221     m_belowModel->setAllUnplayed();
222 }
223 
224 void
emitQueueChanged()225 ProxyBase::emitQueueChanged()
226 {
227     Q_ASSERT_X(false, "emitQueueChanged", "queueChanged() should be emitted at the bottom of "
228                                           "the model stack so it can be received from every model.");
229 }
230 
231 int
queuePositionOfRow(int row)232 ProxyBase::queuePositionOfRow( int row )
233 {
234     return m_belowModel->queuePositionOfRow( rowToSource ( row ) );
235 }
236 
237 void
showOnlyMatches(bool onlyMatches)238 ProxyBase::showOnlyMatches( bool onlyMatches )
239 {
240     ProxyBase *proxyBase = dynamic_cast< ProxyBase * >( m_belowModel );
241     if ( !proxyBase )
242         return ;
243 
244     proxyBase->showOnlyMatches( onlyMatches );
245 }
246 
247 Item::State
stateOfId(quint64 id) const248 ProxyBase::stateOfId( quint64 id ) const
249 {
250     return m_belowModel->stateOfId( id );
251 }
252 
253 Item::State
stateOfRow(int row) const254 ProxyBase::stateOfRow( int row ) const
255 {
256     return m_belowModel->stateOfRow( rowToSource( row ) );
257 }
258 
259 qint64
totalLength() const260 ProxyBase::totalLength() const
261 {
262     return m_belowModel->totalLength();
263 }
264 
265 quint64
totalSize() const266 ProxyBase::totalSize() const
267 {
268     return m_belowModel->totalSize();
269 }
270 
271 Meta::TrackPtr
trackAt(int row) const272 ProxyBase::trackAt(int row) const
273 {
274     return m_belowModel->trackAt( rowToSource( row ) );
275 }
276 
277 Meta::TrackPtr
trackForId(const quint64 id) const278 ProxyBase::trackForId( const quint64 id ) const
279 {
280     return m_belowModel->trackForId( id );
281 }
282 
283 Meta::TrackList
tracks()284 ProxyBase::tracks()
285 {
286     Meta::TrackList tl;
287     for( int i = 0; i < rowCount(); ++i )
288         tl << trackAt( i );
289     return tl;
290 }
291 
292 //protected:
293 
294 bool
rowMatch(int sourceModelRow,const QString & searchTerms,int searchFields) const295 ProxyBase::rowMatch( int sourceModelRow, const QString &searchTerms, int searchFields ) const
296 {
297     if ( !m_belowModel )
298         return false;
299 
300     Meta::TrackPtr track = m_belowModel->trackAt( sourceModelRow );
301 
302     QStringList searchList = searchTerms.split(QLatin1Char(' '), QString::SkipEmptyParts);
303 
304     foreach( const QString& searchTerm, searchList )
305     {
306         bool match = false;
307 
308         if ( searchFields & MatchTrack &&
309             track->prettyName().contains( searchTerm, Qt::CaseInsensitive )
310         )
311             match = true;
312 
313         if ( searchFields & MatchArtist &&
314             track->artist() &&
315             track->artist()->prettyName().contains( searchTerm, Qt::CaseInsensitive )
316         )
317             match = true;
318 
319         if ( searchFields & MatchAlbum &&
320             track->album() &&
321             track->album()->prettyName().contains( searchTerm, Qt::CaseInsensitive )
322         )
323             match = true;
324 
325         if ( searchFields & MatchGenre &&
326             track->genre() &&
327             track->genre()->prettyName().contains( searchTerm, Qt::CaseInsensitive )
328         )
329             match = true;
330 
331         if ( searchFields & MatchComposer &&
332             track->composer() &&
333             track->composer()->prettyName().contains( searchTerm, Qt::CaseInsensitive )
334         )
335             match = true;
336 
337         if ( searchFields & MatchYear &&
338             track->year() &&
339             track->year()->prettyName().contains( searchTerm, Qt::CaseInsensitive )
340         )
341             match = true;
342 
343         if( searchFields & MatchRating )
344         {
345             bool ok;
346             int rating = QString( searchTerm ).remove( QStringLiteral("rating:") ).toInt( &ok );
347             if( ok && ( track->statistics()->rating() == rating ) )
348                 match = true;
349         }
350 
351         if( !match )
352             return false;
353     }
354 
355     return true;
356 }
357 
358 int
rowFromSource(int sourceModelRow) const359 ProxyBase::rowFromSource( int sourceModelRow ) const
360 {
361     QModelIndex sourceModelIndex = sourceModel()->index( sourceModelRow, 0 );
362     QModelIndex proxyModelIndex = mapFromSource( sourceModelIndex );    // Call 'map' even for a 1:1 passthrough proxy: QSFPM might need it.
363 
364     if ( proxyModelIndex.isValid() )
365         return proxyModelIndex.row();
366     else
367         return -1;
368 }
369 
370 int
rowToSource(int proxyModelRow) const371 ProxyBase::rowToSource( int proxyModelRow ) const
372 {
373     QModelIndex proxyModelIndex = this->index( proxyModelRow, 0 );
374     QModelIndex sourceModelIndex = mapToSource( proxyModelIndex );    // Call 'map' even for a 1:1 passthrough proxy: QSFPM might need it.
375 
376     if( sourceModelIndex.isValid() )
377         return sourceModelIndex.row();
378     else
379         if( proxyModelRow == rowCount() )
380             return sourceModel()->rowCount();
381         else
382             return -1;
383 }
384 
385 }   //namespace Playlist
386 
387