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