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

@@ -0,0 +1,31 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "dynamicplaylistcontrols.h"
#include "ui_dynamicplaylistcontrols.h"
DynamicPlaylistControls::DynamicPlaylistControls(QWidget *parent)
: QWidget(parent), ui_(new Ui_DynamicPlaylistControls) {
ui_->setupUi(this);
connect(ui_->expand, SIGNAL(clicked()), SIGNAL(Expand()));
connect(ui_->repopulate, SIGNAL(clicked()), SIGNAL(Repopulate()));
connect(ui_->off, SIGNAL(clicked()), SIGNAL(TurnOff()));
}
DynamicPlaylistControls::~DynamicPlaylistControls() { delete ui_; }

View File

@@ -0,0 +1,41 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef DYNAMICPLAYLISTCONTROLS_H
#define DYNAMICPLAYLISTCONTROLS_H
#include <QWidget>
class Ui_DynamicPlaylistControls;
class DynamicPlaylistControls : public QWidget {
Q_OBJECT
public:
DynamicPlaylistControls(QWidget *parent = nullptr);
~DynamicPlaylistControls();
signals:
void Expand();
void Repopulate();
void TurnOff();
private:
Ui_DynamicPlaylistControls* ui_;
};
#endif // DYNAMICPLAYLISTCONTROLS_H

View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DynamicPlaylistControls</class>
<widget class="QWidget" name="DynamicPlaylistControls">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>483</width>
<height>54</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true">#container {
background: rgba(200, 200, 200, 50%);
border-radius: 10px;
border: 1px solid rgba(200, 200, 200, 75%);
}
#label1 {
font-weight: bold;
}
#label2 {
font-size: 7.5pt;
}</string>
</property>
<layout class="QVBoxLayout" name="layout_dynamic_playlist_controls">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QFrame" name="container">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="layout_container">
<item>
<layout class="QVBoxLayout" name="layout_labels">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label1">
<property name="text">
<string>Dynamic mode is on</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label2">
<property name="text">
<string>New tracks will be added automatically.</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="expand">
<property name="text">
<string>Expand</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="repopulate">
<property name="text">
<string>Repopulate</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="off">
<property name="text">
<string>Turn off</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -84,6 +84,10 @@
#include "songplaylistitem.h"
#include "tagreadermessages.pb.h"
#include "smartplaylists/playlistgenerator.h"
#include "smartplaylists/playlistgeneratorinserter.h"
#include "smartplaylists/playlistgeneratormimedata.h"
#include "internet/internetplaylistitem.h"
#include "internet/internetsongmimedata.h"
@@ -271,6 +275,9 @@ QVariant Playlist::data(const QModelIndex &idx, int role) const {
case Role_QueuePosition:
return queue_->PositionOf(idx);
case Role_CanSetRating:
return idx.column() == Column_Rating && items_[idx.row()]->IsLocalCollectionItem() && items_[idx.row()]->Metadata().id() != -1;
case Qt::EditRole:
case Qt::ToolTipRole:
case Qt::DisplayRole: {
@@ -314,6 +321,8 @@ QVariant Playlist::data(const QModelIndex &idx, int role) const {
case Column_Source: return song.source();
case Column_Rating: return song.rating();
}
return QVariant();
@@ -331,6 +340,9 @@ QVariant Playlist::data(const QModelIndex &idx, int role) const {
if (items_[idx.row()]->HasCurrentForegroundColor()) {
return QBrush(items_[idx.row()]->GetCurrentForegroundColor());
}
if (idx.row() < dynamic_history_length()) {
return QBrush(kDynamicHistoryColor);
}
return QVariant();
@@ -634,6 +646,35 @@ void Playlist::set_current_row(const int i, const AutoScroll autoscroll, const b
InformOfCurrentSongChange(autoscroll);
}
// The structure of a dynamic playlist is as follows:
// history - active song - future
// We have to ensure that this invariant is maintained.
if (dynamic_playlist_ && current_item_index_.isValid()) {
// When advancing to the next track
if (i > old_current_item_index.row()) {
// Move the new item one position ahead of the last item in the history.
MoveItemWithoutUndo(current_item_index_.row(), dynamic_history_length());
// Compute the number of new items that have to be inserted. This is not
// necessarily 1 because the user might have added or removed items
// manually. Note that the future excludes the current item.
const int count = dynamic_history_length() + 1 + dynamic_playlist_->GetDynamicFuture() - items_.count();
if (count > 0) {
InsertDynamicItems(count);
}
// Shrink the history, again this is not necessarily by 1, because the
// user might have moved items by hand.
const int remove_count = dynamic_history_length() - dynamic_playlist_->GetDynamicHistory();
if (0 < remove_count) RemoveItemsWithoutUndo(0, remove_count);
}
// the above actions make all commands on the undo stack invalid, so we
// better clear it.
undo_stack_->clear();
}
if (current_item_index_.isValid()) {
last_played_item_index_ = current_item_index_;
Save();
@@ -643,6 +684,16 @@ void Playlist::set_current_row(const int i, const AutoScroll autoscroll, const b
}
void Playlist::InsertDynamicItems(const int count) {
PlaylistGeneratorInserter* inserter = new PlaylistGeneratorInserter(task_manager_, collection_, this);
connect(inserter, SIGNAL(Error(QString)), SIGNAL(Error(QString)));
connect(inserter, SIGNAL(PlayRequested(QModelIndex)), SIGNAL(PlayRequested(QModelIndex)));
inserter->Load(this, -1, false, false, false, dynamic_playlist_, count);
}
Qt::ItemFlags Playlist::flags(const QModelIndex &idx) const {
if (idx.isValid()) {
@@ -697,6 +748,9 @@ bool Playlist::dropMimeData(const QMimeData *data, Qt::DropAction action, int ro
else if (const InternetSongMimeData* internet_song_data = qobject_cast<const InternetSongMimeData*>(data)) {
InsertInternetItems(internet_song_data->service, internet_song_data->songs, row, play_now, enqueue_now, enqueue_next_now);
}
else if (const PlaylistGeneratorMimeData *generator_data = qobject_cast<const PlaylistGeneratorMimeData*>(data)) {
InsertSmartPlaylist(generator_data->generator_, row, play_now, enqueue_now, enqueue_next_now);
}
else if (data->hasFormat(kRowsMimetype)) {
// Dragged from the playlist
// Rearranging it is tricky...
@@ -768,6 +822,34 @@ void Playlist::InsertUrls(const QList<QUrl> &urls, const int pos, const bool pla
}
void Playlist::InsertSmartPlaylist(PlaylistGeneratorPtr generator, const int pos, const bool play_now, const bool enqueue, const bool enqueue_next) {
// Hack: If the generator hasn't got a collection set then use the main one
if (!generator->collection()) {
generator->set_collection(collection_);
}
PlaylistGeneratorInserter *inserter = new PlaylistGeneratorInserter(task_manager_, collection_, this);
connect(inserter, SIGNAL(Error(QString)), SIGNAL(Error(QString)));
inserter->Load(this, pos, play_now, enqueue, enqueue_next, generator);
if (generator->is_dynamic()) {
TurnOnDynamicPlaylist(generator);
}
}
void Playlist::TurnOnDynamicPlaylist(PlaylistGeneratorPtr gen) {
dynamic_playlist_ = gen;
playlist_sequence_->SetUsingDynamicPlaylist(true);
ShuffleModeChanged(PlaylistSequence::Shuffle_Off);
emit DynamicModeChanged(true);
Save();
}
void Playlist::MoveItemWithoutUndo(const int source, const int dest) {
MoveItemsWithoutUndo(QList<int>() << source, dest);
}
@@ -1139,6 +1221,9 @@ bool Playlist::CompareItems(const int column, const Qt::SortOrder order, std::sh
case Column_Comment: strcmp(comment);
case Column_Source: cmp(source);
case Column_Rating: cmp(rating);
default: qLog(Error) << "No such column" << column;
}
@@ -1196,6 +1281,7 @@ QString Playlist::column_name(Column column) {
case Column_Comment: return tr("Comment");
case Column_Source: return tr("Source");
case Column_Mood: return tr("Mood");
case Column_Rating: return tr("Rating");
default: qLog(Error) << "No such column" << column;;
}
return "";
@@ -1226,6 +1312,9 @@ void Playlist::sort(int column, Qt::SortOrder order) {
PlaylistItemList new_items(items_);
PlaylistItemList::iterator begin = new_items.begin();
if (dynamic_playlist_ && current_item_index_.isValid())
begin += current_item_index_.row() + 1;
if (column == Column_Album) {
// When sorting by album, also take into account discs and tracks.
std::stable_sort(begin, new_items.end(), std::bind(&Playlist::CompareItems, Column_Track, order, _1, _2));
@@ -1291,7 +1380,7 @@ void Playlist::Save() const {
if (!backend_ || is_loading_) return;
backend_->SavePlaylistAsync(id_, items_, last_played_row());
backend_->SavePlaylistAsync(id_, items_, last_played_row(), dynamic_playlist_);
}
@@ -1334,6 +1423,22 @@ void Playlist::ItemsLoaded(QFuture<PlaylistItemList> future) {
// The newly loaded list of items might be shorter than it was before so look out for a bad last_played index
last_played_item_index_ = p.last_played == -1 || p.last_played >= rowCount() ? QModelIndex() : index(p.last_played);
if (p.dynamic_type == PlaylistGenerator::Type_Query) {
PlaylistGeneratorPtr gen = PlaylistGenerator::Create(p.dynamic_type);
if (gen) {
CollectionBackend *backend = nullptr;
if (p.dynamic_backend == collection_->songs_table()) backend = collection_;
if (backend) {
gen->set_collection(backend);
gen->Load(p.dynamic_data);
TurnOnDynamicPlaylist(gen);
}
}
}
emit RestoreFinished();
QSettings s;
@@ -1572,10 +1677,29 @@ void Playlist::Clear() {
undo_stack_->push(new PlaylistUndoCommands::RemoveItems(this, 0, count));
}
TurnOffDynamicPlaylist();
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() && !current_item_index_.isValid()) {
@@ -1650,6 +1774,9 @@ void Playlist::Shuffle() {
begin = 1;
}
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));
@@ -2036,3 +2163,48 @@ void Playlist::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &
}
}
int Playlist::dynamic_history_length() const {
return dynamic_playlist_ && last_played_item_index_.isValid() ? last_played_item_index_.row() + 1 : 0;
}
void Playlist::TurnOffDynamicPlaylist() {
dynamic_playlist_.reset();
if (playlist_sequence_) {
playlist_sequence_->SetUsingDynamicPlaylist(false);
ShuffleModeChanged(playlist_sequence_->shuffle_mode());
}
emit DynamicModeChanged(false);
Save();
}
void Playlist::RateSong(const QModelIndex &idx, const double rating) {
if (has_item_at(idx.row())) {
PlaylistItemPtr item = item_at(idx.row());
if (item && item->IsLocalCollectionItem() && item->Metadata().id() != -1) {
collection_->UpdateSongRatingAsync(item->Metadata().id(), rating);
}
}
}
void Playlist::RateSongs(const QModelIndexList &index_list, const double rating) {
QList<int> id_list;
for (const QModelIndex &idx : index_list) {
const int row = idx.row();
if (has_item_at(row)) {
PlaylistItemPtr item = item_at(row);
if (item && item->IsLocalCollectionItem() && item->Metadata().id() != -1) {
id_list << item->Metadata().id();
}
}
}
collection_->UpdateSongsRatingAsync(id_list, rating);
}

View File

@@ -46,6 +46,7 @@
#include "covermanager/albumcoverloaderresult.h"
#include "playlistitem.h"
#include "playlistsequence.h"
#include "smartplaylists/playlistgenerator_fwd.h"
class QMimeData;
class QSortFilterProxyModel;
@@ -128,6 +129,7 @@ class Playlist : public QAbstractListModel {
Column_Grouping,
Column_Source,
Column_Mood,
Column_Rating,
ColumnCount
};
@@ -135,7 +137,8 @@ class Playlist : public QAbstractListModel {
Role_IsCurrent = Qt::UserRole + 1,
Role_IsPaused,
Role_StopAfter,
Role_QueuePosition
Role_QueuePosition,
Role_CanSetRating,
};
enum Path {
@@ -203,6 +206,8 @@ class Playlist : public QAbstractListModel {
const QModelIndex current_index() const;
bool stop_after_current() const;
bool is_dynamic() const { return static_cast<bool>(dynamic_playlist_); }
int dynamic_history_length() const;
QString special_type() const { return special_type_; }
void set_special_type(const QString &v) { special_type_ = v; }
@@ -240,6 +245,7 @@ class Playlist : public QAbstractListModel {
void InsertSongs(const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
void InsertSongsOrCollectionItems(const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
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);
void InsertSmartPlaylist(PlaylistGeneratorPtr gen, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
void ReshuffleIndices();
@@ -284,6 +290,10 @@ class Playlist : public QAbstractListModel {
static bool ComparePathDepths(Qt::SortOrder, PlaylistItemPtr, PlaylistItemPtr);
// Changes rating of a song to the given value asynchronously
void RateSong(const QModelIndex &idx, const double rating);
void RateSongs(const QModelIndexList &index_list, const double rating);
public slots:
void set_current_row(const int i, const AutoScroll autoscroll = AutoScroll_Maybe, const bool is_stopping = false);
void Paused();
@@ -309,6 +319,10 @@ class Playlist : public QAbstractListModel {
// Removes items with given indices from the playlist. This operation is not undoable.
void RemoveItemsWithoutUndo(const QList<int> &indicesIn);
void ExpandDynamicPlaylist();
void RepopulateDynamicPlaylist();
void TurnOffDynamicPlaylist();
signals:
void RestoreFinished();
void PlaylistLoaded();
@@ -349,6 +363,9 @@ class Playlist : public QAbstractListModel {
// Removes rows with given indices from this playlist.
bool removeRows(QList<int> &rows);
void TurnOnDynamicPlaylist(PlaylistGeneratorPtr gen);
void InsertDynamicItems(const int count);
private slots:
void TracksAboutToBeDequeued(const QModelIndex&, const int begin, const int end);
void TracksDequeued();
@@ -412,6 +429,8 @@ class Playlist : public QAbstractListModel {
int editing_;
PlaylistGeneratorPtr dynamic_playlist_;
};
#endif // PLAYLIST_H

View File

@@ -55,6 +55,7 @@
#include "songplaylistitem.h"
#include "playlistbackend.h"
#include "playlistparsers/cueparser.h"
#include "smartplaylists/playlistgenerator.h"
using std::placeholders::_1;
@@ -121,7 +122,7 @@ PlaylistBackend::PlaylistList PlaylistBackend::GetPlaylists(GetPlaylistsFlags fl
}
QSqlQuery q(db);
q.prepare("SELECT ROWID, name, last_played, special_type, ui_path, is_favorite FROM playlists " + condition + " ORDER BY ui_order");
q.prepare("SELECT ROWID, name, last_played, special_type, ui_path, is_favorite, dynamic_playlist_type, dynamic_playlist_data, dynamic_playlist_backend FROM playlists " + condition + " ORDER BY ui_order");
q.exec();
if (db_->CheckErrors(q)) return ret;
@@ -133,6 +134,9 @@ PlaylistBackend::PlaylistList PlaylistBackend::GetPlaylists(GetPlaylistsFlags fl
p.special_type = q.value(3).toString();
p.ui_path = q.value(4).toString();
p.favorite = q.value(5).toBool();
p.dynamic_type = PlaylistGenerator::Type(q.value(6).toInt());
p.dynamic_data = q.value(7).toByteArray();
p.dynamic_backend = q.value(8).toString();
ret << p;
}
@@ -146,7 +150,7 @@ PlaylistBackend::Playlist PlaylistBackend::GetPlaylist(int id) {
QSqlDatabase db(db_->Connect());
QSqlQuery q(db);
q.prepare("SELECT ROWID, name, last_played, special_type, ui_path, is_favorite FROM playlists WHERE ROWID=:id");
q.prepare("SELECT ROWID, name, last_played, special_type, ui_path, is_favorite, dynamic_playlist_type, dynamic_playlist_data, dynamic_playlist_backend FROM playlists WHERE ROWID=:id");
q.bindValue(":id", id);
q.exec();
@@ -161,6 +165,9 @@ PlaylistBackend::Playlist PlaylistBackend::GetPlaylist(int id) {
p.special_type = q.value(3).toString();
p.ui_path = q.value(4).toString();
p.favorite = q.value(5).toBool();
p.dynamic_type = PlaylistGenerator::Type(q.value(6).toInt());
p.dynamic_data = q.value(7).toByteArray();
p.dynamic_backend = q.value(8).toString();
return p;
@@ -315,13 +322,13 @@ PlaylistItemPtr PlaylistBackend::RestoreCueData(PlaylistItemPtr item, std::share
}
void PlaylistBackend::SavePlaylistAsync(int playlist, const PlaylistItemList &items, int last_played) {
void PlaylistBackend::SavePlaylistAsync(int playlist, const PlaylistItemList &items, int last_played, PlaylistGeneratorPtr dynamic) {
metaObject()->invokeMethod(this, "SavePlaylist", Qt::QueuedConnection, Q_ARG(int, playlist), Q_ARG(PlaylistItemList, items), Q_ARG(int, last_played));
metaObject()->invokeMethod(this, "SavePlaylist", Qt::QueuedConnection, Q_ARG(int, playlist), Q_ARG(PlaylistItemList, items), Q_ARG(int, last_played), Q_ARG(PlaylistGeneratorPtr, dynamic));
}
void PlaylistBackend::SavePlaylist(int playlist, const PlaylistItemList &items, int last_played) {
void PlaylistBackend::SavePlaylist(int playlist, const PlaylistItemList &items, int last_played, PlaylistGeneratorPtr dynamic) {
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
@@ -333,7 +340,7 @@ void PlaylistBackend::SavePlaylist(int playlist, const PlaylistItemList &items,
QSqlQuery insert(db);
insert.prepare("INSERT INTO playlist_items (playlist, type, collection_id, " + Song::kColumnSpec + ") VALUES (:playlist, :type, :collection_id, " + Song::kBindSpec + ")");
QSqlQuery update(db);
update.prepare("UPDATE playlists SET last_played=:last_played WHERE ROWID=:playlist");
update.prepare("UPDATE playlists SET last_played=:last_played, dynamic_playlist_type=:dynamic_type, dynamic_playlist_data=:dynamic_data, dynamic_playlist_backend=:dynamic_backend WHERE ROWID=:playlist");
ScopedTransaction transaction(&db);
@@ -353,6 +360,16 @@ void PlaylistBackend::SavePlaylist(int playlist, const PlaylistItemList &items,
// Update the last played track number
update.bindValue(":last_played", last_played);
if (dynamic) {
update.bindValue(":dynamic_type", dynamic->type());
update.bindValue(":dynamic_data", dynamic->Save());
update.bindValue(":dynamic_backend", dynamic->collection()->songs_table());
}
else {
update.bindValue(":dynamic_type", 0);
update.bindValue(":dynamic_data", QByteArray());
update.bindValue(":dynamic_backend", QString());
}
update.bindValue(":playlist", playlist);
update.exec();
if (db_->CheckErrors(update)) return;

View File

@@ -37,6 +37,7 @@
#include "core/song.h"
#include "collection/sqlrow.h"
#include "playlistitem.h"
#include "smartplaylists/playlistgenerator.h"
class QThread;
class Application;
@@ -57,6 +58,9 @@ class PlaylistBackend : public QObject {
bool favorite;
int last_played;
QString special_type;
PlaylistGenerator::Type dynamic_type;
QString dynamic_backend;
QByteArray dynamic_data;
};
typedef QList<Playlist> PlaylistList;
@@ -77,7 +81,7 @@ class PlaylistBackend : public QObject {
void SetPlaylistUiPath(int id, const QString &path);
int CreatePlaylist(const QString &name, const QString &special_type);
void SavePlaylistAsync(int playlist, const PlaylistItemList &items, int last_played);
void SavePlaylistAsync(int playlist, const PlaylistItemList &items, int last_played, PlaylistGeneratorPtr dynamic);
void RenamePlaylist(int id, const QString &new_name);
void FavoritePlaylist(int id, bool is_favorite);
void RemovePlaylist(int id);
@@ -86,7 +90,7 @@ class PlaylistBackend : public QObject {
public slots:
void Exit();
void SavePlaylist(int playlist, const PlaylistItemList &items, int last_played);
void SavePlaylist(int playlist, const PlaylistItemList &items, int last_played, PlaylistGeneratorPtr dynamic);
signals:
void ExitFinished();

View File

@@ -493,3 +493,40 @@ void SongSourceDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
painter->drawPixmap(draw_rect, pixmap);
}
RatingItemDelegate::RatingItemDelegate(QObject *parent) : PlaylistDelegateBase(parent) {}
void RatingItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &idx) const {
// Draw the background
option.widget->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, option.widget);
// Don't draw anything else if the user can't set the rating of this item
if (!idx.data(Playlist::Role_CanSetRating).toBool()) return;
const bool hover = mouse_over_index_.isValid() && (mouse_over_index_ == idx || (selected_indexes_.contains(mouse_over_index_) && selected_indexes_.contains(idx)));
const double rating = (hover ? RatingPainter::RatingForPos(mouse_over_pos_, option.rect) : idx.data().toDouble());
painter_.Paint(painter, option.rect, rating);
}
QSize RatingItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &idx) const {
QSize size = PlaylistDelegateBase::sizeHint(option, idx);
size.setWidth(size.height() * RatingPainter::kStarCount);
return size;
}
QString RatingItemDelegate::displayText(const QVariant &value, const QLocale&) const {
if (value.isNull() || value.toDouble() <= 0) return QString();
// Round to the nearest 0.5
const double rating = float(int(value.toDouble() * RatingPainter::kStarCount * 2 + 0.5)) / 2;
return QString::number(rating, 'f', 1);
}

View File

@@ -51,6 +51,7 @@
#include "playlist.h"
#include "core/song.h"
#include "widgets/ratingwidget.h"
class CollectionBackend;
class Player;
@@ -185,4 +186,29 @@ class SongSourceDelegate : public PlaylistDelegateBase {
mutable QPixmapCache pixmap_cache_;
};
class RatingItemDelegate : public PlaylistDelegateBase {
public:
RatingItemDelegate(QObject *parent);
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &idx) const override;
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &idx) const override;
QString displayText(const QVariant &value, const QLocale &locale) const override;
void set_mouse_over(const QModelIndex &idx, const QModelIndexList &selected_indexes, const QPoint &pos) {
mouse_over_index_ = idx;
selected_indexes_ = selected_indexes;
mouse_over_pos_ = pos;
}
void set_mouse_out() { mouse_over_index_ = QModelIndex(); }
bool is_mouse_over() const { return mouse_over_index_.isValid(); }
QModelIndex mouse_over_index() const { return mouse_over_index_; }
private:
RatingPainter painter_;
QModelIndex mouse_over_index_;
QPoint mouse_over_pos_;
QModelIndexList selected_indexes_;
};
#endif // PLAYLISTDELEGATES_H

View File

@@ -29,6 +29,7 @@
#include <QMenu>
#include <QAction>
#include <QActionGroup>
#include <QSettings>
#include <QEvent>
#include <QContextMenuEvent>
#include <QEnterEvent>
@@ -36,25 +37,37 @@
#include "playlistheader.h"
#include "playlistview.h"
#include "settings/playlistsettingspage.h"
PlaylistHeader::PlaylistHeader(Qt::Orientation orientation, PlaylistView *view, QWidget *parent)
: StretchHeaderView(orientation, parent),
view_(view),
menu_(new QMenu(this)) {
menu_(new QMenu(this)),
action_hide_(nullptr),
action_reset_(nullptr),
action_stretch_(nullptr),
action_rating_lock_(nullptr),
action_align_left_(nullptr),
action_align_center_(nullptr),
action_align_right_(nullptr)
{
hide_action_ = menu_->addAction(tr("&Hide..."), this, SLOT(HideCurrent()));
stretch_action_ = menu_->addAction(tr("&Stretch columns to fit window"), this, SLOT(ToggleStretchEnabled()));
reset_action_ = menu_->addAction(tr("&Reset columns to default"), this, SLOT(ResetColumns()));
action_hide_ = menu_->addAction(tr("&Hide..."), this, SLOT(HideCurrent()));
action_stretch_ = menu_->addAction(tr("&Stretch columns to fit window"), this, SLOT(ToggleStretchEnabled()));
action_reset_ = menu_->addAction(tr("&Reset columns to default"), this, SLOT(ResetColumns()));
action_rating_lock_ = menu_->addAction(tr("&Lock rating"), this, SLOT(ToggleRatingEditStatus()));
action_rating_lock_->setCheckable(true);
menu_->addSeparator();
QMenu *align_menu = new QMenu(tr("&Align text"), this);
QActionGroup *align_group = new QActionGroup(this);
align_left_action_ = new QAction(tr("&Left"), align_group);
align_center_action_ = new QAction(tr("&Center"), align_group);
align_right_action_ = new QAction(tr("&Right"), align_group);
action_align_left_ = new QAction(tr("&Left"), align_group);
action_align_center_ = new QAction(tr("&Center"), align_group);
action_align_right_ = new QAction(tr("&Right"), align_group);
align_left_action_->setCheckable(true);
align_center_action_->setCheckable(true);
align_right_action_->setCheckable(true);
action_align_left_->setCheckable(true);
action_align_center_->setCheckable(true);
action_align_right_->setCheckable(true);
align_menu->addActions(align_group->actions());
connect(align_group, SIGNAL(triggered(QAction*)), SLOT(SetColumnAlignment(QAction*)));
@@ -62,10 +75,15 @@ PlaylistHeader::PlaylistHeader(Qt::Orientation orientation, PlaylistView *view,
menu_->addMenu(align_menu);
menu_->addSeparator();
stretch_action_->setCheckable(true);
stretch_action_->setChecked(is_stretch_enabled());
action_stretch_->setCheckable(true);
action_stretch_->setChecked(is_stretch_enabled());
connect(this, SIGNAL(StretchEnabledChanged(bool)), stretch_action_, SLOT(setChecked(bool)));
connect(this, SIGNAL(StretchEnabledChanged(bool)), action_stretch_, SLOT(setChecked(bool)));
QSettings s;
s.beginGroup(PlaylistSettingsPage::kSettingsGroup);
action_rating_lock_->setChecked(s.value("rating_locked", false).toBool());
s.endGroup();
}
@@ -74,17 +92,21 @@ void PlaylistHeader::contextMenuEvent(QContextMenuEvent *e) {
menu_section_ = logicalIndexAt(e->pos());
if (menu_section_ == -1 || (menu_section_ == logicalIndex(0) && logicalIndex(1) == -1))
hide_action_->setVisible(false);
action_hide_->setVisible(false);
else {
hide_action_->setVisible(true);
action_hide_->setVisible(true);
QString title(model()->headerData(menu_section_, Qt::Horizontal).toString());
hide_action_->setText(tr("&Hide %1").arg(title));
action_hide_->setText(tr("&Hide %1").arg(title));
Qt::Alignment alignment = view_->column_alignment(menu_section_);
if (alignment & Qt::AlignLeft) align_left_action_->setChecked(true);
else if (alignment & Qt::AlignHCenter) align_center_action_->setChecked(true);
else if (alignment & Qt::AlignRight) align_right_action_->setChecked(true);
if (alignment & Qt::AlignLeft) action_align_left_->setChecked(true);
else if (alignment & Qt::AlignHCenter) action_align_center_->setChecked(true);
else if (alignment & Qt::AlignRight) action_align_right_->setChecked(true);
// Show rating lock action only for ratings section
action_rating_lock_->setVisible(menu_section_ == Playlist::Column_Rating);
}
qDeleteAll(show_actions_);
@@ -126,9 +148,9 @@ void PlaylistHeader::SetColumnAlignment(QAction *action) {
Qt::Alignment alignment = Qt::AlignVCenter;
if (action == align_left_action_) alignment |= Qt::AlignLeft;
if (action == align_center_action_) alignment |= Qt::AlignHCenter;
if (action == align_right_action_) alignment |= Qt::AlignRight;
if (action == action_align_left_) alignment |= Qt::AlignLeft;
if (action == action_align_center_) alignment |= Qt::AlignHCenter;
if (action == action_align_right_) alignment |= Qt::AlignRight;
view_->SetColumnAlignment(menu_section_, alignment);
@@ -150,3 +172,8 @@ void PlaylistHeader::enterEvent(QEvent*) {
void PlaylistHeader::ResetColumns() {
view_->ResetHeaderState();
}
void PlaylistHeader::ToggleRatingEditStatus() {
emit SectionRatingLockStatusChanged(action_rating_lock_->isChecked());
}

View File

@@ -54,12 +54,14 @@ class PlaylistHeader : public StretchHeaderView {
signals:
void SectionVisibilityChanged(int logical, bool visible);
void MouseEntered();
void SectionRatingLockStatusChanged(bool);
private slots:
void HideCurrent();
void ToggleVisible(int section);
void ResetColumns();
void SetColumnAlignment(QAction *action);
void ToggleRatingEditStatus();
private:
void AddColumnAction(int index);
@@ -69,12 +71,13 @@ class PlaylistHeader : public StretchHeaderView {
int menu_section_;
QMenu *menu_;
QAction *hide_action_;
QAction *stretch_action_;
QAction *reset_action_;
QAction *align_left_action_;
QAction *align_center_action_;
QAction *align_right_action_;
QAction *action_hide_;
QAction *action_reset_;
QAction *action_stretch_;
QAction *action_rating_lock_;
QAction *action_align_left_;
QAction *action_align_center_;
QAction *action_align_right_;
QList<QAction*> show_actions_;
};

View File

@@ -96,6 +96,7 @@ void PlaylistManager::Init(CollectionBackend *collection_backend, PlaylistBacken
connect(collection_backend_, SIGNAL(SongsDiscovered(SongList)), SLOT(SongsDiscovered(SongList)));
connect(collection_backend_, SIGNAL(SongsStatisticsChanged(SongList)), SLOT(SongsDiscovered(SongList)));
connect(collection_backend_, SIGNAL(SongsRatingChanged(SongList)), SLOT(SongsDiscovered(SongList)));
for (const PlaylistBackend::Playlist &p : playlist_backend->GetAllOpenPlaylists()) {
++playlists_loading_;
@@ -602,3 +603,26 @@ void PlaylistManager::SetCurrentOrOpen(const int id) {
bool PlaylistManager::IsPlaylistOpen(const int id) {
return playlists_.contains(id);
}
void PlaylistManager::PlaySmartPlaylist(PlaylistGeneratorPtr generator, bool as_new, bool clear) {
if (as_new) {
New(generator->name());
}
if (clear) {
current()->Clear();
}
current()->InsertSmartPlaylist(generator);
}
void PlaylistManager::RateCurrentSong(const double rating) {
active()->RateSong(active()->current_index(), rating);
}
void PlaylistManager::RateCurrentSong(const int rating) {
RateCurrentSong(rating / 5.0);
}

View File

@@ -34,6 +34,7 @@
#include "core/song.h"
#include "playlist.h"
#include "smartplaylists/playlistgenerator.h"
class QModelIndex;
@@ -76,6 +77,8 @@ class PlaylistManagerInterface : public QObject {
virtual PlaylistParser *parser() const = 0;
virtual PlaylistContainer *playlist_container() const = 0;
virtual void PlaySmartPlaylist(PlaylistGeneratorPtr generator, const bool as_new, const bool clear) = 0;
public slots:
virtual void New(const QString &name, const SongList& songs = SongList(), const QString &special_type = QString()) = 0;
virtual void Load(const QString &filename) = 0;
@@ -103,6 +106,11 @@ class PlaylistManagerInterface : public QObject {
virtual void SetActivePaused() = 0;
virtual void SetActiveStopped() = 0;
// Rate current song using 0.0 - 1.0 scale.
virtual void RateCurrentSong(const double rating) = 0;
// Rate current song using 0 - 5 scale.
virtual void RateCurrentSong(const int rating) = 0;
signals:
void PlaylistManagerInitialized();
void AllPlaylistsLoaded();
@@ -196,7 +204,6 @@ class PlaylistManager : public PlaylistManagerInterface {
void ShuffleCurrent() override;
void RemoveDuplicatesCurrent() override;
void RemoveUnavailableCurrent() override;
//void SetActiveStreamMetadata(const QUrl& url, const Song& song);
void SongChangeRequestProcessed(const QUrl& url, const bool valid) override;
@@ -207,6 +214,13 @@ class PlaylistManager : public PlaylistManagerInterface {
// Remove the current playing song
void RemoveCurrentSong();
void PlaySmartPlaylist(PlaylistGeneratorPtr generator, const bool as_new, const bool clear) override;
// Rate current song using 0.0 - 1.0 scale.
void RateCurrentSong(const double rating) override;
// Rate current song using 0 - 5 scale.
void RateCurrentSong(const int rating) override;
private slots:
void SetActivePlaying() override;
void SetActivePaused() override;

View File

@@ -47,7 +47,8 @@ PlaylistSequence::PlaylistSequence(QWidget *parent, SettingsProvider *settings)
shuffle_menu_(new QMenu(this)),
loading_(false),
repeat_mode_(Repeat_Off),
shuffle_mode_(Shuffle_Off)
shuffle_mode_(Shuffle_Off),
dynamic_(false)
{
ui_->setupUi(this);
@@ -161,7 +162,7 @@ void PlaylistSequence::ShuffleActionTriggered(QAction *action) {
}
void PlaylistSequence::SetRepeatMode(RepeatMode mode) {
void PlaylistSequence::SetRepeatMode(const RepeatMode mode) {
ui_->repeat->setChecked(mode != Repeat_Off);
@@ -184,7 +185,7 @@ void PlaylistSequence::SetRepeatMode(RepeatMode mode) {
}
void PlaylistSequence::SetShuffleMode(ShuffleMode mode) {
void PlaylistSequence::SetShuffleMode(const ShuffleMode mode) {
ui_->shuffle->setChecked(mode != Shuffle_Off);
@@ -204,12 +205,23 @@ void PlaylistSequence::SetShuffleMode(ShuffleMode mode) {
}
void PlaylistSequence::SetUsingDynamicPlaylist(const bool dynamic) {
dynamic_ = dynamic;
const QString not_available(tr("Not available while using a dynamic playlist"));
setEnabled(!dynamic);
ui_->shuffle->setToolTip(dynamic ? not_available : tr("Shuffle"));
ui_->repeat->setToolTip(dynamic ? not_available : tr("Repeat"));
}
PlaylistSequence::ShuffleMode PlaylistSequence::shuffle_mode() const {
return shuffle_mode_;
return dynamic_ ? Shuffle_Off : shuffle_mode_;
}
PlaylistSequence::RepeatMode PlaylistSequence::repeat_mode() const {
return repeat_mode_;
return dynamic_ ? Repeat_Off : repeat_mode_;
}
//called from global shortcut

View File

@@ -68,14 +68,15 @@ class PlaylistSequence : public QWidget {
QMenu *shuffle_menu() const { return shuffle_menu_; }
public slots:
void SetRepeatMode(PlaylistSequence::RepeatMode mode);
void SetShuffleMode(PlaylistSequence::ShuffleMode mode);
void SetRepeatMode(const PlaylistSequence::RepeatMode mode);
void SetShuffleMode(const PlaylistSequence::ShuffleMode mode);
void CycleShuffleMode();
void CycleRepeatMode();
void SetUsingDynamicPlaylist(const bool dynamic);
signals:
void RepeatModeChanged(PlaylistSequence::RepeatMode mode);
void ShuffleModeChanged(PlaylistSequence::ShuffleMode mode);
void RepeatModeChanged(const PlaylistSequence::RepeatMode mode);
void ShuffleModeChanged(const PlaylistSequence::ShuffleMode mode);
private slots:
void RepeatActionTriggered(QAction *);
@@ -97,7 +98,7 @@ class PlaylistSequence : public QWidget {
bool loading_;
RepeatMode repeat_mode_;
ShuffleMode shuffle_mode_;
bool dynamic_;
};
#endif // PLAYLISTSEQUENCE_H

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();
}
}

View File

@@ -72,6 +72,8 @@ class QTimerEvent;
class Application;
class CollectionBackend;
class PlaylistHeader;
class DynamicPlaylistControls;
class RatingItemDelegate;
// This proxy style works around a bug/feature introduced in Qt 4.7's QGtkStyle
// that uses Gtk to paint row backgrounds, ignoring any custom brush or palette the caller set in the QStyleOption.
@@ -148,6 +150,7 @@ class PlaylistView : public QTreeView {
void dropEvent(QDropEvent *event) override;
bool eventFilter(QObject *object, QEvent *event) override;
void focusInEvent(QFocusEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
// QTreeView
void drawTree(QPainter *painter, const QRegion &region) const;
@@ -177,6 +180,10 @@ class PlaylistView : public QTreeView {
void Stopped();
void SongChanged(const Song &song);
void AlbumCoverLoaded(const Song &song, AlbumCoverLoaderResult result = AlbumCoverLoaderResult());
void DynamicModeChanged(const bool dynamic);
void SetRatingLockStatus(const bool state);
void RatingHoverIn(const QModelIndex &idx, const QPoint &pos);
void RatingHoverOut();
private:
void LoadHeaderState();
@@ -206,6 +213,8 @@ class PlaylistView : public QTreeView {
QModelIndex NextEditableIndex(const QModelIndex &current);
QModelIndex PrevEditableIndex(const QModelIndex &current);
void RepositionDynamicControls();
Application *app_;
PlaylistProxyStyle *style_;
Playlist *playlist_;
@@ -275,11 +284,16 @@ class PlaylistView : public QTreeView {
int drop_indicator_row_;
bool drag_over_;
int header_state_version_;
QByteArray header_state_;
ColumnAlignmentMap column_alignment_;
bool rating_locked_;
Song song_playing_;
DynamicPlaylistControls *dynamic_controls_;
RatingItemDelegate *rating_delegate_;
};
#endif // PLAYLISTVIEW_H