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