exercisecontroller.cpp 10.4 KB
Newer Older
Sandro Andrade's avatar
Sandro Andrade committed
1 2 3 4 5 6 7 8 9
/****************************************************************************
**
** Copyright (C) 2016 by Sandro S. Andrade <sandroandrade@kde.org>
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public License as
** published by the Free Software Foundation; either version 2 of
** the License or (at your option) version 3 or any later version
** accepted by the membership of KDE e.V. (or its successor approved
10
** by the membership of KDE e.V.), which shall act as a proxy
Sandro Andrade's avatar
Sandro Andrade committed
11 12 13 14 15 16 17 18 19 20 21 22
** defined in Section 14 of version 3 of the license.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program.  If not, see <http://www.gnu.org/licenses/>.
**
****************************************************************************/

23
#include "exercisecontroller.h"
24

Sandro Andrade's avatar
Sandro Andrade committed
25 26
#include <drumstick/alsaevent.h>

27 28 29
#include <KLocalizedString>

#include <QDir>
Sandro Andrade's avatar
Sandro Andrade committed
30
#include <qqml.h>
31 32 33 34
#include <QDateTime>
#include <QJsonDocument>
#include <QStandardPaths>

35 36
namespace Minuet
{
37
    
38 39
ExerciseController::ExerciseController(QObject *parent) :
    IExerciseController(parent),
40
    m_chosenRootNote(0)
41
{
42
    m_exercises["exercises"] = QJsonArray();
43
    m_definitions["definitions"] = QJsonArray();
44
    qmlRegisterType<ExerciseController>("org.kde.minuet", 1, 0, "ExerciseController");
45
}
46

47 48 49
ExerciseController::~ExerciseController()
{
}
50

51
bool ExerciseController::initialize(Core *core)
52
{
53 54
    Q_UNUSED(core)

55 56 57 58 59 60 61 62 63
    bool definitionsMerge = mergeJsonFiles("definitions", m_definitions);
    bool exercisesMerge = mergeJsonFiles("exercises", m_exercises, true, "name", "children");

//     QFile file("merged-exercises.json");
//     file.open(QIODevice::WriteOnly);
//     file.write(QJsonDocument(m_exercises).toJson());
//     file.close();

    return definitionsMerge & exercisesMerge;
64 65
}

66
void ExerciseController::randomlySelectExerciseOptions()
67
{
68 69
    while (!m_selectedExerciseOptions.isEmpty())
        m_selectedExerciseOptions.removeFirst();
70 71 72

    qsrand(QDateTime::currentDateTimeUtc().toTime_t());

73 74
    int minNote = INT_MAX;
    int maxNote = INT_MIN;
75 76
    quint8 numberOfSelectedOptions = m_currentExercise[QStringLiteral("numberOfSelectedOptions")].toInt();
    for (quint8 i = 0; i < numberOfSelectedOptions; ++i) {
77 78
        QJsonArray exerciseOptions = m_currentExercise[QStringLiteral("options")].toArray();
        quint8 chosenExerciseOption = qrand() % exerciseOptions.size();
79

80
        QString sequence = exerciseOptions[chosenExerciseOption].toObject()[QStringLiteral("sequence")].toString();
81 82 83 84
        foreach(const QString &additionalNote, sequence.split(' ')) {
            int note = additionalNote.toInt();
            if (note > maxNote) maxNote = note;
            if (note < minNote) minNote = note;
85
        }
86 87 88 89 90 91 92 93
        if (m_currentExercise["playMode"].toString() != "rhythm") {
            QStringList exerciseRoots = m_currentExercise["root"].toString().split('.');
            quint8 exerciseMinRoot = exerciseRoots.first().toInt();
            quint8 exerciseMaxRoot = exerciseRoots.last().toInt();
            do
                m_chosenRootNote = exerciseMinRoot + qrand() % (exerciseMaxRoot - exerciseMinRoot);
            while (m_chosenRootNote + maxNote > 108 || m_chosenRootNote + minNote < 21);
        }
94

95
        QJsonObject jsonObject = exerciseOptions[chosenExerciseOption].toObject();
96
        jsonObject["rootNote"] = QString::number(m_chosenRootNote);
97
        exerciseOptions[chosenExerciseOption] = jsonObject;
98
        m_selectedExerciseOptions.append(exerciseOptions[chosenExerciseOption]);
99
    }
100
    emit selectedExerciseOptionsChanged(m_selectedExerciseOptions);
101 102 103 104 105 106 107
}

unsigned int ExerciseController::chosenRootNote()
{
    return m_chosenRootNote;
}

108 109 110 111 112
QString ExerciseController::errorString() const
{
    return m_errorString;
}

113
QJsonArray ExerciseController::exercises() const
114
{
115
    return m_exercises[QStringLiteral("exercises")].toArray();
116 117
}

118
bool ExerciseController::mergeJsonFiles(const QString directoryName, QJsonObject &targetObject, bool applyDefinitionsFlag, QString commonKey, QString mergeKey)
119 120
{
    m_errorString.clear();
121 122 123 124 125 126 127
    QStringList jsonDirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, directoryName, QStandardPaths::LocateDirectory);
    foreach (const QString &jsonDirString, jsonDirs) {
        QDir jsonDir(jsonDirString);
        foreach (const QString &json, jsonDir.entryList(QDir::Files)) {
            QFile jsonFile(jsonDir.absoluteFilePath(json));
            if (!jsonFile.open(QIODevice::ReadOnly)) {
                m_errorString = i18n("Couldn't open json file \"%1\".", jsonDir.absoluteFilePath(json));
128 129 130
                return false;
            }
            QJsonParseError error;
131
            QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonFile.readAll(), &error);
132 133 134

            if (error.error != QJsonParseError::NoError) {
                m_errorString = error.errorString();
135
                jsonFile.close();
136 137 138
                return false;
            }
            else {
139
                QJsonObject jsonObject = jsonDocument.object();
140 141 142 143 144 145 146 147
                if (applyDefinitionsFlag)
                    jsonObject[directoryName] = applyDefinitions(jsonObject[directoryName].toArray(),
                                                                 m_definitions[QStringLiteral("definitions")].toArray());

                targetObject[directoryName] = mergeJsonArrays(targetObject[directoryName].toArray(),
                                                              jsonObject[directoryName].toArray(),
                                                              commonKey,
                                                              mergeKey);
148
            }
149
            jsonFile.close();
150 151 152 153 154
        }
    }
    return true;
}

155
QJsonArray ExerciseController::applyDefinitions(QJsonArray exercises, QJsonArray definitions, QJsonObject collectedProperties)
156
{
157 158 159
    QJsonArray::const_iterator exercisesBegin = exercises.constBegin();
    QJsonArray::const_iterator exercisesEnd = exercises.constEnd();
    for (QJsonArray::ConstIterator i1 = exercisesBegin; i1 < exercisesEnd; ++i1) {
160
        if (i1->isObject()) {
161
            QJsonObject exerciseObject = i1->toObject();
162
            QJsonArray filteredDefinitions = definitions;
163
            QStringList exerciseObjectKeys = exerciseObject.keys();
164 165 166 167
            if (exerciseObjectKeys.contains(QStringLiteral("and-tags")) && exerciseObject[QStringLiteral("and-tags")].isArray())
                filterDefinitions(filteredDefinitions, exerciseObject, "and-tags", AndFiltering);
            if (exerciseObjectKeys.contains(QStringLiteral("or-tags")) && exerciseObject[QStringLiteral("or-tags")].isArray())
                filterDefinitions(filteredDefinitions, exerciseObject, "or-tags", OrFiltering);
168 169 170 171 172 173
            if (exerciseObjectKeys.contains(QStringLiteral("children"))) {
                foreach(const QString &key, exerciseObjectKeys)
                    if (key != "name" && key != "children" && key != "and-tags" && key != "or-tags") {
                        collectedProperties.insert(key, exerciseObject[key]);
                        exerciseObject.remove(key);
                    }
174
                exerciseObject[QStringLiteral("children")] = applyDefinitions(exerciseObject[QStringLiteral("children")].toArray(),
175 176 177 178
                                                                              filteredDefinitions, collectedProperties);
            }
            else {
                foreach(const QString &key, collectedProperties.keys())
179 180
                    if (!exerciseObject.contains(key))
                        exerciseObject.insert(key, collectedProperties[key]);
181
                exerciseObject.insert("options", filteredDefinitions);
182
            }
183
            exercises[i1-exercisesBegin] = exerciseObject;
184 185 186
        }
    }
    return exercises;
187 188
}

189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
void ExerciseController::filterDefinitions(QJsonArray &definitions, QJsonObject &exerciseObject, const QString &filterTagsKey, DefinitionFilteringMode definitionFilteringMode)
{
    QJsonArray filterTags = exerciseObject[filterTagsKey].toArray();
    exerciseObject.remove(filterTagsKey);
    for (QJsonArray::Iterator i2 = definitions.begin(); i2 < definitions.end(); ++i2) {
        bool remove = (definitionFilteringMode == AndFiltering) ? false:true;
        QJsonArray::const_iterator filterTagsEnd = filterTags.constEnd();
        for (QJsonArray::ConstIterator i3 = filterTags.constBegin(); i3 < filterTagsEnd; ++i3) {
            QJsonArray tagArray = i2->toObject()["tags"].toArray();
            if (definitionFilteringMode == AndFiltering && !tagArray.contains(*i3)) {
                remove = true;
                break;
            }
            if (definitionFilteringMode == OrFiltering && tagArray.contains(*i3))
                remove = false;
        }
        if (remove) {
            i2 = definitions.erase(i2);
            i2--;
        }
    }
}

QJsonArray ExerciseController::mergeJsonArrays(QJsonArray oldFile, QJsonArray newFile, QString commonKey, QString mergeKey)
213
{
214 215
    QJsonArray::const_iterator newFileEnd = newFile.constEnd();;
    for (QJsonArray::ConstIterator i1 = newFile.constBegin(); i1 < newFileEnd; ++i1) {
216 217
        if (i1->isObject()) {
            QJsonArray::ConstIterator i2;
218 219
            QJsonArray::const_iterator oldFileEnd = oldFile.constEnd();
            for (i2 = oldFile.constBegin(); i2 < oldFileEnd; ++i2) {
220
                QJsonObject newFileObject = i1->toObject();
221
                QJsonObject oldFileObject = i2->toObject();
222 223 224 225 226 227 228 229 230 231 232
                if (i2->isObject() &&
                    i1->isObject() &&
                    !commonKey.isEmpty() &&
                    oldFileObject[commonKey] == newFileObject[commonKey]) {
                        QJsonObject jsonObject = oldFile[i2-oldFile.constBegin()].toObject();
                        jsonObject[mergeKey] = mergeJsonArrays(oldFileObject[mergeKey].toArray(),
                                                            newFileObject[mergeKey].toArray(),
                                                            commonKey,
                                                            mergeKey);
                        oldFile[i2-oldFile.constBegin()] = jsonObject;
                        break;
233 234
                }
            }
235
            if (i2 == oldFile.constEnd())
236
                oldFile.append(*i1);
237 238
        }
    }
239
    return oldFile;
240 241 242
}

}