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