diff --git a/companion/src/firmwares/edgetx/yaml_generalsettings.cpp b/companion/src/firmwares/edgetx/yaml_generalsettings.cpp index cd5b55de86d..8718f83c0ed 100644 --- a/companion/src/firmwares/edgetx/yaml_generalsettings.cpp +++ b/companion/src/firmwares/edgetx/yaml_generalsettings.cpp @@ -213,6 +213,9 @@ Node convert::encode(const GeneralSettings& rhs) node["varioRange"] = rhs.varioRange * 15; node["varioRepeat"] = rhs.varioRepeat; node["backgroundVolume"] = rhs.backgroundVolume + 2; + if (Boards::getCapability(getCurrentFirmware()->getBoard(), Board::HasColorLcd)) { + node["modelQuickSelect"] = (int)rhs.modelQuickSelect; + } Node serialPort; for (int i = 0; i < GeneralSettings::SP_COUNT; i++) { @@ -408,6 +411,7 @@ bool convert::decode(const Node& node, GeneralSettings& rhs) node["varioRange"] >> ifactor(rhs.varioRange, 15); node["varioRepeat"] >> rhs.varioRepeat; node["backgroundVolume"] >> ioffset_int(rhs.backgroundVolume, 2); + node["modelQuickSelect"] >> rhs.modelQuickSelect; // depreciated v2.7 replaced by serialPort if (node["auxSerialMode"]) { diff --git a/companion/src/firmwares/generalsettings.h b/companion/src/firmwares/generalsettings.h index 782f65f27bd..0a991e930d9 100644 --- a/companion/src/firmwares/generalsettings.h +++ b/companion/src/firmwares/generalsettings.h @@ -255,6 +255,7 @@ class GeneralSettings { bool serialPower[SP_COUNT]; int antennaMode; unsigned int backlightColor; + bool modelQuickSelect; CustomFunctionData customFn[CPN_MAX_SPECIAL_FUNCTIONS]; char switchName[CPN_MAX_SWITCHES][HARDWARE_NAME_LEN + 1]; unsigned int switchConfig[CPN_MAX_SWITCHES]; diff --git a/companion/src/generaledit/generalsetup.cpp b/companion/src/generaledit/generalsetup.cpp index 5cad2606dc1..9ff6f2beece 100644 --- a/companion/src/generaledit/generalsetup.cpp +++ b/companion/src/generaledit/generalsetup.cpp @@ -468,6 +468,13 @@ void GeneralSetupPanel::setValues() // TODO: only if ACCESS available?? ui->registrationId->setText(generalSettings.registrationId); + + if (Boards::getCapability(firmware->getBoard(), Board::HasColorLcd)) { + ui->modelQuickSelect_CB->setChecked(generalSettings.modelQuickSelect); + } else { + ui->label_modelQuickSelect->hide(); + ui->modelQuickSelect_CB->hide(); + } } void GeneralSetupPanel::on_faimode_CB_stateChanged(int) @@ -799,3 +806,9 @@ void GeneralSetupPanel::stickReverseEdited() generalSettings.stickReverse = ((int)ui->stickReverse1->isChecked()) | ((int)ui->stickReverse2->isChecked()<<1) | ((int)ui->stickReverse3->isChecked()<<2) | ((int)ui->stickReverse4->isChecked()<<3); emit modified(); } + +void GeneralSetupPanel::on_modelQuickSelect_CB_stateChanged(int) +{ + generalSettings.modelQuickSelect = ui->modelQuickSelect_CB->isChecked(); + emit modified(); +} diff --git a/companion/src/generaledit/generalsetup.h b/companion/src/generaledit/generalsetup.h index 7afd5495357..a36ce536805 100644 --- a/companion/src/generaledit/generalsetup.h +++ b/companion/src/generaledit/generalsetup.h @@ -93,6 +93,8 @@ class GeneralSetupPanel : public GeneralPanel void on_pwrOnDelay_valueChanged(int); void on_pwrOffDelay_valueChanged(int); + void on_modelQuickSelect_CB_stateChanged(int); + private: Ui::GeneralSetup *ui; diff --git a/companion/src/generaledit/generalsetup.ui b/companion/src/generaledit/generalsetup.ui index fac9be1da7d..2e00cc8f323 100644 --- a/companion/src/generaledit/generalsetup.ui +++ b/companion/src/generaledit/generalsetup.ui @@ -810,7 +810,7 @@ - + Qt::Vertical @@ -1155,6 +1155,23 @@ p, li { white-space: pre-wrap; } + + + + Model quick select + + + + + + + Enable this to quickly change model on the model select page (a long press can then be used to open the model edit menu). + + + + + + diff --git a/radio/src/datastructs_private.h b/radio/src/datastructs_private.h index eef70443b05..22bfd48ba1f 100644 --- a/radio/src/datastructs_private.h +++ b/radio/src/datastructs_private.h @@ -769,7 +769,7 @@ PACK(struct TrainerData { NOBACKUP(char switchNames[STORAGE_NUM_SWITCHES][LEN_SWITCH_NAME] SKIP); \ NOBACKUP(char anaNames[NUM_STICKS + STORAGE_NUM_POTS + STORAGE_NUM_SLIDERS][LEN_ANA_NAME] SKIP); \ NOBACKUP(char currModelFilename[LEN_MODEL_FILENAME+1]); \ - NOBACKUP(uint8_t spare5:1 SKIP); \ + NOBACKUP(uint8_t modelQuickSelect:1); \ NOBACKUP(uint8_t blOffBright:7); \ NOBACKUP(char bluetoothName[LEN_BLUETOOTH_NAME]); #else diff --git a/radio/src/gui/colorlcd/model_select.cpp b/radio/src/gui/colorlcd/model_select.cpp index 05035aa65da..4e4ccf4f5db 100644 --- a/radio/src/gui/colorlcd/model_select.cpp +++ b/radio/src/gui/colorlcd/model_select.cpp @@ -238,9 +238,11 @@ class ButtonHolder : public FormWindow class ModelButton : public Button { public: - ModelButton(FormGroup *parent, const rect_t &rect, ModelCell *modelCell) : + ModelButton(FormGroup *parent, const rect_t &rect, ModelCell *modelCell, std::function setSelected) : Button(parent, rect), modelCell(modelCell) { + 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); @@ -298,39 +300,38 @@ class ModelButton : public Button if (modelCell == modelslist.getCurrentModel()) { dc->drawSolidFilledRect(0, 0, width(), 20, COLOR_THEME_ACTIVE); - dc->drawSizedText(width() / 2, 2, modelCell->modelName, LEN_MODEL_NAME, - COLOR_THEME_SECONDARY1 | CENTERED); } else { - LcdFlags textColor; dc->drawFilledRect(0, 0, width(), 20, SOLID, COLOR_THEME_PRIMARY2); - - textColor = COLOR_THEME_SECONDARY1; - - dc->drawSizedText(width() / 2, 2, modelCell->modelName, LEN_MODEL_NAME, - textColor | CENTERED); } + 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; } + void setFocused() { + if (!lv_obj_has_state(lvobj, LV_STATE_FOCUSED)) { + lv_group_focus_obj(lvobj); + } + } + protected: bool loaded = false; ModelCell *modelCell; BitmapBuffer *buffer = nullptr; + std::function m_setSelected = nullptr; void onClicked() override { - if (!lv_obj_has_state(lvobj, LV_STATE_FOCUSED)) { - lv_group_focus_obj(lvobj); - } else { - Button::onClicked(); - } + setFocused(); + Button::onClicked(); } }; @@ -364,23 +365,25 @@ ModelsPageBody::ModelsPageBody(Window *parent, const rect_t &rect) : { setFlexLayout(LV_FLEX_FLOW_ROW_WRAP, MODEL_CELL_PADDING); padRow(MODEL_CELL_PADDING); - update(); } void ModelsPageBody::selectModel(ModelCell *model) { - 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 + // 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 + } } } @@ -388,17 +391,20 @@ void ModelsPageBody::selectModel(ModelCell *model) auto w = Layer::back(); if (w) w->onCancel(); - // store changes (if any) and load selected model - storageFlushCurrentModel(); - storageCheck(true); - memcpy(g_eeGeneral.currModelFilename, model->modelFilename, - LEN_MODEL_FILENAME); + // 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); - loadModel(g_eeGeneral.currModelFilename, true); - modelslist.setCurrentModel(model); + loadModel(g_eeGeneral.currModelFilename, true); + modelslist.setCurrentModel(model); - storageDirty(EE_GENERAL); - storageCheck(true); + storageDirty(EE_GENERAL); + storageCheck(true); + } } void ModelsPageBody::duplicateModel(ModelCell *model) @@ -500,7 +506,22 @@ void ModelsPageBody::editLabels(ModelCell* model) } } -void ModelsPageBody::update(int selected) +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_EDIT_LABELS, [=]() { editLabels(focusedModel); }); + menu->addLine(STR_SAVE_TEMPLATE, [=]() { saveAsTemplate(focusedModel);}); + if (focusedModel != modelslist.getCurrentModel()) { + menu->addLine(STR_DELETE_MODEL, [=]() { deleteModel(focusedModel); }); + } +} + +void ModelsPageBody::update() { clear(); @@ -511,25 +532,55 @@ void ModelsPageBody::update(int selected) 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); + auto button = new ModelButton(this, rect_t{}, model, [=]() { + focusedModel = model; + }); - // Long Press Handler for Models + if (!firstButton) + firstButton = button; + if (model == modelslist.getCurrentModel()) + focusedButton = button; + if (model == focusedModel && !focusedButton) + focusedButton = button; + + // Press Handler for Models button->setPressHandler([=]() -> uint8_t { - Menu *menu = new Menu(this); - menu->setTitle(model->modelName); - if (model != modelslist.getCurrentModel()) { - menu->addLine(STR_SELECT_MODEL, [=]() { selectModel(model); }); + if (model == focusedModel) { + if (g_eeGeneral.modelQuickSelect) + selectModel(model); + else + openMenu(); + } else { + focusedModel = model; } - menu->addLine(STR_DUPLICATE_MODEL, [=]() { duplicateModel(model); }); - if (model != modelslist.getCurrentModel()) { - menu->addLine(STR_DELETE_MODEL, [=]() { deleteModel(model); }); + return 0; + }); + + // Long Press Handler for Models + button->setLongPressHandler([=]() -> uint8_t { + if (model == focusedModel) { + openMenu(); } - menu->addLine(STR_EDIT_LABELS, [=]() { editLabels(model); }); - menu->addLine(STR_SAVE_TEMPLATE, [=]() { saveAsTemplate(model);}); return 0; }); } + + if (!focusedButton) + focusedButton = firstButton; + + if (focusedButton) { + focusedButton->setFocused(); + focusedModel = focusedButton->getModelCell(); + } } //----------------------------------------------------------------------------- @@ -589,8 +640,8 @@ class LabelDialog : public Dialog ModelLabelsWindow::ModelLabelsWindow() : Page(ICON_MODEL) { - buildBody(&body); buildHead(&header); + buildBody(&body); // find the first label of the current model and make that label active auto currentModel = modelslist.getCurrentModel(); diff --git a/radio/src/gui/colorlcd/model_select.h b/radio/src/gui/colorlcd/model_select.h index 66e1d92033e..36421a5abbe 100644 --- a/radio/src/gui/colorlcd/model_select.h +++ b/radio/src/gui/colorlcd/model_select.h @@ -37,12 +37,13 @@ class ModelsPageBody : public FormWindow public: ModelsPageBody(Window *parent, const rect_t &rect); + void update(); + void setLabels(LabelsVector labels) { selectedLabels = labels; update(); } - void update(int selected = -1); inline void setSortOrder(ModelsSortBy sortOrder) { @@ -61,8 +62,10 @@ class ModelsPageBody : public FormWindow 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); diff --git a/radio/src/gui/colorlcd/radio_setup.cpp b/radio/src/gui/colorlcd/radio_setup.cpp index ba95e60da91..9a0720030be 100644 --- a/radio/src/gui/colorlcd/radio_setup.cpp +++ b/radio/src/gui/colorlcd/radio_setup.cpp @@ -735,4 +735,10 @@ void RadioSetupPage::build(FormWindow * window) std::string( &getSourceString(MIXSRC_Rud + modn12x3[4 * value + 1])[1]); }); + grid.setColSpan(1); + + // Model quick select + line = window->newLine(&grid); + new StaticText(line, rect_t{}, STR_MODEL_QUICK_SELECT, 0, COLOR_THEME_PRIMARY1); + new CheckBox(line, rect_t{}, GET_SET_DEFAULT(g_eeGeneral.modelQuickSelect)); } diff --git a/radio/src/storage/yaml/yaml_datastructs_nv14.cpp b/radio/src/storage/yaml/yaml_datastructs_nv14.cpp index 645b847ab9b..8d58d0fb8ad 100644 --- a/radio/src/storage/yaml/yaml_datastructs_nv14.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_nv14.cpp @@ -434,7 +434,7 @@ static const struct YamlNode struct_RadioData[] = { YAML_PADDING( 192 ), YAML_PADDING( 216 ), YAML_STRING("currModelFilename", 17), - YAML_PADDING( 1 ), + YAML_UNSIGNED( "modelQuickSelect", 1 ), YAML_UNSIGNED( "blOffBright", 7 ), YAML_STRING("bluetoothName", 10), YAML_STRING("themeName", 8), diff --git a/radio/src/storage/yaml/yaml_datastructs_x10.cpp b/radio/src/storage/yaml/yaml_datastructs_x10.cpp index e5a4b18e383..1f018874517 100644 --- a/radio/src/storage/yaml/yaml_datastructs_x10.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_x10.cpp @@ -463,7 +463,7 @@ static const struct YamlNode struct_RadioData[] = { YAML_PADDING( 240 ), YAML_PADDING( 360 ), YAML_STRING("currModelFilename", 17), - YAML_PADDING( 1 ), + YAML_UNSIGNED( "modelQuickSelect", 1 ), YAML_UNSIGNED( "blOffBright", 7 ), YAML_STRING("bluetoothName", 10), YAML_STRING("themeName", 8), diff --git a/radio/src/storage/yaml/yaml_datastructs_x12s.cpp b/radio/src/storage/yaml/yaml_datastructs_x12s.cpp index d5b5ec2d4e9..faa58f0937d 100644 --- a/radio/src/storage/yaml/yaml_datastructs_x12s.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_x12s.cpp @@ -461,7 +461,7 @@ static const struct YamlNode struct_RadioData[] = { YAML_PADDING( 240 ), YAML_PADDING( 312 ), YAML_STRING("currModelFilename", 17), - YAML_PADDING( 1 ), + YAML_UNSIGNED( "modelQuickSelect", 1 ), YAML_UNSIGNED( "blOffBright", 7 ), YAML_STRING("bluetoothName", 10), YAML_STRING("themeName", 8), diff --git a/radio/src/translations/cn.h b/radio/src/translations/cn.h index 7626b017cbf..7d7713d6d79 100644 --- a/radio/src/translations/cn.h +++ b/radio/src/translations/cn.h @@ -759,6 +759,10 @@ #define TR_SAMPLE_MODES "标准","OneBit" #define TR_LOADING "加载中..." +#if defined(COLORLCD) + #define TR_MODEL_QUICK_SELECT "快速选择模型" +#endif + #define TR_SELECT_TEMPLATE_FOLDER "选择一个模板文件夹:" #define TR_SELECT_TEMPLATE "选择一个模板:" #define TR_NO_TEMPLATES "在此文件夹中未找到模型模板" diff --git a/radio/src/translations/cz.h b/radio/src/translations/cz.h index 482c81b736e..1dc86cd4d4d 100644 --- a/radio/src/translations/cz.h +++ b/radio/src/translations/cz.h @@ -777,6 +777,10 @@ #define TR_SAMPLE_MODES "Normal","OneBit" #define TR_LOADING "Načítání..." +#if defined(COLORLCD) + #define TR_MODEL_QUICK_SELECT "Rychlý výběr modelu" +#endif + #define TR_SELECT_TEMPLATE_FOLDER "VYBRAT SLOŽKU SE ŠABLONOU:" #define TR_SELECT_TEMPLATE "VYBRAT ŠABLONU MODELU:" #define TR_NO_TEMPLATES "Žádná šablona modelu v této složce nebyla nalezena" diff --git a/radio/src/translations/da.h b/radio/src/translations/da.h index 71c72bf6ffb..c92f819de80 100644 --- a/radio/src/translations/da.h +++ b/radio/src/translations/da.h @@ -764,6 +764,10 @@ #define TR_SAMPLE_MODES "Normal","EnBit" #define TR_LOADING "Indlæser..." +#if defined(COLORLCD) + #define TR_MODEL_QUICK_SELECT "Hurtigvalg af model" +#endif + #define TR_SELECT_TEMPLATE_FOLDER "VÆLG EN SKABELON MAPPE:" #define TR_SELECT_TEMPLATE "VÆLG EN SKABELON til MODEL:" #define TR_NO_TEMPLATES "Ingen model skabeloner fundet i mappe" diff --git a/radio/src/translations/de.h b/radio/src/translations/de.h index 56fd5fbcdcb..ca400d04dbb 100644 --- a/radio/src/translations/de.h +++ b/radio/src/translations/de.h @@ -759,6 +759,10 @@ #define TR_SAMPLE_MODES "Normal","OneBit" #define TR_LOADING "Wird geladen..." +#if defined(COLORLCD) + #define TR_MODEL_QUICK_SELECT "schnelle Modellauswahl" +#endif + #define TR_SELECT_TEMPLATE_FOLDER "WÄHLE VORLAGENVERZEICHNIS:" #define TR_SELECT_TEMPLATE "WÄHLE MODELLVORLAGE:" #define TR_NO_TEMPLATES "Es wurden keine Modellvorlagen in diesem Verzeichnis gefunden" diff --git a/radio/src/translations/en.h b/radio/src/translations/en.h index f99137d09f0..29728895206 100644 --- a/radio/src/translations/en.h +++ b/radio/src/translations/en.h @@ -759,6 +759,10 @@ #define TR_SAMPLE_MODES "Normal","OneBit" #define TR_LOADING "Loading..." +#if defined(COLORLCD) + #define TR_MODEL_QUICK_SELECT "Model quick select" +#endif + #define TR_SELECT_TEMPLATE_FOLDER "Select a template folder" #define TR_SELECT_TEMPLATE "SELECT A MODEL TEMPLATE:" #define TR_NO_TEMPLATES "No model templates were found in this folder" diff --git a/radio/src/translations/fi.h b/radio/src/translations/fi.h index 84f354fe236..3623e55fbaf 100644 --- a/radio/src/translations/fi.h +++ b/radio/src/translations/fi.h @@ -786,6 +786,10 @@ #define TR_SAMPLE_MODES "Normal","OneBit" #define TR_LOADING "Loading..." +#if defined(COLORLCD) + #define TR_MODEL_QUICK_SELECT "Mallin pikavalinta" +#endif + #define TR_SELECT_TEMPLATE_FOLDER "SELECT A TEMPLATE FOLDER:" #define TR_SELECT_TEMPLATE "SELECT A MODEL TEMPLATE:" #define TR_NO_TEMPLATES "No model templates were found in this folder" diff --git a/radio/src/translations/jp.h b/radio/src/translations/jp.h index 66642c0961a..52cd09e9cb1 100644 --- a/radio/src/translations/jp.h +++ b/radio/src/translations/jp.h @@ -771,6 +771,10 @@ #define TR_SAMPLE_MODES "標準","OneBit" #define TR_LOADING "読み込み中..." +#if defined(COLORLCD) + #define TR_MODEL_QUICK_SELECT "モデル クイックセレクト" +#endif + #define TR_SELECT_TEMPLATE_FOLDER "テンプレートフォルダを選択:" #define TR_SELECT_TEMPLATE "モデルテンプレートを選択:" #define TR_NO_TEMPLATES "このフォルダにモデルテンプレートは見つかりませんでした" diff --git a/radio/src/translations/se.h b/radio/src/translations/se.h index 764a0710f21..b1ed2d127ce 100644 --- a/radio/src/translations/se.h +++ b/radio/src/translations/se.h @@ -800,6 +800,10 @@ #define TR_SAMPLE_MODES "Normal","OneBit" #define TR_LOADING "Laddar..." +#if defined(COLORLCD) + #define TR_MODEL_QUICK_SELECT "Snabbval av modell" +#endif + #define TR_SELECT_TEMPLATE_FOLDER "VÄLJ EN MALLKATALOG:" #define TR_SELECT_TEMPLATE "VÄLJ EN MODELLMALL:" #define TR_NO_TEMPLATES "Ingen modellmall finns i denna katalog"