2015-09-06 20:20:00 +02:00
|
|
|
#include "./tagfieldedit.h"
|
|
|
|
#include "./picturepreviewselection.h"
|
2015-04-22 19:33:53 +02:00
|
|
|
|
2015-09-06 20:20:00 +02:00
|
|
|
#include "../application/knownfieldmodel.h"
|
2018-03-07 01:18:01 +01:00
|
|
|
#include "../application/settings.h"
|
2015-04-22 19:33:53 +02:00
|
|
|
|
2015-09-06 20:20:00 +02:00
|
|
|
#include "../misc/utility.h"
|
2015-04-22 19:33:53 +02:00
|
|
|
|
|
|
|
#include <tagparser/id3/id3v2tag.h>
|
|
|
|
#include <tagparser/mediafileinfo.h>
|
2018-03-07 01:18:01 +01:00
|
|
|
#include <tagparser/tag.h>
|
|
|
|
#include <tagparser/tagvalue.h>
|
2015-04-22 19:33:53 +02:00
|
|
|
|
2021-03-20 21:59:49 +01:00
|
|
|
#include <qtutilities/misc/conversion.h>
|
2015-04-22 19:33:53 +02:00
|
|
|
#include <qtutilities/widgets/clearcombobox.h>
|
2018-03-07 01:18:01 +01:00
|
|
|
#include <qtutilities/widgets/clearlineedit.h>
|
2015-04-22 19:33:53 +02:00
|
|
|
#include <qtutilities/widgets/clearplaintextedit.h>
|
2018-03-07 01:18:01 +01:00
|
|
|
#include <qtutilities/widgets/clearspinbox.h>
|
2015-04-22 19:33:53 +02:00
|
|
|
#include <qtutilities/widgets/iconbutton.h>
|
|
|
|
|
|
|
|
#include <c++utilities/conversion/conversionexception.h>
|
|
|
|
|
2018-03-07 01:18:01 +01:00
|
|
|
#include <QAction>
|
|
|
|
#include <QCursor>
|
|
|
|
#include <QEvent>
|
|
|
|
#include <QFile>
|
2015-04-22 19:33:53 +02:00
|
|
|
#include <QGraphicsScene>
|
|
|
|
#include <QGraphicsTextItem>
|
2018-03-07 01:18:01 +01:00
|
|
|
#include <QGraphicsView>
|
2019-11-03 23:05:35 +01:00
|
|
|
#include <QGuiApplication>
|
2018-03-07 01:18:01 +01:00
|
|
|
#include <QHBoxLayout>
|
2015-04-22 19:33:53 +02:00
|
|
|
#include <QImage>
|
|
|
|
#include <QKeyEvent>
|
2018-03-07 01:18:01 +01:00
|
|
|
#include <QLabel>
|
2015-04-22 19:33:53 +02:00
|
|
|
#include <QListIterator>
|
|
|
|
#include <QMenu>
|
2018-03-07 01:18:01 +01:00
|
|
|
#include <QPlainTextEdit>
|
|
|
|
#include <QPushButton>
|
|
|
|
#include <QVBoxLayout>
|
2015-04-22 19:33:53 +02:00
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <functional>
|
2018-03-07 01:18:01 +01:00
|
|
|
#include <initializer_list>
|
2015-04-22 19:33:53 +02:00
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
using namespace std;
|
2019-06-10 22:49:46 +02:00
|
|
|
using namespace CppUtilities;
|
|
|
|
using namespace QtUtilities;
|
2018-03-06 23:10:13 +01:00
|
|
|
using namespace TagParser;
|
2015-04-22 19:33:53 +02:00
|
|
|
|
|
|
|
namespace QtGui {
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \class QtGui::TagFieldEdit
|
|
|
|
* \brief The TagFieldEdit widget allows the user to edit a specified tag field.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Constructs a new TagFieldEdit.
|
|
|
|
* \sa setTagField()
|
|
|
|
*/
|
2018-03-07 01:18:01 +01:00
|
|
|
TagFieldEdit::TagFieldEdit(const QList<TagParser::Tag *> &tags, TagParser::KnownField field, QWidget *parent)
|
|
|
|
: QWidget(parent)
|
|
|
|
, m_layout(new QVBoxLayout(this))
|
|
|
|
, m_tags(&tags)
|
|
|
|
, m_field(field)
|
|
|
|
, m_dataType(determineDataType())
|
|
|
|
, m_lineEdit(nullptr)
|
|
|
|
, m_comboBox(nullptr)
|
2019-06-10 22:49:46 +02:00
|
|
|
, m_spinBoxes(QPair<QtUtilities::ClearSpinBox *, QtUtilities::ClearSpinBox *>(nullptr, nullptr))
|
2018-03-07 01:18:01 +01:00
|
|
|
, m_pictureSelection(nullptr)
|
|
|
|
, m_plainTextEdit(nullptr)
|
|
|
|
, m_descriptionLineEdit(nullptr)
|
2020-06-09 23:27:45 +02:00
|
|
|
, m_restoreAction(nullptr)
|
|
|
|
, m_lockAction(nullptr)
|
2019-05-28 23:59:38 +02:00
|
|
|
, m_isLocked(false)
|
2015-04-22 19:33:53 +02:00
|
|
|
{
|
|
|
|
m_layout->setContentsMargins(QMargins());
|
|
|
|
setLayout(m_layout);
|
|
|
|
setupUi();
|
|
|
|
updateValue();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Assigns the specified \a tags and sets the specified \a fields using the given \a previousValueHandling.
|
|
|
|
*
|
|
|
|
* If \a preventUiUpdate is true, the UI will not be updated.
|
|
|
|
*/
|
2018-03-07 01:18:01 +01:00
|
|
|
void TagFieldEdit::setTagField(
|
|
|
|
const QList<Tag *> &tags, TagParser::KnownField field, PreviousValueHandling previousValueHandling, bool preventUiUpdate)
|
2015-04-22 19:33:53 +02:00
|
|
|
{
|
|
|
|
bool uiRebuildingRequired = false;
|
|
|
|
m_tags = &tags;
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_field != field) {
|
2015-04-22 19:33:53 +02:00
|
|
|
m_field = field;
|
|
|
|
uiRebuildingRequired = true;
|
|
|
|
}
|
2018-07-14 22:59:10 +02:00
|
|
|
if (!tags.empty()) {
|
|
|
|
const auto proposedDataType = determineDataType();
|
2018-03-07 01:18:01 +01:00
|
|
|
if (proposedDataType != m_dataType) {
|
2015-04-22 19:33:53 +02:00
|
|
|
m_dataType = proposedDataType;
|
|
|
|
uiRebuildingRequired = true;
|
|
|
|
}
|
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
if (!preventUiUpdate) {
|
|
|
|
if (uiRebuildingRequired) {
|
2015-04-22 19:33:53 +02:00
|
|
|
setupUi();
|
|
|
|
}
|
|
|
|
updateValue(previousValueHandling);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-03 22:21:15 +01:00
|
|
|
/*!
|
|
|
|
* \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;
|
2018-03-07 01:18:01 +01:00
|
|
|
switch (m_dataType) {
|
2022-08-13 15:42:52 +02:00
|
|
|
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;
|
|
|
|
case TagDataType::Binary:
|
|
|
|
case TagDataType::Picture:
|
|
|
|
break;
|
|
|
|
default:
|
2018-03-07 01:18:01 +01:00
|
|
|
switch (m_field) {
|
2016-03-03 22:21:15 +01:00
|
|
|
case KnownField::Genre:
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_comboBox) {
|
2016-03-03 22:21:15 +01:00
|
|
|
value.assignText(Utility::qstringToString(m_comboBox->currentText(), encoding), encoding);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case KnownField::Lyrics:
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_plainTextEdit) {
|
2016-03-03 22:21:15 +01:00
|
|
|
value.assignText(Utility::qstringToString(m_plainTextEdit->toPlainText(), encoding), encoding);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_lineEdit) {
|
2016-03-03 22:21:15 +01:00
|
|
|
value.assignText(Utility::qstringToString(m_lineEdit->text(), encoding), encoding);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2022-08-13 15:42:52 +02:00
|
|
|
;
|
2016-03-03 22:21:15 +01:00
|
|
|
}
|
2018-07-14 22:59:10 +02:00
|
|
|
// setup description line edit
|
2018-07-14 23:32:10 +02:00
|
|
|
if (m_descriptionLineEdit && m_descriptionLineEdit->isEnabled() && includeDescription) {
|
2016-03-03 22:21:15 +01:00
|
|
|
value.setDescription(Utility::qstringToString(m_descriptionLineEdit->text(), encoding), encoding);
|
|
|
|
}
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2015-04-22 19:33:53 +02:00
|
|
|
/*!
|
|
|
|
* \brief Sets the \a value of the current tag field manually using the given \a previousValueHandling.
|
2018-05-13 20:42:16 +02:00
|
|
|
*
|
|
|
|
* Used for editing tags programmatically, eg. in TagEditorWidget::insertTitleFromFilename() and DbQueryWidget::applyResults().
|
2015-04-22 19:33:53 +02:00
|
|
|
*/
|
|
|
|
bool TagFieldEdit::setValue(const TagValue &value, PreviousValueHandling previousValueHandling)
|
|
|
|
{
|
2019-06-01 12:53:44 +02:00
|
|
|
bool updated = updateValue(value, previousValueHandling, false);
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_pictureSelection) {
|
2019-06-01 12:53:44 +02:00
|
|
|
updated = m_pictureSelection->setValue(value, previousValueHandling) || updated;
|
2016-03-06 17:52:33 +01:00
|
|
|
}
|
2019-06-01 12:53:44 +02:00
|
|
|
return updated;
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns whether at least one of the assigned tags supports a description for the current field.
|
|
|
|
*/
|
|
|
|
bool TagFieldEdit::hasDescription() const
|
|
|
|
{
|
2018-07-14 22:59:10 +02:00
|
|
|
for (const Tag *const tag : tags()) {
|
2018-03-07 01:18:01 +01:00
|
|
|
if (tag->supportsDescription(m_field)) {
|
2015-04-22 19:33:53 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns an indication whether the specified \a field can be applied.
|
|
|
|
*/
|
|
|
|
bool TagFieldEdit::canApply(KnownField field) const
|
|
|
|
{
|
2018-07-14 22:59:10 +02:00
|
|
|
for (const Tag *const tag : tags()) {
|
2018-03-07 01:18:01 +01:00
|
|
|
switch (tag->type()) {
|
2015-04-22 19:33:53 +02:00
|
|
|
case TagType::Id3v1Tag:
|
2018-03-11 18:58:20 +01:00
|
|
|
if (Settings::values().tagPocessing.creationSettings.id3v1usage == TagUsage::Never) {
|
2015-04-22 19:33:53 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case TagType::Id3v2Tag:
|
2018-03-11 18:58:20 +01:00
|
|
|
if (Settings::values().tagPocessing.creationSettings.id3v2usage == TagUsage::Never) {
|
2015-04-22 19:33:53 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
break;
|
2018-03-07 01:18:01 +01:00
|
|
|
default:;
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
if (tag->supportsField(field)) {
|
2015-04-22 19:33:53 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-06-19 13:46:01 +02:00
|
|
|
/*!
|
|
|
|
* \brief Sets whether the tag field edit is locked to keep the current value when switching files.
|
|
|
|
*/
|
2019-05-28 23:59:38 +02:00
|
|
|
void TagFieldEdit::setLocked(bool locked)
|
|
|
|
{
|
|
|
|
if (locked == m_isLocked) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_isLocked = locked;
|
2020-06-09 23:27:45 +02:00
|
|
|
if (m_lockAction) {
|
|
|
|
m_lockAction->setIcon(QIcon::fromTheme(locked ? QStringLiteral("object-locked") : QStringLiteral("object-unlocked")));
|
|
|
|
m_lockAction->setToolTip(
|
2019-05-28 23:59:38 +02:00
|
|
|
locked ? tr("Keep previous value only if not present in the next file") : tr("Keep previous value even if present in next file"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-16 21:01:01 +02:00
|
|
|
/*!
|
|
|
|
* \brief Sets whether the cover buttons are hidden.
|
|
|
|
*/
|
|
|
|
void TagFieldEdit::setCoverButtonsHidden(bool hideCoverButtons)
|
|
|
|
{
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_pictureSelection) {
|
2016-05-16 21:01:01 +02:00
|
|
|
m_pictureSelection->setCoverButtonsHidden(hideCoverButtons);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-22 19:33:53 +02:00
|
|
|
/*!
|
|
|
|
* \brief Internally called to determine the data type of the current tag field.
|
|
|
|
*/
|
|
|
|
TagDataType TagFieldEdit::determineDataType()
|
|
|
|
{
|
2022-06-19 14:25:12 +02:00
|
|
|
auto proposedDataType = TagDataType::Undefined;
|
|
|
|
for (const auto *const tag : tags()) {
|
|
|
|
auto type = tag->proposedDataType(m_field);
|
2018-03-07 01:18:01 +01:00
|
|
|
if (proposedDataType == TagDataType::Undefined) {
|
2015-04-22 19:33:53 +02:00
|
|
|
proposedDataType = type;
|
2018-03-07 01:18:01 +01:00
|
|
|
} else if ((proposedDataType == TagDataType::PositionInSet && type == TagDataType::Integer)
|
|
|
|
|| (type == TagDataType::PositionInSet && proposedDataType == TagDataType::Integer)) {
|
2015-04-22 19:33:53 +02:00
|
|
|
proposedDataType = TagDataType::PositionInSet; // PositionInSet and Number can be considered as compatible
|
2018-03-07 01:18:01 +01:00
|
|
|
} else if (type == TagDataType::Undefined || type != proposedDataType) {
|
2015-04-22 19:33:53 +02:00
|
|
|
return TagDataType::Undefined; // undefined or different (incompatible) types proposed
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return proposedDataType;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Internally called to setup the UI.
|
|
|
|
*
|
|
|
|
* Causes the previous UI widgets to be destroyed. Any unapplied changes are lost.
|
|
|
|
*/
|
|
|
|
void TagFieldEdit::setupUi()
|
|
|
|
{
|
|
|
|
// remove previous widgets
|
|
|
|
m_lineEdit = nullptr;
|
|
|
|
m_comboBox = nullptr;
|
|
|
|
m_spinBoxes.first = nullptr;
|
|
|
|
m_spinBoxes.second = nullptr;
|
|
|
|
m_pictureSelection = nullptr;
|
|
|
|
m_plainTextEdit = nullptr;
|
|
|
|
m_descriptionLineEdit = nullptr;
|
|
|
|
qDeleteAll(m_widgets);
|
|
|
|
m_widgets.clear();
|
|
|
|
// setup widgets
|
2018-03-07 01:18:01 +01:00
|
|
|
switch (m_dataType) {
|
2015-04-22 19:33:53 +02:00
|
|
|
case TagDataType::Picture:
|
|
|
|
setupPictureSelection();
|
|
|
|
break;
|
|
|
|
case TagDataType::Integer:
|
|
|
|
setupSpinBox();
|
|
|
|
break;
|
|
|
|
case TagDataType::PositionInSet:
|
|
|
|
setupPositionInSetSpinBoxes();
|
|
|
|
break;
|
|
|
|
case TagDataType::StandardGenreIndex:
|
|
|
|
setupGenreComboBox();
|
|
|
|
break;
|
|
|
|
case TagDataType::Binary:
|
|
|
|
setupFileSelection();
|
|
|
|
break;
|
|
|
|
default:
|
2022-08-13 15:42:52 +02:00
|
|
|
switch (m_field) {
|
|
|
|
case KnownField::Genre:
|
|
|
|
setupGenreComboBox();
|
|
|
|
break;
|
|
|
|
case KnownField::Lyrics:
|
|
|
|
setupPlainTextEdit();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
setupLineEdit();
|
|
|
|
}
|
|
|
|
break;
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_dataType != TagDataType::Picture && hasDescription()) { // setup description line edit
|
2015-04-22 19:33:53 +02:00
|
|
|
setupDescriptionLineEdit();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Internally called by setupUi() to setup a line edit.
|
|
|
|
*/
|
|
|
|
ClearLineEdit *TagFieldEdit::setupLineEdit()
|
|
|
|
{
|
|
|
|
m_lineEdit = new ClearLineEdit(this);
|
|
|
|
m_lineEdit->setPlaceholderText(tr("empty"));
|
|
|
|
static_cast<ButtonOverlay *>(m_lineEdit)->setClearButtonEnabled(true);
|
2020-06-09 23:27:45 +02:00
|
|
|
m_lineEdit->addCustomAction(setupLockAction());
|
|
|
|
m_lineEdit->addCustomAction(setupRestoreAction());
|
2015-04-22 19:33:53 +02:00
|
|
|
m_lineEdit->installEventFilter(this);
|
2020-06-09 23:27:45 +02:00
|
|
|
connect(m_lineEdit, &ClearLineEdit::textChanged, this, &TagFieldEdit::showRestoreAction);
|
2015-04-22 19:33:53 +02:00
|
|
|
m_layout->addWidget(m_lineEdit);
|
|
|
|
m_widgets << m_lineEdit;
|
|
|
|
return m_lineEdit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Internally called by setupUi() to setup a plain text edit.
|
|
|
|
*/
|
|
|
|
ClearPlainTextEdit *TagFieldEdit::setupPlainTextEdit()
|
|
|
|
{
|
|
|
|
m_plainTextEdit = new ClearPlainTextEdit(this);
|
2020-10-07 00:27:15 +02:00
|
|
|
m_plainTextEdit->setTabChangesFocus(true);
|
2015-04-22 19:33:53 +02:00
|
|
|
m_plainTextEdit->setClearButtonEnabled(true);
|
2020-06-09 23:27:45 +02:00
|
|
|
m_plainTextEdit->addCustomAction(setupLockAction());
|
|
|
|
m_plainTextEdit->addCustomAction(setupRestoreAction());
|
2019-11-03 23:05:35 +01:00
|
|
|
m_plainTextEdit->setStyleSheet(
|
|
|
|
QStringLiteral("color: ") + QGuiApplication::palette().text().color().name(QColor::HexArgb)); // not sure why this is otherwise gray
|
2020-06-09 23:27:45 +02:00
|
|
|
connect(m_plainTextEdit->document(), &QTextDocument::contentsChanged, this, &TagFieldEdit::showRestoreAction);
|
2015-04-22 19:33:53 +02:00
|
|
|
m_layout->addWidget(m_plainTextEdit);
|
|
|
|
m_widgets << m_plainTextEdit;
|
|
|
|
return m_plainTextEdit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Internally called by setupUi() to setup a combo box.
|
|
|
|
*/
|
|
|
|
ClearComboBox *TagFieldEdit::setupGenreComboBox()
|
|
|
|
{
|
|
|
|
m_comboBox = new ClearComboBox(this);
|
|
|
|
m_comboBox->setEditable(true);
|
2018-03-07 01:18:01 +01:00
|
|
|
if (QLineEdit *lineEdit = m_comboBox->lineEdit()) {
|
2015-04-22 19:33:53 +02:00
|
|
|
lineEdit->setPlaceholderText(tr("empty"));
|
|
|
|
}
|
2018-07-14 22:59:10 +02:00
|
|
|
m_comboBox->addItems(QStringList({ QString(), tr("Blues"), tr("A capella"), tr("Abstract"), tr("Acid"), tr("Acid Jazz"), tr("Acid Punk"),
|
|
|
|
tr("Acoustic"), tr("Alternative"), tr("Alternative Rock"), tr("Ambient"), tr("Anime"), tr("Art Rock"), tr("Audio Theatre"), tr("Audiobook"),
|
|
|
|
tr("Avantgarde"), tr("Ballad"), tr("Baroque"), tr("Bass"), tr("Beat"), tr("Bebop"), tr("Bhangra"), tr("Big Band"), tr("Big Beat"),
|
|
|
|
tr("Black Metal"), tr("Bluegrass"), tr("Booty Bass"), tr("Breakbeat"), tr("BritPop"), tr("Cabaret"), tr("Celtic"), tr("Chamber Music"),
|
|
|
|
tr("Chanson"), tr("Chillout"), tr("Chorus"), tr("Christian Gangsta Rap"), tr("Christian Rap"), tr("Christian Rock"), tr("Classic Rock"),
|
|
|
|
tr("Classical"), tr("Club"), tr("Club-House"), tr("Comedy"), tr("Contemporary Christian"), tr("Country"), tr("Crossover"), tr("Cult"),
|
|
|
|
tr("Dance"), tr("Dance Hall"), tr("Darkwave"), tr("Death Metal"), tr("Disco"), tr("Downtempo"), tr("Dream"), tr("Drum & Bass"),
|
|
|
|
tr("Drum Solo"), tr("Dub"), tr("Dubstep"), tr("Duet"), tr("Easy Listening"), tr("EBM"), tr("Eclectic"), tr("Electro"), tr("Electroclash"),
|
|
|
|
tr("Electronic"), tr("Emo"), tr("Ethnic"), tr("Euro-House"), tr("Euro-Techno"), tr("Eurodance"), tr("Experimental"), tr("Fast Fusion"),
|
|
|
|
tr("Folk"), tr("Folk-Rock"), tr("Folklore"), tr("Freestyle"), tr("Funk"), tr("Fusion"), tr("G-Funk"), tr("Game"), tr("Gangsta"), tr("Garage"),
|
|
|
|
tr("Garage Rock"), tr("Global"), tr("Goa"), tr("Gospel"), tr("Gothic"), tr("Gothic Rock"), tr("Grunge"), tr("Hard Rock"),
|
|
|
|
tr("Hardcore Techno"), tr("Heavy Metal"), tr("Hip-Hop"), tr("House"), tr("Humour"), tr("IDM"), tr("Illbient"), tr("Indie"), tr("Indie Rock"),
|
|
|
|
tr("Industrial"), tr("Industro-Goth"), tr("Instrumental"), tr("Instrumental Pop"), tr("Instrumental Rock"), tr("Jam Band"), tr("Jazz"),
|
|
|
|
tr("Jazz & Funk"), tr("Jpop"), tr("Jungle"), tr("Krautrock"), tr("Latin"), tr("Leftfield"), tr("Lo-Fi"), tr("Lounge"), tr("Math Rock"),
|
|
|
|
tr("Meditative"), tr("Merengue"), tr("Metal"), tr("Musical"), tr("National Folk"), tr("Native US"), tr("Negerpunk"), tr("Neoclassical"),
|
|
|
|
tr("Neue Deutsche Welle"), tr("New Age"), tr("New Romantic"), tr("New Wave"), tr("Noise"), tr("Nu-Breakz"), tr("Oldies"), tr("Opera"),
|
|
|
|
tr("Podcast"), tr("Polka"), tr("Polsk Punk"), tr("Pop"), tr("Pop-Folk"), tr("Pop/Funk"), tr("Porn Groove"), tr("Post-Punk"), tr("Post-Rock"),
|
|
|
|
tr("Power Ballad"), tr("Pranks"), tr("Primus"), tr("Progressive Rock"), tr("Psychedelic"), tr("Psychedelic Rock"), tr("Psytrance"),
|
|
|
|
tr("Punk"), tr("Punk Rock"), tr("Rap"), tr("Rave"), tr("Reggae"), tr("Retro"), tr("Revival"), tr("Rhythmic Soul"), tr("Rock"),
|
|
|
|
tr("Rock & Roll"), tr("Salsa"), tr("Samba"), tr("Satire"), tr("Shoegaze"), tr("Showtunes"), tr("Ska"), tr("Slow Jam"), tr("Slow Rock"),
|
|
|
|
tr("Sonata"), tr("Soul"), tr("Sound Clip"), tr("Soundtrack"), tr("Southern Rock"), tr("Space"), tr("Space Rock"), tr("Speech"), tr("Swing"),
|
|
|
|
tr("Symphonic Rock"), tr("Symphony"), tr("Synthpop"), tr("Tango"), tr("Techno"), tr("Techno-Industrial"), tr("Terror"), tr("Thrash Metal"),
|
|
|
|
tr("Top 40"), tr("Trailer"), tr("Trance"), tr("Tribal"), tr("Trip-Hop"), tr("Trop Rock"), tr("Vocal"), tr("World Music") }));
|
2015-04-22 19:33:53 +02:00
|
|
|
m_comboBox->setCurrentIndex(0);
|
|
|
|
m_comboBox->setClearButtonEnabled(true);
|
2020-06-09 23:27:45 +02:00
|
|
|
m_comboBox->addCustomAction(setupLockAction());
|
|
|
|
m_comboBox->addCustomAction(setupRestoreAction());
|
2015-04-22 19:33:53 +02:00
|
|
|
m_comboBox->installEventFilter(this);
|
2016-03-05 16:50:23 +01:00
|
|
|
m_comboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
2020-06-09 23:27:45 +02:00
|
|
|
connect(m_comboBox, &ClearComboBox::currentTextChanged, this, &TagFieldEdit::showRestoreAction);
|
2015-04-22 19:33:53 +02:00
|
|
|
|
|
|
|
m_layout->addWidget(m_comboBox);
|
|
|
|
m_widgets << m_comboBox;
|
|
|
|
return m_comboBox;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Internally called by setupUi() to setup a single spin box.
|
|
|
|
*/
|
|
|
|
ClearSpinBox *TagFieldEdit::setupSpinBox()
|
|
|
|
{
|
|
|
|
m_spinBoxes.first = new ClearSpinBox(this);
|
|
|
|
m_spinBoxes.first->setPlaceholderText(tr("empty"));
|
|
|
|
m_spinBoxes.first->setMinimumHidden(true);
|
|
|
|
m_spinBoxes.first->setClearButtonEnabled(true);
|
2020-06-09 23:27:45 +02:00
|
|
|
m_spinBoxes.first->addCustomAction(setupLockAction());
|
|
|
|
m_spinBoxes.first->addCustomAction(setupRestoreAction());
|
2015-04-22 19:33:53 +02:00
|
|
|
m_spinBoxes.first->installEventFilter(this);
|
|
|
|
m_spinBoxes.first->setMaximum(32766);
|
2021-04-13 21:24:08 +02:00
|
|
|
m_spinBoxes.first->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
2020-06-09 23:27:45 +02:00
|
|
|
connect(m_spinBoxes.first, static_cast<void (ClearSpinBox::*)(int)>(&ClearSpinBox::valueChanged), this, &TagFieldEdit::showRestoreAction);
|
2015-04-22 19:33:53 +02:00
|
|
|
m_layout->addWidget(m_spinBoxes.first);
|
|
|
|
m_widgets << m_spinBoxes.first;
|
|
|
|
return m_spinBoxes.first;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Internally called by setupUi() to setup a a pair of spin boxes.
|
|
|
|
*/
|
2019-06-10 22:49:46 +02:00
|
|
|
QPair<QtUtilities::ClearSpinBox *, QtUtilities::ClearSpinBox *> &TagFieldEdit::setupPositionInSetSpinBoxes()
|
2015-04-22 19:33:53 +02:00
|
|
|
{
|
2018-07-14 22:59:10 +02:00
|
|
|
auto *const subLayout = new QHBoxLayout;
|
2015-04-22 19:33:53 +02:00
|
|
|
|
|
|
|
m_spinBoxes.first = new ClearSpinBox(this);
|
|
|
|
m_spinBoxes.first->setPlaceholderText(tr("empty"));
|
|
|
|
m_spinBoxes.first->setMinimumHidden(true);
|
|
|
|
m_spinBoxes.first->setClearButtonEnabled(true);
|
|
|
|
m_spinBoxes.first->installEventFilter(this);
|
|
|
|
m_spinBoxes.first->setMaximum(32766);
|
2020-03-09 19:05:24 +01:00
|
|
|
m_spinBoxes.first->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
2020-06-09 23:27:45 +02:00
|
|
|
connect(m_spinBoxes.first, static_cast<void (ClearSpinBox::*)(int)>(&ClearSpinBox::valueChanged), this, &TagFieldEdit::showRestoreAction);
|
2015-04-22 19:33:53 +02:00
|
|
|
subLayout->addWidget(m_spinBoxes.first);
|
|
|
|
m_widgets << m_spinBoxes.first;
|
|
|
|
|
2018-07-14 22:59:10 +02:00
|
|
|
auto *const label = new QLabel(tr("of"), this);
|
2015-04-22 19:33:53 +02:00
|
|
|
subLayout->addWidget(label);
|
|
|
|
m_widgets << label;
|
|
|
|
|
|
|
|
m_spinBoxes.second = new ClearSpinBox(this);
|
|
|
|
m_spinBoxes.second->setPlaceholderText(tr("empty"));
|
|
|
|
m_spinBoxes.second->setMinimumHidden(true);
|
|
|
|
m_spinBoxes.second->setClearButtonEnabled(true);
|
|
|
|
m_spinBoxes.second->installEventFilter(this);
|
|
|
|
m_spinBoxes.second->setMaximum(32766);
|
2020-06-09 23:27:45 +02:00
|
|
|
m_spinBoxes.second->addCustomAction(setupLockAction());
|
|
|
|
m_spinBoxes.second->addCustomAction(setupRestoreAction());
|
2020-03-09 19:05:24 +01:00
|
|
|
m_spinBoxes.second->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
2020-06-09 23:27:45 +02:00
|
|
|
connect(m_spinBoxes.second, static_cast<void (ClearSpinBox::*)(int)>(&ClearSpinBox::valueChanged), this, &TagFieldEdit::showRestoreAction);
|
2015-04-22 19:33:53 +02:00
|
|
|
subLayout->addWidget(m_spinBoxes.second);
|
|
|
|
m_widgets << m_spinBoxes.second;
|
|
|
|
|
|
|
|
m_layout->addLayout(subLayout);
|
|
|
|
return m_spinBoxes;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Internally called by setupUi() to setup a PicturePreviewSelection widget.
|
|
|
|
*/
|
|
|
|
PicturePreviewSelection *TagFieldEdit::setupPictureSelection()
|
|
|
|
{
|
|
|
|
m_pictureSelection = new PicturePreviewSelection(nullptr, KnownField::Invalid, this);
|
2020-06-09 23:27:45 +02:00
|
|
|
connect(m_pictureSelection, &PicturePreviewSelection::pictureChanged, this, &TagFieldEdit::showRestoreAction);
|
2015-04-22 19:33:53 +02:00
|
|
|
m_layout->addWidget(m_pictureSelection);
|
|
|
|
m_widgets << m_pictureSelection;
|
|
|
|
return m_pictureSelection;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Internally called by setupUi() to setup a file selection.
|
|
|
|
*/
|
|
|
|
QWidget *TagFieldEdit::setupFileSelection()
|
|
|
|
{
|
|
|
|
return setupTypeNotSupportedLabel();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Internally called by setupUi() to setup a line edit for the description.
|
|
|
|
*/
|
|
|
|
ClearLineEdit *TagFieldEdit::setupDescriptionLineEdit()
|
|
|
|
{
|
2018-07-14 22:59:10 +02:00
|
|
|
auto *const label = new QLabel(tr("Description"), this);
|
2015-04-22 19:33:53 +02:00
|
|
|
m_layout->addWidget(label);
|
|
|
|
|
|
|
|
m_widgets << label;
|
|
|
|
m_descriptionLineEdit = new ClearLineEdit(this);
|
|
|
|
m_descriptionLineEdit->setPlaceholderText(tr("empty"));
|
|
|
|
static_cast<ButtonOverlay *>(m_descriptionLineEdit)->setClearButtonEnabled(true);
|
|
|
|
m_descriptionLineEdit->installEventFilter(this);
|
2020-06-09 23:27:45 +02:00
|
|
|
connect(m_descriptionLineEdit, &ClearLineEdit::textChanged, this, &TagFieldEdit::showRestoreAction);
|
2015-04-22 19:33:53 +02:00
|
|
|
|
|
|
|
m_layout->addWidget(m_descriptionLineEdit);
|
|
|
|
m_widgets << m_descriptionLineEdit;
|
|
|
|
return m_descriptionLineEdit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Internally called by setupUi() to setup a label indicating that the current tag field is not supported by the edit.
|
|
|
|
*/
|
|
|
|
QLabel *TagFieldEdit::setupTypeNotSupportedLabel()
|
|
|
|
{
|
2019-02-13 19:09:13 +01:00
|
|
|
auto *const label = new QLabel(tr("no widget for editing this field type available"), this);
|
2015-04-22 19:33:53 +02:00
|
|
|
m_layout->addWidget(label);
|
|
|
|
m_widgets << label;
|
|
|
|
return label;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2016-03-03 22:21:15 +01:00
|
|
|
* \brief Updates the currently shown value manually.
|
2015-04-22 19:33:53 +02:00
|
|
|
* \param previousValueHandling Specifies how to deal with the previous value.
|
|
|
|
*
|
|
|
|
* The new value is read from the assigned tag(s).
|
|
|
|
*/
|
2019-06-01 12:53:44 +02:00
|
|
|
bool TagFieldEdit::updateValue(PreviousValueHandling previousValueHandling)
|
2015-04-22 19:33:53 +02:00
|
|
|
{
|
2018-08-12 22:13:28 +02:00
|
|
|
// use the values from the last tag which has the specified field
|
|
|
|
for (auto i = tags().crbegin(), end = tags().crend(); i != end; ++i) {
|
2018-07-22 23:21:29 +02:00
|
|
|
// FIXME: use tag->values(m_field) and handle all values
|
2018-08-12 22:13:28 +02:00
|
|
|
auto *const tag = *i;
|
|
|
|
const TagValue &value = tag->value(m_field);
|
|
|
|
if (value.isEmpty()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-06-01 12:53:44 +02:00
|
|
|
bool updated = updateValue(value, previousValueHandling);
|
2018-08-12 22:13:28 +02:00
|
|
|
if (m_pictureSelection) {
|
2019-06-01 12:53:44 +02:00
|
|
|
updated = m_pictureSelection->setTagField(tag, m_field, previousValueHandling) || updated;
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
2019-06-01 12:53:44 +02:00
|
|
|
return updated;
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
2018-08-12 22:13:28 +02:00
|
|
|
|
|
|
|
// set an empty value
|
2019-06-01 12:53:44 +02:00
|
|
|
bool updated = updateValue(TagValue(), previousValueHandling);
|
2018-08-12 22:13:28 +02:00
|
|
|
if (m_pictureSelection) {
|
|
|
|
// pass the last tag if present so the picture selection can operate on that tag instance and won't be disabled
|
2019-06-01 12:53:44 +02:00
|
|
|
updated = m_pictureSelection->setTagField(m_tags->isEmpty() ? nullptr : m_tags->back(), m_field, previousValueHandling) || updated;
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
2019-06-01 12:53:44 +02:00
|
|
|
return updated;
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2016-03-03 22:21:15 +01:00
|
|
|
* \brief Updates the currently shown value manually.
|
2015-04-22 19:33:53 +02:00
|
|
|
* \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.
|
|
|
|
*/
|
2019-06-01 12:53:44 +02:00
|
|
|
bool TagFieldEdit::updateValue(Tag *tag, PreviousValueHandling previousValueHandling)
|
2015-04-22 19:33:53 +02:00
|
|
|
{
|
2019-06-01 12:53:44 +02:00
|
|
|
bool updated = updateValue(tag ? tag->value(m_field) : TagValue::empty(), previousValueHandling);
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_pictureSelection) {
|
2019-06-01 12:53:44 +02:00
|
|
|
updated = m_pictureSelection->setTagField(tag, m_field, previousValueHandling) || updated;
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
2019-06-01 12:53:44 +02:00
|
|
|
return updated;
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2016-03-03 22:21:15 +01:00
|
|
|
* \brief Updates the currently shown value manually.
|
2015-04-22 19:33:53 +02:00
|
|
|
* \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.
|
2016-03-06 17:52:33 +01:00
|
|
|
* \remarks Does not update the picture preview selection.
|
2015-04-22 19:33:53 +02:00
|
|
|
*/
|
2019-06-01 12:53:44 +02:00
|
|
|
bool TagFieldEdit::updateValue(const TagValue &value, PreviousValueHandling previousValueHandling, bool updateRestoreButton)
|
2015-04-22 19:33:53 +02:00
|
|
|
{
|
2019-06-01 12:53:44 +02:00
|
|
|
bool autoCorrectionApplied = false;
|
2015-04-22 19:33:53 +02:00
|
|
|
bool conversionError = false;
|
|
|
|
bool updated = false;
|
|
|
|
concretizePreviousValueHandling(previousValueHandling);
|
2018-07-22 23:21:29 +02:00
|
|
|
|
|
|
|
// setup widget for editing value
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_lineEdit || m_comboBox || m_plainTextEdit) {
|
2018-07-22 23:21:29 +02:00
|
|
|
const auto text([&] {
|
|
|
|
try {
|
2021-03-20 21:59:49 +01:00
|
|
|
const auto textValue = Utility::tagValueToQString(value);
|
|
|
|
const auto correctedText = applyAutoCorrection(textValue);
|
|
|
|
if (correctedText != textValue) {
|
2019-06-01 12:53:44 +02:00
|
|
|
autoCorrectionApplied = true;
|
|
|
|
}
|
|
|
|
return correctedText;
|
2018-07-22 23:21:29 +02:00
|
|
|
} catch (const ConversionException &) {
|
|
|
|
conversionError = true;
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
}());
|
2019-05-28 23:59:38 +02:00
|
|
|
if ((!m_isLocked || text.isEmpty()) && (previousValueHandling == PreviousValueHandling::Clear || !text.isEmpty())) {
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_lineEdit && (previousValueHandling != PreviousValueHandling::Keep || m_lineEdit->isCleared())) {
|
2015-04-22 19:33:53 +02:00
|
|
|
m_lineEdit->setText(text);
|
|
|
|
updated = true;
|
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_comboBox && (previousValueHandling != PreviousValueHandling::Keep || m_comboBox->isCleared())) {
|
2015-04-22 19:33:53 +02:00
|
|
|
m_comboBox->setCurrentText(text);
|
|
|
|
updated = true;
|
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_plainTextEdit && (previousValueHandling != PreviousValueHandling::Keep || m_plainTextEdit->isCleared())) {
|
2015-04-22 19:33:53 +02:00
|
|
|
m_plainTextEdit->setPlainText(text);
|
|
|
|
updated = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_spinBoxes.first) {
|
|
|
|
if (m_spinBoxes.second) {
|
2018-07-22 23:21:29 +02:00
|
|
|
const auto pos([&] {
|
|
|
|
try {
|
|
|
|
return value.toPositionInSet();
|
|
|
|
} catch (const ConversionException &) {
|
|
|
|
conversionError = true;
|
|
|
|
return PositionInSet();
|
|
|
|
}
|
|
|
|
}());
|
2019-05-28 23:59:38 +02:00
|
|
|
if (!m_isLocked || !pos.position()) {
|
|
|
|
if (previousValueHandling == PreviousValueHandling::Clear || pos.position()) {
|
|
|
|
if (previousValueHandling != PreviousValueHandling::Keep || m_spinBoxes.first->isCleared()) {
|
|
|
|
m_spinBoxes.first->setValue(pos.position());
|
|
|
|
updated = true;
|
|
|
|
}
|
|
|
|
} else if (previousValueHandling == PreviousValueHandling::IncrementUpdate && !m_spinBoxes.first->isCleared()) {
|
|
|
|
m_spinBoxes.first->setValue(m_spinBoxes.first->value() + 1);
|
2015-04-22 19:33:53 +02:00
|
|
|
updated = true;
|
|
|
|
}
|
|
|
|
}
|
2019-05-28 23:59:38 +02:00
|
|
|
if (!m_isLocked || !pos.total()) {
|
|
|
|
if (previousValueHandling == PreviousValueHandling::Clear || pos.total()) {
|
|
|
|
if (previousValueHandling != PreviousValueHandling::Keep || m_spinBoxes.second->isCleared()) {
|
|
|
|
m_spinBoxes.second->setValue(pos.total());
|
|
|
|
updated = true;
|
|
|
|
}
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2018-07-22 23:21:29 +02:00
|
|
|
const auto num([&] {
|
|
|
|
try {
|
|
|
|
return value.toInteger();
|
|
|
|
} catch (const ConversionException &) {
|
|
|
|
conversionError = true;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}());
|
2019-05-28 23:59:38 +02:00
|
|
|
if (!m_isLocked || !num) {
|
|
|
|
if (previousValueHandling == PreviousValueHandling::Clear || num) {
|
|
|
|
if (previousValueHandling != PreviousValueHandling::Keep || m_spinBoxes.first->isCleared()) {
|
|
|
|
m_spinBoxes.first->setValue(num);
|
|
|
|
updated = true;
|
|
|
|
}
|
|
|
|
} else if (previousValueHandling == PreviousValueHandling::IncrementUpdate && !m_spinBoxes.first->isCleared()) {
|
|
|
|
m_spinBoxes.first->setValue(m_spinBoxes.first->value() + 1);
|
2015-04-22 19:33:53 +02:00
|
|
|
updated = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-07-22 23:21:29 +02:00
|
|
|
|
|
|
|
// setup description line edit
|
2018-07-14 23:32:10 +02:00
|
|
|
const auto shouldHaveDescriptionLineEdit = m_dataType != TagDataType::Picture && hasDescription();
|
|
|
|
if (shouldHaveDescriptionLineEdit) {
|
|
|
|
if (!m_descriptionLineEdit) {
|
|
|
|
setupDescriptionLineEdit();
|
|
|
|
}
|
|
|
|
m_descriptionLineEdit->setEnabled(true);
|
|
|
|
if (previousValueHandling != PreviousValueHandling::Keep || m_descriptionLineEdit->isCleared()) {
|
|
|
|
try {
|
2019-06-01 12:53:44 +02:00
|
|
|
const auto desc = Utility::stringToQString(value.description(), value.descriptionEncoding());
|
|
|
|
const auto correctedDesc = applyAutoCorrection(desc);
|
|
|
|
if (correctedDesc != desc) {
|
|
|
|
autoCorrectionApplied = true;
|
|
|
|
}
|
|
|
|
if (!m_isLocked || correctedDesc.isEmpty()) {
|
|
|
|
m_descriptionLineEdit->setText(correctedDesc);
|
2019-05-28 23:59:38 +02:00
|
|
|
}
|
2018-07-14 23:32:10 +02:00
|
|
|
} catch (const ConversionException &) {
|
|
|
|
conversionError = true;
|
2019-05-28 23:59:38 +02:00
|
|
|
if (!m_isLocked) {
|
|
|
|
m_descriptionLineEdit->clear();
|
|
|
|
}
|
2018-07-14 23:32:10 +02:00
|
|
|
}
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
2018-07-14 23:32:10 +02:00
|
|
|
} else if (m_descriptionLineEdit) {
|
|
|
|
m_descriptionLineEdit->setEnabled(false);
|
|
|
|
m_descriptionLineEdit->clear();
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
2020-06-09 23:27:45 +02:00
|
|
|
if (updateRestoreButton && m_restoreAction) {
|
|
|
|
m_restoreAction->setVisible((!updated && m_restoreAction->isVisible()) || m_tags->size() > 1);
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
2019-05-28 23:59:38 +02:00
|
|
|
if (updated) {
|
|
|
|
setLocked(false);
|
|
|
|
}
|
2019-06-01 12:53:44 +02:00
|
|
|
m_autoCorrectionApplied = updated && autoCorrectionApplied;
|
2018-07-22 23:21:29 +02:00
|
|
|
|
|
|
|
// setup info button
|
|
|
|
const auto widgets = initializer_list<ButtonOverlay *>{ m_lineEdit, m_comboBox, m_spinBoxes.first, m_spinBoxes.second };
|
2018-07-14 22:59:10 +02:00
|
|
|
const auto canApplyField = canApply(m_field);
|
2018-07-22 23:21:29 +02:00
|
|
|
if (!conversionError && canApplyField) {
|
|
|
|
for (auto *const overlay : widgets) {
|
|
|
|
if (overlay) {
|
|
|
|
overlay->disableInfoButton();
|
|
|
|
}
|
|
|
|
}
|
2019-06-01 12:53:44 +02:00
|
|
|
return updated;
|
2018-07-22 23:21:29 +02:00
|
|
|
}
|
2019-08-13 00:35:04 +02:00
|
|
|
const auto pixmap(QIcon::fromTheme(QStringLiteral("emblem-error")).pixmap(16));
|
2018-07-22 23:21:29 +02:00
|
|
|
const auto text([&] {
|
2021-03-20 21:59:49 +01:00
|
|
|
QString textValue;
|
2018-03-07 01:18:01 +01:00
|
|
|
if (conversionError) {
|
2021-03-20 21:59:49 +01:00
|
|
|
textValue = tr("The value of this field could not be read from the file because it couldn't be converted properly.");
|
2018-07-14 22:59:10 +02:00
|
|
|
if (!canApplyField) {
|
2021-03-20 21:59:49 +01:00
|
|
|
textValue += QChar('\n');
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
}
|
2018-07-14 22:59:10 +02:00
|
|
|
if (!canApplyField) {
|
2021-03-20 21:59:49 +01:00
|
|
|
textValue += tr("The field can not be applied when saving the file and will be lost.");
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
2021-03-20 21:59:49 +01:00
|
|
|
return textValue;
|
2018-07-22 23:21:29 +02:00
|
|
|
}());
|
|
|
|
for (auto *const overlay : widgets) {
|
|
|
|
if (overlay) {
|
|
|
|
overlay->enableInfoButton(pixmap, text);
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
}
|
2019-06-01 12:53:44 +02:00
|
|
|
return updated;
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Internally called by the other setup methods to create the "restore button".
|
|
|
|
*/
|
2020-06-09 23:27:45 +02:00
|
|
|
QAction *TagFieldEdit::setupRestoreAction()
|
2015-04-22 19:33:53 +02:00
|
|
|
{
|
2020-06-09 23:27:45 +02:00
|
|
|
if (m_restoreAction) {
|
|
|
|
return m_restoreAction;
|
2019-05-28 23:59:38 +02:00
|
|
|
}
|
2020-06-09 23:27:45 +02:00
|
|
|
m_restoreAction = new QAction(this);
|
|
|
|
m_restoreAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo")));
|
|
|
|
m_restoreAction->setToolTip(tr("Restore value as it is currently present in the file"));
|
|
|
|
connect(m_restoreAction, &QAction::triggered, this, &TagFieldEdit::handleRestoreButtonClicked);
|
2021-07-03 19:38:27 +02:00
|
|
|
// ownership might be transferred to a child widget/layout
|
2020-06-09 23:27:45 +02:00
|
|
|
connect(m_restoreAction, &QAction::destroyed, this, &TagFieldEdit::handleRestoreButtonDestroyed);
|
|
|
|
return m_restoreAction;
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
|
2019-05-28 23:59:38 +02:00
|
|
|
/*!
|
|
|
|
* \brief Internally called by the other setup methods to create the "lock button".
|
|
|
|
*/
|
2020-06-09 23:27:45 +02:00
|
|
|
QAction *TagFieldEdit::setupLockAction()
|
2019-05-28 23:59:38 +02:00
|
|
|
{
|
2020-06-09 23:27:45 +02:00
|
|
|
if (m_lockAction) {
|
|
|
|
return m_lockAction;
|
2019-05-28 23:59:38 +02:00
|
|
|
}
|
|
|
|
m_isLocked = !m_isLocked;
|
2020-06-09 23:27:45 +02:00
|
|
|
m_lockAction = new QAction(this);
|
2019-05-28 23:59:38 +02:00
|
|
|
setLocked(!m_isLocked);
|
2020-06-09 23:27:45 +02:00
|
|
|
connect(m_lockAction, &QAction::triggered, this, &TagFieldEdit::toggleLocked);
|
2021-07-03 19:38:27 +02:00
|
|
|
// ownership might be transferred to a child widget/layout
|
2020-06-09 23:27:45 +02:00
|
|
|
connect(m_lockAction, &QAction::destroyed, this, &TagFieldEdit::handleLockButtonDestroyed);
|
|
|
|
return m_lockAction;
|
2019-05-28 23:59:38 +02:00
|
|
|
}
|
|
|
|
|
2015-04-22 19:33:53 +02:00
|
|
|
/*!
|
|
|
|
* \brief Internally called to show the restore button (if there is one and at least one tag is assigned).
|
|
|
|
*/
|
2020-06-09 23:27:45 +02:00
|
|
|
void TagFieldEdit::showRestoreAction()
|
2015-04-22 19:33:53 +02:00
|
|
|
{
|
2020-06-09 23:27:45 +02:00
|
|
|
if (m_restoreAction) {
|
|
|
|
m_restoreAction->setVisible(m_tags->size());
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Applies auto correction (according to the settings) for the specified \a textValue.
|
|
|
|
*/
|
2019-06-01 12:53:44 +02:00
|
|
|
QString TagFieldEdit::applyAutoCorrection(const QString &textValue)
|
2015-04-22 19:33:53 +02:00
|
|
|
{
|
2019-06-01 12:53:44 +02:00
|
|
|
QString correctedValue = textValue;
|
2016-10-24 20:15:10 +02:00
|
|
|
const auto &settings = Settings::values().editor.autoCompletition;
|
|
|
|
auto &fields = settings.fields.items();
|
2018-03-07 01:18:01 +01:00
|
|
|
auto i = find_if(fields.constBegin(), fields.constEnd(), [this](const ChecklistItem &item) {
|
2015-04-22 19:33:53 +02:00
|
|
|
bool ok;
|
|
|
|
return (item.id().toInt(&ok) == static_cast<int>(this->field())) && ok;
|
|
|
|
});
|
|
|
|
// if current field is in the list of auto correction fields and auto correction should be applied
|
2018-07-14 22:59:10 +02:00
|
|
|
if (i == fields.constEnd() || !i->isChecked()) {
|
2019-06-01 12:53:44 +02:00
|
|
|
return correctedValue;
|
2018-07-14 22:59:10 +02:00
|
|
|
}
|
|
|
|
if (settings.trimWhitespaces) {
|
2019-06-01 12:53:44 +02:00
|
|
|
correctedValue = correctedValue.trimmed();
|
2018-07-14 22:59:10 +02:00
|
|
|
}
|
|
|
|
if (settings.formatNames) {
|
2019-06-01 12:53:44 +02:00
|
|
|
correctedValue = Utility::formatName(correctedValue);
|
|
|
|
}
|
|
|
|
if (settings.fixUmlauts) {
|
|
|
|
correctedValue = Utility::fixUmlauts(correctedValue);
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
2020-09-08 19:37:23 +02:00
|
|
|
const auto &subst = settings.customSubstitution;
|
|
|
|
if (subst.enabled && subst.regex.isValid()) {
|
|
|
|
correctedValue.replace(subst.regex, subst.replacement);
|
|
|
|
}
|
2019-06-01 12:53:44 +02:00
|
|
|
return correctedValue;
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief If \a previousValueHandling is PreviousValueHandling::Auto it will be set to a concrete value.
|
|
|
|
*/
|
|
|
|
void TagFieldEdit::concretizePreviousValueHandling(PreviousValueHandling &previousValueHandling)
|
|
|
|
{
|
2018-03-07 01:18:01 +01:00
|
|
|
switch (previousValueHandling) {
|
2015-04-22 19:33:53 +02:00
|
|
|
case PreviousValueHandling::Auto:
|
2018-03-07 01:18:01 +01:00
|
|
|
switch (m_field) {
|
2016-03-05 17:03:17 +01:00
|
|
|
// these differ for each song -> always clear previous value
|
2015-04-22 19:33:53 +02:00
|
|
|
case KnownField::Title:
|
2016-03-05 17:03:17 +01:00
|
|
|
case KnownField::Lyrics:
|
2015-04-22 19:33:53 +02:00
|
|
|
previousValueHandling = PreviousValueHandling::Clear;
|
|
|
|
break;
|
|
|
|
// these will be incremented
|
|
|
|
case KnownField::TrackPosition:
|
|
|
|
case KnownField::PartNumber:
|
|
|
|
previousValueHandling = PreviousValueHandling::IncrementUpdate;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
previousValueHandling = PreviousValueHandling::Update;
|
|
|
|
}
|
2018-07-14 22:59:10 +02:00
|
|
|
break;
|
2018-03-07 01:18:01 +01:00
|
|
|
default:;
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Restores the current value to the initial value.
|
|
|
|
*/
|
|
|
|
void TagFieldEdit::restore()
|
|
|
|
{
|
|
|
|
updateValue(PreviousValueHandling::Clear);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Clears the current value.
|
|
|
|
*/
|
|
|
|
void TagFieldEdit::clear()
|
|
|
|
{
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_lineEdit) {
|
2015-04-22 19:33:53 +02:00
|
|
|
m_lineEdit->clear();
|
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_comboBox) {
|
2015-04-22 19:33:53 +02:00
|
|
|
m_comboBox->setCurrentText(QString());
|
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_plainTextEdit) {
|
2015-04-22 19:33:53 +02:00
|
|
|
m_plainTextEdit->clear();
|
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_spinBoxes.first) {
|
2015-04-22 19:33:53 +02:00
|
|
|
m_spinBoxes.first->setValue(0);
|
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_spinBoxes.second) {
|
2015-04-22 19:33:53 +02:00
|
|
|
m_spinBoxes.second->setValue(0);
|
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_pictureSelection) {
|
2015-04-22 19:33:53 +02:00
|
|
|
m_pictureSelection->clear();
|
|
|
|
}
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_descriptionLineEdit) {
|
2015-04-22 19:33:53 +02:00
|
|
|
m_descriptionLineEdit->clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Applies the current value to the assigned tag.
|
|
|
|
*/
|
|
|
|
void TagFieldEdit::apply()
|
|
|
|
{
|
2022-06-19 14:25:12 +02:00
|
|
|
for (auto *const tag : *m_tags) {
|
2018-03-07 01:18:01 +01:00
|
|
|
if (m_dataType == TagDataType::Picture) {
|
|
|
|
if (m_pictureSelection) {
|
2015-04-22 19:33:53 +02:00
|
|
|
m_pictureSelection->apply();
|
|
|
|
}
|
|
|
|
} else {
|
2022-06-19 14:25:12 +02:00
|
|
|
auto encoding = Settings::values().tagPocessing.preferredEncoding;
|
2018-03-07 01:18:01 +01:00
|
|
|
if (!tag->canEncodingBeUsed(encoding)) {
|
2015-04-22 19:33:53 +02:00
|
|
|
encoding = tag->proposedTextEncoding();
|
|
|
|
}
|
2018-07-22 23:21:29 +02:00
|
|
|
// FIXME: use tag->setValues(...) and to set multiple values
|
2016-03-03 22:21:15 +01:00
|
|
|
tag->setValue(m_field, value(encoding, tag->supportsDescription(m_field)));
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Handles different key events.
|
|
|
|
*/
|
|
|
|
bool TagFieldEdit::eventFilter(QObject *obj, QEvent *event)
|
|
|
|
{
|
2018-03-07 01:18:01 +01:00
|
|
|
switch (event->type()) {
|
2015-04-22 19:33:53 +02:00
|
|
|
case QEvent::KeyRelease: {
|
2018-07-14 22:59:10 +02:00
|
|
|
auto *const keyEvent = static_cast<QKeyEvent *>(event);
|
|
|
|
switch (keyEvent->key()) {
|
2015-04-22 19:33:53 +02:00
|
|
|
case Qt::Key_Return:
|
|
|
|
emit returnPressed();
|
|
|
|
break;
|
|
|
|
case Qt::Key_Shift:
|
2018-03-07 01:18:01 +01:00
|
|
|
if (keyEvent->modifiers() & Qt::ControlModifier) {
|
2018-07-14 22:59:10 +02:00
|
|
|
if (auto *const le = qobject_cast<QLineEdit *>(obj)) {
|
2015-04-22 19:33:53 +02:00
|
|
|
le->setText(Utility::formatName(le->text()));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2018-03-07 01:18:01 +01:00
|
|
|
default:;
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
break;
|
2018-03-07 01:18:01 +01:00
|
|
|
}
|
|
|
|
default:;
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
return QWidget::eventFilter(obj, event);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Called when the restore button has been clicked.
|
|
|
|
*/
|
|
|
|
void TagFieldEdit::handleRestoreButtonClicked()
|
|
|
|
{
|
2018-07-14 22:59:10 +02:00
|
|
|
if (tags().empty()) {
|
2015-04-22 19:33:53 +02:00
|
|
|
restore();
|
2018-07-14 22:59:10 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
QMenu menu;
|
|
|
|
int i = 0;
|
2019-05-28 23:59:38 +02:00
|
|
|
for (auto *const tag : tags()) {
|
2021-03-20 21:59:49 +01:00
|
|
|
const auto *const action = menu.addAction(tr("restore to value from %1 (%2)").arg(qstringFromStdStringView(tag->typeName())).arg(++i));
|
2019-05-28 23:59:38 +02:00
|
|
|
connect(action, &QAction::triggered, [this, tag] {
|
|
|
|
setLocked(false);
|
|
|
|
updateValue(tag, PreviousValueHandling::Clear);
|
|
|
|
});
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
2018-07-14 22:59:10 +02:00
|
|
|
menu.exec(QCursor::pos());
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Sets m_restoreButton to nullptr when the restore button has been destroyed.
|
|
|
|
*/
|
|
|
|
void TagFieldEdit::handleRestoreButtonDestroyed(QObject *obj)
|
|
|
|
{
|
2020-06-09 23:27:45 +02:00
|
|
|
if (obj == m_restoreAction) {
|
|
|
|
m_restoreAction = nullptr;
|
2015-04-22 19:33:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-28 23:59:38 +02:00
|
|
|
/*!
|
|
|
|
* \brief Sets m_lockButton to nullptr when the restore button has been destroyed.
|
|
|
|
*/
|
|
|
|
void TagFieldEdit::handleLockButtonDestroyed(QObject *obj)
|
|
|
|
{
|
2020-06-09 23:27:45 +02:00
|
|
|
if (obj == m_lockAction) {
|
|
|
|
m_lockAction = nullptr;
|
2019-05-28 23:59:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-07 01:18:01 +01:00
|
|
|
} // namespace QtGui
|