Several bias bugfixes
[amarok:rengelss-amarok.git] / src / dynamic / Bias.cpp
1 /****************************************************************************************
2  * Copyright (c) 2008 Daniel Jones <danielcjones@gmail.com>                             *
3  * Copyright (c) 2009 Leo Franchi <lfranchi@kde.org>                                    *
4  * Copyright (c) 2010,2011 Ralf Engels <ralf-engels@gmx.de>                                  *
5  *                                                                                      *
6  * This program is free software; you can redistribute it and/or modify it under        *
7  * the terms of the GNU General Public License as published by the Free Software        *
8  * Foundation; either version 2 of the License, or (at your option) version 3 or        *
9  * any later version accepted by the membership of KDE e.V. (or its successor approved  *
10  * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of  *
11  * version 3 of the license.                                                            *
12  *                                                                                      *
13  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
14  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
15  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
16  *                                                                                      *
17  * You should have received a copy of the GNU General Public License along with         *
18  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
19  ****************************************************************************************/
20
21 #define DEBUG_PREFIX "Bias"
22
23 #include "Bias.h"
24 #include "BiasFactory.h"
25
26 #include "core/support/Debug.h"
27 #include "DynamicBiasWidgets.h"
28
29 #include <QXmlStreamReader>
30 #include <QXmlStreamWriter>
31
32 // -------- AbstractBias -------------
33
34 Dynamic::AbstractBias::AbstractBias()
35 { }
36
37 Dynamic::AbstractBias::~AbstractBias()
38 {
39     debug() << "destroying bias" << this << this->name();
40 }
41
42 void
43 Dynamic::AbstractBias::toXml( QXmlStreamWriter *writer ) const
44 {
45     Q_UNUSED( writer );
46 }
47
48 QString
49 Dynamic::AbstractBias::sName()
50 {
51     return QLatin1String( "abstractBias" );
52 }
53
54 QString
55 Dynamic::AbstractBias::name() const
56 {
57     return Dynamic::AbstractBias::sName();
58 }
59
60 QWidget*
61 Dynamic::AbstractBias::widget( QWidget* parent )
62 {
63     return new PlaylistBrowserNS::BiasWidget( BiasPtr(this), parent );
64 }
65
66 void
67 Dynamic::AbstractBias::invalidate()
68 { }
69
70 void
71 Dynamic::AbstractBias::replace( Dynamic::BiasPtr newBias )
72 {
73     emit replaced( BiasPtr(const_cast<Dynamic::AbstractBias*>(this)), newBias );
74 }
75
76 double
77 Dynamic::AbstractBias::energy( const Meta::TrackList& playlist, int contextCount ) const
78 {
79     Q_UNUSED( contextCount );
80
81     if( playlist.count() <= contextCount )
82         return 0.0;
83
84     int matchCount = 0;
85     for( int i = contextCount; i < playlist.count(); i++ )
86     {
87         if( trackMatches( i, playlist, contextCount ) )
88             matchCount++;
89     }
90
91     return 1.0 - (double(matchCount) / (playlist.count() - contextCount));
92 }
93
94
95 // -------- RandomBias ------
96
97 Dynamic::RandomBias::RandomBias()
98 { }
99
100 Dynamic::RandomBias::RandomBias( QXmlStreamReader *reader )
101 {
102     reader->skipCurrentElement();
103 }
104
105 Dynamic::RandomBias::~RandomBias()
106 { }
107
108 void
109 Dynamic::RandomBias::toXml( QXmlStreamWriter *writer ) const
110 {
111     Q_UNUSED( writer );
112 }
113
114 QString
115 Dynamic::RandomBias::sName()
116 {
117     return QLatin1String( "randomBias" );
118 }
119
120 QString
121 Dynamic::RandomBias::name() const
122 {
123     return Dynamic::RandomBias::sName();
124 }
125
126
127 QWidget*
128 Dynamic::RandomBias::widget( QWidget* parent )
129 {
130     return new PlaylistBrowserNS::BiasWidget( BiasPtr(this), parent );
131 }
132
133 Dynamic::TrackSet
134 Dynamic::RandomBias::matchingTracks( int position,
135                                   const Meta::TrackList& playlist, int contextCount,
136                                   Dynamic::TrackCollectionPtr universe ) const
137 {
138     Q_UNUSED( position );
139     Q_UNUSED( playlist );
140     Q_UNUSED( contextCount );
141     return Dynamic::TrackSet( universe, true );
142 }
143
144 bool
145 Dynamic::RandomBias::trackMatches( int position,
146                                    const Meta::TrackList& playlist,
147                                    int contextCount ) const
148 {
149     Q_UNUSED( position );
150     Q_UNUSED( playlist );
151     Q_UNUSED( contextCount );
152     return true;
153 }
154
155 double
156 Dynamic::RandomBias::energy( const Meta::TrackList& playlist, int contextCount ) const
157 {
158     Q_UNUSED( playlist );
159     Q_UNUSED( contextCount );
160     return 0.0;
161 }
162
163 // -------- UniqueBias ------
164
165 Dynamic::UniqueBias::UniqueBias()
166 { }
167
168 Dynamic::UniqueBias::UniqueBias( QXmlStreamReader *reader )
169 {
170     reader->skipCurrentElement();
171 }
172
173 Dynamic::UniqueBias::~UniqueBias()
174 { }
175
176 void
177 Dynamic::UniqueBias::toXml( QXmlStreamWriter *writer ) const
178 {
179     Q_UNUSED( writer );
180 }
181
182 QString
183 Dynamic::UniqueBias::sName()
184 {
185     return QLatin1String( "uniqueBias" );
186 }
187
188 QString
189 Dynamic::UniqueBias::name() const
190 {
191     return Dynamic::UniqueBias::sName();
192 }
193
194
195 QWidget*
196 Dynamic::UniqueBias::widget( QWidget* parent )
197 {
198     return new PlaylistBrowserNS::BiasWidget( BiasPtr(this), parent );
199 }
200
201 Dynamic::TrackSet
202 Dynamic::UniqueBias::matchingTracks( int position,
203                                   const Meta::TrackList& playlist, int contextCount,
204                                   Dynamic::TrackCollectionPtr universe ) const
205 {
206     Q_UNUSED( contextCount );
207
208     Dynamic::TrackSet result = Dynamic::TrackSet( universe, true );
209     for( int i = 0; i < position; i++ )
210         result.subtract( playlist.at(i) );
211
212     return result;
213 }
214
215 bool
216 Dynamic::UniqueBias::trackMatches( int position,
217                                    const Meta::TrackList& playlist,
218                                    int contextCount ) const
219 {
220     Q_UNUSED( contextCount );
221
222     for( int i = 0; i < position; i++ )
223         if( playlist.at(position) == playlist.at(i) )
224             return false;
225     return true;
226 }
227
228
229 // -------- AndBias ------
230
231 Dynamic::AndBias::AndBias()
232     : m_duringConstruction( true )
233 {
234     // add one bias to start with
235     appendBias( BiasPtr( new Dynamic::RandomBias() ) );
236
237     m_duringConstruction = false;
238 }
239
240 Dynamic::AndBias::AndBias( QXmlStreamReader *reader )
241     : m_duringConstruction( true )
242 {
243     while (!reader->atEnd()) {
244         reader->readNext();
245
246         if( reader->isStartElement() )
247         {
248             Dynamic::BiasPtr bias( Dynamic::BiasFactory::fromXml( reader ) );
249             if( bias )
250             {
251                 appendBias( bias );
252             }
253             else
254             {
255                 warning()<<"Unexpected xml start element"<<reader->name()<<"in input";
256                 reader->skipCurrentElement();
257             }
258         }
259         else if( reader->isEndElement() )
260         {
261             break;
262         }
263     }
264
265     m_duringConstruction = false;
266 }
267
268 Dynamic::AndBias::~AndBias()
269 { }
270
271 void
272 Dynamic::AndBias::toXml( QXmlStreamWriter *writer ) const
273 {
274     foreach( Dynamic::BiasPtr bias, m_biases )
275     {
276         writer->writeStartElement( bias->name() );
277         bias->toXml( writer );
278         writer->writeEndElement();
279     }
280 }
281
282 QString
283 Dynamic::AndBias::sName()
284 {
285     return QLatin1String( "andBias" );
286 }
287
288 QString
289 Dynamic::AndBias::name() const
290 {
291     return Dynamic::AndBias::sName();
292 }
293
294
295 QWidget*
296 Dynamic::AndBias::widget( QWidget* parent )
297 {
298     return new PlaylistBrowserNS::LevelBiasWidget( this, false, parent );
299 }
300
301 Dynamic::TrackSet
302 Dynamic::AndBias::matchingTracks( int position,
303                                   const Meta::TrackList& playlist, int contextCount,
304                                   Dynamic::TrackCollectionPtr universe ) const
305 {
306     DEBUG_BLOCK;
307 debug() << "universe:" << universe.data();
308
309     m_tracks = Dynamic::TrackSet( universe, true );
310     m_outstandingMatches = 0;
311
312     foreach( Dynamic::BiasPtr bias, m_biases )
313     {
314         Dynamic::TrackSet tracks = bias->matchingTracks( position, playlist, contextCount, universe );
315         if( tracks.isOutstanding() )
316             m_outstandingMatches++;
317         else
318             m_tracks.intersect( tracks );
319
320         //    debug() << "AndBias::matchingTracks" << bias->name() << "tracks:"<<tracks.trackCount() << "outstanding?" << tracks.isOutstanding() << "numOUt:" << m_outstandingMatches;
321
322         if( m_tracks.isEmpty() )
323             break;
324     }
325
326     // debug() << "AndBias::matchingTracks end: tracks:"<<m_tracks.trackCount() << "outstanding?" << m_tracks.isOutstanding() << "numOUt:" << m_outstandingMatches;
327
328     if( m_outstandingMatches > 0 )
329         return Dynamic::TrackSet();
330     else
331         return m_tracks;
332 }
333
334 bool
335 Dynamic::AndBias::trackMatches( int position,
336                                 const Meta::TrackList& playlist,
337                                 int contextCount ) const
338 {
339     foreach( Dynamic::BiasPtr bias, m_biases )
340     {
341         if( !bias->trackMatches( position, playlist, contextCount ) )
342             return false;
343     }
344     return true;
345 }
346
347 void
348 Dynamic::AndBias::invalidate()
349 {
350     foreach( Dynamic::BiasPtr bias, m_biases )
351     {
352         bias->invalidate();
353     }
354     m_tracks = TrackSet();
355 }
356
357 void
358 Dynamic::AndBias::appendBias( Dynamic::BiasPtr bias )
359 {
360     m_biases.append( bias );
361     connect( bias.data(), SIGNAL( resultReady( const Dynamic::TrackSet & ) ),
362              this,  SLOT( resultReceived( const Dynamic::TrackSet & ) ) );
363     connect( bias.data(), SIGNAL( replaced( Dynamic::BiasPtr, Dynamic::BiasPtr ) ),
364              this, SLOT( biasReplaced( Dynamic::BiasPtr, Dynamic::BiasPtr ) ) );
365     connect( bias.data(), SIGNAL( changed( Dynamic::BiasPtr ) ),
366              this, SIGNAL( changed( Dynamic::BiasPtr ) ) );
367     emit biasAppended( bias );
368
369     // creating a shared pointer and destructing it just afterwards would
370     // also destruct this object.
371     // so we give the object creating this bias a chance to increment the refcount
372     if( !m_duringConstruction )
373         emit changed( BiasPtr( this ) );
374 }
375
376 void
377 Dynamic::AndBias::moveBias( int from, int to )
378 {
379     m_biases.insert( to, m_biases.takeAt( from ) );
380     emit biasMoved( from, to );
381
382     if( !m_duringConstruction )
383         emit changed( BiasPtr( this ) );
384 }
385
386
387 void
388 Dynamic::AndBias::resultReceived( const Dynamic::TrackSet &tracks )
389 {
390     m_tracks.intersect( tracks );
391     --m_outstandingMatches;
392     // debug() << "AndBias::resultReceived" << m_outstandingMatches << "tr" << m_tracks.trackCount();
393
394     if( m_outstandingMatches < 0 )
395         warning() << "Received more results than expected.";
396     else if( m_outstandingMatches == 0 )
397         emit resultReady( m_tracks );
398 }
399
400 void
401 Dynamic::AndBias::biasReplaced( Dynamic::BiasPtr oldBias, Dynamic::BiasPtr newBias )
402 {
403     int index = m_biases.indexOf( oldBias );
404     Q_ASSERT( index >= 0 );
405
406     disconnect( oldBias.data(), 0, this, 0 );
407
408     if( newBias )
409     {
410         m_biases[ index ] = newBias;
411         connect( newBias.data(), SIGNAL( resultReady( const Dynamic::TrackSet & ) ),
412                  this,  SLOT( resultReceived( const Dynamic::TrackSet & ) ) );
413         connect( newBias.data(), SIGNAL( replaced( Dynamic::BiasPtr, Dynamic::BiasPtr ) ),
414                  this, SLOT( biasReplaced( Dynamic::BiasPtr, Dynamic::BiasPtr ) ) );
415         connect( newBias.data(), SIGNAL( changed( Dynamic::BiasPtr ) ),
416                  this, SIGNAL( changed( Dynamic::BiasPtr ) ) );
417
418         // we don't have an bias inserted signal
419         emit biasRemoved( index );
420         emit biasAppended( newBias );
421         emit biasMoved( m_biases.count()-1, index );
422     }
423     else
424     {
425         m_biases.removeAt( index );
426         emit biasRemoved( index );
427     }
428
429     if( !m_duringConstruction &&
430         !qobject_cast<Dynamic::ReplacementBias*>(oldBias.data()) )
431         emit changed( BiasPtr( this ) );
432 }
433
434 // -------- OrBias ------
435
436 Dynamic::OrBias::OrBias()
437     : AndBias()
438 { }
439
440 Dynamic::OrBias::OrBias( QXmlStreamReader *reader )
441     : AndBias( reader )
442 { }
443
444 QString
445 Dynamic::OrBias::sName()
446 {
447     return QLatin1String( "orBias" );
448 }
449
450 QString
451 Dynamic::OrBias::name() const
452 {
453     return Dynamic::OrBias::sName();
454 }
455
456 Dynamic::TrackSet
457 Dynamic::OrBias::matchingTracks( int position,
458                                  const Meta::TrackList& playlist, int contextCount,
459                                  Dynamic::TrackCollectionPtr universe ) const
460 {
461     m_tracks = Dynamic::TrackSet( universe, false );
462     m_outstandingMatches = 0;
463
464     foreach( Dynamic::BiasPtr bias, m_biases )
465     {
466         Dynamic::TrackSet tracks = bias->matchingTracks( position, playlist, contextCount, universe );
467         if( tracks.isOutstanding() )
468             m_outstandingMatches++;
469         else
470             m_tracks.unite( tracks );
471
472         if( m_tracks.isFull() )
473             break;
474     }
475
476     if( m_outstandingMatches > 0 )
477         return Dynamic::TrackSet();
478     else
479         return m_tracks;
480 }
481
482 bool
483 Dynamic::OrBias::trackMatches( int position,
484                                const Meta::TrackList& playlist,
485                                int contextCount ) const
486 {
487     foreach( Dynamic::BiasPtr bias, m_biases )
488     {
489         if( bias->trackMatches( position, playlist, contextCount ) )
490             return true;
491     }
492     return false;
493 }
494
495 void
496 Dynamic::OrBias::resultReceived( const Dynamic::TrackSet &tracks )
497 {
498     m_tracks.unite( tracks );
499     --m_outstandingMatches;
500
501     if( m_outstandingMatches < 0 )
502         warning() << "Received more results than expected.";
503     else if( m_outstandingMatches == 0 )
504         emit resultReady( m_tracks );
505 }
506
507 // -------- NotBias ------
508
509 Dynamic::NotBias::NotBias()
510     : OrBias()
511 { }
512
513 Dynamic::NotBias::NotBias( QXmlStreamReader *reader )
514     : OrBias( reader )
515 { }
516
517 QString
518 Dynamic::NotBias::sName()
519 {
520     return QLatin1String( "notBias" );
521 }
522
523 QString
524 Dynamic::NotBias::name() const
525 {
526     return Dynamic::NotBias::sName();
527 }
528
529 Dynamic::TrackSet
530 Dynamic::NotBias::matchingTracks( int position,
531                                  const Meta::TrackList& playlist, int contextCount,
532                                  Dynamic::TrackCollectionPtr universe ) const
533 {
534     m_tracks = Dynamic::TrackSet( universe, true );
535     m_outstandingMatches = 0;
536
537     foreach( Dynamic::BiasPtr bias, m_biases )
538     {
539         Dynamic::TrackSet tracks = bias->matchingTracks( position, playlist, contextCount, universe );
540         if( tracks.isOutstanding() )
541             m_outstandingMatches++;
542         else
543             m_tracks.subtract( tracks );
544
545         if( m_tracks.isEmpty() )
546             break;
547     }
548
549     if( m_outstandingMatches > 0 )
550         return Dynamic::TrackSet();
551     else
552         return m_tracks;
553 }
554
555 bool
556 Dynamic::NotBias::trackMatches( int position,
557                                const Meta::TrackList& playlist,
558                                int contextCount ) const
559 {
560     return ! Dynamic::OrBias::trackMatches( position, playlist, contextCount );
561 }
562
563 double
564 Dynamic::NotBias::energy( const Meta::TrackList& playlist, int contextCount ) const
565 {
566     return 1.0 - Dynamic::OrBias::energy( playlist, contextCount );
567 }
568
569 void
570 Dynamic::NotBias::resultReceived( const Dynamic::TrackSet &tracks )
571 {
572     m_tracks.subtract( tracks );
573     --m_outstandingMatches;
574
575     if( m_outstandingMatches < 0 )
576         warning() << "Received more results than expected.";
577     else if( m_outstandingMatches == 0 )
578         emit resultReady( m_tracks );
579 }
580
581 #include "Bias.moc"
582