File indexing completed on 2024-05-19 04:49:41

0001 /****************************************************************************************
0002  * Copyright (c) 2011 Ralf Engels <ralf-engels@gmx.de>                                  *
0003  *                                                                                      *
0004  * This program is free software; you can redistribute it and/or modify it under        *
0005  * the terms of the GNU General Public License as published by the Free Software        *
0006  * Foundation; either version 2 of the License, or (at your option) version 3 or        *
0007  * any later version accepted by the membership of KDE e.V. (or its successor approved  *
0008  * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of  *
0009  * version 3 of the license.                                                            *
0010  *                                                                                      *
0011  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0012  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0013  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0014  *                                                                                      *
0015  * You should have received a copy of the GNU General Public License along with         *
0016  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0017  ****************************************************************************************/
0018 
0019 #define DEBUG_PREFIX "QuizPlayBias"
0020 
0021 #include "QuizPlayBias.h"
0022 
0023 #include "core/collections/Collection.h"
0024 #include "core/collections/QueryMaker.h"
0025 #include "core/meta/Meta.h"
0026 #include "core/support/Debug.h"
0027 #include "core-impl/collections/support/CollectionManager.h"
0028 #include "dynamic/TrackSet.h"
0029 
0030 #include <QLabel>
0031 #include <QComboBox>
0032 #include <QVBoxLayout>
0033 
0034 #include <QTimer>
0035 #include <QXmlStreamReader>
0036 #include <QXmlStreamWriter>
0037 
0038 #include <KLocalizedString>
0039 
0040 QString
0041 Dynamic::QuizPlayBiasFactory::i18nName() const
0042 { return i18nc("Name of the \"QuizPlay\" bias", "Quiz play"); }
0043 
0044 QString
0045 Dynamic::QuizPlayBiasFactory::name() const
0046 { return Dynamic::QuizPlayBias::sName(); }
0047 
0048 QString
0049 Dynamic::QuizPlayBiasFactory::i18nDescription() const
0050 { return i18nc("Description of the \"QuizPlay\" bias",
0051                "The \"QuizPlay\" bias adds tracks that start\n"
0052                "with a character the last track ended with."); }
0053 
0054 Dynamic::BiasPtr
0055 Dynamic::QuizPlayBiasFactory::createBias()
0056 { return Dynamic::BiasPtr( new Dynamic::QuizPlayBias() ); }
0057 
0058 
0059 // Note: this whole bias does not work correctly for right-to-left languages.
0060 
0061 
0062 Dynamic::QuizPlayBias::QuizPlayBias()
0063     : SimpleMatchBias()
0064     , m_follow( TitleToTitle )
0065 { }
0066 
0067 void
0068 Dynamic::QuizPlayBias::fromXml( QXmlStreamReader *reader )
0069 {
0070     while (!reader->atEnd()) {
0071         reader->readNext();
0072 
0073         if( reader->isStartElement() )
0074         {
0075             QStringRef name = reader->name();
0076             if( name == "follow" )
0077                 m_follow = followForName( reader->readElementText(QXmlStreamReader::SkipChildElements) );
0078             else
0079             {
0080                 debug()<<"Unexpected xml start element"<<reader->name()<<"in input";
0081                 reader->skipCurrentElement();
0082             }
0083         }
0084         else if( reader->isEndElement() )
0085         {
0086             break;
0087         }
0088     }
0089 }
0090 
0091 void
0092 Dynamic::QuizPlayBias::toXml( QXmlStreamWriter *writer ) const
0093 {
0094     writer->writeTextElement( QStringLiteral("follow"), nameForFollow( m_follow ) );
0095 }
0096 
0097 QString
0098 Dynamic::QuizPlayBias::sName()
0099 {
0100     return QStringLiteral( "quizPlayBias" );
0101 }
0102 
0103 QString
0104 Dynamic::QuizPlayBias::name() const
0105 {
0106     return Dynamic::QuizPlayBias::sName();
0107 }
0108 
0109 QString
0110 Dynamic::QuizPlayBias::toString() const
0111 {
0112     switch( m_follow )
0113     {
0114     case TitleToTitle:
0115         return i18nc("QuizPlay bias representation",
0116                      "Tracks whose title start with a\n"
0117                      "character the last track ended with");
0118     case ArtistToArtist:
0119         return i18nc("QuizPlay bias representation",
0120                      "Tracks whose artist name start\n"
0121                      "with a character the last track ended with");
0122     case AlbumToAlbum:
0123         return i18nc("QuizPlay bias representation",
0124                      "Tracks whose album name start\n"
0125                      "with a character the last track ended with");
0126     }
0127     return QString();
0128 }
0129 
0130 QWidget*
0131 Dynamic::QuizPlayBias::widget( QWidget* parent )
0132 {
0133     QWidget *widget = new QWidget( parent );
0134     QVBoxLayout *layout = new QVBoxLayout( widget );
0135 
0136     QLabel *label = new QLabel( i18n( "Last character of the previous song is\n"
0137                                       "the first character of the next song" ) );
0138     layout->addWidget( label );
0139 
0140     QComboBox *combo = new QComboBox();
0141     combo->addItem( i18n( "of the track title (Title quiz)" ),
0142                     nameForFollow( TitleToTitle ) );
0143     combo->addItem( i18n( "of the artist (Artist quiz)" ),
0144                     nameForFollow( ArtistToArtist ) );
0145     combo->addItem( i18n( "of the album name (Album quiz)" ),
0146                     nameForFollow( AlbumToAlbum ) );
0147     switch( m_follow )
0148     {
0149     case TitleToTitle:   combo->setCurrentIndex(0); break;
0150     case ArtistToArtist: combo->setCurrentIndex(1); break;
0151     case AlbumToAlbum:   combo->setCurrentIndex(2); break;
0152     }
0153     connect( combo, QOverload<int>::of(&QComboBox::currentIndexChanged),
0154              this, &QuizPlayBias::selectionChanged );
0155     layout->addWidget( combo );
0156 
0157     return widget;
0158 }
0159 
0160 Dynamic::TrackSet
0161 Dynamic::QuizPlayBias::matchingTracks( const Meta::TrackList& playlist,
0162                                        int contextCount, int finalCount,
0163                                        const Dynamic::TrackCollectionPtr &universe ) const
0164 {
0165     Q_UNUSED( contextCount );
0166     Q_UNUSED( finalCount );
0167 
0168     if( playlist.isEmpty() )
0169         return Dynamic::TrackSet( universe, true );
0170 
0171     // determine the last character we need to quiz
0172     Meta::TrackPtr lastTrack = playlist.last();
0173     Meta::DataPtr lastData;
0174     if( m_follow == TitleToTitle )
0175         lastData = Meta::DataPtr::staticCast<Meta::Track>(lastTrack);
0176     else if( m_follow == ArtistToArtist )
0177         lastData = Meta::DataPtr::staticCast<Meta::Artist>(lastTrack->artist());
0178     else if( m_follow == AlbumToAlbum )
0179         lastData = Meta::DataPtr::staticCast<Meta::Album>(lastTrack->album());
0180 
0181     if( !lastData || lastData->name().isEmpty() )
0182     {
0183         // debug() << "QuizPlay: no data for"<<lastTrack->name();
0184         return Dynamic::TrackSet( universe, true );
0185     }
0186 
0187     m_currentCharacter = lastChar(lastData->name()).toLower();
0188     // debug() << "QuizPlay: data for"<<lastTrack->name()<<"is"<<m_currentCharacter;
0189 
0190     // -- look if we already buffered it
0191     if( m_characterTrackMap.contains( m_currentCharacter ) )
0192         return m_characterTrackMap.value( m_currentCharacter );
0193 
0194     // -- start a new query
0195     m_tracks = Dynamic::TrackSet( universe, false );
0196     QTimer::singleShot(0,
0197                        const_cast<QuizPlayBias*>(this),
0198                        &QuizPlayBias::newQuery); // create the new query from my parent thread
0199 
0200     return Dynamic::TrackSet();
0201 }
0202 
0203 bool
0204 Dynamic::QuizPlayBias::trackMatches( int position,
0205                                      const Meta::TrackList& playlist,
0206                                      int contextCount ) const
0207 {
0208     Q_UNUSED( contextCount );
0209 
0210     if( position <= 0 || position >= playlist.count())
0211         return true;
0212 
0213     // -- determine the last character we need to quiz
0214     Meta::TrackPtr lastTrack = playlist[position-1];
0215     Meta::DataPtr lastData;
0216     if( m_follow == TitleToTitle )
0217         lastData = Meta::DataPtr::staticCast<Meta::Track>(lastTrack);
0218     else if( m_follow == ArtistToArtist )
0219         lastData = Meta::DataPtr::staticCast<Meta::Artist>(lastTrack->artist());
0220     else if( m_follow == AlbumToAlbum )
0221         lastData = Meta::DataPtr::staticCast<Meta::Album>(lastTrack->album());
0222 
0223     if( !lastData || lastData->name().isEmpty() )
0224         return true;
0225 
0226     // -- determine the first character
0227     Meta::TrackPtr track = playlist[position];
0228     Meta::DataPtr data;
0229     if( m_follow == TitleToTitle )
0230         data = Meta::DataPtr::staticCast<Meta::Track>(track);
0231     else if( m_follow == ArtistToArtist )
0232         data = Meta::DataPtr::staticCast<Meta::Artist>(track->artist());
0233     else if( m_follow == AlbumToAlbum )
0234         data = Meta::DataPtr::staticCast<Meta::Album>(track->album());
0235 
0236     if( !data || data->name().isEmpty() )
0237         return false;
0238 
0239     // -- now compare
0240     QString lastName = lastData->name();
0241     QString name = data->name();
0242     return lastChar( lastName ).toLower() == name[0].toLower();
0243 }
0244 
0245 
0246 Dynamic::QuizPlayBias::FollowType
0247 Dynamic::QuizPlayBias::follow() const
0248 {
0249     return m_follow;
0250 }
0251 
0252 void
0253 Dynamic::QuizPlayBias::setFollow( Dynamic::QuizPlayBias::FollowType value )
0254 {
0255     m_follow = value;
0256     invalidate();
0257     Q_EMIT changed( BiasPtr(this) );
0258 }
0259 
0260 void
0261 Dynamic::QuizPlayBias::updateFinished()
0262 {
0263     m_characterTrackMap.insert( m_currentCharacter, m_tracks );
0264     SimpleMatchBias::updateFinished();
0265 }
0266 
0267 void
0268 Dynamic::QuizPlayBias::invalidate()
0269 {
0270     m_characterTrackMap.clear();
0271     SimpleMatchBias::invalidate();
0272 }
0273 
0274 
0275 void
0276 Dynamic::QuizPlayBias::selectionChanged( int which )
0277 {
0278     if( QComboBox *box = qobject_cast<QComboBox*>(sender()) )
0279         setFollow( followForName( box->itemData( which ).toString() ) );
0280 }
0281 
0282 void
0283 Dynamic::QuizPlayBias::newQuery()
0284 {
0285     // ok, I need a new query maker
0286     m_qm.reset( CollectionManager::instance()->queryMaker() );
0287 
0288     uint field = 0;
0289     switch( m_follow )
0290     {
0291     case Dynamic::QuizPlayBias::TitleToTitle:   field = Meta::valTitle; break;
0292     case Dynamic::QuizPlayBias::ArtistToArtist: field = Meta::valArtist; break;
0293     case Dynamic::QuizPlayBias::AlbumToAlbum:   field = Meta::valAlbum; break;
0294     }
0295     m_qm->addFilter( field,  QString(m_currentCharacter), true, false );
0296 
0297     m_qm->setQueryType( Collections::QueryMaker::Custom );
0298     m_qm->addReturnValue( Meta::valUniqueId );
0299 
0300     connect( m_qm.data(), &Collections::QueryMaker::newResultReady,
0301              this, &QuizPlayBias::updateReady );
0302     connect( m_qm.data(), &Collections::QueryMaker::queryDone,
0303              this, &QuizPlayBias::updateFinished );
0304     m_qm.data()->run();
0305 }
0306 
0307 
0308 QChar
0309 Dynamic::QuizPlayBias::lastChar( const QString &str )
0310 {
0311     int endIndex = str.length();
0312     int index;
0313 
0314     index = str.indexOf( '[' );
0315     if( index > 0 && index < endIndex )
0316         endIndex = index;
0317 
0318     index = str.indexOf( '(' );
0319     if( index > 0 && index < endIndex )
0320         endIndex = index;
0321 
0322     index = str.indexOf( QLatin1String(" cd"), Qt::CaseInsensitive );
0323     if( index > 0 && index < endIndex )
0324         endIndex = index;
0325 
0326     while( endIndex > 0 &&
0327            (str[ endIndex - 1 ].isSpace() || str[ endIndex - 1 ].isPunct()) )
0328         endIndex--;
0329 
0330     if( endIndex > 0 )
0331         return str[ endIndex - 1 ];
0332 
0333     return QChar();
0334 }
0335 
0336 
0337 QString
0338 Dynamic::QuizPlayBias::nameForFollow( Dynamic::QuizPlayBias::FollowType match )
0339 {
0340     switch( match )
0341     {
0342     case Dynamic::QuizPlayBias::TitleToTitle:   return QStringLiteral("titleQuiz");
0343     case Dynamic::QuizPlayBias::ArtistToArtist: return QStringLiteral("artistQuiz");
0344     case Dynamic::QuizPlayBias::AlbumToAlbum:   return QStringLiteral("albumQuiz");
0345     }
0346     return QString();
0347 }
0348 
0349 Dynamic::QuizPlayBias::FollowType
0350 Dynamic::QuizPlayBias::followForName( const QString &name )
0351 {
0352     if( name == QLatin1String("titleQuiz") )       return TitleToTitle;
0353     else if( name == QLatin1String("artistQuiz") ) return ArtistToArtist;
0354     else if( name == QLatin1String("albumQuiz") )  return AlbumToAlbum;
0355     else return TitleToTitle;
0356 }
0357 
0358 
0359 
0360