From ebe7414becf88d930d0ebc86fc9fccf8fafa569c Mon Sep 17 00:00:00 2001 From: philmoz Date: Wed, 7 Aug 2024 19:22:30 +1000 Subject: [PATCH] feat(color): add long press menu for directories in SD browser (#5388) --- radio/src/gui/colorlcd/libui/file_browser.cpp | 29 +++++++++-- radio/src/gui/colorlcd/libui/file_browser.h | 4 +- radio/src/gui/colorlcd/libui/menu.cpp | 28 +++++------ radio/src/gui/colorlcd/libui/table.cpp | 23 ++++++--- radio/src/gui/colorlcd/libui/table.h | 5 ++ .../mainview/screen_user_interface.cpp | 2 +- .../gui/colorlcd/radio/radio_sdmanager.cpp | 49 ++++++++++++++++--- .../src/gui/colorlcd/radio/radio_sdmanager.h | 1 + radio/src/translations.cpp | 2 + radio/src/translations.h | 2 + radio/src/translations/cn.h | 2 + radio/src/translations/cz.h | 2 + radio/src/translations/da.h | 2 + radio/src/translations/de.h | 2 + radio/src/translations/en.h | 2 + radio/src/translations/es.h | 2 + radio/src/translations/fi.h | 2 + radio/src/translations/fr.h | 2 + radio/src/translations/he.h | 2 + radio/src/translations/it.h | 2 + radio/src/translations/jp.h | 2 + radio/src/translations/nl.h | 2 + radio/src/translations/pl.h | 2 + radio/src/translations/pt.h | 2 + radio/src/translations/ru.h | 2 + radio/src/translations/se.h | 2 + radio/src/translations/tw.h | 2 + radio/src/translations/ua.h | 2 + 28 files changed, 146 insertions(+), 35 deletions(-) diff --git a/radio/src/gui/colorlcd/libui/file_browser.cpp b/radio/src/gui/colorlcd/libui/file_browser.cpp index 287effb23..1475c7ca7 100644 --- a/radio/src/gui/colorlcd/libui/file_browser.cpp +++ b/radio/src/gui/colorlcd/libui/file_browser.cpp @@ -222,6 +222,13 @@ void FileBrowser::onPress(uint16_t row, uint16_t col) onPress(lv_table_get_cell_value(lvobj, row, col), is_dir); } +bool FileBrowser::onPressLong(uint16_t row, uint16_t col) +{ + bool is_dir = lv_table_has_cell_ctrl(lvobj, row, col, CELL_CTRL_DIR); + onPressLong(lv_table_get_cell_value(lvobj, row, col), is_dir); + return true; +} + void FileBrowser::onDrawBegin(uint16_t row, uint16_t col, lv_obj_draw_part_dsc_t* dsc) { lv_coord_t cell_left = lv_obj_get_style_pad_left(lvobj, LV_PART_ITEMS); @@ -261,13 +268,13 @@ void FileBrowser::onDrawEnd(uint16_t row, uint16_t col, lv_obj_draw_part_dsc_t* void FileBrowser::onSelected(const char* name, bool is_dir) { if (is_dir) { - if (fileSelected) fileSelected(nullptr, nullptr, nullptr); + if (fileSelected) fileSelected(nullptr, nullptr, nullptr, is_dir); return; } const char* path = getCurrentPath(); const char* fullpath = getFullPath(name); - if (fileSelected) fileSelected(path, name, fullpath); + if (fileSelected) fileSelected(path, name, fullpath, is_dir); selected = name; } @@ -277,7 +284,7 @@ void FileBrowser::onPress(const char* name, bool is_dir) const char* fullpath = getFullPath(name); if (is_dir) { f_chdir(fullpath); - if (fileSelected) fileSelected(nullptr, nullptr, nullptr); + if (fileSelected) fileSelected(nullptr, nullptr, nullptr, is_dir); refresh(); return; } @@ -288,6 +295,20 @@ void FileBrowser::onPress(const char* name, bool is_dir) } if (fileAction){ - fileAction(path, name, fullpath); + fileAction(path, name, fullpath, is_dir); + } +} + +void FileBrowser::onPressLong(const char* name, bool is_dir) +{ + const char* path = getCurrentPath(); + const char* fullpath = getFullPath(name); + + if (!selected || (selected != name)) { + onSelected(name, is_dir); + } + + if (fileAction){ + fileAction(path, name, fullpath, is_dir); } } diff --git a/radio/src/gui/colorlcd/libui/file_browser.h b/radio/src/gui/colorlcd/libui/file_browser.h index 66889df5c..189052aeb 100644 --- a/radio/src/gui/colorlcd/libui/file_browser.h +++ b/radio/src/gui/colorlcd/libui/file_browser.h @@ -27,7 +27,7 @@ class FileBrowser : public TableField { public: // path, name, fullpath - typedef std::function FileAction; + typedef std::function FileAction; FileBrowser(Window* parent, const rect_t& rect, const char* dir); @@ -45,10 +45,12 @@ class FileBrowser : public TableField // TableField methods void onSelected(uint16_t row, uint16_t col) override; void onPress(uint16_t row, uint16_t col) override; + bool onPressLong(uint16_t row, uint16_t col) override; protected: void onSelected(const char* name, bool is_dir); void onPress(const char* name, bool is_dir); + void onPressLong(const char* name, bool is_dir); private: const char* selected = nullptr; diff --git a/radio/src/gui/colorlcd/libui/menu.cpp b/radio/src/gui/colorlcd/libui/menu.cpp index b88b7db3e..a7367f067 100644 --- a/radio/src/gui/colorlcd/libui/menu.cpp +++ b/radio/src/gui/colorlcd/libui/menu.cpp @@ -206,26 +206,22 @@ class MenuBody : public TableField void onPress(uint16_t row, uint16_t col) override { - if (!isLongPressed) { - Menu* menu = getParentMenu(); - if (row < lines.size()) { - if (menu->multiple) { - if (selectedIndex == (int)row) - lines[row]->onPress(); - else { - setIndex(row); - lines[row]->onPress(); - } - } else { - // delete menu first to avoid - // focus issues with onPress() - menu->deleteLater(); + Menu* menu = getParentMenu(); + if (row < lines.size()) { + if (menu->multiple) { + if (selectedIndex == (int)row) + lines[row]->onPress(); + else { + setIndex(row); lines[row]->onPress(); } + } else { + // delete menu first to avoid + // focus issues with onPress() + menu->deleteLater(); + lines[row]->onPress(); } } - - isLongPressed = false; } void onDrawBegin(uint16_t row, uint16_t col, diff --git a/radio/src/gui/colorlcd/libui/table.cpp b/radio/src/gui/colorlcd/libui/table.cpp index 3dcf7d9a0..bce2c821c 100644 --- a/radio/src/gui/colorlcd/libui/table.cpp +++ b/radio/src/gui/colorlcd/libui/table.cpp @@ -20,8 +20,6 @@ #include "etx_lv_theme.h" -static void table_event(const lv_obj_class_t* class_p, lv_event_t* e); - // Table const lv_style_const_prop_t table_cell_props[] = { LV_STYLE_CONST_BORDER_WIDTH(1), @@ -52,7 +50,7 @@ static const lv_obj_class_t table_class = { .constructor_cb = table_constructor, .destructor_cb = nullptr, .user_data = nullptr, - .event_cb = table_event, + .event_cb = TableField::table_event, .width_def = 0, .height_def = 0, .editable = LV_OBJ_CLASS_EDITABLE_TRUE, @@ -65,7 +63,7 @@ static lv_obj_t* table_create(lv_obj_t* parent) return etx_create(&table_class, parent); } -static void table_event(const lv_obj_class_t* class_p, lv_event_t* e) +void TableField::table_event(const lv_obj_class_t* class_p, lv_event_t* e) { lv_obj_t* obj = lv_event_get_target(e); if (obj) { @@ -87,7 +85,13 @@ static void table_event(const lv_obj_class_t* class_p, lv_event_t* e) // Otherwise it's a click if (lv_group_get_editing((lv_group_t*)lv_obj_get_group(obj)) || indev_type == LV_INDEV_TYPE_POINTER) { - tf->onPress(row, col); + if (tf->isLongPress) { + tf->isLongPress = false; + if (!tf->onPressLong(row, col)) + tf->onPress(row, col); + } else { + tf->onPress(row, col); + } } else { tf->onClicked(); } @@ -148,7 +152,11 @@ static void table_event(const lv_obj_class_t* class_p, lv_event_t* e) lv_draw_rect(draw_ctx, &rect_dsc, &coords); } } break; - case LV_EVENT_RELEASED: { + case LV_EVENT_LONG_PRESSED: + tf->isLongPress = true; + break; + case LV_EVENT_RELEASED: + { lv_table_t* table = (lv_table_t*)obj; /*From lv_table.c: handler for LV_EVENT_RELEASED*/ lv_obj_invalidate(obj); @@ -158,8 +166,9 @@ static void table_event(const lv_obj_class_t* class_p, lv_event_t* e) table->row_act != LV_TABLE_CELL_NONE && scroll_obj == NULL) { lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL); } + // Do not call base class event handler return; - } break; + } default: break; } diff --git a/radio/src/gui/colorlcd/libui/table.h b/radio/src/gui/colorlcd/libui/table.h index 045fcba7c..e4900d1f7 100644 --- a/radio/src/gui/colorlcd/libui/table.h +++ b/radio/src/gui/colorlcd/libui/table.h @@ -36,6 +36,7 @@ class TableField : public Window void select(uint16_t row, uint16_t col, bool force = false); virtual void onPress(uint16_t row, uint16_t col) {} + virtual bool onPressLong(uint16_t row, uint16_t col) { return false; } virtual void onSelected(uint16_t row, uint16_t col) {} virtual void onDrawBegin(uint16_t row, uint16_t col, lv_obj_draw_part_dsc_t* dsc) {} @@ -44,6 +45,10 @@ class TableField : public Window void adjustScroll(); void selectNext(int16_t dir); + static void table_event(const lv_obj_class_t* class_p, lv_event_t* e); + protected: + bool isLongPress = false; + void onEvent(event_t event) override; }; diff --git a/radio/src/gui/colorlcd/mainview/screen_user_interface.cpp b/radio/src/gui/colorlcd/mainview/screen_user_interface.cpp index d7627ffe8..322ca05ac 100644 --- a/radio/src/gui/colorlcd/mainview/screen_user_interface.cpp +++ b/radio/src/gui/colorlcd/mainview/screen_user_interface.cpp @@ -148,7 +148,7 @@ void ScreenUserInterfacePage::build(Window* window) auto line = window->newLine(grid); new StaticText(line, rect_t{}, STR_TOP_BAR); - auto setupTopbarWidgets = new TextButton(line, rect_t{}, STR_SETUP_WIDGETS, + new TextButton(line, rect_t{}, STR_SETUP_WIDGETS, [=]() -> uint8_t { menu->deleteLater(); new SetupTopBarWidgetsPage(); diff --git a/radio/src/gui/colorlcd/radio/radio_sdmanager.cpp b/radio/src/gui/colorlcd/radio/radio_sdmanager.cpp index 356a8d8d3..cde86f1d8 100644 --- a/radio/src/gui/colorlcd/radio/radio_sdmanager.cpp +++ b/radio/src/gui/colorlcd/radio/radio_sdmanager.cpp @@ -250,17 +250,19 @@ void RadioSdManagerPage::build(Window * window) preview = new FilePreview(box, {0, 0, PREVIEW_W, PREVIEW_H}); preview->padAll(PAD_SMALL); - browser->setFileAction([=](const char* path, const char* name, const char* fullpath) { - fileAction(path, name, fullpath); + browser->setFileAction([=](const char* path, const char* name, const char* fullpath, bool isDir) { + if (isDir) + dirAction(path, name, fullpath); + else + fileAction(path, name, fullpath); }); - browser->setFileSelected([=](const char* path, const char* name, const char* fullpath) { + browser->setFileSelected([=](const char* path, const char* name, const char* fullpath, bool isDir) { preview->setFile(nullptr); loading->hide(); - if (fullpath) { + if (fullpath && !isDir) { auto ext = getFileExtension(fullpath); if (ext) { - if (strcasecmp(ext, ".bmp") == 0 || strcasecmp(ext, ".png") == 0 || - strcasecmp(ext, ".jpg") == 0 || strcasecmp(ext, ".jpeg") == 0) { + if (isExtensionMatching(ext, BITMAPS_EXT)) { previewFilename = fullpath; loadPreview = 10; loading->show(); @@ -285,6 +287,41 @@ void RadioSdManagerPage::checkEvents() } } } + +void RadioSdManagerPage::dirAction(const char* path, const char* name, + const char* fullpath) +{ + if (strcmp(name, "..") == 0) return; + + auto window = Layer::back(); + auto menu = new Menu(window); + menu->addLine(STR_RENAME_FILE, [=]() { + uint8_t nameLength; + uint8_t extLength; + + const char *ext = getFileExtension(name, 0, 0, &nameLength, &extLength); + + const uint8_t maxNameLength = SD_SCREEN_FILE_LENGTH - extLength; + nameLength = min((uint8_t)(nameLength - extLength), maxNameLength); + + std::string fname(name, nameLength); + std::string extension(""); + if (ext) extension = ext; + + new LabelDialog(Layer::back(), fname.c_str(), maxNameLength, STR_RENAME_FILE, [=](std::string label) { + label += extension; + f_rename((const TCHAR *)name, (const TCHAR *)label.c_str()); + browser->refresh(); + }); + }); + menu->addLine(STR_DELETE_FILE, [=]() { + if (f_unlink(fullpath) != FR_OK) { + new MessageDialog(window, STR_DELETE_FILE, STR_DEL_DIR_NOT_EMPTY); + } + browser->refresh(); + }); +} + void RadioSdManagerPage::fileAction(const char* path, const char* name, const char* fullpath) { diff --git a/radio/src/gui/colorlcd/radio/radio_sdmanager.h b/radio/src/gui/colorlcd/radio/radio_sdmanager.h index 1a68b704f..176844d79 100644 --- a/radio/src/gui/colorlcd/radio/radio_sdmanager.h +++ b/radio/src/gui/colorlcd/radio/radio_sdmanager.h @@ -47,6 +47,7 @@ class RadioSdManagerPage : public PageTab const char* previewFilename = nullptr; void fileAction(const char* path, const char* name, const char* fullpath); + void dirAction(const char* path, const char* name, const char* fullpath); void BootloaderUpdate(const char* fn); #if defined(BLUETOOTH) diff --git a/radio/src/translations.cpp b/radio/src/translations.cpp index b3e7e7b72..26c620b08 100644 --- a/radio/src/translations.cpp +++ b/radio/src/translations.cpp @@ -1112,3 +1112,5 @@ const char STR_WIDGET_SIZE[] = TR_WIDGET_SIZE; const char STR_MIX_SLOW_PREC[] = TR_MIX_SLOW_PREC; const char STR_MIX_DELAY_PREC[] = TR_MIX_DELAY_PREC; + +const char STR_DEL_DIR_NOT_EMPTY[] = TR_DEL_DIR_NOT_EMPTY; diff --git a/radio/src/translations.h b/radio/src/translations.h index 775c62478..b67060b6d 100644 --- a/radio/src/translations.h +++ b/radio/src/translations.h @@ -1277,3 +1277,5 @@ extern const char STR_WIDGET_SIZE[]; extern const char STR_MIX_SLOW_PREC[]; extern const char STR_MIX_DELAY_PREC[]; + +extern const char STR_DEL_DIR_NOT_EMPTY[]; diff --git a/radio/src/translations/cn.h b/radio/src/translations/cn.h index 4b359121c..104dc3c88 100644 --- a/radio/src/translations/cn.h +++ b/radio/src/translations/cn.h @@ -1307,3 +1307,5 @@ #define TR_HIGH_BATT_COLOR "高电量" #define TR_WIDGET_SIZE "小部件尺寸" + +#define TR_DEL_DIR_NOT_EMPTY "Directory must be empty before deletion" diff --git a/radio/src/translations/cz.h b/radio/src/translations/cz.h index 843f7977f..9ad35d149 100644 --- a/radio/src/translations/cz.h +++ b/radio/src/translations/cz.h @@ -1320,3 +1320,5 @@ #define TR_HIGH_BATT_COLOR "Plně nabitá baterie" #define TR_WIDGET_SIZE "Velikost widgetu" + +#define TR_DEL_DIR_NOT_EMPTY "Directory must be empty before deletion" diff --git a/radio/src/translations/da.h b/radio/src/translations/da.h index 416b3f810..0e101d1ae 100644 --- a/radio/src/translations/da.h +++ b/radio/src/translations/da.h @@ -1319,3 +1319,5 @@ #define TR_HIGH_BATT_COLOR "Batteri højt" #define TR_WIDGET_SIZE "Widget størrelse" + +#define TR_DEL_DIR_NOT_EMPTY "Directory must be empty before deletion" diff --git a/radio/src/translations/de.h b/radio/src/translations/de.h index 72a677df9..594f1705d 100644 --- a/radio/src/translations/de.h +++ b/radio/src/translations/de.h @@ -1310,3 +1310,5 @@ #define TR_HIGH_BATT_COLOR "Farbe Akku voll" #define TR_WIDGET_SIZE "Widget Größe" + +#define TR_DEL_DIR_NOT_EMPTY "Directory must be empty before deletion" diff --git a/radio/src/translations/en.h b/radio/src/translations/en.h index fb62e9537..52b3b2897 100644 --- a/radio/src/translations/en.h +++ b/radio/src/translations/en.h @@ -1305,3 +1305,5 @@ #define TR_HIGH_BATT_COLOR "High battery" #define TR_WIDGET_SIZE "Widget size" + +#define TR_DEL_DIR_NOT_EMPTY "Directory must be empty before deletion" diff --git a/radio/src/translations/es.h b/radio/src/translations/es.h index 47010bba3..11f6c7974 100644 --- a/radio/src/translations/es.h +++ b/radio/src/translations/es.h @@ -1307,3 +1307,5 @@ #define TR_HIGH_BATT_COLOR "High battery" #define TR_WIDGET_SIZE "Widget size" + +#define TR_DEL_DIR_NOT_EMPTY "Directory must be empty before deletion" diff --git a/radio/src/translations/fi.h b/radio/src/translations/fi.h index 7bd9014ad..e1b2cd8c9 100644 --- a/radio/src/translations/fi.h +++ b/radio/src/translations/fi.h @@ -1323,3 +1323,5 @@ #define TR_HIGH_BATT_COLOR "High battery" #define TR_WIDGET_SIZE "Widget size" + +#define TR_DEL_DIR_NOT_EMPTY "Directory must be empty before deletion" diff --git a/radio/src/translations/fr.h b/radio/src/translations/fr.h index d550f26df..d968ff49a 100644 --- a/radio/src/translations/fr.h +++ b/radio/src/translations/fr.h @@ -1322,3 +1322,5 @@ #define TR_HIGH_BATT_COLOR "High battery" #define TR_WIDGET_SIZE "Taille Widget" + +#define TR_DEL_DIR_NOT_EMPTY "Directory must be empty before deletion" diff --git a/radio/src/translations/he.h b/radio/src/translations/he.h index 58c02b3d8..f043dabe0 100644 --- a/radio/src/translations/he.h +++ b/radio/src/translations/he.h @@ -1306,3 +1306,5 @@ #define TR_HIGH_BATT_COLOR "מתח סוללה גבוה" #define TR_WIDGET_SIZE "Widget size" + +#define TR_DEL_DIR_NOT_EMPTY "Directory must be empty before deletion" diff --git a/radio/src/translations/it.h b/radio/src/translations/it.h index 6a8d5d2e0..64903b4ec 100644 --- a/radio/src/translations/it.h +++ b/radio/src/translations/it.h @@ -1301,3 +1301,5 @@ #define TR_HIGH_BATT_COLOR "Batteria carica" #define TR_WIDGET_SIZE "Dimensione widget" + +#define TR_DEL_DIR_NOT_EMPTY "Directory must be empty before deletion" diff --git a/radio/src/translations/jp.h b/radio/src/translations/jp.h index 9882da7b3..7ba6575fb 100644 --- a/radio/src/translations/jp.h +++ b/radio/src/translations/jp.h @@ -1306,3 +1306,5 @@ #define TR_HIGH_BATT_COLOR "バッテリー高" #define TR_WIDGET_SIZE "ウィジェットサイズ" + +#define TR_DEL_DIR_NOT_EMPTY "Directory must be empty before deletion" diff --git a/radio/src/translations/nl.h b/radio/src/translations/nl.h index 3700f4ead..ec1e6fac9 100644 --- a/radio/src/translations/nl.h +++ b/radio/src/translations/nl.h @@ -1313,3 +1313,5 @@ #define TR_HIGH_BATT_COLOR "High battery" #define TR_WIDGET_SIZE "Widget size" + +#define TR_DEL_DIR_NOT_EMPTY "Directory must be empty before deletion" diff --git a/radio/src/translations/pl.h b/radio/src/translations/pl.h index ec5ba651b..44f11117c 100644 --- a/radio/src/translations/pl.h +++ b/radio/src/translations/pl.h @@ -1306,3 +1306,5 @@ #define TR_HIGH_BATT_COLOR "Naładowana bateria" #define TR_WIDGET_SIZE "Rozmiar widżetu" + +#define TR_DEL_DIR_NOT_EMPTY "Directory must be empty before deletion" diff --git a/radio/src/translations/pt.h b/radio/src/translations/pt.h index ee5799d51..41f7b48ee 100644 --- a/radio/src/translations/pt.h +++ b/radio/src/translations/pt.h @@ -1310,3 +1310,5 @@ #define TR_HIGH_BATT_COLOR "Bateria Alta" #define TR_WIDGET_SIZE "Tamanho do widget" + +#define TR_DEL_DIR_NOT_EMPTY "Directory must be empty before deletion" diff --git a/radio/src/translations/ru.h b/radio/src/translations/ru.h index f2ce793b1..489bf809b 100644 --- a/radio/src/translations/ru.h +++ b/radio/src/translations/ru.h @@ -1309,3 +1309,5 @@ #define TR_HIGH_BATT_COLOR "Заряженная АКБ" #define TR_WIDGET_SIZE "Размер виджета" + +#define TR_DEL_DIR_NOT_EMPTY "Directory must be empty before deletion" diff --git a/radio/src/translations/se.h b/radio/src/translations/se.h index fe187192f..435adcc39 100644 --- a/radio/src/translations/se.h +++ b/radio/src/translations/se.h @@ -1341,3 +1341,5 @@ #define TR_HIGH_BATT_COLOR "Högt batteri" #define TR_WIDGET_SIZE "Widgetstorlek" + +#define TR_DEL_DIR_NOT_EMPTY "Directory must be empty before deletion" diff --git a/radio/src/translations/tw.h b/radio/src/translations/tw.h index 6e5898abf..e4a9197f2 100644 --- a/radio/src/translations/tw.h +++ b/radio/src/translations/tw.h @@ -1312,3 +1312,5 @@ #define TR_HIGH_BATT_COLOR "高電量" #define TR_WIDGET_SIZE "小部件尺寸" + +#define TR_DEL_DIR_NOT_EMPTY "Directory must be empty before deletion" diff --git a/radio/src/translations/ua.h b/radio/src/translations/ua.h index f308a0bd4..3953e5ef7 100644 --- a/radio/src/translations/ua.h +++ b/radio/src/translations/ua.h @@ -1305,3 +1305,5 @@ #define TR_HIGH_BATT_COLOR "Заряджена батарея" #define TR_WIDGET_SIZE "Розмір віджета" + +#define TR_DEL_DIR_NOT_EMPTY "Directory must be empty before deletion"