feat(color): add long press menu for directories in SD browser (#5388)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ class FileBrowser : public TableField
|
||||
{
|
||||
public:
|
||||
// path, name, fullpath
|
||||
typedef std::function<void(const char*, const char*, const char*)> FileAction;
|
||||
typedef std::function<void(const char*, const char*, const char*, bool isDir)> 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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user