1
0
Fork 0

Add Joystick Configuration dialog, allowing in-sim configuration of joysticks.

This commit is contained in:
Stuart Buchanan 2012-08-24 22:53:21 +01:00
parent acd0091d9d
commit 4a70a703a8
4 changed files with 1588 additions and 0 deletions

View file

@ -38,3 +38,5 @@ WRITE ALLOW $FG_HOME/state/*.xml
WRITE ALLOW $FG_HOME/aircraft-data/*.xml
WRITE ALLOW $FG_HOME/Wildfire/*.xml
WRITE ALLOW $FG_HOME/runtime-jetways/*.xml
WRITE ALLOW $FG_HOME/Input/Joysticks/*.xml

686
Nasal/joystick.nas Normal file
View file

@ -0,0 +1,686 @@
# Joystick configuration library.
var DIALOGROOT = "/sim/gui/dialogs/joystick-config";
var MAX_AXES = 8;
var MAX_BUTTONS = 24;
# Hash of the custom axis/buttons
var custom_bindings = {};
# Class for an individual joystick axis binding
var Axis = {
new: func(name, prop, invertable) {
var m = { parents: [Axis] };
m.name = name;
m.prop = prop;
m.invertable = invertable;
m.inverted = 0;
return m;
},
clone: func() {
var m = { parents: [Axis] };
m.name = me.name;
m.prop = me.prop;
m.invertable = me.invertable;
m.inverted = me.inverted;
return m;
},
match: func(prop) {
return 0;
},
parse: func(prop) { },
readProps: func(props) {},
getName: func() { return me.name; },
getBinding: func(axis) { return props.Node.new(); },
isInvertable: func() { return me.invertable; },
isInverted: func() { return me.inverted; },
setInverted: func(b) {
if (me.invertable) me.inverted = b;
},
};
var CustomAxis = {
new: func() {
var m = { parents: [CustomAxis, Axis.new("Custom", "", 0) ] };
me.custom_binding = nil;
return m;
},
clone: func() {
var m = { parents: [CustomAxis, Axis.new("Custom", "", 0) ] };
m.custom_binding = me.custom_binding;
return m;
},
match: func(prop) {
var p = props.Node.new();
if (prop.getNode("binding") != nil) {
props.copy(prop.getNode("binding"), p);
me.custom_binding = p;
return 1;
} else {
return 0;
}
},
getBinding: func(axis) {
var p = props.Node.new().getNode("axis[" ~ axis ~ "]", 1);
p.getNode("desc", 1).setValue(me.name);
props.copy(me.custom_binding, p.getNode("binding", 1));
return p;
},
};
var UnboundAxis = {
new: func() {
var m = { parents: [UnboundAxis, Axis.new("None", "", 0) ] };
return m;
},
clone: func() {
var m = { parents: [UnboundAxis, Axis.new("None", "", 0) ] };
return m;
},
match: func(prop) {
return 1;
},
getBinding: func(axis) {
return nil;
},
};
var PropertyScaleAxis = {
new: func(name, prop, deadband=0, factor=1, offset=0) {
var m = { parents: [PropertyScaleAxis, Axis.new(name, prop, 1) ] };
m.prop=prop;
m.deadband = deadband;
m.factor = factor;
m.offset = offset;
return m;
},
clone: func() {
var m = { parents: [PropertyScaleAxis, Axis.new(me.name, me.prop, 1) ] };
m.inverted = me.inverted;
m.prop= me.prop;
m.deadband = me.deadband;
m.factor = me.factor;
m.offset = me.offset;
return m;
},
match: func(prop) {
var cmd = prop.getNode("binding", 1).getNode("command", 1).getValue();
var p = prop.getNode("binding", 1).getNode("property", 1).getValue();
return ((cmd == "property-scale") and (p == me.prop));
},
parse: func(p) {
me.deadband = p.getNode("binding", 1).getNode("dead-band", 1).getValue();
if (p.getNode("binding", 1).getNode("factor", 1).getValue() != nil) {
me.inverted = (p.getNode("binding", 1).getNode("factor", 1).getValue() < 0);
}
me.offset = p.getNode("binding", 1).getNode("offset", 1).getValue();
},
getBinding: func(axis) {
var p = props.Node.new();
p = p.getNode("axis[" ~ axis ~ "]", 1);
p.getNode("desc", 1).setValue(me.name);
p.getNode("binding", 1).getNode("command", 1).setValue("property-scale");
p.getNode("binding", 1).getNode("property", 1).setValue(me.prop);
p.getNode("binding", 1).getNode("dead-band", 1).setValue(me.deadband);
if (me.inverted) {
p.getNode("binding", 1).getNode("factor", 1).setValue(0 - me.factor);
} else {
p.getNode("binding", 1).getNode("factor", 1).setValue(me.factor);
}
p.getNode("binding", 1).getNode("offset", 1).setValue(me.offset);
return p;
},
};
var NasalScaleAxis = {
new: func(name, script, prop) {
var m = { parents: [NasalScaleAxis, Axis.new(name, prop, 0) ] };
m.script = script;
m.prop = prop;
return m;
},
clone: func() {
var m = { parents: [NasalScaleAxis, Axis.new(me.name, me.prop, 0) ] };
m.script = me.script;
m.prop = me.prop;
return m;
},
match: func(prop) {
var cmd = prop.getNode("binding", 1).getNode("command", 1).getValue();
var p = prop.getNode("binding", 1).getNode("script", 1).getValue();
if ((p != nil) and (cmd == "nasal")) {
p = string.trim(p);
p = string.replace(p, ";", "");
p = p ~ ";";
return (p == me.script);
} else {
return 0;
}
},
getBinding: func(axis) {
var p = props.Node.new().getNode("axis[" ~ axis ~ "]", 1);
p.getNode("desc", 1).setValue(me.name);
p.getNode("binding", 1).getNode("command", 1).setValue("nasal");
p.getNode("binding", 1).getNode("script", 1).setValue(me.script);
return p;
},
};
var NasalLowHighAxis = {
new: func(name, lowscript, highscript, prop) {
var m = { parents: [NasalLowHighAxis, Axis.new(name, prop, 1) ] };
m.lowscript = lowscript;
m.highscript = highscript;
return m;
},
clone: func() {
var m = { parents: [NasalLowHighAxis, Axis.new(me.name, me.prop, 1) ] };
m.inverted = me.inverted;
m.lowscript = me.lowscript;
m.highscript = me.highscript;
return m;
},
match: func(prop) {
var cmd = prop.getNode("low", 1).getNode("binding", 1).getNode("command", 1).getValue();
var p = prop.getNode("low", 1).getNode("binding", 1).getNode("script", 1).getValue();
if ((p == nil) or (cmd != "nasal")) return 0;
p = string.trim(p);
p = string.replace(p, ";", "");
p = p ~ ";";
if (p == me.lowscript) {
return 1;
}
if (p == me.highscript) {
me.inverted = 1;
return 1;
}
return 0;
},
getBinding: func(axis) {
var p = props.Node.new().getNode("axis[" ~ axis ~ "]", 1);
p.getNode("desc", 1).setValue(me.name);
p.getNode("low", 1).getNode("binding", 1).getNode("command", 1).setValue("nasal");
p.getNode("high", 1).getNode("binding", 1).getNode("command", 1).setValue("nasal");
if (me.inverted) {
p.getNode("low", 1).getNode("binding", 1).getNode("script", 1).setValue(me.highscript);
p.getNode("high", 1).getNode("binding", 1).getNode("script", 1).setValue(me.lowscript);
} else {
p.getNode("low", 1).getNode("binding", 1).getNode("script", 1).setValue(me.lowscript);
p.getNode("high", 1).getNode("binding", 1).getNode("script", 1).setValue(me.highscript);
}
return p;
},
};
var axisBindings = [
PropertyScaleAxis.new("Aileron", "/controls/flight/aileron"),
PropertyScaleAxis.new("Elevator", "/controls/flight/elevator"),
PropertyScaleAxis.new("Rudder", "/controls/flight/rudder"),
NasalScaleAxis.new("Throttle", "controls.throttleAxis();", "/controls/engines/engine[0]/throttle") ,
NasalScaleAxis.new("Mixture", "controls.mixtureAxis();", "/controls/engines/engine[0]/mixture") ,
NasalScaleAxis.new("Propeller", "controls.propellerAxis();", "/controls/engines/engine[0]/propeller-pitch") ,
NasalLowHighAxis.new("View (horizontal)",
"setprop(\"/sim/current-view/goal-heading-offset-deg\", getprop(\"/sim/current-view/goal-heading-offset-deg\") + 30);",
"setprop(\"/sim/current-view/goal-heading-offset-deg\", getprop(\"/sim/current-view/goal-heading-offset-deg\") - 30);",
"/sim/current-view/goal-heading-offset-deg"),
NasalLowHighAxis.new("View (vertical)",
"setprop(\"/sim/current-view/goal-pitch-offset-deg\", getprop(\"/sim/current-view/goal-pitch-offset-deg\") - 20);",
"setprop(\"/sim/current-view/goal-pitch-offset-deg\", getprop(\"/sim/current-view/goal-pitch-offset-deg\") + 20);",
"/sim/current-view/goal-heading-offset-deg"),
# PropertyScaleAxis.new("Aileron Trim", "/controls/flight/aileron-trim"),
# PropertyScaleAxis.new("Elevator Trim", "/controls/flight/elevator-trim"),
# PropertyScaleAxis.new("Rudder Trim", "/controls/flight/rudder-trim"),
PropertyScaleAxis.new("Brake Left", "/controls/gear/brake-left", 0, 0.5, 1.0),
PropertyScaleAxis.new("Brake Right", "/controls/gear/brake-right", 0, 0.5, 1.0),
NasalLowHighAxis.new("Aileron Trim", "controls.aileronTrim(-1);", "controls.aileronTrim(1);", "/controls/flight/aileron-trim"),
NasalLowHighAxis.new("Elevator Trim", "controls.elevatorTrim(-1);", "controls.elevatorTrim(1);", "/controls/flight/elevator-trim"),
NasalLowHighAxis.new("Rudder Trim", "controls.rudderTrim(-1);", "controls.rudderTrim(1);", "/controls/flight/rudder-trim"),
CustomAxis.new(),
UnboundAxis.new(),
];
# Button bindings
var ButtonBinding = {
new: func(name, binding, repeatable) {
var m = { parents: [ButtonBinding] };
m.name = name;
m.binding = binding;
m.repeatable = repeatable;
return m;
},
clone: func() {
var m = { parents: [ButtonBinding] };
m.name = me.name;
m.binding= me.binding;
m.repeatable = me.repeatable;
return m;
},
match: func(prop) {
return 0;
},
getName: func() { return me.name; },
getBinding: func(button) { return nil; },
isRepeatable: func() { return me.repeatable; }
};
var CustomButton = {
new: func() {
var m = { parents: [CustomButton, ButtonBinding.new("Custom", "", 0) ] };
m.custom_binding = nil;
return m;
},
clone: func() {
var m = { parents: [CustomButton, ButtonBinding.new("Custom", "", 0) ] };
m.custom_binding = me.custom_binding;
return m;
},
match: func(prop) {
if (prop.getNode("binding") != nil) {
var p = props.Node.new();
props.copy(prop.getNode("binding"), p);
me.custom_binding = p;
return 1;
} else {
return 0;
}
},
getBinding: func(button) {
var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
p.getNode("desc", 1).setValue(me.name);
props.copy(me.custom_binding, p.getNode("binding", 1));
return p;
},
};
var UnboundButton = {
new: func() {
var m = { parents: [UnboundButton, ButtonBinding.new("None", "", 0) ] };
return m;
},
clone: func() {
var m = { parents: [UnboundButton, ButtonBinding.new("None", "", 0) ] };
return m;
},
match: func(prop) {
return (prop.getNode("binding") != nil);
},
getBinding: func(button) {
var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
p.getNode("desc", 1).setValue(me.name);
return p;
},
};
var PropertyToggleButton = {
new: func(name, prop) {
var m = { parents: [PropertyToggleButton, ButtonBinding.new(name, prop, 0) ] };
return m;
},
clone: func() {
var m = { parents: [PropertyToggleButton, ButtonBinding.new(me.name, me.binding, 0) ] };
return m;
},
match: func(prop) {
var c = prop.getNode("binding", 1).getNode("command", 1).getValue();
var p = prop.getNode("binding", 1).getNode("property", 1).getValue();
return ((c == "property-toggle") and (p == me.prop));
},
getBinding: func(button) {
var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
p.getNode("desc", 1).setValue(me.name);
p.getNode("binding", 1).getNode("command", 1).setValue("property-toggle");
p.getNode("binding", 1).getNode("property", 1).setValue(me.binding);
return p;
},
};
var PropertyAdjustButton = {
new: func(name, prop, step) {
var m = { parents: [PropertyAdjustButton, ButtonBinding.new(name, prop, 0) ] };
m.step = step;
return m;
},
clone: func() {
var m = { parents: [PropertyAdjustButton, ButtonBinding.new(me.name, me.binding, 0) ] };
m.step = me.step;
return m;
},
match: func(prop) {
var c = prop.getNode("binding", 1).getNode("command", 1).getValue();
var p = prop.getNode("binding", 1).getNode("property", 1).getValue();
var s = prop.getNode("binding", 1).getNode("step", 1).getValue();
return ((c == "property-adjust") and (p == me.binding) and (s == me.step));
},
getBinding: func(button) {
var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
p.getNode("desc", 1).setValue(me.name);
p.getNode("binding", 1).getNode("command", 1).setValue("property-adjust");
p.getNode("binding", 1).getNode("property", 1).setValue(me.binding);
p.getNode("binding", 1).getNode("step", 1).setValue(me.step);
return p;
},
};
var NasalButton = {
new: func(name, script, repeatable) {
var m = { parents: [NasalButton, ButtonBinding.new(name, script, repeatable) ] };
return m;
},
clone: func() {
var m = { parents: [NasalButton, ButtonBinding.new(me.name, me.binding, me.repeatable) ] };
return m;
},
match: func(prop) {
var c = prop.getNode("binding", 1).getNode("command", 1).getValue();
var p = prop.getNode("binding", 1).getNode("script", 1).getValue();
if (p == nil) { return 0; }
p = string.trim(p);
p = string.replace(p, ";", "");
p = p ~ ";";
return ((c == "nasal") and (p == me.binding));
},
getBinding: func(button) {
var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
p.getNode("desc", 1).setValue(me.name);
p.getNode("binding", 1).getNode("command", 1).setValue("nasal");
p.getNode("binding", 1).getNode("script", 1).setValue(me.binding);
p.getNode("repeatable", 1).setValue(me.repeatable);
return p;
},
};
var NasalHoldButton = {
new: func(name, script, scriptUp) {
var m = { parents: [NasalHoldButton, ButtonBinding.new(name, script, 0) ] };
m.scriptUp = scriptUp;
return m;
},
clone: func() {
var m = { parents: [NasalHoldButton, ButtonBinding.new(me.name, me.binding, 0) ] };
m.scriptUp = me.scriptUp;
return m;
},
match: func(prop) {
var c = prop.getNode("mod-up", 1).getNode("binding", 1).getNode("command", 1).getValue();
var p1 = prop.getNode("binding", 1).getNode("script", 1).getValue();
var p2 = prop.getNode("mod-up", 1).getNode("binding", 1).getNode("script", 1).getValue();
if (p2 == nil) { return 0; }
p1 = string.trim(p1);
p1 = string.replace(p1, ";", "");
p1 = p1 ~ ";";
return ((c == "nasal") and (p1 == me.binding));
},
getBinding: func(button) {
var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
p.getNode("desc", 1).setValue(me.name);
p.getNode("repeatable", 1).setValue("false");
p.getNode("binding", 1).getNode("command", 1).setValue("nasal");
p.getNode("binding", 1).getNode("script", 1).setValue(me.binding);
p.getNode("mod-up", 1).getNode("binding", 1).getNode("command", 1).setValue("nasal");
p.getNode("mod-up", 1).getNode("binding", 1).getNode("script", 1).setValue(me.scriptUp);
return p;
},
};
var buttonBindings = [
NasalButton.new("Elevator Trim Up", "controls.elevatorTrim(-1);", 1),
NasalButton.new("Elevator Trim Down", "controls.elevatorTrim(1);", 1),
NasalButton.new("Rudder Trim Left", "controls.rudderTrim(-1);", 1),
NasalButton.new("Rudder Trim Right", "controls.rudderTrim(1);", 1),
NasalButton.new("Aileron Trim Left", "controls.aileronTrim(-1);", 1),
NasalButton.new("Aileron Trim Right", "controls.aileronTrim(1);", 1),
NasalHoldButton.new("FGCom PTT", "controls.ptt(1);", "controls.ptt(-1);"),
NasalHoldButton.new("Trigger", "controls.trigger(1);", "controls.trigger(0);"),
NasalHoldButton.new("Flaps Up", "controls.flapsDown(-1);", "controls.flapsDown(0);"),
NasalHoldButton.new("Flaps Down", "controls.flapsDown(1);", "controls.flapsDown(0);"),
NasalHoldButton.new("Gear Up", "controls.gearDown(-1);", "controls.gearDown(0);"),
NasalHoldButton.new("Gear Down", "controls.gearDown(1);", "controls.gearDown(0);"),
NasalHoldButton.new("Spoilers Retract", "controls.stepSpoilers(-1);", "controls.stepSpoilers(0);"),
NasalHoldButton.new("Spoilers Deploy", "controls.stepSpoilers(1);", "controls.stepSpoilers(0);"),
NasalHoldButton.new("Brakes", "controls.applyBrakes(1);", "controls.applyBrakes(0);"),
NasalButton.new("View Decrease", "view.decrease(0.75);", 1),
NasalButton.new("View Increase", "view.increase(0.75);", 1),
NasalButton.new("View Cycle Forwards", "view.stepView(1);", 0),
NasalButton.new("View Cycle Backwards", "view.stepView(-1);", 0),
PropertyAdjustButton.new("View Left", "/sim/current-view/goal-heading-offset-deg", "30.0"),
PropertyAdjustButton.new("View Right", "/sim/current-view/goal-heading-offset-deg", "-30.0"),
PropertyAdjustButton.new("View Up", "/sim/current-view/goal-pitch-offset-deg", "20.0"),
PropertyAdjustButton.new("View Down", "/sim/current-view/goal-pitch-offset-deg", "-20.0"),
CustomButton.new(),
];
# Parse config from the /input tree and write it to the
# dialog_root.
var readConfig = func(dialog_root="/sim/gui/dialogs/joystick-config") {
var js_name = getprop(dialog_root ~ "/selected-joystick");
var joysticks = props.globals.getNode("/input/joysticks").getChildren("js");
if (size(joystick) == 0) { return 0; }
if (js_name == nil) {
js_name = joysticks[0].getNode("id").getValue();
}
var js = nil;
forindex (var i; joysticks) {
if ((joysticks[i].getNode("id") != nil) and
(joysticks[i].getNode("id").getValue() == js_name))
{
js = joysticks[i];
setprop(dialog_root ~ "/selected-joystick", js_name);
setprop(dialog_root ~ "/selected-joystick-index", i);
setprop(dialog_root ~ "/selected-joystick-config", joysticks[i].getNode("source").getValue());
}
}
# Set up the axes assignments
var axes = js.getChildren("axis");
for (var axis = 0; axis < MAX_AXES; axis = axis +1) {
var p = props.globals.getNode(dialog_root ~ "/axis[" ~ axis ~ "]", 1);
p.remove();
p = props.globals.getNode(dialog_root ~ "/axis[" ~ axis ~ "]", 1);
# Note that we can't simply use an index into the axes array
# as that doesn't work for a sparsley populated set of axes.
# E.g. one with n="3"
var a = js.getNode("axis[" ~ axis ~ "]");
if (a != nil) {
# Read properties from bindings
props.copy(a, p.getNode("original_binding", 1));
var binding = nil;
foreach (var b; joystick.axisBindings) {
if ((binding == nil) and (a != nil) and b.match(a)) {
binding = b.clone();
binding.parse(a);
p.getNode("binding", 1).setValue(binding.getName());
p.getNode("invertable", 1).setValue(binding.isInvertable());
p.getNode("inverted", 1).setValue(binding.isInverted());
}
}
if (binding == nil) {
# No binding for this axis
p.getNode("binding", 1).setValue("None");
p.getNode("invertable", 1).setValue(0);
p.getNode("inverted", 1).setValue(0);
p.removeChild("original_binding");
}
} else {
p.getNode("binding", 1).setValue("None");
p.getNode("invertable", 1).setValue(0);
p.getNode("inverted", 1).setValue(0);
p.removeChild("original_binding");
}
}
# Set up button assignment.
var buttons = js.getChildren("button");
for (var button = 0; button < MAX_BUTTONS; button = button + 1) {
var btn = props.globals.getNode(dialog_root ~ "/button[" ~ button ~ "]", 1);
btn.remove();
btn = props.globals.getNode(dialog_root ~ "/button[" ~ button ~ "]", 1);
# Note that we can't simply use an index into the buttons array
# as that doesn't work for a sparsley populated set of buttons.
# E.g. one with n="3"
var a = js.getNode("button[" ~ button ~ "]");
if (a != nil) {
# Read properties from bindings
props.copy(a, p.getNode("original_binding", 1));
var binding = nil;
foreach (var b; joystick.buttonBindings) {
if ((binding == nil) and (a != nil) and b.match(a)) {
binding = b.clone();
btn.getNode("binding", 1).setValue(binding.getName());
props.copy(b.getBinding(button), btn.getNode("original_binding", 1));
}
}
if (b == nil) {
btn.getNode("binding", 1).setValue("None");
btn.removeChild("original_binding");
}
} else {
btn.getNode("binding", 1).setValue("None");
btn.removeChild("original_binding");
}
}
}
var writeConfig = func(dialog_root="/sim/gui/dialogs/joystick-config") {
# Write out the joystick file.
var config = props.Node.new();
var id = getprop(dialog_root ~ "/selected-joystick");
config.getNode("name", 1).setValue(id);
var axes = props.globals.getNode(dialog_root).getChildren("axis");
forindex (var axis; axes) {
var name = getprop(dialog_root ~ "/axis[" ~ axis ~ "]/binding");
if (name != "None") {
foreach (var binding; axisBindings) {
if (binding.getName() == name) {
var b = binding.clone();
b.setInverted(getprop(dialog_root ~ "/axis[" ~ axis ~ "]/inverted"));
# Generate the axis and binding
var axisnode = config.getNode("axis[" ~ axis ~ "]", 1);
if (name == "Custom") {
props.copy(props.globals.getNode(dialog_root ~ "/axis[" ~ axis ~ "]/original_binding", 1), axisnode);
} else {
props.copy(b.getBinding(axis), axisnode);
}
}
}
}
}
var buttons = props.globals.getNode(dialog_root).getChildren("button");
forindex (var btn; buttons) {
var name = getprop(dialog_root ~ "/button[" ~ btn ~ "]/binding");
if (name != "None") {
foreach (var binding; buttonBindings) {
if (binding.getName() == name) {
var b = binding.clone();
# Generate the axis and binding
var buttonprop = config.getNode("button[" ~ btn ~ "]", 1);
if (name == "Custom") {
props.copy(props.globals.getNode(dialog_root ~ "/button[" ~ btn ~ "]/original_binding", 1), buttonprop);
} else {
props.copy(b.getBinding(btn), buttonprop);
}
}
}
}
}
var filename = id;
filename = string.replace(filename, " ", "");
filename = string.replace(filename, ".", "");
filename = string.replace(filename, "/", "");
# Write out the file
io.write_properties(getprop("/sim/fg-home") ~ "/Input/Joysticks/" ~ filename ~ ".xml", config);
}

View file

@ -0,0 +1,437 @@
<?xml version="1.0"?>
<PropertyList>
<nasal>
<open><![CDATA[
var assignButton = func(cmd) {
var i = getprop("/sim/gui/dialogs/joystick-config/current-button");
setprop("/sim/gui/dialogs/joystick-config/button[" ~ i ~ "]/binding", cmd);
joystick.writeConfig();
fgcommand("reinit", props.Node.new({"subsystem": "input"}));
fgcommand("dialog-close", props.Node.new({"dialog-name": "button-config"}));
fgcommand("dialog-close", props.Node.new({"dialog-name": "joystick-config"}));
fgcommand("dialog-show", props.Node.new({"dialog-name": "joystick-config"}));
}
]]></open>
<close><![CDATA[
]]></close>
</nasal>
<name>button-config</name>
<layout>vbox</layout>
<resizable>true</resizable>
<modal>true</modal>
<default-padding>3</default-padding>
<group>
<layout>hbox</layout>
<default-padding>1</default-padding>
<empty><stretch>true</stretch></empty>
<text>
<label>Button Configuration</label>
</text>
<empty><stretch>true</stretch></empty>
<button>
<legend></legend>
<key>Esc</key>
<pref-width>16</pref-width>
<pref-height>16</pref-height>
<border>2</border>
<binding>
<command>dialog-close</command>
</binding>
</button>
</group>
<hrule/>
<text>
<halign>left</halign>
<label>Select the command you wish to assign to this button</label>
</text>
<hrule/>
<group>
<layout>table</layout>
<text>
<row>0</row>
<col>0</col>
<label>Flight surface trim</label>
</text>
<button>
<row>1</row>
<col>0</col>
<halign>fill</halign>
<legend>Elevator Trim Up</legend>
<binding>
<command>nasal</command>
<script>
assignButton("Elevator Trim Up");
</script>
</binding>
</button>
<button>
<row>2</row>
<col>0</col>
<halign>fill</halign>
<legend>Elevator Trim Down</legend>
<binding>
<command>nasal</command>
<script>
assignButton("Elevator Trim Down");
</script>
</binding>
</button>
<button>
<row>3</row>
<col>0</col>
<halign>fill</halign>
<legend>Rudder Trim Left</legend>
<binding>
<command>nasal</command>
<script>
assignButton("Rudder Trim Left");
</script>
</binding>
</button>
<button>
<row>4</row>
<col>0</col>
<halign>fill</halign>
<legend>Rudder Trim Right</legend>
<binding>
<command>nasal</command>
<script>
assignButton("Rudder Trim Right");
</script>
</binding>
</button>
<button>
<row>5</row>
<col>0</col>
<halign>fill</halign>
<legend>Aileron Trim Left</legend>
<binding>
<command>nasal</command>
<script>
assignButton("Aileron Trim Left");
</script>
</binding>
</button>
<button>
<row>6</row>
<col>0</col>
<halign>fill</halign>
<legend>Aileron Trim Right</legend>
<binding>
<command>nasal</command>
<script>
assignButton("Aileron Trim Right");
</script>
</binding>
</button>
<text>
<row>0</row>
<col>1</col>
<label>Control Surfaces</label>
</text>
<button>
<row>1</row>
<col>1</col>
<halign>fill</halign>
<legend>Flaps Up</legend>
<binding>
<command>nasal</command>
<script>
assignButton("Flaps Up");
</script>
</binding>
</button>
<button>
<row>2</row>
<col>1</col>
<halign>fill</halign>
<legend>Flaps Down</legend>
<binding>
<command>nasal</command>
<script>
assignButton("Flaps Down");
</script>
</binding>
</button>
<button>
<row>3</row>
<col>1</col>
<halign>fill</halign>
<legend>Gear Up</legend>
<binding>
<command>nasal</command>
<script>
assignButton("Gear Up");
</script>
</binding>
</button>
<button>
<row>4</row>
<col>1</col>
<halign>fill</halign>
<legend>Gear Down</legend>
<binding>
<command>nasal</command>
<script>
assignButton("Gear Down");
</script>
</binding>
</button>
<button>
<row>5</row>
<col>1</col>
<halign>fill</halign>
<legend>Spoilers Retract</legend>
<binding>
<command>nasal</command>
<script>
assignButton("Spoilers Retract");
</script>
</binding>
</button>
<button>
<row>6</row>
<col>1</col>
<halign>fill</halign>
<legend>Spoilers Deploy</legend>
<binding>
<command>nasal</command>
<script>
assignButton("Spoilers Deploy");
</script>
</binding>
</button>
<text>
<row>0</row>
<col>2</col>
<label>Other Aircraft Controls</label>
</text>
<button>
<row>1</row>
<col>2</col>
<halign>fill</halign>
<legend>Brakes</legend>
<binding>
<command>nasal</command>
<script>
assignButton("Brakes");
</script>
</binding>
</button>
<button>
<row>2</row>
<col>2</col>
<halign>fill</halign>
<legend>FGCom PTT</legend>
<binding>
<command>nasal</command>
<script>
assignButton("FGCom PTT");
</script>
</binding>
</button>
<button>
<row>3</row>
<col>2</col>
<halign>fill</halign>
<legend>Trigger</legend>
<binding>
<command>nasal</command>
<script>
assignButton("Trigger");
</script>
</binding>
</button>
<button>
<row>4</row>
<col>2</col>
<halign>fill</halign>
<legend>Custom</legend>
<binding>
<command>nasal</command>
<script>
assignButton("Custom");
</script>
</binding>
</button>
<button>
<row>5</row>
<col>2</col>
<halign>fill</halign>
<legend>None</legend>
<binding>
<command>nasal</command>
<script>
assignButton("None");
</script>
</binding>
</button>
<text>
<row>0</row>
<col>3</col>
<label>View</label>
</text>
<button>
<row>1</row>
<col>3</col>
<halign>fill</halign>
<legend>View Decrease</legend>
<binding>
<command>nasal</command>
<script>
assignButton("View Decrease");
</script>
</binding>
</button>
<button>
<row>2</row>
<col>3</col>
<halign>fill</halign>
<legend>View Increase</legend>
<binding>
<command>nasal</command>
<script>
assignButton("View Increase");
</script>
</binding>
</button>
<button>
<row>3</row>
<col>3</col>
<halign>fill</halign>
<legend>View Cycle Forwards</legend>
<binding>
<command>nasal</command>
<script>
assignButton("View Cycle Forwards");
</script>
</binding>
</button>
<button>
<row>4</row>
<col>3</col>
<halign>fill</halign>
<legend>View Cycle Backwards</legend>
<binding>
<command>nasal</command>
<script>
assignButton("View Cycle Backwards");
</script>
</binding>
</button>
<button>
<row>5</row>
<col>3</col>
<halign>fill</halign>
<legend>View Left</legend>
<binding>
<command>nasal</command>
<script>
assignButton("View Left");
</script>
</binding>
</button>
<button>
<row>6</row>
<col>3</col>
<halign>fill</halign>
<legend>View Right</legend>
<binding>
<command>nasal</command>
<script>
assignButton("View Right");
</script>
</binding>
</button>
<button>
<row>7</row>
<col>3</col>
<halign>fill</halign>
<legend>View Up</legend>
<binding>
<command>nasal</command>
<script>
assignButton("View Up");
</script>
</binding>
</button>
<button>
<row>8</row>
<col>3</col>
<halign>fill</halign>
<legend>View Down</legend>
<binding>
<command>nasal</command>
<script>
assignButton("View Down");
</script>
</binding>
</button>
</group>
<group>
<empty>
<stretch>true</stretch>
</empty>
<layout>hbox</layout>
<button>
<legend>Close</legend>
<default>true</default>
<key>Esc</key>
<binding>
<command>dialog-close</command>
</binding>
</button>
<empty>
<stretch>true</stretch>
</empty>
</group>
</PropertyList>

View file

@ -0,0 +1,463 @@
<?xml version="1.0"?>
<PropertyList>
<nasal>
<open><![CDATA[
var dlgRoot = cmdarg();
# Debug only
io.load_nasal("/home/stuart/FlightGear/data/Nasal/joystick.nas");
var DIALOG_ROOT = "/sim/gui/dialogs/joystick-config";
# Read the current bindings
joystick.readConfig();
# Fill in the joystick names combo box.
var joysticks = props.globals.getNode("/input/joysticks").getChildren("js");
var jsselect = gui.findElementByName(dlgRoot, "jsselect" );
forindex (var joystick_index; joysticks) {
var js = joysticks[joystick_index];
var js_id = "unknown";
if ((js.getNode("id") != nil) and (js.getNode("id").getValue() != nil))
{
js_id = js.getNode("id").getValue();
}
jsselect.getNode("value[" ~ joystick_index ~ "]", 1).setValue(js_id);
}
var table = gui.findElementByName(dlgRoot, "axistable");
table.removeChildren("checkbox");
table.removeChildren("combo");
# Fill in the valid axis bindings
for (var i = 0; i < joystick.MAX_AXES; i = i + 1) {
# Label
var t = table.getChild("text", 2*i + 4, 1);
t.getNode("row", 1).setValue(i + 1);
t.getNode("col", 1).setValue(0);
t.getNode("label", 1).setValue("Axis " ~ i);
# Raw data
t = table.getChild("text", 2*i + 5, 1);
t.getNode("property", 1).setValue("/devices/status/joysticks/joystick[" ~ joystick_index ~ "]/axis[" ~ i ~ "]");
t.getNode("row", 1).setValue(i +1 );
t.getNode("col", 1).setValue(1);
t.getNode("label", 1).setValue("01234");
t.getNode("format", 1).setValue("%2.2f");
t.getNode("halign", 1).setValue("right");
t.getNode("live", 1).setValue(1);
# Binding
t = table.getChild("combo", i, 1);
t.getNode("name", 1).setValue("axis" ~ i ~ "binding");
t.getNode("row", 1).setValue(i + 1);
t.getNode("col", 1).setValue("2");
t.getNode("halign", 1).setValue("fill");
t.getNode("pref-width", 1).setValue("150");
t.getNode("live", 1).setValue(1);
t.getNode("property", 1).setValue(DIALOG_ROOT ~ "/axis[" ~ i ~ "]/binding");
forindex (var idx; joystick.axisBindings) {
t.getChild("value", idx, 1).setValue(joystick.axisBindings[idx].getName());
}
var b = t.getChild("binding", 0, 1);
b.getNode("command", 1).setValue("dialog-apply");
b.getNode("object-name", 1).setValue("axis" ~ i ~ "binding");
b = t.getChild("binding", 1, 1);
b.getNode("command", 1).setValue("nasal");
b.getNode("script", 1).setValue("updateConfig();");
# Inverted
t = table.getChild("checkbox", i, 1);
t.getNode("name", 1).setValue("axis" ~ i ~ "inverted");
t.getNode("property", 1).setValue(DIALOG_ROOT ~ "/axis[" ~ i ~ "]/inverted");
t.getNode("row", 1).setValue(i +1 );
t.getNode("col", 1).setValue(3);
t.getNode("visible", 1).getNode("property", 1).setValue(DIALOG_ROOT ~ "/axis[" ~ i ~ "]/invertable");
t.getNode("live", 1).setValue(1);
var b = t.getChild("binding", 0, 1);
b.getNode("command", 1).setValue("dialog-apply");
b.getNode("object-name", 1).setValue("axis" ~ i ~ "inverted");
b = t.getChild("binding", 1, 1);
b.getNode("command", 1).setValue("nasal");
b.getNode("script", 1).setValue("updateConfig();");
}
# Set up the buttons.
table = gui.findElementByName(dlgRoot, "buttontable");
table.removeChildren("checkbox");
table.removeChildren("button");
var row = 1;
var col = 0;
for (var button = 0; button < joystick.MAX_BUTTONS; button = button + 1) {
t = table.getChild("checkbox", button, 1);
t.getNode("property", 1).setValue("/devices/status/joysticks/joystick[" ~ joystick_index ~ "]/button[" ~ button ~ "]");
t.getNode("row", 1).setValue(row );
t.getNode("col", 1).setValue(col);
t.getNode("live", 1).setValue(1);
t = table.getChild("button", button, 1);
t.getNode("name", 1).setValue("button" ~ button);
t.getNode("pref-width", 1).setValue(130);
t.getNode("halign", 1).setValue("fill");
t.getNode("live", 1).setValue("fill");
t.getNode("row", 1).setValue(row );
t.getNode("col", 1).setValue(col +1);
t.getNode("legend", 1).setValue(getprop(DIALOG_ROOT ~ "/button[" ~ button ~ "]/binding"));
var b = t.getChild("binding", 0, 1);
b.getNode("command", 1).setValue("property-assign");
b.getNode("property", 1).setValue("/sim/gui/dialogs/joystick-config/current-button");
b.getNode("value", 1).setValue(button);
b = t.getChild("binding", 1, 1);
b.getNode("command", 1).setValue("dialog-show");
b.getNode("dialog-name", 1).setValue("button-config");
col = col + 2;
if (col > 5) {
row = row +1;
col = 0;
}
}
var updateConfig = func() {
joystick.writeConfig();
fgcommand("reinit", props.Node.new({"subsystem": "input"}));
fgcommand("dialog-close", props.Node.new({"dialog-name": "joystick-config"}));
fgcommand("dialog-show", props.Node.new({"dialog-name": "joystick-config"}));
}
]]></open>
<close><![CDATA[
]]></close>
</nasal>
<name>joystick-config</name>
<layout>vbox</layout>
<resizable>true</resizable>
<default-padding>3</default-padding>
<group>
<layout>hbox</layout>
<default-padding>1</default-padding>
<empty><stretch>true</stretch></empty>
<text>
<label>Joystick Configuration</label>
</text>
<empty><stretch>true</stretch></empty>
<button>
<legend></legend>
<key>Esc</key>
<pref-width>16</pref-width>
<pref-height>16</pref-height>
<border>2</border>
<binding>
<command>dialog-close</command>
</binding>
</button>
</group>
<hrule/>
<group>
<layout>table</layout>
<default-padding>2</default-padding>
<empty>
<width>150</width>
<col>0</col>
<row>0</row>
</empty>
<text>
<label>Aileron: </label>
<halign>right</halign>
<col>1</col>
<row>0</row>
</text>
<text>
<label>-0.00000</label>
<halign>left</halign>
<format>%.5f</format>
<property>/controls/flight/aileron</property>
<live>1</live>
<col>2</col>
<row>0</row>
</text>
<text>
<label>Elevator: </label>
<halign>right</halign>
<col>1</col>
<row>1</row>
</text>
<text>
<label>-0.00000</label>
<halign>left</halign>
<format>%.5f</format>
<property>/controls/flight/elevator</property>
<live>1</live>
<col>2</col>
<row>1</row>
</text>
<text>
<label>Rudder: </label>
<halign>right</halign>
<col>3</col>
<row>0</row>
</text>
<text>
<label>-0.00000</label>
<halign>left</halign>
<format>%.5f</format>
<property>/controls/flight/rudder</property>
<live>1</live>
<col>4</col>
<row>0</row>
</text>
<text>
<label>Throttle: </label>
<halign>right</halign>
<col>3</col>
<row>1</row>
</text>
<text>
<label>-0.00000</label>
<halign>left</halign>
<format>%.5f</format>
<property>/controls/engines/engine/throttle</property>
<live>1</live>
<col>4</col>
<row>1</row>
</text>
<empty>
<width>150</width>
<col>5</col>
<row>0</row>
</empty>
</group>
<!-- Joystick selector -->
<group>
<layout>table</layout>
<halign>left</halign>
<text>
<row>0</row>
<col>0</col>
<halign>right</halign>
<label>Joystick</label>
</text>
<combo>
<name>jsselect</name>
<row>0</row>
<col>1</col>
<halign>left</halign>
<property>/sim/gui/dialogs/joystick-config/selected-joystick</property>
<pref-width>350</pref-width>
<binding>
<command>dialog-apply</command>
<object-name>jsselect</object-name>
</binding>
<binding>
<command>nasal</command>
<script>
fgcommand("dialog-close", props.Node.new({"dialog-name": "joystick-config"}));
fgcommand("dialog-show", props.Node.new({"dialog-name": "joystick-config"}));
</script>
</binding>
</combo>
<text>
<row>1</row>
<col>0</col>
<halign>right</halign>
<label>Configuration File</label>
</text>
<text>
<row>1</row>
<col>1</col>
<halign>left</halign>
<label>Joystick Confgig</label>
<property>/sim/gui/dialogs/joystick-config/selected-joystick-config</property>
</text>
</group>
<hrule/>
<group>
<layout>hbox</layout>
<valign>top</valign>
<halign>fill</halign>
<!-- Axis list -->
<group>
<layout>table</layout>
<valign>top</valign>
<name>axistable</name>
<!-- Header Row -->
<text>
<row>0</row>
<col>0</col>
<label>Axis</label>
</text>
<text>
<row>0</row>
<col>1</col>
<label>Input</label>
</text>
<text>
<row>0</row>
<col>2</col>
<label>Control</label>
</text>
<text>
<row>0</row>
<col>3</col>
<label>Inverted?</label>
</text>
<!-- Axes get added here based on above template -->
</group>
<vrule/>
<!-- Buttons -->
<group>
<layout>table</layout>
<valign>top</valign>
<name>buttontable</name>
<!-- Header row-->
<text>
<row>0</row><col>0</col>
<label>Input</label>
</text>
<text>
<row>0</row><col>1</col>
<label>Control</label>
</text>
<text>
<row>0</row><col>2</col>
<label>Input</label>
</text>
<text>
<row>0</row><col>3</col>
<label>Control</label>
</text>
<text>
<row>0</row><col>4</col>
<label>Input</label>
</text>
<text>
<row>0</row><col>5</col>
<label>Control</label>
</text>
<buttontemplate>
<name>buttontemplate</name>
<checkbox>
<row>1</row><col>0</col>
<halign>right</halign>
<live>1</live>
<readonly>1</readonly>
<property>/devices/status/joysticks/joystick[0]/button[0]</property>
</checkbox>
<text>
<row>1</row><col>1</col>
<property>/sim/gui/dialogs/joystick-config/button[0]</property>
<label>01234567890</label>
<halign>left</halign>
<live>true</live>
</text>
<button>
<row>1</row><col>2</col>
<legend>...</legend>
<binding>
<command>property-assign</command>
<property>/sim/gui/dialogs/joystick-config/current-button</property>
<value>0</value>
</binding>
<binding>
<command>dialog-show</command>
<dialog-name>button-config</dialog-name>
</binding>
</button>
</buttontemplate>
<!-- Buttons get added here based on above template -->
</group>
</group>
<hrule/>
<!-- Button bar -->
<group>
<layout>hbox</layout>
<empty>
<stretch>true</stretch>
</empty>
<button>
<legend>Close</legend>
<binding>
<command>dialog-close</command>
</binding>
</button>
<empty>
<stretch>true</stretch>
</empty>
</group>
</PropertyList>