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