refactor(color): classes used to manage models and labels, improve duplication UX (#7739)
Co-authored-by: philmoz <phil.a.mitchell@gmail.com> Co-authored-by: Peter Feerick <5500713+pfeerick@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
philmoz
Peter Feerick
Claude Opus 5.5
parent
61aa7a44c9
commit
6a1cf34079
@@ -230,7 +230,7 @@ bool RadioData::addLabelToModel(int index, QString label)
|
||||
|
||||
auto & modelLabelCsv = models[index].labels;
|
||||
// Make sure it will fit
|
||||
if (modelLabelCsv.size() + (size_t)label.size() + 1 < modelLabelCsv.capacity()) {
|
||||
if (modelLabelCsv.size() + (size_t)label.size() + 1 <= modelLabelCsv.capacity()) {
|
||||
QStringList modelLabels = QString::fromLatin1(modelLabelCsv.c_str()).split(',', Qt::SkipEmptyParts);
|
||||
if (modelLabels.indexOf(label) == -1) {
|
||||
modelLabels.append(label);
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
#define LUA_EXPORT_EXTRA(...)
|
||||
#endif
|
||||
|
||||
#define LABELS_LENGTH 100 // Maximum length of the label string
|
||||
#define LABELS_LENGTH 99 // Maximum length of the label string
|
||||
#define LABEL_LENGTH 16
|
||||
|
||||
#if defined(COLORLCD)
|
||||
|
||||
@@ -608,7 +608,7 @@ PACK(struct ModelHeader {
|
||||
uint8_t modelId[NUM_MODULES];
|
||||
MODEL_HEADER_BITMAP_FIELD
|
||||
#if defined(STORAGE_MODELSLIST)
|
||||
char labels[LABELS_LENGTH];
|
||||
char labels[LABELS_LENGTH + 1]; // alow null terminator
|
||||
#endif
|
||||
});
|
||||
|
||||
|
||||
@@ -2039,11 +2039,7 @@ void menuModelSetup(event_t event)
|
||||
else if (event == EVT_KEY_LONG(KEY_ENTER)) {
|
||||
killEvents(event);
|
||||
uint8_t newVal = 0;
|
||||
#if defined(STORAGE_MODELSLIST)
|
||||
newVal = modelslist.findNextUnusedModelId(moduleIdx);
|
||||
#else
|
||||
newVal = findNextUnusedModelId(g_eeGeneral.currModel, moduleIdx);
|
||||
#endif
|
||||
if (newVal != g_model.header.modelId[moduleIdx]) {
|
||||
modelHeaders[g_eeGeneral.currModel].modelId[moduleIdx] = g_model.header.modelId[moduleIdx] = newVal;
|
||||
storageDirty(EE_MODEL);
|
||||
|
||||
@@ -173,7 +173,8 @@ void ConfirmDialog::onCancel()
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
LabelDialog::LabelDialog(const char *label, int length, const char* title,
|
||||
std::function<void(std::string)> _saveHandler) :
|
||||
std::function<void(std::string)> _saveHandler,
|
||||
const char* excludedChars) :
|
||||
ModalWindow(false), saveHandler(std::move(_saveHandler))
|
||||
{
|
||||
strncpy(this->label, label, std::min(length, MAX_LABEL_LENGTH));
|
||||
@@ -197,7 +198,9 @@ LabelDialog::LabelDialog(const char *label, int length, const char* title,
|
||||
lv_obj_set_flex_align(box->getLvObj(), LV_FLEX_ALIGN_CENTER,
|
||||
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_SPACE_BETWEEN);
|
||||
|
||||
new TextEdit(box, rect_t{0, 0, LV_PCT(100), 0}, this->label, length);
|
||||
auto edit = new TextEdit(box, rect_t{0, 0, LV_PCT(100), 0}, this->label, length);
|
||||
if (excludedChars)
|
||||
edit->setExcludedCharacters(excludedChars);
|
||||
|
||||
box = new Window(form, rect_t{});
|
||||
box->padAll(PAD_MEDIUM);
|
||||
|
||||
@@ -134,7 +134,8 @@ class LabelDialog : public ModalWindow
|
||||
{
|
||||
public:
|
||||
LabelDialog(const char *label, int length, const char* title,
|
||||
std::function<void(std::string)> _saveHandler = nullptr);
|
||||
std::function<void(std::string)> _saveHandler = nullptr,
|
||||
const char* excludedChars = nullptr);
|
||||
|
||||
static constexpr int MAX_LABEL_LENGTH = 255;
|
||||
|
||||
|
||||
@@ -119,8 +119,8 @@ class NumberEdit : public TextButton
|
||||
std::string prefix;
|
||||
std::string suffix;
|
||||
std::string zeroText;
|
||||
std::function<std::string(int)> displayFunction;
|
||||
std::function<bool(int)> isValueAvailable;
|
||||
std::function<std::string(int)> displayFunction = nullptr;
|
||||
std::function<bool(int)> isValueAvailable = nullptr;
|
||||
|
||||
std::string getDisplayVal();
|
||||
|
||||
|
||||
@@ -74,10 +74,21 @@ class TextArea : public FormField
|
||||
cancelHandler = std::move(handler);
|
||||
}
|
||||
|
||||
void setExcludedChars(std::string& excludedChars) {
|
||||
if (excludedChars.size() > 0) {
|
||||
for (char c = ' '; c <= '~'; c += 1) {
|
||||
if (excludedChars.find(c) == std::string::npos)
|
||||
allowedChars += c;
|
||||
}
|
||||
lv_textarea_set_accepted_chars(lvobj, allowedChars.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
char* value;
|
||||
uint8_t length;
|
||||
std::function<void(void)> cancelHandler = nullptr;
|
||||
std::string allowedChars;
|
||||
|
||||
void trim()
|
||||
{
|
||||
@@ -165,6 +176,8 @@ void TextEdit::openEdit()
|
||||
lv_group_focus_obj(lvobj);
|
||||
edit->hide();
|
||||
});
|
||||
if (excludedChars.size() > 0)
|
||||
edit->setExcludedChars(excludedChars);
|
||||
}
|
||||
edit->update();
|
||||
edit->show();
|
||||
|
||||
@@ -36,8 +36,11 @@ class TextEdit : public TextButton
|
||||
void preview(bool edited, char* text, uint8_t length);
|
||||
void update();
|
||||
|
||||
void setExcludedCharacters(const char* chars) { excludedChars = chars; }
|
||||
|
||||
protected:
|
||||
std::function<void(void)> updateHandler = nullptr;
|
||||
std::string excludedChars = "";
|
||||
TextArea* edit = nullptr;
|
||||
char* text;
|
||||
uint8_t length;
|
||||
|
||||
@@ -98,7 +98,7 @@ class ModelButton : public Button
|
||||
}
|
||||
lv_label_set_long_mode(modelName->getLvObj(), LV_LABEL_LONG_DOT);
|
||||
|
||||
bool chk = (modelCell == modelslist.getCurrentModel());
|
||||
bool chk = (modelCell == modelCellManager.getCurrentModel());
|
||||
if (chk != checked()) {
|
||||
check(chk);
|
||||
if (chk)
|
||||
@@ -197,15 +197,15 @@ class ModelsPageBody : public Window
|
||||
|
||||
ModelsVector models;
|
||||
if (selectedLabels.size()) {
|
||||
models = modelslabels.getModelsInLabels(selectedLabels);
|
||||
models = modelCellManager.getFilteredModelList(selectedLabels);
|
||||
} else {
|
||||
models = modelslabels.getAllModels();
|
||||
models = modelCellManager.getAllModels();
|
||||
}
|
||||
|
||||
// Used to work out which button to set focus to.
|
||||
// Priority -
|
||||
// current active model
|
||||
// previously selected model
|
||||
// current active model
|
||||
// first model in the list
|
||||
ModelButton *firstButton = nullptr;
|
||||
ModelButton *focusedButton = nullptr;
|
||||
@@ -238,8 +238,8 @@ class ModelsPageBody : public Window
|
||||
}
|
||||
|
||||
if (!firstButton) firstButton = button;
|
||||
if (model == modelslist.getCurrentModel()) focusedButton = button;
|
||||
if (model == focusedModel && !focusedButton) focusedButton = button;
|
||||
if (model == focusedModel) focusedButton = button;
|
||||
if (model == modelCellManager.getCurrentModel() && !focusedButton) focusedButton = button;
|
||||
|
||||
// Press Handler for Models
|
||||
button->setPressHandler([=]() -> uint8_t {
|
||||
@@ -251,7 +251,7 @@ class ModelsPageBody : public Window
|
||||
} else {
|
||||
focusedModel = model;
|
||||
}
|
||||
return model == modelslist.getCurrentModel();
|
||||
return model == modelCellManager.getCurrentModel();
|
||||
});
|
||||
|
||||
// Long Press Handler for Models
|
||||
@@ -261,7 +261,7 @@ class ModelsPageBody : public Window
|
||||
focusedModel = model;
|
||||
}
|
||||
openMenu();
|
||||
return model == modelslist.getCurrentModel();
|
||||
return model == modelCellManager.getCurrentModel();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -293,11 +293,11 @@ class ModelsPageBody : public Window
|
||||
|
||||
inline void setSortOrder(ModelsSortBy sortOrder)
|
||||
{
|
||||
modelslabels.setSortOrder(sortOrder);
|
||||
modelCellManager.setSortOrder(sortOrder);
|
||||
update();
|
||||
}
|
||||
|
||||
ModelsSortBy getSortOrder() const { return modelslabels.sortOrder(); }
|
||||
ModelsSortBy getSortOrder() const { return modelCellManager.sortOrder(); }
|
||||
|
||||
void setLblRefreshFunc(std::function<void()> fnc)
|
||||
{
|
||||
@@ -305,7 +305,6 @@ class ModelsPageBody : public Window
|
||||
}
|
||||
|
||||
protected:
|
||||
ModelsSortBy _sortOrder;
|
||||
bool isDirty = false;
|
||||
bool refresh = false;
|
||||
std::string selectedLabel;
|
||||
@@ -328,13 +327,13 @@ class ModelsPageBody : public Window
|
||||
Menu *menu = new Menu();
|
||||
menu->setTitle(focusedModel->modelName);
|
||||
if (g_eeGeneral.modelQuickSelect ||
|
||||
focusedModel != modelslist.getCurrentModel()) {
|
||||
focusedModel != modelCellManager.getCurrentModel()) {
|
||||
menu->addLine(STR_SELECT_MODEL, [=]() { selectModel(focusedModel); });
|
||||
}
|
||||
menu->addLine(STR_DUPLICATE_MODEL, [=]() { duplicateModel(focusedModel); });
|
||||
menu->addLine(STR_LABEL_MODEL, [=]() { editLabels(focusedModel); });
|
||||
menu->addLine(STR_SAVE_TEMPLATE, [=]() { saveAsTemplate(focusedModel); });
|
||||
if (focusedModel != modelslist.getCurrentModel()) {
|
||||
if (focusedModel != modelCellManager.getCurrentModel()) {
|
||||
menu->addLine(STR_DELETE_MODEL, [=]() { deleteModel(focusedModel); });
|
||||
}
|
||||
}
|
||||
@@ -343,7 +342,7 @@ class ModelsPageBody : public Window
|
||||
{
|
||||
// Don't need to check connection to receiver if re-selecting the active
|
||||
// model
|
||||
if (model != modelslist.getCurrentModel()) {
|
||||
if (model != modelCellManager.getCurrentModel()) {
|
||||
bool modelConnected =
|
||||
TELEMETRY_STREAMING() && !g_eeGeneral.disableRssiPoweroffAlarm;
|
||||
if (modelConnected) {
|
||||
@@ -364,7 +363,7 @@ class ModelsPageBody : public Window
|
||||
closeHandler();
|
||||
|
||||
// Skip reloading model if re-selecting the active model
|
||||
if (model != modelslist.getCurrentModel()) {
|
||||
if (model != modelCellManager.getCurrentModel()) {
|
||||
// store changes (if any) and load selected model
|
||||
storageFlushCurrentModel();
|
||||
storageCheck(true);
|
||||
@@ -382,7 +381,7 @@ class ModelsPageBody : public Window
|
||||
LayoutFactory::deleteTopBarWidgets();
|
||||
|
||||
loadModel(g_eeGeneral.currModelFilename, true);
|
||||
modelslist.setCurrentModel(model);
|
||||
modelCellManager.setCurrentModel(model);
|
||||
|
||||
// Load new main view layout
|
||||
LayoutFactory::loadCustomScreens();
|
||||
@@ -413,10 +412,12 @@ class ModelsPageBody : public Window
|
||||
// Make a new model which is a copy of the selected one, set the
|
||||
// same labels
|
||||
auto new_model =
|
||||
modelslist.addModel(duplicatedFilename, true, model);
|
||||
for (const auto &lbl : modelslabels.getLabelsByModel(model)) {
|
||||
modelslabels.addLabelToModel(lbl, new_model);
|
||||
}
|
||||
modelCellManager.addModel(duplicatedFilename, true, model);
|
||||
// Give the copy a unique name and save it to disk
|
||||
new_model->setUniqueName();
|
||||
new_model->updateModelFile();
|
||||
// Set new model as focused button
|
||||
focusedModel = new_model;
|
||||
update();
|
||||
} else {
|
||||
TRACE("ModelsListError: Invalid File");
|
||||
@@ -429,7 +430,7 @@ class ModelsPageBody : public Window
|
||||
new ConfirmDialog(
|
||||
STR_DELETE_MODEL,
|
||||
std::string(model->modelName, sizeof(model->modelName)).c_str(), [=] {
|
||||
modelslist.removeModel(model);
|
||||
modelCellManager.removeModel(model);
|
||||
if (refreshLabels != nullptr) refreshLabels();
|
||||
|
||||
update();
|
||||
@@ -438,7 +439,7 @@ class ModelsPageBody : public Window
|
||||
|
||||
void editLabels(ModelCell *model)
|
||||
{
|
||||
auto labels = modelslabels.getLabels();
|
||||
auto labels = modelCellManager.getLabels();
|
||||
|
||||
// dont display menu if there will be no labels
|
||||
if (labels.size()) {
|
||||
@@ -451,18 +452,18 @@ class ModelsPageBody : public Window
|
||||
}
|
||||
});
|
||||
|
||||
for (auto &label : modelslabels.getLabels()) {
|
||||
for (auto &label : modelCellManager.getLabels()) {
|
||||
menu->addLineBuffered(
|
||||
label,
|
||||
[=]() {
|
||||
if (!modelslabels.isLabelSelected(label, model))
|
||||
modelslabels.addLabelToModel(label, model, true);
|
||||
if (!model->hasLabel(label))
|
||||
modelCellManager.addLabelToModel(label, model);
|
||||
else
|
||||
modelslabels.removeLabelFromModel(label, model, true);
|
||||
modelCellManager.removeLabelFromModel(label, model);
|
||||
isDirty = true;
|
||||
if (refreshLabels != nullptr) refreshLabels();
|
||||
},
|
||||
[=]() { return modelslabels.isLabelSelected(label, model); });
|
||||
[=]() { return model->hasLabel(label); });
|
||||
}
|
||||
menu->updateLines();
|
||||
}
|
||||
@@ -537,9 +538,9 @@ ModelLabelsWindow::ModelLabelsWindow() : Page(ICON_MODEL_SELECT, PAD_ZERO, true)
|
||||
buildBody(body);
|
||||
|
||||
// find the first label of the current model and make that label active
|
||||
auto currentModel = modelslist.getCurrentModel();
|
||||
auto currentModel = modelCellManager.getCurrentModel();
|
||||
if (currentModel != nullptr) {
|
||||
auto modelLabels = modelslabels.getLabelsByModel(currentModel);
|
||||
auto modelLabels = currentModel->getLabels();
|
||||
if (modelLabels.size() > 0) {
|
||||
auto allLabels = getLabels();
|
||||
auto found =
|
||||
@@ -613,8 +614,8 @@ void ModelLabelsWindow::newModel()
|
||||
new SelectTemplateFolder([=](std::string folder, std::string name) {
|
||||
// Create a new blank ModelCell and activate it first, createmodel() will
|
||||
// modify the model in memory.
|
||||
auto newCell = modelslist.addModel("", false);
|
||||
modelslist.setCurrentModel(newCell);
|
||||
auto newCell = modelCellManager.addModel("", false);
|
||||
modelCellManager.setCurrentModel(newCell);
|
||||
|
||||
// Make the new model
|
||||
createModel();
|
||||
@@ -638,7 +639,7 @@ void ModelLabelsWindow::newModel()
|
||||
storageCheck(true);
|
||||
|
||||
// Update the current cell's data
|
||||
modelslist.updateCurrentModelCell();
|
||||
modelCellManager.updateCurrentModelCell();
|
||||
|
||||
#if defined(LUA)
|
||||
// If there is a wizard Lua script, fire it up
|
||||
@@ -661,12 +662,12 @@ void ModelLabelsWindow::newLabel()
|
||||
{
|
||||
tmpLabel[0] = '\0';
|
||||
new LabelDialog(tmpLabel, LABEL_LENGTH, STR_ENTER_LABEL, [=](std::string label) {
|
||||
int newlabindex = modelslabels.addLabel(label);
|
||||
int newlabindex = modelCellManager.addLabel(label);
|
||||
if (newlabindex >= 0) {
|
||||
auto labels = getLabels();
|
||||
lblselector->setNames(labels);
|
||||
}
|
||||
});
|
||||
}, labelExcludedChars);
|
||||
}
|
||||
|
||||
void ModelLabelsWindow::buildHead(Window *hdr)
|
||||
@@ -747,14 +748,14 @@ void ModelLabelsWindow::buildBody(Window *window)
|
||||
});
|
||||
#endif
|
||||
|
||||
std::set<uint32_t> filteredLabels = modelslabels.filteredLabels();
|
||||
std::set<uint32_t> filteredLabels = modelCellManager.filteredLabels();
|
||||
|
||||
if (g_eeGeneral.labelSingleSelect == 0) {
|
||||
lblselector->setMultiSelect(true);
|
||||
lblselector->setSelected(filteredLabels);
|
||||
lblselector->setMultiSelectHandler([=](std::set<uint32_t> selected,
|
||||
std::set<uint32_t> oldselection) {
|
||||
if (modelslabels.getUnlabeledModels().size() != 0) {
|
||||
if (modelCellManager.getUnlabeledModels().size() != 0) {
|
||||
// Special case for mutually exclusive Unsorted
|
||||
bool unsrt_is_selected =
|
||||
selected.find(lblselector->getRowCount() - 1) != selected.end();
|
||||
@@ -831,7 +832,7 @@ void ModelLabelsWindow::buildBody(Window *window)
|
||||
if (newLabel.size() > 0) {
|
||||
auto rndialog =
|
||||
new ProgressDialog(STR_RENAME_LABEL, [=]() {});
|
||||
modelslabels.renameLabel(
|
||||
modelCellManager.renameLabel(
|
||||
oldLabel, newLabel, [=](const char *name, int percentage) {
|
||||
rndialog->setTitle(std::string(STR_RENAME_LABEL) + " " +
|
||||
name);
|
||||
@@ -840,8 +841,7 @@ void ModelLabelsWindow::buildBody(Window *window)
|
||||
});
|
||||
auto labels = getLabels();
|
||||
lblselector->setNames(labels);
|
||||
mdlselector->clearButtons();
|
||||
updateFilteredLabels(modelslabels.filteredLabels(), false);
|
||||
updateFilteredLabels(modelCellManager.filteredLabels(), false);
|
||||
}
|
||||
});
|
||||
return 0;
|
||||
@@ -852,7 +852,7 @@ void ModelLabelsWindow::buildBody(Window *window)
|
||||
STR_DELETE_LABEL, labelToDelete.c_str(), [=]() {
|
||||
auto deldialog =
|
||||
new ProgressDialog(STR_DELETE_LABEL, [=]() {});
|
||||
modelslabels.removeLabel(
|
||||
modelCellManager.removeLabel(
|
||||
labelToDelete, [=](const char *name, int percentage) {
|
||||
deldialog->setTitle(std::string(STR_DELETE_LABEL) + " " +
|
||||
name);
|
||||
@@ -869,19 +869,18 @@ void ModelLabelsWindow::buildBody(Window *window)
|
||||
else
|
||||
newset.insert(lblselector->getActiveItem());
|
||||
}
|
||||
mdlselector->clearButtons();
|
||||
updateFilteredLabels(newset);
|
||||
});
|
||||
return 0;
|
||||
});
|
||||
if (modelslabels.getLabels().size() > 1) {
|
||||
if (modelCellManager.getLabels().size() > 1) {
|
||||
if (selected != 0) {
|
||||
menu->addLine(STR_MOVE_UP, [=]() {
|
||||
moveLabel(selected, -1);
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
if (selected != (int)modelslabels.getLabels().size() - 1) {
|
||||
if (selected != (int)modelCellManager.getLabels().size() - 1) {
|
||||
menu->addLine(STR_MOVE_DOWN, [=]() {
|
||||
moveLabel(selected, 1);
|
||||
return 0;
|
||||
@@ -897,7 +896,7 @@ void ModelLabelsWindow::moveLabel(int selected, int direction)
|
||||
{
|
||||
int swapSelected = selected + direction;
|
||||
|
||||
modelslabels.moveLabelTo(selected, swapSelected);
|
||||
modelCellManager.moveLabelUp(selected > swapSelected ? selected : swapSelected);
|
||||
|
||||
std::set<uint32_t> newset = lblselector->getSelection();
|
||||
bool isSelected = newset.find(selected) != newset.end();
|
||||
@@ -937,10 +936,8 @@ void ModelLabelsWindow::updateFilteredLabels(std::set<uint32_t> selected,
|
||||
for (auto sel : selected) {
|
||||
if (sel < labels.size()) sellabels.push_back(labels[sel]);
|
||||
}
|
||||
if (setdirty) { // Save to file?
|
||||
modelslabels.setFilteredLabels(selected);
|
||||
modelslabels.setDirty();
|
||||
}
|
||||
if (setdirty) // Save to file?
|
||||
modelCellManager.setFilteredLabels(selected);
|
||||
mdlselector->setLabels(sellabels); // Update the list
|
||||
}
|
||||
|
||||
@@ -952,7 +949,7 @@ void ModelLabelsWindow::labelRefreshRequest()
|
||||
|
||||
void ModelLabelsWindow::setTitle()
|
||||
{
|
||||
auto curModel = modelslist.getCurrentModel();
|
||||
auto curModel = modelCellManager.getCurrentModel();
|
||||
auto modelName = curModel != nullptr ? curModel->modelName : STR_NONE;
|
||||
|
||||
std::string title2 = STR_ACTIVE;
|
||||
|
||||
@@ -58,8 +58,8 @@ class ModelLabelsWindow : public Page
|
||||
|
||||
LabelsVector getLabels()
|
||||
{
|
||||
auto labels = modelslabels.getLabels();
|
||||
if (modelslabels.getUnlabeledModels().size() > 0)
|
||||
auto labels = modelCellManager.getLabels();
|
||||
if (modelCellManager.getUnlabeledModels().size() > 0)
|
||||
labels.emplace_back(STR_UNLABELEDMODEL);
|
||||
return labels;
|
||||
}
|
||||
|
||||
@@ -391,7 +391,7 @@ const static SetupLineDef setupLines[] = {
|
||||
new ModelTextEdit(parent, {x, y, ModelSetupPage::NAM_W, 0},
|
||||
g_model.header.name, sizeof(g_model.header.name),
|
||||
[=]() {
|
||||
auto model = modelslist.getCurrentModel();
|
||||
auto model = modelCellManager.getCurrentModel();
|
||||
if (model) {
|
||||
model->setModelName(g_model.header.name);
|
||||
}
|
||||
@@ -403,29 +403,23 @@ const static SetupLineDef setupLines[] = {
|
||||
// Model labels
|
||||
STR_DEF(STR_LABELS),
|
||||
[](Window* parent, coord_t x, coord_t y) {
|
||||
auto curmod = modelslist.getCurrentModel();
|
||||
TextButton* btn = new TextButton(parent, {x, y, 0, 0}, modelslabels.getBulletLabelString(curmod, STR_UNLABELEDMODEL));
|
||||
auto curmod = modelCellManager.getCurrentModel();
|
||||
TextButton* btn = new TextButton(parent, {x, y, 0, 0}, curmod->getBulletLabelString(STR_UNLABELEDMODEL));
|
||||
btn->setPressHandler([=]() {
|
||||
Menu *menu = new Menu(true);
|
||||
menu->setTitle(STR_LABELS);
|
||||
for (auto &label : modelslabels.getLabels()) {
|
||||
for (auto &label : modelCellManager.getLabels()) {
|
||||
menu->addLineBuffered(
|
||||
label,
|
||||
[=]() {
|
||||
if (!modelslabels.isLabelSelected(label, curmod))
|
||||
modelslabels.addLabelToModel(label, curmod);
|
||||
if (!curmod->hasLabel(label))
|
||||
modelCellManager.addLabelToModel(label, curmod);
|
||||
else
|
||||
modelslabels.removeLabelFromModel(label, curmod);
|
||||
btn->setText(modelslabels.getBulletLabelString(
|
||||
curmod, STR_UNLABELEDMODEL));
|
||||
strncpy(g_model.header.labels,
|
||||
ModelMap::toCSV(modelslabels.getLabelsByModel(curmod))
|
||||
.c_str(),
|
||||
sizeof(g_model.header.labels));
|
||||
g_model.header.labels[sizeof(g_model.header.labels) - 1] = '\0';
|
||||
modelCellManager.removeLabelFromModel(label, curmod);
|
||||
btn->setText(curmod->getBulletLabelString(STR_UNLABELEDMODEL));
|
||||
SET_DIRTY();
|
||||
},
|
||||
[=]() { return modelslabels.isLabelSelected(label, curmod); });
|
||||
[=]() { return curmod->hasLabel(label); });
|
||||
}
|
||||
menu->updateLines();
|
||||
return 0;
|
||||
@@ -442,7 +436,7 @@ const static SetupLineDef setupLines[] = {
|
||||
},
|
||||
[=](std::string newValue) {
|
||||
strncpy(g_model.header.bitmap, newValue.c_str(), LEN_BITMAP_NAME);
|
||||
auto model = modelslist.getCurrentModel();
|
||||
auto model = modelCellManager.getCurrentModel();
|
||||
if (model) {
|
||||
strncpy(model->modelBitmap, newValue.c_str(), LEN_BITMAP_NAME);
|
||||
model->modelBitmap[LEN_BITMAP_NAME] = '\0';
|
||||
|
||||
@@ -233,7 +233,7 @@ class ModuleWindow : public Window
|
||||
GET_DEFAULT(*modelId), [=](int32_t newValue) {
|
||||
if (newValue != *modelId) {
|
||||
*modelId = newValue;
|
||||
modelslist.updateCurrentModelCell();
|
||||
modelCellManager.updateCurrentModelCell();
|
||||
updateIDStaticText(moduleIdx);
|
||||
#if defined(CROSSFIRE)
|
||||
if (isModuleCrossfire(moduleIdx)) {
|
||||
@@ -546,7 +546,7 @@ class ModuleWindow : public Window
|
||||
if (idUnique == nullptr) return;
|
||||
char buffer[50];
|
||||
std::string idStr = STR_MODELIDUNIQUE;
|
||||
if (!modelslist.isModelIdUnique(mdIdx, buffer, sizeof(buffer))) {
|
||||
if (!modelCellManager.isModelIdUnique(mdIdx, buffer, sizeof(buffer))) {
|
||||
idStr = STR_MODELIDUSED;
|
||||
idStr = idStr + buffer;
|
||||
lv_obj_add_state(idUnique->getLvObj(), ETX_STATE_UNIQUE_ID_WARN);
|
||||
|
||||
@@ -754,7 +754,7 @@ const static SetupLineDef manageModelsSetupLines[] = {
|
||||
GET_DEFAULT(g_eeGeneral.labelSingleSelect),
|
||||
[=](int newValue) {
|
||||
g_eeGeneral.labelSingleSelect = newValue;
|
||||
modelslabels.clearFilter();
|
||||
modelCellManager.clearFilter();
|
||||
Messaging::send(Messaging::REFRESH);
|
||||
SET_DIRTY();
|
||||
});
|
||||
|
||||
+796
-803
File diff suppressed because it is too large
Load Diff
+112
-144
@@ -21,66 +21,25 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include "sdcard.h"
|
||||
|
||||
#include "dataconstants.h"
|
||||
#include "rtc.h"
|
||||
|
||||
#define DEFAULT_MODEL_SORT NAME_ASC
|
||||
|
||||
// Characters not (currently) allowed in labels
|
||||
constexpr const char* labelExcludedChars = "\\\":'-";
|
||||
|
||||
struct ModelHeader;
|
||||
struct ModuleData;
|
||||
|
||||
struct SimpleModuleData {
|
||||
uint8_t type = 0;
|
||||
uint8_t subType = 0;
|
||||
};
|
||||
|
||||
typedef struct PACKED {
|
||||
FSIZE_t fsize; /* File size */
|
||||
WORD fdate; /* Modified date */
|
||||
WORD ftime; /* Modified time */
|
||||
} FInfoH;
|
||||
|
||||
#define FILE_HASH_LENGTH (sizeof(FInfoH) * 2) // Hex string output
|
||||
|
||||
class ModelCell
|
||||
{
|
||||
public:
|
||||
char modelFilename[LEN_MODEL_FILENAME + 1] = "";
|
||||
char modelName[LEN_MODEL_NAME + 1] = "";
|
||||
char modelFinfoHash[FILE_HASH_LENGTH + 1] = "";
|
||||
#if LEN_BITMAP_NAME > 0
|
||||
char modelBitmap[LEN_BITMAP_NAME + 1] = "";
|
||||
#endif
|
||||
gtime_t lastOpened = 0;
|
||||
bool _isDirty = true;
|
||||
|
||||
bool valid_rfData;
|
||||
uint8_t modelId[NUM_MODULES] = {0, 0};
|
||||
SimpleModuleData moduleData[NUM_MODULES];
|
||||
|
||||
explicit ModelCell(const char *fileName);
|
||||
|
||||
void setModelName(char *name);
|
||||
void setRfData(ModelHeader *header, ModuleData* moduleData);
|
||||
|
||||
void setRfModuleData(uint8_t moduleIdx, ModuleData *modData);
|
||||
};
|
||||
|
||||
typedef std::vector<std::string> LabelsVector;
|
||||
typedef std::vector<ModelCell *> ModelsVector;
|
||||
typedef enum {
|
||||
NO_SORT,
|
||||
NAME_ASC,
|
||||
@@ -90,116 +49,101 @@ typedef enum {
|
||||
SORT_COUNT
|
||||
} ModelsSortBy;
|
||||
|
||||
/**
|
||||
* @brief ModelMap is a multimap of all models and their cooresponding
|
||||
* labels. Lables are referenced by index, stored in var labels
|
||||
*/
|
||||
struct SimpleModuleData {
|
||||
uint8_t type = 0;
|
||||
uint8_t subType = 0;
|
||||
};
|
||||
|
||||
class ModelMap : protected std::multimap<uint16_t, ModelCell *>
|
||||
// Must match start of the FILINFO struct
|
||||
typedef struct PACKED {
|
||||
FSIZE_t fsize; /* File size */
|
||||
WORD fdate; /* Modified date */
|
||||
WORD ftime; /* Modified time */
|
||||
} FInfoH;
|
||||
|
||||
#define FILE_HASH_LENGTH (sizeof(FInfoH) * 2) // Hex string output
|
||||
|
||||
typedef std::vector<std::string> LabelsVector;
|
||||
|
||||
struct LabelsMap
|
||||
{
|
||||
public:
|
||||
ModelsVector getUnlabeledModels();
|
||||
ModelsVector getAllModels();
|
||||
ModelsVector getModelsByLabel(const std::string &);
|
||||
ModelsVector getModelsInLabels(const LabelsVector &lbls);
|
||||
LabelsVector getLabelsByModel(ModelCell *);
|
||||
std::map<std::string, bool> getSelectedLabels(ModelCell *);
|
||||
bool isLabelSelected(const std::string &, ModelCell *);
|
||||
LabelsMap() {}
|
||||
|
||||
void addLabel(uint8_t idx);
|
||||
void removeLabel(uint8_t idx);
|
||||
bool deleteLabel(uint8_t idx);
|
||||
void moveLabelUp(uint8_t idx);
|
||||
bool hasLabel(uint8_t idx);
|
||||
LabelsVector getLabels();
|
||||
int addLabel(std::string lbl);
|
||||
bool addLabelToModel(const std::string &, ModelCell *, bool update = false);
|
||||
bool removeLabelFromModel(const std::string &label, ModelCell *,
|
||||
bool update = false);
|
||||
bool removeLabel(
|
||||
const std::string &,
|
||||
std::function<void(const char *file, int progress)> progress = nullptr);
|
||||
bool moveLabelTo(unsigned current, unsigned newind);
|
||||
bool renameLabel(const std::string &from, std::string to,
|
||||
std::function<void(const char *file, int progress)> progress = nullptr);
|
||||
std::string getCurrentLabel() { return currentlabel; };
|
||||
std::string getBulletLabelString(ModelCell *, const char *noresults = "");
|
||||
void setDirty(bool save = false);
|
||||
void resetDirty() { _isDirty = false; }
|
||||
bool isDirty() { return _isDirty; }
|
||||
|
||||
// Currently selected labels in the GUI
|
||||
void setFilteredLabels(std::set<uint32_t> filtlbls)
|
||||
{
|
||||
this->filtlbls = std::move(filtlbls);
|
||||
}
|
||||
void clearFilter() { filtlbls.clear(); }
|
||||
void addFilteredLabel(const std::string &lbl);
|
||||
bool isLabelFiltered(const std::string &lbl);
|
||||
std::set<uint32_t> filteredLabels() { return filtlbls; }
|
||||
|
||||
void setSortOrder(ModelsSortBy sortby);
|
||||
ModelsSortBy sortOrder() {return _sortOrder;}
|
||||
|
||||
static std::string toCSV(const LabelsVector &labels);
|
||||
static LabelsVector fromCSV(const char *str);
|
||||
static void escapeCSV(std::string &str);
|
||||
static void unEscapeCSV(std::string &str);
|
||||
static void removeYAMLChars(std::string &str);
|
||||
|
||||
void clear()
|
||||
{
|
||||
_isDirty = true;
|
||||
labels.clear();
|
||||
std::multimap<uint16_t, ModelCell *>::clear();
|
||||
}
|
||||
|
||||
void updateModelCell(ModelCell *);
|
||||
bool removeModels(ModelCell *);
|
||||
|
||||
bool writeModelLabels(ModelCell*, const char*);
|
||||
|
||||
protected:
|
||||
ModelsSortBy _sortOrder = DEFAULT_MODEL_SORT;
|
||||
bool _isDirty = true;
|
||||
std::set<uint32_t> filtlbls;
|
||||
std::string currentlabel = "";
|
||||
|
||||
bool updateModelFile(ModelCell *);
|
||||
void sortModelsBy(ModelsVector &mv, ModelsSortBy sortby);
|
||||
|
||||
int getIndexByLabel(const std::string &str)
|
||||
{
|
||||
auto a = std::find(labels.begin(), labels.end(), str);
|
||||
return a == labels.end() ? -1 : a - labels.begin();
|
||||
}
|
||||
|
||||
std::string getLabelByIndex(uint16_t index)
|
||||
{
|
||||
if (index < (uint16_t)labels.size())
|
||||
return labels.at(index);
|
||||
else
|
||||
return std::string();
|
||||
}
|
||||
|
||||
private:
|
||||
LabelsVector labels; // Storage space for discovered labels
|
||||
std::vector<uint16_t> labelMap;
|
||||
};
|
||||
|
||||
class ModelCell
|
||||
{
|
||||
public:
|
||||
ModelCell(const char *fileName);
|
||||
|
||||
char modelFilename[LEN_MODEL_FILENAME + 1] = "";
|
||||
char modelName[LEN_MODEL_NAME + 1] = "";
|
||||
char modelFinfoHash[FILE_HASH_LENGTH + 1] = "";
|
||||
#if LEN_BITMAP_NAME > 0
|
||||
char modelBitmap[LEN_BITMAP_NAME + 1] = "";
|
||||
#endif
|
||||
gtime_t lastOpened = 0;
|
||||
bool _isDirty = true;
|
||||
bool valid_rfData = false;
|
||||
uint8_t modelId[NUM_MODULES] = {0, 0};
|
||||
SimpleModuleData moduleData[NUM_MODULES];
|
||||
|
||||
void setModelName(const char *name);
|
||||
void setDefaultName();
|
||||
void setUniqueName();
|
||||
void setRfData(ModelHeader *header, ModuleData* moduleData);
|
||||
|
||||
void addLabel(uint8_t idx);
|
||||
void removeLabel(uint8_t idx);
|
||||
void deleteLabel(uint8_t idx);
|
||||
void moveLabelUp(uint8_t idx);
|
||||
bool hasLabel(uint8_t idx) { return labelsMap.hasLabel(idx); }
|
||||
bool hasLabel(const std::string& label);
|
||||
|
||||
void updateModelFile();
|
||||
void updateModelCell();
|
||||
void updateFinfoHash();
|
||||
|
||||
void duplicateFrom(ModelCell* src);
|
||||
|
||||
bool writeModelLabels(const char*);
|
||||
|
||||
LabelsVector getLabels() { return labelsMap.getLabels(); }
|
||||
|
||||
std::string getBulletLabelString(const char *noresults = "");
|
||||
|
||||
protected:
|
||||
LabelsMap labelsMap;
|
||||
};
|
||||
|
||||
typedef std::vector<ModelCell *> ModelsVector;
|
||||
|
||||
class ModelsList : public ModelsVector
|
||||
{
|
||||
bool loaded;
|
||||
|
||||
ModelCell *currentModel;
|
||||
|
||||
void init();
|
||||
|
||||
public:
|
||||
ModelsList();
|
||||
~ModelsList();
|
||||
|
||||
bool load();
|
||||
const char *save(LabelsVector newOrder=LabelsVector());
|
||||
const char *save();
|
||||
void clear();
|
||||
|
||||
void setCurrentModel(ModelCell *cell);
|
||||
void updateCurrentModelCell();
|
||||
|
||||
ModelCell *getCurrentModel() const { return currentModel; }
|
||||
ModelCell* getCurrentModel() const { return currentModel; }
|
||||
ModelCell* getModel(const char* filename);
|
||||
ModelCell* getModelWithName(const char* name);
|
||||
|
||||
ModelCell *addModel(const char *name, bool save = true, ModelCell *copyCell = nullptr);
|
||||
bool removeModel(ModelCell *model);
|
||||
@@ -207,19 +151,43 @@ class ModelsList : public ModelsVector
|
||||
bool isModelIdUnique(uint8_t moduleIdx, char *warn_buf, size_t warn_buf_len);
|
||||
uint8_t findNextUnusedModelId(uint8_t moduleIdx);
|
||||
|
||||
typedef struct _filedat {
|
||||
std::string name;
|
||||
char hash[FILE_HASH_LENGTH + 1];
|
||||
bool curmodel = false;
|
||||
bool celladded = false;
|
||||
} filedat;
|
||||
std::vector<filedat> fileHashInfo;
|
||||
void setSortOrder(ModelsSortBy sortby);
|
||||
ModelsSortBy sortOrder() {return _sortOrder;}
|
||||
|
||||
void sortModelsBy(ModelsVector &mv, ModelsSortBy sortby);
|
||||
|
||||
ModelsVector getUnlabeledModels();
|
||||
ModelsVector getAllModels();
|
||||
ModelsVector getFilteredModelList(const LabelsVector &lbls);
|
||||
|
||||
int getIndexByLabel(const std::string &str);
|
||||
std::string getLabelByIndex(uint16_t index);
|
||||
const LabelsVector& getLabels() const { return labels; }
|
||||
|
||||
int addLabel(std::string lbl);
|
||||
void removeLabel(const std::string &,
|
||||
std::function<void(const char *file, int progress)> progress = nullptr);
|
||||
void moveLabelUp(uint16_t idx);
|
||||
void renameLabel(const std::string &from, const std::string& to,
|
||||
std::function<void(const char *file, int progress)> progress = nullptr);
|
||||
|
||||
bool addLabelToModel(const std::string &, ModelCell *, bool update = true);
|
||||
void addLabelsToModel(const char* labels, ModelCell* cell);
|
||||
void removeLabelFromModel(const std::string &, ModelCell *);
|
||||
|
||||
void setFilteredLabels(std::set<uint32_t> filtlbls);
|
||||
void clearFilter() { filtlbls.clear(); }
|
||||
void addFilteredLabel(const std::string &lbl);
|
||||
bool isLabelFiltered(const std::string &lbl);
|
||||
std::set<uint32_t> filteredLabels() { return filtlbls; }
|
||||
|
||||
protected:
|
||||
FIL file;
|
||||
ModelCell *currentModel = nullptr;
|
||||
ModelsSortBy _sortOrder = DEFAULT_MODEL_SORT;
|
||||
LabelsVector labels; // Storage space for discovered labels
|
||||
std::set<uint32_t> filtlbls;
|
||||
|
||||
bool loadYaml();
|
||||
};
|
||||
|
||||
extern ModelsList modelslist;
|
||||
extern ModelMap modelslabels;
|
||||
extern ModelsList modelCellManager;
|
||||
|
||||
@@ -114,7 +114,7 @@ void storageCheck(bool immediately)
|
||||
if (storageDirtyMsk & EE_LABELS) {
|
||||
if (retryLabelsCount < retryLimit) {
|
||||
TRACE("SD card write labels");
|
||||
const char * error = modelslist.save();
|
||||
const char * error = modelCellManager.save();
|
||||
if (error) {
|
||||
TRACE("writeLabels error=%s", error);
|
||||
retryLabelsCount += 1;
|
||||
@@ -137,7 +137,7 @@ void storageCheck(bool immediately)
|
||||
TRACE("SD card write model settings");
|
||||
const char * error = writeModel();
|
||||
#if defined(STORAGE_MODELSLIST)
|
||||
modelslist.updateCurrentModelCell();
|
||||
modelCellManager.updateCurrentModelCell();
|
||||
#endif
|
||||
if (error) {
|
||||
TRACE("writeModel error=%s", error);
|
||||
@@ -206,7 +206,7 @@ void storageReadAll()
|
||||
#if defined(STORAGE_MODELSLIST)
|
||||
// Wipe models list in case
|
||||
// it's being reloaded after USB connection
|
||||
modelslist.clear();
|
||||
modelCellManager.clear();
|
||||
#endif
|
||||
|
||||
// Some radio defaults overriden by config loading:
|
||||
@@ -245,7 +245,7 @@ void storageReadAll()
|
||||
|
||||
#if defined(STORAGE_MODELSLIST)
|
||||
// and reload the list
|
||||
modelslist.load();
|
||||
modelCellManager.load();
|
||||
|
||||
// Current model filename is empty...
|
||||
// Let's fix it!
|
||||
|
||||
@@ -410,7 +410,9 @@ void getModelNumberStr(uint8_t idx, char* model_idx)
|
||||
const char * writeModel()
|
||||
{
|
||||
#if defined(STORAGE_MODELSLIST)
|
||||
return writeModelYaml(g_eeGeneral.currModelFilename);
|
||||
auto rv = writeModelYaml(g_eeGeneral.currModelFilename);
|
||||
modelCellManager.getCurrentModel()->updateFinfoHash();
|
||||
return rv;
|
||||
#else
|
||||
char fname[MODELIDX_STRLEN + sizeof(YAML_EXT)];
|
||||
getModelNumberStr(g_eeGeneral.currModel, fname);
|
||||
|
||||
@@ -23,11 +23,9 @@
|
||||
#include "yaml_parser.h"
|
||||
|
||||
#include "debug.h"
|
||||
|
||||
#include "strhelpers.h"
|
||||
#include "storage/modelslist.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#define strcasecmp _stricmp
|
||||
#define strncasecmp _strnicmp
|
||||
@@ -39,23 +37,21 @@
|
||||
#define TRACE_LABELS_YAML(...)
|
||||
#endif
|
||||
|
||||
using std::list;
|
||||
|
||||
struct labelslist_iter
|
||||
{
|
||||
enum SectionType {
|
||||
SEC_Unknown=0,
|
||||
SEC_Labels=1,
|
||||
SEC_Models=2,
|
||||
SEC_Sort=3,
|
||||
};
|
||||
enum SectionType {
|
||||
SEC_Unknown=0,
|
||||
SEC_Labels=1,
|
||||
SEC_Models=2,
|
||||
SEC_Sort=3,
|
||||
};
|
||||
|
||||
ModelCell *curmodel;
|
||||
bool modeldatavalid; // Used to determine if reading yaml values is necessary
|
||||
uint8_t level;
|
||||
uint8_t section;
|
||||
char current_attr[LABELS_LENGTH+1]; // set after find_node()
|
||||
char current_label[LABELS_LENGTH+1]; // set after find_node()
|
||||
ModelCell *curmodel;
|
||||
bool modeldatavalid; // Used to determine if reading yaml values is necessary
|
||||
uint8_t level;
|
||||
uint8_t section;
|
||||
char current_attr[LABELS_LENGTH+1]; // set after find_node()
|
||||
char current_label[LABELS_LENGTH+1]; // set after find_node()
|
||||
};
|
||||
|
||||
static labelslist_iter __labelslist_iter_inst;
|
||||
@@ -132,28 +128,11 @@ static bool find_node(void* ctx, const char* buf, uint8_t len)
|
||||
|
||||
// Model List
|
||||
if(mi->level == 1 && mi->section == labelslist_iter::SEC_Models) {
|
||||
bool found=false;
|
||||
for(auto &filehash : modelslist.fileHashInfo) {
|
||||
if(filehash.name == mi->current_attr) {
|
||||
TRACE_LABELS_YAML(" Model %s has a real file, creating a modelcell", mi->current_attr);
|
||||
if(filehash.celladded) {
|
||||
TRACE_LABELS_YAML(" Duplicate found labels.yml model cell %s already added", mi->current_attr);
|
||||
break;
|
||||
}
|
||||
ModelCell *model = new ModelCell(mi->current_attr);
|
||||
strcpy(model->modelFinfoHash, filehash.hash);
|
||||
modelslist.push_back(model);
|
||||
filehash.celladded = true;
|
||||
if(filehash.curmodel == true)
|
||||
modelslist.setCurrentModel(model);
|
||||
mi->curmodel = model;
|
||||
mi->modeldatavalid = false;
|
||||
mi->curmodel->_isDirty = true;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found) {
|
||||
auto model = modelCellManager.getModel(mi->current_attr);
|
||||
if (model) {
|
||||
mi->curmodel = model;
|
||||
mi->modeldatavalid = false;
|
||||
} else {
|
||||
mi->curmodel = NULL;
|
||||
TRACE_LABELS_YAML("File does not exist in /MODELS");
|
||||
}
|
||||
@@ -162,9 +141,8 @@ static bool find_node(void* ctx, const char* buf, uint8_t len)
|
||||
// Labels List
|
||||
if(mi->level == 1 && mi->section == labelslist_iter::SEC_Labels) {
|
||||
TRACE_LABELS_YAML("Label Found -- %s", mi->current_attr);
|
||||
modelslabels.addLabel(mi->current_attr);
|
||||
strncpy(mi->current_label,mi->current_attr, LABELS_LENGTH);
|
||||
mi->current_label[LABELS_LENGTH] = '\0';
|
||||
modelCellManager.addLabel(mi->current_attr);
|
||||
strAppend(mi->current_label, mi->current_attr, LABELS_LENGTH);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -194,14 +172,10 @@ static void set_attr(void* ctx, const char* buf, uint16_t len)
|
||||
mi->curmodel->_isDirty = false;
|
||||
} else {
|
||||
TRACE_LABELS_YAML("FILE HASH Does not Match, Open model and rebuild modelcell");
|
||||
mi->modeldatavalid = false;
|
||||
mi->curmodel->_isDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Last Opened
|
||||
// Hack: Always load this as the hash value is (currently) not in synch with the saved model file
|
||||
// TODO: Ensure stored hash is up to date.
|
||||
if (!strcasecmp(mi->current_attr, "lastopen")) {
|
||||
mi->curmodel->lastOpened = (gtime_t)strtol(value, NULL, 0);
|
||||
TRACE_LABELS_YAML(" Last Opened %lu", value);
|
||||
@@ -223,11 +197,7 @@ static void set_attr(void* ctx, const char* buf, uint16_t len)
|
||||
|
||||
// Model Labels
|
||||
} else if(!strcasecmp(mi->current_attr, "labels")) {
|
||||
LabelsVector labels = ModelMap::fromCSV(value);
|
||||
for(const auto &lbl : labels ) {
|
||||
modelslabels.addLabelToModel(lbl,mi->curmodel);
|
||||
TRACE_LABELS_YAML(" Adding the label - %s", lbl.c_str());
|
||||
}
|
||||
modelCellManager.addLabelsToModel(value, mi->curmodel);
|
||||
|
||||
// RF Module Data
|
||||
} else {
|
||||
@@ -257,18 +227,15 @@ static void set_attr(void* ctx, const char* buf, uint16_t len)
|
||||
|
||||
// Label Section
|
||||
} else if(mi->level == 2 && mi->section == labelslist_iter::SEC_Labels) {
|
||||
if(!strcasecmp(mi->current_attr, "icon")) {
|
||||
TRACE_LABELS_YAML(" Label Icon - %s", value);
|
||||
// TODO - Check icon exists, or ignore it.
|
||||
} else if(!strcasecmp(mi->current_attr, "selected")) {
|
||||
if(!strcasecmp(mi->current_attr, "selected")) {
|
||||
TRACE("FOUND %s Label is selected", mi->current_label);
|
||||
modelslabels.addFilteredLabel(mi->current_label);
|
||||
modelCellManager.addFilteredLabel(mi->current_label);
|
||||
}
|
||||
|
||||
// Sort Order
|
||||
} else if (mi->level == 0 && mi->section == labelslist_iter::SEC_Sort) {
|
||||
TRACE_LABELS_YAML(" Sort Order Found -- %s", value);
|
||||
modelslabels.setSortOrder((ModelsSortBy)strtol(value,NULL,10));
|
||||
modelCellManager.setSortOrder((ModelsSortBy)strtol(value,NULL,10));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ TEST(PartialModel, HeaderParsesIdenticallyToModelData)
|
||||
|
||||
// Behavioural contract for ModelHeader::labels: it holds the CSV-joined
|
||||
// list of *every* label attached to a model (declared as
|
||||
// char[LABELS_LENGTH], currently 100 bytes) -- as distinct from the length
|
||||
// char[LABELS_LENGTH+1], currently 100 bytes) -- as distinct from the length
|
||||
// of one individual label name (LABEL_LENGTH, 16 bytes). A model with
|
||||
// several labels attached should keep all of them when this field gets
|
||||
// (re)written, up to the field's own declared capacity, not just as much
|
||||
@@ -156,16 +156,16 @@ TEST(PartialModel, LabelsFieldRetainsFullCsvUpToItsOwnCapacity)
|
||||
ASSERT_GT(strlen(csv), (size_t)LABEL_LENGTH);
|
||||
ASSERT_LT(strlen(csv), (size_t)LABELS_LENGTH);
|
||||
|
||||
strAppend(partial.header.labels, csv, LABELS_LENGTH - 1);
|
||||
strAppend(partial.header.labels, csv, LABELS_LENGTH);
|
||||
|
||||
EXPECT_STREQ(csv, partial.header.labels)
|
||||
<< "header.labels should retain the full label list up to "
|
||||
"LABELS_LENGTH-1 characters, not just a single label name's "
|
||||
"LABELS_LENGTH characters, not just a single label name's "
|
||||
"worth.";
|
||||
}
|
||||
#endif // defined(STORAGE_MODELSLIST)
|
||||
|
||||
// Real FatFS round-trip tests for ModelMap::writeModelLabels() itself -
|
||||
// Real FatFS round-trip tests for ModelCell::writeModelLabels() itself -
|
||||
// the raw file-surgery function these PartialModel tests don't exercise.
|
||||
#if defined(SIMU) && defined(STORAGE_MODELSLIST)
|
||||
|
||||
@@ -198,8 +198,6 @@ static size_t modelFileSize(const char* modelFilename)
|
||||
// model file.
|
||||
TEST(ModelsList, WriteModelLabelsPreservesBodySize)
|
||||
{
|
||||
ModelMap map;
|
||||
|
||||
const char* fileA = "wml_test_a.yml";
|
||||
const char* fileB = "wml_test_b.yml";
|
||||
|
||||
@@ -219,8 +217,8 @@ TEST(ModelsList, WriteModelLabelsPreservesBodySize)
|
||||
ModelCell cellA(fileA);
|
||||
ModelCell cellB(fileB);
|
||||
|
||||
EXPECT_TRUE(map.writeModelLabels(&cellA, "NewLabel"));
|
||||
EXPECT_TRUE(map.writeModelLabels(&cellB, "NewLabel"));
|
||||
EXPECT_TRUE(cellA.writeModelLabels("NewLabel"));
|
||||
EXPECT_TRUE(cellB.writeModelLabels("NewLabel"));
|
||||
|
||||
size_t sizeA = modelFileSize(fileA);
|
||||
size_t sizeB = modelFileSize(fileB);
|
||||
@@ -235,13 +233,12 @@ TEST(ModelsList, WriteModelLabelsPreservesBodySize)
|
||||
|
||||
TEST(ModelsList, WriteModelLabelsFailsCleanlyWithoutHeader)
|
||||
{
|
||||
ModelMap map;
|
||||
const char* file = "wml_test_noheader.yml";
|
||||
|
||||
writeRawModelFile(file, "notheader:\n foo: bar\n");
|
||||
ModelCell cell(file);
|
||||
|
||||
EXPECT_FALSE(map.writeModelLabels(&cell, "NewLabel"));
|
||||
EXPECT_FALSE(cell.writeModelLabels("NewLabel"));
|
||||
|
||||
std::filesystem::remove(simuFatfsGetRealPath(std::string(MODELS_PATH) + "/" + file));
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
//
|
||||
// Label management (renaming/adding/removing labels) routinely touches
|
||||
// models other than the currently loaded one. These tests exercise that
|
||||
// label-management path through its public ModelMap API and assert the
|
||||
// label-management path through its public modelCellManager API and assert the
|
||||
// currently loaded model's screen/topbar data is left alone, regardless of
|
||||
// what label edits are made to other models on disk.
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
class ModelMapFsTest : public ::testing::Test
|
||||
class ModelLabelsFsTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
fs::path scratchDir;
|
||||
@@ -56,7 +56,7 @@ class ModelMapFsTest : public ::testing::Test
|
||||
void SetUp() override
|
||||
{
|
||||
scratchDir = fs::temp_directory_path() /
|
||||
fs::path("edgetx-gtest-modelslist-labels");
|
||||
fs::path("edgetx-gtest-modelCellManager-labels");
|
||||
std::error_code ec;
|
||||
fs::remove_all(scratchDir, ec);
|
||||
fs::create_directories(scratchDir / "MODELS", ec);
|
||||
@@ -64,15 +64,13 @@ class ModelMapFsTest : public ::testing::Test
|
||||
|
||||
simuFatfsSetPaths(scratchDir.string().c_str(), nullptr);
|
||||
|
||||
modelslist.clear();
|
||||
modelslabels.clear();
|
||||
modelCellManager.clear();
|
||||
memclear(&g_model, sizeof(g_model));
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
modelslist.clear();
|
||||
modelslabels.clear();
|
||||
modelCellManager.clear();
|
||||
|
||||
simuFatfsSetPaths(TESTS_PATH, nullptr);
|
||||
|
||||
@@ -100,7 +98,7 @@ class ModelMapFsTest : public ::testing::Test
|
||||
// a "header:" section at all. Giving it a name keeps the fixture
|
||||
// realistic and avoids that unrelated edge case.
|
||||
copyToUnTerminated(model.header.name, filename);
|
||||
strAppend(model.header.labels, labels, LABELS_LENGTH - 1);
|
||||
strAppend(model.header.labels, labels, LABELS_LENGTH);
|
||||
model.getScreenData(0)->LayoutId = screenLayoutId;
|
||||
model.getTopbarData()->zones[0].widgetName = widgetName;
|
||||
|
||||
@@ -112,25 +110,25 @@ class ModelMapFsTest : public ::testing::Test
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ModelMapFsTest, RenamingLabelOnOtherModelLeavesActiveScreenDataUntouched)
|
||||
TEST_F(ModelLabelsFsTest, RenamingLabelOnOtherModelLeavesActiveScreenDataUntouched)
|
||||
{
|
||||
// Seed a second, non-active model on disk with a label and screen/topbar
|
||||
// data distinct from the active model's.
|
||||
writeFixtureModel("model0002.yml", "Foo", "OtherLayout", "OtherWidget");
|
||||
ModelCell* other = modelslist.addModel("model0002.yml", false);
|
||||
ModelCell* other = modelCellManager.addModel("model0002.yml", false);
|
||||
ASSERT_NE(other, nullptr);
|
||||
ASSERT_FALSE(modelslabels.addLabelToModel("Foo", other, false));
|
||||
ASSERT_FALSE(modelCellManager.addLabelToModel("Foo", other, false));
|
||||
|
||||
// Now "load" the active model: register its cell and mark it current, and
|
||||
// set its screen/topbar data to its own values.
|
||||
ModelCell* active = modelslist.addModel("model0001.yml", false);
|
||||
ModelCell* active = modelCellManager.addModel("model0001.yml", false);
|
||||
ASSERT_NE(active, nullptr);
|
||||
modelslist.setCurrentModel(active);
|
||||
modelCellManager.setCurrentModel(active);
|
||||
g_model.getScreenData(0)->LayoutId = "ActiveLayout";
|
||||
g_model.getTopbarData()->zones[0].widgetName = "ActiveWidget";
|
||||
|
||||
// Rename a label that only exists on the other, non-active model.
|
||||
modelslabels.renameLabel("Foo", "Bar");
|
||||
modelCellManager.renameLabel("Foo", "Bar");
|
||||
|
||||
// The active model's screen/topbar data must be unaffected by editing an
|
||||
// unrelated model's labels.
|
||||
@@ -147,22 +145,22 @@ TEST_F(ModelMapFsTest, RenamingLabelOnOtherModelLeavesActiveScreenDataUntouched)
|
||||
EXPECT_STREQ(partial.header.labels, "Bar");
|
||||
}
|
||||
|
||||
TEST_F(ModelMapFsTest,
|
||||
TEST_F(ModelLabelsFsTest,
|
||||
AddingLabelToOtherModelWithFileUpdateLeavesActiveScreenDataUntouched)
|
||||
{
|
||||
writeFixtureModel("model0002.yml", "", "OtherLayout", "OtherWidget");
|
||||
ModelCell* other = modelslist.addModel("model0002.yml", false);
|
||||
ModelCell* other = modelCellManager.addModel("model0002.yml", false);
|
||||
ASSERT_NE(other, nullptr);
|
||||
|
||||
ModelCell* active = modelslist.addModel("model0001.yml", false);
|
||||
ModelCell* active = modelCellManager.addModel("model0001.yml", false);
|
||||
ASSERT_NE(active, nullptr);
|
||||
modelslist.setCurrentModel(active);
|
||||
modelCellManager.setCurrentModel(active);
|
||||
g_model.getScreenData(0)->LayoutId = "ActiveLayout";
|
||||
g_model.getTopbarData()->zones[0].widgetName = "ActiveWidget";
|
||||
|
||||
// update=true drives ModelMap::updateModelFile(), the second call site
|
||||
// update=true drives ModelCell::updateModelFile(), the second call site
|
||||
// that reads/writes a non-active model's file on disk.
|
||||
EXPECT_FALSE(modelslabels.addLabelToModel("Baz", other, true));
|
||||
EXPECT_FALSE(modelCellManager.addLabelToModel("Baz", other, true));
|
||||
|
||||
EXPECT_STREQ(g_model.getScreenData(0)->LayoutId.c_str(), "ActiveLayout");
|
||||
EXPECT_STREQ(g_model.getTopbarData()->zones[0].widgetName.c_str(),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user