]> git.tdb.fi Git - libs/gltk.git/blob - source/list.cpp
Add a signal to notify when the automatic size of a widget changes
[libs/gltk.git] / source / list.cpp
1 /* $Id$
2
3 This file is part of libmspgltk
4 Copyright © 2007-2011  Mikko Rasa, Mikkosoft Productions
5 Distributed under the LGPL
6 */
7
8 #include <msp/gl/immediate.h>
9 #include <msp/gl/matrix.h>
10 #include <msp/gl/transform.h>
11 #include "graphic.h"
12 #include "list.h"
13 #include "part.h"
14 #include "style.h"
15 #include "text.h"
16 #include "vslider.h"
17
18 using namespace std;
19
20 namespace Msp {
21 namespace GLtk {
22
23 List::List():
24         sel_index(-1),
25         first(0),
26         n_visible(1),
27         row_height(1),
28         items_part(0)
29 {
30         add(slider);
31         slider.set_step(1);
32         slider.signal_value_changed.connect(sigc::mem_fun(this, &List::slider_value_changed));
33 }
34
35 void List::autosize()
36 {
37         autosize_rows(5);
38 }
39
40 void List::autosize_rows(unsigned n)
41 {
42         if(!style)
43                 return;
44
45         Widget::autosize();
46
47         if(items_part)
48         {
49                 const Sides &margin = items_part->get_margin();
50                 float font_size = style->get_font()->get_default_size();
51
52                 unsigned max_w = 0;
53                 for(vector<string>::iterator i=items.begin(); i!=items.end(); ++i)
54                 {
55                         unsigned w = static_cast<unsigned>(style->get_font()->get_string_width(*i)*font_size);
56                         max_w = max(max_w, w);
57                 }
58
59                 geom.w = max(geom.w, max_w+margin.left+margin.right);
60                 geom.h = max(geom.h, n*row_height+margin.top+margin.bottom);
61         }
62
63         if(const Part *slider_part = style->get_part("slider"))
64         {
65                 Geometry sgeom = slider_part->get_geometry();
66                 if(!sgeom.w || !sgeom.h)
67                 {
68                         slider.autosize();
69                         if(!sgeom.w)
70                                 sgeom.w = slider.get_geometry().w;
71                         if(!sgeom.h)
72                                 sgeom.h = slider.get_geometry().h;
73                 }
74
75                 const Sides &margin = slider_part->get_margin();
76                 geom.w = max(geom.w, sgeom.w+margin.left+margin.right);
77                 geom.h = max(geom.h, sgeom.h+margin.top+margin.bottom);
78
79                 reposition_slider();
80         }
81
82         check_view_range();
83 }
84
85 void List::autosize_all()
86 {
87         autosize_rows(items.size());
88 }
89
90 void List::append(const string &v)
91 {
92         items.push_back(v);
93         check_view_range();
94         signal_autosize_changed.emit();
95 }
96
97 void List::insert(unsigned i, const string &v)
98 {
99         if(i>items.size())
100                 throw InvalidParameterValue("Index out of range");
101
102         items.insert(items.begin()+i, v);
103         check_view_range();
104         signal_autosize_changed.emit();
105 }
106
107 void List::remove(unsigned i)
108 {
109         if(i>items.size())
110                 throw InvalidParameterValue("Index out of range");
111
112         items.erase(items.begin()+i);
113         if(sel_index>static_cast<int>(i))
114                 --sel_index;
115         else if(sel_index==static_cast<int>(i))
116                 sel_index = -1;
117
118         check_view_range();
119         signal_autosize_changed.emit();
120 }
121
122 void List::clear()
123 {
124         items.clear();
125         sel_index = -1;
126
127         check_view_range();
128         signal_autosize_changed.emit();
129 }
130
131 void List::set_selected_index(int i)
132 {
133         if(i<0)
134                 sel_index = -1;
135         else if(i<static_cast<int>(items.size()))
136         {
137                 sel_index = i;
138                 signal_item_selected.emit(sel_index, items[sel_index]);
139         }
140         else
141                 throw InvalidParameterValue("Index out of range");
142 }
143
144 const string &List::get_selected() const
145 {
146         if(sel_index<0)
147                 throw InvalidState("No selection");
148
149         return items[sel_index];
150 }
151
152 void List::render_special(const Part &part) const
153 {
154         if(part.get_name()=="items")
155         {
156                 const Sides &margin = part.get_margin();
157                 Geometry pgeom = geom;
158                 pgeom.h = row_height+margin.top+margin.bottom;
159
160                 GL::PushMatrix push_mtx;
161                 GL::translate(0, geom.h-pgeom.h, 0);
162
163                 for(unsigned i=0; (i<n_visible && first+i<items.size()); ++i)
164                 {
165                         if(i!=0)
166                                 GL::translate(0, -static_cast<int>(row_height), 0);
167                         Text(*style, items[first+i]).render(part, pgeom);
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                         GL::push_matrix();
186                         GL::translate(rgeom.x, rgeom.y, 0);
187                         part.get_graphic(state)->render(rgeom.w, rgeom.h);
188                         GL::pop_matrix();
189                 }
190         }
191         else if(part.get_name()=="slider")
192                 slider.render();
193 }
194
195 void List::button_press(int x, int y, unsigned btn)
196 {
197         Container::button_press(x, y, btn);
198         if(!click_focus && btn==1)
199         {
200                 if(items_part)
201                         y += items_part->get_margin().top;
202
203                 unsigned i = (geom.h-1-y)/row_height;
204                 if(i<n_visible && first+i<items.size())
205                 {
206                         sel_index = first+i;
207
208                         signal_item_selected.emit(sel_index, items[sel_index]);
209                 }
210         }
211 }
212
213 void List::on_geometry_change()
214 {
215         reposition_slider();
216
217         check_view_range();
218 }
219
220 void List::on_style_change()
221 {
222         if(!style)
223         {
224                 items_part = 0;
225                 return;
226         }
227
228         reposition_slider();
229
230         items_part = style->get_part("items");
231
232         const GL::Font &font = *style->get_font();
233         row_height = static_cast<unsigned>((font.get_ascent()-font.get_descent())*font.get_default_size());
234
235         check_view_range();
236 }
237
238 void List::reposition_slider()
239 {
240         if(!style)
241                 return;
242
243         if(const Part *slider_part = style->get_part("slider"))
244         {
245                 Geometry sgeom = slider_part->get_geometry();
246                 slider_part->get_alignment().apply(sgeom, geom, slider_part->get_margin());
247                 slider.set_geometry(sgeom);
248         }
249 }
250
251 void List::check_view_range()
252 {
253         unsigned h = geom.h;
254         if(items_part)
255         {
256                 const Sides &margin = items_part->get_margin();
257                 h -= margin.top+margin.bottom;
258         }
259
260         n_visible = h/row_height;
261
262         if(first+n_visible>items.size())
263         {
264                 if(items.size()>n_visible)
265                         first = items.size()-n_visible;
266                 else
267                         first = 0;
268         }
269
270         if(items.size()>n_visible)
271         {
272                 slider.set_range(0, items.size()-n_visible);
273                 slider.set_value(items.size()-n_visible-first);
274         }
275         else
276         {
277                 slider.set_range(0, 0);
278                 slider.set_value(0);
279         }
280 }
281
282 void List::slider_value_changed(double value)
283 {
284         if(items.size()>n_visible)
285                 first = items.size()-n_visible-static_cast<unsigned>(value);
286 }
287
288
289 List::Loader::Loader(List &l):
290         Widget::Loader(l)
291 {
292         add("item", &Loader::item);
293 }
294
295 void List::Loader::item(const string &v)
296 {
297         dynamic_cast<List &>(obj).append(v);
298 }
299
300 } // namespace GLtk
301 } // namespace Msp