From 97cba0471b2f3893502231ca0cce5d5894d8ff78 Mon Sep 17 00:00:00 2001 From: Celtic Minstrel Date: Sun, 23 Feb 2025 00:48:36 -0500 Subject: [PATCH] Add an editable version of the string picker, which allows you to edit each of the strings inline and even add new ones. This just implements the guts of the dialog, without using it for anything yet. It also fixes a bug that caused a blank page to appear in the string picker if the total number of strings was an exact multiple of 40. Closes #563 --- rsrc/dialogs/choose-edit-string.xml | 54 +++++++++++++ src/dialogxml/dialogs/strchoice.cpp | 117 ++++++++++++++++++++++++---- src/dialogxml/dialogs/strchoice.hpp | 12 ++- 3 files changed, 165 insertions(+), 18 deletions(-) create mode 100644 rsrc/dialogs/choose-edit-string.xml diff --git a/rsrc/dialogs/choose-edit-string.xml b/rsrc/dialogs/choose-edit-string.xml new file mode 100644 index 00000000..54cb04fc --- /dev/null +++ b/rsrc/dialogs/choose-edit-string.xml @@ -0,0 +1,54 @@ + + + + + + Select: + Note: Edits to the names will be saved even if you click Cancel. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/dialogxml/dialogs/strchoice.cpp b/src/dialogxml/dialogs/strchoice.cpp index a95f0e01..59ad767c 100644 --- a/src/dialogxml/dialogs/strchoice.cpp +++ b/src/dialogxml/dialogs/strchoice.cpp @@ -13,17 +13,25 @@ #include +#include "dialogxml/widgets/field.hpp" #include "fileio/resmgr/res_dialog.hpp" +#include "sounds.hpp" -cStringChoice::cStringChoice(cDialog* parent) - : dlg(*ResMgr::dialogs.get("choose-string"), parent) +cStringChoice::cStringChoice(cDialog* parent, bool editable) + : editable(editable) + , per_page(editable ? 20 : 40) + , dlg(*ResMgr::dialogs.get(editable ? "choose-edit-string" : "choose-string"), parent) {} -cStringChoice::cStringChoice(std::vector& strs, std::string title, cDialog* parent) - : cStringChoice(parent) +cStringChoice::cStringChoice(std::vector& strs, std::string title, cDialog* parent, bool editable) + : cStringChoice(parent, editable) { setTitle(title); strings = strs; + if(editable) { + if(strings.empty()) strings.resize(per_page); + else strings.resize(per_page * ceil(strings.size() / double(per_page))); + } attachHandlers(); } @@ -35,7 +43,14 @@ void cStringChoice::attachHandlers() { dlg["cancel"].attachClickHandler(std::bind(&cStringChoice::onCancel,this,_1)); leds = &dynamic_cast(dlg["strings"]); leds->attachFocusHandler(std::bind(&cStringChoice::onSelect,this,_3)); - if(strings.size() <= per_page) { + if(editable) { + for(int i = 1; i <= per_page; i++) { + std::ostringstream sout; + sout << "edit" << i; + dlg[sout.str()].attachFocusHandler(std::bind(&cStringChoice::onFocus,this,_2,_3)); + } + } + if(!editable && strings.size() <= per_page) { dlg["left"].hide(); dlg["right"].hide(); } @@ -47,11 +62,16 @@ cDialog* cStringChoice::operator->() { size_t cStringChoice::show(size_t selectedIndex) { cur = selectedIndex; - if(cur >= strings.size()) cur = 0; + if(cur >= strings.size()) { + if(editable) { + strings.resize(per_page * ceil((cur + 1) / double(per_page))); + } else cur = 0; + } else if(cur < 0) { + cur = 0; + } page = cur / per_page; - fillPage(); dlg.setResult(selectedIndex); - dlg.run(); + dlg.run(std::bind(&cStringChoice::fillPage, this)); return dlg.getResult(); } @@ -66,41 +86,71 @@ void cStringChoice::fillPage(){ short string_idx = page * per_page + i; std::ostringstream sout; sout << "led" << i + 1; - cLed& led = dynamic_cast(dlg[sout.str()]); + std::string led_id = sout.str(), text_id; + if(editable) { + sout.str(""); + sout << "edit" << i + 1; + text_id = sout.str(); + } else text_id = led_id; + cLed& led = dynamic_cast(dlg[led_id]); + cControl& text = dlg[text_id]; if(string_idx >= strings.size()){ led.hide(); + text.hide(); continue; }else{ - led.setText(strings[string_idx]); - led.recalcRect(); + text.setText(strings[string_idx]); + if(!editable) led.recalcRect(); led.show(); + text.show(); + } + if(string_idx == cur) { + group.setSelected(led_id); + if(editable) { + dlg.setFocus(dynamic_cast(&text)); + } } - if(string_idx == cur) - group.setSelected(sout.str()); } group.recalcRect(); } bool cStringChoice::onLeft(){ - if(page == 0) page = strings.size() / per_page; + savePage(); + if(editable && page == lastPage()) { + int blanks = 0; + for(int i = strings.size() - 1; i >= 0; i--) { + if(!strings[i].empty()) break; + blanks++; + } + if(blanks >= per_page) { + strings.resize(strings.size() - per_page); + } + } + if(page == 0) page = lastPage(); else page--; fillPage(); return true; } bool cStringChoice::onRight(){ - if(page == strings.size() / per_page) page = 0; + savePage(); + if(editable && page == lastPage()) { + strings.resize(strings.size() + per_page); + } + if(page == lastPage()) page = 0; else page++; fillPage(); return true; } bool cStringChoice::onCancel(cDialog& me){ + savePage(); me.toast(false); return true; } bool cStringChoice::onOkay(cDialog& me){ + savePage(); dlg.setResult(cur); me.toast(true); return true; @@ -110,11 +160,48 @@ bool cStringChoice::onSelect(bool losing) { if(losing) return true; int i = boost::lexical_cast(leds->getSelected().substr(3)); cur = page * per_page + i - 1; + if(editable) { + std::ostringstream sout; + sout << "edit" << i; + dlg.setFocus(dynamic_cast(&dlg[sout.str()])); + } if(select_handler) select_handler(*this, cur); return true; } +bool cStringChoice::onFocus(std::string which, bool losing) { + if(losing || !editable) return true; + if(!dlg[which].getText().empty()) return true; + int i = boost::lexical_cast(which.substr(4)); + if(!strings[page * per_page + i - 1].empty()) return true; + std::ostringstream sout; + sout << "led" << i; + if(leds->getSelected() != sout.str()) { + play_sound(34); + leds->setSelected(sout.str()); + cur = page * per_page + i - 1; + if(select_handler) + select_handler(*this, cur); + } + return true; +} + void cStringChoice::setTitle(const std::string &title) { if(!title.empty()) dlg["title"].setText(title); } + +size_t cStringChoice::lastPage() const { + return (strings.size() - 1) / per_page; +} + +void cStringChoice::savePage() { + if(!editable) return; + for(unsigned int i = 0; i < per_page; i++){ + short string_idx = page * per_page + i; + std::ostringstream sout; + sout << "edit" << i + 1; + std::string text_id = sout.str(); + strings[string_idx] = dlg[text_id].getText(); + } +} diff --git a/src/dialogxml/dialogs/strchoice.hpp b/src/dialogxml/dialogs/strchoice.hpp index c0ad1178..5d552cd3 100644 --- a/src/dialogxml/dialogs/strchoice.hpp +++ b/src/dialogxml/dialogs/strchoice.hpp @@ -18,26 +18,30 @@ /// A dialog that presents a list of strings with LEDs and allows you to choose one. /// The list may span several pages. class cStringChoice { - static const size_t per_page = 40; + const bool editable = false; + const size_t per_page = 40; cDialog dlg; bool onLeft(); bool onRight(); bool onCancel(cDialog& me); bool onOkay(cDialog& me); bool onSelect(bool losing); + bool onFocus(std::string which, bool losing); void attachHandlers(); void fillPage(); + void savePage(); + size_t lastPage() const; std::vector strings; size_t page, cur; cLedGroup* leds; std::function select_handler; - cStringChoice(cDialog* parent); + cStringChoice(cDialog* parent, bool editable = false); public: /// Initializes a dialog from a list of strings. /// @param strs A list of all strings in the dialog. /// @param title The title to show in the dialog. /// @param parent Optionally, a parent dialog. - explicit cStringChoice(std::vector& strs, std::string title, cDialog* parent = nullptr); + explicit cStringChoice(std::vector& strs, std::string title, cDialog* parent = nullptr, bool editable = false); /// Initializes a dialog from an iterator pair. /// @param begin An iterator to the first string in the dialog. /// @param end An iterator to one past the last string in the dialog. @@ -65,6 +69,8 @@ public: /// Set the dialog's title. /// @param title The new title. void setTitle(const std::string& title); + /// Get the list of strings. + std::vector getStrings() const { return strings; } }; #endif