/**************************************************************************\ * * 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 SoInteractionKit SoInteractionKit.h Inventor/nodekits/SoInteractionKit.h \brief The SoInteractionKit class is a base class for draggers. \ingroup nodekits This nodekit class makes it possible to set surrogate paths for parts. Instead of creating new geometry for the dragger, it is possible to specify an existing path in your scene to be used for interaction. All picks on this path will be handled by the dragger. The SoInteractionKit is primarily an internal class used as a superclass for the dragger classes, and it is unlikely that it should be of interest to application programmers, unless you have very special needs in your application. \NODEKIT_PRE_DIAGRAM \verbatim CLASS SoInteractionKit -->"this" "callbackList" --> "topSeparator" --> "geomSeparator" \endverbatim \NODEKIT_POST_DIAGRAM \NODEKIT_PRE_TABLE \verbatim CLASS SoInteractionKit PVT "this", SoInteractionKit --- "callbackList", SoNodeKitListPart [ SoCallback, SoEventCallback ] PVT "topSeparator", SoSeparator --- PVT "geomSeparator", SoSeparator --- \endverbatim \NODEKIT_POST_TABLE */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // COIN_OBSOLETED() #include /*! \enum SoInteractionKit::CacheEnabled Enumeration of valid values for the cache control fields SoInteractionKit::renderCaching, SoInteractionKit::boundingBoxCaching, SoInteractionKit::renderCulling and SoInteractionKit::pickCulling. The same values with the same semantics are present in this enum as for SoSeparator::CacheEnabled, so see that documentation. */ /*! \var SoSFEnum SoInteractionKit::renderCaching Controls the value of the SoSeparator::renderCaching field in the SoInteractionKit catalog's topSeparator instance. See documentation of SoSeparator::renderCaching. */ /*! \var SoSFEnum SoInteractionKit::boundingBoxCaching Controls the value of the SoSeparator::boundingBoxCaching field in the SoInteractionKit catalog's topSeparator instance. See documentation of SoSeparator::boundingBoxCaching. */ /*! \var SoSFEnum SoInteractionKit::renderCulling Controls the value of the SoSeparator::renderCulling field in the SoInteractionKit catalog's topSeparator instance. See documentation of SoSeparator::renderCulling. */ /*! \var SoSFEnum SoInteractionKit::pickCulling Controls the value of the SoSeparator::pickCulling field in the SoInteractionKit catalog's topSeparator instance. See documentation of SoSeparator::pickCulling. */ /*! \var SoFieldSensor * SoInteractionKit::fieldSensor Obsoleted in Coin. */ /*! \var SoFieldSensor * SoInteractionKit::oldTopSep Obsoleted in Coin. */ #ifndef DOXYGEN_SKIP_THIS class SoInteractionKitP { public: SoInteractionKitP(SoInteractionKit * kit) : kit(kit) { } SoInteractionKit * kit; SoFieldSensor * fieldsensor; SoSeparator * connectedseparator; void connectFields(const SbBool onoff); void attachSensor(const SbBool onoff); static void sensorCB(void *, SoSensor *); SoPathList surrogatepathlist; SbList surrogatenamelist; void addSurrogatePath(SoPath * path, const SbName & name); void removeSurrogatePath(const SbName & partname); void removeSurrogatePath(const int idx); int findSurrogateIndex(const SbName & partname) const; int findSurrogateInPath(const SoPath * path); }; #endif // DOXYGEN_SKIP_THIS static SbList * defaultdraggerparts = NULL; // // atexit callback. To delete default dragger parts files. // static void interactionkit_cleanup(void) { int n = defaultdraggerparts->getLength(); for (int i = 0; i < n; i++) { (*defaultdraggerparts)[i]->unref(); } delete defaultdraggerparts; } #undef THIS #define THIS this->pimpl SO_KIT_SOURCE(SoInteractionKit); /*! Constructor. */ SoInteractionKit::SoInteractionKit(void) { THIS = new SoInteractionKitP(this); SO_KIT_INTERNAL_CONSTRUCTOR(SoInteractionKit); SO_KIT_ADD_FIELD(renderCaching, (SoInteractionKit::AUTO)); SO_KIT_ADD_FIELD(boundingBoxCaching, (SoInteractionKit::AUTO)); SO_KIT_ADD_FIELD(renderCulling, (SoInteractionKit::AUTO)); SO_KIT_ADD_FIELD(pickCulling, (SoInteractionKit::AUTO)); SO_KIT_DEFINE_ENUM_VALUE(CacheEnabled, ON); SO_KIT_DEFINE_ENUM_VALUE(CacheEnabled, OFF); SO_KIT_DEFINE_ENUM_VALUE(CacheEnabled, AUTO); SO_KIT_SET_SF_ENUM_TYPE(renderCaching, CacheEnabled); SO_KIT_SET_SF_ENUM_TYPE(boundingBoxCaching, CacheEnabled); SO_KIT_SET_SF_ENUM_TYPE(renderCulling, CacheEnabled); SO_KIT_SET_SF_ENUM_TYPE(pickCulling, CacheEnabled); // Note: we must use "" instead of , , to humour MS VisualC++ 6. SO_KIT_ADD_CATALOG_ENTRY(topSeparator, SoSeparator, TRUE, this, "", FALSE); SO_KIT_ADD_CATALOG_ENTRY(geomSeparator, SoSeparator, TRUE, topSeparator, "", FALSE); SO_KIT_INIT_INSTANCE(); THIS->connectedseparator = NULL; THIS->fieldsensor = new SoFieldSensor(SoInteractionKit::fieldSensorCB, THIS); THIS->fieldsensor->setPriority(0); this->setUpConnections(TRUE, TRUE); } /*! Destructor. */ SoInteractionKit::~SoInteractionKit() { THIS->connectFields(FALSE); delete THIS->fieldsensor; delete THIS; } // doc in super void SoInteractionKit::initClass(void) { defaultdraggerparts = new SbList ; coin_atexit((coin_atexit_f *)interactionkit_cleanup, CC_ATEXIT_NORMAL); SO_KIT_INTERNAL_INIT_CLASS(SoInteractionKit, SO_FROM_INVENTOR_1); } /*! Sets a part in the kit as a surrogate path. The \a partname part is set to \c NULL, and the surrogate path is remembered. Following picks on the surrogate path will be regarded as a pick on \a partname. */ SbBool SoInteractionKit::setPartAsPath(const SbName & partname, SoPath * path) { return this->setAnySurrogatePath(partname, path, TRUE, TRUE); } /*! Sets the value of \a partname to \a node, and sets the part's field to default (i.e. node will not be written on scene graph export). If \a onlyifdefault is \c TRUE, \a partname is only set if it is already in the default state. The reason for this method is to make it possible for dragger subclasses to avoid having their default parts written out on export. */ SbBool SoInteractionKit::setPartAsDefault(const SbName & partname, SoNode * node, SbBool onlyifdefault) { return this->setAnyPartAsDefault(partname, node, FALSE, onlyifdefault); } /*! Find node in the global dictionary, and set as default. \sa setPartAsDefault() */ SbBool SoInteractionKit::setPartAsDefault(const SbName & partname, const SbName & nodename, SbBool onlyifdefault) { return this->setAnyPartAsDefault(partname, nodename, FALSE, onlyifdefault); } /*! Checks if \a path is contained within any of the surrogate paths in any interaction kits from this node down. Returns information about the owner and the surrogate path if found, and \a fillargs is \e TRUE. The returned path (\a pathToOwner) is not ref'ed, It's the callers responsibility to ref and unref this path. */ SbBool SoInteractionKit::isPathSurrogateInMySubgraph(const SoPath * path, SoPath *& pathToOwner, SbName & surrogatename, SoPath *& surrogatepath, SbBool fillargs) { int idx = THIS->findSurrogateInPath(path); if (idx >= 0) { if (fillargs) { pathToOwner = new SoPath(this); // a very short path surrogatename = THIS->surrogatenamelist[idx]; surrogatepath = THIS->surrogatepathlist[idx]; } return TRUE; } else { SoSearchAction sa; sa.setType(SoInteractionKit::getClassTypeId()); sa.setFind(SoSearchAction::ALL); sa.setSearchingAll(TRUE); sa.apply(this); SoPathList & pathlist = sa.getPaths(); for (int i = 0; i < pathlist.getLength(); i++) { SoInteractionKit * kit = (SoInteractionKit *)pathlist[i]->getTail(); assert(kit->isOfType(SoInteractionKit::getClassTypeId())); int idx = kit->pimpl->findSurrogateInPath(path); if (idx >= 0) { if (fillargs) { pathToOwner = pathlist[i]->copy(); surrogatename = kit->pimpl->surrogatenamelist[idx]; surrogatepath = kit->pimpl->surrogatepathlist[idx]; } return TRUE; } } } return FALSE; } /*! \overload */ SbBool SoInteractionKit::isPathSurrogateInMySubgraph(const SoPath * path) { SoPath * dummypath, * dummypath2; SbName dummyname; return this->isPathSurrogateInMySubgraph(path, dummypath, dummyname, dummypath2, FALSE); } /*! Convenience method that sets the switch value for a switch node. Checks if node != 0, and only sets the switch value if value has changed. */ void SoInteractionKit::setSwitchValue(SoNode * node, const int newVal) { if (node == NULL) return; assert(node->isOfType(SoSwitch::getClassTypeId())); SoSwitch * mySwitch = (SoSwitch *)node; if (mySwitch->whichChild.getValue() != newVal) { mySwitch->whichChild = newVal; } } // Doc in superclass. Overridden to copy the surrogate lists. void SoInteractionKit::copyContents(const SoFieldContainer * fromFC, SbBool copyConnections) { int i; inherited::copyContents(fromFC, copyConnections); assert(fromFC->isOfType(SoInteractionKit::getClassTypeId())); SoInteractionKit * kit = (SoInteractionKit *) fromFC; THIS->surrogatenamelist.truncate(0); THIS->surrogatepathlist.truncate(0); const int n = kit->pimpl->surrogatenamelist.getLength(); for (i = 0; i < n; i++) { THIS->surrogatenamelist.append(kit->pimpl->surrogatenamelist[i]); THIS->surrogatepathlist.append(kit->pimpl->surrogatepathlist[i]); } } // Doc in superclass. Overridden to check topSeperator and fields // after reading. SbBool SoInteractionKit::readInstance(SoInput * in, unsigned short flags) { SbBool ret = inherited::readInstance(in, flags); // will handle fields if (ret) { // remove surrogate paths where part != NULL and not an empty // group or separator int n = THIS->surrogatenamelist.getLength(); for (int i = 0; i < n; i++) { SbName name = THIS->surrogatenamelist[i]; SoNode * node = this->getAnyPart(name, FALSE, FALSE, FALSE); if (node && ((node->getTypeId() != SoGroup::getClassTypeId() && node->getTypeId() != SoSeparator::getClassTypeId()) || node->getChildren()->getLength())) { n--; // don't forget this! THIS->surrogatenamelist.remove(i); THIS->surrogatepathlist.remove(i); } } } return ret; } /*! Reads default parts for a dragger. This method is called from dragger constructors to set up a dragger's nodekit catalog of interaction and feedback geometry. \a fileName is the user-changeable resource file in the Inventor file format, while \a defaultBuffer and \a defBufSize can point to the statically compiled default parts. The environment variable \c SO_DRAGGER_DIR must be set to a valid directory prefix for \a fileName, or no resource file will be loaded (and \a defaultBuffer will be used instead). If both a \a fileName and a \a defaultBuffer is provided, the file will be attempted found and loaded first, if that fails, the geometry will be attempted read from the buffer. */ void SoInteractionKit::readDefaultParts(const char * fileName, const char defaultBuffer[], int defBufSize) { // FIXME: it'd be great if this code could be changed so that the // dragger parts file (if any) would just replace the parts from the // default buffer. Then it would be possible to let the diskfile // contain a subset of the full set of geometries for the dragger. I // seem to remember that SGI Inventor works this way. 20020322 mortene. SoInput input; SoNode * root = NULL; const char * draggerdir = coin_getenv("SO_DRAGGER_DIR"); if (fileName && draggerdir) { SbString fullname = draggerdir; const char dirsep = '/'; if (fullname.getLength() && fullname[fullname.getLength()-1] != dirsep) { fullname += dirsep; } fullname += fileName; if (input.openFile(fullname.getString(), TRUE)) { root = (SoNode *)SoDB::readAll(&input); } else if (COIN_DEBUG) { SoDebugError::post("SoInteractionKit::readDefaultParts", "Could not find file '%s' for the dragger " "default parts.", fullname.getString()); } } if (!root && defaultBuffer) { input.setBuffer((void *)defaultBuffer, defBufSize); root = (SoNode *)SoDB::readAll(&input); } if (root) { root->ref(); // this node is unref'ed at exit // FIXME: the nodes are later picked up by SoNode::getByName(), // which means this is a rather lousy and error-prone technique // with the potential for namespace clashes. Should *at* *least* // append a prefix "coininternal_draggerdefaultpart_" or something // to all nodes. See also the related FIXME in // setAnyPartAsDefault(SbName,SbName). 20020322 mortene. defaultdraggerparts->append(root); } else { SoDebugError::post("SoInteractionKit::readDefaultParts", "Dragger default parts not available."); } } /*! Protected version of setPartAsDefault(), to make it possible to set non-leaf and private parts (if \a anypart is \c TRUE). \sa setPartAsDefault() */ SbBool SoInteractionKit::setAnyPartAsDefault(const SbName & partname, SoNode * node, SbBool anypart, SbBool onlyifdefault) { SoBaseKit * kit = this; int partNum; SbBool isList; int listIdx; if (SoBaseKit::findPart(SbString(partname.getString()), kit, partNum, isList, listIdx, TRUE)) { SoSFNode * field = kit->getCatalogInstances()[partNum]; // FIXME: default check not working properly. pederb, 2000-01-21 if (1 || (!onlyifdefault || field->isDefault())) { kit->setPart(partNum, node); field->setDefault(TRUE); } else { if (COIN_DEBUG) { SoDebugError::postInfo("SoInteractionKit::setAnyPartAsDefault", "no permission to set"); } } } else if (COIN_DEBUG) { SoDebugError::postInfo("SoInteractionKit::setAnyPartAsDefault", "part %s not found", partname.getString()); } // FIXME: this method is _always_ returning FALSE, which seems // completely bogus. 20020322 mortene. return FALSE; } /*! Protected version of setPartAsDefault(), to make it possible to set non-leaf and private parts (if anypart is \c TRUE). \sa setPartAsDefault() */ SbBool SoInteractionKit::setAnyPartAsDefault(const SbName & partname, const SbName & nodename, SbBool anypart, SbBool onlyifdefault) { // FIXME: this is lame and error-prone -- default dragger-parts are // actually just stored outside any scenegraph, and then picked up // like this. We should at least prefix the node names with an // internal namespace prefix. See also the related FIXME in // readDefaultParts(). 20020322 mortene. SoNode * node = (SoNode *) SoBase::getNamedBase(nodename, SoNode::getClassTypeId()); if (node) { return this->setAnyPartAsDefault(partname, node, anypart, onlyifdefault); } else if (COIN_DEBUG && 1) { // debug SoDebugError::postInfo("SoInteractionKit::setAnyPartAsDefault", "nodename %s not found", nodename.getString()); // FIXME: temporary code, pederb 2000-01-21 node = new SoText2(); ((SoText2 *)node)->string = "Default dragger part not found"; return this->setAnyPartAsDefault(partname, node, anypart, onlyifdefault); } return FALSE; } // FIXME: the API doc on setAnySurrogatePath() below stinks. Surrogate // parts is such a useful mechanism that it deserves proper // documentation. We should explain what it's good for, the details of // setting up a surrogate part, and add in a small usage example (ie // source code). 20021008 mortene. /*! Protected version of setPartAsPath(), to make it possible to set non-leaf and private parts. ("The nice thing about C++ is that only your friends can handle your private parts.") \sa setPartAsPath() */ SbBool SoInteractionKit::setAnySurrogatePath(const SbName & partname, SoPath * path, SbBool leafcheck, SbBool publiccheck) { SoBaseKit * kit = this; int partNum; SbBool isList; int listIdx; if (SoBaseKit::findPart(SbString(partname.getString()), kit, partNum, isList, listIdx, TRUE)) { assert(kit->isOfType(SoInteractionKit::getClassTypeId())); const SoNodekitCatalog * catalog = kit->getNodekitCatalog(); if (leafcheck && !catalog->isLeaf(partNum)) { if (COIN_DEBUG) { SoDebugError::postInfo("SoInteractionKit::setAnySurrogatePath", "part %s is not leaf", partname.getString()); } return FALSE; } if (publiccheck && !catalog->isPublic(partNum)) { if (COIN_DEBUG) { SoDebugError::postInfo("SoInteractionKit::setAnySurrogatePath", "part %s is not public", partname.getString()); } return FALSE; } int parentIdx = catalog->getParentPartNumber(partNum); SoNode * parent = this->getCatalogInstances()[parentIdx]->getValue(); if (parent->isOfType(SoSwitch::getClassTypeId())) { SoNode * node = this->getCatalogInstances()[partNum]->getValue(); SoType type = node->getTypeId(); if (type == SoGroup::getClassTypeId() || type == SoSeparator::getClassTypeId()) { // replace with empty group to keep switch numbering kit->setPart(partNum, (SoNode *)type.createInstance()); } else { // set to NULL and update switch numbering SoSwitch * sw = (SoSwitch *)parent; int whichChild = sw->whichChild.getValue(); int partIdx = sw->findChild(node); if (partIdx == whichChild) { sw->whichChild.setValue(SO_SWITCH_NONE); } else if (partIdx < whichChild) { sw->whichChild.setValue(whichChild-1); } kit->setPart(partNum, NULL); } } else { // set part to NULL kit->setPart(partNum, NULL); } // add the path ((SoInteractionKit *)kit)->pimpl->addSurrogatePath(path, catalog->getName(partNum)); return TRUE; } else if (COIN_DEBUG) { SoDebugError::postInfo("SoInteractionKit::setAnyPartAsDefault", "part %s not found", partname.getString()); } return FALSE; } // doc in super SbBool SoInteractionKit::setUpConnections(SbBool onoff, SbBool doitalways) { if (onoff == this->connectionsSetUp && !doitalways) return onoff; if (onoff) { inherited::setUpConnections(onoff, FALSE); THIS->connectFields(TRUE); THIS->attachSensor(TRUE); } else { THIS->attachSensor(FALSE); THIS->connectFields(FALSE); inherited::setUpConnections(onoff, FALSE); } return !(this->connectionsSetUp = onoff); } // Doc in superclass. SbBool SoInteractionKit::setPart(const int partNum, SoNode * node) { // Overriden to detect when part changes value. If a substitute path // for that part exists, it must be cleared. THIS->removeSurrogatePath(this->getNodekitCatalog()->getName(partNum)); return inherited::setPart(partNum, node); } // test if ok to set default and then do it if test succeeds static void test_set_default(SoSFEnum * field, int value) { if (!(field->isConnected() && field->isConnectionEnabled()) && field->getValue() == value) field->setDefault(TRUE); } // doc in super void SoInteractionKit::setDefaultOnNonWritingFields(void) { this->topSeparator.setDefault(TRUE); this->geomSeparator.setDefault(TRUE); test_set_default(&this->renderCaching, SoInteractionKit::AUTO); test_set_default(&this->boundingBoxCaching, SoInteractionKit::AUTO); test_set_default(&this->pickCulling, SoInteractionKit::AUTO); test_set_default(&this->renderCulling, SoInteractionKit::AUTO); const SoNodekitCatalog * catalog = this->getNodekitCatalog(); for (int i = 1; i < this->getCatalogInstances().getLength(); i++) { if (!catalog->isLeaf(i)) { SoNode * node = this->getCatalogInstances()[i]->getValue(); if (node && node->getTypeId() == SoSwitch::getClassTypeId()) { this->getCatalogInstances()[i]->setDefault(TRUE); } } } inherited::setDefaultOnNonWritingFields(); } // doc in super SbBool SoInteractionKit::setPart(const SbName & partname, SoNode * from) { // Overridden only to fool some incredibly stupid behaviour in the // gcc 2.95.2 compiler, who couldn't figure out I wanted to call // this function in SoBaseKit, but instead insisted that I tried to // call SoInteractionKit::setPart(int, SoNode *). Cheeessssh. // . return inherited::setPart(partname, from); } /*! \COININTERNAL */ void SoInteractionKit::fieldSensorCB(void * d, SoSensor * s) { SoInteractionKitP::sensorCB(d, s); } /*! Obsoleted in Coin. */ void SoInteractionKit::connectSeparatorFields(SoSeparator * dest, SbBool onOff) { COIN_OBSOLETED(); SoDebugError::postWarning("SoInteractionKit::connectSeparatorFields", "SoSeparator* input argument ignored, " "using topSeparator"); THIS->connectFields(onOff); } #undef THIS /*** methods for SoInteractionKitP are below *****************************/ #ifndef DOXYGEN_SKIP_THIS // // checks if partname is in surrogate list. Returns index, -1 if not found. // int SoInteractionKitP::findSurrogateIndex(const SbName & partname) const { int i, n = this->surrogatenamelist.getLength(); for (i = 0; i < n; i++) { if (this->surrogatenamelist[i] == partname) return i; } return -1; } // // removes surrogate path with name 'partname' // void SoInteractionKitP::removeSurrogatePath(const SbName & partname) { int idx = this->findSurrogateIndex(partname); if (idx >= 0) this->removeSurrogatePath(idx); } // // removes a specified surrogate path // void SoInteractionKitP::removeSurrogatePath(const int idx) { assert(idx >= 0 && idx < this->surrogatenamelist.getLength()); this->surrogatenamelist.remove(idx); this->surrogatepathlist.remove(idx); } // // return index of surrogate path that is contained within path, // or -1 if none found. // int SoInteractionKitP::findSurrogateInPath(const SoPath * path) { int n = this->surrogatepathlist.getLength(); for (int i = 0; i < n; i++) { if (path->containsPath(this->surrogatepathlist[i])) return i; } return -1; } // // adds or replaces a surrogate path // void SoInteractionKitP::addSurrogatePath(SoPath * path, const SbName & name) { int idx = this->findSurrogateIndex(name); if (idx >= 0) { this->surrogatepathlist.remove(idx); this->surrogatenamelist.remove(idx); } this->surrogatepathlist.append(path); this->surrogatenamelist.append(name); } // // connect fields in topSeparator to the fields in this node. // void SoInteractionKitP::connectFields(const SbBool onoff) { if (this->connectedseparator) { // always disconnect this->connectedseparator->renderCaching.disconnect(); this->connectedseparator->boundingBoxCaching.disconnect(); this->connectedseparator->renderCulling.disconnect(); this->connectedseparator->pickCulling.disconnect(); this->connectedseparator->unref(); this->connectedseparator = NULL; } if (onoff) { SoSeparator * sep = (SoSeparator*) this->kit->topSeparator.getValue(); if (sep) { this->connectedseparator = sep; this->connectedseparator->ref(); // ref to make sure pointer is legal sep->renderCaching.connectFrom(&this->kit->renderCaching); sep->boundingBoxCaching.connectFrom(&this->kit->boundingBoxCaching); sep->renderCulling.connectFrom(&this->kit->renderCulling); sep->pickCulling.connectFrom(&this->kit->pickCulling); } } } // // attach sensor to topSeparator if onoff, detach otherwise // void SoInteractionKitP::attachSensor(const SbBool onoff) { if (onoff) { if (this->fieldsensor->getAttachedField() != &this->kit->topSeparator) { this->fieldsensor->attach(&this->kit->topSeparator); } } else { if (this->fieldsensor->getAttachedField()) this->fieldsensor->detach(); } } // // callback from field sensor connected to topSeparator // void SoInteractionKitP::sensorCB(void * data, SoSensor *) { SoInteractionKitP * thisp = (SoInteractionKitP*) data; if (thisp->connectedseparator != thisp->kit->topSeparator.getValue()) { thisp->connectFields(TRUE); } } #endif // DOXYGEN_SKIP_THIS