]> git.tdb.fi Git - libs/gltk.git/blob - source/list.cpp
c5dddf5aaac8533c0f40bec86ed53f7e171c6e37
[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->autosize();
125         item->signal_autosize_changed.connect(sigc::mem_fun(this, &List::item_autosize_changed));
126         return item;
127 }
128
129 void List::set_view_size(unsigned s)
130 {
131         view_size = s;
132         signal_autosize_changed.emit();
133 }
134
135 void List::set_view_all()
136 {
137         set_view_size(0);
138 }
139
140 void List::set_selected_index(int i)
141 {
142         if(i>=static_cast<int>(data->size()))
143                 throw out_of_range("List::set_selected_index");
144
145         if(i==sel_index)
146                 return;
147
148         if(sel_index>=0)
149                 items[sel_index]->set_active(false);
150         if(i<0)
151                 sel_index = -1;
152         else
153         {
154                 sel_index = i;
155                 items[sel_index]->set_active(true);
156                 signal_item_selected.emit(sel_index);
157         }
158 }
159
160 void List::render_special(const Part &part, GL::Renderer &renderer) const
161 {
162         if(part.get_name()=="items")
163         {
164                 for(unsigned i=first; (i<items.size() && items[i]->is_visible()); ++i)
165                         items[i]->render(renderer);
166         }
167         else if(part.get_name()=="slider")
168                 slider.render(renderer);
169 }
170
171 void List::button_press(int x, int y, unsigned btn)
172 {
173         Container::button_press(x, y, btn);
174         if(click_focus && btn==1)
175         {
176                 for(unsigned i=first; (i<items.size() && items[i]->is_visible()); ++i)
177                         if(click_focus==items[i])
178                         {
179                                 set_selected_index(i);
180                                 break;
181                         }
182         }
183 }
184
185 void List::on_geometry_change()
186 {
187         reposition_slider();
188         reposition_items();
189
190         check_view_range();
191 }
192
193 void List::on_style_change()
194 {
195         if(!style)
196                 return;
197
198         reposition_slider();
199         reposition_items();
200
201         check_view_range();
202 }
203
204 void List::reposition_slider()
205 {
206         if(!style)
207                 return;
208
209         if(const Part *slider_part = style->get_part("slider"))
210         {
211                 Geometry sgeom = slider_part->get_geometry();
212                 slider_part->get_alignment().apply(sgeom, geom, slider_part->get_margin());
213                 slider.set_geometry(sgeom);
214         }
215 }
216
217 void List::item_autosize_changed()
218 {
219         signal_autosize_changed.emit();
220         reposition_items();
221 }
222
223 void List::reposition_items()
224 {
225         if(!style)
226                 return;
227
228         if(const Part *items_part = style->get_part("items"))
229         {
230                 const Sides &margin = items_part->get_margin();
231                 unsigned w = geom.w-min(geom.w, margin.left+margin.right);
232                 unsigned y = geom.h-min(geom.h, margin.top);
233                 for(unsigned i=0; i<items.size(); ++i)
234                 {
235                         if(i<first || !y)
236                                 items[i]->set_visible(false);
237                         else
238                         {
239                                 Geometry igeom;
240                                 items[i]->autosize(igeom);
241                                 if(igeom.h+margin.bottom<=y)
242                                 {
243                                         items[i]->set_visible(true);
244                                         y -= igeom.h;
245                                         igeom.x = margin.left;
246                                         igeom.y = y;
247                                         igeom.w = w;
248                                         items[i]->set_geometry(igeom);
249                                 }
250                                 else
251                                 {
252                                         items[i]->set_visible(false);
253                                         y = 0;
254                                 }
255                         }
256                 }
257         }
258 }
259
260 void List::check_view_range()
261 {
262         if(!style)
263                 return;
264
265         unsigned h = geom.h;
266         if(const Part *items_part = style->get_part("items"))
267         {
268                 const Sides &margin = items_part->get_margin();
269                 h -= margin.top+margin.bottom;
270         }
271
272         max_scroll = items.size();
273         for(unsigned i=items.size(); i-->0; )
274         {
275                 unsigned ih = items[i]->get_geometry().h;
276                 if(ih<=h)
277                 {
278                         h -= ih;
279                         --max_scroll;
280                 }
281         }
282
283         if(first>max_scroll)
284                 first = max_scroll;
285
286         slider.set_range(0, max_scroll);
287         slider.set_value(max_scroll-first);
288 }
289
290 void List::slider_value_changed(double value)
291 {
292         if(max_scroll>0)
293         {
294                 first = max_scroll-static_cast<unsigned>(value);
295                 reposition_items();
296         }
297 }
298
299
300 List::DataObserver::DataObserver(List &l):
301         list(l)
302 {
303         list.data->signal_item_added.connect(sigc::mem_fun(this, &DataObserver::item_added));
304         list.data->signal_item_removed.connect(sigc::mem_fun(this, &DataObserver::item_removed));
305         list.data->signal_cleared.connect(sigc::mem_fun(this, &DataObserver::cleared));
306         list.data->signal_refresh_item.connect(sigc::mem_fun(this, &DataObserver::refresh_item));
307 }
308
309 void List::DataObserver::item_added(unsigned i)
310 {
311         if(list.sel_index>=static_cast<int>(i))
312                 ++list.sel_index;
313
314         Item *item = list.create_item(i);
315         list.items.insert(list.items.begin()+i, item);
316         list.items_changed();
317 }
318
319 void List::DataObserver::item_removed(unsigned i)
320 {
321         if(list.sel_index>static_cast<int>(i))
322                 --list.sel_index;
323         else if(list.sel_index==static_cast<int>(i))
324                 list.sel_index = -1;
325
326         delete list.items[i];
327         list.items.erase(list.items.begin()+i);
328         list.items_changed();
329 }
330
331 void List::DataObserver::cleared()
332 {
333         list.sel_index = -1;
334         for(vector<Item *>::iterator i=list.items.begin(); i!=list.items.end(); ++i)
335                 delete *i;
336         list.items.clear();
337         list.items_changed();
338 }
339
340 void List::DataObserver::refresh_item(unsigned i)
341 {
342         delete list.items[i];
343         list.items[i] = list.create_item(i);
344         list.items_changed();
345 }
346
347
348 void List::Item::autosize_special(const Part &part, Geometry &ageom) const
349 {
350         if(part.get_name()=="children")
351         {
352                 const Sides &margin = part.get_margin();
353                 for(list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i)
354                 {
355                         const Geometry &cgeom = (*i)->widget->get_geometry();
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 List::BasicItem::BasicItem(const string &text):
378         label(text)
379 {
380         add(label);
381 }
382
383 void List::BasicItem::on_style_change()
384 {
385         if(!style)
386                 return;
387
388         label.autosize();
389         if(const Part *part = style->get_part("children"))
390         {
391                 const Sides &margin = part->get_margin();
392                 label.set_position(margin.left, margin.bottom);
393         }
394 }
395
396
397 List::Loader::Loader(List &l):
398         DataFile::DerivedObjectLoader<List, Widget::Loader>(l)
399 {
400         add("item", &Loader::item);
401         add("view_size", &List::view_size);
402 }
403
404 void List::Loader::item(const string &v)
405 {
406         dynamic_cast<BasicListData<string> &>(*obj.data).append(v);
407 }
408
409 } // namespace GLtk
410 } // namespace Msp