+/**
+ * Built-in command: cycle a property through a set of values.
+ *
+ * If the current value isn't in the list, the cycle will
+ * (re)start from the beginning.
+ *
+ * property: the name of the property to cycle.
+ * value[*]: the list of values to cycle through.
+ */
+static bool
+do_property_cycle (const SGPropertyNode * arg)
+{
+ SGPropertyNode * prop = get_prop(arg);
+ vector<SGPropertyNode_ptr> values = arg->getChildren("value");
+ int selection = -1;
+ int nSelections = values.size();
+
+ if (nSelections < 1) {
+ SG_LOG(SG_GENERAL, SG_ALERT, "No values for property-cycle");
+ return false;
+ }
+
+ // Try to find the current selection
+ for (int i = 0; i < nSelections; i++) {
+ if (compare_values(prop, values[i])) {
+ selection = i + 1;
+ break;
+ }
+ }
+
+ // Default or wrap to the first selection
+ if (selection < 0 || selection >= nSelections)
+ selection = 0;
+
+ prop->setUnspecifiedValue(values[selection]->getStringValue());
+ return true;
+}
+
+
+/**
+ * Built-in command: randomize a numeric property value.
+ *
+ * property: the name of the property value to randomize.
+ * min: the minimum allowed value.
+ * max: the maximum allowed value.
+ */
+static bool
+do_property_randomize (const SGPropertyNode * arg)
+{
+ SGPropertyNode * prop = get_prop(arg);
+ double min = arg->getDoubleValue("min", DBL_MIN);
+ double max = arg->getDoubleValue("max", DBL_MAX);
+ prop->setDoubleValue(sg_random() * (max - min) + min);
+ return true;
+}
+
+
+/**
+ * Built-in command: reinit the data logging system based on the
+ * current contents of the /logger tree.
+ */
+static bool
+do_data_logging_commit (const SGPropertyNode * arg)
+{
+ FGLogger *log = (FGLogger *)globals->get_subsystem("logger");
+ log->reinit();
+ return true;
+}
+
+/**
+ * Built-in command: Add a dialog to the GUI system. Does *not*
+ * display the dialog. The property node should have the same format
+ * as a dialog XML configuration. It must include:
+ *
+ * name: the name of the GUI dialog for future reference.
+ */
+static bool
+do_dialog_new (const SGPropertyNode * arg)
+{
+ NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
+
+ // Note the casting away of const: this is *real*. Doing a
+ // "dialog-apply" command later on will mutate this property node.
+ // I'm not convinced that this isn't the Right Thing though; it
+ // allows client to create a node, pass it to dialog-new, and get
+ // the values back from the dialog by reading the same node.
+ // Perhaps command arguments are not as "const" as they would
+ // seem?
+ gui->newDialog((SGPropertyNode*)arg);
+ return true;
+}
+
+/**
+ * Built-in command: Show an XML-configured dialog.
+ *
+ * dialog-name: the name of the GUI dialog to display.
+ */
+static bool
+do_dialog_show (const SGPropertyNode * arg)
+{
+ NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
+ gui->showDialog(arg->getStringValue("dialog-name"));
+ return true;
+}
+
+
+/**
+ * Built-in Command: Hide the active XML-configured dialog.
+ */
+static bool
+do_dialog_close (const SGPropertyNode * arg)
+{
+ NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
+ if(arg->hasValue("dialog-name"))
+ return gui->closeDialog(arg->getStringValue("dialog-name"));
+ return gui->closeActiveDialog();
+}
+
+
+/**
+ * Update a value in the active XML-configured dialog.
+ *
+ * object-name: The name of the GUI object(s) (all GUI objects if omitted).
+ */
+static bool
+do_dialog_update (const SGPropertyNode * arg)
+{
+ NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
+ FGDialog * dialog;
+ if (arg->hasValue("dialog-name"))
+ dialog = gui->getDialog(arg->getStringValue("dialog-name"));
+ else
+ dialog = gui->getActiveDialog();
+
+ if (dialog != 0) {
+ dialog->updateValues(arg->getStringValue("object-name"));
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+/**
+ * Apply a value in the active XML-configured dialog.
+ *
+ * object-name: The name of the GUI object(s) (all GUI objects if omitted).
+ */
+static bool
+do_dialog_apply (const SGPropertyNode * arg)
+{
+ NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
+ FGDialog * dialog;
+ if (arg->hasValue("dialog-name"))
+ dialog = gui->getDialog(arg->getStringValue("dialog-name"));
+ else
+ dialog = gui->getActiveDialog();
+
+ if (dialog != 0) {
+ dialog->applyValues(arg->getStringValue("object-name"));
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+/**
+ * Redraw GUI (applying new widget colors). Doesn't reload the dialogs,
+ * unlike reinit().
+ */
+static bool
+do_gui_redraw (const SGPropertyNode * arg)
+{
+ NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
+ gui->redraw();
+ return true;
+}
+
+/**
+ * Built-in command: play an audio message (i.e. a wav file) This is
+ * fire and forget. Call this once per message and it will get dumped
+ * into a queue. Messages are played sequentially so they do not
+ * overlap.
+ */
+static bool
+do_play_audio_message (const SGPropertyNode * arg)
+{
+ FGFX *fx = (FGFX *)globals->get_subsystem("fx");
+ string path = arg->getStringValue("path");
+ string file = arg->getStringValue("file");
+ double volume = arg->getDoubleValue("volume");
+ // cout << "playing " << path << " / " << file << endl;
+ fx->play_message( path, file, volume );
+
+ return true;
+}
+
+/**
+ * Built-in command: commit presets (read from in /sim/presets/)
+ */
+static bool
+do_presets_commit (const SGPropertyNode * arg)
+{
+ // unbind the current fdm state so property changes
+ // don't get lost when we subsequently delete this fdm
+ // and create a new one.
+ cur_fdm_state->unbind();
+
+ // set position from presets
+ fgInitPosition();
+
+ fgReInitSubsystems();
+
+ globals->get_tile_mgr()->update( fgGetDouble("/environment/visibility-m") );
+
+#if 0
+ if ( ! fgGetBool("/sim/presets/onground") ) {
+ fgSetBool( "/sim/freeze/master", true );
+ fgSetBool( "/sim/freeze/clock", true );
+ }
+#endif
+
+ return true;
+}
+
+/**
+ * Built-in command: set log level (0 ... 7)
+ */
+static bool
+do_log_level (const SGPropertyNode * arg)
+{
+ sglog().setLogLevels( SG_ALL, (sgDebugPriority)arg->getIntValue() );
+
+ return true;
+}
+
+/**
+ * Built-in command: replay the FDR buffer
+ */
+static bool
+do_replay (const SGPropertyNode * arg)
+{
+ // freeze the master fdm
+ fgSetInt( "/sim/freeze/replay-state", 1 );
+
+ FGReplay *r = (FGReplay *)(globals->get_subsystem( "replay" ));
+
+ fgSetDouble( "/sim/replay/start-time", r->get_start_time() );
+ fgSetDouble( "/sim/replay/end-time", r->get_end_time() );
+ double duration = fgGetDouble( "/sim/replay/duration" );
+ if( duration && duration < (r->get_end_time() - r->get_start_time()) ) {
+ fgSetDouble( "/sim/replay/time", r->get_end_time() - duration );
+ } else {
+ fgSetDouble( "/sim/replay/time", r->get_start_time() );
+ }
+
+ // cout << "start = " << r->get_start_time()
+ // << " end = " << r->get_end_time() << endl;
+
+ return true;
+}
+
+
+/**
+ * Return terrain elevation for given longitude/latitude pair.
+ */
+static bool
+do_terrain_elevation (const SGPropertyNode * arg)
+{
+ double lon = arg->getDoubleValue("longitude-deg", 0.0);
+ double lat = arg->getDoubleValue("latitude-deg", 0.0);
+ double elev;
+ bool ret = globals->get_scenery()->get_elevation_m(lat, lon, 10000.0, elev, 0, false);
+ const_cast<SGPropertyNode *>(arg)->setDoubleValue("elevation-m", elev);
+ return ret;
+}
+
+
+static bool
+do_decrease_visibility (const SGPropertyNode * arg)
+{
+ double new_value = fgGetDouble("/environment/visibility-m") * 0.9;
+ fgSetDouble("/environment/visibility-m", new_value);
+ fgDefaultWeatherValue("visibility-m", new_value);
+ globals->get_subsystem("environment")->reinit();
+
+ return true;
+}
+
+static bool
+do_increase_visibility (const SGPropertyNode * arg)
+{
+ double new_value = fgGetDouble("/environment/visibility-m") * 1.1;
+ fgSetDouble("/environment/visibility-m", new_value);
+ fgDefaultWeatherValue("visibility-m", new_value);
+ globals->get_subsystem("environment")->reinit();
+
+ return true;
+}
+
+static bool
+do_hud_init(const SGPropertyNode *)
+{
+ fgHUDInit(0); // minimal HUD
+ return true;
+}
+
+static bool
+do_hud_init2(const SGPropertyNode *)
+{
+ fgHUDInit2(0); // normal HUD
+ return true;
+}
+
+/**
+ * An fgcommand to allow loading of xml files via nasal,
+ * the xml file's structure will be made available within
+ * a (definable) property tree node
+ *
+ * @param filename a string to hold the complete path & filename of a XML file
+ * @param targetnode a string pointing to a location within the property tree
+ * where to store the parsed XML file
+ */
+
+static bool
+do_load_xml_to_proptree(const SGPropertyNode * node)
+{
+ // SG_LOG(SG_GENERAL, SG_ALERT, "fgcommand loadxml executed");
+
+ SGPropertyNode * targetnode;
+ targetnode = fgGetNode(node->getNode("targetnode")->getStringValue(),true);
+
+ const char *filename = node->getNode("filename")->getStringValue();
+ try {
+ fgLoadProps(filename, targetnode);
+ } catch (const sg_exception &e) {
+ string errmsg = "Error reading file " + string(filename) + ":\n";
+ guiErrorMessage(errmsg.c_str(), e);
+ return false;
+ }
+
+ return true;
+}
+
+
+/**
+ * an fgcommand to allow saving of xml files via nasal,
+ * the file's structure will be determined based on what's
+ * encountered in the passed (source) property tree node
+ *
+ * @param filename a string to hold the complete path & filename of the (new)
+ * XML file
+ * @param sourcenode a string pointing to a location within the property tree
+ * where to find the nodes that should be written recursively into an XML file
+ *
+ * TODO:
+ * deal with already existing filenames, optionally return error/success
+ * values in a separate node to provide status information
+ *
+ * note: it's directly using writeProperties, which is not necessarily
+ * preferable, but for now it should work ...
+ */
+
+static bool
+do_save_xml_from_proptree(const SGPropertyNode * node)
+{
+ //TODO: do Parameter validation !
+ SGPropertyNode * sourcenode;
+ sourcenode = fgGetNode(node->getNode("sourcenode")->getStringValue(),true);
+
+ const char *filename = node->getNode("filename")->getStringValue();
+ try {
+ writeProperties (filename, sourcenode, true);
+ } catch (const sg_exception &e) {
+ string errmsg = "Error writing file " + string(filename) + ":\n";
+ guiErrorMessage(errmsg.c_str(), e);
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+do_press_cockpit_button (const SGPropertyNode *arg)
+{
+ const char *prefix = arg->getStringValue("prefix");
+
+ if (arg->getBoolValue("guarded") && fgGetDouble((string(prefix) + "-guard").c_str()) < 1)
+ return true;
+
+ string prop = string(prefix) + "-button";
+ double value;
+
+ if (arg->getBoolValue("latching"))
+ value = fgGetDouble(prop.c_str()) > 0 ? 0 : 1;
+ else
+ value = 1;
+
+ fgSetDouble(prop.c_str(), value);
+ fgSetBool(arg->getStringValue("discrete"), value > 0);
+
+ return true;
+}
+
+static bool
+do_release_cockpit_button (const SGPropertyNode *arg)
+{
+ const char *prefix = arg->getStringValue("prefix");
+
+ if (arg->getBoolValue("guarded")) {
+ string prop = string(prefix) + "-guard";
+ if (fgGetDouble(prop.c_str()) < 1) {
+ fgSetDouble(prop.c_str(), 1);
+ return true;
+ }
+ }
+
+ if (! arg->getBoolValue("latching")) {
+ fgSetDouble((string(prefix) + "-button").c_str(), 0);
+ fgSetBool(arg->getStringValue("discrete"), false);
+ }
+
+ return true;
+}
+
+
+////////////////////////////////////////////////////////////////////////
+// Command setup.
+////////////////////////////////////////////////////////////////////////
+
+