diff --git a/src/meta/Meta.cpp b/src/meta/Meta.cpp index a12ab573d0..01c33669d5 100644 --- a/src/meta/Meta.cpp +++ b/src/meta/Meta.cpp @@ -1,504 +1,515 @@ /* This file is part of the Amarok project Copyright (C) 2007 Maximilian Kossick Copyright (C) 2007 Ian Monroe Copyright (C) 2008 Mark Kretschmann This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "Meta.h" #include "Amarok.h" #include "amarokconfig.h" #include "Collection.h" #include "Debug.h" #include "QueryMaker.h" #include "SvgHandler.h" #include #include #include //Meta::Observer Meta::Observer::~Observer() { // Unsubscribe all stray Meta subscriptions: foreach( TrackPtr ptr, m_trackSubscriptions ) if( ptr ) ptr->unsubscribe( this ); foreach( ArtistPtr ptr, m_artistSubscriptions ) if( ptr ) ptr->unsubscribe( this ); foreach( AlbumPtr ptr, m_albumSubscriptions ) if( ptr ) ptr->unsubscribe( this ); foreach( GenrePtr ptr, m_genreSubscriptions ) if( ptr ) ptr->unsubscribe( this ); foreach( ComposerPtr ptr, m_composerSubscriptions ) if( ptr ) ptr->unsubscribe( this ); foreach( YearPtr ptr, m_yearSubscriptions ) if( ptr ) ptr->unsubscribe( this ); } void Meta::Observer::subscribeTo( TrackPtr ptr ) { if( ptr ) { ptr->subscribe( this ); m_trackSubscriptions.insert( ptr ); } } void Meta::Observer::unsubscribeFrom( TrackPtr ptr ) { if( ptr ) { ptr->unsubscribe( this ); m_trackSubscriptions.remove( ptr ); } } void Meta::Observer::subscribeTo( ArtistPtr ptr ) { if( ptr ) { ptr->subscribe( this ); m_artistSubscriptions.insert( ptr ); } } void Meta::Observer::unsubscribeFrom( ArtistPtr ptr ) { if( ptr ) { ptr->unsubscribe( this ); m_artistSubscriptions.remove( ptr ); } } void Meta::Observer::subscribeTo( AlbumPtr ptr ) { if( ptr ) { ptr->subscribe( this ); m_albumSubscriptions.insert( ptr ); } } void Meta::Observer::unsubscribeFrom( AlbumPtr ptr ) { if( ptr ) { ptr->unsubscribe( this ); m_albumSubscriptions.remove( ptr ); } } void Meta::Observer::subscribeTo( ComposerPtr ptr ) { if( ptr ) { ptr->subscribe( this ); m_composerSubscriptions.insert( ptr ); } } void Meta::Observer::unsubscribeFrom( ComposerPtr ptr ) { if( ptr ) { ptr->unsubscribe( this ); m_composerSubscriptions.remove( ptr ); } } void Meta::Observer::subscribeTo( GenrePtr ptr ) { if( ptr ) { ptr->subscribe( this ); m_genreSubscriptions.insert( ptr ); } } void Meta::Observer::unsubscribeFrom( GenrePtr ptr ) { if( ptr ) { ptr->unsubscribe( this ); m_genreSubscriptions.remove( ptr ); } } void Meta::Observer::subscribeTo( YearPtr ptr ) { if( ptr ) { ptr->subscribe( this ); m_yearSubscriptions.insert( ptr ); } } void Meta::Observer::unsubscribeFrom( YearPtr ptr ) { if( ptr ) { ptr->unsubscribe( this ); m_yearSubscriptions.remove( ptr ); } } void Meta::Observer::metadataChanged( TrackPtr track ) { Q_UNUSED( track ); } void Meta::Observer::metadataChanged( ArtistPtr artist ) { Q_UNUSED( artist ); } void Meta::Observer::metadataChanged( AlbumPtr album ) { Q_UNUSED( album ); } void Meta::Observer::metadataChanged( ComposerPtr composer ) { Q_UNUSED( composer ); } void Meta::Observer::metadataChanged( GenrePtr genre ) { Q_UNUSED( genre ); } void Meta::Observer::metadataChanged( YearPtr year ) { Q_UNUSED( year ); } //Meta::MetaBase void Meta::MetaBase::subscribe( Observer *observer ) { if( observer ) m_observers.insert( observer ); } void Meta::MetaBase::unsubscribe( Observer *observer ) { m_observers.remove( observer ); } bool Meta::MetaBase::hasCapabilityInterface( Meta::Capability::Type type ) const { Q_UNUSED( type ); return false; } Meta::Capability* Meta::MetaBase::asCapabilityInterface( Meta::Capability::Type type ) { Q_UNUSED( type ); return 0; } //Meta::Track QString Meta::Track::prettyName() const { if( !name().isEmpty() ) return name(); return prettyUrl(); } bool Meta::Track::inCollection() const { return false; } Amarok::Collection* Meta::Track::collection() const { return 0; } QString Meta::Track::cachedLyrics() const { return QString(); } void Meta::Track::setCachedLyrics( const QString &lyrics ) { Q_UNUSED( lyrics ) } void Meta::Track::addMatchTo( QueryMaker *qm ) { qm->addMatch( TrackPtr( this ) ); } void Meta::Track::prepareToPlay() { } void Meta::Track::finishedPlaying( double /*playedFraction*/ ) { } void Meta::Track::notifyObservers() const { foreach( Observer *observer, m_observers ) { if( m_observers.contains( observer ) ) // guard against observers removing themselves in destructors observer->metadataChanged( Meta::TrackPtr( const_cast(this) ) ); } } uint Meta::Track::firstPlayed() const { return 0; } bool Meta::Track::operator==( const Meta::Track &track ) const { return dynamic_cast( this ) == dynamic_cast( &track ); } bool Meta::Track::lessThan( const Meta::TrackPtr left, const Meta::TrackPtr right ) { + if( !left || !right ) // These should never be 0, but it can apparently happen (http://bugs.kde.org/show_bug.cgi?id=181187) + return false; + + if( !left->album() || !right->album() ) + return false; + if( left->album()->name() == right->album()->name() ) // If the albums are the same { if ( left->discNumber() < right->discNumber() ) //First compare by disc number { return true; } else if( left->discNumber() == right->discNumber() ) //Disc #'s are equal, compare by track number { return left->trackNumber() < right->trackNumber(); } else { return false; // Right disc has a lower number } } - else if( left->artist()->name() == right->artist()->name() ) - return QString::localeAwareCompare( left->album()->prettyName(), right->album()->prettyName() ) < 0; - // compare artists alphabetically - return QString::localeAwareCompare( left->artist()->prettyName(), right->artist()->prettyName() ) < 0; + else if( left->artist() && right->artist() ) + { + if( left->artist()->name() == right->artist()->name() ) + return QString::localeAwareCompare( left->album()->prettyName(), right->album()->prettyName() ) < 0; + // compare artists alphabetically + return QString::localeAwareCompare( left->artist()->prettyName(), right->artist()->prettyName() ) < 0; + } + + return false; } //Meta::Artist void Meta::Artist::addMatchTo( QueryMaker *qm ) { qm->addMatch( ArtistPtr( this ) ); } void Meta::Artist::notifyObservers() const { DEBUG_BLOCK foreach( Observer *observer, m_observers ) { if( m_observers.contains( observer ) ) // guard against observers removing themselves in destructors observer->metadataChanged( Meta::ArtistPtr( const_cast(this) ) ); } } bool Meta::Artist::operator==( const Meta::Artist &artist ) const { return dynamic_cast( this ) == dynamic_cast( &artist ); } //Meta::Album void Meta::Album::addMatchTo( QueryMaker *qm ) { qm->addMatch( AlbumPtr( this ) ); } void Meta::Album::notifyObservers() const { DEBUG_BLOCK foreach( Observer *observer, m_observers ) { if( m_observers.contains( observer ) ) // guard against observers removing themselves in destructors observer->metadataChanged( Meta::AlbumPtr( const_cast(this) )); } } QPixmap Meta::Album::image( int size ) { // Return "nocover" until it's fetched. QDir cacheCoverDir = QDir( Amarok::saveLocation( "albumcovers/cache/" ) ); if ( size <= 1 ) size = AmarokConfig::coverPreviewSize(); QString sizeKey = QString::number( size ) + '@'; QImage img; if( cacheCoverDir.exists( sizeKey + "nocover.png" ) ) img = QImage( cacheCoverDir.filePath( sizeKey + "nocover.png" ) ); else { QImage orgImage = QImage( KStandardDirs::locate( "data", "amarok/images/nocover.png" ) ); //optimise this! //scaled() does not change the original image but returns a scaled copy img = orgImage.scaled( size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation ); img.save( cacheCoverDir.filePath( sizeKey + "nocover.png" ), "PNG" ); } m_noCoverImage = true; return QPixmap::fromImage( img ); } QPixmap Meta::Album::imageWithBorder( int size, int borderWidth ) { QPixmap coverWithBorders; m_noCoverImage = false; const int imageSize = size - borderWidth * 2; QPixmap cover = image( imageSize ); QString nameForKey = name(); if( m_noCoverImage == true ) nameForKey = "nocover"; coverWithBorders = The::svgHandler()->addBordersToPixmap( cover, borderWidth, nameForKey ); return coverWithBorders; } bool Meta::Album::operator==( const Meta::Album &album ) const { return dynamic_cast( this ) == dynamic_cast( &album ); } //Meta::Genre void Meta::Genre::addMatchTo( QueryMaker *qm ) { qm->addMatch( GenrePtr( this ) ); } void Meta::Genre::notifyObservers() const { foreach( Observer *observer, m_observers ) { if( m_observers.contains( observer ) ) // guard against observers removing themselves in destructors { observer->metadataChanged( Meta::GenrePtr( const_cast(this) ) ); } } } bool Meta::Genre::operator==( const Meta::Genre &genre ) const { return dynamic_cast( this ) == dynamic_cast( &genre ); } //Meta::Composer void Meta::Composer::addMatchTo( QueryMaker *qm ) { qm->addMatch( ComposerPtr( this ) ); } void Meta::Composer::notifyObservers() const { foreach( Observer *observer, m_observers ) { if( m_observers.contains( observer ) ) // guard against observers removing themselves in destructors observer->metadataChanged( Meta::ComposerPtr( const_cast(this) ) ); } } bool Meta::Composer::operator==( const Meta::Composer &composer ) const { return dynamic_cast( this ) == dynamic_cast( &composer ); } //Meta::Year void Meta::Year::addMatchTo( QueryMaker *qm ) { qm->addMatch( YearPtr( this ) ); } void Meta::Year::notifyObservers() const { foreach( Observer *observer, m_observers ) { if( m_observers.contains( observer ) ) // guard against observers removing themselves in destructors observer->metadataChanged( Meta::YearPtr( const_cast(this) ) ); } } bool Meta::Year::operator==( const Meta::Year &year ) const { return dynamic_cast( this ) == dynamic_cast( &year ); } diff --git a/src/playlist/navigators/RandomAlbumNavigator.cpp b/src/playlist/navigators/RandomAlbumNavigator.cpp index 066ce98018..02bcdca2c2 100644 --- a/src/playlist/navigators/RandomAlbumNavigator.cpp +++ b/src/playlist/navigators/RandomAlbumNavigator.cpp @@ -1,305 +1,306 @@ /*************************************************************************** * copyright : (C) 2008 Nikolaj Hald Nielsen * : (C) 2008 Soren Harward * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . **************************************************************************/ #define DEBUG_PREFIX "Playlist::RandomAlbumNavigator" #include "RandomAlbumNavigator.h" #include "Debug.h" #include "Meta.h" #include "playlist/PlaylistModel.h" #include // STL Playlist::RandomAlbumNavigator::RandomAlbumNavigator() { Model* model = Model::instance(); connect( model, SIGNAL( insertedIds( const QList& ) ), this, SLOT( recvInsertedIds( const QList& ) ) ); connect( model, SIGNAL( removedIds( const QList& ) ), this, SLOT( recvRemovedIds( const QList& ) ) ); connect( model, SIGNAL( activeTrackChanged( const quint64 ) ), this, SLOT( recvActiveTrackChanged( const quint64 ) ) ); for ( int i = 0; i < model->rowCount(); i++ ) { Meta::AlbumPtr album = model->trackAt( i )->album(); m_albumGroups[album].append( model->idAt( i ) ); // conveniently creates an empty list if none exists } m_unplayedAlbums = m_albumGroups.uniqueKeys(); std::random_shuffle( m_unplayedAlbums.begin(), m_unplayedAlbums.end() ); if ( m_unplayedAlbums.size() ) sortTheseAlbums( m_unplayedAlbums ); m_currentAlbum = Meta::AlbumPtr(); m_currentTrack = 0; //dump(); } void Playlist::RandomAlbumNavigator::recvInsertedIds( const QList& list ) { Model* model = Model::instance(); Meta::AlbumList modifiedAlbums; foreach( quint64 id, list ) { Meta::AlbumPtr album = model->trackForId( id )->album(); if ( !m_albumGroups.contains( album ) ) { // TODO: handle already played albums m_unplayedAlbums.append( album ); } if ( !modifiedAlbums.contains( album ) ) modifiedAlbums.append( album ); m_albumGroups[album].append( id ); // conveniently creates an empty list if none exists } sortTheseAlbums( modifiedAlbums ); std::random_shuffle( m_unplayedAlbums.begin(), m_unplayedAlbums.end() ); if ( m_currentAlbum == Meta::AlbumPtr() ) { m_currentAlbum = m_unplayedAlbums.takeFirst(); m_currentTrack = m_albumGroups.value( m_currentAlbum ).first(); } //dump(); } void Playlist::RandomAlbumNavigator::recvRemovedIds( const QList& list ) { QList::const_iterator id_iter; for ( id_iter = list.begin(); id_iter != list.end(); ++id_iter ) { quint64 id = *id_iter; debug() << "removing" << id; QHash::iterator alb_iter = m_albumGroups.begin(); while ( alb_iter != m_albumGroups.end() ) { if ( alb_iter->contains( id ) ) { debug() << " from" << alb_iter.key()->prettyName(); Meta::AlbumPtr album = alb_iter.key(); ItemList atl = alb_iter.value(); if ( m_currentTrack == id ) { int idx = atl.indexOf( id ); m_currentTrack = ( idx < ( atl.size() - 1 ) ) ? atl.at( idx + 1 ) : 0; } atl.removeAll( id ); if ( atl.isEmpty() ) { debug() << album->prettyName() << "is now empty"; alb_iter = m_albumGroups.erase( alb_iter ); m_playedAlbums.removeAll( album ); m_unplayedAlbums.removeAll( album ); if ( album == m_currentAlbum ) m_currentAlbum = ( m_unplayedAlbums.isEmpty() ) ? Meta::AlbumPtr() : m_unplayedAlbums.takeFirst(); } else { ++alb_iter; m_albumGroups.insert( album, atl ); } break; } else { ++alb_iter; } } } //dump(); } void Playlist::RandomAlbumNavigator::recvActiveTrackChanged( const quint64 id ) { if ( id == m_currentTrack ) return; if ( !m_albumGroups.value( m_currentAlbum ).contains( id ) ) { if ( m_currentAlbum != Meta::AlbumPtr() ) m_playedAlbums.prepend( m_currentAlbum ); QHash::iterator alb_iter; for ( alb_iter = m_albumGroups.begin(); alb_iter != m_albumGroups.end(); ++alb_iter ) { if ( alb_iter->contains( id ) ) { Meta::AlbumPtr album = alb_iter.key(); if ( m_playedAlbums.contains( album ) ) { m_currentAlbum = m_playedAlbums.takeAt( m_playedAlbums.indexOf( album ) ); } else { m_currentAlbum = m_unplayedAlbums.takeAt( m_unplayedAlbums.indexOf( album ) ); } } } } m_currentTrack = id; //dump(); } quint64 Playlist::RandomAlbumNavigator::requestNextTrack() { if ( m_unplayedAlbums.isEmpty() && m_playedAlbums.isEmpty() ) return 0; if ( m_unplayedAlbums.isEmpty() && m_repeatPlaylist ) { m_unplayedAlbums = m_playedAlbums; m_playedAlbums.clear(); } if ( m_albumGroups.contains( m_currentAlbum ) ) { ItemList atl = m_albumGroups.value( m_currentAlbum ); int idx = atl.indexOf( m_currentTrack ); if ( idx < ( atl.size() - 1 ) ) { m_currentTrack = atl.at( idx + 1 ); } else { m_playedAlbums.prepend( m_currentAlbum ); if ( !m_unplayedAlbums.isEmpty() ) { m_currentAlbum = m_unplayedAlbums.takeFirst(); m_currentTrack = m_albumGroups.value( m_currentAlbum ).first(); } else { m_currentAlbum = Meta::AlbumPtr(); m_currentTrack = 0; } } } else { if ( !m_unplayedAlbums.isEmpty() ) { m_currentAlbum = m_unplayedAlbums.takeFirst(); m_currentTrack = m_albumGroups.value( m_currentAlbum ).first(); } } return m_currentTrack; } quint64 Playlist::RandomAlbumNavigator::requestLastTrack() { if ( m_unplayedAlbums.isEmpty() && m_playedAlbums.isEmpty() ) return 0; if ( m_playedAlbums.isEmpty() && m_repeatPlaylist ) { m_playedAlbums = m_unplayedAlbums; m_unplayedAlbums.clear(); } if ( m_albumGroups.contains( m_currentAlbum ) ) { ItemList atl = m_albumGroups.value( m_currentAlbum ); int idx = atl.indexOf( m_currentTrack ); if ( idx > 0 ) { m_currentTrack = atl.at( idx - 1 ); } else { m_unplayedAlbums.prepend( m_currentAlbum ); if ( !m_playedAlbums.isEmpty() ) { m_currentAlbum = m_playedAlbums.takeFirst(); m_currentTrack = m_albumGroups.value( m_currentAlbum ).last(); } else { m_currentAlbum = Meta::AlbumPtr(); m_currentTrack = 0; } } } else { if ( !m_playedAlbums.isEmpty() ) { m_currentAlbum = m_playedAlbums.takeFirst(); m_currentTrack = m_albumGroups.value( m_currentAlbum ).last(); } } return m_currentTrack; } bool Playlist::RandomAlbumNavigator::idLessThan( const quint64 l, const quint64 r ) { Model* model = Model::instance(); Meta::TrackPtr left = model->trackForId( l ); Meta::TrackPtr right = model->trackForId( r ); + return Meta::Track::lessThan( left, right ); } void Playlist::RandomAlbumNavigator::sortTheseAlbums( const Meta::AlbumList al ) { foreach( Meta::AlbumPtr a, al ) { qStableSort( m_albumGroups[a].begin(), m_albumGroups[a].end(), idLessThan ); } } void Playlist::RandomAlbumNavigator::dump() { Model* model = Model::instance(); debug() << "album groups are as follows:"; debug() << "unplayed:"; foreach( Meta::AlbumPtr album, m_unplayedAlbums ) { debug() << " in" << album->prettyName(); ItemList atl = m_albumGroups.value( album ); foreach( quint64 id, atl ) { Meta::TrackPtr track = model->trackForId( id ); debug() << " " << track->trackNumber() << track->prettyName() << id; } } if ( m_currentAlbum != Meta::AlbumPtr() ) { debug() << "current:"; debug() << " in" << m_currentAlbum->prettyName(); ItemList atl = m_albumGroups.value( m_currentAlbum ); foreach( quint64 id, atl ) { Meta::TrackPtr track = model->trackForId( id ); debug() << " " << track->trackNumber() << track->prettyName() << id << (( id == m_currentTrack ) ? "***" : "" ); } } debug() << "played:"; foreach( Meta::AlbumPtr album, m_playedAlbums ) { debug() << " in" << album->prettyName(); ItemList atl = m_albumGroups.value( album ); foreach( quint64 id, atl ) { Meta::TrackPtr track = model->trackForId( id ); debug() << " " << track->trackNumber() << track->prettyName() << id; } } }