]> git.tdb.fi Git - libs/gltk.git/blob - source/list.cpp
More flexible storage for Lsit and Dropdown items
[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         n_visible = 1;
34         row_height = 1;
35         items_part = 0;
36
37         observer = new DataObserver(*this);
38
39         add(slider);
40         slider.set_step(1);
41         slider.signal_value_changed.connect(sigc::mem_fun(this, &List::slider_value_changed));
42 }
43
44 List::~List()
45 {
46         delete observer;
47         if(own_data)
48                 delete data;
49 }
50
51 void List::autosize()
52 {
53         autosize_rows(5);
54 }
55
56 void List::autosize_rows(unsigned n)
57 {
58         if(!style)
59                 return;
60
61         Widget::autosize();
62
63         if(items_part)
64         {
65                 const Sides &margin = items_part->get_margin();
66                 const GL::Font &font = style->get_font();
67                 float font_size = style->get_font_size();
68
69                 unsigned max_w = 0;
70                 unsigned n_items = data->size();
71                 for(unsigned i=0; i<n_items; ++i)
72                 {
73                         unsigned w = static_cast<unsigned>(font.get_string_width(data->get_string(i))*font_size);
74                         max_w = max(max_w, w);
75                 }
76
77                 geom.w = max(geom.w, max_w+margin.left+margin.right);
78                 geom.h = max(geom.h, n*row_height+margin.top+margin.bottom);
79         }
80
81         if(const Part *slider_part = style->get_part("slider"))
82         {
83                 Geometry sgeom = slider_part->get_geometry();
84                 if(!sgeom.w || !sgeom.h)
85                 {
86                         slider.autosize();
87                         if(!sgeom.w)
88                                 sgeom.w = slider.get_geometry().w;
89                         if(!sgeom.h)
90                                 sgeom.h = slider.get_geometry().h;
91                 }
92
93                 const Sides &margin = slider_part->get_margin();
94                 geom.w = max(geom.w, sgeom.w+margin.left+margin.right);
95                 geom.h = max(geom.h, sgeom.h+margin.top+margin.bottom);
96
97                 reposition_slider();
98         }
99
100         check_view_range();
101         rebuild();
102 }
103
104 void List::autosize_all()
105 {
106         autosize_rows(data->size());
107 }
108
109 void List::set_data(ListData &d)
110 {
111         delete observer;
112         if(own_data)
113                 delete data;
114
115         data = &d;
116         own_data = false;
117         observer = new DataObserver(*this);
118
119         items_changed();
120 }
121
122 void List::items_changed()
123 {
124         check_view_range();
125         signal_autosize_changed.emit();
126         rebuild();
127 }
128
129 void List::set_selected_index(int i)
130 {
131         if(i<0)
132                 sel_index = -1;
133         else if(i<static_cast<int>(data->size()))
134         {
135                 sel_index = i;
136                 signal_item_selected.emit(sel_index);
137                 rebuild();
138         }
139         else
140                 throw out_of_range("List::set_selected_index");
141 }
142
143 void List::rebuild_special(const Part &part, CachedPart &cache)
144 {
145         if(part.get_name()=="items")
146         {
147                 const Sides &margin = part.get_margin();
148                 Geometry pgeom = geom;
149                 pgeom.h = row_height+margin.top+margin.bottom;
150
151                 const GL::Font &font = style->get_font();
152                 cache.texture = &font.get_texture();
153                 cache.clear_mesh();
154
155                 GL::MeshBuilder bld(*cache.mesh);
156                 bld.color(style->get_font_color());
157                 bld.matrix() *= GL::Matrix::translation(margin.left, geom.h-pgeom.h-font.get_descent()*style->get_font_size(), 0);
158
159                 for(unsigned i=0; (i<n_visible && first+i<data->size()); ++i)
160                 {
161                         if(i!=0)
162                                 bld.matrix() *= GL::Matrix::translation(0, -static_cast<int>(row_height), 0);
163
164                         GL::MatrixStack::Push _pushm(bld.matrix());
165                         bld.matrix() *= GL::Matrix::scaling(style->get_font_size());
166
167                         style->get_font().build_string(data->get_string(first+i), bld);
168                 }
169         }
170         else if(part.get_name()=="selection")
171         {
172                 if(sel_index>=static_cast<int>(first) && sel_index<static_cast<int>(first+n_visible))
173                 {
174                         const Sides &margin = part.get_margin();
175
176                         Geometry pgeom = geom;
177                         pgeom.h = row_height;
178                         pgeom.w -= margin.left+margin.right;
179
180                         Geometry rgeom = part.get_geometry();
181                         rgeom.y += geom.h-margin.top-row_height*(sel_index-first+1);
182                         rgeom.x += margin.left;
183                         part.get_alignment().apply(rgeom, pgeom);
184
185                         cache.texture = part.get_graphic(state)->get_texture();
186                         cache.clear_mesh();
187
188                         GL::MeshBuilder bld(*cache.mesh);
189                         bld.matrix() *= GL::Matrix::translation(rgeom.x, rgeom.y, 0);
190                         part.get_graphic(state)->build(rgeom.w, rgeom.h, bld);
191                 }
192                 else
193                         cache.texture = 0;
194         }
195 }
196
197 void List::render_special(const Part &part, GL::Renderer &renderer) const
198 {
199         if(part.get_name()=="slider")
200                 slider.render(renderer);
201 }
202
203 void List::button_press(int x, int y, unsigned btn)
204 {
205         Container::button_press(x, y, btn);
206         if(!click_focus && btn==1)
207         {
208                 if(items_part)
209                         y += items_part->get_margin().top;
210
211                 unsigned i = (geom.h-1-y)/row_height;
212                 if(i<n_visible && first+i<data->size())
213                 {
214                         sel_index = first+i;
215
216                         signal_item_selected.emit(sel_index);
217                         rebuild();
218                 }
219         }
220 }
221
222 void List::on_geometry_change()
223 {
224         reposition_slider();
225
226         check_view_range();
227 }
228
229 void List::on_style_change()
230 {
231         if(!style)
232         {
233                 items_part = 0;
234                 return;
235         }
236
237         reposition_slider();
238
239         items_part = style->get_part("items");
240
241         const GL::Font &font = style->get_font();
242         row_height = static_cast<unsigned>((font.get_ascent()-font.get_descent())*style->get_font_size());
243
244         check_view_range();
245 }
246
247 void List::reposition_slider()
248 {
249         if(!style)
250                 return;
251
252         if(const Part *slider_part = style->get_part("slider"))
253         {
254                 Geometry sgeom = slider_part->get_geometry();
255                 slider_part->get_alignment().apply(sgeom, geom, slider_part->get_margin());
256                 slider.set_geometry(sgeom);
257         }
258 }
259
260 void List::check_view_range()
261 {
262         unsigned h = geom.h;
263         if(items_part)
264         {
265                 const Sides &margin = items_part->get_margin();
266                 h -= margin.top+margin.bottom;
267         }
268
269         n_visible = h/row_height;
270
271         if(first+n_visible>data->size())
272         {
273                 if(data->size()>n_visible)
274                         first = data->size()-n_visible;
275                 else
276                         first = 0;
277         }
278
279         if(data->size()>n_visible)
280         {
281                 slider.set_range(0, data->size()-n_visible);
282                 slider.set_value(data->size()-n_visible-first);
283         }
284         else
285         {
286                 slider.set_range(0, 0);
287                 slider.set_value(0);
288         }
289 }
290
291 void List::slider_value_changed(double value)
292 {
293         if(data->size()>n_visible)
294         {
295                 first = data->size()-n_visible-static_cast<unsigned>(value);
296                 rebuild();
297         }
298 }
299
300
301 List::DataObserver::DataObserver(List &l):
302         list(l)
303 {
304         list.data->signal_item_added.connect(sigc::mem_fun(this, &DataObserver::item_added));
305         list.data->signal_item_removed.connect(sigc::mem_fun(this, &DataObserver::item_removed));
306         list.data->signal_cleared.connect(sigc::mem_fun(this, &DataObserver::cleared));
307         list.data->signal_refresh_strings.connect(sigc::mem_fun(this, &DataObserver::refresh_strings));
308 }
309
310 void List::DataObserver::item_added(unsigned i)
311 {
312         if(list.sel_index>=static_cast<int>(i))
313                 ++list.sel_index;
314
315         list.items_changed();
316 }
317
318 void List::DataObserver::item_removed(unsigned i)
319 {
320         if(list.sel_index>static_cast<int>(i))
321                 --list.sel_index;
322         else if(list.sel_index==static_cast<int>(i))
323                 list.sel_index = -1;
324
325         list.items_changed();
326 }
327
328 void List::DataObserver::cleared()
329 {
330         list.sel_index = -1;
331         list.items_changed();
332 }
333
334 void List::DataObserver::refresh_strings()
335 {
336         list.items_changed();
337 }
338
339
340 List::Loader::Loader(List &l):
341         Widget::Loader(l)
342 {
343         add("item", &Loader::item);
344 }
345
346 void List::Loader::item(const string &v)
347 {
348         dynamic_cast<BasicListData<string> &>(*dynamic_cast<List &>(obj).data).append(v);
349 }
350
351 } // namespace GLtk
352 } // namespace Msp