443 lines
16 KiB
Text
443 lines
16 KiB
Text
# Generic Page switching cockpit display device.
|
|
# ---------------------------
|
|
# Richard Harrison: 2015-10-17 : rjh@zaretto.com
|
|
# ---------------------------
|
|
# I'm calling this a PFD as in Programmable Function Display.
|
|
# ---------------------------
|
|
# documentation: see http://wiki.flightgear.org/Canvas_MFD_Framework
|
|
# See FGAddon/Aircraft/F-15/Nasal/MPCD/MPCD_main.nas for an example usage
|
|
# ---------------------------
|
|
# This is but a straightforwards wrapper to provide the core logic that page switching displays require.
|
|
# Examples of Page switching displays
|
|
# * MFD
|
|
# * PFD
|
|
# * FMCS
|
|
# * F-15 MPCD
|
|
|
|
#
|
|
# Menu Item. There is a list of these for each page changing button per display page
|
|
# Parameters:
|
|
# menu_id : page change event id for this menu item. e.g. button number
|
|
# title : Title Text (for display on the device)
|
|
# page : Instance of page usually returned from PFD.addPage
|
|
# callbackfn : Function to call when menu item is selected
|
|
# displayfn : Function to call when the menu item is displayed. Used to enable
|
|
# highlighting of menu items, for example.
|
|
|
|
var PFD_MenuItem =
|
|
{
|
|
new : func (menu_id, title, page, callbackfn=nil, displayfn=nil)
|
|
{
|
|
var obj = {parents : [PFD_MenuItem] };
|
|
obj.page = page;
|
|
obj.menu_id = menu_id;
|
|
obj.callbackfn = callbackfn;
|
|
obj.displayfn = displayfn;
|
|
obj.title = title;
|
|
return obj;
|
|
},
|
|
};
|
|
|
|
#
|
|
#
|
|
# Create a new PFD Page
|
|
# - related svg
|
|
# - Title: Page title
|
|
# - SVG element for the page
|
|
# - Device to attach the page to
|
|
|
|
var PFD_Page =
|
|
{
|
|
new : func (svg, title, layer_id, device)
|
|
{
|
|
var obj = {parents : [PFD_Page] };
|
|
obj.title = title;
|
|
obj.device = device;
|
|
obj.layer_id = layer_id;
|
|
obj.menus = [];
|
|
obj.svg = svg.getElementById(layer_id);
|
|
if(obj.svg == nil)
|
|
printf("PFD_Device: Error loading %s: svg layer %s ",title, layer_id);
|
|
|
|
return obj;
|
|
},
|
|
|
|
#
|
|
# Makes a page visible.
|
|
# It is the responsibility of the caller to manage the visibility of pages - i.e. to
|
|
# make a page that is currenty visible not visible before making a new page visible,
|
|
# however more than one page could be visible - but only one set of menu buttons can be active
|
|
# so if two pages are visible (e.g. an overlay) then when the overlay removed it would be necessary
|
|
# to call setVisible on the base page to ensure that the menus are setup
|
|
setVisible : func(vis)
|
|
{
|
|
if(me.svg != nil)
|
|
me.svg.setVisible(vis);
|
|
|
|
if (vis)
|
|
me.ondisplay();
|
|
else
|
|
me.offdisplay();
|
|
},
|
|
|
|
# Standard callback for buttons, causing the appropriate page to be displayed
|
|
std_callbackfn : func (device, me, mi)
|
|
{
|
|
device.selectPage(mi.page);
|
|
},
|
|
|
|
# Standard display function for buttons, displaying the text and making visible
|
|
std_displayfn : func(svg_element, menuitem)
|
|
{
|
|
svg_element.setText(menuitem.title);
|
|
svg_element.setVisible(1);
|
|
#me.buttons[mi.menu_id].setText(mi.title);
|
|
#me.buttons[mi.menu_id].setVisible(1);
|
|
},
|
|
|
|
#
|
|
# Perform action when button is pushed
|
|
notifyButton : func(button_id)
|
|
{ foreach(var mi; me.menus)
|
|
{
|
|
if (mi.menu_id == button_id)
|
|
{
|
|
if (mi.callbackfn != nil) mi.callbackfn(me.device, me, mi);
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
#
|
|
# Add an item to a menu
|
|
# Params:
|
|
# menu button id (that is set in controls/PFD/button-pressed by the model)
|
|
# title of the menu for the label
|
|
# page that will be selected when pressed
|
|
#
|
|
# The corresponding menu for the selected page will automatically be loaded
|
|
addMenuItem : func(menu_id, title, page, callbackfn=nil, displayfn=nil)
|
|
{
|
|
if (callbackfn == nil) callbackfn = me.std_callbackfn;
|
|
if (displayfn == nil) displayfn = me.std_displayfn;
|
|
var nm = PFD_MenuItem.new(menu_id, title, page, callbackfn, displayfn);
|
|
append(me.menus, nm);
|
|
return nm;
|
|
},
|
|
|
|
#
|
|
# Clear all items from the menu. Use-case is where they may be a hierarchy
|
|
# of menus within the same page.
|
|
#
|
|
clearMenu : func()
|
|
{
|
|
me.menus = [];
|
|
},
|
|
|
|
# base method for update; this can be overridden per page instance to provide update of the
|
|
# elements on display (e.g. to display updated properties)
|
|
update : func(notification=nil)
|
|
{
|
|
},
|
|
|
|
#
|
|
# notify the page that it is being displayed. use to load any static framework or perform one
|
|
# time initialisation
|
|
ondisplay : func
|
|
{
|
|
},
|
|
|
|
#
|
|
# notify the page that it is going off display; use to clean up any created elements or perform
|
|
# any other required functions
|
|
offdisplay : func
|
|
{
|
|
},
|
|
};
|
|
|
|
|
|
#
|
|
# Container device for pages.
|
|
var PFD_Device =
|
|
{
|
|
# - svg is the page elements from the svg.
|
|
# - num_menu_buttons is the Number of menu buttons; starting from the bottom left then right, then top, then left.
|
|
# - button prefix (e.g MI_) is the prefix of the labels in the SVG for the menu boxes.
|
|
# - _canvas is the canvas group.
|
|
# - designation (optional) is used for Emesary designation
|
|
#NOTE:
|
|
# This does not actually create the canvas elements, or parse the SVG, that would typically be done in
|
|
# a higher level class that contains an instance of this class.
|
|
# see: http://wiki.flightgear.org/Canvas_MFD_Framework
|
|
new : func(svg, num_menu_buttons, button_prefix, _canvas, designation="MFD")
|
|
{
|
|
var obj = {parents : [PFD_Device] };
|
|
obj.svg = svg;
|
|
obj.canvas = _canvas;
|
|
obj.current_page = nil;
|
|
obj.pages = [];
|
|
obj.page_index = {};
|
|
obj.buttons = setsize([], num_menu_buttons);
|
|
obj.transmitter = nil;
|
|
|
|
# change after creation if required
|
|
obj.device_id = 1;
|
|
obj.designation = designation;
|
|
|
|
for(var idx = 0; idx < num_menu_buttons; idx += 1)
|
|
{
|
|
var label_name = sprintf(button_prefix~"%d",idx);
|
|
var msvg = obj.svg.getElementById(label_name);
|
|
if (msvg == nil)
|
|
printf("PFD_Device: Failed to load %s",label_name);
|
|
else
|
|
{
|
|
obj.buttons[idx] = msvg;
|
|
obj.buttons[idx].setText(sprintf("M%d",idx));
|
|
}
|
|
}
|
|
obj.Recipient = nil;
|
|
return obj;
|
|
},
|
|
#
|
|
# instead of using the direct call method this allows the use of Emesary (via a specified or default global transmitter)
|
|
# example to notify that a softkey has been used. The "1" in the line below is the device ID
|
|
# var notification = notifications.PFDEventNotification.new(me.designation, me.DeviceId, notifications.PFDEventNotification.SoftKeyPushed, me.mpcd_button_pushed);
|
|
# emesary.GlobalTransmitter.NotifyAll(notification);
|
|
# - currently supported is
|
|
# 1. setting menu text directly (after page has been loaded)
|
|
# notifications.PFDEventNotification.new(me.designation, 1, notifications.PFDEventNotification.ChangeMenuText, [{ Id: 1, Text: "NNN"}]);
|
|
# 2. SoftKey selection.
|
|
#
|
|
# the device ID must match this device ID (to allow for multiple devices).
|
|
RegisterWithEmesary : func(transmitter = nil){
|
|
if (transmitter == nil)
|
|
transmitter = emesary.GlobalTransmitter;
|
|
|
|
if (me.Recipient == nil){
|
|
me.Recipient = emesary.Recipient.new("PFD_"~me.designation);
|
|
var pfd_obj = me;
|
|
me.Recipient.Receive = func(notification)
|
|
{
|
|
if (notification.Device_Id == pfd_obj.device_id
|
|
and notification.NotificationType == notifications.PFDEventNotification.DefaultType) {
|
|
if (notification.Event_Id == notifications.PFDEventNotification.SoftKeyPushed
|
|
and notification.EventParameter != nil)
|
|
{
|
|
#printf("Button pressed " ~ notification.EventParameter);
|
|
pfd_obj.notifyButton(notification.EventParameter);
|
|
}
|
|
else if (notification.Event_Id == notifications.PFDEventNotification.ChangeMenuText
|
|
and notification.EventParameter != nil)
|
|
{
|
|
foreach(var eventMenu; notification.EventParameter) {
|
|
#printf("Menu Text changed : " ~ eventMenu.Text);
|
|
foreach (var mi ; pfd_obj.current_page.menus) {
|
|
if (pfd_obj.buttons[eventMenu.Id] != nil) {
|
|
pfd_obj.buttons[eventMenu.Id].setText(eventMenu.Text);
|
|
}
|
|
else
|
|
printf("PFD_device: Menu for button not found. Menu ID '%s'",mi.menu_id);
|
|
}
|
|
}
|
|
}
|
|
return emesary.Transmitter.ReceiptStatus_OK;
|
|
}
|
|
return emesary.Transmitter.ReceiptStatus_NotProcessed;
|
|
};
|
|
transmitter.Register(me.Recipient);
|
|
me.transmitter = transmitter;
|
|
}
|
|
},
|
|
DeRegisterWithEmesary : func(transmitter = nil){
|
|
# remove registration from transmitter; but keep the recipient once it is created.
|
|
if (me.transmitter != nil)
|
|
me.transmitter.DeRegister(me.Recipient);
|
|
me.transmitter = nil;
|
|
},
|
|
#
|
|
# called when a button is pushed - connecting the property to this method is implemented in the outer class
|
|
notifyButton : func(button_id)
|
|
{
|
|
#
|
|
# by convention the buttons we have are 0 based; however externally 0 is used
|
|
# to indicate no button pushed.
|
|
if (button_id > 0)
|
|
{
|
|
button_id = button_id - 1;
|
|
if (me.current_page != nil)
|
|
{
|
|
me.current_page.notifyButton(button_id);
|
|
}
|
|
else
|
|
printf("PFD_Device: Could not locate page for button ",button_id);
|
|
}
|
|
},
|
|
#
|
|
#
|
|
# add a page to the device.
|
|
# - page title.
|
|
# - svg element id
|
|
addPage : func(title, layer_id)
|
|
{
|
|
var np = PFD_Page.new(me.svg, title, layer_id, me);
|
|
append(me.pages, np);
|
|
me.page_index[layer_id] = np;
|
|
np.setVisible(0);
|
|
return np;
|
|
},
|
|
#
|
|
# Get a named page
|
|
#
|
|
getPage : func(title)
|
|
{
|
|
foreach(var p; me.pages) {
|
|
if (p.title == title) return p;
|
|
}
|
|
|
|
return nil;
|
|
},
|
|
#
|
|
# manage the update of the currently selected page
|
|
update : func(notification=nil)
|
|
{
|
|
if (me.current_page != nil)
|
|
me.current_page.update(notification);
|
|
},
|
|
#
|
|
# Change to display the selected page.
|
|
# - the page object method controls the visibility
|
|
selectPage : func(p)
|
|
{
|
|
if (me.current_page == p) return;
|
|
|
|
if (me.current_page != nil)
|
|
me.current_page.setVisible(0);
|
|
if (me.buttons != nil)
|
|
{
|
|
foreach(var mb ; me.buttons)
|
|
if (mb != nil)
|
|
mb.setVisible(0);
|
|
|
|
foreach(var mi ; p.menus)
|
|
{
|
|
if (me.buttons[mi.menu_id] != nil)
|
|
{
|
|
mi.displayfn(me.buttons[mi.menu_id], mi);
|
|
}
|
|
else
|
|
printf("PFD_device: Menu for button not found. Menu ID '%s'",mi.menu_id);
|
|
}
|
|
}
|
|
p.setVisible(1);
|
|
me.current_page = p;
|
|
},
|
|
|
|
# Return the current selected page.
|
|
getCurrentPage : func()
|
|
{
|
|
return me.current_page;
|
|
},
|
|
|
|
#
|
|
# ensure that the menus are display correctly for the current page.
|
|
updateMenus : func
|
|
{
|
|
foreach(var mb ; me.buttons)
|
|
if (mb != nil)
|
|
mb.setVisible(0);
|
|
|
|
if (me.current_page == nil) return;
|
|
|
|
foreach(var mi ; me.current_page.menus)
|
|
{
|
|
if (me.buttons[mi.menu_id] != nil)
|
|
{
|
|
mi.displayfn(me.buttons[mi.menu_id], mi);
|
|
}
|
|
else
|
|
printf("No corresponding item '%s'",mi.menu_id);
|
|
}
|
|
},
|
|
};
|
|
|
|
var PFD_NavDisplay =
|
|
{
|
|
#
|
|
# Instantiate parameters:
|
|
# 1. pfd_device (instance of PFD_Device)
|
|
# 2. instrument display ident (e.g. mfd-map, or mfd-map-left mfd-map-right for multiple displays)
|
|
# (this is used to map to the property tree)
|
|
# 3. layer_id: main layer in the SVG
|
|
# 4. nd_group_ident : group (usually within the main layer) to place the NavDisplay
|
|
# 5. switches - used to connect the property tree to the nav display. see the canvas nav display
|
|
# documentation
|
|
new : func (pfd_device, title, instrument_ident, layer_id, nd_group_ident, switches=nil, map_style="Boeing")
|
|
{
|
|
var obj = pfd_device.addPage(title, layer_id);
|
|
|
|
# if no switches given then use a default set.
|
|
if (switches != nil)
|
|
obj.switches = switches;
|
|
else
|
|
obj.switches = {
|
|
'toggle_range': { path: '/inputs/range-nm', value: 50, type: 'INT' },
|
|
'toggle_weather': { path: '/inputs/wxr', value: 0, type: 'BOOL' },
|
|
'toggle_airports': { path: '/inputs/arpt', value: 1, type: 'BOOL' },
|
|
'toggle_stations': { path: '/inputs/sta', value: 1, type: 'BOOL' },
|
|
'toggle_waypoints': { path: '/inputs/wpt', value: 1, 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: 1, type: 'BOOL' },
|
|
'toggle_centered': { path: '/inputs/nd-centered', value: 1, type: 'BOOL' },
|
|
'toggle_lh_vor_adf': { path: '/inputs/lh-vor-adf', value: 1, type: 'INT' },
|
|
'toggle_rh_vor_adf': { path: '/inputs/rh-vor-adf', value: 1, type: 'INT' },
|
|
'toggle_display_mode': { path: '/mfd/display-mode', value: 'MAP', type: 'STRING' },
|
|
'toggle_display_type': { path: '/mfd/display-type', value: 'LCD', type: 'STRING' },
|
|
'toggle_true_north': { path: '/mfd/true-north', value: 0, type: 'BOOL' },
|
|
'toggle_rangearc': { path: '/mfd/rangearc', value: 0, type: 'BOOL' },
|
|
'toggle_track_heading': { path: '/hdg-trk-selected', value: 1, type: 'BOOL' },
|
|
};
|
|
|
|
obj.nd_initialised = 0;
|
|
obj.nd_placeholder_ident = nd_group_ident;
|
|
obj.nd_ident = instrument_ident;
|
|
obj.pfd_device = pfd_device;
|
|
|
|
obj.nd_init = func
|
|
{
|
|
me.ND = canvas.NavDisplay;
|
|
if (!me.nd_initialised)
|
|
{
|
|
me.nd_initialised = 1;
|
|
|
|
me.NDCpt = me.ND.new("instrumentation/"~me.nd_ident, me.switches,map_style);
|
|
|
|
me.group = me.pfd_device.svg.getElementById(me.nd_placeholder_ident);
|
|
me.group.setScale(0.39,0.45);
|
|
me.group.setTranslation(45,0);
|
|
me.NDCpt.newMFD(me.group, pfd_device.canvas);
|
|
}
|
|
me.NDCpt.update();
|
|
};
|
|
#
|
|
# Method overrides
|
|
#-----------------------------------------------
|
|
# Called when the page goes on display - need to delay initialization of the NavDisplay until later (it fails
|
|
# if done too early).
|
|
# NOTE: This causes a display "wobble" the first time on display as resizing happens. I've seen similar things
|
|
# happen on real avionics (when switched on) so it's not necessarily unrealistic -)
|
|
obj.ondisplay = func
|
|
{
|
|
if (!me.nd_initialised)
|
|
me.nd_init();
|
|
};
|
|
#
|
|
# most updates performed by the canvas nav display directly.
|
|
obj.update = func
|
|
{
|
|
};
|
|
return obj;
|
|
},
|
|
};
|