From 3443f02820e5f3086206eae1b547f710cce01f90 Mon Sep 17 00:00:00 2001 From: philmoz Date: Wed, 1 May 2024 10:44:44 +1000 Subject: [PATCH] Add filters to file chooser popup menu. --- .../src/gui/colorlcd/model_mixer_scripts.cpp | 2 +- radio/src/gui/colorlcd/model_setup.cpp | 2 +- radio/src/gui/colorlcd/special_functions.cpp | 2 +- radio/src/thirdparty/libopenui/src/choice.h | 4 + .../thirdparty/libopenui/src/filechoice.cpp | 147 +++++++++++++----- .../src/thirdparty/libopenui/src/filechoice.h | 15 +- .../thirdparty/libopenui/src/menutoolbar.cpp | 7 +- 7 files changed, 133 insertions(+), 46 deletions(-) diff --git a/radio/src/gui/colorlcd/model_mixer_scripts.cpp b/radio/src/gui/colorlcd/model_mixer_scripts.cpp index 3528a0eca2a..fc4873b2af3 100644 --- a/radio/src/gui/colorlcd/model_mixer_scripts.cpp +++ b/radio/src/gui/colorlcd/model_mixer_scripts.cpp @@ -111,7 +111,7 @@ class ScriptEditWindow : public Page LUA_LOAD_MODEL_SCRIPT(idx); // async reload ... update = true; }, - true); + true, STR_SCRIPT); // Custom name line = window->newLine(grid); diff --git a/radio/src/gui/colorlcd/model_setup.cpp b/radio/src/gui/colorlcd/model_setup.cpp index 334471f441b..200cd8cc084 100644 --- a/radio/src/gui/colorlcd/model_setup.cpp +++ b/radio/src/gui/colorlcd/model_setup.cpp @@ -92,7 +92,7 @@ static void setModelBitmap(std::string newValue) struct ModelBitmapEdit : public FileChoice { ModelBitmapEdit(Window *parent, const rect_t &rect) : FileChoice(parent, rect, BITMAPS_PATH, BITMAPS_EXT, LEN_BITMAP_NAME, - getModelBitmap, setModelBitmap) + getModelBitmap, setModelBitmap, false, STR_BITMAP) { } }; diff --git a/radio/src/gui/colorlcd/special_functions.cpp b/radio/src/gui/colorlcd/special_functions.cpp index d31e6ce5043..8892fe791eb 100644 --- a/radio/src/gui/colorlcd/special_functions.cpp +++ b/radio/src/gui/colorlcd/special_functions.cpp @@ -482,7 +482,7 @@ void FunctionEditPage::updateSpecialFunctionOneWindow() if (func == FUNC_PLAY_SCRIPT || func == FUNC_RGB_LED) LUA_LOAD_MODEL_SCRIPTS(); }, - true); // strip extension + true, funcGetLabel(func)); break; case FUNC_SET_TIMER: { diff --git a/radio/src/thirdparty/libopenui/src/choice.h b/radio/src/thirdparty/libopenui/src/choice.h index b9817bb4522..179a3673871 100644 --- a/radio/src/thirdparty/libopenui/src/choice.h +++ b/radio/src/thirdparty/libopenui/src/choice.h @@ -45,6 +45,8 @@ class ChoiceBase : public FormField ChoiceBase(Window *parent, const rect_t &rect, ChoiceType type = CHOICE_TYPE_DROPOWN); + void setChoiceType(ChoiceType t) { type = t; } + protected: ChoiceType type; lv_obj_t *label; @@ -149,6 +151,8 @@ class Choice : public ChoiceBase int getMin() const { return vmin; } int getMax() const { return vmax; } + std::string getString(int val) { return values[val]; } + void set_lv_LongPressHandler(lvHandler_t longPressHandler, void *data); int selectedIx0 = 0; diff --git a/radio/src/thirdparty/libopenui/src/filechoice.cpp b/radio/src/thirdparty/libopenui/src/filechoice.cpp index a3f2931bd62..e0d3439cf21 100644 --- a/radio/src/thirdparty/libopenui/src/filechoice.cpp +++ b/radio/src/thirdparty/libopenui/src/filechoice.cpp @@ -22,14 +22,82 @@ #include "libopenui_file.h" #include "menu.h" +#include "menutoolbar.h" #include "theme.h" +class FileChoiceMenuToolbar : public MenuToolbar +{ + public: + FileChoiceMenuToolbar(FileChoice *choice, Menu *menu) : + MenuToolbar(choice, menu, 3) + { + filterButton(choice, 'a', 'd', "aA-dD"); + filterButton(choice, 'e', 'h', "eE-hH"); + filterButton(choice, 'i', 'l', "iI-lL"); + filterButton(choice, 'm', 'p', "mM-pP"); + filterButton(choice, 'q', 't', "qQ-tT"); + filterButton(choice, 'u', 'z', "uU-zZ"); + filterButton(choice, '0', '9', "0-9"); + + bool found = false; + for (int i = 0; i < choice->getMax(); i += 1) { + char c = choice->getString(i)[0]; + if (c && !isdigit(c) && !isalpha(c)) { + found = true; + break; + } + } + + if (found) { + addButton( + "._-", 0, choice->getMax(), + [=](int16_t index) { + char c = choice->getString(index)[0]; + return c && !isdigit(c) && !isalpha(c); + }, + STR_MENU_OTHER); + } + + addButton(STR_SELECT_MENU_CLR, 0, 0, nullptr, nullptr, true); + } + + void filterButton(FileChoice *choice, char from, char to, const char* title) + { + bool found = false; + for (int i = 0; i < choice->getMax(); i += 1) { + char c = choice->getString(i)[0]; + if (isupper(c)) c += 0x20; + if (c >= from && c <= to) { + found = true; + break; + } + } + + if (found) { + char s[4]; + s[0] = from; s[1] = '-'; s[2] = to; s[3] = 0; + addButton( + s, 0, choice->getMax(), + [=](int16_t index) { + char c = choice->getString(index)[0]; + if (isupper(c)) c += 0x20; + return (c >= from && c <= to); + }, + title); + } + } + + protected: +}; + FileChoice::FileChoice(Window *parent, const rect_t &rect, std::string folder, const char *extension, int maxlen, std::function getValue, std::function setValue, - bool stripExtension) : - ChoiceBase(parent, rect, CHOICE_TYPE_FOLDER), + bool stripExtension, const char *title) : + Choice( + parent, rect, 0, 0, [=]() { return selectedIdx; }, + [=](int val) { setValue(getString(val)); }, title), folder(std::move(folder)), extension(extension), maxlen(maxlen), @@ -37,13 +105,18 @@ FileChoice::FileChoice(Window *parent, const rect_t &rect, std::string folder, setValue(std::move(setValue)), stripExtension(stripExtension) { + setChoiceType(CHOICE_TYPE_FOLDER); lv_event_send(lvobj, LV_EVENT_VALUE_CHANGED, nullptr); } std::string FileChoice::getLabelText() { return getValue(); } -bool FileChoice::openMenu() +void FileChoice::loadFiles() { + if (loaded) return; + + loaded = true; + FILINFO fno; DIR dir; std::list files; @@ -79,41 +152,45 @@ bool FileChoice::openMenu() files.emplace_back(newFile); } - if (!files.empty()) { - // sort files - files.sort(compare_nocase); - files.push_front(""); - - auto menu = new Menu(this); - int count = 0; - int current = -1; - std::string value = getValue(); - for (const auto &file : files) { - menu->addLineBuffered(file, [=]() { - setValue(file); - lv_event_send(lvobj, LV_EVENT_VALUE_CHANGED, nullptr); - }); - // TRACE("%s %d %s %d", value.c_str(), value.size(), file.c_str(), - // file.size()); - if (value.compare(file) == 0) { - // TRACE("OK"); - current = count; - } - ++count; - } - menu->updateLines(); - - if (current >= 0) { - menu->select(current); - } + f_closedir(&dir); + } - return true; + if (!files.empty()) { + // sort files + files.sort(compare_nocase); + files.push_front(""); + + std::string value = getValue(); + int idx = 0; + for (const auto &file : files) { + addValue(file.c_str()); + if (strcmp(value.c_str(), file.c_str()) == 0) selectedIdx = idx; + idx += 1; } - } - new MessageDialog(this, STR_SDCARD, STR_NO_FILES_ON_SD); + setMax(files.size() - 1); + } - return false; + fileCount = files.size(); } -void FileChoice::onClicked() { openMenu(); } +void FileChoice::openMenu() +{ + loadFiles(); + + if (fileCount > 0) { + setEditMode(true); // this needs to be done first before menu is created. + + auto menu = new Menu(this); + if (menuTitle) menu->setTitle(menuTitle); + + auto tb = new FileChoiceMenuToolbar(this, menu); + menu->setToolbar(tb); + + fillMenu(menu); + + menu->setCloseHandler([=]() { setEditMode(false); }); + } else { + new MessageDialog(this, STR_SDCARD, STR_NO_FILES_ON_SD); + } +} diff --git a/radio/src/thirdparty/libopenui/src/filechoice.h b/radio/src/thirdparty/libopenui/src/filechoice.h index 6baf9530aa0..2a67ec63b46 100644 --- a/radio/src/thirdparty/libopenui/src/filechoice.h +++ b/radio/src/thirdparty/libopenui/src/filechoice.h @@ -21,22 +21,25 @@ #include "choice.h" #include -class FileChoice : public ChoiceBase +class FileChoice : public Choice { public: FileChoice(Window* parent, const rect_t& rect, std::string folder, const char* extension, int maxlen, std::function getValue, std::function setValue, - bool stripExtension=false); + bool stripExtension = false, + const char* title = nullptr); #if defined(DEBUG_WINDOWS) std::string getName() const override { return "FileChoice"; } #endif - void onClicked() override; - protected: + bool loaded = false; + int fileCount = 0; + int selectedIdx = -1; + Menu* menu = nullptr; std::string getLabelText() override; std::string folder; const char* extension; @@ -45,5 +48,7 @@ class FileChoice : public ChoiceBase std::function setValue; bool stripExtension; - bool openMenu(); + void loadFiles(); + + void openMenu() override; }; diff --git a/radio/src/thirdparty/libopenui/src/menutoolbar.cpp b/radio/src/thirdparty/libopenui/src/menutoolbar.cpp index 05d89b4e2ad..d1421741130 100644 --- a/radio/src/thirdparty/libopenui/src/menutoolbar.cpp +++ b/radio/src/thirdparty/libopenui/src/menutoolbar.cpp @@ -22,7 +22,8 @@ #include "themes/etx_lv_theme.h" #include "translations.h" -constexpr uint32_t MENUS_TOOLBAR_BUTTON_WIDTH = 32; +constexpr uint32_t MENUS_TOOLBAR_BUTTON_WIDTH = 36; +constexpr uint32_t MENUS_TOOLBAR_BUTTON_HEIGHT = 32; static const lv_obj_class_t menu_button_class = { .base_class = &button_class, @@ -120,13 +121,13 @@ rect_t MenuToolbar::getButtonRect(bool wideButton) coord_t x = (nxtBtnPos % filterColumns) * (MENUS_TOOLBAR_BUTTON_WIDTH + PAD_SMALL); coord_t y = - (nxtBtnPos / filterColumns) * (MENUS_TOOLBAR_BUTTON_WIDTH + PAD_SMALL); + (nxtBtnPos / filterColumns) * (MENUS_TOOLBAR_BUTTON_HEIGHT + PAD_SMALL); coord_t w = wideButton ? (MENUS_TOOLBAR_BUTTON_WIDTH + PAD_SMALL) * (filterColumns - 1) + MENUS_TOOLBAR_BUTTON_WIDTH : MENUS_TOOLBAR_BUTTON_WIDTH; nxtBtnPos += wideButton ? filterColumns : 1; - return {x, y, w, MENUS_TOOLBAR_BUTTON_WIDTH}; + return {x, y, w, MENUS_TOOLBAR_BUTTON_HEIGHT}; } bool MenuToolbar::filterMenu(MenuToolbarButton* btn, int16_t filtermin,