]> git.mxchange.org Git - flightgear.git/blob - src/Main/fg_props.cxx
d8bda0f0829e9b67796f5f3a5f4b2a78a356c954
[flightgear.git] / src / Main / fg_props.cxx
1 // fg_props.cxx -- support for FlightGear properties.
2 //
3 // Written by David Megginson, started 2000.
4 //
5 // Copyright (C) 2000, 2001 David Megginson - david@megginson.com
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21 // $Id$
22
23 #ifdef HAVE_CONFIG_H
24 #  include "config.h"
25 #endif
26
27 #include <simgear/compiler.h>
28 #include <simgear/structure/exception.hxx>
29 #include <simgear/props/props_io.hxx>
30
31 #include <simgear/timing/sg_time.hxx>
32 #include <simgear/misc/sg_path.hxx>
33 #include <simgear/scene/model/particles.hxx>
34 #include <simgear/sound/soundmgr_openal.hxx>
35
36 #include <GUI/gui.h>
37
38 #include "globals.hxx"
39 #include "fg_props.hxx"
40
41
42 static bool winding_ccw = true; // FIXME: temporary
43
44 static bool frozen = false;     // FIXME: temporary
45
46 using std::string;
47 \f
48 ////////////////////////////////////////////////////////////////////////
49 // Default property bindings (not yet handled by any module).
50 ////////////////////////////////////////////////////////////////////////
51
52 struct LogClassMapping {
53   sgDebugClass c;
54   string name;
55   LogClassMapping(sgDebugClass cc, string nname) { c = cc; name = nname; }
56 };
57
58 LogClassMapping log_class_mappings [] = {
59   LogClassMapping(SG_NONE, "none"),
60   LogClassMapping(SG_TERRAIN, "terrain"),
61   LogClassMapping(SG_ASTRO, "astro"),
62   LogClassMapping(SG_FLIGHT, "flight"),
63   LogClassMapping(SG_INPUT, "input"),
64   LogClassMapping(SG_GL, "gl"),
65   LogClassMapping(SG_VIEW, "view"),
66   LogClassMapping(SG_COCKPIT, "cockpit"),
67   LogClassMapping(SG_GENERAL, "general"),
68   LogClassMapping(SG_MATH, "math"),
69   LogClassMapping(SG_EVENT, "event"),
70   LogClassMapping(SG_AIRCRAFT, "aircraft"),
71   LogClassMapping(SG_AUTOPILOT, "autopilot"),
72   LogClassMapping(SG_IO, "io"),
73   LogClassMapping(SG_CLIPPER, "clipper"),
74   LogClassMapping(SG_NETWORK, "network"),
75   LogClassMapping(SG_INSTR, "instrumentation"),
76   LogClassMapping(SG_ATC, "atc"),
77   LogClassMapping(SG_NASAL, "nasal"),
78   LogClassMapping(SG_SYSTEMS, "systems"),
79   LogClassMapping(SG_AI, "ai"),
80   LogClassMapping(SG_ENVIRONMENT, "environment"),
81   LogClassMapping(SG_SOUND, "sound"),
82   LogClassMapping(SG_UNDEFD, "")
83 };
84
85
86 /**
87  * Get the logging classes.
88  */
89 // XXX Making the result buffer be global is a band-aid that hopefully
90 // delays its destruction 'til after its last use.
91 namespace
92 {
93 string loggingResult;
94 }
95
96 static const char *
97 getLoggingClasses ()
98 {
99   sgDebugClass classes = logbuf::get_log_classes();
100   loggingResult.clear();
101   for (int i = 0; log_class_mappings[i].c != SG_UNDEFD; i++) {
102     if ((classes&log_class_mappings[i].c) > 0) {
103       if (!loggingResult.empty())
104         loggingResult += '|';
105       loggingResult += log_class_mappings[i].name;
106     }
107   }
108   return loggingResult.c_str();
109 }
110
111
112 static void
113 addLoggingClass (const string &name)
114 {
115   sgDebugClass classes = logbuf::get_log_classes();
116   for (int i = 0; log_class_mappings[i].c != SG_UNDEFD; i++) {
117     if (name == log_class_mappings[i].name) {
118       logbuf::set_log_classes(sgDebugClass(classes|log_class_mappings[i].c));
119       return;
120     }
121   }
122   SG_LOG(SG_GENERAL, SG_WARN, "Unknown logging class: " << name);
123 }
124
125
126 /**
127  * Set the logging classes.
128  */
129 void
130 setLoggingClasses (const char * c)
131 {
132   string classes = c;
133   logbuf::set_log_classes(SG_NONE);
134
135   if (classes == "none") {
136     SG_LOG(SG_GENERAL, SG_INFO, "Disabled all logging classes");
137     return;
138   }
139
140   if (classes.empty() || classes == "all") { // default
141     logbuf::set_log_classes(SG_ALL);
142     SG_LOG(SG_GENERAL, SG_INFO, "Enabled all logging classes: "
143            << getLoggingClasses());
144     return;
145   }
146
147   string rest = classes;
148   string name = "";
149   string::size_type sep = rest.find('|');
150   if (sep == string::npos)
151     sep = rest.find(',');
152   while (sep != string::npos) {
153     name = rest.substr(0, sep);
154     rest = rest.substr(sep+1);
155     addLoggingClass(name);
156     sep = rest.find('|');
157     if (sep == string::npos)
158       sep = rest.find(',');
159   }
160   addLoggingClass(rest);
161   SG_LOG(SG_GENERAL, SG_INFO, "Set logging classes to "
162          << getLoggingClasses());
163 }
164
165
166 /**
167  * Get the logging priority.
168  */
169 static const char *
170 getLoggingPriority ()
171 {
172   switch (logbuf::get_log_priority()) {
173   case SG_BULK:
174     return "bulk";
175   case SG_DEBUG:
176     return "debug";
177   case SG_INFO:
178     return "info";
179   case SG_WARN:
180     return "warn";
181   case SG_ALERT:
182     return "alert";
183   default:
184     SG_LOG(SG_GENERAL, SG_WARN, "Internal: Unknown logging priority number: "
185            << logbuf::get_log_priority());
186     return "unknown";
187   }
188 }
189
190
191 /**
192  * Set the logging priority.
193  */
194 void
195 setLoggingPriority (const char * p)
196 {
197   if (p == 0)
198       return;
199   string priority = p;
200   if (priority == "bulk") {
201     logbuf::set_log_priority(SG_BULK);
202   } else if (priority == "debug") {
203     logbuf::set_log_priority(SG_DEBUG);
204   } else if (priority.empty() || priority == "info") { // default
205     logbuf::set_log_priority(SG_INFO);
206   } else if (priority == "warn") {
207     logbuf::set_log_priority(SG_WARN);
208   } else if (priority == "alert") {
209     logbuf::set_log_priority(SG_ALERT);
210   } else {
211     SG_LOG(SG_GENERAL, SG_WARN, "Unknown logging priority " << priority);
212   }
213   SG_LOG(SG_GENERAL, SG_DEBUG, "Logging priority is " << getLoggingPriority());
214 }
215
216
217 /**
218  * Return the current frozen state.
219  */
220 static bool
221 getFreeze ()
222 {
223   return frozen;
224 }
225
226
227 /**
228  * Set the current frozen state.
229  */
230 static void
231 setFreeze (bool f)
232 {
233     frozen = f;
234
235     // Stop sound on a pause
236     SGSoundMgr *smgr = globals->get_soundmgr();
237     if ( smgr != NULL ) {
238         if ( f ) {
239             smgr->suspend();
240         } else if (fgGetBool("/sim/sound/working")) {
241             smgr->resume();
242         }
243     }
244
245     // Pause the particle system
246     simgear::Particles::setFrozen(f);
247 }
248
249
250 /**
251  * Return the number of milliseconds elapsed since simulation started.
252  */
253 static double
254 getElapsedTime_sec ()
255 {
256   return globals->get_sim_time_sec();
257 }
258
259
260 /**
261  * Return the current Zulu time.
262  */
263 static const char *
264 getDateString ()
265 {
266   static char buf[64];          // FIXME
267   
268   SGTime * st = globals->get_time_params();
269   if (!st) {
270     buf[0] = 0;
271     return buf;
272   }
273   
274   struct tm * t = st->getGmt();
275   sprintf(buf, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2d",
276           t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
277           t->tm_hour, t->tm_min, t->tm_sec);
278   return buf;
279 }
280
281
282 /**
283  * Set the current Zulu time.
284  */
285 static void
286 setDateString (const char * date_string)
287 {
288   SGTime * st = globals->get_time_params();
289   struct tm * current_time = st->getGmt();
290   struct tm new_time;
291
292                                 // Scan for basic ISO format
293                                 // YYYY-MM-DDTHH:MM:SS
294   int ret = sscanf(date_string, "%d-%d-%dT%d:%d:%d",
295                    &(new_time.tm_year), &(new_time.tm_mon),
296                    &(new_time.tm_mday), &(new_time.tm_hour),
297                    &(new_time.tm_min), &(new_time.tm_sec));
298
299                                 // Be pretty picky about this, so
300                                 // that strange things don't happen
301                                 // if the save file has been edited
302                                 // by hand.
303   if (ret != 6) {
304     SG_LOG(SG_INPUT, SG_WARN, "Date/time string " << date_string
305            << " not in YYYY-MM-DDTHH:MM:SS format; skipped");
306     return;
307   }
308
309                                 // OK, it looks like we got six
310                                 // values, one way or another.
311   new_time.tm_year -= 1900;
312   new_time.tm_mon -= 1;
313                                 // Now, tell flight gear to use
314                                 // the new time.  This was far
315                                 // too difficult, by the way.
316   long int warp =
317     mktime(&new_time) - mktime(current_time) + globals->get_warp();
318     
319   fgSetInt("/sim/time/warp", warp);
320 }
321
322 /**
323  * Return the GMT as a string.
324  */
325 static const char *
326 getGMTString ()
327 {
328   static char buf[16];
329   SGTime * st = globals->get_time_params();
330   if (!st) {
331     buf[0] = 0;
332     return buf;
333   }
334   
335   struct tm *t = st->getGmt();
336   snprintf(buf, 16, "%.2d:%.2d:%.2d",
337       t->tm_hour, t->tm_min, t->tm_sec);
338   return buf;
339 }
340
341 /**
342  * Return the current heading in degrees.
343  */
344 static double
345 getHeadingMag ()
346 {
347   double magheading = fgGetDouble("/orientation/heading-deg") -
348     fgGetDouble("/environment/magnetic-variation-deg");
349   return SGMiscd::normalizePeriodic(0, 360, magheading );
350 }
351
352 /**
353  * Return the current track in degrees.
354  */
355 static double
356 getTrackMag ()
357 {
358   double magtrack = fgGetDouble("/orientation/track-deg") -
359     fgGetDouble("/environment/magnetic-variation-deg");
360   return SGMiscd::normalizePeriodic(0, 360, magtrack );
361 }
362
363 static bool
364 getWindingCCW ()
365 {
366   return winding_ccw;
367 }
368
369 static void
370 setWindingCCW (bool state)
371 {
372   winding_ccw = state;
373   if ( winding_ccw )
374     glFrontFace ( GL_CCW );
375   else
376     glFrontFace ( GL_CW );
377 }
378
379 ////////////////////////////////////////////////////////////////////////
380 // Tie the properties.
381 ////////////////////////////////////////////////////////////////////////
382 SGConstPropertyNode_ptr FGProperties::_longDeg;
383 SGConstPropertyNode_ptr FGProperties::_latDeg;
384 SGConstPropertyNode_ptr FGProperties::_lonLatformat;
385
386 const char *
387 FGProperties::getLongitudeString ()
388 {
389   static char buf[32];
390   double d = _longDeg->getDoubleValue();
391   int format = _lonLatformat->getIntValue();
392   char c = d < 0.0 ? 'W' : 'E';
393
394   if (format == 0) {
395     snprintf(buf, 32, "%3.6f%c", d, c);
396
397   } else if (format == 1) {
398     // dd mm.mmm' (DMM-Format) -- uses a round-off factor tailored to the
399     // required precision of the minutes field (three decimal places),
400     // preventing minute values of 60.
401     double deg = fabs(d) + 5.0E-4 / 60.0;
402     double min = fabs(deg - int(deg)) * 60.0 - 4.999E-4;
403     snprintf(buf, 32, "%d*%06.3f%c", int(d < 0.0 ? -deg : deg), min, c);
404
405   } else {
406     // mm'ss.s'' (DMS-Format) -- uses a round-off factor tailored to the
407     // required precision of the seconds field (one decimal place),
408     // preventing second values of 60.
409     double deg = fabs(d) + 0.05 / 3600.0;
410     double min = (deg - int(deg)) * 60.0;
411     double sec = (min - int(min)) * 60.0 - 0.049;
412     snprintf(buf, 32, "%d*%02d %04.1f%c", int(d < 0.0 ? -deg : deg),
413         int(min), fabs(sec), c);
414   }
415   buf[31] = '\0';
416   return buf;
417 }
418
419 const char *
420 FGProperties::getLatitudeString ()
421 {
422   static char buf[32];
423   double d = _latDeg->getDoubleValue();
424   int format = _lonLatformat->getIntValue();
425   char c = d < 0.0 ? 'S' : 'N';
426
427   if (format == 0) {
428     snprintf(buf, 32, "%3.6f%c", d, c);
429
430   } else if (format == 1) {
431     double deg = fabs(d) + 5.0E-4 / 60.0;
432     double min = fabs(deg - int(deg)) * 60.0 - 4.999E-4;
433     snprintf(buf, 32, "%d*%06.3f%c", int(d < 0.0 ? -deg : deg), min, c);
434
435   } else {
436     double deg = fabs(d) + 0.05 / 3600.0;
437     double min = (deg - int(deg)) * 60.0;
438     double sec = (min - int(min)) * 60.0 - 0.049;
439     snprintf(buf, 32, "%d*%02d %04.1f%c", int(d < 0.0 ? -deg : deg),
440         int(min), fabs(sec), c);
441   }
442   buf[31] = '\0';
443   return buf;
444 }
445
446
447
448 \f
449
450 FGProperties::FGProperties ()
451 {
452 }
453
454 FGProperties::~FGProperties ()
455 {
456 }
457
458 void
459 FGProperties::init ()
460 {
461 }
462
463 void
464 FGProperties::bind ()
465 {
466   _longDeg      = fgGetNode("/position/longitude-deg", true);
467   _latDeg       = fgGetNode("/position/latitude-deg", true);
468   _lonLatformat = fgGetNode("/sim/lon-lat-format", true);
469
470   _offset = fgGetNode("/sim/time/local-offset", true);
471
472   // utc date/time
473   _uyear = fgGetNode("/sim/time/utc/year", true);
474   _umonth = fgGetNode("/sim/time/utc/month", true);
475   _uday = fgGetNode("/sim/time/utc/day", true);
476   _uhour = fgGetNode("/sim/time/utc/hour", true);
477   _umin = fgGetNode("/sim/time/utc/minute", true);
478   _usec = fgGetNode("/sim/time/utc/second", true);
479   _uwday = fgGetNode("/sim/time/utc/weekday", true);
480   _udsec = fgGetNode("/sim/time/utc/day-seconds", true);
481
482   // real local date/time
483   _ryear = fgGetNode("/sim/time/real/year", true);
484   _rmonth = fgGetNode("/sim/time/real/month", true);
485   _rday = fgGetNode("/sim/time/real/day", true);
486   _rhour = fgGetNode("/sim/time/real/hour", true);
487   _rmin = fgGetNode("/sim/time/real/minute", true);
488   _rsec = fgGetNode("/sim/time/real/second", true);
489   _rwday = fgGetNode("/sim/time/real/weekday", true);
490
491   _tiedProperties.setRoot(globals->get_props());
492
493   // Simulation
494   _tiedProperties.Tie("/sim/logging/priority", getLoggingPriority, setLoggingPriority);
495   _tiedProperties.Tie("/sim/logging/classes", getLoggingClasses, setLoggingClasses);
496   _tiedProperties.Tie("/sim/freeze/master", getFreeze, setFreeze);
497
498   _tiedProperties.Tie("/sim/time/elapsed-sec", getElapsedTime_sec);
499   _tiedProperties.Tie("/sim/time/gmt", getDateString, setDateString);
500   fgSetArchivable("/sim/time/gmt");
501   _tiedProperties.Tie("/sim/time/gmt-string", getGMTString);
502
503   // Position
504   _tiedProperties.Tie("/position/latitude-string", getLatitudeString);
505   _tiedProperties.Tie("/position/longitude-string", getLongitudeString);
506
507   // Orientation
508   _tiedProperties.Tie("/orientation/heading-magnetic-deg", getHeadingMag);
509   _tiedProperties.Tie("/orientation/track-magnetic-deg", getTrackMag);
510
511   // Misc. Temporary junk.
512   _tiedProperties.Tie("/sim/temp/winding-ccw", getWindingCCW, setWindingCCW, false);
513 }
514
515 void
516 FGProperties::unbind ()
517 {
518     _tiedProperties.Untie();
519
520     // drop static references to properties
521     _longDeg = 0;
522     _latDeg = 0;
523     _lonLatformat = 0;
524 }
525
526 void
527 FGProperties::update (double dt)
528 {
529     _offset->setIntValue(globals->get_time_params()->get_local_offset());
530
531     // utc date/time
532     struct tm *u = globals->get_time_params()->getGmt();
533     _uyear->setIntValue(u->tm_year + 1900);
534     _umonth->setIntValue(u->tm_mon + 1);
535     _uday->setIntValue(u->tm_mday);
536     _uhour->setIntValue(u->tm_hour);
537     _umin->setIntValue(u->tm_min);
538     _usec->setIntValue(u->tm_sec);
539     _uwday->setIntValue(u->tm_wday);
540     _udsec->setIntValue(u->tm_hour * 3600 + u->tm_min * 60 + u->tm_sec);
541
542     // real local date/time
543     time_t real = time(0);
544     struct tm *r = localtime(&real);
545     _ryear->setIntValue(r->tm_year + 1900);
546     _rmonth->setIntValue(r->tm_mon + 1);
547     _rday->setIntValue(r->tm_mday);
548     _rhour->setIntValue(r->tm_hour);
549     _rmin->setIntValue(r->tm_min);
550     _rsec->setIntValue(r->tm_sec);
551     _rwday->setIntValue(r->tm_wday);
552 }
553
554
555 \f
556 ////////////////////////////////////////////////////////////////////////
557 // Save and restore.
558 ////////////////////////////////////////////////////////////////////////
559
560
561 /**
562  * Save the current state of the simulator to a stream.
563  */
564 bool
565 fgSaveFlight (std::ostream &output, bool write_all)
566 {
567
568   fgSetBool("/sim/presets/onground", false);
569   fgSetArchivable("/sim/presets/onground");
570   fgSetBool("/sim/presets/trim", false);
571   fgSetArchivable("/sim/presets/trim");
572   fgSetString("/sim/presets/speed-set", "UVW");
573   fgSetArchivable("/sim/presets/speed-set");
574
575   try {
576     writeProperties(output, globals->get_props(), write_all);
577   } catch (const sg_exception &e) {
578     guiErrorMessage("Error saving flight: ", e);
579     return false;
580   }
581   return true;
582 }
583
584
585 /**
586  * Restore the current state of the simulator from a stream.
587  */
588 bool
589 fgLoadFlight (std::istream &input)
590 {
591   SGPropertyNode props;
592   try {
593     readProperties(input, &props);
594   } catch (const sg_exception &e) {
595     guiErrorMessage("Error reading saved flight: ", e);
596     return false;
597   }
598
599   fgSetBool("/sim/presets/onground", false);
600   fgSetBool("/sim/presets/trim", false);
601   fgSetString("/sim/presets/speed-set", "UVW");
602
603   copyProperties(&props, globals->get_props());
604   // When loading a flight, make it the
605   // new initial state.
606   globals->saveInitialState();
607   return true;
608 }
609
610
611 bool
612 fgLoadProps (const char * path, SGPropertyNode * props, bool in_fg_root, int default_mode)
613 {
614     string fullpath;
615     if (in_fg_root) {
616         SGPath loadpath(globals->get_fg_root());
617         loadpath.append(path);
618         fullpath = loadpath.str();
619     } else {
620         fullpath = path;
621     }
622
623     try {
624         readProperties(fullpath, props, default_mode);
625     } catch (const sg_exception &e) {
626         guiErrorMessage("Error reading properties: ", e);
627         return false;
628     }
629     return true;
630 }
631
632
633 \f
634 ////////////////////////////////////////////////////////////////////////
635 // Property convenience functions.
636 ////////////////////////////////////////////////////////////////////////
637
638 SGPropertyNode *
639 fgGetNode (const char * path, bool create)
640 {
641   return globals->get_props()->getNode(path, create);
642 }
643
644 SGPropertyNode * 
645 fgGetNode (const char * path, int index, bool create)
646 {
647   return globals->get_props()->getNode(path, index, create);
648 }
649
650 bool
651 fgHasNode (const char * path)
652 {
653   return (fgGetNode(path, false) != 0);
654 }
655
656 void
657 fgAddChangeListener (SGPropertyChangeListener * listener, const char * path)
658 {
659   fgGetNode(path, true)->addChangeListener(listener);
660 }
661
662 void
663 fgAddChangeListener (SGPropertyChangeListener * listener,
664                      const char * path, int index)
665 {
666   fgGetNode(path, index, true)->addChangeListener(listener);
667 }
668
669 bool
670 fgGetBool (const char * name, bool defaultValue)
671 {
672   return globals->get_props()->getBoolValue(name, defaultValue);
673 }
674
675 int
676 fgGetInt (const char * name, int defaultValue)
677 {
678   return globals->get_props()->getIntValue(name, defaultValue);
679 }
680
681 long
682 fgGetLong (const char * name, long defaultValue)
683 {
684   return globals->get_props()->getLongValue(name, defaultValue);
685 }
686
687 float
688 fgGetFloat (const char * name, float defaultValue)
689 {
690   return globals->get_props()->getFloatValue(name, defaultValue);
691 }
692
693 double
694 fgGetDouble (const char * name, double defaultValue)
695 {
696   return globals->get_props()->getDoubleValue(name, defaultValue);
697 }
698
699 const char *
700 fgGetString (const char * name, const char * defaultValue)
701 {
702   return globals->get_props()->getStringValue(name, defaultValue);
703 }
704
705 bool
706 fgSetBool (const char * name, bool val)
707 {
708   return globals->get_props()->setBoolValue(name, val);
709 }
710
711 bool
712 fgSetInt (const char * name, int val)
713 {
714   return globals->get_props()->setIntValue(name, val);
715 }
716
717 bool
718 fgSetLong (const char * name, long val)
719 {
720   return globals->get_props()->setLongValue(name, val);
721 }
722
723 bool
724 fgSetFloat (const char * name, float val)
725 {
726   return globals->get_props()->setFloatValue(name, val);
727 }
728
729 bool
730 fgSetDouble (const char * name, double val)
731 {
732   return globals->get_props()->setDoubleValue(name, val);
733 }
734
735 bool
736 fgSetString (const char * name, const char * val)
737 {
738   return globals->get_props()->setStringValue(name, val);
739 }
740
741 void
742 fgSetArchivable (const char * name, bool state)
743 {
744   SGPropertyNode * node = globals->get_props()->getNode(name);
745   if (node == 0)
746     SG_LOG(SG_GENERAL, SG_DEBUG,
747            "Attempt to set archive flag for non-existant property "
748            << name);
749   else
750     node->setAttribute(SGPropertyNode::ARCHIVE, state);
751 }
752
753 void
754 fgSetReadable (const char * name, bool state)
755 {
756   SGPropertyNode * node = globals->get_props()->getNode(name);
757   if (node == 0)
758     SG_LOG(SG_GENERAL, SG_DEBUG,
759            "Attempt to set read flag for non-existant property "
760            << name);
761   else
762     node->setAttribute(SGPropertyNode::READ, state);
763 }
764
765 void
766 fgSetWritable (const char * name, bool state)
767 {
768   SGPropertyNode * node = globals->get_props()->getNode(name);
769   if (node == 0)
770     SG_LOG(SG_GENERAL, SG_DEBUG,
771            "Attempt to set write flag for non-existant property "
772            << name);
773   else
774     node->setAttribute(SGPropertyNode::WRITE, state);
775 }
776
777 void
778 fgUntie(const char * name)
779 {
780   SGPropertyNode* node = globals->get_props()->getNode(name);
781   if (!node) {
782     SG_LOG(SG_GENERAL, SG_WARN, "fgUntie: unknown property " << name);
783     return;
784   }
785   
786   if (!node->isTied()) {
787     return;
788   }
789   
790   if (!node->untie()) {
791     SG_LOG(SG_GENERAL, SG_WARN, "Failed to untie property " << name);
792   }
793 }
794
795
796 // end of fg_props.cxx