/**************************************************************************\ * * This file is part of the Coin 3D visualization library. * Copyright (C) 1998-2007 by Systems in Motion. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * ("GPL") version 2 as published by the Free Software Foundation. * See the file LICENSE.GPL at the root directory of this source * distribution for additional information about the GNU GPL. * * For using Coin with software that can not be combined with the GNU * GPL, and for taking advantage of the additional benefits of our * support services, please contact Systems in Motion about acquiring * a Coin Professional Edition License. * * See http://www.coin3d.org/ for more information. * * Systems in Motion, Postboks 1283, Pirsenteret, 7462 Trondheim, NORWAY. * http://www.sim.no/ sales@sim.no coin-support@coin3d.org * \**************************************************************************/ /*! \class SoChildList SoChildList.h Inventor/misc/SoChildList.h \brief The SoChildList class is a container for node children. \ingroup general This class does automatic notification on the parent nodes upon adding or removing children. Methods for action traversal of the children are also provided. */ #include #include #include #include #if COIN_DEBUG #include #endif // COIN_DEBUG /*! Default constructor, sets parent container and initializes a minimal list. */ SoChildList::SoChildList(SoNode * const parentptr) : SoNodeList() { this->parent = parentptr; } /*! Constructor with hint about list size. \sa SoNodeList::SoNodeList(const int) */ SoChildList::SoChildList(SoNode * const parentptr, const int size) : SoNodeList(size) { this->parent = parentptr; } /*! Copy constructor. \sa SoNodeList::SoNodeList(const SoNodeList &) */ SoChildList::SoChildList(SoNode * const parentptr, const SoChildList & cl) : SoNodeList() { this->parent = parentptr; this->copy(cl); } /*! Destructor. */ SoChildList::~SoChildList() { this->truncate(0); } /*! Append a new \a node instance as a child of our parent container. Automatically notifies parent node and any SoPath instances auditing paths with nodes from this list. */ void SoChildList::append(SoNode * const node) { if (this->parent) { node->addAuditor(this->parent, SoNotRec::PARENT); } SoNodeList::append(node); if (this->parent) { this->parent->startNotify(); } // Doesn't need to notify SoPath auditors, as adding a new node at // _the end_ won't affect any path "passing through" this childlist. } /*! Insert a new \a node instance as a child of our parent container at position \a addbefore. Automatically notifies parent node and any SoPath instances auditing paths with nodes from this list. */ void SoChildList::insert(SoNode * const node, const int addbefore) { assert(addbefore <= this->getLength()); if (this->parent) { node->addAuditor(this->parent, SoNotRec::PARENT); } SoNodeList::insert(node, addbefore); // FIXME: shouldn't we move this startNotify() call to the end of // the function? pederb, 2002-10-02 if (this->parent) { this->parent->startNotify(); for (int i=0; i < this->auditors.getLength(); i++) { this->auditors[i]->insertIndex(this->parent, addbefore); } } } /*! Remove the child node pointer at \a index. Automatically notifies parent node and any SoPath instances auditing paths with nodes from this list. */ void SoChildList::remove(const int index) { assert(index >= 0 && index < this->getLength()); if (this->parent) { SoNodeList::operator[](index)->removeAuditor(this->parent, SoNotRec::PARENT); } // FIXME: we experienced memory corruption if the // SoNodeList::remove(index) statement was placed here (before // updating paths). It seems to be working ok now, but we should // figure out exactly why we can't remove the node before updating // the paths. pederb, 2002-10-02 if (this->parent) { for (int i=0; i < this->auditors.getLength(); i++) { this->auditors[i]->removeIndex(this->parent, index); } } SoNodeList::remove(index); if (this->parent) { this->parent->startNotify(); } } // Documented in superclass. Overridden to handle notification. void SoChildList::truncate(const int length) { const int n = this->getLength(); assert(length >= 0 && length <= n); if (length != n) { if (this->parent) { for (int i = length; i < n; i++) { SoNodeList::operator[](i)->removeAuditor(this->parent, SoNotRec::PARENT); } } SoNodeList::truncate(length); // FIXME: shouldn't we move this startNotify() call to the end of // the function? pederb, 2002-10-02 if (this->parent) { this->parent->startNotify(); for (int k=0; k < this->auditors.getLength(); k++) { for (int j=length; j < n; j++) { this->auditors[k]->removeIndex(this->parent, j); } } } } } /*! Copy contents of \a cl into this list. */ void SoChildList::copy(const SoChildList & cl) { // Overridden from superclass to handle notification. if (this == &cl) return; // Call truncate() explicitly here to get the path notification. this->truncate(0); SoBaseList::copy(cl); // it's important to add parent as auditor for all nodes (this is // usually done in SoChildList::append/insert) if (this->parent) { for (int i = 0; i < this->getLength(); i++) { (*this)[i]->addAuditor(this->parent, SoNotRec::PARENT); } this->parent->startNotify(); } } /*! Index operator to set element at \a index. Does \e not expand array bounds if \a index is outside the list. */ void SoChildList::set(const int index, SoNode * const node) { // Overridden from superclass to handle notification. #if COIN_DEBUG && 0 // debug SoDebugError::postInfo("SoChildList::set", "(%p) index=%d, node=%p, oldnode=%p", this, index, node, (*this)[index]); #endif // debug assert(index >= 0 && index < this->getLength()); if (this->parent) { SoNodeList::operator[](index)->removeAuditor(this->parent, SoNotRec::PARENT); } SoBaseList::set(index, (SoBase *)node); if (this->parent) { node->addAuditor(this->parent, SoNotRec::PARENT); } // FIXME: shouldn't we move this startNotify() call to the end of // the function? pederb, 2002-10-02 if (this->parent) { this->parent->startNotify(); for (int i=0; i < this->auditors.getLength(); i++) this->auditors[i]->replaceIndex(this->parent, index, node); } } /*! Optimized IN_PATH traversal method. This method is an extension versus the Open Inventor API. */ void SoChildList::traverseInPath(SoAction * const action, const int numindices, const int * indices) { assert(action->getCurPathCode() == SoAction::IN_PATH); // only traverse nodes in path list, and nodes off path that // affects state. int childidx = 0; SoNode * node; for (int i = 0; i < numindices && !action->hasTerminated(); i++) { int stop = indices[i]; for (; childidx < stop && !action->hasTerminated(); childidx++) { // we are off path. Check if node affects state before traversing node = (*this)[childidx]; if (node->affectsState()) { action->pushCurPath(childidx, node); action->traverse(node); action->popCurPath(SoAction::IN_PATH); } } if (!action->hasTerminated()) { // here we are in path. Always traverse node = (*this)[childidx]; action->pushCurPath(childidx, node); action->traverse(node); action->popCurPath(SoAction::IN_PATH); childidx++; } } } /*! Traverse child nodes in the list from index \a first up to and including index \a last, or until the SoAction::hasTerminated() flag of \a action has been set. */ void SoChildList::traverse(SoAction * const action, const int first, const int last) { int i; SoNode * node = NULL; assert((first >= 0) && (first < this->getLength()) && "index out of bounds"); assert((last >= 0) && (last < this->getLength()) && "index out of bounds"); assert((last >= first) && "erroneous indices"); #if COIN_DEBUG // Calculate a checksum over the children node pointers, to later // catch attempts at changing the scene graph layout mid-traversal // with an assert. (chksum reversed to initial value and controlled // at the bottom end of this function.) // // Note: we might find this to be overly strict, because there are // cases where this will stop an unharmful attempt at changing the // current group node's set of children. But that's only if the // application programmer _really_, _really_ know what he is doing, // and it's still a slippery slope.. so "better safe than sorry" and // all that. // // mortene. uintptr_t chksum = 0xdeadbeef; for (i = first; i <= last; i++) { chksum ^= (uintptr_t)(*this)[i]; } SbBool changedetected = FALSE; #endif // COIN_DEBUG SoAction::PathCode pathcode = action->getCurPathCode(); switch (pathcode) { case SoAction::NO_PATH: case SoAction::BELOW_PATH: // always traverse all nodes. action->pushCurPath(); for (i = first; (i <= last) && !action->hasTerminated(); i++) { #if COIN_DEBUG if (i >= this->getLength()) { changedetected = TRUE; break; } #endif // COIN_DEBUG node = (*this)[i]; action->popPushCurPath(i, node); action->traverse(node); } action->popCurPath(); break; case SoAction::OFF_PATH: for (i = first; (i <= last) && !action->hasTerminated(); i++) { #if COIN_DEBUG if (i >= this->getLength()) { changedetected = TRUE; break; } #endif // COIN_DEBUG node = (*this)[i]; // only traverse nodes that affects state if (node->affectsState()) { action->pushCurPath(i, node); action->traverse(node); action->popCurPath(pathcode); } } break; case SoAction::IN_PATH: for (i = first; (i <= last) && !action->hasTerminated(); i++) { #if COIN_DEBUG if (i >= this->getLength()) { changedetected = TRUE; break; } #endif // COIN_DEBUG node = (*this)[i]; action->pushCurPath(i, node); // if we're OFF_PATH after pushing, we only traverse if the node // affects the state. if ((action->getCurPathCode() != SoAction::OFF_PATH) || node->affectsState()) { action->traverse(node); } action->popCurPath(pathcode); } break; default: assert(0 && "unknown path code."); break; } #if COIN_DEBUG if (!changedetected) { for (i = last; i >= first; i--) { chksum ^= (uintptr_t)(*this)[i]; } if (chksum != 0xdeadbeef) changedetected = TRUE; } if (changedetected) { SoDebugError::postWarning("SoChildList::traverse", "Detected modification of scene graph layout " "during action traversal. This is considered to " "be hazardous and error prone, and we " "strongly advice you to change your code " "and/or reorganize your scene graph so that " "this is not necessary."); } #endif // COIN_DEBUG } /*! Traverse all nodes in the list, invoking their methods for the given \a action. */ void SoChildList::traverse(SoAction * const action) { if (this->getLength() == 0) return; this->traverse(action, 0, this->getLength() - 1); } /*! Traverse the node at \a index (and possibly its children, if its a group node), applying the nodes' method for the given \a action. */ void SoChildList::traverse(SoAction * const action, const int index) { assert((index >= 0) && (index < this->getLength()) && "index out of bounds"); this->traverse(action, index, index); } /*! Traverse the \a node (and possibly its children, if its a group node), applying the nodes' method for the given \a action. */ void SoChildList::traverse(SoAction * const action, SoNode * node) { int idx = this->find(node); assert(idx != -1); this->traverse(action, idx); } /*! Notify \a path whenever this list of node children changes. */ void SoChildList::addPathAuditor(SoPath * const path) { #if COIN_DEBUG && 0 // debug SoDebugError::postInfo("SoChildList::addPathAuditor", "add SoPath auditor %p to list %p", path, this); #endif // debug this->auditors.append(path); } /*! Remove \a path as an auditor for our list of node children. */ void SoChildList::removePathAuditor(SoPath * const path) { #if COIN_DEBUG && 0 // debug SoDebugError::postInfo("SoChildList::removePathAuditor", "remove SoPath auditor %p from list %p", path, this); #endif // debug const int index = this->auditors.find(path); #if COIN_DEBUG if (index == -1) { SoDebugError::post("SoChildList::removePathAuditor", "no SoPath %p is auditing list %p! (of parent %p (%s))", path, this, this->parent, this->parent ? this->parent->getTypeId().getName().getString() : ""); return; } #endif // COIN_DEBUG this->auditors.remove(index); }