]> git.mxchange.org Git - flightgear.git/blob - src/Main/fg_commands.cxx
Added simple proof-of-concept support for running PSL scripts from
[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 #include <simgear/misc/exception.hxx>
7
8 #include STL_STRING
9 #include STL_FSTREAM
10
11 #include <simgear/debug/logstream.hxx>
12 #include <simgear/misc/commands.hxx>
13 #include <simgear/misc/props.hxx>
14 #include <simgear/sg_inlines.h>
15
16 #include <Cockpit/panel.hxx>
17 #include <Cockpit/panel_io.hxx>
18 #include <FDM/flight.hxx>
19 #include <GUI/gui.h>
20 #include <GUI/new_gui.hxx>
21 #include <Scenery/tilemgr.hxx>
22 #include <Scripting/scriptmgr.hxx>
23 #include <Time/tmp.hxx>
24
25 #include "fg_init.hxx"
26 #include "fg_commands.hxx"
27
28 SG_USING_STD(string);
29 SG_USING_STD(ifstream);
30 SG_USING_STD(ofstream);
31
32 #include "fg_props.hxx"
33 #include "fg_io.hxx"
34 #include "globals.hxx"
35 #include "viewmgr.hxx"
36
37
38 \f
39 ////////////////////////////////////////////////////////////////////////
40 // Static helper functions.
41 ////////////////////////////////////////////////////////////////////////
42
43
44 static inline SGPropertyNode *
45 get_prop (const SGPropertyNode * arg)
46 {
47     return fgGetNode(arg->getStringValue("property[0]", "/null"), true);
48 }
49
50 static inline SGPropertyNode *
51 get_prop2 (const SGPropertyNode * arg)
52 {
53     return fgGetNode(arg->getStringValue("property[1]", "/null"), true);
54 }
55
56
57 /**
58  * Get a double value and split it as required.
59  */
60 static void
61 split_value (double full_value, const char * mask,
62              double * unmodifiable, double * modifiable)
63 {
64     if (!strcmp("integer", mask)) {
65         *modifiable = (full_value < 0 ? ceil(full_value) : floor (full_value));
66         *unmodifiable = full_value - *modifiable;
67     } else if (!strcmp("decimal", mask)) {
68         *unmodifiable = (full_value < 0 ? ceil(full_value) : floor(full_value));
69         *modifiable = full_value - *unmodifiable;
70     } else {
71         if (strcmp("all", mask))
72             SG_LOG(SG_GENERAL, SG_ALERT, "Bad value " << mask << " for mask;"
73                    << " assuming 'all'");
74         *unmodifiable = 0;
75         *modifiable = full_value;
76     }
77 }
78
79
80 /**
81  * Clamp or wrap a value as specified.
82  */
83 static void
84 limit_value (double * value, const SGPropertyNode * arg)
85 {
86     const SGPropertyNode * min_node = arg->getChild("min");
87     const SGPropertyNode * max_node = arg->getChild("max");
88
89     bool wrap = arg->getBoolValue("wrap");
90
91     if (min_node == 0 || max_node == 0)
92         wrap = false;
93   
94     if (wrap) {                 // wrap such that min <= x < max
95         double min_val = min_node->getDoubleValue();
96         double max_val = max_node->getDoubleValue();
97         double resolution = arg->getDoubleValue("resolution");
98         if (resolution > 0.0) {
99             // snap to (min + N*resolution), taking special care to handle imprecision
100             int n = (int)floor((*value - min_val) / resolution + 0.5);
101             int steps = (int)floor((max_val - min_val) / resolution + 0.5);
102             SG_NORMALIZE_RANGE(n, 0, steps);
103             *value = min_val + resolution * n;
104         } else {
105             // plain circular wrapping
106             SG_NORMALIZE_RANGE(*value, min_val, max_val);
107         }
108     } else {                    // clamp such that min <= x <= max
109         if ((min_node != 0) && (*value < min_node->getDoubleValue()))
110             *value = min_node->getDoubleValue();
111         else if ((max_node != 0) && (*value > max_node->getDoubleValue()))
112             *value = max_node->getDoubleValue();
113     }
114 }
115
116
117 \f
118 ////////////////////////////////////////////////////////////////////////
119 // Command implementations.
120 ////////////////////////////////////////////////////////////////////////
121
122
123 /**
124  * Built-in command: do nothing.
125  */
126 static bool
127 do_null (const SGPropertyNode * arg)
128 {
129   return true;
130 }
131
132 /**
133  * Built-in command: run a PSL script.
134  */
135 static bool
136 do_script (const SGPropertyNode * arg)
137 {
138     FGScriptMgr * mgr = (FGScriptMgr *)globals->get_subsystem_mgr()
139         ->get_group(FGSubsystemMgr::GENERAL)->get_subsystem("scripting");
140
141     return mgr->run(arg->getStringValue("script"));
142 }
143
144
145 /**
146  * Built-in command: exit FlightGear.
147  *
148  * TODO: show a confirm dialog.
149  */
150 static bool
151 do_exit (const SGPropertyNode * arg)
152 {
153   SG_LOG(SG_INPUT, SG_ALERT, "Program exit requested.");
154   ConfirmExitDialog();
155   return true;
156 }
157
158
159 /**
160  * Built-in command: load flight.
161  *
162  * file (optional): the name of the file to load (relative to current
163  * directory).  Defaults to "fgfs.sav".
164  */
165 static bool
166 do_load (const SGPropertyNode * arg)
167 {
168   const string &file = arg->getStringValue("file", "fgfs.sav");
169   ifstream input(file.c_str());
170   if (input.good() && fgLoadFlight(input)) {
171     input.close();
172     SG_LOG(SG_INPUT, SG_INFO, "Restored flight from " << file);
173     return true;
174   } else {
175     SG_LOG(SG_INPUT, SG_ALERT, "Cannot load flight from " << file);
176     return false;
177   }
178 }
179
180
181 /**
182  * Built-in command: save flight.
183  *
184  * file (optional): the name of the file to save (relative to the
185  * current directory).  Defaults to "fgfs.sav".
186  */
187 static bool
188 do_save (const SGPropertyNode * arg)
189 {
190   const string &file = arg->getStringValue("file", "fgfs.sav");
191   bool write_all = arg->getBoolValue("write-all", false);
192   SG_LOG(SG_INPUT, SG_INFO, "Saving flight");
193   ofstream output(file.c_str());
194   if (output.good() && fgSaveFlight(output, write_all)) {
195     output.close();
196     SG_LOG(SG_INPUT, SG_INFO, "Saved flight to " << file);
197     return true;
198   } else {
199     SG_LOG(SG_INPUT, SG_ALERT, "Cannot save flight to " << file);
200     return false;
201   }
202 }
203
204
205 /**
206  * Built-in command: (re)load the panel.
207  *
208  * path (optional): the file name to load the panel from 
209  * (relative to FG_ROOT).  Defaults to the value of /sim/panel/path,
210  * and if that's unspecified, to "Panels/Default/default.xml".
211  */
212 static bool
213 do_panel_load (const SGPropertyNode * arg)
214 {
215   string panel_path =
216     arg->getStringValue("path",
217                         fgGetString("/sim/panel/path",
218                                     "Panels/Default/default.xml"));
219   FGPanel * new_panel = fgReadPanel(panel_path);
220   if (new_panel == 0) {
221     SG_LOG(SG_INPUT, SG_ALERT,
222            "Error reading new panel from " << panel_path);
223     return false;
224   }
225   SG_LOG(SG_INPUT, SG_INFO, "Loaded new panel from " << panel_path);
226   current_panel->unbind();
227   delete current_panel;
228   current_panel = new_panel;
229   current_panel->bind();
230   return true;
231 }
232
233
234 /**
235  * Built-in command: pass a mouse click to the panel.
236  *
237  * button: the mouse button number, zero-based.
238  * is-down: true if the button is down, false if it is up.
239  * x-pos: the x position of the mouse click.
240  * y-pos: the y position of the mouse click.
241  */
242 static bool
243 do_panel_mouse_click (const SGPropertyNode * arg)
244 {
245   if (current_panel != 0)
246     return current_panel
247       ->doMouseAction(arg->getIntValue("button"),
248                       arg->getBoolValue("is-down") ? PU_DOWN : PU_UP,
249                       arg->getIntValue("x-pos"),
250                       arg->getIntValue("y-pos"));
251   else
252     return false;
253 }
254
255
256 /**
257  * Built-in command: (re)load preferences.
258  *
259  * path (optional): the file name to load the panel from (relative
260  * to FG_ROOT). Defaults to "preferences.xml".
261  */
262 static bool
263 do_preferences_load (const SGPropertyNode * arg)
264 {
265   try {
266     fgLoadProps(arg->getStringValue("path", "preferences.xml"),
267                 globals->get_props());
268   } catch (const sg_exception &e) {
269     guiErrorMessage("Error reading global preferences: ", e);
270     return false;
271   }
272   SG_LOG(SG_INPUT, SG_INFO, "Successfully read global preferences.");
273   return true;
274 }
275
276
277 static void
278 fix_hud_visibility()
279 {
280   if ( !strcmp(fgGetString("/sim/flight-model"), "ada") ) {
281       globals->get_props()->setBoolValue( "/sim/hud/visibility", true );
282       if ( globals->get_viewmgr()->get_current() == 1 ) {
283           globals->get_props()->setBoolValue( "/sim/hud/visibility", false );
284       }
285   }
286 }
287
288 static void
289 do_view_next( bool )
290 {
291     globals->get_current_view()->setHeadingOffset_deg(0.0);
292     globals->get_viewmgr()->next_view();
293     fix_hud_visibility();
294     globals->get_tile_mgr()->refresh_view_timestamps();
295 }
296
297 static void
298 do_view_prev( bool )
299 {
300     globals->get_current_view()->setHeadingOffset_deg(0.0);
301     globals->get_viewmgr()->prev_view();
302     fix_hud_visibility();
303     globals->get_tile_mgr()->refresh_view_timestamps();
304 }
305
306 /**
307  * Built-in command: cycle view.
308  */
309 static bool
310 do_view_cycle (const SGPropertyNode * arg)
311 {
312   globals->get_current_view()->setHeadingOffset_deg(0.0);
313   globals->get_viewmgr()->next_view();
314   fix_hud_visibility();
315   globals->get_tile_mgr()->refresh_view_timestamps();
316 //   fgReshape(fgGetInt("/sim/startup/xsize"), fgGetInt("/sim/startup/ysize"));
317   return true;
318 }
319
320 /**
321  * Built-in command: capture screen.
322  */
323 static bool
324 do_screen_capture (const SGPropertyNode * arg)
325 {
326   fgDumpSnapShot();
327   return true;
328 }
329
330
331 /**
332  * Reload the tile cache.
333  */
334 static bool
335 do_tile_cache_reload (const SGPropertyNode * arg)
336 {
337     static const SGPropertyNode *master_freeze
338         = fgGetNode("/sim/freeze/master");
339     bool freeze = master_freeze->getBoolValue();
340     SG_LOG(SG_INPUT, SG_INFO, "ReIniting TileCache");
341     if ( !freeze ) {
342         fgSetBool("/sim/freeze/master", true);
343     }
344     // BusyCursor(0);
345     if ( globals->get_tile_mgr()->init() ) {
346         // Load the local scenery data
347         double visibility_meters = fgGetDouble("/environment/visibility-m");
348         globals->get_tile_mgr()->update( visibility_meters );
349     } else {
350         SG_LOG( SG_GENERAL, SG_ALERT, 
351                 "Error in Tile Manager initialization!" );
352         exit(-1);
353     }
354     // BusyCursor(1);
355     if ( !freeze ) {
356         fgSetBool("/sim/freeze/master", false);
357     }
358     return true;
359 }
360
361
362 /**
363  * Update the lighting manually.
364  */
365 static bool
366 do_lighting_update (const SGPropertyNode * arg)
367 {
368   fgUpdateSkyAndLightingParams();
369   return true;
370 }
371
372
373 /**
374  * Built-in command: toggle a bool property value.
375  *
376  * property: The name of the property to toggle.
377  */
378 static bool
379 do_property_toggle (const SGPropertyNode * arg)
380 {
381   SGPropertyNode * prop = get_prop(arg);
382   return prop->setBoolValue(!prop->getBoolValue());
383 }
384
385
386 /**
387  * Built-in command: assign a value to a property.
388  *
389  * property: the name of the property to assign.
390  * value: the value to assign; or
391  * property[1]: the property to copy from.
392  */
393 static bool
394 do_property_assign (const SGPropertyNode * arg)
395 {
396   SGPropertyNode * prop = get_prop(arg);
397   const SGPropertyNode * prop2 = get_prop2(arg);
398   const SGPropertyNode * value = arg->getNode("value");
399
400   if (value != 0)
401       return prop->setUnspecifiedValue(value->getStringValue());
402   else if (prop2)
403       return prop->setUnspecifiedValue(prop2->getStringValue());
404   else
405       return false;
406 }
407
408
409 /**
410  * Built-in command: increment or decrement a property value.
411  *
412  * If the 'step' argument is present, it will be used; otherwise,
413  * the command uses 'offset' and 'factor', usually from the mouse.
414  *
415  * property: the name of the property to increment or decrement.
416  * step: the amount of the increment or decrement (default: 0).
417  * offset: offset from the current setting (used for the mouse; multiplied 
418  *         by factor)
419  * factor: scaling amount for the offset (defaults to 1).
420  * min: the minimum allowed value (default: no minimum).
421  * max: the maximum allowed value (default: no maximum).
422  * mask: 'integer' to apply only to the left of the decimal point, 
423  *       'decimal' to apply only to the right of the decimal point,
424  *       or 'all' to apply to the whole number (the default).
425  * wrap: true if the value should be wrapped when it passes min or max;
426  *       both min and max must be present for this to work (default:
427  *       false).
428  */
429 static bool
430 do_property_adjust (const SGPropertyNode * arg)
431 {
432   SGPropertyNode * prop = get_prop(arg);
433
434   double amount = 0;
435   if (arg->hasValue("step"))
436       amount = arg->getDoubleValue("step");
437   else
438       amount = (arg->getDoubleValue("factor", 1)
439                 * arg->getDoubleValue("offset"));
440           
441   double unmodifiable, modifiable;
442   split_value(prop->getDoubleValue(), arg->getStringValue("mask", "all"),
443               &unmodifiable, &modifiable);
444   modifiable += amount;
445   limit_value(&modifiable, arg);
446
447   prop->setDoubleValue(unmodifiable + modifiable);
448
449   return true;
450 }
451
452
453 /**
454  * Built-in command: multiply a property value.
455  *
456  * property: the name of the property to multiply.
457  * factor: the amount by which to multiply.
458  * min: the minimum allowed value (default: no minimum).
459  * max: the maximum allowed value (default: no maximum).
460  * mask: 'integer' to apply only to the left of the decimal point, 
461  *       'decimal' to apply only to the right of the decimal point,
462  *       or 'all' to apply to the whole number (the default).
463  * wrap: true if the value should be wrapped when it passes min or max;
464  *       both min and max must be present for this to work (default:
465  *       false).
466  */
467 static bool
468 do_property_multiply (const SGPropertyNode * arg)
469 {
470   SGPropertyNode * prop = get_prop(arg);
471   double factor = arg->getDoubleValue("factor", 1);
472
473   double unmodifiable, modifiable;
474   split_value(prop->getDoubleValue(), arg->getStringValue("mask", "all"),
475               &unmodifiable, &modifiable);
476   modifiable *= factor;
477   limit_value(&modifiable, arg);
478
479   prop->setDoubleValue(unmodifiable + modifiable);
480
481   return true;
482 }
483
484
485 /**
486  * Built-in command: swap two property values.
487  *
488  * property[0]: the name of the first property.
489  * property[1]: the name of the second property.
490  */
491 static bool
492 do_property_swap (const SGPropertyNode * arg)
493 {
494   SGPropertyNode * prop1 = get_prop(arg);
495   SGPropertyNode * prop2 = get_prop2(arg);
496
497                                 // FIXME: inefficient
498   const string & tmp = prop1->getStringValue();
499   return (prop1->setUnspecifiedValue(prop2->getStringValue()) &&
500           prop2->setUnspecifiedValue(tmp.c_str()));
501 }
502
503
504 /**
505  * Built-in command: Set a property to an axis or other moving input.
506  *
507  * property: the name of the property to set.
508  * setting: the current input setting, usually between -1.0 and 1.0.
509  * offset: the offset to shift by, before applying the factor.
510  * factor: the factor to multiply by (use negative to reverse).
511  */
512 static bool
513 do_property_scale (const SGPropertyNode * arg)
514 {
515   SGPropertyNode * prop = get_prop(arg);
516   double setting = arg->getDoubleValue("setting");
517   double offset = arg->getDoubleValue("offset", 0.0);
518   double factor = arg->getDoubleValue("factor", 1.0);
519   bool squared = arg->getBoolValue("squared", false);
520
521   if (squared)
522     setting = (setting < 0 ? -1 : 1) * setting * setting;
523
524   return prop->setDoubleValue((setting + offset) * factor);
525 }
526
527
528 /**
529  * Built-in command: Show an XML-configured dialog.
530  *
531  * dialog-name: the name of the GUI dialog to display.
532  */
533 static bool
534 do_dialog_show (const SGPropertyNode * arg)
535 {
536     NewGUI * gui = (NewGUI *)globals->get_subsystem_mgr()
537         ->get_group(FGSubsystemMgr::INIT)->get_subsystem("gui");
538     gui->display(arg->getStringValue("dialog-name"));
539     return true;
540 }
541
542
543 /**
544  * Built-in Command: Hide the active XML-configured dialog.
545  */
546 static bool
547 do_dialog_close (const SGPropertyNode * arg)
548 {
549     NewGUI * gui = (NewGUI *)globals->get_subsystem_mgr()
550         ->get_group(FGSubsystemMgr::INIT)->get_subsystem("gui");
551     GUIWidget * widget = gui->getCurrentWidget();
552     if (widget != 0) {
553         delete widget;
554         gui->setCurrentWidget(0);
555         return true;
556     } else {
557         return false;
558     }
559 }
560
561
562 /**
563  * Update a value in the active XML-configured dialog.
564  *
565  * object-name: The name of the GUI object(s) (all GUI objects if omitted).
566  */
567 static bool
568 do_dialog_update (const SGPropertyNode * arg)
569 {
570     NewGUI * gui = (NewGUI *)globals->get_subsystem_mgr()
571         ->get_group(FGSubsystemMgr::INIT)->get_subsystem("gui");
572     GUIWidget * widget = gui->getCurrentWidget();
573     if (widget != 0) {
574         if (arg->hasValue("object-name")) {
575             gui->getCurrentWidget()
576                 ->updateValue(arg->getStringValue("object-name"));
577         } else {
578             gui->getCurrentWidget()->updateValues();
579         }
580         return true;
581     } else {
582         return false;
583     }
584 }
585
586
587 /**
588  * Apply a value in the active XML-configured dialog.
589  *
590  * object-name: The name of the GUI object(s) (all GUI objects if omitted).
591  */
592 static bool
593 do_dialog_apply (const SGPropertyNode * arg)
594 {
595     NewGUI * gui = (NewGUI *)globals->get_subsystem_mgr()
596         ->get_group(FGSubsystemMgr::INIT)->get_subsystem("gui");
597     GUIWidget * widget = gui->getCurrentWidget();
598     if (widget != 0) {
599         if (arg->hasValue("object-name")) {
600             gui->getCurrentWidget()
601                 ->applyValue(arg->getStringValue("object-name"));
602         } else {
603             gui->getCurrentWidget()->applyValues();
604         }
605         return true;
606     } else {
607         return false;
608     }
609 }
610
611
612 /**
613  * Built-in command: commit presets (read from in /sim/presets/)
614  */
615 static bool
616 do_presets_commit (const SGPropertyNode * arg)
617 {
618     // unbind the current fdm state so property changes
619     // don't get lost when we subsequently delete this fdm
620     // and create a new one.
621     cur_fdm_state->unbind();
622         
623     // set position from presets
624     fgInitPosition();
625
626     // BusyCursor(0);
627     fgReInitSubsystems();
628
629     globals->get_tile_mgr()->update( fgGetDouble("/environment/visibility-m") );
630
631     if ( ! fgGetBool("/sim/presets/onground") ) {
632         fgSetBool( "/sim/freeze/master", true );
633         fgSetBool( "/sim/freeze/clock", true );
634     }
635
636     return true;
637 }
638
639
640 \f
641 ////////////////////////////////////////////////////////////////////////
642 // Command setup.
643 ////////////////////////////////////////////////////////////////////////
644
645
646 /**
647  * Table of built-in commands.
648  *
649  * New commands do not have to be added here; any module in the application
650  * can add a new command using globals->get_commands()->addCommand(...).
651  */
652 static struct {
653   const char * name;
654   SGCommandMgr::command_t command;
655 } built_ins [] = {
656     { "null", do_null },
657     { "script", do_script },
658     { "exit", do_exit },
659     { "load", do_load },
660     { "save", do_save },
661     { "panel-load", do_panel_load },
662     { "panel-mouse-click", do_panel_mouse_click },
663     { "preferences-load", do_preferences_load },
664     { "view-cycle", do_view_cycle },
665     { "screen-capture", do_screen_capture },
666     { "tile-cache-reload", do_tile_cache_reload },
667     { "lighting-update", do_lighting_update },
668     { "property-toggle", do_property_toggle },
669     { "property-assign", do_property_assign },
670     { "property-adjust", do_property_adjust },
671     { "property-multiply", do_property_multiply },
672     { "property-swap", do_property_swap },
673     { "property-scale", do_property_scale },
674     { "dialog-show", do_dialog_show },
675     { "dialog-close", do_dialog_close },
676     { "dialog-show", do_dialog_show },
677     { "dialog-update", do_dialog_update },
678     { "dialog-apply", do_dialog_apply },
679     { "presets-commit", do_presets_commit },
680     { 0, 0 }                    // zero-terminated
681 };
682
683
684 /**
685  * Initialize the default built-in commands.
686  *
687  * Other commands may be added by other parts of the application.
688  */
689 void
690 fgInitCommands ()
691 {
692   SG_LOG(SG_GENERAL, SG_INFO, "Initializing basic built-in commands:");
693   for (int i = 0; built_ins[i].name != 0; i++) {
694     SG_LOG(SG_GENERAL, SG_INFO, "  " << built_ins[i].name);
695     globals->get_commands()->addCommand(built_ins[i].name,
696                                         built_ins[i].command);
697   }
698
699   typedef bool (*dummy)();
700   fgTie( "/command/view/next", dummy(0), do_view_next );
701   fgTie( "/command/view/prev", dummy(0), do_view_prev );
702 }
703
704 // end of fg_commands.cxx