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