Skip to content
Snippets Groups Projects
tlevelcreatoritem.cpp 31.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • /***************************************************************************
    
     *   Copyright (C) 2017-2021 by Tomasz Bojczuk                             *
    
     *   seelook@gmail.com                                                     *
     *                                                                         *
     *   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 "tlevelcreatoritem.h"
    
    #include "tlevelselector.h"
    #include <Android/tfiledialog.h>
    
    #include <music/ttune.h>
    #include <texamparams.h>
    
    #include <tglobals.h>
    #if defined(Q_OS_ANDROID)
    #include <Android/tandroid.h>
    
    #include <QtCore/qfileinfo.h>
    #include <QtCore/qtimer.h>
    
    TlevelCreatorItem::TlevelCreatorItem(QQuickItem *parent)
        : QQuickItem(parent)
    
        m_level = new Tlevel();
        m_level->name.clear();
        m_level->desc.clear();
        m_title = tr("Level creator");
        m_answersList << m_level->answersAs[0].value() << m_level->answersAs[1].value() << m_level->answersAs[2].value() << m_level->answersAs[3].value();
    
        QTimer::singleShot(500, this, [=] {
            emit updateLevel();
        });
    
    void TlevelCreatorItem::setSelector(TlevelSelector *sel)
    {
        if (sel != m_selector) {
            m_selector = sel;
            if (m_selector)
                connect(m_selector, &TlevelSelector::levelChanged, this, &TlevelCreatorItem::whenLevelChanged);
        }
    
    QString TlevelCreatorItem::title() const
    {
        return m_title + m_titleExtension;
    }
    
    bool TlevelCreatorItem::notSaved() const
    {
        return !m_titleExtension.isEmpty();
    
    void TlevelCreatorItem::saveLevel()
    {
        emit save();
        if (!m_level->canBeInstr() && !m_level->answerIsSound()) { // no guitar and no played sound
            // adjust fret range - validation will skip it for non guitar levels
            m_level->loFret = 0; // Set range to fret number and rest will be done by function preparing question list
            m_level->hiFret = GLOB->GfretsNumber;
            m_level->onlyLowPos = true; // otherwise the above invokes doubled/tripled questions in the list
            // set all strings as available
            for (int str = 0; str < 6; str++)
                m_level->usedStrings[str] = true;
        }
        auto validMessage = validateLevel();
        if (!validMessage.isEmpty()) {
            showValidationMessage(validMessage);
            return;
        }
        // set instrument to none when it is not important for the level
        m_level->instrument = m_level->detectInstrument(GLOB->instrument().type());
        // invoke QML routines
        bool isWrong = m_level->desc.contains(QStringLiteral("<font color=\"red\">"));
        // but do not display description of not suitable level
        emit saveNewLevel(m_level->name, isWrong ? QString() : m_level->desc.replace(QLatin1String("<br>"), QLatin1String("\n")));
    }
    
    void TlevelCreatorItem::continueLevelSave(const QString &name, const QString &desc)
    {
        m_level->name = name;
        m_level->desc = desc;
    
    #if defined(Q_OS_ANDROID)
    
        if (GLOB->examParams->levelsDir.isEmpty())
            GLOB->examParams->levelsDir = Tandroid::getExternalPath();
    
        // Saving to file
        QLatin1String dotNel(".nel");
        bool toTr = name.startsWith(QLatin1String("tr("));
        QString fName = QDir::toNativeSeparators(GLOB->examParams->levelsDir + QLatin1String("/") + m_level->name.mid(toTr ? 3 : 0));
        fName = fName.replace(QLatin1String("."), QString()); // HACK: file dialogues don't like dots in the names
        if (QFileInfo::exists(fName + dotNel))
            fName += QLatin1String("-") + QDateTime::currentDateTime().toString(QLatin1String("(dd-MMM-hhmmss)"));
    #if defined(Q_OS_ANDROID)
        QString fileName = TfileDialog::getSaveFileName(fName, QStringLiteral("nel"));
    
        QString fileName = TfileDialog::getSaveFileName(tr("Save exam level"), fName, TlevelSelector::levelFilterTxt() + QLatin1String(" (*.nel)"));
    
        if (fileName.isEmpty()) {
            qDebug() << "[TlevelCreatorItem] Empty file name!";
            if (m_resumeAfterLevelChange)
                resumeAfterLevelChange();
            return;
        }
    
        if (fileName.right(4) != dotNel)
            fileName += dotNel;
        GLOB->examParams->levelsDir = QFileInfo(fileName).absoluteDir().absolutePath();
        if (!Tlevel::saveToFile(*m_level, fileName)) {
            wantNotSavedMessage(QString(), tr("Cannot open file for writing") + QLatin1String("\n") + fileName);
            if (m_resumeAfterLevelChange)
                resumeAfterLevelChange();
            return;
        }
    
        m_titleExtension.clear();
        emit saveStateChanged();
        selector()->addLevel(*m_level, fileName, true);
        selector()->updateRecentLevels(); // Put the file name to the settings list
        emit selector() -> levelsModelChanged();
        emit selector() -> selectLast();
        selector()->showLevel(selector()->currentIndex()); // Refresh level preview after save - it could got another name and description
    
        if (m_resumeAfterLevelChange)
            resumeAfterLevelChange();
    
    void TlevelCreatorItem::checkLevel()
    {
        QStringList validMessage = validateLevel();
        if (validMessage.isEmpty())
            emit wantValidationMessage(tr("Level validation"), QStringList(tr("Level seems to be correct")), Qt::green);
        else
            showValidationMessage(validMessage);
    }
    
    int TlevelCreatorItem::questionAs() const
    {
        return m_level->questionAs.value();
    
    void TlevelCreatorItem::setQuestionAs(int qAs)
    {
        bool prevState = m_level->questionAs.isOnScore();
        bool doEmit = false;
        m_level->questionAs.setOnScore(qAs & 1);
        if (m_level->questionAs.isOnScore() != prevState) {
            doEmit = true;
            if (m_level->questionAs.isOnScore()) { // question checkbox was set, so select all answers
                m_level->answersAs[TQAtype::e_onScore].setValue(15);
                m_answersList[TQAtype::e_onScore] = 15;
            } else { // question was unset, switch off all answers
                m_level->answersAs[TQAtype::e_onScore].setValue(0);
                m_answersList[TQAtype::e_onScore] = 0;
            }
        }
        prevState = m_level->questionAs.isName();
        m_level->questionAs.setAsName(qAs & 2);
        if (m_level->questionAs.isName() != prevState) {
            doEmit = true;
            if (m_level->questionAs.isName()) { // question checkbox was set, so select all answers
                m_level->answersAs[TQAtype::e_asName].setValue(15);
                m_answersList[TQAtype::e_asName] = 15;
            } else { // question was unset, switch off all answers
                m_level->answersAs[TQAtype::e_asName].setValue(0);
                m_answersList[TQAtype::e_asName] = 0;
            }
        }
        prevState = m_level->questionAs.isOnInstr();
        m_level->questionAs.setOnInstr(qAs & 4);
        if (m_level->questionAs.isOnInstr() != prevState) {
            doEmit = true;
            if (m_level->questionAs.isOnInstr()) { // question checkbox was set, so select all answers
                m_level->answersAs[TQAtype::e_onInstr].setValue(15);
                m_answersList[TQAtype::e_onInstr] = 15;
            } else { // question was unset, switch off all answers
                m_level->answersAs[TQAtype::e_onInstr].setValue(0);
                m_answersList[TQAtype::e_onInstr] = 0;
            }
    
        prevState = m_level->questionAs.isSound();
        m_level->questionAs.setAsSound(qAs & 8);
        if (m_level->questionAs.isSound() != prevState) {
            doEmit = true;
            if (m_level->questionAs.isSound()) { // question checkbox was set, so select all answers
                m_level->answersAs[TQAtype::e_asSound].setValue(15);
                m_answersList[TQAtype::e_asSound] = 15;
            } else { // question was unset, switch off all answers
                m_level->answersAs[TQAtype::e_asSound].setValue(0);
                m_answersList[TQAtype::e_asSound] = 0;
            }
        }
        levelParamChanged();
        if (doEmit)
            emit updateLevel();
    
    /** TODO: it is not used, @p setAnswers() is called directly from QML instead */
    void TlevelCreatorItem::setAnswersAs(QList<int> aAs)
    {
        for (int a = 0; a < 4; ++a) {
            m_level->answersAs[a].setValue(aAs[a]);
            switch (a) {
            case 0:
                m_level->questionAs.setOnScore(aAs[a] != 0);
                break;
            case 1:
                m_level->questionAs.setAsName(aAs[a] != 0);
                break;
            case 2:
                m_level->questionAs.setOnInstr(aAs[a] != 0);
                break;
            case 3:
                m_level->questionAs.setAsSound(aAs[a] != 0);
                break;
            }
    
    void TlevelCreatorItem::setAnswers(int questionType, int answersValue)
    {
        if (questionType >= 0 && questionType < 4) {
            m_level->answersAs[questionType].setValue(answersValue);
            m_answersList[questionType] = answersValue;
            switch (questionType) {
            case 0:
                m_level->questionAs.setOnScore(answersValue != 0);
                break;
            case 1:
                m_level->questionAs.setAsName(answersValue != 0);
                break;
            case 2:
                m_level->questionAs.setOnInstr(answersValue != 0);
                break;
            case 3:
                m_level->questionAs.setAsSound(answersValue != 0);
                break;
            }
            levelParamChanged();
            emit updateLevel();
        }
    
    bool TlevelCreatorItem::requireOctave() const
    {
        return m_level->requireOctave;
    }
    void TlevelCreatorItem::setRequireOctave(bool require)
    {
        m_level->requireOctave = require;
        levelParamChanged();
    
    bool TlevelCreatorItem::requireStyle() const
    {
        return m_level->requireStyle;
    }
    void TlevelCreatorItem::setRequireStyle(bool require)
    {
        m_level->requireStyle = require;
        levelParamChanged();
    
    bool TlevelCreatorItem::showStrNr() const
    {
        return m_level->showStrNr;
    }
    void TlevelCreatorItem::setShowStrNr(bool showStr)
    {
        m_level->showStrNr = showStr;
        levelParamChanged();
    
    bool TlevelCreatorItem::onlyLowPos() const
    {
        return m_level->onlyLowPos;
    
    void TlevelCreatorItem::setOnlyLowPos(bool only)
    {
        m_level->onlyLowPos = only;
        levelParamChanged();
    
    bool TlevelCreatorItem::playMelody() const
    {
        return m_level->canBeMelody() && m_level->questionAs.isOnScore() && m_level->answersAs[TQAtype::e_onScore].isSound();
    
    void TlevelCreatorItem::setPlayMelody(bool play)
    {
        m_level->answersAs[TQAtype::e_onScore].setAsSound(play);
        m_level->questionAs.setOnScore(m_level->answersAs[TQAtype::e_onScore].value() != 0);
        m_answersList[TQAtype::e_onScore] = m_level->answersAs[TQAtype::e_onScore].value();
        levelParamChanged();
        emit updateLevel();
    
    bool TlevelCreatorItem::writeMelody() const
    {
        return m_level->canBeMelody() && m_level->questionAs.isSound() && m_level->answersAs[TQAtype::e_asSound].isOnScore();
    
    void TlevelCreatorItem::setWriteMelody(bool write)
    {
        m_level->answersAs[TQAtype::e_asSound].setOnScore(write);
        m_level->questionAs.setAsSound(m_level->answersAs[TQAtype::e_asSound].value() != 0);
        m_answersList[TQAtype::e_asSound] = m_level->answersAs[TQAtype::e_asSound].value();
        levelParamChanged();
        emit updateLevel();
    
    bool TlevelCreatorItem::repeatMelody() const
    {
        return m_level->canBeMelody() && m_level->questionAs.isSound() && m_level->answersAs[TQAtype::e_asSound].isSound();
    }
    void TlevelCreatorItem::setRepeatMelody(bool repeat)
    {
        m_level->answersAs[TQAtype::e_asSound].setAsSound(repeat);
        m_level->questionAs.setAsSound(m_level->answersAs[TQAtype::e_asSound].value() != 0);
        m_answersList[TQAtype::e_asSound] = m_level->answersAs[TQAtype::e_asSound].value();
        levelParamChanged();
        emit updateLevel();
    
    bool TlevelCreatorItem::isMelody() const
    {
        return m_level->melodyLen > 1;
    }
    void TlevelCreatorItem::setIsMelody(bool isMel)
    {
        m_level->melodyLen = isMel ? 2 : 1;
        m_level->questionAs.setValue(0);
        for (int a = 0; a < 4; ++a) {
            m_level->answersAs[a].setValue(0);
            m_answersList[a] = 0;
        }
        levelParamChanged();
        emit updateLevel();
    }
    
    int TlevelCreatorItem::melodyLen() const
    {
        return m_level->melodyLen;
    }
    void TlevelCreatorItem::setMelodyLen(int len)
    {
        if (m_level->melodyLen != len) {
            bool prevRtmState = m_level->useRhythms();
            m_level->melodyLen = len;
            levelParamChanged();
            if (m_level->useRhythms() != prevRtmState)
                emit hasRhythmsChanged();
        }
    
    bool TlevelCreatorItem::endsOnTonic() const
    {
        return m_level->endsOnTonic;
    }
    void TlevelCreatorItem::setEndsOnTonic(bool ends)
    {
        m_level->endsOnTonic = ends;
        levelParamChanged();
    
     * Due to @p Tlevel::EhowGetMelody enumerator has values 1, 2, 4 (power of 2) we need to convert them to ordered indexes: 0, 1, 2
    
    int TlevelCreatorItem::howGetMelody() const
    {
        return qRound(qLn(static_cast<qreal>(m_level->howGetMelody) / qLn(2.0)));
    }
    void TlevelCreatorItem::setHowGetMelody(int hgm)
    {
        auto rCast = static_cast<Tlevel::EhowGetMelody>(qPow(2.0, static_cast<qreal>(hgm)));
        if (rCast != m_level->howGetMelody) {
            m_level->howGetMelody = rCast;
            levelParamChanged();
            emit updateLevel();
        }
    
    int TlevelCreatorItem::notesInList() const
    {
        return m_level->notesList.count();
    }
    Tnote TlevelCreatorItem::noteFromList(int id) const
    {
        return id >= 0 && id < m_level->notesList.count() ? m_level->notesList[id] : Tnote();
    
    }
    
    /**
     * Logic is simple, set note @p n if @p id matches range of the list
     * or simply append this note to the list end
     */
    
    void TlevelCreatorItem::setNoteOfList(int id, const Tnote &n)
    {
        if (id >= 0 && id < m_level->notesList.count())
            m_level->notesList[id] = n;
        else
            m_level->notesList << n;
    
    int TlevelCreatorItem::keyOfRandList() const
    {
        return static_cast<int>(m_level->keyOfrandList.value());
    
    void TlevelCreatorItem::setKeyOfRandList(int key)
    {
        m_level->keyOfrandList = TkeySignature(static_cast<char>(key));
        levelParamChanged();
    
    bool TlevelCreatorItem::randomOrder() const
    {
        return m_level->randOrderInSet;
    }
    void TlevelCreatorItem::setRandomOrder(bool randO)
    {
        m_level->randOrderInSet = randO;
        levelParamChanged();
    
    int TlevelCreatorItem::repeatsNumber() const
    {
        return m_level->repeatNrInSet;
    }
    void TlevelCreatorItem::setRepeatsNumber(int rNr)
    {
        m_level->repeatNrInSet = static_cast<quint8>(rNr);
        levelParamChanged();
    }
    
    int TlevelCreatorItem::loFret() const
    {
        return static_cast<int>(m_level->loFret);
    }
    void TlevelCreatorItem::setLoFret(int lf)
    {
        if (static_cast<char>(lf) != m_level->loFret) {
            m_level->loFret = static_cast<char>(lf);
            levelParamChanged();
            emit updateLevel();
        }
    
    int TlevelCreatorItem::hiFret() const
    {
        return static_cast<int>(m_level->hiFret);
    }
    void TlevelCreatorItem::setHiFret(int hf)
    {
        if (static_cast<char>(hf) != m_level->hiFret) {
            m_level->hiFret = static_cast<char>(hf);
            levelParamChanged();
            emit updateLevel();
        }
    
    Tnote TlevelCreatorItem::loNote() const
    {
        return m_level->loNote;
    }
    void TlevelCreatorItem::setLoNote(const Tnote &n)
    {
        if (!m_level->loNote.compareNotes(n) && n.isValid()) {
            m_level->loNote = n;
            levelParamChanged();
        }
    }
    
    Tnote TlevelCreatorItem::hiNote() const
    {
        return m_level->hiNote;
    }
    void TlevelCreatorItem::setHiNote(const Tnote &n)
    {
        if (!m_level->hiNote.compareNotes(n) && n.isValid()) {
            m_level->hiNote = n;
            levelParamChanged();
        }
    
    int TlevelCreatorItem::clef() const
    {
        return static_cast<int>(m_level->clef.type());
    }
    void TlevelCreatorItem::setClef(int c)
    {
        m_level->clef.setClef(static_cast<Tclef::EclefType>(c));
    
    int TlevelCreatorItem::usedStrings() const
    {
        return (m_level->usedStrings[0] ? 1 : 0) + (m_level->usedStrings[1] ? 2 : 0) + (m_level->usedStrings[2] ? 4 : 0) + (m_level->usedStrings[3] ? 8 : 0)
            + (m_level->usedStrings[4] ? 16 : 0) + (m_level->usedStrings[5] ? 32 : 0);
    
    void TlevelCreatorItem::setUsedStrings(int uStr)
    {
        for (int s = 0; s < 6; ++s)
            m_level->usedStrings[s] = (uStr & qRound(qPow(2.0, static_cast<qreal>(s)))) > 0;
        levelParamChanged();
    
    void TlevelCreatorItem::adjustFretsToScale()
    {
        char loF, hiF;
        if (m_level->adjustFretsToScale(loF, hiF)) {
            if (loF != m_level->loFret || hiF != m_level->hiFret) {
                m_level->loFret = loF;
                m_level->hiFret = hiF;
                levelParamChanged();
                emit updateLevel();
            }
        } else
            qDebug() << "[TlevelCreatorItem] Can't adjust fret range!";
    
    void TlevelCreatorItem::adjustNotesToFretRange()
    {
        Tnote loN(GLOB->loString().chromatic() + static_cast<short>(m_level->loFret));
        Tnote hiN(GLOB->hiString().chromatic() + static_cast<short>(m_level->hiFret));
        if (!loN.compareNotes(m_level->loNote) || !hiN.compareNotes(m_level->hiNote)) {
            m_level->loNote = loN;
            m_level->hiNote = hiN;
    
            levelParamChanged();
            emit updateLevel();
    
    // Accidentals page
    bool TlevelCreatorItem::withSharps() const
    {
        return m_level->withSharps;
    }
    void TlevelCreatorItem::setWithSharps(bool sharps)
    {
        m_level->withSharps = sharps;
    
    bool TlevelCreatorItem::withFlats() const
    {
        return m_level->withFlats;
    
    void TlevelCreatorItem::setWithFlats(bool flats)
    {
        m_level->withFlats = flats;
        levelParamChanged();
    
    bool TlevelCreatorItem::withDblAccids() const
    {
        return m_level->withDblAcc;
    
    void TlevelCreatorItem::setWithDblAccids(bool dblAccids)
    {
        m_level->withDblAcc = dblAccids;
        levelParamChanged();
    
    bool TlevelCreatorItem::forceAccids() const
    {
        return m_level->forceAccids;
    }
    void TlevelCreatorItem::setForceAccids(bool fa)
    {
        m_level->forceAccids = fa;
        levelParamChanged();
    
    bool TlevelCreatorItem::useKeySign() const
    {
        return m_level->useKeySign;
    }
    void TlevelCreatorItem::setUseKeySign(bool useKeys)
    {
        m_level->useKeySign = useKeys;
        levelParamChanged();
    
    bool TlevelCreatorItem::isSingleKey() const
    {
        return m_level->isSingleKey;
    }
    void TlevelCreatorItem::setIsSingleKey(bool isSingle)
    {
        m_level->isSingleKey = isSingle;
        levelParamChanged();
    
    int TlevelCreatorItem::loKey() const
    {
        return static_cast<int>(m_level->loKey.value());
    }
    void TlevelCreatorItem::setLoKey(int k)
    {
        m_level->loKey = TkeySignature(static_cast<char>(k));
        if (k < 0)
            m_level->withFlats = true;
        if (k > 0)
            m_level->withSharps = true;
        levelParamChanged();
        emit updateLevel();
    
    int TlevelCreatorItem::hiKey() const
    {
        return static_cast<int>(m_level->hiKey.value());
    }
    void TlevelCreatorItem::setHiKey(int k)
    {
        m_level->hiKey = TkeySignature(static_cast<char>(k));
        if (k < 0)
            m_level->withFlats = true;
        if (k > 0)
            m_level->withSharps = true;
        levelParamChanged();
        emit updateLevel();
    
    bool TlevelCreatorItem::onlyCurrKey() const
    {
        return m_level->onlyCurrKey;
    }
    void TlevelCreatorItem::setOnlyCurrKey(bool only)
    {
        m_level->onlyCurrKey = only;
        levelParamChanged();
    
    bool TlevelCreatorItem::manualKey() const
    {
        return m_level->manualKey;
    }
    void TlevelCreatorItem::setManualKey(bool manual)
    {
        m_level->manualKey = manual;
        levelParamChanged();
    }
    
    int TlevelCreatorItem::meters() const
    {
        return static_cast<int>(m_level->meters);
    }
    void TlevelCreatorItem::setMeters(int m)
    {
        bool prevRtmState = m_level->useRhythms();
        m_level->meters = static_cast<quint16>(m);
        levelParamChanged();
        if (m_level->useRhythms() != prevRtmState)
            emit hasRhythmsChanged();
    
    quint32 TlevelCreatorItem::basicRhythms() const
    {
        return m_level->basicRhythms;
    }
    void TlevelCreatorItem::setBasicRhythms(quint32 br)
    {
        bool prevRtmState = m_level->useRhythms();
        m_level->basicRhythms = br;
        levelParamChanged();
        if (m_level->useRhythms() != prevRtmState)
            emit hasRhythmsChanged();
    
    quint32 TlevelCreatorItem::dotsRhythms() const
    {
        return m_level->dotsRhythms;
    }
    void TlevelCreatorItem::setDotsRhythms(quint32 dr)
    {
        bool prevRtmState = m_level->useRhythms();
        m_level->dotsRhythms = dr;
        levelParamChanged();
        if (m_level->useRhythms() != prevRtmState)
            emit hasRhythmsChanged();
    
    int TlevelCreatorItem::rhythmDiversity() const
    {
        return m_level->rhythmDiversity;
    }
    void TlevelCreatorItem::setRhythmDiversity(int diversity)
    {
        m_level->rhythmDiversity = diversity;
        levelParamChanged();
    
    int TlevelCreatorItem::barNumber() const
    {
        return m_level->barNumber;
    }
    void TlevelCreatorItem::setBarNumber(int bNr)
    {
        m_level->barNumber = bNr;
        levelParamChanged();
    
    bool TlevelCreatorItem::variableBarNr() const
    {
        return m_level->variableBarNr;
    }
    void TlevelCreatorItem::setVariableBarNr(bool variabus)
    {
        m_level->variableBarNr = variabus;
        levelParamChanged();
    
    bool TlevelCreatorItem::useRests() const
    {
        return m_level->useRests;
    }
    void TlevelCreatorItem::setUseRests(bool rests)
    {
        m_level->useRests = rests;
        levelParamChanged();
    
    bool TlevelCreatorItem::useTies() const
    {
        return m_level->useTies;
    }
    void TlevelCreatorItem::setUseTies(bool ties)
    {
        m_level->useTies = ties;
        levelParamChanged();
    
    bool TlevelCreatorItem::hasRhythms() const
    {
        return m_level->useRhythms();
    }
    
    void TlevelCreatorItem::openLevel(const QString &levelFile)
    {
        if (selector()) {
            selector()->loadFromFile(levelFile);
            *m_level = *m_selector->currentLevel();
        } else { // delay is necessary because selector is created when level creator component is completed
            QTimer::singleShot(200, this, [=] {
                if (selector()) {
                    selector()->loadFromFile(levelFile);
                    *m_level = *m_selector->currentLevel();
                } else
                    qDebug() << "[TlevelCreatorItem] device is too slow to open file as command line argument.";
            });
        }
    }
    
    void TlevelCreatorItem::melodyListChanged()
    {
        levelParamChanged();
    
    void TlevelCreatorItem::resumeAfterLevelChange()
    {
        if (!m_resumeAfterLevelChange)
            return;
    
        m_resumeAfterLevelChange = false;
        if (!m_titleExtension.isEmpty()) {
            m_titleExtension.clear();
            emit saveStateChanged();
        }
        *m_level = *m_selector->currentLevel();
        m_answersList.clear();
        m_answersList << m_level->answersAs[0].value() << m_level->answersAs[1].value() << m_level->answersAs[2].value() << m_level->answersAs[3].value();
        emit updateLevel();
        if (m_level->howGetMelody == Tlevel::e_randFromList)
            emit updateNotesList();
        else if (m_level->howGetMelody == Tlevel::e_melodyFromSet)
            emit updateMelodyList();
    
    // #################################################################################################
    // ###################              PROTECTED           ############################################
    // #################################################################################################
    
    void TlevelCreatorItem::whenLevelChanged()
    {
        if (m_selector->currentLevel() == nullptr) {
            qDebug() << "[TlevelCreatorItem] Null pointer of level is selected!";
            return;
        }
    
        m_resumeAfterLevelChange = true;
        if (!m_titleExtension.isEmpty())
            emit wantNotSavedMessage(tr("level not saved!").toUpper(), tr("Level was changed and not saved!"));
        else
            resumeAfterLevelChange();
    }
    
    QStringList TlevelCreatorItem::validateLevel()
    {
        QStringList res;
        // Check has a level sense - are there an questions and answers
        if (!m_level->canBeScore() && !m_level->canBeName() && !m_level->canBeInstr() && !m_level->canBeSound()) {
            res << tr("There aren't any questions or answers selected.<br>Level makes no sense.");
            return res;
        }
        // checking range
        // determine the highest note of fret range on available strings
        if (m_level->canBeInstr() || (m_level->instrument != Tinstrument::NoInstrument && m_level->answerIsSound())) {
            // only when guitar is enabled otherwise frets range was adjusted automatically
            int hiAvailStr, loAvailStr, cnt = -1;
            do {
                cnt++;
            } while (!m_level->usedStrings[GLOB->strOrder(cnt)] && cnt < GLOB->Gtune()->stringNr());
            hiAvailStr = GLOB->strOrder(cnt);
            cnt = GLOB->Gtune()->stringNr();
            do {
                cnt--;
            } while (!m_level->usedStrings[GLOB->strOrder(cnt)] && cnt >= 0);
            loAvailStr = GLOB->strOrder(cnt);
            if (m_level->loNote.chromatic() > GLOB->Gtune()->str(hiAvailStr + 1).chromatic() + m_level->hiFret
                || m_level->hiNote.chromatic() < GLOB->Gtune()->str(loAvailStr + 1).chromatic() + m_level->loFret)
                res << tr("<li>Range of frets is beyond the scale of this level</li>");
        }
        // Check is level range fit to instrument scale
        if (m_level->canBeInstr() || m_level->answerIsSound()) {
            if (!m_level->inScaleOf(GLOB->loString().chromatic(), GLOB->hiString().chromatic() + GLOB->GfretsNumber))
                res << "<li>" + TlevelSelector::rangeBeyondScaleTxt() + "</li>";
        }
        // checking are accidentals needed because of hi and low notes in range
        char acc = 0;
        if (m_level->loNote.alter())
            acc = m_level->loNote.alter();
        if (m_level->hiNote.alter())
            acc = m_level->hiNote.alter();
        if (acc) {
            if ((acc == 1 && !m_level->withSharps) || (acc == -1 && !m_level->withFlats))
                res << tr("<li>In range of notes some accidentals are used<br>but not available in this level</li>");
        }
        // Force accidentals enabled but any accidental was selected
        if (m_level->forceAccids && (!m_level->withFlats && !m_level->withSharps && !m_level->withDblAcc))
            res << tr("<li>Force appropriate accidental is enabled but any accidental was selected.</li>");
        // When no accidentals and no style and question and answers are note name
        if ((m_level->questionAs.isName() && m_level->answersAs[TQAtype::e_asName].isName())) {
            if ((m_level->withFlats && m_level->withSharps) || m_level->withDblAcc || m_level->requireStyle) {
                // do nothing - level is valid
            } else
                res << tr(
                    "<li>Questions and answers as note names will be the same. To avoid that level has to use flats and sharps and/or double accidentals and/or to "
                    "use different name styles.</li>");
        }
        // Check is possible of using naming style
        if (m_level->requireStyle && !m_level->canBeName())
            res << tr(
                "<li>'Use different naming styles' was checked but neither questions nor answers as note name are checked.<br>Check this type of answer/question "
                "or uncheck 'Use different naming styles'.</li>");
        // Check are questions and answers as note on the staff have sense (are different)
        if (m_level->questionAs.isOnScore() && m_level->answersAs[TQAtype::e_onScore].isOnScore())
            if (!m_level->manualKey && !m_level->forceAccids)
                res << tr(
                    "<li>Questions and answers as notes on the staff will be the same. Manually selecting keys or forcing accidentals has to be selected to avoid "
                    "that.</li>");
        // Check is possible of manualKey
        if (m_level->useKeySign && m_level->manualKey) {
            if (!m_level->answersAs[TQAtype::e_onScore].isOnScore() && !m_level->answersAs[TQAtype::e_asName].isOnScore()
                && !m_level->answersAs[TQAtype::e_onInstr].isOnScore() && !m_level->answersAs[TQAtype::e_asSound].isOnScore())
                res << tr("<li>Manual selecting of a key signature was checked but answer as note on the staff was not checked.</li>");
        }
        // 'Fret to fret' has to have suitable fret range to be possible
        if (m_level->questionAs.isOnInstr() && m_level->answersAs[TQAtype::e_onInstr].isOnInstr()) {
            int minRange = 0; // first determine a minimal range for current tune
            int startStr = GLOB->Gtune()->str(GLOB->strOrder(0) + 1).chromatic();
            for (int i = 1; i < GLOB->Gtune()->stringNr(); i++) {
                minRange = qMax(minRange, startStr - GLOB->Gtune()->str(GLOB->strOrder(i) + 1).chromatic());
                startStr = GLOB->Gtune()->str(GLOB->strOrder(i) + 1).chromatic();
            }
            if (m_level->hiFret - m_level->loFret < minRange)
                res << tr("<li>Fret range is not enough to find any note in different positions. At least <b>%1</b> frets range is required.</li>").arg(minRange);
        }
        // Melodies finished on tonic have to contain the tonic note in range
        if (m_level->canBeMelody() && m_level->endsOnTonic) {
            bool inKeyNotes[7]; // array with notes used by key signatures from range
            for (int i = 0; i < 7; ++i)
                inKeyNotes[i] = false;
            for (int i = m_level->loKey.value(); i <= m_level->hiKey.value(); ++i) {
                inKeyNotes[static_cast<int>(TkeySignature::minorKeys[i + 7])] = true;
                inKeyNotes[static_cast<int>(TkeySignature::majorKeys[i + 7])] = true;
    
            int startNote = m_level->loNote.note() + (m_level->loNote.octave() + 5) * 7 - 1;
            int endNote = m_level->hiNote.note() + (m_level->hiNote.octave() + 5) * 7 - 1;
            for (int n = 0; n < 7; ++n) {
                if (inKeyNotes[n]) {
                    bool found = false;
                    for (int i = startNote; i <= endNote; ++i) {
                        if (n == i % 7) {
                            found = true;
                            break;
                        }
                    }
                    if (!found) {
                        res << tr("<li>Possible missing a tonic note for some key signatures in selected note range .</li>");
                        break;
                    }
                }
    
        // Note list is empty
        if (m_level->canBeMelody() && m_level->howGetMelody == Tlevel::e_randFromList) {
            if (m_level->notesList.count() < 2)
                res << tr("<li>There are not enough selected notes to create a melody.</li>");
        }
        // When level is set of melodies but no melody was added
        if (m_level->canBeMelody() && m_level->howGetMelody == Tlevel::e_melodyFromSet) {
            if (m_level->melodySet.isEmpty())
                res << tr("<li>No melody was added to the list.</li>");
        }
    
    void TlevelCreatorItem::showValidationMessage(QStringList &message)
    {
        if (!message.isEmpty()) {
            QString title = tr("Level validation");
            if (message.last().contains(QLatin1String("</li>"))) { // when <li> exist - show warning
                message.replaceInStrings(QLatin1String("</li>"), QString());
                message.replaceInStrings(QLatin1String("<li>"), QString());
                emit wantValidationMessage(tr("Seems like this level has some mistakes") + QLatin1String(":"), message, Qt::yellow);
            } else // no questions nor answers
                emit wantValidationMessage(title, message, Qt::red);
        }
    
    // #################################################################################################
    // #####################              PRIVATE           ############################################
    // #################################################################################################
    
    void TlevelCreatorItem::levelParamChanged()
    {
        if (m_titleExtension.isEmpty()) {
            m_titleExtension = QLatin1String("  (") + tr("level not saved!") + QLatin1String(")");
            emit saveStateChanged();
        }
        m_selector->levelPreview()->setLevel(m_level);