2016-02-29 23:59:46 +01:00
|
|
|
#include "./tageditorwidget.h"
|
|
|
|
#include "./attachmentsedit.h"
|
|
|
|
#include "./entertargetdialog.h"
|
2016-04-24 21:07:36 +02:00
|
|
|
#include "./fileinfomodel.h"
|
2018-03-07 01:18:01 +01:00
|
|
|
#include "./notificationlabel.h"
|
|
|
|
#include "./tagedit.h"
|
2017-03-14 23:00:26 +01:00
|
|
|
#include "./webviewincludes.h"
|
2016-02-29 23:59:46 +01:00
|
|
|
|
|
|
|
#include "../application/settings.h"
|
2016-05-26 02:15:41 +02:00
|
|
|
#include "../application/targetlevelmodel.h"
|
2016-02-29 23:59:46 +01:00
|
|
|
#include "../misc/htmlinfo.h"
|
|
|
|
#include "../misc/utility.h"
|
|
|
|
|
2017-09-20 19:37:49 +02:00
|
|
|
#include "resources/config.h"
|
2018-03-07 01:18:01 +01:00
|
|
|
#include "ui_tageditorwidget.h"
|
2016-02-29 23:59:46 +01:00
|
|
|
|
|
|
|
#include <tagparser/exceptions.h>
|
|
|
|
#include <tagparser/id3/id3v1tag.h>
|
|
|
|
#include <tagparser/id3/id3v2tag.h>
|
2018-03-07 01:18:01 +01:00
|
|
|
#include <tagparser/matroska/matroskatag.h>
|
2016-02-29 23:59:46 +01:00
|
|
|
#include <tagparser/mp4/mp4container.h>
|
|
|
|
#include <tagparser/mp4/mp4tag.h>
|
2016-05-14 23:23:47 +02:00
|
|
|
#include <tagparser/ogg/oggcontainer.h>
|
2018-03-07 01:18:01 +01:00
|
|
|
#include <tagparser/signature.h>
|
2016-02-29 23:59:46 +01:00
|
|
|
|
2016-12-19 23:53:12 +01:00
|
|
|
#include <qtutilities/misc/conversion.h>
|
2018-03-07 01:18:01 +01:00
|
|
|
#include <qtutilities/misc/dialogutils.h>
|
2016-02-29 23:59:46 +01:00
|
|
|
#include <qtutilities/widgets/clearlineedit.h>
|
|
|
|
|
|
|
|
#include <c++utilities/conversion/stringconversion.h>
|
2018-05-26 22:41:59 +02:00
|
|
|
#include <c++utilities/io/ansiescapecodes.h>
|
2018-03-07 01:18:01 +01:00
|
|
|
#include <c++utilities/io/path.h>
|
2016-02-29 23:59:46 +01:00
|
|
|
|
2020-09-04 00:59:22 +02:00
|
|
|
#include <QActionGroup>
|
2018-03-07 01:18:01 +01:00
|
|
|
#include <QCheckBox>
|
2016-02-29 23:59:46 +01:00
|
|
|
#include <QClipboard>
|
2018-03-07 01:18:01 +01:00
|
|
|
#include <QDesktopServices>
|
2017-09-20 19:37:49 +02:00
|
|
|
#include <QDir>
|
|
|
|
#include <QFileDialog>
|
2018-03-07 01:18:01 +01:00
|
|
|
#include <QFileInfo>
|
|
|
|
#include <QFileSystemModel>
|
2016-02-29 23:59:46 +01:00
|
|
|
#include <QFileSystemWatcher>
|
2018-04-29 19:02:31 +02:00
|
|
|
#include <QGuiApplication>
|
2018-05-28 19:52:13 +02:00
|
|
|
#include <QHeaderView>
|
2022-07-29 23:51:32 +02:00
|
|
|
#include <QInputDialog>
|
2018-03-07 01:18:01 +01:00
|
|
|
#include <QKeyEvent>
|
2016-02-29 23:59:46 +01:00
|
|
|
#include <QMenu>
|
2018-03-07 01:18:01 +01:00
|
|
|
#include <QMessageBox>
|
|
|
|
#include <QMimeData>
|
|
|
|
#include <QPlainTextEdit>
|
2018-06-05 22:45:43 +02:00
|
|
|
#include <QStyle>
|
2018-03-07 01:18:01 +01:00
|
|
|
#include <QTemporaryFile>
|
2016-04-24 21:07:36 +02:00
|
|
|
#include <QTreeView>
|
2016-03-10 22:13:43 +01:00
|
|
|
#include <QtConcurrent>
|
2016-02-29 23:59:46 +01:00
|
|
|
|
|
|
|
#include <algorithm>
|
2021-08-21 01:22:29 +02:00
|
|
|
#include <filesystem>
|
2018-03-07 01:18:01 +01:00
|
|
|
#include <functional>
|
2018-05-26 22:41:59 +02:00
|
|
|
#include <iostream>
|
2016-02-29 23:59:46 +01:00
|
|
|
|
|
|
|
using namespace std;
|
2016-05-16 21:01:01 +02:00
|
|
|
using namespace std::placeholders;
|
2019-06-10 22:49:46 +02:00
|
|
|
using namespace CppUtilities;
|
|
|
|
using namespace CppUtilities::EscapeCodes;
|
|
|
|
using namespace QtUtilities;
|
2018-03-06 23:10:13 +01:00
|
|
|
using namespace TagParser;
|
2019-06-10 22:49:46 +02:00
|
|
|
using namespace Utility;
|
2016-02-29 23:59:46 +01:00
|
|
|
|
|
|
|
namespace QtGui {
|
|
|
|
|
2019-06-10 22:49:46 +02:00
|
|
|
enum LoadingResult : char { ParsingSuccessful, FatalParsingError, IoError };
|
2016-02-29 23:59:46 +01:00
|
|
|
|
|
|
|
/*!
|
|
|
|
* \class QtGui::TagEditorWidget
|
|
|
|
* \brief The TagEditorWidget class provides a widget for tag editing.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Constructs a new tag editor widget.
|
|
|
|
*/
|
2018-03-07 01:18:01 +01:00
|
|
|
TagEditorWidget::TagEditorWidget(QWidget *parent)
|
|
|
|
: QWidget(parent)
|
|
|
|
, m_ui(new Ui::TagEditorWidget)
|
|
|
|
,
|
|
|
|
#ifndef TAGEDITOR_NO_WEBVIEW
|
|
|
|
m_infoWebView(nullptr)
|
|
|
|
,
|
|
|
|
#endif
|
|
|
|
m_infoModel(nullptr)
|
|
|
|
, m_infoTreeView(nullptr)
|
|
|
|
, m_nextFileAfterSaving(false)
|
|
|
|
, m_makingResultsAvailable(false)
|
|
|
|
, m_abortClicked(false)
|
2016-02-29 23:59:46 +01:00
|
|
|
{
|
|
|
|
// setup UI
|
|
|
|
m_ui->setupUi(this);
|
|
|
|
makeHeading(m_ui->fileNameLabel);
|
2018-03-20 21:41:42 +01:00
|
|
|
|
2016-04-21 23:55:22 +02:00
|
|
|
// setup (web) view
|
|
|
|
initInfoView();
|
2018-03-20 21:41:42 +01:00
|
|
|
|
2016-02-29 23:59:46 +01:00
|
|
|
// setup file watcher
|
|
|
|
m_fileWatcher = new QFileSystemWatcher(this);
|
|
|
|
m_fileChangedOnDisk = false;
|
2018-03-20 21:41:42 +01:00
|
|
|
|
2016-02-29 23:59:46 +01:00
|
|
|
// setup command link button icons
|
|
|
|
m_ui->saveButton->setIcon(style()->standardIcon(QStyle::SP_DialogSaveButton, nullptr, m_ui->saveButton));
|
|
|
|
m_ui->deleteTagsButton->setIcon(style()->standardIcon(QStyle::SP_DialogResetButton, nullptr, m_ui->deleteTagsButton));
|
|
|
|
m_ui->closeButton->setIcon(style()->standardIcon(QStyle::SP_DialogDiscardButton, nullptr, m_ui->closeButton));
|
|
|
|
// setup m_keepPreviousValuesMenu
|
|
|
|
m_keepPreviousValuesMenu = new QMenu(this);
|
|
|
|
QActionGroup *group = new QActionGroup(this);
|
|
|
|
group->addAction(m_ui->actionKeep_previous_values_never);
|
|
|
|
group->addAction(m_ui->actionKeep_previous_values_within_same_dir);
|
|
|
|
group->addAction(m_ui->actionKeep_previous_values_always);
|
|
|
|
connect(group, &QActionGroup::triggered, this, &TagEditorWidget::handleKeepPreviousValuesActionTriggered);
|
|
|
|
m_keepPreviousValuesMenu->addActions(group->actions());
|
|
|
|
m_ui->keepPreviousValuesPushButton->setMenu(m_keepPreviousValuesMenu);
|
2018-03-20 21:41:42 +01:00
|
|
|
|
2016-02-29 23:59:46 +01:00
|
|
|
// setup m_tagOptionsMenu, m_addTagMenu, m_removeTagMenu, m_changeTargetMenu
|
|
|
|
m_tagOptionsMenu = new QMenu(this);
|
|
|
|
m_tagOptionsMenu->addAction(m_ui->actionManage_tags_automatically_when_loading_file);
|
2018-03-07 01:18:01 +01:00
|
|
|
connect(m_ui->actionManage_tags_automatically_when_loading_file, &QAction::triggered,
|
|
|
|
[](bool checked) { Settings::values().tagPocessing.autoTagManagement = checked; });
|
2016-02-29 23:59:46 +01:00
|
|
|
m_tagOptionsMenu->addSeparator();
|
|
|
|
m_addTagMenu = new QMenu(tr("Add tag"), m_tagOptionsMenu);
|
|
|
|
m_addTagMenu->setEnabled(false);
|
|
|
|
m_addTagMenu->setIcon(QIcon::fromTheme(QStringLiteral("tag-add")));
|
|
|
|
m_tagOptionsMenu->addMenu(m_addTagMenu);
|
|
|
|
m_removeTagMenu = new QMenu(tr("Remove tag"), m_tagOptionsMenu);
|
|
|
|
m_removeTagMenu->setEnabled(false);
|
|
|
|
m_removeTagMenu->setIcon(QIcon::fromTheme(QStringLiteral("tag-delete")));
|
|
|
|
m_tagOptionsMenu->addMenu(m_removeTagMenu);
|
|
|
|
m_changeTargetMenu = new QMenu(tr("Change target"), m_tagOptionsMenu);
|
|
|
|
m_changeTargetMenu->setEnabled(false);
|
|
|
|
m_changeTargetMenu->setIcon(QIcon::fromTheme(QStringLiteral("tag-properties")));
|
|
|
|
m_tagOptionsMenu->addMenu(m_changeTargetMenu);
|
|
|
|
m_ui->tagOptionsPushButton->setMenu(m_tagOptionsMenu);
|
2018-03-20 21:41:42 +01:00
|
|
|
|
2016-02-29 23:59:46 +01:00
|
|
|
// other widgets
|
|
|
|
updateFileStatusStatus();
|
|
|
|
m_ui->abortButton->setVisible(false);
|
2017-07-30 20:57:55 +02:00
|
|
|
m_ui->parsingNotificationWidget->setContext(tr("Parsing notifications"));
|
|
|
|
m_ui->makingNotificationWidget->setContext(tr("Applying notifications"));
|
2016-02-29 23:59:46 +01:00
|
|
|
// connect signals and slots, install event filter
|
|
|
|
// buttons: save, delete, next, close
|
|
|
|
connect(m_ui->saveButton, &QPushButton::clicked, this, &TagEditorWidget::applyEntriesAndSaveChangings);
|
|
|
|
connect(m_ui->deleteTagsButton, &QPushButton::clicked, this, &TagEditorWidget::deleteAllTagsAndSave);
|
|
|
|
connect(m_ui->nextButton, &QPushButton::clicked, this, &TagEditorWidget::saveAndShowNextFile);
|
|
|
|
connect(m_ui->closeButton, &QPushButton::clicked, this, &TagEditorWidget::closeFile);
|
2022-07-29 23:51:32 +02:00
|
|
|
connect(m_ui->renamePushButton, &QPushButton::clicked, this, &TagEditorWidget::renameFile);
|
2018-03-20 21:41:42 +01:00
|
|
|
|
2016-02-29 23:59:46 +01:00
|
|
|
// misc
|
2018-03-07 01:18:01 +01:00
|
|
|
connect(m_ui->abortButton, &QPushButton::clicked, [this] {
|
|
|
|
m_abortClicked = true;
|
2019-12-30 23:54:04 +01:00
|
|
|
m_ui->makingNotificationWidget->setText(tr("Cancelling ..."));
|
2018-03-07 01:18:01 +01:00
|
|
|
m_ui->abortButton->setEnabled(false);
|
|
|
|
});
|
|
|
|
connect(m_ui->tagSelectionComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), m_ui->stackedWidget,
|
|
|
|
&QStackedWidget::setCurrentIndex);
|
2016-02-29 23:59:46 +01:00
|
|
|
connect(m_fileWatcher, &QFileSystemWatcher::fileChanged, this, &TagEditorWidget::fileChangedOnDisk);
|
2018-03-20 21:41:42 +01:00
|
|
|
m_fileInfo.setWritingApplication(APP_NAME " v" APP_VERSION);
|
|
|
|
|
2016-02-29 23:59:46 +01:00
|
|
|
// apply settings
|
|
|
|
applySettingsFromDialog();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Destroys the tag editor widget.
|
|
|
|
*/
|
|
|
|
TagEditorWidget::~TagEditorWidget()
|
2018-03-07 01:18:01 +01:00
|
|
|
{
|
2018-05-26 22:41:59 +02:00
|
|
|
if (isFileOperationOngoing()) {
|
|
|
|
cout << Phrases::Warning << "Waiting for the ongoing file operation to finish ..." << Phrases::EndFlush;
|
|
|
|
m_ongoingFileOperation.waitForFinished();
|
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
}
|
2016-02-29 23:59:46 +01:00
|
|
|
|
2017-09-20 19:37:49 +02:00
|
|
|
/*!
|
|
|
|
* \brief Returns the HTML source of the info website.
|
|
|
|
* \remarks In contrast to fileInfoHtml(), this method will generate file info if not available yet.
|
|
|
|
*/
|
|
|
|
const QByteArray &TagEditorWidget::generateFileInfoHtml()
|
|
|
|
{
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_fileInfoHtml.isEmpty()) {
|
2018-03-06 02:04:35 +01:00
|
|
|
m_fileInfoHtml = HtmlInfo::generateInfo(m_fileInfo, m_diag, m_diagReparsing);
|
2017-09-20 19:37:49 +02:00
|
|
|
}
|
|
|
|
return m_fileInfoHtml;
|
|
|
|
}
|
|
|
|
|
2016-02-29 23:59:46 +01:00
|
|
|
/*!
|
|
|
|
* \brief Returns whether the file name is visible at the top of the widget.
|
|
|
|
*/
|
|
|
|
bool TagEditorWidget::isFileNameVisible() const
|
|
|
|
{
|
|
|
|
return m_ui->fileNameLabel->isVisible();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Sets whether the file name is visible at the top of the widget.
|
|
|
|
*/
|
|
|
|
void TagEditorWidget::setFileNameVisible(bool visible)
|
|
|
|
{
|
|
|
|
m_ui->fileNameLabel->setVisible(visible);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns whether the buttons default save, next, delete and close buttons are visible.
|
|
|
|
*/
|
|
|
|
bool TagEditorWidget::areButtonsVisible() const
|
|
|
|
{
|
|
|
|
return m_ui->buttonsWidget->isVisible();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Sets whether the buttons default save, next, delete and close buttons are visible.
|
|
|
|
*/
|
|
|
|
void TagEditorWidget::setButtonVisible(bool visible)
|
|
|
|
{
|
|
|
|
m_ui->buttonsWidget->setVisible(visible);
|
|
|
|
m_ui->line->setVisible(visible);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief
|
|
|
|
* - Saves the applications settings relating the state of the main window.
|
|
|
|
* - Updates the info webview when the palette changed.
|
|
|
|
*/
|
|
|
|
bool TagEditorWidget::event(QEvent *event)
|
|
|
|
{
|
2018-03-07 01:18:01 +01:00
|
|
|
switch (event->type()) {
|
2016-02-29 23:59:46 +01:00
|
|
|
case QEvent::PaletteChange:
|
2016-04-21 23:55:22 +02:00
|
|
|
updateInfoView();
|
2016-02-29 23:59:46 +01:00
|
|
|
break;
|
|
|
|
case QEvent::DragEnter:
|
|
|
|
case QEvent::Drop: {
|
|
|
|
auto *dropEvent = static_cast<QDropEvent *>(event);
|
2018-03-07 01:18:01 +01:00
|
|
|
for (const auto &url : dropEvent->mimeData()->urls()) {
|
|
|
|
if (url.scheme() == QLatin1String("file")) {
|
2016-02-29 23:59:46 +01:00
|
|
|
event->accept();
|
2018-03-07 01:18:01 +01:00
|
|
|
if (event->type() == QEvent::Drop) {
|
2016-02-29 23:59:46 +01:00
|
|
|
#ifdef Q_OS_WIN32
|
|
|
|
// remove leading slash
|
|
|
|
QString path = url.path();
|
|
|
|
int index = 0;
|
2018-03-07 01:18:01 +01:00
|
|
|
for (const auto &c : path) {
|
|
|
|
if (c == QChar('/')) {
|
2016-02-29 23:59:46 +01:00
|
|
|
++index;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
if (index) {
|
2016-02-29 23:59:46 +01:00
|
|
|
path = path.mid(index);
|
|
|
|
}
|
|
|
|
startParsing(path, true);
|
|
|
|
#else
|
2016-08-07 19:59:33 +02:00
|
|
|
emit currentPathChanged(url.path());
|
2016-02-29 23:59:46 +01:00
|
|
|
startParsing(url.path(), true);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2019-06-01 12:53:44 +02:00
|
|
|
break;
|
2018-03-07 01:18:01 +01:00
|
|
|
}
|
|
|
|
default:;
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
|
|
|
return QWidget::event(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Updates the line edits for the document title(s).
|
|
|
|
*/
|
|
|
|
void TagEditorWidget::updateDocumentTitleEdits()
|
|
|
|
{
|
|
|
|
// get container, segment count and present titles
|
2019-07-20 17:59:18 +02:00
|
|
|
const auto *const container = m_fileInfo.container();
|
|
|
|
const auto segmentCount = [&] {
|
|
|
|
constexpr auto segmentLimit = 10;
|
|
|
|
const auto count = container ? container->segmentCount() : static_cast<size_t>(0);
|
|
|
|
if (count <= segmentLimit) {
|
|
|
|
return static_cast<int>(count);
|
|
|
|
}
|
|
|
|
m_ui->parsingNotificationWidget->appendLine(tr("The file contains more segments than the GUI can handle."));
|
|
|
|
return segmentLimit;
|
|
|
|
}();
|
2016-02-29 23:59:46 +01:00
|
|
|
const vector<string> &titles = container ? container->titles() : vector<string>();
|
|
|
|
|
|
|
|
// get layout
|
2020-03-19 16:24:32 +01:00
|
|
|
QLayout *const docTitleLayout = m_ui->docTitleWidget->layout();
|
|
|
|
int lineEditCount = docTitleLayout->count();
|
2016-02-29 23:59:46 +01:00
|
|
|
|
|
|
|
// update existing line edits, remove unneeded line edits
|
|
|
|
int i = 0;
|
2018-03-07 01:18:01 +01:00
|
|
|
for (; i < lineEditCount; ++i) {
|
|
|
|
if (i < segmentCount) {
|
2016-02-29 23:59:46 +01:00
|
|
|
// update existing line edit
|
2020-03-19 16:24:32 +01:00
|
|
|
static_cast<ClearLineEdit *>(docTitleLayout->itemAt(i)->widget())
|
2019-07-20 17:59:18 +02:00
|
|
|
->setText(static_cast<size_t>(i) < titles.size() ? QString::fromUtf8(titles[static_cast<size_t>(i)].data()) : QString());
|
2016-02-29 23:59:46 +01:00
|
|
|
} else {
|
|
|
|
// remove unneeded line edit
|
|
|
|
docTitleLayout->removeItem(docTitleLayout->itemAt(i + 1));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// add missing line edits
|
2018-03-07 01:18:01 +01:00
|
|
|
while (i < segmentCount) {
|
2019-06-10 22:49:46 +02:00
|
|
|
auto *const lineEdit = new ClearLineEdit;
|
2018-03-07 01:18:01 +01:00
|
|
|
if (static_cast<size_t>(i) < titles.size()) {
|
2019-07-20 17:59:18 +02:00
|
|
|
lineEdit->setText(QString::fromUtf8(titles[static_cast<size_t>(i)].data()));
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
|
|
|
lineEdit->setPlaceholderText(tr("Segment %1").arg(++i));
|
|
|
|
docTitleLayout->addWidget(lineEdit);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Update the tag edits and the tag selection to show the specified \a tags.
|
|
|
|
* \param tags Specifies the tags to be shown.
|
|
|
|
* \param updateUi Specifies whether the UI of the tag edits should be updated.
|
|
|
|
* \remarks The tag selection combo box should be updated after calling this method to
|
|
|
|
* ensure the updated edits can be selected properly.
|
|
|
|
*/
|
|
|
|
void TagEditorWidget::updateTagEditsAndAttachmentEdits(bool updateUi, PreviousValueHandling previousValueHandling)
|
|
|
|
{
|
|
|
|
// determine to previous value handling according to the settings if auto is specified
|
2018-03-07 01:18:01 +01:00
|
|
|
switch (previousValueHandling) {
|
2016-02-29 23:59:46 +01:00
|
|
|
case PreviousValueHandling::Auto:
|
2018-03-07 01:18:01 +01:00
|
|
|
switch (Settings::values().editor.adoptFields) {
|
2016-02-29 23:59:46 +01:00
|
|
|
case Settings::AdoptFields::WithinDirectory:
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_lastDir != m_currentDir) {
|
2016-02-29 23:59:46 +01:00
|
|
|
previousValueHandling = PreviousValueHandling::Clear;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Settings::AdoptFields::Never:
|
|
|
|
previousValueHandling = PreviousValueHandling::Clear;
|
|
|
|
break;
|
2018-03-07 01:18:01 +01:00
|
|
|
default:;
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
|
|
|
break;
|
2018-03-07 01:18:01 +01:00
|
|
|
default:;
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
2019-06-01 12:53:44 +02:00
|
|
|
|
2016-02-29 23:59:46 +01:00
|
|
|
// define helper function to fetch next edit
|
|
|
|
TagEdit *edit; // holds current edit
|
|
|
|
int widgetIndex = 0; // holds index of current edit in the stacked widget
|
|
|
|
auto fetchNextEdit = [this, &edit, &widgetIndex, &previousValueHandling] {
|
|
|
|
// reuse existing edit (assigned in if-condition!) or ...
|
2018-03-07 01:18:01 +01:00
|
|
|
if (!((widgetIndex < m_ui->stackedWidget->count()) && (edit = qobject_cast<TagEdit *>(m_ui->stackedWidget->widget(widgetIndex))))) {
|
2016-02-29 23:59:46 +01:00
|
|
|
// ... create and add a new edit
|
|
|
|
edit = new TagEdit;
|
|
|
|
connect(m_ui->clearEntriesPushButton, &QPushButton::clicked, edit, &TagEdit::clear);
|
|
|
|
connect(m_ui->restoreEntriesPushButton, &QPushButton::clicked, edit, &TagEdit::restore);
|
|
|
|
connect(edit, &TagEdit::returnPressed, this, &TagEditorWidget::handleReturnPressed);
|
|
|
|
m_ui->stackedWidget->insertWidget(widgetIndex, edit);
|
|
|
|
}
|
|
|
|
// apply settings
|
|
|
|
edit->setPreviousValueHandling(previousValueHandling);
|
|
|
|
};
|
2019-06-01 12:53:44 +02:00
|
|
|
|
2016-02-29 23:59:46 +01:00
|
|
|
// add/update TagEdit widgets
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_tags.size()) {
|
2016-02-29 23:59:46 +01:00
|
|
|
// create a lists of the targets and tags
|
2023-07-23 22:17:47 +02:00
|
|
|
auto targets = QList<TagTarget>();
|
|
|
|
auto tagsByTarget = QList<QList<Tag *>>();
|
|
|
|
for (auto *const tag : m_tags) {
|
|
|
|
const auto &target = tag->target();
|
|
|
|
auto index = targets.indexOf(target);
|
2018-03-07 01:18:01 +01:00
|
|
|
if (index < 0) {
|
2016-02-29 23:59:46 +01:00
|
|
|
targets << target;
|
2023-07-25 23:34:39 +02:00
|
|
|
tagsByTarget << QList<Tag *>({ tag });
|
2016-02-29 23:59:46 +01:00
|
|
|
} else {
|
|
|
|
tagsByTarget[index] << tag;
|
|
|
|
}
|
|
|
|
}
|
2021-07-03 19:38:27 +02:00
|
|
|
// create a single editor per target or separate editors for each tag depending on the settings
|
2019-06-01 12:53:44 +02:00
|
|
|
bool hasAutoCorrectionBeenApplied = false;
|
2018-03-07 01:18:01 +01:00
|
|
|
switch (Settings::values().editor.multipleTagHandling) {
|
2016-02-29 23:59:46 +01:00
|
|
|
case Settings::MultipleTagHandling::SingleEditorPerTarget:
|
|
|
|
// iterate through all targets in both cases
|
2023-07-23 22:17:47 +02:00
|
|
|
for (auto targetIndex = QList<TagTarget>::size_type(), targetCount = targets.size(); targetIndex < targetCount; ++targetIndex) {
|
2016-02-29 23:59:46 +01:00
|
|
|
fetchNextEdit();
|
|
|
|
edit->setTags(tagsByTarget.at(targetIndex), updateUi); // set all tags with the same target to a single edit
|
2019-06-01 12:53:44 +02:00
|
|
|
if (!hasAutoCorrectionBeenApplied) {
|
|
|
|
hasAutoCorrectionBeenApplied = edit->hasAutoCorrectionBeenApplied();
|
|
|
|
}
|
2016-02-29 23:59:46 +01:00
|
|
|
++widgetIndex;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Settings::MultipleTagHandling::SeparateEditors:
|
|
|
|
// iterate through all targets in both cases
|
2023-07-23 22:17:47 +02:00
|
|
|
for (auto targetIndex = QList<TagTarget>::size_type(), targetCount = targets.size(); targetIndex < targetCount; ++targetIndex) {
|
2018-03-07 01:18:01 +01:00
|
|
|
for (Tag *tag : tagsByTarget.at(targetIndex)) {
|
2016-02-29 23:59:46 +01:00
|
|
|
fetchNextEdit();
|
|
|
|
edit->setTag(tag, updateUi); // use a separate edit for each tag
|
2019-06-01 12:53:44 +02:00
|
|
|
if (!hasAutoCorrectionBeenApplied) {
|
|
|
|
hasAutoCorrectionBeenApplied = edit->hasAutoCorrectionBeenApplied();
|
|
|
|
}
|
2016-02-29 23:59:46 +01:00
|
|
|
++widgetIndex;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2019-06-01 12:53:44 +02:00
|
|
|
if (hasAutoCorrectionBeenApplied) {
|
|
|
|
addParsingNotificationLine(tr("Some values have been changed by the auto-correction."));
|
|
|
|
}
|
2016-02-29 23:59:46 +01:00
|
|
|
} else {
|
2021-07-03 19:38:27 +02:00
|
|
|
// there are no tags -> leave one edit existed but ensure no tags are assigned
|
2016-02-29 23:59:46 +01:00
|
|
|
fetchNextEdit();
|
|
|
|
edit->setTag(nullptr, false);
|
|
|
|
++widgetIndex;
|
|
|
|
}
|
2019-06-01 12:53:44 +02:00
|
|
|
|
2016-02-29 23:59:46 +01:00
|
|
|
// add/update AttachmentsEdit widget
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_fileInfo.areAttachmentsSupported()) {
|
2021-03-20 21:59:49 +01:00
|
|
|
AttachmentsEdit *attachmentsEdit;
|
2016-02-29 23:59:46 +01:00
|
|
|
// reuse existing edit (assigned in if-condition!) or ...
|
2021-03-20 21:59:49 +01:00
|
|
|
if ((widgetIndex < m_ui->stackedWidget->count())
|
|
|
|
&& (attachmentsEdit = qobject_cast<AttachmentsEdit *>(m_ui->stackedWidget->widget(widgetIndex)))) {
|
|
|
|
attachmentsEdit->setFileInfo(&m_fileInfo, true);
|
2016-02-29 23:59:46 +01:00
|
|
|
} else {
|
|
|
|
// ... create and add a new edit
|
2021-03-20 21:59:49 +01:00
|
|
|
attachmentsEdit = new AttachmentsEdit(&m_fileInfo, this);
|
|
|
|
connect(m_ui->clearEntriesPushButton, &QPushButton::clicked, attachmentsEdit, &AttachmentsEdit::clear);
|
|
|
|
connect(m_ui->restoreEntriesPushButton, &QPushButton::clicked, attachmentsEdit, &AttachmentsEdit::restore);
|
2016-02-29 23:59:46 +01:00
|
|
|
//connect(edit, &AttachmentsEdit::returnPressed, this, &TagEditorWidget::handleReturnPressed);
|
2021-03-20 21:59:49 +01:00
|
|
|
m_ui->stackedWidget->insertWidget(widgetIndex, attachmentsEdit);
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
|
|
|
++widgetIndex;
|
|
|
|
}
|
2019-06-01 12:53:44 +02:00
|
|
|
|
2016-02-29 23:59:46 +01:00
|
|
|
// remove surplus edits
|
2018-03-07 01:18:01 +01:00
|
|
|
while (widgetIndex < m_ui->stackedWidget->count()) {
|
2016-02-29 23:59:46 +01:00
|
|
|
QWidget *toRemove = m_ui->stackedWidget->widget(widgetIndex);
|
|
|
|
m_ui->stackedWidget->removeWidget(toRemove);
|
|
|
|
delete toRemove;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Updates the items and the visibility of the tag selection combo box.
|
|
|
|
* \remarks Tag edits should have been updated before since this method uses the
|
|
|
|
* tag edits to generate the labels.
|
|
|
|
*/
|
|
|
|
void TagEditorWidget::updateTagSelectionComboBox()
|
|
|
|
{
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_fileInfo.isOpen()) {
|
2016-02-29 23:59:46 +01:00
|
|
|
// memorize the index of the previously selected edit
|
|
|
|
int previouslySelectedEditIndex = m_ui->tagSelectionComboBox->currentIndex();
|
|
|
|
// clear old entries and create new labels
|
|
|
|
m_ui->tagSelectionComboBox->clear();
|
|
|
|
bool haveTargetInfo = false;
|
2018-03-07 01:18:01 +01:00
|
|
|
for (int index = 0, count = m_ui->stackedWidget->count(); index < count; ++index) {
|
2016-02-29 23:59:46 +01:00
|
|
|
QString label;
|
2018-03-07 01:18:01 +01:00
|
|
|
if (TagEdit *edit = qobject_cast<TagEdit *>(m_ui->stackedWidget->widget(index))) {
|
|
|
|
if (!edit->tags().isEmpty()) {
|
2016-02-29 23:59:46 +01:00
|
|
|
label = edit->generateLabel();
|
|
|
|
haveTargetInfo |= !edit->tags().at(0)->target().isEmpty();
|
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
} else if (qobject_cast<AttachmentsEdit *>(m_ui->stackedWidget->widget(index))) {
|
2016-02-29 23:59:46 +01:00
|
|
|
static const QString attachmentsLabel = tr("Attachments");
|
|
|
|
label = attachmentsLabel;
|
|
|
|
}
|
|
|
|
m_ui->tagSelectionComboBox->addItem(label);
|
|
|
|
}
|
|
|
|
// set visibility
|
2018-03-07 01:18:01 +01:00
|
|
|
m_ui->tagSelectionComboBox->setHidden(
|
|
|
|
Settings::values().editor.hideTagSelectionComboBox && m_ui->tagSelectionComboBox->count() <= 1 && !haveTargetInfo);
|
2016-02-29 23:59:46 +01:00
|
|
|
// restore selected index
|
2018-03-07 01:18:01 +01:00
|
|
|
if (previouslySelectedEditIndex >= 0 && previouslySelectedEditIndex < m_ui->tagSelectionComboBox->count()) {
|
2016-02-29 23:59:46 +01:00
|
|
|
m_ui->tagSelectionComboBox->setCurrentIndex(previouslySelectedEditIndex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Updates the status of the relevant widgets (enabled/disabled, visible/hidden) according to the
|
|
|
|
* current "file status" (opened/closed, has tags/no tags).
|
|
|
|
*/
|
|
|
|
void TagEditorWidget::updateFileStatusStatus()
|
|
|
|
{
|
2020-03-19 16:24:32 +01:00
|
|
|
const bool opened = m_fileInfo.isOpen();
|
|
|
|
const bool hasTag = opened && m_tags.size();
|
2016-02-29 23:59:46 +01:00
|
|
|
// notification widgets
|
|
|
|
m_ui->parsingNotificationWidget->setVisible(opened);
|
|
|
|
m_ui->makingNotificationWidget->setVisible(opened && (m_makingResultsAvailable));
|
|
|
|
// document title widget
|
2020-03-19 16:24:32 +01:00
|
|
|
const bool showDocumentTitle = opened && m_fileInfo.container() && m_fileInfo.container()->supportsTitle();
|
|
|
|
m_ui->docTitleLabel->setVisible(showDocumentTitle);
|
|
|
|
m_ui->docTitleWidget->setVisible(showDocumentTitle);
|
2022-07-29 23:51:32 +02:00
|
|
|
// buttons and actions to save, delete, close and rename
|
2016-02-29 23:59:46 +01:00
|
|
|
m_ui->saveButton->setEnabled(opened);
|
|
|
|
m_ui->nextButton->setEnabled(opened);
|
|
|
|
m_ui->deleteTagsButton->setEnabled(hasTag);
|
|
|
|
m_ui->buttonsWidget->setEnabled(opened);
|
2022-07-29 23:51:32 +02:00
|
|
|
m_ui->renamePushButton->setEnabled(opened);
|
2016-02-29 23:59:46 +01:00
|
|
|
// clear and restore buttons
|
|
|
|
m_ui->clearEntriesPushButton->setEnabled(hasTag);
|
|
|
|
m_ui->restoreEntriesPushButton->setEnabled(hasTag);
|
|
|
|
m_ui->clearEntriesPushButton->setEnabled(hasTag);
|
|
|
|
m_ui->restoreEntriesPushButton->setEnabled(hasTag);
|
|
|
|
// tag management button
|
|
|
|
m_ui->tagOptionsPushButton->setEnabled(opened);
|
|
|
|
// stacked widget containering edits and selection
|
|
|
|
m_ui->tagSelectionComboBox->setEnabled(hasTag);
|
|
|
|
// visibility of m_ui->tagSelectionComboBox is set within updateTagEdits
|
|
|
|
m_ui->stackedWidget->setEnabled(hasTag);
|
|
|
|
// webview
|
2016-03-28 20:28:59 +02:00
|
|
|
#ifndef TAGEDITOR_NO_WEBVIEW
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_infoWebView) {
|
2020-04-26 19:45:54 +02:00
|
|
|
m_infoWebView->setVisible(opened);
|
2016-04-21 23:55:22 +02:00
|
|
|
}
|
2016-03-28 20:28:59 +02:00
|
|
|
#endif
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_infoTreeView) {
|
2020-04-26 19:45:54 +02:00
|
|
|
m_infoTreeView->setVisible(opened);
|
2016-04-24 21:07:36 +02:00
|
|
|
}
|
2016-02-29 23:59:46 +01:00
|
|
|
// inform the main window about the file status change as well
|
2016-08-07 19:59:33 +02:00
|
|
|
emit fileStatusChanged(opened, hasTag);
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Updates the "tag management menu".
|
|
|
|
*/
|
|
|
|
void TagEditorWidget::updateTagManagementMenu()
|
|
|
|
{
|
|
|
|
m_addTagMenu->clear();
|
|
|
|
m_removeTagMenu->clear();
|
|
|
|
m_changeTargetMenu->clear();
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_fileInfo.isOpen()) {
|
2016-02-29 23:59:46 +01:00
|
|
|
// add "Add tag" actions
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_fileInfo.areTagsSupported() && m_fileInfo.container()) {
|
2016-03-14 22:03:50 +01:00
|
|
|
// there is a container object which is able to create tags
|
2016-02-29 23:59:46 +01:00
|
|
|
QString label;
|
2018-03-07 01:18:01 +01:00
|
|
|
switch (m_fileInfo.containerFormat()) {
|
2016-05-26 02:15:41 +02:00
|
|
|
case ContainerFormat::Matroska:
|
|
|
|
case ContainerFormat::Webm:
|
2016-02-29 23:59:46 +01:00
|
|
|
// tag format supports targets (Matroska tags are currently the only tag format supporting targets.)
|
|
|
|
label = tr("Matroska tag");
|
2018-03-07 01:18:01 +01:00
|
|
|
connect(m_addTagMenu->addAction(label), &QAction::triggered,
|
|
|
|
std::bind(&TagEditorWidget::addTag, this, [this](MediaFileInfo &file) -> TagParser::Tag * {
|
|
|
|
if (file.container()) {
|
|
|
|
EnterTargetDialog targetDlg(this);
|
|
|
|
targetDlg.setTarget(TagTarget(50), &this->m_fileInfo);
|
|
|
|
if (targetDlg.exec() == QDialog::Accepted) {
|
|
|
|
return file.container()->createTag(targetDlg.target());
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}));
|
2016-05-26 02:15:41 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2016-02-29 23:59:46 +01:00
|
|
|
// tag format does not support targets
|
2018-03-07 01:18:01 +01:00
|
|
|
if (!m_fileInfo.container()->tagCount()) {
|
|
|
|
switch (m_fileInfo.containerFormat()) {
|
2016-02-29 23:59:46 +01:00
|
|
|
case ContainerFormat::Mp4:
|
|
|
|
label = tr("MP4/iTunes tag");
|
|
|
|
break;
|
|
|
|
case ContainerFormat::Ogg:
|
2016-05-26 02:15:41 +02:00
|
|
|
label = tr("Vorbis comment");
|
2016-02-29 23:59:46 +01:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
label = tr("Tag");
|
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
connect(m_addTagMenu->addAction(label), &QAction::triggered, std::bind(&TagEditorWidget::addTag, this, [](MediaFileInfo &file) {
|
|
|
|
return file.container() ? file.container()->createTag() : nullptr;
|
|
|
|
}));
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2016-05-26 02:15:41 +02:00
|
|
|
// there is no container object which is able to create tags
|
2018-03-07 01:18:01 +01:00
|
|
|
switch (m_fileInfo.containerFormat()) {
|
2016-05-26 02:15:41 +02:00
|
|
|
case ContainerFormat::Flac:
|
2018-03-07 01:18:01 +01:00
|
|
|
if (!m_fileInfo.vorbisComment()) {
|
|
|
|
connect(m_addTagMenu->addAction(tr("Vorbis comment")), &QAction::triggered,
|
|
|
|
std::bind(&TagEditorWidget::addTag, this, [](MediaFileInfo &file) { return file.createVorbisComment(); }));
|
2016-05-26 02:15:41 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
// creation of ID3 tags is always possible
|
2018-03-07 01:18:01 +01:00
|
|
|
if (!m_fileInfo.hasId3v1Tag()) {
|
|
|
|
connect(m_addTagMenu->addAction(tr("ID3v1 tag")), &QAction::triggered,
|
|
|
|
std::bind(&TagEditorWidget::addTag, this, [](MediaFileInfo &file) { return file.createId3v1Tag(); }));
|
2016-05-26 02:15:41 +02:00
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
if (!m_fileInfo.hasId3v2Tag()) {
|
|
|
|
connect(m_addTagMenu->addAction(tr("ID3v2 tag")), &QAction::triggered,
|
|
|
|
std::bind(&TagEditorWidget::addTag, this, [](MediaFileInfo &file) { return file.createId3v2Tag(); }));
|
2016-05-26 02:15:41 +02:00
|
|
|
}
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// add "Remove tag" and "Change target" actions
|
2018-03-07 01:18:01 +01:00
|
|
|
for (Tag *tag : m_tags) {
|
2016-05-14 23:23:47 +02:00
|
|
|
// don't propose removal for Vorbis comments from Voribs or FLAC streams (removing from Opus streams should be ok)
|
2022-05-03 23:54:46 +02:00
|
|
|
const auto tagType = tag->type();
|
|
|
|
if (tagType == TagType::OggVorbisComment) {
|
2018-03-07 01:18:01 +01:00
|
|
|
switch (static_cast<OggVorbisComment *>(tag)->oggParams().streamFormat) {
|
2016-05-14 23:23:47 +02:00
|
|
|
case GeneralMediaFormat::Vorbis:
|
|
|
|
case GeneralMediaFormat::Flac:
|
|
|
|
continue;
|
2018-03-07 01:18:01 +01:00
|
|
|
default:;
|
2016-03-22 23:01:50 +01:00
|
|
|
}
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
2016-05-14 23:23:47 +02:00
|
|
|
|
2018-03-07 01:18:01 +01:00
|
|
|
connect(m_removeTagMenu->addAction(QString::fromUtf8(tag->toString().c_str())), &QAction::triggered,
|
|
|
|
std::bind(&TagEditorWidget::removeTag, this, tag));
|
2022-05-03 23:54:46 +02:00
|
|
|
if (tagType != TagType::OggVorbisComment && tag->supportsTarget()) {
|
2018-03-07 01:18:01 +01:00
|
|
|
connect(m_changeTargetMenu->addAction(QString::fromUtf8(tag->toString().c_str())), &QAction::triggered,
|
|
|
|
std::bind(&TagEditorWidget::changeTarget, this, tag));
|
2016-05-14 23:23:47 +02:00
|
|
|
}
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
m_addTagMenu->setEnabled(!m_addTagMenu->actions().empty());
|
|
|
|
m_removeTagMenu->setEnabled(!m_removeTagMenu->actions().empty());
|
|
|
|
m_changeTargetMenu->setEnabled(!m_changeTargetMenu->actions().empty());
|
|
|
|
}
|
|
|
|
|
2018-03-14 19:35:52 +01:00
|
|
|
void TagEditorWidget::updateKeepPreviousValuesButton()
|
|
|
|
{
|
|
|
|
switch (Settings::values().editor.adoptFields) {
|
|
|
|
case Settings::AdoptFields::Never:
|
|
|
|
m_ui->keepPreviousValuesPushButton->setText(tr("Clear previous values"));
|
|
|
|
break;
|
|
|
|
case Settings::AdoptFields::WithinDirectory:
|
|
|
|
m_ui->keepPreviousValuesPushButton->setText(tr("Keep previous values in same dir"));
|
|
|
|
break;
|
|
|
|
case Settings::AdoptFields::Always:
|
|
|
|
m_ui->keepPreviousValuesPushButton->setText(tr("Keep previous values"));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-29 23:59:46 +01:00
|
|
|
/*!
|
2016-05-26 02:15:41 +02:00
|
|
|
* \brief Inserts the title from the filename if no title is available from the tags.
|
2016-12-01 22:23:01 +01:00
|
|
|
* \remarks Does nothing if there are no tags assigned or if this feature is not enabled.
|
2016-02-29 23:59:46 +01:00
|
|
|
*/
|
|
|
|
void TagEditorWidget::insertTitleFromFilename()
|
|
|
|
{
|
2019-06-01 12:53:44 +02:00
|
|
|
if (m_tags.empty() || !Settings::values().editor.autoCompletition.insertTitleFromFilename) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
QString title;
|
|
|
|
int trackNum;
|
|
|
|
parseFileName(m_fileName, title, trackNum);
|
|
|
|
const auto titleValue = qstringToTagValue(title, TagTextEncoding::Utf16LittleEndian);
|
|
|
|
auto updated = false;
|
|
|
|
foreachTagEdit([&titleValue, &updated](TagEdit *const edit) {
|
|
|
|
for (const auto *const tag : edit->tags()) {
|
|
|
|
if (tag->supportsTarget() && tag->isTargetingLevel(TagTargetLevel::Part)) {
|
|
|
|
return;
|
2016-05-26 02:15:41 +02:00
|
|
|
}
|
2019-06-01 12:53:44 +02:00
|
|
|
}
|
|
|
|
if (edit->setValue(KnownField::Title, titleValue, PreviousValueHandling::Keep)) {
|
|
|
|
updated = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (updated) {
|
|
|
|
addParsingNotificationLine(tr("Inserted title from filename."));
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-21 23:55:22 +02:00
|
|
|
void TagEditorWidget::initInfoView()
|
|
|
|
{
|
|
|
|
#ifndef TAGEDITOR_NO_WEBVIEW
|
2018-03-07 01:18:01 +01:00
|
|
|
if (!Settings::values().editor.noWebView && !m_infoWebView) {
|
|
|
|
if (m_infoTreeView) {
|
|
|
|
m_infoTreeView->deleteLater();
|
|
|
|
m_infoTreeView = nullptr;
|
2016-04-24 21:07:36 +02:00
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_infoModel) {
|
2016-04-24 21:07:36 +02:00
|
|
|
m_infoModel->deleteLater();
|
|
|
|
m_infoModel = nullptr;
|
|
|
|
}
|
2017-03-14 23:00:26 +01:00
|
|
|
m_infoWebView = new TAGEDITOR_WEB_VIEW(m_ui->tagSplitter);
|
2016-04-21 23:55:22 +02:00
|
|
|
m_infoWebView->setAcceptDrops(false);
|
|
|
|
m_infoWebView->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
|
|
connect(m_infoWebView, &QWidget::customContextMenuRequested, this, &TagEditorWidget::showInfoWebViewContextMenu);
|
|
|
|
m_ui->tagSplitter->addWidget(m_infoWebView);
|
2018-03-07 01:18:01 +01:00
|
|
|
} else if (Settings::values().editor.noWebView && !m_infoTreeView) {
|
|
|
|
if (m_infoWebView) {
|
2016-04-24 21:07:36 +02:00
|
|
|
m_infoWebView->deleteLater();
|
|
|
|
m_infoWebView = nullptr;
|
|
|
|
}
|
|
|
|
#endif
|
2018-03-07 01:18:01 +01:00
|
|
|
if (!m_infoTreeView) {
|
2016-04-24 21:07:36 +02:00
|
|
|
m_infoTreeView = new QTreeView(this);
|
2016-04-24 22:08:41 +02:00
|
|
|
m_infoTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
|
|
m_infoTreeView->setSelectionBehavior(QAbstractItemView::SelectItems);
|
|
|
|
m_infoTreeView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
|
|
|
connect(m_infoTreeView, &QWidget::customContextMenuRequested, this, &TagEditorWidget::showInfoTreeViewContextMenu);
|
2016-04-24 21:07:36 +02:00
|
|
|
m_ui->tagSplitter->addWidget(m_infoTreeView);
|
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
if (!m_infoModel) {
|
2018-03-06 02:04:35 +01:00
|
|
|
m_infoModel = new FileInfoModel(this);
|
|
|
|
m_infoModel->setFileInfo(m_fileInfo, m_diag, m_makingResultsAvailable ? &m_diagReparsing : nullptr);
|
2016-04-24 21:07:36 +02:00
|
|
|
m_infoTreeView->setModel(m_infoModel);
|
2016-04-25 22:47:54 +02:00
|
|
|
m_infoTreeView->setHeaderHidden(true);
|
|
|
|
m_infoTreeView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
2016-04-24 21:07:36 +02:00
|
|
|
}
|
|
|
|
#ifndef TAGEDITOR_NO_WEBVIEW
|
2016-04-21 23:55:22 +02:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2016-02-29 23:59:46 +01:00
|
|
|
/*!
|
2017-09-20 19:37:49 +02:00
|
|
|
* \brief Updates the info web view to show information about the currently opened file.
|
2016-02-29 23:59:46 +01:00
|
|
|
*/
|
2016-04-21 23:55:22 +02:00
|
|
|
void TagEditorWidget::updateInfoView()
|
2016-02-29 23:59:46 +01:00
|
|
|
{
|
2017-09-20 19:37:49 +02:00
|
|
|
// ensure previous file info HTML is cleared in any case
|
|
|
|
m_fileInfoHtml.clear();
|
|
|
|
|
|
|
|
// update webview if present
|
2016-03-28 20:28:59 +02:00
|
|
|
#ifndef TAGEDITOR_NO_WEBVIEW
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_infoWebView) {
|
|
|
|
if (m_fileInfo.isOpen()) {
|
2017-09-20 19:37:49 +02:00
|
|
|
m_infoWebView->setContent(generateFileInfoHtml(), QStringLiteral("application/xhtml+xml"));
|
2016-04-21 23:55:22 +02:00
|
|
|
} else {
|
|
|
|
m_infoWebView->setUrl(QStringLiteral("about:blank"));
|
|
|
|
}
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
2016-03-28 20:28:59 +02:00
|
|
|
#endif
|
2017-09-20 19:37:49 +02:00
|
|
|
|
|
|
|
// update info model if present
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_infoModel) {
|
2018-03-06 02:04:35 +01:00
|
|
|
m_infoModel->setFileInfo(m_fileInfo, m_diag, m_makingResultsAvailable ? &m_diagReparsing : nullptr); // resets the model
|
2016-04-24 21:07:36 +02:00
|
|
|
}
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
|
|
|
|
2019-07-20 18:49:32 +02:00
|
|
|
void TagEditorWidget::showInfoTreeViewContextMenu(const QPoint &position)
|
2016-04-24 22:08:41 +02:00
|
|
|
{
|
|
|
|
QAction copyAction(QIcon::fromTheme(QStringLiteral("edit-copy")), tr("Copy"), nullptr);
|
|
|
|
copyAction.setDisabled(m_infoTreeView->selectionModel()->selectedIndexes().isEmpty());
|
|
|
|
connect(©Action, &QAction::triggered, [this] {
|
|
|
|
const auto selection = m_infoTreeView->selectionModel()->selectedIndexes();
|
2018-03-07 01:18:01 +01:00
|
|
|
if (!selection.isEmpty()) {
|
2016-04-24 22:08:41 +02:00
|
|
|
QStringList text;
|
|
|
|
text.reserve(selection.size());
|
2018-03-07 01:18:01 +01:00
|
|
|
for (const QModelIndex &index : selection) {
|
2016-04-24 22:08:41 +02:00
|
|
|
text << index.data().toString();
|
|
|
|
}
|
2018-04-29 19:02:31 +02:00
|
|
|
QGuiApplication::clipboard()->setText(text.join(QChar(' ')));
|
2016-04-24 22:08:41 +02:00
|
|
|
// TODO: improve copied text
|
|
|
|
}
|
|
|
|
});
|
|
|
|
QAction expandAllAction(QIcon::fromTheme(QStringLiteral("expand-menu-hover")), tr("Expand all"), nullptr);
|
|
|
|
connect(&expandAllAction, &QAction::triggered, m_infoTreeView, &QTreeView::expandAll);
|
|
|
|
QAction collapseAllAction(QIcon::fromTheme(QStringLiteral("collapse-menu-hover")), tr("Collapse all"), nullptr);
|
|
|
|
connect(&collapseAllAction, &QAction::triggered, m_infoTreeView, &QTreeView::collapseAll);
|
|
|
|
QMenu menu;
|
|
|
|
menu.addAction(©Action);
|
|
|
|
menu.addSeparator();
|
|
|
|
menu.addAction(&expandAllAction);
|
|
|
|
menu.addAction(&collapseAllAction);
|
2019-07-20 18:49:32 +02:00
|
|
|
menu.exec(m_infoTreeView->viewport()->mapToGlobal(position));
|
2016-04-24 22:08:41 +02:00
|
|
|
}
|
|
|
|
|
2016-04-21 23:55:22 +02:00
|
|
|
#ifndef TAGEDITOR_NO_WEBVIEW
|
2016-02-29 23:59:46 +01:00
|
|
|
/*!
|
|
|
|
* \brief Shows the context menu for the info web view.
|
|
|
|
*/
|
2019-07-20 18:49:32 +02:00
|
|
|
void TagEditorWidget::showInfoWebViewContextMenu(const QPoint &position)
|
2016-02-29 23:59:46 +01:00
|
|
|
{
|
|
|
|
QAction copyAction(QIcon::fromTheme(QStringLiteral("edit-copy")), tr("Copy"), nullptr);
|
|
|
|
copyAction.setDisabled(m_infoWebView->selectedText().isEmpty());
|
2018-04-29 19:02:31 +02:00
|
|
|
connect(©Action, &QAction::triggered, [this] { QGuiApplication::clipboard()->setText(m_infoWebView->selectedText()); });
|
2017-09-20 19:37:49 +02:00
|
|
|
QAction saveAction(QIcon::fromTheme(QStringLiteral("document-save")), tr("Save ..."), nullptr);
|
|
|
|
saveAction.setDisabled(m_fileInfoHtml.isEmpty());
|
|
|
|
connect(&saveAction, &QAction::triggered, this, &TagEditorWidget::saveFileInfo);
|
|
|
|
QAction openAction(QIcon::fromTheme(QStringLiteral("internet-web-browser")), tr("Open in browser"), nullptr);
|
|
|
|
openAction.setDisabled(m_fileInfoHtml.isEmpty());
|
|
|
|
connect(&openAction, &QAction::triggered, this, &TagEditorWidget::openFileInfoInBrowser);
|
2016-02-29 23:59:46 +01:00
|
|
|
QMenu menu;
|
|
|
|
menu.addAction(©Action);
|
2017-09-20 19:37:49 +02:00
|
|
|
menu.addAction(&saveAction);
|
|
|
|
menu.addAction(&openAction);
|
2019-07-20 18:49:32 +02:00
|
|
|
menu.exec(m_infoWebView->mapToGlobal(position));
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
2016-04-21 23:55:22 +02:00
|
|
|
#endif
|
2016-02-29 23:59:46 +01:00
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Calls the specified \a function for each of the currently present tag edits.
|
|
|
|
*/
|
2018-03-07 01:18:01 +01:00
|
|
|
void TagEditorWidget::foreachTagEdit(const std::function<void(TagEdit *)> &function)
|
2016-02-29 23:59:46 +01:00
|
|
|
{
|
2018-03-07 01:18:01 +01:00
|
|
|
for (int i = 0, count = m_ui->stackedWidget->count(); i < count; ++i) {
|
|
|
|
if (auto *edit = qobject_cast<TagEdit *>(m_ui->stackedWidget->widget(i))) {
|
2016-02-29 23:59:46 +01:00
|
|
|
function(edit);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-03 22:21:15 +01:00
|
|
|
/*!
|
2021-07-03 19:38:27 +02:00
|
|
|
* \brief Returns the active tag edit or nullptr if there is currently no active tag edit.
|
2016-03-03 22:21:15 +01:00
|
|
|
*/
|
|
|
|
TagEdit *TagEditorWidget::activeTagEdit()
|
|
|
|
{
|
|
|
|
return m_fileInfo.isOpen() ? qobject_cast<TagEdit *>(m_ui->stackedWidget->currentWidget()) : nullptr;
|
|
|
|
}
|
|
|
|
|
2016-02-29 23:59:46 +01:00
|
|
|
/*!
|
|
|
|
* \brief Opens and parses a file using another thread.
|
|
|
|
*
|
|
|
|
* Shows its tags and general information using the showFile() method.
|
|
|
|
*
|
|
|
|
* \param path Specifies the \a path of the file.
|
|
|
|
* \param forceRefresh Specifies whether the file should be reparsed if it is already opened.
|
|
|
|
*/
|
|
|
|
bool TagEditorWidget::startParsing(const QString &path, bool forceRefresh)
|
|
|
|
{
|
|
|
|
// check if file is current file
|
2017-08-20 02:01:37 +02:00
|
|
|
const bool sameFile = m_currentPath == path;
|
2018-03-07 01:18:01 +01:00
|
|
|
if (!forceRefresh && sameFile) {
|
2016-02-29 23:59:46 +01:00
|
|
|
return true;
|
|
|
|
}
|
2018-05-26 22:41:59 +02:00
|
|
|
if (isFileOperationOngoing()) {
|
2016-11-23 21:46:33 +01:00
|
|
|
emit statusMessage(tr("Unable to load the selected file \"%1\" because the current process hasn't finished yet.").arg(path));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// clear previous results and status
|
|
|
|
m_tags.clear();
|
|
|
|
m_fileInfo.clearParsingResults();
|
2018-03-07 01:18:01 +01:00
|
|
|
if (!sameFile) {
|
2016-11-23 21:46:33 +01:00
|
|
|
// close last file if possibly open
|
|
|
|
m_fileInfo.close();
|
|
|
|
// set path of file info
|
|
|
|
m_currentPath = path;
|
|
|
|
m_fileInfo.setSaveFilePath(string());
|
2021-04-23 22:18:30 +02:00
|
|
|
m_fileInfo.setPath(std::string(toNativeFileName(path).data()));
|
2016-12-01 22:23:01 +01:00
|
|
|
// update file name and directory
|
2018-03-06 02:04:35 +01:00
|
|
|
const QFileInfo fileInfo(path);
|
2016-11-23 21:46:33 +01:00
|
|
|
m_lastDir = m_currentDir;
|
2016-12-01 22:23:01 +01:00
|
|
|
m_currentDir = fileInfo.absolutePath();
|
|
|
|
m_fileName = fileInfo.fileName();
|
2016-11-23 21:46:33 +01:00
|
|
|
}
|
2021-07-03 19:38:27 +02:00
|
|
|
// write diagnostics to m_diagReparsing if making results are available
|
2016-11-23 21:46:33 +01:00
|
|
|
m_makingResultsAvailable &= sameFile;
|
2018-03-06 02:04:35 +01:00
|
|
|
Diagnostics &diag = m_makingResultsAvailable ? m_diagReparsing : m_diag;
|
|
|
|
// clear diagnostics
|
|
|
|
diag.clear();
|
|
|
|
m_diagReparsing.clear();
|
2016-11-23 21:46:33 +01:00
|
|
|
// show filename
|
2016-12-01 22:23:01 +01:00
|
|
|
m_ui->fileNameLabel->setText(m_fileName);
|
2016-11-23 21:46:33 +01:00
|
|
|
// define function to parse the file
|
2021-02-15 23:45:39 +01:00
|
|
|
const auto startThread = [this, &diag] {
|
2021-06-01 22:36:48 +02:00
|
|
|
auto result = char();
|
|
|
|
auto ioError = QString();
|
2021-02-02 15:05:45 +01:00
|
|
|
try {
|
|
|
|
// try to open with write access
|
2016-11-23 21:46:33 +01:00
|
|
|
try {
|
2021-02-02 15:05:45 +01:00
|
|
|
m_fileInfo.reopen(false);
|
2019-03-13 19:07:51 +01:00
|
|
|
} catch (const std::ios_base::failure &) {
|
2021-02-02 15:05:45 +01:00
|
|
|
// try to open read-only if opening with write access failed
|
|
|
|
m_fileInfo.reopen(true);
|
2016-03-10 22:13:43 +01:00
|
|
|
}
|
2021-02-15 23:45:39 +01:00
|
|
|
AbortableProgressFeedback progress; // FIXME: actually use the progress object
|
2021-02-02 15:05:45 +01:00
|
|
|
m_fileInfo.setForceFullParse(Settings::values().editor.forceFullParse);
|
2021-02-04 23:22:43 +01:00
|
|
|
m_fileInfo.parseEverything(diag, progress);
|
2021-02-02 15:05:45 +01:00
|
|
|
result = ParsingSuccessful;
|
|
|
|
} catch (const Failure &) {
|
|
|
|
// the file has been opened; parsing notifications will be shown in the info box
|
|
|
|
result = FatalParsingError;
|
2021-06-01 22:36:48 +02:00
|
|
|
} catch (const std::ios_base::failure &e) {
|
2021-07-03 19:38:27 +02:00
|
|
|
// the file could not be opened because an IO error occurred
|
2021-02-02 15:05:45 +01:00
|
|
|
m_fileInfo.close(); // ensure file is closed
|
|
|
|
result = IoError;
|
2021-06-01 22:36:48 +02:00
|
|
|
if ((ioError = QString::fromLocal8Bit(e.what())).isEmpty()) {
|
|
|
|
ioError = tr("unknown error");
|
|
|
|
}
|
2021-02-02 15:05:45 +01:00
|
|
|
} catch (const std::exception &e) {
|
2018-03-07 01:18:01 +01:00
|
|
|
diag.emplace_back(TagParser::DiagLevel::Critical, argsToString("Something completely unexpected happened: ", +e.what()), "parsing");
|
2016-11-23 21:46:33 +01:00
|
|
|
result = FatalParsingError;
|
|
|
|
}
|
2021-06-01 22:36:48 +02:00
|
|
|
QMetaObject::invokeMethod(this, "showFile", Qt::QueuedConnection, Q_ARG(char, result), Q_ARG(QString, ioError));
|
2016-11-23 21:46:33 +01:00
|
|
|
};
|
|
|
|
// perform the operation concurrently
|
2018-05-26 22:41:59 +02:00
|
|
|
m_ongoingFileOperation = QtConcurrent::run(startThread);
|
2016-11-23 21:46:33 +01:00
|
|
|
// inform user
|
2022-04-12 01:13:58 +02:00
|
|
|
static const auto statusMsg(tr("The file is being parsed ..."));
|
2016-11-23 21:46:33 +01:00
|
|
|
m_ui->parsingNotificationWidget->setNotificationType(NotificationType::Progress);
|
|
|
|
m_ui->parsingNotificationWidget->setText(statusMsg);
|
|
|
|
m_ui->parsingNotificationWidget->setVisible(true); // ensure widget is visible!
|
|
|
|
emit statusMessage(statusMsg);
|
|
|
|
return true;
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Reparses the current file.
|
|
|
|
*/
|
|
|
|
bool TagEditorWidget::reparseFile()
|
|
|
|
{
|
2018-05-26 22:41:59 +02:00
|
|
|
if (isFileOperationOngoing()) {
|
2016-11-23 21:46:33 +01:00
|
|
|
emit statusMessage(tr("Unable to reload the file because the current process hasn't finished yet."));
|
|
|
|
return false;
|
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
if (!m_fileInfo.isOpen() || m_currentPath.isEmpty()) {
|
2016-11-23 21:46:33 +01:00
|
|
|
QMessageBox::warning(this, windowTitle(), tr("Currently is not file opened."));
|
|
|
|
return false;
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
|
|
|
return startParsing(m_currentPath, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Shows the current file info (technical info, tags, ...).
|
|
|
|
* This private slot is invoked from the thread which performed the
|
|
|
|
* parsing operation using Qt::QueuedConnection.
|
2021-07-03 19:38:27 +02:00
|
|
|
* \param result Specifies whether the file could be load successfully.
|
2016-02-29 23:59:46 +01:00
|
|
|
*/
|
2021-06-01 22:36:48 +02:00
|
|
|
void TagEditorWidget::showFile(char result, const QString &ioError)
|
2018-03-07 01:18:01 +01:00
|
|
|
{
|
2021-02-02 15:05:45 +01:00
|
|
|
// handle IO errors
|
2018-03-07 01:18:01 +01:00
|
|
|
if (result == IoError) {
|
2016-02-29 23:59:46 +01:00
|
|
|
// update status
|
|
|
|
updateFileStatusStatus();
|
2021-06-01 22:36:48 +02:00
|
|
|
static const QString statusMsg(tr("The file could not be opened because an IO error occurred: %1"));
|
2016-06-14 00:52:03 +02:00
|
|
|
auto msgBox = new QMessageBox(this);
|
|
|
|
msgBox->setIcon(QMessageBox::Critical);
|
|
|
|
msgBox->setAttribute(Qt::WA_DeleteOnClose, true);
|
2016-12-01 22:23:01 +01:00
|
|
|
msgBox->setWindowTitle(tr("Opening file - ") + QCoreApplication::applicationName());
|
2021-06-01 22:36:48 +02:00
|
|
|
msgBox->setText(statusMsg.arg(ioError));
|
|
|
|
msgBox->setInformativeText(tr("Tried to open file: ") + m_currentPath);
|
2016-06-14 00:52:03 +02:00
|
|
|
msgBox->show();
|
2016-02-29 23:59:46 +01:00
|
|
|
emit statusMessage(statusMsg);
|
2021-02-02 15:05:45 +01:00
|
|
|
return;
|
|
|
|
}
|
2016-07-27 20:30:56 +02:00
|
|
|
|
2021-02-02 15:05:45 +01:00
|
|
|
// load existing tags
|
|
|
|
m_tags.clear();
|
|
|
|
m_fileInfo.tags(m_tags);
|
|
|
|
// show notification if no existing tag(s) could be found
|
|
|
|
if (!m_tags.size()) {
|
|
|
|
m_ui->parsingNotificationWidget->appendLine(tr("There is no (supported) tag assigned."));
|
|
|
|
}
|
2016-07-27 20:30:56 +02:00
|
|
|
|
2021-02-02 15:05:45 +01:00
|
|
|
// show parsing status/result using parsing notification widget
|
|
|
|
auto diagLevel = m_diag.level();
|
|
|
|
if (diagLevel < worstDiagLevel) {
|
|
|
|
diagLevel |= m_diagReparsing.level();
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
2021-02-02 15:05:45 +01:00
|
|
|
if (diagLevel >= DiagLevel::Critical) {
|
2021-07-03 19:38:27 +02:00
|
|
|
// we caught no exception, but there are critical diag messages
|
2021-02-02 15:05:45 +01:00
|
|
|
// -> treat those as fatal parsing errors
|
|
|
|
result = LoadingResult::FatalParsingError;
|
|
|
|
}
|
|
|
|
switch (result) {
|
|
|
|
case ParsingSuccessful:
|
|
|
|
m_ui->parsingNotificationWidget->setNotificationType(NotificationType::TaskComplete);
|
|
|
|
m_ui->parsingNotificationWidget->setText(tr("File could be parsed correctly."));
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
m_ui->parsingNotificationWidget->setNotificationType(NotificationType::Critical);
|
|
|
|
m_ui->parsingNotificationWidget->setText(tr("File couldn't be parsed correctly."));
|
|
|
|
}
|
|
|
|
bool multipleSegmentsNotTested = m_fileInfo.containerFormat() == ContainerFormat::Matroska && m_fileInfo.container()->segmentCount() > 1;
|
|
|
|
if (diagLevel >= TagParser::DiagLevel::Critical) {
|
|
|
|
m_ui->parsingNotificationWidget->setNotificationType(NotificationType::Critical);
|
2021-07-03 19:38:27 +02:00
|
|
|
m_ui->parsingNotificationWidget->appendLine(tr("Errors occurred."));
|
2021-02-04 23:22:43 +01:00
|
|
|
} else if (diagLevel == TagParser::DiagLevel::Warning || m_fileInfo.isReadOnly() || !m_fileInfo.areTagsSupported() || multipleSegmentsNotTested) {
|
2021-02-02 15:05:45 +01:00
|
|
|
m_ui->parsingNotificationWidget->setNotificationType(NotificationType::Warning);
|
|
|
|
if (diagLevel == TagParser::DiagLevel::Warning) {
|
|
|
|
m_ui->parsingNotificationWidget->appendLine(tr("There are warnings."));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (m_fileInfo.isReadOnly()) {
|
|
|
|
m_ui->parsingNotificationWidget->appendLine(tr("No write access; the file has been opened in read-only mode."));
|
|
|
|
}
|
|
|
|
if (!m_fileInfo.areTagsSupported()) {
|
|
|
|
m_ui->parsingNotificationWidget->appendLine(tr("File format is not supported (an ID3 tag can be added anyways)."));
|
|
|
|
}
|
|
|
|
if (multipleSegmentsNotTested) {
|
|
|
|
m_ui->parsingNotificationWidget->appendLine(
|
|
|
|
tr("The file is composed of multiple segments. Dealing with such files has not been tested yet and might be broken."));
|
|
|
|
}
|
|
|
|
|
2021-09-06 18:28:13 +02:00
|
|
|
// show file info (before creating tags so the file is shown as on disk)
|
|
|
|
updateInfoView();
|
|
|
|
|
|
|
|
// create appropriate tags according to file type and user preferences when automatic tag management is enabled
|
|
|
|
auto &settings = Settings::values().tagPocessing;
|
|
|
|
if (settings.autoTagManagement) {
|
|
|
|
settings.creationSettings.requiredTargets.clear();
|
|
|
|
settings.creationSettings.requiredTargets.reserve(2);
|
|
|
|
for (const ChecklistItem &targetItem : Settings::values().editor.defaultTargets.items()) {
|
|
|
|
if (targetItem.isChecked()) {
|
|
|
|
settings.creationSettings.requiredTargets.emplace_back(
|
|
|
|
containerTargetLevelValue(m_fileInfo.containerFormat(), static_cast<TagTargetLevel>(targetItem.id().toInt())));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// TODO: allow initialization of new ID3 tag with values from already present ID3 tag
|
|
|
|
// TODO: allow not to transfer values from removed ID3 tag to remaining ID3 tags
|
|
|
|
settings.creationSettings.flags -= TagCreationFlags::TreatUnknownFilesAsMp3Files;
|
|
|
|
if (!m_fileInfo.createAppropriateTags(settings.creationSettings) && confirmCreationOfId3TagForUnsupportedFile()) {
|
|
|
|
settings.creationSettings.flags += TagCreationFlags::TreatUnknownFilesAsMp3Files;
|
|
|
|
m_fileInfo.createAppropriateTags(settings.creationSettings);
|
|
|
|
}
|
|
|
|
// tags might have been adjusted -> reload tags
|
|
|
|
m_tags.clear();
|
|
|
|
m_fileInfo.tags(m_tags);
|
|
|
|
}
|
|
|
|
|
2021-02-02 15:05:45 +01:00
|
|
|
// update relevant (UI) components
|
|
|
|
m_fileWatcher->addPath(m_currentPath);
|
|
|
|
m_fileChangedOnDisk = false;
|
|
|
|
updateDocumentTitleEdits();
|
|
|
|
updateTagEditsAndAttachmentEdits();
|
|
|
|
updateTagSelectionComboBox();
|
|
|
|
updateTagManagementMenu();
|
|
|
|
emit tagValuesLoaded();
|
|
|
|
insertTitleFromFilename();
|
|
|
|
updateFileStatusStatus();
|
|
|
|
emit statusMessage(tr("The file %1 has been opened.").arg(m_currentPath));
|
|
|
|
emit fileShown();
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Invokes saving the current file and - if saving was successful loading the next file.
|
|
|
|
*/
|
|
|
|
void TagEditorWidget::saveAndShowNextFile()
|
|
|
|
{
|
|
|
|
m_nextFileAfterSaving = true;
|
2016-03-03 22:21:15 +01:00
|
|
|
applyEntriesAndSaveChangings();
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Applies all entries and starts saving.
|
|
|
|
* \sa startSaving()
|
|
|
|
*/
|
|
|
|
bool TagEditorWidget::applyEntriesAndSaveChangings()
|
|
|
|
{
|
2018-05-26 22:41:59 +02:00
|
|
|
if (isFileOperationOngoing()) {
|
2016-11-23 21:46:33 +01:00
|
|
|
static const QString statusMsg(tr("Unable to apply the entered tags to the file because the current process hasn't finished yet."));
|
|
|
|
emit statusMessage(statusMsg);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_ui->makingNotificationWidget->setNotificationType(NotificationType::Information);
|
|
|
|
m_ui->makingNotificationWidget->setNotificationSubject(NotificationSubject::Saving);
|
|
|
|
m_ui->makingNotificationWidget->setHidden(false);
|
|
|
|
|
2018-03-07 01:18:01 +01:00
|
|
|
if (!m_fileInfo.isOpen()) {
|
2016-11-23 21:46:33 +01:00
|
|
|
m_ui->makingNotificationWidget->setText(tr("No file has been opened, so tags can not be saved."));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_makingResultsAvailable = true;
|
|
|
|
|
|
|
|
// apply titles
|
2020-03-19 16:24:32 +01:00
|
|
|
AbstractContainer *const container = m_fileInfo.container();
|
|
|
|
if (container && container->supportsTitle()) {
|
|
|
|
QLayout *const docTitleLayout = m_ui->docTitleWidget->layout();
|
2020-04-24 23:26:52 +02:00
|
|
|
for (std::size_t i = 0, count = min<std::size_t>(static_cast<std::size_t>(docTitleLayout->count()), container->segmentCount()); i < count;
|
|
|
|
++i) {
|
2020-03-19 16:24:32 +01:00
|
|
|
container->setTitle(static_cast<ClearLineEdit *>(docTitleLayout->itemAt(static_cast<int>(i))->widget())->text().toUtf8().data(), i);
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
|
|
|
}
|
2016-11-23 21:46:33 +01:00
|
|
|
// apply all tags
|
2018-03-07 01:18:01 +01:00
|
|
|
foreachTagEdit([](TagEdit *edit) { edit->apply(); });
|
2016-11-23 21:46:33 +01:00
|
|
|
static const QString statusMsg(tr("Saving tags ..."));
|
|
|
|
m_ui->makingNotificationWidget->setNotificationSubject(NotificationSubject::None);
|
|
|
|
m_ui->makingNotificationWidget->setNotificationType(NotificationType::Progress);
|
|
|
|
m_ui->makingNotificationWidget->setText(statusMsg);
|
|
|
|
emit statusMessage(statusMsg);
|
2016-02-29 23:59:46 +01:00
|
|
|
return startSaving();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Deletes all tags and starts saving.
|
|
|
|
* \sa startSaving()
|
|
|
|
*/
|
|
|
|
bool TagEditorWidget::deleteAllTagsAndSave()
|
|
|
|
{
|
2018-05-26 22:41:59 +02:00
|
|
|
if (isFileOperationOngoing()) {
|
2016-11-23 21:46:33 +01:00
|
|
|
static const QString statusMsg(tr("Unable to delete all tags from the file because the current process hasn't been finished yet."));
|
|
|
|
emit statusMessage(statusMsg);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_ui->makingNotificationWidget->setNotificationSubject(NotificationSubject::Saving);
|
|
|
|
m_ui->makingNotificationWidget->setNotificationType(NotificationType::Information);
|
|
|
|
m_ui->makingNotificationWidget->setHidden(false);
|
|
|
|
|
2018-03-07 01:18:01 +01:00
|
|
|
if (!m_fileInfo.isOpen()) {
|
2016-11-23 21:46:33 +01:00
|
|
|
m_ui->makingNotificationWidget->setText(tr("No file has been opened, so no tags can be deleted."));
|
|
|
|
return false;
|
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
if (!m_fileInfo.hasAnyTag()) {
|
2016-11-23 21:46:33 +01:00
|
|
|
m_ui->makingNotificationWidget->setText(tr("The selected file has no tag (at least no supported), so there is nothing to delete."));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-03-07 01:18:01 +01:00
|
|
|
if (Settings::values().editor.askBeforeDeleting) {
|
2016-11-23 21:46:33 +01:00
|
|
|
m_ui->makingNotificationWidget->setText(tr("Do you really want to delete all tags from the file?"));
|
|
|
|
|
|
|
|
QMessageBox msgBox(this);
|
|
|
|
msgBox.setText(m_ui->makingNotificationWidget->text());
|
|
|
|
msgBox.setIcon(QMessageBox::Warning);
|
|
|
|
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
|
|
|
msgBox.setDefaultButton(QMessageBox::No);
|
2016-02-29 23:59:46 +01:00
|
|
|
#if QT_VERSION >= 0x050200
|
2016-11-23 21:46:33 +01:00
|
|
|
auto *checkBox = new QCheckBox(&msgBox);
|
|
|
|
checkBox->setText(tr("Don't show this message again"));
|
|
|
|
msgBox.setCheckBox(checkBox);
|
2016-02-29 23:59:46 +01:00
|
|
|
#endif
|
2016-11-23 21:46:33 +01:00
|
|
|
int res = msgBox.exec();
|
2016-02-29 23:59:46 +01:00
|
|
|
#if QT_VERSION >= 0x050200
|
2018-03-07 01:18:01 +01:00
|
|
|
if (checkBox->isChecked()) {
|
2016-11-23 21:46:33 +01:00
|
|
|
Settings::values().editor.askBeforeDeleting = false;
|
|
|
|
}
|
2016-02-29 23:59:46 +01:00
|
|
|
#endif
|
2018-03-07 01:18:01 +01:00
|
|
|
if (res != QMessageBox::Yes) {
|
2016-11-23 21:46:33 +01:00
|
|
|
m_ui->makingNotificationWidget->setText(tr("Deletion aborted."));
|
2016-02-29 23:59:46 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2016-11-23 21:46:33 +01:00
|
|
|
|
|
|
|
m_makingResultsAvailable = true;
|
|
|
|
|
2018-03-07 01:18:01 +01:00
|
|
|
foreachTagEdit([](TagEdit *edit) { edit->clear(); });
|
2016-11-23 21:46:33 +01:00
|
|
|
m_fileInfo.removeAllTags();
|
|
|
|
m_ui->makingNotificationWidget->setNotificationSubject(NotificationSubject::None);
|
|
|
|
m_ui->makingNotificationWidget->setNotificationType(NotificationType::Progress);
|
|
|
|
static const QString statusMsg(tr("Deleting all tags ..."));
|
|
|
|
m_ui->makingNotificationWidget->setText(statusMsg);
|
|
|
|
emit statusMessage(statusMsg);
|
2016-02-29 23:59:46 +01:00
|
|
|
return startSaving();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Starts saving. This method is called by applyEntriesAndSaveChangings() and deleteAllTagsAndSave().
|
2016-11-23 21:46:33 +01:00
|
|
|
*
|
2016-02-29 23:59:46 +01:00
|
|
|
* The actual process is performed in another thread.
|
2016-11-23 21:46:33 +01:00
|
|
|
*
|
2016-02-29 23:59:46 +01:00
|
|
|
* \remarks Will start a new thread to perform the operation. Then showSavingResult() is called
|
2016-11-23 21:46:33 +01:00
|
|
|
* using Qt::QueuedConnection in the main thread.
|
2016-02-29 23:59:46 +01:00
|
|
|
*/
|
|
|
|
bool TagEditorWidget::startSaving()
|
|
|
|
{
|
2018-05-26 22:41:59 +02:00
|
|
|
if (isFileOperationOngoing()) {
|
2016-02-29 23:59:46 +01:00
|
|
|
static const QString errorMsg(tr("Unable to start saving process because there an other process hasn't finished yet."));
|
|
|
|
emit statusMessage(errorMsg);
|
2018-04-29 19:02:31 +02:00
|
|
|
QMessageBox::warning(this, QCoreApplication::applicationName(), errorMsg);
|
2016-02-29 23:59:46 +01:00
|
|
|
return false;
|
|
|
|
}
|
2016-11-23 21:46:33 +01:00
|
|
|
|
|
|
|
// tags might get invalidated
|
|
|
|
m_tags.clear();
|
2018-03-07 01:18:01 +01:00
|
|
|
foreachTagEdit([](TagEdit *edit) { edit->setTag(nullptr, false); });
|
2016-11-23 21:46:33 +01:00
|
|
|
// show abort button
|
|
|
|
m_ui->abortButton->setHidden(false);
|
|
|
|
m_ui->abortButton->setEnabled(true);
|
|
|
|
m_abortClicked = false;
|
|
|
|
// remove current path from file watcher
|
|
|
|
m_fileWatcher->removePath(m_currentPath);
|
|
|
|
// use current configuration
|
2018-07-14 22:38:25 +02:00
|
|
|
const auto &settings = Settings::values();
|
|
|
|
const auto &fileLayoutSettings = settings.tagPocessing.fileLayout;
|
|
|
|
m_fileInfo.setForceRewrite(fileLayoutSettings.forceRewrite);
|
|
|
|
m_fileInfo.setTagPosition(fileLayoutSettings.preferredTagPosition);
|
|
|
|
m_fileInfo.setForceTagPosition(fileLayoutSettings.forceTagPosition);
|
|
|
|
m_fileInfo.setIndexPosition(fileLayoutSettings.preferredIndexPosition);
|
|
|
|
m_fileInfo.setForceIndexPosition(fileLayoutSettings.forceIndexPosition);
|
|
|
|
m_fileInfo.setMinPadding(fileLayoutSettings.minPadding);
|
|
|
|
m_fileInfo.setMaxPadding(fileLayoutSettings.maxPadding);
|
|
|
|
m_fileInfo.setPreferredPadding(fileLayoutSettings.preferredPadding);
|
|
|
|
m_fileInfo.setBackupDirectory(settings.editor.backupDirectory);
|
2021-08-21 01:22:29 +02:00
|
|
|
const auto startThread = [this, preserveModificationTime = settings.tagPocessing.preserveModificationTime] {
|
2018-03-06 02:04:35 +01:00
|
|
|
// define functions to show the saving progress and to actually applying the changes
|
2019-12-30 23:54:04 +01:00
|
|
|
auto showPercentage([this](AbortableProgressFeedback &progress) {
|
|
|
|
if (m_abortClicked) {
|
|
|
|
progress.tryToAbort();
|
|
|
|
}
|
2018-03-06 02:04:35 +01:00
|
|
|
QMetaObject::invokeMethod(m_ui->makingNotificationWidget, "setPercentage", Qt::QueuedConnection, Q_ARG(int, progress.stepPercentage()));
|
|
|
|
});
|
2019-12-30 23:54:04 +01:00
|
|
|
auto showStep([this](AbortableProgressFeedback &progress) {
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_abortClicked) {
|
2018-03-06 02:04:35 +01:00
|
|
|
progress.tryToAbort();
|
|
|
|
} else {
|
2018-03-07 01:18:01 +01:00
|
|
|
QMetaObject::invokeMethod(
|
|
|
|
m_ui->makingNotificationWidget, "setText", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(progress.step())));
|
2018-03-06 02:04:35 +01:00
|
|
|
}
|
2019-12-30 23:54:04 +01:00
|
|
|
QMetaObject::invokeMethod(m_ui->makingNotificationWidget, "setPercentage", Qt::QueuedConnection, Q_ARG(int, progress.stepPercentage()));
|
2018-03-06 02:04:35 +01:00
|
|
|
});
|
|
|
|
AbortableProgressFeedback progress(std::move(showStep), std::move(showPercentage));
|
|
|
|
|
2021-06-01 22:36:48 +02:00
|
|
|
auto ioError = QString();
|
|
|
|
auto processingError = false, canceled = false;
|
2016-11-23 21:46:33 +01:00
|
|
|
try {
|
2021-08-21 01:22:29 +02:00
|
|
|
auto modificationDateError = std::error_code();
|
|
|
|
auto modificationDate = std::filesystem::file_time_type();
|
|
|
|
auto modifiedFilePath = std::filesystem::path();
|
|
|
|
if (preserveModificationTime) {
|
2022-03-17 22:41:51 +01:00
|
|
|
modifiedFilePath = makeNativePath(m_fileInfo.saveFilePath().empty() ? m_fileInfo.path() : m_fileInfo.saveFilePath());
|
2021-08-21 01:22:29 +02:00
|
|
|
modificationDate = std::filesystem::last_write_time(modifiedFilePath, modificationDateError);
|
|
|
|
}
|
2016-11-23 21:46:33 +01:00
|
|
|
try {
|
2018-03-06 02:04:35 +01:00
|
|
|
m_fileInfo.applyChanges(m_diag, progress);
|
2019-12-30 23:54:04 +01:00
|
|
|
} catch (const OperationAbortedException &) {
|
|
|
|
canceled = true;
|
2018-03-07 01:18:01 +01:00
|
|
|
} catch (const Failure &) {
|
2016-11-23 21:46:33 +01:00
|
|
|
processingError = true;
|
2021-06-01 22:36:48 +02:00
|
|
|
} catch (const std::ios_base::failure &e) {
|
|
|
|
if ((ioError = QString::fromLocal8Bit(e.what())).isEmpty()) {
|
|
|
|
ioError = tr("unknown error");
|
|
|
|
}
|
2016-11-23 21:46:33 +01:00
|
|
|
}
|
2021-08-21 01:22:29 +02:00
|
|
|
if (preserveModificationTime) {
|
|
|
|
if (!modificationDateError) {
|
|
|
|
std::filesystem::last_write_time(modifiedFilePath, modificationDate, modificationDateError);
|
|
|
|
}
|
|
|
|
if (modificationDateError) {
|
|
|
|
m_diag.emplace_back(
|
|
|
|
DiagLevel::Critical, "Unable to preserve modification time: " + modificationDateError.message(), "applying changes");
|
|
|
|
}
|
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
} catch (const exception &e) {
|
2021-08-21 01:22:29 +02:00
|
|
|
m_diag.emplace_back(
|
|
|
|
TagParser::DiagLevel::Critical, argsToString("Something completely unexpected happened: ", e.what()), "applying changes");
|
2016-11-23 21:46:33 +01:00
|
|
|
processingError = true;
|
|
|
|
}
|
2019-12-30 23:54:04 +01:00
|
|
|
QMetaObject::invokeMethod(
|
2021-06-01 22:36:48 +02:00
|
|
|
this, "showSavingResult", Qt::QueuedConnection, Q_ARG(QString, ioError), Q_ARG(bool, processingError), Q_ARG(bool, canceled));
|
2016-11-23 21:46:33 +01:00
|
|
|
};
|
|
|
|
// use another thread to perform the operation
|
2018-05-26 22:41:59 +02:00
|
|
|
m_ongoingFileOperation = QtConcurrent::run(startThread);
|
2016-11-23 21:46:33 +01:00
|
|
|
return true;
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Shows the saving results.
|
2016-11-23 21:46:33 +01:00
|
|
|
*
|
2016-02-29 23:59:46 +01:00
|
|
|
* This private slot is invoked from the thread which performed the
|
|
|
|
* saving operation using Qt::QueuedConnection.
|
2016-11-23 21:46:33 +01:00
|
|
|
*
|
2021-07-03 19:38:27 +02:00
|
|
|
* \param success Specifies whether the file could be saved successfully.
|
2016-02-29 23:59:46 +01:00
|
|
|
*/
|
2021-06-01 22:36:48 +02:00
|
|
|
void TagEditorWidget::showSavingResult(QString ioError, bool processingError, bool canceled)
|
2016-02-29 23:59:46 +01:00
|
|
|
{
|
|
|
|
m_ui->abortButton->setHidden(true);
|
|
|
|
m_ui->makingNotificationWidget->setNotificationType(NotificationType::TaskComplete);
|
|
|
|
m_ui->makingNotificationWidget->setNotificationSubject(NotificationSubject::Saving);
|
|
|
|
m_ui->makingNotificationWidget->setPercentage(-1);
|
|
|
|
m_ui->makingNotificationWidget->setHidden(false);
|
|
|
|
m_makingResultsAvailable = true;
|
2021-06-01 22:36:48 +02:00
|
|
|
if (!processingError && ioError.isEmpty()) {
|
2016-02-29 23:59:46 +01:00
|
|
|
// display status messages
|
|
|
|
QString statusMsg;
|
|
|
|
size_t critical = 0, warnings = 0;
|
2018-03-07 01:18:01 +01:00
|
|
|
for (const auto &msg : m_diag) {
|
|
|
|
switch (msg.level()) {
|
2018-03-06 23:10:13 +01:00
|
|
|
case TagParser::DiagLevel::Fatal:
|
|
|
|
case TagParser::DiagLevel::Critical:
|
2016-02-29 23:59:46 +01:00
|
|
|
++critical;
|
|
|
|
break;
|
2018-03-06 23:10:13 +01:00
|
|
|
case TagParser::DiagLevel::Warning:
|
2016-02-29 23:59:46 +01:00
|
|
|
++warnings;
|
|
|
|
break;
|
2018-03-07 01:18:01 +01:00
|
|
|
default:;
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
|
|
|
}
|
2019-12-30 23:54:04 +01:00
|
|
|
if (canceled) {
|
|
|
|
if (critical) {
|
|
|
|
statusMsg = tr("Saving has been canceled and there is/are %1 warning(s) ", nullptr, trQuandity(warnings)).arg(warnings);
|
|
|
|
statusMsg.append(tr("and %1 error(s).", nullptr, trQuandity(critical)).arg(critical));
|
|
|
|
} else if (warnings) {
|
|
|
|
statusMsg = tr("Saving has been canceled and there is/are %1 warning(s).", nullptr, trQuandity(warnings)).arg(warnings);
|
|
|
|
} else {
|
|
|
|
statusMsg = tr("Saving tags has been canceled.");
|
|
|
|
}
|
|
|
|
m_ui->makingNotificationWidget->setNotificationType(critical ? NotificationType::Critical : NotificationType::Warning);
|
|
|
|
|
|
|
|
} else if (warnings || critical) {
|
2018-03-07 01:18:01 +01:00
|
|
|
if (critical) {
|
2018-03-06 02:04:35 +01:00
|
|
|
statusMsg = tr("The tags have been saved, but there is/are %1 warning(s) ", nullptr, trQuandity(warnings)).arg(warnings);
|
|
|
|
statusMsg.append(tr("and %1 error(s).", nullptr, trQuandity(critical)).arg(critical));
|
2016-02-29 23:59:46 +01:00
|
|
|
} else {
|
2018-03-06 02:04:35 +01:00
|
|
|
statusMsg = tr("The tags have been saved, but there is/are %1 warning(s).", nullptr, trQuandity(warnings)).arg(warnings);
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
2018-03-06 02:04:35 +01:00
|
|
|
m_ui->makingNotificationWidget->setNotificationType(critical ? NotificationType::Critical : NotificationType::Warning);
|
2016-02-29 23:59:46 +01:00
|
|
|
|
|
|
|
} else {
|
|
|
|
statusMsg = tr("The tags have been saved.");
|
|
|
|
}
|
|
|
|
m_ui->makingNotificationWidget->setText(statusMsg);
|
|
|
|
emit statusMessage(statusMsg);
|
|
|
|
// let the main window know that the current file has been saved
|
|
|
|
// -> the main window will ensure the current file is still selected
|
2016-08-07 19:59:33 +02:00
|
|
|
emit currentPathChanged(m_currentPath);
|
2016-02-29 23:59:46 +01:00
|
|
|
// show next file (only if there are critical notifications)
|
2018-03-07 01:18:01 +01:00
|
|
|
if (!critical && m_nextFileAfterSaving) {
|
2016-02-29 23:59:46 +01:00
|
|
|
emit nextFileSelected();
|
|
|
|
} else {
|
2016-05-01 20:07:04 +02:00
|
|
|
// the current path might have changed through "save file path" mechanism
|
2016-12-20 23:53:33 +01:00
|
|
|
startParsing(m_currentPath = fromNativeFileName(m_fileInfo.path()), true);
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
2016-03-03 22:21:15 +01:00
|
|
|
m_nextFileAfterSaving = false;
|
2016-02-29 23:59:46 +01:00
|
|
|
} else {
|
2021-07-03 19:38:27 +02:00
|
|
|
// fatal errors occurred
|
2016-05-01 20:07:04 +02:00
|
|
|
|
|
|
|
// -> show status
|
2021-06-01 22:36:48 +02:00
|
|
|
static const QString processingErrorMsg = tr("The tags could not be saved. Checkout the info box for details.");
|
2021-07-03 19:38:27 +02:00
|
|
|
static const QString ioErrorMsg = tr("The tags could not be saved because an IO error occurred: %1");
|
2021-06-01 22:36:48 +02:00
|
|
|
const auto errorMsg = !ioError.isEmpty() ? ioErrorMsg.arg(ioError) : processingErrorMsg;
|
|
|
|
auto msgBox = new QMessageBox(this);
|
|
|
|
msgBox->setIcon(QMessageBox::Critical);
|
|
|
|
msgBox->setAttribute(Qt::WA_DeleteOnClose, true);
|
|
|
|
msgBox->setWindowTitle(tr("Saving file - ") + QCoreApplication::applicationName());
|
|
|
|
msgBox->setText(errorMsg);
|
|
|
|
msgBox->setInformativeText(tr("Tried to save file: ") + m_currentPath);
|
|
|
|
msgBox->show();
|
2016-02-29 23:59:46 +01:00
|
|
|
emit statusMessage(errorMsg);
|
|
|
|
m_ui->makingNotificationWidget->setText(errorMsg);
|
|
|
|
m_ui->makingNotificationWidget->setNotificationType(NotificationType::Critical);
|
2016-05-01 20:07:04 +02:00
|
|
|
|
|
|
|
// -> reset "save as path" in any case after fatal error
|
|
|
|
m_fileInfo.setSaveFilePath(string());
|
|
|
|
|
2016-02-29 23:59:46 +01:00
|
|
|
startParsing(m_currentPath, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Asks the user whether an ID3 tag should be add to a not supported container format and returns the result.
|
|
|
|
*/
|
|
|
|
bool TagEditorWidget::confirmCreationOfId3TagForUnsupportedFile()
|
|
|
|
{
|
|
|
|
QMessageBox msgBox(this);
|
|
|
|
msgBox.setWindowTitle(tr("Automatic tag management"));
|
2018-03-07 01:18:01 +01:00
|
|
|
msgBox.setText(tr("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?"));
|
2016-02-29 23:59:46 +01:00
|
|
|
msgBox.setIcon(QMessageBox::Warning);
|
|
|
|
msgBox.addButton(tr("Treat file as MP3 file"), QMessageBox::AcceptRole);
|
|
|
|
msgBox.addButton(tr("Abort"), QMessageBox::RejectRole);
|
|
|
|
return msgBox.exec() == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief This slot is connected to the fileChanged() signal of the file info watcher.
|
|
|
|
*/
|
|
|
|
void TagEditorWidget::fileChangedOnDisk(const QString &path)
|
|
|
|
{
|
2018-03-07 01:18:01 +01:00
|
|
|
if (!m_fileChangedOnDisk && m_fileInfo.isOpen() && path == m_currentPath) {
|
2016-02-29 23:59:46 +01:00
|
|
|
auto ¬ifyWidget = *m_ui->parsingNotificationWidget;
|
|
|
|
notifyWidget.appendLine(tr("The currently opened file changed on the disk."));
|
2018-03-07 01:18:01 +01:00
|
|
|
notifyWidget.setNotificationType(
|
|
|
|
notifyWidget.notificationType() == NotificationType::Critical ? NotificationType::Critical : NotificationType::Warning);
|
2016-02-29 23:59:46 +01:00
|
|
|
m_fileChangedOnDisk = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Closes the currently opened file and disables all related widgets.
|
|
|
|
*/
|
|
|
|
void TagEditorWidget::closeFile()
|
|
|
|
{
|
2018-05-26 22:41:59 +02:00
|
|
|
if (isFileOperationOngoing()) {
|
2022-07-29 23:50:28 +02:00
|
|
|
emit statusMessage(tr("Unable to close the file because the current process hasn't been finished yet."));
|
2016-11-23 21:46:33 +01:00
|
|
|
return;
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
2016-11-23 21:46:33 +01:00
|
|
|
|
|
|
|
// close file
|
2022-07-29 23:48:35 +02:00
|
|
|
auto errorMsg = QString();
|
|
|
|
try {
|
|
|
|
m_fileInfo.close();
|
|
|
|
} catch (const std::ios_base::failure &e) {
|
|
|
|
auto msgBox = new QMessageBox(this);
|
|
|
|
msgBox->setIcon(QMessageBox::Critical);
|
|
|
|
msgBox->setAttribute(Qt::WA_DeleteOnClose, true);
|
|
|
|
msgBox->setWindowTitle(tr("Closing file - ") + QCoreApplication::applicationName());
|
|
|
|
msgBox->setText(errorMsg = tr("Unable to close file: %1").arg(e.what()));
|
|
|
|
msgBox->setInformativeText(tr("Tried to close file: ") + m_currentPath);
|
|
|
|
msgBox->show();
|
|
|
|
}
|
|
|
|
|
2016-11-23 21:46:33 +01:00
|
|
|
// remove current path from file watcher
|
|
|
|
m_fileWatcher->removePath(m_currentPath);
|
|
|
|
// update ui
|
2022-07-29 23:48:35 +02:00
|
|
|
emit statusMessage(errorMsg.isEmpty() ? tr("The file has been closed.") : errorMsg);
|
2016-11-23 21:46:33 +01:00
|
|
|
updateFileStatusStatus();
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
|
|
|
|
2023-06-07 23:18:04 +02:00
|
|
|
void TagEditorWidget::renameFile()
|
2022-07-29 23:51:32 +02:00
|
|
|
{
|
|
|
|
if (isFileOperationOngoing()) {
|
|
|
|
emit statusMessage(tr("Unable to rename the file because the current process hasn't been finished yet."));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (m_currentPath.isEmpty() || m_fileName.isEmpty() || !m_fileInfo.isOpen()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
static const auto windowTitle = tr("Renaming file - ") + QCoreApplication::applicationName();
|
|
|
|
const auto newFileName = QInputDialog::getText(this, windowTitle, tr("New file name:"), QLineEdit::Normal, m_fileName);
|
|
|
|
if (newFileName.isEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto errorMsg = QString();
|
|
|
|
try {
|
|
|
|
// remove watcher, close file
|
|
|
|
m_fileWatcher->removePath(m_currentPath);
|
|
|
|
m_fileInfo.stream().close();
|
|
|
|
|
|
|
|
// rename file
|
|
|
|
auto oldPath = std::filesystem::path(makeNativePath(m_currentPath.toStdString()));
|
|
|
|
auto newPath = oldPath.parent_path();
|
|
|
|
newPath.append(makeNativePath(newFileName.toStdString()));
|
|
|
|
std::filesystem::rename(oldPath, newPath);
|
|
|
|
|
|
|
|
// open again with write access
|
2023-03-06 22:20:41 +01:00
|
|
|
m_fileInfo.reportPathChanged(extractNativePath(newPath.native()));
|
2022-07-29 23:51:32 +02:00
|
|
|
try {
|
|
|
|
m_fileInfo.stream().open(m_fileInfo.path().data(), ios_base::in | ios_base::out | ios_base::binary);
|
|
|
|
} catch (const std::ios_base::failure &) {
|
|
|
|
// try to open read-only if opening with write access failed
|
|
|
|
m_fileInfo.stream().open(m_fileInfo.path().data(), ios_base::in | ios_base::binary);
|
|
|
|
}
|
|
|
|
m_currentPath = QString::fromStdString(m_fileInfo.path());
|
|
|
|
m_fileName = QString::fromStdString(m_fileInfo.fileName());
|
|
|
|
m_ui->fileNameLabel->setText(m_fileName);
|
|
|
|
emit currentPathChanged(m_currentPath);
|
|
|
|
|
|
|
|
} catch (const std::runtime_error &e) {
|
|
|
|
auto msgBox = new QMessageBox(this);
|
|
|
|
msgBox->setIcon(QMessageBox::Critical);
|
|
|
|
msgBox->setAttribute(Qt::WA_DeleteOnClose, true);
|
|
|
|
msgBox->setWindowTitle(windowTitle);
|
|
|
|
msgBox->setText(errorMsg = tr("Unable to rename file: %1").arg(e.what()));
|
|
|
|
msgBox->setInformativeText(tr("Tried to rename file: ") + m_currentPath);
|
|
|
|
msgBox->show();
|
|
|
|
}
|
|
|
|
|
|
|
|
// add new current path to file watcher
|
|
|
|
m_fileWatcher->addPath(m_currentPath);
|
|
|
|
// update ui
|
|
|
|
emit statusMessage(errorMsg.isEmpty() ? tr("The file has been renamed.") : errorMsg);
|
|
|
|
updateFileStatusStatus();
|
|
|
|
}
|
|
|
|
|
2017-09-20 19:37:49 +02:00
|
|
|
bool TagEditorWidget::handleFileInfoUnavailable()
|
|
|
|
{
|
2018-05-26 22:41:59 +02:00
|
|
|
if (isFileOperationOngoing()) {
|
2017-09-20 19:37:49 +02:00
|
|
|
emit statusMessage(tr("Unable to save file information because the current process hasn't been finished yet."));
|
|
|
|
return true;
|
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
if (!fileInfo().isOpen()) {
|
2018-04-29 19:02:31 +02:00
|
|
|
QMessageBox::information(this, QCoreApplication::applicationName(), tr("No file is opened."));
|
2017-09-20 19:37:49 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-03-07 01:18:01 +01:00
|
|
|
if (generateFileInfoHtml().isEmpty()) {
|
2018-04-29 19:02:31 +02:00
|
|
|
QMessageBox::information(this, QCoreApplication::applicationName(), tr("No file information available."));
|
2017-09-20 19:37:49 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TagEditorWidget::writeFileInfoToFile(QFile &file)
|
|
|
|
{
|
2018-03-07 01:18:01 +01:00
|
|
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
2018-04-29 19:02:31 +02:00
|
|
|
QMessageBox::critical(this, QCoreApplication::applicationName(), tr("Unable to open file \"%1\".").arg(file.fileName()));
|
2017-09-20 19:37:49 +02:00
|
|
|
return false;
|
|
|
|
}
|
2020-05-13 18:42:17 +02:00
|
|
|
file.write(fileInfoHtml());
|
2017-09-20 19:37:49 +02:00
|
|
|
file.close();
|
|
|
|
|
2018-03-07 01:18:01 +01:00
|
|
|
if (file.error() != QFileDevice::NoError) {
|
|
|
|
QMessageBox::critical(
|
2018-04-29 19:02:31 +02:00
|
|
|
this, QCoreApplication::applicationName(), tr("Unable to write to file \"%1\".\n%2").arg(file.fileName(), file.errorString()));
|
2017-09-20 19:37:49 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Saves the file information generated to be displayed in the info web view in a file.
|
|
|
|
*/
|
|
|
|
void TagEditorWidget::saveFileInfo()
|
|
|
|
{
|
2018-03-07 01:18:01 +01:00
|
|
|
if (handleFileInfoUnavailable()) {
|
2017-09-20 19:37:49 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const QString path(QFileDialog::getSaveFileName(this, tr("Save file information - ") + QCoreApplication::applicationName()));
|
2018-03-07 01:18:01 +01:00
|
|
|
if (path.isEmpty()) {
|
2018-04-29 19:02:31 +02:00
|
|
|
QMessageBox::critical(this, QCoreApplication::applicationName(), tr("Unable to open file."));
|
2017-09-20 19:37:49 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QFile file(path);
|
|
|
|
writeFileInfoToFile(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TagEditorWidget::openFileInfoInBrowser()
|
|
|
|
{
|
2018-03-07 01:18:01 +01:00
|
|
|
if (handleFileInfoUnavailable()) {
|
2017-09-20 19:37:49 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_temporaryInfoFile = make_unique<QTemporaryFile>(QDir::tempPath() + QStringLiteral("/" PROJECT_NAME "-fileinfo-XXXXXX.xhtml"));
|
2018-03-07 01:18:01 +01:00
|
|
|
if (!writeFileInfoToFile(*m_temporaryInfoFile)) {
|
2017-09-20 19:37:49 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QDesktopServices::openUrl(QStringLiteral("file://") + m_temporaryInfoFile->fileName());
|
|
|
|
}
|
|
|
|
|
2016-02-29 23:59:46 +01:00
|
|
|
/*!
|
|
|
|
* \brief This private slot is invoked the the return key has been pressed in a tag edit.
|
|
|
|
*
|
|
|
|
* The file will be saved and then the next opened if the user selected that option.
|
|
|
|
*/
|
|
|
|
void TagEditorWidget::handleReturnPressed()
|
|
|
|
{
|
2018-03-07 01:18:01 +01:00
|
|
|
if (Settings::values().editor.saveAndShowNextOnEnter && m_fileInfo.isOpen()) {
|
2016-02-29 23:59:46 +01:00
|
|
|
saveAndShowNextFile();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TagEditorWidget::handleKeepPreviousValuesActionTriggered(QAction *action)
|
|
|
|
{
|
2018-03-14 19:35:52 +01:00
|
|
|
auto &adoptFields = Settings::values().editor.adoptFields;
|
2018-03-07 01:18:01 +01:00
|
|
|
if (action == m_ui->actionKeep_previous_values_never) {
|
2018-03-14 19:35:52 +01:00
|
|
|
adoptFields = Settings::AdoptFields::Never;
|
2018-03-07 01:18:01 +01:00
|
|
|
} else if (action == m_ui->actionKeep_previous_values_within_same_dir) {
|
2018-03-14 19:35:52 +01:00
|
|
|
adoptFields = Settings::AdoptFields::WithinDirectory;
|
2018-03-07 01:18:01 +01:00
|
|
|
} else if (action == m_ui->actionKeep_previous_values_always) {
|
2018-03-14 19:35:52 +01:00
|
|
|
adoptFields = Settings::AdoptFields::Always;
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
2018-03-14 19:35:52 +01:00
|
|
|
updateKeepPreviousValuesButton();
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Applies settings from Settings namespace. Only settings configurable through the SettingsDialog
|
|
|
|
* will be applied and not settings like the main window's geometry and state.
|
|
|
|
*/
|
|
|
|
void TagEditorWidget::applySettingsFromDialog()
|
|
|
|
{
|
2016-10-24 20:15:10 +02:00
|
|
|
const auto &settings = Settings::values();
|
2018-03-07 01:18:01 +01:00
|
|
|
switch (settings.editor.adoptFields) {
|
2016-02-29 23:59:46 +01:00
|
|
|
case Settings::AdoptFields::Never:
|
|
|
|
m_ui->actionKeep_previous_values_never->setChecked(true);
|
|
|
|
break;
|
|
|
|
case Settings::AdoptFields::WithinDirectory:
|
|
|
|
m_ui->actionKeep_previous_values_within_same_dir->setChecked(true);
|
|
|
|
break;
|
|
|
|
case Settings::AdoptFields::Always:
|
|
|
|
m_ui->actionKeep_previous_values_always->setChecked(true);
|
|
|
|
break;
|
|
|
|
}
|
2018-03-14 19:35:52 +01:00
|
|
|
updateKeepPreviousValuesButton();
|
2016-10-24 20:15:10 +02:00
|
|
|
m_ui->actionManage_tags_automatically_when_loading_file->setChecked(settings.tagPocessing.autoTagManagement);
|
|
|
|
foreachTagEdit(bind(&TagEdit::setCoverButtonsHidden, _1, settings.editor.hideCoverButtons));
|
2016-04-21 23:55:22 +02:00
|
|
|
// ensure info view is displayed/not displayed according to settings
|
|
|
|
initInfoView();
|
|
|
|
updateInfoView();
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
|
|
|
|
2019-06-01 12:53:44 +02:00
|
|
|
void TagEditorWidget::addParsingNotificationLine(const QString &line)
|
|
|
|
{
|
|
|
|
m_ui->parsingNotificationWidget->appendLine(line);
|
|
|
|
}
|
|
|
|
|
2016-02-29 23:59:46 +01:00
|
|
|
/*!
|
|
|
|
* \brief Adds a tag (using the specified \a createTag function) to the currently opened file.
|
|
|
|
*
|
|
|
|
* Shows an error message if no file is opened. Tag edits, tag management menu und UI status will be updated.
|
|
|
|
*/
|
2018-03-06 23:10:13 +01:00
|
|
|
void TagEditorWidget::addTag(const function<TagParser::Tag *(TagParser::MediaFileInfo &)> &createTag)
|
2016-02-29 23:59:46 +01:00
|
|
|
{
|
2018-05-26 22:41:59 +02:00
|
|
|
if (isFileOperationOngoing()) {
|
2022-07-29 23:50:28 +02:00
|
|
|
emit statusMessage(tr("Unable to add a tag because the current process hasn't been finished yet."));
|
2016-11-23 21:46:33 +01:00
|
|
|
return;
|
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
if (!m_fileInfo.isOpen()) {
|
2022-07-29 23:50:28 +02:00
|
|
|
emit statusMessage(tr("Unable to add a tag because no file is opened."));
|
2016-11-23 21:46:33 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-04-29 19:02:31 +02:00
|
|
|
Tag *const tag = createTag(m_fileInfo);
|
|
|
|
if (!tag) {
|
2016-11-23 21:46:33 +01:00
|
|
|
QMessageBox::warning(this, windowTitle(), tr("The tag can not be created."));
|
2018-04-29 19:02:31 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (std::find(m_tags.cbegin(), m_tags.cend(), tag) != m_tags.cend()) {
|
|
|
|
QMessageBox::warning(this, windowTitle(), tr("A tag (with the selected target) already exists."));
|
|
|
|
return;
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
2018-04-29 19:02:31 +02:00
|
|
|
|
|
|
|
m_tags.push_back(tag);
|
|
|
|
updateTagEditsAndAttachmentEdits(true, m_tags.size() > 1 ? PreviousValueHandling::Keep : PreviousValueHandling::Auto);
|
|
|
|
updateTagSelectionComboBox();
|
|
|
|
updateTagManagementMenu();
|
|
|
|
updateFileStatusStatus();
|
|
|
|
insertTitleFromFilename();
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Removes the specified \a tag from the currently opened file.
|
|
|
|
*
|
|
|
|
* Shows an error message if the removal is (currently) not possible. Tag edits, tag management menu und UI status will be updated.
|
|
|
|
*/
|
|
|
|
void TagEditorWidget::removeTag(Tag *tag)
|
|
|
|
{
|
2018-04-29 19:02:31 +02:00
|
|
|
if (!tag) {
|
|
|
|
return;
|
|
|
|
}
|
2018-05-26 22:41:59 +02:00
|
|
|
if (isFileOperationOngoing()) {
|
2018-04-29 19:02:31 +02:00
|
|
|
emit statusMessage(tr("Unable to remove the tag because the current process hasn't been finished yet."));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!m_fileInfo.isOpen()) {
|
|
|
|
emit statusMessage(tr("Unable to remove the tag because no file is opened."));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove tag itself
|
|
|
|
m_fileInfo.removeTag(tag);
|
|
|
|
m_tags.erase(remove(m_tags.begin(), m_tags.end(), tag), m_tags.end());
|
|
|
|
|
|
|
|
// remove tag from all TagEdit widgets
|
|
|
|
vector<TagEdit *> toRemove;
|
|
|
|
for (int index = 0, count = m_ui->stackedWidget->count(); index < count; ++index) {
|
|
|
|
auto *const edit = qobject_cast<TagEdit *>(m_ui->stackedWidget->widget(index));
|
|
|
|
if (!edit || !edit->tags().contains(tag)) {
|
|
|
|
continue;
|
2016-11-23 21:46:33 +01:00
|
|
|
}
|
|
|
|
|
2018-04-29 19:02:31 +02:00
|
|
|
QList<Tag *> tagsOfEdit = edit->tags();
|
|
|
|
tagsOfEdit.removeAll(tag);
|
|
|
|
|
|
|
|
// reassign remaining tags (keeping the previous values)
|
|
|
|
if (!tagsOfEdit.empty()) {
|
|
|
|
edit->setPreviousValueHandling(PreviousValueHandling::Keep);
|
|
|
|
edit->setTags(tagsOfEdit, true);
|
|
|
|
continue;
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
2018-04-29 19:02:31 +02:00
|
|
|
|
|
|
|
// handle case when no tags left in the edit
|
|
|
|
if (m_tags.empty()) {
|
|
|
|
// disable the edit if there are no other tag edits
|
|
|
|
edit->setTag(nullptr, false);
|
|
|
|
} else {
|
|
|
|
// remove the edit if there are still other tag edits
|
|
|
|
toRemove.push_back(edit);
|
2016-11-23 21:46:33 +01:00
|
|
|
}
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
2018-04-29 19:02:31 +02:00
|
|
|
|
|
|
|
// remove TagEdit widgets
|
2018-05-26 22:43:35 +02:00
|
|
|
for (TagEdit *const edit : toRemove) {
|
2018-04-29 19:02:31 +02:00
|
|
|
m_ui->tagSelectionComboBox->removeItem(m_ui->stackedWidget->indexOf(edit));
|
|
|
|
m_ui->stackedWidget->removeWidget(edit);
|
|
|
|
delete edit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// update affected widgets
|
|
|
|
updateTagSelectionComboBox();
|
|
|
|
updateTagManagementMenu();
|
|
|
|
updateFileStatusStatus();
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Changes the target of the specified \a tag; prompts the user to enter tag target information.
|
|
|
|
*
|
|
|
|
* Shows an error message if the change is (currently) not possible. Tag management menu and tag selection combo box will be updated.
|
|
|
|
*/
|
|
|
|
void TagEditorWidget::changeTarget(Tag *tag)
|
|
|
|
{
|
2018-04-29 19:02:31 +02:00
|
|
|
if (!tag) {
|
|
|
|
return;
|
|
|
|
}
|
2018-05-26 22:41:59 +02:00
|
|
|
if (isFileOperationOngoing()) {
|
2018-04-29 19:02:31 +02:00
|
|
|
emit statusMessage(tr("Unable to change the target because the current process hasn't been finished yet."));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!m_fileInfo.isOpen()) {
|
|
|
|
emit statusMessage(tr("Unable to change the target because no file is opened."));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!tag->supportsTarget()) {
|
|
|
|
QMessageBox::warning(this, windowTitle(), tr("Can not change the target of the selected tag because the tag does not support targets."));
|
|
|
|
return;
|
|
|
|
}
|
2016-11-23 21:46:33 +01:00
|
|
|
|
2018-04-29 19:02:31 +02:00
|
|
|
EnterTargetDialog targetDlg(this);
|
|
|
|
targetDlg.setTarget(tag->target(), &m_fileInfo);
|
|
|
|
if (targetDlg.exec() != QDialog::Accepted) {
|
|
|
|
return;
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
2018-04-29 19:02:31 +02:00
|
|
|
tag->setTarget(targetDlg.target());
|
|
|
|
updateTagSelectionComboBox();
|
|
|
|
updateTagManagementMenu();
|
2016-02-29 23:59:46 +01:00
|
|
|
}
|
|
|
|
|
2018-03-07 01:18:01 +01:00
|
|
|
} // namespace QtGui
|