Merge pull request #4489 from gavinhoward/playlist_save

Playlist Save Preferences 2
This commit is contained in:
John Maguire 2014-08-19 10:59:32 +02:00
commit 96f7fb31d1
12 changed files with 283 additions and 63 deletions

View File

@ -89,6 +89,10 @@ const QRgb Playlist::kDynamicHistoryColor = qRgb(0x80, 0x80, 0x80);
const char* Playlist::kSettingsGroup = "Playlist";
const char* Playlist::kPathType = "path_type";
const char* Playlist::kWriteMetadata = "write_metadata";
const char* Playlist::kQuickChangeMenu = "quick_change_menu";
const int Playlist::kUndoStackSize = 20;
const int Playlist::kUndoItemLimit = 500;
@ -1097,9 +1101,8 @@ void Playlist::InsertInternetItems(const InternetModel* model,
switch (item.data(InternetModel::Role_PlayBehaviour).toInt()) {
case InternetModel::PlayBehaviour_SingleItem:
playlist_items << shared_ptr<PlaylistItem>(new InternetPlaylistItem(
model->ServiceForIndex(item),
item.data(InternetModel::Role_SongMetadata)
.value<Song>()));
model->ServiceForIndex(item),
item.data(InternetModel::Role_SongMetadata).value<Song>()));
break;
case InternetModel::PlayBehaviour_UseSongLoader:
@ -1122,7 +1125,7 @@ void Playlist::InsertInternetItems(InternetService* service,
PlaylistItemList playlist_items;
for (const Song& song : songs) {
playlist_items << shared_ptr<PlaylistItem>(
new InternetPlaylistItem(service, song));
new InternetPlaylistItem(service, song));
}
InsertItems(playlist_items, pos, play_now, enqueue);
@ -1402,10 +1405,10 @@ void Playlist::ReOrderWithoutUndo(const PlaylistItemList& new_items) {
new_rows[new_items[i].get()] = i;
}
for (const QModelIndex& idx: persistentIndexList()) {
for (const QModelIndex& idx : persistentIndexList()) {
const PlaylistItem* item = old_items[idx.row()].get();
changePersistentIndex(
idx, index(new_rows[item], idx.column(), idx.parent()));
changePersistentIndex(idx,
index(new_rows[item], idx.column(), idx.parent()));
}
layoutChanged();

View File

@ -134,6 +134,12 @@ class Playlist : public QAbstractListModel {
LastFM_Queued, // Track added to the queue for scrobbling
};
enum Path {
Path_Automatic = 0, // Automatically select path type
Path_Absolute, // Always use absolute paths
Path_Relative, // Always use relative paths
};
static const char* kCddaMimeType;
static const char* kRowsMimetype;
static const char* kPlayNowMimetype;
@ -146,6 +152,10 @@ class Playlist : public QAbstractListModel {
static const char* kSettingsGroup;
static const char* kPathType;
static const char* kWriteMetadata;
static const char* kQuickChangeMenu;
static const int kUndoStackSize;
static const int kUndoItemLimit;

View File

@ -51,6 +51,13 @@ PlaylistContainer::PlaylistContainer(QWidget* parent)
filter_timer_(new QTimer(this)) {
ui_->setupUi(this);
ui_->file_path_box->addItem("Automatic");
ui_->file_path_box->addItem("Absolute");
ui_->file_path_box->addItem("Relative");
connect(ui_->file_path_box, SIGNAL(currentIndexChanged(int)),
SLOT(PathSettingChanged(int)));
no_matches_label_ = new QLabel(ui_->playlist);
no_matches_label_->setAlignment(Qt::AlignTop | Qt::AlignHCenter);
no_matches_label_->setAttribute(Qt::WA_TransparentForMouseEvents);
@ -75,6 +82,8 @@ PlaylistContainer::PlaylistContainer(QWidget* parent)
settings_.beginGroup(kSettingsGroup);
ReloadSettings();
// Tab bar
ui_->tab_bar->setExpanding(false);
ui_->tab_bar->setMovable(true);
@ -101,6 +110,28 @@ PlaylistContainer::PlaylistContainer(QWidget* parent)
PlaylistContainer::~PlaylistContainer() { delete ui_; }
void PlaylistContainer::ReloadSettings() {
bool show_menu = settings_.value(Playlist::kQuickChangeMenu, true).toBool();
ui_->line->setVisible(show_menu);
ui_->file_path_label->setVisible(show_menu);
ui_->file_path_box->setVisible(show_menu);
int value =
settings_.value(Playlist::kPathType, Playlist::Path_Automatic).toInt();
Playlist::Path path = static_cast<Playlist::Path>(value);
switch (path) {
case Playlist::Path_Automatic:
ui_->file_path_box->setCurrentIndex(0);
break;
case Playlist::Path_Absolute:
ui_->file_path_box->setCurrentIndex(1);
break;
case Playlist::Path_Relative:
ui_->file_path_box->setCurrentIndex(2);
break;
}
}
PlaylistView* PlaylistContainer::view() const { return ui_->playlist; }
void PlaylistContainer::SetActions(QAction* new_playlist,
@ -361,14 +392,15 @@ void PlaylistContainer::UpdateNoMatchesLabel() {
QString text;
if (has_rows && !has_results) {
if (ui_->filter->text().trimmed().compare("the answer to life the universe "
"and everything",
Qt::CaseInsensitive) == 0) {
if (ui_->filter->text().trimmed().compare(
"the answer to life the universe "
"and everything",
Qt::CaseInsensitive) == 0) {
text = "42";
} else {
text =
tr("No matches found. Clear the search box to show the whole playlist "
"again.");
text = tr(
"No matches found. Clear the search box to show the whole playlist "
"again.");
}
}
@ -438,3 +470,7 @@ bool PlaylistContainer::eventFilter(QObject* objectWatched, QEvent* event) {
}
return QWidget::eventFilter(objectWatched, event);
}
void PlaylistContainer::PathSettingChanged(int index) {
settings_.setValue(Playlist::kPathType, index);
}

View File

@ -21,6 +21,8 @@
#include <QWidget>
#include <QSettings>
#include "playlist.h"
class Ui_PlaylistContainer;
class LineEditInterface;
@ -46,6 +48,8 @@ class PlaylistContainer : public QWidget {
QAction* previous_playlist);
void SetManager(PlaylistManager* manager);
void ReloadSettings();
PlaylistView* view() const;
bool eventFilter(QObject* objectWatched, QEvent* event);
@ -90,6 +94,8 @@ signals:
void UpdateNoMatchesLabel();
void PathSettingChanged(int index);
private:
void UpdateActiveIcon(const QIcon& icon);
void RepositionNoMatchesLabel(bool force = false);

View File

@ -24,7 +24,16 @@
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<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>
@ -36,7 +45,16 @@
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<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>
@ -104,6 +122,36 @@
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="file_path_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>File Paths:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="file_path_box">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">

View File

@ -105,15 +105,23 @@ bool M3UParser::ParseMetadata(const QString& line,
void M3UParser::Save(const SongList& songs, QIODevice* device,
const QDir& dir) const {
device->write("#EXTM3U\n");
QSettings s;
s.beginGroup(Playlist::kSettingsGroup);
bool writeMetadata = s.value(Playlist::kWriteMetadata, true).toBool();
s.endGroup();
for (const Song& song : songs) {
if (song.url().isEmpty()) {
continue;
}
QString meta = QString("#EXTINF:%1,%2 - %3\n")
.arg(song.length_nanosec() / kNsecPerSec)
.arg(song.artist())
.arg(song.title());
device->write(meta.toUtf8());
if (writeMetadata) {
QString meta = QString("#EXTINF:%1,%2 - %3\n")
.arg(song.length_nanosec() / kNsecPerSec)
.arg(song.artist())
.arg(song.title());
device->write(meta.toUtf8());
}
device->write(URLOrRelativeFilename(song.url(), dir).toUtf8());
device->write("\n");
}

View File

@ -20,6 +20,7 @@
#include "library/librarybackend.h"
#include "library/libraryquery.h"
#include "library/sqlrow.h"
#include "playlist/playlist.h"
#include <QUrl>
@ -89,11 +90,19 @@ QString ParserBase::URLOrRelativeFilename(const QUrl& url,
const QDir& dir) const {
if (url.scheme() != "file") return url.toString();
QSettings s;
s.beginGroup(Playlist::kSettingsGroup);
int p = s.value("path_type", Playlist::Path_Automatic).toInt();
const Playlist::Path path = static_cast<Playlist::Path>(p);
s.endGroup();
const QString filename = url.toLocalFile();
if (QDir::isAbsolutePath(filename)) {
if (path != Playlist::Path_Absolute && QDir::isAbsolutePath(filename)) {
const QString relative = dir.relativeFilePath(filename);
if (!relative.startsWith("../")) return relative;
if (!relative.startsWith("../") || path == Playlist::Path_Relative)
return relative;
}
return filename;
}

View File

@ -22,6 +22,7 @@
#include <QDir>
#include "core/song.h"
#include "playlist/playlist.h"
class LibraryBackendInterface;

View File

@ -111,53 +111,55 @@ void XSPFParser::Save(const SongList& songs, QIODevice* device,
writer.writeAttribute("version", "1");
writer.writeDefaultNamespace("http://xspf.org/ns/0/");
QSettings s;
s.beginGroup(Playlist::kSettingsGroup);
bool writeMetadata = s.value("write_metadata", true).toBool();
s.endGroup();
StreamElement tracklist("trackList", &writer);
for (const Song& song : songs) {
QString filename_or_url;
if (song.url().scheme() == "file") {
// Make the filename relative to the directory we're saving the playlist.
filename_or_url = dir.relativeFilePath(
QFileInfo(song.url().toLocalFile()).absoluteFilePath());
} else {
filename_or_url = song.url().toEncoded();
}
QString filename_or_url = URLOrRelativeFilename(song.url(), dir).toUtf8();
StreamElement track("track", &writer);
writer.writeTextElement("location", filename_or_url);
writer.writeTextElement("title", song.title());
if (!song.artist().isEmpty()) {
writer.writeTextElement("creator", song.artist());
}
if (!song.album().isEmpty()) {
writer.writeTextElement("album", song.album());
}
if (song.length_nanosec() != -1) {
writer.writeTextElement(
"duration", QString::number(song.length_nanosec() / kNsecPerMsec));
}
QString art =
song.art_manual().isEmpty() ? song.art_automatic() : song.art_manual();
// Ignore images that are in our resource bundle.
if (!art.startsWith(":") && !art.isEmpty()) {
QString art_filename;
if (!art.contains("://")) {
art_filename = art;
} else if (QUrl(art).scheme() == "file") {
art_filename = QUrl(art).toLocalFile();
if (writeMetadata) {
writer.writeTextElement("title", song.title());
if (!song.artist().isEmpty()) {
writer.writeTextElement("creator", song.artist());
}
if (!song.album().isEmpty()) {
writer.writeTextElement("album", song.album());
}
if (song.length_nanosec() != -1) {
writer.writeTextElement(
"duration", QString::number(song.length_nanosec() / kNsecPerMsec));
}
if (!art_filename.isEmpty()) {
// Make this filename relative to the directory we're saving the
// playlist.
art_filename = dir.relativeFilePath(
QFileInfo(art_filename).absoluteFilePath());
} else {
// Just use whatever URL was in the Song.
art_filename = art;
}
QString art = song.art_manual().isEmpty() ? song.art_automatic()
: song.art_manual();
// Ignore images that are in our resource bundle.
if (!art.startsWith(":") && !art.isEmpty()) {
QString art_filename;
if (!art.contains("://")) {
art_filename = art;
} else if (QUrl(art).scheme() == "file") {
art_filename = QUrl(art).toLocalFile();
}
writer.writeTextElement("image", art_filename);
if (!art_filename.isEmpty() && !(art_filename == "(embedded)")) {
// Make this filename relative to the directory we're saving the
// playlist.
QUrl url = QUrl(art_filename);
url.setScheme("file"); // Need to explicitly set this.
art_filename = URLOrRelativeFilename(url, dir).toUtf8();
} else {
// Just use whatever URL was in the Song.
art_filename = art;
}
writer.writeTextElement("image", art_filename);
}
}
}
writer.writeEndDocument();

View File

@ -133,6 +133,24 @@ void BehaviourSettingsPage::Load() {
s.value("greyoutdeleted", false).toBool());
ui_->b_click_edit_inline_->setChecked(
s.value("click_edit_inline", true).toBool());
Playlist::Path path = Playlist::Path(
s.value(Playlist::kPathType, Playlist::Path_Automatic).toInt());
switch (path) {
case Playlist::Path_Automatic:
ui_->b_automatic_path->setChecked(true);
break;
case Playlist::Path_Absolute:
ui_->b_absolute_path->setChecked(true);
break;
case Playlist::Path_Relative:
ui_->b_relative_path->setChecked(true);
break;
}
ui_->b_write_metadata->setChecked(
s.value(Playlist::kWriteMetadata, true).toBool());
ui_->b_quickchange_menu->setChecked(
s.value(Playlist::kQuickChangeMenu, false).toBool());
s.endGroup();
s.beginGroup(PlaylistTabBar::kSettingsGroup);
@ -162,6 +180,15 @@ void BehaviourSettingsPage::Save() {
MainWindow::PlayBehaviour menu_playmode = MainWindow::PlayBehaviour(
ui_->menu_playmode->itemData(ui_->menu_playmode->currentIndex()).toInt());
Playlist::Path path = Playlist::Path_Automatic;
if (ui_->b_automatic_path->isChecked()) {
path = Playlist::Path_Automatic;
} else if (ui_->b_absolute_path->isChecked()) {
path = Playlist::Path_Absolute;
} else if (ui_->b_relative_path->isChecked()) {
path = Playlist::Path_Relative;
}
s.beginGroup(MainWindow::kSettingsGroup);
s.setValue("showtray", ui_->b_show_tray_icon_->isChecked());
s.setValue("keeprunning", ui_->b_keep_running_->isChecked());
@ -182,6 +209,9 @@ void BehaviourSettingsPage::Save() {
s.beginGroup(Playlist::kSettingsGroup);
s.setValue("greyoutdeleted", ui_->b_grey_out_deleted_->isChecked());
s.setValue("click_edit_inline", ui_->b_click_edit_inline_->isChecked());
s.setValue(Playlist::kPathType, static_cast<int>(path));
s.setValue(Playlist::kWriteMetadata, ui_->b_write_metadata->isChecked());
s.setValue(Playlist::kQuickChangeMenu, ui_->b_quickchange_menu->isChecked());
s.endGroup();
s.beginGroup(PlaylistTabBar::kSettingsGroup);

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>516</width>
<height>561</height>
<height>728</height>
</rect>
</property>
<property name="windowTitle">
@ -235,6 +235,72 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_9">
<property name="title">
<string>Playlist file paths should be</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QRadioButton" name="b_automatic_path">
<property name="text">
<string>Automatic</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="b_absolute_path">
<property name="text">
<string>Absolute</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="b_relative_path">
<property name="text">
<string>Relative</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_7">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="b_quickchange_menu">
<property name="text">
<string>Show Quick Change Menu</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="b_write_metadata">
<property name="text">
<string>Write Metadata</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">

View File

@ -161,8 +161,8 @@ const char* MainWindow::kSettingsGroup = "MainWindow";
const char* MainWindow::kAllFilesFilterSpec = QT_TR_NOOP("All Files (*)");
namespace {
const int kTrackSliderUpdateTimeMs = 40;
const int kTrackPositionUpdateTimeMs = 1000;
const int kTrackSliderUpdateTimeMs = 40;
const int kTrackPositionUpdateTimeMs = 1000;
}
MainWindow::MainWindow(Application* app, SystemTrayIcon* tray_icon, OSD* osd,
@ -976,6 +976,7 @@ void MainWindow::ReloadAllSettings() {
library_view_->ReloadSettings();
song_info_view_->ReloadSettings();
app_->player()->engine()->ReloadSettings();
ui_->playlist->ReloadSettings();
ui_->playlist->view()->ReloadSettings();
app_->internet_model()->ReloadSettings();
#ifdef HAVE_WIIMOTEDEV