]> git.mxchange.org Git - flightgear.git/blob - src/Main/fg_props.cxx
Support a --no-default-config option.
[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 static const char *
380 getLongitudeString ()
381 {
382   static SGConstPropertyNode_ptr n = fgGetNode("/position/longitude-deg", true);
383   static SGConstPropertyNode_ptr f = fgGetNode("/sim/lon-lat-format", true);
384   static char buf[32];
385   double d = n->getDoubleValue();
386   int format = f->getIntValue();
387   char c = d < 0.0 ? 'W' : 'E';
388
389   if (format == 0) {
390     snprintf(buf, 32, "%3.6f%c", d, c);
391
392   } else if (format == 1) {
393     // dd mm.mmm' (DMM-Format) -- uses a round-off factor tailored to the
394     // required precision of the minutes field (three decimal places),
395     // preventing minute values of 60.
396     double deg = fabs(d) + 5.0E-4 / 60.0;
397     double min = fabs(deg - int(deg)) * 60.0 - 4.999E-4;
398     snprintf(buf, 32, "%d*%06.3f%c", int(d < 0.0 ? -deg : deg), min, c);
399
400   } else {
401     // mm'ss.s'' (DMS-Format) -- uses a round-off factor tailored to the
402     // required precision of the seconds field (one decimal place),
403     // preventing second values of 60.
404     double deg = fabs(d) + 0.05 / 3600.0;
405     double min = (deg - int(deg)) * 60.0;
406     double sec = (min - int(min)) * 60.0 - 0.049;
407     snprintf(buf, 32, "%d*%02d %04.1f%c", int(d < 0.0 ? -deg : deg),
408         int(min), fabs(sec), c);
409   }
410   buf[31] = '\0';
411   return buf;
412 }
413
414 static const char *
415 getLatitudeString ()
416 {
417   static SGConstPropertyNode_ptr n = fgGetNode("/position/latitude-deg", true);
418   static SGConstPropertyNode_ptr f = fgGetNode("/sim/lon-lat-format", true);
419   static char buf[32];
420   double d = n->getDoubleValue();
421   int format = f->getIntValue();
422   char c = d < 0.0 ? 'S' : 'N';
423
424   if (format == 0) {
425     snprintf(buf, 32, "%3.6f%c", d, c);
426
427   } else if (format == 1) {
428     double deg = fabs(d) + 5.0E-4 / 60.0;
429     double min = fabs(deg - int(deg)) * 60.0 - 4.999E-4;
430     snprintf(buf, 32, "%d*%06.3f%c", int(d < 0.0 ? -deg : deg), min, c);
431
432   } else {
433     double deg = fabs(d) + 0.05 / 3600.0;
434     double min = (deg - int(deg)) * 60.0;
435     double sec = (min - int(min)) * 60.0 - 0.049;
436     snprintf(buf, 32, "%d*%02d %04.1f%c", int(d < 0.0 ? -deg : deg),
437         int(min), fabs(sec), c);
438   }
439   buf[31] = '\0';
440   return buf;
441 }
442
443
444
445 \f
446 ////////////////////////////////////////////////////////////////////////
447 // Tie the properties.
448 ////////////////////////////////////////////////////////////////////////
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   _tiedProperties.setRoot(globals->get_props());
467
468   // Simulation
469   _tiedProperties.Tie("/sim/logging/priority", getLoggingPriority, setLoggingPriority);
470   _tiedProperties.Tie("/sim/logging/classes", getLoggingClasses, setLoggingClasses);
471   _tiedProperties.Tie("/sim/freeze/master", getFreeze, setFreeze);
472
473   _tiedProperties.Tie("/sim/time/elapsed-sec", getElapsedTime_sec);
474   _tiedProperties.Tie("/sim/time/gmt", getDateString, setDateString);
475   fgSetArchivable("/sim/time/gmt");
476   _tiedProperties.Tie("/sim/time/gmt-string", getGMTString);
477
478   // Position
479   _tiedProperties.Tie("/position/latitude-string", getLatitudeString);
480   _tiedProperties.Tie("/position/longitude-string", getLongitudeString);
481
482   // Orientation
483   _tiedProperties.Tie("/orientation/heading-magnetic-deg", getHeadingMag);
484   _tiedProperties.Tie("/orientation/track-magnetic-deg", getTrackMag);
485
486   // Misc. Temporary junk.
487   _tiedProperties.Tie("/sim/temp/winding-ccw", getWindingCCW, setWindingCCW, false);
488 }
489
490 void
491 FGProperties::unbind ()
492 {
493     _tiedProperties.Untie();
494 }
495
496 void
497 FGProperties::update (double dt)
498 {
499     static SGPropertyNode_ptr offset = fgGetNode("/sim/time/local-offset", true);
500     offset->setIntValue(globals->get_time_params()->get_local_offset());
501
502     // utc date/time
503     static SGPropertyNode_ptr uyear = fgGetNode("/sim/time/utc/year", true);
504     static SGPropertyNode_ptr umonth = fgGetNode("/sim/time/utc/month", true);
505     static SGPropertyNode_ptr uday = fgGetNode("/sim/time/utc/day", true);
506     static SGPropertyNode_ptr uhour = fgGetNode("/sim/time/utc/hour", true);
507     static SGPropertyNode_ptr umin = fgGetNode("/sim/time/utc/minute", true);
508     static SGPropertyNode_ptr usec = fgGetNode("/sim/time/utc/second", true);
509     static SGPropertyNode_ptr uwday = fgGetNode("/sim/time/utc/weekday", true);
510     static SGPropertyNode_ptr udsec = fgGetNode("/sim/time/utc/day-seconds", true);
511
512     struct tm *u = globals->get_time_params()->getGmt();
513     uyear->setIntValue(u->tm_year + 1900);
514     umonth->setIntValue(u->tm_mon + 1);
515     uday->setIntValue(u->tm_mday);
516     uhour->setIntValue(u->tm_hour);
517     umin->setIntValue(u->tm_min);
518     usec->setIntValue(u->tm_sec);
519     uwday->setIntValue(u->tm_wday);
520
521     udsec->setIntValue(u->tm_hour * 3600 + u->tm_min * 60 + u->tm_sec);
522
523
524     // real local date/time
525     static SGPropertyNode_ptr ryear = fgGetNode("/sim/time/real/year", true);
526     static SGPropertyNode_ptr rmonth = fgGetNode("/sim/time/real/month", true);
527     static SGPropertyNode_ptr rday = fgGetNode("/sim/time/real/day", true);
528     static SGPropertyNode_ptr rhour = fgGetNode("/sim/time/real/hour", true);
529     static SGPropertyNode_ptr rmin = fgGetNode("/sim/time/real/minute", true);
530     static SGPropertyNode_ptr rsec = fgGetNode("/sim/time/real/second", true);
531     static SGPropertyNode_ptr rwday = fgGetNode("/sim/time/real/weekday", true);
532
533     time_t real = time(0);
534     struct tm *r = localtime(&real);
535     ryear->setIntValue(r->tm_year + 1900);
536     rmonth->setIntValue(r->tm_mon + 1);
537     rday->setIntValue(r->tm_mday);
538     rhour->setIntValue(r->tm_hour);
539     rmin->setIntValue(r->tm_min);
540     rsec->setIntValue(r->tm_sec);
541     rwday->setIntValue(r->tm_wday);
542 }
543
544
545 \f
546 ////////////////////////////////////////////////////////////////////////
547 // Save and restore.
548 ////////////////////////////////////////////////////////////////////////
549
550
551 /**
552  * Save the current state of the simulator to a stream.
553  */
554 bool
555 fgSaveFlight (std::ostream &output, bool write_all)
556 {
557
558   fgSetBool("/sim/presets/onground", false);
559   fgSetArchivable("/sim/presets/onground");
560   fgSetBool("/sim/presets/trim", false);
561   fgSetArchivable("/sim/presets/trim");
562   fgSetString("/sim/presets/speed-set", "UVW");
563   fgSetArchivable("/sim/presets/speed-set");
564
565   try {
566     writeProperties(output, globals->get_props(), write_all);
567   } catch (const sg_exception &e) {
568     guiErrorMessage("Error saving flight: ", e);
569     return false;
570   }
571   return true;
572 }
573
574
575 /**
576  * Restore the current state of the simulator from a stream.
577  */
578 bool
579 fgLoadFlight (std::istream &input)
580 {
581   SGPropertyNode props;
582   try {
583     readProperties(input, &props);
584   } catch (const sg_exception &e) {
585     guiErrorMessage("Error reading saved flight: ", e);
586     return false;
587   }
588
589   fgSetBool("/sim/presets/onground", false);
590   fgSetBool("/sim/presets/trim", false);
591   fgSetString("/sim/presets/speed-set", "UVW");
592
593   copyProperties(&props, globals->get_props());
594   // When loading a flight, make it the
595   // new initial state.
596   globals->saveInitialState();
597   return true;
598 }
599
600
601 bool
602 fgLoadProps (const char * path, SGPropertyNode * props, bool in_fg_root, int default_mode)
603 {
604     string fullpath;
605     if (in_fg_root) {
606         SGPath loadpath(globals->get_fg_root());
607         loadpath.append(path);
608         fullpath = loadpath.str();
609     } else {
610         fullpath = path;
611     }
612
613     try {
614         readProperties(fullpath, props, default_mode);
615     } catch (const sg_exception &e) {
616         guiErrorMessage("Error reading properties: ", e);
617         return false;
618     }
619     return true;
620 }
621
622
623 \f
624 ////////////////////////////////////////////////////////////////////////
625 // Property convenience functions.
626 ////////////////////////////////////////////////////////////////////////
627
628 SGPropertyNode *
629 fgGetNode (const char * path, bool create)
630 {
631   return globals->get_props()->getNode(path, create);
632 }
633
634 SGPropertyNode * 
635 fgGetNode (const char * path, int index, bool create)
636 {
637   return globals->get_props()->getNode(path, index, create);
638 }
639
640 bool
641 fgHasNode (const char * path)
642 {
643   return (fgGetNode(path, false) != 0);
644 }
645
646 void
647 fgAddChangeListener (SGPropertyChangeListener * listener, const char * path)
648 {
649   fgGetNode(path, true)->addChangeListener(listener);
650 }
651
652 void
653 fgAddChangeListener (SGPropertyChangeListener * listener,
654                      const char * path, int index)
655 {
656   fgGetNode(path, index, true)->addChangeListener(listener);
657 }
658
659 bool
660 fgGetBool (const char * name, bool defaultValue)
661 {
662   return globals->get_props()->getBoolValue(name, defaultValue);
663 }
664
665 int
666 fgGetInt (const char * name, int defaultValue)
667 {
668   return globals->get_props()->getIntValue(name, defaultValue);
669 }
670
671 long
672 fgGetLong (const char * name, long defaultValue)
673 {
674   return globals->get_props()->getLongValue(name, defaultValue);
675 }
676
677 float
678 fgGetFloat (const char * name, float defaultValue)
679 {
680   return globals->get_props()->getFloatValue(name, defaultValue);
681 }
682
683 double
684 fgGetDouble (const char * name, double defaultValue)
685 {
686   return globals->get_props()->getDoubleValue(name, defaultValue);
687 }
688
689 const char *
690 fgGetString (const char * name, const char * defaultValue)
691 {
692   return globals->get_props()->getStringValue(name, defaultValue);
693 }
694
695 bool
696 fgSetBool (const char * name, bool val)
697 {
698   return globals->get_props()->setBoolValue(name, val);
699 }
700
701 bool
702 fgSetInt (const char * name, int val)
703 {
704   return globals->get_props()->setIntValue(name, val);
705 }
706
707 bool
708 fgSetLong (const char * name, long val)
709 {
710   return globals->get_props()->setLongValue(name, val);
711 }
712
713 bool
714 fgSetFloat (const char * name, float val)
715 {
716   return globals->get_props()->setFloatValue(name, val);
717 }
718
719 bool
720 fgSetDouble (const char * name, double val)
721 {
722   return globals->get_props()->setDoubleValue(name, val);
723 }
724
725 bool
726 fgSetString (const char * name, const char * val)
727 {
728   return globals->get_props()->setStringValue(name, val);
729 }
730
731 void
732 fgSetArchivable (const char * name, bool state)
733 {
734   SGPropertyNode * node = globals->get_props()->getNode(name);
735   if (node == 0)
736     SG_LOG(SG_GENERAL, SG_DEBUG,
737            "Attempt to set archive flag for non-existant property "
738            << name);
739   else
740     node->setAttribute(SGPropertyNode::ARCHIVE, state);
741 }
742
743 void
744 fgSetReadable (const char * name, bool state)
745 {
746   SGPropertyNode * node = globals->get_props()->getNode(name);
747   if (node == 0)
748     SG_LOG(SG_GENERAL, SG_DEBUG,
749            "Attempt to set read flag for non-existant property "
750            << name);
751   else
752     node->setAttribute(SGPropertyNode::READ, state);
753 }
754
755 void
756 fgSetWritable (const char * name, bool state)
757 {
758   SGPropertyNode * node = globals->get_props()->getNode(name);
759   if (node == 0)
760     SG_LOG(SG_GENERAL, SG_DEBUG,
761            "Attempt to set write flag for non-existant property "
762            << name);
763   else
764     node->setAttribute(SGPropertyNode::WRITE, state);
765 }
766
767 void
768 fgUntie(const char * name)
769 {
770   SGPropertyNode* node = globals->get_props()->getNode(name);
771   if (!node) {
772     SG_LOG(SG_GENERAL, SG_WARN, "fgUntie: unknown property " << name);
773     return;
774   }
775   
776   if (!node->isTied()) {
777     return;
778   }
779   
780   if (!node->untie()) {
781     SG_LOG(SG_GENERAL, SG_WARN, "Failed to untie property " << name);
782   }
783 }
784
785
786 // end of fg_props.cxx