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