Initial commit.

This commit is contained in:
Jonas Kvinge
2018-02-27 18:06:05 +01:00
parent 85d9664df7
commit b2b1ba7abe
1393 changed files with 177311 additions and 1 deletions

119
src/dialogs/about.cpp Normal file
View File

@@ -0,0 +1,119 @@
/*
* Strawberry Music Player
* Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "about.h"
#include "ui_about.h"
#include <QCoreApplication>
#include <QPushButton>
About::About(QWidget *parent):QDialog(parent) {
authors_ \
<< Person("Jonas Kvinge", "jonas@strawbs.net");
clementine_authors_
<< Person("David Sansome", "me@davidsansome.com")
<< Person("John Maguire", "john.maguire@gmail.com")
<< Person(QString::fromUtf8("Paweł Bara"), "keirangtp@gmail.com")
<< Person("Arnaud Bienner", "arnaud.bienner@gmail.com");
thanks_to_ \
<< Person("Mark Kretschmann", "kretschmann@kde.org")
<< Person("Max Howell", "max.howell@methylblue.com")
<< Person(QString::fromUtf8("Bartłomiej Burdukiewicz"), "dev.strikeu@gmail.com")
<< Person("Jakub Stachowski", "qbast@go2.pl")
<< Person("Paul Cifarelli", "paul@cifarelli.net")
<< Person("Felipe Rivera", "liebremx@users.sourceforge.net")
<< Person("Alexander Peitz")
<< Person("Artur Rona", "artur.rona@gmail.com")
<< Person("Andreas Muttscheller", "asfa194@gmail.com");
QString Title = "";
ui_.setupUi(this);
setWindowFlags(this->windowFlags()|Qt::WindowStaysOnTopHint);
setWindowTitle(tr("About Strawberry"));
Title = QString("About Strawberry");
ui_.title->setText(Title);
QFont title_font;
title_font.setBold(true);
title_font.setPointSize(title_font.pointSize() + 4);
ui_.title->setFont(title_font);
ui_.text->setWordWrap(true);
ui_.text->setText(MakeHtml());
ui_.buttonBox->button(QDialogButtonBox::Close)->setShortcut(QKeySequence::Close);
}
QString About::MakeHtml() const {
QString ret = "";
ret = tr("<p>Version %1</p>").arg(QCoreApplication::applicationVersion());
ret += tr("<p>");
ret += tr("Strawberry is a fork of Clementine created in 2013, it's written in C++ and Qt5. So far it works on Linux, it is currently untested on Mac OS X and Windows.<br />");
ret += tr("The main goal was to create a player for playing local music files that looked a bit more like Amarok 1.4.");
ret += tr("</p>");
//ret += tr("<p><a href=\"%1\">%2</a></p><p><b>%3:</b>").arg(kUrl, kUrl, tr("Authors"));
ret += QString("<p><b>%1</b>").arg(tr("Authors"));
for (const Person &person : authors_) {
ret += "<br />" + MakeHtml(person);
}
ret += QString("</p><p><b>%3:</b>").arg(tr("Clementine Authors"));
for (const Person &person : clementine_authors_) {
ret += "<br />" + MakeHtml(person);
}
ret += QString("</p><p><b>%3:</b>").arg(tr("Thanks to"));
for (const Person &person : thanks_to_) {
ret += "<br />" + MakeHtml(person);
}
ret += QString("<br />%1</p>").arg(tr("...and all the Amarok and Clementine contributors"));
return ret;
}
QString About::MakeHtml(const Person &person) const {
if (person.email.isNull())
return person.name;
else
return QString("%1 &lt;<a href=\"mailto:%2\">%3</a>&gt;").arg(person.name, person.email, person.email);
}

57
src/dialogs/about.h Normal file
View File

@@ -0,0 +1,57 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ABOUT_H
#define ABOUT_H
#include "config.h"
#include <QDialog>
#include "ui_about.h"
class About : public QDialog {
Q_OBJECT
public:
About(QWidget *parent = nullptr);
struct Person {
Person(const QString &n, const QString &e = QString()) : name(n), email(e) {}
bool operator<(const Person& other) const { return name < other.name; }
QString name;
QString email;
};
private:
QString MakeHtml() const;
QString MakeHtml(const Person& person) const;
private:
Ui::About ui_;
QList<Person> authors_;
QList<Person> clementine_authors_;
QList<Person> thanks_to_;
};
#endif // ABOUT_H

186
src/dialogs/about.ui Normal file
View File

@@ -0,0 +1,186 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>About</class>
<widget class="QDialog" name="About">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>650</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>600</width>
<height>650</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>600</width>
<height>650</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="windowIcon">
<iconset resource="../../data/data.qrc">
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
</property>
<property name="styleSheet">
<string notr="true">#line {
color: lightgrey;
}
</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout_logo">
<item>
<widget class="QLabel" name="label">
<property name="pixmap">
<pixmap resource="../../data/data.qrc">:/icons/64x64/strawberry.png</pixmap>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="minimumSize">
<size>
<width>2</width>
<height>2</height>
</size>
</property>
<property name="baseSize">
<size>
<width>1</width>
<height>1</height>
</size>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_text">
<item>
<widget class="QLabel" name="title">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>20</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="text">
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<resources>
<include location="../../data/data.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>About</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>257</x>
<y>460</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>About</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>325</x>
<y>460</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

71
src/dialogs/console.cpp Normal file
View File

@@ -0,0 +1,71 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QFont>
#include <QScrollBar>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlRecord>
#include "console.h"
#include "core/application.h"
#include "core/database.h"
Console::Console(Application *app, QWidget *parent) : QDialog(parent), app_(app) {
ui_.setupUi(this);
connect(ui_.run, SIGNAL(clicked()), SLOT(RunQuery()));
QFont font("Monospace");
font.setStyleHint(QFont::TypeWriter);
ui_.output->setFont(font);
ui_.query->setFont(font);
}
void Console::RunQuery() {
QSqlDatabase db = app_->database()->Connect();
QSqlQuery query = db.exec(ui_.query->text());
//ui_.query->clear();
ui_.output->append("<b>&gt; " + query.executedQuery() + "</b>");
query.next();
while (query.isValid()) {
QSqlRecord record = query.record();
QStringList values;
for (int i = 0; i < record.count(); ++i) {
values.append(record.value(i).toString());
}
ui_.output->append(values.join("|"));
query.next();
}
ui_.output->verticalScrollBar()->setValue(ui_.output->verticalScrollBar()->maximum());
}

45
src/dialogs/console.h Normal file
View File

@@ -0,0 +1,45 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef CONSOLE_H
#define CONSOLE_H
#include "config.h"
#include <QDialog>
#include "ui_console.h"
class Application;
class Console : public QDialog {
Q_OBJECT
public:
Console(Application *app, QWidget *parent = nullptr);
private slots:
void RunQuery();
private:
Ui::Console ui_;
Application *app_;
};
#endif // CONSOLE_H

47
src/dialogs/console.ui Normal file
View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Console</class>
<widget class="QDialog" name="Console">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>545</width>
<height>347</height>
</rect>
</property>
<property name="windowTitle">
<string>Console</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTextBrowser" name="output"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="query"/>
</item>
<item>
<widget class="QPushButton" name="run">
<property name="text">
<string>Run</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<tabstops>
<tabstop>query</tabstop>
<tabstop>run</tabstop>
<tabstop>output</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,910 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <limits>
#include <QDateTime>
#include <QDir>
#include <QFuture>
#include <QLabel>
#include <QMenu>
#include <QMessageBox>
#include <QPushButton>
#include <QShortcut>
#include <QtConcurrentRun>
#include <QtDebug>
#include "edittagdialog.h"
#include "trackselectiondialog.h"
#include "ui_edittagdialog.h"
#include "core/application.h"
#include "core/logging.h"
#include "core/tagreaderclient.h"
#include "core/utilities.h"
#include "collection/collection.h"
#include "collection/collectionbackend.h"
#include "playlist/playlistdelegates.h"
#include "covermanager/albumcovermanager.h"
#include "covermanager/albumcoverloader.h"
#include "covermanager/coverproviders.h"
#include "covermanager/albumcoverchoicecontroller.h"
#include "covermanager/coverfromurldialog.h"
const char *EditTagDialog::kHintText = QT_TR_NOOP("(different across multiple songs)");
const char *EditTagDialog::kSettingsGroup = "EditTagDialog";
EditTagDialog::EditTagDialog(Application *app, QWidget *parent)
: QDialog(parent),
ui_(new Ui_EditTagDialog),
app_(app),
album_cover_choice_controller_(new AlbumCoverChoiceController(this)),
loading_(false),
ignore_edits_(false),
#ifdef HAVE_GSTREAMER
tag_fetcher_(new TagFetcher(this)),
#endif
cover_art_id_(0),
cover_art_is_set_(false),
results_dialog_(new TrackSelectionDialog(this))
{
// qLog(Debug) << __PRETTY_FUNCTION__;
//QIcon nocover = IconLoader::Load("nocover");
//cover_options_.default_output_image_ = AlbumCoverLoader::ScaleAndPad(cover_options_, nocover.pixmap(nocover.availableSizes().last()).toImage());
cover_options_.default_output_image_ = AlbumCoverLoader::ScaleAndPad(cover_options_, QImage(":/pictures/noalbumart.png"));
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64,QImage,QImage)), SLOT(ArtLoaded(quint64,QImage,QImage)));
#ifdef HAVE_GSTREAMER
connect(tag_fetcher_, SIGNAL(ResultAvailable(Song, SongList)), results_dialog_, SLOT(FetchTagFinished(Song, SongList)), Qt::QueuedConnection);
connect(tag_fetcher_, SIGNAL(Progress(Song,QString)), results_dialog_, SLOT(FetchTagProgress(Song,QString)));
connect(results_dialog_, SIGNAL(SongChosen(Song, Song)), SLOT(FetchTagSongChosen(Song, Song)));
connect(results_dialog_, SIGNAL(finished(int)), tag_fetcher_, SLOT(Cancel()));
#endif
album_cover_choice_controller_->SetApplication(app_);
ui_->setupUi(this);
ui_->splitter->setSizes(QList<int>() << 200 << width() - 200);
ui_->loading_label->hide();
ui_->fetch_tag->setIcon(QPixmap::fromImage(QImage(":/pictures/musicbrainz.png")));
// An editable field is one that has a label as a buddy. The label is
// important because it gets turned bold when the field is changed.
for (QLabel *label : findChildren<QLabel*>()) {
QWidget *widget = label->buddy();
if (widget) {
// Store information about the field
fields_ << FieldData(label, widget, widget->objectName());
// Connect the Reset signal
if (dynamic_cast<ExtendedEditor*>(widget)) {
connect(widget, SIGNAL(Reset()), SLOT(ResetField()));
}
// Connect the edited signal
if (qobject_cast<QLineEdit*>(widget)) {
connect(widget, SIGNAL(textChanged(QString)), SLOT(FieldValueEdited()));
}
else if (qobject_cast<QPlainTextEdit*>(widget)) {
connect(widget, SIGNAL(textChanged()), SLOT(FieldValueEdited()));
}
else if (qobject_cast<QSpinBox*>(widget)) {
connect(widget, SIGNAL(valueChanged(int)), SLOT(FieldValueEdited()));
}
}
}
// Set the colour of all the labels on the summary page
const bool light = palette().color(QPalette::Base).value() > 128;
const QColor color = palette().color(QPalette::WindowText);
QPalette summary_label_palette(palette());
summary_label_palette.setColor(QPalette::WindowText, light ? color.lighter(150) : color.darker(150));
for (QLabel *label : ui_->summary_tab->findChildren<QLabel*>()) {
if (label->property("field_label").toBool()) {
label->setPalette(summary_label_palette);
}
}
// Pretend the summary text is just a label
ui_->summary->setMaximumHeight(ui_->art->height() - ui_->summary_art_button->height() - 4);
connect(ui_->song_list->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(SelectionChanged()));
connect(ui_->button_box, SIGNAL(clicked(QAbstractButton*)), SLOT(ButtonClicked(QAbstractButton*)));
//connect(ui_->rating, SIGNAL(RatingChanged(float)), SLOT(SongRated(float)));
connect(ui_->playcount_reset, SIGNAL(clicked()), SLOT(ResetPlayCounts()));
connect(ui_->fetch_tag, SIGNAL(clicked()), SLOT(FetchTag()));
// Set up the album cover menu
cover_menu_ = new QMenu(this);
QList<QAction*> actions = album_cover_choice_controller_->GetAllActions();
connect(album_cover_choice_controller_->cover_from_file_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromFile()));
connect(album_cover_choice_controller_->cover_to_file_action(), SIGNAL(triggered()), this, SLOT(SaveCoverToFile()));
connect(album_cover_choice_controller_->cover_from_url_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromURL()));
connect(album_cover_choice_controller_->search_for_cover_action(), SIGNAL(triggered()), this, SLOT(SearchForCover()));
connect(album_cover_choice_controller_->unset_cover_action(), SIGNAL(triggered()), this, SLOT(UnsetCover()));
connect(album_cover_choice_controller_->show_cover_action(), SIGNAL(triggered()), this, SLOT(ShowCover()));
cover_menu_->addActions(actions);
ui_->summary_art_button->setMenu(cover_menu_);
ui_->art->installEventFilter(this);
ui_->art->setAcceptDrops(true);
// Add the next/previous buttons
previous_button_ = new QPushButton(IconLoader::Load("go-previous"), tr("Previous"), this);
next_button_ = new QPushButton(IconLoader::Load("go-next"), tr("Next"), this);
ui_->button_box->addButton(previous_button_, QDialogButtonBox::ResetRole);
ui_->button_box->addButton(next_button_, QDialogButtonBox::ResetRole);
connect(previous_button_, SIGNAL(clicked()), SLOT(PreviousSong()));
connect(next_button_, SIGNAL(clicked()), SLOT(NextSong()));
// Set some shortcuts for the buttons
new QShortcut(QKeySequence::Back, previous_button_, SLOT(click()));
new QShortcut(QKeySequence::Forward, next_button_, SLOT(click()));
new QShortcut(QKeySequence::MoveToPreviousPage, previous_button_, SLOT(click()));
new QShortcut(QKeySequence::MoveToNextPage, next_button_, SLOT(click()));
// Show the shortcuts as tooltips
previous_button_->setToolTip(QString("%1 (%2 / %3)").arg(
previous_button_->text(),
QKeySequence(QKeySequence::Back).toString(QKeySequence::NativeText),
QKeySequence(QKeySequence::MoveToPreviousPage).toString(QKeySequence::NativeText)));
next_button_->setToolTip(QString("%1 (%2 / %3)").arg(
next_button_->text(),
QKeySequence(QKeySequence::Forward).toString(QKeySequence::NativeText),
QKeySequence(QKeySequence::MoveToNextPage).toString(QKeySequence::NativeText)));
new TagCompleter(app_->collection_backend(), Playlist::Column_Artist, ui_->artist);
new TagCompleter(app_->collection_backend(), Playlist::Column_Album, ui_->album);
new TagCompleter(app_->collection_backend(), Playlist::Column_AlbumArtist, ui_->albumartist);
new TagCompleter(app_->collection_backend(), Playlist::Column_Genre, ui_->genre);
new TagCompleter(app_->collection_backend(), Playlist::Column_Composer, ui_->composer);
new TagCompleter(app_->collection_backend(), Playlist::Column_Performer, ui_->performer);
new TagCompleter(app_->collection_backend(), Playlist::Column_Grouping, ui_->grouping);
}
EditTagDialog::~EditTagDialog() {
delete ui_;
}
bool EditTagDialog::SetLoading(const QString &message) {
// qLog(Debug) << __PRETTY_FUNCTION__;
const bool loading = !message.isEmpty();
if (loading == loading_) return false;
loading_ = loading;
ui_->button_box->setEnabled(!loading);
ui_->tab_widget->setEnabled(!loading);
ui_->song_list->setEnabled(!loading);
ui_->fetch_tag->setEnabled(!loading);
ui_->loading_label->setVisible(loading);
ui_->loading_label->set_text(message);
return true;
}
QList<EditTagDialog::Data> EditTagDialog::LoadData(const SongList &songs) const {
// qLog(Debug) << __PRETTY_FUNCTION__;
QList<Data> ret;
for (const Song &song : songs) {
if (song.IsEditable()) {
// Try reloading the tags from file
Song copy(song);
TagReaderClient::Instance()->ReadFileBlocking(copy.url().toLocalFile(), &copy);
if (copy.is_valid()) {
copy.MergeUserSetData(song);
ret << Data(copy);
}
}
}
return ret;
}
void EditTagDialog::SetSongs(const SongList &s, const PlaylistItemList &items) {
// qLog(Debug) << __PRETTY_FUNCTION__;
// Show the loading indicator
if (!SetLoading(tr("Loading tracks") + "...")) return;
data_.clear();
playlist_items_ = items;
ui_->song_list->clear();
// Reload tags in the background
QFuture<QList<Data>> future = QtConcurrent::run(this, &EditTagDialog::LoadData, s);
NewClosure(future, this, SLOT(SetSongsFinished(QFuture<QList<EditTagDialog::Data>>)), future);
}
void EditTagDialog::SetSongsFinished(QFuture<QList<Data>> future) {
// qLog(Debug) << __PRETTY_FUNCTION__;
if (!SetLoading(QString())) return;
data_ = future.result();
if (data_.count() == 0) {
// If there were no valid songs, disable everything
ui_->song_list->setEnabled(false);
ui_->tab_widget->setEnabled(false);
// Show a summary with empty information
UpdateSummaryTab(Song());
ui_->tab_widget->setCurrentWidget(ui_->summary_tab);
SetSongListVisibility(false);
return;
}
// Add the filenames to the list
for (const Data &data : data_) {
ui_->song_list->addItem(data.current_.basefilename());
}
// Select all
ui_->song_list->setCurrentRow(0);
ui_->song_list->selectAll();
// Hide the list if there's only one song in it
SetSongListVisibility(data_.count() != 1);
}
void EditTagDialog::SetSongListVisibility(bool visible) {
ui_->song_list->setVisible(visible);
previous_button_->setEnabled(visible);
next_button_->setEnabled(visible);
}
QVariant EditTagDialog::Data::value(const Song &song, const QString &id) {
// qLog(Debug) << __PRETTY_FUNCTION__;
if (id == "title") return song.title();
if (id == "artist") return song.artist();
if (id == "album") return song.album();
if (id == "albumartist") return song.albumartist();
if (id == "composer") return song.composer();
if (id == "performer") return song.performer();
if (id == "grouping") return song.grouping();
if (id == "genre") return song.genre();
if (id == "comment") return song.comment();
if (id == "track") return song.track();
if (id == "disc") return song.disc();
if (id == "year") return song.year();
qLog(Warning) << "Unknown ID" << id;
return QVariant();
}
void EditTagDialog::Data::set_value(const QString &id, const QVariant &value) {
// qLog(Debug) << __PRETTY_FUNCTION__;
if (id == "title") current_.set_title(value.toString());
else if (id == "artist") current_.set_artist(value.toString());
else if (id == "album") current_.set_album(value.toString());
else if (id == "albumartist") current_.set_albumartist(value.toString());
else if (id == "composer") current_.set_composer(value.toString());
else if (id == "performer") current_.set_performer(value.toString());
else if (id == "grouping") current_.set_grouping(value.toString());
else if (id == "genre") current_.set_genre(value.toString());
else if (id == "comment") current_.set_comment(value.toString());
else if (id == "track") current_.set_track(value.toInt());
else if (id == "disc") current_.set_disc(value.toInt());
else if (id == "year") current_.set_year(value.toInt());
else qLog(Warning) << "Unknown ID" << id;
}
bool EditTagDialog::DoesValueVary(const QModelIndexList &sel, const QString &id) const {
QVariant value = data_[sel.first().row()].current_value(id);
for (int i = 1; i < sel.count(); ++i) {
if (value != data_[sel[i].row()].current_value(id)) return true;
}
return false;
}
bool EditTagDialog::IsValueModified(const QModelIndexList &sel, const QString &id) const {
for (const QModelIndex &i : sel) {
if (data_[i.row()].original_value(id) != data_[i.row()].current_value(id))
return true;
}
return false;
}
void EditTagDialog::InitFieldValue(const FieldData &field, const QModelIndexList &sel) {
// qLog(Debug) << __PRETTY_FUNCTION__;
const bool varies = DoesValueVary(sel, field.id_);
// const bool modified = IsValueModified(sel, field.id_);
if (ExtendedEditor *editor = dynamic_cast<ExtendedEditor*>(field.editor_)) {
editor->clear();
editor->clear_hint();
if (varies) {
editor->set_hint(tr(EditTagDialog::kHintText));
}
else {
editor->set_text(data_[sel[0].row()].current_value(field.id_).toString());
}
}
UpdateModifiedField(field, sel);
}
void EditTagDialog::UpdateFieldValue(const FieldData &field, const QModelIndexList &sel) {
// qLog(Debug) << __PRETTY_FUNCTION__;
// Get the value from the field
QVariant value;
if (ExtendedEditor *editor = dynamic_cast<ExtendedEditor*>(field.editor_)) {
value = editor->text();
}
// Did we get it?
if (!value.isValid()) {
return;
}
// Set it in each selected song
for (const QModelIndex &i : sel) {
data_[i.row()].set_value(field.id_, value);
}
UpdateModifiedField(field, sel);
}
void EditTagDialog::UpdateModifiedField(const FieldData &field, const QModelIndexList &sel) {
// qLog(Debug) << __PRETTY_FUNCTION__;
const bool modified = IsValueModified(sel, field.id_);
// Update the boldness
QFont new_font(font());
new_font.setBold(modified);
field.label_->setFont(new_font);
field.editor_->setFont(new_font);
}
void EditTagDialog::ResetFieldValue(const FieldData &field, const QModelIndexList &sel) {
// qLog(Debug) << __PRETTY_FUNCTION__;
// Reset each selected song
for (const QModelIndex &i : sel) {
Data &data = data_[i.row()];
data.set_value(field.id_, data.original_value(field.id_));
}
// Reset the field
InitFieldValue(field, sel);
}
void EditTagDialog::SelectionChanged() {
// qLog(Debug) << __PRETTY_FUNCTION__;
const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes();
if (sel.isEmpty())
return;
// Set the editable fields
UpdateUI(sel);
// If we're editing multiple songs then we have to disable certain tabs
const bool multiple = sel.count() > 1;
ui_->tab_widget->setTabEnabled(ui_->tab_widget->indexOf(ui_->summary_tab), !multiple);
if (!multiple) {
const Song &song = data_[sel.first().row()].original_;
UpdateSummaryTab(song);
UpdateStatisticsTab(song);
}
}
void EditTagDialog::UpdateUI(const QModelIndexList &sel){
// qLog(Debug) << __PRETTY_FUNCTION__;
ignore_edits_ = true;
for (const FieldData &field : fields_) {
InitFieldValue(field, sel);
}
ignore_edits_ = false;
}
static void SetText(QLabel *label, int value, const QString &suffix, const QString &def = QString()) {
// qLog(Debug) << __PRETTY_FUNCTION__;
label->setText(value <= 0 ? def : (QString::number(value) + " " + suffix));
}
static void SetDate(QLabel *label, uint time) {
// qLog(Debug) << __PRETTY_FUNCTION__;
if (time == std::numeric_limits<uint>::max()) { // -1
label->setText(QObject::tr("Unknown"));
}
else {
label->setText(QDateTime::fromTime_t(time).toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)));
}
}
void EditTagDialog::UpdateSummaryTab(const Song &song) {
// qLog(Debug) << __PRETTY_FUNCTION__;
cover_art_id_ = app_->album_cover_loader()->LoadImageAsync(cover_options_, song);
QString summary = "<b>" + song.PrettyTitleWithArtist().toHtmlEscaped() + "</b><br/>";
bool art_is_set = true;
if (song.has_manually_unset_cover()) {
summary += tr("Cover art manually unset").toHtmlEscaped();
art_is_set = false;
}
else if (!song.art_manual().isEmpty()) {
summary += tr("Cover art set from %1").arg(song.art_manual()).toHtmlEscaped();
}
else if (song.has_embedded_cover()) {
summary += tr("Cover art from embedded image");
}
else if (!song.art_automatic().isEmpty()) {
summary += tr("Cover art loaded automatically from %1").arg(song.art_automatic()).toHtmlEscaped();
}
else {
summary += tr("Cover art not set").toHtmlEscaped();
art_is_set = false;
}
ui_->summary->setText(summary);
album_cover_choice_controller_->unset_cover_action()->setEnabled(art_is_set);
album_cover_choice_controller_->show_cover_action()->setEnabled(art_is_set);
ui_->summary_art_button->setEnabled(song.id() != -1);
ui_->length->setText(Utilities::PrettyTimeNanosec(song.length_nanosec()));
SetText(ui_->samplerate, song.samplerate(), "Hz");
SetText(ui_->bitdepth, song.bitdepth(), "Bit");
SetText(ui_->bitrate, song.bitrate(), tr("kbps"));
SetDate(ui_->mtime, song.mtime());
SetDate(ui_->ctime, song.ctime());
if (song.filesize() == -1) {
ui_->filesize->setText(tr("Unknown"));
}
else {
ui_->filesize->setText(Utilities::PrettySize(song.filesize()));
}
ui_->filetype->setText(song.TextForFiletype());
if (song.url().scheme() == "file")
ui_->filename->setText(QDir::toNativeSeparators(song.url().toLocalFile()));
else
ui_->filename->setText(song.url().toString());
album_cover_choice_controller_->search_for_cover_action()->setEnabled(app_->cover_providers()->HasAnyProviders());
}
void EditTagDialog::UpdateStatisticsTab(const Song &song) {
// qLog(Debug) << __PRETTY_FUNCTION__;
ui_->playcount->setText(QString::number(qMax(0, song.playcount())));
ui_->skipcount->setText(QString::number(qMax(0, song.skipcount())));
ui_->lastplayed->setText(song.lastplayed() <= 0 ? tr("Never") : QDateTime::fromTime_t(song.lastplayed()).toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)));
}
void EditTagDialog::ArtLoaded(quint64 id, const QImage &scaled, const QImage &original) {
// qLog(Debug) << __PRETTY_FUNCTION__;
if (id == cover_art_id_) {
ui_->art->setPixmap(QPixmap::fromImage(scaled));
original_ = original;
}
}
void EditTagDialog::FieldValueEdited() {
// qLog(Debug) << __PRETTY_FUNCTION__;
if (ignore_edits_) return;
const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes();
if (sel.isEmpty())
return;
QWidget *w = qobject_cast<QWidget*>(sender());
// Find the field
for (const FieldData &field : fields_) {
if (field.editor_ == w) {
UpdateFieldValue(field, sel);
return;
}
}
}
void EditTagDialog::ResetField() {
// qLog(Debug) << __PRETTY_FUNCTION__;
const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes();
if (sel.isEmpty())
return;
QWidget *w = qobject_cast<QWidget*>(sender());
// Find the field
for (const FieldData &field : fields_) {
if (field.editor_ == w) {
ignore_edits_ = true;
ResetFieldValue(field, sel);
ignore_edits_ = false;
return;
}
}
}
Song *EditTagDialog::GetFirstSelected() {
// qLog(Debug) << __PRETTY_FUNCTION__;
const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes();
if (sel.isEmpty()) return nullptr;
return &data_[sel.first().row()].original_;
}
void EditTagDialog::LoadCoverFromFile() {
// qLog(Debug) << __PRETTY_FUNCTION__;
Song *song = GetFirstSelected();
if (!song) return;
const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes();
QString cover = album_cover_choice_controller_->LoadCoverFromFile(song);
if (!cover.isEmpty()) UpdateCoverOf(*song, sel, cover);
}
void EditTagDialog::SaveCoverToFile() {
// qLog(Debug) << __PRETTY_FUNCTION__;
Song *song = GetFirstSelected();
if (!song) return;
album_cover_choice_controller_->SaveCoverToFile(*song, original_);
}
void EditTagDialog::LoadCoverFromURL() {
// qLog(Debug) << __PRETTY_FUNCTION__;
Song *song = GetFirstSelected();
if (!song) return;
const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes();
QString cover = album_cover_choice_controller_->LoadCoverFromURL(song);
if (!cover.isEmpty()) UpdateCoverOf(*song, sel, cover);
}
void EditTagDialog::SearchForCover() {
// qLog(Debug) << __PRETTY_FUNCTION__;
Song *song = GetFirstSelected();
if (!song) return;
const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes();
QString cover = album_cover_choice_controller_->SearchForCover(song);
if (!cover.isEmpty()) UpdateCoverOf(*song, sel, cover);
}
void EditTagDialog::UnsetCover() {
// qLog(Debug) << __PRETTY_FUNCTION__;
Song *song = GetFirstSelected();
if (!song) return;
const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes();
QString cover = album_cover_choice_controller_->UnsetCover(song);
UpdateCoverOf(*song, sel, cover);
}
void EditTagDialog::ShowCover() {
// qLog(Debug) << __PRETTY_FUNCTION__;
Song *song = GetFirstSelected();
if (!song) {
return;
}
album_cover_choice_controller_->ShowCover(*song);
}
void EditTagDialog::UpdateCoverOf(const Song &selected, const QModelIndexList &sel, const QString &cover) {
// qLog(Debug) << __PRETTY_FUNCTION__;
if (!selected.is_valid() || selected.id() == -1) return;
UpdateSummaryTab(selected);
// Now check if we have any other songs cached that share that artist and
// album (and would therefore be changed as well)
for (int i = 0; i < data_.count(); ++i) {
if (i == sel.first().row()) // Already changed this one
continue;
Song *other_song = &data_[i].original_;
if (selected.artist() == other_song->artist() && selected.album() == other_song->album()) {
other_song->set_art_manual(cover);
}
}
}
void EditTagDialog::NextSong() {
// qLog(Debug) << __PRETTY_FUNCTION__;
if (ui_->song_list->count() == 0) {
return;
}
int row = (ui_->song_list->currentRow() + 1) % ui_->song_list->count();
ui_->song_list->setCurrentRow(row);
}
void EditTagDialog::PreviousSong() {
// qLog(Debug) << __PRETTY_FUNCTION__;
if (ui_->song_list->count() == 0) {
return;
}
int row = (ui_->song_list->currentRow() - 1 + ui_->song_list->count()) % ui_->song_list->count();
ui_->song_list->setCurrentRow(row);
}
void EditTagDialog::ButtonClicked(QAbstractButton *button) {
if (button == ui_->button_box->button(QDialogButtonBox::Discard)) {
reject();
}
}
void EditTagDialog::SaveData(const QList<Data> &data) {
// qLog(Debug) << __PRETTY_FUNCTION__;
for (int i = 0; i < data.count(); ++i) {
const Data &ref = data[i];
if (ref.current_.IsMetadataEqual(ref.original_)) continue;
if (!TagReaderClient::Instance()->SaveFileBlocking(ref.current_.url().toLocalFile(), ref.current_)) {
emit Error(tr("An error occurred writing metadata to '%1'").arg(ref.current_.url().toLocalFile()));
}
}
}
void EditTagDialog::accept() {
// qLog(Debug) << __PRETTY_FUNCTION__;
// Show the loading indicator
if (!SetLoading(tr("Saving tracks") + "...")) return;
// Save tags in the background
QFuture<void> future = QtConcurrent::run(this, &EditTagDialog::SaveData, data_);
NewClosure(future, this, SLOT(AcceptFinished()));
}
void EditTagDialog::AcceptFinished() {
// qLog(Debug) << __PRETTY_FUNCTION__;
if (!SetLoading(QString())) return;
QDialog::accept();
}
bool EditTagDialog::eventFilter(QObject *o, QEvent *e) {
// qLog(Debug) << __PRETTY_FUNCTION__;
if (o == ui_->art) {
switch (e->type()) {
case QEvent::MouseButtonRelease:
cover_menu_->popup(static_cast<QMouseEvent*>(e)->globalPos());
break;
case QEvent::DragEnter: {
QDragEnterEvent *event = static_cast<QDragEnterEvent*>(e);
if (AlbumCoverChoiceController::CanAcceptDrag(event)) {
event->acceptProposedAction();
}
break;
}
case QEvent::Drop: {
const QDropEvent *event = static_cast<QDropEvent*>(e);
const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes();
Song *song = GetFirstSelected();
const QString cover = album_cover_choice_controller_->SaveCover(song, event);
if (!cover.isEmpty()) {
UpdateCoverOf(*song, sel, cover);
}
break;
}
default:
break;
}
}
return false;
}
void EditTagDialog::showEvent(QShowEvent *e) {
// qLog(Debug) << __PRETTY_FUNCTION__;
// Set the dialog's height to the smallest possible
resize(width(), sizeHint().height());
// Restore the tab that was current last time.
QSettings s;
s.beginGroup(kSettingsGroup);
ui_->tab_widget->setCurrentIndex(s.value("current_tab").toInt());
QDialog::showEvent(e);
}
void EditTagDialog::hideEvent(QHideEvent *e) {
// qLog(Debug) << __PRETTY_FUNCTION__;
// Save the current tab
QSettings s;
s.beginGroup(kSettingsGroup);
s.setValue("current_tab", ui_->tab_widget->currentIndex());
QDialog::hideEvent(e);
}
void EditTagDialog::ResetPlayCounts() {
// qLog(Debug) << __PRETTY_FUNCTION__;
const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes();
if (sel.isEmpty())
return;
Song *song = &data_[sel.first().row()].original_;
if (!song->is_valid() || song->id() == -1) return;
if (QMessageBox::question(this, tr("Reset play counts"), tr("Are you sure you want to reset this song's statistics?"), QMessageBox::Reset, QMessageBox::Cancel) != QMessageBox::Reset) {
return;
}
song->set_playcount(0);
song->set_skipcount(0);
song->set_lastplayed(-1);
app_->collection_backend()->ResetStatisticsAsync(song->id());
UpdateStatisticsTab(*song);
}
#ifdef HAVE_GSTREAMER
void EditTagDialog::FetchTag() {
// qLog(Debug) << __PRETTY_FUNCTION__;
const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes();
SongList songs;
for (const QModelIndex &index : sel) {
Song song = data_[index.row()].original_;
if (!song.is_valid()) {
continue;
}
songs << song;
}
if (songs.isEmpty()) return;
results_dialog_->Init(songs);
tag_fetcher_->StartFetch(songs);
results_dialog_->show();
}
void EditTagDialog::FetchTagSongChosen(const Song &original_song, const Song &new_metadata) {
// qLog(Debug) << __PRETTY_FUNCTION__;
const QString filename = original_song.url().toLocalFile();
// Find the song with this filename
auto data_it =
std::find_if(data_.begin(), data_.end(), [&filename](const Data &d) {
return d.original_.url().toLocalFile() == filename;
});
if (data_it == data_.end()) {
qLog(Warning) << "Could not find song to filename: " << filename;
return;
}
// Update song data
data_it->current_.set_title(new_metadata.title());
data_it->current_.set_artist(new_metadata.artist());
data_it->current_.set_album(new_metadata.album());
data_it->current_.set_track(new_metadata.track());
data_it->current_.set_year(new_metadata.year());
// Is it currently being displayed in the UI?
if (ui_->song_list->currentRow() == std::distance(data_.begin(), data_it)) {
// Yes! Additionally update UI
const QModelIndexList sel = ui_->song_list->selectionModel()->selectedIndexes();
UpdateUI(sel);
}
}
#endif

180
src/dialogs/edittagdialog.h Normal file
View File

@@ -0,0 +1,180 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef EDITTAGDIALOG_H
#define EDITTAGDIALOG_H
#include "config.h"
#include <QDialog>
#include <QModelIndexList>
#include "core/song.h"
#include "covermanager/albumcoverloaderoptions.h"
#include "musicbrainz/tagfetcher.h"
#include "playlist/playlistitem.h"
#include "widgets/lineedit.h"
#include "trackselectiondialog.h"
class Application;
class AlbumCoverChoiceController;
class CollectionBackend;
class Ui_EditTagDialog;
class QAbstractButton;
class QItemSelection;
class QLabel;
class QPushButton;
class EditTagDialog : public QDialog {
Q_OBJECT
public:
EditTagDialog(Application *app, QWidget *parent = nullptr);
~EditTagDialog();
static const char *kHintText;
static const char *kSettingsGroup;
void SetSongs(const SongList &songs, const PlaylistItemList &items = PlaylistItemList());
PlaylistItemList playlist_items() const { return playlist_items_; }
void accept();
signals:
void Error(const QString &message);
protected:
bool eventFilter(QObject *o, QEvent *e);
void showEvent(QShowEvent*);
void hideEvent(QHideEvent*);
private:
struct Data {
Data(const Song &song = Song()) : original_(song), current_(song) {}
static QVariant value(const Song &song, const QString &id);
QVariant original_value(const QString &id) const {
return value(original_, id);
}
QVariant current_value(const QString &id) const {
return value(current_, id);
}
void set_value(const QString &id, const QVariant &value);
Song original_;
Song current_;
};
private slots:
void SetSongsFinished(QFuture<QList<EditTagDialog::Data>> future);
void AcceptFinished();
void SelectionChanged();
void FieldValueEdited();
void ResetField();
void ButtonClicked(QAbstractButton *button);
void ResetPlayCounts();
#ifdef HAVE_GSTREAMER
void FetchTag();
void FetchTagSongChosen(const Song &original_song, const Song &new_metadata);
#endif
void ArtLoaded(quint64 id, const QImage &scaled, const QImage &original);
void LoadCoverFromFile();
void SaveCoverToFile();
void LoadCoverFromURL();
void SearchForCover();
void UnsetCover();
void ShowCover();
void PreviousSong();
void NextSong();
private:
struct FieldData {
FieldData(QLabel *label = nullptr, QWidget *editor = nullptr, const QString &id = QString())
: label_(label), editor_(editor), id_(id) {}
QLabel *label_;
QWidget *editor_;
QString id_;
};
Song *GetFirstSelected();
void UpdateCoverOf(const Song &selected, const QModelIndexList &sel, const QString &cover);
bool DoesValueVary(const QModelIndexList &sel, const QString &id) const;
bool IsValueModified(const QModelIndexList &sel, const QString &id) const;
void InitFieldValue(const FieldData &field, const QModelIndexList &sel);
void UpdateFieldValue(const FieldData &field, const QModelIndexList &sel);
void UpdateModifiedField(const FieldData &field, const QModelIndexList &sel);
void ResetFieldValue(const FieldData &field, const QModelIndexList &sel);
void UpdateSummaryTab(const Song &song);
void UpdateStatisticsTab(const Song &song);
void UpdateUI(const QModelIndexList &sel);
bool SetLoading(const QString &message);
void SetSongListVisibility(bool visible);
// Called by QtConcurrentRun
QList<Data> LoadData(const SongList &songs) const;
void SaveData(const QList<Data> &data);
private:
Ui_EditTagDialog *ui_;
Application *app_;
AlbumCoverChoiceController *album_cover_choice_controller_;
bool loading_;
PlaylistItemList playlist_items_;
QList<Data> data_;
QList<FieldData> fields_;
bool ignore_edits_;
#ifdef HAVE_GSTREAMER
TagFetcher *tag_fetcher_;
#endif
AlbumCoverLoaderOptions cover_options_;
quint64 cover_art_id_;
bool cover_art_is_set_;
// A copy of the original, unscaled album cover.
QImage original_;
QMenu *cover_menu_;
QPushButton *previous_button_;
QPushButton *next_button_;
TrackSelectionDialog *results_dialog_;
};
#endif // EDITTAGDIALOG_H

View File

@@ -0,0 +1,917 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>EditTagDialog</class>
<widget class="QDialog" name="EditTagDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>863</width>
<height>635</height>
</rect>
</property>
<property name="windowTitle">
<string>Edit track information</string>
</property>
<property name="windowIcon">
<iconset resource="../../data/data.qrc">
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QListWidget" name="song_list">
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget>
<widget class="QTabWidget" name="tab_widget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="summary_tab">
<attribute name="title">
<string>Summary</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="art">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>124</width>
<height>124</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="margin">
<number>2</number>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTextEdit" name="summary">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">QTextEdit {
background: transparent;
}</string>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="summary_art_button">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Change cover art</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="playcount_reset">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Reset play counts</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_3" rowstretch="0,0,0,0,0,0,0,0,0">
<property name="horizontalSpacing">
<number>18</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="length_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Length</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="length">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="baseSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="playcount_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Play count</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLabel" name="playcount">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="skipcount_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Skip count</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QLabel" name="skipcount">
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="bitrate_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Bit rate</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="bitrate">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="lastplayed_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string comment="A playlist's tag.">Last played</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QLabel" name="lastplayed">
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="samplerate_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Sample rate</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="samplerate">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="bitdepth_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Bit depth</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="bitdepth">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="filesize_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>File size</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="filesize">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="filetype_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>File type</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="6" column="1" colspan="3">
<widget class="QLabel" name="filetype">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="mtime_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Date modified</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="7" column="1" colspan="3">
<widget class="QLabel" name="mtime">
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="ctime_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Date created</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="1" colspan="3">
<widget class="QLabel" name="ctime">
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="filename_label">
<property name="text">
<string>File name</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="1" colspan="3">
<widget class="QLineEdit" name="filename">
<property name="styleSheet">
<string notr="true">QLineEdit {
background: transparent;
}</string>
</property>
<property name="frame">
<bool>false</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="tags_tab">
<attribute name="title">
<string>Edit tags</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="title_label">
<property name="text">
<string>Title</string>
</property>
<property name="buddy">
<cstring>title</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="LineEdit" name="title">
<property name="has_reset_button" stdset="0">
<bool>true</bool>
</property>
<property name="has_clear_button" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="track_label">
<property name="text">
<string>Track</string>
</property>
<property name="buddy">
<cstring>track</cstring>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="SpinBox" name="track">
<property name="correctionMode">
<enum>QAbstractSpinBox::CorrectToNearestValue</enum>
</property>
<property name="maximum">
<number>9999</number>
</property>
<property name="has_clear_button" stdset="0">
<bool>false</bool>
</property>
<property name="has_reset_button" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="artist_label">
<property name="text">
<string>Artist</string>
</property>
<property name="buddy">
<cstring>artist</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="LineEdit" name="artist">
<property name="has_reset_button" stdset="0">
<bool>true</bool>
</property>
<property name="has_clear_button" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="disc_label">
<property name="text">
<string>Disc</string>
</property>
<property name="buddy">
<cstring>disc</cstring>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="SpinBox" name="disc">
<property name="correctionMode">
<enum>QAbstractSpinBox::CorrectToNearestValue</enum>
</property>
<property name="maximum">
<number>9999</number>
</property>
<property name="has_clear_button" stdset="0">
<bool>false</bool>
</property>
<property name="has_reset_button" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="album_label">
<property name="text">
<string>Album</string>
</property>
<property name="buddy">
<cstring>album</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="LineEdit" name="album">
<property name="has_reset_button" stdset="0">
<bool>true</bool>
</property>
<property name="has_clear_button" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="year_label">
<property name="text">
<string>Year</string>
</property>
<property name="buddy">
<cstring>year</cstring>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="SpinBox" name="year">
<property name="correctionMode">
<enum>QAbstractSpinBox::CorrectToNearestValue</enum>
</property>
<property name="maximum">
<number>9999</number>
</property>
<property name="has_clear_button" stdset="0">
<bool>false</bool>
</property>
<property name="has_reset_button" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="albumartist_label">
<property name="text">
<string>Album artist</string>
</property>
<property name="buddy">
<cstring>albumartist</cstring>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="LineEdit" name="albumartist">
<property name="has_reset_button" stdset="0">
<bool>true</bool>
</property>
<property name="has_clear_button" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="composer_label">
<property name="text">
<string>Composer</string>
</property>
<property name="buddy">
<cstring>composer</cstring>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="LineEdit" name="composer">
<property name="has_reset_button" stdset="0">
<bool>true</bool>
</property>
<property name="has_clear_button" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="performer_label">
<property name="text">
<string>Performer</string>
</property>
<property name="buddy">
<cstring>performer</cstring>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="LineEdit" name="performer">
<property name="has_reset_button" stdset="0">
<bool>true</bool>
</property>
<property name="has_clear_button" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="grouping_label">
<property name="text">
<string>Grouping</string>
</property>
<property name="buddy">
<cstring>grouping</cstring>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="LineEdit" name="grouping">
<property name="has_reset_button" stdset="0">
<bool>true</bool>
</property>
<property name="has_clear_button" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="genre_label">
<property name="text">
<string>Genre</string>
</property>
<property name="buddy">
<cstring>genre</cstring>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="LineEdit" name="genre">
<property name="has_reset_button" stdset="0">
<bool>true</bool>
</property>
<property name="has_clear_button" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QPushButton" name="fetch_tag">
<property name="text">
<string>Complete tags automatically</string>
</property>
<property name="icon">
<iconset resource="../../data/data.qrc">
<normaloff>:/pictures/musicbrainz.png</normaloff>:/pictures/musicbrainz.png</iconset>
</property>
<property name="iconSize">
<size>
<width>38</width>
<height>22</height>
</size>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QLabel" name="comment_label">
<property name="text">
<string>Comment</string>
</property>
<property name="buddy">
<cstring>comment</cstring>
</property>
</widget>
</item>
<item row="11" column="1" colspan="3">
<widget class="TextEdit" name="comment">
<property name="has_reset_button" stdset="0">
<bool>true</bool>
</property>
<property name="has_clear_button" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</widget>
</item>
<item>
<widget class="BusyIndicator" name="loading_label"/>
</item>
<item>
<widget class="QDialogButtonBox" name="button_box">
<property name="standardButtons">
<set>QDialogButtonBox::Discard|QDialogButtonBox::Save</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>BusyIndicator</class>
<extends>QWidget</extends>
<header>widgets/busyindicator.h</header>
</customwidget>
<customwidget>
<class>LineEdit</class>
<extends>QLineEdit</extends>
<header>widgets/lineedit.h</header>
</customwidget>
<customwidget>
<class>RatingWidget</class>
<extends>QWidget</extends>
<header>widgets/ratingwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>TextEdit</class>
<extends>QTextEdit</extends>
<header>widgets/lineedit.h</header>
</customwidget>
<customwidget>
<class>SpinBox</class>
<extends>QSpinBox</extends>
<header>widgets/lineedit.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../../data/data.qrc"/>
</resources>
<connections>
<connection>
<sender>button_box</sender>
<signal>accepted()</signal>
<receiver>EditTagDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>65</x>
<y>552</y>
</hint>
<hint type="destinationlabel">
<x>84</x>
<y>533</y>
</hint>
</hints>
</connection>
<connection>
<sender>button_box</sender>
<signal>rejected()</signal>
<receiver>EditTagDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>236</x>
<y>542</y>
</hint>
<hint type="destinationlabel">
<x>242</x>
<y>532</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,75 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "errordialog.h"
#include "ui_errordialog.h"
ErrorDialog::ErrorDialog(QWidget *parent)
: QDialog(parent),
ui_(new Ui_ErrorDialog)
{
ui_->setupUi(this);
QIcon warning_icon(style()->standardIcon(QStyle::SP_MessageBoxWarning));
QPixmap warning_pixmap(warning_icon.pixmap(48));
QPalette messages_palette(ui_->messages->palette());
messages_palette.setColor(QPalette::Base, messages_palette.color(QPalette::Background));
ui_->messages->setPalette(messages_palette);
ui_->icon->setPixmap(warning_pixmap);
}
ErrorDialog::~ErrorDialog() {
delete ui_;
}
void ErrorDialog::ShowMessage(const QString &message) {
current_messages_ << message;
UpdateContent();
show();
raise();
activateWindow();
}
void ErrorDialog::hideEvent(QHideEvent *) {
current_messages_.clear();
UpdateContent();
}
void ErrorDialog::UpdateContent() {
QString html;
for (const QString& message : current_messages_) {
if (!html.isEmpty())
html += "<hr/>";
html += message.toHtmlEscaped();
}
ui_->messages->setHtml(html);
}

52
src/dialogs/errordialog.h Normal file
View File

@@ -0,0 +1,52 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ERRORDIALOG_H
#define ERRORDIALOG_H
#include "config.h"
#include <QDialog>
class Ui_ErrorDialog;
class ErrorDialog : public QDialog {
Q_OBJECT
public:
ErrorDialog(QWidget *parent = nullptr);
~ErrorDialog();
public slots:
void ShowMessage(const QString& message);
protected:
void hideEvent(QHideEvent *);
private:
void UpdateContent();
Ui_ErrorDialog *ui_;
QStringList current_messages_;
};
#endif // ERRORDIALOG_H

107
src/dialogs/errordialog.ui Normal file
View File

@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ErrorDialog</class>
<widget class="QDialog" name="ErrorDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>411</width>
<height>180</height>
</rect>
</property>
<property name="windowTitle">
<string>Strawberry Error</string>
</property>
<property name="windowIcon">
<iconset resource="../../data/data.qrc">
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="icon"/>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QTextEdit" name="messages">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../../data/data.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ErrorDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ErrorDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,382 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "organisedialog.h"
#include "ui_organisedialog.h"
#include <memory>
#include <QDir>
#include <QFileInfo>
#include <QHash>
#include <QMenu>
#include <QPushButton>
#include <QResizeEvent>
#include <QSettings>
#include <QSignalMapper>
#include <QtConcurrentRun>
#include <QtDebug>
#include "organiseerrordialog.h"
#include "core/musicstorage.h"
#include "core/organise.h"
#include "core/tagreaderclient.h"
#include "core/utilities.h"
#include "core/iconloader.h"
const char *OrganiseDialog::kDefaultFormat = "%artist/%album{ (Disc %disc)}/{%track - }%title.%extension";
const char *OrganiseDialog::kSettingsGroup = "OrganiseDialog";
OrganiseDialog::OrganiseDialog(TaskManager *task_manager, QWidget *parent)
: QDialog(parent),
ui_(new Ui_OrganiseDialog),
task_manager_(task_manager),
total_size_(0),
resized_by_user_(false) {
ui_->setupUi(this);
connect(ui_->button_box->button(QDialogButtonBox::Reset), SIGNAL(clicked()), SLOT(Reset()));
ui_->aftercopying->setItemIcon(1, IconLoader::Load("edit-delete"));
// Valid tags
QMap<QString, QString> tags;
tags[tr("Title")] = "title";
tags[tr("Album")] = "album";
tags[tr("Artist")] = "artist";
tags[tr("Artist's initial")] = "artistinitial";
tags[tr("Album artist")] = "albumartist";
tags[tr("Composer")] = "composer";
tags[tr("Performer")] = "performer";
tags[tr("Grouping")] = "grouping";
tags[tr("Track")] = "track";
tags[tr("Disc")] = "disc";
tags[tr("Year")] = "year";
tags[tr("Original year")] = "originalyear";
tags[tr("Genre")] = "genre";
tags[tr("Comment")] = "comment";
tags[tr("Length")] = "length";
tags[tr("Bitrate", "Refers to bitrate in file organise dialog.")] = "bitrate";
tags[tr("Samplerate")] = "samplerate";
tags[tr("File extension")] = "extension";
// Naming scheme input field
new OrganiseFormat::SyntaxHighlighter(ui_->naming);
connect(ui_->destination, SIGNAL(currentIndexChanged(int)), SLOT(UpdatePreviews()));
connect(ui_->naming, SIGNAL(textChanged()), SLOT(UpdatePreviews()));
connect(ui_->replace_ascii, SIGNAL(toggled(bool)), SLOT(UpdatePreviews()));
connect(ui_->replace_the, SIGNAL(toggled(bool)), SLOT(UpdatePreviews()));
connect(ui_->replace_spaces, SIGNAL(toggled(bool)), SLOT(UpdatePreviews()));
// Get the titles of the tags to put in the insert menu
QStringList tag_titles = tags.keys();
qStableSort(tag_titles);
// Build the insert menu
QMenu *tag_menu = new QMenu(this);
QSignalMapper *tag_mapper = new QSignalMapper(this);
for (const QString &title : tag_titles) {
QAction *action = tag_menu->addAction(title, tag_mapper, SLOT(map()));
tag_mapper->setMapping(action, tags[title]);
}
connect(tag_mapper, SIGNAL(mapped(QString)), SLOT(InsertTag(QString)));
ui_->insert->setMenu(tag_menu);
}
OrganiseDialog::~OrganiseDialog() {
delete ui_;
}
void OrganiseDialog::SetDestinationModel(QAbstractItemModel *model, bool devices) {
ui_->destination->setModel(model);
ui_->eject_after->setVisible(devices);
}
bool OrganiseDialog::SetSongs(const SongList &songs) {
total_size_ = 0;
songs_.clear();
for (const Song &song : songs) {
if (song.url().scheme() != "file") {
continue;
}
if (song.filesize() > 0) total_size_ += song.filesize();
songs_ << song;
}
ui_->free_space->set_additional_bytes(total_size_);
UpdatePreviews();
SetLoadingSongs(false);
if (songs_future_.isRunning()) {
songs_future_.cancel();
}
songs_future_ = QFuture<SongList>();
return songs_.count();
}
bool OrganiseDialog::SetUrls(const QList<QUrl> &urls) {
QStringList filenames;
// Only add file:// URLs
for (const QUrl &url : urls) {
if (url.scheme() == "file") {
filenames << url.toLocalFile();
}
}
return SetFilenames(filenames);
}
bool OrganiseDialog::SetFilenames(const QStringList &filenames) {
songs_future_ = QtConcurrent::run(this, &OrganiseDialog::LoadSongsBlocking, filenames);
NewClosure(songs_future_, [=]() { SetSongs(songs_future_.result()); });
SetLoadingSongs(true);
return true;
}
void OrganiseDialog::SetLoadingSongs(bool loading) {
if (loading) {
ui_->preview_stack->setCurrentWidget(ui_->loading_page);
ui_->button_box->button(QDialogButtonBox::Ok)->setEnabled(false);
}
else {
ui_->preview_stack->setCurrentWidget(ui_->preview_page);
// The Ok button is enabled by UpdatePreviews
}
}
SongList OrganiseDialog::LoadSongsBlocking(const QStringList &filenames) {
SongList songs;
Song song;
QStringList filenames_copy = filenames;
while (!filenames_copy.isEmpty()) {
const QString filename = filenames_copy.takeFirst();
// If it's a directory, add all the files inside.
if (QFileInfo(filename).isDir()) {
const QDir dir(filename);
for (const QString &entry : dir.entryList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot | QDir::Readable)) {
filenames_copy << dir.filePath(entry);
}
continue;
}
TagReaderClient::Instance()->ReadFileBlocking(filename, &song);
if (song.is_valid()) songs << song;
}
return songs;
}
void OrganiseDialog::SetCopy(bool copy) {
ui_->aftercopying->setCurrentIndex(copy ? 0 : 1);
}
void OrganiseDialog::InsertTag(const QString &tag) {
ui_->naming->insertPlainText("%" + tag);
}
Organise::NewSongInfoList OrganiseDialog::ComputeNewSongsFilenames(const SongList &songs, const OrganiseFormat &format) {
// Check if we will have multiple files with the same name.
// If so, they will erase each other if the overwrite flag is set.
// Better to rename them: e.g. foo.bar -> foo(2).bar
QHash<QString, int> filenames;
Organise::NewSongInfoList new_songs_info;
for (const Song &song : songs) {
QString new_filename = format.GetFilenameForSong(song);
if (filenames.contains(new_filename)) {
QString song_number = QString::number(++filenames[new_filename]);
new_filename = Utilities::PathWithoutFilenameExtension(new_filename) + "(" + song_number + ")." + QFileInfo(new_filename).suffix();
}
filenames.insert(new_filename, 1);
new_songs_info << Organise::NewSongInfo(song, new_filename);
}
return new_songs_info;
}
void OrganiseDialog::UpdatePreviews() {
if (songs_future_.isRunning()) {
return;
}
const QModelIndex destination = ui_->destination->model()->index(ui_->destination->currentIndex(), 0);
std::shared_ptr<MusicStorage> storage;
bool has_local_destination = false;
if (destination.isValid()) {
storage = destination.data(MusicStorage::Role_Storage).value<std::shared_ptr<MusicStorage>>();
if (storage) {
has_local_destination = !storage->LocalPath().isEmpty();
}
}
// Update the free space bar
quint64 capacity = destination.data(MusicStorage::Role_Capacity).toLongLong();
quint64 free = destination.data(MusicStorage::Role_FreeSpace).toLongLong();
if (!capacity) {
ui_->free_space->hide();
}
else {
ui_->free_space->show();
ui_->free_space->set_free_bytes(free);
ui_->free_space->set_total_bytes(capacity);
}
// Update the format object
format_.set_format(ui_->naming->toPlainText());
format_.set_replace_non_ascii(ui_->replace_ascii->isChecked());
format_.set_replace_spaces(ui_->replace_spaces->isChecked());
format_.set_replace_the(ui_->replace_the->isChecked());
const bool format_valid = !has_local_destination || format_.IsValid();
// Are we gonna enable the ok button?
bool ok = format_valid && !songs_.isEmpty();
if (capacity != 0 && total_size_ > free) ok = false;
ui_->button_box->button(QDialogButtonBox::Ok)->setEnabled(ok);
if (!format_valid) return;
new_songs_info_ = ComputeNewSongsFilenames(songs_, format_);
// Update the previews
ui_->preview->clear();
ui_->preview_group->setVisible(has_local_destination);
ui_->naming_group->setVisible(has_local_destination);
if (has_local_destination) {
for (const Organise::NewSongInfo &song_info : new_songs_info_) {
QString filename = storage->LocalPath() + "/" + song_info.new_filename_;
ui_->preview->addItem(QDir::toNativeSeparators(filename));
}
}
if (!resized_by_user_) {
adjustSize();
}
}
QSize OrganiseDialog::sizeHint() const { return QSize(650, 0); }
void OrganiseDialog::Reset() {
ui_->naming->setPlainText(kDefaultFormat);
ui_->replace_ascii->setChecked(false);
ui_->replace_spaces->setChecked(false);
ui_->replace_the->setChecked(false);
ui_->overwrite->setChecked(false);
ui_->mark_as_listened->setChecked(false);
ui_->eject_after->setChecked(false);
}
void OrganiseDialog::showEvent(QShowEvent*) {
resized_by_user_ = false;
QSettings s;
s.beginGroup(kSettingsGroup);
ui_->naming->setPlainText(s.value("format", kDefaultFormat).toString());
ui_->replace_ascii->setChecked(s.value("replace_ascii", false).toBool());
ui_->replace_spaces->setChecked(s.value("replace_spaces", false).toBool());
ui_->replace_the->setChecked(s.value("replace_the", false).toBool());
ui_->overwrite->setChecked(s.value("overwrite", false).toBool());
ui_->mark_as_listened->setChecked(s.value("mark_as_listened", false).toBool());
ui_->eject_after->setChecked(s.value("eject_after", false).toBool());
QString destination = s.value("destination").toString();
int index = ui_->destination->findText(destination);
if (index != -1 && !destination.isEmpty()) {
ui_->destination->setCurrentIndex(index);
}
}
void OrganiseDialog::accept() {
QSettings s;
s.beginGroup(kSettingsGroup);
s.setValue("format", ui_->naming->toPlainText());
s.setValue("replace_ascii", ui_->replace_ascii->isChecked());
s.setValue("replace_spaces", ui_->replace_spaces->isChecked());
s.setValue("replace_the", ui_->replace_the->isChecked());
s.setValue("overwrite", ui_->overwrite->isChecked());
s.setValue("mark_as_listened", ui_->overwrite->isChecked());
s.setValue("destination", ui_->destination->currentText());
s.setValue("eject_after", ui_->eject_after->isChecked());
const QModelIndex destination = ui_->destination->model()->index(ui_->destination->currentIndex(), 0);
std::shared_ptr<MusicStorage> storage = destination.data(MusicStorage::Role_StorageForceConnect).value<std::shared_ptr<MusicStorage>>();
if (!storage) return;
// It deletes itself when it's finished.
const bool copy = ui_->aftercopying->currentIndex() == 0;
Organise *organise = new Organise(task_manager_, storage, format_, copy, ui_->overwrite->isChecked(), ui_->mark_as_listened->isChecked(), new_songs_info_, ui_->eject_after->isChecked());
connect(organise, SIGNAL(Finished(QStringList)), SLOT(OrganiseFinished(QStringList)));
connect(organise, SIGNAL(FileCopied(int)), this, SIGNAL(FileCopied(int)));
organise->Start();
QDialog::accept();
}
void OrganiseDialog::OrganiseFinished(const QStringList &files_with_errors) {
if (files_with_errors.isEmpty()) return;
error_dialog_.reset(new OrganiseErrorDialog);
error_dialog_->Show(OrganiseErrorDialog::Type_Copy, files_with_errors);
}
void OrganiseDialog::resizeEvent(QResizeEvent *e) {
if (e->spontaneous()) {
resized_by_user_ = true;
}
QDialog::resizeEvent(e);
}

View File

@@ -0,0 +1,110 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ORGANISEDIALOG_H
#define ORGANISEDIALOG_H
#include "config.h"
#include <memory>
#include <QDialog>
#include <QFuture>
#include <QMap>
#include <QUrl>
#include "gtest/gtest_prod.h"
#include "core/organise.h"
#include "core/organiseformat.h"
#include "core/song.h"
class CollectionWatcher;
class OrganiseErrorDialog;
class TaskManager;
class Ui_OrganiseDialog;
class QAbstractItemModel;
class OrganiseDialog : public QDialog {
Q_OBJECT
public:
OrganiseDialog(TaskManager *task_manager, QWidget *parent = nullptr);
~OrganiseDialog();
static const char *kDefaultFormat;
static const char *kSettingsGroup;
QSize sizeHint() const;
void SetDestinationModel(QAbstractItemModel *model, bool devices = false);
// These functions return true if any songs were actually added to the dialog.
// SetSongs returns immediately, SetUrls and SetFilenames load the songs in
// the background.
bool SetSongs(const SongList &songs);
bool SetUrls(const QList<QUrl> &urls);
bool SetFilenames(const QStringList &filenames);
void SetCopy(bool copy);
signals:
void FileCopied(int);
public slots:
void accept();
protected:
void showEvent(QShowEvent *);
void resizeEvent(QResizeEvent *);
private slots:
void Reset();
void InsertTag(const QString &tag);
void UpdatePreviews();
void OrganiseFinished(const QStringList &files_with_errors);
private:
SongList LoadSongsBlocking(const QStringList &filenames);
void SetLoadingSongs(bool loading);
static Organise::NewSongInfoList ComputeNewSongsFilenames(const SongList &songs, const OrganiseFormat &format);
Ui_OrganiseDialog *ui_;
TaskManager *task_manager_;
OrganiseFormat format_;
QFuture<SongList> songs_future_;
SongList songs_;
Organise::NewSongInfoList new_songs_info_;
quint64 total_size_;
std::unique_ptr<OrganiseErrorDialog> error_dialog_;
bool resized_by_user_;
FRIEND_TEST(OrganiseDialogTest, ComputeNewSongsFilenamesTest);
};
#endif // ORGANISEDIALOG_H

View File

@@ -0,0 +1,291 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OrganiseDialog</class>
<widget class="QDialog" name="OrganiseDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>588</width>
<height>525</height>
</rect>
</property>
<property name="windowTitle">
<string>Organise Files</string>
</property>
<property name="windowIcon">
<iconset resource="../../data/data.qrc">
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Destination</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="destination"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>After copying...</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="aftercopying">
<item>
<property name="text">
<string>Keep the original files</string>
</property>
</item>
<item>
<property name="text">
<string>Delete the original files</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="FreeSpaceBar" name="free_space" native="true"/>
</item>
<item>
<widget class="QCheckBox" name="eject_after">
<property name="text">
<string>Safely remove the device after copying</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="naming_group">
<property name="title">
<string>Naming options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="LineTextEdit" name="naming">
<property name="toolTip">
<string>&lt;p&gt;Tokens start with %, for example: %artist %album %title &lt;/p&gt;
&lt;p&gt;If you surround sections of text that contain a token with curly-braces, that section will be hidden if the token is empty.&lt;/p&gt;</string>
</property>
<property name="lineWrapMode">
<enum>QTextEdit::NoWrap</enum>
</property>
<property name="acceptRichText">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="insert">
<property name="text">
<string>Insert...</string>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="replace_the">
<property name="text">
<string>Ignore &quot;The&quot; in artist names</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="replace_spaces">
<property name="text">
<string>Replaces spaces with underscores</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="replace_ascii">
<property name="text">
<string>Restrict to ASCII characters</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="overwrite">
<property name="text">
<string>Overwrite existing files</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mark_as_listened">
<property name="text">
<string>Mark as listened</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="preview_group">
<property name="title">
<string>Preview</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QStackedWidget" name="preview_stack">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="preview_page">
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QListWidget" name="preview"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="loading_page">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>264</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="BusyIndicator" name="loading_indicator" native="true">
<property name="text" stdset="0">
<string>Loading...</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>264</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="button_box">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>FreeSpaceBar</class>
<extends>QWidget</extends>
<header>widgets/freespacebar.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>LineTextEdit</class>
<extends>QTextEdit</extends>
<header>widgets/linetextedit.h</header>
</customwidget>
<customwidget>
<class>BusyIndicator</class>
<extends>QWidget</extends>
<header>widgets/busyindicator.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>destination</tabstop>
<tabstop>aftercopying</tabstop>
<tabstop>eject_after</tabstop>
<tabstop>naming</tabstop>
<tabstop>insert</tabstop>
<tabstop>replace_the</tabstop>
<tabstop>replace_spaces</tabstop>
<tabstop>replace_ascii</tabstop>
<tabstop>overwrite</tabstop>
<tabstop>button_box</tabstop>
</tabstops>
<resources>
<include location="../../data/data.qrc"/>
</resources>
<connections>
<connection>
<sender>button_box</sender>
<signal>accepted()</signal>
<receiver>OrganiseDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>257</x>
<y>487</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>button_box</sender>
<signal>rejected()</signal>
<receiver>OrganiseDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>325</x>
<y>487</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,73 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QUrl>
#include "organiseerrordialog.h"
#include "ui_organiseerrordialog.h"
OrganiseErrorDialog::OrganiseErrorDialog(QWidget *parent) : QDialog(parent), ui_(new Ui_OrganiseErrorDialog) {
ui_->setupUi(this);
const int icon_size = style()->pixelMetric(QStyle::PM_MessageBoxIconSize, 0, this);
QIcon icon = style()->standardIcon(QStyle::SP_MessageBoxCritical, 0, this);
ui_->icon->setPixmap(icon.pixmap(icon_size));
}
OrganiseErrorDialog::~OrganiseErrorDialog() {
delete ui_;
}
void OrganiseErrorDialog::Show(OperationType type, const SongList &songs_with_errors) {
QStringList files;
for (const Song &song : songs_with_errors) {
files << song.url().toLocalFile();
}
Show(type, files);
}
void OrganiseErrorDialog::Show(OperationType type, const QStringList &files_with_errors) {
QStringList sorted_files = files_with_errors;
qStableSort(sorted_files);
switch (type) {
case Type_Copy:
setWindowTitle(tr("Error copying songs"));
ui_->label->setText(tr("There were problems copying some songs. The following files could not be copied:"));
break;
case Type_Delete:
setWindowTitle(tr("Error deleting songs"));
ui_->label->setText(tr("There were problems deleting some songs. The following files could not be deleted:"));
break;
}
ui_->list->addItems(sorted_files);
show();
}

View File

@@ -0,0 +1,51 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ORGANISEERRORDIALOG_H
#define ORGANISEERRORDIALOG_H
#include "config.h"
#include <QDialog>
#include "core/song.h"
class Ui_OrganiseErrorDialog;
class OrganiseErrorDialog : public QDialog {
Q_OBJECT
public:
OrganiseErrorDialog(QWidget *parent = nullptr);
~OrganiseErrorDialog();
enum OperationType {
Type_Copy,
Type_Delete,
};
void Show(OperationType type, const SongList& songs_with_errors);
void Show(OperationType type, const QStringList &files_with_errors);
private:
Ui_OrganiseErrorDialog *ui_;
};
#endif // ORGANISEERRORDIALOG_H

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OrganiseErrorDialog</class>
<widget class="QDialog" name="OrganiseErrorDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>779</width>
<height>355</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="icon"/>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label"/>
</item>
<item>
<widget class="QListWidget" name="list"/>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>OrganiseErrorDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>OrganiseErrorDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,316 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QFileInfo>
#include <QFutureWatcher>
#include <QPushButton>
#include <QShortcut>
#include <QTreeWidget>
#include <QUrl>
#include <QtConcurrentRun>
#include <QtDebug>
#include "trackselectiondialog.h"
#include "ui_trackselectiondialog.h"
#include "core/tagreaderclient.h"
#include "core/iconloader.h"
TrackSelectionDialog::TrackSelectionDialog(QWidget *parent)
: QDialog(parent),
ui_(new Ui_TrackSelectionDialog),
save_on_close_(false)
{
// Setup dialog window
ui_->setupUi(this);
connect(ui_->song_list, SIGNAL(currentRowChanged(int)), SLOT(UpdateStack()));
connect(ui_->results, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), SLOT(ResultSelected()));
ui_->splitter->setSizes(QList<int>() << 200 << width() - 200);
SetLoading(QString());
// Add the next/previous buttons
previous_button_ = new QPushButton(IconLoader::Load("go-previous"), tr("Previous"), this);
next_button_ = new QPushButton(IconLoader::Load("go-next"), tr("Next"), this);
ui_->button_box->addButton(previous_button_, QDialogButtonBox::ResetRole);
ui_->button_box->addButton(next_button_, QDialogButtonBox::ResetRole);
connect(previous_button_, SIGNAL(clicked()), SLOT(PreviousSong()));
connect(next_button_, SIGNAL(clicked()), SLOT(NextSong()));
// Set some shortcuts for the buttons
new QShortcut(QKeySequence::Back, previous_button_, SLOT(click()));
new QShortcut(QKeySequence::Forward, next_button_, SLOT(click()));
new QShortcut(QKeySequence::MoveToPreviousPage, previous_button_, SLOT(click()));
new QShortcut(QKeySequence::MoveToNextPage, next_button_, SLOT(click()));
// Resize columns
ui_->results->setColumnWidth(0, 50); // Track column
ui_->results->setColumnWidth(1, 50); // Year column
ui_->results->setColumnWidth(2, 160); // Title column
ui_->results->setColumnWidth(3, 160); // Artist column
ui_->results->setColumnWidth(4, 160); // Album column
}
TrackSelectionDialog::~TrackSelectionDialog() {
delete ui_;
}
void TrackSelectionDialog::Init(const SongList &songs) {
ui_->song_list->clear();
ui_->stack->setCurrentWidget(ui_->loading_page);
data_.clear();
for (const Song &song : songs) {
Data data;
data.original_song_ = song;
data_ << data;
QListWidgetItem *item = new QListWidgetItem(ui_->song_list);
item->setText(QFileInfo(song.url().toLocalFile()).fileName());
item->setForeground(palette().color(QPalette::Disabled, QPalette::Text));
}
const bool multiple = songs.count() > 1;
ui_->song_list->setVisible(multiple);
next_button_->setEnabled(multiple);
previous_button_->setEnabled(multiple);
ui_->song_list->setCurrentRow(0);
}
void TrackSelectionDialog::FetchTagProgress(const Song &original_song, const QString &progress) {
// Find the item with this filename
int row = -1;
for (int i = 0; i < data_.count(); ++i) {
if (data_[i].original_song_.url() == original_song.url()) {
row = i;
break;
}
}
if (row == -1) return;
data_[row].progress_string_ = progress;
// If it's the current item, update the display
if (ui_->song_list->currentIndex().row() == row) {
UpdateStack();
}
}
void TrackSelectionDialog::FetchTagFinished(const Song &original_song, const SongList &songs_guessed) {
// Find the item with this filename
int row = -1;
for (int i = 0; i < data_.count(); ++i) {
if (data_[i].original_song_.url() == original_song.url()) {
row = i;
break;
}
}
if (row == -1) return;
// Set the color back to black
ui_->song_list->item(row)->setForeground(palette().text());
// Add the results to the list
data_[row].pending_ = false;
data_[row].results_ = songs_guessed;
// If it's the current item, update the display
if (ui_->song_list->currentIndex().row() == row) {
UpdateStack();
}
}
void TrackSelectionDialog::UpdateStack() {
const int row = ui_->song_list->currentRow();
if (row < 0 || row >= data_.count()) return;
const Data &data = data_[row];
if (data.pending_) {
ui_->stack->setCurrentWidget(ui_->loading_page);
ui_->progress->set_text(data.progress_string_ + "...");
return;
}
else if (data.results_.isEmpty()) {
ui_->stack->setCurrentWidget(ui_->error_page);
return;
}
ui_->stack->setCurrentWidget(ui_->results_page);
// Clear tree widget
ui_->results->clear();
// Put the original tags at the top
AddDivider(tr("Original tags"), ui_->results);
AddSong(data.original_song_, -1, ui_->results);
// Fill tree view with songs
AddDivider(tr("Suggested tags"), ui_->results);
int song_index = 0;
for (const Song &song : data.results_) {
AddSong(song, song_index++, ui_->results);
}
// Find the item that was selected last time
for (int i = 0; i < ui_->results->model()->rowCount(); ++i) {
const QModelIndex index = ui_->results->model()->index(i, 0);
const QVariant id = index.data(Qt::UserRole);
if (!id.isNull() && id.toInt() == data.selected_result_) {
ui_->results->setCurrentIndex(index);
break;
}
}
}
void TrackSelectionDialog::AddDivider(const QString &text, QTreeWidget *parent) const {
QTreeWidgetItem *item = new QTreeWidgetItem(parent);
item->setFirstColumnSpanned(true);
item->setText(0, text);
item->setFlags(Qt::NoItemFlags);
item->setForeground(0, palette().color(QPalette::Disabled, QPalette::Text));
QFont bold_font(font());
bold_font.setBold(true);
item->setFont(0, bold_font);
}
void TrackSelectionDialog::AddSong(const Song &song, int result_index, QTreeWidget *parent) const {
QStringList values;
values << ((song.track() > 0) ? QString::number(song.track()) : QString()) << ((song.year() > 0) ? QString::number(song.year()) : QString()) << song.title() << song.artist() << song.album();
QTreeWidgetItem *item = new QTreeWidgetItem(parent, values);
item->setData(0, Qt::UserRole, result_index);
item->setData(0, Qt::TextAlignmentRole, Qt::AlignRight);
}
void TrackSelectionDialog::ResultSelected() {
if (!ui_->results->currentItem()) return;
const int song_row = ui_->song_list->currentRow();
if (song_row == -1) return;
const int result_index = ui_->results->currentItem()->data(0, Qt::UserRole).toInt();
data_[song_row].selected_result_ = result_index;
}
void TrackSelectionDialog::SetLoading(const QString &message) {
const bool loading = !message.isEmpty();
ui_->button_box->setEnabled(!loading);
ui_->splitter->setEnabled(!loading);
ui_->loading_label->setVisible(loading);
ui_->loading_label->set_text(message);
}
void TrackSelectionDialog::SaveData(const QList<Data> &data) {
for (int i = 0; i < data.count(); ++i) {
const Data &ref = data[i];
if (ref.pending_ || ref.results_.isEmpty() || ref.selected_result_ == -1)
continue;
const Song &new_metadata = ref.results_[ref.selected_result_];
Song copy(ref.original_song_);
copy.set_title(new_metadata.title());
copy.set_artist(new_metadata.artist());
copy.set_album(new_metadata.album());
copy.set_track(new_metadata.track());
copy.set_year(new_metadata.year());
if (!TagReaderClient::Instance()->SaveFileBlocking(copy.url().toLocalFile(), copy)) {
qLog(Warning) << "Failed to write new auto-tags to" << copy.url().toLocalFile();
}
}
}
void TrackSelectionDialog::accept() {
if (save_on_close_) {
SetLoading(tr("Saving tracks") + "...");
// Save tags in the background
QFuture<void> future = QtConcurrent::run(&TrackSelectionDialog::SaveData, data_);
QFutureWatcher<void> *watcher = new QFutureWatcher<void>(this);
watcher->setFuture(future);
connect(watcher, SIGNAL(finished()), SLOT(AcceptFinished()));
return;
}
QDialog::accept();
for (const Data &data : data_) {
if (data.pending_ || data.results_.isEmpty() || data.selected_result_ == -1)
continue;
const Song &new_metadata = data.results_[data.selected_result_];
emit SongChosen(data.original_song_, new_metadata);
}
}
void TrackSelectionDialog::AcceptFinished() {
QFutureWatcher<void> *watcher = dynamic_cast<QFutureWatcher<void>*>(sender());
if (!watcher) return;
watcher->deleteLater();
SetLoading(QString());
QDialog::accept();
}
void TrackSelectionDialog::NextSong() {
int row = (ui_->song_list->currentRow() + 1) % ui_->song_list->count();
ui_->song_list->setCurrentRow(row);
}
void TrackSelectionDialog::PreviousSong() {
int row = (ui_->song_list->currentRow() - 1 + ui_->song_list->count()) % ui_->song_list->count();
ui_->song_list->setCurrentRow(row);
}

View File

@@ -0,0 +1,93 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TRACKSELECTIONDIALOG_H
#define TRACKSELECTIONDIALOG_H
#include "config.h"
#include <QDialog>
#include "core/song.h"
class Ui_TrackSelectionDialog;
class QTreeWidget;
class TrackSelectionDialog : public QDialog {
Q_OBJECT
public:
TrackSelectionDialog(QWidget *parent = nullptr);
~TrackSelectionDialog();
void set_save_on_close(bool save_on_close) { save_on_close_ = save_on_close; }
void Init(const SongList &songs);
public slots:
void FetchTagProgress(const Song &original_song, const QString &progress);
void FetchTagFinished(const Song &original_song, const SongList &songs_guessed);
// QDialog
void accept();
signals:
void SongChosen(const Song &original_song, const Song &new_metadata);
private slots:
void UpdateStack();
void NextSong();
void PreviousSong();
void ResultSelected();
void AcceptFinished();
private:
Ui_TrackSelectionDialog *ui_;
struct Data {
Data() : pending_(true), selected_result_(0) {}
Song original_song_;
bool pending_;
QString progress_string_;
SongList results_;
int selected_result_;
};
void AddDivider(const QString &text, QTreeWidget *parent) const;
void AddSong(const Song &song, int result_index, QTreeWidget *parent) const;
void SetLoading(const QString &message);
static void SaveData(const QList<Data> &data);
private:
QList<Data> data_;
QPushButton *previous_button_;
QPushButton *next_button_;
bool save_on_close_;
};
#endif // TRACKSELECTIONDIALOG_H

View File

@@ -0,0 +1,290 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TrackSelectionDialog</class>
<widget class="QDialog" name="TrackSelectionDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>773</width>
<height>375</height>
</rect>
</property>
<property name="windowTitle">
<string>Tag fetcher</string>
</property>
<property name="windowIcon">
<iconset resource="../../data/data.qrc">
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QSplitter" name="splitter">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QListWidget" name="song_list">
<property name="uniformItemSizes">
<bool>true</bool>
</property>
</widget>
<widget class="QStackedWidget" name="stack">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="loading_page">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>133</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="BusyIndicator" name="progress" native="true"/>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>133</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="error_page">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>124</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="styleSheet">
<string notr="true">QLabel { font-weight: bold; }</string>
</property>
<property name="text">
<string>Sorry</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Strawberry was unable to find results for this file</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>124</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="results_page">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="styleSheet">
<string notr="true">QLabel { font-weight: bold; }</string>
</property>
<property name="text">
<string>Select best possible match</string>
</property>
</widget>
</item>
<item>
<widget class="QTreeWidget" name="results">
<property name="editTriggers">
<set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed</set>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<attribute name="headerDefaultSectionSize">
<number>150</number>
</attribute>
<attribute name="headerMinimumSectionSize">
<number>50</number>
</attribute>
<column>
<property name="text">
<string>Track</string>
</property>
</column>
<column>
<property name="text">
<string>Year</string>
</property>
</column>
<column>
<property name="text">
<string>Title</string>
</property>
</column>
<column>
<property name="text">
<string>Artist</string>
</property>
</column>
<column>
<property name="text">
<string>Album</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="BusyIndicator" name="loading_label" native="true"/>
</item>
<item>
<widget class="QDialogButtonBox" name="button_box">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>BusyIndicator</class>
<extends>QWidget</extends>
<header>widgets/busyindicator.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../../data/data.qrc"/>
</resources>
<connections>
<connection>
<sender>button_box</sender>
<signal>accepted()</signal>
<receiver>TrackSelectionDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>button_box</sender>
<signal>rejected()</signal>
<receiver>TrackSelectionDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>