]> git.mxchange.org Git - flightgear.git/blob - src/Main/fg_commands.cxx
Merge branch 'luff/kln89'
[flightgear.git] / src / Main / fg_commands.cxx
1 // fg_commands.cxx - internal FGFS commands.
2
3 #ifdef HAVE_CONFIG_H
4 #  include "config.h"
5 #endif
6
7 #include <string.h>             // strcmp()
8
9 #include <simgear/compiler.h>
10
11 #include <string>
12 #include <fstream>
13
14 #include <simgear/sg_inlines.h>
15 #include <simgear/debug/logstream.hxx>
16 #include <simgear/math/sg_random.h>
17 #include <simgear/scene/material/mat.hxx>
18 #include <simgear/scene/material/matlib.hxx>
19 #include <simgear/structure/exception.hxx>
20 #include <simgear/structure/commands.hxx>
21 #include <simgear/props/props.hxx>
22 #include <simgear/structure/event_mgr.hxx>
23
24 #include <Cockpit/panel.hxx>
25 #include <Cockpit/panel_io.hxx>
26 #include <Cockpit/hud.hxx>
27 #include <Environment/environment.hxx>
28 #include <FDM/flight.hxx>
29 #include <GUI/gui.h>
30 #include <GUI/new_gui.hxx>
31 #include <GUI/dialog.hxx>
32 #include <Aircraft/replay.hxx>
33 #include <Scenery/tilemgr.hxx>
34 #include <Scenery/scenery.hxx>
35 #include <Scripting/NasalSys.hxx>
36 #include <Sound/fg_fx.hxx>
37 #include <Time/sunsolver.hxx>
38 #include <Time/tmp.hxx>
39
40 #include "fg_init.hxx"
41 #include "fg_io.hxx"
42 #include "fg_os.hxx"
43 #include "fg_commands.hxx"
44 #include "fg_props.hxx"
45 #include "globals.hxx"
46 #include "logger.hxx"
47 #include "util.hxx"
48 #include "viewmgr.hxx"
49 #include "main.hxx"
50 #include <Main/viewer.hxx>
51
52 using std::string;
53 using std::ifstream;
54 using std::ofstream;
55
56
57 \f
58 ////////////////////////////////////////////////////////////////////////
59 // Static helper functions.
60 ////////////////////////////////////////////////////////////////////////
61
62
63 static inline SGPropertyNode *
64 get_prop (const SGPropertyNode * arg)
65 {
66     return fgGetNode(arg->getStringValue("property[0]", "/null"), true);
67 }
68
69 static inline SGPropertyNode *
70 get_prop2 (const SGPropertyNode * arg)
71 {
72     return fgGetNode(arg->getStringValue("property[1]", "/null"), true);
73 }
74
75
76 /**
77  * Get a double value and split it as required.
78  */
79 static void
80 split_value (double full_value, const char * mask,
81              double * unmodifiable, double * modifiable)
82 {
83     if (!strcmp("integer", mask)) {
84         *modifiable = (full_value < 0 ? ceil(full_value) : floor (full_value));
85         *unmodifiable = full_value - *modifiable;
86     } else if (!strcmp("decimal", mask)) {
87         *unmodifiable = (full_value < 0 ? ceil(full_value) : floor(full_value));
88         *modifiable = full_value - *unmodifiable;
89     } else {
90         if (strcmp("all", mask))
91             SG_LOG(SG_GENERAL, SG_ALERT, "Bad value " << mask << " for mask;"
92                    << " assuming 'all'");
93         *unmodifiable = 0;
94         *modifiable = full_value;
95     }
96 }
97
98
99 /**
100  * Clamp or wrap a value as specified.
101  */
102 static void
103 limit_value (double * value, const SGPropertyNode * arg)
104 {
105     const SGPropertyNode * min_node = arg->getChild("min");
106     const SGPropertyNode * max_node = arg->getChild("max");
107
108     bool wrap = arg->getBoolValue("wrap");
109
110     if (min_node == 0 || max_node == 0)
111         wrap = false;
112   
113     if (wrap) {                 // wrap such that min <= x < max
114         double min_val = min_node->getDoubleValue();
115         double max_val = max_node->getDoubleValue();
116         double resolution = arg->getDoubleValue("resolution");
117         if (resolution > 0.0) {
118             // snap to (min + N*resolution), taking special care to handle imprecision
119             int n = (int)floor((*value - min_val) / resolution + 0.5);
120             int steps = (int)floor((max_val - min_val) / resolution + 0.5);
121             SG_NORMALIZE_RANGE(n, 0, steps);
122             *value = min_val + resolution * n;
123         } else {
124             // plain circular wrapping
125             SG_NORMALIZE_RANGE(*value, min_val, max_val);
126         }
127     } else {                    // clamp such that min <= x <= max
128         if ((min_node != 0) && (*value < min_node->getDoubleValue()))
129             *value = min_node->getDoubleValue();
130         else if ((max_node != 0) && (*value > max_node->getDoubleValue()))
131             *value = max_node->getDoubleValue();
132     }
133 }
134
135 static bool
136 compare_values (SGPropertyNode * value1, SGPropertyNode * value2)
137 {
138     switch (value1->getType()) {
139     case simgear::props::BOOL:
140         return (value1->getBoolValue() == value2->getBoolValue());
141     case simgear::props::INT:
142         return (value1->getIntValue() == value2->getIntValue());
143     case simgear::props::LONG:
144         return (value1->getLongValue() == value2->getLongValue());
145     case simgear::props::FLOAT:
146         return (value1->getFloatValue() == value2->getFloatValue());
147     case simgear::props::DOUBLE:
148         return (value1->getDoubleValue() == value2->getDoubleValue());
149     default:
150         return !strcmp(value1->getStringValue(), value2->getStringValue());
151     }
152 }
153
154
155 \f
156 ////////////////////////////////////////////////////////////////////////
157 // Command implementations.
158 ////////////////////////////////////////////////////////////////////////
159
160
161 /**
162  * Built-in command: do nothing.
163  */
164 static bool
165 do_null (const SGPropertyNode * arg)
166 {
167   return true;
168 }
169
170 /**
171  * Built-in command: run a Nasal script.
172  */
173 static bool
174 do_nasal (const SGPropertyNode * arg)
175 {
176     return ((FGNasalSys*)globals->get_subsystem("nasal"))->handleCommand(arg);
177 }
178
179 /**
180  * Built-in command: exit FlightGear.
181  *
182  * status: the exit status to return to the operating system (defaults to 0)
183  */
184 static bool
185 do_exit (const SGPropertyNode * arg)
186 {
187     SG_LOG(SG_INPUT, SG_INFO, "Program exit requested.");
188     fgSetBool("/sim/signals/exit", true);
189
190     if (fgGetBool("/sim/startup/save-on-exit")) {
191 #ifdef _MSC_VER
192         char* envp = ::getenv( "APPDATA" );
193         if ( envp != NULL ) {
194             SGPath config( envp );
195             config.append( "flightgear.org" );
196 #else
197         if ( homedir != NULL ) {
198             SGPath config( homedir );
199             config.append( ".fgfs" );
200 #endif
201             config.append( "autosave.xml" );
202             config.create_dir( 0700 );
203             SG_LOG(SG_IO, SG_INFO, "Saving user settings to " << config.str());
204             try {
205                 writeProperties(config.str(), globals->get_props(), false, SGPropertyNode::USERARCHIVE);
206             } catch (const sg_exception &e) {
207                 guiErrorMessage("Error writing autosave.xml: ", e);
208             }
209
210             SG_LOG(SG_INPUT, SG_DEBUG, "Finished Saving user settings");
211         }
212     }
213     fgExit(arg->getIntValue("status", 0));
214     return true;
215 }
216
217
218 /**
219  * Reset FlightGear (Shift-Escape or Menu->File->Reset)
220  */
221 static bool
222 do_reset (const SGPropertyNode * arg)
223 {
224     reInit();
225     return true;
226 }
227
228
229 /**
230  * Built-in command: reinitialize one or more subsystems.
231  *
232  * subsystem[*]: the name(s) of the subsystem(s) to reinitialize; if
233  * none is specified, reinitialize all of them.
234  */
235 static bool
236 do_reinit (const SGPropertyNode * arg)
237 {
238     bool result = true;
239
240     vector<SGPropertyNode_ptr> subsystems = arg->getChildren("subsystem");
241     if (subsystems.size() == 0) {
242         globals->get_subsystem_mgr()->reinit();
243     } else {
244         for ( unsigned int i = 0; i < subsystems.size(); i++ ) {
245             const char * name = subsystems[i]->getStringValue();
246             SGSubsystem * subsystem = globals->get_subsystem(name);
247             if (subsystem == 0) {
248                 result = false;
249                 SG_LOG( SG_GENERAL, SG_ALERT,
250                         "Subsystem " << name << "not found" );
251             } else {
252                 subsystem->reinit();
253             }
254         }
255     }
256
257     globals->get_event_mgr()->reinit();
258
259     return result;
260 }
261
262 #if 0
263   //
264   // these routines look useful ??? but are never used in the code ???
265   //
266
267 /**
268  * Built-in command: suspend one or more subsystems.
269  *
270  * subsystem[*] - the name(s) of the subsystem(s) to suspend.
271  */
272 static bool
273 do_suspend (const SGPropertyNode * arg)
274 {
275     bool result = true;
276
277     vector<SGPropertyNode_ptr> subsystems = arg->getChildren("subsystem");
278     for ( unsigned int i = 0; i < subsystems.size(); i++ ) {
279         const char * name = subsystems[i]->getStringValue();
280         SGSubsystem * subsystem = globals->get_subsystem(name);
281         if (subsystem == 0) {
282             result = false;
283             SG_LOG(SG_GENERAL, SG_ALERT, "Subsystem " << name << "not found");
284         } else {
285             subsystem->suspend();
286         }
287     }
288     return result;
289 }
290
291 /**
292  * Built-in command: suspend one or more subsystems.
293  *
294  * subsystem[*] - the name(s) of the subsystem(s) to suspend.
295  */
296 static bool
297 do_resume (const SGPropertyNode * arg)
298 {
299     bool result = true;
300
301     vector<SGPropertyNode_ptr> subsystems = arg->getChildren("subsystem");
302     for ( unsigned int i = 0; i < subsystems.size(); i++ ) {
303         const char * name = subsystems[i]->getStringValue();
304         SGSubsystem * subsystem = globals->get_subsystem(name);
305         if (subsystem == 0) {
306             result = false;
307             SG_LOG(SG_GENERAL, SG_ALERT, "Subsystem " << name << "not found");
308         } else {
309             subsystem->resume();
310         }
311     }
312     return result;
313 }
314
315 #endif
316
317
318 /**
319  * Built-in command: load flight.
320  *
321  * file (optional): the name of the file to load (relative to current
322  *   directory).  Defaults to "fgfs.sav"
323  */
324 static bool
325 do_load (const SGPropertyNode * arg)
326 {
327     string file = arg->getStringValue("file", "fgfs.sav");
328     if (file.size() < 4 || file.substr(file.size() - 4) != ".sav")
329         file += ".sav";
330
331     if (!fgValidatePath(file.c_str(), false)) {
332         SG_LOG(SG_IO, SG_ALERT, "load: reading '" << file << "' denied "
333                 "(unauthorized access)");
334         return false;
335     }
336
337     ifstream input(file.c_str());
338     if (input.good() && fgLoadFlight(input)) {
339         input.close();
340         SG_LOG(SG_INPUT, SG_INFO, "Restored flight from " << file);
341         return true;
342     } else {
343         SG_LOG(SG_INPUT, SG_WARN, "Cannot load flight from " << file);
344         return false;
345     }
346 }
347
348
349 /**
350  * Built-in command: save flight.
351  *
352  * file (optional): the name of the file to save (relative to the
353  * current directory).  Defaults to "fgfs.sav".
354  */
355 static bool
356 do_save (const SGPropertyNode * arg)
357 {
358     string file = arg->getStringValue("file", "fgfs.sav");
359     if (file.size() < 4 || file.substr(file.size() - 4) != ".sav")
360         file += ".sav";
361
362     if (!fgValidatePath(file.c_str(), false)) {
363         SG_LOG(SG_IO, SG_ALERT, "save: writing '" << file << "' denied "
364                 "(unauthorized access)");
365         return false;
366     }
367
368     bool write_all = arg->getBoolValue("write-all", false);
369     SG_LOG(SG_INPUT, SG_INFO, "Saving flight");
370     ofstream output(file.c_str());
371     if (output.good() && fgSaveFlight(output, write_all)) {
372         output.close();
373         SG_LOG(SG_INPUT, SG_INFO, "Saved flight to " << file);
374         return true;
375     } else {
376         SG_LOG(SG_INPUT, SG_ALERT, "Cannot save flight to " << file);
377         return false;
378     }
379 }
380
381
382 /**
383  * Built-in command: (re)load the panel.
384  *
385  * path (optional): the file name to load the panel from 
386  * (relative to FG_ROOT).  Defaults to the value of /sim/panel/path,
387  * and if that's unspecified, to "Panels/Default/default.xml".
388  */
389 static bool
390 do_panel_load (const SGPropertyNode * arg)
391 {
392   string panel_path =
393     arg->getStringValue("path",
394                         fgGetString("/sim/panel/path",
395                                     "Panels/Default/default.xml"));
396   FGPanel * new_panel = fgReadPanel(panel_path);
397   if (new_panel == 0) {
398     SG_LOG(SG_INPUT, SG_ALERT,
399            "Error reading new panel from " << panel_path);
400     return false;
401   }
402   SG_LOG(SG_INPUT, SG_INFO, "Loaded new panel from " << panel_path);
403   globals->get_current_panel()->unbind();
404   delete globals->get_current_panel();
405   globals->set_current_panel( new_panel );
406   globals->get_current_panel()->bind();
407   return true;
408 }
409
410
411 /**
412  * Built-in command: pass a mouse click to the panel.
413  *
414  * button: the mouse button number, zero-based.
415  * is-down: true if the button is down, false if it is up.
416  * x-pos: the x position of the mouse click.
417  * y-pos: the y position of the mouse click.
418  */
419 static bool
420 do_panel_mouse_click (const SGPropertyNode * arg)
421 {
422   if (globals->get_current_panel() != 0)
423     return globals->get_current_panel()
424       ->doMouseAction(arg->getIntValue("button"),
425                       arg->getBoolValue("is-down") ? PU_DOWN : PU_UP,
426                       arg->getIntValue("x-pos"),
427                       arg->getIntValue("y-pos"));
428   else
429     return false;
430 }
431
432
433 /**
434  * Built-in command: (re)load preferences.
435  *
436  * path (optional): the file name to load the panel from (relative
437  * to FG_ROOT). Defaults to "preferences.xml".
438  */
439 static bool
440 do_preferences_load (const SGPropertyNode * arg)
441 {
442   try {
443     fgLoadProps(arg->getStringValue("path", "preferences.xml"),
444                 globals->get_props());
445   } catch (const sg_exception &e) {
446     guiErrorMessage("Error reading global preferences: ", e);
447     return false;
448   }
449   SG_LOG(SG_INPUT, SG_INFO, "Successfully read global preferences.");
450   return true;
451 }
452
453
454 static void
455 fix_hud_visibility()
456 {
457   if ( !strcmp(fgGetString("/sim/flight-model"), "ada") ) {
458       globals->get_props()->setBoolValue( "/sim/hud/visibility", true );
459       if ( globals->get_viewmgr()->get_current() == 1 ) {
460           globals->get_props()->setBoolValue( "/sim/hud/visibility", false );
461       }
462   }
463 }
464
465 static void
466 do_view_next( bool )
467 {
468     globals->get_current_view()->setHeadingOffset_deg(0.0);
469     globals->get_viewmgr()->next_view();
470     fix_hud_visibility();
471 }
472
473 static void
474 do_view_prev( bool )
475 {
476     globals->get_current_view()->setHeadingOffset_deg(0.0);
477     globals->get_viewmgr()->prev_view();
478     fix_hud_visibility();
479 }
480
481 /**
482  * Built-in command: cycle view.
483  */
484 static bool
485 do_view_cycle (const SGPropertyNode * arg)
486 {
487   globals->get_current_view()->setHeadingOffset_deg(0.0);
488   globals->get_viewmgr()->next_view();
489   fix_hud_visibility();
490   return true;
491 }
492
493 /**
494  * Built-in command: capture screen.
495  */
496 static bool
497 do_screen_capture (const SGPropertyNode * arg)
498 {
499   return fgDumpSnapShot();
500 }
501
502 static bool
503 do_reload_shaders (const SGPropertyNode*)
504 {
505     simgear::reload_shaders();
506     return true;
507 }
508
509 static bool
510 do_dump_scene_graph (const SGPropertyNode*)
511 {
512     fgDumpSceneGraph();
513     return true;
514 }
515
516 static bool
517 do_dump_terrain_branch (const SGPropertyNode*)
518 {
519     fgDumpTerrainBranch();
520
521     double lon_deg = fgGetDouble("/position/longitude-deg");
522     double lat_deg = fgGetDouble("/position/latitude-deg");
523     SGGeod geodPos = SGGeod::fromDegFt(lon_deg, lat_deg, 0.0);
524     SGVec3d zero = SGVec3d::fromGeod(geodPos);
525
526     SG_LOG(SG_INPUT, SG_INFO, "Model parameters:");
527     SG_LOG(SG_INPUT, SG_INFO, "Center: " << zero.x() << ", " << zero.y() << ", " << zero.z() );
528     SG_LOG(SG_INPUT, SG_INFO, "Rotation: " << lat_deg << ", " << lon_deg );
529
530     return true;
531 }
532
533 /**
534  * Built-in command: hires capture screen.
535  */
536 static bool
537 do_hires_screen_capture (const SGPropertyNode * arg)
538 {
539   fgHiResDump();
540   return true;
541 }
542
543
544 /**
545  * Reload the tile cache.
546  */
547 static bool
548 do_tile_cache_reload (const SGPropertyNode * arg)
549 {
550     static const SGPropertyNode *master_freeze
551         = fgGetNode("/sim/freeze/master");
552     bool freeze = master_freeze->getBoolValue();
553     SG_LOG(SG_INPUT, SG_INFO, "ReIniting TileCache");
554     if ( !freeze ) {
555         fgSetBool("/sim/freeze/master", true);
556     }
557     if ( globals->get_tile_mgr()->init() ) {
558         // Load the local scenery data
559         double visibility_meters = fgGetDouble("/environment/visibility-m");
560         globals->get_tile_mgr()->update( visibility_meters );
561     } else {
562         SG_LOG( SG_GENERAL, SG_ALERT, 
563                 "Error in Tile Manager initialization!" );
564         exit(-1);
565     }
566     if ( !freeze ) {
567         fgSetBool("/sim/freeze/master", false);
568     }
569     return true;
570 }
571
572
573 /**
574  * Set the sea level outside air temperature and assigning that to all
575  * boundary and aloft environment layers.
576  */
577 static bool
578 do_set_sea_level_degc (const SGPropertyNode * arg)
579 {
580     double temp_sea_level_degc = arg->getDoubleValue("temp-degc", 15.0);
581
582     SGPropertyNode *node, *child;
583
584     // boundary layers
585     node = fgGetNode( "/environment/config/boundary" );
586     if ( node != NULL ) {
587       int i = 0;
588       while ( ( child = node->getNode( "entry", i ) ) != NULL ) {
589         child->setDoubleValue( "temperature-sea-level-degc",
590                                temp_sea_level_degc );
591         ++i;
592       }
593     }
594
595     // aloft layers
596     node = fgGetNode( "/environment/config/aloft" );
597     if ( node != NULL ) {
598       int i = 0;
599       while ( ( child = node->getNode( "entry", i ) ) != NULL ) {
600         child->setDoubleValue( "temperature-sea-level-degc",
601                                temp_sea_level_degc );
602         ++i;
603       }
604     }
605
606     return true;
607 }
608
609
610 /**
611  * Set the outside air temperature at the "current" altitude by first
612  * calculating the corresponding sea level temp, and assigning that to
613  * all boundary and aloft environment layers.
614  */
615 static bool
616 do_set_oat_degc (const SGPropertyNode * arg)
617 {
618     const string &temp_str = arg->getStringValue("temp-degc", "15.0");
619
620     // check for an altitude specified in the arguments, otherwise use
621     // current aircraft altitude.
622     const SGPropertyNode *altitude_ft = arg->getChild("altitude-ft");
623     if ( altitude_ft == NULL ) {
624         altitude_ft = fgGetNode("/position/altitude-ft");
625     }
626
627     FGEnvironment dummy;        // instantiate a dummy so we can leech a method
628     dummy.set_elevation_ft( altitude_ft->getDoubleValue() );
629     dummy.set_temperature_degc( atof( temp_str.c_str() ) );
630     double temp_sea_level_degc = dummy.get_temperature_sea_level_degc();
631
632     //cout << "Altitude = " << altitude_ft->getDoubleValue() << endl;
633     //cout << "Temp at alt (C) = " << atof( temp_str.c_str() ) << endl;
634     //cout << "Temp sea level (C) = " << temp_sea_level_degc << endl;
635  
636     SGPropertyNode *node, *child;
637
638     // boundary layers
639     node = fgGetNode( "/environment/config/boundary" );
640     if ( node != NULL ) {
641       int i = 0;
642       while ( ( child = node->getNode( "entry", i ) ) != NULL ) {
643         child->setDoubleValue( "temperature-sea-level-degc",
644                                temp_sea_level_degc );
645         ++i;
646       }
647     }
648
649     // aloft layers
650     node = fgGetNode( "/environment/config/aloft" );
651     if ( node != NULL ) {
652       int i = 0;
653       while ( ( child = node->getNode( "entry", i ) ) != NULL ) {
654         child->setDoubleValue( "temperature-sea-level-degc",
655                                temp_sea_level_degc );
656         ++i;
657       }
658     }
659
660     return true;
661 }
662
663 /**
664  * Set the sea level outside air dewpoint and assigning that to all
665  * boundary and aloft environment layers.
666  */
667 static bool
668 do_set_dewpoint_sea_level_degc (const SGPropertyNode * arg)
669 {
670     double dewpoint_sea_level_degc = arg->getDoubleValue("dewpoint-degc", 5.0);
671
672     SGPropertyNode *node, *child;
673
674     // boundary layers
675     node = fgGetNode( "/environment/config/boundary" );
676     if ( node != NULL ) {
677       int i = 0;
678       while ( ( child = node->getNode( "entry", i ) ) != NULL ) {
679         child->setDoubleValue( "dewpoint-sea-level-degc",
680                                dewpoint_sea_level_degc );
681         ++i;
682       }
683     }
684
685     // aloft layers
686     node = fgGetNode( "/environment/config/aloft" );
687     if ( node != NULL ) {
688       int i = 0;
689       while ( ( child = node->getNode( "entry", i ) ) != NULL ) {
690         child->setDoubleValue( "dewpoint-sea-level-degc",
691                                dewpoint_sea_level_degc );
692         ++i;
693       }
694     }
695
696     return true;
697 }
698
699
700 /**
701  * Set the outside air dewpoint at the "current" altitude by first
702  * calculating the corresponding sea level dewpoint, and assigning
703  * that to all boundary and aloft environment layers.
704  */
705 static bool
706 do_set_dewpoint_degc (const SGPropertyNode * arg)
707 {
708     const string &dewpoint_str = arg->getStringValue("dewpoint-degc", "5.0");
709
710     // check for an altitude specified in the arguments, otherwise use
711     // current aircraft altitude.
712     const SGPropertyNode *altitude_ft = arg->getChild("altitude-ft");
713     if ( altitude_ft == NULL ) {
714         altitude_ft = fgGetNode("/position/altitude-ft");
715     }
716
717     FGEnvironment dummy;        // instantiate a dummy so we can leech a method
718     dummy.set_elevation_ft( altitude_ft->getDoubleValue() );
719     dummy.set_dewpoint_degc( atof( dewpoint_str.c_str() ) );
720     double dewpoint_sea_level_degc = dummy.get_dewpoint_sea_level_degc();
721
722     //cout << "Altitude = " << altitude_ft->getDoubleValue() << endl;
723     //cout << "Dewpoint at alt (C) = " << atof( dewpoint_str.c_str() ) << endl;
724     //cout << "Dewpoint at sea level (C) = " << dewpoint_sea_level_degc << endl;
725  
726     SGPropertyNode *node, *child;
727
728     // boundary layers
729     node = fgGetNode( "/environment/config/boundary" );
730     if ( node != NULL ) {
731       int i = 0;
732       while ( ( child = node->getNode( "entry", i ) ) != NULL ) {
733         child->setDoubleValue( "dewpoint-sea-level-degc",
734                                dewpoint_sea_level_degc );
735         ++i;
736       }
737     }
738
739     // aloft layers
740     node = fgGetNode( "/environment/config/aloft" );
741     if ( node != NULL ) {
742       int i = 0;
743       while ( ( child = node->getNode( "entry", i ) ) != NULL ) {
744         child->setDoubleValue( "dewpoint-sea-level-degc",
745                                dewpoint_sea_level_degc );
746         ++i;
747       }
748     }
749
750     return true;
751 }
752
753 /**
754  * Update the lighting manually.
755  */
756 static bool
757 do_timeofday (const SGPropertyNode * arg)
758 {
759     const string &offset_type = arg->getStringValue("timeofday", "noon");
760
761     static const SGPropertyNode *longitude
762         = fgGetNode("/position/longitude-deg");
763     static const SGPropertyNode *latitude
764         = fgGetNode("/position/latitude-deg");
765     static const SGPropertyNode *cur_time_override
766         = fgGetNode("/sim/time/cur-time-override", true);
767
768     int orig_warp = globals->get_warp();
769     SGTime *t = globals->get_time_params();
770     time_t cur_time = t->get_cur_time();
771     // cout << "cur_time = " << cur_time << endl;
772     // cout << "orig_warp = " << orig_warp << endl;
773
774     int warp = 0;
775     if ( offset_type == "real" ) {
776         warp = -orig_warp;
777     } else if ( offset_type == "dawn" ) {
778         warp = fgTimeSecondsUntilSunAngle( cur_time,
779                                            longitude->getDoubleValue()
780                                              * SGD_DEGREES_TO_RADIANS,
781                                            latitude->getDoubleValue()
782                                              * SGD_DEGREES_TO_RADIANS,
783                                            90.0, true ); 
784     } else if ( offset_type == "morning" ) {
785         warp = fgTimeSecondsUntilSunAngle( cur_time,
786                                            longitude->getDoubleValue()
787                                              * SGD_DEGREES_TO_RADIANS,
788                                            latitude->getDoubleValue()
789                                              * SGD_DEGREES_TO_RADIANS,
790                                            75.0, true ); 
791     } else if ( offset_type == "noon" ) {
792         warp = fgTimeSecondsUntilSunAngle( cur_time,
793                                            longitude->getDoubleValue()
794                                              * SGD_DEGREES_TO_RADIANS,
795                                            latitude->getDoubleValue()
796                                              * SGD_DEGREES_TO_RADIANS,
797                                            0.0, true ); 
798     } else if ( offset_type == "afternoon" ) {
799         warp = fgTimeSecondsUntilSunAngle( cur_time,
800                                            longitude->getDoubleValue()
801                                              * SGD_DEGREES_TO_RADIANS,
802                                            latitude->getDoubleValue()
803                                              * SGD_DEGREES_TO_RADIANS,
804                                            60.0, false ); 
805      } else if ( offset_type == "dusk" ) {
806         warp = fgTimeSecondsUntilSunAngle( cur_time,
807                                            longitude->getDoubleValue()
808                                              * SGD_DEGREES_TO_RADIANS,
809                                            latitude->getDoubleValue()
810                                              * SGD_DEGREES_TO_RADIANS,
811                                            90.0, false ); 
812      } else if ( offset_type == "evening" ) {
813         warp = fgTimeSecondsUntilSunAngle( cur_time,
814                                            longitude->getDoubleValue()
815                                              * SGD_DEGREES_TO_RADIANS,
816                                            latitude->getDoubleValue()
817                                              * SGD_DEGREES_TO_RADIANS,
818                                            100.0, false ); 
819     } else if ( offset_type == "midnight" ) {
820         warp = fgTimeSecondsUntilSunAngle( cur_time,
821                                            longitude->getDoubleValue()
822                                              * SGD_DEGREES_TO_RADIANS,
823                                            latitude->getDoubleValue()
824                                              * SGD_DEGREES_TO_RADIANS,
825                                            180.0, false ); 
826     }
827     // cout << "warp = " << warp << endl;
828     globals->set_warp( orig_warp + warp );
829
830     t->update( longitude->getDoubleValue() * SGD_DEGREES_TO_RADIANS,
831                latitude->getDoubleValue() * SGD_DEGREES_TO_RADIANS,
832                cur_time_override->getLongValue(),
833                globals->get_warp() );
834
835     return true;
836 }
837
838
839 /**
840  * Built-in command: toggle a bool property value.
841  *
842  * property: The name of the property to toggle.
843  */
844 static bool
845 do_property_toggle (const SGPropertyNode * arg)
846 {
847   SGPropertyNode * prop = get_prop(arg);
848   return prop->setBoolValue(!prop->getBoolValue());
849 }
850
851
852 /**
853  * Built-in command: assign a value to a property.
854  *
855  * property: the name of the property to assign.
856  * value: the value to assign; or
857  * property[1]: the property to copy from.
858  */
859 static bool
860 do_property_assign (const SGPropertyNode * arg)
861 {
862   SGPropertyNode * prop = get_prop(arg);
863   const SGPropertyNode * prop2 = get_prop2(arg);
864   const SGPropertyNode * value = arg->getNode("value");
865
866   if (value != 0)
867       return prop->setUnspecifiedValue(value->getStringValue());
868   else if (prop2)
869       return prop->setUnspecifiedValue(prop2->getStringValue());
870   else
871       return false;
872 }
873
874
875 /**
876  * Built-in command: increment or decrement a property value.
877  *
878  * If the 'step' argument is present, it will be used; otherwise,
879  * the command uses 'offset' and 'factor', usually from the mouse.
880  *
881  * property: the name of the property to increment or decrement.
882  * step: the amount of the increment or decrement (default: 0).
883  * offset: offset from the current setting (used for the mouse; multiplied 
884  *         by factor)
885  * factor: scaling amount for the offset (defaults to 1).
886  * min: the minimum allowed value (default: no minimum).
887  * max: the maximum allowed value (default: no maximum).
888  * mask: 'integer' to apply only to the left of the decimal point, 
889  *       'decimal' to apply only to the right of the decimal point,
890  *       or 'all' to apply to the whole number (the default).
891  * wrap: true if the value should be wrapped when it passes min or max;
892  *       both min and max must be present for this to work (default:
893  *       false).
894  */
895 static bool
896 do_property_adjust (const SGPropertyNode * arg)
897 {
898   SGPropertyNode * prop = get_prop(arg);
899
900   double amount = 0;
901   if (arg->hasValue("step"))
902       amount = arg->getDoubleValue("step");
903   else
904       amount = (arg->getDoubleValue("factor", 1)
905                 * arg->getDoubleValue("offset"));
906           
907   double unmodifiable, modifiable;
908   split_value(prop->getDoubleValue(), arg->getStringValue("mask", "all"),
909               &unmodifiable, &modifiable);
910   modifiable += amount;
911   limit_value(&modifiable, arg);
912
913   prop->setDoubleValue(unmodifiable + modifiable);
914
915   return true;
916 }
917
918
919 /**
920  * Built-in command: multiply a property value.
921  *
922  * property: the name of the property to multiply.
923  * factor: the amount by which to multiply.
924  * min: the minimum allowed value (default: no minimum).
925  * max: the maximum allowed value (default: no maximum).
926  * mask: 'integer' to apply only to the left of the decimal point, 
927  *       'decimal' to apply only to the right of the decimal point,
928  *       or 'all' to apply to the whole number (the default).
929  * wrap: true if the value should be wrapped when it passes min or max;
930  *       both min and max must be present for this to work (default:
931  *       false).
932  */
933 static bool
934 do_property_multiply (const SGPropertyNode * arg)
935 {
936   SGPropertyNode * prop = get_prop(arg);
937   double factor = arg->getDoubleValue("factor", 1);
938
939   double unmodifiable, modifiable;
940   split_value(prop->getDoubleValue(), arg->getStringValue("mask", "all"),
941               &unmodifiable, &modifiable);
942   modifiable *= factor;
943   limit_value(&modifiable, arg);
944
945   prop->setDoubleValue(unmodifiable + modifiable);
946
947   return true;
948 }
949
950
951 /**
952  * Built-in command: swap two property values.
953  *
954  * property[0]: the name of the first property.
955  * property[1]: the name of the second property.
956  */
957 static bool
958 do_property_swap (const SGPropertyNode * arg)
959 {
960   SGPropertyNode * prop1 = get_prop(arg);
961   SGPropertyNode * prop2 = get_prop2(arg);
962
963                                 // FIXME: inefficient
964   const string & tmp = prop1->getStringValue();
965   return (prop1->setUnspecifiedValue(prop2->getStringValue()) &&
966           prop2->setUnspecifiedValue(tmp.c_str()));
967 }
968
969
970 /**
971  * Built-in command: Set a property to an axis or other moving input.
972  *
973  * property: the name of the property to set.
974  * setting: the current input setting, usually between -1.0 and 1.0.
975  * offset: the offset to shift by, before applying the factor.
976  * factor: the factor to multiply by (use negative to reverse).
977  */
978 static bool
979 do_property_scale (const SGPropertyNode * arg)
980 {
981   SGPropertyNode * prop = get_prop(arg);
982   double setting = arg->getDoubleValue("setting");
983   double offset = arg->getDoubleValue("offset", 0.0);
984   double factor = arg->getDoubleValue("factor", 1.0);
985   bool squared = arg->getBoolValue("squared", false);
986   int power = arg->getIntValue("power", (squared ? 2 : 1));
987
988   int sign = (setting < 0 ? -1 : 1);
989
990   switch (power) {
991   case 1:
992       break;
993   case 2:
994       setting = setting * setting * sign;
995       break;
996   case 3:
997       setting = setting * setting * setting;
998       break;
999   case 4:
1000       setting = setting * setting * setting * setting * sign;
1001       break;
1002   default:
1003       setting =  pow(setting, power);
1004       if ((power % 2) == 0)
1005           setting *= sign;
1006       break;
1007   }
1008
1009   return prop->setDoubleValue((setting + offset) * factor);
1010 }
1011
1012
1013 /**
1014  * Built-in command: cycle a property through a set of values.
1015  *
1016  * If the current value isn't in the list, the cycle will
1017  * (re)start from the beginning.
1018  *
1019  * property: the name of the property to cycle.
1020  * value[*]: the list of values to cycle through.
1021  */
1022 static bool
1023 do_property_cycle (const SGPropertyNode * arg)
1024 {
1025     SGPropertyNode * prop = get_prop(arg);
1026     vector<SGPropertyNode_ptr> values = arg->getChildren("value");
1027     int selection = -1;
1028     int nSelections = values.size();
1029
1030     if (nSelections < 1) {
1031         SG_LOG(SG_GENERAL, SG_ALERT, "No values for property-cycle");
1032         return false;
1033     }
1034
1035                                 // Try to find the current selection
1036     for (int i = 0; i < nSelections; i++) {
1037         if (compare_values(prop, values[i])) {
1038             selection = i + 1;
1039             break;
1040         }
1041     }
1042
1043                                 // Default or wrap to the first selection
1044     if (selection < 0 || selection >= nSelections)
1045         selection = 0;
1046
1047     prop->setUnspecifiedValue(values[selection]->getStringValue());
1048     return true;
1049 }
1050
1051
1052 /**
1053  * Built-in command: randomize a numeric property value.
1054  *
1055  * property: the name of the property value to randomize.
1056  * min: the minimum allowed value.
1057  * max: the maximum allowed value.
1058  */
1059 static bool
1060 do_property_randomize (const SGPropertyNode * arg)
1061 {
1062     SGPropertyNode * prop = get_prop(arg);
1063     double min = arg->getDoubleValue("min", DBL_MIN);
1064     double max = arg->getDoubleValue("max", DBL_MAX);
1065     prop->setDoubleValue(sg_random() * (max - min) + min);
1066     return true;
1067 }
1068
1069
1070 /**
1071  * Built-in command: reinit the data logging system based on the
1072  * current contents of the /logger tree.
1073  */
1074 static bool
1075 do_data_logging_commit (const SGPropertyNode * arg)
1076 {
1077     FGLogger *log = (FGLogger *)globals->get_subsystem("logger");
1078     log->reinit();
1079     return true;
1080 }
1081
1082 /**
1083  * Built-in command: Add a dialog to the GUI system.  Does *not*
1084  * display the dialog.  The property node should have the same format
1085  * as a dialog XML configuration.  It must include:
1086  *
1087  * name: the name of the GUI dialog for future reference.
1088  */
1089 static bool
1090 do_dialog_new (const SGPropertyNode * arg)
1091 {
1092     NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
1093
1094     // Note the casting away of const: this is *real*.  Doing a
1095     // "dialog-apply" command later on will mutate this property node.
1096     // I'm not convinced that this isn't the Right Thing though; it
1097     // allows client to create a node, pass it to dialog-new, and get
1098     // the values back from the dialog by reading the same node.
1099     // Perhaps command arguments are not as "const" as they would
1100     // seem?
1101     gui->newDialog((SGPropertyNode*)arg);
1102     return true;
1103 }
1104
1105 /**
1106  * Built-in command: Show an XML-configured dialog.
1107  *
1108  * dialog-name: the name of the GUI dialog to display.
1109  */
1110 static bool
1111 do_dialog_show (const SGPropertyNode * arg)
1112 {
1113     NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
1114     gui->showDialog(arg->getStringValue("dialog-name"));
1115     return true;
1116 }
1117
1118
1119 /**
1120  * Built-in Command: Hide the active XML-configured dialog.
1121  */
1122 static bool
1123 do_dialog_close (const SGPropertyNode * arg)
1124 {
1125     NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
1126     if(arg->hasValue("dialog-name"))
1127         return gui->closeDialog(arg->getStringValue("dialog-name"));
1128     return gui->closeActiveDialog();
1129 }
1130
1131
1132 /**
1133  * Update a value in the active XML-configured dialog.
1134  *
1135  * object-name: The name of the GUI object(s) (all GUI objects if omitted).
1136  */
1137 static bool
1138 do_dialog_update (const SGPropertyNode * arg)
1139 {
1140     NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
1141     FGDialog * dialog;
1142     if (arg->hasValue("dialog-name"))
1143         dialog = gui->getDialog(arg->getStringValue("dialog-name"));
1144     else
1145         dialog = gui->getActiveDialog();
1146
1147     if (dialog != 0) {
1148         dialog->updateValues(arg->getStringValue("object-name"));
1149         return true;
1150     } else {
1151         return false;
1152     }
1153 }
1154
1155
1156 /**
1157  * Apply a value in the active XML-configured dialog.
1158  *
1159  * object-name: The name of the GUI object(s) (all GUI objects if omitted).
1160  */
1161 static bool
1162 do_dialog_apply (const SGPropertyNode * arg)
1163 {
1164     NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
1165     FGDialog * dialog;
1166     if (arg->hasValue("dialog-name"))
1167         dialog = gui->getDialog(arg->getStringValue("dialog-name"));
1168     else
1169         dialog = gui->getActiveDialog();
1170
1171     if (dialog != 0) {
1172         dialog->applyValues(arg->getStringValue("object-name"));
1173         return true;
1174     } else {
1175         return false;
1176     }
1177 }
1178
1179
1180 /**
1181  * Redraw GUI (applying new widget colors). Doesn't reload the dialogs,
1182  * unlike reinit().
1183  */
1184 static bool
1185 do_gui_redraw (const SGPropertyNode * arg)
1186 {
1187     NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
1188     gui->redraw();
1189     return true;
1190 }
1191
1192
1193 /**
1194  * Adds model to the scenery. The path to the added branch (/models/model[*])
1195  * is returned in property "property".
1196  */
1197 static bool
1198 do_add_model (const SGPropertyNode * arg)
1199 {
1200     SGPropertyNode * model = fgGetNode("models", true);
1201     for (int i = 0;; i++) {
1202         if (i < 0)
1203             return false;
1204         if (!model->getChild("model", i, false)) {
1205             model = model->getChild("model", i, true);
1206             break;
1207         }
1208     }
1209     copyProperties(arg, model);
1210     if (model->hasValue("elevation-m"))
1211         model->setDoubleValue("elevation-ft", model->getDoubleValue("elevation-m")
1212                 * SG_METER_TO_FEET);
1213     model->getNode("load", true);
1214     model->removeChildren("load");
1215     const_cast<SGPropertyNode *>(arg)->setStringValue("property", model->getPath());
1216     return true;
1217 }
1218
1219
1220 /**
1221  * Set mouse cursor coordinates and cursor shape.
1222  */
1223 static bool
1224 do_set_cursor (const SGPropertyNode * arg)
1225 {
1226     if (arg->hasValue("x") || arg->hasValue("y")) {
1227         SGPropertyNode *mx = fgGetNode("/devices/status/mice/mouse/x", true);
1228         SGPropertyNode *my = fgGetNode("/devices/status/mice/mouse/y", true);
1229         int x = arg->getIntValue("x", mx->getIntValue());
1230         int y = arg->getIntValue("y", my->getIntValue());
1231         fgWarpMouse(x, y);
1232         mx->setIntValue(x);
1233         my->setIntValue(y);
1234     }
1235
1236     SGPropertyNode *cursor = const_cast<SGPropertyNode *>(arg)->getNode("cursor", true);
1237     if (cursor->getType() != simgear::props::NONE)
1238         fgSetMouseCursor(cursor->getIntValue());
1239
1240     cursor->setIntValue(fgGetMouseCursor());
1241     return true;
1242 }
1243
1244
1245 /**
1246  * Built-in command: play an audio message (i.e. a wav file) This is
1247  * fire and forget.  Call this once per message and it will get dumped
1248  * into a queue.  Messages are played sequentially so they do not
1249  * overlap.
1250  */
1251 static bool
1252 do_play_audio_sample (const SGPropertyNode * arg)
1253 {
1254     FGFX *fx = (FGFX *)globals->get_subsystem("fx");
1255     string path = arg->getStringValue("path");
1256     string file = arg->getStringValue("file");
1257     double volume = arg->getDoubleValue("volume");
1258     // cout << "playing " << path << " / " << file << endl;
1259     try {
1260         fx->play_message( path, file, volume );
1261         return true;
1262
1263     } catch (const sg_io_exception&) {
1264         SG_LOG(SG_GENERAL, SG_ALERT, "play-audio-sample: "
1265                 "failed to load" << path << '/' << file);
1266         return false;
1267     }
1268 }
1269
1270 /**
1271  * Built-in command: commit presets (read from in /sim/presets/)
1272  */
1273 static bool
1274 do_presets_commit (const SGPropertyNode * arg)
1275 {
1276     // unbind the current fdm state so property changes
1277     // don't get lost when we subsequently delete this fdm
1278     // and create a new one.
1279     cur_fdm_state->unbind();
1280
1281     // set position from presets
1282     fgInitPosition();
1283
1284     fgReInitSubsystems();
1285
1286     globals->get_tile_mgr()->update( fgGetDouble("/environment/visibility-m") );
1287
1288 #if 0
1289     if ( ! fgGetBool("/sim/presets/onground") ) {
1290         fgSetBool( "/sim/freeze/master", true );
1291         fgSetBool( "/sim/freeze/clock", true );
1292     }
1293 #endif
1294
1295     return true;
1296 }
1297
1298 /**
1299  * Built-in command: set log level (0 ... 7)
1300  */
1301 static bool
1302 do_log_level (const SGPropertyNode * arg)
1303 {
1304    sglog().setLogLevels( SG_ALL, (sgDebugPriority)arg->getIntValue() );
1305
1306    return true;
1307 }
1308
1309 /**
1310  * Built-in command: replay the FDR buffer
1311  */
1312 static bool
1313 do_replay (const SGPropertyNode * arg)
1314 {
1315     // freeze the master fdm
1316     fgSetInt( "/sim/freeze/replay-state", 1 );
1317
1318     FGReplay *r = (FGReplay *)(globals->get_subsystem( "replay" ));
1319
1320     fgSetDouble( "/sim/replay/start-time", r->get_start_time() );
1321     fgSetDouble( "/sim/replay/end-time", r->get_end_time() );
1322     double duration = fgGetDouble( "/sim/replay/duration" );
1323     if( duration && duration < (r->get_end_time() - r->get_start_time()) ) {
1324         fgSetDouble( "/sim/replay/time", r->get_end_time() - duration );
1325     } else {
1326         fgSetDouble( "/sim/replay/time", r->get_start_time() );
1327     }
1328
1329     // cout << "start = " << r->get_start_time()
1330     //      << "  end = " << r->get_end_time() << endl;
1331
1332     return true;
1333 }
1334
1335
1336 static bool
1337 do_decrease_visibility (const SGPropertyNode * arg)
1338 {
1339     double new_value = fgGetDouble("/environment/visibility-m") * 0.9;
1340     fgSetDouble("/environment/visibility-m", new_value);
1341     fgDefaultWeatherValue("visibility-m", new_value);
1342     globals->get_subsystem("environment")->reinit();
1343
1344     return true;
1345 }
1346  
1347 static bool
1348 do_increase_visibility (const SGPropertyNode * arg)
1349 {
1350     double new_value = fgGetDouble("/environment/visibility-m") * 1.1;
1351     fgSetDouble("/environment/visibility-m", new_value);
1352     fgDefaultWeatherValue("visibility-m", new_value);
1353     globals->get_subsystem("environment")->reinit();
1354
1355     return true;
1356 }
1357
1358 static bool
1359 do_hud_init(const SGPropertyNode *)
1360 {
1361     fgHUDInit(0); // minimal HUD
1362     return true;
1363 }
1364
1365 static bool
1366 do_hud_init2(const SGPropertyNode *)
1367 {
1368     fgHUDInit2(0);  // normal HUD
1369     return true;
1370 }
1371
1372
1373 /**
1374  * An fgcommand to allow loading of xml files via nasal,
1375  * the xml file's structure will be made available within
1376  * a property tree node defined under argument "targetnode",
1377  * or in the given argument tree under "data" otherwise.
1378  *
1379  * @param filename a string to hold the complete path & filename of an XML file
1380  * @param targetnode a string pointing to a location within the property tree
1381  * where to store the parsed XML file. If <targetnode> is undefined, then the
1382  * file contents are stored under a node <data> in the argument tree.
1383  */
1384
1385 static bool
1386 do_load_xml_to_proptree(const SGPropertyNode * arg)
1387 {
1388     SGPath file(arg->getStringValue("filename"));
1389     if (file.str().empty())
1390         return false;
1391
1392     if (file.extension() != "xml")
1393         file.concat(".xml");
1394
1395     if (!fgValidatePath(file.c_str(), false)) {
1396         SG_LOG(SG_IO, SG_ALERT, "loadxml: reading '" << file.str() << "' denied "
1397                 "(unauthorized access)");
1398         return false;
1399     }
1400
1401     SGPropertyNode *targetnode;
1402     if (arg->hasValue("targetnode"))
1403         targetnode = fgGetNode(arg->getStringValue("targetnode"), true);
1404     else
1405         targetnode = const_cast<SGPropertyNode *>(arg)->getNode("data", true);
1406
1407     try {
1408         readProperties(file.c_str(), targetnode, true);
1409     } catch (const sg_exception &e) {
1410         SG_LOG(SG_IO, SG_WARN, "loadxml: " << e.getFormattedMessage());
1411         return false;
1412     }
1413
1414     return true;
1415 }
1416
1417
1418 /**
1419  * An fgcommand to allow saving of xml files via nasal,
1420  * the file's structure will be determined based on what's
1421  * encountered in the passed (source) property tree node
1422  *
1423  * @param filename a string to hold the complete path & filename of the (new)
1424  * XML file
1425  * @param sourcenode a string pointing to a location within the property tree
1426  * where to find the nodes that should be written recursively into an XML file
1427  * @param data if no sourcenode is given, then the file contents are taken from
1428  * the argument tree's "data" node.
1429  */
1430
1431 static bool
1432 do_save_xml_from_proptree(const SGPropertyNode * arg)
1433 {
1434     SGPath file(arg->getStringValue("filename"));
1435     if (file.str().empty())
1436         return false;
1437
1438     if (file.extension() != "xml")
1439         file.concat(".xml");
1440
1441     if (!fgValidatePath(file.c_str(), true)) {
1442         SG_LOG(SG_IO, SG_ALERT, "savexml: writing to '" << file.str() << "' denied "
1443                 "(unauthorized access)");
1444         return false;
1445     }
1446
1447     SGPropertyNode *sourcenode;
1448     if (arg->hasValue("sourcenode"))
1449         sourcenode = fgGetNode(arg->getStringValue("sourcenode"), true);
1450     else if (arg->getNode("data", false))
1451         sourcenode = const_cast<SGPropertyNode *>(arg)->getNode("data");
1452     else
1453         return false;
1454
1455     try {
1456         writeProperties (file.c_str(), sourcenode, true);
1457     } catch (const sg_exception &e) {
1458         SG_LOG(SG_IO, SG_WARN, "savexml: " << e.getFormattedMessage());
1459         return false;
1460     }
1461
1462     return true;
1463 }
1464
1465 static bool
1466 do_press_cockpit_button (const SGPropertyNode *arg)
1467 {
1468   const char *prefix = arg->getStringValue("prefix");
1469
1470   if (arg->getBoolValue("guarded") && fgGetDouble((string(prefix) + "-guard").c_str()) < 1)
1471     return true;
1472
1473   string prop = string(prefix) + "-button";
1474   double value;
1475
1476   if (arg->getBoolValue("latching"))
1477     value = fgGetDouble(prop.c_str()) > 0 ? 0 : 1;
1478   else
1479     value = 1;
1480
1481   fgSetDouble(prop.c_str(), value);
1482   fgSetBool(arg->getStringValue("discrete"), value > 0);
1483
1484   return true;
1485 }
1486
1487 static bool
1488 do_release_cockpit_button (const SGPropertyNode *arg)
1489 {
1490   const char *prefix = arg->getStringValue("prefix");
1491
1492   if (arg->getBoolValue("guarded")) {
1493     string prop = string(prefix) + "-guard";
1494     if (fgGetDouble(prop.c_str()) < 1) {
1495       fgSetDouble(prop.c_str(), 1);
1496       return true;
1497     }
1498   }
1499
1500   if (! arg->getBoolValue("latching")) {
1501     fgSetDouble((string(prefix) + "-button").c_str(), 0);
1502     fgSetBool(arg->getStringValue("discrete"), false);
1503   }
1504
1505   return true;
1506 }
1507
1508
1509 ////////////////////////////////////////////////////////////////////////
1510 // Command setup.
1511 ////////////////////////////////////////////////////////////////////////
1512
1513
1514 /**
1515  * Table of built-in commands.
1516  *
1517  * New commands do not have to be added here; any module in the application
1518  * can add a new command using globals->get_commands()->addCommand(...).
1519  */
1520 static struct {
1521   const char * name;
1522   SGCommandMgr::command_t command;
1523 } built_ins [] = {
1524     { "null", do_null },
1525     { "nasal", do_nasal },
1526     { "exit", do_exit },
1527     { "reset", do_reset },
1528     { "reinit", do_reinit },
1529     { "suspend", do_reinit },
1530     { "resume", do_reinit },
1531     { "load", do_load },
1532     { "save", do_save },
1533     { "panel-load", do_panel_load },
1534     { "panel-mouse-click", do_panel_mouse_click },
1535     { "preferences-load", do_preferences_load },
1536     { "view-cycle", do_view_cycle },
1537     { "screen-capture", do_screen_capture },
1538     { "hires-screen-capture", do_hires_screen_capture },
1539     { "tile-cache-reload", do_tile_cache_reload },
1540     { "set-sea-level-air-temp-degc", do_set_sea_level_degc },
1541     { "set-outside-air-temp-degc", do_set_oat_degc },
1542     { "set-dewpoint-sea-level-air-temp-degc", do_set_dewpoint_sea_level_degc },
1543     { "set-dewpoint-temp-degc", do_set_dewpoint_degc },
1544     { "timeofday", do_timeofday },
1545     { "property-toggle", do_property_toggle },
1546     { "property-assign", do_property_assign },
1547     { "property-adjust", do_property_adjust },
1548     { "property-multiply", do_property_multiply },
1549     { "property-swap", do_property_swap },
1550     { "property-scale", do_property_scale },
1551     { "property-cycle", do_property_cycle },
1552     { "property-randomize", do_property_randomize },
1553     { "data-logging-commit", do_data_logging_commit },
1554     { "dialog-new", do_dialog_new },
1555     { "dialog-show", do_dialog_show },
1556     { "dialog-close", do_dialog_close },
1557     { "dialog-update", do_dialog_update },
1558     { "dialog-apply", do_dialog_apply },
1559     { "gui-redraw", do_gui_redraw },
1560     { "add-model", do_add_model },
1561     { "set-cursor", do_set_cursor },
1562     { "play-audio-sample", do_play_audio_sample },
1563     { "presets-commit", do_presets_commit },
1564     { "log-level", do_log_level },
1565     { "replay", do_replay },
1566     { "decrease-visibility", do_decrease_visibility },
1567     { "increase-visibility", do_increase_visibility },
1568     { "hud-init", do_hud_init },
1569     { "hud-init2", do_hud_init2 },
1570     { "loadxml", do_load_xml_to_proptree},
1571     { "savexml", do_save_xml_from_proptree },
1572     { "press-cockpit-button", do_press_cockpit_button },
1573     { "release-cockpit-button", do_release_cockpit_button },
1574     { "dump-scenegraph", do_dump_scene_graph },
1575     { "dump-terrainbranch", do_dump_terrain_branch },
1576     { "reload-shaders", do_reload_shaders },
1577     { 0, 0 }                    // zero-terminated
1578 };
1579
1580
1581 /**
1582  * Initialize the default built-in commands.
1583  *
1584  * Other commands may be added by other parts of the application.
1585  */
1586 void
1587 fgInitCommands ()
1588 {
1589   SG_LOG(SG_GENERAL, SG_INFO, "Initializing basic built-in commands:");
1590   for (int i = 0; built_ins[i].name != 0; i++) {
1591     SG_LOG(SG_GENERAL, SG_INFO, "  " << built_ins[i].name);
1592     globals->get_commands()->addCommand(built_ins[i].name,
1593                                         built_ins[i].command);
1594   }
1595
1596   typedef bool (*dummy)();
1597   fgTie( "/command/view/next", dummy(0), do_view_next );
1598   fgTie( "/command/view/prev", dummy(0), do_view_prev );
1599 }
1600
1601 // end of fg_commands.cxx