]> git.tdb.fi Git - libs/gltk.git/blob - source/list.cpp
f61d7c26dec8f3ba194058050a4411f423e6e646
[libs/gltk.git] / source / list.cpp
1 #include <msp/core/raii.h>
2 #include <msp/debug/demangle.h>
3 #include <msp/gl/matrix.h>
4 #include <msp/gl/meshbuilder.h>
5 #include "graphic.h"
6 #include "list.h"
7 #include "part.h"
8 #include "style.h"
9 #include "text.h"
10 #include "vslider.h"
11
12 using namespace std;
13
14 namespace Msp {
15 namespace GLtk {
16
17 incompatible_data::incompatible_data(const type_info &ti):
18         logic_error("expected "+Debug::demangle(ti.name()))
19 { }
20
21
22 List::List():
23         data(new BasicListData<string>),
24         own_data(true)
25 {
26         init();
27 }
28
29 List::List(ListData &d):
30         data(&d),
31         own_data(false)
32 {
33         init();
34 }
35
36 void List::init()
37 {
38         item_factory = 0;
39         sel_index = -1;
40         first = 0;
41         max_scroll = 0;
42         view_size = 5;
43         ignore_slider_change = false;
44
45         observer = new DataObserver(*this);
46
47         add(slider);
48         slider.set_step(1);
49         slider.signal_value_changed.connect(sigc::mem_fun(this, &List::slider_value_changed));
50 }
51
52 List::~List()
53 {
54         delete item_factory;
55         delete observer;
56         if(own_data)
57                 delete data;
58 }
59
60 void List::autosize_special(const Part &part, Geometry &ageom) const
61 {
62         if(part.get_name()=="items")
63         {
64                 const Sides &margin = part.get_margin();
65
66                 unsigned max_w = 0;
67                 unsigned total_h = 0;
68                 for(unsigned i=0; i<items.size(); ++i)
69                 {
70                         Geometry igeom;
71                         items[i]->autosize(igeom);
72                         max_w = max(max_w, igeom.w);
73                         if(view_size==0 || i<view_size)
74                                 total_h += igeom.h;
75                 }
76
77                 if(!items.empty() && items.size()<view_size)
78                         total_h = total_h*view_size/items.size();
79
80                 ageom.w = max(ageom.w, max_w+margin.left+margin.right);
81                 ageom.h = max(ageom.h, total_h+margin.top+margin.bottom);
82         }
83         else if(part.get_name()=="slider")
84                 autosize_child(slider, part, ageom);
85 }
86
87 void List::set_data(ListData &d)
88 {
89         if(item_factory)
90                 item_factory->set_data(d);
91
92         delete observer;
93         if(own_data)
94                 delete data;
95
96         data = &d;
97         own_data = false;
98         observer = new DataObserver(*this);
99
100         for(vector<Item *>::iterator i=items.begin(); i!=items.end(); ++i)
101                 delete *i;
102         items.clear();
103         unsigned n_items = data->size();
104         for(unsigned i=0; i<n_items; ++i)
105         {
106                 Item *item = create_item(i);
107                 items.push_back(item);
108         }
109
110         items_changed();
111 }
112
113 void List::items_changed()
114 {
115         signal_autosize_changed.emit();
116         rebuild();
117 }
118
119 List::Item *List::create_item(unsigned index)
120 {
121         Item *item = 0;
122         if(item_factory)
123                 item = item_factory->create_item(index);
124         else
125                 item = new BasicItem(data->get_string(index));
126         if(static_cast<int>(index)==sel_index)
127                 item->set_active(true);
128         add(*item);
129         item->autosize();
130         item->signal_autosize_changed.connect(sigc::bind(sigc::mem_fun(this, &List::item_autosize_changed), item));
131         return item;
132 }
133
134 void List::set_view_size(unsigned s)
135 {
136         view_size = s;
137         signal_autosize_changed.emit();
138 }
139
140 void List::set_view_all()
141 {
142         set_view_size(0);
143 }
144
145 void List::set_selected_index(int i)
146 {
147         if(i>=static_cast<int>(data->size()))
148                 throw out_of_range("List::set_selected_index");
149
150         if(i==sel_index)
151                 return;
152
153         if(sel_index>=0)
154                 items[sel_index]->set_active(false);
155         if(i<0)
156                 sel_index = -1;
157         else
158         {
159                 sel_index = i;
160                 items[sel_index]->set_active(true);
161                 signal_item_selected.emit(sel_index);
162         }
163 }
164
165 void List::rebuild_special(const Part &part)
166 {
167         if(part.get_name()=="slider")
168                 reposition_child(slider, part);
169         else if(part.get_name()=="items")
170         {
171                 SetFlag flag(ignore_slider_change);
172                 check_view_range();
173
174                 const Sides &margin = part.get_margin();
175                 unsigned w = geom.w-min(geom.w, margin.left+margin.right);
176                 unsigned y = geom.h-min(geom.h, margin.top);
177                 for(unsigned i=0; i<items.size(); ++i)
178                 {
179                         if(i<first || !y)
180                                 items[i]->set_visible(false);
181                         else
182                         {
183                                 Geometry igeom = items[i]->get_geometry();
184                                 if(igeom.h+margin.bottom<=y)
185                                 {
186                                         items[i]->set_visible(true);
187                                         y -= igeom.h;
188                                         igeom.x = margin.left;
189                                         igeom.y = y;
190                                         igeom.w = w;
191                                         items[i]->set_geometry(igeom);
192                                 }
193                                 else
194                                 {
195                                         items[i]->set_visible(false);
196                                         y = 0;
197                                 }
198                         }
199                 }
200         }
201
202         Widget::rebuild_special(part);
203 }
204
205 void List::render_special(const Part &part, GL::Renderer &renderer) const
206 {
207         if(part.get_name()=="items")
208         {
209                 for(unsigned i=first; (i<items.size() && items[i]->is_visible()); ++i)
210                         items[i]->render(renderer);
211         }
212         else if(part.get_name()=="slider")
213                 slider.render(renderer);
214 }
215
216 void List::button_press(int x, int y, unsigned btn)
217 {
218         if(btn==4 || btn==5)
219         {
220                 unsigned change = 3;
221                 if(btn==4)
222                 {
223                         change = min(first, change);
224                         slider.set_value(max_scroll-(first-change));
225                 }
226                 else if(btn==5)
227                 {
228                         change = min(max_scroll-first, change);
229                         slider.set_value(max_scroll-(first+change));
230                 }
231         }
232         else
233         {
234                 Container::button_press(x, y, btn);
235                 if(click_focus && btn==1)
236                 {
237                         for(unsigned i=first; (i<items.size() && items[i]->is_visible()); ++i)
238                                 if(click_focus==items[i])
239                                 {
240                                         set_selected_index(i);
241                                         break;
242                                 }
243                 }
244         }
245 }
246
247 void List::item_autosize_changed(Item *item)
248 {
249         item->autosize();
250         signal_autosize_changed.emit();
251         rebuild();
252 }
253
254 void List::check_view_range()
255 {
256         if(!style)
257                 return;
258
259         unsigned h = geom.h;
260         if(const Part *items_part = style->get_part("items"))
261         {
262                 const Sides &margin = items_part->get_margin();
263                 h -= margin.top+margin.bottom;
264         }
265
266         max_scroll = items.size();
267         for(unsigned i=items.size(); i-->0; )
268         {
269                 unsigned ih = items[i]->get_geometry().h;
270                 if(ih<=h)
271                 {
272                         h -= ih;
273                         --max_scroll;
274                 }
275         }
276
277         if(first>max_scroll)
278                 first = max_scroll;
279
280         slider.set_range(0, max_scroll);
281         slider.set_value(max_scroll-first);
282 }
283
284 void List::slider_value_changed(double value)
285 {
286         if(max_scroll>0 && !ignore_slider_change)
287         {
288                 first = max_scroll-static_cast<unsigned>(value);
289                 rebuild();
290         }
291 }
292
293
294 List::DataObserver::DataObserver(List &l):
295         list(l)
296 {
297         list.data->signal_item_added.connect(sigc::mem_fun(this, &DataObserver::item_added));
298         list.data->signal_item_removed.connect(sigc::mem_fun(this, &DataObserver::item_removed));
299         list.data->signal_cleared.connect(sigc::mem_fun(this, &DataObserver::cleared));
300         list.data->signal_refresh_item.connect(sigc::mem_fun(this, &DataObserver::refresh_item));
301 }
302
303 void List::DataObserver::item_added(unsigned i)
304 {
305         if(list.sel_index>=static_cast<int>(i))
306                 ++list.sel_index;
307
308         Item *item = list.create_item(i);
309         list.items.insert(list.items.begin()+i, item);
310         list.items_changed();
311 }
312
313 void List::DataObserver::item_removed(unsigned i)
314 {
315         if(list.sel_index>static_cast<int>(i))
316                 --list.sel_index;
317         else if(list.sel_index==static_cast<int>(i))
318                 list.sel_index = -1;
319
320         delete list.items[i];
321         list.items.erase(list.items.begin()+i);
322         list.items_changed();
323 }
324
325 void List::DataObserver::cleared()
326 {
327         list.sel_index = -1;
328         for(vector<Item *>::iterator i=list.items.begin(); i!=list.items.end(); ++i)
329                 delete *i;
330         list.items.clear();
331         list.items_changed();
332 }
333
334 void List::DataObserver::refresh_item(unsigned i)
335 {
336         delete list.items[i];
337         // Avoid stale pointer while create_item is executing
338         list.items[i] = 0;
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) const
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                         Geometry cgeom;
352                         (*i)->widget->autosize(cgeom);
353                         ageom.w = max(ageom.w, cgeom.x+cgeom.w+margin.right);
354                         ageom.h = max(ageom.h, cgeom.y+cgeom.h+margin.top);
355                 }
356         }
357 }
358
359 void List::Item::set_active(bool a)
360 {
361         set_state(ACTIVE, (a ? ACTIVE : NORMAL));
362 }
363
364 void List::Item::render_special(const Part &part, GL::Renderer &renderer) const
365 {
366         if(part.get_name()=="children")
367         {
368                 for(list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i)
369                         (*i)->widget->render(renderer);
370         }
371 }
372
373
374 void List::MultiColumnItem::check_widths(vector<unsigned> &widths) const
375 {
376         if(widths.size()<children.size())
377                 widths.resize(children.size(), 0);
378
379         unsigned n = 0;
380         for(list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i, ++n)
381         {
382                 Geometry cgeom;
383                 (*i)->widget->autosize(cgeom);
384                 // TODO invent a better way to specify spacings
385                 widths[n] = max(widths[n], cgeom.w+8);
386         }
387 }
388
389 void List::MultiColumnItem::set_widths(const vector<unsigned> &widths)
390 {
391         if(!style)
392                 return;
393
394         const Part *part = style->get_part("children");
395         if(!part)
396                 return;
397
398         const Sides &margin = part->get_margin();
399         int x = margin.left;
400         unsigned n = 0;
401         for(list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i, ++n)
402         {
403                 (*i)->widget->set_position(x, margin.bottom);
404                 x += widths[n];
405         }
406 }
407
408 void List::MultiColumnItem::on_style_change()
409 {
410         if(!style)
411                 return;
412
413         for(std::list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i)
414                 (*i)->widget->autosize();
415
416         vector<unsigned> widths;
417         List *list = static_cast<List *>(parent);
418         for(vector<Item *>::const_iterator i=list->items.begin(); i!=list->items.end(); ++i)
419                 if(*i!=this)
420                         if(MultiColumnItem *mci = dynamic_cast<MultiColumnItem *>(*i))
421                                 mci->check_widths(widths);
422
423         vector<unsigned> self_widths(widths);
424         check_widths(self_widths);
425         bool update_all = false;
426         for(unsigned i=0; (!update_all && i<widths.size() && i<self_widths.size()); ++i)
427                 update_all = self_widths[i]>widths[i];
428
429         if(update_all)
430         {
431                 for(vector<Item *>::const_iterator i=list->items.begin(); i!=list->items.end(); ++i)
432                         if(MultiColumnItem *mci = dynamic_cast<MultiColumnItem *>(*i))
433                                 mci->set_widths(self_widths);
434         }
435
436         set_widths(self_widths);
437 }
438
439
440 List::BasicItem::BasicItem(const string &text):
441         label(text)
442 {
443         add(label);
444 }
445
446 void List::BasicItem::on_style_change()
447 {
448         if(!style)
449                 return;
450
451         label.autosize();
452         if(const Part *part = style->get_part("children"))
453         {
454                 const Sides &margin = part->get_margin();
455                 label.set_position(margin.left, margin.bottom);
456         }
457 }
458
459
460 List::Loader::Loader(List &l):
461         DataFile::DerivedObjectLoader<List, Widget::Loader>(l)
462 {
463         add("item", &Loader::item);
464         add("view_size", &List::view_size);
465 }
466
467 void List::Loader::item(const string &v)
468 {
469         dynamic_cast<BasicListData<string> &>(*obj.data).append(v);
470 }
471
472 } // namespace GLtk
473 } // namespace Msp