feat(color): Use generic YAML parser to save/load Theme yaml files (#3346)
* Change ThemeFile class to use generic YAML parser instead of hard wired code. Fix handling of empty input lines in YAML parser. Increase size of YAML parser variables to handle theme 'info' property length. * Update comment. * Fix error. * Proper fix for blank lines in YAML file. * Fix YAML blank line handling (attempt 3). * Ensure theme YAML file is backward compatible. * Store selected theme in radio settings instead of selectedtheme.txt file. On start, if radio settings theme is empty, try and load from selectedtheme.txt file. In Companion, save/load the selected theme in the radio settings YAML file; but no UI to select theme. * Update selected theme name in radio.yml if the theme name was edited. * Only save 'selectedTheme' in Companion on color LCD radios.
This commit is contained in:
@@ -148,6 +148,8 @@ Node convert<GeneralSettings>::encode(const GeneralSettings& rhs)
|
||||
|
||||
auto fw = getCurrentFirmware();
|
||||
|
||||
bool hasColorLcd = Boards::getCapability(fw->getBoard(), Board::HasColorLcd);
|
||||
|
||||
node["semver"] = VERSION;
|
||||
|
||||
std::string board = fw->getFlavour().toStdString();
|
||||
@@ -281,6 +283,9 @@ Node convert<GeneralSettings>::encode(const GeneralSettings& rhs)
|
||||
// OneBit sampling (X9D only?)
|
||||
node["uartSampleMode"] = rhs.uartSampleMode;
|
||||
|
||||
if (hasColorLcd)
|
||||
node["selectedTheme"] = rhs.selectedTheme;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
@@ -496,6 +501,8 @@ bool convert<GeneralSettings>::decode(const Node& node, GeneralSettings& rhs)
|
||||
if (needsConversion)
|
||||
rhs.init();
|
||||
|
||||
node["selectedTheme"] >> rhs.selectedTheme;
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace YAML
|
||||
|
||||
@@ -101,6 +101,7 @@ constexpr int BLUETOOTH_NAME_LEN {10};
|
||||
constexpr int TTS_LANGUAGE_LEN {2};
|
||||
constexpr int HARDWARE_NAME_LEN {3};
|
||||
constexpr int REGISTRATION_ID_LEN {8};
|
||||
constexpr int SELECTED_THEME_NAME_LEN {26};
|
||||
|
||||
class GeneralSettings {
|
||||
Q_DECLARE_TR_FUNCTIONS(GeneralSettings)
|
||||
@@ -283,6 +284,8 @@ class GeneralSettings {
|
||||
int pwrOnSpeed;
|
||||
int pwrOffSpeed;
|
||||
|
||||
char selectedTheme[SELECTED_THEME_NAME_LEN + 1];
|
||||
|
||||
bool switchPositionAllowedTaranis(int index) const;
|
||||
bool switchSourceAllowedTaranis(int index) const;
|
||||
bool isPotAvailable(int index) const;
|
||||
|
||||
@@ -934,4 +934,6 @@ enum ModelOverridableEnable {
|
||||
OVERRIDE_ON
|
||||
};
|
||||
|
||||
#define SELECTED_THEME_NAME_LEN 26
|
||||
|
||||
#endif // _DATACONSTANTS_H_
|
||||
|
||||
@@ -139,14 +139,14 @@ static inline void check_struct()
|
||||
CHKSIZE(ModelData, 6661);
|
||||
#elif defined(PCBHORUS)
|
||||
#if defined(PCBX10)
|
||||
CHKSIZE(RadioData, 924);
|
||||
CHKSIZE(RadioData, 950);
|
||||
CHKSIZE(ModelData, 15431);
|
||||
#else
|
||||
CHKSIZE(RadioData, 906);
|
||||
CHKSIZE(RadioData, 932);
|
||||
CHKSIZE(ModelData, 15429);
|
||||
#endif
|
||||
#elif defined(PCBNV14)
|
||||
CHKSIZE(RadioData, 852);
|
||||
CHKSIZE(RadioData, 878);
|
||||
CHKSIZE(ModelData, 15245);
|
||||
#endif
|
||||
|
||||
|
||||
@@ -948,6 +948,10 @@ PACK(struct RadioData {
|
||||
NOBACKUP(int8_t imuMax);
|
||||
NOBACKUP(int8_t imuOffset);
|
||||
#endif
|
||||
|
||||
#if defined(COLORLCD)
|
||||
NOBACKUP(char selectedTheme[SELECTED_THEME_NAME_LEN]);
|
||||
#endif
|
||||
});
|
||||
|
||||
#undef SWITCHES_WARNING_DATA
|
||||
|
||||
@@ -143,7 +143,7 @@ class ThemeDetailsDialog: public Dialog
|
||||
auto line = form->newLine(&grid);
|
||||
|
||||
new StaticText(line, rect_t{}, STR_NAME, 0, COLOR_THEME_PRIMARY1);
|
||||
auto te = new TextEdit(line, rect_t{}, this->theme.getName(), NAME_LENGTH);
|
||||
auto te = new TextEdit(line, rect_t{}, this->theme.getName(), SELECTED_THEME_NAME_LEN);
|
||||
lv_obj_set_grid_cell(te->getLvObj(), LV_GRID_ALIGN_STRETCH, 1, 1, LV_GRID_ALIGN_CENTER, 0, 1);
|
||||
|
||||
line = form->newLine(&grid);
|
||||
@@ -540,7 +540,7 @@ void ThemeSetupPage::displayThemeMenu(Window *window, ThemePersistance *tp)
|
||||
*theme = editedTheme;
|
||||
|
||||
theme->serialize();
|
||||
|
||||
|
||||
// if the theme info currently displayed
|
||||
// were changed, update the UI
|
||||
if (themeIdx == currentTheme) {
|
||||
@@ -552,6 +552,8 @@ void ThemeSetupPage::displayThemeMenu(Window *window, ThemePersistance *tp)
|
||||
|
||||
// if the active theme changed, re-apply it
|
||||
if (themeIdx == tp->getThemeIndex()) {
|
||||
// Update saved them name in case it was changed.
|
||||
tp->setDefaultTheme(themeIdx);
|
||||
theme->applyTheme();
|
||||
TabsGroup::refreshTheme();
|
||||
}
|
||||
@@ -564,8 +566,8 @@ void ThemeSetupPage::displayThemeMenu(Window *window, ThemePersistance *tp)
|
||||
|
||||
new ThemeDetailsDialog(window, newTheme, [=](ThemeFile theme) {
|
||||
if (strlen(theme.getName()) != 0) {
|
||||
char name[NAME_LENGTH + 20];
|
||||
strncpy(name, theme.getName(), NAME_LENGTH + 19);
|
||||
char name[SELECTED_THEME_NAME_LEN + 20];
|
||||
strncpy(name, theme.getName(), SELECTED_THEME_NAME_LEN + 19);
|
||||
removeAllWhiteSpace(name);
|
||||
|
||||
// use the selected themes color list to make the new theme
|
||||
|
||||
@@ -21,27 +21,125 @@
|
||||
#include "theme_manager.h"
|
||||
#include "view_main.h"
|
||||
|
||||
#include "../../storage/yaml/yaml_tree_walker.h"
|
||||
#include "../../storage/yaml/yaml_bits.h"
|
||||
#include "../../storage/sdcard_common.h"
|
||||
|
||||
#define SET_DIRTY() storageDirty(EE_GENERAL)
|
||||
|
||||
#define MAX_FILES 9
|
||||
|
||||
constexpr const char *RGBSTRING = "RGB(";
|
||||
|
||||
ThemePersistance themePersistance;
|
||||
|
||||
// prototype for SD card function to read a YAML file
|
||||
// TODO: should this be in sdcard header file?
|
||||
enum class ChecksumResult;
|
||||
extern const char* readYamlFile(const char* fullpath, const YamlParserCalls* calls, void* parser_ctx, ChecksumResult* checksum_result);
|
||||
|
||||
static uint32_t r_color(const YamlNode* node, const char* val, uint8_t val_len)
|
||||
{
|
||||
if ((strncmp(val, RGBSTRING, strlen(RGBSTRING)) == 0) && (val[val_len - 1] == ')')) {
|
||||
int r, g, b;
|
||||
int numTokens = sscanf(val, "RGB(%d,%d,%d)", &r, &g, &b);
|
||||
|
||||
if (numTokens == 3)
|
||||
return RGB(r, g, b);
|
||||
|
||||
} else if (val_len > 2 && val[0] == '0' && (val[1] == 'x' || val[1] == 'X')) {
|
||||
val += 2; val_len -= 2;
|
||||
|
||||
auto rgb24 = yaml_hex2uint(val, val_len);
|
||||
return RGB((rgb24 & 0xFF0000) >> 16, (rgb24 & 0xFF00) >> 8, rgb24 & 0xFF);
|
||||
}
|
||||
|
||||
TRACE("Theme: Invalid color value");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool w_color(const YamlNode* node, uint32_t val, yaml_writer_func wf,
|
||||
void* opaque)
|
||||
{
|
||||
uint32_t color = (uint32_t)GET_RED(val) << 16 |
|
||||
(uint32_t)GET_GREEN(val) << 8 |
|
||||
(uint32_t)GET_BLUE(val);
|
||||
|
||||
if (!wf(opaque, "0x", 2)) return false;
|
||||
return wf(opaque, yaml_rgb2hex(color), 3 * 2);
|
||||
}
|
||||
|
||||
PACK(struct YAMLThemeSummary
|
||||
{
|
||||
char name[SELECTED_THEME_NAME_LEN + 1];
|
||||
char author[AUTHOR_LENGTH + 1];
|
||||
char info[INFO_LENGTH+1];
|
||||
});
|
||||
|
||||
PACK(struct YAMLThemeColors
|
||||
{
|
||||
uint32_t colors[COLOR_COUNT - 2]; // We don't use DEFAULT or CUSTOM
|
||||
});
|
||||
|
||||
PACK(struct YAMLTheme
|
||||
{
|
||||
struct YAMLThemeSummary summary;
|
||||
struct YAMLThemeColors colors;
|
||||
|
||||
YAMLTheme() {
|
||||
memset(this, 0, sizeof(YAMLTheme));
|
||||
}
|
||||
});
|
||||
|
||||
static const struct YamlNode struct_YAMLThemeSummary[] = {
|
||||
YAML_STRING("name", (SELECTED_THEME_NAME_LEN + 1)),
|
||||
YAML_STRING("author", (AUTHOR_LENGTH + 1)),
|
||||
YAML_STRING("info", (INFO_LENGTH + 1)),
|
||||
YAML_END
|
||||
};
|
||||
|
||||
static const struct YamlNode struct_YAMLThemeColors[] = {
|
||||
YAML_UNSIGNED_CUST( "PRIMARY1", 32, r_color, w_color ),
|
||||
YAML_UNSIGNED_CUST( "PRIMARY2", 32, r_color, w_color ),
|
||||
YAML_UNSIGNED_CUST( "PRIMARY3", 32, r_color, w_color ),
|
||||
YAML_UNSIGNED_CUST( "SECONDARY1", 32, r_color, w_color ),
|
||||
YAML_UNSIGNED_CUST( "SECONDARY2", 32, r_color, w_color ),
|
||||
YAML_UNSIGNED_CUST( "SECONDARY3", 32, r_color, w_color ),
|
||||
YAML_UNSIGNED_CUST( "FOCUS", 32, r_color, w_color ),
|
||||
YAML_UNSIGNED_CUST( "EDIT", 32, r_color, w_color ),
|
||||
YAML_UNSIGNED_CUST( "ACTIVE", 32, r_color, w_color ),
|
||||
YAML_UNSIGNED_CUST( "WARNING", 32, r_color, w_color ),
|
||||
YAML_UNSIGNED_CUST( "DISABLED", 32, r_color, w_color ),
|
||||
YAML_END
|
||||
};
|
||||
|
||||
// This hack is required to ensure the YAML file written is backward compatible with
|
||||
// the old parser. Otherwise older versions of EdgeTX will not load the theme files.
|
||||
// TODO: Remove this sometime in the future
|
||||
static const struct YamlNode w_struct_YAMLTheme[] = {
|
||||
YAML_STRUCT("---\r\nsummary", (SELECTED_THEME_NAME_LEN + AUTHOR_LENGTH + INFO_LENGTH + 3) * 8, struct_YAMLThemeSummary, NULL),
|
||||
YAML_STRUCT("colors", (COLOR_COUNT - 2) * 32, struct_YAMLThemeColors, NULL),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode r_struct_YAMLTheme[] = {
|
||||
YAML_STRUCT("summary", (SELECTED_THEME_NAME_LEN + AUTHOR_LENGTH + INFO_LENGTH + 3) * 8, struct_YAMLThemeSummary, NULL),
|
||||
YAML_STRUCT("colors", (COLOR_COUNT - 2) * 32, struct_YAMLThemeColors, NULL),
|
||||
YAML_END
|
||||
};
|
||||
|
||||
static const char *colorNames[COLOR_COUNT] = {
|
||||
"DEFAULT", "PRIMARY1", "PRIMARY2", "PRIMARY3",
|
||||
"SECONDARY1", "SECONDARY2", "SECONDARY3", "FOCUS",
|
||||
"EDIT", "ACTIVE", "WARNING", "DISABLED", "CUSTOM",
|
||||
};
|
||||
|
||||
#define HEX_COLOR_VALUE_LEN 8
|
||||
constexpr const char *RGBSTRING = "RGB(";
|
||||
constexpr const char* SELECTED_THEME_FILE = THEMES_PATH "/selectedtheme.txt";
|
||||
|
||||
ThemeFile::ThemeFile(std::string themePath) :
|
||||
ThemeFile::ThemeFile(std::string themePath, bool loadYAML) :
|
||||
path(themePath)
|
||||
{
|
||||
if (path.size()) {
|
||||
if (loadYAML && path.size()) {
|
||||
deSerialize();
|
||||
}
|
||||
|
||||
FIL file;
|
||||
auto found = path.rfind('/');
|
||||
if (found != std::string::npos) {
|
||||
int n = 0;
|
||||
@@ -64,7 +162,7 @@ ThemeFile& ThemeFile::operator= (const ThemeFile& theme)
|
||||
return *this;
|
||||
|
||||
path = theme.path;
|
||||
strAppend(name, theme.name, NAME_LENGTH);
|
||||
strAppend(name, theme.name, SELECTED_THEME_NAME_LEN);
|
||||
strAppend(author, theme.author, AUTHOR_LENGTH);
|
||||
strAppend(info, theme.info, INFO_LENGTH);
|
||||
colorList.assign(theme.colorList.begin(), theme.colorList.end());
|
||||
@@ -80,195 +178,40 @@ std::vector<std::string> ThemeFile::getThemeImageFileNames()
|
||||
|
||||
void ThemeFile::serialize()
|
||||
{
|
||||
FIL file;
|
||||
FRESULT result = f_open(&file, path.c_str(), FA_CREATE_ALWAYS | FA_WRITE);
|
||||
if (result == FR_OK) {
|
||||
f_printf(&file, "---\n");
|
||||
f_printf(&file, "summary:\n");
|
||||
f_printf(&file, " name: %s\n", name);
|
||||
f_printf(&file, " author: %s\n", author);
|
||||
f_printf(&file, " info: %s\n", info);
|
||||
f_printf(&file, "\n");
|
||||
f_printf(&file, "colors:\n");
|
||||
struct YAMLTheme yt;
|
||||
struct YamlNode themeRootNode = YAML_ROOT(w_struct_YAMLTheme);
|
||||
|
||||
for (auto colorEntry : colorList) {
|
||||
auto r = GET_RED(colorEntry.colorValue);
|
||||
auto g = GET_GREEN(colorEntry.colorValue);
|
||||
auto b = GET_BLUE(colorEntry.colorValue);
|
||||
strAppend(yt.summary.name, name, SELECTED_THEME_NAME_LEN);
|
||||
strAppend(yt.summary.author, author, AUTHOR_LENGTH);
|
||||
strAppend(yt.summary.info, info, INFO_LENGTH);
|
||||
for (auto colorEntry : colorList) {
|
||||
yt.colors.colors[colorEntry.colorNumber-1] = colorEntry.colorValue;
|
||||
}
|
||||
|
||||
std::string colorName(colorNames[colorEntry.colorNumber]);
|
||||
colorName += ":";
|
||||
|
||||
f_printf(&file, " %-11s 0x%02X%02X%02X\n", colorName.c_str(), r,g,b);
|
||||
}
|
||||
f_close(&file);
|
||||
auto err = writeFileYaml(path.c_str(), &themeRootNode, (uint8_t*)&yt, 0);
|
||||
if (err != nullptr) {
|
||||
ALERT(STR_WARNING, err, AU_WARNING1);
|
||||
}
|
||||
}
|
||||
|
||||
void ThemeFile::deSerialize()
|
||||
{
|
||||
char line[256 + 1];
|
||||
ScanState scanState = none;
|
||||
struct YAMLTheme yt;
|
||||
struct YamlNode themeRootNode = YAML_ROOT(r_struct_YAMLTheme);
|
||||
|
||||
FIL file;
|
||||
FRESULT result = f_open(&file, path.c_str(), FA_OPEN_EXISTING | FA_READ);
|
||||
if (result != FR_OK) return;
|
||||
YamlTreeWalker tree;
|
||||
tree.reset(&themeRootNode, (uint8_t*)&yt);
|
||||
auto err = readYamlFile(path.c_str(), YamlTreeWalker::get_parser_calls(), &tree, nullptr);
|
||||
|
||||
int lineNo = 1;
|
||||
while (readNextLine(file, line, 256)) {
|
||||
int len = strlen(line);
|
||||
if (len == 0) continue;
|
||||
|
||||
if (lineNo == 1 && len != 3 && strcmp(line, "---") != 0) {
|
||||
TRACE("invalid yml file at line %d", lineNo);
|
||||
return;
|
||||
} else if (lineNo != 1) {
|
||||
if (line[0] != ' ' && line[0] != '\t') {
|
||||
char *pline = trim(line);
|
||||
if (line[strlen(line) - 1] != ':' && pline[0] != '#') {
|
||||
TRACE("invalid yml file at line %d", lineNo);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(pline, "colors:") == 0) {
|
||||
scanState = colors;
|
||||
continue;
|
||||
} else if (strcmp(pline, "summary:") == 0) {
|
||||
scanState = summary;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const char *ptr = strchr(line, ':');
|
||||
if (!ptr) continue;
|
||||
|
||||
char lvalue[64];
|
||||
char rvalue[64];
|
||||
char *plvalue;
|
||||
char *prvalue;
|
||||
|
||||
strAppend(lvalue, line, min((int)(ptr - line), 63));
|
||||
strAppend(rvalue, ptr + 1, 63);
|
||||
plvalue = trim(lvalue);
|
||||
prvalue = trim(rvalue);
|
||||
|
||||
switch (scanState) {
|
||||
case colors: {
|
||||
int colorIndex = findColorIndex(plvalue);
|
||||
if (colorIndex >= 0) {
|
||||
uint32_t color;
|
||||
if (convertRGB(prvalue, color))
|
||||
colorList.emplace_back(ColorEntry{(LcdColorIndex)colorIndex, color});
|
||||
else
|
||||
TRACE("Theme: Could not convert color value");
|
||||
}
|
||||
} break;
|
||||
|
||||
case summary: {
|
||||
if (strcmp(plvalue, "name") == 0) {
|
||||
strAppend(name, prvalue, NAME_LENGTH);
|
||||
} else if (strcmp(plvalue, "author") == 0) {
|
||||
strAppend(author, prvalue, AUTHOR_LENGTH);
|
||||
} else if (strcmp(plvalue, "info") == 0) {
|
||||
strAppend(info, prvalue, INFO_LENGTH);
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (err == nullptr) {
|
||||
strAppend(name, yt.summary.name, SELECTED_THEME_NAME_LEN);
|
||||
strAppend(author, yt.summary.author, AUTHOR_LENGTH);
|
||||
strAppend(info, yt.summary.info, INFO_LENGTH);
|
||||
for (int i = 0; i < COLOR_COUNT - 2; i += 1) {
|
||||
colorList.emplace_back(ColorEntry{(LcdColorIndex)(i+1), yt.colors.colors[i]});
|
||||
}
|
||||
|
||||
lineNo++;
|
||||
}
|
||||
|
||||
f_close(&file);
|
||||
}
|
||||
|
||||
bool ThemeFile::convertRGB(char *pColorRGB, uint32_t &color)
|
||||
{
|
||||
if (strlen(pColorRGB) < strlen(RGBSTRING)) return false;
|
||||
|
||||
if (strncmp(pColorRGB, RGBSTRING, strlen(RGBSTRING)) == 0) {
|
||||
if (pColorRGB[strlen(pColorRGB) - 1] != ')') return false;
|
||||
|
||||
pColorRGB[strlen(pColorRGB) - 1] = '\0';
|
||||
pColorRGB += strlen(RGBSTRING);
|
||||
if (strlen(pColorRGB)) {
|
||||
char *token = nullptr;
|
||||
int numTokens = 0;
|
||||
uint32_t tokens[3];
|
||||
|
||||
token = strtok(pColorRGB, ",");
|
||||
while (token != nullptr) {
|
||||
if (numTokens < 3) {
|
||||
tokens[numTokens] = strtol(token, nullptr, 0);
|
||||
numTokens++;
|
||||
} else
|
||||
break;
|
||||
|
||||
token = strtok(nullptr, ",");
|
||||
}
|
||||
|
||||
if (numTokens != 3) return false;
|
||||
|
||||
color = RGB(tokens[0], tokens[1], tokens[2]);
|
||||
return true;
|
||||
}
|
||||
} else if (pColorRGB[0] == '0' && (pColorRGB[1] == 'x' || pColorRGB[1] == 'X')) {
|
||||
if (strlen(pColorRGB) != HEX_COLOR_VALUE_LEN) return false;
|
||||
pColorRGB += 2;
|
||||
uint32_t tokens[3];
|
||||
char hexVal[3];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
strncpy(hexVal, pColorRGB, 2);
|
||||
hexVal[2] = '\0';
|
||||
tokens[i] = strtol(hexVal, nullptr, 16);
|
||||
pColorRGB += 2;
|
||||
}
|
||||
|
||||
color = RGB(tokens[0], tokens[1], tokens[2]);
|
||||
return true;
|
||||
}
|
||||
|
||||
TRACE("Theme: Invalid color value");
|
||||
return false;
|
||||
}
|
||||
|
||||
LcdColorIndex ThemeFile::findColorIndex(const char *name)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < COLOR_COUNT; i++) {
|
||||
if (strcmp(name, colorNames[i]) == 0) break;
|
||||
}
|
||||
|
||||
if (i >= COLOR_COUNT) return DEFAULT_COLOR_INDEX;
|
||||
|
||||
return (LcdColorIndex)i;
|
||||
}
|
||||
|
||||
bool ThemeFile::readNextLine(FIL &file, char *line, int maxlen)
|
||||
{
|
||||
if (f_gets(line, maxlen, &file) != NULL) {
|
||||
int curlen = strlen(line) - 1;
|
||||
if (line[curlen] == '\n') { // remove unwanted chars if file was edited using windows
|
||||
if (curlen > 0 && line[curlen - 1] == '\r') {
|
||||
line[curlen - 1] = 0;
|
||||
} else {
|
||||
line[curlen] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ThemeFile::setColorByIndex(int index, uint32_t color)
|
||||
{
|
||||
if (index >= 0 && index < (int) colorList.size()) {
|
||||
colorList[index].colorValue = color;
|
||||
} else {
|
||||
ALERT(STR_WARNING, err, AU_WARNING1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,37 +336,66 @@ void ThemePersistance::loadDefaultTheme()
|
||||
{
|
||||
refresh();
|
||||
|
||||
FIL file;
|
||||
FRESULT status = f_open(&file, SELECTED_THEME_FILE, FA_READ);
|
||||
if (status != FR_OK) return;
|
||||
int index = 0;
|
||||
bool found = false;
|
||||
|
||||
char line[256];
|
||||
unsigned int len;
|
||||
// Load theme from 'selectedtheme.txt' file, if not set in radio settings
|
||||
// TODO: remove this sometime in the future
|
||||
if (g_eeGeneral.selectedTheme[0] == 0) {
|
||||
constexpr const char* SELECTED_THEME_FILE = THEMES_PATH "/selectedtheme.txt";
|
||||
|
||||
status = f_read(&file, line, 256, &len);
|
||||
if (status == FR_OK) {
|
||||
FIL file;
|
||||
FRESULT status = f_open(&file, SELECTED_THEME_FILE, FA_READ);
|
||||
|
||||
line[len] = '\0';
|
||||
if (status == FR_OK) {
|
||||
char line[256];
|
||||
unsigned int len;
|
||||
|
||||
int index = 0;
|
||||
bool found = false;
|
||||
for (auto theme : themes) {
|
||||
if (theme->getPath() == std::string(line)) {
|
||||
found = true;
|
||||
break;
|
||||
status = f_read(&file, line, 256, &len);
|
||||
if (status == FR_OK) {
|
||||
line[len] = '\0';
|
||||
|
||||
for (auto theme : themes) {
|
||||
if (theme->getPath() == std::string(line)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
// Force default if no match for last selected theme
|
||||
if (!found)
|
||||
index = 0;
|
||||
}
|
||||
index++;
|
||||
|
||||
f_close(&file);
|
||||
|
||||
// Delete old file
|
||||
f_unlink(SELECTED_THEME_FILE);
|
||||
}
|
||||
|
||||
// Force default if no match for last selected theme
|
||||
if (!found)
|
||||
index = 0;
|
||||
// Save selected theme (sets to default if nothing found)
|
||||
setDefaultTheme(index);
|
||||
|
||||
applyTheme(index);
|
||||
setThemeIndex(index);
|
||||
// Reset values (used below);
|
||||
index = 0;
|
||||
found = false;
|
||||
}
|
||||
|
||||
f_close(&file);
|
||||
for (auto theme : themes) {
|
||||
if (strncmp(theme->getName(), g_eeGeneral.selectedTheme, SELECTED_THEME_NAME_LEN) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
// Force default if no match for last selected theme
|
||||
if (!found)
|
||||
index = 0;
|
||||
|
||||
applyTheme(index);
|
||||
setThemeIndex(index);
|
||||
}
|
||||
|
||||
char ** ThemePersistance::getColorNames()
|
||||
@@ -472,23 +444,17 @@ bool ThemePersistance::createNewTheme(std::string name, ThemeFile &theme)
|
||||
|
||||
void ThemePersistance::setDefaultTheme(int index)
|
||||
{
|
||||
FIL file;
|
||||
if (index >= 0 && index < (int) themes.size()) {
|
||||
auto theme = themes[index];
|
||||
FRESULT status = f_open(&file, SELECTED_THEME_FILE, FA_CREATE_ALWAYS | FA_WRITE);
|
||||
if (status != FR_OK) return;
|
||||
|
||||
strAppend(g_eeGeneral.selectedTheme, themes[index]->getName(), SELECTED_THEME_NAME_LEN);
|
||||
SET_DIRTY();
|
||||
currentTheme = index;
|
||||
if (index > 0)
|
||||
f_printf(&file, theme->getPath().c_str());
|
||||
f_close(&file);
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultEdgeTxTheme : public ThemeFile
|
||||
{
|
||||
public:
|
||||
DefaultEdgeTxTheme() : ThemeFile(THEMES_PATH"/EdgeTX/")
|
||||
DefaultEdgeTxTheme() : ThemeFile(THEMES_PATH"/EdgeTX/", false)
|
||||
{
|
||||
setName("EdgeTX Default");
|
||||
setAuthor("EdgeTX Team");
|
||||
@@ -514,4 +480,3 @@ void ThemePersistance::insertDefaultTheme()
|
||||
auto themeFile = new DefaultEdgeTxTheme();
|
||||
themes.insert(themes.begin(), themeFile);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,11 +29,14 @@
|
||||
#include "sdcard.h"
|
||||
#include "str_functions.h"
|
||||
|
||||
#define COLOR_COUNT 13
|
||||
|
||||
#define AUTHOR_LENGTH 50
|
||||
#define INFO_LENGTH 255
|
||||
|
||||
class ThemePersistance;
|
||||
extern ThemePersistance themePersistance;
|
||||
|
||||
#define COLOR_COUNT 13
|
||||
|
||||
struct ColorEntry
|
||||
{
|
||||
LcdColorIndex colorNumber;
|
||||
@@ -42,10 +45,6 @@ struct ColorEntry
|
||||
bool operator== (const ColorEntry &a) { return this->colorNumber == a.colorNumber; }
|
||||
};
|
||||
|
||||
|
||||
#define AUTHOR_LENGTH 50
|
||||
#define INFO_LENGTH 255
|
||||
#define NAME_LENGTH 26
|
||||
class ThemeFile
|
||||
{
|
||||
public:
|
||||
@@ -55,16 +54,8 @@ class ThemeFile
|
||||
info[0] = '\0';
|
||||
}
|
||||
|
||||
ThemeFile(std::string themePath);
|
||||
ThemeFile(const ThemeFile &theme) :
|
||||
colorList(theme.colorList),
|
||||
_imageFileNames(theme._imageFileNames)
|
||||
{
|
||||
path = theme.path;
|
||||
strAppend(name, theme.name, NAME_LENGTH);
|
||||
strAppend(author, theme.author, AUTHOR_LENGTH);
|
||||
strAppend(info, theme.info, INFO_LENGTH);
|
||||
}
|
||||
ThemeFile(std::string themePath, bool loadYAML = true);
|
||||
|
||||
virtual ~ThemeFile() {}
|
||||
|
||||
ThemeFile& operator= (const ThemeFile& theme);
|
||||
@@ -86,33 +77,21 @@ class ThemeFile
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint32_t getColorByName(std::string colorName) {
|
||||
|
||||
auto colorIndex = findColorIndex(colorName.c_str());
|
||||
ColorEntry a = {colorIndex, 0};
|
||||
auto colorEntry = std::find(colorList.begin(), colorList.end(), a);
|
||||
if (colorEntry != colorList.end())
|
||||
return colorEntry->colorValue;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void setName(std::string name) { strAppend(this->name, name.c_str(), NAME_LENGTH); }
|
||||
void setName(std::string name) { strAppend(this->name, name.c_str(), SELECTED_THEME_NAME_LEN); }
|
||||
void setAuthor(std::string author) { strAppend(this->author, author.c_str(), AUTHOR_LENGTH); }
|
||||
void setInfo(std::string info) { strAppend(this->info, info.c_str(), INFO_LENGTH); }
|
||||
void setPath(std::string path) { this->path = path; }
|
||||
|
||||
std::vector<ColorEntry>& getColorList() { return colorList; }
|
||||
void setColor(LcdColorIndex colorIndex, uint32_t color);
|
||||
void setColorByIndex(int index, uint32_t color);
|
||||
|
||||
virtual std::vector<std::string> getThemeImageFileNames();
|
||||
void applyTheme();
|
||||
|
||||
protected:
|
||||
std::string path;
|
||||
char name[NAME_LENGTH + 1];
|
||||
char name[SELECTED_THEME_NAME_LEN + 1];
|
||||
char author[AUTHOR_LENGTH + 1];
|
||||
char info[INFO_LENGTH + 1];
|
||||
std::vector<ColorEntry> colorList;
|
||||
@@ -120,17 +99,7 @@ class ThemeFile
|
||||
void applyColors();
|
||||
virtual void applyBackground();
|
||||
|
||||
enum ScanState
|
||||
{
|
||||
none,
|
||||
summary,
|
||||
colors
|
||||
};
|
||||
|
||||
void deSerialize();
|
||||
bool convertRGB(char *pColorRGB, uint32_t &color);
|
||||
LcdColorIndex findColorIndex(const char *name);
|
||||
bool readNextLine(FIL &file, char *line, int maxlen);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -458,6 +458,7 @@ static const struct YamlNode struct_RadioData[] = {
|
||||
YAML_SIGNED( "uartSampleMode", 2 ),
|
||||
YAML_UNSIGNED( "stickDeadZone", 3 ),
|
||||
YAML_PADDING( 1 ),
|
||||
YAML_STRING("selectedTheme", 26),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_unsigned_8[] = {
|
||||
|
||||
@@ -489,6 +489,7 @@ static const struct YamlNode struct_RadioData[] = {
|
||||
YAML_PADDING( 1 ),
|
||||
YAML_SIGNED( "imuMax", 8 ),
|
||||
YAML_SIGNED( "imuOffset", 8 ),
|
||||
YAML_STRING("selectedTheme", 26),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_unsigned_8[] = {
|
||||
|
||||
@@ -487,6 +487,7 @@ static const struct YamlNode struct_RadioData[] = {
|
||||
YAML_PADDING( 1 ),
|
||||
YAML_SIGNED( "imuMax", 8 ),
|
||||
YAML_SIGNED( "imuOffset", 8 ),
|
||||
YAML_STRING("selectedTheme", 26),
|
||||
YAML_END
|
||||
};
|
||||
static const struct YamlNode struct_unsigned_8[] = {
|
||||
|
||||
@@ -165,7 +165,7 @@ static bool find_node(void* ctx, char* buf, uint8_t len)
|
||||
return true;
|
||||
}
|
||||
|
||||
static void set_attr(void* ctx, char* buf, uint8_t len)
|
||||
static void set_attr(void* ctx, char* buf, uint16_t len)
|
||||
{
|
||||
char value[LABELS_LENGTH + 1];
|
||||
if(len > LABELS_LENGTH) {
|
||||
|
||||
@@ -95,7 +95,7 @@ static bool find_node(void* ctx, char* buf, uint8_t len)
|
||||
return true;
|
||||
}
|
||||
|
||||
static void set_attr(void* ctx, char* buf, uint8_t len)
|
||||
static void set_attr(void* ctx, char* buf, uint16_t len)
|
||||
{
|
||||
char fnamebuf[LEN_MODEL_FILENAME + 1];
|
||||
modelslist_iter* mi = (modelslist_iter*)ctx;
|
||||
|
||||
@@ -115,7 +115,7 @@ YamlParser::parse(const char* buffer, unsigned int size)
|
||||
state = ps_CRLF;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (indent < getLastIndent()) {
|
||||
// go up as many levels as necessary
|
||||
do {
|
||||
@@ -303,8 +303,12 @@ YamlParser::parse(const char* buffer, unsigned int size)
|
||||
|
||||
case ps_CRLF:
|
||||
if (*c == '\n') {
|
||||
// Skip blank lines
|
||||
while (c < end && (*c == '\r' || *c == '\n'))
|
||||
c += 1;
|
||||
// reset state at EOL
|
||||
reset();
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define MAX_STR 100
|
||||
#define MAX_STR 256
|
||||
#define MAX_DEPTH 16 // 12 real + 4 virtual
|
||||
|
||||
struct YamlParserCalls
|
||||
@@ -33,7 +33,7 @@ struct YamlParserCalls
|
||||
bool (*to_child) (void* ctx);
|
||||
bool (*to_next_elmt) (void* ctx);
|
||||
bool (*find_node) (void* ctx, char* buf, uint8_t len);
|
||||
void (*set_attr) (void* ctx, char* buf, uint8_t len);
|
||||
void (*set_attr) (void* ctx, char* buf, uint16_t len);
|
||||
};
|
||||
|
||||
class YamlParser
|
||||
@@ -70,7 +70,7 @@ class YamlParser
|
||||
// scratch buffer w/ 16 bytes
|
||||
// used for attribute and values
|
||||
char scratch_buf[MAX_STR];
|
||||
uint8_t scratch_len;
|
||||
uint16_t scratch_len;
|
||||
|
||||
bool node_found;
|
||||
bool eof;
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
#define MIN(a,b) (a < b ? a : b)
|
||||
|
||||
static void copy_string(char* dst, uint8_t dst_len, const char* src,
|
||||
static void copy_string(char* dst, uint16_t dst_len, const char* src,
|
||||
uint8_t src_len)
|
||||
{
|
||||
if (src_len < dst_len) {
|
||||
@@ -57,7 +57,7 @@ uint32_t yaml_parse_enum(const struct YamlIdStr* choices, const char* val, uint8
|
||||
|
||||
static void yaml_set_attr(void* user, uint8_t* ptr, uint32_t bit_ofs,
|
||||
const YamlNode* node, const char* val,
|
||||
uint8_t val_len)
|
||||
uint16_t val_len)
|
||||
{
|
||||
uint32_t i = 0;
|
||||
|
||||
@@ -265,7 +265,7 @@ bool YamlTreeWalker::findNode(const char* tag, uint8_t tag_len)
|
||||
setAttrValue((char*)tag, tag_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
while(attr && attr->type != YDT_NONE) {
|
||||
|
||||
if ((tag_len == attr->tag_len)
|
||||
@@ -442,7 +442,7 @@ void YamlTreeWalker::toNextAttr()
|
||||
}
|
||||
}
|
||||
|
||||
void YamlTreeWalker::setAttrValue(char* buf, uint8_t len)
|
||||
void YamlTreeWalker::setAttrValue(char* buf, uint16_t len)
|
||||
{
|
||||
if (!buf || !len || isIdxInvalid())
|
||||
return;
|
||||
@@ -684,7 +684,7 @@ static bool find_node(void* ctx, char* buf, uint8_t len)
|
||||
return ((YamlTreeWalker*)ctx)->findNode(buf,len);
|
||||
}
|
||||
|
||||
static void set_attr(void* ctx, char* buf, uint8_t len)
|
||||
static void set_attr(void* ctx, char* buf, uint16_t len)
|
||||
{
|
||||
((YamlTreeWalker*)ctx)->setAttrValue(buf,len);
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ public:
|
||||
|
||||
bool isElmtEmpty(uint8_t* data);
|
||||
|
||||
void setAttrValue(char* buf, uint8_t len);
|
||||
void setAttrValue(char* buf, uint16_t len);
|
||||
|
||||
bool generate(yaml_writer_func wf, void* opaque);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user