1 /**************************************************************************\
2  * Copyright (c) Kongsberg Oil & Gas Technologies AS
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * Redistributions of source code must retain the above copyright notice,
10  * this list of conditions and the following disclaimer.
11  *
12  * Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in the
14  * documentation and/or other materials provided with the distribution.
15  *
16  * Neither the name of the copyright holder nor the names of its
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 \**************************************************************************/
32 
33 /*!
34   \class SoChildList SoChildList.h Inventor/misc/SoChildList.h
35   \brief The SoChildList class is a container for node children.
36 
37   \ingroup general
38 
39   This class does automatic notification on the parent nodes upon
40   adding or removing children.
41 
42   Methods for action traversal of the children are also provided.
43 */
44 
45 #include <Inventor/misc/SoChildList.h>
46 #include <Inventor/actions/SoAction.h>
47 #include <Inventor/nodes/SoNode.h>
48 #include <Inventor/SbName.h>
49 
50 #if COIN_DEBUG
51 #include <Inventor/errors/SoDebugError.h>
52 #endif // COIN_DEBUG
53 
54 
55 
56 /*!
57   Default constructor, sets parent container and initializes a minimal
58   list.
59 */
SoChildList(SoNode * const parentptr)60 SoChildList::SoChildList(SoNode * const parentptr)
61   : SoNodeList()
62 {
63   this->parent = parentptr;
64 }
65 
66 /*!
67   Constructor with hint about list size.
68 
69   \sa SoNodeList::SoNodeList(const int)
70 */
SoChildList(SoNode * const parentptr,const int size)71 SoChildList::SoChildList(SoNode * const parentptr, const int size)
72   : SoNodeList(size)
73 {
74   this->parent = parentptr;
75 }
76 
77 /*!
78   Copy constructor.
79 
80   \sa SoNodeList::SoNodeList(const SoNodeList &)
81 */
SoChildList(SoNode * const parentptr,const SoChildList & cl)82 SoChildList::SoChildList(SoNode * const parentptr, const SoChildList & cl)
83   : SoNodeList()
84 {
85   this->parent = parentptr;
86   this->copy(cl);
87 }
88 
89 /*!
90   Destructor.
91 */
~SoChildList()92 SoChildList::~SoChildList()
93 {
94   this->truncate(0);
95 }
96 
97 /*!
98   Append a new \a node instance as a child of our parent container.
99 
100   Automatically notifies parent node and any SoPath instances auditing
101   paths with nodes from this list.
102 */
103 void
append(SoNode * const node)104 SoChildList::append(SoNode * const node)
105 {
106   if (this->parent) {
107     node->addAuditor(this->parent, SoNotRec::PARENT);
108   }
109   SoNodeList::append(node);
110 
111   if (this->parent) {
112     this->parent->startNotify();
113   }
114   // Doesn't need to notify SoPath auditors, as adding a new node at
115   // _the end_ won't affect any path "passing through" this childlist.
116 }
117 
118 /*!
119   Insert a new \a node instance as a child of our parent container at
120   position \a addbefore.
121 
122   Automatically notifies parent node and any SoPath instances auditing
123   paths with nodes from this list.
124 */
125 void
insert(SoNode * const node,const int addbefore)126 SoChildList::insert(SoNode * const node, const int addbefore)
127 {
128   assert(addbefore <= this->getLength());
129   if (this->parent) {
130     node->addAuditor(this->parent, SoNotRec::PARENT);
131   }
132   SoNodeList::insert(node, addbefore);
133 
134   // FIXME: shouldn't we move this startNotify() call to the end of
135   // the function?  pederb, 2002-10-02
136   if (this->parent) {
137     this->parent->startNotify();
138     for (int i=0; i < this->auditors.getLength(); i++) {
139       this->auditors[i]->insertIndex(this->parent, addbefore);
140     }
141   }
142 }
143 
144 /*!
145   Remove the child node pointer at \a index.
146 
147   Automatically notifies parent node and any SoPath instances auditing
148   paths with nodes from this list.
149 */
150 void
remove(const int index)151 SoChildList::remove(const int index)
152 {
153   assert(index >= 0 && index < this->getLength());
154   if (this->parent) {
155     SoNodeList::operator[](index)->removeAuditor(this->parent, SoNotRec::PARENT);
156   }
157   // FIXME: we experienced memory corruption if the
158   // SoNodeList::remove(index) statement was placed here (before
159   // updating paths). It seems to be working ok now, but we should
160   // figure out exactly why we can't remove the node before updating
161   // the paths.  pederb, 2002-10-02
162   if (this->parent) {
163     for (int i=0; i < this->auditors.getLength(); i++) {
164       this->auditors[i]->removeIndex(this->parent, index);
165     }
166     /* notify before removal, so that the notification source gets the
167      * chance to operate on the child to be removed. 20100426 tamer. */
168     this->parent->startNotify();
169   }
170   SoNodeList::remove(index);
171 }
172 
173 // Documented in superclass. Overridden to handle notification.
174 void
truncate(const int length)175 SoChildList::truncate(const int length)
176 {
177   const int n = this->getLength();
178   assert(length >= 0 && length <= n);
179 
180   if (length != n) {
181     if (this->parent) {
182       for (int i = length; i < n; i++) {
183         SoNodeList::operator[](i)->removeAuditor(this->parent, SoNotRec::PARENT);
184       }
185       /* FIXME: shouldn't we move this startNotify() call to the end of
186 	 the function?  pederb, 2002-10-02 */
187       /* notify before truncation, so that the notification source gets
188 	 the chance to operate on the child to be removed. 20100426
189 	 tamer. */
190       this->parent->startNotify();
191       for (int k=0; k < this->auditors.getLength(); k++) {
192         for (int j=n-1; j >= length; --j) {
193           this->auditors[k]->removeIndex(this->parent, j);
194         }
195       }
196     }
197     SoNodeList::truncate(length);
198   }
199 }
200 
201 /*!
202   Copy contents of \a cl into this list.
203 */
204 void
copy(const SoChildList & cl)205 SoChildList::copy(const SoChildList & cl)
206 {
207   // Overridden from superclass to handle notification.
208 
209   if (this == &cl) return;
210 
211   // Call truncate() explicitly here to get the path notification.
212   this->truncate(0);
213   SoBaseList::copy(cl);
214 
215   // it's important to add parent as auditor for all nodes (this is
216   // usually done in SoChildList::append/insert)
217   if (this->parent) {
218     for (int i = 0; i < this->getLength(); i++) {
219       (*this)[i]->addAuditor(this->parent, SoNotRec::PARENT);
220     }
221     this->parent->startNotify();
222   }
223 }
224 
225 /*!
226   Index operator to set element at \a index. Does \e not expand array
227   bounds if \a index is outside the list.
228 */
229 void
set(const int index,SoNode * const node)230 SoChildList::set(const int index, SoNode * const node)
231 {
232   // Overridden from superclass to handle notification.
233 
234 #if COIN_DEBUG && 0 // debug
235   SoDebugError::postInfo("SoChildList::set",
236                          "(%p) index=%d, node=%p, oldnode=%p",
237                          this, index, node, (*this)[index]);
238 #endif // debug
239 
240   assert(index >= 0 && index < this->getLength());
241   if (this->parent) {
242     SoNodeList::operator[](index)->removeAuditor(this->parent, SoNotRec::PARENT);
243     node->addAuditor(this->parent, SoNotRec::PARENT);
244   }
245 
246   /* keep the node that is to be replaced around until after the
247    * notifications have been sent */
248   SoNode * prevchild = (SoNode *)this->get(index);
249   prevchild->ref();
250 
251   SoBaseList::set(index, (SoBase *)node);
252 
253   // FIXME: shouldn't we move this startNotify() call to the end of
254   // the function?  pederb, 2002-10-02
255   /* notify before truncation, so that the notification source gets
256      the chance to operate on the child to be removed. 20100426
257      tamer. */
258   if (this->parent) {
259     this->parent->startNotify();
260     for (int i=0; i < this->auditors.getLength(); i++) {
261       this->auditors[i]->replaceIndex(this->parent, index, node);
262     }
263   }
264 
265   prevchild->unref();
266 }
267 
268 /*!
269   Optimized IN_PATH traversal method.
270 
271   This method is an extension versus the Open Inventor API.
272 */
273 void
traverseInPath(SoAction * const action,const int numindices,const int * indices)274 SoChildList::traverseInPath(SoAction * const action,
275                             const int numindices,
276                             const int * indices)
277 {
278   assert(action->getCurPathCode() == SoAction::IN_PATH);
279 
280   // only traverse nodes in path list, and nodes off path that
281   // affects state.
282   int childidx = 0;
283 
284   for (int i = 0; i < numindices && !action->hasTerminated(); i++) {
285     int stop = indices[i];
286     for (; childidx < stop && !action->hasTerminated(); childidx++) {
287       // we are off path. Check if node affects state before traversing
288       SoNode * node = (*this)[childidx];
289       if (node->affectsState()) {
290         action->pushCurPath(childidx, node);
291         action->traverse(node);
292         action->popCurPath(SoAction::IN_PATH);
293       }
294     }
295 
296     if (!action->hasTerminated()) {
297       // here we are in path. Always traverse
298       SoNode * node = (*this)[childidx];
299       action->pushCurPath(childidx, node);
300       action->traverse(node);
301       action->popCurPath(SoAction::IN_PATH);
302       childidx++;
303     }
304   }
305 }
306 
307 /*!
308   Traverse child nodes in the list from index \a first up to and
309   including index \a last, or until the SoAction::hasTerminated() flag
310   of \a action has been set.
311 */
312 void
traverse(SoAction * const action,const int first,const int last)313 SoChildList::traverse(SoAction * const action, const int first, const int last)
314 {
315   int i;
316   SoNode * node = NULL;
317 
318   assert((first >= 0) && (first < this->getLength()) && "index out of bounds");
319   assert((last >= 0) && (last < this->getLength()) && "index out of bounds");
320   assert((last >= first) && "erroneous indices");
321 
322 #if COIN_DEBUG
323   // Calculate a checksum over the children node pointers, to later
324   // catch attempts at changing the scene graph layout mid-traversal
325   // with an assert. (chksum reversed to initial value and controlled
326   // at the bottom end of this function.)
327   //
328   // Note: we might find this to be overly strict, because there are
329   // cases where this will stop an unharmful attempt at changing the
330   // current group node's set of children. But that's only if the
331   // application programmer _really_, _really_ know what he is doing,
332   // and it's still a slippery slope.. so "better safe than sorry" and
333   // all that.
334   //
335   // mortene.
336   uintptr_t chksum = 0xdeadbeef;
337   for (i = first; i <= last; i++) { chksum ^= (uintptr_t)(*this)[i]; }
338   SbBool changedetected = FALSE;
339 #endif // COIN_DEBUG
340 
341   SoAction::PathCode pathcode = action->getCurPathCode();
342 
343   switch (pathcode) {
344   case SoAction::NO_PATH:
345   case SoAction::BELOW_PATH:
346     // always traverse all nodes.
347     action->pushCurPath();
348     for (i = first; (i <= last) && !action->hasTerminated(); i++) {
349 #if COIN_DEBUG
350       if (i >= this->getLength()) {
351         changedetected = TRUE;
352         break;
353       }
354 #endif // COIN_DEBUG
355       node = (*this)[i];
356       action->popPushCurPath(i, node);
357       action->traverse(node);
358     }
359     action->popCurPath();
360     break;
361   case SoAction::OFF_PATH:
362     for (i = first; (i <= last) && !action->hasTerminated(); i++) {
363 #if COIN_DEBUG
364       if (i >= this->getLength()) {
365         changedetected = TRUE;
366         break;
367       }
368 #endif // COIN_DEBUG
369       node = (*this)[i];
370       // only traverse nodes that affects state
371       if (node->affectsState()) {
372         action->pushCurPath(i, node);
373         action->traverse(node);
374         action->popCurPath(pathcode);
375       }
376     }
377     break;
378   case SoAction::IN_PATH:
379     for (i = first; (i <= last) && !action->hasTerminated(); i++) {
380 #if COIN_DEBUG
381       if (i >= this->getLength()) {
382         changedetected = TRUE;
383         break;
384       }
385 #endif // COIN_DEBUG
386       node = (*this)[i];
387       action->pushCurPath(i, node);
388       // if we're OFF_PATH after pushing, we only traverse if the node
389       // affects the state.
390       if ((action->getCurPathCode() != SoAction::OFF_PATH) ||
391           node->affectsState()) {
392         action->traverse(node);
393       }
394       action->popCurPath(pathcode);
395     }
396     break;
397   default:
398     assert(0 && "unknown path code.");
399     break;
400   }
401 
402 #if COIN_DEBUG
403   if (!changedetected) {
404     for (i = last; i >= first; i--) { chksum ^= (uintptr_t)(*this)[i]; }
405     if (chksum != 0xdeadbeef) changedetected = TRUE;
406   }
407   if (changedetected) {
408     SoDebugError::postWarning("SoChildList::traverse",
409                               "Detected modification of scene graph layout "
410                               "during action traversal. This is considered to "
411                               "be hazardous and error prone, and we "
412                               "strongly advice you to change your code "
413                               "and/or reorganize your scene graph so that "
414                               "this is not necessary.");
415   }
416 #endif // COIN_DEBUG
417 }
418 
419 /*!
420   Traverse all nodes in the list, invoking their methods for the given
421   \a action.
422 */
423 void
traverse(SoAction * const action)424 SoChildList::traverse(SoAction * const action)
425 {
426   if (this->getLength() == 0) return;
427   this->traverse(action, 0, this->getLength() - 1);
428 }
429 
430 /*!
431   Traverse the node at \a index (and possibly its children, if its a
432   group node), applying the nodes' method for the given \a action.
433 */
434 void
traverse(SoAction * const action,const int index)435 SoChildList::traverse(SoAction * const action, const int index)
436 {
437   assert((index >= 0) && (index < this->getLength()) && "index out of bounds");
438   this->traverse(action, index, index);
439 }
440 
441 /*!
442   Traverse the \a node (and possibly its children, if its a group
443   node), applying the nodes' method for the given \a action.
444 */
445 void
traverse(SoAction * const action,SoNode * node)446 SoChildList::traverse(SoAction * const action, SoNode * node)
447 {
448   int idx = this->find(node);
449   assert(idx != -1);
450   this->traverse(action, idx);
451 }
452 
453 /*!
454   Notify \a path whenever this list of node children changes.
455 */
456 void
addPathAuditor(SoPath * const path)457 SoChildList::addPathAuditor(SoPath * const path)
458 {
459 #if COIN_DEBUG && 0 // debug
460   SoDebugError::postInfo("SoChildList::addPathAuditor",
461                          "add SoPath auditor %p to list %p", path, this);
462 #endif // debug
463 
464   this->auditors.append(path);
465 }
466 
467 /*!
468   Remove \a path as an auditor for our list of node children.
469 */
470 void
removePathAuditor(SoPath * const path)471 SoChildList::removePathAuditor(SoPath * const path)
472 {
473 #if COIN_DEBUG && 0 // debug
474   SoDebugError::postInfo("SoChildList::removePathAuditor",
475                          "remove SoPath auditor %p from list %p", path, this);
476 #endif // debug
477 
478   const int index = this->auditors.find(path);
479 #if COIN_DEBUG
480   if (index == -1) {
481     SoDebugError::post("SoChildList::removePathAuditor",
482                        "no SoPath %p is auditing list %p! (of parent %p (%s))",
483                        path,
484                        this,
485                        this->parent,
486                        this->parent ? this->parent->getTypeId().getName().getString() : "<no type>");
487     return;
488   }
489 #endif // COIN_DEBUG
490   this->auditors.remove(index);
491 }
492