mirror of
https://github.com/clementine-player/Clementine
synced 2024-12-18 20:34:39 +01:00
Formatting
This commit is contained in:
parent
c890af6306
commit
ce8b7303c0
@ -1639,495 +1639,492 @@ void Playlist::StopAfter(int row) {
|
||||
else
|
||||
stop_after_ = index(row, 0);
|
||||
|
||||
if (old_stop_after.isValid())
|
||||
emit dataChanged(
|
||||
old_stop_after,
|
||||
old_stop_after.sibling(old_stop_after.row(), ColumnCount - 1));
|
||||
if (stop_after_.isValid())
|
||||
emit dataChanged(stop_after_,
|
||||
stop_after_.sibling(stop_after_.row(), ColumnCount - 1));
|
||||
if (old_stop_after.isValid())
|
||||
emit dataChanged(
|
||||
old_stop_after,
|
||||
old_stop_after.sibling(old_stop_after.row(), ColumnCount - 1));
|
||||
if (stop_after_.isValid())
|
||||
emit dataChanged(stop_after_,
|
||||
stop_after_.sibling(stop_after_.row(), ColumnCount - 1));
|
||||
}
|
||||
|
||||
void Playlist::SetStreamMetadata(const QUrl& url, const Song& song) {
|
||||
qLog(Debug) << "Setting metadata for" << url << "to" << song.artist()
|
||||
<< song.title();
|
||||
if (!current_item()) return;
|
||||
|
||||
if (current_item()->Url() != url) return;
|
||||
|
||||
// Don't update the metadata if it's only a minor change from before
|
||||
if (current_item()->Metadata().artist() == song.artist() &&
|
||||
current_item()->Metadata().title() == song.title())
|
||||
return;
|
||||
|
||||
current_item()->SetTemporaryMetadata(song);
|
||||
UpdateScrobblePoint();
|
||||
|
||||
InformOfCurrentSongChange();
|
||||
}
|
||||
|
||||
void Playlist::ClearStreamMetadata() {
|
||||
if (!current_item()) return;
|
||||
|
||||
current_item()->ClearTemporaryMetadata();
|
||||
UpdateScrobblePoint();
|
||||
|
||||
emit dataChanged(index(current_item_index_.row(), 0),
|
||||
index(current_item_index_.row(), ColumnCount - 1));
|
||||
}
|
||||
|
||||
bool Playlist::stop_after_current() const {
|
||||
return stop_after_.isValid() && current_item_index_.isValid() &&
|
||||
stop_after_.row() == current_item_index_.row();
|
||||
}
|
||||
|
||||
PlaylistItemPtr Playlist::current_item() const {
|
||||
// QList[] runs in constant time, so no need to cache current_item
|
||||
if (current_item_index_.isValid() &&
|
||||
current_item_index_.row() <= items_.length())
|
||||
return items_[current_item_index_.row()];
|
||||
return PlaylistItemPtr();
|
||||
}
|
||||
|
||||
PlaylistItem::Options Playlist::current_item_options() const {
|
||||
if (!current_item()) return PlaylistItem::Default;
|
||||
|
||||
return current_item()->options();
|
||||
}
|
||||
|
||||
Song Playlist::current_item_metadata() const {
|
||||
if (!current_item()) return Song();
|
||||
|
||||
return current_item()->Metadata();
|
||||
}
|
||||
|
||||
void Playlist::UpdateScrobblePoint() {
|
||||
const qint64 length = current_item_metadata().length_nanosec();
|
||||
|
||||
if (length == 0) {
|
||||
scrobble_point_ = 240ll * kNsecPerSec; // 4 minutes
|
||||
} else {
|
||||
scrobble_point_ =
|
||||
qBound(31ll * kNsecPerSec, length / 2, 240ll * kNsecPerSec);
|
||||
}
|
||||
|
||||
void Playlist::SetStreamMetadata(const QUrl& url, const Song& song) {
|
||||
qLog(Debug) << "Setting metadata for" << url << "to" << song.artist()
|
||||
<< song.title();
|
||||
if (!current_item()) return;
|
||||
set_lastfm_status(LastFM_New);
|
||||
have_incremented_playcount_ = false;
|
||||
}
|
||||
|
||||
if (current_item()->Url() != url) return;
|
||||
void Playlist::Clear() {
|
||||
const int count = items_.count();
|
||||
|
||||
// Don't update the metadata if it's only a minor change from before
|
||||
if (current_item()->Metadata().artist() == song.artist() &&
|
||||
current_item()->Metadata().title() == song.title())
|
||||
return;
|
||||
|
||||
current_item()->SetTemporaryMetadata(song);
|
||||
UpdateScrobblePoint();
|
||||
|
||||
InformOfCurrentSongChange();
|
||||
if (count > kUndoItemLimit) {
|
||||
// Too big to keep in the undo stack. Also clear the stack because it
|
||||
// might have been invalidated.
|
||||
RemoveItemsWithoutUndo(0, count);
|
||||
undo_stack_->clear();
|
||||
} else {
|
||||
undo_stack_->push(new PlaylistUndoCommands::RemoveItems(this, 0, count));
|
||||
}
|
||||
|
||||
void Playlist::ClearStreamMetadata() {
|
||||
if (!current_item()) return;
|
||||
TurnOffDynamicPlaylist();
|
||||
|
||||
current_item()->ClearTemporaryMetadata();
|
||||
UpdateScrobblePoint();
|
||||
Save();
|
||||
}
|
||||
|
||||
emit dataChanged(index(current_item_index_.row(), 0),
|
||||
index(current_item_index_.row(), ColumnCount - 1));
|
||||
void Playlist::TurnOffDynamicPlaylist() {
|
||||
dynamic_playlist_.reset();
|
||||
|
||||
if (playlist_sequence_) {
|
||||
playlist_sequence_->SetUsingDynamicPlaylist(false);
|
||||
ShuffleModeChanged(playlist_sequence_->shuffle_mode());
|
||||
}
|
||||
emit DynamicModeChanged(false);
|
||||
Save();
|
||||
}
|
||||
|
||||
void Playlist::RepopulateDynamicPlaylist() {
|
||||
if (!dynamic_playlist_) return;
|
||||
|
||||
RemoveItemsNotInQueue();
|
||||
InsertSmartPlaylist(dynamic_playlist_);
|
||||
}
|
||||
|
||||
void Playlist::ExpandDynamicPlaylist() {
|
||||
if (!dynamic_playlist_) return;
|
||||
|
||||
InsertDynamicItems(5);
|
||||
}
|
||||
|
||||
void Playlist::RemoveItemsNotInQueue() {
|
||||
if (queue_->is_empty()) {
|
||||
RemoveItemsWithoutUndo(0, items_.count());
|
||||
return;
|
||||
}
|
||||
|
||||
bool Playlist::stop_after_current() const {
|
||||
return stop_after_.isValid() && current_item_index_.isValid() &&
|
||||
stop_after_.row() == current_item_index_.row();
|
||||
}
|
||||
|
||||
PlaylistItemPtr Playlist::current_item() const {
|
||||
// QList[] runs in constant time, so no need to cache current_item
|
||||
if (current_item_index_.isValid() &&
|
||||
current_item_index_.row() <= items_.length())
|
||||
return items_[current_item_index_.row()];
|
||||
return PlaylistItemPtr();
|
||||
}
|
||||
|
||||
PlaylistItem::Options Playlist::current_item_options() const {
|
||||
if (!current_item()) return PlaylistItem::Default;
|
||||
|
||||
return current_item()->options();
|
||||
}
|
||||
|
||||
Song Playlist::current_item_metadata() const {
|
||||
if (!current_item()) return Song();
|
||||
|
||||
return current_item()->Metadata();
|
||||
}
|
||||
|
||||
void Playlist::UpdateScrobblePoint() {
|
||||
const qint64 length = current_item_metadata().length_nanosec();
|
||||
|
||||
if (length == 0) {
|
||||
scrobble_point_ = 240ll * kNsecPerSec; // 4 minutes
|
||||
} else {
|
||||
scrobble_point_ =
|
||||
qBound(31ll * kNsecPerSec, length / 2, 240ll * kNsecPerSec);
|
||||
}
|
||||
|
||||
set_lastfm_status(LastFM_New);
|
||||
have_incremented_playcount_ = false;
|
||||
}
|
||||
|
||||
void Playlist::Clear() {
|
||||
const int count = items_.count();
|
||||
|
||||
if (count > kUndoItemLimit) {
|
||||
// Too big to keep in the undo stack. Also clear the stack because it
|
||||
// might have been invalidated.
|
||||
RemoveItemsWithoutUndo(0, count);
|
||||
undo_stack_->clear();
|
||||
} else {
|
||||
undo_stack_->push(new PlaylistUndoCommands::RemoveItems(this, 0, count));
|
||||
}
|
||||
|
||||
TurnOffDynamicPlaylist();
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
void Playlist::TurnOffDynamicPlaylist() {
|
||||
dynamic_playlist_.reset();
|
||||
|
||||
if (playlist_sequence_) {
|
||||
playlist_sequence_->SetUsingDynamicPlaylist(false);
|
||||
ShuffleModeChanged(playlist_sequence_->shuffle_mode());
|
||||
}
|
||||
emit DynamicModeChanged(false);
|
||||
Save();
|
||||
}
|
||||
|
||||
void Playlist::RepopulateDynamicPlaylist() {
|
||||
if (!dynamic_playlist_) return;
|
||||
|
||||
RemoveItemsNotInQueue();
|
||||
InsertSmartPlaylist(dynamic_playlist_);
|
||||
}
|
||||
|
||||
void Playlist::ExpandDynamicPlaylist() {
|
||||
if (!dynamic_playlist_) return;
|
||||
|
||||
InsertDynamicItems(5);
|
||||
}
|
||||
|
||||
void Playlist::RemoveItemsNotInQueue() {
|
||||
if (queue_->is_empty()) {
|
||||
RemoveItemsWithoutUndo(0, items_.count());
|
||||
return;
|
||||
}
|
||||
|
||||
int start = 0;
|
||||
int start = 0;
|
||||
forever {
|
||||
// Find a place to start - first row that isn't in the queue
|
||||
forever {
|
||||
// Find a place to start - first row that isn't in the queue
|
||||
forever {
|
||||
if (start >= rowCount()) return;
|
||||
if (!queue_->ContainsSourceRow(start)) break;
|
||||
start++;
|
||||
}
|
||||
|
||||
// Figure out how many rows to remove - keep going until we find a row
|
||||
// that is in the queue
|
||||
int count = 1;
|
||||
forever {
|
||||
if (start + count >= rowCount()) break;
|
||||
if (queue_->ContainsSourceRow(start + count)) break;
|
||||
count++;
|
||||
}
|
||||
|
||||
RemoveItemsWithoutUndo(start, count);
|
||||
if (start >= rowCount()) return;
|
||||
if (!queue_->ContainsSourceRow(start)) break;
|
||||
start++;
|
||||
}
|
||||
|
||||
// Figure out how many rows to remove - keep going until we find a row
|
||||
// that is in the queue
|
||||
int count = 1;
|
||||
forever {
|
||||
if (start + count >= rowCount()) break;
|
||||
if (queue_->ContainsSourceRow(start + count)) break;
|
||||
count++;
|
||||
}
|
||||
|
||||
RemoveItemsWithoutUndo(start, count);
|
||||
start++;
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::ReloadItems(const QList<int>& rows) {
|
||||
for (int row : rows) {
|
||||
PlaylistItemPtr item = item_at(row);
|
||||
|
||||
item->Reload();
|
||||
|
||||
if (row == current_row()) {
|
||||
InformOfCurrentSongChange();
|
||||
} else {
|
||||
emit dataChanged(index(row, 0), index(row, ColumnCount - 1));
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::ReloadItems(const QList<int>& rows) {
|
||||
for (int row : rows) {
|
||||
PlaylistItemPtr item = item_at(row);
|
||||
Save();
|
||||
}
|
||||
|
||||
item->Reload();
|
||||
void Playlist::RateSong(const QModelIndex& index, double rating) {
|
||||
int row = index.row();
|
||||
|
||||
if (row == current_row()) {
|
||||
InformOfCurrentSongChange();
|
||||
if (has_item_at(row)) {
|
||||
PlaylistItemPtr item = item_at(row);
|
||||
if (item && item->IsLocalLibraryItem() && item->Metadata().id() != -1) {
|
||||
library_->UpdateSongRatingAsync(item->Metadata().id(), rating);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::AddSongInsertVetoListener(SongInsertVetoListener* listener) {
|
||||
veto_listeners_.append(listener);
|
||||
connect(listener, SIGNAL(destroyed()), this,
|
||||
SLOT(SongInsertVetoListenerDestroyed()));
|
||||
}
|
||||
|
||||
void Playlist::RemoveSongInsertVetoListener(SongInsertVetoListener* listener) {
|
||||
disconnect(listener, SIGNAL(destroyed()), this,
|
||||
SLOT(SongInsertVetoListenerDestroyed()));
|
||||
veto_listeners_.removeAll(listener);
|
||||
}
|
||||
|
||||
void Playlist::SongInsertVetoListenerDestroyed() {
|
||||
veto_listeners_.removeAll(qobject_cast<SongInsertVetoListener*>(sender()));
|
||||
}
|
||||
|
||||
void Playlist::Shuffle() {
|
||||
PlaylistItemList new_items(items_);
|
||||
|
||||
int begin = 0;
|
||||
if (dynamic_playlist_ && current_item_index_.isValid())
|
||||
begin += current_item_index_.row() + 1;
|
||||
|
||||
const int count = items_.count();
|
||||
for (int i = begin; i < count; ++i) {
|
||||
int new_pos = i + (rand() % (count - i));
|
||||
|
||||
std::swap(new_items[i], new_items[new_pos]);
|
||||
}
|
||||
|
||||
undo_stack_->push(new PlaylistUndoCommands::ShuffleItems(this, new_items));
|
||||
}
|
||||
|
||||
namespace {
|
||||
bool AlbumShuffleComparator(const QMap<QString, int>& album_key_positions,
|
||||
const QMap<int, QString>& album_keys, int left,
|
||||
int right) {
|
||||
const int left_pos = album_key_positions[album_keys[left]];
|
||||
const int right_pos = album_key_positions[album_keys[right]];
|
||||
|
||||
if (left_pos == right_pos) return left < right;
|
||||
return left_pos < right_pos;
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::ReshuffleIndices() {
|
||||
if (!playlist_sequence_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (playlist_sequence_->shuffle_mode() == PlaylistSequence::Shuffle_Off) {
|
||||
// No shuffling - sort the virtual item list normally.
|
||||
std::sort(virtual_items_.begin(), virtual_items_.end());
|
||||
if (current_row() != -1)
|
||||
current_virtual_index_ = virtual_items_.indexOf(current_row());
|
||||
return;
|
||||
}
|
||||
|
||||
// If the user is already playing a song, advance the begin iterator to
|
||||
// only shuffle items that haven't been played yet.
|
||||
QList<int>::iterator begin = virtual_items_.begin();
|
||||
QList<int>::iterator end = virtual_items_.end();
|
||||
if (current_virtual_index_ != -1)
|
||||
std::advance(begin, current_virtual_index_ + 1);
|
||||
|
||||
switch (playlist_sequence_->shuffle_mode()) {
|
||||
case PlaylistSequence::Shuffle_Off:
|
||||
// Handled above.
|
||||
break;
|
||||
|
||||
case PlaylistSequence::Shuffle_All:
|
||||
case PlaylistSequence::Shuffle_InsideAlbum:
|
||||
std::random_shuffle(begin, end);
|
||||
break;
|
||||
|
||||
case PlaylistSequence::Shuffle_Albums: {
|
||||
QMap<int, QString> album_keys; // real index -> key
|
||||
QSet<QString> album_key_set; // unique keys
|
||||
|
||||
// Find all the unique albums in the playlist
|
||||
for (QList<int>::iterator it = begin; it != end; ++it) {
|
||||
const int index = *it;
|
||||
const QString key = items_[index]->Metadata().AlbumKey();
|
||||
album_keys[index] = key;
|
||||
album_key_set << key;
|
||||
}
|
||||
|
||||
// Shuffle them
|
||||
QStringList shuffled_album_keys = album_key_set.toList();
|
||||
std::random_shuffle(shuffled_album_keys.begin(),
|
||||
shuffled_album_keys.end());
|
||||
|
||||
// If the user is currently playing a song, force its album to be first.
|
||||
if (current_virtual_index_ != -1) {
|
||||
const QString key = items_[current_row()]->Metadata().AlbumKey();
|
||||
const int pos = shuffled_album_keys.indexOf(key);
|
||||
if (pos >= 1) {
|
||||
std::swap(shuffled_album_keys[0], shuffled_album_keys[pos]);
|
||||
}
|
||||
}
|
||||
|
||||
// Create album key -> position mapping for fast lookup
|
||||
QMap<QString, int> album_key_positions;
|
||||
for (int i = 0; i < shuffled_album_keys.count(); ++i) {
|
||||
album_key_positions[shuffled_album_keys[i]] = i;
|
||||
}
|
||||
|
||||
// Sort the virtual items
|
||||
std::stable_sort(begin, end,
|
||||
std::bind(AlbumShuffleComparator, album_key_positions,
|
||||
album_keys, _1, _2));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::set_sequence(PlaylistSequence* v) {
|
||||
playlist_sequence_ = v;
|
||||
connect(v, SIGNAL(ShuffleModeChanged(PlaylistSequence::ShuffleMode)),
|
||||
SLOT(ShuffleModeChanged(PlaylistSequence::ShuffleMode)));
|
||||
|
||||
ShuffleModeChanged(v->shuffle_mode());
|
||||
}
|
||||
|
||||
QSortFilterProxyModel* Playlist::proxy() const { return proxy_; }
|
||||
|
||||
SongList Playlist::GetAllSongs() const {
|
||||
SongList ret;
|
||||
for (PlaylistItemPtr item : items_) {
|
||||
ret << item->Metadata();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
PlaylistItemList Playlist::GetAllItems() const { return items_; }
|
||||
|
||||
quint64 Playlist::GetTotalLength() const {
|
||||
quint64 ret = 0;
|
||||
for (PlaylistItemPtr item : items_) {
|
||||
quint64 length = item->Metadata().length_nanosec();
|
||||
if (length > 0) ret += length;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
PlaylistItemList Playlist::library_items_by_id(int id) const {
|
||||
return library_items_by_id_.values(id);
|
||||
}
|
||||
|
||||
void Playlist::TracksAboutToBeDequeued(const QModelIndex&, int begin, int end) {
|
||||
for (int i = begin; i <= end; ++i) {
|
||||
temp_dequeue_change_indexes_
|
||||
<< queue_->mapToSource(queue_->index(i, Column_Title));
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::TracksDequeued() {
|
||||
for (const QModelIndex& index : temp_dequeue_change_indexes_) {
|
||||
emit dataChanged(index, index);
|
||||
}
|
||||
temp_dequeue_change_indexes_.clear();
|
||||
}
|
||||
|
||||
void Playlist::TracksEnqueued(const QModelIndex&, int begin, int end) {
|
||||
const QModelIndex& b =
|
||||
queue_->mapToSource(queue_->index(begin, Column_Title));
|
||||
const QModelIndex& e = queue_->mapToSource(queue_->index(end, Column_Title));
|
||||
emit dataChanged(b, e);
|
||||
}
|
||||
|
||||
void Playlist::QueueLayoutChanged() {
|
||||
for (int i = 0; i < queue_->rowCount(); ++i) {
|
||||
const QModelIndex& index =
|
||||
queue_->mapToSource(queue_->index(i, Column_Title));
|
||||
emit dataChanged(index, index);
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::ItemChanged(PlaylistItemPtr item) {
|
||||
for (int row = 0; row < items_.count(); ++row) {
|
||||
if (items_[row] == item) {
|
||||
emit dataChanged(index(row, 0), index(row, ColumnCount - 1));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::InformOfCurrentSongChange() {
|
||||
emit dataChanged(index(current_item_index_.row(), 0),
|
||||
index(current_item_index_.row(), ColumnCount - 1));
|
||||
|
||||
// if the song is invalid, we won't play it - there's no point in
|
||||
// informing anybody about the change
|
||||
const Song metadata(current_item_metadata());
|
||||
if (metadata.is_valid()) {
|
||||
emit CurrentSongChanged(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::InvalidateDeletedSongs() {
|
||||
QList<int> invalidated_rows;
|
||||
|
||||
for (int row = 0; row < items_.count(); ++row) {
|
||||
PlaylistItemPtr item = items_[row];
|
||||
Song song = item->Metadata();
|
||||
|
||||
if (!song.is_stream()) {
|
||||
bool exists = QFile::exists(song.url().toLocalFile());
|
||||
|
||||
if (!exists && !item->HasForegroundColor(kInvalidSongPriority)) {
|
||||
// gray out the song if it's not there
|
||||
item->SetForegroundColor(kInvalidSongPriority, kInvalidSongColor);
|
||||
invalidated_rows.append(row);
|
||||
} else if (exists && item->HasForegroundColor(kInvalidSongPriority)) {
|
||||
item->RemoveForegroundColor(kInvalidSongPriority);
|
||||
invalidated_rows.append(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ReloadItems(invalidated_rows);
|
||||
}
|
||||
|
||||
void Playlist::RemoveDeletedSongs() {
|
||||
QList<int> rows_to_remove;
|
||||
|
||||
for (int row = 0; row < items_.count(); ++row) {
|
||||
PlaylistItemPtr item = items_[row];
|
||||
Song song = item->Metadata();
|
||||
|
||||
if (!song.is_stream() && !QFile::exists(song.url().toLocalFile())) {
|
||||
rows_to_remove.append(row);
|
||||
}
|
||||
}
|
||||
|
||||
removeRows(rows_to_remove);
|
||||
}
|
||||
|
||||
struct SongSimilarHash {
|
||||
long operator()(const Song& song) const { return HashSimilar(song); }
|
||||
};
|
||||
|
||||
struct SongSimilarEqual {
|
||||
long operator()(const Song& song1, const Song& song2) const {
|
||||
return song1.IsSimilar(song2);
|
||||
}
|
||||
};
|
||||
|
||||
void Playlist::RemoveDuplicateSongs() {
|
||||
QList<int> rows_to_remove;
|
||||
unordered_map<Song, int, SongSimilarHash, SongSimilarEqual> unique_songs;
|
||||
|
||||
for (int row = 0; row < items_.count(); ++row) {
|
||||
PlaylistItemPtr item = items_[row];
|
||||
const Song& song = item->Metadata();
|
||||
|
||||
bool found_duplicate = false;
|
||||
|
||||
auto uniq_song_it = unique_songs.find(song);
|
||||
if (uniq_song_it != unique_songs.end()) {
|
||||
const Song& uniq_song = uniq_song_it->first;
|
||||
|
||||
if (song.bitrate() > uniq_song.bitrate()) {
|
||||
rows_to_remove.append(unique_songs[uniq_song]);
|
||||
unique_songs.erase(uniq_song);
|
||||
unique_songs.insert(std::make_pair(song, row));
|
||||
} else {
|
||||
emit dataChanged(index(row, 0), index(row, ColumnCount - 1));
|
||||
}
|
||||
}
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
void Playlist::RateSong(const QModelIndex& index, double rating) {
|
||||
int row = index.row();
|
||||
|
||||
if (has_item_at(row)) {
|
||||
PlaylistItemPtr item = item_at(row);
|
||||
if (item && item->IsLocalLibraryItem() && item->Metadata().id() != -1) {
|
||||
library_->UpdateSongRatingAsync(item->Metadata().id(), rating);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::AddSongInsertVetoListener(SongInsertVetoListener * listener) {
|
||||
veto_listeners_.append(listener);
|
||||
connect(listener, SIGNAL(destroyed()), this,
|
||||
SLOT(SongInsertVetoListenerDestroyed()));
|
||||
}
|
||||
|
||||
void Playlist::RemoveSongInsertVetoListener(SongInsertVetoListener *
|
||||
listener) {
|
||||
disconnect(listener, SIGNAL(destroyed()), this,
|
||||
SLOT(SongInsertVetoListenerDestroyed()));
|
||||
veto_listeners_.removeAll(listener);
|
||||
}
|
||||
|
||||
void Playlist::SongInsertVetoListenerDestroyed() {
|
||||
veto_listeners_.removeAll(qobject_cast<SongInsertVetoListener*>(sender()));
|
||||
}
|
||||
|
||||
void Playlist::Shuffle() {
|
||||
PlaylistItemList new_items(items_);
|
||||
|
||||
int begin = 0;
|
||||
if (dynamic_playlist_ && current_item_index_.isValid())
|
||||
begin += current_item_index_.row() + 1;
|
||||
|
||||
const int count = items_.count();
|
||||
for (int i = begin; i < count; ++i) {
|
||||
int new_pos = i + (rand() % (count - i));
|
||||
|
||||
std::swap(new_items[i], new_items[new_pos]);
|
||||
}
|
||||
|
||||
undo_stack_->push(new PlaylistUndoCommands::ShuffleItems(this, new_items));
|
||||
}
|
||||
|
||||
namespace {
|
||||
bool AlbumShuffleComparator(const QMap<QString, int>& album_key_positions,
|
||||
const QMap<int, QString>& album_keys, int left,
|
||||
int right) {
|
||||
const int left_pos = album_key_positions[album_keys[left]];
|
||||
const int right_pos = album_key_positions[album_keys[right]];
|
||||
|
||||
if (left_pos == right_pos) return left < right;
|
||||
return left_pos < right_pos;
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::ReshuffleIndices() {
|
||||
if (!playlist_sequence_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (playlist_sequence_->shuffle_mode() == PlaylistSequence::Shuffle_Off) {
|
||||
// No shuffling - sort the virtual item list normally.
|
||||
std::sort(virtual_items_.begin(), virtual_items_.end());
|
||||
if (current_row() != -1)
|
||||
current_virtual_index_ = virtual_items_.indexOf(current_row());
|
||||
return;
|
||||
}
|
||||
|
||||
// If the user is already playing a song, advance the begin iterator to
|
||||
// only shuffle items that haven't been played yet.
|
||||
QList<int>::iterator begin = virtual_items_.begin();
|
||||
QList<int>::iterator end = virtual_items_.end();
|
||||
if (current_virtual_index_ != -1)
|
||||
std::advance(begin, current_virtual_index_ + 1);
|
||||
|
||||
switch (playlist_sequence_->shuffle_mode()) {
|
||||
case PlaylistSequence::Shuffle_Off:
|
||||
// Handled above.
|
||||
break;
|
||||
|
||||
case PlaylistSequence::Shuffle_All:
|
||||
case PlaylistSequence::Shuffle_InsideAlbum:
|
||||
std::random_shuffle(begin, end);
|
||||
break;
|
||||
|
||||
case PlaylistSequence::Shuffle_Albums: {
|
||||
QMap<int, QString> album_keys; // real index -> key
|
||||
QSet<QString> album_key_set; // unique keys
|
||||
|
||||
// Find all the unique albums in the playlist
|
||||
for (QList<int>::iterator it = begin; it != end; ++it) {
|
||||
const int index = *it;
|
||||
const QString key = items_[index]->Metadata().AlbumKey();
|
||||
album_keys[index] = key;
|
||||
album_key_set << key;
|
||||
}
|
||||
|
||||
// Shuffle them
|
||||
QStringList shuffled_album_keys = album_key_set.toList();
|
||||
std::random_shuffle(shuffled_album_keys.begin(),
|
||||
shuffled_album_keys.end());
|
||||
|
||||
// If the user is currently playing a song, force its album to be first.
|
||||
if (current_virtual_index_ != -1) {
|
||||
const QString key = items_[current_row()]->Metadata().AlbumKey();
|
||||
const int pos = shuffled_album_keys.indexOf(key);
|
||||
if (pos >= 1) {
|
||||
std::swap(shuffled_album_keys[0], shuffled_album_keys[pos]);
|
||||
}
|
||||
}
|
||||
|
||||
// Create album key -> position mapping for fast lookup
|
||||
QMap<QString, int> album_key_positions;
|
||||
for (int i = 0; i < shuffled_album_keys.count(); ++i) {
|
||||
album_key_positions[shuffled_album_keys[i]] = i;
|
||||
}
|
||||
|
||||
// Sort the virtual items
|
||||
std::stable_sort(begin, end,
|
||||
std::bind(AlbumShuffleComparator, album_key_positions,
|
||||
album_keys, _1, _2));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::set_sequence(PlaylistSequence * v) {
|
||||
playlist_sequence_ = v;
|
||||
connect(v, SIGNAL(ShuffleModeChanged(PlaylistSequence::ShuffleMode)),
|
||||
SLOT(ShuffleModeChanged(PlaylistSequence::ShuffleMode)));
|
||||
|
||||
ShuffleModeChanged(v->shuffle_mode());
|
||||
}
|
||||
|
||||
QSortFilterProxyModel* Playlist::proxy() const { return proxy_; }
|
||||
|
||||
SongList Playlist::GetAllSongs() const {
|
||||
SongList ret;
|
||||
for (PlaylistItemPtr item : items_) {
|
||||
ret << item->Metadata();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
PlaylistItemList Playlist::GetAllItems() const { return items_; }
|
||||
|
||||
quint64 Playlist::GetTotalLength() const {
|
||||
quint64 ret = 0;
|
||||
for (PlaylistItemPtr item : items_) {
|
||||
quint64 length = item->Metadata().length_nanosec();
|
||||
if (length > 0) ret += length;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
PlaylistItemList Playlist::library_items_by_id(int id) const {
|
||||
return library_items_by_id_.values(id);
|
||||
}
|
||||
|
||||
void Playlist::TracksAboutToBeDequeued(const QModelIndex&, int begin,
|
||||
int end) {
|
||||
for (int i = begin; i <= end; ++i) {
|
||||
temp_dequeue_change_indexes_
|
||||
<< queue_->mapToSource(queue_->index(i, Column_Title));
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::TracksDequeued() {
|
||||
for (const QModelIndex& index : temp_dequeue_change_indexes_) {
|
||||
emit dataChanged(index, index);
|
||||
}
|
||||
temp_dequeue_change_indexes_.clear();
|
||||
}
|
||||
|
||||
void Playlist::TracksEnqueued(const QModelIndex&, int begin, int end) {
|
||||
const QModelIndex& b =
|
||||
queue_->mapToSource(queue_->index(begin, Column_Title));
|
||||
const QModelIndex& e =
|
||||
queue_->mapToSource(queue_->index(end, Column_Title));
|
||||
emit dataChanged(b, e);
|
||||
}
|
||||
|
||||
void Playlist::QueueLayoutChanged() {
|
||||
for (int i = 0; i < queue_->rowCount(); ++i) {
|
||||
const QModelIndex& index =
|
||||
queue_->mapToSource(queue_->index(i, Column_Title));
|
||||
emit dataChanged(index, index);
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::ItemChanged(PlaylistItemPtr item) {
|
||||
for (int row = 0; row < items_.count(); ++row) {
|
||||
if (items_[row] == item) {
|
||||
emit dataChanged(index(row, 0), index(row, ColumnCount - 1));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::InformOfCurrentSongChange() {
|
||||
emit dataChanged(index(current_item_index_.row(), 0),
|
||||
index(current_item_index_.row(), ColumnCount - 1));
|
||||
|
||||
// if the song is invalid, we won't play it - there's no point in
|
||||
// informing anybody about the change
|
||||
const Song metadata(current_item_metadata());
|
||||
if (metadata.is_valid()) {
|
||||
emit CurrentSongChanged(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::InvalidateDeletedSongs() {
|
||||
QList<int> invalidated_rows;
|
||||
|
||||
for (int row = 0; row < items_.count(); ++row) {
|
||||
PlaylistItemPtr item = items_[row];
|
||||
Song song = item->Metadata();
|
||||
|
||||
if (!song.is_stream()) {
|
||||
bool exists = QFile::exists(song.url().toLocalFile());
|
||||
|
||||
if (!exists && !item->HasForegroundColor(kInvalidSongPriority)) {
|
||||
// gray out the song if it's not there
|
||||
item->SetForegroundColor(kInvalidSongPriority, kInvalidSongColor);
|
||||
invalidated_rows.append(row);
|
||||
} else if (exists && item->HasForegroundColor(kInvalidSongPriority)) {
|
||||
item->RemoveForegroundColor(kInvalidSongPriority);
|
||||
invalidated_rows.append(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ReloadItems(invalidated_rows);
|
||||
}
|
||||
|
||||
void Playlist::RemoveDeletedSongs() {
|
||||
QList<int> rows_to_remove;
|
||||
|
||||
for (int row = 0; row < items_.count(); ++row) {
|
||||
PlaylistItemPtr item = items_[row];
|
||||
Song song = item->Metadata();
|
||||
|
||||
if (!song.is_stream() && !QFile::exists(song.url().toLocalFile())) {
|
||||
rows_to_remove.append(row);
|
||||
}
|
||||
found_duplicate = true;
|
||||
}
|
||||
|
||||
removeRows(rows_to_remove);
|
||||
}
|
||||
|
||||
struct SongSimilarHash {
|
||||
long operator()(const Song& song) const { return HashSimilar(song); }
|
||||
};
|
||||
|
||||
struct SongSimilarEqual {
|
||||
long operator()(const Song& song1, const Song& song2) const {
|
||||
return song1.IsSimilar(song2);
|
||||
}
|
||||
};
|
||||
|
||||
void Playlist::RemoveDuplicateSongs() {
|
||||
QList<int> rows_to_remove;
|
||||
unordered_map<Song, int, SongSimilarHash, SongSimilarEqual> unique_songs;
|
||||
|
||||
for (int row = 0; row < items_.count(); ++row) {
|
||||
PlaylistItemPtr item = items_[row];
|
||||
const Song& song = item->Metadata();
|
||||
|
||||
bool found_duplicate = false;
|
||||
|
||||
auto uniq_song_it = unique_songs.find(song);
|
||||
if (uniq_song_it != unique_songs.end()) {
|
||||
const Song& uniq_song = uniq_song_it->first;
|
||||
|
||||
if (song.bitrate() > uniq_song.bitrate()) {
|
||||
rows_to_remove.append(unique_songs[uniq_song]);
|
||||
unique_songs.erase(uniq_song);
|
||||
unique_songs.insert(std::make_pair(song, row));
|
||||
} else {
|
||||
rows_to_remove.append(row);
|
||||
}
|
||||
found_duplicate = true;
|
||||
}
|
||||
|
||||
if (!found_duplicate) {
|
||||
unique_songs.insert(std::make_pair(song, row));
|
||||
}
|
||||
}
|
||||
|
||||
removeRows(rows_to_remove);
|
||||
}
|
||||
|
||||
bool Playlist::ApplyValidityOnCurrentSong(const QUrl& url, bool valid) {
|
||||
PlaylistItemPtr current = current_item();
|
||||
|
||||
if (current) {
|
||||
Song current_song = current->Metadata();
|
||||
|
||||
// if validity has changed, reload the item
|
||||
if (!current_song.is_stream() && !current_song.is_cdda() &&
|
||||
current_song.url() == url &&
|
||||
current_song.is_valid() !=
|
||||
QFile::exists(current_song.url().toLocalFile())) {
|
||||
ReloadItems(QList<int>() << current_row());
|
||||
}
|
||||
|
||||
// gray out the song if it's now broken; otherwise undo the gray color
|
||||
if (valid) {
|
||||
current->RemoveForegroundColor(kInvalidSongPriority);
|
||||
} else {
|
||||
current->SetForegroundColor(kInvalidSongPriority, kInvalidSongColor);
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<bool>(current);
|
||||
}
|
||||
|
||||
void Playlist::SetColumnAlignment(const ColumnAlignmentMap& alignment) {
|
||||
column_alignments_ = alignment;
|
||||
}
|
||||
|
||||
void Playlist::SkipTracks(const QModelIndexList& source_indexes) {
|
||||
for (const QModelIndex& source_index : source_indexes) {
|
||||
PlaylistItemPtr track_to_skip = item_at(source_index.row());
|
||||
track_to_skip->SetShouldSkip(!((track_to_skip)->GetShouldSkip()));
|
||||
if (!found_duplicate) {
|
||||
unique_songs.insert(std::make_pair(song, row));
|
||||
}
|
||||
}
|
||||
|
||||
removeRows(rows_to_remove);
|
||||
}
|
||||
|
||||
bool Playlist::ApplyValidityOnCurrentSong(const QUrl& url, bool valid) {
|
||||
PlaylistItemPtr current = current_item();
|
||||
|
||||
if (current) {
|
||||
Song current_song = current->Metadata();
|
||||
|
||||
// if validity has changed, reload the item
|
||||
if (!current_song.is_stream() && !current_song.is_cdda() &&
|
||||
current_song.url() == url &&
|
||||
current_song.is_valid() !=
|
||||
QFile::exists(current_song.url().toLocalFile())) {
|
||||
ReloadItems(QList<int>() << current_row());
|
||||
}
|
||||
|
||||
// gray out the song if it's now broken; otherwise undo the gray color
|
||||
if (valid) {
|
||||
current->RemoveForegroundColor(kInvalidSongPriority);
|
||||
} else {
|
||||
current->SetForegroundColor(kInvalidSongPriority, kInvalidSongColor);
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<bool>(current);
|
||||
}
|
||||
|
||||
void Playlist::SetColumnAlignment(const ColumnAlignmentMap& alignment) {
|
||||
column_alignments_ = alignment;
|
||||
}
|
||||
|
||||
void Playlist::SkipTracks(const QModelIndexList& source_indexes) {
|
||||
for (const QModelIndex& source_index : source_indexes) {
|
||||
PlaylistItemPtr track_to_skip = item_at(source_index.row());
|
||||
track_to_skip->SetShouldSkip(!((track_to_skip)->GetShouldSkip()));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user