853 lines
31 KiB
C++
853 lines
31 KiB
C++
#include "./tagfieldedit.h"
|
|
#include "./picturepreviewselection.h"
|
|
|
|
#include "../application/knownfieldmodel.h"
|
|
#include "../application/settings.h"
|
|
|
|
#include "../misc/utility.h"
|
|
|
|
#include <tagparser/id3/id3v2tag.h>
|
|
#include <tagparser/mediafileinfo.h>
|
|
#include <tagparser/tag.h>
|
|
#include <tagparser/tagvalue.h>
|
|
|
|
#include <qtutilities/widgets/clearcombobox.h>
|
|
#include <qtutilities/widgets/clearlineedit.h>
|
|
#include <qtutilities/widgets/clearplaintextedit.h>
|
|
#include <qtutilities/widgets/clearspinbox.h>
|
|
#include <qtutilities/widgets/iconbutton.h>
|
|
|
|
#include <c++utilities/conversion/conversionexception.h>
|
|
|
|
#include <QAction>
|
|
#include <QCursor>
|
|
#include <QEvent>
|
|
#include <QFile>
|
|
#include <QGraphicsScene>
|
|
#include <QGraphicsTextItem>
|
|
#include <QGraphicsView>
|
|
#include <QHBoxLayout>
|
|
#include <QImage>
|
|
#include <QKeyEvent>
|
|
#include <QLabel>
|
|
#include <QListIterator>
|
|
#include <QMenu>
|
|
#include <QPlainTextEdit>
|
|
#include <QPushButton>
|
|
#include <QVBoxLayout>
|
|
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#include <initializer_list>
|
|
#include <iostream>
|
|
|
|
using namespace std;
|
|
using namespace Models;
|
|
using namespace Widgets;
|
|
using namespace TagParser;
|
|
using namespace ConversionUtilities;
|
|
|
|
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()
|
|
*/
|
|
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)
|
|
, m_spinBoxes(QPair<Widgets::ClearSpinBox *, Widgets::ClearSpinBox *>(nullptr, nullptr))
|
|
, m_pictureSelection(nullptr)
|
|
, m_plainTextEdit(nullptr)
|
|
, m_descriptionLineEdit(nullptr)
|
|
, m_restoreButton(nullptr)
|
|
{
|
|
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.
|
|
*/
|
|
void TagFieldEdit::setTagField(
|
|
const QList<Tag *> &tags, TagParser::KnownField field, PreviousValueHandling previousValueHandling, bool preventUiUpdate)
|
|
{
|
|
bool uiRebuildingRequired = false;
|
|
m_tags = &tags;
|
|
if (m_field != field) {
|
|
m_field = field;
|
|
uiRebuildingRequired = true;
|
|
}
|
|
if (tags.size()) {
|
|
TagDataType proposedDataType = determineDataType();
|
|
if (proposedDataType != m_dataType) {
|
|
m_dataType = proposedDataType;
|
|
uiRebuildingRequired = true;
|
|
}
|
|
}
|
|
if (!preventUiUpdate) {
|
|
if (uiRebuildingRequired) {
|
|
setupUi();
|
|
}
|
|
updateValue(previousValueHandling);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the currently shown value.
|
|
* \remarks
|
|
* - The specified \a encoding is used to encode text values.
|
|
* - Does not work for values of the type picture.
|
|
*/
|
|
TagValue TagFieldEdit::value(TagTextEncoding encoding, bool includeDescription) const
|
|
{
|
|
TagValue value;
|
|
switch (m_dataType) {
|
|
case TagDataType::Text:
|
|
case TagDataType::TimeSpan:
|
|
case TagDataType::DateTime:
|
|
switch (m_field) {
|
|
case KnownField::Genre:
|
|
if (m_comboBox) {
|
|
value.assignText(Utility::qstringToString(m_comboBox->currentText(), encoding), encoding);
|
|
}
|
|
break;
|
|
case KnownField::Lyrics:
|
|
if (m_plainTextEdit) {
|
|
value.assignText(Utility::qstringToString(m_plainTextEdit->toPlainText(), encoding), encoding);
|
|
}
|
|
break;
|
|
default:
|
|
if (m_lineEdit) {
|
|
value.assignText(Utility::qstringToString(m_lineEdit->text(), encoding), encoding);
|
|
}
|
|
}
|
|
break;
|
|
case TagDataType::Integer:
|
|
if (m_spinBoxes.first && m_spinBoxes.first->value()) {
|
|
value.assignInteger(m_spinBoxes.first->value());
|
|
}
|
|
break;
|
|
case TagDataType::PositionInSet:
|
|
if (m_spinBoxes.first && m_spinBoxes.second) {
|
|
value.assignPosition(PositionInSet(m_spinBoxes.first->value(), m_spinBoxes.second->value()));
|
|
}
|
|
break;
|
|
case TagDataType::StandardGenreIndex:
|
|
if (m_comboBox) {
|
|
value.assignText(Utility::qstringToString(m_comboBox->currentText(), encoding), encoding);
|
|
}
|
|
break;
|
|
default:;
|
|
}
|
|
if (m_descriptionLineEdit && includeDescription) { // setup description line edit
|
|
value.setDescription(Utility::qstringToString(m_descriptionLineEdit->text(), encoding), encoding);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
/*!
|
|
* \brief Sets the \a value of the current tag field manually using the given \a previousValueHandling.
|
|
*/
|
|
bool TagFieldEdit::setValue(const TagValue &value, PreviousValueHandling previousValueHandling)
|
|
{
|
|
updateValue(value, previousValueHandling, false);
|
|
if (m_pictureSelection) {
|
|
m_pictureSelection->setValue(value, previousValueHandling);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns whether at least one of the assigned tags supports a description for the current field.
|
|
*/
|
|
bool TagFieldEdit::hasDescription() const
|
|
{
|
|
for (const Tag *tag : tags()) {
|
|
if (tag->supportsDescription(m_field)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns an indication whether the specified \a field can be applied.
|
|
*/
|
|
bool TagFieldEdit::canApply(KnownField field) const
|
|
{
|
|
for (const Tag *tag : tags()) {
|
|
switch (tag->type()) {
|
|
case TagType::Id3v1Tag:
|
|
if (Settings::values().tagPocessing.creationSettings.id3v1usage == TagUsage::Never) {
|
|
continue;
|
|
}
|
|
break;
|
|
case TagType::Id3v2Tag:
|
|
if (Settings::values().tagPocessing.creationSettings.id3v2usage == TagUsage::Never) {
|
|
continue;
|
|
}
|
|
break;
|
|
default:;
|
|
}
|
|
if (tag->supportsField(field)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*!
|
|
* \brief Sets whether the cover buttons are hidden.
|
|
*/
|
|
void TagFieldEdit::setCoverButtonsHidden(bool hideCoverButtons)
|
|
{
|
|
if (m_pictureSelection) {
|
|
m_pictureSelection->setCoverButtonsHidden(hideCoverButtons);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Internally called to determine the data type of the current tag field.
|
|
*/
|
|
TagDataType TagFieldEdit::determineDataType()
|
|
{
|
|
TagDataType proposedDataType = TagDataType::Undefined;
|
|
for (Tag *tag : tags()) {
|
|
TagDataType type = tag->proposedDataType(m_field);
|
|
if (proposedDataType == TagDataType::Undefined) {
|
|
proposedDataType = type;
|
|
} else if ((proposedDataType == TagDataType::PositionInSet && type == TagDataType::Integer)
|
|
|| (type == TagDataType::PositionInSet && proposedDataType == TagDataType::Integer)) {
|
|
proposedDataType = TagDataType::PositionInSet; // PositionInSet and Number can be considered as compatible
|
|
} else if (type == TagDataType::Undefined || type != proposedDataType) {
|
|
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
|
|
switch (m_dataType) {
|
|
case TagDataType::Text:
|
|
case TagDataType::TimeSpan:
|
|
case TagDataType::DateTime:
|
|
switch (m_field) {
|
|
case KnownField::Genre:
|
|
setupGenreComboBox();
|
|
break;
|
|
case KnownField::Lyrics:
|
|
setupPlainTextEdit();
|
|
break;
|
|
default:
|
|
setupLineEdit();
|
|
}
|
|
break;
|
|
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:
|
|
setupTypeNotSupportedLabel();
|
|
;
|
|
}
|
|
if (m_dataType != TagDataType::Picture && hasDescription()) { // setup description line edit
|
|
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);
|
|
m_lineEdit->insertCustomButton(0, setupRestoreButton());
|
|
m_lineEdit->installEventFilter(this);
|
|
connect(m_lineEdit, &ClearLineEdit::textChanged, this, &TagFieldEdit::showRestoreButton);
|
|
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);
|
|
m_plainTextEdit->setClearButtonEnabled(true);
|
|
m_plainTextEdit->insertCustomButton(0, setupRestoreButton());
|
|
connect(m_plainTextEdit->document(), &QTextDocument::contentsChanged, this, &TagFieldEdit::showRestoreButton);
|
|
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);
|
|
if (QLineEdit *lineEdit = m_comboBox->lineEdit()) {
|
|
lineEdit->setPlaceholderText(tr("empty"));
|
|
}
|
|
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"));
|
|
m_comboBox->setCurrentIndex(0);
|
|
m_comboBox->setClearButtonEnabled(true);
|
|
m_comboBox->insertCustomButton(0, setupRestoreButton());
|
|
m_comboBox->installEventFilter(this);
|
|
m_comboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
|
connect(m_comboBox, &ClearComboBox::currentTextChanged, this, &TagFieldEdit::showRestoreButton);
|
|
|
|
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);
|
|
m_spinBoxes.first->insertCustomButton(0, setupRestoreButton());
|
|
m_spinBoxes.first->installEventFilter(this);
|
|
m_spinBoxes.first->setMaximum(32766);
|
|
connect(m_spinBoxes.first, static_cast<void (ClearSpinBox::*)(int)>(&ClearSpinBox::valueChanged), this, &TagFieldEdit::showRestoreButton);
|
|
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.
|
|
*/
|
|
QPair<Widgets::ClearSpinBox *, Widgets::ClearSpinBox *> &TagFieldEdit::setupPositionInSetSpinBoxes()
|
|
{
|
|
QHBoxLayout *subLayout = new QHBoxLayout;
|
|
|
|
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);
|
|
m_spinBoxes.first->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
connect(m_spinBoxes.first, static_cast<void (ClearSpinBox::*)(int)>(&ClearSpinBox::valueChanged), this, &TagFieldEdit::showRestoreButton);
|
|
subLayout->addWidget(m_spinBoxes.first);
|
|
m_widgets << m_spinBoxes.first;
|
|
|
|
QLabel *label = new QLabel(tr("of"), this);
|
|
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);
|
|
m_spinBoxes.second->insertCustomButton(0, setupRestoreButton());
|
|
m_spinBoxes.second->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
connect(m_spinBoxes.second, static_cast<void (ClearSpinBox::*)(int)>(&ClearSpinBox::valueChanged), this, &TagFieldEdit::showRestoreButton);
|
|
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);
|
|
connect(m_pictureSelection, &PicturePreviewSelection::pictureChanged, this, &TagFieldEdit::showRestoreButton);
|
|
m_layout->addWidget(m_pictureSelection);
|
|
m_widgets << m_pictureSelection;
|
|
return m_pictureSelection;
|
|
}
|
|
|
|
/*!
|
|
* \brief Internally called by setupUi() to setup a file selection.
|
|
* \remarks TODO
|
|
*/
|
|
QWidget *TagFieldEdit::setupFileSelection()
|
|
{
|
|
return setupTypeNotSupportedLabel();
|
|
}
|
|
|
|
/*!
|
|
* \brief Internally called by setupUi() to setup a line edit for the description.
|
|
*/
|
|
ClearLineEdit *TagFieldEdit::setupDescriptionLineEdit()
|
|
{
|
|
QLabel *label = new QLabel(tr("Description"), this);
|
|
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);
|
|
connect(m_descriptionLineEdit, &ClearLineEdit::textChanged, this, &TagFieldEdit::showRestoreButton);
|
|
|
|
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()
|
|
{
|
|
QLabel *label = new QLabel(tr("editing widget for field type not supported"), this);
|
|
m_layout->addWidget(label);
|
|
m_widgets << label;
|
|
return label;
|
|
}
|
|
|
|
/*!
|
|
* \brief Updates the currently shown value manually.
|
|
* \param previousValueHandling Specifies how to deal with the previous value.
|
|
*
|
|
* The new value is read from the assigned tag(s).
|
|
*/
|
|
void TagFieldEdit::updateValue(PreviousValueHandling previousValueHandling)
|
|
{
|
|
QListIterator<Tag *> i(tags());
|
|
i.toBack();
|
|
bool pictureSelectionUpdated = false;
|
|
while (i.hasPrevious()) {
|
|
Tag *tag = i.previous();
|
|
const TagValue &value = tag->value(m_field);
|
|
if (!value.isEmpty()) {
|
|
updateValue(value, previousValueHandling);
|
|
if (m_pictureSelection) {
|
|
m_pictureSelection->setTagField(tag, m_field, previousValueHandling);
|
|
pictureSelectionUpdated = true;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
updateValue(TagValue::empty(), previousValueHandling);
|
|
if (m_pictureSelection && !pictureSelectionUpdated) {
|
|
if (m_tags->size()) {
|
|
m_pictureSelection->setTagField(m_tags->last(), m_field, previousValueHandling);
|
|
} else {
|
|
m_pictureSelection->setTagField(nullptr, m_field, previousValueHandling);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Updates the currently shown value manually.
|
|
* \param tag Specifies the tag to read the new value from.
|
|
* \param previousValueHandling Specifies how to deal with the previous value.
|
|
* \remarks If \a tag is nullptr, the new value is empty.
|
|
*/
|
|
void TagFieldEdit::updateValue(Tag *tag, PreviousValueHandling previousValueHandling)
|
|
{
|
|
if (tag) {
|
|
updateValue(tag->value(m_field), previousValueHandling);
|
|
} else {
|
|
updateValue(TagValue::empty(), previousValueHandling);
|
|
}
|
|
if (m_pictureSelection) {
|
|
m_pictureSelection->setTagField(tag, m_field, previousValueHandling);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Updates the currently shown value manually.
|
|
* \param value Specifies the new value.
|
|
* \param previousValueHandling Specifies how to deal with the previous value.
|
|
* \param updateRestoreButton Specifies whether the "restore button" should be updated.
|
|
* \remarks Does not update the picture preview selection.
|
|
*/
|
|
void TagFieldEdit::updateValue(const TagValue &value, PreviousValueHandling previousValueHandling, bool updateRestoreButton)
|
|
{
|
|
bool conversionError = false;
|
|
bool updated = false;
|
|
concretizePreviousValueHandling(previousValueHandling);
|
|
if (m_lineEdit || m_comboBox || m_plainTextEdit) {
|
|
QString text;
|
|
try {
|
|
text = Utility::tagValueToQString(value);
|
|
} catch (const ConversionException &) {
|
|
conversionError = true;
|
|
}
|
|
applyAutoCorrection(text);
|
|
if (previousValueHandling == PreviousValueHandling::Clear || !text.isEmpty()) {
|
|
if (m_lineEdit && (previousValueHandling != PreviousValueHandling::Keep || m_lineEdit->isCleared())) {
|
|
m_lineEdit->setText(text);
|
|
updated = true;
|
|
}
|
|
if (m_comboBox && (previousValueHandling != PreviousValueHandling::Keep || m_comboBox->isCleared())) {
|
|
m_comboBox->setCurrentText(text);
|
|
updated = true;
|
|
}
|
|
if (m_plainTextEdit && (previousValueHandling != PreviousValueHandling::Keep || m_plainTextEdit->isCleared())) {
|
|
m_plainTextEdit->setPlainText(text);
|
|
updated = true;
|
|
}
|
|
}
|
|
}
|
|
if (m_spinBoxes.first) {
|
|
if (m_spinBoxes.second) {
|
|
PositionInSet pos;
|
|
try {
|
|
pos = value.toPositionInSet();
|
|
} catch (const ConversionException &) {
|
|
conversionError = true;
|
|
}
|
|
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);
|
|
updated = true;
|
|
}
|
|
if (previousValueHandling == PreviousValueHandling::Clear || pos.total()) {
|
|
if (previousValueHandling != PreviousValueHandling::Keep || m_spinBoxes.second->isCleared()) {
|
|
m_spinBoxes.second->setValue(pos.total());
|
|
updated = true;
|
|
}
|
|
}
|
|
} else {
|
|
int num;
|
|
try {
|
|
num = value.toInteger();
|
|
} catch (const ConversionException &) {
|
|
conversionError = true;
|
|
num = 0;
|
|
}
|
|
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);
|
|
updated = true;
|
|
}
|
|
}
|
|
}
|
|
if (m_descriptionLineEdit && (previousValueHandling != PreviousValueHandling::Keep || m_descriptionLineEdit->isCleared())) {
|
|
try {
|
|
QString desc = Utility::stringToQString(value.description(), value.descriptionEncoding());
|
|
applyAutoCorrection(desc);
|
|
m_descriptionLineEdit->setText(desc);
|
|
} catch (const ConversionException &) {
|
|
conversionError = true;
|
|
m_descriptionLineEdit->clear();
|
|
}
|
|
}
|
|
if (updateRestoreButton && m_restoreButton) {
|
|
m_restoreButton->setVisible((!updated && m_restoreButton->isVisible()) || m_tags->size() > 1);
|
|
}
|
|
const initializer_list<ButtonOverlay *> widgets = { m_lineEdit, m_comboBox, m_spinBoxes.first, m_spinBoxes.second };
|
|
bool canApply = this->canApply(m_field);
|
|
if (conversionError || !canApply) {
|
|
const QPixmap pixmap(QIcon(QStringLiteral(":/qtutilities/icons/hicolor/48x48/actions/edit-error.png")).pixmap(16));
|
|
QString text;
|
|
if (conversionError) {
|
|
text = tr("The value of this field could not be read from the file because it couldn't be converted properly.");
|
|
if (!canApply) {
|
|
text += QChar('\n');
|
|
}
|
|
}
|
|
if (!canApply) {
|
|
text += tr("The field can not be applied when saving the file and will be lost.");
|
|
}
|
|
for (ButtonOverlay *overlay : widgets) {
|
|
if (overlay) {
|
|
overlay->enableInfoButton(pixmap, text);
|
|
}
|
|
}
|
|
} else {
|
|
for (ButtonOverlay *overlay : widgets) {
|
|
if (overlay) {
|
|
overlay->disableInfoButton();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Internally called by the other setup methods to create the "restore button".
|
|
*/
|
|
IconButton *TagFieldEdit::setupRestoreButton()
|
|
{
|
|
if (!m_restoreButton) { // setup restore button
|
|
m_restoreButton = new IconButton(this);
|
|
m_restoreButton->setPixmap(
|
|
/*QIcon::fromTheme(QStringLiteral("edit-undo"), */ QIcon(QStringLiteral(":/qtutilities/icons/hicolor/48x48/actions/edit-menu.png") /*)*/)
|
|
.pixmap(16));
|
|
m_restoreButton->setToolTip(tr("Restore value as it is currently present in the file"));
|
|
connect(m_restoreButton, &IconButton::clicked, this, &TagFieldEdit::handleRestoreButtonClicked);
|
|
// ownership might be transfered to a child widget/layout
|
|
connect(m_restoreButton, &IconButton::destroyed, this, &TagFieldEdit::handleRestoreButtonDestroyed);
|
|
}
|
|
return m_restoreButton;
|
|
}
|
|
|
|
/*!
|
|
* \brief Internally called to show the restore button (if there is one and at least one tag is assigned).
|
|
*/
|
|
void TagFieldEdit::showRestoreButton()
|
|
{
|
|
if (m_restoreButton) {
|
|
m_restoreButton->setVisible(m_tags->size());
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Applies auto correction (according to the settings) for the specified \a textValue.
|
|
*/
|
|
void TagFieldEdit::applyAutoCorrection(QString &textValue)
|
|
{
|
|
const auto &settings = Settings::values().editor.autoCompletition;
|
|
auto &fields = settings.fields.items();
|
|
auto i = find_if(fields.constBegin(), fields.constEnd(), [this](const ChecklistItem &item) {
|
|
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
|
|
if (i != fields.constEnd() && i->isChecked()) {
|
|
if (settings.trimWhitespaces) {
|
|
textValue = textValue.trimmed();
|
|
}
|
|
if (settings.fixUmlauts) {
|
|
textValue = Utility::fixUmlauts(textValue);
|
|
}
|
|
if (settings.formatNames) {
|
|
textValue = Utility::formatName(textValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief If \a previousValueHandling is PreviousValueHandling::Auto it will be set to a concrete value.
|
|
*/
|
|
void TagFieldEdit::concretizePreviousValueHandling(PreviousValueHandling &previousValueHandling)
|
|
{
|
|
switch (previousValueHandling) {
|
|
case PreviousValueHandling::Auto:
|
|
switch (m_field) {
|
|
// these differ for each song -> always clear previous value
|
|
case KnownField::Title:
|
|
case KnownField::Lyrics:
|
|
previousValueHandling = PreviousValueHandling::Clear;
|
|
break;
|
|
// these will be incremented
|
|
case KnownField::TrackPosition:
|
|
case KnownField::PartNumber:
|
|
previousValueHandling = PreviousValueHandling::IncrementUpdate;
|
|
break;
|
|
default:
|
|
previousValueHandling = PreviousValueHandling::Update;
|
|
}
|
|
default:;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Restores the current value to the initial value.
|
|
*/
|
|
void TagFieldEdit::restore()
|
|
{
|
|
updateValue(PreviousValueHandling::Clear);
|
|
}
|
|
|
|
/*!
|
|
* \brief Clears the current value.
|
|
*/
|
|
void TagFieldEdit::clear()
|
|
{
|
|
if (m_lineEdit) {
|
|
m_lineEdit->clear();
|
|
}
|
|
if (m_comboBox) {
|
|
m_comboBox->setCurrentText(QString());
|
|
}
|
|
if (m_plainTextEdit) {
|
|
m_plainTextEdit->clear();
|
|
}
|
|
if (m_spinBoxes.first) {
|
|
m_spinBoxes.first->setValue(0);
|
|
}
|
|
if (m_spinBoxes.second) {
|
|
m_spinBoxes.second->setValue(0);
|
|
}
|
|
if (m_pictureSelection) {
|
|
m_pictureSelection->clear();
|
|
}
|
|
if (m_descriptionLineEdit) {
|
|
m_descriptionLineEdit->clear();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Applies the current value to the assigned tag.
|
|
*/
|
|
void TagFieldEdit::apply()
|
|
{
|
|
for (Tag *tag : *m_tags) {
|
|
if (m_dataType == TagDataType::Picture) {
|
|
if (m_pictureSelection) {
|
|
m_pictureSelection->apply();
|
|
}
|
|
} else {
|
|
TagTextEncoding encoding = Settings::values().tagPocessing.preferredEncoding;
|
|
if (!tag->canEncodingBeUsed(encoding)) {
|
|
encoding = tag->proposedTextEncoding();
|
|
}
|
|
tag->setValue(m_field, value(encoding, tag->supportsDescription(m_field)));
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Handles different key events.
|
|
*/
|
|
bool TagFieldEdit::eventFilter(QObject *obj, QEvent *event)
|
|
{
|
|
switch (event->type()) {
|
|
case QEvent::KeyRelease: {
|
|
auto *keyEvent = static_cast<QKeyEvent *>(event);
|
|
int key = keyEvent->key();
|
|
switch (key) {
|
|
case Qt::Key_Return:
|
|
emit returnPressed();
|
|
break;
|
|
case Qt::Key_Shift:
|
|
if (keyEvent->modifiers() & Qt::ControlModifier) {
|
|
if (QLineEdit *le = qobject_cast<QLineEdit *>(obj)) {
|
|
le->setText(Utility::formatName(le->text()));
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
default:;
|
|
}
|
|
break;
|
|
}
|
|
default:;
|
|
}
|
|
return QWidget::eventFilter(obj, event);
|
|
}
|
|
|
|
/*!
|
|
* \brief Called when the restore button has been clicked.
|
|
*/
|
|
void TagFieldEdit::handleRestoreButtonClicked()
|
|
{
|
|
if (tags().size()) {
|
|
QMenu menu;
|
|
int i = 0;
|
|
for (Tag *tag : tags()) {
|
|
++i;
|
|
QAction *action = menu.addAction(tr("restore to value from %1 (%2)").arg(tag->typeName()).arg(i));
|
|
connect(action, &QAction::triggered,
|
|
std::bind(static_cast<void (TagFieldEdit::*)(Tag *, PreviousValueHandling)>(&TagFieldEdit::updateValue), this, tag,
|
|
PreviousValueHandling::Clear));
|
|
}
|
|
menu.exec(QCursor::pos());
|
|
} else {
|
|
restore();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Sets m_restoreButton to nullptr when the restore button has been destroyed.
|
|
*/
|
|
void TagFieldEdit::handleRestoreButtonDestroyed(QObject *obj)
|
|
{
|
|
if (obj == m_restoreButton) {
|
|
m_restoreButton = nullptr;
|
|
}
|
|
}
|
|
|
|
} // namespace QtGui
|