]> git.tdb.fi Git - libs/gltk.git/blob - source/list.cpp
Avoid segfault with a single multi-column item
[libs/gltk.git] / source / list.cpp
1 #include <msp/debug/demangle.h>
2 #include <msp/gl/matrix.h>
3 #include <msp/gl/meshbuilder.h>
4 #include "graphic.h"
5 #include "list.h"
6 #include "part.h"
7 #include "style.h"
8 #include "text.h"
9 #include "vslider.h"
10
11 using namespace std;
12
13 namespace Msp {
14 namespace GLtk {
15
16 incompatible_data::incompatible_data(const type_info &ti):
17         logic_error("expected "+Debug::demangle(ti.name()))
18 { }
19
20
21 List::List():
22         data(new BasicListData<string>),
23         own_data(true)
24 {
25         init();
26 }
27
28 List::List(ListData &d):
29         data(&d),
30         own_data(false)
31 {
32         init();
33 }
34
35 void List::init()
36 {
37         item_factory = 0;
38         sel_index = -1;
39         first = 0;
40         max_scroll = 0;
41         view_size = 5;
42
43         observer = new DataObserver(*this);
44
45         add(slider);
46         slider.set_step(1);
47         slider.signal_value_changed.connect(sigc::mem_fun(this, &List::slider_value_changed));
48 }
49
50 List::~List()
51 {
52         delete item_factory;
53         delete observer;
54         if(own_data)
55                 delete data;
56 }
57
58 void List::autosize_special(const Part &part, Geometry &ageom) const
59 {
60         if(part.get_name()=="items")
61         {
62                 const Sides &margin = part.get_margin();
63
64                 unsigned max_w = 0;
65                 unsigned total_h = 0;
66                 for(unsigned i=0; i<items.size(); ++i)
67                 {
68                         Geometry igeom;
69                         items[i]->autosize(igeom);
70                         max_w = max(max_w, igeom.w);
71                         if(view_size==0 || i<view_size)
72                                 total_h += igeom.h;
73                 }
74
75                 if(!items.empty() && items.size()<view_size)
76                         total_h = total_h*view_size/items.size();
77
78                 ageom.w = max(ageom.w, max_w+margin.left+margin.right);
79                 ageom.h = max(ageom.h, total_h+margin.top+margin.bottom);
80         }
81 }
82
83 void List::set_data(ListData &d)
84 {
85         if(item_factory)
86                 item_factory->set_data(d);
87
88         delete observer;
89         if(own_data)
90                 delete data;
91
92         data = &d;
93         own_data = false;
94         observer = new DataObserver(*this);
95
96         for(vector<Item *>::iterator i=items.begin(); i!=items.end(); ++i)
97                 delete *i;
98         items.clear();
99         unsigned n_items = data->size();
100         for(unsigned i=0; i<n_items; ++i)
101         {
102                 Item *item = create_item(i);
103                 items.push_back(item);
104         }
105
106         items_changed();
107 }
108
109 void List::items_changed()
110 {
111         check_view_range();
112         signal_autosize_changed.emit();
113         reposition_items();
114 }
115
116 List::Item *List::create_item(unsigned index)
117 {
118         Item *item = 0; 
119         if(item_factory)
120                 item = item_factory->create_item(index);
121         else
122                 item = new BasicItem(data->get_string(index));
123         add(*item);
124         item->signal_autosize_changed.connect(sigc::mem_fun(this, &List::item_autosize_changed));
125         return item;
126 }
127
128 void List::set_view_size(unsigned s)
129 {
130         view_size = s;
131         signal_autosize_changed.emit();
132 }
133
134 void List::set_view_all()
135 {
136         set_view_size(0);
137 }
138
139 void List::set_selected_index(int i)
140 {
141         if(i>=static_cast<int>(data->size()))
142                 throw out_of_range("List::set_selected_index");
143
144         if(i==sel_index)
145                 return;
146
147         if(sel_index>=0)
148                 items[sel_index]->set_active(false);
149         if(i<0)
150                 sel_index = -1;
151         else
152         {
153                 sel_index = i;
154                 items[sel_index]->set_active(true);
155                 signal_item_selected.emit(sel_index);
156         }
157 }
158
159 void List::render_special(const Part &part, GL::Renderer &renderer) const
160 {
161         if(part.get_name()=="items")
162         {
163                 for(unsigned i=first; (i<items.size() && items[i]->is_visible()); ++i)
164                         items[i]->render(renderer);
165         }
166         else if(part.get_name()=="slider")
167                 slider.render(renderer);
168 }
169
170 void List::button_press(int x, int y, unsigned btn)
171 {
172         Container::button_press(x, y, btn);
173         if(click_focus && btn==1)
174         {
175                 for(unsigned i=first; (i<items.size() && items[i]->is_visible()); ++i)
176                         if(click_focus==items[i])
177                         {
178                                 set_selected_index(i);
179                                 break;
180                         }
181         }
182 }
183
184 void List::on_geometry_change()
185 {
186         reposition_slider();
187         reposition_items();
188
189         check_view_range();
190 }
191
192 void List::on_style_change()
193 {
194         if(!style)
195                 return;
196
197         reposition_slider();
198         reposition_items();
199
200         check_view_range();
201 }
202
203 void List::reposition_slider()
204 {
205         if(!style)
206                 return;
207
208         if(const Part *slider_part = style->get_part("slider"))
209         {
210                 Geometry sgeom = slider_part->get_geometry();
211                 slider_part->get_alignment().apply(sgeom, geom, slider_part->get_margin());
212                 slider.set_geometry(sgeom);
213         }
214 }
215
216 void List::item_autosize_changed()
217 {
218         signal_autosize_changed.emit();
219         reposition_items();
220 }
221
222 void List::reposition_items()
223 {
224         if(!style)
225                 return;
226
227         if(const Part *items_part = style->get_part("items"))
228         {
229                 const Sides &margin = items_part->get_margin();
230                 unsigned w = geom.w-min(geom.w, margin.left+margin.right);
231                 unsigned y = geom.h-min(geom.h, margin.top);
232                 for(unsigned i=0; i<items.size(); ++i)
233                 {
234                         if(i<first || !y)
235                                 items[i]->set_visible(false);
236                         else
237                         {
238                                 Geometry igeom;
239                                 items[i]->autosize(igeom);
240                                 if(igeom.h+margin.bottom<=y)
241                                 {
242                                         items[i]->set_visible(true);
243                                         y -= igeom.h;
244                                         igeom.x = margin.left;
245                                         igeom.y = y;
246                                         igeom.w = w;
247                                         items[i]->set_geometry(igeom);
248                                 }
249                                 else
250                                 {
251                                         items[i]->set_visible(false);
252                                         y = 0;
253                                 }
254                         }
255                 }
256         }
257 }
258
259 void List::check_view_range()
260 {
261         if(!style)
262                 return;
263
264         unsigned h = geom.h;
265         if(const Part *items_part = style->get_part("items"))
266         {
267                 const Sides &margin = items_part->get_margin();
268                 h -= margin.top+margin.bottom;
269         }
270
271         max_scroll = items.size();
272         for(unsigned i=items.size(); i-->0; )
273         {
274                 unsigned ih = items[i]->get_geometry().h;
275                 if(ih<=h)
276                 {
277                         h -= ih;
278                         --max_scroll;
279                 }
280         }
281
282         if(first>max_scroll)
283                 first = max_scroll;
284
285         slider.set_range(0, max_scroll);
286         slider.set_value(max_scroll-first);
287 }
288
289 void List::slider_value_changed(double value)
290 {
291         if(max_scroll>0)
292         {
293                 first = max_scroll-static_cast<unsigned>(value);
294                 reposition_items();
295         }
296 }
297
298
299 List::DataObserver::DataObserver(List &l):
300         list(l)
301 {
302         list.data->signal_item_added.connect(sigc::mem_fun(this, &DataObserver::item_added));
303         list.data->signal_item_removed.connect(sigc::mem_fun(this, &DataObserver::item_removed));
304         list.data->signal_cleared.connect(sigc::mem_fun(this, &DataObserver::cleared));
305         list.data->signal_refresh_item.connect(sigc::mem_fun(this, &DataObserver::refresh_item));
306 }
307
308 void List::DataObserver::item_added(unsigned i)
309 {
310         if(list.sel_index>=static_cast<int>(i))
311                 ++list.sel_index;
312
313         Item *item = list.create_item(i);
314         list.items.insert(list.items.begin()+i, item);
315         list.items_changed();
316 }
317
318 void List::DataObserver::item_removed(unsigned i)
319 {
320         if(list.sel_index>static_cast<int>(i))
321                 --list.sel_index;
322         else if(list.sel_index==static_cast<int>(i))
323                 list.sel_index = -1;
324
325         delete list.items[i];
326         list.items.erase(list.items.begin()+i);
327         list.items_changed();
328 }
329
330 void List::DataObserver::cleared()
331 {
332         list.sel_index = -1;
333         for(vector<Item *>::iterator i=list.items.begin(); i!=list.items.end(); ++i)
334                 delete *i;
335         list.items.clear();
336         list.items_changed();
337 }
338
339 void List::DataObserver::refresh_item(unsigned i)
340 {
341         delete list.items[i];
342         list.items[i] = list.create_item(i);
343         list.items_changed();
344 }
345
346
347 void List::Item::autosize_special(const Part &part, Geometry &ageom) const
348 {
349         if(part.get_name()=="children")
350         {
351                 const Sides &margin = part.get_margin();
352                 for(list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i)
353                 {
354                         Geometry cgeom;
355                         (*i)->widget->autosize(cgeom);
356                         ageom.w = max(ageom.w, cgeom.x+cgeom.w+margin.right);
357                         ageom.h = max(ageom.h, cgeom.y+cgeom.h+margin.top);
358                 }
359         }
360 }
361
362 void List::Item::set_active(bool a)
363 {
364         set_state(ACTIVE, (a ? ACTIVE : NORMAL));
365 }
366
367 void List::Item::render_special(const Part &part, GL::Renderer &renderer) const
368 {
369         if(part.get_name()=="children")
370         {
371                 for(list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i)
372                         (*i)->widget->render(renderer);
373         }
374 }
375
376
377 void List::MultiColumnItem::check_widths(vector<unsigned> &widths) const
378 {
379         if(widths.size()<children.size())
380                 widths.resize(children.size(), 0);
381
382         unsigned n = 0;
383         for(list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i, ++n)
384         {
385                 Geometry cgeom;
386                 (*i)->widget->autosize(cgeom);
387                 // TODO invent a better way to specify spacings
388                 widths[n] = max(widths[n], cgeom.w+8);
389         }
390 }
391
392 void List::MultiColumnItem::set_widths(const vector<unsigned> &widths)
393 {
394         if(!style)
395                 return;
396
397         const Part *part = style->get_part("children");
398         if(!part)
399                 return;
400
401         const Sides &margin = part->get_margin();
402         int x = margin.left;
403         unsigned n = 0;
404         for(list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i, ++n)
405         {
406                 (*i)->widget->set_position(x, margin.bottom);
407                 x += widths[n];
408         }
409 }
410
411 void List::MultiColumnItem::on_style_change()
412 {
413         if(!style)
414                 return;
415
416         for(std::list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i)
417                 (*i)->widget->autosize();
418
419         vector<unsigned> widths;
420         List *list = static_cast<List *>(parent);
421         for(vector<Item *>::const_iterator i=list->items.begin(); i!=list->items.end(); ++i)
422                 if(*i!=this)
423                         if(MultiColumnItem *mci = dynamic_cast<MultiColumnItem *>(*i))
424                                 mci->check_widths(widths);
425
426         vector<unsigned> self_widths(widths);
427         check_widths(self_widths);
428         bool update_all = false;
429         for(unsigned i=0; (!update_all && i<widths.size() && i<self_widths.size()); ++i)
430                 update_all = self_widths[i]>widths[i];
431
432         if(update_all)
433         {
434                 for(vector<Item *>::const_iterator i=list->items.begin(); i!=list->items.end(); ++i)
435                         if(MultiColumnItem *mci = dynamic_cast<MultiColumnItem *>(*i))
436                                 mci->set_widths(self_widths);
437         }
438         else
439                 set_widths(self_widths);
440 }
441
442
443 List::BasicItem::BasicItem(const string &text):
444         label(text)
445 {
446         add(label);
447 }
448
449 void List::BasicItem::on_style_change()
450 {
451         if(!style)
452                 return;
453
454         label.autosize();
455         if(const Part *part = style->get_part("children"))
456         {
457                 const Sides &margin = part->get_margin();
458                 label.set_position(margin.left, margin.bottom);
459         }
460 }
461
462
463 List::Loader::Loader(List &l):
464         DataFile::DerivedObjectLoader<List, Widget::Loader>(l)
465 {
466         add("item", &Loader::item);
467         add("view_size", &List::view_size);
468 }
469
470 void List::Loader::item(const string &v)
471 {
472         dynamic_cast<BasicListData<string> &>(*obj.data).append(v);
473 }
474
475 } // namespace GLtk
476 } // namespace Msp