File indexing completed on 2024-05-05 04:48:29

0001 /****************************************************************************************
0002  * Copyright (c) 2008 Daniel Jones <danielcjones@gmail.com>                             *
0003  * Copyright (c) 2009 Leo Franchi <lfranchi@kde.org>                                    *
0004  * Copyright (c) 2010,2011 Ralf Engels <ralf-engels@gmx.de>                                  *
0005  *                                                                                      *
0006  * This program is free software; you can redistribute it and/or modify it under        *
0007  * the terms of the GNU General Public License as published by the Free Software        *
0008  * Foundation; either version 2 of the License, or (at your option) version 3 or        *
0009  * any later version accepted by the membership of KDE e.V. (or its successor approved  *
0010  * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of  *
0011  * version 3 of the license.                                                            *
0012  *                                                                                      *
0013  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0014  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0015  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0016  *                                                                                      *
0017  * You should have received a copy of the GNU General Public License along with         *
0018  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0019  ****************************************************************************************/
0020 
0021 #define DEBUG_PREFIX "Bias"
0022 
0023 #include "Bias.h"
0024 
0025 #include "core/support/Debug.h"
0026 #include "dynamic/BiasFactory.h"
0027 #include "dynamic/DynamicModel.h"
0028 #include "dynamic/biases/SearchQueryBias.h"
0029 
0030 #include <KLocalizedString>
0031 
0032 #include <QPainter>
0033 #include <QBuffer>
0034 #include <QXmlStreamReader>
0035 #include <QXmlStreamWriter>
0036 
0037 // -------- AbstractBias -------------
0038 
0039 Dynamic::AbstractBias::AbstractBias()
0040 { }
0041 
0042 Dynamic::AbstractBias::~AbstractBias()
0043 {
0044     // debug() << "destroying bias" << this;
0045 }
0046 
0047 void
0048 Dynamic::AbstractBias::fromXml( QXmlStreamReader *reader )
0049 {
0050     reader->skipCurrentElement();
0051 }
0052 
0053 
0054 void
0055 Dynamic::AbstractBias::toXml( QXmlStreamWriter *writer ) const
0056 {
0057     Q_UNUSED( writer );
0058 }
0059 
0060 Dynamic::BiasPtr
0061 Dynamic::AbstractBias::clone() const
0062 {
0063     QByteArray bytes;
0064     QBuffer buffer( &bytes, nullptr );
0065     buffer.open( QIODevice::ReadWrite );
0066 
0067     // write the bias
0068     QXmlStreamWriter xmlWriter( &buffer );
0069     xmlWriter.writeStartElement( name() );
0070     toXml( &xmlWriter );
0071     xmlWriter.writeEndElement();
0072 
0073     // and read a new list
0074     buffer.seek( 0 );
0075     QXmlStreamReader xmlReader( &buffer );
0076     while( !xmlReader.isStartElement() )
0077         xmlReader.readNext();
0078     return Dynamic::BiasFactory::fromXml( &xmlReader );
0079 }
0080 
0081 QString
0082 Dynamic::AbstractBias::sName()
0083 {
0084     return QStringLiteral( "abstractBias" );
0085 }
0086 
0087 QString
0088 Dynamic::AbstractBias::name() const
0089 {
0090     return Dynamic::AbstractBias::sName();
0091 }
0092 
0093 QWidget*
0094 Dynamic::AbstractBias::widget( QWidget* parent )
0095 {
0096     Q_UNUSED( parent );
0097     return nullptr;
0098 }
0099 
0100 void
0101 Dynamic::AbstractBias::paintOperator( QPainter* painter, const QRect& rect, Dynamic::AbstractBias* bias )
0102 {
0103     Q_UNUSED( painter );
0104     Q_UNUSED( rect );
0105     Q_UNUSED( bias );
0106 }
0107 
0108 void
0109 Dynamic::AbstractBias::invalidate()
0110 { }
0111 
0112 void
0113 Dynamic::AbstractBias::replace( const Dynamic::BiasPtr &newBias )
0114 {
0115     Q_EMIT replaced( BiasPtr(const_cast<Dynamic::AbstractBias*>(this)), newBias );
0116 }
0117 
0118 // -------- RandomBias ------
0119 
0120 Dynamic::RandomBias::RandomBias()
0121 { }
0122 
0123 Dynamic::RandomBias::~RandomBias()
0124 { }
0125 
0126 QString
0127 Dynamic::RandomBias::sName()
0128 {
0129     return QStringLiteral( "randomBias" );
0130 }
0131 
0132 QString
0133 Dynamic::RandomBias::name() const
0134 {
0135     return Dynamic::RandomBias::sName();
0136 }
0137 
0138 QString
0139 Dynamic::RandomBias::toString() const
0140 {
0141     return i18nc("Random bias representation", "Random tracks");
0142 }
0143 
0144 QWidget*
0145 Dynamic::RandomBias::widget( QWidget* parent )
0146 {
0147     Q_UNUSED( parent );
0148     return nullptr;
0149 }
0150 
0151 Dynamic::TrackSet
0152 Dynamic::RandomBias::matchingTracks( const Meta::TrackList& playlist,
0153                                      int contextCount, int finalCount,
0154                                      const Dynamic::TrackCollectionPtr &universe ) const
0155 {
0156     Q_UNUSED( playlist );
0157     Q_UNUSED( contextCount );
0158     Q_UNUSED( finalCount );
0159     return Dynamic::TrackSet( universe, true );
0160 }
0161 
0162 bool
0163 Dynamic::RandomBias::trackMatches( int position,
0164                                    const Meta::TrackList& playlist,
0165                                    int contextCount ) const
0166 {
0167     Q_UNUSED( position );
0168     Q_UNUSED( playlist );
0169     Q_UNUSED( contextCount );
0170     return true;
0171 }
0172 
0173 // -------- AndBias ------
0174 
0175 Dynamic::AndBias::AndBias()
0176 { }
0177 
0178 Dynamic::AndBias::~AndBias()
0179 { }
0180 
0181 void
0182 Dynamic::AndBias::fromXml( QXmlStreamReader *reader )
0183 {
0184     while (!reader->atEnd()) {
0185         reader->readNext();
0186 
0187         if( reader->isStartElement() )
0188         {
0189             Dynamic::BiasPtr bias( Dynamic::BiasFactory::fromXml( reader ) );
0190             if( bias )
0191             {
0192                 appendBias( bias );
0193             }
0194             else
0195             {
0196                 warning()<<"Unexpected xml start element"<<reader->name()<<"in input";
0197                 reader->skipCurrentElement();
0198             }
0199         }
0200         else if( reader->isEndElement() )
0201         {
0202             break;
0203         }
0204     }
0205 }
0206 
0207 void
0208 Dynamic::AndBias::toXml( QXmlStreamWriter *writer ) const
0209 {
0210     foreach( Dynamic::BiasPtr bias, m_biases )
0211     {
0212         writer->writeStartElement( bias->name() );
0213         bias->toXml( writer );
0214         writer->writeEndElement();
0215     }
0216 }
0217 
0218 QString
0219 Dynamic::AndBias::sName()
0220 {
0221     return QStringLiteral( "andBias" );
0222 }
0223 
0224 QString
0225 Dynamic::AndBias::name() const
0226 {
0227     return Dynamic::AndBias::sName();
0228 }
0229 
0230 QString
0231 Dynamic::AndBias::toString() const
0232 {
0233     return i18nc("And bias representation", "Match all");
0234 }
0235 
0236 
0237 QWidget*
0238 Dynamic::AndBias::widget( QWidget* parent )
0239 {
0240     Q_UNUSED( parent );
0241     return nullptr;
0242 }
0243 
0244 void
0245 Dynamic::AndBias::paintOperator( QPainter* painter, const QRect& rect, Dynamic::AbstractBias* bias )
0246 {
0247     if( m_biases.indexOf( Dynamic::BiasPtr(bias) ) > 0 )
0248     {
0249         painter->drawText( rect.adjusted(2, 0, -2, 0),
0250                            Qt::AlignRight,
0251                            i18nc("Prefix for AndBias. Shown in front of a bias in the dynamic playlist view", "and" ) );
0252     }
0253 }
0254 
0255 Dynamic::TrackSet
0256 Dynamic::AndBias::matchingTracks( const Meta::TrackList& playlist,
0257                                   int contextCount, int finalCount,
0258                                   const Dynamic::TrackCollectionPtr &universe ) const
0259 {
0260     DEBUG_BLOCK;
0261 debug() << "universe:" << universe.data();
0262 
0263     m_tracks = Dynamic::TrackSet( universe, true );
0264     m_outstandingMatches = 0;
0265 
0266     foreach( Dynamic::BiasPtr bias, m_biases )
0267     {
0268         Dynamic::TrackSet tracks = bias->matchingTracks( playlist, contextCount, finalCount, universe );
0269         if( tracks.isOutstanding() )
0270             m_outstandingMatches++;
0271         else
0272             m_tracks.intersect( tracks );
0273 
0274         //    debug() << "AndBias::matchingTracks" << bias->name() << "tracks:"<<tracks.trackCount() << "outstanding?" << tracks.isOutstanding() << "numOUt:" << m_outstandingMatches;
0275 
0276         if( m_tracks.isEmpty() )
0277             break;
0278     }
0279 
0280     // debug() << "AndBias::matchingTracks end: tracks:"<<m_tracks.trackCount() << "outstanding?" << m_tracks.isOutstanding() << "numOUt:" << m_outstandingMatches;
0281 
0282     if( m_outstandingMatches > 0 )
0283         return Dynamic::TrackSet();
0284     else
0285         return m_tracks;
0286 }
0287 
0288 bool
0289 Dynamic::AndBias::trackMatches( int position,
0290                                 const Meta::TrackList& playlist,
0291                                 int contextCount ) const
0292 {
0293     foreach( Dynamic::BiasPtr bias, m_biases )
0294     {
0295         if( !bias->trackMatches( position, playlist, contextCount ) )
0296             return false;
0297     }
0298     return true;
0299 }
0300 
0301 void
0302 Dynamic::AndBias::invalidate()
0303 {
0304     foreach( Dynamic::BiasPtr bias, m_biases )
0305     {
0306         bias->invalidate();
0307     }
0308     m_tracks = TrackSet();
0309 }
0310 
0311 void
0312 Dynamic::AndBias::appendBias(const BiasPtr &bias )
0313 {
0314     bool newInModel = DynamicModel::instance()->index( bias ).isValid();
0315     if (newInModel) {
0316         warning() << "Argh, the old bias "<<bias->toString()<<"is still in a model";
0317         return;
0318     }
0319 
0320     BiasPtr thisPtr( this );
0321     bool inModel = DynamicModel::instance()->index( thisPtr ).isValid();
0322     if( inModel )
0323         DynamicModel::instance()->beginInsertBias( thisPtr, m_biases.count() );
0324     m_biases.append( bias );
0325     if( inModel )
0326         DynamicModel::instance()->endInsertBias();
0327 
0328     connect( bias.data(), &Dynamic::AbstractBias::resultReady,
0329              this, &AndBias::resultReceived );
0330     connect( bias.data(), &Dynamic::AbstractBias::replaced,
0331              this, &AndBias::biasReplaced );
0332     connect( bias.data(), &Dynamic::AbstractBias::changed,
0333              this, &AndBias::biasChanged );
0334     Q_EMIT biasAppended( bias );
0335 
0336     // creating a shared pointer and destructing it just afterwards would
0337     // also destruct this object.
0338     // so we give the object creating this bias a chance to increment the refcount
0339     Q_EMIT changed( thisPtr );
0340 }
0341 
0342 void
0343 Dynamic::AndBias::moveBias( int from, int to )
0344 {
0345     if( from == to )
0346         return;
0347     if( from < 0 || from >= m_biases.count() )
0348         return;
0349     if( to < 0 || to >= m_biases.count() )
0350         return;
0351 
0352     BiasPtr thisPtr( this );
0353     bool inModel = DynamicModel::instance()->index( thisPtr ).isValid();
0354     if( inModel )
0355         DynamicModel::instance()->beginMoveBias( thisPtr, from, to );
0356     m_biases.insert( to, m_biases.takeAt( from ) );
0357     if( inModel )
0358         DynamicModel::instance()->endMoveBias();
0359 
0360     Q_EMIT biasMoved( from, to );
0361     Q_EMIT changed( BiasPtr( this ) );
0362 }
0363 
0364 
0365 void
0366 Dynamic::AndBias::resultReceived( const Dynamic::TrackSet &tracks )
0367 {
0368     m_tracks.intersect( tracks );
0369     --m_outstandingMatches;
0370 
0371     if( m_outstandingMatches < 0 )
0372         warning() << "Received more results than expected.";
0373     else if( m_outstandingMatches == 0 )
0374         Q_EMIT resultReady( m_tracks );
0375 }
0376 
0377 void
0378 Dynamic::AndBias::biasReplaced( const Dynamic::BiasPtr &oldBias, const Dynamic::BiasPtr &newBias )
0379 {
0380     DEBUG_BLOCK;
0381     BiasPtr thisPtr( this );
0382     int index = m_biases.indexOf( oldBias );
0383     Q_ASSERT( index >= 0 );
0384 
0385     disconnect( oldBias.data(), nullptr, this, nullptr );
0386     bool inModel = DynamicModel::instance()->index( thisPtr ).isValid();
0387     if( inModel )
0388         DynamicModel::instance()->beginRemoveBias( thisPtr, index );
0389     m_biases.removeAt( index );
0390     if( inModel )
0391         DynamicModel::instance()->endRemoveBias();
0392     Q_EMIT biasRemoved( index );
0393 
0394     if( newBias )
0395     {
0396         connect( newBias.data(), &Dynamic::AbstractBias::resultReady,
0397                  this, &AndBias::resultReceived );
0398         connect( newBias.data(), &Dynamic::AbstractBias::replaced,
0399                  this, &AndBias::biasReplaced );
0400         connect( newBias.data(), &Dynamic::AbstractBias::changed,
0401                  this, &AndBias::changed );
0402 
0403         if( inModel )
0404             DynamicModel::instance()->beginInsertBias( thisPtr, index );
0405         m_biases.insert( index, newBias );
0406         if( inModel )
0407             DynamicModel::instance()->endInsertBias();
0408 
0409         // we don't have an bias inserted signal
0410         Q_EMIT biasAppended( newBias );
0411         Q_EMIT biasMoved( m_biases.count()-1, index );
0412     }
0413 
0414     Q_EMIT changed( thisPtr );
0415 }
0416 
0417 void
0418 Dynamic::AndBias::biasChanged( const Dynamic::BiasPtr &bias )
0419 {
0420     BiasPtr thisPtr( this );
0421     Q_EMIT changed( thisPtr );
0422     bool inModel = DynamicModel::instance()->index( thisPtr ).isValid();
0423     if( inModel )
0424         DynamicModel::instance()->biasChanged( bias );
0425 }
0426 
0427 // -------- OrBias ------
0428 
0429 Dynamic::OrBias::OrBias()
0430     : AndBias()
0431 { }
0432 
0433 QString
0434 Dynamic::OrBias::sName()
0435 {
0436     return QStringLiteral( "orBias" );
0437 }
0438 
0439 QString
0440 Dynamic::OrBias::name() const
0441 {
0442     return Dynamic::OrBias::sName();
0443 }
0444 
0445 void
0446 Dynamic::OrBias::paintOperator( QPainter* painter, const QRect& rect, Dynamic::AbstractBias* bias )
0447 {
0448     if( m_biases.indexOf( Dynamic::BiasPtr(bias) ) > 0 )
0449     {
0450         painter->drawText( rect.adjusted(2, 0, -2, 0),
0451                            Qt::AlignRight,
0452                            i18nc("Prefix for OrBias. Shown in front of a bias in the dynamic playlist view", "or" ) );
0453     }
0454 }
0455 
0456 
0457 QString
0458 Dynamic::OrBias::toString() const
0459 {
0460     return i18nc("Or bias representation", "Match any");
0461 }
0462 
0463 Dynamic::TrackSet
0464 Dynamic::OrBias::matchingTracks( const Meta::TrackList& playlist,
0465                                  int contextCount, int finalCount,
0466                                  const Dynamic::TrackCollectionPtr &universe ) const
0467 {
0468     m_tracks = Dynamic::TrackSet( universe, false );
0469     m_outstandingMatches = 0;
0470 
0471     foreach( Dynamic::BiasPtr bias, m_biases )
0472     {
0473         Dynamic::TrackSet tracks = bias->matchingTracks( playlist, contextCount, finalCount, universe );
0474         if( tracks.isOutstanding() )
0475             m_outstandingMatches++;
0476         else
0477             m_tracks.unite( tracks );
0478 
0479         if( m_tracks.isFull() )
0480             break;
0481     }
0482 
0483     if( m_outstandingMatches > 0 )
0484         return Dynamic::TrackSet();
0485     else
0486         return m_tracks;
0487 }
0488 
0489 bool
0490 Dynamic::OrBias::trackMatches( int position,
0491                                const Meta::TrackList& playlist,
0492                                int contextCount ) const
0493 {
0494     foreach( Dynamic::BiasPtr bias, m_biases )
0495     {
0496         if( bias->trackMatches( position, playlist, contextCount ) )
0497             return true;
0498     }
0499     return false;
0500 }
0501 
0502 void
0503 Dynamic::OrBias::resultReceived( const Dynamic::TrackSet &tracks )
0504 {
0505     m_tracks.unite( tracks );
0506     --m_outstandingMatches;
0507 
0508     if( m_outstandingMatches < 0 )
0509         warning() << "Received more results than expected.";
0510     else if( m_outstandingMatches == 0 )
0511         Q_EMIT resultReady( m_tracks );
0512 }
0513 
0514