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