File indexing completed on 2024-04-28 03:51:00
0001 /**************************************************************************** 0002 ** 0003 ** Copyright (C) 2016 by Sandro S. Andrade <sandroandrade@kde.org> 0004 ** 0005 ** This program is free software; you can redistribute it and/or 0006 ** modify it under the terms of the GNU General Public License as 0007 ** published by the Free Software Foundation; either version 2 of 0008 ** the License or (at your option) version 3 or any later version 0009 ** accepted by the membership of KDE e.V. (or its successor approved 0010 ** by the membership of KDE e.V.), which shall act as a proxy 0011 ** defined in Section 14 of version 3 of the license. 0012 ** 0013 ** This program is distributed in the hope that it will be useful, 0014 ** but WITHOUT ANY WARRANTY; without even the implied warranty of 0015 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0016 ** GNU General Public License for more details. 0017 ** 0018 ** You should have received a copy of the GNU General Public License 0019 ** along with this program. If not, see <http://www.gnu.org/licenses/>. 0020 ** 0021 ****************************************************************************/ 0022 0023 #include "exercisecontroller.h" 0024 0025 #if !defined(Q_OS_ANDROID) 0026 #include <KLocalizedString> 0027 #endif 0028 0029 #include <QDateTime> 0030 #include <QDir> 0031 #include <QJsonDocument> 0032 #include <QRandomGenerator> 0033 #include <QStandardPaths> 0034 #include <qqml.h> 0035 0036 #include <utils/xdgdatadirs.h> 0037 0038 namespace Minuet 0039 { 0040 ExerciseController::ExerciseController(QObject *parent) 0041 : IExerciseController(parent), m_chosenRootNote(0) 0042 { 0043 m_exercises[QStringLiteral("exercises")] = QJsonArray(); 0044 m_definitions[QStringLiteral("definitions")] = QJsonArray(); 0045 } 0046 0047 bool ExerciseController::initialize(Core *core) 0048 { 0049 Q_UNUSED(core) 0050 0051 m_errorString.clear(); 0052 bool definitionsMerge = mergeJsonFiles(QStringLiteral("definitions"), m_definitions); 0053 bool exercisesMerge = mergeJsonFiles(QStringLiteral("exercises"), m_exercises, true, 0054 QStringLiteral("name"), QStringLiteral("children")); 0055 0056 // QFile file("merged-exercises.json"); 0057 // file.open(QIODevice::WriteOnly); 0058 // file.write(QJsonDocument(m_exercises).toJson()); 0059 // file.close(); 0060 0061 return definitionsMerge & exercisesMerge; 0062 } 0063 0064 QString ExerciseController::errorString() const 0065 { 0066 return m_errorString; 0067 } 0068 0069 void ExerciseController::randomlySelectExerciseOptions() 0070 { 0071 while (!m_selectedExerciseOptions.isEmpty()) { 0072 m_selectedExerciseOptions.removeFirst(); 0073 } 0074 0075 int minNote = INT_MAX; 0076 int maxNote = INT_MIN; 0077 auto *generator = QRandomGenerator::global(); 0078 quint8 numberOfSelectedOptions 0079 = m_currentExercise[QStringLiteral("numberOfSelectedOptions")].toInt(); 0080 for (quint8 i = 0; i < numberOfSelectedOptions; ++i) { 0081 QJsonArray exerciseOptions 0082 = QJsonObject::fromVariantMap(m_currentExercise)[QStringLiteral("options")].toArray(); 0083 quint8 chosenExerciseOption = generator->bounded(exerciseOptions.size()); 0084 0085 QString sequence = exerciseOptions[chosenExerciseOption] 0086 .toObject()[QStringLiteral("sequence")] 0087 .toString(); 0088 foreach (const QString &additionalNote, sequence.split(' ')) { 0089 int note = additionalNote.toInt(); 0090 if (note > maxNote) { 0091 maxNote = note; 0092 } 0093 if (note < minNote) { 0094 minNote = note; 0095 } 0096 } 0097 if (m_currentExercise[QStringLiteral("playMode")].toString() != QLatin1String("rhythm")) { 0098 QStringList exerciseRoots 0099 = m_currentExercise[QStringLiteral("root")].toString().split('.'); 0100 quint8 exerciseMinRoot = exerciseRoots.first().toInt(); 0101 quint8 exerciseMaxRoot = exerciseRoots.last().toInt(); 0102 do { 0103 m_chosenRootNote 0104 = exerciseMinRoot + generator->bounded(exerciseMaxRoot - exerciseMinRoot); 0105 } while (m_chosenRootNote + maxNote > 108 || m_chosenRootNote + minNote < 21); 0106 } 0107 0108 QJsonObject jsonObject = exerciseOptions[chosenExerciseOption].toObject(); 0109 jsonObject[QStringLiteral("rootNote")] = QString::number(m_chosenRootNote); 0110 exerciseOptions[chosenExerciseOption] = jsonObject; 0111 m_selectedExerciseOptions.append(exerciseOptions[chosenExerciseOption]); 0112 } 0113 emit selectedExerciseOptionsChanged(m_selectedExerciseOptions); 0114 } 0115 0116 unsigned int ExerciseController::chosenRootNote() const 0117 { 0118 return m_chosenRootNote; 0119 } 0120 0121 QJsonArray ExerciseController::exercises() const 0122 { 0123 return m_exercises[QStringLiteral("exercises")].toArray(); 0124 } 0125 0126 bool ExerciseController::mergeJsonFiles(const QString directoryName, QJsonObject &targetObject, 0127 bool applyDefinitionsFlag, QString commonKey, 0128 QString mergeKey) 0129 { 0130 QStringList jsonDirs; 0131 #if defined(Q_OS_ANDROID) 0132 jsonDirs << "assets:/data/" + directoryName; 0133 #elif defined(Q_OS_WIN) 0134 jsonDirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, 0135 QStringLiteral("minuet/") + directoryName, 0136 QStandardPaths::LocateDirectory); 0137 #else 0138 jsonDirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, directoryName, 0139 QStandardPaths::LocateDirectory); 0140 #ifdef Q_OS_MACOS 0141 if (jsonDirs.isEmpty()) { 0142 const QStringList xdgDataDirs = Utils::getXdgDataDirs(); 0143 for (const auto &dirPath : xdgDataDirs) { 0144 const QDir testDir( 0145 QDir(dirPath).absoluteFilePath(QStringLiteral("minuet/") + directoryName)); 0146 if (testDir.exists()) { 0147 jsonDirs << testDir.absolutePath(); 0148 } 0149 } 0150 } 0151 #endif 0152 #endif 0153 foreach (const QString &jsonDirString, jsonDirs) { 0154 QDir jsonDir(jsonDirString); 0155 foreach (const QString &json, jsonDir.entryList(QDir::Files)) { 0156 if (!json.endsWith(QLatin1String(".json"))) { 0157 break; 0158 } 0159 QFile jsonFile(jsonDir.absoluteFilePath(json)); 0160 if (!jsonFile.open(QIODevice::ReadOnly)) { 0161 #if !defined(Q_OS_ANDROID) 0162 m_errorString 0163 = i18n("Could not open JSON file \"%1\".", jsonDir.absoluteFilePath(json)); 0164 #else 0165 m_errorString = QStringLiteral("Couldn't open json file \"%1\".") 0166 .arg(jsonDir.absoluteFilePath(json)); 0167 #endif 0168 return false; 0169 } 0170 QJsonParseError error; 0171 QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonFile.readAll(), &error); 0172 0173 if (error.error != QJsonParseError::NoError) { 0174 m_errorString += QStringLiteral("Error when parsing JSON file '%1'. ") 0175 .arg(jsonDir.absoluteFilePath(json)); 0176 jsonFile.close(); 0177 return false; 0178 } 0179 QJsonObject jsonObject = jsonDocument.object(); 0180 if (applyDefinitionsFlag) { 0181 jsonObject[directoryName] 0182 = applyDefinitions(jsonObject[directoryName].toArray(), 0183 m_definitions[QStringLiteral("definitions")].toArray()); 0184 } 0185 0186 targetObject[directoryName] 0187 = mergeJsonArrays(targetObject[directoryName].toArray(), 0188 jsonObject[directoryName].toArray(), commonKey, mergeKey); 0189 jsonFile.close(); 0190 } 0191 } 0192 return true; 0193 } 0194 0195 QJsonArray ExerciseController::applyDefinitions(QJsonArray exercises, QJsonArray definitions, 0196 QJsonObject collectedProperties) 0197 { 0198 QJsonArray::const_iterator exercisesBegin = exercises.constBegin(); 0199 QJsonArray::const_iterator exercisesEnd = exercises.constEnd(); 0200 for (QJsonArray::ConstIterator i1 = exercisesBegin; i1 < exercisesEnd; ++i1) { 0201 if (i1->isObject()) { 0202 QJsonObject exerciseObject = i1->toObject(); 0203 QJsonArray filteredDefinitions = definitions; 0204 QStringList exerciseObjectKeys = exerciseObject.keys(); 0205 if (exerciseObjectKeys.contains(QStringLiteral("and-tags")) 0206 && exerciseObject[QStringLiteral("and-tags")].isArray()) { 0207 filterDefinitions(filteredDefinitions, exerciseObject, QStringLiteral("and-tags"), 0208 AndFiltering); 0209 } 0210 if (exerciseObjectKeys.contains(QStringLiteral("or-tags")) 0211 && exerciseObject[QStringLiteral("or-tags")].isArray()) { 0212 filterDefinitions(filteredDefinitions, exerciseObject, QStringLiteral("or-tags"), 0213 OrFiltering); 0214 } 0215 if (exerciseObjectKeys.contains(QStringLiteral("children"))) { 0216 foreach (const QString &key, exerciseObjectKeys) 0217 if (key != QLatin1String("name") && key != QLatin1String("children") 0218 && key != QLatin1String("and-tags") && key != QLatin1String("or-tags") 0219 && !key.startsWith('_')) { 0220 collectedProperties.insert(key, exerciseObject[key]); 0221 exerciseObject.remove(key); 0222 } 0223 exerciseObject[QStringLiteral("children")] 0224 = applyDefinitions(exerciseObject[QStringLiteral("children")].toArray(), 0225 filteredDefinitions, collectedProperties); 0226 } else { 0227 foreach (const QString &key, collectedProperties.keys()) 0228 if (!exerciseObject.contains(key)) { 0229 exerciseObject.insert(key, collectedProperties[key]); 0230 } 0231 exerciseObject.insert(QStringLiteral("options"), filteredDefinitions); 0232 } 0233 exercises[i1 - exercisesBegin] = exerciseObject; 0234 } 0235 } 0236 return exercises; 0237 } 0238 0239 void ExerciseController::filterDefinitions(QJsonArray &definitions, QJsonObject &exerciseObject, 0240 const QString &filterTagsKey, 0241 DefinitionFilteringMode definitionFilteringMode) 0242 { 0243 QJsonArray filterTags = exerciseObject[filterTagsKey].toArray(); 0244 exerciseObject.remove(filterTagsKey); 0245 for (QJsonArray::Iterator i2 = definitions.begin(); i2 < definitions.end(); ++i2) { 0246 bool remove = definitionFilteringMode != AndFiltering; 0247 QJsonArray::const_iterator filterTagsEnd = filterTags.constEnd(); 0248 for (QJsonArray::ConstIterator i3 = filterTags.constBegin(); i3 < filterTagsEnd; ++i3) { 0249 QJsonArray tagArray = i2->toObject()[QStringLiteral("tags")].toArray(); 0250 if (definitionFilteringMode == AndFiltering && !tagArray.contains(*i3)) { 0251 remove = true; 0252 break; 0253 } 0254 if (definitionFilteringMode == OrFiltering && tagArray.contains(*i3)) { 0255 remove = false; 0256 } 0257 } 0258 if (remove) { 0259 i2 = definitions.erase(i2); 0260 i2--; 0261 } 0262 } 0263 } 0264 0265 QJsonArray ExerciseController::mergeJsonArrays(QJsonArray oldFile, QJsonArray newFile, 0266 QString commonKey, QString mergeKey) 0267 { 0268 QJsonArray::const_iterator newFileEnd = newFile.constEnd(); 0269 ; 0270 for (QJsonArray::ConstIterator i1 = newFile.constBegin(); i1 < newFileEnd; ++i1) { 0271 if (i1->isObject()) { 0272 QJsonArray::ConstIterator i2; 0273 QJsonArray::const_iterator oldFileEnd = oldFile.constEnd(); 0274 for (i2 = oldFile.constBegin(); i2 < oldFileEnd; ++i2) { 0275 QJsonObject newFileObject = i1->toObject(); 0276 QJsonObject oldFileObject = i2->toObject(); 0277 if (i2->isObject() && i1->isObject() && !commonKey.isEmpty() 0278 && oldFileObject[commonKey] == newFileObject[commonKey]) { 0279 QJsonObject jsonObject = oldFile[i2 - oldFile.constBegin()].toObject(); 0280 jsonObject[mergeKey] 0281 = mergeJsonArrays(oldFileObject[mergeKey].toArray(), 0282 newFileObject[mergeKey].toArray(), commonKey, mergeKey); 0283 oldFile[i2 - oldFile.constBegin()] = jsonObject; 0284 break; 0285 } 0286 } 0287 if (i2 == oldFile.constEnd()) { 0288 oldFile.append(*i1); 0289 } 0290 } 0291 } 0292 return oldFile; 0293 } 0294 0295 } 0296 0297 #include "moc_exercisecontroller.cpp"