1 ///@file
2 /// A group of 2D Canvas elements
3 //
4 // Copyright (C) 2012  Thomas Geymayer <tomgey@gmail.com>
5 //
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Library General Public
8 // License as published by the Free Software Foundation; either
9 // version 2 of the License, or (at your option) any later version.
10 //
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 // Library General Public License for more details.
15 //
16 // You should have received a copy of the GNU Library General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
19 
20 #include <simgear_config.h>
21 
22 #include "CanvasGroup.hxx"
23 #include "CanvasImage.hxx"
24 #include "CanvasMap.hxx"
25 #include "CanvasPath.hxx"
26 #include "CanvasText.hxx"
27 
28 #include <simgear/canvas/CanvasEventVisitor.hxx>
29 #include <simgear/canvas/events/MouseEvent.hxx>
30 
31 namespace simgear
32 {
33 namespace canvas
34 {
35   /**
36    * Add canvas Element type to factory map
37    */
38   template<typename ElementType>
add(ElementFactories & factories)39   void add(ElementFactories& factories)
40   {
41     ElementType::staticInit();
42     factories[ElementType::TYPE_NAME] = &Element::create<ElementType>;
43   }
44 
45   //----------------------------------------------------------------------------
46   ElementFactories Group::_child_factories;
47   const std::string Group::TYPE_NAME = "group";
48 
warnSceneGroupExpired(const char * member_name)49   void warnSceneGroupExpired(const char* member_name)
50   {
51     SG_LOG( SG_GENERAL,
52             SG_WARN,
53             "canvas::Group::" << member_name << ": Group has expired." );
54   }
55 
56   //----------------------------------------------------------------------------
staticInit()57   void Group::staticInit()
58   {
59     if( isInit<Group>() )
60       return;
61 
62     add<Group>(_child_factories);
63     add<Image>(_child_factories);
64     add<Map  >(_child_factories);
65     add<Path >(_child_factories);
66     add<Text >(_child_factories);
67   }
68 
69   //----------------------------------------------------------------------------
Group(const CanvasWeakPtr & canvas,const SGPropertyNode_ptr & node,const Style & parent_style,ElementWeakPtr parent)70   Group::Group( const CanvasWeakPtr& canvas,
71                 const SGPropertyNode_ptr& node,
72                 const Style& parent_style,
73                 ElementWeakPtr parent ):
74     Element(canvas, node, parent_style, parent)
75   {
76     staticInit();
77   }
78 
79   //----------------------------------------------------------------------------
~Group()80   Group::~Group()
81   {
82 
83   }
84 
85   //----------------------------------------------------------------------------
createChild(const std::string & type,const std::string & id)86   ElementPtr Group::createChild( const std::string& type,
87                                  const std::string& id )
88   {
89     SGPropertyNode* node = _node->addChild(type, 0, false);
90     if( !id.empty() )
91       node->setStringValue("id", id);
92 
93     return getChild(node);
94   }
95 
96   //----------------------------------------------------------------------------
getChild(const SGPropertyNode * node)97   ElementPtr Group::getChild(const SGPropertyNode* node)
98   {
99     return findChild(node, "");
100   }
101 
102   //----------------------------------------------------------------------------
getChild(const std::string & id)103   ElementPtr Group::getChild(const std::string& id)
104   {
105     return findChild(0, id);
106   }
107 
108   //----------------------------------------------------------------------------
getOrCreateChild(const std::string & type,const std::string & id)109   ElementPtr Group::getOrCreateChild( const std::string& type,
110                                       const std::string& id )
111   {
112     ElementPtr child = getChild(id);
113     if( child )
114     {
115       if( child->getProps()->getNameString() == type )
116         return child;
117 
118       SG_LOG
119       (
120         SG_GENERAL,
121         SG_WARN,
122         "Group::getOrCreateChild: type missmatch! "
123         "('" << type << "' != '" << child->getProps()->getName() << "', "
124         "id = '" << id << "')"
125       );
126 
127       return ElementPtr();
128     }
129 
130     return createChild(type, id);
131   }
132 
133   //----------------------------------------------------------------------------
getElementById(const std::string & id)134   ElementPtr Group::getElementById(const std::string& id)
135   {
136     if( !_scene_group.valid() )
137     {
138       warnSceneGroupExpired("getElementById");
139       return {};
140     }
141 
142     // TODO check search algorithm. Not completely breadth-first and might be
143     //      possible with using less dynamic memory
144     std::vector<GroupPtr> child_groups;
145     for(size_t i = 0; i < _scene_group->getNumChildren(); ++i)
146     {
147       const ElementPtr& el = getChildByIndex(i);
148       if( el->get<std::string>("id") == id )
149         return el;
150 
151       if( Group* child_group = dynamic_cast<Group*>(el.get()) )
152         child_groups.push_back(child_group);
153     }
154 
155     for(auto group: child_groups)
156     {
157       if( ElementPtr el = group->getElementById(id) )
158         return el;
159     }
160 
161     return {};
162   }
163 
164   //----------------------------------------------------------------------------
clearEventListener()165   void Group::clearEventListener()
166   {
167     Element::clearEventListener();
168 
169     if( !_scene_group.valid() )
170       return warnSceneGroupExpired("clearEventListener");
171 
172     // TODO should this be recursive?
173     for(size_t i = 0; i < _scene_group->getNumChildren(); ++i)
174       getChildByIndex(i)->clearEventListener();
175   }
176 
177   //----------------------------------------------------------------------------
traverse(EventVisitor & visitor)178   bool Group::traverse(EventVisitor& visitor)
179   {
180     if( _scene_group.valid() )
181     {
182       // Iterate in reverse order as last child is displayed on top
183       for(size_t i = _scene_group->getNumChildren(); i --> 0;)
184       {
185         if( getChildByIndex(i)->accept(visitor) )
186           return true;
187       }
188     }
189     return false;
190   }
191 
192   //----------------------------------------------------------------------------
setStyle(const SGPropertyNode * style,const StyleInfo * style_info)193   bool Group::setStyle( const SGPropertyNode* style,
194                         const StyleInfo* style_info )
195   {
196     if( !canApplyStyle(style) )
197       return false;
198 
199     bool handled = setStyleImpl(style, style_info);
200     if( style_info->inheritable )
201     {
202       if( !_scene_group.valid() )
203       {
204         warnSceneGroupExpired("setStyle");
205         return false;
206       }
207 
208       for(size_t i = 0; i < _scene_group->getNumChildren(); ++i)
209         handled |= getChildByIndex(i)->setStyle(style, style_info);
210     }
211 
212     return handled;
213   }
214 
215   //----------------------------------------------------------------------------
getTransformedBounds(const osg::Matrix & m) const216   osg::BoundingBox Group::getTransformedBounds(const osg::Matrix& m) const
217   {
218     if( !_scene_group.valid() )
219     {
220       warnSceneGroupExpired("getTransformedBounds");
221       return {};
222     }
223 
224     osg::BoundingBox bb;
225     for(size_t i = 0; i < _scene_group->getNumChildren(); ++i)
226     {
227       auto child = getChildByIndex(i);
228       if( !child || !child->isVisible() )
229         continue;
230 
231       bb.expandBy( child->getTransformedBounds(child->getMatrix() * m) );
232     }
233 
234     return bb;
235   }
236 
237   //----------------------------------------------------------------------------
getChildFactory(const std::string & type) const238   ElementFactory Group::getChildFactory(const std::string& type) const
239   {
240     ElementFactories::iterator child_factory = _child_factories.find(type);
241     if( child_factory != _child_factories.end() )
242       return child_factory->second;
243 
244     return ElementFactory();
245   }
246 
247   //----------------------------------------------------------------------------
updateImpl(double dt)248   void Group::updateImpl(double dt)
249   {
250     Element::updateImpl(dt);
251 
252     for(size_t i = 0; i < _scene_group->getNumChildren(); ++i)
253       getChildByIndex(i)->update(dt);
254   }
255 
256   //----------------------------------------------------------------------------
childAdded(SGPropertyNode * child)257   void Group::childAdded(SGPropertyNode* child)
258   {
259     if( child->getParent() != _node )
260       return;
261 
262     ElementFactory child_factory = getChildFactory( child->getNameString() );
263     if( child_factory )
264     {
265       if( !_scene_group.valid() )
266         return warnSceneGroupExpired("childAdded");
267 
268       ElementPtr element = child_factory(_canvas, child, _style, this);
269 
270       // Add to osg scene graph...
271       _scene_group->addChild(element->getSceneGroup());
272 
273       // ...and ensure correct ordering
274       handleZIndexChanged(element);
275 
276       return;
277     }
278 
279     StyleInfo const* style = getStyleInfo(child->getNameString());
280     if( style && style->inheritable )
281       _style[ child->getNameString() ] = child;
282   }
283 
284   //----------------------------------------------------------------------------
childRemoved(SGPropertyNode * node)285   void Group::childRemoved(SGPropertyNode* node)
286   {
287     if( node->getParent() != _node )
288       return;
289 
290     if( getChildFactory(node->getNameString()) )
291     {
292       if( !_scene_group.valid() )
293         // If transform is destroyed also all children are destroyed, so we can
294         // not do anything here.
295         return;
296 
297       ElementPtr child = getChild(node);
298       if( !child )
299         SG_LOG
300         (
301           SG_GL,
302           SG_WARN,
303           "can't removed unknown child " << node->getDisplayName()
304         );
305       else
306       {
307         // Remove child from the scenegraph (this automatically invalidates the
308         // reference on the element hold by the MatrixTransform)
309         child->onDestroy();
310       }
311     }
312     else
313     {
314       _style.erase( node->getNameString() );
315     }
316   }
317 
318   //----------------------------------------------------------------------------
childChanged(SGPropertyNode * node)319   void Group::childChanged(SGPropertyNode* node)
320   {
321     SGPropertyNode* parent = node->getParent();
322     SGPropertyNode* grand_parent = parent ? parent->getParent() : nullptr;
323 
324     if(    grand_parent == _node
325         && node->getNameString() == "z-index" )
326       return handleZIndexChanged(getChild(parent), node->getIntValue());
327   }
328 
329   //----------------------------------------------------------------------------
handleZIndexChanged(ElementPtr child,int z_index)330   void Group::handleZIndexChanged(ElementPtr child, int z_index)
331   {
332     if( !child || !_scene_group.valid() )
333       return;
334 
335     // Keep reference to prevent deleting while removing and re-inserting later
336     osg::ref_ptr<osg::MatrixTransform> tf = child->getSceneGroup();
337 
338     size_t index = _scene_group->getChildIndex(tf),
339            index_new = index;
340 
341     for(;; ++index_new)
342     {
343       if( index_new + 1 == _scene_group->getNumChildren() )
344         break;
345 
346       // Move to end of block with same index (= move upwards until the next
347       // element has a higher index)
348       if( getChildByIndex(index_new + 1)->get<int>("z-index", 0) > z_index )
349         break;
350     }
351 
352     if( index_new == index )
353     {
354       // We were not able to move upwards so now try downwards
355       for(;; --index_new)
356       {
357         if( index_new == 0 )
358           break;
359 
360         // Move to end of block with same index (= move downwards until the
361         // previous element has the same or a lower index)
362         if( getChildByIndex(index_new - 1)->get<int>("z-index", 0) <= z_index )
363           break;
364       }
365 
366       if( index == index_new )
367         return;
368     }
369 
370     _scene_group->removeChild(index);
371     _scene_group->insertChild(index_new, tf);
372 
373     SG_LOG
374     (
375       SG_GENERAL,
376       SG_DEBUG,
377       "canvas::Group: Moved element " << index << " to position " << index_new
378     );
379   }
380 
381   //----------------------------------------------------------------------------
getChildByIndex(size_t index) const382   ElementPtr Group::getChildByIndex(size_t index) const
383   {
384     assert( _scene_group.valid() );
385 
386     auto child = _scene_group->getChild(index);
387     if( !child )
388       return {};
389 
390     auto ud = static_cast<OSGUserData*>(child->getUserData());
391     return ud ? ud->element : ElementPtr();
392   }
393 
394   //----------------------------------------------------------------------------
findChild(const SGPropertyNode * node,const std::string & id) const395   ElementPtr Group::findChild( const SGPropertyNode* node,
396                                const std::string& id ) const
397   {
398     if( !_scene_group.valid() )
399     {
400       warnSceneGroupExpired("findChild");
401       return {};
402     }
403 
404     for(size_t i = 0; i < _scene_group->getNumChildren(); ++i)
405     {
406       ElementPtr el = getChildByIndex(i);
407 
408       if( node )
409       {
410         if( el->getProps() == node )
411           return el;
412       }
413       else
414       {
415         if( el->get<std::string>("id") == id )
416           return el;
417       }
418     }
419 
420     return {};
421   }
422 
423 } // namespace canvas
424 } // namespace simgear
425