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