diff --git a/src/mpris2/mpris2.cpp b/src/mpris2/mpris2.cpp index b23d6aa3f6..9a1b9229f8 100644 --- a/src/mpris2/mpris2.cpp +++ b/src/mpris2/mpris2.cpp @@ -107,6 +107,10 @@ namespace mpris { constexpr char kMprisObjectPath[] = "/org/mpris/MediaPlayer2"; constexpr char kServiceName[] = "org.mpris.MediaPlayer2.strawberry"; constexpr char kFreedesktopPath[] = "org.freedesktop.DBus.Properties"; +constexpr char kTrackPrefix[] = "/org/strawberrymusicplayer/strawberry/Track/"; +constexpr char kNoTrack[] = "/org/mpris/MediaPlayer2/TrackList/NoTrack"; + +constexpr int kNbTracksDisplaid = 20; Mpris2::Mpris2(const SharedPtr player, const SharedPtr playlist_manager, @@ -141,6 +145,8 @@ Mpris2::Mpris2(const SharedPtr player, QObject::connect(&*playlist_manager_, &PlaylistManager::PlaylistManagerInitialized, this, &Mpris2::PlaylistManagerInitialized); QObject::connect(&*playlist_manager_, &PlaylistManager::AllPlaylistsLoaded, this, &Mpris2::AllPlaylistsLoaded); QObject::connect(&*playlist_manager_, &PlaylistManager::CurrentSongChanged, this, &Mpris2::CurrentSongChanged); + QObject::connect(&*playlist_manager_, &PlaylistManager::PlaylistItemsAdded, this, &Mpris2::PlaylistItemsAdded); + QObject::connect(&*playlist_manager_, &PlaylistManager::PlaylistItemsRemoved, this, &Mpris2::PlaylistItemsRemoved); QObject::connect(&*playlist_manager_, &PlaylistManager::PlaylistChanged, this, &Mpris2::PlaylistChangedSlot); QObject::connect(&*playlist_manager_, &PlaylistManager::CurrentChanged, this, &Mpris2::PlaylistCollectionChanged); @@ -340,6 +346,7 @@ QString Mpris2::LoopStatus() const { } switch (playlist_manager_->active() ? playlist_manager_->active()->RepeatMode() : playlist_manager_->sequence()->repeat_mode()) { + case PlaylistSequence::RepeatMode::Off: return u"None"_s; case PlaylistSequence::RepeatMode::Album: case PlaylistSequence::RepeatMode::Playlist: return u"Playlist"_s; case PlaylistSequence::RepeatMode::Track: return u"Track"_s; @@ -350,8 +357,11 @@ QString Mpris2::LoopStatus() const { void Mpris2::SetLoopStatus(const QString &value) { - PlaylistSequence::RepeatMode mode = PlaylistSequence::RepeatMode::Off; + if (!playlist_manager_->sequence()) { + return; + } + PlaylistSequence::RepeatMode mode = PlaylistSequence::RepeatMode::Off; if (value == "None"_L1) { mode = PlaylistSequence::RepeatMode::Off; } @@ -362,7 +372,12 @@ void Mpris2::SetLoopStatus(const QString &value) { mode = PlaylistSequence::RepeatMode::Playlist; } - playlist_manager_->active()->sequence()->SetRepeatMode(mode); + if (playlist_manager_->active()) { + playlist_manager_->active()->sequence()->SetRepeatMode(mode); + } + else { + playlist_manager_->sequence()->SetRepeatMode(mode); + } } @@ -378,20 +393,38 @@ void Mpris2::SetRate(double rate) { bool Mpris2::Shuffle() const { + if (!playlist_manager_->active() && !playlist_manager_->sequence()) { + return false; + } + const PlaylistSequence::ShuffleMode shuffle_mode = playlist_manager_->active() ? playlist_manager_->active()->ShuffleMode() : playlist_manager_->sequence()->shuffle_mode(); return shuffle_mode != PlaylistSequence::ShuffleMode::Off; } void Mpris2::SetShuffle(bool enable) { - playlist_manager_->active()->sequence()->SetShuffleMode(enable ? PlaylistSequence::ShuffleMode::All : PlaylistSequence::ShuffleMode::Off); + + if (!playlist_manager_->sequence()) { + return; + } + + const PlaylistSequence::ShuffleMode mode = enable ? PlaylistSequence::ShuffleMode::All : PlaylistSequence::ShuffleMode::Off; + if (playlist_manager_->active()) { + playlist_manager_->active()->sequence()->SetShuffleMode(mode); + } + else { + playlist_manager_->sequence()->SetShuffleMode(mode); + } + } QVariantMap Mpris2::Metadata() const { return last_metadata_; } double Mpris2::Rating() const { - float rating = playlist_manager_->active()->current_item_metadata().rating(); + + const float rating = playlist_manager_->active() ? playlist_manager_->active()->current_item_metadata().rating() : .0F; return (rating <= 0) ? 0 : rating; + } void Mpris2::SetRating(double rating) { @@ -408,11 +441,11 @@ void Mpris2::SetRating(double rating) { } int Mpris2::current_playlist_row() const { - return playlist_manager_->active()->current_row(); + return playlist_manager_->active() ? playlist_manager_->active()->current_row() : -1; } -QDBusObjectPath Mpris2::current_track_id(const int current_row) const { - return QDBusObjectPath(QStringLiteral("/org/strawberrymusicplayer/strawberry/Track/%1").arg(current_row)); +QDBusObjectPath Mpris2::current_track_id(const QUuid ¤t_row) const { + return QDBusObjectPath(QStringLiteral("/org/strawberrymusicplayer/strawberry/Track/%1").arg(current_row.toString(QUuid::WithoutBraces).replace(u'-', u'_'))); } // We send Metadata change notification as soon as the process of changing song starts... @@ -427,17 +460,53 @@ void Mpris2::CurrentSongChanged(const Song &song) { } +void Mpris2::PlaylistItemsAdded(const int playlist_id, const QList &tracks_id, const QUuid after_track_id) { + + if (tracks_id.count() != 1 || !playlist_manager_->active() || playlist_manager_->active_id() != playlist_id) { + return; + } + QDBusObjectPath after_track(QLatin1String(kTrackPrefix) + after_track_id.toString(QUuid::WithoutBraces).replace(u'-', u'_')); + + if (after_track_id.isNull()) { + after_track = QDBusObjectPath(kNoTrack); + } + Q_EMIT TrackAdded(GetTracksMetadata(Track_Ids() << QDBusObjectPath(QLatin1String(kTrackPrefix) + tracks_id[0].toString(QUuid::WithoutBraces).replace(u'-', u'_'))), after_track); + +} + +void Mpris2::PlaylistItemsRemoved(const int playlist_id, const QList &tracks_id) { + + if (tracks_id.count() != 1 || !playlist_manager_->active() || playlist_manager_->active_id() != playlist_id) { + return; + } + Q_EMIT TrackRemoved(QDBusObjectPath(QLatin1String(kTrackPrefix) + tracks_id[0].toString(QUuid::WithoutBraces).replace(u'-', u'_'))); + +} + +void Mpris2::EmitTrackListReplaced() { + const Track_Ids tracks = Tracks(); + const int current_row = playlist_manager_->active()->current_row(); + QDBusObjectPath current_track_path(kNoTrack); + if (current_row >= 0 && current_row < playlist_manager_->active()->rowCount()) { + const QUuid &unique_id = playlist_manager_->active()->items()[current_row]->uuid(); + current_track_path = QDBusObjectPath(QLatin1String(kTrackPrefix) + unique_id.toString(QUuid::WithoutBraces).replace(u'-', u'_')); + } + + Q_EMIT TrackListReplaced(tracks, current_track_path); +} + // ... and we add the cover information later, when it's available. void Mpris2::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result) { const int current_row = current_playlist_row(); if (current_row == -1) return; + const QUuid unique_id = playlist_manager_->active()->items()[current_row]->uuid(); last_metadata_ = QVariantMap(); song.ToXesam(&last_metadata_); using mpris::AddMetadata; - AddMetadata(u"mpris:trackid"_s, current_track_id(current_row), &last_metadata_); + AddMetadata(u"mpris:trackid"_s, current_track_id(unique_id), &last_metadata_); QUrl cover_url; if (result.album_cover.cover_url.isValid() && result.album_cover.cover_url.isLocalFile() && QFile(result.album_cover.cover_url.toLocalFile()).exists()) { @@ -580,10 +649,10 @@ void Mpris2::Seek(qint64 offset) { void Mpris2::SetPosition(const QDBusObjectPath &trackId, qint64 offset) { const int current_row = current_playlist_row(); - if (current_row == -1) return; - if (CanSeek() && trackId == current_track_id(current_row) && offset >= 0) { + const QUuid unique_id = playlist_manager_->active()->items()[current_row]->uuid(); + if (CanSeek() && trackId == current_track_id(unique_id) && offset >= 0) { offset *= kNsecPerUsec; if (offset < player_->GetCurrentItem()->EffectiveMetadata().length_nanosec()) { @@ -594,43 +663,149 @@ void Mpris2::SetPosition(const QDBusObjectPath &trackId, qint64 offset) { } void Mpris2::OpenUri(const QString &uri) { - playlist_manager_->active()->InsertUrls(QList() << QUrl(uri), -1, true); + + if (playlist_manager_->active()) { + playlist_manager_->active()->InsertUrls(QList() << QUrl(uri), -1, true); + } + } Track_Ids Mpris2::Tracks() const { - // TODO - return Track_Ids(); + + if (!playlist_manager_->active() || playlist_manager_->active()->rowCount() == 0) { + return Track_Ids(); + } + + Track_Ids track_ids; + int current_row = playlist_manager_->active()->current_row() - kNbTracksDisplaid; + int last_row = playlist_manager_->active()->current_row() + kNbTracksDisplaid; + + if (current_row < 0) { + current_row = 0; + } + if (last_row >= playlist_manager_->active()->rowCount()) { + last_row = playlist_manager_->active()->rowCount(); + } + for (; current_row < last_row; ++current_row) { + track_ids << current_track_id(playlist_manager_->active()->items()[current_row]->uuid()); + } + + return track_ids; + } -bool Mpris2::CanEditTracks() const { return false; } +bool Mpris2::CanEditTracks() const { return playlist_manager_->active() != nullptr; } TrackMetadata Mpris2::GetTracksMetadata(const Track_Ids &tracks) const { - Q_UNUSED(tracks); + if (!playlist_manager_->active()) { + return TrackMetadata(); + } + + TrackMetadata track_metadata; + for (const QDBusObjectPath &track_object_path : tracks) { + const QString path = track_object_path.path(); + static const QString track_prefix = QLatin1String(kTrackPrefix); + if (!path.startsWith(track_prefix)) { + track_metadata << QVariantMap(); + continue; + } + QString id_part = path.mid(track_prefix.length()); + if (id_part.isEmpty()) { + track_metadata << QVariantMap(); + continue; + } + const QUuid playlist_id = QUuid::fromString(id_part.replace(u'_', u'-')); + PlaylistItemPtr playlist_item = playlist_manager_->active()->find_item_id(playlist_id); + if (!playlist_item) { + track_metadata << QVariantMap(); + continue; + } + QVariantMap track_map; + playlist_item->EffectiveMetadata().ToXesam(&track_map); + track_map.insert(u"mpris:trackid"_s, QVariant::fromValue(track_object_path)); + track_metadata << track_map; + } - // TODO - return TrackMetadata(); + return track_metadata; } void Mpris2::AddTrack(const QString &uri, const QDBusObjectPath &afterTrack, bool setAsCurrent) { - Q_UNUSED(uri); - Q_UNUSED(afterTrack); - Q_UNUSED(setAsCurrent); + if (!playlist_manager_->active() || playlist_manager_->active_id() < 0) { + return; + } + int after_track_pos = playlist_manager_->active()->rowCount() - 1; + const QString path = afterTrack.path(); + if (path == u"/"_s || path == QLatin1String(kNoTrack)) { + after_track_pos = -1; + } + else { + static const QString track_prefix = QLatin1String(kTrackPrefix); + if (path.startsWith(track_prefix)) { + QString id_part = path.mid(track_prefix.length()); + if (!id_part.isEmpty()) { + const QUuid parsed_track_id = QUuid::fromString(id_part.replace(u'_', u'-')); + + after_track_pos = playlist_manager_->active()->find_pos_id(parsed_track_id); + } + } + } - // TODO + const int insert_row = after_track_pos + 1; + playlist_manager_->InsertUrls(playlist_manager_->active_id(), QList() << QUrl(uri), insert_row, setAsCurrent, false, true); } void Mpris2::RemoveTrack(const QDBusObjectPath &trackId) { - Q_UNUSED(trackId); - // TODO + + if (!playlist_manager_->active()) { + return; + } + + const QString path = trackId.path(); + if (!path.startsWith(QLatin1String(kTrackPrefix))) { + return; + } + QStringList split_path = path.split(u'/'); + if (split_path.isEmpty()) { + return; + } + const QUuid track_uid = QUuid::fromString(split_path.last().replace(u'_', u'-')); + const int track_index = playlist_manager_->active()->find_pos_id(track_uid); + + if (track_index >= 0 && !playlist_manager_->active()->removeRowSignal(track_index)) { + qLog(Warning) << "Mpris2::RemoveTrack failed to remove row " << track_index << " id " << track_uid; + } + } void Mpris2::GoTo(const QDBusObjectPath &trackId) { - Q_UNUSED(trackId); - // TODO + + if (!playlist_manager_->active()) { + return; + } + + const QString path = trackId.path(); + if (!path.startsWith(QLatin1String(kTrackPrefix))) { + return; + } + QStringList split_path = path.split(u'/'); + if (split_path.isEmpty()) { + return; + } + const QUuid track_uid = QUuid::fromString(split_path.last().replace(u'_', u'-')); + const int track_index = playlist_manager_->active()->find_pos_id(track_uid); + + if (track_index >= 0) { + playlist_manager_->active()->set_current_row(track_index); + EmitTrackListReplaced(); + + // As the GoTo should launch the track play... and PlayAt has some parameters I am not sure of + Play(); + } + } quint32 Mpris2::PlaylistCount() const { @@ -680,14 +855,12 @@ void Mpris2::ActivatePlaylist(const QDBusObjectPath &playlist_id) { } playlist_manager_->SetActivePlaylist(p); player_->Next(); + EmitTrackListReplaced(); } -// TODO: Support sort orders. MprisPlaylistList Mpris2::GetPlaylists(quint32 index, quint32 max_count, const QString &order, bool reverse_order) { - Q_UNUSED(order); - const QList playlists = playlist_manager_->GetAllPlaylists(); MprisPlaylistList ret; ret.reserve(playlists.count()); @@ -698,6 +871,17 @@ MprisPlaylistList Mpris2::GetPlaylists(quint32 index, quint32 max_count, const Q ret << mpris_playlist; } + if (!order.isEmpty()) { + // Align with Orderings(): currently only "Alphabetical" is advertised there. + // other values : CreationDate, ModifiedDate, LastPlayDate cannot be handled as we don't have such values + if (order == "Alphabetical"_L1) { + std::sort(ret.begin(), ret.end(), [](const MprisPlaylist &a, const MprisPlaylist &b) { return a.name < b.name; }); + } + // Let's say user defined will sort by the id + else if (order == "UserDefined"_L1) { + std::sort(ret.begin(), ret.end(), [](const MprisPlaylist &a, const MprisPlaylist &b) { return a.id < b.id; }); + } + } if (reverse_order) { std::reverse(ret.begin(), ret.end()); } diff --git a/src/mpris2/mpris2.h b/src/mpris2/mpris2.h index 27b6cb85f2..50cd369979 100644 --- a/src/mpris2/mpris2.h +++ b/src/mpris2/mpris2.h @@ -205,6 +205,9 @@ class Mpris2 : public QObject { void ActivatePlaylist(const QDBusObjectPath &playlist_id); MprisPlaylistList GetPlaylists(quint32 index, quint32 max_count, const QString &order, bool reverse_order); + // To emit the track list changed signal + void EmitTrackListReplaced(); + Q_SIGNALS: // Player void Seeked(const qint64 position); @@ -232,6 +235,8 @@ class Mpris2 : public QObject { void RepeatModeChanged(); void PlaylistChangedSlot(Playlist *playlist); void PlaylistCollectionChanged(Playlist *playlist); + void PlaylistItemsAdded(const int playlist_id, const QList &tracks_id, const QUuid after_track_id); + void PlaylistItemsRemoved(const int playlist_id, const QList &tracks_id); private: void EmitNotification(const QString &name); @@ -241,7 +246,7 @@ class Mpris2 : public QObject { QString PlaybackStatus(EngineBase::State state) const; int current_playlist_row() const; - QDBusObjectPath current_track_id(const int current_row) const; + QDBusObjectPath current_track_id(const QUuid ¤t_row) const; bool CanSeek(EngineBase::State state) const; diff --git a/src/playlist/playlist.cpp b/src/playlist/playlist.cpp index 9fcd183b8b..a0476ad547 100644 --- a/src/playlist/playlist.cpp +++ b/src/playlist/playlist.cpp @@ -122,6 +122,8 @@ constexpr qint64 kMaxScrobblePointNsecs = 240LL * kNsecPerSec; constexpr int kMaxPlayedIndexes = 100; +const PlaylistItemPtr empty_item; + } // namespace Playlist::Playlist(const SharedPtr task_manager, @@ -961,12 +963,12 @@ bool Playlist::dropMimeData(const QMimeData *data, Qt::DropAction action, const } -void Playlist::InsertUrls(const QList &urls, const int pos, const bool play_now, const bool enqueue, const bool enqueue_next) { +void Playlist::InsertUrls(const QList &urls, const int pos, const bool play_now, const bool enqueue, const bool enqueue_next, const bool emit_signal) { SongLoaderInserter *inserter = new SongLoaderInserter(task_manager_, tagreader_client_, url_handlers_, collection_backend_); QObject::connect(inserter, &SongLoaderInserter::Error, this, &Playlist::Error); - inserter->Load(this, pos, play_now, enqueue, enqueue_next, urls); + inserter->Load(this, pos, play_now, enqueue, enqueue_next, emit_signal, urls); } @@ -1145,7 +1147,7 @@ void Playlist::MoveItemsWithoutUndo(int start, const QList &dest_rows) { } -void Playlist::InsertItems(const PlaylistItemPtrList &itemsIn, const int pos, const bool play_now, const bool enqueue, const bool enqueue_next) { +void Playlist::InsertItems(const PlaylistItemPtrList &itemsIn, const int pos, const bool play_now, const bool enqueue, const bool enqueue_next, const bool emit_signal) { if (itemsIn.isEmpty()) { return; @@ -1157,23 +1159,34 @@ void Playlist::InsertItems(const PlaylistItemPtrList &itemsIn, const int pos, co if (items.count() > kUndoItemLimit) { // Too big to keep in the undo stack. Also clear the stack because it might have been invalidated. - InsertItemsWithoutUndo(items, pos, enqueue, enqueue_next); + InsertItemsWithoutUndo(items, pos, enqueue, enqueue_next, emit_signal); undo_stack_->clear(); } else { - undo_stack_->push(new PlaylistUndoCommandInsertItems(this, items, pos, enqueue, enqueue_next)); + undo_stack_->push(new PlaylistUndoCommandInsertItems(this, items, pos, enqueue, enqueue_next, emit_signal)); } if (play_now) Q_EMIT PlayRequested(index(start, 0), AutoScroll::Maybe); } -void Playlist::InsertItemsWithoutUndo(const PlaylistItemPtrList &items, const int pos, const bool enqueue, const bool enqueue_next) { +void Playlist::InsertItemsWithoutUndo(const PlaylistItemPtrList &items, const int pos, const bool enqueue, const bool enqueue_next, const bool emit_signal) { if (items.isEmpty()) return; + QUuid after_track_id; const int start = pos == -1 ? static_cast(items_.count()) : pos; const int end = start + static_cast(items.count()) - 1; + QList tracks_id_added; + + if (items_.length() > 0) { + if (static_cast(pos) < items_.length()) { + after_track_id = items_[pos]->uuid(); + } + else { + after_track_id = items_[items_.length() - 1]->uuid(); + } + } bool has_generated_uuids = false; beginInsertRows(QModelIndex(), start, end); @@ -1182,6 +1195,8 @@ void Playlist::InsertItemsWithoutUndo(const PlaylistItemPtrList &items, const in items_.insert(i, item); virtual_items_ << static_cast(virtual_items_.count()); + tracks_id_added << item->uuid(); + if (Song::IsLinkedCollectionSource(item->source())) { const int id = item->EffectiveMetadata().id(); if (id != -1) { @@ -1202,6 +1217,8 @@ void Playlist::InsertItemsWithoutUndo(const PlaylistItemPtrList &items, const in } endInsertRows(); + if (emit_signal) Q_EMIT PlaylistItemsAdded(id_, tracks_id_added, after_track_id); + if (enqueue) { QModelIndexList indexes; for (int i = start; i <= end; ++i) { @@ -1241,7 +1258,7 @@ void Playlist::InsertSongs(const SongList &songs, const int pos, const bool play InsertSongItems(songs, pos, play_now, enqueue, enqueue_next); } -void Playlist::InsertSongsOrCollectionItems(const SongList &songs, const QString &playlist_name, const int pos, const bool play_now, const bool enqueue, const bool enqueue_next) { +void Playlist::InsertSongsOrCollectionItems(const SongList &songs, const QString &playlist_name, const int pos, const bool play_now, const bool enqueue, const bool enqueue_next, const bool emit_signal) { if (!playlist_name.isEmpty()) { Q_EMIT Rename(id_, playlist_name); @@ -1253,7 +1270,7 @@ void Playlist::InsertSongsOrCollectionItems(const SongList &songs, const QString items << PlaylistItem::NewFromSong(song); } - InsertItems(items, pos, play_now, enqueue, enqueue_next); + InsertItems(items, pos, play_now, enqueue, enqueue_next, emit_signal); } @@ -1783,6 +1800,18 @@ bool Playlist::removeRows(const int row, const int count, const QModelIndex &par } +bool Playlist::removeRowSignal(const int row) { + + if (row < 0 || row >= items_.size()) { + return false; + } + + undo_stack_->push(new PlaylistUndoCommandRemoveItems(this, row, 1, true)); + + return true; + +} + bool Playlist::removeRows(QList &rows) { if (rows.isEmpty()) { @@ -1812,7 +1841,7 @@ bool Playlist::removeRows(QList &rows) { } -PlaylistItemPtrList Playlist::RemoveItemsWithoutUndo(const int row, const int count) { +PlaylistItemPtrList Playlist::RemoveItemsWithoutUndo(const int row, const int count, const bool emit_signal) { if (row < 0 || row >= items_.size() || row + count > items_.size()) { return PlaylistItemPtrList(); @@ -1821,10 +1850,12 @@ PlaylistItemPtrList Playlist::RemoveItemsWithoutUndo(const int row, const int co // Remove items beginRemoveRows(QModelIndex(), row, row + count - 1); PlaylistItemPtrList items; + QList tracks_id_removed; items.reserve(count); for (int i = 0; i < count; ++i) { PlaylistItemPtr item(items_.takeAt(row)); items << item; + tracks_id_removed << item->uuid(); const int id = item->EffectiveMetadata().id(); const int source_id = item->EffectiveMetadata().source_id(); if (id != -1 && collection_items_[source_id].contains(id, item)) { @@ -1832,6 +1863,8 @@ PlaylistItemPtrList Playlist::RemoveItemsWithoutUndo(const int row, const int co } } + if (emit_signal) Q_EMIT PlaylistItemsRemoved(id_, tracks_id_removed); + // Update virtual items for (int i = row; i < items_.count() + count; ++i) { Q_ASSERT(virtual_items_.count(i) == 1); @@ -1912,6 +1945,30 @@ bool Playlist::stop_after_current() const { } +const PlaylistItemPtr &Playlist::find_item_id(const QUuid &id) const { + + for (const PlaylistItemPtr &item : items_) { + if (item->uuid() == id) { + return item; + } + } + return empty_item; + +} + +int Playlist::find_pos_id(const QUuid &id) const { + + int pos_found = 0; + for (const PlaylistItemPtr &item : items_) { + if (item->uuid() == id) { + return pos_found; + } + ++pos_found; + } + return -1; + +} + PlaylistItemPtr Playlist::current_item() const { // QList[] runs in constant time, so no need to cache current_item diff --git a/src/playlist/playlist.h b/src/playlist/playlist.h index 31b6892cf1..38a7a406c4 100644 --- a/src/playlist/playlist.h +++ b/src/playlist/playlist.h @@ -205,6 +205,8 @@ class Playlist : public QAbstractListModel { QString special_type() const { return special_type_; } void set_special_type(const QString &v) { special_type_ = v; } + const PlaylistItemPtr &find_item_id(const QUuid &id) const; + int find_pos_id(const QUuid &id) const; const PlaylistItemPtr &item_at(const int index) const { return items_[index]; } bool has_item_at(const int index) const { return index >= 0 && index < rowCount(); } @@ -233,10 +235,10 @@ class Playlist : public QAbstractListModel { void UpdateScrobblePoint(const qint64 seek_point_nanosec = 0); // Changing the playlist - void InsertItems(const PlaylistItemPtrList &itemsIn, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false); + void InsertItems(const PlaylistItemPtrList &itemsIn, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false, const bool emit_signal = false); void InsertCollectionItems(const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false); void InsertSongs(const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false); - void InsertSongsOrCollectionItems(const SongList &songs, const QString &playlist_name = QString(), const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false); + void InsertSongsOrCollectionItems(const SongList &songs, const QString &playlist_name = QString(), const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false, const bool emit_signal = false); void InsertSmartPlaylist(PlaylistGeneratorPtr gen, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false); void InsertStreamingItems(StreamingServicePtr service, const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false); void InsertRadioItems(const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false); @@ -263,6 +265,7 @@ class Playlist : public QAbstractListModel { #endif // QAbstractListModel + PlaylistItemPtrList items() const { return items_; } int rowCount(const QModelIndex& = QModelIndex()) const override { return items_.count(); } int columnCount(const QModelIndex& = QModelIndex()) const override { return static_cast(ColumnCount); } QVariant data(const QModelIndex &idx, const int role = Qt::DisplayRole) const override; @@ -275,6 +278,7 @@ class Playlist : public QAbstractListModel { bool dropMimeData(const QMimeData *data, Qt::DropAction action, const int row, const int column, const QModelIndex &parent_index) override; void sort(const int column_number, const Qt::SortOrder order) override; bool removeRows(const int row, const int count, const QModelIndex &parent = QModelIndex()) override; + bool removeRowSignal(const int row); static Columns ChangedColumns(const Song &metadata1, const Song &metadata2); static bool MinorMetadataChange(const Song &old_metadata, const Song &new_metadata); @@ -309,7 +313,7 @@ class Playlist : public QAbstractListModel { void SetColumnAlignment(const ColumnAlignmentMap &alignment); - void InsertUrls(const QList &urls, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false); + void InsertUrls(const QList &urls, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false, const bool emit_signal = false); // Removes items with given indices from the playlist. This operation is not undoable. void RemoveItemsWithoutUndo(const QList &indicesIn); @@ -328,6 +332,11 @@ class Playlist : public QAbstractListModel { void PlayRequested(const QModelIndex idx, const Playlist::AutoScroll autoscroll); void MaybeAutoscroll(const Playlist::AutoScroll autoscroll); + // Signal for tracks added to playlist, mainly used by mpris + void PlaylistItemsAdded(const int playlist_id, const QList &tracks_id, const QUuid after_track_id); + // Signal for tracks removed from playlist, mainly used by mpris + void PlaylistItemsRemoved(const int playlist_id, const QList &tracks_id); + // Signals that the underlying list of items was changed, meaning that something was added to it, removed from it or the ordering changed. void PlaylistChanged(); void DynamicModeChanged(bool dynamic); @@ -349,8 +358,8 @@ class Playlist : public QAbstractListModel { void InsertSongItems(const SongList &songs, const int pos, const bool play_now, const bool enqueue, const bool enqueue_next = false); // Modify the playlist without changing the undo stack. These are used by our friends in PlaylistUndoCommands - void InsertItemsWithoutUndo(const PlaylistItemPtrList &items, const int pos, const bool enqueue = false, const bool enqueue_next = false); - PlaylistItemPtrList RemoveItemsWithoutUndo(const int row, const int count); + void InsertItemsWithoutUndo(const PlaylistItemPtrList &items, const int pos, const bool enqueue = false, const bool enqueue_next = false, const bool emit_signal = false); + PlaylistItemPtrList RemoveItemsWithoutUndo(const int row, const int count, const bool emit_signal = false); void MoveItemsWithoutUndo(const QList &source_rows, int pos); void MoveItemWithoutUndo(const int source, const int dest); void MoveItemsWithoutUndo(int start, const QList &dest_rows); diff --git a/src/playlist/playlistitem.h b/src/playlist/playlistitem.h index e2319197fb..ff853b2434 100644 --- a/src/playlist/playlistitem.h +++ b/src/playlist/playlistitem.h @@ -36,6 +36,7 @@ #include #include #include +#include #include "includes/shared_ptr.h" #include "core/song.h" @@ -115,6 +116,7 @@ class PlaylistItem : public enable_shared_from_this { virtual bool IsLocalCollectionItem() const { return false; } void SetShouldSkip(const bool should_skip); bool GetShouldSkip() const; + const QUuid &GetId() const; protected: Song::Source source_; diff --git a/src/playlist/playlistmanager.cpp b/src/playlist/playlistmanager.cpp index 8118228f61..bdd50e7288 100644 --- a/src/playlist/playlistmanager.cpp +++ b/src/playlist/playlistmanager.cpp @@ -161,6 +161,8 @@ Playlist *PlaylistManager::AddPlaylist(const int id, const QString &name, const QObject::connect(ret, &Playlist::CurrentSongChanged, this, &PlaylistManager::CurrentSongChanged); QObject::connect(ret, &Playlist::CurrentSongMetadataChanged, this, &PlaylistManager::CurrentSongMetadataChanged); + QObject::connect(ret, &Playlist::PlaylistItemsAdded, this, &PlaylistManager::PlaylistItemsAdded); + QObject::connect(ret, &Playlist::PlaylistItemsRemoved, this, &PlaylistManager::PlaylistItemsRemoved); QObject::connect(ret, &Playlist::PlaylistChanged, this, &PlaylistManager::OneOfPlaylistsChanged); QObject::connect(ret, &Playlist::PlaylistChanged, this, &PlaylistManager::UpdateSummaryText); QObject::connect(ret, &Playlist::EditingFinished, this, &PlaylistManager::EditingFinished); @@ -499,11 +501,11 @@ void PlaylistManager::SongChangeRequestProcessed(const QUrl &url, const bool val } -void PlaylistManager::InsertUrls(const int id, const QList &urls, const int pos, const bool play_now, const bool enqueue) { +void PlaylistManager::InsertUrls(const int id, const QList &urls, const int pos, const bool play_now, const bool enqueue, const bool emit_signal) { Q_ASSERT(playlists_.contains(id)); - playlists_[id].p->InsertUrls(urls, pos, play_now, enqueue); + playlists_[id].p->InsertUrls(urls, pos, play_now, enqueue, emit_signal); } diff --git a/src/playlist/playlistmanager.h b/src/playlist/playlistmanager.h index be568aed22..82ebc5d825 100644 --- a/src/playlist/playlistmanager.h +++ b/src/playlist/playlistmanager.h @@ -124,7 +124,7 @@ class PlaylistManager : public PlaylistManagerInterface { void SongChangeRequestProcessed(const QUrl &url, const bool valid) override; - void InsertUrls(const int id, const QList &urls, const int pos = -1, const bool play_now = false, const bool enqueue = false); + void InsertUrls(const int id, const QList &urls, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool emit_signal = false); void InsertSongs(const int id, const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false); // Removes items with given indices from the playlist. This operation is not undoable. void RemoveItemsWithoutUndo(const int id, const QList &indices); diff --git a/src/playlist/playlistmanagerinterface.h b/src/playlist/playlistmanagerinterface.h index f22b8c299c..f936e401d0 100644 --- a/src/playlist/playlistmanagerinterface.h +++ b/src/playlist/playlistmanagerinterface.h @@ -126,6 +126,8 @@ class PlaylistManagerInterface : public QObject { // Forwarded from individual playlists void CurrentSongChanged(const Song &song); void CurrentSongMetadataChanged(const Song &song); + void PlaylistItemsAdded(const int playlist_id, const QList &tracks_id, const QUuid after_track_id); + void PlaylistItemsRemoved(const int playlist_id, const QList &tracks_id); // Signals that one of manager's playlists has changed (new items, new ordering etc.) - the argument shows which. void PlaylistChanged(Playlist *playlist); diff --git a/src/playlist/playlistundocommandinsertitems.cpp b/src/playlist/playlistundocommandinsertitems.cpp index 71b184e28c..7343274c34 100644 --- a/src/playlist/playlistundocommandinsertitems.cpp +++ b/src/playlist/playlistundocommandinsertitems.cpp @@ -22,19 +22,20 @@ #include "playlistundocommandinsertitems.h" #include "playlist.h" -PlaylistUndoCommandInsertItems::PlaylistUndoCommandInsertItems(Playlist *playlist, const PlaylistItemPtrList &items, const int pos, const bool enqueue, const bool enqueue_next) +PlaylistUndoCommandInsertItems::PlaylistUndoCommandInsertItems(Playlist *playlist, const PlaylistItemPtrList &items, const int pos, const bool enqueue, const bool enqueue_next, const bool emit_signal) : PlaylistUndoCommandBase(playlist), items_(items), pos_(pos), enqueue_(enqueue), - enqueue_next_(enqueue_next) { + enqueue_next_(enqueue_next), + emit_signal_(emit_signal) { setText(QObject::tr("add %n songs", "", static_cast(items_.count()))); } void PlaylistUndoCommandInsertItems::redo() { - playlist_->InsertItemsWithoutUndo(items_, pos_, enqueue_, enqueue_next_); + playlist_->InsertItemsWithoutUndo(items_, pos_, enqueue_, enqueue_next_, emit_signal_); } void PlaylistUndoCommandInsertItems::undo() { diff --git a/src/playlist/playlistundocommandinsertitems.h b/src/playlist/playlistundocommandinsertitems.h index 8550d4c4b3..5b18a4c0ac 100644 --- a/src/playlist/playlistundocommandinsertitems.h +++ b/src/playlist/playlistundocommandinsertitems.h @@ -25,7 +25,7 @@ class PlaylistUndoCommandInsertItems : public PlaylistUndoCommandBase { public: - explicit PlaylistUndoCommandInsertItems(Playlist *playlist, const PlaylistItemPtrList &items, const int pos, const bool enqueue = false, const bool enqueue_next = false); + explicit PlaylistUndoCommandInsertItems(Playlist *playlist, const PlaylistItemPtrList &items, const int pos, const bool enqueue = false, const bool enqueue_next = false, const bool emit_signal = false); void undo() override; void redo() override; @@ -39,6 +39,7 @@ class PlaylistUndoCommandInsertItems : public PlaylistUndoCommandBase { int pos_; bool enqueue_; bool enqueue_next_; + bool emit_signal_; }; #endif // PLAYLISTUNDOCOMMANDINSERTITEMS_H diff --git a/src/playlist/playlistundocommandremoveitems.cpp b/src/playlist/playlistundocommandremoveitems.cpp index f725c6cedb..fb285d9c5d 100644 --- a/src/playlist/playlistundocommandremoveitems.cpp +++ b/src/playlist/playlistundocommandremoveitems.cpp @@ -22,7 +22,7 @@ #include "playlist.h" #include "playlistundocommandremoveitems.h" -PlaylistUndoCommandRemoveItems::PlaylistUndoCommandRemoveItems(Playlist *playlist, const int pos, const int count) : PlaylistUndoCommandBase(playlist) { +PlaylistUndoCommandRemoveItems::PlaylistUndoCommandRemoveItems(Playlist *playlist, const int pos, const int count, const bool emit_signal) : PlaylistUndoCommandBase(playlist), emit_signal_(emit_signal) { setText(QObject::tr("remove %n songs", "", count)); ranges_ << Range(pos, count); @@ -31,7 +31,7 @@ PlaylistUndoCommandRemoveItems::PlaylistUndoCommandRemoveItems(Playlist *playlis void PlaylistUndoCommandRemoveItems::redo() { for (int i = 0; i < ranges_.count(); ++i) { - ranges_[i].items_ = playlist_->RemoveItemsWithoutUndo(ranges_[i].pos_, ranges_[i].count_); + ranges_[i].items_ = playlist_->RemoveItemsWithoutUndo(ranges_[i].pos_, ranges_[i].count_, emit_signal_); } } diff --git a/src/playlist/playlistundocommandremoveitems.h b/src/playlist/playlistundocommandremoveitems.h index 0eadf5c686..4f0797dffa 100644 --- a/src/playlist/playlistundocommandremoveitems.h +++ b/src/playlist/playlistundocommandremoveitems.h @@ -27,7 +27,7 @@ class PlaylistUndoCommandRemoveItems : public PlaylistUndoCommandBase { public: - explicit PlaylistUndoCommandRemoveItems(Playlist *playlist, const int pos, const int count); + explicit PlaylistUndoCommandRemoveItems(Playlist *playlist, const int pos, const int count, const bool emit_signal = false); int id() const override { return static_cast(PlaylistUndoCommandBase::Type::RemoveItems); } @@ -44,6 +44,7 @@ class PlaylistUndoCommandRemoveItems : public PlaylistUndoCommandBase { }; QList ranges_; + bool emit_signal_; }; #endif // PLAYLISTUNDOCOMMANDREMOVEITEMS_H diff --git a/src/playlist/songloaderinserter.cpp b/src/playlist/songloaderinserter.cpp index a1e467faa3..de3ffdf3d1 100644 --- a/src/playlist/songloaderinserter.cpp +++ b/src/playlist/songloaderinserter.cpp @@ -54,13 +54,14 @@ SongLoaderInserter::SongLoaderInserter(const SharedPtr task_manager SongLoaderInserter::~SongLoaderInserter() { qDeleteAll(pending_); } -void SongLoaderInserter::Load(Playlist *destination, const int row, const bool play_now, const bool enqueue, const bool enqueue_next, const QList &urls) { +void SongLoaderInserter::Load(Playlist *destination, const int row, const bool play_now, const bool enqueue, const bool enqueue_next, const bool emit_signal, const QList &urls) { destination_ = destination; row_ = row; play_now_ = play_now; enqueue_ = enqueue; enqueue_next_ = enqueue_next; + emit_signal_ = emit_signal; QObject::connect(destination, &Playlist::destroyed, this, &SongLoaderInserter::DestinationDestroyed); QObject::connect(this, &SongLoaderInserter::PreloadFinished, this, &SongLoaderInserter::InsertSongs); @@ -176,7 +177,7 @@ void SongLoaderInserter::InsertSongs() { // Insert songs (that haven't been completely loaded) to allow user to see and play them while not loaded completely if (destination_) { - destination_->InsertSongsOrCollectionItems(songs_, playlist_name_, row_, play_now_, enqueue_, enqueue_next_); + destination_->InsertSongsOrCollectionItems(songs_, playlist_name_, row_, play_now_, enqueue_, enqueue_next_, emit_signal_); } } diff --git a/src/playlist/songloaderinserter.h b/src/playlist/songloaderinserter.h index 5aaf2b8568..8e7d4c6276 100644 --- a/src/playlist/songloaderinserter.h +++ b/src/playlist/songloaderinserter.h @@ -52,7 +52,7 @@ class SongLoaderInserter : public QObject { ~SongLoaderInserter() override; - void Load(Playlist *destination, const int row, const bool play_now, const bool enqueue, const bool enqueue_next, const QList &urls); + void Load(Playlist *destination, const int row, const bool play_now, const bool enqueue, const bool enqueue_next, const bool emit_signal, const QList &urls); void LoadAudioCD(Playlist *destination, const int row, const bool play_now, const bool enqueue, const bool enqueue_next); Q_SIGNALS: @@ -81,6 +81,7 @@ class SongLoaderInserter : public QObject { bool play_now_; bool enqueue_; bool enqueue_next_; + bool emit_signal_; SongList songs_; QString playlist_name_;