Phi: nicer scroll animation for METAR widget
[fg:fgdata.git] / Nasal / joystick.nas
1 # Joystick configuration library.
2 var DIALOGROOT  = "/sim/gui/dialogs/joystick-config";
3 var MAX_AXES = 8;
4 var MAX_NASALS = 8;
5 var MAX_BUTTONS = 24;
6
7 # Hash of the custom axis/buttons
8 var custom_bindings = {};
9
10 # Class for an individual joystick axis binding
11 var Axis = {
12   new: func(name, prop, invertable) {
13     var m = { parents: [Axis] };
14     m.name = name;
15     m.prop = prop;
16     m.invertable = invertable;
17     m.inverted = 0;
18     return m;
19   },
20   
21   clone: func() {
22     var m = { parents: [Axis] };
23     m.name = me.name;
24     m.prop = me.prop;
25     m.invertable = me.invertable;
26     m.inverted = me.inverted;
27     return m;
28   },
29   
30   match: func(prop) {
31     return 0;
32   },
33   
34   parse: func(prop) { },
35
36   readProps: func(props) {},
37     
38   getName: func() { return me.name; },
39   getBinding: func(axis) { return props.Node.new(); },
40   isInvertable: func() { return me.invertable; },
41   isInverted: func() { return me.inverted; },
42   
43   setInverted: func(b) { 
44     if (me.invertable) me.inverted = b;
45   },  
46 };
47
48 var CustomAxis = {
49   new: func() {
50     var m = { parents: [CustomAxis, Axis.new("Custom", "", 0) ] };
51     me.custom_binding = nil;
52     return m;
53   },
54
55   clone: func() {
56     var m = { parents: [CustomAxis, Axis.new("Custom", "", 0) ] };
57     m.custom_binding = me.custom_binding;
58     return m;
59   },
60   
61   match: func(prop) {
62     var p = props.Node.new();
63     
64     if (prop.getNode("binding") != nil) {
65       props.copy(prop.getNode("binding"), p);
66       me.custom_binding = p;
67       return 1;
68     } else  {
69       return 0;
70     }
71   },
72   
73   getBinding: func(axis) {
74     var p = props.Node.new().getNode("axis[" ~ axis ~ "]", 1);
75     p.getNode("desc", 1).setValue(me.name);
76     props.copy(me.custom_binding, p.getNode("binding", 1));    
77     return p;
78   },  
79 };
80
81 var UnboundAxis = {
82   new: func() {
83     var m = { parents: [UnboundAxis, Axis.new("None", "", 0) ] };
84     return m;
85   },
86
87   clone: func() {
88     var m = { parents: [UnboundAxis, Axis.new("None", "", 0) ] };
89     return m;
90   },
91   
92   match: func(prop) {
93     return 1;
94   },
95   
96   getBinding: func(axis) {
97     return nil;
98   },
99 };
100
101
102 var PropertyScaleAxis = {
103   new: func(name, prop, deadband=0, factor=1, offset=0) {
104     var m = { parents: [PropertyScaleAxis, Axis.new(name, prop, 1) ] };
105     m.prop=prop;
106     m.deadband = deadband;
107     m.factor = factor;
108     m.offset = offset;
109     return m;
110   },
111
112   clone: func() {
113     var m = { parents: [PropertyScaleAxis, Axis.new(me.name, me.prop, 1) ] };
114
115     m.inverted = me.inverted;
116     
117     m.prop= me.prop;
118     m.deadband = me.deadband;
119     m.factor = me.factor;
120     m.offset = me.offset;
121     return m;
122   },
123
124   
125   match: func(prop) {
126     var cmd = prop.getNode("binding", 1).getNode("command", 1).getValue();
127     var p = prop.getNode("binding", 1).getNode("property", 1).getValue();
128     return ((cmd == "property-scale") and (p == me.prop));
129   },
130   
131   parse: func(p) { 
132     me.deadband = p.getNode("binding", 1).getNode("dead-band", 1).getValue();        
133     if (p.getNode("binding", 1).getNode("factor", 1).getValue() != nil) {
134       me.inverted = (p.getNode("binding", 1).getNode("factor", 1).getValue() < 0);
135     }
136     me.offset = p.getNode("binding", 1).getNode("offset", 1).getValue();
137   },
138   
139   getBinding: func(axis) {
140     var p = props.Node.new();
141     p = p.getNode("axis[" ~ axis ~ "]", 1);
142     p.getNode("desc", 1).setValue(me.name);
143     p.getNode("binding", 1).getNode("command", 1).setValue("property-scale");
144     p.getNode("binding", 1).getNode("property", 1).setValue(me.prop);
145     p.getNode("binding", 1).getNode("dead-band", 1).setValue(me.deadband);
146     if (me.inverted) {    
147       p.getNode("binding", 1).getNode("factor", 1).setValue(0 - me.factor);
148     } else {
149       p.getNode("binding", 1).getNode("factor", 1).setValue(me.factor);
150     }
151     p.getNode("binding", 1).getNode("offset", 1).setValue(me.offset);
152     return p;
153   },
154   
155 };
156
157 var NasalScaleAxis = {
158   new: func(name, script, prop) {
159     var m = { parents: [NasalScaleAxis, Axis.new(name, prop, 0) ] };
160     m.script = script;
161     m.prop = prop;
162     return m;
163   },
164   
165   clone: func() {
166     var m = { parents: [NasalScaleAxis, Axis.new(me.name, me.prop, 0) ] };
167     
168     m.script = me.script;
169     m.prop = me.prop;
170     return m;
171   },
172   
173   match: func(prop) {
174     var cmd = prop.getNode("binding", 1).getNode("command", 1).getValue();
175     var p = prop.getNode("binding", 1).getNode("script", 1).getValue();
176     if ((p != nil) and (cmd == "nasal")) {
177       p = string.trim(p);
178       p = string.replace(p, ";", "");
179       p = p ~ ";";
180       return (p == me.script);
181     } else {
182       return 0;
183     }
184   },
185   
186   getBinding: func(axis) {
187     var p = props.Node.new().getNode("axis[" ~ axis ~ "]", 1);
188     p.getNode("desc", 1).setValue(me.name);
189     p.getNode("binding", 1).getNode("command", 1).setValue("nasal");
190     p.getNode("binding", 1).getNode("script", 1).setValue(me.script);
191     return p;
192   },
193 };
194
195 var NasalLowHighAxis = {
196   new: func(name, lowscript, highscript, prop) {
197     var m = { parents: [NasalLowHighAxis, Axis.new(name, prop, 1) ] };
198     m.lowscript = lowscript;
199     m.highscript = highscript;
200     return m;
201   },
202   
203   clone: func() {
204     var m = { parents: [NasalLowHighAxis, Axis.new(me.name, me.prop, 1) ] };
205
206     m.inverted = me.inverted;
207
208     m.lowscript = me.lowscript;
209     m.highscript = me.highscript;
210     return m;
211   },  
212   
213   match: func(prop) {
214     var cmd = prop.getNode("low", 1).getNode("binding", 1).getNode("command", 1).getValue();
215     var p = prop.getNode("low", 1).getNode("binding", 1).getNode("script", 1).getValue();
216
217     if ((p == nil) or (cmd != "nasal")) return 0;
218     p = string.trim(p);
219     p = string.replace(p, ";", "");
220     p = p ~ ";";
221     
222     if (p == me.lowscript) { 
223       return 1;
224     }
225     
226     if (p == me.highscript) {
227       me.inverted = 1;
228       return 1;    
229     }
230     
231     return 0;
232   },
233   
234   getBinding: func(axis) {
235     var p = props.Node.new().getNode("axis[" ~ axis ~ "]", 1);
236     p.getNode("desc", 1).setValue(me.name);
237
238     p.getNode("low", 1).getNode("binding", 1).getNode("command", 1).setValue("nasal");
239     p.getNode("high", 1).getNode("binding", 1).getNode("command", 1).setValue("nasal");
240     
241     if (me.inverted) {
242       p.getNode("low", 1).getNode("binding", 1).getNode("script", 1).setValue(me.highscript);
243       p.getNode("high", 1).getNode("binding", 1).getNode("script", 1).setValue(me.lowscript);
244     } else {
245       p.getNode("low", 1).getNode("binding", 1).getNode("script", 1).setValue(me.lowscript);
246       p.getNode("high", 1).getNode("binding", 1).getNode("script", 1).setValue(me.highscript);
247     }
248     
249     return p;
250   },
251 };
252
253
254 var axisBindings = [
255   PropertyScaleAxis.new("Aileron", "/controls/flight/aileron"),
256   PropertyScaleAxis.new("Elevator", "/controls/flight/elevator"),
257   PropertyScaleAxis.new("Rudder",   "/controls/flight/rudder"),
258   NasalScaleAxis.new("Throttle",  "controls.throttleAxis();", "/controls/engines/engine[0]/throttle") ,
259   NasalScaleAxis.new("Mixture",   "controls.mixtureAxis();", "/controls/engines/engine[0]/mixture") ,
260   NasalScaleAxis.new("Propeller", "controls.propellerAxis();", "/controls/engines/engine[0]/propeller-pitch") , 
261   NasalLowHighAxis.new("View (horizontal)",   
262                       "setprop(\"/sim/current-view/goal-heading-offset-deg\", getprop(\"/sim/current-view/goal-heading-offset-deg\") + 30);", 
263                       "setprop(\"/sim/current-view/goal-heading-offset-deg\", getprop(\"/sim/current-view/goal-heading-offset-deg\") - 30);", 
264                       "/sim/current-view/goal-heading-offset-deg"), 
265   NasalLowHighAxis.new("View (vertical)",
266                        "setprop(\"/sim/current-view/goal-pitch-offset-deg\", getprop(\"/sim/current-view/goal-pitch-offset-deg\") - 20);", 
267                        "setprop(\"/sim/current-view/goal-pitch-offset-deg\", getprop(\"/sim/current-view/goal-pitch-offset-deg\") + 20);", 
268                        "/sim/current-view/goal-heading-offset-deg"), 
269 #  PropertyScaleAxis.new("Aileron Trim",  "/controls/flight/aileron-trim"),
270 #  PropertyScaleAxis.new("Elevator Trim", "/controls/flight/elevator-trim"),
271 #  PropertyScaleAxis.new("Rudder Trim",  "/controls/flight/rudder-trim"),
272   PropertyScaleAxis.new("Brake Left", "/controls/gear/brake-left", 0, 0.5, 1.0),
273   PropertyScaleAxis.new("Brake Right", "/controls/gear/brake-right", 0, 0.5, 1.0),
274   NasalLowHighAxis.new("Aileron Trim",  "controls.aileronTrim(-1);", "controls.aileronTrim(1);", "/controls/flight/aileron-trim"),
275   NasalLowHighAxis.new("Elevator Trim", "controls.elevatorTrim(-1);", "controls.elevatorTrim(1);", "/controls/flight/elevator-trim"),
276   NasalLowHighAxis.new("Rudder Trim",   "controls.rudderTrim(-1);", "controls.rudderTrim(1);", "/controls/flight/rudder-trim"), 
277   CustomAxis.new(),
278   UnboundAxis.new(),
279 ];
280
281 # Button bindings
282 var ButtonBinding = {
283   new: func(name, binding, repeatable) {
284     var m = { parents: [ButtonBinding] };
285     m.name = name;
286     m.binding = binding;
287     m.repeatable = repeatable;
288     return m;
289   },
290   
291   clone: func() {
292     var m = { parents: [ButtonBinding] };
293     m.name = me.name;
294     m.binding= me.binding;
295     m.repeatable = me.repeatable;
296     return m;
297   },
298   
299   match: func(prop) {
300     return 0;
301   },
302
303   getName: func() { return me.name; },
304   getBinding: func(button) { return nil; },
305   isRepeatable: func() { return me.repeatable; }
306 };
307
308 var CustomButton = {
309   new: func() {
310     var m = { parents: [CustomButton, ButtonBinding.new("Custom", "", 0) ] };
311     m.custom_binding = nil;
312     return m;
313   },
314
315   clone: func() {
316     var m = { parents: [CustomButton, ButtonBinding.new("Custom", "", 0) ] };
317     m.custom_binding = me.custom_binding;
318     return m;
319   },
320     
321   match: func(prop) {
322     if (prop.getNode("binding") != nil) {
323       var p = props.Node.new();
324       props.copy(prop.getNode("binding"), p);
325       me.custom_binding = p;
326       return 1;
327     } else  {
328       return 0;
329     }
330   },
331   
332   getBinding: func(button) {
333     var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
334     p.getNode("desc", 1).setValue(me.name);
335     props.copy(me.custom_binding, p.getNode("binding", 1));    
336     return p;
337   },  
338 };
339
340 var UnboundButton = {
341   new: func() {
342     var m = { parents: [UnboundButton, ButtonBinding.new("None", "", 0) ] };
343     return m;
344   },
345
346   clone: func() {
347     var m = { parents: [UnboundButton, ButtonBinding.new("None", "", 0) ] };
348     return m;
349   },
350   
351   match: func(prop) {
352     return (prop.getNode("binding") != nil);
353   },
354   
355   getBinding: func(button) {
356     var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
357     p.getNode("desc", 1).setValue(me.name);
358     return p;
359   },
360 };
361
362
363 var PropertyToggleButton = {
364   new: func(name, prop) {
365     var m = { parents: [PropertyToggleButton, ButtonBinding.new(name, prop, 0) ] };
366     return m;
367   },
368
369   clone: func() {
370     var m = { parents: [PropertyToggleButton, ButtonBinding.new(me.name, me.binding, 0) ] };
371     return m;
372   },
373
374   match: func(prop) {
375   
376     var c = prop.getNode("binding", 1).getNode("command", 1).getValue();
377     var p = prop.getNode("binding", 1).getNode("property", 1).getValue();
378     return ((c == "property-toggle") and (p == me.prop));
379   },
380   
381   getBinding: func(button) {
382     var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
383     p.getNode("desc", 1).setValue(me.name);
384     p.getNode("binding", 1).getNode("command", 1).setValue("property-toggle");
385     p.getNode("binding", 1).getNode("property", 1).setValue(me.binding);
386     return p;
387   },
388 };
389
390 var PropertyAdjustButton = {
391   new: func(name, prop, step) {
392     var m = { parents: [PropertyAdjustButton, ButtonBinding.new(name, prop, 0) ] };
393     m.step = step;
394     return m;
395   },
396
397   clone: func() {
398     var m = { parents: [PropertyAdjustButton, ButtonBinding.new(me.name, me.binding, 0) ] };
399     m.step = me.step;
400     return m;
401   },
402
403   match: func(prop) {  
404     var c = prop.getNode("binding", 1).getNode("command", 1).getValue();
405     var p = prop.getNode("binding", 1).getNode("property", 1).getValue();
406     var s = prop.getNode("binding", 1).getNode("step", 1).getValue();
407     return ((c == "property-adjust") and (p == me.binding) and (s == me.step));
408   },
409   
410   getBinding: func(button) {
411     var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
412     p.getNode("desc", 1).setValue(me.name);
413     p.getNode("binding", 1).getNode("command", 1).setValue("property-adjust");
414     p.getNode("binding", 1).getNode("property", 1).setValue(me.binding);
415     p.getNode("binding", 1).getNode("step", 1).setValue(me.step);
416     return p;
417   },
418 };
419
420 var NasalButton = {
421   new: func(name, script, repeatable) {
422     var m = { parents: [NasalButton, ButtonBinding.new(name, script, repeatable) ] };
423     return m;
424   },
425
426   clone: func() {
427     var m = { parents: [NasalButton, ButtonBinding.new(me.name, me.binding, me.repeatable) ] };
428     return m;
429   },
430
431   match: func(prop) {  
432     var c = prop.getNode("binding", 1).getNode("command", 1).getValue();
433     var p = prop.getNode("binding", 1).getNode("script", 1).getValue();
434     
435     if (p == nil) { return 0; }
436     
437     p = string.trim(p);
438     p = string.replace(p, ";", "");
439     p = p ~ ";";
440     
441     return ((c == "nasal") and (p == me.binding));
442   },
443   
444   getBinding: func(button) {
445     var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
446     p.getNode("desc", 1).setValue(me.name);
447     p.getNode("binding", 1).getNode("command", 1).setValue("nasal");
448     p.getNode("binding", 1).getNode("script", 1).setValue(me.binding);
449     p.getNode("repeatable", 1).setValue(me.repeatable);
450     return p;
451   },
452 };
453
454
455 var NasalHoldButton = {
456   new: func(name, script, scriptUp) {
457     var m = { parents: [NasalHoldButton, ButtonBinding.new(name, script, 0) ] };
458     m.scriptUp = scriptUp;
459     return m;
460   },
461
462   clone: func() {
463     var m = { parents: [NasalHoldButton, ButtonBinding.new(me.name, me.binding, 0) ] };
464     m.scriptUp = me.scriptUp;
465     return m;
466   },
467
468   match: func(prop) {  
469   
470     var c = prop.getNode("mod-up", 1).getNode("binding", 1).getNode("command", 1).getValue();
471     var p1 = prop.getNode("binding", 1).getNode("script", 1).getValue();
472     var p2 = prop.getNode("mod-up", 1).getNode("binding", 1).getNode("script", 1).getValue();
473     
474     if (p2 == nil) { return 0; }
475     
476     p1 = string.trim(p1);
477     p1 = string.replace(p1, ";", "");
478     p1 = p1 ~ ";";
479     
480     return ((c == "nasal") and (p1 == me.binding));
481   },
482   
483   getBinding: func(button) {
484     var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
485     p.getNode("desc", 1).setValue(me.name);
486     p.getNode("repeatable", 1).setValue("false");
487     p.getNode("binding", 1).getNode("command", 1).setValue("nasal");
488     p.getNode("binding", 1).getNode("script", 1).setValue(me.binding);
489     p.getNode("mod-up", 1).getNode("binding", 1).getNode("command", 1).setValue("nasal");
490     p.getNode("mod-up", 1).getNode("binding", 1).getNode("script", 1).setValue(me.scriptUp);
491     return p;
492   },
493 };
494
495 var buttonBindings = [
496   NasalButton.new("Elevator Trim Up", "controls.elevatorTrim(-1);", 1),
497   NasalButton.new("Elevator Trim Down", "controls.elevatorTrim(1);", 1),
498   NasalButton.new("Rudder Trim Left", "controls.rudderTrim(-1);", 1),
499   NasalButton.new("Rudder Trim Right", "controls.rudderTrim(1);", 1),
500   NasalButton.new("Aileron Trim Left", "controls.aileronTrim(-1);", 1),
501   NasalButton.new("Aileron Trim Right", "controls.aileronTrim(1);", 1),
502   NasalHoldButton.new("FGCom PTT", "controls.ptt(1);", "controls.ptt(0);"),
503   NasalHoldButton.new("Trigger", "controls.trigger(1);", "controls.trigger(0);"),
504   NasalHoldButton.new("Flaps Up", "controls.flapsDown(-1);", "controls.flapsDown(0);"),
505   NasalHoldButton.new("Flaps Down", "controls.flapsDown(1);", "controls.flapsDown(0);"),
506   NasalHoldButton.new("Gear Up", "controls.gearDown(-1);", "controls.gearDown(0);"),
507   NasalHoldButton.new("Gear Down", "controls.gearDown(1);", "controls.gearDown(0);"),
508   NasalHoldButton.new("Spoilers Retract", "controls.stepSpoilers(-1);", "controls.stepSpoilers(0);"),
509   NasalHoldButton.new("Spoilers Deploy", "controls.stepSpoilers(1);", "controls.stepSpoilers(0);"),
510   NasalHoldButton.new("Brakes", "controls.applyBrakes(1);", "controls.applyBrakes(0);"),
511
512   NasalButton.new("View Decrease", "view.decrease(0.75);", 1),
513   NasalButton.new("View Increase", "view.increase(0.75);", 1),
514   NasalButton.new("View Cycle Forwards", "view.stepView(1);", 0),
515   NasalButton.new("View Cycle Backwards", "view.stepView(-1);", 0),
516   PropertyAdjustButton.new("View Left", "/sim/current-view/goal-heading-offset-deg", "30.0"),
517   PropertyAdjustButton.new("View Right", "/sim/current-view/goal-heading-offset-deg", "-30.0"),
518   PropertyAdjustButton.new("View Up", "/sim/current-view/goal-pitch-offset-deg", "20.0"),
519   PropertyAdjustButton.new("View Down", "/sim/current-view/goal-pitch-offset-deg", "-20.0"),
520   CustomButton.new(),
521 ];
522
523 # Parse config from the /input tree and write it to the
524 # dialog_root.
525 var readConfig = func(dialog_root="/sim/gui/dialogs/joystick-config") {
526
527   var js_name = getprop(dialog_root ~ "/selected-joystick");
528   var joysticks = props.globals.getNode("/input/joysticks").getChildren("js");
529   
530   if (size(joysticks) == 0) { return 0; }
531   
532   if (js_name == nil) {
533     js_name = joysticks[0].getNode("id").getValue();  
534   }
535   
536   var js = nil;
537   
538   forindex (var i; joysticks) {
539     if ((joysticks[i].getNode("id") != nil) and 
540         (joysticks[i].getNode("id").getValue() == js_name)) 
541     {
542       js = joysticks[i];
543       setprop(dialog_root ~ "/selected-joystick", js_name);
544       setprop(dialog_root ~ "/selected-joystick-index", i);
545       setprop(dialog_root ~ "/selected-joystick-config", joysticks[i].getNode("source").getValue());
546     }
547   }
548   
549   if (js == nil) {
550     # We didn't find the joystick we expected - default to the first 
551     setprop(dialog_root ~ "/selected-joystick", joysticks[0].getNode("id").getValue());
552     setprop(dialog_root ~ "/selected-joystick-index", 0);
553     setprop(dialog_root ~ "/selected-joystick-config", joysticks[0].getNode("source").getValue());
554   }
555   
556   # Set up the axes assignments
557   var axes = js.getChildren("axis");
558
559   for (var axis = 0; axis < MAX_AXES; axis = axis +1) {
560
561     var p = props.globals.getNode(dialog_root ~ "/axis[" ~ axis ~ "]", 1);
562     p.remove();
563     p = props.globals.getNode(dialog_root ~ "/axis[" ~ axis ~ "]", 1);
564     
565     # Note that we can't simply use an index into the axes array
566     # as that doesn't work for a sparsley populated set of axes.
567     # E.g. one with n="3"
568     var a = js.getNode("axis[" ~ axis ~ "]");
569   
570     if (a != nil) {         
571       # Read properties from bindings
572       props.copy(a, p.getNode("original_binding", 1));
573
574       var binding = nil;
575       foreach (var b; joystick.axisBindings) {
576         if ((binding == nil) and (a != nil) and b.match(a)) {
577           binding = b.clone();
578           binding.parse(a);
579           p.getNode("binding", 1).setValue(binding.getName());
580           p.getNode("invertable", 1).setValue(binding.isInvertable());
581           p.getNode("inverted", 1).setValue(binding.isInverted());
582         }
583       }
584       
585       if (binding == nil) {
586         # No binding for this axis
587         p.getNode("binding", 1).setValue("None");
588         p.getNode("invertable", 1).setValue(0);
589         p.getNode("inverted", 1).setValue(0);
590         p.removeChild("original_binding");
591       }         
592     } else {
593       p.getNode("binding", 1).setValue("None");
594       p.getNode("invertable", 1).setValue(0);
595       p.getNode("inverted", 1).setValue(0);
596       p.removeChild("original_binding");
597     }
598   }
599
600   # Set up button assignment.
601   var buttons = js.getChildren("button");
602
603   for (var button = 0; button < MAX_BUTTONS; button = button + 1) {
604     var btn = props.globals.getNode(dialog_root ~ "/button[" ~ button ~ "]", 1);
605     btn.remove();
606     btn = props.globals.getNode(dialog_root ~ "/button[" ~ button ~ "]", 1);
607     
608     # Note that we can't simply use an index into the buttons array
609     # as that doesn't work for a sparsley populated set of buttons.
610     # E.g. one with n="3"
611     var a = js.getNode("button[" ~ button ~ "]");
612
613     if (a != nil) {         
614       # Read properties from bindings
615       props.copy(a, btn.getNode("original_binding", 1));
616       var binding = nil;
617       foreach (var b; joystick.buttonBindings) {
618         if ((binding == nil) and (a != nil) and b.match(a)) {
619           binding = b.clone();
620           btn.getNode("binding", 1).setValue(binding.getName());
621           props.copy(b.getBinding(button), btn.getNode("original_binding", 1));
622         }
623       }
624       
625       if (b == nil) {
626         btn.getNode("binding", 1).setValue("None");
627         btn.removeChild("original_binding");
628       }
629     } else {
630       btn.getNode("binding", 1).setValue("None");
631       btn.removeChild("original_binding");
632     }
633   }  
634
635   # Set up Nasal code.
636   var nasals = js.getChildren("nasal");
637
638   for (var nasal = 0; nasal < MAX_NASALS; nasal = nasal + 1) {
639     var nas = props.globals.getNode(dialog_root ~ "/nasal[" ~ nasal ~ "]", 1);
640     nas.remove();
641     nas = props.globals.getNode(dialog_root ~ "/nasal[" ~ nasal ~ "]", 1);
642     
643     # Note that we can't simply use an index into the buttons array
644     # as that doesn't work for a sparsley populated set of buttons.
645     # E.g. one with n="3"
646     var a = js.getNode("nasal[" ~ nasal ~ "]");
647     if (a != nil) {
648       props.copy(a, nas.getNode("original_script", 1));
649     }
650   }  
651 }
652
653 var writeConfig = func(dialog_root="/sim/gui/dialogs/joystick-config", reset=0) {  
654
655   # Write out the joystick file.
656   var config = props.Node.new();
657   var id = getprop(dialog_root ~ "/selected-joystick");
658   
659   if (reset == 1) {
660     # We've been asked to reset the joystick config to the default.  As we can't
661     # delete the configuration file, we achieve this by setting an invalid name 
662     # tag that won't match. 
663     config.getNode("name", 1).setValue("UNUSED INVALID CONFIG");
664   } else {
665     config.getNode("name", 1).setValue(id);
666   }
667   
668   var nasals = props.globals.getNode(dialog_root).getChildren("nasal");
669   forindex (var nas; nasals) {
670       var nasalscript = config.getNode("nasal[" ~ nas ~ "]", 1);
671       props.copy(props.globals.getNode(dialog_root ~ "/nasal[" ~ nas ~ "]/original_script", 1), nasalscript);
672   }  
673   
674   var axes = props.globals.getNode(dialog_root).getChildren("axis");
675   forindex (var axis; axes) {
676     var name = getprop(dialog_root ~ "/axis[" ~ axis ~ "]/binding");
677     
678     if (name != "None") {
679       foreach (var binding; axisBindings) {
680         if (binding.getName() == name) {
681           var b = binding.clone();
682           b.setInverted(getprop(dialog_root ~ "/axis[" ~ axis ~ "]/inverted"));
683           
684           # Generate the axis and binding
685           var axisnode = config.getNode("axis[" ~ axis ~ "]", 1);
686           if (name == "Custom") {          
687             props.copy(props.globals.getNode(dialog_root ~ "/axis[" ~ axis ~ "]/original_binding", 1), axisnode);
688           } else {
689             props.copy(b.getBinding(axis), axisnode);
690           }
691         }
692       }    
693     }
694   }
695
696   var buttons = props.globals.getNode(dialog_root).getChildren("button");
697   forindex (var btn; buttons) {
698     var name = getprop(dialog_root ~ "/button[" ~ btn ~ "]/binding");
699
700     if (name != "None") {
701       foreach (var binding; buttonBindings) {
702         if (binding.getName() == name) {
703           var b = binding.clone();
704           # Generate the axis and binding
705           var buttonprop = config.getNode("button[" ~ btn ~ "]", 1);
706           if (name == "Custom") {          
707             props.copy(props.globals.getNode(dialog_root ~ "/button[" ~ btn ~ "]/original_binding", 1), buttonprop);
708           } else {
709             props.copy(b.getBinding(btn), buttonprop);
710           }
711         }
712       }    
713     }
714   }
715   
716   var filename = id;
717   filename = string.replace(filename, " ", "-");
718   filename = string.replace(filename, ".", "");
719   filename = string.replace(filename, "/", "");
720   
721   # Write out the file
722   io.write_properties(getprop("/sim/fg-home") ~ "/Input/Joysticks/" ~ filename ~ ".xml", config);
723 }
724
725 var resetConfig = func(dialog_root="/sim/gui/dialogs/joystick-config") {
726   writeConfig(dialog_root, 1);
727 }