From 9f7e17a43433b7c8f8cc04aa2af95389fe4efdb3 Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Thu, 12 Apr 2018 22:24:57 +0100 Subject: [PATCH] Support for of checklists. Checklists now support grouping by placing a group of checklists under a node. e.g. /sim/checklists/group/checklist The group can have a , which is used by the checklist dialog and can be used by other interfaces, such as the FG1000. --- Docs/README.checklists | 26 +++- Nasal/checklist.nas | 131 +++++++++-------- gui/dialogs/checklist.xml | 299 +++++++++++++++++++++++++------------- 3 files changed, 293 insertions(+), 163 deletions(-) diff --git a/Docs/README.checklists b/Docs/README.checklists index 9567e1cff..18cf1db59 100644 --- a/Docs/README.checklists +++ b/Docs/README.checklists @@ -7,6 +7,8 @@ the Help->Checklists menu within the simulator. Tutorials are automatically generated from checklists on startup. Each checklist is defined as a property tree under /sim/checklists/checklist[n] +or /sim/checklists/group[n]/checklist[m] + with the following tags - Name of the checklist @@ -16,17 +18,29 @@ with the following tags <value> - One or more values for the checklist item, to appear on the right hand side <marker> - A tutorial marker (displayed when the user presses the ? button) - This can be easily placed using the Help->Display Tutorial Marker. + This can be easily placed using the Help->Display Tutorial Marker. Contains x-m, y-m, z-m and scale tag. <condition> - Optional standard FlightGear condition node that evaluates when the checklist item has been completed. <binding> - Zero or more bindings to execute the checklist item. Allows the user - to have their virtual co-pilot perform the action if they select the + to have their virtual co-pilot perform the action if they select the ">" button next to the checklist item. - -The <page> tag may be omitted for single-page checklists, with the <item> tags + +The <page> tag may be omitted for single-page checklists, with the <item> tags immediately under the <checklist[n]> node. -See the c172p for an example of this in action (Aircraft/c172p/c172-checklists.xml). +Checklists may be grouped under <group> nodes with a <name> tag decribing the +group. For example - +<group> + <name>Emergency</name> + <checklist>... + <checklist>... +</group> +<group> + <name>Normal</name> + <checklist>... + <checklist>... +</group> + +See the c172p for an example of this in action (Aircraft/c172p/c172-checklists.xml). diff --git a/Nasal/checklist.nas b/Nasal/checklist.nas index 27f852a15..6d29fbbeb 100644 --- a/Nasal/checklist.nas +++ b/Nasal/checklist.nas @@ -3,69 +3,80 @@ # Convert checklists into full tutorials. var convert_checklists = func { - if ((props.globals.getNode("/sim/checklists") != nil) and - (props.globals.getNode("/sim/checklists").getChildren("checklist") != nil) and - (size(props.globals.getNode("/sim/checklists").getChildren("checklist")) > 0) ) - { - var checklists = props.globals.getNode("/sim/checklists").getChildren("checklist"); - var tutorials = props.globals.getNode("/sim/tutorials", 1); + if (props.globals.getNode("/sim/checklists") == nil) return; - foreach (var ch; checklists) { - var name = ch.getNode("title", 1).getValue(); - var tutorial = tutorials.getNode("tutorial[" ~ size(tutorials.getChildren("tutorial")) ~ "]", 1); + var tutorials = props.globals.getNode("/sim/tutorials", 1); + var groups = props.globals.getNode("/sim/checklists").getChildren("group"); + var checklists = []; - # Initial high level config - tutorial.getNode("name", 1).setValue("Checklist: " ~ name); - var description = - "Tutorial to run through the " ~ - name ~ - " checklist.\n\nChecklist available through the Help->Checklists menu.\n\n"; - - var step = tutorial.getNode("step", 1); - step.getNode("message", 1).setValue("Checklist: " ~ name); - - # Now go through each of the checklist items and generate a tutorial step - # for each. - - # Checklist may consist of one or more pages. - var pages = ch.getChildren("page"); - - if (size(pages) == 0) { - # Or no pages at all, in which case we need to create a checklist of one page - append(pages, ch); - } - - foreach (var page; pages) { - foreach (var item; page.getChildren("item")) { - step = tutorial.getNode("step["~ size(tutorial.getChildren("step")) ~ "]", 1); - - var msg = item.getNode("name", 1).getValue(); - - if (size(item.getChildren("value")) > 0) { - msg = msg ~ " :"; - foreach (var v; item.getChildren("value")) { - msg = msg ~ " " ~ v.getValue(); - } - } - - step.getNode("message", 1).setValue(msg); - description = description ~ msg ~ "\n"; - - if (item.getNode("condition") != nil) { - var cond = step.getNode("exit", 1).getNode("condition", 1); - props.copy(item.getNode("condition"), cond); - } - - if (item.getNode("marker") != nil) { - var marker= step.getNode("marker", 1); - props.copy(item.getNode("marker"), marker); - } - - } - } - - tutorial.getNode("description", 1).setValue(description); + if (size(groups) > 0) { + foreach (var grp; groups) { + var checks = grp.getChildren("checklist"); + foreach (var chk; checks) { + append(checklists, chk); + } } + } else { + checklists = props.globals.getNode("/sim/checklists").getChildren("checklist"); + } + + if (size(checklists) == 0) return; + + foreach (var ch; checklists) { + var name = ch.getNode("title", 1).getValue(); + var tutorial = tutorials.getNode("tutorial[" ~ size(tutorials.getChildren("tutorial")) ~ "]", 1); + + # Initial high level config + tutorial.getNode("name", 1).setValue("Checklist: " ~ name); + var description = + "Tutorial to run through the " ~ + name ~ + " checklist.\n\nChecklist available through the Help->Checklists menu.\n\n"; + + var step = tutorial.getNode("step", 1); + step.getNode("message", 1).setValue("Checklist: " ~ name); + + # Now go through each of the checklist items and generate a tutorial step + # for each. + + # Checklist may consist of one or more pages. + var pages = ch.getChildren("page"); + + if (size(pages) == 0) { + # Or no pages at all, in which case we need to create a checklist of one page + append(pages, ch); + } + + foreach (var page; pages) { + foreach (var item; page.getChildren("item")) { + step = tutorial.getNode("step["~ size(tutorial.getChildren("step")) ~ "]", 1); + + var msg = item.getNode("name", 1).getValue(); + + if (size(item.getChildren("value")) > 0) { + msg = msg ~ " :"; + foreach (var v; item.getChildren("value")) { + msg = msg ~ " " ~ v.getValue(); + } + } + + step.getNode("message", 1).setValue(msg); + description = description ~ msg ~ "\n"; + + if (item.getNode("condition") != nil) { + var cond = step.getNode("exit", 1).getNode("condition", 1); + props.copy(item.getNode("condition"), cond); + } + + if (item.getNode("marker") != nil) { + var marker= step.getNode("marker", 1); + props.copy(item.getNode("marker"), marker); + } + + } + } + + tutorial.getNode("description", 1).setValue(description); } } diff --git a/gui/dialogs/checklist.xml b/gui/dialogs/checklist.xml index 97fb4389b..7069c6cf4 100644 --- a/gui/dialogs/checklist.xml +++ b/gui/dialogs/checklist.xml @@ -13,48 +13,129 @@ <nasal> <open><![CDATA[ - var dlgRoot = cmdarg(); + var dlgname = dlgRoot.getNode("name").getValue(); + + + # Update the checklist-combo with appropriate set of checklists for + # the user to select from + var setChecklistGroup = func(group_name) { + var combo = gui.findElementByName(dlgRoot, "checklist-combo"); + combo.removeChildren("value"); + var idx = 0; + + foreach (var name; checklist_group[group_name]) { + combo.getChild("value", idx, 1).setValue(name); + idx = idx + 1; + } + + var checklist_name = checklist_group[group_name][0]; + + var pgs = checklist_size[checklist_name]; + if (pgs == nil) pgs = 1; + + setprop("/sim/gui/dialogs/checklist/selected-checklist-group", group_name); + setprop("/sim/gui/dialogs/checklist/selected-checklist-max-pages", pgs); + setprop("/sim/gui/dialogs/checklist/selected-page-text", "1 / " ~ pgs); + setprop("sim/gui/dialogs/checklist/selected-checklist", checklist_name); + gui.dialog_update("checklist", "checklist-combo", "checklist-table-group"); + }; + + var groups = props.globals.getNode("/sim/checklists", 1).getChildren("group"); var checklists = props.globals.getNode("/sim/checklists", 1).getChildren("checklist"); var checklist_size = {}; - - + var checklist_group = {}; + var current_group = nil; + + # Set up the list of groups so the user can select a group and then + # a checklist. + var groups = props.globals.getNode("/sim/checklists").getChildren("group"); + + if (size(groups) > 0) { + foreach (var grp; groups) { + var name = grp.getNode("name", 1).getValue(); + var checks = grp.getChildren("checklist"); + foreach (var chk; checks) { + var title = chk.getNode("title", 1).getValue(); + if (current_group == nil) current_group = name; + append(checklists, chk); + if (checklist_group[name] == nil) checklist_group[name] = []; + append(checklist_group[name], title); + } + } + } else { + checklists = props.globals.getNode("/sim/checklists").getChildren("checklist"); + foreach (var chk; checklists) { + var title = chk.getNode("title", 1).getValue(); + var grp = "Standard"; + var items = []; + if (find("emergency", string.lc(title)) != -1) { + grp = "EMERGENCY"; + } + + if (current_group == nil) current_group = grp; + + if (checklist_group[grp] == nil) checklist_group[grp] = []; + append(checklist_group[grp], title); + } + } + if (size(checklists) > 0) { - if (getprop("sim/gui/dialogs/checklist/selected-checklist") == nil) { - setprop("sim/gui/dialogs/checklist/selected-checklist", checklists[0].getNode("title", 1).getValue()); - setprop("sim/gui/dialogs/checklist/selected-page", 0); + var current_checklist = getprop("sim/gui/dialogs/checklist/selected-checklist"); + if (current_checklist == nil) { + current_checklist = checklists[0].getNode("title", 1).getValue(); + setprop("sim/gui/dialogs/checklist/selected-checklist", current_checklist); + setprop("sim/gui/dialogs/checklist/selected-page", 0); } - - var combo = gui.findElementByName(dlgRoot, "checklist-combo"); - var group = gui.findElementByName(dlgRoot, "checklist-table-group"); - + + foreach (var grp; keys(checklist_group)) { + foreach (chklist; checklist_group[grp]) { + if (chklist == current_checklist) { + setChecklistGroup(grp); + } + } + } + + var combo = gui.findElementByName(dlgRoot, "checklist-group-combo"); + var idx = 0; + + foreach (var grp_name; keys(checklist_group)) { + combo.getChild("value", idx, 1).setValue(grp_name); + idx = idx + 1; + } + + dlgRoot.setValues({"dialog-name": dlgname, "object-name": "checklist-group-combo"}); + fgcommand("dialog-update", dlgRoot); + + var group = gui.findElementByName(dlgRoot, "checklist-table-group"); + var table_count = 0; - + forindex (var idx; checklists) { var checklist_name = checklists[idx].getNode("title", 1).getValue(); - combo.getChild("value", idx, 1).setValue(checklist_name); - + # Checklist may consist of one or more pages. var pages = checklists[idx].getChildren("page"); - + if (size(pages) == 0) { # Or no pages at all, in which case we need to create a checklist of one page append(pages, checklists[idx]); } - - checklist_size[checklist_name] = size(pages); - + + checklist_size[checklist_name] = size(pages); + if (idx == 0) { + setprop("/sim/gui/dialogs/checklist/next-available", 1); setprop("/sim/gui/dialogs/checklist/selected-checklist-max-pages", checklist_size[checklist_name]); - setprop("/sim/gui/dialogs/checklist/selected-page-text", "1 / " ~ checklist_size[checklist_name]); + setprop("/sim/gui/dialogs/checklist/selected-page-text", "1 / " ~ checklist_size[checklist_name]); } - - forindex (var p; pages) { - var c = pages[p]; + + forindex (var p; pages) { + var c = pages[p]; var row = 0; - + # Set up a new table, only visible when this checklist is selected. var table = group.getChild("group", table_count, 1); table.getNode("row", 1).setValue(0); @@ -62,29 +143,29 @@ table.getNode("default-padding", 1).setValue(4); table.getNode("layout", 1).setValue("table"); table.getNode("valign", 1).setValue("top"); - + # Set up conditional to only display when the checklist is selected # and the page is correct. - var vis = table.getNode("visible", 1).getNode("and", 1); + var vis = table.getNode("visible", 1).getNode("and", 1); var e = vis.getChild("equals", 0, 1); e.getNode("property", 1).setValue("sim/gui/dialogs/checklist/selected-checklist"); e.getNode("value", 1).setValue(checklists[idx].getNode("title").getValue()); e = vis.getChild("equals", 1, 1); e.getNode("property", 1).setValue("sim/gui/dialogs/checklist/selected-page"); e.getNode("value", 1).setValue(p); - + var items = c.getChildren("item"); var txtcount = 0; var btncount = 0; - - forindex (var i; items) { + + forindex (var i; items) { var item = items[i]; var t = table.getChild("text", txtcount, 1); - txtcount += 1; + txtcount += 1; var values = item.getChildren("value"); - + if (size(values) == 0) { # Single name element with no values. Used as title t.getNode("halign", 1).setValue("center"); @@ -98,7 +179,7 @@ t.getNode("row", 1).setValue(row); t.getNode("col", 1).setValue(0); t.getNode("label", 1).setValue(item.getNode("name", 1).getValue()); - + forindex (var v; values) { var t = table.getChild("text", txtcount, 1); txtcount += 1; @@ -106,23 +187,23 @@ t.getNode("row", 1).setValue(row); if (v > 0) { # The second row of values can overlap with the - # first column if required - helps keep the + # first column if required - helps keep the # checklist dialog as compact as possible t.getNode("col", 1).setValue(0); t.getNode("colspan", 1).setValue(2); } else { t.getNode("col", 1).setValue(1); } - - t.getNode("label", 1).setValue(values[v].getValue()); - + + t.getNode("label", 1).setValue(values[v].getValue()); + # If there's a complete node, it contains a condition # that can be checked to ensure the checklist item is - # complete. We display this item in yellow while the + # complete. We display this item in yellow while the # condition is not met, and green once it is complete. - + var condition = item.getNode("condition"); - + if (condition != nil) { var vis = t.getNode("visible", 1); props.copy(condition, vis); @@ -135,39 +216,39 @@ # Now create an amber version for when the condition # is not met. t = table.getChild("text", txtcount, 1); - txtcount += 1; - + txtcount += 1; + t.getNode("halign", 1).setValue("right"); t.getNode("row", 1).setValue(row); if (v > 0) { # The second row of values can overlap with the - # first column if required - helps keep the + # first column if required - helps keep the # checklist dialog as compact as possible t.getNode("col", 1).setValue(0); t.getNode("colspan", 1).setValue(2); } else { t.getNode("col", 1).setValue(1); } - - t.getNode("label", 1).setValue(values[v].getValue()); - + + t.getNode("label", 1).setValue(values[v].getValue()); + c = t.getNode("color", 1); c.getNode("red", 1).setValue(1.0); c.getNode("green", 1).setValue(0.7); c.getNode("blue", 1).setValue(0.2); - + vis = t.getNode("visible", 1).getNode("not", 1); - props.copy(condition, vis); + props.copy(condition, vis); } - + # If there is a marker node we display a small # button that enables the marker. var marker = item.getNode("marker"); - + if ((v == 0) and (marker != nil)) { var s = marker.getNode("scale"); var scale = s != nil ? s.getValue() : 1; - + var btn = table.getChild("button", btncount, 1); btncount += 1; btn.getNode("row", 1).setValue(row); @@ -184,12 +265,12 @@ marker.getNode("z-m", 1).getValue() ~ ", " ~ scale ~ ");"); } - + # If there's one or more binding nodes we display a - # small button that executes the binding. Used to + # small button that executes the binding. Used to # demonstrate the checklist item var bindings = item.getChildren("binding"); - + if ((v == 0) and (size(bindings) > 0)) { var btn = table.getChild("button", btncount, 1); btncount += 1; @@ -199,19 +280,19 @@ btn.getNode("pref-height", 1).setValue(20); btn.getNode("padding", 1).setValue(1); btn.getNode("legend", 1).setValue(">"); - + forindex (var bdg; bindings) { var binding = btn.getChild("binding", bdg, 1); props.copy(bindings[bdg], binding); } } - - row = row + 1; + + row = row + 1; } - + table_count = table_count + 1; } - } + } } } @@ -219,8 +300,8 @@ setprop("/sim/gui/dialogs/checklist/selected-checklist-max-pages", checklist_size[s]); setprop("/sim/gui/dialogs/checklist/selected-page", 0); setprop("/sim/gui/dialogs/checklist/next-available", 1); - setprop("/sim/gui/dialogs/checklist/selected-page-text", "1 / " ~ checklist_size[s]); - + setprop("/sim/gui/dialogs/checklist/selected-page-text", "1 / " ~ checklist_size[s]); + } else { var group = gui.findElementByName(dlgRoot, "checklist-table-group"); var table = group.getNode("text", 1); @@ -230,12 +311,12 @@ table.getNode("layout", 1).setValue("table"); table.getNode("valign", 1).setValue("top"); table.getNode("halign", 1).setValue("center"); - table.getNode("label", 1).setValue("No checklists exist for this aircraft"); + table.getNode("label", 1).setValue("No checklists exist for this aircraft"); } - + var placeMarker = func(x,y,z,scale) { var markerN = props.globals.getNode("/sim/model/marker", 1); - + markerN.setValues({ "x/value": x, "y/value": y, @@ -243,34 +324,34 @@ "scale/value": scale, "arrow-enabled": 1, }); - } - + }; + var nextPage = func() { var currentPage = getprop("/sim/gui/dialogs/checklist/selected-page"); var s = getprop("/sim/gui/dialogs/checklist/selected-checklist"); - + if (currentPage < (checklist_size[s] - 1)) { - setprop("/sim/gui/dialogs/checklist/selected-page", currentPage + 1); - } + setprop("/sim/gui/dialogs/checklist/selected-page", currentPage + 1); + } if (currentPage == (checklist_size[s] - 2)) { setprop("/sim/gui/dialogs/checklist/next-available", 0); } - - setprop("/sim/gui/dialogs/checklist/selected-page-text", (currentPage + 2) ~ " / " ~ checklist_size[s]); - } - + + setprop("/sim/gui/dialogs/checklist/selected-page-text", (currentPage + 2) ~ " / " ~ checklist_size[s]); + }; + var previousPage = func() { var currentPage = getprop("/sim/gui/dialogs/checklist/selected-page"); - + if (currentPage > 0) { - setprop("/sim/gui/dialogs/checklist/selected-page", currentPage - 1); - } - + setprop("/sim/gui/dialogs/checklist/selected-page", currentPage - 1); + } + setprop("/sim/gui/dialogs/checklist/next-available", 1); - setprop("/sim/gui/dialogs/checklist/selected-page-text", currentPage ~ " / " ~ checklist_size[s]); - } - + setprop("/sim/gui/dialogs/checklist/selected-page-text", currentPage ~ " / " ~ checklist_size[s]); + }; + var setTransparency = func(updateDialog){ var alpha = (getprop("/sim/gui/dialogs/checklist/transparent") or 0); dlgRoot.getNode("color/alpha").setValue(1-alpha*0.3); @@ -280,11 +361,11 @@ fgcommand("dialog-close", n); fgcommand("dialog-show", n); } - } + }; setTransparency(0); - + ]]></open> - + <close><![CDATA[ # Hide the marker. setprop("/sim/model/marker/arrow-enabled", 0); @@ -324,6 +405,30 @@ <label> </label> </text> + <text> + <halign>right</halign> + <label>Group:</label> + </text> + + <combo> + <name>checklist-group-combo</name> + <property>/sim/gui/dialogs/checklist/selected-checklist-group</property> + <editable>false</editable> + <pref-width>230</pref-width> + <halign>fill</halign> + <binding> + <command>dialog-apply</command> + <object-name>checklist-group-combo</object-name> + </binding> + <binding> + <command>nasal</command> + <script> + var s = getprop("/sim/gui/dialogs/checklist/selected-checklist-group"); + setChecklistGroup(s); + </script> + </binding> + </combo> + <text> <halign>right</halign> <label>Checklist:</label> @@ -343,19 +448,19 @@ <command>nasal</command> <script> var s = getprop("/sim/gui/dialogs/checklist/selected-checklist"); - + setprop("/sim/gui/dialogs/checklist/selected-checklist-max-pages", checklist_size[s]); setprop("/sim/gui/dialogs/checklist/selected-page", 0); - setprop("/sim/gui/dialogs/checklist/next-available", 1); - setprop("/sim/gui/dialogs/checklist/selected-page-text", "1 / " ~ checklist_size[s]); - + setprop("/sim/gui/dialogs/checklist/next-available", 1); + setprop("/sim/gui/dialogs/checklist/selected-page-text", "1 / " ~ checklist_size[s]); + </script> </binding> - + </combo> - + <empty><stretch>true</stretch></empty> - + <!-- gap to next element --> <text> <halign>right</halign> @@ -380,32 +485,32 @@ <halign>right</halign> </checkbox> </group> - + <hrule/> - + <group> <default-padding>4</default-padding> <halign>fill</halign> <layout>table</layout> <name>checklist-table-group</name> </group> - + <group> <default-padding>5</default-padding> <halign>fill</halign> <layout>hbox</layout> - + <visible> <greater-than> <property>/sim/gui/dialogs/checklist/selected-checklist-max-pages</property> <value>1</value> </greater-than> </visible> - + <empty> <stretch>true</stretch> </empty> - + <button> <legend>Previous page</legend> <pref-height>24</pref-height> @@ -421,11 +526,11 @@ <script>previousPage();</script> </binding> </button> - + <empty> <stretch>true</stretch> </empty> - + <text> <halign>center</halign> <label>XX/XX</label> @@ -454,7 +559,7 @@ <empty> <stretch>true</stretch> </empty> - + </group> - + </PropertyList>