diff --git a/radio/src/bitmaps/Horus/mask_library_empty_img.png b/radio/src/bitmaps/Horus/mask_library_slot.png similarity index 100% rename from radio/src/bitmaps/Horus/mask_library_empty_img.png rename to radio/src/bitmaps/Horus/mask_library_slot.png diff --git a/radio/src/gui/Horus/bitmaps.cpp b/radio/src/gui/Horus/bitmaps.cpp index 2b9e3d0cb..df598671b 100644 --- a/radio/src/gui/Horus/bitmaps.cpp +++ b/radio/src/gui/Horus/bitmaps.cpp @@ -212,6 +212,22 @@ const uint8_t * const LBM_MODEL_ICONS[] = { LBM_TELEMETRY_ICON }; +/* + * Model selection screen bitmaps + */ + +const uint8_t LBM_LIBRARY_ICON[] = { +#include "../../bitmaps/Horus/mask_library.lbm" +}; + +const uint8_t LBM_LIBRARY_SLOT[] = { +#include "../../bitmaps/Horus/mask_library_slot.lbm" +}; + +/* + * Other + */ + const uint16_t LBM_ASTERISK[] = { #include "../../bitmaps/Horus/asterisk.lbm" }; diff --git a/radio/src/gui/Horus/gui.h b/radio/src/gui/Horus/gui.h index fea301634..83ace5c22 100644 --- a/radio/src/gui/Horus/gui.h +++ b/radio/src/gui/Horus/gui.h @@ -98,5 +98,10 @@ extern const uint8_t LBM_RSCALE[]; extern const uint8_t * const LBM_RADIO_ICONS[]; extern const uint8_t * const LBM_MODEL_ICONS[]; +// Model selection icons +extern const uint8_t LBM_LIBRARY_ICON[]; +extern const uint8_t LBM_LIBRARY_SLOT[]; + +// Other icons extern const uint16_t LBM_ASTERISK[]; diff --git a/radio/src/gui/Horus/menu_model_select.cpp b/radio/src/gui/Horus/menu_model_select.cpp index fe0f450dc..1b03c8536 100644 --- a/radio/src/gui/Horus/menu_model_select.cpp +++ b/radio/src/gui/Horus/menu_model_select.cpp @@ -37,19 +37,76 @@ #define CATEGORIES_WIDTH 140 +void drawModel(coord_t x, coord_t y, const char * name) +{ + lcdDrawSolidRect(x, y, 153, 61, LINE_COLOR); + lcdDrawText(x+5, y+2, name, TEXT_COLOR); + lcdDrawSolidHorizontalLine(x+5, y+19, 143, LINE_COLOR); + lcdDrawBitmapPattern(x+5, y+23, LBM_LIBRARY_SLOT, TEXT_COLOR); +} + void menuModelSelect(evt_t event) { + switch(event) { + case 0: + // no need to refresh the screen + return; + + case EVT_KEY_FIRST(KEY_EXIT): + chainMenu(menuMainView); + return; + } + // Header lcdDrawSolidFilledRect(0, 0, LCD_W, MENU_HEADER_HEIGHT, HEADER_BGCOLOR); lcdDrawBitmapPattern(0, 0, LBM_TOPMENU_POLYGON, TITLE_BGCOLOR); - // lcdDrawBitmapPattern(4, 10, LBM_TOPMENU_OPENTX, MENU_TITLE_COLOR); + lcdDrawBitmapPattern(5, 7, LBM_LIBRARY_ICON, MENU_TITLE_COLOR); drawTopmenuDatetime(); // Categories lcdDrawSolidFilledRect(0, MENU_HEADER_HEIGHT, CATEGORIES_WIDTH, LCD_H-MENU_HEADER_HEIGHT-MENU_FOOTER_HEIGHT, TITLE_BGCOLOR); + StorageModelsList storage; + const char * error = storageOpenModelsList(&storage); + if (!error) { + bool result = true; + coord_t y = MENU_HEADER_HEIGHT+10; + while (y < LCD_H) { + char line[256]; + result = storageReadNextCategory(&storage, line, sizeof(line)-1); + if (!result) + break; + lcdDrawText(MENUS_MARGIN_LEFT, y, line, MENU_TITLE_COLOR); + y += FH; + } + } + // Models lcdDrawSolidFilledRect(CATEGORIES_WIDTH, MENU_HEADER_HEIGHT, LCD_W-CATEGORIES_WIDTH, LCD_H-MENU_HEADER_HEIGHT-MENU_FOOTER_HEIGHT, TEXT_BGCOLOR); + if (!error) { + bool result = storageSeekCategory(&storage, 0); + coord_t y = MENU_HEADER_HEIGHT+7; + unsigned int i = 0; + while (result) { + char line[256]; + result = storageReadNextModel(&storage, line, sizeof(line)-1); + if (!result) + break; + if (y < LCD_H) { + coord_t x; + if (i & 1) { + drawModel(CATEGORIES_WIDTH+MENUS_MARGIN_LEFT+162, y, line); + y += 66; + } + else { + drawModel(CATEGORIES_WIDTH+MENUS_MARGIN_LEFT+1, y, line); + } + } + i++; + } + } + + drawScrollbar(DEFAULT_SCROLLBAR_X, MENU_HEADER_HEIGHT+7, MENU_FOOTER_TOP-MENU_HEADER_HEIGHT-15, 0, 4, 2); // Footer lcdDrawSolidFilledRect(0, MENU_FOOTER_TOP, LCD_W, MENU_FOOTER_HEIGHT, HEADER_BGCOLOR); diff --git a/radio/src/gui/Horus/view_statistics.cpp b/radio/src/gui/Horus/view_statistics.cpp index dee6c55ed..36763ab99 100644 --- a/radio/src/gui/Horus/view_statistics.cpp +++ b/radio/src/gui/Horus/view_statistics.cpp @@ -38,13 +38,10 @@ void menuStatisticsView(evt_t event) { - drawMenuTemplate("Statistics", event); - - switch(event) - { + switch(event) { case EVT_KEY_FIRST(KEY_UP): chainMenu(menuStatisticsDebug); - break; + return; case EVT_KEY_LONG(KEY_MENU): g_eeGeneral.globalTimer = 0; @@ -54,9 +51,11 @@ void menuStatisticsView(evt_t event) case EVT_KEY_FIRST(KEY_EXIT): chainMenu(menuMainView); - break; + return; } + drawMenuTemplate("Statistics", event); + lcdDrawText( 10, MENU_CONTENT_TOP + FH*0, "\037\145TOT:\037\317BATT:", HEADER_COLOR); lcdDrawText( 10, MENU_CONTENT_TOP + FH*1, "TM1:\037\145TM2:", HEADER_COLOR); lcdDrawText( 10, MENU_CONTENT_TOP + FH*2, "THR:\037\145TH%:", HEADER_COLOR); diff --git a/radio/src/myeeprom.h b/radio/src/myeeprom.h index 7cb024650..3ad41c06d 100644 --- a/radio/src/myeeprom.h +++ b/radio/src/myeeprom.h @@ -119,7 +119,7 @@ #define MAX_INPUTS 32 #define NUM_TRAINER 16 #define NUM_POTS 3 - #define NUM_XPOTS 0 + #define NUM_XPOTS 3 #define MAX_SENSORS 32 #elif defined(PCBFLAMENCO) #define MAX_MODELS 60 diff --git a/radio/src/storage/sdcard_raw.cpp b/radio/src/storage/sdcard_raw.cpp index 8d2fd1ed1..f34e3be0b 100644 --- a/radio/src/storage/sdcard_raw.cpp +++ b/radio/src/storage/sdcard_raw.cpp @@ -143,6 +143,7 @@ const char * loadModel(const char * filename) } const char RADIO_SETTINGS_PATH[] = RADIO_PATH "/radio.bin"; +const char RADIO_MODELSLIST_PATH[] = RADIO_PATH "/models.txt"; const char * loadGeneralSettings() { @@ -215,3 +216,175 @@ void storageFormat() sdCheckAndCreateDirectory(RADIO_PATH); sdCheckAndCreateDirectory(MODELS_PATH); } + +struct StorageModelsList { + FIL file; +}; + +const char * storageOpenModelsList(StorageModelsList * storage) +{ + FRESULT result = f_open(&storage->file, RADIO_MODELSLIST_PATH, FA_OPEN_EXISTING | FA_READ); + if (result != FR_OK) { + return SDCARD_ERROR(result); + } + return NULL; +} + +bool storageReadNextLine(StorageModelsList * storage, char * line, int maxlen) +{ + char c; + unsigned int read; + int len=0; + while (1) { + FRESULT result = f_read(&storage->file, (uint8_t *)&c, 1, &read); + if (result != FR_OK || read != 1) { + line[len] = '\0'; + return false; + } + if (c == '\n') { + if (len > 0) { + // we skip empty lines + line[len] = '\0'; + return true; + } + } + else if (c != '\r' && len < maxlen) { + line[len++] = c; + } + } +} + +int storageGetCategoryLength(const char * line) +{ + int len = strlen(line); + if (len > 2 && line[0] == '[' && line[len-1] == ']') { + return len-2; + } + else { + return 0; + } +} + +bool storageReadNextCategory(StorageModelsList * storage, char * line, int maxlen) +{ + bool result = true; + while (result) { + result = storageReadNextLine(storage, line, maxlen); + int len = storageGetCategoryLength(line); + if (len > 0) { + memmove(line, &line[1], len); + line[len] = '\0'; + return result; + } + } + line[0] = '\0'; + return false; +} + +bool storageSeekCategory(StorageModelsList * storage, int category) +{ + f_lseek(&storage->file, 0); + char line[256] = ""; + int result = true; + for (int i=0; result && i<=category; i++) { + result = storageReadNextCategory(storage, line, sizeof(line)-1); + } + return line[0] != '\0'; +} + +bool storageReadNextModel(StorageModelsList * storage, char * line, int maxlen) +{ + bool result = true; + while (result) { + result = storageReadNextLine(storage, line, maxlen); + if (line[0] == '[') + return false; + else if (line[0] != '\0') + return result; + } + line[0] = '\0'; + return false; +} + +#define STORAGE_INSERT 1 +#define STORAGE_REMOVE 2 +#define STORAGE_RENAME 3 + +const char * storageModifyModel(unsigned int operation, int category, int position, const char * name="") +{ + StorageModelsList storage; + FIL file; + const char * error = storageOpenModelsList(&storage); + if (error) { + return error; + } + + { + FRESULT result = f_open(&file, RADIO_PATH "/models.tmp", FA_CREATE_ALWAYS | FA_WRITE); + if (result != FR_OK) { + return SDCARD_ERROR(result); + } + } + + bool operationdone = false; + bool result = true; + int categoryindex = -1; + int modelindex = 0; + + while (result) { + char line[256]; + result = storageReadNextLine(&storage, line, sizeof(line)-1); + if (!operationdone) { + int len = storageGetCategoryLength(line); + if (len > 0) { + if (categoryindex++ == category) { + operationdone = true; + if (operation == STORAGE_INSERT) { + f_puts(name, &file); + f_putc('\n', &file); + } + } + } + else if (categoryindex == category) { + if (modelindex++ == position) { + operationdone = true; + if (operation == STORAGE_INSERT) { + f_puts(name, &file); + f_putc('\n', &file); + } + else if (operation == STORAGE_RENAME) { + f_puts(name, &file); + f_putc('\n', &file); + continue; + } + } + } + } + f_puts(line, &file); + f_putc('\n', &file); + } + + if (!operationdone && categoryindex>=0 && operation==STORAGE_INSERT) { + f_puts(name, &file); + f_putc('\n', &file); + } + + f_close(&file); + f_rename(RADIO_PATH "/models.tmp", RADIO_MODELSLIST_PATH); + return NULL; +} + +const char * storageInsertModel(const char * name, int category, int position) +{ + return storageModifyModel(STORAGE_INSERT, category, position, name); +} + +const char * storageRemoveModel(int category, int position) +{ + return storageModifyModel(STORAGE_REMOVE, category, position); +} + +const char * storageRenameModel(const char * name, int category, int position) +{ + return storageModifyModel(STORAGE_RENAME, category, position, name); +}