]> git.tdb.fi Git - libs/gltk.git/blob - source/list.cpp
Add a persistent view size attribute to List
[libs/gltk.git] / source / list.cpp
1 #include <msp/gl/matrix.h>
2 #include <msp/gl/meshbuilder.h>
3 #include "graphic.h"
4 #include "list.h"
5 #include "part.h"
6 #include "style.h"
7 #include "text.h"
8 #include "vslider.h"
9
10 using namespace std;
11
12 namespace Msp {
13 namespace GLtk {
14
15 List::List():
16         data(new BasicListData<string>),
17         own_data(true)
18 {
19         init();
20 }
21
22 List::List(ListData &d):
23         data(&d),
24         own_data(false)
25 {
26         init();
27 }
28
29 void List::init()
30 {
31         sel_index = -1;
32         first = 0;
33         max_scroll = 0;
34         view_size = 5;
35
36         observer = new DataObserver(*this);
37
38         add(slider);
39         slider.set_step(1);
40         slider.signal_value_changed.connect(sigc::mem_fun(this, &List::slider_value_changed));
41 }
42
43 List::~List()
44 {
45         delete observer;
46         if(own_data)
47                 delete data;
48 }
49
50 void List::autosize()
51 {
52         if(!style)
53                 return;
54
55         Widget::autosize();
56
57         if(const Part *items_part = style->get_part("items"))
58         {
59                 const Sides &margin = items_part->get_margin();
60
61                 unsigned max_w = 0;
62                 unsigned total_h = 0;
63                 for(unsigned i=0; i<items.size(); ++i)
64                 {
65                         items[i]->autosize();
66                         const Geometry &igeom = items[i]->get_geometry();
67                         max_w = max(max_w, igeom.w);
68                         if(view_size==0 || i<view_size)
69                                 total_h += igeom.h;
70                 }
71
72                 if(!items.empty() && items.size()<view_size)
73                         total_h = total_h*view_size/items.size();
74
75                 geom.w = max(geom.w, max_w+margin.left+margin.right);
76                 geom.h = max(geom.h, total_h+margin.top+margin.bottom);
77         }
78
79         check_view_range();
80         rebuild();
81 }
82
83 void List::set_data(ListData &d)
84 {
85         delete observer;
86         if(own_data)
87                 delete data;
88
89         data = &d;
90         own_data = false;
91         observer = new DataObserver(*this);
92
93         items_changed();
94 }
95
96 void List::items_changed()
97 {
98         check_view_range();
99         signal_autosize_changed.emit();
100         reposition_items();
101 }
102
103 List::Item *List::create_item(unsigned index)
104 {
105         return new BasicItem(data->get_string(index));
106 }
107
108 void List::set_view_size(unsigned s)
109 {
110         view_size = s;
111         signal_autosize_changed.emit();
112 }
113
114 void List::set_view_all()
115 {
116         set_view_size(0);
117 }
118
119 void List::set_selected_index(int i)
120 {
121         if(i>static_cast<int>(data->size()))
122                 throw out_of_range("List::set_selected_index");
123
124         if(sel_index>=0)
125                 items[sel_index]->set_active(false);
126         if(i<0)
127                 sel_index = -1;
128         else
129         {
130                 sel_index = i;
131                 items[sel_index]->set_active(true);
132                 signal_item_selected.emit(sel_index);
133         }
134 }
135
136 void List::render_special(const Part &part, GL::Renderer &renderer) const
137 {
138         if(part.get_name()=="items")
139         {
140                 for(unsigned i=first; (i<items.size() && items[i]->is_visible()); ++i)
141                         items[i]->render(renderer);
142         }
143         else if(part.get_name()=="slider")
144                 slider.render(renderer);
145 }
146
147 void List::button_press(int x, int y, unsigned btn)
148 {
149         Container::button_press(x, y, btn);
150         if(click_focus && btn==1)
151         {
152                 for(unsigned i=first; (i<items.size() && items[i]->is_visible()); ++i)
153                         if(click_focus==items[i])
154                         {
155                                 set_selected_index(i);
156                                 break;
157                         }
158         }
159 }
160
161 void List::on_geometry_change()
162 {
163         reposition_slider();
164         reposition_items();
165
166         check_view_range();
167 }
168
169 void List::on_style_change()
170 {
171         if(!style)
172                 return;
173
174         reposition_slider();
175         reposition_items();
176
177         check_view_range();
178 }
179
180 void List::reposition_slider()
181 {
182         if(!style)
183                 return;
184
185         if(const Part *slider_part = style->get_part("slider"))
186         {
187                 Geometry sgeom = slider_part->get_geometry();
188                 slider_part->get_alignment().apply(sgeom, geom, slider_part->get_margin());
189                 slider.set_geometry(sgeom);
190         }
191 }
192
193 void List::reposition_items()
194 {
195         if(!style)
196                 return;
197
198         if(const Part *items_part = style->get_part("items"))
199         {
200                 const Sides &margin = items_part->get_margin();
201                 unsigned w = geom.w-margin.left-margin.right;
202                 unsigned y = geom.h-margin.top;
203                 for(unsigned i=0; i<items.size(); ++i)
204                 {
205                         if(i<first || !y)
206                                 items[i]->set_visible(false);
207                         else
208                         {
209                                 Geometry igeom = items[i]->get_geometry();
210                                 if(igeom.h+margin.bottom<=y)
211                                 {
212                                         items[i]->set_visible(true);
213                                         y -= igeom.h;
214                                         igeom.x = margin.left;
215                                         igeom.y = y;
216                                         igeom.w = w;
217                                         items[i]->set_geometry(igeom);
218                                 }
219                                 else
220                                 {
221                                         items[i]->set_visible(false);
222                                         y = 0;
223                                 }
224                         }
225                 }
226         }
227 }
228
229 void List::check_view_range()
230 {
231         if(!style)
232                 return;
233
234         unsigned h = geom.h;
235         if(const Part *items_part = style->get_part("items"))
236         {
237                 const Sides &margin = items_part->get_margin();
238                 h -= margin.top+margin.bottom;
239         }
240
241         max_scroll = items.size();
242         for(unsigned i=items.size(); i-->0; )
243         {
244                 unsigned ih = items[i]->get_geometry().h;
245                 if(ih<=h)
246                 {
247                         h -= ih;
248                         --max_scroll;
249                 }
250         }
251
252         if(first>max_scroll)
253                 first = max_scroll;
254
255         slider.set_range(0, max_scroll);
256         slider.set_value(max_scroll-first);
257 }
258
259 void List::slider_value_changed(double value)
260 {
261         if(max_scroll>0)
262         {
263                 first = max_scroll-static_cast<unsigned>(value);
264                 reposition_items();
265         }
266 }
267
268
269 List::DataObserver::DataObserver(List &l):
270         list(l)
271 {
272         list.data->signal_item_added.connect(sigc::mem_fun(this, &DataObserver::item_added));
273         list.data->signal_item_removed.connect(sigc::mem_fun(this, &DataObserver::item_removed));
274         list.data->signal_cleared.connect(sigc::mem_fun(this, &DataObserver::cleared));
275         list.data->signal_refresh_strings.connect(sigc::mem_fun(this, &DataObserver::refresh_strings));
276 }
277
278 void List::DataObserver::item_added(unsigned i)
279 {
280         if(list.sel_index>=static_cast<int>(i))
281                 ++list.sel_index;
282
283         Item *item = list.create_item(i);
284         list.add(*item);
285         list.items.insert(list.items.begin()+i, item);
286         list.items_changed();
287 }
288
289 void List::DataObserver::item_removed(unsigned i)
290 {
291         if(list.sel_index>static_cast<int>(i))
292                 --list.sel_index;
293         else if(list.sel_index==static_cast<int>(i))
294                 list.sel_index = -1;
295
296         delete list.items[i];
297         list.items.erase(list.items.begin()+i);
298         list.items_changed();
299 }
300
301 void List::DataObserver::cleared()
302 {
303         list.sel_index = -1;
304         for(vector<Item *>::iterator i=list.items.begin(); i!=list.items.end(); ++i)
305                 delete *i;
306         list.items.clear();
307         list.items_changed();
308 }
309
310 void List::DataObserver::refresh_strings()
311 {
312         list.items_changed();
313 }
314
315
316 void List::Item::autosize()
317 {
318         Widget::autosize();
319
320         if(const Part *part = style->get_part("children"))
321         {
322                 const Sides &margin = part->get_margin();
323                 for(list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i)
324                 {
325                         const Geometry &cgeom = (*i)->widget->get_geometry();
326                         geom.w = max(geom.w, cgeom.x+cgeom.w+margin.right);
327                         geom.h = max(geom.h, cgeom.y+cgeom.h+margin.top);
328                 }
329         }
330 }
331
332 void List::Item::set_active(bool a)
333 {
334         set_state(ACTIVE, (a ? ACTIVE : NORMAL));
335 }
336
337 void List::Item::render_special(const Part &part, GL::Renderer &renderer) const
338 {
339         if(part.get_name()=="children")
340         {
341                 for(list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i)
342                         (*i)->widget->render(renderer);
343         }
344 }
345
346
347 List::BasicItem::BasicItem(const string &text):
348         label(text)
349 {
350         add(label);
351 }
352
353 void List::BasicItem::on_style_change()
354 {
355         if(!style)
356                 return;
357
358         label.autosize();
359         if(const Part *part = style->get_part("children"))
360         {
361                 const Sides &margin = part->get_margin();
362                 label.set_position(margin.left, margin.bottom);
363         }
364 }
365
366
367 List::Loader::Loader(List &l):
368         DataFile::DerivedObjectLoader<List, Widget::Loader>(l)
369 {
370         add("item", &Loader::item);
371 }
372
373 void List::Loader::item(const string &v)
374 {
375         dynamic_cast<BasicListData<string> &>(*obj.data).append(v);
376 }
377
378 } // namespace GLtk
379 } // namespace Msp