basic support for MusicBrainz queries

This commit is contained in:
Martchus 2016-03-03 22:21:15 +01:00
parent f8cfb3ec14
commit 95993ebfc9
33 changed files with 3220 additions and 1887 deletions

View File

@ -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
#)

View File

@ -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) {

View File

@ -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;

View File

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

View File

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

27
dbquery/api.md Normal file
View File

@ -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)

340
dbquery/dbquery.cpp Normal file
View File

@ -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"

86
dbquery/dbquery.h Normal file
View File

@ -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

228
gui/dbquerywidget.cpp Normal file
View File

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

55
gui/dbquerywidget.h Normal file
View File

@ -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

272
gui/dbquerywidget.ui Normal file
View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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

View File

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

View File

@ -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;
};
}

View File

@ -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&amp;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&amp;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>&amp;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>&amp;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>

View File

@ -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()) {

View File

@ -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);

View File

@ -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()) {

View File

@ -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;

View File

@ -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);

View File

@ -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."));

View File

@ -53,11 +53,14 @@ public:
std::mutex &fileOperationMutex();
const QString &currentPath() 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

View File

@ -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>

View File

@ -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:

View File

@ -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;

View File

@ -0,0 +1,14 @@
#include "./networkaccessmanager.h"
#include <QNetworkAccessManager>
namespace Utility
{
QNetworkAccessManager &networkAccessManager()
{
static QNetworkAccessManager mgr;
return mgr;
}
}

View File

@ -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

View File

@ -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