]> git.tdb.fi Git - libs/gltk.git/blob - source/list.cpp
Ensure that list items are autosized properly in all cases
[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)
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                         const Geometry &igeom = items[i]->get_geometry();
69                         max_w = max(max_w, igeom.w);
70                         if(view_size==0 || i<view_size)
71                                 total_h += igeom.h;
72                 }
73
74                 if(!items.empty() && items.size()<view_size)
75                         total_h = total_h*view_size/items.size();
76
77                 ageom.w = max(ageom.w, max_w+margin.left+margin.right);
78                 ageom.h = max(ageom.h, total_h+margin.top+margin.bottom);
79         }
80 }
81
82 void List::set_data(ListData &d)
83 {
84         if(item_factory)
85                 item_factory->set_data(d);
86
87         delete observer;
88         if(own_data)
89                 delete data;
90
91         data = &d;
92         own_data = false;
93         observer = new DataObserver(*this);
94
95         for(vector<Item *>::iterator i=items.begin(); i!=items.end(); ++i)
96                 delete *i;
97         items.clear();
98         unsigned n_items = data->size();
99         for(unsigned i=0; i<n_items; ++i)
100         {
101                 Item *item = create_item(i);
102                 items.push_back(item);
103         }
104
105         items_changed();
106 }
107
108 void List::items_changed()
109 {
110         check_view_range();
111         signal_autosize_changed.emit();
112         reposition_items();
113 }
114
115 List::Item *List::create_item(unsigned index)
116 {
117         Item *item = 0; 
118         if(item_factory)
119                 item = item_factory->create_item(index);
120         else
121                 item = new BasicItem(data->get_string(index));
122         add(*item);
123         item->autosize();
124         item->signal_autosize_changed.connect(sigc::bind(sigc::mem_fun(this, &List::item_autosize_changed), item));
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(sel_index>=0)
145                 items[sel_index]->set_active(false);
146         if(i<0)
147                 sel_index = -1;
148         else if(i!=sel_index)
149         {
150                 sel_index = i;
151                 items[sel_index]->set_active(true);
152                 signal_item_selected.emit(sel_index);
153         }
154 }
155
156 void List::render_special(const Part &part, GL::Renderer &renderer) const
157 {
158         if(part.get_name()=="items")
159         {
160                 for(unsigned i=first; (i<items.size() && items[i]->is_visible()); ++i)
161                         items[i]->render(renderer);
162         }
163         else if(part.get_name()=="slider")
164                 slider.render(renderer);
165 }
166
167 void List::button_press(int x, int y, unsigned btn)
168 {
169         Container::button_press(x, y, btn);
170         if(click_focus && btn==1)
171         {
172                 for(unsigned i=first; (i<items.size() && items[i]->is_visible()); ++i)
173                         if(click_focus==items[i])
174                         {
175                                 set_selected_index(i);
176                                 break;
177                         }
178         }
179 }
180
181 void List::on_geometry_change()
182 {
183         reposition_slider();
184         reposition_items();
185
186         check_view_range();
187 }
188
189 void List::on_style_change()
190 {
191         if(!style)
192                 return;
193
194         reposition_slider();
195         reposition_items();
196
197         check_view_range();
198 }
199
200 void List::reposition_slider()
201 {
202         if(!style)
203                 return;
204
205         if(const Part *slider_part = style->get_part("slider"))
206         {
207                 Geometry sgeom = slider_part->get_geometry();
208                 slider_part->get_alignment().apply(sgeom, geom, slider_part->get_margin());
209                 slider.set_geometry(sgeom);
210         }
211 }
212
213 void List::item_autosize_changed(Item *item)
214 {
215         signal_autosize_changed.emit();
216         item->autosize();
217         reposition_items();
218 }
219
220 void List::reposition_items()
221 {
222         if(!style)
223                 return;
224
225         if(const Part *items_part = style->get_part("items"))
226         {
227                 const Sides &margin = items_part->get_margin();
228                 unsigned w = geom.w-margin.left-margin.right;
229                 unsigned y = geom.h-margin.top;
230                 for(unsigned i=0; i<items.size(); ++i)
231                 {
232                         if(i<first || !y)
233                                 items[i]->set_visible(false);
234                         else
235                         {
236                                 Geometry igeom = items[i]->get_geometry();
237                                 if(igeom.h+margin.bottom<=y)
238                                 {
239                                         items[i]->set_visible(true);
240                                         y -= igeom.h;
241                                         igeom.x = margin.left;
242                                         igeom.y = y;
243                                         igeom.w = w;
244                                         items[i]->set_geometry(igeom);
245                                 }
246                                 else
247                                 {
248                                         items[i]->set_visible(false);
249                                         y = 0;
250                                 }
251                         }
252                 }
253         }
254 }
255
256 void List::check_view_range()
257 {
258         if(!style)
259                 return;
260
261         unsigned h = geom.h;
262         if(const Part *items_part = style->get_part("items"))
263         {
264                 const Sides &margin = items_part->get_margin();
265                 h -= margin.top+margin.bottom;
266         }
267
268         max_scroll = items.size();
269         for(unsigned i=items.size(); i-->0; )
270         {
271                 unsigned ih = items[i]->get_geometry().h;
272                 if(ih<=h)
273                 {
274                         h -= ih;
275                         --max_scroll;
276                 }
277         }
278
279         if(first>max_scroll)
280                 first = max_scroll;
281
282         slider.set_range(0, max_scroll);
283         slider.set_value(max_scroll-first);
284 }
285
286 void List::slider_value_changed(double value)
287 {
288         if(max_scroll>0)
289         {
290                 first = max_scroll-static_cast<unsigned>(value);
291                 reposition_items();
292         }
293 }
294
295
296 List::DataObserver::DataObserver(List &l):
297         list(l)
298 {
299         list.data->signal_item_added.connect(sigc::mem_fun(this, &DataObserver::item_added));
300         list.data->signal_item_removed.connect(sigc::mem_fun(this, &DataObserver::item_removed));
301         list.data->signal_cleared.connect(sigc::mem_fun(this, &DataObserver::cleared));
302         list.data->signal_refresh_item.connect(sigc::mem_fun(this, &DataObserver::refresh_item));
303 }
304
305 void List::DataObserver::item_added(unsigned i)
306 {
307         if(list.sel_index>=static_cast<int>(i))
308                 ++list.sel_index;
309
310         Item *item = list.create_item(i);
311         list.items.insert(list.items.begin()+i, item);
312         list.items_changed();
313 }
314
315 void List::DataObserver::item_removed(unsigned i)
316 {
317         if(list.sel_index>static_cast<int>(i))
318                 --list.sel_index;
319         else if(list.sel_index==static_cast<int>(i))
320                 list.sel_index = -1;
321
322         delete list.items[i];
323         list.items.erase(list.items.begin()+i);
324         list.items_changed();
325 }
326
327 void List::DataObserver::cleared()
328 {
329         list.sel_index = -1;
330         for(vector<Item *>::iterator i=list.items.begin(); i!=list.items.end(); ++i)
331                 delete *i;
332         list.items.clear();
333         list.items_changed();
334 }
335
336 void List::DataObserver::refresh_item(unsigned i)
337 {
338         delete list.items[i];
339         list.items[i] = list.create_item(i);
340         list.items_changed();
341 }
342
343
344 void List::Item::autosize_special(const Part &part, Geometry &ageom)
345 {
346         if(part.get_name()=="children")
347         {
348                 const Sides &margin = part.get_margin();
349                 for(list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i)
350                 {
351                         const Geometry &cgeom = (*i)->widget->get_geometry();
352                         ageom.w = max(ageom.w, cgeom.x+cgeom.w+margin.right);
353                         ageom.h = max(ageom.h, cgeom.y+cgeom.h+margin.top);
354                 }
355         }
356 }
357
358 void List::Item::set_active(bool a)
359 {
360         set_state(ACTIVE, (a ? ACTIVE : NORMAL));
361 }
362
363 void List::Item::render_special(const Part &part, GL::Renderer &renderer) const
364 {
365         if(part.get_name()=="children")
366         {
367                 for(list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i)
368                         (*i)->widget->render(renderer);
369         }
370 }
371
372
373 List::BasicItem::BasicItem(const string &text):
374         label(text)
375 {
376         add(label);
377 }
378
379 void List::BasicItem::on_style_change()
380 {
381         if(!style)
382                 return;
383
384         label.autosize();
385         if(const Part *part = style->get_part("children"))
386         {
387                 const Sides &margin = part->get_margin();
388                 label.set_position(margin.left, margin.bottom);
389         }
390 }
391
392
393 List::Loader::Loader(List &l):
394         DataFile::DerivedObjectLoader<List, Widget::Loader>(l)
395 {
396         add("item", &Loader::item);
397         add("view_size", &List::view_size);
398 }
399
400 void List::Loader::item(const string &v)
401 {
402         dynamic_cast<BasicListData<string> &>(*obj.data).append(v);
403 }
404
405 } // namespace GLtk
406 } // namespace Msp