]> git.tdb.fi Git - libs/gltk.git/blob - source/list.cpp
If the active list item is refreshed, mark it as such
[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         Container::button_press(x, y, btn);
219         if(click_focus && btn==1)
220         {
221                 for(unsigned i=first; (i<items.size() && items[i]->is_visible()); ++i)
222                         if(click_focus==items[i])
223                         {
224                                 set_selected_index(i);
225                                 break;
226                         }
227         }
228 }
229
230 void List::item_autosize_changed(Item *item)
231 {
232         item->autosize();
233         signal_autosize_changed.emit();
234         rebuild();
235 }
236
237 void List::check_view_range()
238 {
239         if(!style)
240                 return;
241
242         unsigned h = geom.h;
243         if(const Part *items_part = style->get_part("items"))
244         {
245                 const Sides &margin = items_part->get_margin();
246                 h -= margin.top+margin.bottom;
247         }
248
249         max_scroll = items.size();
250         for(unsigned i=items.size(); i-->0; )
251         {
252                 unsigned ih = items[i]->get_geometry().h;
253                 if(ih<=h)
254                 {
255                         h -= ih;
256                         --max_scroll;
257                 }
258         }
259
260         if(first>max_scroll)
261                 first = max_scroll;
262
263         slider.set_range(0, max_scroll);
264         slider.set_value(max_scroll-first);
265 }
266
267 void List::slider_value_changed(double value)
268 {
269         if(max_scroll>0 && !ignore_slider_change)
270         {
271                 first = max_scroll-static_cast<unsigned>(value);
272                 rebuild();
273         }
274 }
275
276
277 List::DataObserver::DataObserver(List &l):
278         list(l)
279 {
280         list.data->signal_item_added.connect(sigc::mem_fun(this, &DataObserver::item_added));
281         list.data->signal_item_removed.connect(sigc::mem_fun(this, &DataObserver::item_removed));
282         list.data->signal_cleared.connect(sigc::mem_fun(this, &DataObserver::cleared));
283         list.data->signal_refresh_item.connect(sigc::mem_fun(this, &DataObserver::refresh_item));
284 }
285
286 void List::DataObserver::item_added(unsigned i)
287 {
288         if(list.sel_index>=static_cast<int>(i))
289                 ++list.sel_index;
290
291         Item *item = list.create_item(i);
292         list.items.insert(list.items.begin()+i, item);
293         list.items_changed();
294 }
295
296 void List::DataObserver::item_removed(unsigned i)
297 {
298         if(list.sel_index>static_cast<int>(i))
299                 --list.sel_index;
300         else if(list.sel_index==static_cast<int>(i))
301                 list.sel_index = -1;
302
303         delete list.items[i];
304         list.items.erase(list.items.begin()+i);
305         list.items_changed();
306 }
307
308 void List::DataObserver::cleared()
309 {
310         list.sel_index = -1;
311         for(vector<Item *>::iterator i=list.items.begin(); i!=list.items.end(); ++i)
312                 delete *i;
313         list.items.clear();
314         list.items_changed();
315 }
316
317 void List::DataObserver::refresh_item(unsigned i)
318 {
319         delete list.items[i];
320         // Avoid stale pointer while create_item is executing
321         list.items[i] = 0;
322         list.items[i] = list.create_item(i);
323         list.items_changed();
324 }
325
326
327 void List::Item::autosize_special(const Part &part, Geometry &ageom) const
328 {
329         if(part.get_name()=="children")
330         {
331                 const Sides &margin = part.get_margin();
332                 for(list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i)
333                 {
334                         Geometry cgeom;
335                         (*i)->widget->autosize(cgeom);
336                         ageom.w = max(ageom.w, cgeom.x+cgeom.w+margin.right);
337                         ageom.h = max(ageom.h, cgeom.y+cgeom.h+margin.top);
338                 }
339         }
340 }
341
342 void List::Item::set_active(bool a)
343 {
344         set_state(ACTIVE, (a ? ACTIVE : NORMAL));
345 }
346
347 void List::Item::render_special(const Part &part, GL::Renderer &renderer) const
348 {
349         if(part.get_name()=="children")
350         {
351                 for(list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i)
352                         (*i)->widget->render(renderer);
353         }
354 }
355
356
357 void List::MultiColumnItem::check_widths(vector<unsigned> &widths) const
358 {
359         if(widths.size()<children.size())
360                 widths.resize(children.size(), 0);
361
362         unsigned n = 0;
363         for(list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i, ++n)
364         {
365                 Geometry cgeom;
366                 (*i)->widget->autosize(cgeom);
367                 // TODO invent a better way to specify spacings
368                 widths[n] = max(widths[n], cgeom.w+8);
369         }
370 }
371
372 void List::MultiColumnItem::set_widths(const vector<unsigned> &widths)
373 {
374         if(!style)
375                 return;
376
377         const Part *part = style->get_part("children");
378         if(!part)
379                 return;
380
381         const Sides &margin = part->get_margin();
382         int x = margin.left;
383         unsigned n = 0;
384         for(list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i, ++n)
385         {
386                 (*i)->widget->set_position(x, margin.bottom);
387                 x += widths[n];
388         }
389 }
390
391 void List::MultiColumnItem::on_style_change()
392 {
393         if(!style)
394                 return;
395
396         for(std::list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i)
397                 (*i)->widget->autosize();
398
399         vector<unsigned> widths;
400         List *list = static_cast<List *>(parent);
401         for(vector<Item *>::const_iterator i=list->items.begin(); i!=list->items.end(); ++i)
402                 if(*i!=this)
403                         if(MultiColumnItem *mci = dynamic_cast<MultiColumnItem *>(*i))
404                                 mci->check_widths(widths);
405
406         vector<unsigned> self_widths(widths);
407         check_widths(self_widths);
408         bool update_all = false;
409         for(unsigned i=0; (!update_all && i<widths.size() && i<self_widths.size()); ++i)
410                 update_all = self_widths[i]>widths[i];
411
412         if(update_all)
413         {
414                 for(vector<Item *>::const_iterator i=list->items.begin(); i!=list->items.end(); ++i)
415                         if(MultiColumnItem *mci = dynamic_cast<MultiColumnItem *>(*i))
416                                 mci->set_widths(self_widths);
417         }
418
419         set_widths(self_widths);
420 }
421
422
423 List::BasicItem::BasicItem(const string &text):
424         label(text)
425 {
426         add(label);
427 }
428
429 void List::BasicItem::on_style_change()
430 {
431         if(!style)
432                 return;
433
434         label.autosize();
435         if(const Part *part = style->get_part("children"))
436         {
437                 const Sides &margin = part->get_margin();
438                 label.set_position(margin.left, margin.bottom);
439         }
440 }
441
442
443 List::Loader::Loader(List &l):
444         DataFile::DerivedObjectLoader<List, Widget::Loader>(l)
445 {
446         add("item", &Loader::item);
447         add("view_size", &List::view_size);
448 }
449
450 void List::Loader::item(const string &v)
451 {
452         dynamic_cast<BasicListData<string> &>(*obj.data).append(v);
453 }
454
455 } // namespace GLtk
456 } // namespace Msp