Skip to content
Snippets Groups Projects
tlevelselector.cpp 12.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • /***************************************************************************
    
     *   Copyright (C) 2011-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 "tlevelselector.h"
    #include "tlevelpreviewitem.h"
    
    #include "tlevelsdefs.h"
    #include <Android/tfiledialog.h>
    
    #include <texamparams.h>
    
    #include <tpath.h>
    
    #if defined(Q_OS_ANDROID)
    #include <Android/tandroid.h>
    
    #include <QtCore/qdiriterator.h>
    
    #include <QtCore/qsettings.h>
    
    QString TlevelSelector::checkLevel(const Tlevel &l)
    {
        QString warningText;
        if (GLOB->instrument().none()) {
            if (l.instrument != Tinstrument::NoInstrument)
                warningText = tr("Level is not suitable for current instrument type");
        } else if (GLOB->instrument().isGuitar()) {
            if (l.canBeInstr() || (l.instrument != Tinstrument::NoInstrument && l.canBeSound())) {
                if (l.hiFret > GLOB->GfretsNumber || GLOB->Gtune()->stringNr() < 3 || l.loNote.chromatic() < GLOB->loString().chromatic()
                    || l.hiNote.chromatic() > GLOB->hiNote().chromatic())
                    warningText = tr("Level is not suitable for current tuning and/or fret number");
            }
    
            if (GLOB->instrument().type() != l.instrument)
                warningText = tr("Level is not suitable for current instrument type");
        }
        return warningText;
    
    TlevelSelector::TlevelSelector(QQuickItem *parent)
        : QQuickItem(parent)
    
    TlevelSelector::~TlevelSelector()
    {
        updateRecentLevels();
    
    // ##########################################################################################################
    // ########################################## PUBLIC #######################################################
    // ##########################################################################################################
    
    void TlevelSelector::setLevelPreview(TlevelPreviewItem *lpi)
    {
        m_levelPreview = lpi;
        if (m_levelPreview)
            m_levelPreview->setLevel(nullptr);
    
    void TlevelSelector::setCurrentIndex(int ci)
    {
        m_currentLevelId = ci;
    
    void TlevelSelector::findLevels()
    {
        Tlevel lev;
        // from predefined list
        QList<Tlevel> levels;
        getExampleLevels(levels);
        for (int i = 0; i < levels.size(); i++) {
            addLevel(levels[i]);
            // NOTE: Do not check built-in levels
        }
    
        // from files shipped with Nootka installation (levels directory)
        addLevelsFormInstallDir(Tpath::levels()); // general purpose levels
        addLevelsFormInstallDir(Tpath::levels() + QLatin1String("/") + GLOB->instrument().levelsDir()); // instrument specific levels
    
        // from constructor (Master of Masters)
        addLevel(lev);
        m_levels.last().suitable = true; // always suitable
    
        // from setting file - recent load/saved levels
        QStringList recentLevels = GLOB->config->value(QLatin1String("recentLevels")).toStringList();
        for (int i = recentLevels.size() - 1; i >= 0; i--) {
            QFile file(recentLevels[i]);
            if (file.exists()) {
                auto level = getLevelFromFile(file);
                if (!level.name.isEmpty()) {
                    addLevel(level, file.fileName());
                    checkLast();
                } else
                    recentLevels.removeAt(i);
    
        }
        GLOB->config->setValue(QLatin1String("recentLevels"), recentLevels);
    
    void TlevelSelector::addLevel(const Tlevel &lev, const QString &levelFile, bool check)
    {
        if (check && !levelFile.isEmpty()) { // check for duplicates
            int pos = -1;
            for (int i = 0; i < m_levels.size(); i++) {
                if (m_levels[i].file == levelFile) // file and level exist
                    pos = i;
            }
            if (pos > -1) {
                m_levels.removeAt(pos);
                m_levelsModel.removeAt(pos);
                qDebug() << "[TlevelSelector] Level file" << levelFile << "was already on the list. Previous entry removed!";
            }
    
        SlevelContener l;
        l.level = lev;
        l.file = levelFile;
        l.suitable = true;
        m_levels << l;
        m_levelsModel << l.level.name;
    
    Tlevel *TlevelSelector::currentLevel()
    {
        return m_currentLevelId >= 0 && m_currentLevelId < m_levels.count() ? &m_levels[m_currentLevelId].level : nullptr;
    
    QVariant TlevelSelector::currentLevelVar()
    {
        return QVariant::fromValue(currentLevel());
    
    bool TlevelSelector::isRemovable(int id) const
    {
        return id >= 0 && id < m_levels.count() ? !m_levels[id].file.isEmpty() : false;
    
    void TlevelSelector::showLevel(int id)
    {
        if (id >= 0 && id < m_levels.count() && m_levelPreview)
            m_levelPreview->setLevel(&m_levels[id].level);
        emit levelChanged();
    
    QString TlevelSelector::desc(int id)
    {
        return id >= 0 && id < m_levels.count() ? m_levels[id].level.desc : QString();
    
    bool TlevelSelector::isMelody(int id)
    {
        return id >= 0 && id < m_levels.count() ? m_levels[id].level.canBeMelody() : false;
    
    QString TlevelSelector::levelName(int id) const
    {
        return id >= 0 && id < m_levels.count() ? m_levels[id].level.name : QString();
    
    QString TlevelSelector::levelFile(int id) const
    {
        return id >= 0 && id < m_levels.count() ? m_levels[id].file : QString();
    
    void TlevelSelector::loadFromFile(QString levelFile)
    {
        if (levelFile.isEmpty())
    #if defined(Q_OS_ANDROID)
    
            if (GLOB->examParams->levelsDir.isEmpty())
                GLOB->examParams->levelsDir = Tandroid::getExternalPath();
        levelFile = TfileDialog::getOpenFileName(GLOB->examParams->levelsDir, QStringLiteral("nel"));
    
            levelFile = TfileDialog::getOpenFileName(tr("Load exam level"), GLOB->examParams->levelsDir, levelFilterTxt() + QLatin1String(" (*.nel)"));
    
        QFile file(levelFile);
        Tlevel level = getLevelFromFile(file);
        if (!level.name.isEmpty()) {
            GLOB->examParams->levelsDir = QFileInfo(levelFile).absoluteDir().absolutePath();
            addLevel(level, levelFile, true);
            checkLast();
            updateRecentLevels();
            emit levelsModelChanged();
            //     if (m_levels.last().suitable) {
            emit selectLast();
            emit levelChanged();
            m_levelPreview->setLevel(&level);
            //     }
        }
    
    bool TlevelSelector::removeLevel(int id, bool removeFile)
    {
        if (id >= 0 && id < m_levels.count()) {
            if (removeFile) {
                QFile levF(m_levels[id].file);
                if (!levF.remove())
                    qDebug() << "[TlevelSelector] Can't remove level file" << levF.fileName();
            }
            m_levels.removeAt(id);
            m_levelsModel.removeAt(id);
            emit levelsModelChanged();
            if (m_levelPreview)
                m_levelPreview->setLevel(nullptr);
            updateRecentLevels();
            return true;
    
    SeeLook's avatar
    SeeLook committed
        }
    
    void TlevelSelector::updateRecentLevels()
    {
        QStringList recentLevels;
        for (int i = m_levels.size() - 1; i > 1; i--) {
            if (!m_levels[i].file.isEmpty())
                recentLevels << m_levels[i].file;
        }
        GLOB->config->setValue(QLatin1String("recentLevels"), recentLevels);
    
    // ##########################################################################################################
    // ########################################## PRIVATE #######################################################
    // ##########################################################################################################
    
    Tlevel TlevelSelector::getLevelFromFile(QFile &file)
    {
        Tlevel level;
        level.name.clear();
        ;
        if (file.open(QIODevice::ReadOnly)) {
    
    SeeLook's avatar
    SeeLook committed
            QDataStream in(&file);
            quint32 lv; // level version identifier
            in >> lv;
            bool wasLevelValid = true;
            bool wasLevelFile = true;
    
            auto levelVer = Tlevel::levelVersionNr(lv);
            if (levelVer == 1 || levelVer == 2) { // *.nel with binary data
    
                in.setVersion(QDataStream::Qt_4_7);
    
                wasLevelValid = getLevelFromStream(in, level, lv);
    
            } else if (levelVer > 2 && levelVer <= 5) { // *.nel in XML, from version 5 compressed
    
                Tlevel::EerrorType er;
    
                QXmlStreamReader xml;
                if (levelVer >= 5) { // compressed level
                    in.setVersion(QDataStream::Qt_5_9);
                    auto arrayXML = file.readAll();
                    arrayXML.remove(0, 4);
                    auto unZipXml = qUncompress(arrayXML);
                    xml.addData(unZipXml);
                } else { // bare text (XML)
                    in.setVersion(QDataStream::Qt_5_2);
                    xml.setDevice(in.device());
                }
    
                if (!xml.readNextStartElement()) // open first XML node
    
                    er = Tlevel::e_noLevelInXml;
    
                    er = level.loadFromXml(xml);
    
                case Tlevel::e_levelFixed:
                    wasLevelValid = false;
                    break;
                case Tlevel::e_noLevelInXml:
                case Tlevel::e_otherError:
                    wasLevelFile = false;
                    break;
                default:
    
                    break;
                }
            } else {
                qDebug() << "[TlevelSelector] Level was created with newer Nootka version!";
                GLOB->warnAboutNewerVersion(file.fileName());
                level.name.clear();
                return level;
            }
    
    SeeLook's avatar
    SeeLook committed
            file.close();
    
    
            if (!wasLevelFile) {
                auto m = QLatin1String("<br><font size=\"4\"><b>")
                    + tr("File: %1 \n is not Nootka level file!").arg(file.fileName()).replace(QLatin1String("\n"), QLatin1String("<br>"))
                    + QLatin1String("</b></font>");
                emit warnMessage(m, Qt::red);
                level.name.clear();
                return level;
            } else if (!wasLevelValid) {
                auto m = QLatin1String("<br><font size=\"4\"><b>")
    
                    + tr("Level file\n %1 \n was corrupted and repaired!\n Check please, if its parameters are as expected.")
                          .arg(file.fileName())
                          .replace(QLatin1String("\n"), QLatin1String("<br>"))
                    + QLatin1String("</font>");
    
                emit warnMessage(m, Qt::yellow);
            }
    
            if (wasLevelFile) {
    
                if (level.clef.type() == Tclef::Bass_F_8down) {
                    qDebug() << "[TlevelSelector] OBSOLETE bass dropped clef detected. Converting level to ordinary bass clef.";
                    level.convFromDropedBass();
                }
    
        } else {
            if (!file.fileName().isEmpty()) // skip empty file names (ignored by user)
                Tlevel::fileIOerrorMsg(file);
        }
        return level;
    
    void TlevelSelector::checkLast()
    {
        SlevelContener &last = m_levels.last();
        QString notSuitableText = checkLevel(last.level);
        if (notSuitableText.isEmpty())
            m_levels.last().suitable = true;
        else {
            m_levels.last().suitable = false;
            m_levels.last().level.desc = QLatin1String("<font color=\"red\">") + notSuitableText + QLatin1String("</font>");
        }
    
    void TlevelSelector::addLevelsFormInstallDir(const QString &dirPath)
    {
        QDir instrDIrPath(dirPath);
        if (!instrDIrPath.exists())
            return;
    
        QDirIterator nelFiles(dirPath, QStringList() << QStringLiteral("*.nel"), QDir::Files);
        while (nelFiles.hasNext()) {
            QFile file(nelFiles.next());
            auto level = getLevelFromFile(file);
            if (!level.name.isEmpty()) {
                addLevel(level);
                checkLast();
            } else
                qDebug() << "[TlevelSelector] built-in level is corrupted:\n  =>" << file.fileName();
        }