File indexing completed on 2024-05-19 04:55:54

0001 /**
0002  * \file formatconfig.cpp
0003  * Format configuration.
0004  *
0005  * \b Project: Kid3
0006  * \author Urs Fleisch
0007  * \date 17 Sep 2003
0008  *
0009  * Copyright (C) 2003-2024  Urs Fleisch
0010  *
0011  * This file is part of Kid3.
0012  *
0013  * Kid3 is free software; you can redistribute it and/or modify
0014  * it under the terms of the GNU General Public License as published by
0015  * the Free Software Foundation; either version 2 of the License, or
0016  * (at your option) any later version.
0017  *
0018  * Kid3 is distributed in the hope that it will be useful,
0019  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0020  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0021  * GNU General Public License for more details.
0022  *
0023  * You should have received a copy of the GNU General Public License
0024  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0025  */
0026 
0027 #include "formatconfig.h"
0028 #include "config.h"
0029 #include <QString>
0030 #include <QRegularExpression>
0031 #include <QLocale>
0032 #include <QCoreApplication>
0033 #include "generalconfig.h"
0034 #include "isettings.h"
0035 #include "frame.h"
0036 
0037 /**
0038  * Constructor.
0039  */
0040 FormatConfig::FormatConfig(const QString& grp)
0041   : GeneralConfig(grp),
0042     m_caseConversion(AllFirstLettersUppercase),
0043     m_maximumLength(255),
0044     m_useForOtherFileNames(true),
0045     m_enableMaximumLength(false),
0046     m_filenameFormatter(false),
0047     m_formatWhileEditing(false),
0048     m_strRepEnabled(false),
0049     m_enableValidation(true)
0050 {
0051   m_strRepMap.clear();
0052 }
0053 
0054 /**
0055  * Destructor.
0056  */
0057 FormatConfig::~FormatConfig()
0058 {
0059   // Must not be inline because of forwared declared QScopedPointer.
0060 }
0061 
0062 /**
0063  * Set specific properties for a filename format.
0064  * This will set default string conversions and not touch the file
0065  * extension when formatting.
0066  */
0067 void FormatConfig::setAsFilenameFormatter()
0068 {
0069   m_filenameFormatter = true;
0070   m_caseConversion = NoChanges;
0071   m_localeName = QString();
0072   m_locale.reset();
0073   m_strRepEnabled = true;
0074   m_strRepMap.append({
0075     {QLatin1String("/"), QLatin1String("-")},
0076     {QLatin1String(":"), QLatin1String("-")},
0077     {QLatin1String("."), QLatin1String("")},
0078     {QLatin1String("?"), QLatin1String("")},
0079     {QLatin1String("*"), QLatin1String("")},
0080     {QLatin1String("\""), QLatin1String("''")},
0081     {QLatin1String("<"), QLatin1String("-")},
0082     {QLatin1String(">"), QLatin1String("-")},
0083     {QLatin1String("|"), QLatin1String("-")},
0084     {QChar(0xe4), QLatin1String("ae")},
0085     {QChar(0xf6), QLatin1String("oe")},
0086     {QChar(0xfc), QLatin1String("ue")},
0087     {QChar(0xc4), QLatin1String("Ae")},
0088     {QChar(0xd6), QLatin1String("Oe")},
0089     {QChar(0xdc), QLatin1String("Ue")},
0090     {QChar(0xdf), QLatin1String("ss")},
0091     {QChar(0xc0), QLatin1String("A")},
0092     {QChar(0xc1), QLatin1String("A")},
0093     {QChar(0xc2), QLatin1String("A")},
0094     {QChar(0xc3), QLatin1String("A")},
0095     {QChar(0xc5), QLatin1String("A")},
0096     {QChar(0xc6), QLatin1String("AE")},
0097     {QChar(0xc7), QLatin1String("C")},
0098     {QChar(0xc8), QLatin1String("E")},
0099     {QChar(0xc9), QLatin1String("E")},
0100     {QChar(0xca), QLatin1String("E")},
0101     {QChar(0xcb), QLatin1String("E")},
0102     {QChar(0xcc), QLatin1String("I")},
0103     {QChar(0xcd), QLatin1String("I")},
0104     {QChar(0xce), QLatin1String("I")},
0105     {QChar(0xcf), QLatin1String("I")},
0106     {QChar(0xd0), QLatin1String("D")},
0107     {QChar(0xd1), QLatin1String("N")},
0108     {QChar(0xd2), QLatin1String("O")},
0109     {QChar(0xd3), QLatin1String("O")},
0110     {QChar(0xd4), QLatin1String("O")},
0111     {QChar(0xd5), QLatin1String("O")},
0112     {QChar(0xd7), QLatin1String("x")},
0113     {QChar(0xd8), QLatin1String("O")},
0114     {QChar(0xd9), QLatin1String("U")},
0115     {QChar(0xda), QLatin1String("U")},
0116     {QChar(0xdb), QLatin1String("U")},
0117     {QChar(0xdd), QLatin1String("Y")},
0118     {QChar(0xe0), QLatin1String("a")},
0119     {QChar(0xe1), QLatin1String("a")},
0120     {QChar(0xe2), QLatin1String("a")},
0121     {QChar(0xe3), QLatin1String("a")},
0122     {QChar(0xe5), QLatin1String("a")},
0123     {QChar(0xe6), QLatin1String("ae")},
0124     {QChar(0xe7), QLatin1String("c")},
0125     {QChar(0xe8), QLatin1String("e")},
0126     {QChar(0xe9), QLatin1String("e")},
0127     {QChar(0xea), QLatin1String("e")},
0128     {QChar(0xeb), QLatin1String("e")},
0129     {QChar(0xec), QLatin1String("i")},
0130     {QChar(0xed), QLatin1String("i")},
0131     {QChar(0xee), QLatin1String("i")},
0132     {QChar(0xef), QLatin1String("i")},
0133     {QChar(0xf0), QLatin1String("d")},
0134     {QChar(0xf1), QLatin1String("n")},
0135     {QChar(0xf2), QLatin1String("o")},
0136     {QChar(0xf3), QLatin1String("o")},
0137     {QChar(0xf4), QLatin1String("o")},
0138     {QChar(0xf5), QLatin1String("o")},
0139     {QChar(0xf8), QLatin1String("o")},
0140     {QChar(0xf9), QLatin1String("u")},
0141     {QChar(0xfa), QLatin1String("u")},
0142     {QChar(0xfb), QLatin1String("u")},
0143     {QChar(0xfd), QLatin1String("y")},
0144     {QChar(0xff), QLatin1String("y")}
0145   });
0146 }
0147 
0148 /**
0149  * Format a string using this configuration.
0150  *
0151  * @param str string to format
0152  */
0153 void FormatConfig::formatString(QString& str) const
0154 {
0155   QString ext;
0156   int dotPos = -1;
0157   if (m_filenameFormatter) {
0158     /* Do not format the extension if it is a filename */
0159     dotPos = str.lastIndexOf(QLatin1Char('.'));
0160     if (dotPos != -1) {
0161       ext = str.right(str.length() - dotPos);
0162       str = str.left(dotPos);
0163     }
0164   }
0165   if (m_caseConversion != NoChanges) {
0166     switch (m_caseConversion) {
0167       case AllLowercase:
0168         str = toLower(str);
0169         break;
0170       case AllUppercase:
0171         str = toUpper(str);
0172         break;
0173       case FirstLetterUppercase: {
0174         const int strLen = str.length();
0175         int firstLetterPos = 0;
0176         while (firstLetterPos < strLen && !str.at(firstLetterPos).isLetter()) {
0177           ++firstLetterPos;
0178         }
0179         if (firstLetterPos < strLen) {
0180           str = toUpper(str.left(firstLetterPos + 1)) +
0181                 toLower(str.right(strLen - firstLetterPos - 1));
0182         }
0183         break;
0184       }
0185       case AllFirstLettersUppercase: {
0186         static const QString romanLetters(QLatin1String("IVXLCDM"));
0187         QString newstr;
0188         bool wordstart = true;
0189         const int strLen = str.length();
0190         for (int i = 0; i < strLen; ++i) {
0191           if (QChar ch = str.at(i); !ch.isLetterOrNumber() &&
0192             ch != QLatin1Char('\'') && ch != QLatin1Char('`')) {
0193             wordstart = true;
0194             newstr.append(ch);
0195           } else if (wordstart) {
0196             wordstart = false;
0197 
0198             // Skip word if it is a roman number
0199             if (romanLetters.contains(ch)) {
0200               int j = i + 1;
0201               while (j < strLen) {
0202                 if (QChar c = str.at(j); !c.isLetterOrNumber()) {
0203                   break;
0204                 } else if (!romanLetters.contains(c)) {
0205                   j = i;
0206                   break;
0207                 }
0208                 ++j;
0209               }
0210               if (j > i) {
0211 #if QT_VERSION >= 0x060000
0212                 newstr.append(str.mid(i, j - i));
0213 #else
0214                 newstr.append(str.midRef(i, j - i));
0215 #endif
0216                 i = j - 1;
0217                 continue;
0218               }
0219             }
0220 
0221             newstr.append(toUpper(ch));
0222           } else {
0223             newstr.append(toLower(ch));
0224           }
0225         }
0226         str = newstr;
0227         break;
0228       }
0229       default:
0230         ;
0231     }
0232   }
0233   if (m_strRepEnabled) {
0234     for (auto it = m_strRepMap.constBegin(); it != m_strRepMap.constEnd(); ++it) {
0235       QString before = it->first;
0236       QString after = it->second;
0237       if (before.length() > 1 &&
0238           before.startsWith(QLatin1Char('/')) &&
0239           before.endsWith(QLatin1Char('/'))) {
0240         QRegularExpression re(before.mid(1, before.length() - 2));
0241         str.replace(re, after);
0242       } else {
0243         str.replace(before, after);
0244       }
0245     }
0246   }
0247   str = joinFileName(str, ext);
0248 }
0249 
0250 /**
0251  * Join base name and extension respecting maximum length.
0252  *
0253  * Truncation to maximumLength() is only done if enableMaximumLength() and
0254  * setAsFilenameFormatter() are set.
0255  *
0256  * @param baseName file name without extension
0257  * @param extension file name extension starting with dot
0258  * @return file name with extension, eventually truncated to maximum length.
0259  */
0260 QString FormatConfig::joinFileName(const QString& baseName,
0261                                    const QString& extension) const
0262 {
0263   QString str(baseName);
0264   QString ext(extension);
0265   if (m_filenameFormatter && m_enableMaximumLength) {
0266     if (m_maximumLength > 0 && ext.length() > m_maximumLength) {
0267       ext.truncate(m_maximumLength);
0268     }
0269     if (int maxLength = m_maximumLength - ext.length();
0270         maxLength > 0 && str.length() > maxLength) {
0271       str.truncate(maxLength);
0272       str = str.trimmed();
0273     }
0274   }
0275   if (!ext.isEmpty()) {
0276     str.append(ext);
0277   }
0278   return str;
0279 }
0280 
0281 /** Returns a lowercase copy of @a str. */
0282 QString FormatConfig::toLower(const QString& str) const
0283 {
0284   if (m_locale)
0285     return m_locale->toLower(str);
0286   return str.toLower();
0287 }
0288 
0289 /** Returns an uppercase copy of @a str. */
0290 QString FormatConfig::toUpper(const QString& str) const
0291 {
0292   if (m_locale)
0293     return m_locale->toUpper(str);
0294   return str.toUpper();
0295 }
0296 
0297 /**
0298  * Format frames using this configuration.
0299  *
0300  * @param frames frames
0301  */
0302 void FormatConfig::formatFrames(FrameCollection& frames) const
0303 {
0304   for (auto it = frames.cbegin(); it != frames.cend(); ++it) {
0305     if (auto& frame = const_cast<Frame&>(*it);
0306         frame.getType() != Frame::FT_Genre) {
0307       if (QString value(frame.getValue()); !value.isEmpty()) {
0308         formatString(value);
0309         frame.setValueIfChanged(value);
0310       }
0311     }
0312   }
0313 }
0314 
0315 /**
0316  * Format frames if format while editing is switched on.
0317  *
0318  * @param frames frames
0319  */
0320 void FormatConfig::formatFramesIfEnabled(FrameCollection& frames) const
0321 {
0322   if (m_formatWhileEditing) {
0323     formatFrames(frames);
0324   }
0325 }
0326 
0327 /**
0328  * Persist configuration.
0329  *
0330  * @param config configuration
0331  */
0332 void FormatConfig::writeToConfig(ISettings* config) const
0333 {
0334   config->beginGroup(m_group);
0335   config->setValue(QLatin1String("FormatWhileEditing"), QVariant(m_formatWhileEditing));
0336   config->setValue(QLatin1String("CaseConversion"), QVariant(m_caseConversion));
0337   config->setValue(QLatin1String("LocaleName"), QVariant(m_localeName));
0338   config->setValue(QLatin1String("StrRepEnabled"), QVariant(m_strRepEnabled));
0339   config->setValue(QLatin1String("EnableValidation"), QVariant(m_enableValidation));
0340   config->setValue(QLatin1String("UseForOtherFileNames"), QVariant(m_useForOtherFileNames));
0341   config->setValue(QLatin1String("EnableMaximumLength"), QVariant(m_enableMaximumLength));
0342   config->setValue(QLatin1String("MaximumLength"), QVariant(m_maximumLength));
0343   QStringList keys, values;
0344   for (auto it = m_strRepMap.constBegin(); it != m_strRepMap.constEnd(); ++it) {
0345     keys.append(it->first);
0346     values.append(it->second);
0347   }
0348   config->setValue(QLatin1String("StrRepMapKeys"), QVariant(keys));
0349   config->setValue(QLatin1String("StrRepMapValues"), QVariant(values));
0350   config->endGroup();
0351 }
0352 
0353 /**
0354  * Read persisted configuration.
0355  *
0356  * @param config configuration
0357  */
0358 void FormatConfig::readFromConfig(ISettings* config)
0359 {
0360   config->beginGroup(m_group);
0361   m_formatWhileEditing = config->value(QLatin1String("FormatWhileEditing"),
0362                                        m_formatWhileEditing).toBool();
0363   m_caseConversion = static_cast<CaseConversion>(
0364         config->value(QLatin1String("CaseConversion"),
0365                       static_cast<int>(m_caseConversion)).toInt());
0366   m_localeName = config->value(QLatin1String("LocaleName"),
0367                                m_localeName).toString();
0368   m_strRepEnabled = config->value(QLatin1String("StrRepEnabled"),
0369                                   m_strRepEnabled).toBool();
0370   m_enableValidation = config->value(QLatin1String("EnableValidation"),
0371                                      m_enableValidation).toBool();
0372   m_useForOtherFileNames = config->value(QLatin1String("UseForOtherFileNames"),
0373                                         m_useForOtherFileNames).toBool();
0374   m_enableMaximumLength = config->value(QLatin1String("EnableMaximumLength"),
0375                                         m_enableMaximumLength).toBool();
0376   m_maximumLength = config->value(QLatin1String("MaximumLength"),
0377                                   m_maximumLength).toInt();
0378   QStringList keys = config->value(QLatin1String("StrRepMapKeys"),
0379                                    QStringList()).toStringList();
0380   QStringList values = config->value(QLatin1String("StrRepMapValues"),
0381                                      QStringList()).toStringList();
0382   if (!keys.empty() && !values.empty()) {
0383     m_strRepMap.clear();
0384     for (auto itk = keys.constBegin(), itv = values.constBegin();
0385          itk != keys.constEnd() && itv != values.constEnd();
0386          ++itk, ++itv) {
0387       m_strRepMap.append({*itk, *itv});
0388     }
0389   }
0390   config->endGroup();
0391 }
0392 
0393 /**
0394  * Set name of locale to use for string conversions.
0395  * @param localeName locale name
0396  */
0397 void FormatConfig::setLocaleName(const QString& localeName)
0398 {
0399   if (localeName != m_localeName) {
0400     m_localeName = localeName;
0401     m_locale.reset(new QLocale(m_localeName));
0402     emit localeNameChanged(m_localeName);
0403   }
0404 }
0405 
0406 void FormatConfig::setEnableValidation(bool enableValidation)
0407 {
0408   if (m_enableValidation != enableValidation) {
0409     m_enableValidation = enableValidation;
0410     emit enableValidationChanged(m_enableValidation);
0411   }
0412 }
0413 
0414 void FormatConfig::setUseForOtherFileNames(bool useForOtherFileNames)
0415 {
0416   if (m_useForOtherFileNames != useForOtherFileNames) {
0417     m_useForOtherFileNames = useForOtherFileNames;
0418     emit useForOtherFileNamesChanged(m_useForOtherFileNames);
0419   }
0420 }
0421 
0422 void FormatConfig::setEnableMaximumLength(bool enableMaximumLength)
0423 {
0424   if (m_enableMaximumLength != enableMaximumLength) {
0425     m_enableMaximumLength = enableMaximumLength;
0426     emit enableMaximumLengthChanged(m_enableMaximumLength);
0427   }
0428 }
0429 
0430 void FormatConfig::setMaximumLength(int maximumLength)
0431 {
0432   if (m_maximumLength != maximumLength) {
0433     m_maximumLength = maximumLength;
0434     emit maximumLengthChanged(m_maximumLength);
0435   }
0436 }
0437 
0438 void FormatConfig::setStrRepMap(const QList<QPair<QString, QString>>& strRepMap)
0439 {
0440   if (m_strRepMap != strRepMap) {
0441     m_strRepMap = strRepMap;
0442     emit strRepMapChanged(m_strRepMap);
0443   }
0444 }
0445 
0446 QStringList FormatConfig::strRepStringList() const
0447 {
0448   QStringList lst;
0449   for (auto it = m_strRepMap.constBegin();
0450        it != m_strRepMap.constEnd();
0451        ++it) {
0452     lst.append(it->first);
0453     lst.append(it->second);
0454   }
0455   return lst;
0456 }
0457 
0458 void FormatConfig::setStrRepStringList(const QStringList& lst)
0459 {
0460   QList<QPair<QString, QString>> strRepMap;
0461   auto it = lst.constBegin();
0462   while (it != lst.constEnd()) {
0463     QString key = *it++;
0464     if (it != lst.constEnd()) {
0465       strRepMap.append({key, *it++});
0466     }
0467   }
0468   setStrRepMap(strRepMap);
0469 }
0470 
0471 void FormatConfig::setCaseConversion(CaseConversion caseConversion)
0472 {
0473   if (m_caseConversion != caseConversion) {
0474     m_caseConversion = caseConversion;
0475     emit caseConversionChanged(m_caseConversion);
0476   }
0477 }
0478 
0479 void FormatConfig::setStrRepEnabled(bool strRepEnabled)
0480 {
0481   if (m_strRepEnabled != strRepEnabled) {
0482     m_strRepEnabled = strRepEnabled;
0483     emit strRepEnabledChanged(m_strRepEnabled);
0484   }
0485 }
0486 
0487 void FormatConfig::setFormatWhileEditing(bool formatWhileEditing)
0488 {
0489   if (m_formatWhileEditing != formatWhileEditing) {
0490     m_formatWhileEditing = formatWhileEditing;
0491     emit formatWhileEditingChanged(m_formatWhileEditing);
0492   }
0493 }
0494 
0495 /**
0496  * String list of case conversion names.
0497  */
0498 QStringList FormatConfig::getCaseConversionNames()
0499 {
0500   static const char* const names[NumCaseConversions] = {
0501     QT_TRANSLATE_NOOP("@default", "No changes"),
0502     QT_TRANSLATE_NOOP("@default", "All lowercase"),
0503     QT_TRANSLATE_NOOP("@default", "All uppercase"),
0504     QT_TRANSLATE_NOOP("@default", "First letter uppercase"),
0505     QT_TRANSLATE_NOOP("@default", "All first letters uppercase")
0506   };
0507   QStringList strs;
0508   strs.reserve(NumCaseConversions);
0509   for (int i = 0; i < NumCaseConversions; ++i) {
0510     strs.append(QCoreApplication::translate("@default", names[i]));
0511   }
0512   return strs;
0513 }
0514 
0515 /**
0516  * String list of locale names.
0517  */
0518 QStringList FormatConfig::getLocaleNames()
0519 {
0520   return QStringList() << tr("None") << QLocale().uiLanguages();
0521 }
0522 
0523 
0524 int FilenameFormatConfig::s_index = -1;
0525 
0526 /**
0527  * Constructor.
0528  */
0529 FilenameFormatConfig::FilenameFormatConfig()
0530   : StoredConfig(QLatin1String("FilenameFormat"))
0531 {
0532   setAsFilenameFormatter();
0533 }
0534 
0535 
0536 int TagFormatConfig::s_index = -1;
0537 
0538 /**
0539  * Constructor.
0540  */
0541 TagFormatConfig::TagFormatConfig()
0542   : StoredConfig(QLatin1String("TagFormat"))
0543 {
0544 }