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