Howto talk:Processing legacy PUI dialogs using Canvas: Difference between revisions

Jump to navigation Jump to search
→‎pastebin ...: new section
(→‎pastebin ...: new section)
Line 66: Line 66:


</syntaxhighlight>
</syntaxhighlight>
== pastebin ... ==
<pre># beginning of PUI namespace
var PUI = {
# static
unsupported: func(tag, what="Widget") {
print("PUI/XML ",what," not yet supported:", tag);
},
##
# call with exception handling (stub)
#
safeCall: func(codeObj, namespace=nil) {
  var retval = call(codeObj, [], nil, namespace, var err=[]);
  if (size(err)) {
debug.dump(err);
  }
  return retval;
},
##
# generator for creating callbacks used for all Nasal compilation
# TODO: this should take a closure for the <open> and <load> blocks ...
makeTagCompiler: func(codeTag, description=nil) {
if (description==nil) description = codeTag ~" block";
return func(node, namespace=nil) {
var codeSrc = node.getNode(codeTag).getValue();
return func PUI.safeCall(compile(codeSrc), namespace);
};
}, # makeTagCompiler
setupCommandBinding: func(window, node) {
var command = node.getNode("command").getValue();
if (command=="nasal") return PUI.setupScriptBinding(node);
# TODO: fgcommands relating to PUI must be dynamically patched/overridden (e.g. dialog-apply/dialog-close)
if (contains(PUI.fgcommandPatches, command)) {
print("Most PUI related fgcommand patching not yet implemented");
# returns a patched fgcommand (Nasal callback) for the window/node
return PUI.fgcommandPatches[command](window, node);
}
# else print("Returning non-PUI fgcommand");
# standard fgcommand, wrapped in a Nasal func
#
return func() {
fgcommand(command, node);
  };
}, # setupCommandBinding
setupScriptBinding: func(node) {
# TODO: needs to be bound to the dialog's open block
return PUI.nasalBindingCompiler(node);
}, # setupScriptBinding
# returns a vector of callback bindings (fgcommands and nasal scripts)
setupBindings: func(window, node) {
var callbacks=[];
var b=node.getChildren("binding");
foreach(var binding; b) {
        append(callbacks, PUI.setupCommandBinding(window, binding));
}
return callbacks;
}, # setupBindings
makeBindingsCallback: func(bindingsVec) {
return func() {
foreach(var b; bindingsVec) {
# bindings are invoked via call to ensure that
# runtime errors don't block other bindings
# i.e. if one binding fails, the other ones will still be executed
# PUI.safeCall(b);
b();
} # foreach binding
} # return anonymous func
}, # makeBindingsCallback
###
# TODO remove default PUI close button based on heuristic ?
###
# TODO: Some PUI fgcommands may need to be patched/replaced dynamically
# and mapped to their Canvas equivalents - e.g. to call a window.del() destructor
fgcommandPatches: {
'dialog-close': func(window, node) {
return func window.del();
},
'dialog-apply': func(window, node) {
print("dialog-apply is not yet implemented ...");
return func;
},
'dialog-update': func(window, node) {
print("patched dialog-update is not yet implemented");
return func;
},
'dialog-show': func(window, node) {
return func;
},
}, # fgcommandPatches
###
# TODO: unify node checking (vector], callback to be invoked if found
applyPUIAttributes: func(widget, layout, node) {
var stretch = node.getNode('stretch');
if (stretch != nil) {
print("stretch not yet implemented");
}
var halign = node.getNode('halign');
if (halign != nil) {
print("halign not yet supported");
}
var width = node.getNode('pref-width');
var height = node.getNode('pref-height');
height = height != nil ? height.getValue() : 32;
if (width!=nil and height!=nil)
widget.setFixedSize(width.getValue(), height);
# TODO: visible & enable, live
var visible = node.getNode("visible");
if (visible != nil) print("visible tag not yet supported");
var live = node.getNode("live");
if (live != nil) {
# TODO: listeners should be stored and freed properly
print("live properties not yet supported:", node.getNode('property').getValue());
}
var format = node.getNode('format');
if (format !=nil) {
var property=node.getNode('property').getValue();
var legend=sprintf(format.getValue(), getprop(property));
widget.setText(legend);
}
},
# checks if a tag is a known widget (i.e. listed in the WidgetFactory hash)
isWidget: func(tag) {
return contains(PUI.WidgetFactory,tag);
},
getOrCreateTableCell: func(layout, row, col) {
},
# pre-allocate a table data structure using ROW/COLS as per README.layout
# and Canvas vbox/hbox layouts
createTable: func(layout, node) {
var nodeHash = node.getValues();
var topLevelLayout = layout;
foreach(var child; node.getChildren()) {
var tagType = child.getName();
if (PUI.isWidget(tagType)) {
print("widget found in table:", tagType);
} # known widget
} # foreach
return [];
}, # createTable()
##
# deals with different layouts (group/frame)
# and creates a table layout using Canvas vbox/hbox layouts
LayoutFactory: {
'layout': func(window, root, layout, node) {
debug.dump(node);
var mode = node.getNode('layout').getValue(); #getValues().layout;
var myLayout = PUI.LayoutFactory[mode](window, root, layout, node);
#layout.addItem(myLayout);
},
'hbox': func(window, root, layout, node) {
var myLayout = canvas.HBoxLayout.new();
PUI.parseWidgets(window, root, myLayout, node);
layout.addItem(myLayout);
}, # hbox
'vbox': func(window, root, layout, node) {
var myLayout = canvas.VBoxLayout.new();
PUI.parseWidgets(window, root, myLayout, node);
layout.addItem(myLayout);
}, #vbox
'table': func(window, root, layout, node) {
var layouts = PUI.createTable(layout, node);
}, #table
}, # LayoutFactory
##
# maps PUI tags ($FG_ROOT/Docs/README.gui) to callbacks/widgets implementing the Canvas equivalent
WidgetFactory: {
'frame': func(window, root, layout, node) {
# groups and frames are equivalent
PUI.WidgetFactory.group(window, root, layout, node);
}, # frame
'group': func(window, root, layout, node) {
var myLayout = PUI.LayoutFactory.layout(window, root, layout, node);
}, # group
'dummy': func(window, root, layout, node) {
PUI.WidgetFactory.button(window, root, layout, node);
}, # dummy widget, adds a button
'text': func(window, root, layout, node) {
var legend = node.getNode('label').getValue();
var label = canvas.gui.widgets.Label.new(root, canvas.style, {wordWrap: 0});
label.setText(legend);
PUI.applyPUIAttributes(label, layout, node);
layout.addItem(label);
}, # text
'button': func(window, root, layout, node) {
var legend = node.getNode('legend').getValue();
var button = canvas.gui.widgets.Button.new(root, canvas.style, {})
        .setText(legend);
layout.addItem(button);
PUI.applyPUIAttributes(button, layout, node);
# set up bindings: (should be moved to applyPUI*)
var bindings = PUI.setupBindings(window, node);
button.listen("clicked", PUI.makeBindingsCallback(bindings));
}, # button
'input': func(window, root, layout, node) {
var input = canvas.gui.widgets.LineEdit.new(root, canvas.style, {});
PUI.applyPUIAttributes(input, layout, node);
layout.addItem(input);
}, # input
'checkbox': func(window, root, layout, node) {
var hbox=canvas.HBoxLayout.new();
var checkbox = canvas.gui.widgets.CheckBox.new(root, canvas.style, {});
hbox.addItem(checkbox);
# add a label
PUI.WidgetFactory.text(window, root, hbox, node);
layout.addItem(hbox);
}, # checkbox
'radio': func(window, root, layout, node) {
}, # radio
'combo': func(window, root, layout, node) {
PUI.unsupported("combo");
}, # combo
# this one is special in that it is actually shared between different list implementations:
'list': func(window, root, layout, node) {
var vbox = canvas.VBoxLayout.new();
layout.addItem(vbox);
var scroll = canvas.gui.widgets.ScrollArea.new(root, canvas.style, {size: [96, 128]}).move(20, 100);
vbox.addItem(scroll, 1);
var scrollContent =
      scroll.getContent()
            .set("font", "LiberationFonts/LiberationSans-Bold.ttf")
            .set("character-size", 16)
            .set("alignment", "left");
var list = canvas.VBoxLayout.new();
scroll.setLayout(list);
# returns the created list to the caller, which means we can reuse the code for other types of lists (airports, waypoints, property browser)
PUI.applyPUIAttributes(scroll, layout, node);
return {layout:list, root:scrollContent};
}, # list
'airport-list': func(window, root, layout, node) {
var list = PUI.WidgetFactory.list(window, root, layout, node);
}, # airport-list
'property-list': func(window, root, layout, node) {
var myList = PUI.WidgetFactory.list(window, root, layout, node);
var list = myList.layout;
var scrollContent = myList.root;
# we will be using this callback to populate/update the property browser
var updateList = func(node) {
var upButton = canvas.gui.widgets.Button.new(scrollContent, canvas.style, {})
        .setText('..' )
        .setFixedSize(200, 25);
upButton._view._root.addEventListener("dblclick", func() {
list.clear();
var parent = node.getParent();
if (parent == nil) parent = node;
updateList( parent );
});
# add the navigation button (..)
list.addItem( upButton );
# next, add all child nodes
# TODO: this should probbably be sorted ...
foreach(var n; node.getChildren() ) {
# TODO: should probably use label instead here ?
var PropertyButton = canvas.gui.widgets.Button.new(scrollContent, canvas.style, {})
        .setText(n.getPath() )
        .setFixedSize(200, 25);
# save the node in a closure for later use
(func() {
var n = n;
var bindings = PUI.setupBindings(window, node);
# FIXME: this needs to use the patched fgcommand bindings ... for dialog-apply etc
        PropertyButton.listen("clicked", PUI.makeBindingsCallback(bindings));
PropertyButton._view._root.addEventListener("click", func() {
print("click event:",n.getPath() );
});
# this will clear the list and call the function with the selected new node
PropertyButton._view._root.addEventListener("dblclick", func() {
print("double click event",n.getPath() );
# clear list
list.clear();
updateList( n );
});
}()); # call the anonymous function
list.addItem(PropertyButton);
} # foreach child
} # updateList()
# invoke the updateList() function with thhe root node
updateList( props.getNode("") );
}, # property-list
'waypointlist': func(window, root, layout, node) {
var list = PUI.WidgetFactory.list(window, root, layout, node);
}, # waypointlist
'select': func(window, root, layout, node) {
}, # select
'slider':func(window, root, layout, node) {
}, # slider
'dial': func(window, root, layout, node) {
}, # dial
'textbox':func(window, root, layout, node) {
# TODO: should be list wrapping a label ...
var myList = PUI.WidgetFactory.list(window, root, layout, node);
var list = myList.layout;
var scrollContent = myList.root;
var input = canvas.gui.widgets.LineEdit.new(scrollContent, canvas.style, {});
        #PUI.applyPUIAttributes(input, layout, node);
        list.addItem(input);
}, # textbox
'hrule': func(window, root, layout, node) {
PUI.unsupported("hrule");
}, #hrule
'vrule': func(window, root, layout, node) {
PUI.unsupported("vrule");
}, # vrule
'canvas': func(window, root, layout, node) {
}, # canvas
'map': func(window, root, layout, node) {
  # must be mapped to a Canvas that instantiates a MapStructure map
}, # map
'loglist': func(window, root, layout, node) {
  #die("loglist widget cannot be reimplemented due to missing C++ hooks: logstream");
}, # loglist
'empty':  func(window, root, layout, node) {
layout.addStretch(1);
}, # empty
}, # WidgetFactory
parseWidgets: func(window, root, layout, node) {
foreach(var tag; node.getChildren()) {
var t=tag.getName() ;
if(contains(PUI.WidgetFactory, t)) {
PUI.WidgetFactory[t] (window, root, layout, tag);
# TODO: applyPUIAttributes
# TODO: addItem/layout here
# TODO: process bindings
}
# TODO: check if Nasal tag, layout tag, styling tag
elsif (!contains(PUI.LayoutFactory, t) )
PUI.unsupported(t, "Tag");
} # foreach tag
},
##
# this is basically the entrypoint of the whole parser, it's what will be called by the fgcommand
showDialog: func(node) {
var filename = node.getNode('dialog-name').getValue() or die("Missing dialog file name");
# append .xml suffix:
filename ~= ".xml";
# build path relative to $FG_ROOT
# TODO: this may need to be fixed for aircraft-level dialogs
var path = getprop("/sim/fg-root")~"/gui/dialogs/"~filename;
print("Custom PUI/Canvas parser for dialog:", filename);
# load the dialog from the base package
# FIXME: should be using this to support aircraft dialogs: http://wiki.flightgear.org/Nasal_library#resolvepath.28.29
var dlgNode = io.read_properties(path);
# turn the property node into a hash
dlgHash = dlgNode.getValues();
# create a Canvas window
var window = canvas.Window.new([640,480],"dialog").set('title', dlgHash.name);
# adding a canvas to the new window and setting up background colors/transparency
var myCanvas = window.createCanvas().set("background", canvas.style.getColor("bg_color"));
# window.setCanvas(myCanvas);
# creating the top-level/root group which will contain all other elements/group
var root = myCanvas.createGroup();
# determine/set top-level layout
var layoutMode = dlgHash.layout;
var topLevelLayout = (layoutMode=='vbox')?canvas.VBoxLayout.new() : canvas.HBoxLayout.new();
myCanvas.setLayout(topLevelLayout);
var namespace = {cmdarg: func dlgNode};
if(dlgNode.getNode("nasal/open")!=nil) {
print("Executing nasal open block");
var cb=PUI.nasalOpenCompiler(dlgNode, namespace); # compile
cb(); # and call
}
window.del = func() {
var namespace = closure(cb);
if(dlgNode.getNode("nasal/close")!=nil) {
var close=PUI.nasalCloseCompiler(dlgNode, namespace); # invoke Nasal/close block
#close=bind(close, namespace);
close();
}
# call the original dtor
call(canvas.Window.del, [], window, var err=[]);
}
# http://wiki.flightgear.org/Built-in_Profiler#Nasal
fgcommand("profiler-start", props.Node.new({filename:"pui2canvas-"~filename}));
PUI.parseWidgets(window:window, root:root, layout:topLevelLayout, node:dlgNode);
fgcommand("profiler-stop");
}, # showDialog
}; # end of PUI namespace
###
# create a handful of tag compiler instances for different purposes
# TODO: these should be moved to function scope for the closure handling ...
# helper to compile bindings
PUI.nasalBindingCompiler = PUI.makeTagCompiler(codeTag: "script");
# helper to compile & call embedded blocks
PUI.nasalOpenCompiler = PUI.makeTagCompiler(codeTag:"nasal/open");
PUI.nasalCloseCompiler = PUI.makeTagCompiler(codeTag:"nasal/close");
PUI.canvasLoadCompiler = PUI.makeTagCompiler(codeTag:"canvas/load");
##
# finally, register a new fgcommand for turning PUI dialogs into Canvas widgets
# NOTE: Once/if the parser is sufficiently complete and works for most dialogs
# this could actually override the built-in dialog-show fgcommand
var fgcommand_name = getprop('/sim/gui/override-pui-fgcommands',0) == 1 ? 'dialog-show' : 'canvas-dialog-show';
addcommand(fgcommand_name, PUI.showDialog);
print("pui2canvas module loaded, fgcommand registered using name:", fgcommand_name);
</pre>

Navigation menu