Howto:Hooking into the GUI

From FlightGear wiki
Jump to navigation Jump to search
This article is a stub. You can help the wiki by expanding it.
Screen shot showing the unmodified About dialog with procedurally-replaced <button> widgets that are at runtime mapped to a Nasal factory that instantiates CanvasWidget based puObjects for each replaced widget.
About dialog with custom canvas-driven <button> widgets

Background

Objective

Procedurally adding and replacing/re-implementing existing PUI/XML widgets in scripting space using Nasal and Canvas.

For example, the hard-coded PUI engine knows nothing about an <image> tag, but we can still introduce one using very little code.

<?xml version="1.0"?>
<PropertyList>
  <name>test</name>
  <modal>false</modal>
  <layout>vbox</layout>
  <text>
    <label>Image test</label>
  </text>
  <image>
  <filename>Textures/Splash1.png</filename>
  </image>
</PropertyList>

Status

working / proof-of-concept (03/2018)

Changes

  • 2 new fgcommands (to add/remove entries from the widget factory/map)

C++ Patches

This article or section contains out-of-date information

Please help improve this article by updating it. There may be additional information on the talk page.

The following set of patches implements a very simple fgcommand-based pre-processor scheme implemented on top of the SGPropertyNode (property tree) structure. This hooks into the dialog building engine, i.e. after the layouting engine has pre-processed all widgets.

diff --git a/src/GUI/CanvasWidget.cxx b/src/GUI/CanvasWidget.cxx
index 33f1af0..e2b1cbd 100644
--- a/src/GUI/CanvasWidget.cxx
+++ b/src/GUI/CanvasWidget.cxx
@@ -192,6 +192,12 @@ void CanvasWidget::setSize(int w, int h)
 }
 
 //------------------------------------------------------------------------------
+void CanvasWidget::update() 
+{
+
+}
+
+//------------------------------------------------------------------------------
 void CanvasWidget::draw(int dx, int dy)
 {
 
diff --git a/src/GUI/CanvasWidget.hxx b/src/GUI/CanvasWidget.hxx
index 0dd6543..3efe294 100644
--- a/src/GUI/CanvasWidget.hxx
+++ b/src/GUI/CanvasWidget.hxx
@@ -24,10 +24,13 @@
 
 #include <simgear/canvas/canvas_fwd.hxx>
 
+#include "FGPUIDialog.hxx"
+
 class CanvasMgr;
 
 class CanvasWidget:
-  public puObject
+  public puObject,
+  public FGPUIDialog::ActiveWidget
 {
   public:
     CanvasWidget( int x, int y,
@@ -42,6 +45,9 @@ class CanvasWidget:
     virtual void setSize ( int w, int h );
     virtual void draw(int dx, int dy);
 
+    // PUIDialog::ActiveWidget over-rides
+    virtual void update();
+ 
   private:
 
     CanvasMgr  *_canvas_mgr; // TODO maybe we should store this in some central
diff --git a/src/GUI/FGPUIDialog.cxx b/src/GUI/FGPUIDialog.cxx
index ffe3206..9b28e25 100644
--- a/src/GUI/FGPUIDialog.cxx
+++ b/src/GUI/FGPUIDialog.cxx
@@ -32,6 +32,24 @@ static const int FORMAT_BUFSIZE = 255;
 static const int RESIZE_MARGIN = 7;
 
 
+std::map<std::string, const SGPropertyNode*> FGPUIDialog::_customWidgetFactory;
+
+bool 
+FGPUIDialog::registerCustomWidget(const SGPropertyNode* arg, SGPropertyNode *)
+{
+  std::string name = arg->getNode("name")->getStringValue();
+  _customWidgetFactory[ name ] = arg; // change factory: NasalCommand
+  return true;
+}
+
+bool 
+FGPUIDialog::unregisterCustomWidget(const SGPropertyNode* arg, SGPropertyNode *)
+{
+  std::string name = arg->getNode("name")->getStringValue();
+  return _customWidgetFactory.erase(name); 
+}
+
+
 /**
  * Makes sure the format matches '%[ -+#]?\d*(\.\d*)?(l?[df]|s)', with
  * only one number or string placeholder and otherwise arbitrary prefix
@@ -662,14 +680,16 @@ FGPUIDialog::FGPUIDialog (SGPropertyNode *props) :
     _needsRelayout(false)
 {
     _module = string("__dlg:") + props->getStringValue("name", "[unnamed]");
-        
+
+     puObject *puObj;
+       
     SGPropertyNode *nasal = props->getNode("nasal");
     if (nasal) {
         _nasal_close = nasal->getNode("close");
         SGPropertyNode *open = nasal->getNode("open");
         if (open) {
             const char *s = open->getStringValue();
-            FGNasalSys *nas = (FGNasalSys *)globals->get_subsystem("nasal");
+            FGNasalSys *nas = globals->get_subsystem<FGNasalSys>();
             if (nas)
                 nas->createModule(_module.c_str(), _module.c_str(), s, strlen(s), props);
         }
@@ -684,7 +704,7 @@ FGPUIDialog::~FGPUIDialog ()
     _props->setIntValue("lastx", x);
     _props->setIntValue("lasty", y);
 
-    FGNasalSys *nas = (FGNasalSys *)globals->get_subsystem("nasal");
+    FGNasalSys *nas = globals->get_subsystem<FGNasalSys>();
     if (nas) {
         if (_nasal_close) {
             const char *s = _nasal_close->getStringValue();
@@ -873,7 +893,31 @@ FGPUIDialog::makeObject (SGPropertyNode *props, int parentWidth, int parentHeigh
     if (type.empty())
         type = "dialog";
 
-    if (type == "dialog") {
+    //SG_LOG(SG_GENERAL, SG_ALERT, "Checking for pseudo widget:"<<type);
+    if (_customWidgetFactory.find(type) != _customWidgetFactory.end()) {
+	SG_LOG(SG_GENERAL, SG_ALERT, "Using custom widget:" << type);
+
+	const SGPropertyNode* preprocessor = _customWidgetFactory[type];
+	copyProperties(preprocessor, props);
+
+	// mutate props by invoking the script/module portion  via copy_properties
+ 	globals->get_subsystem<FGNasalSys>()->handleCommand( props, nullptr);
+
+
+	CanvasWidget* canvasWidget = new CanvasWidget( x, y,
+                                                       x + width, y + height,
+                                                       props,
+                                                       _module );
+        setupObject(canvasWidget, props);
+	//setColor(canvasWidget, props);
+        //_activeWidgets.push_back(canvasWidget);
+        return canvasWidget;
+
+	// puObject* customWidget = new _customWidgets[type] 
+
+    // end of custom widget (Canvas replacement)
+
+    } else if (type == "dialog") {
         puPopup *obj;
         bool draggable = props->getBoolValue("draggable", true);
         bool resizable = props->getBoolValue("resizable", false);
@@ -1062,7 +1106,7 @@ FGPUIDialog::makeObject (SGPropertyNode *props, int parentWidth, int parentHeigh
             obj->setBuffer(tsync->log());
           }
         } else {
-          FGNasalSys* nasal = (FGNasalSys*) globals->get_subsystem("nasal");
+          FGNasalSys* nasal = globals->get_subsystem<FGNasalSys>();
           obj->setBuffer(nasal->log());
         }
 
diff --git a/src/GUI/FGPUIDialog.hxx b/src/GUI/FGPUIDialog.hxx
index 104760c..9da7ad0 100644
--- a/src/GUI/FGPUIDialog.hxx
+++ b/src/GUI/FGPUIDialog.hxx
@@ -12,6 +12,7 @@
 #include <simgear/props/condition.hxx>
 
 #include <vector>
+#include <map>
 
 
 // ugly temporary workaround for plib's lack of user defined class ids  FIXME
@@ -42,6 +43,9 @@ class FGPUIDialog : public FGDialog
 {
 public:
 
+    static bool registerCustomWidget(const SGPropertyNode*, SGPropertyNode*);
+    static bool unregisterCustomWidget(const SGPropertyNode*, SGPropertyNode*);
+
     /**
      * Construct a new GUI widget configured by a property tree.
      *
@@ -91,6 +95,9 @@ public:
      */
     virtual void update ();
 
+
+    
+
     /**
      * Recompute the dialog's layout
      */
@@ -125,6 +132,8 @@ private:
     puObject * makeObject (SGPropertyNode * props,
                            int parentWidth, int parentHeight);
 
+    static std::map<std::string, const SGPropertyNode*> _customWidgetFactory;
+
     // Common configuration for all GUI objects.
     void setupObject (puObject * object, SGPropertyNode * props);
 
diff --git a/src/GUI/new_gui.cxx b/src/GUI/new_gui.cxx
index 58cd8df..a2f4b37 100644
--- a/src/GUI/new_gui.cxx
+++ b/src/GUI/new_gui.cxx
@@ -55,7 +55,8 @@ using std::string;
 
 
 NewGUI::NewGUI () :
-  _active_dialog(0)
+  _active_dialog(0),
+  _cmdMgr( globals->get_commands() )
 {
 }
 
@@ -179,13 +180,31 @@ NewGUI::reset (bool reload)
 }
 
 void
+NewGUI::registerGUICommands() {
+SG_LOG(SG_GENERAL, SG_ALERT, "registering GUI related fgcommands");
+_cmdMgr->addCommand("register-widget", &FGPUIDialog::registerCustomWidget );
+_cmdMgr->addCommand("unregister-widget",&FGPUIDialog::unregisterCustomWidget );
+} 
+
+void
+NewGUI::unregisterGUICommands() {
+SG_LOG(SG_GENERAL, SG_ALERT, "removing GUI related fgcommands");
+_cmdMgr->removeCommand("register-widget");
+_cmdMgr->removeCommand("unregister-widget");
+// TODO: erase  std::map here
+} 
+
+
+void
 NewGUI::bind ()
 {
+  registerGUICommands();
 }
 
 void
 NewGUI::unbind ()
 {
+  unregisterGUICommands();
 }
 
 void
diff --git a/src/GUI/new_gui.hxx b/src/GUI/new_gui.hxx
index f89acb1..f82a71a 100644
--- a/src/GUI/new_gui.hxx
+++ b/src/GUI/new_gui.hxx
@@ -6,12 +6,15 @@
 #include <simgear/props/props.hxx>
 #include <simgear/structure/subsystem_mgr.hxx>
 #include <simgear/misc/sg_path.hxx>
+#include <simgear/structure/commands.hxx>
+
 
 #include <vector>
 #include <map>
 #include <memory> // for unique_ptr on some systems
 #include <cstring> // for strcmp in lstr() (in this header, alas)
 
+class FGNasalSys;
 class FGMenuBar;
 class FGDialog;
 class FGColor;
@@ -174,6 +177,12 @@ public:
     static const char* subsystemName() { return "gui"; }
 
 protected:
+    /**
+     * register/unregister GUI related fgcommands
+     */
+    virtual void registerGUICommands ();
+    virtual void unregisterGUICommands ();
+
 
     /**
      * Test if the menubar is visible.
@@ -204,6 +213,7 @@ protected:
     bool getMenuBarOverlapHide() const;
     void setMenuBarOverlapHide(bool hide);
 private:
+
     void createMenuBarImplementation();
     
     struct ltstr
@@ -226,6 +236,7 @@ private:
 
     std::unique_ptr<FGMenuBar> _menubar;
     FGDialog * _active_dialog;
+    SGCommandMgr* _cmdMgr;
     typedef std::map<std::string,FGDialog *> DialogDict;
     DialogDict _active_dialogs;
   
diff --git a/src/Main/fg_scene_commands.cxx b/src/Main/fg_scene_commands.cxx
index 7337a3f..8819ae5 100644
--- a/src/Main/fg_scene_commands.cxx
+++ b/src/Main/fg_scene_commands.cxx
@@ -53,7 +53,7 @@
 #include <boost/scoped_array.hpp>
 
 #if FG_HAVE_GPERFTOOLS
-# include <google/profiler.h>
+# include <gperftools/profiler.h>
 #endif
 
 #if defined(HAVE_QT)

Registering the pre-processor

Note  This works analogous to the existing addcommand() API - but it will be treated like an fgcommand based pre-processor that gets a handle to the widgets props.Node via the cmdarg() API. The preprocessor must be implemented as part of the script portion shown below. That way, the whole thing is mutable, too (i.e. it can add/remove and change nodes as necessary). In other words, the following 5 lines of code can be used to customize widgets and create entirely new ones in scripting space.
fgcommand("register-widget", props.Node.new({
 "module": "ui",
 "name": "image",
 "script": "props.dump( cmdarg() );"
}));

Testing the new tag/widget

Okay, let's create a simple PUI/XML dialog from scratch (procedurally) using the new (unsupported!) <image> tag. Use the Nasal Console to test the following:

var name = "test";
var myDialog = {};

myDialog = gui.Widget.new();
myDialog.set("name", name);
myDialog.set("layout", "vbox");


var image = myDialog.addChild("image");
image.set("name", "someImage");

var cancel = myDialog.addChild("button");
cancel.set("key", "Esc");
cancel.set("legend", "Cancel");
cancel.setBinding("dialog-close");

fgcommand("dialog-new", myDialog.prop() );
gui.showDialog(name);

Alternatively, you can also use an XML file

Implementation details

Related

References