Skip to content
Snippets Groups Projects
texam.cpp 26.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • SeeLook's avatar
    SeeLook committed
    /***************************************************************************
    
     *   Copyright (C) 2011-2021 by Tomasz Bojczuk                             *
    
     *   seelook@gmail.com                                                     *
    
    SeeLook's avatar
    SeeLook committed
     *                                                                         *
     *   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 3 of the License, or     *
     *   (at your option) any later version.                                   *
     *                                                                         *
     *   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/>.  *
     ***************************************************************************/
    
    #include "texam.h"
    
    #include <QtCore/qdatastream.h>
    #include <QtCore/qdatetime.h>
    #include <QtCore/qdir.h>
    
    #include <QtCore/qrandom.h>
    
    #include <QtCore/qsettings.h>
    
    #if defined(Q_OS_ANDROID)
    #include <Android/tandroid.h>
    
    SeeLook's avatar
    SeeLook committed
    
    /*static*/
    
    SeeLook's avatar
    SeeLook committed
     * 2. 0x95121704; (2012.07)
    
    SeeLook's avatar
    SeeLook committed
     *
     * 3. 0x95121706 (2013.12.02)
    
     *    - encoded XML structure
     *
     * 5. 0x9512170A (05.02.2018)
     *    - new instruments, melodies in Music XML, new mistake types - it can not be compatible with older versions
    
     *
     * 6. 0x9512170C (09.06.2021)
     *    - ukulele support - opening with previous versions would cause problems
     *
    
    SeeLook's avatar
    SeeLook committed
     */
    
    const qint32 Texam::examVersion = 0x95121702;
    const qint32 Texam::examVersion2 = 0x95121704;
    
    const qint32 Texam::currentVersion = 0x9512170C; // version 5
    
    SeeLook's avatar
    SeeLook committed
    
    const quint16 Texam::maxAnswerTime = 65500;
    
    
    int Texam::examVersionNr(qint32 ver)
    {
        if ((ver - examVersion) % 2)
            return -1; // invalid when rest of division is 1
        return ((ver - examVersion) / 2) + 1;
    
    SeeLook's avatar
    SeeLook committed
    }
    
    
    bool Texam::couldBeExam(qint32 ver)
    {
        int givenVersion = examVersionNr(ver);
        if (givenVersion >= 1 && givenVersion <= 127)
            return true;
        else
            return false;
    
    SeeLook's avatar
    SeeLook committed
    }
    
    
    bool Texam::isExamVersion(qint32 ver)
    {
        if (examVersionNr(ver) <= examVersionNr(currentVersion))
            return true;
        else
            return false;
    
    SeeLook's avatar
    SeeLook committed
    }
    
    
    /** Obsolete! It has no meaning in XML versions (above 3) */
    
    qint32 Texam::examVersionToLevel(qint32 examVer)
    {
        if (examVersionNr(examVer) <= 2)
            return Tlevel::getVersionId(1); // level version 1 for exam versions 1 and 2
        else
            return Tlevel::getVersionId(2); // level version 2 for exam versions 3 and so
    
    SeeLook's avatar
    SeeLook committed
    }
    
    
    bool Texam::areQuestTheSame(TQAunit *q1, TQAunit *q2)
    {
        if (q1->questionAs == q2->questionAs && // the same questions
            q1->answerAs == q2->answerAs && // the same answers
            q1->qa.note == q2->qa.note && // the same notes
            q1->qa.pos() == q2->qa.pos() // the same frets
        )
            return true;
        else
            return false;
    
    SeeLook's avatar
    SeeLook committed
    }
    
    
    QString Texam::formatReactTime(quint16 timeX10, bool withUnit)
    {
        QString hh, mm, ss;
        int dig = 0;
        if (timeX10 / 36000) {
            hh = QString("%1").arg(timeX10 / 36000);
            dig = 2;
        }
        int dig2 = 0;
        if ((timeX10 % 36000) / 600) {
            mm = QString("%1").arg((timeX10 % 36000) / 600, dig, 'i', 0, '0');
            dig2 = 2;
        }
        ss = QString("%1").arg(((timeX10 % 36000) % 600) / 10, dig2, 'i', 0, '0');
        QString res;
        if (!hh.isEmpty())
            res = hh + QLatin1String(":");
        if (!mm.isEmpty())
            res += mm + QLatin1String(":");
        QString unitS;
        if (withUnit && timeX10 < 600)
            unitS = QLatin1String(" s");
        return res + ss + QString(".%1").arg(timeX10 % 10) + unitS;
    
    SeeLook's avatar
    SeeLook committed
    }
    
    /*end of static*/
    
    
    // #################################################################################################
    // ###################             CONSTRUCTOR          ############################################
    // #################################################################################################
    Texam::Texam(Tlevel *l, const QString &userName)
        : m_fileName(QString())
        , m_userName(userName)
        , m_totalTime(0)
        , m_attempts(0)
        , m_mistNr(0)
        , m_tmpMist(0)
        , m_averReactTime(0)
        , m_workTime(0)
        , m_halfMistNr(0)
        , m_tmpHalf(0)
        , m_isFinished(false)
        , m_melody(false)
        , m_isExercise(false)
        , m_penaltysNr(0)
        , m_blackCount(0)
        , m_okTime(0)
        , m_effectivenes(0.0)
        , m_skippedUnit(nullptr)
    
    SeeLook's avatar
    SeeLook committed
    {
    
    SeeLook's avatar
    SeeLook committed
    }
    
    Texam::~Texam()
    {
    
        clearAnswList();
        m_blackList.clear();
        m_blackNumbers.clear();
        if (m_skippedUnit)
            delete m_skippedUnit;
    
    // #################################################################################################
    // ###################                PUBLIC            ############################################
    // #################################################################################################
    void Texam::setExercise()
    {
        if (count()) {
            qDebug() << "[Texam] Exam has got questions already. Can't set it as an exercise!";
            return;
        }
        setFileName(QDir::toNativeSeparators(QFileInfo(GLOB->config->fileName()).absolutePath() + QLatin1String("/exercise2.noo")));
        m_isExercise = true;
    
    SeeLook's avatar
    SeeLook committed
    }
    
    
    void Texam::setLevel(Tlevel *l)
    {
        m_level = l;
        m_melody = l->canBeMelody();
    
    void Texam::setFileName(const QString &fileName)
    {
        if (isExercise()) {
            qDebug() << "[Texam] Can not set a file name for exercise";
            return;
        }
        m_fileName = fileName;
    
    void Texam::skipLast(bool skip)
    {
        if (skip != (bool)m_skippedUnit) {
            if (skip) {
                if (m_skippedUnit)
                    qDebug() << "[Texam] Previously skipped question unit will be overridden by newly skipped.";
                delete m_skippedUnit;
                m_skippedUnit = m_answList.takeLast();
            } else {
                if (!m_skippedUnit)
                    qDebug() << "[Texam] There is no skipped unit to revert it back!";
                else {
                    m_answList << m_skippedUnit;
                    m_skippedUnit = 0;
                }
    
    Texam::EerrorType Texam::loadFromFile(const QString &fileName)
    {
    #if defined(Q_OS_ANDROID)
        Tandroid::askForWriteAcces();
    
        m_okTime = 0;
        m_tmpMist = 0;
        m_tmpHalf = 0;
        m_fileName = fileName;
        QFile file(fileName);
        m_workTime = 0;
        m_mistNr = 0;
        m_blackCount = 0;
        m_attempts = 0;
        m_isExercise = false;
        m_blackList.clear();
        clearAnswList();
        EerrorType result = e_file_OK;
        quint32 ev; // exam template version
        if (file.open(QIODevice::ReadOnly)) {
            QDataStream in(&file);
            in >> ev;
            if (couldBeExam(ev)) {
                if (!isExamVersion(ev)) {
                    qDebug() << "[Texam] Exam file" << fileName << "created with newer Nootka version";
                    GLOB->warnAboutNewerVersion(fileName);
                    return e_newerVersion;
                }
            } else
                return e_file_not_valid;
    
            bool isExamFileOk = true;
            if (examVersionNr(ev) > 3) {
                if (examVersionNr(ev) > 5)
                    in.setVersion(QDataStream::Qt_5_9);
                else
                    in.setVersion(QDataStream::Qt_5_2);
                QByteArray arrayXML = file.readAll();
                arrayXML.remove(0, 4);
                QByteArray unZipXml = qUncompress(arrayXML);
                if (!unZipXml.isEmpty()) {
                    QXmlStreamReader xml(unZipXml);
                    isExamFileOk = loadFromXml(xml);
                } else {
                    qDebug() << "[Texam] Problems with decompressing exam file";
                    return e_file_not_valid;
                }
            } else {
                in.setVersion(QDataStream::Qt_4_7);
                isExamFileOk = loadFromBin(in, ev);
            }
    
    SeeLook's avatar
    SeeLook committed
    
    
            m_melody = m_level->canBeMelody();
            updateEffectiveness();
            updateAverageReactTime(true);
    
    SeeLook's avatar
    SeeLook committed
    
    
            if (!isExamFileOk)
                result = e_file_corrupted;
            file.close();
        } else {
            Tlevel::fileIOerrorMsg(file);
            result = e_cant_open;
    
        if (m_level->clef.type() == Tclef::Bass_F_8down) {
            qDebug() << "[Texam] OBSOLETE bass dropped clef detected. Converting exam to ordinary bass clef.";
            transposeAfterBassDropped();
        }
    
    bool Texam::loadFromBin(QDataStream &in, quint32 ev)
    {
        quint16 questNr;
        bool isExamFileOk = true;
        in >> m_userName;
        getLevelFromStream(in, *(m_level), examVersionToLevel(ev));
        in >> m_tune;
        in >> m_totalTime;
        in >> questNr >> m_averReactTime >> m_mistNr;
        if (examVersionNr(ev) >= 2) {
            in >> m_halfMistNr >> m_penaltysNr >> m_isFinished;
        } else { // exam version 1
            m_halfMistNr = 0;
            m_penaltysNr = 0;
            m_isFinished = false;
    
        while (!in.atEnd()) {
            TQAunit qaUnit;
            if (!getTQAunitFromStream(in, qaUnit))
                isExamFileOk = false;
            if ((qaUnit.questionAs == TQAtype::e_asName || qaUnit.answerAs == TQAtype::e_asName) && qaUnit.styleOfQuestion() < 0) {
                qaUnit.setStyle(static_cast<Tnote::EnameStyle>(GLOB->noteNameStyle()), qaUnit.styleOfAnswer());
            } /** In old versions, style was set to 0 so now it gives styleOfQuestion = -1
               * Also in transition Nootka versions it was left unchanged.
               * Unfixed it invokes stupid names in charts.
               * We are fixing it by insert user preferred style of naming */
            if (qaUnit.time <= maxAnswerTime || ev == examVersion) { // add to m_answList
                m_answList << new TQAunit(qaUnit);
                grabFromLastUnit();
            } else { // add to m_blackList
                m_blackList << qaUnit;
            }
        }
        if (!checkQuestionNumber(questNr)) {
            isExamFileOk = false;
        }
        if (examVersionNr(ev) >= 2 && (m_tmpMist != m_mistNr || m_tmpHalf != m_halfMistNr)) {
            m_mistNr = m_tmpMist; // we try to fix exam file to give proper number of mistakes
            m_halfMistNr = m_tmpHalf;
            isExamFileOk = false;
        } else {
            m_mistNr = m_tmpMist; // transition to exam version 2
        }
        if (ev == examVersion) {
            convertToVersion2();
            m_halfMistNr = m_tmpHalf;
        }
        return isExamFileOk;
    }
    
    bool Texam::loadFromXml(QXmlStreamReader &xml)
    {
        bool ok = true;
        int questNr = 0;
        if (xml.readNextStartElement()) {
            if (xml.name() != QLatin1String("exam")) {
                qDebug() << "[Texam] There is no 'exam' key in that XML";
                return false;
            }
            m_userName = xml.attributes().value(QStringLiteral("user")).toString();
            if (m_userName.isEmpty() || m_userName.size() > 30) {
                qDebug() << "[Texam] Exam has wrong user name";
                return false;
            }
    
        while (xml.readNextStartElement()) {
            if (xml.name() == QLatin1String("head")) {
                while (xml.readNextStartElement()) {
                    if (xml.name() == QLatin1String("level")) {
                        auto err = m_level->loadFromXml(xml);
                        if (err != Tlevel::e_level_OK) {
                            qDebug() << "[Texam] Exam has wrong level definition" << static_cast<int>(err);
                            ok = false;
                        }
                    } else if (xml.name() == QLatin1String("tuning")) {
                        if (!m_tune.fromXml(xml, true)) {
                            qDebug() << "[Texam] Exam has wrong tuning";
                            ok = false;
                        }
                    } else if (xml.name() == QLatin1String("totalTime"))
                        m_totalTime = xml.readElementText().toInt();
                    else if (xml.name() == QLatin1String("questNr"))
                        questNr = xml.readElementText().toInt();
                    else if (xml.name() == QLatin1String("averReactTime"))
                        m_averReactTime = xml.readElementText().toInt();
                    else if (xml.name() == QLatin1String("mistNr"))
                        m_mistNr = xml.readElementText().toInt();
                    else if (xml.name() == QLatin1String("halfMistNr"))
                        m_halfMistNr = xml.readElementText().toInt();
                    else if (xml.name() == QLatin1String("penaltysNr"))
                        m_penaltysNr = xml.readElementText().toInt();
                    else if (xml.name() == QLatin1String("finished"))
                        m_isFinished = QVariant(xml.readElementText()).toBool();
                    else if (xml.name() == QLatin1String("exercise")) {
                        m_isExercise = true;
                        xml.skipCurrentElement();
                    } else
                        Tlevel::skipCurrentXmlKey(xml);
                }
            } else if (xml.name() == QLatin1String("answers")) {
                if (!readAnswerFromXml(m_answList, xml))
    
            } else if (xml.name() == QLatin1String("penalties")) {
                if (!readPenaltyFromXml(m_blackList, xml))
    
            } else if (xml.name() == QLatin1String("black")) {
                m_blackNumbers.clear();
                while (xml.readNextStartElement()) {
    
                    if (xml.name() == QLatin1String("n"))
    
                        m_blackNumbers << xml.readElementText().toInt();
                    else
                        Tlevel::skipCurrentXmlKey(xml);
                }
            } else
    
        }
        if (!checkQuestionNumber(questNr)) {
            ok = false;
        }
        if (m_tmpMist != m_mistNr || m_tmpHalf != m_halfMistNr) {
            if (m_tmpMist != m_mistNr)
                qDebug() << "[Texam] Mistakes number do not match. Exam file corrupted!" << m_tmpMist << m_mistNr;
            else if (m_tmpHalf != m_halfMistNr)
                qDebug() << "[Texam] 'Not bad' number do not match. Exam file corrupted!" << m_tmpHalf << m_halfMistNr;
            m_mistNr = m_tmpMist; // we try to fix exam file to give proper number of mistakes
            m_halfMistNr = m_tmpHalf;
            ok = false;
        }
        return ok;
    
    Texam::EerrorType Texam::saveToFile(const QString &fileName)
    {
        if (!fileName.isEmpty())
            setFileName(fileName); // m_fileName becomes fileName
        if (m_fileName.isEmpty())
            return e_noFileName;
    
        QFile file(m_fileName);
        if (file.open(QIODevice::WriteOnly)) {
            QDataStream out(&file);
            out.setVersion(QDataStream::Qt_5_9);
            out << currentVersion;
            QByteArray arrayXML;
            QXmlStreamWriter xml(&arrayXML);
            //     xml.setAutoFormatting(true);
            xml.writeStartDocument();
            xml.writeComment(
                "\nXML file of Nootka exam data.\nhttps://nootka.sf.net\nThis file should never be opened in other software then Nootka.\nProbably you are doing "
                "something illegal!");
            writeToXml(xml);
            xml.writeEndDocument();
    
            out << qCompress(arrayXML);
    
            file.close();
        } else {
            QMessageBox::critical(nullptr, QString(), QObject::tr("Cannot save exam file:\n%1").arg(QString::fromLocal8Bit(qPrintable(file.errorString()))));
            return e_cant_open;
        }
        qDebug() << "[Texam] Exam saved to:" << m_fileName;
        return e_file_OK;
    
    SeeLook's avatar
    SeeLook committed
    }
    
    
    void Texam::writeToXml(QXmlStreamWriter &xml)
    {
        xml.writeStartElement(QStringLiteral("exam"));
    
        xml.writeAttribute(QStringLiteral("user"), m_userName);
        xml.writeStartElement(QStringLiteral("head"));
    
        m_level->writeToXml(xml);
        m_tune.toXml(xml, true);
        xml.writeTextElement(QStringLiteral("totalTime"), QVariant(m_totalTime).toString());
        xml.writeTextElement(QStringLiteral("questNr"), QVariant(count()).toString());
        xml.writeTextElement(QStringLiteral("averReactTime"), QVariant(m_averReactTime).toString());
        xml.writeTextElement(QStringLiteral("mistNr"), QVariant(m_mistNr).toString());
        xml.writeTextElement(QStringLiteral("halfMistNr"), QVariant(m_halfMistNr).toString());
        xml.writeTextElement(QStringLiteral("penaltysNr"), QVariant(m_penaltysNr).toString());
        xml.writeTextElement(QStringLiteral("finished"), QVariant(m_isFinished).toString());
        if (isExercise())
    
            xml.writeEmptyElement(QStringLiteral("exercise"));
    
        xml.writeStartElement(QStringLiteral("answers"));
    
        xml.writeEndElement(); // answers
        if (m_blackList.size()) {
    
            xml.writeStartElement(QStringLiteral("penalties"));
            for (int i = 0; i < m_blackList.size(); ++i)
    
            xml.writeEndElement(); // penalties
    
            xml.writeStartElement(QStringLiteral("black"));
            for (int i = 0; i < m_blackNumbers.size(); ++i)
    
                xml.writeTextElement(QStringLiteral("n"), QString::number(m_blackNumbers[i]));
    
            xml.writeEndElement(); // penalties
    
        xml.writeEndElement(); // exam
    
    void Texam::newAttempt()
    {
        curQ()->newAttempt();
        if (curQ()->attemptsCount() > 1) { // unset answered and revert mistakes - user tries once more
            if (curQ()->isNotSoBad())
                m_halfMistNr--;
            else if (curQ()->isWrong())
                m_mistNr--;
            else
                qDebug() << "[Texam] A new attempt called for correct answer!";
            curQ()->unsetAnswered();
    
    void Texam::sumarizeAnswer()
    {
        curQ()->updateEffectiveness();
        curQ()->time = qMin(maxAnswerTime, curQ()->time); // when user think too much
        if (melodies()) {
            m_workTime += curQ()->lastAttempt()->totalTime();
            if (!curQ()->isWrong()) {
                if (curQ()->effectiveness() < 50)
                    curQ()->setMistake(TQAunit::e_veryPoor);
                else if (curQ()->effectiveness() < 70)
                    curQ()->setMistake(TQAunit::e_poorEffect);
            }
            m_attempts++;
        }
        updateAverageReactTime(true);
        if (melodies()) {
            if (curQ()->isNotSoBad())
                m_halfMistNr++;
            else if (curQ()->isWrong())
                m_mistNr++;
    
            addPenalties(); // for melodies it should be invoked after ensuring that answer was finished
            if (!isExercise())
                updateBlackCount();
            m_workTime += curQ()->time;
    
    void Texam::addPenalties()
    {
        if (!curQ()->isCorrect()) {
            if (melodies()) // for any kind of mistake add one more random melody or mistaken one if not random and melody set
                m_blackNumbers.append(m_level->isMelodySet() && !m_level->randOrderInSet ? count() - 1 : -1);
            if (curQ()->isNotSoBad()) {
                if (!isExercise() && !isFinished())
                    m_penaltysNr++;
                if (!melodies())
                    m_halfMistNr++;
            } else {
                if (melodies())
                    m_blackNumbers.append(count() - 1); // repeat current melody in some further question
                if (!isExercise() && !isFinished())
                    m_penaltysNr += 2;
                if (!melodies())
                    m_mistNr++;
            }
    
    void Texam::updateEffectiveness()
    {
        qreal sum = 0.0;
        for (int i = 0; i < count(); ++i)
            sum += answList()->at(i)->effectiveness();
        m_effectivenes = sum / (qreal)count();
    
    SeeLook's avatar
    SeeLook committed
    }
    
    
    void Texam::updateAverageReactTime(bool skipWrong)
    {
        int totalTime = 0;
        int cnt = 0;
        for (int i = 0; i < count(); ++i) {
            if (!skipWrong || (skipWrong && !m_answList[i]->isWrong())) {
                totalTime += m_answList[i]->time;
                cnt++;
    
        }
        if (cnt)
            m_averReactTime = totalTime / cnt;
        else
            m_averReactTime = 0;
    
    // #################################################################################################
    // ###################              PROTECTED           ############################################
    // #################################################################################################
    
    void Texam::updateBlackCount()
    {
        m_blackCount = 0;
        if (m_blackList.size()) {
            for (int i = 0; i < m_blackList.size(); i++)
                m_blackCount += (m_blackList[i].time - maxAnswerTime);
        }
    
    bool Texam::readPenaltyFromXml(QList<TQAunit> &blackList, QXmlStreamReader &xml)
    {
        bool ok = true;
        while (xml.readNextStartElement()) {
            if (xml.name() == QLatin1String("u")) {
                blackList << TQAunit(this);
                if (!blackList.last().fromXml(xml)) {
                    qDebug() << "[Texam] Exam has wrong unit" << blackList.size();
                    blackList.removeLast();
                    ok = false;
                }
            } else
                Tlevel::skipCurrentXmlKey(xml);
    
    bool Texam::readAnswerFromXml(QList<TQAunit *> &list, QXmlStreamReader &xml)
    {
        bool ok = true;
        while (xml.readNextStartElement()) {
            if (xml.name() == QLatin1String("u")) {
                list << new TQAunit(this);
                if (list.last()->fromXml(xml)) {
                    grabFromLastUnit();
                    if (melodies())
                        m_attempts += curQ()->attemptsCount();
                } else {
                    qDebug() << "[Texam] Exam has wrong unit" << list.size();
                    list.removeLast();
                    ok = false;
                }
            } else
                Tlevel::skipCurrentXmlKey(xml);
        }
        return ok;
    
    void Texam::grabFromLastUnit()
    {
        m_workTime += curQ()->time;
        if (!curQ()->isCorrect()) {
            if (curQ()->isWrong())
                m_tmpMist++;
            else
                m_tmpHalf++; // not so bad answer
        }
        if (!curQ()->isWrong())
            m_okTime += curQ()->time;
    
    bool Texam::checkQuestionNumber(int questNr)
    {
        bool ok = true;
        if (questNr != m_answList.size()) {
            qDebug() << "[Texam] Exam questions number read from file" << questNr << "and those calculated" << m_answList.size()
                     << "do not match. Exam file corrupted.";
            ok = false;
        }
        return ok;
    }
    
    void Texam::clearAnswList()
    {
        for (int i = 0; i < m_answList.size(); ++i)
            delete m_answList[i];
        m_answList.clear();
    
    void Texam::transposeAfterBassDropped()
    {
        if (m_tune.type() == Ttune::Custom)
            m_tune.riseOctaveUp();
        m_level->convFromDropedBass();
        for (int a = 0; a < m_answList.size(); ++a)
            m_answList[a]->riseOctaveUp();
        for (int b = 0; b < m_blackList.size(); ++b)
            m_blackList[b].riseOctaveUp();
    }
    
    
    /**
     * This method exist for backward compatibility but is has very rare usage in 'modern' Nootka times
     */
    
    void Texam::convertToVersion2()
    {
        bool hasStyle = false;
        Tnote::EnameStyle randStyles[3];
        if (m_level->canBeName()) {
            // version 1 didn't put proper Tnote::EnameStyle to file - we fixing it
            hasStyle = true;
            qDebug() << "[Texam] Fixing styles of note names in file";
            if (m_level->requireStyle) { // prepare styles array to imitate switching
                randStyles[0] = Tnote::e_italiano_Si;
                if (GLOB->seventhIsB()) {
                    randStyles[1] = Tnote::e_english_Bb;
                    randStyles[2] = Tnote::e_nederl_Bis;
                } else {
                    randStyles[1] = Tnote::e_norsk_Hb;
                    randStyles[2] = Tnote::e_deutsch_His;
                }
            }
    
    SeeLook's avatar
    SeeLook committed
        }
    
        for (int i = 0; i < m_answList.size(); i++) {
            if (m_answList[i]->time > maxAnswerTime) // fix too long times from version 1 if any
                m_answList[i]->time = maxAnswerTime;
            // version 1 didn't put proper Tnote::EnameStyle to file - we fixing it
            if (hasStyle) {
                if (m_level->requireStyle) {
                    if (m_answList[i]->questionAs == TQAtype::e_asName && m_answList[i]->answerAs == TQAtype::e_asName) {
                        Tnote::EnameStyle qSt = randStyles[QRandomGenerator::global()->bounded(3)];
                        Tnote::EnameStyle aSt;
                        if (qSt == Tnote::e_italiano_Si)
                            aSt = randStyles[QRandomGenerator::global()->bounded(2) + 1];
                        else
                            aSt = Tnote::e_italiano_Si;
                        m_answList[i]->setStyle(qSt, aSt);
                    } else if (m_answList[i]->questionAs == TQAtype::e_asName) {
                        m_answList[i]->setStyle(randStyles[QRandomGenerator::global()->bounded(3)], static_cast<Tnote::EnameStyle>(GLOB->noteNameStyle()));
                    } else {
                        if (m_answList[i]->questionAs == TQAtype::e_asName)
                            m_answList[i]->setStyle(static_cast<Tnote::EnameStyle>(GLOB->noteNameStyle()), randStyles[QRandomGenerator::global()->bounded(3)]);
                    }
                } else // fixed style - we changing to user preferred
                    m_answList[i]->setStyle(static_cast<Tnote::EnameStyle>(GLOB->noteNameStyle()), static_cast<Tnote::EnameStyle>(GLOB->noteNameStyle()));
            }
    
            if (!m_answList[i]->isCorrect()) {
                quint16 penCnt = 0; // counts of penalties
                if (m_answList[i]->isWrong()) {
                    if (i < (m_answList.size() - 1) && areQuestTheSame(m_answList[i], m_answList[i + 1])) {
                        // there was next question repeated
                        if (m_answList[i + 1]->isCorrect()) // and was correct
                            penCnt = 65501; // so add one penalty
                        else // when again wrong
                            penCnt = 65502; // add two
                        // The next loop will add next two penalties !!
                    } else // question was not repeated
                        penCnt = 65502;
                } else { // not so bad
                    if (i < (m_answList.size() - 1) && areQuestTheSame(m_answList[i], m_answList[i + 1])) {
                        // there was next question repeated
                        if (m_answList[i + 1]->isCorrect()) // and was correct
                            penCnt = 0;
                        else
                            penCnt = 65501;
                    }
                }
                if (penCnt) {
                    m_blackList << *m_answList[i];
                    m_blackList.last().time = penCnt;
                    m_penaltysNr += (penCnt - 65500);
                }
            }
    
    SeeLook's avatar
    SeeLook committed
        }
    }