From 60352375d0ff1a410f35b1729d2fd62a05b8f9c0 Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Sun, 25 Mar 2018 17:55:03 +0100 Subject: [PATCH] FG1000 - PFD Flightplan and improved GroupElement --- .../FG1000/MFDPages/DirectTo.svg | 8 +- .../FG1000/MFDPages/FlightPlanPFD.svg | 443 ++++++++++++++++++ .../Nasal/Interfaces/GenericFMSPublisher.nas | 161 ++++--- .../Nasal/Interfaces/PropertyPublisher.nas | 2 +- .../PFDInstruments/PFDInstruments.nas | 74 ++- .../PFDInstrumentsController.nas | 62 ++- Aircraft/Instruments-3d/FG1000/Nasal/PFD.nas | 5 + Nasal/canvas/PFD/GroupElement.nas | 160 +++---- 8 files changed, 757 insertions(+), 158 deletions(-) create mode 100644 Aircraft/Instruments-3d/FG1000/MFDPages/FlightPlanPFD.svg diff --git a/Aircraft/Instruments-3d/FG1000/MFDPages/DirectTo.svg b/Aircraft/Instruments-3d/FG1000/MFDPages/DirectTo.svg index 542a85ac8..818971566 100644 --- a/Aircraft/Instruments-3d/FG1000/MFDPages/DirectTo.svg +++ b/Aircraft/Instruments-3d/FG1000/MFDPages/DirectTo.svg @@ -23,9 +23,9 @@ borderopacity="0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="3.2473958" - inkscape:cx="817.98224" - inkscape:cy="351.10971" + inkscape:zoom="2.2962556" + inkscape:cx="913.04869" + inkscape:cy="547.52243" inkscape:document-units="px" inkscape:current-layer="DirectToGroup" showgrid="true" @@ -89,7 +89,7 @@ style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> diff --git a/Aircraft/Instruments-3d/FG1000/MFDPages/FlightPlanPFD.svg b/Aircraft/Instruments-3d/FG1000/MFDPages/FlightPlanPFD.svg new file mode 100644 index 000000000..0a4ee5172 --- /dev/null +++ b/Aircraft/Instruments-3d/FG1000/MFDPages/FlightPlanPFD.svg @@ -0,0 +1,443 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + FLIGHT PLAN NAME + FLIGHT PLAN + DIS + DTK + + + + + WP1 + type + WP1 + WP1 + WP1 + type + WP1 + WP1 + WP1 + type + WP1 + WP1 + WP1 + type + WP1 + WP1 + WP1 + type + WP1 + WP1 + + + + + + + + + diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/GenericFMSPublisher.nas b/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/GenericFMSPublisher.nas index 284bf291b..5d55e5429 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/GenericFMSPublisher.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/GenericFMSPublisher.nas @@ -22,84 +22,109 @@ var GenericFMSPublisher = var obj = { parents : [ GenericFMSPublisher, - PeriodicPropertyPublisher.new(notifications.PFDEventNotification.FMSData, frequency) ], }; - obj.addPropMap("FMSHeadingBug", "/autopilot/settings/heading-bug-deg"); - obj.addPropMap("FMSSelectedAlt", "/autopilot/settings/target-alt-ft"); + # We have two publishers here: + # + # 1) a triggered publisher for properties that will change ocassionally, but which + # we need to update immediately. These are typically settings. + # + # 2) a periodic publisher which triggers every 0.5s to update data values. + obj._triggeredPublisher = TriggeredPropertyPublisher.new(notifications.PFDEventNotification.FMSData); + obj._periodicPublisher = PeriodicPropertyPublisher.new(notifications.PFDEventNotification.FMSData, frequency); - obj.addPropMap("FMSMode", "/instrumentation/gps/mode"); - obj.addPropMap("FMSLegValid", "/instrumentation/gps/wp/wp[1]/valid"); - obj.addPropMap("FMSPreviousLegID", "/instrumentation/gps/wp/wp[0]/ID"); - obj.addPropMap("FMSLegID", "/instrumentation/gps/wp/wp[1]/ID"); - obj.addPropMap("FMSLegBearingMagDeg", "/instrumentation/gps/wp/wp[1]/bearing-mag-deg"); - obj.addPropMap("FMSLegDistanceNM", "/instrumentation/gps/wp/wp[1]/distance-nm"); - obj.addPropMap("FMSLegCourseError", "/instrumentation/gps/wp/wp[1]/course-error-nm"); - obj.addPropMap("FMSLegDesiredTrack", "/instrumentation/gps/wp/wp[1]/desired-course-deg"); - obj.addPropMap("FMSLegTrackErrorAngle", "/instrumentation/gps/wp/wp[1]/course-deviation-deg"); - obj.addPropMap("FMSWayPointCourseError", "/instrumentation/gps/wp/wp[1]/course-error-nm"); + obj._triggeredPublisher.addPropMap("FMSHeadingBug", "/autopilot/settings/heading-bug-deg"); + obj._triggeredPublisher.addPropMap("FMSSelectedAlt", "/autopilot/settings/target-alt-ft"); + obj._triggeredPublisher.addPropMap("FMSFlightPlanActive", "/autopilot/route-manager/active"); + obj._triggeredPublisher.addPropMap("FMSFlightPlanCurrentWP", "/autopilot/route-manager/current-wp"); + obj._triggeredPublisher.addPropMap("FMSFlightPlanSequenced", "/autopilot/route-manager/signals/sequenced"); + obj._triggeredPublisher.addPropMap("FMSFlightPlanFinished", "/autopilot/route-manager/signals/finished"); + obj._triggeredPublisher.addPropMap("FMSFlightPlanEdited", "/autopilot/route-manager/signals/edited"); - obj.addPropMap("FMSGroundspeed", "/instrumentation/gps/indicated-ground-speed-kt"); + obj._triggeredPublisher.addPropMap("FMSMode", "/instrumentation/gps/mode"); - obj.addPropMap("FMSNav1From", "/instrumentation/nav/from-flag"); - obj.addPropMap("FMSNav2From", "/instrumentation/nav[1]/from-flag"); + obj._periodicPublisher.addPropMap("FMSLegValid", "/instrumentation/gps/wp/wp[1]/valid"); + obj._periodicPublisher.addPropMap("FMSPreviousLegID", "/instrumentation/gps/wp/wp[0]/ID"); + obj._periodicPublisher.addPropMap("FMSLegID", "/instrumentation/gps/wp/wp[1]/ID"); + obj._periodicPublisher.addPropMap("FMSLegBearingMagDeg", "/instrumentation/gps/wp/wp[1]/bearing-mag-deg"); + obj._periodicPublisher.addPropMap("FMSLegDistanceNM", "/instrumentation/gps/wp/wp[1]/distance-nm"); + obj._periodicPublisher.addPropMap("FMSLegCourseError", "/instrumentation/gps/wp/wp[1]/course-error-nm"); + obj._periodicPublisher.addPropMap("FMSLegDesiredTrack", "/instrumentation/gps/wp/wp[1]/desired-course-deg"); + obj._periodicPublisher.addPropMap("FMSLegTrackErrorAngle", "/instrumentation/gps/wp/wp[1]/course-deviation-deg"); + obj._periodicPublisher.addPropMap("FMSWayPointCourseError", "/instrumentation/gps/wp/wp[1]/course-error-nm"); + + obj._periodicPublisher.addPropMap("FMSGroundspeed", "/instrumentation/gps/indicated-ground-speed-kt"); + + obj._periodicPublisher.addPropMap("FMSNav1From", "/instrumentation/nav/from-flag"); + obj._periodicPublisher.addPropMap("FMSNav2From", "/instrumentation/nav[1]/from-flag"); + + # Custom publish method as we need to calculate some particular values manually. + obj._periodicPublisher.publish = func() { + var gpsdata = {}; + + foreach (var propmap; me._propmaps) { + var name = propmap.getName(); + gpsdata[name] = propmap.getValue(); + } + + # Some GPS properties have odd values to indicate that nothing is set, so + # remove them from the data set. + if (gpsdata["FMSLegBearingMagDeg"] == -9999) gpsdata["FMSLegBearingMagDeg"] = nil; + if (gpsdata["FMSLegDistanceNM"] == -1) gpsdata["FMSLegDistanceNM"] = nil; + + # A couple of calculated values used by the MFD Header display + var total_fuel = getprop("/consumables/fuel/tank[0]/indicated-level-gal_us") or 0.0; + total_fuel = total_fuel + (getprop("/consumables/fuel/tank[1]/indicated-level-gal_us") or 0.0); + var fuel_flow = getprop("/engines/engine[0]/fuel-flow-gph") or 1.0; + gpsdata["FuelOnBoard"] = total_fuel; + gpsdata["EnduranceHrs"] = total_fuel / fuel_flow; + + var plan = flightplan(); + + var dst = 0.0; + if (plan.getPlanSize() > 0) { + # Determine the distance to travel, based on + # - current distance to the next WP, + # - length of each subsequent leg. + dst = getprop("/instrumentation/gps/wp/wp[1]/distance-nm") or 0.0; + + if (plan.indexOfWP(plan.currentWP()) < (plan.getPlanSize() -1)) { + for(var i=plan.indexOfWP(plan.currentWP()) + 1; i < plan.getPlanSize(); i = i+1) { + var leg = plan.getWP(i); + if (leg != nil ) dst = dst + leg.leg_distance; + } + } + } + + gpsdata["FMSDistance"] = dst; + var spd = getprop("/instrumentation/gps/indicated-ground-speed-kt") or 1.0; + var time_hrs = dst / spd; + + gpsdata["FMSEstimatedTimeEnroute"] = time_hrs; + gpsdata["FMSFuelOverDestination"] = total_fuel - time_hrs * fuel_flow; + + var notification = notifications.PFDEventNotification.new( + "MFD", + 1, + notifications.PFDEventNotification.FMSData, + gpsdata); + + me._transmitter.NotifyAll(notification); + }; return obj; }, - # Custom publish method as we need to calculate some particular values manually. - publish : func() { - var gpsdata = {}; - - foreach (var propmap; me._propmaps) { - var name = propmap.getName(); - gpsdata[name] = propmap.getValue(); - } - - # Some GPS properties have odd values to indicate that nothing is set, so - # remove them from the data set. - if (gpsdata["FMSLegBearingMagDeg"] == -9999) gpsdata["FMSLegBearingMagDeg"] = nil; - if (gpsdata["FMSLegDistanceNM"] == -1) gpsdata["FMSLegDistanceNM"] = nil; - - # A couple of calculated values used by the MFD Header display - var total_fuel = getprop("/consumables/fuel/tank[0]/indicated-level-gal_us") or 0.0; - total_fuel = total_fuel + (getprop("/consumables/fuel/tank[1]/indicated-level-gal_us") or 0.0); - var fuel_flow = getprop("/engines/engine[0]/fuel-flow-gph") or 1.0; - gpsdata["FuelOnBoard"] = total_fuel; - gpsdata["EnduranceHrs"] = total_fuel / fuel_flow; - - var plan = flightplan(); - - var dst = 0.0; - if (plan.getPlanSize() > 0) { - # Determine the distance to travel, based on - # - current distance to the next WP, - # - length of each subsequent leg. - dst = getprop("/instrumentation/gps/wp/wp[1]/distance-nm") or 0.0; - - if (plan.indexOfWP(plan.currentWP()) < (plan.getPlanSize() -1)) { - for(var i=plan.indexOfWP(plan.currentWP()) + 1; i < plan.getPlanSize(); i = i+1) { - var leg = plan.getWP(i); - if (leg != nil ) dst = dst + leg.leg_distance; - } - } - } - - gpsdata["FMSDistance"] = dst; - var spd = getprop("/instrumentation/gps/indicated-ground-speed-kt") or 1.0; - var time_hrs = dst / spd; - - gpsdata["FMSEstimatedTimeEnroute"] = time_hrs; - gpsdata["FMSFuelOverDestination"] = total_fuel - time_hrs * fuel_flow; - - var notification = notifications.PFDEventNotification.new( - "MFD", - 1, - notifications.PFDEventNotification.FMSData, - gpsdata); - - me._transmitter.NotifyAll(notification); + start : func() { + me._triggeredPublisher.start(); + me._periodicPublisher.start(); }, + stop : func() { + me._triggeredPublisher.stop(); + me._periodicPublisher.stop(); + }, + + }; diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/PropertyPublisher.nas b/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/PropertyPublisher.nas index a27ca1945..70d6fdef1 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/PropertyPublisher.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/PropertyPublisher.nas @@ -131,7 +131,7 @@ var TriggeredPropertyPublisher = # Set up a listener triggering on create (to ensure all values are set at # start of day) and only on changed values. These are the last two # arguments to the setlistener call. - var listener = setlistener(prop, func(p) { me.publish(p); }, 1, 0); + var listener = setlistener(prop, func(p) { me.publish(p); }, 1, 1); append(me._listeners, listener); } }, diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstruments.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstruments.nas index 510a47b7a..c782d9813 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstruments.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstruments.nas @@ -81,7 +81,18 @@ var PFDInstruments = obj._SVGGroup.setInt("z-index", 10); obj.insetMap = fg1000.NavMap.new(obj, obj.getElement("PFD-Map-Display"), [119,601], "rect(-109px, 109px, 109px, -109px)", 0, 2); - #obj.topMenu(device, obj, nil); + # Flight Plan Window group + obj.flightplanList = PFD.GroupElement.new( + obj.pageName, + svg, + [ "FlightPlanArrow", "FlightPlanID", "FlightPlanType", "FlightPlanDTK", "FlightPlanDIS"], + 5, + "FlightPlanArrow", + 1, + "FlightPlanScrollTrough", + "FlightPlanScrollThumb", + 120 + ); obj.setController(fg1000.PFDInstrumentsController.new(obj, svg)); obj.setWindDisplay(0); @@ -97,6 +108,7 @@ var PFDInstruments = obj.updateHDG(0); obj.updateSelectedALT(0); obj.updateCRS(0); + obj.setFlightPlanVisible(0); return obj; }, @@ -705,4 +717,64 @@ var PFDInstruments = me.getElement("PFD-Map-bg").setVisible(enabled); me.insetMap.setVisible(enabled); }, + + setFlightPlanVisible : func(enabled) { + me.getElement("FlightPlanGroup").setVisible(enabled); + }, + + # Update the FlightPlan display with an updated flightplan. + setFlightPlan : func(fp) { + var elements = []; + + if (fp == nil) return; + + for (var i = 0; i < fp.getPlanSize(); i = i + 1) { + var wp = fp.getWP(i); + + var element = { + FlightPlanArrow : 0, + FlightPlanID : "", + FlightPlanType : "", + FlightPlanDTK : 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); + append(elements, element); + } + + me.flightplanList.setValues(elements); + + # 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) { + 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("FlightPlanName").setText(from ~ " / " ~ dest); + } else { + me.getElement("FlightPlanName").setText(fp.id); + } + }, + + # Update the FlightPlan display to indicate the current waypoint + updateFlightPlan : func(current_wp) { + if (current_wp == -1) return; + me.flightplanList.setCRSR(current_wp); + me.flightplanList.displayGroup(); + }, }; diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstrumentsController.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstrumentsController.nas index 2f43874ab..cd9f1fa2a 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstrumentsController.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstrumentsController.nas @@ -37,9 +37,14 @@ var PFDInstrumentsController = _last_alt_ft : 0, _last_trend : systime(), _selected_alt_ft : 0, - _heading : 0, + _heading_magnetic_deg : 0, _mag_var : 0, + _fp_active : 0, + _fp_current_wp : 0, + _current_flightplan : nil, + _fp_visible : 0, + _leg_from :0, _leg_id : "", _leg_bearing : 0, @@ -78,6 +83,12 @@ var PFDInstrumentsController = _adf_heading_deg : 0.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); + } + return obj; }, @@ -239,6 +250,37 @@ var PFDInstrumentsController = if (data["FMSLegCourseError"] != nil) me._deflection_dots = data["FMSLegCourseError"] /2.0; if (data["FMSLegCourseError"] != nil) me._leg_xtrk_nm = data["FMSLegCourseError"]; + var update_fp = 0; + + if (data["FMSFlightPlanEdited"] != nil) { + # The flightplan has changed in some way, so reload it. + update_fp = 1; + } + + if ((data["FMSFlightPlanActive"] != nil) and (data["FMSFlightPlanActive"] != me._fp_active)) { + me._fp_active = data["FMSFlightPlanActive"]; + me.page.setFlightPlanVisible(me._fp_active); + update_fp = 1; + } + + if ((data["FMSFlightPlanCurrentWP"] != nil) and (data["FMSFlightPlanCurrentWP"] != me._fp_current_wp)) { + me._fp_current_wp = data["FMSFlightPlanCurrentWP"]; + 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; + } + + me.page.updateFlightPlan(me._fp_current_wp); + } + if (me.getCDISource() == "GPS") { if (me._leg_valid == 0) { # No valid leg data, likely because there's no GPS course set @@ -357,6 +399,24 @@ var PFDInstrumentsController = return emesary.Transmitter.ReceiptStatus_OK; }, + 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; + } + }, + PFDRegisterWithEmesary : func(transmitter = nil){ if (transmitter == nil) transmitter = emesary.GlobalTransmitter; diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/PFD.nas b/Aircraft/Instruments-3d/FG1000/Nasal/PFD.nas index 8d6caad9f..9f668267a 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/PFD.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/PFD.nas @@ -61,6 +61,11 @@ var PFDDisplay = '/Aircraft/Instruments-3d/FG1000/MFDPages/PFDInstruments.svg', {'font-mapper': fontmapper}); + canvas.parsesvg(obj._svg, + '/Aircraft/Instruments-3d/FG1000/MFDPages/FlightPlanPFD.svg', + {'font-mapper': fontmapper}); + + canvas.parsesvg(obj._svg, '/Aircraft/Instruments-3d/FG1000/MFDPages/DirectToPFD.svg', {'font-mapper': fontmapper}); diff --git a/Nasal/canvas/PFD/GroupElement.nas b/Nasal/canvas/PFD/GroupElement.nas index 4d104f847..529fbbcde 100644 --- a/Nasal/canvas/PFD/GroupElement.nas +++ b/Nasal/canvas/PFD/GroupElement.nas @@ -20,9 +20,6 @@ new : func (pageName, svg, elementNames, size, highlightElement, arrow=0, scroll # must be an SVG Element [pageName][elementName]{0...pageSize} _size : size, - # Current size of the selectable elements. - _currentSize : 0, - # ElementName to be highlighted. Must be an hash value from ._elementNames _highlightElement : highlightElement, @@ -39,13 +36,14 @@ new : func (pageName, svg, elementNames, size, highlightElement, arrow=0, scroll # List of SVG elements to display the values _elements : [], - # Cursor index into the elements array + # Cursor index into the _values array _crsrIndex : 0, # Whether the CRSR is enabled _crsrEnabled : 0, - # Page index + # Page index - which _values index element[0] refers to. The currently + # selected _element has index (_crsrIndex - _pageIndex) _pageIndex : 0, }; @@ -97,64 +95,64 @@ setValues : func (values_array) { me._scrollTroughElement.setVisible(0); } - me.displayPage(); + me.displayGroup(); }, -nextPage : func() { - if (size(me._values) > ((me._pageIndex +1) * me._size)) { - me._pageIndex = me._pageIndex + 1; - me._crsrIndex = 0; - me.displayPage(); - } else { - me._crsrIndex = me._currentSize -1; +displayGroup : func () { + + # The _crsrIndex element should be displayed as close to the middle of the + # group as possible. So as the user scrolls the list appears to move around + # a static cursor position. + # + # The exceptions to this is as the _crsrIndex approaches the ends of the list. + # In these cases, we let the cursor move to the top or bottom of the list. + + # Determine the middle element + var middle_element_index = int(me._size / 2); + me._pageIndex = me._crsrIndex - middle_element_index; + + if (me._crsrIndex < middle_element_index) { + # Start of list + me._pageIndex = 0; + } else if (me._crsrIndex > (size(me._values) - middle_element_index - 1)) { + # End of list + me._pageIndex = size(me._values) - me._size; } -}, -previousPage : func() { - if (me._pageIndex > 0) { - me._pageIndex = me._pageIndex - 1; - me._crsrIndex = me._size -1; - me.displayPage(); - } else { - me._crsrIndex = 0; - } -}, + for (var i = 0; i < me._size; i = i + 1) { + if (me._pageIndex + i < size(me._values)) { + var value = me._values[me._pageIndex + i]; + foreach (var k; keys(value)) { + if (k == me._highlightElement) { + me._elements[i].unhighlightElement(); -displayPage : func () { - # Determine how many elements to display in this page - me._currentSize = math.min(me._size, size(me._values) - me._size * me._pageIndex); + if (me._arrow) { + # If we're using a HighlightElement, then we only show the element + # the cursor is on. + if (i + me._pageIndex == me._crsrIndex) { + me._elements[i].setVisible(1); + if (me._crsrEnabled) me._elements[i].highlightElement(); + } else { + me._elements[i].setVisible(0); + } - for (var i = 0; i < me._currentSize; i = i + 1) { - var value = me._values[i + me._size * me._pageIndex]; - foreach (var k; keys(value)) { - if (k == me._highlightElement) { - me._elements[i].unhighlightElement(); - - if (me._arrow) { - # If we're using a HighlightElement, then we only show the element - # the cursor is on. - if (i == me._crsrIndex) { - me._elements[i].setVisible(1); } else { - me._elements[i].setVisible(0); + me._elements[i].setVisible(1); + if (me._crsrEnabled and (i + me._pageIndex == me._crsrIndex)) + me._elements[i].highlightElement(); } - } else { - me._elements[i].setVisible(1); - } - me._elements[i].setValue(value[k]); - } else { - var name = me._pageName ~ k ~ i; - var element = me._svg.getElementById(name); - assert(element != nil, "Unable to find element " ~ name); - element.setVisible(1); - element.setText(value[k]); - } - } - } - # Hide any further elements - if (me._currentSize < me._size) { - for (var i = me._currentSize; i < me._size; i = i + 1) { + me._elements[i].setValue(value[k]); + } else { + var name = me._pageName ~ k ~ i; + var element = me._svg.getElementById(name); + assert(element != nil, "Unable to find element " ~ name); + element.setVisible(1); + element.setText(value[k]); + } + } + } else { + # We've gone off the end of the values list, so hide any further values. foreach (var k; me._elementNames) { if (k == me._highlightElement) { me._elements[i].setVisible(0); @@ -172,10 +170,9 @@ displayPage : func () { if ((me._scrollThumbElement != nil) and (me._size < size(me._values))) { # Shift the scrollbar if it's relevant - var numScrollPositions = math.ceil(size(me._values) / me._size) -1; me._scrollThumbElement.setTranslation([ me._scrollBaseTransform[0], - me._scrollBaseTransform[1] + me._scrollHeight * (me._pageIndex / numScrollPositions) + me._scrollBaseTransform[1] + me._scrollHeight * (me._crsrIndex / (size(me._values) -1)) ]); } }, @@ -190,77 +187,74 @@ addTextElement : func(name, value) { }, showCRSR : func() { - if (me._currentSize == 0) return; + if (size(me._values) == 0) return; me._crsrEnabled = 1; - me._elements[me._crsrIndex].highlightElement(); + me._elements[me._crsrIndex - me._pageIndex].highlightElement(); }, hideCRSR : func() { if (me._crsrEnabled == 0) return; - me._elements[me._crsrIndex].unhighlightElement(); + me._elements[me._crsrIndex - me._pageIndex].unhighlightElement(); # If we're using a HighlightElement, then we need to make the cursor position visible - if (me._arrow) me._elements[me._crsrIndex].setVisible(1); + if (me._arrow) me._elements[me._crsrIndex - me._pageIndex].setVisible(1); me._crsrEnabled = 0; }, setCRSR : func(index) { - me._crsrIndex = math.min(index, me._currentSize -1); + me._crsrIndex = math.min(index, size(me._values) -1); + me._crsrIndex = math.max(0, me._crsrIndex); }, getCursorElementName : func() { if (me._crsrEnabled == -1) return nil; - return me._elements[me._crsrIndex].name; + return me._elements[me._crsrIndex - me._pageIndex].name; }, isCursorOnDataEntryElement : func() { if (me._crsrEnabled == -1) return 0; - return isa(me._elements[me._crsrIndex], DataEntryElement); + return isa(me._elements[me._crsrIndex - me._pageIndex], DataEntryElement); }, enterElement : func() { if (me._crsrEnabled == 0) return; - return me._elements[me._crsrIndex].enterElement(); + return me._elements[me._crsrIndex - me._pageIndex].enterElement(); }, getValue : func() { if (me._crsrEnabled == -1) return nil; - return me._elements[me._crsrIndex].getValue(); + return me._elements[me._crsrIndex - me._pageIndex].getValue(); +}, +setValue : func(idx, key, value) { + me._values[idx][key] = value; }, clearElement : func() { if (me._crsrEnabled == 0) return; - me._elements[me._crsrIndex].clearElement(); + me._elements[me._crsrIndex - me._pageIndex].clearElement(); }, incrSmall : func(value) { if (me._crsrEnabled == 0) return; var incr_or_decr = (value > 0) ? 1 : -1; - if (me._elements[me._crsrIndex].isInEdit()) { + 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].incrSmall(); + me._elements[me._crsrIndex - me._pageIndex].incrSmall(val); } else { # Move to next selection element - me._elements[me._crsrIndex].unhighlightElement(); - me._crsrIndex = me._crsrIndex + incr_or_decr; - - if (me._crsrIndex < 0 ) me.previousPage(); - if (me._crsrIndex == me._currentSize) me.nextPage(); - - me._elements[me._crsrIndex].highlightElement(); + if (me._crsrIndex < 0 ) me._crsrIndex = 0; + if (me._crsrIndex == size(me._values)) me._crsrIndex = size(me._values) -1; + me.displayGroup(); } }, incrLarge : func(val) { if (me._crsrEnabled == 0) return; var incr_or_decr = (val > 0) ? 1 : -1; - if (me._elements[me._crsrIndex].isInEdit()) { + 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].incrLarge(); + me._elements[me._crsrIndex - me._pageIndex].incrLarge(val); } else { # Move to next selection element - me._elements[me._crsrIndex].unhighlightElement(); me._crsrIndex = me._crsrIndex + incr_or_decr; - - if (me._crsrIndex < 0 ) me.previousPage(); - if (me._crsrIndex == me._currentSize) me.nextPage(); - - me._elements[me._crsrIndex].highlightElement(); + if (me._crsrIndex < 0 ) me._crsrIndex = 0; + if (me._crsrIndex == size(me._values)) me._crsrIndex = size(me._values) -1; + me.displayGroup(); } }, };