diff --git a/companion/src/firmwares/edgetx/edgetxinterface.cpp b/companion/src/firmwares/edgetx/edgetxinterface.cpp index f1f1a0c91..ad97a2b79 100644 --- a/companion/src/firmwares/edgetx/edgetxinterface.cpp +++ b/companion/src/firmwares/edgetx/edgetxinterface.cpp @@ -51,34 +51,27 @@ bool loadModelsListFromYaml(std::vector& categories, if (data.size() == 0) return true; - try { - YAML::Node node = loadYamlFromByteArray(data); - if (!node.IsSequence()) return false; + YAML::Node node = loadYamlFromByteArray(data); + if (!node.IsSequence()) return false; - int modelIdx = 0; - for (const auto& cat : node) { + int modelIdx = 0; + for (const auto& cat : node) { + if (!cat.IsMap()) continue; - if (!cat.IsMap()) continue; + for (const auto& cat_map : cat) { + categories.push_back(cat_map.first.Scalar().c_str()); - for (const auto& cat_map : cat) { - categories.push_back(cat_map.first.Scalar().c_str()); + const auto& models = cat_map.second; + if (!models.IsSequence()) continue; - const auto& models = cat_map.second; - if (!models.IsSequence()) continue; - - for (const auto& model : models) { - std::string filename, name; - model["filename"] >> filename; - model["name"] >> name; - modelFiles.push_back( - {filename, name, (int)categories.size() - 1, modelIdx++}); - } + for (const auto& model : models) { + std::string filename, name; + model["filename"] >> filename; + model["name"] >> name; + modelFiles.push_back( + {filename, name, (int)categories.size() - 1, modelIdx++}); } } - - } catch (const std::runtime_error& e) { - qDebug() << "YAML::ParserException: " << e.what(); - return false; } return true; @@ -117,18 +110,35 @@ bool writeModelsListToYaml(const std::vector& categories, QByteArray& data) { YAML::Node node; + std::vector cats = categories; + std::vector files = { modelFiles.begin(), modelFiles.end() }; + + std::stable_sort(files.begin(), files.end(), + [](const EtxModelMetadata &a, const EtxModelMetadata &b) { + return a.category < b.category; + }); + + int catIdx = 0; + for (const auto& cat : cats) { + node[catIdx++][cat.name] = YAML::Node(); + } + for (const auto& modelFile: modelFiles) { YAML::Node cat_attrs; cat_attrs["filename"] = modelFile.filename; cat_attrs["name"] = modelFile.name; - if (modelFile.category >= (int)categories.size()) { - return false; + catIdx = modelFile.category; + if (catIdx >= (int)cats.size()) { + catIdx = 0; + if (cats.size() == 0) { + cats.push_back("Models"); + } } - const std::string cat_name = categories[modelFile.category].name; - node[modelFile.category][cat_name].push_back(cat_attrs); + const std::string cat_name = cats[catIdx].name; + node[catIdx][cat_name].push_back(cat_attrs); } writeYamlToByteArray(node, data); diff --git a/companion/src/modelslist.cpp b/companion/src/modelslist.cpp index 7581d201e..99a059aac 100644 --- a/companion/src/modelslist.cpp +++ b/companion/src/modelslist.cpp @@ -671,6 +671,7 @@ void TreeModel::refresh() defaultCategoryItem = rootItem->appendChild(0, -1); /*: Translators do NOT use accent for this, this is the default category name on Horus. */ defaultCategoryItem->setData(0, tr("Models")); + radioData->categories.push_back(qPrintable(tr("Models"))); } categoryItem = defaultCategoryItem; } diff --git a/companion/src/radiointerface.cpp b/companion/src/radiointerface.cpp index 9b5cca0dd..9e508cfda 100644 --- a/companion/src/radiointerface.cpp +++ b/companion/src/radiointerface.cpp @@ -211,12 +211,16 @@ bool readSettingsSDCard(const QString & filename, ProgressWidget * progress) RadioData radioData; Storage inputStorage(radioPath); if (!inputStorage.load(radioData)) { - QMessageBox::critical(progress, CPN_STR_TTL_ERROR, QCoreApplication::translate("RadioInterface", "Failed to read Models and Settings from") % radioPath); + QString errorMsg = inputStorage.error(); + if (errorMsg.isEmpty()) { + errorMsg = QCoreApplication::translate("RadioInterface", "Failed to read Models and Settings from") % radioPath; + } + QMessageBox::critical(progress, CPN_STR_TTL_ERROR, errorMsg); return false; } Storage outputStorage(filename); if (!outputStorage.write(radioData)) { - QMessageBox::critical(progress, CPN_STR_TTL_ERROR, QCoreApplication::translate("RadioInterface", "Failed to write Models and Setting file") % filename); + QMessageBox::critical(progress, CPN_STR_TTL_ERROR, QCoreApplication::translate("RadioInterface", "Failed to write Models and Setting file") % " " % filename); return false; } diff --git a/companion/src/storage/categorized.cpp b/companion/src/storage/categorized.cpp index 4c3caf7c2..8a29c0edf 100644 --- a/companion/src/storage/categorized.cpp +++ b/companion/src/storage/categorized.cpp @@ -31,7 +31,8 @@ bool CategorizedStorageFormat::load(RadioData & radioData) if (st == STORAGE_TYPE_UNKNOWN) { st = probeFormat(); } - if (st == STORAGE_TYPE_ETX || st == STORAGE_TYPE_YML) { + if (st == STORAGE_TYPE_ETX || st == STORAGE_TYPE_YML + || st == STORAGE_TYPE_UNKNOWN) { return loadYaml(radioData); } else { return loadBin(radioData); @@ -254,9 +255,35 @@ bool CategorizedStorageFormat::loadYaml(RadioData & radioData) QByteArray modelslistBuffer; if (loadFile(modelslistBuffer, "MODELS/models.yml")) { - if (!loadModelsListFromYaml(radioData.categories, modelFiles, modelslistBuffer)) { - setError(tr("Can't load MODELS/models.yml")); - return false; + try { + if (!loadModelsListFromYaml(radioData.categories, modelFiles, modelslistBuffer)) { + setError(tr("Can't load MODELS/models.yml")); + return false; + } + } catch(const std::runtime_error& e) { + setError(tr("Can't load MODELS/models.yml") + ":\n" + QString(e.what())); + //return false; + + //TODO: fall back to directory scan + std::list filelist; + if (!getFileList(filelist)) { + return false; + } + + // push default category + radioData.categories.push_back("Models"); + + const std::regex yml_regex("MODELS/(model([0-9]+)\\.yml)"); + for(const auto& f : filelist) { + std::smatch match; + if (std::regex_match(f, match, yml_regex)) { + if (match.size() == 3) { + std::ssub_match modelFile = match[1]; + std::ssub_match modelIdx = match[2]; + modelFiles.push_back({ modelFile.str(), "", 0, std::stoi(modelIdx.str()) }); + } + } + } } } else { // fetch "MODELS/modelXX.yml" @@ -279,6 +306,9 @@ bool CategorizedStorageFormat::loadYaml(RadioData & radioData) } } + int modelIdx = 0; + bool hasCategories = getCurrentFirmware()->getCapability(HasModelCategories); + radioData.models.resize(modelFiles.size()); for (const auto& mc : modelFiles) { qDebug() << "Filename: " << mc.filename.c_str() << " / Category: " << mc.category; @@ -293,7 +323,7 @@ bool CategorizedStorageFormat::loadYaml(RadioData & radioData) // Please note: // ModelData() use memset to clear everything to 0 // - auto& model = radioData.models[mc.modelIdx]; + auto& model = radioData.models[modelIdx]; try { if (!loadModelFromYaml(model,modelBuffer)) { @@ -306,10 +336,17 @@ bool CategorizedStorageFormat::loadYaml(RadioData & radioData) } model.category = mc.category; - model.modelIndex = mc.modelIdx; + model.modelIndex = modelIdx; strncpy(model.filename, mc.filename.c_str(), sizeof(model.filename)-1); + if (hasCategories && !strncmp(radioData.generalSettings.currModelFilename, + model.filename, sizeof(model.filename))) { + radioData.generalSettings.currModelIndex = modelIdx; + qDebug() << "currModelIndex =" << modelIdx; + } + model.used = true; + modelIdx++; } return true;