1 /*
2  * Strawberry Music Player
3  * This file was part of Clementine.
4  * Copyright 2010, David Sansome <me@davidsansome.com>
5  * Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
6  *
7  * Strawberry is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * Strawberry is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with Strawberry.  If not, see <http://www.gnu.org/licenses/>.
19  *
20  */
21 
22 #ifndef PLAYLIST_H
23 #define PLAYLIST_H
24 
25 #include "config.h"
26 
27 #include <QtGlobal>
28 #include <QObject>
29 #include <QAbstractItemModel>
30 #include <QAbstractListModel>
31 #include <QPersistentModelIndex>
32 #include <QFuture>
33 #include <QList>
34 #include <QMap>
35 #include <QMultiMap>
36 #include <QMetaType>
37 #include <QVariant>
38 #include <QString>
39 #include <QStringList>
40 #include <QUrl>
41 #include <QColor>
42 #include <QRgb>
43 
44 #include "core/song.h"
45 #include "core/tagreaderclient.h"
46 #include "covermanager/albumcoverloaderresult.h"
47 #include "playlistitem.h"
48 #include "playlistsequence.h"
49 #include "smartplaylists/playlistgenerator_fwd.h"
50 
51 class QMimeData;
52 class QSortFilterProxyModel;
53 class QUndoStack;
54 class QTimer;
55 
56 class CollectionBackend;
57 class PlaylistBackend;
58 class PlaylistFilter;
59 class Queue;
60 class TaskManager;
61 class InternetService;
62 class RadioService;
63 
64 namespace PlaylistUndoCommands {
65 class InsertItems;
66 class MoveItems;
67 class ReOrderItems;
68 class RemoveItems;
69 class ShuffleItems;
70 class SortItems;
71 }  // namespace PlaylistUndoCommands
72 
73 typedef QMap<int, Qt::Alignment> ColumnAlignmentMap;
74 Q_DECLARE_METATYPE(Qt::Alignment)
75 Q_DECLARE_METATYPE(ColumnAlignmentMap)
76 
77 // Objects that may prevent a song being added to the playlist.
78 // When there is something about to be inserted into it,
79 // Playlist notifies all of it's listeners about the fact and every one of them picks 'invalid' songs.
80 class SongInsertVetoListener : public QObject {
81   Q_OBJECT
82 
83  public:
84   // Listener returns a list of 'invalid' songs.
85   // 'old_songs' are songs that are currently in the playlist and 'new_songs' are the songs about to be added if nobody exercises a veto.
86   virtual SongList AboutToInsertSongs(const SongList &old_songs, const SongList &new_songs) = 0;
87 };
88 
89 class Playlist : public QAbstractListModel {
90   Q_OBJECT
91 
92   friend class PlaylistUndoCommands::InsertItems;
93   friend class PlaylistUndoCommands::RemoveItems;
94   friend class PlaylistUndoCommands::MoveItems;
95   friend class PlaylistUndoCommands::ReOrderItems;
96 
97  public:
98   explicit Playlist(PlaylistBackend *backend, TaskManager *task_manager, CollectionBackend *collection, const int id, const QString &special_type = QString(), const bool favorite = false, QObject *parent = nullptr);
99   ~Playlist() override;
100 
101   void SkipTracks(const QModelIndexList &source_indexes);
102 
103   // Always add new columns to the end of this enum - the values are persisted
104   enum Column {
105     Column_Title = 0,
106     Column_Artist,
107     Column_Album,
108     Column_AlbumArtist,
109     Column_Performer,
110     Column_Composer,
111     Column_Year,
112     Column_OriginalYear,
113     Column_Track,
114     Column_Disc,
115     Column_Length,
116     Column_Genre,
117     Column_Samplerate,
118     Column_Bitdepth,
119     Column_Bitrate,
120     Column_Filename,
121     Column_BaseFilename,
122     Column_Filesize,
123     Column_Filetype,
124     Column_DateCreated,
125     Column_DateModified,
126     Column_PlayCount,
127     Column_SkipCount,
128     Column_LastPlayed,
129     Column_Comment,
130     Column_Grouping,
131     Column_Source,
132     Column_Mood,
133     Column_Rating,
134     ColumnCount
135   };
136 
137   enum Role {
138     Role_IsCurrent = Qt::UserRole + 1,
139     Role_IsPaused,
140     Role_StopAfter,
141     Role_QueuePosition,
142     Role_CanSetRating,
143   };
144 
145   enum Path {
146     Path_Automatic = 0,  // Automatically select path type
147     Path_Absolute,       // Always use absolute paths
148     Path_Relative,       // Always use relative paths
149     Path_Ask_User,       // Only used in preferences: to ask user which of the previous values he wants to use.
150   };
151 
152   enum AutoScroll {
153     AutoScroll_Never,
154     AutoScroll_Maybe,
155     AutoScroll_Always
156   };
157 
158   static const char *kCddaMimeType;
159   static const char *kRowsMimetype;
160   static const char *kPlayNowMimetype;
161 
162   static const int kInvalidSongPriority;
163   static const QRgb kInvalidSongColor;
164 
165   static const int kDynamicHistoryPriority;
166   static const QRgb kDynamicHistoryColor;
167 
168   static const char *kSettingsGroup;
169 
170   static const char *kPathType;
171   static const char *kWriteMetadata;
172 
173   static const int kUndoStackSize;
174   static const int kUndoItemLimit;
175 
176   static const qint64 kMinScrobblePointNsecs;
177   static const qint64 kMaxScrobblePointNsecs;
178 
179   static bool CompareItems(const int column, const Qt::SortOrder order, PlaylistItemPtr a, PlaylistItemPtr b);
180 
181   static QString column_name(Column column);
182   static QString abbreviated_column_name(Column column);
183 
184   static bool column_is_editable(Playlist::Column column);
185   static bool set_column_value(Song &song, Column column, const QVariant &value);
186 
187   // Persistence
188   void Restore();
189   void ScheduleSaveAsync();
190 
191   // Accessors
192   QSortFilterProxyModel *proxy() const;
193   Queue *queue() const { return queue_; }
194 
195   int id() const { return id_; }
196   const QString &ui_path() const { return ui_path_; }
197   void set_ui_path(const QString &path) { ui_path_ = path; }
198   bool is_favorite() const { return favorite_; }
199   void set_favorite(const bool favorite) { favorite_ = favorite; }
200 
201   int current_row() const;
202   int last_played_row() const;
203   void reset_last_played() { last_played_item_index_ = QPersistentModelIndex(); }
204   int next_row(const bool ignore_repeat_track = false) const;
205   int previous_row(const bool ignore_repeat_track = false) const;
206 
207   const QModelIndex current_index() const;
208 
209   bool stop_after_current() const;
210   bool is_dynamic() const { return static_cast<bool>(dynamic_playlist_); }
211   int dynamic_history_length() const;
212 
213   QString special_type() const { return special_type_; }
214   void set_special_type(const QString &v) { special_type_ = v; }
215 
216   const PlaylistItemPtr &item_at(const int index) const { return items_[index]; }
217   bool has_item_at(const int index) const { return index >= 0 && index < rowCount(); }
218 
219   PlaylistItemPtr current_item() const;
220 
221   PlaylistItem::Options current_item_options() const;
222   Song current_item_metadata() const;
223 
224   PlaylistItemList collection_items_by_id(const int id) const;
225 
226   SongList GetAllSongs() const;
227   PlaylistItemList GetAllItems() const;
228   quint64 GetTotalLength() const;  // in seconds
229 
230   void set_sequence(PlaylistSequence *v);
231   PlaylistSequence *sequence() const { return playlist_sequence_; }
232 
233   QUndoStack *undo_stack() const { return undo_stack_; }
234 
235   bool scrobbled() const { return scrobbled_; }
236   void set_scrobbled(const bool state) { scrobbled_ = state; }
237   void set_editing(const int row) { editing_ = row; }
238   qint64 scrobble_point_nanosec() const { return scrobble_point_; }
239   void UpdateScrobblePoint(const qint64 seek_point_nanosec = 0);
240 
241   // Changing the playlist
242   void InsertItems(const PlaylistItemList &itemsIn, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
243   void InsertCollectionItems(const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
244   void InsertSongs(const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
245   void InsertSongsOrCollectionItems(const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
246   void InsertSmartPlaylist(PlaylistGeneratorPtr gen, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
247   void InsertInternetItems(InternetService *service, const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
248   void InsertRadioItems(const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
249 
250   void ReshuffleIndices();
251 
252   // If this playlist contains the current item, this method will apply the "valid" flag on it.
253   // If the "valid" flag is false, the song will be greyed out. Otherwise the grey color will be undone.
254   // If the song is a local file and it's valid but non existent or invalid but exists, the
255   // song will be reloaded to even out the situation because obviously something has changed.
256   // This returns true if this playlist had current item when the method was invoked.
257   bool ApplyValidityOnCurrentSong(const QUrl &url, bool valid);
258   // Grays out and reloads all deleted songs in all playlists. Also, "ungreys" those songs which were once deleted but now got restored somehow.
259   void InvalidateDeletedSongs();
260   // Removes from the playlist all local files that don't exist anymore.
261   void RemoveDeletedSongs();
262 
263   void StopAfter(const int row);
264   void ReloadItems(const QList<int> &rows);
265   void ReloadItemsBlocking(const QList<int> &rows);
266   void InformOfCurrentSongChange(const AutoScroll autoscroll, const bool minor);
267 
268   // Registers an object which will get notifications when new songs are about to be inserted into this playlist.
269   void AddSongInsertVetoListener(SongInsertVetoListener *listener);
270   // Unregisters a SongInsertVetoListener object.
271   void RemoveSongInsertVetoListener(SongInsertVetoListener *listener);
272 
273   // Just emits the dataChanged() signal so the mood column is repainted.
274 #ifdef HAVE_MOODBAR
275   void MoodbarUpdated(const QModelIndex &idx);
276 #endif
277 
278   // QAbstractListModel
279   int rowCount(const QModelIndex& = QModelIndex()) const override { return items_.count(); }
280   int columnCount(const QModelIndex& = QModelIndex()) const override { return ColumnCount; }
281   QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
282   bool setData(const QModelIndex &idx, const QVariant &value, int role) override;
283   QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
284   Qt::ItemFlags flags(const QModelIndex &idx) const override;
285   QStringList mimeTypes() const override;
286   Qt::DropActions supportedDropActions() const override;
287   QMimeData *mimeData(const QModelIndexList &indexes) const override;
288   bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
289   void sort(int column, Qt::SortOrder order) override;
290   bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
291 
292   static bool ComparePathDepths(Qt::SortOrder, PlaylistItemPtr, PlaylistItemPtr);
293 
294   void ItemChanged(PlaylistItemPtr item);
295   void ItemChanged(const int row);
296 
297   // Changes rating of a song to the given value asynchronously
298   void RateSong(const QModelIndex &idx, const double rating);
299   void RateSongs(const QModelIndexList &index_list, const double rating);
300 
301   void set_auto_sort(const bool auto_sort) { auto_sort_ = auto_sort; }
302 
303   void ItemReload(const QPersistentModelIndex &idx, const Song &old_metadata, const bool metadata_edit);
304 
305  public slots:
306   void set_current_row(const int i, const Playlist::AutoScroll autoscroll = Playlist::AutoScroll_Maybe, const bool is_stopping = false, const bool force_inform = false);
307   void Paused();
308   void Playing();
309   void Stopped();
310   void IgnoreSorting(const bool value) { ignore_sorting_ = value; }
311 
312   void ClearStreamMetadata();
313   void SetStreamMetadata(const QUrl &url, const Song &song, const bool minor);
314   void UpdateItems(SongList songs);
315 
316   void Clear();
317   void RemoveDuplicateSongs();
318   void RemoveUnavailableSongs();
319   void Shuffle();
320 
321   void ShuffleModeChanged(const PlaylistSequence::ShuffleMode mode);
322 
323   void SetColumnAlignment(const ColumnAlignmentMap &alignment);
324 
325   void InsertUrls(const QList<QUrl> &urls, int pos = -1, bool play_now = false, bool enqueue = false, bool enqueue_next = false);
326   // Removes items with given indices from the playlist. This operation is not undoable.
327   void RemoveItemsWithoutUndo(const QList<int> &indicesIn);
328 
329   void ExpandDynamicPlaylist();
330   void RepopulateDynamicPlaylist();
331   void TurnOffDynamicPlaylist();
332 
333   void AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result);
334 
335  signals:
336   void RestoreFinished();
337   void PlaylistLoaded();
338   void CurrentSongChanged(Song metadata);
339   void SongMetadataChanged(Song metadata);
340   void EditingFinished(const int playlist_id, QModelIndex idx);
341   void PlayRequested(QModelIndex idx, Playlist::AutoScroll autoscroll);
342   void MaybeAutoscroll(Playlist::AutoScroll autoscroll);
343 
344   // Signals that the underlying list of items was changed, meaning that something was added to it, removed from it or the ordering changed.
345   void PlaylistChanged();
346   void DynamicModeChanged(bool dynamic);
347 
348   void Error(QString message);
349 
350   // Signals that the queue has changed, meaning that the remaining queued items should update their position.
351   void QueueChanged();
352 
353  private:
354   void SetCurrentIsPaused(const bool paused);
355   int NextVirtualIndex(int i, const bool ignore_repeat_track) const;
356   int PreviousVirtualIndex(int i, const bool ignore_repeat_track) const;
357   bool FilterContainsVirtualIndex(const int i) const;
358 
359   template <typename T>
360   void InsertSongItems(const SongList &songs, const int pos, const bool play_now, const bool enqueue, const bool enqueue_next = false);
361 
362   // Modify the playlist without changing the undo stack.  These are used by our friends in PlaylistUndoCommands
363   void InsertItemsWithoutUndo(const PlaylistItemList &items, int pos, bool enqueue = false, bool enqueue_next = false);
364   PlaylistItemList RemoveItemsWithoutUndo(const int row, const int count);
365   void MoveItemsWithoutUndo(const QList<int> &source_rows, int pos);
366   void MoveItemWithoutUndo(const int source, const int dest);
367   void MoveItemsWithoutUndo(int start, const QList<int> &dest_rows);
368   void ReOrderWithoutUndo(const PlaylistItemList &new_items);
369 
370   void RemoveItemsNotInQueue();
371 
372   // Removes rows with given indices from this playlist.
373   bool removeRows(QList<int> &rows);
374 
375   void TurnOnDynamicPlaylist(PlaylistGeneratorPtr gen);
376   void InsertDynamicItems(const int count);
377 
378  private slots:
379   void TracksAboutToBeDequeued(const QModelIndex&, const int begin, const int end);
380   void TracksDequeued();
381   void TracksEnqueued(const QModelIndex&, const int begin, const int end);
382   void QueueLayoutChanged();
383   void SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &idx, const Song &old_metadata);
384   void ItemReloadComplete(const QPersistentModelIndex &idx, const Song &old_metadata, const bool metadata_edit);
385   void ItemsLoaded();
386   void SongInsertVetoListenerDestroyed();
387   void ScheduleSave();
388   void Save();
389 
390  private:
391   bool is_loading_;
392   PlaylistFilter *proxy_;
393   Queue *queue_;
394   QTimer *timer_save_;
395 
396   QList<QModelIndex> temp_dequeue_change_indexes_;
397 
398   PlaylistBackend *backend_;
399   TaskManager *task_manager_;
400   CollectionBackend *collection_;
401   int id_;
402   QString ui_path_;
403   bool favorite_;
404 
405   PlaylistItemList items_;
406 
407   // Contains the indices into items_ in the order that they will be played.
408   QList<int> virtual_items_;
409 
410   // A map of collection ID to playlist item - for fast lookups when collection items change.
411   QMultiMap<int, PlaylistItemPtr> collection_items_by_id_;
412 
413   QPersistentModelIndex current_item_index_;
414   QPersistentModelIndex last_played_item_index_;
415   QPersistentModelIndex stop_after_;
416   bool current_is_paused_;
417   int current_virtual_index_;
418 
419   bool is_shuffled_;
420 
421   PlaylistSequence *playlist_sequence_;
422 
423   // Hack to stop QTreeView::setModel sorting the playlist
424   bool ignore_sorting_;
425 
426   QUndoStack *undo_stack_;
427 
428   ColumnAlignmentMap column_alignments_;
429 
430   QList<SongInsertVetoListener*> veto_listeners_;
431 
432   QString special_type_;
433 
434   // Cancel async restore if songs are already replaced
435   bool cancel_restore_;
436 
437   bool scrobbled_;
438   qint64 scrobble_point_;
439 
440   int editing_;
441 
442   PlaylistGeneratorPtr dynamic_playlist_;
443 
444   bool auto_sort_;
445   int sort_column_;
446   Qt::SortOrder sort_order_;
447 
448 };
449 
450 #endif  // PLAYLIST_H
451