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
This commit is contained in:
2025-02-23 00:48:36 -05:00
committed by Celtic Minstrel
parent 58890f1b57
commit 97cba0471b
3 changed files with 165 additions and 18 deletions

View File

@@ -0,0 +1,54 @@
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<!-- NOTE: This file should be updated to use relative positioning the next time it changes. -->
<?xml-stylesheet href="dialog.xsl" type="text/xsl"?>
<dialog defbtn='done'>
<pict type='dlog' num='16' top='8' left='8'/>
<text name='title' size='large' top='6' left='50' width='256' height='14'>Select:</text>
<text top='24' left='50'>Note: Edits to the names will be saved even if you click Cancel.</text>
<group name='strings'>
<led name='led1' state='off' top='56' left='8'/>
<led name='led2' state='off' top='86' left='8'/>
<led name='led3' state='off' top='116' left='8'/>
<led name='led4' state='off' top='146' left='8'/>
<led name='led5' state='off' top='176' left='8'/>
<led name='led6' state='off' top='206' left='8'/>
<led name='led7' state='off' top='236' left='8'/>
<led name='led8' state='off' top='266' left='8'/>
<led name='led9' state='off' top='296' left='8'/>
<led name='led10' state='off' top='326' left='8'/>
<led name='led11' state='off' top='56' left='216'/>
<led name='led12' state='off' top='86' left='216'/>
<led name='led13' state='off' top='116' left='216'/>
<led name='led14' state='off' top='146' left='216'/>
<led name='led15' state='off' top='176' left='216'/>
<led name='led16' state='off' top='206' left='216'/>
<led name='led17' state='off' top='236' left='216'/>
<led name='led18' state='off' top='266' left='216'/>
<led name='led19' state='off' top='296' left='216'/>
<led name='led20' state='off' top='326' left='216'/>
</group>
<field name='edit1' top='54' left='29' width='180' height='14'/>
<field name='edit2' top='84' left='29' width='180' height='14'/>
<field name='edit3' top='114' left='29' width='180' height='14'/>
<field name='edit4' top='144' left='29' width='180' height='14'/>
<field name='edit5' top='174' left='29' width='180' height='14'/>
<field name='edit6' top='204' left='29' width='180' height='14'/>
<field name='edit7' top='234' left='29' width='180' height='14'/>
<field name='edit8' top='264' left='29' width='180' height='14'/>
<field name='edit9' top='294' left='29' width='180' height='14'/>
<field name='edit10' top='324' left='29' width='180' height='14'/>
<field name='edit11' top='54' left='237' width='180' height='14'/>
<field name='edit12' top='84' left='237' width='180' height='14'/>
<field name='edit13' top='114' left='237' width='180' height='14'/>
<field name='edit14' top='144' left='237' width='180' height='14'/>
<field name='edit15' top='174' left='237' width='180' height='14'/>
<field name='edit16' top='204' left='237' width='180' height='14'/>
<field name='edit17' top='234' left='237' width='180' height='14'/>
<field name='edit18' top='264' left='237' width='180' height='14'/>
<field name='edit19' top='294' left='237' width='180' height='14'/>
<field name='edit20' top='324' left='237' width='180' height='14'/>
<button name='left' type='left' def-key='left' top='358' left='8'/>
<button name='right' type='right' def-key='right' top='358' left='71'/>
<button name='done' type='regular' top='358' left='338'>OK</button>
<button name='cancel' type='regular' def-key='esc' top='358' left='272'>Cancel</button>
</dialog>

View File

@@ -13,17 +13,25 @@
#include <boost/lexical_cast.hpp>
#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<std::string>& strs, std::string title, cDialog* parent)
: cStringChoice(parent)
cStringChoice::cStringChoice(std::vector<std::string>& 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<cLedGroup&>(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<size_t>(selectedIndex);
dlg.run();
dlg.run(std::bind(&cStringChoice::fillPage, this));
return dlg.getResult<size_t>();
}
@@ -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<cLed&>(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<cLed&>(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<cTextField*>(&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<int>(leds->getSelected().substr(3));
cur = page * per_page + i - 1;
if(editable) {
std::ostringstream sout;
sout << "edit" << i;
dlg.setFocus(dynamic_cast<cTextField*>(&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<int>(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();
}
}

View File

@@ -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<std::string> strings;
size_t page, cur;
cLedGroup* leds;
std::function<void(cStringChoice&,int)> 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<std::string>& strs, std::string title, cDialog* parent = nullptr);
explicit cStringChoice(std::vector<std::string>& 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<std::string> getStrings() const { return strings; }
};
#endif