diff --git a/radio/src/bitmaps/480x272/default_theme/mask_sort_alpha_down.png b/radio/src/bitmaps/480x272/default_theme/mask_sort_alpha_down.png deleted file mode 100644 index 718486eb7..000000000 Binary files a/radio/src/bitmaps/480x272/default_theme/mask_sort_alpha_down.png and /dev/null differ diff --git a/radio/src/bitmaps/480x272/default_theme/mask_sort_alpha_up.png b/radio/src/bitmaps/480x272/default_theme/mask_sort_alpha_up.png deleted file mode 100644 index b736e8752..000000000 Binary files a/radio/src/bitmaps/480x272/default_theme/mask_sort_alpha_up.png and /dev/null differ diff --git a/radio/src/bitmaps/480x272/default_theme/mask_sort_date_down.png b/radio/src/bitmaps/480x272/default_theme/mask_sort_date_down.png deleted file mode 100644 index 9cd801d62..000000000 Binary files a/radio/src/bitmaps/480x272/default_theme/mask_sort_date_down.png and /dev/null differ diff --git a/radio/src/bitmaps/480x272/default_theme/mask_sort_date_up.png b/radio/src/bitmaps/480x272/default_theme/mask_sort_date_up.png deleted file mode 100644 index a87ab486b..000000000 Binary files a/radio/src/bitmaps/480x272/default_theme/mask_sort_date_up.png and /dev/null differ diff --git a/radio/src/gui/colorlcd/model_select.cpp b/radio/src/gui/colorlcd/model_select.cpp index ba342008a..b9eb8149e 100644 --- a/radio/src/gui/colorlcd/model_select.cpp +++ b/radio/src/gui/colorlcd/model_select.cpp @@ -21,12 +21,7 @@ #include "model_select.h" -#include -#include -#include - #include "libopenui.h" -#include "listbox.h" #include "menu_model.h" #include "menu_radio.h" #include "menu_screen.h" @@ -35,32 +30,11 @@ #include "standalone_lua.h" #include "view_channels.h" -// bitmaps for toolbar -const uint8_t _mask_sort_alpha_up[] = { -#include "mask_sort_alpha_up.lbm" -}; -STATIC_LZ4_BITMAP(mask_sort_alpha_up); - -const uint8_t _mask_sort_alpha_down[] = { -#include "mask_sort_alpha_down.lbm" -}; -STATIC_LZ4_BITMAP(mask_sort_alpha_down); - -const uint8_t _mask_sort_date_up[] = { -#include "mask_sort_date_up.lbm" -}; -STATIC_LZ4_BITMAP(mask_sort_date_up); - -const uint8_t _mask_sort_date_down[] = { -#include "mask_sort_date_down.lbm" -}; -STATIC_LZ4_BITMAP(mask_sort_date_down); - inline tmr10ms_t getTicks() { return g_tmr10ms; } constexpr coord_t MODEL_CELL_PADDING = 6; constexpr coord_t MODEL_SELECT_CELL_HEIGHT = 92; -constexpr int BUTTONS_HEIGHT = 30; +constexpr int BUTTONS_HEIGHT = 32; constexpr int MODEL_CELLS_PER_LINE = 2; #if LCD_W > LCD_H // Landscape @@ -69,188 +43,81 @@ constexpr int LAY_MARGIN = 5; constexpr coord_t MODEL_SELECT_CELL_WIDTH = (LCD_W - LABELS_WIDTH - (MODEL_CELLS_PER_LINE + 1) * MODEL_CELL_PADDING) / MODEL_CELLS_PER_LINE; +constexpr int LABELS_ROW = 0; +constexpr int MODELS_COL = 1; +constexpr int MODELS_ROW = 0; +constexpr int MODELS_ROW_CNT = 2; +constexpr int BUTTONS_ROW = 1; #else // Portrait constexpr int LAY_MARGIN = 8; constexpr int LABELS_HEIGHT = 140; constexpr coord_t MODEL_SELECT_CELL_WIDTH = (LCD_W - LAY_MARGIN - (MODEL_CELLS_PER_LINE + 1) * MODEL_CELL_PADDING) / MODEL_CELLS_PER_LINE; +constexpr int LABELS_ROW = 1; +constexpr int MODELS_COL = 0; +constexpr int MODELS_ROW = 0; +constexpr int MODELS_ROW_CNT = 1; +constexpr int BUTTONS_ROW = 2; #endif -class ToolbarButton : public Button -{ - public: - ToolbarButton(FormWindow *parent, const rect_t &rect, const uint8_t *bitmap, - std::function pressHandler = nullptr) : - Button(parent, rect, pressHandler, 0), _bitmap(bitmap) - { - lv_obj_set_style_border_width(lvobj, lv_dpx(2), 0); - lv_obj_set_style_border_opa(lvobj, LV_OPA_TRANSP, 0); - - lv_obj_set_style_border_color(lvobj, makeLvColor(COLOR_THEME_FOCUS), - LV_STATE_FOCUSED); - lv_obj_set_style_border_opa(lvobj, LV_OPA_100, LV_STATE_FOCUSED); - } - - inline bool getSelected() { return _selected; } - void setSelected(bool selected) - { - _selected = selected; - invalidate(); - } - - void setBitmap(const uint8_t *bitmap) - { - _bitmap = bitmap; - invalidate(); - } - - void paint(BitmapBuffer *dc) override - { - int width; - uint32_t bgColor = - !_selected ? COLOR_THEME_SECONDARY3 : COLOR_THEME_SECONDARY2; - auto bm = getBitmap(_bitmap, bgColor, COLOR_THEME_PRIMARY1, &width); - dc->drawScaledBitmap(bm, 2, 2, this->width() - 4, this->height() - 4); - delete bm; - } - - protected: - const uint8_t *_bitmap; - bool _selected = false; - - BitmapBuffer *getBitmap(const uint8_t *maskData, uint32_t bgColor, - uint32_t fgColor, int *width) - { - auto mask = BitmapBuffer::load8bitMask(maskData); - BitmapBuffer *newBm = - new BitmapBuffer(BMP_RGB565, mask->width(), mask->height()); - newBm->clear(bgColor); - newBm->drawMask(0, 0, mask, fgColor); - delete mask; - return newBm; - } -}; - -class ButtonHolder : public FormWindow -{ - public: - struct ButtonInfo { - ToolbarButton *button; - const uint8_t *states[2]; - int sortState; - }; - - ButtonHolder(Window *parent, const rect_t &rect) : FormWindow(parent, rect) - { - setHeight(25); - addButton(mask_sort_alpha_up, mask_sort_alpha_down); - addButton(mask_sort_date_up, mask_sort_date_down); - - // New label button - auto btn = new TextButton(this, rect_t{}, -#if LCD_W > LCD_H - STR_NEW, -#else - STR_NEW_LABEL, -#endif - [=]() { - if (_newLabelHandler) _newLabelHandler(); - return 0; - }); - - btn->padAll(lv_dpx(4)); - lv_obj_align(btn->getLvObj(), LV_ALIGN_RIGHT_MID, 0, 0); - - switch (modelslabels.sortOrder()) { - case NAME_ASC: - _buttons[0].button->setSelected(true); - break; - case NAME_DES: - _buttons[0].button->setSelected(true); - _buttons[0].sortState = 1; - _buttons[0].button->setBitmap(_buttons[0].states[1]); - break; - case DATE_ASC: - _buttons[1].button->setSelected(true); - break; - case DATE_DES: - _buttons[1].button->setSelected(true); - _buttons[1].sortState = 0; - _buttons[1].button->setBitmap(_buttons[1].states[1]); - break; - default: - break; - } - } - - inline void setPressHandler( - std::function pressHandler) - { - _pressHandler = std::move(pressHandler); - } - - inline void setNewLabelHandler(std::function newLabelHandler) - { - _newLabelHandler = std::move(newLabelHandler); - } - - void addButton(const uint8_t *state1Bm, const uint8_t *state2Bm) - { - int buttonNumber = _buttons.size(); - auto tb = new ToolbarButton( - this, {buttonNumber * (height() + 4), 0, height(), height()}, state1Bm); - tb->setPressHandler([=]() { - bool isSelected = tb->getSelected(); - - ButtonInfo *b = nullptr; - int buttonIndex = -1; - for (int i = 0; i < (int)_buttons.size(); i++) { - _buttons[i].button->setSelected(false); - if (tb == _buttons[i].button) { - b = &_buttons[i]; - buttonIndex = i; - } - } - tb->setSelected(true); - - if (isSelected && b != nullptr) { - if (b->sortState == 0) - b->sortState = 1; - else - b->sortState = 0; - - tb->setBitmap(b->states[b->sortState]); - } - - if (_pressHandler != nullptr && b != nullptr) - _pressHandler(buttonIndex, b); - - return 0; - }); - - ButtonInfo bi = {tb, state1Bm, state2Bm, 0}; - _buttons.push_back(bi); - } - - protected: - std::vector _buttons; - std::function _pressHandler; - std::function _newLabelHandler; -}; - class ModelButton : public Button { public: ModelButton(FormWindow *parent, const rect_t &rect, ModelCell *modelCell, std::function setSelected) : - Button(parent, rect), modelCell(modelCell) + Button(parent, rect, nullptr, 0, 0, etx_button_create), + modelCell(modelCell) { + padAll(0); + m_setSelected = std::move(setSelected); lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICK_FOCUSABLE); setWidth(MODEL_SELECT_CELL_WIDTH); setHeight(MODEL_SELECT_CELL_HEIGHT); + + LcdFlags bg_color = modelCell == modelslist.getCurrentModel() + ? COLOR_THEME_ACTIVE + : COLOR_THEME_PRIMARY2; + + coord_t w = width() - 8; + coord_t h = height() - 8; + + GET_FILENAME(filename, BITMAPS_PATH, modelCell->modelBitmap, ""); + const BitmapBuffer *bitmap = BitmapBuffer::loadBitmap(filename); + + if (bitmap) { + buffer = new BitmapBuffer(BMP_RGB565, w, h); + if (buffer) { + buffer->clear(bg_color); + buffer->drawScaledBitmap(bitmap, 0, 0, w, h); + delete bitmap; + + lv_obj_t *bm = lv_canvas_create(lvobj); + lv_obj_center(bm); + lv_canvas_set_buffer(bm, buffer->getData(), buffer->width(), + buffer->height(), LV_IMG_CF_TRUE_COLOR); + } + } + + if (!buffer) { + std::string errorMsg = "("; + errorMsg += STR_NO_PICTURE; + errorMsg += ")"; + new StaticText(this, {2, h / 2, w, 17}, errorMsg, 0, + CENTERED | COLOR_THEME_SECONDARY1 | FONT(XS)); + } + + auto name = + new StaticText(this, {2, 2, width() - 8, 17}, modelCell->modelName, 0, + CENTERED | COLOR_THEME_SECONDARY1); + name->setHeight(17); + name->setBackgroudOpacity(LV_OPA_80); + name->setBackgroundColor(bg_color); + name->padTop(-3); + + check(modelCell == modelslist.getCurrentModel()); } ~ModelButton() @@ -260,65 +127,6 @@ class ModelButton : public Button } } - void load() - { - const char *error = nullptr; - - delete buffer; - buffer = new BitmapBuffer(BMP_RGB565, width(), height()); - if (buffer == nullptr) { - return; - } - buffer->clear(COLOR_THEME_PRIMARY2); - - if (error) { - std::string errorMsg = "("; - errorMsg += STR_INVALID_MODEL; - errorMsg += ")"; - buffer->drawText(width() / 2, height() / 2, errorMsg.c_str(), - COLOR_THEME_SECONDARY1 | CENTERED); - } else { - GET_FILENAME(filename, BITMAPS_PATH, modelCell->modelBitmap, ""); - const BitmapBuffer *bitmap = BitmapBuffer::loadBitmap(filename); - if (bitmap) { - buffer->drawScaledBitmap(bitmap, 0, 0, width(), height()); - delete bitmap; - } else { - std::string errorMsg = "("; - errorMsg += STR_NO_PICTURE; - errorMsg += ")"; - buffer->drawText(width() / 2, 56, errorMsg.c_str(), - FONT(XXS) | COLOR_THEME_SECONDARY1 | CENTERED); - } - } - } - - void paint(BitmapBuffer *dc) override - { - if (!loaded) { // Load them on the fly - load(); - loaded = true; - } - FormField::paint(dc); - - if (buffer) dc->drawBitmap(0, 0, buffer); - - if (modelCell == modelslist.getCurrentModel()) { - dc->drawSolidFilledRect(0, 0, width(), 20, COLOR_THEME_ACTIVE); - } else { - dc->drawFilledRect(0, 0, width(), 20, SOLID, COLOR_THEME_PRIMARY2); - } - dc->drawSizedText(width() / 2, 2, modelCell->modelName, LEN_MODEL_NAME, - COLOR_THEME_SECONDARY1 | CENTERED); - - if (!hasFocus()) { - dc->drawSolidRect(0, 0, width(), height(), 1, COLOR_THEME_SECONDARY2); - } else { - dc->drawSolidRect(0, 0, width(), height(), 2, COLOR_THEME_FOCUS); - if (m_setSelected) m_setSelected(); - } - } - const char *modelFilename() { return modelCell->modelFilename; } ModelCell *getModelCell() const { return modelCell; } @@ -339,253 +147,270 @@ class ModelButton : public Button { setFocused(); Button::onClicked(); + if (m_setSelected) m_setSelected(); } }; //----------------------------------------------------------------------------- -class MyMenu : public Menu +class ModelsPageBody : public FormWindow { public: - using Menu::Menu; - void setFinishHandler(std::function finishHandler) + ModelsPageBody(Window *parent, const rect_t &rect) : FormWindow(parent, rect) { - _finishHandler = std::move(finishHandler); + padAll(0); +#if LCD_W > LCD_H + padRight(LAY_MARGIN); +#else + padLeft(LAY_MARGIN); +#endif + setFlexLayout(LV_FLEX_FLOW_ROW_WRAP, MODEL_CELL_PADDING); + padRow(MODEL_CELL_PADDING); } - void deleteLater(bool detach = true, bool trash = true) override + void update() { - Menu::deleteLater(detach, trash); - if (_finishHandler != nullptr) { - _finishHandler(); + clear(); + + ModelsVector models; + if (selectedLabels.size()) { + models = modelslabels.getModelsInLabels(selectedLabels); + } else { + models = modelslabels.getAllModels(); } + + // Used to work out which button to set focus to. + // Priority - + // current active model + // previously selected model + // first model in the list + ModelButton *firstButton = nullptr; + ModelButton *focusedButton = nullptr; + + for (auto &model : models) { + auto button = new ModelButton(this, rect_t{}, model, + [=]() { focusedModel = model; }); + + if (!firstButton) firstButton = button; + if (model == modelslist.getCurrentModel()) focusedButton = button; + if (model == focusedModel && !focusedButton) focusedButton = button; + + // Press Handler for Models + button->setPressHandler([=]() -> uint8_t { + if (model == focusedModel) { + if (g_eeGeneral.modelQuickSelect) + selectModel(model); + else + openMenu(); + } else { + focusedModel = model; + } + return 0; + }); + + // Long Press Handler for Models + button->setLongPressHandler([=]() -> uint8_t { + button->setFocused(); + focusedModel = model; + openMenu(); + return 0; + }); + } + + if (!focusedButton) focusedButton = firstButton; + + if (focusedButton) { + focusedButton->setFocused(); + focusedModel = focusedButton->getModelCell(); + } + } + + void setLabels(LabelsVector labels) + { + selectedLabels = labels; + update(); + } + + inline void setSortOrder(ModelsSortBy sortOrder) + { + modelslabels.setSortOrder(sortOrder); + update(); + } + + ModelsSortBy getSortOrder() const { return modelslabels.sortOrder(); } + + void setLblRefreshFunc(std::function fnc) + { + refreshLabels = std::move(fnc); } protected: - std::function _finishHandler = nullptr; -}; + ModelsSortBy _sortOrder; + bool isDirty = false; + bool refresh = false; + std::string selectedLabel; + LabelsVector selectedLabels; + ModelCell *focusedModel = nullptr; + std::function refreshLabels = nullptr; -//----------------------------------------------------------------------------- - -ModelsPageBody::ModelsPageBody(Window *parent, const rect_t &rect) : - FormWindow(parent, rect) -{ - setFlexLayout(LV_FLEX_FLOW_ROW_WRAP, MODEL_CELL_PADDING); - padRow(MODEL_CELL_PADDING); -} - -void ModelsPageBody::selectModel(ModelCell *model) -{ - // Don't need to check connection to receiver if re-selecting the active model - if (model != modelslist.getCurrentModel()) { - bool modelConnected = - TELEMETRY_STREAMING() && !g_eeGeneral.disableRssiPoweroffAlarm; - if (modelConnected) { - AUDIO_ERROR_MESSAGE(AU_MODEL_STILL_POWERED); - if (!confirmationDialog(STR_MODEL_STILL_POWERED, nullptr, false, []() { - tmr10ms_t startTime = getTicks(); - while (!TELEMETRY_STREAMING()) { - if (getTicks() - startTime > TELEMETRY_CHECK_DELAY10ms) break; - } - return !TELEMETRY_STREAMING() || - g_eeGeneral.disableRssiPoweroffAlarm; - })) { - return; // stop if connected but not confirmed - } + void openMenu() + { + Menu *menu = new Menu(this); + menu->setTitle(focusedModel->modelName); + if (g_eeGeneral.modelQuickSelect || + focusedModel != modelslist.getCurrentModel()) { + menu->addLine(STR_SELECT_MODEL, [=]() { selectModel(focusedModel); }); + } + menu->addLine(STR_DUPLICATE_MODEL, [=]() { duplicateModel(focusedModel); }); + menu->addLine(STR_LABEL_MODEL, [=]() { editLabels(focusedModel); }); + menu->addLine(STR_SAVE_TEMPLATE, [=]() { saveAsTemplate(focusedModel); }); + if (focusedModel != modelslist.getCurrentModel()) { + menu->addLine(STR_DELETE_MODEL, [=]() { deleteModel(focusedModel); }); } } - // Exit to main view - auto w = Layer::back(); - if (w) w->onCancel(); + void selectModel(ModelCell *model) + { + // Don't need to check connection to receiver if re-selecting the active + // model + if (model != modelslist.getCurrentModel()) { + bool modelConnected = + TELEMETRY_STREAMING() && !g_eeGeneral.disableRssiPoweroffAlarm; + if (modelConnected) { + AUDIO_ERROR_MESSAGE(AU_MODEL_STILL_POWERED); + if (!confirmationDialog(STR_MODEL_STILL_POWERED, nullptr, false, []() { + tmr10ms_t startTime = getTicks(); + while (!TELEMETRY_STREAMING()) { + if (getTicks() - startTime > TELEMETRY_CHECK_DELAY10ms) break; + } + return !TELEMETRY_STREAMING() || + g_eeGeneral.disableRssiPoweroffAlarm; + })) { + return; // stop if connected but not confirmed + } + } + } - // Skip reloading model if re-selecting the active model - if (model != modelslist.getCurrentModel()) { - // store changes (if any) and load selected model - storageFlushCurrentModel(); - storageCheck(true); - memcpy(g_eeGeneral.currModelFilename, model->modelFilename, - LEN_MODEL_FILENAME); + // Exit to main view + auto w = Layer::back(); + if (w) w->onCancel(); - loadModel(g_eeGeneral.currModelFilename, true); - modelslist.setCurrentModel(model); + // Skip reloading model if re-selecting the active model + if (model != modelslist.getCurrentModel()) { + // store changes (if any) and load selected model + storageFlushCurrentModel(); + storageCheck(true); + memcpy(g_eeGeneral.currModelFilename, model->modelFilename, + LEN_MODEL_FILENAME); - storageDirty(EE_GENERAL); - storageCheck(true); + loadModel(g_eeGeneral.currModelFilename, true); + modelslist.setCurrentModel(model); + + storageDirty(EE_GENERAL); + storageCheck(true); + } } -} -void ModelsPageBody::duplicateModel(ModelCell *model) -{ - new ConfirmDialog( - parent, STR_DUPLICATE_MODEL, - std::string(model->modelName, sizeof(model->modelName)).c_str(), [=] { - storageFlushCurrentModel(); - storageCheck(true); + void duplicateModel(ModelCell *model) + { + new ConfirmDialog( + parent, STR_DUPLICATE_MODEL, + std::string(model->modelName, sizeof(model->modelName)).c_str(), [=] { + storageFlushCurrentModel(); + storageCheck(true); - char duplicatedFilename[LEN_MODEL_FILENAME + 1]; - memcpy(duplicatedFilename, model->modelFilename, - sizeof(duplicatedFilename)); - if (findNextFileIndex(duplicatedFilename, LEN_MODEL_FILENAME, - MODELS_PATH)) { - sdCopyFile(model->modelFilename, MODELS_PATH, duplicatedFilename, - MODELS_PATH); - // Make a new model which is a copy of the selected one, set the same - // labels - auto new_model = modelslist.addModel(duplicatedFilename, true, model); - for (const auto &lbl : modelslabels.getLabelsByModel(model)) { - modelslabels.addLabelToModel(lbl, new_model); + char duplicatedFilename[LEN_MODEL_FILENAME + 1]; + memcpy(duplicatedFilename, model->modelFilename, + sizeof(duplicatedFilename)); + if (findNextFileIndex(duplicatedFilename, LEN_MODEL_FILENAME, + MODELS_PATH)) { + sdCopyFile(model->modelFilename, MODELS_PATH, duplicatedFilename, + MODELS_PATH); + // Make a new model which is a copy of the selected one, set the + // same labels + auto new_model = + modelslist.addModel(duplicatedFilename, true, model); + for (const auto &lbl : modelslabels.getLabelsByModel(model)) { + modelslabels.addLabelToModel(lbl, new_model); + } + update(); + } else { + TRACE("ModelsListError: Invalid File"); } + }); + } + + void deleteModel(ModelCell *model) + { + new ConfirmDialog( + parent, STR_DELETE_MODEL, + std::string(model->modelName, sizeof(model->modelName)).c_str(), [=] { + modelslist.removeModel(model); + if (refreshLabels != nullptr) refreshLabels(); + + update(); + }); + } + + void editLabels(ModelCell *model) + { + auto labels = modelslabels.getLabels(); + + // dont display menu if there will be no labels + if (labels.size()) { + auto menu = new Menu(getParent(), true); + menu->setTitle(model->modelName); + menu->setCloseHandler([=]() { + if (isDirty) { + isDirty = false; update(); - } else { - TRACE("ModelsListError: Invalid File"); } }); -} -void ModelsPageBody::deleteModel(ModelCell *model) -{ - new ConfirmDialog( - parent, STR_DELETE_MODEL, - std::string(model->modelName, sizeof(model->modelName)).c_str(), [=] { - modelslist.removeModel(model); - if (refreshLabels != nullptr) refreshLabels(); + for (auto &label : modelslabels.getLabels()) { + menu->addLineBuffered( + label, + [=]() { + if (!modelslabels.isLabelSelected(label, model)) + modelslabels.addLabelToModel(label, model, true); + else + modelslabels.removeLabelFromModel(label, model, true); + isDirty = true; + if (refreshLabels != nullptr) refreshLabels(); + }, + [=]() { return modelslabels.isLabelSelected(label, model); }); + } + menu->updateLines(); + } + } - update(); - }); -} - -void ModelsPageBody::saveAsTemplate(ModelCell *model) -{ - new ConfirmDialog( - parent, STR_SAVE_TEMPLATE, - std::string(model->modelName, sizeof(model->modelName)).c_str(), [=] { - storageDirty(EE_MODEL); - storageCheck(true); - constexpr size_t size = sizeof(model->modelName) + sizeof(YAML_EXT); - char modelName[size]; - snprintf(modelName, size, "%s%s", model->modelName, YAML_EXT); - char templatePath[FF_MAX_LFN]; - snprintf(templatePath, FF_MAX_LFN, "%s%c%s", PERS_TEMPL_PATH, '/', - modelName); - sdCheckAndCreateDirectory(TEMPLATES_PATH); - sdCheckAndCreateDirectory(PERS_TEMPL_PATH); - if (isFileAvailable(templatePath)) { - new ConfirmDialog(parent, STR_FILE_EXISTS, STR_ASK_OVERWRITE, [=] { + void saveAsTemplate(ModelCell *model) + { + new ConfirmDialog( + parent, STR_SAVE_TEMPLATE, + std::string(model->modelName, sizeof(model->modelName)).c_str(), [=] { + storageDirty(EE_MODEL); + storageCheck(true); + constexpr size_t size = sizeof(model->modelName) + sizeof(YAML_EXT); + char modelName[size]; + snprintf(modelName, size, "%s%s", model->modelName, YAML_EXT); + char templatePath[FF_MAX_LFN]; + snprintf(templatePath, FF_MAX_LFN, "%s%c%s", PERS_TEMPL_PATH, '/', + modelName); + sdCheckAndCreateDirectory(TEMPLATES_PATH); + sdCheckAndCreateDirectory(PERS_TEMPL_PATH); + if (isFileAvailable(templatePath)) { + new ConfirmDialog(parent, STR_FILE_EXISTS, STR_ASK_OVERWRITE, [=] { + sdCopyFile(model->modelFilename, MODELS_PATH, modelName, + PERS_TEMPL_PATH); + }); + } else { sdCopyFile(model->modelFilename, MODELS_PATH, modelName, PERS_TEMPL_PATH); - }); - } else { - sdCopyFile(model->modelFilename, MODELS_PATH, modelName, - PERS_TEMPL_PATH); - } - }); -} - -void ModelsPageBody::editLabels(ModelCell *model) -{ - auto labels = modelslabels.getLabels(); - - // dont display menu if there will be no labels - if (labels.size()) { - MyMenu *menu = new MyMenu(getParent(), true); - menu->setTitle(model->modelName); - menu->setFinishHandler([=]() { - if (isDirty) { - isDirty = false; - update(); - } - }); - - for (auto &label : modelslabels.getLabels()) { - menu->addLineBuffered( - label, - [=]() { - if (!modelslabels.isLabelSelected(label, model)) - modelslabels.addLabelToModel(label, model, true); - else - modelslabels.removeLabelFromModel(label, model, true); - isDirty = true; - if (refreshLabels != nullptr) refreshLabels(); - }, - [=]() { return modelslabels.isLabelSelected(label, model); }); - } - menu->updateLines(); + } + }); } -} - -void ModelsPageBody::openMenu() -{ - Menu *menu = new Menu(this); - menu->setTitle(focusedModel->modelName); - if (g_eeGeneral.modelQuickSelect || - focusedModel != modelslist.getCurrentModel()) { - menu->addLine(STR_SELECT_MODEL, [=]() { selectModel(focusedModel); }); - } - menu->addLine(STR_DUPLICATE_MODEL, [=]() { duplicateModel(focusedModel); }); - menu->addLine(STR_LABEL_MODEL, [=]() { editLabels(focusedModel); }); - menu->addLine(STR_SAVE_TEMPLATE, [=]() { saveAsTemplate(focusedModel); }); - if (focusedModel != modelslist.getCurrentModel()) { - menu->addLine(STR_DELETE_MODEL, [=]() { deleteModel(focusedModel); }); - } -} - -void ModelsPageBody::update() -{ - clear(); - - ModelsVector models; - if (selectedLabels.size()) { - models = modelslabels.getModelsInLabels(selectedLabels); - } else { - models = modelslabels.getAllModels(); - } - - // Used to work out which button to set focus to. - // Priority - - // current active model - // previously selected model - // first model in the list - ModelButton *firstButton = nullptr; - ModelButton *focusedButton = nullptr; - - for (auto &model : models) { - auto button = - new ModelButton(this, rect_t{}, model, [=]() { focusedModel = model; }); - - if (!firstButton) firstButton = button; - if (model == modelslist.getCurrentModel()) focusedButton = button; - if (model == focusedModel && !focusedButton) focusedButton = button; - - // Press Handler for Models - button->setPressHandler([=]() -> uint8_t { - if (model == focusedModel) { - if (g_eeGeneral.modelQuickSelect) - selectModel(model); - else - openMenu(); - } else { - focusedModel = model; - } - return 0; - }); - - // Long Press Handler for Models - button->setLongPressHandler([=]() -> uint8_t { - button->setFocused(); - focusedModel = model; - openMenu(); - return 0; - }); - } - - if (!focusedButton) focusedButton = firstButton; - - if (focusedButton) { - focusedButton->setFocused(); - focusedModel = focusedButton->getModelCell(); - } -} +}; //----------------------------------------------------------------------------- @@ -619,13 +444,13 @@ class LabelDialog : public Dialog deleteLater(); return 0; }); - btn->setWidth(LV_DPI_DEF); + btn->setWidth(100); btn = new TextButton(box, rect_t{}, STR_CANCEL, [=]() { deleteLater(); return 0; }); - btn->setWidth(LV_DPI_DEF); + btn->setWidth(100); content->setWidth(LCD_W * 0.8); content->updateSize(); @@ -785,8 +610,11 @@ void ModelLabelsWindow::buildHead(PageHeader *hdr) setTitle(); // new model button - auto btn = new TextButton(hdr, rect_t{}, STR_NEW_MODEL, [=]() { - newModel(); + auto btn = new TextButton(hdr, rect_t{}, STR_NEW, [=]() { + auto menu = new Menu(this); + menu->setTitle(STR_CREATE_NEW); + menu->addLine(STR_NEW_MODEL, [=]() { newModel(); }); + menu->addLine(STR_NEW_LABEL, [=]() { newLabel(); }); return 0; }); @@ -810,70 +638,49 @@ static const lv_coord_t row_dsc[] = {LV_GRID_FR(1), LABELS_HEIGHT, void ModelLabelsWindow::buildBody(FormWindow *window) { - auto win_obj = window->getLvObj(); + window->padAll(LAY_MARGIN); + window->padRow(LAY_MARGIN); + window->padColumn(LAY_MARGIN); + window->padLeft(0); + window->padRight(0); - lv_obj_set_grid_dsc_array(win_obj, col_dsc, row_dsc); + lv_obj_set_grid_dsc_array(window->getLvObj(), col_dsc, row_dsc); // Models List mdlselector = new ModelsPageBody(window, rect_t{}); mdlselector->setLblRefreshFunc([=]() { labelRefreshRequest(); }); auto mdl_obj = mdlselector->getLvObj(); + lv_obj_set_grid_cell(mdl_obj, LV_GRID_ALIGN_STRETCH, MODELS_COL, 1, + LV_GRID_ALIGN_STRETCH, MODELS_ROW, MODELS_ROW_CNT); // Labels - lblselector = new ListBox(window, rect_t{}, getLabels()); + auto box = new Window(window, rect_t{}); + box->padAll(0); + box->padLeft(LAY_MARGIN); +#if LCD_H > LCD_W + box->padRight(LAY_MARGIN); +#endif + lv_obj_set_grid_cell(box->getLvObj(), LV_GRID_ALIGN_STRETCH, 0, 1, + LV_GRID_ALIGN_STRETCH, LABELS_ROW, 1); + lblselector = new ListBox(box, rect_t{0, 0, LV_PCT(100), LV_PCT(100)}, getLabels()); auto lbl_obj = lblselector->getLvObj(); // Sort Buttons - auto btnh = new ButtonHolder(window, rect_t{}); - btnh->setNewLabelHandler(std::bind(&ModelLabelsWindow::newLabel, this)); - auto buth_obj = btnh->getLvObj(); - - window->padAll(LAY_MARGIN); - window->padRow(LAY_MARGIN); - window->padColumn(LAY_MARGIN); - -#if LCD_W > LCD_H - // Labels top left - lv_obj_set_grid_cell(lbl_obj, LV_GRID_ALIGN_STRETCH, 0, 1, - LV_GRID_ALIGN_STRETCH, 0, 1); - - // Buttons bottom left - lv_obj_set_grid_cell(mdl_obj, LV_GRID_ALIGN_STRETCH, 1, 1, - LV_GRID_ALIGN_STRETCH, 0, 2); - lv_obj_set_width(mdl_obj, lv_pct(100)); - window->padRight(0); - - // Models right - lv_obj_set_grid_cell(buth_obj, LV_GRID_ALIGN_STRETCH, 0, 1, - LV_GRID_ALIGN_STRETCH, 1, 1); -#else - // Models top - lv_obj_set_grid_cell(mdl_obj, LV_GRID_ALIGN_STRETCH, 0, 1, - LV_GRID_ALIGN_STRETCH, 0, 1); - lv_obj_set_width(mdl_obj, lv_pct(100)); - - // Labels middle - lv_obj_set_grid_cell(lbl_obj, LV_GRID_ALIGN_STRETCH, 0, 1, - LV_GRID_ALIGN_STRETCH, 1, 1); - - // Buttons bottom - lv_obj_set_grid_cell(buth_obj, LV_GRID_ALIGN_STRETCH, 0, 1, - LV_GRID_ALIGN_STRETCH, 2, 1); -#endif + box = new Window(window, rect_t{}); + box->padAll(0); + box->padLeft(LAY_MARGIN); + box->padRight(LAY_MARGIN); + lv_obj_set_grid_cell(box->getLvObj(), LV_GRID_ALIGN_STRETCH, 0, 1, + LV_GRID_ALIGN_STRETCH, BUTTONS_ROW, 1); + auto sortOrder = new Choice( + box, {}, STR_SORT_ORDERS, NAME_ASC, DATE_DES, + [=]() { return mdlselector->getSortOrder(); }, + [=](int newValue) { mdlselector->setSortOrder((ModelsSortBy)newValue); }); + sortOrder->setMenuTitle(STR_SORT_MODELS_BY); lv_obj_update_layout(mdl_obj); lblselector->setColumnWidth(0, lv_obj_get_content_width(lbl_obj)); - btnh->setPressHandler([=](int index, ButtonHolder::ButtonInfo *button) { - if (index == 0) { // alpha - sort = button->sortState == 0 ? NAME_ASC : NAME_DES; - } else { - sort = button->sortState == 0 ? DATE_ASC : DATE_DES; - } - // Update the list asynchronously - mdlselector->setSortOrder(sort); - }); - lblselector->setMultiSelect(true); lblselector->setSelected(modelslabels.filteredLabels()); updateFilteredLabels(modelslabels.filteredLabels(), false); diff --git a/radio/src/gui/colorlcd/model_select.h b/radio/src/gui/colorlcd/model_select.h index 737c42df1..0ec1ffc01 100644 --- a/radio/src/gui/colorlcd/model_select.h +++ b/radio/src/gui/colorlcd/model_select.h @@ -19,59 +19,14 @@ * GNU General Public License for more details. */ -#ifndef _MODEL_SELECT_H_ -#define _MODEL_SELECT_H_ - -#include -#include +#pragma once #include "libopenui.h" #include "listbox.h" #include "storage/modelslist.h" #include "tabsgroup.h" -class ModelButton; - -class ModelsPageBody : public FormWindow -{ - public: - ModelsPageBody(Window *parent, const rect_t &rect); - - void update(); - - void setLabels(LabelsVector labels) - { - selectedLabels = labels; - update(); - } - - inline void setSortOrder(ModelsSortBy sortOrder) - { - modelslabels.setSortOrder(sortOrder); - update(); - } - - void setLblRefreshFunc(std::function fnc) - { - refreshLabels = std::move(fnc); - } - - protected: - ModelsSortBy _sortOrder; - bool isDirty = false; - bool refresh = false; - std::string selectedLabel; - LabelsVector selectedLabels; - ModelCell *focusedModel = nullptr; - std::function refreshLabels = nullptr; - - void openMenu(); - void selectModel(ModelCell *model); - void duplicateModel(ModelCell *model); - void deleteModel(ModelCell *model); - void editLabels(ModelCell *model); - void saveAsTemplate(ModelCell *model); -}; +class ModelsPageBody; class ModelLabelsWindow : public Page { @@ -112,5 +67,3 @@ class ModelLabelsWindow : public Page void onPressPGDN() override; #endif }; - -#endif // _MODEL_SELECT_H_ diff --git a/radio/src/translations.cpp b/radio/src/translations.cpp index d517ff416..612e1e5b7 100644 --- a/radio/src/translations.cpp +++ b/radio/src/translations.cpp @@ -1084,3 +1084,9 @@ const char STR_SURFACE_NAMES1[] = TR_SURFACE_NAMES1; const char STR_SELECT_MENU_ALL[] = TR_SELECT_MENU_ALL; const char STR_SELECT_MENU_CLR[] = TR_SELECT_MENU_CLR; const char STR_SELECT_MENU_INV[] = TR_SELECT_MENU_INV; + +#if defined(COLORLCD) +ISTR(SORT_ORDERS); +const char STR_SORT_MODELS_BY[] = TR_SORT_MODELS_BY; +const char STR_CREATE_NEW[] = TR_CREATE_NEW; +#endif diff --git a/radio/src/translations.h b/radio/src/translations.h index c58cc16c0..54fa861fa 100644 --- a/radio/src/translations.h +++ b/radio/src/translations.h @@ -1267,5 +1267,11 @@ extern const char STR_SELECT_MENU_ALL[]; extern const char STR_SELECT_MENU_CLR[]; extern const char STR_SELECT_MENU_INV[]; +#if defined(COLORLCD) +extern const char* const STR_SORT_ORDERS[]; +extern const char STR_SORT_MODELS_BY[]; +extern const char STR_CREATE_NEW[]; +#endif + #endif // _TRANSLATIONS_H_ diff --git a/radio/src/translations/cn.h b/radio/src/translations/cn.h index d70d00ec4..afc9e1f41 100644 --- a/radio/src/translations/cn.h +++ b/radio/src/translations/cn.h @@ -1240,3 +1240,7 @@ #define TR_SELECT_MENU_ALL "All" #define TR_SELECT_MENU_CLR "Clear" #define TR_SELECT_MENU_INV "Invert" + +#define TR_SORT_ORDERS "名称 A-Z","名称 Z-A","不常用","常用" +#define TR_SORT_MODELS_BY "排序" +#define TR_CREATE_NEW "新建" diff --git a/radio/src/translations/cz.h b/radio/src/translations/cz.h index 262504af2..cf2c91e47 100644 --- a/radio/src/translations/cz.h +++ b/radio/src/translations/cz.h @@ -1260,3 +1260,7 @@ #define TR_SELECT_MENU_ALL "All" #define TR_SELECT_MENU_CLR "Clear" #define TR_SELECT_MENU_INV "Invert" + +#define TR_SORT_ORDERS "Název A-Z","Název Z-A","Nejméne používané","Nejvíce používané" +#define TR_SORT_MODELS_BY "Seřadit modely podle" +#define TR_CREATE_NEW "Vytvořit" diff --git a/radio/src/translations/da.h b/radio/src/translations/da.h index 346ced2c8..a56c97e78 100644 --- a/radio/src/translations/da.h +++ b/radio/src/translations/da.h @@ -1265,3 +1265,7 @@ #define TR_SELECT_MENU_ALL "Alle" #define TR_SELECT_MENU_CLR "Slet" #define TR_SELECT_MENU_INV "Invers" + +#define TR_SORT_ORDERS "Navn A-Z","Navn Z-A","Sidst anvendt","Mest anvendt" +#define TR_SORT_MODELS_BY "Sorter modeller" +#define TR_CREATE_NEW "Opret" diff --git a/radio/src/translations/de.h b/radio/src/translations/de.h index ee02509a0..306ce463a 100644 --- a/radio/src/translations/de.h +++ b/radio/src/translations/de.h @@ -1256,3 +1256,7 @@ #define TR_SELECT_MENU_ALL "All" #define TR_SELECT_MENU_CLR "Clear" #define TR_SELECT_MENU_INV "Invert" + +#define TR_SORT_ORDERS "Name A-Z","Name Z-A","Wenig benutzt","Meist benutzt" +#define TR_SORT_MODELS_BY "Modelle sortieren nach" +#define TR_CREATE_NEW "Erstelle" diff --git a/radio/src/translations/en.h b/radio/src/translations/en.h index f1ff657ef..f9a94bdac 100644 --- a/radio/src/translations/en.h +++ b/radio/src/translations/en.h @@ -1248,3 +1248,7 @@ #define TR_SELECT_MENU_ALL "All" #define TR_SELECT_MENU_CLR "Clear" #define TR_SELECT_MENU_INV "Invert" + +#define TR_SORT_ORDERS "Name A-Z","Name Z-A","Least used","Most used" +#define TR_SORT_MODELS_BY "Sort models by" +#define TR_CREATE_NEW "Create" diff --git a/radio/src/translations/es.h b/radio/src/translations/es.h index a0bf07739..ce94b8bef 100644 --- a/radio/src/translations/es.h +++ b/radio/src/translations/es.h @@ -1253,3 +1253,7 @@ #define TR_SELECT_MENU_ALL "All" #define TR_SELECT_MENU_CLR "Clear" #define TR_SELECT_MENU_INV "Invert" + +#define TR_SORT_ORDERS "Name A-Z","Name Z-A","Least used","Most used" +#define TR_SORT_MODELS_BY "Sort models by" +#define TR_CREATE_NEW "Create" diff --git a/radio/src/translations/fi.h b/radio/src/translations/fi.h index afd546aca..357981532 100644 --- a/radio/src/translations/fi.h +++ b/radio/src/translations/fi.h @@ -1269,3 +1269,7 @@ #define TR_SELECT_MENU_ALL "All" #define TR_SELECT_MENU_CLR "Clear" #define TR_SELECT_MENU_INV "Invert" + +#define TR_SORT_ORDERS "Name A-Z","Name Z-A","Least used","Most used" +#define TR_SORT_MODELS_BY "Sort models by" +#define TR_CREATE_NEW "Create" diff --git a/radio/src/translations/fr.h b/radio/src/translations/fr.h index f00e329a2..0a67be333 100644 --- a/radio/src/translations/fr.h +++ b/radio/src/translations/fr.h @@ -1270,3 +1270,7 @@ #define TR_SELECT_MENU_ALL "All" #define TR_SELECT_MENU_CLR "Clear" #define TR_SELECT_MENU_INV "Invert" + +#define TR_SORT_ORDERS "Nom A-Z","Nom Z-A","Dernier utilisé","Couramment utilisé" +#define TR_SORT_MODELS_BY "Trier modèles par" +#define TR_CREATE_NEW "Créer" diff --git a/radio/src/translations/he.h b/radio/src/translations/he.h index 4bd9258e5..24d18f11b 100644 --- a/radio/src/translations/he.h +++ b/radio/src/translations/he.h @@ -96,6 +96,7 @@ #define TR_SF_FAILSAFE "SetFailsafe" #define TR_SF_RANGE_CHECK "RangeCheck" #define TR_SF_MOD_BIND "ModuleBind" +#define TR_SF_RGBLEDS "RGB leds" #define TR_SOUND "Play Sound" #define TR_PLAY_TRACK "Play Track" @@ -1256,3 +1257,7 @@ #define TR_SELECT_MENU_ALL "All" #define TR_SELECT_MENU_CLR "Clear" #define TR_SELECT_MENU_INV "Invert" + +#define TR_SORT_ORDERS "A-Z סידור","Z-A סידור","יותר בשימוש","פחות בשימוש" +#define TR_SORT_MODELS_BY "סדר תצוגה" +#define TR_CREATE_NEW "יצירה חדשה" diff --git a/radio/src/translations/it.h b/radio/src/translations/it.h index 97eb4247a..9a5181d37 100644 --- a/radio/src/translations/it.h +++ b/radio/src/translations/it.h @@ -1248,3 +1248,7 @@ #define TR_SELECT_MENU_ALL "All" #define TR_SELECT_MENU_CLR "Clear" #define TR_SELECT_MENU_INV "Invert" + +#define TR_SORT_ORDERS "Nome A-Z","Nome Z-A","Meno utilizzato","Più utilizzato" +#define TR_SORT_MODELS_BY "Ordina modelli per" +#define TR_CREATE_NEW "Crea" diff --git a/radio/src/translations/jp.h b/radio/src/translations/jp.h index 4636ce60d..5fa6abb79 100644 --- a/radio/src/translations/jp.h +++ b/radio/src/translations/jp.h @@ -1243,3 +1243,7 @@ #define TR_SELECT_MENU_ALL "All" #define TR_SELECT_MENU_CLR "Clear" #define TR_SELECT_MENU_INV "Invert" + +#define TR_SORT_ORDERS "名称 A-Z順","名称 Z-A順","利用の少ない順","利用の多い順" +#define TR_SORT_MODELS_BY "モデルをソート" +#define TR_CREATE_NEW "作成" diff --git a/radio/src/translations/nl.h b/radio/src/translations/nl.h index 093beb2f6..d613fb11f 100644 --- a/radio/src/translations/nl.h +++ b/radio/src/translations/nl.h @@ -1260,3 +1260,7 @@ #define TR_SELECT_MENU_ALL "All" #define TR_SELECT_MENU_CLR "Clear" #define TR_SELECT_MENU_INV "Invert" + +#define TR_SORT_ORDERS "Name A-Z","Name Z-A","Least used","Most used" +#define TR_SORT_MODELS_BY "Sort models by" +#define TR_CREATE_NEW "Create" diff --git a/radio/src/translations/pl.h b/radio/src/translations/pl.h index 9c35bb6ca..55be56494 100644 --- a/radio/src/translations/pl.h +++ b/radio/src/translations/pl.h @@ -1247,3 +1247,7 @@ #define TR_SELECT_MENU_ALL "All" #define TR_SELECT_MENU_CLR "Clear" #define TR_SELECT_MENU_INV "Invert" + +#define TR_SORT_ORDERS "Nazwa A-Z","Nazwa Z-A","Najrzadziej użyty","Najczęściej użyty" +#define TR_SORT_MODELS_BY "Sortuj modele" +#define TR_CREATE_NEW "Utwórz" diff --git a/radio/src/translations/pt.h b/radio/src/translations/pt.h index 75f5cd68e..33675ef3b 100644 --- a/radio/src/translations/pt.h +++ b/radio/src/translations/pt.h @@ -1251,3 +1251,7 @@ #define TR_SELECT_MENU_ALL "All" #define TR_SELECT_MENU_CLR "Clear" #define TR_SELECT_MENU_INV "Invert" + +#define TR_SORT_ORDERS "Nome A-Z","Nome Z-A","Menos usado","Mais usado" +#define TR_SORT_MODELS_BY "Ordenar modelos por" +#define TR_CREATE_NEW "Criar" diff --git a/radio/src/translations/ru.h b/radio/src/translations/ru.h index 987c4a1b7..c3ebde84d 100644 --- a/radio/src/translations/ru.h +++ b/radio/src/translations/ru.h @@ -1250,3 +1250,7 @@ #define TR_SELECT_MENU_ALL "Все" #define TR_SELECT_MENU_CLR "Чисто" #define TR_SELECT_MENU_INV "Инверт" + +#define TR_SORT_ORDERS "Имя A-Z","Имя Z-A","Редко использ","Часто использ" +#define TR_SORT_MODELS_BY "Сортировка моделей" +#define TR_CREATE_NEW "Создать" diff --git a/radio/src/translations/se.h b/radio/src/translations/se.h index 33292f387..6f24942f8 100644 --- a/radio/src/translations/se.h +++ b/radio/src/translations/se.h @@ -1287,3 +1287,7 @@ #define TR_SELECT_MENU_ALL "All" #define TR_SELECT_MENU_CLR "Clear" #define TR_SELECT_MENU_INV "Invert" + +#define TR_SORT_ORDERS "Namn A-Z","Namn Z-A","Minst använda","Mest använda" +#define TR_SORT_MODELS_BY "Sortera modeller" +#define TR_CREATE_NEW "Skapa" diff --git a/radio/src/translations/tw.h b/radio/src/translations/tw.h index 9dd371193..4a97d4792 100644 --- a/radio/src/translations/tw.h +++ b/radio/src/translations/tw.h @@ -1245,3 +1245,7 @@ #define TR_SELECT_MENU_ALL "All" #define TR_SELECT_MENU_CLR "Clear" #define TR_SELECT_MENU_INV "Invert" + +#define TR_SORT_ORDERS "名稱 A-Z","名稱 Z-A","不常用","常用" +#define TR_SORT_MODELS_BY "排序" +#define TR_CREATE_NEW "新建"