]> git.tdb.fi Git - libs/gltk.git/blob - source/container.cpp
Add an event handler to allow containers to reach to focus changes
[libs/gltk.git] / source / container.cpp
1 #include "container.h"
2 #include "part.h"
3
4 using namespace std;
5
6 namespace Msp {
7 namespace GLtk {
8
9 hierarchy_error::hierarchy_error(const string &w):
10         logic_error(w)
11 { }
12
13
14 Container::Container():
15         click_focus(0),
16         click_button(0),
17         pointer_focus(0),
18         pointer_grabbed(false),
19         input_focus(0),
20         touch_focus(0)
21 { }
22
23 Container::~Container()
24 {
25         while(!children.empty())
26                 delete children.front()->widget;
27 }
28
29 void Container::add(Widget &wdg)
30 {
31         wdg.set_parent(this);
32         children.push_back(create_child(&wdg));
33         if(wdg.get_animation_interval())
34                 check_animation_interval();
35         on_child_added(wdg);
36 }
37
38 void Container::remove(Widget &wdg)
39 {
40         for(list<Child *>::iterator i=children.begin(); i!=children.end(); ++i)
41                 if((*i)->widget==&wdg)
42                 {
43                         wdg.set_parent(0);
44                         delete *i;
45                         children.erase(i);
46                         if(wdg.get_animation_interval())
47                                 check_animation_interval();
48                         on_child_removed(wdg);
49                         return;
50                 }
51
52         throw hierarchy_error("widget not in container");
53 }
54
55 Container::Child *Container::create_child(Widget *wdg)
56 {
57         return new Child(*this, wdg);
58 }
59
60 Geometry Container::determine_child_geometry(const Widget &child, const Part &part) const
61 {
62         Geometry pgeom = part.get_geometry();
63         if(!pgeom.w || !pgeom.h)
64         {
65                 Geometry cgeom;
66                 child.autosize(cgeom);
67                 if(!pgeom.w)
68                         pgeom.w = cgeom.w;
69                 if(!pgeom.h)
70                         pgeom.h = cgeom.h;
71         }
72
73         part.get_alignment().apply(pgeom, geom, part.get_margin());
74         return pgeom;
75 }
76
77 void Container::autosize_child(const Widget &child, const Part &part, Geometry &ageom) const
78 {
79         Geometry cgeom = determine_child_geometry(child, part);
80         const Sides &margin = part.get_margin();
81         ageom.w = max(ageom.w, cgeom.w+margin.left+margin.right);
82         ageom.h = max(ageom.h, cgeom.h+margin.top+margin.bottom);
83 }
84
85 void Container::reposition_child(Widget &child, const Part &part) const
86 {
87         child.set_geometry(determine_child_geometry(child, part));
88 }
89
90 list<Widget *> Container::get_children() const
91 {
92         list<Widget *> result;
93         for(list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i)
94                 result.push_back((*i)->widget);
95         return result;
96 }
97
98 Widget *Container::get_child_at(int x, int y) const
99 {
100         for(list<Child *>::const_iterator i=children.end(); i!=children.begin();)
101                 if((*--i)->widget->is_visible() && (*i)->widget->get_geometry().is_inside(x, y))
102                         return (*i)->widget;
103
104         return 0;
105 }
106
107 Widget *Container::get_descendant_at(int x, int y) const
108 {
109         Widget *wdg = get_child_at(x, y);
110         if(Container *cont = dynamic_cast<Container *>(wdg))
111         {
112                 const Geometry &cgeom = wdg->get_geometry();
113                 Widget *wdg2 = cont->get_descendant_at(x-cgeom.x, y-cgeom.y);
114                 if(wdg2)
115                         return wdg2;
116         }
117         return wdg;
118 }
119
120 void Container::raise(Widget &wdg)
121 {
122         for(list<Child *>::iterator i=children.begin(); i!=children.end(); ++i)
123                 if((*i)->widget==&wdg)
124                 {
125                         children.splice(children.end(), children, i);
126                         return;
127                 }
128
129         throw hierarchy_error("widget not in container");
130 }
131
132 void Container::set_pointer_focus(Widget *wdg)
133 {
134         if(wdg!=pointer_focus)
135         {
136                 if(pointer_focus)
137                         pointer_focus->pointer_leave();
138
139                 pointer_focus = wdg;
140
141                 if(pointer_focus)
142                         pointer_focus->pointer_enter();
143         }
144 }
145
146 void Container::set_input_focus(Widget *wdg)
147 {
148         if(wdg!=input_focus)
149         {
150                 if(input_focus)
151                         input_focus->focus_out();
152
153                 input_focus = wdg;
154                 on_input_focus_changed(input_focus);
155
156                 if(input_focus)
157                         input_focus->focus_in();
158         }
159 }
160
161 Widget *Container::get_final_input_focus() const
162 {
163         if(Container *container = dynamic_cast<Container *>(input_focus))
164                 if(Widget *focus = container->get_final_input_focus())
165                         return focus;
166
167         return input_focus;
168 }
169
170 void Container::check_animation_interval()
171 {
172         Time::TimeDelta shortest;
173         for(list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i)
174         {
175                 const Time::TimeDelta &child_iv = (*i)->widget->get_animation_interval();
176                 if(child_iv && (!shortest || child_iv<shortest))
177                         shortest = child_iv;
178         }
179
180         if(shortest!=anim_interval)
181                 set_animation_interval(shortest);
182 }
183
184 void Container::button_press(int x, int y, unsigned btn)
185 {
186         if(Widget *child = get_pointer_target(x, y, false))
187         {
188                 if(!click_focus)
189                 {
190                         set_pointer_focus(child);
191                         if(child->is_focusable())
192                                 set_input_focus(child);
193
194                         click_focus = child;
195                         click_button = btn;
196                 }
197
198                 const Geometry &cgeom = child->get_geometry();
199                 child->button_press(x-cgeom.x, y-cgeom.y, btn);
200         }
201 }
202
203 void Container::button_release(int x, int y, unsigned btn)
204 {
205         if(Widget *child = get_pointer_target(x, y, false))
206         {
207                 if(child==click_focus && btn==click_button)
208                 {
209                         click_focus = 0;
210                         if(!pointer_focus)
211                                 set_pointer_focus(get_child_at(x, y));
212                 }
213
214                 const Geometry &cgeom = child->get_geometry();
215                 child->button_release(x-cgeom.x, y-cgeom.y, btn);
216         }
217 }
218
219 void Container::pointer_motion(int x, int y)
220 {
221         Widget *child = get_pointer_target(x, y, false);
222         if(!pointer_grabbed)
223                 set_pointer_focus((!click_focus || child->get_geometry().is_inside(x, y)) ? child : 0);
224
225         if(child)
226         {
227                 const Geometry &cgeom = child->get_geometry();
228                 child->pointer_motion(x-cgeom.x, y-cgeom.y);
229         }
230 }
231
232 Widget *Container::get_pointer_target(int x, int y, bool touch) const
233 {
234         if(pointer_grabbed)
235                 return pointer_focus;
236         else if(!touch && click_focus)
237                 return click_focus;
238         else if(touch && touch_focus)
239                 return touch_focus;
240         else
241         {
242                 Widget *child = get_child_at(x, y);
243                 if(child && child->is_enabled())
244                         return child;
245                 else
246                         return 0;
247         }
248 }
249
250 void Container::pointer_leave()
251 {
252         Widget::pointer_leave();
253         set_pointer_focus(0);
254 }
255
256 void Container::touch_press(int x, int y, unsigned finger)
257 {
258         if(Widget *child = get_pointer_target(x, y, true))
259         {
260                 // TODO track focus for each finger separately
261                 if(!touch_focus)
262                         touch_focus = child;
263
264                 const Geometry &cgeom = child->get_geometry();
265                 child->touch_press(x-cgeom.x, y-cgeom.y, finger);
266         }
267 }
268
269 void Container::touch_release(int x, int y, unsigned finger)
270 {
271         if(Widget *child = get_pointer_target(x, y, true))
272         {
273                 // TODO track focus for each finger separately
274                 if(child==touch_focus)
275                         touch_focus = 0;
276
277                 const Geometry &cgeom = child->get_geometry();
278                 child->touch_release(x-cgeom.x, y-cgeom.y, finger);
279         }
280 }
281
282 void Container::touch_motion(int x, int y, unsigned finger)
283 {
284         if(Widget *child = get_pointer_target(x, y, true))
285         {
286                 const Geometry &cgeom = child->get_geometry();
287                 child->touch_motion(x-cgeom.x, y-cgeom.y, finger);
288         }
289 }
290
291 bool Container::key_press(unsigned key, unsigned mod)
292 {
293         if(input_focus && input_focus->is_enabled())
294                 return input_focus->key_press(key, mod);
295         else
296                 return false;
297 }
298
299 bool Container::key_release(unsigned key, unsigned mod)
300 {
301         if(input_focus && input_focus->is_enabled())
302                 return input_focus->key_release(key, mod);
303         else
304                 return false;
305 }
306
307 bool Container::character(wchar_t ch)
308 {
309         if(input_focus && input_focus->is_enabled())
310                 return input_focus->character(ch);
311         else
312                 return false;
313 }
314
315 void Container::focus_out()
316 {
317         set_input_focus(0);
318         Widget::focus_out();
319 }
320
321 bool Container::navigate(Navigation nav)
322 {
323         if(input_focus && input_focus->is_enabled())
324                 return input_focus->navigate(nav);
325         else
326                 return false;
327 }
328
329 void Container::animate(const Time::TimeDelta &dt)
330 {
331         for(list<Child *>::iterator i=children.begin(); i!=children.end(); ++i)
332         {
333                 const Time::TimeDelta &child_iv = (*i)->widget->get_animation_interval();
334                 if(!child_iv)
335                         continue;
336
337                 (*i)->time_since_animate += dt;
338                 if((*i)->time_since_animate>=child_iv)
339                 {
340                         Time::TimeDelta child_dt = (*i)->time_since_animate;
341                         (*i)->time_since_animate = min((*i)->time_since_animate-child_iv, child_iv);
342                         (*i)->widget->animate(child_dt);
343                 }
344         }
345 }
346
347 void Container::on_reparent()
348 {
349         for(list<Child *>::iterator i=children.begin(); i!=children.end(); ++i)
350         {
351                 if(Container *c = dynamic_cast<Container *>((*i)->widget))
352                         c->on_reparent();
353                 (*i)->widget->update_style();
354         }
355 }
356
357 void Container::on_input_focus_changed(Widget *wdg)
358 {
359         if(wdg)
360                 raise(*wdg);
361 }
362
363
364 Container::Child::Child(Container &c, Widget *w):
365         container(c),
366         widget(w)
367 {
368         widget->signal_visibility_changed.connect(sigc::mem_fun(this, &Child::visibility_changed));
369         widget->signal_request_focus.connect(sigc::mem_fun(this, &Child::request_focus));
370         widget->signal_grab_pointer.connect(sigc::mem_fun(this, &Child::grab_pointer));
371         widget->signal_ungrab_pointer.connect(sigc::mem_fun(this, &Child::ungrab_pointer));
372         widget->signal_request_animation.connect(sigc::mem_fun(this, &Child::request_animation));
373 }
374
375 Container::Child::~Child()
376 {
377         visibility_changed(false);
378 }
379
380 void Container::Child::visibility_changed(bool v)
381 {
382         if(!v)
383         {
384                 if(widget==container.click_focus)
385                         container.click_focus = 0;
386                 if(widget==container.pointer_focus)
387                         container.set_pointer_focus(0);
388                 if(widget==container.input_focus)
389                         container.set_input_focus(0);
390         }
391 }
392
393 void Container::Child::request_focus()
394 {
395         if(container.parent && container.visible)
396                 container.set_focus();
397         if(container.state&FOCUS)
398                 container.set_input_focus(widget);
399 }
400
401 void Container::Child::grab_pointer()
402 {
403         if(!container.pointer_grabbed)
404         {
405                 container.set_pointer_focus(widget);
406                 container.pointer_grabbed = true;
407                 container.signal_grab_pointer.emit();
408         }
409 }
410
411 void Container::Child::ungrab_pointer()
412 {
413         if(container.pointer_grabbed && container.pointer_focus==widget)
414         {
415                 // XXX Should set to the widget under pointer
416                 container.set_pointer_focus(0);
417                 container.pointer_grabbed = false;
418                 container.signal_ungrab_pointer.emit();
419         }
420 }
421
422 void Container::Child::request_animation(const Time::TimeDelta &interval)
423 {
424         if(!interval)
425                 time_since_animate = Time::zero;
426         container.check_animation_interval();
427 }
428
429 } // namespace GLtk
430 } // namespace Msp