diff --git a/Aircraft/Instruments-3d/FG1000/MFDPages/ActiveFlightPlanNarrow.svg b/Aircraft/Instruments-3d/FG1000/MFDPages/ActiveFlightPlanNarrow.svg new file mode 100644 index 000000000..9fd9057dc --- /dev/null +++ b/Aircraft/Instruments-3d/FG1000/MFDPages/ActiveFlightPlanNarrow.svg @@ -0,0 +1,1295 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + DTK + DTK + LEGID + ALT + LEGID + LEGID + LEGID + ALT + DTK + DTK + LEGID + + + Zoom nm + + NORTH UP + + + ACTIVE FLIGHT PLAN + + + + SELECTED WAYPOINT WEATHER + + + Press the "FPL" key to view the previous page + + + CURRENT VNV PROFILE + + NO FLIGHT PLAN ACTIVE + DTK + DIS + ALT + + LEGID + DTK + DIS + ALT + + LEGID + LEGID + DTK + DTK + ALT + LEGID + + + LEGID + DTK + DTK + ALT + LEGID + + LEGID + + LEGID + DTK + DTK + ALT + + DTK + DTK + + DTK + ALT + LEGID + + LEGID + DTK + DTK + ALT + LEGID + + LEGID + DTK + DTK + ALT + LEGID + + LEGID + DTK + DTK + ALT + LEGID + + LEGID + DTK + ALT + LEGID + ACT VNV WPT + VS TGT + VS REQ + _____ft + at + ____ + ____fpm + FPA + ____ + ____fpm + V DEV + ____ft + TOD + __:__ + + + diff --git a/Aircraft/Instruments-3d/FG1000/MFDPages/Surround.svg b/Aircraft/Instruments-3d/FG1000/MFDPages/Surround.svg index a01e3c8cb..3fbfa9f5f 100644 --- a/Aircraft/Instruments-3d/FG1000/MFDPages/Surround.svg +++ b/Aircraft/Instruments-3d/FG1000/MFDPages/Surround.svg @@ -15,7 +15,7 @@ version="1.1" id="SVGRoot" inkscape:version="0.91 r13725" - sodipodi:docname="MFD.svg"> + sodipodi:docname="Surround.svg"> + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + IDENT, FACILITY, CITY + + MAP + + LOCATION + KSFO + SAN FRANCISCO INTERNATIONAL + CITY + REGION + K + S + F + O + WAYPOINT INFORMATION + BRG + 189 + 12.3 + DIS + + Zoom nm + + NORTH UP + ________ + ________ + Press "ENT" to accept + + + + + + REGION + + + + REGION + REGION + REGION + REGION + + + + + + diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/GenericFMSPublisher.nas b/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/GenericFMSPublisher.nas index 5d55e5429..22f789d85 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/GenericFMSPublisher.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/GenericFMSPublisher.nas @@ -15,6 +15,7 @@ # along with FlightGear. If not, see . # # FMS Driver using Emesary to publish data from the inbuilt FMS properties + var GenericFMSPublisher = { @@ -23,6 +24,7 @@ var GenericFMSPublisher = parents : [ GenericFMSPublisher, ], + _running : 0, }; # We have two publishers here: @@ -119,12 +121,88 @@ var GenericFMSPublisher = start : func() { me._triggeredPublisher.start(); me._periodicPublisher.start(); + me._running = 1; }, stop : func() { me._triggeredPublisher.stop(); me._periodicPublisher.stop(); + me._running = 0; }, + isRunning : func() { + return me._running; + }, }; + +# FMS Delegate which will be triggered by the underlying route manager +var FMSDataDelegate = { + new : func(fp) { + var obj = { + parents : [FMSDataDelegate], + flightplan : fp, + }; + return obj; + }, + currentWaypointChanged : func (fp) { + var notification = notifications.PFDEventNotification.new( + "MFD", + 1, + notifications.PFDEventNotification.FMSData, + {"FMSFlightPlanSequenced" : fp.current}); + emesary.GlobalTransmitter.NotifyAll(notification); + }, + departureChanged : func (fp) { + var notification = notifications.PFDEventNotification.new( + "MFD", + 1, + notifications.PFDEventNotification.FMSData, + {"FMSFlightPlanEdited" : 1}); + emesary.GlobalTransmitter.NotifyAll(notification); + }, + arrivalChanged : func (fp) { + var notification = notifications.PFDEventNotification.new( + "MFD", + 1, + notifications.PFDEventNotification.FMSData, + {"FMSFlightPlanEdited" : 1}); + emesary.GlobalTransmitter.NotifyAll(notification); + }, + waypointsChanged : func (fp) { + var notification = notifications.PFDEventNotification.new( + "MFD", + 1, + notifications.PFDEventNotification.FMSData, + {"FMSFlightPlanEdited" : 1}); + emesary.GlobalTransmitter.NotifyAll(notification); + }, + activated : func (fp) { + var notification = notifications.PFDEventNotification.new( + "MFD", + 1, + notifications.PFDEventNotification.FMSData, + {"FMSFlightPlanEdited" : 1}); + emesary.GlobalTransmitter.NotifyAll(notification); + }, + cleared : func (fp) { + me.sendFMSNotification({"FMSFlightPlanEdited" : 1}); + var notification = notifications.PFDEventNotification.new( + "MFD", + 1, + notifications.PFDEventNotification.FMSData, + {"FMSFlightPlanEdited" : 1}); + }, + endOfFlightPlan: func (fp) { + var notification = notifications.PFDEventNotification.new( + "MFD", + 1, + notifications.PFDEventNotification.FMSData, + {"FMSFlightPlanFinished" : 1}); + emesary.GlobalTransmitter.NotifyAll(notification); + }, +}; + +var dd = FMSDataDelegate.new(flightplan()); + +registerFlightPlanDelegate(FMSDataDelegate.new); diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/GenericInterfaceController.nas b/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/GenericInterfaceController.nas index 870442dbf..239481c93 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/GenericInterfaceController.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/GenericInterfaceController.nas @@ -27,7 +27,6 @@ io.load_nasal(nasal_dir ~ 'Interfaces/GenericFMSPublisher.nas', "fg1000"); io.load_nasal(nasal_dir ~ 'Interfaces/GenericFMSUpdater.nas', "fg1000"); io.load_nasal(nasal_dir ~ 'Interfaces/GenericADCPublisher.nas', "fg1000"); - var GenericInterfaceController = { _instance : nil, @@ -54,6 +53,7 @@ var GenericInterfaceController = { obj.gpsPublisher = fg1000.GenericFMSPublisher.new(); obj.gpsUpdater = fg1000.GenericFMSUpdater.new(); obj.adcPublisher = fg1000.GenericADCPublisher.new(); + return obj; }, diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/NavDataInterface.nas b/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/NavDataInterface.nas index 886b07418..2b66c1c3e 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/NavDataInterface.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/NavDataInterface.nas @@ -20,6 +20,14 @@ var NavDataInterface = { +# Valid FMS Modes + +FMS_MODES : { + direct : 1, + leg : 1, + obs : 1, +}, + new : func () { var obj = { parents : [ NavDataInterface ] }; @@ -142,6 +150,47 @@ getFlightplan : func () return flightplan(); }, +insertWaypoint : func (data) +{ + assert(data["index"] != nil, "InsertWaypoint message with no index parameter"); + assert(data["wp"] != nil, "InsertWaypoint message with no wp parameter"); + + var wp = data["wp"]; + var idx = int(data["index"]); + + # Simple data verification that we have the parameters we need + assert(idx != nil, "InsertWaypoint index parameter does not contain an integer index"); + assert(wp.id != nil, "InsertWaypoint wp parameter does not contain an id"); + assert(wp.lat != nil, "InsertWaypoint wp parameter does not contain a lat"); + assert(wp.lon != nil, "InsertWaypoint wp parameter does not contain an lon"); + + var newwp = createWP(wp.lat, wp.lon, wp.id); + + var fp = flightplan(); + fp.insertWP(newwp, idx); + + # Set a suitable name. + if ((fp.id == nil) or (fp.id == "default-flightplan")) { + var from = "????"; + var dest = "????"; + + if ((fp.getWP(0) != nil) and (fp.getWP(0).wp_name != nil)) { + from = fp.getWP(0).wp_name; + } + + if ((fp.getWP(fp.getPlanSize() -1) != nil) and (fp.getWP(fp.getPlanSize() -1).wp_name != nil)) { + dest = fp.getWP(fp.getPlanSize() -1).wp_name; + } + + if (fp.departure != nil) from = fp.departure.id; + if (fp.destination != nil) dest = fp.destination.id; + fp.id == from ~ " / " ~ dest; + } + + # Activate flightplan + if (fp.getPlanSize() == 2) fgcommand("activate-flightplan", props.Node.new({"activate": 1})); +}, + # Retrieve the Airway waypoints on the current leg. getAirwayWaypoints : func() { var fp = flightplan(); @@ -220,6 +269,15 @@ setDirectTo : func(param) setprop("/instrumentation/gps/command", "direct"); }, +setFMSMode : func(mode) { + if (NavDataInterface.FMS_MODES[mode] != nil) { + # mode is valid, so simply set it as the GPS command + setprop("/instrumentation/gps/command", mode); + } else { + die("Invalid FMS Mode " ~ mode); + } +}, + # Return the current DTO location to use getCurrentDTO : func() { @@ -301,6 +359,15 @@ RegisterWithEmesary : func() controller.setDefaultDTO(notification.EventParameter.Value); return emesary.Transmitter.ReceiptStatus_Finished; } + if (id == "SetFMSMode") { + controller.setFMSMode(notification.EventParameter.Value); + return emesary.Transmitter.ReceiptStatus_Finished; + } + if (id == "InsertWaypoint") { + controller.insertWaypoint(notification.EventParameter.Value); + return emesary.Transmitter.ReceiptStatus_Finished; + } + } return emesary.Transmitter.ReceiptStatus_NotProcessed; }; diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFD.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFD.nas index c00e14807..6a450234a 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/MFD.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFD.nas @@ -38,8 +38,8 @@ var MFDPages = [ "XMRadio", "XMInfo", "SystemStatus", - "ActiveFlightPlanWide", "ActiveFlightPlanNarrow", + "ActiveFlightPlanWide", "FlightPlanCatalog", "StoredFlightPlan", "Checklist1", @@ -54,6 +54,7 @@ var MFDPages = [ "NearestUserWPT", "NearestFrequencies", "NearestAirspaces", + "WaypointEntry", "DirectTo", "Surround", ]; @@ -117,6 +118,11 @@ var MFDDisplay = obj._DTO = fg1000.DirectTo.new(obj, myCanvas, obj._MFDDevice, obj._svg); obj._DTO.getController().RegisterWithEmesary(); + # Next, the WaypointEntry "Page" so that it too receives any Emesary notifications + # _before_ the actual page. + obj._WaypointEntry = fg1000.WaypointEntry.new(obj, myCanvas, obj._MFDDevice, obj._svg); + obj._WaypointEntry.getController().RegisterWithEmesary(); + obj._MFDDevice.RegisterWithEmesary(); # Surround dynamic elements @@ -139,7 +145,7 @@ var MFDDisplay = # Now load the other pages normally; foreach (var page; MFDPages) { - if ((page != "NavigationMap") and (page != "EIS") and (page != "DirectTo")) { + if ((page != "NavigationMap") and (page != "EIS") and (page != "DirectTo") and (page != "WaypointEntry")) { #var code = "obj.Surround.addPage(\"" ~ page ~ "\", fg1000." ~ page ~ ".new(obj, myCanvas, obj._MFDDevice, obj._svg));"; var code = "obj.addPage(\"" ~ page ~ "\", fg1000." ~ page ~ ".new(obj, myCanvas, obj._MFDDevice, obj._svg));"; var addPageFn = compile(code); diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPage.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPage.nas index c3af81dad..557cf1611 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPage.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPage.nas @@ -129,6 +129,37 @@ setTextElement : func(symbolName, value) { sym.setValue(value); }, +setTextElementLat : func(symbolName, value) { + var degrees_part = int(value); + + if (degrees_part == nil) { + me.setTextElement(symbolName, "_ __°__.__'"); + } else { + var minutes_part = 100.0 * (value - degrees_part); + if (value < 0.0) { + me.setTextElement(symbolName, sprintf("S %2d°%.2f'", -degrees_part, -minutes_part)); + } else { + me.setTextElement(symbolName, sprintf("N %2d°%.2f'", degrees_part, minutes_part)); + } + } +}, + +setTextElementLon : func(symbolName, value) { + var degrees_part = int(value); + + if (degrees_part == nil) { + me.setTextElement(symbolName, "____°__.__'"); + } else { + var minutes_part = 100.0 * (value - degrees_part); + if (value < 0.0) { + me.setTextElement(symbolName, sprintf("W%3d°%.2f'", -degrees_part, -minutes_part)); + } else { + me.setTextElement(symbolName, sprintf("E%3d°%.2f'", degrees_part, minutes_part)); + } + } +}, + + # Function to undo any colors set by display_toggle when loading a new menu resetMenuColors : func() { for(var i = 0; i < 12; i +=1) { @@ -159,6 +190,6 @@ getSVG : func() { }, getGroup : func() { return me._group; -} +}, }; diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPageController.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPageController.nas index 92a014128..4d9497f2c 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPageController.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPageController.nas @@ -30,10 +30,6 @@ new : func (page) obj._page = page; obj._transmitter = emesary.GlobalTransmitter; obj._registered = 0; - - # DirectTo controls - obj._directTo = 0; - return obj; }, @@ -70,9 +66,19 @@ handleComFreqTransferHold : func (value) { return me.page.mfd.SurroundController handleComVol : func (value) { return me.page.mfd.SurroundController.handleComVol(value); }, handleComVolToggle : func (value) { return me.page.mfd.SurroundController.handleComVolToggle(value); }, -# DTO button brings up the DirectTo Page. handleDTO : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; }, -handleFPL : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; }, + + +handleFPL : func (value) { + var fppage = me._page.getMFD().getPage("ActiveFlightPlanNarrow"); + if (fppage != nil) { + me._page.getDevice().selectPage(fppage); + return emesary.Transmitter.ReceiptStatus_Finished; + } else { + return emesary.Transmitter.ReceiptStatus_NotProcessed; + } +}, + handleClear : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; }, # Holding the Clear button goes straight to the Navigation Map page. @@ -203,7 +209,7 @@ setDefaultDTOWayPoint : func(id) }, getDeviceID : func() { - return me.page.mfd.getDeviceID(); + return me._page.mfd.getDeviceID(); }, diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/ActiveFlightPlanNarrow/ActiveFlightPlanNarrow.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/ActiveFlightPlanNarrow/ActiveFlightPlanNarrow.nas index f5209b91a..a421512ce 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/ActiveFlightPlanNarrow/ActiveFlightPlanNarrow.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/ActiveFlightPlanNarrow/ActiveFlightPlanNarrow.nas @@ -17,6 +17,8 @@ # ActiveFlightPlanNarrow var ActiveFlightPlanNarrow = { + SHORTCUTS : [ "FPL", "NRST", "RECENT", "USER", "AIRWAY" ], + new : func (mfd, myCanvas, device, svg) { var obj = { @@ -24,8 +26,45 @@ var ActiveFlightPlanNarrow = ActiveFlightPlanNarrow, MFDPage.new(mfd, myCanvas, device, svg, "ActiveFlightPlanNarrow", "FPL - ACTIVE FLIGHT PLAN") ], + current_flightplan : nil, }; + # Scrolling list of waypoints. We have _two_ lists here as we use one + # Arrow highlight element to show the current leg, and another to allow + # the FMS knob to highlight a waypoint for insertion/deletion or to make + # active. + obj.flightplanList = PFD.GroupElement.new( + obj.pageName, + svg, + [ "Header", "Leg", "DTK", "DIS", "ALT"], + 11, + "Leg", + 0, + "ScrollTrough", + "ScrollThumb", + 180 + ); + + obj.currentLegIndicator = PFD.GroupElement.new( + obj.pageName, + svg, + [ "Arrow"], + 11, + "Arrow", + 1, + ); + + obj.Map = fg1000.NavMap.new( + obj, + obj.getElement("NavMap"), + #[360, 275], + #[fg1000.MAP_PARTIAL.CENTER.X, fg1000.MAP_PARTIAL.CENTER.Y], + [860,400], + #"rect(345, 233, -345, -233)", + "", + -50, + 2); + obj.topMenu(device, obj, nil); obj.setController(fg1000.ActiveFlightPlanNarrowController.new(obj, svg)); @@ -42,18 +81,111 @@ var ActiveFlightPlanNarrow = me.device.svg.getElementById(name ~ "-bg").setColorFill(0.0,0.0,0.0); me.device.svg.getElementById(name).setColor(1.0,1.0,1.0); } + me.getElement("NavMap").setVisible(0); + me.Map.setVisible(0); me.getController().offdisplay(); }, ondisplay : func() { me._group.setVisible(1); me.mfd.setPageTitle(me.title); + me.getElement("NavMap").setVisible(1); + me.Map.setVisible(1); me.getController().ondisplay(); }, topMenu : func(device, pg, menuitem) { pg.clearMenu(); pg.resetMenuColors(); + pg.addMenuItem(0, "ENGINE", pg, pg.mfd.EIS.engineMenu); + pg.addMenuItem(2, "MAP", pg, pg.mfd.NavigationMap.mapMenu); + device.updateMenus(); }, + # Update the FlightPlan display with an updated flightplan. + setFlightPlan : func(fp, current_wp) { + var elements = []; + var arrowElements = []; + if (fp == nil) { + me.flightplanList.setValues([]); + me.flightplanList.setCRSR(0); + me.flightplanList.displayGroup(); + me.currentLegIndicator.setValues([]); + me.currentLegIndicator.setCRSR(0); + me.currentLegIndicator.displayGroup(); + return; + } + + for (var i = 0; i < fp.getPlanSize(); i = i + 1) { + var wp = fp.getWP(i); + + var element = { + Header : "", + Leg : "", + DTK : "", + DIS : "", + ALT : "_____ft", + }; + + if (wp.wp_name != nil) { + element.Leg = substr(wp.wp_name, 0, 7); + } else { + element.Leg = "____"; + } + + if (i == 0) { + element.DIS = ""; + element.DTK = ""; + element.ALT = "_____ft"; + } else if (i < current_wp) { + # Passed waypoints are blanked out on the display + element.DIS = "___nm"; + element.DTK = "___°"; + element.ALT = "_____ft"; + } else { + if (wp.leg_distance != nil) element.DIS = sprintf("%.1fnm", wp.leg_distance); + if (wp.leg_bearing != nil) element.DTK = sprintf("%03d°", wp.leg_bearing); + if (wp.alt_cstr_type != nil) element.ALT = sprintf("%dft", wp.alt_cstr); + } + + append(elements, element); + append(arrowElements, { Arrow : i}); + } + + me.flightplanList.setValues(elements); + me.currentLegIndicator.setValues(arrowElements); + + if (current_wp == -1) { + me.flightplanList.setCRSR(0); + me.currentLegIndicator.setCRSR(0); + } else { + me.flightplanList.setCRSR(current_wp); + me.currentLegIndicator.setCRSR(current_wp); + } + + me.flightplanList.displayGroup(); + me.currentLegIndicator.displayGroup(); + + # Determine a suitable name to display, using the flightplan name if there is one, + # but falling back to the flightplan departure / destination airports, or failing + # that the IDs of the first and last waypoints. + if ((fp.id == nil) or (fp.id == "default-flightplan")) { + var from = "????"; + var dest = "????"; + + if ((fp.getWP(0) != nil) and (fp.getWP(0).wp_name != nil)) { + from = fp.getWP(0).wp_name; + } + + if ((fp.getWP(fp.getPlanSize() -1) != nil) and (fp.getWP(fp.getPlanSize() -1).wp_name != nil)) { + dest = fp.getWP(fp.getPlanSize() -1).wp_name; + } + + if (fp.departure != nil) from = fp.departure.id; + if (fp.destination != nil) dest = fp.destination.id; + me.getElement("Name").setText(from ~ " / " ~ dest); + } else { + me.getElement("Name").setText(fp.id); + } + }, }; diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/ActiveFlightPlanNarrow/ActiveFlightPlanNarrowController.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/ActiveFlightPlanNarrow/ActiveFlightPlanNarrowController.nas index 56f8d3821..c6d6dea18 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/ActiveFlightPlanNarrow/ActiveFlightPlanNarrowController.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/ActiveFlightPlanNarrow/ActiveFlightPlanNarrowController.nas @@ -24,24 +24,44 @@ var ActiveFlightPlanNarrowController = _crsrToggle : 0, _recipient : nil, _page : page, + _fp_current_wp : 0, + _fp_active : 0, + _current_flightplan : nil, + _fprecipient : nil, + transmitter : nil, + _waypointSubmenuVisible : 0, }; + obj._current_flightplan = obj.getNavData("Flightplan"); + if (obj._current_flightplan != nil) { + obj._fp_current_wp = obj._current_flightplan.current; + obj._page.setFlightPlan(obj._current_flightplan, obj._fp_current_wp); + } else { + obj._page.setFlightPlan(nil, nil); + } + return obj; }, - # Input Handling handleCRSR : func() { me._crsrToggle = (! me._crsrToggle); if (me._crsrToggle) { + me._page.flightplanList.showCRSR(); } else { - me._page.hideCRSR(); + me._page.flightplanList.hideCRSR(); } return emesary.Transmitter.ReceiptStatus_Finished; }, handleFMSInner : func(value) { if (me._crsrToggle == 1) { - # Scroll through whatever is the current list + # Enable the WaypointEntry window + me._page.mfd._WaypointEntry.ondisplay(); + + # Also directly pass in the message. This is because the WaypointEntry page + # is above this in the Emesary stack, and as it was not displayed, it won't + # have picked up the message to display either an entry box or the submenu. + me._page.mfd._WaypointEntry.getController().handleFMSInner(value); return emesary.Transmitter.ReceiptStatus_Finished; } else { # Pass to the page group controller to display and scroll through the page group menu @@ -50,6 +70,7 @@ var ActiveFlightPlanNarrowController = }, handleFMSOuter : func(value) { if (me._crsrToggle == 1) { + me._page.flightplanList.incrLarge(value); return emesary.Transmitter.ReceiptStatus_Finished; } else { # Pass to the page group controller to display and scroll through the page group menu @@ -64,12 +85,177 @@ var ActiveFlightPlanNarrowController = } }, + handleRange : func(val) + { + # Pass any range entries to the NavMapController + me._page.Map.handleRange(val); + }, + + # Handle the user entry of a waypoint. We now need to insert it into the + # flightplan at the point after the selected waypoint, then update the + # flightplan. This should cause a cascade of Emesary updates, which will + # subsequently update this display (and any others). + handleWaypointEntry : func(data) { + assert(data.id != nil, "handleWaypointEntry called with invalid hash"); + # Place this after the current index + var params = { + index : me._page.flightplanList.getCRSR() + 1, + wp : data + }; + + # Update the FMS with the new flightplan via Emesary + var notification = notifications.PFDEventNotification.new( + "MFD", + me.getDeviceID(), + notifications.PFDEventNotification.NavData, + {Id: "InsertWaypoint", Value: params}); + + var response = me._transmitter.NotifyAll(notification); + + if (me._transmitter.IsFailed(response)) { + print("ActiveFlightPlanNarrowController.handleWaypointEntry() : Failed to set FMS Data " ~ params); + debug.dump(params); + } else { + # The flightplan has changed. For some reason this isn't triggering an + # update from the flightplan delegate, so we'll just trigger an update + # ourselves. + var notification = notifications.PFDEventNotification.new( + "MFD", + 1, + notifications.PFDEventNotification.FMSData, + {"FMSFlightPlanEdited" : 1}); + + var response = me._transmitter.NotifyAll(notification); + + if (me._transmitter.IsFailed(response)) { + print("ActiveFlightPlanNarrowController.handleWaypointEntry() : Failed to set FMS Data " ~ params); + debug.dump(params); + } + } + + # Critically, only this page should handle the waypoint entry. + return emesary.Transmitter.ReceiptStatus_Finished; + }, + + # Handle update to the FMS information. Note that there is no guarantee + # that the entire set of FMS data will be available. + handleFMSData : func(data) { + var update_fp = 0; + var reload_fp = 0; + + if (data["FMSLegID"] != nil) me._leg_id = data["FMSLegID"]; + + if ((data["FMSFlightPlanSequenced"] != nil) and (data["FMSFlightPlanSequenced"] != me._fp_current_wp)) { + me._fp_current_wp = data["FMSFlightPlanSequenced"]; + update_fp = 1; + } + + if (data["FMSFlightPlanEdited"] != nil) { + reload_fp = 1; + } + + if ((data["FMSFlightPlanActive"] != nil) and (data["FMSFlightPlanActive"] != me._fp_active)) { + me._fp_active = data["FMSFlightPlanActive"]; + if (me._fp_active) { + reload_fp = 1; + } else { + # No flightplan active, so we will display nothing. + me._current_flightplan = nil; + me._fp_current_wp = -1; + update_fp = 1; + } + } + + if ((data["FMSFlightPlanCurrentWP"] != nil) and (data["FMSFlightPlanCurrentWP"] != me._fp_current_wp)) { + me._fp_current_wp = data["FMSFlightPlanCurrentWP"]; + update_fp = 1; + } + + if (reload_fp) { + # The flightplan has changed in some way, so reload it. + me._current_flightplan = me.getNavData("Flightplan"); + if (me._current_flightplan != nil) { + me._fp_current_wp = me._current_flightplan.current; + update_fp = 1; + } + } + + if (update_fp) { + #me._current_flightplan = me.getNavData("Flightplan"); + me._page.setFlightPlan(me._current_flightplan, me._fp_current_wp); + } + + return emesary.Transmitter.ReceiptStatus_OK; + }, + # Reset controller if required when the page is displayed or hidden ondisplay : func() { me.RegisterWithEmesary(); + me.FPRegisterWithEmesary(); }, offdisplay : func() { me.DeRegisterWithEmesary(); + me.FPDeRegisterWithEmesary(); }, + FPRegisterWithEmesary : func(transmitter = nil){ + if (transmitter == nil) + transmitter = emesary.GlobalTransmitter; + + if (me._fprecipient == nil){ + me._fprecipient = emesary.Recipient.new("ActiveFlightPlanNarrowController_" ~ me._page.device.designation); + var pfd_obj = me._page.device; + var controller = me; + me._fprecipient.Receive = func(notification) + { + + if (notification.Device_Id == pfd_obj.device_id and + notification.NotificationType == notifications.PFDEventNotification.DefaultType and + notification.Event_Id == notifications.PFDEventNotification.FMSData and + notification.EventParameter != nil and + notification.EventParameter["Id"] == "SetWaypointEntry") + { + # Special case where THIS DEVICE has displayed the WaypointEntry page and + # we are now receiving the entered waypoint. In this case we need to + # determine where to enter it in the flightplan and update it. + return controller.handleWaypointEntry(notification.EventParameter.Value); + } + + if (notification.NotificationType == notifications.PFDEventNotification.DefaultType and + notification.Event_Id == notifications.PFDEventNotification.FMSData and + notification.EventParameter != nil) + { + return controller.handleFMSData(notification.EventParameter); + } + + return emesary.Transmitter.ReceiptStatus_NotProcessed; + }; + } + transmitter.Register(me._fprecipient); + me.transmitter = transmitter; + }, + FPDeRegisterWithEmesary : func(transmitter = nil){ + # remove registration from transmitter; but keep the recipient once it is created. + if (me.transmitter != nil) + me.transmitter.DeRegister(me._fprecipient); + me.transmitter = nil; + }, + + getNavData : func(type, value=nil) { + # Use Emesary to get a piece from the NavData system, using the provided + # type and value; + var notification = notifications.PFDEventNotification.new( + "MFD", + me._page.mfd.getDeviceID(), + notifications.PFDEventNotification.NavData, + {Id: type, Value: value}); + + var response = me._transmitter.NotifyAll(notification); + + if (! me._transmitter.IsFailed(response)) { + return notification.EventParameter.Value; + } else { + return nil; + } + }, }; diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/AirportInfo/AirportInfo.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/AirportInfo/AirportInfo.nas index 06d5d19ad..5f1a51564 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/AirportInfo/AirportInfo.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/AirportInfo/AirportInfo.nas @@ -117,19 +117,8 @@ var AirportInfo = me.setTextElement("City", "CITY"); me.setTextElement("Region", "REGION"); me.setTextElement("Alt", sprintf("%ift", M2FT * apt_info.elevation)); - - if (apt_info.lat < 0.0) { - me.setTextElement("Lat", sprintf("S %.4f", -apt_info.lat)); - } else { - me.setTextElement("Lat", sprintf("N %.4f", apt_info.lat)); - } - - if (apt_info.lon < 0.0) { - me.setTextElement("Lon", sprintf("W%3.4f", -apt_info.lon)); - } else { - me.setTextElement("Lon", sprintf("E%3.4f", apt_info.lon)); - } - + me.setTextElementLat("Lat", apt_info.lat); + me.setTextElementLon("Lon", apt_info.lon); me.setTextElement("Fuel", "AVGAS, AVTUR"); me.setTextElement("TZ", "UTC-6"); diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/DirectTo/DirectTo.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/DirectTo/DirectTo.nas index 79f90eca6..15dc5e4be 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/DirectTo/DirectTo.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/DirectTo/DirectTo.nas @@ -69,9 +69,8 @@ var DirectTo = obj.WaypointSubmenuSelect = PFD.ScrollElement.new(obj.pageName, svg, "WaypointSubmenuSelect", DirectTo.SHORTCUTS); obj.WaypointSubmenuScroll = PFD.GroupElement.new(obj.pageName, svg, [ "WaypointSubmenuScroll" ] , 4, "WaypointSubmenuScroll", 0, "WaypointSubmenuScrollTrough" , "WaypointSubmenuScrollThumb", 60); - # The Airport Chart + # The Airport Chart - only displayed on the MFD variant (where thre's if there's a Map SVG element present) if (obj.elementExists("Map")) { - print("DirectoTo Map exists"); obj.DirectToChart = fg1000.NavMap.new(obj, obj.getElement("Map"), [860,440], "rect(-160px, 160px, 160px, -160px)", 0, 2, 1); } else { obj.DirectToChart = nil; diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/DirectTo/DirectToController.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/DirectTo/DirectToController.nas index 7704127b6..0c8a1854f 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/DirectTo/DirectToController.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/DirectTo/DirectToController.nas @@ -177,6 +177,8 @@ var DirectToController = handleRange : func(val) { + if (! me.dto_displayed) return emesary.Transmitter.ReceiptStatus_NotProcessed; + if (me.page.DirectToChart != nil) { return me.page.DirectToChart.handleRange(val); } else { @@ -336,7 +338,7 @@ var DirectToController = # Some elements don't have names var name = destination.id; - if (!defined("destination.name")) name = destination.name; + if (defined("destination.name")) name = destination.name; var point = { lat: destination.lat, lon: destination.lon }; var (course, dist) = courseAndDistance(point); diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/NavigationMap/NavigationMapController.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/NavigationMap/NavigationMapController.nas index 52f21345f..4ec647340 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/NavigationMap/NavigationMapController.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/NavigationMap/NavigationMapController.nas @@ -17,15 +17,6 @@ # Navigation Map Controller var NavigationMapController = { - # TODO: Add STAMEN topo layer, which is visible on all levels as opposed to - # roads, railways, boundaries, cities which are only visible on declutter 0. - - # Declutter levels. - DCLTR : [ "DCLTR", "DCLTR-1", "DCLTR-2", "DCLTR-3"], - - # Airways levels. - AIRWAYS : [ "AIRWAYS", "AIRWY ON", "AIRWY LO", "AIRWY HI"], - new : func (page, svg) { var obj = { parents : [ NavigationMapController, MFDPageController.new(page) ] }; diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstruments.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstruments.nas index c782d9813..5093f71c0 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstruments.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstruments.nas @@ -95,6 +95,7 @@ var PFDInstruments = ); obj.setController(fg1000.PFDInstrumentsController.new(obj, svg)); + obj.setWindDisplay(0); obj.setCDISource("GPS"); obj.setBRG1("OFF"); @@ -728,6 +729,8 @@ var PFDInstruments = if (fp == nil) return; + var current_wp = fp.current; + for (var i = 0; i < fp.getPlanSize(); i = i + 1) { var wp = fp.getWP(i); @@ -736,22 +739,30 @@ var PFDInstruments = FlightPlanID : "", FlightPlanType : "", FlightPlanDTK : 0, - FlightPlanDIS : 0 + FlightPlanDIS : 0, }; if (wp.wp_name != nil) element.FlightPlanID = substr(wp.wp_name, 0, 7); if (wp.wp_role != nil) element.FlightPlanType = substr(wp.wp_role, 0, 4); - if (wp.leg_distance != nil) element.FlightPlanDIS = sprintf("%.1fnm", wp.leg_distance); - if (wp.leg_bearing != nil) element.FlightPlanDTK = sprintf("%03d°", wp.leg_bearing); + + if (i < current_wp) { + # Passed waypoints are blanked out on the display + element.FlightPlanDIS = "___nm"; + element.FlightPlanDTK = "___°"; + } else { + if (wp.leg_distance != nil) element.FlightPlanDIS = sprintf("%.1fnm", wp.leg_distance); + if (wp.leg_bearing != nil) element.FlightPlanDTK = sprintf("%03d°", wp.leg_bearing); + } append(elements, element); } me.flightplanList.setValues(elements); + me.flightplanList.setCRSR(current_wp); # Determine a suitable name to display, using the flightplan name if there is one, # but falling back to the flightplan departure / destination airports, or failing # that the IDs of the first and last waypoints. - if (fp.id == nil) { + if ((fp.id == nil) or (fp.id == "default-flightplan")) { var from = "????"; var dest = "????"; diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstrumentsController.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstrumentsController.nas index cd9f1fa2a..23c8cebd9 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstrumentsController.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstrumentsController.nas @@ -131,6 +131,13 @@ var PFDInstrumentsController = var incr_or_decr = (val > 0) ? me.page.insetMap.zoomIn() : me.page.insetMap.zoomOut(); }, + handleFPL : func (value) { + # Display/hide the FPL display + me._fp_visible = (! me._fp_visible); + me.page.setFlightPlanVisible(me._fp_active and me._fp_visible); + return emesary.Transmitter.ReceiptStatus_Finished; + }, + # Set the STD BARO to 29.92 in Hg setStdBaro : func() { var data = {}; @@ -254,12 +261,17 @@ var PFDInstrumentsController = if (data["FMSFlightPlanEdited"] != nil) { # The flightplan has changed in some way, so reload it. - update_fp = 1; + me._current_flightplan = me.getNavData("Flightplan"); + if (me._current_flightplan != nil) { + me._fp_current_wp = me._current_flightplan.current; + me.page.setFlightPlan(me._current_flightplan); + update_fp = 1; + } } if ((data["FMSFlightPlanActive"] != nil) and (data["FMSFlightPlanActive"] != me._fp_active)) { me._fp_active = data["FMSFlightPlanActive"]; - me.page.setFlightPlanVisible(me._fp_active); + me.page.setFlightPlanVisible(me._fp_active and me._fp_visible); update_fp = 1; } @@ -268,16 +280,7 @@ var PFDInstrumentsController = update_fp = 1; } - if (update_fp and me._fp_active) { - # For some reason the signals to indicate a FP change aren't firing, so reload the - # flightplan here - me._current_flightplan = me.getNavData("Flightplan"); - if (me._current_flightplan != nil) { - me._fp_current_wp = me._current_flightplan.current; - me.page.setFlightPlan(me._current_flightplan); - update_fp = 1; - } - + if (me._fp_visible and update_fp and me._fp_active) { me.page.updateFlightPlan(me._fp_current_wp); } diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/Surround/Surround.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/Surround/Surround.nas index c4f2b78e3..c38371be0 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/Surround/Surround.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/Surround/Surround.nas @@ -40,7 +40,7 @@ var PAGE_GROUPS = [ { label: "FPLGroupLabel", group: "FPLPageGroup", - pages: [ "ActiveFlightPlanWide", "FlightPlanCatalog", "StoredFlightPlan"], + pages: [ "ActiveFlightPlanNarrow", "FlightPlanCatalog", "StoredFlightPlan"], }, { label: "LstGroupLabel", diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/DirectTo.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/WaypointEntry/WaypointEntry.nas similarity index 50% rename from Aircraft/Instruments-3d/FG1000/Nasal/DirectTo.nas rename to Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/WaypointEntry/WaypointEntry.nas index 889355a7c..bd4f0e90f 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/DirectTo.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/WaypointEntry/WaypointEntry.nas @@ -14,14 +14,11 @@ # You should have received a copy of the GNU General Public License # along with FlightGear. If not, see . # -# DirectTo page -# -# Technically this is supposed to be an overlay page, sitting on top of -# whatever page the user was on already. However, to simplify implementation, -# we will assume that the user was on the Map page, and simply display the -# NavigationMap page underneath. +# WaypointEntry page. This is an overlay, sitting on whatever page the user +# is on already. Hence it is not called in the normal way, but instead +# explicitly called by Flightplan pages to insert a waypoint. -var DirectTo = +var WaypointEntry = { SHORTCUTS : [ "FPL", "NRST", "RECENT", "USER", "AIRWAY" ], @@ -29,21 +26,23 @@ var DirectTo = { var obj = { parents : [ - DirectTo, - MFDPage.new(mfd, myCanvas, device, svg, "DirectTo", "DIRECT TO") + WaypointEntry, + MFDPage.new(mfd, myCanvas, device, svg, "WaypointEntry", "") ], symbols : {}, }; obj.crsrIdx = 0; - # Dynamic text elements in the SVG file. In the SVG these have an "DirectTo" prefix. + # Dynamic text elements in the SVG file. In the SVG these have an "WaypointEntry" prefix. textelements = [ "Name", "City", "Region", "LocationBRG", "LocationDIS", + "LocationLat", + "LocationLon", ]; obj.addTextElements(textelements); @@ -59,88 +58,62 @@ var DirectTo = # .chars is the set of characters, used to scroll through using the small # FMS knob. obj.IDEntry = PFD.DataEntryElement.new(obj.pageName, svg, "ID", "", 4, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"); - obj.VNVAltEntry = PFD.DataEntryElement.new(obj.pageName, svg, "VNVAlt", "", 5, "0123456789"); - obj.VNVOffsetEntry = PFD.DataEntryElement.new(obj.pageName, svg, "VNVOffset", "", 2, "0123456789"); - obj.CourseEntry = PFD.DataEntryElement.new(obj.pageName, svg, "Course", "", 3, "0123456789"); - obj.Activate = PFD.TextElement.new(obj.pageName, svg, "Activate", "ACTIVATE?"); # The Shortcut window. This allows the user to scroll through a set of lists # of waypoints. - obj.WaypointSubmenuGroup = obj._SVGGroup.getElementById("DirectToWaypointSubmenuGroup"); - assert(obj.WaypointSubmenuGroup != nil, "Unable to find DirectToWaypointSubmenuGroup"); + obj.WaypointSubmenuGroup = obj._SVGGroup.getElementById("WaypointEntryWaypointSubmenuGroup"); + assert(obj.WaypointSubmenuGroup != nil, "Unable to find WaypointEntryWaypointSubmenuGroup"); obj.WaypointSubmenuGroup.setVisible(0); - obj.WaypointSubmenuSelect = PFD.ScrollElement.new(obj.pageName, svg, "WaypointSubmenuSelect", DirectTo.SHORTCUTS); + obj.WaypointSubmenuSelect = PFD.ScrollElement.new(obj.pageName, svg, "WaypointSubmenuSelect", WaypointEntry.SHORTCUTS); obj.WaypointSubmenuScroll = PFD.GroupElement.new(obj.pageName, svg, [ "WaypointSubmenuScroll" ] , 4, "WaypointSubmenuScroll", 0, "WaypointSubmenuScrollTrough" , "WaypointSubmenuScrollThumb", 60); - # The Airport Chart + # The Airport Chart - only displayed on the MFD variant (where thre's if there's a Map SVG element present) if (obj.elementExists("Map")) { - obj.DirectToChart = fg1000.NavMap.new(obj, obj.getElement("Map"), [860,440], "rect(-160px, 160px, 160px, -160px)", 0, 2, 1); + obj.WaypointEntryChart = fg1000.NavMap.new(obj, obj.getElement("Map"), [860,440], "rect(-160px, 160px, 160px, -160px)", 0, 2, 1); + } else { + obj.WaypointEntryChart = nil; } - obj.setController(fg1000.DirectToController.new(obj, svg)); + obj.setController(fg1000.WaypointEntryController.new(obj, svg)); return obj; }, + displayDestination : func(destination) { - - #me.IDEntry.clearElement(); - if (destination != nil) { # Display a given location - if (me.DirectToChart) { - me.DirectToChart.setVisible(1); - me.DirectToChart.getController().setPosition(destination.lat,destination.lon); + if (me.WaypointEntryChart != nil) { + me.WaypointEntryChart.setVisible(1); + me.WaypointEntryChart.getController().setPosition(destination.lat,destination.lon); } me.setTextElement("Name", string.uc(destination.name)); - me.setTextElement("City", "CITY"); - me.setTextElement("Region", "REGION"); - me.setTextElement("LocationBRG", "" ~ sprintf("%03d°", destination.course)); - me.setTextElement("LocationDIS", sprintf("%d", destination.range_nm) ~ "nm"); - - me.IDEntry.setValue(destination.id); - me.VNVAltEntry.setValue("00000"); - me.VNVOffsetEntry.setValue("00"); - me.CourseEntry.setValue("" ~ sprintf("%03d°", destination.course)); - } else { - if (me.DirectToChart) me.DirectToChart.setVisible(0); - me.setTextElement("Name", ""); me.setTextElement("City", ""); me.setTextElement("Region", ""); - me.setTextElement("LocationBRG", "_"); - me.setTextElement("LocationDIS", "_"); - - me.IDEntry.setValue("____"); - me.VNVAltEntry.setValue("00000"); - me.VNVOffsetEntry.setValue("00"); - me.CourseEntry.setValue(0); + me.setTextElement("LocationBRG", "" ~ sprintf("%03d°", destination.course)); + me.setTextElement("LocationDIS", sprintf("%d", destination.range_nm) ~ "nm"); + me.setTextElementLat("LocationLat", destination.lat); + me.setTextElementLon("LocationLon", destination.lon); + me.IDEntry.setValue(destination.id); + } else { + if (me.WaypointEntryChart != nil) me.WaypointEntryChart.setVisible(0); + me.setTextElement("Name", "___"); + me.setTextElement("City", "____________"); + me.setTextElement("Region", "____________"); + me.setTextElement("LocationBRG", "___°"); + me.setTextElement("LocationDIS", "__._nm"); + me.setTextElement("LocationLat", "_ __°__.__'"); + me.setTextElement("LocationLon", "____°__.__'"); + me.IDEntry.setValue("####"); } }, offdisplay : func() { me._group.setVisible(0); + me.getElement("Group").setVisible(0); me.getController().offdisplay(); - me.mfd.NavigationMap.offdisplay(0); }, ondisplay : func() { me._group.setVisible(1); - # Display a false title, as underneath we're showing the navigation map. - me.mfd.setPageTitle("MAP - NAVIGATION MAP"); - me.getElement("Map").setVisible(1); - me.getElement("Map-bg").setVisible(1); - if (me.DirectToChart) me.DirectToChart.setVisible(1); + me.getElement("Group").setVisible(1); me.getController().ondisplay(); - - # The DirectTo pages displays over the NavigationMap. This is a hack - # as the page should just magically sit ontop of whatever page the user was - # on. However, we also need to disable the NavMap's own controller so there's - # no confusion. - me.mfd.NavigationMap.ondisplay(0); }, - - # When the Direct To display is enabled, nothing is displayed on the softkeys. - topMenu : func(device, pg, menuitem) { - pg.clearMenu(); - pg.resetMenuColors(); - device.updateMenus(); - }, - }; diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/WaypointEntry/WaypointEntryController.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/WaypointEntry/WaypointEntryController.nas new file mode 100644 index 000000000..f269ea432 --- /dev/null +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/WaypointEntry/WaypointEntryController.nas @@ -0,0 +1,314 @@ +# Copyright 2018 Stuart Buchanan +# This file is part of FlightGear. +# +# Foobar is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# FlightGear is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with FlightGear. If not, see . +# +# WaypointEntry Controller +var WaypointEntryController = +{ + new : func (page, svg) + { + var obj = { parents : [ WaypointEntryController, MFDPageController.new(page)] }; + obj.id = ""; + obj.page = page; + obj._wpentry_displayed = 0; + obj._destination = nil; + + obj._cursorElements = [ + obj.page.IDEntry, + ]; + + obj._activateIndex = size(obj._cursorElements) - 1; + + obj._selectedElement = 0; + + # Whether the WaypointSubmenuGroup is enabled + obj._waypointSubmenuVisible = 0; + + return obj; + }, + + setCursorElement : func(value) { + + for (var i = 0; i < size(me._cursorElements); i = i+1) { + me._cursorElements[i].unhighlightElement(); + } + + if (value < 0) value = 0; + if (value > (size(me._cursorElements) -1)) value = size(me._cursorElements) -1; + me._selectedElement = value; + me._cursorElements[me._selectedElement].highlightElement(); + }, + + nextCursorElement : func(value) { + var incr_or_decr = (value > 0) ? 1 : -1; + me.setCursorElement(me._selectedElement + incr_or_decr); + }, + + handleCRSR : func() { + if (! me._wpentry_displayed) return emesary.Transmitter.ReceiptStatus_NotProcessed; + + # No effect, but shouldn't be passed to underlying page? + return emesary.Transmitter.ReceiptStatus_Finished; + }, + updateWaypointSubmenu : func() { + var type = me.page.WaypointSubmenuSelect.getValue(); + + var items = []; + + if (type == "FPL") { + # Get the contents of the flightplan and display the list of waypoints. + var fp = me.getNavData("Flightplan"); + + for (var i = 0; i < fp.getPlanSize(); i = i + 1) { + var wp = fp.getWP(i); + if (wp.wp_name != nil) append(items, wp.wp_name); + } + } + + if (type == "NRST") { + # Get the nearest airports + var apts = me.getNavData("NearestAirports"); + + for (var i = 0; i < size(apts); i = i + 1) { + var apt = apts[i]; + if (apt.id != nil) append(items, apt.id); + } + } + + if (type == "RECENT") { + # Get the set of recent waypoints + items = me.getNavData("RecentWaypoints"); + } + + if (type == "USER") { + items = me.getNavData("UserWaypoints"); + } + + if (type == "AIRWAY") { + var airways = me.getNavData("AirwayWaypoints"); + if (airways != nil) { + foreach (var wp; airways) { + if (wp.wp_name != nil) append(items, wp.wp_name); + } + } + } + + if ((items != nil) and (size(items) > 0)) { + # At this point we have a vector of waypoint names. We need to convert + # this into a vector of { "WaypointSubmenuScroll" : [name] } hashes for consumption by the + # list of waypoints + var groupitems = []; + foreach (var item; items) { + append(groupitems, { "WaypointSubmenuScroll" : item } ); + } + + # Now display them! + me.page.WaypointSubmenuScroll.setValues(groupitems); + } else { + # Nothing to display + me.page.WaypointSubmenuScroll.setValues([]); + } + }, + + getNavData : func(type, value=nil) { + # Use Emesary to get a piece from the NavData system, using the provided + # type and value; + var notification = notifications.PFDEventNotification.new( + "MFD", + me.getDeviceID(), + notifications.PFDEventNotification.NavData, + {Id: type, Value: value}); + + var response = me._transmitter.NotifyAll(notification); + + if (! me._transmitter.IsFailed(response)) { + return notification.EventParameter.Value; + } else { + return nil; + } + }, + + setFMSData : func(type, value=nil) { + # Use Emesary to set a piece of data in the NavData system, using the provided + # type and value; + var notification = notifications.PFDEventNotification.new( + "MFD", + me.getDeviceID(), + notifications.PFDEventNotification.FMSData, + {Id: type, Value: value}); + + var response = me._transmitter.NotifyAll(notification); + + if (me._transmitter.IsFailed(response)) { + print("WaypointEntryController.setNavData() : Failed to set Nav Data " ~ value); + debug.dump(value); + } + }, + + handleRange : func(val) + { + if (! me._wpentry_displayed) return emesary.Transmitter.ReceiptStatus_NotProcessed; + + if (me.page.WaypointEntryChart != nil) { + return me.page.WaypointEntryChart.handleRange(val); + } else { + return emesary.Transmitter.ReceiptStatus_NotProcessed; + } + }, + + handleFMSInner : func(value) { + if (! me._wpentry_displayed) return emesary.Transmitter.ReceiptStatus_NotProcessed; + + if (me._waypointSubmenuVisible) { + # We're in the Waypoint Submenu, in which case the inner FMS knob + # selects between the different waypoint types. + me.page.WaypointSubmenuSelect.highlightElement(); + me.page.WaypointSubmenuSelect.incrSmall(value); + # Now update the Scroll group with the new type of waypoints + me.updateWaypointSubmenu(); + } else if ((me._selectedElement == 0) and (! me.page.IDEntry.isInEdit()) and (value == -1)) { + # The WaypointSubmenuGroup group is displayed if the small FMS knob is rotated + # anti-clockwise as an initial rotation where the ID Entry is not being editted. + me._cursorElements[0].unhighlightElement(); + + me.page.WaypointSubmenuGroup.setVisible(1); + me.page.WaypointSubmenuSelect.highlightElement(); + me._waypointSubmenuVisible = 1; + me.updateWaypointSubmenu(); + } else { + # We've already got something selected, and we're not in the + # WaypointSubmenuGroup, so increment it. + me._cursorElements[me._selectedElement].incrSmall(value); + } + + return emesary.Transmitter.ReceiptStatus_Finished; + }, + + handleFMSOuter : func(value) { + if (! me._wpentry_displayed) return emesary.Transmitter.ReceiptStatus_NotProcessed; + + if (me._waypointSubmenuVisible) { + # We're in the Waypoint Submenu, in which case the outer FMS knob + # selects between the different waypoints in the Waypoint Submenu. + me.page.WaypointSubmenuSelect.unhighlightElement(); + me.page.WaypointSubmenuScroll.showCRSR(); + me.page.WaypointSubmenuScroll.incrLarge(value); + } else if (me._cursorElements[me._selectedElement].isInEdit()) { + # If we're editing an element, then get on with it! + me._cursorElements[me._selectedElement].incrLarge(value); + } else { + me.nextCursorElement(value); + } + + return emesary.Transmitter.ReceiptStatus_Finished; + }, + + handleEnter : func(value) { + if (! me._wpentry_displayed) return emesary.Transmitter.ReceiptStatus_NotProcessed; + + if (me._waypointSubmenuVisible) { + # If we're in the Waypoint Submenu, then take whatever is highlighted + # in the scroll list, load it and hide the Waypoint submenu + var id = me.page.WaypointSubmenuScroll.getValue(); + if (id != nil) me.loadDestination(id); + me.page.WaypointSubmenuGroup.setVisible(0); + me._waypointSubmenuVisible = 0; + } else if (me.page.IDEntry.isInEdit()) { + # If we're editing an element, complete the data entry, the load it. + me.page.IDEntry.enterElement(); + me.loadDestination(me.page.IDEntry.getValue()); + } else { + # Pass the entered waypoint to the surrounding page TODO + me.setFMSData("SetWaypointEntry", me._destination); + + me._wpentry_displayed = 0; + me.page.offdisplay(); + + return emesary.Transmitter.ReceiptStatus_Finished; + } + + return emesary.Transmitter.ReceiptStatus_Finished; + }, + + handleClear : func(value) { + if (! me._wpentry_displayed) return emesary.Transmitter.ReceiptStatus_NotProcessed; + + if (me._waypointSubmenuVisible) { + # If we're in the Waypoint Submenu, then this clears it. + me.page.WaypointSubmenuGroup.setVisible(0); + me._waypointSubmenuVisible = 0; + } else if (me._cursorElements[me._selectedElement].isInEdit()) { + me._cursorElements[me._selectedElement].clearElement(); + } else { + # Cancel the entire Waypoint Entry page. + me._wpentry_displayed = 0; + me.page.offdisplay(); + } + return emesary.Transmitter.ReceiptStatus_Finished; + }, + + # Reset controller if required when the page is displayed or hidden + # Note that we explicitly do NOT RegisterWithEmesary/DeRegisterWithEmesary! + # This page should RegisterWithEmesary at start of day instead. + ondisplay : func() { + # On initial display we simply display a blank destination + me._wpentry_displayed = 1; + me.loadDestination(nil); + }, + + offdisplay : func() { + me._wpentry_displayed = 0; + }, + + loadDestination : func(id) { + if ((id == nil) or (id == "")) { + me._destination = nil; + } else { + # Use Emesary to get the destination + var notification = notifications.PFDEventNotification.new( + "MFD", + me.getDeviceID(), + notifications.PFDEventNotification.NavData, + {Id: "NavDataByID", Value: id}); + + var response = me._transmitter.NotifyAll(notification); + var retval = notification.EventParameter.Value; + + if ((! me._transmitter.IsFailed(response)) and (size(retval) > 0)) { + var destination = retval[0]; + # set the course and distance to the destination if required + + # Some elements don't have names + var name = destination.id; + if (defined("destination.name")) name = destination.name; + + var point = { lat: destination.lat, lon: destination.lon }; + var (course, dist) = courseAndDistance(point); + + me._destination = { + id: destination.id, + name: name, + lat: destination.lat, + lon: destination.lon, + course : course, + range_nm : dist, + }; + } + } + + + me.page.displayDestination(me._destination); + }, +}; diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/WaypointEntry/WaypointEntryOptions.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/WaypointEntry/WaypointEntryOptions.nas new file mode 100644 index 000000000..b10a9ff0a --- /dev/null +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/WaypointEntry/WaypointEntryOptions.nas @@ -0,0 +1,44 @@ +# Copyright 2018 Stuart Buchanan +# This file is part of FlightGear. +# +# Foobar is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# FlightGear is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with FlightGear. If not, see . +# +# WaypointEntry Options +var WaypointEntryOptions = +{ + new : func() { + var obj = { parents : [WaypointEntryOptions] }; + obj.Options= {}; + obj.loadOptions(); + return obj; + }, + + getOption : func(type) { + return me.Options[type]; + }, + + setOption : func(type, name, value) { + me.Options[type][name] = value; + }, + + loadOptions : func() { + me.clearOptions(); + me.Options.APS = {}; + }, + + clearOptions : func() { + me.Options = {}; + }, + +}; diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/WaypointEntry/WaypointEntryStyles.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/WaypointEntry/WaypointEntryStyles.nas new file mode 100644 index 000000000..98ee135f1 --- /dev/null +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/WaypointEntry/WaypointEntryStyles.nas @@ -0,0 +1,82 @@ +# Copyright 2018 Stuart Buchanan +# This file is part of FlightGear. +# +# Foobar is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# FlightGear is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with FlightGear. If not, see . +# +# WaypointEntry Styles +var WaypointEntryStyles = +{ + new : func() { + var obj = { parents : [ WaypointEntryStyles ]}; + obj.Styles = {}; + obj.loadStyles(); + return obj; + }, + + getStyle : func(type) { + return me.Styles[type]; + }, + + setStyle : func(type, name, value) { + me.Styles[type][name] = value; + }, + + loadStyles : func() { + me. clearStyles(); + me.Styles.DME = {}; + me.Styles.DME.debug = 1; # HACK for benchmarking/debugging purposes + me.Styles.DME.animation_test = 0; # for prototyping animated symbols + + me.Styles.DME.scale_factor = 0.4; # 40% (applied to whole group) + me.Styles.DME.line_width = 3.0; + me.Styles.DME.color_tuned = [0,1,0]; #rgb + me.Styles.DME.color_default = [1,1,0]; #rgb + + me.Styles.APT = {}; + me.Styles.APT.scale_factor = 0.4; # 40% (applied to whole group) + me.Styles.APT.line_width = 3.0; + me.Styles.APT.color_default = [0,0.6,0.85]; #rgb + me.Styles.APT.label_font_color = me.Styles.APT.color_default; + me.Styles.APT.label_font_size=28; + + me.Styles.TFC = {}; + me.Styles.TFC.scale_factor = 0.4; # 40% (applied to whole group) + + me.Styles.WPT = {}; + me.Styles.WPT.scale_factor = 0.5; # 50% (applied to whole group) + + me.Styles.RTE = {}; + me.Styles.RTE.line_width = 2; + + me.Styles.FLT = {}; + me.Styles.FLT.line_width = 3; + + me.Styles.FIX = {}; + me.Styles.FIX.color = [1,0,0]; + me.Styles.FIX.scale_factor = 0.4; # 40% + + me.Styles.VOR = {}; + me.Styles.VOR.range_line_width = 2; + me.Styles.VOR.radial_line_width = 1; + me.Styles.VOR.scale_factor = 0.6; # 60% + + me.Styles.APS = {}; + me.Styles.APS.scale_factor = 0.25; + }, + + clearStyles : func() { + me.Styles = {}; + }, + +}; diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/NavMap.nas b/Aircraft/Instruments-3d/FG1000/Nasal/NavMap.nas index d6f079a9f..9253c30ee 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/NavMap.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/NavMap.nas @@ -42,8 +42,8 @@ var NavMap = { obj.Styles = fg1000.NavigationMapStyles.new(); obj.Options = fg1000.NavigationMapOptions.new(); - obj.Map = element.createChild("map"); - obj.Map.setScreenRange(689/2.0); + obj._map = element.createChild("map"); + obj._map.setScreenRange(689/2.0); obj._rangeDisplay = obj._svg.getElementById(obj._pageName ~ "RangeDisplay"); if (obj._rangeDisplay == nil) die("Unable to find element " ~ obj._pageName ~ "RangeDisplay"); @@ -53,7 +53,7 @@ var NavMap = { # Initialize the controllers: if (static) { - obj.Map.setController("Static position", "main"); + obj._map.setController("Static position", "main"); } else { var ctrl_ns = canvas.Map.Controller.get("Aircraft position"); var source = ctrl_ns.SOURCES["current-pos"]; @@ -74,12 +74,12 @@ var NavMap = { source.aircraft_heading = n.getBoolValue(); }, 1); # Make it move with our aircraft: - obj.Map.setController("Aircraft position", "current-pos"); # from aircraftpos.controller + obj._map.setController("Aircraft position", "current-pos"); # from aircraftpos.controller } if (clip != "") { - obj.Map.set("clip-frame", canvas.Element.LOCAL); - obj.Map.set("clip", clip); + obj._map.set("clip-frame", canvas.Element.LOCAL); + obj._map.set("clip", clip); } if (zindex != 0) { @@ -94,7 +94,7 @@ var NavMap = { if ((static == 0) or (layer.static == 1)) { # Not all layers are displayed for all map types. Specifically, # some layers are not displayed on static maps - e.g. DirectTo - obj.Map.addLayer( + obj._map.addLayer( factory: layer.factory, type_arg: layer_name, priority: layer.priority, @@ -106,29 +106,24 @@ var NavMap = { obj.setZoom(obj.current_zoom); obj.setOrientation(0); - obj.Map.setVisible(0); + obj._map.setVisible(0); return obj; }, setController : func(type, controller ) { - me.Map.setController(type, controller); + me._map.setController(type, controller); }, getController : func() { - return me.Map.getController(); + return me._map.getController(); }, toggleLayerVisible : func(name) { - (var l = me.Map.getLayer(name)).setVisible(l.getVisible()); + (var l = me._map.getLayer(name)).setVisible(l.getVisible()); }, setLayerVisible : func(name,n=1) { - me.Map.getLayer(name).setVisible(n); - }, - - setRange : func(range, label) { - me.Map.setRange(range); - me._rangeDisplay.setText(label); + me._map.getLayer(name).setVisible(n); }, setOrientation : func(orientation) { @@ -137,7 +132,7 @@ var NavMap = { }, setScreenRange : func(range) { - me.Map.setScreenRange(range); + me._map.setScreenRange(range); }, zoomIn : func() { @@ -151,10 +146,8 @@ var NavMap = { setZoom : func(zoom) { if ((zoom < 0) or (zoom > (size(fg1000.RANGES) - 1))) return; me.current_zoom = zoom; - # Ranges above represent vertical ranges, but the display is a rectangle, so - # we need to use the diagonal range of the 1024 x 689, which is 617px. - # 617px is 1.8 x 689/2, so we need to increase the range values by x1.8 - me.setRange(fg1000.RANGES[zoom].range, fg1000.RANGES[zoom].label); + me._rangeDisplay.setText(fg1000.RANGES[zoom].label); + me._map.setRange(fg1000.RANGES[zoom].range); me.updateVisibility(); }, @@ -163,7 +156,7 @@ var NavMap = { foreach (var layer_name; me._page.mfd.ConfigStore.getLayerNames()) { var layer = me._page.mfd.ConfigStore.getLayer(layer_name); - if (me.Map.getLayer(layer_name) == nil) continue; + if (me._map.getLayer(layer_name) == nil) continue; # Layers are only displayed if: # 1) the user has enabled them. @@ -171,15 +164,15 @@ var NavMap = { # (i.e. as the range gets larger, we remove layers). Note that for # inset maps, the range that items are removed is lower. # 3) They haven't been removed due to the declutter level. - var effective_zoom = math.clamp(me.current_zoom + me.vis_shift, 0, size(fg1000.RANGES)); + var effective_zoom = math.clamp(me.current_zoom + me.vis_shift, 0, size(fg1000.RANGES) -1); var effective_range = fg1000.RANGES[effective_zoom].range; if (layer.enabled and (effective_range <= layer.range) and (me.declutter <= layer.declutter) ) { - me.Map.getLayer(layer_name).setVisible(1); + me._map.getLayer(layer_name).setVisible(1); } else { - me.Map.getLayer(layer_name).setVisible(0); + me._map.getLayer(layer_name).setVisible(0); } } }, @@ -220,7 +213,7 @@ var NavMap = { # Set the DTO line target setDTOLineTarget : func(lat, lon) { - me.Map.getLayer("DTO").controller.setTarget(lat,lon); + me._map.getLayer("DTO").controller.setTarget(lat,lon); }, enableDTO : func(enable) { me._page.mfd.ConfigStore.setLayerEnabled("DTO", enable); @@ -235,16 +228,16 @@ var NavMap = { }, getMap : func() { - return me.Map; + return me._map; }, show : func() { - me.Map.show(); + me._map.show(); }, hide : func() { - me.Map.hide(); + me._map.hide(); }, setVisible : func(visible) { - me.Map.setVisible(visible); + me._map.setVisible(visible); } }; diff --git a/Nasal/canvas/PFD/GroupElement.nas b/Nasal/canvas/PFD/GroupElement.nas index 529fbbcde..8eeab874e 100644 --- a/Nasal/canvas/PFD/GroupElement.nas +++ b/Nasal/canvas/PFD/GroupElement.nas @@ -2,7 +2,7 @@ var GroupElement = { -new : func (pageName, svg, elementNames, size, highlightElement, arrow=0, scrollTroughElement=nil, scrollThumbElement=nil, scrollHeight=0, style=nil) +new : func (pageName, svg, elementNames, displaysize, highlightElement, arrow=0, scrollTroughElement=nil, scrollThumbElement=nil, scrollHeight=0, style=nil) { var obj = { parents : [ GroupElement ], @@ -18,7 +18,7 @@ new : func (pageName, svg, elementNames, size, highlightElement, arrow=0, scroll # The size of the group. For each of the ._elementNames hash values there # must be an SVG Element [pageName][elementName]{0...pageSize} - _size : size, + _size : displaysize, # ElementName to be highlighted. Must be an hash value from ._elementNames _highlightElement : highlightElement, @@ -66,7 +66,7 @@ new : func (pageName, svg, elementNames, size, highlightElement, arrow=0, scroll if (style == nil) obj._style = PFD.DefaultStyle; - for (var i = 0; i < size; i = i + 1) { + for (var i = 0; i < displaysize; i = i + 1) { if (obj._arrow == 1) { append(obj._elements, PFD.HighlightElement.new(pageName, svg, highlightElement ~ i, i, obj._style)); } else { @@ -87,12 +87,12 @@ setValues : func (values_array) { if (size(me._values) > me._size) { # Number of elements exceeds our ability to display them, so enable # the scroll bar. - me._scrollThumbElement.setVisible(1); - me._scrollTroughElement.setVisible(1); + if (me._scrollThumbElement != nil) me._scrollThumbElement.setVisible(1); + if (me._scrollTroughElement != nil) me._scrollTroughElement.setVisible(1); } else { # There is no scrolling to do, so hide the scrollbar. - me._scrollThumbElement.setVisible(0); - me._scrollTroughElement.setVisible(0); + if (me._scrollThumbElement != nil) me._scrollThumbElement.setVisible(0); + if (me._scrollTroughElement != nil) me._scrollTroughElement.setVisible(0); } me.displayGroup(); @@ -203,6 +203,9 @@ setCRSR : func(index) { me._crsrIndex = math.min(index, size(me._values) -1); me._crsrIndex = math.max(0, me._crsrIndex); }, +getCRSR : func() { + return me._crsrIndex; +}, getCursorElementName : func() { if (me._crsrEnabled == -1) return nil; return me._elements[me._crsrIndex - me._pageIndex].name; @@ -232,7 +235,6 @@ incrSmall : func(value) { var incr_or_decr = (value > 0) ? 1 : -1; if (me._elements[me._crsrIndex - me._pageIndex].isInEdit()) { # We're editing, so pass to the element. - #print("Moving cursor to next character entry"); me._elements[me._crsrIndex - me._pageIndex].incrSmall(val); } else { # Move to next selection element @@ -247,7 +249,6 @@ incrLarge : func(val) { var incr_or_decr = (val > 0) ? 1 : -1; if (me._elements[me._crsrIndex - me._pageIndex].isInEdit()) { # We're editing, so pass to the element. - #print("Moving cursor to next character entry"); me._elements[me._crsrIndex - me._pageIndex].incrLarge(val); } else { # Move to next selection element