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