Skip to content

Commit 52878df

Browse files
committed
ADD: searchable comboboxes; for editing click twice
git-svn-id: svn://tron.homeunix.org/simutrans/simutrans/trunk@11879 8aca7d54-2c30-db11-9de9-000461428c89
1 parent c42b284 commit 52878df

File tree

6 files changed

+109
-34
lines changed

6 files changed

+109
-34
lines changed

simutrans/history.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
ADD: new flag for bridges and ways in makeobj 60.9 "clip_below" Default is (1) but for slim structures (e.g. powerline) it could be set to zero for nicer display of structures below
5757
CHG: (poppo) jump tool now allows also for z coordinaate
5858
FIX: (poppo) last tool not show when too many tools to fit
59+
ADD: searchable comboboxes; for editing click twice
5960

6061

6162
Release of 124.3.1 (r11671 on 5-Apr-2025):

src/simutrans/gui/components/gui_combobox.cc

Lines changed: 92 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,22 @@ gui_combobox_t::gui_combobox_t(gui_scrolled_list_t::item_compare_func cmp) :
2828
{
2929
minimize = false;
3030
bt_prev.set_typ(button_t::arrowleft);
31-
bt_prev.set_pos( scr_coord(0,2) );
31+
bt_prev.set_pos(scr_coord(0, 2));
3232

3333
bt_next.set_typ(button_t::arrowright);
3434

35-
set_focusable( true ); // needed, otherwise fails on closing when clicking elsewhere!
35+
set_focusable(true); // needed, otherwise fails on closing when clicking elsewhere!
3636

37+
search_str[0] = 0;
3738
editstr[0] = 0;
3839
old_editstr[0] = 0;
3940
textinp.add_listener(this);
4041

4142
first_call = true;
4243
finish = false;
4344
wrapping = true;
45+
force_selection = false;
46+
selection_when_open = -1;
4447
droplist.set_visible(false);
4548
droplist.add_listener(this);
4649
closed_size = get_size();
@@ -99,17 +102,31 @@ DBG_MESSAGE("event","HOWDY!");
99102
}
100103

101104
// goto next/previous choice
102-
if( IS_KEYDOWN(ev) && (ev->ev_code == SIM_KEYCODE_UP || ev->ev_code == SIM_KEYCODE_DOWN) ) {
105+
if( IS_KEYDOWN(ev) && (ev->ev_code == SIM_KEYCODE_UP || ev->ev_code == SIM_KEYCODE_DOWN) && droplist.get_count()>0) {
106+
int delta = ev->ev_code == SIM_KEYCODE_UP ? -1 : 1;
103107
int sel = droplist.get_selection();
104-
if( ev->ev_code == SIM_KEYCODE_UP ) {
105-
set_selection( sel > 0 ? sel-1 : (wrapping ? droplist.get_count()-1 : 0) );
108+
if (delta < 0 && sel < 0) {
109+
// start at last
110+
sel = 0;
106111
}
107-
else {
108-
set_selection( sel < (sint32)droplist.get_count()-1 ? sel+1 : (wrapping ? 0 : droplist.get_count()-1) );
112+
const int cnt = droplist.get_count();
113+
bool found = false;
114+
for (int i = 0; i < droplist.get_count(); i++) {
115+
sel = (sel + delta + cnt) % cnt;
116+
if (droplist.get_element(sel)->is_visible()) {
117+
found = true;
118+
break;
119+
}
120+
}
121+
if (sel != droplist.get_selection()) {
122+
droplist.set_selection(sel);
123+
if (!droplist.is_visible()) {
124+
// update if we are not in the open list scrolling
125+
value_t p;
126+
p.i = droplist.get_selection();
127+
call_listeners(p);
128+
}
109129
}
110-
value_t p;
111-
p.i = droplist.get_selection();
112-
call_listeners( p );
113130
return true;
114131
}
115132

@@ -138,6 +155,7 @@ DBG_MESSAGE("event","HOWDY!");
138155

139156
// else prepare for selection (after a left mbutton release event!)
140157
droplist.set_visible(true);
158+
selection_when_open = droplist.get_selection();
141159

142160
// determine possible size of droplist and whether open below/above input field
143161
scr_coord_val win_height = win_get_top()->get_windowsize().h - D_TITLEBAR_HEIGHT;
@@ -160,6 +178,9 @@ DBG_MESSAGE("event","HOWDY!");
160178
sel = 0;
161179
}
162180
droplist.show_selection(sel);
181+
search_str[0] = 0;
182+
textinp.set_text(NULL, 0);
183+
textinp.set_text(search_str, lengthof(search_str));
163184
}
164185
else if (droplist.is_visible()) {
165186

@@ -169,16 +190,8 @@ DBG_MESSAGE("event","HOWDY!");
169190
event_t ev2 = *ev;
170191
ev2.move_origin(droplist.get_pos());
171192
if( droplist.infowin_event(&ev2) ) {
172-
if( droplist.get_selection() != old_selection ) {
173-
// close box will anyway call
174-
if( !IS_LEFTRELEASE( ev ) ) {
175-
// in case of LEFTRELEASE, close_box will call it again
176-
call_listeners( droplist.get_selection() );
177-
}
178-
finish = true;
179-
}
180-
// we selected something?
181-
if(finish && IS_LEFTRELEASE(ev)) {
193+
finish = droplist.get_selection() != old_selection;
194+
if(IS_LEFTRELEASE(ev)) {
182195
close_box();
183196
}
184197
}
@@ -200,7 +213,27 @@ DBG_MESSAGE("gui_combobox_t::infowin_event()","close");
200213
else {
201214
// finally handle textinput
202215
gui_scrolled_list_t::scrollitem_t *item = droplist.get_selected_item();
203-
if( item==NULL || item->is_editable() ) {
216+
if (droplist.is_visible()) {
217+
// we are searching, not renaming
218+
int first_match = -1;
219+
event_t ev2 = *ev;
220+
ev2.move_origin(textinp.get_pos());
221+
char same = search_str[0];
222+
if (textinp.infowin_event(&ev2)) {
223+
if (search_str[0]!=same) {
224+
for (int i = 0; i < droplist.get_count(); i++) {
225+
droplist.get_element(i)->set_visible(strstr(droplist.get_element(i)->get_text(), search_str));
226+
if (droplist.get_element(i)->is_visible() && first_match <= droplist.get_selection()) {
227+
first_match = i;
228+
}
229+
}
230+
droplist.show_selection(first_match);
231+
droplist.set_selection(first_match);
232+
}
233+
return true;
234+
}
235+
}
236+
else if( item==NULL || item->is_editable() ) {
204237
event_t ev2 = *ev;
205238
ev2.move_origin(textinp.get_pos());
206239
return textinp.infowin_event(&ev2);
@@ -234,8 +267,11 @@ void gui_combobox_t::draw(scr_coord offset)
234267
last_draw_offset = offset;
235268
// text changed? Then update it
236269
gui_scrolled_list_t::scrollitem_t *item = droplist.get_selected_item();
237-
if( item && item->is_valid() && item->is_editable() && strncmp( item->get_text(), old_editstr, 127 ) ) {
238-
reset_selected_item_name();
270+
if (!droplist.is_visible()) {
271+
// maybe rename item unless we are searching reight now
272+
if (item && item->is_valid() && item->is_editable() && strncmp(item->get_text(), old_editstr, lengthof(old_editstr))) {
273+
reset_selected_item_name();
274+
}
239275
}
240276

241277
offset += pos;
@@ -274,18 +310,22 @@ void gui_combobox_t::disable()
274310
*/
275311
void gui_combobox_t::set_selection(int s)
276312
{
313+
search_str[0] = 0;
314+
277315
// try to finish renaming first
278316
rename_selected_item();
279317

318+
// just set it
319+
if (force_selection && s < 0 && droplist.get_count() > 0) {
320+
// force always a valid selection unless empty
321+
s = 0;
322+
}
323+
droplist.set_selection(s);
280324
if (droplist.is_visible() && !finish) {
281325
// visible and not closing
282326
// change also offset of scrollbar
283327
droplist.show_selection( s );
284328
}
285-
else {
286-
// just set it
287-
droplist.set_selection(s);
288-
}
289329
// edit the text
290330
reset_selected_item_name();
291331
}
@@ -300,7 +340,7 @@ void gui_combobox_t::rename_selected_item()
300340
// if name was not changed in the meantime, we can rename it
301341
if( item && item->is_valid() && item->is_editable() ) {
302342
const char *current_str = ((gui_scrolled_list_t::const_text_scrollitem_t *)item)->get_text();
303-
if( strncmp( current_str, old_editstr, 127 )==0 && strncmp( current_str, editstr, 127 )!=0 ) {
343+
if( strncmp( current_str, old_editstr, lengthof(old_editstr)-1) == 0 && strncmp( current_str, editstr, lengthof(old_editstr)-1) != 0 ) {
304344
item->set_text(editstr);
305345
}
306346
}
@@ -312,14 +352,18 @@ void gui_combobox_t::reset_selected_item_name()
312352
gui_scrolled_list_t::scrollitem_t *item = droplist.get_selected_item();
313353
if( item==NULL ) {
314354
editstr[0] = 0;
315-
textinp.set_text( editstr, 0 );
355+
if(!droplist.is_visible()) {
356+
textinp.set_text( editstr, 0 );
357+
}
316358
droplist.set_selection(-1);
317359
}
318360
else if( item->is_valid() ) {
319361
const char *current_str = ((gui_scrolled_list_t::const_text_scrollitem_t *)item)->get_text();
320-
if( strncmp( current_str, old_editstr, 127 )!=0 ) {
362+
if( strncmp( current_str, old_editstr, lengthof(old_editstr)-1 )!=0 ) {
321363
tstrncpy( editstr, current_str, lengthof(editstr) );
322-
textinp.set_text( editstr, lengthof(editstr) );
364+
if (!droplist.is_visible()) {
365+
textinp.set_text(editstr, lengthof(editstr));
366+
}
323367
}
324368
}
325369
tstrncpy( old_editstr, editstr, lengthof(old_editstr) );
@@ -331,12 +375,27 @@ void gui_combobox_t::reset_selected_item_name()
331375
*/
332376
void gui_combobox_t::close_box()
333377
{
334-
if(finish) {
378+
bool selection_changed = selection_when_open != droplist.get_selection();
379+
reset_selected_item_name();
380+
if(finish && selection_changed) {
335381
value_t p;
336382
p.i = droplist.get_selection();
337383
call_listeners(p);
338384
finish = false;
339385
}
386+
if (search_str[0] || selection_changed) {
387+
// reset filter: set all visible again
388+
search_str[0] = 0;
389+
for (int i = 0; i < droplist.get_count(); i++) {
390+
droplist.get_element(i)->set_visible(true);
391+
}
392+
textinp.set_text(NULL, 0); // also to reset cursor position
393+
}
394+
textinp.set_text(editstr, lengthof(editstr) );
395+
if (!selection_changed) {
396+
sint32 len = strlen(editstr);
397+
textinp.set_cursor(len, len);
398+
}
340399
droplist.set_visible(false);
341400
first_call = true;
342401
}
@@ -411,6 +470,7 @@ scr_size gui_combobox_t::get_max_size() const
411470

412471
void gui_combobox_t::rdwr( loadsave_t *file )
413472
{
473+
// todo: all pending edits and searches are lost/executed
414474
sint32 i = get_selection();
415475
file->rdwr_long(i);
416476
set_selection(i);

src/simutrans/gui/components/gui_combobox.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,15 @@ class gui_combobox_t :
2525
public action_listener_t
2626
{
2727
private:
28-
char editstr[128],old_editstr[128];
28+
char editstr[256],old_editstr[256],search_str[256];
2929

3030
// buttons for setting selection manually
3131
gui_textinput_t textinp;
3232
button_t bt_prev;
3333
button_t bt_next;
3434

3535
scr_size closed_size;
36+
sint32 selection_when_open;
3637

3738
/**
3839
* the drop box list
@@ -52,6 +53,9 @@ class gui_combobox_t :
5253
// try to shrink to minimum size even if there is more space
5354
bool minimize:1;
5455

56+
// force always a vlid selection; if nothing is selected, select element 0
57+
bool force_selection : 1;
58+
5559
// offset of last draw call, needed to decide, where to open droplist
5660
scr_coord last_draw_offset;
5761

@@ -143,6 +147,7 @@ class gui_combobox_t :
143147
void close_box();
144148

145149
void set_wrapping(const bool wrap) { wrapping = wrap; }
150+
void set_force_selection(const bool force) { force_selection = force; }
146151

147152
bool is_dropped() const { return droplist.is_visible(); }
148153

src/simutrans/gui/components/gui_textinput.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@ class gui_textinput_t :
129129
// x position of the current cursor (for IME purposes)
130130
scr_coord_val get_current_cursor_x() { return calc_cursor_pos(head_cursor_pos); }
131131

132+
// set currsen selection (not checks!)
133+
void set_cursor(sint32 h, sint32 t) {
134+
head_cursor_pos = h;
135+
tail_cursor_pos = t;
136+
}
137+
132138
/**
133139
* Detect change of focus state and determine whether cursor should be displayed,
134140
* and call the function that performs the actual display

src/simutrans/gui/depot_frame.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ void depot_frame_t::init(depot_t *dep)
330330
cont_4cols->new_component<gui_label_t>("Filter:", SYSCOL_TEXT, gui_label_t::right);
331331

332332
vehicle_filter.add_listener(this);
333+
vehicle_filter.set_force_selection(true);
333334
cont_4cols->add_component(&vehicle_filter);
334335

335336
cont_4cols->new_component<gui_label_t>("Search:", SYSCOL_TEXT, gui_label_t::right);

src/simutrans/gui/schedule_list.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ schedule_list_gui_t::schedule_list_gui_t(player_t *player_) :
8181

8282
// freight type filter
8383
new_component<gui_empty_t>();
84+
freight_type_c.set_force_selection(true);
8485
viewable_freight_types.append(NULL);
8586
freight_type_c.new_component<gui_scrolled_list_t::const_text_scrollitem_t>(translator::translate("All"), SYSCOL_TEXT);
8687
viewable_freight_types.append(goods_manager_t::passengers);
@@ -115,6 +116,7 @@ schedule_list_gui_t::schedule_list_gui_t(player_t *player_) :
115116

116117
// sort by what
117118
new_component<gui_label_t>("hl_txt_sort");
119+
sort_type_c.set_force_selection(true);
118120
for( int i=0; i<MAX_SORT_IDX; i++ ) {
119121
sort_type_c.new_component<gui_scrolled_list_t::const_text_scrollitem_t>( translator::translate(idx_to_sort_text[i]), SYSCOL_TEXT) ;
120122
}
@@ -380,7 +382,7 @@ void schedule_list_gui_t::rdwr( loadsave_t *file )
380382
// borrowed code from minimap
381383
bool schedule_list_gui_t::is_matching_freight_catg(const minivec_tpl<uint8> &goods_catg_index)
382384
{
383-
const goods_desc_t *line_freight_type_group_index = viewable_freight_types[ freight_type_c.get_selection() ];
385+
const goods_desc_t *line_freight_type_group_index = viewable_freight_types[freight_type_c.get_selection()];
384386
// does this line/convoi has a matching freight
385387
if( line_freight_type_group_index == goods_manager_t::passengers ) {
386388
return goods_catg_index.is_contained(goods_manager_t::INDEX_PAS);

0 commit comments

Comments
 (0)