Source canvas MFD_Generic.nas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
# 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.NotificationType == notifications.PFDEventNotification.DefaultType and
notification.Device_Id == pfd_obj.device_id) {
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();
#2018.2 - manage the timer so that the nav display is only updated when visibile
me.NDCpt.onDisplay();
};
obj.offdisplay = func
{
#2018.2 - manage the timer so that the nav display is only updated when visibile
if (me.nd_initialised)
me.NDCpt.offDisplay();
};
#
# most updates performed by the canvas nav display directly.
obj.update = func
{
};
return obj;
},
};