basic support for MusicBrainz queries
This commit is contained in:
parent
f8cfb3ec14
commit
95993ebfc9
|
@ -49,7 +49,6 @@ set(WIDGETS_HEADER_FILES
|
|||
gui/pathlineedit.h
|
||||
gui/picturepreviewselection.h
|
||||
gui/filefilterproxymodel.h
|
||||
gui/infowidgetbase.h
|
||||
gui/initiate.h
|
||||
gui/previousvaluehandling.h
|
||||
gui/renamefilesdialog.h
|
||||
|
@ -57,6 +56,9 @@ set(WIDGETS_HEADER_FILES
|
|||
gui/tagedit.h
|
||||
gui/tagfieldedit.h
|
||||
gui/tageditorwidget.h
|
||||
dbquery/dbquery.h
|
||||
gui/dbquerywidget.h
|
||||
misc/networkaccessmanager.h
|
||||
renamingutility/filesystemitem.h
|
||||
renamingutility/filesystemitemmodel.h
|
||||
renamingutility/filteredfilesystemitemmodel.h
|
||||
|
@ -76,7 +78,6 @@ set(WIDGETS_SRC_FILES
|
|||
gui/pathlineedit.cpp
|
||||
gui/picturepreviewselection.cpp
|
||||
gui/filefilterproxymodel.cpp
|
||||
gui/infowidgetbase.cpp
|
||||
gui/initiate.cpp
|
||||
gui/javascripthighlighter.cpp
|
||||
gui/renamefilesdialog.cpp
|
||||
|
@ -84,6 +85,9 @@ set(WIDGETS_SRC_FILES
|
|||
gui/tagedit.cpp
|
||||
gui/tagfieldedit.cpp
|
||||
gui/tageditorwidget.cpp
|
||||
dbquery/dbquery.cpp
|
||||
gui/dbquerywidget.cpp
|
||||
misc/networkaccessmanager.cpp
|
||||
renamingutility/filesystemitem.cpp
|
||||
renamingutility/filesystemitemmodel.cpp
|
||||
renamingutility/filteredfilesystemitemmodel.cpp
|
||||
|
@ -109,6 +113,7 @@ set(WIDGETS_UI_FILES
|
|||
gui/editortempoptionpage.ui
|
||||
gui/filelayout.ui
|
||||
gui/tageditorwidget.ui
|
||||
gui/dbquerywidget.ui
|
||||
)
|
||||
#set(QUICK_HEADER_FILES
|
||||
#)
|
||||
|
|
|
@ -97,6 +97,12 @@ KnownFieldModel::KnownFieldModel(QObject *parent, DefaultSelection defaultSelect
|
|||
setItems(items);
|
||||
}
|
||||
|
||||
KnownFieldModel::KnownFieldModel(const QList<Models::ChecklistItem> &items, QObject *parent) :
|
||||
ChecklistModel(parent)
|
||||
{
|
||||
setItems(items);
|
||||
}
|
||||
|
||||
QVariant KnownFieldModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
switch(orientation) {
|
||||
|
|
|
@ -24,9 +24,10 @@ public:
|
|||
|
||||
static const char *fieldName(Media::KnownField field);
|
||||
static QString translatedFieldName(Media::KnownField field);
|
||||
static Models::ChecklistItem mkItem(Media::KnownField field, Qt::CheckState checkState);
|
||||
static Models::ChecklistItem mkItem(Media::KnownField field, Qt::CheckState checkState = Qt::Checked);
|
||||
|
||||
explicit KnownFieldModel(QObject *parent = nullptr, DefaultSelection defaultSelection = DefaultSelection::None);
|
||||
explicit KnownFieldModel(const QList<Models::ChecklistItem> &items, QObject *parent = nullptr);
|
||||
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
|
||||
virtual QString labelForId(const QVariant &id) const;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "./knownfieldmodel.h"
|
||||
|
||||
#include <tagparser/mediafileinfo.h>
|
||||
#include <tagparser/tag.h>
|
||||
#include <tagparser/backuphelper.h>
|
||||
|
||||
#include <QString>
|
||||
|
@ -213,6 +214,32 @@ QString &mainWindowCurrentFileBrowserDirectory()
|
|||
return v;
|
||||
}
|
||||
|
||||
// db query
|
||||
bool &dbQueryWidgetShown()
|
||||
{
|
||||
static bool v;
|
||||
return v;
|
||||
}
|
||||
|
||||
bool &dbQueryOverride()
|
||||
{
|
||||
static bool v;
|
||||
return v;
|
||||
}
|
||||
|
||||
KnownFieldModel &dbQueryFields()
|
||||
{
|
||||
static KnownFieldModel v(QList<Models::ChecklistItem>()
|
||||
<< KnownFieldModel::mkItem(KnownField::Title)
|
||||
<< KnownFieldModel::mkItem(KnownField::TrackPosition)
|
||||
<< KnownFieldModel::mkItem(KnownField::DiskPosition)
|
||||
<< KnownFieldModel::mkItem(KnownField::Album)
|
||||
<< KnownFieldModel::mkItem(KnownField::Album)
|
||||
<< KnownFieldModel::mkItem(KnownField::Year)
|
||||
<< KnownFieldModel::mkItem(KnownField::Genre));
|
||||
return v;
|
||||
}
|
||||
|
||||
// renaming files dialog
|
||||
int &scriptSource()
|
||||
{
|
||||
|
@ -226,8 +253,6 @@ QString &externalScript()
|
|||
return v;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void restore()
|
||||
{
|
||||
QSettings settings(QSettings::IniFormat, QSettings::UserScope, QApplication::organizationName(), QApplication::applicationName());
|
||||
|
@ -256,10 +281,10 @@ void restore()
|
|||
}
|
||||
hideTagSelectionComboBox() = settings.value(QStringLiteral("hidetagselectioncombobox"), true).toBool();
|
||||
settings.beginGroup(QStringLiteral("autocorrection"));
|
||||
Settings::insertTitleFromFilename() = settings.value(QStringLiteral("inserttitlefromfilename"), false).toBool();
|
||||
Settings::trimWhitespaces() = settings.value(QStringLiteral("trimwhitespaces"), true).toBool();
|
||||
Settings::formatNames() = settings.value(QStringLiteral("formatnames"), false).toBool();
|
||||
Settings::fixUmlauts() = settings.value(QStringLiteral("fixumlauts"), false).toBool();
|
||||
insertTitleFromFilename() = settings.value(QStringLiteral("inserttitlefromfilename"), false).toBool();
|
||||
trimWhitespaces() = settings.value(QStringLiteral("trimwhitespaces"), true).toBool();
|
||||
formatNames() = settings.value(QStringLiteral("formatnames"), false).toBool();
|
||||
fixUmlauts() = settings.value(QStringLiteral("fixumlauts"), false).toBool();
|
||||
settings.endGroup();
|
||||
BackupHelper::backupDirectory() = settings.value(QStringLiteral("tempdir")).toString().toStdString();
|
||||
settings.endGroup();
|
||||
|
@ -354,14 +379,20 @@ void restore()
|
|||
settings.endGroup();
|
||||
|
||||
settings.beginGroup(QStringLiteral("mainwindow"));
|
||||
Settings::mainWindowGeometry() = settings.value(QStringLiteral("geometry")).toByteArray();
|
||||
Settings::mainWindowState() = settings.value(QStringLiteral("windowstate")).toByteArray();
|
||||
Settings::mainWindowCurrentFileBrowserDirectory() = settings.value(QStringLiteral("currentfilebrowserdirectory")).toString();
|
||||
mainWindowGeometry() = settings.value(QStringLiteral("geometry")).toByteArray();
|
||||
mainWindowState() = settings.value(QStringLiteral("windowstate")).toByteArray();
|
||||
mainWindowCurrentFileBrowserDirectory() = settings.value(QStringLiteral("currentfilebrowserdirectory")).toString();
|
||||
settings.endGroup();
|
||||
|
||||
settings.beginGroup(QStringLiteral("dbquery"));
|
||||
dbQueryWidgetShown() = settings.value(QStringLiteral("visible"), false).toBool();
|
||||
dbQueryOverride() = settings.value(QStringLiteral("override"), true).toBool();
|
||||
dbQueryFields().restore(settings, QStringLiteral("fields"));
|
||||
settings.endGroup();
|
||||
|
||||
settings.beginGroup(QStringLiteral("renamedlg"));
|
||||
Settings::scriptSource() = settings.value(QStringLiteral("src")).toInt();
|
||||
Settings::externalScript() = settings.value(QStringLiteral("file")).toString();
|
||||
scriptSource() = settings.value(QStringLiteral("src")).toInt();
|
||||
externalScript() = settings.value(QStringLiteral("file")).toString();
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
|
@ -428,6 +459,12 @@ void save()
|
|||
settings.setValue(QStringLiteral("currentfilebrowserdirectory"), mainWindowCurrentFileBrowserDirectory());
|
||||
settings.endGroup();
|
||||
|
||||
settings.beginGroup(QStringLiteral("dbquery"));
|
||||
settings.setValue(QStringLiteral("visible"), dbQueryWidgetShown());
|
||||
settings.setValue(QStringLiteral("override"), dbQueryOverride());
|
||||
dbQueryFields().save(settings, QStringLiteral("fields"));
|
||||
settings.endGroup();
|
||||
|
||||
settings.beginGroup(QStringLiteral("renamedlg"));
|
||||
settings.setValue(QStringLiteral("src"), Settings::scriptSource());
|
||||
settings.setValue(QStringLiteral("file"), Settings::externalScript());
|
||||
|
|
|
@ -91,6 +91,11 @@ QByteArray &mainWindowGeometry();
|
|||
QByteArray &mainWindowState();
|
||||
QString &mainWindowCurrentFileBrowserDirectory();
|
||||
|
||||
// db query
|
||||
bool &dbQueryWidgetShown();
|
||||
bool &dbQueryOverride();
|
||||
KnownFieldModel &dbQueryFields();
|
||||
|
||||
// rename files dialog
|
||||
int &scriptSource();
|
||||
QString &externalScript();
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# MusicBrainz API
|
||||
Available at ```http://musicbrainz.org/ws/2```
|
||||
|
||||
## Most useful queries
|
||||
* artist by name
|
||||
```
|
||||
/artist/?query=artist:$artist_name&inc=releases
|
||||
```
|
||||
* albums of artist
|
||||
```
|
||||
/artist/$artist_id?inc=releases
|
||||
```
|
||||
* songs of album
|
||||
```
|
||||
/release/$album_id?inc=recordings
|
||||
```
|
||||
* song by name, album and artist
|
||||
```
|
||||
/recording?query="$song_name" AND artist:"$artist_name" AND release:"$release_name"
|
||||
```
|
||||
|
||||
## Misc
|
||||
* enable JSON response: ```&fmt=json```
|
||||
|
||||
## See also
|
||||
* [Web Service](https://wiki.musicbrainz.org/Development/JSON_Web_Service)
|
||||
* [Web Service/Search](https://wiki.musicbrainz.org/Development/XML_Web_Service/Version_2/Search)
|
|
@ -0,0 +1,340 @@
|
|||
#include "./dbquery.h"
|
||||
|
||||
#include "../misc/utility.h"
|
||||
#include "../misc/networkaccessmanager.h"
|
||||
|
||||
#include <tagparser/tagvalue.h>
|
||||
#include <tagparser/tag.h>
|
||||
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QUrlQuery>
|
||||
#include <QStringBuilder>
|
||||
|
||||
#define xmlReader m_reader
|
||||
#include <qtutilities/misc/xmlparsermacros.h>
|
||||
|
||||
using namespace Utility;
|
||||
using namespace Media;
|
||||
|
||||
namespace QtGui {
|
||||
|
||||
SongDescription::SongDescription() :
|
||||
track(0),
|
||||
totalTracks(0),
|
||||
disk(0)
|
||||
{}
|
||||
|
||||
QueryResultsModel::QueryResultsModel(QObject *parent) :
|
||||
QAbstractTableModel(parent),
|
||||
m_resultsAvailable(false)
|
||||
{}
|
||||
|
||||
void QueryResultsModel::setResultsAvailable(bool resultsAvailable)
|
||||
{
|
||||
if(resultsAvailable != m_resultsAvailable) {
|
||||
if(m_resultsAvailable = resultsAvailable) {
|
||||
emit this->resultsAvailable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define returnValue(field) return qstringToTagValue(res.field, TagTextEncoding::Utf16LittleEndian)
|
||||
|
||||
TagValue QueryResultsModel::fieldValue(int row, KnownField knownField) const
|
||||
{
|
||||
if(row < m_results.size()) {
|
||||
const SongDescription &res = m_results.at(row);
|
||||
switch(knownField) {
|
||||
case KnownField::Title:
|
||||
returnValue(title);
|
||||
case KnownField::Album:
|
||||
returnValue(album);
|
||||
case KnownField::Artist:
|
||||
returnValue(artist);
|
||||
case KnownField::Genre:
|
||||
returnValue(genre);
|
||||
case KnownField::Year:
|
||||
returnValue(year);
|
||||
case KnownField::TrackPosition:
|
||||
return TagValue(PositionInSet(res.track, res.totalTracks));
|
||||
case KnownField::PartNumber:
|
||||
return TagValue(res.track);
|
||||
case KnownField::TotalParts:
|
||||
return TagValue(res.totalTracks);
|
||||
default:
|
||||
;
|
||||
}
|
||||
}
|
||||
return TagValue();
|
||||
}
|
||||
|
||||
#undef returnValue
|
||||
|
||||
QVariant QueryResultsModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if(index.isValid() && index.row() < m_results.size()) {
|
||||
const SongDescription &res = m_results.at(index.row());
|
||||
switch(role) {
|
||||
case Qt::DisplayRole:
|
||||
switch(index.column()) {
|
||||
case TitleCol:
|
||||
return res.title;
|
||||
case AlbumCol:
|
||||
return res.album;
|
||||
case ArtistCol:
|
||||
return res.artist;
|
||||
case GenreCol:
|
||||
return res.genre;
|
||||
case YearCol:
|
||||
return res.year;
|
||||
case TrackCol:
|
||||
if(res.track) {
|
||||
return res.track;
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
case TotalTracksCol:
|
||||
if(res.totalTracks) {
|
||||
return res.totalTracks;
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
default:
|
||||
;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
Qt::ItemFlags QueryResultsModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
Qt::ItemFlags flags = Qt::ItemNeverHasChildren | Qt::ItemIsSelectable | Qt::ItemIsEnabled;
|
||||
if(index.isValid()) {
|
||||
flags |= Qt::ItemIsUserCheckable;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
QVariant QueryResultsModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
switch(orientation) {
|
||||
case Qt::Horizontal:
|
||||
switch(role) {
|
||||
case Qt::DisplayRole:
|
||||
switch(section) {
|
||||
case TitleCol:
|
||||
return tr("Song title");
|
||||
case AlbumCol:
|
||||
return tr("Album");
|
||||
case ArtistCol:
|
||||
return tr("Artist");
|
||||
case YearCol:
|
||||
return tr("Year");
|
||||
case TrackCol:
|
||||
return tr("Track");
|
||||
case TotalTracksCol:
|
||||
return tr("Total tracks");
|
||||
case GenreCol:
|
||||
return tr("Genre");
|
||||
default:
|
||||
;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
int QueryResultsModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return parent.isValid() ? 0 : m_results.size();
|
||||
}
|
||||
|
||||
int QueryResultsModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return parent.isValid() ? 0 : (TotalTracksCol + 1);
|
||||
}
|
||||
|
||||
class MusicBrainzResultsModel : public QueryResultsModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MusicBrainzResultsModel(QNetworkReply *reply);
|
||||
~MusicBrainzResultsModel();
|
||||
|
||||
private slots:
|
||||
void parseResults();
|
||||
|
||||
private:
|
||||
QNetworkReply *m_reply;
|
||||
QXmlStreamReader m_reader;
|
||||
};
|
||||
|
||||
MusicBrainzResultsModel::MusicBrainzResultsModel(QNetworkReply *reply) :
|
||||
m_reply(reply)
|
||||
{
|
||||
connect(reply, &QNetworkReply::finished, this, &MusicBrainzResultsModel::parseResults);
|
||||
m_reader.setDevice(m_reply);
|
||||
}
|
||||
|
||||
MusicBrainzResultsModel::~MusicBrainzResultsModel()
|
||||
{
|
||||
if(m_reply) {
|
||||
m_reply->abort();
|
||||
delete m_reply;
|
||||
}
|
||||
}
|
||||
|
||||
void MusicBrainzResultsModel::parseResults()
|
||||
{
|
||||
beginResetModel();
|
||||
|
||||
// check for network error
|
||||
switch(m_reply->error()) {
|
||||
case QNetworkReply::NoError:
|
||||
// parse XML tree
|
||||
children {
|
||||
iftag("metadata") {
|
||||
children {
|
||||
iftag("recording-list") {
|
||||
children {
|
||||
iftag("recording") {
|
||||
SongDescription currentDescription;
|
||||
children {
|
||||
iftag("title") {
|
||||
currentDescription.title = text;
|
||||
} eliftag("artist-credit") {
|
||||
children {
|
||||
iftag("name-credit") {
|
||||
children {
|
||||
iftag("artist") {
|
||||
children {
|
||||
iftag("name") {
|
||||
currentDescription.artist = text;
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} eliftag("release-list") {
|
||||
children {
|
||||
iftag("release") {
|
||||
children {
|
||||
iftag("title") {
|
||||
currentDescription.album = text;
|
||||
} eliftag("date") {
|
||||
currentDescription.year = text;
|
||||
} eliftag("medium-list") {
|
||||
children {
|
||||
iftag("medium") {
|
||||
children {
|
||||
iftag("position") {
|
||||
currentDescription.disk = text.toUInt();
|
||||
} eliftag("track-list") {
|
||||
currentDescription.totalTracks = attribute("count").toUInt();
|
||||
children {
|
||||
iftag("track") {
|
||||
children {
|
||||
iftag("number") {
|
||||
currentDescription.track = text.toUInt();
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} eliftag("tag-list") {
|
||||
children {
|
||||
iftag("tag") {
|
||||
children {
|
||||
iftag("name") {
|
||||
if(!currentDescription.genre.isEmpty()) {
|
||||
currentDescription.genre.append(QLatin1Char(' '));
|
||||
}
|
||||
currentDescription.genre.append(text);
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
m_results << currentDescription;
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
|
||||
// check for parsing errors
|
||||
switch(m_reader.error()) {
|
||||
case QXmlStreamReader::NoError:
|
||||
case QXmlStreamReader::PrematureEndOfDocumentError:
|
||||
break;
|
||||
default:
|
||||
m_errorList << m_reader.errorString();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
m_errorList << m_reply->errorString();
|
||||
}
|
||||
|
||||
// delete reply
|
||||
m_reply->deleteLater();
|
||||
m_reader.setDevice(m_reply = nullptr);
|
||||
|
||||
// promote changes
|
||||
endResetModel();
|
||||
setResultsAvailable(true);
|
||||
}
|
||||
|
||||
QueryResultsModel *queryMusicBrainz(const SongDescription &songDescription)
|
||||
{
|
||||
static QString musicBrainzUrl(QStringLiteral("https://musicbrainz.org/ws/2/recording"));
|
||||
// TODO: make this configurable
|
||||
|
||||
// compose parts
|
||||
QStringList parts;
|
||||
parts.reserve(4);
|
||||
if(!songDescription.title.isEmpty()) {
|
||||
parts << QChar('\"') % songDescription.title % QChar('\"');
|
||||
}
|
||||
if(!songDescription.artist.isEmpty()) {
|
||||
parts << QStringLiteral("artist:\"") % songDescription.artist % QChar('\"');
|
||||
}
|
||||
if(!songDescription.album.isEmpty()) {
|
||||
parts << QStringLiteral("release:\"") % songDescription.album % QChar('\"');
|
||||
}
|
||||
|
||||
// compose URL
|
||||
QUrl url(musicBrainzUrl);
|
||||
QUrlQuery query;
|
||||
query.addQueryItem(QStringLiteral("query"), parts.join(QStringLiteral(" AND ")));
|
||||
url.setQuery(query);
|
||||
|
||||
// make request
|
||||
return new MusicBrainzResultsModel(Utility::networkAccessManager().get(QNetworkRequest(url)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#include "dbquery.moc"
|
|
@ -0,0 +1,86 @@
|
|||
#ifndef DBQUERY_H
|
||||
#define DBQUERY_H
|
||||
|
||||
#include <c++utilities/application/global.h>
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
|
||||
namespace Media {
|
||||
class TagValue;
|
||||
DECLARE_ENUM(KnownField, unsigned int)
|
||||
}
|
||||
|
||||
namespace QtGui {
|
||||
|
||||
struct SongDescription
|
||||
{
|
||||
SongDescription();
|
||||
|
||||
QString title;
|
||||
QString album;
|
||||
QString artist;
|
||||
QString year;
|
||||
QString genre;
|
||||
unsigned int track;
|
||||
unsigned int totalTracks;
|
||||
unsigned int disk;
|
||||
};
|
||||
|
||||
class QueryResultsModel : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Column {
|
||||
TitleCol,
|
||||
AlbumCol,
|
||||
ArtistCol,
|
||||
GenreCol,
|
||||
YearCol,
|
||||
TrackCol,
|
||||
TotalTracksCol
|
||||
};
|
||||
|
||||
const QList<SongDescription> &results() const;
|
||||
const QStringList &errorList() const;
|
||||
bool areResultsAvailable() const;
|
||||
Media::TagValue fieldValue(int row, Media::KnownField knownField) const;
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
|
||||
int rowCount(const QModelIndex &parent) const;
|
||||
int columnCount(const QModelIndex &parent) const;
|
||||
|
||||
signals:
|
||||
void resultsAvailable();
|
||||
|
||||
protected:
|
||||
QueryResultsModel(QObject *parent = nullptr);
|
||||
void setResultsAvailable(bool resultsAvailable);
|
||||
|
||||
QList<SongDescription> m_results;
|
||||
QStringList m_errorList;
|
||||
bool m_resultsAvailable;
|
||||
};
|
||||
|
||||
inline const QList<SongDescription> &QueryResultsModel::results() const
|
||||
{
|
||||
return m_results;
|
||||
}
|
||||
|
||||
inline const QStringList &QueryResultsModel::errorList() const
|
||||
{
|
||||
return m_errorList;
|
||||
}
|
||||
|
||||
inline bool QueryResultsModel::areResultsAvailable() const
|
||||
{
|
||||
return m_resultsAvailable;
|
||||
}
|
||||
|
||||
QueryResultsModel *queryMusicBrainz(const SongDescription &songDescription);
|
||||
|
||||
}
|
||||
|
||||
#endif // DBQUERY_H
|
|
@ -0,0 +1,228 @@
|
|||
#include "./dbquerywidget.h"
|
||||
#include "./tageditorwidget.h"
|
||||
#include "./tagedit.h"
|
||||
|
||||
#include "../application/knownfieldmodel.h"
|
||||
#include "../application/settings.h"
|
||||
#include "../dbquery/dbquery.h"
|
||||
#include "../misc/utility.h"
|
||||
|
||||
#include "ui_dbquerywidget.h"
|
||||
|
||||
#include <tagparser/tag.h>
|
||||
|
||||
#include <qtutilities/misc/dialogutils.h>
|
||||
|
||||
#include <c++utilities/conversion/conversionexception.h>
|
||||
|
||||
#include <QKeyEvent>
|
||||
|
||||
using namespace ConversionUtilities;
|
||||
using namespace Dialogs;
|
||||
using namespace Models;
|
||||
using namespace Settings;
|
||||
using namespace Utility;
|
||||
using namespace Media;
|
||||
|
||||
namespace QtGui {
|
||||
|
||||
DbQueryWidget::DbQueryWidget(TagEditorWidget *tagEditorWidget, QWidget *parent) :
|
||||
QWidget(parent),
|
||||
m_ui(new Ui::DbQueryWidget),
|
||||
m_tagEditorWidget(tagEditorWidget),
|
||||
m_model(nullptr)
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
#ifdef Q_OS_WIN32
|
||||
setStyleSheet(dialogStyle());
|
||||
#endif
|
||||
|
||||
m_ui->notificationLabel->setText(tr("Search hasn't been started."));
|
||||
m_ui->searchGroupBox->installEventFilter(this);
|
||||
|
||||
// initialize buttons
|
||||
m_ui->abortPushButton->setIcon(style()->standardIcon(QStyle::SP_DialogCancelButton, nullptr, m_ui->abortPushButton));
|
||||
m_ui->abortPushButton->setVisible(false);
|
||||
m_ui->applyPushButton->setIcon(style()->standardIcon(QStyle::SP_DialogApplyButton, nullptr, m_ui->applyPushButton));
|
||||
|
||||
// initialize fields model
|
||||
m_ui->fieldsListView->setModel(&Settings::dbQueryFields());
|
||||
|
||||
// initialize search terms form
|
||||
insertSearchTermsFromTagEdit(m_tagEditorWidget->activeTagEdit());
|
||||
|
||||
// restore settings
|
||||
m_ui->overrideCheckBox->setChecked(Settings::dbQueryOverride());
|
||||
|
||||
// connect signals and slots
|
||||
connect(m_ui->abortPushButton, &QPushButton::clicked, this, &DbQueryWidget::abortSearch);
|
||||
connect(m_ui->startPushButton, &QPushButton::clicked, this, &DbQueryWidget::startSearch);
|
||||
connect(m_ui->applyPushButton, &QPushButton::clicked, this, &DbQueryWidget::applyResults);
|
||||
connect(m_tagEditorWidget, &TagEditorWidget::fileStatusChange, this, &DbQueryWidget::fileStatusChanged);
|
||||
}
|
||||
|
||||
DbQueryWidget::~DbQueryWidget()
|
||||
{
|
||||
Settings::dbQueryOverride() = m_ui->overrideCheckBox->isChecked();
|
||||
}
|
||||
|
||||
void DbQueryWidget::insertSearchTermsFromTagEdit(TagEdit *tagEdit)
|
||||
{
|
||||
if(tagEdit) {
|
||||
// set title, album and artist
|
||||
m_ui->titleLineEdit->setText(tagValueToQString(tagEdit->value(KnownField::Title)));
|
||||
m_ui->albumLineEdit->setText(tagValueToQString(tagEdit->value(KnownField::Album)));
|
||||
m_ui->artistLineEdit->setText(tagValueToQString(tagEdit->value(KnownField::Artist)));
|
||||
|
||||
// set track number, or if not available part number
|
||||
bool trackValueOk = false;
|
||||
try {
|
||||
TagValue trackValue = tagEdit->value(KnownField::TrackPosition);
|
||||
if(!trackValue.isEmpty()) {
|
||||
m_ui->trackSpinBox->setValue(trackValue.toPositionIntSet().position());
|
||||
trackValueOk = true;
|
||||
}
|
||||
} catch(const ConversionException &) {
|
||||
}
|
||||
if(!trackValueOk) {
|
||||
TagValue trackValue = tagEdit->value(KnownField::PartNumber);
|
||||
if(!trackValue.isEmpty()) {
|
||||
m_ui->trackSpinBox->setValue(trackValue.toInteger());
|
||||
trackValueOk = true;
|
||||
}
|
||||
}
|
||||
if(!trackValueOk) {
|
||||
m_ui->trackSpinBox->clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DbQueryWidget::startSearch()
|
||||
{
|
||||
// check whether enought search terms are supplied
|
||||
if(m_ui->titleLineEdit->text().isEmpty() && (!m_ui->trackSpinBox->value() || m_ui->albumLineEdit->text().isEmpty() || m_ui->artistLineEdit->text().isEmpty())) {
|
||||
m_ui->notificationLabel->setNotificationType(NotificationType::Critical);
|
||||
m_ui->notificationLabel->setText(tr("Insufficient search criteria supplied"));
|
||||
return;
|
||||
}
|
||||
|
||||
// delete current model
|
||||
m_ui->resultsTreeView->setModel(nullptr);
|
||||
delete m_model;
|
||||
|
||||
// show status
|
||||
m_ui->notificationLabel->setNotificationType(NotificationType::Progress);
|
||||
m_ui->notificationLabel->setText(tr("Retrieving ..."));
|
||||
setStatus(false);
|
||||
|
||||
// get song description
|
||||
SongDescription desc;
|
||||
desc.title = m_ui->titleLineEdit->text();
|
||||
desc.album = m_ui->albumLineEdit->text();
|
||||
desc.artist = m_ui->artistLineEdit->text();
|
||||
desc.track = m_ui->trackSpinBox->value();
|
||||
|
||||
// do actual query
|
||||
m_ui->resultsTreeView->setModel(m_model = queryMusicBrainz(desc));
|
||||
connect(m_model, &QueryResultsModel::resultsAvailable, this, &DbQueryWidget::showResults);
|
||||
}
|
||||
|
||||
void DbQueryWidget::abortSearch()
|
||||
{
|
||||
if(m_model && !m_model->areResultsAvailable()) {
|
||||
// delete model to abort search
|
||||
m_ui->resultsTreeView->setModel(nullptr);
|
||||
delete m_model;
|
||||
m_model = nullptr;
|
||||
|
||||
// update status
|
||||
m_ui->notificationLabel->setNotificationType(NotificationType::Information);
|
||||
m_ui->notificationLabel->setText(tr("Aborted"));
|
||||
setStatus(true);
|
||||
}
|
||||
}
|
||||
|
||||
void DbQueryWidget::showResults()
|
||||
{
|
||||
if(m_model) {
|
||||
if(m_model->errorList().isEmpty()) {
|
||||
m_ui->notificationLabel->setNotificationType(NotificationType::TaskComplete);
|
||||
if(m_model->results().isEmpty()) {
|
||||
m_ui->notificationLabel->setText(tr("No results available"));
|
||||
} else {
|
||||
m_ui->notificationLabel->setText(tr("%1 result(s) available", 0, m_model->results().size()).arg(m_model->results().size()));
|
||||
}
|
||||
} else {
|
||||
m_ui->notificationLabel->setNotificationType(NotificationType::Critical);
|
||||
m_ui->notificationLabel->clearText();
|
||||
for(const QString &error : m_model->errorList()) {
|
||||
m_ui->notificationLabel->appendLine(error);
|
||||
}
|
||||
}
|
||||
if(m_model->results().isEmpty()) {
|
||||
m_ui->applyPushButton->setEnabled(false);
|
||||
} else {
|
||||
m_ui->resultsTreeView->selectionModel()->setCurrentIndex(m_model->index(0, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Columns);
|
||||
m_ui->applyPushButton->setEnabled(m_tagEditorWidget->activeTagEdit());
|
||||
}
|
||||
setStatus(true);
|
||||
}
|
||||
}
|
||||
|
||||
void DbQueryWidget::setStatus(bool aborted)
|
||||
{
|
||||
m_ui->abortPushButton->setVisible(!aborted);
|
||||
m_ui->startPushButton->setVisible(aborted);
|
||||
m_ui->applyPushButton->setVisible(aborted);
|
||||
}
|
||||
|
||||
void DbQueryWidget::fileStatusChanged(bool , bool hasTags)
|
||||
{
|
||||
m_ui->applyPushButton->setEnabled(hasTags && m_ui->resultsTreeView->selectionModel() && m_ui->resultsTreeView->selectionModel()->hasSelection());
|
||||
insertSearchTermsFromTagEdit(m_tagEditorWidget->activeTagEdit());
|
||||
}
|
||||
|
||||
void DbQueryWidget::applyResults()
|
||||
{
|
||||
if(m_model) {
|
||||
if(TagEdit *tagEdit = m_tagEditorWidget->activeTagEdit()) {
|
||||
if(const QItemSelectionModel *selectionModel = m_ui->resultsTreeView->selectionModel()) {
|
||||
const QModelIndexList selection = selectionModel->selection().indexes();
|
||||
if(!selection.isEmpty()) {
|
||||
PreviousValueHandling previousValueHandling = m_ui->overrideCheckBox->isChecked()
|
||||
? PreviousValueHandling::Update : PreviousValueHandling::Keep;
|
||||
for(const ChecklistItem &item : Settings::dbQueryFields().items()) {
|
||||
if(item.isChecked()) {
|
||||
const auto field = static_cast<KnownField>(item.id().toInt());
|
||||
tagEdit->setValue(field, m_model->fieldValue(selection.front().row(), field), previousValueHandling);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DbQueryWidget::eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
if(obj = m_ui->searchGroupBox) {
|
||||
switch(event->type()) {
|
||||
case QEvent::KeyRelease: {
|
||||
auto *keyEvent = static_cast<QKeyEvent *>(event);
|
||||
int key = keyEvent->key();
|
||||
switch(key) {
|
||||
case Qt::Key_Return:
|
||||
startSearch();
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
break;
|
||||
} default:
|
||||
;
|
||||
}
|
||||
return QWidget::eventFilter(obj, event);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
#ifndef DBQUERYWIDGET_H
|
||||
#define DBQUERYWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include <memory>
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QItemSelection)
|
||||
|
||||
namespace Settings {
|
||||
class KnownFieldModel;
|
||||
}
|
||||
|
||||
namespace QtGui {
|
||||
|
||||
namespace Ui {
|
||||
class DbQueryWidget;
|
||||
}
|
||||
|
||||
class QueryResultsModel;
|
||||
class TagEditorWidget;
|
||||
class TagEdit;
|
||||
|
||||
class DbQueryWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DbQueryWidget(TagEditorWidget *tagEditorWidget, QWidget *parent = nullptr);
|
||||
~DbQueryWidget();
|
||||
|
||||
void insertSearchTermsFromTagEdit(TagEdit *tagEdit);
|
||||
|
||||
public slots:
|
||||
void startSearch();
|
||||
void abortSearch();
|
||||
|
||||
private slots:
|
||||
void showResults();
|
||||
void setStatus(bool aborted);
|
||||
void fileStatusChanged(bool opened, bool hasTags);
|
||||
void applyResults();
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event);
|
||||
|
||||
private:
|
||||
std::unique_ptr<Ui::DbQueryWidget> m_ui;
|
||||
TagEditorWidget *m_tagEditorWidget;
|
||||
QueryResultsModel *m_model;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // DBQUERYWIDGET_H
|
|
@ -0,0 +1,272 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>QtGui::DbQueryWidget</class>
|
||||
<widget class="QWidget" name="QtGui::DbQueryWidget">
|
||||
<property name="windowTitle">
|
||||
<string>MusicBrainz search</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="dialogLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QWidget" name="mainWidget" native="true">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="searchGroupBox">
|
||||
<property name="title">
|
||||
<string>Search criteria</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="searchFormLayout">
|
||||
<property name="horizontalSpacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="verticalSpacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="songLabel">
|
||||
<property name="text">
|
||||
<string>Song</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="songLayout">
|
||||
<property name="spacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="Widgets::ClearSpinBox" name="trackSpinBox">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="specialValueText">
|
||||
<string>?</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Widgets::ClearLineEdit" name="titleLineEdit">
|
||||
<property name="placeholderText">
|
||||
<string>?</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="albumLabel">
|
||||
<property name="text">
|
||||
<string>Album</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="Widgets::ClearLineEdit" name="albumLineEdit">
|
||||
<property name="placeholderText">
|
||||
<string>?</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="artistLabel">
|
||||
<property name="text">
|
||||
<string>Artist</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="Widgets::ClearLineEdit" name="artistLineEdit">
|
||||
<property name="placeholderText">
|
||||
<string>?</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<widget class="QTreeView" name="resultsTreeView"/>
|
||||
<widget class="QGroupBox" name="fieldsGroupBox">
|
||||
<property name="title">
|
||||
<string>Fields to be used</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QListView" name="fieldsListView">
|
||||
<property name="movement">
|
||||
<enum>QListView::Free</enum>
|
||||
</property>
|
||||
<property name="flow">
|
||||
<enum>QListView::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="isWrapping" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="overrideCheckBox">
|
||||
<property name="text">
|
||||
<string>override existing values</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="bottomWidget" native="true">
|
||||
<layout class="QHBoxLayout" name="bottomLayout">
|
||||
<item>
|
||||
<widget class="NotificationLabel" name="notificationLabel" native="true">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>325</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>433</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="abortPushButton">
|
||||
<property name="text">
|
||||
<string>Abort</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="startPushButton">
|
||||
<property name="text">
|
||||
<string>Search</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="edit-find">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="applyPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Apply results</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>Widgets::ClearLineEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header location="global">qtutilities/widgets/clearlineedit.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>NotificationLabel</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/notificationlabel.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>Widgets::ClearSpinBox</class>
|
||||
<extends>QSpinBox</extends>
|
||||
<header location="global">qtutilities/widgets/clearspinbox.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -2,6 +2,14 @@
|
|||
<ui version="4.0">
|
||||
<class>QtGui::EditorAutoCorrectionOptionPage</class>
|
||||
<widget class="QWidget" name="QtGui::EditorAutoCorrectionOptionPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>346</width>
|
||||
<height>446</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>General options page</string>
|
||||
</property>
|
||||
|
@ -68,15 +76,6 @@
|
|||
</item>
|
||||
<item>
|
||||
<widget class="QListView" name="fieldsListView">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Panel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="showDropIndicator" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
|
|
|
@ -2,6 +2,14 @@
|
|||
<ui version="4.0">
|
||||
<class>QtGui::EditorFieldsOptionPage</class>
|
||||
<widget class="QWidget" name="QtGui::EditorFieldsOptionPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>268</width>
|
||||
<height>342</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
|
@ -18,12 +26,6 @@
|
|||
</item>
|
||||
<item>
|
||||
<widget class="QListView" name="fieldsListView">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Panel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>1</number>
|
||||
</property>
|
||||
|
|
|
@ -1,167 +0,0 @@
|
|||
#include "./infowidgetbase.h"
|
||||
#include "./notificationmodel.h"
|
||||
|
||||
#include <c++utilities/conversion/stringconversion.h>
|
||||
#include <c++utilities/chrono/timespan.h>
|
||||
|
||||
#include <tagparser/mediafileinfo.h>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFormLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QMessageBox>
|
||||
#include <QTreeView>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
|
||||
using namespace std;
|
||||
using namespace ConversionUtilities;
|
||||
using namespace ChronoUtilities;
|
||||
using namespace Media;
|
||||
|
||||
namespace QtGui {
|
||||
|
||||
InfoWidgetBase::InfoWidgetBase(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
m_notificationModel(nullptr)
|
||||
{
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||||
|
||||
m_formLayout = new QFormLayout(this);
|
||||
mainLayout->addLayout(m_formLayout);
|
||||
|
||||
QHBoxLayout *m_bottomLayout = new QHBoxLayout(this);
|
||||
m_bottomLayout->setAlignment(Qt::AlignBottom | Qt::AlignRight);
|
||||
|
||||
m_notificationsButton = new QPushButton(tr("Show notifications"), this);
|
||||
m_notificationsButton->setHidden(true);
|
||||
connect(m_notificationsButton, &QPushButton::clicked, this, &InfoWidgetBase::showNotifications);
|
||||
m_bottomLayout->addWidget(m_notificationsButton);
|
||||
|
||||
mainLayout->addLayout(m_bottomLayout);
|
||||
|
||||
setLayout(mainLayout);
|
||||
}
|
||||
|
||||
InfoWidgetBase::~InfoWidgetBase()
|
||||
{}
|
||||
|
||||
void InfoWidgetBase::setupName(const string &name)
|
||||
{
|
||||
m_name = QString::fromLocal8Bit(name.c_str());
|
||||
}
|
||||
|
||||
void InfoWidgetBase::setupNotifications(const StatusProvider &provider)
|
||||
{
|
||||
if(provider.hasNotifications()) {
|
||||
m_notificationsButton->setHidden(false);
|
||||
if(provider.hasCriticalNotifications()) {
|
||||
m_notificationsButton->setIcon(NotificationModel::errorIcon());
|
||||
} else {
|
||||
m_notificationsButton->setIcon(NotificationModel::warningIcon());
|
||||
}
|
||||
if(!m_notificationModel) {
|
||||
m_notificationModel = new NotificationModel(this);
|
||||
}
|
||||
m_notificationModel->setNotifications(provider.notifications());
|
||||
} else {
|
||||
m_notificationsButton->setHidden(true);
|
||||
}
|
||||
}
|
||||
|
||||
void InfoWidgetBase::showNotifications()
|
||||
{
|
||||
if(m_notificationModel) {
|
||||
QDialog dlg;
|
||||
dlg.setWindowFlags(Qt::Tool);
|
||||
dlg.setWindowTitle(tr("Notifications for %1 - %2").arg(m_name).arg(QApplication::applicationName()));
|
||||
QPoint point = m_notificationsButton->mapToGlobal(QPoint(m_notificationsButton->size().width(), m_notificationsButton->size().height()));
|
||||
dlg.resize(600, 350);
|
||||
dlg.move(point.x() - 600, point.y());
|
||||
QHBoxLayout layout;
|
||||
layout.setMargin(0);
|
||||
QTreeView view;
|
||||
view.setIndentation(0);
|
||||
view.setFrameStyle(QFrame::NoFrame);
|
||||
view.setModel(m_notificationModel);
|
||||
view.setWordWrap(true);
|
||||
view.setColumnWidth(0, 100);
|
||||
view.setColumnWidth(1, 350);
|
||||
layout.addWidget(&view);
|
||||
dlg.setLayout(&layout);
|
||||
dlg.exec();
|
||||
} else {
|
||||
QMessageBox::warning(this, windowTitle(), tr("There are no notifications to be shown."));
|
||||
}
|
||||
}
|
||||
|
||||
void InfoWidgetBase::setupRow(int index, const char *labelText, QLabel *&label, const string &text)
|
||||
{
|
||||
setupRow(index, labelText, label, QString::fromLocal8Bit(text.c_str()));
|
||||
}
|
||||
|
||||
void InfoWidgetBase::setupRow(int index, const char *labelText, QLabel *&label, const QString &text)
|
||||
{
|
||||
if(!label) {
|
||||
label = new QLabel(this);
|
||||
label->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||
label->setWordWrap(true);
|
||||
m_formLayout->insertRow(index, tr(labelText), label);
|
||||
} else {
|
||||
int currentIndex = m_formLayout->indexOf(label);
|
||||
if(currentIndex != (index * 2 + 1)) {
|
||||
if(QWidget *labelWidget = m_formLayout->labelForField(label)) {
|
||||
labelWidget->deleteLater();
|
||||
}
|
||||
m_formLayout->removeWidget(label);
|
||||
m_formLayout->insertRow(index, tr(labelText), label);
|
||||
}
|
||||
}
|
||||
label->setText(text);
|
||||
}
|
||||
|
||||
void InfoWidgetBase::setupRow(int index, const char *labelText, QLineEdit *&edit, const string &text)
|
||||
{
|
||||
setupRow(index, labelText, edit, QString::fromLocal8Bit(text.c_str()));
|
||||
}
|
||||
|
||||
void InfoWidgetBase::setupRow(int index, const char *labelText, QLineEdit *&edit, const QString &text)
|
||||
{
|
||||
if(!edit) {
|
||||
edit = new QLineEdit(this);
|
||||
m_formLayout->insertRow(index, tr(labelText), edit);
|
||||
} else {
|
||||
int currentIndex = m_formLayout->indexOf(edit);
|
||||
if(currentIndex != (index * 2 + 1)) {
|
||||
if(QWidget *labelWidget = m_formLayout->labelForField(edit)) {
|
||||
labelWidget->deleteLater();
|
||||
}
|
||||
m_formLayout->removeWidget(edit);
|
||||
m_formLayout->insertRow(index, tr(labelText), edit);
|
||||
}
|
||||
}
|
||||
edit->setText(text);
|
||||
edit->setPlaceholderText(tr("empty"));
|
||||
}
|
||||
|
||||
void InfoWidgetBase::removeRow(QLabel *&label)
|
||||
{
|
||||
if(QWidget *labelWidget = m_formLayout->labelForField(label)) {
|
||||
labelWidget->deleteLater();
|
||||
}
|
||||
label->deleteLater();
|
||||
label = nullptr;
|
||||
}
|
||||
|
||||
void InfoWidgetBase::removeRow(QLineEdit *&edit)
|
||||
{
|
||||
if(QWidget *labelWidget = m_formLayout->labelForField(edit)) {
|
||||
labelWidget->deleteLater();
|
||||
}
|
||||
edit->deleteLater();
|
||||
edit = nullptr;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
#ifndef INFOWIDGETBASE_H
|
||||
#define INFOWIDGETBASE_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include <string>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QFormLayout;
|
||||
class QHBoxLayout;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QPushButton;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace Media {
|
||||
class MediaFileInfo;
|
||||
class StatusProvider;
|
||||
}
|
||||
|
||||
namespace QtGui {
|
||||
|
||||
class NotificationModel;
|
||||
|
||||
class InfoWidgetBase : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit InfoWidgetBase(QWidget *parent = nullptr);
|
||||
~InfoWidgetBase();
|
||||
|
||||
protected:
|
||||
void setupName(const std::string &name);
|
||||
void setupNotifications(const Media::StatusProvider &provider);
|
||||
void setupRow(int index, const char *labelText, QLabel *&label, const std::string &text);
|
||||
void setupRow(int index, const char *labelText, QLabel *&label, const QString &text);
|
||||
void setupRow(int index, const char *labelText, QLineEdit *&edit, const std::string &text);
|
||||
void setupRow(int index, const char *labelText, QLineEdit *&edit, const QString &text);
|
||||
void removeRow(QLabel *&label);
|
||||
void removeRow(QLineEdit *&edit);
|
||||
|
||||
private slots:
|
||||
void showNotifications();
|
||||
|
||||
private:
|
||||
QFormLayout *m_formLayout;
|
||||
QHBoxLayout *m_bottomLayout;
|
||||
QPushButton *m_notificationsButton;
|
||||
NotificationModel *m_notificationModel;
|
||||
QString m_name;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // INFOWIDGETBASE_H
|
|
@ -1,6 +1,7 @@
|
|||
#include "./mainwindow.h"
|
||||
#include "./settingsdialog.h"
|
||||
#include "./renamefilesdialog.h"
|
||||
#include "./dbquerywidget.h"
|
||||
#include "./tageditorwidget.h"
|
||||
|
||||
#include "../application/settings.h"
|
||||
|
@ -70,18 +71,21 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
QMainWindow(parent, Qt::Window),
|
||||
m_ui(new Ui::MainWindow()),
|
||||
m_aboutDlg(nullptr),
|
||||
m_settingsDlg(nullptr)
|
||||
m_settingsDlg(nullptr),
|
||||
m_dbQueryWidget(nullptr)
|
||||
{
|
||||
// setup UI
|
||||
m_ui->setupUi(this);
|
||||
#ifdef Q_OS_WIN32
|
||||
setStyleSheet(dialogStyle() + QStringLiteral("#rightWidget, #rightWidget QSplitter::handle { color: palette(text); background-color: palette(base); }"));
|
||||
setStyleSheet(dialogStyle() + QStringLiteral("#tagEditorWidget { color: palette(text); background-color: palette(base); }"));
|
||||
#else
|
||||
setStyleSheet(dialogStyle());
|
||||
#endif
|
||||
|
||||
// restore geometry and state
|
||||
restoreGeometry(Settings::mainWindowGeometry());
|
||||
restoreState(Settings::mainWindowState());
|
||||
|
||||
// setup file model and file tree view
|
||||
m_fileModel = new QFileSystemModel(this);
|
||||
m_fileModel->setRootPath(QString());
|
||||
|
@ -92,13 +96,25 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
m_ui->filesTreeView->sortByColumn(0, Qt::AscendingOrder);
|
||||
m_ui->filesTreeView->setModel(m_fileFilterModel);
|
||||
m_ui->filesTreeView->setColumnWidth(0, 300);
|
||||
|
||||
// setup path line edit
|
||||
m_ui->pathLineEdit->setCompletionModel(m_fileModel);
|
||||
|
||||
// apply initial file status
|
||||
handleFileStatusChange(false, false);
|
||||
|
||||
// dbquery dock widget
|
||||
if(Settings::dbQueryWidgetShown()) {
|
||||
showDbQueryWidget();
|
||||
} else {
|
||||
// ensure the dock widget is invisible
|
||||
m_ui->dbQueryDockWidget->setVisible(false);
|
||||
}
|
||||
|
||||
// connect signals and slots, install event filter
|
||||
// menu: application
|
||||
connect(m_ui->actionSettings, &QAction::triggered, this, &MainWindow::showSettingsDlg);
|
||||
connect(m_ui->actionOpen_MusicBrainz_search, &QAction::triggered, this, &MainWindow::showDbQueryWidget);
|
||||
connect(m_ui->actionQuit, &QAction::triggered, this, &MainWindow::close);
|
||||
// menu: file
|
||||
connect(m_ui->actionOpen, &QAction::triggered, this, &MainWindow::showOpenFileDlg);
|
||||
|
@ -172,6 +188,7 @@ bool MainWindow::event(QEvent *event)
|
|||
Settings::mainWindowGeometry() = saveGeometry();
|
||||
Settings::mainWindowState() = saveState();
|
||||
Settings::mainWindowCurrentFileBrowserDirectory() = currentDirectory();
|
||||
Settings::dbQueryWidgetShown() = m_ui->dbQueryDockWidget->isVisible();
|
||||
break;
|
||||
default:
|
||||
;
|
||||
|
@ -263,6 +280,17 @@ void MainWindow::spawnExternalPlayer()
|
|||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Shows the database query widget.
|
||||
*/
|
||||
void MainWindow::showDbQueryWidget()
|
||||
{
|
||||
if(!m_dbQueryWidget) {
|
||||
m_ui->dbQueryDockWidget->setWidget(m_dbQueryWidget = new DbQueryWidget(m_ui->tagEditorWidget, this));
|
||||
}
|
||||
m_ui->dbQueryDockWidget->setVisible(true);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Shows the about dialog.
|
||||
*/
|
||||
|
@ -343,7 +371,12 @@ void MainWindow::selectNextFile(QItemSelectionModel *selectionModel, const QMode
|
|||
if(parent == currentIndex) {
|
||||
QModelIndex next = m_fileFilterModel->index(0, 0, parent);
|
||||
if(next.isValid()) {
|
||||
m_ui->filesTreeView->selectionModel()->setCurrentIndex(next, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
|
||||
if(m_ui->filesTreeView->model()->hasChildren(next)) {
|
||||
// next item is a directory -> keep on searching
|
||||
selectNextFile(selectionModel, next, false);
|
||||
} else {
|
||||
m_ui->filesTreeView->selectionModel()->setCurrentIndex(next, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
|
||||
}
|
||||
} else {
|
||||
selectNextFile(selectionModel, currentIndex, true);
|
||||
}
|
||||
|
@ -369,7 +402,12 @@ void MainWindow::selectNextFile(QItemSelectionModel *selectionModel, const QMode
|
|||
}
|
||||
}
|
||||
if(next.isValid()) {
|
||||
selectionModel->setCurrentIndex(next, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
|
||||
if(selectionModel->model()->hasChildren(next)) {
|
||||
// next item is a directory -> keep on searching
|
||||
selectNextFile(selectionModel, next, false);
|
||||
} else {
|
||||
selectionModel->setCurrentIndex(next, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
|
||||
}
|
||||
} else {
|
||||
showNextFileNotFound();
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ class MainWindow;
|
|||
|
||||
class TagEditorWidget;
|
||||
class RenameFilesDialog;
|
||||
class DbQueryWidget;
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
|
@ -69,6 +70,7 @@ private slots:
|
|||
void showAboutDlg();
|
||||
void showRenameFilesDlg();
|
||||
void spawnExternalPlayer();
|
||||
void showDbQueryWidget();
|
||||
|
||||
private:
|
||||
std::mutex &fileOperationMutex();
|
||||
|
@ -83,6 +85,7 @@ private:
|
|||
Dialogs::AboutDialog *m_aboutDlg;
|
||||
Dialogs::SettingsDialog *m_settingsDlg;
|
||||
std::unique_ptr<RenameFilesDialog> m_renameFilesDlg;
|
||||
DbQueryWidget *m_dbQueryWidget;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<normaloff>:/tageditor/icons/hicolor/16x16/apps/tageditor.png</normaloff>:/tageditor/icons/hicolor/16x16/apps/tageditor.png</iconset>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralWidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
|
@ -35,45 +35,7 @@
|
|||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="QWidget" name="leftWidget" native="true">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="PathLineEdit" name="pathLineEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeView" name="filesTreeView">
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerShowSortIndicator" stdset="0">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCommandLinkButton" name="selectNextCommandLinkButton">
|
||||
<property name="text">
|
||||
<string>Select next file/directory</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="TagEditorWidget" name="tagEditorWidget"/>
|
||||
</widget>
|
||||
<widget class="TagEditorWidget" name="tagEditorWidget" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
@ -120,6 +82,7 @@
|
|||
<string>A&pplication</string>
|
||||
</property>
|
||||
<addaction name="actionSettings"/>
|
||||
<addaction name="actionOpen_MusicBrainz_search"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionQuit"/>
|
||||
<addaction name="separator"/>
|
||||
|
@ -129,6 +92,71 @@
|
|||
<addaction name="menuDirectory"/>
|
||||
<addaction name="menu"/>
|
||||
</widget>
|
||||
<widget class="QDockWidget" name="fileSelectionDockWidget">
|
||||
<property name="floating">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="features">
|
||||
<set>QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>File sele&ction</string>
|
||||
</property>
|
||||
<attribute name="dockWidgetArea">
|
||||
<number>1</number>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="dockWidgetContents">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="PathLineEdit" name="pathLineEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeView" name="filesTreeView">
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerShowSortIndicator" stdset="0">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCommandLinkButton" name="selectNextCommandLinkButton">
|
||||
<property name="text">
|
||||
<string>Select next file</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QDockWidget" name="dbQueryDockWidget">
|
||||
<property name="floating">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>&MusicBrains search (beta)</string>
|
||||
</property>
|
||||
<attribute name="dockWidgetArea">
|
||||
<number>1</number>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="dockWidgetContents_2"/>
|
||||
</widget>
|
||||
<action name="actionSave">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
|
@ -297,6 +325,18 @@
|
|||
<string>Ctrl+E</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionOpen_MusicBrainz_search">
|
||||
<property name="icon">
|
||||
<iconset theme="edit-find">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Open MusicBrainz search</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>F10</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
@ -308,12 +348,8 @@
|
|||
<class>TagEditorWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/tageditorwidget.h</header>
|
||||
<container>0</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>filesTreeView</tabstop>
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="../resources/icons.qrc"/>
|
||||
</resources>
|
||||
|
|
|
@ -39,7 +39,7 @@ void NotificationLabel::paintEvent(QPaintEvent *event)
|
|||
QPainter painter(this);
|
||||
|
||||
if(event->rect().contains(textRect)) {
|
||||
style->drawItemText(&painter, textRect, QStyle::visualAlignment(layoutDirection(), Qt::AlignVCenter | Qt::AlignLeft), option.palette, isEnabled(), m_text, foregroundRole());
|
||||
style->drawItemText(&painter, textRect, QStyle::visualAlignment(layoutDirection(), Qt::AlignVCenter | Qt::AlignLeft | Qt::AlignJustify), option.palette, isEnabled(), m_text, foregroundRole());
|
||||
}
|
||||
if(event->rect().contains(pixmapRect)) {
|
||||
setupPixmaps(pixmapRect.size());
|
||||
|
@ -178,6 +178,13 @@ void NotificationLabel::setText(const QString &text)
|
|||
update(textRect());
|
||||
}
|
||||
|
||||
void NotificationLabel::clearText()
|
||||
{
|
||||
m_text.clear();
|
||||
updateGeometry();
|
||||
update(textRect());
|
||||
}
|
||||
|
||||
void NotificationLabel::appendLine(const QString &line)
|
||||
{
|
||||
if(m_text.isEmpty()) {
|
||||
|
|
|
@ -46,6 +46,7 @@ public:
|
|||
|
||||
public slots:
|
||||
void setText(const QString &text);
|
||||
void clearText();
|
||||
void appendLine(const QString &line);
|
||||
void setNotificationType(NotificationType value);
|
||||
void setNotificationSubject(NotificationSubject value);
|
||||
|
|
|
@ -67,7 +67,7 @@ RenameFilesDialog::RenameFilesDialog(QWidget *parent) :
|
|||
m_ui->generatePreviewPushButton->setIcon(style()->standardIcon(QStyle::SP_BrowserReload, nullptr, m_ui->generatePreviewPushButton));
|
||||
m_ui->applyChangingsPushButton->setIcon(style()->standardIcon(QStyle::SP_DialogApplyButton, nullptr, m_ui->applyChangingsPushButton));
|
||||
m_ui->applyChangingsPushButton->setEnabled(false);
|
||||
m_ui->abortClosePushButton->setIcon(style()->standardIcon(QStyle::SP_DialogCancelButton, nullptr, m_ui->applyChangingsPushButton));
|
||||
m_ui->abortClosePushButton->setIcon(style()->standardIcon(QStyle::SP_DialogCancelButton, nullptr, m_ui->abortClosePushButton));
|
||||
|
||||
// restore settings
|
||||
if(Settings::scriptSource() < m_ui->sourceFileStackedWidget->count()) {
|
||||
|
|
|
@ -51,6 +51,19 @@ TagEdit::TagEdit(QWidget *parent) :
|
|||
setLayout(mainLayout);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the current value for the specified \a field.
|
||||
* \remarks Doesn't work for fields of the type picture.
|
||||
*/
|
||||
TagValue TagEdit::value(KnownField field) const
|
||||
{
|
||||
if(const TagFieldEdit *edit = m_widgets.value(field, nullptr)) {
|
||||
return edit->value(TagTextEncoding::Utf16LittleEndian, false);
|
||||
} else {
|
||||
return TagValue();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Assigns the specified \a tag to the edit.
|
||||
* \param updateUi Specifies whether the UI of should be updated.
|
||||
|
@ -89,8 +102,7 @@ void TagEdit::setTags(const QList<Tag *> &tags, bool updateUi)
|
|||
*/
|
||||
bool TagEdit::setValue(KnownField field, const Media::TagValue &value, PreviousValueHandling previousValueHandling)
|
||||
{
|
||||
TagFieldEdit *edit = m_widgets.value(field, nullptr);
|
||||
if(edit) {
|
||||
if(TagFieldEdit *edit = m_widgets.value(field, nullptr)) {
|
||||
return edit->setValue(value, previousValueHandling);
|
||||
} else {
|
||||
return false;
|
||||
|
@ -214,24 +226,6 @@ void TagEdit::setupUi()
|
|||
m_widgets.clear();
|
||||
} else {
|
||||
// there are tags assigned
|
||||
// if(m_tags.size() > 1) {
|
||||
// // display targets
|
||||
// list<string> targets;
|
||||
// for(const Tag *tag : m_tags) {
|
||||
// if(tag->supportsTarget()) {
|
||||
// const TagTarget &target = tag->target();
|
||||
// if(!target.levelName().empty()) {
|
||||
// targets.push_back(target.levelName());
|
||||
// } else {
|
||||
// targets.push_back(numberToString(target.level()));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if(targets.size()) {
|
||||
// QString res = QString::fromStdString(joinStrings(targets, ", ", true));
|
||||
|
||||
// }
|
||||
// }
|
||||
// setup editing controls
|
||||
TagFieldEdit *edit = nullptr;
|
||||
int rowOverall = 0, rowLeft = 0, rowRight = 0;
|
||||
|
|
|
@ -32,6 +32,7 @@ class TagEdit : public QWidget
|
|||
public:
|
||||
explicit TagEdit(QWidget *parent = nullptr);
|
||||
const QList<Media::Tag *> &tags() const;
|
||||
Media::TagValue value(Media::KnownField field) const;
|
||||
void setTag(Media::Tag *tag, bool updateUi = true);
|
||||
void setTags(const QList<Media::Tag *> &tags, bool updateUi = true);
|
||||
bool setValue(Media::KnownField field, const Media::TagValue &value, PreviousValueHandling previousValueHandling = PreviousValueHandling::Clear);
|
||||
|
|
|
@ -579,6 +579,14 @@ void TagEditorWidget::foreachTagEdit(const std::function<void (TagEdit *)> &func
|
|||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the active tag edit or nullptr if there is currently no acitve tag edit.
|
||||
*/
|
||||
TagEdit *TagEditorWidget::activeTagEdit()
|
||||
{
|
||||
return m_fileInfo.isOpen() ? qobject_cast<TagEdit *>(m_ui->stackedWidget->currentWidget()) : nullptr;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Opens and parses a file using another thread.
|
||||
*
|
||||
|
@ -769,7 +777,7 @@ void TagEditorWidget::showFile(char result)
|
|||
void TagEditorWidget::saveAndShowNextFile()
|
||||
{
|
||||
m_nextFileAfterSaving = true;
|
||||
startSaving();
|
||||
applyEntriesAndSaveChangings();
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -996,12 +1004,12 @@ void TagEditorWidget::showSavingResult(bool processingError, bool ioError)
|
|||
// -> the main window will ensure the current file is still selected
|
||||
emit fileSaved(m_currentPath);
|
||||
// show next file (only if there are critical notifications)
|
||||
m_nextFileAfterSaving = false;
|
||||
if(critical < 1 && m_nextFileAfterSaving) {
|
||||
emit nextFileSelected();
|
||||
} else {
|
||||
startParsing(m_currentPath, true);
|
||||
}
|
||||
m_nextFileAfterSaving = false;
|
||||
} else {
|
||||
static const QString processingErrorMsg(tr("The tags couldn't be saved. See the info box for detail."));
|
||||
static const QString ioErrorMsg(tr("The tags couldn't be saved because an IO error occured."));
|
||||
|
|
|
@ -53,11 +53,14 @@ public:
|
|||
std::mutex &fileOperationMutex();
|
||||
const QString ¤tPath() const;
|
||||
Media::MediaFileInfo &fileInfo();
|
||||
bool isTagEditShown() const;
|
||||
const QByteArray &fileInfoHtml() const;
|
||||
bool isFileNameVisible() const;
|
||||
void setFileNameVisible(bool visible);
|
||||
bool areButtonsVisible() const;
|
||||
void setButtonVisible(bool visible);
|
||||
void foreachTagEdit(const std::function<void (TagEdit *)> &function);
|
||||
TagEdit *activeTagEdit();
|
||||
|
||||
public slots:
|
||||
// operations with the currently opened file: load, save, delete, close
|
||||
|
@ -119,7 +122,6 @@ private:
|
|||
void updateFileStatusStatus();
|
||||
void updateTagManagementMenu();
|
||||
void insertTitleFromFilename();
|
||||
void foreachTagEdit(const std::function<void (TagEdit *)> &function);
|
||||
bool confirmCreationOfId3TagForUnsupportedFile();
|
||||
|
||||
// UI
|
||||
|
@ -182,6 +184,14 @@ inline const QByteArray &TagEditorWidget::fileInfoHtml() const
|
|||
return m_fileInfoHtml;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns whether currently a tag edit is shown.
|
||||
*/
|
||||
inline bool TagEditorWidget::isTagEditShown() const
|
||||
{
|
||||
return !m_tags.empty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // TAGEDITORWIDGET_H
|
||||
|
|
|
@ -2,6 +2,14 @@
|
|||
<ui version="4.0">
|
||||
<class>QtGui::TagEditorWidget</class>
|
||||
<widget class="QWidget" name="QtGui::TagEditorWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>634</width>
|
||||
<height>203</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="acceptDrops">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
|
|
|
@ -106,6 +106,60 @@ void TagFieldEdit::setTagField(const QList<Tag *> &tags, Media::KnownField field
|
|||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the currently shown value.
|
||||
* \remarks
|
||||
* - The specified \a encoding is used to encode text values.
|
||||
* - Does not work for values of the type picture.
|
||||
*/
|
||||
TagValue TagFieldEdit::value(TagTextEncoding encoding, bool includeDescription) const
|
||||
{
|
||||
TagValue value;
|
||||
switch(m_dataType) {
|
||||
case TagDataType::Text:
|
||||
case TagDataType::TimeSpan:
|
||||
case TagDataType::DateTime:
|
||||
switch(m_field) {
|
||||
case KnownField::Genre:
|
||||
if(m_comboBox) {
|
||||
value.assignText(Utility::qstringToString(m_comboBox->currentText(), encoding), encoding);
|
||||
}
|
||||
break;
|
||||
case KnownField::Lyrics:
|
||||
if(m_plainTextEdit) {
|
||||
value.assignText(Utility::qstringToString(m_plainTextEdit->toPlainText(), encoding), encoding);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if(m_lineEdit) {
|
||||
value.assignText(Utility::qstringToString(m_lineEdit->text(), encoding), encoding);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TagDataType::Integer:
|
||||
if(m_spinBoxes.first && m_spinBoxes.first->value()) {
|
||||
value.assignInteger(m_spinBoxes.first->value());
|
||||
}
|
||||
break;
|
||||
case TagDataType::PositionInSet:
|
||||
if(m_spinBoxes.first && m_spinBoxes.second) {
|
||||
value.assignPosition(PositionInSet(m_spinBoxes.first->value(), m_spinBoxes.second->value()));
|
||||
}
|
||||
break;
|
||||
case TagDataType::StandardGenreIndex:
|
||||
if(m_comboBox) {
|
||||
value.assignText(Utility::qstringToString(m_comboBox->currentText(), encoding), encoding);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
if(m_descriptionLineEdit && includeDescription) { // setup description line edit
|
||||
value.setDescription(Utility::qstringToString(m_descriptionLineEdit->text(), encoding), encoding);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Sets the \a value of the current tag field manually using the given \a previousValueHandling.
|
||||
*/
|
||||
|
@ -584,7 +638,7 @@ QLabel *TagFieldEdit::setupTypeNotSupportedLabel()
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Updates the current value manually.
|
||||
* \brief Updates the currently shown value manually.
|
||||
* \param previousValueHandling Specifies how to deal with the previous value.
|
||||
*
|
||||
* The new value is read from the assigned tag(s).
|
||||
|
@ -617,7 +671,7 @@ void TagFieldEdit::updateValue(PreviousValueHandling previousValueHandling)
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Updates the current value manually.
|
||||
* \brief Updates the currently shown value manually.
|
||||
* \param tag Specifies the tag to read the new value from.
|
||||
* \param previousValueHandling Specifies how to deal with the previous value.
|
||||
* \remarks If \a tag is nullptr, the new value is empty.
|
||||
|
@ -635,7 +689,7 @@ void TagFieldEdit::updateValue(Tag *tag, PreviousValueHandling previousValueHand
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Updates the current value manually.
|
||||
* \brief Updates the currently shown value manually.
|
||||
* \param value Specifies the new value.
|
||||
* \param previousValueHandling Specifies how to deal with the previous value.
|
||||
* \param updateRestoreButton Specifies whether the "restore button" should be updated.
|
||||
|
@ -673,7 +727,7 @@ void TagFieldEdit::updateValue(const TagValue &value, PreviousValueHandling prev
|
|||
PositionInSet pos;
|
||||
try {
|
||||
pos = value.toPositionIntSet();
|
||||
} catch(ConversionException &) {
|
||||
} catch(const ConversionException &) {
|
||||
conversionError = true;
|
||||
}
|
||||
if(previousValueHandling == PreviousValueHandling::Clear || pos.position()) {
|
||||
|
@ -695,7 +749,7 @@ void TagFieldEdit::updateValue(const TagValue &value, PreviousValueHandling prev
|
|||
int num;
|
||||
try {
|
||||
num = value.toInteger();
|
||||
} catch(ConversionException &) {
|
||||
} catch(const ConversionException &) {
|
||||
conversionError = true;
|
||||
num = 0;
|
||||
}
|
||||
|
@ -872,54 +926,11 @@ void TagFieldEdit::apply()
|
|||
m_pictureSelection->apply();
|
||||
}
|
||||
} else {
|
||||
TagValue value;
|
||||
TagTextEncoding encoding = Settings::preferredEncoding();
|
||||
if(!tag->canEncodingBeUsed(encoding)) {
|
||||
encoding = tag->proposedTextEncoding();
|
||||
}
|
||||
switch(m_dataType) {
|
||||
case TagDataType::Text:
|
||||
case TagDataType::TimeSpan:
|
||||
case TagDataType::DateTime:
|
||||
switch(m_field) {
|
||||
case KnownField::Genre:
|
||||
if(m_comboBox) {
|
||||
value.assignText(Utility::qstringToString(m_comboBox->currentText(), encoding), encoding);
|
||||
}
|
||||
break;
|
||||
case KnownField::Lyrics:
|
||||
if(m_plainTextEdit) {
|
||||
value.assignText(Utility::qstringToString(m_plainTextEdit->toPlainText(), encoding), encoding);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if(m_lineEdit) {
|
||||
value.assignText(Utility::qstringToString(m_lineEdit->text(), encoding), encoding);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TagDataType::Integer:
|
||||
if(m_spinBoxes.first && m_spinBoxes.first->value()) {
|
||||
value.assignInteger(m_spinBoxes.first->value());
|
||||
}
|
||||
break;
|
||||
case TagDataType::PositionInSet:
|
||||
if(m_spinBoxes.first && m_spinBoxes.second) {
|
||||
value.assignPosition(PositionInSet(m_spinBoxes.first->value(), m_spinBoxes.second->value()));
|
||||
}
|
||||
break;
|
||||
case TagDataType::StandardGenreIndex:
|
||||
if(m_comboBox) {
|
||||
value.assignText(Utility::qstringToString(m_comboBox->currentText(), encoding), encoding);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
if(m_descriptionLineEdit && tag->supportsDescription(m_field)) { // setup description line edit
|
||||
value.setDescription(Utility::qstringToString(m_descriptionLineEdit->text(), encoding), encoding);
|
||||
}
|
||||
tag->setValue(m_field, value);
|
||||
tag->setValue(m_field, value(encoding, tag->supportsDescription(m_field)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -931,7 +942,7 @@ bool TagFieldEdit::eventFilter(QObject *obj, QEvent *event)
|
|||
{
|
||||
switch(event->type()) {
|
||||
case QEvent::KeyRelease: {
|
||||
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
||||
auto *keyEvent = static_cast<QKeyEvent *>(event);
|
||||
int key = keyEvent->key();
|
||||
switch(key) {
|
||||
case Qt::Key_Return:
|
||||
|
|
|
@ -17,6 +17,7 @@ namespace Media {
|
|||
class Tag;
|
||||
DECLARE_ENUM(KnownField, unsigned int)
|
||||
DECLARE_ENUM(TagDataType, unsigned int)
|
||||
DECLARE_ENUM(TagTextEncoding, unsigned int)
|
||||
class TagValue;
|
||||
}
|
||||
|
||||
|
@ -43,6 +44,7 @@ public:
|
|||
const QList<Media::Tag *> &tags() const;
|
||||
Media::KnownField field() const;
|
||||
void setTagField(const QList<Media::Tag *> &tags, Media::KnownField field, PreviousValueHandling previousValueHandling = PreviousValueHandling::Clear, bool preventUiUpdate = false);
|
||||
Media::TagValue value(Media::TagTextEncoding encoding, bool includeDescription) const;
|
||||
bool setValue(const Media::TagValue &value, PreviousValueHandling previousValueHandling = PreviousValueHandling::Clear);
|
||||
bool hasDescription() const;
|
||||
bool canApply(Media::KnownField field) const;
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
#include "./networkaccessmanager.h"
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
namespace Utility
|
||||
{
|
||||
|
||||
QNetworkAccessManager &networkAccessManager()
|
||||
{
|
||||
static QNetworkAccessManager mgr;
|
||||
return mgr;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
#ifndef TAGEDITOR_NETWORKACCESSMANAGER_H
|
||||
#define TAGEDITOR_NETWORKACCESSMANAGER_H
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QNetworkAccessManager)
|
||||
|
||||
namespace Utility {
|
||||
|
||||
QNetworkAccessManager &networkAccessManager();
|
||||
|
||||
}
|
||||
|
||||
#endif // TAGEDITOR_NETWORKACCESSMANAGER_H
|
|
@ -46,7 +46,6 @@ HEADERS += \
|
|||
gui/javascripthighlighter.h \
|
||||
gui/picturepreviewselection.h \
|
||||
gui/notificationmodel.h \
|
||||
gui/infowidgetbase.h \
|
||||
gui/settingsdialog.h \
|
||||
renamingutility/filesystemitem.h \
|
||||
renamingutility/filesystemitemmodel.h \
|
||||
|
@ -63,7 +62,9 @@ HEADERS += \
|
|||
gui/attachmentsmodel.h \
|
||||
gui/attachmentsedit.h \
|
||||
gui/codeedit.h \
|
||||
gui/tageditorwidget.h
|
||||
gui/tageditorwidget.h \
|
||||
dbquery/dbquery.h \
|
||||
gui/dbquerywidget.h
|
||||
|
||||
SOURCES += \
|
||||
application/main.cpp \
|
||||
|
@ -80,7 +81,6 @@ SOURCES += \
|
|||
gui/tagedit.cpp \
|
||||
gui/picturepreviewselection.cpp \
|
||||
gui/notificationmodel.cpp \
|
||||
gui/infowidgetbase.cpp \
|
||||
renamingutility/filesystemitem.cpp \
|
||||
renamingutility/filesystemitemmodel.cpp \
|
||||
renamingutility/filteredfilesystemitemmodel.cpp \
|
||||
|
@ -94,7 +94,9 @@ SOURCES += \
|
|||
gui/attachmentsmodel.cpp \
|
||||
gui/attachmentsedit.cpp \
|
||||
gui/codeedit.cpp \
|
||||
gui/tageditorwidget.cpp
|
||||
gui/tageditorwidget.cpp \
|
||||
dbquery/dbquery.cpp \
|
||||
gui/dbquerywidget.cpp
|
||||
|
||||
FORMS += \
|
||||
gui/id3v2optionpage.ui \
|
||||
|
@ -112,7 +114,8 @@ FORMS += \
|
|||
gui/attachmentsedit.ui \
|
||||
gui/editortempoptionpage.ui \
|
||||
gui/filelayout.ui \
|
||||
gui/tageditorwidget.ui
|
||||
gui/tageditorwidget.ui \
|
||||
gui/dbquerywidget.ui
|
||||
|
||||
RESOURCES += \
|
||||
resources/icons.qrc \
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue