Howto:Prototyping a new NavDisplay Style: Difference between revisions

Jump to navigation Jump to search
m
→‎Code: cleaning up things
m (→‎Code: cleaning up things)
Line 366: Line 366:
}}
}}


<syntaxhighlight lang="xml"><?xml version="1.0"?>
<syntaxhighlight lang="xml"><?xml version="1.0"?><?xml version="1.0"?>
<PropertyList>
<PropertyList>
   <name>canvas-nd</name>
   <name>canvas-nd</name>
Line 376: Line 376:


   <widget-templates>
   <widget-templates>
 
  <!--
  The following markup is processed by the dialog's nasal/open block to instantiate independent ND instances, including widgets to control them
  -->
<nd-checkbox>
<nd-checkbox>
<name>checkbox-template</name>
<name>checkbox-template</name>
Line 404: Line 406:
       <name>canvas-mfd</name>
       <name>canvas-mfd</name>


<!--
       <group>
       <group>
       <layout>vbox</layout>
       <layout>vbox</layout>
      <name>mfd-controls2</name>
<checkbox>
<pref-width>30</pref-width>
<pref-height>22</pref-height>
  <label>Test</label>
  <halign>left</halign>
  <property>/sim/none/foo</property>
</checkbox>
      </group>
-->
      <frame>
      <layout>vbox</layout>
      <padding>30</padding>
      <stretch>true</stretch>


   <!-- style selector (property will be overridden) -->
   <!-- style selector (property will be overridden) -->
   <select>
   <combo>
   <label>Style</label>
   <label>Style</label>
   <name>unchanged</name>
   <name>unchanged</name>
Line 417: Line 439:
     <object-name>unchanged</object-name>
     <object-name>unchanged</object-name>
   </binding>
   </binding>
 
<!-- This actually works but triggers a Canvas related segfault if we don't use the timer -->
    <binding>
        <command>nasal</command>
        <script>
          fgcommand("dialog-close", props.Node.new({"dialog-name": "canvas-nd"}));
          settimer(func fgcommand("dialog-show", props.Node.new({"dialog-name": "canvas-nd"})), 0);
        </script>
      </binding>


   </select>
   </combo>




      <!-- this is where our checkboxes end up TODO: should be moved somewhere else and become a vbox-->
       <group>
       <group>
       <layout>hbox</layout>
       <layout>hbox</layout>
       <name>mfd-controls</name>
       <name>mfd-controls</name>
       </group>
       </group>
       <canvas>
       <canvas>
    <name></name>
    <name></name>
    <!--
    <valign>fill</valign>
    <valign>fill</valign>
                     <halign>fill</halign>
                     <halign>fill</halign>
                     <stretch>true</stretch>
                     <stretch>false</stretch>
    -->
    <!-- reducing the dimensions even more will look weird due to layouting issues, given the number of hbox aligned checkbox widgets -->
    <!-- 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-width>400</pref-width>
Line 443: Line 478:
     <group> <!-- move this down for better layout -->
     <group> <!-- move this down for better layout -->
     <layout>hbox</layout>
     <layout>hbox</layout>
<!--
 
    <button>
<combo>
      <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>
   <label>Mode</label>
   <name>unchanged mode!!!</name>
   <name>unchanged mode!!!</name>
Line 465: Line 488:
       <object-name>unchanged mode !!!</object-name>
       <object-name>unchanged mode !!!</object-name>
   </binding>   
   </binding>   
   </select>
   </combo>
<!--
 
  <select_>
<combo>
   <label>VOR/ADF(l)</label>
   <label></label>
   <name>a</name>
   <name></name>
   <property>/a/c</property>
   <property></property>
      <value>VOR</value>
      <value>OFF</value>
      <value>ADF</value>
 
   <binding>
   <binding>
       <command>dialog-apply</command>
       <command>dialog-apply</command>
       <object-name>a</object-name>
       <object-name></object-name>
       </binding>   
       </binding>   
   </select_>
   </combo>
-->
 
    <select>


    <combo>
       <name>unchanged range</name>
       <name>unchanged range</name>
       <label>nm</label>
       <label>nm</label>
Line 491: Line 508:
       <object-name>unchanged range</object-name>
       <object-name>unchanged range</object-name>
     </binding>   
     </binding>   
     </select>
     </combo>
 
<!--
  <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>


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


     </group>
     </group>
     </group>
     </frame>


     </canvas-widget>
     </canvas-widget>


   </widget-templates>
   </widget-templates>
   <nasal>
   <nasal>
   <open><![CDATA[
   <open><![CDATA[
# print("Nasal/open");
var getWidgetTemplate = func(root, identifier) {
var getWidgetTemplate = func(identifier) {
var target = globals.gui.findElementByName(root,  identifier );
var target = globals.gui.findElementByName(root,  identifier );
if(target == nil) die("Target node not found for identifier:"~identifier);
if(target == nil) die("Target node not found for identifier:"~identifier);
Line 538: Line 537:
var populateSelectWidget = func(widget, label, attribute, index, property, values)  {
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)
# make up an identifier to be used for the object-name (fgcommands dialog-apply and -update)
# TODO: change to read ND[x].attribute
# format: ND[x].attribute
var objectName = "ND["~index~"]."~attribute; # attribute~index;
var objectName = "ND["~index~"]."~attribute; # attribute~index;


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


forindex(var c; values) {
var list = nil;
  widget.getChild("value",c,1).setValue(values[c]);
 
if (typeof(values) == 'hash') {
# we have a hash with key/value pairs
list = keys(values);
print("FIXME: key/value mapping missing for value hash map:", attribute);
# widget.getNode("property",1).setValue(property~"value-to-key");
 
}
else {
list = values;
widget.getNode("property",1).setValue(property);
}
# for vectors with values
forindex(var c; list) {
  widget.getChild("value",c,1).setValue(list[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
# locate required templates
var target = getWidgetTemplate(root:cmdarg(), identifier:'canvas-placeholder');
var WidgetTemplates = {};
var template = getWidgetTemplate(root:cmdarg(), identifier:'canvas-mfd');
var checkboxTemplate = getWidgetTemplate(root:cmdarg(), identifier:'checkbox-template');


# populate a hash with templates that we will need later on
foreach(var t; ['canvas-placeholder', 'canvas-mfd', 'checkbox-template']) {
WidgetTemplates[t]=getWidgetTemplate(root:cmdarg(), identifier: t);
#print("Dump:\n");
#props.dump( WidgetTemplates[t] );
}


##
## 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) {
var initialize_nd = func(index) {
  # print("running init nd");
  # 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() );
  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


  # FIXME: use the proper root here
  var myND = setupND(mfd_root: "/instrumentation/efis["~index~"]", my_canvas: my_canvas, index:index);
  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
# not currently used, but could be used for validating the mfd/styles files before using them
Line 618: Line 590:
# call(func[, args[, me[, locals[, error]]]);
# call(func[, args[, me[, locals[, error]]]);
# call(
# call(
# actually include the navdisplay.mfd file so that this gets reloaded whenever the dialog is closed/reopened
io.include('Nasal/canvas/map/navdisplay.mfd');
io.include('Nasal/canvas/map/navdisplay.mfd');
#, nil, closure(initialize_nd), var errors=[]);
#, nil, closure(initialize_nd), var errors=[]);
Line 631: Line 604:
}
}


# debug.dump( NDStyles );
var ND = NavDisplay;
 
# TODO: this info could also be added to the GUI dialog
# TODO: this info could also be added to the GUI dialog
print("Number of ND Styles found:", size(keys(NDStyles)));
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)
# to be used for shutting down each created instance upon closing the dialog (see the close block below)
  var MFDInstances = [];
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
# http://wiki.flightgear.org/Canvas_ND_Framework#Cockpit_switches
Line 673: Line 619:
}
}


# TODO: make style configurable via property/listener
var getSwitchesForND = func(index) {
var setupND = func(mfd_root, my_canvas, index) {
 
  var style_property = "/gui/dialogs/canvas-nd/nd["~index~"]/selected-style";
  var style = getprop(style_property);


   var style = getprop("/gui/dialogs/canvas-nd/nd["~index~"]/selected-style") or 'Boeing';
   # make sure that the style is exposed via the property tree
  print("Selected style for ND index #", index, " is: ", style);
  if (style == nil) {
style = 'Boeing'; # our default style
setprop(style_property, style);
}


   if (style == "Airbus") {
   var switches = NDStyles[style].default_switches;
   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");
   print("Using ND style/switches:", style);
  }


   ###
   if (switches == nil) print("Unknown ND style: ", style);
    # entry point, this will set up an ND instance
  return switches;


    # get a handle to the NavDisplay in canvas namespace (for now), see $FG_ROOT/Nasal/canvas/map/navdisplay.mfd
}
    var ND = NavDisplay;
 
var setupND = func(mfd_root, my_canvas, index) {
 
  var style = getprop("/gui/dialogs/canvas-nd/nd["~index~"]/selected-style") or 'Boeing';


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


# this determines how many NDs will be added to the dialog, and where their controls live in the property tree
# this determines how many NDs will be added to the dialog, and where their controls live in the property tree
# TODO: support default style per ND, different dimensions ?
var canvas_areas = [
var canvas_areas = [
{name: 'captain.ND', property_root:'/instrumentation/efis[0]',},
{name: 'captain.ND', property_root:'/instrumentation/efis[0]',},
Line 712: Line 666:
];
];


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


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


# next, create a new symbol named canvasWidget, create child in target, with the index specified (idx)
# next, create a new symbol named canvasWidget, create child in target, with the index specified (idx)
var canvasWidget = target.getChild("group", index, 1);
var canvasWidget = WidgetTemplates['canvas-placeholder'].getChild("frame", index, 1);
 
# now, copy our template stuff into the new tree  
# now, copy our template stuff into the new tree  
props.copy(template.getChild("group"), canvasWidget);
props.copy(WidgetTemplates['canvas-mfd'].getChild("frame"), canvasWidget);


# customize the subtree and override a few things
# customize the subtree and override a few things
Line 728: Line 681:
canvasWidget.getNode("canvas/name").setValue(c.name);
canvasWidget.getNode("canvas/name").setValue(c.name);


var StyleSelector = canvasWidget.getNode("select");
# instantiate and populate combo widgets
var style_property = "/gui/dialogs/canvas-nd/nd["~index~"]/selected-style";
var selectWidgets= [
populateSelectWidget(StyleSelector, "Style", "ndStyle", index, style_property, keys(NDStyles));   
{node: 'combo', label:'Style', attribute: 'Style', property:'/gui/dialogs/canvas-nd/nd['~index~']/selected-style', values:keys(NDStyles) },
{node: 'group[1]/combo[2]', label:'nm', attribute: 'RangeNm', property:c.property_root~switches['toggle_range'].path, values:switches['toggle_range'].values },
{node: 'group[1]/combo', label:'', attribute: 'ndMode', property:c.property_root~switches['toggle_display_mode'].path, values:switches['toggle_display_mode'].values },
];
 
foreach(var s; selectWidgets) {
populateSelectWidget(canvasWidget.getNode(s.node), s.label, s.attribute, index, s.property, s.values);   
}


# add a single line of code to each canvas/nasal section setting up the ND instance  
# 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~");");
canvasWidget.getNode("canvas/nasal/load").setValue("initialize_nd(index:"~index~");");


var RangeSelector = canvasWidget.getNode("group[1]/select[1]");
# --------------- This whole thing can be simplified by putting it into the previous foreach loop
var range_property = c.property_root ~ '/inputs/range-nm';
# TODO: this should be using VOR/OFF/ADF instead of the numerical values ...
populateSelectWidget(RangeSelector, "nm", "ndRangeNm", index, range_property, [10,20,40,80,160,320] ); 
var leftVORADFSelector = canvasWidget.getNode("group[1]/combo[1]");


# hook up the mode selector property
# FIXME: shouldn't hard-code this here ...
var modeSelector = canvasWidget.getNode("group[1]/select");
var keyValueMap = [1,0,-1]; # switches['toggle_lh_vor_adf'].values; # myCockpit_switches['toggle_lh_vor_adf'].values;
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']);   
# FIXME look up the proper lh/rh values here
populateSelectWidget(leftVORADFSelector, "", "VOR/ADF(l)", index, "/instrumentation/efis["~index~"]/inputs/lh-vor-adf", keyValueMap); 
var rightVORADFSelector = canvasWidget.getNode("group[1]/combo[3]");
populateSelectWidget(rightVORADFSelector, "", "VOR/ADF(r)",index, "/instrumentation/efis["~index~"]/inputs/rh-vor-adf", keyValueMap);   
# ---------------


var checkboxArea = getWidgetTemplate(root:canvasWidget, identifier:'mfd-controls');  
var checkboxArea = getWidgetTemplate(root:canvasWidget, identifier:'mfd-controls');  
Line 748: Line 712:
# add checkboxes for each boolean switch
# add checkboxes for each boolean switch
#  TODO: customize this for style-specific switches !
#  TODO: customize this for style-specific switches !
# HACK: get rid of this, it's just an alias for now
var myCockpit_switches = getSwitchesForND(index); # FIXME: should be using index instead of 0
foreach(var s; keys(myCockpit_switches)) {
foreach(var s; keys(myCockpit_switches)) {
var switch = s;
var switch = s;
Line 753: Line 720:


var checkbox = checkboxArea.getChild("checkbox",cb_index, 1);
var checkbox = checkboxArea.getChild("checkbox",cb_index, 1);
props.copy(checkboxTemplate.getChild("checkbox"), checkbox);
props.copy(WidgetTemplates['checkbox-template'].getChild("checkbox"), checkbox);
cb_index+=1;
cb_index+=1;
checkbox.getNode("label").setValue(myCockpit_switches[switch].legend);
checkbox.getNode("label").setValue(myCockpit_switches[switch].legend);
checkbox.getNode("property").setValue(c.property_root ~ myCockpit_switches[switch].path);
checkbox.getNode("property").setValue(c.property_root ~ myCockpit_switches[switch].path);
var object_name = myCockpit_switches[switch].legend~cb_index;
# FIXME: use notation ND[x].attribute.toggle
var object_name = "checkbox["~cb_index~"]("~myCockpit_switches[switch].legend~")";
checkbox.getNode("name",1).setValue(object_name);
checkbox.getNode("name",1).setValue(object_name);
checkbox.getNode("binding/object-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
} # add checkboxes for each boolean ND switch
Line 768: Line 735:


#print("Complete dialog is:");
#print("Complete dialog is:");
#props.dump( target );
#props.dump( WidgetTemplates['canvas-placeholder'] );
   ]]>
   ]]>
   </open>
   </open>


   <close><![CDATA[
   <close><![CDATA[
print("nasal/closing block canvas-nd.xml");
print("invoking nasal/close block canvas-nd.xml");
foreach(var mfd; MFDInstances) {
foreach(var mfd; keys(MFDInstances) ) {
mfd.del();
MFDInstances[mfd].del();
}
}
   ]]>
   ]]>
Line 786: Line 753:
     <name>canvas-placeholder</name>
     <name>canvas-placeholder</name>
     <!-- this will be populated dynamically when the dialog is opened -->
     <!-- this will be populated dynamically when the dialog is opened -->
  </group>
    </group>


<group>
<group>
<layout>hbox</layout>
<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>
     <button>
       <legend>Exit</legend>
       <legend>Exit</legend>
Line 827: Line 768:


</PropertyList>
</PropertyList>
</syntaxhighlight>
</syntaxhighlight>


== References ==
== References ==
{{Appendix}}
{{Appendix}}

Navigation menu