/* This file is part of Clementine. Copyright 2010, David Sansome Clementine 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 3 of the License, or (at your option) any later version. Clementine 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 Clementine. If not, see . */ #include "playlist.h" #include "playlistbackend.h" #include "playlistmanager.h" #include "core/songloader.h" #include "core/utilities.h" #include "library/librarybackend.h" #include "library/libraryplaylistitem.h" #include "playlistparsers/playlistparser.h" #include "smartplaylists/generator.h" #include #include using smart_playlists::GeneratorPtr; const int PlaylistManagerInterface::kInvalidSongPriority = 200; const QRgb PlaylistManagerInterface::kInvalidSongColor = qRgb(0xC0, 0xC0, 0xC0); PlaylistManager::PlaylistManager(TaskManager* task_manager, QObject *parent) : PlaylistManagerInterface(parent), task_manager_(task_manager), playlist_backend_(NULL), library_backend_(NULL), sequence_(NULL), parser_(NULL), current_(-1), active_(-1) { } PlaylistManager::~PlaylistManager() { foreach (const Data& data, playlists_.values()) { delete data.p; } } void PlaylistManager::Init(LibraryBackend* library_backend, PlaylistBackend* playlist_backend, PlaylistSequence* sequence) { library_backend_ = library_backend; playlist_backend_ = playlist_backend; sequence_ = sequence; parser_ = new PlaylistParser(library_backend, this); connect(library_backend_, SIGNAL(SongsDiscovered(SongList)), SLOT(SongsDiscovered(SongList))); connect(library_backend_, SIGNAL(SongsStatisticsChanged(SongList)), SLOT(SongsDiscovered(SongList))); foreach (const PlaylistBackend::Playlist& p, playlist_backend->GetAllPlaylists()) { AddPlaylist(p.id, p.name); } // If no playlist exists then make a new one if (playlists_.isEmpty()) New(tr("Playlist")); emit PlaylistManagerInitialized(); } QList PlaylistManager::GetAllPlaylists() const { QList result; foreach(const Data& data, playlists_.values()) { result.append(data.p); } return result; } const QItemSelection& PlaylistManager::selection(int id) const { QMap::const_iterator it = playlists_.find(id); return it->selection; } Playlist* PlaylistManager::AddPlaylist(int id, const QString& name) { Playlist* ret = new Playlist(playlist_backend_, task_manager_, library_backend_, id); ret->set_sequence(sequence_); connect(ret, SIGNAL(CurrentSongChanged(Song)), SIGNAL(CurrentSongChanged(Song))); connect(ret, SIGNAL(PlaylistChanged()), SLOT(OneOfPlaylistsChanged())); connect(ret, SIGNAL(PlaylistChanged()), SLOT(UpdateSummaryText())); connect(ret, SIGNAL(EditingFinished(QModelIndex)), SIGNAL(EditingFinished(QModelIndex))); connect(ret, SIGNAL(LoadTracksError(QString)), SIGNAL(Error(QString))); connect(ret, SIGNAL(PlayRequested(QModelIndex)), SIGNAL(PlayRequested(QModelIndex))); playlists_[id] = Data(ret, name); emit PlaylistAdded(id, name); if (current_ == -1) { SetCurrentPlaylist(id); } if (active_ == -1) { SetActivePlaylist(id); } return ret; } void PlaylistManager::New(const QString& name, const SongList& songs) { int id = playlist_backend_->CreatePlaylist(name); if (id == -1) qFatal("Couldn't create playlist"); Playlist* playlist = AddPlaylist(id, name); playlist->InsertSongsOrLibraryItems(songs); SetCurrentPlaylist(id); } void PlaylistManager::Load(const QString& filename) { QUrl url = QUrl::fromLocalFile(filename); SongLoader* loader = new SongLoader(library_backend_, this); connect(loader, SIGNAL(LoadFinished(bool)), SLOT(LoadFinished(bool))); SongLoader::Result result = loader->Load(url); QFileInfo info(filename); if (result == SongLoader::Error || (result == SongLoader::Success && loader->songs().isEmpty())) { emit Error(tr("The playlist '%1' was empty or could not be loaded.").arg( info.completeBaseName())); delete loader; return; } if (result == SongLoader::Success) { New(info.baseName(), loader->songs()); delete loader; } } void PlaylistManager::LoadFinished(bool success) { SongLoader* loader = qobject_cast(sender()); loader->deleteLater(); QString localfile = loader->url().toLocalFile(); QFileInfo info(localfile); if (!success || loader->songs().isEmpty()) { emit Error(tr("The playlist '%1' was empty or could not be loaded.").arg( info.completeBaseName())); } New(info.baseName(), loader->songs()); } void PlaylistManager::Save(int id, const QString& filename) { Q_ASSERT(playlists_.contains(id)); parser_->Save(playlist(id)->GetAllSongs(), filename); } void PlaylistManager::Rename(int id, const QString& new_name) { Q_ASSERT(playlists_.contains(id)); playlist_backend_->RenamePlaylist(id, new_name); playlists_[id].name = new_name; emit PlaylistRenamed(id, new_name); } void PlaylistManager::Remove(int id) { Q_ASSERT(playlists_.contains(id)); // Won't allow removing the last playlist if (playlists_.count() <= 1) return; playlist_backend_->RemovePlaylist(id); int next_id = -1; foreach (int possible_next_id, playlists_.keys()) { if (possible_next_id != id) { next_id = possible_next_id; break; } } if (next_id == -1) return; if (id == active_) SetActivePlaylist(next_id); if (id == current_) SetCurrentPlaylist(next_id); Data data = playlists_.take(id); delete data.p; emit PlaylistRemoved(id); } void PlaylistManager::OneOfPlaylistsChanged() { emit PlaylistChanged(qobject_cast(sender())); } void PlaylistManager::SetCurrentPlaylist(int id) { Q_ASSERT(playlists_.contains(id)); current_ = id; emit CurrentChanged(current()); UpdateSummaryText(); } void PlaylistManager::SetActivePlaylist(int id) { Q_ASSERT(playlists_.contains(id)); // Kinda a hack: unset the current item from the old active playlist before // setting the new one if (active_ != -1 && active_ != id) active()->set_current_row(-1); active_ = id; emit ActiveChanged(active()); sequence_->SetUsingDynamicPlaylist(active()->is_dynamic()); } void PlaylistManager::ClearCurrent() { current()->Clear(); } void PlaylistManager::ShuffleCurrent() { current()->Shuffle(); } void PlaylistManager::SetActivePlaying() { active()->Playing(); } void PlaylistManager::SetActivePaused() { active()->Paused(); } void PlaylistManager::SetActiveStopped() { active()->Stopped(); } void PlaylistManager::SetActiveStreamMetadata(const QUrl &url, const Song &song) { active()->SetStreamMetadata(url, song); } void PlaylistManager::RateCurrentSong(double rating) { active()->RateSong(active()->current_index(), rating); } void PlaylistManager::RateCurrentSong(int rating) { RateCurrentSong(rating / 5.0); } void PlaylistManager::ChangePlaylistOrder(const QList& ids) { playlist_backend_->SetPlaylistOrder(ids); } void PlaylistManager::UpdateSummaryText() { int tracks = current()->rowCount(); quint64 nanoseconds = 0; int selected = 0; // Get the length of the selected tracks foreach (const QItemSelectionRange& range, playlists_[current_id()].selection) { if (!range.isValid()) continue; selected += range.bottom() - range.top() + 1; for (int i=range.top() ; i<=range.bottom() ; ++i) { qint64 length = range.model()->index(i, Playlist::Column_Length).data().toLongLong(); if (length > 0) nanoseconds += length; } } QString summary; if (selected > 1) { summary += tr("%1 selected of").arg(selected) + " "; } else { nanoseconds = current()->GetTotalLength(); } // TODO: Make the plurals translatable summary += tracks == 1 ? tr("1 track") : tr("%1 tracks").arg(tracks); if (nanoseconds) summary += " - [ " + Utilities::WordyTimeNanosec(nanoseconds) + " ]"; emit SummaryTextChanged(summary); } void PlaylistManager::SelectionChanged(const QItemSelection& selection) { playlists_[current_id()].selection = selection; UpdateSummaryText(); } void PlaylistManager::SongsDiscovered(const SongList& songs) { // Some songs might've changed in the library, let's update any playlist // items we have that match those songs foreach (const Song& song, songs) { foreach (const Data& data, playlists_) { PlaylistItemList items = data.p->library_items_by_id(song.id()); foreach (PlaylistItemPtr item, items) { if (item->Metadata().directory_id() != song.directory_id()) continue; static_cast(item.get())->SetMetadata(song); data.p->ItemChanged(item); } } } } void PlaylistManager::PlaySmartPlaylist(GeneratorPtr generator, bool as_new, bool clear) { if (as_new) { New(generator->name()); } if (clear) { current()->Clear(); } current()->InsertSmartPlaylist(generator); } // When Player has processed the new song chosen by the user... void PlaylistManager::SongChangeRequestProcessed(const QUrl& url, bool valid) { foreach(Playlist* playlist, GetAllPlaylists()) { PlaylistItemPtr current = playlist->current_item(); if(current) { Song current_song = current->Metadata(); // if validity has changed, reload the item if(current_song.filetype() != Song::Type_Stream && current_song.filename() == url.toLocalFile() && current_song.is_valid() != QFile::exists(current_song.filename())) { playlist->ReloadItems(QList() << playlist->current_row()); } // gray out the song if it's now broken; otherwise undo the gray color if(valid) { current->RemoveForegroundColor(kInvalidSongPriority); } else { current->SetForegroundColor(kInvalidSongPriority, kInvalidSongColor); } // we have at most one current item break; } } }