Phi: nicer scroll animation for METAR widget
[fg:fgdata.git] / Nasal / jetways_edit / jetways_edit.nas
1 ###############################################################################
2 ##
3 ##  Animated Jetway System. Allows the user to edit jetways during runtime.
4 ##
5 ##  Copyright (C) 2011  Ryan Miller
6 ##  This file is licensed under the GPL license version 2 or later.
7 ##
8 ###############################################################################
9
10 ###############################################################################
11 # (See http://wiki.flightgear.org/Howto:_Animated_jetways)
12 #
13
14 ### Static jetway model profiles ###
15 ### This class specifies the offsets used when converting static jetways using the STG converter ###
16 var Static_jetway =
17  [
18   # Models/Airport/Jetway/jetway-movable.ac
19   # Models/Airport/Jetway/jetway-movable.xml
20   # Models/Airport/Jetway/jetway-movable-2.ac
21   # Models/Airport/Jetway/jetway-movable-2.xml
22   # Models/Airport/Jetway/jetway-movable-3.ac
23   # Models/Airport/Jetway/jetway-movable-3.xml
24   {
25   models:
26    [
27    "Models/Airport/Jetway/jetway-movable.ac",
28    "Models/Airport/Jetway/jetway-movable.xml",
29    "Models/Airport/Jetway/jetway-movable-2.ac",
30    "Models/Airport/Jetway/jetway-movable-2.xml",
31    "Models/Airport/Jetway/jetway-movable-3.ac",
32    "Models/Airport/Jetway/jetway-movable-3.xml"
33    ],
34   offsets:
35    {
36    x: -2.042,
37    y: 0,
38    z: 0,
39    heading: 0
40    },
41   init_pos:
42    {
43    extend: 7.24,
44    heading: 0,
45    pitch: 0,
46    ent_heading: -90
47    },
48   model: "generic",
49   airline: "None"
50   },
51   # Models/Airport/Jetway/jetway.xml
52   # Models/Airport/Jetway/jetway-ba.ac
53   # Models/Airport/Jetway/jetway-ba.xml
54   {
55   models:
56    [
57    "Models/Airport/Jetway/jetway.xml",
58    "Models/Airport/Jetway/jetway-ba.ac",
59    "Models/Airport/Jetway/jetway-ba.xml"
60    ],
61   offsets:
62    {
63    x: 0,
64    y: 0,
65    z: -0.25,
66    heading: 0
67    },
68   init_pos:
69    {
70    extend: 7.24,
71    heading: -6.7,
72    pitch: -3.6,
73    ent_heading: -83.3
74    },
75   model: "generic",
76   airline: "None"
77   },
78   # Models/Airport/Jetway/jetway-737-ba.ac
79   # Models/Airport/Jetway/jetway-737-ba.xml
80   {
81   models:
82    [
83    "Models/Airport/Jetway/jetway-737-ba.ac",
84    "Models/Airport/Jetway/jetway-737-ba.xml"
85    ],
86   offsets:
87    {
88    x: 0,
89    y: 0,
90    z: -0.25,
91    heading: 0
92    },
93   init_pos:
94    {
95    extend: 7.24,
96    heading: -6.7,
97    pitch: -4,
98    ent_heading: -83.3
99    },
100   model: "generic",
101   airline: "None"
102   },
103   # Models/Airport/Jetway/jetway-747-ba.ac
104   # Models/Airport/Jetway/jetway-747-ba.xml
105   {
106   models:
107    [
108    "Models/Airport/Jetway/jetway-747-ba.ac",
109    "Models/Airport/Jetway/jetway-747-ba.xml"
110    ],
111   offsets:
112    {
113    x: 0,
114    y: 0,
115    z: -0.25,
116    heading: 0
117    },
118   init_pos:
119    {
120    extend: 7.24,
121    heading: -6.7,
122    pitch: 2,
123    ent_heading: -83.3
124    },
125   model: "generic",
126   airline: "None"
127   },
128   # Models/Airport/Jetway/jetway-a320-ba.ac
129   # Models/Airport/Jetway/jetway-a320-ba.xml
130   {
131   models:
132    [
133    "Models/Airport/Jetway/jetway-a320-ba.ac",
134    "Models/Airport/Jetway/jetway-a320-ba.xml"
135    ],
136   offsets:
137    {
138    x: 0,
139    y: 0,
140    z: -0.25,
141    heading: 0
142    },
143   init_pos:
144    {
145    extend: 7.24,
146    heading: -6.7,
147    pitch: -1.6,
148    ent_heading: -83.3
149    },
150   model: "generic",
151   airline: "None"
152   },
153   # Models/Airport/Jetway/AutoGate-ba.ac
154   # Models/Airport/Jetway/AutoGate.xml
155   {
156   models:
157    [
158    "Models/Airport/Jetway/AutoGate-ba.ac",
159    "Models/Airport/Jetway/AutoGate.xml"
160    ],
161   offsets:
162    {
163    x: -10,
164    y: 25,
165    z: 0,
166    heading: -90
167    },
168   init_pos:
169    {
170    extend: 7.68,
171    heading: 0,
172    pitch: 0,
173    ent_heading: -90
174    },
175   model: "generic",
176   airline: "None"
177   },
178   # Models/Airport/Jetway/DockingGate-ba.ac
179   # Models/Airport/Jetway/DockingGate.xml
180   {
181   models:
182    [
183    "Models/Airport/Jetway/DockingGate-ba.ac",
184    "Models/Airport/Jetway/DockingGate.xml"
185    ],
186   offsets:
187    {
188    x: -10,
189    y: 5,
190    z: 0,
191    heading: -90
192    },
193   init_pos:
194    {
195    extend: 7.68,
196    heading: 0,
197    pitch: 0,
198    ent_heading: -90
199    },
200   model: "generic",
201   airline: "None"
202   }
203  ];
204
205 ### Rest of script follows below ###
206 ### Watch your step! :) ###
207 var dialog_object = nil;
208 var selected_jetway = nil;
209 var mouse_mmb = 0;
210 var kbd_shift = nil;
211 var kbd_ctrl = nil;
212 var kbd_alt = nil;
213 var enabled = nil;
214 var FLASH_PERIOD = 0.3;
215 var FLASH_NUM = 3;
216 var filedialog_listener = 0;
217
218 var click = func(pos)
219  {
220  if (kbd_alt.getBoolValue())
221   {
222   if (selected_jetway == nil) return;
223   selected_jetway.setpos(pos.lat(), pos.lon(), selected_jetway.heading, pos.alt());
224   }
225  elsif (kbd_shift.getBoolValue())
226   {
227   if (selected_jetway != nil) selected_jetway._edit = 0;
228   selected_jetway = nil;
229   }
230  elsif (kbd_ctrl.getBoolValue())
231   {
232   var nearest_jetway = nil;
233   var min_dist = geo.ERAD;
234   for (var i = 0; i < size(jetways.jetways); i += 1)
235    {
236    var jetway = jetways.jetways[i];
237    if (jetway == nil) continue;
238    var dist = geo.Coord.new().set_latlon(jetway.lat, jetway.lon, jetway.alt).direct_distance_to(pos);
239    if (dist < min_dist)
240     {
241     min_dist = dist;
242     nearest_jetway = jetway;
243     }
244    }
245   if (nearest_jetway != nil)
246    {
247    if (selected_jetway != nil) selected_jetway._edit = 0;
248    selected_jetway = nearest_jetway;
249    setprop("/sim/jetways/adjust/model", selected_jetway.model);
250    setprop("/sim/jetways/adjust/door", selected_jetway.door);
251    setprop("/sim/jetways/adjust/airline", selected_jetway.airline);
252    setprop("/sim/jetways/adjust/gate", selected_jetway.gate);
253    selected_jetway._edit = 1;
254    flash(nearest_jetway);
255    }
256   }
257  else
258   {
259   var airport = getprop("/sim/airport/closest-airport-id");
260   if (airport == "") return;
261   selected_jetway = jetways.Jetway.new(airport, "generic", "FG", 0, "FGFS", pos.lat(), pos.lon(), pos.alt(), 0);
262   selected_jetway._edit = 1;
263   if (!jetways.isin(jetways.loaded_airports, airport)) append(jetways.loaded_airports, airport);
264   setprop("/sim/jetways/adjust/model", selected_jetway.model);
265   setprop("/sim/jetways/adjust/door", selected_jetway.door);
266   setprop("/sim/jetways/adjust/airline", selected_jetway.airline);
267   setprop("/sim/jetways/adjust/gate", selected_jetway.gate);
268   flash(selected_jetway);
269   }
270  };
271 var delete = func
272  {
273  if (selected_jetway == nil) return;
274  selected_jetway.remove();
275  selected_jetway = nil;
276  };
277 var adjust = func(name, value)
278  {
279  if (selected_jetway == nil) return;
280  if (name == "longitudinal")
281   {
282   var jetway_pos = geo.Coord.new();
283   jetway_pos.set_latlon(selected_jetway.lat, selected_jetway.lon, selected_jetway.alt);
284   var dir = geo.aircraft_position().course_to(jetway_pos);
285   jetway_pos.apply_course_distance(dir, value);
286   selected_jetway.setpos(jetway_pos.lat(), jetway_pos.lon(), selected_jetway.heading, selected_jetway.alt);
287   }
288  elsif (name == "transversal")
289   {
290   var jetway_pos = geo.Coord.new();
291   jetway_pos.set_latlon(selected_jetway.lat, selected_jetway.lon, selected_jetway.alt);
292   var dir = geo.aircraft_position().course_to(jetway_pos) + 90;
293   jetway_pos.apply_course_distance(dir, value);
294   selected_jetway.setpos(jetway_pos.lat(), jetway_pos.lon(), selected_jetway.heading, selected_jetway.alt);
295   }
296  elsif (name == "altitude")
297   {
298   var alt = selected_jetway.alt + value * 0.4;
299   selected_jetway.setpos(selected_jetway.lat, selected_jetway.lon, selected_jetway.heading, alt);
300   }
301  elsif (name == "heading")
302   {
303   var hdg = geo.normdeg(selected_jetway.heading + value * 4);
304   selected_jetway.setpos(selected_jetway.lat, selected_jetway.lon, hdg, selected_jetway.alt);
305   }
306  elsif (name == "initial-extension")
307   {
308   var newvalue = selected_jetway.init_extend + value;
309   if (newvalue > selected_jetway.max_extend)
310    {
311    gui.popupTip("Value exceeds maximum jetway extension limit");
312    }
313   elsif (newvalue < selected_jetway.min_extend)
314    {
315    gui.popupTip("Value lower than minimum jetway extension limit");
316    }
317   else
318    {
319    selected_jetway.init_extend = newvalue;
320    }
321   }
322  elsif (name == "initial-pitch")
323   {
324   selected_jetway.init_pitch += value;
325   }
326  elsif (name == "initial-heading")
327   {
328   selected_jetway.init_heading += value;
329   }
330  elsif (name == "initial-entrance-heading")
331   {
332   selected_jetway.init_ent_heading += value;
333   }
334  elsif (name == "model")
335   {
336   selected_jetway.setmodel(value, selected_jetway.airline, selected_jetway.gate);
337   selected_jetway = jetways.jetways[getprop("/sim/jetways/last-loaded-jetway")];
338   }
339  elsif (name == "door")
340   {
341   selected_jetway.door = value;
342   }
343  elsif (name == "airline")
344   {
345   selected_jetway.setmodel(selected_jetway.model, value, selected_jetway.gate);
346   selected_jetway = jetways.jetways[getprop("/sim/jetways/last-loaded-jetway")];
347   }
348  elsif (name == "gate")
349   {
350   selected_jetway.setmodel(selected_jetway.model, selected_jetway.airline, value);
351   selected_jetway = jetways.jetways[getprop("/sim/jetways/last-loaded-jetway")];
352   }
353  };
354 var export = func
355  {
356  var path = getprop("/sim/fg-home") ~ "/Export/";
357  var airports = {};
358  var airportarray = [];
359  foreach (var jetway; jetways.jetways)
360   {
361   if (jetway == nil) continue;
362   if (airports[jetway.airport] == nil)
363    {
364    airports[jetway.airport] = [];
365    append(airportarray, jetway.airport);
366    }
367   var node = props.Node.new();
368   node.getNode("model", 1).setValue(jetway.model);
369   node.getNode("gate", 1).setValue(jetway.gate);
370   node.getNode("door", 1).setIntValue(jetway.door);
371   node.getNode("airline", 1).setValue(jetway.airline);
372   node.getNode("latitude-deg", 1).setDoubleValue(jetway.lat);
373   node.getNode("longitude-deg", 1).setDoubleValue(jetway.lon);
374   var alt = jetway.alt;
375   jetway.setpos(jetway.lat, jetway.lon, jetway.heading, -geo.ERAD);
376   node.getNode("elevation-m", 1).setDoubleValue(alt - geo.elevation(jetway.lat, jetway.lon));
377   jetway.setpos(jetway.lat, jetway.lon, jetway.heading, alt);
378   node.getNode("heading-deg", 1).setDoubleValue(geo.normdeg(180 - jetway.heading));
379   node.getNode("initial-position/jetway-extension-m", 1).setDoubleValue(jetway.init_extend);
380   node.getNode("initial-position/jetway-heading-deg", 1).setDoubleValue(jetway.init_heading);
381   node.getNode("initial-position/jetway-pitch-deg", 1).setDoubleValue(jetway.init_pitch);
382   node.getNode("initial-position/entrance-heading-deg", 1).setDoubleValue(jetway.init_ent_heading);
383   append(airports[jetway.airport], node);
384   }
385  foreach (var airport; airportarray)
386   {
387   var file = path ~ airport ~ ".xml";
388   var args = props.Node.new({ filename: file });
389   var nodes = airports[airport];
390   foreach (var node; nodes)
391    {
392    var data = args.getNode("data", 1);
393    for (var i = 0; 1; i += 1)
394     {
395     if (data.getChild("jetway", i, 0) == nil)
396      {
397      props.copy(node, data.getChild("jetway", i, 1));
398      break;
399      }
400     }
401    }
402   fgcommand("savexml", args);
403   print("jetway definitions for airport " ~ airport ~ " exported to " ~ file);
404   }
405  };
406 var convert_stg = func
407  {
408  fgcommand("dialog-show", props.Node.new({ "dialog-name": "file-select" }));
409  setprop("/sim/gui/dialogs/file-select/path", "");
410  filedialog_listener = setlistener("/sim/gui/dialogs/file-select/path", func(n)
411   {
412   removelistener(filedialog_listener);
413   var path = n.getValue();
414   if (path == "") return;
415   var stg = io.readfile(path);
416   var stg_lines = [[]];
417   var current_word = "";
418   for (var i = 0; i < size(stg); i += 1)
419    {
420    var char = substr(stg, i, 1);
421    if (char == " " or char == "\n")
422     {
423     append(stg_lines[size(stg_lines) - 1], current_word);
424     current_word = "";
425     if (char == "\n") append(stg_lines, []);
426     }
427    else
428     {
429     current_word ~= char;
430     }
431    }
432
433   var jetway_array = [];
434   foreach (var line; stg_lines)
435    {
436    if (size(line) < 6 or line[0] != "OBJECT_SHARED") continue;
437    var foundmodel = 0;
438    var jetway = nil;
439    foreach (var profile; Static_jetway)
440     {
441     foreach (var model; profile.models)
442      {
443      if (model == line[1]) foundmodel = 1;
444      }
445     if (foundmodel)
446      {
447      jetway = profile;
448      break;
449      }
450     }
451    if (jetway == nil) continue;
452    var heading = num(line[5]);
453    var coord = geo.Coord.new();
454    coord.set_latlon(line[3], line[2], line[4]);
455    coord.apply_course_distance(360 - heading, -jetway.offsets.x);
456    coord.apply_course_distance(360 - heading + 90, jetway.offsets.y);
457    coord.set_alt(coord.alt() + jetway.offsets.z);
458    var hash = {};
459    hash.coord = coord;
460    hash.heading = heading + jetway.offsets.heading;
461    hash.init_extend = jetway.init_pos.extend;
462    hash.init_heading = jetway.init_pos.heading;
463    hash.init_pitch = jetway.init_pos.pitch;
464    hash.init_ent_heading = jetway.init_pos.ent_heading;
465    hash.model = jetway.model;
466    hash.airline = jetway.airline;
467    append(jetway_array, hash);
468    }
469
470   var airport = getprop("/sim/airport/closest-airport-id");
471   if (airport == "") return;
472   var i = 0;
473   var loop = func
474    {
475    if (i >= size(jetway_array)) return;
476    var jetway = jetway_array[i];
477    jetways.Jetway.new(airport, jetway.model, "", 0, jetway.airline, jetway.coord.lat(), jetway.coord.lon(), jetway.coord.alt(), jetway.heading, jetway.init_extend, jetway.init_heading, jetway.init_pitch, jetway.init_ent_heading);
478    if (!jetways.isin(jetways.loaded_airports, airport)) append(jetways.loaded_airports, airport);
479    i += 1;
480    settimer(loop, jetways.LOAD_JETWAY_PERIOD);
481    };
482   settimer(loop, 0);
483   jetways.alert("Creating " ~ size(jetway_array) ~ " jetways for airport " ~ airport);
484   }, 0, 1);
485  };
486 var flash = func(jetway)
487  {
488  if (!contains(jetway, "_flashnum") or jetway._flashnum == -1)
489   {
490   jetway._alt = jetway.alt;
491   jetway.setpos(jetway.lat, jetway.lon, jetway.heading, -geo.ERAD);
492   jetway._flashnum = 0;
493   settimer(func flash(jetway), FLASH_PERIOD);
494   }
495  elsif (!contains(jetway, "_alt"))
496   {
497   jetway._flashnum = -1;
498   jetway.setpos(jetway.lat, jetway.lon, jetway.heading, geo.elevation(jetway.lat, jetway.lon));
499   return;
500   }
501  elsif (jetway._flashnum == FLASH_NUM + 1)
502   {
503   jetway.setpos(jetway.lat, jetway.lon, jetway.heading, jetway._alt);
504   jetway._alt = nil;
505   jetway._flashnum = -1;
506   }
507  else
508   {
509   if (jetway.alt == -geo.ERAD)
510    {
511    jetway.setpos(jetway.lat, jetway.lon, jetway.heading, jetway._alt);
512    }
513   else
514    {
515    jetway.setpos(jetway.lat, jetway.lon, jetway.heading, -geo.ERAD);
516    }
517   jetway._flashnum += 1;
518   settimer(func flash(jetway), FLASH_PERIOD);
519   }
520  };
521
522 var dialog = func
523  {
524  if (dialog_object == nil) dialog_object = gui.Dialog.new("/sim/gui/dialogs/jetways-adjust/dialog", "gui/dialogs/jetways-adjust.xml");
525  dialog_object.open();
526  };
527 var print_help = func
528  {
529  print("JETWAY EDITOR HELP");
530  print("*******************************************************");
531  print("See: http://wiki.flightgear.org/Howto:_Animated_jetways");
532  print("");
533  print("Adjust position, heading, and altitude with top sliders");
534  print("Adjust initial jetway positions with bottom sliders");
535  print("");
536  print("<Model>                 model of selected jetway");
537  print("<Door>                  aircraft door number of selected jetway");
538  print("<Airline sign>          airline sign code of selected jetway");
539  print("<Gate>                  gate number of selected jetway");
540  print("");
541  print("[Center sliders]        apply slider offsets and return sliders to 0");
542  print("[Export]                export jetway definition file(s)");
543  print("[STG converter]         convert static jetways in STG files to animated jetways");
544  print("[?]                     show this help text");
545  print("");
546  print("Click                   add jetway on click position");
547  print("Alt-click               move selected jetway to click position");
548  print("Ctrl-click              select a jetway near click position");
549  print("Shift-click             deselect selected jetway");
550  print("Backspace               delete selected jetway");
551  print("*******************************************************");
552  };
553
554 _setlistener("/nasal/jetways_edit/loaded", func
555  {
556  print("Animated jetway editor ... loaded");
557  kbd_shift = props.globals.getNode("/devices/status/keyboard/shift");
558  kbd_ctrl = props.globals.getNode("/devices/status/keyboard/ctrl");
559  kbd_alt = props.globals.getNode("/devices/status/keyboard/alt");
560  enabled = props.globals.getNode("/nasal/jetways_edit/enabled");
561
562  setlistener("/sim/jetways/adjust/model", func(n)
563   {
564   var v = n.getValue();
565   if (selected_jetway != nil and v != selected_jetway.model)
566    {
567    adjust("model", v);
568    }
569   }, 0, 0);
570  setlistener("/sim/jetways/adjust/door", func(n)
571   {
572   var v = n.getValue();
573   if (selected_jetway != nil and v != selected_jetway.door)
574    {
575    adjust("door", v);
576    }
577   }, 0, 0);
578  setlistener("/sim/jetways/adjust/airline", func(n)
579   {
580   var v = n.getValue();
581   if (selected_jetway != nil and v != selected_jetway.airline)
582    {
583    adjust("airline", v);
584    }
585   }, 0, 0);
586  setlistener("/sim/jetways/adjust/gate", func(n)
587   {
588   var v = n.getValue();
589   if (selected_jetway != nil and v != selected_jetway.gate)
590    {
591    adjust("gate", v);
592    }
593   }, 0, 0);
594  setlistener("/devices/status/keyboard/event", func(event)
595   {
596   if (!event.getNode("pressed").getValue()) return;
597   if (enabled.getBoolValue() and event.getNode("key").getValue() == 8) delete();
598   });
599  setlistener("/devices/status/mice/mouse/button[1]", func(n) mouse_mmb = n.getBoolValue(), 1, 0);
600  setlistener("/sim/signals/click", func if (!mouse_mmb and enabled.getBoolValue()) click(geo.click_position()));
601  });