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

commit dc99dd40d2
Author: 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

commit 4b747caff9
Author: 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.

commit e97f23a305
Author: 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:
Cliff
2022-09-03 09:01:19 +10:00
committed by GitHub
co-authored by HThuren
parent 20dc9c4d26
commit c8693569b0
85 changed files with 4006 additions and 2067 deletions
+371
View File
@@ -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;
}