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 }