31
src/playlist/dynamicplaylistcontrols.cpp
Normal file
31
src/playlist/dynamicplaylistcontrols.cpp
Normal 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_; }
|
||||
41
src/playlist/dynamicplaylistcontrols.h
Normal file
41
src/playlist/dynamicplaylistcontrols.h
Normal 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
|
||||
99
src/playlist/dynamicplaylistcontrols.ui
Normal file
99
src/playlist/dynamicplaylistcontrols.ui
Normal 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>
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 ®ion) 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 ¤t);
|
||||
QModelIndex PrevEditableIndex(const QModelIndex ¤t);
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user