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 SoProto SoProto.h Inventor/misc/SoProto.h
35   \brief The SoProto class handles PROTO definitions.
36 
37   SoProto and SoProtoInstance are mostly internal classes. They're
38   designed to read and handle VRML97 PROTOs. However, it's possible to
39   define your protos in C++. You must define your proto in a char
40   array, and read that char array using SoInput::setBuffer() and
41   SoDB::readAllVRML(). Example:
42 
43   \code
44 
45   char myproto[] =
46   "#VRML V2.0 utf8\n"
47   "PROTO ColorCube [\n"
48   "  field SFColor color 1 1 1\n"
49   "  field SFVec3f size 1 1 1\n"
50   "]\n"
51   "{\n"
52   "  Shape {\n"
53   "    appearance Appearance {\n"
54   "      material Material {\n"
55   "        diffuseColor IS color\n"
56   "      }\n"
57   "    }\n"
58   "    geometry Box { size IS size }\n"
59   "  }\n"
60   "}\n"
61   "ColorCube { color 1 0 0 size 2 1 1 }\n";
62 
63   SoInput in;
64   in.setBuffer((void*) myproto, strlen(myproto));
65   SoVRMLGroup * protoroot = SoDB::readAllVRML(&in);
66 
67   \endcode
68 
69   Now you can create new instances of the ColorCube proto using
70   SoProto::findProto() and SoProto::createProtoInstance(). If you want
71   to insert proto instances into your scene graph, you should insert
72   the node returned from SoProtoInstance::getRootNode().
73 
74   See
75   http://www.web3d.org/documents/specifications/14772/V2.0/part1/concepts.html#4.8
76   for more information about PROTOs in VRML97.
77 
78 */
79 
80 // *************************************************************************
81 
82 /*! \file SoProto.h */
83 #include <Inventor/misc/SoProto.h>
84 
85 #include <cstring>
86 
87 #include <Inventor/C/tidbits.h>
88 #include <Inventor/SbName.h>
89 #include <Inventor/SoDB.h>
90 #include <Inventor/SoInput.h>
91 #include <Inventor/SoOutput.h>
92 #include <Inventor/actions/SoSearchAction.h>
93 #include <Inventor/actions/SoWriteAction.h>
94 #include <Inventor/engines/SoEngineOutput.h>
95 #include <Inventor/engines/SoNodeEngine.h>
96 #include <Inventor/errors/SoDebugError.h>
97 #include <Inventor/errors/SoReadError.h>
98 #include <Inventor/fields/SoField.h>
99 #include <Inventor/fields/SoFieldData.h>
100 #include <Inventor/fields/SoSFNode.h>
101 #include <Inventor/fields/SoMFString.h>
102 #include <Inventor/lists/SbList.h>
103 #include <Inventor/lists/SoNodeList.h>
104 #include <Inventor/misc/SoChildList.h>
105 #include <Inventor/misc/SoProtoInstance.h>
106 #include <Inventor/nodes/SoGroup.h>
107 #include <Inventor/nodes/SoSeparator.h>
108 
109 #include "threads/threadsutilp.h"
110 #include "io/SoWriterefCounter.h"
111 #include "misc/SbHash.h"
112 #include "tidbitsp.h"
113 
114 // *************************************************************************
115 
116 static SoType soproto_type;
117 
118 static SbList <SoProto*> * protolist;
119 static SoFetchExternProtoCB * soproto_fetchextern_cb = NULL;
120 static void * soproto_fetchextern_closure = NULL;
121 static void * soproto_mutex;
122 
123 // atexit callback
124 static void
soproto_cleanup(void)125 soproto_cleanup(void)
126 {
127   delete protolist;
128   protolist = NULL;
129   CC_MUTEX_DESTRUCT(soproto_mutex);
130 }
131 
132 static SoProto *
soproto_fetchextern_default_cb(SoInput * in,const SbString * urls,const int numurls,void * COIN_UNUSED_ARG (closure))133 soproto_fetchextern_default_cb(SoInput * in,
134                                const SbString * urls,
135                                const int numurls,
136                                void * COIN_UNUSED_ARG(closure))
137 {
138   if (numurls == 0) return NULL;
139   SbString filename(urls[0]);
140   SbString name("");
141 
142   int nameidx = filename.find("#");
143   if (nameidx >= 1) {
144     SbString tmp(filename);
145     filename = tmp.getSubString(0, nameidx-1);
146     name = tmp.getSubString(nameidx+1);
147   }
148 
149   if (!in->pushFile(filename.getString())) {
150     SoReadError::post(in, "Unable to find EXTERNPROTO file: ``%s''",
151                       filename.getString());
152     return NULL;
153   }
154 
155   SoSeparator * root = SoDB::readAll(in);
156   if (!root) {
157     // Take care of popping the file off the stack. This is a bit
158     // "hack-ish", but its done this way instead of loosening the
159     // protection of SoInput::popFile().
160     if (in->getCurFileName() == filename) {
161       char dummy;
162       while (!in->eof() && in->get(dummy)) { }
163 
164       assert(in->eof());
165 
166       // Make sure the stack is really popped on EOF. Popping happens
167       // when attempting to read when the current file in the stack is
168       // at EOF.
169       SbBool gotchar = in->get(dummy);
170       if (gotchar) in->putBack(dummy);
171     }
172 
173     SoReadError::post(in, "Unable to read EXTERNPROTO file: ``%s''",
174                       filename.getString());
175     return NULL;
176   }
177   else {
178     root->ref();
179     SoProto * foundproto = NULL;
180 
181     SoSearchAction sa;
182     sa.setType(SoProto::getClassTypeId());
183     sa.setSearchingAll(TRUE);
184     sa.setInterest(SoSearchAction::ALL);
185     sa.apply(root);
186 
187     SoPathList & pl = sa.getPaths();
188 
189     if (pl.getLength() == 1) {
190       foundproto = (SoProto*) pl[0]->getTail();
191       if (name.getLength() && name != foundproto->getProtoName().getString()) {
192         foundproto = NULL;
193       }
194     }
195     else if (name.getLength()) {
196       int i;
197       for (i = 0; i < pl.getLength(); i++) {
198         SoProto * proto = (SoProto*) pl[i]->getTail();
199         if (name == proto->getProtoName().getString()) break;
200       }
201       if (i < pl.getLength()) {
202         foundproto = (SoProto*) pl[i]->getTail();
203       }
204     }
205     sa.reset(); // clear paths in action.
206     if (foundproto) foundproto->ref();
207     root->unref();
208     if (foundproto) foundproto->unrefNoDelete();
209     return foundproto;
210   }
211 
212   // just in case to fool stupid compilers
213   return NULL;
214 }
215 
216 // *************************************************************************
217 
218 typedef SbHash<const char *, SoBase *> Name2SoBaseMap;
219 
220 class SoProtoP {
221 public:
SoProtoP()222   SoProtoP() : fielddata(NULL), defroot(NULL) { }
223 
224   SoFieldData * fielddata;
225   SoGroup * defroot;
226   SbName name;
227   SbList <SoNode*> isnodelist; // FIXME: consider using SoNodeList
228   SbList <SbName> isfieldlist;
229   SbList <SbName> isnamelist;
230   Name2SoBaseMap refdict;
231   SbList <SbName> routelist;
232   SoMFString * externurl;
233   SoProto * extprotonode;
234 };
235 
236 // *************************************************************************
237 
238 // doc in parent
239 SoType
getTypeId(void) const240 SoProto::getTypeId(void) const
241 {
242   return soproto_type;
243 }
244 
245 // doc in parent
246 SoType
getClassTypeId(void)247 SoProto::getClassTypeId(void)
248 {
249   return soproto_type;
250 }
251 
252 // doc in parent
253 void
initClass(void)254 SoProto::initClass(void)
255 {
256   CC_MUTEX_CONSTRUCT(soproto_mutex);
257   soproto_type = SoType::createType(SoNode::getClassTypeId(),
258                                     SbName("SoProto"), NULL,
259                                     SoNode::nextActionMethodIndex++);
260   protolist = new SbList<SoProto*>;
261 
262   coin_atexit((coin_atexit_f*) soproto_cleanup, CC_ATEXIT_NORMAL);
263   // this will set a default callback
264   SoProto::setFetchExternProtoCallback(NULL, NULL);
265 }
266 
267 #define PRIVATE(obj) ((obj)->pimpl)
268 
269 /*!
270   Constructor.
271 */
SoProto(const SbBool externproto)272 SoProto::SoProto(const SbBool externproto)
273 {
274   PRIVATE(this) = new SoProtoP;
275   PRIVATE(this)->externurl = NULL;
276   if (externproto) {
277     PRIVATE(this)->externurl = new SoMFString;
278   }
279   PRIVATE(this)->fielddata = new SoFieldData;
280   PRIVATE(this)->defroot = new SoGroup;
281   PRIVATE(this)->defroot->ref();
282   PRIVATE(this)->extprotonode = NULL;
283 
284   CC_MUTEX_LOCK(soproto_mutex);
285   protolist->insert(this, 0);
286   CC_MUTEX_UNLOCK(soproto_mutex);
287 }
288 
289 /*!
290   Destructor.
291 */
~SoProto()292 SoProto::~SoProto()
293 {
294   const int n = PRIVATE(this)->fielddata->getNumFields();
295   for (int i = 0; i < n; i++) {
296     delete PRIVATE(this)->fielddata->getField(this, i);
297   }
298   PRIVATE(this)->defroot->unref();
299   delete PRIVATE(this)->externurl;
300 
301   if (PRIVATE(this)->extprotonode) {
302     PRIVATE(this)->extprotonode->unref();
303   }
304   delete PRIVATE(this)->fielddata;
305   delete PRIVATE(this);
306 }
307 
308 void
setFetchExternProtoCallback(SoFetchExternProtoCB * cb,void * closure)309 SoProto::setFetchExternProtoCallback(SoFetchExternProtoCB * cb,
310                                      void * closure)
311 {
312   if (cb == NULL) {
313     soproto_fetchextern_cb = soproto_fetchextern_default_cb;
314     soproto_fetchextern_closure = NULL;
315   }
316   else {
317     soproto_fetchextern_cb = cb;
318     soproto_fetchextern_closure = closure;
319   }
320 }
321 
322 /*!
323   Returns the PROTO definition named \a name or NULL if not found.
324 */
325 SoProto *
findProto(const SbName & name)326 SoProto::findProto(const SbName & name)
327 {
328   SoProto * ret = NULL;
329   CC_MUTEX_LOCK(soproto_mutex);
330   if (protolist) {
331     const int n = protolist->getLength();
332     SoProto * const * ptr = protolist->getArrayPtr();
333     for (int i = 0; (ret == NULL) && (i < n); i++) {
334       if (ptr[i]->getProtoName() == name) ret = ptr[i];
335     }
336   }
337   CC_MUTEX_UNLOCK(soproto_mutex);
338   return ret;
339 }
340 
341 /*!
342   Creates an instance of the PROTO.
343 */
344 SoProtoInstance *
createProtoInstance(void)345 SoProto::createProtoInstance(void)
346 {
347   if (PRIVATE(this)->extprotonode) {
348     return PRIVATE(this)->extprotonode->createProtoInstance();
349   }
350   SoProtoInstance * inst = new SoProtoInstance(this, PRIVATE(this)->fielddata);
351   inst->ref();
352   inst->setRootNode(this->createInstanceRoot(inst));
353   return inst;
354 }
355 
356 /*!
357   Returns the PROTO name.
358 */
359 SbName
getProtoName(void) const360 SoProto::getProtoName(void) const
361 {
362   return PRIVATE(this)->name;
363 }
364 
365 // Documented in superclass. Overridden to read Proto definition.
366 SbBool
readInstance(SoInput * in,unsigned short COIN_UNUSED_ARG (flags))367 SoProto::readInstance(SoInput * in, unsigned short COIN_UNUSED_ARG(flags))
368 {
369   SbName protoname;
370 
371   char c;
372   SbBool ok = in->read(protoname, TRUE);
373   if (ok) {
374     PRIVATE(this)->name = protoname;
375     ok = this->readInterface(in);
376   }
377   if (!ok) {
378     SoReadError::post(in, "Error parsing PROTO interface.");
379   }
380   else if (!PRIVATE(this)->externurl) {
381     ok = in->read(c) && c == '{';
382     if (ok) ok = this->readDefinition(in);
383   }
384   else {
385     ok = PRIVATE(this)->externurl->read(in, SbName("EXTERNPROTO URL"));
386     if (ok) {
387       SoProto * proto = soproto_fetchextern_cb(in,
388                                                PRIVATE(this)->externurl->getValues(0),
389                                                PRIVATE(this)->externurl->getNum(),
390                                                soproto_fetchextern_closure);
391       if (proto == NULL) {
392         SoReadError::post(in, "Error reading EXTERNPROTO definition.");
393         ok = FALSE;
394       }
395       else {
396         ok = this->setupExtern(in, proto);
397       }
398     }
399   }
400   return ok;
401 }
402 
403 // Doc in parent
404 void
destroy(void)405 SoProto::destroy(void)
406 {
407   CC_MUTEX_LOCK(soproto_mutex);
408   int idx = protolist->find(this);
409   assert(idx >= 0);
410   protolist->remove(idx);
411   CC_MUTEX_UNLOCK(soproto_mutex);
412   SoBase::destroy();
413 }
414 
415 // doc in parent
416 void
write(SoWriteAction * action)417 SoProto::write(SoWriteAction * action)
418 {
419   SoOutput * out = action->getOutput();
420   out->pushProto(this);
421 
422   if (out->getStage() == SoOutput::COUNT_REFS) {
423     this->addWriteReference(out, FALSE);
424     if (PRIVATE(this)->defroot && !PRIVATE(this)->externurl) {
425       this->writeDefinition(action);
426     }
427     this->writeInterface(out);
428   }
429   else if (out->getStage() == SoOutput::WRITE) {
430     int writerefcount = SoWriterefCounter::instance(out)->getWriteref(this);
431 
432     out->write(PRIVATE(this)->externurl ? "EXTERNPROTO " : "PROTO ");
433     out->write(PRIVATE(this)->name.getString());
434     if (SoWriterefCounter::debugWriterefs()) {
435       SbString tmp;
436       tmp.sprintf(" [ # writeref: %d\n",
437                   writerefcount);
438       out->write(tmp.getString());
439     }
440     else {
441       out->write(" [\n");
442     }
443     out->incrementIndent();
444 
445     this->writeInterface(out);
446 
447     out->decrementIndent();
448     out->indent();
449     out->write("]\n");
450     if (PRIVATE(this)->externurl) {
451       this->writeURLs(out);
452     }
453     else {
454       out->indent();
455       out->write("{\n");
456       out->incrementIndent();
457 
458       if (PRIVATE(this)->defroot) {
459         this->writeDefinition(action);
460       }
461       out->resolveRoutes();
462       out->decrementIndent();
463       out->indent();
464       out->write("}");
465     }
466 
467 #if COIN_DEBUG
468     if (SoWriterefCounter::debugWriterefs()) {
469       int writerefcount = SoWriterefCounter::instance(out)->getWriteref(this);
470       SoDebugError::postInfo("SoProto::write",
471                              "%p/%s/'%s': %d -> %d",
472                              this,
473                              this->getTypeId().getName().getString(),
474                              this->getName().getString(),
475                              writerefcount, writerefcount - 1);
476     }
477 #endif // COIN_DEBUG
478 
479     writerefcount--;
480     SoWriterefCounter::instance(out)->setWriteref(this, writerefcount);
481   }
482   else assert(0 && "unknown stage");
483 
484   out->popProto();
485 }
486 
487 //
488 // Writes the PROTO interface
489 //
490 SbBool
writeInterface(SoOutput * out)491 SoProto::writeInterface(SoOutput * out)
492 {
493   const SoFieldData * fd = PRIVATE(this)->fielddata;
494 
495   if (out->getStage() == SoOutput::COUNT_REFS) {
496     for (int i = 0; i < fd->getNumFields(); i++) {
497       SoField * f = fd->getField(this, i);
498       switch (f->getFieldType()) {
499       case SoField::NORMAL_FIELD:
500       case SoField::EXPOSED_FIELD:
501         if (!PRIVATE(this)->externurl) {
502           SbBool fieldwasdefault = f->isDefault();
503           if (fieldwasdefault) f->setDefault(FALSE);
504           f->write(out, fd->getFieldName(i));
505           if (fieldwasdefault) f->setDefault(TRUE);
506         }
507         break;
508       }
509     }
510   }
511   else {
512     for (int i = 0; i < fd->getNumFields(); i++) {
513       out->indent();
514       SoField * f = fd->getField(this, i);
515       SoType t = f->getTypeId();
516       switch (f->getFieldType()) {
517       case SoField::NORMAL_FIELD:
518         out->write("field ");
519         out->write(t.getName().getString());
520         if (PRIVATE(this)->externurl) {
521           out->write(' ');
522           out->write(fd->getFieldName(i).getString());
523           out->write("\n");
524         }
525         else {
526           // field values are tagged as default for proto interface instances,
527           // but needs to be written to file anyway -- 20040115 larsa
528           SbBool fieldwasdefault = f->isDefault();
529           if ( fieldwasdefault ) f->setDefault(FALSE);
530           f->write(out, fd->getFieldName(i));
531           if ( fieldwasdefault ) f->setDefault(TRUE);
532         }
533         break;
534       case SoField::EXPOSED_FIELD:
535         out->write("exposedField ");
536         out->write(t.getName().getString());
537         if (PRIVATE(this)->externurl) {
538           out->write(' ');
539           out->write(fd->getFieldName(i).getString());
540           out->write("\n");
541         }
542         else {
543           SbBool fieldwasdefault = f->isDefault();
544           if ( fieldwasdefault ) f->setDefault(FALSE);
545           f->write(out, fd->getFieldName(i));
546           if ( fieldwasdefault ) f->setDefault(TRUE);
547         }
548         break;
549       case SoField::EVENTIN_FIELD:
550         out->write("eventIn ");
551         out->write(t.getName().getString());
552         out->write(' ');
553         out->write(fd->getFieldName(i).getString());
554         break;
555       case SoField::EVENTOUT_FIELD:
556         out->write("eventOut ");
557         out->write(t.getName().getString());
558         out->write(' ');
559         out->write(fd->getFieldName(i).getString());
560         break;
561       default:
562         assert(0 && "invalid field type");
563         break;
564       }
565     }
566   }
567   return TRUE;
568 }
569 
570 //
571 // Writes the PROTO definition
572 //
573 SbBool
writeDefinition(SoWriteAction * action)574 SoProto::writeDefinition(SoWriteAction * action)
575 {
576   SoOutput * out = action->getOutput();
577   SoGroup * def = PRIVATE(this)->defroot;
578 
579   if (out->getStage() == SoOutput::COUNT_REFS) {
580     for (int i = 0; i < def->getNumChildren(); i++) {
581       def->getChild(i)->write(action);
582     }
583   }
584   else if (out->getStage() == SoOutput::WRITE) {
585     for (int i = 0; i < def->getNumChildren(); i++) {
586       def->getChild(i)->write(action);
587     }
588   }
589   else assert(0 && "unknown stage");
590   return TRUE;
591 }
592 
593 SbBool
writeURLs(SoOutput * out)594 SoProto::writeURLs(SoOutput * out)
595 {
596   // We use this code to write the URLs to get nicer indentation. Just
597   // calling PRIVATE(this)->externurl->write(out, SbName::empty()) would also have
598   // produced a valid VRML file.
599 
600   const int n = PRIVATE(this)->externurl->getNum();
601   if (n == 1) {
602     out->indent();
603     out->write('\"');
604     out->write((*PRIVATE(this)->externurl)[0].getString());
605     out->write("\"\n");
606   }
607   else {
608     out->indent();
609     out->write("[\n");
610     out->incrementIndent();
611     for (int i = 0; i < n; i++) {
612       out->indent();
613       out->write('\"');
614       out->write((*PRIVATE(this)->externurl)[i].getString());
615       out->write(i < n-1 ? "\",\n" : "\"\n");
616     }
617     out->decrementIndent();
618     out->indent();
619     out->write("]\n");
620   }
621   return TRUE;
622 }
623 
624 /*!
625   Adds an IS reference for this PROTO definition.
626 */
627 void
addISReference(SoNode * container,const SbName & fieldname,const SbName & interfacename)628 SoProto::addISReference(SoNode * container,
629                         const SbName & fieldname,
630                         const SbName & interfacename)
631 {
632   assert(container->isOfType(SoNode::getClassTypeId()));
633   PRIVATE(this)->isnodelist.append(container);
634   PRIVATE(this)->isfieldlist.append(fieldname);
635   PRIVATE(this)->isnamelist.append(interfacename);
636 }
637 
638 /*!
639   If \a container is a PROTO definition node with an IS interface
640   field named \a fieldname, return the interface name, otherwise
641   return an empty SbName.
642 */
643 SbName
findISReference(const SoFieldContainer * container,const SbName & fieldname)644 SoProto::findISReference(const SoFieldContainer * container,
645                          const SbName & fieldname)
646 {
647   const int n = PRIVATE(this)->isnodelist.getLength();
648   for (int i = 0; i < n; i++) {
649     if (PRIVATE(this)->isnodelist[i] == container &&
650         PRIVATE(this)->isfieldlist[i] == fieldname) return PRIVATE(this)->isnamelist[i];
651   }
652   return SbName::empty();
653 }
654 
655 
656 /*!
657   Adds a reference for this PROTO definition.
658 */
659 void
addReference(const SbName & name,SoBase * base)660 SoProto::addReference(const SbName & name, SoBase * base)
661 {
662   PRIVATE(this)->refdict.put(name.getString(), base);
663 }
664 
665 /*!
666   Removes a reference for this PROTO definition.
667 */
668 void
removeReference(const SbName & name)669 SoProto::removeReference(const SbName & name)
670 {
671   PRIVATE(this)->refdict.erase(name.getString());
672 }
673 
674 /*!
675   Finds a reference for this PROTO definition.
676 */
677 SoBase *
findReference(const SbName & name) const678 SoProto::findReference(const SbName & name) const
679 {
680   SoBase * base;
681 
682   if (PRIVATE(this)->refdict.get(name.getString(), base)) { return base; }
683   return NULL;
684 }
685 
686 /*!
687   Adds a ROUTE for this PROTO definition.
688 */
689 void
addRoute(const SbName & fromnode,const SbName & fromfield,const SbName & tonode,const SbName & tofield)690 SoProto::addRoute(const SbName & fromnode, const SbName & fromfield,
691                   const SbName & tonode, const SbName & tofield)
692 {
693   PRIVATE(this)->routelist.append(fromnode);
694   PRIVATE(this)->routelist.append(fromfield);
695   PRIVATE(this)->routelist.append(tonode);
696   PRIVATE(this)->routelist.append(tofield);
697 }
698 
699 //
700 // Reads the interface
701 //
702 SbBool
readInterface(SoInput * in)703 SoProto::readInterface(SoInput * in)
704 {
705   SbBool ok = PRIVATE(this)->fielddata->readFieldDescriptions(in, this, 4, PRIVATE(this)->externurl == NULL);
706   if ( ok ) {
707     const int numfields = PRIVATE(this)->fielddata->getNumFields();
708     for (int i = 0; i < numfields; i++) {
709       SoField * f = PRIVATE(this)->fielddata->getField(this, i);
710       switch ( f->getFieldType() ) {
711       case SoField::NORMAL_FIELD:
712       case SoField::EXPOSED_FIELD:
713         f->setDefault(TRUE);
714       }
715     }
716   }
717   return ok;
718 }
719 
720 //
721 // Reads the definition
722 //
723 SbBool
readDefinition(SoInput * in)724 SoProto::readDefinition(SoInput * in)
725 {
726   SbBool ok = TRUE;
727   SoBase * child;
728   in->pushProto(this);
729 
730   while (ok) {
731     ok = SoBase::read(in, child, SoNode::getClassTypeId());
732     if (ok) {
733       if (child == NULL) {
734         if (in->eof()) {
735           ok = FALSE;
736           SoReadError::post(in, "Premature end of file");
737         }
738         break; // finished reading, break out
739       }
740       else {
741         PRIVATE(this)->defroot->addChild((SoNode*) child);
742       }
743     }
744   }
745   in->popProto();
746   char c;
747   return ok && in->read(c) && c == '}';
748 }
749 
750 static SoNode *
soproto_find_node(SoNode * root,SbName name,SoSearchAction & sa)751 soproto_find_node(SoNode * root, SbName name, SoSearchAction & sa)
752 {
753   sa.setName(name);
754   sa.setInterest(SoSearchAction::FIRST);
755   sa.setSearchingAll(TRUE);
756 
757   sa.apply(root);
758 
759   SoNode * ret = NULL;
760 
761   if (sa.getPath()) {
762     ret = ((SoFullPath*)sa.getPath())->getTail();
763   }
764   sa.reset();
765   return ret;
766 }
767 
768 //
769 // Used to check for fieldname. Wil first test "<name>", then "set_<name>",
770 // and then "<name>_changed".
771 //
772 static SbName
soproto_find_fieldname(SoNode * node,const SbName & name)773 soproto_find_fieldname(SoNode * node, const SbName & name)
774 {
775   if (node->getField(name)) return name;
776   SbString test;
777   if (strncmp("set_", name.getString(), 4) == 0) {
778     test = name.getString() + 4;
779     if (node->getField(test.getString())) return SbName(test.getString());
780   }
781   test = name.getString();
782   // test for "_changed" at the end of the fieldname
783   if (test.getLength() > 8) { // 8 == strlen("_changed")
784     test = test.getSubString(0, test.getLength() - 9);
785     if (node->getField(test.getString())) return SbName(test.getString());
786   }
787   return name;
788 }
789 
790 //
791 // Used to check for outputname. Wil first test "<name>", then "set_<name>",
792 // and then "<name>_changed".
793 //
794 static SbName
soproto_find_outputname(SoNodeEngine * node,const SbName & name)795 soproto_find_outputname(SoNodeEngine * node, const SbName & name)
796 {
797   if (node->getOutput(name)) return name;
798   SbString test;
799   if (strncmp("set_", name.getString(), 4) == 0) {
800     test = name.getString() + 4;
801     if (node->getOutput(test.getString())) return SbName(test.getString());
802   }
803   test = name.getString();
804   // test for "_changed" at the end of the fieldname
805   if (test.getLength() > 8) { // 8 == strlen("_changed")
806     test = test.getSubString(0, test.getLength() - 9);
807     if (node->getOutput(test.getString())) return SbName(test.getString());
808   }
809   return name;
810 }
811 
812 
813 //
814 // helper function to find field. First test the actual fieldname,
815 // then set set_<fieldname>, then <fieldname>_changed.
816 //
817 static SoField *
soproto_find_field(SoNode * node,const SbName & fieldname)818 soproto_find_field(SoNode * node, const SbName & fieldname)
819 {
820   SoField * field = node->getField(fieldname);
821 
822   if (!field) {
823     if (strncmp(fieldname.getString(), "set_", 4) == 0) {
824       SbName newname = fieldname.getString() + 4;
825       field = node->getField(newname);
826     }
827     else {
828       SbString str = fieldname.getString();
829       int len = str.getLength();
830       const char CHANGED[] = "_changed";
831       const int changedsize = sizeof(CHANGED) - 1;
832 
833       if (len > changedsize && strcmp(str.getString()+len-changedsize,
834                                       CHANGED) == 0) {
835         SbString substr = str.getSubString(0, len-(changedsize+1));
836         SbName newname = substr.getString();
837         field = node->getField(newname);
838       }
839     }
840   }
841   return field;
842 }
843 
844 //
845 // Create a root node for a PROTO instance
846 //
847 SoNode *
createInstanceRoot(SoProtoInstance * inst) const848 SoProto::createInstanceRoot(SoProtoInstance * inst) const
849 {
850   if (PRIVATE(this)->extprotonode) {
851     return PRIVATE(this)->extprotonode->createInstanceRoot(inst);
852   }
853 
854   SoNode * root;
855   if (PRIVATE(this)->defroot->getNumChildren() == 1)
856     root = PRIVATE(this)->defroot->getChild(0);
857   else root = PRIVATE(this)->defroot;
858 
859   SoNode * cpy;
860   cpy = root->copy(FALSE);
861   cpy->ref();
862   this->connectISRefs(inst, root, cpy);
863 
864   int n = PRIVATE(this)->routelist.getLength() / 4;
865   SoSearchAction sa;
866 
867   for (int i = 0; i < n; i++) {
868     SbName fromnodename = PRIVATE(this)->routelist[i*4];
869     SbName fromfieldname = PRIVATE(this)->routelist[i*4+1];
870     SbName tonodename = PRIVATE(this)->routelist[i*4+2];
871     SbName tofieldname = PRIVATE(this)->routelist[i*4+3];
872 
873     SoNode * fromnode = soproto_find_node(cpy, fromnodename, sa);
874     SoNode * tonode = soproto_find_node(cpy, tonodename, sa);
875 
876     if (fromnode && tonode) {
877       SoField * from = soproto_find_field(fromnode, fromfieldname);
878       SoField * to = soproto_find_field(tonode, tofieldname);
879       SoEngineOutput * output = NULL;
880       if (from == NULL && fromnode->isOfType(SoNodeEngine::getClassTypeId())) {
881         output = ((SoNodeEngine*) fromnode)->getOutput(fromfieldname);
882       }
883 
884       if (to && (from || output)) {
885         SbBool notnotify = FALSE;
886         SbBool append = FALSE;
887         if (output || from->getFieldType() == SoField::EVENTOUT_FIELD) {
888           notnotify = TRUE;
889         }
890         if (to->getFieldType() == SoField::EVENTIN_FIELD) append = TRUE;
891 
892         // Check that there exists a field converter, if one is needed.
893         SoType totype = to->getTypeId();
894         SoType fromtype = from ? from->getTypeId() : output->getConnectionType();
895         if (totype != fromtype) {
896           SoType convtype = SoDB::getConverter(fromtype, totype);
897           if (convtype == SoType::badType()) {
898             continue;
899           }
900         }
901 
902         SbBool ok;
903         if (from) ok = to->connectFrom(from, notnotify, append);
904         else ok = to->connectFrom(output, notnotify, append);
905         // Both known possible failure points are caught above.
906         assert(ok && "unexpected connection error");
907 
908       }
909     }
910   }
911 
912   cpy->unrefNoDelete();
913   return cpy;
914 }
915 
916 static SoNode *
locate_node_copy(SoNode * searchfor,SoNode * org,SoNode * cpy)917 locate_node_copy(SoNode * searchfor, SoNode * org, SoNode * cpy)
918 {
919   if (org == NULL) return NULL;
920   if (cpy == NULL) return NULL;
921 
922   if (org->getTypeId() != cpy->getTypeId()) return NULL;
923   if (org == searchfor) return cpy;
924 
925   const SoFieldData * fd = org->getFieldData();
926   const SoFieldData * fd2 = cpy->getFieldData();
927 
928   int n = fd ? fd->getNumFields() : 0;
929   int n2 = fd2 ? fd2->getNumFields() : 0;
930 
931   if (n != n2) {
932     // should never happen (in theory)
933     SoDebugError::postWarning("SoProto::locate_node_copy",
934                               "SoFieldData mismatch in PROTO scene.");
935     return NULL;
936   }
937 
938   int i;
939 
940   SoType sosftype = SoSFNode::getClassTypeId();
941   for (i = 0; i < n; i++) {
942     SoField * orgf = fd->getField(org, i);
943     if (orgf->getTypeId() == sosftype) {
944       SoNode * orgnode = ((SoSFNode*) orgf)->getValue();
945       if (orgnode != NULL) {
946         SoField * cpyf = fd2->getField(cpy, i);
947         if (cpyf->getTypeId() == sosftype) {
948           SoNode * found = locate_node_copy(searchfor, orgnode, ((SoSFNode*) cpyf)->getValue());
949           if (found) return found;
950         }
951         else {
952           SoDebugError::postWarning("SoProto::locate_node_copy",
953                                     "SoField mismatch in PROTO scene.");
954           return NULL;
955         }
956       }
957     }
958   }
959 
960   SoChildList * cl = org->getChildren();
961   if (cl) {
962     SoChildList * cl2 = cpy->getChildren();
963     n = SbMin(cl->getLength(), cl2->getLength());
964     for (i = 0; i < n; i++) {
965       SoNode * found = locate_node_copy(searchfor, (*cl)[i], (*cl2)[i]);
966       if (found) return found;
967     }
968   }
969   return NULL;
970 }
971 
972 
973 //
974 // Connects all IS references for the a new instance
975 //
976 void
connectISRefs(SoProtoInstance * inst,SoNode * src,SoNode * dst) const977 SoProto::connectISRefs(SoProtoInstance * inst, SoNode * src, SoNode * dst) const
978 {
979   if (PRIVATE(this)->externurl) {
980     SoDebugError::postWarning("SoProto::connectISRefs",
981                               "EXTERNPROTO URL fetching is not yet supported.");
982     return;
983   }
984 
985   const int n = PRIVATE(this)->isfieldlist.getLength();
986 
987   for (int i = 0; i < n; i++) {
988     SoNode * node = PRIVATE(this)->isnodelist[i];
989 
990     SbName fieldname = PRIVATE(this)->isfieldlist[i];
991     fieldname = soproto_find_fieldname(node, fieldname);
992     SoField * dstfield = node->getField(fieldname);
993     SoEngineOutput * eventout = NULL;
994 
995     if (!dstfield) {
996       if (node->isOfType(SoNodeEngine::getClassTypeId())) {
997         fieldname = soproto_find_outputname((SoNodeEngine*)node, fieldname);
998         eventout = ((SoNodeEngine*)node)->getOutput(fieldname);
999       }
1000       if (!eventout) {
1001 #if COIN_DEBUG
1002         SoDebugError::postWarning("SoProto::connectISRefs",
1003                                   "Destionation field '%s' is not found in node type '%s'. "
1004                                   "Unable to resolve IS reference.",
1005                                   fieldname.getString(), node->getTypeId().getName().getString());
1006 #endif // COIN_DEBUG
1007         continue; // skip to next field
1008       }
1009     }
1010 
1011     SbBool isprotoinstance = FALSE;
1012     if (node->isOfType(SoProtoInstance::getClassTypeId())) {
1013       node = ((SoProtoInstance*) node)->getRootNode();
1014       isprotoinstance = TRUE;
1015     }
1016     SbName iname = PRIVATE(this)->isnamelist[i];
1017 
1018     node = locate_node_copy(node, src, dst);
1019 
1020     if (!node) {
1021       SoDebugError::postWarning("SoProto::connectISRefs",
1022                                 "Unable to find '%s' from '%s' in '%s' PROTO",
1023                                 fieldname.getString(), iname.getString(), PRIVATE(this)->name.getString());
1024       continue;
1025     }
1026 
1027     if (dstfield) {
1028       if (isprotoinstance) {
1029         node = SoProtoInstance::findProtoInstance(node);
1030         assert(node);
1031       }
1032       dstfield = node->getField(fieldname);
1033     }
1034     else {
1035       assert(node->isOfType(SoNodeEngine::getClassTypeId()));
1036       eventout = ((SoNodeEngine*)node)->getOutput(fieldname);
1037     }
1038     assert(dstfield || eventout);
1039     SoField * srcfield = inst->getField(iname);
1040     if (srcfield) {
1041       // if destination field is an eventOut field, or an EngineOutput,
1042       // reverse the connection, since we then just need to route the
1043       // events to the srcfield.
1044       if (eventout) {
1045         srcfield->connectFrom(eventout);
1046       }
1047       else if (dstfield->getFieldType() == SoField::EVENTOUT_FIELD) {
1048         srcfield->connectFrom(dstfield);
1049       }
1050       else {
1051         // We make bidirectional connections for regular fields.  That way
1052         // you can modify the fields in the scene graph and have their PROTO
1053         // instances written to file with the updated values.  The alternative
1054         // is to have to locate the PROTO instance object yourself, and
1055         // modify the fields on it directly - 20040115 larsa
1056         srcfield->setDefault(FALSE);
1057         dstfield->connectFrom(srcfield);
1058 #if 0 // start of problematic code
1059         // this piece of code causes problems when writing PROTO
1060         // instances, since the PROTO instance is counted once for
1061         // each IS connection. The code is enabled for now, but I'll
1062         // investigate more if this bidirectional connection is really
1063         // necessary and if we should handle this case when counting
1064         // write references. pederb, 2005-11-15
1065 
1066         // update 2005-12-16, pederb:
1067         // This bidirectional thingie also causes bugs when importing
1068         // gator_1.wrl (the connections are not set up correctly, or
1069         // are messed up). It seems like we probably should disable
1070         // this code since it causes a lot of problems.
1071 
1072         // propagate value immediately, before setting up reverse connection
1073         dstfield->evaluate();
1074         srcfield->connectFrom(dstfield, FALSE, TRUE);
1075         // propagate value immediately, so we can tag field as default
1076         srcfield->evaluate();
1077         if ( srcisdefault ) srcfield->setDefault(TRUE);
1078 #endif // end of problemetic code
1079       }
1080     }
1081     else {
1082       assert(dstfield);
1083       SoEngineOutput * output = NULL;
1084       if (inst->isOfType(SoNodeEngine::getClassTypeId())) {
1085         output = ((SoNodeEngine*) inst)->getOutput(iname);
1086       }
1087       if (output) {
1088         dstfield->connectFrom(output);
1089       }
1090 #if COIN_DEBUG
1091       else {
1092         SoDebugError::postWarning("SoProto::connectISRefs",
1093                                   "Source field or engine output '%s' is not found in node type '%s'. "
1094                                   "Unable to resolve IS reference.",
1095                                   iname.getString(), node->getTypeId().getName().getString());
1096       }
1097 #endif // COIN_DEBUG
1098     }
1099   }
1100 }
1101 
1102 SbBool
setupExtern(SoInput * COIN_UNUSED_ARG (in),SoProto * externproto)1103 SoProto::setupExtern(SoInput * COIN_UNUSED_ARG(in), SoProto * externproto)
1104 {
1105   assert(externproto);
1106   PRIVATE(this)->extprotonode = externproto;
1107   PRIVATE(this)->extprotonode->ref();
1108   return TRUE;
1109 }
1110 
1111 #undef PRIVATE
1112