# Copyright 2018 Stuart Buchanan # This file is part of FlightGear. # # FlightGear 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 . # # Checklist var Checklist = { new : func (mfd, myCanvas, device, svg) { var obj = { parents : [ Checklist, MFDPage.new(mfd, myCanvas, device, svg, "Checklist", "LST - CHECKLIST") ], _groupSelectVisible : 0, _checklistSelectVisible : 0, _checklistDisplayVisible : 0, }; obj.topMenu(device, obj, nil); obj.checklistGroupSelect = PFD.GroupElement.new( obj.pageName, svg, ["GroupItem"], 7, "GroupItem", 0, "GroupSelectScrollTrough", "GroupSelectScrollThumb", (165 - 120) ); obj.checklistSelect = PFD.GroupElement.new( obj.pageName, svg, ["ChecklistItem"], 7, "ChecklistItem", 0, "SelectScrollTrough", "SelectScrollThumb", (165 - 120) ); obj.checklistDisplay = ChecklistGroupElement.new( obj, svg, 16, "ScrollTrough", "ScrollThumb", (525 - 180) ); # Other dynamic text elements obj.addTextElements(["GroupName", "Name", "Next", "Finished", "NotFinished"]); # The "Next" element isn't technically dynamic, though we want it to be # highlighted as a text element. We need to set a value for it explicitly, # as it'll be set to an empty string otherwise. obj.setTextElement("Next", "GO TO NEXT CHECKLIST?"); obj.setTextElement("Finished", "* Checklist Finished *"); obj.setTextElement("NotFinished", "* CHECKLIST NOT FINISHED *"); # Hide the various groups obj.hideChecklistSelect(); obj.hideGroupSelect(); obj.setController(fg1000.ChecklistController.new(obj, svg)); return obj; }, displayChecklist : func(group, name, checklist_data) { me.setTextElement("GroupName", group); me.setTextElement("Name", name); if (checklist_data == nil) { me.checklistGroupSelect.setValues([]); me.checklistGroupSelect.setValues([]); me.checklistDisplay.setValues([]); return; } # Populate the list of groups var grouplist = []; foreach (var grp; keys(checklist_data)) { append(grouplist, { GroupItem : substr(grp, 0, 20) } ); } me.checklistGroupSelect.setValues(grouplist); # Populate the list of checklists for this group var checklist_group = checklist_data[group]; var checklistlist = []; foreach (var checklist; keys(checklist_group)) { append(checklistlist, { ChecklistItem : checklist } ); } me.checklistSelect.setValues(checklistlist); # Finally, populate the checklist itself! var checklist = checklist_group[name]; var checklistitems = []; foreach (var item; checklist) { append(checklistitems, { "ItemName" : item.Name, "ItemValue" : item.Value, "ItemBox" : 1, "ItemDots" : 1, "ItemTick" : item.Checked, "ItemSelect" : size(checklistitems), }); } me.checklistDisplay.setValues(checklistitems); }, displayChecklistSelect : func () { me.getElement("Select").setVisible(1); me.checklistSelect.displayGroup(); me.checklistSelect.showCRSR(); }, hideChecklistSelect : func () { me.getElement("Select").setVisible(0); }, isChecklistSelectVisible : func() { return me.getElement("Select").getVisible(); }, displayGroupSelect : func () { me.getElement("GroupSelect").setVisible(1); me.checklistGroupSelect.displayGroup(); me.checklistGroupSelect.showCRSR(); }, hideGroupSelect : func () { me.getElement("GroupSelect").setVisible(0); }, isGroupSelectVisible : func() { return me.getElement("GroupSelect").getVisible(); }, displayItemSelect : func () { me.checklistDisplay.displayGroup(); me.checklistDisplay.showCRSR(); }, hideItemSelect : func () { me.checklistDisplay.hideCRSR(); }, offdisplay : func() { me._group.setVisible(0); # Reset the menu colours. Shouldn't have to do this here, but # there's not currently an obvious other location to do so. for(var i = 0; i < 12; i +=1) { var name = sprintf("SoftKey%d",i); 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.getController().offdisplay(); }, ondisplay : func() { me._group.setVisible(1); me.mfd.setPageTitle(me.title); 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); pg.addMenuItem(5, "CHECK", pg, func(dev, pg, mi) { pg.getController().toggleCurrentItem(); dev.updateMenus(); }, # callback func(svg, mi) { pg.displayCheckUncheck(svg); } # Display function ); pg.addMenuItem(10, "EXIT", pg, # This should return to the previous page... func(dev, pg, mi) { dev.selectPage(pg.getMFD().getPage("NavigationMap")); }, ); pg.addMenuItem(11, "EMERGENCY", pg, func(dev, pg, mi) { pg.getController().selectEmergencyChecklist(); }, # callback ); device.updateMenus(); }, # Display function for the CHECK/UNCHECK softkey displayCheckUncheck : func (svg) { if (me.checklistDisplay.getValue()) { svg.setText("UNCHECK"); } else { svg.setText("CHECK"); } svg.setVisible(1); }, }; # A modified GroupElement for specific use by the checklist function. # # Key differences: # - Current selected item is shown in white. # - Checked items are shown in green # - Unchecked items are shown in blue. var ChecklistGroupElement = { new : func (page, svg, displaysize, scrollTroughElement=nil, scrollThumbElement=nil, scrollHeight=0, style=nil) { var obj = { parents : [ ChecklistGroupElement ], _page : page, _pageName : page.pageName, _svg : svg, _style : style, _scrollTroughElement : nil, _scrollThumbElement : nil, _scrollBaseTransform : nil, # A hash mapping keys to the element name prefix of an SVG element _textElementNames : ["ItemName", "ItemValue"], _highlightElementNames : ["ItemBox", "ItemTick", "ItemDots", "ItemSelect"], # The size of the group. For each of the ._textElementNames hash values there # must be an SVG Element [pageName][elementName]{0...pageSize} _size : displaysize, # Length of the scroll bar. _scrollHeight : scrollHeight, # List of values to display _values : [], # List of SVG elements to display the values _elements : [], # Cursor index into the _values array _crsrIndex : 0, # Whether the CRSR is enabled _crsrEnabled : 0, # Page index - which _values index element[0] refers to. The currently # selected _element has index (_crsrIndex - _pageIndex) _pageIndex : 0, }; # Optional scroll bar elements, consisting of the Thumb and the Trough *, # which will be used to display the scroll position. # * Yes, these are the terms of art for the elements. assert(((scrollTroughElement == nil) and (scrollThumbElement == nil)) or ((scrollTroughElement != nil) and (scrollThumbElement != nil)), "Both the scroll trough element and the scroll thumb element must be defined, or neither"); if (scrollTroughElement != nil) { obj._scrollTroughElement = svg.getElementById(obj._pageName ~ scrollTroughElement); assert(obj._scrollTroughElement != nil, "Unable to find scroll element " ~ obj._pageName ~ scrollTroughElement); } if (scrollThumbElement != nil) { obj._scrollThumbElement = svg.getElementById(obj._pageName ~ scrollThumbElement); assert(obj._scrollThumbElement != nil, "Unable to find scroll element " ~ obj._pageName ~ scrollThumbElement); obj._scrollBaseTransform = obj._scrollThumbElement.getTranslation(); } if (style == nil) obj._style = PFD.DefaultStyle; for (var i = 0; i < displaysize; i = i + 1) { append(obj._elements, PFD.HighlightElement.new(obj._pageName, svg, "ItemSelect" ~ i, i, obj._style)); } return obj; }, # Set the values of the group. values_array is an array of hashes, each of which # has keys that match those of ._textElementNames setValues : func (values_array) { me._values = values_array; #me._pageIndex = 0; #me._crsrIndex = 0; if (size(me._values) > me._size) { # Number of elements exceeds our ability to display them, so enable # the scroll bar. 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. if (me._scrollThumbElement != nil) me._scrollThumbElement.setVisible(0); if (me._scrollTroughElement != nil) me._scrollTroughElement.setVisible(0); } me.displayGroup(); }, 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. # Check the CRSR index is valid if (me._crsrIndex > (size(me._values) -1)) me._crsrIndex = 0; # Determine the middle element var middle_element_index = math.ceil(me._size / 2); me._pageIndex = me._crsrIndex - middle_element_index; if ((size(me._values) <= me._size) or (me._crsrIndex < middle_element_index)) { # Start of list or the list is too short to require scrolling me._pageIndex = 0; } else if (me._crsrIndex > (size(me._values) - middle_element_index - 1)) { # End of list me._pageIndex = size(me._values) - me._size; } for (var i = 0; i < me._size; i = i + 1) { if (me._pageIndex + i < size(me._values)) { var value = me._values[me._pageIndex + i]; var checked = 0; var crsr = 0; if (value["ItemTick"] == 1) checked = 1; if (me._crsrEnabled and (i + me._pageIndex == me._crsrIndex)) crsr = 1; foreach (var k; keys(value)) { var name = me._pageName ~ k ~ i; var element = me._svg.getElementById(name); assert(element != nil, "Unable to find element " ~ name); if (k == "ItemSelect") { # Display if this is the cursor element element.setVisible(crsr); } else if (k == "ItemTick") { # Check the box if appropriate element.setVisible(checked); } else if (k == "ItemBox") { # Always display the box, but don't colour it. element.setVisible(1); } else if (k == "ItemDots") { # Always display the dots element.setVisible(1); if (crsr) { # White - current cursor element.setColor("#FFFFFF"); } else if (checked) { # Green - checked element.setColor("#00FF00"); } else { # Cyan - unchecked element.setColor("#00FFFF"); } } else if ((k == "ItemName") or (k == "ItemValue")) { element.setVisible(1); # We need to fill the bounding box with black so that we don't # see the underlying dots. element.setText(value[k]) .setDrawMode(canvas.Text.TEXT + canvas.Text.FILLEDBOUNDINGBOX) .setPadding(2) .setColorFill("#000000"); if (crsr) { # White - current cursor element.setColor("#FFFFFF"); } else if (checked) { # Green - checked element.setColor("#00FF00"); } else { # Cyan - unchecked element.setColor("#00FFFF"); } } else { print("Unknown value " ~ k); } } } else { # We've gone off the end of the values list, so hide any further values. foreach (var k; me._textElementNames) { var name = me._pageName ~ k ~ i; var element = me._svg.getElementById(name); assert(element != nil, "Unable to find element " ~ name); element.setVisible(0); } foreach (var k; me._highlightElementNames) { var name = me._pageName ~ k ~ i; var element = me._svg.getElementById(name); assert(element != nil, "Unable to find element " ~ name); element.setVisible(0); } } } if ((me._scrollThumbElement != nil) and (me._size < size(me._values))) { # Shift the scrollbar if it's relevant me._scrollThumbElement.setTranslation([ me._scrollBaseTransform[0], me._scrollBaseTransform[1] + me._scrollHeight * (me._crsrIndex / (size(me._values) -1)) ]); } # Indicate whether we're finished with this checklist or not var finished = me.isComplete(); me._page.getTextElement("Finished").setVisible(finished); me._page.getTextElement("NotFinished").setVisible(! finished); # Update the softkeys, which will in particular change the CHECK/UNCHECK softkeys # appropriately. me._page.device.updateMenus(); }, isComplete : func() { var finished = 1; foreach (var entry; me._values) { if (entry["ItemTick"] == 0) { finished = 0; break; } } return finished; }, # Methods to add dynamic elements to the group. Must be called in the # scroll order, as they are simply appended to the end of the list of elements! addHighlightElement : func(name, value) { append(me._elements, HighlightElement.new(me._pageName, me._svg, name, value)); }, addTextElement : func(name, value) { append(me._elements, TextElement.new(me._pageName, me._svg, name, value)); }, showCRSR : func() { if (size(me._values) == 0) return; me._crsrEnabled = 1; me.displayGroup(); }, hideCRSR : func() { if (me._crsrEnabled == 0) return; me._crsrEnabled = 0; me.displayGroup(); }, 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; }, isCursorOnDataEntryElement : func() { if (me._crsrEnabled == -1) return 0; return isa(me._elements[me._crsrIndex - me._pageIndex], DataEntryElement); }, enterElement : func() { if (me._crsrEnabled == 0) return; # ENT on an element of the checklist checks the box, # indicated by whether the check mark is visible or not. var name = me._pageName ~ "ItemTick" ~ (me._crsrIndex - me._pageIndex); var element = me._svg.getElementById(name); element.setVisible(1); return element.getVisible(); }, getValue : func() { if (me._crsrEnabled == -1) return nil; # In this case, all we care about is whether this particular value is # checked or not. var name = me._pageName ~ "ItemTick" ~ (me._crsrIndex - me._pageIndex); var element = me._svg.getElementById(name); return element.getVisible(); }, setValue : func(idx, key, value) { me._values[idx][key] = value; }, clearElement : func() { if (me._crsrEnabled == 0) return; # CLR on an element of the checklist unchecks the box, # indicated by whether the check mark is visible or not. var name = me._pageName ~ "ItemTick" ~ (me._crsrIndex - me._pageIndex); var element = me._svg.getElementById(name); element.setVisible(0); return element.getVisible(); }, incrSmall : func(value) { if (me._crsrEnabled == 0) return; var incr_or_decr = (value > 0) ? 1 : -1; if (me._elements[me._crsrIndex - me._pageIndex].isInEdit()) { # We're editing, so pass to the element. me._elements[me._crsrIndex - me._pageIndex].incrSmall(val); } else { # Move to next selection element me._crsrIndex = me._crsrIndex + incr_or_decr; 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 - me._pageIndex].isInEdit()) { # We're editing, so pass to the element. me._elements[me._crsrIndex - me._pageIndex].incrLarge(val); } else { # Move to next selection element me._crsrIndex = me._crsrIndex + incr_or_decr; if (me._crsrIndex < 0 ) me._crsrIndex = 0; if (me._crsrIndex == size(me._values)) me._crsrIndex = size(me._values) -1; me.displayGroup(); } }, };