From 95993ebfc9d4951cbfbaf9bf551fcec0bfe7eec7 Mon Sep 17 00:00:00 2001 From: Martchus Date: Thu, 3 Mar 2016 22:21:15 +0100 Subject: [PATCH] basic support for MusicBrainz queries --- CMakeLists.txt | 9 +- application/knownfieldmodel.cpp | 6 + application/knownfieldmodel.h | 3 +- application/settings.cpp | 59 +- application/settings.h | 5 + dbquery/api.md | 27 + dbquery/dbquery.cpp | 340 +++++ dbquery/dbquery.h | 86 ++ gui/dbquerywidget.cpp | 228 ++++ gui/dbquerywidget.h | 55 + gui/dbquerywidget.ui | 272 ++++ gui/editorautocorrectionoptionpage.ui | 17 +- gui/editorfieldsoptionpage.ui | 14 +- gui/infowidgetbase.cpp | 167 --- gui/infowidgetbase.h | 55 - gui/mainwindow.cpp | 46 +- gui/mainwindow.h | 3 + gui/mainwindow.ui | 124 +- gui/notificationlabel.cpp | 9 +- gui/notificationlabel.h | 1 + gui/renamefilesdialog.cpp | 2 +- gui/tagedit.cpp | 34 +- gui/tagedit.h | 1 + gui/tageditorwidget.cpp | 12 +- gui/tageditorwidget.h | 12 +- gui/tageditorwidget.ui | 8 + gui/tagfieldedit.cpp | 111 +- gui/tagfieldedit.h | 2 + misc/networkaccessmanager.cpp | 14 + misc/networkaccessmanager.h | 14 + tageditor.pro | 13 +- translations/tageditor_de_DE.ts | 1679 ++++++++++++++----------- translations/tageditor_en_US.ts | 1679 ++++++++++++++----------- 33 files changed, 3220 insertions(+), 1887 deletions(-) create mode 100644 dbquery/api.md create mode 100644 dbquery/dbquery.cpp create mode 100644 dbquery/dbquery.h create mode 100644 gui/dbquerywidget.cpp create mode 100644 gui/dbquerywidget.h create mode 100644 gui/dbquerywidget.ui delete mode 100644 gui/infowidgetbase.cpp delete mode 100644 gui/infowidgetbase.h create mode 100644 misc/networkaccessmanager.cpp create mode 100644 misc/networkaccessmanager.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1fe38cb..603bebb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 #) diff --git a/application/knownfieldmodel.cpp b/application/knownfieldmodel.cpp index 4ef2381..ee12730 100644 --- a/application/knownfieldmodel.cpp +++ b/application/knownfieldmodel.cpp @@ -97,6 +97,12 @@ KnownFieldModel::KnownFieldModel(QObject *parent, DefaultSelection defaultSelect setItems(items); } +KnownFieldModel::KnownFieldModel(const QList &items, QObject *parent) : + ChecklistModel(parent) +{ + setItems(items); +} + QVariant KnownFieldModel::headerData(int section, Qt::Orientation orientation, int role) const { switch(orientation) { diff --git a/application/knownfieldmodel.h b/application/knownfieldmodel.h index a7aed7c..576fc2c 100644 --- a/application/knownfieldmodel.h +++ b/application/knownfieldmodel.h @@ -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 &items, QObject *parent = nullptr); QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; virtual QString labelForId(const QVariant &id) const; diff --git a/application/settings.cpp b/application/settings.cpp index e86ecea..55ef3fe 100644 --- a/application/settings.cpp +++ b/application/settings.cpp @@ -2,6 +2,7 @@ #include "./knownfieldmodel.h" #include +#include #include #include @@ -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() + << 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()); diff --git a/application/settings.h b/application/settings.h index 6ea6d9c..26d6b88 100644 --- a/application/settings.h +++ b/application/settings.h @@ -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(); diff --git a/dbquery/api.md b/dbquery/api.md new file mode 100644 index 0000000..1ffd913 --- /dev/null +++ b/dbquery/api.md @@ -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) diff --git a/dbquery/dbquery.cpp b/dbquery/dbquery.cpp new file mode 100644 index 0000000..10e3632 --- /dev/null +++ b/dbquery/dbquery.cpp @@ -0,0 +1,340 @@ +#include "./dbquery.h" + +#include "../misc/utility.h" +#include "../misc/networkaccessmanager.h" + +#include +#include + +#include +#include +#include +#include + +#define xmlReader m_reader +#include + +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" diff --git a/dbquery/dbquery.h b/dbquery/dbquery.h new file mode 100644 index 0000000..870346e --- /dev/null +++ b/dbquery/dbquery.h @@ -0,0 +1,86 @@ +#ifndef DBQUERY_H +#define DBQUERY_H + +#include + +#include + +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 &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 m_results; + QStringList m_errorList; + bool m_resultsAvailable; +}; + +inline const QList &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 diff --git a/gui/dbquerywidget.cpp b/gui/dbquerywidget.cpp new file mode 100644 index 0000000..0334053 --- /dev/null +++ b/gui/dbquerywidget.cpp @@ -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 + +#include + +#include + +#include + +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(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(event); + int key = keyEvent->key(); + switch(key) { + case Qt::Key_Return: + startSearch(); + break; + default: + ; + } + break; + } default: + ; + } + return QWidget::eventFilter(obj, event); + } +} + +} diff --git a/gui/dbquerywidget.h b/gui/dbquerywidget.h new file mode 100644 index 0000000..1466eb5 --- /dev/null +++ b/gui/dbquerywidget.h @@ -0,0 +1,55 @@ +#ifndef DBQUERYWIDGET_H +#define DBQUERYWIDGET_H + +#include + +#include + +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 m_ui; + TagEditorWidget *m_tagEditorWidget; + QueryResultsModel *m_model; +}; + +} + +#endif // DBQUERYWIDGET_H diff --git a/gui/dbquerywidget.ui b/gui/dbquerywidget.ui new file mode 100644 index 0000000..ed9a3e7 --- /dev/null +++ b/gui/dbquerywidget.ui @@ -0,0 +1,272 @@ + + + QtGui::DbQueryWidget + + + MusicBrainz search + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Search criteria + + + + 2 + + + 2 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + Song + + + + + + + 2 + + + + + + 60 + 0 + + + + ? + + + + + + + ? + + + + + + + + + Album + + + + + + + ? + + + + + + + Artist + + + + + + + ? + + + + + + + + + + Qt::Vertical + + + + + Fields to be used + + + + 2 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + QListView::Free + + + QListView::LeftToRight + + + true + + + 3 + + + true + + + + + + + override existing values + + + true + + + + + + + + + + + + + + + + + + 325 + 16777215 + + + + + + + + Qt::Horizontal + + + + 433 + 20 + + + + + + + + Abort + + + + + + + Search + + + + .. + + + + + + + false + + + Apply results + + + + + + + + + + + Widgets::ClearLineEdit + QLineEdit +
qtutilities/widgets/clearlineedit.h
+
+ + NotificationLabel + QWidget +
gui/notificationlabel.h
+ 1 +
+ + Widgets::ClearSpinBox + QSpinBox +
qtutilities/widgets/clearspinbox.h
+
+
+ + +
diff --git a/gui/editorautocorrectionoptionpage.ui b/gui/editorautocorrectionoptionpage.ui index 244de1a..708619c 100644 --- a/gui/editorautocorrectionoptionpage.ui +++ b/gui/editorautocorrectionoptionpage.ui @@ -2,6 +2,14 @@ QtGui::EditorAutoCorrectionOptionPage + + + 0 + 0 + 346 + 446 + + General options page @@ -68,15 +76,6 @@ - - QFrame::Panel - - - QFrame::Sunken - - - 1 - true diff --git a/gui/editorfieldsoptionpage.ui b/gui/editorfieldsoptionpage.ui index d793dad..48b7aea 100644 --- a/gui/editorfieldsoptionpage.ui +++ b/gui/editorfieldsoptionpage.ui @@ -2,6 +2,14 @@ QtGui::EditorFieldsOptionPage + + + 0 + 0 + 268 + 342 + + Form @@ -18,12 +26,6 @@ - - QFrame::Panel - - - QFrame::Sunken - 1 diff --git a/gui/infowidgetbase.cpp b/gui/infowidgetbase.cpp deleted file mode 100644 index 1e63dcd..0000000 --- a/gui/infowidgetbase.cpp +++ /dev/null @@ -1,167 +0,0 @@ -#include "./infowidgetbase.h" -#include "./notificationmodel.h" - -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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; -} - -} diff --git a/gui/infowidgetbase.h b/gui/infowidgetbase.h deleted file mode 100644 index e924d13..0000000 --- a/gui/infowidgetbase.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef INFOWIDGETBASE_H -#define INFOWIDGETBASE_H - -#include - -#include - -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 diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 9b6b07f..b59b64c 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -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(); } diff --git a/gui/mainwindow.h b/gui/mainwindow.h index a888fb2..9611967 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -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 m_renameFilesDlg; + DbQueryWidget *m_dbQueryWidget; }; } diff --git a/gui/mainwindow.ui b/gui/mainwindow.ui index 0904aad..3609533 100644 --- a/gui/mainwindow.ui +++ b/gui/mainwindow.ui @@ -18,7 +18,7 @@ :/tageditor/icons/hicolor/16x16/apps/tageditor.png:/tageditor/icons/hicolor/16x16/apps/tageditor.png - + 0 @@ -35,45 +35,7 @@ 0 - - - Qt::Horizontal - - - - - 2 - - - 4 - - - 4 - - - - - - - - true - - - true - - - - - - - Select next file/directory - - - - - - - + @@ -120,6 +82,7 @@ A&pplication + @@ -129,6 +92,71 @@ + + + false + + + QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable + + + File sele&ction + + + 1 + + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + + + true + + + true + + + + + + + Select next file + + + + + + + + + false + + + &MusicBrains search (beta) + + + 1 + + + false @@ -297,6 +325,18 @@ Ctrl+E + + + + .. + + + &Open MusicBrainz search + + + F10 + + @@ -308,12 +348,8 @@ TagEditorWidget QWidget
gui/tageditorwidget.h
- 0
- - filesTreeView - diff --git a/gui/notificationlabel.cpp b/gui/notificationlabel.cpp index 988db96..5cebe31 100644 --- a/gui/notificationlabel.cpp +++ b/gui/notificationlabel.cpp @@ -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()) { diff --git a/gui/notificationlabel.h b/gui/notificationlabel.h index 73d1396..f087417 100644 --- a/gui/notificationlabel.h +++ b/gui/notificationlabel.h @@ -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); diff --git a/gui/renamefilesdialog.cpp b/gui/renamefilesdialog.cpp index 4e37ccd..97f8689 100644 --- a/gui/renamefilesdialog.cpp +++ b/gui/renamefilesdialog.cpp @@ -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()) { diff --git a/gui/tagedit.cpp b/gui/tagedit.cpp index 6b45250..65f30e8 100644 --- a/gui/tagedit.cpp +++ b/gui/tagedit.cpp @@ -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 &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 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; diff --git a/gui/tagedit.h b/gui/tagedit.h index 903d458..c81d0a0 100644 --- a/gui/tagedit.h +++ b/gui/tagedit.h @@ -32,6 +32,7 @@ class TagEdit : public QWidget public: explicit TagEdit(QWidget *parent = nullptr); const QList &tags() const; + Media::TagValue value(Media::KnownField field) const; void setTag(Media::Tag *tag, bool updateUi = true); void setTags(const QList &tags, bool updateUi = true); bool setValue(Media::KnownField field, const Media::TagValue &value, PreviousValueHandling previousValueHandling = PreviousValueHandling::Clear); diff --git a/gui/tageditorwidget.cpp b/gui/tageditorwidget.cpp index 2fef1e2..ad0b1f3 100644 --- a/gui/tageditorwidget.cpp +++ b/gui/tageditorwidget.cpp @@ -579,6 +579,14 @@ void TagEditorWidget::foreachTagEdit(const std::function &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(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.")); diff --git a/gui/tageditorwidget.h b/gui/tageditorwidget.h index fa5805c..5fb4d1f 100644 --- a/gui/tageditorwidget.h +++ b/gui/tageditorwidget.h @@ -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 &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 &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 diff --git a/gui/tageditorwidget.ui b/gui/tageditorwidget.ui index 6c98b6e..12ae9c9 100644 --- a/gui/tageditorwidget.ui +++ b/gui/tageditorwidget.ui @@ -2,6 +2,14 @@ QtGui::TagEditorWidget + + + 0 + 0 + 634 + 203 + + true diff --git a/gui/tagfieldedit.cpp b/gui/tagfieldedit.cpp index 4c0c808..ceec6b2 100644 --- a/gui/tagfieldedit.cpp +++ b/gui/tagfieldedit.cpp @@ -106,6 +106,60 @@ void TagFieldEdit::setTagField(const QList &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(event); + auto *keyEvent = static_cast(event); int key = keyEvent->key(); switch(key) { case Qt::Key_Return: diff --git a/gui/tagfieldedit.h b/gui/tagfieldedit.h index fb95a19..5b60006 100644 --- a/gui/tagfieldedit.h +++ b/gui/tagfieldedit.h @@ -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 &tags() const; Media::KnownField field() const; void setTagField(const QList &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; diff --git a/misc/networkaccessmanager.cpp b/misc/networkaccessmanager.cpp new file mode 100644 index 0000000..63e3acb --- /dev/null +++ b/misc/networkaccessmanager.cpp @@ -0,0 +1,14 @@ +#include "./networkaccessmanager.h" + +#include + +namespace Utility +{ + +QNetworkAccessManager &networkAccessManager() +{ + static QNetworkAccessManager mgr; + return mgr; +} + +} diff --git a/misc/networkaccessmanager.h b/misc/networkaccessmanager.h new file mode 100644 index 0000000..e54f11a --- /dev/null +++ b/misc/networkaccessmanager.h @@ -0,0 +1,14 @@ +#ifndef TAGEDITOR_NETWORKACCESSMANAGER_H +#define TAGEDITOR_NETWORKACCESSMANAGER_H + +#include + +QT_FORWARD_DECLARE_CLASS(QNetworkAccessManager) + +namespace Utility { + +QNetworkAccessManager &networkAccessManager(); + +} + +#endif // TAGEDITOR_NETWORKACCESSMANAGER_H diff --git a/tageditor.pro b/tageditor.pro index dfe8001..5a81c32 100644 --- a/tageditor.pro +++ b/tageditor.pro @@ -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 \ diff --git a/translations/tageditor_de_DE.ts b/translations/tageditor_de_DE.ts index 39c020f..915f4d2 100644 --- a/translations/tageditor_de_DE.ts +++ b/translations/tageditor_de_DE.ts @@ -22,175 +22,175 @@ - + Path - - - - + + + + Size - - + + Duration - + Overall avg. bitrate - - + + Mime-type - - - - - - + + + + + + show details - + Container - + Title - - - + + + Version - + Channel count - + Attachment - + Description - + Chapter - + Name (%1) - + Start time - + End time - + Edition - + Chapters - + , size: - + section has not been analyzed - + Notifications (reparsing) - + Info for %1 - + Title (segment %1) - + Read version - + Document type - + Document version - + Document read version - + Padding size - + Tags - + Target level - + Field count - - + + Tracks @@ -201,9 +201,9 @@ - - - + + + ID @@ -224,7 +224,7 @@ - + Name @@ -254,127 +254,148 @@ - + + Level + + + + + + The version/level of the track's format. + + + + Extension - + Used format extensions. - + Format/codec ID - + The raw format/codec identifier extracted from the container. - + Avg. bitrate - + Maximum bitrate - + Creation time - + Modification time - + Language - + Compressor name - - + + Sampling frequency - + Sample count - + Bits per sample - + Quality - + Pixel size - + Display size - + + Pixel Aspect Ratio + + + + Cropping - + Resolution - - + + Channel config - - + + Channel configuration - + Bit depth - + Frames per second - - - + + Chroma format + + + + + + Labeled as - + %1 tag(s) assigned @@ -382,7 +403,7 @@ - + file has %1 track(s) @@ -390,7 +411,7 @@ - + %1 attachment(s) assigned @@ -398,7 +419,7 @@ - + file has %1 edition(s) @@ -406,12 +427,12 @@ - + chapters - + file has %1 chapter(s) @@ -419,42 +440,42 @@ - + expand all - + collapse all - + hex - + Attachments - + Editions/chapters - + Structure - + Notifications - + %1 notification(s) available @@ -462,22 +483,22 @@ - + show notifications - + Context - + Message - + Time @@ -573,45 +594,134 @@ + + QtGui::DbQueryWidget + + + MusicBrainz search + + + + + Search criteria + + + + + Song + + + + + + + + ? + + + + + Album + + + + + Artist + + + + + Fields to be used + + + + + override existing values + + + + + Abort + + + + + Search + + + + + Apply results + + + + + Search hasn't been started. + + + + + Retrieving ... + + + + + Aborted + + + + + No results available + + + + + %1 result(s) available + + + + + + QtGui::EditorAutoCorrectionOptionPage - + General options page - + <html><head/><body><p>Auto correction/completion will be applied when showing the selected tag fields after <span style=" font-style:italic;">loading</span> a file but <span style=" font-style:italic;">not</span> before saving.</p></body></html> - + Auto correction/completion features to be used - + insert title from filename if none present - + trim whitespaces - + format names like "an example_name" to "An Example Name" - + fix umlauts (replaces ae with ä for example) - + Fields @@ -624,17 +734,17 @@ QtGui::EditorFieldsOptionPage - + Form - + Fields to be shown - + <html> <head/> <body> @@ -1092,280 +1202,170 @@ another position would prevent rewriting the entire file - + Select next file/directory - - No file selected - - - - - Document title - - - - - Keep previous values - - - - - Restore - - - - - Clear - - - - - - Abort - - - - - Save - - - - - Delete - - - - - all tags from the file - - - - - Open next file - - - - - and save current before - - - - - Close - - - - + Fi&le - + Di&rectory - + A&pplication - + + File sele&ction + + + + + &MusicBrains search + + + + Save &entered tags - + &Delete all tags - + Ctrl+D - + &Close - + &Select next file - + &Rename files using tags - + &Open - + &Save file information as HTML document - + Ctrl+H - + Select &next file and save current - + F3 - + &About - + &Quit - + &Settings - + &Play (external player) - + Ctrl+E - + + &Open MusicBrainz search + + + + + F10 + + + + ? - + Ctrl+S - + Ctrl+O - + F8 - - No, disable this feature - - - - - Yes, but only if both files are in the same directory - - - - - Yes, regardless where the files are stored - - - - + Ctrl+Q - - Let you choose whether the values of the -previously opened file should be cleared when -opening the next file. -Keeping these values might be useful when -tagging multiple files of the same album. - - - - - Let you enable or disable the automatic -creation or removal of tags (according to -the settings) when loading a file. - -You can also create or remove tags manually. - - - - - Tag management - - - - - Restores the original values read from -the file reverting all unsaved changings. - - - - - Clears all values. - - - - - Aborts the saving process. The tageditor will try to restore the original file from the backup. - - - - - all entered values - - - - - the file and discard changings - - - - + F6 - + F2 - + &Reload (reverts all changes!) - + F5 - - - Manage tags automatically when loading file - - @@ -1373,312 +1373,38 @@ the file reverting all unsaved changings. - - Unable to save the selected file and load the next file after saving because the current process hasn't finished yet. - - - - - Unable to find the next file. The chanings of the currently opened file will be saved regardless. - - - - - Unable to load the selected file "%1" because the current process hasn't finished yet. - - - - - The file could not be opened because an IO error occurred. - - - - - File could be parsed correctly. - - - - - File couldn't be parsed correctly. - - - - - There are critical parsing notifications. - - - - - There are warnings. - - - - - There is no (supported) tag assigned. - - - - - File format is not supported (an ID3 tag can be added anyways). - - - - - The file %1 has been opened. - - - - - Unable to apply the entered tags to the file because the current process hasn't finished yet. - - - - - Saving tags ... - - - - - No file has been opened. - - - - - Do you really want to delete all tags from the file? - - - - - don't show this message again - - - - - Unable to delete all tags from the file because the current process hasn't been finished yet. - - - - - Add tag - - - - - Remove tag - - - - - Change target - - - - - Segment %1 - - - - - Attachments - - - - - Matroska tag - - - - - MP4/iTunes tag - - - - - Vorbis comment - - - - - Tag - - - - - ID3v1 tag - - - - - ID3v2 tag - - - - - Copy - - - - + No file opened. - - The file is beeing parsed ... - - - - - Unable to reload the file because the current process hasn't finished yet. - - - - - Currently is not file opened. - - - - - Deleting all tags ... - - - - - The selected file stores no tag (at least no supported), so there is nothing to delete. - - - - - No file has been opened, so no tags can be deleted. - - - - - Unable to start saving process because there an other process hasn't finished yet. - - - - - Cancelling ... - - - - - The tags have been saved, but there is/are %1 warning(s) - - - - - - - - and %1 error(s). - - - - - - - - The tags couldn't be saved. See the info box for detail. - - - - - The tags couldn't be saved because an IO error occured. - - - - + A tag editing utility supporting ID3, MP4 (iTunes style), Vorbis and Matroska tags. - - Automatic tag management - - - - - The currently opened file changed on the disk. - - - - - A tag (with the selected target) already exists. - - - - - The tag can not be created. - - - - - Unable to remove the tag because the current process hasn't been finished yet. - - - - - Unable to remove the tag because no file is opened. - - - - - Unable to change the target because the current process hasn't been finished yet. - - - - - Unable to change the target because no file is opened. - - - - - Can not change the target of the selected tag because the tag does not support targets. - - - - - The tags have been saved, but there is/are %1 warning(s). - - - - - - - - The tags have been saved. - - - - + Unable to show the next file because it can't be found anymore. - - The container format of the selected file is not supported. The file can be treated as MP3 file (an ID3 tag according to the settings will be created). This might break the file. Do you want to continue? + + Unable to save file information because the current process hasn't been finished yet. - - Treat file as MP3 file - - - - + Unable to write to file. %1 - + Unable to open file. - + No file information available. @@ -1721,12 +1447,17 @@ the file reverting all unsaved changings. - + Browse - + + Open + + + + Explore @@ -1987,6 +1718,44 @@ the file reverting all unsaved changings. + + QtGui::QueryResultsModel + + + Song title + + + + + Album + + + + + Artist + + + + + Year + + + + + Track + + + + + Total tracks + + + + + Genre + + + QtGui::RenameFilesDialog @@ -2253,6 +2022,408 @@ Error in line %1: %3 + + QtGui::TagEditorWidget + + + No file selected + + + + + Document title + + + + + Let you choose whether the values of the +previously opened file should be cleared when +opening the next file. +Keeping these values might be useful when +tagging multiple files of the same album. + + + + + Keep previous values + + + + + Let you enable or disable the automatic +creation or removal of tags (according to +the settings) when loading a file. + +You can also create or remove tags manually. + + + + + Tag management + + + + + Restores the original values read from +the file reverting all unsaved changings. + + + + + Restore + + + + + Clears all values. + + + + + Clear + + + + + Aborts the saving process. The tageditor will try to restore the original file from the backup. + + + + + + Abort + + + + + Save + + + + + all entered values + + + + + Delete + + + + + all tags from the file + + + + + Open next file + + + + + and save current before + + + + + Close + + + + + the file and discard changings + + + + + No, disable this feature + + + + + Yes, but only if both files are in the same directory + + + + + Yes, regardless where the files are stored + + + + + Manage tags automatically when loading file + + + + + Add tag + + + + + Remove tag + + + + + Change target + + + + + Segment %1 + + + + + Attachments + + + + + Matroska tag + + + + + MP4/iTunes tag + + + + + Vorbis comment + + + + + Tag + + + + + ID3v1 tag + + + + + ID3v2 tag + + + + + Copy + + + + + Unable to load the selected file "%1" because the current process hasn't finished yet. + + + + + The file is beeing parsed ... + + + + + Unable to reload the file because the current process hasn't finished yet. + + + + + Currently is not file opened. + + + + + The file could not be opened because an IO error occurred. + + + + + File could be parsed correctly. + + + + + File couldn't be parsed correctly. + + + + + There are critical parsing notifications. + + + + + There are warnings. + + + + + There is no (supported) tag assigned. + + + + + File format is not supported (an ID3 tag can be added anyways). + + + + + The file %1 has been opened. + + + + + Unable to apply the entered tags to the file because the current process hasn't finished yet. + + + + + Saving tags ... + + + + + No file has been opened. + + + + + Unable to delete all tags from the file because the current process hasn't been finished yet. + + + + + Do you really want to delete all tags from the file? + + + + + don't show this message again + + + + + Deleting all tags ... + + + + + The selected file stores no tag (at least no supported), so there is nothing to delete. + + + + + No file has been opened, so no tags can be deleted. + + + + + Unable to start saving process because there an other process hasn't finished yet. + + + + + Cancelling ... + + + + + The tags have been saved, but there is/are %1 warning(s) + + + + + + + + and %1 error(s). + + + + + + + + The tags have been saved, but there is/are %1 warning(s). + + + + + + + + The tags have been saved. + + + + + The tags couldn't be saved. See the info box for detail. + + + + + The tags couldn't be saved because an IO error occured. + + + + + Automatic tag management + + + + + The container format of the selected file is not supported. The file can be treated as MP3 file (an ID3 tag according to the settings will be created). This might break the file. Do you want to continue? + + + + + Treat file as MP3 file + + + + + The currently opened file changed on the disk. + + + + + A tag (with the selected target) already exists. + + + + + The tag can not be created. + + + + + Unable to remove the tag because the current process hasn't been finished yet. + + + + + Unable to remove the tag because no file is opened. + + + + + Unable to change the target because the current process hasn't been finished yet. + + + + + Unable to change the target because no file is opened. + + + + + Can not change the target of the selected tag because the tag does not support targets. + + + QtGui::TagFieldEdit @@ -2262,992 +2433,992 @@ Error in line %1: %3 - - - - - - + + + + + + empty - + Country - + Folk - + Jazz - + Rock - + Reggae - + Blues - + A capella - + Abstract - + Acid - + Acid Jazz - + Acid Punk - + Acoustic - + Alternative - + Alternative Rock - + Ambient - + Anime - + Art Rock - + Audio Theatre - + Audiobook - + Avantgarde - + Ballad - + Baroque - + Bass - + Beat - + Bebop - + Bhangra - + Big Band - + Big Beat - + Black Metal - + Bluegrass - + Booty Bass - + Breakbeat - + BritPop - + Cabaret - + Celtic - + Chamber Music - + Chanson - + Chillout - + Chorus - + Christian Gangsta Rap - + Christian Rap - + Christian Rock - + Classic Rock - + Classical - + Club - + Club-House - + Comedy - + Contemporary Christian - + Crossover - + Cult - + Dance - + Dance Hall - + Darkwave - + Death Metal - + Disco - + Downtempo - + Dream - + Drum & Bass - + Drum Solo - + Dub - + Dubstep - + Duet - + Easy Listening - + EBM - + Eclectic - + Electro - + Electroclash - + Electronic - + Emo - + Ethnic - + Euro-House - + Euro-Techno - + Eurodance - + Experimental - + Fast Fusion - + Folk-Rock - + Folklore - + Freestyle - + Funk - + Fusion - + G-Funk - + Game - + Gangsta - + Garage - + Garage Rock - + Global - + Goa - + Gospel - + Gothic - + Gothic Rock - + Grunge - + Hard Rock - + Hardcore Techno - + Heavy Metal - + Hip-Hop - + House - + Humour - + IDM - + Illbient - + Indie - + Indie Rock - + Industrial - + Industro-Goth - + Instrumental - + Instrumental Pop - + Instrumental Rock - + Jam Band - + Jazz & Funk - + Jpop - + Jungle - + Krautrock - + Latin - + Leftfield - + Lo-Fi - + Lounge - + Math Rock - + Meditative - + Merengue - + Metal - + Musical - + National Folk - + Native US - + Negerpunk - + Neoclassical - + Neue Deutsche Welle - + New Age - + New Romantic - + New Wave - + Noise - + Nu-Breakz - + Oldies - + Opera - + Podcast - + Polka - + Polsk Punk - + Pop - + Pop-Folk - + Pop/Funk - + Porn Groove - + Post-Punk - + Post-Rock - + Power Ballad - + Pranks - + Primus - + Progressive Rock - + Psychedelic - + Psychedelic Rock - + Psytrance - + Punk - + Punk Rock - + Rap - + Rave - + Retro - + Revival - + Rhythmic Soul - + Rock & Roll - + Salsa - + Samba - + Satire - + Shoegaze - + Showtunes - + Ska - + Slow Jam - + Slow Rock - + Sonata - + Soul - + Sound Clip - + Soundtrack - + Southern Rock - + Space - + Space Rock - + Speech - + Swing - + Symphonic Rock - + Symphony - + Synthpop - + Tango - + Techno - + Techno-Industrial - + Terror - + Thrash Metal - + Top 40 - + Trailer - + Trance - + Tribal - + Trip-Hop - + Trop Rock - + Vocal - + World Music - + of - + Description - + editing widget for field type not supported - + The value of this field could not be read from the file because it couldn't be converted proberly. - + The field can not be applied when saving the file and will be lost. - + restore - + restore to value from %1 (%2) @@ -3500,7 +3671,7 @@ Error in line %1: %3 - + Field @@ -3648,12 +3819,12 @@ Error in line %1: %3 TagEdit - + with different targets - + targeting %1 diff --git a/translations/tageditor_en_US.ts b/translations/tageditor_en_US.ts index 8fdbaff..38b90fc 100644 --- a/translations/tageditor_en_US.ts +++ b/translations/tageditor_en_US.ts @@ -22,175 +22,175 @@ - + Path - - - - + + + + Size - - + + Duration - + Overall avg. bitrate - - + + Mime-type - - - - - - + + + + + + show details - + Container - + Title - - - + + + Version - + Channel count - + Attachment - + Description - + Chapter - + Name (%1) - + Start time - + End time - + Edition - + Chapters - + , size: - + section has not been analyzed - + Notifications (reparsing) - + Info for %1 - + Title (segment %1) - + Read version - + Document type - + Document version - + Document read version - + Padding size - + Tags - + Target level - + Field count - - + + Tracks @@ -201,9 +201,9 @@ - - - + + + ID @@ -224,7 +224,7 @@ - + Name @@ -254,127 +254,148 @@ - + + Level + + + + + + The version/level of the track's format. + + + + Extension - + Used format extensions. - + Format/codec ID - + The raw format/codec identifier extracted from the container. - + Avg. bitrate - + Maximum bitrate - + Creation time - + Modification time - + Language - + Compressor name - - + + Sampling frequency - + Sample count - + Bits per sample - + Quality - + Pixel size - + Display size - + + Pixel Aspect Ratio + + + + Cropping - + Resolution - - + + Channel config - - + + Channel configuration - + Bit depth - + Frames per second - - - + + Chroma format + + + + + + Labeled as - + %1 tag(s) assigned %1 tag assigned @@ -382,7 +403,7 @@ - + file has %1 track(s) file has %1 track @@ -390,7 +411,7 @@ - + %1 attachment(s) assigned %1 attachment assigned @@ -398,7 +419,7 @@ - + file has %1 edition(s) file has %1 edition @@ -406,12 +427,12 @@ - + chapters - + file has %1 chapter(s) file has %1 chapter @@ -419,42 +440,42 @@ - + expand all - + collapse all - + hex - + Attachments - + Editions/chapters - + Structure - + Notifications - + %1 notification(s) available %1 notification available @@ -462,22 +483,22 @@ - + show notifications - + Context - + Message - + Time @@ -573,45 +594,134 @@ + + QtGui::DbQueryWidget + + + MusicBrainz search + + + + + Search criteria + + + + + Song + + + + + + + + ? + + + + + Album + + + + + Artist + + + + + Fields to be used + + + + + override existing values + + + + + Abort + + + + + Search + + + + + Apply results + + + + + Search hasn't been started. + + + + + Retrieving ... + + + + + Aborted + + + + + No results available + + + + + %1 result(s) available + + %1 result available + %1 results available + + + QtGui::EditorAutoCorrectionOptionPage - + General options page - + <html><head/><body><p>Auto correction/completion will be applied when showing the selected tag fields after <span style=" font-style:italic;">loading</span> a file but <span style=" font-style:italic;">not</span> before saving.</p></body></html> - + Auto correction/completion features to be used - + insert title from filename if none present - + trim whitespaces - + format names like "an example_name" to "An Example Name" - + fix umlauts (replaces ae with ä for example) - + Fields @@ -624,17 +734,17 @@ QtGui::EditorFieldsOptionPage - + Form - + Fields to be shown - + <html> <head/> <body> @@ -1092,280 +1202,170 @@ another position would prevent rewriting the entire file - + Select next file/directory - - No file selected - - - - - Document title - - - - - Keep previous values - - - - - Restore - - - - - Clear - - - - - - Abort - - - - - Save - - - - - Delete - - - - - all tags from the file - - - - - Open next file - - - - - and save current before - - - - - Close - - - - + Fi&le - + Di&rectory - + A&pplication - + + File sele&ction + + + + + &MusicBrains search + + + + Save &entered tags - + &Delete all tags - + Ctrl+D - + &Close - + &Select next file - + &Rename files using tags - + &Open - + &Save file information as HTML document - + Ctrl+H - + Select &next file and save current - + F3 - + &About - + &Quit - + &Settings - + &Play (external player) - + Ctrl+E - + + &Open MusicBrainz search + + + + + F10 + + + + ? - + Ctrl+S - + Ctrl+O - + F8 - - No, disable this feature - - - - - Yes, but only if both files are in the same directory - - - - - Yes, regardless where the files are stored - - - - + Ctrl+Q - - Let you choose whether the values of the -previously opened file should be cleared when -opening the next file. -Keeping these values might be useful when -tagging multiple files of the same album. - - - - - Let you enable or disable the automatic -creation or removal of tags (according to -the settings) when loading a file. - -You can also create or remove tags manually. - - - - - Tag management - - - - - Restores the original values read from -the file reverting all unsaved changings. - - - - - Clears all values. - - - - - Aborts the saving process. The tageditor will try to restore the original file from the backup. - - - - - all entered values - - - - - the file and discard changings - - - - + F6 - + F2 - + &Reload (reverts all changes!) - + F5 - - - Manage tags automatically when loading file - - @@ -1373,312 +1373,38 @@ the file reverting all unsaved changings. - - Unable to save the selected file and load the next file after saving because the current process hasn't finished yet. - - - - - Unable to find the next file. The chanings of the currently opened file will be saved regardless. - - - - - Unable to load the selected file "%1" because the current process hasn't finished yet. - - - - - The file could not be opened because an IO error occurred. - - - - - File could be parsed correctly. - - - - - File couldn't be parsed correctly. - - - - - There are critical parsing notifications. - - - - - There are warnings. - - - - - There is no (supported) tag assigned. - - - - - File format is not supported (an ID3 tag can be added anyways). - - - - - The file %1 has been opened. - - - - - Unable to apply the entered tags to the file because the current process hasn't finished yet. - - - - - Saving tags ... - - - - - No file has been opened. - - - - - Do you really want to delete all tags from the file? - - - - - don't show this message again - - - - - Unable to delete all tags from the file because the current process hasn't been finished yet. - - - - - Add tag - - - - - Remove tag - - - - - Change target - - - - - Segment %1 - - - - - Attachments - - - - - Matroska tag - - - - - MP4/iTunes tag - - - - - Vorbis comment - - - - - Tag - - - - - ID3v1 tag - - - - - ID3v2 tag - - - - - Copy - - - - + No file opened. - - The file is beeing parsed ... - - - - - Unable to reload the file because the current process hasn't finished yet. - - - - - Currently is not file opened. - - - - - Deleting all tags ... - - - - - The selected file stores no tag (at least no supported), so there is nothing to delete. - - - - - No file has been opened, so no tags can be deleted. - - - - - Unable to start saving process because there an other process hasn't finished yet. - - - - - Cancelling ... - - - - - The tags have been saved, but there is/are %1 warning(s) - - The tags have been saved, but there is %1 warning - The tags have been saved, but there are %1 warnings - - - - - and %1 error(s). - - - - - - - - The tags couldn't be saved. See the info box for detail. - - - - - The tags couldn't be saved because an IO error occured. - - - - + A tag editing utility supporting ID3, MP4 (iTunes style), Vorbis and Matroska tags. - - Automatic tag management - - - - - The currently opened file changed on the disk. - - - - - A tag (with the selected target) already exists. - - - - - The tag can not be created. - - - - - Unable to remove the tag because the current process hasn't been finished yet. - - - - - Unable to remove the tag because no file is opened. - - - - - Unable to change the target because the current process hasn't been finished yet. - - - - - Unable to change the target because no file is opened. - - - - - Can not change the target of the selected tag because the tag does not support targets. - - - - - The tags have been saved, but there is/are %1 warning(s). - - The tags have been saved, but there is %1 warning. - The tags have been saved, but there are %1 warnings. - - - - - The tags have been saved. - - - - + Unable to show the next file because it can't be found anymore. - - The container format of the selected file is not supported. The file can be treated as MP3 file (an ID3 tag according to the settings will be created). This might break the file. Do you want to continue? + + Unable to save file information because the current process hasn't been finished yet. - - Treat file as MP3 file - - - - + Unable to write to file. %1 - + Unable to open file. - + No file information available. @@ -1721,12 +1447,17 @@ the file reverting all unsaved changings. - + Browse - + + Open + + + + Explore @@ -1987,6 +1718,44 @@ the file reverting all unsaved changings. + + QtGui::QueryResultsModel + + + Song title + + + + + Album + + + + + Artist + + + + + Year + + + + + Track + + + + + Total tracks + + + + + Genre + + + QtGui::RenameFilesDialog @@ -2253,6 +2022,408 @@ Error in line %1: %3 + + QtGui::TagEditorWidget + + + No file selected + + + + + Document title + + + + + Let you choose whether the values of the +previously opened file should be cleared when +opening the next file. +Keeping these values might be useful when +tagging multiple files of the same album. + + + + + Keep previous values + + + + + Let you enable or disable the automatic +creation or removal of tags (according to +the settings) when loading a file. + +You can also create or remove tags manually. + + + + + Tag management + + + + + Restores the original values read from +the file reverting all unsaved changings. + + + + + Restore + + + + + Clears all values. + + + + + Clear + + + + + Aborts the saving process. The tageditor will try to restore the original file from the backup. + + + + + + Abort + + + + + Save + + + + + all entered values + + + + + Delete + + + + + all tags from the file + + + + + Open next file + + + + + and save current before + + + + + Close + + + + + the file and discard changings + + + + + No, disable this feature + + + + + Yes, but only if both files are in the same directory + + + + + Yes, regardless where the files are stored + + + + + Manage tags automatically when loading file + + + + + Add tag + + + + + Remove tag + + + + + Change target + + + + + Segment %1 + + + + + Attachments + + + + + Matroska tag + + + + + MP4/iTunes tag + + + + + Vorbis comment + + + + + Tag + + + + + ID3v1 tag + + + + + ID3v2 tag + + + + + Copy + + + + + Unable to load the selected file "%1" because the current process hasn't finished yet. + + + + + The file is beeing parsed ... + + + + + Unable to reload the file because the current process hasn't finished yet. + + + + + Currently is not file opened. + + + + + The file could not be opened because an IO error occurred. + + + + + File could be parsed correctly. + + + + + File couldn't be parsed correctly. + + + + + There are critical parsing notifications. + + + + + There are warnings. + + + + + There is no (supported) tag assigned. + + + + + File format is not supported (an ID3 tag can be added anyways). + + + + + The file %1 has been opened. + + + + + Unable to apply the entered tags to the file because the current process hasn't finished yet. + + + + + Saving tags ... + + + + + No file has been opened. + + + + + Unable to delete all tags from the file because the current process hasn't been finished yet. + + + + + Do you really want to delete all tags from the file? + + + + + don't show this message again + + + + + Deleting all tags ... + + + + + The selected file stores no tag (at least no supported), so there is nothing to delete. + + + + + No file has been opened, so no tags can be deleted. + + + + + Unable to start saving process because there an other process hasn't finished yet. + + + + + Cancelling ... + + + + + The tags have been saved, but there is/are %1 warning(s) + + The tags have been saved, but there is %1 warning + The tags have been saved, but there are %1 warnings + + + + + and %1 error(s). + + + + + + + + The tags have been saved, but there is/are %1 warning(s). + + The tags have been saved, but there is %1 warning. + The tags have been saved, but there are %1 warnings. + + + + + The tags have been saved. + + + + + The tags couldn't be saved. See the info box for detail. + + + + + The tags couldn't be saved because an IO error occured. + + + + + Automatic tag management + + + + + The container format of the selected file is not supported. The file can be treated as MP3 file (an ID3 tag according to the settings will be created). This might break the file. Do you want to continue? + + + + + Treat file as MP3 file + + + + + The currently opened file changed on the disk. + + + + + A tag (with the selected target) already exists. + + + + + The tag can not be created. + + + + + Unable to remove the tag because the current process hasn't been finished yet. + + + + + Unable to remove the tag because no file is opened. + + + + + Unable to change the target because the current process hasn't been finished yet. + + + + + Unable to change the target because no file is opened. + + + + + Can not change the target of the selected tag because the tag does not support targets. + + + QtGui::TagFieldEdit @@ -2262,992 +2433,992 @@ Error in line %1: %3 - - - - - - + + + + + + empty - + Country - + Folk - + Jazz - + Rock - + Reggae - + Blues - + A capella - + Abstract - + Acid - + Acid Jazz - + Acid Punk - + Acoustic - + Alternative - + Alternative Rock - + Ambient - + Anime - + Art Rock - + Audio Theatre - + Audiobook - + Avantgarde - + Ballad - + Baroque - + Bass - + Beat - + Bebop - + Bhangra - + Big Band - + Big Beat - + Black Metal - + Bluegrass - + Booty Bass - + Breakbeat - + BritPop - + Cabaret - + Celtic - + Chamber Music - + Chanson - + Chillout - + Chorus - + Christian Gangsta Rap - + Christian Rap - + Christian Rock - + Classic Rock - + Classical - + Club - + Club-House - + Comedy - + Contemporary Christian - + Crossover - + Cult - + Dance - + Dance Hall - + Darkwave - + Death Metal - + Disco - + Downtempo - + Dream - + Drum & Bass - + Drum Solo - + Dub - + Dubstep - + Duet - + Easy Listening - + EBM - + Eclectic - + Electro - + Electroclash - + Electronic - + Emo - + Ethnic - + Euro-House - + Euro-Techno - + Eurodance - + Experimental - + Fast Fusion - + Folk-Rock - + Folklore - + Freestyle - + Funk - + Fusion - + G-Funk - + Game - + Gangsta - + Garage - + Garage Rock - + Global - + Goa - + Gospel - + Gothic - + Gothic Rock - + Grunge - + Hard Rock - + Hardcore Techno - + Heavy Metal - + Hip-Hop - + House - + Humour - + IDM - + Illbient - + Indie - + Indie Rock - + Industrial - + Industro-Goth - + Instrumental - + Instrumental Pop - + Instrumental Rock - + Jam Band - + Jazz & Funk - + Jpop - + Jungle - + Krautrock - + Latin - + Leftfield - + Lo-Fi - + Lounge - + Math Rock - + Meditative - + Merengue - + Metal - + Musical - + National Folk - + Native US - + Negerpunk - + Neoclassical - + Neue Deutsche Welle - + New Age - + New Romantic - + New Wave - + Noise - + Nu-Breakz - + Oldies - + Opera - + Podcast - + Polka - + Polsk Punk - + Pop - + Pop-Folk - + Pop/Funk - + Porn Groove - + Post-Punk - + Post-Rock - + Power Ballad - + Pranks - + Primus - + Progressive Rock - + Psychedelic - + Psychedelic Rock - + Psytrance - + Punk - + Punk Rock - + Rap - + Rave - + Retro - + Revival - + Rhythmic Soul - + Rock & Roll - + Salsa - + Samba - + Satire - + Shoegaze - + Showtunes - + Ska - + Slow Jam - + Slow Rock - + Sonata - + Soul - + Sound Clip - + Soundtrack - + Southern Rock - + Space - + Space Rock - + Speech - + Swing - + Symphonic Rock - + Symphony - + Synthpop - + Tango - + Techno - + Techno-Industrial - + Terror - + Thrash Metal - + Top 40 - + Trailer - + Trance - + Tribal - + Trip-Hop - + Trop Rock - + Vocal - + World Music - + of - + Description - + editing widget for field type not supported - + The value of this field could not be read from the file because it couldn't be converted proberly. - + The field can not be applied when saving the file and will be lost. - + restore - + restore to value from %1 (%2) @@ -3500,7 +3671,7 @@ Error in line %1: %3 - + Field @@ -3648,12 +3819,12 @@ Error in line %1: %3 TagEdit - + with different targets - + targeting %1