]> git.tdb.fi Git - libs/gltk.git/blob - source/list.cpp
Use the maximum item height for autosizing List
[libs/gltk.git] / source / list.cpp
1 #include <msp/core/raii.h>
2 #include <msp/debug/demangle.h>
3 #include <msp/gl/matrix.h>
4 #include <msp/gl/meshbuilder.h>
5 #include <msp/input/keys.h>
6 #include "graphic.h"
7 #include "list.h"
8 #include "part.h"
9 #include "style.h"
10 #include "text.h"
11 #include "vslider.h"
12
13 using namespace std;
14
15 namespace Msp {
16 namespace GLtk {
17
18 incompatible_data::incompatible_data(const type_info &ti):
19         logic_error("expected "+Debug::demangle(ti.name()))
20 { }
21
22
23 List::List():
24         data(new BasicListData<string>),
25         own_data(true)
26 {
27         init();
28 }
29
30 List::List(ListData &d):
31         data(&d),
32         own_data(false)
33 {
34         init();
35 }
36
37 void List::init()
38 {
39         input_type = INPUT_NAVIGATION;
40
41         item_factory = 0;
42         sel_index = -1;
43         focus_index = -1;
44         first = 0;
45         max_scroll = 0;
46         view_size = 5;
47         ignore_slider_change = false;
48         dragging = false;
49         drag_start_x = 0;
50         drag_start_y = 0;
51
52         observer = new DataObserver(*this);
53
54         add(slider);
55         slider.set_step(1);
56         slider.signal_value_changed.connect(sigc::mem_fun(this, &List::slider_value_changed));
57 }
58
59 List::~List()
60 {
61         delete item_factory;
62         delete observer;
63         if(own_data)
64                 delete data;
65 }
66
67 void List::autosize_special(const Part &part, Geometry &ageom) const
68 {
69         if(part.get_name()=="items")
70         {
71                 const Sides &margin = part.get_margin();
72
73                 unsigned max_w = 0;
74                 unsigned max_h = 0;
75                 for(unsigned i=0; i<items.size(); ++i)
76                 {
77                         Geometry igeom;
78                         items[i]->autosize(igeom);
79                         max_w = max(max_w, igeom.w);
80                         max_h = max(max_h, igeom.h);
81                 }
82
83                 unsigned total_h = max_h*(view_size==0 ? items.size() : view_size);
84
85                 ageom.w = max(ageom.w, max_w+margin.left+margin.right);
86                 ageom.h = max(ageom.h, total_h+margin.top+margin.bottom);
87         }
88         else if(part.get_name()=="slider")
89                 autosize_child(slider, part, ageom);
90 }
91
92 void List::set_data(ListData &d)
93 {
94         if(item_factory)
95                 item_factory->set_data(d);
96
97         delete observer;
98         if(own_data)
99                 delete data;
100
101         data = &d;
102         own_data = false;
103         observer = new DataObserver(*this);
104
105         for(vector<Item *>::iterator i=items.begin(); i!=items.end(); ++i)
106                 delete *i;
107         items.clear();
108         unsigned n_items = data->size();
109         for(unsigned i=0; i<n_items; ++i)
110         {
111                 Item *item = create_item(i);
112                 items.push_back(item);
113         }
114
115         items_changed();
116 }
117
118 void List::items_changed()
119 {
120         signal_autosize_changed.emit();
121         rebuild();
122 }
123
124 List::Item *List::create_item(unsigned index)
125 {
126         Item *item = 0;
127         if(item_factory)
128                 item = item_factory->create_item(index);
129         else
130                 item = new BasicItem(data->get_string(index));
131         if(static_cast<int>(index)==sel_index)
132                 item->set_active(true);
133         add(*item);
134         item->autosize();
135         item->signal_autosize_changed.connect(sigc::bind(sigc::mem_fun(this, &List::item_autosize_changed), item));
136         return item;
137 }
138
139 void List::set_view_size(unsigned s)
140 {
141         view_size = s;
142         signal_autosize_changed.emit();
143 }
144
145 void List::set_view_all()
146 {
147         set_view_size(0);
148 }
149
150 void List::set_selected_index(int i)
151 {
152         if(i>=static_cast<int>(data->size()))
153                 throw out_of_range("List::set_selected_index");
154
155         if(i==sel_index || (i<0 && sel_index<0))
156                 return;
157
158         if(sel_index>=0)
159                 items[sel_index]->set_active(false);
160         if(i<0)
161         {
162                 sel_index = -1;
163                 focus_index = -1;
164                 set_input_focus(0);
165                 signal_selection_cleared.emit();
166         }
167         else
168         {
169                 sel_index = i;
170                 focus_index = i;
171                 items[sel_index]->set_active(true);
172                 if(state&FOCUS)
173                         set_input_focus(items[focus_index]);
174                 signal_item_selected.emit(sel_index);
175         }
176 }
177
178 void List::set_selected_item(Widget *item)
179 {
180         for(unsigned i=first; (i<items.size() && items[i]->is_visible()); ++i)
181                 if(item==items[i])
182                         return set_selected_index(i);
183 }
184
185 void List::rebuild_special(const Part &part)
186 {
187         if(part.get_name()=="slider")
188                 reposition_child(slider, part);
189         else if(part.get_name()=="items")
190         {
191                 SetFlag flag(ignore_slider_change);
192                 check_view_range();
193
194                 const Sides &margin = part.get_margin();
195                 unsigned w = geom.w-min(geom.w, margin.left+margin.right);
196                 unsigned y = geom.h-min(geom.h, margin.top);
197                 for(unsigned i=0; i<items.size(); ++i)
198                 {
199                         if(i<first || !y)
200                                 items[i]->set_visible(false);
201                         else
202                         {
203                                 Geometry igeom = items[i]->get_geometry();
204                                 if(igeom.h+margin.bottom<=y)
205                                 {
206                                         items[i]->set_visible(true);
207                                         y -= igeom.h;
208                                         igeom.x = margin.left;
209                                         igeom.y = y;
210                                         igeom.w = w;
211                                         items[i]->set_geometry(igeom);
212                                 }
213                                 else
214                                 {
215                                         items[i]->set_visible(false);
216                                         y = 0;
217                                 }
218                         }
219                 }
220         }
221
222         Widget::rebuild_special(part);
223 }
224
225 void List::render_special(const Part &part, GL::Renderer &renderer) const
226 {
227         if(part.get_name()=="items")
228         {
229                 for(unsigned i=first; (i<items.size() && items[i]->is_visible()); ++i)
230                         items[i]->render(renderer);
231         }
232         else if(part.get_name()=="slider")
233                 slider.render(renderer);
234 }
235
236 bool List::key_press(unsigned key, unsigned mod)
237 {
238         if(key==Input::KEY_UP && mod==MOD_CTRL)
239                 move_focus(NAV_UP, false);
240         else if(key==Input::KEY_DOWN && mod==MOD_CTRL)
241                 move_focus(NAV_DOWN, false);
242         else
243                 return false;
244
245         return true;
246 }
247
248 void List::button_press(int x, int y, unsigned btn)
249 {
250         if(btn==4 || btn==5)
251         {
252                 unsigned change = 3;
253                 if(btn==4)
254                 {
255                         change = min(first, change);
256                         slider.set_value(max_scroll-(first-change));
257                 }
258                 else if(btn==5)
259                 {
260                         change = min(max_scroll-first, change);
261                         slider.set_value(max_scroll-(first+change));
262                 }
263         }
264         else
265         {
266                 Container::button_press(x, y, btn);
267                 if(click_focus && btn==1)
268                         set_selected_item(click_focus);
269         }
270 }
271
272 void List::touch_press(int x, int y, unsigned finger)
273 {
274         if(finger==0)
275         {
276                 dragging = true;
277                 drag_start_x = x;
278                 drag_start_y = y;
279         }
280 }
281
282 void List::touch_release(int x, int y, unsigned finger)
283 {
284         if(finger==0)
285         {
286                 int dx = x-drag_start_x;
287                 int dy = y-drag_start_y;
288                 if(dx*dx+dy*dy<25)
289                 {
290                         Container::touch_press(drag_start_x, drag_start_y, finger);
291                         if(touch_focus)
292                                 set_selected_item(touch_focus);
293                         Container::touch_motion(x, y, finger);
294                         Container::touch_release(x, y, finger);
295                 }
296                 dragging = false;
297         }
298 }
299
300 void List::touch_motion(int, int y, unsigned finger)
301 {
302         if(finger==0 && !items.empty() && dragging)
303         {
304                 int dy = y-drag_start_y;
305                 if(dy>0 && first<max_scroll)
306                 {
307                         int item_h = items[first]->get_geometry().h;
308                         if(dy>item_h)
309                         {
310                                 drag_start_y += item_h;
311                                 slider.set_value(max_scroll-(first+1));
312                         }
313                 }
314                 else if(dy<0 && first>0)
315                 {
316                         int item_h = items[first-1]->get_geometry().h;
317                         if(-dy>item_h)
318                         {
319                                 drag_start_y -= item_h;
320                                 slider.set_value(max_scroll-(first-1));
321                         }
322                 }
323         }
324 }
325
326 void List::focus_in()
327 {
328         Container::focus_in();
329         if(focus_index>=0 && items[focus_index]->is_visible())
330                 set_input_focus(items[focus_index]);
331         else
332         {
333                 if(sel_index>=0 && items[sel_index]->is_visible())
334                         set_focus_index(sel_index);
335                 else if(!items.empty())
336                         set_focus_index(first);
337         }
338 }
339
340 bool List::navigate(Navigation nav)
341 {
342         if((nav==NAV_UP || nav==NAV_DOWN) && !items.empty())
343                 move_focus(nav, true);
344         else if(nav==NAV_ACTIVATE)
345                 set_selected_index(focus_index);
346         else
347                 return false;
348
349         return true;
350 }
351
352 void List::move_focus(Navigation nav, bool select)
353 {
354         if(nav==NAV_UP)
355         {
356                 if(focus_index>0)
357                         set_focus_index(focus_index-1);
358         }
359         else if(nav==NAV_DOWN)
360         {
361                 if(static_cast<unsigned>(focus_index+1)<items.size())
362                         set_focus_index(focus_index+1);
363         }
364
365         if(select)
366                 set_selected_index(focus_index);
367 }
368
369 void List::set_focus_index(int i)
370 {
371         focus_index = i;
372         if(focus_index>=0)
373         {
374                 scroll_to_focus();
375                 if(state&FOCUS)
376                         set_input_focus(items[focus_index]);
377         }
378 }
379
380 void List::item_autosize_changed(Item *item)
381 {
382         item->autosize();
383         signal_autosize_changed.emit();
384         rebuild();
385 }
386
387 unsigned List::last_to_first(unsigned last) const
388 {
389         if(!style)
390                 return last;
391
392         unsigned view_h = geom.h;
393         if(const Part *items_part = style->get_part("items"))
394         {
395                 const Sides &margin = items_part->get_margin();
396                 view_h -= margin.top+margin.bottom;
397         }
398
399         unsigned items_h = 0;
400         for(unsigned i=last; i<items.size(); --i)
401         {
402                 items_h += items[i]->get_geometry().h;
403                 if(items_h>view_h)
404                         return min(i+1, last);
405         }
406
407         return 0;
408 }
409
410 void List::check_view_range()
411 {
412         if(!style)
413                 return;
414
415         if(items.empty())
416                 max_scroll = 0;
417         else
418                 max_scroll = last_to_first(items.size()-1);
419
420         if(first>max_scroll)
421                 first = max_scroll;
422
423         slider.set_range(0, max_scroll);
424         slider.set_value(max_scroll-first);
425 }
426
427 void List::scroll_to_focus()
428 {
429         if(focus_index<0 || items[focus_index]->is_visible())
430                 return;
431
432         if(static_cast<unsigned>(focus_index)<first)
433                 slider.set_value(max_scroll-focus_index);
434         else
435                 slider.set_value(max_scroll-last_to_first(focus_index));
436 }
437
438 void List::slider_value_changed(double value)
439 {
440         if(max_scroll>0 && !ignore_slider_change)
441         {
442                 first = max_scroll-static_cast<unsigned>(value);
443                 rebuild();
444         }
445 }
446
447 void List::adjust_index(int &index, int pos, int change)
448 {
449         if(index>pos)
450                 index += change;
451         else if(index==pos)
452                 index = (change>0 ? index+change : -1);
453 }
454
455
456 List::DataObserver::DataObserver(List &l):
457         list(l)
458 {
459         list.data->signal_item_added.connect(sigc::mem_fun(this, &DataObserver::item_added));
460         list.data->signal_item_removed.connect(sigc::mem_fun(this, &DataObserver::item_removed));
461         list.data->signal_cleared.connect(sigc::mem_fun(this, &DataObserver::cleared));
462         list.data->signal_refresh_item.connect(sigc::mem_fun(this, &DataObserver::refresh_item));
463 }
464
465 void List::DataObserver::item_added(unsigned i)
466 {
467         adjust_index(list.sel_index, i, 1);
468         adjust_index(list.focus_index, i, 1);
469
470         Item *item = list.create_item(i);
471         list.items.insert(list.items.begin()+i, item);
472         list.items_changed();
473 }
474
475 void List::DataObserver::item_removed(unsigned i)
476 {
477         bool had_selection = (list.sel_index>=0);
478         adjust_index(list.sel_index, i, -1);
479         adjust_index(list.focus_index, i, -1);
480
481         delete list.items[i];
482         list.items.erase(list.items.begin()+i);
483         list.items_changed();
484
485         if(had_selection && list.sel_index<0)
486                 list.signal_selection_cleared.emit();
487 }
488
489 void List::DataObserver::cleared()
490 {
491         list.sel_index = -1;
492         list.focus_index = -1;
493         for(vector<Item *>::iterator i=list.items.begin(); i!=list.items.end(); ++i)
494                 delete *i;
495         list.items.clear();
496         list.items_changed();
497
498         list.signal_selection_cleared.emit();
499 }
500
501 void List::DataObserver::refresh_item(unsigned i)
502 {
503         delete list.items[i];
504         // Avoid stale pointer while create_item is executing
505         list.items[i] = 0;
506         list.items[i] = list.create_item(i);
507         list.items_changed();
508 }
509
510
511 void List::Item::autosize_special(const Part &part, Geometry &ageom) const
512 {
513         if(part.get_name()=="children")
514         {
515                 const Sides &margin = part.get_margin();
516                 for(list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i)
517                 {
518                         Geometry cgeom;
519                         (*i)->widget->autosize(cgeom);
520                         ageom.w = max(ageom.w, cgeom.x+cgeom.w+margin.right);
521                         ageom.h = max(ageom.h, cgeom.y+cgeom.h+margin.top);
522                 }
523         }
524 }
525
526 void List::Item::set_active(bool a)
527 {
528         set_state(ACTIVE, (a ? ACTIVE : NORMAL));
529 }
530
531 void List::Item::render_special(const Part &part, GL::Renderer &renderer) const
532 {
533         if(part.get_name()=="children")
534         {
535                 for(list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i)
536                         (*i)->widget->render(renderer);
537         }
538 }
539
540
541 void List::MultiColumnItem::check_widths(vector<unsigned> &widths) const
542 {
543         if(widths.size()<children.size())
544                 widths.resize(children.size(), 0);
545
546         unsigned n = 0;
547         for(list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i, ++n)
548         {
549                 Geometry cgeom;
550                 (*i)->widget->autosize(cgeom);
551                 // TODO invent a better way to specify spacings
552                 widths[n] = max(widths[n], cgeom.w+8);
553         }
554 }
555
556 void List::MultiColumnItem::set_widths(const vector<unsigned> &widths)
557 {
558         if(!style)
559                 return;
560
561         const Part *part = style->get_part("children");
562         if(!part)
563                 return;
564
565         const Sides &margin = part->get_margin();
566         int x = margin.left;
567         unsigned n = 0;
568         for(list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i, ++n)
569         {
570                 (*i)->widget->set_position(x, margin.bottom);
571                 x += widths[n];
572         }
573 }
574
575 void List::MultiColumnItem::on_style_change()
576 {
577         if(!style)
578                 return;
579
580         for(std::list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i)
581                 (*i)->widget->autosize();
582
583         vector<unsigned> widths;
584         List *list = static_cast<List *>(parent);
585         for(vector<Item *>::const_iterator i=list->items.begin(); i!=list->items.end(); ++i)
586                 if(*i!=this)
587                         if(MultiColumnItem *mci = dynamic_cast<MultiColumnItem *>(*i))
588                                 mci->check_widths(widths);
589
590         vector<unsigned> self_widths(widths);
591         check_widths(self_widths);
592         bool update_all = false;
593         for(unsigned i=0; (!update_all && i<widths.size() && i<self_widths.size()); ++i)
594                 update_all = self_widths[i]>widths[i];
595
596         if(update_all)
597         {
598                 for(vector<Item *>::const_iterator i=list->items.begin(); i!=list->items.end(); ++i)
599                         if(MultiColumnItem *mci = dynamic_cast<MultiColumnItem *>(*i))
600                                 mci->set_widths(self_widths);
601         }
602
603         set_widths(self_widths);
604 }
605
606
607 List::BasicItem::BasicItem(const string &text):
608         label(text)
609 {
610         add(label);
611 }
612
613 void List::BasicItem::on_style_change()
614 {
615         if(!style)
616                 return;
617
618         label.autosize();
619         if(const Part *part = style->get_part("children"))
620         {
621                 const Sides &margin = part->get_margin();
622                 label.set_position(margin.left, margin.bottom);
623         }
624 }
625
626
627 List::Loader::Loader(List &l):
628         DataFile::DerivedObjectLoader<List, Widget::Loader>(l)
629 {
630         add("item", &Loader::item);
631         add("view_size", &List::view_size);
632 }
633
634 void List::Loader::item(const string &v)
635 {
636         dynamic_cast<BasicListData<string> &>(*obj.data).append(v);
637 }
638
639 } // namespace GLtk
640 } // namespace Msp