diff --git a/Nasal/IOrules b/Nasal/IOrules
index 07117c0f1..b130958f7 100644
--- a/Nasal/IOrules
+++ b/Nasal/IOrules
@@ -37,3 +37,4 @@ WRITE ALLOW $FG_HOME/Export/*
 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
diff --git a/Nasal/jetways.nas b/Nasal/jetways.nas
new file mode 100644
index 000000000..68d0b68fa
--- /dev/null
+++ b/Nasal/jetways.nas
@@ -0,0 +1,889 @@
+###############################################################################
+##
+##  Animated Jetway System. Spawns and manages interactive jetway models.
+##
+##  Copyright (C) 2011  Ryan Miller
+##  This file is licensed under the GPL license version 2 or later.
+##
+###############################################################################
+
+###############################################################################
+# (See http://wiki.flightgear.org/Howto:_Animated_jetways)
+#
+# Special jetway definition files located in $FG_ROOT/Airports/Jetways/XXXX.xml
+# for each airport are loaded when the user's aircraft is within 50 nm of the
+# airport. The script dynamically generates runtime model files, writes them to
+# $FG_ROOT/Models/Airport/Jetway/runtimeX.xml, and places them into the
+# simulator using the model manager.
+#
+# Different jetway models can be defined and are placed under
+# $FG_ROOT/Models/Airport/Jetway/XXX.xml.
+#
+# Jetways can be extended/retracted independently either by user operation or
+# by automatic extension for AI models and multiplayer aircraft.
+#
+# UTILITY FUNCTIONS
+# -----------------
+#
+# print_debug(<message>)			- prints debug messages
+#	<message>				- message to print
+#
+# print_error(<messsage>)			- prints error messages
+#	<message>				- error to print
+#
+# alert(<message>)				- displays an alert message in-sim
+#	<message>				- the message
+#
+# normdeg(<angle>)				- normalizes angle measures between -180° and 180°
+#	<angle>					- angle to normalize
+#
+# remove(<vector>, <item>)			- removes an element from a vector
+#	<vector>				- vector
+#	<index>					- item
+#
+# isin(<vector>, <item>)			- checks if an item exists in a vector
+#	<vector>				- vector
+#	<item>					- item
+#
+# putmodel(<path>, <lat>, <lon>, <alt>, <hdg>)	- add a model to the scene graph (unlike geo.put_model(), models added with this function can be adjusted)
+#	<path>					- model path
+#	<lat>					- latitude
+#	<lon>					- longitude
+#	<alt>					- altitude in m
+#	<hdg>					- heading
+#
+# interpolate_table(<table>, <value>)		- interpolates a value within a table
+#	<table>					- interpolation table/vector, in the format of [[<ind>, <dep>], [<ind>, <dep>], ... ]
+#	<value>					- value
+#
+# get_relative_filepath(<path>, <target>)	- gets a relative file path from a directory
+#	<path>					- directory path should be relative to
+#	<target>				- target directory
+#
+# find_airports(<max dist>)			- gets a list of nearest airports
+#	<max dist>				- maximum search distance in nm (currently unused)
+#
+# JETWAY CLASS
+# ------------
+#
+# Jetway.					- creates a new jetway object/model
+#   new(<airport>, <model>, <gate>, <door>,
+#       <airline>, <lat>, <lon>, <elev>,
+#	<heading>, [, <init_extend>]
+#	[, <init_heading>] [, <init_pitch>]
+#	[, <init_ent_heading>])
+#	<airport>				- ICAO of associated airport
+#	<model>					- jetway model definition (i.e. Models/Airport/Jetway/generic.xml)
+#	<gate>					- gate number (i.e. "A1")
+#	<door>					- door number (i.e. 0)
+#	<airline>				- airline code (i.e. "AAL")
+#	<lat>					- latitude location of model
+#	<lon>					- longitude location of model
+#	<elev>					- elevation of model in m
+#	<heading>				- (optional) heading of model
+#	<init_extend>				- (optional) initial extension of tunnel in m
+#	<init_heading>				- (optional) initial rotation of tunnel along the Z axis
+#	<init_pitch>				- (optional) initial pitch of tunnel (rotation along Y axis)
+#	<init_ent_heading>			- (optional) initial rotation of entrance along the Z axis
+#
+#   toggle(<user>, <heading>, <coord>		- extends/retracts a jetway
+#          [, <hood>])
+#	<user>					- whether or not jetway is toggled by user command (0/1)
+#	<heading>				- heading of aircraft to connect to
+#	<coord>					- a geo.Coord of the target aircraft's door
+#	<hood>					- (optional) amount to rotate jetway hood (only required when <user> != 1)
+#
+#   extend(<user>, <heading>, <coord>		- extends a jetway (should be called by Jetway.toggle())
+#          [, <hood>])
+#	<user>					- whether or not jetway is toggled by user command (0/1)
+#	<heading>				- heading of aircraft to connect to
+#	<coord>					- a geo.Coord of the target aircraft's door
+#	<hood>					- (optional) amount to rotate jetway hood (only required when <user> != 1)
+#
+#   retract(<user>)				- retracts a jetway (should be called by Jetway.toggle())
+#	<user>					- whether or not a jetway is toggled by user command (0/1)
+#
+#   remove()					- removes a jetway object and its model
+#
+#   reload()					- reloads a jetway object and its model
+#
+#   setpos(<lat>, <lon>, <heading>, <alt>)	- moves a jetway to a new location
+#	<lat>					- new latitude
+#	<lon>					- new longitude
+#	<heading>				- new heading
+#	<alt>					- new altitude in m
+#
+#   setmodel(<model>, <airline>, <gate>)	- changes the jetway model
+#	<model>					- new model
+#	<airline>				- new airline sign code
+#	<gate>					- new gate number
+#
+# INTERACTION FUNCTIONS
+# ---------------------
+#
+# dialog()					- open settings dialog
+#
+# toggle_jetway(<id>)				- toggles a jetway by user command (should be called by a pick animation in a jetway model)
+#	<id>					- id number of jetway to toggle
+#
+# toggle_jetway_from_coord(<door>, <hood>,	- toggles a jetway with the target door at the specified coordinates
+#                          <heading>, [<lat>,
+#                          <lon>] [<coord>])
+#	<door>					- door number (i.e. 0)
+#	<hood>					- amount to rotate jetway hood
+#	<lat>					- (required or <coord>) latitude location of door
+#	<lon>					- (required or <coord>) longitude location of door
+#	<coord>					- (required or <lat>, <lon>) a geo.Coord of the door
+#
+# toggle_jetway_from_model(<model node>)	- toggles a jetway using an AI model instead of the user's aircraft
+#	<model node>				- path of AI model (i.e. /ai/models/aircraft[0])- can be the path in a string or a props.Node
+#
+# INTERNAL FUNCTIONS
+# ------------------
+#
+# load_airport_jetways(<airport>)		- loads jetways at an airport
+#	<airport>				- ICAO of airport
+#
+# unload_airport_jetways(<airport>)		- unloads jetways at an airport
+#	<airport>				- ICAO of airport
+#
+# update_jetways()				- interpolates model animation values and keeps the system tidy
+#
+# load_jetways()				- loads new jetway models and unloads out-of-range models every 30 seconds
+#
+
+## Utility functions
+####################
+
+# prints debug messages
+var print_debug = func(msg)
+ {
+ if (debug_switch.getBoolValue())
+  {
+  print(msg);
+  }
+ };
+# prints error messages
+var print_error = func(msg)
+ {
+ print("\x1b[31m" ~ msg ~ "\x1b[m");
+ };
+# alerts the user
+var alert = func(msg)
+ {
+ setprop("/sim/messages/ground", msg);
+ };
+# normalizes headings between -180 and 180
+var normdeg = func(x)
+ {
+ while (x >= 180)
+  {
+  x -= 360;
+  }
+ while (x <= -180)
+  {
+  x += 360;
+  }
+ return x;
+ };
+# deletes an item in a vector
+var remove = func(vector, item)
+ {
+ var s = size(vector);
+ var found = 0;
+ for (var i = 0; i < s; i += 1)
+  {
+  if (found)
+   {
+   vector[i - 1] = vector[i];
+   }
+  elsif (vector[i] == item)
+   {
+   found = 1;
+   }
+  }
+ if (found) setsize(vector, s - 1);
+ return vector;
+ };
+# checks if an item is in a vector
+var isin = func(vector, v)
+ {
+ for (var i = 0; i < size(vector); i += 1)
+  {
+  if (vector[i] == v) return 1;
+  }
+ return 0;
+ };
+# adds a model
+var putmodel = func(path, lat, lon, alt, hdg)
+ {
+ var models = props.globals.getNode("/models");
+ var model = nil;
+ for (var i = 0; 1; i += 1)
+  {
+  if (models.getChild("model", i, 0) == nil)
+   {
+   model = models.getChild("model", i, 1);
+   break;
+   }
+  }
+ var model_path = model.getPath();
+ model.getNode("path", 1).setValue(path);
+ model.getNode("latitude-deg", 1).setDoubleValue(lat);
+ model.getNode("latitude-deg-prop", 1).setValue(model_path ~ "/latitude-deg");
+ model.getNode("longitude-deg", 1).setDoubleValue(lon);
+ model.getNode("longitude-deg-prop", 1).setValue(model_path ~ "/longitude-deg");
+ model.getNode("elevation-ft", 1).setDoubleValue(alt * M2FT);
+ model.getNode("elevation-ft-prop", 1).setValue(model_path ~ "/elevation-ft");
+ model.getNode("heading-deg", 1).setDoubleValue(hdg);
+ model.getNode("heading-deg-prop", 1).setValue(model_path ~ "/heading-deg");
+ model.getNode("pitch-deg", 1).setDoubleValue(0);
+ model.getNode("pitch-deg-prop", 1).setValue(model_path ~ "/pitch-deg");
+ model.getNode("roll-deg", 1).setDoubleValue(0);
+ model.getNode("roll-deg-prop", 1).setValue(model_path ~ "/roll-deg");
+ model.getNode("load", 1).remove();
+ return model;
+ };
+
+# interpolates a value
+var interpolate_table = func(table, v)
+ {
+ var x = 0;
+ forindex (i; table)
+  {
+  if (v >= table[i][0])
+   {
+   x = i + 1 < size(table) ? (v - table[i][0]) / (table[i + 1][0] - table[i][0]) * (table[i + 1][1] - table[i][1]) + table[i][1] : table[i][1];
+   }
+  }
+ return x;
+ };
+# gets a relative file path
+var get_relative_filepath = func(path, target)
+ {
+ var newpath = "";
+ for (var i = size(path) - 1; i >= 0; i -= 1)
+  {
+  var char = substr(path, i, 1);
+  if (char == "/") newpath ~= "../";
+  }
+ # we can just append the target path for UNIX systems, but we need to remove the drive letter prefix for DOS systems
+ return newpath ~ (string.match(substr(target, 0, 3), "?:/") ? substr(target, 2, size(target) - 2) : target);
+ };
+# gets a list of nearest airports
+# TODO: Don't use /sim/airport/nearest-airport-id, which restricts the list to 1 airport
+var find_airports = func(max_distance)
+ {
+ var apt = getprop("/sim/airport/closest-airport-id");
+ return apt == "" ? nil : [apt];
+ };
+
+## Global variables
+###################
+
+var root = nil;
+var home = nil;
+var scenery = [];
+
+var UPDATE_PERIOD = 1 / 30;
+var LOAD_PERIOD = 30;
+var LOAD_DISTANCE = 50;				# in nautical miles
+var LOAD_JETWAY_PERIOD = 0.05;
+var NUMBER_OF_JETWAYS = 2000;			# approx max number of jetways loadable in FG
+var runtime_files = NUMBER_OF_JETWAYS / LOAD_PERIOD * LOAD_JETWAY_PERIOD;
+runtime_files = int(runtime_files) == runtime_files ? runtime_files : int(runtime_files) + 1;
+var runtime_file = 0;
+var update_loopid = -1;
+var load_loopid = -1;
+var load_listenerid = nil;
+var loadids = {};
+var dialog_object = nil;
+var loaded_airports = [];
+var jetways = [];
+
+# properties
+var on_switch = "/sim/jetways/enabled";
+var debug_switch = "/sim/jetways/debug";
+var mp_switch = "/sim/jetways/interact-with-multiplay";
+var jetway_id_prop = "/sim/jetways/last-loaded-jetway";
+
+# interpolation tables
+var extend_rate = 0.5;
+var extend_table = [
+  [0.0, 0.0],
+  [0.2, 0.3],
+  [0.6, 0.3],
+  [0.8, 1.0],
+  [1.0, 1.0]
+ ];
+var pitch_rate = 1;
+var pitch_table = [
+  [0.0, 0.0],
+  [0.4, 0.7],
+  [0.7, 1.0],
+  [1.0, 1.0]
+ ];
+var heading_rate = 1;
+var heading_table = [
+  [0.0, 0.0],
+  [0.2, 0.0],
+  [0.6, 0.7],
+  [0.9, 1.0],
+  [1.0, 1.0]
+ ];
+var heading_entrance_rate = 5;
+var heading_entrance_table = [
+  [0.0, 0.0],
+  [0.3, 0.0],
+  [0.6, 0.7],
+  [0.8, 1.0],
+  [1.0, 1.0]
+ ];
+var hood_rate = 1;
+var hood_table = [
+  [0.0, 0.0],
+  [0.9, 0.0],
+  [1.0, 1.0]
+ ];
+
+## Classes
+##########
+
+# main jetway class
+var Jetway =
+ {
+ new: func(airport, model, gate, door, airline, lat, lon, elev, heading, init_extend = 0, init_heading = 0, init_pitch = 0, init_ent_heading = 0)
+  {
+  var id = 0;
+  for (var i = 0; 1; i += 1)
+   {
+   if (i == size(jetways))
+    {
+    setsize(jetways, i + 1);
+    id = i;
+    break;
+    }
+   elsif (jetways[i] == nil)
+    {
+    id = i;
+    }
+   }
+  # locate the jetway model directory and load the model tree
+  var model_tree = nil;
+  var model_file = "";
+  var model_dir = "";
+  var airline_file = "";
+  if (props.globals.getNode("/sim/paths/use-custom-scenery-data", 1).getBoolValue())
+   {
+   # search in scenery directories
+   foreach (var scenery_path; scenery)
+    {
+    model_dir = scenery_path ~ "/Models/Airport/Jetway";
+    model_file = model_dir ~ "/" ~ model ~ ".xml";
+    print_debug("Trying to load a jetway model from " ~ model_file);
+    if (io.stat(model_file) == nil) continue;
+    model_tree = io.read_properties(model_file);
+    airline_file = get_relative_filepath(home ~ "/runtime-jetways", model_dir ~ "/Airlines/" ~ airline ~ ".png");
+    if (io.stat(airline_file) == nil) airline_file = get_relative_filepath(home ~ "/runtime-jetways", model_dir ~ "/Airlines/None.png");
+    break;
+    }
+   }
+  if (model_tree == nil)
+   {
+   model_dir = root ~ "/Models/Airport/Jetway";
+   model_file = model_dir ~ "/" ~ model ~ ".xml";
+   print_debug("Falling back to " ~ model_file);
+   if (io.stat(model_file) == nil)
+    {
+    print_error("Failed to load jetway model: " ~ model);
+    return;
+    }
+   model_tree = io.read_properties(model_file);
+   airline_file = get_relative_filepath(home ~ "/runtime-jetways", model_dir ~ "/Airlines/" ~ airline ~ ".png");
+   if (io.stat(airline_file) == nil) airline_file = get_relative_filepath(home ~ "/runtime-jetways", model_dir ~ "/Airlines/None.png");
+   }
+
+  var m =
+   {
+   parents: [Jetway]
+   };
+  m.airport = airport;
+  m.gate = gate;
+  m.airline = airline;
+  m.id = id;
+  m.model = model;
+  m.extended = 0;
+  m.door = door;
+  m.lat = lat;
+  m.lon = lon;
+  m.elev = elev;
+  m.heading = geo.normdeg(180 - heading);
+  m.init_extend = init_extend;
+  m.init_heading = init_heading;
+  m.init_pitch = init_pitch;
+  m.init_ent_heading = init_ent_heading;
+  m.target_extend = 0;
+  m.target_pitch = 0;
+  m.target_heading = 0;
+  m.target_ent_heading = 0;
+  m.target_hood = 0;
+  m.rotunda_x = model_tree.getNode("rotunda/x-m").getValue();
+  m.rotunda_y = model_tree.getNode("rotunda/y-m").getValue();
+  m.rotunda_z = model_tree.getNode("rotunda/z-m").getValue();
+  m.offset_extend = model_tree.getNode("extend-offset-m").getValue();
+  m.offset_entrance = model_tree.getNode("entrance-offset-m").getValue();
+  m.min_extend = model_tree.getNode("min-extend-m").getValue();
+  m.max_extend = model_tree.getNode("max-extend-m").getValue();
+
+  # get the runtime file path
+  if (runtime_file == runtime_files)
+   {
+   runtime_file = 0;
+   }
+  var runtime_file_path = home ~ "/runtime-jetways/" ~ runtime_file ~ ".xml";
+  runtime_file += 1;
+
+  # create the model node and the door object
+  m.node = putmodel(runtime_file_path, lat, lon, elev, geo.normdeg(360 - heading));
+  var node_path = m.node.getPath();
+  m.door_object = aircraft.door.new(node_path ~ "/jetway-position", 0);
+
+  # manipulate the model tree
+  model_tree.getNode("path").setValue(model_dir ~ "/" ~ model_tree.getNode("path").getValue());
+  model_tree.getNode("toggle-action-script").setValue("jetways.toggle_jetway(" ~ id ~ ");");
+  model_tree.getNode("gate").setValue(m.gate);
+  model_tree.getNode("airline-code").setValue(airline_file);
+  model_tree.getNode("extend-m").setValue(props.globals.initNode(node_path ~ "/jetway-position/extend-m", 0, "DOUBLE").getPath());
+  model_tree.getNode("pitch-deg").setValue(props.globals.initNode(node_path ~ "/jetway-position/pitch-deg", 0, "DOUBLE").getPath());
+  model_tree.getNode("heading-deg").setValue(props.globals.initNode(node_path ~ "/jetway-position/heading-deg", 0, "DOUBLE").getPath());
+  model_tree.getNode("entrance-heading-deg").setValue(props.globals.initNode(node_path ~ "/jetway-position/entrance-heading-deg", 0, "DOUBLE").getPath());
+  model_tree.getNode("hood-deg").setValue(props.globals.initNode(node_path ~ "/jetway-position/hood-deg", 0, "DOUBLE").getPath());
+  # write the model tree
+  io.write_properties(runtime_file_path, model_tree);
+
+  jetways[id] = m;
+  print_debug("Loaded jetway #" ~ id);
+  jetway_id_prop.setValue(id);
+  return m;
+  },
+ toggle: func(user, heading, coord, hood = 0)
+  {
+  if (me.extended)
+   {
+   me.retract(user, heading, coord);
+   }
+  else
+   {
+   me.extend(user, heading, coord, hood);
+   }
+  },
+ extend: func(user, heading, door_coord, hood = 0)
+  {
+  me.extended = 1;
+
+  # get the coordinates of the jetway and offset for the rotunda position
+  var jetway_coord = geo.Coord.new();
+  jetway_coord.set_latlon(me.lat, me.lon);
+  jetway_coord.apply_course_distance(me.heading, me.rotunda_x);
+  jetway_coord.apply_course_distance(me.heading - 90, me.rotunda_y);
+  jetway_coord.set_alt(me.elev + me.rotunda_z);
+
+  if (debug_switch.getBoolValue())
+   {
+   # place UFO cursors at the calculated door and jetway positions for debugging purposes
+   geo.put_model("Aircraft/ufo/Models/cursor.ac", door_coord);
+   geo.put_model("Aircraft/ufo/Models/cursor.ac", jetway_coord);
+   }
+
+  # offset the door for the length of the jetway entrance
+  door_coord.apply_course_distance(heading - 90, me.offset_entrance);
+
+  # calculate the bearing to the aircraft and the distance from the door
+  me.target_heading = normdeg(jetway_coord.course_to(door_coord) - me.heading - me.init_heading);
+  me.target_extend = jetway_coord.distance_to(door_coord) - me.offset_extend - me.init_extend;
+
+  # check if distance exceeds maximum jetway extension length
+  if (me.target_extend + me.init_extend > me.max_extend)
+   {
+   me.extended = 0;
+   me.target_extend = 0;
+   me.target_heading = 0;
+   if (user) alert("Your aircraft is too far from this jetway.");
+   print_debug("Jetway #" ~ me.id ~ " is too far from the door");
+   return;
+   }
+  # check if distance fails to meet minimum jetway extension length
+  if (me.target_extend + me.init_extend < me.min_extend)
+   {
+   me.extended = 0;
+   me.target_extend = 0;
+   me.target_heading = 0;
+   if (user) alert("Your aircraft is too close to this jetway.");
+   print_debug("Jetway #" ~ me.id ~ " is too close to the door");
+   return;
+   }
+
+  # calculate the jetway pitch, entrance heading, and hood
+  me.target_pitch = math.atan2((door_coord.alt() - jetway_coord.alt()) / (me.target_extend + me.offset_extend + me.init_extend), 1) * R2D - me.init_pitch;
+  me.target_ent_heading = normdeg((heading + 90) - (me.heading + (me.target_heading + me.init_heading) + me.init_ent_heading));
+  me.target_hood = user ? getprop("/sim/model/door[" ~ me.door ~ "]/jetway-hood-deg") : hood;
+
+  # fire up the animation
+  if (user) alert("Extending jetway.");
+  var animation_time = math.abs(me.target_extend / extend_rate) + math.abs(me.target_pitch / pitch_rate) + math.abs(me.target_heading / heading_rate) + math.abs(me.target_ent_heading / heading_entrance_rate) + math.abs(me.target_hood / hood_rate);
+  me.door_object.swingtime = animation_time;
+  me.door_object.open();
+
+  print_debug("************************************************");
+  print_debug("Activated jetway #" ~ me.id);
+  print_debug("Using door #" ~ me.door);
+  print_debug("Jetway heading:		" ~ me.heading ~ " deg");
+  print_debug("Extension:		" ~ me.target_extend ~ " m");
+  print_debug("Pitch:			" ~ me.target_pitch ~ " deg");
+  print_debug("Heading:		" ~ me.target_heading ~ " deg");
+  print_debug("Entrance heading:	" ~ me.target_ent_heading ~ " deg");
+  print_debug("Hood:			" ~ me.target_hood ~ " deg");
+  print_debug("Total animation time:	" ~ animation_time ~ " sec");
+  print_debug("Jetway extending");
+  print_debug("************************************************");
+  },
+ retract: func(user)
+  {
+  if (user) alert("Retracting jetway.");
+  me.door_object.close();
+  me.extended = 0;
+
+  print_debug("************************************************");
+  print_debug("Activated jetway #" ~ me.id);
+  print_debug("Total animation time:	" ~ me.door_object.swingtime ~ " sec");
+  print_debug("Jetway retracting");
+  print_debug("************************************************");
+  },
+ remove: func
+  {
+  me.node.remove();
+  var id = me.id;
+  jetways[me.id] = nil;
+  print_debug("Unloaded jetway #" ~ id);
+  },
+ reload: func
+  {
+  var airport = me.airport;
+  var model = me.model;
+  var gate = me.gate;
+  var door = me.door;
+  var airline = me.airline;
+  var lat = me.lat;
+  var lon = me.lon;
+  var elev = me.elev;
+  var heading = geo.normdeg(180 - (me.heading - 360));
+  var init_extend = me.init_extend;
+  var init_heading = me.init_heading;
+  var init_pitch = me.init_pitch;
+  var init_ent_heading = me.init_ent_heading;
+  me.remove();
+  Jetway.new(airport, model, gate, door, airline, lat, lon, elev, heading, init_extend, init_heading, init_pitch, init_ent_heading);
+  },
+ setpos: func(lat, lon, hdg, alt)
+  {
+  me.node.getNode("latitude-deg").setValue(lat);
+  me.lat = lat;
+  me.node.getNode("longitude-deg").setValue(lon);
+  me.lon = lon;
+  me.node.getNode("heading-deg").setValue(geo.normdeg(hdg - 180));
+  me.heading = hdg;
+  me.node.getNode("elevation-ft").setValue(alt * M2FT);
+  me.elev = alt;
+  },
+ setmodel: func(model, airline, gate)
+  {
+  me.airline = airline;
+  me.gate = gate;
+  me.model = model;
+  me.extended = 0;
+  me.target_extend = 0;
+  me.target_pitch = 0;
+  me.target_heading = 0;
+  me.target_ent_heading = 0;
+  me.target_hood = 0;
+  me.door_object.setpos(0);
+  me.reload();
+  }
+ };
+
+## Interaction functions
+########################
+
+var dialog = func
+ {
+ if (dialog_object == nil) dialog_object = gui.Dialog.new("/sim/gui/dialogs/jetways/dialog", "gui/dialogs/jetways.xml");
+ dialog_object.open();
+ };
+
+var toggle_jetway = func(n)
+ {
+ var jetway = jetways[n];
+ if (jetway == nil) return;
+ var door = props.globals.getNode("/sim/model/door[" ~ jetway.door ~ "]");
+ if (door == nil)
+  {
+  alert("Your aircraft does not define the location of door " ~ (jetway.door + 1) ~ ", cannot extend this jetway.");
+  return;
+  }
+
+ # get the coordinates of the user's aircraft and offset for the door position and aircraft pitch
+ var coord = geo.aircraft_position();
+ var heading = getprop("/orientation/heading-deg");
+ var pitch = getprop("/orientation/pitch-deg");
+ coord.apply_course_distance(heading, -door.getChild("position-x-m").getValue());
+ coord.apply_course_distance(heading + 90, door.getChild("position-y-m").getValue());
+ coord.set_alt(coord.alt() + door.getChild("position-z-m").getValue());
+ coord.set_alt(coord.alt() + math.tan(pitch * D2R) * -door.getChild("position-x-m").getValue());
+
+ jetway.toggle(1, heading, coord);
+ };
+var toggle_jetway_from_coord = func(door, hood, heading, lat, lon = nil)
+ {
+ if (isa(lat, geo.Coord))
+  {
+  var coord = lat;
+  }
+ else
+  {
+  var coord = geo.Coord.new();
+  coord.set_latlon(lat, lon);
+  }
+ var closest_jetway = nil;
+ var closest_jetway_dist = nil;
+ var closest_jetway_coord = nil;
+ for (var i = 0; i < size(jetways); i += 1)
+  {
+  if (jetways[i] == nil) continue;
+  var jetway = jetways[i];
+  var jetway_coord = geo.Coord.new();
+  jetway_coord.set_latlon(jetway.lat, jetway.lon);
+
+  var distance = jetway_coord.distance_to(coord);
+  if ((closest_jetway_dist == nil or distance < closest_jetway_dist) and jetway.door == door)
+   {
+   closest_jetway = jetway;
+   closest_jetway_dist = distance;
+   closest_jetway_coord = jetway_coord;
+   }
+  }
+ if (closest_jetway == nil)
+  {
+  print_debug("No jetways available");
+  }
+ elsif (!closest_jetway.extended)
+  {
+  closest_jetway.toggle(0, heading, coord, hood);
+  }
+ };
+var toggle_jetway_from_model = func(model)
+ {
+ model = aircraft.makeNode(model);
+ var doors = model.getChildren("door");
+ if (doors == nil or size(doors) == 0) return;
+ for (var i = 0; i < size(doors); i += 1)
+  {
+  var coord = geo.Coord.new();
+  var hdg = model.getNode("orientation/true-heading-deg").getValue();
+  var lat = model.getNode("position/latitude-deg").getValue();
+  var lon = model.getNode("position/longitude-deg").getValue();
+  var alt = model.getNode("position/altitude-ft").getValue() * FT2M + doors[i].getNode("position-z-m").getValue();
+  coord.set_latlon(lat, lon, alt);
+  coord.apply_course_distance(hdg, -doors[i].getNode("position-x-m").getValue());
+  coord.apply_course_distance(hdg + 90, doors[i].getNode("position-y-m").getValue());
+  print_debug("Connecting a jetway to door #" ~ i ~ " for model " ~ model.getPath());
+  toggle_jetway_from_coord(i, doors[i].getNode("jetway-hood-deg").getValue(), hdg, coord);
+  }
+ };
+
+## Internal functions
+#####################
+
+# loads jetways at an airport
+var load_airport_jetways = func(airport)
+ {
+ if (isin(loaded_airports, airport)) return;
+ var tree = props.globals.getNode("/sim/paths/use-custom-scenery-data", 1).getBoolValue() ? io.read_airport_properties(airport, "jetways") : (io.stat(root ~ "/AI/Airports/" ~ airport ~ "/jetways.xml") == nil ? nil : io.read_properties(root ~ "/AI/Airports/" ~ airport ~ "/jetways.xml"));
+ if (tree == nil) return;
+ print_debug("Loading jetways for airport " ~ airport);
+ var nodes = tree.getChildren("jetway");
+
+ loadids[airport] = loadids[airport] == nil ? 0 : loadids[airport] + 1;
+ var i = 0;
+ var loop = func(id)
+  {
+  if (id != loadids[airport]) return;
+  if (i >= size(nodes))
+   {
+   append(loaded_airports, airport);
+   return;
+   }
+  var jetway = nodes[i];
+  var model = jetway.getNode("model", 1).getValue() or return;
+  var gate = jetway.getNode("gate", 1).getValue() or "";
+  var door = jetway.getNode("door", 1).getValue() or 0;
+  var airline = jetway.getNode("airline", 1).getValue() or "None";
+  var lat = jetway.getNode("latitude-deg", 1).getValue() or return;
+  var lon = jetway.getNode("longitude-deg", 1).getValue() or return;
+  var elev = jetway.getNode("elevation-m", 1).getValue() or 0;
+  var heading = jetway.getNode("heading-deg", 1).getValue() or 0;
+  var init_extend = jetway.getNode("initial-position/jetway-extension-m", 1).getValue() or 0;
+  var init_heading = jetway.getNode("initial-position/jetway-heading-deg", 1).getValue() or 0;
+  var init_pitch = jetway.getNode("initial-position/jetway-pitch-deg", 1).getValue() or 0;
+  var init_ent_heading = jetway.getNode("initial-position/entrance-heading-deg", 1).getValue() or 0;
+  Jetway.new(airport, model, gate, door, airline, lat, lon, elev, heading, init_extend, init_heading, init_pitch, init_ent_heading);
+
+  i += 1;
+  settimer(func loop(id), LOAD_JETWAY_PERIOD);
+  };
+ settimer(func loop(loadids[airport]), 0);
+ };
+# unloads jetways at an airport
+var unload_airport_jetways = func(airport)
+ {
+ print_debug("Unloading jetways for airport " ~ airport);
+ foreach (var jetway; jetways)
+  {
+  if (jetway != nil and jetway.airport == airport) jetway.remove();
+  }
+ remove(loaded_airports, airport);
+ };
+
+# restarts the main update loop
+var restart = func(node)
+ {
+ if (!node.getBoolValue()) return;
+ update_loopid += 1;
+ update_jetways(update_loopid);
+ settimer(func
+  {
+  load_loopid += 1;
+  load_jetways(load_loopid);
+  }, 2);
+ print("Animated jetways ... initialized");
+ };
+# main update loop (runs when jetways are enabled)
+var update_jetways = func(loopid)
+ {
+ # terminate if loopid does not match
+ if (loopid != update_loopid) return;
+ # if jetways disabled, unload jetways and terminate
+ if (!on_switch.getBoolValue())
+  {
+  for (var i = 0; i < size(jetways); i += 1)
+   {
+   if (jetways[i] != nil) jetways[i].remove();
+   }
+  setsize(jetways, 0);
+  setsize(loaded_airports, 0);
+  return;
+  }
+
+ var nearest_airport = airportinfo();
+ nearest_airport = nearest_airport == nil ? nil : nearest_airport.id;
+ if (isin(loaded_airports, nearest_airport))
+  {
+  # loop through the AI aircraft and extend/retract jetways
+  var ai_aircraft = props.globals.getNode("ai/models").getChildren("aircraft");
+  foreach (var aircraft; ai_aircraft)
+   {
+   if (!aircraft.getNode("valid", 1).getBoolValue()) continue;
+   var connected = aircraft.getNode("connected-to-jetways", 1);
+   var velocity = aircraft.getNode("velocities/true-airspeed-kt").getValue();
+   # TODO: Find a better way to know when the aircraft is "parked"
+   if (velocity > -1 and velocity < 1)
+    {
+    if (!connected.getBoolValue()) toggle_jetway_from_model(aircraft);
+    connected.setBoolValue(1);
+    }
+   else
+    {
+    if (connected.getBoolValue()) toggle_jetway_from_model(aircraft);
+    connected.setBoolValue(0);
+    }
+   }
+  # loop through the multiplayer aircraft and extend/retract jetways
+  # TODO: In the future, broadcast jetway properties over MP, making this part obselete
+  if (mp_switch.getBoolValue())
+   {
+   var multiplayers = props.globals.getNode("ai/models").getChildren("multiplayer");
+   foreach (var aircraft; multiplayers)
+    {
+    if (!aircraft.getNode("valid", 1).getBoolValue()) continue;
+    var connected = aircraft.getNode("connected-to-jetways", 1);
+    var velocity = aircraft.getNode("velocities/true-airspeed-kt").getValue();
+    if (velocity > -1 and velocity < 1)
+     {
+     if (!connected.getBoolValue()) toggle_jetway_from_model(aircraft);
+     connected.setBoolValue(1);
+     }
+    else
+     {
+     if (connected.getBoolValue()) toggle_jetway_from_model(aircraft);
+     connected.setBoolValue(0);
+     }
+    }
+   }
+  }
+ # interpolate jetway values
+ for (var i = 0; i < size(jetways); i += 1)
+  {
+  if (jetways[i] == nil) continue;
+  var jetway = jetways[i];
+  var position = jetway.door_object.getpos();
+  jetway.node.getNode("jetway-position/extend-m").setValue(interpolate_table(extend_table, position) * jetway.target_extend + jetway.init_extend);
+  jetway.node.getNode("jetway-position/pitch-deg").setValue(interpolate_table(pitch_table, position) * jetway.target_pitch + jetway.init_pitch);
+  jetway.node.getNode("jetway-position/heading-deg").setValue(interpolate_table(heading_table, position) * jetway.target_heading + jetway.init_heading);
+  jetway.node.getNode("jetway-position/entrance-heading-deg").setValue(interpolate_table(heading_entrance_table, position) * jetway.target_ent_heading + jetway.init_ent_heading);
+  jetway.node.getNode("jetway-position/hood-deg").setValue(interpolate_table(hood_table, position) * jetway.target_hood);
+  }
+ settimer(func update_jetways(loopid), UPDATE_PERIOD);
+ };
+# loading/unloading loop (runs continuously)
+var load_jetways = func(loopid)
+ {
+ if (load_listenerid != nil) removelistener(load_listenerid);
+ # terminate if loopid does not match
+ # unloading jetways if jetways are disabled is handled by update loop
+ if (loopid != load_loopid or !on_switch.getBoolValue()) return;
+ var airports = find_airports(LOAD_DISTANCE);
+ if (airports == nil) return;
+ # search for any airports out of range and unload their jetways
+ foreach (var airport; loaded_airports)
+  {
+  if (!isin(airports, airport))
+   {
+   unload_airport_jetways(airport);
+   }
+  }
+ # load any airports in range
+ foreach (var airport; airports)
+  {
+  load_airport_jetways(airport);
+  }
+ settimer(func load_jetways(loopid), LOAD_PERIOD);
+ };
+## fire it up
+_setlistener("/sim/signals/fdm-initialized", func
+ {
+ # global variables
+ root = string.normpath(getprop("/sim/fg-root"));
+ home = string.normpath(getprop("/sim/fg-home"));
+ foreach (var scenery_path; props.globals.getNode("/sim").getChildren("fg-scenery"))
+  {
+  append(scenery, string.normpath(scenery_path.getValue()));
+  }
+ if (size(scenery) == 0) append(scenery, root ~ "/Scenery");
+
+ # properties
+ on_switch = props.globals.getNode(on_switch, 1);
+ debug_switch = props.globals.getNode(debug_switch, 1);
+ mp_switch = props.globals.getNode(mp_switch, 1);
+
+ jetway_id_prop = props.globals.getNode(jetway_id_prop, 1);
+ setlistener(on_switch, restart, 1, 0);
+ });
diff --git a/Nasal/jetways_edit.nas b/Nasal/jetways_edit.nas
new file mode 100644
index 000000000..1b16925c7
--- /dev/null
+++ b/Nasal/jetways_edit.nas
@@ -0,0 +1,593 @@
+###############################################################################
+##
+##  Animated Jetway System. Allows the user to edit jetways during runtime.
+##
+##  Copyright (C) 2011  Ryan Miller
+##  This file is licensed under the GPL license version 2 or later.
+##
+###############################################################################
+
+###############################################################################
+# (See http://wiki.flightgear.org/Howto:_Animated_jetways)
+#
+
+### Static jetway model profiles ###
+### This class specifies the offsets used when converting static jetways using the STG converter ###
+var Static_jetway =
+ [
+  # Models/Airport/jetway-movable.ac
+  # Models/Airport/jetway-movable.xml
+  # Models/Airport/jetway-movable-2.ac
+  # Models/Airport/jetway-movable-2.xml
+  # Models/Airport/jetway-movable-3.ac
+  # Models/Airport/jetway-movable-3.xml
+  {
+  models:
+   [
+   "Models/Airport/jetway-movable.ac",
+   "Models/Airport/jetway-movable.xml",
+   "Models/Airport/jetway-movable-2.ac",
+   "Models/Airport/jetway-movable-2.xml",
+   "Models/Airport/jetway-movable-3.ac",
+   "Models/Airport/jetway-movable-3.xml"
+   ],
+  offsets:
+   {
+   x: -2.042,
+   y: 0,
+   z: 0,
+   heading: 0
+   },
+  init_pos:
+   {
+   extend: 7.24,
+   heading: 0,
+   pitch: 0,
+   ent_heading: -90
+   },
+  model: "generic",
+  airline: "None"
+  },
+  # Models/Airport/jetway.xml
+  # Models/Airport/jetway-ba.ac
+  # Models/Airport/jetway-ba.xml
+  {
+  models:
+   [
+   "Models/Airport/jetway.xml",
+   "Models/Airport/jetway-ba.ac",
+   "Models/Airport/jetway-ba.xml"
+   ],
+  offsets:
+   {
+   x: 0,
+   y: 0,
+   z: -0.25,
+   heading: 0
+   },
+  init_pos:
+   {
+   extend: 7.24,
+   heading: -6.7,
+   pitch: -3.6,
+   ent_heading: -83.3
+   },
+  model: "generic",
+  airline: "None"
+  },
+  # Models/Airport/jetway-737-ba.ac
+  # Models/Airport/jetway-737-ba.xml
+  {
+  models:
+   [
+   "Models/Airport/jetway-737-ba.ac",
+   "Models/Airport/jetway-737-ba.xml"
+   ],
+  offsets:
+   {
+   x: 0,
+   y: 0,
+   z: -0.25,
+   heading: 0
+   },
+  init_pos:
+   {
+   extend: 7.24,
+   heading: -6.7,
+   pitch: -4,
+   ent_heading: -83.3
+   },
+  model: "generic",
+  airline: "None"
+  },
+  # Models/Airport/jetway-747-ba.ac
+  # Models/Airport/jetway-747-ba.xml
+  {
+  models:
+   [
+   "Models/Airport/jetway-747-ba.ac",
+   "Models/Airport/jetway-747-ba.xml"
+   ],
+  offsets:
+   {
+   x: 0,
+   y: 0,
+   z: -0.25,
+   heading: 0
+   },
+  init_pos:
+   {
+   extend: 7.24,
+   heading: -6.7,
+   pitch: 2,
+   ent_heading: -83.3
+   },
+  model: "generic",
+  airline: "None"
+  },
+  # Models/Airport/jetway-a320-ba.ac
+  # Models/Airport/jetway-a320-ba.xml
+  {
+  models:
+   [
+   "Models/Airport/jetway-a320-ba.ac",
+   "Models/Airport/jetway-a320-ba.xml"
+   ],
+  offsets:
+   {
+   x: 0,
+   y: 0,
+   z: -0.25,
+   heading: 0
+   },
+  init_pos:
+   {
+   extend: 7.24,
+   heading: -6.7,
+   pitch: -1.6,
+   ent_heading: -83.3
+   },
+  model: "generic",
+  airline: "None"
+  },
+  # Models/Airport/AutoGate-ba.ac
+  # Models/Airport/AutoGate.xml
+  {
+  models:
+   [
+   "Models/Airport/AutoGate-ba.ac",
+   "Models/Airport/AutoGate.xml"
+   ],
+  offsets:
+   {
+   x: -10,
+   y: 25,
+   z: 0,
+   heading: -90
+   },
+  init_pos:
+   {
+   extend: 7.68,
+   heading: 0,
+   pitch: 0,
+   ent_heading: -90
+   },
+  model: "generic",
+  airline: "None"
+  },
+  # Models/Airport/DockingGate-ba.ac
+  # Models/Airport/DockingGate.xml
+  {
+  models:
+   [
+   "Models/Airport/DockingGate-ba.ac",
+   "Models/Airport/DockingGate.xml"
+   ],
+  offsets:
+   {
+   x: -10,
+   y: 5,
+   z: 0,
+   heading: -90
+   },
+  init_pos:
+   {
+   extend: 7.68,
+   heading: 0,
+   pitch: 0,
+   ent_heading: -90
+   },
+  model: "generic",
+  airline: "None"
+  }
+ ];
+
+### Rest of script follows below ###
+### Watch your step! :) ###
+var dialog_object = nil;
+var selected_jetway = nil;
+var mouse_mmb = 0;
+var kbd_shift = nil;
+var kbd_ctrl = nil;
+var kbd_alt = nil;
+var enabled = nil;
+var FLASH_PERIOD = 0.3;
+var FLASH_NUM = 3;
+var filedialog_listener = 0;
+
+var click = func(pos)
+ {
+ if (kbd_alt.getBoolValue())
+  {
+  if (selected_jetway == nil) return;
+  selected_jetway.setpos(pos.lat(), pos.lon(), selected_jetway.heading, pos.alt());
+  }
+ elsif (kbd_shift.getBoolValue())
+  {
+  selected_jetway = nil;
+  }
+ elsif (kbd_ctrl.getBoolValue())
+  {
+  var nearest_jetway = nil;
+  var min_dist = geo.ERAD;
+  for (var i = 0; i < size(jetways.jetways); i += 1)
+   {
+   var jetway = jetways.jetways[i];
+   if (jetway == nil) continue;
+   var dist = geo.Coord.new().set_latlon(jetway.lat, jetway.lon, jetway.elev).direct_distance_to(pos);
+   if (dist < min_dist)
+    {
+    min_dist = dist;
+    nearest_jetway = jetway;
+    }
+   }
+  if (nearest_jetway != nil)
+   {
+   selected_jetway = nearest_jetway;
+   setprop("/sim/jetways/adjust/model", selected_jetway.model);
+   setprop("/sim/jetways/adjust/door", selected_jetway.door);
+   setprop("/sim/jetways/adjust/airline", selected_jetway.airline);
+   setprop("/sim/jetways/adjust/gate", selected_jetway.gate);
+   flash(nearest_jetway);
+   }
+  }
+ else
+  {
+  var airport = getprop("/sim/airport/closest-airport-id");
+  if (airport == "") return;
+  selected_jetway = jetways.Jetway.new(airport, "generic", "FG", 0, "FGFS", pos.lat(), pos.lon(), pos.alt(), 0);
+  if (!jetways.isin(jetways.loaded_airports, airport)) append(jetways.loaded_airports, airport);
+  setprop("/sim/jetways/adjust/model", selected_jetway.model);
+  setprop("/sim/jetways/adjust/door", selected_jetway.door);
+  setprop("/sim/jetways/adjust/airline", selected_jetway.airline);
+  setprop("/sim/jetways/adjust/gate", selected_jetway.gate);
+  flash(selected_jetway);
+  }
+ };
+var delete = func
+ {
+ if (selected_jetway == nil) return;
+ selected_jetway.remove();
+ selected_jetway = nil;
+ };
+var adjust = func(name, value)
+ {
+ if (selected_jetway == nil) return;
+ if (name == "longitudinal")
+  {
+  var jetway_pos = geo.Coord.new();
+  jetway_pos.set_latlon(selected_jetway.lat, selected_jetway.lon, selected_jetway.elev);
+  var dir = geo.aircraft_position().course_to(jetway_pos);
+  jetway_pos.apply_course_distance(dir, value);
+  selected_jetway.setpos(jetway_pos.lat(), jetway_pos.lon(), selected_jetway.heading, selected_jetway.elev);
+  }
+ elsif (name == "transversal")
+  {
+  var jetway_pos = geo.Coord.new();
+  jetway_pos.set_latlon(selected_jetway.lat, selected_jetway.lon, selected_jetway.elev);
+  var dir = geo.aircraft_position().course_to(jetway_pos) + 90;
+  jetway_pos.apply_course_distance(dir, value);
+  selected_jetway.setpos(jetway_pos.lat(), jetway_pos.lon(), selected_jetway.heading, selected_jetway.elev);
+  }
+ elsif (name == "altitude")
+  {
+  var elev = selected_jetway.elev + value * 0.4;
+  selected_jetway.setpos(selected_jetway.lat, selected_jetway.lon, selected_jetway.heading, elev);
+  }
+ elsif (name == "heading")
+  {
+  var hdg = geo.normdeg(selected_jetway.heading + value * 4);
+  selected_jetway.setpos(selected_jetway.lat, selected_jetway.lon, hdg, selected_jetway.elev);
+  }
+ elsif (name == "initial-extension")
+  {
+  var newvalue = selected_jetway.init_extend + value;
+  if (newvalue > selected_jetway.max_extend)
+   {
+   gui.popupTip("Value exceeds maximum jetway extension limit");
+   }
+  elsif (newvalue < selected_jetway.min_extend)
+   {
+   gui.popupTip("Value lower than minimum jetway extension limit");
+   }
+  else
+   {
+   selected_jetway.init_extend = newvalue;
+   }
+  }
+ elsif (name == "initial-pitch")
+  {
+  selected_jetway.init_pitch += value;
+  }
+ elsif (name == "initial-heading")
+  {
+  selected_jetway.init_heading += value;
+  }
+ elsif (name == "initial-entrance-heading")
+  {
+  selected_jetway.init_ent_heading += value;
+  }
+ elsif (name == "model")
+  {
+  selected_jetway.setmodel(value, selected_jetway.airline, selected_jetway.gate);
+  selected_jetway = jetways.jetways[getprop("/sim/jetways/last-loaded-jetway")];
+  }
+ elsif (name == "door")
+  {
+  selected_jetway.door = value;
+  }
+ elsif (name == "airline")
+  {
+  selected_jetway.setmodel(selected_jetway.model, value, selected_jetway.gate);
+  selected_jetway = jetways.jetways[getprop("/sim/jetways/last-loaded-jetway")];
+  }
+ elsif (name == "gate")
+  {
+  selected_jetway.setmodel(selected_jetway.model, selected_jetway.airline, value);
+  selected_jetway = jetways.jetways[getprop("/sim/jetways/last-loaded-jetway")];
+  }
+ };
+var export = func
+ {
+ var path = getprop("/sim/fg-home") ~ "/Export/";
+ var airports = {};
+ var airportarray = [];
+ foreach (var jetway; jetways.jetways)
+  {
+  if (jetway == nil) continue;
+  if (airports[jetway.airport] == nil)
+   {
+   airports[jetway.airport] = [];
+   append(airportarray, jetway.airport);
+   }
+  var node = props.Node.new();
+  node.getNode("model", 1).setValue(jetway.model);
+  node.getNode("gate", 1).setValue(jetway.gate);
+  node.getNode("door", 1).setIntValue(jetway.door);
+  node.getNode("airline", 1).setValue(jetway.airline);
+  node.getNode("latitude-deg", 1).setDoubleValue(jetway.lat);
+  node.getNode("longitude-deg", 1).setDoubleValue(jetway.lon);
+  node.getNode("elevation-m", 1).setDoubleValue(jetway.elev);
+  node.getNode("heading-deg", 1).setDoubleValue(geo.normdeg(180 - jetway.heading));
+  node.getNode("initial-position/jetway-extension-m", 1).setDoubleValue(jetway.init_extend);
+  node.getNode("initial-position/jetway-heading-deg", 1).setDoubleValue(jetway.init_heading);
+  node.getNode("initial-position/jetway-pitch-deg", 1).setDoubleValue(jetway.init_pitch);
+  node.getNode("initial-position/entrance-heading-deg", 1).setDoubleValue(jetway.init_ent_heading);
+  append(airports[jetway.airport], node);
+  }
+ foreach (var airport; airportarray)
+  {
+  var file = path ~ airport ~ ".xml";
+  var args = props.Node.new({ filename: file });
+  var nodes = airports[airport];
+  foreach (var node; nodes)
+   {
+   var data = args.getNode("data", 1);
+   for (var i = 0; 1; i += 1)
+    {
+    if (data.getChild("jetway", i, 0) == nil)
+     {
+     props.copy(node, data.getChild("jetway", i, 1));
+     break;
+     }
+    }
+   }
+  fgcommand("savexml", args);
+  print("jetway definitions for airport " ~ airport ~ " exported to " ~ file);
+  }
+ };
+var convert_stg = func
+ {
+ fgcommand("dialog-show", props.Node.new({ "dialog-name": "file-select" }));
+ setprop("/sim/gui/dialogs/file-select/path", "");
+ filedialog_listener = setlistener("/sim/gui/dialogs/file-select/path", func(n)
+  {
+  removelistener(filedialog_listener);
+  var path = n.getValue();
+  if (path == "") return;
+  var stg = io.readfile(path);
+  var stg_lines = [[]];
+  var current_word = "";
+  for (var i = 0; i < size(stg); i += 1)
+   {
+   var char = substr(stg, i, 1);
+   if (char == " " or char == "\n")
+    {
+    append(stg_lines[size(stg_lines) - 1], current_word);
+    current_word = "";
+    if (char == "\n") append(stg_lines, []);
+    }
+   else
+    {
+    current_word ~= char;
+    }
+   }
+
+  var jetway_array = [];
+  foreach (var line; stg_lines)
+   {
+   if (size(line) < 6 or line[0] != "OBJECT_SHARED") continue;
+   var foundmodel = 0;
+   var jetway = nil;
+   foreach (var profile; Static_jetway)
+    {
+    foreach (var model; profile.models)
+     {
+     if (model == line[1]) foundmodel = 1;
+     }
+    if (foundmodel)
+     {
+     jetway = profile;
+     break;
+     }
+    }
+   if (jetway == nil) continue;
+   var heading = num(line[5]);
+   var coord = geo.Coord.new();
+   coord.set_latlon(line[3], line[2], line[4]);
+   coord.apply_course_distance(360 - heading, -jetway.offsets.x);
+   coord.apply_course_distance(360 - heading + 90, jetway.offsets.y);
+   coord.set_alt(coord.alt() + jetway.offsets.z);
+   var hash = {};
+   hash.coord = coord;
+   hash.heading = heading + jetway.offsets.heading;
+   hash.init_extend = jetway.init_pos.extend;
+   hash.init_heading = jetway.init_pos.heading;
+   hash.init_pitch = jetway.init_pos.pitch;
+   hash.init_ent_heading = jetway.init_pos.ent_heading;
+   hash.model = jetway.model;
+   hash.airline = jetway.airline;
+   append(jetway_array, hash);
+   }
+
+  var airport = getprop("/sim/jetways/closest-airport-id");
+  if (airport == "") return;
+  var i = 0;
+  var loop = func
+   {
+   if (i >= size(jetway_array)) return;
+   var jetway = jetway_array[i];
+   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);
+   if (!jetways.isin(jetways.loaded_airports, airport)) append(jetways.loaded_airports, airport);
+   i += 1;
+   settimer(loop, jetways.LOAD_JETWAY_PERIOD);
+   };
+  settimer(loop, 0);
+  jetways.alert("Creating " ~ size(jetway_array) ~ " jetways for airport " ~ airport);
+  }, 0, 1);
+ };
+var flash = func(jetway)
+ {
+ if (!contains(jetway, "_flashnum") or jetway._flashnum == -1)
+  {
+  jetway._elev = jetway.elev;
+  jetway.setpos(jetway.lat, jetway.lon, jetway.heading, -geo.ERAD);
+  jetway._flashnum = 0;
+  settimer(func flash(jetway), FLASH_PERIOD);
+  }
+ elsif (!contains(jetway, "_elev"))
+  {
+  jetway._flashnum = -1;
+  jetway.setpos(jetway.lat, jetway.lon, geo.elevation(jetway.lat, jetway.lon));
+  return;
+  }
+ elsif (jetway._flashnum == FLASH_NUM + 1)
+  {
+  jetway.setpos(jetway.lat, jetway.lon, jetway.heading, jetway._elev);
+  jetway._elev = nil;
+  jetway._flashnum = -1;
+  }
+ else
+  {
+  if (jetway.elev == -geo.ERAD)
+   {
+   jetway.setpos(jetway.lat, jetway.lon, jetway.heading, jetway._elev);
+   }
+  else
+   {
+   jetway.setpos(jetway.lat, jetway.lon, jetway.heading, -geo.ERAD);
+   }
+  jetway._flashnum += 1;
+  settimer(func flash(jetway), FLASH_PERIOD);
+  }
+ };
+
+var dialog = func
+ {
+ if (dialog_object == nil) dialog_object = gui.Dialog.new("/sim/gui/dialogs/jetways-adjust/dialog", "gui/dialogs/jetways-adjust.xml");
+ dialog_object.open();
+ };
+var print_help = func
+ {
+ print("JETWAY EDITOR HELP");
+ print("*******************************************************");
+ print("See: http://wiki.flightgear.org/Howto:_Animated_jetways");
+ print("");
+ print("Adjust position, heading, and altitude with top sliders");
+ print("Adjust initial jetway positions with bottom sliders");
+ print("");
+ print("<Model>			model of selected jetway");
+ print("<Door>			aircraft door number of selected jetway");
+ print("<Airline sign>		airline sign code of selected jetway");
+ print("<Gate>			gate number of selected jetway");
+ print("");
+ print("[Center sliders]	apply slider offsets and return sliders to 0");
+ print("[Export]		export jetway definition file(s)");
+ print("[STG converter]		convert static jetways in STG files to animated jetways");
+ print("[?]			show this help text");
+ print("");
+ print("Click			add jetway on click position");
+ print("Alt-click		move selected jetway to click position");
+ print("Ctrl-click		select a jetway near click position");
+ print("Shift-click		deselect selected jetway");
+ print("Backspace		delete selected jetway");
+ print("*******************************************************");
+ };
+
+_setlistener("/sim/signals/nasal-dir-initialized", func
+ {
+ kbd_shift = props.globals.getNode("/devices/status/keyboard/shift");
+ kbd_ctrl = props.globals.getNode("/devices/status/keyboard/ctrl");
+ kbd_alt = props.globals.getNode("/devices/status/keyboard/alt");
+ enabled = props.globals.getNode("/sim/jetways/enable-editor");
+
+ setlistener("/sim/jetways/adjust/model", func(n)
+  {
+  var v = n.getValue();
+  if (selected_jetway != nil and v != selected_jetway.model)
+   {
+   adjust("model", v);
+   }
+  }, 0, 0);
+ setlistener("/sim/jetways/adjust/door", func(n)
+  {
+  var v = n.getValue();
+  if (selected_jetway != nil and v != selected_jetway.door)
+   {
+   adjust("door", v);
+   }
+  }, 0, 0);
+ setlistener("/sim/jetways/adjust/airline", func(n)
+  {
+  var v = n.getValue();
+  if (selected_jetway != nil and v != selected_jetway.airline)
+   {
+   adjust("airline", v);
+   }
+  }, 0, 0);
+ setlistener("/sim/jetways/adjust/gate", func(n)
+  {
+  var v = n.getValue();
+  if (selected_jetway != nil and v != selected_jetway.gate)
+   {
+   adjust("gate", v);
+   }
+  }, 0, 0);
+ setlistener("/devices/status/keyboard/event", func(event)
+  {
+  if (!event.getNode("pressed").getValue()) return;
+  if (enabled.getBoolValue() and event.getNode("key").getValue() == 8) delete();
+  });
+ setlistener("/devices/status/mice/mouse/button[1]", func(n) mouse_mmb = n.getBoolValue(), 1, 0);
+ setlistener("/sim/signals/click", func if (!mouse_mmb and enabled.getBoolValue()) click(geo.click_position()));
+ });
diff --git a/gui/dialogs/jetways-adjust.xml b/gui/dialogs/jetways-adjust.xml
new file mode 100644
index 000000000..692acc7b9
--- /dev/null
+++ b/gui/dialogs/jetways-adjust.xml
@@ -0,0 +1,990 @@
+<?xml version="1.0" ?>
+
+<PropertyList>
+
+	<name>jetways-adjust</name>
+	<modal>false</modal>
+	<layout>vbox</layout>
+
+	<nasal>
+		<open><![CDATA[
+		 var self = cmdarg();
+		 var dlgname = self.getNode("name").getValue();
+		 var root = getprop("/sim/fg-root");
+		 var modelfiles = directory(root ~ "/Models/Airport/Jetway");
+		 var modelcombo = self.getNode("group[9]/combo[0]");
+		 var models = [];
+		 foreach (var file; modelfiles)
+		  {
+		  if (substr(file, -3) == "xml")
+		   {
+		   append(models, substr(file, 0, size(file) - 4));
+		   }
+		  }
+		 for (var i = 0; i < size(models); i += 1)
+		  {
+		  modelcombo.getNode("value[" ~ i ~ "]", 1).setValue(models[i]);
+		  }
+
+		 var airlinefiles = directory(root ~ "/Models/Airport/Jetway/Airlines");
+		 var airlinecombo = self.getNode("group[9]/combo[2]");
+		 var airlines = [];
+		 foreach (var file; airlinefiles)
+		  {
+		  append(airlines, substr(file, 0, size(file) - 4));
+		  }
+		 for (var i = 0; i < size(airlines); i += 1)
+		  {
+		  airlinecombo.getNode("value[" ~ i ~ "]", 1).setValue(airlines[i]);
+		  }
+
+		 var Value =
+		  {
+		  new: func(name)
+		   {
+		   var m = { parents: [Value] };
+		   m.name = name;
+		   var n = props.globals.getNode("/sim/jetways/adjust/" ~ m.name, 1);
+		   m.sliderN = n.getNode("slider", 1);
+		   m.offsetN = n.getNode("offset", 1);
+		   m.offsetN.setDoubleValue(0);
+		   m.sliderN.setDoubleValue(0);
+		   m.last_slider = 0;
+		   m.center();
+		   m.sliderL = setlistener(m.sliderN, func m.update());
+		   return m;
+		   },
+		  update: func
+		   {
+		   var offset = me.sliderN.getValue();
+		   var value = me.offsetN.getValue() + me.sliderN.getValue() - me.last_slider;
+		   jetways_edit.adjust(me.name, value);
+		   me.offsetN.setDoubleValue(0);
+		   me.last_slider = offset;
+		   },
+		  center : func
+		   {
+		   me.offsetN.setValue(me.offsetN.getValue() + me.sliderN.getValue());
+		   me.sliderN.setDoubleValue(0);
+		   },
+		  remove: func
+		   {
+		   removelistener(me.sliderL);
+		   }
+		  };
+		 var values =
+		  [
+		  Value.new("longitudinal"),
+		  Value.new("transversal"),
+		  Value.new("altitude"),
+		  Value.new("heading"),
+		  Value.new("initial-extension"),
+		  Value.new("initial-heading"),
+		  Value.new("initial-pitch"),
+		  Value.new("initial-entrance-heading")
+		  ];
+		 var center_sliders = func
+		  {
+		  foreach (var v; values)
+		   {
+		   v.center();
+		   }
+		  };
+		 var update = func(w)
+		  {
+		  self.setValues({"dialog-name": dlgname, "object-name": w});
+		  fgcommand("dialog-update", self);
+		  center_sliders();
+		  };
+		]]></open>
+		<close>
+		 foreach (var v; values)
+		  {
+		  v.remove();
+		  }
+		</close>
+	</nasal>
+
+	<group>
+		<layout>hbox</layout>
+		<empty><stretch>1</stretch></empty>
+
+		<text>
+			<label>Jetway Editor</label>
+		</text>
+
+		<empty><stretch>1</stretch></empty>
+
+		<button>
+			<pref-width>16</pref-width>
+			<pref-height>16</pref-height>
+			<legend></legend>
+			<default>1</default>
+			<keynum>27</keynum>
+			<border>2</border>
+			<binding>
+				<command>dialog-close</command>
+			</binding>
+			<binding>
+				<command>dialog-apply</command>
+			</binding>
+		</button>
+	</group>
+
+	<hrule />
+
+	<group>
+		<layout>hbox</layout>
+		<default-padding>0</default-padding>
+
+		<button>
+			<legend>&lt;&lt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/longitudinal/offset</property>
+				<step>-10</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("longitudinal")</script>
+			</binding>
+		</button>
+
+		<button>
+			<legend>&lt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/longitudinal/offset</property>
+				<step>-1</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("longitudinal")</script>
+			</binding>
+		</button>
+
+		<slider>
+			<name>longitudinal</name>
+			<property>/sim/jetways/adjust/longitudinal/slider</property>
+			<legend>near/far</legend>
+			<pref-width>250</pref-width>
+			<live>1</live>
+			<min>-1</min>
+			<max>1</max>
+
+			<color>
+				<red>1.0</red>
+				<green>0.6</green>
+				<blue>0.6</blue>
+				<alpha>1</alpha>
+			</color>
+
+			<binding>
+				<command>dialog-apply</command>
+				<object-name>longitudinal</object-name>
+			</binding>
+		</slider>
+
+		<button>
+			<legend>&gt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/longitudinal/offset</property>
+				<step>1</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("longitudinal")</script>
+			</binding>
+		</button>
+
+		<button>
+			<legend>&gt;&gt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/longitudinal/offset</property>
+				<step>10</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("longitudinal")</script>
+			</binding>
+		</button>
+	</group>
+
+	<group>
+		<layout>hbox</layout>
+		<default-padding>0</default-padding>
+
+		<button>
+			<legend>&lt;&lt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/transversal/offset</property>
+				<step>-10</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("transversal")</script>
+			</binding>
+		</button>
+
+		<button>
+			<legend>&lt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/transversal/offset</property>
+				<step>-1</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("transversal")</script>
+			</binding>
+		</button>
+
+		<slider>
+			<name>transversal</name>
+			<property>/sim/jetways/adjust/transversal/slider</property>
+			<legend>left/right</legend>
+			<pref-width>250</pref-width>
+			<live>1</live>
+			<min>-1</min>
+			<max>1</max>
+
+			<color>
+				<red>0.6</red>
+				<green>1.0</green>
+				<blue>0.6</blue>
+				<alpha>1.0</alpha>
+			</color>
+
+			<binding>
+				<command>dialog-apply</command>
+				<object-name>transversal</object-name>
+			</binding>
+		</slider>
+
+		<button>
+			<legend>&gt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/transversal/offset</property>
+				<step>1</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("transversal")</script>
+			</binding>
+		</button>
+
+		<button>
+			<legend>&gt;&gt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/transversal/offset</property>
+				<step>10</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("transversal")</script>
+			</binding>
+		</button>
+	</group>
+
+	<group>
+		<layout>hbox</layout>
+		<default-padding>0</default-padding>
+
+		<button>
+			<legend>&lt;&lt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/altitude/offset</property>
+				<step>-10</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("altitude")</script>
+			</binding>
+		</button>
+
+		<button>
+			<legend>&lt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/altitude/offset</property>
+				<step>-1</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("altitude")</script>
+			</binding>
+		</button>
+
+		<slider>
+			<name>altitude</name>
+			<property>/sim/jetways/adjust/altitude/slider</property>
+			<legend>altitude</legend>
+			<pref-width>250</pref-width>
+			<live>1</live>
+			<min>-1</min>
+			<max>1</max>
+
+			<color>
+				<red>0.6</red>
+				<green>0.6</green>
+				<blue>1.0</blue>
+				<alpha>1.0</alpha>
+			</color>
+
+			<binding>
+				<command>dialog-apply</command>
+				<object-name>altitude</object-name>
+			</binding>
+		</slider>
+
+		<button>
+			<legend>&gt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/altitude/offset</property>
+				<step>1</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("altitude")</script>
+			</binding>
+		</button>
+
+		<button>
+			<legend>&gt;&gt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/altitude/offset</property>
+				<step>10</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("altitude")</script>
+			</binding>
+		</button>
+	</group>
+
+	<group>
+		<layout>hbox</layout>
+		<default-padding>0</default-padding>
+
+		<button>
+			<legend>&lt;&lt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/heading/offset</property>
+				<step>-6</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("heading")</script>
+			</binding>
+		</button>
+
+		<button>
+			<legend>&lt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/heading/offset</property>
+				<step>-1</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("heading")</script>
+			</binding>
+		</button>
+
+		<slider>
+			<name>heading</name>
+			<property>/sim/jetways/adjust/heading/slider</property>
+			<legend>heading</legend>
+			<pref-width>250</pref-width>
+			<live>1</live>
+			<min>-1</min>
+			<max>1</max>
+
+			<color>
+				<red>1.0</red>
+				<green>1.0</green>
+				<blue>0.6</blue>
+				<alpha>1.0</alpha>
+			</color>
+
+			<binding>
+				<command>dialog-apply</command>
+				<object-name>heading</object-name>
+			</binding>
+		</slider>
+
+		<button>
+			<legend>&gt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/heading/offset</property>
+				<step>1</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("heading")</script>
+			</binding>
+		</button>
+
+		<button>
+			<legend>&gt;&gt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/heading/offset</property>
+				<step>6</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("heading")</script>
+			</binding>
+		</button>
+	</group>
+
+	<hrule />
+
+	<group>
+		<layout>hbox</layout>
+		<default-padding>0</default-padding>
+
+		<button>
+			<legend>&lt;&lt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/initial-extension/offset</property>
+				<step>-3</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("initial-extension")</script>
+			</binding>
+		</button>
+
+		<button>
+			<legend>&lt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/initial-extension/offset</property>
+				<step>-1</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("initial-extension")</script>
+			</binding>
+		</button>
+
+		<slider>
+			<name>initial-extension</name>
+			<property>/sim/jetways/adjust/initial-extension/slider</property>
+			<legend>extension offset</legend>
+			<pref-width>250</pref-width>
+			<live>1</live>
+			<min>-1</min>
+			<max>1</max>
+
+			<color>
+				<red>1.0</red>
+				<green>1.0</green>
+				<blue>1.0</blue>
+				<alpha>1.0</alpha>
+			</color>
+
+			<binding>
+				<command>dialog-apply</command>
+				<object-name>initial-extension</object-name>
+			</binding>
+		</slider>
+
+		<button>
+			<legend>&gt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/initial-extension/offset</property>
+				<step>1</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("initial-extension")</script>
+			</binding>
+		</button>
+
+		<button>
+			<legend>&gt;&gt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/initial-extension/offset</property>
+				<step>3</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("initial-extension")</script>
+			</binding>
+		</button>
+	</group>
+
+	<group>
+		<layout>hbox</layout>
+		<default-padding>0</default-padding>
+
+		<button>
+			<legend>&lt;&lt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/initial-pitch/offset</property>
+				<step>-2</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("initial-pitch")</script>
+			</binding>
+		</button>
+
+		<button>
+			<legend>&lt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/initial-pitch/offset</property>
+				<step>-1</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("initial-pitch")</script>
+			</binding>
+		</button>
+
+		<slider>
+			<name>initial-pitch</name>
+			<property>/sim/jetways/adjust/initial-pitch/slider</property>
+			<legend>pitch offset</legend>
+			<pref-width>250</pref-width>
+			<live>1</live>
+			<min>-1</min>
+			<max>1</max>
+
+			<color>
+				<red>0.8</red>
+				<green>0.8</green>
+				<blue>0.8</blue>
+				<alpha>0.8</alpha>
+			</color>
+
+			<binding>
+				<command>dialog-apply</command>
+				<object-name>initial-pitch</object-name>
+			</binding>
+		</slider>
+
+		<button>
+			<legend>&gt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/initial-pitch/offset</property>
+				<step>1</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("initial-pitch")</script>
+			</binding>
+		</button>
+
+		<button>
+			<legend>&gt;&gt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/initial-pitch/offset</property>
+				<step>2</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("initial-pitch")</script>
+			</binding>
+		</button>
+	</group>
+
+	<group>
+		<layout>hbox</layout>
+		<default-padding>0</default-padding>
+
+		<button>
+			<legend>&lt;&lt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/initial-heading/offset</property>
+				<step>-2</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("initial-heading")</script>
+			</binding>
+		</button>
+
+		<button>
+			<legend>&lt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/initial-heading/offset</property>
+				<step>-1</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("initial-heading")</script>
+			</binding>
+		</button>
+
+		<slider>
+			<name>initial-heading</name>
+			<property>/sim/jetways/adjust/initial-heading/slider</property>
+			<legend>rotation offset</legend>
+			<pref-width>250</pref-width>
+			<live>1</live>
+			<min>-1</min>
+			<max>1</max>
+
+			<color>
+				<red>0.6</red>
+				<green>0.6</green>
+				<blue>0.6</blue>
+				<alpha>0.6</alpha>
+			</color>
+
+			<binding>
+				<command>dialog-apply</command>
+				<object-name>initial-heading</object-name>
+			</binding>
+		</slider>
+
+		<button>
+			<legend>&gt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/initial-heading/offset</property>
+				<step>1</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("initial-heading")</script>
+			</binding>
+		</button>
+
+		<button>
+			<legend>&gt;&gt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/initial-heading/offset</property>
+				<step>2</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("initial-heading")</script>
+			</binding>
+		</button>
+	</group>
+
+	<group>
+		<layout>hbox</layout>
+		<default-padding>0</default-padding>
+
+		<button>
+			<legend>&lt;&lt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/initial-entrance-heading/offset</property>
+				<step>-6</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("initial-entrance-heading")</script>
+			</binding>
+		</button>
+
+		<button>
+			<legend>&lt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/initial-entrance-heading/offset</property>
+				<step>-1</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("initial-entrance-heading")</script>
+			</binding>
+		</button>
+
+		<slider>
+			<name>initial-entrance-heading</name>
+			<property>/sim/jetways/adjust/initial-entrance-heading/slider</property>
+			<legend>entrance rotation offset</legend>
+			<pref-width>250</pref-width>
+			<live>1</live>
+			<min>-1</min>
+			<max>1</max>
+
+			<color>
+				<red>0.4</red>
+				<green>0.4</green>
+				<blue>0.4</blue>
+				<alpha>0.4</alpha>
+			</color>
+
+			<binding>
+				<command>dialog-apply</command>
+				<object-name>initial-entrance-heading</object-name>
+			</binding>
+		</slider>
+
+		<button>
+			<legend>&gt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/initial-entrance-heading/offset</property>
+				<step>1</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("initial-entrance-heading")</script>
+			</binding>
+		</button>
+
+		<button>
+			<legend>&gt;&gt;</legend>
+			<pref-width>22</pref-width>
+			<pref-height>22</pref-height>
+
+			<binding>
+				<command>property-adjust</command>
+				<property>/sim/jetways/adjust/initial-entrance-heading/offset</property>
+				<step>6</step>
+			</binding>
+			<binding>
+				<command>nasal</command>
+				<script>update("initial-entrance-heading")</script>
+			</binding>
+		</button>
+	</group>
+
+	<group>
+		<layout>table</layout>
+		<text>
+			<row>0</row>
+			<col>0</col>
+			<halign>right</halign>
+			<label>Model:</label>
+		</text>
+		<combo>
+			<name>model</name>
+			<row>0</row>
+			<col>1</col>
+			<property>/sim/jetways/adjust/model</property>
+			<live type="bool">true</live>
+			<binding>
+				<command>dialog-apply</command>
+				<object-name>model</object-name>
+			</binding>
+		</combo>
+		<text>
+			<row>1</row>
+			<col>0</col>
+			<halign>right</halign>
+			<label>Door:</label>
+		</text>
+		<combo>
+			<name>door</name>
+			<row>1</row>
+			<col>1</col>
+			<property>/sim/jetways/adjust/door</property>
+			<live type="bool">true</live>
+			<value>0</value>
+			<value>1</value>
+			<value>2</value>
+			<binding>
+				<command>dialog-apply</command>
+				<object-name>door</object-name>
+			</binding>
+		</combo>
+		<text>
+			<row>0</row>
+			<col>2</col>
+			<halign>right</halign>
+			<label>Airline sign:</label>
+		</text>
+		<combo>
+			<name>airline</name>
+			<row>0</row>
+			<col>3</col>
+			<property>/sim/jetways/adjust/airline</property>
+			<live type="bool">true</live>
+			<binding>
+				<command>dialog-apply</command>
+				<object-name>airline</object-name>
+			</binding>
+		</combo>
+		<text>
+			<row>1</row>
+			<col>2</col>
+			<halign>right</halign>
+			<label>Gate:</label>
+		</text>
+		<input>
+			<name>gate</name>
+			<row>1</row>
+			<col>3</col>
+			<property>/sim/jetways/adjust/gate</property>
+			<live type="bool">true</live>
+			<binding>
+				<command>dialog-apply</command>
+				<object-name>gate</object-name>
+			</binding>
+		</input>
+	</group>
+
+	<hrule />
+
+	<group>
+		<layout>hbox</layout>
+		<button>
+			<pref-height>24</pref-height>
+			<legend>Center sliders</legend>
+			<binding>
+				<command>nasal</command>
+				<script>
+				 center_sliders();
+				</script>
+			</binding>
+		</button>
+		<button>
+			<pref-height>24</pref-height>
+			<legend>Export</legend>
+			<border>2</border>
+			<binding>
+				<command>nasal</command>
+				<script>
+				 jetways_edit.export();
+				</script>
+			</binding>
+		</button>
+		<button>
+			<pref-height>24</pref-height>
+			<legend>STG converter</legend>
+			<border>2</border>
+			<binding>
+				<command>nasal</command>
+				<script>
+				 jetways_edit.convert_stg();
+				</script>
+			</binding>
+		</button>
+		<button>
+			<pref-height>24</pref-height>
+			<legend>?</legend>
+			<border>2</border>
+			<binding>
+				<command>nasal</command>
+				<script>
+				 jetways_edit.print_help();
+				</script>
+			</binding>
+		</button>
+	</group>
+
+</PropertyList>
diff --git a/gui/dialogs/jetways.xml b/gui/dialogs/jetways.xml
new file mode 100644
index 000000000..8215178fa
--- /dev/null
+++ b/gui/dialogs/jetways.xml
@@ -0,0 +1,131 @@
+<?xml version="1.0" ?>
+
+<PropertyList>
+
+	<name>jetways</name>
+	<modal>false</modal>
+	<layout>vbox</layout>
+
+	<nasal>
+		<open><![CDATA[
+		 var self = cmdarg();
+		 var dlgname = self.getNode("name").getValue();
+		 var aptlist = props.globals.getNode(self.getNode("text/property").getValue(), 1);
+		 var UPDATE_PERIOD = 1;
+		 var update = func
+		  {
+		  var list = "";
+		  foreach (var apt; jetways.loaded_airports)
+		   {
+		   list ~= apt ~ " ";
+		   }
+		  aptlist.setValue(list == "" ? "No airports loaded" : "Loaded airports: " ~ list);
+		  settimer(update, UPDATE_PERIOD);
+		  };
+		 settimer(update, 0);
+		]]></open>
+	</nasal>
+
+	<group>
+		<layout>hbox</layout>
+		<empty><stretch>1</stretch></empty>
+
+		<text>
+			<label>Jetway Settings</label>
+		</text>
+
+		<empty><stretch>1</stretch></empty>
+
+		<button>
+			<pref-width>16</pref-width>
+			<pref-height>16</pref-height>
+			<legend></legend>
+			<default>1</default>
+			<keynum>27</keynum>
+			<border>2</border>
+			<binding>
+				<command>dialog-close</command>
+			</binding>
+			<binding>
+				<command>dialog-apply</command>
+			</binding>
+		</button>
+	</group>
+
+	<hrule />
+
+	<checkbox>
+		<halign>left</halign>
+		<label>Enable animated jetways</label>
+		<property>/sim/jetways/enabled</property>
+		<live type="bool">true</live>
+		<binding>
+			<command>dialog-apply</command>
+		</binding>
+	</checkbox>
+	<checkbox>
+		<halign>left</halign>
+		<label>Connect to multiplayer aircraft</label>
+		<property>/sim/jetways/interact-with-multiplay</property>
+		<live type="bool">true</live>
+		<enable>
+			<property>/sim/jetways/enabled</property>
+		</enable>
+		<binding>
+		        <command>dialog-apply</command>
+		</binding>
+	</checkbox>
+	<checkbox>
+		<halign>left</halign>
+		<label>Enable jetway editor</label>
+		<property>/sim/jetways/enable-editor</property>
+		<live type="bool">true</live>
+		<enable>
+			<property>/sim/jetways/enabled</property>
+		</enable>
+		<binding>
+		        <command>dialog-apply</command>
+		</binding>
+	</checkbox>
+	<checkbox>
+		<halign>left</halign>
+		<label>Debug mode</label>
+		<property>/sim/jetways/debug</property>
+		<live type="bool">true</live>
+		<enable>
+			<property>/sim/jetways/enabled</property>
+		</enable>
+		<binding>
+		        <command>dialog-apply</command>
+		</binding>
+	</checkbox>
+
+	<group>
+		<layout>hbox</layout>
+		<button>
+			<legend>Open editor</legend>
+			<enable>
+				<and>
+					<property>/sim/jetways/enabled</property>
+					<property>/sim/jetways/enable-editor</property>
+				</and>
+			</enable>
+			<binding>
+				<command>nasal</command>
+				<script>
+				 jetways_edit.dialog();
+				</script>
+			</binding>
+		</button>
+	</group>
+
+	<hrule />
+
+	<text>
+		<halign>left</halign>
+		<label>No airports loaded</label>
+		<property>/sim/gui/dialogs/jetways/dialog/loaded-airports</property>
+		<live type="bool">true</live>
+	</text>
+
+</PropertyList>
diff --git a/gui/dialogs/rendering.xml b/gui/dialogs/rendering.xml
index fe6197cb1..e64e98e79 100644
--- a/gui/dialogs/rendering.xml
+++ b/gui/dialogs/rendering.xml
@@ -207,6 +207,17 @@
 				</binding>
 			</checkbox>
 
+                        <checkbox>
+                                <halign>left</halign>
+                                <label>Animated jetways</label>
+                                <name>jetways</name>
+                                <property>/sim/jetways/enabled</property>
+                                <binding>
+                                        <command>dialog-apply</command>
+                                        <object-name>jetways</object-name>
+                                </binding>
+                        </checkbox>
+
 			<group>
 				<layout>hbox</layout>
 				<text>
diff --git a/gui/menubar.xml b/gui/menubar.xml
index 753304b63..15381a9f3 100644
--- a/gui/menubar.xml
+++ b/gui/menubar.xml
@@ -367,6 +367,14 @@
 			</binding>
 		</item>
 
+		<item>
+			<label>Jetway Settings</label>
+			<binding>
+                                <command>nasal</command>
+                                <script>jetways.dialog()</script>
+			</binding>
+		</item>
+
 	</menu>
 
 	<menu>
diff --git a/preferences.xml b/preferences.xml
index a4938454b..cc5f58aa9 100644
--- a/preferences.xml
+++ b/preferences.xml
@@ -746,6 +746,14 @@ Started September 2000 by David Megginson, david@megginson.com
 									 archive="n">100.0</min-time-ms>
 		</timing-statistics>
 
+		<!-- animated jetway options -->
+		<jetways>
+			<enabled type="bool" archive="y">false</enabled> <!-- disabled due to FPS impact -->
+			<enable-editor type="bool" archive="y">false</enable-editor>
+			<interact-with-multiplay type="bool" archive="y">true</interact-with-multiplay>
+			<debug type="bool" archive="y">false</debug>
+		</jetways>
+
 	</sim>