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