Skip to content

Commit

Permalink
Add an editable version of the string picker, which allows you to edi…
Browse files Browse the repository at this point in the history
…t 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
  • Loading branch information
CelticMinstrel committed Feb 23, 2025
1 parent 5d0afd2 commit 14ef515
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 18 deletions.
54 changes: 54 additions & 0 deletions rsrc/dialogs/choose-edit-string.xml
Original file line number Diff line number Diff line change
@@ -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>
118 changes: 103 additions & 15 deletions src/dialogxml/dialogs/strchoice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand All @@ -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();
}
Expand All @@ -47,11 +62,17 @@ 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();
using namespace std::placeholders;
dlg.run(std::bind(&cStringChoice::fillPage, this));
return dlg.getResult<size_t>();
}

Expand All @@ -66,41 +87,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;
Expand All @@ -110,11 +161,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();
}
}
12 changes: 9 additions & 3 deletions src/dialogxml/dialogs/strchoice.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -65,6 +69,8 @@ class cStringChoice {
/// 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

0 comments on commit 14ef515

Please sign in to comment.