Add smart playlists, ratings and Qobuz

Fixes #259
Fixes #264
This commit is contained in:
Jonas Kvinge
2020-09-17 17:50:17 +02:00
parent fdf96e8342
commit 89d6b7cec0
102 changed files with 10949 additions and 525 deletions

View File

@@ -78,6 +78,7 @@
#include "covermanager/albumcoverloaderresult.h"
#include "settings/appearancesettingspage.h"
#include "settings/playlistsettingspage.h"
#include "dynamicplaylistcontrols.h"
#ifdef HAVE_MOODBAR
# include "moodbar/moodbaritemdelegate.h"
@@ -168,7 +169,11 @@ PlaylistView::PlaylistView(QWidget *parent)
cached_current_row_row_(-1),
drop_indicator_row_(-1),
drag_over_(false),
column_alignment_(DefaultColumnAlignment()) {
header_state_version_(1),
column_alignment_(DefaultColumnAlignment()),
rating_locked_(false),
dynamic_controls_(new DynamicPlaylistControls(this)),
rating_delegate_(nullptr) {
setHeader(header_);
header_->setSectionsMovable(true);
@@ -195,6 +200,9 @@ PlaylistView::PlaylistView(QWidget *parent)
connect(header_, SIGNAL(SectionVisibilityChanged(int, bool)), SLOT(InvalidateCachedCurrentPixmap()));
connect(header_, SIGNAL(StretchEnabledChanged(bool)), SLOT(StretchChanged(bool)));
connect(header_, SIGNAL(SectionRatingLockStatusChanged(bool)), SLOT(SetRatingLockStatus(bool)));
connect(header_, SIGNAL(MouseEntered()), SLOT(RatingHoverOut()));
inhibit_autoscroll_timer_->setInterval(kAutoscrollGraceTimeout * 1000);
inhibit_autoscroll_timer_->setSingleShot(true);
connect(inhibit_autoscroll_timer_, SIGNAL(timeout()), SLOT(InhibitAutoscrollTimeout()));
@@ -202,6 +210,8 @@ PlaylistView::PlaylistView(QWidget *parent)
horizontalScrollBar()->installEventFilter(this);
verticalScrollBar()->installEventFilter(this);
dynamic_controls_->hide();
// For fading
connect(fade_animation_, SIGNAL(valueChanged(qreal)), SLOT(FadePreviousBackgroundImage(qreal)));
fade_animation_->setDirection(QTimeLine::Backward); // 1.0 -> 0.0
@@ -258,12 +268,16 @@ void PlaylistView::SetItemDelegates() {
setItemDelegateForColumn(Playlist::Column_Mood, new MoodbarItemDelegate(app_, this, this));
#endif
rating_delegate_ = new RatingItemDelegate(this);
setItemDelegateForColumn(Playlist::Column_Rating, rating_delegate_);
}
void PlaylistView::setModel(QAbstractItemModel *m) {
if (model()) {
disconnect(model(), SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(InvalidateCachedCurrentPixmap()));
disconnect(model(), SIGNAL(layoutAboutToBeChanged()), this, SLOT(RatingHoverOut()));
// When changing the model, always invalidate the current pixmap.
// If a remote client uses "stop after", without invaliding the stop mark would not appear.
@@ -273,6 +287,7 @@ void PlaylistView::setModel(QAbstractItemModel *m) {
QTreeView::setModel(m);
connect(model(), SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(InvalidateCachedCurrentPixmap()));
connect(model(), SIGNAL(layoutAboutToBeChanged()), this, SLOT(RatingHoverOut()));
}
@@ -282,10 +297,16 @@ void PlaylistView::SetPlaylist(Playlist *playlist) {
disconnect(playlist_, SIGNAL(MaybeAutoscroll(Playlist::AutoScroll)), this, SLOT(MaybeAutoscroll(Playlist::AutoScroll)));
disconnect(playlist_, SIGNAL(destroyed()), this, SLOT(PlaylistDestroyed()));
disconnect(playlist_, SIGNAL(QueueChanged()), this, SLOT(update()));
disconnect(playlist_, SIGNAL(DynamicModeChanged(bool)), this, SLOT(DynamicModeChanged(bool)));
disconnect(dynamic_controls_, SIGNAL(Expand()), playlist_, SLOT(ExpandDynamicPlaylist()));
disconnect(dynamic_controls_, SIGNAL(Repopulate()), playlist_, SLOT(RepopulateDynamicPlaylist()));
disconnect(dynamic_controls_, SIGNAL(TurnOff()), playlist_, SLOT(TurnOffDynamicPlaylist()));
}
playlist_ = playlist;
RestoreHeaderState();
DynamicModeChanged(playlist->is_dynamic());
setFocus();
JumpToLastPlayedTrack();
@@ -294,13 +315,21 @@ void PlaylistView::SetPlaylist(Playlist *playlist) {
connect(playlist_, SIGNAL(destroyed()), SLOT(PlaylistDestroyed()));
connect(playlist_, SIGNAL(QueueChanged()), SLOT(update()));
connect(playlist_, SIGNAL(DynamicModeChanged(bool)), SLOT(DynamicModeChanged(bool)));
connect(dynamic_controls_, SIGNAL(Expand()), playlist_, SLOT(ExpandDynamicPlaylist()));
connect(dynamic_controls_, SIGNAL(Repopulate()), playlist_, SLOT(RepopulateDynamicPlaylist()));
connect(dynamic_controls_, SIGNAL(TurnOff()), playlist_, SLOT(TurnOffDynamicPlaylist()));
}
void PlaylistView::LoadHeaderState() {
QSettings s;
s.beginGroup(Playlist::kSettingsGroup);
if (s.contains("state")) header_state_ = s.value("state").toByteArray();
if (s.contains("state")) {
header_state_version_ = s.value("state_version", 0).toInt();
header_state_ = s.value("state").toByteArray();
}
if (s.contains("column_alignments")) column_alignment_ = s.value("column_alignments").value<ColumnAlignmentMap>();
s.endGroup();
@@ -322,6 +351,7 @@ void PlaylistView::SetHeaderState() {
void PlaylistView::ResetHeaderState() {
set_initial_header_layout_ = true;
header_state_version_ = 1;
header_state_ = header_->ResetState();
RestoreHeaderState();
@@ -355,6 +385,7 @@ void PlaylistView::RestoreHeaderState() {
header_->HideSection(Playlist::Column_Comment);
header_->HideSection(Playlist::Column_Grouping);
header_->HideSection(Playlist::Column_Mood);
header_->HideSection(Playlist::Column_Rating);
header_->moveSection(header_->visualIndex(Playlist::Column_Track), 0);
@@ -378,6 +409,11 @@ void PlaylistView::RestoreHeaderState() {
}
if (header_state_version_ < 1) {
header_->HideSection(Playlist::Column_Rating);
header_state_version_ = 1;
}
// Make sure at least one column is visible
bool all_hidden = true;
for (int i = 0; i < header_->count(); ++i) {
@@ -744,6 +780,17 @@ void PlaylistView::closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHi
void PlaylistView::mouseMoveEvent(QMouseEvent *event) {
// Check whether rating section is locked by user or not
if (!rating_locked_) {
QModelIndex idx = indexAt(event->pos());
if (idx.isValid() && idx.data(Playlist::Role_CanSetRating).toBool()) {
RatingHoverIn(idx, event->pos());
}
else if (rating_delegate_->is_mouse_over()) {
RatingHoverOut();
}
}
if (!drag_over_) {
QTreeView::mouseMoveEvent(event);
}
@@ -752,6 +799,10 @@ void PlaylistView::mouseMoveEvent(QMouseEvent *event) {
void PlaylistView::leaveEvent(QEvent *e) {
if (rating_delegate_->is_mouse_over() && !rating_locked_) {
RatingHoverOut();
}
QTreeView::leaveEvent(e);
}
@@ -764,19 +815,47 @@ void PlaylistView::mousePressEvent(QMouseEvent *event) {
}
QModelIndex idx = indexAt(event->pos());
if (event->button() == Qt::XButton1 && idx.isValid()) {
app_->player()->Previous();
}
else if (event->button() == Qt::XButton2 && idx.isValid()) {
app_->player()->Next();
}
else {
QTreeView::mousePressEvent(event);
if (idx.isValid()) {
switch (event->button()) {
case Qt::XButton1:
app_->player()->Previous();
break;
case Qt::XButton2:
app_->player()->Next();
break;
case Qt::LeftButton:{
if (idx.data(Playlist::Role_CanSetRating).toBool() && !rating_locked_) {
// Calculate which star was clicked
double new_rating = RatingPainter::RatingForPos(event->pos(), visualRect(idx));
if (selectedIndexes().contains(idx)) {
// Update all the selected item ratings
QModelIndexList src_index_list;
for (const QModelIndex &i : selectedIndexes()) {
if (i.data(Playlist::Role_CanSetRating).toBool()) {
src_index_list << playlist_->proxy()->mapToSource(i);
}
}
if (!src_index_list.isEmpty()) {
playlist_->RateSongs(src_index_list, new_rating);
}
}
else {
// Update only this item rating
playlist_->RateSong(playlist_->proxy()->mapToSource(idx), new_rating);
}
}
break;
}
default:
break;
}
}
inhibit_autoscroll_ = true;
inhibit_autoscroll_timer_->start();
QTreeView::mousePressEvent(event);
}
void PlaylistView::scrollContentsBy(int dx, int dy) {
@@ -1150,8 +1229,10 @@ void PlaylistView::SaveSettings() {
QSettings s;
s.beginGroup(Playlist::kSettingsGroup);
s.setValue("state_version", header_state_version_);
s.setValue("state", header_->SaveState());
s.setValue("column_alignments", QVariant::fromValue<ColumnAlignmentMap>(column_alignment_));
s.setValue("rating_locked", rating_locked_);
s.endGroup();
}
@@ -1165,6 +1246,15 @@ void PlaylistView::StretchChanged(const bool stretch) {
}
void PlaylistView::resizeEvent(QResizeEvent *e) {
QTreeView::resizeEvent(e);
if (dynamic_controls_->isVisible()) {
RepositionDynamicControls();
}
}
bool PlaylistView::eventFilter(QObject *object, QEvent *event) {
if (event->type() == QEvent::Enter && (object == horizontalScrollBar() || object == verticalScrollBar())) {
@@ -1357,3 +1447,75 @@ void PlaylistView::focusInEvent(QFocusEvent *event) {
}
}
void PlaylistView::DynamicModeChanged(bool dynamic) {
if (!dynamic) {
dynamic_controls_->hide();
}
else {
RepositionDynamicControls();
dynamic_controls_->show();
}
}
void PlaylistView::RepositionDynamicControls() {
dynamic_controls_->resize(dynamic_controls_->sizeHint());
dynamic_controls_->move((width() - dynamic_controls_->width()) / 2, height() - dynamic_controls_->height() - 20);
}
void PlaylistView::SetRatingLockStatus(const bool state) {
if (!header_state_loaded_) return;
rating_locked_ = state;
}
void PlaylistView::RatingHoverIn(const QModelIndex &idx, const QPoint &pos) {
if (editTriggers() & QAbstractItemView::NoEditTriggers) {
return;
}
const QModelIndex old_index = rating_delegate_->mouse_over_index();
rating_delegate_->set_mouse_over(idx, selectedIndexes(), pos);
setCursor(Qt::PointingHandCursor);
update(idx);
update(old_index);
for (const QModelIndex &i : selectedIndexes()) {
if (i.column() == Playlist::Column_Rating) update(i);
}
if (idx.data(Playlist::Role_IsCurrent).toBool() || old_index.data(Playlist::Role_IsCurrent).toBool()) {
InvalidateCachedCurrentPixmap();
}
}
void PlaylistView::RatingHoverOut() {
if (editTriggers() & QAbstractItemView::NoEditTriggers) {
return;
}
const QModelIndex old_index = rating_delegate_->mouse_over_index();
rating_delegate_->set_mouse_out();
setCursor(QCursor());
update(old_index);
for (const QModelIndex &i : selectedIndexes()) {
if (i.column() == Playlist::Column_Rating) {
update(i);
}
}
if (old_index.data(Playlist::Role_IsCurrent).toBool()) {
InvalidateCachedCurrentPixmap();
}
}