Howto:Prototyping a new NavDisplay Style: Difference between revisions

From FlightGear wiki
Jump to navigation Jump to search
Line 64: Line 64:


== Issues ==
== Issues ==
* Currently, the code will re-instantiate a new NavDisplay when the style is changed - however, it makes more sense to simply set a corresponding property and close/reopen the dialog, as that would then also allow us to procedurally add style-specific GUI widgets (the Airbus style supports a handful of features that the Boeing one does not know anything about), besides this would make the whole reload button obsolete, too. {{Not done}}
* Currently, the code will re-instantiate a new NavDisplay when the style is changed - however, it makes more sense to simply set a corresponding property and close/reopen the dialog, as that would then also allow us to procedurally add style-specific GUI widgets (the Airbus style supports a handful of features that the Boeing one does not know anything about), besides this would make the whole reload button obsolete, too. {{Progressbar|60}}


== Status ==
== Status ==

Revision as of 15:29, 23 October 2016

This article is a stub. You can help the wiki by expanding it.
NavDisplay testing/prototyping environment
CanvasND development via PUI.png
Started in 10/2016
Description PUI dialog to instantiate independent ND instances using different NavDisplay styles
Contributor(s) Hooray (since 10/2016)
Status Under active development as of 10/2016

Objective

Demonstrate how a simple PUI/XML dialog can be used to easily prototype new NavDisplay styles, showing two instances for each pilot, which can be controlled/tested independently (using different settings for range, modes, traffic etc). Once the dialog is closed/reopened, the underlying navdisplay.mfd/styles files are also automatically reloaded from disk, so that you don't need to exist/restart fgfs to test your cchanges.

Gallery

Approach

This is a PUI/XML dialog with two embedded CanvasWidget areas that instantiate independent ND instances using a subset of the code commonly found in the aircraft specific ND.nas file, adapted to use the embedded Canvas region for rendering the two NDs and GUI buttons/widgets added to control the whole thing without necessarily requiring a fully developed cockpit.

This is primarily useful for rapid prototyping ("RAD"), i.e. quickly testing additions and changes without having to exit/restart fgfs and without having a full aircraft developed yet.

Goals

Primarily, this dialog is intended to help with:

  • regression testing
  • refactoring of the ND/PFD code
  • decoupling of styling related stuff and aircraft specific code (hard-coded assumptions)
  • integration with Richard's MFD framework
  • testing of independent instances
  • benchmarking/profiling and stress-testing the ND/Canvas code respectively
  • hardening the code

Roadmap

  • add buttons for controlling each ND instance (range, zoom, centering) 80}% completed
  • use io.include to directly reload the styles stuff (for rapid prototyping) (this will require changes to navdisplay.mfd which should be reviewed/tested by Gijs and Hyde) 60}% completed
  • procedurally add buttons for each switch by enhancing the myCockpit_switches hash with legends/labels for the UI use-case (use checkboxes for boolean props) Done Done
  • add a dropdown menu to select the position source (main aircraft vs. AI/MP traffic)
  • maybe allow the style to be selected/changed at runtime, possibly per ND ? 30}% completed
  • add VSD support
  • make some more settings configurable (and persistent using the userarchive attribute):
    • number of NDs to be shown
    • resolution/size of the ND/Canvas
    • default style
    • settings previously used per ND

Issues

  • Currently, the code will re-instantiate a new NavDisplay when the style is changed - however, it makes more sense to simply set a corresponding property and close/reopen the dialog, as that would then also allow us to procedurally add style-specific GUI widgets (the Airbus style supports a handful of features that the Boeing one does not know anything about), besides this would make the whole reload button obsolete, too. 60}% completed

Status

For now this is heavily based on ideas, and code, borrowed from the original MapStructure Debugger (code still available at [1])

The canvas widget is procedurally added to the dialog using the techniques discussed at Aircraft_Generation_Wizard#Under_the_Hood.

Base Package changes

Note  The following patch contains changes that move the definition of aircraft specific switches back into the navdisplay.styles file, adding GUI related fields to the hash, so that these can be used to procedurally create a UI without requiring an actual cockpit (including an optional values vector). In addition, this makes deletion of the Canvas by the ND optional, so that PUI based CanvasWidgets can reuse the ND without having to reallocate a new Canvas. Also, changes references to properly resolve to canvas.Path.
diff --git a/Nasal/canvas/api.nas b/Nasal/canvas/api.nas
index ea67217..8fd77e5 100644
--- a/Nasal/canvas/api.nas
+++ b/Nasal/canvas/api.nas
@@ -462,7 +462,7 @@ var Map = {
   },
   del: func()
   {
-    #print("canvas.Map.del()");
+    # print("canvas.Map.del()");
     if (me.controller != nil)
       me.controller.del(me);
     foreach (var k; keys(me.layers)) {
diff --git a/Nasal/canvas/map/navdisplay.mfd b/Nasal/canvas/map/navdisplay.mfd
index 89996f4..7f8f4f8 100644
--- a/Nasal/canvas/map/navdisplay.mfd
+++ b/Nasal/canvas/map/navdisplay.mfd
@@ -60,33 +60,6 @@ NDSourceDriver.new = func {
 # To get started implementing your own ND, just copy the switches hash to your
 # ND.nas file and map the keys to your cockpit properties - and things will just work.
 
-# TODO: switches are ND specific, so move to the NDStyle hash!
-
-var default_switches = {
-	'toggle_range':        {path: '/inputs/range-nm', value:40, type:'INT'},
-	'toggle_weather':      {path: '/inputs/wxr', value:0, type:'BOOL'},
-	'toggle_airports':     {path: '/inputs/arpt', value:0, type:'BOOL'},
-	'toggle_stations':     {path: '/inputs/sta', value:0, type:'BOOL'},
-	'toggle_waypoints':    {path: '/inputs/wpt', value:0, type:'BOOL'},
-	'toggle_position':     {path: '/inputs/pos', value:0, type:'BOOL'},
-	'toggle_data':         {path: '/inputs/data',value:0, type:'BOOL'},
-	'toggle_terrain':      {path: '/inputs/terr',value:0, type:'BOOL'},
-	'toggle_traffic':      {path: '/inputs/tfc',value:0, type:'BOOL'},
-	'toggle_centered':     {path: '/inputs/nd-centered',value:0, type:'BOOL'},
-	'toggle_lh_vor_adf':   {path: '/inputs/lh-vor-adf',value:0, type:'INT'},
-	'toggle_rh_vor_adf':   {path: '/inputs/rh-vor-adf',value:0, type:'INT'},
-	'toggle_display_mode': {path: '/mfd/display-mode', value:'MAP', type:'STRING'}, # valid values are: APP, MAP, PLAN or VOR
-	'toggle_display_type': {path: '/mfd/display-type', value:'CRT', type:'STRING'}, # valid values are: CRT or LCD
-	'toggle_true_north':   {path: '/mfd/true-north', value:0, type:'BOOL'},
-	'toggle_rangearc':     {path: '/mfd/rangearc', value:0, type:'BOOL'},
-	'toggle_track_heading':{path: '/trk-selected', value:0, type:'BOOL'},
-	'toggle_weather_live': {path: '/mfd/wxr-live-enabled', value: 0, type: 'BOOL'},
-	'toggle_chrono': {path: '/inputs/CHRONO', value: 0, type: 'INT'},
-	'toggle_xtrk_error': {path: '/mfd/xtrk-error', value: 0, type: 'BOOL'},
-	'toggle_trk_line': {path: '/mfd/trk-line', value: 0, type: 'BOOL'},
-	'toggle_hdg_bug_only': {path: '/mfd/hdg-bug-only', value: 0, type: 'BOOL'},
-};
-
 ##
 # TODO:
 # - introduce a MFD class (use it also for PFD/EICAS)
@@ -96,20 +69,26 @@ var NavDisplay = {
 	# static
 	id:0,
 
-	del: func {
+	del: func(destroy_canvas=1) {
 		print("Cleaning up NavDisplay");
 		# shut down all timers and other loops here
 		me.update_timer.stop();
 		foreach(var t; me.timers)
 			t.stop();
 		foreach(var l; me.listeners)
-			removelistener(l);
+			# removelistener(l);
+			call(removelistener, [l]);
 		# clean up MapStructure
 		me.map.del();
 		# call(canvas.Map.del, [], me.map);
-		# destroy the canvas
-		if (me.canvas_handle != nil)
+
+		me.nd.del(); # delete symbols (compass rose etc, needed in case we keep the underlying canvas)
+
+		# destroy the canvas (now optional, we may not always want to delete the whole canvas)
+		if (destroy_canvas and me.canvas_handle != nil) {
+			print("ND: destroying Canvas!");
 			me.canvas_handle.del();
+		}
 		me.inited = 0;
 		NavDisplay.id -= 1;
 	},
@@ -127,7 +106,7 @@ var NavDisplay = {
 	listen_switch: func(s,c) {
 		# print("event setup for: ", id(c));
 		if (!contains(me.efis_switches, s)) {
-			print('EFIS Switch not defined: '~ s);
+			print('cannot set up listener, EFIS Switch not defined in style/switches: '~ s);
 			return;
 		}
 		me.listen( me.get_full_switch_path(s), func {
@@ -178,14 +157,26 @@ var NavDisplay = {
 
 	# TODO: the ctor should allow customization, for different aircraft
 	# especially properties and SVG files/handles (747, 757, 777 etc)
-	new : func(prop1, switches=default_switches, style='Boeing') {
+	new : func(prop1, switches, style='Boeing') {
+
+		# if no custom switches specified, use default switches
+		if (switches==nil) {
+			print("ND: Using ND specific default switches");
+			switches = NDStyles[styles].default_switches;
+			}
+
 		NavDisplay.id +=1;
 		var m = { parents : [NavDisplay]};
 
-		var df_toggles = keys(default_switches);
+		m.nd_style = NDStyles[style]; # look up ND specific stuff (file names etc)
+
+		var df_toggles = keys(m.nd_style.default_switches);
+		print("ND specific default switches:", size(df_toggles));
 		foreach(var toggle_name; df_toggles){
-			if(!contains(switches, toggle_name))
-			switches[toggle_name] = default_switches[toggle_name];
+			if(!contains(switches, toggle_name)) {
+			print("Undefined ND switch, using default mapping for:", toggle_name);
+			switches[toggle_name] = m.nd_style.default_switches[toggle_name];
+			}
 		}
 		
 		m.inited = 0;
@@ -194,11 +185,11 @@ var NavDisplay = {
 		m.listeners=[]; # for cleanup handling
 		m.aircraft_source = NDSourceDriver.new(); # uses the main aircraft as the driver/source (speeds, position, heading)
 
-		m.nd_style = NDStyles[style]; # look up ND specific stuff (file names etc)
 		m.style_name = style;
 
 		m.radio_list=["instrumentation/comm/frequencies","instrumentation/comm[1]/frequencies",
 		              "instrumentation/nav/frequencies", "instrumentation/nav[1]/frequencies"];
+		# FIXME: this is redundant, must be moved to the style/Switches list
 		m.mfd_mode_list=["APP","VOR","MAP","PLAN"];
 
 		m.efis_path = prop1;
@@ -251,7 +242,7 @@ var NavDisplay = {
 		me.update_timer = maketimer(update_time, func me.update() );
 		me.nd = canvas_group;
 		me.canvas_handle = parent;
-		me.df_options = nil;
+		me.df_options = { map:{width:1024, height:1024} };
 		if (contains(me.nd_style, 'options'))
 			me.df_options = me.nd_style.options;
 		nd_options = canvas.default_hash(nd_options, me.df_options);
@@ -278,7 +269,8 @@ var NavDisplay = {
 
 		me.nd_style.initialize_elements(me);
 
-
+	
+		# var map_rect = [124, me.options.map.width, me.options.map.height, 0];
 		var map_rect = [124, 1024, 1024, 0];
 		var map_opts = me.options['map'];
 		if (map_opts == nil) map_opts = {};
@@ -582,7 +574,7 @@ var NavDisplay = {
 			me.map.setTranslation(trsl.x, trsl.y);
 		} else {
 			if(me.in_mode('toggle_display_mode', ['PLAN']))
-				me.map.setTranslation(512,512);
+				me.map.setTranslation(512,512); # FIXME use options hash to look up actual texture dimensions here
 			elsif(me.get_switch('toggle_centered'))
 				me.map.setTranslation(512,565);
 			else
diff --git a/Nasal/canvas/map/navdisplay.styles b/Nasal/canvas/map/navdisplay.styles
index c2196c8..6539d63 100644
--- a/Nasal/canvas/map/navdisplay.styles
+++ b/Nasal/canvas/map/navdisplay.styles
@@ -29,6 +29,37 @@ var NDStyles = {
 				return "LiberationFonts/LiberationSans-Regular.ttf";
 		},
 
+		# to be used for validating new/modified styles, this can be used to encode a list of switches that 
+		# should be configured by the aircraft developer, lest the ND may not work completely
+
+# switches are ND specific, TODO: use UI version with legends/labels
+
+default_switches: {
+    # symbolic alias : GUI legend/tooltip, relative property (as used in bindings), initial value, valid values (vector), property type
+    # TODO: should support a vector of valid_values() (ranges)
+    'toggle_range':         {legend:'rng', path: '/inputs/range-nm', value:40, values:[10,20,40,80,160,320], type:'INT'},
+    'toggle_weather':       {legend:'wxr', path: '/inputs/wxr', value:0, type:'BOOL'},
+    'toggle_airports':      {legend:'apt', path: '/inputs/arpt', value:0, type:'BOOL'},
+    'toggle_stations':      {legend:'sta', path: '/inputs/sta', value:0, type:'BOOL'},
+    'toggle_waypoints':     {legend:'wpt', path: '/inputs/wpt', value:0, type:'BOOL'},
+    'toggle_position':      {legend:'pos', path: '/inputs/pos', value:0, type:'BOOL'},
+    'toggle_data':          {legend:'dat', path: '/inputs/data',value:0, type:'BOOL'},
+    'toggle_terrain':       {legend:'terr', path: '/inputs/terr',value:0, type:'BOOL'},
+    'toggle_traffic':       {legend:'tfc', path: '/inputs/tfc',value:0, type:'BOOL'},
+    'toggle_centered':      {legend:'ctr', path: '/inputs/nd-centered',value:0, type:'BOOL'},
+    'toggle_lh_vor_adf':    {legend:'vor/adf (l)', path: '/inputs/lh-vor-adf',value:0, values:{'VOR':1, 'OFF':0, 'ADF':-1 }, type:'INT'},
+    'toggle_rh_vor_adf':    {legend:'vor/adf (r)', path: '/inputs/rh-vor-adf',value:0, values:{'VOR':1, 'OFF':0, 'ADF':-1 }, type:'INT'},
+    'toggle_display_mode':  {legend:'map',path: '/mfd/display-mode', value:'MAP', values:['APP', 'MAP', 'PLAN', 'VOR' ], type:'STRING'},
+    'toggle_display_type':  {legend:'lcd',path: '/mfd/display-type', value:'LCD', values:['CRT', 'LCD' ], type:'STRING'},
+    'toggle_true_north':    {legend:'tru',path: '/mfd/true-north', value:0, type:'BOOL'},
+    'toggle_rangearc':      {legend:'rng',path: '/mfd/rangearc', value:0, type:'BOOL'},
+    'toggle_track_heading': {legend:'trk',path: '/hdg-trk-selected', value:0, type:'BOOL'},
+    'toggle_hdg_bug_only':  {legend:'hdg',path: '/hdg-bug-only', value:0, type:'BOOL'},
+    # add any new switches here (and update navdisplay.styles as needed)
+ 
+}, # end of aircraft-specific default switches
+
+
 		# where all the symbols are stored
 		# TODO: SVG elements should be renamed to use boeing/airbus prefix
 		# aircraft developers should all be editing the same ND.svg image
@@ -52,7 +83,7 @@ var NDStyles = {
 		                      "HdgBugCRT2","TrkBugLCD2","HdgBugLCD2","hdgBug2","selHdgLine","selHdgLine2","curHdgPtr2",
 		                      "staArrowL","staArrowR","staToL","staFromL","staToR","staFromR"] )
 			me.symbols[element] = me.nd.getElementById(element).updateCenter();
-
+	
 		}, # initialize_elements
 
 		##
@@ -1147,6 +1178,43 @@ var NDStyles = {
 				if( family == "Liberation Sans" and weight == "normal" )
 						return "LiberationFonts/LiberationSans-Regular.ttf";
 			},
+# switches are ND specific, TODO: see actual Airbus ND.nas (artix/omega95)
+
+# switches are ND specific, TODO: use UI version with legends/labels
+# https://github.com/artix75/A320neo/blob/master/Models/Instruments/ND/canvas/ND.nas#L16
+
+default_switches: {
+		# symbolic alias : relative property (as used in bindings), initial value, type
+		'toggle_range': 	{legend: "range", path: '/inputs/range-nm', value:40, values:[10,20,40,80,160,320],type:'INT'},
+		'toggle_weather': 	{legend: "wxr", path: '/inputs/wxr', value:0, type:'BOOL'},
+		'toggle_airports': 	{legend: "apt", path: '/inputs/arpt', value:0, type:'BOOL'},
+		'toggle_ndb': 	{legend: "ndb", path: '/inputs/NDB', value:0, type:'BOOL'},
+		'toggle_stations':     {legend: "sta", path: '/inputs/sta', value:0, type:'BOOL'},
+		'toggle_vor': 	{legend:"vor", path: '/inputs/VORD', value:0, type:'BOOL'},
+		'toggle_dme': 	{legend: "dme", path: '/inputs/DME', value:0, type:'BOOL'},
+		'toggle_cstr': 	{legend: "cstr", path: '/inputs/CSTR', value:0, type:'BOOL'},
+		'toggle_waypoints': 	{legend:"wpt", path: '/inputs/wpt', value:0, type:'BOOL'},
+		'toggle_position': 	{legend:"pos", path: '/inputs/pos', value:0, type:'BOOL'},
+		'toggle_data': 		{legend:"dat", path: '/inputs/data',value:0, type:'BOOL'},
+		'toggle_terrain': 	{legend:"terr", path: '/inputs/terr',value:0, type:'BOOL'},
+		'toggle_traffic': 	{legend:"tfc",path: '/inputs/tfc',value:0, type:'BOOL'},
+		'toggle_centered': 		{legend:"ctr",path: '/inputs/nd-centered',value:0, type:'BOOL'},
+		'toggle_lh_vor_adf':	{legend:"vor/adf(l)",path: '/input/lh-vor-adf',value:0, values:{'VOR':1, 'OFF':0, 'ADF':-1 }, type:'INT'},
+		'toggle_rh_vor_adf':	{legend:"vor/adf(r)", path: '/input/rh-vor-adf',value:0,values:{'VOR':1, 'OFF':0, 'ADF':-1 }, type:'INT'},
+		# FIXME: this should be reviewed by one of the airbus developers, not sure if these values make sense for the Airbus style (will only affect the GUI dialog though)
+		'toggle_display_mode': 	{legend: "mode", path: '/nd/canvas-display-mode', value:'NAV', values:['NAV','APP','MAP','VOR','PLAN'],type:'STRING'},
+		'toggle_display_type': 	{legend: "LCD/CRT", path: '/mfd/display-type', value:'LCD', values:['LCD','CRT'],type:'STRING'},
+		'toggle_true_north': 	{legend: "tru", path: '/mfd/true-north', value:1, type:'BOOL'},
+		'toggle_track_heading': 	{legend:"trk", path: '/trk-selected', value:0, type:'BOOL'},
+		'toggle_wpt_idx': {legend:"wpt idx", path: '/inputs/plan-wpt-index', value: -1,type: 'INT'},
+		'toggle_plan_loop': {legend:"plan loop", path: '/nd/plan-mode-loop', value: 0,type: 'INT'},
+		'toggle_weather_live': {legend: "live wxr", path: '/mfd/wxr-live-enabled', value: 0, type: 'BOOL'},
+		'toggle_chrono': {legend:"chrono", path: '/inputs/CHRONO', value: 0, values:[],type: 'INT'},
+		'toggle_xtrk_error': {legend: "xte", path: '/mfd/xtrk-error', value: 0,type: 'BOOL'},
+		'toggle_trk_line': {legend:"trk line", path: '/mfd/trk-line', value: 0, type: 'BOOL'},
+		# add new switches here
+}, # end of aircraft-specific default switches
+
 
 			# where all the symbols are stored
 			# TODO: SVG elements should be renamed to use boeing/airbus prefix
@@ -1171,7 +1239,7 @@ var NDStyles = {
 		                      "HdgBugCRT2","TrkBugLCD2","HdgBugLCD2","hdgBug2","selHdgLine","selHdgLine2","curHdgPtr2",
 		                      "staArrowL","staArrowR","staToL","staFromL","staToR","staFromR"] )
 			me.symbols[element] = me.nd.getElementById(element).updateCenter();
-
+	
 		}, # initialize_elements
 
 
@@ -1530,9 +1598,9 @@ var NDStyles = {
 								var adf1_frq = getprop(me.options.adf1_frq);
 								var adf2_frq = getprop(me.options.adf2_frq);
 								if(adf1_frq == frq or adf2_frq == frq){
-									me.element.setColor(tuned_color, [Path]);
+									me.element.setColor(tuned_color, [canvas.Path]);
 								} else {
-									me.element.setColor(dfcolor, [Path]);
+									me.element.setColor(dfcolor, [canvas.Path]);
 								}
 							}
 						},

Code

Note  For testing purposes, put the following dialog into $FG_ROOT/gui/dialogs/canvas-nd.xml and use the Nasal Console to run the dialog (or extend the Menubar accordingly):
fgcommand('dialog-show', props.Node.new({'dialog-name':'canvas-nd'}) );
<?xml version="1.0"?>
<PropertyList>
  <name>canvas-nd</name>
  <modal>false</modal>
  <layout>vbox</layout>
  <text>
    <label>Canvas ND</label>
  </text>

  <widget-templates>

<nd-checkbox>
<name>checkbox-template</name>

<checkbox>
<pref-width>30</pref-width>
<pref-height>22</pref-height>

  <label>nd-checkbox</label>
  <halign>left</halign>
  <property></property>
  <live>true</live>

  <binding>
  <command>dialog-apply</command>
  </binding>
<!--
  <binding>
  <command>dialog-toggle</command>
  </binding>
-->
</checkbox>

</nd-checkbox>

    <canvas-widget>
      <name>canvas-mfd</name>

      <group>
      <layout>vbox</layout>

  <!-- style selector (property will be overridden) -->
  <select>
   <label>Style</label>
   <name>unchanged</name>
   <property>WILL BE FILLED IN PROCEDURALLY</property>

   <binding>
    <command>dialog-apply</command>
    <object-name>unchanged</object-name>
   </binding>

  </select>


      <group>
      <layout>hbox</layout>
      <name>mfd-controls</name>
      </group>
      <canvas>
		    <name></name>
		    <valign>fill</valign>
                    <halign>fill</halign>
                    <stretch>true</stretch>
		    <!-- reducing the dimensions even more will look weird due to layouting issues, given the number of hbox aligned checkbox widgets -->
                    <pref-width>400</pref-width>
                    <pref-height>400</pref-height>
		    <view>1024</view>
		    <view>1024</view>
      <nasal>      
        <load>
       </load>
      </nasal>
    </canvas>
    <!-- ND/Canvas specific controls placed at the bottom of the display (e.g. range) -->
    <group> <!-- move this down for better layout -->
    <layout>hbox</layout>
<!--
    <button>
      <legend>Cycle range</legend>
      <equal>true</equal>
    <binding>
      <command>property-cycle</command>
    </binding>
    <value>10</value>
    <value>20</value>
    <value>40</value>
    <value>80</value>
    </button>
-->
  <select>
   <label>Mode</label>
   <name>unchanged mode!!!</name>
   <property>WILL BE FILLED IN PROCEDURALLY</property>

   <binding>
      <command>dialog-apply</command>
      <object-name>unchanged mode !!!</object-name>
   </binding>   
  </select>
<!--
  <select_>
   <label>VOR/ADF(l)</label>
   <name>a</name>
   <property>/a/c</property>
      <value>VOR</value>
      <value>OFF</value>
      <value>ADF</value>

   <binding>
      <command>dialog-apply</command>
      <object-name>a</object-name>
      </binding>   
  </select_>
-->

    <select>

      <name>unchanged range</name>
      <label>nm</label>
      <property>WILL BE FILLED IN PROCEDURALLY</property>
    <binding>
      <command>dialog-apply</command>
      <object-name>unchanged range</object-name>
    </binding>   
    </select>

<!--
   <text>
    <format>Range: %s nm</format>
    <halign>left</halign>
    <property>WILL BE FILLED IN PROCEDURALLY</property>
    <live>true</live>
   </text>
-->

<!--
  <select_>
   <label>VOR/ADF(r)</label>
   <name>b</name>
   <property>/a/b</property>

      <value>VOR</value>
      <value>OFF</value>
      <value>ADF</value>

   <binding>
      <command>dialog-apply</command>
      <object-name>b</object-name>
      </binding>   
  </select_>
-->

    </group>
    </group>

    </canvas-widget>

  </widget-templates>

  <nasal>
  <open><![CDATA[
# print("Nasal/open");
var getWidgetTemplate = func(identifier) {
var target = globals.gui.findElementByName(root,  identifier );
if(target == nil) die("Target node not found for identifier:"~identifier);

return target;
}

var populateSelectWidget = func(widget, label, attribute, index, property, values)  {
# make up an identifier to be used for the object-name (fgcommands dialog-apply and -update)
# TODO: change to read ND[x].attribute
var objectName = "ND["~index~"]."~attribute; # attribute~index;

widget.getNode("label",1).setValue(label);
widget.getNode("name",1).setValue(objectName);
widget.getNode("property",1).setValue(property);
widget.getNode("binding/object-name",1).setValue(objectName);

forindex(var c; values) {
 widget.getChild("value",c,1).setValue(values[c]);
}
};

##
# store handles to each PUI/CanvasWidget canvas because cmdarg() may be invalid later on
# but we cannot simply delete the ND entirely, because that would also delete the Canvas
# so we keep a handle to the canvas region, and use that to instantiate a new ND
var canvas_handles = {};

###
# locate required templates
var target = getWidgetTemplate(root:cmdarg(), identifier:'canvas-placeholder');
var template = getWidgetTemplate(root:cmdarg(), identifier:'canvas-mfd');
var checkboxTemplate = getWidgetTemplate(root:cmdarg(), identifier:'checkbox-template');


##
## FIXME: this should be setting the style and reloading (closing/opening) the dialog
## so that custom checkboxes can be supported !
## right now it's kinda pointless to show the Airbus style, because it's not using the same switches ...
var initialize_nd = func(index) {
		   # print("running init nd");
	
		   var identifier = "nd#"~index;
		   var my_canvas = nil;

		   # determine the canvas/placement to be used	
		   if (canvas_handles[identifier]==nil) {
	           print("Storing Canvas handle");
		   my_canvas = canvas.get( cmdarg() );
		   canvas_handles[identifier] = my_canvas;
		   } else {
		   print("Retrieving stored Canvas handle");
		   my_canvas = canvas_handles[identifier];
		   }

		   # debug.dump(my_canvas);
		   # show_canvas_id(my_canvas); # this is for debugging only

		   var myND = setupND(mfd_root: "/instrumentation/efis["~index~"]", my_canvas: my_canvas, index:index);
		   registerStyleChangeHandler(nd:myND.nd, index:index, my_canvas:my_canvas);

};

var registerStyleChangeHandler = func(nd, index, my_canvas) {
		   # TODO: set up listener for styleSelector, clean up previous ND, re-run the init logic to apply the new style
		   var l = setlistener("/gui/dialogs/canvas-nd/nd["~index~"]/selected-style",func() {
			var nd =nd;
			removelistener(l);
			# don't ask, there's something really weird going on with Nasal's naSubContext handling here
			settimer(func handleStyleChange(nd,index), 0.0);
			} ); # setlistener

		    print("StyleChangeHandler registered");
} # registerStyleChangeHandler

var handleStyleChange = func(nd, index) {
			print("Style change notification for ND #", index);
			nd.del(destroy_canvas:0); # don't actually destroy the Canvas yet, we're gonna reuse it
			initialize_nd(index:index);
}; # handleStyleChange



# not currently used, but could be used for validating the mfd/styles files before using them
var errors = [];

# NOTE: this requires changes to navdisplay.mfd
# call(func[, args[, me[, locals[, error]]]);
# call(
io.include('Nasal/canvas/map/navdisplay.mfd');
#, nil, closure(initialize_nd), var errors=[]);

if (size(errors)) {
canvas.MessageBox.critical(
  "$FG_ROOT/Nasal/canvas/map/navdisplay.mfd",
  "Error reloading navdisplay.mfd and/or navdisplay.styles:\n",
  cb = nil,
  buttons = canvas.MessageBox.Ok
);
# TODO: close dialog on error
}

# debug.dump( NDStyles );
# TODO: this info could also be added to the GUI dialog
print("Number of ND Styles found:", size(keys(NDStyles)));

  # to be used for shutting down each created instance upon closing the dialog (see the close block below)
  var MFDInstances = [];

  #### 
  ## an adapted version of the setup logic found in ND.nas
  ## FIXME: switches should be moved into the styles hash, after all they are highly aircraft specific
var myCockpit_switches = {
    # symbolic alias : GUI legend/tooltip, relative property (as used in bindings), initial value, valid values (vector), property type
    # TODO: should support a vector of valid_values() (ranges)
    'toggle_range':         {legend:'rng', path: '/inputs/range-nm', value:40, values:[10,20,40,80,160,320], type:'INT'},
    'toggle_weather':       {legend:'wxr', path: '/inputs/wxr', value:0, type:'BOOL'},
    'toggle_airports':      {legend:'apt', path: '/inputs/arpt', value:0, type:'BOOL'},
    'toggle_stations':      {legend:'sta', path: '/inputs/sta', value:0, type:'BOOL'},
    'toggle_waypoints':     {legend:'wpt', path: '/inputs/wpt', value:0, type:'BOOL'},
    'toggle_position':      {legend:'pos', path: '/inputs/pos', value:0, type:'BOOL'},
    'toggle_data':          {legend:'dat', path: '/inputs/data',value:0, type:'BOOL'},
    'toggle_terrain':       {legend:'terr', path: '/inputs/terr',value:0, type:'BOOL'},
    'toggle_traffic':       {legend:'tfc', path: '/inputs/tfc',value:0, type:'BOOL'},
    'toggle_centered':      {legend:'ctr', path: '/inputs/nd-centered',value:0, type:'BOOL'},
    'toggle_lh_vor_adf':    {legend:'vor/adf (l)', path: '/inputs/lh-vor-adf',value:0, values:[1, 0, -1 ], type:'INT'},
    'toggle_rh_vor_adf':    {legend:'vor/adf (r)', path: '/inputs/rh-vor-adf',value:0, values: [1, 0, -1 ], type:'INT'},
    'toggle_display_mode':  {legend:'map',path: '/mfd/display-mode', value:'MAP', values:['APP', 'MAP', 'PLAN', 'VOR' ], type:'STRING'},
    'toggle_display_type':  {legend:'lcd',path: '/mfd/display-type', value:'LCD', values:['CRT', 'LCD' ], type:'STRING'},
    'toggle_true_north':    {legend:'tru',path: '/mfd/true-north', value:0, type:'BOOL'},
    'toggle_rangearc':      {legend:'rng',path: '/mfd/rangearc', value:0, type:'BOOL'},
    'toggle_track_heading': {legend:'trk',path: '/hdg-trk-selected', value:0, type:'BOOL'},
    'toggle_hdg_bug_only':  {legend:'hdg',path: '/hdg-bug-only', value:0, type:'BOOL'},
    # add any new switches here (and update navdisplay.mfd as needed)
      };


# http://wiki.flightgear.org/Canvas_ND_Framework#Cockpit_switches
var resolve_adf_vor_mode = func(num) {
if (num == -1) return 'ADF';
if (num == 1) return 'VOR';
return 'OFF';
}

# TODO: make style configurable via property/listener 
var setupND = func(mfd_root, my_canvas, index) {

   var style = getprop("/gui/dialogs/canvas-nd/nd["~index~"]/selected-style") or 'Boeing';
   print("Selected style for ND index #", index, " is: ", style);

   if (style == "Airbus") {
   print("NOTE: The Airbus style isnt yet well integrated, because it's using switches that are not used by the Boeing style, so this requires additional work, see the code for details");
   }

   ###
    # entry point, this will set up an ND instance

    # get a handle to the NavDisplay in canvas namespace (for now), see $FG_ROOT/Nasal/canvas/map/navdisplay.mfd
    var ND = NavDisplay;

    ##
    # set up a  new ND instance, under mfd_root and use the
    # myCockpit_switches hash to map ND specific control properties
    var myND= ND.new(mfd_root, myCockpit_switches, style);
    #print("my_canvas is:");
    #debug.dump(my_canvas);
    var group = my_canvas.createGroup();
    myND.newMFD(group, my_canvas);
    myND.update();
    # store the instance for later cleanup
    append(MFDInstances, myND);
    # print("ND setup completed");
    return {nd: myND, property_root: mfd_root};
} # setupND()

# this determines how many NDs will be added to the dialog, and where their controls live in the property tree
var canvas_areas = [
	{name: 'captain.ND', property_root:'/instrumentation/efis[0]',},
	{name: 'copilot.ND', property_root:'/instrumentation/efis[1]',},
	# you can add more entries below, for example: 
	#  {name: 'engineer.ND', property_root:'/instrumentation/efis[2]',},
];

# procedurally add one canvas for each ND to be shown (requires less code/maintenance)

var index=0;
foreach(var c; canvas_areas) {
# print("Adding Canvas widget to GUI dialog procedurally");

# next, create a new symbol named canvasWidget, create child in target, with the index specified (idx)
var canvasWidget = target.getChild("group", index, 1);

# now, copy our template stuff into the new tree 
props.copy(template.getChild("group"), canvasWidget);

# customize the subtree and override a few things
canvasWidget.getNode("text/label",1).setValue(c.name);
canvasWidget.getNode("canvas/name").setValue(c.name);

var StyleSelector = canvasWidget.getNode("select");
var style_property = "/gui/dialogs/canvas-nd/nd["~index~"]/selected-style";
populateSelectWidget(StyleSelector, "Style", "ndStyle", index, style_property, keys(NDStyles));  

# add a single line of code to each canvas/nasal section setting up the ND instance 
canvasWidget.getNode("canvas/nasal/load").setValue("initialize_nd(index:"~index~");");

var RangeSelector = canvasWidget.getNode("group[1]/select[1]");
var range_property = c.property_root ~ '/inputs/range-nm';
populateSelectWidget(RangeSelector, "nm", "ndRangeNm", index, range_property, [10,20,40,80,160,320] );  

# hook up the mode selector property
var modeSelector = canvasWidget.getNode("group[1]/select");
var mode_property = "/instrumentation/efis["~index~"]/mfd/display-mode"; #FIXME: look up correct property via the style
populateSelectWidget(modeSelector, "foo", "ndMode", index, mode_property, ['APP','PLAN','MAP','VOR']);  

var checkboxArea = getWidgetTemplate(root:canvasWidget, identifier:'mfd-controls'); 
var cb_index = 0;
# add checkboxes for each boolean switch
#  TODO: customize this for style-specific switches !
foreach(var s; keys(myCockpit_switches)) {
var switch = s;
if (myCockpit_switches[switch].type != 'BOOL') continue; # skip non boolean switches for now 

var checkbox = checkboxArea.getChild("checkbox",cb_index, 1);
props.copy(checkboxTemplate.getChild("checkbox"), checkbox);
cb_index+=1;
checkbox.getNode("label").setValue(myCockpit_switches[switch].legend);
checkbox.getNode("property").setValue(c.property_root ~ myCockpit_switches[switch].path);
var object_name = myCockpit_switches[switch].legend~cb_index;
checkbox.getNode("name",1).setValue(object_name);
checkbox.getNode("binding/object-name",1).setValue(object_name);
#checkbox.getNode("binding/command/property-toggle/object-name",1).setValue(object_name);

} # add checkboxes for each boolean ND switch

index += 1;
} # foreach ND instance

#print("Complete dialog is:");
#props.dump( target );
  ]]>
  </open>

  <close><![CDATA[
	print("nasal/closing block canvas-nd.xml");
	foreach(var mfd; MFDInstances) {
		mfd.del();
	}
  ]]>
  </close>

  </nasal>
 
    <group>
    <layout>hbox</layout>
    <name>canvas-placeholder</name>
    <!-- this will be populated dynamically when the dialog is opened -->
   </group>

<group>
<layout>hbox</layout>

<button>
       <legend>Reload</legend>

 	<enable>
        <property>/do-not-enable/reloading</property>
        </enable>


         <border>2</border>
         <binding>
             <command>reinit</command>
              <subsystem>gui</subsystem>
          </binding>
<!--
         <binding>
             <command>dialog-close</command>
              <dialog-name>canvas-nd</dialog-name>
          </binding>
          <binding>
             <command>dialog-show</command>
              <dialog-name>canvas-nd</dialog-name>
          </binding>
-->
      </button>

    <button>
      <legend>Exit</legend>
      <equal>true</equal>
      <key>Esc</key>
      <binding>
        <command>dialog-close</command>
      </binding>
    </button>
</group>

</PropertyList>

References

References