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