Index: src/mlx/acft.py
===================================================================
--- src/mlx/acft.py	(revision 71)
+++ src/mlx/acft.py	(revision 71)
@@ -0,0 +1,614 @@
+# Module for the simulator-independent aircraft classes
+
+#---------------------------------------------------------------------------------------
+
+import const
+import checks
+
+import time
+
+#---------------------------------------------------------------------------------------
+
+class Aircraft(object):
+    """Base class for aircraft."""
+    @staticmethod
+    def create(flight):
+        """Create an aircraft instance for the type in the given flight."""
+        return _classes[flight.aircraftType](flight)
+
+    def __init__(self, flight):
+        """Construct the aircraft for the given type."""
+        self._flight = flight
+        self._aircraftState = None
+       
+        self._maxVS = -10000.0
+        self._minVS = 10000.0
+
+        self._checkers = []
+
+        # Loggers
+
+        self._checkers.append(checks.StageChecker())
+        self._checkers.append(checks.TakeOffLogger())
+
+        self._checkers.append(checks.AltimeterLogger())
+        
+        self._checkers.append(checks.NAV1Logger())
+        self._checkers.append(checks.NAV2Logger())
+        self._checkers.append(checks.SquawkLogger())
+
+        self._checkers.append(checks.AnticollisionLightsLogger())
+        self._checkers.append(checks.LandingLightsLogger())
+        self._checkers.append(checks.StrobeLightsLogger())
+        self._checkers.append(checks.NavLightsLogger())
+
+        self._checkers.append(checks.FlapsLogger())
+
+        self._checkers.append(checks.GearsLogger())
+        self._checkers.append(checks.CruiseSpeedLogger())
+        self._checkers.append(checks.SpoilerLogger())
+
+        # Fault checkers
+        
+        self._checkers.append(checks.AntiCollisionLightsChecker())
+        self._checkers.append(checks.LandingLightsChecker())
+        self._checkers.append(checks.NavLightsChecker())
+        self._checkers.append(checks.StrobeLightsChecker())
+
+        self._checkers.append(checks.BankChecker())
+
+        self._checkers.append(checks.FlapsRetractChecker())
+        self._checkers.append(checks.FlapsSpeedLimitChecker())
+
+        self._checkers.append(checks.GearsDownChecker())
+        self._checkers.append(checks.GearSpeedLimitChecker())
+
+        self._checkers.append(checks.GLoadChecker())
+
+        self._checkers.append(checks.MLWChecker())
+        self._checkers.append(checks.MTOWChecker())
+        self._checkers.append(checks.MZFWChecker())
+        self._checkers.append(checks.PayloadChecker())
+
+        self._checkers.append(checks.SpeedChecker())
+        self._checkers.append(checks.VSChecker())
+        self._checkers.append(checks.OverspeedChecker())
+        self._checkers.append(checks.StallChecker())
+
+        self._checkers.append(checks.PitotChecker())
+        
+        self._checkers.append(checks.ThrustChecker())
+        self._checkers.append(checks.ReverserChecker())
+
+    @property
+    def type(self):
+        """Get the type of the aircraft."""
+        return self._flight.aircraftType
+
+    @property
+    def flight(self):
+        """Get the flight the aircraft belongs to."""
+        return self._flight
+
+    @property
+    def logger(self):
+        """Get the logger to use for the aircraft."""
+        return self._flight.logger
+
+    def getFlapsSpeedLimit(self, flaps):
+        """Get the speed limit for the given flaps setting."""
+        return self.flapSpeedLimits[flaps] if flaps in self.flapSpeedLimits \
+               else None
+
+    def modelChanged(self, timestamp, aircraftName, modelName):
+        """Called when the simulator's aircraft changes."""
+        self._flight.logger.message(timestamp,
+                                    "Aircraft: name='%s', model='%s'" % \
+                                    (aircraftName, modelName))
+
+    def handleState(self, aircraftState):
+        """Called when the state of the aircraft changes."""
+        for checker in self._checkers:
+            checker.check(self._flight, self, self._flight.logger,
+                          self._aircraftState, aircraftState)
+
+        self._maxVS = max(self._maxVS, aircraftState.vs)
+        self._minVS = min(self._minVS, aircraftState.vs)
+
+        self._aircraftState = aircraftState
+    
+    def setStage(self, aircraftState, newStage):
+        """Set the given stage as the new one and do whatever should be
+        done."""
+        if self._flight.setStage(aircraftState.timestamp, newStage):
+            if newStage==const.STAGE_PUSHANDTAXI:
+                self.logger.message(aircraftState.timestamp, "Block time start")
+                self.logFuel(aircraftState)
+                self.logger.message(aircraftState.timestamp, 
+                                    "Zero-fuel weight: %.0f kg" % (aircraftState.zfw))
+            elif newStage==const.STAGE_TAKEOFF:
+                self.logger.message(aircraftState.timestamp, "Flight time start")
+                self.logger.message(aircraftState.timestamp, 
+                                    "Takeoff weight: %.0f kg, MTOW: %.0f kg" % \
+                                    (aircraftState.grossWeight, self.mtow))
+                self.logger.message(aircraftState.timestamp,
+                                    "Wind %03.0f degrees at %.0f knots" % \
+                                    (aircraftState.windDirection, 
+                                     aircraftState.windSpeed))
+                self.logger.message(aircraftState.timestamp,
+                                    "Speeds calculated by the pilot: V1: %s, VR: %s, V2: %s" % \
+                                    ("-" if self._flight.v1 is None
+                                     else str(self._flight.v1),
+                                     "-" if self._flight.vr is None
+                                     else str(self._flight.vr),
+                                     "-" if self._flight.v2 is None
+                                     else str(self._flight.v2)))
+            elif newStage==const.STAGE_TAXIAFTERLAND:
+                self.logger.message(aircraftState.timestamp, "Flight time end")
+                self.logFuel(aircraftState)
+                self.logger.message(aircraftState.timestamp,
+                                    "Landing weight: %.0f kg, MLW: %.0f" % \
+                                    (aircraftState.grossWeight, self.mlw))
+                self.logger.message(aircraftState.timestamp,
+                                    "Vertical speed range: %.0f..%.0f feet/min" % \
+                                    (self._minVS, self._maxVS))
+            elif newStage==const.STAGE_PARKING:
+                self.logger.message(aircraftState.timestamp, "Block time end")
+
+    def prepareFlare(self):
+        """Called when it is detected that we will soon flare.
+
+        On the first call, it should start monitoring some parameters more
+        closely to determine flare time."""
+        self.flight.simulator.startFlare()
+
+    def flareStarted(self, windSpeed, windDirection, visibility, 
+                     flareStart, flareStartFS):
+        """Called when the flare has started."""
+        self.logger.message(self._aircraftState.timestamp, "The flare has begun")
+        self.logger.message(self._aircraftState.timestamp,
+                            "Wind %03.0f degrees at %.0f knots" % \
+                            (windDirection, windSpeed))
+        self.logger.message(self._aircraftState.timestamp,
+                            "Visibility: %.0f metres" % (visibility,))
+        self.logger.message(self._aircraftState.timestamp,
+                            "Altimeter setting: %.0f hPa" % \
+                            (self._aircraftState.altimeter,))
+        self.flight.flareStarted(flareStart, flareStartFS)
+         
+    def flareFinished(self, flareEnd, flareEndFS, tdRate, tdRateCalculatedByFS,
+                      ias, pitch, bank, heading):
+        """Called when the flare has finished."""
+        (flareTimeFromFS, flareTime) = self.flight.flareFinished(flareEnd,
+                                                                 flareEndFS)
+        self.logger.message(self._aircraftState.timestamp,
+                            "Flare time: %.1f s (from %s)" % \
+                            (flareTime, 
+                             "the simulator" if flareTimeFromFS else "real time",))
+        self.logger.message(self._aircraftState.timestamp,
+                            "Touchdown rate: %.0f feet/min" % (tdRate,))
+        self.logger.message(self._aircraftState.timestamp,
+                            "Touchdown rate was calculated by the %s" % \
+                            ("simulator" if tdRateCalculatedByFS else "logger",))
+        self.logger.message(self._aircraftState.timestamp,
+                            "Touchdown speed: %.0f knots" % (ias,))
+        self.logger.message(self._aircraftState.timestamp,
+                            "Touchdown pitch: %.1f degrees" % (pitch,))
+        self.logger.message(self._aircraftState.timestamp,
+                            "Touchdown bank: %.1f degrees" % (bank,))
+        self.logger.message(self._aircraftState.timestamp,
+                            "Touchdown heading: %03.0f degrees" % (heading,))
+
+    def cancelFlare(self):
+        """Cancel flare, if it has started."""
+        self.flight.simulator.cancelFlare()
+
+    def checkFlightEnd(self, aircraftState):
+        """Check if the end of the flight has arrived.
+
+        This default implementation checks the N1 values, but for
+        piston-powered aircraft you need to check the RPMs."""
+        for n1 in aircraftState.n1:
+            if n1>=0.5: return False
+        return True
+
+#---------------------------------------------------------------------------------------
+
+class Boeing737(Aircraft):
+    """Base class for the various aircraft in the Boeing 737 family.
+
+    The aircraft type-specific values in the aircraft state have the following
+    structure:
+    - fuel: centre, left, right
+    - n1: left, right
+    - reverser: left, right"""
+    def __init__(self, flight):
+        super(Boeing737, self).__init__(flight)
+        self.gearSpeedLimit = 270
+        self.flapSpeedLimits = { 1 : 260,
+                                 2 : 260,
+                                 5 : 250,
+                                 10 : 210,
+                                 15 : 200,
+                                 25 : 190,
+                                 30 : 175,
+                                 40 : 162 }
+
+    def logFuel(self, aircraftState):
+        """Log the amount of fuel"""
+        self.logger.message(aircraftState.timestamp,
+                            "Fuel: left=%.0f kg - centre=%.0f kg - right=%.0f kg" % \
+                            (aircraftState.fuel[1], aircraftState.fuel[0],
+                             aircraftState.fuel[2]))
+        self.logger.message(aircraftState.timestamp,
+                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
+                            
+#---------------------------------------------------------------------------------------
+
+class B736(Boeing737):
+    """Boeing 737-600 aircraft."""
+    def __init__(self, flight):
+        super(B736, self).__init__(flight)
+        self.dow = 38307
+        self.mtow = 58328
+        self.mlw = 54657
+        self.mzfw = 51482
+
+#---------------------------------------------------------------------------------------
+
+class B737(Boeing737):
+    """Boeing 737-700 aircraft."""
+    def __init__(self, flight):
+        super(B737, self).__init__(flight)
+        self.dow = 39250
+        self.mtow = 61410
+        self.mlw = 58059
+        self.mzfw = 54657
+
+#---------------------------------------------------------------------------------------
+
+class B738(Boeing737):
+    """Boeing 737-800 aircraft."""
+    def __init__(self, flight):
+        super(B738, self).__init__(flight)
+        self.dow = 42690
+        self.mtow = 71709
+        self.mlw = 65317
+        self.mzfw = 61688
+
+#---------------------------------------------------------------------------------------
+
+class B738Charter(B738):
+    """Boeing 737-800 aircraft used for charters."""
+    def __init__(self, flight):
+        super(B738Charter, self).__init__(flight)
+        self.mtow = 77791
+
+#---------------------------------------------------------------------------------------
+
+class B733(Boeing737):
+    """Boeing 737-300 aircraft."""
+    def __init__(self, flight):
+        super(B733, self).__init__(flight)
+        self.dow = 32700
+        self.mtow = 62820
+        self.mlw = 51700
+        self.mzfw = 48410
+
+#---------------------------------------------------------------------------------------
+
+class B734(Boeing737):
+    """Boeing 737-400 aircraft."""
+    def __init__(self, flight):
+        super(B734, self).__init__(flight)
+        self.dow = 33200
+        self.mtow = 68050
+        self.mlw = 56200
+        self.mzfw = 53100
+
+#---------------------------------------------------------------------------------------
+
+class B735(Boeing737):
+    """Boeing 737-500 aircraft."""
+    def __init__(self, flight):
+        super(B735, self).__init__(flight)
+        self.dow = 31300
+        self.mtow = 60550
+        self.mlw = 50000
+        self.mzfw = 46700
+
+#---------------------------------------------------------------------------------------
+
+class DH8D(Aircraft):
+    """Bombardier Dash-8 Q400 aircraft.
+
+    The aircraft type-specific values in the aircraft state have the following
+    structure:
+    - fuel: centre, left, right
+    - n1: left, right
+    - reverser: left, right."""
+    def __init__(self, flight):
+        super(DH8D, self).__init__(flight)
+        self.dow = 17185
+        self.mtow = 29257
+        self.mlw = 28009
+        self.mzfw = 25855
+        self.gearSpeedLimit = 215
+        self.flapSpeedLimits = { 5 : 200,
+                                 10 : 181,
+                                 15 : 172,
+                                 35 : 158 }
+
+    def logFuel(self, aircraftState):
+        """Log the amount of fuel"""
+        self.logger.message(aircraftState.timestamp,
+                            "Fuel: left=%.0f kg - centre=%.0f kg - right=%.0f kg" % \
+                            (aircraftState.fuel[1], aircraftState.fuel[0],
+                             aircraftState.fuel[2]))
+        self.logger.message(aircraftState.timestamp,
+                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
+
+#---------------------------------------------------------------------------------------
+
+class Boeing767(Aircraft):
+    """Base class for the various aircraft in the Boeing 767 family.
+
+    The aircraft type-specific values in the aircraft state have the following
+    structure:
+    - fuel: centre, left, right
+    - n1: left, right
+    - reverser: left, right"""
+    def __init__(self, flight):
+        super(Boeing767, self).__init__(flight)
+        self.gearSpeedLimit = 270
+        self.flapSpeedLimits = { 1 : 255,
+                                 5 : 235,
+                                 10 : 215,
+                                 20 : 215,
+                                 25 : 185,
+                                 30 : 175 }
+
+    def logFuel(self, aircraftState):
+        """Log the amount of fuel"""
+        self.logger.message(aircraftState.timestamp,
+                            "Fuel: left=%.0f kg - centre=%.0f kg - right=%.0f kg" % \
+                            (aircraftState.fuel[1], aircraftState.fuel[0],
+                             aircraftState.fuel[2]))
+        self.logger.message(aircraftState.timestamp,
+                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
+                            
+#---------------------------------------------------------------------------------------
+
+class B762(Boeing767):
+    """Boeing 767-200 aircraft."""
+    def __init__(self, flight):
+        super(B762, self).__init__(flight)
+        self.dow = 84507
+        self.mtow = 175540
+        self.mlw = 126098
+        self.mzfw = 114758
+
+#---------------------------------------------------------------------------------------
+
+class B763(Boeing767):
+    """Boeing 767-300 aircraft."""
+    def __init__(self, flight):
+        super(B763, self).__init__(cflight)
+        self.dow = 91311
+        self.mtow = 181436
+        self.mlw = 137892
+        self.mzfw = 130635
+
+#---------------------------------------------------------------------------------------
+
+class CRJ2(Aircraft):
+    """Bombardier CRJ-200 aircraft.
+
+    The aircraft type-specific values in the aircraft state have the following
+    structure:
+    - fuel: centre, left, right
+    - n1: left, right
+    - reverser: left, right."""
+    def __init__(self, flight):
+        super(CRJ2, self).__init__(flight)
+        self.dow = 14549
+        self.mtow = 22995
+        self.mlw = 21319
+        self.mzfw = 19958
+        self.gearSpeedLimit = 240
+        self.flapSpeedLimits = { 8 : 260,
+                                 20 : 220,
+                                 30 : 190,
+                                 45 : 175 }
+
+    def logFuel(self, aircraftState):
+        """Log the amount of fuel"""
+        self.logger.message(aircraftState.timestamp,
+                            "Fuel: left=%.0f kg - centre=%.0f kg - right=%.0f kg" % \
+                            (aircraftState.fuel[1], aircraftState.fuel[0],
+                             aircraftState.fuel[2]))
+        self.logger.message(aircraftState.timestamp,
+                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
+                            
+#---------------------------------------------------------------------------------------
+
+class F70(Aircraft):
+    """Fokker 70 aircraft.
+
+    The aircraft type-specific values in the aircraft state have the following
+    structure:
+    - fuel: centre, left, right
+    - n1: left, right
+    - reverser: left, right."""
+    def __init__(self, flight):
+        super(F70, self).__init__(flight)
+        self.dow = 24283
+        self.mtow = 38100 # FIXME: differentiate by registration number,
+                          # MTOW of HA-LMF: 41955
+        self.mlw = 36740
+        self.mzfw = 32655
+        self.gearSpeedLimit = 200
+        self.flapSpeedLimits = { 8 : 250,
+                                 15 : 220,
+                                 25 : 220,
+                                 42 : 180 }
+
+    def logFuel(self, aircraftState):
+        """Log the amount of fuel"""
+        self.logger.message(aircraftState.timestamp,
+                            "Fuel: left=%.0f kg - centre=%.0f kg - right=%.0f kg" % \
+                            (aircraftState.fuel[1], aircraftState.fuel[0],
+                             aircraftState.fuel[2]))
+        self.logger.message(aircraftState.timestamp,
+                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
+                            
+#---------------------------------------------------------------------------------------
+
+class DC3(Aircraft):
+    """Lisunov Li-2 (DC-3) aircraft.
+
+    The aircraft type-specific values in the aircraft state have the following
+    structure:
+    - fuel: left, right, left aux, right aux
+    - rpm: left, right
+    - reverser: left, right."""
+    def __init__(self, flight):
+        super(DC3, self).__init__(flight)
+        self.dow = 8627
+        self.mtow = 11884
+        self.mlw = 11793
+        self.mzfw = 11780
+        self.gearSpeedLimit = 148
+        self.flapSpeedLimits = { 15 : 135,
+                                 30 : 99,
+                                 45 : 97 }
+
+    def _checkFlightEnd(self, aircraftState):
+        """Check if the end of the flight has arrived.
+
+        This implementation checks the RPM values to be 0."""
+        for rpm in aircraftState.rpm:
+            if rpm>0: return False
+        return True
+
+    def logFuel(self, aircraftState):
+        """Log the amount of fuel"""
+        self.logger.message(aircraftState.timestamp,
+                            "Fuel: left aux=%.0f kg - left=%.0f kg - right=%.0f kg - right aux=%.0f kg" % \
+                            (aircraftState.fuel[2], aircraftState.fuel[0],
+                             aircraftState.fuel[1], aircraftState.fuel[3]))
+        self.logger.message(aircraftState.timestamp,
+                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
+                            
+#---------------------------------------------------------------------------------------
+
+class T134(Aircraft):
+    """Tupolev Tu-134 aircraft.
+
+    The aircraft type-specific values in the aircraft state have the following
+    structure:
+    - fuel: centre, left tip, left aux, right tip, right aux, external 1,
+    external 2
+    - n1: left, right
+    - reverser: left, right."""
+    def __init__(self, flight):
+        super(T134, self).__init__(flight)
+        self.dow = 29927
+        self.mtow = 47600
+        self.mlw = 43000
+        self.mzfw = 38500
+        self.gearSpeedLimit = 216
+        self.flapSpeedLimits = { 10 : 240,
+                                 20 : 216,
+                                 30 : 161 }
+
+    def logFuel(self, aircraftState):
+        """Log the amount of fuel"""
+        self.logger.message(aircraftState.timestamp,
+                            "Fuel: left aux=%.0f kg - left tip=%.0f kg - centre= %.0f kg - right tip=%.0f kg - right aux=%.0f kg - external 1=%.0f kg - external 2=%.0f kg" % \
+                            (aircraftState.fuel[2], aircraftState.fuel[1],
+                             aircraftState.fuel[0], 
+                             aircraftState.fuel[3], aircraftState.fuel[4],
+                             aircraftState.fuel[5], aircraftState.fuel[6]))
+        self.logger.message(aircraftState.timestamp,
+                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
+                            
+#---------------------------------------------------------------------------------------
+
+class T154(Aircraft):
+    """Tupolev Tu-154 aircraft.
+
+    The aircraft type-specific values in the aircraft state have the following
+    structure:
+    - fuel: centre, left, right, centre 2, left aux, right aux
+    - n1: left, centre, right
+    - reverser: left, right"""
+    def __init__(self, flight):
+        super(T154, self).__init__(flight)
+        self.dow = 53259
+        self.mtow = 98000
+        self.mlw = 78000
+        self.mzfw = 72000
+        self.gearSpeedLimit = 216
+        self.flapSpeedLimits = { 15 : 227,
+                                 28 : 194,
+                                 45 : 162 }
+
+    def logFuel(self, aircraftState):
+        """Log the amount of fuel"""
+        self.logger.message(aircraftState.timestamp,
+                            "Fuel: left aux=%.0f kg - left=%.0f kg - centre=%.0f kg - centre 2=%.0f kg - right=%.0f kg - right aux=%.0f kg" % \
+                            (aircraftState.fuel[4], aircraftState.fuel[1],
+                             aircraftState.fuel[0], aircraftState.fuel[3], 
+                             aircraftState.fuel[2], aircraftState.fuel[5]))
+        self.logger.message(aircraftState.timestamp,
+                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
+
+#---------------------------------------------------------------------------------------
+
+
+class YK40(Aircraft):
+    """Yakovlev Yak-40 aircraft.
+
+    The aircraft type-specific values in the aircraft state have the following
+    structure:
+    - fuel: left, right
+    - n1: left, right
+    - reverser: left, right"""
+    def __init__(self, flight):
+        super(YK40, self).__init__(flight)
+        self.dow = 9400
+        self.mtow = 17200
+        self.mlw = 16800
+        self.mzfw = 12100
+        self.gearSpeedLimit = 165
+        self.flapSpeedLimits = { 20 : 165,
+                                 35 : 135 }
+
+    def logFuel(self, aircraftState):
+        """Log the amount of fuel"""
+        self.logger.message(aircraftState.timestamp,
+                            "Fuel: left=%.0f kg - right=%.0f kg" % \
+                            (aircraftState.fuel[0], aircraftState.fuel[1]))
+        self.logger.message(aircraftState.timestamp,
+                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
+
+#---------------------------------------------------------------------------------------
+
+_classes = { const.AIRCRAFT_B736 : B736,
+             const.AIRCRAFT_B737 : B737,
+             const.AIRCRAFT_B738 : B738,
+             const.AIRCRAFT_B733 : B733,
+             const.AIRCRAFT_B734 : B734,
+             const.AIRCRAFT_B735 : B735,
+             const.AIRCRAFT_DH8D : DH8D,
+             const.AIRCRAFT_B762 : B762,
+             const.AIRCRAFT_B763 : B763,
+             const.AIRCRAFT_CRJ2 : CRJ2,
+             const.AIRCRAFT_F70 : F70,
+             const.AIRCRAFT_DC3 : DC3,
+             const.AIRCRAFT_T134 : T134,
+             const.AIRCRAFT_T154 : T154,
+             const.AIRCRAFT_YK40 : YK40 }
+
+#---------------------------------------------------------------------------------------
Index: src/mlx/acft.py
===================================================================
--- src/mlx/acft.py	(revision 71)
+++ 	(revision )
@@ -1,614 +1,0 @@
-# Module for the simulator-independent aircraft classes
-
-#---------------------------------------------------------------------------------------
-
-import const
-import checks
-
-import time
-
-#---------------------------------------------------------------------------------------
-
-class Aircraft(object):
-    """Base class for aircraft."""
-    @staticmethod
-    def create(flight):
-        """Create an aircraft instance for the type in the given flight."""
-        return _classes[flight.aircraftType](flight)
-
-    def __init__(self, flight):
-        """Construct the aircraft for the given type."""
-        self._flight = flight
-        self._aircraftState = None
-       
-        self._maxVS = -10000.0
-        self._minVS = 10000.0
-
-        self._checkers = []
-
-        # Loggers
-
-        self._checkers.append(checks.StageChecker())
-        self._checkers.append(checks.TakeOffLogger())
-
-        self._checkers.append(checks.AltimeterLogger())
-        
-        self._checkers.append(checks.NAV1Logger())
-        self._checkers.append(checks.NAV2Logger())
-        self._checkers.append(checks.SquawkLogger())
-
-        self._checkers.append(checks.AnticollisionLightsLogger())
-        self._checkers.append(checks.LandingLightsLogger())
-        self._checkers.append(checks.StrobeLightsLogger())
-        self._checkers.append(checks.NavLightsLogger())
-
-        self._checkers.append(checks.FlapsLogger())
-
-        self._checkers.append(checks.GearsLogger())
-        self._checkers.append(checks.CruiseSpeedLogger())
-        self._checkers.append(checks.SpoilerLogger())
-
-        # Fault checkers
-        
-        self._checkers.append(checks.AntiCollisionLightsChecker())
-        self._checkers.append(checks.LandingLightsChecker())
-        self._checkers.append(checks.NavLightsChecker())
-        self._checkers.append(checks.StrobeLightsChecker())
-
-        self._checkers.append(checks.BankChecker())
-
-        self._checkers.append(checks.FlapsRetractChecker())
-        self._checkers.append(checks.FlapsSpeedLimitChecker())
-
-        self._checkers.append(checks.GearsDownChecker())
-        self._checkers.append(checks.GearSpeedLimitChecker())
-
-        self._checkers.append(checks.GLoadChecker())
-
-        self._checkers.append(checks.MLWChecker())
-        self._checkers.append(checks.MTOWChecker())
-        self._checkers.append(checks.MZFWChecker())
-        self._checkers.append(checks.PayloadChecker())
-
-        self._checkers.append(checks.SpeedChecker())
-        self._checkers.append(checks.VSChecker())
-        self._checkers.append(checks.OverspeedChecker())
-        self._checkers.append(checks.StallChecker())
-
-        self._checkers.append(checks.PitotChecker())
-        
-        self._checkers.append(checks.ThrustChecker())
-        self._checkers.append(checks.ReverserChecker())
-
-    @property
-    def type(self):
-        """Get the type of the aircraft."""
-        return self._flight.aircraftType
-
-    @property
-    def flight(self):
-        """Get the flight the aircraft belongs to."""
-        return self._flight
-
-    @property
-    def logger(self):
-        """Get the logger to use for the aircraft."""
-        return self._flight.logger
-
-    def getFlapsSpeedLimit(self, flaps):
-        """Get the speed limit for the given flaps setting."""
-        return self.flapSpeedLimits[flaps] if flaps in self.flapSpeedLimits \
-               else None
-
-    def modelChanged(self, timestamp, aircraftName, modelName):
-        """Called when the simulator's aircraft changes."""
-        self._flight.logger.message(timestamp,
-                                    "Aircraft: name='%s', model='%s'" % \
-                                    (aircraftName, modelName))
-
-    def handleState(self, aircraftState):
-        """Called when the state of the aircraft changes."""
-        for checker in self._checkers:
-            checker.check(self._flight, self, self._flight.logger,
-                          self._aircraftState, aircraftState)
-
-        self._maxVS = max(self._maxVS, aircraftState.vs)
-        self._minVS = min(self._minVS, aircraftState.vs)
-
-        self._aircraftState = aircraftState
-    
-    def setStage(self, aircraftState, newStage):
-        """Set the given stage as the new one and do whatever should be
-        done."""
-        if self._flight.setStage(aircraftState.timestamp, newStage):
-            if newStage==const.STAGE_PUSHANDTAXI:
-                self.logger.message(aircraftState.timestamp, "Block time start")
-                self.logFuel(aircraftState)
-                self.logger.message(aircraftState.timestamp, 
-                                    "Zero-fuel weight: %.0f kg" % (aircraftState.zfw))
-            elif newStage==const.STAGE_TAKEOFF:
-                self.logger.message(aircraftState.timestamp, "Flight time start")
-                self.logger.message(aircraftState.timestamp, 
-                                    "Takeoff weight: %.0f kg, MTOW: %.0f kg" % \
-                                    (aircraftState.grossWeight, self.mtow))
-                self.logger.message(aircraftState.timestamp,
-                                    "Wind %03.0f degrees at %.0f knots" % \
-                                    (aircraftState.windDirection, 
-                                     aircraftState.windSpeed))
-                self.logger.message(aircraftState.timestamp,
-                                    "Speeds calculated by the pilot: V1: %s, VR: %s, V2: %s" % \
-                                    ("-" if self._flight.v1 is None
-                                     else str(self._flight.v1),
-                                     "-" if self._flight.vr is None
-                                     else str(self._flight.vr),
-                                     "-" if self._flight.v2 is None
-                                     else str(self._flight.v2)))
-            elif newStage==const.STAGE_TAXIAFTERLAND:
-                self.logger.message(aircraftState.timestamp, "Flight time end")
-                self.logFuel(aircraftState)
-                self.logger.message(aircraftState.timestamp,
-                                    "Landing weight: %.0f kg, MLW: %.0f" % \
-                                    (aircraftState.grossWeight, self.mlw))
-                self.logger.message(aircraftState.timestamp,
-                                    "Vertical speed range: %.0f..%.0f feet/min" % \
-                                    (self._minVS, self._maxVS))
-            elif newStage==const.STAGE_PARKING:
-                self.logger.message(aircraftState.timestamp, "Block time end")
-
-    def prepareFlare(self):
-        """Called when it is detected that we will soon flare.
-
-        On the first call, it should start monitoring some parameters more
-        closely to determine flare time."""
-        self.flight.simulator.startFlare()
-
-    def flareStarted(self, windSpeed, windDirection, visibility, 
-                     flareStart, flareStartFS):
-        """Called when the flare has started."""
-        self.logger.message(self._aircraftState.timestamp, "The flare has begun")
-        self.logger.message(self._aircraftState.timestamp,
-                            "Wind %03.0f degrees at %.0f knots" % \
-                            (windDirection, windSpeed))
-        self.logger.message(self._aircraftState.timestamp,
-                            "Visibility: %.0f metres" % (visibility,))
-        self.logger.message(self._aircraftState.timestamp,
-                            "Altimeter setting: %.0f hPa" % \
-                            (self._aircraftState.altimeter,))
-        self.flight.flareStarted(flareStart, flareStartFS)
-         
-    def flareFinished(self, flareEnd, flareEndFS, tdRate, tdRateCalculatedByFS,
-                      ias, pitch, bank, heading):
-        """Called when the flare has finished."""
-        (flareTimeFromFS, flareTime) = self.flight.flareFinished(flareEnd,
-                                                                 flareEndFS)
-        self.logger.message(self._aircraftState.timestamp,
-                            "Flare time: %.1f s (from %s)" % \
-                            (flareTime, 
-                             "the simulator" if flareTimeFromFS else "real time",))
-        self.logger.message(self._aircraftState.timestamp,
-                            "Touchdown rate: %.0f feet/min" % (tdRate,))
-        self.logger.message(self._aircraftState.timestamp,
-                            "Touchdown rate was calculated by the %s" % \
-                            ("simulator" if tdRateCalculatedByFS else "logger",))
-        self.logger.message(self._aircraftState.timestamp,
-                            "Touchdown speed: %.0f knots" % (ias,))
-        self.logger.message(self._aircraftState.timestamp,
-                            "Touchdown pitch: %.1f degrees" % (pitch,))
-        self.logger.message(self._aircraftState.timestamp,
-                            "Touchdown bank: %.1f degrees" % (bank,))
-        self.logger.message(self._aircraftState.timestamp,
-                            "Touchdown heading: %03.0f degrees" % (heading,))
-
-    def cancelFlare(self):
-        """Cancel flare, if it has started."""
-        self.flight.simulator.cancelFlare()
-
-    def checkFlightEnd(self, aircraftState):
-        """Check if the end of the flight has arrived.
-
-        This default implementation checks the N1 values, but for
-        piston-powered aircraft you need to check the RPMs."""
-        for n1 in aircraftState.n1:
-            if n1>=0.5: return False
-        return True
-
-#---------------------------------------------------------------------------------------
-
-class Boeing737(Aircraft):
-    """Base class for the various aircraft in the Boeing 737 family.
-
-    The aircraft type-specific values in the aircraft state have the following
-    structure:
-    - fuel: centre, left, right
-    - n1: left, right
-    - reverser: left, right"""
-    def __init__(self, flight):
-        super(Boeing737, self).__init__(flight)
-        self.gearSpeedLimit = 270
-        self.flapSpeedLimits = { 1 : 260,
-                                 2 : 260,
-                                 5 : 250,
-                                 10 : 210,
-                                 15 : 200,
-                                 25 : 190,
-                                 30 : 175,
-                                 40 : 162 }
-
-    def logFuel(self, aircraftState):
-        """Log the amount of fuel"""
-        self.logger.message(aircraftState.timestamp,
-                            "Fuel: left=%.0f kg - centre=%.0f kg - right=%.0f kg" % \
-                            (aircraftState.fuel[1], aircraftState.fuel[0],
-                             aircraftState.fuel[2]))
-        self.logger.message(aircraftState.timestamp,
-                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
-                            
-#---------------------------------------------------------------------------------------
-
-class B736(Boeing737):
-    """Boeing 737-600 aircraft."""
-    def __init__(self, flight):
-        super(B736, self).__init__(flight)
-        self.dow = 38307
-        self.mtow = 58328
-        self.mlw = 54657
-        self.mzfw = 51482
-
-#---------------------------------------------------------------------------------------
-
-class B737(Boeing737):
-    """Boeing 737-700 aircraft."""
-    def __init__(self, flight):
-        super(B737, self).__init__(flight)
-        self.dow = 39250
-        self.mtow = 61410
-        self.mlw = 58059
-        self.mzfw = 54657
-
-#---------------------------------------------------------------------------------------
-
-class B738(Boeing737):
-    """Boeing 737-800 aircraft."""
-    def __init__(self, flight):
-        super(B738, self).__init__(flight)
-        self.dow = 42690
-        self.mtow = 71709
-        self.mlw = 65317
-        self.mzfw = 61688
-
-#---------------------------------------------------------------------------------------
-
-class B738Charter(B738):
-    """Boeing 737-800 aircraft used for charters."""
-    def __init__(self, flight):
-        super(B738Charter, self).__init__(flight)
-        self.mtow = 77791
-
-#---------------------------------------------------------------------------------------
-
-class B733(Boeing737):
-    """Boeing 737-300 aircraft."""
-    def __init__(self, flight):
-        super(B733, self).__init__(flight)
-        self.dow = 32700
-        self.mtow = 62820
-        self.mlw = 51700
-        self.mzfw = 48410
-
-#---------------------------------------------------------------------------------------
-
-class B734(Boeing737):
-    """Boeing 737-400 aircraft."""
-    def __init__(self, flight):
-        super(B734, self).__init__(flight)
-        self.dow = 33200
-        self.mtow = 68050
-        self.mlw = 56200
-        self.mzfw = 53100
-
-#---------------------------------------------------------------------------------------
-
-class B735(Boeing737):
-    """Boeing 737-500 aircraft."""
-    def __init__(self, flight):
-        super(B735, self).__init__(flight)
-        self.dow = 31300
-        self.mtow = 60550
-        self.mlw = 50000
-        self.mzfw = 46700
-
-#---------------------------------------------------------------------------------------
-
-class DH8D(Aircraft):
-    """Bombardier Dash-8 Q400 aircraft.
-
-    The aircraft type-specific values in the aircraft state have the following
-    structure:
-    - fuel: centre, left, right
-    - n1: left, right
-    - reverser: left, right."""
-    def __init__(self, flight):
-        super(DH8D, self).__init__(flight)
-        self.dow = 17185
-        self.mtow = 29257
-        self.mlw = 28009
-        self.mzfw = 25855
-        self.gearSpeedLimit = 215
-        self.flapSpeedLimits = { 5 : 200,
-                                 10 : 181,
-                                 15 : 172,
-                                 35 : 158 }
-
-    def logFuel(self, aircraftState):
-        """Log the amount of fuel"""
-        self.logger.message(aircraftState.timestamp,
-                            "Fuel: left=%.0f kg - centre=%.0f kg - right=%.0f kg" % \
-                            (aircraftState.fuel[1], aircraftState.fuel[0],
-                             aircraftState.fuel[2]))
-        self.logger.message(aircraftState.timestamp,
-                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
-
-#---------------------------------------------------------------------------------------
-
-class Boeing767(Aircraft):
-    """Base class for the various aircraft in the Boeing 767 family.
-
-    The aircraft type-specific values in the aircraft state have the following
-    structure:
-    - fuel: centre, left, right
-    - n1: left, right
-    - reverser: left, right"""
-    def __init__(self, flight):
-        super(Boeing767, self).__init__(flight)
-        self.gearSpeedLimit = 270
-        self.flapSpeedLimits = { 1 : 255,
-                                 5 : 235,
-                                 10 : 215,
-                                 20 : 215,
-                                 25 : 185,
-                                 30 : 175 }
-
-    def logFuel(self, aircraftState):
-        """Log the amount of fuel"""
-        self.logger.message(aircraftState.timestamp,
-                            "Fuel: left=%.0f kg - centre=%.0f kg - right=%.0f kg" % \
-                            (aircraftState.fuel[1], aircraftState.fuel[0],
-                             aircraftState.fuel[2]))
-        self.logger.message(aircraftState.timestamp,
-                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
-                            
-#---------------------------------------------------------------------------------------
-
-class B762(Boeing767):
-    """Boeing 767-200 aircraft."""
-    def __init__(self, flight):
-        super(B762, self).__init__(flight)
-        self.dow = 84507
-        self.mtow = 175540
-        self.mlw = 126098
-        self.mzfw = 114758
-
-#---------------------------------------------------------------------------------------
-
-class B763(Boeing767):
-    """Boeing 767-300 aircraft."""
-    def __init__(self, flight):
-        super(B763, self).__init__(cflight)
-        self.dow = 91311
-        self.mtow = 181436
-        self.mlw = 137892
-        self.mzfw = 130635
-
-#---------------------------------------------------------------------------------------
-
-class CRJ2(Aircraft):
-    """Bombardier CRJ-200 aircraft.
-
-    The aircraft type-specific values in the aircraft state have the following
-    structure:
-    - fuel: centre, left, right
-    - n1: left, right
-    - reverser: left, right."""
-    def __init__(self, flight):
-        super(CRJ2, self).__init__(flight)
-        self.dow = 14549
-        self.mtow = 22995
-        self.mlw = 21319
-        self.mzfw = 19958
-        self.gearSpeedLimit = 240
-        self.flapSpeedLimits = { 8 : 260,
-                                 20 : 220,
-                                 30 : 190,
-                                 45 : 175 }
-
-    def logFuel(self, aircraftState):
-        """Log the amount of fuel"""
-        self.logger.message(aircraftState.timestamp,
-                            "Fuel: left=%.0f kg - centre=%.0f kg - right=%.0f kg" % \
-                            (aircraftState.fuel[1], aircraftState.fuel[0],
-                             aircraftState.fuel[2]))
-        self.logger.message(aircraftState.timestamp,
-                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
-                            
-#---------------------------------------------------------------------------------------
-
-class F70(Aircraft):
-    """Fokker 70 aircraft.
-
-    The aircraft type-specific values in the aircraft state have the following
-    structure:
-    - fuel: centre, left, right
-    - n1: left, right
-    - reverser: left, right."""
-    def __init__(self, flight):
-        super(F70, self).__init__(flight)
-        self.dow = 24283
-        self.mtow = 38100 # FIXME: differentiate by registration number,
-                          # MTOW of HA-LMF: 41955
-        self.mlw = 36740
-        self.mzfw = 32655
-        self.gearSpeedLimit = 200
-        self.flapSpeedLimits = { 8 : 250,
-                                 15 : 220,
-                                 25 : 220,
-                                 42 : 180 }
-
-    def logFuel(self, aircraftState):
-        """Log the amount of fuel"""
-        self.logger.message(aircraftState.timestamp,
-                            "Fuel: left=%.0f kg - centre=%.0f kg - right=%.0f kg" % \
-                            (aircraftState.fuel[1], aircraftState.fuel[0],
-                             aircraftState.fuel[2]))
-        self.logger.message(aircraftState.timestamp,
-                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
-                            
-#---------------------------------------------------------------------------------------
-
-class DC3(Aircraft):
-    """Lisunov Li-2 (DC-3) aircraft.
-
-    The aircraft type-specific values in the aircraft state have the following
-    structure:
-    - fuel: left, right, left aux, right aux
-    - rpm: left, right
-    - reverser: left, right."""
-    def __init__(self, flight):
-        super(DC3, self).__init__(flight)
-        self.dow = 8627
-        self.mtow = 11884
-        self.mlw = 11793
-        self.mzfw = 11780
-        self.gearSpeedLimit = 148
-        self.flapSpeedLimits = { 15 : 135,
-                                 30 : 99,
-                                 45 : 97 }
-
-    def _checkFlightEnd(self, aircraftState):
-        """Check if the end of the flight has arrived.
-
-        This implementation checks the RPM values to be 0."""
-        for rpm in aircraftState.rpm:
-            if rpm>0: return False
-        return True
-
-    def logFuel(self, aircraftState):
-        """Log the amount of fuel"""
-        self.logger.message(aircraftState.timestamp,
-                            "Fuel: left aux=%.0f kg - left=%.0f kg - right=%.0f kg - right aux=%.0f kg" % \
-                            (aircraftState.fuel[2], aircraftState.fuel[0],
-                             aircraftState.fuel[1], aircraftState.fuel[3]))
-        self.logger.message(aircraftState.timestamp,
-                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
-                            
-#---------------------------------------------------------------------------------------
-
-class T134(Aircraft):
-    """Tupolev Tu-134 aircraft.
-
-    The aircraft type-specific values in the aircraft state have the following
-    structure:
-    - fuel: centre, left tip, left aux, right tip, right aux, external 1,
-    external 2
-    - n1: left, right
-    - reverser: left, right."""
-    def __init__(self, flight):
-        super(T134, self).__init__(flight)
-        self.dow = 29927
-        self.mtow = 47600
-        self.mlw = 43000
-        self.mzfw = 38500
-        self.gearSpeedLimit = 216
-        self.flapSpeedLimits = { 10 : 240,
-                                 20 : 216,
-                                 30 : 161 }
-
-    def logFuel(self, aircraftState):
-        """Log the amount of fuel"""
-        self.logger.message(aircraftState.timestamp,
-                            "Fuel: left aux=%.0f kg - left tip=%.0f kg - centre= %.0f kg - right tip=%.0f kg - right aux=%.0f kg - external 1=%.0f kg - external 2=%.0f kg" % \
-                            (aircraftState.fuel[2], aircraftState.fuel[1],
-                             aircraftState.fuel[0], 
-                             aircraftState.fuel[3], aircraftState.fuel[4],
-                             aircraftState.fuel[5], aircraftState.fuel[6]))
-        self.logger.message(aircraftState.timestamp,
-                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
-                            
-#---------------------------------------------------------------------------------------
-
-class T154(Aircraft):
-    """Tupolev Tu-154 aircraft.
-
-    The aircraft type-specific values in the aircraft state have the following
-    structure:
-    - fuel: centre, left, right, centre 2, left aux, right aux
-    - n1: left, centre, right
-    - reverser: left, right"""
-    def __init__(self, flight):
-        super(T154, self).__init__(flight)
-        self.dow = 53259
-        self.mtow = 98000
-        self.mlw = 78000
-        self.mzfw = 72000
-        self.gearSpeedLimit = 216
-        self.flapSpeedLimits = { 15 : 227,
-                                 28 : 194,
-                                 45 : 162 }
-
-    def logFuel(self, aircraftState):
-        """Log the amount of fuel"""
-        self.logger.message(aircraftState.timestamp,
-                            "Fuel: left aux=%.0f kg - left=%.0f kg - centre=%.0f kg - centre 2=%.0f kg - right=%.0f kg - right aux=%.0f kg" % \
-                            (aircraftState.fuel[4], aircraftState.fuel[1],
-                             aircraftState.fuel[0], aircraftState.fuel[3], 
-                             aircraftState.fuel[2], aircraftState.fuel[5]))
-        self.logger.message(aircraftState.timestamp,
-                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
-
-#---------------------------------------------------------------------------------------
-
-
-class YK40(Aircraft):
-    """Yakovlev Yak-40 aircraft.
-
-    The aircraft type-specific values in the aircraft state have the following
-    structure:
-    - fuel: left, right
-    - n1: left, right
-    - reverser: left, right"""
-    def __init__(self, flight):
-        super(YK40, self).__init__(flight)
-        self.dow = 9400
-        self.mtow = 17200
-        self.mlw = 16800
-        self.mzfw = 12100
-        self.gearSpeedLimit = 165
-        self.flapSpeedLimits = { 20 : 165,
-                                 35 : 135 }
-
-    def logFuel(self, aircraftState):
-        """Log the amount of fuel"""
-        self.logger.message(aircraftState.timestamp,
-                            "Fuel: left=%.0f kg - right=%.0f kg" % \
-                            (aircraftState.fuel[0], aircraftState.fuel[1]))
-        self.logger.message(aircraftState.timestamp,
-                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
-
-#---------------------------------------------------------------------------------------
-
-_classes = { const.AIRCRAFT_B736 : B736,
-             const.AIRCRAFT_B737 : B737,
-             const.AIRCRAFT_B738 : B738,
-             const.AIRCRAFT_B733 : B733,
-             const.AIRCRAFT_B734 : B734,
-             const.AIRCRAFT_B735 : B735,
-             const.AIRCRAFT_DH8D : DH8D,
-             const.AIRCRAFT_B762 : B762,
-             const.AIRCRAFT_B763 : B763,
-             const.AIRCRAFT_CRJ2 : CRJ2,
-             const.AIRCRAFT_F70 : F70,
-             const.AIRCRAFT_DC3 : DC3,
-             const.AIRCRAFT_T134 : T134,
-             const.AIRCRAFT_T154 : T154,
-             const.AIRCRAFT_YK40 : YK40 }
-
-#---------------------------------------------------------------------------------------
Index: src/mlx/checks.py
===================================================================
--- src/mlx/checks.py	(revision 60)
+++ src/mlx/checks.py	(revision 60)
@@ -0,0 +1,929 @@
+# The various checks that may be performed during flight
+
+#---------------------------------------------------------------------------------------
+
+import const
+
+#---------------------------------------------------------------------------------------
+
+class StateChecker(object):
+    """Base class for classes the instances of which check the aircraft's state
+    from some aspect.
+
+    As a result of the check they may log something, or notify of some fault, etc."""
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Perform the check and do whatever is needed.
+
+        This default implementation raises a NotImplementedError."""
+        raise NotImplementedError()
+    
+#---------------------------------------------------------------------------------------
+
+class StageChecker(StateChecker):
+    """Check the flight stage transitions."""
+    def __init__(self):
+        """Construct the stage checker."""
+        self._flareStarted = False
+
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Check the stage of the aircraft."""
+        stage = flight.stage
+        if stage==None:
+            aircraft.setStage(state, const.STAGE_BOARDING)
+        elif stage==const.STAGE_BOARDING:
+            if not state.parking or \
+               (not state.trickMode and state.groundSpeed>5.0):
+                aircraft.setStage(state, const.STAGE_PUSHANDTAXI)
+        elif stage==const.STAGE_PUSHANDTAXI or stage==const.STAGE_RTO:
+            if state.landingLightsOn or state.strobeLightsOn or \
+               state.groundSpeed>80.0:
+                aircraft.setStage(state, const.STAGE_TAKEOFF)
+        elif stage==const.STAGE_TAKEOFF:
+            if not state.gearsDown or \
+               (state.radioAltitude>3000.0 and state.vs>0):
+                aircraft.setStage(state, const.STAGE_CLIMB)
+            elif not state.landingLightsOn and \
+                 not state.strobeLightsOn and \
+                 state.onTheGround and \
+                 state.groundSpeed<50.0:
+                aircraft.setStage(state, const.STAGE_RTO)
+        elif stage==const.STAGE_CLIMB:
+            if (state.altitude+2000) > flight.cruiseAltitude:
+                aircraft.setStage(state, const.STAGE_CRUISE)
+            elif state.radioAltitude<2000.0 and \
+                 state.vs < 0.0 and state.gearsDown:
+                aircraft.setStage(state, const.STAGE_LANDING)
+        elif stage==const.STAGE_CRUISE:
+            if (state.altitude+2000) < flight.cruiseAltitude:
+                aircraft.setStage(state, const.STAGE_DESCENT)
+        elif stage==const.STAGE_DESCENT or stage==const.STAGE_GOAROUND:
+            if state.gearsDown and state.radioAltitude<2000.0:
+                aircraft.setStage(state, const.STAGE_LANDING)
+            elif (state.altitude+2000) > flight.cruiseAltitude:
+                aircraft.setStage(state, const.STAGE_CRUISE)
+        elif stage==const.STAGE_LANDING:
+            if state.onTheGround and state.groundSpeed<50.0:
+                aircraft.setStage(state, const.STAGE_TAXIAFTERLAND)
+            elif not state.gearsDown:
+                aircraft.setStage(state, const.STAGE_GOAROUND)
+            elif state.radioAltitude>200 and self._flareStarted:
+                aircraft.cancelFlare()
+                self._flareStarted = False
+            elif state.radioAltitude<150 and not state.onTheGround and \
+                 not self._flareStarted:
+                self._flareStarted = True
+                aircraft.prepareFlare()
+        elif stage==const.STAGE_TAXIAFTERLAND:
+            if state.parking:
+                aircraft.setStage(state, const.STAGE_PARKING)
+        elif stage==const.STAGE_PARKING:
+            if aircraft.checkFlightEnd(state):
+                aircraft.setStage(state, const.STAGE_END)
+
+#---------------------------------------------------------------------------------------
+
+class TakeOffLogger(StateChecker):
+    """Logger for the cruise speed."""
+    def __init__(self):
+        """Construct the logger."""
+        self._onTheGround = True
+    
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Log the cruise speed if necessary."""
+        if flight.stage==const.STAGE_TAKEOFF and \
+           self._onTheGround and not state.onTheGround:
+            logger.message(state.timestamp,
+                           "Takeoff speed: %.0f knots" % (state.ias,))
+            logger.message(state.timestamp,
+                           "Takeoff heading: %03.0f degrees" % (state.heading,))
+            logger.message(state.timestamp,
+                           "Takeoff pitch: %.1f degrees" % (state.pitch,))
+            self._onTheGround = False
+
+#---------------------------------------------------------------------------------------
+
+class CruiseSpeedLogger(StateChecker):
+    """Logger for the cruise speed."""
+    def __init__(self):
+        """Construct the logger."""
+        self._lastTime = None
+    
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Log the cruise speed if necessary."""
+        if flight.stage==const.STAGE_CRUISE and \
+           (self._lastTime is None or \
+            (self._lastTime+800)<=state.timestamp):
+                if state.altitude>24500.0:
+                    logger.message(state.timestamp,
+                                   "Cruise speed: %.3f mach" % (state.mach,))
+                else:
+                    logger.message(state.timestamp,
+                                   "Cruise speed: %.0f knots" % (state.ias,))
+                self._lastTime = state.timestamp
+
+#---------------------------------------------------------------------------------------
+
+class SpoilerLogger(StateChecker):
+    """Logger for the cruise speed."""
+    def __init__(self):
+        """Construct the logger."""
+        self._logged = False
+        self._spoilersExtension = None
+    
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Log the cruise speed if necessary."""
+        if flight.stage==const.STAGE_LANDING and not self._logged:
+            if state.onTheGround:
+                if state.spoilersExtension!=self._spoilersExtension:
+                    logger.message(state.timestamp, "Spoilers deployed")
+                    self._logged = True
+            else:
+                self._spoilersExtension = state.spoilersExtension
+
+#---------------------------------------------------------------------------------------
+
+class StateChangeLogger(StateChecker):
+    """Base class for classes the instances of which check if a specific change has
+    occured in the aircraft's state, and log such change."""
+    def __init__(self, logInitial = True):
+        """Construct the logger.
+
+        If logInitial is True, the initial value will be logged, not just the
+        changes later.
+
+        Child classes should define the following functions:
+        - _changed(self, oldState, state): returns a boolean indicating if the
+        value has changed or not
+        - _getMessage(self, state): return a strings containing the message to log
+        with the new value
+        """
+        self._logInitial = logInitial    
+
+    def _getLogTimestamp(self, state):
+        """Get the log timestamp."""
+        return state.timestamp
+
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Check if the state has changed, and if so, log the new state."""
+        shouldLog = False
+        if oldState is None:
+            shouldLog = self._logInitial
+        else:
+            shouldLog = self._changed(oldState, state)
+        
+        if shouldLog:
+            logger.message(self._getLogTimestamp(state), self._getMessage(state))
+        
+#---------------------------------------------------------------------------------------
+
+class SimpleChangeMixin(object):
+    """A mixin that defines a _changed() function which simply calls a function
+    to retrieve the value two monitor for both states and compares them.
+
+    Child classes should define the following function:
+    - _getValue(state): get the value we are interested in."""
+    def _changed(self, oldState, state):
+        """Determine if the value has changed."""
+        return self._getValue(oldState)!=self._getValue(state)
+
+#---------------------------------------------------------------------------------------
+
+class SingleValueMixin(object):
+    """A mixin that provides a _getValue() function to query a value from the
+    state using the name of the attribute."""
+    def __init__(self, attrName):
+        """Construct the mixin with the given attribute name."""
+        self._attrName = attrName
+
+    def _getValue(self, state):
+        """Get the value of the attribute from the state."""
+        return getattr(state, self._attrName)
+    
+#---------------------------------------------------------------------------------------
+
+class DelayedChangeMixin(object):
+    """A mixin to a StateChangeLogger that stores the old value and reports a
+    change only if the change has been there for a certain amount of time.
+
+    Child classes should define the following function:
+    - _getValue(state): get the value we are interested in."""
+    def __init__(self, minDelay = 3.0, maxDelay = 10.0):
+        """Construct the mixin with the given delay in seconds."""
+        self._minDelay = minDelay
+        self._maxDelay = maxDelay
+        self._oldValue = None
+        self._firstChange = None
+        self._lastChangeState = None
+        
+    def _changed(self, oldState, state):
+        """Determine if the value has changed."""
+        if self._oldValue is None:
+            self._oldValue = self._getValue(oldState)
+            
+        newValue = self._getValue(state)
+        if self._oldValue!=newValue:
+            if self._firstChange is None:
+                self._firstChange = state.timestamp
+            self._lastChangeState = state
+            self._oldValue = newValue
+
+        if self._firstChange is not None:
+            if state.timestamp >= min(self._lastChangeState.timestamp + self._minDelay,
+                                      self._firstChange + self._maxDelay):
+                self._firstChange = None
+                return True
+            
+        return False
+
+    def _getLogTimestamp(self, state):
+        """Get the log timestamp."""
+        return self._lastChangeState.timestamp if \
+               self._lastChangeState is not None else state.timestamp
+
+#---------------------------------------------------------------------------------------
+
+class TemplateMessageMixin(object):
+    """Mixin to generate a message based on a template.
+
+    Child classes should define the following function:
+    - _getValue(state): get the value we are interested in."""
+    def __init__(self, template):
+        """Construct the mixin."""
+        self._template = template
+
+    def _getMessage(self, state):
+        """Get the message."""
+        return self._template % (self._getValue(state),)
+
+#---------------------------------------------------------------------------------------
+
+class GenericStateChangeLogger(StateChangeLogger, SingleValueMixin,
+                               DelayedChangeMixin, TemplateMessageMixin):
+    """Base for generic state change loggers that monitor a single value in the
+    state possibly with a delay and the logged message comes from a template"""
+    def __init__(self, attrName, template, logInitial = True,
+                 minDelay = 0.0, maxDelay = 0.0):
+        """Construct the object."""
+        StateChangeLogger.__init__(self, logInitial = logInitial)
+        SingleValueMixin.__init__(self, attrName)
+        DelayedChangeMixin.__init__(self, minDelay = minDelay, maxDelay = maxDelay)
+        TemplateMessageMixin.__init__(self, template)
+        self._getLogTimestamp = lambda state: \
+                                DelayedChangeMixin._getLogTimestamp(self, state)
+
+#---------------------------------------------------------------------------------------
+
+class AltimeterLogger(StateChangeLogger, SingleValueMixin,
+                      DelayedChangeMixin):
+    """Logger for the altimeter setting."""
+    def __init__(self):
+        """Construct the logger."""
+        StateChangeLogger.__init__(self, logInitial = True)
+        SingleValueMixin.__init__(self, "altimeter")
+        DelayedChangeMixin.__init__(self)
+        self._getLogTimestamp = lambda state: \
+                                DelayedChangeMixin._getLogTimestamp(self, state)
+
+    def _getMessage(self, state):
+        """Get the message to log on a change."""
+        logState = self._lastChangeState if \
+                   self._lastChangeState is not None else state
+        return "Altimeter: %.0f hPa at %.0f feet" % \
+               (logState.altimeter, logState.altitude)
+
+#---------------------------------------------------------------------------------------
+
+class NAV1Logger(GenericStateChangeLogger):
+    """Logger for the NAV1 radio setting."""
+    def __init__(self):
+        """Construct the logger."""
+        super(NAV1Logger, self).__init__("nav1", "NAV1 frequency: %s MHz")
+
+#---------------------------------------------------------------------------------------
+
+class NAV2Logger(GenericStateChangeLogger):
+    """Logger for the NAV2 radio setting."""
+    def __init__(self):
+        """Construct the logger."""
+        super(NAV2Logger, self).__init__("nav2", "NAV2 frequency: %s MHz")
+
+#---------------------------------------------------------------------------------------
+
+class SquawkLogger(GenericStateChangeLogger):
+    """Logger for the squawk setting."""
+    def __init__(self):
+        """Construct the logger."""
+        super(SquawkLogger, self).__init__("squawk", "Squawk code: %s",
+                                           minDelay = 3.0, maxDelay = 10.0)
+
+#---------------------------------------------------------------------------------------
+
+class LightsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
+    """Base class for the loggers of the various lights."""
+    def __init__(self, attrName, template):
+        """Construct the logger."""
+        StateChangeLogger.__init__(self)
+        SingleValueMixin.__init__(self, attrName)
+        
+        self._template = template
+
+    def _getMessage(self, state):
+        """Get the message from the given state."""
+        return self._template % ("ON" if self._getValue(state) else "OFF")
+        
+#---------------------------------------------------------------------------------------
+
+class AnticollisionLightsLogger(LightsLogger):
+    """Logger for the anti-collision lights."""
+    def __init__(self):
+        LightsLogger.__init__(self, "antiCollisionLightsOn",
+                              "Anti-collision lights: %s")
+
+#---------------------------------------------------------------------------------------
+
+class LandingLightsLogger(LightsLogger):
+    """Logger for the landing lights."""
+    def __init__(self):
+        LightsLogger.__init__(self, "landingLightsOn",
+                              "Landing lights: %s")
+
+#---------------------------------------------------------------------------------------
+
+class StrobeLightsLogger(LightsLogger):
+    """Logger for the strobe lights."""
+    def __init__(self):
+        LightsLogger.__init__(self, "strobeLightsOn",
+                              "Strobe lights: %s")
+
+#---------------------------------------------------------------------------------------
+
+class NavLightsLogger(LightsLogger):
+    """Logger for the navigational lights."""
+    def __init__(self):
+        LightsLogger.__init__(self, "navLightsOn",
+                              "Navigational lights: %s")
+
+#---------------------------------------------------------------------------------------
+
+class FlapsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
+    """Logger for the flaps setting."""
+    def __init__(self):
+        """Construct the logger."""
+        StateChangeLogger.__init__(self, logInitial = True)
+        SingleValueMixin.__init__(self, "flapsSet")
+
+    def _getMessage(self, state):
+        """Get the message to log on a change."""
+        speed = state.groundSpeed if state.groundSpeed<80.0 else state.ias
+        return "Flaps set to %.0f at %.0f knots" % (state.flapsSet, speed)
+
+#---------------------------------------------------------------------------------------
+
+class GearsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
+    """Logger for the gears state."""
+    def __init__(self):
+        """Construct the logger."""
+        StateChangeLogger.__init__(self, logInitial = True)
+        SingleValueMixin.__init__(self, "gearsDown")
+
+    def _getMessage(self, state):
+        """Get the message to log on a change."""
+        return "Gears %s at %.0f knots, %.0f feet" % \
+            ("DOWN" if state.gearsDown else "UP", state.ias, state.altitude)
+
+#---------------------------------------------------------------------------------------
+
+class FaultChecker(StateChecker):
+    """Base class for checkers that look for faults."""
+    @staticmethod
+    def _appendDuring(flight, message):
+        """Append a 'during XXX' test to the given message, depending on the
+        flight stage."""
+        stageStr = const.stage2string(flight.stage)
+        return message if stageStr is None \
+               else (message + " during " + stageStr.upper())
+
+    @staticmethod
+    def _getLinearScore(minFaultValue, maxFaultValue, minScore, maxScore,
+                        value):
+        """Get the score for a faulty value where the score is calculated
+        linearly within a certain range."""
+        if value<minFaultValue:
+            return 0
+        elif value>maxFaultValue:
+            return maxScore
+        else:
+            return minScore + (maxScore-minScore) * (value-minFaultValue) / \
+                   (maxFaultValue - minFaultValue)
+
+#---------------------------------------------------------------------------------------
+
+class SimpleFaultChecker(FaultChecker):
+    """Base class for fault checkers that check for a single occurence of a
+    faulty condition.
+
+    Child classes should implement the following functions:
+    - isCondition(self, flight, aircraft, oldState, state): should return whether the
+    condition holds
+    - logFault(self, flight, aircraft, logger, oldState, state): log the fault
+    via the logger."""
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Perform the check."""
+        if self.isCondition(flight, aircraft, oldState, state):
+            self.logFault(flight, aircraft, logger, oldState, state)
+
+#---------------------------------------------------------------------------------------
+
+class PatientFaultChecker(FaultChecker):
+    """A fault checker that does not decides on a fault when the condition
+    arises immediately, but can wait some time.
+
+    Child classes should implement the following functions:
+    - isCondition(self, flight, aircraft, oldState, state): should return whether the
+    condition holds
+    - logFault(self, flight, aircraft, logger, oldState, state): log the fault
+    via the logger
+    """
+    def __init__(self, timeout = 2.0):
+        """Construct the fault checker with the given timeout."""
+        self._timeout = timeout
+        self._faultStarted = None
+
+    def getTimeout(self, flight, aircraft, oldState, state):
+        """Get the timeout.
+
+        This default implementation returns the timeout given in the
+        constructor, but child classes might want to enforce a different
+        policy."""
+        return self._timeout
+
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Perform the check."""
+        if self.isCondition(flight, aircraft, oldState, state):
+            if self._faultStarted is None:
+                self._faultStarted = state.timestamp
+            timeout = self.getTimeout(flight, aircraft, oldState, state)
+            if state.timestamp>=(self._faultStarted + timeout):
+                self.logFault(flight, aircraft, logger, oldState, state)
+                self._faultStarted = state.timestamp
+        else:
+            self._faultStarted = None
+                
+#---------------------------------------------------------------------------------------
+
+class AntiCollisionLightsChecker(PatientFaultChecker):
+    """Check for the anti-collision light being off at high N1 values."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        isTupolev = aircraft.type in [const.AIRCRAFT_T134, 
+                                      const.AIRCRAFT_T154]
+        return (flight.stage!=const.STAGE_PARKING or \
+                not flight.options.fs2Crew) and \
+                not state.antiCollisionLightsOn and \
+                ((isTupolev and max(state.n1[1:])>5) or \
+                 (not isTupolev and max(state.n1)>5))
+
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        flight.handleFault(AntiCollisionLightsChecker, state.timestamp,
+                           FaultChecker._appendDuring(flight,
+                                                      "Anti-collision lights were off"),
+                           1)
+
+#---------------------------------------------------------------------------------------
+
+class BankChecker(SimpleFaultChecker):
+    """Check for the anti-collision light being off at high N1 values."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        if flight.stage==const.STAGE_CRUISE:
+            bankLimit = 30
+        elif flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
+                              const.STAGE_DESCENT, const.STAGE_LANDING]:
+            bankLimit = 35
+        else:
+            return False
+
+        return state.bank>bankLimit or state.bank<-bankLimit
+
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        flight.handleFault(BankChecker, state.timestamp,
+                           FaultChecker._appendDuring(flight, "Bank too steep"),
+                           2)
+
+#---------------------------------------------------------------------------------------
+
+class FlapsRetractChecker(SimpleFaultChecker):
+    """Check if the flaps are not retracted too early."""
+    def __init__(self):
+        """Construct the flaps checker."""
+        self._timeStart = None
+    
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds.
+
+        FIXME: check if this really is the intention (FlapsRetractedMistake.java)"""
+        if (flight.stage==const.STAGE_TAKEOFF and not state.onTheGround) or \
+           (flight.stage==const.STAGE_LANDING and state.onTheGround):
+            if self._timeStart is None:
+                self._timeStart = state.timestamp
+
+            if state.flapsSet==0 and state.timestamp<=(self._timeStart+2.0):
+                return True
+        else:
+            self._timeStart = None
+        return False
+
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        flight.handleFault(FlapsRetractChecker, state.timestamp,
+                           FaultChecker._appendDuring(flight, "Flaps retracted"),
+                           20)
+
+#---------------------------------------------------------------------------------------
+
+class FlapsSpeedLimitChecker(SimpleFaultChecker):
+    """Check if the flaps are extended only at the right speeds."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        speedLimit = aircraft.getFlapsSpeedLimit(state.flapsSet)
+        return speedLimit is not None and state.ias>speedLimit
+
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        flight.handleFault(FlapsSpeedLimitChecker, state.timestamp,
+                           FaultChecker._appendDuring(flight, "Flap speed limit fault"),
+                           5)
+
+#---------------------------------------------------------------------------------------
+
+class GearsDownChecker(SimpleFaultChecker):
+    """Check if the gears are down at low altitudes."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return state.radioAltitude<10 and not state.gearsDown and \
+               flight.stage!=const.STAGE_TAKEOFF
+
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        flight.handleNoGo(GearsDownChecker, state.timestamp,
+                          "Gears not down at %.0f feet radio altitude" % \
+                          (state.radioAltitude,),
+                          "GEAR DOWN NO GO")
+
+#---------------------------------------------------------------------------------------
+
+class GearSpeedLimitChecker(PatientFaultChecker):
+    """Check if the gears not down at too high a speed."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return state.gearsDown and state.ias>aircraft.gearSpeedLimit
+
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        flight.handleFault(GearSpeedLimitChecker, state.timestamp,
+                           FaultChecker._appendDuring(flight, "Gear speed limit fault"),
+                           5)
+
+#---------------------------------------------------------------------------------------
+
+class GLoadChecker(SimpleFaultChecker):
+    """Check if the G-load does not exceed 2 except during flare."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return state.gLoad>2.0 and (flight.stage!=const.STAGE_LANDING or \
+                                    state.radioAltitude>=50)
+               
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        flight.handleFault(GLoadChecker, state.timestamp,
+                           "G-load was %.2f" % (state.gLoad,),
+                           10)
+
+#---------------------------------------------------------------------------------------
+
+class LandingLightsChecker(PatientFaultChecker):
+    """Check if the landing lights are used properly."""
+    def getTimeout(self, flight, aircraft, oldState, state):
+        """Get the timeout.
+
+        It is the default timeout except for landing and takeoff."""
+        return 0.0 if flight.stage in [const.STAGE_TAKEOFF,
+                                       const.STAGE_LANDING] else self._timeout
+
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return (flight.stage==const.STAGE_BOARDING and \
+                state.landingLightsOn and state.onTheGround) or \
+               (flight.stage==const.STAGE_TAKEOFF and \
+                not state.landingLightsOn and not state.onTheGround) or \
+               (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
+                                 const.STAGE_DESCENT] and \
+                state.landingLightsOn and state.altitude>12500) or \
+               (flight.stage==const.STAGE_LANDING and \
+                not state.landingLightsOn and state.onTheGround) or \
+               (flight.stage==const.STAGE_PARKING and \
+                state.landingLightsOn and state.onTheGround)
+               
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        score = 0 if flight.stage==const.STAGE_LANDING else 1
+        message = "Landing lights were %s" % (("on" if state.landingLightsOn else "off"),)
+        flight.handleFault(LandingLightsChecker, state.timestamp,
+                           FaultChecker._appendDuring(flight, message),
+                           score)   
+
+#---------------------------------------------------------------------------------------
+
+class WeightChecker(PatientFaultChecker):
+    """Base class for checkers that check that some limit is not exceeded."""
+    def __init__(self, name):
+        """Construct the checker."""
+        super(WeightChecker, self).__init__(timeout = 5.0)
+        self._name = name
+
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        if flight.entranceExam:
+            return False
+
+        limit = self.getLimit(flight, aircraft, state)
+        if limit is not None:
+            if flight.options.compensation is not None:
+                limit += flight.options.compensation
+            return self.getWeight(state)>limit
+
+        return False
+
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        mname = "M" + self._name
+        flight.handleNoGo(self.__class__, state.timestamp,
+                          "%s exceeded: %s is %.0f kg" % \
+                                    (mname, self._name, self.getWeight(state)),
+                          "%s NO GO" % (mname,))
+
+    def getWeight(self, state):
+        """Get the weight that is interesting for us."""
+        return state.grossWeight
+
+#---------------------------------------------------------------------------------------
+
+class MLWChecker(WeightChecker):
+    """Checks if the MLW is not exceeded on landing."""
+    def __init__(self):
+        """Construct the checker."""
+        super(MLWChecker, self).__init__("LW")
+
+    def getLimit(self, flight, aircraft, state):
+        """Get the limit if we are in the right state."""
+        return aircraft.mlw if flight.stage==const.STAGE_LANDING and \
+                               state.onTheGround else None
+
+#---------------------------------------------------------------------------------------
+
+class MTOWChecker(WeightChecker):
+    """Checks if the MTOW is not exceeded on landing."""
+    def __init__(self):
+        """Construct the checker."""
+        super(MTOWChecker, self).__init__("TOW")
+
+    def getLimit(self, flight, aircraft, state):
+        """Get the limit if we are in the right state."""
+        return aircraft.mtow if flight.stage==const.STAGE_TAKEOFF else None
+
+#---------------------------------------------------------------------------------------
+
+class MZFWChecker(WeightChecker):
+    """Checks if the MZFW is not exceeded on landing."""
+    def __init__(self):
+        """Construct the checker."""
+        super(MZFWChecker, self).__init__("ZFW")
+
+    def getLimit(self, flight, aircraft, state):
+        """Get the limit if we are in the right state."""
+        return aircraft.mzfw
+
+    def getWeight(self, state):
+        """Get the weight that is interesting for us."""
+        return state.zfw
+
+#---------------------------------------------------------------------------------------
+
+class NavLightsChecker(PatientFaultChecker):
+    """Check if the navigational lights are used properly."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return flight.stage!=const.STAGE_BOARDING and \
+               flight.stage!=const.STAGE_PARKING and \
+               not state.navLightsOn
+               
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        flight.handleFault(NavLightsChecker, state.timestamp,
+                           FaultChecker._appendDuring(flight,
+                                                      "Navigation lights were off"),
+                           1)
+
+#---------------------------------------------------------------------------------------
+
+class OverspeedChecker(PatientFaultChecker):
+    """Check if Vne has been exceeded."""
+    def __init__(self):
+        """Construct the checker."""
+        super(OverspeedChecker, self).__init__(timeout = 5.0)
+
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return state.overspeed
+               
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        flight.handleFault(OverspeedChecker, state.timestamp,
+                           FaultChecker._appendDuring(flight, "Overspeed"),
+                           20)        
+
+#---------------------------------------------------------------------------------------
+
+class PayloadChecker(SimpleFaultChecker):
+    """Check if the payload matches the specification."""
+    TOLERANCE=550
+
+    @staticmethod
+    def isZFWFaulty(aircraftZFW, flightZFW):
+        """Check if the given aircraft's ZFW is outside of the limits."""
+        return aircraftZFW < (flightZFW - PayloadChecker.TOLERANCE) or \
+               aircraftZFW > (flightZFW + PayloadChecker.TOLERANCE)        
+    
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return flight.stage==const.STAGE_PUSHANDTAXI and \
+               PayloadChecker.isZFWFaulty(state.zfw, flight.zfw)
+               
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        flight.handleNoGo(PayloadChecker, state.timestamp,
+                          "ZFW difference is more than %d kgs" % \
+                          (PayloadChecker.TOLERANCE,),
+                          "ZFW NO GO")
+
+#---------------------------------------------------------------------------------------
+
+class PitotChecker(PatientFaultChecker):
+    """Check if pitot heat is on."""
+    def __init__(self):
+        """Construct the checker."""
+        super(PitotChecker, self).__init__(timeout = 3.0)
+
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return state.groundSpeed>80 and not state.pitotHeatOn
+               
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        score = 2 if flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
+                                      const.STAGE_CRUISE, const.STAGE_DESCENT,
+                                      const.STAGE_LANDING] else 0
+        flight.handleFault(PitotChecker, state.timestamp,
+                           FaultChecker._appendDuring(flight, "Pitot heat was off"),
+                           score)   
+
+#---------------------------------------------------------------------------------------
+
+class ReverserChecker(SimpleFaultChecker):
+    """Check if the reverser is not used below 60 knots."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return flight.stage in [const.STAGE_DESCENT, const.STAGE_LANDING,
+                                const.STAGE_TAXIAFTERLAND] and \
+            state.groundSpeed<60 and max(state.reverser)
+                           
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        flight.handleFault(ReverserChecker, state.timestamp,
+                           FaultChecker._appendDuring(flight,
+                                                      "Reverser used below 60 knots"),
+                           15)        
+
+#---------------------------------------------------------------------------------------
+
+class SpeedChecker(SimpleFaultChecker):
+    """Check if the speed is in the prescribed limits."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return flight.stage in [const.STAGE_PUSHANDTAXI,
+                                const.STAGE_TAXIAFTERLAND] and \
+            state.groundSpeed>50
+                           
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        flight.handleFault(SpeedChecker, state.timestamp,
+                           FaultChecker._appendDuring(flight,
+                                                      "Taxi speed over 50 knots"),
+                           FaultChecker._getLinearScore(50, 80, 10, 15,
+                                                        state.groundSpeed))
+
+#---------------------------------------------------------------------------------------
+
+class StallChecker(PatientFaultChecker):
+    """Check if stall occured."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
+                                const.STAGE_CRUISE, const.STAGE_DESCENT,
+                                const.STAGE_LANDING] and state.stalled
+               
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        score = 40 if flight.stage in [const.STAGE_TAKEOFF,
+                                       const.STAGE_LANDING] else 30
+        flight.handleFault(StallChecker, state.timestamp,
+                           FaultChecker._appendDuring(flight, "Stalled"),
+                           score)   
+
+#---------------------------------------------------------------------------------------
+
+class StrobeLightsChecker(PatientFaultChecker):
+    """Check if the strobe lights are used properly."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        if aircraft.type in [const.AIRCRAFT_T134,
+                             const.AIRCRAFT_T154,
+                             const.AIRCRAFT_YK40]:
+            return False
+        else:
+            return (flight.stage==const.STAGE_BOARDING and \
+                    state.strobeLightsOn and state.onTheGround) or \
+                   (flight.stage==const.STAGE_TAKEOFF and \
+                    not state.strobeLightsOn and not state.gearsDown) or \
+                   (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
+                                     const.STAGE_DESCENT] and \
+                   not state.strobeLightsOn and not state.onTheGround) or \
+                   (flight.stage==const.STAGE_PARKING and \
+                    state.strobeLightsOn and state.onTheGround)
+               
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        message = "Strobe lights were %s" % (("on" if state.strobeLightsOn else "off"),)
+        flight.handleFault(StrobeLightsChecker, state.timestamp,
+                           FaultChecker._appendDuring(flight, message),
+                           1)
+
+#---------------------------------------------------------------------------------------
+
+class ThrustChecker(SimpleFaultChecker):
+    """Check if the thrust setting is not too high during takeoff.
+
+    FIXME: is this really so general, for all aircraft?"""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return flight.stage==const.STAGE_TAKEOFF and max(state.n1)>97
+               
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        print state.n1
+        flight.handleFault(ThrustChecker, state.timestamp,
+                           FaultChecker._appendDuring(flight,
+                                                      "Thrust setting was too high (>97%)"),
+                           FaultChecker._getLinearScore(97, 110, 0, 10, max(state.n1)))
+
+#---------------------------------------------------------------------------------------
+
+class VSChecker(SimpleFaultChecker):
+    """Check if the vertical speed is not too low at certain altitudes"""
+    BELOW10000 = -5000
+    BELOW5000 = -2500
+    BELOW2500 = -1500
+    BELOW500 = -1000
+    TOLERANCE = 1.2
+
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        vs = state.vs
+        altitude = state.altitude
+        return vs < -8000 or vs > 8000 or \
+               (altitude<500 and vs < (VSChecker.BELOW500 *
+                                       VSChecker.TOLERANCE)) or \
+               (altitude<2500 and vs < (VSChecker.BELOW2500 *
+                                        VSChecker.TOLERANCE)) or \
+               (altitude<5000 and vs < (VSChecker.BELOW5000 *
+                                        VSChecker.TOLERANCE)) or \
+               (altitude<10000 and vs < (VSChecker.BELOW10000 *
+                                         VSChecker.TOLERANCE))
+               
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        vs = state.vs
+
+        message = "Vertical speed was %.0f feet/min" % (vs,)
+        if vs>-8000 and vs<8000:
+            message += " at %.0f feet (exceeds company limit)" % (state.altitude,)
+
+        score = 10 if vs<-8000 or vs>8000 else 0
+
+        flight.handleFault(VSChecker, state.timestamp,
+                           FaultChecker._appendDuring(flight, message),
+                           score)
+
+#---------------------------------------------------------------------------------------
Index: src/mlx/checks.py
===================================================================
--- src/mlx/checks.py	(revision 60)
+++ 	(revision )
@@ -1,929 +1,0 @@
-# The various checks that may be performed during flight
-
-#---------------------------------------------------------------------------------------
-
-import const
-
-#---------------------------------------------------------------------------------------
-
-class StateChecker(object):
-    """Base class for classes the instances of which check the aircraft's state
-    from some aspect.
-
-    As a result of the check they may log something, or notify of some fault, etc."""
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Perform the check and do whatever is needed.
-
-        This default implementation raises a NotImplementedError."""
-        raise NotImplementedError()
-    
-#---------------------------------------------------------------------------------------
-
-class StageChecker(StateChecker):
-    """Check the flight stage transitions."""
-    def __init__(self):
-        """Construct the stage checker."""
-        self._flareStarted = False
-
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Check the stage of the aircraft."""
-        stage = flight.stage
-        if stage==None:
-            aircraft.setStage(state, const.STAGE_BOARDING)
-        elif stage==const.STAGE_BOARDING:
-            if not state.parking or \
-               (not state.trickMode and state.groundSpeed>5.0):
-                aircraft.setStage(state, const.STAGE_PUSHANDTAXI)
-        elif stage==const.STAGE_PUSHANDTAXI or stage==const.STAGE_RTO:
-            if state.landingLightsOn or state.strobeLightsOn or \
-               state.groundSpeed>80.0:
-                aircraft.setStage(state, const.STAGE_TAKEOFF)
-        elif stage==const.STAGE_TAKEOFF:
-            if not state.gearsDown or \
-               (state.radioAltitude>3000.0 and state.vs>0):
-                aircraft.setStage(state, const.STAGE_CLIMB)
-            elif not state.landingLightsOn and \
-                 not state.strobeLightsOn and \
-                 state.onTheGround and \
-                 state.groundSpeed<50.0:
-                aircraft.setStage(state, const.STAGE_RTO)
-        elif stage==const.STAGE_CLIMB:
-            if (state.altitude+2000) > flight.cruiseAltitude:
-                aircraft.setStage(state, const.STAGE_CRUISE)
-            elif state.radioAltitude<2000.0 and \
-                 state.vs < 0.0 and state.gearsDown:
-                aircraft.setStage(state, const.STAGE_LANDING)
-        elif stage==const.STAGE_CRUISE:
-            if (state.altitude+2000) < flight.cruiseAltitude:
-                aircraft.setStage(state, const.STAGE_DESCENT)
-        elif stage==const.STAGE_DESCENT or stage==const.STAGE_GOAROUND:
-            if state.gearsDown and state.radioAltitude<2000.0:
-                aircraft.setStage(state, const.STAGE_LANDING)
-            elif (state.altitude+2000) > flight.cruiseAltitude:
-                aircraft.setStage(state, const.STAGE_CRUISE)
-        elif stage==const.STAGE_LANDING:
-            if state.onTheGround and state.groundSpeed<50.0:
-                aircraft.setStage(state, const.STAGE_TAXIAFTERLAND)
-            elif not state.gearsDown:
-                aircraft.setStage(state, const.STAGE_GOAROUND)
-            elif state.radioAltitude>200 and self._flareStarted:
-                aircraft.cancelFlare()
-                self._flareStarted = False
-            elif state.radioAltitude<150 and not state.onTheGround and \
-                 not self._flareStarted:
-                self._flareStarted = True
-                aircraft.prepareFlare()
-        elif stage==const.STAGE_TAXIAFTERLAND:
-            if state.parking:
-                aircraft.setStage(state, const.STAGE_PARKING)
-        elif stage==const.STAGE_PARKING:
-            if aircraft.checkFlightEnd(state):
-                aircraft.setStage(state, const.STAGE_END)
-
-#---------------------------------------------------------------------------------------
-
-class TakeOffLogger(StateChecker):
-    """Logger for the cruise speed."""
-    def __init__(self):
-        """Construct the logger."""
-        self._onTheGround = True
-    
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Log the cruise speed if necessary."""
-        if flight.stage==const.STAGE_TAKEOFF and \
-           self._onTheGround and not state.onTheGround:
-            logger.message(state.timestamp,
-                           "Takeoff speed: %.0f knots" % (state.ias,))
-            logger.message(state.timestamp,
-                           "Takeoff heading: %03.0f degrees" % (state.heading,))
-            logger.message(state.timestamp,
-                           "Takeoff pitch: %.1f degrees" % (state.pitch,))
-            self._onTheGround = False
-
-#---------------------------------------------------------------------------------------
-
-class CruiseSpeedLogger(StateChecker):
-    """Logger for the cruise speed."""
-    def __init__(self):
-        """Construct the logger."""
-        self._lastTime = None
-    
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Log the cruise speed if necessary."""
-        if flight.stage==const.STAGE_CRUISE and \
-           (self._lastTime is None or \
-            (self._lastTime+800)<=state.timestamp):
-                if state.altitude>24500.0:
-                    logger.message(state.timestamp,
-                                   "Cruise speed: %.3f mach" % (state.mach,))
-                else:
-                    logger.message(state.timestamp,
-                                   "Cruise speed: %.0f knots" % (state.ias,))
-                self._lastTime = state.timestamp
-
-#---------------------------------------------------------------------------------------
-
-class SpoilerLogger(StateChecker):
-    """Logger for the cruise speed."""
-    def __init__(self):
-        """Construct the logger."""
-        self._logged = False
-        self._spoilersExtension = None
-    
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Log the cruise speed if necessary."""
-        if flight.stage==const.STAGE_LANDING and not self._logged:
-            if state.onTheGround:
-                if state.spoilersExtension!=self._spoilersExtension:
-                    logger.message(state.timestamp, "Spoilers deployed")
-                    self._logged = True
-            else:
-                self._spoilersExtension = state.spoilersExtension
-
-#---------------------------------------------------------------------------------------
-
-class StateChangeLogger(StateChecker):
-    """Base class for classes the instances of which check if a specific change has
-    occured in the aircraft's state, and log such change."""
-    def __init__(self, logInitial = True):
-        """Construct the logger.
-
-        If logInitial is True, the initial value will be logged, not just the
-        changes later.
-
-        Child classes should define the following functions:
-        - _changed(self, oldState, state): returns a boolean indicating if the
-        value has changed or not
-        - _getMessage(self, state): return a strings containing the message to log
-        with the new value
-        """
-        self._logInitial = logInitial    
-
-    def _getLogTimestamp(self, state):
-        """Get the log timestamp."""
-        return state.timestamp
-
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Check if the state has changed, and if so, log the new state."""
-        shouldLog = False
-        if oldState is None:
-            shouldLog = self._logInitial
-        else:
-            shouldLog = self._changed(oldState, state)
-        
-        if shouldLog:
-            logger.message(self._getLogTimestamp(state), self._getMessage(state))
-        
-#---------------------------------------------------------------------------------------
-
-class SimpleChangeMixin(object):
-    """A mixin that defines a _changed() function which simply calls a function
-    to retrieve the value two monitor for both states and compares them.
-
-    Child classes should define the following function:
-    - _getValue(state): get the value we are interested in."""
-    def _changed(self, oldState, state):
-        """Determine if the value has changed."""
-        return self._getValue(oldState)!=self._getValue(state)
-
-#---------------------------------------------------------------------------------------
-
-class SingleValueMixin(object):
-    """A mixin that provides a _getValue() function to query a value from the
-    state using the name of the attribute."""
-    def __init__(self, attrName):
-        """Construct the mixin with the given attribute name."""
-        self._attrName = attrName
-
-    def _getValue(self, state):
-        """Get the value of the attribute from the state."""
-        return getattr(state, self._attrName)
-    
-#---------------------------------------------------------------------------------------
-
-class DelayedChangeMixin(object):
-    """A mixin to a StateChangeLogger that stores the old value and reports a
-    change only if the change has been there for a certain amount of time.
-
-    Child classes should define the following function:
-    - _getValue(state): get the value we are interested in."""
-    def __init__(self, minDelay = 3.0, maxDelay = 10.0):
-        """Construct the mixin with the given delay in seconds."""
-        self._minDelay = minDelay
-        self._maxDelay = maxDelay
-        self._oldValue = None
-        self._firstChange = None
-        self._lastChangeState = None
-        
-    def _changed(self, oldState, state):
-        """Determine if the value has changed."""
-        if self._oldValue is None:
-            self._oldValue = self._getValue(oldState)
-            
-        newValue = self._getValue(state)
-        if self._oldValue!=newValue:
-            if self._firstChange is None:
-                self._firstChange = state.timestamp
-            self._lastChangeState = state
-            self._oldValue = newValue
-
-        if self._firstChange is not None:
-            if state.timestamp >= min(self._lastChangeState.timestamp + self._minDelay,
-                                      self._firstChange + self._maxDelay):
-                self._firstChange = None
-                return True
-            
-        return False
-
-    def _getLogTimestamp(self, state):
-        """Get the log timestamp."""
-        return self._lastChangeState.timestamp if \
-               self._lastChangeState is not None else state.timestamp
-
-#---------------------------------------------------------------------------------------
-
-class TemplateMessageMixin(object):
-    """Mixin to generate a message based on a template.
-
-    Child classes should define the following function:
-    - _getValue(state): get the value we are interested in."""
-    def __init__(self, template):
-        """Construct the mixin."""
-        self._template = template
-
-    def _getMessage(self, state):
-        """Get the message."""
-        return self._template % (self._getValue(state),)
-
-#---------------------------------------------------------------------------------------
-
-class GenericStateChangeLogger(StateChangeLogger, SingleValueMixin,
-                               DelayedChangeMixin, TemplateMessageMixin):
-    """Base for generic state change loggers that monitor a single value in the
-    state possibly with a delay and the logged message comes from a template"""
-    def __init__(self, attrName, template, logInitial = True,
-                 minDelay = 0.0, maxDelay = 0.0):
-        """Construct the object."""
-        StateChangeLogger.__init__(self, logInitial = logInitial)
-        SingleValueMixin.__init__(self, attrName)
-        DelayedChangeMixin.__init__(self, minDelay = minDelay, maxDelay = maxDelay)
-        TemplateMessageMixin.__init__(self, template)
-        self._getLogTimestamp = lambda state: \
-                                DelayedChangeMixin._getLogTimestamp(self, state)
-
-#---------------------------------------------------------------------------------------
-
-class AltimeterLogger(StateChangeLogger, SingleValueMixin,
-                      DelayedChangeMixin):
-    """Logger for the altimeter setting."""
-    def __init__(self):
-        """Construct the logger."""
-        StateChangeLogger.__init__(self, logInitial = True)
-        SingleValueMixin.__init__(self, "altimeter")
-        DelayedChangeMixin.__init__(self)
-        self._getLogTimestamp = lambda state: \
-                                DelayedChangeMixin._getLogTimestamp(self, state)
-
-    def _getMessage(self, state):
-        """Get the message to log on a change."""
-        logState = self._lastChangeState if \
-                   self._lastChangeState is not None else state
-        return "Altimeter: %.0f hPa at %.0f feet" % \
-               (logState.altimeter, logState.altitude)
-
-#---------------------------------------------------------------------------------------
-
-class NAV1Logger(GenericStateChangeLogger):
-    """Logger for the NAV1 radio setting."""
-    def __init__(self):
-        """Construct the logger."""
-        super(NAV1Logger, self).__init__("nav1", "NAV1 frequency: %s MHz")
-
-#---------------------------------------------------------------------------------------
-
-class NAV2Logger(GenericStateChangeLogger):
-    """Logger for the NAV2 radio setting."""
-    def __init__(self):
-        """Construct the logger."""
-        super(NAV2Logger, self).__init__("nav2", "NAV2 frequency: %s MHz")
-
-#---------------------------------------------------------------------------------------
-
-class SquawkLogger(GenericStateChangeLogger):
-    """Logger for the squawk setting."""
-    def __init__(self):
-        """Construct the logger."""
-        super(SquawkLogger, self).__init__("squawk", "Squawk code: %s",
-                                           minDelay = 3.0, maxDelay = 10.0)
-
-#---------------------------------------------------------------------------------------
-
-class LightsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
-    """Base class for the loggers of the various lights."""
-    def __init__(self, attrName, template):
-        """Construct the logger."""
-        StateChangeLogger.__init__(self)
-        SingleValueMixin.__init__(self, attrName)
-        
-        self._template = template
-
-    def _getMessage(self, state):
-        """Get the message from the given state."""
-        return self._template % ("ON" if self._getValue(state) else "OFF")
-        
-#---------------------------------------------------------------------------------------
-
-class AnticollisionLightsLogger(LightsLogger):
-    """Logger for the anti-collision lights."""
-    def __init__(self):
-        LightsLogger.__init__(self, "antiCollisionLightsOn",
-                              "Anti-collision lights: %s")
-
-#---------------------------------------------------------------------------------------
-
-class LandingLightsLogger(LightsLogger):
-    """Logger for the landing lights."""
-    def __init__(self):
-        LightsLogger.__init__(self, "landingLightsOn",
-                              "Landing lights: %s")
-
-#---------------------------------------------------------------------------------------
-
-class StrobeLightsLogger(LightsLogger):
-    """Logger for the strobe lights."""
-    def __init__(self):
-        LightsLogger.__init__(self, "strobeLightsOn",
-                              "Strobe lights: %s")
-
-#---------------------------------------------------------------------------------------
-
-class NavLightsLogger(LightsLogger):
-    """Logger for the navigational lights."""
-    def __init__(self):
-        LightsLogger.__init__(self, "navLightsOn",
-                              "Navigational lights: %s")
-
-#---------------------------------------------------------------------------------------
-
-class FlapsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
-    """Logger for the flaps setting."""
-    def __init__(self):
-        """Construct the logger."""
-        StateChangeLogger.__init__(self, logInitial = True)
-        SingleValueMixin.__init__(self, "flapsSet")
-
-    def _getMessage(self, state):
-        """Get the message to log on a change."""
-        speed = state.groundSpeed if state.groundSpeed<80.0 else state.ias
-        return "Flaps set to %.0f at %.0f knots" % (state.flapsSet, speed)
-
-#---------------------------------------------------------------------------------------
-
-class GearsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
-    """Logger for the gears state."""
-    def __init__(self):
-        """Construct the logger."""
-        StateChangeLogger.__init__(self, logInitial = True)
-        SingleValueMixin.__init__(self, "gearsDown")
-
-    def _getMessage(self, state):
-        """Get the message to log on a change."""
-        return "Gears %s at %.0f knots, %.0f feet" % \
-            ("DOWN" if state.gearsDown else "UP", state.ias, state.altitude)
-
-#---------------------------------------------------------------------------------------
-
-class FaultChecker(StateChecker):
-    """Base class for checkers that look for faults."""
-    @staticmethod
-    def _appendDuring(flight, message):
-        """Append a 'during XXX' test to the given message, depending on the
-        flight stage."""
-        stageStr = const.stage2string(flight.stage)
-        return message if stageStr is None \
-               else (message + " during " + stageStr.upper())
-
-    @staticmethod
-    def _getLinearScore(minFaultValue, maxFaultValue, minScore, maxScore,
-                        value):
-        """Get the score for a faulty value where the score is calculated
-        linearly within a certain range."""
-        if value<minFaultValue:
-            return 0
-        elif value>maxFaultValue:
-            return maxScore
-        else:
-            return minScore + (maxScore-minScore) * (value-minFaultValue) / \
-                   (maxFaultValue - minFaultValue)
-
-#---------------------------------------------------------------------------------------
-
-class SimpleFaultChecker(FaultChecker):
-    """Base class for fault checkers that check for a single occurence of a
-    faulty condition.
-
-    Child classes should implement the following functions:
-    - isCondition(self, flight, aircraft, oldState, state): should return whether the
-    condition holds
-    - logFault(self, flight, aircraft, logger, oldState, state): log the fault
-    via the logger."""
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Perform the check."""
-        if self.isCondition(flight, aircraft, oldState, state):
-            self.logFault(flight, aircraft, logger, oldState, state)
-
-#---------------------------------------------------------------------------------------
-
-class PatientFaultChecker(FaultChecker):
-    """A fault checker that does not decides on a fault when the condition
-    arises immediately, but can wait some time.
-
-    Child classes should implement the following functions:
-    - isCondition(self, flight, aircraft, oldState, state): should return whether the
-    condition holds
-    - logFault(self, flight, aircraft, logger, oldState, state): log the fault
-    via the logger
-    """
-    def __init__(self, timeout = 2.0):
-        """Construct the fault checker with the given timeout."""
-        self._timeout = timeout
-        self._faultStarted = None
-
-    def getTimeout(self, flight, aircraft, oldState, state):
-        """Get the timeout.
-
-        This default implementation returns the timeout given in the
-        constructor, but child classes might want to enforce a different
-        policy."""
-        return self._timeout
-
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Perform the check."""
-        if self.isCondition(flight, aircraft, oldState, state):
-            if self._faultStarted is None:
-                self._faultStarted = state.timestamp
-            timeout = self.getTimeout(flight, aircraft, oldState, state)
-            if state.timestamp>=(self._faultStarted + timeout):
-                self.logFault(flight, aircraft, logger, oldState, state)
-                self._faultStarted = state.timestamp
-        else:
-            self._faultStarted = None
-                
-#---------------------------------------------------------------------------------------
-
-class AntiCollisionLightsChecker(PatientFaultChecker):
-    """Check for the anti-collision light being off at high N1 values."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        isTupolev = aircraft.type in [const.AIRCRAFT_T134, 
-                                      const.AIRCRAFT_T154]
-        return (flight.stage!=const.STAGE_PARKING or \
-                not flight.options.fs2Crew) and \
-                not state.antiCollisionLightsOn and \
-                ((isTupolev and max(state.n1[1:])>5) or \
-                 (not isTupolev and max(state.n1)>5))
-
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        flight.handleFault(AntiCollisionLightsChecker, state.timestamp,
-                           FaultChecker._appendDuring(flight,
-                                                      "Anti-collision lights were off"),
-                           1)
-
-#---------------------------------------------------------------------------------------
-
-class BankChecker(SimpleFaultChecker):
-    """Check for the anti-collision light being off at high N1 values."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        if flight.stage==const.STAGE_CRUISE:
-            bankLimit = 30
-        elif flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
-                              const.STAGE_DESCENT, const.STAGE_LANDING]:
-            bankLimit = 35
-        else:
-            return False
-
-        return state.bank>bankLimit or state.bank<-bankLimit
-
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        flight.handleFault(BankChecker, state.timestamp,
-                           FaultChecker._appendDuring(flight, "Bank too steep"),
-                           2)
-
-#---------------------------------------------------------------------------------------
-
-class FlapsRetractChecker(SimpleFaultChecker):
-    """Check if the flaps are not retracted too early."""
-    def __init__(self):
-        """Construct the flaps checker."""
-        self._timeStart = None
-    
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds.
-
-        FIXME: check if this really is the intention (FlapsRetractedMistake.java)"""
-        if (flight.stage==const.STAGE_TAKEOFF and not state.onTheGround) or \
-           (flight.stage==const.STAGE_LANDING and state.onTheGround):
-            if self._timeStart is None:
-                self._timeStart = state.timestamp
-
-            if state.flapsSet==0 and state.timestamp<=(self._timeStart+2.0):
-                return True
-        else:
-            self._timeStart = None
-        return False
-
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        flight.handleFault(FlapsRetractChecker, state.timestamp,
-                           FaultChecker._appendDuring(flight, "Flaps retracted"),
-                           20)
-
-#---------------------------------------------------------------------------------------
-
-class FlapsSpeedLimitChecker(SimpleFaultChecker):
-    """Check if the flaps are extended only at the right speeds."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        speedLimit = aircraft.getFlapsSpeedLimit(state.flapsSet)
-        return speedLimit is not None and state.ias>speedLimit
-
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        flight.handleFault(FlapsSpeedLimitChecker, state.timestamp,
-                           FaultChecker._appendDuring(flight, "Flap speed limit fault"),
-                           5)
-
-#---------------------------------------------------------------------------------------
-
-class GearsDownChecker(SimpleFaultChecker):
-    """Check if the gears are down at low altitudes."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return state.radioAltitude<10 and not state.gearsDown and \
-               flight.stage!=const.STAGE_TAKEOFF
-
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        flight.handleNoGo(GearsDownChecker, state.timestamp,
-                          "Gears not down at %.0f feet radio altitude" % \
-                          (state.radioAltitude,),
-                          "GEAR DOWN NO GO")
-
-#---------------------------------------------------------------------------------------
-
-class GearSpeedLimitChecker(PatientFaultChecker):
-    """Check if the gears not down at too high a speed."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return state.gearsDown and state.ias>aircraft.gearSpeedLimit
-
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        flight.handleFault(GearSpeedLimitChecker, state.timestamp,
-                           FaultChecker._appendDuring(flight, "Gear speed limit fault"),
-                           5)
-
-#---------------------------------------------------------------------------------------
-
-class GLoadChecker(SimpleFaultChecker):
-    """Check if the G-load does not exceed 2 except during flare."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return state.gLoad>2.0 and (flight.stage!=const.STAGE_LANDING or \
-                                    state.radioAltitude>=50)
-               
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        flight.handleFault(GLoadChecker, state.timestamp,
-                           "G-load was %.2f" % (state.gLoad,),
-                           10)
-
-#---------------------------------------------------------------------------------------
-
-class LandingLightsChecker(PatientFaultChecker):
-    """Check if the landing lights are used properly."""
-    def getTimeout(self, flight, aircraft, oldState, state):
-        """Get the timeout.
-
-        It is the default timeout except for landing and takeoff."""
-        return 0.0 if flight.stage in [const.STAGE_TAKEOFF,
-                                       const.STAGE_LANDING] else self._timeout
-
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return (flight.stage==const.STAGE_BOARDING and \
-                state.landingLightsOn and state.onTheGround) or \
-               (flight.stage==const.STAGE_TAKEOFF and \
-                not state.landingLightsOn and not state.onTheGround) or \
-               (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
-                                 const.STAGE_DESCENT] and \
-                state.landingLightsOn and state.altitude>12500) or \
-               (flight.stage==const.STAGE_LANDING and \
-                not state.landingLightsOn and state.onTheGround) or \
-               (flight.stage==const.STAGE_PARKING and \
-                state.landingLightsOn and state.onTheGround)
-               
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        score = 0 if flight.stage==const.STAGE_LANDING else 1
-        message = "Landing lights were %s" % (("on" if state.landingLightsOn else "off"),)
-        flight.handleFault(LandingLightsChecker, state.timestamp,
-                           FaultChecker._appendDuring(flight, message),
-                           score)   
-
-#---------------------------------------------------------------------------------------
-
-class WeightChecker(PatientFaultChecker):
-    """Base class for checkers that check that some limit is not exceeded."""
-    def __init__(self, name):
-        """Construct the checker."""
-        super(WeightChecker, self).__init__(timeout = 5.0)
-        self._name = name
-
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        if flight.entranceExam:
-            return False
-
-        limit = self.getLimit(flight, aircraft, state)
-        if limit is not None:
-            if flight.options.compensation is not None:
-                limit += flight.options.compensation
-            return self.getWeight(state)>limit
-
-        return False
-
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        mname = "M" + self._name
-        flight.handleNoGo(self.__class__, state.timestamp,
-                          "%s exceeded: %s is %.0f kg" % \
-                                    (mname, self._name, self.getWeight(state)),
-                          "%s NO GO" % (mname,))
-
-    def getWeight(self, state):
-        """Get the weight that is interesting for us."""
-        return state.grossWeight
-
-#---------------------------------------------------------------------------------------
-
-class MLWChecker(WeightChecker):
-    """Checks if the MLW is not exceeded on landing."""
-    def __init__(self):
-        """Construct the checker."""
-        super(MLWChecker, self).__init__("LW")
-
-    def getLimit(self, flight, aircraft, state):
-        """Get the limit if we are in the right state."""
-        return aircraft.mlw if flight.stage==const.STAGE_LANDING and \
-                               state.onTheGround else None
-
-#---------------------------------------------------------------------------------------
-
-class MTOWChecker(WeightChecker):
-    """Checks if the MTOW is not exceeded on landing."""
-    def __init__(self):
-        """Construct the checker."""
-        super(MTOWChecker, self).__init__("TOW")
-
-    def getLimit(self, flight, aircraft, state):
-        """Get the limit if we are in the right state."""
-        return aircraft.mtow if flight.stage==const.STAGE_TAKEOFF else None
-
-#---------------------------------------------------------------------------------------
-
-class MZFWChecker(WeightChecker):
-    """Checks if the MZFW is not exceeded on landing."""
-    def __init__(self):
-        """Construct the checker."""
-        super(MZFWChecker, self).__init__("ZFW")
-
-    def getLimit(self, flight, aircraft, state):
-        """Get the limit if we are in the right state."""
-        return aircraft.mzfw
-
-    def getWeight(self, state):
-        """Get the weight that is interesting for us."""
-        return state.zfw
-
-#---------------------------------------------------------------------------------------
-
-class NavLightsChecker(PatientFaultChecker):
-    """Check if the navigational lights are used properly."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return flight.stage!=const.STAGE_BOARDING and \
-               flight.stage!=const.STAGE_PARKING and \
-               not state.navLightsOn
-               
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        flight.handleFault(NavLightsChecker, state.timestamp,
-                           FaultChecker._appendDuring(flight,
-                                                      "Navigation lights were off"),
-                           1)
-
-#---------------------------------------------------------------------------------------
-
-class OverspeedChecker(PatientFaultChecker):
-    """Check if Vne has been exceeded."""
-    def __init__(self):
-        """Construct the checker."""
-        super(OverspeedChecker, self).__init__(timeout = 5.0)
-
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return state.overspeed
-               
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        flight.handleFault(OverspeedChecker, state.timestamp,
-                           FaultChecker._appendDuring(flight, "Overspeed"),
-                           20)        
-
-#---------------------------------------------------------------------------------------
-
-class PayloadChecker(SimpleFaultChecker):
-    """Check if the payload matches the specification."""
-    TOLERANCE=550
-
-    @staticmethod
-    def isZFWFaulty(aircraftZFW, flightZFW):
-        """Check if the given aircraft's ZFW is outside of the limits."""
-        return aircraftZFW < (flightZFW - PayloadChecker.TOLERANCE) or \
-               aircraftZFW > (flightZFW + PayloadChecker.TOLERANCE)        
-    
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return flight.stage==const.STAGE_PUSHANDTAXI and \
-               PayloadChecker.isZFWFaulty(state.zfw, flight.zfw)
-               
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        flight.handleNoGo(PayloadChecker, state.timestamp,
-                          "ZFW difference is more than %d kgs" % \
-                          (PayloadChecker.TOLERANCE,),
-                          "ZFW NO GO")
-
-#---------------------------------------------------------------------------------------
-
-class PitotChecker(PatientFaultChecker):
-    """Check if pitot heat is on."""
-    def __init__(self):
-        """Construct the checker."""
-        super(PitotChecker, self).__init__(timeout = 3.0)
-
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return state.groundSpeed>80 and not state.pitotHeatOn
-               
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        score = 2 if flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
-                                      const.STAGE_CRUISE, const.STAGE_DESCENT,
-                                      const.STAGE_LANDING] else 0
-        flight.handleFault(PitotChecker, state.timestamp,
-                           FaultChecker._appendDuring(flight, "Pitot heat was off"),
-                           score)   
-
-#---------------------------------------------------------------------------------------
-
-class ReverserChecker(SimpleFaultChecker):
-    """Check if the reverser is not used below 60 knots."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return flight.stage in [const.STAGE_DESCENT, const.STAGE_LANDING,
-                                const.STAGE_TAXIAFTERLAND] and \
-            state.groundSpeed<60 and max(state.reverser)
-                           
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        flight.handleFault(ReverserChecker, state.timestamp,
-                           FaultChecker._appendDuring(flight,
-                                                      "Reverser used below 60 knots"),
-                           15)        
-
-#---------------------------------------------------------------------------------------
-
-class SpeedChecker(SimpleFaultChecker):
-    """Check if the speed is in the prescribed limits."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return flight.stage in [const.STAGE_PUSHANDTAXI,
-                                const.STAGE_TAXIAFTERLAND] and \
-            state.groundSpeed>50
-                           
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        flight.handleFault(SpeedChecker, state.timestamp,
-                           FaultChecker._appendDuring(flight,
-                                                      "Taxi speed over 50 knots"),
-                           FaultChecker._getLinearScore(50, 80, 10, 15,
-                                                        state.groundSpeed))
-
-#---------------------------------------------------------------------------------------
-
-class StallChecker(PatientFaultChecker):
-    """Check if stall occured."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
-                                const.STAGE_CRUISE, const.STAGE_DESCENT,
-                                const.STAGE_LANDING] and state.stalled
-               
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        score = 40 if flight.stage in [const.STAGE_TAKEOFF,
-                                       const.STAGE_LANDING] else 30
-        flight.handleFault(StallChecker, state.timestamp,
-                           FaultChecker._appendDuring(flight, "Stalled"),
-                           score)   
-
-#---------------------------------------------------------------------------------------
-
-class StrobeLightsChecker(PatientFaultChecker):
-    """Check if the strobe lights are used properly."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        if aircraft.type in [const.AIRCRAFT_T134,
-                             const.AIRCRAFT_T154,
-                             const.AIRCRAFT_YK40]:
-            return False
-        else:
-            return (flight.stage==const.STAGE_BOARDING and \
-                    state.strobeLightsOn and state.onTheGround) or \
-                   (flight.stage==const.STAGE_TAKEOFF and \
-                    not state.strobeLightsOn and not state.gearsDown) or \
-                   (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
-                                     const.STAGE_DESCENT] and \
-                   not state.strobeLightsOn and not state.onTheGround) or \
-                   (flight.stage==const.STAGE_PARKING and \
-                    state.strobeLightsOn and state.onTheGround)
-               
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        message = "Strobe lights were %s" % (("on" if state.strobeLightsOn else "off"),)
-        flight.handleFault(StrobeLightsChecker, state.timestamp,
-                           FaultChecker._appendDuring(flight, message),
-                           1)
-
-#---------------------------------------------------------------------------------------
-
-class ThrustChecker(SimpleFaultChecker):
-    """Check if the thrust setting is not too high during takeoff.
-
-    FIXME: is this really so general, for all aircraft?"""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return flight.stage==const.STAGE_TAKEOFF and max(state.n1)>97
-               
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        print state.n1
-        flight.handleFault(ThrustChecker, state.timestamp,
-                           FaultChecker._appendDuring(flight,
-                                                      "Thrust setting was too high (>97%)"),
-                           FaultChecker._getLinearScore(97, 110, 0, 10, max(state.n1)))
-
-#---------------------------------------------------------------------------------------
-
-class VSChecker(SimpleFaultChecker):
-    """Check if the vertical speed is not too low at certain altitudes"""
-    BELOW10000 = -5000
-    BELOW5000 = -2500
-    BELOW2500 = -1500
-    BELOW500 = -1000
-    TOLERANCE = 1.2
-
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        vs = state.vs
-        altitude = state.altitude
-        return vs < -8000 or vs > 8000 or \
-               (altitude<500 and vs < (VSChecker.BELOW500 *
-                                       VSChecker.TOLERANCE)) or \
-               (altitude<2500 and vs < (VSChecker.BELOW2500 *
-                                        VSChecker.TOLERANCE)) or \
-               (altitude<5000 and vs < (VSChecker.BELOW5000 *
-                                        VSChecker.TOLERANCE)) or \
-               (altitude<10000 and vs < (VSChecker.BELOW10000 *
-                                         VSChecker.TOLERANCE))
-               
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        vs = state.vs
-
-        message = "Vertical speed was %.0f feet/min" % (vs,)
-        if vs>-8000 and vs<8000:
-            message += " at %.0f feet (exceeds company limit)" % (state.altitude,)
-
-        score = 10 if vs<-8000 or vs>8000 else 0
-
-        flight.handleFault(VSChecker, state.timestamp,
-                           FaultChecker._appendDuring(flight, message),
-                           score)
-
-#---------------------------------------------------------------------------------------
Index: src/mlx/config.py
===================================================================
--- src/mlx/config.py	(revision 45)
+++ src/mlx/config.py	(revision 45)
@@ -0,0 +1,148 @@
+# Configuration and related stuff
+
+#-------------------------------------------------------------------------------
+
+import os
+import sys
+import ConfigParser
+
+#-------------------------------------------------------------------------------
+
+configPath = os.path.join(os.path.expanduser("~"),
+                          "mlx.config" if os.name=="nt" else ".mlxrc")
+
+#-------------------------------------------------------------------------------
+
+class Config(object):
+    """Our configuration."""
+    DEFAULT_UPDATE_URL = "http://mlx.varadiistvan.hu/update"
+    
+    def __init__(self):
+        """Construct the configuration with default values."""
+
+        self._pilotID = ""
+        self._password = ""
+        self._rememberPassword = False
+
+        self._autoUpdate = True        
+        self._updateURL = Config.DEFAULT_UPDATE_URL
+
+        self._modified = False
+
+    @property
+    def pilotID(self):
+        """Get the pilot ID."""
+        return self._pilotID
+
+    @pilotID.setter
+    def pilotID(self, pilotID):
+        """Set the pilot ID."""
+        if pilotID!=self._pilotID:
+            self._pilotID = pilotID
+            self._modified = True
+
+    @property
+    def password(self):
+        """Get the password."""
+        return self._password
+
+    @password.setter
+    def password(self, password):
+        """Set the password."""
+        if password!=self._password:
+            self._password = password
+            self._modified = True
+
+    @property
+    def rememberPassword(self):
+        """Get if we should remember the password."""
+        return self._rememberPassword
+
+    @rememberPassword.setter
+    def rememberPassword(self, rememberPassword):
+        """Set if we should remember the password."""
+        if rememberPassword!=self._rememberPassword:
+            self._rememberPassword = rememberPassword
+            self._modified = True
+
+    @property
+    def autoUpdate(self):
+        """Get if an automatic update is needed."""
+        return self._autoUpdate
+
+    @autoUpdate.setter
+    def autoUpdate(self, autoUpdate):
+        """Set if an automatic update is needed."""
+        if autoUpdate!=self._autoUpdate:
+            self._autoUpdate = autoUpdate
+            self._modified = True
+
+    @property
+    def updateURL(self):
+        """Get the update URL."""
+        return self._updateURL
+
+    @updateURL.setter
+    def updateURL(self, updateURL):
+        """Set the update URL."""
+        if updateURL!=self._updateURL:
+            self._updateURL = updateURL
+            self._modified = True
+
+    def load(self):
+        """Load the configuration from its default location."""
+        config = ConfigParser.RawConfigParser()
+        config.read(configPath)
+
+        self._pilotID = self._get(config, "login", "id", "")
+        self._password = self._get(config, "login", "password", "")
+        self._rememberPassword = self._getBoolean(config, "login",
+                                                  "rememberPassword", False)
+
+        self._autoUpdate = self._getBoolean(config, "update", "auto", True)
+        self._updateURL = self._get(config, "update", "url",
+                                    Config.DEFAULT_UPDATE_URL)
+        self._modified = False
+
+    def save(self):
+        """Save the configuration file if it has been modified."""
+        if not self._modified:
+            return
+
+        config = ConfigParser.RawConfigParser()
+
+        config.add_section("login")
+        config.set("login", "id", self._pilotID)
+        config.set("login", "password", self._password)
+        config.set("login", "rememberPassword",
+                   "yes" if self._rememberPassword else "no")
+
+        config.add_section("update")
+        config.set("update", "auto",
+                   "yes" if self._autoUpdate else "no")
+        config.set("update", "url", self._updateURL)
+
+        try:
+            fd = os.open(configPath, os.O_CREAT|os.O_TRUNC|os.O_WRONLY,
+                         0600)
+            with os.fdopen(fd, "wt") as f:
+                config.write(f)
+            self._modified = False
+        except Exception, e:
+            print >> sys.stderr, "Failed to update config: " + str(e)
+
+    def _getBoolean(self, config, section, option, default):
+        """Get the given option as a boolean, if found in the given config,
+        otherwise the default."""
+        return config.getboolean(section, option) \
+               if config.has_option(section, option) \
+               else default
+    
+    def _get(self, config, section, option, default):
+        """Get the given option as a string, if found in the given config,
+        otherwise the default."""
+        return config.get(section, option) \
+               if config.has_option(section, option) \
+               else default
+
+#-------------------------------------------------------------------------------
Index: src/mlx/config.py
===================================================================
--- src/mlx/config.py	(revision 45)
+++ 	(revision )
@@ -1,148 +1,0 @@
-# Configuration and related stuff
-
-#-------------------------------------------------------------------------------
-
-import os
-import sys
-import ConfigParser
-
-#-------------------------------------------------------------------------------
-
-configPath = os.path.join(os.path.expanduser("~"),
-                          "mlx.config" if os.name=="nt" else ".mlxrc")
-
-#-------------------------------------------------------------------------------
-
-class Config(object):
-    """Our configuration."""
-    DEFAULT_UPDATE_URL = "http://mlx.varadiistvan.hu/update"
-    
-    def __init__(self):
-        """Construct the configuration with default values."""
-
-        self._pilotID = ""
-        self._password = ""
-        self._rememberPassword = False
-
-        self._autoUpdate = True        
-        self._updateURL = Config.DEFAULT_UPDATE_URL
-
-        self._modified = False
-
-    @property
-    def pilotID(self):
-        """Get the pilot ID."""
-        return self._pilotID
-
-    @pilotID.setter
-    def pilotID(self, pilotID):
-        """Set the pilot ID."""
-        if pilotID!=self._pilotID:
-            self._pilotID = pilotID
-            self._modified = True
-
-    @property
-    def password(self):
-        """Get the password."""
-        return self._password
-
-    @password.setter
-    def password(self, password):
-        """Set the password."""
-        if password!=self._password:
-            self._password = password
-            self._modified = True
-
-    @property
-    def rememberPassword(self):
-        """Get if we should remember the password."""
-        return self._rememberPassword
-
-    @rememberPassword.setter
-    def rememberPassword(self, rememberPassword):
-        """Set if we should remember the password."""
-        if rememberPassword!=self._rememberPassword:
-            self._rememberPassword = rememberPassword
-            self._modified = True
-
-    @property
-    def autoUpdate(self):
-        """Get if an automatic update is needed."""
-        return self._autoUpdate
-
-    @autoUpdate.setter
-    def autoUpdate(self, autoUpdate):
-        """Set if an automatic update is needed."""
-        if autoUpdate!=self._autoUpdate:
-            self._autoUpdate = autoUpdate
-            self._modified = True
-
-    @property
-    def updateURL(self):
-        """Get the update URL."""
-        return self._updateURL
-
-    @updateURL.setter
-    def updateURL(self, updateURL):
-        """Set the update URL."""
-        if updateURL!=self._updateURL:
-            self._updateURL = updateURL
-            self._modified = True
-
-    def load(self):
-        """Load the configuration from its default location."""
-        config = ConfigParser.RawConfigParser()
-        config.read(configPath)
-
-        self._pilotID = self._get(config, "login", "id", "")
-        self._password = self._get(config, "login", "password", "")
-        self._rememberPassword = self._getBoolean(config, "login",
-                                                  "rememberPassword", False)
-
-        self._autoUpdate = self._getBoolean(config, "update", "auto", True)
-        self._updateURL = self._get(config, "update", "url",
-                                    Config.DEFAULT_UPDATE_URL)
-        self._modified = False
-
-    def save(self):
-        """Save the configuration file if it has been modified."""
-        if not self._modified:
-            return
-
-        config = ConfigParser.RawConfigParser()
-
-        config.add_section("login")
-        config.set("login", "id", self._pilotID)
-        config.set("login", "password", self._password)
-        config.set("login", "rememberPassword",
-                   "yes" if self._rememberPassword else "no")
-
-        config.add_section("update")
-        config.set("update", "auto",
-                   "yes" if self._autoUpdate else "no")
-        config.set("update", "url", self._updateURL)
-
-        try:
-            fd = os.open(configPath, os.O_CREAT|os.O_TRUNC|os.O_WRONLY,
-                         0600)
-            with os.fdopen(fd, "wt") as f:
-                config.write(f)
-            self._modified = False
-        except Exception, e:
-            print >> sys.stderr, "Failed to update config: " + str(e)
-
-    def _getBoolean(self, config, section, option, default):
-        """Get the given option as a boolean, if found in the given config,
-        otherwise the default."""
-        return config.getboolean(section, option) \
-               if config.has_option(section, option) \
-               else default
-    
-    def _get(self, config, section, option, default):
-        """Get the given option as a string, if found in the given config,
-        otherwise the default."""
-        return config.get(section, option) \
-               if config.has_option(section, option) \
-               else default
-
-#-------------------------------------------------------------------------------
Index: src/mlx/const.py
===================================================================
--- src/mlx/const.py	(revision 52)
+++ src/mlx/const.py	(revision 52)
@@ -0,0 +1,175 @@
+# Various constants used in the logger
+
+#-------------------------------------------------------------------------------
+
+# The version of the program
+VERSION="0.004"
+
+#-------------------------------------------------------------------------------
+
+# The ratio between lbs and kg
+LBSTOKG=0.4536
+
+# The ratio between kgs and lbs
+KGSTOLB=1/LBSTOKG
+
+# The ratio between feet and metre
+FEETTOMETRES=0.3048
+
+#-------------------------------------------------------------------------------
+
+# Flight simulator type: MS Flight Simulator 2004
+SIM_MSFS9 = 1
+
+# Flight simulator type: MS Flight Simulator X
+SIM_MSFSX = 2
+
+# Flight simulator type: X-Plane 9
+SIM_XPLANE9 = 3
+
+# Flight simulator type: X-Plane 10
+SIM_XPLANE10 = 4
+
+#-------------------------------------------------------------------------------
+
+# Aircraft type: Boeing 737-600
+AIRCRAFT_B736 = 1
+
+# Aircraft type: Boeing 737-700
+AIRCRAFT_B737 = 2
+
+# Aircraft type: Boeing 737-800
+AIRCRAFT_B738 = 3
+
+# Aircraft type: Boeing 737-300
+AIRCRAFT_B733 = 4
+
+# Aircraft type: Boeing 737-400
+AIRCRAFT_B734 = 5
+
+# Aircraft type: Boeing 737-500
+AIRCRAFT_B735 = 6
+
+# Aircraft type: Dash-8 Q400
+AIRCRAFT_DH8D = 7
+
+# Aircraft type: Boeing 767-200
+AIRCRAFT_B762 = 8
+
+# Aircraft type: Boeing 767-300
+AIRCRAFT_B763 = 9
+
+# Aircraft type: Canadair CRJ-200
+AIRCRAFT_CRJ2 = 10
+
+# Aircraft type: Fokker F-70
+AIRCRAFT_F70 = 11
+
+# Aircraft type: Lisunov Li-2
+AIRCRAFT_DC3 = 12
+
+# Aircraft type: Tupolev Tu-134
+AIRCRAFT_T134 = 13
+
+# Aircraft type: Tupolev Tu-154
+AIRCRAFT_T154 = 14
+
+# Aircraft type: Yakovlev Yak-40
+AIRCRAFT_YK40 = 15
+
+#-------------------------------------------------------------------------------
+
+# Flight stage: boarding
+STAGE_BOARDING = 1
+
+# Flight stage: pushback, startup and taxi
+STAGE_PUSHANDTAXI = 2
+
+# Flight stage: takeoff
+STAGE_TAKEOFF = 3
+
+# Flight stage: RTO
+STAGE_RTO = 4
+
+# Flight stage: climb
+STAGE_CLIMB = 5
+
+# Flight stage: cruise
+STAGE_CRUISE = 6
+
+# Flight stage: descent
+STAGE_DESCENT = 7
+
+# Flight stage: landing
+STAGE_LANDING = 8
+
+# Flight stage: taxi after landing
+STAGE_TAXIAFTERLAND = 9
+
+# Flight stage: parking
+STAGE_PARKING = 10
+
+# Flight stage: go-around
+STAGE_GOAROUND = 11
+
+# Flight stage: end
+STAGE_END = 12
+
+#-------------------------------------------------------------------------------
+
+# Plane status: unknown
+PLANE_UNKNOWN = 0
+
+# Plane status: at home, i.e. LHBP
+PLANE_HOME = 1
+
+# Plane status: away
+PLANE_AWAY = 2
+
+# Plane status: parking
+PLANE_PARKING = 3
+
+#-------------------------------------------------------------------------------
+
+# The available gates at LHBP
+lhbpGateNumbers = []
+
+for i in range(1, 7):
+    lhbpGateNumbers.append(str(i))
+
+for i in range(10, 19):
+    lhbpGateNumbers.append(str(i))
+
+for i in range(24, 28):
+    lhbpGateNumbers.append(str(i))
+
+for i in range(31, 39):
+    lhbpGateNumbers.append(str(i))
+
+for i in range(42, 47):
+    lhbpGateNumbers.append(str(i))
+
+for i in range(60, 84):
+    if i!=70 and i!=80:
+        lhbpGateNumbers.append(str(i))
+
+#-------------------------------------------------------------------------------
+
+_stageStrings = { STAGE_BOARDING : "boarding",
+                  STAGE_PUSHANDTAXI : "pushback and taxi",
+                  STAGE_TAKEOFF : "takeoff",
+                  STAGE_RTO : "RTO",
+                  STAGE_CLIMB : "climb",
+                  STAGE_CRUISE : "cruise",
+                  STAGE_DESCENT : "descent",
+                  STAGE_LANDING : "landing",
+                  STAGE_TAXIAFTERLAND : "taxi",
+                  STAGE_PARKING : "parking",
+                  STAGE_GOAROUND : "go-around",
+                  STAGE_END : "end" }
+
+def stage2string(stage):
+    """Convert the given stage to a lower-case string."""
+    return _stageStrings[stage] if stage in _stageStrings else None
+        
+    
Index: src/mlx/const.py
===================================================================
--- src/mlx/const.py	(revision 52)
+++ 	(revision )
@@ -1,175 +1,0 @@
-# Various constants used in the logger
-
-#-------------------------------------------------------------------------------
-
-# The version of the program
-VERSION="0.004"
-
-#-------------------------------------------------------------------------------
-
-# The ratio between lbs and kg
-LBSTOKG=0.4536
-
-# The ratio between kgs and lbs
-KGSTOLB=1/LBSTOKG
-
-# The ratio between feet and metre
-FEETTOMETRES=0.3048
-
-#-------------------------------------------------------------------------------
-
-# Flight simulator type: MS Flight Simulator 2004
-SIM_MSFS9 = 1
-
-# Flight simulator type: MS Flight Simulator X
-SIM_MSFSX = 2
-
-# Flight simulator type: X-Plane 9
-SIM_XPLANE9 = 3
-
-# Flight simulator type: X-Plane 10
-SIM_XPLANE10 = 4
-
-#-------------------------------------------------------------------------------
-
-# Aircraft type: Boeing 737-600
-AIRCRAFT_B736 = 1
-
-# Aircraft type: Boeing 737-700
-AIRCRAFT_B737 = 2
-
-# Aircraft type: Boeing 737-800
-AIRCRAFT_B738 = 3
-
-# Aircraft type: Boeing 737-300
-AIRCRAFT_B733 = 4
-
-# Aircraft type: Boeing 737-400
-AIRCRAFT_B734 = 5
-
-# Aircraft type: Boeing 737-500
-AIRCRAFT_B735 = 6
-
-# Aircraft type: Dash-8 Q400
-AIRCRAFT_DH8D = 7
-
-# Aircraft type: Boeing 767-200
-AIRCRAFT_B762 = 8
-
-# Aircraft type: Boeing 767-300
-AIRCRAFT_B763 = 9
-
-# Aircraft type: Canadair CRJ-200
-AIRCRAFT_CRJ2 = 10
-
-# Aircraft type: Fokker F-70
-AIRCRAFT_F70 = 11
-
-# Aircraft type: Lisunov Li-2
-AIRCRAFT_DC3 = 12
-
-# Aircraft type: Tupolev Tu-134
-AIRCRAFT_T134 = 13
-
-# Aircraft type: Tupolev Tu-154
-AIRCRAFT_T154 = 14
-
-# Aircraft type: Yakovlev Yak-40
-AIRCRAFT_YK40 = 15
-
-#-------------------------------------------------------------------------------
-
-# Flight stage: boarding
-STAGE_BOARDING = 1
-
-# Flight stage: pushback, startup and taxi
-STAGE_PUSHANDTAXI = 2
-
-# Flight stage: takeoff
-STAGE_TAKEOFF = 3
-
-# Flight stage: RTO
-STAGE_RTO = 4
-
-# Flight stage: climb
-STAGE_CLIMB = 5
-
-# Flight stage: cruise
-STAGE_CRUISE = 6
-
-# Flight stage: descent
-STAGE_DESCENT = 7
-
-# Flight stage: landing
-STAGE_LANDING = 8
-
-# Flight stage: taxi after landing
-STAGE_TAXIAFTERLAND = 9
-
-# Flight stage: parking
-STAGE_PARKING = 10
-
-# Flight stage: go-around
-STAGE_GOAROUND = 11
-
-# Flight stage: end
-STAGE_END = 12
-
-#-------------------------------------------------------------------------------
-
-# Plane status: unknown
-PLANE_UNKNOWN = 0
-
-# Plane status: at home, i.e. LHBP
-PLANE_HOME = 1
-
-# Plane status: away
-PLANE_AWAY = 2
-
-# Plane status: parking
-PLANE_PARKING = 3
-
-#-------------------------------------------------------------------------------
-
-# The available gates at LHBP
-lhbpGateNumbers = []
-
-for i in range(1, 7):
-    lhbpGateNumbers.append(str(i))
-
-for i in range(10, 19):
-    lhbpGateNumbers.append(str(i))
-
-for i in range(24, 28):
-    lhbpGateNumbers.append(str(i))
-
-for i in range(31, 39):
-    lhbpGateNumbers.append(str(i))
-
-for i in range(42, 47):
-    lhbpGateNumbers.append(str(i))
-
-for i in range(60, 84):
-    if i!=70 and i!=80:
-        lhbpGateNumbers.append(str(i))
-
-#-------------------------------------------------------------------------------
-
-_stageStrings = { STAGE_BOARDING : "boarding",
-                  STAGE_PUSHANDTAXI : "pushback and taxi",
-                  STAGE_TAKEOFF : "takeoff",
-                  STAGE_RTO : "RTO",
-                  STAGE_CLIMB : "climb",
-                  STAGE_CRUISE : "cruise",
-                  STAGE_DESCENT : "descent",
-                  STAGE_LANDING : "landing",
-                  STAGE_TAXIAFTERLAND : "taxi",
-                  STAGE_PARKING : "parking",
-                  STAGE_GOAROUND : "go-around",
-                  STAGE_END : "end" }
-
-def stage2string(stage):
-    """Convert the given stage to a lower-case string."""
-    return _stageStrings[stage] if stage in _stageStrings else None
-        
-    
Index: src/mlx/flight.py
===================================================================
--- src/mlx/flight.py	(revision 71)
+++ src/mlx/flight.py	(revision 71)
@@ -0,0 +1,111 @@
+# Module related to the high-level tracking of the flight
+
+#---------------------------------------------------------------------------------------
+
+import const
+
+import threading
+
+#---------------------------------------------------------------------------------------
+
+class Options(object):
+    """Various configuration options."""
+    def __init__(self):
+        """Construct the object with default values."""
+        self.fs2Crew = False
+        self.compensation = None        
+
+#---------------------------------------------------------------------------------------
+
+class Flight(object):
+    """The object with the global flight state.
+    
+    It is also the hub for the other main objects participating in the handling of
+    the flight."""
+    def __init__(self, logger, gui):
+        """Construct the flight."""
+        self._stage = None
+        self.logger = logger
+        self._gui = gui
+
+        gui.resetFlightStatus()
+
+        self.cruiseAltitude = None
+        self.flareTimeFromFS = False
+        self.entranceExam = False
+        self.zfw = None
+
+        self.options = Options()
+
+        self.aircraftType = None
+        self.aircraft = None
+        self.simulator = None
+
+        self._endCondition = threading.Condition()
+
+        self.v1 = None
+        self.vr = None
+        self.v2 = None
+
+        self._flareStart = None
+        self._flareStartFS = None
+
+    @property
+    def stage(self):
+        """Get the flight stage."""
+        return self._stage
+
+    def setStage(self, timestamp, stage):
+        """Set the flight stage.
+
+        Returns if the stage has really changed."""
+        if stage!=self._stage:
+            self._stage = stage
+            self.logger.stage(timestamp, stage)
+            self._gui.setStage(stage)
+            if stage==const.STAGE_END:
+                with self._endCondition:
+                    self._endCondition.notify()
+            return True
+        else:
+            return False
+
+    def handleFault(self, faultID, timestamp, what, score):
+        """Handle the given fault.
+
+        faultID as a unique ID for the given kind of fault. If another fault of
+        this ID has been reported earlier, it will be reported again only if
+        the score is greater than last time. This ID can be, e.g. the checker
+        the report comes from."""
+        self.logger.fault(faultID, timestamp, what, score)
+        self._gui.setRating(self.logger.getRating())
+
+    def handleNoGo(self, faultID, timestamp, what, shortReason):
+        """Handle a No-Go fault."""
+        self.logger.noGo(faultID, timestamp, what)
+        self._gui.setNoGo(shortReason)
+
+    def flareStarted(self, flareStart, flareStartFS):
+        """Called when the flare time has started."""
+        self._flareStart = flareStart
+        self._flareStartFS = flareStartFS
+
+    def flareFinished(self, flareEnd, flareEndFS):
+        """Called when the flare time has ended.
+        
+        Return a tuple of the following items:
+        - a boolean indicating if FS time is used
+        - the flare time
+        """
+        if self.flareTimeFromFS:
+            return (True, flareEndFS - self._flareStartFS)
+        else:
+            return (False, flareEnd - self._flareStart)
+
+    def wait(self):
+        """Wait for the flight to end."""
+        with self._endCondition:
+            while self._stage!=const.STAGE_END:
+                self._endCondition.wait(1)
+
+#---------------------------------------------------------------------------------------
Index: src/mlx/flight.py
===================================================================
--- src/mlx/flight.py	(revision 71)
+++ 	(revision )
@@ -1,111 +1,0 @@
-# Module related to the high-level tracking of the flight
-
-#---------------------------------------------------------------------------------------
-
-import const
-
-import threading
-
-#---------------------------------------------------------------------------------------
-
-class Options(object):
-    """Various configuration options."""
-    def __init__(self):
-        """Construct the object with default values."""
-        self.fs2Crew = False
-        self.compensation = None        
-
-#---------------------------------------------------------------------------------------
-
-class Flight(object):
-    """The object with the global flight state.
-    
-    It is also the hub for the other main objects participating in the handling of
-    the flight."""
-    def __init__(self, logger, gui):
-        """Construct the flight."""
-        self._stage = None
-        self.logger = logger
-        self._gui = gui
-
-        gui.resetFlightStatus()
-
-        self.cruiseAltitude = None
-        self.flareTimeFromFS = False
-        self.entranceExam = False
-        self.zfw = None
-
-        self.options = Options()
-
-        self.aircraftType = None
-        self.aircraft = None
-        self.simulator = None
-
-        self._endCondition = threading.Condition()
-
-        self.v1 = None
-        self.vr = None
-        self.v2 = None
-
-        self._flareStart = None
-        self._flareStartFS = None
-
-    @property
-    def stage(self):
-        """Get the flight stage."""
-        return self._stage
-
-    def setStage(self, timestamp, stage):
-        """Set the flight stage.
-
-        Returns if the stage has really changed."""
-        if stage!=self._stage:
-            self._stage = stage
-            self.logger.stage(timestamp, stage)
-            self._gui.setStage(stage)
-            if stage==const.STAGE_END:
-                with self._endCondition:
-                    self._endCondition.notify()
-            return True
-        else:
-            return False
-
-    def handleFault(self, faultID, timestamp, what, score):
-        """Handle the given fault.
-
-        faultID as a unique ID for the given kind of fault. If another fault of
-        this ID has been reported earlier, it will be reported again only if
-        the score is greater than last time. This ID can be, e.g. the checker
-        the report comes from."""
-        self.logger.fault(faultID, timestamp, what, score)
-        self._gui.setRating(self.logger.getRating())
-
-    def handleNoGo(self, faultID, timestamp, what, shortReason):
-        """Handle a No-Go fault."""
-        self.logger.noGo(faultID, timestamp, what)
-        self._gui.setNoGo(shortReason)
-
-    def flareStarted(self, flareStart, flareStartFS):
-        """Called when the flare time has started."""
-        self._flareStart = flareStart
-        self._flareStartFS = flareStartFS
-
-    def flareFinished(self, flareEnd, flareEndFS):
-        """Called when the flare time has ended.
-        
-        Return a tuple of the following items:
-        - a boolean indicating if FS time is used
-        - the flare time
-        """
-        if self.flareTimeFromFS:
-            return (True, flareEndFS - self._flareStartFS)
-        else:
-            return (False, flareEnd - self._flareStart)
-
-    def wait(self):
-        """Wait for the flight to end."""
-        with self._endCondition:
-            while self._stage!=const.STAGE_END:
-                self._endCondition.wait(1)
-
-#---------------------------------------------------------------------------------------
Index: src/mlx/fs.py
===================================================================
--- src/mlx/fs.py	(revision 59)
+++ src/mlx/fs.py	(revision 59)
@@ -0,0 +1,114 @@
+# Module for generic flight-simulator interfaces
+
+#-------------------------------------------------------------------------------
+
+import const
+
+import fsuipc
+
+#-------------------------------------------------------------------------------
+
+class ConnectionListener(object):
+    """Base class for listeners on connections to the flight simulator."""
+    def connected(self, fsType, descriptor):
+        """Called when a connection has been established to the flight
+        simulator of the given type."""
+        print "fs.ConnectionListener.connected, fsType:", fsType, ", descriptor:", descriptor
+
+    def connectionFailed(self):
+        """Called when the connection could not be established."""
+        print "fs.ConnectionListener.connectionFailed"        
+
+    def disconnected(self):
+        """Called when a connection to the flight simulator has been broken."""
+        print "fs.ConnectionListener.disconnected"
+
+#-------------------------------------------------------------------------------
+
+class SimulatorException(Exception):
+    """Exception thrown by the simulator interface for communication failure."""
+
+#-------------------------------------------------------------------------------
+
+def createSimulator(type, connectionListener):
+    """Create a simulator instance for the given simulator type with the given
+    connection listener.
+
+    The returned object should provide the following members:
+    FIXME: add info
+    """
+    assert type in [const.SIM_MSFS9, const.SIM_MSFSX], \
+           "Only MS Flight Simulator 2004 and X are supported"
+    return fsuipc.Simulator(connectionListener, connectAttempts = 3)
+
+#-------------------------------------------------------------------------------
+
+class AircraftState(object):
+    """Base class for the aircraft state produced by the aircraft model based
+    on readings from the simulator.
+
+    The following data members should be provided at least:
+    - timestamp: the simulator time of the measurement in seconds since the
+    epoch (float)
+    - paused: a boolean indicating if the flight simulator is paused for
+    whatever reason (it could be a pause mode, or a menu, a dialog, or a
+    replay, etc.)
+    - trickMode: a boolean indicating if some "trick" mode (e.g. "SLEW" in
+    MSFS) is activated
+    - overspeed: a boolean indicating if the aircraft is in overspeed
+    - stalled: a boolean indicating if the aircraft is stalled
+    - onTheGround: a boolean indicating if the aircraft is on the ground
+    - zfw: the zero-fuel weight in kilograms (float)
+    - grossWeight: the gross weight in kilograms (float)
+    - heading: the heading of the aircraft in degrees (float)
+    - pitch: the pitch of the aircraft in degrees. Positive means pitch down,
+    negative means pitch up (float)
+    - bank: the bank of the aircraft in degrees. Positive means bank left,
+    negative means bank right (float)
+    - ias: the indicated airspeed in knots (float)    
+    - mach: the airspeed in mach (float)    
+    - groundSpeed: the ground speed (float)
+    - vs: the vertical speed in feet/minutes (float)
+    - radioAltitude: the radio altitude of the aircraft in feet (float)
+    - altitude: the altitude of the aircraft in feet (float)
+    - gLoad: G-load (float)
+    - flapsSet: the selected degrees of the flaps (float)
+    - flaps: the actual degrees of the flaps (float)
+    - fuelWeight[]: the fuel weights in the different tanks in kgs (array of
+    floats of as many items as the number fuel tanks)
+    - n1[]: the N1 values of the turbine engines (array of floats
+    of as many items as the number of engines, present only for aircraft with
+    turbines)
+    - rpm[]: the RPM values of the piston engines (array of floats
+    of as many items as the number of engines, present only for aircraft with
+    pistons)
+    - reverser[]: an array of booleans indicating if the thrust reversers are
+    activated on any of the engines. The number of items equals to the number
+    of engines with a reverser.
+    - navLightsOn: a boolean indicating if the navigation lights are on
+    - antiCollisionLightsOn: a boolean indicating if the anti-collision lights are on
+    - strobeLightsOn: a boolean indicating if the strobe lights are on
+    - landingLightsOn: a boolean indicating if the landing lights are on
+    - pitotHeatOn: a boolean indicating if the pitot heat is on
+    - parking: a boolean indicating if the parking brake is set
+    - gearsDown: a boolean indicating if the gears are down
+    - spoilersArmed: a boolean indicating if the spoilers have been armed for
+    automatic deployment
+    - spoilersExtension: the percentage of how much the spoiler is extended
+    (float) 
+    - altimeter: the altimeter setting in hPa (float)
+    - nav1: the frequency of the NAV1 radio in MHz (string)
+    - nav2: the frequency of the NAV1 radio in MHz (string)
+    - squawk: the transponder code (string)
+    - windSpeed: the speed of the wind at the aircraft in knots (float)
+    - windDirection: the direction of the wind at the aircraft in degrees (float)
+
+    FIXME: needed when taxiing only:
+    - payload weight
+
+    FIXME: needed rarely:
+    - latitude, longitude
+    - transporter
+    - visibility 
+    """
+    
Index: src/mlx/fs.py
===================================================================
--- src/mlx/fs.py	(revision 59)
+++ 	(revision )
@@ -1,114 +1,0 @@
-# Module for generic flight-simulator interfaces
-
-#-------------------------------------------------------------------------------
-
-import const
-
-import fsuipc
-
-#-------------------------------------------------------------------------------
-
-class ConnectionListener(object):
-    """Base class for listeners on connections to the flight simulator."""
-    def connected(self, fsType, descriptor):
-        """Called when a connection has been established to the flight
-        simulator of the given type."""
-        print "fs.ConnectionListener.connected, fsType:", fsType, ", descriptor:", descriptor
-
-    def connectionFailed(self):
-        """Called when the connection could not be established."""
-        print "fs.ConnectionListener.connectionFailed"        
-
-    def disconnected(self):
-        """Called when a connection to the flight simulator has been broken."""
-        print "fs.ConnectionListener.disconnected"
-
-#-------------------------------------------------------------------------------
-
-class SimulatorException(Exception):
-    """Exception thrown by the simulator interface for communication failure."""
-
-#-------------------------------------------------------------------------------
-
-def createSimulator(type, connectionListener):
-    """Create a simulator instance for the given simulator type with the given
-    connection listener.
-
-    The returned object should provide the following members:
-    FIXME: add info
-    """
-    assert type in [const.SIM_MSFS9, const.SIM_MSFSX], \
-           "Only MS Flight Simulator 2004 and X are supported"
-    return fsuipc.Simulator(connectionListener, connectAttempts = 3)
-
-#-------------------------------------------------------------------------------
-
-class AircraftState(object):
-    """Base class for the aircraft state produced by the aircraft model based
-    on readings from the simulator.
-
-    The following data members should be provided at least:
-    - timestamp: the simulator time of the measurement in seconds since the
-    epoch (float)
-    - paused: a boolean indicating if the flight simulator is paused for
-    whatever reason (it could be a pause mode, or a menu, a dialog, or a
-    replay, etc.)
-    - trickMode: a boolean indicating if some "trick" mode (e.g. "SLEW" in
-    MSFS) is activated
-    - overspeed: a boolean indicating if the aircraft is in overspeed
-    - stalled: a boolean indicating if the aircraft is stalled
-    - onTheGround: a boolean indicating if the aircraft is on the ground
-    - zfw: the zero-fuel weight in kilograms (float)
-    - grossWeight: the gross weight in kilograms (float)
-    - heading: the heading of the aircraft in degrees (float)
-    - pitch: the pitch of the aircraft in degrees. Positive means pitch down,
-    negative means pitch up (float)
-    - bank: the bank of the aircraft in degrees. Positive means bank left,
-    negative means bank right (float)
-    - ias: the indicated airspeed in knots (float)    
-    - mach: the airspeed in mach (float)    
-    - groundSpeed: the ground speed (float)
-    - vs: the vertical speed in feet/minutes (float)
-    - radioAltitude: the radio altitude of the aircraft in feet (float)
-    - altitude: the altitude of the aircraft in feet (float)
-    - gLoad: G-load (float)
-    - flapsSet: the selected degrees of the flaps (float)
-    - flaps: the actual degrees of the flaps (float)
-    - fuelWeight[]: the fuel weights in the different tanks in kgs (array of
-    floats of as many items as the number fuel tanks)
-    - n1[]: the N1 values of the turbine engines (array of floats
-    of as many items as the number of engines, present only for aircraft with
-    turbines)
-    - rpm[]: the RPM values of the piston engines (array of floats
-    of as many items as the number of engines, present only for aircraft with
-    pistons)
-    - reverser[]: an array of booleans indicating if the thrust reversers are
-    activated on any of the engines. The number of items equals to the number
-    of engines with a reverser.
-    - navLightsOn: a boolean indicating if the navigation lights are on
-    - antiCollisionLightsOn: a boolean indicating if the anti-collision lights are on
-    - strobeLightsOn: a boolean indicating if the strobe lights are on
-    - landingLightsOn: a boolean indicating if the landing lights are on
-    - pitotHeatOn: a boolean indicating if the pitot heat is on
-    - parking: a boolean indicating if the parking brake is set
-    - gearsDown: a boolean indicating if the gears are down
-    - spoilersArmed: a boolean indicating if the spoilers have been armed for
-    automatic deployment
-    - spoilersExtension: the percentage of how much the spoiler is extended
-    (float) 
-    - altimeter: the altimeter setting in hPa (float)
-    - nav1: the frequency of the NAV1 radio in MHz (string)
-    - nav2: the frequency of the NAV1 radio in MHz (string)
-    - squawk: the transponder code (string)
-    - windSpeed: the speed of the wind at the aircraft in knots (float)
-    - windDirection: the direction of the wind at the aircraft in degrees (float)
-
-    FIXME: needed when taxiing only:
-    - payload weight
-
-    FIXME: needed rarely:
-    - latitude, longitude
-    - transporter
-    - visibility 
-    """
-    
Index: src/mlx/fsuipc.py
===================================================================
--- src/mlx/fsuipc.py	(revision 61)
+++ src/mlx/fsuipc.py	(revision 61)
@@ -0,0 +1,1289 @@
+# Module handling the connection to FSUIPC
+
+#------------------------------------------------------------------------------
+
+import fs
+import const
+import util
+
+import threading
+import os
+import time
+import calendar
+import sys
+import codecs
+
+if os.name == "nt":
+    import pyuipc
+else:
+    import pyuipc_sim as pyuipc
+
+#------------------------------------------------------------------------------
+
+class Handler(threading.Thread):
+    """The thread to handle the FSUIPC requests."""
+    @staticmethod
+    def fsuipc2VS(data):
+        """Convert the given vertical speed data read from FSUIPC into feet/min."""
+        return data*60.0/const.FEETTOMETRES/256.0
+
+    @staticmethod
+    def fsuipc2radioAltitude(data):
+        """Convert the given radio altitude data read from FSUIPC into feet."""
+        return data/const.FEETTOMETRES/65536.0
+
+    @staticmethod
+    def fsuipc2Degrees(data):
+        """Convert the given data into degrees."""
+        return data * 360.0 / 65536.0 / 65536.0
+
+    @staticmethod
+    def fsuipc2PositiveDegrees(data):
+        """Convert the given data into positive degrees."""
+        degrees = Handler.fsuipc2Degrees(data)
+        if degrees<0.0: degrees += 360.0
+        return degrees
+
+    @staticmethod
+    def fsuipc2IAS(data):
+        """Convert the given data into indicated airspeed."""
+        return data / 128.0
+
+    @staticmethod
+    def _callSafe(fun):
+        """Call the given function and swallow any exceptions."""
+        try:
+            return fun()
+        except Exception, e:
+            print >> sys.stderr, str(e)
+            return None
+
+    # The number of times a read is attempted
+    NUM_READATTEMPTS = 3
+
+    # The number of connection attempts
+    NUM_CONNECTATTEMPTS = 3
+
+    # The interval between successive connect attempts
+    CONNECT_INTERVAL = 0.25
+
+    @staticmethod
+    def _performRead(data, callback, extra, validator):
+        """Perform a read request.
+
+        If there is a validator, that will be called with the return values,
+        and if the values are wrong, the request is retried at most a certain
+        number of times.
+
+        Return True if the request has succeeded, False if validation has
+        failed during all attempts. An exception may also be thrown if there is
+        some lower-level communication problem."""
+        attemptsLeft = Handler.NUM_READATTEMPTS
+        while attemptsLeft>0:
+            values = pyuipc.read(data)
+            if validator is None or \
+               Handler._callSafe(lambda: validator(values, extra)):
+                Handler._callSafe(lambda: callback(values, extra))
+                return True
+            else:
+                attemptsLeft -= 1
+        return False
+
+    class Request(object):
+        """A simple, one-shot request."""
+        def __init__(self, forWrite, data, callback, extra, validator = None):
+            """Construct the request."""
+            self._forWrite = forWrite
+            self._data = data
+            self._callback = callback
+            self._extra = extra
+            self._validator = validator
+            
+        def process(self, time):
+            """Process the request.
+
+            Return True if the request has succeeded, False if data validation
+            has failed for a reading request. An exception may also be thrown
+            if there is some lower-level communication problem."""            
+            if self._forWrite:
+                pyuipc.write(self._data)
+                Handler._callSafe(lambda: self._callback(True, self._extra))
+                return True
+            else:
+                return Handler._performRead(self._data, self._callback,
+                                            self._extra, self._validator)
+
+        def fail(self):
+            """Handle the failure of this request."""
+            if self._forWrite:
+                Handler._callSafe(lambda: self._callback(False, self._extra))
+            else:
+                Handler._callSafe(lambda: self._callback(None, self._extra))
+
+    class PeriodicRequest(object):
+        """A periodic request."""
+        def __init__(self, id,  period, data, callback, extra, validator):
+            """Construct the periodic request."""
+            self._id = id
+            self._period = period
+            self._nextFire = time.time() + period
+            self._data = data
+            self._preparedData = None
+            self._callback = callback
+            self._extra = extra
+            self._validator = validator
+            
+        @property
+        def id(self):
+            """Get the ID of this periodic request."""
+            return self._id
+
+        @property
+        def nextFire(self):
+            """Get the next firing time."""
+            return self._nextFire
+
+        def process(self, time):
+            """Check if this request should be executed, and if so, do so.
+
+            time is the time at which the request is being executed. If this
+            function is called too early, nothing is done, and True is
+            returned.
+
+            Return True if the request has succeeded, False if data validation
+            has failed. An exception may also be thrown if there is some
+            lower-level communication problem."""
+            if time<self._nextFire:
+                return True
+            
+            if self._preparedData is None:
+                self._preparedData = pyuipc.prepare_data(self._data)
+                self._data = None
+
+            isOK = Handler._performRead(self._preparedData, self._callback,
+                                        self._extra, self._validator)
+
+            if isOK:
+                while self._nextFire <= time:
+                    self._nextFire += self._period
+            
+            return isOK
+
+        def fail(self):
+            """Handle the failure of this request."""
+            pass
+
+        def __cmp__(self, other):
+            """Compare two periodic requests. They are ordered by their next
+            firing times."""
+            return cmp(self._nextFire, other._nextFire)
+
+    def __init__(self, connectionListener,
+                 connectAttempts = -1, connectInterval = 0.2):
+        """Construct the handler with the given connection listener."""
+        threading.Thread.__init__(self)
+
+        self._connectionListener = connectionListener
+        self._connectAttempts = connectAttempts
+        self._connectInterval = connectInterval
+
+        self._requestCondition = threading.Condition()
+        self._connectionRequested = False
+        self._connected = False        
+
+        self._requests = []
+        self._nextPeriodicID = 1
+        self._periodicRequests = []
+
+        self.daemon = True
+
+    def requestRead(self, data, callback, extra = None, validator = None):
+        """Request the reading of some data.
+
+        data is a list of tuples of the following items:
+        - the offset of the data as an integer
+        - the type letter of the data as a string
+
+        callback is a function that receives two pieces of data:
+        - the values retrieved or None on error
+        - the extra parameter
+
+        It will be called in the handler's thread!
+        """
+        with self._requestCondition:
+            self._requests.append(Handler.Request(False, data, callback, extra,
+                                                  validator))
+            self._requestCondition.notify()
+
+    def requestWrite(self, data, callback, extra = None):
+        """Request the writing of some data.
+
+        data is a list of tuples of the following items:
+        - the offset of the data as an integer
+        - the type letter of the data as a string
+        - the data to write
+
+        callback is a function that receives two pieces of data:
+        - a boolean indicating if writing was successful
+        - the extra data
+        It will be called in the handler's thread!
+        """
+        with self._requestCondition:
+            self._requests.append(Handler.Request(True, data, callback, extra))
+            self._requestCondition.notify()
+
+    @staticmethod
+    def _readWriteCallback(data, extra):
+        """Callback for the read() and write() calls below."""
+        extra.append(data)
+        with extra[0] as condition:
+            condition.notify()
+
+    def requestPeriodicRead(self, period, data, callback, extra = None,
+                            validator = None):
+        """Request a periodic read of data.
+
+        period is a floating point number with the period in seconds.
+
+        This function returns an identifier which can be used to cancel the
+        request."""
+        with self._requestCondition:
+            id = self._nextPeriodicID
+            self._nextPeriodicID += 1
+            request = Handler.PeriodicRequest(id, period, data, callback,
+                                              extra, validator)
+            self._periodicRequests.append(request)
+            self._requestCondition.notify()
+            return id
+
+    def clearPeriodic(self, id):
+        """Clear the periodic request with the given ID."""
+        with self._requestCondition:
+            for i in range(0, len(self._periodicRequests)):
+                if self._periodicRequests[i].id==id:
+                    del self._periodicRequests[i]
+                    return True
+        return False
+
+    def connect(self):
+        """Initiate the connection to the flight simulator."""
+        with self._requestCondition:
+            if not self._connectionRequested:
+                self._connectionRequested = True
+                self._requestCondition.notify()
+        
+    def disconnect(self):
+        """Disconnect from the flight simulator."""
+        with self._requestCondition:
+            self._requests = []
+            if self._connectionRequested:
+                self._connectionRequested = False                
+                self._requestCondition.notify()
+
+    def clearRequests(self):
+        """Clear the outstanding one-shot requests."""
+        with self._requestCondition:
+            self._requests = []
+
+    def run(self):
+        """Perform the operation of the thread."""
+        while True:
+            self._waitConnectionRequest()
+            
+            if self._connect():
+                self._handleConnection()
+
+            self._disconnect()
+            
+    def _waitConnectionRequest(self):
+        """Wait for a connection request to arrive."""
+        with self._requestCondition:
+            while not self._connectionRequested:
+                self._requestCondition.wait()
+            
+    def _connect(self, autoReconnection = False):
+        """Try to connect to the flight simulator via FSUIPC
+
+        Returns True if the connection has been established, False if it was
+        not due to no longer requested.
+        """
+        attempts = 0
+        while self._connectionRequested:
+            try:
+                attempts += 1
+                pyuipc.open(pyuipc.SIM_ANY)
+                description = "(FSUIPC version: 0x%04x, library version: 0x%04x, FS version: %d)" % \
+                    (pyuipc.fsuipc_version, pyuipc.lib_version, 
+                     pyuipc.fs_version)
+                if not autoReconnection:
+                    Handler._callSafe(lambda:     
+                                      self._connectionListener.connected(const.SIM_MSFS9, 
+                                                                         description))
+                self._connected = True
+                return True
+            except Exception, e:
+                print "fsuipc.Handler._connect: connection failed: " + str(e)
+                if attempts<self.NUM_CONNECTATTEMPTS:
+                    time.sleep(self.CONNECT_INTERVAL)
+                else:
+                    self._connectionRequested = False
+                    if autoReconnection:
+                        Handler._callSafe(lambda:     
+                                          self._connectionListener.disconnected())
+                    else:
+                        Handler._callSafe(lambda:     
+                                          self._connectionListener.connectionFailed())
+
+        return False
+                
+    def _handleConnection(self):
+        """Handle a living connection."""
+        with self._requestCondition:
+            while self._connectionRequested:
+                self._processRequests()
+                self._waitRequest()
+
+    def _waitRequest(self):
+        """Wait for the time of the next request.
+
+        Returns also, if the connection is no longer requested.
+
+        Should be called with the request condition lock held."""
+        while self._connectionRequested:
+            timeout = None
+            if self._periodicRequests:
+                self._periodicRequests.sort()
+                timeout = self._periodicRequests[0].nextFire - time.time()
+
+            if timeout is not None and timeout <= 0.0:
+                return
+            
+            self._requestCondition.wait(timeout)
+                
+    def _disconnect(self):
+        """Disconnect from the flight simulator."""
+        if self._connected:
+            pyuipc.close()
+            self._connected = False
+            
+    def _processRequest(self, request, time):
+        """Process the given request. 
+
+        If an exception occurs or invalid data is read too many times, we try
+        to reconnect.
+
+        This function returns only if the request has succeeded, or if a
+        connection is no longer requested.
+
+        This function is called with the request lock held, but is relased
+        whole processing the request and reconnecting."""
+        self._requestCondition.release()
+
+        needReconnect = False
+        try:
+            try:
+                if not request.process(time):
+                    print "fsuipc.Handler._processRequest: FSUIPC returned invalid data too many times, reconnecting"
+                    needReconnect = True
+            except Exception as e:
+                print "fsuipc.Handler._processRequest: FSUIPC connection failed (" + \
+                      str(e) + "), reconnecting."
+                needReconnect = True
+
+            if needReconnect:
+                with self._requestCondition:
+                    self._requests.insert(0, request)
+                self._disconnect()
+                self._connect(autoReconnection = True)
+        finally:
+            self._requestCondition.acquire()
+        
+    def _processRequests(self):
+        """Process any pending requests.
+
+        Will be called with the request lock held."""
+        while self._connectionRequested and self._periodicRequests:
+            self._periodicRequests.sort()
+            request = self._periodicRequests[0]
+
+            t = time.time()
+
+            if request.nextFire>t:
+                break
+            
+            self._processRequest(request, t)
+
+        while self._connectionRequested and self._requests:
+            request = self._requests[0]
+            del self._requests[0]
+
+            self._processRequest(request, None)
+
+        return self._connectionRequested
+
+#------------------------------------------------------------------------------
+
+class Simulator(object):
+    """The simulator class representing the interface to the flight simulator
+    via FSUIPC."""
+    # The basic data that should be queried all the time once we are connected
+    timeData = [ (0x0240, "H"),            # Year
+                 (0x023e, "H"),            # Number of day in year
+                 (0x023b, "b"),            # UTC hour
+                 (0x023c, "b"),            # UTC minute
+                 (0x023a, "b") ]           # seconds
+    
+    normalData = timeData + \
+                 [ (0x3d00, -256),           # The name of the current aircraft
+                   (0x3c00, -256) ]          # The path of the current AIR file
+
+    flareData1 = [ (0x023a, "b"),            # Seconds of time
+                   (0x31e4, "d"),            # Radio altitude
+                   (0x02c8, "d") ]           # Vertical speed
+                   
+    flareStartData = [ (0x0e90, "H"),        # Ambient wind speed
+                       (0x0e92, "H"),        # Ambient wind direction
+                       (0x0e8a, "H") ]       # Visibility
+                       
+    flareData2 = [ (0x023a, "b"),            # Seconds of time
+                   (0x0366, "H"),            # On the ground
+                   (0x02c8, "d"),            # Vertical speed
+                   (0x030c, "d"),            # Touch-down rate
+                   (0x02bc, "d"),            # IAS
+                   (0x0578, "d"),            # Pitch
+                   (0x057c, "d"),            # Bank
+                   (0x0580, "d") ]           # Heading
+
+    @staticmethod
+    def _getTimestamp(data):
+        """Convert the given data into a timestamp."""
+        timestamp = calendar.timegm(time.struct_time([data[0],
+                                                      1, 1, 0, 0, 0, -1, 1, 0]))
+        timestamp += data[1] * 24 * 3600
+        timestamp += data[2] * 3600
+        timestamp += data[3] * 60
+        timestamp += data[4]        
+
+        return timestamp
+
+    def __init__(self, connectionListener, connectAttempts = -1,
+                 connectInterval = 0.2):
+        """Construct the simulator.
+        
+        The aircraft object passed must provide the following members:
+        - type: one of the AIRCRAFT_XXX constants from const.py
+        - modelChanged(aircraftName, modelName): called when the model handling
+        the aircraft has changed.
+        - handleState(aircraftState): handle the given state.
+        - flareStarted(windSpeed, windDirection, visibility, flareStart,
+                       flareStartFS): called when the flare has
+          started. windSpeed is in knots, windDirection is in degrees and
+          visibility is in metres. flareStart and flareStartFS are two time
+          values expressed in seconds that can be used to calculate the flare
+          time. 
+       - flareFinished(flareEnd, flareEndFS, tdRate, tdRateCalculatedByFS,
+                       ias, pitch, bank, heading): called when the flare has
+         finished, i.e. the aircraft is on the ground. flareEnd and flareEndFS
+         are the two time values corresponding to the touchdown time. tdRate is
+         the touch-down rate, tdRateCalculatedBySim indicates if the data comes
+         from the simulator or was calculated by the adapter. The other data
+         are self-explanatory and expressed in their 'natural' units."""
+        self._aircraft = None
+
+        self._handler = Handler(connectionListener,
+                                connectAttempts = connectAttempts,
+                                connectInterval = connectInterval)
+        self._handler.start()
+
+        self._normalRequestID = None
+
+        self._monitoringRequested = False
+        self._monitoring = False
+
+        self._aircraftName = None
+        self._aircraftModel = None
+
+        self._flareRequestID = None
+        self._flareRates = []
+        self._flareStart = None
+        self._flareStartFS = None
+    
+        self._latin1decoder = codecs.getdecoder("iso-8859-1")
+    
+    def connect(self, aircraft):
+        """Initiate a connection to the simulator."""
+        self._aircraft = aircraft
+        self._aircraftName = None
+        self._aircraftModel = None
+        self._handler.connect()
+        if self._normalRequestID is None:
+            self._startDefaultNormal()
+
+    def reconnect(self):
+        """Initiate a reconnection to the simulator.
+
+        It does not reset already set up data, just calls connect() on the
+        handler."""
+        self._handler.connect()
+
+    def requestZFW(self, callback):
+        """Send a request for the ZFW."""
+        self._handler.requestRead([(0x3bfc, "d")], self._handleZFW, extra = callback)
+
+    def requestTime(self, callback):
+        """Request the time from the simulator."""
+        self._handler.requestRead(Simulator.timeData, self._handleTime,
+                                  extra = callback)
+                                                            
+    def startMonitoring(self):
+        """Start the periodic monitoring of the aircraft and pass the resulting
+        state to the aircraft object periodically."""
+        assert not self._monitoringRequested         
+        self._monitoringRequested = True
+
+    def stopMonitoring(self):
+        """Stop the periodic monitoring of the aircraft."""
+        assert self._monitoringRequested 
+        self._monitoringRequested = False
+
+    def startFlare(self):
+        """Start monitoring the flare time.
+
+        At present it is assumed to be called from the FSUIPC thread, hence no
+        protection."""
+        #self._aircraft.logger.debug("startFlare")
+        if self._flareRequestID is None:
+            self._flareRates = []
+            self._flareRequestID = self._handler.requestPeriodicRead(0.1,
+                                                                     Simulator.flareData1,
+                                                                     self._handleFlare1)
+
+    def cancelFlare(self):
+        """Cancel monitoring the flare time.
+
+        At present it is assumed to be called from the FSUIPC thread, hence no
+        protection."""
+        if self._flareRequestID is not None:
+            self._handler.clearPeriodic(self._flareRequestID)
+            self._flareRequestID = None
+
+    def disconnect(self):
+        """Disconnect from the simulator."""
+        assert not self._monitoringRequested 
+        
+        self._stopNormal()
+        self._handler.disconnect()
+
+    def _startDefaultNormal(self):
+        """Start the default normal periodic request."""
+        assert self._normalRequestID is None
+        self._normalRequestID = \
+             self._handler.requestPeriodicRead(1.0,
+                                               Simulator.normalData,
+                                               self._handleNormal,
+                                               validator = self._validateNormal)
+
+    def _stopNormal(self):
+        """Stop the normal period request."""
+        assert self._normalRequestID is not None
+        self._handler.clearPeriodic(self._normalRequestID)
+        self._normalRequestID = None
+        self._monitoring = False
+
+    def _validateNormal(self, data, extra):
+        """Validate the normal data."""
+        return data[0]!=0 and data[1]!=0 and len(data[5])>0 and len(data[6])>0
+
+    def _handleNormal(self, data, extra):
+        """Handle the reply to the normal request.
+
+        At the beginning the result consists the data for normalData. When
+        monitoring is started, it contains the result also for the
+        aircraft-specific values.
+        """
+        timestamp = Simulator._getTimestamp(data)
+
+        createdNewModel = self._setAircraftName(timestamp, data[5], data[6])
+        
+        if self._monitoringRequested and not self._monitoring:
+            self._stopNormal()
+            self._startMonitoring()
+        elif self._monitoring and not self._monitoringRequested:
+            self._stopNormal()
+            self._startDefaultNormal()
+        elif self._monitoring and self._aircraftModel is not None and \
+             not createdNewModel:
+            aircraftState = self._aircraftModel.getAircraftState(self._aircraft, 
+                                                                 timestamp, data)
+            self._aircraft.handleState(aircraftState)
+
+    def _setAircraftName(self, timestamp, name, airPath):
+        """Set the name of the aicraft and if it is different from the
+        previous, create a new model for it.
+        
+        If so, also notifty the aircraft about the change.
+
+        Return if a new model was created."""
+        aircraftName = (name, airPath)
+        if aircraftName==self._aircraftName:
+            return False
+
+        self._aircraftName = aircraftName
+        needNew = self._aircraftModel is None
+        needNew = needNew or\
+            not self._aircraftModel.doesHandle(self._aircraft, aircraftName)
+        if not needNew:
+            specialModel = AircraftModel.findSpecial(self._aircraft, aircraftName)
+            needNew = specialModel is not None and \
+                specialModel is not self._aircraftModel.__class__
+
+        if needNew:
+            self._setAircraftModel(AircraftModel.create(self._aircraft, aircraftName))
+ 
+        
+        self._aircraft.modelChanged(timestamp, self._latin1decoder(name), 
+                                    self._aircraftModel.name)        
+
+        return needNew
+
+    def _setAircraftModel(self, model):
+        """Set a new aircraft model.
+
+        It will be queried for the data to monitor and the monitoring request
+        will be replaced by a new one."""
+        self._aircraftModel = model
+        
+        if self._monitoring:
+            self._stopNormal()
+            self._startMonitoring()
+            
+    def _startMonitoring(self):
+        """Start monitoring with the current aircraft model."""
+        data = Simulator.normalData[:]
+        self._aircraftModel.addMonitoringData(data)
+        
+        self._normalRequestID = \
+            self._handler.requestPeriodicRead(1.0, data, 
+                                              self._handleNormal,
+                                              validator = self._validateNormal)
+        self._monitoring = True
+
+    def _addFlareRate(self, data):
+        """Append a flare rate to the list of last rates."""
+        if len(self._flareRates)>=3:
+            del self._flareRates[0]
+        self._flareRates.append(Handler.fsuipc2VS(data))
+
+    def _handleFlare1(self, data, normal):
+        """Handle the first stage of flare monitoring."""
+        #self._aircraft.logger.debug("handleFlare1: " + str(data))
+        if Handler.fsuipc2radioAltitude(data[1])<=50.0:
+            self._flareStart = time.time()
+            self._flareStartFS = data[0]
+            self._handler.clearPeriodic(self._flareRequestID)
+            self._flareRequestID = \
+                self._handler.requestPeriodicRead(0.1, 
+                                                  Simulator.flareData2,
+                                                  self._handleFlare2)
+            self._handler.requestRead(Simulator.flareStartData,
+                                      self._handleFlareStart)
+                
+        self._addFlareRate(data[2])
+
+    def _handleFlareStart(self, data, extra):
+        """Handle the data need to notify the aircraft about the starting of
+        the flare."""
+        #self._aircraft.logger.debug("handleFlareStart: " + str(data))
+        if data is not None:
+            windDirection = data[1]*360.0/65536.0
+            if windDirection<0.0: windDirection += 360.0
+            self._aircraft.flareStarted(data[0], windDirection,
+                                        data[2]*1609.344/100.0,
+                                        self._flareStart, self._flareStartFS)
+
+    def _handleFlare2(self, data, normal):
+        """Handle the first stage of flare monitoring."""
+        #self._aircraft.logger.debug("handleFlare2: " + str(data))
+        if data[1]!=0:
+            flareEnd = time.time()
+            self._handler.clearPeriodic(self._flareRequestID)
+            self._flareRequestID = None
+
+            flareEndFS = data[0]
+            if flareEndFS<self._flareStartFS:
+                flareEndFS += 60
+
+            tdRate = Handler.fsuipc2VS(data[3])
+            tdRateCalculatedByFS = True
+            if tdRate==0 or tdRate>1000.0 or tdRate<-1000.0:
+                tdRate = min(self._flareRates)
+                tdRateCalculatedByFS = False
+
+            self._aircraft.flareFinished(flareEnd, flareEndFS,
+                                         tdRate, tdRateCalculatedByFS,
+                                         Handler.fsuipc2IAS(data[4]),
+                                         Handler.fsuipc2Degrees(data[5]),
+                                         Handler.fsuipc2Degrees(data[6]),
+                                         Handler.fsuipc2PositiveDegrees(data[7]))
+        else:
+            self._addFlareRate(data[2])
+
+    def _handleZFW(self, data, callback):
+        """Callback for a ZFW retrieval request."""
+        zfw = data[0] * const.LBSTOKG / 256.0
+        callback(zfw)
+                                                  
+    def _handleTime(self, data, callback):
+        """Callback for a time retrieval request."""
+        callback(Simulator._getTimestamp(data))
+                                                  
+#------------------------------------------------------------------------------
+
+class AircraftModel(object):
+    """Base class for the aircraft models.
+
+    Aircraft models handle the data arriving from FSUIPC and turn it into an
+    object describing the aircraft's state."""
+    monitoringData = [("paused", 0x0264, "H"),
+                      ("frozen", 0x3364, "H"),
+                      ("replay", 0x0628, "d"),
+                      ("slew", 0x05dc, "H"),
+                      ("overspeed", 0x036d, "b"),
+                      ("stalled", 0x036c, "b"),
+                      ("onTheGround", 0x0366, "H"),
+                      ("zfw", 0x3bfc, "d"),
+                      ("grossWeight", 0x30c0, "f"),
+                      ("heading", 0x0580, "d"),
+                      ("pitch", 0x0578, "d"),
+                      ("bank", 0x057c, "d"),
+                      ("ias", 0x02bc, "d"),
+                      ("mach", 0x11c6, "H"),
+                      ("groundSpeed", 0x02b4, "d"),
+                      ("vs", 0x02c8, "d"),
+                      ("radioAltitude", 0x31e4, "d"),
+                      ("altitude", 0x0570, "l"),
+                      ("gLoad", 0x11ba, "H"),
+                      ("flapsControl", 0x0bdc, "d"),
+                      ("flapsLeft", 0x0be0, "d"),
+                      ("flapsRight", 0x0be4, "d"),
+                      ("lights", 0x0d0c, "H"),
+                      ("pitot", 0x029c, "b"),
+                      ("parking", 0x0bc8, "H"),
+                      ("noseGear", 0x0bec, "d"),
+                      ("spoilersArmed", 0x0bcc, "d"),
+                      ("spoilers", 0x0bd0, "d"),
+                      ("altimeter", 0x0330, "H"),
+                      ("nav1", 0x0350, "H"),
+                      ("nav2", 0x0352, "H"),
+                      ("squawk", 0x0354, "H"),
+                      ("windSpeed", 0x0e90, "H"),
+                      ("windDirection", 0x0e92, "H")]
+
+
+    specialModels = []
+
+    @staticmethod
+    def registerSpecial(clazz):
+        """Register the given class as a special model."""
+        AircraftModel.specialModels.append(clazz)
+
+    @staticmethod
+    def findSpecial(aircraft, aircraftName):
+        for specialModel in AircraftModel.specialModels:
+            if specialModel.doesHandle(aircraft, aircraftName):
+                return specialModel
+        return None
+
+    @staticmethod
+    def create(aircraft, aircraftName):
+        """Create the model for the given aircraft name, and notify the
+        aircraft about it."""
+        specialModel = AircraftModel.findSpecial(aircraft, aircraftName)
+        if specialModel is not None:
+            return specialModel()
+        if aircraft.type in _genericModels:
+            return _genericModels[aircraft.type]()
+        else:
+            return GenericModel()
+
+    @staticmethod
+    def convertBCD(data, length):
+        """Convert a data item encoded as BCD into a string of the given number
+        of digits."""
+        bcd = ""
+        for i in range(0, length):
+            digit = chr(ord('0') + (data&0x0f))
+            data >>= 4
+            bcd = digit + bcd
+        return bcd
+
+    @staticmethod
+    def convertFrequency(data):
+        """Convert the given frequency data to a string."""
+        bcd = AircraftModel.convertBCD(data, 4)
+        return "1" + bcd[0:2] + "." + bcd[2:4]
+
+    def __init__(self, flapsNotches):
+        """Construct the aircraft model.
+        
+        flapsNotches is a list of degrees of flaps that are available on the aircraft."""
+        self._flapsNotches = flapsNotches
+    
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic"
+    
+    def doesHandle(self, aircraft, aircraftName):
+        """Determine if the model handles the given aircraft name.
+        
+        This default implementation returns False."""
+        return False
+
+    def _addOffsetWithIndexMember(self, dest, offset, type, attrName = None):
+        """Add the given FSUIPC offset and type to the given array and a member
+        attribute with the given name."""        
+        dest.append((offset, type))
+        if attrName is not None:
+            setattr(self, attrName, len(dest)-1)
+
+    def _addDataWithIndexMembers(self, dest, prefix, data):
+        """Add FSUIPC data to the given array and also corresponding index
+        member variables with the given prefix.
+
+        data is a list of triplets of the following items:
+        - the name of the data item. The index member variable will have a name
+        created by prepending the given prefix to this name.
+        - the FSUIPC offset
+        - the FSUIPC type
+        
+        The latter two items will be appended to dest."""
+        for (name, offset, type) in data:
+            self._addOffsetWithIndexMember(dest, offset, type, prefix + name)
+            
+    def addMonitoringData(self, data):
+        """Add the model-specific monitoring data to the given array."""
+        self._addDataWithIndexMembers(data, "_monidx_",
+                                      AircraftModel.monitoringData)
+    
+    def getAircraftState(self, aircraft, timestamp, data):
+        """Get an aircraft state object for the given monitoring data."""
+        state = fs.AircraftState()
+        
+        state.timestamp = timestamp
+        
+        state.paused = data[self._monidx_paused]!=0 or \
+            data[self._monidx_frozen]!=0 or \
+            data[self._monidx_replay]!=0
+        state.trickMode = data[self._monidx_slew]!=0
+
+        state.overspeed = data[self._monidx_overspeed]!=0
+        state.stalled = data[self._monidx_stalled]!=0
+        state.onTheGround = data[self._monidx_onTheGround]!=0
+
+        state.zfw = data[self._monidx_zfw] * const.LBSTOKG / 256.0
+        state.grossWeight = data[self._monidx_grossWeight] * const.LBSTOKG
+        
+        state.heading = Handler.fsuipc2PositiveDegrees(data[self._monidx_heading])
+
+        state.pitch = Handler.fsuipc2Degrees(data[self._monidx_pitch])
+        state.bank = Handler.fsuipc2Degrees(data[self._monidx_bank])
+
+        state.ias = Handler.fsuipc2IAS(data[self._monidx_ias])
+        state.mach = data[self._monidx_mach] / 20480.0
+        state.groundSpeed = data[self._monidx_groundSpeed]* 3600.0/65536.0/1852.0
+        state.vs = Handler.fsuipc2VS(data[self._monidx_vs])
+
+        state.radioAltitude = \
+            Handler.fsuipc2radioAltitude(data[self._monidx_radioAltitude])
+        state.altitude = data[self._monidx_altitude]/const.FEETTOMETRES/65536.0/65536.0
+
+        state.gLoad = data[self._monidx_gLoad] / 625.0
+        
+        numNotchesM1 = len(self._flapsNotches) - 1
+        flapsIncrement = 16383 / numNotchesM1
+        flapsControl = data[self._monidx_flapsControl]
+        flapsIndex = flapsControl / flapsIncrement
+        if flapsIndex < numNotchesM1:
+            if (flapsControl - (flapsIndex*flapsIncrement) >
+                (flapsIndex+1)*flapsIncrement - flapsControl):
+                flapsIndex += 1
+        state.flapsSet = self._flapsNotches[flapsIndex]
+        
+        flapsLeft = data[self._monidx_flapsLeft]
+        state.flaps = self._flapsNotches[-1]*flapsLeft/16383.0
+        
+        lights = data[self._monidx_lights]
+        
+        state.navLightsOn = (lights&0x01) != 0
+        state.antiCollisionLightsOn = (lights&0x02) != 0
+        state.landingLightsOn = (lights&0x04) != 0
+        state.strobeLightsOn = (lights&0x10) != 0
+        
+        state.pitotHeatOn = data[self._monidx_pitot]!=0
+
+        state.parking = data[self._monidx_parking]!=0
+
+        state.gearsDown = data[self._monidx_noseGear]==16383
+
+        state.spoilersArmed = data[self._monidx_spoilersArmed]!=0
+        
+        spoilers = data[self._monidx_spoilers]
+        if spoilers<=4800:
+            state.spoilersExtension = 0.0
+        else:
+            state.spoilersExtension = (spoilers - 4800) * 100.0 / (16383 - 4800)
+
+        state.altimeter = data[self._monidx_altimeter] / 16.0
+           
+        state.nav1 = AircraftModel.convertFrequency(data[self._monidx_nav1])
+        state.nav2 = AircraftModel.convertFrequency(data[self._monidx_nav2])
+        state.squawk = AircraftModel.convertBCD(data[self._monidx_squawk], 4)
+
+        state.windSpeed = data[self._monidx_windSpeed]
+        state.windDirection = data[self._monidx_windDirection]*360.0/65536.0
+        if state.windDirection<0.0: state.windDirection += 360.0
+        
+        return state
+
+#------------------------------------------------------------------------------
+
+class GenericAircraftModel(AircraftModel):
+    """A generic aircraft model that can handle the fuel levels, the N1 or RPM
+    values and some other common parameters in a generic way."""
+    def __init__(self, flapsNotches, fuelInfo, numEngines, isN1 = True):
+        """Construct the generic aircraft model with the given data.
+
+        flapsNotches is an array of how much degrees the individual flaps
+        notches mean.
+
+        fuelInfo is an array of FSUIPC offsets for the levels of the fuel
+        tanks. It is assumed to be a 4-byte value, followed by another 4-byte
+        value, which is the fuel tank capacity.
+
+        numEngines is the number of engines the aircraft has.
+
+        isN1 determines if the engines have an N1 value or an RPM value
+        (e.g. pistons)."""
+        super(GenericAircraftModel, self).__init__(flapsNotches = flapsNotches)
+
+        self._fuelInfo = fuelInfo
+        self._fuelStartIndex = None
+        self._numEngines = numEngines
+        self._engineStartIndex = None
+        self._isN1 = isN1
+
+    def doesHandle(self, aircraft, aircraftName):
+        """Determine if the model handles the given aircraft name.
+        
+        This implementation returns True."""
+        return True
+
+    def addMonitoringData(self, data):
+        """Add the model-specific monitoring data to the given array."""
+        super(GenericAircraftModel, self).addMonitoringData(data)
+        
+        self._addOffsetWithIndexMember(data, 0x0af4, "H", "_monidx_fuelWeight")
+
+        self._fuelStartIndex = len(data)
+        for offset in self._fuelInfo:
+            self._addOffsetWithIndexMember(data, offset, "u")    # tank level
+            self._addOffsetWithIndexMember(data, offset+4, "u")  # tank capacity
+
+        if self._isN1:
+            self._engineStartIndex = len(data)
+            for i in range(0, self._numEngines):
+                self._addOffsetWithIndexMember(data, 0x2000 + i * 0x100, "f")  # N1
+                self._addOffsetWithIndexMember(data, 0x088c + i * 0x98, "h")  # throttle lever
+        
+    def getAircraftState(self, aircraft, timestamp, data):
+        """Get the aircraft state.
+
+        Get it from the parent, and then add the data about the fuel levels and 
+        the engine parameters."""
+        state = super(GenericAircraftModel, self).getAircraftState(aircraft,
+                                                                   timestamp,
+                                                                   data)
+
+        fuelWeight = data[self._monidx_fuelWeight]/256.0
+        state.fuel = []
+        for i in range(self._fuelStartIndex, 
+                       self._fuelStartIndex + 2*len(self._fuelInfo), 2):
+            fuel = data[i+1]*data[i]*fuelWeight*const.LBSTOKG/128.0/65536.0
+            state.fuel.append(fuel)
+
+        state.n1 = []
+        state.reverser = []
+        for i in range(self._engineStartIndex,
+                       self._engineStartIndex + 2*self._numEngines, 2):
+            state.n1.append(data[i])
+            state.reverser.append(data[i+1]<0)
+
+        return state
+
+#------------------------------------------------------------------------------
+
+class GenericModel(GenericAircraftModel):
+    """Generic aircraft model for an unknown type."""
+    def __init__(self):
+        """Construct the model."""
+        super(GenericModel, self). \
+            __init__(flapsNotches = [0, 10, 20, 30],
+                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic"    
+
+#------------------------------------------------------------------------------
+
+class B737Model(GenericAircraftModel):
+    """Generic model for the Boeing 737 Classing and NG aircraft."""
+    def __init__(self):
+        """Construct the model."""
+        super(B737Model, self). \
+            __init__(flapsNotches = [0, 1, 2, 5, 10, 15, 25, 30, 40],
+                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic Boeing 737"
+
+#------------------------------------------------------------------------------
+
+class PMDGBoeing737NGModel(B737Model):
+    """A model handler for the PMDG Boeing 737NG model."""
+    @staticmethod
+    def doesHandle(aircraft, (name, airPath)):
+        """Determine if this model handler handles the aircraft with the given
+        name."""
+        return aircraft.type in [const.AIRCRAFT_B736,
+                                 const.AIRCRAFT_B737,
+                                 const.AIRCRAFT_B738] and \
+            (name.find("PMDG")!=-1 or airPath.find("PMDG")!=-1) and \
+            (name.find("737")!=-1 or airPath.find("737")!=-1) and \
+            (name.find("600")!=-1 or airPath.find("600")!=-1 or \
+             name.find("700")!=-1 or airPath.find("700")!=-1 or \
+             name.find("800")!=-1 or airPath.find("800")!=-1 or \
+             name.find("900")!=-1 or airPath.find("900")!=-1)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/PMDG Boeing 737NG"
+
+    def addMonitoringData(self, data):
+        """Add the model-specific monitoring data to the given array."""
+        super(PMDGBoeing737NGModel, self).addMonitoringData(data)
+                
+        self._addOffsetWithIndexMember(data, 0x6202, "b", "_pmdgidx_switches")
+
+    def getAircraftState(self, aircraft, timestamp, data):
+        """Get the aircraft state.
+
+        Get it from the parent, and then check some PMDG-specific stuff."""
+        state = super(PMDGBoeing737NGModel, self).getAircraftState(aircraft,
+                                                                   timestamp,
+                                                                   data)
+        if data[self._pmdgidx_switches]&0x01==0x01:
+            state.altimeter = 1013.25
+
+        return state
+
+#------------------------------------------------------------------------------
+
+class B767Model(GenericAircraftModel):
+    """Generic model for the Boeing 767 aircraft."""
+    def __init__(self):
+        """Construct the model."""
+        super(B767Model, self). \
+            __init__(flapsNotches = [0, 1, 5, 15, 20, 25, 30],
+                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic Boeing 767"
+
+#------------------------------------------------------------------------------
+
+class DH8DModel(GenericAircraftModel):
+    """Generic model for the Bombardier  Dash 8-Q400 aircraft."""
+    def __init__(self):
+        """Construct the model."""
+        super(DH8DModel, self). \
+            __init__(flapsNotches = [0, 5, 10, 15, 35],
+                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic Bombardier Dash 8-Q400"
+
+#------------------------------------------------------------------------------
+
+class DreamwingsDH8DModel(DH8DModel):
+    """Model handler for the Dreamwings Dash 8-Q400."""
+    @staticmethod
+    def doesHandle(aircraft, (name, airPath)):
+        """Determine if this model handler handles the aircraft with the given
+        name."""
+        return aircraft.type==const.AIRCRAFT_DH8D and \
+            (name.find("Dreamwings")!=-1 or airPath.find("Dreamwings")!=-1) and \
+            (name.find("Dash")!=-1 or airPath.find("Dash")!=-1) and \
+            (name.find("Q400")!=-1 or airPath.find("Q400")!=-1) and \
+            airPath.find("Dash8Q400")!=-1
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Dreamwings Bombardier Dash 8-Q400"
+
+    def getAircraftState(self, aircraft, timestamp, data):
+        """Get the aircraft state.
+
+        Get it from the parent, and then invert the pitot heat state."""
+        state = super(DreamwingsDH8DModel, self).getAircraftState(aircraft,
+                                                                  timestamp,
+                                                                  data)
+        state.pitotHeatOn = not state.pitotHeatOn
+
+        return state
+#------------------------------------------------------------------------------
+
+class CRJ2Model(GenericAircraftModel):
+    """Generic model for the Bombardier CRJ-200 aircraft."""
+    def __init__(self):
+        """Construct the model."""
+        super(CRJ2Model, self). \
+            __init__(flapsNotches = [0, 8, 20, 30, 45],
+                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic Bombardier CRJ-200"
+
+#------------------------------------------------------------------------------
+
+class F70Model(GenericAircraftModel):
+    """Generic model for the Fokker F70 aircraft."""
+    def __init__(self):
+        """Construct the model."""
+        super(F70Model, self). \
+            __init__(flapsNotches = [0, 8, 15, 25, 42],
+                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic Fokker 70"
+
+#------------------------------------------------------------------------------
+
+class DC3Model(GenericAircraftModel):
+    """Generic model for the Lisunov Li-2 (DC-3) aircraft."""
+    def __init__(self):
+        """Construct the model."""
+        super(DC3Model, self). \
+            __init__(flapsNotches = [0, 15, 30, 45],
+                     fuelInfo = [0x0b7c, 0x0b84, 0x0b94, 0x0b9c], 
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic Lisunov Li-2"
+
+#------------------------------------------------------------------------------
+
+class T134Model(GenericAircraftModel):
+    """Generic model for the Tupolev Tu-134 aircraft."""
+    def __init__(self):
+        """Construct the model."""
+        super(T134Model, self). \
+            __init__(flapsNotches = [0, 10, 20, 30],
+                     fuelInfo = [0x0b74, 
+                                 0x0b8c, 0x0b84, 
+                                 0x0ba4, 0x0b9c,
+                                 0x1254, 0x125c], 
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic Tupolev Tu-134"
+
+#------------------------------------------------------------------------------
+
+class T154Model(GenericAircraftModel):
+    """Generic model for the Tupolev Tu-134 aircraft."""
+    def __init__(self):
+        """Construct the model."""
+        super(T154Model, self). \
+            __init__(flapsNotches = [0, 15, 28, 45],
+                     fuelInfo = [0x0b74, 0x0b7c, 0x0b94, 
+                                 0x1244, 0x0b84, 0x0b9c],
+                     numEngines = 3)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic Tupolev Tu-154"
+
+    def getAircraftState(self, aircraft, timestamp, data):
+        """Get an aircraft state object for the given monitoring data.
+
+        This removes the reverser value for the middle engine."""
+        state = super(T154Model, self).getAircraftState(aircraft, timestamp, data)
+        del state.reverser[1]
+        return state
+
+#------------------------------------------------------------------------------
+
+class YK40Model(GenericAircraftModel):
+    """Generic model for the Yakovlev Yak-40 aircraft."""
+    def __init__(self):
+        """Construct the model."""
+        super(YK40Model, self). \
+            __init__(flapsNotches = [0, 20, 35],
+                     fuelInfo = [0x0b7c, 0x0b94],
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic Yakovlev Yak-40"
+
+#------------------------------------------------------------------------------
+
+_genericModels = { const.AIRCRAFT_B736 : B737Model,
+                   const.AIRCRAFT_B737 : B737Model,
+                   const.AIRCRAFT_B738 : B737Model,
+                   const.AIRCRAFT_B733 : B737Model,
+                   const.AIRCRAFT_B734 : B737Model,
+                   const.AIRCRAFT_B735 : B737Model,
+                   const.AIRCRAFT_DH8D : DH8DModel,
+                   const.AIRCRAFT_B762 : B767Model,
+                   const.AIRCRAFT_B763 : B767Model,
+                   const.AIRCRAFT_CRJ2 : B767Model,
+                   const.AIRCRAFT_F70  : F70Model,
+                   const.AIRCRAFT_DC3  : DC3Model,
+                   const.AIRCRAFT_T134 : T134Model,
+                   const.AIRCRAFT_T154 : T154Model,
+                   const.AIRCRAFT_YK40 : YK40Model }
+
+#------------------------------------------------------------------------------
+
+AircraftModel.registerSpecial(PMDGBoeing737NGModel)
+AircraftModel.registerSpecial(DreamwingsDH8DModel)
+
+#------------------------------------------------------------------------------
Index: src/mlx/fsuipc.py
===================================================================
--- src/mlx/fsuipc.py	(revision 61)
+++ 	(revision )
@@ -1,1289 +1,0 @@
-# Module handling the connection to FSUIPC
-
-#------------------------------------------------------------------------------
-
-import fs
-import const
-import util
-
-import threading
-import os
-import time
-import calendar
-import sys
-import codecs
-
-if os.name == "nt":
-    import pyuipc
-else:
-    import pyuipc_sim as pyuipc
-
-#------------------------------------------------------------------------------
-
-class Handler(threading.Thread):
-    """The thread to handle the FSUIPC requests."""
-    @staticmethod
-    def fsuipc2VS(data):
-        """Convert the given vertical speed data read from FSUIPC into feet/min."""
-        return data*60.0/const.FEETTOMETRES/256.0
-
-    @staticmethod
-    def fsuipc2radioAltitude(data):
-        """Convert the given radio altitude data read from FSUIPC into feet."""
-        return data/const.FEETTOMETRES/65536.0
-
-    @staticmethod
-    def fsuipc2Degrees(data):
-        """Convert the given data into degrees."""
-        return data * 360.0 / 65536.0 / 65536.0
-
-    @staticmethod
-    def fsuipc2PositiveDegrees(data):
-        """Convert the given data into positive degrees."""
-        degrees = Handler.fsuipc2Degrees(data)
-        if degrees<0.0: degrees += 360.0
-        return degrees
-
-    @staticmethod
-    def fsuipc2IAS(data):
-        """Convert the given data into indicated airspeed."""
-        return data / 128.0
-
-    @staticmethod
-    def _callSafe(fun):
-        """Call the given function and swallow any exceptions."""
-        try:
-            return fun()
-        except Exception, e:
-            print >> sys.stderr, str(e)
-            return None
-
-    # The number of times a read is attempted
-    NUM_READATTEMPTS = 3
-
-    # The number of connection attempts
-    NUM_CONNECTATTEMPTS = 3
-
-    # The interval between successive connect attempts
-    CONNECT_INTERVAL = 0.25
-
-    @staticmethod
-    def _performRead(data, callback, extra, validator):
-        """Perform a read request.
-
-        If there is a validator, that will be called with the return values,
-        and if the values are wrong, the request is retried at most a certain
-        number of times.
-
-        Return True if the request has succeeded, False if validation has
-        failed during all attempts. An exception may also be thrown if there is
-        some lower-level communication problem."""
-        attemptsLeft = Handler.NUM_READATTEMPTS
-        while attemptsLeft>0:
-            values = pyuipc.read(data)
-            if validator is None or \
-               Handler._callSafe(lambda: validator(values, extra)):
-                Handler._callSafe(lambda: callback(values, extra))
-                return True
-            else:
-                attemptsLeft -= 1
-        return False
-
-    class Request(object):
-        """A simple, one-shot request."""
-        def __init__(self, forWrite, data, callback, extra, validator = None):
-            """Construct the request."""
-            self._forWrite = forWrite
-            self._data = data
-            self._callback = callback
-            self._extra = extra
-            self._validator = validator
-            
-        def process(self, time):
-            """Process the request.
-
-            Return True if the request has succeeded, False if data validation
-            has failed for a reading request. An exception may also be thrown
-            if there is some lower-level communication problem."""            
-            if self._forWrite:
-                pyuipc.write(self._data)
-                Handler._callSafe(lambda: self._callback(True, self._extra))
-                return True
-            else:
-                return Handler._performRead(self._data, self._callback,
-                                            self._extra, self._validator)
-
-        def fail(self):
-            """Handle the failure of this request."""
-            if self._forWrite:
-                Handler._callSafe(lambda: self._callback(False, self._extra))
-            else:
-                Handler._callSafe(lambda: self._callback(None, self._extra))
-
-    class PeriodicRequest(object):
-        """A periodic request."""
-        def __init__(self, id,  period, data, callback, extra, validator):
-            """Construct the periodic request."""
-            self._id = id
-            self._period = period
-            self._nextFire = time.time() + period
-            self._data = data
-            self._preparedData = None
-            self._callback = callback
-            self._extra = extra
-            self._validator = validator
-            
-        @property
-        def id(self):
-            """Get the ID of this periodic request."""
-            return self._id
-
-        @property
-        def nextFire(self):
-            """Get the next firing time."""
-            return self._nextFire
-
-        def process(self, time):
-            """Check if this request should be executed, and if so, do so.
-
-            time is the time at which the request is being executed. If this
-            function is called too early, nothing is done, and True is
-            returned.
-
-            Return True if the request has succeeded, False if data validation
-            has failed. An exception may also be thrown if there is some
-            lower-level communication problem."""
-            if time<self._nextFire:
-                return True
-            
-            if self._preparedData is None:
-                self._preparedData = pyuipc.prepare_data(self._data)
-                self._data = None
-
-            isOK = Handler._performRead(self._preparedData, self._callback,
-                                        self._extra, self._validator)
-
-            if isOK:
-                while self._nextFire <= time:
-                    self._nextFire += self._period
-            
-            return isOK
-
-        def fail(self):
-            """Handle the failure of this request."""
-            pass
-
-        def __cmp__(self, other):
-            """Compare two periodic requests. They are ordered by their next
-            firing times."""
-            return cmp(self._nextFire, other._nextFire)
-
-    def __init__(self, connectionListener,
-                 connectAttempts = -1, connectInterval = 0.2):
-        """Construct the handler with the given connection listener."""
-        threading.Thread.__init__(self)
-
-        self._connectionListener = connectionListener
-        self._connectAttempts = connectAttempts
-        self._connectInterval = connectInterval
-
-        self._requestCondition = threading.Condition()
-        self._connectionRequested = False
-        self._connected = False        
-
-        self._requests = []
-        self._nextPeriodicID = 1
-        self._periodicRequests = []
-
-        self.daemon = True
-
-    def requestRead(self, data, callback, extra = None, validator = None):
-        """Request the reading of some data.
-
-        data is a list of tuples of the following items:
-        - the offset of the data as an integer
-        - the type letter of the data as a string
-
-        callback is a function that receives two pieces of data:
-        - the values retrieved or None on error
-        - the extra parameter
-
-        It will be called in the handler's thread!
-        """
-        with self._requestCondition:
-            self._requests.append(Handler.Request(False, data, callback, extra,
-                                                  validator))
-            self._requestCondition.notify()
-
-    def requestWrite(self, data, callback, extra = None):
-        """Request the writing of some data.
-
-        data is a list of tuples of the following items:
-        - the offset of the data as an integer
-        - the type letter of the data as a string
-        - the data to write
-
-        callback is a function that receives two pieces of data:
-        - a boolean indicating if writing was successful
-        - the extra data
-        It will be called in the handler's thread!
-        """
-        with self._requestCondition:
-            self._requests.append(Handler.Request(True, data, callback, extra))
-            self._requestCondition.notify()
-
-    @staticmethod
-    def _readWriteCallback(data, extra):
-        """Callback for the read() and write() calls below."""
-        extra.append(data)
-        with extra[0] as condition:
-            condition.notify()
-
-    def requestPeriodicRead(self, period, data, callback, extra = None,
-                            validator = None):
-        """Request a periodic read of data.
-
-        period is a floating point number with the period in seconds.
-
-        This function returns an identifier which can be used to cancel the
-        request."""
-        with self._requestCondition:
-            id = self._nextPeriodicID
-            self._nextPeriodicID += 1
-            request = Handler.PeriodicRequest(id, period, data, callback,
-                                              extra, validator)
-            self._periodicRequests.append(request)
-            self._requestCondition.notify()
-            return id
-
-    def clearPeriodic(self, id):
-        """Clear the periodic request with the given ID."""
-        with self._requestCondition:
-            for i in range(0, len(self._periodicRequests)):
-                if self._periodicRequests[i].id==id:
-                    del self._periodicRequests[i]
-                    return True
-        return False
-
-    def connect(self):
-        """Initiate the connection to the flight simulator."""
-        with self._requestCondition:
-            if not self._connectionRequested:
-                self._connectionRequested = True
-                self._requestCondition.notify()
-        
-    def disconnect(self):
-        """Disconnect from the flight simulator."""
-        with self._requestCondition:
-            self._requests = []
-            if self._connectionRequested:
-                self._connectionRequested = False                
-                self._requestCondition.notify()
-
-    def clearRequests(self):
-        """Clear the outstanding one-shot requests."""
-        with self._requestCondition:
-            self._requests = []
-
-    def run(self):
-        """Perform the operation of the thread."""
-        while True:
-            self._waitConnectionRequest()
-            
-            if self._connect():
-                self._handleConnection()
-
-            self._disconnect()
-            
-    def _waitConnectionRequest(self):
-        """Wait for a connection request to arrive."""
-        with self._requestCondition:
-            while not self._connectionRequested:
-                self._requestCondition.wait()
-            
-    def _connect(self, autoReconnection = False):
-        """Try to connect to the flight simulator via FSUIPC
-
-        Returns True if the connection has been established, False if it was
-        not due to no longer requested.
-        """
-        attempts = 0
-        while self._connectionRequested:
-            try:
-                attempts += 1
-                pyuipc.open(pyuipc.SIM_ANY)
-                description = "(FSUIPC version: 0x%04x, library version: 0x%04x, FS version: %d)" % \
-                    (pyuipc.fsuipc_version, pyuipc.lib_version, 
-                     pyuipc.fs_version)
-                if not autoReconnection:
-                    Handler._callSafe(lambda:     
-                                      self._connectionListener.connected(const.SIM_MSFS9, 
-                                                                         description))
-                self._connected = True
-                return True
-            except Exception, e:
-                print "fsuipc.Handler._connect: connection failed: " + str(e)
-                if attempts<self.NUM_CONNECTATTEMPTS:
-                    time.sleep(self.CONNECT_INTERVAL)
-                else:
-                    self._connectionRequested = False
-                    if autoReconnection:
-                        Handler._callSafe(lambda:     
-                                          self._connectionListener.disconnected())
-                    else:
-                        Handler._callSafe(lambda:     
-                                          self._connectionListener.connectionFailed())
-
-        return False
-                
-    def _handleConnection(self):
-        """Handle a living connection."""
-        with self._requestCondition:
-            while self._connectionRequested:
-                self._processRequests()
-                self._waitRequest()
-
-    def _waitRequest(self):
-        """Wait for the time of the next request.
-
-        Returns also, if the connection is no longer requested.
-
-        Should be called with the request condition lock held."""
-        while self._connectionRequested:
-            timeout = None
-            if self._periodicRequests:
-                self._periodicRequests.sort()
-                timeout = self._periodicRequests[0].nextFire - time.time()
-
-            if timeout is not None and timeout <= 0.0:
-                return
-            
-            self._requestCondition.wait(timeout)
-                
-    def _disconnect(self):
-        """Disconnect from the flight simulator."""
-        if self._connected:
-            pyuipc.close()
-            self._connected = False
-            
-    def _processRequest(self, request, time):
-        """Process the given request. 
-
-        If an exception occurs or invalid data is read too many times, we try
-        to reconnect.
-
-        This function returns only if the request has succeeded, or if a
-        connection is no longer requested.
-
-        This function is called with the request lock held, but is relased
-        whole processing the request and reconnecting."""
-        self._requestCondition.release()
-
-        needReconnect = False
-        try:
-            try:
-                if not request.process(time):
-                    print "fsuipc.Handler._processRequest: FSUIPC returned invalid data too many times, reconnecting"
-                    needReconnect = True
-            except Exception as e:
-                print "fsuipc.Handler._processRequest: FSUIPC connection failed (" + \
-                      str(e) + "), reconnecting."
-                needReconnect = True
-
-            if needReconnect:
-                with self._requestCondition:
-                    self._requests.insert(0, request)
-                self._disconnect()
-                self._connect(autoReconnection = True)
-        finally:
-            self._requestCondition.acquire()
-        
-    def _processRequests(self):
-        """Process any pending requests.
-
-        Will be called with the request lock held."""
-        while self._connectionRequested and self._periodicRequests:
-            self._periodicRequests.sort()
-            request = self._periodicRequests[0]
-
-            t = time.time()
-
-            if request.nextFire>t:
-                break
-            
-            self._processRequest(request, t)
-
-        while self._connectionRequested and self._requests:
-            request = self._requests[0]
-            del self._requests[0]
-
-            self._processRequest(request, None)
-
-        return self._connectionRequested
-
-#------------------------------------------------------------------------------
-
-class Simulator(object):
-    """The simulator class representing the interface to the flight simulator
-    via FSUIPC."""
-    # The basic data that should be queried all the time once we are connected
-    timeData = [ (0x0240, "H"),            # Year
-                 (0x023e, "H"),            # Number of day in year
-                 (0x023b, "b"),            # UTC hour
-                 (0x023c, "b"),            # UTC minute
-                 (0x023a, "b") ]           # seconds
-    
-    normalData = timeData + \
-                 [ (0x3d00, -256),           # The name of the current aircraft
-                   (0x3c00, -256) ]          # The path of the current AIR file
-
-    flareData1 = [ (0x023a, "b"),            # Seconds of time
-                   (0x31e4, "d"),            # Radio altitude
-                   (0x02c8, "d") ]           # Vertical speed
-                   
-    flareStartData = [ (0x0e90, "H"),        # Ambient wind speed
-                       (0x0e92, "H"),        # Ambient wind direction
-                       (0x0e8a, "H") ]       # Visibility
-                       
-    flareData2 = [ (0x023a, "b"),            # Seconds of time
-                   (0x0366, "H"),            # On the ground
-                   (0x02c8, "d"),            # Vertical speed
-                   (0x030c, "d"),            # Touch-down rate
-                   (0x02bc, "d"),            # IAS
-                   (0x0578, "d"),            # Pitch
-                   (0x057c, "d"),            # Bank
-                   (0x0580, "d") ]           # Heading
-
-    @staticmethod
-    def _getTimestamp(data):
-        """Convert the given data into a timestamp."""
-        timestamp = calendar.timegm(time.struct_time([data[0],
-                                                      1, 1, 0, 0, 0, -1, 1, 0]))
-        timestamp += data[1] * 24 * 3600
-        timestamp += data[2] * 3600
-        timestamp += data[3] * 60
-        timestamp += data[4]        
-
-        return timestamp
-
-    def __init__(self, connectionListener, connectAttempts = -1,
-                 connectInterval = 0.2):
-        """Construct the simulator.
-        
-        The aircraft object passed must provide the following members:
-        - type: one of the AIRCRAFT_XXX constants from const.py
-        - modelChanged(aircraftName, modelName): called when the model handling
-        the aircraft has changed.
-        - handleState(aircraftState): handle the given state.
-        - flareStarted(windSpeed, windDirection, visibility, flareStart,
-                       flareStartFS): called when the flare has
-          started. windSpeed is in knots, windDirection is in degrees and
-          visibility is in metres. flareStart and flareStartFS are two time
-          values expressed in seconds that can be used to calculate the flare
-          time. 
-       - flareFinished(flareEnd, flareEndFS, tdRate, tdRateCalculatedByFS,
-                       ias, pitch, bank, heading): called when the flare has
-         finished, i.e. the aircraft is on the ground. flareEnd and flareEndFS
-         are the two time values corresponding to the touchdown time. tdRate is
-         the touch-down rate, tdRateCalculatedBySim indicates if the data comes
-         from the simulator or was calculated by the adapter. The other data
-         are self-explanatory and expressed in their 'natural' units."""
-        self._aircraft = None
-
-        self._handler = Handler(connectionListener,
-                                connectAttempts = connectAttempts,
-                                connectInterval = connectInterval)
-        self._handler.start()
-
-        self._normalRequestID = None
-
-        self._monitoringRequested = False
-        self._monitoring = False
-
-        self._aircraftName = None
-        self._aircraftModel = None
-
-        self._flareRequestID = None
-        self._flareRates = []
-        self._flareStart = None
-        self._flareStartFS = None
-    
-        self._latin1decoder = codecs.getdecoder("iso-8859-1")
-    
-    def connect(self, aircraft):
-        """Initiate a connection to the simulator."""
-        self._aircraft = aircraft
-        self._aircraftName = None
-        self._aircraftModel = None
-        self._handler.connect()
-        if self._normalRequestID is None:
-            self._startDefaultNormal()
-
-    def reconnect(self):
-        """Initiate a reconnection to the simulator.
-
-        It does not reset already set up data, just calls connect() on the
-        handler."""
-        self._handler.connect()
-
-    def requestZFW(self, callback):
-        """Send a request for the ZFW."""
-        self._handler.requestRead([(0x3bfc, "d")], self._handleZFW, extra = callback)
-
-    def requestTime(self, callback):
-        """Request the time from the simulator."""
-        self._handler.requestRead(Simulator.timeData, self._handleTime,
-                                  extra = callback)
-                                                            
-    def startMonitoring(self):
-        """Start the periodic monitoring of the aircraft and pass the resulting
-        state to the aircraft object periodically."""
-        assert not self._monitoringRequested         
-        self._monitoringRequested = True
-
-    def stopMonitoring(self):
-        """Stop the periodic monitoring of the aircraft."""
-        assert self._monitoringRequested 
-        self._monitoringRequested = False
-
-    def startFlare(self):
-        """Start monitoring the flare time.
-
-        At present it is assumed to be called from the FSUIPC thread, hence no
-        protection."""
-        #self._aircraft.logger.debug("startFlare")
-        if self._flareRequestID is None:
-            self._flareRates = []
-            self._flareRequestID = self._handler.requestPeriodicRead(0.1,
-                                                                     Simulator.flareData1,
-                                                                     self._handleFlare1)
-
-    def cancelFlare(self):
-        """Cancel monitoring the flare time.
-
-        At present it is assumed to be called from the FSUIPC thread, hence no
-        protection."""
-        if self._flareRequestID is not None:
-            self._handler.clearPeriodic(self._flareRequestID)
-            self._flareRequestID = None
-
-    def disconnect(self):
-        """Disconnect from the simulator."""
-        assert not self._monitoringRequested 
-        
-        self._stopNormal()
-        self._handler.disconnect()
-
-    def _startDefaultNormal(self):
-        """Start the default normal periodic request."""
-        assert self._normalRequestID is None
-        self._normalRequestID = \
-             self._handler.requestPeriodicRead(1.0,
-                                               Simulator.normalData,
-                                               self._handleNormal,
-                                               validator = self._validateNormal)
-
-    def _stopNormal(self):
-        """Stop the normal period request."""
-        assert self._normalRequestID is not None
-        self._handler.clearPeriodic(self._normalRequestID)
-        self._normalRequestID = None
-        self._monitoring = False
-
-    def _validateNormal(self, data, extra):
-        """Validate the normal data."""
-        return data[0]!=0 and data[1]!=0 and len(data[5])>0 and len(data[6])>0
-
-    def _handleNormal(self, data, extra):
-        """Handle the reply to the normal request.
-
-        At the beginning the result consists the data for normalData. When
-        monitoring is started, it contains the result also for the
-        aircraft-specific values.
-        """
-        timestamp = Simulator._getTimestamp(data)
-
-        createdNewModel = self._setAircraftName(timestamp, data[5], data[6])
-        
-        if self._monitoringRequested and not self._monitoring:
-            self._stopNormal()
-            self._startMonitoring()
-        elif self._monitoring and not self._monitoringRequested:
-            self._stopNormal()
-            self._startDefaultNormal()
-        elif self._monitoring and self._aircraftModel is not None and \
-             not createdNewModel:
-            aircraftState = self._aircraftModel.getAircraftState(self._aircraft, 
-                                                                 timestamp, data)
-            self._aircraft.handleState(aircraftState)
-
-    def _setAircraftName(self, timestamp, name, airPath):
-        """Set the name of the aicraft and if it is different from the
-        previous, create a new model for it.
-        
-        If so, also notifty the aircraft about the change.
-
-        Return if a new model was created."""
-        aircraftName = (name, airPath)
-        if aircraftName==self._aircraftName:
-            return False
-
-        self._aircraftName = aircraftName
-        needNew = self._aircraftModel is None
-        needNew = needNew or\
-            not self._aircraftModel.doesHandle(self._aircraft, aircraftName)
-        if not needNew:
-            specialModel = AircraftModel.findSpecial(self._aircraft, aircraftName)
-            needNew = specialModel is not None and \
-                specialModel is not self._aircraftModel.__class__
-
-        if needNew:
-            self._setAircraftModel(AircraftModel.create(self._aircraft, aircraftName))
- 
-        
-        self._aircraft.modelChanged(timestamp, self._latin1decoder(name), 
-                                    self._aircraftModel.name)        
-
-        return needNew
-
-    def _setAircraftModel(self, model):
-        """Set a new aircraft model.
-
-        It will be queried for the data to monitor and the monitoring request
-        will be replaced by a new one."""
-        self._aircraftModel = model
-        
-        if self._monitoring:
-            self._stopNormal()
-            self._startMonitoring()
-            
-    def _startMonitoring(self):
-        """Start monitoring with the current aircraft model."""
-        data = Simulator.normalData[:]
-        self._aircraftModel.addMonitoringData(data)
-        
-        self._normalRequestID = \
-            self._handler.requestPeriodicRead(1.0, data, 
-                                              self._handleNormal,
-                                              validator = self._validateNormal)
-        self._monitoring = True
-
-    def _addFlareRate(self, data):
-        """Append a flare rate to the list of last rates."""
-        if len(self._flareRates)>=3:
-            del self._flareRates[0]
-        self._flareRates.append(Handler.fsuipc2VS(data))
-
-    def _handleFlare1(self, data, normal):
-        """Handle the first stage of flare monitoring."""
-        #self._aircraft.logger.debug("handleFlare1: " + str(data))
-        if Handler.fsuipc2radioAltitude(data[1])<=50.0:
-            self._flareStart = time.time()
-            self._flareStartFS = data[0]
-            self._handler.clearPeriodic(self._flareRequestID)
-            self._flareRequestID = \
-                self._handler.requestPeriodicRead(0.1, 
-                                                  Simulator.flareData2,
-                                                  self._handleFlare2)
-            self._handler.requestRead(Simulator.flareStartData,
-                                      self._handleFlareStart)
-                
-        self._addFlareRate(data[2])
-
-    def _handleFlareStart(self, data, extra):
-        """Handle the data need to notify the aircraft about the starting of
-        the flare."""
-        #self._aircraft.logger.debug("handleFlareStart: " + str(data))
-        if data is not None:
-            windDirection = data[1]*360.0/65536.0
-            if windDirection<0.0: windDirection += 360.0
-            self._aircraft.flareStarted(data[0], windDirection,
-                                        data[2]*1609.344/100.0,
-                                        self._flareStart, self._flareStartFS)
-
-    def _handleFlare2(self, data, normal):
-        """Handle the first stage of flare monitoring."""
-        #self._aircraft.logger.debug("handleFlare2: " + str(data))
-        if data[1]!=0:
-            flareEnd = time.time()
-            self._handler.clearPeriodic(self._flareRequestID)
-            self._flareRequestID = None
-
-            flareEndFS = data[0]
-            if flareEndFS<self._flareStartFS:
-                flareEndFS += 60
-
-            tdRate = Handler.fsuipc2VS(data[3])
-            tdRateCalculatedByFS = True
-            if tdRate==0 or tdRate>1000.0 or tdRate<-1000.0:
-                tdRate = min(self._flareRates)
-                tdRateCalculatedByFS = False
-
-            self._aircraft.flareFinished(flareEnd, flareEndFS,
-                                         tdRate, tdRateCalculatedByFS,
-                                         Handler.fsuipc2IAS(data[4]),
-                                         Handler.fsuipc2Degrees(data[5]),
-                                         Handler.fsuipc2Degrees(data[6]),
-                                         Handler.fsuipc2PositiveDegrees(data[7]))
-        else:
-            self._addFlareRate(data[2])
-
-    def _handleZFW(self, data, callback):
-        """Callback for a ZFW retrieval request."""
-        zfw = data[0] * const.LBSTOKG / 256.0
-        callback(zfw)
-                                                  
-    def _handleTime(self, data, callback):
-        """Callback for a time retrieval request."""
-        callback(Simulator._getTimestamp(data))
-                                                  
-#------------------------------------------------------------------------------
-
-class AircraftModel(object):
-    """Base class for the aircraft models.
-
-    Aircraft models handle the data arriving from FSUIPC and turn it into an
-    object describing the aircraft's state."""
-    monitoringData = [("paused", 0x0264, "H"),
-                      ("frozen", 0x3364, "H"),
-                      ("replay", 0x0628, "d"),
-                      ("slew", 0x05dc, "H"),
-                      ("overspeed", 0x036d, "b"),
-                      ("stalled", 0x036c, "b"),
-                      ("onTheGround", 0x0366, "H"),
-                      ("zfw", 0x3bfc, "d"),
-                      ("grossWeight", 0x30c0, "f"),
-                      ("heading", 0x0580, "d"),
-                      ("pitch", 0x0578, "d"),
-                      ("bank", 0x057c, "d"),
-                      ("ias", 0x02bc, "d"),
-                      ("mach", 0x11c6, "H"),
-                      ("groundSpeed", 0x02b4, "d"),
-                      ("vs", 0x02c8, "d"),
-                      ("radioAltitude", 0x31e4, "d"),
-                      ("altitude", 0x0570, "l"),
-                      ("gLoad", 0x11ba, "H"),
-                      ("flapsControl", 0x0bdc, "d"),
-                      ("flapsLeft", 0x0be0, "d"),
-                      ("flapsRight", 0x0be4, "d"),
-                      ("lights", 0x0d0c, "H"),
-                      ("pitot", 0x029c, "b"),
-                      ("parking", 0x0bc8, "H"),
-                      ("noseGear", 0x0bec, "d"),
-                      ("spoilersArmed", 0x0bcc, "d"),
-                      ("spoilers", 0x0bd0, "d"),
-                      ("altimeter", 0x0330, "H"),
-                      ("nav1", 0x0350, "H"),
-                      ("nav2", 0x0352, "H"),
-                      ("squawk", 0x0354, "H"),
-                      ("windSpeed", 0x0e90, "H"),
-                      ("windDirection", 0x0e92, "H")]
-
-
-    specialModels = []
-
-    @staticmethod
-    def registerSpecial(clazz):
-        """Register the given class as a special model."""
-        AircraftModel.specialModels.append(clazz)
-
-    @staticmethod
-    def findSpecial(aircraft, aircraftName):
-        for specialModel in AircraftModel.specialModels:
-            if specialModel.doesHandle(aircraft, aircraftName):
-                return specialModel
-        return None
-
-    @staticmethod
-    def create(aircraft, aircraftName):
-        """Create the model for the given aircraft name, and notify the
-        aircraft about it."""
-        specialModel = AircraftModel.findSpecial(aircraft, aircraftName)
-        if specialModel is not None:
-            return specialModel()
-        if aircraft.type in _genericModels:
-            return _genericModels[aircraft.type]()
-        else:
-            return GenericModel()
-
-    @staticmethod
-    def convertBCD(data, length):
-        """Convert a data item encoded as BCD into a string of the given number
-        of digits."""
-        bcd = ""
-        for i in range(0, length):
-            digit = chr(ord('0') + (data&0x0f))
-            data >>= 4
-            bcd = digit + bcd
-        return bcd
-
-    @staticmethod
-    def convertFrequency(data):
-        """Convert the given frequency data to a string."""
-        bcd = AircraftModel.convertBCD(data, 4)
-        return "1" + bcd[0:2] + "." + bcd[2:4]
-
-    def __init__(self, flapsNotches):
-        """Construct the aircraft model.
-        
-        flapsNotches is a list of degrees of flaps that are available on the aircraft."""
-        self._flapsNotches = flapsNotches
-    
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic"
-    
-    def doesHandle(self, aircraft, aircraftName):
-        """Determine if the model handles the given aircraft name.
-        
-        This default implementation returns False."""
-        return False
-
-    def _addOffsetWithIndexMember(self, dest, offset, type, attrName = None):
-        """Add the given FSUIPC offset and type to the given array and a member
-        attribute with the given name."""        
-        dest.append((offset, type))
-        if attrName is not None:
-            setattr(self, attrName, len(dest)-1)
-
-    def _addDataWithIndexMembers(self, dest, prefix, data):
-        """Add FSUIPC data to the given array and also corresponding index
-        member variables with the given prefix.
-
-        data is a list of triplets of the following items:
-        - the name of the data item. The index member variable will have a name
-        created by prepending the given prefix to this name.
-        - the FSUIPC offset
-        - the FSUIPC type
-        
-        The latter two items will be appended to dest."""
-        for (name, offset, type) in data:
-            self._addOffsetWithIndexMember(dest, offset, type, prefix + name)
-            
-    def addMonitoringData(self, data):
-        """Add the model-specific monitoring data to the given array."""
-        self._addDataWithIndexMembers(data, "_monidx_",
-                                      AircraftModel.monitoringData)
-    
-    def getAircraftState(self, aircraft, timestamp, data):
-        """Get an aircraft state object for the given monitoring data."""
-        state = fs.AircraftState()
-        
-        state.timestamp = timestamp
-        
-        state.paused = data[self._monidx_paused]!=0 or \
-            data[self._monidx_frozen]!=0 or \
-            data[self._monidx_replay]!=0
-        state.trickMode = data[self._monidx_slew]!=0
-
-        state.overspeed = data[self._monidx_overspeed]!=0
-        state.stalled = data[self._monidx_stalled]!=0
-        state.onTheGround = data[self._monidx_onTheGround]!=0
-
-        state.zfw = data[self._monidx_zfw] * const.LBSTOKG / 256.0
-        state.grossWeight = data[self._monidx_grossWeight] * const.LBSTOKG
-        
-        state.heading = Handler.fsuipc2PositiveDegrees(data[self._monidx_heading])
-
-        state.pitch = Handler.fsuipc2Degrees(data[self._monidx_pitch])
-        state.bank = Handler.fsuipc2Degrees(data[self._monidx_bank])
-
-        state.ias = Handler.fsuipc2IAS(data[self._monidx_ias])
-        state.mach = data[self._monidx_mach] / 20480.0
-        state.groundSpeed = data[self._monidx_groundSpeed]* 3600.0/65536.0/1852.0
-        state.vs = Handler.fsuipc2VS(data[self._monidx_vs])
-
-        state.radioAltitude = \
-            Handler.fsuipc2radioAltitude(data[self._monidx_radioAltitude])
-        state.altitude = data[self._monidx_altitude]/const.FEETTOMETRES/65536.0/65536.0
-
-        state.gLoad = data[self._monidx_gLoad] / 625.0
-        
-        numNotchesM1 = len(self._flapsNotches) - 1
-        flapsIncrement = 16383 / numNotchesM1
-        flapsControl = data[self._monidx_flapsControl]
-        flapsIndex = flapsControl / flapsIncrement
-        if flapsIndex < numNotchesM1:
-            if (flapsControl - (flapsIndex*flapsIncrement) >
-                (flapsIndex+1)*flapsIncrement - flapsControl):
-                flapsIndex += 1
-        state.flapsSet = self._flapsNotches[flapsIndex]
-        
-        flapsLeft = data[self._monidx_flapsLeft]
-        state.flaps = self._flapsNotches[-1]*flapsLeft/16383.0
-        
-        lights = data[self._monidx_lights]
-        
-        state.navLightsOn = (lights&0x01) != 0
-        state.antiCollisionLightsOn = (lights&0x02) != 0
-        state.landingLightsOn = (lights&0x04) != 0
-        state.strobeLightsOn = (lights&0x10) != 0
-        
-        state.pitotHeatOn = data[self._monidx_pitot]!=0
-
-        state.parking = data[self._monidx_parking]!=0
-
-        state.gearsDown = data[self._monidx_noseGear]==16383
-
-        state.spoilersArmed = data[self._monidx_spoilersArmed]!=0
-        
-        spoilers = data[self._monidx_spoilers]
-        if spoilers<=4800:
-            state.spoilersExtension = 0.0
-        else:
-            state.spoilersExtension = (spoilers - 4800) * 100.0 / (16383 - 4800)
-
-        state.altimeter = data[self._monidx_altimeter] / 16.0
-           
-        state.nav1 = AircraftModel.convertFrequency(data[self._monidx_nav1])
-        state.nav2 = AircraftModel.convertFrequency(data[self._monidx_nav2])
-        state.squawk = AircraftModel.convertBCD(data[self._monidx_squawk], 4)
-
-        state.windSpeed = data[self._monidx_windSpeed]
-        state.windDirection = data[self._monidx_windDirection]*360.0/65536.0
-        if state.windDirection<0.0: state.windDirection += 360.0
-        
-        return state
-
-#------------------------------------------------------------------------------
-
-class GenericAircraftModel(AircraftModel):
-    """A generic aircraft model that can handle the fuel levels, the N1 or RPM
-    values and some other common parameters in a generic way."""
-    def __init__(self, flapsNotches, fuelInfo, numEngines, isN1 = True):
-        """Construct the generic aircraft model with the given data.
-
-        flapsNotches is an array of how much degrees the individual flaps
-        notches mean.
-
-        fuelInfo is an array of FSUIPC offsets for the levels of the fuel
-        tanks. It is assumed to be a 4-byte value, followed by another 4-byte
-        value, which is the fuel tank capacity.
-
-        numEngines is the number of engines the aircraft has.
-
-        isN1 determines if the engines have an N1 value or an RPM value
-        (e.g. pistons)."""
-        super(GenericAircraftModel, self).__init__(flapsNotches = flapsNotches)
-
-        self._fuelInfo = fuelInfo
-        self._fuelStartIndex = None
-        self._numEngines = numEngines
-        self._engineStartIndex = None
-        self._isN1 = isN1
-
-    def doesHandle(self, aircraft, aircraftName):
-        """Determine if the model handles the given aircraft name.
-        
-        This implementation returns True."""
-        return True
-
-    def addMonitoringData(self, data):
-        """Add the model-specific monitoring data to the given array."""
-        super(GenericAircraftModel, self).addMonitoringData(data)
-        
-        self._addOffsetWithIndexMember(data, 0x0af4, "H", "_monidx_fuelWeight")
-
-        self._fuelStartIndex = len(data)
-        for offset in self._fuelInfo:
-            self._addOffsetWithIndexMember(data, offset, "u")    # tank level
-            self._addOffsetWithIndexMember(data, offset+4, "u")  # tank capacity
-
-        if self._isN1:
-            self._engineStartIndex = len(data)
-            for i in range(0, self._numEngines):
-                self._addOffsetWithIndexMember(data, 0x2000 + i * 0x100, "f")  # N1
-                self._addOffsetWithIndexMember(data, 0x088c + i * 0x98, "h")  # throttle lever
-        
-    def getAircraftState(self, aircraft, timestamp, data):
-        """Get the aircraft state.
-
-        Get it from the parent, and then add the data about the fuel levels and 
-        the engine parameters."""
-        state = super(GenericAircraftModel, self).getAircraftState(aircraft,
-                                                                   timestamp,
-                                                                   data)
-
-        fuelWeight = data[self._monidx_fuelWeight]/256.0
-        state.fuel = []
-        for i in range(self._fuelStartIndex, 
-                       self._fuelStartIndex + 2*len(self._fuelInfo), 2):
-            fuel = data[i+1]*data[i]*fuelWeight*const.LBSTOKG/128.0/65536.0
-            state.fuel.append(fuel)
-
-        state.n1 = []
-        state.reverser = []
-        for i in range(self._engineStartIndex,
-                       self._engineStartIndex + 2*self._numEngines, 2):
-            state.n1.append(data[i])
-            state.reverser.append(data[i+1]<0)
-
-        return state
-
-#------------------------------------------------------------------------------
-
-class GenericModel(GenericAircraftModel):
-    """Generic aircraft model for an unknown type."""
-    def __init__(self):
-        """Construct the model."""
-        super(GenericModel, self). \
-            __init__(flapsNotches = [0, 10, 20, 30],
-                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic"    
-
-#------------------------------------------------------------------------------
-
-class B737Model(GenericAircraftModel):
-    """Generic model for the Boeing 737 Classing and NG aircraft."""
-    def __init__(self):
-        """Construct the model."""
-        super(B737Model, self). \
-            __init__(flapsNotches = [0, 1, 2, 5, 10, 15, 25, 30, 40],
-                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic Boeing 737"
-
-#------------------------------------------------------------------------------
-
-class PMDGBoeing737NGModel(B737Model):
-    """A model handler for the PMDG Boeing 737NG model."""
-    @staticmethod
-    def doesHandle(aircraft, (name, airPath)):
-        """Determine if this model handler handles the aircraft with the given
-        name."""
-        return aircraft.type in [const.AIRCRAFT_B736,
-                                 const.AIRCRAFT_B737,
-                                 const.AIRCRAFT_B738] and \
-            (name.find("PMDG")!=-1 or airPath.find("PMDG")!=-1) and \
-            (name.find("737")!=-1 or airPath.find("737")!=-1) and \
-            (name.find("600")!=-1 or airPath.find("600")!=-1 or \
-             name.find("700")!=-1 or airPath.find("700")!=-1 or \
-             name.find("800")!=-1 or airPath.find("800")!=-1 or \
-             name.find("900")!=-1 or airPath.find("900")!=-1)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/PMDG Boeing 737NG"
-
-    def addMonitoringData(self, data):
-        """Add the model-specific monitoring data to the given array."""
-        super(PMDGBoeing737NGModel, self).addMonitoringData(data)
-                
-        self._addOffsetWithIndexMember(data, 0x6202, "b", "_pmdgidx_switches")
-
-    def getAircraftState(self, aircraft, timestamp, data):
-        """Get the aircraft state.
-
-        Get it from the parent, and then check some PMDG-specific stuff."""
-        state = super(PMDGBoeing737NGModel, self).getAircraftState(aircraft,
-                                                                   timestamp,
-                                                                   data)
-        if data[self._pmdgidx_switches]&0x01==0x01:
-            state.altimeter = 1013.25
-
-        return state
-
-#------------------------------------------------------------------------------
-
-class B767Model(GenericAircraftModel):
-    """Generic model for the Boeing 767 aircraft."""
-    def __init__(self):
-        """Construct the model."""
-        super(B767Model, self). \
-            __init__(flapsNotches = [0, 1, 5, 15, 20, 25, 30],
-                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic Boeing 767"
-
-#------------------------------------------------------------------------------
-
-class DH8DModel(GenericAircraftModel):
-    """Generic model for the Bombardier  Dash 8-Q400 aircraft."""
-    def __init__(self):
-        """Construct the model."""
-        super(DH8DModel, self). \
-            __init__(flapsNotches = [0, 5, 10, 15, 35],
-                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic Bombardier Dash 8-Q400"
-
-#------------------------------------------------------------------------------
-
-class DreamwingsDH8DModel(DH8DModel):
-    """Model handler for the Dreamwings Dash 8-Q400."""
-    @staticmethod
-    def doesHandle(aircraft, (name, airPath)):
-        """Determine if this model handler handles the aircraft with the given
-        name."""
-        return aircraft.type==const.AIRCRAFT_DH8D and \
-            (name.find("Dreamwings")!=-1 or airPath.find("Dreamwings")!=-1) and \
-            (name.find("Dash")!=-1 or airPath.find("Dash")!=-1) and \
-            (name.find("Q400")!=-1 or airPath.find("Q400")!=-1) and \
-            airPath.find("Dash8Q400")!=-1
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Dreamwings Bombardier Dash 8-Q400"
-
-    def getAircraftState(self, aircraft, timestamp, data):
-        """Get the aircraft state.
-
-        Get it from the parent, and then invert the pitot heat state."""
-        state = super(DreamwingsDH8DModel, self).getAircraftState(aircraft,
-                                                                  timestamp,
-                                                                  data)
-        state.pitotHeatOn = not state.pitotHeatOn
-
-        return state
-#------------------------------------------------------------------------------
-
-class CRJ2Model(GenericAircraftModel):
-    """Generic model for the Bombardier CRJ-200 aircraft."""
-    def __init__(self):
-        """Construct the model."""
-        super(CRJ2Model, self). \
-            __init__(flapsNotches = [0, 8, 20, 30, 45],
-                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic Bombardier CRJ-200"
-
-#------------------------------------------------------------------------------
-
-class F70Model(GenericAircraftModel):
-    """Generic model for the Fokker F70 aircraft."""
-    def __init__(self):
-        """Construct the model."""
-        super(F70Model, self). \
-            __init__(flapsNotches = [0, 8, 15, 25, 42],
-                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic Fokker 70"
-
-#------------------------------------------------------------------------------
-
-class DC3Model(GenericAircraftModel):
-    """Generic model for the Lisunov Li-2 (DC-3) aircraft."""
-    def __init__(self):
-        """Construct the model."""
-        super(DC3Model, self). \
-            __init__(flapsNotches = [0, 15, 30, 45],
-                     fuelInfo = [0x0b7c, 0x0b84, 0x0b94, 0x0b9c], 
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic Lisunov Li-2"
-
-#------------------------------------------------------------------------------
-
-class T134Model(GenericAircraftModel):
-    """Generic model for the Tupolev Tu-134 aircraft."""
-    def __init__(self):
-        """Construct the model."""
-        super(T134Model, self). \
-            __init__(flapsNotches = [0, 10, 20, 30],
-                     fuelInfo = [0x0b74, 
-                                 0x0b8c, 0x0b84, 
-                                 0x0ba4, 0x0b9c,
-                                 0x1254, 0x125c], 
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic Tupolev Tu-134"
-
-#------------------------------------------------------------------------------
-
-class T154Model(GenericAircraftModel):
-    """Generic model for the Tupolev Tu-134 aircraft."""
-    def __init__(self):
-        """Construct the model."""
-        super(T154Model, self). \
-            __init__(flapsNotches = [0, 15, 28, 45],
-                     fuelInfo = [0x0b74, 0x0b7c, 0x0b94, 
-                                 0x1244, 0x0b84, 0x0b9c],
-                     numEngines = 3)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic Tupolev Tu-154"
-
-    def getAircraftState(self, aircraft, timestamp, data):
-        """Get an aircraft state object for the given monitoring data.
-
-        This removes the reverser value for the middle engine."""
-        state = super(T154Model, self).getAircraftState(aircraft, timestamp, data)
-        del state.reverser[1]
-        return state
-
-#------------------------------------------------------------------------------
-
-class YK40Model(GenericAircraftModel):
-    """Generic model for the Yakovlev Yak-40 aircraft."""
-    def __init__(self):
-        """Construct the model."""
-        super(YK40Model, self). \
-            __init__(flapsNotches = [0, 20, 35],
-                     fuelInfo = [0x0b7c, 0x0b94],
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic Yakovlev Yak-40"
-
-#------------------------------------------------------------------------------
-
-_genericModels = { const.AIRCRAFT_B736 : B737Model,
-                   const.AIRCRAFT_B737 : B737Model,
-                   const.AIRCRAFT_B738 : B737Model,
-                   const.AIRCRAFT_B733 : B737Model,
-                   const.AIRCRAFT_B734 : B737Model,
-                   const.AIRCRAFT_B735 : B737Model,
-                   const.AIRCRAFT_DH8D : DH8DModel,
-                   const.AIRCRAFT_B762 : B767Model,
-                   const.AIRCRAFT_B763 : B767Model,
-                   const.AIRCRAFT_CRJ2 : B767Model,
-                   const.AIRCRAFT_F70  : F70Model,
-                   const.AIRCRAFT_DC3  : DC3Model,
-                   const.AIRCRAFT_T134 : T134Model,
-                   const.AIRCRAFT_T154 : T154Model,
-                   const.AIRCRAFT_YK40 : YK40Model }
-
-#------------------------------------------------------------------------------
-
-AircraftModel.registerSpecial(PMDGBoeing737NGModel)
-AircraftModel.registerSpecial(DreamwingsDH8DModel)
-
-#------------------------------------------------------------------------------
Index: src/mlx/gui/common.py
===================================================================
--- src/mlx/gui/common.py	(revision 76)
+++ src/mlx/gui/common.py	(revision 76)
@@ -0,0 +1,79 @@
+# Common things for the GUI
+
+import os
+
+appIndicator = False
+
+if os.name=="nt" or "FORCE_PYGTK" in os.environ:
+    print "Using PyGTK"
+    pygobject = False
+    import pygtk
+    import gtk.gdk as gdk
+    import gtk
+    import gobject
+    import pango
+    try:
+        import appindicator
+        appIndicator = True
+    except Exception, e:
+        pass
+
+    MESSAGETYPE_ERROR = gtk.MESSAGE_ERROR
+    MESSAGETYPE_QUESTION = gtk.MESSAGE_QUESTION
+    BUTTONSTYPE_OK = gtk.BUTTONS_OK
+    BUTTONSTYPE_YES_NO = gtk.BUTTONS_YES_NO
+    RESPONSETYPE_YES = gtk.RESPONSE_YES
+else:
+    print "Using PyGObject"
+    pygobject = True
+    from gi.repository import Gdk as gdk
+    from gi.repository import Gtk as gtk
+    from gi.repository import GObject as gobject
+    from gi.repository import AppIndicator3 as appindicator
+    from gi.repository import Pango as pango
+    appIndicator = True
+    
+    MESSAGETYPE_ERROR = gtk.MessageType.ERROR
+    MESSAGETYPE_QUESTION = gtk.MessageType.QUESTION
+    BUTTONSTYPE_OK = gtk.ButtonsType.OK
+    BUTTONSTYPE_YES_NO = gtk.ButtonsType.YES_NO
+    RESPONSETYPE_YES = gtk.ResponseType.YES
+
+import cairo
+
+#------------------------------------------------------------------------------
+
+class FlightStatusHandler(object):
+    """Base class for objects that handle the flight status in some way."""
+    def __init__(self):
+        self._stage = None
+        self._rating = 100
+        self._noGoReason = None
+
+    def resetFlightStatus(self):
+        """Reset the flight status."""
+        self._stage = None
+        self._rating = 100
+        self._noGoReason = None
+        self._updateFlightStatus()
+        
+    def setStage(self, stage):
+        """Set the stage of the flight."""
+        if stage!=self._stage:
+            self._stage = stage
+            self._updateFlightStatus()
+
+    def setRating(self, rating):
+        """Set the rating to the given value."""
+        if rating!=self._rating:
+            self._rating = rating
+            if self._noGoReason is None:
+                self._updateFlightStatus()
+
+    def setNoGo(self, reason):
+        """Set a No-Go condition with the given reason."""
+        if self._noGoReason is None:
+            self._noGoReason = reason
+            self._updateFlightStatus()
+
+#------------------------------------------------------------------------------
Index: src/mlx/gui/common.py
===================================================================
--- src/mlx/gui/common.py	(revision 76)
+++ 	(revision )
@@ -1,79 +1,0 @@
-# Common things for the GUI
-
-import os
-
-appIndicator = False
-
-if os.name=="nt" or "FORCE_PYGTK" in os.environ:
-    print "Using PyGTK"
-    pygobject = False
-    import pygtk
-    import gtk.gdk as gdk
-    import gtk
-    import gobject
-    import pango
-    try:
-        import appindicator
-        appIndicator = True
-    except Exception, e:
-        pass
-
-    MESSAGETYPE_ERROR = gtk.MESSAGE_ERROR
-    MESSAGETYPE_QUESTION = gtk.MESSAGE_QUESTION
-    BUTTONSTYPE_OK = gtk.BUTTONS_OK
-    BUTTONSTYPE_YES_NO = gtk.BUTTONS_YES_NO
-    RESPONSETYPE_YES = gtk.RESPONSE_YES
-else:
-    print "Using PyGObject"
-    pygobject = True
-    from gi.repository import Gdk as gdk
-    from gi.repository import Gtk as gtk
-    from gi.repository import GObject as gobject
-    from gi.repository import AppIndicator3 as appindicator
-    from gi.repository import Pango as pango
-    appIndicator = True
-    
-    MESSAGETYPE_ERROR = gtk.MessageType.ERROR
-    MESSAGETYPE_QUESTION = gtk.MessageType.QUESTION
-    BUTTONSTYPE_OK = gtk.ButtonsType.OK
-    BUTTONSTYPE_YES_NO = gtk.ButtonsType.YES_NO
-    RESPONSETYPE_YES = gtk.ResponseType.YES
-
-import cairo
-
-#------------------------------------------------------------------------------
-
-class FlightStatusHandler(object):
-    """Base class for objects that handle the flight status in some way."""
-    def __init__(self):
-        self._stage = None
-        self._rating = 100
-        self._noGoReason = None
-
-    def resetFlightStatus(self):
-        """Reset the flight status."""
-        self._stage = None
-        self._rating = 100
-        self._noGoReason = None
-        self._updateFlightStatus()
-        
-    def setStage(self, stage):
-        """Set the stage of the flight."""
-        if stage!=self._stage:
-            self._stage = stage
-            self._updateFlightStatus()
-
-    def setRating(self, rating):
-        """Set the rating to the given value."""
-        if rating!=self._rating:
-            self._rating = rating
-            if self._noGoReason is None:
-                self._updateFlightStatus()
-
-    def setNoGo(self, reason):
-        """Set a No-Go condition with the given reason."""
-        if self._noGoReason is None:
-            self._noGoReason = reason
-            self._updateFlightStatus()
-
-#------------------------------------------------------------------------------
Index: src/mlx/gui/flight.py
===================================================================
--- src/mlx/gui/flight.py	(revision 78)
+++ src/mlx/gui/flight.py	(revision 78)
@@ -0,0 +1,1751 @@
+# The flight handling "wizard"
+
+from mlx.gui.common import *
+
+import mlx.const as const
+import mlx.fs as fs
+from mlx.checks import PayloadChecker
+
+import datetime
+import time
+
+#------------------------------------------------------------------------------
+
+acftTypeNames = { const.AIRCRAFT_B736: "Boeing 737-600",
+                  const.AIRCRAFT_B737: "Boeing 737-700",
+                  const.AIRCRAFT_B738: "Boeing 737-800",
+                  const.AIRCRAFT_DH8D: "Bombardier Dash 8-Q400",
+                  const.AIRCRAFT_B733: "Boeing 737-300",
+                  const.AIRCRAFT_B734: "Boeing 737-400",
+                  const.AIRCRAFT_B735: "Boeing 737-500",
+                  const.AIRCRAFT_B762: "Boeing 767-200",
+                  const.AIRCRAFT_B763: "Boeing 767-300",
+                  const.AIRCRAFT_CRJ2: "Bombardier CRJ200",
+                  const.AIRCRAFT_F70:  "Fokker 70",
+                  const.AIRCRAFT_DC3:  "Lisunov Li-2",
+                  const.AIRCRAFT_T134: "Tupolev Tu-134",
+                  const.AIRCRAFT_T154: "Tupolev Tu-154",
+                  const.AIRCRAFT_YK40: "Yakovlev Yak-40" }
+
+#-----------------------------------------------------------------------------
+
+class Page(gtk.Alignment):
+    """A page in the flight wizard."""
+    def __init__(self, wizard, title, help):
+        """Construct the page."""
+        super(Page, self).__init__(xalign = 0.0, yalign = 0.0,
+                                   xscale = 1.0, yscale = 1.0)
+        self.set_padding(padding_top = 4, padding_bottom = 4,
+                         padding_left = 12, padding_right = 12)
+
+        frame = gtk.Frame()
+        self.add(frame)
+
+        style = self.get_style() if pygobject else self.rc_get_style()
+
+        self._vbox = gtk.VBox()
+        self._vbox.set_homogeneous(False)
+        frame.add(self._vbox)
+
+        eventBox = gtk.EventBox()
+        eventBox.modify_bg(0, style.bg[3])
+
+        alignment = gtk.Alignment(xalign = 0.0, xscale = 0.0)
+        
+        label = gtk.Label(title)
+        label.modify_fg(0, style.fg[3])
+        label.modify_font(pango.FontDescription("bold 24"))
+        alignment.set_padding(padding_top = 4, padding_bottom = 4,
+                              padding_left = 6, padding_right = 0)
+                              
+        alignment.add(label)
+        eventBox.add(alignment)
+        
+        self._vbox.pack_start(eventBox, False, False, 0)
+
+        table = gtk.Table(3, 1)
+        table.set_homogeneous(False)
+
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                                  xscale = 1.0, yscale = 1.0)        
+        alignment.set_padding(padding_top = 16, padding_bottom = 16,
+                              padding_left = 16, padding_right = 16)
+        alignment.add(table)
+        self._vbox.pack_start(alignment, True, True, 0)
+        
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.0,
+                                  xscale = 0, yscale = 0.0)
+        alignment.set_padding(padding_top = 0, padding_bottom = 16,
+                              padding_left = 0, padding_right = 0)
+
+        label = gtk.Label(help)
+        label.set_justify(gtk.Justification.CENTER if pygobject
+                          else gtk.JUSTIFY_CENTER)
+        label.set_use_markup(True)
+        alignment.add(label)
+        table.attach(alignment, 0, 1, 0, 1)
+
+        self._mainAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                            xscale = 1.0, yscale = 1.0)
+        table.attach(self._mainAlignment, 0, 1, 1, 3)
+                                            
+        buttonAlignment =  gtk.Alignment(xalign = 1.0, xscale=0.0, yscale = 0.0)
+        buttonAlignment.set_padding(padding_top = 4, padding_bottom = 10,
+                                    padding_left = 16, padding_right = 16)
+
+        self._buttonBox = gtk.HBox()
+        self._buttonBox.set_homogeneous(False)
+        self._defaultButton = None
+        buttonAlignment.add(self._buttonBox)
+
+        self._vbox.pack_start(buttonAlignment, False, False, 0)
+
+        self._wizard = wizard
+
+        self._finalized = False
+        self._fromPage = None
+
+    def setMainWidget(self, widget):
+        """Set the given widget as the main one."""
+        self._mainAlignment.add(widget)
+
+    def addButton(self, label, default = False):
+        """Add a button with the given label.
+
+        Return the button object created."""
+        button = gtk.Button(label)
+        self._buttonBox.pack_start(button, False, False, 4)
+        button.set_use_underline(True)
+        if default:
+            button.set_can_default(True)
+            self._defaultButton = button
+        return button
+
+    def activate(self):
+        """Called when this page becomes active.
+
+        This default implementation does nothing."""
+        pass
+
+    def finalize(self):
+        """Called when the page is finalized."""
+        pass
+
+    def grabDefault(self):
+        """If the page has a default button, make it the default one."""
+        if self._defaultButton is not None:
+            self._defaultButton.grab_default()
+
+    def reset(self):
+        """Reset the page if the wizard is reset."""
+        self._finalized = False
+        self._fromPage = None
+
+    def goBack(self):
+        """Go to the page we were invoked from."""
+        assert self._fromPage is not None
+        
+        self._wizard.setCurrentPage(self._fromPage, finalize = False)
+    
+#-----------------------------------------------------------------------------
+
+class LoginPage(Page):
+    """The login page."""
+    def __init__(self, wizard):
+        """Construct the login page."""
+        help = "Enter your MAVA pilot's ID and password to\n" \
+        "log in to the MAVA website and download\n" \
+        "your booked flights."
+        super(LoginPage, self).__init__(wizard, "Login", help)
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        
+        table = gtk.Table(2, 3)
+        table.set_row_spacings(4)
+        table.set_col_spacings(32)
+        alignment.add(table)
+        self.setMainWidget(alignment)
+
+        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
+        label = gtk.Label("Pilot _ID:")
+        label.set_use_underline(True)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, 0, 1)
+
+        self._pilotID = gtk.Entry()
+        self._pilotID.connect("changed", self._setLoginButton)
+        self._pilotID.set_tooltip_text("Enter your MAVA pilot's ID. This "
+                                       "usually starts with a "
+                                       "'P' followed by 3 digits.")
+        table.attach(self._pilotID, 1, 2, 0, 1)
+        label.set_mnemonic_widget(self._pilotID)
+
+        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
+        label = gtk.Label("_Password:")
+        label.set_use_underline(True)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, 1, 2)
+
+        self._password = gtk.Entry()
+        self._password.set_visibility(False)
+        self._password.connect("changed", self._setLoginButton)
+        self._password.set_tooltip_text("Enter the password for your pilot's ID")
+        table.attach(self._password, 1, 2, 1, 2)
+        label.set_mnemonic_widget(self._password)
+
+        self._rememberButton = gtk.CheckButton("_Remember password")
+        self._rememberButton.set_use_underline(True)
+        self._rememberButton.set_tooltip_text("If checked, your password will "
+                                              "be stored, so that you should "
+                                              "not have to enter it every time. "
+                                              "Note, however, that the password "
+                                              "is stored as text, and anybody "
+                                              "who can access your files will "
+                                              "be able to read it.")
+        table.attach(self._rememberButton, 1, 2, 2, 3, ypadding = 8)
+
+        self._loginButton = self.addButton("_Login", default = True)
+        self._loginButton.set_sensitive(False)
+        self._loginButton.connect("clicked", self._loginClicked)
+        self._loginButton.set_tooltip_text("Click to log in.")
+        
+        config = self._wizard.gui.config
+        self._pilotID.set_text(config.pilotID)
+        self._password.set_text(config.password)
+        self._rememberButton.set_active(config.rememberPassword)
+
+    def _setLoginButton(self, entry):
+        """Set the login button's sensitivity.
+
+        The button is sensitive only if both the pilot ID and the password
+        fields contain values."""
+        self._loginButton.set_sensitive(self._pilotID.get_text()!="" and
+                                        self._password.get_text()!="")
+
+    def _loginClicked(self, button):
+        """Called when the login button was clicked."""
+        self._loginButton.set_sensitive(False)
+        gui = self._wizard.gui
+        gui.beginBusy("Logging in...")
+        gui.webHandler.login(self._loginResultCallback,
+                            self._pilotID.get_text(),
+                            self._password.get_text())
+
+    def _loginResultCallback(self, returned, result):
+        """The login result callback, called in the web handler's thread."""
+        gobject.idle_add(self._handleLoginResult, returned, result)
+
+    def _handleLoginResult(self, returned, result):
+        """Handle the login result."""
+        self._wizard.gui.endBusy()
+        self._loginButton.set_sensitive(True)
+        if returned:
+            if result.loggedIn:
+                config = self._wizard.gui.config
+
+                config.pilotID = self._pilotID.get_text()
+
+                rememberPassword = self._rememberButton.get_active()                
+                config.password = self._password.get_text() if rememberPassword  \
+                                  else ""
+
+                config.rememberPassword = rememberPassword
+                
+                config.save()
+                self._wizard._loginResult = result
+                self._wizard.nextPage()
+            else:
+                dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
+                                           buttons = BUTTONSTYPE_OK,
+                                           message_format =
+                                           "Invalid pilot's ID or password.")
+                dialog.format_secondary_markup("Check the ID and try to reenter"
+                                               " the password.")
+                dialog.run()
+                dialog.hide()
+        else:
+            dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
+                                       buttons = BUTTONSTYPE_OK,
+                                       message_format = 
+                                       "Failed to connect to the MAVA website.")
+            dialog.format_secondary_markup("Try again in a few minutes.")
+            dialog.run()
+            dialog.hide()
+
+#-----------------------------------------------------------------------------
+
+class FlightSelectionPage(Page):
+    """The page to select the flight."""
+    def __init__(self, wizard):
+        """Construct the flight selection page."""
+        super(FlightSelectionPage, self).__init__(wizard, "Flight selection",
+                                                  "Select the flight you want "
+                                                  "to perform.")
+
+
+        self._listStore = gtk.ListStore(str, str, str, str)
+        self._flightList = gtk.TreeView(self._listStore)
+        column = gtk.TreeViewColumn("Flight no.", gtk.CellRendererText(),
+                                    text = 1)
+        column.set_expand(True)
+        self._flightList.append_column(column)
+        column = gtk.TreeViewColumn("Departure time [UTC]", gtk.CellRendererText(),
+                                    text = 0)
+        column.set_expand(True)
+        self._flightList.append_column(column)
+        column = gtk.TreeViewColumn("From", gtk.CellRendererText(),
+                                    text = 2)
+        column.set_expand(True)
+        self._flightList.append_column(column)
+        column = gtk.TreeViewColumn("To", gtk.CellRendererText(),
+                                    text = 3)
+        column.set_expand(True)
+        self._flightList.append_column(column)
+
+        flightSelection = self._flightList.get_selection()
+        flightSelection.connect("changed", self._selectionChanged)
+
+        scrolledWindow = gtk.ScrolledWindow()
+        scrolledWindow.add(self._flightList)
+        scrolledWindow.set_size_request(400, -1)
+        scrolledWindow.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
+                                  else gtk.POLICY_AUTOMATIC,
+                                  gtk.PolicyType.AUTOMATIC if pygobject
+                                  else gtk.POLICY_AUTOMATIC)
+        scrolledWindow.set_shadow_type(gtk.ShadowType.IN if pygobject
+                                       else gtk.SHADOW_IN)
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.0, xscale = 0.0, yscale = 1.0)
+        alignment.add(scrolledWindow)
+
+        self.setMainWidget(alignment)
+
+        self._button = self.addButton(gtk.STOCK_GO_FORWARD, default = True)
+        self._button.set_use_stock(True)
+        self._button.set_sensitive(False)
+        self._button.connect("clicked", self._forwardClicked)
+
+    def activate(self):
+        """Fill the flight list."""
+        self._flightList.set_sensitive(True)
+        self._listStore.clear()
+        for flight in self._wizard.loginResult.flights:
+            self._listStore.append([str(flight.departureTime),
+                                    flight.callsign,
+                                    flight.departureICAO,
+                                    flight.arrivalICAO])
+
+    def finalize(self):
+        """Finalize the page."""
+        self._flightList.set_sensitive(False)
+
+    def _selectionChanged(self, selection):
+        """Called when the selection is changed."""
+        self._button.set_sensitive(selection.count_selected_rows()==1)
+
+    def _forwardClicked(self, button):
+        """Called when the forward button was clicked."""
+        if self._finalized:
+            self._wizard.jumpPage(self._nextDistance, finalize = False)
+        else:
+            selection = self._flightList.get_selection()
+            (listStore, iter) = selection.get_selected()
+            path = listStore.get_path(iter)
+            [index] = path.get_indices() if pygobject else path
+
+            flight = self._wizard.loginResult.flights[index]
+            self._wizard._bookedFlight = flight
+
+            self._updateDepartureGate()
+        
+    def _updateDepartureGate(self):
+        """Update the departure gate for the booked flight."""
+        flight = self._wizard._bookedFlight
+        if flight.departureICAO=="LHBP":
+            self._wizard._getFleet(self._fleetRetrieved)
+        else:
+            self._nextDistance = 2
+            self._wizard.jumpPage(2)
+
+    def _fleetRetrieved(self, fleet):
+        """Called when the fleet has been retrieved."""
+        if fleet is None:
+            self._nextDistance = 2
+            self._wizard.jumpPage(2)
+        else:
+            plane = fleet[self._wizard._bookedFlight.tailNumber]
+            if plane is None:
+                self._nextDistance = 2
+                self._wizard.jumpPage(2)
+            elif plane.gateNumber is not None and \
+                 not fleet.isGateConflicting(plane):
+                self._wizard._departureGate = plane.gateNumber
+                self._nextDistance = 2
+                self._wizard.jumpPage(2)
+            else:
+                self._nextDistance = 1
+                self._wizard.nextPage()
+        
+#-----------------------------------------------------------------------------
+
+class GateSelectionPage(Page):
+    """Page to select a free gate at LHBP.
+
+    This page should be displayed only if we have fleet information!."""
+    def __init__(self, wizard):
+        """Construct the gate selection page."""
+        help = "The airplane's gate position is invalid.\n\n" \
+               "Select the gate from which you\n" \
+               "would like to begin the flight."
+        super(GateSelectionPage, self).__init__(wizard,
+                                                "LHBP gate selection",
+                                                help)
+
+        self._listStore = gtk.ListStore(str)
+        self._gateList = gtk.TreeView(self._listStore)
+        column = gtk.TreeViewColumn(None, gtk.CellRendererText(),
+                                    text = 0)
+        column.set_expand(True)
+        self._gateList.append_column(column)
+        self._gateList.set_headers_visible(False)
+
+        gateSelection = self._gateList.get_selection()
+        gateSelection.connect("changed", self._selectionChanged)
+
+        scrolledWindow = gtk.ScrolledWindow()
+        scrolledWindow.add(self._gateList)
+        scrolledWindow.set_size_request(50, -1)
+        scrolledWindow.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
+                                  else gtk.POLICY_AUTOMATIC,
+                                  gtk.PolicyType.AUTOMATIC if pygobject
+                                  else gtk.POLICY_AUTOMATIC)
+        scrolledWindow.set_shadow_type(gtk.ShadowType.IN if pygobject
+                                       else gtk.SHADOW_IN)
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.0, xscale = 0.0, yscale = 1.0)
+        alignment.add(scrolledWindow)
+
+        self.setMainWidget(alignment)        
+
+        button = self.addButton(gtk.STOCK_GO_BACK)
+        button.set_use_stock(True)
+        button.connect("clicked", self._backClicked)
+        
+        self._button = self.addButton(gtk.STOCK_GO_FORWARD, default = True)
+        self._button.set_use_stock(True)
+        self._button.set_sensitive(False)
+        self._button.connect("clicked", self._forwardClicked)
+
+    def activate(self):
+        """Fill the gate list."""
+        self._listStore.clear()
+        self._gateList.set_sensitive(True)
+        occupiedGateNumbers = self._wizard._fleet.getOccupiedGateNumbers()
+        for gateNumber in const.lhbpGateNumbers:
+            if gateNumber not in occupiedGateNumbers:
+                self._listStore.append([gateNumber])
+
+    def finalize(self):
+        """Finalize the page."""
+        self._gateList.set_sensitive(False)
+
+    def _selectionChanged(self, selection):
+        """Called when the selection is changed."""
+        self._button.set_sensitive(selection.count_selected_rows()==1)
+
+    def _backClicked(self, button):
+        """Called when the Back button is pressed."""
+        self.goBack()
+
+    def _forwardClicked(self, button):
+        """Called when the forward button is clicked."""
+        if not self._finalized:
+            selection = self._gateList.get_selection()
+            (listStore, iter) = selection.get_selected()
+            (gateNumber,) = listStore.get(iter, 0)
+
+            self._wizard._departureGate = gateNumber
+
+            #self._wizard._updatePlane(self._planeUpdated,
+            #                          self._wizard._bookedFlight.tailNumber,
+            #                          const.PLANE_HOME,
+            #                          gateNumber)
+        
+        self._wizard.nextPage()
+
+    def _planeUpdated(self, success):
+        """Callback for the plane updating call."""
+        if success is None or success:
+            self._wizard.nextPage()
+        else:
+            dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
+                                       buttons = BUTTONSTYPE_OK,
+                                       message_format = "Gate conflict detected again")
+            dialog.format_secondary_markup("Try to select a different gate.")
+            dialog.run()
+            dialog.hide()
+
+            self._wizard._getFleet(self._fleetRetrieved)
+
+    def _fleetRetrieved(self, fleet):
+        """Called when the fleet has been retrieved."""
+        if fleet is None:
+            self._wizard.nextPage()
+        else:
+            self.activate()
+            
+#-----------------------------------------------------------------------------
+
+class ConnectPage(Page):
+    """Page which displays the departure airport and gate (if at LHBP)."""
+    def __init__(self, wizard):
+        """Construct the connect page."""
+        help = "Load the aircraft below into the simulator and park it\n" \
+               "at the given airport, at the gate below, if present.\n\n" \
+               "Then press the Connect button to connect to the simulator."
+        super(ConnectPage, self).__init__(wizard,
+                                          "Connect to the simulator",
+                                          help)
+        
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+
+        table = gtk.Table(5, 2)
+        table.set_row_spacings(4)
+        table.set_col_spacings(16)
+        table.set_homogeneous(True)
+        alignment.add(table)
+        self.setMainWidget(alignment)
+
+        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
+        label = gtk.Label("Flight number:")
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, 0, 1)
+
+        labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
+        self._flightNumber = gtk.Label()
+        self._flightNumber.set_width_chars(6)
+        self._flightNumber.set_alignment(0.0, 0.5)
+        labelAlignment.add(self._flightNumber)
+        table.attach(labelAlignment, 1, 2, 0, 1)
+
+        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
+        label = gtk.Label("Aircraft:")
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, 1, 2)
+
+        labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
+        self._aircraft = gtk.Label()
+        self._aircraft.set_width_chars(25)
+        self._aircraft.set_alignment(0.0, 0.5)
+        labelAlignment.add(self._aircraft)
+        table.attach(labelAlignment, 1, 2, 1, 2)
+
+        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
+        label = gtk.Label("Tail number:")
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, 2, 3)
+
+        labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
+        self._tailNumber = gtk.Label()
+        self._tailNumber.set_width_chars(6)
+        self._tailNumber.set_alignment(0.0, 0.5)
+        labelAlignment.add(self._tailNumber)
+        table.attach(labelAlignment, 1, 2, 2, 3)
+
+        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
+        label = gtk.Label("Airport:")
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, 3, 4)
+
+        labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
+        self._departureICAO = gtk.Label()
+        self._departureICAO.set_width_chars(5)
+        self._departureICAO.set_alignment(0.0, 0.5)
+        labelAlignment.add(self._departureICAO)
+        table.attach(labelAlignment, 1, 2, 3, 4)
+
+        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
+        label = gtk.Label("Gate:")
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, 4, 5)
+
+        labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
+        self._departureGate = gtk.Label()
+        self._departureGate.set_width_chars(5)
+        self._departureGate.set_alignment(0.0, 0.5)
+        labelAlignment.add(self._departureGate)
+        table.attach(labelAlignment, 1, 2, 4, 5)
+
+        button = self.addButton(gtk.STOCK_GO_BACK)
+        button.set_use_stock(True)
+        button.connect("clicked", self._backClicked)
+
+        self._button = self.addButton("_Connect", default = True)
+        self._button.set_use_underline(True)
+        self._clickedID = self._button.connect("clicked", self._connectClicked)
+
+    def activate(self):
+        """Setup the departure information."""
+        self._button.set_label("_Connect")
+        self._button.set_use_underline(True)
+        self._button.disconnect(self._clickedID)
+        self._clickedID = self._button.connect("clicked", self._connectClicked)
+
+        bookedFlight = self._wizard._bookedFlight
+
+        self._flightNumber.set_markup("<b>" + bookedFlight.callsign + "</b>")
+
+        aircraftType = acftTypeNames[bookedFlight.aircraftType]
+        self._aircraft.set_markup("<b>" + aircraftType + "</b>")
+        
+        self._tailNumber.set_markup("<b>" + bookedFlight.tailNumber + "</b>")
+
+        icao = bookedFlight.departureICAO
+        self._departureICAO.set_markup("<b>" + icao + "</b>")
+        gate = self._wizard._departureGate
+        if gate!="-":
+            gate = "<b>" + gate + "</b>"
+        self._departureGate.set_markup(gate)
+
+    def finalize(self):
+        """Finalize the page."""
+        self._button.set_label(gtk.STOCK_GO_FORWARD)
+        self._button.set_use_stock(True)
+        self._button.disconnect(self._clickedID)
+        self._clickedID = self._button.connect("clicked", self._forwardClicked)
+
+    def _backClicked(self, button):
+        """Called when the Back button is pressed."""
+        self.goBack()
+
+    def _connectClicked(self, button):
+        """Called when the Connect button is pressed."""
+        self._wizard._connectSimulator()
+
+    def _forwardClicked(self, button):
+        """Called when the Forward button is pressed."""
+        self._wizard.nextPage()
+
+#-----------------------------------------------------------------------------
+
+class PayloadPage(Page):
+    """Page to allow setting up the payload."""
+    def __init__(self, wizard):
+        """Construct the page."""
+        help = "The briefing contains the weights below.\n" \
+               "Setup the cargo weight here and the payload weight in the simulator.\n\n" \
+               "You can also check here what the simulator reports as ZFW."
+               
+        super(PayloadPage, self).__init__(wizard, "Payload", help)
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+
+        table = gtk.Table(7, 3)
+        table.set_row_spacings(4)
+        table.set_col_spacings(16)
+        table.set_homogeneous(False)
+        alignment.add(table)
+        self.setMainWidget(alignment)
+
+        label = gtk.Label("Crew:")
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, 0, 1)
+
+        self._numCrew = gtk.Label()
+        self._numCrew.set_width_chars(6)
+        self._numCrew.set_alignment(1.0, 0.5)
+        table.attach(self._numCrew, 1, 2, 0, 1)
+        
+        label = gtk.Label("Passengers:")
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, 1, 2)
+
+        self._numPassengers = gtk.Label()
+        self._numPassengers.set_width_chars(6)
+        self._numPassengers.set_alignment(1.0, 0.5)
+        table.attach(self._numPassengers, 1, 2, 1, 2)
+        
+        label = gtk.Label("Baggage:")
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, 2, 3)
+
+        self._bagWeight = gtk.Label()
+        self._bagWeight.set_width_chars(6)
+        self._bagWeight.set_alignment(1.0, 0.5)
+        table.attach(self._bagWeight, 1, 2, 2, 3)
+
+        table.attach(gtk.Label("kg"), 2, 3, 2, 3)
+        
+        label = gtk.Label("_Cargo:")
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, 3, 4)
+
+        self._cargoWeight = gtk.Entry()
+        self._cargoWeight.set_width_chars(6)
+        self._cargoWeight.set_alignment(1.0)
+        self._cargoWeight.connect("changed", self._cargoWeightChanged)
+        self._cargoWeight.set_tooltip_text("The weight of the cargo for your flight.")
+        table.attach(self._cargoWeight, 1, 2, 3, 4)
+        self._cargoWeightValue = 0        
+        label.set_mnemonic_widget(self._cargoWeight)
+
+        table.attach(gtk.Label("kg"), 2, 3, 3, 4)
+        
+        label = gtk.Label("Mail:")
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, 4, 5)
+
+        self._mailWeight = gtk.Label()
+        self._mailWeight.set_width_chars(6)
+        self._mailWeight.set_alignment(1.0, 0.5)
+        table.attach(self._mailWeight, 1, 2, 4, 5)
+
+        table.attach(gtk.Label("kg"), 2, 3, 4, 5)
+        
+        label = gtk.Label("<b>Calculated ZFW:</b>")
+        label.set_alignment(0.0, 0.5)
+        label.set_use_markup(True)
+        table.attach(label, 0, 1, 5, 6)
+
+        self._calculatedZFW = gtk.Label()
+        self._calculatedZFW.set_width_chars(6)
+        self._calculatedZFW.set_alignment(1.0, 0.5)
+        table.attach(self._calculatedZFW, 1, 2, 5, 6)
+
+        table.attach(gtk.Label("kg"), 2, 3, 5, 6)
+
+        self._zfwButton = gtk.Button("_ZFW from FS:")
+        self._zfwButton.set_use_underline(True)
+        self._zfwButton.connect("clicked", self._zfwRequested)
+        table.attach(self._zfwButton, 0, 1, 6, 7)
+
+        self._simulatorZFW = gtk.Label("-")
+        self._simulatorZFW.set_width_chars(6)
+        self._simulatorZFW.set_alignment(1.0, 0.5)
+        table.attach(self._simulatorZFW, 1, 2, 6, 7)
+        self._simulatorZFWValue = None
+
+        table.attach(gtk.Label("kg"), 2, 3, 6, 7)
+
+        self._backButton = self.addButton(gtk.STOCK_GO_BACK)
+        self._backButton.set_use_stock(True)
+        self._backButton.connect("clicked", self._backClicked)
+
+        self._button = self.addButton(gtk.STOCK_GO_FORWARD, default = True)
+        self._button.set_use_stock(True)
+        self._button.connect("clicked", self._forwardClicked)
+
+    def activate(self):
+        """Setup the information."""
+        bookedFlight = self._wizard._bookedFlight
+        self._numCrew.set_text(str(bookedFlight.numCrew))
+        self._numPassengers.set_text(str(bookedFlight.numPassengers))
+        self._bagWeight.set_text(str(bookedFlight.bagWeight))
+        self._cargoWeightValue = bookedFlight.cargoWeight
+        self._cargoWeight.set_text(str(bookedFlight.cargoWeight))
+        self._cargoWeight.set_sensitive(True)
+        self._mailWeight.set_text(str(bookedFlight.mailWeight))
+        self._zfwButton.set_sensitive(True)
+        self._updateCalculatedZFW()
+
+    def finalize(self):
+        """Finalize the payload page."""
+        self._cargoWeight.set_sensitive(False)
+        self._zfwButton.set_sensitive(False)
+
+    def _calculateZFW(self):
+        """Calculate the ZFW value."""
+        zfw = self._wizard.gui._flight.aircraft.dow
+        bookedFlight = self._wizard._bookedFlight
+        zfw += (bookedFlight.numCrew + bookedFlight.numPassengers) * 82
+        zfw += bookedFlight.bagWeight
+        zfw += self._cargoWeightValue
+        zfw += bookedFlight.mailWeight
+        return zfw
+        
+    def _updateCalculatedZFW(self):
+        """Update the calculated ZFW"""
+        zfw = self._calculateZFW()
+
+        markupBegin = "<b>"
+        markupEnd = "</b>"
+        if self._simulatorZFWValue is not None and \
+           PayloadChecker.isZFWFaulty(self._simulatorZFWValue, zfw):
+            markupBegin += '<span foreground="red">'
+            markupEnd = "</span>" + markupEnd
+        self._calculatedZFW.set_markup(markupBegin + str(zfw) + markupEnd)
+
+    def _cargoWeightChanged(self, entry):
+        """Called when the cargo weight has changed."""
+        text = self._cargoWeight.get_text()
+        if text=="":
+            self._cargoWeightValue = 0
+        else:
+            try:
+                self._cargoWeightValue = int(text)
+            except:
+                self._cargoWeight.set_text(str(self._cargoWeightValue))
+        self._updateCalculatedZFW()
+            
+    def _zfwRequested(self, button):
+        """Called when the ZFW is requested from the simulator."""
+        self._zfwButton.set_sensitive(False)
+        self._backButton.set_sensitive(False)
+        self._button.set_sensitive(False)
+        gui = self._wizard.gui
+        gui.beginBusy("Querying ZFW...")
+        gui.simulator.requestZFW(self._handleZFW)
+
+    def _handleZFW(self, zfw):
+        """Called when the ZFW value is retrieved."""
+        gobject.idle_add(self._processZFW, zfw)
+
+    def _processZFW(self, zfw):
+        """Process the given ZFW value received from the simulator."""
+        self._wizard.gui.endBusy()
+        self._zfwButton.set_sensitive(True)
+        self._backButton.set_sensitive(True)
+        self._button.set_sensitive(True)
+        self._simulatorZFWValue = zfw
+        self._simulatorZFW.set_text("%.0f" % (zfw,))
+        self._updateCalculatedZFW()
+
+    def _forwardClicked(self, button):
+        """Called when the forward button is clicked."""
+        if not self._finalized:
+            self._wizard._zfw = self._calculateZFW()
+        self._wizard.nextPage()
+
+    def _backClicked(self, button):
+        """Called when the Back button is pressed."""
+        self.goBack()
+        
+#-----------------------------------------------------------------------------
+
+class TimePage(Page):
+    """Page displaying the departure and arrival times and allows querying the
+    current time from the flight simulator."""
+    def __init__(self, wizard):
+        help = "The departure and arrival times are displayed below in UTC.\n\n" \
+               "You can also query the current UTC time from the simulator.\n" \
+               "Ensure that you have enough time to properly prepare for the flight."
+               
+        super(TimePage, self).__init__(wizard, "Time", help)
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+
+        table = gtk.Table(3, 2)
+        table.set_row_spacings(4)
+        table.set_col_spacings(16)
+        table.set_homogeneous(False)
+        alignment.add(table)
+        self.setMainWidget(alignment)
+
+        label = gtk.Label("Departure:")
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, 0, 1)
+
+        self._departure = gtk.Label()
+        self._departure.set_alignment(0.0, 0.5)
+        table.attach(self._departure, 1, 2, 0, 1)
+        
+        label = gtk.Label("Arrival:")
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, 1, 2)
+
+        self._arrival = gtk.Label()
+        self._arrival.set_alignment(0.0, 0.5)
+        table.attach(self._arrival, 1, 2, 1, 2)
+
+        self._timeButton = gtk.Button("_Time from FS:")
+        self._timeButton.set_use_underline(True)
+        self._timeButton.connect("clicked", self._timeRequested)
+        table.attach(self._timeButton, 0, 1, 2, 3)
+
+        self._simulatorTime = gtk.Label("-")
+        self._simulatorTime.set_alignment(0.0, 0.5)
+        table.attach(self._simulatorTime, 1, 2, 2, 3)
+
+        self._backButton = self.addButton(gtk.STOCK_GO_BACK)
+        self._backButton.set_use_stock(True)
+        self._backButton.connect("clicked", self._backClicked)
+
+        self._button = self.addButton(gtk.STOCK_GO_FORWARD, default = True)
+        self._button.set_use_stock(True)
+        self._button.connect("clicked", self._forwardClicked)
+
+    def activate(self):
+        """Activate the page."""
+        self._timeButton.set_sensitive(True)
+        bookedFlight = self._wizard._bookedFlight
+        self._departure.set_text(str(bookedFlight.departureTime.time()))
+        self._arrival.set_text(str(bookedFlight.arrivalTime.time()))
+
+    def finalize(self):
+        """Finalize the page."""
+        self._timeButton.set_sensitive(False)
+
+    def _timeRequested(self, button):
+        """Request the time from the simulator."""
+        self._timeButton.set_sensitive(False)
+        self._backButton.set_sensitive(False)
+        self._button.set_sensitive(False)
+        self._wizard.gui.beginBusy("Querying time...")
+        self._wizard.gui.simulator.requestTime(self._handleTime)
+
+    def _handleTime(self, timestamp):
+        """Handle the result of a time retrieval."""
+        gobject.idle_add(self._processTime, timestamp)
+
+    def _processTime(self, timestamp):
+        """Process the given time."""
+        self._wizard.gui.endBusy()
+        self._timeButton.set_sensitive(True)
+        self._backButton.set_sensitive(True)
+        self._button.set_sensitive(True)
+        tm = time.gmtime(timestamp)
+        t = datetime.time(tm.tm_hour, tm.tm_min, tm.tm_sec)
+        self._simulatorTime.set_text(str(t))
+
+        ts = tm.tm_hour * 3600 + tm.tm_min * 60 + tm.tm_sec
+        dt = self._wizard._bookedFlight.departureTime.time()
+        dts = dt.hour * 3600 + dt.minute * 60 + dt.second
+        diff = dts-ts
+
+        markupBegin = ""
+        markupEnd = ""
+        if diff < 0:
+            markupBegin = '<b><span foreground="red">'
+            markupEnd = '</span></b>'
+        elif diff < 3*60 or diff > 30*60:
+            markupBegin = '<b><span foreground="orange">'
+            markupEnd = '</span></b>'
+
+        self._departure.set_markup(markupBegin + str(dt) + markupEnd)
+
+    def _backClicked(self, button):
+        """Called when the Back button is pressed."""
+        self.goBack()
+        
+    def _forwardClicked(self, button):
+        """Called when the forward button is clicked."""
+        self._wizard.nextPage()
+        
+#-----------------------------------------------------------------------------
+
+class RoutePage(Page):
+    """The page containing the route and the flight level."""
+    def __init__(self, wizard):
+        help = "Set your cruise flight level below, and\n" \
+               "if necessary, edit the flight plan."
+               
+        super(RoutePage, self).__init__(wizard, "Route", help)
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+
+        mainBox = gtk.VBox()
+        alignment.add(mainBox)
+        self.setMainWidget(alignment)
+
+        levelBox = gtk.HBox()
+
+        label = gtk.Label("_Cruise level")
+        label.set_use_underline(True)
+        levelBox.pack_start(label, True, True, 0)
+
+        self._cruiseLevel = gtk.SpinButton()
+        self._cruiseLevel.set_increments(step = 10, page = 100)
+        self._cruiseLevel.set_range(min = 50, max = 500)
+        self._cruiseLevel.set_value(240)
+        self._cruiseLevel.set_tooltip_text("The cruise flight level.")
+        self._cruiseLevel.set_numeric(True)
+        self._cruiseLevel.connect("value-changed", self._cruiseLevelChanged)
+        label.set_mnemonic_widget(self._cruiseLevel)        
+
+        levelBox.pack_start(self._cruiseLevel, False, False, 8)
+
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(levelBox)
+
+        mainBox.pack_start(alignment, False, False, 0)
+
+
+        routeBox = gtk.VBox()
+
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        label = gtk.Label("_Route")
+        label.set_use_underline(True)
+        alignment.add(label)
+        routeBox.pack_start(alignment, True, True, 0)
+
+        routeWindow = gtk.ScrolledWindow()
+        routeWindow.set_size_request(400, 80)
+        routeWindow.set_shadow_type(gtk.ShadowType.IN if pygobject
+                                    else gtk.SHADOW_IN)
+        routeWindow.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
+                               else gtk.POLICY_AUTOMATIC,
+                               gtk.PolicyType.AUTOMATIC if pygobject
+                               else gtk.POLICY_AUTOMATIC)
+
+        self._route = gtk.TextView()
+        self._route.set_tooltip_text("The planned flight route.")
+        self._route.get_buffer().connect("changed", self._routeChanged)
+        routeWindow.add(self._route)
+
+        label.set_mnemonic_widget(self._route)
+        routeBox.pack_start(routeWindow, True, True, 0)        
+
+        mainBox.pack_start(routeBox, True, True, 8)
+
+        self._backButton = self.addButton(gtk.STOCK_GO_BACK)
+        self._backButton.set_use_stock(True)
+        self._backButton.connect("clicked", self._backClicked)
+
+        self._button = self.addButton(gtk.STOCK_GO_FORWARD, default = True)
+        self._button.set_use_stock(True)
+        self._button.connect("clicked", self._forwardClicked)
+
+    def activate(self):
+        """Setup the route from the booked flight."""
+        self._route.set_sensitive(True)
+        self._cruiseLevel.set_sensitive(True)
+        self._route.get_buffer().set_text(self._wizard._bookedFlight.route)
+        self._updateForwardButton()
+
+    def finalize(self):
+        """Finalize the page."""
+        self._route.set_sensitive(False)
+        self._cruiseLevel.set_sensitive(False)
+
+    def _getRoute(self):
+        """Get the text of the route."""
+        buffer = self._route.get_buffer()
+        return buffer.get_text(buffer.get_start_iter(),
+                               buffer.get_end_iter(), True)
+
+    def _updateForwardButton(self):
+        """Update the sensitivity of the forward button."""
+        self._button.set_sensitive(self._cruiseLevel.get_value_as_int()>=50 and \
+                                   self._getRoute()!="")
+                                   
+    def _cruiseLevelChanged(self, spinButton):
+        """Called when the cruise level has changed."""
+        self._updateForwardButton()
+
+    def _routeChanged(self, textBuffer):
+        """Called when the route has changed."""
+        self._updateForwardButton()
+
+    def _backClicked(self, button):
+        """Called when the Back button is pressed."""
+        self.goBack()
+        
+    def _forwardClicked(self, button):
+        """Called when the Forward button is clicked."""
+        if self._finalized:
+            self._wizard.nextPage()
+        else:
+            self._wizard._cruiseAltitude = self._cruiseLevel.get_value_as_int() * 100
+            self._wizard._route = self._getRoute()
+
+            self._backButton.set_sensitive(False)
+            self._button.set_sensitive(False)
+            self._cruiseLevel.set_sensitive(False)
+            self._route.set_sensitive(False)
+
+            bookedFlight = self._wizard._bookedFlight
+            self._wizard.gui.beginBusy("Downloading NOTAMs...")
+            self._wizard.gui.webHandler.getNOTAMs(self._notamsCallback,
+                                                  bookedFlight.departureICAO,
+                                                  bookedFlight.arrivalICAO)
+
+    def _notamsCallback(self, returned, result):
+        """Callback for the NOTAMs."""
+        gobject.idle_add(self._handleNOTAMs, returned, result)
+
+    def _handleNOTAMs(self, returned, result):
+        """Handle the NOTAMs."""
+        if returned:
+            self._wizard._departureNOTAMs = result.departureNOTAMs
+            self._wizard._arrivalNOTAMs = result.arrivalNOTAMs
+        else:
+            self._wizard._departureNOTAMs = None
+            self._wizard._arrivalNOTAMs = None
+
+        bookedFlight = self._wizard._bookedFlight
+        self._wizard.gui.beginBusy("Downloading METARs...")
+        self._wizard.gui.webHandler.getMETARs(self._metarsCallback,
+                                              [bookedFlight.departureICAO,
+                                               bookedFlight.arrivalICAO])
+
+    def _metarsCallback(self, returned, result):
+        """Callback for the METARs."""
+        gobject.idle_add(self._handleMETARs, returned, result)
+
+    def _handleMETARs(self, returned, result):
+        """Handle the METARs."""
+        self._wizard._departureMETAR = None
+        self._wizard._arrivalMETAR = None
+        bookedFlight = self._wizard._bookedFlight
+        if returned:
+            if bookedFlight.departureICAO in result.metars:
+                self._wizard._departureMETAR = result.metars[bookedFlight.departureICAO]
+            if bookedFlight.arrivalICAO in result.metars:
+                self._wizard._arrivalMETAR = result.metars[bookedFlight.arrivalICAO]
+
+        self._wizard.gui.endBusy()
+        self._backButton.set_sensitive(True)
+        self._button.set_sensitive(True)
+        self._wizard.nextPage()
+
+#-----------------------------------------------------------------------------
+
+class BriefingPage(Page):
+    """Page for the briefing."""
+    def __init__(self, wizard, departure):
+        """Construct the briefing page."""
+        self._departure = departure
+        
+        title = "Briefing (%d/2): %s" % (1 if departure else 2,
+                                        "departure" if departure
+                                         else "arrival")
+                                                                
+        help = "Read carefully the NOTAMs and METAR below."
+
+        super(BriefingPage, self).__init__(wizard, title, help)
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 1.0, yscale = 1.0)
+
+        mainBox = gtk.VBox()
+        alignment.add(mainBox)
+        self.setMainWidget(alignment)
+
+        self._notamsFrame = gtk.Frame()
+        self._notamsFrame.set_label("LHBP NOTAMs")
+        scrolledWindow = gtk.ScrolledWindow()
+        scrolledWindow.set_size_request(-1, 128)
+        scrolledWindow.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
+                                  else gtk.POLICY_AUTOMATIC,
+                                  gtk.PolicyType.AUTOMATIC if pygobject
+                                  else gtk.POLICY_AUTOMATIC)
+        self._notams = gtk.TextView()
+        self._notams.set_editable(False)
+        self._notams.set_accepts_tab(False)
+        self._notams.set_wrap_mode(gtk.WrapMode.WORD if pygobject else gtk.WRAP_WORD)
+        scrolledWindow.add(self._notams)
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                                  xscale = 1.0, yscale = 1.0)
+        alignment.set_padding(padding_top = 4, padding_bottom = 0,
+                              padding_left = 0, padding_right = 0)
+        alignment.add(scrolledWindow)
+        self._notamsFrame.add(alignment)
+        mainBox.pack_start(self._notamsFrame, True, True, 4)
+        
+        self._metarFrame = gtk.Frame()
+        self._metarFrame.set_label("LHBP METAR")
+        scrolledWindow = gtk.ScrolledWindow()
+        scrolledWindow.set_size_request(-1, 32)
+        scrolledWindow.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
+                                  else gtk.POLICY_AUTOMATIC,
+                                  gtk.PolicyType.AUTOMATIC if pygobject
+                                  else gtk.POLICY_AUTOMATIC)
+        self._metar = gtk.TextView()
+        self._metar.set_editable(False)
+        self._metar.set_accepts_tab(False)
+        self._metar.set_wrap_mode(gtk.WrapMode.WORD if pygobject else gtk.WRAP_WORD)
+        scrolledWindow.add(self._metar)
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                                  xscale = 1.0, yscale = 1.0)
+        alignment.set_padding(padding_top = 4, padding_bottom = 0,
+                              padding_left = 0, padding_right = 0)
+        alignment.add(scrolledWindow)
+        self._metarFrame.add(alignment)
+        mainBox.pack_start(self._metarFrame, True, True, 4)
+
+        button = self.addButton(gtk.STOCK_GO_BACK)
+        button.set_use_stock(True)
+        button.connect("clicked", self._backClicked)
+
+        self._button = self.addButton(gtk.STOCK_GO_FORWARD, default = True)
+        self._button.set_use_stock(True)
+        self._button.connect("clicked", self._forwardClicked)
+
+    def activate(self):
+        """Activate the page."""
+        if not self._departure:
+            self._button.set_label("I have read the briefing and am ready to fly!")
+            self._button.set_use_stock(False)
+
+        bookedFlight = self._wizard._bookedFlight
+
+        icao = bookedFlight.departureICAO if self._departure \
+               else bookedFlight.arrivalICAO
+        notams = self._wizard._departureNOTAMs if self._departure \
+                 else self._wizard._arrivalNOTAMs
+        metar = self._wizard._departureMETAR if self._departure \
+                 else self._wizard._arrivalMETAR
+
+        self._notamsFrame.set_label(icao + " NOTAMs")
+        buffer = self._notams.get_buffer()
+        if notams is None:
+            buffer.set_text("Could not download NOTAMs")
+        else:
+            s = ""
+            for notam in notams:
+                s += str(notam.begin)
+                if notam.end is not None:
+                    s += " - " + str(notam.end)
+                elif notam.permanent:
+                    s += " - PERMANENT"
+                s += "\n"
+                if notam.repeatCycle:
+                    s += "Repeat cycle: " + notam.repeatCycle + "\n"
+                s += notam.notice + "\n"
+                s += "-------------------- * --------------------\n"
+            buffer.set_text(s)
+
+        self._metarFrame.set_label(icao + " METAR")
+        buffer = self._metar.get_buffer()
+        if metar is None:
+            buffer.set_text("Could not download METAR")
+        else:
+            buffer.set_text(metar)
+
+    def finalize(self):
+        """Finalize the page."""
+        if not self._departure:
+            self._button.set_use_stock(True)
+            self._button.set_label(gtk.STOCK_GO_FORWARD)
+
+    def _backClicked(self, button):
+        """Called when the Back button is pressed."""
+        self.goBack()
+        
+    def _forwardClicked(self, button):
+        """Called when the forward button is clicked."""
+        if not self._departure:
+            if not self._finalized:
+                self._wizard.gui.startMonitoring()
+                self.finalize()
+                self._finalized = True
+
+        self._wizard.nextPage()
+
+#-----------------------------------------------------------------------------
+
+class TakeoffPage(Page):
+    """Page for entering the takeoff data."""
+    def __init__(self, wizard):
+        """Construct the takeoff page."""
+        help = "Enter the runway and SID used, as well as the speeds."
+
+        super(TakeoffPage, self).__init__(wizard, "Takeoff", help)
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+
+        table = gtk.Table(5, 3)
+        table.set_row_spacings(4)
+        table.set_col_spacings(16)
+        table.set_homogeneous(False)
+        alignment.add(table)
+        self.setMainWidget(alignment)
+
+        label = gtk.Label("Run_way:")
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, 0, 1)
+
+        self._runway = gtk.Entry()
+        self._runway.set_width_chars(10)
+        self._runway.set_tooltip_text("The runway the takeoff is performed from.")
+        self._runway.connect("changed", self._updateForwardButton)
+        table.attach(self._runway, 1, 2, 0, 1)
+        label.set_mnemonic_widget(self._runway)
+        
+        label = gtk.Label("_SID:")
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, 1, 2)
+
+        self._sid = gtk.Entry()
+        self._sid.set_width_chars(10)
+        self._sid.set_tooltip_text("The name of the Standard Instrument Deparature procedure followed.")
+        self._sid.connect("changed", self._updateForwardButton)
+        table.attach(self._sid, 1, 2, 1, 2)
+        label.set_mnemonic_widget(self._sid)
+        
+        label = gtk.Label("V<sub>_1</sub>:")
+        label.set_use_markup(True)
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, 2, 3)
+
+        self._v1 = gtk.SpinButton()
+        self._v1.set_increments(step = 1, page = 10)
+        self._v1.set_range(min = 50, max = 300)
+        self._v1.set_value(100)
+        self._v1.set_numeric(True)
+        self._v1.set_tooltip_markup("The takeoff decision speed in knots.")
+        self._v1.connect("changed", self._updateForwardButton)
+        table.attach(self._v1, 1, 2, 2, 3)
+        label.set_mnemonic_widget(self._v1)
+        
+        table.attach(gtk.Label("knots"), 2, 3, 2, 3)
+        
+        label = gtk.Label("V<sub>_r</sub>:")
+        label.set_use_markup(True)
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, 3, 4)
+
+        self._vr = gtk.SpinButton()
+        self._vr.set_increments(step = 1, page = 10)
+        self._vr.set_range(min = 50, max = 300)
+        self._vr.set_value(110)
+        self._vr.set_numeric(True)
+        self._vr.set_tooltip_markup("The takeoff rotation speed in knots.")
+        self._vr.connect("changed", self._updateForwardButton)
+        table.attach(self._vr, 1, 2, 3, 4)
+        label.set_mnemonic_widget(self._vr)
+        
+        table.attach(gtk.Label("knots"), 2, 3, 3, 4)
+        
+        label = gtk.Label("V<sub>_2</sub>:")
+        label.set_use_markup(True)
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, 4, 5)
+
+        self._v2 = gtk.SpinButton()
+        self._v2.set_increments(step = 1, page = 10)
+        self._v2.set_range(min = 50, max = 300)
+        self._v2.set_value(120)
+        self._v2.set_numeric(True)
+        self._v2.set_tooltip_markup("The takeoff safety speed in knots.")
+        self._v2.connect("changed", self._updateForwardButton)
+        table.attach(self._v2, 1, 2, 4, 5)
+        label.set_mnemonic_widget(self._v2)
+        
+        table.attach(gtk.Label("knots"), 2, 3, 4, 5)
+        
+        button = self.addButton(gtk.STOCK_GO_BACK)
+        button.set_use_stock(True)
+        button.connect("clicked", self._backClicked)
+
+        self._button = self.addButton(gtk.STOCK_GO_FORWARD, default = True)
+        self._button.set_use_stock(True)
+        self._button.connect("clicked", self._forwardClicked)
+
+    def activate(self):
+        """Activate the page."""
+        self._runway.set_text("")
+        self._runway.set_sensitive(True)
+        self._sid.set_text("")
+        self._sid.set_sensitive(True)
+        self._v1.set_sensitive(True)
+        self._vr.set_sensitive(True)
+        self._v2.set_sensitive(True)
+        self._updateForwardButton()
+        
+    def finalize(self):
+        """Finalize the page."""
+        self._runway.set_sensitive(False)
+        self._sid.set_sensitive(False)
+        self._v1.set_sensitive(False)
+        self._vr.set_sensitive(False)
+        self._v2.set_sensitive(False)
+
+        flight = self._wizard.gui.flight
+        flight.v1 = self._v1.get_value_as_int()
+        flight.vr = self._vr.get_value_as_int()
+        flight.v2 = self._v2.get_value_as_int()
+
+    def _updateForwardButton(self, widget = None):
+        """Update the Forward buttons sensitivity."""
+        self._button.set_sensitive(self._runway.get_text()!="" and
+                                   self._sid.get_text()!="" and
+                                   self._v1.get_value_as_int()<=self._vr.get_value_as_int() and 
+                                   self._vr.get_value_as_int()<=self._v2.get_value_as_int())
+
+    def _backClicked(self, button):
+        """Called when the Back button is pressed."""
+        self.goBack()
+        
+    def _forwardClicked(self, button):
+        """Called when the forward button is clicked."""
+        self._wizard.nextPage()
+
+#-----------------------------------------------------------------------------
+
+class LandingPage(Page):
+    """Page for entering landing data."""
+    def __init__(self, wizard):
+        """Construct the landing page."""
+        help = "Enter the STAR and/or transition, runway,\n" \
+               "approach type and V<sub>ref</sub> used."
+
+        super(LandingPage, self).__init__(wizard, "Landing", help)
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+
+        table = gtk.Table(5, 4)
+        table.set_row_spacings(4)
+        table.set_col_spacings(16)
+        table.set_homogeneous(False)
+        alignment.add(table)
+        self.setMainWidget(alignment)
+
+        self._starButton = gtk.CheckButton()
+        self._starButton.connect("clicked", self._starButtonClicked)
+        table.attach(self._starButton, 0, 1, 0, 1)
+
+        label = gtk.Label("_STAR:")
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 1, 2, 0, 1)
+
+        self._star = gtk.Entry()
+        self._star.set_width_chars(10)
+        self._star.set_tooltip_text("The name of Standard Terminal Arrival Route followed.")
+        self._star.connect("changed", self._updateForwardButton)
+        self._star.set_sensitive(False)
+        table.attach(self._star, 2, 3, 0, 1)
+        label.set_mnemonic_widget(self._starButton)
+
+        self._transitionButton = gtk.CheckButton()
+        self._transitionButton.connect("clicked", self._transitionButtonClicked)
+        table.attach(self._transitionButton, 0, 1, 1, 2)
+
+        label = gtk.Label("_Transition:")
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 1, 2, 1, 2)
+
+        self._transition = gtk.Entry()
+        self._transition.set_width_chars(10)
+        self._transition.set_tooltip_text("The name of transition executed or VECTORS if vectored by ATC.")
+        self._transition.connect("changed", self._updateForwardButton)
+        self._transition.set_sensitive(False)
+        table.attach(self._transition, 2, 3, 1, 2)
+        label.set_mnemonic_widget(self._transitionButton)
+
+        label = gtk.Label("Run_way:")
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 1, 2, 2, 3)
+
+        self._runway = gtk.Entry()
+        self._runway.set_width_chars(10)
+        self._runway.set_tooltip_text("The runway the landing is performed on.")
+        self._runway.connect("changed", self._updateForwardButton)
+        table.attach(self._runway, 2, 3, 2, 3)
+        label.set_mnemonic_widget(self._runway)
+
+        label = gtk.Label("_Approach type:")
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 1, 2, 3, 4)
+
+        self._approachType = gtk.Entry()
+        self._approachType.set_width_chars(10)
+        self._approachType.set_tooltip_text("The type of the approach, e.g. ILS or VISUAL.")
+        self._approachType.connect("changed", self._updateForwardButton)
+        table.attach(self._approachType, 2, 3, 3, 4)
+        label.set_mnemonic_widget(self._approachType)
+
+        label = gtk.Label("V<sub>_ref</sub>:")
+        label.set_use_markup(True)
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 1, 2, 5, 6)
+
+        self._vref = gtk.SpinButton()
+        self._vref.set_increments(step = 1, page = 10)
+        self._vref.set_range(min = 50, max = 300)
+        self._vref.set_value(140)
+        self._vref.set_numeric(True)
+        self._vref.set_tooltip_markup("The approach reference speed in knots.")
+        table.attach(self._vref, 2, 3, 5, 6)
+        label.set_mnemonic_widget(self._vref)
+        
+        table.attach(gtk.Label("knots"), 3, 4, 5, 6)
+        
+        button = self.addButton(gtk.STOCK_GO_BACK)
+        button.set_use_stock(True)
+        button.connect("clicked", self._backClicked)
+
+        self._button = self.addButton(gtk.STOCK_GO_FORWARD, default = True)
+        self._button.set_use_stock(True)
+        self._button.connect("clicked", self._forwardClicked)
+
+        # These are needed for correct size calculations
+        self._starButton.set_active(True)
+        self._transitionButton.set_active(True)
+
+    def activate(self):
+        """Called when the page is activated."""
+        self._starButton.set_sensitive(True)
+        self._starButton.set_active(False)
+        self._star.set_text("")
+
+        self._transitionButton.set_sensitive(True)
+        self._transitionButton.set_active(False)
+        self._transition.set_text("")
+
+        self._runway.set_text("")
+        self._runway.set_sensitive(True)
+
+        self._approachType.set_text("")
+        self._approachType.set_sensitive(True)
+
+        self._vref.set_value(140)
+        self._vref.set_sensitive(True)
+
+        self._updateForwardButton()
+
+    def finalize(self):
+        """Finalize the page."""
+        self._starButton.set_sensitive(False)
+        self._star.set_sensitive(False)
+
+        self._transitionButton.set_sensitive(False)
+        self._transition.set_sensitive(False)
+
+        self._runway.set_sensitive(False)
+
+        self._approachType.set_sensitive(False)
+
+        self._vref.set_sensitive(False)
+
+    def _starButtonClicked(self, button):
+        """Called when the STAR button is clicked."""
+        active = button.get_active()
+        self._star.set_sensitive(active)
+        if active:
+            self._star.grab_focus()
+        self._updateForwardButton()
+
+    def _transitionButtonClicked(self, button):
+        """Called when the Transition button is clicked."""
+        active = button.get_active()
+        self._transition.set_sensitive(active)
+        if active:
+            self._transition.grab_focus()        
+        self._updateForwardButton()
+
+    def _updateForwardButton(self, widget = None):
+        """Update the sensitivity of the forward button."""
+        sensitive = (self._starButton.get_active() or \
+                     self._transitionButton.get_active()) and \
+                    (self._star.get_text()!="" or
+                     not self._starButton.get_active()) and \
+                    (self._transition.get_text()!="" or
+                     not self._transitionButton.get_active()) and \
+                    self._runway.get_text()!="" and \
+                    self._approachType.get_text()!=""
+        self._button.set_sensitive(sensitive)
+
+    def _backClicked(self, button):
+        """Called when the Back button is pressed."""
+        self.goBack()
+        
+    def _forwardClicked(self, button):
+        """Called when the forward button is clicked."""
+        #self._wizard.nextPage()
+        self.finalize()
+
+#-----------------------------------------------------------------------------
+
+class Wizard(gtk.VBox):
+    """The flight wizard."""
+    def __init__(self, gui):
+        """Construct the wizard."""
+        super(Wizard, self).__init__()
+
+        self.gui = gui
+
+        self._pages = []
+        self._currentPage = None
+        
+        self._pages.append(LoginPage(self))
+        self._pages.append(FlightSelectionPage(self))
+        self._pages.append(GateSelectionPage(self))
+        self._pages.append(ConnectPage(self))
+        self._pages.append(PayloadPage(self))
+        self._pages.append(TimePage(self))
+        self._pages.append(RoutePage(self))
+        self._pages.append(BriefingPage(self, True))
+        self._pages.append(BriefingPage(self, False))
+        self._pages.append(TakeoffPage(self))
+        self._pages.append(LandingPage(self))
+        
+        maxWidth = 0
+        maxHeight = 0
+        for page in self._pages:
+            page.show_all()
+            pageSizeRequest = page.size_request()
+            width = pageSizeRequest.width if pygobject else pageSizeRequest[0]
+            height = pageSizeRequest.height if pygobject else pageSizeRequest[1]
+            maxWidth = max(maxWidth, width)
+            maxHeight = max(maxHeight, height)
+        maxWidth += 16
+        maxHeight += 32
+        self.set_size_request(maxWidth, maxHeight)
+
+        self._initialize()
+        
+    @property
+    def loginResult(self):
+        """Get the login result."""
+        return self._loginResult
+
+    def setCurrentPage(self, index, finalize = False):
+        """Set the current page to the one with the given index."""
+        assert index < len(self._pages)
+
+        fromPage = self._currentPage
+        if fromPage is not None:
+            page = self._pages[fromPage]
+            if finalize and not page._finalized:
+                page.finalize()
+                page._finalized = True
+            self.remove(page)
+
+        self._currentPage = index
+        page = self._pages[index]
+        self.add(page)
+        if page._fromPage is None:
+            page._fromPage = fromPage
+            page.activate()
+        self.show_all()
+        if fromPage is not None:
+            self.grabDefault()
+
+    def nextPage(self, finalize = True):
+        """Go to the next page."""
+        self.jumpPage(1, finalize)
+
+    def jumpPage(self, count, finalize = True):
+        """Go to the page which is 'count' pages after the current one."""
+        self.setCurrentPage(self._currentPage + count, finalize = finalize)
+
+    def grabDefault(self):
+        """Make the default button of the current page the default."""
+        self._pages[self._currentPage].grabDefault()
+
+    def connected(self, fsType, descriptor):
+        """Called when the connection could be made to the simulator."""
+        self.nextPage()
+
+    def connectionFailed(self):
+        """Called when the connection could not be made to the simulator."""
+        self._initialize()
+
+    def disconnected(self):
+        """Called when we have disconnected from the simulator."""
+        self._initialize()
+
+    def _initialize(self):
+        """Initialize the wizard."""
+        self._fleet = None
+        self._fleetCallback = None
+        self._updatePlaneCallback = None
+        
+        self._loginResult = None
+        self._bookedFlight = None
+        self._departureGate = "-"
+        self._zfw = None
+        self._cruiseAltitude = None
+        self._route = None
+        self._departureNOTAMs = None
+        self._departureMETAR = None
+        self._arrivalNOTAMs = None
+        self._arrivalMETAR = None
+
+        for page in self._pages:
+            page.reset()
+        
+        self.setCurrentPage(0)
+        
+    def _getFleet(self, callback, force = False):
+        """Get the fleet, if needed.
+
+        callback is function that will be called, when the feet is retrieved,
+        or the retrieval fails. It should have a single argument that will
+        receive the fleet object on success, None otherwise.
+        """
+        if self._fleet is not None and not force:
+            callback(self._fleet)
+
+        self.gui.beginBusy("Retrieving fleet...")
+        self._fleetCallback = callback
+        self.gui.webHandler.getFleet(self._fleetResultCallback)
+
+    def _fleetResultCallback(self, returned, result):
+        """Called when the fleet has been queried."""
+        gobject.idle_add(self._handleFleetResult, returned, result)
+
+    def _handleFleetResult(self, returned, result):
+        """Handle the fleet result."""
+        self.gui.endBusy()
+        if returned:
+            self._fleet = result.fleet
+        else:
+            self._fleet = None
+
+            dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
+                                       buttons = BUTTONSTYPE_OK,
+                                       message_format =
+                                       "Failed to retrieve the information on "
+                                       "the fleet.")
+            dialog.run()
+            dialog.hide()
+
+        self._fleetCallback(self._fleet)
+
+    def _updatePlane(self, callback, tailNumber, status, gateNumber = None):
+        """Update the given plane's gate information."""
+        self.gui.beginBusy("Updating plane status...")
+        self._updatePlaneCallback = callback
+        self.gui.webHandler.updatePlane(self._updatePlaneResultCallback,
+                                        tailNumber, status, gateNumber)
+
+    def _updatePlaneResultCallback(self, returned, result):
+        """Callback for the plane updating operation."""
+        gobject.idle_add(self._handleUpdatePlaneResult, returned, result)
+
+    def _handleUpdatePlaneResult(self, returned, result):
+        """Handle the result of a plane update operation."""
+        self.gui.endBusy()
+        if returned:
+            success = result.success
+        else:
+            success = None
+
+            dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
+                                       buttons = BUTTONSTYPE_OK,
+                                       message_format =
+                                       "Failed to update the statuis of "
+                                       "the airplane.")
+            dialog.run()
+            dialog.hide()
+
+        self._updatePlaneCallback(success)
+
+    def _connectSimulator(self):
+        """Connect to the simulator."""
+        self.gui.connectSimulator(self._bookedFlight.aircraftType)
+    
+#-----------------------------------------------------------------------------
+
Index: src/mlx/gui/flight.py
===================================================================
--- src/mlx/gui/flight.py	(revision 76)
+++ 	(revision )
@@ -1,1688 +1,0 @@
-# The flight handling "wizard"
-
-from mlx.gui.common import *
-
-import mlx.const as const
-import mlx.fs as fs
-from mlx.checks import PayloadChecker
-
-import datetime
-import time
-
-#-----------------------------------------------------------------------------
-
-class Page(gtk.Alignment):
-    """A page in the flight wizard."""
-    def __init__(self, wizard, title, help):
-        """Construct the page."""
-        super(Page, self).__init__(xalign = 0.0, yalign = 0.0,
-                                   xscale = 1.0, yscale = 1.0)
-        self.set_padding(padding_top = 4, padding_bottom = 4,
-                         padding_left = 12, padding_right = 12)
-
-        frame = gtk.Frame()
-        self.add(frame)
-
-        style = self.get_style() if pygobject else self.rc_get_style()
-
-        self._vbox = gtk.VBox()
-        self._vbox.set_homogeneous(False)
-        frame.add(self._vbox)
-
-        eventBox = gtk.EventBox()
-        eventBox.modify_bg(0, style.bg[3])
-
-        alignment = gtk.Alignment(xalign = 0.0, xscale = 0.0)
-        
-        label = gtk.Label(title)
-        label.modify_fg(0, style.fg[3])
-        label.modify_font(pango.FontDescription("bold 24"))
-        alignment.set_padding(padding_top = 4, padding_bottom = 4,
-                              padding_left = 6, padding_right = 0)
-                              
-        alignment.add(label)
-        eventBox.add(alignment)
-        
-        self._vbox.pack_start(eventBox, False, False, 0)
-
-        table = gtk.Table(3, 1)
-        table.set_homogeneous(False)
-
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
-                                  xscale = 1.0, yscale = 1.0)        
-        alignment.set_padding(padding_top = 16, padding_bottom = 16,
-                              padding_left = 16, padding_right = 16)
-        alignment.add(table)
-        self._vbox.pack_start(alignment, True, True, 0)
-        
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.0,
-                                  xscale = 0, yscale = 0.0)
-        alignment.set_padding(padding_top = 0, padding_bottom = 16,
-                              padding_left = 0, padding_right = 0)
-
-        label = gtk.Label(help)
-        label.set_justify(gtk.Justification.CENTER if pygobject
-                          else gtk.JUSTIFY_CENTER)
-        label.set_use_markup(True)
-        alignment.add(label)
-        table.attach(alignment, 0, 1, 0, 1)
-
-        self._mainAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                            xscale = 1.0, yscale = 1.0)
-        table.attach(self._mainAlignment, 0, 1, 1, 3)
-                                            
-        buttonAlignment =  gtk.Alignment(xalign = 1.0, xscale=0.0, yscale = 0.0)
-        buttonAlignment.set_padding(padding_top = 4, padding_bottom = 10,
-                                    padding_left = 16, padding_right = 16)
-
-        self._buttonBox = gtk.HBox()
-        self._buttonBox.set_homogeneous(False)
-        self._defaultButton = None
-        buttonAlignment.add(self._buttonBox)
-
-        self._vbox.pack_start(buttonAlignment, False, False, 0)
-
-        self._wizard = wizard
-
-        self._finalized = False
-        self._fromPage = None
-
-    def setMainWidget(self, widget):
-        """Set the given widget as the main one."""
-        self._mainAlignment.add(widget)
-
-    def addButton(self, label, default = False):
-        """Add a button with the given label.
-
-        Return the button object created."""
-        button = gtk.Button(label)
-        self._buttonBox.pack_start(button, False, False, 4)
-        button.set_use_underline(True)
-        if default:
-            button.set_can_default(True)
-            self._defaultButton = button
-        return button
-
-    def activate(self):
-        """Called when this page becomes active.
-
-        This default implementation does nothing."""
-        pass
-
-    def finalize(self):
-        """Called when the page is finalized."""
-        pass
-
-    def grabDefault(self):
-        """If the page has a default button, make it the default one."""
-        if self._defaultButton is not None:
-            self._defaultButton.grab_default()
-
-    def reset(self):
-        """Reset the page if the wizard is reset."""
-        self._finalized = False
-        self._fromPage = None
-
-    def goBack(self):
-        """Go to the page we were invoked from."""
-        assert self._fromPage is not None
-        
-        self._wizard.setCurrentPage(self._fromPage, finalize = False)
-    
-#-----------------------------------------------------------------------------
-
-class LoginPage(Page):
-    """The login page."""
-    def __init__(self, wizard):
-        """Construct the login page."""
-        help = "Enter your MAVA pilot's ID and password to\n" \
-        "log in to the MAVA website and download\n" \
-        "your booked flights."
-        super(LoginPage, self).__init__(wizard, "Login", help)
-
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        
-        table = gtk.Table(2, 3)
-        table.set_row_spacings(4)
-        table.set_col_spacings(32)
-        alignment.add(table)
-        self.setMainWidget(alignment)
-
-        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
-        label = gtk.Label("Pilot _ID:")
-        label.set_use_underline(True)
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 0, 1, 0, 1)
-
-        self._pilotID = gtk.Entry()
-        self._pilotID.connect("changed", self._setLoginButton)
-        self._pilotID.set_tooltip_text("Enter your MAVA pilot's ID. This "
-                                       "usually starts with a "
-                                       "'P' followed by 3 digits.")
-        table.attach(self._pilotID, 1, 2, 0, 1)
-        label.set_mnemonic_widget(self._pilotID)
-
-        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
-        label = gtk.Label("_Password:")
-        label.set_use_underline(True)
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 0, 1, 1, 2)
-
-        self._password = gtk.Entry()
-        self._password.set_visibility(False)
-        self._password.connect("changed", self._setLoginButton)
-        self._password.set_tooltip_text("Enter the password for your pilot's ID")
-        table.attach(self._password, 1, 2, 1, 2)
-        label.set_mnemonic_widget(self._password)
-
-        self._rememberButton = gtk.CheckButton("_Remember password")
-        self._rememberButton.set_use_underline(True)
-        self._rememberButton.set_tooltip_text("If checked, your password will "
-                                              "be stored, so that you should "
-                                              "not have to enter it every time. "
-                                              "Note, however, that the password "
-                                              "is stored as text, and anybody "
-                                              "who can access your files will "
-                                              "be able to read it.")
-        table.attach(self._rememberButton, 1, 2, 2, 3, ypadding = 8)
-
-        self._loginButton = self.addButton("_Login", default = True)
-        self._loginButton.set_sensitive(False)
-        self._loginButton.connect("clicked", self._loginClicked)
-        self._loginButton.set_tooltip_text("Click to log in.")
-        
-        config = self._wizard.gui.config
-        self._pilotID.set_text(config.pilotID)
-        self._password.set_text(config.password)
-        self._rememberButton.set_active(config.rememberPassword)
-
-    def _setLoginButton(self, entry):
-        """Set the login button's sensitivity.
-
-        The button is sensitive only if both the pilot ID and the password
-        fields contain values."""
-        self._loginButton.set_sensitive(self._pilotID.get_text()!="" and
-                                        self._password.get_text()!="")
-
-    def _loginClicked(self, button):
-        """Called when the login button was clicked."""
-        self._loginButton.set_sensitive(False)
-        gui = self._wizard.gui
-        gui.beginBusy("Logging in...")
-        gui.webHandler.login(self._loginResultCallback,
-                            self._pilotID.get_text(),
-                            self._password.get_text())
-
-    def _loginResultCallback(self, returned, result):
-        """The login result callback, called in the web handler's thread."""
-        gobject.idle_add(self._handleLoginResult, returned, result)
-
-    def _handleLoginResult(self, returned, result):
-        """Handle the login result."""
-        self._wizard.gui.endBusy()
-        self._loginButton.set_sensitive(True)
-        if returned:
-            if result.loggedIn:
-                config = self._wizard.gui.config
-
-                config.pilotID = self._pilotID.get_text()
-
-                rememberPassword = self._rememberButton.get_active()                
-                config.password = self._password.get_text() if rememberPassword  \
-                                  else ""
-
-                config.rememberPassword = rememberPassword
-                
-                config.save()
-                self._wizard._loginResult = result
-                self._wizard.nextPage()
-            else:
-                dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
-                                           buttons = BUTTONSTYPE_OK,
-                                           message_format =
-                                           "Invalid pilot's ID or password.")
-                dialog.format_secondary_markup("Check the ID and try to reenter"
-                                               " the password.")
-                dialog.run()
-                dialog.hide()
-        else:
-            dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
-                                       buttons = BUTTONSTYPE_OK,
-                                       message_format = 
-                                       "Failed to connect to the MAVA website.")
-            dialog.format_secondary_markup("Try again in a few minutes.")
-            dialog.run()
-            dialog.hide()
-
-#-----------------------------------------------------------------------------
-
-class FlightSelectionPage(Page):
-    """The page to select the flight."""
-    def __init__(self, wizard):
-        """Construct the flight selection page."""
-        super(FlightSelectionPage, self).__init__(wizard, "Flight selection",
-                                                  "Select the flight you want "
-                                                  "to perform.")
-
-
-        self._listStore = gtk.ListStore(str, str, str, str)
-        self._flightList = gtk.TreeView(self._listStore)
-        column = gtk.TreeViewColumn("Flight no.", gtk.CellRendererText(),
-                                    text = 1)
-        column.set_expand(True)
-        self._flightList.append_column(column)
-        column = gtk.TreeViewColumn("Departure time [UTC]", gtk.CellRendererText(),
-                                    text = 0)
-        column.set_expand(True)
-        self._flightList.append_column(column)
-        column = gtk.TreeViewColumn("From", gtk.CellRendererText(),
-                                    text = 2)
-        column.set_expand(True)
-        self._flightList.append_column(column)
-        column = gtk.TreeViewColumn("To", gtk.CellRendererText(),
-                                    text = 3)
-        column.set_expand(True)
-        self._flightList.append_column(column)
-
-        flightSelection = self._flightList.get_selection()
-        flightSelection.connect("changed", self._selectionChanged)
-
-        scrolledWindow = gtk.ScrolledWindow()
-        scrolledWindow.add(self._flightList)
-        scrolledWindow.set_size_request(400, -1)
-        scrolledWindow.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
-                                  else gtk.POLICY_AUTOMATIC,
-                                  gtk.PolicyType.AUTOMATIC if pygobject
-                                  else gtk.POLICY_AUTOMATIC)
-        scrolledWindow.set_shadow_type(gtk.ShadowType.IN if pygobject
-                                       else gtk.SHADOW_IN)
-
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.0, xscale = 0.0, yscale = 1.0)
-        alignment.add(scrolledWindow)
-
-        self.setMainWidget(alignment)
-
-        self._button = self.addButton(gtk.STOCK_GO_FORWARD, default = True)
-        self._button.set_use_stock(True)
-        self._button.set_sensitive(False)
-        self._button.connect("clicked", self._forwardClicked)
-
-    def activate(self):
-        """Fill the flight list."""
-        self._flightList.set_sensitive(True)
-        self._listStore.clear()
-        for flight in self._wizard.loginResult.flights:
-            self._listStore.append([str(flight.departureTime),
-                                    flight.callsign,
-                                    flight.departureICAO,
-                                    flight.arrivalICAO])
-
-    def finalize(self):
-        """Finalize the page."""
-        self._flightList.set_sensitive(False)
-
-    def _selectionChanged(self, selection):
-        """Called when the selection is changed."""
-        self._button.set_sensitive(selection.count_selected_rows()==1)
-
-    def _forwardClicked(self, button):
-        """Called when the forward button was clicked."""
-        if self._finalized:
-            self._wizard.jumpPage(self._nextDistance, finalize = False)
-        else:
-            selection = self._flightList.get_selection()
-            (listStore, iter) = selection.get_selected()
-            path = listStore.get_path(iter)
-            [index] = path.get_indices() if pygobject else path
-
-            flight = self._wizard.loginResult.flights[index]
-            self._wizard._bookedFlight = flight
-
-            self._updateDepartureGate()
-        
-    def _updateDepartureGate(self):
-        """Update the departure gate for the booked flight."""
-        flight = self._wizard._bookedFlight
-        if flight.departureICAO=="LHBP":
-            self._wizard._getFleet(self._fleetRetrieved)
-        else:
-            self._nextDistance = 2
-            self._wizard.jumpPage(2)
-
-    def _fleetRetrieved(self, fleet):
-        """Called when the fleet has been retrieved."""
-        if fleet is None:
-            self._nextDistance = 2
-            self._wizard.jumpPage(2)
-        else:
-            plane = fleet[self._wizard._bookedFlight.tailNumber]
-            if plane is None:
-                self._nextDistance = 2
-                self._wizard.jumpPage(2)
-            elif plane.gateNumber is not None and \
-                 not fleet.isGateConflicting(plane):
-                self._wizard._departureGate = plane.gateNumber
-                self._nextDistance = 2
-                self._wizard.jumpPage(2)
-            else:
-                self._nextDistance = 1
-                self._wizard.nextPage()
-        
-#-----------------------------------------------------------------------------
-
-class GateSelectionPage(Page):
-    """Page to select a free gate at LHBP.
-
-    This page should be displayed only if we have fleet information!."""
-    def __init__(self, wizard):
-        """Construct the gate selection page."""
-        help = "The airplane's gate position is invalid.\n\n" \
-               "Select the gate from which you\n" \
-               "would like to begin the flight."
-        super(GateSelectionPage, self).__init__(wizard,
-                                                "LHBP gate selection",
-                                                help)
-
-        self._listStore = gtk.ListStore(str)
-        self._gateList = gtk.TreeView(self._listStore)
-        column = gtk.TreeViewColumn(None, gtk.CellRendererText(),
-                                    text = 0)
-        column.set_expand(True)
-        self._gateList.append_column(column)
-        self._gateList.set_headers_visible(False)
-
-        gateSelection = self._gateList.get_selection()
-        gateSelection.connect("changed", self._selectionChanged)
-
-        scrolledWindow = gtk.ScrolledWindow()
-        scrolledWindow.add(self._gateList)
-        scrolledWindow.set_size_request(50, -1)
-        scrolledWindow.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
-                                  else gtk.POLICY_AUTOMATIC,
-                                  gtk.PolicyType.AUTOMATIC if pygobject
-                                  else gtk.POLICY_AUTOMATIC)
-        scrolledWindow.set_shadow_type(gtk.ShadowType.IN if pygobject
-                                       else gtk.SHADOW_IN)
-
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.0, xscale = 0.0, yscale = 1.0)
-        alignment.add(scrolledWindow)
-
-        self.setMainWidget(alignment)        
-
-        button = self.addButton(gtk.STOCK_GO_BACK)
-        button.set_use_stock(True)
-        button.connect("clicked", self._backClicked)
-        
-        self._button = self.addButton(gtk.STOCK_GO_FORWARD, default = True)
-        self._button.set_use_stock(True)
-        self._button.set_sensitive(False)
-        self._button.connect("clicked", self._forwardClicked)
-
-    def activate(self):
-        """Fill the gate list."""
-        self._listStore.clear()
-        self._gateList.set_sensitive(True)
-        occupiedGateNumbers = self._wizard._fleet.getOccupiedGateNumbers()
-        for gateNumber in const.lhbpGateNumbers:
-            if gateNumber not in occupiedGateNumbers:
-                self._listStore.append([gateNumber])
-
-    def finalize(self):
-        """Finalize the page."""
-        self._gateList.set_sensitive(False)
-
-    def _selectionChanged(self, selection):
-        """Called when the selection is changed."""
-        self._button.set_sensitive(selection.count_selected_rows()==1)
-
-    def _backClicked(self, button):
-        """Called when the Back button is pressed."""
-        self.goBack()
-
-    def _forwardClicked(self, button):
-        """Called when the forward button is clicked."""
-        if not self._finalized:
-            selection = self._gateList.get_selection()
-            (listStore, iter) = selection.get_selected()
-            (gateNumber,) = listStore.get(iter, 0)
-
-            self._wizard._departureGate = gateNumber
-
-            #self._wizard._updatePlane(self._planeUpdated,
-            #                          self._wizard._bookedFlight.tailNumber,
-            #                          const.PLANE_HOME,
-            #                          gateNumber)
-        
-        self._wizard.nextPage()
-
-    def _planeUpdated(self, success):
-        """Callback for the plane updating call."""
-        if success is None or success:
-            self._wizard.nextPage()
-        else:
-            dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
-                                       buttons = BUTTONSTYPE_OK,
-                                       message_format = "Gate conflict detected again")
-            dialog.format_secondary_markup("Try to select a different gate.")
-            dialog.run()
-            dialog.hide()
-
-            self._wizard._getFleet(self._fleetRetrieved)
-
-    def _fleetRetrieved(self, fleet):
-        """Called when the fleet has been retrieved."""
-        if fleet is None:
-            self._wizard.nextPage()
-        else:
-            self.activate()
-            
-#-----------------------------------------------------------------------------
-
-class ConnectPage(Page):
-    """Page which displays the departure airport and gate (if at LHBP)."""
-    def __init__(self, wizard):
-        """Construct the connect page."""
-        help = "The flight begins at the airport given below.\n" \
-               "Park your aircraft there, at the gate below, if given.\n\n" \
-               "Then press the Connect button to connect to the simulator."
-        super(ConnectPage, self).__init__(wizard,
-                                          "Connect to the simulator",
-                                          help)
-        
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-
-        table = gtk.Table(2, 2)
-        table.set_row_spacings(4)
-        table.set_col_spacings(16)
-        table.set_homogeneous(True)
-        alignment.add(table)
-        self.setMainWidget(alignment)
-
-        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
-        label = gtk.Label("ICAO code:")
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 0, 1, 0, 1)
-
-        labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
-        self._departureICAO = gtk.Label()
-        self._departureICAO.set_width_chars(5)
-        self._departureICAO.set_alignment(0.0, 0.5)
-        labelAlignment.add(self._departureICAO)
-        table.attach(labelAlignment, 1, 2, 0, 1)
-
-        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
-        label = gtk.Label("Gate:")
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 0, 1, 1, 2)
-
-        labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
-        self._departureGate = gtk.Label()
-        self._departureGate.set_width_chars(5)
-        self._departureGate.set_alignment(0.0, 0.5)
-        labelAlignment.add(self._departureGate)
-        table.attach(labelAlignment, 1, 2, 1, 2)
-
-        button = self.addButton(gtk.STOCK_GO_BACK)
-        button.set_use_stock(True)
-        button.connect("clicked", self._backClicked)
-
-        self._button = self.addButton("_Connect", default = True)
-        self._button.set_use_underline(True)
-        self._clickedID = self._button.connect("clicked", self._connectClicked)
-
-    def activate(self):
-        """Setup the departure information."""
-        self._button.set_label("_Connect")
-        self._button.set_use_underline(True)
-        self._button.disconnect(self._clickedID)
-        self._clickedID = self._button.connect("clicked", self._connectClicked)
-        
-        icao = self._wizard._bookedFlight.departureICAO
-        self._departureICAO.set_markup("<b>" + icao + "</b>")
-        gate = self._wizard._departureGate
-        if gate!="-":
-            gate = "<b>" + gate + "</b>"
-        self._departureGate.set_markup(gate)
-
-    def finalize(self):
-        """Finalize the page."""
-        self._button.set_label(gtk.STOCK_GO_FORWARD)
-        self._button.set_use_stock(True)
-        self._button.disconnect(self._clickedID)
-        self._clickedID = self._button.connect("clicked", self._forwardClicked)
-
-    def _backClicked(self, button):
-        """Called when the Back button is pressed."""
-        self.goBack()
-
-    def _connectClicked(self, button):
-        """Called when the Connect button is pressed."""
-        self._wizard._connectSimulator()
-
-    def _forwardClicked(self, button):
-        """Called when the Forward button is pressed."""
-        self._wizard.nextPage()
-
-#-----------------------------------------------------------------------------
-
-class PayloadPage(Page):
-    """Page to allow setting up the payload."""
-    def __init__(self, wizard):
-        """Construct the page."""
-        help = "The briefing contains the weights below.\n" \
-               "Setup the cargo weight here and the payload weight in the simulator.\n\n" \
-               "You can also check here what the simulator reports as ZFW."
-               
-        super(PayloadPage, self).__init__(wizard, "Payload", help)
-
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-
-        table = gtk.Table(7, 3)
-        table.set_row_spacings(4)
-        table.set_col_spacings(16)
-        table.set_homogeneous(False)
-        alignment.add(table)
-        self.setMainWidget(alignment)
-
-        label = gtk.Label("Crew:")
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 0, 1, 0, 1)
-
-        self._numCrew = gtk.Label()
-        self._numCrew.set_width_chars(6)
-        self._numCrew.set_alignment(1.0, 0.5)
-        table.attach(self._numCrew, 1, 2, 0, 1)
-        
-        label = gtk.Label("Passengers:")
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 0, 1, 1, 2)
-
-        self._numPassengers = gtk.Label()
-        self._numPassengers.set_width_chars(6)
-        self._numPassengers.set_alignment(1.0, 0.5)
-        table.attach(self._numPassengers, 1, 2, 1, 2)
-        
-        label = gtk.Label("Baggage:")
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 0, 1, 2, 3)
-
-        self._bagWeight = gtk.Label()
-        self._bagWeight.set_width_chars(6)
-        self._bagWeight.set_alignment(1.0, 0.5)
-        table.attach(self._bagWeight, 1, 2, 2, 3)
-
-        table.attach(gtk.Label("kg"), 2, 3, 2, 3)
-        
-        label = gtk.Label("_Cargo:")
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 0, 1, 3, 4)
-
-        self._cargoWeight = gtk.Entry()
-        self._cargoWeight.set_width_chars(6)
-        self._cargoWeight.set_alignment(1.0)
-        self._cargoWeight.connect("changed", self._cargoWeightChanged)
-        self._cargoWeight.set_tooltip_text("The weight of the cargo for your flight.")
-        table.attach(self._cargoWeight, 1, 2, 3, 4)
-        self._cargoWeightValue = 0        
-        label.set_mnemonic_widget(self._cargoWeight)
-
-        table.attach(gtk.Label("kg"), 2, 3, 3, 4)
-        
-        label = gtk.Label("Mail:")
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 0, 1, 4, 5)
-
-        self._mailWeight = gtk.Label()
-        self._mailWeight.set_width_chars(6)
-        self._mailWeight.set_alignment(1.0, 0.5)
-        table.attach(self._mailWeight, 1, 2, 4, 5)
-
-        table.attach(gtk.Label("kg"), 2, 3, 4, 5)
-        
-        label = gtk.Label("<b>Calculated ZFW:</b>")
-        label.set_alignment(0.0, 0.5)
-        label.set_use_markup(True)
-        table.attach(label, 0, 1, 5, 6)
-
-        self._calculatedZFW = gtk.Label()
-        self._calculatedZFW.set_width_chars(6)
-        self._calculatedZFW.set_alignment(1.0, 0.5)
-        table.attach(self._calculatedZFW, 1, 2, 5, 6)
-
-        table.attach(gtk.Label("kg"), 2, 3, 5, 6)
-
-        self._zfwButton = gtk.Button("_ZFW from FS:")
-        self._zfwButton.set_use_underline(True)
-        self._zfwButton.connect("clicked", self._zfwRequested)
-        table.attach(self._zfwButton, 0, 1, 6, 7)
-
-        self._simulatorZFW = gtk.Label("-")
-        self._simulatorZFW.set_width_chars(6)
-        self._simulatorZFW.set_alignment(1.0, 0.5)
-        table.attach(self._simulatorZFW, 1, 2, 6, 7)
-        self._simulatorZFWValue = None
-
-        table.attach(gtk.Label("kg"), 2, 3, 6, 7)
-
-        self._backButton = self.addButton(gtk.STOCK_GO_BACK)
-        self._backButton.set_use_stock(True)
-        self._backButton.connect("clicked", self._backClicked)
-
-        self._button = self.addButton(gtk.STOCK_GO_FORWARD, default = True)
-        self._button.set_use_stock(True)
-        self._button.connect("clicked", self._forwardClicked)
-
-    def activate(self):
-        """Setup the information."""
-        bookedFlight = self._wizard._bookedFlight
-        self._numCrew.set_text(str(bookedFlight.numCrew))
-        self._numPassengers.set_text(str(bookedFlight.numPassengers))
-        self._bagWeight.set_text(str(bookedFlight.bagWeight))
-        self._cargoWeightValue = bookedFlight.cargoWeight
-        self._cargoWeight.set_text(str(bookedFlight.cargoWeight))
-        self._cargoWeight.set_sensitive(True)
-        self._mailWeight.set_text(str(bookedFlight.mailWeight))
-        self._zfwButton.set_sensitive(True)
-        self._updateCalculatedZFW()
-
-    def finalize(self):
-        """Finalize the payload page."""
-        self._cargoWeight.set_sensitive(False)
-        self._zfwButton.set_sensitive(False)
-
-    def _calculateZFW(self):
-        """Calculate the ZFW value."""
-        zfw = self._wizard.gui._flight.aircraft.dow
-        bookedFlight = self._wizard._bookedFlight
-        zfw += (bookedFlight.numCrew + bookedFlight.numPassengers) * 82
-        zfw += bookedFlight.bagWeight
-        zfw += self._cargoWeightValue
-        zfw += bookedFlight.mailWeight
-        return zfw
-        
-    def _updateCalculatedZFW(self):
-        """Update the calculated ZFW"""
-        zfw = self._calculateZFW()
-
-        markupBegin = "<b>"
-        markupEnd = "</b>"
-        if self._simulatorZFWValue is not None and \
-           PayloadChecker.isZFWFaulty(self._simulatorZFWValue, zfw):
-            markupBegin += '<span foreground="red">'
-            markupEnd = "</span>" + markupEnd
-        self._calculatedZFW.set_markup(markupBegin + str(zfw) + markupEnd)
-
-    def _cargoWeightChanged(self, entry):
-        """Called when the cargo weight has changed."""
-        text = self._cargoWeight.get_text()
-        if text=="":
-            self._cargoWeightValue = 0
-        else:
-            try:
-                self._cargoWeightValue = int(text)
-            except:
-                self._cargoWeight.set_text(str(self._cargoWeightValue))
-        self._updateCalculatedZFW()
-            
-    def _zfwRequested(self, button):
-        """Called when the ZFW is requested from the simulator."""
-        self._zfwButton.set_sensitive(False)
-        self._backButton.set_sensitive(False)
-        self._button.set_sensitive(False)
-        gui = self._wizard.gui
-        gui.beginBusy("Querying ZFW...")
-        gui.simulator.requestZFW(self._handleZFW)
-
-    def _handleZFW(self, zfw):
-        """Called when the ZFW value is retrieved."""
-        gobject.idle_add(self._processZFW, zfw)
-
-    def _processZFW(self, zfw):
-        """Process the given ZFW value received from the simulator."""
-        self._wizard.gui.endBusy()
-        self._zfwButton.set_sensitive(True)
-        self._backButton.set_sensitive(True)
-        self._button.set_sensitive(True)
-        self._simulatorZFWValue = zfw
-        self._simulatorZFW.set_text("%.0f" % (zfw,))
-        self._updateCalculatedZFW()
-
-    def _forwardClicked(self, button):
-        """Called when the forward button is clicked."""
-        if not self._finalized:
-            self._wizard._zfw = self._calculateZFW()
-        self._wizard.nextPage()
-
-    def _backClicked(self, button):
-        """Called when the Back button is pressed."""
-        self.goBack()
-        
-#-----------------------------------------------------------------------------
-
-class TimePage(Page):
-    """Page displaying the departure and arrival times and allows querying the
-    current time from the flight simulator."""
-    def __init__(self, wizard):
-        help = "The departure and arrival times are displayed below in UTC.\n\n" \
-               "You can also query the current UTC time from the simulator.\n" \
-               "Ensure that you have enough time to properly prepare for the flight."
-               
-        super(TimePage, self).__init__(wizard, "Time", help)
-
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-
-        table = gtk.Table(3, 2)
-        table.set_row_spacings(4)
-        table.set_col_spacings(16)
-        table.set_homogeneous(False)
-        alignment.add(table)
-        self.setMainWidget(alignment)
-
-        label = gtk.Label("Departure:")
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 0, 1, 0, 1)
-
-        self._departure = gtk.Label()
-        self._departure.set_alignment(0.0, 0.5)
-        table.attach(self._departure, 1, 2, 0, 1)
-        
-        label = gtk.Label("Arrival:")
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 0, 1, 1, 2)
-
-        self._arrival = gtk.Label()
-        self._arrival.set_alignment(0.0, 0.5)
-        table.attach(self._arrival, 1, 2, 1, 2)
-
-        self._timeButton = gtk.Button("_Time from FS:")
-        self._timeButton.set_use_underline(True)
-        self._timeButton.connect("clicked", self._timeRequested)
-        table.attach(self._timeButton, 0, 1, 2, 3)
-
-        self._simulatorTime = gtk.Label("-")
-        self._simulatorTime.set_alignment(0.0, 0.5)
-        table.attach(self._simulatorTime, 1, 2, 2, 3)
-
-        self._backButton = self.addButton(gtk.STOCK_GO_BACK)
-        self._backButton.set_use_stock(True)
-        self._backButton.connect("clicked", self._backClicked)
-
-        self._button = self.addButton(gtk.STOCK_GO_FORWARD, default = True)
-        self._button.set_use_stock(True)
-        self._button.connect("clicked", self._forwardClicked)
-
-    def activate(self):
-        """Activate the page."""
-        self._timeButton.set_sensitive(True)
-        bookedFlight = self._wizard._bookedFlight
-        self._departure.set_text(str(bookedFlight.departureTime.time()))
-        self._arrival.set_text(str(bookedFlight.arrivalTime.time()))
-
-    def finalize(self):
-        """Finalize the page."""
-        self._timeButton.set_sensitive(False)
-
-    def _timeRequested(self, button):
-        """Request the time from the simulator."""
-        self._timeButton.set_sensitive(False)
-        self._backButton.set_sensitive(False)
-        self._button.set_sensitive(False)
-        self._wizard.gui.beginBusy("Querying time...")
-        self._wizard.gui.simulator.requestTime(self._handleTime)
-
-    def _handleTime(self, timestamp):
-        """Handle the result of a time retrieval."""
-        gobject.idle_add(self._processTime, timestamp)
-
-    def _processTime(self, timestamp):
-        """Process the given time."""
-        self._wizard.gui.endBusy()
-        self._timeButton.set_sensitive(True)
-        self._backButton.set_sensitive(True)
-        self._button.set_sensitive(True)
-        tm = time.gmtime(timestamp)
-        t = datetime.time(tm.tm_hour, tm.tm_min, tm.tm_sec)
-        self._simulatorTime.set_text(str(t))
-
-        ts = tm.tm_hour * 3600 + tm.tm_min * 60 + tm.tm_sec
-        dt = self._wizard._bookedFlight.departureTime.time()
-        dts = dt.hour * 3600 + dt.minute * 60 + dt.second
-        diff = dts-ts
-
-        markupBegin = ""
-        markupEnd = ""
-        if diff < 0:
-            markupBegin = '<b><span foreground="red">'
-            markupEnd = '</span></b>'
-        elif diff < 3*60 or diff > 30*60:
-            markupBegin = '<b><span foreground="orange">'
-            markupEnd = '</span></b>'
-
-        self._departure.set_markup(markupBegin + str(dt) + markupEnd)
-
-    def _backClicked(self, button):
-        """Called when the Back button is pressed."""
-        self.goBack()
-        
-    def _forwardClicked(self, button):
-        """Called when the forward button is clicked."""
-        self._wizard.nextPage()
-        
-#-----------------------------------------------------------------------------
-
-class RoutePage(Page):
-    """The page containing the route and the flight level."""
-    def __init__(self, wizard):
-        help = "Set your cruise flight level below, and\n" \
-               "if necessary, edit the flight plan."
-               
-        super(RoutePage, self).__init__(wizard, "Route", help)
-
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-
-        mainBox = gtk.VBox()
-        alignment.add(mainBox)
-        self.setMainWidget(alignment)
-
-        levelBox = gtk.HBox()
-
-        label = gtk.Label("_Cruise level")
-        label.set_use_underline(True)
-        levelBox.pack_start(label, True, True, 0)
-
-        self._cruiseLevel = gtk.SpinButton()
-        self._cruiseLevel.set_increments(step = 10, page = 100)
-        self._cruiseLevel.set_range(min = 50, max = 500)
-        self._cruiseLevel.set_value(240)
-        self._cruiseLevel.set_tooltip_text("The cruise flight level.")
-        self._cruiseLevel.set_numeric(True)
-        self._cruiseLevel.connect("value-changed", self._cruiseLevelChanged)
-        label.set_mnemonic_widget(self._cruiseLevel)        
-
-        levelBox.pack_start(self._cruiseLevel, False, False, 8)
-
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(levelBox)
-
-        mainBox.pack_start(alignment, False, False, 0)
-
-
-        routeBox = gtk.VBox()
-
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        label = gtk.Label("_Route")
-        label.set_use_underline(True)
-        alignment.add(label)
-        routeBox.pack_start(alignment, True, True, 0)
-
-        routeWindow = gtk.ScrolledWindow()
-        routeWindow.set_size_request(400, 80)
-        routeWindow.set_shadow_type(gtk.ShadowType.IN if pygobject
-                                    else gtk.SHADOW_IN)
-        routeWindow.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
-                               else gtk.POLICY_AUTOMATIC,
-                               gtk.PolicyType.AUTOMATIC if pygobject
-                               else gtk.POLICY_AUTOMATIC)
-
-        self._route = gtk.TextView()
-        self._route.set_tooltip_text("The planned flight route.")
-        self._route.get_buffer().connect("changed", self._routeChanged)
-        routeWindow.add(self._route)
-
-        label.set_mnemonic_widget(self._route)
-        routeBox.pack_start(routeWindow, True, True, 0)        
-
-        mainBox.pack_start(routeBox, True, True, 8)
-
-        self._backButton = self.addButton(gtk.STOCK_GO_BACK)
-        self._backButton.set_use_stock(True)
-        self._backButton.connect("clicked", self._backClicked)
-
-        self._button = self.addButton(gtk.STOCK_GO_FORWARD, default = True)
-        self._button.set_use_stock(True)
-        self._button.connect("clicked", self._forwardClicked)
-
-    def activate(self):
-        """Setup the route from the booked flight."""
-        self._route.set_sensitive(True)
-        self._cruiseLevel.set_sensitive(True)
-        self._route.get_buffer().set_text(self._wizard._bookedFlight.route)
-        self._updateForwardButton()
-
-    def finalize(self):
-        """Finalize the page."""
-        self._route.set_sensitive(False)
-        self._cruiseLevel.set_sensitive(False)
-
-    def _getRoute(self):
-        """Get the text of the route."""
-        buffer = self._route.get_buffer()
-        return buffer.get_text(buffer.get_start_iter(),
-                               buffer.get_end_iter(), True)
-
-    def _updateForwardButton(self):
-        """Update the sensitivity of the forward button."""
-        self._button.set_sensitive(self._cruiseLevel.get_value_as_int()>=50 and \
-                                   self._getRoute()!="")
-                                   
-    def _cruiseLevelChanged(self, spinButton):
-        """Called when the cruise level has changed."""
-        self._updateForwardButton()
-
-    def _routeChanged(self, textBuffer):
-        """Called when the route has changed."""
-        self._updateForwardButton()
-
-    def _backClicked(self, button):
-        """Called when the Back button is pressed."""
-        self.goBack()
-        
-    def _forwardClicked(self, button):
-        """Called when the Forward button is clicked."""
-        if self._finalized:
-            self._wizard.nextPage()
-        else:
-            self._wizard._cruiseAltitude = self._cruiseLevel.get_value_as_int() * 100
-            self._wizard._route = self._getRoute()
-
-            self._backButton.set_sensitive(False)
-            self._button.set_sensitive(False)
-            self._cruiseLevel.set_sensitive(False)
-            self._route.set_sensitive(False)
-
-            bookedFlight = self._wizard._bookedFlight
-            self._wizard.gui.beginBusy("Downloading NOTAMs...")
-            self._wizard.gui.webHandler.getNOTAMs(self._notamsCallback,
-                                                  bookedFlight.departureICAO,
-                                                  bookedFlight.arrivalICAO)
-
-    def _notamsCallback(self, returned, result):
-        """Callback for the NOTAMs."""
-        gobject.idle_add(self._handleNOTAMs, returned, result)
-
-    def _handleNOTAMs(self, returned, result):
-        """Handle the NOTAMs."""
-        if returned:
-            self._wizard._departureNOTAMs = result.departureNOTAMs
-            self._wizard._arrivalNOTAMs = result.arrivalNOTAMs
-        else:
-            self._wizard._departureNOTAMs = None
-            self._wizard._arrivalNOTAMs = None
-
-        bookedFlight = self._wizard._bookedFlight
-        self._wizard.gui.beginBusy("Downloading METARs...")
-        self._wizard.gui.webHandler.getMETARs(self._metarsCallback,
-                                              [bookedFlight.departureICAO,
-                                               bookedFlight.arrivalICAO])
-
-    def _metarsCallback(self, returned, result):
-        """Callback for the METARs."""
-        gobject.idle_add(self._handleMETARs, returned, result)
-
-    def _handleMETARs(self, returned, result):
-        """Handle the METARs."""
-        self._wizard._departureMETAR = None
-        self._wizard._arrivalMETAR = None
-        bookedFlight = self._wizard._bookedFlight
-        if returned:
-            if bookedFlight.departureICAO in result.metars:
-                self._wizard._departureMETAR = result.metars[bookedFlight.departureICAO]
-            if bookedFlight.arrivalICAO in result.metars:
-                self._wizard._arrivalMETAR = result.metars[bookedFlight.arrivalICAO]
-
-        self._wizard.gui.endBusy()
-        self._backButton.set_sensitive(True)
-        self._button.set_sensitive(True)
-        self._wizard.nextPage()
-
-#-----------------------------------------------------------------------------
-
-class BriefingPage(Page):
-    """Page for the briefing."""
-    def __init__(self, wizard, departure):
-        """Construct the briefing page."""
-        self._departure = departure
-        
-        title = "Briefing (%d/2): %s" % (1 if departure else 2,
-                                        "departure" if departure
-                                         else "arrival")
-                                                                
-        help = "Read carefully the NOTAMs and METAR below."
-
-        super(BriefingPage, self).__init__(wizard, title, help)
-
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                  xscale = 1.0, yscale = 1.0)
-
-        mainBox = gtk.VBox()
-        alignment.add(mainBox)
-        self.setMainWidget(alignment)
-
-        self._notamsFrame = gtk.Frame()
-        self._notamsFrame.set_label("LHBP NOTAMs")
-        scrolledWindow = gtk.ScrolledWindow()
-        scrolledWindow.set_size_request(-1, 128)
-        scrolledWindow.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
-                                  else gtk.POLICY_AUTOMATIC,
-                                  gtk.PolicyType.AUTOMATIC if pygobject
-                                  else gtk.POLICY_AUTOMATIC)
-        self._notams = gtk.TextView()
-        self._notams.set_editable(False)
-        self._notams.set_accepts_tab(False)
-        self._notams.set_wrap_mode(gtk.WrapMode.WORD if pygobject else gtk.WRAP_WORD)
-        scrolledWindow.add(self._notams)
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
-                                  xscale = 1.0, yscale = 1.0)
-        alignment.set_padding(padding_top = 4, padding_bottom = 0,
-                              padding_left = 0, padding_right = 0)
-        alignment.add(scrolledWindow)
-        self._notamsFrame.add(alignment)
-        mainBox.pack_start(self._notamsFrame, True, True, 4)
-        
-        self._metarFrame = gtk.Frame()
-        self._metarFrame.set_label("LHBP METAR")
-        scrolledWindow = gtk.ScrolledWindow()
-        scrolledWindow.set_size_request(-1, 32)
-        scrolledWindow.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
-                                  else gtk.POLICY_AUTOMATIC,
-                                  gtk.PolicyType.AUTOMATIC if pygobject
-                                  else gtk.POLICY_AUTOMATIC)
-        self._metar = gtk.TextView()
-        self._metar.set_editable(False)
-        self._metar.set_accepts_tab(False)
-        self._metar.set_wrap_mode(gtk.WrapMode.WORD if pygobject else gtk.WRAP_WORD)
-        scrolledWindow.add(self._metar)
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
-                                  xscale = 1.0, yscale = 1.0)
-        alignment.set_padding(padding_top = 4, padding_bottom = 0,
-                              padding_left = 0, padding_right = 0)
-        alignment.add(scrolledWindow)
-        self._metarFrame.add(alignment)
-        mainBox.pack_start(self._metarFrame, True, True, 4)
-
-        button = self.addButton(gtk.STOCK_GO_BACK)
-        button.set_use_stock(True)
-        button.connect("clicked", self._backClicked)
-
-        self._button = self.addButton(gtk.STOCK_GO_FORWARD, default = True)
-        self._button.set_use_stock(True)
-        self._button.connect("clicked", self._forwardClicked)
-
-    def activate(self):
-        """Activate the page."""
-        if not self._departure:
-            self._button.set_label("I have read the briefing and am ready to fly!")
-            self._button.set_use_stock(False)
-
-        bookedFlight = self._wizard._bookedFlight
-
-        icao = bookedFlight.departureICAO if self._departure \
-               else bookedFlight.arrivalICAO
-        notams = self._wizard._departureNOTAMs if self._departure \
-                 else self._wizard._arrivalNOTAMs
-        metar = self._wizard._departureMETAR if self._departure \
-                 else self._wizard._arrivalMETAR
-
-        self._notamsFrame.set_label(icao + " NOTAMs")
-        buffer = self._notams.get_buffer()
-        if notams is None:
-            buffer.set_text("Could not download NOTAMs")
-        else:
-            s = ""
-            for notam in notams:
-                s += str(notam.begin)
-                if notam.end is not None:
-                    s += " - " + str(notam.end)
-                elif notam.permanent:
-                    s += " - PERMANENT"
-                s += "\n"
-                if notam.repeatCycle:
-                    s += "Repeat cycle: " + notam.repeatCycle + "\n"
-                s += notam.notice + "\n"
-                s += "-------------------- * --------------------\n"
-            buffer.set_text(s)
-
-        self._metarFrame.set_label(icao + " METAR")
-        buffer = self._metar.get_buffer()
-        if metar is None:
-            buffer.set_text("Could not download METAR")
-        else:
-            buffer.set_text(metar)
-
-    def finalize(self):
-        """Finalize the page."""
-        if not self._departure:
-            self._button.set_use_stock(True)
-            self._button.set_label(gtk.STOCK_GO_FORWARD)
-
-    def _backClicked(self, button):
-        """Called when the Back button is pressed."""
-        self.goBack()
-        
-    def _forwardClicked(self, button):
-        """Called when the forward button is clicked."""
-        if not self._departure:
-            if not self._finalized:
-                self._wizard.gui.startMonitoring()
-                self.finalize()
-                self._finalized = True
-
-        self._wizard.nextPage()
-
-#-----------------------------------------------------------------------------
-
-class TakeoffPage(Page):
-    """Page for entering the takeoff data."""
-    def __init__(self, wizard):
-        """Construct the takeoff page."""
-        help = "Enter the runway and SID used, as well as the speeds."
-
-        super(TakeoffPage, self).__init__(wizard, "Takeoff", help)
-
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-
-        table = gtk.Table(5, 3)
-        table.set_row_spacings(4)
-        table.set_col_spacings(16)
-        table.set_homogeneous(False)
-        alignment.add(table)
-        self.setMainWidget(alignment)
-
-        label = gtk.Label("Run_way:")
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 0, 1, 0, 1)
-
-        self._runway = gtk.Entry()
-        self._runway.set_width_chars(10)
-        self._runway.set_tooltip_text("The runway the takeoff is performed from.")
-        self._runway.connect("changed", self._updateForwardButton)
-        table.attach(self._runway, 1, 2, 0, 1)
-        label.set_mnemonic_widget(self._runway)
-        
-        label = gtk.Label("_SID:")
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 0, 1, 1, 2)
-
-        self._sid = gtk.Entry()
-        self._sid.set_width_chars(10)
-        self._sid.set_tooltip_text("The name of the Standard Instrument Deparature procedure followed.")
-        self._sid.connect("changed", self._updateForwardButton)
-        table.attach(self._sid, 1, 2, 1, 2)
-        label.set_mnemonic_widget(self._sid)
-        
-        label = gtk.Label("V<sub>_1</sub>:")
-        label.set_use_markup(True)
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 0, 1, 2, 3)
-
-        self._v1 = gtk.SpinButton()
-        self._v1.set_increments(step = 1, page = 10)
-        self._v1.set_range(min = 50, max = 300)
-        self._v1.set_value(100)
-        self._v1.set_numeric(True)
-        self._v1.set_tooltip_markup("The takeoff decision speed in knots.")
-        self._v1.connect("changed", self._updateForwardButton)
-        table.attach(self._v1, 1, 2, 2, 3)
-        label.set_mnemonic_widget(self._v1)
-        
-        table.attach(gtk.Label("knots"), 2, 3, 2, 3)
-        
-        label = gtk.Label("V<sub>_r</sub>:")
-        label.set_use_markup(True)
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 0, 1, 3, 4)
-
-        self._vr = gtk.SpinButton()
-        self._vr.set_increments(step = 1, page = 10)
-        self._vr.set_range(min = 50, max = 300)
-        self._vr.set_value(110)
-        self._vr.set_numeric(True)
-        self._vr.set_tooltip_markup("The takeoff rotation speed in knots.")
-        self._vr.connect("changed", self._updateForwardButton)
-        table.attach(self._vr, 1, 2, 3, 4)
-        label.set_mnemonic_widget(self._vr)
-        
-        table.attach(gtk.Label("knots"), 2, 3, 3, 4)
-        
-        label = gtk.Label("V<sub>_2</sub>:")
-        label.set_use_markup(True)
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 0, 1, 4, 5)
-
-        self._v2 = gtk.SpinButton()
-        self._v2.set_increments(step = 1, page = 10)
-        self._v2.set_range(min = 50, max = 300)
-        self._v2.set_value(120)
-        self._v2.set_numeric(True)
-        self._v2.set_tooltip_markup("The takeoff safety speed in knots.")
-        self._v2.connect("changed", self._updateForwardButton)
-        table.attach(self._v2, 1, 2, 4, 5)
-        label.set_mnemonic_widget(self._v2)
-        
-        table.attach(gtk.Label("knots"), 2, 3, 4, 5)
-        
-        button = self.addButton(gtk.STOCK_GO_BACK)
-        button.set_use_stock(True)
-        button.connect("clicked", self._backClicked)
-
-        self._button = self.addButton(gtk.STOCK_GO_FORWARD, default = True)
-        self._button.set_use_stock(True)
-        self._button.connect("clicked", self._forwardClicked)
-
-    def activate(self):
-        """Activate the page."""
-        self._runway.set_text("")
-        self._runway.set_sensitive(True)
-        self._sid.set_text("")
-        self._sid.set_sensitive(True)
-        self._v1.set_sensitive(True)
-        self._vr.set_sensitive(True)
-        self._v2.set_sensitive(True)
-        self._updateForwardButton()
-        
-    def finalize(self):
-        """Finalize the page."""
-        self._runway.set_sensitive(False)
-        self._sid.set_sensitive(False)
-        self._v1.set_sensitive(False)
-        self._vr.set_sensitive(False)
-        self._v2.set_sensitive(False)
-
-        flight = self._wizard.gui.flight
-        flight.v1 = self._v1.get_value_as_int()
-        flight.vr = self._vr.get_value_as_int()
-        flight.v2 = self._v2.get_value_as_int()
-
-    def _updateForwardButton(self, widget = None):
-        """Update the Forward buttons sensitivity."""
-        self._button.set_sensitive(self._runway.get_text()!="" and
-                                   self._sid.get_text()!="" and
-                                   self._v1.get_value_as_int()<=self._vr.get_value_as_int() and 
-                                   self._vr.get_value_as_int()<=self._v2.get_value_as_int())
-
-    def _backClicked(self, button):
-        """Called when the Back button is pressed."""
-        self.goBack()
-        
-    def _forwardClicked(self, button):
-        """Called when the forward button is clicked."""
-        self._wizard.nextPage()
-
-#-----------------------------------------------------------------------------
-
-class LandingPage(Page):
-    """Page for entering landing data."""
-    def __init__(self, wizard):
-        """Construct the landing page."""
-        help = "Enter the STAR and/or transition, runway,\n" \
-               "approach type and V<sub>ref</sub> used."
-
-        super(LandingPage, self).__init__(wizard, "Landing", help)
-
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-
-        table = gtk.Table(5, 4)
-        table.set_row_spacings(4)
-        table.set_col_spacings(16)
-        table.set_homogeneous(False)
-        alignment.add(table)
-        self.setMainWidget(alignment)
-
-        self._starButton = gtk.CheckButton()
-        self._starButton.connect("clicked", self._starButtonClicked)
-        table.attach(self._starButton, 0, 1, 0, 1)
-
-        label = gtk.Label("_STAR:")
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 1, 2, 0, 1)
-
-        self._star = gtk.Entry()
-        self._star.set_width_chars(10)
-        self._star.set_tooltip_text("The name of Standard Terminal Arrival Route followed.")
-        self._star.connect("changed", self._updateForwardButton)
-        self._star.set_sensitive(False)
-        table.attach(self._star, 2, 3, 0, 1)
-        label.set_mnemonic_widget(self._starButton)
-
-        self._transitionButton = gtk.CheckButton()
-        self._transitionButton.connect("clicked", self._transitionButtonClicked)
-        table.attach(self._transitionButton, 0, 1, 1, 2)
-
-        label = gtk.Label("_Transition:")
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 1, 2, 1, 2)
-
-        self._transition = gtk.Entry()
-        self._transition.set_width_chars(10)
-        self._transition.set_tooltip_text("The name of transition executed or VECTORS if vectored by ATC.")
-        self._transition.connect("changed", self._updateForwardButton)
-        self._transition.set_sensitive(False)
-        table.attach(self._transition, 2, 3, 1, 2)
-        label.set_mnemonic_widget(self._transitionButton)
-
-        label = gtk.Label("Run_way:")
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 1, 2, 2, 3)
-
-        self._runway = gtk.Entry()
-        self._runway.set_width_chars(10)
-        self._runway.set_tooltip_text("The runway the landing is performed on.")
-        self._runway.connect("changed", self._updateForwardButton)
-        table.attach(self._runway, 2, 3, 2, 3)
-        label.set_mnemonic_widget(self._runway)
-
-        label = gtk.Label("_Approach type:")
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 1, 2, 3, 4)
-
-        self._approachType = gtk.Entry()
-        self._approachType.set_width_chars(10)
-        self._approachType.set_tooltip_text("The type of the approach, e.g. ILS or VISUAL.")
-        self._approachType.connect("changed", self._updateForwardButton)
-        table.attach(self._approachType, 2, 3, 3, 4)
-        label.set_mnemonic_widget(self._approachType)
-
-        label = gtk.Label("V<sub>_ref</sub>:")
-        label.set_use_markup(True)
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 1, 2, 5, 6)
-
-        self._vref = gtk.SpinButton()
-        self._vref.set_increments(step = 1, page = 10)
-        self._vref.set_range(min = 50, max = 300)
-        self._vref.set_value(140)
-        self._vref.set_numeric(True)
-        self._vref.set_tooltip_markup("The approach reference speed in knots.")
-        table.attach(self._vref, 2, 3, 5, 6)
-        label.set_mnemonic_widget(self._vref)
-        
-        table.attach(gtk.Label("knots"), 3, 4, 5, 6)
-        
-        button = self.addButton(gtk.STOCK_GO_BACK)
-        button.set_use_stock(True)
-        button.connect("clicked", self._backClicked)
-
-        self._button = self.addButton(gtk.STOCK_GO_FORWARD, default = True)
-        self._button.set_use_stock(True)
-        self._button.connect("clicked", self._forwardClicked)
-
-        # These are needed for correct size calculations
-        self._starButton.set_active(True)
-        self._transitionButton.set_active(True)
-
-    def activate(self):
-        """Called when the page is activated."""
-        self._starButton.set_sensitive(True)
-        self._starButton.set_active(False)
-        self._star.set_text("")
-
-        self._transitionButton.set_sensitive(True)
-        self._transitionButton.set_active(False)
-        self._transition.set_text("")
-
-        self._runway.set_text("")
-        self._runway.set_sensitive(True)
-
-        self._approachType.set_text("")
-        self._approachType.set_sensitive(True)
-
-        self._vref.set_value(140)
-        self._vref.set_sensitive(True)
-
-        self._updateForwardButton()
-
-    def finalize(self):
-        """Finalize the page."""
-        self._starButton.set_sensitive(False)
-        self._star.set_sensitive(False)
-
-        self._transitionButton.set_sensitive(False)
-        self._transition.set_sensitive(False)
-
-        self._runway.set_sensitive(False)
-
-        self._approachType.set_sensitive(False)
-
-        self._vref.set_sensitive(False)
-
-    def _starButtonClicked(self, button):
-        """Called when the STAR button is clicked."""
-        active = button.get_active()
-        self._star.set_sensitive(active)
-        if active:
-            self._star.grab_focus()
-        self._updateForwardButton()
-
-    def _transitionButtonClicked(self, button):
-        """Called when the Transition button is clicked."""
-        active = button.get_active()
-        self._transition.set_sensitive(active)
-        if active:
-            self._transition.grab_focus()        
-        self._updateForwardButton()
-
-    def _updateForwardButton(self, widget = None):
-        """Update the sensitivity of the forward button."""
-        sensitive = (self._starButton.get_active() or \
-                     self._transitionButton.get_active()) and \
-                    (self._star.get_text()!="" or
-                     not self._starButton.get_active()) and \
-                    (self._transition.get_text()!="" or
-                     not self._transitionButton.get_active()) and \
-                    self._runway.get_text()!="" and \
-                    self._approachType.get_text()!=""
-        self._button.set_sensitive(sensitive)
-
-    def _backClicked(self, button):
-        """Called when the Back button is pressed."""
-        self.goBack()
-        
-    def _forwardClicked(self, button):
-        """Called when the forward button is clicked."""
-        #self._wizard.nextPage()
-        self.finalize()
-
-#-----------------------------------------------------------------------------
-
-class Wizard(gtk.VBox):
-    """The flight wizard."""
-    def __init__(self, gui):
-        """Construct the wizard."""
-        super(Wizard, self).__init__()
-
-        self.gui = gui
-
-        self._pages = []
-        self._currentPage = None
-        
-        self._pages.append(LoginPage(self))
-        self._pages.append(FlightSelectionPage(self))
-        self._pages.append(GateSelectionPage(self))
-        self._pages.append(ConnectPage(self))
-        self._pages.append(PayloadPage(self))
-        self._pages.append(TimePage(self))
-        self._pages.append(RoutePage(self))
-        self._pages.append(BriefingPage(self, True))
-        self._pages.append(BriefingPage(self, False))
-        self._pages.append(TakeoffPage(self))
-        self._pages.append(LandingPage(self))
-        
-        maxWidth = 0
-        maxHeight = 0
-        for page in self._pages:
-            page.show_all()
-            pageSizeRequest = page.size_request()
-            width = pageSizeRequest.width if pygobject else pageSizeRequest[0]
-            height = pageSizeRequest.height if pygobject else pageSizeRequest[1]
-            maxWidth = max(maxWidth, width)
-            maxHeight = max(maxHeight, height)
-        maxWidth += 16
-        maxHeight += 32
-        self.set_size_request(maxWidth, maxHeight)
-
-        self._initialize()
-        
-    @property
-    def loginResult(self):
-        """Get the login result."""
-        return self._loginResult
-
-    def setCurrentPage(self, index, finalize = False):
-        """Set the current page to the one with the given index."""
-        assert index < len(self._pages)
-
-        fromPage = self._currentPage
-        if fromPage is not None:
-            page = self._pages[fromPage]
-            if finalize and not page._finalized:
-                page.finalize()
-                page._finalized = True
-            self.remove(page)
-
-        self._currentPage = index
-        page = self._pages[index]
-        self.add(page)
-        if page._fromPage is None:
-            page._fromPage = fromPage
-            page.activate()
-        self.show_all()
-        if fromPage is not None:
-            self.grabDefault()
-
-    def nextPage(self, finalize = True):
-        """Go to the next page."""
-        self.jumpPage(1, finalize)
-
-    def jumpPage(self, count, finalize = True):
-        """Go to the page which is 'count' pages after the current one."""
-        self.setCurrentPage(self._currentPage + count, finalize = finalize)
-
-    def grabDefault(self):
-        """Make the default button of the current page the default."""
-        self._pages[self._currentPage].grabDefault()
-
-    def connected(self, fsType, descriptor):
-        """Called when the connection could be made to the simulator."""
-        self.nextPage()
-
-    def connectionFailed(self):
-        """Called when the connection could not be made to the simulator."""
-        self._initialize()
-
-    def disconnected(self):
-        """Called when we have disconnected from the simulator."""
-        self._initialize()
-
-    def _initialize(self):
-        """Initialize the wizard."""
-        self._fleet = None
-        self._fleetCallback = None
-        self._updatePlaneCallback = None
-        
-        self._loginResult = None
-        self._bookedFlight = None
-        self._departureGate = "-"
-        self._zfw = None
-        self._cruiseAltitude = None
-        self._route = None
-        self._departureNOTAMs = None
-        self._departureMETAR = None
-        self._arrivalNOTAMs = None
-        self._arrivalMETAR = None
-
-        for page in self._pages:
-            page.reset()
-        
-        self.setCurrentPage(0)
-        
-    def _getFleet(self, callback, force = False):
-        """Get the fleet, if needed.
-
-        callback is function that will be called, when the feet is retrieved,
-        or the retrieval fails. It should have a single argument that will
-        receive the fleet object on success, None otherwise.
-        """
-        if self._fleet is not None and not force:
-            callback(self._fleet)
-
-        self.gui.beginBusy("Retrieving fleet...")
-        self._fleetCallback = callback
-        self.gui.webHandler.getFleet(self._fleetResultCallback)
-
-    def _fleetResultCallback(self, returned, result):
-        """Called when the fleet has been queried."""
-        gobject.idle_add(self._handleFleetResult, returned, result)
-
-    def _handleFleetResult(self, returned, result):
-        """Handle the fleet result."""
-        self.gui.endBusy()
-        if returned:
-            self._fleet = result.fleet
-        else:
-            self._fleet = None
-
-            dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
-                                       buttons = BUTTONSTYPE_OK,
-                                       message_format =
-                                       "Failed to retrieve the information on "
-                                       "the fleet.")
-            dialog.run()
-            dialog.hide()
-
-        self._fleetCallback(self._fleet)
-
-    def _updatePlane(self, callback, tailNumber, status, gateNumber = None):
-        """Update the given plane's gate information."""
-        self.gui.beginBusy("Updating plane status...")
-        self._updatePlaneCallback = callback
-        self.gui.webHandler.updatePlane(self._updatePlaneResultCallback,
-                                        tailNumber, status, gateNumber)
-
-    def _updatePlaneResultCallback(self, returned, result):
-        """Callback for the plane updating operation."""
-        gobject.idle_add(self._handleUpdatePlaneResult, returned, result)
-
-    def _handleUpdatePlaneResult(self, returned, result):
-        """Handle the result of a plane update operation."""
-        self.gui.endBusy()
-        if returned:
-            success = result.success
-        else:
-            success = None
-
-            dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
-                                       buttons = BUTTONSTYPE_OK,
-                                       message_format =
-                                       "Failed to update the statuis of "
-                                       "the airplane.")
-            dialog.run()
-            dialog.hide()
-
-        self._updatePlaneCallback(success)
-
-    def _connectSimulator(self):
-        """Connect to the simulator."""
-        self.gui.connectSimulator(self._bookedFlight.aircraftType)
-    
-#-----------------------------------------------------------------------------
-
Index: src/mlx/gui/gui.py
===================================================================
--- src/mlx/gui/gui.py	(revision 78)
+++ src/mlx/gui/gui.py	(revision 78)
@@ -0,0 +1,437 @@
+# The main file for the GUI
+
+from statusicon import StatusIcon
+from statusbar import Statusbar
+from update import Updater
+from mlx.gui.common import *
+from mlx.gui.flight import Wizard
+from mlx.gui.monitor import MonitorWindow
+
+import mlx.const as const
+import mlx.fs as fs
+import mlx.flight as flight
+import mlx.logger as logger
+import mlx.acft as acft
+import mlx.web as web
+
+import time
+import threading
+import sys
+
+#------------------------------------------------------------------------------
+
+class GUI(fs.ConnectionListener):
+    """The main GUI class."""
+    def __init__(self, programDirectory, config):
+        """Construct the GUI."""
+        gobject.threads_init()
+
+        self._programDirectory = programDirectory
+        self.config = config
+        self._connecting = False
+        self._reconnecting = False
+        self._connected = False
+        self._logger = logger.Logger(output = self)
+        self._flight = None
+        self._simulator = None
+        self._monitoring = False
+
+        self._stdioLock = threading.Lock()
+        self._stdioText = ""
+        self._stdioAfterNewLine = True
+
+        self.webHandler = web.Handler()
+        self.webHandler.start()
+
+        self.toRestart = False
+
+    def build(self, iconDirectory):
+        """Build the GUI."""
+        
+        window = gtk.Window()
+        window.set_title("MAVA Logger X " + const.VERSION)
+        window.set_icon_from_file(os.path.join(iconDirectory, "logo.ico"))
+        window.connect("delete-event",
+                       lambda a, b: self.hideMainWindow())
+        window.connect("window-state-event", self._handleMainWindowState)
+
+        mainVBox = gtk.VBox()
+        window.add(mainVBox)
+
+        notebook = gtk.Notebook()
+        mainVBox.add(notebook)
+
+        self._wizard = Wizard(self)
+        label = gtk.Label("_Flight")
+        label.set_use_underline(True)
+        label.set_tooltip_text("Flight wizard")
+        notebook.append_page(self._wizard, label)
+
+        logVBox = gtk.VBox()
+        label = gtk.Label("_Log")
+        label.set_use_underline(True)
+        label.set_tooltip_text("Flight log")
+        notebook.append_page(logVBox, label)
+        
+        logFrame = self._buildLogFrame()
+        logFrame.set_border_width(8)
+        logVBox.pack_start(logFrame, True, True, 0)
+
+        mainVBox.pack_start(gtk.HSeparator(), False, False, 0)
+
+        self._statusbar = Statusbar()
+        mainVBox.pack_start(self._statusbar, False, False, 0)
+
+        notebook.connect("switch-page", self._notebookPageSwitch)
+
+        self._monitorWindow = MonitorWindow(self, iconDirectory)
+        self._monitorWindowX = None
+        self._monitorWindowY = None
+
+        window.show_all()
+        self._wizard.grabDefault()
+
+        self._mainWindow = window
+
+        self._statusIcon = StatusIcon(iconDirectory, self)
+
+        self._busyCursor = gdk.Cursor(gdk.CursorType.WATCH if pygobject
+                                      else gdk.WATCH)
+
+    @property
+    def simulator(self):
+        """Get the simulator used by us."""
+        return self._simulator
+        
+    @property
+    def flight(self):
+        """Get the flight being performed."""
+        return self._flight
+        
+    def run(self):
+        """Run the GUI."""
+        if self.config.autoUpdate:
+            self._updater = Updater(self,
+                                    self._programDirectory,
+                                    self.config.updateURL,
+                                    self._mainWindow)
+            self._updater.start()
+
+        gtk.main()
+
+        if self._flight is not None:
+            simulator = self._flight.simulator
+            if self._monitoring:
+                simulator.stopMonitoring()
+                self._monitoring = False
+            simulator.disconnect()                        
+
+    def connected(self, fsType, descriptor):
+        """Called when we have connected to the simulator."""
+        self._connected = True
+        self._logger.untimedMessage("Connected to the simulator %s" % (descriptor,))
+        gobject.idle_add(self._handleConnected, fsType, descriptor)
+
+    def _handleConnected(self, fsType, descriptor):
+        """Called when the connection to the simulator has succeeded."""
+        self._statusbar.updateConnection(self._connecting, self._connected)
+        self.endBusy()
+        if not self._reconnecting:
+            self._wizard.connected(fsType, descriptor)
+        self._reconnecting = False
+
+    def connectionFailed(self):
+        """Called when the connection failed."""
+        self._logger.untimedMessage("Connection to the simulator failed")
+        gobject.idle_add(self._connectionFailed)
+
+    def _connectionFailed(self):
+        """Called when the connection failed."""
+        self.endBusy()
+        self._statusbar.updateConnection(self._connecting, self._connected)
+
+        dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
+                                   message_format =
+                                   "Cannot connect to the simulator.",
+                                   parent = self._mainWindow)
+        dialog.format_secondary_markup("Rectify the situation, and press <b>Try again</b> "
+                                       "to try the connection again, " 
+                                       "or <b>Cancel</b> to cancel the flight.")
+        
+        dialog.add_button("_Cancel", 0)
+        dialog.add_button("_Try again", 1)
+        dialog.set_default_response(1)
+        
+        result = dialog.run()
+        dialog.hide()
+        if result == 1:
+            self.beginBusy("Connecting to the simulator.")
+            self._simulator.reconnect()
+        else:
+            self._connecting = False
+            self._reconnecting = False
+            self._statusbar.updateConnection(self._connecting, self._connected)
+            self._wizard.connectionFailed()
+        
+    def disconnected(self):
+        """Called when we have disconnected from the simulator."""
+        self._connected = False
+        self._logger.untimedMessage("Disconnected from the simulator")
+
+        gobject.idle_add(self._disconnected)
+
+    def _disconnected(self):
+        """Called when we have disconnected from the simulator unexpectedly."""        
+        self._statusbar.updateConnection(self._connecting, self._connected)
+
+        dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
+                                   message_format =
+                                   "The connection to the simulator failed unexpectedly.",
+                                   parent = self._mainWindow)
+        dialog.format_secondary_markup("If the simulator has crashed, restart it "
+                                       "and restore your flight as much as possible "
+                                       "to the state it was in before the crash.\n"
+                                       "Then press <b>Reconnect</b> to reconnect.\n\n"
+                                       "If you want to cancel the flight, press <b>Cancel</b>.")
+
+        dialog.add_button("_Cancel", 0)
+        dialog.add_button("_Reconnect", 1)
+        dialog.set_default_response(1)
+
+        result = dialog.run()
+        dialog.hide()
+        if result == 1:
+            self.beginBusy("Connecting to the simulator.")
+            self._reconnecting = True
+            self._simulator.reconnect()
+        else:
+            self._connecting = False
+            self._reconnecting = False
+            self._statusbar.updateConnection(self._connecting, self._connected)
+            self._wizard.disconnected()
+
+    def write(self, msg):
+        """Write the given message to the log."""
+        gobject.idle_add(self._writeLog, msg)
+        
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Update the data."""
+        gobject.idle_add(self._monitorWindow.setData, state)
+
+    def resetFlightStatus(self):
+        """Reset the status of the flight."""
+        self._statusbar.resetFlightStatus()
+        self._statusIcon.resetFlightStatus()
+
+    def setStage(self, stage):
+        """Set the stage of the flight."""
+        gobject.idle_add(self._setStage, stage)
+
+    def _setStage(self, stage):
+        """Set the stage of the flight."""
+        self._statusbar.setStage(stage)
+        self._statusIcon.setStage(stage)
+
+    def setRating(self, rating):
+        """Set the rating of the flight."""
+        gobject.idle_add(self._setRating, rating)
+
+    def _setRating(self, rating):
+        """Set the rating of the flight."""
+        self._statusbar.setRating(rating)
+        self._statusIcon.setRating(rating)
+
+    def setNoGo(self, reason):
+        """Set the rating of the flight to No-Go with the given reason."""
+        gobject.idle_add(self._setNoGo, reason)
+
+    def _setNoGo(self, reason):
+        """Set the rating of the flight."""
+        self._statusbar.setNoGo(reason)
+        self._statusIcon.setNoGo(reason)
+
+    def _handleMainWindowState(self, window, event):
+        """Hande a change in the state of the window"""
+        iconified = gdk.WindowState.ICONIFIED if pygobject \
+                    else gdk.WINDOW_STATE_ICONIFIED
+        if (event.changed_mask&iconified)!=0 and (event.new_window_state&iconified)!=0:
+            self.hideMainWindow(savePosition = False)
+
+    def hideMainWindow(self, savePosition = True):
+        """Hide the main window and save its position."""
+        if savePosition:
+            (self._mainWindowX, self._mainWindowY) = \
+                 self._mainWindow.get_window().get_root_origin()
+        else:
+            self._mainWindowX = self._mainWindowY = None
+        self._mainWindow.hide()
+        self._statusIcon.mainWindowHidden()
+        return True
+
+    def showMainWindow(self):
+        """Show the main window at its former position."""
+        if self._mainWindowX is not None and self._mainWindowY is not None:
+            self._mainWindow.move(self._mainWindowX, self._mainWindowY)
+
+        self._mainWindow.show()
+        self._mainWindow.deiconify()
+            
+        self._statusIcon.mainWindowShown()
+
+    def toggleMainWindow(self):
+        """Toggle the main window."""
+        if self._mainWindow.get_visible():
+            self.hideMainWindow()
+        else:
+            self.showMainWindow()
+
+    def hideMonitorWindow(self, savePosition = True):
+        """Hide the monitor window."""
+        if savePosition:
+            (self._monitorWindowX, self._monitorWindowY) = \
+                 self._monitorWindow.get_window().get_root_origin()
+        else:
+            self._monitorWindowX = self._monitorWindowY = None
+        self._monitorWindow.hide()
+        self._statusIcon.monitorWindowHidden()
+        return True
+
+    def showMonitorWindow(self):
+        """Show the monitor window."""
+        if self._monitorWindowX is not None and self._monitorWindowY is not None:
+            self._monitorWindow.move(self._monitorWindowX, self._monitorWindowY)
+        self._monitorWindow.show_all()
+        self._statusIcon.monitorWindowShown()
+
+    def restart(self):
+        """Quit and restart the application."""
+        self.toRestart = True
+        self._quit()
+
+    def flushStdIO(self):
+        """Flush any text to the standard error that could not be logged."""
+        if self._stdioText:
+            sys.__stderr__.write(self._stdioText)
+            
+    def writeStdIO(self, text):
+        """Write the given text into standard I/O log."""
+        with self._stdioLock:
+            self._stdioText += text
+
+        gobject.idle_add(self._writeStdIO)
+
+    def beginBusy(self, message):
+        """Begin a period of background processing."""
+        self._mainWindow.get_window().set_cursor(self._busyCursor)
+        self._statusbar.updateBusyState(message)
+
+    def endBusy(self):
+        """End a period of background processing."""
+        self._mainWindow.get_window().set_cursor(None)
+        self._statusbar.updateBusyState(None)
+
+    def _writeStdIO(self):
+        """Perform the real writing."""
+        with self._stdioLock:
+            text = self._stdioText
+            self._stdioText = ""
+        if not text: return
+            
+        lines = text.splitlines()
+        if text[-1]=="\n":
+            text = ""
+        else:
+            text = lines[-1]
+            lines = lines[:-1]
+            
+        for line in lines:
+            if self._stdioAfterNewLine:
+                line = "[STDIO] " + line
+            self._writeLog(line + "\n")
+            self._stdioAfterNewLine = True
+
+        if text:
+            if self._stdioAfterNewLine:
+                text = "[STDIO] " + text
+            self._writeLog(text)
+            self._stdioAfterNewLine = False
+
+    def connectSimulator(self, aircraftType):
+        """Connect to the simulator for the first time."""
+        self._logger.reset()
+
+        self._flight = flight.Flight(self._logger, self)
+        self._flight.aircraftType = aircraftType
+        self._flight.aircraft = acft.Aircraft.create(self._flight)
+        self._flight.aircraft._checkers.append(self)
+        
+        if self._simulator is None:
+            self._simulator = fs.createSimulator(const.SIM_MSFS9, self)
+
+        self._flight.simulator = self._simulator
+
+        self.beginBusy("Connecting to the simulator...")
+        self._statusbar.updateConnection(self._connecting, self._connected)
+
+        self._connecting = True
+        self._simulator.connect(self._flight.aircraft)        
+
+    def startMonitoring(self):
+        """Start monitoring."""
+        self._simulator.startMonitoring()
+        self._monitoring = True
+
+    def stopMonitoring(self):
+        """Stop monitoring."""
+        self._simulator.stoptMonitoring()
+        self._monitoring = False
+
+    def _buildLogFrame(self):
+        """Build the frame for the log."""
+        logFrame = gtk.Frame(label = "Log")
+
+        frameAlignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
+
+        frameAlignment.set_padding(padding_top = 4, padding_bottom = 10,
+                                   padding_left = 16, padding_right = 16)
+
+        logFrame.add(frameAlignment)
+
+        logScroller = gtk.ScrolledWindow()
+        self._logView = gtk.TextView()
+        self._logView.set_editable(False)
+        logScroller.add(self._logView)
+
+        logBox = gtk.VBox()
+        logBox.pack_start(logScroller, True, True, 0)
+        logBox.set_size_request(-1, 200)
+
+        frameAlignment.add(logBox)
+
+        return logFrame
+
+    def _writeLog(self, msg):
+        """Write the given message to the log."""
+        buffer = self._logView.get_buffer()
+        buffer.insert(buffer.get_end_iter(), msg)
+        self._logView.scroll_mark_onscreen(buffer.get_insert())
+
+    def _quit(self, what = None):
+        """Quit from the application."""
+        dialog = gtk.MessageDialog(type = MESSAGETYPE_QUESTION,
+                                   buttons = BUTTONSTYPE_YES_NO,
+                                   message_format =
+                                   "Are you sure to quit the logger?")
+        result = dialog.run()
+        dialog.hide()
+        
+        if result==RESPONSETYPE_YES:
+            self._statusIcon.destroy()
+            return gtk.main_quit()
+
+    def _notebookPageSwitch(self, notebook, page, page_num):
+        """Called when the current page of the notebook has changed."""
+        if page_num==0:
+            gobject.idle_add(self._wizard.grabDefault)
+        else:
+            self._mainWindow.set_default(None)
Index: src/mlx/gui/gui.py
===================================================================
--- src/mlx/gui/gui.py	(revision 77)
+++ 	(revision )
@@ -1,455 +1,0 @@
-# The main file for the GUI
-
-from statusicon import StatusIcon
-from statusbar import Statusbar
-from update import Updater
-from mlx.gui.common import *
-from mlx.gui.flight import Wizard
-from mlx.gui.monitor import MonitorWindow
-
-import mlx.const as const
-import mlx.fs as fs
-import mlx.flight as flight
-import mlx.logger as logger
-import mlx.acft as acft
-import mlx.web as web
-
-import time
-import threading
-import sys
-
-#------------------------------------------------------------------------------
-
-acftTypes = [ ("Boeing 737-600", const.AIRCRAFT_B736),
-              ("Boeing 737-700", const.AIRCRAFT_B737),
-              ("Boeing 737-800", const.AIRCRAFT_B738),
-              ("Bombardier Dash 8-Q400", const.AIRCRAFT_DH8D),
-              ("Boeing 737-300", const.AIRCRAFT_B733),
-              ("Boeing 737-400", const.AIRCRAFT_B734),
-              ("Boeing 737-500", const.AIRCRAFT_B735),
-              ("Boeing 767-200", const.AIRCRAFT_B762),
-              ("Boeing 767-300", const.AIRCRAFT_B763),
-              ("Bombardier CRJ200", const.AIRCRAFT_CRJ2),
-              ("Fokker 70", const.AIRCRAFT_F70),
-              ("Lisunov Li-2", const.AIRCRAFT_DC3),
-              ("Tupolev Tu-134", const.AIRCRAFT_T134),
-              ("Tupolev Tu-154", const.AIRCRAFT_T154),
-              ("Yakovlev Yak-40", const.AIRCRAFT_YK40) ]
-
-#------------------------------------------------------------------------------
-
-class GUI(fs.ConnectionListener):
-    """The main GUI class."""
-    def __init__(self, programDirectory, config):
-        """Construct the GUI."""
-        gobject.threads_init()
-
-        self._programDirectory = programDirectory
-        self.config = config
-        self._connecting = False
-        self._reconnecting = False
-        self._connected = False
-        self._logger = logger.Logger(output = self)
-        self._flight = None
-        self._simulator = None
-        self._monitoring = False
-
-        self._stdioLock = threading.Lock()
-        self._stdioText = ""
-        self._stdioAfterNewLine = True
-
-        self.webHandler = web.Handler()
-        self.webHandler.start()
-
-        self.toRestart = False
-
-    def build(self, iconDirectory):
-        """Build the GUI."""
-        
-        window = gtk.Window()
-        window.set_title("MAVA Logger X " + const.VERSION)
-        window.set_icon_from_file(os.path.join(iconDirectory, "logo.ico"))
-        window.connect("delete-event",
-                       lambda a, b: self.hideMainWindow())
-        window.connect("window-state-event", self._handleMainWindowState)
-
-        mainVBox = gtk.VBox()
-        window.add(mainVBox)
-
-        notebook = gtk.Notebook()
-        mainVBox.add(notebook)
-
-        self._wizard = Wizard(self)
-        label = gtk.Label("_Flight")
-        label.set_use_underline(True)
-        label.set_tooltip_text("Flight wizard")
-        notebook.append_page(self._wizard, label)
-
-        logVBox = gtk.VBox()
-        label = gtk.Label("_Log")
-        label.set_use_underline(True)
-        label.set_tooltip_text("Flight log")
-        notebook.append_page(logVBox, label)
-        
-        logFrame = self._buildLogFrame()
-        logFrame.set_border_width(8)
-        logVBox.pack_start(logFrame, True, True, 0)
-
-        mainVBox.pack_start(gtk.HSeparator(), False, False, 0)
-
-        self._statusbar = Statusbar()
-        mainVBox.pack_start(self._statusbar, False, False, 0)
-
-        notebook.connect("switch-page", self._notebookPageSwitch)
-
-        self._monitorWindow = MonitorWindow(self, iconDirectory)
-        self._monitorWindowX = None
-        self._monitorWindowY = None
-
-        window.show_all()
-        self._wizard.grabDefault()
-
-        self._mainWindow = window
-
-        self._statusIcon = StatusIcon(iconDirectory, self)
-
-        self._busyCursor = gdk.Cursor(gdk.CursorType.WATCH if pygobject
-                                      else gdk.WATCH)
-
-    @property
-    def simulator(self):
-        """Get the simulator used by us."""
-        return self._simulator
-        
-    @property
-    def flight(self):
-        """Get the flight being performed."""
-        return self._flight
-        
-    def run(self):
-        """Run the GUI."""
-        if self.config.autoUpdate:
-            self._updater = Updater(self,
-                                    self._programDirectory,
-                                    self.config.updateURL,
-                                    self._mainWindow)
-            self._updater.start()
-
-        gtk.main()
-
-        if self._flight is not None:
-            simulator = self._flight.simulator
-            if self._monitoring:
-                simulator.stopMonitoring()
-                self._monitoring = False
-            simulator.disconnect()                        
-
-    def connected(self, fsType, descriptor):
-        """Called when we have connected to the simulator."""
-        self._connected = True
-        self._logger.untimedMessage("Connected to the simulator %s" % (descriptor,))
-        gobject.idle_add(self._handleConnected, fsType, descriptor)
-
-    def _handleConnected(self, fsType, descriptor):
-        """Called when the connection to the simulator has succeeded."""
-        self._statusbar.updateConnection(self._connecting, self._connected)
-        self.endBusy()
-        if not self._reconnecting:
-            self._wizard.connected(fsType, descriptor)
-        self._reconnecting = False
-
-    def connectionFailed(self):
-        """Called when the connection failed."""
-        self._logger.untimedMessage("Connection to the simulator failed")
-        gobject.idle_add(self._connectionFailed)
-
-    def _connectionFailed(self):
-        """Called when the connection failed."""
-        self.endBusy()
-        self._statusbar.updateConnection(self._connecting, self._connected)
-
-        dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
-                                   message_format =
-                                   "Cannot connect to the simulator.",
-                                   parent = self._mainWindow)
-        dialog.format_secondary_markup("Rectify the situation, and press <b>Try again</b> "
-                                       "to try the connection again, " 
-                                       "or <b>Cancel</b> to cancel the flight.")
-        
-        dialog.add_button("_Cancel", 0)
-        dialog.add_button("_Try again", 1)
-        dialog.set_default_response(1)
-        
-        result = dialog.run()
-        dialog.hide()
-        if result == 1:
-            self.beginBusy("Connecting to the simulator.")
-            self._simulator.reconnect()
-        else:
-            self._connecting = False
-            self._reconnecting = False
-            self._statusbar.updateConnection(self._connecting, self._connected)
-            self._wizard.connectionFailed()
-        
-    def disconnected(self):
-        """Called when we have disconnected from the simulator."""
-        self._connected = False
-        self._logger.untimedMessage("Disconnected from the simulator")
-
-        gobject.idle_add(self._disconnected)
-
-    def _disconnected(self):
-        """Called when we have disconnected from the simulator unexpectedly."""        
-        self._statusbar.updateConnection(self._connecting, self._connected)
-
-        dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
-                                   message_format =
-                                   "The connection to the simulator failed unexpectedly.",
-                                   parent = self._mainWindow)
-        dialog.format_secondary_markup("If the simulator has crashed, restart it "
-                                       "and restore your flight as much as possible "
-                                       "to the state it was in before the crash.\n"
-                                       "Then press <b>Reconnect</b> to reconnect.\n\n"
-                                       "If you want to cancel the flight, press <b>Cancel</b>.")
-
-        dialog.add_button("_Cancel", 0)
-        dialog.add_button("_Reconnect", 1)
-        dialog.set_default_response(1)
-
-        result = dialog.run()
-        dialog.hide()
-        if result == 1:
-            self.beginBusy("Connecting to the simulator.")
-            self._reconnecting = True
-            self._simulator.reconnect()
-        else:
-            self._connecting = False
-            self._reconnecting = False
-            self._statusbar.updateConnection(self._connecting, self._connected)
-            self._wizard.disconnected()
-
-    def write(self, msg):
-        """Write the given message to the log."""
-        gobject.idle_add(self._writeLog, msg)
-        
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Update the data."""
-        gobject.idle_add(self._monitorWindow.setData, state)
-
-    def resetFlightStatus(self):
-        """Reset the status of the flight."""
-        self._statusbar.resetFlightStatus()
-        self._statusIcon.resetFlightStatus()
-
-    def setStage(self, stage):
-        """Set the stage of the flight."""
-        gobject.idle_add(self._setStage, stage)
-
-    def _setStage(self, stage):
-        """Set the stage of the flight."""
-        self._statusbar.setStage(stage)
-        self._statusIcon.setStage(stage)
-
-    def setRating(self, rating):
-        """Set the rating of the flight."""
-        gobject.idle_add(self._setRating, rating)
-
-    def _setRating(self, rating):
-        """Set the rating of the flight."""
-        self._statusbar.setRating(rating)
-        self._statusIcon.setRating(rating)
-
-    def setNoGo(self, reason):
-        """Set the rating of the flight to No-Go with the given reason."""
-        gobject.idle_add(self._setNoGo, reason)
-
-    def _setNoGo(self, reason):
-        """Set the rating of the flight."""
-        self._statusbar.setNoGo(reason)
-        self._statusIcon.setNoGo(reason)
-
-    def _handleMainWindowState(self, window, event):
-        """Hande a change in the state of the window"""
-        iconified = gdk.WindowState.ICONIFIED if pygobject \
-                    else gdk.WINDOW_STATE_ICONIFIED
-        if (event.changed_mask&iconified)!=0 and (event.new_window_state&iconified)!=0:
-            self.hideMainWindow(savePosition = False)
-
-    def hideMainWindow(self, savePosition = True):
-        """Hide the main window and save its position."""
-        if savePosition:
-            (self._mainWindowX, self._mainWindowY) = \
-                 self._mainWindow.get_window().get_root_origin()
-        else:
-            self._mainWindowX = self._mainWindowY = None
-        self._mainWindow.hide()
-        self._statusIcon.mainWindowHidden()
-        return True
-
-    def showMainWindow(self):
-        """Show the main window at its former position."""
-        if self._mainWindowX is not None and self._mainWindowY is not None:
-            self._mainWindow.move(self._mainWindowX, self._mainWindowY)
-
-        self._mainWindow.show()
-        self._mainWindow.deiconify()
-            
-        self._statusIcon.mainWindowShown()
-
-    def toggleMainWindow(self):
-        """Toggle the main window."""
-        if self._mainWindow.get_visible():
-            self.hideMainWindow()
-        else:
-            self.showMainWindow()
-
-    def hideMonitorWindow(self, savePosition = True):
-        """Hide the monitor window."""
-        if savePosition:
-            (self._monitorWindowX, self._monitorWindowY) = \
-                 self._monitorWindow.get_window().get_root_origin()
-        else:
-            self._monitorWindowX = self._monitorWindowY = None
-        self._monitorWindow.hide()
-        self._statusIcon.monitorWindowHidden()
-        return True
-
-    def showMonitorWindow(self):
-        """Show the monitor window."""
-        if self._monitorWindowX is not None and self._monitorWindowY is not None:
-            self._monitorWindow.move(self._monitorWindowX, self._monitorWindowY)
-        self._monitorWindow.show_all()
-        self._statusIcon.monitorWindowShown()
-
-    def restart(self):
-        """Quit and restart the application."""
-        self.toRestart = True
-        self._quit()
-
-    def flushStdIO(self):
-        """Flush any text to the standard error that could not be logged."""
-        if self._stdioText:
-            sys.__stderr__.write(self._stdioText)
-            
-    def writeStdIO(self, text):
-        """Write the given text into standard I/O log."""
-        with self._stdioLock:
-            self._stdioText += text
-
-        gobject.idle_add(self._writeStdIO)
-
-    def beginBusy(self, message):
-        """Begin a period of background processing."""
-        self._mainWindow.get_window().set_cursor(self._busyCursor)
-        self._statusbar.updateBusyState(message)
-
-    def endBusy(self):
-        """End a period of background processing."""
-        self._mainWindow.get_window().set_cursor(None)
-        self._statusbar.updateBusyState(None)
-
-    def _writeStdIO(self):
-        """Perform the real writing."""
-        with self._stdioLock:
-            text = self._stdioText
-            self._stdioText = ""
-        if not text: return
-            
-        lines = text.splitlines()
-        if text[-1]=="\n":
-            text = ""
-        else:
-            text = lines[-1]
-            lines = lines[:-1]
-            
-        for line in lines:
-            if self._stdioAfterNewLine:
-                line = "[STDIO] " + line
-            self._writeLog(line + "\n")
-            self._stdioAfterNewLine = True
-
-        if text:
-            if self._stdioAfterNewLine:
-                text = "[STDIO] " + text
-            self._writeLog(text)
-            self._stdioAfterNewLine = False
-
-    def connectSimulator(self, aircraftType):
-        """Connect to the simulator for the first time."""
-        self._logger.reset()
-
-        self._flight = flight.Flight(self._logger, self)
-        self._flight.aircraftType = aircraftType
-        self._flight.aircraft = acft.Aircraft.create(self._flight)
-        self._flight.aircraft._checkers.append(self)
-        
-        if self._simulator is None:
-            self._simulator = fs.createSimulator(const.SIM_MSFS9, self)
-
-        self._flight.simulator = self._simulator
-
-        self.beginBusy("Connecting to the simulator...")
-        self._statusbar.updateConnection(self._connecting, self._connected)
-
-        self._connecting = True
-        self._simulator.connect(self._flight.aircraft)        
-
-    def startMonitoring(self):
-        """Start monitoring."""
-        self._simulator.startMonitoring()
-        self._monitoring = True
-
-    def stopMonitoring(self):
-        """Stop monitoring."""
-        self._simulator.stoptMonitoring()
-        self._monitoring = False
-
-    def _buildLogFrame(self):
-        """Build the frame for the log."""
-        logFrame = gtk.Frame(label = "Log")
-
-        frameAlignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
-
-        frameAlignment.set_padding(padding_top = 4, padding_bottom = 10,
-                                   padding_left = 16, padding_right = 16)
-
-        logFrame.add(frameAlignment)
-
-        logScroller = gtk.ScrolledWindow()
-        self._logView = gtk.TextView()
-        self._logView.set_editable(False)
-        logScroller.add(self._logView)
-
-        logBox = gtk.VBox()
-        logBox.pack_start(logScroller, True, True, 0)
-        logBox.set_size_request(-1, 200)
-
-        frameAlignment.add(logBox)
-
-        return logFrame
-
-    def _writeLog(self, msg):
-        """Write the given message to the log."""
-        buffer = self._logView.get_buffer()
-        buffer.insert(buffer.get_end_iter(), msg)
-        self._logView.scroll_mark_onscreen(buffer.get_insert())
-
-    def _quit(self, what = None):
-        """Quit from the application."""
-        dialog = gtk.MessageDialog(type = MESSAGETYPE_QUESTION,
-                                   buttons = BUTTONSTYPE_YES_NO,
-                                   message_format =
-                                   "Are you sure to quit the logger?")
-        result = dialog.run()
-        dialog.hide()
-        
-        if result==RESPONSETYPE_YES:
-            self._statusIcon.destroy()
-            return gtk.main_quit()
-
-    def _notebookPageSwitch(self, notebook, page, page_num):
-        """Called when the current page of the notebook has changed."""
-        if page_num==0:
-            gobject.idle_add(self._wizard.grabDefault)
-        else:
-            self._mainWindow.set_default(None)
Index: src/mlx/gui/monitor.py
===================================================================
--- src/mlx/gui/monitor.py	(revision 77)
+++ src/mlx/gui/monitor.py	(revision 77)
@@ -0,0 +1,307 @@
+# The monitoring window
+
+from mlx.gui.common import *
+
+import mlx.const as const
+
+import time
+
+#------------------------------------------------------------------------------
+
+class MonitorWindow(gtk.Window):
+    """The window for the data monitor."""
+    def __init__(self, gui, iconDirectory):
+        """Construct the monitor window."""
+        super(MonitorWindow, self).__init__()
+        
+        self._gui = gui
+
+        self.set_title("MAVA Logger X " + const.VERSION + " - Data Monitor")
+        self.set_icon_from_file(os.path.join(iconDirectory, "logo.ico"))
+        self.connect("delete-event",
+                     lambda a, b: self._gui.hideMonitorWindow())
+
+        alignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
+
+        alignment.set_padding(padding_top = 4, padding_bottom = 10,
+                              padding_left = 16, padding_right = 16)
+
+        table = gtk.Table(rows = 7, columns = 12)
+        table.set_homogeneous(False)
+        table.set_row_spacings(4)
+        table.set_col_spacings(8)
+
+        (label, self._timestamp) = self._createLabeledEntry("Time:")
+        table.attach(label, 0, 1, 0, 1)
+        table.attach(self._timestamp, 1, 2, 0, 1)
+
+        self._paused = gtk.Label("PAUSED")
+        table.attach(self._paused, 2, 4, 0, 1)
+        
+        self._trickMode = gtk.Label("TRICKMODE")
+        table.attach(self._trickMode, 4, 6, 0, 1, xoptions = 0)
+        
+        self._overspeed = gtk.Label("OVERSPEED")
+        table.attach(self._overspeed, 6, 8, 0, 1)
+        
+        self._stalled = gtk.Label("STALLED")
+        table.attach(self._stalled, 8, 10, 0, 1)
+        
+        self._onTheGround = gtk.Label("ONTHEGROUND")
+        table.attach(self._onTheGround, 10, 12, 0, 1)
+        
+        (label, self._zfw) = self._createLabeledEntry("ZFW:", 6)
+        table.attach(label, 0, 1, 1, 2)
+        table.attach(self._zfw, 1, 2, 1, 2)
+
+        (label, self._grossWeight) = self._createLabeledEntry("Weight:", 6)
+        table.attach(label, 2, 3, 1, 2)
+        table.attach(self._grossWeight, 3, 4, 1, 2)
+
+        (label, self._heading) = self._createLabeledEntry("Heading:", 3)
+        table.attach(label, 4, 5, 1, 2)
+        table.attach(self._heading, 5, 6, 1, 2)
+
+        (label, self._pitch) = self._createLabeledEntry("Pitch:", 3)
+        table.attach(label, 6, 7, 1, 2)
+        table.attach(self._pitch, 7, 8, 1, 2)
+
+        (label, self._bank) = self._createLabeledEntry("Bank:", 3)
+        table.attach(label, 8, 9, 1, 2)
+        table.attach(self._bank, 9, 10, 1, 2)
+
+        (label, self._vs) = self._createLabeledEntry("VS:", 5)
+        table.attach(label, 10, 11, 1, 2)
+        table.attach(self._vs, 11, 12, 1, 2)
+
+        (label, self._ias) = self._createLabeledEntry("IAS:", 4)
+        table.attach(label, 0, 1, 2, 3)
+        table.attach(self._ias, 1, 2, 2, 3)
+
+        (label, self._mach) = self._createLabeledEntry("Mach:", 4)
+        table.attach(label, 2, 3, 2, 3)
+        table.attach(self._mach, 3, 4, 2, 3)
+
+        (label, self._groundSpeed) = self._createLabeledEntry("GS:", 4)
+        table.attach(label, 4, 5, 2, 3)
+        table.attach(self._groundSpeed, 5, 6, 2, 3)
+
+        (label, self._radioAltitude) = self._createLabeledEntry("Radio alt.:", 6)
+        table.attach(label, 6, 7, 2, 3)
+        table.attach(self._radioAltitude, 7, 8, 2, 3)
+
+        (label, self._altitude) = self._createLabeledEntry("Altitude:", 6)
+        table.attach(label, 8, 9, 2, 3)
+        table.attach(self._altitude, 9, 10, 2, 3)
+
+        (label, self._gLoad) = self._createLabeledEntry("G-Load:", 4)
+        table.attach(label, 10, 11, 2, 3)
+        table.attach(self._gLoad, 11, 12, 2, 3)
+
+        (label, self._flapsSet) = self._createLabeledEntry("Flaps set:", 2)
+        table.attach(label, 0, 1, 3, 4)
+        table.attach(self._flapsSet, 1, 2, 3, 4)
+
+        (label, self._flaps) = self._createLabeledEntry("Flaps:", 2)
+        table.attach(label, 2, 3, 3, 4)
+        table.attach(self._flaps, 3, 4, 3, 4)
+
+        (label, self._altimeter) = self._createLabeledEntry("Altimeter:", 4)
+        table.attach(label, 4, 5, 3, 4)
+        table.attach(self._altimeter, 5, 6, 3, 4)
+
+        (label, self._squawk) = self._createLabeledEntry("Squawk:", 4)
+        table.attach(label, 6, 7, 3, 4)
+        table.attach(self._squawk, 7, 8, 3, 4)
+
+        (label, self._nav1) = self._createLabeledEntry("NAV1:", 5)
+        table.attach(label, 8, 9, 3, 4)
+        table.attach(self._nav1, 9, 10, 3, 4)
+
+        (label, self._nav2) = self._createLabeledEntry("NAV2:", 5)
+        table.attach(label, 10, 11, 3, 4)
+        table.attach(self._nav2, 11, 12, 3, 4)
+
+        (label, self._fuel) = self._createLabeledEntry("Fuel:", 40, xalign = 0.0)
+        table.attach(label, 0, 1, 4, 5)
+        table.attach(self._fuel, 1, 4, 4, 5)
+
+        (label, self._n1) = self._createLabeledEntry("N1/RPM:", 20, xalign = 0.0)
+        table.attach(label, 4, 5, 4, 5)
+        table.attach(self._n1, 5, 8, 4, 5)
+
+        (label, self._reverser) = self._createLabeledEntry("Reverser:", 20, xalign = 0.0)
+        table.attach(label, 8, 9, 4, 5)
+        table.attach(self._reverser, 9, 12, 4, 5)
+
+        self._navLightsOn = gtk.Label("NAV")
+        table.attach(self._navLightsOn, 0, 1, 5, 6)
+
+        self._antiCollisionLightsOn = gtk.Label("ANTICOLLISION")
+        table.attach(self._antiCollisionLightsOn, 1, 3, 5, 6)
+
+        self._strobeLightsOn = gtk.Label("STROBE")
+        table.attach(self._strobeLightsOn, 3, 4, 5, 6)
+
+        self._landingLightsOn = gtk.Label("LANDING")
+        table.attach(self._landingLightsOn, 4, 5, 5, 6)
+
+        self._pitotHeatOn = gtk.Label("PITOT HEAT")
+        table.attach(self._pitotHeatOn, 5, 7, 5, 6)
+
+        self._parking = gtk.Label("PARKING")
+        table.attach(self._parking, 7, 8, 5, 6)
+
+        self._gearsDown = gtk.Label("GEARS DOWN")
+        table.attach(self._gearsDown, 8, 10, 5, 6)
+
+        self._spoilersArmed = gtk.Label("SPOILERS ARMED")
+        table.attach(self._spoilersArmed, 10, 12, 5, 6)
+
+        (label, self._spoilersExtension) = self._createLabeledEntry("Spoilers:", 3)
+        table.attach(label, 0, 1, 6, 7)
+        table.attach(self._spoilersExtension, 1, 2, 6, 7)
+
+        (label, self._windSpeed) = self._createLabeledEntry("Wind speed:", 3)
+        table.attach(label, 2, 3, 6, 7)
+        table.attach(self._windSpeed, 3, 4, 6, 7)
+
+        (label, self._windDirection) = self._createLabeledEntry("Wind from:", 3)
+        table.attach(label, 4, 5, 6, 7)
+        table.attach(self._windDirection, 5, 6, 6, 7)
+
+        alignment.add(table)
+
+        self.add(alignment)
+
+        self.setData()
+
+    def _createLabeledEntry(self, label, width = 8, xalign = 1.0):
+        """Create a labeled entry.
+
+        Return a tuple consisting of:
+        - the box
+        - the entry."""
+        
+        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5, xscale = 1.0)
+        alignment.set_padding(padding_top = 0, padding_bottom = 0,
+                              padding_left = 0, padding_right = 16)
+        alignment.add(gtk.Label(label))
+
+        entry = gtk.Entry()
+        entry.set_editable(False)
+        entry.set_width_chars(width)
+        entry.set_max_length(width)
+        entry.set_alignment(xalign)
+
+        return (alignment, entry)
+
+    def setData(self, aircraftState = None):
+        """Set the data.
+
+        If aircraftState is None, everything will be set to its default."""
+        if aircraftState is None:
+            self._timestamp.set_text("--:--:--")
+            self._paused.set_sensitive(False)
+            self._trickMode.set_sensitive(False)
+            self._overspeed.set_sensitive(False)
+            self._stalled.set_sensitive(False)
+            self._onTheGround.set_sensitive(False)
+            self._zfw.set_text("-")
+            self._grossWeight.set_text("-")
+            self._heading.set_text("-")
+            self._pitch.set_text("-")
+            self._bank.set_text("-")
+            self._vs.set_text("-")
+            self._ias.set_text("-")
+            self._mach.set_text("-")
+            self._groundSpeed.set_text("-")
+            self._radioAltitude.set_text("-")
+            self._altitude.set_text("-")
+            self._gLoad.set_text("-")
+            self._flapsSet.set_text("-")
+            self._flaps.set_text("-")
+            self._altimeter.set_text("-")
+            self._squawk.set_text("-")
+            self._nav1.set_text("-")
+            self._nav2.set_text("-")
+            self._fuel.set_text("-")
+            self._n1.set_text("-")
+            self._reverser.set_text("-")
+            self._navLightsOn.set_sensitive(False)
+            self._antiCollisionLightsOn.set_sensitive(False)
+            self._strobeLightsOn.set_sensitive(False)
+            self._landingLightsOn.set_sensitive(False)
+            self._pitotHeatOn.set_sensitive(False)
+            self._parking.set_sensitive(False)
+            self._gearsDown.set_sensitive(False)
+            self._spoilersArmed.set_sensitive(False)
+            self._spoilersExtension.set_text("-")
+            self._windSpeed.set_text("-")
+            self._windDirection.set_text("-")
+        else:
+            self._timestamp.set_text(time.strftime("%H:%M:%S",
+                                                   time.gmtime(aircraftState.timestamp)))
+            self._paused.set_sensitive(aircraftState.paused)
+            self._trickMode.set_sensitive(aircraftState.trickMode)
+            self._overspeed.set_sensitive(aircraftState.overspeed)
+            self._stalled.set_sensitive(aircraftState.stalled)
+            self._onTheGround.set_sensitive(aircraftState.onTheGround)
+            self._zfw.set_text("%.0f" % (aircraftState.zfw,))
+            self._grossWeight.set_text("%.0f" % (aircraftState.grossWeight,))
+            self._heading.set_text("%03.0f" % (aircraftState.heading,))
+            self._pitch.set_text("%.0f" % (aircraftState.pitch,))
+            self._bank.set_text("%.0f" % (aircraftState.bank,))
+            self._vs.set_text("%.0f" % (aircraftState.vs,))
+            self._ias.set_text("%.0f" % (aircraftState.ias,))
+            self._mach.set_text("%.2f" % (aircraftState.mach,))
+            self._groundSpeed.set_text("%.0f" % (aircraftState.groundSpeed,))
+            self._radioAltitude.set_text("%.0f" % (aircraftState.radioAltitude,))
+            self._altitude.set_text("%.0f" % (aircraftState.altitude,))
+            self._gLoad.set_text("%.2f" % (aircraftState.gLoad,))
+            self._flapsSet.set_text("%.0f" % (aircraftState.flapsSet,))
+            self._flaps.set_text("%.0f" % (aircraftState.flaps,))
+            self._altimeter.set_text("%.0f" % (aircraftState.altimeter,))
+            self._squawk.set_text(aircraftState.squawk)
+            self._nav1.set_text(aircraftState.nav1)
+            self._nav2.set_text(aircraftState.nav2)
+
+            fuelStr = ""
+            for fuel in aircraftState.fuel:
+                if fuelStr: fuelStr += ", "
+                fuelStr += "%.0f" % (fuel,)                
+            self._fuel.set_text(fuelStr)
+
+            if hasattr(aircraftState, "n1"):
+                n1Str = ""
+                for n1 in aircraftState.n1:
+                    if n1Str: n1Str += ", "
+                    n1Str += "%.0f" % (n1,)
+            elif hasattr(aircraftState, "rpm"):
+                n1Str = ""
+                for rpm in aircraftState.rpm:
+                    if n1Str: n1Str += ", "
+                    n1Str += "%.0f" % (rpm,)
+            else:
+                n1Str = "-"
+            self._n1.set_text(n1Str)
+
+            reverserStr = ""
+            for reverser in aircraftState.reverser:
+                if reverserStr: reverserStr += ", "
+                reverserStr += "ON" if reverser else "OFF"
+            self._reverser.set_text(reverserStr)
+
+            self._navLightsOn.set_sensitive(aircraftState.navLightsOn)
+            self._antiCollisionLightsOn.set_sensitive(aircraftState.antiCollisionLightsOn)
+            self._strobeLightsOn.set_sensitive(aircraftState.strobeLightsOn)
+            self._landingLightsOn.set_sensitive(aircraftState.landingLightsOn)
+            self._pitotHeatOn.set_sensitive(aircraftState.pitotHeatOn)
+            self._parking.set_sensitive(aircraftState.parking)
+            self._gearsDown.set_sensitive(aircraftState.gearsDown)
+            self._spoilersArmed.set_sensitive(aircraftState.spoilersArmed)
+            self._spoilersExtension.set_text("%.0f" % (aircraftState.spoilersExtension,))
+            self._windSpeed.set_text("%.0f" % (aircraftState.windSpeed,))
+            self._windDirection.set_text("%03.0f" % (aircraftState.windDirection,))
+
+#------------------------------------------------------------------------------
Index: src/mlx/gui/monitor.py
===================================================================
--- src/mlx/gui/monitor.py	(revision 77)
+++ 	(revision )
@@ -1,307 +1,0 @@
-# The monitoring window
-
-from mlx.gui.common import *
-
-import mlx.const as const
-
-import time
-
-#------------------------------------------------------------------------------
-
-class MonitorWindow(gtk.Window):
-    """The window for the data monitor."""
-    def __init__(self, gui, iconDirectory):
-        """Construct the monitor window."""
-        super(MonitorWindow, self).__init__()
-        
-        self._gui = gui
-
-        self.set_title("MAVA Logger X " + const.VERSION + " - Data Monitor")
-        self.set_icon_from_file(os.path.join(iconDirectory, "logo.ico"))
-        self.connect("delete-event",
-                     lambda a, b: self._gui.hideMonitorWindow())
-
-        alignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
-
-        alignment.set_padding(padding_top = 4, padding_bottom = 10,
-                              padding_left = 16, padding_right = 16)
-
-        table = gtk.Table(rows = 7, columns = 12)
-        table.set_homogeneous(False)
-        table.set_row_spacings(4)
-        table.set_col_spacings(8)
-
-        (label, self._timestamp) = self._createLabeledEntry("Time:")
-        table.attach(label, 0, 1, 0, 1)
-        table.attach(self._timestamp, 1, 2, 0, 1)
-
-        self._paused = gtk.Label("PAUSED")
-        table.attach(self._paused, 2, 4, 0, 1)
-        
-        self._trickMode = gtk.Label("TRICKMODE")
-        table.attach(self._trickMode, 4, 6, 0, 1, xoptions = 0)
-        
-        self._overspeed = gtk.Label("OVERSPEED")
-        table.attach(self._overspeed, 6, 8, 0, 1)
-        
-        self._stalled = gtk.Label("STALLED")
-        table.attach(self._stalled, 8, 10, 0, 1)
-        
-        self._onTheGround = gtk.Label("ONTHEGROUND")
-        table.attach(self._onTheGround, 10, 12, 0, 1)
-        
-        (label, self._zfw) = self._createLabeledEntry("ZFW:", 6)
-        table.attach(label, 0, 1, 1, 2)
-        table.attach(self._zfw, 1, 2, 1, 2)
-
-        (label, self._grossWeight) = self._createLabeledEntry("Weight:", 6)
-        table.attach(label, 2, 3, 1, 2)
-        table.attach(self._grossWeight, 3, 4, 1, 2)
-
-        (label, self._heading) = self._createLabeledEntry("Heading:", 3)
-        table.attach(label, 4, 5, 1, 2)
-        table.attach(self._heading, 5, 6, 1, 2)
-
-        (label, self._pitch) = self._createLabeledEntry("Pitch:", 3)
-        table.attach(label, 6, 7, 1, 2)
-        table.attach(self._pitch, 7, 8, 1, 2)
-
-        (label, self._bank) = self._createLabeledEntry("Bank:", 3)
-        table.attach(label, 8, 9, 1, 2)
-        table.attach(self._bank, 9, 10, 1, 2)
-
-        (label, self._vs) = self._createLabeledEntry("VS:", 5)
-        table.attach(label, 10, 11, 1, 2)
-        table.attach(self._vs, 11, 12, 1, 2)
-
-        (label, self._ias) = self._createLabeledEntry("IAS:", 4)
-        table.attach(label, 0, 1, 2, 3)
-        table.attach(self._ias, 1, 2, 2, 3)
-
-        (label, self._mach) = self._createLabeledEntry("Mach:", 4)
-        table.attach(label, 2, 3, 2, 3)
-        table.attach(self._mach, 3, 4, 2, 3)
-
-        (label, self._groundSpeed) = self._createLabeledEntry("GS:", 4)
-        table.attach(label, 4, 5, 2, 3)
-        table.attach(self._groundSpeed, 5, 6, 2, 3)
-
-        (label, self._radioAltitude) = self._createLabeledEntry("Radio alt.:", 6)
-        table.attach(label, 6, 7, 2, 3)
-        table.attach(self._radioAltitude, 7, 8, 2, 3)
-
-        (label, self._altitude) = self._createLabeledEntry("Altitude:", 6)
-        table.attach(label, 8, 9, 2, 3)
-        table.attach(self._altitude, 9, 10, 2, 3)
-
-        (label, self._gLoad) = self._createLabeledEntry("G-Load:", 4)
-        table.attach(label, 10, 11, 2, 3)
-        table.attach(self._gLoad, 11, 12, 2, 3)
-
-        (label, self._flapsSet) = self._createLabeledEntry("Flaps set:", 2)
-        table.attach(label, 0, 1, 3, 4)
-        table.attach(self._flapsSet, 1, 2, 3, 4)
-
-        (label, self._flaps) = self._createLabeledEntry("Flaps:", 2)
-        table.attach(label, 2, 3, 3, 4)
-        table.attach(self._flaps, 3, 4, 3, 4)
-
-        (label, self._altimeter) = self._createLabeledEntry("Altimeter:", 4)
-        table.attach(label, 4, 5, 3, 4)
-        table.attach(self._altimeter, 5, 6, 3, 4)
-
-        (label, self._squawk) = self._createLabeledEntry("Squawk:", 4)
-        table.attach(label, 6, 7, 3, 4)
-        table.attach(self._squawk, 7, 8, 3, 4)
-
-        (label, self._nav1) = self._createLabeledEntry("NAV1:", 5)
-        table.attach(label, 8, 9, 3, 4)
-        table.attach(self._nav1, 9, 10, 3, 4)
-
-        (label, self._nav2) = self._createLabeledEntry("NAV2:", 5)
-        table.attach(label, 10, 11, 3, 4)
-        table.attach(self._nav2, 11, 12, 3, 4)
-
-        (label, self._fuel) = self._createLabeledEntry("Fuel:", 40, xalign = 0.0)
-        table.attach(label, 0, 1, 4, 5)
-        table.attach(self._fuel, 1, 4, 4, 5)
-
-        (label, self._n1) = self._createLabeledEntry("N1/RPM:", 20, xalign = 0.0)
-        table.attach(label, 4, 5, 4, 5)
-        table.attach(self._n1, 5, 8, 4, 5)
-
-        (label, self._reverser) = self._createLabeledEntry("Reverser:", 20, xalign = 0.0)
-        table.attach(label, 8, 9, 4, 5)
-        table.attach(self._reverser, 9, 12, 4, 5)
-
-        self._navLightsOn = gtk.Label("NAV")
-        table.attach(self._navLightsOn, 0, 1, 5, 6)
-
-        self._antiCollisionLightsOn = gtk.Label("ANTICOLLISION")
-        table.attach(self._antiCollisionLightsOn, 1, 3, 5, 6)
-
-        self._strobeLightsOn = gtk.Label("STROBE")
-        table.attach(self._strobeLightsOn, 3, 4, 5, 6)
-
-        self._landingLightsOn = gtk.Label("LANDING")
-        table.attach(self._landingLightsOn, 4, 5, 5, 6)
-
-        self._pitotHeatOn = gtk.Label("PITOT HEAT")
-        table.attach(self._pitotHeatOn, 5, 7, 5, 6)
-
-        self._parking = gtk.Label("PARKING")
-        table.attach(self._parking, 7, 8, 5, 6)
-
-        self._gearsDown = gtk.Label("GEARS DOWN")
-        table.attach(self._gearsDown, 8, 10, 5, 6)
-
-        self._spoilersArmed = gtk.Label("SPOILERS ARMED")
-        table.attach(self._spoilersArmed, 10, 12, 5, 6)
-
-        (label, self._spoilersExtension) = self._createLabeledEntry("Spoilers:", 3)
-        table.attach(label, 0, 1, 6, 7)
-        table.attach(self._spoilersExtension, 1, 2, 6, 7)
-
-        (label, self._windSpeed) = self._createLabeledEntry("Wind speed:", 3)
-        table.attach(label, 2, 3, 6, 7)
-        table.attach(self._windSpeed, 3, 4, 6, 7)
-
-        (label, self._windDirection) = self._createLabeledEntry("Wind from:", 3)
-        table.attach(label, 4, 5, 6, 7)
-        table.attach(self._windDirection, 5, 6, 6, 7)
-
-        alignment.add(table)
-
-        self.add(alignment)
-
-        self.setData()
-
-    def _createLabeledEntry(self, label, width = 8, xalign = 1.0):
-        """Create a labeled entry.
-
-        Return a tuple consisting of:
-        - the box
-        - the entry."""
-        
-        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5, xscale = 1.0)
-        alignment.set_padding(padding_top = 0, padding_bottom = 0,
-                              padding_left = 0, padding_right = 16)
-        alignment.add(gtk.Label(label))
-
-        entry = gtk.Entry()
-        entry.set_editable(False)
-        entry.set_width_chars(width)
-        entry.set_max_length(width)
-        entry.set_alignment(xalign)
-
-        return (alignment, entry)
-
-    def setData(self, aircraftState = None):
-        """Set the data.
-
-        If aircraftState is None, everything will be set to its default."""
-        if aircraftState is None:
-            self._timestamp.set_text("--:--:--")
-            self._paused.set_sensitive(False)
-            self._trickMode.set_sensitive(False)
-            self._overspeed.set_sensitive(False)
-            self._stalled.set_sensitive(False)
-            self._onTheGround.set_sensitive(False)
-            self._zfw.set_text("-")
-            self._grossWeight.set_text("-")
-            self._heading.set_text("-")
-            self._pitch.set_text("-")
-            self._bank.set_text("-")
-            self._vs.set_text("-")
-            self._ias.set_text("-")
-            self._mach.set_text("-")
-            self._groundSpeed.set_text("-")
-            self._radioAltitude.set_text("-")
-            self._altitude.set_text("-")
-            self._gLoad.set_text("-")
-            self._flapsSet.set_text("-")
-            self._flaps.set_text("-")
-            self._altimeter.set_text("-")
-            self._squawk.set_text("-")
-            self._nav1.set_text("-")
-            self._nav2.set_text("-")
-            self._fuel.set_text("-")
-            self._n1.set_text("-")
-            self._reverser.set_text("-")
-            self._navLightsOn.set_sensitive(False)
-            self._antiCollisionLightsOn.set_sensitive(False)
-            self._strobeLightsOn.set_sensitive(False)
-            self._landingLightsOn.set_sensitive(False)
-            self._pitotHeatOn.set_sensitive(False)
-            self._parking.set_sensitive(False)
-            self._gearsDown.set_sensitive(False)
-            self._spoilersArmed.set_sensitive(False)
-            self._spoilersExtension.set_text("-")
-            self._windSpeed.set_text("-")
-            self._windDirection.set_text("-")
-        else:
-            self._timestamp.set_text(time.strftime("%H:%M:%S",
-                                                   time.gmtime(aircraftState.timestamp)))
-            self._paused.set_sensitive(aircraftState.paused)
-            self._trickMode.set_sensitive(aircraftState.trickMode)
-            self._overspeed.set_sensitive(aircraftState.overspeed)
-            self._stalled.set_sensitive(aircraftState.stalled)
-            self._onTheGround.set_sensitive(aircraftState.onTheGround)
-            self._zfw.set_text("%.0f" % (aircraftState.zfw,))
-            self._grossWeight.set_text("%.0f" % (aircraftState.grossWeight,))
-            self._heading.set_text("%03.0f" % (aircraftState.heading,))
-            self._pitch.set_text("%.0f" % (aircraftState.pitch,))
-            self._bank.set_text("%.0f" % (aircraftState.bank,))
-            self._vs.set_text("%.0f" % (aircraftState.vs,))
-            self._ias.set_text("%.0f" % (aircraftState.ias,))
-            self._mach.set_text("%.2f" % (aircraftState.mach,))
-            self._groundSpeed.set_text("%.0f" % (aircraftState.groundSpeed,))
-            self._radioAltitude.set_text("%.0f" % (aircraftState.radioAltitude,))
-            self._altitude.set_text("%.0f" % (aircraftState.altitude,))
-            self._gLoad.set_text("%.2f" % (aircraftState.gLoad,))
-            self._flapsSet.set_text("%.0f" % (aircraftState.flapsSet,))
-            self._flaps.set_text("%.0f" % (aircraftState.flaps,))
-            self._altimeter.set_text("%.0f" % (aircraftState.altimeter,))
-            self._squawk.set_text(aircraftState.squawk)
-            self._nav1.set_text(aircraftState.nav1)
-            self._nav2.set_text(aircraftState.nav2)
-
-            fuelStr = ""
-            for fuel in aircraftState.fuel:
-                if fuelStr: fuelStr += ", "
-                fuelStr += "%.0f" % (fuel,)                
-            self._fuel.set_text(fuelStr)
-
-            if hasattr(aircraftState, "n1"):
-                n1Str = ""
-                for n1 in aircraftState.n1:
-                    if n1Str: n1Str += ", "
-                    n1Str += "%.0f" % (n1,)
-            elif hasattr(aircraftState, "rpm"):
-                n1Str = ""
-                for rpm in aircraftState.rpm:
-                    if n1Str: n1Str += ", "
-                    n1Str += "%.0f" % (rpm,)
-            else:
-                n1Str = "-"
-            self._n1.set_text(n1Str)
-
-            reverserStr = ""
-            for reverser in aircraftState.reverser:
-                if reverserStr: reverserStr += ", "
-                reverserStr += "ON" if reverser else "OFF"
-            self._reverser.set_text(reverserStr)
-
-            self._navLightsOn.set_sensitive(aircraftState.navLightsOn)
-            self._antiCollisionLightsOn.set_sensitive(aircraftState.antiCollisionLightsOn)
-            self._strobeLightsOn.set_sensitive(aircraftState.strobeLightsOn)
-            self._landingLightsOn.set_sensitive(aircraftState.landingLightsOn)
-            self._pitotHeatOn.set_sensitive(aircraftState.pitotHeatOn)
-            self._parking.set_sensitive(aircraftState.parking)
-            self._gearsDown.set_sensitive(aircraftState.gearsDown)
-            self._spoilersArmed.set_sensitive(aircraftState.spoilersArmed)
-            self._spoilersExtension.set_text("%.0f" % (aircraftState.spoilersExtension,))
-            self._windSpeed.set_text("%.0f" % (aircraftState.windSpeed,))
-            self._windDirection.set_text("%03.0f" % (aircraftState.windDirection,))
-
-#------------------------------------------------------------------------------
Index: src/mlx/gui/statusbar.py
===================================================================
--- src/mlx/gui/statusbar.py	(revision 70)
+++ src/mlx/gui/statusbar.py	(revision 70)
@@ -0,0 +1,117 @@
+# Implementation of the status bar.
+
+#-------------------------------------------------------------------------------
+
+from common import *
+
+import mlx.const as const
+
+import math
+
+#-------------------------------------------------------------------------------
+
+class Statusbar(gtk.Frame, FlightStatusHandler):
+    """A status bar for the logger."""
+    def __init__(self):
+        """Construct the status bar."""
+        gtk.Frame.__init__(self)
+        FlightStatusHandler.__init__(self)
+
+        self._connecting = False
+        self._connected = False
+        
+        self.set_shadow_type(gtk.ShadowType.NONE if pygobject
+                             else gtk.SHADOW_NONE)
+
+        frameAlignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
+
+        frameAlignment.set_padding(padding_top = 2, padding_bottom = 2,
+                                   padding_left = 16, padding_right = 16)
+        self.add(frameAlignment)
+
+        statusBox = gtk.HBox()
+        frameAlignment.add(statusBox)
+
+        self._connStateArea = gtk.DrawingArea()
+        self._connStateArea.set_size_request(16, 16)
+        self._connStateArea.set_tooltip_markup('The state of the connection.\n'
+                                               '<span foreground="grey">Grey</span> means idle.\n'
+                                               '<span foreground="red">Red</span> means trying to connect.\n'
+                                               '<span foreground="green">Green</span> means connected.')
+
+        if pygobject:
+            self._connStateArea.connect("draw", self._drawConnState)
+        else:
+            self._connStateArea.connect("expose_event", self._drawConnState)
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5)
+        alignment.add(self._connStateArea)        
+
+        statusBox.pack_start(alignment, False, False, 8)
+
+        statusBox.pack_start(gtk.VSeparator(), False, False, 8)
+
+        self._stageLabel = gtk.Label()
+        self._stageLabel.set_width_chars(20)
+        self._stageLabel.set_tooltip_text("The flight stage")
+        self._stageLabel.set_alignment(0.0, 0.5)
+        
+        statusBox.pack_start(self._stageLabel, False, False, 8)
+
+        statusBox.pack_start(gtk.VSeparator(), False, False, 8)
+
+        self._ratingLabel = gtk.Label()
+        self._ratingLabel.set_width_chars(12)
+        self._ratingLabel.set_tooltip_text("The flight rating")
+        self._ratingLabel.set_alignment(0.0, 0.5)
+        
+        statusBox.pack_start(self._ratingLabel, False, False, 8)
+
+        self._busyLabel = gtk.Label()
+        self._busyLabel.set_width_chars(30)
+        self._busyLabel.set_tooltip_text("The status of the background tasks.")
+        self._busyLabel.set_alignment(1.0, 0.5)
+        statusBox.pack_start(self._busyLabel, True, True, 8)
+        
+        self._updateFlightStatus()
+
+    def updateConnection(self, connecting, connected):
+        """Update the connection status."""
+        self._connecting = connecting
+        self._connected = connected
+        self._connStateArea.queue_draw()
+
+    def updateBusyState(self, message):
+        """Update the busy state."""
+        self._busyLabel.set_text("" if message is None else message)
+
+    def _drawConnState(self, connStateArea, eventOrContext):
+        """Draw the connection state."""        
+        context = eventOrContext if pygobject else connStateArea.window.cairo_create()
+
+        if self._connecting:
+            if self._connected:
+                context.set_source_rgb(0.0, 1.0, 0.0)
+            else:
+                context.set_source_rgb(1.0, 0.0, 0.0)
+        else:
+            context.set_source_rgb(0.75, 0.75, 0.75)
+
+        width = connStateArea.get_allocated_width() if pygobject \
+                else connStateArea.allocation.width
+        height = connStateArea.get_allocated_height() if pygobject \
+                 else connStateArea.allocation.height
+        context.arc(width/2, height/2, width/2, 0, 2*math.pi)
+
+        context.fill()
+
+    def _updateFlightStatus(self):
+        """Update the flight status information."""
+        self._stageLabel.set_text("-" if self._stage is None
+                                  else const.stage2string(self._stage).upper())
+    
+        if self._noGoReason is None:
+            rating = "%.0f%%" % (self._rating,)
+        else:
+            rating = '<span foreground="red">' + self._noGoReason + '</span>'
+        self._ratingLabel.set_markup(rating)
Index: src/mlx/gui/statusbar.py
===================================================================
--- src/mlx/gui/statusbar.py	(revision 70)
+++ 	(revision )
@@ -1,117 +1,0 @@
-# Implementation of the status bar.
-
-#-------------------------------------------------------------------------------
-
-from common import *
-
-import mlx.const as const
-
-import math
-
-#-------------------------------------------------------------------------------
-
-class Statusbar(gtk.Frame, FlightStatusHandler):
-    """A status bar for the logger."""
-    def __init__(self):
-        """Construct the status bar."""
-        gtk.Frame.__init__(self)
-        FlightStatusHandler.__init__(self)
-
-        self._connecting = False
-        self._connected = False
-        
-        self.set_shadow_type(gtk.ShadowType.NONE if pygobject
-                             else gtk.SHADOW_NONE)
-
-        frameAlignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
-
-        frameAlignment.set_padding(padding_top = 2, padding_bottom = 2,
-                                   padding_left = 16, padding_right = 16)
-        self.add(frameAlignment)
-
-        statusBox = gtk.HBox()
-        frameAlignment.add(statusBox)
-
-        self._connStateArea = gtk.DrawingArea()
-        self._connStateArea.set_size_request(16, 16)
-        self._connStateArea.set_tooltip_markup('The state of the connection.\n'
-                                               '<span foreground="grey">Grey</span> means idle.\n'
-                                               '<span foreground="red">Red</span> means trying to connect.\n'
-                                               '<span foreground="green">Green</span> means connected.')
-
-        if pygobject:
-            self._connStateArea.connect("draw", self._drawConnState)
-        else:
-            self._connStateArea.connect("expose_event", self._drawConnState)
-
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5)
-        alignment.add(self._connStateArea)        
-
-        statusBox.pack_start(alignment, False, False, 8)
-
-        statusBox.pack_start(gtk.VSeparator(), False, False, 8)
-
-        self._stageLabel = gtk.Label()
-        self._stageLabel.set_width_chars(20)
-        self._stageLabel.set_tooltip_text("The flight stage")
-        self._stageLabel.set_alignment(0.0, 0.5)
-        
-        statusBox.pack_start(self._stageLabel, False, False, 8)
-
-        statusBox.pack_start(gtk.VSeparator(), False, False, 8)
-
-        self._ratingLabel = gtk.Label()
-        self._ratingLabel.set_width_chars(12)
-        self._ratingLabel.set_tooltip_text("The flight rating")
-        self._ratingLabel.set_alignment(0.0, 0.5)
-        
-        statusBox.pack_start(self._ratingLabel, False, False, 8)
-
-        self._busyLabel = gtk.Label()
-        self._busyLabel.set_width_chars(30)
-        self._busyLabel.set_tooltip_text("The status of the background tasks.")
-        self._busyLabel.set_alignment(1.0, 0.5)
-        statusBox.pack_start(self._busyLabel, True, True, 8)
-        
-        self._updateFlightStatus()
-
-    def updateConnection(self, connecting, connected):
-        """Update the connection status."""
-        self._connecting = connecting
-        self._connected = connected
-        self._connStateArea.queue_draw()
-
-    def updateBusyState(self, message):
-        """Update the busy state."""
-        self._busyLabel.set_text("" if message is None else message)
-
-    def _drawConnState(self, connStateArea, eventOrContext):
-        """Draw the connection state."""        
-        context = eventOrContext if pygobject else connStateArea.window.cairo_create()
-
-        if self._connecting:
-            if self._connected:
-                context.set_source_rgb(0.0, 1.0, 0.0)
-            else:
-                context.set_source_rgb(1.0, 0.0, 0.0)
-        else:
-            context.set_source_rgb(0.75, 0.75, 0.75)
-
-        width = connStateArea.get_allocated_width() if pygobject \
-                else connStateArea.allocation.width
-        height = connStateArea.get_allocated_height() if pygobject \
-                 else connStateArea.allocation.height
-        context.arc(width/2, height/2, width/2, 0, 2*math.pi)
-
-        context.fill()
-
-    def _updateFlightStatus(self):
-        """Update the flight status information."""
-        self._stageLabel.set_text("-" if self._stage is None
-                                  else const.stage2string(self._stage).upper())
-    
-        if self._noGoReason is None:
-            rating = "%.0f%%" % (self._rating,)
-        else:
-            rating = '<span foreground="red">' + self._noGoReason + '</span>'
-        self._ratingLabel.set_markup(rating)
Index: src/mlx/gui/statusicon.py
===================================================================
--- src/mlx/gui/statusicon.py	(revision 77)
+++ src/mlx/gui/statusicon.py	(revision 77)
@@ -0,0 +1,159 @@
+# Implementation of the status icon
+
+#-------------------------------------------------------------------------------
+
+from common import *
+
+import mlx.const as const
+
+#-------------------------------------------------------------------------------
+
+class StatusIcon(FlightStatusHandler):
+    """The class handling the status icon."""
+    def __init__(self, iconDirectory, gui):
+        """Construct the status icon."""
+        super(StatusIcon, self).__init__()
+
+        self._gui = gui
+        self._selfToggling = False
+
+        menu = gtk.Menu()
+
+        if appIndicator:
+            self._stageMenuItem = gtk.MenuItem()
+            self._stageMenuItem.show()
+            menu.append(self._stageMenuItem)
+
+            self._ratingMenuItem = gtk.MenuItem()
+            self._ratingMenuItem.show()
+            menu.append(self._ratingMenuItem)
+
+            separator = gtk.SeparatorMenuItem()
+            separator.show()
+            menu.append(separator)
+
+        self._showHideMenuItem = gtk.CheckMenuItem()  
+        self._showHideMenuItem.set_label("Show main window")  
+        self._showHideMenuItem.set_active(True)
+        self._showHideMenuItem.connect("toggled", self._showHideToggled)
+        self._showHideMenuItem.show()  
+        menu.append(self._showHideMenuItem)  
+
+        self._showMonitorMenuItem = gtk.CheckMenuItem()  
+        self._showMonitorMenuItem.set_label("Show monitor window")  
+        self._showMonitorMenuItem.set_active(False)
+        self._showMonitorMenuItem.connect("toggled", self._showMonitorToggled)
+        self._showMonitorMenuItem.show()  
+        menu.append(self._showMonitorMenuItem)  
+
+        separator = gtk.SeparatorMenuItem()
+        separator.show()
+        menu.append(separator)
+
+        self._quitMenuItem = gtk.MenuItem()  
+        self._quitMenuItem.set_label("Quit")  
+        self._quitMenuItem.show()  
+        self._quitMenuItem.connect("activate", self._gui._quit)
+        menu.append(self._quitMenuItem)  
+
+        menu.show()  
+
+        iconFile = os.path.join(iconDirectory, "logo.ico")
+
+        if appIndicator:
+            if pygobject:
+                indicator = appindicator.Indicator.new ("mava-logger-x", iconFile,
+                                                        appindicator.IndicatorCategory.APPLICATION_STATUS)
+                indicator.set_status (appindicator.IndicatorStatus.ACTIVE)
+            else:
+                indicator = appindicator.Indicator ("mava-logger-x", iconFile,
+                                                    appindicator.CATEGORY_APPLICATION_STATUS)
+                indicator.set_status (appindicator.STATUS_ACTIVE)
+
+            indicator.set_menu(menu)
+            self._indicator = indicator
+        else:
+            def popup_menu(status, button, time):
+                menu.popup(None, None, gtk.status_icon_position_menu,
+                           button, time, status)
+
+            statusIcon = gtk.StatusIcon()
+            statusIcon.set_from_file(iconFile)
+            statusIcon.set_visible(True)
+            statusIcon.connect('popup-menu', popup_menu)
+            statusIcon.connect('activate',
+                               lambda status: self._gui.toggleMainWindow())
+            self._statusIcon = statusIcon
+
+        self._updateFlightStatus()
+
+    def mainWindowHidden(self):
+        """Called when the main window is hidden."""
+        if self._showHideMenuItem.get_active():
+            self._selfToggling = True
+            self._showHideMenuItem.set_active(False)
+
+    def mainWindowShown(self):
+        """Called when the main window is shown."""
+        if not self._showHideMenuItem.get_active():
+            self._selfToggling = True
+            self._showHideMenuItem.set_active(True)
+
+    def monitorWindowHidden(self):
+        """Called when the monitor window is hidden."""
+        if self._showMonitorMenuItem.get_active():
+            self._selfToggling = True
+            self._showMonitorMenuItem.set_active(False)
+
+    def monitorWindowShown(self):
+        """Called when the monitor window is shown."""
+        if not self._showMonitorMenuItem.get_active():
+            self._selfToggling = True
+            self._showMonitorMenuItem.set_active(True)
+
+    def destroy(self):
+        """Hide and destroy the status icon."""
+        if appIndicator:
+            if pygobject:
+                self._indicator.set_status(appindicator.IndicatorStatus.PASSIVE)
+            else:
+                self._indicator.set_status(appindicator.STATUS_PASSIVE)
+        else:
+            self._statusIcon.set_visible(False)
+        
+    def _showHideToggled(self, menuitem):
+        """Called when the show/hide menu item is toggled."""
+        if self._selfToggling:
+            self._selfToggling = False
+        elif self._showHideMenuItem.get_active():
+            self._gui.showMainWindow()
+        else:
+            self._gui.hideMainWindow()
+
+    def _showMonitorToggled(self, menuitem):
+        """Called when the show/hide monitor window menu item is toggled."""
+        if self._selfToggling:
+            self._selfToggling = False
+        elif self._showMonitorMenuItem.get_active():
+            self._gui.showMonitorWindow()
+        else:
+            self._gui.hideMonitorWindow()
+
+    def _updateFlightStatus(self):
+        """Update the flight status."""
+        stage = "-" if self._stage is None else const.stage2string(self._stage)
+        
+        if self._noGoReason is None:
+            rating = "%.0f%%" % (self._rating,)
+        else:
+            rating = self._noGoReason
+
+        if appIndicator:
+            self._stageMenuItem.set_label("Stage: %s" % (stage,))
+            self._ratingMenuItem.set_label("Rating: %s" % (rating,))
+        else:
+            if self._noGoReason is not None:
+                rating = '<span foreground="red">' + rating + '</span>'
+            markup = "MAVA Logger X %s\n\nStage: %s\nRating: %s" %\
+                     (const.VERSION, stage, rating)
+            self._statusIcon.set_tooltip_markup(markup)
Index: src/mlx/gui/statusicon.py
===================================================================
--- src/mlx/gui/statusicon.py	(revision 77)
+++ 	(revision )
@@ -1,159 +1,0 @@
-# Implementation of the status icon
-
-#-------------------------------------------------------------------------------
-
-from common import *
-
-import mlx.const as const
-
-#-------------------------------------------------------------------------------
-
-class StatusIcon(FlightStatusHandler):
-    """The class handling the status icon."""
-    def __init__(self, iconDirectory, gui):
-        """Construct the status icon."""
-        super(StatusIcon, self).__init__()
-
-        self._gui = gui
-        self._selfToggling = False
-
-        menu = gtk.Menu()
-
-        if appIndicator:
-            self._stageMenuItem = gtk.MenuItem()
-            self._stageMenuItem.show()
-            menu.append(self._stageMenuItem)
-
-            self._ratingMenuItem = gtk.MenuItem()
-            self._ratingMenuItem.show()
-            menu.append(self._ratingMenuItem)
-
-            separator = gtk.SeparatorMenuItem()
-            separator.show()
-            menu.append(separator)
-
-        self._showHideMenuItem = gtk.CheckMenuItem()  
-        self._showHideMenuItem.set_label("Show main window")  
-        self._showHideMenuItem.set_active(True)
-        self._showHideMenuItem.connect("toggled", self._showHideToggled)
-        self._showHideMenuItem.show()  
-        menu.append(self._showHideMenuItem)  
-
-        self._showMonitorMenuItem = gtk.CheckMenuItem()  
-        self._showMonitorMenuItem.set_label("Show monitor window")  
-        self._showMonitorMenuItem.set_active(False)
-        self._showMonitorMenuItem.connect("toggled", self._showMonitorToggled)
-        self._showMonitorMenuItem.show()  
-        menu.append(self._showMonitorMenuItem)  
-
-        separator = gtk.SeparatorMenuItem()
-        separator.show()
-        menu.append(separator)
-
-        self._quitMenuItem = gtk.MenuItem()  
-        self._quitMenuItem.set_label("Quit")  
-        self._quitMenuItem.show()  
-        self._quitMenuItem.connect("activate", self._gui._quit)
-        menu.append(self._quitMenuItem)  
-
-        menu.show()  
-
-        iconFile = os.path.join(iconDirectory, "logo.ico")
-
-        if appIndicator:
-            if pygobject:
-                indicator = appindicator.Indicator.new ("mava-logger-x", iconFile,
-                                                        appindicator.IndicatorCategory.APPLICATION_STATUS)
-                indicator.set_status (appindicator.IndicatorStatus.ACTIVE)
-            else:
-                indicator = appindicator.Indicator ("mava-logger-x", iconFile,
-                                                    appindicator.CATEGORY_APPLICATION_STATUS)
-                indicator.set_status (appindicator.STATUS_ACTIVE)
-
-            indicator.set_menu(menu)
-            self._indicator = indicator
-        else:
-            def popup_menu(status, button, time):
-                menu.popup(None, None, gtk.status_icon_position_menu,
-                           button, time, status)
-
-            statusIcon = gtk.StatusIcon()
-            statusIcon.set_from_file(iconFile)
-            statusIcon.set_visible(True)
-            statusIcon.connect('popup-menu', popup_menu)
-            statusIcon.connect('activate',
-                               lambda status: self._gui.toggleMainWindow())
-            self._statusIcon = statusIcon
-
-        self._updateFlightStatus()
-
-    def mainWindowHidden(self):
-        """Called when the main window is hidden."""
-        if self._showHideMenuItem.get_active():
-            self._selfToggling = True
-            self._showHideMenuItem.set_active(False)
-
-    def mainWindowShown(self):
-        """Called when the main window is shown."""
-        if not self._showHideMenuItem.get_active():
-            self._selfToggling = True
-            self._showHideMenuItem.set_active(True)
-
-    def monitorWindowHidden(self):
-        """Called when the monitor window is hidden."""
-        if self._showMonitorMenuItem.get_active():
-            self._selfToggling = True
-            self._showMonitorMenuItem.set_active(False)
-
-    def monitorWindowShown(self):
-        """Called when the monitor window is shown."""
-        if not self._showMonitorMenuItem.get_active():
-            self._selfToggling = True
-            self._showMonitorMenuItem.set_active(True)
-
-    def destroy(self):
-        """Hide and destroy the status icon."""
-        if appIndicator:
-            if pygobject:
-                self._indicator.set_status(appindicator.IndicatorStatus.PASSIVE)
-            else:
-                self._indicator.set_status(appindicator.STATUS_PASSIVE)
-        else:
-            self._statusIcon.set_visible(False)
-        
-    def _showHideToggled(self, menuitem):
-        """Called when the show/hide menu item is toggled."""
-        if self._selfToggling:
-            self._selfToggling = False
-        elif self._showHideMenuItem.get_active():
-            self._gui.showMainWindow()
-        else:
-            self._gui.hideMainWindow()
-
-    def _showMonitorToggled(self, menuitem):
-        """Called when the show/hide monitor window menu item is toggled."""
-        if self._selfToggling:
-            self._selfToggling = False
-        elif self._showMonitorMenuItem.get_active():
-            self._gui.showMonitorWindow()
-        else:
-            self._gui.hideMonitorWindow()
-
-    def _updateFlightStatus(self):
-        """Update the flight status."""
-        stage = "-" if self._stage is None else const.stage2string(self._stage)
-        
-        if self._noGoReason is None:
-            rating = "%.0f%%" % (self._rating,)
-        else:
-            rating = self._noGoReason
-
-        if appIndicator:
-            self._stageMenuItem.set_label("Stage: %s" % (stage,))
-            self._ratingMenuItem.set_label("Rating: %s" % (rating,))
-        else:
-            if self._noGoReason is not None:
-                rating = '<span foreground="red">' + rating + '</span>'
-            markup = "MAVA Logger X %s\n\nStage: %s\nRating: %s" %\
-                     (const.VERSION, stage, rating)
-            self._statusIcon.set_tooltip_markup(markup)
Index: src/mlx/gui/update.py
===================================================================
--- src/mlx/gui/update.py	(revision 38)
+++ src/mlx/gui/update.py	(revision 38)
@@ -0,0 +1,286 @@
+# The GUI part of the update
+
+#-------------------------------------------------------------------------------
+
+from mlx.gui.common import *
+
+from mlx.update import update
+
+import mlx.const as const
+
+import threading
+import os
+import sys
+
+#-------------------------------------------------------------------------------
+
+class Updater(threading.Thread):
+    """The updater thread."""
+
+    # The removal or renaming of one file equals to the downloading of this
+    # many bytes in time (for the progress calculation)
+    REMOVE2BYTES = 100
+
+    _progressWindow = None
+
+    _progressLabel = None
+    
+    _progressBar = None
+    
+    _progressOKButton = None
+
+    _sudoDialog = None
+
+    @staticmethod
+    def _createGUI(parentWindow):
+        """Create the GUI elements, if needed."""
+        if Updater._progressWindow is not None:
+            return
+
+        Updater._progressWindow = window = gtk.Window()
+        window.set_title("MAVA Logger X " + const.VERSION + " Update")
+        window.set_transient_for(parentWindow)
+        #win.set_icon_from_file(os.path.join(iconDirectory, "logo.ico"))
+        window.set_size_request(400, -1)
+        window.set_resizable(False)
+        window.set_modal(True)
+        window.connect("delete-event", lambda a, b: True)
+        window.set_deletable(False)
+        window.set_position(gtk.WindowPosition.CENTER_ON_PARENT if pygobject
+                            else gtk.WIN_POS_CENTER_ON_PARENT)
+
+        mainAlignment = gtk.Alignment(xscale = 1.0)
+        mainAlignment.set_padding(padding_top = 4, padding_bottom = 10,
+                                  padding_left = 16, padding_right = 16)
+        window.add(mainAlignment)
+
+        mainVBox = gtk.VBox()
+        mainAlignment.add(mainVBox)
+
+        labelAlignment = gtk.Alignment(xalign = 0.0, xscale = 0.0)
+        Updater._progressLabel = progressLabel = gtk.Label()
+        labelAlignment.add(progressLabel)
+        mainVBox.pack_start(labelAlignment, True, True, 4)
+        
+        Updater._progressBar = progressBar = gtk.ProgressBar()
+        mainVBox.pack_start(progressBar, True, True, 4)
+
+        buttonAlignment = gtk.Alignment(xalign = 0.5, xscale = 0.1)
+        Updater._progressOKButton = progressOKButton = gtk.Button("OK")
+        buttonAlignment.add(progressOKButton)        
+        mainVBox.pack_start(buttonAlignment, True, True, 4)
+
+        Updater._sudoDialog = sudoDialog = \
+            gtk.Dialog("MAVA Logger X " + const.VERSION + " Update",
+                       parentWindow,
+                       gtk.DialogFlags.MODAL if pygobject else gtk.DIALOG_MODAL,
+                       (gtk.STOCK_CANCEL, 0,
+                        gtk.STOCK_OK, 1))
+
+        infoLabelAlignment = gtk.Alignment(xalign = 0.5, xscale = 0.1)
+        infoLabelAlignment.set_padding(padding_top = 4, padding_bottom = 10,
+                                       padding_left = 16, padding_right = 16)
+
+        infoLabel = gtk.Label("There is an update available, but the program cannot write\n"
+                              "its directory due to insufficient privileges.\n\n"
+                              "Click OK, if you want to run a helper program\n"
+                              "with administrator privileges "
+                              "to complete the update,\n"
+                              "Cancel otherwise.")
+        infoLabel.set_justify(gtk.Justification.CENTER if pygobject
+                              else gtk.JUSTIFY_CENTER)
+        infoLabelAlignment.add(infoLabel)
+        sudoDialog.vbox.pack_start(infoLabelAlignment, True, True, 4)
+
+        sudoDialog.set_position(gtk.WindowPosition.CENTER_ON_PARENT if pygobject
+                                else gtk.WIN_POS_CENTER_ON_PARENT)
+
+    def __init__(self, gui, programDirectory, updateURL, parentWindow):
+        """Construct the updater. If not created yet, the windows used by the
+        updater are also created."""
+        super(Updater, self).__init__()
+
+        self._gui = gui
+        
+        self._programDirectory = programDirectory
+        self._updateURL = updateURL
+
+        self._totalProgress = 0
+        self._waitAfterFinish = False
+        self._restart = False
+
+        self._sudoReply = None
+        self._sudoCondition = threading.Condition()
+
+        Updater._createGUI(parentWindow)
+
+    def run(self):
+        """Execute the thread's operation."""
+        gobject.idle_add(self._startUpdate)
+        update(self._programDirectory, self._updateURL, self, fromGUI = True)
+        if not self._waitAfterFinish:
+            gobject.idle_add(self._progressWindow.hide)
+
+    def downloadingManifest(self):
+        """Called when the downloading of the manifest has started."""
+        gobject.idle_add(self._downloadingManifest)
+
+    def _downloadingManifest(self):
+        """Called when the downloading of the manifest has started."""
+        self._progressLabel.set_text("Downloading manifest...")
+        self._progressBar.set_fraction(0)
+
+    def downloadedManifest(self):
+        """Called when the downloading of the manifest has finished."""
+        gobject.idle_add(self._downloadedManifest)
+
+    def _downloadedManifest(self):
+        """Called when the downloading of the manifest has finished."""
+        self._progressLabel.set_text("Downloaded manifest...")
+        self._progressBar.set_fraction(0.05)
+
+    def needSudo(self):
+        """Called when the program is interested in whether we want to run a
+        program with administrator rights to do the update."""
+        gobject.idle_add(self._needSudo)
+        with self._sudoCondition:
+            while self._sudoReply is None:
+                self._sudoCondition.wait(1)
+            return self._sudoReply
+
+    def _needSudo(self):
+        """Called when the program is interested in whether we want to run a
+        program with administrator rights to do the update."""
+        self._sudoDialog.show_all()
+        result = self._sudoDialog.run()
+        self._sudoDialog.hide()
+        with self._sudoCondition:
+            self._sudoReply = result!=0
+            self._sudoCondition.notify()
+        
+    def setTotalSize(self, numToModifyAndNew, totalSize, numToRemove,
+                     numToRemoveLocal):
+        """Called when the downloading of the files has started."""
+        self._numToModifyAndNew = numToModifyAndNew
+        self._numModifiedOrNew = 0
+        self._totalSize = totalSize
+        self._downloaded = 0
+        self._numToRemove = numToRemove
+        self._numToRemoveLocal = numToRemoveLocal
+        self._numRemoved = 0
+
+        self._totalProgress = self._totalSize + \
+                              (self._numToModifyAndNew + \
+                               self._numToRemove + self._numToRemoveLocal) * \
+                              Updater.REMOVE2BYTES
+        self._waitAfterFinish = self._totalSize > 0 or self._numToRemove > 0
+
+    def _startDownload(self):
+        """Called when the download has started."""
+        self._progressLabel.set_text("Downloading files...")
+
+    def setDownloaded(self, downloaded):
+        """Called when a certain number of bytes are downloaded."""
+        self._downloaded = downloaded
+        gobject.idle_add(self._setDownloaded, downloaded)
+
+    def _setDownloaded(self, downloaded):
+        """Called when a certain number of bytes are downloaded."""
+        self._progressLabel.set_text("Downloaded %d of %d" % \
+                                     (downloaded, self._totalSize))
+        self._setProgress()
+
+    def startRenaming(self):
+        """Called when the renaming of files has started."""
+        gobject.idle_add(self._startRenaming)
+
+    def _startRenaming(self):
+        """Called when the renaming of files has started."""
+        self._progressLabel.set_text("Renaming downloaded files...")
+
+    def renamed(self, path, count):
+        """Called when a file has been renamed."""
+        self._numModifiedOrNew = count
+        gobject.idle_add(self._renamed, path, count)
+
+    def _renamed(self, path, count):
+        """Called when a file has been renamed."""
+        self._progressLabel.set_text("Renamed %s" % (path,))
+        self._setProgress()
+
+    def startRemoving(self):
+        """Called when the removing of files has started."""
+        gobject.idle_add(self._startRemoving)
+
+    def _startRemoving(self):
+        """Called when the removing of files has started."""
+        self._progressLabel.set_text("Removing files...")
+
+    def removed(self, path, count):
+        """Called when a file has been removed."""
+        self._numRemoved = count
+        gobject.idle_add(self._removed, path, count)
+
+    def _removed(self, path, count):
+        """Called when a file has been removed."""
+        self._progressLabel.set_text("Removed %s" % (path,))
+        self._setProgress()
+
+    def writingManifest(self):
+        """Called when the writing of the new manifest file has started."""
+        gobject.idle_add(self._writingManifest)
+
+    def _writingManifest(self):
+        """Called when the writing of the new manifest file has started."""
+        self._progressLabel.set_text("Writing the new manifest")
+        
+    def done(self):
+        """Called when the update has been done."""
+        gobject.idle_add(self._done)
+        self._restart = self._waitAfterFinish
+
+    def _done(self):
+        """Called when the writing of the new manifest file has started."""
+        self._progressBar.set_fraction(1)
+        if self._totalProgress>0:
+            self._progressLabel.set_text("Finished updating. Press OK to restart the program.")
+            self._progressOKButton.set_sensitive(True)
+        else:
+            self._progressLabel.set_text("There was nothing to update")
+        
+    def _setProgress(self):
+        """Set the progress bar based on the current stage."""
+        if self._totalProgress>0:
+            progress = self._downloaded + \
+                       (self._numModifiedOrNew + self._numRemoved) * \
+                       Updater.REMOVE2BYTES
+            self._progressBar.set_fraction(0.05 + 0.94 * progress / self._totalProgress)
+
+    def failed(self, what):
+        """Called when the downloading has failed."""
+        self._waitAfterFinish = True
+        gobject.idle_add(self._failed, what)
+
+    def _failed(self, what):
+        """Called when the downloading has failed."""        
+        self._progressLabel.set_text("Failed, see the debug log for details.")
+        self._progressBar.set_fraction(1)
+        self._progressOKButton.set_sensitive(True)
+
+    def _startUpdate(self):
+        """Start the update.
+
+        It resets the GUI elements."""
+        self._progressLabel.set_text("")
+        self._progressBar.set_fraction(0)
+        self._progressWindow.show_all()
+        self._progressOKButton.set_sensitive(False)
+        self._progressOKButton.connect("clicked", self._progressOKClicked)
+
+    def _progressOKClicked(self, button):
+        """Called when the OK button on the progress window is clicked."""
+        self._progressWindow.hide()
+        if self._restart:
+            self._gui.restart()
+
+#-------------------------------------------------------------------------------
Index: src/mlx/gui/update.py
===================================================================
--- src/mlx/gui/update.py	(revision 38)
+++ 	(revision )
@@ -1,286 +1,0 @@
-# The GUI part of the update
-
-#-------------------------------------------------------------------------------
-
-from mlx.gui.common import *
-
-from mlx.update import update
-
-import mlx.const as const
-
-import threading
-import os
-import sys
-
-#-------------------------------------------------------------------------------
-
-class Updater(threading.Thread):
-    """The updater thread."""
-
-    # The removal or renaming of one file equals to the downloading of this
-    # many bytes in time (for the progress calculation)
-    REMOVE2BYTES = 100
-
-    _progressWindow = None
-
-    _progressLabel = None
-    
-    _progressBar = None
-    
-    _progressOKButton = None
-
-    _sudoDialog = None
-
-    @staticmethod
-    def _createGUI(parentWindow):
-        """Create the GUI elements, if needed."""
-        if Updater._progressWindow is not None:
-            return
-
-        Updater._progressWindow = window = gtk.Window()
-        window.set_title("MAVA Logger X " + const.VERSION + " Update")
-        window.set_transient_for(parentWindow)
-        #win.set_icon_from_file(os.path.join(iconDirectory, "logo.ico"))
-        window.set_size_request(400, -1)
-        window.set_resizable(False)
-        window.set_modal(True)
-        window.connect("delete-event", lambda a, b: True)
-        window.set_deletable(False)
-        window.set_position(gtk.WindowPosition.CENTER_ON_PARENT if pygobject
-                            else gtk.WIN_POS_CENTER_ON_PARENT)
-
-        mainAlignment = gtk.Alignment(xscale = 1.0)
-        mainAlignment.set_padding(padding_top = 4, padding_bottom = 10,
-                                  padding_left = 16, padding_right = 16)
-        window.add(mainAlignment)
-
-        mainVBox = gtk.VBox()
-        mainAlignment.add(mainVBox)
-
-        labelAlignment = gtk.Alignment(xalign = 0.0, xscale = 0.0)
-        Updater._progressLabel = progressLabel = gtk.Label()
-        labelAlignment.add(progressLabel)
-        mainVBox.pack_start(labelAlignment, True, True, 4)
-        
-        Updater._progressBar = progressBar = gtk.ProgressBar()
-        mainVBox.pack_start(progressBar, True, True, 4)
-
-        buttonAlignment = gtk.Alignment(xalign = 0.5, xscale = 0.1)
-        Updater._progressOKButton = progressOKButton = gtk.Button("OK")
-        buttonAlignment.add(progressOKButton)        
-        mainVBox.pack_start(buttonAlignment, True, True, 4)
-
-        Updater._sudoDialog = sudoDialog = \
-            gtk.Dialog("MAVA Logger X " + const.VERSION + " Update",
-                       parentWindow,
-                       gtk.DialogFlags.MODAL if pygobject else gtk.DIALOG_MODAL,
-                       (gtk.STOCK_CANCEL, 0,
-                        gtk.STOCK_OK, 1))
-
-        infoLabelAlignment = gtk.Alignment(xalign = 0.5, xscale = 0.1)
-        infoLabelAlignment.set_padding(padding_top = 4, padding_bottom = 10,
-                                       padding_left = 16, padding_right = 16)
-
-        infoLabel = gtk.Label("There is an update available, but the program cannot write\n"
-                              "its directory due to insufficient privileges.\n\n"
-                              "Click OK, if you want to run a helper program\n"
-                              "with administrator privileges "
-                              "to complete the update,\n"
-                              "Cancel otherwise.")
-        infoLabel.set_justify(gtk.Justification.CENTER if pygobject
-                              else gtk.JUSTIFY_CENTER)
-        infoLabelAlignment.add(infoLabel)
-        sudoDialog.vbox.pack_start(infoLabelAlignment, True, True, 4)
-
-        sudoDialog.set_position(gtk.WindowPosition.CENTER_ON_PARENT if pygobject
-                                else gtk.WIN_POS_CENTER_ON_PARENT)
-
-    def __init__(self, gui, programDirectory, updateURL, parentWindow):
-        """Construct the updater. If not created yet, the windows used by the
-        updater are also created."""
-        super(Updater, self).__init__()
-
-        self._gui = gui
-        
-        self._programDirectory = programDirectory
-        self._updateURL = updateURL
-
-        self._totalProgress = 0
-        self._waitAfterFinish = False
-        self._restart = False
-
-        self._sudoReply = None
-        self._sudoCondition = threading.Condition()
-
-        Updater._createGUI(parentWindow)
-
-    def run(self):
-        """Execute the thread's operation."""
-        gobject.idle_add(self._startUpdate)
-        update(self._programDirectory, self._updateURL, self, fromGUI = True)
-        if not self._waitAfterFinish:
-            gobject.idle_add(self._progressWindow.hide)
-
-    def downloadingManifest(self):
-        """Called when the downloading of the manifest has started."""
-        gobject.idle_add(self._downloadingManifest)
-
-    def _downloadingManifest(self):
-        """Called when the downloading of the manifest has started."""
-        self._progressLabel.set_text("Downloading manifest...")
-        self._progressBar.set_fraction(0)
-
-    def downloadedManifest(self):
-        """Called when the downloading of the manifest has finished."""
-        gobject.idle_add(self._downloadedManifest)
-
-    def _downloadedManifest(self):
-        """Called when the downloading of the manifest has finished."""
-        self._progressLabel.set_text("Downloaded manifest...")
-        self._progressBar.set_fraction(0.05)
-
-    def needSudo(self):
-        """Called when the program is interested in whether we want to run a
-        program with administrator rights to do the update."""
-        gobject.idle_add(self._needSudo)
-        with self._sudoCondition:
-            while self._sudoReply is None:
-                self._sudoCondition.wait(1)
-            return self._sudoReply
-
-    def _needSudo(self):
-        """Called when the program is interested in whether we want to run a
-        program with administrator rights to do the update."""
-        self._sudoDialog.show_all()
-        result = self._sudoDialog.run()
-        self._sudoDialog.hide()
-        with self._sudoCondition:
-            self._sudoReply = result!=0
-            self._sudoCondition.notify()
-        
-    def setTotalSize(self, numToModifyAndNew, totalSize, numToRemove,
-                     numToRemoveLocal):
-        """Called when the downloading of the files has started."""
-        self._numToModifyAndNew = numToModifyAndNew
-        self._numModifiedOrNew = 0
-        self._totalSize = totalSize
-        self._downloaded = 0
-        self._numToRemove = numToRemove
-        self._numToRemoveLocal = numToRemoveLocal
-        self._numRemoved = 0
-
-        self._totalProgress = self._totalSize + \
-                              (self._numToModifyAndNew + \
-                               self._numToRemove + self._numToRemoveLocal) * \
-                              Updater.REMOVE2BYTES
-        self._waitAfterFinish = self._totalSize > 0 or self._numToRemove > 0
-
-    def _startDownload(self):
-        """Called when the download has started."""
-        self._progressLabel.set_text("Downloading files...")
-
-    def setDownloaded(self, downloaded):
-        """Called when a certain number of bytes are downloaded."""
-        self._downloaded = downloaded
-        gobject.idle_add(self._setDownloaded, downloaded)
-
-    def _setDownloaded(self, downloaded):
-        """Called when a certain number of bytes are downloaded."""
-        self._progressLabel.set_text("Downloaded %d of %d" % \
-                                     (downloaded, self._totalSize))
-        self._setProgress()
-
-    def startRenaming(self):
-        """Called when the renaming of files has started."""
-        gobject.idle_add(self._startRenaming)
-
-    def _startRenaming(self):
-        """Called when the renaming of files has started."""
-        self._progressLabel.set_text("Renaming downloaded files...")
-
-    def renamed(self, path, count):
-        """Called when a file has been renamed."""
-        self._numModifiedOrNew = count
-        gobject.idle_add(self._renamed, path, count)
-
-    def _renamed(self, path, count):
-        """Called when a file has been renamed."""
-        self._progressLabel.set_text("Renamed %s" % (path,))
-        self._setProgress()
-
-    def startRemoving(self):
-        """Called when the removing of files has started."""
-        gobject.idle_add(self._startRemoving)
-
-    def _startRemoving(self):
-        """Called when the removing of files has started."""
-        self._progressLabel.set_text("Removing files...")
-
-    def removed(self, path, count):
-        """Called when a file has been removed."""
-        self._numRemoved = count
-        gobject.idle_add(self._removed, path, count)
-
-    def _removed(self, path, count):
-        """Called when a file has been removed."""
-        self._progressLabel.set_text("Removed %s" % (path,))
-        self._setProgress()
-
-    def writingManifest(self):
-        """Called when the writing of the new manifest file has started."""
-        gobject.idle_add(self._writingManifest)
-
-    def _writingManifest(self):
-        """Called when the writing of the new manifest file has started."""
-        self._progressLabel.set_text("Writing the new manifest")
-        
-    def done(self):
-        """Called when the update has been done."""
-        gobject.idle_add(self._done)
-        self._restart = self._waitAfterFinish
-
-    def _done(self):
-        """Called when the writing of the new manifest file has started."""
-        self._progressBar.set_fraction(1)
-        if self._totalProgress>0:
-            self._progressLabel.set_text("Finished updating. Press OK to restart the program.")
-            self._progressOKButton.set_sensitive(True)
-        else:
-            self._progressLabel.set_text("There was nothing to update")
-        
-    def _setProgress(self):
-        """Set the progress bar based on the current stage."""
-        if self._totalProgress>0:
-            progress = self._downloaded + \
-                       (self._numModifiedOrNew + self._numRemoved) * \
-                       Updater.REMOVE2BYTES
-            self._progressBar.set_fraction(0.05 + 0.94 * progress / self._totalProgress)
-
-    def failed(self, what):
-        """Called when the downloading has failed."""
-        self._waitAfterFinish = True
-        gobject.idle_add(self._failed, what)
-
-    def _failed(self, what):
-        """Called when the downloading has failed."""        
-        self._progressLabel.set_text("Failed, see the debug log for details.")
-        self._progressBar.set_fraction(1)
-        self._progressOKButton.set_sensitive(True)
-
-    def _startUpdate(self):
-        """Start the update.
-
-        It resets the GUI elements."""
-        self._progressLabel.set_text("")
-        self._progressBar.set_fraction(0)
-        self._progressWindow.show_all()
-        self._progressOKButton.set_sensitive(False)
-        self._progressOKButton.connect("clicked", self._progressOKClicked)
-
-    def _progressOKClicked(self, button):
-        """Called when the OK button on the progress window is clicked."""
-        self._progressWindow.hide()
-        if self._restart:
-            self._gui.restart()
-
-#-------------------------------------------------------------------------------
Index: src/mlx/logger.py
===================================================================
--- src/mlx/logger.py	(revision 31)
+++ src/mlx/logger.py	(revision 31)
@@ -0,0 +1,99 @@
+# Module for the logging.
+
+#--------------------------------------------------------------------------------------
+
+import const
+
+import sys
+import time
+
+#--------------------------------------------------------------------------------------
+
+class Logger(object):
+    """The class with the interface to log the various events."""
+    # FIXME: shall we use const.stage2string() instead?
+    _stages = { const.STAGE_BOARDING : "Boarding",
+                const.STAGE_PUSHANDTAXI : "Pushback and Taxi",
+                const.STAGE_TAKEOFF : "Takeoff",
+                const.STAGE_RTO : "RTO",
+                const.STAGE_CLIMB : "Climb",
+                const.STAGE_CRUISE : "Cruise",
+                const.STAGE_DESCENT : "Descent",
+                const.STAGE_LANDING : "Landing",
+                const.STAGE_TAXIAFTERLAND : "Taxi",
+                const.STAGE_PARKING : "Parking",
+                const.STAGE_GOAROUND : "Go-Around",
+                const.STAGE_END : "End" }
+    
+    NO_GO_SCORE = 10000
+
+    def __init__(self, output = sys.stdout):
+        """Construct the logger."""
+        self._faults = {}
+        self._output = output
+
+    @staticmethod
+    def _getTimeStr(timestamp):
+        """Get the string representation of the given timestamp."""
+        return time.strftime("%H:%M:%S", time.gmtime(timestamp))
+
+    def reset(self):
+        """Reset the logger.
+
+        The faults logged so far will be cleared."""
+        self._faults.clear()
+                
+    def message(self, timestamp, msg):
+        """Put a simple textual message into the log with the given timestamp."""
+        timeStr = Logger._getTimeStr(timestamp)
+        print >> self._output, timeStr + ":", msg
+        print timeStr + ":", msg        
+
+    def untimedMessage(self, msg):
+        """Put an untimed message into the log."""
+        print >> self._output, msg
+        print msg        
+
+    def debug(self, msg):
+        """Log a debug message."""
+        print >> self._output, "[DEBUG]", msg
+        print "[DEBUG]", msg
+
+    def stage(self, timestamp, stage):
+        """Report a change in the flight stage."""
+        s = Logger._stages[stage] if stage in Logger._stages else "<Unknown>"
+        self.message(timestamp, "--- %s ---" % (s,))
+        if stage==const.STAGE_END:
+            self.untimedMessage("Rating: %.0f" % (self.getRating(),))
+        
+    def fault(self, faultID, timestamp, what, score):
+        """Report a fault.
+
+        faultID as a unique ID for the given kind of fault. If another fault of
+        this ID has been reported earlier, it will be reported again only if
+        the score is greater than last time. This ID can be, e.g. the checker
+        the report comes from."""
+        if faultID in self._faults:
+            if score<=self._faults[faultID]:
+                return
+        self._faults[faultID] = score
+        if score==Logger.NO_GO_SCORE:
+            self.message(timestamp, "%s (NO GO)" % (what))
+        else:
+            self.message(timestamp, "%s (%.1f)" % (what, score))
+
+    def noGo(self, faultID, timestamp, what):
+        """Report a No-Go fault."""
+        self.fault(faultID, timestamp, what, Logger.NO_GO_SCORE)
+
+    def getRating(self):
+        """Get the rating of the flight so far."""
+        totalScore = 100
+        for (id, score) in self._faults.iteritems():
+            if score==Logger.NO_GO_SCORE:
+                return -score
+            else:
+                totalScore -= score
+        return totalScore
+        
+#--------------------------------------------------------------------------------------
Index: src/mlx/logger.py
===================================================================
--- src/mlx/logger.py	(revision 31)
+++ 	(revision )
@@ -1,99 +1,0 @@
-# Module for the logging.
-
-#--------------------------------------------------------------------------------------
-
-import const
-
-import sys
-import time
-
-#--------------------------------------------------------------------------------------
-
-class Logger(object):
-    """The class with the interface to log the various events."""
-    # FIXME: shall we use const.stage2string() instead?
-    _stages = { const.STAGE_BOARDING : "Boarding",
-                const.STAGE_PUSHANDTAXI : "Pushback and Taxi",
-                const.STAGE_TAKEOFF : "Takeoff",
-                const.STAGE_RTO : "RTO",
-                const.STAGE_CLIMB : "Climb",
-                const.STAGE_CRUISE : "Cruise",
-                const.STAGE_DESCENT : "Descent",
-                const.STAGE_LANDING : "Landing",
-                const.STAGE_TAXIAFTERLAND : "Taxi",
-                const.STAGE_PARKING : "Parking",
-                const.STAGE_GOAROUND : "Go-Around",
-                const.STAGE_END : "End" }
-    
-    NO_GO_SCORE = 10000
-
-    def __init__(self, output = sys.stdout):
-        """Construct the logger."""
-        self._faults = {}
-        self._output = output
-
-    @staticmethod
-    def _getTimeStr(timestamp):
-        """Get the string representation of the given timestamp."""
-        return time.strftime("%H:%M:%S", time.gmtime(timestamp))
-
-    def reset(self):
-        """Reset the logger.
-
-        The faults logged so far will be cleared."""
-        self._faults.clear()
-                
-    def message(self, timestamp, msg):
-        """Put a simple textual message into the log with the given timestamp."""
-        timeStr = Logger._getTimeStr(timestamp)
-        print >> self._output, timeStr + ":", msg
-        print timeStr + ":", msg        
-
-    def untimedMessage(self, msg):
-        """Put an untimed message into the log."""
-        print >> self._output, msg
-        print msg        
-
-    def debug(self, msg):
-        """Log a debug message."""
-        print >> self._output, "[DEBUG]", msg
-        print "[DEBUG]", msg
-
-    def stage(self, timestamp, stage):
-        """Report a change in the flight stage."""
-        s = Logger._stages[stage] if stage in Logger._stages else "<Unknown>"
-        self.message(timestamp, "--- %s ---" % (s,))
-        if stage==const.STAGE_END:
-            self.untimedMessage("Rating: %.0f" % (self.getRating(),))
-        
-    def fault(self, faultID, timestamp, what, score):
-        """Report a fault.
-
-        faultID as a unique ID for the given kind of fault. If another fault of
-        this ID has been reported earlier, it will be reported again only if
-        the score is greater than last time. This ID can be, e.g. the checker
-        the report comes from."""
-        if faultID in self._faults:
-            if score<=self._faults[faultID]:
-                return
-        self._faults[faultID] = score
-        if score==Logger.NO_GO_SCORE:
-            self.message(timestamp, "%s (NO GO)" % (what))
-        else:
-            self.message(timestamp, "%s (%.1f)" % (what, score))
-
-    def noGo(self, faultID, timestamp, what):
-        """Report a No-Go fault."""
-        self.fault(faultID, timestamp, what, Logger.NO_GO_SCORE)
-
-    def getRating(self):
-        """Get the rating of the flight so far."""
-        totalScore = 100
-        for (id, score) in self._faults.iteritems():
-            if score==Logger.NO_GO_SCORE:
-                return -score
-            else:
-                totalScore -= score
-        return totalScore
-        
-#--------------------------------------------------------------------------------------
Index: src/mlx/mlx.py
===================================================================
--- src/mlx/mlx.py	(revision 40)
+++ src/mlx/mlx.py	(revision 40)
@@ -0,0 +1,61 @@
+# The main program
+
+from .gui.gui import GUI
+
+from config import Config
+
+import os
+import sys
+
+if os.name=="nt":
+    import win32api
+
+#--------------------------------------------------------------------------------------
+
+class StdIOHandler(object):
+    """Handler for the standard I/O messages."""
+    def __init__(self, gui):
+        """Construct the handler."""
+        self._gui = gui
+
+    def write(self, text):
+        """Write the given text into the log."""
+        self._gui.writeStdIO(text)
+
+#--------------------------------------------------------------------------------------
+
+def main():
+    """The main operation of the program."""
+    programDirectory = os.path.dirname(sys.argv[0])
+
+    config = Config()
+    config.load()
+    
+    gui = GUI(programDirectory, config)
+    
+    sys.stdout = StdIOHandler(gui)
+    sys.stderr = StdIOHandler(gui)
+
+    try:
+        gui.build(programDirectory)
+        
+        gui.run()
+    finally:
+        gui.flushStdIO()
+        sys.stdout = sys.__stdout__
+        sys.stderr = sys.__stderr__
+
+    config.save()
+
+    if gui.toRestart:
+        programPath = os.path.join(os.path.dirname(sys.argv[0]),
+                                   "runmlx.exe" if os.name=="nt" else "runmlx.sh")
+        if os.name=="nt":
+            programPath = win32api.GetShortPathName(programPath)
+
+        os.execl(programPath, programPath)
+
+#--------------------------------------------------------------------------------------
+
+if __name__ == "__main__":
+    main()
Index: src/mlx/mlx.py
===================================================================
--- src/mlx/mlx.py	(revision 40)
+++ 	(revision )
@@ -1,61 +1,0 @@
-# The main program
-
-from .gui.gui import GUI
-
-from config import Config
-
-import os
-import sys
-
-if os.name=="nt":
-    import win32api
-
-#--------------------------------------------------------------------------------------
-
-class StdIOHandler(object):
-    """Handler for the standard I/O messages."""
-    def __init__(self, gui):
-        """Construct the handler."""
-        self._gui = gui
-
-    def write(self, text):
-        """Write the given text into the log."""
-        self._gui.writeStdIO(text)
-
-#--------------------------------------------------------------------------------------
-
-def main():
-    """The main operation of the program."""
-    programDirectory = os.path.dirname(sys.argv[0])
-
-    config = Config()
-    config.load()
-    
-    gui = GUI(programDirectory, config)
-    
-    sys.stdout = StdIOHandler(gui)
-    sys.stderr = StdIOHandler(gui)
-
-    try:
-        gui.build(programDirectory)
-        
-        gui.run()
-    finally:
-        gui.flushStdIO()
-        sys.stdout = sys.__stdout__
-        sys.stderr = sys.__stderr__
-
-    config.save()
-
-    if gui.toRestart:
-        programPath = os.path.join(os.path.dirname(sys.argv[0]),
-                                   "runmlx.exe" if os.name=="nt" else "runmlx.sh")
-        if os.name=="nt":
-            programPath = win32api.GetShortPathName(programPath)
-
-        os.execl(programPath, programPath)
-
-#--------------------------------------------------------------------------------------
-
-if __name__ == "__main__":
-    main()
Index: src/mlx/pyuipc_emu.py
===================================================================
--- src/mlx/pyuipc_emu.py	(revision 27)
+++ src/mlx/pyuipc_emu.py	(revision 27)
@@ -0,0 +1,280 @@
+# Emulator for the pyuipc module
+#------------------------------------------------------------------------------
+
+import time
+
+#------------------------------------------------------------------------------
+
+# The ratio between kg and lbs
+KGTOLBS=1/0.4536
+
+# Feet to metres
+FEETTOMETRES=0.3048
+
+#------------------------------------------------------------------------------
+
+def interpolate_proportion(proportion, value0, value1):
+    """
+    Interpolate between value0 and value1 using the given proportion.
+    """
+    return value0 + (value1-value0)*1.0*proportion
+
+#------------------------------------------------------------------------------
+
+def interpolate(point0, point, point1, value0, value1):
+    """
+    Interpolate linearly between the given points for the given
+    values.
+    """
+    if point0==point1:
+        if point>=point1: return value1
+        else: return value0
+    else:
+        return interpolate_proportion((point-point0)*1.0/(point1-point0),
+                                      value0, value1)
+
+#------------------------------------------------------------------------------
+
+# Version constants
+SIM_ANY=0
+SIM_FS98=1
+SIM_FS2K=2
+SIM_CFS2=3
+SIM_CFS1=4
+SIM_FLY=5
+SIM_FS2K2=6
+SIM_FS2K4=7
+
+#------------------------------------------------------------------------------
+
+# Error constants
+ERR_OK=0
+ERR_OPEN=1
+ERR_NOFS=2
+ERR_REGMSG=3
+ERR_ATOM=4
+ERR_MAP=5
+ERR_VIEW=6
+ERR_VERSION=7
+ERR_WRONGFS=8
+ERR_NOTOPEN=9
+ERR_NODATA=10
+ERR_TIMEOUT=11
+ERR_SENDMSG=12
+ERR_DATA=13
+ERR_RUNNING=14
+ERR_SIZE=15
+
+#------------------------------------------------------------------------------
+
+# The version of FSUIPC
+fsuipc_version=0x0401
+lib_version=0x0302
+fs_version=SIM_FS2K4
+
+#------------------------------------------------------------------------------
+
+class FSUIPCException(Exception):
+    """
+    FSUIPC exception class. It contains a member variable named
+    errorCode. The string is a text describing the error.
+    """
+
+    errors=["OK",
+            "Attempt to Open when already Open",
+            "Cannot link to FSUIPC or WideClient",
+            "Failed to Register common message with Windows",
+            "Failed to create Atom for mapping filename",
+            "Failed to create a file mapping object",
+            "Failed to open a view to the file map",
+            "Incorrect version of FSUIPC, or not FSUIPC",
+            "Sim is not version requested",
+            "Call cannot execute, link not Open",
+            "Call cannot execute: no requests accumulated",
+            "IPC timed out all retries",
+            "IPC sendmessage failed all retries",
+            "IPC request contains bad data",
+            "Maybe running on WideClient, but FS not running on Server, or wrong FSUIPC",
+            "Read or Write request cannot be added, memory for Process is full"]
+
+    def __init__(self, errorCode):
+        """
+        Construct the exception
+        """
+        if errorCode<len(self.errors):
+            self.errorString =  self.errors[errorCode]
+        else:
+            self.errorString = "Unknown error"
+        Exception.__init__(self, self.errorString)
+        self.errorCode = errorCode
+
+    def __str__(self):
+        """
+        Convert the excption to string
+        """
+        return "FSUIPC error: %d (%s)" % (self.errorCode, self.errorString)
+
+#------------------------------------------------------------------------------
+
+open_count=0
+read_count=0
+
+#------------------------------------------------------------------------------
+
+def open(request):
+    """
+    Open the connection.
+    """
+
+    global open_count, read_count
+    
+    open_count += 1
+    if open_count<5:
+        raise FSUIPCException(open_count+2)
+    elif open_count==5:
+        return True
+    else:
+        raise FSUIPCException(ERR_OPEN)
+
+#------------------------------------------------------------------------------
+
+def prepare_data(pattern, forRead = True):
+    """
+    Prepate the given pattern for reading and/or writing
+    """
+    return pattern
+
+#------------------------------------------------------------------------------
+
+flight=None
+schedule_points=None
+
+#------------------------------------------------------------------------------
+
+def set_flight(f):
+    """
+    Set the flight to use for providing the data
+    """
+
+    global flight, schedule_points, read_count
+
+    flight=f
+    schedule_points=flight.get_schedule_points()
+    read_count = 0
+
+#------------------------------------------------------------------------------
+
+fuel_weight=4.5
+centre_tank_capacity=12900
+side_tank_capacity=3900
+
+def read(data):
+    """
+    Read the given data.
+    """
+
+    global open_count, read_count
+    if open_count<5:
+        raise FSUIPCException(ERR_NOTOPEN)
+    
+    global flight, schedule_points
+
+    tm=time.gmtime()
+
+    if read_count<=10:
+        latitude = 0
+        longitude = 0
+        altitude = 0
+        tas = 0
+        vs = 0.0
+        fuel_flow = 0.0
+        fuel_remaining = 1000
+        ground_altitude = 0
+    else:
+        dist = read_count - 10
+
+        latitude = 0
+        longitude = 0
+        altitude = dist*20
+        tas = 100+dist*20
+        vs = dist*100.0
+        fuel_flow = 3.0
+        fuel_remaining = 1000 - dist*2
+
+        ground_altitude = 0
+        
+    results=[]
+    for (offset, type) in data:
+        if offset==0x02b8:    # True airspeed (128*knots)
+            results.append(int(tas*128))
+        elif offset==0x02c8:    # Vertical speed (256*m/s)
+            results.append(int(vs / 60.0 * 0.3048 * 256))
+        elif offset==0x0560:  # Latitude
+            results.append(long(latitude*10001750.0*65536.0*65536.0/90.0))
+        elif offset==0x0568:  # Longitude
+            results.append(long(longitude*65536.9*65536.0*65536.0*65536.0/360.0))
+        elif offset==0x0570:  # Aircraft altitude in metres (fractional part)
+            results.append(long( (altitude*0.3048*65536*65536)%(65536*65536)))
+        elif offset==0x0574:  # Aircraft altitude in metres (whole part)
+            results.append(int(altitude*.3048))
+        elif offset==0x0918 or \
+             offset==0x09b0:  # Engine 1 and 2 fuel flow (pounds per hour)
+            results.append(fuel_flow*KGTOLBS/2.0)
+        elif offset==0x0a48 or \
+             offset==0x0ae0:  # Engine 3 and 4 fuel flow (pounds per hour)
+            results.append(0.0)
+        elif offset==0x0aec:  # Number of engines
+            results.append(2)
+        elif offset==0x0af4:  # Fuel weight (pounds per gallon)
+            results.append(int(round(fuel_weight * 256)))
+        elif offset==0x0b74:  # Centre tank level (%*128*65536)
+            centre_fuel = fuel_remaining - 2*side_tank_capacity
+            if centre_fuel<0: centre_fuel = 0.0
+            results.append(long(round(centre_fuel/centre_tank_capacity*128.0*65536.0)))
+        elif offset==0x0b78:  # Centre tank capacity (gallons)
+            results.append(long(round(centre_tank_capacity*KGTOLBS/fuel_weight)))
+        elif offset==0x0b7c or \
+             offset==0x0b94:  # Left and right main tank level (%*128*65536)
+            fuel = fuel_remaining/2
+            if fuel>side_tank_capacity: fuel = side_tank_capacity
+            results.append(long(round(fuel/side_tank_capacity*128.0*65536.0)))
+        elif offset==0x0b80 or \
+             offset==0x0b98:  # Left and right main tank capacity (gallons)
+            results.append(long(round(side_tank_capacity*KGTOLBS/fuel_weight)))
+        elif offset in [0x0b84, 0x0b88, 0x0b8c, 0x0b90,
+                        0x0b9c, 0x0ba0, 0x0ba4, 0x0ba8,
+                        0x1244, 0x1248, 0x124c, 0x1250,
+                        0x1254, 0x1258, 0x125c, 0x1260]:
+                              # Other tank capacities and levels
+            results.append(long(0))
+        elif offset==0x023a:  # Second of time
+            results.append(int(tm[5]))
+        elif offset==0x023b:  # Hour of UTC time
+            results.append(int(tm[3]))
+        elif offset==0x023c:  # Minute of UTC time
+            results.append(int(tm[4]))
+        elif offset==0x023e:  # Day number in year
+            results.append(int(tm[7]))
+        elif offset==0x0240:  # Year
+            results.append(int(tm[0]))
+        elif offset==0x0020:  # Ground altitude (metres*256)
+            results.append(int(round(ground_altitude*FEETTOMETRES*256.0)))
+        else:
+            raise FSUIPCException(ERR_DATA)
+
+    read_count += 1
+
+    return results
+                
+
+#------------------------------------------------------------------------------
+
+def close():
+    """
+    Close the connection
+    """
+    global open_count
+    open_count = 0
+
+#------------------------------------------------------------------------------
+
Index: src/mlx/pyuipc_emu.py
===================================================================
--- src/mlx/pyuipc_emu.py	(revision 27)
+++ 	(revision )
@@ -1,280 +1,0 @@
-# Emulator for the pyuipc module
-#------------------------------------------------------------------------------
-
-import time
-
-#------------------------------------------------------------------------------
-
-# The ratio between kg and lbs
-KGTOLBS=1/0.4536
-
-# Feet to metres
-FEETTOMETRES=0.3048
-
-#------------------------------------------------------------------------------
-
-def interpolate_proportion(proportion, value0, value1):
-    """
-    Interpolate between value0 and value1 using the given proportion.
-    """
-    return value0 + (value1-value0)*1.0*proportion
-
-#------------------------------------------------------------------------------
-
-def interpolate(point0, point, point1, value0, value1):
-    """
-    Interpolate linearly between the given points for the given
-    values.
-    """
-    if point0==point1:
-        if point>=point1: return value1
-        else: return value0
-    else:
-        return interpolate_proportion((point-point0)*1.0/(point1-point0),
-                                      value0, value1)
-
-#------------------------------------------------------------------------------
-
-# Version constants
-SIM_ANY=0
-SIM_FS98=1
-SIM_FS2K=2
-SIM_CFS2=3
-SIM_CFS1=4
-SIM_FLY=5
-SIM_FS2K2=6
-SIM_FS2K4=7
-
-#------------------------------------------------------------------------------
-
-# Error constants
-ERR_OK=0
-ERR_OPEN=1
-ERR_NOFS=2
-ERR_REGMSG=3
-ERR_ATOM=4
-ERR_MAP=5
-ERR_VIEW=6
-ERR_VERSION=7
-ERR_WRONGFS=8
-ERR_NOTOPEN=9
-ERR_NODATA=10
-ERR_TIMEOUT=11
-ERR_SENDMSG=12
-ERR_DATA=13
-ERR_RUNNING=14
-ERR_SIZE=15
-
-#------------------------------------------------------------------------------
-
-# The version of FSUIPC
-fsuipc_version=0x0401
-lib_version=0x0302
-fs_version=SIM_FS2K4
-
-#------------------------------------------------------------------------------
-
-class FSUIPCException(Exception):
-    """
-    FSUIPC exception class. It contains a member variable named
-    errorCode. The string is a text describing the error.
-    """
-
-    errors=["OK",
-            "Attempt to Open when already Open",
-            "Cannot link to FSUIPC or WideClient",
-            "Failed to Register common message with Windows",
-            "Failed to create Atom for mapping filename",
-            "Failed to create a file mapping object",
-            "Failed to open a view to the file map",
-            "Incorrect version of FSUIPC, or not FSUIPC",
-            "Sim is not version requested",
-            "Call cannot execute, link not Open",
-            "Call cannot execute: no requests accumulated",
-            "IPC timed out all retries",
-            "IPC sendmessage failed all retries",
-            "IPC request contains bad data",
-            "Maybe running on WideClient, but FS not running on Server, or wrong FSUIPC",
-            "Read or Write request cannot be added, memory for Process is full"]
-
-    def __init__(self, errorCode):
-        """
-        Construct the exception
-        """
-        if errorCode<len(self.errors):
-            self.errorString =  self.errors[errorCode]
-        else:
-            self.errorString = "Unknown error"
-        Exception.__init__(self, self.errorString)
-        self.errorCode = errorCode
-
-    def __str__(self):
-        """
-        Convert the excption to string
-        """
-        return "FSUIPC error: %d (%s)" % (self.errorCode, self.errorString)
-
-#------------------------------------------------------------------------------
-
-open_count=0
-read_count=0
-
-#------------------------------------------------------------------------------
-
-def open(request):
-    """
-    Open the connection.
-    """
-
-    global open_count, read_count
-    
-    open_count += 1
-    if open_count<5:
-        raise FSUIPCException(open_count+2)
-    elif open_count==5:
-        return True
-    else:
-        raise FSUIPCException(ERR_OPEN)
-
-#------------------------------------------------------------------------------
-
-def prepare_data(pattern, forRead = True):
-    """
-    Prepate the given pattern for reading and/or writing
-    """
-    return pattern
-
-#------------------------------------------------------------------------------
-
-flight=None
-schedule_points=None
-
-#------------------------------------------------------------------------------
-
-def set_flight(f):
-    """
-    Set the flight to use for providing the data
-    """
-
-    global flight, schedule_points, read_count
-
-    flight=f
-    schedule_points=flight.get_schedule_points()
-    read_count = 0
-
-#------------------------------------------------------------------------------
-
-fuel_weight=4.5
-centre_tank_capacity=12900
-side_tank_capacity=3900
-
-def read(data):
-    """
-    Read the given data.
-    """
-
-    global open_count, read_count
-    if open_count<5:
-        raise FSUIPCException(ERR_NOTOPEN)
-    
-    global flight, schedule_points
-
-    tm=time.gmtime()
-
-    if read_count<=10:
-        latitude = 0
-        longitude = 0
-        altitude = 0
-        tas = 0
-        vs = 0.0
-        fuel_flow = 0.0
-        fuel_remaining = 1000
-        ground_altitude = 0
-    else:
-        dist = read_count - 10
-
-        latitude = 0
-        longitude = 0
-        altitude = dist*20
-        tas = 100+dist*20
-        vs = dist*100.0
-        fuel_flow = 3.0
-        fuel_remaining = 1000 - dist*2
-
-        ground_altitude = 0
-        
-    results=[]
-    for (offset, type) in data:
-        if offset==0x02b8:    # True airspeed (128*knots)
-            results.append(int(tas*128))
-        elif offset==0x02c8:    # Vertical speed (256*m/s)
-            results.append(int(vs / 60.0 * 0.3048 * 256))
-        elif offset==0x0560:  # Latitude
-            results.append(long(latitude*10001750.0*65536.0*65536.0/90.0))
-        elif offset==0x0568:  # Longitude
-            results.append(long(longitude*65536.9*65536.0*65536.0*65536.0/360.0))
-        elif offset==0x0570:  # Aircraft altitude in metres (fractional part)
-            results.append(long( (altitude*0.3048*65536*65536)%(65536*65536)))
-        elif offset==0x0574:  # Aircraft altitude in metres (whole part)
-            results.append(int(altitude*.3048))
-        elif offset==0x0918 or \
-             offset==0x09b0:  # Engine 1 and 2 fuel flow (pounds per hour)
-            results.append(fuel_flow*KGTOLBS/2.0)
-        elif offset==0x0a48 or \
-             offset==0x0ae0:  # Engine 3 and 4 fuel flow (pounds per hour)
-            results.append(0.0)
-        elif offset==0x0aec:  # Number of engines
-            results.append(2)
-        elif offset==0x0af4:  # Fuel weight (pounds per gallon)
-            results.append(int(round(fuel_weight * 256)))
-        elif offset==0x0b74:  # Centre tank level (%*128*65536)
-            centre_fuel = fuel_remaining - 2*side_tank_capacity
-            if centre_fuel<0: centre_fuel = 0.0
-            results.append(long(round(centre_fuel/centre_tank_capacity*128.0*65536.0)))
-        elif offset==0x0b78:  # Centre tank capacity (gallons)
-            results.append(long(round(centre_tank_capacity*KGTOLBS/fuel_weight)))
-        elif offset==0x0b7c or \
-             offset==0x0b94:  # Left and right main tank level (%*128*65536)
-            fuel = fuel_remaining/2
-            if fuel>side_tank_capacity: fuel = side_tank_capacity
-            results.append(long(round(fuel/side_tank_capacity*128.0*65536.0)))
-        elif offset==0x0b80 or \
-             offset==0x0b98:  # Left and right main tank capacity (gallons)
-            results.append(long(round(side_tank_capacity*KGTOLBS/fuel_weight)))
-        elif offset in [0x0b84, 0x0b88, 0x0b8c, 0x0b90,
-                        0x0b9c, 0x0ba0, 0x0ba4, 0x0ba8,
-                        0x1244, 0x1248, 0x124c, 0x1250,
-                        0x1254, 0x1258, 0x125c, 0x1260]:
-                              # Other tank capacities and levels
-            results.append(long(0))
-        elif offset==0x023a:  # Second of time
-            results.append(int(tm[5]))
-        elif offset==0x023b:  # Hour of UTC time
-            results.append(int(tm[3]))
-        elif offset==0x023c:  # Minute of UTC time
-            results.append(int(tm[4]))
-        elif offset==0x023e:  # Day number in year
-            results.append(int(tm[7]))
-        elif offset==0x0240:  # Year
-            results.append(int(tm[0]))
-        elif offset==0x0020:  # Ground altitude (metres*256)
-            results.append(int(round(ground_altitude*FEETTOMETRES*256.0)))
-        else:
-            raise FSUIPCException(ERR_DATA)
-
-    read_count += 1
-
-    return results
-                
-
-#------------------------------------------------------------------------------
-
-def close():
-    """
-    Close the connection
-    """
-    global open_count
-    open_count = 0
-
-#------------------------------------------------------------------------------
-
Index: src/mlx/pyuipc_sim.py
===================================================================
--- src/mlx/pyuipc_sim.py	(revision 61)
+++ src/mlx/pyuipc_sim.py	(revision 61)
@@ -0,0 +1,1194 @@
+# Simulator for the pyuipc module
+#------------------------------------------------------------------------------
+
+import const
+
+import cmd
+import threading
+import socket
+import time
+import calendar
+import sys
+import struct
+import cPickle
+import math
+
+#------------------------------------------------------------------------------
+
+# Version constants
+SIM_ANY=0
+SIM_FS98=1
+SIM_FS2K=2
+SIM_CFS2=3
+SIM_CFS1=4
+SIM_FLY=5
+SIM_FS2K2=6
+SIM_FS2K4=7
+
+#------------------------------------------------------------------------------
+
+# Error constants
+ERR_OK=0
+ERR_OPEN=1
+ERR_NOFS=2
+ERR_REGMSG=3
+ERR_ATOM=4
+ERR_MAP=5
+ERR_VIEW=6
+ERR_VERSION=7
+ERR_WRONGFS=8
+ERR_NOTOPEN=9
+ERR_NODATA=10
+ERR_TIMEOUT=11
+ERR_SENDMSG=12
+ERR_DATA=13
+ERR_RUNNING=14
+ERR_SIZE=15
+
+#------------------------------------------------------------------------------
+
+# The version of FSUIPC
+fsuipc_version=0x0401
+lib_version=0x0302
+fs_version=SIM_FS2K4
+
+#------------------------------------------------------------------------------
+
+class FSUIPCException(Exception):
+    """FSUIPC exception class.
+
+    It contains a member variable named errorCode. The string is a text
+    describing the error."""
+
+    errors=["OK",
+            "Attempt to Open when already Open",
+            "Cannot link to FSUIPC or WideClient",
+            "Failed to Register common message with Windows",
+            "Failed to create Atom for mapping filename",
+            "Failed to create a file mapping object",
+            "Failed to open a view to the file map",
+            "Incorrect version of FSUIPC, or not FSUIPC",
+            "Sim is not version requested",
+            "Call cannot execute, link not Open",
+            "Call cannot execute: no requests accumulated",
+            "IPC timed out all retries",
+            "IPC sendmessage failed all retries",
+            "IPC request contains bad data",
+            "Maybe running on WideClient, but FS not running on Server, or wrong FSUIPC",
+            "Read or Write request cannot be added, memory for Process is full"]
+
+    def __init__(self, errorCode):
+        """
+        Construct the exception
+        """
+        if errorCode<len(self.errors):
+            self.errorString =  self.errors[errorCode]
+        else:
+            self.errorString = "Unknown error"
+        Exception.__init__(self, self.errorString)
+        self.errorCode = errorCode
+
+    def __str__(self):
+        """
+        Convert the excption to string
+        """
+        return "FSUIPC error: %d (%s)" % (self.errorCode, self.errorString)
+
+#------------------------------------------------------------------------------
+
+class Values(object):
+    """The values that can be read from 'FSUIPC'."""
+    # Fuel data index: centre tank
+    FUEL_CENTRE = 0
+
+    # Fuel data index: left main tank
+    FUEL_LEFT = 1
+
+    # Fuel data index: right main tank
+    FUEL_RIGHT = 2
+
+    # Fuel data index: left aux tank
+    FUEL_LEFT_AUX = 3
+
+    # Fuel data index: right aux tank
+    FUEL_RIGHT_AUX = 4
+
+    # Fuel data index: left tip tank
+    FUEL_LEFT_TIP = 5
+
+    # Fuel data index: right tip tank
+    FUEL_RIGHT_AUX = 6
+
+    # Fuel data index: external 1 tank
+    FUEL_EXTERNAL_1 = 7
+
+    # Fuel data index: external 2 tank
+    FUEL_EXTERNAL_2 = 8
+
+    # Fuel data index: centre 2 tank
+    FUEL_CENTRE_2 = 9
+
+    # The number of fuel tank entries
+    NUM_FUEL = FUEL_CENTRE_2 + 1
+
+    # Engine index: engine #1
+    ENGINE_1 = 0
+
+    # Engine index: engine #2
+    ENGINE_2 = 1
+
+    # Engine index: engine #3
+    ENGINE_3 = 2
+
+    @staticmethod
+    def _readFrequency(frequency):
+        """Convert the given frequency into BCD."""
+        return Values._readBCD(int(frequency*100.0))
+
+    @staticmethod
+    def _readBCD(value):
+        """Convert the given value into BCD format."""
+        bcd = (value/1000) % 10
+        bcd <<= 4
+        bcd |= (value/100) % 10
+        bcd <<= 4
+        bcd |= (value/10) % 10
+        bcd <<= 4
+        bcd |= value % 10
+        return bcd
+        
+    @staticmethod
+    def _writeFrequency(value):
+        """Convert the given value into a frequency."""
+        return (Values._writeBCD(value) + 10000) / 100.0
+
+    @staticmethod
+    def _writeBCD(value):
+        """Convert the given BCD value into a real value."""
+        bcd = (value>>12) & 0x0f
+        bcd *= 10
+        bcd += (value>>8) & 0x0f
+        bcd *= 10
+        bcd += (value>>4) & 0x0f
+        bcd *= 10
+        bcd += (value>>0) & 0x0f
+
+        return bcd
+        
+    def __init__(self):
+        """Construct the values with defaults."""
+        self._timeOffset = 0
+        self.airPath = "C:\\Program Files\\Microsoft Games\\" \
+                       "FS9\\Aircraft\\Cessna\\cessna172.air"
+        self.aircraftName = "Cessna 172SP"
+        self.flapsNotches = [0, 1, 2, 5, 10, 15, 25, 30, 40]
+        self.fuelCapacities = [10000.0, 5000.0, 5000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
+        
+        self.paused = False
+        self.frozen = False
+        self.replay = False
+        self.slew = False
+        self.overspeed = False
+        self.stalled = False
+        self.onTheGround = True
+        
+        self.zfw = 50000.0
+        
+        self.fuelWeights = [0.0, 3000.0, 3000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
+        # Wikipedia: "Jet fuel", Jet A-1 density at 15*C .804kg/l -> 6.7 pounds/gallon
+        self.fuelWeight = 6.70970518
+
+        self.heading = 220.0
+        self.pitch = 0.0
+        self.bank = 0.0
+
+        self.ias = 0.0
+        self.vs = 0.0
+
+        self.radioAltitude = None
+        self.altitude = 513.0
+
+        self.gLoad = 1.0
+
+        self.flapsControl = 0.0
+        self.flaps = 0.0
+
+        self.navLightsOn = True
+        self.antiCollisionLightsOn = False
+        self.landingLightsOn = False
+        self.strobeLightsOn = False
+
+        self.pitot = False
+        self.parking = True
+
+        self.noseGear = 1.0
+
+        self.spoilersArmed = False
+        self.spoilers = 0.0
+
+        self.altimeter = 1013.0
+
+        self.nav1 = 117.3
+        self.nav2 = 109.5
+        self.squawk = 2200
+
+        self.windSpeed = 8.0
+        self.windDirection = 300.0
+        
+        self.n1 = [0.0, 0.0, 0.0]
+        self.throttles = [0.0, 0.0, 0.0]
+
+    def read(self, offset):
+        """Read the value at the given offset."""
+        try:
+            return self._read(offset)
+        except Exception, e:
+            print "failed to read offset %04x: %s" % (offset, str(e))
+            raise FSUIPCException(ERR_DATA)
+
+    def _read(self, offset):
+        """Read the value at the given offset."""
+        if offset==0x023a:         # Second of time
+            return self._readUTC().tm_sec
+        elif offset==0x023b:       # Hour of Zulu time
+            return self._readUTC().tm_hour
+        elif offset==0x023c:       # Minute of Zulu time
+            return self._readUTC().tm_min
+        elif offset==0x023e:       # Day number on year
+            return self._readUTC().tm_yday
+        elif offset==0x0240:       # Year in FS
+            return self._readUTC().tm_year
+        elif offset==0x0264:       # Paused
+            return 1 if self.paused else 0
+        elif offset==0x029c:       # Pitot
+            return 1 if self.pitot else 0
+        elif offset==0x02b4:       # Ground speed
+            # FIXME: calculate TAS first, then from the heading and
+            # wind the GS
+            return int(self.ias * 65536.0 * 1852.0 / 3600.0)
+        elif offset==0x02b8:       # TAS
+            return int(self._getTAS() * 128.0)
+        elif offset==0x02bc:       # IAS
+            return int(self.ias * 128.0)
+        elif offset==0x02c8:       # VS
+            return int(self.vs * const.FEETTOMETRES * 256.0 / 60.0)
+        elif offset==0x0330:       # Altimeter
+            return int(self.altimeter * 16.0)
+        elif offset==0x0350:       # NAV1
+            return Values._readFrequency(self.nav1)
+        elif offset==0x0352:       # NAV2
+            return Values._readFrequency(self.nav2)
+        elif offset==0x0354:       # Squawk
+            return Values._readBCD(self.squawk)
+        elif offset==0x0366:       # On the ground
+            return 1 if self.onTheGround else 0
+        elif offset==0x036c:       # Stalled
+            return 1 if self.stalled else 0
+        elif offset==0x036d:       # Overspeed
+            return 1 if self.overspeed else 0
+        elif offset==0x0570:       # Altitude
+            return long(self.altitude * const.FEETTOMETRES * 65536.0 * 65536.0)
+        elif offset==0x0578:       # Pitch
+            return int(self.pitch * 65536.0 * 65536.0 / 360.0)
+        elif offset==0x057c:       # Bank
+            return int(self.bank * 65536.0 * 65536.0 / 360.0)
+        elif offset==0x0580:       # Heading
+            return int(self.heading * 65536.0 * 65536.0 / 360.0)
+        elif offset==0x05dc:       # Slew
+            return 1 if self.slew else 0
+        elif offset==0x0628:       # Replay
+            return 1 if self.replay else 0
+        elif offset==0x088c:       # Engine #1 throttle
+            return self._getThrottle(self.ENGINE_1)
+        elif offset==0x0924:       # Engine #2 throttle
+            return self._getThrottle(self.ENGINE_2)
+        elif offset==0x09bc:       # Engine #3 throttle
+            return self._getThrottle(self.ENGINE_3)
+        elif offset==0x0af4:       # Fuel weight
+            return int(self.fuelWeight * 256.0)
+        elif offset==0x0b74:       # Centre tank level
+            return self._getFuelLevel(self.FUEL_CENTRE)
+        elif offset==0x0b78:       # Centre tank capacity
+            return self._getFuelCapacity(self.FUEL_CENTRE)
+        elif offset==0x0b7c:       # Left tank level
+            return self._getFuelLevel(self.FUEL_LEFT)
+        elif offset==0x0b80:       # Left tank capacity
+            return self._getFuelCapacity(self.FUEL_LEFT)
+        elif offset==0x0b84:       # Left aux tank level
+            return self._getFuelLevel(self.FUEL_LEFT_AUX)
+        elif offset==0x0b88:       # Left aux tank capacity
+            return self._getFuelCapacity(self.FUEL_LEFT_AUX)
+        elif offset==0x0b8c:       # Left tip tank level
+            return self._getFuelLevel(self.FUEL_LEFT_TIP)
+        elif offset==0x0b90:       # Left tip tank capacity
+            return self._getFuelCapacity(self.FUEL_LEFT_TIP)
+        elif offset==0x0b94:       # Right aux tank level
+            return self._getFuelLevel(self.FUEL_RIGHT)
+        elif offset==0x0b98:       # Right aux tank capacity
+            return self._getFuelCapacity(self.FUEL_RIGHT)
+        elif offset==0x0b9c:       # Right tank level
+            return self._getFuelLevel(self.FUEL_RIGHT_AUX)
+        elif offset==0x0ba0:       # Right tank capacity
+            return self._getFuelCapacity(self.FUEL_RIGHT_AUX)
+        elif offset==0x0ba4:       # Right tip tank level
+            return self._getFuelLevel(self.FUEL_RIGHT_TIP)
+        elif offset==0x0ba8:       # Right tip tank capacity
+            return self._getFuelCapacity(self.FUEL_RIGHT_TIP)
+        elif offset==0x0bc8:       # Parking
+            return 1 if self.parking else 0
+        elif offset==0x0bcc:       # Spoilers armed
+            return 1 if self.spoilersArmed else 0
+        elif offset==0x0bd0:       # Spoilers
+            return 0 if self.spoilers == 0 \
+                else int(self.spoilers * (16383 - 4800) + 4800)
+        elif offset==0x0bdc:       # Flaps control
+            numNotchesM1 = len(self.flapsNotches) - 1
+            flapsIncrement = 16383.0 / numNotchesM1
+            index = 0
+            while index<numNotchesM1 and \
+                  self.flapsControl<self.flapsNotches[index]:
+                index += 1
+                
+            if index==numNotchesM1:
+                return 16383
+            else:
+                return int((self.flapsControl-self.flapsNotches[index]) * \
+                           flapsIncrement / \
+                           (self.flapsNotches[index+1] - self.flapsNotches[index]))
+        elif offset==0x0be0 or offset==0x0be4:    # Flaps left and  right
+            return self.flaps * 16383.0 / self.flapsNotches[-1]        
+        elif offset==0x0bec:       # Nose gear
+            return int(self.noseGear * 16383.0)
+        elif offset==0x0d0c:       # Lights
+            lights = 0
+            if self.navLightsOn: lights |= 0x01
+            if self.antiCollisionLightsOn: lights |= 0x02
+            if self.landingLightsOn: lights |= 0x04
+            if self.strobeLightsOn: lights |= 0x10
+            return lights
+        elif offset==0x0e90:       # Wind speed
+            return int(self.windSpeed)
+        elif offset==0x0e92:       # Wind direction
+            return int(self.windDirection * 65536.0 / 360.0)
+        elif offset==0x11ba:       # G-Load
+            return int(self.gLoad * 625.0)
+        elif offset==0x11c6:       # Mach
+            # FIXME: calculate from IAS, altitude and QNH
+            return int(self.ias * 0.05 * 20480.)
+        elif offset==0x1244:       # Centre 2 tank level
+            return self._getFuelLevel(self.FUEL_CENTRE_2)
+        elif offset==0x1248:       # Centre 2 tank capacity
+            return self._getFuelCapacity(self.FUEL_CENTRE_2)
+        elif offset==0x1254:       # External 1 tank level
+            return self._getFuelLevel(self.FUEL_EXTERNAL_1)
+        elif offset==0x1258:       # External 1 tank capacity
+            return self._getFuelCapacity(self.FUEL_EXTERNAL_1)
+        elif offset==0x125c:       # External 2 tank level
+            return self._getFuelLevel(self.FUEL_EXTERNAL_2)
+        elif offset==0x1260:       # External 2 tank capacity
+            return self._getFuelCapacity(self.FUEL_EXTERNAL_2)
+        elif offset==0x2000:       # Engine #1 N1
+            return self.n1[self.ENGINE_1]
+        elif offset==0x2100:       # Engine #2 N1
+            return self.n1[self.ENGINE_2]
+        elif offset==0x2200:       # Engine #3 N1
+            return self.n1[self.ENGINE_3]
+        elif offset==0x30c0:       # Gross weight
+            return (self.zfw + sum(self.fuelWeights)) * const.KGSTOLB
+        elif offset==0x31e4:       # Radio altitude
+            # FIXME: if self.radioAltitude is None, calculate from the 
+            # altitude with some, perhaps random, ground altitude
+            # value
+            radioAltitude = (self.altitude - 517) \
+                if self.radioAltitude is None else self.radioAltitude
+            return (radioAltitude * const.FEETTOMETRES * 65536.0)
+        elif offset==0x3364:       # Frozen
+            return 1 if self.frozen else 0
+        elif offset==0x3bfc:       # ZFW
+            return int(self.zfw * 256.0 * const.KGSTOLB)
+        elif offset==0x3c00:       # Path of the current AIR file
+            return self.airPath
+        elif offset==0x3d00:       # Name of the current aircraft
+            return self.aircraftName
+        else:
+            print "Unhandled offset: %04x" % (offset,)
+            raise FSUIPCException(ERR_DATA)
+
+    def write(self, offset, value):
+        """Write the value at the given offset."""
+        try:
+            return self._write(offset, value)
+        except Exception, e:
+            print "failed to write offset %04x: %s" % (offset, str(e))
+            raise FSUIPCException(ERR_DATA)
+
+    def _write(self, offset, value):
+        """Write the given value at the given offset."""
+        if offset==0x023a:         # Second of time
+            self._updateTimeOffset(5, value)
+        elif offset==0x023b:       # Hour of Zulu time
+            self._updateTimeOffset(3, value)
+        elif offset==0x023c:       # Minute of Zulu time
+            self._updateTimeOffset(4, value)
+        elif offset==0x023e:       # Day number in the year
+            self._updateTimeOffset(7, value)
+        elif offset==0x0240:       # Year in FS
+            self._updateTimeOffset(0, value)
+        elif offset==0x0264:       # Paused
+            self.paused = value!=0
+        elif offset==0x029c:       # Pitot
+            self.pitot = value!=0
+        elif offset==0x02b4:       # Ground speed
+            # FIXME: calculate TAS using the heading and the wind, and
+            # then IAS based on the altitude
+            self.ias = value * 3600.0 / 65536.0 / 1852.0
+        elif offset==0x02bc:       # IAS
+            self.ias = value / 128.0
+        elif offset==0x02c8:       # VS
+            self.vs = value * 60.0 / const.FEETTOMETRES / 256.0
+        elif offset==0x0330:       # Altimeter
+            self.altimeter = value / 16.0
+        elif offset==0x0350:       # NAV1
+            self.nav1 = Values._writeFrequency(value)
+        elif offset==0x0352:       # NAV2
+            self.nav2 = Values._writeFrequency(value)
+        elif offset==0x0354:       # Squawk
+            self.squawk = Values._writeBCD(value)
+        elif offset==0x0366:       # Stalled
+            self.onTheGround = value!=0
+        elif offset==0x036c:       # Stalled
+            self.stalled = value!=0
+        elif offset==0x036d:       # Overspeed
+            self.overspeed = value!=0
+        elif offset==0x0570:       # Altitude
+            self.altitude = value / const.FEETTOMETRES / 65536.0 / 65536.0
+        elif offset==0x0578:       # Pitch
+            self.pitch = value * 360.0 / 65536.0 / 65536.0
+        elif offset==0x057c:       # Bank
+            self.bank = value * 360.0 / 65536.0 / 65536.0
+        elif offset==0x0580:       # Heading
+            self.heading = value * 360.0 / 65536.0 / 65536.0
+        elif offset==0x05dc:       # Slew
+            self.slew = value!=0
+        elif offset==0x0628:       # Replay
+            self.replay = value!=0
+        elif offset==0x088c:       # Engine #1 throttle
+            self._setThrottle(self.ENGINE_1, value)
+        elif offset==0x0924:       # Engine #2 throttle
+            self._setThrottle(self.ENGINE_2, value)
+        elif offset==0x09bc:       # Engine #3 throttle
+            self._setThrottle(self.ENGINE_3, value)
+        elif offset==0x0af4:       # Fuel weight
+            self.fuelWeight = value / 256.0
+        elif offset==0x0b74:       # Centre tank level
+            self._setFuelLevel(self.FUEL_CENTRE, value)
+        elif offset==0x0b78:       # Centre tank capacity
+            self._setFuelCapacity(self.FUEL_CENTRE, value)
+        elif offset==0x0b7c:       # Left tank level
+            self._setFuelLevel(self.FUEL_LEFT, value)
+        elif offset==0x0b80:       # Left tank capacity
+            self._setFuelCapacity(self.FUEL_LEFT, value)
+        elif offset==0x0b84:       # Left aux tank level
+            self._setFuelLevel(self.FUEL_LEFT_AUX, value)
+        elif offset==0x0b88:       # Left aux tank capacity
+            self._setFuelCapacity(self.FUEL_LEFT_AUX, value)
+        elif offset==0x0b8c:       # Left tip tank level
+            self._setFuelLevel(self.FUEL_LEFT_TIP, value)
+        elif offset==0x0b90:       # Left tip tank capacity
+            self._setFuelCapacity(self.FUEL_LEFT_TIP, value)
+        elif offset==0x0b94:       # Right aux tank level
+            self._setFuelLevel(self.FUEL_RIGHT, value)
+        elif offset==0x0b98:       # Right aux tank capacity
+            self._setFuelCapacity(self.FUEL_RIGHT, value)
+        elif offset==0x0b9c:       # Right tank level
+            self._setFuelLevel(self.FUEL_RIGHT_AUX, value)
+        elif offset==0x0ba0:       # Right tank capacity
+            self._setFuelCapacity(self.FUEL_RIGHT_AUX, value)
+        elif offset==0x0ba4:       # Right tip tank level
+            self._setFuelLevel(self.FUEL_RIGHT_TIP, value)
+        elif offset==0x0ba8:       # Right tip tank capacity
+            self._setFuelCapacity(self.FUEL_RIGHT_TIP, value)
+        elif offset==0x0bc8:       # Parking
+            self.parking = value!=0
+        elif offset==0x0bcc:       # Spoilers armed
+            self.spoilersArmed = value!=0
+        elif offset==0x0bd0:       # Spoilers
+            self.spoilters = 0 if value==0 \
+                             else (value - 4800) / (16383 - 4800)
+        elif offset==0x0bdc:       # Flaps control
+            numNotchesM1 = len(self.flapsNotches) - 1
+            flapsIncrement = 16383.0 / numNotchesM1
+            index = value / flapsIncrement
+            if index>=numNotchesM1:
+                self.flapsControl = self.flapsNotches[-1]
+            else:
+                self.flapsControl = self.flapsNotches[index]
+                self.flapsControl += value * \
+                    (self.flapsNotches[index+1] - self.flapsNotches[index]) / \
+                    flapsIncrement
+        elif offset==0x0be0 or offset==0x0be4:    # Flaps left and  right
+            self.flaps = value * self.flapsNotches[-1] / 16383.0
+        elif offset==0x0bec:       # Nose gear
+            self.noseGear = value / 16383.0
+        elif offset==0x0d0c:       # Lights
+            self.navLightsOn = (lights&0x01)!=0
+            self.antiCollisionLightsOn = (lights&0x02)!=0
+            self.landingLightsOn = (lights&0x04)!=0 
+            self.strobeLightsOn = (lights&0x10)!=0 
+        elif offset==0x0e90:       # Wind speed
+            self.windSpeed = value
+        elif offset==0x0e92:       # Wind direction
+            self.windDirection = value * 360.0 / 65536.0
+        elif offset==0x11ba:       # G-Load
+            self.gLoad = value / 625.0
+        elif offset==0x11c6:       # Mach
+            # FIXME: calculate IAS using the altitude and QNH
+            self.ias = value / 0.05 / 20480
+        elif offset==0x1244:       # Centre 2 tank level
+            self._setFuelLevel(self.FUEL_CENTRE_2, value)
+        elif offset==0x1248:       # Centre 2 tank capacity
+            self._setFuelCapacity(self.FUEL_CENTRE_2, value)
+        elif offset==0x1254:       # External 1 tank level
+            self._setFuelLevel(self.FUEL_EXTERNAL_1, value)
+        elif offset==0x1258:       # External 1 tank capacity
+            self._setFuelCapacity(self.FUEL_EXTERNAL_1, value)
+        elif offset==0x125c:       # External 2 tank level
+            self._setFuelLevel(self.FUEL_EXTERNAL_2, value)
+        elif offset==0x1260:       # External 2 tank capacity
+            self._setFuelCapacity(self.FUEL_EXTERNAL_2, value)
+        elif offset==0x2000:       # Engine #1 N1
+            self.n1[self.ENGINE_1] = value
+        elif offset==0x2100:       # Engine #2 N1
+            self.n1[self.ENGINE_2] = value
+        elif offset==0x2200:       # Engine #3 N1
+            self.n1[self.ENGINE_3] = value
+        elif offset==0x30c0:       # Gross weight
+            raise FSUIPCException(ERR_DATA)
+        elif offset==0x31e4:       # Radio altitude
+            raise FSUIPCException(ERR_DATA)
+        elif offset==0x3364:       # Frozen
+            self.frozen = value!=0
+        elif offset==0x3bfc:       # ZFW
+            self.zfw = value * const.LBSTOKG / 256.0
+            print "ZFW:", self.zfw
+        elif offset==0x3c00:       # Path of the current AIR file
+            self.airPath = value
+        elif offset==0x3d00:       # Name of the current aircraft
+            self.aircraftName = value
+        else:
+            print "Unhandled offset: %04x" % (offset,)
+            raise FSUIPCException(ERR_DATA)
+        
+    def _readUTC(self):
+        """Read the UTC time.
+        
+        The current offset is added to it."""
+        return time.gmtime(time.time() + self._timeOffset)
+        
+    def _getFuelLevel(self, index):
+        """Get the fuel level for the fuel tank with the given
+        index."""
+        return 0 if self.fuelCapacities[index]==0.0 else \
+            int(self.fuelWeights[index] * 65536.0 * 128.0 / self.fuelCapacities[index])
+    
+    def _getFuelCapacity(self, index):
+        """Get the capacity of the fuel tank with the given index."""
+        return int(self.fuelCapacities[index] * const.KGSTOLB  / self.fuelWeight)
+
+    def _getThrottle(self, index):
+        """Get the throttle value for the given index."""
+        return int(self.throttles[index] * 16383.0)
+
+    def _updateTimeOffset(self, index, value):
+        """Update the time offset if the value in the tm structure is replaced
+        by the given value."""
+        tm = self._readUTC()
+        tm1 = tm[:index] + (value,) + tm[(index+1):]
+        self._timeOffset += calendar.timegm(tm1) - calendar.timegm(tm)
+    
+    def _setThrottle(self, index, value):
+        """Set the throttle value for the given index."""
+        self.throttles[index] = value / 16383.0
+
+    def _setFuelLevel(self, index, value):
+        """Set the fuel level for the fuel tank with the given index."""
+        self.fuelWeights[index] = self.fuelCapacities[index] * float(value) / \
+                                  65536.0 / 128.0
+    
+    def _setFuelCapacity(self, index, value):
+        """Set the capacity of the fuel tank with the given index."""
+        self.fuelCapacities[index] = value * self.fuelWeight * const.LBSTOKG
+
+    def _getTAS(self):
+        """Calculate the true airspeed."""
+        pressure = 101325 * math.pow(1 - 2.25577e-5 * self.altitude *
+                                      const.FEETTOMETRES,
+                                      5.25588)
+        temperature = 15 - self.altitude * 6.5 * const.FEETTOMETRES / 1000.0 
+        temperature += 273.15  # Celsius -> Kelvin
+        airDensity = pressure / (temperature * 287.05)
+        print "pressure:", pressure, "temperature:", temperature, "airDensity:", airDensity
+        return self.ias * math.sqrt(1.225 / airDensity)
+
+#------------------------------------------------------------------------------
+
+values = Values()
+
+#------------------------------------------------------------------------------
+
+failOpen = False
+
+opened = False
+
+#------------------------------------------------------------------------------
+
+def open(request):
+    """Open the connection."""
+    global opened
+    if failOpen:
+        raise FSUIPCException(ERR_NOFS)
+    elif opened:
+        raise FSUIPCException(ERR_OPEN)
+    else:
+        time.sleep(0.5)
+        opened = True
+        return True
+
+#------------------------------------------------------------------------------
+
+def prepare_data(pattern, forRead = True):
+    """Prepare the given pattern for reading and/or writing."""
+    if opened:
+        return pattern
+    else:
+        raise FSUIPCException(ERR_OPEN)
+        
+#------------------------------------------------------------------------------
+
+def read(data):
+    """Read the given data."""
+    if opened:
+        return [values.read(offset) for (offset, type) in data]
+    else:
+        raise FSUIPCException(ERR_OPEN)
+            
+#------------------------------------------------------------------------------
+
+def write(data):
+    """Write the given data."""
+    if opened:
+        for (offset, type, value) in data:
+            values.write(offset, value)
+    else:
+        raise FSUIPCException(ERR_OPEN)
+            
+#------------------------------------------------------------------------------
+
+def close():
+    """Close the connection."""
+    global opened
+    opened = False
+
+#------------------------------------------------------------------------------
+
+PORT=15015
+
+CALL_READ=1
+CALL_WRITE=2
+CALL_CLOSE=3
+CALL_FAILOPEN=4
+CALL_QUIT = 99
+
+RESULT_RETURNED=1
+RESULT_EXCEPTION=2
+
+#------------------------------------------------------------------------------
+
+class Server(threading.Thread):
+    """The server thread."""
+    def __init__(self):
+        """Construct the thread."""
+        super(Server, self).__init__()
+        self.daemon = True
+
+    def run(self):
+        """Perform the server's operation."""
+        serverSocket = socket.socket()
+
+        serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        serverSocket.bind(("", PORT))
+    
+        serverSocket.listen(5)
+    
+        while True:
+            (clientSocket, clientAddress) = serverSocket.accept()
+            thread = threading.Thread(target = self._process, args=(clientSocket,))
+            thread.start()
+
+    def _process(self, clientSocket):
+        """Process the commands arriving on the given socket."""
+        socketFile = clientSocket.makefile()
+        try:
+            while True:                
+                (length,) = struct.unpack("I", clientSocket.recv(4))
+                data = clientSocket.recv(length)
+                (call, args) = cPickle.loads(data)
+                exception = None
+                
+                try:
+                    if call==CALL_READ:
+                        result = read(args[0])
+                    elif call==CALL_WRITE:
+                        result = write(args[0])
+                    elif call==CALL_CLOSE:
+                        global opened
+                        opened = False
+                        result = None
+                    elif call==CALL_FAILOPEN:
+                        global failOpen
+                        failOpen = args[0]
+                        result = None
+                    else:
+                        break
+                except Exception, e:
+                    exception = e
+
+                if exception is None:
+                    data = cPickle.dumps((RESULT_RETURNED, result))
+                else:
+                    data = cPickle.dumps((RESULT_EXCEPTION, exception))
+                clientSocket.send(struct.pack("I", len(data)) + data)
+        except Exception, e:
+            print >> sys.stderr, "pyuipc_sim.Server._process: failed with exception:", str(e)
+        finally:
+            try:
+                socketFile.close()
+            except:
+                pass
+            clientSocket.close()
+                   
+#------------------------------------------------------------------------------
+
+class Client(object):
+    """Client to the server."""
+    def __init__(self, serverHost):
+        """Construct the client and connect to the given server
+        host."""
+        self._socket = socket.socket()
+        self._socket.connect((serverHost, PORT))
+
+        self._socketFile = self._socket.makefile()
+
+    def read(self, data):
+        """Read the given data."""
+        return self._call(CALL_READ, data)
+
+    def write(self, data):
+        """Write the given data."""
+        return self._call(CALL_WRITE, data)
+
+    def close(self):
+        """Close the connection currently opened in the simulator."""
+        return self._call(CALL_CLOSE, None)
+
+    def failOpen(self, really):
+        """Enable/disable open failure in the simulator."""
+        return self._call(CALL_FAILOPEN, really)
+
+    def quit(self):
+        """Quit from the simulator."""
+        data = cPickle.dumps((CALL_QUIT, None))
+        self._socket.send(struct.pack("I", len(data)) + data)        
+
+    def _call(self, command, data):
+        """Perform a call with the given command and data."""
+        data = cPickle.dumps((command, [data]))
+        self._socket.send(struct.pack("I", len(data)) + data)
+        (length,) = struct.unpack("I", self._socket.recv(4))
+        data = self._socket.recv(length)
+        (resultCode, result) = cPickle.loads(data)
+        if resultCode==RESULT_RETURNED:
+            return result
+        else:
+            raise result        
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+
+# FIXME: implement proper completion and history
+class CLI(cmd.Cmd):
+    """The command-line interpreter."""
+    @staticmethod
+    def str2bool(s):
+        """Convert the given string to a PyUIPC boolean value (i.e. 0 or 1)."""
+        return 1 if s in ["yes", "true", "on"] else 0
+    
+    @staticmethod
+    def bool2str(value):
+        """Convert the PyUIPC boolean value (i.e. 0 or 1) into a string."""
+        return "no" if value==0 else "yes"
+
+    @staticmethod
+    def degree2pyuipc(degree):
+        """Convert the given degree (as a string) into a PyUIPC value."""
+        return int(float(degree) * 65536.0 * 65536.0 / 360.0)
+        
+    @staticmethod
+    def pyuipc2degree(value):
+        """Convert the given PyUIPC value into a degree."""
+        return valie * 360.0 / 65536.0 / 65536.0
+
+    @staticmethod
+    def fuelLevel2pyuipc(level):
+        """Convert the given percentage value (as a string) into a PyUIPC value."""
+        return int(float(level) * 128.0 * 65536.0 / 100.0)
+        
+    @staticmethod
+    def pyuipc2fuelLevel(value):
+        """Convert the PyUIPC value into a percentage value."""
+        return value * 100.0 / 128.0 / 65536.0
+        
+    @staticmethod
+    def fuelCapacity2pyuipc(capacity):
+        """Convert the given capacity value (as a string) into a PyUIPC value."""
+        return int(capacity)
+        
+    @staticmethod
+    def pyuipc2fuelCapacity(value):
+        """Convert the given capacity value into a PyUIPC value."""
+        return value
+        
+    @staticmethod
+    def throttle2pyuipc(throttle):
+        """Convert the given throttle value (as a string) into a PyUIPC value."""
+        return int(float(throttle) * 16384.0 / 100.0)
+        
+    @staticmethod
+    def pyuipc2throttle(value):
+        """Convert the given PyUIPC value into a throttle value."""
+        return value * 100.0 / 16384.0
+        
+    def __init__(self):
+        """Construct the CLI."""
+        cmd.Cmd.__init__(self)
+        
+        self.use_rawinput = True
+        self.intro = "\nPyUIPC simulator command prompt\n"
+        self.prompt = "PyUIPC> "
+
+        self.daemon = True
+
+        self._client = Client("localhost")
+
+        self._valueHandlers = {}
+        self._valueHandlers["year"] = (0x0240, "H",  lambda value: value,
+                                      lambda word: int(word))
+        self._valueHandlers["yday"] = (0x023e, "H",  lambda value: value,
+                                       lambda word: int(word))
+        self._valueHandlers["hour"] = (0x023b, "b",  lambda value: value,
+                                       lambda word: int(word))
+        self._valueHandlers["min"] = (0x023c, "b",  lambda value: value,
+                                      lambda word: int(word))
+        self._valueHandlers["sec"] = (0x023a, "b",  lambda value: value,
+                                      lambda word: int(word))
+        self._valueHandlers["acftName"] = (0x3d00, -256,  lambda value: value,
+                                           lambda word: word)
+        self._valueHandlers["airPath"] = (0x3c00, -256,  lambda value: value,
+                                          lambda word: word)
+        self._valueHandlers["paused"] = (0x0264, "H", CLI.bool2str, CLI.str2bool)
+        self._valueHandlers["frozen"] = (0x3364, "H", CLI.bool2str, CLI.str2bool)
+        self._valueHandlers["replay"] = (0x0628, "d", CLI.bool2str, CLI.str2bool)
+        self._valueHandlers["slew"] = (0x05dc, "H", CLI.bool2str, CLI.str2bool)
+        self._valueHandlers["overspeed"] = (0x036d, "b", CLI.bool2str, CLI.str2bool)
+        self._valueHandlers["stalled"] = (0x036c, "b", CLI.bool2str, CLI.str2bool)
+        self._valueHandlers["onTheGround"] = (0x0366, "H", CLI.bool2str, CLI.str2bool)
+        self._valueHandlers["zfw"] = (0x3bfc, "d",
+                                      lambda value: value * const.LBSTOKG / 256.0,
+                                      lambda word: int(float(word) * 256.0 *
+                                                       const.KGSTOLB))
+        self._valueHandlers["grossWeight"] = (0x30c0, "f",
+                                              lambda value: value * const.LBSTOKG,
+                                              lambda word: None)
+        self._valueHandlers["heading"] = (0x0580, "d",
+                                          CLI.pyuipc2degree, CLI.degree2pyuipc)
+        self._valueHandlers["pitch"] = (0x0578, "d",
+                                        CLI.pyuipc2degree, CLI.degree2pyuipc)
+        self._valueHandlers["bank"] = (0x057c, "d",
+                                       CLI.pyuipc2degree, CLI.degree2pyuipc)
+        self._valueHandlers["ias"] = (0x02bc, "d",
+                                      lambda value: value / 128.0,
+                                      lambda word: int(float(word) * 128.0))
+        self._valueHandlers["mach"] = (0x11c6, "H",
+                                       lambda value: value / 20480.0,
+                                       lambda word: int(float(word) * 20480.0))
+        self._valueHandlers["gs"] = (0x02b4, "d",
+                                     lambda value: value * 3600.0 / 65536.0 / 1852.0,
+                                     lambda word: int(float(word) * 65536.0 *
+                                                      1852.0 / 3600))
+        self._valueHandlers["tas"] = (0x02b8, "d",
+                                     lambda value: value / 128.0,
+                                     lambda word: None)
+        self._valueHandlers["vs"] = (0x02c8, "d",
+                                     lambda value: value * 60 /                                     
+                                     const.FEETTOMETRES / 256.0,
+                                     lambda word: int(float(word) *
+                                                      const.FEETTOMETRES *
+                                                      256.0 / 60.0))
+        self._valueHandlers["radioAltitude"] = (0x31e4, "d",
+                                                lambda value: value /
+                                                const.FEETTOMETRES /
+                                                65536.0,
+                                                lambda word: int(float(word) *
+                                                                 const.FEETTOMETRES *
+                                                                 65536.0))
+        self._valueHandlers["altitude"] = (0x0570, "l",
+                                           lambda value: value /
+                                           const.FEETTOMETRES / 65536.0 /
+                                           65536.0,
+                                           lambda word: long(float(word) *
+                                                             const.FEETTOMETRES *
+                                                             65536.0 * 65536.0))
+        self._valueHandlers["gLoad"] = (0x11ba, "H",
+                                        lambda value: value / 625.0,
+                                        lambda word: int(float(word) * 625.0))
+
+        self._valueHandlers["flapsControl"] = (0x0bdc, "d",
+                                               lambda value: value * 100.0 / 16383.0,
+                                               lambda word: int(float(word) *
+                                                                16383.0 / 100.0))
+        self._valueHandlers["flaps"] = (0x0be0, "d",
+                                        lambda value: value * 100.0 / 16383.0,
+                                        lambda word: int(float(word) *
+                                                         16383.0 / 100.0))
+        self._valueHandlers["lights"] = (0x0d0c, "H",
+                                        lambda value: value,
+                                        lambda word: int(word))
+        self._valueHandlers["pitot"] = (0x029c, "b", CLI.bool2str, CLI.str2bool)
+        self._valueHandlers["parking"] = (0x0bc8, "H", CLI.bool2str, CLI.str2bool)
+        self._valueHandlers["noseGear"] = (0x0bec, "d",
+                                           lambda value: value * 100.0 / 16383.0,
+                                           lambda word: int(float(word) *
+                                                            16383.0 / 100.0))
+        self._valueHandlers["spoilersArmed"] = (0x0bcc, "d",
+                                                CLI.bool2str, CLI.str2bool)
+        self._valueHandlers["spoilers"] = (0x0bd0, "d",
+                                           lambda value: value,
+                                           lambda word: int(word))
+        self._valueHandlers["qnh"] = (0x0330, "H",
+                                      lambda value: value / 16.0,
+                                      lambda word: int(float(word)*16.0))
+        self._valueHandlers["nav1"] = (0x0350, "H",
+                                       Values._writeFrequency,
+                                       lambda word: Values._readFrequency(float(word)))
+        self._valueHandlers["nav2"] = (0x0352, "H",
+                                       Values._writeFrequency,
+                                       lambda word: Values._readFrequency(float(word)))
+        self._valueHandlers["squawk"] = (0x0354, "H",
+                                       Values._writeBCD,
+                                       lambda word: Values._readBCD(int(word)))
+        self._valueHandlers["windSpeed"] = (0x0e90, "H",
+                                            lambda value: value,
+                                            lambda word: int(word))
+        self._valueHandlers["windDirection"] = (0x0e92, "H",
+                                                lambda value: value * 360.0 / 65536.0,
+                                                lambda word: int(int(word) *
+                                                                 65536.0 / 360.0))
+        self._valueHandlers["fuelWeight"] = (0x0af4, "H",
+                                             lambda value: value / 256.0,
+                                             lambda word: int(float(word)*256.0))
+
+
+        self._valueHandlers["centreLevel"] = (0x0b74, "d", CLI.pyuipc2fuelLevel,
+                                              CLI.fuelLevel2pyuipc)
+        self._valueHandlers["centreCapacity"] = (0x0b78, "d",
+                                                 CLI.pyuipc2fuelCapacity,
+                                                 CLI.fuelCapacity2pyuipc)
+        self._valueHandlers["leftMainLevel"] = (0x0b7c, "d", CLI.pyuipc2fuelLevel,
+                                              CLI.fuelLevel2pyuipc)
+        self._valueHandlers["leftMainCapacity"] = (0x0b80, "d",
+                                                   CLI.pyuipc2fuelCapacity,
+                                                   CLI.fuelCapacity2pyuipc)
+        self._valueHandlers["leftAuxLevel"] = (0x0b84, "d", CLI.pyuipc2fuelLevel,
+                                               CLI.fuelLevel2pyuipc)
+        self._valueHandlers["leftAuxCapacity"] = (0x0b88, "d",
+                                                   CLI.pyuipc2fuelCapacity,
+                                                  CLI.fuelCapacity2pyuipc)
+        self._valueHandlers["leftTipLevel"] = (0x0b8c, "d", CLI.pyuipc2fuelLevel,
+                                              CLI.fuelLevel2pyuipc)
+        self._valueHandlers["leftTipCapacity"] = (0x0b90, "d",
+                                                  CLI.pyuipc2fuelCapacity,
+                                                  CLI.fuelCapacity2pyuipc)
+        self._valueHandlers["rightMainLevel"] = (0x0b94, "d", CLI.pyuipc2fuelLevel,
+                                                 CLI.fuelLevel2pyuipc)
+        self._valueHandlers["rightMainCapacity"] = (0x0b98, "d",
+                                                    CLI.pyuipc2fuelCapacity,
+                                                    CLI.fuelCapacity2pyuipc)
+        self._valueHandlers["rightAuxLevel"] = (0x0b9c, "d", CLI.pyuipc2fuelLevel,
+                                                CLI.fuelLevel2pyuipc)
+        self._valueHandlers["rightAuxCapacity"] = (0x0ba0, "d",
+                                                   CLI.pyuipc2fuelCapacity,
+                                                   CLI.fuelCapacity2pyuipc)
+        self._valueHandlers["rightTipLevel"] = (0x0ba4, "d", CLI.pyuipc2fuelLevel,
+                                                CLI.fuelLevel2pyuipc)
+        self._valueHandlers["rightTipCapacity"] = (0x0ba8, "d",
+                                                   CLI.pyuipc2fuelCapacity,
+                                                   CLI.fuelCapacity2pyuipc)
+        self._valueHandlers["centre2Level"] = (0x1244, "d", CLI.pyuipc2fuelLevel,
+                                               CLI.fuelLevel2pyuipc)
+        self._valueHandlers["centre2Capacity"] = (0x1248, "d",
+                                                  CLI.pyuipc2fuelCapacity,
+                                                  CLI.fuelCapacity2pyuipc)
+        self._valueHandlers["external1Level"] = (0x1254, "d", CLI.pyuipc2fuelLevel,
+                                                 CLI.fuelLevel2pyuipc)
+        self._valueHandlers["external1Capacity"] = (0x1258, "d",
+                                                    CLI.pyuipc2fuelCapacity,
+                                                    CLI.fuelCapacity2pyuipc)
+        self._valueHandlers["external2Level"] = (0x125c, "d", CLI.pyuipc2fuelLevel,
+                                                 CLI.fuelLevel2pyuipc)
+        self._valueHandlers["external2Capacity"] = (0x1260, "d",
+                                                    CLI.pyuipc2fuelCapacity,
+                                                    CLI.fuelCapacity2pyuipc)
+
+        self._valueHandlers["n1_1"] = (0x2000, "f", lambda value: value,
+                                       lambda word: float(word))
+        self._valueHandlers["n1_2"] = (0x2100, "f", lambda value: value,
+                                       lambda word: float(word))
+        self._valueHandlers["n1_3"] = (0x2200, "f", lambda value: value,
+                                       lambda word: float(word))
+
+        self._valueHandlers["throttle_1"] = (0x088c, "H",
+                                             CLI.pyuipc2throttle,
+                                             CLI.throttle2pyuipc)
+        self._valueHandlers["throttle_2"] = (0x0924, "H",
+                                             CLI.pyuipc2throttle,
+                                             CLI.throttle2pyuipc)
+        self._valueHandlers["throttle_3"] = (0x09bc, "H",
+                                             CLI.pyuipc2throttle,
+                                             CLI.throttle2pyuipc)
+                                                            
+    def default(self, line):
+        """Handle unhandle commands."""
+        if line=="EOF":
+            print
+            return self.do_quit("")
+        else:
+            return super(CLI, self).default(line)
+
+    def do_get(self, args):
+        """Handle the get command."""
+        names = args.split()
+        data = []        
+        for name in names:
+            if name not in self._valueHandlers:
+                print >> sys.stderr, "Unknown variable: " + name
+                return False
+            valueHandler = self._valueHandlers[name]
+            data.append((valueHandler[0], valueHandler[1]))
+
+        try:
+            result = self._client.read(data)
+            for i in range(0, len(result)):
+                name = names[i]
+                valueHandler = self._valueHandlers[name]
+                print name + "=" + str(valueHandler[2](result[i]))
+        except Exception, e:
+            print >> sys.stderr, "Failed to read data: " + str(e)                        
+
+        return False
+
+    def help_get(self):
+        """Print help for the get command."""
+        print "get <variable> [<variable>...]"
+
+    def complete_get(self, text, line, begidx, endidx):
+        """Try to complete the get command."""
+        return [key for key in self._valueHandlers if key.startswith(text)]
+
+    def do_set(self, args):
+        """Handle the set command."""
+        arguments = args.split()
+        names = []
+        data = []
+        for argument in arguments:
+            words = argument.split("=")
+            if len(words)!=2:
+                print >> sys.stderr, "Invalid argument: " + argument
+                return False
+
+            (name, value) = words
+            if name not in self._valueHandlers:
+                print >> sys.stderr, "Unknown variable: " + name
+                return False
+
+            valueHandler = self._valueHandlers[name]
+            try:
+                value = valueHandler[3](value)
+                data.append((valueHandler[0], valueHandler[1], value))
+            except Exception, e:
+                print >> sys.stderr, "Invalid value '%s' for variable %s: %s" % \
+                      (value, name, str(e))
+                return False
+                
+        try:
+            self._client.write(data)
+            print "Data written"
+        except Exception, e:
+            print >> sys.stderr, "Failed to write data: " + str(e)
+
+        return False
+
+    def help_set(self):
+        """Print help for the set command."""
+        print "set <variable>=<value> [<variable>=<value>...]"
+
+    def complete_set(self, text, line, begidx, endidx):
+        """Try to complete the set command."""
+        if not text and begidx>0 and line[begidx-1]=="=":
+            return []
+        else:
+            return [key + "=" for key in self._valueHandlers if key.startswith(text)]
+
+    def do_close(self, args):
+        """Close an existing connection so that FS will fail."""
+        try:
+            self._client.close()
+            print "Connection closed"
+        except Exception, e:
+            print >> sys.stderr, "Failed to close the connection: " + str(e)        
+        
+    def do_failopen(self, args):
+        """Enable/disable the failing of opens."""
+        try:
+            value = self.str2bool(args)
+            self._client.failOpen(value)
+            print "Opening will%s fail" % ("" if value else " not",)
+        except Exception, e:
+            print >> sys.stderr, "Failed to set open failure: " + str(e)        
+
+    def help_failopen(self, usage = False):
+        """Help for the failopen close"""
+        if usage: print "Usage:",
+        print "failopen yes|no"
+
+    def complete_failopen(self, text, line, begidx, endidx):
+        if text:
+            if "yes".startswith(text): return ["yes"]
+            elif "no".startswith(text): return ["no"]
+            else: return []
+        else:
+            return ["yes", "no"]
+        
+    def do_quit(self, args):
+        """Handle the quit command."""
+        self._client.quit()
+        return True
+
+#------------------------------------------------------------------------------
+
+if __name__ == "__main__":
+    CLI().cmdloop()
+else:
+    server = Server()
+    server.start()    
+
+#------------------------------------------------------------------------------
Index: src/mlx/pyuipc_sim.py
===================================================================
--- src/mlx/pyuipc_sim.py	(revision 61)
+++ 	(revision )
@@ -1,1194 +1,0 @@
-# Simulator for the pyuipc module
-#------------------------------------------------------------------------------
-
-import const
-
-import cmd
-import threading
-import socket
-import time
-import calendar
-import sys
-import struct
-import cPickle
-import math
-
-#------------------------------------------------------------------------------
-
-# Version constants
-SIM_ANY=0
-SIM_FS98=1
-SIM_FS2K=2
-SIM_CFS2=3
-SIM_CFS1=4
-SIM_FLY=5
-SIM_FS2K2=6
-SIM_FS2K4=7
-
-#------------------------------------------------------------------------------
-
-# Error constants
-ERR_OK=0
-ERR_OPEN=1
-ERR_NOFS=2
-ERR_REGMSG=3
-ERR_ATOM=4
-ERR_MAP=5
-ERR_VIEW=6
-ERR_VERSION=7
-ERR_WRONGFS=8
-ERR_NOTOPEN=9
-ERR_NODATA=10
-ERR_TIMEOUT=11
-ERR_SENDMSG=12
-ERR_DATA=13
-ERR_RUNNING=14
-ERR_SIZE=15
-
-#------------------------------------------------------------------------------
-
-# The version of FSUIPC
-fsuipc_version=0x0401
-lib_version=0x0302
-fs_version=SIM_FS2K4
-
-#------------------------------------------------------------------------------
-
-class FSUIPCException(Exception):
-    """FSUIPC exception class.
-
-    It contains a member variable named errorCode. The string is a text
-    describing the error."""
-
-    errors=["OK",
-            "Attempt to Open when already Open",
-            "Cannot link to FSUIPC or WideClient",
-            "Failed to Register common message with Windows",
-            "Failed to create Atom for mapping filename",
-            "Failed to create a file mapping object",
-            "Failed to open a view to the file map",
-            "Incorrect version of FSUIPC, or not FSUIPC",
-            "Sim is not version requested",
-            "Call cannot execute, link not Open",
-            "Call cannot execute: no requests accumulated",
-            "IPC timed out all retries",
-            "IPC sendmessage failed all retries",
-            "IPC request contains bad data",
-            "Maybe running on WideClient, but FS not running on Server, or wrong FSUIPC",
-            "Read or Write request cannot be added, memory for Process is full"]
-
-    def __init__(self, errorCode):
-        """
-        Construct the exception
-        """
-        if errorCode<len(self.errors):
-            self.errorString =  self.errors[errorCode]
-        else:
-            self.errorString = "Unknown error"
-        Exception.__init__(self, self.errorString)
-        self.errorCode = errorCode
-
-    def __str__(self):
-        """
-        Convert the excption to string
-        """
-        return "FSUIPC error: %d (%s)" % (self.errorCode, self.errorString)
-
-#------------------------------------------------------------------------------
-
-class Values(object):
-    """The values that can be read from 'FSUIPC'."""
-    # Fuel data index: centre tank
-    FUEL_CENTRE = 0
-
-    # Fuel data index: left main tank
-    FUEL_LEFT = 1
-
-    # Fuel data index: right main tank
-    FUEL_RIGHT = 2
-
-    # Fuel data index: left aux tank
-    FUEL_LEFT_AUX = 3
-
-    # Fuel data index: right aux tank
-    FUEL_RIGHT_AUX = 4
-
-    # Fuel data index: left tip tank
-    FUEL_LEFT_TIP = 5
-
-    # Fuel data index: right tip tank
-    FUEL_RIGHT_AUX = 6
-
-    # Fuel data index: external 1 tank
-    FUEL_EXTERNAL_1 = 7
-
-    # Fuel data index: external 2 tank
-    FUEL_EXTERNAL_2 = 8
-
-    # Fuel data index: centre 2 tank
-    FUEL_CENTRE_2 = 9
-
-    # The number of fuel tank entries
-    NUM_FUEL = FUEL_CENTRE_2 + 1
-
-    # Engine index: engine #1
-    ENGINE_1 = 0
-
-    # Engine index: engine #2
-    ENGINE_2 = 1
-
-    # Engine index: engine #3
-    ENGINE_3 = 2
-
-    @staticmethod
-    def _readFrequency(frequency):
-        """Convert the given frequency into BCD."""
-        return Values._readBCD(int(frequency*100.0))
-
-    @staticmethod
-    def _readBCD(value):
-        """Convert the given value into BCD format."""
-        bcd = (value/1000) % 10
-        bcd <<= 4
-        bcd |= (value/100) % 10
-        bcd <<= 4
-        bcd |= (value/10) % 10
-        bcd <<= 4
-        bcd |= value % 10
-        return bcd
-        
-    @staticmethod
-    def _writeFrequency(value):
-        """Convert the given value into a frequency."""
-        return (Values._writeBCD(value) + 10000) / 100.0
-
-    @staticmethod
-    def _writeBCD(value):
-        """Convert the given BCD value into a real value."""
-        bcd = (value>>12) & 0x0f
-        bcd *= 10
-        bcd += (value>>8) & 0x0f
-        bcd *= 10
-        bcd += (value>>4) & 0x0f
-        bcd *= 10
-        bcd += (value>>0) & 0x0f
-
-        return bcd
-        
-    def __init__(self):
-        """Construct the values with defaults."""
-        self._timeOffset = 0
-        self.airPath = "C:\\Program Files\\Microsoft Games\\" \
-                       "FS9\\Aircraft\\Cessna\\cessna172.air"
-        self.aircraftName = "Cessna 172SP"
-        self.flapsNotches = [0, 1, 2, 5, 10, 15, 25, 30, 40]
-        self.fuelCapacities = [10000.0, 5000.0, 5000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
-        
-        self.paused = False
-        self.frozen = False
-        self.replay = False
-        self.slew = False
-        self.overspeed = False
-        self.stalled = False
-        self.onTheGround = True
-        
-        self.zfw = 50000.0
-        
-        self.fuelWeights = [0.0, 3000.0, 3000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
-        # Wikipedia: "Jet fuel", Jet A-1 density at 15*C .804kg/l -> 6.7 pounds/gallon
-        self.fuelWeight = 6.70970518
-
-        self.heading = 220.0
-        self.pitch = 0.0
-        self.bank = 0.0
-
-        self.ias = 0.0
-        self.vs = 0.0
-
-        self.radioAltitude = None
-        self.altitude = 513.0
-
-        self.gLoad = 1.0
-
-        self.flapsControl = 0.0
-        self.flaps = 0.0
-
-        self.navLightsOn = True
-        self.antiCollisionLightsOn = False
-        self.landingLightsOn = False
-        self.strobeLightsOn = False
-
-        self.pitot = False
-        self.parking = True
-
-        self.noseGear = 1.0
-
-        self.spoilersArmed = False
-        self.spoilers = 0.0
-
-        self.altimeter = 1013.0
-
-        self.nav1 = 117.3
-        self.nav2 = 109.5
-        self.squawk = 2200
-
-        self.windSpeed = 8.0
-        self.windDirection = 300.0
-        
-        self.n1 = [0.0, 0.0, 0.0]
-        self.throttles = [0.0, 0.0, 0.0]
-
-    def read(self, offset):
-        """Read the value at the given offset."""
-        try:
-            return self._read(offset)
-        except Exception, e:
-            print "failed to read offset %04x: %s" % (offset, str(e))
-            raise FSUIPCException(ERR_DATA)
-
-    def _read(self, offset):
-        """Read the value at the given offset."""
-        if offset==0x023a:         # Second of time
-            return self._readUTC().tm_sec
-        elif offset==0x023b:       # Hour of Zulu time
-            return self._readUTC().tm_hour
-        elif offset==0x023c:       # Minute of Zulu time
-            return self._readUTC().tm_min
-        elif offset==0x023e:       # Day number on year
-            return self._readUTC().tm_yday
-        elif offset==0x0240:       # Year in FS
-            return self._readUTC().tm_year
-        elif offset==0x0264:       # Paused
-            return 1 if self.paused else 0
-        elif offset==0x029c:       # Pitot
-            return 1 if self.pitot else 0
-        elif offset==0x02b4:       # Ground speed
-            # FIXME: calculate TAS first, then from the heading and
-            # wind the GS
-            return int(self.ias * 65536.0 * 1852.0 / 3600.0)
-        elif offset==0x02b8:       # TAS
-            return int(self._getTAS() * 128.0)
-        elif offset==0x02bc:       # IAS
-            return int(self.ias * 128.0)
-        elif offset==0x02c8:       # VS
-            return int(self.vs * const.FEETTOMETRES * 256.0 / 60.0)
-        elif offset==0x0330:       # Altimeter
-            return int(self.altimeter * 16.0)
-        elif offset==0x0350:       # NAV1
-            return Values._readFrequency(self.nav1)
-        elif offset==0x0352:       # NAV2
-            return Values._readFrequency(self.nav2)
-        elif offset==0x0354:       # Squawk
-            return Values._readBCD(self.squawk)
-        elif offset==0x0366:       # On the ground
-            return 1 if self.onTheGround else 0
-        elif offset==0x036c:       # Stalled
-            return 1 if self.stalled else 0
-        elif offset==0x036d:       # Overspeed
-            return 1 if self.overspeed else 0
-        elif offset==0x0570:       # Altitude
-            return long(self.altitude * const.FEETTOMETRES * 65536.0 * 65536.0)
-        elif offset==0x0578:       # Pitch
-            return int(self.pitch * 65536.0 * 65536.0 / 360.0)
-        elif offset==0x057c:       # Bank
-            return int(self.bank * 65536.0 * 65536.0 / 360.0)
-        elif offset==0x0580:       # Heading
-            return int(self.heading * 65536.0 * 65536.0 / 360.0)
-        elif offset==0x05dc:       # Slew
-            return 1 if self.slew else 0
-        elif offset==0x0628:       # Replay
-            return 1 if self.replay else 0
-        elif offset==0x088c:       # Engine #1 throttle
-            return self._getThrottle(self.ENGINE_1)
-        elif offset==0x0924:       # Engine #2 throttle
-            return self._getThrottle(self.ENGINE_2)
-        elif offset==0x09bc:       # Engine #3 throttle
-            return self._getThrottle(self.ENGINE_3)
-        elif offset==0x0af4:       # Fuel weight
-            return int(self.fuelWeight * 256.0)
-        elif offset==0x0b74:       # Centre tank level
-            return self._getFuelLevel(self.FUEL_CENTRE)
-        elif offset==0x0b78:       # Centre tank capacity
-            return self._getFuelCapacity(self.FUEL_CENTRE)
-        elif offset==0x0b7c:       # Left tank level
-            return self._getFuelLevel(self.FUEL_LEFT)
-        elif offset==0x0b80:       # Left tank capacity
-            return self._getFuelCapacity(self.FUEL_LEFT)
-        elif offset==0x0b84:       # Left aux tank level
-            return self._getFuelLevel(self.FUEL_LEFT_AUX)
-        elif offset==0x0b88:       # Left aux tank capacity
-            return self._getFuelCapacity(self.FUEL_LEFT_AUX)
-        elif offset==0x0b8c:       # Left tip tank level
-            return self._getFuelLevel(self.FUEL_LEFT_TIP)
-        elif offset==0x0b90:       # Left tip tank capacity
-            return self._getFuelCapacity(self.FUEL_LEFT_TIP)
-        elif offset==0x0b94:       # Right aux tank level
-            return self._getFuelLevel(self.FUEL_RIGHT)
-        elif offset==0x0b98:       # Right aux tank capacity
-            return self._getFuelCapacity(self.FUEL_RIGHT)
-        elif offset==0x0b9c:       # Right tank level
-            return self._getFuelLevel(self.FUEL_RIGHT_AUX)
-        elif offset==0x0ba0:       # Right tank capacity
-            return self._getFuelCapacity(self.FUEL_RIGHT_AUX)
-        elif offset==0x0ba4:       # Right tip tank level
-            return self._getFuelLevel(self.FUEL_RIGHT_TIP)
-        elif offset==0x0ba8:       # Right tip tank capacity
-            return self._getFuelCapacity(self.FUEL_RIGHT_TIP)
-        elif offset==0x0bc8:       # Parking
-            return 1 if self.parking else 0
-        elif offset==0x0bcc:       # Spoilers armed
-            return 1 if self.spoilersArmed else 0
-        elif offset==0x0bd0:       # Spoilers
-            return 0 if self.spoilers == 0 \
-                else int(self.spoilers * (16383 - 4800) + 4800)
-        elif offset==0x0bdc:       # Flaps control
-            numNotchesM1 = len(self.flapsNotches) - 1
-            flapsIncrement = 16383.0 / numNotchesM1
-            index = 0
-            while index<numNotchesM1 and \
-                  self.flapsControl<self.flapsNotches[index]:
-                index += 1
-                
-            if index==numNotchesM1:
-                return 16383
-            else:
-                return int((self.flapsControl-self.flapsNotches[index]) * \
-                           flapsIncrement / \
-                           (self.flapsNotches[index+1] - self.flapsNotches[index]))
-        elif offset==0x0be0 or offset==0x0be4:    # Flaps left and  right
-            return self.flaps * 16383.0 / self.flapsNotches[-1]        
-        elif offset==0x0bec:       # Nose gear
-            return int(self.noseGear * 16383.0)
-        elif offset==0x0d0c:       # Lights
-            lights = 0
-            if self.navLightsOn: lights |= 0x01
-            if self.antiCollisionLightsOn: lights |= 0x02
-            if self.landingLightsOn: lights |= 0x04
-            if self.strobeLightsOn: lights |= 0x10
-            return lights
-        elif offset==0x0e90:       # Wind speed
-            return int(self.windSpeed)
-        elif offset==0x0e92:       # Wind direction
-            return int(self.windDirection * 65536.0 / 360.0)
-        elif offset==0x11ba:       # G-Load
-            return int(self.gLoad * 625.0)
-        elif offset==0x11c6:       # Mach
-            # FIXME: calculate from IAS, altitude and QNH
-            return int(self.ias * 0.05 * 20480.)
-        elif offset==0x1244:       # Centre 2 tank level
-            return self._getFuelLevel(self.FUEL_CENTRE_2)
-        elif offset==0x1248:       # Centre 2 tank capacity
-            return self._getFuelCapacity(self.FUEL_CENTRE_2)
-        elif offset==0x1254:       # External 1 tank level
-            return self._getFuelLevel(self.FUEL_EXTERNAL_1)
-        elif offset==0x1258:       # External 1 tank capacity
-            return self._getFuelCapacity(self.FUEL_EXTERNAL_1)
-        elif offset==0x125c:       # External 2 tank level
-            return self._getFuelLevel(self.FUEL_EXTERNAL_2)
-        elif offset==0x1260:       # External 2 tank capacity
-            return self._getFuelCapacity(self.FUEL_EXTERNAL_2)
-        elif offset==0x2000:       # Engine #1 N1
-            return self.n1[self.ENGINE_1]
-        elif offset==0x2100:       # Engine #2 N1
-            return self.n1[self.ENGINE_2]
-        elif offset==0x2200:       # Engine #3 N1
-            return self.n1[self.ENGINE_3]
-        elif offset==0x30c0:       # Gross weight
-            return (self.zfw + sum(self.fuelWeights)) * const.KGSTOLB
-        elif offset==0x31e4:       # Radio altitude
-            # FIXME: if self.radioAltitude is None, calculate from the 
-            # altitude with some, perhaps random, ground altitude
-            # value
-            radioAltitude = (self.altitude - 517) \
-                if self.radioAltitude is None else self.radioAltitude
-            return (radioAltitude * const.FEETTOMETRES * 65536.0)
-        elif offset==0x3364:       # Frozen
-            return 1 if self.frozen else 0
-        elif offset==0x3bfc:       # ZFW
-            return int(self.zfw * 256.0 * const.KGSTOLB)
-        elif offset==0x3c00:       # Path of the current AIR file
-            return self.airPath
-        elif offset==0x3d00:       # Name of the current aircraft
-            return self.aircraftName
-        else:
-            print "Unhandled offset: %04x" % (offset,)
-            raise FSUIPCException(ERR_DATA)
-
-    def write(self, offset, value):
-        """Write the value at the given offset."""
-        try:
-            return self._write(offset, value)
-        except Exception, e:
-            print "failed to write offset %04x: %s" % (offset, str(e))
-            raise FSUIPCException(ERR_DATA)
-
-    def _write(self, offset, value):
-        """Write the given value at the given offset."""
-        if offset==0x023a:         # Second of time
-            self._updateTimeOffset(5, value)
-        elif offset==0x023b:       # Hour of Zulu time
-            self._updateTimeOffset(3, value)
-        elif offset==0x023c:       # Minute of Zulu time
-            self._updateTimeOffset(4, value)
-        elif offset==0x023e:       # Day number in the year
-            self._updateTimeOffset(7, value)
-        elif offset==0x0240:       # Year in FS
-            self._updateTimeOffset(0, value)
-        elif offset==0x0264:       # Paused
-            self.paused = value!=0
-        elif offset==0x029c:       # Pitot
-            self.pitot = value!=0
-        elif offset==0x02b4:       # Ground speed
-            # FIXME: calculate TAS using the heading and the wind, and
-            # then IAS based on the altitude
-            self.ias = value * 3600.0 / 65536.0 / 1852.0
-        elif offset==0x02bc:       # IAS
-            self.ias = value / 128.0
-        elif offset==0x02c8:       # VS
-            self.vs = value * 60.0 / const.FEETTOMETRES / 256.0
-        elif offset==0x0330:       # Altimeter
-            self.altimeter = value / 16.0
-        elif offset==0x0350:       # NAV1
-            self.nav1 = Values._writeFrequency(value)
-        elif offset==0x0352:       # NAV2
-            self.nav2 = Values._writeFrequency(value)
-        elif offset==0x0354:       # Squawk
-            self.squawk = Values._writeBCD(value)
-        elif offset==0x0366:       # Stalled
-            self.onTheGround = value!=0
-        elif offset==0x036c:       # Stalled
-            self.stalled = value!=0
-        elif offset==0x036d:       # Overspeed
-            self.overspeed = value!=0
-        elif offset==0x0570:       # Altitude
-            self.altitude = value / const.FEETTOMETRES / 65536.0 / 65536.0
-        elif offset==0x0578:       # Pitch
-            self.pitch = value * 360.0 / 65536.0 / 65536.0
-        elif offset==0x057c:       # Bank
-            self.bank = value * 360.0 / 65536.0 / 65536.0
-        elif offset==0x0580:       # Heading
-            self.heading = value * 360.0 / 65536.0 / 65536.0
-        elif offset==0x05dc:       # Slew
-            self.slew = value!=0
-        elif offset==0x0628:       # Replay
-            self.replay = value!=0
-        elif offset==0x088c:       # Engine #1 throttle
-            self._setThrottle(self.ENGINE_1, value)
-        elif offset==0x0924:       # Engine #2 throttle
-            self._setThrottle(self.ENGINE_2, value)
-        elif offset==0x09bc:       # Engine #3 throttle
-            self._setThrottle(self.ENGINE_3, value)
-        elif offset==0x0af4:       # Fuel weight
-            self.fuelWeight = value / 256.0
-        elif offset==0x0b74:       # Centre tank level
-            self._setFuelLevel(self.FUEL_CENTRE, value)
-        elif offset==0x0b78:       # Centre tank capacity
-            self._setFuelCapacity(self.FUEL_CENTRE, value)
-        elif offset==0x0b7c:       # Left tank level
-            self._setFuelLevel(self.FUEL_LEFT, value)
-        elif offset==0x0b80:       # Left tank capacity
-            self._setFuelCapacity(self.FUEL_LEFT, value)
-        elif offset==0x0b84:       # Left aux tank level
-            self._setFuelLevel(self.FUEL_LEFT_AUX, value)
-        elif offset==0x0b88:       # Left aux tank capacity
-            self._setFuelCapacity(self.FUEL_LEFT_AUX, value)
-        elif offset==0x0b8c:       # Left tip tank level
-            self._setFuelLevel(self.FUEL_LEFT_TIP, value)
-        elif offset==0x0b90:       # Left tip tank capacity
-            self._setFuelCapacity(self.FUEL_LEFT_TIP, value)
-        elif offset==0x0b94:       # Right aux tank level
-            self._setFuelLevel(self.FUEL_RIGHT, value)
-        elif offset==0x0b98:       # Right aux tank capacity
-            self._setFuelCapacity(self.FUEL_RIGHT, value)
-        elif offset==0x0b9c:       # Right tank level
-            self._setFuelLevel(self.FUEL_RIGHT_AUX, value)
-        elif offset==0x0ba0:       # Right tank capacity
-            self._setFuelCapacity(self.FUEL_RIGHT_AUX, value)
-        elif offset==0x0ba4:       # Right tip tank level
-            self._setFuelLevel(self.FUEL_RIGHT_TIP, value)
-        elif offset==0x0ba8:       # Right tip tank capacity
-            self._setFuelCapacity(self.FUEL_RIGHT_TIP, value)
-        elif offset==0x0bc8:       # Parking
-            self.parking = value!=0
-        elif offset==0x0bcc:       # Spoilers armed
-            self.spoilersArmed = value!=0
-        elif offset==0x0bd0:       # Spoilers
-            self.spoilters = 0 if value==0 \
-                             else (value - 4800) / (16383 - 4800)
-        elif offset==0x0bdc:       # Flaps control
-            numNotchesM1 = len(self.flapsNotches) - 1
-            flapsIncrement = 16383.0 / numNotchesM1
-            index = value / flapsIncrement
-            if index>=numNotchesM1:
-                self.flapsControl = self.flapsNotches[-1]
-            else:
-                self.flapsControl = self.flapsNotches[index]
-                self.flapsControl += value * \
-                    (self.flapsNotches[index+1] - self.flapsNotches[index]) / \
-                    flapsIncrement
-        elif offset==0x0be0 or offset==0x0be4:    # Flaps left and  right
-            self.flaps = value * self.flapsNotches[-1] / 16383.0
-        elif offset==0x0bec:       # Nose gear
-            self.noseGear = value / 16383.0
-        elif offset==0x0d0c:       # Lights
-            self.navLightsOn = (lights&0x01)!=0
-            self.antiCollisionLightsOn = (lights&0x02)!=0
-            self.landingLightsOn = (lights&0x04)!=0 
-            self.strobeLightsOn = (lights&0x10)!=0 
-        elif offset==0x0e90:       # Wind speed
-            self.windSpeed = value
-        elif offset==0x0e92:       # Wind direction
-            self.windDirection = value * 360.0 / 65536.0
-        elif offset==0x11ba:       # G-Load
-            self.gLoad = value / 625.0
-        elif offset==0x11c6:       # Mach
-            # FIXME: calculate IAS using the altitude and QNH
-            self.ias = value / 0.05 / 20480
-        elif offset==0x1244:       # Centre 2 tank level
-            self._setFuelLevel(self.FUEL_CENTRE_2, value)
-        elif offset==0x1248:       # Centre 2 tank capacity
-            self._setFuelCapacity(self.FUEL_CENTRE_2, value)
-        elif offset==0x1254:       # External 1 tank level
-            self._setFuelLevel(self.FUEL_EXTERNAL_1, value)
-        elif offset==0x1258:       # External 1 tank capacity
-            self._setFuelCapacity(self.FUEL_EXTERNAL_1, value)
-        elif offset==0x125c:       # External 2 tank level
-            self._setFuelLevel(self.FUEL_EXTERNAL_2, value)
-        elif offset==0x1260:       # External 2 tank capacity
-            self._setFuelCapacity(self.FUEL_EXTERNAL_2, value)
-        elif offset==0x2000:       # Engine #1 N1
-            self.n1[self.ENGINE_1] = value
-        elif offset==0x2100:       # Engine #2 N1
-            self.n1[self.ENGINE_2] = value
-        elif offset==0x2200:       # Engine #3 N1
-            self.n1[self.ENGINE_3] = value
-        elif offset==0x30c0:       # Gross weight
-            raise FSUIPCException(ERR_DATA)
-        elif offset==0x31e4:       # Radio altitude
-            raise FSUIPCException(ERR_DATA)
-        elif offset==0x3364:       # Frozen
-            self.frozen = value!=0
-        elif offset==0x3bfc:       # ZFW
-            self.zfw = value * const.LBSTOKG / 256.0
-            print "ZFW:", self.zfw
-        elif offset==0x3c00:       # Path of the current AIR file
-            self.airPath = value
-        elif offset==0x3d00:       # Name of the current aircraft
-            self.aircraftName = value
-        else:
-            print "Unhandled offset: %04x" % (offset,)
-            raise FSUIPCException(ERR_DATA)
-        
-    def _readUTC(self):
-        """Read the UTC time.
-        
-        The current offset is added to it."""
-        return time.gmtime(time.time() + self._timeOffset)
-        
-    def _getFuelLevel(self, index):
-        """Get the fuel level for the fuel tank with the given
-        index."""
-        return 0 if self.fuelCapacities[index]==0.0 else \
-            int(self.fuelWeights[index] * 65536.0 * 128.0 / self.fuelCapacities[index])
-    
-    def _getFuelCapacity(self, index):
-        """Get the capacity of the fuel tank with the given index."""
-        return int(self.fuelCapacities[index] * const.KGSTOLB  / self.fuelWeight)
-
-    def _getThrottle(self, index):
-        """Get the throttle value for the given index."""
-        return int(self.throttles[index] * 16383.0)
-
-    def _updateTimeOffset(self, index, value):
-        """Update the time offset if the value in the tm structure is replaced
-        by the given value."""
-        tm = self._readUTC()
-        tm1 = tm[:index] + (value,) + tm[(index+1):]
-        self._timeOffset += calendar.timegm(tm1) - calendar.timegm(tm)
-    
-    def _setThrottle(self, index, value):
-        """Set the throttle value for the given index."""
-        self.throttles[index] = value / 16383.0
-
-    def _setFuelLevel(self, index, value):
-        """Set the fuel level for the fuel tank with the given index."""
-        self.fuelWeights[index] = self.fuelCapacities[index] * float(value) / \
-                                  65536.0 / 128.0
-    
-    def _setFuelCapacity(self, index, value):
-        """Set the capacity of the fuel tank with the given index."""
-        self.fuelCapacities[index] = value * self.fuelWeight * const.LBSTOKG
-
-    def _getTAS(self):
-        """Calculate the true airspeed."""
-        pressure = 101325 * math.pow(1 - 2.25577e-5 * self.altitude *
-                                      const.FEETTOMETRES,
-                                      5.25588)
-        temperature = 15 - self.altitude * 6.5 * const.FEETTOMETRES / 1000.0 
-        temperature += 273.15  # Celsius -> Kelvin
-        airDensity = pressure / (temperature * 287.05)
-        print "pressure:", pressure, "temperature:", temperature, "airDensity:", airDensity
-        return self.ias * math.sqrt(1.225 / airDensity)
-
-#------------------------------------------------------------------------------
-
-values = Values()
-
-#------------------------------------------------------------------------------
-
-failOpen = False
-
-opened = False
-
-#------------------------------------------------------------------------------
-
-def open(request):
-    """Open the connection."""
-    global opened
-    if failOpen:
-        raise FSUIPCException(ERR_NOFS)
-    elif opened:
-        raise FSUIPCException(ERR_OPEN)
-    else:
-        time.sleep(0.5)
-        opened = True
-        return True
-
-#------------------------------------------------------------------------------
-
-def prepare_data(pattern, forRead = True):
-    """Prepare the given pattern for reading and/or writing."""
-    if opened:
-        return pattern
-    else:
-        raise FSUIPCException(ERR_OPEN)
-        
-#------------------------------------------------------------------------------
-
-def read(data):
-    """Read the given data."""
-    if opened:
-        return [values.read(offset) for (offset, type) in data]
-    else:
-        raise FSUIPCException(ERR_OPEN)
-            
-#------------------------------------------------------------------------------
-
-def write(data):
-    """Write the given data."""
-    if opened:
-        for (offset, type, value) in data:
-            values.write(offset, value)
-    else:
-        raise FSUIPCException(ERR_OPEN)
-            
-#------------------------------------------------------------------------------
-
-def close():
-    """Close the connection."""
-    global opened
-    opened = False
-
-#------------------------------------------------------------------------------
-
-PORT=15015
-
-CALL_READ=1
-CALL_WRITE=2
-CALL_CLOSE=3
-CALL_FAILOPEN=4
-CALL_QUIT = 99
-
-RESULT_RETURNED=1
-RESULT_EXCEPTION=2
-
-#------------------------------------------------------------------------------
-
-class Server(threading.Thread):
-    """The server thread."""
-    def __init__(self):
-        """Construct the thread."""
-        super(Server, self).__init__()
-        self.daemon = True
-
-    def run(self):
-        """Perform the server's operation."""
-        serverSocket = socket.socket()
-
-        serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-        serverSocket.bind(("", PORT))
-    
-        serverSocket.listen(5)
-    
-        while True:
-            (clientSocket, clientAddress) = serverSocket.accept()
-            thread = threading.Thread(target = self._process, args=(clientSocket,))
-            thread.start()
-
-    def _process(self, clientSocket):
-        """Process the commands arriving on the given socket."""
-        socketFile = clientSocket.makefile()
-        try:
-            while True:                
-                (length,) = struct.unpack("I", clientSocket.recv(4))
-                data = clientSocket.recv(length)
-                (call, args) = cPickle.loads(data)
-                exception = None
-                
-                try:
-                    if call==CALL_READ:
-                        result = read(args[0])
-                    elif call==CALL_WRITE:
-                        result = write(args[0])
-                    elif call==CALL_CLOSE:
-                        global opened
-                        opened = False
-                        result = None
-                    elif call==CALL_FAILOPEN:
-                        global failOpen
-                        failOpen = args[0]
-                        result = None
-                    else:
-                        break
-                except Exception, e:
-                    exception = e
-
-                if exception is None:
-                    data = cPickle.dumps((RESULT_RETURNED, result))
-                else:
-                    data = cPickle.dumps((RESULT_EXCEPTION, exception))
-                clientSocket.send(struct.pack("I", len(data)) + data)
-        except Exception, e:
-            print >> sys.stderr, "pyuipc_sim.Server._process: failed with exception:", str(e)
-        finally:
-            try:
-                socketFile.close()
-            except:
-                pass
-            clientSocket.close()
-                   
-#------------------------------------------------------------------------------
-
-class Client(object):
-    """Client to the server."""
-    def __init__(self, serverHost):
-        """Construct the client and connect to the given server
-        host."""
-        self._socket = socket.socket()
-        self._socket.connect((serverHost, PORT))
-
-        self._socketFile = self._socket.makefile()
-
-    def read(self, data):
-        """Read the given data."""
-        return self._call(CALL_READ, data)
-
-    def write(self, data):
-        """Write the given data."""
-        return self._call(CALL_WRITE, data)
-
-    def close(self):
-        """Close the connection currently opened in the simulator."""
-        return self._call(CALL_CLOSE, None)
-
-    def failOpen(self, really):
-        """Enable/disable open failure in the simulator."""
-        return self._call(CALL_FAILOPEN, really)
-
-    def quit(self):
-        """Quit from the simulator."""
-        data = cPickle.dumps((CALL_QUIT, None))
-        self._socket.send(struct.pack("I", len(data)) + data)        
-
-    def _call(self, command, data):
-        """Perform a call with the given command and data."""
-        data = cPickle.dumps((command, [data]))
-        self._socket.send(struct.pack("I", len(data)) + data)
-        (length,) = struct.unpack("I", self._socket.recv(4))
-        data = self._socket.recv(length)
-        (resultCode, result) = cPickle.loads(data)
-        if resultCode==RESULT_RETURNED:
-            return result
-        else:
-            raise result        
-
-#------------------------------------------------------------------------------
-#------------------------------------------------------------------------------
-
-# FIXME: implement proper completion and history
-class CLI(cmd.Cmd):
-    """The command-line interpreter."""
-    @staticmethod
-    def str2bool(s):
-        """Convert the given string to a PyUIPC boolean value (i.e. 0 or 1)."""
-        return 1 if s in ["yes", "true", "on"] else 0
-    
-    @staticmethod
-    def bool2str(value):
-        """Convert the PyUIPC boolean value (i.e. 0 or 1) into a string."""
-        return "no" if value==0 else "yes"
-
-    @staticmethod
-    def degree2pyuipc(degree):
-        """Convert the given degree (as a string) into a PyUIPC value."""
-        return int(float(degree) * 65536.0 * 65536.0 / 360.0)
-        
-    @staticmethod
-    def pyuipc2degree(value):
-        """Convert the given PyUIPC value into a degree."""
-        return valie * 360.0 / 65536.0 / 65536.0
-
-    @staticmethod
-    def fuelLevel2pyuipc(level):
-        """Convert the given percentage value (as a string) into a PyUIPC value."""
-        return int(float(level) * 128.0 * 65536.0 / 100.0)
-        
-    @staticmethod
-    def pyuipc2fuelLevel(value):
-        """Convert the PyUIPC value into a percentage value."""
-        return value * 100.0 / 128.0 / 65536.0
-        
-    @staticmethod
-    def fuelCapacity2pyuipc(capacity):
-        """Convert the given capacity value (as a string) into a PyUIPC value."""
-        return int(capacity)
-        
-    @staticmethod
-    def pyuipc2fuelCapacity(value):
-        """Convert the given capacity value into a PyUIPC value."""
-        return value
-        
-    @staticmethod
-    def throttle2pyuipc(throttle):
-        """Convert the given throttle value (as a string) into a PyUIPC value."""
-        return int(float(throttle) * 16384.0 / 100.0)
-        
-    @staticmethod
-    def pyuipc2throttle(value):
-        """Convert the given PyUIPC value into a throttle value."""
-        return value * 100.0 / 16384.0
-        
-    def __init__(self):
-        """Construct the CLI."""
-        cmd.Cmd.__init__(self)
-        
-        self.use_rawinput = True
-        self.intro = "\nPyUIPC simulator command prompt\n"
-        self.prompt = "PyUIPC> "
-
-        self.daemon = True
-
-        self._client = Client("localhost")
-
-        self._valueHandlers = {}
-        self._valueHandlers["year"] = (0x0240, "H",  lambda value: value,
-                                      lambda word: int(word))
-        self._valueHandlers["yday"] = (0x023e, "H",  lambda value: value,
-                                       lambda word: int(word))
-        self._valueHandlers["hour"] = (0x023b, "b",  lambda value: value,
-                                       lambda word: int(word))
-        self._valueHandlers["min"] = (0x023c, "b",  lambda value: value,
-                                      lambda word: int(word))
-        self._valueHandlers["sec"] = (0x023a, "b",  lambda value: value,
-                                      lambda word: int(word))
-        self._valueHandlers["acftName"] = (0x3d00, -256,  lambda value: value,
-                                           lambda word: word)
-        self._valueHandlers["airPath"] = (0x3c00, -256,  lambda value: value,
-                                          lambda word: word)
-        self._valueHandlers["paused"] = (0x0264, "H", CLI.bool2str, CLI.str2bool)
-        self._valueHandlers["frozen"] = (0x3364, "H", CLI.bool2str, CLI.str2bool)
-        self._valueHandlers["replay"] = (0x0628, "d", CLI.bool2str, CLI.str2bool)
-        self._valueHandlers["slew"] = (0x05dc, "H", CLI.bool2str, CLI.str2bool)
-        self._valueHandlers["overspeed"] = (0x036d, "b", CLI.bool2str, CLI.str2bool)
-        self._valueHandlers["stalled"] = (0x036c, "b", CLI.bool2str, CLI.str2bool)
-        self._valueHandlers["onTheGround"] = (0x0366, "H", CLI.bool2str, CLI.str2bool)
-        self._valueHandlers["zfw"] = (0x3bfc, "d",
-                                      lambda value: value * const.LBSTOKG / 256.0,
-                                      lambda word: int(float(word) * 256.0 *
-                                                       const.KGSTOLB))
-        self._valueHandlers["grossWeight"] = (0x30c0, "f",
-                                              lambda value: value * const.LBSTOKG,
-                                              lambda word: None)
-        self._valueHandlers["heading"] = (0x0580, "d",
-                                          CLI.pyuipc2degree, CLI.degree2pyuipc)
-        self._valueHandlers["pitch"] = (0x0578, "d",
-                                        CLI.pyuipc2degree, CLI.degree2pyuipc)
-        self._valueHandlers["bank"] = (0x057c, "d",
-                                       CLI.pyuipc2degree, CLI.degree2pyuipc)
-        self._valueHandlers["ias"] = (0x02bc, "d",
-                                      lambda value: value / 128.0,
-                                      lambda word: int(float(word) * 128.0))
-        self._valueHandlers["mach"] = (0x11c6, "H",
-                                       lambda value: value / 20480.0,
-                                       lambda word: int(float(word) * 20480.0))
-        self._valueHandlers["gs"] = (0x02b4, "d",
-                                     lambda value: value * 3600.0 / 65536.0 / 1852.0,
-                                     lambda word: int(float(word) * 65536.0 *
-                                                      1852.0 / 3600))
-        self._valueHandlers["tas"] = (0x02b8, "d",
-                                     lambda value: value / 128.0,
-                                     lambda word: None)
-        self._valueHandlers["vs"] = (0x02c8, "d",
-                                     lambda value: value * 60 /                                     
-                                     const.FEETTOMETRES / 256.0,
-                                     lambda word: int(float(word) *
-                                                      const.FEETTOMETRES *
-                                                      256.0 / 60.0))
-        self._valueHandlers["radioAltitude"] = (0x31e4, "d",
-                                                lambda value: value /
-                                                const.FEETTOMETRES /
-                                                65536.0,
-                                                lambda word: int(float(word) *
-                                                                 const.FEETTOMETRES *
-                                                                 65536.0))
-        self._valueHandlers["altitude"] = (0x0570, "l",
-                                           lambda value: value /
-                                           const.FEETTOMETRES / 65536.0 /
-                                           65536.0,
-                                           lambda word: long(float(word) *
-                                                             const.FEETTOMETRES *
-                                                             65536.0 * 65536.0))
-        self._valueHandlers["gLoad"] = (0x11ba, "H",
-                                        lambda value: value / 625.0,
-                                        lambda word: int(float(word) * 625.0))
-
-        self._valueHandlers["flapsControl"] = (0x0bdc, "d",
-                                               lambda value: value * 100.0 / 16383.0,
-                                               lambda word: int(float(word) *
-                                                                16383.0 / 100.0))
-        self._valueHandlers["flaps"] = (0x0be0, "d",
-                                        lambda value: value * 100.0 / 16383.0,
-                                        lambda word: int(float(word) *
-                                                         16383.0 / 100.0))
-        self._valueHandlers["lights"] = (0x0d0c, "H",
-                                        lambda value: value,
-                                        lambda word: int(word))
-        self._valueHandlers["pitot"] = (0x029c, "b", CLI.bool2str, CLI.str2bool)
-        self._valueHandlers["parking"] = (0x0bc8, "H", CLI.bool2str, CLI.str2bool)
-        self._valueHandlers["noseGear"] = (0x0bec, "d",
-                                           lambda value: value * 100.0 / 16383.0,
-                                           lambda word: int(float(word) *
-                                                            16383.0 / 100.0))
-        self._valueHandlers["spoilersArmed"] = (0x0bcc, "d",
-                                                CLI.bool2str, CLI.str2bool)
-        self._valueHandlers["spoilers"] = (0x0bd0, "d",
-                                           lambda value: value,
-                                           lambda word: int(word))
-        self._valueHandlers["qnh"] = (0x0330, "H",
-                                      lambda value: value / 16.0,
-                                      lambda word: int(float(word)*16.0))
-        self._valueHandlers["nav1"] = (0x0350, "H",
-                                       Values._writeFrequency,
-                                       lambda word: Values._readFrequency(float(word)))
-        self._valueHandlers["nav2"] = (0x0352, "H",
-                                       Values._writeFrequency,
-                                       lambda word: Values._readFrequency(float(word)))
-        self._valueHandlers["squawk"] = (0x0354, "H",
-                                       Values._writeBCD,
-                                       lambda word: Values._readBCD(int(word)))
-        self._valueHandlers["windSpeed"] = (0x0e90, "H",
-                                            lambda value: value,
-                                            lambda word: int(word))
-        self._valueHandlers["windDirection"] = (0x0e92, "H",
-                                                lambda value: value * 360.0 / 65536.0,
-                                                lambda word: int(int(word) *
-                                                                 65536.0 / 360.0))
-        self._valueHandlers["fuelWeight"] = (0x0af4, "H",
-                                             lambda value: value / 256.0,
-                                             lambda word: int(float(word)*256.0))
-
-
-        self._valueHandlers["centreLevel"] = (0x0b74, "d", CLI.pyuipc2fuelLevel,
-                                              CLI.fuelLevel2pyuipc)
-        self._valueHandlers["centreCapacity"] = (0x0b78, "d",
-                                                 CLI.pyuipc2fuelCapacity,
-                                                 CLI.fuelCapacity2pyuipc)
-        self._valueHandlers["leftMainLevel"] = (0x0b7c, "d", CLI.pyuipc2fuelLevel,
-                                              CLI.fuelLevel2pyuipc)
-        self._valueHandlers["leftMainCapacity"] = (0x0b80, "d",
-                                                   CLI.pyuipc2fuelCapacity,
-                                                   CLI.fuelCapacity2pyuipc)
-        self._valueHandlers["leftAuxLevel"] = (0x0b84, "d", CLI.pyuipc2fuelLevel,
-                                               CLI.fuelLevel2pyuipc)
-        self._valueHandlers["leftAuxCapacity"] = (0x0b88, "d",
-                                                   CLI.pyuipc2fuelCapacity,
-                                                  CLI.fuelCapacity2pyuipc)
-        self._valueHandlers["leftTipLevel"] = (0x0b8c, "d", CLI.pyuipc2fuelLevel,
-                                              CLI.fuelLevel2pyuipc)
-        self._valueHandlers["leftTipCapacity"] = (0x0b90, "d",
-                                                  CLI.pyuipc2fuelCapacity,
-                                                  CLI.fuelCapacity2pyuipc)
-        self._valueHandlers["rightMainLevel"] = (0x0b94, "d", CLI.pyuipc2fuelLevel,
-                                                 CLI.fuelLevel2pyuipc)
-        self._valueHandlers["rightMainCapacity"] = (0x0b98, "d",
-                                                    CLI.pyuipc2fuelCapacity,
-                                                    CLI.fuelCapacity2pyuipc)
-        self._valueHandlers["rightAuxLevel"] = (0x0b9c, "d", CLI.pyuipc2fuelLevel,
-                                                CLI.fuelLevel2pyuipc)
-        self._valueHandlers["rightAuxCapacity"] = (0x0ba0, "d",
-                                                   CLI.pyuipc2fuelCapacity,
-                                                   CLI.fuelCapacity2pyuipc)
-        self._valueHandlers["rightTipLevel"] = (0x0ba4, "d", CLI.pyuipc2fuelLevel,
-                                                CLI.fuelLevel2pyuipc)
-        self._valueHandlers["rightTipCapacity"] = (0x0ba8, "d",
-                                                   CLI.pyuipc2fuelCapacity,
-                                                   CLI.fuelCapacity2pyuipc)
-        self._valueHandlers["centre2Level"] = (0x1244, "d", CLI.pyuipc2fuelLevel,
-                                               CLI.fuelLevel2pyuipc)
-        self._valueHandlers["centre2Capacity"] = (0x1248, "d",
-                                                  CLI.pyuipc2fuelCapacity,
-                                                  CLI.fuelCapacity2pyuipc)
-        self._valueHandlers["external1Level"] = (0x1254, "d", CLI.pyuipc2fuelLevel,
-                                                 CLI.fuelLevel2pyuipc)
-        self._valueHandlers["external1Capacity"] = (0x1258, "d",
-                                                    CLI.pyuipc2fuelCapacity,
-                                                    CLI.fuelCapacity2pyuipc)
-        self._valueHandlers["external2Level"] = (0x125c, "d", CLI.pyuipc2fuelLevel,
-                                                 CLI.fuelLevel2pyuipc)
-        self._valueHandlers["external2Capacity"] = (0x1260, "d",
-                                                    CLI.pyuipc2fuelCapacity,
-                                                    CLI.fuelCapacity2pyuipc)
-
-        self._valueHandlers["n1_1"] = (0x2000, "f", lambda value: value,
-                                       lambda word: float(word))
-        self._valueHandlers["n1_2"] = (0x2100, "f", lambda value: value,
-                                       lambda word: float(word))
-        self._valueHandlers["n1_3"] = (0x2200, "f", lambda value: value,
-                                       lambda word: float(word))
-
-        self._valueHandlers["throttle_1"] = (0x088c, "H",
-                                             CLI.pyuipc2throttle,
-                                             CLI.throttle2pyuipc)
-        self._valueHandlers["throttle_2"] = (0x0924, "H",
-                                             CLI.pyuipc2throttle,
-                                             CLI.throttle2pyuipc)
-        self._valueHandlers["throttle_3"] = (0x09bc, "H",
-                                             CLI.pyuipc2throttle,
-                                             CLI.throttle2pyuipc)
-                                                            
-    def default(self, line):
-        """Handle unhandle commands."""
-        if line=="EOF":
-            print
-            return self.do_quit("")
-        else:
-            return super(CLI, self).default(line)
-
-    def do_get(self, args):
-        """Handle the get command."""
-        names = args.split()
-        data = []        
-        for name in names:
-            if name not in self._valueHandlers:
-                print >> sys.stderr, "Unknown variable: " + name
-                return False
-            valueHandler = self._valueHandlers[name]
-            data.append((valueHandler[0], valueHandler[1]))
-
-        try:
-            result = self._client.read(data)
-            for i in range(0, len(result)):
-                name = names[i]
-                valueHandler = self._valueHandlers[name]
-                print name + "=" + str(valueHandler[2](result[i]))
-        except Exception, e:
-            print >> sys.stderr, "Failed to read data: " + str(e)                        
-
-        return False
-
-    def help_get(self):
-        """Print help for the get command."""
-        print "get <variable> [<variable>...]"
-
-    def complete_get(self, text, line, begidx, endidx):
-        """Try to complete the get command."""
-        return [key for key in self._valueHandlers if key.startswith(text)]
-
-    def do_set(self, args):
-        """Handle the set command."""
-        arguments = args.split()
-        names = []
-        data = []
-        for argument in arguments:
-            words = argument.split("=")
-            if len(words)!=2:
-                print >> sys.stderr, "Invalid argument: " + argument
-                return False
-
-            (name, value) = words
-            if name not in self._valueHandlers:
-                print >> sys.stderr, "Unknown variable: " + name
-                return False
-
-            valueHandler = self._valueHandlers[name]
-            try:
-                value = valueHandler[3](value)
-                data.append((valueHandler[0], valueHandler[1], value))
-            except Exception, e:
-                print >> sys.stderr, "Invalid value '%s' for variable %s: %s" % \
-                      (value, name, str(e))
-                return False
-                
-        try:
-            self._client.write(data)
-            print "Data written"
-        except Exception, e:
-            print >> sys.stderr, "Failed to write data: " + str(e)
-
-        return False
-
-    def help_set(self):
-        """Print help for the set command."""
-        print "set <variable>=<value> [<variable>=<value>...]"
-
-    def complete_set(self, text, line, begidx, endidx):
-        """Try to complete the set command."""
-        if not text and begidx>0 and line[begidx-1]=="=":
-            return []
-        else:
-            return [key + "=" for key in self._valueHandlers if key.startswith(text)]
-
-    def do_close(self, args):
-        """Close an existing connection so that FS will fail."""
-        try:
-            self._client.close()
-            print "Connection closed"
-        except Exception, e:
-            print >> sys.stderr, "Failed to close the connection: " + str(e)        
-        
-    def do_failopen(self, args):
-        """Enable/disable the failing of opens."""
-        try:
-            value = self.str2bool(args)
-            self._client.failOpen(value)
-            print "Opening will%s fail" % ("" if value else " not",)
-        except Exception, e:
-            print >> sys.stderr, "Failed to set open failure: " + str(e)        
-
-    def help_failopen(self, usage = False):
-        """Help for the failopen close"""
-        if usage: print "Usage:",
-        print "failopen yes|no"
-
-    def complete_failopen(self, text, line, begidx, endidx):
-        if text:
-            if "yes".startswith(text): return ["yes"]
-            elif "no".startswith(text): return ["no"]
-            else: return []
-        else:
-            return ["yes", "no"]
-        
-    def do_quit(self, args):
-        """Handle the quit command."""
-        self._client.quit()
-        return True
-
-#------------------------------------------------------------------------------
-
-if __name__ == "__main__":
-    CLI().cmdloop()
-else:
-    server = Server()
-    server.start()    
-
-#------------------------------------------------------------------------------
Index: src/mlx/test.py
===================================================================
--- src/mlx/test.py	(revision 27)
+++ src/mlx/test.py	(revision 27)
@@ -0,0 +1,35 @@
+# Test module
+
+import fs
+import flight
+import logger
+import acft
+import const
+
+import time
+import sys
+
+def callback(data, extra):
+    print data
+
+def main():
+    with open(sys.argv[1], "wt") as output:
+        fl = flight.Flight(logger.Logger(output = output))
+        fl.cruiseAltitude = 18000
+        fl.aircraftType = const.AIRCRAFT_DH8D
+        fl.aircraft = acft.Aircraft.create(fl)
+        #fl._stage = const.STAGE_LANDING
+        simulator = fs.createSimulator(const.SIM_MSFS9, fs.ConnectionListener(),
+                                       fl.aircraft)
+        fl.simulator = simulator
+
+        simulator.connect()
+        simulator.startMonitoring()
+        
+        fl.wait()
+
+        simulator.stopMonitoring()
+        simulator.disconnect()
+
+if __name__ == "__main__":
+    main()
Index: src/mlx/test.py
===================================================================
--- src/mlx/test.py	(revision 27)
+++ 	(revision )
@@ -1,35 +1,0 @@
-# Test module
-
-import fs
-import flight
-import logger
-import acft
-import const
-
-import time
-import sys
-
-def callback(data, extra):
-    print data
-
-def main():
-    with open(sys.argv[1], "wt") as output:
-        fl = flight.Flight(logger.Logger(output = output))
-        fl.cruiseAltitude = 18000
-        fl.aircraftType = const.AIRCRAFT_DH8D
-        fl.aircraft = acft.Aircraft.create(fl)
-        #fl._stage = const.STAGE_LANDING
-        simulator = fs.createSimulator(const.SIM_MSFS9, fs.ConnectionListener(),
-                                       fl.aircraft)
-        fl.simulator = simulator
-
-        simulator.connect()
-        simulator.startMonitoring()
-        
-        fl.wait()
-
-        simulator.stopMonitoring()
-        simulator.disconnect()
-
-if __name__ == "__main__":
-    main()
Index: src/mlx/update.py
===================================================================
--- src/mlx/update.py	(revision 39)
+++ src/mlx/update.py	(revision 39)
@@ -0,0 +1,583 @@
+# Module handling the download of updates
+
+#------------------------------------------------------------------------------
+
+from config import Config
+
+import os
+import sys
+import urllib2
+import tempfile
+import socket
+import subprocess
+import hashlib
+
+if os.name=="nt":
+    import win32api
+
+#------------------------------------------------------------------------------
+
+manifestName = "MLXMANIFEST"
+toremoveName = "toremove"
+
+#------------------------------------------------------------------------------
+
+class Manifest(object):
+    """The manifest of the files.
+
+    The manifest file consists of one line for each file. Each line contains 3
+    items separated by tabs:
+    - the path of the file relative to the distribution's root
+    - the size of the file
+    - the MD5 sum of the file."""
+    def __init__(self):
+        """Construct the manifest."""
+        self._files = {}
+
+    @property
+    def files(self):
+        """Get an iterator over the files.
+
+        Each file is returned as a 3-tuple with items as in the file."""
+        for (path, (size, sum)) in self._files.iteritems():
+            yield (path, size, sum)
+        
+    def addFile(self, path, size, sum):
+        """Add a file to the manifest."""
+        self._files[path] = (size, sum)
+
+    def addFiles(self, baseDirectory, subdirectory):
+        """Add the files in the given directory and subdirectories of it to the
+        manifest."""
+        directory = baseDirectory
+        for d in subdirectory: directory = os.path.join(directory, d)
+        
+        for entry in os.listdir(directory):
+            fullPath = os.path.join(directory, entry)
+            if os.path.isfile(fullPath):
+                size = os.stat(fullPath).st_size
+                sum = hashlib.md5()
+                with open(fullPath, "rb") as f:
+                    while True:
+                        data = f.read(4096)
+                        if data: sum.update(data)
+                        else: break
+                self.addFile("/".join(subdirectory + [entry]), size,
+                             sum.hexdigest())
+            elif os.path.isdir(fullPath):
+                self.addFiles(baseDirectory, subdirectory + [entry])
+                
+    def readFrom(self, file):
+        """Read a manifest from the given file object."""
+        for line in iter(file.readline, ""):
+            (path, size, sum) = line.strip().split("\t")
+            self._files[path] = (int(size), sum)
+
+    def writeInto(self, file):
+        """Write the manifest into the file at the given path."""
+        for (path, (size, sum)) in self._files.iteritems():
+            file.write("%s\t%d\t%s\n" % (path, size, sum))
+
+    def compare(self, other):
+        """Compare this manifest with the other one.
+
+        This returns a tuple of two lists:
+        - the files that are either different or are present in other, but not
+        here. Each file is returned as a 3-tuple as with other functions, the
+        size and sum are those of the new file.
+        - the paths of the files that are present here, but not in other."""
+        modifiedAndNew = []
+        for (path, otherSize, otherSum) in other.files:
+            if path in self._files:
+                (size, sum) = self._files[path]
+                if size!=otherSize or sum!=otherSum:
+                    modifiedAndNew.append((path, otherSize, otherSum))
+            else:
+                modifiedAndNew.append((path, otherSize, otherSum))
+
+        removed = [path for path in self._files if path not in other._files]
+        
+        return (modifiedAndNew, removed)
+            
+#------------------------------------------------------------------------------
+
+class ClientListener(object):
+    """A listener that sends any requests via a socket."""
+    def __init__(self, sock):
+        """Construct the listener."""
+        self._sock = sock
+
+    def downloadingManifest(self):
+        """Called when the downloading of the manifest has started."""
+        self._send(["downloadingManifest"])
+
+    def downloadedManifest(self):
+        """Called when the manifest has been downloaded."""
+        self._send(["downloadedManifest"])
+
+    def needSudo(self):
+        """Called when an admin-level program must be called to complete the
+        update.
+
+        This is not valid from a client, as that client is supposed to be the
+        admin-level program :)"""
+        assert False
+
+    def setTotalSize(self, numToModifyAndNew, totalSize, numToRemove,
+                     numToRemoveLocal):
+        """Called when starting the downloading of the files."""
+        self._send(["setTotalSize", str(numToModifyAndNew), str(totalSize),
+                    str(numToRemove), str(numToRemoveLocal)])
+
+    def setDownloaded(self, downloaded):
+        """Called periodically after downloading a certain amount of data."""
+        self._send(["setDownloaded", str(downloaded)])
+
+    def startRenaming(self):
+        """Called when the renaming of the downloaded files is started."""
+        self._send(["startRenaming"])
+
+    def renamed(self, path, count):
+        """Called when a file has been renamed."""
+        self._send(["renamed", path, str(count)])
+
+    def startRemoving(self):
+        """Called when the removing of files has started."""
+        self._send(["startRemoving"])
+
+    def removed(self, path, count):
+        """Called when a file has been removed."""
+        self._send(["removed", path, str(count)])
+
+    def writingManifest(self):
+        """Called when we have started writing the manifest."""
+        self._send(["writingManifest"])
+
+    def done(self):
+        """Called when the update has completed."""
+        self._send(["done"])
+
+    def failed(self, what):
+        """Called when something has failed."""
+        self._send(["failed", what])
+
+    def _send(self, words):
+        """Send the given words via the socket."""
+        self._sock.send("\t".join(words) + "\n")
+
+#------------------------------------------------------------------------------
+
+def readLocalManifest(directory):
+    """Read the local manifest from the given directory."""
+    manifestPath = os.path.join(directory, manifestName)
+
+    manifest = Manifest()
+    try:
+        with open(manifestPath, "rt") as f:
+            manifest.readFrom(f)
+    except Exception, e:
+        print "Error reading the manifest, ignoring:", str(e)
+        manifest = Manifest()
+
+    return manifest
+
+#------------------------------------------------------------------------------
+
+def prepareUpdate(directory, updateURL, listener):
+    """Prepare the update by downloading the manifest and comparing it with the
+    local one."""
+    manifest = readLocalManifest(directory)
+        
+    updateURL += "/" + os.name
+
+    listener.downloadingManifest()
+    f = None
+    try:
+        updateManifest = Manifest()
+        f= urllib2.urlopen(updateURL + "/" + manifestName)
+        updateManifest.readFrom(f)
+
+    except Exception, e:
+        print >> sys.stderr, "Error downloading manifest:", str(e)
+        listener.failed(str(e))
+        return None
+    finally:
+        if f is not None: f.close()
+
+    listener.downloadedManifest()
+
+    (modifiedAndNew, removed) = manifest.compare(updateManifest)
+
+    return (manifest, updateManifest, modifiedAndNew, removed)
+
+#------------------------------------------------------------------------------
+
+def getToremoveFiles(directory):
+    """Add the files to remove from the toremove directory."""
+    toremove = []
+    toremoveDirectory = os.path.join(directory, toremoveName)
+    if os.path.isdir(toremoveDirectory):
+        for entry in os.listdir(toremoveDirectory):
+            toremove.append(os.path.join(toremoveName, entry))
+    return toremove
+
+#------------------------------------------------------------------------------
+
+def createLocalPath(directory, path):
+    """Create a local path from the given manifest path."""
+    localPath = directory
+    for element in path.split("/"):
+        localPath = os.path.join(localPath, element)
+    return localPath
+
+#------------------------------------------------------------------------------
+
+def getToremoveDir(toremoveDir, directory):
+    """Get the path of the directory that will contain the files that are to be
+    removed."""
+    if toremoveDir is None:
+        toremoveDir = os.path.join(directory, toremoveName)
+        try:
+            os.mkdir(toremoveDir)
+        except:
+            pass
+
+    return toremoveDir
+
+#------------------------------------------------------------------------------
+
+def removeFile(toremoveDir, directory, path):
+    """Remove the file at the given path or store it in a temporary directory.
+
+    If the removal of the file fails, it will be stored in a temporary
+    directory. This is useful for files thay may be open and cannot be removed
+    right away."""
+    try:
+        os.remove(path)
+    except:
+        try:
+            sum = hashlib.md5()
+            sum.update(path)
+            toremoveDir = getToremoveDir(toremoveDir, directory)
+            os.rename(path, os.path.join(toremoveDir, sum.hexdigest()))
+        except Exception, e:
+            print "Cannot remove file " + path + ": " + str(e)    
+
+#------------------------------------------------------------------------------
+
+def updateFiles(directory, updateURL, listener,
+                manifest, modifiedAndNew, removed, localRemoved):
+    """Update the files according to the given information."""
+    totalSize = 0
+    for (path, size, sum) in modifiedAndNew:
+        totalSize += size
+
+    listener.setTotalSize(len(modifiedAndNew), totalSize,
+                          len(removed), len(localRemoved))
+
+    downloaded = 0
+    fin = None
+    toremoveDir = None
+
+    try:        
+        updateURL += "/" + os.name
+
+        for (path, size, sum) in modifiedAndNew:
+            targetFile = createLocalPath(directory, path)
+            targetFile += "."
+            targetFile += sum
+
+            targetDirectory = os.path.dirname(targetFile)
+            if not os.path.isdir(targetDirectory):
+                os.makedirs(targetDirectory)
+                
+            with open(targetFile, "wb") as fout:
+                fin = urllib2.urlopen(updateURL + "/" + path)
+                while True:
+                    data = fin.read(4096)
+                    if not data:
+                        break
+                    fout.write(data)
+                    downloaded += len(data)
+                    listener.setDownloaded(downloaded)
+                fin.close()
+                fin = None
+                
+        listener.startRenaming()
+        count = 0
+        for (path, size, sum) in modifiedAndNew:
+            targetFile = createLocalPath(directory, path)
+
+            downloadedFile = targetFile + "." + sum
+            if os.name=="nt" and os.path.isfile(targetFile):
+                removeFile(toremoveDir, directory, targetFile)
+            os.rename(downloadedFile, targetFile)
+            count += 1
+            listener.renamed(path, count)
+        
+        listener.startRemoving()
+        count = 0
+        removed += localRemoved
+        removed.sort(reverse = True)
+        for path in removed:            
+            removePath = createLocalPath(directory, path)
+            removeFile(toremoveDir, directory, removePath)
+
+            removeDirectory = os.path.dirname(removePath)
+            try:
+                os.removedirs(removeDirectory)
+            except:
+                pass
+
+            count += 1
+            listener.removed(path, count)
+
+        listener.writingManifest()
+
+        manifestPath = os.path.join(directory, manifestName)
+        with open(manifestPath, "wt") as f:
+            manifest.writeInto(f)
+        
+        listener.done()
+    except Exception, e:
+        print >> sys.stderr, "Error:", str(e)
+        listener.failed(str(e))        
+
+#------------------------------------------------------------------------------
+
+def isDirectoryWritable(directory):
+    """Determine if the given directory can be written."""
+    checkFile = os.path.join(directory, "writable.chk")
+    try:
+        f = open(checkFile, "wt")
+        f.close()
+        return True
+    except Exception, e:
+        return False
+    finally:
+        try:
+            os.remove(checkFile)
+        except:
+            pass
+
+#------------------------------------------------------------------------------
+
+def processMLXUpdate(buffer, listener):
+    """Process the given buffer supposedly containing a list of commands."""
+    endsWithLine = buffer[-1]=="\n"
+    lines = buffer.splitlines()
+
+    if endsWithLine:
+        buffer = ""
+    else:
+        buffer = lines[-1]
+        lines = lines[:-1]
+        
+    for line in lines:
+        words = line.split("\t")
+        try:
+            command = words[0]
+            if command=="downloadingManifest":
+                listener.downloadingManifest()
+            elif command=="downloadedManifest":
+                listener.downloadedManifest()
+            elif command=="setTotalSize":
+                listener.setTotalSize(int(words[1]), long(words[2]),
+                                      int(words[3]), int(words[4]))
+            elif command=="setDownloaded":
+                listener.setDownloaded(long(words[1]))
+            elif command=="startRenaming":
+                listener.startRenaming()
+            elif command=="renamed":
+                listener.renamed(words[1], int(words[2]))
+            elif command=="startRemoving":
+                listener.startRemoving()
+            elif command=="removed":
+                listener.removed(words[1], int(words[2]))
+            elif command=="writingManifest":
+                listener.writingManifest()
+            elif command=="done":
+                listener.done()
+            elif command=="failed":
+                listener.failed(words[1])
+        except Exception, e:
+            print >> sys.stderr, "Failed to parse line '%s': %s" % \
+                  (line, str(e))                
+
+    return buffer
+
+#------------------------------------------------------------------------------
+
+def sudoUpdate(directory, updateURL, listener, manifest):
+    """Perform the update via the mlxupdate program."""
+    manifestFD = None
+    manifestFile = None
+    serverSocket = None
+    mlxUpdateSocket = None
+    try:
+        (manifestFD, manifestFile) = tempfile.mkstemp()
+        f = os.fdopen(manifestFD, "wt")
+        try:
+            manifest.writeInto(f)
+        finally:
+            f.close()            
+            manifestFD = None
+
+        serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        serverSocket.bind(("127.0.0.1", 0))
+        (_host, port) = serverSocket.getsockname()
+        serverSocket.listen(1)
+
+
+        if os.name=="nt":
+            win32api.ShellExecute(0, "open", os.path.join(directory, "mlxupdate"),
+                                  str(port) + " " +  manifestFile, "", 1)
+        else:
+            process = subprocess.Popen([os.path.join(directory, "mlxupdate"),
+                                        str(port), manifestFile],
+                                       shell = os.name=="nt")
+
+        (mlxUpdateSocket, _) = serverSocket.accept()
+        serverSocket.close()
+        serverSocket = None
+    
+        buffer = ""
+        while True:
+            data = mlxUpdateSocket.recv(4096)
+            if not data:
+                break;
+
+            buffer += data
+            buffer = processMLXUpdate(buffer, listener)
+
+        mlxUpdateSocket.close()
+        mlxUpdateSocket = None
+
+        if os.name!="nt":
+            process.wait()
+        
+    except Exception, e:
+        print >> sys.stderr, "Failed updating:", str(e)
+        listener.failed(str(e))
+    finally:
+        if serverSocket is not None:
+            try:
+                serverSocket.close()
+            except:
+                pass
+        if mlxUpdateSocket is not None:
+            try:
+                mlxUpdateSocket.close()
+            except:
+                pass
+        if manifestFD is not None:
+            try:
+                os.close(manifestFD)
+            except:
+                pass
+        if manifestFile is not None:
+            try:
+                os.remove(manifestFile)
+            except:
+                pass
+        
+
+#------------------------------------------------------------------------------
+
+def update(directory, updateURL, listener, fromGUI = False):
+    """Perform the update."""
+    result = prepareUpdate(directory, updateURL, listener)
+    if result is None:
+        return
+
+    (manifest, updateManifest, modifiedAndNew, removed) = result        
+    localRemoved = getToremoveFiles(directory)
+
+    if not modifiedAndNew and not removed and not localRemoved:
+        listener.done()
+        return
+
+    if fromGUI and not isDirectoryWritable(directory):
+        if listener.needSudo():
+            sudoUpdate(directory, updateURL, listener, updateManifest)
+    else:
+        updateFiles(directory, updateURL, listener, updateManifest,
+                    modifiedAndNew, removed, localRemoved)
+
+#------------------------------------------------------------------------------
+
+def updateProcess():
+    """This is called in the child process, when we need a child process."""
+    clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    
+    clientSocket.connect(("127.0.0.1", int(sys.argv[1])))
+
+    config = Config()
+
+    directory = os.path.dirname(sys.argv[0])
+
+    manifest = readLocalManifest(directory)
+    
+    updateManifest = Manifest()
+    with open(sys.argv[2], "rt") as f:
+        updateManifest.readFrom(f)
+
+    (modifiedAndNew, removed) = manifest.compare(updateManifest)
+    localRemoved = getToremoveFiles(directory)
+
+    updateFiles(directory, config.updateURL,
+                ClientListener(clientSocket),
+                updateManifest, modifiedAndNew, removed, localRemoved)
+
+#------------------------------------------------------------------------------
+
+def buildManifest(directory):
+    """Build a manifest from the contents of the given directory, into the
+    given directory."""
+
+    manifestPath = os.path.join(directory, manifestName)
+    try:
+        os.remove(manifestPath)
+    except:
+        pass
+    
+    manifest = Manifest()
+    manifest.addFiles(directory, [])
+    with open(manifestPath, "wt") as f:
+        manifest.writeInto(f)
+
+#------------------------------------------------------------------------------
+
+# if __name__ == "__main__":
+#     manifest1 = Manifest()
+#     manifest1.addFile("file1.exe", 3242, "40398754589435345934")
+#     manifest1.addFile("dir/file2.zip", 45645, "347893245873456987")
+#     manifest1.addFile("dir/file3.txt", 123, "3432434534534534")
+
+#     with open("manifest1", "wt") as f:
+#         manifest1.writeInto(f)
+
+#     manifest2 = Manifest()
+#     manifest2.addFile("file1.exe", 4353, "390734659834756349876")
+#     manifest2.addFile("dir/file2.zip", 45645, "347893245873456987")
+#     manifest2.addFile("dir/file4.log", 2390, "56546546546546")
+
+#     with open("manifest2", "wt") as f:
+#         manifest2.writeInto(f)
+
+#     manifest1 = Manifest()
+#     with open("manifest1", "rt") as f:
+#         manifest1.readFrom(f)
+
+#     manifest2 = Manifest()
+#     with open("manifest2", "rt") as f:
+#         manifest2.readFrom(f)
+
+#     (modifiedAndNew, removed) = manifest1.compare(manifest2)
+
+#     for (path, size, sum) in modifiedAndNew:
+#         print "modified or new:", path, size, sum
+
+#     for path in removed:
+#         print "removed:", path
+
+#------------------------------------------------------------------------------
Index: src/mlx/update.py
===================================================================
--- src/mlx/update.py	(revision 39)
+++ 	(revision )
@@ -1,583 +1,0 @@
-# Module handling the download of updates
-
-#------------------------------------------------------------------------------
-
-from config import Config
-
-import os
-import sys
-import urllib2
-import tempfile
-import socket
-import subprocess
-import hashlib
-
-if os.name=="nt":
-    import win32api
-
-#------------------------------------------------------------------------------
-
-manifestName = "MLXMANIFEST"
-toremoveName = "toremove"
-
-#------------------------------------------------------------------------------
-
-class Manifest(object):
-    """The manifest of the files.
-
-    The manifest file consists of one line for each file. Each line contains 3
-    items separated by tabs:
-    - the path of the file relative to the distribution's root
-    - the size of the file
-    - the MD5 sum of the file."""
-    def __init__(self):
-        """Construct the manifest."""
-        self._files = {}
-
-    @property
-    def files(self):
-        """Get an iterator over the files.
-
-        Each file is returned as a 3-tuple with items as in the file."""
-        for (path, (size, sum)) in self._files.iteritems():
-            yield (path, size, sum)
-        
-    def addFile(self, path, size, sum):
-        """Add a file to the manifest."""
-        self._files[path] = (size, sum)
-
-    def addFiles(self, baseDirectory, subdirectory):
-        """Add the files in the given directory and subdirectories of it to the
-        manifest."""
-        directory = baseDirectory
-        for d in subdirectory: directory = os.path.join(directory, d)
-        
-        for entry in os.listdir(directory):
-            fullPath = os.path.join(directory, entry)
-            if os.path.isfile(fullPath):
-                size = os.stat(fullPath).st_size
-                sum = hashlib.md5()
-                with open(fullPath, "rb") as f:
-                    while True:
-                        data = f.read(4096)
-                        if data: sum.update(data)
-                        else: break
-                self.addFile("/".join(subdirectory + [entry]), size,
-                             sum.hexdigest())
-            elif os.path.isdir(fullPath):
-                self.addFiles(baseDirectory, subdirectory + [entry])
-                
-    def readFrom(self, file):
-        """Read a manifest from the given file object."""
-        for line in iter(file.readline, ""):
-            (path, size, sum) = line.strip().split("\t")
-            self._files[path] = (int(size), sum)
-
-    def writeInto(self, file):
-        """Write the manifest into the file at the given path."""
-        for (path, (size, sum)) in self._files.iteritems():
-            file.write("%s\t%d\t%s\n" % (path, size, sum))
-
-    def compare(self, other):
-        """Compare this manifest with the other one.
-
-        This returns a tuple of two lists:
-        - the files that are either different or are present in other, but not
-        here. Each file is returned as a 3-tuple as with other functions, the
-        size and sum are those of the new file.
-        - the paths of the files that are present here, but not in other."""
-        modifiedAndNew = []
-        for (path, otherSize, otherSum) in other.files:
-            if path in self._files:
-                (size, sum) = self._files[path]
-                if size!=otherSize or sum!=otherSum:
-                    modifiedAndNew.append((path, otherSize, otherSum))
-            else:
-                modifiedAndNew.append((path, otherSize, otherSum))
-
-        removed = [path for path in self._files if path not in other._files]
-        
-        return (modifiedAndNew, removed)
-            
-#------------------------------------------------------------------------------
-
-class ClientListener(object):
-    """A listener that sends any requests via a socket."""
-    def __init__(self, sock):
-        """Construct the listener."""
-        self._sock = sock
-
-    def downloadingManifest(self):
-        """Called when the downloading of the manifest has started."""
-        self._send(["downloadingManifest"])
-
-    def downloadedManifest(self):
-        """Called when the manifest has been downloaded."""
-        self._send(["downloadedManifest"])
-
-    def needSudo(self):
-        """Called when an admin-level program must be called to complete the
-        update.
-
-        This is not valid from a client, as that client is supposed to be the
-        admin-level program :)"""
-        assert False
-
-    def setTotalSize(self, numToModifyAndNew, totalSize, numToRemove,
-                     numToRemoveLocal):
-        """Called when starting the downloading of the files."""
-        self._send(["setTotalSize", str(numToModifyAndNew), str(totalSize),
-                    str(numToRemove), str(numToRemoveLocal)])
-
-    def setDownloaded(self, downloaded):
-        """Called periodically after downloading a certain amount of data."""
-        self._send(["setDownloaded", str(downloaded)])
-
-    def startRenaming(self):
-        """Called when the renaming of the downloaded files is started."""
-        self._send(["startRenaming"])
-
-    def renamed(self, path, count):
-        """Called when a file has been renamed."""
-        self._send(["renamed", path, str(count)])
-
-    def startRemoving(self):
-        """Called when the removing of files has started."""
-        self._send(["startRemoving"])
-
-    def removed(self, path, count):
-        """Called when a file has been removed."""
-        self._send(["removed", path, str(count)])
-
-    def writingManifest(self):
-        """Called when we have started writing the manifest."""
-        self._send(["writingManifest"])
-
-    def done(self):
-        """Called when the update has completed."""
-        self._send(["done"])
-
-    def failed(self, what):
-        """Called when something has failed."""
-        self._send(["failed", what])
-
-    def _send(self, words):
-        """Send the given words via the socket."""
-        self._sock.send("\t".join(words) + "\n")
-
-#------------------------------------------------------------------------------
-
-def readLocalManifest(directory):
-    """Read the local manifest from the given directory."""
-    manifestPath = os.path.join(directory, manifestName)
-
-    manifest = Manifest()
-    try:
-        with open(manifestPath, "rt") as f:
-            manifest.readFrom(f)
-    except Exception, e:
-        print "Error reading the manifest, ignoring:", str(e)
-        manifest = Manifest()
-
-    return manifest
-
-#------------------------------------------------------------------------------
-
-def prepareUpdate(directory, updateURL, listener):
-    """Prepare the update by downloading the manifest and comparing it with the
-    local one."""
-    manifest = readLocalManifest(directory)
-        
-    updateURL += "/" + os.name
-
-    listener.downloadingManifest()
-    f = None
-    try:
-        updateManifest = Manifest()
-        f= urllib2.urlopen(updateURL + "/" + manifestName)
-        updateManifest.readFrom(f)
-
-    except Exception, e:
-        print >> sys.stderr, "Error downloading manifest:", str(e)
-        listener.failed(str(e))
-        return None
-    finally:
-        if f is not None: f.close()
-
-    listener.downloadedManifest()
-
-    (modifiedAndNew, removed) = manifest.compare(updateManifest)
-
-    return (manifest, updateManifest, modifiedAndNew, removed)
-
-#------------------------------------------------------------------------------
-
-def getToremoveFiles(directory):
-    """Add the files to remove from the toremove directory."""
-    toremove = []
-    toremoveDirectory = os.path.join(directory, toremoveName)
-    if os.path.isdir(toremoveDirectory):
-        for entry in os.listdir(toremoveDirectory):
-            toremove.append(os.path.join(toremoveName, entry))
-    return toremove
-
-#------------------------------------------------------------------------------
-
-def createLocalPath(directory, path):
-    """Create a local path from the given manifest path."""
-    localPath = directory
-    for element in path.split("/"):
-        localPath = os.path.join(localPath, element)
-    return localPath
-
-#------------------------------------------------------------------------------
-
-def getToremoveDir(toremoveDir, directory):
-    """Get the path of the directory that will contain the files that are to be
-    removed."""
-    if toremoveDir is None:
-        toremoveDir = os.path.join(directory, toremoveName)
-        try:
-            os.mkdir(toremoveDir)
-        except:
-            pass
-
-    return toremoveDir
-
-#------------------------------------------------------------------------------
-
-def removeFile(toremoveDir, directory, path):
-    """Remove the file at the given path or store it in a temporary directory.
-
-    If the removal of the file fails, it will be stored in a temporary
-    directory. This is useful for files thay may be open and cannot be removed
-    right away."""
-    try:
-        os.remove(path)
-    except:
-        try:
-            sum = hashlib.md5()
-            sum.update(path)
-            toremoveDir = getToremoveDir(toremoveDir, directory)
-            os.rename(path, os.path.join(toremoveDir, sum.hexdigest()))
-        except Exception, e:
-            print "Cannot remove file " + path + ": " + str(e)    
-
-#------------------------------------------------------------------------------
-
-def updateFiles(directory, updateURL, listener,
-                manifest, modifiedAndNew, removed, localRemoved):
-    """Update the files according to the given information."""
-    totalSize = 0
-    for (path, size, sum) in modifiedAndNew:
-        totalSize += size
-
-    listener.setTotalSize(len(modifiedAndNew), totalSize,
-                          len(removed), len(localRemoved))
-
-    downloaded = 0
-    fin = None
-    toremoveDir = None
-
-    try:        
-        updateURL += "/" + os.name
-
-        for (path, size, sum) in modifiedAndNew:
-            targetFile = createLocalPath(directory, path)
-            targetFile += "."
-            targetFile += sum
-
-            targetDirectory = os.path.dirname(targetFile)
-            if not os.path.isdir(targetDirectory):
-                os.makedirs(targetDirectory)
-                
-            with open(targetFile, "wb") as fout:
-                fin = urllib2.urlopen(updateURL + "/" + path)
-                while True:
-                    data = fin.read(4096)
-                    if not data:
-                        break
-                    fout.write(data)
-                    downloaded += len(data)
-                    listener.setDownloaded(downloaded)
-                fin.close()
-                fin = None
-                
-        listener.startRenaming()
-        count = 0
-        for (path, size, sum) in modifiedAndNew:
-            targetFile = createLocalPath(directory, path)
-
-            downloadedFile = targetFile + "." + sum
-            if os.name=="nt" and os.path.isfile(targetFile):
-                removeFile(toremoveDir, directory, targetFile)
-            os.rename(downloadedFile, targetFile)
-            count += 1
-            listener.renamed(path, count)
-        
-        listener.startRemoving()
-        count = 0
-        removed += localRemoved
-        removed.sort(reverse = True)
-        for path in removed:            
-            removePath = createLocalPath(directory, path)
-            removeFile(toremoveDir, directory, removePath)
-
-            removeDirectory = os.path.dirname(removePath)
-            try:
-                os.removedirs(removeDirectory)
-            except:
-                pass
-
-            count += 1
-            listener.removed(path, count)
-
-        listener.writingManifest()
-
-        manifestPath = os.path.join(directory, manifestName)
-        with open(manifestPath, "wt") as f:
-            manifest.writeInto(f)
-        
-        listener.done()
-    except Exception, e:
-        print >> sys.stderr, "Error:", str(e)
-        listener.failed(str(e))        
-
-#------------------------------------------------------------------------------
-
-def isDirectoryWritable(directory):
-    """Determine if the given directory can be written."""
-    checkFile = os.path.join(directory, "writable.chk")
-    try:
-        f = open(checkFile, "wt")
-        f.close()
-        return True
-    except Exception, e:
-        return False
-    finally:
-        try:
-            os.remove(checkFile)
-        except:
-            pass
-
-#------------------------------------------------------------------------------
-
-def processMLXUpdate(buffer, listener):
-    """Process the given buffer supposedly containing a list of commands."""
-    endsWithLine = buffer[-1]=="\n"
-    lines = buffer.splitlines()
-
-    if endsWithLine:
-        buffer = ""
-    else:
-        buffer = lines[-1]
-        lines = lines[:-1]
-        
-    for line in lines:
-        words = line.split("\t")
-        try:
-            command = words[0]
-            if command=="downloadingManifest":
-                listener.downloadingManifest()
-            elif command=="downloadedManifest":
-                listener.downloadedManifest()
-            elif command=="setTotalSize":
-                listener.setTotalSize(int(words[1]), long(words[2]),
-                                      int(words[3]), int(words[4]))
-            elif command=="setDownloaded":
-                listener.setDownloaded(long(words[1]))
-            elif command=="startRenaming":
-                listener.startRenaming()
-            elif command=="renamed":
-                listener.renamed(words[1], int(words[2]))
-            elif command=="startRemoving":
-                listener.startRemoving()
-            elif command=="removed":
-                listener.removed(words[1], int(words[2]))
-            elif command=="writingManifest":
-                listener.writingManifest()
-            elif command=="done":
-                listener.done()
-            elif command=="failed":
-                listener.failed(words[1])
-        except Exception, e:
-            print >> sys.stderr, "Failed to parse line '%s': %s" % \
-                  (line, str(e))                
-
-    return buffer
-
-#------------------------------------------------------------------------------
-
-def sudoUpdate(directory, updateURL, listener, manifest):
-    """Perform the update via the mlxupdate program."""
-    manifestFD = None
-    manifestFile = None
-    serverSocket = None
-    mlxUpdateSocket = None
-    try:
-        (manifestFD, manifestFile) = tempfile.mkstemp()
-        f = os.fdopen(manifestFD, "wt")
-        try:
-            manifest.writeInto(f)
-        finally:
-            f.close()            
-            manifestFD = None
-
-        serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        serverSocket.bind(("127.0.0.1", 0))
-        (_host, port) = serverSocket.getsockname()
-        serverSocket.listen(1)
-
-
-        if os.name=="nt":
-            win32api.ShellExecute(0, "open", os.path.join(directory, "mlxupdate"),
-                                  str(port) + " " +  manifestFile, "", 1)
-        else:
-            process = subprocess.Popen([os.path.join(directory, "mlxupdate"),
-                                        str(port), manifestFile],
-                                       shell = os.name=="nt")
-
-        (mlxUpdateSocket, _) = serverSocket.accept()
-        serverSocket.close()
-        serverSocket = None
-    
-        buffer = ""
-        while True:
-            data = mlxUpdateSocket.recv(4096)
-            if not data:
-                break;
-
-            buffer += data
-            buffer = processMLXUpdate(buffer, listener)
-
-        mlxUpdateSocket.close()
-        mlxUpdateSocket = None
-
-        if os.name!="nt":
-            process.wait()
-        
-    except Exception, e:
-        print >> sys.stderr, "Failed updating:", str(e)
-        listener.failed(str(e))
-    finally:
-        if serverSocket is not None:
-            try:
-                serverSocket.close()
-            except:
-                pass
-        if mlxUpdateSocket is not None:
-            try:
-                mlxUpdateSocket.close()
-            except:
-                pass
-        if manifestFD is not None:
-            try:
-                os.close(manifestFD)
-            except:
-                pass
-        if manifestFile is not None:
-            try:
-                os.remove(manifestFile)
-            except:
-                pass
-        
-
-#------------------------------------------------------------------------------
-
-def update(directory, updateURL, listener, fromGUI = False):
-    """Perform the update."""
-    result = prepareUpdate(directory, updateURL, listener)
-    if result is None:
-        return
-
-    (manifest, updateManifest, modifiedAndNew, removed) = result        
-    localRemoved = getToremoveFiles(directory)
-
-    if not modifiedAndNew and not removed and not localRemoved:
-        listener.done()
-        return
-
-    if fromGUI and not isDirectoryWritable(directory):
-        if listener.needSudo():
-            sudoUpdate(directory, updateURL, listener, updateManifest)
-    else:
-        updateFiles(directory, updateURL, listener, updateManifest,
-                    modifiedAndNew, removed, localRemoved)
-
-#------------------------------------------------------------------------------
-
-def updateProcess():
-    """This is called in the child process, when we need a child process."""
-    clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    
-    clientSocket.connect(("127.0.0.1", int(sys.argv[1])))
-
-    config = Config()
-
-    directory = os.path.dirname(sys.argv[0])
-
-    manifest = readLocalManifest(directory)
-    
-    updateManifest = Manifest()
-    with open(sys.argv[2], "rt") as f:
-        updateManifest.readFrom(f)
-
-    (modifiedAndNew, removed) = manifest.compare(updateManifest)
-    localRemoved = getToremoveFiles(directory)
-
-    updateFiles(directory, config.updateURL,
-                ClientListener(clientSocket),
-                updateManifest, modifiedAndNew, removed, localRemoved)
-
-#------------------------------------------------------------------------------
-
-def buildManifest(directory):
-    """Build a manifest from the contents of the given directory, into the
-    given directory."""
-
-    manifestPath = os.path.join(directory, manifestName)
-    try:
-        os.remove(manifestPath)
-    except:
-        pass
-    
-    manifest = Manifest()
-    manifest.addFiles(directory, [])
-    with open(manifestPath, "wt") as f:
-        manifest.writeInto(f)
-
-#------------------------------------------------------------------------------
-
-# if __name__ == "__main__":
-#     manifest1 = Manifest()
-#     manifest1.addFile("file1.exe", 3242, "40398754589435345934")
-#     manifest1.addFile("dir/file2.zip", 45645, "347893245873456987")
-#     manifest1.addFile("dir/file3.txt", 123, "3432434534534534")
-
-#     with open("manifest1", "wt") as f:
-#         manifest1.writeInto(f)
-
-#     manifest2 = Manifest()
-#     manifest2.addFile("file1.exe", 4353, "390734659834756349876")
-#     manifest2.addFile("dir/file2.zip", 45645, "347893245873456987")
-#     manifest2.addFile("dir/file4.log", 2390, "56546546546546")
-
-#     with open("manifest2", "wt") as f:
-#         manifest2.writeInto(f)
-
-#     manifest1 = Manifest()
-#     with open("manifest1", "rt") as f:
-#         manifest1.readFrom(f)
-
-#     manifest2 = Manifest()
-#     with open("manifest2", "rt") as f:
-#         manifest2.readFrom(f)
-
-#     (modifiedAndNew, removed) = manifest1.compare(manifest2)
-
-#     for (path, size, sum) in modifiedAndNew:
-#         print "modified or new:", path, size, sum
-
-#     for path in removed:
-#         print "removed:", path
-
-#------------------------------------------------------------------------------
Index: src/mlx/util.py
===================================================================
--- src/mlx/util.py	(revision 27)
+++ src/mlx/util.py	(revision 27)
@@ -0,0 +1,2 @@
+# Various utilities
+
Index: src/mlx/util.py
===================================================================
--- src/mlx/util.py	(revision 27)
+++ 	(revision )
@@ -1,2 +1,0 @@
-# Various utilities
-
Index: src/mlx/web.py
===================================================================
--- src/mlx/web.py	(revision 74)
+++ src/mlx/web.py	(revision 74)
@@ -0,0 +1,559 @@
+# Interface towards the websites used
+
+#------------------------------------------------------------------------------
+
+import const
+
+import threading
+import sys
+import urllib
+import urllib2
+import hashlib
+import time
+import datetime
+import codecs
+import xml.sax
+
+#---------------------------------------------------------------------------------------
+
+def readline(f):
+    """Read a line from the given file.
+
+    The line is stripped and empty lines are discarded."""
+    while True:
+        line = f.readline()
+        if not line: return ""
+        line = line.strip()
+        if line:
+            return line
+
+#---------------------------------------------------------------------------------------
+
+class BookedFlight(object):
+    """A flight that was booked."""
+    TYPECODE2TYPE = { "736" : const.AIRCRAFT_B736,
+                      "73G" : const.AIRCRAFT_B737,
+                      "738" : const.AIRCRAFT_B738,
+                      "733" : const.AIRCRAFT_B733,
+                      "734" : const.AIRCRAFT_B734,
+                      "735" : const.AIRCRAFT_B735,
+                      "DH4" : const.AIRCRAFT_DH8D,
+                      "762" : const.AIRCRAFT_B762,
+                      "763" : const.AIRCRAFT_B763,
+                      "CR2" : const.AIRCRAFT_CRJ2,
+                      "F70" : const.AIRCRAFT_F70,
+                      "LI2" : const.AIRCRAFT_DC3,
+                      "TU3" : const.AIRCRAFT_T134,
+                      "TU5" : const.AIRCRAFT_T154,
+                      "YK4" : const.AIRCRAFT_YK40 }
+
+    def __init__(self, id, f):
+        """Construct a booked flight with the given ID.
+
+        The rest of the data is read from the given file."""
+
+        self.id = id
+        self.callsign = readline(f)
+
+        date = readline(f)
+
+        self.departureICAO = readline(f)
+        self.arrivalICAO = readline(f)
+
+        self._readAircraftType(f)
+        self.tailNumber = readline(f)
+        self.numPassengers = int(readline(f))
+        self.numCrew = int(readline(f))
+        self.bagWeight = int(readline(f))
+        self.cargoWeight = int(readline(f))
+        self.mailWeight = int(readline(f))
+        self.route = readline(f)
+
+        departureTime = readline(f)
+        self.departureTime = datetime.datetime.strptime(date + " " + departureTime,
+                                                        "%Y-%m-%d %H:%M:%S")
+                                               
+        arrivalTime = readline(f)
+        self.arrivalTime = datetime.datetime.strptime(date + " " + arrivalTime,
+                                                      "%Y-%m-%d %H:%M:%S")
+
+        if not readline(f)==".NEXT.":
+            raise Exception("Invalid line in flight data")
+
+    def _readAircraftType(self, f):
+        """Read the aircraft type from the given file."""
+        line = readline(f)
+        typeCode = line[:3]
+        if typeCode in self.TYPECODE2TYPE:
+            self.aircraftType = self.TYPECODE2TYPE[typeCode]
+        else:
+            raise Exception("Invalid aircraft type code: '" + typeCode + "'")
+        
+    def __repr__(self):
+        """Get a representation of the flight."""
+        s = "<Flight: %s-%s, %s, %s-%s," % (self.departureICAO,
+                                           self.arrivalICAO,
+                                           self.route,
+                                           self.departureTime, self.arrivalTime)
+        s += " %d %s," % (self.aircraftType, self.tailNumber)
+        s += " pax=%d, crew=%d, bag=%d, cargo=%d, mail=%d" % \
+             (self.numPassengers, self.numCrew,
+              self.bagWeight, self.cargoWeight, self.mailWeight)
+        s += ">"
+        return s
+        
+#------------------------------------------------------------------------------
+
+class Plane(object):
+    """Information about an airplane in the fleet."""
+    def __init__(self, s):
+        """Build a plane info based on the given string.
+
+        The string consists of three, space-separated fields.
+        The first field is the tail number, the second field is the gate
+        number, the third field is the plane's status as a character."""
+        try:
+            words = s.split(" ")
+            tailNumber = words[0]
+            self.tailNumber = tailNumber
+
+            status = words[2] if len(words)>2 else None
+            self.status = const.PLANE_HOME if status=="H" else \
+                          const.PLANE_AWAY if status=="A" else \
+                          const.PLANE_PARKING if status=="P" else \
+                          const.PLANE_UNKNOWN
+
+            gateNumber = words[1] if len(words)>1 else ""
+            self.gateNumber = gateNumber if gateNumber else None
+
+        except:
+            print >> sys.stderr, "Plane string is invalid: '" + s + "'"
+            self.tailNumber = None
+
+    def __repr__(self):
+        """Get the representation of the plane object."""
+        s = "<Plane: %s %s" % (self.tailNumber, 
+                               "home" if self.status==const.PLANE_HOME else \
+                               "away" if self.status==const.PLANE_AWAY else \
+                               "parking" if self.status==const.PLANE_PARKING \
+                               else "unknown")
+        if self.gateNumber is not None:
+            s += " (gate " + self.gateNumber + ")"
+        s += ">"
+        return s
+        
+
+#------------------------------------------------------------------------------
+
+class Fleet(object):
+    """Information about the whole fleet."""
+    def __init__(self, f):
+        """Construct the fleet information by reading the given file object."""
+        self._planes = {}
+        while True:
+            line = readline(f)
+            if not line or line == "#END": break
+
+            plane = Plane(line)
+            if plane.tailNumber is not None:
+                self._planes[plane.tailNumber] = plane        
+
+    def isGateConflicting(self, plane):
+        """Check if the gate of the given plane conflicts with another plane's
+        position."""
+        for p in self._planes.itervalues():
+            if p.tailNumber!=plane.tailNumber and \
+               p.status==const.PLANE_HOME and \
+               p.gateNumber==plane.gateNumber:
+                return True
+
+        return False
+
+    def getOccupiedGateNumbers(self):
+        """Get a set containing the numbers of the gates occupied by planes."""
+        gateNumbers = set()
+        for p in self._planes.itervalues():
+            if p.status==const.PLANE_HOME and p.gateNumber:
+                gateNumbers.add(p.gateNumber)
+        return gateNumbers
+        
+    def __getitem__(self, tailNumber):
+        """Get the plane with the given tail number.
+
+        If the plane is not in the fleet, None is returned."""
+        return self._planes[tailNumber] if tailNumber in self._planes else None
+
+    def __repr__(self):
+        """Get the representation of the fleet object."""
+        return self._planes.__repr__()
+        
+#------------------------------------------------------------------------------
+
+class NOTAM(object):
+    """A NOTAM for an airport."""
+    def __init__(self, begin, notice, end = None, permanent = False,
+                 repeatCycle = None):
+        """Construct the NOTAM."""
+        self.begin = begin
+        self.notice = notice
+        self.end = end
+        self.permanent = permanent
+        self.repeatCycle = None
+
+    def __repr__(self):
+        """Get the representation of the NOTAM."""
+        s = "<NOTAM " + str(self.begin)
+        if self.end:
+            s += " - " + str(self.end)
+        elif self.permanent:
+            s += " - PERMANENT"
+        if self.repeatCycle:
+            s += " (" + self.repeatCycle + ")"
+        s += ": " + self.notice
+        s += ">"
+        return s
+    
+#------------------------------------------------------------------------------
+
+class NOTAMHandler(xml.sax.handler.ContentHandler):
+    """A handler for the NOTAM database."""
+    def __init__(self, airportICAOs):
+        """Construct the handler for the airports with the given ICAO code."""
+        self._notams = {}
+        for icao in airportICAOs:
+            self._notams[icao] = []
+
+    def startElement(self, name, attrs):
+        """Start an element."""
+        if name!="notam" or \
+           "A" not in attrs or not attrs["A"] or \
+           "B" not in attrs or not attrs["B"] or \
+           "E" not in attrs or not attrs["E"]:
+            return
+        
+        icao = attrs["A"]
+        if icao not in self._notams:
+            return
+        
+        begin = datetime.datetime.strptime(attrs["B"], "%Y-%m-%d %H:%M:%S")
+
+        c = attrs["C"] if "C" in attrs else None
+        end = datetime.datetime.strptime(c, "%Y-%m-%d %H:%M:%S") if c else None
+        
+        permanent = attrs["C_flag"]=="PERM" if "C_flag" in attrs else False
+        
+        repeatCycle = attrs["D"] if "D" in attrs else None
+
+        self._notams[icao].append(NOTAM(begin, attrs["E"], end = end,
+                                        permanent = permanent,
+                                        repeatCycle = repeatCycle))
+
+    def get(self, icao):
+        """Get the NOTAMs for the given ICAO code."""
+        return self._notams[icao] if icao in self._notams else []
+
+#------------------------------------------------------------------------------
+
+class Result(object):
+    """A result object.
+
+    An instance of this filled with the appropriate data is passed to the
+    callback function on each request."""
+
+    def __repr__(self):
+        """Get a representation of the result."""
+        s = "<Result:"
+        for (key, value) in self.__dict__.iteritems():
+            s += " " + key + "=" + unicode(value)
+        s += ">"
+        return s
+
+#------------------------------------------------------------------------------
+
+class Request(object):
+    """Base class for requests.
+
+    It handles any exceptions and the calling of the callback.
+
+    If an exception occurs during processing, the callback is called with
+    the two parameters: a boolean value of False, and the exception object.
+
+    If no exception occurs, the callback is called with True and the return
+    value of the run() function.
+
+    If the callback function throws an exception, that is caught and logged
+    to the debug log."""
+    def __init__(self, callback):
+        """Construct the request."""
+        self._callback = callback
+
+    def perform(self):
+        """Perform the request.
+
+        The object's run() function is called. If it throws an exception,
+        the callback is called with False, and the exception. Otherwise the
+        callback is called with True and the return value of the run()
+        function. Any exceptions thrown by the callback are caught and
+        reported."""
+        try:
+            result = self.run()
+            returned = True
+        except Exception, e:
+            result = e
+            returned = False
+
+        try:
+            self._callback(returned, result)
+        except Exception, e:
+            print >> sys.stderr, "web.Handler.Request.perform: callback throwed an exception: " + str(e)
+
+#------------------------------------------------------------------------------
+
+class Login(Request):
+    """A login request."""
+    iso88592decoder = codecs.getdecoder("iso-8859-2")
+    
+    def __init__(self, callback, pilotID, password):
+        """Construct the login request with the given pilot ID and
+        password."""
+        super(Login, self).__init__(callback)
+
+        self._pilotID = pilotID
+        self._password = password
+
+    def run(self):
+        """Perform the login request."""
+        md5 = hashlib.md5()
+        md5.update(self._pilotID)
+        pilotID = md5.hexdigest()
+        
+        md5 = hashlib.md5()
+        md5.update(self._password)
+        password = md5.hexdigest()
+
+        url = "http://www.virtualairlines.hu/leker2.php?pid=%s&psw=%s" % \
+              (pilotID, password)
+
+        result = Result()
+
+        f = urllib2.urlopen(url)
+
+        status = readline(f)
+        result.loggedIn = status == ".OK."
+
+        if result.loggedIn:
+            result.pilotName = self.iso88592decoder(readline(f))[0]
+            result.exams = readline(f)
+            result.flights = []
+
+            while True:
+                line = readline(f)
+                if not line or line == "#ENDPIREP": break
+
+                flight = BookedFlight(line, f)
+                result.flights.append(flight)
+
+            result.flights.sort(cmp = lambda flight1, flight2:
+                                cmp(flight1.departureTime,
+                                    flight2.departureTime))
+
+        f.close()
+
+        return result
+        
+#------------------------------------------------------------------------------
+
+class GetFleet(Request):
+    """Request to get the fleet from the website."""
+    
+    def __init__(self, callback):
+        """Construct the fleet request."""
+        super(GetFleet, self).__init__(callback)
+
+    def run(self):
+        """Perform the login request."""
+        url = "http://www.virtualairlines.hu/onlinegates_get.php"
+
+        f = urllib2.urlopen(url)
+        result = Result()
+        result.fleet = Fleet(f)
+        f.close()
+        
+        return result
+
+#------------------------------------------------------------------------------
+
+class UpdatePlane(Request):
+    """Update the status of one of the planes in the fleet."""
+    def __init__(self, callback, tailNumber, status, gateNumber = None):
+        """Construct the request."""
+        super(UpdatePlane, self).__init__(callback)
+        self._tailNumber = tailNumber
+        self._status = status
+        self._gateNumber = gateNumber
+
+    def run(self):
+        """Perform the plane update."""
+        url = "http://www.virtualairlines.hu/onlinegates_set.php"
+
+        status = "H" if self._status==const.PLANE_HOME else \
+                 "A" if self._status==const.PLANE_AWAY else \
+                 "P" if self._status==const.PLANE_PARKING else ""
+
+        gateNumber = self._gateNumber if self._gateNumber else ""
+
+        data = urllib.urlencode([("lajstrom", self._tailNumber),
+                                 ("status", status),
+                                 ("kapu", gateNumber)])
+        
+        f = urllib2.urlopen(url, data)
+        line = readline(f)
+        
+        result = Result()
+        result.success = line == "OK"
+
+        return result
+            
+#------------------------------------------------------------------------------
+
+class GetNOTAMs(Request):
+    """Get the NOTAMs from EURoutePro and select the ones we are interested
+    in."""
+    def __init__(self, callback, departureICAO, arrivalICAO):
+        """Construct the request for the given airports."""
+        super(GetNOTAMs, self).__init__(callback)
+        self._departureICAO = departureICAO
+        self._arrivalICAO = arrivalICAO
+
+    def run(self):
+        """Perform the retrieval of the NOTAMs."""
+        xmlParser = xml.sax.make_parser()
+        notamHandler = NOTAMHandler([self._departureICAO, self._arrivalICAO])
+        xmlParser.setContentHandler(notamHandler)
+
+        url = "http://notams.euroutepro.com/notams.xml"
+
+        f = urllib2.urlopen(url)
+        try:
+            xmlParser.parse(f)
+        finally:
+            f.close()
+
+        result = Result()
+        result.departureNOTAMs = notamHandler.get(self._departureICAO)
+        result.arrivalNOTAMs = notamHandler.get(self._arrivalICAO)
+
+        return result
+
+#------------------------------------------------------------------------------
+
+class GetMETARs(Request):
+    """Get the METARs from the NOAA website for certain airport ICAOs."""    
+
+    def __init__(self, callback, airports):
+        """Construct the request for the given airports."""
+        super(GetMETARs, self).__init__(callback)
+        self._airports = airports
+
+    def run(self):
+        """Perform the retrieval opf the METARs."""
+        url = "http://www.aviationweather.gov/adds/dataserver_current/httpparam?"
+        data = urllib.urlencode([ ("dataSource" , "metars"),
+                                  ("requestType",  "retrieve"), 
+                                  ("format", "csv"),
+                                  ("stationString", " ".join(self._airports)),
+                                  ("hoursBeforeNow", "24"),
+                                  ("mostRecentForEachStation", "constraint")])
+        url += data
+        f = urllib2.urlopen(url)
+        try:
+            result = Result()
+            result.metars = {}
+            for line in iter(f.readline, ""):
+                if len(line)>5 and line[4]==' ':
+                    icao = line[0:4]
+                    if icao in self._airports:                        
+                        result.metars[icao] = line.strip().split(",")[0]
+        finally:
+            f.close()
+
+        return result
+
+#------------------------------------------------------------------------------
+
+class Handler(threading.Thread):
+    """The handler for the web services.
+
+    It can process one request at a time. The results are passed to a callback
+    function."""
+    def __init__(self):
+        """Construct the handler."""
+        super(Handler, self).__init__()
+
+        self._requests = []
+        self._requestCondition = threading.Condition()
+
+        self.daemon = True
+
+    def login(self, callback, pilotID, password):
+        """Enqueue a login request."""
+        self._addRequest(Login(callback, pilotID, password))
+
+    def getFleet(self, callback):
+        """Enqueue a fleet retrieval request."""
+        self._addRequest(GetFleet(callback))
+        
+    def updatePlane(self, callback, tailNumber, status, gateNumber = None):
+        """Update the status of the given plane."""        
+        self._addRequest(UpdatePlane(callback, tailNumber, status, gateNumber))
+
+    def getNOTAMs(self, callback, departureICAO, arrivalICAO):
+        """Get the NOTAMs for the given two airports."""
+        self._addRequest(GetNOTAMs(callback, departureICAO, arrivalICAO))
+        
+    def getMETARs(self, callback, airports):
+        """Get the METARs for the given airports."""
+        self._addRequest(GetMETARs(callback, airports))
+        
+    def run(self):
+        """Process the requests."""
+        while True:
+            with self._requestCondition:
+                while not self._requests:
+                    self._requestCondition.wait()
+                request = self._requests[0]
+                del self._requests[0]
+
+            request.perform()
+    
+    def _addRequest(self, request):
+        """Add the given request to the queue."""
+        with self._requestCondition:
+            self._requests.append(request)
+            self._requestCondition.notify()
+
+#------------------------------------------------------------------------------
+
+if __name__ == "__main__":
+    import time
+    
+    def callback(returned, result):
+        print returned, unicode(result)
+        
+    handler = Handler()
+    handler.start()
+
+    #handler.login(callback, "P096", "V5fwj")
+    #handler.getFleet(callback)
+    # Plane: HA-LEG home (gate 67)
+    #handler.updatePlane(callback, "HA-LQC", const.PLANE_AWAY, "72")
+    #time.sleep(3)    
+    #handler.getFleet(callback)
+    #time.sleep(3)
+
+    #handler.getNOTAMs(callback, "LHBP", "EPWA")
+    handler.getMETARs(callback, ["LHBP", "EPWA"])
+    time.sleep(5)
+    
+
+#------------------------------------------------------------------------------
Index: src/mlx/web.py
===================================================================
--- src/mlx/web.py	(revision 74)
+++ 	(revision )
@@ -1,559 +1,0 @@
-# Interface towards the websites used
-
-#------------------------------------------------------------------------------
-
-import const
-
-import threading
-import sys
-import urllib
-import urllib2
-import hashlib
-import time
-import datetime
-import codecs
-import xml.sax
-
-#---------------------------------------------------------------------------------------
-
-def readline(f):
-    """Read a line from the given file.
-
-    The line is stripped and empty lines are discarded."""
-    while True:
-        line = f.readline()
-        if not line: return ""
-        line = line.strip()
-        if line:
-            return line
-
-#---------------------------------------------------------------------------------------
-
-class BookedFlight(object):
-    """A flight that was booked."""
-    TYPECODE2TYPE = { "736" : const.AIRCRAFT_B736,
-                      "73G" : const.AIRCRAFT_B737,
-                      "738" : const.AIRCRAFT_B738,
-                      "733" : const.AIRCRAFT_B733,
-                      "734" : const.AIRCRAFT_B734,
-                      "735" : const.AIRCRAFT_B735,
-                      "DH4" : const.AIRCRAFT_DH8D,
-                      "762" : const.AIRCRAFT_B762,
-                      "763" : const.AIRCRAFT_B763,
-                      "CR2" : const.AIRCRAFT_CRJ2,
-                      "F70" : const.AIRCRAFT_F70,
-                      "LI2" : const.AIRCRAFT_DC3,
-                      "TU3" : const.AIRCRAFT_T134,
-                      "TU5" : const.AIRCRAFT_T154,
-                      "YK4" : const.AIRCRAFT_YK40 }
-
-    def __init__(self, id, f):
-        """Construct a booked flight with the given ID.
-
-        The rest of the data is read from the given file."""
-
-        self.id = id
-        self.callsign = readline(f)
-
-        date = readline(f)
-
-        self.departureICAO = readline(f)
-        self.arrivalICAO = readline(f)
-
-        self._readAircraftType(f)
-        self.tailNumber = readline(f)
-        self.numPassengers = int(readline(f))
-        self.numCrew = int(readline(f))
-        self.bagWeight = int(readline(f))
-        self.cargoWeight = int(readline(f))
-        self.mailWeight = int(readline(f))
-        self.route = readline(f)
-
-        departureTime = readline(f)
-        self.departureTime = datetime.datetime.strptime(date + " " + departureTime,
-                                                        "%Y-%m-%d %H:%M:%S")
-                                               
-        arrivalTime = readline(f)
-        self.arrivalTime = datetime.datetime.strptime(date + " " + arrivalTime,
-                                                      "%Y-%m-%d %H:%M:%S")
-
-        if not readline(f)==".NEXT.":
-            raise Exception("Invalid line in flight data")
-
-    def _readAircraftType(self, f):
-        """Read the aircraft type from the given file."""
-        line = readline(f)
-        typeCode = line[:3]
-        if typeCode in self.TYPECODE2TYPE:
-            self.aircraftType = self.TYPECODE2TYPE[typeCode]
-        else:
-            raise Exception("Invalid aircraft type code: '" + typeCode + "'")
-        
-    def __repr__(self):
-        """Get a representation of the flight."""
-        s = "<Flight: %s-%s, %s, %s-%s," % (self.departureICAO,
-                                           self.arrivalICAO,
-                                           self.route,
-                                           self.departureTime, self.arrivalTime)
-        s += " %d %s," % (self.aircraftType, self.tailNumber)
-        s += " pax=%d, crew=%d, bag=%d, cargo=%d, mail=%d" % \
-             (self.numPassengers, self.numCrew,
-              self.bagWeight, self.cargoWeight, self.mailWeight)
-        s += ">"
-        return s
-        
-#------------------------------------------------------------------------------
-
-class Plane(object):
-    """Information about an airplane in the fleet."""
-    def __init__(self, s):
-        """Build a plane info based on the given string.
-
-        The string consists of three, space-separated fields.
-        The first field is the tail number, the second field is the gate
-        number, the third field is the plane's status as a character."""
-        try:
-            words = s.split(" ")
-            tailNumber = words[0]
-            self.tailNumber = tailNumber
-
-            status = words[2] if len(words)>2 else None
-            self.status = const.PLANE_HOME if status=="H" else \
-                          const.PLANE_AWAY if status=="A" else \
-                          const.PLANE_PARKING if status=="P" else \
-                          const.PLANE_UNKNOWN
-
-            gateNumber = words[1] if len(words)>1 else ""
-            self.gateNumber = gateNumber if gateNumber else None
-
-        except:
-            print >> sys.stderr, "Plane string is invalid: '" + s + "'"
-            self.tailNumber = None
-
-    def __repr__(self):
-        """Get the representation of the plane object."""
-        s = "<Plane: %s %s" % (self.tailNumber, 
-                               "home" if self.status==const.PLANE_HOME else \
-                               "away" if self.status==const.PLANE_AWAY else \
-                               "parking" if self.status==const.PLANE_PARKING \
-                               else "unknown")
-        if self.gateNumber is not None:
-            s += " (gate " + self.gateNumber + ")"
-        s += ">"
-        return s
-        
-
-#------------------------------------------------------------------------------
-
-class Fleet(object):
-    """Information about the whole fleet."""
-    def __init__(self, f):
-        """Construct the fleet information by reading the given file object."""
-        self._planes = {}
-        while True:
-            line = readline(f)
-            if not line or line == "#END": break
-
-            plane = Plane(line)
-            if plane.tailNumber is not None:
-                self._planes[plane.tailNumber] = plane        
-
-    def isGateConflicting(self, plane):
-        """Check if the gate of the given plane conflicts with another plane's
-        position."""
-        for p in self._planes.itervalues():
-            if p.tailNumber!=plane.tailNumber and \
-               p.status==const.PLANE_HOME and \
-               p.gateNumber==plane.gateNumber:
-                return True
-
-        return False
-
-    def getOccupiedGateNumbers(self):
-        """Get a set containing the numbers of the gates occupied by planes."""
-        gateNumbers = set()
-        for p in self._planes.itervalues():
-            if p.status==const.PLANE_HOME and p.gateNumber:
-                gateNumbers.add(p.gateNumber)
-        return gateNumbers
-        
-    def __getitem__(self, tailNumber):
-        """Get the plane with the given tail number.
-
-        If the plane is not in the fleet, None is returned."""
-        return self._planes[tailNumber] if tailNumber in self._planes else None
-
-    def __repr__(self):
-        """Get the representation of the fleet object."""
-        return self._planes.__repr__()
-        
-#------------------------------------------------------------------------------
-
-class NOTAM(object):
-    """A NOTAM for an airport."""
-    def __init__(self, begin, notice, end = None, permanent = False,
-                 repeatCycle = None):
-        """Construct the NOTAM."""
-        self.begin = begin
-        self.notice = notice
-        self.end = end
-        self.permanent = permanent
-        self.repeatCycle = None
-
-    def __repr__(self):
-        """Get the representation of the NOTAM."""
-        s = "<NOTAM " + str(self.begin)
-        if self.end:
-            s += " - " + str(self.end)
-        elif self.permanent:
-            s += " - PERMANENT"
-        if self.repeatCycle:
-            s += " (" + self.repeatCycle + ")"
-        s += ": " + self.notice
-        s += ">"
-        return s
-    
-#------------------------------------------------------------------------------
-
-class NOTAMHandler(xml.sax.handler.ContentHandler):
-    """A handler for the NOTAM database."""
-    def __init__(self, airportICAOs):
-        """Construct the handler for the airports with the given ICAO code."""
-        self._notams = {}
-        for icao in airportICAOs:
-            self._notams[icao] = []
-
-    def startElement(self, name, attrs):
-        """Start an element."""
-        if name!="notam" or \
-           "A" not in attrs or not attrs["A"] or \
-           "B" not in attrs or not attrs["B"] or \
-           "E" not in attrs or not attrs["E"]:
-            return
-        
-        icao = attrs["A"]
-        if icao not in self._notams:
-            return
-        
-        begin = datetime.datetime.strptime(attrs["B"], "%Y-%m-%d %H:%M:%S")
-
-        c = attrs["C"] if "C" in attrs else None
-        end = datetime.datetime.strptime(c, "%Y-%m-%d %H:%M:%S") if c else None
-        
-        permanent = attrs["C_flag"]=="PERM" if "C_flag" in attrs else False
-        
-        repeatCycle = attrs["D"] if "D" in attrs else None
-
-        self._notams[icao].append(NOTAM(begin, attrs["E"], end = end,
-                                        permanent = permanent,
-                                        repeatCycle = repeatCycle))
-
-    def get(self, icao):
-        """Get the NOTAMs for the given ICAO code."""
-        return self._notams[icao] if icao in self._notams else []
-
-#------------------------------------------------------------------------------
-
-class Result(object):
-    """A result object.
-
-    An instance of this filled with the appropriate data is passed to the
-    callback function on each request."""
-
-    def __repr__(self):
-        """Get a representation of the result."""
-        s = "<Result:"
-        for (key, value) in self.__dict__.iteritems():
-            s += " " + key + "=" + unicode(value)
-        s += ">"
-        return s
-
-#------------------------------------------------------------------------------
-
-class Request(object):
-    """Base class for requests.
-
-    It handles any exceptions and the calling of the callback.
-
-    If an exception occurs during processing, the callback is called with
-    the two parameters: a boolean value of False, and the exception object.
-
-    If no exception occurs, the callback is called with True and the return
-    value of the run() function.
-
-    If the callback function throws an exception, that is caught and logged
-    to the debug log."""
-    def __init__(self, callback):
-        """Construct the request."""
-        self._callback = callback
-
-    def perform(self):
-        """Perform the request.
-
-        The object's run() function is called. If it throws an exception,
-        the callback is called with False, and the exception. Otherwise the
-        callback is called with True and the return value of the run()
-        function. Any exceptions thrown by the callback are caught and
-        reported."""
-        try:
-            result = self.run()
-            returned = True
-        except Exception, e:
-            result = e
-            returned = False
-
-        try:
-            self._callback(returned, result)
-        except Exception, e:
-            print >> sys.stderr, "web.Handler.Request.perform: callback throwed an exception: " + str(e)
-
-#------------------------------------------------------------------------------
-
-class Login(Request):
-    """A login request."""
-    iso88592decoder = codecs.getdecoder("iso-8859-2")
-    
-    def __init__(self, callback, pilotID, password):
-        """Construct the login request with the given pilot ID and
-        password."""
-        super(Login, self).__init__(callback)
-
-        self._pilotID = pilotID
-        self._password = password
-
-    def run(self):
-        """Perform the login request."""
-        md5 = hashlib.md5()
-        md5.update(self._pilotID)
-        pilotID = md5.hexdigest()
-        
-        md5 = hashlib.md5()
-        md5.update(self._password)
-        password = md5.hexdigest()
-
-        url = "http://www.virtualairlines.hu/leker2.php?pid=%s&psw=%s" % \
-              (pilotID, password)
-
-        result = Result()
-
-        f = urllib2.urlopen(url)
-
-        status = readline(f)
-        result.loggedIn = status == ".OK."
-
-        if result.loggedIn:
-            result.pilotName = self.iso88592decoder(readline(f))[0]
-            result.exams = readline(f)
-            result.flights = []
-
-            while True:
-                line = readline(f)
-                if not line or line == "#ENDPIREP": break
-
-                flight = BookedFlight(line, f)
-                result.flights.append(flight)
-
-            result.flights.sort(cmp = lambda flight1, flight2:
-                                cmp(flight1.departureTime,
-                                    flight2.departureTime))
-
-        f.close()
-
-        return result
-        
-#------------------------------------------------------------------------------
-
-class GetFleet(Request):
-    """Request to get the fleet from the website."""
-    
-    def __init__(self, callback):
-        """Construct the fleet request."""
-        super(GetFleet, self).__init__(callback)
-
-    def run(self):
-        """Perform the login request."""
-        url = "http://www.virtualairlines.hu/onlinegates_get.php"
-
-        f = urllib2.urlopen(url)
-        result = Result()
-        result.fleet = Fleet(f)
-        f.close()
-        
-        return result
-
-#------------------------------------------------------------------------------
-
-class UpdatePlane(Request):
-    """Update the status of one of the planes in the fleet."""
-    def __init__(self, callback, tailNumber, status, gateNumber = None):
-        """Construct the request."""
-        super(UpdatePlane, self).__init__(callback)
-        self._tailNumber = tailNumber
-        self._status = status
-        self._gateNumber = gateNumber
-
-    def run(self):
-        """Perform the plane update."""
-        url = "http://www.virtualairlines.hu/onlinegates_set.php"
-
-        status = "H" if self._status==const.PLANE_HOME else \
-                 "A" if self._status==const.PLANE_AWAY else \
-                 "P" if self._status==const.PLANE_PARKING else ""
-
-        gateNumber = self._gateNumber if self._gateNumber else ""
-
-        data = urllib.urlencode([("lajstrom", self._tailNumber),
-                                 ("status", status),
-                                 ("kapu", gateNumber)])
-        
-        f = urllib2.urlopen(url, data)
-        line = readline(f)
-        
-        result = Result()
-        result.success = line == "OK"
-
-        return result
-            
-#------------------------------------------------------------------------------
-
-class GetNOTAMs(Request):
-    """Get the NOTAMs from EURoutePro and select the ones we are interested
-    in."""
-    def __init__(self, callback, departureICAO, arrivalICAO):
-        """Construct the request for the given airports."""
-        super(GetNOTAMs, self).__init__(callback)
-        self._departureICAO = departureICAO
-        self._arrivalICAO = arrivalICAO
-
-    def run(self):
-        """Perform the retrieval of the NOTAMs."""
-        xmlParser = xml.sax.make_parser()
-        notamHandler = NOTAMHandler([self._departureICAO, self._arrivalICAO])
-        xmlParser.setContentHandler(notamHandler)
-
-        url = "http://notams.euroutepro.com/notams.xml"
-
-        f = urllib2.urlopen(url)
-        try:
-            xmlParser.parse(f)
-        finally:
-            f.close()
-
-        result = Result()
-        result.departureNOTAMs = notamHandler.get(self._departureICAO)
-        result.arrivalNOTAMs = notamHandler.get(self._arrivalICAO)
-
-        return result
-
-#------------------------------------------------------------------------------
-
-class GetMETARs(Request):
-    """Get the METARs from the NOAA website for certain airport ICAOs."""    
-
-    def __init__(self, callback, airports):
-        """Construct the request for the given airports."""
-        super(GetMETARs, self).__init__(callback)
-        self._airports = airports
-
-    def run(self):
-        """Perform the retrieval opf the METARs."""
-        url = "http://www.aviationweather.gov/adds/dataserver_current/httpparam?"
-        data = urllib.urlencode([ ("dataSource" , "metars"),
-                                  ("requestType",  "retrieve"), 
-                                  ("format", "csv"),
-                                  ("stationString", " ".join(self._airports)),
-                                  ("hoursBeforeNow", "24"),
-                                  ("mostRecentForEachStation", "constraint")])
-        url += data
-        f = urllib2.urlopen(url)
-        try:
-            result = Result()
-            result.metars = {}
-            for line in iter(f.readline, ""):
-                if len(line)>5 and line[4]==' ':
-                    icao = line[0:4]
-                    if icao in self._airports:                        
-                        result.metars[icao] = line.strip().split(",")[0]
-        finally:
-            f.close()
-
-        return result
-
-#------------------------------------------------------------------------------
-
-class Handler(threading.Thread):
-    """The handler for the web services.
-
-    It can process one request at a time. The results are passed to a callback
-    function."""
-    def __init__(self):
-        """Construct the handler."""
-        super(Handler, self).__init__()
-
-        self._requests = []
-        self._requestCondition = threading.Condition()
-
-        self.daemon = True
-
-    def login(self, callback, pilotID, password):
-        """Enqueue a login request."""
-        self._addRequest(Login(callback, pilotID, password))
-
-    def getFleet(self, callback):
-        """Enqueue a fleet retrieval request."""
-        self._addRequest(GetFleet(callback))
-        
-    def updatePlane(self, callback, tailNumber, status, gateNumber = None):
-        """Update the status of the given plane."""        
-        self._addRequest(UpdatePlane(callback, tailNumber, status, gateNumber))
-
-    def getNOTAMs(self, callback, departureICAO, arrivalICAO):
-        """Get the NOTAMs for the given two airports."""
-        self._addRequest(GetNOTAMs(callback, departureICAO, arrivalICAO))
-        
-    def getMETARs(self, callback, airports):
-        """Get the METARs for the given airports."""
-        self._addRequest(GetMETARs(callback, airports))
-        
-    def run(self):
-        """Process the requests."""
-        while True:
-            with self._requestCondition:
-                while not self._requests:
-                    self._requestCondition.wait()
-                request = self._requests[0]
-                del self._requests[0]
-
-            request.perform()
-    
-    def _addRequest(self, request):
-        """Add the given request to the queue."""
-        with self._requestCondition:
-            self._requests.append(request)
-            self._requestCondition.notify()
-
-#------------------------------------------------------------------------------
-
-if __name__ == "__main__":
-    import time
-    
-    def callback(returned, result):
-        print returned, unicode(result)
-        
-    handler = Handler()
-    handler.start()
-
-    #handler.login(callback, "P096", "V5fwj")
-    #handler.getFleet(callback)
-    # Plane: HA-LEG home (gate 67)
-    #handler.updatePlane(callback, "HA-LQC", const.PLANE_AWAY, "72")
-    #time.sleep(3)    
-    #handler.getFleet(callback)
-    #time.sleep(3)
-
-    #handler.getNOTAMs(callback, "LHBP", "EPWA")
-    handler.getMETARs(callback, ["LHBP", "EPWA"])
-    time.sleep(5)
-    
-
-#------------------------------------------------------------------------------
