feat(color): New label based Model Selector (#1374)
* Model Labels Selector * Portrait Layout * Receiver ID Check * Fix Labels .yml format * Companion Initial Commit commit 967ddf3979b4954402fe62c858faba4b7a416bba Author: dlktdr <cblackburn320@gmail.com> Date: Mon Aug 29 01:49:15 2022 -0700 Fix Labels .yml format commit 534221cad9e01146c47a9ac1080a655e04f1f6c8 Author: dlktdr <cblackburn320@gmail.com> Date: Mon Aug 29 01:20:57 2022 -0700 Receiver ID Check commit f32c0939d44e9ae60bc0705a9176354c23fec198 Author: dlktdr <cblackburn320@gmail.com> Date: Sun Aug 28 23:13:25 2022 -0700 Portrait Layout commit 961181217c7841ff3f21299ac6aa419b810f80e9 Author: dlktdr <cblackburn320@gmail.com> Date: Sat Dec 11 16:56:13 2021 -0800 Model Labels Selector commitdc99dd40d2Author: David PHAM-VAN <1387855+DavBfr@users.noreply.github.com> Date: Fri Aug 26 03:04:34 2022 -0300 feat: Allow Lua widget to override built-in widget with same name (#2208) * Allow CPP Widgets Override with LUA * Reset a widget option it the type differs commit4b747caff9Author: eshifri <46511024+eshifri@users.noreply.github.com> Date: Thu Aug 25 19:47:53 2022 -0700 fix: Write model name into models.yml when duplicating model (#2224) * Update model name in the models.yml file (#2214) * Fix the length of the name. commite97f23a305Author: eshifri <46511024+eshifri@users.noreply.github.com> Date: Wed Aug 24 21:19:52 2022 -0700 fix(cpn): Fix full path to model notes for Simulator (#2223) * Correct Highlighted Column * labels .yml indentation * Update da.h New defines translated to Danish * Update dataconstants.h Omit warnings due to difference of LEN_MODEL_FILENAME and LEN_MODEL_NAME in modellist.cpp * Update modelslist.cpp Wonder if this are the right file in PR #1374 (?), seems a lot are changed to the better in #1374 * Update dataconstants.h Restore #define for LEN_MODEL_FILENAME to 16 * Rebase, Correct Issues Delete now deletes instead of select New model stores correct information Duplicate save all information in cell Rework items to use cell over button Reword function variables * CMPN: Add context menu Fix output not functional. Removed Yaml::null at some point :) Fix some compiler warnings * CMPN: Compiler warning, replace ,->_ in list * FW: Various Changes Change view to a grid Don't allow comma in label name Add scroll indicators Save sort order Co-authored-by: HThuren <99370924+HThuren@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,371 @@
|
||||
/*
|
||||
* Copyright (C) OpenTX
|
||||
*
|
||||
* Based on code named
|
||||
* th9x - http://code.google.com/p/th9x
|
||||
* er9x - http://code.google.com/p/er9x
|
||||
* gruvin9x - http://code.google.com/p/gruvin9x
|
||||
*
|
||||
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "labeled.h"
|
||||
#include "firmwares/opentx/opentxinterface.h"
|
||||
#include "firmwares/edgetx/edgetxinterface.h"
|
||||
#include "miniz.c" // Can only be included once!
|
||||
|
||||
#include <regex>
|
||||
|
||||
bool LabelsStorageFormat::load(RadioData & radioData)
|
||||
{
|
||||
StorageType st = getStorageType(filename);
|
||||
if (st == STORAGE_TYPE_UNKNOWN) {
|
||||
st = probeFormat();
|
||||
}
|
||||
if (st == STORAGE_TYPE_ETX || st == STORAGE_TYPE_YML
|
||||
|| st == STORAGE_TYPE_UNKNOWN) {
|
||||
return loadYaml(radioData);
|
||||
} else {
|
||||
return loadBin(radioData);
|
||||
}
|
||||
}
|
||||
|
||||
bool LabelsStorageFormat::write(const RadioData & radioData)
|
||||
{
|
||||
StorageType st = getStorageType(filename);
|
||||
if (st == STORAGE_TYPE_ETX || st == STORAGE_TYPE_YML ||
|
||||
st == STORAGE_TYPE_UNKNOWN) {
|
||||
return writeYaml(radioData);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
StorageType LabelsStorageFormat::probeFormat()
|
||||
{
|
||||
if (QFile(filename + "/RADIO/radio.yml").exists()) // converted
|
||||
return getStorageType("radio.yml");
|
||||
else if (QFile(filename + "/RADIO/radio.bin").exists()) // unconverted
|
||||
return getStorageType("radio.bin");
|
||||
else
|
||||
return getStorageType(filename);
|
||||
}
|
||||
|
||||
bool LabelsStorageFormat::loadBin(RadioData & radioData)
|
||||
{
|
||||
QByteArray radioSettingsBuffer;
|
||||
if (!loadFile(radioSettingsBuffer, "RADIO/radio.bin")) {
|
||||
setError(tr("Cannot extract RADIO/radio.bin"));
|
||||
return false;
|
||||
}
|
||||
|
||||
OpenTxEepromInterface * loadInterface = loadRadioSettingsFromByteArray(radioData.generalSettings, radioSettingsBuffer);
|
||||
if (!loadInterface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
board = loadInterface->getBoard();
|
||||
|
||||
QByteArray modelsListBuffer;
|
||||
if (!loadFile(modelsListBuffer, "RADIO/models.txt")) {
|
||||
setError(tr("Cannot extract RADIO/models.txt"));
|
||||
return false;
|
||||
}
|
||||
|
||||
QList<QByteArray> lines = modelsListBuffer.split('\n');
|
||||
int modelIndex = 0;
|
||||
foreach (const QByteArray & lineArray, lines) {
|
||||
QString line = QString(lineArray).trimmed();
|
||||
if (line.isEmpty()) continue;
|
||||
// qDebug() << "parsing line" << line;
|
||||
|
||||
if (line.startsWith('[') && line.endsWith(']')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// determine if we have a model number
|
||||
QStringList parts = line.split(QRegExp("\\s+"), QString::SkipEmptyParts);
|
||||
if (parts.size() == 2) {
|
||||
// parse model number
|
||||
int modelNumber = parts[0].toInt();
|
||||
if (modelNumber > 0 && modelNumber > modelIndex && modelNumber < getCurrentFirmware()->getCapability(Models)) {
|
||||
modelIndex = modelNumber;
|
||||
qDebug() << "advanced model number to" << modelIndex;
|
||||
}
|
||||
else {
|
||||
if (modelNumber != modelIndex) qDebug() << "Invalid model number" << parts[0];
|
||||
}
|
||||
parts.removeFirst();
|
||||
}
|
||||
if (parts.size() == 1) {
|
||||
// parse model file name and load
|
||||
QString fileName = parts[0];
|
||||
qDebug() << "Loading model from file" << fileName << "into slot" << modelIndex;
|
||||
QByteArray modelBuffer;
|
||||
if (!loadFile(modelBuffer, QString("MODELS/%1").arg(fileName))) {
|
||||
setError(tr("Cannot extract %1").arg(fileName));
|
||||
return false;
|
||||
}
|
||||
if ((int)radioData.models.size() <= modelIndex) {
|
||||
radioData.models.resize(modelIndex + 1);
|
||||
}
|
||||
if (!loadModelFromByteArray(radioData.models[modelIndex], modelBuffer)) {
|
||||
setError(tr("Error loading models"));
|
||||
return false;
|
||||
}
|
||||
strncpy(radioData.models[modelIndex].filename, qPrintable(fileName), sizeof(radioData.models[modelIndex].filename));
|
||||
if (IS_FAMILY_HORUS_OR_T16(board) && !strcmp(radioData.generalSettings.currModelFilename, qPrintable(fileName))) {
|
||||
radioData.generalSettings.currModelIndex = modelIndex;
|
||||
qDebug() << "currModelIndex =" << modelIndex;
|
||||
}
|
||||
radioData.models[modelIndex].used = true;
|
||||
modelIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// invalid line
|
||||
// TODO add to parsing report
|
||||
qDebug() << "Invalid line" <<line;
|
||||
continue;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LabelsStorageFormat::writeBin(const RadioData & radioData)
|
||||
{
|
||||
QByteArray radioSettingsData; // radio.bin
|
||||
size_t numModels = radioData.models.size();
|
||||
size_t numCategories = radioData.labels.size();
|
||||
std::vector<std::vector<QString>> sortedModels(numCategories);
|
||||
|
||||
writeRadioSettingsToByteArray(radioData.generalSettings, radioSettingsData);
|
||||
if (!writeFile(radioSettingsData, "RADIO/radio.bin")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t m=0; m<numModels; m++) {
|
||||
const ModelData & model = radioData.models[m];
|
||||
if (model.isEmpty()) continue;
|
||||
|
||||
QString modelFilename = QString("MODELS/%1").arg(model.filename);
|
||||
QByteArray modelData;
|
||||
writeModelToByteArray(model, modelData);
|
||||
if (!writeFile(modelData, modelFilename)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LabelsStorageFormat::loadYaml(RadioData & radioData)
|
||||
{
|
||||
if (getStorageType(filename) == STORAGE_TYPE_UNKNOWN && probeFormat() == STORAGE_TYPE_ETX) {
|
||||
if (!QFile(filename + "/" + "RADIO/radio.yml").exists())
|
||||
qDebug() << tr("Cannot find %1/RADIO/radio.yml").arg(filename);
|
||||
else
|
||||
qDebug() << tr("Found %1/RADIO/radio.yml").arg(filename);
|
||||
|
||||
if (!QFile(filename + "/" + "MODELS/models.yml").exists())
|
||||
qDebug() << tr("Cannot find %1/MODELS/models.yml").arg(filename);
|
||||
else
|
||||
qDebug() << tr("Found %1/MODELS/models.yml").arg(filename);
|
||||
}
|
||||
else {
|
||||
if (!QFile(filename).exists())
|
||||
qDebug() << tr("Cannot find %1").arg(filename);
|
||||
else
|
||||
qDebug() << tr("Found %1").arg(filename);
|
||||
}
|
||||
|
||||
QByteArray radioSettingsBuffer;
|
||||
if (!loadFile(radioSettingsBuffer, "RADIO/radio.yml")) {
|
||||
setError(tr("Cannot extract RADIO/radio.yml"));
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!loadRadioSettingsFromYaml(radioData.generalSettings, radioSettingsBuffer)) {
|
||||
setError(tr("Cannot load RADIO/radio.yml"));
|
||||
return false;
|
||||
}
|
||||
} catch(const std::runtime_error& e) {
|
||||
setError(tr("Cannot load RADIO/radio.yml") + ":\n" + QString(e.what()));
|
||||
return false;
|
||||
}
|
||||
|
||||
board = (Board::Type)radioData.generalSettings.variant;
|
||||
|
||||
// get models
|
||||
EtxModelfiles modelFiles;
|
||||
|
||||
// Labels - Read the labels from labels.yml file
|
||||
// - Always scan for /MODELS/modelXX.yml
|
||||
// - Update possible labels with all found in models too
|
||||
|
||||
QByteArray labelslistBuffer;
|
||||
if (loadFile(labelslistBuffer, "MODELS/labels.yml")) {
|
||||
try {
|
||||
if (!loadLabelsListFromYaml(radioData.labels, modelFiles, labelslistBuffer)) {
|
||||
setError(tr("Can't load MODELS/labels.yml"));
|
||||
}
|
||||
} catch(const std::runtime_error& e) {
|
||||
setError(tr("Can't load MODELS/labels.yml") + ":\n" + QString(e.what()));
|
||||
}
|
||||
|
||||
// Scan for all models
|
||||
std::list<std::string> filelist;
|
||||
if (!getFileList(filelist)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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(), "", std::stoi(modelIdx.str()) });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int modelIdx = 0;
|
||||
bool hasLabels = getCurrentFirmware()->getCapability(HasModelLabels);
|
||||
|
||||
radioData.models.resize(modelFiles.size());
|
||||
for (const auto& mc : modelFiles) {
|
||||
qDebug() << "Filename: " << mc.filename.c_str();
|
||||
|
||||
QByteArray modelBuffer;
|
||||
QString filename = "MODELS/" + QString::fromStdString(mc.filename);
|
||||
if (!loadFile(modelBuffer, filename)) {
|
||||
setError(tr("Cannot extract ") + filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Please note:
|
||||
// ModelData() use memset to clear everything to 0
|
||||
//
|
||||
auto& model = radioData.models[modelIdx];
|
||||
|
||||
try {
|
||||
if (!loadModelFromYaml(model,modelBuffer)) {
|
||||
setError(tr("Cannot load ") + filename);
|
||||
return false;
|
||||
}
|
||||
} catch(const std::runtime_error& e) {
|
||||
setError(tr("Cannot load ") + filename + ":\n" + QString(e.what()));
|
||||
return false;
|
||||
}
|
||||
|
||||
model.modelIndex = modelIdx;
|
||||
strncpy(model.filename, mc.filename.c_str(), sizeof(model.filename)-1);
|
||||
|
||||
if (hasLabels && !strncmp(radioData.generalSettings.currModelFilename,
|
||||
model.filename, sizeof(model.filename))) {
|
||||
radioData.generalSettings.currModelIndex = modelIdx;
|
||||
}
|
||||
|
||||
if (hasLabels) {
|
||||
QStringList labels = QString(model.labels).split(',',QString::SkipEmptyParts);
|
||||
foreach(QString label, labels) {
|
||||
radioData.addLabel(label);
|
||||
}
|
||||
}
|
||||
|
||||
model.used = true;
|
||||
modelIdx++;
|
||||
}
|
||||
|
||||
// If no labels, add a Favorites
|
||||
if(hasLabels && !radioData.labels.size()) {
|
||||
radioData.labels.append(tr("Favorites"));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LabelsStorageFormat::writeYaml(const RadioData & radioData)
|
||||
{
|
||||
QByteArray radioSettingsBuffer;
|
||||
if (!writeRadioSettingsToYaml(radioData.generalSettings, radioSettingsBuffer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!writeFile(radioSettingsBuffer, "RADIO/radio.yml")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasLabels = getCurrentFirmware()->getCapability(HasModelLabels);
|
||||
|
||||
// B&W radios do not use the models.yml file and scan the MODELS folder for modelxx.yml files
|
||||
// models have been deleted or reordered in Companion so delete all old modelxx.yml and just in case models.yml files
|
||||
// from radio MODELS folder before writing new modelxx.yml files
|
||||
if (!hasLabels) {
|
||||
// fetch "MODELS/modelXX.yml"
|
||||
std::list<std::string> filelist;
|
||||
if (!getFileList(filelist)) {
|
||||
setError(tr("Cannot list files"));
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::regex yml_regex("MODELS/(model([0-9s]+)\\.yml)");
|
||||
for(const auto& f : filelist) {
|
||||
std::smatch match;
|
||||
if (std::regex_match(f, match, yml_regex)) {
|
||||
if (match.size() == 3) {
|
||||
if (!deleteFile(QString(f.c_str()))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EtxModelfiles modelFiles;
|
||||
for (const auto& model : radioData.models) {
|
||||
|
||||
if (model.isEmpty())
|
||||
continue;
|
||||
|
||||
QString modelFilename;
|
||||
if (hasLabels) {
|
||||
std::string ymlFilename = patchFilenameToYaml(model.filename);
|
||||
modelFilename =
|
||||
QString("MODELS/%1").arg(QString::fromStdString(ymlFilename));
|
||||
modelFiles.push_back(
|
||||
{ymlFilename, std::string(model.name)});
|
||||
} else {
|
||||
modelFilename = QString("MODELS/model%1.yml")
|
||||
.arg(model.modelIndex, 2, 10, QLatin1Char('0'));
|
||||
}
|
||||
|
||||
QByteArray modelData;
|
||||
writeModelToYaml(model, modelData);
|
||||
if (!writeFile(modelData, modelFilename)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasLabels) {
|
||||
QByteArray labelsListBuffer;
|
||||
if (!writeLabelsListToYaml(radioData, labelsListBuffer)
|
||||
|| !writeFile(labelsListBuffer, "MODELS/labels.yml")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user